Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ScreenTextGrab/ScreenTextGrabApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ struct ScreenTextGrabApp: App {
MenuBarExtra {
MenuBarView()
.environmentObject(appDelegate.appState)
.environmentObject(ThemeManager.shared)
.preferredColorScheme(ThemeManager.shared.effectiveColorScheme)
} label: {
MenuBarStatusIcon()
}
Expand All @@ -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)
}
}
Expand Down
224 changes: 224 additions & 0 deletions ScreenTextGrab/Views/AppTheme.swift
Original file line number Diff line number Diff line change
@@ -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))
}
}
30 changes: 15 additions & 15 deletions ScreenTextGrab/Views/MenuBar/MenuBarQuickSettingsSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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: [
Expand All @@ -65,7 +65,7 @@ struct MenuBarQuickSettingsSection: View {
}

Rectangle()
.fill(Color.white.opacity(0.08))
.fill(Color.stgWhite.opacity(0.08))
.frame(height: 1)

watchRow
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
}
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down
Loading