Files
mmd-pdf/Controls/Viewer/PdfViewer.xaml
T
kua-agent 6610acfa44 feat: rebrand to MMD PDF, single corporate theme, and remove original text under an edit
- Services/PdfRedactText.cs: strip text whose origin falls inside a CoverAnnotation
  from the page content stream at save time, hooked into PdfBurn.DrawAnnotationsIntoDoc.
  Fixes edited values staying recoverable by text extraction.
- Themes/MMD.xaml replaces all thirteen themes; picker and accent strip removed; no dark mode.
- Rename KillerPDF -> MMD PDF across code, resources, packaging and locale strings; new icon.
- Remove the upstream author credit and the in-app install button.
2026-08-27 07:37:09 +02:00

595 lines
49 KiB
XML

<UserControl x:Class="MmdPdf.Controls.PdfViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- One document view: this pane's tab strip and its card.
The card's MARGIN lives on this control, not on the borders below - ApplySidebarSide sets
it to flip the 8px gutter to whichever side the document is on. -->
<Grid>
<!-- Row 0 is the strip, row 1 the card. Each pane carrying its own strip means the strip
tracks its pane's width and position for free, including through a boundary drag.
Row 0 is Auto and the band collapses below two tabs. -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- ZIndex above the card: the card's -1 top margin tucks its top border into this band's
row, and the active tab - which carries the pane's own BgCanvas - covers that pixel so
the two read as one surface. -->
<Border Grid.Row="0" x:Name="TabStripBorder" Panel.ZIndex="10"
Height="{DynamicResource TabBandHeight}"
Background="Transparent" Visibility="Collapsed"
SizeChanged="TabStripBorder_SizeChanged">
<Grid>
<!-- Match KillerShell: the strip is transparent and does not paint another grain
tile over the window's existing background. Only the opaque active tab
replaces that shared grain below. -->
<Border x:Name="TabStripFade" IsHitTestVisible="False"
Opacity="{DynamicResource BarShadowOpacity}">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#00000000" Offset="0"/>
<GradientStop Color="#00000000" Offset="0.45"/>
<GradientStop Color="#59000000" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
</Border>
<!-- TabBarRing, ported from KillerShell. The card's top border drawn again, radii
and all - 7px tall with a -6 bottom margin, so its 1px top edge lands in the
band's last row and its curved sides drop out of the band onto the card's own
left/right border. A flat 1px strip would run past the card's rounded corners.
SyncPaneLeadingCorner keeps the radii in step with the card's; SetFocusHalo
drives its brush alongside the card's.
Declared after the fade (whose dark bottom would wash it out) and before the
tabs, so the active tab covers its own segment and breaks the line at the tab. -->
<Border x:Name="TabBarRing" Height="7" VerticalAlignment="Bottom" Margin="0,0,0,-6"
BorderThickness="1,1,1,0" CornerRadius="{DynamicResource TabCornerRadius}" IsHitTestVisible="False"
BorderBrush="{DynamicResource PaneEdgeBrush}"/>
<DockPanel LastChildFill="True">
<!-- Overflow chevron: every tab in this pane, hidden ones included
(TabOverflowMenu). Docked BEFORE the strip so the strip measures against
the band's REMAINING width - docked after, the strip would take the whole
band and the chevron would sit on top of the last tab.
Collapsed while everything fits, which is the normal case: a chevron that
is always there is a control that does nothing most of the time, and its
26px would come out of the tabs to say so. -->
<!-- DynamicResource, NOT StaticResource, on the style - same reason as
GrainBrushShared above. TabNewButton lives in MainWindow.Resources, and
StaticResource resolves at parse time against this control's own scope,
before the control is in the window's tree, so it throws from
InitializeComponent. -->
<Button x:Name="TabOverflowBtn" DockPanel.Dock="Right" Visibility="Collapsed"
Style="{DynamicResource TabNewButton}"
Content="&#xE70D;" FontFamily="{DynamicResource IconFont}" FontSize="10"
Width="22" Height="20" Margin="2,4,2,3" VerticalAlignment="Center"
FocusVisualStyle="{x:Null}"
ToolTip="{DynamicResource Str_TT_TabOverflow}" Click="TabOverflow_Click"/>
<!-- No side insets. The strip has to be flush with the card beneath it, or the
first tab's left edge sits 6px inside the card's left border and the ring
steps sideways where it should run straight up. -->
<!-- Horizontal scrolling is DISABLED, not hidden: hidden still measures the
content at infinite width, which would let the tabs size to their own
content and defeat the UniformGrid below. Disabled constrains them to the
band's actual width, which is the point.
FocusVisualStyle nulled: a ScrollViewer is focusable, and once focus lands
here WPF draws its stock dotted rectangle around the whole strip. -->
<ScrollViewer x:Name="TabScroll" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled"
Background="Transparent" FocusVisualStyle="{x:Null}"
UseLayoutRounding="True" SnapsToDevicePixels="True"
MouseLeftButtonDown="TabScroll_MouseLeftButtonDown">
<!-- Keep the edge overlays in the SAME viewport as the tab containers.
When they were siblings of the ScrollViewer they started from the
band's origin while the tabs started from the viewport's origin,
producing the three-pixel vertical mismatch at the scrollbar corner. -->
<Grid UseLayoutRounding="True" SnapsToDevicePixels="True">
<ItemsControl x:Name="TabStrip" UseLayoutRounding="True" SnapsToDevicePixels="True">
<!-- The ACTIVE tab draws above its neighbors. Items paint in index
order, so the tab to the RIGHT of the active one painted over the
active tab's 1px right border and the ring stopped dead at that
side - which is why a middle tab lost its right edge while the last
tab, having no neighbor after it, kept both. -->
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Panel.ZIndex" Value="1"/>
</DataTrigger>
<!-- Windowed out of the strip and living in the chevron
instead (ApplyTabWindow). Collapsed rather than removed
from the collection, which is what makes this cheap:
UniformGrid does not count a collapsed child when it
divides the band, so the survivors fill it edge to edge
on their own, and nothing about the tabs' order or
their drag indices moves. -->
<DataTrigger Binding="{Binding IsStripVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<!-- Tabs share the band equally and fill it, browser-style, instead
of hugging their titles and leaving dead space on the right.
This is also what makes the last tab always REACH the strip's
right edge, so edge ownership is a fact rather than something
that has to be measured after every reflow. -->
<ItemsPanelTemplate><UniformGrid Rows="1"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<!-- No DataType: the item is PdfViewer.DocumentSession, an internal
nested type, which x:Type cannot name. -->
<DataTemplate>
<!-- No MaxWidth: it would cap each cell and put the dead space
back. The MinWidth floor is low so a lot of open tabs still
divide the band rather than overflowing it.
CornerRadius matches the card's 6: the rightmost tab sits
directly above the card's top-right corner, and a 4px curve
stacked on a 6px one reads as a mistake rather than as two
rounded things.
No right MARGIN: the 1px right BorderThickness below
already separates one tab from the next, and a margin only
stopped the last tab reaching the strip's right edge -
which is the card's edge, so it left a 1px step under the
corner.
1px bottom margin keeps an inactive tab clear of
TabBarRing; the active tab drops it and covers its own
segment, which is what breaks that line exactly at the tab
and makes the browser-tab join. -->
<Border x:Name="tabBd" CornerRadius="{DynamicResource TabCornerRadius}" Margin="{DynamicResource TabMargin}" Padding="{DynamicResource TabPadding}"
MinWidth="60"
UseLayoutRounding="True" SnapsToDevicePixels="True"
Cursor="Hand" Background="Transparent"
BorderThickness="0,0,1,0" BorderBrush="{DynamicResource PaneEdgeBrush}"
ToolTip="{Binding TabTip}"
MouseDown="Tab_MouseDown"
MouseRightButtonUp="Tab_RightClick"
PreviewMouseLeftButtonDown="Tab_DragDown"
PreviewMouseMove="Tab_DragMove"
PreviewMouseLeftButtonUp="Tab_DragUp">
<Grid>
<!-- Grain follows the tab's radius, or the texture
squares off the corner the border just rounded. -->
<Border x:Name="tabGrain" IsHitTestVisible="False"
CornerRadius="{DynamicResource TabCornerRadius}" Margin="-12,-4,-5,-5"
Background="{DynamicResource GrainTileBrush}" Opacity="0"/>
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Right" Content="&#xE711;"
Style="{DynamicResource TabCloseButton}"
FontFamily="{DynamicResource IconFont}" FontSize="9"
Width="16" Height="16" Margin="6,0,0,0" Padding="0"
VerticalAlignment="Center" FocusVisualStyle="{x:Null}"
ToolTip="{DynamicResource Str_TT_CloseTab}"
Tag="{Binding}" Click="CloseTab_Click"/>
<TextBlock x:Name="tabLbl" Text="{Binding TabLabel}"
FontFamily="{DynamicResource UiFont}" FontSize="11"
Foreground="{DynamicResource MutedTextBrush}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</DockPanel>
<Border x:Name="tabBevelLight" IsHitTestVisible="False" Panel.ZIndex="4"
Margin="{DynamicResource TabBevelMargin}" SnapsToDevicePixels="True"
BorderBrush="{DynamicResource BevelLightBrush}" BorderThickness="{DynamicResource BevelLightThickness}"/>
<Border x:Name="tabBevelDark" IsHitTestVisible="False" Panel.ZIndex="4"
Margin="{DynamicResource TabBevelMargin}" SnapsToDevicePixels="True"
BorderBrush="{DynamicResource BevelDarkBrush}" BorderThickness="{DynamicResource TabInactiveBevelDarkThickness}"/>
<!-- Second, inset highlight of the selected 98SE tab. Together
with tabBevelLight this makes the tab a raised two-tone edge,
matching the pane's outer and inner highlights instead of
degenerating into a flat rule at the tab/pane join. Other
themes resolve these resources to a transparent zero edge. -->
<Border x:Name="tabActiveRetroInnerBevel" IsHitTestVisible="False" Panel.ZIndex="6"
Margin="{DynamicResource TabActiveInnerBevelMargin}"
BorderBrush="{DynamicResource TabActiveInnerBevelBrush}"
BorderThickness="{DynamicResource TabActiveInnerBevelThickness}"
SnapsToDevicePixels="True" Visibility="Collapsed"/>
</Grid>
</Border>
<DataTemplate.Triggers>
<!-- Last tab drops its right border: that 1px is a divider
BETWEEN tabs, and on the strip's right edge it reads as
a stray rule instead. Declared BEFORE the IsActive
trigger so the active tab's accent-stripe thickness
still wins when the last tab is also the active one. -->
<DataTrigger Binding="{Binding IsLast}" Value="True">
<Setter TargetName="tabBd" Property="BorderThickness" Value="0"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFirst}" Value="True"/>
<Condition Binding="{Binding IsActive}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="Margin" Value="{DynamicResource TabInactiveFirstMargin}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsLast}" Value="True"/>
<Condition Binding="{Binding IsActive}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="Margin" Value="{DynamicResource TabInactiveLastMargin}"/>
</MultiDataTrigger>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<!-- Front tab takes the card's own canvas color, so the
tab and the card read as one surface. -->
<Setter TargetName="tabBd" Property="Background" Value="{DynamicResource BgCanvas}"/>
<Setter TargetName="tabGrain" Property="Opacity" Value="{DynamicResource GrainOpacity}"/>
<Setter TargetName="tabBd" Property="BorderThickness" Value="{DynamicResource TabStripeThickness}"/>
<!-- Reaches the foot of the band so its fill breaks the
ring line where the tab is. -->
<Setter TargetName="tabBd" Property="Margin" Value="0,3,0,0"/>
<!-- Top padding gives back the 3px the accent stripe
takes, so the title does not drop on activation. -->
<Setter TargetName="tabBd" Property="Padding" Value="{DynamicResource TabActivePadding}"/>
<Setter TargetName="tabBd" Property="BorderBrush" Value="{DynamicResource TabActiveRingBrush}"/>
<!-- Modern themes use the original raised-tab shadow. 98SE
disables it in the theme-gated trigger below because an Effect
rasterizes the label and destroys the crisp classic text. -->
<Setter TargetName="tabBd" Property="Effect" Value="{DynamicResource BarShadowEffect}"/>
<Setter TargetName="tabLbl" Property="Foreground" Value="{DynamicResource TextBrush}"/>
<Setter TargetName="tabLbl" Property="FontWeight" Value="Bold"/>
<Setter TargetName="tabLbl" Property="FontSize" Value="11.5"/>
<Setter TargetName="tabBevelDark" Property="BorderThickness" Value="{DynamicResource TabActiveBevelDarkThickness}"/>
<Setter TargetName="tabBevelDark" Property="Margin" Value="{DynamicResource TabActiveBevelDarkMargin}"/>
<Setter TargetName="tabActiveRetroInnerBevel" Property="Visibility" Value="{DynamicResource RetroActiveTabOutlineVisibility}"/>
</DataTrigger>
<DataTrigger Binding="{Binding UseRetroTabChrome}" Value="True">
<Setter TargetName="tabBd" Property="Effect" Value="{x:Null}"/>
</DataTrigger>
<!-- A first 98SE tab follows the pane's inner light bevel,
one pixel inside its dark outer frame. Modern themes
resolve this token to the normal flush active margin. -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsActive}" Value="True"/>
<Condition Binding="{Binding IsFirst}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="Margin" Value="{DynamicResource TabActiveFirstMargin}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsActive}" Value="True"/>
<Condition Binding="{Binding IsLast}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="Margin" Value="{DynamicResource TabActiveLastMargin}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsActive}" Value="True"/>
<Condition Binding="{Binding IsFirst}" Value="True"/>
<Condition Binding="{Binding IsLast}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="Margin" Value="{DynamicResource TabActiveOnlyMargin}"/>
</MultiDataTrigger>
<!-- Unfocused pane: the lip drops off the accent entirely
and takes the card's own border color, so the whole
unlit ring - lip, band line and card border - is ONE
value. At full accent both panes claimed to be the live
one. The active tab is still obvious on an unfocused
pane from its canvas fill and bold title, which is how
a browser marks it too.
Mutually exclusive with PaneFocused below, and NOT
simply !PaneFocused: with one pane open both are false
and the lone pane's lip stays bright. -->
<DataTrigger Binding="{Binding PaneDimmed}" Value="True">
<Setter TargetName="tabBd" Property="BorderBrush" Value="{DynamicResource PaneBorderBrush}"/>
</DataTrigger>
<!-- Focused pane: the ring continues UP both sides of the
active tab, so the tab and the card read as one
outlined surface instead of the ring stopping dead at
the strip. Declared LAST on purpose - PaneFocused
implies IsActive, both triggers match, and with
multiple matches the last one wins. Put earlier,
IsActive would overwrite the thickness straight back to
a top-only stripe.
One brush for the whole lip, matching the card's ring:
a top stripe in one color meeting sides in another
reads as two edges rather than one ring.
Padding drops 1px each side to pay for the new borders,
so the title does not shift when focus arrives. -->
<DataTrigger Binding="{Binding PaneFocused}" Value="True">
<Setter TargetName="tabBd" Property="BorderThickness" Value="{DynamicResource TabFocusThickness}"/>
<Setter TargetName="tabBd" Property="Padding" Value="{DynamicResource TabFocusPadding}"/>
<Setter TargetName="tabBd" Property="BorderBrush" Value="{DynamicResource TabActiveRingBrush}"/>
</DataTrigger>
<!-- Pane shading is the 98SE focus cue only. Keeping it behind an
explicit theme flag preserves every modern palette's original
active-tab BgCanvas fill. -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding UseRetroTabChrome}" Value="True"/>
<Condition Binding="{Binding PaneDimmed}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="Background" Value="{DynamicResource TabInactiveBrush}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding UseRetroTabChrome}" Value="True"/>
<Condition Binding="{Binding PaneFocused}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="Background" Value="{DynamicResource FocusedPaneBrush}"/>
</MultiDataTrigger>
<!-- The OUTERMOST side is the band's to draw (TabEdgeLeft /
TabEdgeRight), so the first and last tab must not draw
it as well. Two 1px borders at the same x is a 2px edge
on a ring that is 1px everywhere else - and only
SOMETIMES, because whether the tab's own outer border
clears the ScrollViewer's clip depends on how the
UniformGrid divided a fractional band width. That is
where the halo came out uneven, and why it changed when
the split moved.
The padding gives the pixel back on that side so the
title does not shift, exactly as PaneFocused does.
Declared after PaneFocused - all three match on a first
or last tab and the last match wins. -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding PaneFocused}" Value="True"/>
<Condition Binding="{Binding IsFirst}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="BorderThickness" Value="{DynamicResource TabFocusFirstThickness}"/>
<Setter TargetName="tabBd" Property="Padding" Value="{DynamicResource TabFocusFirstPadding}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding PaneFocused}" Value="True"/>
<Condition Binding="{Binding IsLast}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="BorderThickness" Value="{DynamicResource TabFocusLastThickness}"/>
<Setter TargetName="tabBd" Property="Padding" Value="{DynamicResource TabFocusLastPadding}"/>
</MultiDataTrigger>
<!-- One tab in a focused pane owns BOTH edges, so it draws
neither. Last of the three for the same reason. -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding PaneFocused}" Value="True"/>
<Condition Binding="{Binding IsFirst}" Value="True"/>
<Condition Binding="{Binding IsLast}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="tabBd" Property="BorderThickness" Value="{DynamicResource TabFocusOnlyThickness}"/>
<Setter TargetName="tabBd" Property="Padding" Value="{DynamicResource TabFocusOnlyPadding}"/>
</MultiDataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- The outer gray frame pixels. The tabs reserve their edge pixel and
draw the inner white/gray bevel themselves; these borders complete
only the outermost frame and therefore cannot thicken the bevel. -->
<Border x:Name="TabEdgeLeft" Width="1" HorizontalAlignment="Left"
IsHitTestVisible="False" Visibility="Collapsed"/>
<Border x:Name="TabEdgeRight" Width="1" HorizontalAlignment="Right"
IsHitTestVisible="False" Visibility="Collapsed"/>
</Grid>
</ScrollViewer>
</DockPanel>
</Grid>
</Border>
<!-- ── This pane's card ───────────────────────────────────────────────────────────
Row 1. The -1 top margin tucks the card's top border up into the strip band so the
active tab and the card read as one surface (see TabBarRing).
Margin is set in CODE, not here: it must be -1 only while this pane HAS a strip.
A pane with one tab collapses its strip, and the -1 then lifts that pane a pixel above
the other one instead of tucking under anything. RebuildTabStrip owns both. -->
<Grid x:Name="CardRow" Grid.Row="1">
<!-- Separate shadow caster keeps the pane content crisp while allowing each modern
theme to supply its intended elevation. 98SE sets PaneShadowOpacity to zero. -->
<!-- DynamicResource PaneShadowEffect, built per theme in ThemeManager (null on 98SE) -
the old app-level StaticResource effect froze with its startup opacity and 98SE's
zero never applied. -->
<Border x:Name="PaneShadow" IsHitTestVisible="False" Margin="0,1,0,0"
Background="{DynamicResource BgCanvas}"
CornerRadius="{DynamicResource RadCard}"
Effect="{DynamicResource PaneShadowEffect}"/>
<Border x:Name="PaneBorder" Panel.ZIndex="6"
Background="{DynamicResource BgCanvas}"
BorderBrush="{DynamicResource PaneBorderBrush}" BorderThickness="1"
CornerRadius="{DynamicResource RadCard}">
<!-- Clipped to the card's own corners (DocPane_SizeChanged). A Border with a
CornerRadius does NOT clip its child, so the canvas and the page square the
corners straight back off. Same treatment as KillerShell's PaneContent. -->
<Grid x:Name="DocPaneContent" SizeChanged="DocPane_SizeChanged">
<Border x:Name="PaneBevelOuterDark" Panel.ZIndex="100" IsHitTestVisible="False" BorderBrush="{DynamicResource PaneBevelDarkBrush}" BorderThickness="{DynamicResource PaneBevelLightThickness}"/>
<Border x:Name="PaneBevelOuterLight" Panel.ZIndex="100" IsHitTestVisible="False" BorderBrush="{DynamicResource PaneBevelLightBrush}" BorderThickness="{DynamicResource PaneBevelDarkThickness}"/>
<Border x:Name="PaneBevelInnerDark" Panel.ZIndex="100" IsHitTestVisible="False" Margin="{DynamicResource PaneBevelInnerMargin}" BorderBrush="{DynamicResource PaneBevelDark2Brush}" BorderThickness="{DynamicResource PaneBevel2LightThickness}"/>
<Border x:Name="PaneBevelInnerLight" Panel.ZIndex="100" IsHitTestVisible="False" Margin="{DynamicResource PaneBevelInnerMargin}" BorderBrush="{DynamicResource PaneBevelLight2Brush}" BorderThickness="{DynamicResource PaneBevel2DarkThickness}"/>
<!-- Film grain - sits on the canvas background, behind the document.
Needs no CornerRadius of its own: the clip on DocPaneContent rounds
everything inside the card, grain included. -->
<Border IsHitTestVisible="False" Opacity="{DynamicResource GrainOpacity}">
<Border.Background>
<ImageBrush x:Name="GrainBrush"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0,0,256,256"
Stretch="None"/>
</Border.Background>
</Border>
<!-- Drop zone -->
<Border x:Name="DropZone" Background="Transparent"
AllowDrop="True" Drop="DropZone_Drop" DragOver="DropZone_DragOver"
MouseLeftButtonDown="DropZone_Click">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Drop target, centered in the remaining space -->
<Border Grid.Column="0" BorderBrush="{DynamicResource DropBorder}" BorderThickness="2"
CornerRadius="{DynamicResource PanelCornerRadius}" Padding="40"
HorizontalAlignment="Center" VerticalAlignment="Center"
Background="Transparent" Style="{x:Null}">
<StackPanel HorizontalAlignment="Center">
<TextBlock Text="{DynamicResource Str_Drop_Title}" FontFamily="Segoe UI, Microsoft JhengHei UI, Nirmala UI" FontSize="20"
Foreground="{DynamicResource MutedTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Text="{DynamicResource Str_Drop_Sub}" FontFamily="Segoe UI, Microsoft JhengHei UI, Nirmala UI" FontSize="13"
Foreground="{DynamicResource MutedTextBrush}" HorizontalAlignment="Center" Margin="0,8,0,0"/>
</StackPanel>
</Border>
<!-- Recent files sidebar (populated in code-behind; hidden when empty).
Width and visibility are driven by SyncRecentBoxWidth: at 340 fixed it
swamped the drop target in a half-width pane, so it now scales with the
pane and drops out entirely below a threshold. -->
<Border x:Name="RecentFilesBox" Grid.Column="1" Width="340" Visibility="Collapsed"
Background="{DynamicResource BgRecentPanel}"
BorderBrush="{DynamicResource PaneBorderBrush}" BorderThickness="1,0,0,0">
<Grid>
<Border Background="{DynamicResource GrainBrushShared}" IsHitTestVisible="False"
Opacity="{DynamicResource GrainOpacity}"/>
<DockPanel Margin="18,22,12,16">
<DockPanel DockPanel.Dock="Top" Margin="2,0,0,10">
<!-- Clear all (#146): one click empties the whole list, matching the
dropdown's Clear list item. Foreground lives in the style so the
hover trigger can override it (a local value would win over it). -->
<TextBlock DockPanel.Dock="Right" Text="{DynamicResource Str_Menu_ClearList}"
FontFamily="Segoe UI, Microsoft JhengHei UI, Nirmala UI"
FontSize="11" Cursor="Hand" Margin="8,0,6,0"
MouseLeftButtonDown="RecentClearAll_Click">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource MutedTextBrush}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{DynamicResource PrimaryBrush}"/>
<Setter Property="TextDecorations" Value="Underline"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{DynamicResource Str_RecentHeading}" FontFamily="Segoe UI, Microsoft JhengHei UI, Nirmala UI"
FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryBrush}"/>
</DockPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<ItemsControl x:Name="RecentFilesList"/>
</ScrollViewer>
</DockPanel>
</Grid>
</Border>
</Grid>
</Border>
<!-- PDF page with annotation overlay -->
<ScrollViewer x:Name="PagePreviewPanel" Visibility="Collapsed"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Background="Transparent"
FocusVisualStyle="{x:Null}"
PreviewMouseWheel="PagePreview_PreviewMouseWheel"
SizeChanged="PagePreviewPanel_SizeChanged"
PreviewMouseDown="PagePreviewPanel_PreviewMouseDown"
PreviewMouseMove="PagePreviewPanel_PreviewMouseMove"
PreviewMouseUp="PagePreviewPanel_PreviewMouseUp"
MouseRightButtonUp="DocPaneBackground_RightClick"
AllowDrop="True" Drop="DropZone_Drop" DragOver="DropZone_DragOver">
<!-- Custom template: the VERTICAL scrollbar spans the full height (RowSpan=2),
covering the bottom-right corner cell, and the horizontal bar butts up
against it. The default template puts a white system-colored corner
rectangle there, which read as a stray white square against the dark
canvas whenever both scrollbars were visible.
The PART_ names below are TEMPLATE-scoped and so are unaffected by this
control being its own namescope - do not "fix" them. -->
<ScrollViewer.Template>
<ControlTemplate TargetType="ScrollViewer">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Row="0" Grid.Column="0"
Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CanContentScroll="{TemplateBinding CanContentScroll}"/>
<ScrollBar x:Name="PART_VerticalScrollBar" Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"
Panel.ZIndex="101"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
<ScrollBar x:Name="PART_HorizontalScrollBar" Grid.Row="1" Grid.Column="0"
Panel.ZIndex="101"
Orientation="Horizontal"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<Border x:Name="DocSurfacePad" VerticalAlignment="Top" HorizontalAlignment="Center" Padding="12">
<!-- Override the window's Display/ClearType text mode for the zoomed
document surface. Display + ClearType pixel-snaps and color-fringes
when scaled, giving hard-edged text on annotations and form fields;
Ideal + Grayscale stays smoothly anti-aliased at any zoom, matching
how other PDF editors render. -->
<Grid x:Name="PageContentGrid"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Grayscale">
<Grid.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="1"/>
</Grid.LayoutTransform>
<WrapPanel x:Name="PageContentPanel" Orientation="Horizontal">
<!-- Tile 0 (the primary page) is built in code by BuildPrimaryTile and
inserted at index 0; additional pages are rendered dynamically too. -->
</WrapPanel>
<!-- Continuous scroll panel - shown in Continuous view mode -->
<StackPanel x:Name="ContinuousPanel"
HorizontalAlignment="Center"
Orientation="Vertical"
Visibility="Collapsed"/>
<!-- Top-most layer for the selection marquee, so a drag can span pages -->
<Canvas x:Name="MarqueeLayer" IsHitTestVisible="False"/>
</Grid>
</Border>
</ScrollViewer>
<!-- #197: current-page badge. Replaces the cursor-following page tooltips: slides
up from the bottom corner on scroll and page changes, slides back down when
the view settles (ShowPageBadge, Viewport.cs). Never hit-testable, so it
cannot eat a page click. 26px right margin clears the vertical scrollbar. -->
<Grid x:Name="PageBadge" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="0,0,26,14" Panel.ZIndex="40" IsHitTestVisible="False" Opacity="0">
<Grid.RenderTransform>
<TranslateTransform x:Name="PageBadgeSlide" Y="46"/>
</Grid.RenderTransform>
<!-- Cast from a separate empty rectangle so the effect never rasterizes the
badge text. BarShadowOpacity keeps the weight consistent with the chrome
and lets 98SE suppress it without a special-case code path. -->
<Border Background="{DynamicResource MenuBackgroundBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}">
<Border.Effect>
<DropShadowEffect Color="Black" BlurRadius="7" ShadowDepth="2"
Direction="270" Opacity="{DynamicResource BarShadowOpacity}"
RenderingBias="Quality"/>
</Border.Effect>
</Border>
<Border Background="{DynamicResource MenuBackgroundBrush}"
BorderBrush="{DynamicResource MenuBorderBrush}" BorderThickness="1"
CornerRadius="{DynamicResource ControlCornerRadius}" Padding="9,3">
<Grid>
<TextBlock x:Name="PageBadgeText" FontFamily="Consolas" FontSize="12"
Foreground="{DynamicResource TextBrush}"/>
<!-- Grain OVER the content, family rule -->
<Border IsHitTestVisible="False" Margin="-9,-3"
CornerRadius="{DynamicResource ControlCornerRadius}"
Background="{DynamicResource GrainTileBrush}" Opacity="{DynamicResource GrainOpacity}"/>
</Grid>
</Border>
</Grid>
</Grid>
</Border>
</Grid><!-- /card row -->
</Grid>
</UserControl>