226 lines
13 KiB
C#
226 lines
13 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using System.Windows.Threading;
|
|
|
|
namespace KillerPDF
|
|
{
|
|
public partial class MainWindow
|
|
{
|
|
private bool _fullScreen;
|
|
private GridLength _fsTitleRow, _fsFooterRow, _fsSidebarCol, _fsSplitterCol;
|
|
private double _fsSidebarMin;
|
|
private WindowState _fsPrevState;
|
|
private bool _fsPrevTopmost;
|
|
private ResizeMode _fsPrevResize;
|
|
private double _fsPrevLeft, _fsPrevTop, _fsPrevW, _fsPrevH;
|
|
private bool _fsAnimating;
|
|
|
|
// F11 distraction-free mode: hides all chrome (title bar, toolbar, tab strip, sidebar, footer) and
|
|
// grows the window over the whole monitor so just the document pane fills the screen on a dark-gray
|
|
// backdrop. F11 or Esc exits. The switch happens under a black cross-fade so the resize never jumps.
|
|
private void ToggleFullScreen()
|
|
{
|
|
if (_fsAnimating) return; // ignore re-presses mid-transition
|
|
_fsAnimating = true;
|
|
bool entering = !_fullScreen;
|
|
|
|
var cover = new Border { Background = Brushes.Black, Opacity = 0, IsHitTestVisible = false };
|
|
Grid.SetRow(cover, 0);
|
|
Grid.SetRowSpan(cover, RootClipGrid.RowDefinitions.Count);
|
|
Panel.SetZIndex(cover, 99998);
|
|
RootClipGrid.Children.Add(cover);
|
|
|
|
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(150))
|
|
{ EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn } };
|
|
fadeIn.Completed += (_, _2) =>
|
|
{
|
|
ApplyFullScreen(entering);
|
|
// Reveal only after the resize/relayout has settled, so no edge of old layout flashes through.
|
|
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
|
|
{
|
|
var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(200))
|
|
{ EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } };
|
|
fadeOut.Completed += (_, _3) =>
|
|
{
|
|
RootClipGrid.Children.Remove(cover);
|
|
_fsAnimating = false;
|
|
if (entering) ShowFullScreenHint();
|
|
};
|
|
cover.BeginAnimation(UIElement.OpacityProperty, fadeOut);
|
|
}));
|
|
};
|
|
cover.BeginAnimation(UIElement.OpacityProperty, fadeIn);
|
|
}
|
|
|
|
private void ApplyFullScreen(bool entering)
|
|
{
|
|
// The split panes keep their RATIO across the size jump - OnSplitHostResized handles
|
|
// every non-interactive resize (full screen included), so nothing extra is needed here.
|
|
_fullScreen = entering;
|
|
var v = entering ? Visibility.Collapsed : Visibility.Visible;
|
|
|
|
TitleBarBorder.Visibility = v;
|
|
// Exit must respect the user's hide-toolbar setting (#215), not blanket-restore it.
|
|
ToolbarRowBorder.Visibility = entering || _toolbarHidden ? Visibility.Collapsed : Visibility.Visible;
|
|
// BOTH panes - full screen hides every strip, not just the focused pane's.
|
|
Viewer.TabStripBorderCtl.Visibility = v;
|
|
ViewerB.TabStripBorderCtl.Visibility = v;
|
|
FooterBorder.Visibility = v;
|
|
SidebarOuterGrid.Visibility = v;
|
|
SidebarToggleStrip.Visibility = v;
|
|
SidebarSplitter.Visibility = v;
|
|
|
|
// The document pane is a lifted card in normal use (RadCard corners, an 8px inset on
|
|
// its outer side, PaneShadow). Full screen wants the canvas edge to edge, so the card
|
|
// treatment comes off entirely and goes back on exit - otherwise a rounded corner and
|
|
// an 8px strip of window background sit inside a "full screen" view.
|
|
// The MARGIN is on the PdfViewer control, not on the card border inside it - the
|
|
// control is what the layout positions.
|
|
// SplitHost, not Viewer - the inset to the window edge belongs to the host that holds
|
|
// both panes. See the note in ApplySidebarSide.
|
|
SplitHost.Margin = entering ? new Thickness(0) : DocPaneInsetMargin();
|
|
DocPaneBorder.CornerRadius = entering ? new CornerRadius(0)
|
|
: (CornerRadius)FindResource("RadCard");
|
|
DocPaneBorder.BorderThickness = new Thickness(entering ? 0 : 1);
|
|
// The shadow caster is inside the control now, so it needs no margin of its own -
|
|
// hiding it is still right, so nothing casts onto a full-screen canvas.
|
|
DocPaneShadow.Visibility = v;
|
|
|
|
if (entering)
|
|
{
|
|
_fsTitleRow = RootClipGrid.RowDefinitions[0].Height;
|
|
_fsFooterRow = RootClipGrid.RowDefinitions[4].Height;
|
|
_fsSidebarCol = _sidebarCol.Width;
|
|
_fsSidebarMin = _sidebarCol.MinWidth;
|
|
_fsSplitterCol = MainContentGrid.ColumnDefinitions[1].Width;
|
|
RootClipGrid.RowDefinitions[0].Height = new GridLength(0);
|
|
RootClipGrid.RowDefinitions[4].Height = new GridLength(0);
|
|
// Collapse the ACTUAL sidebar column (_sidebarCol), which ApplySidebarSide repoints to DocCol
|
|
// when the sidebar is on the right. MinWidth must drop to 0 too (it floors the width to 24
|
|
// otherwise) and the splitter column (col 1) collapses, so the document fills the whole screen
|
|
// with no leftover strip on either side, regardless of side or sidebar width.
|
|
_sidebarCol.MinWidth = 0;
|
|
_sidebarCol.Width = new GridLength(0);
|
|
MainContentGrid.ColumnDefinitions[1].Width = new GridLength(0);
|
|
DocPaneBorder.Background = new SolidColorBrush(Color.FromRgb(0x26, 0x26, 0x26)); // dark-gray backdrop
|
|
|
|
// Cover the whole monitor with explicit bounds. A maximized window is clamped to the work
|
|
// area (taskbar stays visible), so instead we go Normal, size to the full monitor rect, and
|
|
// set Topmost so the window sits above the always-on-top taskbar - true full screen.
|
|
_fsPrevState = WindowState;
|
|
_fsPrevTopmost = Topmost;
|
|
_fsPrevResize = ResizeMode;
|
|
_fsPrevLeft = Left; _fsPrevTop = Top; _fsPrevW = Width; _fsPrevH = Height;
|
|
|
|
// Read the target monitor while still on it (before any WindowState change). Set the target
|
|
// bounds FIRST, then drop to Normal: WPF restores to the just-set bounds, so the window lands
|
|
// straight on this monitor instead of momentarily restoring to its old normal rect on another
|
|
// screen (the "flash to another monitor"). Re-apply bounds after Normal to be certain.
|
|
// Paint the window background black for the grow. When the window jumps from its small
|
|
// rect to the full monitor, the newly-exposed area is filled by the window background
|
|
// until the black cover re-lays-out over it. Black (instead of the gray BgDark) makes
|
|
// that exposed edge match the cover, so entering no longer flashes a gray border the way
|
|
// it did - shrinking on exit exposes nothing, which is why exit already looked clean.
|
|
// Restored to BgDark on exit.
|
|
Background = Brushes.Black;
|
|
|
|
var b = CurrentMonitorBoundsDip();
|
|
Topmost = true;
|
|
// #215: Topmost exists only to cover the always-on-top taskbar while KillerPDF is
|
|
// the ACTIVE window. Held unconditionally, it sat over every other program the user
|
|
// switched to. Yield it on deactivate, take it back on return - browser behavior.
|
|
Deactivated += FsYieldTopmost;
|
|
Activated += FsReassertTopmost;
|
|
ResizeMode = ResizeMode.NoResize;
|
|
Left = b.Left; Top = b.Top; Width = b.Width; Height = b.Height;
|
|
if (WindowState == WindowState.Maximized) WindowState = WindowState.Normal;
|
|
Left = b.Left; Top = b.Top; Width = b.Width; Height = b.Height;
|
|
}
|
|
else
|
|
{
|
|
RootClipGrid.RowDefinitions[0].Height = _fsTitleRow;
|
|
RootClipGrid.RowDefinitions[4].Height = _fsFooterRow;
|
|
_sidebarCol.MinWidth = _fsSidebarMin;
|
|
_sidebarCol.Width = _fsSidebarCol;
|
|
MainContentGrid.ColumnDefinitions[1].Width = _fsSplitterCol;
|
|
DocPaneBorder.SetResourceReference(Border.BackgroundProperty, "BgCanvas");
|
|
SetResourceReference(BackgroundProperty, "SurfaceBrush"); // undo the black grow-backdrop
|
|
|
|
// Drop topmost and restore the pre-full-screen window placement. Restore the normal bounds
|
|
// first (so WPF's remembered restore rect is correct) then re-maximize if it was maximized.
|
|
Deactivated -= FsYieldTopmost;
|
|
Activated -= FsReassertTopmost;
|
|
Topmost = _fsPrevTopmost;
|
|
ResizeMode = _fsPrevResize;
|
|
WindowState = WindowState.Normal;
|
|
Left = _fsPrevLeft; Top = _fsPrevTop; Width = _fsPrevW; Height = _fsPrevH;
|
|
if (_fsPrevState == WindowState.Maximized) WindowState = WindowState.Maximized;
|
|
}
|
|
|
|
// Re-apply the frame treatment: squared (no border, square corners, full clip) in full screen,
|
|
// back to the floating border on exit. _fullScreen is already set, so UpdateWindowChrome reads it.
|
|
UpdateWindowChrome();
|
|
}
|
|
|
|
// #215: full-screen topmost is active-window-only; see the enter branch above.
|
|
private void FsYieldTopmost(object? sender, EventArgs e) => Topmost = false;
|
|
private void FsReassertTopmost(object? sender, EventArgs e) => Topmost = true;
|
|
|
|
// Full bounds (taskbar included) of the monitor the window is currently on, in WPF device-independent
|
|
// units. MonitorFromWindow/GetMonitorInfo/MONITORINFO/RECT are declared in WindowChrome.cs (same class).
|
|
private Rect CurrentMonitorBoundsDip()
|
|
{
|
|
var hwnd = new WindowInteropHelper(this).Handle;
|
|
IntPtr mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
|
var info = new MONITORINFO { cbSize = Marshal.SizeOf(typeof(MONITORINFO)) };
|
|
GetMonitorInfo(mon, ref info);
|
|
var r = info.rcMonitor;
|
|
var dpi = VisualTreeHelper.GetDpi(this);
|
|
return new Rect(r.left / dpi.DpiScaleX, r.top / dpi.DpiScaleY,
|
|
(r.right - r.left) / dpi.DpiScaleX, (r.bottom - r.top) / dpi.DpiScaleY);
|
|
}
|
|
|
|
// Chrome-style toast: fades in near the top, holds, then fades out and removes itself.
|
|
private void ShowFullScreenHint()
|
|
{
|
|
var toast = new Border
|
|
{
|
|
Background = new SolidColorBrush(Color.FromArgb(0xE0, 0x1c, 0x1c, 0x1c)),
|
|
CornerRadius = new CornerRadius(7),
|
|
Padding = new Thickness(18, 9, 18, 9),
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
VerticalAlignment = VerticalAlignment.Top,
|
|
Margin = new Thickness(0, 44, 0, 0),
|
|
Opacity = 0,
|
|
IsHitTestVisible = false,
|
|
Effect = new System.Windows.Media.Effects.DropShadowEffect { Color = Colors.Black, BlurRadius = 12, ShadowDepth = 2, Opacity = 0.5 },
|
|
Child = new TextBlock
|
|
{
|
|
Text = Loc("Str_FullScreen_Hint"), Foreground = Brushes.White, FontSize = 13,
|
|
FontFamily = UiKit.UiFont
|
|
}
|
|
};
|
|
Grid.SetRow(toast, 0);
|
|
Grid.SetRowSpan(toast, RootClipGrid.RowDefinitions.Count);
|
|
Panel.SetZIndex(toast, 99999);
|
|
RootClipGrid.Children.Add(toast);
|
|
|
|
toast.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200)));
|
|
var t = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2.2) };
|
|
t.Tick += (_, _2) =>
|
|
{
|
|
t.Stop();
|
|
var fade = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(400));
|
|
fade.Completed += (_, _3) => RootClipGrid.Children.Remove(toast);
|
|
toast.BeginAnimation(UIElement.OpacityProperty, fade);
|
|
};
|
|
t.Start();
|
|
}
|
|
}
|
|
}
|