142 lines
7.3 KiB
C#
142 lines
7.3 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
|
|
namespace KillerPDF
|
|
{
|
|
// App-wide accessibility size, ported from KillerNotes: a LayoutTransform scale on the
|
|
// chrome (toolbar row, sidebar, tab strip) grows or shrinks the UI crisply -
|
|
// LayoutTransform reflows and re-rasterizes text rather than bitmap-stretching it. The
|
|
// title bar and footer stay fixed, so the logo you scroll to drive this (MainWindow.xaml,
|
|
// LogoBar) never moves. The document pane is deliberately NOT scaled: app size and page
|
|
// zoom are two separate controls. Persisted app-wide ("AppScale").
|
|
public partial class MainWindow
|
|
{
|
|
internal double _appScale = 1.0;
|
|
private const double AppScaleMin = 0.7, AppScaleMax = 2.5, AppScaleStep = 0.02;
|
|
|
|
// The sidebar column lives in the UNSCALED grid (screen px) while its content lays
|
|
// out at screen/scale logical px. Every site that pushes a logical sidebar width
|
|
// (SidebarMinOpen, SidebarMaxPages, the 24px collapse strip...) into the column
|
|
// converts through this, so the sidebar's LOGICAL width holds steady across scales
|
|
// and the thumbnails grow with the rest of the chrome instead of being squeezed.
|
|
internal double SbPx(double logical) => logical * _appScale;
|
|
|
|
private void InitAppScale()
|
|
{
|
|
if (double.TryParse(App.GetSetting("AppScale"), NumberStyles.Float,
|
|
CultureInfo.InvariantCulture, out double s))
|
|
ApplyAppScale(s);
|
|
}
|
|
|
|
// Roll the wheel over the logo: one small step per notch (fine-grained, no big jumps).
|
|
private void LogoBar_MouseWheel(object sender, MouseWheelEventArgs e)
|
|
{
|
|
ApplyAppScale(_appScale + (e.Delta > 0 ? AppScaleStep : -AppScaleStep), persist: true);
|
|
e.Handled = true;
|
|
}
|
|
|
|
// The logo is marked IsHitTestVisibleInChrome (MainWindow.xaml) so the scroll wheel
|
|
// reaches it for the zoom above - but that also takes it out of WindowChrome's native
|
|
// caption, so window drag and double-click-maximize are restored here by hand.
|
|
private void LogoBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (e.ClickCount == 2)
|
|
{
|
|
MaximizeBtn_Click(this, new RoutedEventArgs()); // WindowChrome.cs
|
|
e.Handled = true;
|
|
return;
|
|
}
|
|
// #206: hand the drag to Windows the way TitleBar_MouseLeftButtonDown does, rather than
|
|
// calling DragMove. DragMove throws while maximized, so the logo had to bail out there
|
|
// and drag-down-to-restore did nothing over it - worst in themes whose wordmark makes
|
|
// the logo wide, leaving only the file-name gap in the middle of the bar draggable.
|
|
// WM_NCLBUTTONDOWN(HTCAPTION) restores under the cursor and keeps following it.
|
|
if (e.ButtonState != MouseButtonState.Pressed) return;
|
|
e.Handled = true;
|
|
var hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
|
|
if (hwnd != IntPtr.Zero) SendMessage(hwnd, WM_NCLBUTTONDOWN, new IntPtr(HTCAPTION), IntPtr.Zero);
|
|
}
|
|
|
|
private void ApplyAppScale(double scale, bool persist = false)
|
|
{
|
|
double prev = _appScale;
|
|
scale = Math.Round(Math.Max(AppScaleMin, Math.Min(AppScaleMax, scale)), 3);
|
|
_appScale = scale;
|
|
// CHROME ONLY: toolbar, sidebar, and tab strip scale; the document pane is
|
|
// deliberately untouched, so the app size and the page zoom stay two separate
|
|
// controls.
|
|
var t = scale == 1.0 ? Transform.Identity : new ScaleTransform(scale, scale);
|
|
ToolbarRowBorder.LayoutTransform = t;
|
|
SidebarOuterGrid.LayoutTransform = t;
|
|
// BOTH panes: each carries its own strip, and scaling only the focused one would leave
|
|
// the other pane's tabs at the previous size.
|
|
Viewer.TabStripBorderCtl.LayoutTransform = t;
|
|
ViewerB.TabStripBorderCtl.LayoutTransform = t;
|
|
// Keep the sidebar's LOGICAL width constant across the change: the column and
|
|
// the saved widths are screen px, so grow them with the scale (see SbPx above).
|
|
if (scale != prev && prev > 0)
|
|
{
|
|
double f = scale / prev;
|
|
_savedPagesWidth *= f;
|
|
_savedOutlinesWidth *= f;
|
|
if (_sidebarCol is { } col)
|
|
{
|
|
if (col.Width.GridUnitType == GridUnitType.Pixel)
|
|
col.Width = new GridLength(col.Width.Value * f);
|
|
if (col.MinWidth > 0) col.MinWidth *= f;
|
|
if (!double.IsPositiveInfinity(col.MaxWidth)) col.MaxWidth *= f;
|
|
}
|
|
}
|
|
if (persist)
|
|
{
|
|
App.SetSetting("AppScale", scale.ToString("0.###", CultureInfo.InvariantCulture));
|
|
ShowScaleReadout(scale);
|
|
}
|
|
}
|
|
|
|
// The readout is transient. Every wheel notch rewrites it and restarts the hold timer,
|
|
// so the footer carries it while you are zooming and gives the line back a beat after
|
|
// you stop. It still goes out through SetStatusHeld, because the chrome resize re-runs
|
|
// the fit pipeline and its page/zoom status would otherwise stomp this the same frame
|
|
// (MainWindow.xaml.cs SetStatus) - that hold is short and only covers the stomp.
|
|
//
|
|
// Whatever was showing before the first notch of a burst is snapshotted and put back,
|
|
// but only if the readout is still the text on screen, so a status written after the
|
|
// hold expired is never overwritten by a stale one. The restore assigns directly
|
|
// rather than going through SetStatus: this is putting a line back, not reporting
|
|
// something new, so it should not land in the crash breadcrumb a second time.
|
|
//
|
|
// Normal priority rather than the DispatcherTimer default of Background, so a busy
|
|
// render cannot leave the readout parked on the footer.
|
|
private System.Windows.Threading.DispatcherTimer? _appScaleHide;
|
|
private string _appScaleStatusWas = string.Empty;
|
|
private string _appScaleReadout = string.Empty;
|
|
|
|
private void ShowScaleReadout(double scale)
|
|
{
|
|
if (_appScaleHide is null)
|
|
{
|
|
_appScaleHide = new System.Windows.Threading.DispatcherTimer(
|
|
System.Windows.Threading.DispatcherPriority.Normal)
|
|
{ Interval = TimeSpan.FromSeconds(5) };
|
|
_appScaleHide.Tick += (_, _) =>
|
|
{
|
|
_appScaleHide!.Stop();
|
|
if (StatusText.Text == _appScaleReadout) StatusText.Text = _appScaleStatusWas;
|
|
};
|
|
}
|
|
|
|
// Only the first notch of a burst snapshots; the rest are our own readout.
|
|
if (!_appScaleHide.IsEnabled) _appScaleStatusWas = StatusText.Text;
|
|
_appScaleHide.Stop();
|
|
|
|
_appScaleReadout = string.Format(Loc("Str_St_AppSize"), (int)Math.Round(scale * 100));
|
|
SetStatusHeld(_appScaleReadout);
|
|
_appScaleHide.Start();
|
|
}
|
|
}
|
|
}
|