Update sidebar badges and key modifier translation
This commit is contained in:
parent
5acb4e47b1
commit
b715f0cebe
3 changed files with 72 additions and 17 deletions
|
|
@ -137,6 +137,7 @@ struct VerticalTabsSidebar: View {
|
|||
|
||||
struct TabItemView: View {
|
||||
@EnvironmentObject var tabManager: TabManager
|
||||
@EnvironmentObject var notificationStore: TerminalNotificationStore
|
||||
@ObservedObject var tab: Tab
|
||||
@Binding var selection: SidebarSelection
|
||||
@State private var isHovering = false
|
||||
|
|
@ -147,9 +148,17 @@ struct TabItemView: View {
|
|||
|
||||
var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "terminal")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(isSelected ? .white : .secondary)
|
||||
let unreadCount = notificationStore.unreadCount(forTabId: tab.id)
|
||||
if unreadCount > 0 {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(isSelected ? Color.white.opacity(0.25) : Color.accentColor)
|
||||
Text("\(unreadCount)")
|
||||
.font(.system(size: 9, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
||||
|
||||
Text(tab.title)
|
||||
.font(.system(size: 12))
|
||||
|
|
@ -159,15 +168,15 @@ struct TabItemView: View {
|
|||
|
||||
Spacer()
|
||||
|
||||
if isHovering || isSelected {
|
||||
Button(action: { tabManager.closeTab(tab) }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 9, weight: .medium))
|
||||
.foregroundColor(isSelected ? .white.opacity(0.7) : .secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.opacity(tabManager.tabs.count > 1 ? 1 : 0)
|
||||
Button(action: { tabManager.closeTab(tab) }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 9, weight: .medium))
|
||||
.foregroundColor(isSelected ? .white.opacity(0.7) : .secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(width: 16, height: 16)
|
||||
.opacity((isHovering || isSelected) && tabManager.tabs.count > 1 ? 1 : 0)
|
||||
.allowsHitTesting((isHovering || isSelected) && tabManager.tabs.count > 1)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 8)
|
||||
|
|
|
|||
|
|
@ -704,12 +704,54 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
|
|||
|
||||
let action = event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS
|
||||
|
||||
// Translate mods to respect Ghostty config (e.g., macos-option-as-alt)
|
||||
let translationModsGhostty = ghostty_surface_key_translation_mods(surface, modsFromEvent(event))
|
||||
var translationMods = event.modifierFlags
|
||||
for flag in [NSEvent.ModifierFlags.shift, .control, .option, .command] {
|
||||
let hasFlag: Bool
|
||||
switch flag {
|
||||
case .shift:
|
||||
hasFlag = (translationModsGhostty.rawValue & GHOSTTY_MODS_SHIFT.rawValue) != 0
|
||||
case .control:
|
||||
hasFlag = (translationModsGhostty.rawValue & GHOSTTY_MODS_CTRL.rawValue) != 0
|
||||
case .option:
|
||||
hasFlag = (translationModsGhostty.rawValue & GHOSTTY_MODS_ALT.rawValue) != 0
|
||||
case .command:
|
||||
hasFlag = (translationModsGhostty.rawValue & GHOSTTY_MODS_SUPER.rawValue) != 0
|
||||
default:
|
||||
hasFlag = translationMods.contains(flag)
|
||||
}
|
||||
if hasFlag {
|
||||
translationMods.insert(flag)
|
||||
} else {
|
||||
translationMods.remove(flag)
|
||||
}
|
||||
}
|
||||
|
||||
let translationEvent: NSEvent
|
||||
if translationMods == event.modifierFlags {
|
||||
translationEvent = event
|
||||
} else {
|
||||
translationEvent = NSEvent.keyEvent(
|
||||
with: event.type,
|
||||
location: event.locationInWindow,
|
||||
modifierFlags: translationMods,
|
||||
timestamp: event.timestamp,
|
||||
windowNumber: event.windowNumber,
|
||||
context: nil,
|
||||
characters: event.characters(byApplyingModifiers: translationMods) ?? "",
|
||||
charactersIgnoringModifiers: event.charactersIgnoringModifiers ?? "",
|
||||
isARepeat: event.isARepeat,
|
||||
keyCode: event.keyCode
|
||||
) ?? event
|
||||
}
|
||||
|
||||
// Set up text accumulator for interpretKeyEvents
|
||||
keyTextAccumulator = []
|
||||
defer { keyTextAccumulator = nil }
|
||||
|
||||
// Let the input system handle the event (for IME, dead keys, etc.)
|
||||
interpretKeyEvents([event])
|
||||
interpretKeyEvents([translationEvent])
|
||||
|
||||
// Build the key event
|
||||
var keyEvent = ghostty_input_key_s()
|
||||
|
|
@ -717,7 +759,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
|
|||
keyEvent.keycode = UInt32(event.keyCode)
|
||||
keyEvent.mods = modsFromEvent(event)
|
||||
// Control and Command never contribute to text translation
|
||||
keyEvent.consumed_mods = consumedModsFromEvent(event)
|
||||
keyEvent.consumed_mods = consumedModsFromFlags(translationMods)
|
||||
keyEvent.composing = markedText.length > 0
|
||||
keyEvent.unshifted_codepoint = unshiftedCodepointFromEvent(event)
|
||||
|
||||
|
|
@ -733,7 +775,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
|
|||
// Get the appropriate text for this key event
|
||||
// For control characters, this returns the unmodified character
|
||||
// so Ghostty's KeyEncoder can handle ctrl encoding
|
||||
if let text = textForKeyEvent(event) {
|
||||
if let text = textForKeyEvent(translationEvent) {
|
||||
text.withCString { ptr in
|
||||
keyEvent.text = ptr
|
||||
_ = ghostty_surface_key(surface, keyEvent)
|
||||
|
|
@ -789,12 +831,12 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
|
|||
/// Consumed mods are modifiers that were used for text translation.
|
||||
/// Control and Command never contribute to text translation, so they
|
||||
/// should be excluded from consumed_mods.
|
||||
private func consumedModsFromEvent(_ event: NSEvent) -> ghostty_input_mods_e {
|
||||
private func consumedModsFromFlags(_ flags: NSEvent.ModifierFlags) -> ghostty_input_mods_e {
|
||||
var mods = GHOSTTY_MODS_NONE.rawValue
|
||||
// Only include Shift and Option as potentially consumed
|
||||
// Control and Command are never consumed for text translation
|
||||
if event.modifierFlags.contains(.shift) { mods |= GHOSTTY_MODS_SHIFT.rawValue }
|
||||
if event.modifierFlags.contains(.option) { mods |= GHOSTTY_MODS_ALT.rawValue }
|
||||
if flags.contains(.shift) { mods |= GHOSTTY_MODS_SHIFT.rawValue }
|
||||
if flags.contains(.option) { mods |= GHOSTTY_MODS_ALT.rawValue }
|
||||
return ghostty_input_mods_e(rawValue: mods)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ final class TerminalNotificationStore: ObservableObject {
|
|||
notifications.filter { !$0.isRead }.count
|
||||
}
|
||||
|
||||
func unreadCount(forTabId tabId: UUID) -> Int {
|
||||
notifications.filter { $0.tabId == tabId && !$0.isRead }.count
|
||||
}
|
||||
|
||||
func addNotification(tabId: UUID, surfaceId: UUID?, title: String, body: String) {
|
||||
let isActiveTab = AppDelegate.shared?.tabManager?.selectedTabId == tabId
|
||||
let focusedSurfaceId = AppDelegate.shared?.tabManager?.focusedSurfaceId(for: tabId)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue