feat: divide preferences by topic (closes #130)
This commit is contained in:
1 parent
d52eb6d074
commit
291f872d3d
7 files changed
+111
-77
No files matched your search
@@ -32,6 +32,7 @@
|
||||
D04BAD4DE538FDF7E7532EE2 /* Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD32E130E4A061DC8332 /* Labels.swift */; };
|
||||
D04BAE2E8E9B9898A4DF9B3B /* FontIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAED53465957807CBF8B2 /* FontIcon.swift */; };
|
||||
D04BAE369A14C3126A1606FE /* HelperExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8F1AA48A323EE5638DC /* HelperExtensions.swift */; };
|
||||
D04BAE53AE67492CF654B4AE /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE70A24C83D571C77B1A /* TabViewController.swift */; };
|
||||
D04BAEF78503D7A2CEFB9E9E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA44C837F3A67403B9DB /* main.swift */; };
|
||||
F029861A378EC1417106FEC3 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0298E42A818112B290FF6C7 /* TextField.swift */; };
|
||||
F0298AB28A3CE5DBEC385730 /* HyperlinkLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0298708E2B13DBD4738AE76 /* HyperlinkLabel.swift */; };
|
||||
@@ -92,6 +93,7 @@
|
||||
D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
D04BAE1243C9B4BE3ED1B524 /* 7 windows - 2 lines - extra wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - extra wide window.jpg"; sourceTree = "<group>"; };
|
||||
D04BAE5BBE182DD5DDFE2E3E /* ThumbnailsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailsPanel.swift; sourceTree = "<group>"; };
|
||||
D04BAE70A24C83D571C77B1A /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = "<group>"; };
|
||||
D04BAE80772D25834E440975 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = "<group>"; };
|
||||
D04BAE93A5854C501639C640 /* update_homebrew_cask.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = update_homebrew_cask.sh; sourceTree = "<group>"; };
|
||||
D04BAEA3EDC4F80FA23DBEC4 /* CGWindowID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGWindowID.swift; sourceTree = "<group>"; };
|
||||
@@ -257,6 +259,7 @@
|
||||
F0298E42A818112B290FF6C7 /* TextField.swift */,
|
||||
F0298708E2B13DBD4738AE76 /* HyperlinkLabel.swift */,
|
||||
D04BAED53465957807CBF8B2 /* FontIcon.swift */,
|
||||
D04BAE70A24C83D571C77B1A /* TabViewController.swift */,
|
||||
);
|
||||
path = ui;
|
||||
sourceTree = "<group>";
|
||||
@@ -388,6 +391,7 @@
|
||||
D04BA1BA0B3F2E0A47883569 /* Application.swift in Sources */,
|
||||
D04BA2378832FD7E5DE3BC23 /* Applications.swift in Sources */,
|
||||
D04BAAD43731608067734ED3 /* DispatchQueues.swift in Sources */,
|
||||
D04BAE53AE67492CF654B4AE /* TabViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ class BaseLabel: NSTextView {
|
||||
|
||||
init(_ text: String) {
|
||||
super.init(frame: .zero)
|
||||
_init()
|
||||
string = text
|
||||
}
|
||||
|
||||
@@ -20,7 +21,6 @@ class BaseLabel: NSTextView {
|
||||
backgroundColor = .clear
|
||||
isSelectable = false
|
||||
isEditable = false
|
||||
font = Preferences.font
|
||||
enabledTextCheckingTypes = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,35 @@ import Cocoa
|
||||
import Foundation
|
||||
|
||||
class PreferencesWindow: NSWindow, NSWindowDelegate {
|
||||
let width = CGFloat(496)
|
||||
let height = CGFloat(256) // auto expands to content height (but does not auto shrink)
|
||||
let padding = CGFloat(40)
|
||||
var labelWidth: CGFloat {
|
||||
return (width - padding) * CGFloat(0.45)
|
||||
}
|
||||
let tabViewController = TabViewController()
|
||||
let padding = CGFloat(20)
|
||||
let interPadding = CGFloat(10)
|
||||
var windowCloseRequested = false
|
||||
|
||||
override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) {
|
||||
let initialRect = NSRect(x: 0, y: 0, width: width, height: height)
|
||||
super.init(contentRect: initialRect, styleMask: style, backing: backingStoreType, defer: flag)
|
||||
super.init(contentRect: .zero, styleMask: style, backing: backingStoreType, defer: flag)
|
||||
title = App.name + " Preferences"
|
||||
hidesOnDeactivate = false
|
||||
isReleasedWhenClosed = false
|
||||
styleMask.insert([.miniaturizable, .closable])
|
||||
contentView = makeContentView()
|
||||
tabViewController.tabStyle = .toolbar
|
||||
contentViewController = tabViewController
|
||||
makeTabViews()
|
||||
}
|
||||
|
||||
private func makeTabViews() {
|
||||
for tabTuple in [
|
||||
("Shortcuts", makeShortcutsView(), NSImage.preferencesGeneralName),
|
||||
("Appearance", makeAppearanceView(), NSImage.colorPanelName),
|
||||
("About", makeAboutView(), NSImage.infoName)
|
||||
] {
|
||||
let viewController = NSViewController()
|
||||
viewController.view = tabTuple.1
|
||||
let tabViewItem = NSTabViewItem(viewController: viewController)
|
||||
tabViewItem.label = tabTuple.0
|
||||
tabViewItem.image = NSImage(named: tabTuple.2)!
|
||||
tabViewController.addTabViewItem(tabViewItem)
|
||||
}
|
||||
}
|
||||
|
||||
func show() {
|
||||
@@ -45,24 +58,7 @@ class PreferencesWindow: NSWindow, NSWindowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func makeContentView() -> NSView {
|
||||
let wrappingView = NSStackView(views: makePreferencesViews())
|
||||
let contentView = NSView()
|
||||
contentView.addSubview(wrappingView)
|
||||
|
||||
// visual setup
|
||||
wrappingView.orientation = .vertical
|
||||
wrappingView.alignment = .left
|
||||
wrappingView.spacing = padding * 0.3
|
||||
wrappingView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding * 0.5).isActive = true
|
||||
wrappingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: padding * -0.5).isActive = true
|
||||
wrappingView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: padding * 0.5).isActive = true
|
||||
wrappingView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: padding * -0.5).isActive = true
|
||||
|
||||
return contentView
|
||||
}
|
||||
|
||||
private func makePreferencesViews() -> [NSView] {
|
||||
private func makeShortcutsView() -> NSGridView {
|
||||
// TODO: make the validators be a part of each Preference
|
||||
let tabKeyCodeValidator: ((String) -> Bool) = {
|
||||
guard let int = Int($0) else {
|
||||
@@ -77,10 +73,14 @@ class PreferencesWindow: NSWindow, NSWindowDelegate {
|
||||
return whitelistedKeycodes.contains(int)
|
||||
}
|
||||
|
||||
return [
|
||||
return makeGridLayout([
|
||||
makeLabelWithDropdown("Alt key", "metaKey", Preferences.metaKeyMacro.labels),
|
||||
makeLabelWithInput("Tab key", "tabKeyCode", 33, "KeyCodes Reference", "https://eastmanreference.com/complete-list-of-applescript-key-codes", tabKeyCodeValidator),
|
||||
makeHorizontalSeparator(),
|
||||
])
|
||||
}
|
||||
|
||||
private func makeAppearanceView() -> NSGridView {
|
||||
return makeGridLayout([
|
||||
makeLabelWithDropdown("Theme", "theme", Preferences.themeMacro.labels),
|
||||
makeLabelWithSlider("Max size on screen", "maxScreenUsage", 10, 100, 10, true, "%"),
|
||||
makeLabelWithSlider("Min windows per row", "minCellsPerRow", 1, 20, 20, true),
|
||||
@@ -88,82 +88,93 @@ class PreferencesWindow: NSWindow, NSWindowDelegate {
|
||||
makeLabelWithSlider("Min rows of windows", "minRows", 1, 20, 20, true),
|
||||
makeLabelWithSlider("Window app icon size", "iconSize", 0, 64, 11, false, "px"),
|
||||
makeLabelWithSlider("Window title font size", "fontHeight", 0, 64, 11, false, "px"),
|
||||
makeLabelWithCheckbox("Hide space number labels", "hideSpaceNumberLabels"),
|
||||
makeHorizontalSeparator(),
|
||||
makeLabelWithDropdown("Show on", "showOnScreen", Preferences.showOnScreenMacro.labels),
|
||||
makeLabelWithSlider("Apparition delay", "windowDisplayDelay", 0, 2000, 11, false, "ms"),
|
||||
makeLabelWithDropdown("Show on", "showOnScreen", Preferences.showOnScreenMacro.labels)
|
||||
]
|
||||
makeLabelWithCheckbox("Hide space number labels", "hideSpaceNumberLabels"),
|
||||
])
|
||||
}
|
||||
|
||||
private func makeHorizontalSeparator() -> NSView {
|
||||
let view = NSBox()
|
||||
view.boxType = .separator
|
||||
|
||||
return view
|
||||
private func makeAboutView() -> NSGridView {
|
||||
return makeGridLayout([
|
||||
[NSTextField(wrappingLabelWithString: "\(App.name) #VERSION#"), ],
|
||||
[HyperlinkLabel(labelWithUrl: "Source code repository", nsUrl: NSURL(string: "https://github.com/lwouis/alt-tab-macos")!)],
|
||||
])
|
||||
}
|
||||
|
||||
private func makeLabelWithInput(_ labelText: String, _ rawName: String, _ width: CGFloat? = nil, _ suffixText: String? = nil, _ suffixUrl: String? = nil, _ validator: ((String) -> Bool)? = nil) -> NSStackView {
|
||||
private func makeGridLayout(_ controls: [[NSView]]) -> NSGridView {
|
||||
let gridView = NSGridView(views: controls)
|
||||
gridView.yPlacement = .fill
|
||||
gridView.columnSpacing = interPadding
|
||||
gridView.rowSpacing = interPadding
|
||||
gridView.column(at: 0).xPlacement = .trailing
|
||||
gridView.column(at: 0).leadingPadding = padding
|
||||
gridView.column(at: gridView.numberOfColumns - 1).trailingPadding = padding
|
||||
gridView.row(at: 0).topPadding = padding
|
||||
gridView.row(at: gridView.numberOfRows - 1).bottomPadding = padding
|
||||
gridView.widthAnchor.constraint(equalToConstant: gridView.fittingSize.width).isActive = true
|
||||
gridView.rowAlignment = .lastBaseline
|
||||
for i in 0..<gridView.numberOfRows {
|
||||
gridView.row(at: i).height = 20
|
||||
}
|
||||
return gridView
|
||||
}
|
||||
|
||||
private func makeLabelWithInput(_ labelText: String, _ rawName: String, _ width: CGFloat, _ suffixText: String? = nil, _ suffixUrl: String? = nil, _ validator: ((String) -> Bool)? = nil) -> [NSView] {
|
||||
let input = TextField(Preferences.rawValues[rawName]!)
|
||||
input.validationHandler = validator
|
||||
input.delegate = input
|
||||
input.visualizeValidationState()
|
||||
if width != nil {
|
||||
input.widthAnchor.constraint(equalToConstant: width!).isActive = true
|
||||
}
|
||||
|
||||
return makeLabelWithProvidedControl(labelText, rawName, input, suffixText, nil, suffixUrl)
|
||||
input.widthAnchor.constraint(equalToConstant: width).isActive = true
|
||||
input.heightAnchor.constraint(equalToConstant: input.fittingSize.height).isActive = true
|
||||
let views = makeLabelWithProvidedControl(labelText, rawName, input)
|
||||
return [views[0], NSStackView(views: [views[1], makeSuffix(rawName, suffixText!, suffixUrl)])]
|
||||
}
|
||||
|
||||
private func makeLabelWithCheckbox(_ labelText: String, _ rawName: String) -> NSStackView {
|
||||
private func makeLabelWithCheckbox(_ labelText: String, _ rawName: String) -> [NSView] {
|
||||
let checkbox = NSButton.init(checkboxWithTitle: "", target: nil, action: nil)
|
||||
setControlValue(checkbox, Preferences.rawValues[rawName]!)
|
||||
return makeLabelWithProvidedControl(labelText, rawName, checkbox)
|
||||
}
|
||||
|
||||
private func makeLabelWithDropdown(_ labelText: String, _ rawName: String, _ values: [String], _ suffixText: String? = nil) -> NSStackView {
|
||||
private func makeLabelWithDropdown(_ labelText: String, _ rawName: String, _ values: [String], _ suffixText: String? = nil) -> [NSView] {
|
||||
let popUp = NSPopUpButton()
|
||||
popUp.addItems(withTitles: values)
|
||||
popUp.selectItem(withTitle: Preferences.rawValues[rawName]!)
|
||||
|
||||
return makeLabelWithProvidedControl(labelText, rawName, popUp, suffixText)
|
||||
}
|
||||
|
||||
private func makeLabelWithSlider(_ labelText: String, _ rawName: String, _ minValue: Double, _ maxValue: Double, _ numberOfTickMarks: Int, _ allowsTickMarkValuesOnly: Bool, _ unitText: String = "") -> NSStackView {
|
||||
private func makeLabelWithSlider(_ labelText: String, _ rawName: String, _ minValue: Double, _ maxValue: Double, _ numberOfTickMarks: Int, _ allowsTickMarkValuesOnly: Bool, _ unitText: String = "") -> [NSView] {
|
||||
let value = Preferences.rawValues[rawName]!
|
||||
let suffixText = value + unitText
|
||||
let suffixText = value + " " + unitText
|
||||
let slider = NSSlider()
|
||||
slider.minValue = minValue
|
||||
slider.maxValue = maxValue
|
||||
slider.stringValue = value
|
||||
slider.numberOfTickMarks = numberOfTickMarks
|
||||
slider.allowsTickMarkValuesOnly = allowsTickMarkValuesOnly
|
||||
slider.tickMarkPosition = .below
|
||||
// slider.numberOfTickMarks = numberOfTickMarks
|
||||
// slider.allowsTickMarkValuesOnly = allowsTickMarkValuesOnly
|
||||
// slider.tickMarkPosition = .below
|
||||
slider.isContinuous = true
|
||||
|
||||
return makeLabelWithProvidedControl(labelText, rawName, slider, suffixText, 60)
|
||||
return makeLabelWithProvidedControl(labelText, rawName, slider, suffixText)
|
||||
}
|
||||
|
||||
private func makeLabelWithProvidedControl(_ labelText: String?, _ rawName: String, _ control: NSControl, _ suffixText: String? = nil, _ suffixWidth: CGFloat? = nil, _ suffixUrl: String? = nil) -> NSStackView {
|
||||
let label = NSTextField(wrappingLabelWithString: (labelText != nil ? labelText! + ": " : ""))
|
||||
label.alignment = .right
|
||||
label.widthAnchor.constraint(equalToConstant: labelWidth).isActive = true
|
||||
label.identifier = NSUserInterfaceItemIdentifier(rawName + ControlIdentifierDiscriminator.LABEL.rawValue)
|
||||
label.isSelectable = false
|
||||
|
||||
private func makeLabelWithProvidedControl(_ labelText: String?, _ rawName: String, _ control: NSControl, _ suffixText: String? = nil, _ suffixUrl: String? = nil) -> [NSView] {
|
||||
let label = makeLabel(labelText, rawName)
|
||||
control.identifier = NSUserInterfaceItemIdentifier(rawName)
|
||||
control.target = self
|
||||
control.action = #selector(controlWasChanged)
|
||||
let containerView = NSStackView(views: [label, control])
|
||||
|
||||
if suffixText != nil {
|
||||
let suffix = makeSuffix(rawName, suffixText!, suffixWidth, suffixUrl)
|
||||
containerView.addView(suffix, in: .leading)
|
||||
}
|
||||
|
||||
return containerView
|
||||
return [label, control, suffixText != nil ? makeSuffix(rawName, suffixText!, suffixUrl) : NSView()]
|
||||
}
|
||||
|
||||
private func makeSuffix(_ controlName: String, _ text: String, _ width: CGFloat? = nil, _ url: String? = nil) -> NSTextField {
|
||||
private func makeLabel(_ labelText: String?, _ rawName: String) -> NSTextField {
|
||||
let label = NSTextField(wrappingLabelWithString: labelText != nil ? labelText! + ": " : "")
|
||||
label.widthAnchor.constraint(equalToConstant: label.fittingSize.width).isActive = true
|
||||
label.heightAnchor.constraint(equalToConstant: label.fittingSize.height).isActive = true
|
||||
label.alignment = .right
|
||||
label.identifier = NSUserInterfaceItemIdentifier(rawName + ControlIdentifierDiscriminator.LABEL.rawValue)
|
||||
return label
|
||||
}
|
||||
|
||||
private func makeSuffix(_ controlName: String, _ text: String, _ url: String? = nil) -> NSTextField {
|
||||
let suffix: NSTextField
|
||||
if url == nil {
|
||||
suffix = NSTextField(labelWithString: text)
|
||||
@@ -172,10 +183,8 @@ class PreferencesWindow: NSWindow, NSWindowDelegate {
|
||||
}
|
||||
suffix.textColor = .gray
|
||||
suffix.identifier = NSUserInterfaceItemIdentifier(controlName + ControlIdentifierDiscriminator.SUFFIX.rawValue)
|
||||
if width != nil {
|
||||
suffix.widthAnchor.constraint(equalToConstant: width!).isActive = true
|
||||
}
|
||||
|
||||
suffix.widthAnchor.constraint(equalToConstant: suffix.fittingSize.width).isActive = true
|
||||
suffix.heightAnchor.constraint(equalToConstant: suffix.fittingSize.height).isActive = true
|
||||
return suffix
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class StatusItem {
|
||||
action: #selector(app.showPreferencesPanel),
|
||||
keyEquivalent: ",")
|
||||
item.menu!.addItem(
|
||||
withTitle: "Quit \(App.name) #VERSION#",
|
||||
withTitle: "Quit",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
keyEquivalent: "q")
|
||||
return item
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
class TabViewController: NSTabViewController {
|
||||
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
|
||||
super.tabView(tabView, didSelect: tabViewItem)
|
||||
guard let tabViewItem = tabViewItem, let window = view.window else { return }
|
||||
window.title = tabViewItem.label
|
||||
resizeWindowToFit(tabViewItem, window)
|
||||
}
|
||||
|
||||
private func resizeWindowToFit(_ tabViewItem: NSTabViewItem, _ window: NSWindow) {
|
||||
let contentFrame = window.frameRect(forContentRect: NSRect(origin: .zero, size: tabViewItem.view!.frame.size))
|
||||
let toolbarHeight = window.frame.size.height - contentFrame.size.height
|
||||
let newOrigin = NSPoint(x: window.frame.origin.x, y: window.frame.origin.y + toolbarHeight)
|
||||
let newFrame = NSRect(origin: newOrigin, size: contentFrame.size)
|
||||
window.setFrame(newFrame, display: false, animate: true)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ class TextField: NSTextField, NSTextFieldDelegate {
|
||||
|
||||
public convenience init(_ value: String) {
|
||||
self.init(string: value)
|
||||
usesSingleLineMode = true
|
||||
font = .labelFont(ofSize: NSFont.systemFontSize)
|
||||
wantsLayer = true
|
||||
layer?.borderWidth = 1
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ set -exu
|
||||
|
||||
version="$(cat VERSION.txt)"
|
||||
# set the version for the app menubar menu text
|
||||
sed -i '' -e "s/#VERSION#/$version/" alt-tab-macos/ui/StatusItem.swift
|
||||
sed -i '' -e "s/#VERSION#/$version/" alt-tab-macos/ui/PreferencesWindow.swift
|
||||
# set the version in the app meta-data for the AppStore and app "Get Info" panel
|
||||
sed -i '' -e "s/#VERSION#/$version/" alt-tab-macos/Info.plist
|
||||
Reference in new issue
Block a user