Skip to content
Merged
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
18 changes: 18 additions & 0 deletions Sources/CodeIsland/L10n.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ final class L10n: ObservableObject {
"max_visible_sessions_desc": "Sessions beyond this limit will be scrollable",
"collapsed_width_scale": "Island Width",
"collapsed_width_scale_desc": "Scale the collapsed island width on non-notch displays",
"notch_height_mode": "Top Bar Height",
"notch_height_mode_desc": "Align the panel to the real notch height, menu bar height, or a custom value",
"notch_height_match_notch": "Match Notch Height",
"notch_height_match_menubar": "Match Menu Bar Height",
"notch_height_custom": "Custom Height",
"custom_notch_height": "Custom Height",
"default": "Default",
"content": "Content",
"content_font_size": "Content Font Size",
Expand Down Expand Up @@ -321,6 +327,12 @@ final class L10n: ObservableObject {
"max_visible_sessions_desc": "超出数量的会话将通过滚动查看",
"collapsed_width_scale": "灵动岛宽度",
"collapsed_width_scale_desc": "调整非刘海屏幕上灵动岛的收起宽度",
"notch_height_mode": "顶部高度对齐",
"notch_height_mode_desc": "让面板与真实 notch 高度、菜单栏高度或自定义值对齐",
"notch_height_match_notch": "对齐 notch 高度",
"notch_height_match_menubar": "对齐菜单栏高度",
"notch_height_custom": "自定义高度",
"custom_notch_height": "自定义高度",
"default": "默认",
"content": "内容",
"content_font_size": "内容字体大小",
Expand Down Expand Up @@ -534,6 +546,12 @@ final class L10n: ObservableObject {
"max_visible_sessions_desc": "Bu sınırdan fazla oturumlar kaydırılabilir olacak",
"collapsed_width_scale": "Ada Genişliği",
"collapsed_width_scale_desc": "Çentiksiz ekranlarda daraltılmış ada genişliğini ölçekle",
"notch_height_mode": "Üst Çubuk Yüksekliği",
"notch_height_mode_desc": "Paneli gerçek çentik yüksekliğine, menü çubuğu yüksekliğine veya özel bir değere hizala",
"notch_height_match_notch": "Çentik Yüksekliğiyle Eşleştir",
"notch_height_match_menubar": "Menü Çubuğu Yüksekliğiyle Eşleştir",
"notch_height_custom": "Özel Yükseklik",
"custom_notch_height": "Özel Yükseklik",
"default": "Varsayılan",
"content": "İçerik",
"content_font_size": "İçerik Yazı Boyutu",
Expand Down
14 changes: 12 additions & 2 deletions Sources/CodeIsland/PanelWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ class PanelWindowController: NSObject, NSWindowDelegate {
private var dragStartPanelX: CGFloat?
private var isDraggingPanel = false
private var localDragMonitor: Any?
private var lastDisplayChoice = ""
private var lastNotchHeightMode = SettingsDefaults.notchHeightMode
private var lastCustomNotchHeight = SettingsDefaults.customNotchHeight

init(appState: AppState) {
self.appState = appState
Expand Down Expand Up @@ -375,10 +378,10 @@ class PanelWindowController: NSObject, NSWindowDelegate {
}
}

private var lastDisplayChoice = ""

private func observeSettingsChanges() {
lastDisplayChoice = SettingsManager.shared.displayChoice
lastNotchHeightMode = SettingsManager.shared.notchHeightMode.rawValue
lastCustomNotchHeight = SettingsManager.shared.customNotchHeight
let observer = NotificationCenter.default.addObserver(
forName: UserDefaults.didChangeNotification,
object: nil,
Expand All @@ -387,10 +390,17 @@ class PanelWindowController: NSObject, NSWindowDelegate {
Task { @MainActor in
guard let self = self else { return }
let newChoice = SettingsManager.shared.displayChoice
let newHeightMode = SettingsManager.shared.notchHeightMode.rawValue
let newCustomHeight = SettingsManager.shared.customNotchHeight
if newChoice != self.lastDisplayChoice {
self.lastDisplayChoice = newChoice
self.refreshCurrentScreen(forceRebuild: true)
self.configureAutoScreenPolling()
} else if newHeightMode != self.lastNotchHeightMode
|| abs(newCustomHeight - self.lastCustomNotchHeight) > 0.001 {
self.lastNotchHeightMode = newHeightMode
self.lastCustomNotchHeight = newCustomHeight
self.refreshCurrentScreen(forceRebuild: true)
} else {
self.updateVisibility()
self.updatePosition()
Expand Down
40 changes: 30 additions & 10 deletions Sources/CodeIsland/ScreenDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,41 @@ struct ScreenDetector {

/// Height of the notch/menu bar area for a specific screen
static func topBarHeight(for screen: NSScreen) -> CGFloat {
let realNotchHeight: CGFloat
if #available(macOS 12.0, *) {
let real = screen.safeAreaInsets.top
if real > 0 { return real }
realNotchHeight = screen.safeAreaInsets.top
} else {
realNotchHeight = 0
}

// Menu bar height — only present on the screen that has it
let menuBarHeight = screen.frame.maxY - screen.visibleFrame.maxY
// On the primary screen, this is ~25pt (non-notch) or ~37pt (notch)
// On secondary screens without menu bar, this is 0
if menuBarHeight > 5 { return menuBarHeight }
// Fallback: use main screen's menu bar height, or default 25
if let main = NSScreen.main {
let mainMenuBar = main.frame.maxY - main.visibleFrame.maxY
if mainMenuBar > 5 { return mainMenuBar }

func resolvedMenuBarHeight() -> CGFloat {
// On the primary screen, this is ~25pt (non-notch) or ~37pt (notch)
// On secondary screens without menu bar, this is 0
if menuBarHeight > 5 { return menuBarHeight }
if let main = NSScreen.main {
let mainMenuBar = main.frame.maxY - main.visibleFrame.maxY
if mainMenuBar > 5 { return mainMenuBar }
}
return 25
}

let modeRaw = UserDefaults.standard.string(forKey: SettingsKey.notchHeightMode) ?? SettingsDefaults.notchHeightMode
let mode = NotchHeightMode(rawValue: modeRaw) ?? .matchNotch

switch mode {
case .matchNotch:
if realNotchHeight > 0 { return realNotchHeight }
return resolvedMenuBarHeight()
case .matchMenuBar:
return resolvedMenuBarHeight()
case .custom:
let custom = UserDefaults.standard.double(forKey: SettingsKey.customNotchHeight)
let fallback = SettingsDefaults.customNotchHeight
return CGFloat(max(15, min(custom > 0 ? custom : fallback, 60)))
}
return 25
}

/// Height of the notch area — returns menu bar height on non-notch screens
Expand Down
25 changes: 25 additions & 0 deletions Sources/CodeIsland/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ enum AppVersion {
}
}

enum NotchHeightMode: String, CaseIterable {
case matchNotch = "matchNotch"
case matchMenuBar = "matchMenuBar"
case custom = "custom"
}

enum SettingsKey {
// Language
static let appLanguage = "appLanguage" // "system", "en", "zh", "tr"
Expand All @@ -33,6 +39,8 @@ enum SettingsKey {
static let contentFontSize = "contentFontSize"
static let aiMessageLines = "aiMessageLines"
static let showAgentDetails = "showAgentDetails"
static let notchHeightMode = "notchHeightMode"
static let customNotchHeight = "customNotchHeight"

// Sound
static let soundEnabled = "soundEnabled"
Expand Down Expand Up @@ -87,6 +95,8 @@ struct SettingsDefaults {
static let contentFontSize = 11
static let aiMessageLines = 1
static let showAgentDetails = false
static let notchHeightMode = NotchHeightMode.matchNotch.rawValue
static let customNotchHeight = 37.0

static let soundEnabled = false
static let soundVolume = 50
Expand Down Expand Up @@ -131,6 +141,8 @@ class SettingsManager {
SettingsKey.contentFontSize: SettingsDefaults.contentFontSize,
SettingsKey.aiMessageLines: SettingsDefaults.aiMessageLines,
SettingsKey.showAgentDetails: SettingsDefaults.showAgentDetails,
SettingsKey.notchHeightMode: SettingsDefaults.notchHeightMode,
SettingsKey.customNotchHeight: SettingsDefaults.customNotchHeight,
SettingsKey.soundEnabled: SettingsDefaults.soundEnabled,
SettingsKey.soundVolume: SettingsDefaults.soundVolume,
SettingsKey.soundSessionStart: SettingsDefaults.soundSessionStart,
Expand Down Expand Up @@ -215,6 +227,19 @@ class SettingsManager {
set { defaults.set(newValue, forKey: SettingsKey.showAgentDetails) }
}

var notchHeightMode: NotchHeightMode {
get {
let raw = defaults.string(forKey: SettingsKey.notchHeightMode) ?? SettingsDefaults.notchHeightMode
return NotchHeightMode(rawValue: raw) ?? .matchNotch
}
set { defaults.set(newValue.rawValue, forKey: SettingsKey.notchHeightMode) }
}

var customNotchHeight: Double {
get { defaults.double(forKey: SettingsKey.customNotchHeight) }
set { defaults.set(newValue, forKey: SettingsKey.customNotchHeight) }
}

var maxToolHistory: Int {
get { defaults.integer(forKey: SettingsKey.maxToolHistory) }
set { defaults.set(newValue, forKey: SettingsKey.maxToolHistory) }
Expand Down
30 changes: 30 additions & 0 deletions Sources/CodeIsland/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,15 @@ private struct AppearancePage: View {
@AppStorage(SettingsKey.showAgentDetails) private var showAgentDetails = SettingsDefaults.showAgentDetails
@AppStorage(SettingsKey.showToolStatus) private var showToolStatus = SettingsDefaults.showToolStatus
@AppStorage(SettingsKey.collapsedWidthScale) private var collapsedWidthScale = SettingsDefaults.collapsedWidthScale
@AppStorage(SettingsKey.notchHeightMode) private var notchHeightModeRaw = SettingsDefaults.notchHeightMode
@AppStorage(SettingsKey.customNotchHeight) private var customNotchHeight = SettingsDefaults.customNotchHeight

private var notchHeightMode: Binding<NotchHeightMode> {
Binding(
get: { NotchHeightMode(rawValue: notchHeightModeRaw) ?? .matchNotch },
set: { notchHeightModeRaw = $0.rawValue }
)
}

var body: some View {
Form {
Expand Down Expand Up @@ -700,6 +709,27 @@ private struct AppearancePage: View {
.font(.caption)
.foregroundStyle(.secondary)
}
VStack(alignment: .leading, spacing: 4) {
Picker(selection: notchHeightMode) {
Text(l10n["notch_height_match_notch"]).tag(NotchHeightMode.matchNotch)
Text(l10n["notch_height_match_menubar"]).tag(NotchHeightMode.matchMenuBar)
Text(l10n["notch_height_custom"]).tag(NotchHeightMode.custom)
} label: {
Text(l10n["notch_height_mode"])
Text(l10n["notch_height_mode_desc"])
}

if notchHeightMode.wrappedValue == .custom {
HStack {
Text(l10n["custom_notch_height"])
Spacer()
Text("\(Int(customNotchHeight.rounded()))pt")
.foregroundStyle(.secondary)
.monospacedDigit()
}
Slider(value: $customNotchHeight, in: 15...60, step: 1)
}
}
}

Section(l10n["content"]) {
Expand Down