Files
mmd-pdf/Models/ViewerState.cs
T

119 lines
7.9 KiB
C#

using System.Collections.Generic;
using System.Windows.Controls;
namespace KillerPDF
{
/// <summary>
/// Everything ONE document view owns. Split pane needs two of these; each PdfViewer control
/// owns one, and the window reads the active one back through its `_view` property, so the ~500
/// call sites behind the window's forwarding properties are untouched.
///
/// This is deliberately the PER-VIEW cut, not the per-document one. Per-document state
/// (annotations, undo, form values, search hits) already travels in DocumentSession, which
/// tab switching swaps by reference - see the comment above _annotations in
/// MainWindow.xaml.cs. A second pane needs its own live visual maps and its own view mode
/// and zoom; it does NOT need a second copy of the per-document machinery, because each
/// pane will simply own its own set of sessions.
///
/// TOP-LEVEL, not nested in MainWindow. The viewer lives in KillerPDF.Controls and cannot own a
/// type nested in the window without every reference spelling out MainWindow.ViewerState.
/// ViewMode and FitMode live in Models/ViewTypes.cs for the same reason.
/// </summary>
internal sealed class ViewerState
{
/// <summary>Unified page -> overlay map covering EVERY rendered page, the primary
/// included. The single source of truth the canvas accessors read from.</summary>
public readonly Dictionary<int, Canvas> Pages = [];
/// <summary>Per-page overlay canvases for the multi-page tile systems (continuous
/// overlays, or grid / two-page secondaries). Holds only secondary tiles and is driven
/// by the tile-recycling machinery.</summary>
public readonly Dictionary<int, Canvas> ContinuousCanvases = [];
/// <summary>The page this view is showing (0-based; -1 = no document).
///
/// This exists because reading the SIDEBAR's selected thumbnail,
/// `PageList.SelectedIndex` (118 times across 24 files), works with one pane and cannot
/// work with two - there is one sidebar and two current pages, so a viewer inside the
/// control has nothing to ask.
///
/// This is the storage; the sidebar FOLLOWS it. Kept in sync in exactly two places,
/// which between them cover every write:
/// - PageList_SelectionChanged (PageSelection.cs) mirrors the sidebar back into here,
/// unconditionally and before its own >= 0 guard, so clearing the list to -1 (tab
/// close, document close) is mirrored too.
/// - SyncCurrentPageTo (Viewport.cs), which detaches that handler to avoid re-entry
/// and so would otherwise slip past the mirror.
/// Everything that sets PageList.SelectedIndex directly still routes through the
/// handler, so those need no change.
///
/// The 118 call sites are deliberately NOT repointed at this field: the render pipeline
/// switches over to reading it as it moves into the control.</summary>
public int CurrentPage = -1;
/// <summary>Current view mode for this view.</summary>
public ViewMode Mode = ViewMode.Continuous;
/// <summary>Mode a fade is transitioning to, if one is in flight. Reads that need the
/// destination rather than the current mode use `Pending ?? Mode` - the fade takes
/// ~90ms and Mode lags behind it, which is what made wheel-cycling need several
/// notches before it was fixed.</summary>
public ViewMode? Pending;
// ── Zoom / fit ──────────────────────────────────────────────────────────────────
public double ZoomLevel = 1.0;
/// <summary>Zoom the current bitmaps were rasterized at, so the re-sharpen pass knows
/// whether what is on screen is still crisp enough.</summary>
public double LastRenderZoom = 1.0;
/// <summary>Primary (spread-left) page currently rasterized.</summary>
public int RenderedPrimaryPage = -1;
public FitMode Fit = FitMode.None;
// ── In-flight render work ───────────────────────────────────────────────────────
// Each view cancels and reschedules its own rendering, so two panes must not share
// these - one pane's mode switch would otherwise cancel the other's render.
public System.Windows.Threading.DispatcherTimer? RerenderTimer;
public System.Threading.CancellationTokenSource? SecondaryRenderCts;
public System.Threading.CancellationTokenSource? ContinuousRenderCts;
/// <summary>#85 visible-page re-sharpen.</summary>
public System.Threading.CancellationTokenSource? ContinuousSharpenCts;
// ── Continuous-view bookkeeping ─────────────────────────────────────────────────
/// <summary>Slots currently holding a hi-res bitmap.</summary>
public readonly HashSet<int> ContinuousSharpPages = [];
/// <summary>Budget those slots were sharpened at.</summary>
public int ContinuousSharpW;
public readonly List<double> ContinuousTops = [];
/// <summary>Page to scroll to once its grid tile streams in (-1 = none).</summary>
public int GridScrollToPage = -1;
/// <summary>Re-scroll here once its true height is known.</summary>
public int ContinuousScrollTarget = -1;
public double ContinuousPageW;
// ── Gesture routing ─────────────────────────────────────────────────────────────
/// <summary>The page surface a pointer gesture started on, captured on mouse-down.
/// Kept separate from the active canvas because RenderAllAnnotations reuses that as its
/// render target, and in Grid view tiles stream in asynchronously and re-point it
/// mid-gesture - which committed annotations to the wrong page.</summary>
public Canvas? GestureCanvas;
public int GesturePage = -1;
// ── Visual hosts ────────────────────────────────────────────────────────────────
// References only - the window still creates and owns the actual elements. Today
// ContinuousPanel / PageContentPanel / PageContentGrid come from FindName in the
// window ctor and are the ONE window's XAML; AnnotationCanvas / PageImage are the
// code-built primary tile (Viewport.BuildPrimaryTile) and ActiveCanvas is re-pointed
// on mouse-down. Holding them here is what lets the next stage hand each viewer its
// own tile tree without touching any of the ~250 call sites that use them.
public StackPanel ContinuousPanel = null!;
public WrapPanel PageContentPanel = null!;
public Grid PageContentGrid = null!;
/// <summary>The hardcoded primary tile's overlay, shown in Single/Grid/TwoPage.</summary>
public Canvas AnnotationCanvas = null!;
public Image PageImage = null!;
/// <summary>Active annotation surface. Single view: always AnnotationCanvas.
/// Continuous: set on mouse-down to the clicked page's overlay.</summary>
public Canvas ActiveCanvas = null!;
}
}