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 @@ - - + + - + - + @@ -32,13 +32,13 @@ - + - + - + 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()