Files
zed/crates/gpui/examples/tab_stop.rs
Xipeng Jin f084e20c56 Fix stale pending keybinding indicators on focus change (#44678)
Closes #ISSUE

Problem:

- The status bar’s pending keystroke indicator (shown next to --NORMAL--
in Vim mode) didn’t clear when focus moved to another context, e.g.
hitting g in the editor then clicking the Git panel. The keymap state
correctly canceled the prefix, but observers that render the indicator
never received a “pending input changed” notification, so the UI kept
showing stale prefixes until a new keystroke occurred.

Fix:

- The change introduces a `pending_input_changed_queued` flag and a new
helper `notify_pending_input_if_needed` which will flushes the queued
notification as soon as we have an App context. The
`pending_input_changed` now resets the flag after notifying subscribers.

Before:


https://github.com/user-attachments/assets/7bec4c34-acbf-42bd-b0d1-88df5ff099aa

After:



https://github.com/user-attachments/assets/2264dc93-3405-4d63-ad8f-50ada6733ae7



Release Notes:

- Fixed: pending keybinding prefixes on the status bar now clear
immediately when focus moves to another panel or UI context.

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-12-17 16:51:16 +00:00

156 lines
5.2 KiB
Rust

use gpui::{
App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString,
Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
};
actions!(example, [Tab, TabPrev]);
struct Example {
focus_handle: FocusHandle,
items: Vec<FocusHandle>,
message: SharedString,
}
impl Example {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let items = vec![
cx.focus_handle().tab_index(1).tab_stop(true),
cx.focus_handle().tab_index(2).tab_stop(true),
cx.focus_handle().tab_index(3).tab_stop(true),
cx.focus_handle(),
cx.focus_handle().tab_index(2).tab_stop(true),
];
let focus_handle = cx.focus_handle();
window.focus(&focus_handle, cx);
Self {
focus_handle,
items,
message: SharedString::from("Press `Tab`, `Shift-Tab` to switch focus."),
}
}
fn on_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
window.focus_next(cx);
self.message = SharedString::from("You have pressed `Tab`.");
}
fn on_tab_prev(&mut self, _: &TabPrev, window: &mut Window, cx: &mut Context<Self>) {
window.focus_prev(cx);
self.message = SharedString::from("You have pressed `Shift-Tab`.");
}
}
impl Render for Example {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn tab_stop_style<T: Styled>(this: T) -> T {
this.border_3().border_color(gpui::blue())
}
fn button(id: impl Into<ElementId>) -> Stateful<Div> {
div()
.id(id)
.h_10()
.flex_1()
.flex()
.justify_center()
.items_center()
.border_1()
.border_color(gpui::black())
.bg(gpui::black())
.text_color(gpui::white())
.focus(tab_stop_style)
.shadow_sm()
}
div()
.id("app")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_tab))
.on_action(cx.listener(Self::on_tab_prev))
.size_full()
.flex()
.flex_col()
.p_4()
.gap_3()
.bg(gpui::white())
.text_color(gpui::black())
.child(self.message.clone())
.children(
self.items
.clone()
.into_iter()
.enumerate()
.map(|(ix, item_handle)| {
div()
.id(("item", ix))
.track_focus(&item_handle)
.h_10()
.w_full()
.flex()
.justify_center()
.items_center()
.border_1()
.border_color(gpui::black())
.when(
item_handle.tab_stop && item_handle.is_focused(window),
tab_stop_style,
)
.map(|this| match item_handle.tab_stop {
true => this
.hover(|this| this.bg(gpui::black().opacity(0.1)))
.child(format!("tab_index: {}", item_handle.tab_index)),
false => this.opacity(0.4).child("tab_stop: false"),
})
}),
)
.child(
div()
.flex()
.flex_row()
.gap_3()
.items_center()
.child(
button("el1")
.tab_index(4)
.child("Button 1")
.on_click(cx.listener(|this, _, _, cx| {
this.message = "You have clicked Button 1.".into();
cx.notify();
})),
)
.child(
button("el2")
.tab_index(5)
.child("Button 2")
.on_click(cx.listener(|this, _, _, cx| {
this.message = "You have clicked Button 2.".into();
cx.notify();
})),
),
)
}
}
fn main() {
Application::new().run(|cx: &mut App| {
cx.bind_keys([
KeyBinding::new("tab", Tab, None),
KeyBinding::new("shift-tab", TabPrev, None),
]);
let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|window, cx| cx.new(|cx| Example::new(window, cx)),
)
.unwrap();
cx.activate(true);
});
}