Compare commits

...

8 Commits

Author SHA1 Message Date
Ben Olden-Cooligan
1d69f4852d WinForms: Fix dark mode link color 2024-08-31 14:20:43 -07:00
Ben Olden-Cooligan
774efbbd54 Create ColorScheme property on EtoPlatform 2024-08-31 14:12:48 -07:00
Ben Olden-Cooligan
5b3d5607e8 WinForms: Fix ellipsized page numbers 2024-08-31 13:59:36 -07:00
Ben Olden-Cooligan
67e17ee2cb WinForms: Dark mode
#109
2024-08-31 13:59:20 -07:00
Ben Olden-Cooligan
11f3dad8b3 Set DesignerSerializationVisibility on control properties 2024-08-31 12:52:15 -07:00
Ben Olden-Cooligan
abff5c5cb8 Set natural size for profiles listview 2024-08-31 12:51:54 -07:00
Ben Olden-Cooligan
c5b0703e15 Fix undisposed surface 2024-08-31 12:33:38 -07:00
Ben Olden-Cooligan
b78bae06d3 Unify layout scaling 2024-08-31 12:33:28 -07:00
18 changed files with 97 additions and 57 deletions

View File

@ -19,6 +19,7 @@ public class GtkEtoPlatform : EtoPlatform
public override bool IsGtk => true;
public override IIconProvider IconProvider { get; } = new DefaultIconProvider();
public override IDarkModeProvider DarkModeProvider { get; } = new GtkDarkModeProvider();
public override Application CreateApplication()
{

View File

@ -25,7 +25,7 @@ public static class GtkEtoExtensions
// physical size, which will look too big and blurry. To get an actual crisp image rendered at 32x32 logical
// pixels and 64x64 physical pixels, we first create a surface with a 2x scale factor and then create the
// Gtk.Image from that.
var surface = Gdk.CairoHelper.SurfaceCreateFromPixbuf(pixbuf, scaleFactor, null);
using var surface = Gdk.CairoHelper.SurfaceCreateFromPixbuf(pixbuf, scaleFactor, null);
return new Image(surface);
}

View File

@ -15,6 +15,7 @@ public class MacEtoPlatform : EtoPlatform
public override bool IsMac => true;
public override IIconProvider IconProvider { get; } = new MacIconProvider(new DefaultIconProvider());
public override IDarkModeProvider DarkModeProvider { get; } = new MacDarkModeProvider();
public override void InitializePlatform()
{
@ -102,6 +103,8 @@ public class MacEtoPlatform : EtoPlatform
control.ToNative().RemoveFromSuperview();
}
public override float GetScaleFactor(Window window) => 2f;
public override Control AccessibleImageButton(Image image, String text, Action onClick,
int xOffset = 0, int yOffset = 0)
{

View File

@ -35,8 +35,6 @@ public class MacDesktopForm : DesktopForm
thumbnailController, thumbnailProvider, desktopController, desktopScanController, imageListActions,
imageListViewBehavior, desktopFormProvider, desktopSubFormController, commands, sidebar, iconProvider)
{
// For retina screens
_thumbnailController.Oversample = 2.0;
}
protected override void UpdateTitle(ScanProfile? defaultProfile)

View File

@ -51,6 +51,21 @@ public class WinFormsDesktopForm : DesktopForm
// TODO: Remove this if https://github.com/picoe/Eto/issues/2601 is fixed
NativeListView.KeyDown += (_, e) => OnKeyDown(new KeyEventArgs(e.KeyData.ToEto(), KeyEventType.KeyDown));
Load += (_, _) => colorScheme.ColorSchemeChanged += ColorSchemeChanged;
UnLoad += (_, _) => colorScheme.ColorSchemeChanged -= ColorSchemeChanged;
}
private void ColorSchemeChanged(object? sender, EventArgs e)
{
Invoker.Current.InvokeDispatch(() =>
{
if (WF.Application.OpenForms.Count == 1)
{
// Reload the form as WinForms dark mode doesn't dynamically switch everything
SetCulture(Config.Get(c => c.Culture) ?? "en");
}
});
}
protected override void OnClosing(CancelEventArgs e)

View File

@ -3,16 +3,13 @@ using Microsoft.Win32;
namespace NAPS2.EtoForms.WinForms;
public class WinFormsDarkModeProvider : IDarkModeProvider, IMessageFilter
public class WinFormsDarkModeProvider : IDarkModeProvider
{
private const int WM_SETTINGCHANGE = 0x1A;
private const int WM_REFLECT = 0x2000;
private bool? _value;
public WinFormsDarkModeProvider()
{
Application.AddMessageFilter(this);
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
}
public bool IsDarkModeEnabled => _value ??= ReadDarkMode();
@ -33,15 +30,17 @@ public class WinFormsDarkModeProvider : IDarkModeProvider, IMessageFilter
public event EventHandler? DarkModeChanged;
public bool PreFilterMessage(ref Message m)
private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
if (m.Msg is WM_SETTINGCHANGE or (WM_SETTINGCHANGE | WM_REFLECT))
var newValue = ReadDarkMode();
if (newValue != _value)
{
// 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;
_value = newValue;
// WinForms dark mode is experimental
#pragma warning disable WFO5001
Application.SetColorMode(newValue ? SystemColorMode.Dark : SystemColorMode.Classic);
#pragma warning restore WFO5001
DarkModeChanged?.Invoke(this, EventArgs.Empty);
}
return false;
}
}

View File

@ -24,12 +24,17 @@ public class WinFormsEtoPlatform : EtoPlatform
public override bool IsWinForms => true;
public override IIconProvider IconProvider { get; } = new DefaultIconProvider();
public override IDarkModeProvider DarkModeProvider { get; } = new WinFormsDarkModeProvider();
public override Application CreateApplication()
{
WF.Application.EnableVisualStyles();
WF.Application.SetCompatibleTextRenderingDefault(false);
WF.Application.SetHighDpiMode(WF.HighDpiMode.PerMonitorV2);
// WinForms dark mode is experimental
#pragma warning disable WFO5001
WF.Application.SetColorMode(WF.SystemColorMode.System);
#pragma warning restore WFO5001
return new Application(Eto.Platforms.WinForms);
}
@ -265,6 +270,8 @@ public class WinFormsEtoPlatform : EtoPlatform
public override float GetScaleFactor(Window window) => window.ToNative().DeviceDpi / 96f;
public override bool ScaleLayout => true;
public override void SetImageSize(ButtonMenuItem menuItem, int size)
{
var handler = (ButtonMenuItemHandler) menuItem.Handler;
@ -287,7 +294,7 @@ public class WinFormsEtoPlatform : EtoPlatform
var wfButton = (WF.Button) button.ToNative();
wfButton.AccessibleName = button.Text;
wfButton.Text = "";
wfButton.BackColor = SD.Color.White;
wfButton.BackColor = ColorScheme.BackgroundColor.ToSD();
wfButton.FlatStyle = WF.FlatStyle.Flat;
}

View File

@ -10,12 +10,12 @@ namespace NAPS2.EtoForms.WinForms;
public class WinFormsListView<T> : IListView<T> where T : notnull
{
private static readonly Pen DefaultPen = new(Color.Black, 1);
private Pen DefaultPen => new(_behavior.ColorScheme.ForegroundColor.ToSD(), 1);
private static readonly Pen BasicSelectionPen = new(Color.FromArgb(0x60, 0xa0, 0xe8), 3);
private const int PageNumberTextPadding = 6;
private const int PageNumberSelectionPadding = 3;
private static readonly SolidBrush PageNumberOutlineBrush = new(Color.FromArgb(0x60, 0xa0, 0xe8));
private static readonly SolidBrush PageNumberSelectionBrush = new(Color.FromArgb(0xcc, 0xe8, 0xff));
private SolidBrush PageNumberOutlineBrush => new(_behavior.ColorScheme.HighlightBorderColor.ToSD());
private SolidBrush PageNumberSelectionBrush => new(_behavior.ColorScheme.HighlightBackgroundColor.ToSD());
private static readonly StringFormat PageNumberLabelFormat = new()
{ Alignment = StringAlignment.Center, Trimming = StringTrimming.EllipsisCharacter };
@ -141,12 +141,13 @@ public class WinFormsListView<T> : IListView<T> where T : notnull
e.Graphics.DrawImage(image, new Rectangle(x, y, width, height));
// Draw the text below the image
var drawBrush = Brushes.Black;
var drawBrush = new SolidBrush(_behavior.ColorScheme.ForegroundColor.ToSD());
float x1 = x + width / 2f;
float y1 = y + height + tp;
RectangleF labelRect = new(x1, y1, 0, textSize.Height);
float maxLabelWidth = Math.Min(textSize.Width, e.Bounds.Width - 2 * tp);
labelRect.Inflate(maxLabelWidth / 2, 0);
labelRect.Width += 2;
e.Graphics.DrawString(label, _view.Font, drawBrush, labelRect, PageNumberLabelFormat);
// Draw unselected border

View File

@ -15,8 +15,7 @@ public class WinFormsModule : GuiModule
builder.RegisterType<WindowsApplicationLifecycle>().As<ApplicationLifecycle>();
builder.RegisterType<PrintDocumentPrinter>().As<IScannedImagePrinter>();
// TODO: Change this when implementing dark mode on Windows
builder.RegisterType<StubDarkModeProvider>().As<IDarkModeProvider>().SingleInstance();
builder.RegisterType<WinFormsDarkModeProvider>().As<IDarkModeProvider>().SingleInstance();
builder.RegisterType<WindowsServiceManager>().As<IOsServiceManager>().SingleInstance();
builder.RegisterType<WinFormsDesktopForm>().As<DesktopForm>();

View File

@ -11,15 +11,21 @@ public class ToolStripDoubleButton : ToolStripButton
public event EventHandler? FirstClick;
public event EventHandler? SecondClick;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image? FirstImage { get; set; }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image? SecondImage { get; set; }
[Localizable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public required string FirstText { get; init; }
[Localizable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public required string SecondText { get; init; }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int MaxTextWidth { get; init; }
public override Size GetPreferredSize(Size constrainingSize)

View File

@ -8,6 +8,10 @@ public class ColorScheme
private static readonly Color MidGray = Color.FromRgb(0x606060);
private static readonly Color LightGray = Color.FromRgb(0xdddddd);
private static readonly Color HighlightBlue = Color.FromRgb(0x007bff);
private static readonly Color MidBlue = Color.FromRgb(0x60a0e8);
private static readonly Color PaleBlue = Color.FromRgb(0xcce8ff);
private static readonly Color DarkGrayBlue = Color.FromRgb(0x28445b);
private static readonly Color DarkOutlineBlue = Color.FromRgb(0x0078d4);
private readonly IDarkModeProvider _darkModeProvider;
@ -29,9 +33,15 @@ public class ColorScheme
public Color CropColor => DarkMode ? HighlightBlue : Colors.Black;
public Color HighlightBorderColor => DarkMode ? DarkOutlineBlue : MidBlue;
public Color HighlightBackgroundColor => DarkMode ? DarkGrayBlue : PaleBlue;
public Color NotificationBackgroundColor => DarkMode ? Color.FromRgb(0x323232) : Color.FromRgb(0xf2f2f2);
public Color NotificationBorderColor => DarkMode ? Color.FromRgb(0x606060) : Color.FromRgb(0xb2b2b2);
public Color LinkColor => DarkMode ? Color.FromRgb(0x60cdff) : Color.FromRgb(0x0000ff);
public event EventHandler? ColorSchemeChanged;
}

View File

@ -15,11 +15,18 @@ public abstract class EtoPlatform
set => _current = value ?? throw new ArgumentNullException(nameof(value));
}
protected EtoPlatform()
{
ColorScheme = new ColorScheme(DarkModeProvider);
}
public virtual bool IsGtk => false;
public virtual bool IsMac => false;
public virtual bool IsWinForms => false;
public abstract IIconProvider IconProvider { get; }
public abstract IDarkModeProvider DarkModeProvider { get; }
public ColorScheme ColorScheme { get; }
public abstract Application CreateApplication();
public abstract IListView<T> CreateListView<T>(ListViewBehavior<T> behavior) where T : notnull;
@ -131,6 +138,7 @@ public abstract class EtoPlatform
public virtual void ConfigureEllipsis(Label label)
{
// TODO: Maybe implement our own ellipsis logic that uses text-measuring to strip trailing characters and add "..."?
}
public virtual Bitmap? ExtractAssociatedIcon(string exePath) => throw new NotSupportedException();
@ -152,6 +160,8 @@ public abstract class EtoPlatform
public virtual float GetScaleFactor(Window window) => 1;
public virtual bool ScaleLayout => false;
public virtual void SetImageSize(ButtonMenuItem menuItem, int size)
{
}

View File

@ -15,22 +15,6 @@ public static class C
public static Label NoWrap(string text) =>
new Label { Text = text, Wrap = WrapMode.None };
/// <summary>
/// Creates a link button with the given URL as both text and click action.
/// </summary>
/// <param name="url"></param>
/// <param name="label"></param>
/// <returns></returns>
public static LinkButton UrlLink(string url, string? label = null)
{
void OnClick() => ProcessHelper.OpenUrl(url);
return new LinkButton
{
Text = label ?? url,
Command = new ActionCommand(OnClick)
};
}
/// <summary>
/// Creates a link button with the specified text.
/// </summary>
@ -38,7 +22,13 @@ public static class C
/// <returns></returns>
public static LinkButton Link(string text)
{
return new LinkButton { Text = text };
var link = new LinkButton { Text = text };
if (EtoPlatform.Current.IsWinForms)
{
// TODO: Remove this when https://github.com/dotnet/winforms/issues/11935 is fixed
link.TextColor = EtoPlatform.Current.ColorScheme.LinkColor;
}
return link;
}
/// <summary>
@ -49,11 +39,21 @@ public static class C
/// <returns></returns>
public static LinkButton Link(string text, Action onClick)
{
return new LinkButton
{
Text = text,
Command = new ActionCommand(onClick)
};
var link = Link(text);
link.Command = new ActionCommand(onClick);
return link;
}
/// <summary>
/// Creates a link button with the given URL as both text and click action.
/// </summary>
/// <param name="url"></param>
/// <param name="label"></param>
/// <returns></returns>
public static LinkButton UrlLink(string url, string? label = null)
{
void OnClick() => ProcessHelper.OpenUrl(url);
return Link(label ?? url, OnClick);
}
/// <summary>

View File

@ -132,7 +132,7 @@ public class LayoutController
DefaultSpacing = DefaultSpacing,
DefaultLabelSpacing = DefaultLabelSpacing,
Invalidate = Invalidate,
Scale = EtoPlatform.Current.GetScaleFactor(_window!)
Scale = EtoPlatform.Current.ScaleLayout ? EtoPlatform.Current.GetScaleFactor(_window!) : 1f
};
}

View File

@ -10,8 +10,8 @@ public class LinkNotificationView : NotificationView
private readonly string? _folderTarget;
private readonly Label _label = new();
private readonly LinkButton _link = new();
private readonly ContextMenu _contextMenu = new();
private readonly LinkButton _link;
protected LinkNotificationView(
NotificationModel model, string title, string linkLabel, string? linkTarget, string? folderTarget)
@ -19,7 +19,7 @@ public class LinkNotificationView : NotificationView
{
_label.Text = title;
_label.Font = new Font(_label.Font.Family, _label.Font.Size, FontStyle.Bold);
_link.Text = linkLabel;
_link = C.Link(linkLabel);
_linkTarget = linkTarget;
_folderTarget = folderTarget;

View File

@ -136,15 +136,6 @@ public abstract class DesktopForm : EtoFormBase
).Padding(8)
).Scale()
);
UpdateColors();
// TODO: Memory leak?
_colorScheme.ColorSchemeChanged += (_, _) => UpdateColors();
}
protected virtual void UpdateColors()
{
// TODO: Do something here or in inheritors?
}
private void OpeningContextMenu(object? sender, EventArgs e)

View File

@ -27,7 +27,7 @@ public class EditProfileForm : EtoDialogBase
private readonly EnumDropDownWidget<ScanHorizontalAlign> _horAlign = new();
private readonly EnumDropDownWidget<ScanScale> _scale = new();
private readonly CheckBox _enableAutoSave = new() { Text = UiStrings.EnableAutoSave };
private readonly LinkButton _autoSaveSettings = new() { Text = UiStrings.AutoSaveSettings };
private readonly LinkButton _autoSaveSettings = C.Link(UiStrings.AutoSaveSettings);
private readonly Button _advanced = new() { Text = UiStrings.Advanced };
private readonly SliderWithTextBox _brightnessSlider = new();
private readonly SliderWithTextBox _contrastSlider = new();

View File

@ -139,7 +139,7 @@ public class ProfilesForm : EtoDialogBase
LayoutController.Content = L.Column(
L.Row(
_listView.Control.Scale(),
_listView.Control.NaturalSize(150, 100).Scale(),
C.Button(_scanCommand, "control_play_blue", ButtonImagePosition.Above, ButtonFlags.LargeIcon)
.Height(80)
).Aligned().Scale(),