Working a bit

This commit is contained in:
Hariharan Mudaliar 2025-12-01 13:01:44 +05:30
commit 1f0eea3cc6
8 changed files with 881 additions and 91 deletions

View file

@ -0,0 +1,182 @@
#if os(macOS)
import SwiftUI
import Defaults
/// Custom OSD panel that mimics macOS native OSD appearance
/// Supports volume, brightness, and keyboard backlight displays
struct CustomOSDView: View {
@Binding var type: SneakContentType
@Binding var value: CGFloat
@Binding var icon: String
@Default(.osdMaterial) var osdMaterial
@Default(.osdIconColorStyle) var osdIconColorStyle
@Environment(\.colorScheme) var colorScheme
private let iconSize: CGFloat = 56
private let progressBarHeight: CGFloat = 6
private let spacing: CGFloat = 20
private let segmentCount: Int = 16
var body: some View {
VStack(spacing: spacing) {
// Icon
Image(systemName: symbolName)
.font(.system(size: iconSize, weight: .regular))
.foregroundStyle(iconColor)
.symbolRenderingMode(.hierarchical)
// Progress/Value Indicator
progressView
.frame(width: 140)
}
.frame(width: 200, height: 200)
.background(
backgroundView
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
)
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
.shadow(color: Color.black.opacity(0.25), radius: 24, x: 0, y: 8)
}
// MARK: - Symbol Logic
private var symbolName: String {
if !icon.isEmpty {
return icon
}
switch type {
case .volume:
if value < 0.01 {
return "speaker.slash.fill"
} else if value < 0.33 {
return "speaker.fill"
} else if value < 0.66 {
return "speaker.wave.1.fill"
} else {
return "speaker.wave.3.fill"
}
case .brightness:
return "sun.max.fill"
case .backlight:
return value >= 0.5 ? "light.max" : "light.min"
default:
return "questionmark"
}
}
private var iconColor: Color {
switch osdIconColorStyle {
case .white:
return .white
case .whiteTransparent:
return .white.opacity(0.85)
case .lightGray:
return Color(white: 0.85)
case .darkGray:
return Color(white: 0.3)
case .black:
return .black
case .blackTransparent:
return .black.opacity(0.85)
case .auto:
return colorScheme == .dark ? .white : .black
case .autoTransparent:
return colorScheme == .dark ? .white.opacity(0.85) : .black.opacity(0.85)
}
}
private var textColor: Color {
switch osdIconColorStyle {
case .white, .whiteTransparent, .lightGray:
return .white
case .darkGray, .black, .blackTransparent:
return .black
case .auto, .autoTransparent:
return colorScheme == .dark ? .white : .black
}
}
// MARK: - Progress View
private var progressView: some View {
HStack(spacing: 3) {
ForEach(0..<segmentCount, id: \.self) { index in
Capsule()
.fill(index < Int(value * CGFloat(segmentCount)) ? iconColor : inactiveSegmentColor)
.frame(width: 6, height: progressBarHeight)
}
}
}
private var inactiveSegmentColor: Color {
switch osdIconColorStyle {
case .white, .whiteTransparent, .lightGray:
return Color.white.opacity(0.25)
case .darkGray, .black, .blackTransparent:
return Color.black.opacity(0.25)
case .auto, .autoTransparent:
return colorScheme == .dark ? Color.white.opacity(0.25) : Color.black.opacity(0.25)
}
}
// MARK: - Background
@ViewBuilder
private var backgroundView: some View {
switch osdMaterial {
case .frosted:
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(.ultraThinMaterial)
case .liquid:
if #available(macOS 26.0, *) {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.glassEffect(
.clear.interactive(),
in: .rect(cornerRadius: 18)
)
} else {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(.ultraThinMaterial)
}
case .solidDark:
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(Color.black.opacity(0.85))
case .solidLight:
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(Color.white.opacity(0.85))
case .solidAuto:
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill((colorScheme == .dark ? Color.black : Color.white).opacity(0.85))
}
}
}
// MARK: - OSD Material Enum
enum OSDMaterial: String, CaseIterable, Defaults.Serializable {
case frosted = "Frosted Glass"
case liquid = "Liquid Glass"
case solidDark = "Solid Dark"
case solidLight = "Solid Light"
case solidAuto = "Solid Auto"
}
// MARK: - OSD Icon Color Style Enum
enum OSDIconColorStyle: String, CaseIterable, Defaults.Serializable {
case white = "White"
case whiteTransparent = "White (Transparent)"
case lightGray = "Light Gray"
case darkGray = "Dark Gray"
case black = "Black"
case blackTransparent = "Black (Transparent)"
case auto = "Auto"
case autoTransparent = "Auto (Transparent)"
}
#endif

View file

@ -24,6 +24,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
case timer
case calendar
case hud
case osd
case battery
case stats
case clipboard
@ -46,6 +47,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
case .timer: return "Timer"
case .calendar: return "Calendar"
case .hud: return "HUDs"
case .osd: return "Custom OSD"
case .battery: return "Battery"
case .stats: return "Stats"
case .clipboard: return "Clipboard"
@ -68,6 +70,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
case .timer: return "timer"
case .calendar: return "calendar"
case .hud: return "dial.medium.fill"
case .osd: return "square.fill.on.square.fill"
case .battery: return "battery.100.bolt"
case .stats: return "chart.xyaxis.line"
case .clipboard: return "clipboard"
@ -90,6 +93,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
case .timer: return .red
case .calendar: return .cyan
case .hud: return .indigo
case .osd: return .teal
case .battery: return .yellow
case .stats: return .teal
case .clipboard: return .mint
@ -352,6 +356,7 @@ struct SettingsView: View {
.timer,
.calendar,
.hud,
.osd,
.battery,
.stats,
.clipboard,
@ -536,7 +541,7 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .general, title: "Show on all displays", keywords: ["multi-display", "external monitor"], highlightID: SettingsTab.general.highlightID(for: "Show on all displays")),
SettingsSearchEntry(tab: .general, title: "Show on a specific display", keywords: ["preferred screen", "display picker"], highlightID: SettingsTab.general.highlightID(for: "Show on a specific display")),
SettingsSearchEntry(tab: .general, title: "Automatically switch displays", keywords: ["auto switch", "displays"], highlightID: SettingsTab.general.highlightID(for: "Automatically switch displays")),
SettingsSearchEntry(tab: .general, title: "Hide Atoll during screenshots & recordings", keywords: ["privacy", "screenshot", "recording"], highlightID: SettingsTab.general.highlightID(for: "Hide Atoll during screenshots & recordings")),
SettingsSearchEntry(tab: .general, title: "Hide Dynamic Island during screenshots & recordings", keywords: ["privacy", "screenshot", "recording"], highlightID: SettingsTab.general.highlightID(for: "Hide Dynamic Island during screenshots & recordings")),
SettingsSearchEntry(tab: .general, title: "Enable Screen Recording Detection", keywords: ["screen recording", "indicator"], highlightID: SettingsTab.general.highlightID(for: "Enable Screen Recording Detection")),
SettingsSearchEntry(tab: .general, title: "Show Recording Indicator", keywords: ["recording indicator", "red dot"], highlightID: SettingsTab.general.highlightID(for: "Show Recording Indicator")),
SettingsSearchEntry(tab: .general, title: "Enable Focus Detection", keywords: ["focus", "do not disturb", "dnd"], highlightID: SettingsTab.general.highlightID(for: "Enable Focus Detection")),
@ -577,6 +582,14 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .hud, title: "Enable glowing effect", keywords: ["glow", "indicator"], highlightID: SettingsTab.hud.highlightID(for: "Enable glowing effect")),
SettingsSearchEntry(tab: .hud, title: "Use accent color", keywords: ["accent", "color"], highlightID: SettingsTab.hud.highlightID(for: "Use accent color")),
// Custom OSD
SettingsSearchEntry(tab: .osd, title: "Enable Custom OSD", keywords: ["osd", "on-screen display", "custom osd"], highlightID: SettingsTab.osd.highlightID(for: "Enable Custom OSD")),
SettingsSearchEntry(tab: .osd, title: "Volume OSD", keywords: ["volume", "osd"], highlightID: SettingsTab.osd.highlightID(for: "Volume OSD")),
SettingsSearchEntry(tab: .osd, title: "Brightness OSD", keywords: ["brightness", "osd"], highlightID: SettingsTab.osd.highlightID(for: "Brightness OSD")),
SettingsSearchEntry(tab: .osd, title: "Keyboard Backlight OSD", keywords: ["keyboard", "backlight", "osd"], highlightID: SettingsTab.osd.highlightID(for: "Keyboard Backlight OSD")),
SettingsSearchEntry(tab: .osd, title: "Material", keywords: ["material", "frosted", "liquid", "glass", "solid", "osd"], highlightID: SettingsTab.osd.highlightID(for: "Material")),
SettingsSearchEntry(tab: .osd, title: "Icon & Progress Color", keywords: ["color", "icon", "white", "black", "gray", "osd"], highlightID: SettingsTab.osd.highlightID(for: "Icon & Progress Color")),
// Media
SettingsSearchEntry(tab: .media, title: "Music Source", keywords: ["media source", "controller"], highlightID: SettingsTab.media.highlightID(for: "Music Source")),
SettingsSearchEntry(tab: .media, title: "Skip buttons", keywords: ["skip", "controls", "±10"], highlightID: SettingsTab.media.highlightID(for: "Skip buttons")),
@ -713,6 +726,10 @@ struct SettingsView: View {
SettingsForm(tab: .hud) {
HUD()
}
case .osd:
SettingsForm(tab: .osd) {
CustomOSDSettings()
}
case .battery:
SettingsForm(tab: .battery) {
Charge()
@ -839,8 +856,8 @@ struct GeneralSettings: View {
}
.disabled(showOnAllDisplays)
.settingsHighlight(id: highlightID("Automatically switch displays"))
Defaults.Toggle("Hide Atoll during screenshots & recordings", key: .hideDynamicIslandFromScreenCapture)
.settingsHighlight(id: highlightID("Hide Atoll during screenshots & recordings"))
Defaults.Toggle("Hide Dynamic Island during screenshots & recordings", key: .hideDynamicIslandFromScreenCapture)
.settingsHighlight(id: highlightID("Hide Dynamic Island during screenshots & recordings"))
} header: {
Text("System features")
}
@ -1239,48 +1256,31 @@ struct HUD: View {
var body: some View {
Form {
Section {
Toggle("Enable HUD replacement", isOn: $coordinator.hudReplacement)
Toggle("Enable HUDs", isOn: $enableSystemHUD)
.disabled(Defaults[.enableCustomOSD])
} header: {
Text("General")
} footer: {
Text("Replaces system HUD notifications with Atoll displays.")
.foregroundStyle(.secondary)
.font(.caption)
}
Section {
Toggle("Enable Built-in System HUD", isOn: $enableSystemHUD)
if enableSystemHUD {
Toggle("Volume HUD", isOn: $enableVolumeHUD)
Toggle("Brightness HUD", isOn: $enableBrightnessHUD)
Toggle("Keyboard Backlight HUD", isOn: $enableKeyboardBacklightHUD)
HStack {
Text("Sensitivity")
Spacer()
Slider(value: Binding(
get: { Double(systemHUDSensitivity) },
set: { systemHUDSensitivity = Int($0) }
), in: 1...10, step: 1) {
Text("Sensitivity")
}
.frame(width: 120)
Text("\(systemHUDSensitivity)")
.foregroundStyle(.secondary)
.font(.caption)
.frame(width: 20)
}
}
} header: {
Text("Built-in System Monitoring")
} footer: {
if enableSystemHUD {
Text("Built-in system monitoring detects volume, brightness, and keyboard backlight changes directly without requiring external apps.")
if Defaults[.enableCustomOSD] {
Text("HUDs are disabled because Custom OSD is enabled. Disable Custom OSD in the Custom OSD tab to use HUDs.")
.foregroundStyle(.secondary)
.font(.caption)
} else {
Text("Enable built-in system monitoring to replace macOS HUD notifications with Atoll displays.")
Text("Replaces macOS system HUD with Dynamic Island displays for volume, brightness, and keyboard backlight.")
.foregroundStyle(.secondary)
.font(.caption)
}
}
if enableSystemHUD && !Defaults[.enableCustomOSD] {
Section {
Toggle("Volume HUD", isOn: $enableVolumeHUD)
Toggle("Brightness HUD", isOn: $enableBrightnessHUD)
Toggle("Keyboard Backlight HUD", isOn: $enableKeyboardBacklightHUD)
} header: {
Text("Controls")
} footer: {
Text("Choose which system controls should display HUD notifications.")
.foregroundStyle(.secondary)
.font(.caption)
}
@ -2414,7 +2414,7 @@ struct LockScreenSettings: View {
} header: {
Text("Live Activity & Feedback")
} footer: {
Text("Controls whether Atoll mirrors lock/unlock events with its own live activity and audible chimes.")
Text("Controls whether Dynamic Island mirrors lock/unlock events with its own live activity and audible chimes.")
}
Section {
@ -3003,7 +3003,7 @@ struct Shortcuts: View {
} header: {
Text("Navigation")
} footer: {
Text("Toggle the Atoll open or closed from anywhere.")
Text("Toggle the Dynamic Island open or closed from anywhere.")
.multilineTextAlignment(.trailing)
.foregroundStyle(.secondary)
.font(.caption)
@ -3777,7 +3777,7 @@ struct StatsSettings: View {
Text("Graph Visibility")
} footer: {
if enabledGraphsCount >= 4 {
Text("With \(enabledGraphsCount) graphs enabled, the Atoll will expand horizontally to accommodate all graphs in a single row.")
Text("With \(enabledGraphsCount) graphs enabled, the Dynamic Island will expand horizontally to accommodate all graphs in a single row.")
.multilineTextAlignment(.trailing)
.foregroundStyle(.secondary)
.font(.caption)
@ -4360,6 +4360,151 @@ struct ColorPickerSettings: View {
}
}
struct CustomOSDSettings: View {
@Default(.enableCustomOSD) var enableCustomOSD
@Default(.enableOSDVolume) var enableOSDVolume
@Default(.enableOSDBrightness) var enableOSDBrightness
@Default(.enableOSDKeyboardBacklight) var enableOSDKeyboardBacklight
@Default(.osdMaterial) var osdMaterial
@Default(.osdIconColorStyle) var osdIconColorStyle
@Default(.enableSystemHUD) var enableSystemHUD
@State private var previewValue: CGFloat = 0.65
@State private var previewType: SneakContentType = .volume
private func highlightID(_ title: String) -> String {
SettingsTab.osd.highlightID(for: title)
}
var body: some View {
Form {
Section {
Toggle("Enable Custom OSD", isOn: $enableCustomOSD)
.settingsHighlight(id: highlightID("Enable Custom OSD"))
.disabled(enableSystemHUD)
.onChange(of: enableCustomOSD) { _, enabled in
if enabled {
// Disable System HUD when OSD is enabled
enableSystemHUD = false
}
}
} header: {
Text("General")
} footer: {
if enableSystemHUD {
Text("Custom OSD is disabled because HUDs are enabled. Disable HUDs in the HUDs tab to use Custom OSD.")
.foregroundStyle(.secondary)
.font(.caption)
} else {
Text("Display custom macOS-style OSD windows at the bottom center of the screen when adjusting volume, brightness, or keyboard backlight.")
.foregroundStyle(.secondary)
.font(.caption)
}
}
if enableCustomOSD {
Section {
Toggle("Volume OSD", isOn: $enableOSDVolume)
.settingsHighlight(id: highlightID("Volume OSD"))
Toggle("Brightness OSD", isOn: $enableOSDBrightness)
.settingsHighlight(id: highlightID("Brightness OSD"))
Toggle("Keyboard Backlight OSD", isOn: $enableOSDKeyboardBacklight)
.settingsHighlight(id: highlightID("Keyboard Backlight OSD"))
} header: {
Text("Controls")
} footer: {
Text("Choose which system controls should display custom OSD windows.")
.foregroundStyle(.secondary)
.font(.caption)
}
Section {
Picker("Material", selection: $osdMaterial) {
ForEach(OSDMaterial.allCases, id: \.self) { material in
Text(material.rawValue).tag(material)
}
}
.settingsHighlight(id: highlightID("Material"))
.onChange(of: osdMaterial) { _, _ in
previewValue = previewValue == 0.65 ? 0.651 : 0.65
}
Picker("Icon & Progress Color", selection: $osdIconColorStyle) {
ForEach(OSDIconColorStyle.allCases, id: \.self) { style in
Text(style.rawValue).tag(style)
}
}
.settingsHighlight(id: highlightID("Icon & Progress Color"))
.onChange(of: osdIconColorStyle) { _, _ in
previewValue = previewValue == 0.65 ? 0.651 : 0.65
}
} header: {
Text("Appearance")
} footer: {
VStack(alignment: .leading, spacing: 4) {
Text("Material Options:")
Text("• Frosted Glass: Translucent blur effect")
Text("• Liquid Glass: Modern glass effect (macOS 26+)")
Text("• Solid Dark/Light/Auto: Opaque backgrounds")
Text("")
Text("Color options control the icon and progress bar appearance. Auto adapts to system theme.")
}
.foregroundStyle(.secondary)
.font(.caption)
}
Section {
HStack {
Spacer()
VStack(spacing: 16) {
Text("Live Preview")
.font(.caption)
.foregroundStyle(.secondary)
CustomOSDView(
type: .constant(previewType),
value: .constant(previewValue),
icon: .constant("")
)
.frame(width: 200, height: 200)
HStack(spacing: 8) {
Button("Volume") {
previewType = .volume
}
.buttonStyle(.bordered)
Button("Brightness") {
previewType = .brightness
}
.buttonStyle(.bordered)
Button("Backlight") {
previewType = .backlight
}
.buttonStyle(.bordered)
}
.controlSize(.small)
Slider(value: $previewValue, in: 0...1)
.frame(width: 160)
}
.padding(.vertical, 12)
Spacer()
}
} header: {
Text("Preview")
} footer: {
Text("Adjust settings above to see changes in real-time. The actual OSD appears at the bottom center of your screen.")
.foregroundStyle(.secondary)
.font(.caption)
}
}
}
.navigationTitle("Custom OSD")
}
}
#Preview {
HUD()
}