diff --git a/BifcodePackage/Sources/BifcodeFeature/ContentView.swift b/BifcodePackage/Sources/BifcodeFeature/ContentView.swift index 24e6a78..5e6a465 100644 --- a/BifcodePackage/Sources/BifcodeFeature/ContentView.swift +++ b/BifcodePackage/Sources/BifcodeFeature/ContentView.swift @@ -112,6 +112,11 @@ public struct ContentView: View { ThemeMode(rawValue: themeModeRaw) ?? .dark } + /// The effective theme mode, resolving system mode to dark or light. + private var effectiveThemeMode: ThemeMode { + themeMode.effectiveMode + } + public init() {} /// Check if both code panels are empty (export should be disabled) @@ -222,7 +227,7 @@ public struct ContentView: View { showTitle: showTitle, fontSize: fontSize, theme: selectedTheme.editorTheme, - themeMode: themeMode, + themeMode: effectiveThemeMode, panelWidth: panelWidth, doPanelHeight: doPanelHeight, dontPanelHeight: dontPanelHeight diff --git a/BifcodePackage/Sources/BifcodeFeature/Models/SettingsTypes.swift b/BifcodePackage/Sources/BifcodeFeature/Models/SettingsTypes.swift index 1ce1ca0..1e6e9f6 100644 --- a/BifcodePackage/Sources/BifcodeFeature/Models/SettingsTypes.swift +++ b/BifcodePackage/Sources/BifcodeFeature/Models/SettingsTypes.swift @@ -264,9 +264,16 @@ public enum WindowLayout: String, CaseIterable, Sendable { /// /// ### Modes /// +/// - ``system`` /// - ``dark`` /// - ``light`` public enum ThemeMode: String, CaseIterable, Sendable { + /// Follows the macOS system appearance. + /// + /// Automatically switches between dark and light themes based on + /// the user's macOS appearance setting. + case system + /// Dark mode themes with dark backgrounds. /// /// Ideal for low-light environments and reducing eye strain. @@ -280,10 +287,26 @@ public enum ThemeMode: String, CaseIterable, Sendable { /// A human-readable label for display in pickers. public var label: String { switch self { + case .system: "System" case .dark: "Dark" case .light: "Light" } } + + /// Returns the effective mode based on the current system appearance. + /// + /// For `.system` mode, this resolves to either `.dark` or `.light` + /// based on the current macOS appearance setting. + /// + /// - Returns: The effective theme mode (always `.dark` or `.light`). + public var effectiveMode: ThemeMode { + switch self { + case .system: + NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? .dark : .light + case .dark, .light: + self + } + } } // MARK: - Editor Themes diff --git a/BifcodePackage/Sources/BifcodeFeature/Views/CodeEditorView.swift b/BifcodePackage/Sources/BifcodeFeature/Views/CodeEditorView.swift index 092783e..751eedc 100644 --- a/BifcodePackage/Sources/BifcodeFeature/Views/CodeEditorView.swift +++ b/BifcodePackage/Sources/BifcodeFeature/Views/CodeEditorView.swift @@ -94,6 +94,11 @@ public struct CodeEditorView: View { ThemeMode(rawValue: themeModeRaw) ?? .dark } + /// The effective theme mode, resolving system mode to dark or light. + private var effectiveThemeMode: ThemeMode { + themeMode.effectiveMode + } + /// Creates a new code editor view for the specified panel. /// /// - Parameters: @@ -129,7 +134,7 @@ public struct CodeEditorView: View { .clipShape(RoundedRectangle(cornerRadius: 12)) .overlay( RoundedRectangle(cornerRadius: 12) - .stroke(Color.windowBorder(for: themeMode), lineWidth: 1) + .stroke(Color.windowBorder(for: effectiveThemeMode), lineWidth: 1) ) } @@ -155,13 +160,13 @@ public struct CodeEditorView: View { HStack(spacing: 14) { // Traffic light placeholder Circle() - .fill(Color.editorCloseButton(for: themeMode)) + .fill(Color.editorCloseButton(for: effectiveThemeMode)) .frame(width: 14, height: 14) // Title - single line with truncation Label(panel.title, systemImage: "text.document.fill") .font(.system(size: 13, weight: .medium)) - .foregroundStyle(Color.titleText(for: themeMode)) + .foregroundStyle(Color.titleText(for: effectiveThemeMode)) .lineLimit(1) .truncationMode(.tail) @@ -170,7 +175,7 @@ public struct CodeEditorView: View { .padding(.horizontal, 12) .padding(.vertical, 10) .frame(maxWidth: .infinity) - .background(Color.titleBarBackground(for: themeMode)) + .background(Color.titleBarBackground(for: effectiveThemeMode)) .clipShape(UnevenRoundedRectangle(topLeadingRadius: 12, topTrailingRadius: 12)) .accessibilityElement(children: .combine) .accessibilityLabel("\(panel.title) panel header") diff --git a/BifcodePackage/Sources/BifcodeFeature/Views/ExportPanelView.swift b/BifcodePackage/Sources/BifcodeFeature/Views/ExportPanelView.swift index 62b4766..21f43d1 100644 --- a/BifcodePackage/Sources/BifcodeFeature/Views/ExportPanelView.swift +++ b/BifcodePackage/Sources/BifcodeFeature/Views/ExportPanelView.swift @@ -66,6 +66,11 @@ struct ExportPanelView: View { /// The current theme mode for window styling. let themeMode: ThemeMode + /// The effective theme mode, resolving system mode to dark or light. + private var effectiveThemeMode: ThemeMode { + themeMode.effectiveMode + } + /// Local editor state (not shared with interactive editor). @State private var editorState = SourceEditorState() @@ -83,7 +88,7 @@ struct ExportPanelView: View { .clipShape(RoundedRectangle(cornerRadius: 12)) .overlay( RoundedRectangle(cornerRadius: 12) - .stroke(Color.windowBorder(for: themeMode), lineWidth: 1) + .stroke(Color.windowBorder(for: effectiveThemeMode), lineWidth: 1) ) } @@ -93,13 +98,13 @@ struct ExportPanelView: View { HStack(spacing: 14) { // Traffic light placeholder Circle() - .fill(Color.editorCloseButton(for: themeMode)) + .fill(Color.editorCloseButton(for: effectiveThemeMode)) .frame(width: 14, height: 14) // Title - single line with truncation Label(panel.title, systemImage: "text.document.fill") .font(.system(size: 13, weight: .medium)) - .foregroundStyle(Color.titleText(for: themeMode)) + .foregroundStyle(Color.titleText(for: effectiveThemeMode)) .lineLimit(1) .truncationMode(.tail) @@ -108,7 +113,7 @@ struct ExportPanelView: View { .padding(.horizontal, 12) .padding(.vertical, 10) .frame(maxWidth: .infinity) - .background(Color.titleBarBackground(for: themeMode)) + .background(Color.titleBarBackground(for: effectiveThemeMode)) .clipShape(UnevenRoundedRectangle(topLeadingRadius: 12, topTrailingRadius: 12)) .accessibilityElement(children: .combine) .accessibilityLabel("\(panel.title) panel header") diff --git a/BifcodePackage/Sources/BifcodeFeature/Views/ToolBarView.swift b/BifcodePackage/Sources/BifcodeFeature/Views/ToolBarView.swift index 1232ac2..254dfe2 100644 --- a/BifcodePackage/Sources/BifcodeFeature/Views/ToolBarView.swift +++ b/BifcodePackage/Sources/BifcodeFeature/Views/ToolBarView.swift @@ -309,13 +309,14 @@ public struct ToolBarView: View { private var themeModeToggle: some View { Picker("", selection: $themeModeRaw) { + Image(systemName: "circle.lefthalf.filled").tag(ThemeMode.system.rawValue) Image(systemName: "moon.fill").tag(ThemeMode.dark.rawValue) Image(systemName: "sun.max.fill").tag(ThemeMode.light.rawValue) } .pickerStyle(.segmented) .fixedSize() .accessibilityLabel("Theme Mode") - .accessibilityHint("Toggle dark or light mode") + .accessibilityHint("System follows macOS appearance, or choose dark or light mode") } // MARK: - Theme Picker