diff --git a/Free Ruler/Base.lproj/PreferencesController.xib b/Free Ruler/Base.lproj/PreferencesController.xib
index eadc90a..1c0a9fb 100644
--- a/Free Ruler/Base.lproj/PreferencesController.xib
+++ b/Free Ruler/Base.lproj/PreferencesController.xib
@@ -17,14 +17,14 @@
-
-
+
+
-
+
-
+
-
+
-
+
diff --git a/Free Ruler/Base.lproj/RulerSettingsController.xib b/Free Ruler/Base.lproj/RulerSettingsController.xib
index 4af3cf0..e129e4b 100644
--- a/Free Ruler/Base.lproj/RulerSettingsController.xib
+++ b/Free Ruler/Base.lproj/RulerSettingsController.xib
@@ -19,7 +19,7 @@
-
+
diff --git a/Free Ruler/Base.lproj/RulerSettingsControlsView.xib b/Free Ruler/Base.lproj/RulerSettingsControlsView.xib
index cb04815..e21eba1 100644
--- a/Free Ruler/Base.lproj/RulerSettingsControlsView.xib
+++ b/Free Ruler/Base.lproj/RulerSettingsControlsView.xib
@@ -10,108 +10,211 @@
+
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Free Ruler/Localizable.xcstrings b/Free Ruler/Localizable.xcstrings
index b6c2d2a..6245bef 100644
--- a/Free Ruler/Localizable.xcstrings
+++ b/Free Ruler/Localizable.xcstrings
@@ -971,90 +971,6 @@
}
}
},
- "RulerSettingsControls.Dimensions" : {
- "comment" : "Label for active ruler width and height fields",
- "extractionState" : "manual",
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Abmessungen"
- }
- },
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Dimensions"
- }
- },
- "es" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Dimensiones"
- }
- },
- "fi" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Mitat"
- }
- },
- "ja" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "寸法"
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "尺寸"
- }
- }
- }
- },
- "RulerSettingsControls.Unit" : {
- "comment" : "Label for the active ruler measurement unit setting",
- "extractionState" : "manual",
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Einheit"
- }
- },
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Unit"
- }
- },
- "es" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Unidad"
- }
- },
- "fi" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Yksikkö"
- }
- },
- "ja" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "単位"
- }
- },
- "zh-Hans" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "单位"
- }
- }
- }
- },
"Unit.Inches.Abbreviation" : {
"comment" : "Inches unit abbreviation",
"extractionState" : "manual",
diff --git a/Free Ruler/PreferencesController.swift b/Free Ruler/PreferencesController.swift
index 1852e80..153da90 100644
--- a/Free Ruler/PreferencesController.swift
+++ b/Free Ruler/PreferencesController.swift
@@ -206,61 +206,25 @@ final class RulerSettingsControlsView: NSView, NSTextFieldDelegate {
weak var delegate: RulerSettingsControlsViewDelegate?
@IBOutlet var contentView: NSView!
+ @IBOutlet weak var unitLabel: NSTextField!
+ @IBOutlet weak var unitSegmentedControl: NSSegmentedControl!
+ @IBOutlet weak var dimensionsLabel: NSTextField!
+ @IBOutlet weak var dimensionWidthField: NSTextField!
+ @IBOutlet weak var dimensionsSeparatorLabel: NSTextField!
+ @IBOutlet weak var dimensionHeightField: NSTextField!
+ @IBOutlet weak var rulerColorLabel: NSTextField!
@IBOutlet weak var rulerColorWell: RulerColorWell!
@IBOutlet weak var resetRulerColorButton: NSButton!
+ @IBOutlet weak var foregroundOpacityTitleLabel: NSTextField!
@IBOutlet weak var foregroundOpacitySlider: NSSlider!
+ @IBOutlet weak var backgroundOpacityTitleLabel: NSTextField!
@IBOutlet weak var backgroundOpacitySlider: NSSlider!
@IBOutlet weak var foregroundOpacityLabel: NSTextField!
@IBOutlet weak var backgroundOpacityLabel: NSTextField!
@IBOutlet weak var floatRulersCheckbox: NSButton!
@IBOutlet weak var rulerShadowCheckbox: NSButton!
- let unitLabel = NSTextField(labelWithString: NSLocalizedString(
- "RulerSettingsControls.Unit",
- value: "Unit",
- comment: "Label for the active ruler measurement unit setting"
- ))
- let unitSegmentedControl = NSSegmentedControl(
- labels: [
- NSLocalizedString(
- "Unit.Pixels.Abbreviation",
- value: "px",
- comment: "Pixels unit abbreviation"
- ),
- NSLocalizedString(
- "Unit.Millimeters.Abbreviation",
- value: "mm",
- comment: "Millimeters unit abbreviation"
- ),
- NSLocalizedString(
- "Unit.Inches.Abbreviation",
- value: "in",
- comment: "Inches unit abbreviation"
- ),
- ],
- trackingMode: .selectOne,
- target: nil,
- action: nil
- )
- let dimensionsLabel = NSTextField(labelWithString: NSLocalizedString(
- "RulerSettingsControls.Dimensions",
- value: "Dimensions",
- comment: "Label for active ruler width and height fields"
- ))
- let dimensionWidthField = NSTextField()
- let dimensionsSeparatorLabel = NSTextField(labelWithString: "x")
- let dimensionHeightField = NSTextField()
-
- private var showsDimensions = true
private var dimensionScreen: NSScreen?
- private var dimensionControls: [NSView] {
- return [
- dimensionsLabel,
- dimensionWidthField,
- dimensionsSeparatorLabel,
- dimensionHeightField,
- ]
- }
var selectedUnit: Unit {
return Unit(rawValue: unitSegmentedControl.selectedSegment) ?? .pixels
@@ -301,8 +265,6 @@ final class RulerSettingsControlsView: NSView, NSTextFieldDelegate {
}
func configureForPreferences() {
- showsDimensions = true
- updateDimensionsVisibility()
configureControls(
unitSegmentedControlIdentifier: "ruler-unit-segmented-control",
widthFieldIdentifier: "ruler-width-field",
@@ -321,8 +283,6 @@ final class RulerSettingsControlsView: NSView, NSTextFieldDelegate {
}
func configureForRulerSettings() {
- showsDimensions = true
- updateDimensionsVisibility()
configureControls(
unitSegmentedControlIdentifier: "ruler-settings-unit-segmented-control",
widthFieldIdentifier: "ruler-settings-width-field",
@@ -413,12 +373,6 @@ final class RulerSettingsControlsView: NSView, NSTextFieldDelegate {
}
}
- override func layout() {
- super.layout()
-
- layoutTopControls()
- }
-
func performRulerSettingsKeyEquivalent(with event: NSEvent) -> Bool {
guard event.type == .keyDown,
event.modifierFlags
@@ -463,18 +417,9 @@ final class RulerSettingsControlsView: NSView, NSTextFieldDelegate {
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
- installTopControls()
configureBaseControls()
}
- private func installTopControls() {
- for control in [unitLabel, unitSegmentedControl] + dimensionControls {
- control.translatesAutoresizingMaskIntoConstraints = true
- contentView.addSubview(control)
- }
- updateDimensionsVisibility()
- }
-
private func configureBaseControls() {
unitSegmentedControl.target = self
unitSegmentedControl.action = #selector(setUnit(_:))
@@ -571,7 +516,7 @@ final class RulerSettingsControlsView: NSView, NSTextFieldDelegate {
}
private func configureKeyViewLoop() {
- unitSegmentedControl.nextKeyView = showsDimensions ? dimensionWidthField : rulerColorWell
+ unitSegmentedControl.nextKeyView = dimensionWidthField
dimensionWidthField.nextKeyView = dimensionHeightField
dimensionHeightField.nextKeyView = rulerColorWell
rulerColorWell.nextKeyView = resetRulerColorButton.isHidden
@@ -601,62 +546,6 @@ final class RulerSettingsControlsView: NSView, NSTextFieldDelegate {
field.action = #selector(setDimensions(_:))
}
- private func updateDimensionsVisibility() {
- for control in dimensionControls {
- control.isHidden = !showsDimensions
- }
- needsLayout = true
- configureKeyViewLoop()
- }
-
- private func layoutTopControls() {
- guard contentView != nil else { return }
-
- let labelX: CGFloat = 15
- let labelWidth: CGFloat = 150
- let labelHeight: CGFloat = 16
- let rightMargin: CGFloat = 21
- let contentWidth = contentView.bounds.width
- let controlRight = contentWidth - rightMargin
-
- let unitControlWidth: CGFloat = 108
- let unitY: CGFloat = showsDimensions ? 285 : 249
- unitLabel.frame = NSRect(x: labelX, y: unitY, width: labelWidth, height: labelHeight)
- unitSegmentedControl.frame = NSRect(
- x: controlRight - unitControlWidth,
- y: unitY - 4,
- width: unitControlWidth,
- height: 24
- )
-
- let dimensionsY: CGFloat = 249
- let fieldWidth: CGFloat = 56
- let separatorWidth: CGFloat = 18
- let heightFieldX = controlRight - fieldWidth
- let separatorX = heightFieldX - separatorWidth
- let widthFieldX = separatorX - fieldWidth
-
- dimensionsLabel.frame = NSRect(x: labelX, y: dimensionsY, width: labelWidth, height: labelHeight)
- dimensionWidthField.frame = NSRect(
- x: widthFieldX,
- y: dimensionsY - 4,
- width: fieldWidth,
- height: 24
- )
- dimensionsSeparatorLabel.frame = NSRect(
- x: separatorX,
- y: dimensionsY,
- width: separatorWidth,
- height: labelHeight
- )
- dimensionHeightField.frame = NSRect(
- x: heightFieldX,
- y: dimensionsY - 4,
- width: fieldWidth,
- height: 24
- )
- }
-
private func toggleFloatRulersFromKeyEquivalent() -> Bool {
guard floatRulersCheckbox.isEnabled else { return false }
diff --git a/Free Ruler/de.lproj/RulerSettingsControlsView.strings b/Free Ruler/de.lproj/RulerSettingsControlsView.strings
index 3bb8da3..4a94f2a 100644
--- a/Free Ruler/de.lproj/RulerSettingsControlsView.strings
+++ b/Free Ruler/de.lproj/RulerSettingsControlsView.strings
@@ -5,6 +5,9 @@
/* Class = "NSTextFieldCell"; title = "Ruler Color"; ObjectID = "RSV-color-cell"; */
"RSV-color-cell.title" = "Linealfarbe";
+/* Class = "NSTextFieldCell"; title = "Dimensions"; ObjectID = "RSV-dimensions-cell"; */
+"RSV-dimensions-cell.title" = "Abmessungen";
+
/* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "RSV-fg-cell"; */
"RSV-fg-cell.title" = "Deckkraft im Vordergrund";
@@ -13,3 +16,6 @@
/* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "RSV-shadow-cell"; */
"RSV-shadow-cell.title" = "Linealschatten anzeigen";
+
+/* Class = "NSTextFieldCell"; title = "Unit"; ObjectID = "RSV-unit-cell"; */
+"RSV-unit-cell.title" = "Einheit";
diff --git a/Free Ruler/es.lproj/RulerSettingsControlsView.strings b/Free Ruler/es.lproj/RulerSettingsControlsView.strings
index 6196a4d..35ffc26 100644
--- a/Free Ruler/es.lproj/RulerSettingsControlsView.strings
+++ b/Free Ruler/es.lproj/RulerSettingsControlsView.strings
@@ -5,6 +5,9 @@
/* Class = "NSTextFieldCell"; title = "Ruler Color"; ObjectID = "RSV-color-cell"; */
"RSV-color-cell.title" = "Color de la regla";
+/* Class = "NSTextFieldCell"; title = "Dimensions"; ObjectID = "RSV-dimensions-cell"; */
+"RSV-dimensions-cell.title" = "Dimensiones";
+
/* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "RSV-fg-cell"; */
"RSV-fg-cell.title" = "Opacidad del primer plano";
@@ -13,3 +16,6 @@
/* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "RSV-shadow-cell"; */
"RSV-shadow-cell.title" = "Mostrar sombra de la regla";
+
+/* Class = "NSTextFieldCell"; title = "Unit"; ObjectID = "RSV-unit-cell"; */
+"RSV-unit-cell.title" = "Unidad";
diff --git a/Free Ruler/fi.lproj/RulerSettingsControlsView.strings b/Free Ruler/fi.lproj/RulerSettingsControlsView.strings
index 84adb4e..d2cc0ad 100644
--- a/Free Ruler/fi.lproj/RulerSettingsControlsView.strings
+++ b/Free Ruler/fi.lproj/RulerSettingsControlsView.strings
@@ -5,6 +5,9 @@
/* Class = "NSTextFieldCell"; title = "Ruler Color"; ObjectID = "RSV-color-cell"; */
"RSV-color-cell.title" = "Viivaimen väri";
+/* Class = "NSTextFieldCell"; title = "Dimensions"; ObjectID = "RSV-dimensions-cell"; */
+"RSV-dimensions-cell.title" = "Mitat";
+
/* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "RSV-fg-cell"; */
"RSV-fg-cell.title" = "Peittävyys edustalla";
@@ -13,3 +16,6 @@
/* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "RSV-shadow-cell"; */
"RSV-shadow-cell.title" = "Näytä viivainten varjot";
+
+/* Class = "NSTextFieldCell"; title = "Unit"; ObjectID = "RSV-unit-cell"; */
+"RSV-unit-cell.title" = "Yksikkö";
diff --git a/Free Ruler/ja.lproj/RulerSettingsControlsView.strings b/Free Ruler/ja.lproj/RulerSettingsControlsView.strings
index 8a0334d..21fb77e 100644
--- a/Free Ruler/ja.lproj/RulerSettingsControlsView.strings
+++ b/Free Ruler/ja.lproj/RulerSettingsControlsView.strings
@@ -5,6 +5,9 @@
/* Class = "NSTextFieldCell"; title = "Ruler Color"; ObjectID = "RSV-color-cell"; */
"RSV-color-cell.title" = "定規の色";
+/* Class = "NSTextFieldCell"; title = "Dimensions"; ObjectID = "RSV-dimensions-cell"; */
+"RSV-dimensions-cell.title" = "寸法";
+
/* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "RSV-fg-cell"; */
"RSV-fg-cell.title" = "前景の不透明度";
@@ -13,3 +16,6 @@
/* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "RSV-shadow-cell"; */
"RSV-shadow-cell.title" = "定規の影を表示";
+
+/* Class = "NSTextFieldCell"; title = "Unit"; ObjectID = "RSV-unit-cell"; */
+"RSV-unit-cell.title" = "単位";
diff --git a/Free Ruler/zh-hans.lproj/RulerSettingsControlsView.strings b/Free Ruler/zh-hans.lproj/RulerSettingsControlsView.strings
index 2f85d90..5452440 100644
--- a/Free Ruler/zh-hans.lproj/RulerSettingsControlsView.strings
+++ b/Free Ruler/zh-hans.lproj/RulerSettingsControlsView.strings
@@ -5,6 +5,9 @@
/* Class = "NSTextFieldCell"; title = "Ruler Color"; ObjectID = "RSV-color-cell"; */
"RSV-color-cell.title" = "尺子颜色";
+/* Class = "NSTextFieldCell"; title = "Dimensions"; ObjectID = "RSV-dimensions-cell"; */
+"RSV-dimensions-cell.title" = "尺寸";
+
/* Class = "NSTextFieldCell"; title = "Foreground Opacity"; ObjectID = "RSV-fg-cell"; */
"RSV-fg-cell.title" = "前景不透明度";
@@ -13,3 +16,6 @@
/* Class = "NSButtonCell"; title = "Show ruler shadow"; ObjectID = "RSV-shadow-cell"; */
"RSV-shadow-cell.title" = "显示尺子阴影";
+
+/* Class = "NSTextFieldCell"; title = "Unit"; ObjectID = "RSV-unit-cell"; */
+"RSV-unit-cell.title" = "单位";
diff --git a/FreeRulerTests/RulerCoreTests.swift b/FreeRulerTests/RulerCoreTests.swift
index 22602c7..b3b531a 100644
--- a/FreeRulerTests/RulerCoreTests.swift
+++ b/FreeRulerTests/RulerCoreTests.swift
@@ -846,6 +846,76 @@ final class RulerCoreTests: XCTestCase {
XCTAssertEqual(controlsView.selectedVerticalLength, 2.75 * NSScreen.defaultDpi, accuracy: 0.0001)
}
+ func testRulerSettingsControlsLayoutUsesSharedInsetsAndAlignedRows() {
+ let controlsView = RulerSettingsControlsView(frame: NSRect(x: 0, y: 0, width: 315, height: 320))
+ controlsView.configureForRulerSettings()
+
+ controlsView.update(
+ unit: .pixels,
+ horizontalLength: 260,
+ verticalLength: 180,
+ rulerColor: Prefs.defaultRulerFillColor,
+ foregroundOpacity: 90,
+ backgroundOpacity: 50,
+ floatRulers: true,
+ rulerShadow: false
+ )
+ controlsView.layoutSubtreeIfNeeded()
+ controlsView.contentView.layoutSubtreeIfNeeded()
+
+ let leftAlignedControls: [NSView] = [
+ controlsView.unitLabel,
+ controlsView.dimensionsLabel,
+ controlsView.rulerColorLabel,
+ controlsView.foregroundOpacityTitleLabel,
+ controlsView.foregroundOpacitySlider,
+ controlsView.backgroundOpacityTitleLabel,
+ controlsView.backgroundOpacitySlider,
+ controlsView.floatRulersCheckbox,
+ controlsView.rulerShadowCheckbox,
+ ]
+ let rightAlignedControls: [NSView] = [
+ controlsView.unitSegmentedControl,
+ controlsView.dimensionHeightField,
+ controlsView.rulerColorWell,
+ controlsView.foregroundOpacityLabel,
+ controlsView.foregroundOpacitySlider,
+ controlsView.backgroundOpacityLabel,
+ controlsView.backgroundOpacitySlider,
+ ]
+ func alignmentRect(_ view: NSView) -> NSRect {
+ return view.alignmentRect(forFrame: view.frame)
+ }
+ func firstBaselineY(_ view: NSView) -> CGFloat {
+ return view.frame.maxY - view.firstBaselineOffsetFromTop
+ }
+
+ let expectedInset: CGFloat = 15
+ let baselineAccuracy: CGFloat = 1
+ let expectedLeftInset = controlsView.contentView.bounds.minX + expectedInset
+ let expectedRightEdge = controlsView.contentView.bounds.maxX - expectedInset
+ let unitTopInset = controlsView.contentView.bounds.maxY
+ - alignmentRect(controlsView.unitSegmentedControl).maxY
+ let unitToDimensionsSpacing = alignmentRect(controlsView.unitSegmentedControl).minY
+ - alignmentRect(controlsView.dimensionHeightField).maxY
+
+ XCTAssertEqual(unitTopInset, expectedInset, accuracy: 0.5)
+ for control in leftAlignedControls {
+ XCTAssertEqual(alignmentRect(control).minX, expectedLeftInset, accuracy: 0.5)
+ }
+ for control in rightAlignedControls {
+ XCTAssertEqual(alignmentRect(control).maxX, expectedRightEdge, accuracy: 0.5)
+ }
+ XCTAssertEqual(unitToDimensionsSpacing, expectedInset, accuracy: 0.5)
+ XCTAssertEqual(firstBaselineY(controlsView.unitLabel), firstBaselineY(controlsView.unitSegmentedControl), accuracy: baselineAccuracy)
+ XCTAssertEqual(firstBaselineY(controlsView.dimensionsLabel), firstBaselineY(controlsView.dimensionWidthField), accuracy: baselineAccuracy)
+ XCTAssertEqual(firstBaselineY(controlsView.dimensionWidthField), firstBaselineY(controlsView.dimensionsSeparatorLabel), accuracy: baselineAccuracy)
+ XCTAssertEqual(firstBaselineY(controlsView.dimensionsSeparatorLabel), firstBaselineY(controlsView.dimensionHeightField), accuracy: baselineAccuracy)
+ XCTAssertEqual(alignmentRect(controlsView.rulerColorLabel).midY, alignmentRect(controlsView.rulerColorWell).midY, accuracy: 0.5)
+ XCTAssertEqual(firstBaselineY(controlsView.foregroundOpacityTitleLabel), firstBaselineY(controlsView.foregroundOpacityLabel), accuracy: baselineAccuracy)
+ XCTAssertEqual(firstBaselineY(controlsView.backgroundOpacityTitleLabel), firstBaselineY(controlsView.backgroundOpacityLabel), accuracy: baselineAccuracy)
+ }
+
func testRulerSettingsControlsKeyViewLoopFollowsVisibleControls() {
let controlsView = RulerSettingsControlsView(frame: NSRect(x: 0, y: 0, width: 315, height: 320))
controlsView.configureForRulerSettings()