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
6 changes: 6 additions & 0 deletions Free Ruler/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {

// MARK: - Application Quit

func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
closeRulerColorPanel()
return .terminateNow
}

func applicationWillTerminate(_ aNotification: Notification) {
closeRulerColorPanel()
prefs.save()
}

Expand Down
19 changes: 14 additions & 5 deletions Free Ruler/AppStoreScreenshotPreview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,11 @@ private enum AppStoreScreenshotLayout {
static let screen2RulerLength: CGFloat = 2200

static let screen3BackgroundColor = #colorLiteral(red: 0.875857736, green: 0.8972384907, blue: 0.94, alpha: 1)
static let screen3RulerScale: CGFloat = 10.28
static let screen3RulerOpacity: CGFloat = 1
static let screen3RulerCount = 7
static let screen3FirstRulerX: CGFloat = 0
static let screen3RulerGap: CGFloat = 0
static let screen3RulerGap: CGFloat = 30
static let screen3FirstRulerStart: CGFloat = screen3RulerGap
static let screen3LastRulerEnd: CGFloat = canvasWidth - screen3RulerGap
static let screen3RulerArcTop: CGFloat = 450
static let screen3RulerArcBottom: CGFloat = 1000
static let screen3RulerCurvature: CGFloat = 1.5
Expand Down Expand Up @@ -421,14 +421,22 @@ private enum AppStoreScreenshotLayout {
)
}

static var screen3RulerScale: CGFloat {
screen3ScaledRulerThickness / Ruler.thickness
}

private static var screen3AvailableRulerWidth: CGFloat {
screen3LastRulerEnd - screen3FirstRulerStart - (CGFloat(screen3RulerCount - 1) * screen3RulerGap)
}

static var screen3ScaledRulerThickness: CGFloat {
Ruler.thickness * screen3RulerScale
screen3AvailableRulerWidth / CGFloat(screen3RulerCount)
}

static func screen3RulerRect(index: Int) -> NSRect {
let top = screen3RulerTopY(index: index)
return NSRect(
x: screen3FirstRulerX + CGFloat(index) * (screen3ScaledRulerThickness + screen3RulerGap),
x: screen3FirstRulerStart + CGFloat(index) * (screen3ScaledRulerThickness + screen3RulerGap),
y: top,
width: screen3ScaledRulerThickness,
height: screen3RulerOffscreenBottom - top
Expand Down Expand Up @@ -873,6 +881,7 @@ private final class AppStoreScreenshotScenarioNSView: NSView {
snapshotWindow.contentView = view
snapshotWindow.orderFrontRegardless()
snapshotWindow.makeKeyAndOrderFront(nil)
snapshotWindow.makeFirstResponder(nil)
defer {
snapshotWindow.orderOut(nil)
snapshotWindow.contentView = nil
Expand Down
45 changes: 31 additions & 14 deletions Free Ruler/PreferencesController.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import Cocoa
import ObjectiveC.runtime

private let colorPanelOpaqueConfigurationRetryDelays: [TimeInterval] = [0.1, 0.3]
private let rulerColorPanelIdentifier = NSUserInterfaceItemIdentifier("ruler-color-panel")
private let rulerColorPanelOpaqueAccessibilityValue = "ruler-color-panel-alpha-hidden"

func configureOpaqueColorPicking() {
let colorPanel = NSColorPanel.shared
colorPanel.identifier = rulerColorPanelIdentifier
colorPanel.setAccessibilityIdentifier(rulerColorPanelIdentifier.rawValue)
if UI_TESTS {
colorPanel.setAccessibilityValue(rulerColorPanelOpaqueAccessibilityValue)
}
setColorPickingIgnoresAlpha(true)
NSColorPanel.shared.showsAlpha = false
NSColorPanel.shared.isContinuous = true
NSColorPanel.shared.animationBehavior = .none
colorPanel.showsAlpha = false
colorPanel.isContinuous = true
colorPanel.animationBehavior = .none
colorPanel.isRestorable = false
}

private func setColorPickingIgnoresAlpha(_ ignoresAlpha: Bool) {
typealias Setter = @convention(c) (AnyClass, Selector, Bool) -> Void
let selector = Selector(("setIgnoresAlpha:"))
private func configureOpaqueColorPickingAfterPanelUpdates() {
configureOpaqueColorPicking()

guard let method = class_getClassMethod(NSColor.self, selector) else { return }
// The shared color panel can rebuild picker controls shortly after opening; reapply during
// that churn so alpha controls stay hidden without doing work for every color change.
for delay in colorPanelOpaqueConfigurationRetryDelays {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
configureOpaqueColorPicking()
}
Comment thread
pascalpp marked this conversation as resolved.
}
}

private func setColorPickingIgnoresAlpha(_ ignoresAlpha: Bool) {
// AppKit still consults this deprecated global switch when deciding whether color wells support alpha.
let setter = unsafeBitCast(method_getImplementation(method), to: Setter.self)
setter(NSColor.self, selector, ignoresAlpha)
NSColor.ignoresAlpha = ignoresAlpha
}

class RulerColorWell: NSColorWell {
Expand Down Expand Up @@ -45,8 +61,9 @@ class RulerColorWell: NSColorWell {
colorPanel.color = color
colorPanel.setTarget(self)
colorPanel.setAction(#selector(takeColorFrom(_:)))
NSApp.orderFrontColorPanel(self)
colorPanel.orderFront(self)
configureForOpaqueColors()
configureOpaqueColorPickingAfterPanelUpdates()
}

override func draw(_ dirtyRect: NSRect) {
Expand Down Expand Up @@ -136,6 +153,7 @@ class PreferencesController: NSWindowController, NSWindowDelegate, NotificationP
}

func windowWillClose(_ notification: Notification) {
rulerColorWell.deactivate()
closeRulerColorPanel()

// send closed notification
Expand Down Expand Up @@ -263,16 +281,15 @@ class PreferencesController: NSWindowController, NSWindowDelegate, NotificationP
let colorPanel = notification.object as? NSColorPanel,
colorPanel.isVisible else { return }

configureOpaqueColorPicking()
prefs.rulerColor = colorPanel.color
}

}

private func closeRulerColorPanel() {
func closeRulerColorPanel() {
let colorPanel = NSColorPanel.shared
colorPanel.animationBehavior = .none
colorPanel.setTarget(nil)
colorPanel.setAction(nil)
colorPanel.orderOut(nil)
colorPanel.close()
}
98 changes: 98 additions & 0 deletions FreeRulerUITests/FreeRulerUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Darwin

final class FreeRulerUITests: XCTestCase {

private let opaqueColorPanelValue = "ruler-color-panel-alpha-hidden"

private var app: XCUIApplication!
private var cursorStateURL: URL!

Expand Down Expand Up @@ -103,6 +105,45 @@ final class FreeRulerUITests: XCTestCase {
XCTAssertTrue(preferences.waitForNonExistence(timeout: 2))
}

func testRulerColorPanelHidesOpacityControl() {
openRulerColorPanel()
XCTAssertTrue(
colorPanel.waitForVisibleFrame(timeout: 1),
"The ruler color panel should be visible."
)
XCTAssertEqual(
colorPanel.value as? String,
opaqueColorPanelValue,
"The ruler color panel should be configured for opaque color picking."
)
XCTAssertEqual(
visibleSliderCount(in: colorPanel),
1,
"The ruler color panel should show the color slider, but not an opacity slider."
)
}

func testClosingPreferencesClosesRulerColorPanel() {
openRulerColorPanel()

preferencesWindow.click()
app.typeKey("w", modifierFlags: .command)

Comment thread
pascalpp marked this conversation as resolved.
XCTAssertTrue(preferencesWindow.waitForNonExistence(timeout: 2))
XCTAssertTrue(colorPanel.waitForNonExistence(timeout: 2))
}

func testRulerColorPanelDoesNotReopenAfterRelaunch() {
openRulerColorPanel()

app.terminate()
app.launch()
app.activate()

XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3))
XCTAssertTrue(colorPanel.waitForNonExistence(timeout: 2))
Comment thread
pascalpp marked this conversation as resolved.
}

func testRulerCloseWithCommandW() {
XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3))
XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3))
Expand Down Expand Up @@ -298,6 +339,14 @@ final class FreeRulerUITests: XCTestCase {
app.checkBoxes["ruler-shadow-checkbox"]
}

private var rulerColorWell: XCUIElement {
app.colorWells["ruler-color-well"]
}

private var colorPanel: XCUIElement {
app.windows["ruler-color-panel"]
}
Comment thread
pascalpp marked this conversation as resolved.

private var hotkeyBezelLabel: XCUIElement {
app.staticTexts["hotkey-bezel-label"]
}
Expand All @@ -321,6 +370,19 @@ final class FreeRulerUITests: XCTestCase {
}
}

private func openRulerColorPanel() {
openPreferences()

if colorPanel.exists {
colorPanel.click()
app.typeKey("w", modifierFlags: .command)
XCTAssertTrue(colorPanel.waitForNonExistence(timeout: 2))
}

rulerColorWell.click()
XCTAssertTrue(colorPanel.waitForExistence(timeout: 3))
}

private func setGroupRulers(_ enabled: Bool) {
openPreferences()

Expand Down Expand Up @@ -486,6 +548,10 @@ final class FreeRulerUITests: XCTestCase {

return String(cString: passwd.pointee.pw_dir)
}

private func visibleSliderCount(in element: XCUIElement) -> Int {
return element.sliders.allElementsBoundByIndex.filter(\.hasVisibleFrame).count
}
}

private extension XCUIElement {
Expand All @@ -495,6 +561,38 @@ private extension XCUIElement {
return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed
}

func waitForNoVisibleFrame(timeout: TimeInterval) -> Bool {
let deadline = Date().addingTimeInterval(timeout)

while Date() < deadline {
if !hasVisibleFrame {
return true
}

RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}

return !hasVisibleFrame
}

func waitForVisibleFrame(timeout: TimeInterval) -> Bool {
let deadline = Date().addingTimeInterval(timeout)

while Date() < deadline {
if hasVisibleFrame {
return true
}

RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}

return hasVisibleFrame
}

var hasVisibleFrame: Bool {
return exists && !frame.isEmpty && !frame.isNull
}

func waitForFrameChange(from originalFrame: CGRect, timeout: TimeInterval) -> Bool {
let deadline = Date().addingTimeInterval(timeout)

Expand Down
Binary file modified appstore/screenshots/01-measure-anything.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified appstore/screenshots/02-custom-colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified appstore/screenshots/03-switch-units.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified appstore/screenshots/04-customize-rulers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.