macos: show a highlight animation when a terminal is presented
parent
d23f7e051f
commit
e1356538ac
|
|
@ -715,6 +715,9 @@ class BaseTerminalController: NSWindowController,
|
|||
// We use a small delay to ensure this runs after any UI cleanup
|
||||
// (e.g., command palette restoring focus to its original surface).
|
||||
Ghostty.moveFocus(to: target, delay: 0.1)
|
||||
|
||||
// Show a brief highlight to help the user locate the presented terminal.
|
||||
target.highlight()
|
||||
}
|
||||
|
||||
// MARK: Local Events
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ extension Ghostty {
|
|||
|
||||
// True if we're hovering over the left URL view, so we can show it on the right.
|
||||
@State private var isHoveringURLLeft: Bool = false
|
||||
|
||||
|
||||
#if canImport(AppKit)
|
||||
// Observe SecureInput to detect when its enabled
|
||||
@ObservedObject private var secureInput = SecureInput.shared
|
||||
|
|
@ -219,6 +219,9 @@ extension Ghostty {
|
|||
BellBorderOverlay(bell: surfaceView.bell)
|
||||
}
|
||||
|
||||
// Show a highlight effect when this surface needs attention
|
||||
HighlightOverlay(highlighted: surfaceView.highlighted)
|
||||
|
||||
// If our surface is not healthy, then we render an error view over it.
|
||||
if (!surfaceView.healthy) {
|
||||
Rectangle().fill(ghostty.config.backgroundColor)
|
||||
|
|
@ -242,6 +245,7 @@ extension Ghostty {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -764,6 +768,62 @@ extension Ghostty {
|
|||
}
|
||||
}
|
||||
|
||||
/// Visual overlay that briefly highlights a surface to draw attention to it.
|
||||
/// Uses a soft, soothing highlight with a pulsing border effect.
|
||||
struct HighlightOverlay: View {
|
||||
let highlighted: Bool
|
||||
|
||||
@State private var borderPulse: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
gradient: Gradient(colors: [
|
||||
Color.accentColor.opacity(0.12),
|
||||
Color.accentColor.opacity(0.03),
|
||||
Color.clear
|
||||
]),
|
||||
center: .center,
|
||||
startRadius: 0,
|
||||
endRadius: 2000
|
||||
)
|
||||
)
|
||||
|
||||
Rectangle()
|
||||
.strokeBorder(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [
|
||||
Color.accentColor.opacity(0.8),
|
||||
Color.accentColor.opacity(0.5),
|
||||
Color.accentColor.opacity(0.8)
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: borderPulse ? 4 : 2
|
||||
)
|
||||
.shadow(color: Color.accentColor.opacity(borderPulse ? 0.8 : 0.6), radius: borderPulse ? 12 : 8, x: 0, y: 0)
|
||||
.shadow(color: Color.accentColor.opacity(borderPulse ? 0.5 : 0.3), radius: borderPulse ? 24 : 16, x: 0, y: 0)
|
||||
}
|
||||
.allowsHitTesting(false)
|
||||
.opacity(highlighted ? 1.0 : 0.0)
|
||||
.animation(.easeOut(duration: 0.4), value: highlighted)
|
||||
.onChange(of: highlighted) { newValue in
|
||||
if newValue {
|
||||
withAnimation(.easeInOut(duration: 0.4).repeatForever(autoreverses: true)) {
|
||||
borderPulse = true
|
||||
}
|
||||
} else {
|
||||
withAnimation(.easeOut(duration: 0.4)) {
|
||||
borderPulse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Readonly Badge
|
||||
|
||||
/// A badge overlay that indicates a surface is in readonly mode.
|
||||
|
|
|
|||
|
|
@ -126,6 +126,9 @@ extension Ghostty {
|
|||
/// True when the surface is in readonly mode.
|
||||
@Published private(set) var readonly: Bool = false
|
||||
|
||||
/// True when the surface should show a highlight effect (e.g., when presented via goto_split).
|
||||
@Published private(set) var highlighted: Bool = false
|
||||
|
||||
// An initial size to request for a window. This will only affect
|
||||
// then the view is moved to a new window.
|
||||
var initialSize: NSSize? = nil
|
||||
|
|
@ -1523,6 +1526,14 @@ extension Ghostty {
|
|||
}
|
||||
}
|
||||
|
||||
/// Triggers a brief highlight animation on this surface.
|
||||
func highlight() {
|
||||
highlighted = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in
|
||||
self?.highlighted = false
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func splitRight(_ sender: Any) {
|
||||
guard let surface = self.surface else { return }
|
||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
||||
|
|
|
|||
Loading…
Reference in New Issue