From df5b4f87ba5b8e9b1be3bd654ed2498bede74cf8 Mon Sep 17 00:00:00 2001 From: JustAHubber Date: Thu, 11 Jun 2026 20:20:56 +0100 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20complete=20UI/UX=20and=20performanc?= =?UTF-8?q?e=20overhaul=20=E2=80=94=20cocoa=20brown=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI/UX: - New Themes/CocoaTheme.xaml: single source of truth for the palette, control styles, and vector icon geometries. All four windows (Main, Editor, Settings, History) now share consistent, professional chrome with a custom title bar and proper caption buttons. - Emoji glyphs replaced with hand-drawn vector Path icons everywhere. - Region selector gains snipping-tool UX: dimmed surround, caramel selection outline, live W x H size badge, and an instruction hint. - Editor: zoom percentage indicator, keyboard tool shortcuts now move the active-tool highlight, themed text-label dialog with Enter/Esc. - Settings: capture-history controls (toggle, retention, clear, open folder) and a View Logs button (Opus-Speaks C & D). Fixed the default file format combo silently never saving. - About dialog reads the real assembly version instead of a hard-coded string. Performance: - All windows migrated off AllowsTransparency to WindowChrome (Opus-Speaks B). Layered windows forced software composition of the entire surface - the region selector paid this across every monitor on each capture. - Removed every DropShadowEffect/BlurEffect (per-element software rendering). - Pixelate tool no longer renders the full screenshot into a RenderTargetBitmap (~33 MB at 4K) with a CroppedBitmap per block; it now copies just the covered region once and block-averages in a single buffer. - Frozen shared brushes for selection chrome; HighQuality bitmap scaling on the editor image to kill zoom-out shimmer artifacts. Verified by rendering Editor/Settings/History windows through an offscreen harness; service tests still pass (95/95). Version bumped to 2.1.0. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 13 +- MoneyShot/App.xaml | 15 +- MoneyShot/Editor/CanvasRenderer.cs | 92 ++- MoneyShot/MainWindow.xaml | 299 ++++------ MoneyShot/MainWindow.xaml.cs | 117 ++-- MoneyShot/MoneyShot.csproj | 6 +- MoneyShot/Themes/CocoaTheme.xaml | 690 ++++++++++++++++++++++ MoneyShot/Views/EditorWindow.xaml | 759 ++++++++++--------------- MoneyShot/Views/EditorWindow.xaml.cs | 140 ++--- MoneyShot/Views/HistoryWindow.xaml | 85 ++- MoneyShot/Views/HistoryWindow.xaml.cs | 22 +- MoneyShot/Views/RegionSelector.xaml | 19 +- MoneyShot/Views/RegionSelector.xaml.cs | 70 ++- MoneyShot/Views/SettingsWindow.xaml | 518 ++++++----------- MoneyShot/Views/SettingsWindow.xaml.cs | 86 ++- 15 files changed, 1764 insertions(+), 1167 deletions(-) create mode 100644 MoneyShot/Themes/CocoaTheme.xaml diff --git a/CLAUDE.md b/CLAUDE.md index 60dceef..da733b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Build / Run -The project is a WPF app targeting `net8.0-windows`. `EnableWindowsTargeting=true` is set in the csproj so it can be restored/built on non-Windows agents, but it can only be **run** on Windows. +The project is a WPF app targeting `net10.0-windows`. `EnableWindowsTargeting=true` is set in the csproj so it can be restored/built on non-Windows agents, but it can only be **run** on Windows. ```powershell dotnet restore MoneyShot/MoneyShot.csproj @@ -22,6 +22,17 @@ The base version lives in `MoneyShot/MoneyShot.csproj` (``, ` - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - + + + + + - - - - - - - - - - - - - - + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + - - - + + + + + - + - - - + + + + + + - - - - + + + + + + + diff --git a/MoneyShot/MainWindow.xaml.cs b/MoneyShot/MainWindow.xaml.cs index 4c6446d..51f6d72 100644 --- a/MoneyShot/MainWindow.xaml.cs +++ b/MoneyShot/MainWindow.xaml.cs @@ -135,44 +135,47 @@ private async Task CheckForUpdatesAsync(bool showUpToDateMessage, bool showError private void PopulateMonitorButtons() { var screens = _screenshotService.GetAllScreens(); - if (screens.Count > 1) + if (screens.Count <= 1) return; + + var label = new TextBlock + { + Text = "Individual monitors", + FontSize = 12, + Foreground = (Brush)FindResource("Cocoa.TextSecondaryBrush"), + Margin = new Thickness(0, 10, 0, 6) + }; + MonitorButtonsPanel.Children.Add(label); + + var subtleStyle = (Style)FindResource("SubtleButton"); + var monitorIcon = (Geometry)FindResource("Icon.Monitor"); + var iconStyle = (Style)FindResource("ToolIcon"); + for (int i = 0; i < screens.Count; i++) { - var separator = new Separator + var screenIndex = i; + var screen = screens[i]; + var isPrimary = screen.Primary ? " · primary" : string.Empty; + var hotkeyHint = screenIndex < MaxMonitorHotkeys ? $"Ctrl+Shift+{screenIndex + 1}" : null; + + var content = new StackPanel { Orientation = System.Windows.Controls.Orientation.Horizontal }; + content.Children.Add(new System.Windows.Shapes.Path { Style = iconStyle, Data = monitorIcon }); + content.Children.Add(new TextBlock { - Margin = new Thickness(0, 10, 0, 5), - Background = new SolidColorBrush(Color.FromRgb(85, 85, 85)) - }; - MonitorButtonsPanel.Children.Add(separator); + Text = $"Monitor {i + 1}{isPrimary}", + Margin = new Thickness(9, 0, 0, 0), + VerticalAlignment = System.Windows.VerticalAlignment.Center + }); - var label = new TextBlock + var button = new System.Windows.Controls.Button { - Text = "Individual Monitors:", - FontSize = 14, - Foreground = new SolidColorBrush(Colors.White), - Margin = new Thickness(0, 5, 0, 5), - HorizontalAlignment = System.Windows.HorizontalAlignment.Center + Content = content, + Style = subtleStyle, + Padding = new Thickness(14, 9, 14, 9), + Margin = new Thickness(0, 3, 0, 3), + HorizontalContentAlignment = System.Windows.HorizontalAlignment.Left, + ToolTip = hotkeyHint == null ? null : $"Capture this monitor (hotkey: {hotkeyHint})" }; - MonitorButtonsPanel.Children.Add(label); - - for (int i = 0; i < screens.Count; i++) - { - var screenIndex = i; - var screen = screens[i]; - var isPrimary = screen.Primary ? " (Primary)" : ""; - var button = new System.Windows.Controls.Button - { - Content = $"🖥️ Monitor {i + 1}{isPrimary}", - Padding = new Thickness(20, 10, 20, 10), - Margin = new Thickness(0, 3, 0, 3), - FontSize = 14, - Background = new SolidColorBrush(Color.FromRgb(62, 62, 66)), - Foreground = new SolidColorBrush(Colors.White), - BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85)), - Cursor = System.Windows.Input.Cursors.Hand - }; - button.Click += (s, ev) => CaptureMonitor(screenIndex); - MonitorButtonsPanel.Children.Add(button); - } + button.Click += (s, ev) => CaptureMonitor(screenIndex); + MonitorButtonsPanel.Children.Add(button); } } @@ -461,32 +464,30 @@ private void Settings_Click(object sender, RoutedEventArgs e) ShowSettings(); } + private void History_Click(object sender, RoutedEventArgs e) + { + ShowHistory(); + } + private void About_Click(object sender, RoutedEventArgs e) { var settings = _settingsService.LoadSettings(); var screens = _screenshotService.GetAllScreens(); - var monitorHotkeys = screens.Count > 1 ? $"\n• Ctrl+Shift+1-{Math.Min(screens.Count, MaxMonitorHotkeys)} - Capture individual monitors" : ""; - + var monitorHotkeys = screens.Count > 1 ? $"\n• Ctrl+Shift+1-{Math.Min(screens.Count, MaxMonitorHotkeys)} — Capture individual monitors" : ""; + // SemVer portion only — the 4th part is the CI build number and isn't meaningful to users. + var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + var displayVersion = version == null ? "unknown" : $"{version.Major}.{version.Minor}.{version.Build}"; + System.Windows.MessageBox.Show( - "Money Shot - Incredible AI Slop\n\n" + - "Version 2.0.0\n\n" + + "Money Shot — screenshot capture and annotation for Windows\n\n" + + $"Version {displayVersion}\n" + "Developed by Daolyap & iSaluki\n\n" + - "Features:\n" + - "• Full screen, region, and individual monitor capture\n" + - "• Multi-monitor support\n" + - "• Rich annotation tools (shapes, text, arrows, numbers, blur)\n" + - "• Customizable hotkeys\n" + - "• Save to file or clipboard\n" + - "• System tray integration\n" + - "• Start in tray option\n" + - "• Disable default print screen behaviour\n\n" + - "Current Hotkeys:\n" + - $"• {settings.HotKeyCapture} - Capture full screen\n" + - $"• {settings.HotKeyRegionCapture} - Capture region" + + "Current hotkeys:\n" + + $"• {settings.HotKeyCapture} — Capture full screen\n" + + $"• {settings.HotKeyRegionCapture} — Capture region" + monitorHotkeys + - "\n\nContact:\n" + - "Features - https://github.com/daolyap/moneyshot/issues\n" + - "Security - moneyshot@daolyap.dev", + "\n\nFeedback & issues: https://github.com/Daolyap/Money-Shot/issues\n" + + "Security contact: moneyshot@daolyap.dev", "About Money Shot", MessageBoxButton.OK, MessageBoxImage.Information); @@ -552,21 +553,15 @@ private void MaximizeRestore_Click(object sender, RoutedEventArgs e) if (WindowState == WindowState.Maximized) { WindowState = WindowState.Normal; - if (MaximizeRestoreButton != null) - { - MaximizeRestoreButton.Content = "🗖"; - } + MaximizeRestoreIcon.Data = (Geometry)FindResource("Icon.WindowMaximize"); } else { WindowState = WindowState.Maximized; - if (MaximizeRestoreButton != null) - { - MaximizeRestoreButton.Content = "🗗"; - } + MaximizeRestoreIcon.Data = (Geometry)FindResource("Icon.WindowRestore"); } } - + private void Close_Click(object sender, RoutedEventArgs e) { Close(); diff --git a/MoneyShot/MoneyShot.csproj b/MoneyShot/MoneyShot.csproj index c6a3c8d..952a93c 100644 --- a/MoneyShot/MoneyShot.csproj +++ b/MoneyShot/MoneyShot.csproj @@ -9,9 +9,9 @@ true app.manifest favicon.ico - 2.0.0 - 2.0.0.0 - 2.0.0.0 + 2.1.0 + 2.1.0.0 + 2.1.0.0 Daolyap & iSaluki Daolyap & iSaluki Money Shot Screenshot Tool diff --git a/MoneyShot/Themes/CocoaTheme.xaml b/MoneyShot/Themes/CocoaTheme.xaml new file mode 100644 index 0000000..8b07d84 --- /dev/null +++ b/MoneyShot/Themes/CocoaTheme.xaml @@ -0,0 +1,690 @@ + + + + + + #221A13 + #2A2018 + #2E241A + #3A2D20 + #46382A + #534333 + #4C3C2B + #3A2E22 + #C28E5C + #D2A06E + #AD7C4D + #403122 + #271C11 + #F1E9DE + #C8B69E + #97846C + #C75048 + #191209 + + + + + + + + + + + + + + + + + + + + + + + M5,2 L5,15 L8.2,12.2 L10.4,17 L12.6,16 L10.4,11.4 L14.6,11.4 Z + M5.5,1.5 V14.5 H18.5 M1.5,5.5 H14.5 V18.5 + M2.5,4.5 H17.5 V15.5 H2.5 Z + M10,4.5 A7.5,5.5 0 1 0 10,15.5 A7.5,5.5 0 1 0 10,4.5 Z + M4,16 L15,5 M15,5 H9 M15,5 V11 + M3.5,16.5 L16.5,3.5 + M3,15 C5,7 7,17 10,10 C12,5.5 14,8 17,4.5 + M7.5,3.5 L6,16.5 M13.5,3.5 L12,16.5 M4,7.5 H16.5 M3.5,12.5 H16 + M4,5.5 V3.5 H16 V5.5 M10,3.5 V16.5 M8,16.5 H12 + M3,3 H8 V8 H3 Z M8,8 H13 V13 H8 Z M13,3 H18 V8 H13 Z M3,13 H8 V18 H3 Z M13,13 H18 V18 H13 Z + M4,8 H12.5 A4.5,4.5 0 0 1 12.5,17 H7 M7.5,4.5 L4,8 L7.5,11.5 + M16.5,10 A6.5,6.5 0 1 1 13.2,4.35 M13.2,1.2 V4.7 H16.7 + M8.5,3 A5.5,5.5 0 1 0 8.5,14 A5.5,5.5 0 1 0 8.5,3 M12.7,12.7 L17,17 M8.5,6 V11 M6,8.5 H11 + M8.5,3 A5.5,5.5 0 1 0 8.5,14 A5.5,5.5 0 1 0 8.5,3 M12.7,12.7 L17,17 M6,8.5 H11 + M8.5,3 A5.5,5.5 0 1 0 8.5,14 A5.5,5.5 0 1 0 8.5,3 M12.7,12.7 L17,17 + M3.5,3.5 H13.5 L16.5,6.5 V16.5 H3.5 Z M6.5,3.5 V7.5 H12.5 V3.5 M5.5,16.5 V11.5 H14.5 V16.5 + M7.5,5.5 V2.5 H17.5 V12.5 H14.5 M2.5,5.5 H12.5 V17.5 H2.5 Z + M3.5,6.5 H6.5 L8,4.5 H12 L13.5,6.5 H16.5 V15.5 H3.5 Z M10,8.5 A2.6,2.6 0 1 0 10,13.7 A2.6,2.6 0 1 0 10,8.5 + M3.5,6.5 V3.5 H6.5 M13.5,3.5 H16.5 V6.5 M16.5,13.5 V16.5 H13.5 M6.5,16.5 H3.5 V13.5 + M2.5,3.5 H17.5 V13.5 H2.5 Z M7,16.5 H13 M10,13.5 V16.5 + M10,3 A7,7 0 1 0 10,17 A7,7 0 1 0 10,3 M10,6 V10 L13,12 + M2.5,4.5 H8 L9.5,6.5 H17.5 V15.5 H2.5 Z + M3,5.5 H17 M3,10 H17 M3,14.5 H17 M7,3.5 V7.5 M13,8 V12 M5.5,12.5 V16.5 + M10,3 A7,7 0 1 0 10,17 A7,7 0 1 0 10,3 M10,9 V13.5 M10,6.4 V6.9 + M6.5,7 A3.5,3.5 0 1 1 10,10.5 V12.5 M10,15.4 V15.9 + + + M0,5 H10 + M0.5,0.5 H9.5 V9.5 H0.5 Z + M2.5,2.5 V0.5 H9.5 V7.5 H7.5 M0.5,2.5 H7.5 V9.5 H0.5 Z + M0.5,0.5 L9.5,9.5 M9.5,0.5 L0.5,9.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MoneyShot/Views/EditorWindow.xaml b/MoneyShot/Views/EditorWindow.xaml index 2d4d4e6..a3985ba 100644 --- a/MoneyShot/Views/EditorWindow.xaml +++ b/MoneyShot/Views/EditorWindow.xaml @@ -1,477 +1,332 @@ - - - + ResizeMode="CanResize" + UseLayoutRounding="True" + TextOptions.TextFormattingMode="Display"> + + + + - - + + + + + + - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - + - - - - - - + + + + + + - - - - + - - - - + - - - - - - + + + + - - - - - - - - + - + + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/MoneyShot/Views/EditorWindow.xaml.cs b/MoneyShot/Views/EditorWindow.xaml.cs index 09990e3..166de3a 100644 --- a/MoneyShot/Views/EditorWindow.xaml.cs +++ b/MoneyShot/Views/EditorWindow.xaml.cs @@ -78,6 +78,17 @@ public partial class EditorWindow : Window // Cached pen for hit testing to avoid repeated allocations private static readonly Pen HitTestPen = new(Brushes.Black, 10); + // Selection chrome (border + handles). Bright caramel matches the cocoa theme and stays + // visible against most screenshot content. Frozen so it can be shared by every handle. + private static readonly SolidColorBrush SelectionBrush = CreateFrozen(Color.FromRgb(0xE8, 0xA8, 0x5C)); + + private static SolidColorBrush CreateFrozen(Color color) + { + var brush = new SolidColorBrush(color); + brush.Freeze(); + return brush; + } + // Middle-mouse pan state. Held while the user is dragging with MMB to translate the view. private bool _isPanning; private Point _panStartPoint; @@ -169,36 +180,36 @@ private void EditorWindow_KeyDown(object sender, KeyEventArgs e) switch (e.Key) { case Key.R: - _currentTool = AnnotationTool.Rectangle; + SelectTool(AnnotationTool.Rectangle); e.Handled = true; break; case Key.C when !e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Control): - _currentTool = AnnotationTool.Circle; + SelectTool(AnnotationTool.Circle); e.Handled = true; break; case Key.A when !e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Control): - _currentTool = AnnotationTool.Arrow; + SelectTool(AnnotationTool.Arrow); e.Handled = true; break; case Key.L: - _currentTool = AnnotationTool.Line; + SelectTool(AnnotationTool.Line); e.Handled = true; break; case Key.F: - _currentTool = AnnotationTool.Freehand; + SelectTool(AnnotationTool.Freehand); e.Handled = true; break; case Key.T: - _currentTool = AnnotationTool.Text; + SelectTool(AnnotationTool.Text); e.Handled = true; break; case Key.P: - _currentTool = AnnotationTool.Blur; + SelectTool(AnnotationTool.Blur); e.Handled = true; break; case Key.D1: case Key.NumPad1: - _currentTool = AnnotationTool.Number; + SelectTool(AnnotationTool.Number); e.Handled = true; break; case Key.Escape: @@ -409,7 +420,7 @@ internal void UndoCrop(BitmapSource previousImage, IReadOnlyList prev _cropRectangle = null; _isCropping = false; _numberCounter = previousNumberCounter; - _currentTool = AnnotationTool.Cursor; + SelectTool(AnnotationTool.Cursor); ClearSelection(); } @@ -862,65 +873,64 @@ private TextBlock CreateNumberLabel() private TextBlock? CreateTextLabel() { - // Show a simple input dialog + // Small modal prompt for the label text, styled from the cocoa theme. var inputDialog = new Window { - Title = "Enter Text", - Width = 300, - Height = 150, + Title = "Add text", + Width = 340, + SizeToContent = SizeToContent.Height, + ResizeMode = ResizeMode.NoResize, WindowStartupLocation = WindowStartupLocation.CenterOwner, Owner = this, - Background = new SolidColorBrush(Color.FromRgb(45, 45, 48)) + Background = (Brush)FindResource("Cocoa.WindowBrush"), + Foreground = (Brush)FindResource("Cocoa.TextBrush") }; - var grid = new Grid { Margin = new Thickness(10) }; + var grid = new Grid { Margin = new Thickness(16) }; + grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); - grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); var label = new TextBlock { - Text = "Enter text:", - Foreground = Brushes.White, - Margin = new Thickness(0, 0, 0, 5) + Text = "Label text", + Foreground = (Brush)FindResource("Cocoa.TextSecondaryBrush"), + Margin = new Thickness(0, 0, 0, 6) }; Grid.SetRow(label, 0); grid.Children.Add(label); var textBox = new TextBox { - Margin = new Thickness(0, 5, 0, 10), - Padding = new Thickness(5), - Background = new SolidColorBrush(Color.FromRgb(62, 62, 66)), - Foreground = Brushes.White, - BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85)) + Style = (Style)FindResource("CocoaTextBox"), + Margin = new Thickness(0, 0, 0, 14) }; Grid.SetRow(textBox, 1); grid.Children.Add(textBox); var okButton = new Button { - Content = "OK", - Padding = new Thickness(20, 5, 20, 5), - Background = new SolidColorBrush(Color.FromRgb(14, 99, 156)), - Foreground = Brushes.White, - BorderBrush = new SolidColorBrush(Color.FromRgb(17, 119, 187)), - HorizontalAlignment = HorizontalAlignment.Right + Content = "Add", + Style = (Style)FindResource("AccentButton"), + MinWidth = 76, + IsDefault = true }; okButton.Click += (s, e) => inputDialog.DialogResult = true; + var cancelButton = new Button + { + Content = "Cancel", + Style = (Style)FindResource("SubtleButton"), + MinWidth = 76, + Margin = new Thickness(0, 0, 8, 0), + IsCancel = true + }; + var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right }; - var cancelButton = new Button - { - Content = "Cancel", - Padding = new Thickness(20, 5, 20, 5), - Margin = new Thickness(0, 0, 8, 0) - }; - cancelButton.Click += (s, e) => inputDialog.DialogResult = false; buttonPanel.Children.Add(cancelButton); buttonPanel.Children.Add(okButton); Grid.SetRow(buttonPanel, 2); @@ -1155,7 +1165,7 @@ private void SelectElement(UIElement element) // Add visual indicator for selection _selectionBorder = new Border { - BorderBrush = new SolidColorBrush(Colors.Blue), + BorderBrush = SelectionBrush, BorderThickness = new Thickness(2), IsHitTestVisible = false }; @@ -1224,7 +1234,7 @@ private void CreateResizeHandles(double left, double top, double width, double h { ClearResizeHandlesOnly(); - var handleColor = new SolidColorBrush(Colors.Blue); + var handleColor = SelectionBrush; // 8-handle bounding box. Corner handles drive proportional resize, edges drive single-axis. _resizeHandles.Add(CreateResizeHandle(left - 2, top - 2, handleColor, ElementResizeMode.TopLeft)); @@ -1240,7 +1250,7 @@ private void CreateResizeHandles(double left, double top, double width, double h private void CreateEndpointHandles(Point start, Point end) { ClearResizeHandlesOnly(); - var handleColor = new SolidColorBrush(Colors.Blue); + var handleColor = SelectionBrush; var startHandle = CreateEndpointHandle(start, handleColor, isStart: true); var endHandle = CreateEndpointHandle(end, handleColor, isStart: false); _resizeHandles.Add(startHandle); @@ -1704,28 +1714,26 @@ private void ToolButton_Click(object sender, RoutedEventArgs e) { if (Enum.TryParse(toolName, out var tool)) { - _currentTool = tool; - UpdateActiveToolButton(button); + SelectTool(tool); } } } /// - /// Highlights the toolbar button for the active tool by swapping its style. Walks up the - /// visual tree to find the WrapPanel that holds all tool buttons so we don't need named - /// references for each one. + /// Switches the active tool and highlights its toolbar button. Used by both toolbar clicks + /// and keyboard shortcuts so the highlight never goes out of sync with the actual tool. /// - private void UpdateActiveToolButton(Button activeButton) + private void SelectTool(AnnotationTool tool) { - var glassStyle = (Style)Resources["GlassButton"]; - var activeStyle = (Style)Resources["ActiveToolButton"]; - var parent = System.Windows.Media.VisualTreeHelper.GetParent(activeButton); - if (parent is not Panel toolPanel) return; - foreach (var child in toolPanel.Children) + _currentTool = tool; + var normalStyle = (Style)FindResource("ToolButton"); + var activeStyle = (Style)FindResource("ToolButtonActive"); + var toolName = tool.ToString(); + foreach (var child in ToolButtonsPanel.Children) { - if (child is Button b && b.Tag is string) + if (child is Button b && b.Tag is string tag) { - b.Style = ReferenceEquals(b, activeButton) ? activeStyle : glassStyle; + b.Style = tag == toolName ? activeStyle : normalStyle; } } } @@ -1842,10 +1850,12 @@ private void ChangeElementColor(UIElement element, Color newColor) private bool IsColorChangeableElement(UIElement element) { - // Pixelate rectangles have DrawingBrush and should not be color-changed - if (element is Rectangle rect && rect.Fill is DrawingBrush) + // Pixelate rectangles render an ImageBrush of the underlying pixels; recolouring them + // makes no sense and would just paint a visible stroke. (Detect via tag — the fill is + // an ImageBrush, not a DrawingBrush, so a brush-type check doesn't identify them.) + if (element is Rectangle { Tag: PixelateTag }) return false; - + return element is Shape || element is TextBlock; } @@ -1921,7 +1931,7 @@ private void ApplyCrop() _numberCounter = 1; // Reset to cursor tool - _currentTool = AnnotationTool.Cursor; + SelectTool(AnnotationTool.Cursor); _undo.Push(new UndoController.CropUndoAction(previousImage, previousElements, previousNumberCounter)); } catch (Exception ex) @@ -2015,6 +2025,10 @@ private void ApplyZoom() { ZoomTransform.ScaleX = _zoomLevel; ZoomTransform.ScaleY = _zoomLevel; + if (ZoomLevelLabel != null) + { + ZoomLevelLabel.Text = $"{Math.Round(_zoomLevel * 100)}%"; + } } private BitmapSource CaptureCanvasAsImage() => @@ -2051,21 +2065,15 @@ private void MaximizeRestore_Click(object sender, RoutedEventArgs e) if (WindowState == WindowState.Maximized) { WindowState = WindowState.Normal; - if (MaximizeRestoreButton != null) - { - MaximizeRestoreButton.Content = "🗖"; - } + MaximizeRestoreIcon.Data = (Geometry)FindResource("Icon.WindowMaximize"); } else { WindowState = WindowState.Maximized; - if (MaximizeRestoreButton != null) - { - MaximizeRestoreButton.Content = "🗗"; - } + MaximizeRestoreIcon.Data = (Geometry)FindResource("Icon.WindowRestore"); } } - + private void Close_Click(object sender, RoutedEventArgs e) { Close(); diff --git a/MoneyShot/Views/HistoryWindow.xaml b/MoneyShot/Views/HistoryWindow.xaml index 59e7c9a..de93d63 100644 --- a/MoneyShot/Views/HistoryWindow.xaml +++ b/MoneyShot/Views/HistoryWindow.xaml @@ -1,41 +1,86 @@ + Background="{StaticResource Cocoa.WindowBrush}" + Foreground="{StaticResource Cocoa.TextBrush}" + FontFamily="Segoe UI" + WindowStyle="None" + ResizeMode="CanResize" + UseLayoutRounding="True" + TextOptions.TextFormattingMode="Display"> + + + + + + - + + + + + + + + + + + + + + + + - - + + - + + @@ -45,13 +90,19 @@ - + + - + + + + + + + + + - + - - - - - -