Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0b93004
Handle grouped zero-corner flips
pascalpp Jun 13, 2026
c7e3f6c
Wire and guard grouped ruler flips
pascalpp Jun 13, 2026
db24323
Add flip ruler UI
pascalpp Jun 13, 2026
e84f0d6
Verify flippable ruler integration
pascalpp Jun 13, 2026
031c854
Derive flipped label test scale from screen
pascalpp Jun 13, 2026
00f4c51
Ignore Caps Lock for ruler hotkeys
pascalpp Jun 13, 2026
5aafebb
Address flip ruler review comments
pascalpp Jun 13, 2026
94e01f9
Anchor labels/handles to opposite sides and update tests
pascalpp Jun 13, 2026
67b5a72
Encapsulate resize-handle placement logic
pascalpp Jun 13, 2026
8cad8a2
Add UnitLabelView and integrate unit labels
pascalpp Jun 13, 2026
cf0702c
Simplify unit label positioning and add insets
pascalpp Jun 13, 2026
51ac56c
Adjust unit label insets and update tests
pascalpp Jun 13, 2026
47d3046
Refactor mouse tick layout & handle visibility
pascalpp Jun 13, 2026
5692401
Add mouse tick label preview views
pascalpp Jun 13, 2026
04802ec
Propagate zeroCorner and refactor previews
pascalpp Jun 13, 2026
a806c6d
Remove RuleViewPreview and simplify previews
pascalpp Jun 13, 2026
211e652
Update Free Ruler help screen
pascalpp Jun 13, 2026
e452fdb
Use current app icon in help book
pascalpp Jun 13, 2026
bd917dc
Update help icon when generating app icons
pascalpp Jun 13, 2026
c0cfeab
Stabilize Free Ruler help book caching
pascalpp Jun 14, 2026
640bbee
Use timestamped help icon filenames
pascalpp Jun 14, 2026
028e0c5
Use CSS var for help icon and refresh assets
pascalpp Jun 14, 2026
9a280ed
Update project.pbxproj
pascalpp Jun 14, 2026
903df52
Update project.pbxproj
pascalpp Jun 14, 2026
3e4f065
Add help-index generator and npm script
pascalpp Jun 14, 2026
f761bef
Fix help bundle identifier & version handling
pascalpp Jun 14, 2026
f43c144
Update set-version.js
pascalpp Jun 14, 2026
cb45972
revert help menu handler
pascalpp Jun 14, 2026
0f71340
Update bump-version.js
pascalpp Jun 14, 2026
35f1a8c
Update generate-app-icon.sh
pascalpp Jun 14, 2026
7383b04
Add interactive mouse tick label preview UI
pascalpp Jun 14, 2026
6ca2be4
Centralize resize handle end-region check
pascalpp Jun 14, 2026
9eff253
Avoid mouse tick label overlapping resize handle
pascalpp Jun 14, 2026
62eac58
Propagate zeroCorner and refactor geometry
pascalpp Jun 14, 2026
cf51789
tweak mouse tick layout and update tests
pascalpp Jun 14, 2026
4167666
Address PR feedback on ruler labels and help assets
pascalpp Jun 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Free Ruler.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@
50B1D3532D05D00100B1D135 /* RulerCursorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3522D05D00100B1D135 /* RulerCursorController.swift */; };
50B1D3552D05E00000B1D138 /* RulerTickLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3542D05E00000B1D138 /* RulerTickLayout.swift */; };
50B1D3592D06000100B1D139 /* AppIconRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3582D06000100B1D139 /* AppIconRenderer.swift */; };
50B1D3602D06000600B1D139 /* AppIconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D35F2D06000600B1D139 /* AppIconGenerator.swift */; };
50B1D35D2D06000400B1D139 /* AppIconRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3582D06000100B1D139 /* AppIconRenderer.swift */; };
50B1D3602D06000600B1D139 /* AppIconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D35F2D06000600B1D139 /* AppIconGenerator.swift */; };
50B1D3612D06000700B1D139 /* AppIconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D35F2D06000600B1D139 /* AppIconGenerator.swift */; };
50B1D3652D06010100B1D139 /* AppStoreScreenshotPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3642D06010100B1D139 /* AppStoreScreenshotPreview.swift */; };
50B1D3662D06010200B1D139 /* AppStoreScreenshotPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3642D06010100B1D139 /* AppStoreScreenshotPreview.swift */; };
50B1D3672D06100000B1D13A /* ResizeHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3682D06100000B1D13A /* ResizeHandleView.swift */; };
50B1D3692D06100000B1D13A /* ResizeHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3682D06100000B1D13A /* ResizeHandleView.swift */; };
50B1D36A2D06110000B1D13B /* UnitLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D36C2D06110000B1D13B /* UnitLabelView.swift */; };
50B1D36B2D06110000B1D13B /* UnitLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D36C2D06110000B1D13B /* UnitLabelView.swift */; };
50C6D891228BDBAD0091F19E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50C6D890228BDBAD0091F19E /* Images.xcassets */; };
50D7BEE7227D42FD0008B95E /* RulerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEE6227D42FD0008B95E /* RulerController.swift */; };
50D7BEE9227D43270008B95E /* Ruler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEE8227D43270008B95E /* Ruler.swift */; };
Expand Down Expand Up @@ -118,6 +120,7 @@
50B1D35F2D06000600B1D139 /* AppIconGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconGenerator.swift; sourceTree = "<group>"; };
50B1D3642D06010100B1D139 /* AppStoreScreenshotPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreScreenshotPreview.swift; sourceTree = "<group>"; };
50B1D3682D06100000B1D13A /* ResizeHandleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResizeHandleView.swift; sourceTree = "<group>"; };
50B1D36C2D06110000B1D13B /* UnitLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitLabelView.swift; sourceTree = "<group>"; };
50C6D890228BDBAD0091F19E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
50D7BEE6227D42FD0008B95E /* RulerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulerController.swift; sourceTree = "<group>"; };
50D7BEE8227D43270008B95E /* Ruler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ruler.swift; sourceTree = "<group>"; };
Expand All @@ -139,7 +142,7 @@
8F629823243003EA004F9099 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = "<group>"; };
8F629825243003F6004F9099 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreferencesController.xib; sourceTree = "<group>"; };
8F629828243003FF004F9099 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/PreferencesController.strings; sourceTree = "<group>"; };
AB053EE93E1DC9AF341A8D4F /* Free Ruler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = "Free Ruler.app"; path = "Free Ruler.app"; sourceTree = BUILT_PRODUCTS_DIR; };
AB053EE93E1DC9AF341A8D4F /* Free Ruler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Free Ruler.app"; sourceTree = BUILT_PRODUCTS_DIR; };
B894A5002BBFE61A005A3B6F /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MainMenu.strings; sourceTree = "<group>"; };
B894A5012BBFE61A005A3B6F /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/PreferencesController.strings; sourceTree = "<group>"; };
D9DBE8A12C791B1600A42589 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainMenu.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -252,6 +255,7 @@
50D7BEEA227D432E0008B95E /* RulerWindow.swift */,
50D7BEEC227D5C810008B95E /* RuleView.swift */,
50B1D3682D06100000B1D13A /* ResizeHandleView.swift */,
50B1D36C2D06110000B1D13B /* UnitLabelView.swift */,
50B1D3542D05E00000B1D138 /* RulerTickLayout.swift */,
6F41029E22607DC900F06A10 /* HorizontalRule.swift */,
5012CAAC226AB09000BD9565 /* VerticalRule.swift */,
Expand Down Expand Up @@ -479,6 +483,7 @@
6F4102892260712F00F06A10 /* AppDelegate.swift in Sources */,
50D7BEED227D5C810008B95E /* RuleView.swift in Sources */,
50B1D3672D06100000B1D13A /* ResizeHandleView.swift in Sources */,
50B1D36A2D06110000B1D13B /* UnitLabelView.swift in Sources */,
50D7BEE9227D43270008B95E /* Ruler.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -504,6 +509,7 @@
23E2B80975F07733EB6CF5DB /* AppDelegate.swift in Sources */,
9A082BBC4A583513B0858C41 /* RuleView.swift in Sources */,
50B1D3692D06100000B1D13A /* ResizeHandleView.swift in Sources */,
50B1D36B2D06110000B1D13B /* UnitLabelView.swift in Sources */,
E6BC6663BAF0D11D7A9D2195 /* Ruler.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
159 changes: 157 additions & 2 deletions Free Ruler/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ private enum HotkeyBezelLocalizationKey: String {
case rulersUngrouped = "HotkeyBezel.RulersUngrouped"
case shadowEnabled = "HotkeyBezel.ShadowEnabled"
case shadowDisabled = "HotkeyBezel.ShadowDisabled"
case horizontalOriginFormat = "HotkeyBezel.HorizontalOriginFormat"
case verticalOriginFormat = "HotkeyBezel.VerticalOriginFormat"
case originLeft = "HotkeyBezel.OriginLeft"
case originRight = "HotkeyBezel.OriginRight"
case originTop = "HotkeyBezel.OriginTop"
case originBottom = "HotkeyBezel.OriginBottom"
case unitsFormat = "HotkeyBezel.UnitsFormat"
case pixelsUnit = "Unit.Pixels.Abbreviation"
case millimetersUnit = "Unit.Millimeters.Abbreviation"
Expand All @@ -57,6 +63,18 @@ private enum HotkeyBezelLocalizationKey: String {
return "Hotkey status bezel text indicating ruler shadow is enabled"
case .shadowDisabled:
return "Hotkey status bezel text indicating ruler shadow is disabled"
case .horizontalOriginFormat:
return "Hotkey status bezel format for the horizontal ruler origin side"
case .verticalOriginFormat:
return "Hotkey status bezel format for the vertical ruler origin side"
case .originLeft:
return "Hotkey status bezel value for a ruler origin on the left"
case .originRight:
return "Hotkey status bezel value for a ruler origin on the right"
case .originTop:
return "Hotkey status bezel value for a ruler origin at the top"
case .originBottom:
return "Hotkey status bezel value for a ruler origin at the bottom"
case .unitsFormat:
return "Hotkey status bezel format for the selected measurement unit"
case .pixelsUnit:
Expand Down Expand Up @@ -127,7 +145,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
#endif

showRulers()

}

#if DEBUG
Expand Down Expand Up @@ -218,6 +235,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
prefs.observe(\Prefs.rulerColor, options: .new) { prefs, changed in
self.redrawRulers()
},
prefs.observe(\Prefs.zeroCorner, options: .new) { prefs, changed in
self.redrawRulers()
},
]
}

Expand Down Expand Up @@ -507,7 +527,108 @@ class AppDelegate: NSObject, NSApplicationDelegate {
toggleRuler(orientation: .vertical)
}

func performRulerHotkey(keyCode: Int, sender: Any) -> Bool {
@IBAction func flipHorizontalRuler(_ sender: Any) {
flipRulers(along: .horizontal)
showHorizontalOriginHotkeyBezel(on: bezelScreen(for: sender))
}

@IBAction func flipVerticalRuler(_ sender: Any) {
flipRulers(along: .vertical)
showVerticalOriginHotkeyBezel(on: bezelScreen(for: sender))
}

func flipRulers(along orientation: Orientation) {
createRulersIfNeeded()

let oldGeometry = ZeroCornerGeometry(zeroCorner: prefs.zeroCorner)
let flippedCorner = prefs.zeroCorner.flipped(along: orientation)
let flippedRuler = existingRulerController(orientation: orientation)
Comment thread
pascalpp marked this conversation as resolved.
let otherOrientation: Orientation = orientation == .horizontal ? .vertical : .horizontal
let otherRuler = existingRulerController(orientation: otherOrientation)
let zeroPointOffset = zeroPointOffset(
from: flippedRuler?.rulerWindow,
to: otherRuler?.rulerWindow,
geometry: oldGeometry
)

prefs.zeroCorner = flippedCorner

guard prefs.groupRulers,
let flippedWindow = flippedRuler?.rulerWindow,
let otherWindow = otherRuler?.rulerWindow,
isRulerWindowShown(otherWindow),
let zeroPointOffset = zeroPointOffset else { return }

let newGeometry = ZeroCornerGeometry(zeroCorner: flippedCorner)
let flippedZeroPoint = newGeometry.zeroPoint(in: flippedWindow.frame, for: orientation)
let targetOtherZeroPoint = NSPoint(
x: flippedZeroPoint.x + zeroPointOffset.width,
y: flippedZeroPoint.y + zeroPointOffset.height
)
let otherFrame = newGeometry.frame(
for: otherOrientation,
zeroPoint: targetOtherZeroPoint,
size: otherWindow.frame.size
)

detachRulerWindows()
otherWindow.setFrame(otherFrame, display: true)
updateRulerGrouping()
}

func isRulerWindowShown(_ window: RulerWindow) -> Bool {
return window.isVisible || window.parent != nil || rulers.contains {
$0.rulerWindow.childWindows?.contains(window) == true
}
}

private func zeroPointOffset(
from sourceWindow: RulerWindow?,
to targetWindow: RulerWindow?,
geometry: ZeroCornerGeometry
) -> NSSize? {
guard let sourceWindow = sourceWindow,
let targetWindow = targetWindow else { return nil }

let sourceZeroPoint = geometry.zeroPoint(
in: sourceWindow.frame,
for: sourceWindow.ruler.orientation
)
let targetZeroPoint = geometry.zeroPoint(
in: targetWindow.frame,
for: targetWindow.ruler.orientation
)

return NSSize(
width: targetZeroPoint.x - sourceZeroPoint.x,
height: targetZeroPoint.y - sourceZeroPoint.y
)
}

func performRulerHotkey(
keyCode: Int,
modifierFlags: NSEvent.ModifierFlags,
sender: Any
) -> Bool {
let keyboardModifiers = modifierFlags
.intersection(.deviceIndependentFlagsMask)
.subtracting(.capsLock)

if keyboardModifiers == .shift {
switch keyCode {
case kVK_ANSI_H:
flipHorizontalRuler(sender)
case kVK_ANSI_V:
flipVerticalRuler(sender)
default:
return false
}

return true
}

guard keyboardModifiers.isEmpty else { return false }

switch keyCode {
case kVK_ANSI_H:
toggleHorizontalRuler(sender)
Expand Down Expand Up @@ -542,6 +663,40 @@ class AppDelegate: NSObject, NSApplicationDelegate {
showHotkeyBezel(prefs.groupRulers ? .rulersGrouped : .rulersUngrouped, on: screen)
}

private func showHorizontalOriginHotkeyBezel(on screen: NSScreen?) {
switch prefs.zeroCorner {
case .topLeft, .bottomLeft:
showHotkeyBezel(
format: .horizontalOriginFormat,
HotkeyBezelLocalizationKey.originLeft.localizedString,
on: screen
)
case .topRight, .bottomRight:
showHotkeyBezel(
format: .horizontalOriginFormat,
HotkeyBezelLocalizationKey.originRight.localizedString,
on: screen
)
}
}

private func showVerticalOriginHotkeyBezel(on screen: NSScreen?) {
switch prefs.zeroCorner {
case .topLeft, .topRight:
showHotkeyBezel(
format: .verticalOriginFormat,
HotkeyBezelLocalizationKey.originTop.localizedString,
on: screen
)
case .bottomLeft, .bottomRight:
showHotkeyBezel(
format: .verticalOriginFormat,
HotkeyBezelLocalizationKey.originBottom.localizedString,
on: screen
)
}
}

private func bezelScreen(for sender: Any) -> NSScreen? {
if let rulerController = sender as? RulerController {
return rulerController.rulerWindow.screen
Expand Down
6 changes: 6 additions & 0 deletions Free Ruler/Base.lproj/MainMenu.xib
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,15 @@
<items>
<menuItem title="Flip Horizontal" keyEquivalent="h" id="GZl-Zd-Ad4">
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
<connections>
<action selector="flipHorizontalRuler:" target="Voe-Tx-rLC" id="VqQ-nA-hUx"/>
</connections>
</menuItem>
<menuItem title="Flip Vertical" keyEquivalent="v" id="IQD-xF-keq">
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
<connections>
<action selector="flipVerticalRuler:" target="Voe-Tx-rLC" id="xUP-Jr-AP0"/>
</connections>
</menuItem>
</items>
</menu>
Expand Down
6 changes: 3 additions & 3 deletions Free Ruler/FreeRuler.help/Contents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1</string>
<string>2</string>
<key>CFBundleSignature</key>
<string>hbwr</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>2</string>
<key>HPDBookAccessPath</key>
<string>FreeRuler.html</string>
<key>HPDBookIconPath</key>
<string></string>
<key>HPDBookIndexPath</key>
<string>English.lproj.helpindex</string>
<key>HPDBookKBProduct</key>
<string>freeruler1</string>
<string>freeruler2</string>
<key>HPDBookTitle</key>
<string>Free Ruler Help</string>
<key>HPDBookType</key>
Expand Down
Binary file not shown.
Loading