From 6bded439495b39df984f0b4ad793a557fbcb6398 Mon Sep 17 00:00:00 2001 From: luw2007 Date: Fri, 11 Sep 2026 00:03:36 +0800 Subject: [PATCH 1/2] Add light/dark theme toggle and Raycast-style UI redesign Features: - Theme system with system/light/dark modes (ThemeManager + @AppStorage) - Dynamic color palette that adapts to appearance (all existing colors made dynamic via NSColor(name:dynamicProvider:)) - Theme picker in Settings > General > Appearance (segmented control) - Raycast-style menu bar panel: ultraThinMaterial background with tint - Raycast-style settings cards: 12px corner radius, 0.5px dynamic border, compact padding, SF Pro typography - Applies preferredColorScheme to all windows (menu bar, settings, table editor) All existing UI components automatically support both modes because the hardcoded colors in ScreenTextGrabTheme.swift are now dynamic. --- ScreenTextGrab/ScreenTextGrabApp.swift | 6 + ScreenTextGrab/Views/AppTheme.swift | 224 ++++++++++++++++++ ScreenTextGrab/Views/MenuBarView.swift | 32 +-- .../Views/ScreenTextGrabTheme.swift | 109 +++++++-- .../Settings/SettingsSharedComponents.swift | 14 +- .../Views/Settings/SettingsTabViews.swift | 19 ++ 6 files changed, 356 insertions(+), 48 deletions(-) create mode 100644 ScreenTextGrab/Views/AppTheme.swift diff --git a/ScreenTextGrab/ScreenTextGrabApp.swift b/ScreenTextGrab/ScreenTextGrabApp.swift index 678b798..56f8890 100644 --- a/ScreenTextGrab/ScreenTextGrabApp.swift +++ b/ScreenTextGrab/ScreenTextGrabApp.swift @@ -11,6 +11,8 @@ struct ScreenTextGrabApp: App { MenuBarExtra { MenuBarView() .environmentObject(appDelegate.appState) + .environmentObject(ThemeManager.shared) + .preferredColorScheme(ThemeManager.shared.effectiveColorScheme) } label: { MenuBarStatusIcon() } @@ -19,12 +21,16 @@ struct ScreenTextGrabApp: App { Window(L10n.pair("Ayarlar", "Settings"), id: Self.settingsWindowID) { SettingsView() .environmentObject(appDelegate.appState) + .environmentObject(ThemeManager.shared) + .preferredColorScheme(ThemeManager.shared.effectiveColorScheme) .frame(width: 620, height: 560) } Window(L10n.pair("Tablo Duzenleyici", "Table Editor"), id: Self.tableReviewWindowID) { TableReviewView() .environmentObject(appDelegate.appState) + .environmentObject(ThemeManager.shared) + .preferredColorScheme(ThemeManager.shared.effectiveColorScheme) .frame(minWidth: 860, idealWidth: 920, minHeight: 580, idealHeight: 640) } } diff --git a/ScreenTextGrab/Views/AppTheme.swift b/ScreenTextGrab/Views/AppTheme.swift new file mode 100644 index 0000000..7c14e22 --- /dev/null +++ b/ScreenTextGrab/Views/AppTheme.swift @@ -0,0 +1,224 @@ +import SwiftUI +import AppKit + +// MARK: - Theme Mode + +enum ThemeMode: String, CaseIterable, Identifiable, Sendable { + case system + case light + case dark + + var id: String { rawValue } + + var title: String { + switch self { + case .system: return L10n.pair("Sistem", "System") + case .light: return L10n.pair("Açık", "Light") + case .dark: return L10n.pair("Koyu", "Dark") + } + } + + var icon: String { + switch self { + case .system: return "circle.lefthalf.filled" + case .light: return "sun.max.fill" + case .dark: return "moon.fill" + } + } + + var colorScheme: ColorScheme? { + switch self { + case .system: return nil + case .light: return .light + case .dark: return .dark + } + } +} + +// MARK: - Theme Manager + +@MainActor +final class ThemeManager: ObservableObject { + static let shared = ThemeManager() + + @AppStorage("appearance.themeMode") private(set) var mode: ThemeMode = .system + + var effectiveColorScheme: ColorScheme? { + mode.colorScheme + } + + var isDark: Bool { + switch mode { + case .dark: return true + case .light: return false + case .system: + return NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + } + } + + func setMode(_ newMode: ThemeMode) { + mode = newMode + objectWillChange.send() + } +} + +// MARK: - Dynamic Colors (Raycast-inspired palette) + +extension Color { + // Background + static var rayBackground: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.11, green: 0.11, blue: 0.12, alpha: 1.0) + : NSColor(red: 0.96, green: 0.96, blue: 0.97, alpha: 1.0) + }) + } + + static var raySurface: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 1.0, alpha: 0.04) + : NSColor(white: 0.0, alpha: 0.03) + }) + } + + static var raySurfaceHover: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 1.0, alpha: 0.07) + : NSColor(white: 0.0, alpha: 0.05) + }) + } + + static var raySurfacePressed: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 1.0, alpha: 0.10) + : NSColor(white: 0.0, alpha: 0.07) + }) + } + + // Text + static var rayTextPrimary: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 0.98, alpha: 1.0) + : NSColor(white: 0.05, alpha: 1.0) + }) + } + + static var rayTextSecondary: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 0.65, alpha: 1.0) + : NSColor(white: 0.35, alpha: 1.0) + }) + } + + static var rayTextTertiary: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 0.45, alpha: 1.0) + : NSColor(white: 0.55, alpha: 1.0) + }) + } + + // Separators & borders + static var raySeparator: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 1.0, alpha: 0.08) + : NSColor(white: 0.0, alpha: 0.08) + }) + } + + static var rayBorder: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 1.0, alpha: 0.10) + : NSColor(white: 0.0, alpha: 0.10) + }) + } + + // Accent + static var rayAccent: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.40, green: 0.65, blue: 0.95, alpha: 1.0) + : NSColor(red: 0.15, green: 0.45, blue: 0.85, alpha: 1.0) + }) + } + + static var rayAccentSoft: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.40, green: 0.65, blue: 0.95, alpha: 0.15) + : NSColor(red: 0.15, green: 0.45, blue: 0.85, alpha: 0.10) + }) + } + + // Status colors + static var raySuccess: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.35, green: 0.78, blue: 0.50, alpha: 1.0) + : NSColor(red: 0.15, green: 0.60, blue: 0.35, alpha: 1.0) + }) + } + + static var rayWarning: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.90, green: 0.70, blue: 0.30, alpha: 1.0) + : NSColor(red: 0.75, green: 0.55, blue: 0.10, alpha: 1.0) + }) + } + + static var rayDanger: Color { + Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.90, green: 0.45, blue: 0.45, alpha: 1.0) + : NSColor(red: 0.80, green: 0.25, blue: 0.25, alpha: 1.0) + }) + } +} + +// MARK: - Raycast-style view modifiers + +struct RayCardStyle: ViewModifier { + var padding: CGFloat = 12 + var cornerRadius: CGFloat = 10 + + func body(content: Content) -> some View { + content + .padding(padding) + .background(Color.raySurface) + .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)) + .overlay( + RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) + .stroke(Color.rayBorder, lineWidth: 0.5) + ) + } +} + +struct RayRowStyle: ViewModifier { + var horizontalPadding: CGFloat = 10 + var verticalPadding: CGFloat = 8 + + func body(content: Content) -> some View { + content + .padding(.horizontal, horizontalPadding) + .padding(.vertical, verticalPadding) + .contentShape(Rectangle()) + } +} + +extension View { + func rayCard(padding: CGFloat = 12, cornerRadius: CGFloat = 10) -> some View { + modifier(RayCardStyle(padding: padding, cornerRadius: cornerRadius)) + } + + func rayRow(hPadding: CGFloat = 10, vPadding: CGFloat = 8) -> some View { + modifier(RayRowStyle(horizontalPadding: hPadding, verticalPadding: vPadding)) + } +} diff --git a/ScreenTextGrab/Views/MenuBarView.swift b/ScreenTextGrab/Views/MenuBarView.swift index 99743ad..60d52f7 100644 --- a/ScreenTextGrab/Views/MenuBarView.swift +++ b/ScreenTextGrab/Views/MenuBarView.swift @@ -111,33 +111,13 @@ struct MenuBarView: View { } private var background: some View { - LinearGradient( - colors: [.surfaceTop, .surfaceBottom], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - .overlay(alignment: .topTrailing) { - Circle() - .fill(Color.accentCool.opacity(0.16)) - .frame(width: 168, height: 168) - .blur(radius: 36) - .offset(x: 54, y: -52) - } - .overlay(alignment: .bottomLeading) { - Circle() - .fill(Color.accentWarm.opacity(0.12)) - .frame(width: 184, height: 184) - .blur(radius: 42) - .offset(x: -56, y: 70) - } - .overlay { - LinearGradient( - colors: [Color.white.opacity(0.06), Color.clear, Color.black.opacity(0.10)], - startPoint: .top, - endPoint: .bottom - ) + ZStack { + // Raycast-style material background with tint + Rectangle() + .fill(.ultraThinMaterial) + .overlay(Color.rayBackground.opacity(0.70)) + .ignoresSafeArea() } - .ignoresSafeArea() } private var header: some View { diff --git a/ScreenTextGrab/Views/ScreenTextGrabTheme.swift b/ScreenTextGrab/Views/ScreenTextGrabTheme.swift index 13ab111..ddb0245 100644 --- a/ScreenTextGrab/Views/ScreenTextGrabTheme.swift +++ b/ScreenTextGrab/Views/ScreenTextGrabTheme.swift @@ -1,19 +1,98 @@ import SwiftUI +import AppKit extension Color { - static let surfaceTop = Color(red: 0.03, green: 0.07, blue: 0.11) - static let surfaceBottom = Color(red: 0.07, green: 0.12, blue: 0.18) - static let accentWarm = Color(red: 0.57, green: 0.73, blue: 0.86) - static let accentAmber = Color(red: 0.70, green: 0.85, blue: 0.95) - static let accentCoral = Color(red: 0.47, green: 0.76, blue: 0.90) - static let accentCool = Color(red: 0.52, green: 0.82, blue: 0.96) - static let accentMint = Color(red: 0.63, green: 0.90, blue: 0.93) - static let accentNeutral = Color(red: 0.86, green: 0.93, blue: 0.97) - static let accentRose = Color(red: 0.51, green: 0.66, blue: 0.80) - static let cardFill = Color(red: 0.10, green: 0.15, blue: 0.20).opacity(0.90) - static let cardStroke = Color.white.opacity(0.08) - static let controlFill = Color(red: 0.96, green: 0.99, blue: 1.0).opacity(0.055) - static let controlFillStrong = Color(red: 0.96, green: 0.99, blue: 1.0).opacity(0.082) - static let controlStroke = Color(red: 0.80, green: 0.90, blue: 0.98).opacity(0.12) - static let controlStrokeStrong = Color(red: 0.80, green: 0.90, blue: 0.98).opacity(0.18) + // MARK: - Background surfaces (dynamic) + static let surfaceTop = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.03, green: 0.07, blue: 0.11, alpha: 1.0) + : NSColor(red: 0.94, green: 0.95, blue: 0.97, alpha: 1.0) + }) + + static let surfaceBottom = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.07, green: 0.12, blue: 0.18, alpha: 1.0) + : NSColor(red: 0.97, green: 0.98, blue: 0.99, alpha: 1.0) + }) + + // MARK: - Accent colors (adjusted for both modes) + static let accentWarm = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.57, green: 0.73, blue: 0.86, alpha: 1.0) + : NSColor(red: 0.35, green: 0.50, blue: 0.65, alpha: 1.0) + }) + + static let accentAmber = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.70, green: 0.85, blue: 0.95, alpha: 1.0) + : NSColor(red: 0.40, green: 0.55, blue: 0.70, alpha: 1.0) + }) + + static let accentCoral = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.47, green: 0.76, blue: 0.90, alpha: 1.0) + : NSColor(red: 0.25, green: 0.55, blue: 0.72, alpha: 1.0) + }) + + static let accentCool = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.52, green: 0.82, blue: 0.96, alpha: 1.0) + : NSColor(red: 0.20, green: 0.55, blue: 0.78, alpha: 1.0) + }) + + static let accentMint = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.63, green: 0.90, blue: 0.93, alpha: 1.0) + : NSColor(red: 0.25, green: 0.60, blue: 0.62, alpha: 1.0) + }) + + static let accentNeutral = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.86, green: 0.93, blue: 0.97, alpha: 1.0) + : NSColor(red: 0.35, green: 0.40, blue: 0.45, alpha: 1.0) + }) + + static let accentRose = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.51, green: 0.66, blue: 0.80, alpha: 1.0) + : NSColor(red: 0.35, green: 0.45, blue: 0.60, alpha: 1.0) + }) + + // MARK: - Card styling (dynamic) + static let cardFill = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.10, green: 0.15, blue: 0.20, alpha: 0.90) + : NSColor(white: 1.0, alpha: 0.70) + }) + + static let cardStroke = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(white: 1.0, alpha: 0.08) + : NSColor(white: 0.0, alpha: 0.08) + }) + + // MARK: - Control styling (dynamic) + static let controlFill = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.96, green: 0.99, blue: 1.0, alpha: 0.055) + : NSColor(white: 0.0, alpha: 0.04) + }) + + static let controlFillStrong = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.96, green: 0.99, blue: 1.0, alpha: 0.082) + : NSColor(white: 0.0, alpha: 0.06) + }) + + static let controlStroke = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.80, green: 0.90, blue: 0.98, alpha: 0.12) + : NSColor(white: 0.0, alpha: 0.10) + }) + + static let controlStrokeStrong = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(red: 0.80, green: 0.90, blue: 0.98, alpha: 0.18) + : NSColor(white: 0.0, alpha: 0.15) + }) } diff --git a/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift b/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift index 6b15453..efb88d4 100644 --- a/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift +++ b/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift @@ -20,24 +20,24 @@ struct SettingsSectionCard: View { VStack(alignment: .leading, spacing: 14) { VStack(alignment: .leading, spacing: 4) { Text(title) - .font(.system(size: 15, weight: .bold, design: .rounded)) + .font(.system(size: 14, weight: .semibold)) Text(subtitle) - .font(.system(size: 12, weight: .medium, design: .rounded)) + .font(.system(size: 12, weight: .regular)) .foregroundStyle(.secondary) } content } - .padding(18) + .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background( - RoundedRectangle(cornerRadius: 18, style: .continuous) - .fill(Color(NSColor.windowBackgroundColor).opacity(0.92)) + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(NSColor.controlBackgroundColor).opacity(0.7)) ) .overlay( - RoundedRectangle(cornerRadius: 18, style: .continuous) - .stroke(Color.black.opacity(0.06), lineWidth: 1) + RoundedRectangle(cornerRadius: 12, style: .continuous) + .stroke(Color.rayBorder, lineWidth: 0.5) ) } } diff --git a/ScreenTextGrab/Views/Settings/SettingsTabViews.swift b/ScreenTextGrab/Views/Settings/SettingsTabViews.swift index 3e3d601..c8d42d6 100644 --- a/ScreenTextGrab/Views/Settings/SettingsTabViews.swift +++ b/ScreenTextGrab/Views/Settings/SettingsTabViews.swift @@ -1,6 +1,8 @@ import SwiftUI struct SettingsGeneralTabView: View { + @EnvironmentObject private var themeManager: ThemeManager + let interfaceLanguageBinding: Binding let interfaceLanguageDetail: String let languageFeedback: InlineFeedback? @@ -84,6 +86,23 @@ struct SettingsGeneralTabView: View { } } + SettingsSectionCard( + title: L10n.pair("Görünüm", "Appearance"), + subtitle: L10n.pair("Menü paneli ve ayarlar için tema seç.", "Choose a theme for the menu panel and settings.") + ) { + Picker("", selection: Binding( + get: { themeManager.mode }, + set: { themeManager.setMode($0) } + )) { + ForEach(ThemeMode.allCases) { mode in + Label(mode.title, systemImage: mode.icon) + .tag(mode) + } + } + .pickerStyle(.segmented) + .accessibilityLabel(L10n.pair("Tema", "Theme")) + } + SettingsSectionCard( title: L10n.pair("Yakalama Modu", "Capture Mode"), subtitle: L10n.pair("Metin, altyazı, kod veya tablo odaklı yakalama arasında geçiş yap.", "Switch between text, subtitle, code, or table-focused capture.") From 1b14991c4eb9b0d8e9369a30827f0e45fe959346 Mon Sep 17 00:00:00 2001 From: luw2007 Date: Fri, 11 Sep 2026 15:27:03 +0800 Subject: [PATCH 2/2] Fix light mode text contrast: dynamic stgWhite color Replace all hardcoded .white / Color.white text and fill colors with dynamic stgWhite (white in dark mode, black in light mode). - Add Color.stgWhite dynamic color via NSColor(name:dynamicProvider:) - Add ShapeStyle extension so .stgWhite works in foregroundStyle/fill/stroke - Batch replace 57+ occurrences across MenuBarView, MenuBarSharedComponents, MenuBarQuickSettingsSection, SettingsTabViews, TableReviewView - Fix accidental NSColor.stgWhite in SelectionOverlay (use NSColor.white) - Fix accidental self-reference in ScreenTextGrabTheme definition --- .../MenuBar/MenuBarQuickSettingsSection.swift | 30 ++++++------- .../MenuBar/MenuBarSharedComponents.swift | 42 +++++++++---------- ScreenTextGrab/Views/MenuBarView.swift | 40 +++++++++--------- .../Views/ScreenTextGrabTheme.swift | 16 ++++++- .../Settings/SettingsSharedComponents.swift | 2 +- .../Views/Settings/SettingsTabViews.swift | 2 +- ScreenTextGrab/Views/SettingsView.swift | 12 +++--- ScreenTextGrab/Views/TableReviewView.swift | 2 +- 8 files changed, 80 insertions(+), 66 deletions(-) diff --git a/ScreenTextGrab/Views/MenuBar/MenuBarQuickSettingsSection.swift b/ScreenTextGrab/Views/MenuBar/MenuBarQuickSettingsSection.swift index 533d8ac..60f8122 100644 --- a/ScreenTextGrab/Views/MenuBar/MenuBarQuickSettingsSection.swift +++ b/ScreenTextGrab/Views/MenuBar/MenuBarQuickSettingsSection.swift @@ -31,7 +31,7 @@ struct MenuBarQuickSettingsSection: View { HStack(spacing: 8) { Text(L10n.controlsTitle) .font(.system(size: 12.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Spacer() @@ -41,7 +41,7 @@ struct MenuBarQuickSettingsSection: View { VStack(alignment: .leading, spacing: isCompact ? 8 : 10) { Text(L10n.pair("Yakalama Modu", "Capture Mode")) .font(.system(size: 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) LazyVGrid( columns: [ @@ -65,7 +65,7 @@ struct MenuBarQuickSettingsSection: View { } Rectangle() - .fill(Color.white.opacity(0.08)) + .fill(Color.stgWhite.opacity(0.08)) .frame(height: 1) watchRow @@ -80,13 +80,13 @@ struct MenuBarQuickSettingsSection: View { if shouldShowSmartActionsPanel { Rectangle() - .fill(Color.white.opacity(0.08)) + .fill(Color.stgWhite.opacity(0.08)) .frame(height: 1) VStack(alignment: .leading, spacing: 7) { Text(L10n.pair("Hızlı İşlem", "Quick Actions")) .font(.system(size: 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) HStack(spacing: 8) { ForEach(Array(smartActions.prefix(2))) { action in @@ -142,7 +142,7 @@ struct MenuBarQuickSettingsSection: View { HStack(spacing: 8) { Image(systemName: outputPresetIcon(outputPreset)) .font(.system(size: 10.5, weight: .bold)) - .foregroundStyle(.white.opacity(0.9)) + .foregroundStyle(.stgWhite.opacity(0.9)) VStack(alignment: .leading, spacing: 1) { Text(outputPreset.title) @@ -151,17 +151,17 @@ struct MenuBarQuickSettingsSection: View { Text(outputPreset.summary) .font(.system(size: 9, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.68)) + .foregroundStyle(Color.stgWhite.opacity(0.68)) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) } Image(systemName: "chevron.down") .font(.system(size: 9, weight: .bold)) - .foregroundStyle(Color.white.opacity(0.65)) + .foregroundStyle(Color.stgWhite.opacity(0.65)) } .font(.system(size: 10.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .padding(.horizontal, 10) .padding(.vertical, 7) .frame(minWidth: isCompact ? 146 : 160, alignment: .leading) @@ -186,11 +186,11 @@ struct MenuBarQuickSettingsSection: View { VStack(alignment: .leading, spacing: 2) { Text(L10n.pair("İzleme", "Watch")) .font(.system(size: 11.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(watchSummary) .font(.system(size: 10, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.66)) + .foregroundStyle(Color.stgWhite.opacity(0.66)) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) } @@ -220,11 +220,11 @@ struct MenuBarQuickSettingsSection: View { HStack(spacing: 6) { Image(systemName: captureModeIcon(mode)) .font(.system(size: isCompact ? 9.5 : 10.5, weight: .bold)) - .foregroundStyle(isSelected ? .white : tint) + .foregroundStyle(isSelected ? .stgWhite : tint) .frame(width: isCompact ? 22 : 24, height: isCompact ? 22 : 24) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(isSelected ? Color.white.opacity(0.14) : tint.opacity(0.12)) + .fill(isSelected ? Color.stgWhite.opacity(0.14) : tint.opacity(0.12)) ) Text(mode.title) .font(.system(size: isCompact ? 11 : 12, weight: .bold, design: .rounded)) @@ -238,9 +238,9 @@ struct MenuBarQuickSettingsSection: View { .minimumScaleFactor(0.9) .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.leading) - .foregroundStyle(isSelected ? Color.white : Color.white.opacity(0.84)) + .foregroundStyle(isSelected ? Color.stgWhite : Color.stgWhite.opacity(0.84)) } - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .padding(.horizontal, isCompact ? 9 : 10) .padding(.vertical, isCompact ? 8 : 10) .frame(maxWidth: .infinity, minHeight: isCompact ? 68 : 74, alignment: .leading) diff --git a/ScreenTextGrab/Views/MenuBar/MenuBarSharedComponents.swift b/ScreenTextGrab/Views/MenuBar/MenuBarSharedComponents.swift index db4aaf6..5b044e7 100644 --- a/ScreenTextGrab/Views/MenuBar/MenuBarSharedComponents.swift +++ b/ScreenTextGrab/Views/MenuBar/MenuBarSharedComponents.swift @@ -23,7 +23,7 @@ struct MenuBarCard: View { RoundedRectangle(cornerRadius: 18, style: .continuous) .fill( LinearGradient( - colors: [Color.white.opacity(0.04), Color.clear], + colors: [Color.stgWhite.opacity(0.04), Color.clear], startPoint: .topLeading, endPoint: .bottomTrailing ) @@ -76,7 +76,7 @@ struct MenuBarSecondaryButton: View { .fixedSize(horizontal: false, vertical: true) } .font(.system(size: isCompact ? 9.8 : 10.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(2) .padding(.horizontal, isCompact ? 10 : 12) .padding(.vertical, isCompact ? 8 : 9) @@ -114,7 +114,7 @@ struct MenuBarSmallActionButton: View { Text(title) } .font(.system(size: isCompact ? 9.5 : 10, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(1) .minimumScaleFactor(0.82) .padding(.horizontal, isCompact ? 10 : 11) @@ -153,7 +153,7 @@ struct MenuBarCompactInlineButton: View { .fixedSize(horizontal: false, vertical: true) } .font(.system(size: isCompact ? 9.6 : 10.1, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(2) .minimumScaleFactor(0.8) .padding(.horizontal, isCompact ? 9 : 10) @@ -187,7 +187,7 @@ struct MenuBarIconActionButton: View { Button(action: action) { Image(systemName: systemName) .font(.system(size: 11, weight: .bold)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .frame(width: isCompact ? 30 : 32, height: isCompact ? 30 : 32) .background( RoundedRectangle(cornerRadius: 12, style: .continuous) @@ -223,7 +223,7 @@ struct MenuBarUpdateActionButton: View { .minimumScaleFactor(0.84) } .font(.system(size: isCompact ? 8.9 : 9.8, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .padding(.horizontal, isCompact ? 10 : 11) .padding(.vertical, isCompact ? 6 : 7) .frame(width: presentation.width, height: isCompact ? 32 : 34) @@ -272,11 +272,11 @@ struct MenuBarQuickSettingRow: View { VStack(alignment: .leading, spacing: 3) { Text(title) .font(.system(size: isCompact ? 11.5 : 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(detail) .font(.system(size: isCompact ? 10 : 10.3, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.66)) + .foregroundStyle(Color.stgWhite.opacity(0.66)) .fixedSize(horizontal: false, vertical: true) } @@ -319,13 +319,13 @@ struct MenuBarHeaderSection: View { VStack(alignment: .leading, spacing: 2) { Text("ScreenTextGrab") .font(.system(size: isCompact ? 15 : 16.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(1) .minimumScaleFactor(0.72) Text(headerLine) .font(.system(size: isCompact ? 10 : 11, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.68)) + .foregroundStyle(Color.stgWhite.opacity(0.68)) .lineLimit(1) .minimumScaleFactor(0.8) } @@ -370,7 +370,7 @@ struct MenuBarStatusSection: View { Text(statusTitle) .font(.system(size: 12.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Spacer() @@ -383,7 +383,7 @@ struct MenuBarStatusSection: View { Text(statusDescription) .font(.system(size: isCompact ? 11 : 12, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.72)) + .foregroundStyle(Color.stgWhite.opacity(0.72)) .lineLimit(isCompact ? 2 : 3) } } @@ -403,18 +403,18 @@ struct MenuBarPrimaryActionSection: View { HStack(spacing: 12) { Image(systemName: icon) .font(.system(size: isCompact ? 15 : 17, weight: .bold)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .frame(width: isCompact ? 32 : 36, height: isCompact ? 32 : 36) - .background(Color.white.opacity(0.14), in: RoundedRectangle(cornerRadius: isCompact ? 10 : 12, style: .continuous)) + .background(Color.stgWhite.opacity(0.14), in: RoundedRectangle(cornerRadius: isCompact ? 10 : 12, style: .continuous)) VStack(alignment: .leading, spacing: 2) { Text(title) .font(.system(size: isCompact ? 14 : 15, weight: .bold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(subtitle) .font(.system(size: isCompact ? 10.5 : 11.5, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.78)) + .foregroundStyle(Color.stgWhite.opacity(0.78)) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) } @@ -432,7 +432,7 @@ struct MenuBarPrimaryActionSection: View { ) .overlay( RoundedRectangle(cornerRadius: 18, style: .continuous) - .stroke(canStartCapture ? Color.white.opacity(0.14) : Color.controlStroke, lineWidth: 1) + .stroke(canStartCapture ? Color.stgWhite.opacity(0.14) : Color.controlStroke, lineWidth: 1) ) .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) .shadow(color: canStartCapture ? Color.black.opacity(0.18) : .clear, radius: 12, y: 8) @@ -459,11 +459,11 @@ struct MenuBarNoticeSection: View { VStack(alignment: .leading, spacing: 2) { Text(notice.title) .font(.system(size: isCompact ? 11 : 11.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(notice.message) .font(.system(size: isCompact ? 10 : 11, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.66)) + .foregroundStyle(Color.stgWhite.opacity(0.66)) .lineLimit(isCompact ? 2 : 3) } } @@ -498,11 +498,11 @@ struct MenuBarImportDropOverlay: View { Text(L10n.pair("Görsel veya PDF bırak", "Drop an Image or PDF")) .font(.system(size: 15, weight: .bold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(L10n.pair("Görsel dosyası OCR’a gider, PDF dosyası doğrudan içe alınır.", "Image files go through OCR, and PDF files are imported directly.")) .font(.system(size: 11.5, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.72)) + .foregroundStyle(Color.stgWhite.opacity(0.72)) .multilineTextAlignment(.center) .frame(maxWidth: 220) } diff --git a/ScreenTextGrab/Views/MenuBarView.swift b/ScreenTextGrab/Views/MenuBarView.swift index 60d52f7..d37b32c 100644 --- a/ScreenTextGrab/Views/MenuBarView.swift +++ b/ScreenTextGrab/Views/MenuBarView.swift @@ -199,11 +199,11 @@ struct MenuBarView: View { VStack(alignment: .leading, spacing: 2) { Text(L10n.pair("İzleme", "Watch")) .font(.system(size: 11.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(watchSummary) .font(.system(size: 10, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.66)) + .foregroundStyle(Color.stgWhite.opacity(0.66)) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) } @@ -251,10 +251,10 @@ struct MenuBarView: View { Image(systemName: "chevron.down") .font(.system(size: 8.5, weight: .bold)) - .foregroundStyle(Color.white.opacity(0.64)) + .foregroundStyle(Color.stgWhite.opacity(0.64)) } .font(.system(size: isCompactPanel ? 9.6 : 10.2, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(1) .padding(.horizontal, isCompactPanel ? 10 : 11) .padding(.vertical, isCompactPanel ? 7 : 8) @@ -284,7 +284,7 @@ struct MenuBarView: View { Text(title) } .font(.system(size: isCompactPanel ? 9.5 : 10, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(1) .minimumScaleFactor(0.82) .padding(.horizontal, isCompactPanel ? 10 : 11) @@ -353,7 +353,7 @@ struct MenuBarView: View { .fixedSize(horizontal: false, vertical: true) } .font(.system(size: isCompactPanel ? 9.6 : 10.1, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(2) .minimumScaleFactor(0.8) .padding(.horizontal, isCompactPanel ? 9 : 10) @@ -386,7 +386,7 @@ struct MenuBarView: View { RoundedRectangle(cornerRadius: 18, style: .continuous) .fill( LinearGradient( - colors: [Color.white.opacity(0.04), Color.clear], + colors: [Color.stgWhite.opacity(0.04), Color.clear], startPoint: .topLeading, endPoint: .bottomTrailing ) @@ -408,11 +408,11 @@ struct MenuBarView: View { VStack(alignment: .leading, spacing: 3) { Text(title) .font(.system(size: isCompactPanel ? 11.5 : 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(detail) .font(.system(size: isCompactPanel ? 10 : 10.3, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.66)) + .foregroundStyle(Color.stgWhite.opacity(0.66)) .fixedSize(horizontal: false, vertical: true) } @@ -448,7 +448,7 @@ struct MenuBarView: View { .fixedSize(horizontal: false, vertical: true) } .font(.system(size: isCompactPanel ? 9.6 : 10.2, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(2) .minimumScaleFactor(0.8) .padding(.horizontal, isCompactPanel ? 10 : 11) @@ -487,11 +487,11 @@ struct MenuBarView: View { HStack(spacing: 6) { Image(systemName: captureModeIcon(for: mode)) .font(.system(size: isCompactPanel ? 9.5 : 10.5, weight: .bold)) - .foregroundStyle(isSelected ? .white : tint) + .foregroundStyle(isSelected ? .stgWhite : tint) .frame(width: isCompactPanel ? 22 : 24, height: isCompactPanel ? 22 : 24) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(isSelected ? Color.white.opacity(0.14) : tint.opacity(0.12)) + .fill(isSelected ? Color.stgWhite.opacity(0.14) : tint.opacity(0.12)) ) Text(mode.title) .font(.system(size: isCompactPanel ? 11 : 12, weight: .bold, design: .rounded)) @@ -505,9 +505,9 @@ struct MenuBarView: View { .minimumScaleFactor(0.9) .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.leading) - .foregroundStyle((isSelected ? Color.white : Color.white.opacity(0.84))) + .foregroundStyle((isSelected ? Color.stgWhite : Color.stgWhite.opacity(0.84))) } - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .padding(.horizontal, isCompactPanel ? 9 : 10) .padding(.vertical, isCompactPanel ? 8 : 10) .frame(maxWidth: .infinity, minHeight: isCompactPanel ? 68 : 74, alignment: .leading) @@ -1070,11 +1070,11 @@ struct MenuBarView: View { VStack(alignment: .leading, spacing: 2) { Text(L10n.pair("Hazır snippet'ler", "Ready snippets")) .font(.system(size: isCompactPanel ? 11 : 11.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) Text(activeSavedSnippetQuickPickSummary(for: suggestion, hiddenCount: hiddenCount)) .font(.system(size: isCompactPanel ? 10 : 11, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.66)) + .foregroundStyle(Color.stgWhite.opacity(0.66)) .lineLimit(isCompactPanel ? 2 : 3) } @@ -1455,12 +1455,12 @@ struct MenuBarView: View { VStack(alignment: .leading, spacing: 3) { Text(snippet.name) .font(.system(size: isCompactPanel ? 11 : 11.5, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .lineLimit(1) Text(snippet.previewText) .font(.system(size: isCompactPanel ? 9.5 : 10.5, weight: .medium, design: .rounded)) - .foregroundStyle(Color.white.opacity(0.68)) + .foregroundStyle(Color.stgWhite.opacity(0.68)) .lineLimit(1) } @@ -1476,11 +1476,11 @@ struct MenuBarView: View { .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color.white.opacity(0.07)) + .fill(Color.stgWhite.opacity(0.07)) ) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.white.opacity(0.10), lineWidth: 1) + .stroke(Color.stgWhite.opacity(0.10), lineWidth: 1) ) } .buttonStyle(.plain) diff --git a/ScreenTextGrab/Views/ScreenTextGrabTheme.swift b/ScreenTextGrab/Views/ScreenTextGrabTheme.swift index ddb0245..24b7754 100644 --- a/ScreenTextGrab/Views/ScreenTextGrabTheme.swift +++ b/ScreenTextGrab/Views/ScreenTextGrabTheme.swift @@ -2,7 +2,21 @@ import SwiftUI import AppKit extension Color { - // MARK: - Background surfaces (dynamic) + // Dynamic "white" — white in dark mode, black in light mode. + // Replaces hardcoded .white / Color.stgWhite for text and subtle fills. + static let stgWhite = Color(NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor.white + : NSColor.black + }) +} + +// Make .stgWhite work in ShapeStyle contexts (foregroundStyle, fill, stroke, etc.) +extension ShapeStyle where Self == Color { + static var stgWhite: Color { Color.stgWhite } +} + +extension Color { static let surfaceTop = Color(NSColor(name: nil) { appearance in appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? NSColor(red: 0.03, green: 0.07, blue: 0.11, alpha: 1.0) diff --git a/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift b/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift index efb88d4..0f4aa6c 100644 --- a/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift +++ b/ScreenTextGrab/Views/Settings/SettingsSharedComponents.swift @@ -125,7 +125,7 @@ struct SettingsFilterChip: View { Button(action: action) { Text(title) .font(.system(size: 10.5, weight: .bold, design: .rounded)) - .foregroundStyle(isSelected ? Color.white : Color.primary) + .foregroundStyle(isSelected ? Color.stgWhite : Color.primary) .padding(.horizontal, 10) .padding(.vertical, 7) .background( diff --git a/ScreenTextGrab/Views/Settings/SettingsTabViews.swift b/ScreenTextGrab/Views/Settings/SettingsTabViews.swift index c8d42d6..0cfacf5 100644 --- a/ScreenTextGrab/Views/Settings/SettingsTabViews.swift +++ b/ScreenTextGrab/Views/Settings/SettingsTabViews.swift @@ -155,7 +155,7 @@ struct SettingsGeneralTabView: View { Button(action: toggleHotkeyRecording) { Text(isRecordingHotkey ? L10n.pair("Tuşa Bas...", "Press Keys...") : hotkeyDisplayLabel) .font(.system(size: 12, weight: .bold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .padding(.horizontal, 14) .padding(.vertical, 10) .background( diff --git a/ScreenTextGrab/Views/SettingsView.swift b/ScreenTextGrab/Views/SettingsView.swift index 04e5dd6..fcf6ec1 100644 --- a/ScreenTextGrab/Views/SettingsView.swift +++ b/ScreenTextGrab/Views/SettingsView.swift @@ -347,7 +347,7 @@ struct SettingsView: View { .frame(width: 28, height: 28) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) - .fill((entry.isPinned ? Color.accentAmber : Color.white).opacity(entry.isPinned ? 0.18 : 0.08)) + .fill((entry.isPinned ? Color.accentAmber : Color.stgWhite).opacity(entry.isPinned ? 0.18 : 0.08)) ) } .buttonStyle(.plain) @@ -1008,7 +1008,7 @@ struct SettingsView: View { .font(.system(size: 10.5, weight: .medium, design: .rounded)) .lineLimit(2) } - .foregroundStyle(isSelected ? Color.white : Color.primary) + .foregroundStyle(isSelected ? Color.stgWhite : Color.primary) .padding(.horizontal, 12) .padding(.vertical, 11) .frame(maxWidth: .infinity, alignment: .leading) @@ -1040,7 +1040,7 @@ struct SettingsView: View { .lineLimit(2) .fixedSize(horizontal: false, vertical: true) } - .foregroundStyle(isSelected ? Color.white : Color.primary) + .foregroundStyle(isSelected ? Color.stgWhite : Color.primary) .padding(.horizontal, 12) .padding(.vertical, 11) .frame(maxWidth: .infinity, minHeight: 66, alignment: .leading) @@ -1072,7 +1072,7 @@ struct SettingsView: View { .lineLimit(3) .fixedSize(horizontal: false, vertical: true) } - .foregroundStyle(isSelected ? Color.white : Color.primary) + .foregroundStyle(isSelected ? Color.stgWhite : Color.primary) .padding(.horizontal, 12) .padding(.vertical, 11) .frame(maxWidth: .infinity, alignment: .leading) @@ -1139,7 +1139,7 @@ struct SettingsView: View { .font(.system(size: 10.5, weight: .medium, design: .rounded)) .lineLimit(1) } - .foregroundStyle(isSelected ? Color.white : Color.primary) + .foregroundStyle(isSelected ? Color.stgWhite : Color.primary) .padding(.horizontal, 10) .padding(.vertical, 12) .frame(maxWidth: .infinity) @@ -1172,7 +1172,7 @@ struct SettingsView: View { .minimumScaleFactor(0.9) .fixedSize(horizontal: false, vertical: true) } - .foregroundStyle(isSelected ? Color.white : Color.primary) + .foregroundStyle(isSelected ? Color.stgWhite : Color.primary) .padding(.horizontal, 12) .padding(.vertical, 12) .frame(maxWidth: .infinity, minHeight: 76, alignment: .leading) diff --git a/ScreenTextGrab/Views/TableReviewView.swift b/ScreenTextGrab/Views/TableReviewView.swift index 87ceb6e..1d6ede8 100644 --- a/ScreenTextGrab/Views/TableReviewView.swift +++ b/ScreenTextGrab/Views/TableReviewView.swift @@ -417,7 +417,7 @@ struct TableReviewView: View { Text(title) } .font(.system(size: 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white) + .foregroundStyle(.stgWhite) .padding(.horizontal, 14) .padding(.vertical, 10) .background(