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