anchor.rs

  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}