1#![cfg_attr(target_family = "wasm", no_main)]
2
3use gpui::{
4 Anchor, AnchoredPositionMode, App, Axis, Bounds, Context, Half as _, InteractiveElement,
5 ParentElement, Pixels, Point, Render, SharedString, Size, Window, WindowBounds, WindowOptions,
6 anchored, deferred, div, point, prelude::*, px, rgb, size,
7};
8use gpui_platform::application;
9
10struct AnchorDemo {
11 hovered_button: Option<usize>,
12}
13
14struct ButtonDemo {
15 label: SharedString,
16 corner: Option<Anchor>,
17}
18
19fn resolved_position(corner: Anchor, button_size: Size<Pixels>) -> Point<Pixels> {
20 let offset = Point {
21 x: px(0.),
22 y: -button_size.height,
23 };
24
25 offset
26 + match corner.other_side_along(Axis::Vertical) {
27 Anchor::TopLeft => point(px(0.0), px(0.0)),
28 Anchor::TopCenter => point(button_size.width.half(), px(0.0)),
29 Anchor::TopRight => point(button_size.width, px(0.0)),
30 Anchor::LeftCenter => point(button_size.width, button_size.height.half()),
31 Anchor::RightCenter => point(px(0.), button_size.height.half()),
32 Anchor::BottomLeft => point(px(0.0), button_size.height),
33 Anchor::BottomCenter => point(button_size.width / 2.0, button_size.height),
34 Anchor::BottomRight => point(button_size.width, button_size.height),
35 }
36}
37
38impl AnchorDemo {
39 fn buttons() -> Vec<ButtonDemo> {
40 vec![
41 ButtonDemo {
42 label: "TopLeft".into(),
43 corner: Some(Anchor::TopLeft),
44 },
45 ButtonDemo {
46 label: "TopCenter".into(),
47 corner: Some(Anchor::TopCenter),
48 },
49 ButtonDemo {
50 label: "TopRight".into(),
51 corner: Some(Anchor::TopRight),
52 },
53 ButtonDemo {
54 label: "LeftCenter".into(),
55 corner: Some(Anchor::LeftCenter),
56 },
57 ButtonDemo {
58 label: "Center".into(),
59 corner: None,
60 },
61 ButtonDemo {
62 label: "RightCenter".into(),
63 corner: Some(Anchor::RightCenter),
64 },
65 ButtonDemo {
66 label: "BottomLeft".into(),
67 corner: Some(Anchor::BottomLeft),
68 },
69 ButtonDemo {
70 label: "BottomCenter".into(),
71 corner: Some(Anchor::BottomCenter),
72 },
73 ButtonDemo {
74 label: "BottomRight".into(),
75 corner: Some(Anchor::BottomRight),
76 },
77 ]
78 }
79}
80
81impl Render for AnchorDemo {
82 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
83 let buttons = Self::buttons();
84 let button_size = size(px(120.0), px(65.0));
85
86 div()
87 .flex()
88 .flex_col()
89 .size_full()
90 .items_center()
91 .justify_center()
92 .bg(gpui::white())
93 .gap_4()
94 .p_10()
95 .child("Popover with Anchor")
96 .child(
97 div()
98 .size_128()
99 .grid()
100 .grid_cols(3)
101 .gap_6()
102 .relative()
103 .children(buttons.iter().enumerate().map(|(index, button)| {
104 let is_hovered = self.hovered_button == Some(index);
105 let is_hoverable = button.corner.is_some();
106 div()
107 .relative()
108 .child(
109 div()
110 .id(("button", index))
111 .w(button_size.width)
112 .h(button_size.height)
113 .flex()
114 .items_center()
115 .justify_center()
116 .bg(gpui::white())
117 .when(is_hoverable, |this| {
118 this.border_1()
119 .rounded_lg()
120 .border_color(gpui::black())
121 .hover(|style| {
122 style.bg(gpui::black()).text_color(gpui::white())
123 })
124 .on_hover(cx.listener(
125 move |this, hovered, _window, cx| {
126 if *hovered {
127 this.hovered_button = Some(index);
128 } else if this.hovered_button == Some(index) {
129 this.hovered_button = None;
130 }
131 cx.notify();
132 },
133 ))
134 .child(button.label.clone())
135 }),
136 )
137 .when_some(self.hovered_button.filter(|_| is_hovered), |this, index| {
138 let button = &buttons[index];
139 let Some(corner) = button.corner else {
140 return this;
141 };
142
143 let position = resolved_position(corner, button_size);
144 this.child(deferred(
145 anchored()
146 .anchor(corner)
147 .position(position)
148 .position_mode(AnchoredPositionMode::Local)
149 .snap_to_window()
150 .child(
151 div()
152 .py_0p5()
153 .px_2()
154 .bg(gpui::black().opacity(0.75))
155 .text_color(rgb(0xffffff))
156 .rounded_sm()
157 .shadow_sm()
158 .min_w(px(100.0))
159 .text_sm()
160 .child(button.label.clone()),
161 ),
162 ))
163 })
164 })),
165 )
166 }
167}
168
169fn run_example() {
170 application().run(|cx: &mut App| {
171 cx.open_window(
172 WindowOptions {
173 window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
174 None,
175 size(px(750.), px(600.)),
176 cx,
177 ))),
178 ..Default::default()
179 },
180 |_, cx| {
181 cx.new(|_| AnchorDemo {
182 hovered_button: None,
183 })
184 },
185 )
186 .unwrap();
187 cx.activate(true);
188 });
189}
190
191#[cfg(not(target_family = "wasm"))]
192fn main() {
193 run_example();
194}
195
196#[cfg(target_family = "wasm")]
197#[wasm_bindgen::prelude::wasm_bindgen(start)]
198pub fn start() {
199 gpui_platform::web_init();
200 run_example();
201}