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
|
// We use a small delay to ensure this runs after any UI cleanup
|
||||||
// (e.g., command palette restoring focus to its original surface).
|
// (e.g., command palette restoring focus to its original surface).
|
||||||
Ghostty.moveFocus(to: target, delay: 0.1)
|
Ghostty.moveFocus(to: target, delay: 0.1)
|
||||||
|
|
||||||
|
// Show a brief highlight to help the user locate the presented terminal.
|
||||||
|
target.highlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Local Events
|
// MARK: Local Events
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,9 @@ extension Ghostty {
|
||||||
BellBorderOverlay(bell: surfaceView.bell)
|
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 our surface is not healthy, then we render an error view over it.
|
||||||
if (!surfaceView.healthy) {
|
if (!surfaceView.healthy) {
|
||||||
Rectangle().fill(ghostty.config.backgroundColor)
|
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
|
// MARK: Readonly Badge
|
||||||
|
|
||||||
/// A badge overlay that indicates a surface is in readonly mode.
|
/// 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.
|
/// True when the surface is in readonly mode.
|
||||||
@Published private(set) var readonly: Bool = false
|
@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
|
// An initial size to request for a window. This will only affect
|
||||||
// then the view is moved to a new window.
|
// then the view is moved to a new window.
|
||||||
var initialSize: NSSize? = nil
|
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) {
|
@IBAction func splitRight(_ sender: Any) {
|
||||||
guard let surface = self.surface else { return }
|
guard let surface = self.surface else { return }
|
||||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue