High-dpi fixes

This commit is contained in:
Ben Olden-Cooligan 2024-09-06 14:38:10 -07:00
parent 6e33494e33
commit 233c9005ce
8 changed files with 67 additions and 29 deletions

View File

@ -49,12 +49,18 @@ public class MacEtoPlatform : EtoPlatform
public override void ConfigureImageButton(Button button, ButtonFlags flags)
{
var nsButton = (NSButton) button.ToNative();
if (button.ImagePosition == ButtonImagePosition.Above)
{
var nsButton = (NSButton) button.ToNative();
nsButton.ImageHugsTitle = true;
nsButton.Title = Environment.NewLine + nsButton.Title;
}
var image = nsButton.Image;
if (image.Representations() is [NSBitmapImageRep rep, ..])
{
image.Size = new CGSize(rep.PixelsWide / 2f, rep.PixelsHigh / 2f);
nsButton.Image = image;
}
}
public override Bitmap ToBitmap(IMemoryImage image)

View File

@ -109,7 +109,8 @@ public abstract class EtoPlatform
{
}
public virtual void AttachDpiDependency(Control control, Action<float> callback) => callback(1f);
public virtual void AttachDpiDependency(Control control, Action<float> callback) =>
callback(GetScaleFactor(control.ParentWindow));
public virtual SizeF GetWrappedSize(Control control, int defaultWidth)
{

View File

@ -1,7 +1,6 @@
using System.Windows.Input;
using Eto.Drawing;
using Eto.Forms;
using NAPS2.Scan;
namespace NAPS2.EtoForms.Layout;
@ -93,7 +92,8 @@ public static class C
return Button(command, command.IconName, imagePosition, flags);
}
public static Button Button(ActionCommand command, string? iconName, ButtonImagePosition imagePosition = default, ButtonFlags flags = default)
public static Button Button(ActionCommand command, string? iconName, ButtonImagePosition imagePosition = default,
ButtonFlags flags = default)
{
var button = Button(command);
if (command.Image != null)
@ -114,8 +114,10 @@ public static class C
button.ImagePosition = imagePosition;
if (flags.HasFlag(ButtonFlags.LargeText))
{
var baseFontSize = button.Font.Size;
EtoPlatform.Current.AttachDpiDependency(button,
scale => button.Font = new Font(button.Font.Family, 12 * scale));
_ => button.Font = new Font(button.Font.Family,
baseFontSize * 4 / 3 * EtoPlatform.Current.GetLayoutScaleFactor(button.ParentWindow)));
}
EtoPlatform.Current.ConfigureImageButton(button, flags);
return button;

View File

@ -35,6 +35,7 @@ public class ChooseDeviceForm : EtoDialogBase
private CancellationTokenSource? _getDevicesCts;
private Driver? _activeQuery;
private string? _statusIconName;
public ChooseDeviceForm(Naps2Config config, IIconProvider iconProvider,
DeviceListViewBehavior deviceListViewBehavior, ScanningContext scanningContext,
@ -47,8 +48,10 @@ public class ChooseDeviceForm : EtoDialogBase
_selectDevice = C.OkButton(this, SelectDevice, UiStrings.Select);
_deviceIconList = EtoPlatform.Current.CreateListView(deviceListViewBehavior);
_deviceIconList.ImageSize = new Size(48, 32);
deviceListViewBehavior.SetImage(AlwaysAskMarker, iconProvider.GetIcon("ask")!);
deviceListViewBehavior.SetImage(ManualIpMarker, iconProvider.GetIcon("network_ip")!);
deviceListViewBehavior.SetIconName(AlwaysAskMarker, "ask");
deviceListViewBehavior.SetIconName(ManualIpMarker, "network_ip");
EtoPlatform.Current.AttachDpiDependency(this, _ => UpdateStatusIcon());
_deviceTextList.Activated += (_, _) => _selectDevice.PerformClick();
_deviceIconList.ItemClicked += (_, _) => _selectDevice.PerformClick();
@ -62,6 +65,15 @@ public class ChooseDeviceForm : EtoDialogBase
_textListVis.IsVisible = config.Get(c => c.DeviceListAsTextOnly);
}
private void UpdateStatusIcon()
{
if (_statusIconName != null)
{
_statusIcon.Image = _iconProvider.GetIcon(_statusIconName, EtoPlatform.Current.GetScaleFactor(this));
_statusIcon.Size = Size.Round(new SizeF(16, 16) * EtoPlatform.Current.GetLayoutScaleFactor(this));
}
}
private void Driver_MouseUp(object? sender, EventArgs e)
{
QueryForDevices();
@ -274,10 +286,8 @@ public class ChooseDeviceForm : EtoDialogBase
if (!cts.IsCancellationRequested)
{
_spinnerVis.IsVisible = false;
_statusIcon.Image =
DeviceList.Count > 0
? _iconProvider.GetIcon("accept_small")
: _iconProvider.GetIcon("exclamation_small");
_statusIconName = DeviceList.Count > 0 ? "accept_small" : "exclamation_small";
UpdateStatusIcon();
_statusLabel.Text = DeviceList.Count switch
{
> 1 => string.Format(UiStrings.DevicesFound, DeviceList.Count),
@ -294,7 +304,8 @@ public class ChooseDeviceForm : EtoDialogBase
if (!cts.IsCancellationRequested)
{
_spinnerVis.IsVisible = false;
_statusIcon.Image = _iconProvider.GetIcon("exclamation_small");
_statusIconName = "exclamation_small";
UpdateStatusIcon();
_statusLabel.Text = ex.Message;
}
});

View File

@ -68,7 +68,8 @@ public class DesktopCommands
};
SaveAll = new ActionCommand(imageListActions.SaveAllAsPdfOrImages)
{
Text = UiStrings.SaveAll
Text = UiStrings.SaveAll,
IconName = "diskette"
};
SaveSelected = new ActionCommand(imageListActions.SaveSelectedAsPdfOrImages)
{

View File

@ -6,6 +6,7 @@ namespace NAPS2.EtoForms.Widgets;
public class DeviceListViewBehavior : ListViewBehavior<ScanDevice>
{
private readonly Dictionary<ScanDevice, Image> _imageMap = new();
private readonly Dictionary<ScanDevice, string> _iconNameMap = new();
public DeviceListViewBehavior(ColorScheme colorScheme) : base(colorScheme)
{
@ -16,10 +17,19 @@ public class DeviceListViewBehavior : ListViewBehavior<ScanDevice>
public void SetImage(ScanDevice item, Image image) => _imageMap[item] = image;
public void SetIconName(ScanDevice item, string iconName) => _iconNameMap[item] = iconName;
public override string GetLabel(ScanDevice item) => item.Name;
public override Image GetImage(IListView<ScanDevice> listView, ScanDevice item)
{
return (_imageMap.Get(item)?.Clone() ?? Icons.device.ToEtoImage()).PadTo(listView.ImageSize);
float scale = EtoPlatform.Current.GetScaleFactor(listView.Control.ParentWindow);
if (_imageMap.Get(item) is { } image)
{
int scaledSize = (int) Math.Round(48 * scale);
return image.Clone().ResizeTo(scaledSize).PadTo(listView.ImageSize);
}
string iconName = _iconNameMap.Get(item) ?? "device";
return EtoPlatform.Current.IconProvider.GetIcon(iconName, scale)!.PadTo(listView.ImageSize);
}
}

View File

@ -1,4 +1,5 @@
using System.Threading;
using Eto.Drawing;
using Eto.Forms;
using NAPS2.EtoForms.Layout;
using NAPS2.Scan;
@ -19,6 +20,8 @@ public class DeviceSelectorWidget
private readonly Button _chooseDevice = new() { Text = UiStrings.ChooseDevice };
private DeviceChoice _choice = DeviceChoice.None;
private Image? _deviceIconImage;
private string _deviceIconName = "device";
private CancellationTokenSource? _loadIconCts;
public DeviceSelectorWidget(IScanPerformer scanPerformer, DeviceCapsCache deviceCapsCache,
@ -29,6 +32,7 @@ public class DeviceSelectorWidget
_iconProvider = iconProvider;
_parentWindow = parentWindow;
_chooseDevice.Click += ChooseDevice;
EtoPlatform.Current.AttachDpiDependency(_deviceIcon, _ => UpdateDeviceIconImage());
}
public required Func<ScanProfile> ProfileFunc { get; init; }
@ -76,7 +80,6 @@ public class DeviceSelectorWidget
private async void ChooseDevice(object? sender, EventArgs args)
{
;
var choice = await _scanPerformer.PromptForDevice(ProfileFunc(), AllowAlwaysAsk, _parentWindow.NativeHandle);
if (choice.Device != null || choice.AlwaysAsk)
{
@ -90,15 +93,15 @@ public class DeviceSelectorWidget
public void SetDeviceIcon(string? iconUri)
{
var cachedIcon = _deviceCapsCache.GetCachedIcon(iconUri);
EtoPlatform.Current.AttachDpiDependency(_deviceIcon, scale =>
_deviceIcon.Image =
cachedIcon ?? (_choice.AlwaysAsk ? _iconProvider.GetIcon("ask", scale) : _iconProvider.GetIcon("device", scale)));
_deviceIconImage = _deviceCapsCache.GetCachedIcon(iconUri);
_deviceIconName = _choice.AlwaysAsk ? "ask" : "device";
UpdateDeviceIconImage();
if (((Window) _parentWindow).Loaded)
{
_parentWindow.LayoutController.Invalidate();
}
if (cachedIcon == null && iconUri != null)
if (_deviceIconImage == null && iconUri != null)
{
ReloadDeviceIcon(iconUri);
}
@ -118,7 +121,8 @@ public class DeviceSelectorWidget
{
if (!cts.IsCancellationRequested)
{
_deviceIcon.Image = icon;
_deviceIconImage = icon;
UpdateDeviceIconImage();
_parentWindow.LayoutController.Invalidate();
}
});
@ -126,6 +130,14 @@ public class DeviceSelectorWidget
});
}
private void UpdateDeviceIconImage()
{
float scale = EtoPlatform.Current.GetScaleFactor(_deviceIcon.ParentWindow);
_deviceIcon.Image = _deviceIconImage ?? _iconProvider.GetIcon(_deviceIconName, scale);
var size = _deviceIconImage != null ? new SizeF(48, 48) : new SizeF(32, 32);
_deviceIcon.Size = Size.Round(size * EtoPlatform.Current.GetLayoutScaleFactor(_deviceIcon.ParentWindow));
}
public static implicit operator LayoutElement(DeviceSelectorWidget control)
{
return control.AsControl();
@ -142,11 +154,8 @@ public class DeviceSelectorWidget
_deviceDriver,
C.Filler()
).Spacing(5).Visible(_deviceVis).Scale(),
// TODO: We should probably have a compact choose-device button for the sidebar.
// It should also change the name of the profile if it matches the device name.
// i.e. for users that are naming their own profiles, its their responsibility to keep the devices
// matched up. For a "basic" user that might only create one profile, its name should keep matched
// with the device.
// TODO: We can consider a compact choose-device button for the sidebar, but maybe simpler to force
// creation of separate profiles
ShowChooseDevice ? _chooseDevice.AlignCenter() : C.None()
)
);

View File

@ -6,8 +6,6 @@ namespace NAPS2.Scan;
public class DeviceCapsCache
{
private const int ICON_SIZE = 48;
private readonly Dictionary<DeviceKey, ScanCaps> _capsCache = new();
private readonly Dictionary<string, Image> _iconCache = new();
@ -120,7 +118,7 @@ public class DeviceCapsCache
var imageBytes = await client.GetByteArrayAsync(iconUri);
image = _imageContext.Load(imageBytes);
}
return image.PerformTransform(new ThumbnailTransform(ICON_SIZE)).ToEtoImage();
return image.ToEtoImage();
}
private DeviceKey GetDeviceKey(ScanProfile profile)