From 0e94080c35e1f00fa096563cbfe006faa42dc2bc Mon Sep 17 00:00:00 2001 From: Steve Stonebraker Date: Mon, 2 Mar 2026 22:47:11 -0600 Subject: [PATCH 1/2] Add Cmd+/Cmd-/Cmd+0 zoom shortcuts for editor and preview Implements transient per-document zoom as requested in #335: - Cmd+ zooms in (10% increments, max 300%) - Cmd- zooms out (10% decrements, min 50%) - Cmd+0 resets to actual size - Zoom applies to both editor and preview when preference is enabled - Zoom is transient (not saved to preferences) - Menu items validate at zoom limits - Does not mutate base font preference Fixes #335 --- MacDown/Code/Document/MPDocument.h | 4 ++ MacDown/Code/Document/MPDocument.m | 63 +++++++++++++++++++- MacDown/Localization/Base.lproj/MainMenu.xib | 16 +++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/MacDown/Code/Document/MPDocument.h b/MacDown/Code/Document/MPDocument.h index 312f5c09..1f795108 100644 --- a/MacDown/Code/Document/MPDocument.h +++ b/MacDown/Code/Document/MPDocument.h @@ -27,4 +27,8 @@ */ + (NSString *)toggleCheckboxAtIndex:(NSUInteger)index inMarkdown:(NSString *)markdown; +- (IBAction)zoomIn:(id)sender; +- (IBAction)zoomOut:(id)sender; +- (IBAction)resetZoom:(id)sender; + @end diff --git a/MacDown/Code/Document/MPDocument.m b/MacDown/Code/Document/MPDocument.m index 3148f3ce..ecef7666 100644 --- a/MacDown/Code/Document/MPDocument.m +++ b/MacDown/Code/Document/MPDocument.m @@ -247,6 +247,9 @@ typedef NS_ENUM(NSUInteger, MPWordCountType) { // Store file content in initializer until nib is loaded. @property (copy) NSString *loadedString; +// Transient per-document zoom level (not saved to preferences) +@property CGFloat zoomMultiplier; + - (void)scaleWebview; - (void)syncScrollers; - (void)syncScrollersReverse; @@ -394,6 +397,7 @@ - (instancetype)init self.shouldHandleBoundsChange = YES; self.shouldHandlePreviewBoundsChange = YES; self.previousSplitRatio = -1.0; + self.zoomMultiplier = 1.0; return self; } @@ -774,7 +778,23 @@ - (BOOL)validateUserInterfaceItem:(id)item { BOOL result = [super validateUserInterfaceItem:item]; SEL action = item.action; - if (action == @selector(toggleToolbar:)) + + // Zoom menu validation + if (action == @selector(zoomIn:)) + { + static const CGFloat kMaxZoom = 3.0; + return self.zoomMultiplier < kMaxZoom; + } + else if (action == @selector(zoomOut:)) + { + static const CGFloat kMinZoom = 0.5; + return self.zoomMultiplier > kMinZoom; + } + else if (action == @selector(resetZoom:)) + { + return self.zoomMultiplier != 1.0; + } + else if (action == @selector(toggleToolbar:)) { NSMenuItem *it = ((NSMenuItem *)item); it.title = self.toolbarVisible ? @@ -2108,7 +2128,7 @@ - (void)scaleWebview return; static const CGFloat defaultSize = 14.0; - CGFloat scale = fontSize / defaultSize; + CGFloat scale = (fontSize / defaultSize) * self.zoomMultiplier; #if 0 // Sadly, this doesn’t work correctly. @@ -2124,6 +2144,45 @@ - (void)scaleWebview #endif } +- (IBAction)zoomIn:(id)sender +{ + static const CGFloat kMaxZoom = 3.0; + if (self.zoomMultiplier >= kMaxZoom) + return; + + self.zoomMultiplier = MIN(self.zoomMultiplier + 0.1, kMaxZoom); + [self applyCurrentZoom]; +} + +- (IBAction)zoomOut:(id)sender +{ + static const CGFloat kMinZoom = 0.5; + if (self.zoomMultiplier <= kMinZoom) + return; + + self.zoomMultiplier = MAX(self.zoomMultiplier - 0.1, kMinZoom); + [self applyCurrentZoom]; +} + +- (IBAction)resetZoom:(id)sender +{ + self.zoomMultiplier = 1.0; + [self applyCurrentZoom]; +} + +- (void)applyCurrentZoom +{ + NSFont *baseFont = self.preferences.editorBaseFont; + CGFloat zoomedSize = baseFont.pointSize * self.zoomMultiplier; + + // Apply zoom to editor (transient, not saved to preferences) + [self.editor setFont:[NSFont fontWithName:baseFont.fontName + size:zoomedSize]]; + + // Apply zoom to preview + [self scaleWebview]; +} + /** * Updates cached positions of reference points (headers, standalone images) in both * editor and preview for scroll synchronization. diff --git a/MacDown/Localization/Base.lproj/MainMenu.xib b/MacDown/Localization/Base.lproj/MainMenu.xib index 57a5b077..4728f596 100644 --- a/MacDown/Localization/Base.lproj/MainMenu.xib +++ b/MacDown/Localization/Base.lproj/MainMenu.xib @@ -405,6 +405,22 @@ + + + + + + + + + + + + + + + + From 802b2732cd9e162c621d039bef650d65c9321c87 Mon Sep 17 00:00:00 2001 From: Steve Stonebraker Date: Fri, 20 Mar 2026 17:15:32 -0500 Subject: [PATCH 2/2] Address review feedback for zoom shortcuts - Move kMinZoom/kMaxZoom to file-scope constants - Fix preview zoom when previewZoomRelativeToBaseFontSize is off - Fix floating-point comparison in resetZoom: validation --- MacDown/Code/Document/MPDocument.m | 40 ++++++++++++++++-------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/MacDown/Code/Document/MPDocument.m b/MacDown/Code/Document/MPDocument.m index ecef7666..a7cffd3d 100644 --- a/MacDown/Code/Document/MPDocument.m +++ b/MacDown/Code/Document/MPDocument.m @@ -37,6 +37,9 @@ static NSString * const kMPDefaultAutosaveName = @"Untitled"; +static const CGFloat kMPMinZoom = 0.5; +static const CGFloat kMPMaxZoom = 3.0; + NS_INLINE NSString *MPEditorPreferenceKeyWithValueKey(NSString *key) { @@ -782,17 +785,15 @@ - (BOOL)validateUserInterfaceItem:(id)item // Zoom menu validation if (action == @selector(zoomIn:)) { - static const CGFloat kMaxZoom = 3.0; - return self.zoomMultiplier < kMaxZoom; + return self.zoomMultiplier < kMPMaxZoom; } else if (action == @selector(zoomOut:)) { - static const CGFloat kMinZoom = 0.5; - return self.zoomMultiplier > kMinZoom; + return self.zoomMultiplier > kMPMinZoom; } else if (action == @selector(resetZoom:)) { - return self.zoomMultiplier != 1.0; + return fabs(self.zoomMultiplier - 1.0) > 0.001; } else if (action == @selector(toggleToolbar:)) { @@ -2120,16 +2121,19 @@ - (void)redrawDivider - (void)scaleWebview { - if (!self.preferences.previewZoomRelativeToBaseFontSize) - return; + CGFloat scale = self.zoomMultiplier; - CGFloat fontSize = self.preferences.editorBaseFontSize; - if (fontSize <= 0.0) - return; + if (self.preferences.previewZoomRelativeToBaseFontSize) + { + CGFloat fontSize = self.preferences.editorBaseFontSize; + if (fontSize > 0.0) + { + static const CGFloat defaultSize = 14.0; + scale = (fontSize / defaultSize) + * self.zoomMultiplier; + } + } - static const CGFloat defaultSize = 14.0; - CGFloat scale = (fontSize / defaultSize) * self.zoomMultiplier; - #if 0 // Sadly, this doesn’t work correctly. // It looks fine, but selections are offset relative to the mouse cursor. @@ -2146,21 +2150,19 @@ - (void)scaleWebview - (IBAction)zoomIn:(id)sender { - static const CGFloat kMaxZoom = 3.0; - if (self.zoomMultiplier >= kMaxZoom) + if (self.zoomMultiplier >= kMPMaxZoom) return; - self.zoomMultiplier = MIN(self.zoomMultiplier + 0.1, kMaxZoom); + self.zoomMultiplier = MIN(self.zoomMultiplier + 0.1, kMPMaxZoom); [self applyCurrentZoom]; } - (IBAction)zoomOut:(id)sender { - static const CGFloat kMinZoom = 0.5; - if (self.zoomMultiplier <= kMinZoom) + if (self.zoomMultiplier <= kMPMinZoom) return; - self.zoomMultiplier = MAX(self.zoomMultiplier - 0.1, kMinZoom); + self.zoomMultiplier = MAX(self.zoomMultiplier - 0.1, kMPMinZoom); [self applyCurrentZoom]; }