using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Effects; namespace MmdPdf { // Chrome for modal dialog windows: Configure (borderless window setup), Frame (the rounded card + // title bar + grain), and BuildTitleBar (the MmdPdf wordmark + red close button). internal static class DialogChrome { // Keep generated dialog captions on the same close mark as the main window. // E711 renders noticeably smaller inside the 18x16 Win98 caption face; E8BB is // the shared chrome glyph used by the main title bar and fills that face correctly. public const string CloseGlyph = "ξΆ»"; // Brush from the owner (then app) resources, with a safe fallback so the helper never throws. private static Brush Brush(Window? owner, string key, Brush fallback) => (owner?.TryFindResource(key) ?? Application.Current?.TryFindResource(key)) as Brush ?? fallback; private static T Value(Window? owner, string key, T fallback) => (owner?.TryFindResource(key) ?? Application.Current?.TryFindResource(key)) is T value ? value : fallback; // Builds the title bar. // win - the window being chromed (used for DragMove on the whole bar) // owner - supplies the themed brushes + the ChromeCloseButton style (pass the window's owner) // fullTitle - the complete title, e.g. "MmdPdf - Transform"; the "MmdPdf" part becomes the // wordmark and the remainder (" - Transform") is rendered in the courier title font // onClose - invoked when the red close button is clicked (e.g. set a result then Close()) public static Border BuildTitleBar(Window win, Window? owner, string? fullTitle, Action onClose) { // Transparent (not null) background so the WHOLE bar is hit-testable and acts as a drag handle. bool caption = Value(owner, "UseDialogCaption", false); var bar = new Border { Background = caption ? Brush(owner, "TitleBarBrush", Brushes.Navy) : Brushes.Transparent, SnapsToDevicePixels = true, UseLayoutRounding = true }; bar.MouseLeftButtonDown += (_, e) => { if (e.ButtonState == MouseButtonState.Pressed) win.DragMove(); }; var grid = new Grid { // KillerNotes uses the shared title-bar inset for its dialog caption too. In // particular, the 2px top inset keeps the 16px caption button centered in the // 20px classic band instead of riding against its upper edge. Margin = caption ? Value(owner, "TitleBarPadding", new Thickness(4, 2, 0, 0)) : new Thickness(0) }; grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); var wordmark = UiKit.WordmarkFont; var wordmarkPdf = UiKit.WordmarkFontPdf; // Build the wordmark row. A DropShadowEffect applied directly to text rasterizes it and // disables ClearType, which reads as blurry. So we LAYER it instead: a blurred black duplicate // sits behind a crisp, effect-free copy - soft shadow, sharp text. `shadow` paints the duplicate. StackPanel BuildWordmark(bool shadow) { var sp = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; Brush primary = shadow ? Brushes.Black : Brush(owner, "TextBrush", Brushes.White); Brush logo = shadow ? Brushes.Black : Brush(owner, "AccentLogo", Brushes.LimeGreen); Brush secondary = shadow ? Brushes.Black : Brush(owner, "MutedTextBrush", Brushes.Gray); int kp = fullTitle?.IndexOf("MmdPdf", StringComparison.Ordinal) ?? -1; if (kp >= 0) { // MMD + PDF in one TextBlock so the two sizes share a baseline (cohesive wordmark). var logoTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center }; logoTb.Inlines.Add(new System.Windows.Documents.Run("MMD ") { FontFamily = wordmark, FontWeight = FontWeights.Normal, FontSize = 16, Foreground = primary }); logoTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = wordmarkPdf, FontWeight = FontWeights.Bold, FontSize = 20.8, Foreground = logo }); sp.Children.Add(logoTb); string after = fullTitle![(kp + "MmdPdf".Length)..]; if (!string.IsNullOrEmpty(after)) sp.Children.Add(new TextBlock { Text = after, FontFamily = UiKit.MonoFont, FontSize = 14, Foreground = secondary, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(4, 1, 0, 0) }); } else { sp.Children.Add(new TextBlock { Text = fullTitle ?? "", FontFamily = UiKit.MonoFont, FontSize = 14, Foreground = primary, VerticalAlignment = VerticalAlignment.Center }); } return sp; } var title = new Grid { Margin = caption ? new Thickness(0) : new Thickness(16, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center }; if (caption) { title.Children.Add(new TextBlock { Text = fullTitle ?? "MmdPdf", FontFamily = Value(owner, "ChromeFontFamily", new FontFamily("Tahoma")), FontSize = 11, FontWeight = FontWeights.Bold, Foreground = Brush(owner, "ChromeTextBrush", Brushes.White), VerticalAlignment = VerticalAlignment.Center }); } else { var shadowLayer = BuildWordmark(true); shadowLayer.Opacity = 0.5; shadowLayer.Effect = new BlurEffect { Radius = 2 }; shadowLayer.RenderTransform = new TranslateTransform(0.7, 1.2); title.Children.Add(shadowLayer); title.Children.Add(BuildWordmark(false)); } Grid.SetColumn(title, 0); grid.Children.Add(title); // The close glyph and its complete raised/pressed face live in ChromeCloseButton. // Supplying another glyph/font/background here was overriding that canonical style and // produced the off-centre X and the exposed title-bar pixel seen in classic dialogs. var close = new Button { HorizontalAlignment = HorizontalAlignment.Right, // The hover face is part of the card's top-right corner. Centering a 26px // button in the 40px caption left a visible 7px strip above it. VerticalAlignment = VerticalAlignment.Top, Background = Brushes.Transparent, Cursor = Cursors.Hand, FocusVisualStyle = null, SnapsToDevicePixels = true, UseLayoutRounding = true }; if (owner?.TryFindResource("ChromeCloseButton") is Style chromeClose) { close.Style = chromeClose; } else { close.Content = CloseGlyph; close.FontFamily = UiKit.IconFont; close.FontSize = 10; close.Width = 46; close.Height = 36; close.Foreground = Brush(owner, "DangerRed", Brushes.Red); close.Background = Brushes.Transparent; close.BorderThickness = new Thickness(0); close.Cursor = Cursors.Hand; } close.SetResourceReference(FrameworkElement.WidthProperty, "DialogCloseWidth"); close.SetResourceReference(FrameworkElement.HeightProperty, "DialogCloseHeight"); close.SetResourceReference(FrameworkElement.MarginProperty, "DialogCaptionButtonsMargin"); // Resizable borderless dialogs use WindowChrome. Without this exemption its resize // band wins the top-right hit test, turning the close button into a resize handle. System.Windows.Shell.WindowChrome.SetIsHitTestVisibleInChrome(close, true); // Get the click before the caption's DragMove handler starts its modal mouse loop. close.PreviewMouseLeftButtonDown += (_, e) => { e.Handled = true; onClose(); }; Grid.SetColumn(close, 1); grid.Children.Add(close); bar.Child = grid; return bar; } // Borderless transparent window setup shared by every dialog. public static void Configure(Window win, Window? owner, bool resizable = false, bool fade = true) { win.Owner = owner; win.WindowStyle = WindowStyle.None; win.AllowsTransparency = true; win.Background = Brushes.Transparent; win.ResizeMode = resizable ? ResizeMode.CanResize : ResizeMode.NoResize; win.WindowStartupLocation = owner != null ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen; win.FontFamily = UiKit.UiFont; TextOptions.SetTextFormattingMode(win, TextFormattingMode.Display); TextOptions.SetTextRenderingMode(win, TextRenderingMode.Grayscale); if (fade) WindowFx.EnableFadeClose(win); } private static Border FrameRing(Window? owner, string brushKey, string thicknessKey, string? marginKey = null) { var ring = new Border { IsHitTestVisible = false, BorderBrush = Brush(owner, brushKey, Brushes.Transparent), BorderThickness = Value(owner, thicknessKey, new Thickness(0)) }; if (marginKey != null) ring.Margin = Value(owner, marginKey, new Thickness(0)); return ring; } private static UIElement WindowFrame(Window? owner) { var frame = new Grid { IsHitTestVisible = false }; frame.Children.Add(FrameRing(owner, "WindowFrameBrush", "DialogWindowFrameThickness", "WindowFrameMargin")); frame.Children.Add(FrameRing(owner, "FrameInnerLightBrush", "FrameInnerLightThickness", "FrameInnerMargin")); frame.Children.Add(FrameRing(owner, "FrameInnerDarkBrush", "FrameInnerDarkThickness", "FrameInnerMargin")); frame.Children.Add(FrameRing(owner, "FrameOuterLightBrush", "FrameOuterLightThickness")); frame.Children.Add(FrameRing(owner, "FrameOuterDarkBrush", "FrameOuterDarkThickness")); return frame; } internal static UIElement WrapContent(Window? owner, UIElement content) { var host = new Grid { Margin = Value(owner, "DialogHaloMargin", new Thickness(12)) }; var radius = Value(owner, "WindowCornerRadius", new CornerRadius(7)); host.Children.Add(new Border { Background = Brush(owner, "WindowFrameBrush", UiKit.Brush("MenuBackgroundBrush")), CornerRadius = radius, IsHitTestVisible = false, Effect = UiKit.ShadowDialog() }); var card = new Grid(); card.Children.Add(new Border { Background = Brush(owner, "BackgroundBrush", UiKit.Brush("BackgroundBrush")), CornerRadius = radius, Margin = Value(owner, "DialogWindowFramePadding", new Thickness(0)), Child = content }); card.Children.Add(WindowFrame(owner)); // The 1px window outline every dialog was missing: same DialogFrameBrush the file // picker draws (defaults to AppBorderBrush, the main window's DWM border tone). card.Children.Add(new Border { BorderBrush = Brush(owner, "DialogFrameBrush", UiKit.Brush("MenuBorderBrush")), BorderThickness = Value(owner, "DialogFrameThickness", new Thickness(1)), CornerRadius = radius, IsHitTestVisible = false, }); host.Children.Add(card); return host; } // Standard dialog: content is inset from the same five-layer frame used by KillerNotes. public static UIElement Frame(Window win, Window? owner, string title, Action onClose, UIElement body) { win.KeyDown += (_, e) => { if (e.Key == Key.Escape) { e.Handled = true; onClose(); } }; var card = new Border { // Print Preview already used BackgroundBrush directly. The shared frame used // MenuBackgroundBrush, making every other generated window a different color. Background = Brush(owner, "BackgroundBrush", UiKit.Brush("BackgroundBrush")), CornerRadius = UiKit.RadWindow, Margin = Value(owner, "WindowFramePadding", new Thickness(0)) }; var root = new DockPanel(); var titleBar = BuildTitleBar(win, owner, title, onClose); titleBar.Height = Value(owner, "DialogTitleBarHeight", 40.0); DockPanel.SetDock(titleBar, Dock.Top); root.Children.Add(titleBar); root.Children.Add(body); var grain = (owner as MainWindow)?.GrainTexture; if (grain != null) { var grid = new Grid(); double op = Application.Current?.Resources["GrainOpacity"] is double go ? go : 0.05; grid.Children.Add(new Border { CornerRadius = UiKit.RadWindow, IsHitTestVisible = false, Opacity = op, Background = new ImageBrush(grain) { TileMode = TileMode.Tile, ViewportUnits = BrushMappingMode.Absolute, Viewport = new Rect(0, 0, 256, 256), Stretch = Stretch.None } }); grid.Children.Add(root); card.Child = grid; } else { var grid = new Grid(); grid.Children.Add(root); card.Child = grid; } var framedContent = card.Child!; card.Child = null; return WrapContent(owner, framedContent); } internal static void AddBevels(Grid grid, Window? owner) { grid.Children.Add(new Border { IsHitTestVisible = false, BorderBrush = Brush(owner, "BevelLightBrush", Brushes.Transparent), BorderThickness = Value(owner, "BevelLightThickness", new Thickness(0)) }); grid.Children.Add(new Border { IsHitTestVisible = false, BorderBrush = Brush(owner, "BevelDarkBrush", Brushes.Transparent), BorderThickness = Value(owner, "BevelDarkThickness", new Thickness(0)) }); } } }