Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Setup/Assets/Language/Chinese Simplified.iglang.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"_Metadata": {
"Code": "zh-CN",
"EnglishName": "Chinese (Simplified)",
Expand Down Expand Up @@ -499,6 +499,9 @@
"FrmMain.MnuLayout": "布局",
"_._Website": "网站",
"FrmMain.MnuPasteImage": "粘贴图像",
"FrmSettings._ShowGalleryScrollbars": "显示相册滚动条"
"FrmSettings._ShowGalleryScrollbars": "显示相册滚动条",
"FrmSettings._EnableAutoSwitchSiblingDir": "当前目录图片加载完毕后自动切换到下/上一个目录",
"FrmMain._SwitchedToNextDirectory": "已切换到下一个目录:{0}",
"FrmMain._SwitchedToPrevDirectory": "已切换到上一个目录:{0}"
}
}
5 changes: 4 additions & 1 deletion Source/Components/ImageGlass.Base/Language/IgLang.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
ImageGlass Project - Image viewer for Windows
Copyright (C) 2010 - 2026 DUONG DIEU PHAP
Project homepage: https://imageglass.org
Expand Down Expand Up @@ -555,6 +555,8 @@ public Dictionary<string, string> InitDefaultLanguage()
{ "FrmMain._OpenWith", "Open with {0}" }, //v9.0
{ "FrmMain._ReachedFirstImage", "Reached the first image" }, // v4.0
{ "FrmMain._ReachedLastLast", "Reached the last image" }, // v4.0
{ "FrmMain._SwitchedToNextDirectory", "Switched to next folder: {0}" },
{ "FrmMain._SwitchedToPrevDirectory", "Switched to previous folder: {0}" },
{ "FrmMain._ClipboardImage", "Clipboard image" }, //v9.0

#endregion
Expand Down Expand Up @@ -648,6 +650,7 @@ public Dictionary<string, string> InitDefaultLanguage()
{ "FrmSettings._ShouldGroupImagesByDirectory", "Group images by directory" },
{ "FrmSettings._ShouldLoadHiddenImages", "Load hidden images" },
{ "FrmSettings._EnableLoopBackNavigation", "Loop back to the first image when reaching the end of the image list" },
{ "FrmSettings._EnableAutoSwitchSiblingDir", "Automatically switch to the next/previous sibling directory when reaching the end of the image list" },
{ "FrmSettings._ShowImagePreview", "Display image preview while it's being loaded" },
{ "FrmSettings._EnableImageAsyncLoading", "Enable image asynchronous loading" },

Expand Down
11 changes: 10 additions & 1 deletion Source/Components/ImageGlass.Settings/Config.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
ImageGlass Project - Image viewer for Windows
Copyright (C) 2010 - 2026 DUONG DIEU PHAP
Project homepage: https://imageglass.org
Expand Down Expand Up @@ -193,6 +193,13 @@ public static class Config
/// </summary>
public static bool EnableLoopBackNavigation { get; set; } = true;

/// <summary>
/// Gets, sets value indicating that ImageGlass will automatically switch
/// to the next or previous sibling directory when reaching the boundary
/// of the current image list.
/// </summary>
public static bool EnableAutoSwitchSiblingDir { get; set; } = false;

/// <summary>
/// Gets, sets value indicating that checker board is shown or not
/// </summary>
Expand Down Expand Up @@ -708,6 +715,7 @@ public static void Load(IConfigurationRoot? items = null)
ShowFrameNavTool = items.GetValueEx(nameof(ShowFrameNavTool), ShowFrameNavTool);
ShowAppIcon = items.GetValueEx(nameof(ShowAppIcon), ShowAppIcon);
EnableLoopBackNavigation = items.GetValueEx(nameof(EnableLoopBackNavigation), EnableLoopBackNavigation);
EnableAutoSwitchSiblingDir = items.GetValueEx(nameof(EnableAutoSwitchSiblingDir), EnableAutoSwitchSiblingDir);
ShowCheckerboard = items.GetValueEx(nameof(ShowCheckerboard), ShowCheckerboard);
ShowCheckerboardOnlyImageRegion = items.GetValueEx(nameof(ShowCheckerboardOnlyImageRegion), ShowCheckerboardOnlyImageRegion);
EnableMultiInstances = items.GetValueEx(nameof(EnableMultiInstances), EnableMultiInstances);
Expand Down Expand Up @@ -1080,6 +1088,7 @@ public static ExpandoObject PrepareJsonSettingsObject(bool useAbsoluteFileUrl =
_ = settings.TryAdd(nameof(ShowFrameNavTool), ShowFrameNavTool);
_ = settings.TryAdd(nameof(ShowAppIcon), ShowAppIcon);
_ = settings.TryAdd(nameof(EnableLoopBackNavigation), EnableLoopBackNavigation);
_ = settings.TryAdd(nameof(EnableAutoSwitchSiblingDir), EnableAutoSwitchSiblingDir);
_ = settings.TryAdd(nameof(ShowCheckerboard), ShowCheckerboard);
_ = settings.TryAdd(nameof(ShowCheckerboardOnlyImageRegion), ShowCheckerboardOnlyImageRegion);
_ = settings.TryAdd(nameof(EnableMultiInstances), EnableMultiInstances);
Expand Down
6 changes: 6 additions & 0 deletions Source/Components/ImageGlass.Settings/WebUI/FrmSettings.html
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@
<div lang-text="FrmSettings._EnableLoopBackNavigation">[EnableLoopBackNavigation]</div>
</label>
</div>
<div class="mb-2">
<label class="ig-checkbox">
<input type="checkbox" name="EnableAutoSwitchSiblingDir" />
<div lang-text="FrmSettings._EnableAutoSwitchSiblingDir">[EnableAutoSwitchSiblingDir]</div>
</label>
</div>
<div class="mb-2">
<label class="ig-checkbox">
<input type="checkbox" name="ShowImagePreview" />
Expand Down
127 changes: 126 additions & 1 deletion Source/ImageGlass/FrmMain.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
ImageGlass Project - Image viewer for Windows
Copyright (C) 2010 - 2026 DUONG DIEU PHAP
Project homepage: https://imageglass.org
Expand Down Expand Up @@ -43,6 +43,8 @@ public partial class FrmMain : ThemedForm
private MovableForm? _movableForm;
private FileFinder? _fileFinder = new();
string? _inputFilePath;
private bool _pendingNavigateToLast;
private string? _pendingSwitchDirMessage;


// variable to back up / restore window layout when changing window mode
Expand Down Expand Up @@ -439,6 +441,86 @@ public async Task PrepareLoadingAsync(string[] paths, string? currentFile = null
}


/// <summary>
/// Gets the next or previous sibling directory relative to the current one.
/// </summary>
/// <param name="direction">+1 for next, -1 for previous</param>
/// <returns>Full path of the sibling directory, or <c>null</c> if none exists.</returns>
private static string? GetSiblingDirectory(int direction)
{
// prefer deriving directory from the current image path
string? currentDir = null;

if (Local.CurrentIndex >= 0 && Local.CurrentIndex < Local.Images.Length)
{
var imgPath = Local.Images.GetFilePath(Local.CurrentIndex);
if (!string.IsNullOrEmpty(imgPath))
{
currentDir = Path.GetDirectoryName(imgPath);
}
}

if (string.IsNullOrEmpty(currentDir))
{
currentDir = Local.InitialInputPath;
}

if (string.IsNullOrEmpty(currentDir)) return null;

// ensure we have a directory path, not a file path
if (File.Exists(currentDir))
{
currentDir = Path.GetDirectoryName(currentDir);
if (string.IsNullOrEmpty(currentDir)) return null;
}

var parentDir = Directory.GetParent(currentDir)?.FullName;
if (string.IsNullOrEmpty(parentDir)) return null;

try
{
var siblingDirs = Directory.GetDirectories(parentDir)
.OrderBy(d => d, StringComparer.OrdinalIgnoreCase)
.ToList();

var currentIndex = siblingDirs.FindIndex(d =>
string.Equals(d, currentDir, StringComparison.OrdinalIgnoreCase));
if (currentIndex < 0) return null;

var nextIndex = currentIndex + direction;
while (nextIndex >= 0 && nextIndex < siblingDirs.Count)
{
if (DirectoryContainsImages(siblingDirs[nextIndex]))
return siblingDirs[nextIndex];
nextIndex += direction;
}
}
catch (UnauthorizedAccessException) { }
catch (IOException) { }

return null;
}


/// <summary>
/// Checks if a directory contains at least one supported image file.
/// </summary>
private static bool DirectoryContainsImages(string dirPath)
{
try
{
return Directory.EnumerateFiles(dirPath)
.Any(f =>
{
var ext = Path.GetExtension(f).ToLowerInvariant();
return ext.Length > 0 && Config.FileFormats.Contains(ext);
});
}
catch (UnauthorizedAccessException) { return false; }
catch (IOException) { return false; }
}


/// <summary>
/// Load the images list.
/// </summary>
Expand Down Expand Up @@ -700,6 +782,19 @@ private async Task ViewNextAsync(int step,
FilePath = oldImgPath,
}, nameof(Local.RaiseLastImageReachedEvent)));

if (Config.EnableAutoSwitchSiblingDir)
{
var nextDir = GetSiblingDirectory(1);
if (nextDir != null)
{
_pendingSwitchDirMessage = string.Format(
Config.Language[$"{Name}._SwitchedToNextDirectory"],
Path.GetFileName(nextDir));
PrepareLoading(nextDir, true);
return;
}
}

if (!Config.EnableLoopBackNavigation || Local.Images.Length == 1)
{
// if the image is not rendered yet
Expand All @@ -722,6 +817,19 @@ private async Task ViewNextAsync(int step,
FilePath = oldImgPath,
}, nameof(Local.RaiseFirstImageReachedEvent)));

if (Config.EnableAutoSwitchSiblingDir)
{
var prevDir = GetSiblingDirectory(-1);
if (prevDir != null)
{
_pendingSwitchDirMessage = string.Format(
Config.Language[$"{Name}._SwitchedToPrevDirectory"],
Path.GetFileName(prevDir));
_pendingNavigateToLast = true;
PrepareLoading(prevDir, true);
return;
}
}

if (!Config.EnableLoopBackNavigation || Local.Images.Length == 1)
{
Expand Down Expand Up @@ -1134,6 +1242,12 @@ private async Task HandleImageProgress_LoadedAsync(ImageLoadedEventArgs e)
LoadImageInfo(ImageInfoUpdateTypes.Dimension | ImageInfoUpdateTypes.FrameCount);


if (_pendingSwitchDirMessage != null)
{
PicMain.ShowMessage(_pendingSwitchDirMessage, Config.InAppMessageDuration);
_pendingSwitchDirMessage = null;
}

// Collect system garbage
Local.GcCollect();
}
Expand All @@ -1146,6 +1260,13 @@ private void HandleImageList_Loaded(ImageListLoadedEventArgs e)
UpdateCurrentIndex(e.InitFilePath);
}

if (_pendingNavigateToLast && Local.Images.Length > 0)
{
_pendingNavigateToLast = false;
Local.CurrentIndex = Local.Images.Length - 1;
_ = ViewNextCancellableAsync(0);
}

LoadImageInfo(ImageInfoUpdateTypes.ListCount);

// start image caching, don't cache the current index
Expand All @@ -1159,6 +1280,8 @@ private void HandleImageList_Loaded(ImageListLoadedEventArgs e)

private void HandleImage_FirstReached()
{
if (Config.EnableAutoSwitchSiblingDir) return;

if (!Config.EnableLoopBackNavigation || Local.Images.Length == 1)
{
PicMain.ShowMessage(Config.Language[$"{Name}._ReachedFirstImage"],
Expand All @@ -1169,6 +1292,8 @@ private void HandleImage_FirstReached()

private void HandleImage_LastReached()
{
if (Config.EnableAutoSwitchSiblingDir) return;

if (!Config.EnableLoopBackNavigation || Local.Images.Length == 1)
{
PicMain.ShowMessage(Config.Language[$"{Name}._ReachedLastLast"],
Expand Down
3 changes: 2 additions & 1 deletion Source/ImageGlass/Local.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
ImageGlass Project - Image viewer for Windows
Copyright (C) 2010 - 2026 DUONG DIEU PHAP
Project homepage: https://imageglass.org
Expand Down Expand Up @@ -890,6 +890,7 @@ public static void ApplySettingsFromJson(string dataJson, Action<UpdateRequests>
if (Config.SetFromJson(dict, nameof(Config.ShouldLoadHiddenImages)).Done) { reloadImgList = true; }

_ = Config.SetFromJson(dict, nameof(Config.EnableLoopBackNavigation));
_ = Config.SetFromJson(dict, nameof(Config.EnableAutoSwitchSiblingDir));
_ = Config.SetFromJson(dict, nameof(Config.ShowImagePreview));
_ = Config.SetFromJson(dict, nameof(Config.EnableImageAsyncLoading));

Expand Down