diff --git a/NAPS2.Lib.Gtk/EtoForms/Gtk/GtkDarkModeProvider.cs b/NAPS2.Lib.Gtk/EtoForms/Gtk/GtkDarkModeProvider.cs new file mode 100644 index 000000000..0d8e19d56 --- /dev/null +++ b/NAPS2.Lib.Gtk/EtoForms/Gtk/GtkDarkModeProvider.cs @@ -0,0 +1,8 @@ +namespace NAPS2.EtoForms.Gtk; + +public class GtkDarkModeProvider : IDarkModeProvider +{ + public bool IsDarkModeEnabled { get; } + + public event EventHandler? DarkModeChanged; +} \ No newline at end of file diff --git a/NAPS2.Lib.Gtk/EtoForms/Ui/GtkDesktopForm.cs b/NAPS2.Lib.Gtk/EtoForms/Ui/GtkDesktopForm.cs index a201967f3..800b6e71a 100644 --- a/NAPS2.Lib.Gtk/EtoForms/Ui/GtkDesktopForm.cs +++ b/NAPS2.Lib.Gtk/EtoForms/Ui/GtkDesktopForm.cs @@ -5,6 +5,7 @@ using Gdk; using Gtk; using NAPS2.EtoForms.Desktop; using NAPS2.EtoForms.Gtk; +using NAPS2.EtoForms.Widgets; using NAPS2.ImportExport.Images; using Command = Eto.Forms.Command; @@ -24,6 +25,7 @@ public class GtkDesktopForm : DesktopForm DesktopKeyboardShortcuts keyboardShortcuts, INotificationManager notify, CultureHelper cultureHelper, + ColorScheme colorScheme, IProfileManager profileManager, UiImageList imageList, ImageTransfer imageTransfer, @@ -32,12 +34,13 @@ public class GtkDesktopForm : DesktopForm DesktopController desktopController, IDesktopScanController desktopScanController, ImageListActions imageListActions, + ImageListViewBehavior imageListViewBehavior, DesktopFormProvider desktopFormProvider, IDesktopSubFormController desktopSubFormController, DesktopCommands commands) - : base(config, keyboardShortcuts, notify, cultureHelper, profileManager, + : base(config, keyboardShortcuts, notify, cultureHelper, colorScheme, profileManager, imageList, imageTransfer, thumbnailController, thumbnailProvider, desktopController, desktopScanController, - imageListActions, desktopFormProvider, desktopSubFormController, commands) + imageListActions, imageListViewBehavior, desktopFormProvider, desktopSubFormController, commands) { var cssProvider = new CssProvider(); cssProvider.LoadFromData(@" diff --git a/NAPS2.Lib.Gtk/Modules/GtkModule.cs b/NAPS2.Lib.Gtk/Modules/GtkModule.cs index 5b5fffa3b..9c94fb159 100644 --- a/NAPS2.Lib.Gtk/Modules/GtkModule.cs +++ b/NAPS2.Lib.Gtk/Modules/GtkModule.cs @@ -16,6 +16,7 @@ public class GtkModule : GuiModule builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().AsSelf(); diff --git a/NAPS2.Lib.Mac/EtoForms/Mac/MacDarkModeProvider.cs b/NAPS2.Lib.Mac/EtoForms/Mac/MacDarkModeProvider.cs new file mode 100644 index 000000000..84c5728d9 --- /dev/null +++ b/NAPS2.Lib.Mac/EtoForms/Mac/MacDarkModeProvider.cs @@ -0,0 +1,8 @@ +namespace NAPS2.EtoForms.Mac; + +public class MacDarkModeProvider : IDarkModeProvider +{ + public bool IsDarkModeEnabled { get; } + + public event EventHandler? DarkModeChanged; +} \ No newline at end of file diff --git a/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs b/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs index d269b22f9..f7c9c1b47 100644 --- a/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs +++ b/NAPS2.Lib.Mac/EtoForms/Ui/MacDesktopForm.cs @@ -3,6 +3,7 @@ using Eto.Forms; using NAPS2.EtoForms.Desktop; using NAPS2.EtoForms.Layout; using NAPS2.EtoForms.Mac; +using NAPS2.EtoForms.Widgets; using NAPS2.ImportExport.Images; using NAPS2.Scan; @@ -15,6 +16,7 @@ public class MacDesktopForm : DesktopForm DesktopKeyboardShortcuts keyboardShortcuts, INotificationManager notify, CultureHelper cultureHelper, + ColorScheme colorScheme, IProfileManager profileManager, UiImageList imageList, ImageTransfer imageTransfer, @@ -23,12 +25,13 @@ public class MacDesktopForm : DesktopForm DesktopController desktopController, IDesktopScanController desktopScanController, ImageListActions imageListActions, + ImageListViewBehavior imageListViewBehavior, DesktopFormProvider desktopFormProvider, IDesktopSubFormController desktopSubFormController, DesktopCommands commands) - : base(config, keyboardShortcuts, notify, cultureHelper, profileManager, + : base(config, keyboardShortcuts, notify, cultureHelper, colorScheme, profileManager, imageList, imageTransfer, thumbnailController, thumbnailProvider, desktopController, desktopScanController, - imageListActions, desktopFormProvider, desktopSubFormController, commands) + imageListActions, imageListViewBehavior, desktopFormProvider, desktopSubFormController, commands) { // For retina screens _thumbnailController.Oversample = 2.0; diff --git a/NAPS2.Lib.Mac/Modules/MacModule.cs b/NAPS2.Lib.Mac/Modules/MacModule.cs index df5f25d6e..5b8389384 100644 --- a/NAPS2.Lib.Mac/Modules/MacModule.cs +++ b/NAPS2.Lib.Mac/Modules/MacModule.cs @@ -17,6 +17,7 @@ public class MacModule : GuiModule builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().AsSelf(); builder.RegisterType().As(); diff --git a/NAPS2.Lib.WinForms/EtoForms/Ui/WinFormsDesktopForm.cs b/NAPS2.Lib.WinForms/EtoForms/Ui/WinFormsDesktopForm.cs index 4ceb33c09..55f00d16b 100644 --- a/NAPS2.Lib.WinForms/EtoForms/Ui/WinFormsDesktopForm.cs +++ b/NAPS2.Lib.WinForms/EtoForms/Ui/WinFormsDesktopForm.cs @@ -5,6 +5,7 @@ using Eto.WinForms; using Eto.WinForms.Forms.ToolBar; using NAPS2.EtoForms.Desktop; using NAPS2.EtoForms.Layout; +using NAPS2.EtoForms.Widgets; using NAPS2.EtoForms.WinForms; using NAPS2.ImportExport.Images; using NAPS2.WinForms; @@ -27,6 +28,7 @@ public class WinFormsDesktopForm : DesktopForm DesktopKeyboardShortcuts keyboardShortcuts, INotificationManager notify, CultureHelper cultureHelper, + ColorScheme colorScheme, IProfileManager profileManager, UiImageList imageList, ImageTransfer imageTransfer, @@ -35,12 +37,13 @@ public class WinFormsDesktopForm : DesktopForm DesktopController desktopController, IDesktopScanController desktopScanController, ImageListActions imageListActions, + ImageListViewBehavior imageListViewBehavior, DesktopFormProvider desktopFormProvider, IDesktopSubFormController desktopSubFormController, DesktopCommands commands) - : base(config, keyboardShortcuts, notify, cultureHelper, profileManager, + : base(config, keyboardShortcuts, notify, cultureHelper, colorScheme, profileManager, imageList, imageTransfer, thumbnailController, thumbnailProvider, desktopController, desktopScanController, - imageListActions, desktopFormProvider, desktopSubFormController, commands) + imageListActions, imageListViewBehavior, desktopFormProvider, desktopSubFormController, commands) { _form = this.ToNative(); _form.FormClosing += OnFormClosing; diff --git a/NAPS2.Lib.WinForms/EtoForms/WinForms/WinFormsDarkModeProvider.cs b/NAPS2.Lib.WinForms/EtoForms/WinForms/WinFormsDarkModeProvider.cs new file mode 100644 index 000000000..9440cf564 --- /dev/null +++ b/NAPS2.Lib.WinForms/EtoForms/WinForms/WinFormsDarkModeProvider.cs @@ -0,0 +1,47 @@ +using System.Windows.Forms; +using Microsoft.Win32; + +namespace NAPS2.EtoForms.WinForms; + +public class WinFormsDarkModeProvider : IDarkModeProvider, IMessageFilter +{ + private const int WM_SETTINGCHANGE = 0x1A; + private const int WM_REFLECT = 0x2000; + + private bool? _value; + + public WinFormsDarkModeProvider() + { + Application.AddMessageFilter(this); + } + + public bool IsDarkModeEnabled => _value ??= ReadDarkMode(); + + private bool ReadDarkMode() + { + try + { + using var key = + Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize"); + return Equals(key?.GetValue("AppsUseLightTheme"), 0); + } + catch (Exception) + { + return false; + } + } + + public event EventHandler? DarkModeChanged; + + public bool PreFilterMessage(ref Message m) + { + if (m.Msg is WM_SETTINGCHANGE or (WM_SETTINGCHANGE | WM_REFLECT)) + { + // TODO: Maybe we can narrow down the changed setting based on lParam? + // https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-settingchange + _value = null; + DarkModeChanged?.Invoke(this, EventArgs.Empty); + } + return false; + } +} \ No newline at end of file diff --git a/NAPS2.Lib.WinForms/Modules/WinFormsModule.cs b/NAPS2.Lib.WinForms/Modules/WinFormsModule.cs index 15f7d420c..5de4f0da4 100644 --- a/NAPS2.Lib.WinForms/Modules/WinFormsModule.cs +++ b/NAPS2.Lib.WinForms/Modules/WinFormsModule.cs @@ -15,6 +15,7 @@ public class WinFormsModule : GuiModule builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); diff --git a/NAPS2.Lib/EtoForms/ColorScheme.cs b/NAPS2.Lib/EtoForms/ColorScheme.cs new file mode 100644 index 000000000..c5d7b0ba0 --- /dev/null +++ b/NAPS2.Lib/EtoForms/ColorScheme.cs @@ -0,0 +1,22 @@ +using Eto.Drawing; + +namespace NAPS2.EtoForms; + +public class ColorScheme +{ + private static readonly Color DarkGray = Color.FromRgb(0x262626); + + private readonly IDarkModeProvider _darkModeProvider; + + public ColorScheme(IDarkModeProvider darkModeProvider) + { + _darkModeProvider = darkModeProvider; + _darkModeProvider.DarkModeChanged += (_, _) => ColorSchemeChanged?.Invoke(this, EventArgs.Empty); + } + + public Color ForegroundColor => _darkModeProvider.IsDarkModeEnabled ? Colors.White : Colors.Black; + + public Color BackgroundColor => _darkModeProvider.IsDarkModeEnabled ? DarkGray : Colors.White; + + public event EventHandler? ColorSchemeChanged; +} \ No newline at end of file diff --git a/NAPS2.Lib/EtoForms/IDarkModeProvider.cs b/NAPS2.Lib/EtoForms/IDarkModeProvider.cs new file mode 100644 index 000000000..12684c5f7 --- /dev/null +++ b/NAPS2.Lib/EtoForms/IDarkModeProvider.cs @@ -0,0 +1,8 @@ +namespace NAPS2.EtoForms; + +public interface IDarkModeProvider +{ + bool IsDarkModeEnabled { get; } + + event EventHandler? DarkModeChanged; +} \ No newline at end of file diff --git a/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs b/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs index df74fbf99..45a641de6 100644 --- a/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/DesktopForm.cs @@ -16,6 +16,7 @@ public abstract class DesktopForm : EtoFormBase private readonly DesktopKeyboardShortcuts _keyboardShortcuts; private readonly INotificationManager _notify; private readonly CultureHelper _cultureHelper; + protected readonly ColorScheme _colorScheme; private readonly IProfileManager _profileManager; private readonly ImageTransfer _imageTransfer; protected readonly ThumbnailController _thumbnailController; @@ -38,6 +39,7 @@ public abstract class DesktopForm : EtoFormBase DesktopKeyboardShortcuts keyboardShortcuts, INotificationManager notify, CultureHelper cultureHelper, + ColorScheme colorScheme, IProfileManager profileManager, UiImageList imageList, ImageTransfer imageTransfer, @@ -46,6 +48,7 @@ public abstract class DesktopForm : EtoFormBase DesktopController desktopController, IDesktopScanController desktopScanController, ImageListActions imageListActions, + ImageListViewBehavior imageListViewBehavior, DesktopFormProvider desktopFormProvider, IDesktopSubFormController desktopSubFormController, DesktopCommands commands) : base(config) @@ -53,6 +56,7 @@ public abstract class DesktopForm : EtoFormBase _keyboardShortcuts = keyboardShortcuts; _notify = notify; _cultureHelper = cultureHelper; + _colorScheme = colorScheme; _profileManager = profileManager; ImageList = imageList; _imageTransfer = imageTransfer; @@ -71,7 +75,7 @@ public abstract class DesktopForm : EtoFormBase UpdateScanButton(); InitLanguageDropdown(); - _listView = EtoPlatform.Current.CreateListView(new ImageListViewBehavior(_thumbnailProvider, _imageTransfer)); + _listView = EtoPlatform.Current.CreateListView(imageListViewBehavior); _listView.Selection = ImageList.Selection; _listView.ItemClicked += ListViewItemClicked; _listView.Drop += ListViewDrop; @@ -120,6 +124,14 @@ public abstract class DesktopForm : EtoFormBase L.Row(GetZoomButtons(), C.Filler()) ).Padding(10) ); + + UpdateColors(); + // TODO: Memory leak? + _colorScheme.ColorSchemeChanged += (_, _) => UpdateColors(); + } + + protected virtual void UpdateColors() + { } private void OpeningContextMenu(object? sender, EventArgs e) diff --git a/NAPS2.Lib/EtoForms/Ui/OcrDownloadForm.cs b/NAPS2.Lib/EtoForms/Ui/OcrDownloadForm.cs index fcbafa360..768b6e29c 100644 --- a/NAPS2.Lib/EtoForms/Ui/OcrDownloadForm.cs +++ b/NAPS2.Lib/EtoForms/Ui/OcrDownloadForm.cs @@ -10,14 +10,15 @@ public class OcrDownloadForm : EtoDialogBase { private readonly TesseractLanguageManager _tesseractLanguageManager; - private readonly IListView _languageList = - EtoPlatform.Current.CreateListView(new OcrLanguagesListViewBehavior()); + private readonly IListView _languageList; private readonly Label _downloadSize = new(); private readonly Button _downloadButton; - public OcrDownloadForm(Naps2Config config, TesseractLanguageManager tesseractLanguageManager) : base(config) + public OcrDownloadForm(Naps2Config config, TesseractLanguageManager tesseractLanguageManager, + OcrLanguagesListViewBehavior ocrLanguagesListViewBehavior) : base(config) { _tesseractLanguageManager = tesseractLanguageManager; + _languageList = EtoPlatform.Current.CreateListView(ocrLanguagesListViewBehavior); var initialSelection = new HashSet(); // TODO: We used to select old installed languages here, maybe we could do it again if we get new lang data diff --git a/NAPS2.Lib/EtoForms/Widgets/ImageListViewBehavior.cs b/NAPS2.Lib/EtoForms/Widgets/ImageListViewBehavior.cs index 07b93d618..11376ad05 100644 --- a/NAPS2.Lib/EtoForms/Widgets/ImageListViewBehavior.cs +++ b/NAPS2.Lib/EtoForms/Widgets/ImageListViewBehavior.cs @@ -10,10 +10,12 @@ public class ImageListViewBehavior : ListViewBehavior private readonly UiThumbnailProvider _thumbnailProvider; private readonly ImageTransfer _imageTransfer; - public ImageListViewBehavior(UiThumbnailProvider thumbnailProvider, ImageTransfer imageTransfer) + public ImageListViewBehavior(UiThumbnailProvider thumbnailProvider, ImageTransfer imageTransfer, + ColorScheme colorScheme) { _thumbnailProvider = thumbnailProvider; _imageTransfer = imageTransfer; + ColorScheme = colorScheme; MultiSelect = true; ShowLabels = false; ScrollOnDrag = true; diff --git a/NAPS2.Lib/EtoForms/Widgets/ListViewBehavior.cs b/NAPS2.Lib/EtoForms/Widgets/ListViewBehavior.cs index 51e8ca106..d5a463479 100644 --- a/NAPS2.Lib/EtoForms/Widgets/ListViewBehavior.cs +++ b/NAPS2.Lib/EtoForms/Widgets/ListViewBehavior.cs @@ -5,6 +5,8 @@ namespace NAPS2.EtoForms.Widgets; public abstract class ListViewBehavior where T : notnull { + public ColorScheme ColorScheme { get; protected set; } + public bool MultiSelect { get; protected set; } public bool ShowLabels { get; protected set; } diff --git a/NAPS2.Lib/EtoForms/Widgets/OcrLanguagesListViewBehavior.cs b/NAPS2.Lib/EtoForms/Widgets/OcrLanguagesListViewBehavior.cs index 1f57be504..614909ee2 100644 --- a/NAPS2.Lib/EtoForms/Widgets/OcrLanguagesListViewBehavior.cs +++ b/NAPS2.Lib/EtoForms/Widgets/OcrLanguagesListViewBehavior.cs @@ -4,8 +4,9 @@ namespace NAPS2.EtoForms.Widgets; public class OcrLanguagesListViewBehavior : ListViewBehavior { - public OcrLanguagesListViewBehavior() + public OcrLanguagesListViewBehavior(ColorScheme colorScheme) { + ColorScheme = colorScheme; ShowLabels = true; Checkboxes = true; } diff --git a/NAPS2.Lib/EtoForms/Widgets/ProfileListViewBehavior.cs b/NAPS2.Lib/EtoForms/Widgets/ProfileListViewBehavior.cs index c27bcde41..ecafdbd6d 100644 --- a/NAPS2.Lib/EtoForms/Widgets/ProfileListViewBehavior.cs +++ b/NAPS2.Lib/EtoForms/Widgets/ProfileListViewBehavior.cs @@ -9,9 +9,10 @@ public class ProfileListViewBehavior : ListViewBehavior { private readonly ProfileTransfer _profileTransfer; - public ProfileListViewBehavior(ProfileTransfer profileTransfer) + public ProfileListViewBehavior(ProfileTransfer profileTransfer, ColorScheme colorScheme) { _profileTransfer = profileTransfer; + ColorScheme = colorScheme; MultiSelect = false; ShowLabels = true; ScrollOnDrag = false;