Skip to content

Commit

Permalink
feat: divide preferences by topic (closes #130)
Browse files Browse the repository at this point in the history
  • Loading branch information
louis.pontoise authored and lwouis committed Mar 10, 2020
1 parent d52eb6d commit 291f872
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 75 deletions.
4 changes: 4 additions & 0 deletions alt-tab-macos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand Down Expand Up @@ -257,6 +259,7 @@
F0298E42A818112B290FF6C7 /* TextField.swift */,
F0298708E2B13DBD4738AE76 /* HyperlinkLabel.swift */,
D04BAED53465957807CBF8B2 /* FontIcon.swift */,
D04BAE70A24C83D571C77B1A /* TabViewController.swift */,
);
path = ui;
sourceTree = "<group>";
Expand Down Expand Up @@ -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;
};
Expand Down
2 changes: 1 addition & 1 deletion alt-tab-macos/ui/Labels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class BaseLabel: NSTextView {

init(_ text: String) {
super.init(frame: .zero)
_init()
string = text
}

Expand All @@ -20,7 +21,6 @@ class BaseLabel: NSTextView {
backgroundColor = .clear
isSelectable = false
isEditable = false
font = Preferences.font
enabledTextCheckingTypes = 0
}
}
Expand Down
153 changes: 81 additions & 72 deletions alt-tab-macos/ui/PreferencesWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 {
Expand All @@ -77,93 +73,108 @@ 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),
makeLabelWithSlider("Max windows per row", "maxCellsPerRow", 1, 40, 20, true),
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
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")!)],
])
}

return view
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? = nil, _ suffixText: String? = nil, _ suffixUrl: String? = nil, _ validator: ((String) -> Bool)? = nil) -> NSStackView {
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 [label, control, suffixText != nil ? makeSuffix(rawName, suffixText!, suffixUrl) : NSView()]
}

return containerView
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, _ width: CGFloat? = nil, _ url: String? = nil) -> NSTextField {
private func makeSuffix(_ controlName: String, _ text: String, _ url: String? = nil) -> NSTextField {
let suffix: NSTextField
if url == nil {
suffix = NSTextField(labelWithString: text)
Expand All @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion alt-tab-macos/ui/StatusItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions alt-tab-macos/ui/TabViewController.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 2 additions & 0 deletions alt-tab-macos/ui/TextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion ci/set_version_in_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 291f872

Please sign in to comment.