1use gpui::{
2 App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString,
3 Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
4};
5
6actions!(example, [Tab, TabPrev, Quit]);
7
8struct Example {
9 focus_handle: FocusHandle,
10 items: Vec<(FocusHandle, &'static str)>,
11 message: SharedString,
12}
13
14impl Example {
15 fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
16 let items = vec![
17 (
18 cx.focus_handle().tab_index(1).tab_stop(true),
19 "Button with .focus() - always shows border when focused",
20 ),
21 (
22 cx.focus_handle().tab_index(2).tab_stop(true),
23 "Button with .focus_visible() - only shows border with keyboard",
24 ),
25 (
26 cx.focus_handle().tab_index(3).tab_stop(true),
27 "Button with both .focus() and .focus_visible()",
28 ),
29 ];
30
31 let focus_handle = cx.focus_handle();
32 window.focus(&focus_handle);
33
34 Self {
35 focus_handle,
36 items,
37 message: SharedString::from(
38 "Try clicking vs tabbing! Click shows no border, Tab shows border.",
39 ),
40 }
41 }
42
43 fn on_tab(&mut self, _: &Tab, window: &mut Window, _: &mut Context<Self>) {
44 window.focus_next();
45 self.message = SharedString::from("Pressed Tab - focus-visible border should appear!");
46 }
47
48 fn on_tab_prev(&mut self, _: &TabPrev, window: &mut Window, _: &mut Context<Self>) {
49 window.focus_prev();
50 self.message =
51 SharedString::from("Pressed Shift-Tab - focus-visible border should appear!");
52 }
53
54 fn on_quit(&mut self, _: &Quit, _window: &mut Window, cx: &mut Context<Self>) {
55 cx.quit();
56 }
57}
58
59impl Render for Example {
60 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
61 fn button_base(id: impl Into<ElementId>, label: &'static str) -> Stateful<Div> {
62 div()
63 .id(id)
64 .h_16()
65 .w_full()
66 .flex()
67 .justify_center()
68 .items_center()
69 .bg(gpui::rgb(0x2563eb))
70 .text_color(gpui::white())
71 .rounded_md()
72 .cursor_pointer()
73 .hover(|style| style.bg(gpui::rgb(0x1d4ed8)))
74 .child(label)
75 }
76
77 div()
78 .id("app")
79 .track_focus(&self.focus_handle)
80 .on_action(cx.listener(Self::on_tab))
81 .on_action(cx.listener(Self::on_tab_prev))
82 .on_action(cx.listener(Self::on_quit))
83 .size_full()
84 .flex()
85 .flex_col()
86 .p_8()
87 .gap_6()
88 .bg(gpui::rgb(0xf3f4f6))
89 .child(
90 div()
91 .text_2xl()
92 .font_weight(gpui::FontWeight::BOLD)
93 .text_color(gpui::rgb(0x111827))
94 .child("CSS focus-visible Demo"),
95 )
96 .child(
97 div()
98 .p_4()
99 .rounded_md()
100 .bg(gpui::rgb(0xdbeafe))
101 .text_color(gpui::rgb(0x1e3a8a))
102 .child(self.message.clone()),
103 )
104 .child(
105 div()
106 .flex()
107 .flex_col()
108 .gap_4()
109 .child(
110 div()
111 .flex()
112 .flex_col()
113 .gap_2()
114 .child(
115 div()
116 .text_sm()
117 .font_weight(gpui::FontWeight::BOLD)
118 .text_color(gpui::rgb(0x374151))
119 .child("1. Regular .focus() - always visible:"),
120 )
121 .child(
122 button_base("button1", self.items[0].1)
123 .track_focus(&self.items[0].0)
124 .focus(|style| {
125 style.border_4().border_color(gpui::rgb(0xfbbf24))
126 })
127 .on_click(cx.listener(|this, _, _, cx| {
128 this.message =
129 "Clicked button 1 - focus border is visible!".into();
130 cx.notify();
131 })),
132 ),
133 )
134 .child(
135 div()
136 .flex()
137 .flex_col()
138 .gap_2()
139 .child(
140 div()
141 .text_sm()
142 .font_weight(gpui::FontWeight::BOLD)
143 .text_color(gpui::rgb(0x374151))
144 .child("2. New .focus_visible() - only keyboard:"),
145 )
146 .child(
147 button_base("button2", self.items[1].1)
148 .track_focus(&self.items[1].0)
149 .focus_visible(|style| {
150 style.border_4().border_color(gpui::rgb(0x10b981))
151 })
152 .on_click(cx.listener(|this, _, _, cx| {
153 this.message =
154 "Clicked button 2 - no border! Try Tab instead.".into();
155 cx.notify();
156 })),
157 ),
158 )
159 .child(
160 div()
161 .flex()
162 .flex_col()
163 .gap_2()
164 .child(
165 div()
166 .text_sm()
167 .font_weight(gpui::FontWeight::BOLD)
168 .text_color(gpui::rgb(0x374151))
169 .child(
170 "3. Both .focus() (yellow) and .focus_visible() (green):",
171 ),
172 )
173 .child(
174 button_base("button3", self.items[2].1)
175 .track_focus(&self.items[2].0)
176 .focus(|style| {
177 style.border_4().border_color(gpui::rgb(0xfbbf24))
178 })
179 .focus_visible(|style| {
180 style.border_4().border_color(gpui::rgb(0x10b981))
181 })
182 .on_click(cx.listener(|this, _, _, cx| {
183 this.message =
184 "Clicked button 3 - yellow border. Tab shows green!"
185 .into();
186 cx.notify();
187 })),
188 ),
189 ),
190 )
191 }
192}
193
194fn main() {
195 Application::new().run(|cx: &mut App| {
196 cx.bind_keys([
197 KeyBinding::new("tab", Tab, None),
198 KeyBinding::new("shift-tab", TabPrev, None),
199 KeyBinding::new("cmd-q", Quit, None),
200 ]);
201
202 let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx);
203 cx.open_window(
204 WindowOptions {
205 window_bounds: Some(WindowBounds::Windowed(bounds)),
206 ..Default::default()
207 },
208 |window, cx| cx.new(|cx| Example::new(window, cx)),
209 )
210 .unwrap();
211
212 cx.activate(true);
213 });
214}