Skip to content
Closed
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
98 changes: 79 additions & 19 deletions Free Ruler/ResizeHandleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ final class ResizeHandleView: NSView {
)
let nextFrame = resizedRulerFrame(
orientation: orientation,
zeroCorner: prefs.zeroCorner,
initialFrame: dragInitialWindowFrame,
delta: delta,
minSize: window.minSize,
Expand Down Expand Up @@ -153,14 +154,29 @@ final class ResizeHandleView: NSView {
}

func frame(in bounds: NSRect) -> NSRect {
let resizeSide = ZeroCornerGeometry(zeroCorner: prefs.zeroCorner).resizeSide(for: orientation)

switch orientation {
case .horizontal:
let topY = bounds.maxY - horizontalYOffset
let bottomY = topY - length
let firstX = bounds.maxX
- horizontalXOffset
- CGFloat(lineCount - 1) * lineSpacing
- 1
let firstX: CGFloat

switch resizeSide {
case .left:
firstX = bounds.minX + horizontalXOffset
case .right:
firstX = bounds.maxX
- horizontalXOffset
- CGFloat(lineCount - 1) * lineSpacing
- 1
case .top, .bottom:
assertionFailure("Horizontal resize handle must be placed on a horizontal side")
firstX = bounds.maxX
- horizontalXOffset
- CGFloat(lineCount - 1) * lineSpacing
- 1
}

return NSRect(
x: firstX - backgroundPadding,
Expand All @@ -171,7 +187,20 @@ final class ResizeHandleView: NSView {
case .vertical:
let rightX = bounds.minX + verticalXOffset + length
let leftX = rightX - length
let firstY = bounds.minY + verticalYOffset + 1
let firstY: CGFloat

switch resizeSide {
case .top:
firstY = bounds.maxY
- verticalYOffset
- CGFloat(lineCount - 1) * lineSpacing
- 1
case .bottom:
firstY = bounds.minY + verticalYOffset + 1
case .left, .right:
assertionFailure("Vertical resize handle must be placed on a vertical side")
firstY = bounds.minY + verticalYOffset + 1
}

return NSRect(
x: leftX - backgroundPadding,
Expand Down Expand Up @@ -267,28 +296,59 @@ private func screenLocation(for event: NSEvent, in window: NSWindow) -> NSPoint

func resizedRulerFrame(
orientation: Orientation,
zeroCorner: ZeroCorner = .topLeft,
initialFrame: NSRect,
delta: NSSize,
minSize: NSSize,
maxSize: NSSize
) -> NSRect {
let resizeSide = ZeroCornerGeometry(zeroCorner: zeroCorner).resizeSide(for: orientation)

switch orientation {
case .horizontal:
let width = clamp(initialFrame.width + delta.width, minSize.width, maxSize.width)
return NSRect(
x: initialFrame.minX,
y: initialFrame.minY,
width: width,
height: initialFrame.height
)
switch resizeSide {
case .left:
let width = clamp(initialFrame.width - delta.width, minSize.width, maxSize.width)
return NSRect(
x: initialFrame.maxX - width,
y: initialFrame.minY,
width: width,
height: initialFrame.height
)
case .right:
let width = clamp(initialFrame.width + delta.width, minSize.width, maxSize.width)
return NSRect(
x: initialFrame.minX,
y: initialFrame.minY,
width: width,
height: initialFrame.height
)
case .top, .bottom:
assertionFailure("Horizontal ruler resize side must be left or right")
return initialFrame
}
case .vertical:
let height = clamp(initialFrame.height - delta.height, minSize.height, maxSize.height)
return NSRect(
x: initialFrame.minX,
y: initialFrame.maxY - height,
width: initialFrame.width,
height: height
)
switch resizeSide {
case .top:
let height = clamp(initialFrame.height + delta.height, minSize.height, maxSize.height)
return NSRect(
x: initialFrame.minX,
y: initialFrame.minY,
width: initialFrame.width,
height: height
)
case .bottom:
let height = clamp(initialFrame.height - delta.height, minSize.height, maxSize.height)
return NSRect(
x: initialFrame.minX,
y: initialFrame.maxY - height,
width: initialFrame.width,
height: height
)
case .left, .right:
assertionFailure("Vertical ruler resize side must be top or bottom")
return initialFrame
}
}
}

Expand Down
194 changes: 162 additions & 32 deletions Free Ruler/Ruler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,157 @@ enum Orientation: String {
case bottomRight = 3
}

enum RulerGrowthDirection: Equatable {
case positive
case negative
}

enum RulerSide: Equatable {
case top
case right
case bottom
case left
}

struct ZeroCornerGeometry {
let zeroCorner: ZeroCorner

private let borderCompensation: CGFloat = 1.0

init(zeroCorner: ZeroCorner) {
self.zeroCorner = zeroCorner
}

func growthDirection(for orientation: Orientation) -> RulerGrowthDirection {
switch orientation {
case .horizontal:
return horizontalZeroSide == .left ? .positive : .negative
case .vertical:
return verticalZeroSide == .bottom ? .positive : .negative
}
}

func tickSide(for orientation: Orientation) -> RulerSide {
switch orientation {
case .horizontal:
return verticalZeroSide == .top ? .bottom : .top
case .vertical:
return horizontalZeroSide == .left ? .right : .left
}
}

func resizeSide(for orientation: Orientation) -> RulerSide {
switch orientation {
case .horizontal:
return horizontalZeroSide == .left ? .right : .left
case .vertical:
return verticalZeroSide == .top ? .bottom : .top
}
}

func zeroPoint(in frame: NSRect, for orientation: Orientation) -> NSPoint {
switch orientation {
case .horizontal:
return NSPoint(
x: horizontalZeroSide == .left ? frame.minX : frame.maxX,
y: verticalZeroSide == .top ? frame.minY + borderCompensation : frame.maxY - borderCompensation
)
case .vertical:
return NSPoint(
x: horizontalZeroSide == .left ? frame.maxX - borderCompensation : frame.minX,
y: verticalZeroSide == .top ? frame.maxY : frame.minY
)
}
}

func frame(for orientation: Orientation, zeroPoint: NSPoint, size: NSSize) -> NSRect {
switch orientation {
case .horizontal:
return NSRect(
x: horizontalZeroSide == .left ? zeroPoint.x : zeroPoint.x - size.width,
y: verticalZeroSide == .top ? zeroPoint.y - borderCompensation : zeroPoint.y - size.height + borderCompensation,
width: size.width,
height: size.height
)
case .vertical:
return NSRect(
x: horizontalZeroSide == .left ? zeroPoint.x - size.width + borderCompensation : zeroPoint.x,
y: verticalZeroSide == .top ? zeroPoint.y - size.height : zeroPoint.y,
width: size.width,
height: size.height
)
}
}

func defaultFrame(for orientation: Orientation, screenFrame: NSRect) -> NSRect {
let xOffset: CGFloat = 30
let yOffset: CGFloat = 50
let horizontalLength = screenFrame.width / 2
let aspectRatio = screenFrame.width / screenFrame.height
let verticalLength = horizontalLength / aspectRatio
let topLeftZeroPoint = NSPoint(
x: screenFrame.minX + xOffset + Ruler.thickness - borderCompensation,
y: screenFrame.maxY - yOffset - Ruler.thickness + borderCompensation
)
let zeroPoint = zeroPointMatchingSelectedCorner(
topLeftZeroPoint: topLeftZeroPoint,
horizontalLength: horizontalLength,
verticalLength: verticalLength
)

switch orientation {
case .horizontal:
return frame(
for: orientation,
zeroPoint: zeroPoint,
size: NSSize(width: horizontalLength, height: Ruler.thickness)
)
case .vertical:
return frame(
for: orientation,
zeroPoint: zeroPoint,
size: NSSize(width: Ruler.thickness, height: verticalLength)
)
}
}

private var horizontalZeroSide: RulerSide {
switch zeroCorner {
case .topLeft, .bottomLeft:
return .left
case .topRight, .bottomRight:
return .right
}
}

private var verticalZeroSide: RulerSide {
switch zeroCorner {
case .topLeft, .topRight:
return .top
case .bottomLeft, .bottomRight:
return .bottom
}
}

private func zeroPointMatchingSelectedCorner(
topLeftZeroPoint: NSPoint,
horizontalLength: CGFloat,
verticalLength: CGFloat
) -> NSPoint {
var point = topLeftZeroPoint

if horizontalZeroSide == .right {
point.x += horizontalLength
}

if verticalZeroSide == .bottom {
point.y -= verticalLength
}

return point
}
}

class Ruler {
static let thickness: CGFloat = 40

Expand Down Expand Up @@ -46,40 +197,19 @@ class Ruler {
// MARK: - Ruler size helpers

func getDefaultContentRect(orientation: Orientation) -> NSRect {
var screenWidth: CGFloat = 1000
var screenHeight: CGFloat = 800
if let screen = NSScreen.main?.frame {
screenWidth = screen.width
screenHeight = screen.height
}

let aspectRatio = screenWidth / screenHeight
let xOffset: CGFloat = 30
let yOffset: CGFloat = 50
let rulerThickness: CGFloat = 40

let horizontalLength = screenWidth / 2
let verticalLength = horizontalLength / aspectRatio
return getDefaultContentRect(orientation: orientation, zeroCorner: .topLeft)
}

switch orientation {
case .horizontal:
return NSRect(
// offset horizontal by 1px leftward to compensate for ruler border
x: xOffset + rulerThickness - 1.0,
y: screenHeight - yOffset - rulerThickness,
width: horizontalLength,
height: rulerThickness
)
case .vertical:
return NSRect(
// offset vertical by 1px upward to compensate for ruler border
x: xOffset,
y: screenHeight - yOffset - rulerThickness - verticalLength + 1.0,
width: rulerThickness,
height: verticalLength
)
}
func getDefaultContentRect(orientation: Orientation, zeroCorner: ZeroCorner) -> NSRect {
let fallbackScreenFrame = NSRect(x: 0, y: 0, width: 1000, height: 800)
let screenFrame = NSScreen.main
.map { NSRect(origin: .zero, size: $0.frame.size) }
?? fallbackScreenFrame

return ZeroCornerGeometry(zeroCorner: zeroCorner).defaultFrame(
for: orientation,
screenFrame: screenFrame
)
}

func getMinSize(ruler: Ruler) -> NSSize {
Expand Down
25 changes: 5 additions & 20 deletions Free Ruler/RulerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,32 +245,17 @@ class RulerController: NSWindowController, NSWindowDelegate, NotificationObserve
guard let window = window else { return }

let frame = window.frame
var x: CGFloat
var y: CGFloat

switch window.ruler.orientation {
case .horizontal:
// offset horizontal by 1px downward to compensate for ruler border
x = point.x
y = point.y - 1.0
case .vertical:
// offset vertical by 1px rightward to compensate for ruler border
x = point.x - frame.width + 1.0
y = point.y - frame.height
}

let rect = NSRect(
x: x,
y: y,
width: frame.width,
height: frame.height
let rect = ZeroCornerGeometry(zeroCorner: prefs.zeroCorner).frame(
for: window.ruler.orientation,
zeroPoint: point,
size: frame.size
)

window.setFrame(rect, display: false)
}

func resetPosition() {
let frame = getDefaultContentRect(orientation: ruler.orientation)
let frame = getDefaultContentRect(orientation: ruler.orientation, zeroCorner: prefs.zeroCorner)
rulerWindow.setFrame(frame, display: true)
}

Expand Down
Loading