From b6528c69134d8088bf1e012cb37d2df68c46c715 Mon Sep 17 00:00:00 2001 From: Jordan Baird Date: Mon, 14 Oct 2024 10:08:21 -0600 Subject: [PATCH] Add ability to preview dynamic menu bar appearance --- .../MenuBarAppearanceEditor.swift | 70 +++++++++++++++++-- .../MenuBarAppearanceEditorPanel.swift | 0 .../Appearance/MenuBarAppearanceManager.swift | 3 + .../Appearance/MenuBarOverlayPanel.swift | 18 +++-- 4 files changed, 82 insertions(+), 9 deletions(-) rename Ice/MenuBar/Appearance/{ => MenuBarAppearanceEditor}/MenuBarAppearanceEditorPanel.swift (100%) diff --git a/Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditor.swift b/Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditor.swift index 32c3cf2..687e1b2 100644 --- a/Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditor.swift +++ b/Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditor.swift @@ -64,13 +64,25 @@ struct MenuBarAppearanceEditor: View { } if appearanceManager.configuration.isDynamic { VStack(alignment: .leading) { - Text("Light Appearance") - .font(.headline) + HStack { + Text("Light Appearance") + .font(.headline) + if case .dark = SystemAppearance.current { + PreviewButton(configuration: appearanceManager.configuration.lightModeConfiguration) + } + } + .frame(height: 16) MenuBarPartialAppearanceEditor(configuration: appearanceManager.bindings.configuration.lightModeConfiguration) } VStack(alignment: .leading) { - Text("Dark Appearance") - .font(.headline) + HStack { + Text("Dark Appearance") + .font(.headline) + if case .light = SystemAppearance.current { + PreviewButton(configuration: appearanceManager.configuration.darkModeConfiguration) + } + } + .frame(height: 16) MenuBarPartialAppearanceEditor(configuration: appearanceManager.bindings.configuration.darkModeConfiguration) } } else { @@ -137,7 +149,7 @@ struct MenuBarAppearanceEditor: View { } } -struct MenuBarPartialAppearanceEditor: View { +private struct MenuBarPartialAppearanceEditor: View { @Binding var configuration: MenuBarAppearancePartialConfiguration var body: some View { @@ -222,3 +234,51 @@ struct MenuBarPartialAppearanceEditor: View { } } } + +private struct PreviewButton: View { + @EnvironmentObject var appearanceManager: MenuBarAppearanceManager + + @State private var frame = CGRect.zero + @State private var isPressed = false + + let configuration: MenuBarAppearancePartialConfiguration + + var body: some View { + ZStack { + DummyButton(isPressed: $isPressed) + .allowsHitTesting(false) + Text("Hold to Preview") + .baselineOffset(1.5) + .padding(.horizontal, 10) + .contentShape(Rectangle()) + } + .fixedSize() + .simultaneousGesture( + DragGesture(minimumDistance: 0) + .onChanged { value in + isPressed = frame.contains(value.location) + } + .onEnded { _ in + isPressed = false + } + ) + .onChange(of: isPressed) { _, newValue in + appearanceManager.previewConfiguration = newValue ? configuration : nil + } + .onFrameChange(update: $frame) + } +} + +private struct DummyButton: NSViewRepresentable { + @Binding var isPressed: Bool + + func makeNSView(context: Context) -> NSButton { + let button = NSButton() + button.title = "" + return button + } + + func updateNSView(_ nsView: NSButton, context: Context) { + nsView.isHighlighted = isPressed + } +} diff --git a/Ice/MenuBar/Appearance/MenuBarAppearanceEditorPanel.swift b/Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditorPanel.swift similarity index 100% rename from Ice/MenuBar/Appearance/MenuBarAppearanceEditorPanel.swift rename to Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditorPanel.swift diff --git a/Ice/MenuBar/Appearance/MenuBarAppearanceManager.swift b/Ice/MenuBar/Appearance/MenuBarAppearanceManager.swift index bfcd9eb..74cda2f 100644 --- a/Ice/MenuBar/Appearance/MenuBarAppearanceManager.swift +++ b/Ice/MenuBar/Appearance/MenuBarAppearanceManager.swift @@ -12,6 +12,9 @@ final class MenuBarAppearanceManager: ObservableObject { /// The current menu bar appearance configuration. @Published var configuration: MenuBarAppearanceConfigurationV2 = .defaultConfiguration + /// The currently previewed partial configuration. + @Published var previewConfiguration: MenuBarAppearancePartialConfiguration? + /// The shared app state. private weak var appState: AppState? diff --git a/Ice/MenuBar/Appearance/MenuBarOverlayPanel.swift b/Ice/MenuBar/Appearance/MenuBarOverlayPanel.swift index 165cfc1..5b1776c 100644 --- a/Ice/MenuBar/Appearance/MenuBarOverlayPanel.swift +++ b/Ice/MenuBar/Appearance/MenuBarOverlayPanel.swift @@ -360,6 +360,8 @@ final class MenuBarOverlayPanel: NSPanel { private final class MenuBarOverlayPanelContentView: NSView { @Published private var fullConfiguration: MenuBarAppearanceConfigurationV2 = .defaultConfiguration + @Published private var previewConfiguration: MenuBarAppearancePartialConfiguration? + private var cancellables = Set() /// The overlay panel that contains the content view. @@ -367,6 +369,11 @@ private final class MenuBarOverlayPanelContentView: NSView { window as? MenuBarOverlayPanel } + /// The currently displayed configuration. + private var configuration: MenuBarAppearancePartialConfiguration { + previewConfiguration ?? fullConfiguration.current + } + override func viewDidMoveToWindow() { super.viewDidMoveToWindow() configureCancellables() @@ -381,6 +388,10 @@ private final class MenuBarOverlayPanelContentView: NSView { .removeDuplicates() .assign(to: &$fullConfiguration) + appState.appearanceManager.$previewConfiguration + .removeDuplicates() + .assign(to: &$previewConfiguration) + for section in appState.menuBarManager.sections { // Redraw whenever the window frame of a control item changes. // @@ -436,8 +447,9 @@ private final class MenuBarOverlayPanelContentView: NSView { .store(in: &c) } - // Redraw whenever the full configuration changes. - $fullConfiguration + // Redraw whenever the configurations change. + $fullConfiguration.mapToVoid() + .merge(with: $previewConfiguration.mapToVoid()) .sink { [weak self] _ in self?.needsDisplay = true } @@ -616,7 +628,6 @@ private final class MenuBarOverlayPanelContentView: NSView { /// Draws the tint defined by the given configuration in the given rectangle. private func drawTint(in rect: CGRect) { - let configuration = fullConfiguration.current switch configuration.tintKind { case .none: break @@ -641,7 +652,6 @@ private final class MenuBarOverlayPanelContentView: NSView { } let drawableBounds = getDrawableBounds() - let configuration = fullConfiguration.current let shapePath = switch fullConfiguration.shapeKind { case .none: