Skip to content

Add ability to scale x and y. #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions Free Ruler.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
50D7BEEB227D432E0008B95E /* RulerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEEA227D432E0008B95E /* RulerWindow.swift */; };
50D7BEED227D5C810008B95E /* RuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEEC227D5C810008B95E /* RuleView.swift */; };
50FC527E25BF326800B84228 /* FreeRuler.help in Resources */ = {isa = PBXBuildFile; fileRef = 50FC527D25BF326800B84228 /* FreeRuler.help */; };
62EA14412D47120F00DA3964 /* ScaleController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62EA14402D47120F00DA3964 /* ScaleController.xib */; };
62EA14432D4713C000DA3964 /* ScaleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EA14422D4713C000DA3964 /* ScaleController.swift */; };
6F4102892260712F00F06A10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4102882260712F00F06A10 /* AppDelegate.swift */; };
6F41028E2260713100F06A10 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6F41028C2260713100F06A10 /* MainMenu.xib */; };
6F41029F22607DC900F06A10 /* HorizontalRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F41029E22607DC900F06A10 /* HorizontalRule.swift */; };
Expand Down Expand Up @@ -60,6 +62,8 @@
50FC527D25BF326800B84228 /* FreeRuler.help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = FreeRuler.help; sourceTree = "<group>"; };
53AF6A4225A456E30076AAB7 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/MainMenu.strings; sourceTree = "<group>"; };
53AF6A4325A456E30076AAB7 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/PreferencesController.strings; sourceTree = "<group>"; };
62EA14402D47120F00DA3964 /* ScaleController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ScaleController.xib; sourceTree = "<group>"; };
62EA14422D4713C000DA3964 /* ScaleController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleController.swift; sourceTree = "<group>"; };
6F4102852260712F00F06A10 /* Free Ruler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Free Ruler.app"; sourceTree = BUILT_PRODUCTS_DIR; };
6F4102882260712F00F06A10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
6F41028D2260713100F06A10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
Expand Down Expand Up @@ -109,6 +113,8 @@
507FED5F2280E13200BD77DC /* Prefs.swift */,
50008B6022846FCD001E3EE4 /* Notifications.swift */,
6F4102882260712F00F06A10 /* AppDelegate.swift */,
62EA14402D47120F00DA3964 /* ScaleController.xib */,
62EA14422D4713C000DA3964 /* ScaleController.swift */,
6F41028C2260713100F06A10 /* MainMenu.xib */,
50D7BEE8227D43270008B95E /* Ruler.swift */,
50D7BEE6227D42FD0008B95E /* RulerController.swift */,
Expand Down Expand Up @@ -193,6 +199,7 @@
files = (
50C6D891228BDBAD0091F19E /* Images.xcassets in Resources */,
50FC527E25BF326800B84228 /* FreeRuler.help in Resources */,
62EA14412D47120F00DA3964 /* ScaleController.xib in Resources */,
507FED44227FFF5300BD77DC /* PreferencesController.xib in Resources */,
6F41028E2260713100F06A10 /* MainMenu.xib in Resources */,
);
Expand All @@ -205,6 +212,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
62EA14432D4713C000DA3964 /* ScaleController.swift in Sources */,
50008B6122846FCD001E3EE4 /* Notifications.swift in Sources */,
507FED602280E13200BD77DC /* Prefs.swift in Sources */,
50CCB206227FCD26004645C5 /* AppIconLayout.swift in Sources */,
Expand Down
20 changes: 20 additions & 0 deletions Free Ruler/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var pixelsMenuItem: NSMenuItem!
@IBOutlet weak var millimetersMenuItem: NSMenuItem!
@IBOutlet weak var inchesMenuItem: NSMenuItem!
@IBOutlet weak var scaledMenuItem: NSMenuItem!
@IBOutlet weak var cycleUnitsMenuItem: NSMenuItem!

@IBOutlet weak var floatRulersMenuItem: NSMenuItem!
Expand All @@ -27,6 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var alignRulersMenuItem: NSMenuItem!

var preferencesController: PreferencesController? = nil
var scaleController: ScaleController? = nil

// MARK: - Lifecycle

Expand Down Expand Up @@ -73,6 +75,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
pixelsMenuItem?.state = prefs.unit == .pixels ? .on : .off
millimetersMenuItem?.state = prefs.unit == .millimeters ? .on : .off
inchesMenuItem?.state = prefs.unit == .inches ? .on : .off
scaledMenuItem?.state = prefs.unit == .scaled ? .on : .off
}

func redrawRulers() {
Expand Down Expand Up @@ -138,13 +141,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@IBAction func setUnitInches(_ sender: Any) {
prefs.unit = .inches
}
@IBAction func setUnitScaled(_ sender: Any) {
prefs.unit = .scaled
}
@IBAction func cycleUnits(_ sender: Any) {
switch prefs.unit {
case .pixels:
prefs.unit = .millimeters
case .millimeters:
prefs.unit = .inches
case .inches:
prefs.unit = .scaled
case .scaled:
prefs.unit = .pixels
}
}
Expand All @@ -170,6 +178,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}

@IBAction func openScale(_ sender: Any) {
if scaleController == nil {
scaleController = ScaleController()
}

if scaleController != nil {
scaleController?.xRulerWidth = rulers[1].rulerWindow.frame.width;
scaleController?.yRulerHeight = rulers[0].rulerWindow.frame.height;
scaleController?.showWindow(self)
}
}

@IBAction func alignRulersAtMouseLocation(_ sender: Any) {
var mouseLoc = NSEvent.mouseLocation
mouseLoc.x = mouseLoc.x.rounded()
Expand Down
18 changes: 15 additions & 3 deletions Free Ruler/Base.lproj/MainMenu.xib
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
Expand All @@ -22,6 +21,7 @@
<outlet property="millimetersMenuItem" destination="B6Y-Hi-AkN" id="9Cc-tZ-RRZ"/>
<outlet property="pixelsMenuItem" destination="pYR-Ba-kKi" id="Wus-JK-rs3"/>
<outlet property="rulerShadowMenuItem" destination="a8D-hN-A59" id="FZM-pS-71y"/>
<outlet property="scaledMenuItem" destination="4bj-UZ-A13" id="tRT-QG-OXU"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
Expand Down Expand Up @@ -159,13 +159,25 @@
<action selector="setUnitInches:" target="Voe-Tx-rLC" id="Apf-6P-Oz8"/>
</connections>
</menuItem>
<menuItem title="Scaled" id="4bj-UZ-A13">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="setUnitScaled:" target="Voe-Tx-rLC" id="qEj-TJ-1HO"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="tK2-3l-lAd"/>
<menuItem title="Cycle Units" keyEquivalent="u" id="2nm-aL-kZd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="cycleUnits:" target="Voe-Tx-rLC" id="jzB-to-aha"/>
</connections>
</menuItem>
<menuItem title="Set Scale..." keyEquivalent="s" id="efg-xu-6jd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openScale:" target="Voe-Tx-rLC" id="gYC-VA-6er"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
Expand Down
11 changes: 9 additions & 2 deletions Free Ruler/HorizontalRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class HorizontalRule: RuleView {
mediumTicks = 8
smallTicks = 4
tinyTicks = 1
case .scaled:
tickScale = 1 / prefs.xscale
textScale = 1
largeTicks = 10
mediumTicks = 5
smallTicks = 1
tinyTicks = nil
default:
tickScale = 1
textScale = 1
Expand Down Expand Up @@ -150,7 +157,7 @@ class HorizontalRule: RuleView {
NSAttributedString.Key.foregroundColor: color.mouseNumber,
]

let mouseNumber = self.getMouseNumberLabel(number)
let mouseNumber = self.getXMouseNumberLabel(number)
let label = NSAttributedString(string: mouseNumber, attributes: attributes)
let labelSize = label.size()

Expand Down Expand Up @@ -188,5 +195,5 @@ class HorizontalRule: RuleView {
context: nil
)
}

}
17 changes: 15 additions & 2 deletions Free Ruler/Prefs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let prefs = Prefs.shared
case pixels
case millimeters
case inches
case scaled
}

class Prefs: NSObject {
Expand All @@ -31,6 +32,8 @@ class Prefs: NSObject {
@objc dynamic var foregroundOpacity : Int
@objc dynamic var backgroundOpacity : Int
@objc dynamic var unit : Unit
@objc dynamic var xscale : CGFloat
@objc dynamic var yscale : CGFloat

// MARK: - public save method
func save() {
Expand All @@ -47,7 +50,9 @@ class Prefs: NSObject {
"rulerShadow": false,
"foregroundOpacity": 90,
"backgroundOpacity": 50,
"unit": Unit.pixels.rawValue
"unit": Unit.pixels.rawValue,
"xscale": 1.0,
"yscale": 1.0
]

private override init() {
Expand All @@ -59,7 +64,9 @@ class Prefs: NSObject {
foregroundOpacity = defaults.integer(forKey: "foregroundOpacity")
backgroundOpacity = defaults.integer(forKey: "backgroundOpacity")
unit = Unit(rawValue: defaults.integer(forKey: "unit")) ?? .pixels

xscale = CGFloat(defaults.float(forKey: "xscale"))
yscale = CGFloat(defaults.float(forKey: "yscale"))

super.init()

addObservers()
Expand Down Expand Up @@ -87,6 +94,12 @@ class Prefs: NSObject {
observe(\Prefs.unit, options: .new) { prefs, changed in
self.defaults.set(prefs.unit.rawValue, forKey: "unit")
},
observe(\Prefs.xscale, options: .new) { prefs, changed in
self.defaults.set(prefs.xscale.description, forKey: "xscale")
},
observe(\Prefs.yscale, options: .new) { prefs, changed in
self.defaults.set(prefs.yscale.description, forKey: "yscale")
},
]
}

Expand Down
18 changes: 17 additions & 1 deletion Free Ruler/RuleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,33 @@ class RuleView: NSView {
return "mm"
case .inches:
return "in"
case .scaled:
return "sc"
}
}

func getMouseNumberLabel(_ number: CGFloat) -> String {
func getXMouseNumberLabel(_ number: CGFloat) -> String {
switch prefs.unit {
case .pixels:
return String(format: "%d", Int(number))
case .millimeters:
return String(format: "%.1f", number / (screen?.dpmm.width ?? NSScreen.defaultDpmm))
case .inches:
return String(format: "%.3f", number / (screen?.dpi.width ?? NSScreen.defaultDpi))
case .scaled:
return String(format: "%.3f", number * prefs.xscale )
}
}
func getYMouseNumberLabel(_ number: CGFloat) -> String {
switch prefs.unit {
case .pixels:
return String(format: "%d", Int(number))
case .millimeters:
return String(format: "%.1f", number / (screen?.dpmm.width ?? NSScreen.defaultDpmm))
case .inches:
return String(format: "%.3f", number / (screen?.dpi.width ?? NSScreen.defaultDpi))
case .scaled:
return String(format: "%.3f", number * prefs.yscale )
}
}

Expand Down
121 changes: 121 additions & 0 deletions Free Ruler/ScaleController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// ScaleController.swift
// Free Ruler
//
// Created by Kevin Meziere on 1/26/25.
// Copyright © 2025 Free Ruler. All rights reserved.
//

import Cocoa

class ScaleController: NSWindowController, NSWindowDelegate,
NSTextFieldDelegate, NotificationPoster
{

@IBOutlet weak var xScaleTextField: NSTextField!
@IBOutlet weak var yScaleTextField: NSTextField!
@IBOutlet weak var lockedRatioButton: NSButton!

var observers: [NSKeyValueObservation] = []

public var xRulerWidth: CGFloat = 0
public var yRulerHeight: CGFloat = 0

var lockedAspectRatio: Bool = true

override var windowNibName: String {
return "ScaleController"
}

override func windowDidLoad() {
super.windowDidLoad()
window?.isMovableByWindowBackground = true

lockedAspectRatio = prefs.xscale == prefs.yscale
}

func windowDidBecomeKey(_ notification: Notification) {
lockedRatioButton.state = lockedAspectRatio ? .on : .off
xScaleTextField.stringValue = String(
format: "%.5f", xRulerWidth * prefs.xscale)
yScaleTextField.stringValue = String(
format: "%.5f", yRulerHeight * prefs.yscale)

}

override func showWindow(_ sender: Any?) {
window?.makeKeyAndOrderFront(sender)
window?.center()
}

override func windowWillLoad() {

}

@IBAction func xValueChanged(_ sender: NSTextField) {

}

func controlTextDidChange(_ obj: Notification) {

let textField = obj.object as! NSTextField

// https://stackoverflow.com/a/52311371
var stringValue = textField.stringValue

let charSet = NSCharacterSet(charactersIn: "1234567890.").inverted
let chars = textField.stringValue.components(separatedBy: charSet)
stringValue = chars.joined()

// Second step : only one '.'
let comma = NSCharacterSet(charactersIn: ".")
let chuncks = stringValue.components(separatedBy: comma as CharacterSet)
switch chuncks.count {
case 0:
stringValue = ""
case 1:
stringValue = "\(chuncks[0])"
default:
stringValue = "\(chuncks[0]).\(chuncks[1])"
}

// replace string
textField.stringValue = stringValue

if textField.identifier?.rawValue == "xscale" {
if lockedRatioButton.state == .on {
yScaleTextField.stringValue = String(
format: "%.5f",
(CGFloat(yRulerHeight * Double(stringValue)!) / xRulerWidth)
)
}
}

if textField.identifier?.rawValue == "yscale" {
if lockedRatioButton.state == .on {
xScaleTextField.stringValue = String(
format: "%.5f",
(CGFloat(xRulerWidth * Double(stringValue)!) / yRulerHeight)
)
}
}

}

@IBAction func lockRatio(_ sender: Any) {
if lockedRatioButton.state == .on {
yScaleTextField.stringValue = String(
format: "%.5f",
(CGFloat(yRulerHeight * Double(xScaleTextField.stringValue)!)
/ xRulerWidth))
}

}

@IBAction func saveScale(_ sender: Any) {
prefs.xscale = Double(xScaleTextField.stringValue)! / xRulerWidth
prefs.yscale = Double(yScaleTextField.stringValue)! / yRulerHeight
self.window?.close()
}

}
Loading