using System.Collections.Generic; using System.Windows.Controls; namespace MmdPdf { /// /// 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 MmdPdf.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. /// internal sealed class ViewerState { /// Unified page -> overlay map covering EVERY rendered page, the primary /// included. The single source of truth the canvas accessors read from. public readonly Dictionary Pages = []; /// 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. public readonly Dictionary ContinuousCanvases = []; /// 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. public int CurrentPage = -1; /// Current view mode for this view. public ViewMode Mode = ViewMode.Continuous; /// 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. public ViewMode? Pending; // ── Zoom / fit ────────────────────────────────────────────────────────────────── public double ZoomLevel = 1.0; /// Zoom the current bitmaps were rasterized at, so the re-sharpen pass knows /// whether what is on screen is still crisp enough. public double LastRenderZoom = 1.0; /// Primary (spread-left) page currently rasterized. 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; /// #85 visible-page re-sharpen. public System.Threading.CancellationTokenSource? ContinuousSharpenCts; // ── Continuous-view bookkeeping ───────────────────────────────────────────────── /// Slots currently holding a hi-res bitmap. public readonly HashSet ContinuousSharpPages = []; /// Budget those slots were sharpened at. public int ContinuousSharpW; public readonly List ContinuousTops = []; /// Page to scroll to once its grid tile streams in (-1 = none). public int GridScrollToPage = -1; /// Re-scroll here once its true height is known. public int ContinuousScrollTarget = -1; public double ContinuousPageW; // ── Gesture routing ───────────────────────────────────────────────────────────── /// 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. 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!; /// The hardcoded primary tile's overlay, shown in Single/Grid/TwoPage. public Canvas AnnotationCanvas = null!; public Image PageImage = null!; /// Active annotation surface. Single view: always AnnotationCanvas. /// Continuous: set on mouse-down to the clicked page's overlay. public Canvas ActiveCanvas = null!; } }