diff --git a/src/logic/Preferences.swift b/src/logic/Preferences.swift index 3fff2da5..8ae73eeb 100644 --- a/src/logic/Preferences.swift +++ b/src/logic/Preferences.swift @@ -11,6 +11,7 @@ class Preferences { "fontHeight": "15", "holdShortcut": "⌥", "nextWindowShortcut": "⇥", + "nextWindowShortcut2": "`", "previousWindowShortcut": "⇧", "cancelShortcut": "⎋", "closeWindowShortcut": "W", @@ -20,8 +21,11 @@ class Preferences { "arrowKeysEnabled": "true", "mouseHoverEnabled": "true", "showFullscreenWindows": "true", + "showFullscreenWindows2": "true", "showMinimizedWindows": "true", + "showMinimizedWindows2": "true", "showHiddenWindows": "true", + "showHiddenWindows2": "true", "showTabsAsWindows": "false", "windowDisplayDelay": "0", "theme": "0", @@ -29,8 +33,11 @@ class Preferences { "titleTruncation": "0", "alignThumbnails": "0", "appsToShow": "0", + "appsToShow2": "1", "spacesToShow": "0", + "spacesToShow2": "0", "screensToShow": "0", + "screensToShow2": "0", "fadeOutAnimation": "false", "hideSpaceNumberLabels": "false", "startAtLogin": "true", @@ -57,7 +64,7 @@ class Preferences { static var iconSize: CGFloat { defaults.cgfloat("iconSize") } static var fontHeight: CGFloat { defaults.cgfloat("fontHeight") } static var holdShortcut: String { defaults.string("holdShortcut") } - static var nextWindowShortcut: String { defaults.string("nextWindowShortcut") } + static var nextWindowShortcut: [String] { ["nextWindowShortcut", "nextWindowShortcut2"].map { defaults.string($0) } } static var previousWindowShortcut: String { defaults.string("previousWindowShortcut") } static var cancelShortcut: String { defaults.string("cancelShortcut") } static var closeWindowShortcut: String { defaults.string("closeWindowShortcut") } @@ -66,9 +73,9 @@ class Preferences { static var hideShowAppShortcut: String { defaults.string("hideShowAppShortcut") } static var arrowKeysEnabled: Bool { defaults.bool("arrowKeysEnabled") } static var mouseHoverEnabled: Bool { defaults.bool("mouseHoverEnabled") } - static var showFullscreenWindows: Bool { defaults.bool("showFullscreenWindows") } - static var showMinimizedWindows: Bool { defaults.bool("showMinimizedWindows") } - static var showHiddenWindows: Bool { defaults.bool("showHiddenWindows") } + static var showFullscreenWindows: [Bool] { ["showFullscreenWindows", "showFullscreenWindows2"].map { defaults.bool($0) } } + static var showMinimizedWindows: [Bool] { ["showMinimizedWindows", "showMinimizedWindows2"].map { defaults.bool($0) } } + static var showHiddenWindows: [Bool] { ["showHiddenWindows", "showHiddenWindows2"].map { defaults.bool($0) } } static var showTabsAsWindows: Bool { defaults.bool("showTabsAsWindows") } static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(defaults.int("windowDisplayDelay")) } static var fadeOutAnimation: Bool { defaults.bool("fadeOutAnimation") } @@ -84,9 +91,9 @@ class Preferences { static var showOnScreen: ShowOnScreenPreference { defaults.macroPref("showOnScreen", ShowOnScreenPreference.allCases) } static var titleTruncation: TitleTruncationPreference { defaults.macroPref("titleTruncation", TitleTruncationPreference.allCases) } static var alignThumbnails: AlignThumbnailsPreference { defaults.macroPref("alignThumbnails", AlignThumbnailsPreference.allCases) } - static var appsToShow: AppsToShowPreference { defaults.macroPref("appsToShow", AppsToShowPreference.allCases) } - static var spacesToShow: SpacesToShowPreference { defaults.macroPref("spacesToShow", SpacesToShowPreference.allCases) } - static var screensToShow: ScreensToShowPreference { defaults.macroPref("screensToShow", ScreensToShowPreference.allCases) } + static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2"].map { defaults.macroPref($0, AppsToShowPreference.allCases) } } + static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2"].map { defaults.macroPref($0, SpacesToShowPreference.allCases) } } + static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2"].map { defaults.macroPref($0, ScreensToShowPreference.allCases) } } // derived values static var cellBorderWidth: CGFloat { theme.themeParameters.cellBorderWidth } diff --git a/src/logic/Windows.swift b/src/logic/Windows.swift index e873e4fd..19414080 100644 --- a/src/logic/Windows.swift +++ b/src/logic/Windows.swift @@ -96,12 +96,12 @@ class Windows { static func refreshIfWindowShouldBeShownToTheUser(_ window: Window, _ screen: NSScreen) { window.shouldShowTheUser = - !(!Preferences.showFullscreenWindows && window.isFullscreen) && - !(!Preferences.showMinimizedWindows && window.isMinimized) && - !(!Preferences.showHiddenWindows && window.isHidden) && - !(Preferences.appsToShow == .active && window.application.runningApplication != NSWorkspace.shared.frontmostApplication) && - !(Preferences.spacesToShow == .active && window.spaceId != Spaces.currentSpaceId) && - !(Preferences.screensToShow == .showingAltTab && !isOnScreen(window, screen)) && + !(!Preferences.showFullscreenWindows[App.app.shortcutIndex] && window.isFullscreen) && + !(!Preferences.showMinimizedWindows[App.app.shortcutIndex] && window.isMinimized) && + !(!Preferences.showHiddenWindows[App.app.shortcutIndex] && window.isHidden) && + !(Preferences.appsToShow[App.app.shortcutIndex] == .active && window.application.runningApplication != NSWorkspace.shared.frontmostApplication) && + !(Preferences.spacesToShow[App.app.shortcutIndex] == .active && window.spaceId != Spaces.currentSpaceId) && + !(Preferences.screensToShow[App.app.shortcutIndex] == .showingAltTab && !isOnScreen(window, screen)) && (Preferences.showTabsAsWindows || !window.isTabbed) && !Preferences.dontShowBlacklist.contains(window.application.runningApplication.bundleIdentifier ?? "") } diff --git a/src/ui/App.swift b/src/ui/App.swift index 7ea018f4..dfe74e48 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -21,6 +21,7 @@ class App: NSApplication, NSApplicationDelegate { var isFirstSummon = true var appIsBeingUsed = false var shortcutsShouldBeDisabled = false + var shortcutIndex = 0 override init() { super.init() @@ -33,6 +34,9 @@ class App: NSApplication, NSApplicationDelegate { } func applicationDidFinishLaunching(_ aNotification: Notification) { + #if DEBUG + UserDefaults.standard.set(true, forKey: "NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints") + #endif #if !DEBUG PFMoveToApplicationsFolderIfNecessary() #endif @@ -53,6 +57,9 @@ class App: NSApplication, NSApplicationDelegate { // TODO: undeterministic; events in the queue may still be processing; good enough for now DispatchQueue.main.async { () -> () in Windows.sortByLevel() } self.preloadWindows() + #if DEBUG + self.preferencesWindowController.show() + #endif } } @@ -164,7 +171,7 @@ class App: NSApplication, NSApplicationDelegate { @objc func showUi() { appIsBeingUsed = true - DispatchQueue.main.async { () -> () in self.showUiOrCycleSelection() } + DispatchQueue.main.async { () -> () in self.showUiOrCycleSelection(0) } } func cycleSelection(_ direction: Direction) { @@ -215,7 +222,7 @@ class App: NSApplication, NSApplicationDelegate { } } - func showUiOrCycleSelection() { + func showUiOrCycleSelection(_ shortcutIndex: Int) { debugPrint("showUiOrCycleSelection") if isFirstSummon { debugPrint("showUiOrCycleSelection: isFirstSummon") @@ -231,6 +238,7 @@ class App: NSApplication, NSApplicationDelegate { Spaces.idsAndIndexes = Spaces.allIdsAndIndexes() Windows.updateSpaces() let screen = Screen.preferred() + self.shortcutIndex = shortcutIndex Windows.refreshWhichWindowsToShowTheUser(screen) if (!Windows.list.contains { $0.shouldShowTheUser }) { hideUi(); return } Windows.updateFocusedWindowIndex(0) diff --git a/src/ui/generic-components/CustomRecorderControl.swift b/src/ui/generic-components/CustomRecorderControl.swift index 13678b5d..8f5b9fa9 100644 --- a/src/ui/generic-components/CustomRecorderControl.swift +++ b/src/ui/generic-components/CustomRecorderControl.swift @@ -6,6 +6,7 @@ class CustomRecorderControl: RecorderControl, RecorderControlDelegate { convenience init(_ shortcutString: String, _ clearable: Bool) { self.init(frame: .zero) + translatesAutoresizingMaskIntoConstraints = false self.clearable = clearable delegate = self allowsEscapeToCancelRecording = false diff --git a/src/ui/generic-components/StackView.swift b/src/ui/generic-components/StackView.swift index 607dc640..96f71a58 100644 --- a/src/ui/generic-components/StackView.swift +++ b/src/ui/generic-components/StackView.swift @@ -1,15 +1,18 @@ import Cocoa class StackView: NSStackView { - convenience init(_ views: [NSView], _ orientation: NSUserInterfaceLayoutOrientation = .horizontal, top: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0) { + convenience init(_ views: [NSView], _ orientation: NSUserInterfaceLayoutOrientation = .horizontal, _ shouldFit: Bool = true, top: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0) { self.init(views: views) + translatesAutoresizingMaskIntoConstraints = false edgeInsets = NSEdgeInsets(top: top, left: left, bottom: bottom, right: right) alignment = orientation == .horizontal ? .firstBaseline : .leading - // workaround: for some reason, horizontal stackviews with a RecorderControl have extra fittingSize.height - if orientation == .horizontal && (views.contains { $0 is CustomRecorderControl }) { - fit(fittingSize.width, fittingSize.height - 7) - } else { - fit() + if shouldFit { + // workaround: for some reason, horizontal stackviews with a RecorderControl have extra fittingSize.height + if orientation == .horizontal && (views.contains { $0 is CustomRecorderControl }) { + fit(fittingSize.width, fittingSize.height - 7) + } else { + fit() + } } } } diff --git a/src/ui/generic-components/text/TextField.swift b/src/ui/generic-components/text/TextField.swift index 5908ab9c..10869701 100644 --- a/src/ui/generic-components/text/TextField.swift +++ b/src/ui/generic-components/text/TextField.swift @@ -1,10 +1,18 @@ import Cocoa class TextField: NSTextField { + // NSTextField has 2px insets left and right by default; we remove those let insets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + override var alignmentRectInsets: NSEdgeInsets { insets } + + convenience init(_ labelWithString: String) { + self.init(labelWithString: labelWithString) + translatesAutoresizingMaskIntoConstraints = false + } convenience init(_ attributedString: NSAttributedString) { self.init(labelWithAttributedString: TextField.forceLeftToRight(attributedString)) + translatesAutoresizingMaskIntoConstraints = false } // we know the content to display should be left-to-right, so we force it to avoid displayed it right-to-left @@ -16,7 +24,4 @@ class TextField: NSTextField { forced.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: forced.length)) return forced } - - // NSTextField has 2px insets left and right by default; we remove those - override var alignmentRectInsets: NSEdgeInsets { insets } } diff --git a/src/ui/preferences-window/LabelAndControl.swift b/src/ui/preferences-window/LabelAndControl.swift index f3f1cd75..39be1c56 100644 --- a/src/ui/preferences-window/LabelAndControl.swift +++ b/src/ui/preferences-window/LabelAndControl.swift @@ -17,6 +17,7 @@ class LabelAndControl: NSObject { static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String, extraAction: ActionClosure? = nil, labelPosition: LabelPosition = .leftWithSeparator) -> [NSView] { let checkbox = NSButton(checkboxWithTitle: labelPosition == .right ? labelText : "", target: nil, action: nil) + checkbox.translatesAutoresizingMaskIntoConstraints = false checkbox.state = (Preferences.getString(rawName)! as NSString).boolValue ? .on : .off let views = makeLabelWithProvidedControl(labelText, rawName, checkbox, labelPosition: labelPosition, extraAction: extraAction) return views @@ -39,6 +40,7 @@ class LabelAndControl: NSObject { static func makeDropDown(_ rawName: String, _ macroPreferences: [MacroPreference]) -> NSPopUpButton { let popUp = NSPopUpButton() + popUp.translatesAutoresizingMaskIntoConstraints = false popUp.addItems(withTitles: macroPreferences.map { $0.localizedString }) popUp.selectItem(at: Int(Preferences.getString(rawName)!)!) return popUp @@ -97,7 +99,7 @@ class LabelAndControl: NSObject { } static func makeLabel(_ labelText: String, _ labelPosition: LabelPosition = .leftWithoutSeparator) -> NSTextField { - let label = NSTextField(wrappingLabelWithString: labelText) + let label = TextField(labelText) label.isSelectable = false label.usesSingleLineMode = true label.alignment = .right @@ -150,3 +152,14 @@ class LabelAndControl: NSObject { enum ControlIdentifierDiscriminator: String { case SUFFIX = "_suffix" } + +class TabView: NSTabView { + // without the left/right value, the whole view is shifted to the right for some reason +// let insets = NSEdgeInsets(top: 0, left: 7, bottom: 0, right: 7) +// override var alignmentRectInsets: NSEdgeInsets { get { insets } } + + convenience init() { + self.init(frame: .zero) + translatesAutoresizingMaskIntoConstraints = false + } +} diff --git a/src/ui/preferences-window/tabs/GeneralTab.swift b/src/ui/preferences-window/tabs/GeneralTab.swift index 124eb6bc..c3c17cfb 100644 --- a/src/ui/preferences-window/tabs/GeneralTab.swift +++ b/src/ui/preferences-window/tabs/GeneralTab.swift @@ -11,7 +11,8 @@ class GeneralTab: NSViewController, PreferencePane { static var shortcutsDependentOnHoldShortcut = [NSControl]() static var shortcutsActionsBlocks = [ "holdShortcut": { App.app.focusTarget() }, - "nextWindowShortcut": { App.app.showUiOrCycleSelection() }, + "nextWindowShortcut": { App.app.showUiOrCycleSelection(0) }, + "nextWindowShortcut2": { App.app.showUiOrCycleSelection(1) }, "previousWindowShortcut": { App.app.cycleSelection(.trailing) }, "→": { App.app.cycleSelection(.right) }, "←": { App.app.cycleSelection(.left) }, @@ -29,7 +30,6 @@ class GeneralTab: NSViewController, PreferencePane { let hideMenubarIcon = LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide menubar icon:", comment: ""), "hideMenubarIcon", extraAction: hideMenubarIconCallback) var holdShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Hold", comment: ""), "holdShortcut", Preferences.holdShortcut, false, labelPosition: .leftWithoutSeparator) holdShortcut.append(LabelAndControl.makeLabel(NSLocalizedString("and press:", comment: ""))) - let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), "nextWindowShortcut", Preferences.nextWindowShortcut, labelPosition: .right) let previousWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select previous window", comment: ""), "previousWindowShortcut", Preferences.previousWindowShortcut, labelPosition: .right) let cancelShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Cancel and hide", comment: ""), "cancelShortcut", Preferences.cancelShortcut, labelPosition: .right) let closeWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Close window", comment: ""), "closeWindowShortcut", Preferences.closeWindowShortcut, labelPosition: .right) @@ -41,36 +41,46 @@ class GeneralTab: NSViewController, PreferencePane { let holdAndPress = StackView(holdShortcut) let checkboxesExplanations = LabelAndControl.makeLabel(NSLocalizedString("Select windows using:", comment: "")) let checkboxes = StackView([StackView(enableArrows), StackView(enableMouse)], .vertical) - let appsToShow = dropdown("appsToShow", AppsToShowPreference.allCases) - let spacesToShow = dropdown("spacesToShow", SpacesToShowPreference.allCases) - let screensToShow = dropdown("screensToShow", ScreensToShowPreference.allCases) - let showFullscreenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Fullscreen", comment: ""), "showFullscreenWindows", labelPosition: .right)) - let showMinimizedWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Minimized", comment: ""), "showMinimizedWindows", labelPosition: .right)) - let showHiddenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hidden", comment: ""), "showHiddenWindows", labelPosition: .right)) let shortcuts = StackView([previousWindowShortcut, cancelShortcut, closeWindowShortcut, minDeminWindowShortcut, quitAppShortcut, hideShowAppShortcut].map { (view: [NSView]) in StackView(view) }, .vertical) let thenRelease = LabelAndControl.makeLabel(NSLocalizedString("then release:", comment: "")) let orPress = LabelAndControl.makeLabel(NSLocalizedString("or press:", comment: "")) let focusSelectedWindow = LabelAndControl.makeLabel(NSLocalizedString("Focus selected window", comment: "")) - let toShowDropdowns = StackView([appsToShow, spacesToShow, screensToShow], .vertical) - let toShowCheckboxes = StackView([showMinimizedWindows, showHiddenWindows, showFullscreenWindows], .vertical) let toShowExplanations = LabelAndControl.makeLabel(NSLocalizedString("Show the following windows:", comment: "")) - let toShow = StackView([toShowDropdowns, toShowCheckboxes], .vertical) + let tab1 = NSTabViewItem(identifier: 1) + tab1.label = "1" + let (nextWindowShortcut, tab1View) = toShowSection("") + tab1.view = tab1View + let tab2 = NSTabViewItem(identifier: 2) + tab2.label = "2" + let (nextWindowShortcut2, tab2View) = toShowSection("2") + tab2.view = tab2View + let tabView = TabView() + tabView.addTabViewItem(tab1) + tabView.addTabViewItem(tab2) + tabView.tabPosition = .right + tabView.heightAnchor.constraint(equalToConstant: tabView.fittingSize.height - 8).isActive = true + + let vertical1 = StackView([toShowExplanations, holdAndPress], .vertical, false, top: 5, bottom: 3) + vertical1.alignment = .trailing + vertical1.distribution = .equalCentering + vertical1.heightAnchor.constraint(equalToConstant: tabView.fittingSize.height - 14).isActive = true let grid = GridView([ startAtLogin, hideMenubarIcon, - [holdAndPress, StackView(nextWindowShortcut)], + [vertical1, tabView], [thenRelease, focusSelectedWindow], [orPress, shortcuts], [checkboxesExplanations, checkboxes], - [toShowExplanations, toShow], ]) grid.column(at: 0).xPlacement = .trailing - [2, 5, 6].forEach { grid.row(at: $0).topPadding = GridView.interPadding } + grid.row(at: 2).rowAlignment = .none + [2, 5].forEach { grid.row(at: $0).topPadding = GridView.interPadding } grid.fit() GeneralTab.shortcutsDependentOnHoldShortcut.append(contentsOf: [enableArrows[0] as! NSControl] + - [nextWindowShortcut, previousWindowShortcut, cancelShortcut, closeWindowShortcut, minDeminWindowShortcut, quitAppShortcut, hideShowAppShortcut].map { $0[0] as! NSControl }) + [nextWindowShortcut, nextWindowShortcut2, previousWindowShortcut, cancelShortcut, + closeWindowShortcut, minDeminWindowShortcut, quitAppShortcut, hideShowAppShortcut].map { $0[0] as! NSControl }) GeneralTab.arrowKeysEnabledCallback(enableArrows[0] as! NSControl) startAtLoginCallback(startAtLogin[1] as! NSControl) hideMenubarIconCallback(hideMenubarIcon[1] as! NSControl) @@ -78,6 +88,23 @@ class GeneralTab: NSViewController, PreferencePane { view = grid } + private func toShowSection(_ postfix: String) -> ([NSView], NSView) { + let appsToShow = dropdown("appsToShow" + postfix, AppsToShowPreference.allCases) + let spacesToShow = dropdown("spacesToShow" + postfix, SpacesToShowPreference.allCases) + let screensToShow = dropdown("screensToShow" + postfix, ScreensToShowPreference.allCases) + let showMinimizedWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Minimized", comment: ""), "showMinimizedWindows" + postfix, labelPosition: .right)) + let showHiddenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hidden", comment: ""), "showHiddenWindows" + postfix, labelPosition: .right)) + let showFullscreenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Fullscreen", comment: ""), "showFullscreenWindows" + postfix, labelPosition: .right)) + let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), "nextWindowShortcut" + postfix, Preferences.nextWindowShortcut[postfix == "" ? 0 : 1], labelPosition: .right) + let toShowDropdowns = StackView([appsToShow, spacesToShow, screensToShow, showMinimizedWindows, showHiddenWindows, showFullscreenWindows], .vertical, false) + toShowDropdowns.spacing = 8 + toShowDropdowns.fit() + let tab = StackView([toShowDropdowns, StackView(nextWindowShortcut)], .vertical, false) + tab.spacing = GridView.padding + tab.fit() + return (nextWindowShortcut, tab) + } + private func dropdown(_ rawName: String, _ macroPreferences: [MacroPreference]) -> NSControl { let dropdown = LabelAndControl.makeDropDown(rawName, macroPreferences) return LabelAndControl.setupControl(dropdown, rawName) @@ -86,7 +113,7 @@ class GeneralTab: NSViewController, PreferencePane { private static func addShortcut(_ type: KeyEventType, _ shortcut: Shortcut, _ controlId: String) { removeShortcutIfExists(controlId, type) // remove the previous shortcut shortcutActions[controlId] = ShortcutAction(shortcut: shortcut, actionHandler: { _ in - let isShortcutInitiatingTheApp = controlId == "nextWindowShortcut" + let isShortcutInitiatingTheApp = ["nextWindowShortcut", "nextWindowShortcut2"].contains(controlId) if isShortcutInitiatingTheApp { App.app.appIsBeingUsed = true } @@ -158,7 +185,7 @@ class GeneralTab: NSViewController, PreferencePane { let _ = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst.takeRetainedValue(), nil, nil, itemUrl, nil, nil).takeRetainedValue() } } - + private func hideMenubarIconCallback(_ sender: NSControl) { App.statusItem.isVisible = (sender as! NSButton).state == .off }