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