Skip to content
Merged
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
4 changes: 4 additions & 0 deletions Free Ruler.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
50B149012D04A00000149C0D /* HotkeyBezel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B149002D04A00000149C0D /* HotkeyBezel.swift */; };
50B1D3512D05D00000B1D135 /* MouseTickTimerPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3502D05D00000B1D135 /* MouseTickTimerPolicy.swift */; };
50B1D3532D05D00100B1D135 /* RulerCursorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3522D05D00100B1D135 /* RulerCursorController.swift */; };
50B1D3552D05E00000B1D138 /* RulerTickLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B1D3542D05E00000B1D138 /* RulerTickLayout.swift */; };
50C6D891228BDBAD0091F19E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50C6D890228BDBAD0091F19E /* Images.xcassets */; };
50CCB206227FCD26004645C5 /* AppIconLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CCB205227FCD26004645C5 /* AppIconLayout.swift */; };
50D7BEE7227D42FD0008B95E /* RulerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D7BEE6227D42FD0008B95E /* RulerController.swift */; };
Expand Down Expand Up @@ -85,6 +86,7 @@
50B149002D04A00000149C0D /* HotkeyBezel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyBezel.swift; sourceTree = "<group>"; };
50B1D3502D05D00000B1D135 /* MouseTickTimerPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseTickTimerPolicy.swift; sourceTree = "<group>"; };
50B1D3522D05D00100B1D135 /* RulerCursorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulerCursorController.swift; sourceTree = "<group>"; };
50B1D3542D05E00000B1D138 /* RulerTickLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulerTickLayout.swift; sourceTree = "<group>"; };
50C6D890228BDBAD0091F19E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
50CCB205227FCD26004645C5 /* AppIconLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconLayout.swift; sourceTree = "<group>"; };
50D7BEE6227D42FD0008B95E /* RulerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulerController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -197,6 +199,7 @@
50D7BEE6227D42FD0008B95E /* RulerController.swift */,
50D7BEEA227D432E0008B95E /* RulerWindow.swift */,
50D7BEEC227D5C810008B95E /* RuleView.swift */,
50B1D3542D05E00000B1D138 /* RulerTickLayout.swift */,
6F41029E22607DC900F06A10 /* HorizontalRule.swift */,
5012CAAC226AB09000BD9565 /* VerticalRule.swift */,
507FED41227FFF5300BD77DC /* PreferencesController.swift */,
Expand Down Expand Up @@ -371,6 +374,7 @@
50B149012D04A00000149C0D /* HotkeyBezel.swift in Sources */,
50B1D3532D05D00100B1D135 /* RulerCursorController.swift in Sources */,
50B1D3512D05D00000B1D135 /* MouseTickTimerPolicy.swift in Sources */,
50B1D3552D05E00000B1D138 /* RulerTickLayout.swift in Sources */,
6F41029F22607DC900F06A10 /* HorizontalRule.swift in Sources */,
50D7BEEB227D432E0008B95E /* RulerWindow.swift in Sources */,
507FED43227FFF5300BD77DC /* PreferencesController.swift in Sources */,
Expand Down
71 changes: 11 additions & 60 deletions Free Ruler/HorizontalRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,11 @@ class HorizontalRule: RuleView {
color.fill.setFill()
dirtyRect.fill()

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attrs: [NSAttributedString.Key: Any] = [
.font: NSFont(name: "HelveticaNeue", size: 10)!,
.paragraphStyle: paragraphStyle,
.foregroundColor: color.numbers
]
let attrs = labelAttributes(alignment: .center, foregroundColor: color.numbers)

let width = dirtyRect.width
let path = NSBezierPath()
let tickScale: CGFloat
let textScale: Int
let largeTicks: Int
let mediumTicks: Int
let smallTicks: Int
let tinyTicks: Int?

switch prefs.unit {
case .millimeters:
tickScale = screen?.dpmm.width ?? NSScreen.defaultDpmm
textScale = 1
largeTicks = 10
mediumTicks = 5
smallTicks = 1
tinyTicks = nil
case .inches:
tickScale = (screen?.dpi.width ?? NSScreen.defaultDpi) / 16
textScale = 16
largeTicks = 16
mediumTicks = 8
smallTicks = 4
tinyTicks = 1
default:
tickScale = 1
textScale = 1
largeTicks = 50
mediumTicks = 10
smallTicks = 2
tinyTicks = nil
}
let tickLayout = RulerTickLayout(unit: prefs.unit, screen: screen)

let labelWidth: CGFloat = 50
let labelHeight: CGFloat = 20
Expand All @@ -67,13 +32,13 @@ class HorizontalRule: RuleView {

// substract two so ticks don't overlap with border
// subtract from this range so width var is accurate
for i in 1...Int((width - 2) / tickScale) {
let pos = CGFloat(i) * tickScale
if i.isMultiple(of: largeTicks) {
for i in 1...Int((width - 2) / tickLayout.tickScale) {
let pos = CGFloat(i) * tickLayout.tickScale
if i.isMultiple(of: tickLayout.largeTicks) {
path.move(to: CGPoint(x: pos, y: 1))
path.line(to: CGPoint(x: pos, y: 10))

let label = String(i / textScale)
let label = String(i / tickLayout.textScale)
let labelX: CGFloat = pos - (labelWidth / 2) + 0.5 // half-pixel nudge /shrug
let labelY: CGFloat = labelOffset
let labelRect = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
Expand All @@ -85,15 +50,15 @@ class HorizontalRule: RuleView {
)

}
else if i.isMultiple(of: mediumTicks) {
else if i.isMultiple(of: tickLayout.mediumTicks) {
path.move(to: CGPoint(x: pos, y: 1))
path.line(to: CGPoint(x: pos, y: 8))
}
else if i.isMultiple(of: smallTicks) {
else if i.isMultiple(of: tickLayout.smallTicks) {
path.move(to: CGPoint(x: pos, y: 1))
path.line(to: CGPoint(x: pos, y: 5))
}
else if let tinyTicks = tinyTicks, i.isMultiple(of: tinyTicks) {
else if let tinyTicks = tickLayout.tinyTicks, i.isMultiple(of: tinyTicks) {
path.move(to: CGPoint(x: pos, y: 1))
path.line(to: CGPoint(x: pos, y: 3))
}
Expand Down Expand Up @@ -141,14 +106,7 @@ class HorizontalRule: RuleView {
let height = self.frame.height
let labelOffset: CGFloat = 5

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center

let attributes = [
NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!,
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.foregroundColor: color.mouseNumber,
]
let attributes = labelAttributes(alignment: .center, foregroundColor: color.mouseNumber)

let mouseNumber = self.getMouseNumberLabel(number)
let label = NSAttributedString(string: mouseNumber, attributes: attributes)
Expand All @@ -168,14 +126,7 @@ class HorizontalRule: RuleView {
}

func drawUnitLabel() {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left

let attributes = [
NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!,
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.foregroundColor: color.ticks,
]
let attributes = labelAttributes(alignment: .left, foregroundColor: color.ticks)

let unitlabel = self.getUnitLabel()
let label = NSAttributedString(string: unitlabel, attributes: attributes)
Expand Down
15 changes: 15 additions & 0 deletions Free Ruler/RuleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ class RuleView: NSView {
}
}

func labelAttributes(
alignment: NSTextAlignment,
foregroundColor: NSColor
) -> [NSAttributedString.Key: Any] {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = alignment
let font = NSFont(name: "HelveticaNeue", size: 10) ?? .systemFont(ofSize: 10)

return [
.font: font,
.paragraphStyle: paragraphStyle,
.foregroundColor: foregroundColor,
]
}
Comment thread
pascalpp marked this conversation as resolved.

override func accessibilityValue() -> Any? {
return getUnitLabel()
}
Expand Down
44 changes: 44 additions & 0 deletions Free Ruler/RulerTickLayout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Cocoa

struct RulerTickLayout {
let tickScale: CGFloat
let textScale: Int
let largeTicks: Int
let mediumTicks: Int
let smallTicks: Int
let tinyTicks: Int?

init(unit: Unit, screen: NSScreen?) {
self.init(
unit: unit,
dpi: screen?.dpi.width ?? NSScreen.defaultDpi,
dpmm: screen?.dpmm.width ?? NSScreen.defaultDpmm
)
}

init(unit: Unit, dpi: CGFloat, dpmm: CGFloat) {
switch unit {
case .millimeters:
tickScale = dpmm
textScale = 1
largeTicks = 10
mediumTicks = 5
smallTicks = 1
tinyTicks = nil
case .inches:
tickScale = dpi / 16
textScale = 16
largeTicks = 16
mediumTicks = 8
smallTicks = 4
tinyTicks = 1
case .pixels:
tickScale = 1
textScale = 1
Comment thread
pascalpp marked this conversation as resolved.
largeTicks = 50
mediumTicks = 10
smallTicks = 2
tinyTicks = nil
}
}
}
71 changes: 11 additions & 60 deletions Free Ruler/VerticalRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,12 @@ class VerticalRule: RuleView {
color.fill.setFill()
dirtyRect.fill()

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .right
let attrs: [NSAttributedString.Key: Any] = [
.font: NSFont(name: "HelveticaNeue", size: 10)!,
.paragraphStyle: paragraphStyle,
.foregroundColor: color.numbers
]
let attrs = labelAttributes(alignment: .right, foregroundColor: color.numbers)

let width = dirtyRect.width
let height = dirtyRect.height
let path = NSBezierPath()
let tickScale: CGFloat
let textScale: Int
let largeTicks: Int
let mediumTicks: Int
let smallTicks: Int
let tinyTicks: Int?

switch prefs.unit {
case .millimeters:
tickScale = screen?.dpmm.width ?? NSScreen.defaultDpmm
textScale = 1
largeTicks = 10
mediumTicks = 5
smallTicks = 1
tinyTicks = nil
case .inches:
tickScale = (screen?.dpi.width ?? NSScreen.defaultDpi) / 16
textScale = 16
largeTicks = 16
mediumTicks = 8
smallTicks = 4
tinyTicks = 1
default:
tickScale = 1
textScale = 1
largeTicks = 50
mediumTicks = 10
smallTicks = 2
tinyTicks = nil
}
let tickLayout = RulerTickLayout(unit: prefs.unit, screen: screen)

let labelWidth: CGFloat = 50
let labelHeight: CGFloat = 20
Expand All @@ -69,13 +34,13 @@ class VerticalRule: RuleView {

// substract two so ticks don't overlap with border
// substract from this range so we can use the height var for position calculations
for i in 1...Int((height - 2) / tickScale) {
let pos = CGFloat(i) * tickScale
if i.isMultiple(of: largeTicks) {
for i in 1...Int((height - 2) / tickLayout.tickScale) {
let pos = CGFloat(i) * tickLayout.tickScale
if i.isMultiple(of: tickLayout.largeTicks) {
path.move(to: CGPoint(x: width - 1, y: height - pos))
path.line(to: CGPoint(x: width - 10, y: height - pos))

let label = String(i / textScale)
let label = String(i / tickLayout.textScale)
let labelX = width - labelWidth - labelOffset
let labelY = height - pos - (textHeight / 2)
let labelRect = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
Expand All @@ -87,15 +52,15 @@ class VerticalRule: RuleView {
)

}
else if i.isMultiple(of: mediumTicks) {
else if i.isMultiple(of: tickLayout.mediumTicks) {
path.move(to: CGPoint(x: width - 1, y: height - pos))
path.line(to: CGPoint(x: width - 8, y: height - pos))
}
else if i.isMultiple(of: smallTicks) {
else if i.isMultiple(of: tickLayout.smallTicks) {
path.move(to: CGPoint(x: width - 1, y: height - pos))
path.line(to: CGPoint(x: width - 5, y: height - pos))
}
else if let tinyTicks = tinyTicks, i.isMultiple(of: tinyTicks) {
else if let tinyTicks = tickLayout.tinyTicks, i.isMultiple(of: tinyTicks) {
path.move(to: CGPoint(x: width - 1, y: height - pos))
path.line(to: CGPoint(x: width - 3, y: height - pos))
}
Expand Down Expand Up @@ -142,14 +107,7 @@ class VerticalRule: RuleView {
let number = height - mouseTickY
let labelOffset: CGFloat = 2

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left

let attributes = [
NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!,
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.foregroundColor: color.mouseNumber,
]
let attributes = labelAttributes(alignment: .left, foregroundColor: color.mouseNumber)

let mouseNumber = self.getMouseNumberLabel(number)
let label = NSAttributedString(string: mouseNumber, attributes: attributes)
Expand All @@ -173,14 +131,7 @@ class VerticalRule: RuleView {
}

func drawUnitLabel() {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left

let attributes = [
NSAttributedString.Key.font: NSFont(name: "HelveticaNeue", size: 10)!,
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.foregroundColor: color.ticks,
]
let attributes = labelAttributes(alignment: .left, foregroundColor: color.ticks)

let unitlabel = self.getUnitLabel()
let label = NSAttributedString(string: unitlabel, attributes: attributes)
Expand Down
26 changes: 26 additions & 0 deletions FreeRulerTests/RulerCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,32 @@ final class RulerCoreTests: XCTestCase {
XCTAssertEqual(getMaxSize(ruler: vertical), NSSize(width: 40, height: 4000))
}

func testRulerTickLayoutMatchesUnitScales() {
let pixels = RulerTickLayout(unit: .pixels, dpi: 144, dpmm: 4)
XCTAssertEqual(pixels.tickScale, 1)
XCTAssertEqual(pixels.textScale, 1)
XCTAssertEqual(pixels.largeTicks, 50)
XCTAssertEqual(pixels.mediumTicks, 10)
XCTAssertEqual(pixels.smallTicks, 2)
XCTAssertNil(pixels.tinyTicks)

let millimeters = RulerTickLayout(unit: .millimeters, dpi: 144, dpmm: 4)
XCTAssertEqual(millimeters.tickScale, 4)
XCTAssertEqual(millimeters.textScale, 1)
XCTAssertEqual(millimeters.largeTicks, 10)
XCTAssertEqual(millimeters.mediumTicks, 5)
XCTAssertEqual(millimeters.smallTicks, 1)
XCTAssertNil(millimeters.tinyTicks)

let inches = RulerTickLayout(unit: .inches, dpi: 144, dpmm: 4)
XCTAssertEqual(inches.tickScale, 9)
XCTAssertEqual(inches.textScale, 16)
XCTAssertEqual(inches.largeTicks, 16)
XCTAssertEqual(inches.mediumTicks, 8)
XCTAssertEqual(inches.smallTicks, 4)
XCTAssertEqual(inches.tinyTicks, 1)
}

func testDefaultRulerRectsUseExpectedShapeAndOffsets() {
let screen = NSScreen.main?.frame
let screenWidth = screen?.width ?? 1000
Expand Down