list_example.rs

  1#![cfg_attr(target_family = "wasm", no_main)]
  2
  3use gpui::{
  4    App, Bounds, Context, ListAlignment, ListState, Render, Window, WindowBounds, WindowOptions,
  5    div, list, prelude::*, px, rgb, size,
  6};
  7use gpui_platform::application;
  8
  9const ITEM_COUNT: usize = 40;
 10const SCROLLBAR_WIDTH: f32 = 12.;
 11
 12struct BottomListDemo {
 13    list_state: ListState,
 14}
 15
 16impl BottomListDemo {
 17    fn new() -> Self {
 18        Self {
 19            list_state: ListState::new(ITEM_COUNT, ListAlignment::Bottom, px(500.)).measure_all(),
 20        }
 21    }
 22}
 23
 24impl Render for BottomListDemo {
 25    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 26        let max_offset = self.list_state.max_offset_for_scrollbar().y;
 27        let current_offset = -self.list_state.scroll_px_offset_for_scrollbar().y;
 28
 29        let viewport_height = self.list_state.viewport_bounds().size.height;
 30
 31        let raw_fraction = if max_offset > px(0.) {
 32            current_offset / max_offset
 33        } else {
 34            0.
 35        };
 36
 37        let total_height = viewport_height + max_offset;
 38        let thumb_height = if total_height > px(0.) {
 39            px(viewport_height.as_f32() * viewport_height.as_f32() / total_height.as_f32())
 40                .max(px(30.))
 41        } else {
 42            px(30.)
 43        };
 44
 45        let track_space = viewport_height - thumb_height;
 46        let thumb_top = track_space * raw_fraction;
 47
 48        let bug_detected = raw_fraction > 1.0;
 49
 50        div()
 51            .size_full()
 52            .bg(rgb(0xFFFFFF))
 53            .flex()
 54            .flex_col()
 55            .p_4()
 56            .gap_2()
 57            .child(
 58                div()
 59                    .text_sm()
 60                    .flex()
 61                    .flex_col()
 62                    .gap_1()
 63                    .child(format!(
 64                        "offset: {:.0} / max: {:.0} | fraction: {:.3}",
 65                        current_offset.as_f32(),
 66                        max_offset.as_f32(),
 67                        raw_fraction,
 68                    ))
 69                    .child(
 70                        div()
 71                            .text_color(if bug_detected {
 72                                rgb(0xCC0000)
 73                            } else {
 74                                rgb(0x008800)
 75                            })
 76                            .child(if bug_detected {
 77                                format!(
 78                                    "BUG: fraction is {:.3} (> 1.0) — thumb is off-track!",
 79                                    raw_fraction
 80                                )
 81                            } else {
 82                                "OK: fraction <= 1.0 — thumb is within track.".to_string()
 83                            }),
 84                    ),
 85            )
 86            .child(
 87                div()
 88                    .flex_1()
 89                    .flex()
 90                    .flex_row()
 91                    .overflow_hidden()
 92                    .border_1()
 93                    .border_color(rgb(0xCCCCCC))
 94                    .rounded_sm()
 95                    .child(
 96                        list(self.list_state.clone(), |index, _window, _cx| {
 97                            let height = px(30. + (index % 5) as f32 * 10.);
 98                            div()
 99                                .h(height)
100                                .w_full()
101                                .flex()
102                                .items_center()
103                                .px_3()
104                                .border_b_1()
105                                .border_color(rgb(0xEEEEEE))
106                                .bg(if index % 2 == 0 {
107                                    rgb(0xFAFAFA)
108                                } else {
109                                    rgb(0xFFFFFF)
110                                })
111                                .text_sm()
112                                .child(format!("Item {index}"))
113                                .into_any()
114                        })
115                        .flex_1(),
116                    )
117                    // Scrollbar track
118                    .child(
119                        div()
120                            .w(px(SCROLLBAR_WIDTH))
121                            .h_full()
122                            .flex_shrink_0()
123                            .bg(rgb(0xE0E0E0))
124                            .relative()
125                            .child(
126                                // Thumb — position is unclamped to expose the bug
127                                div()
128                                    .absolute()
129                                    .top(thumb_top)
130                                    .w_full()
131                                    .h(thumb_height)
132                                    .bg(if bug_detected {
133                                        rgb(0xCC0000)
134                                    } else {
135                                        rgb(0x888888)
136                                    })
137                                    .rounded_sm(),
138                            ),
139                    ),
140            )
141    }
142}
143
144fn run_example() {
145    application().run(|cx: &mut App| {
146        let bounds = Bounds::centered(None, size(px(400.), px(500.)), cx);
147        cx.open_window(
148            WindowOptions {
149                focus: true,
150                window_bounds: Some(WindowBounds::Windowed(bounds)),
151                ..Default::default()
152            },
153            |_, cx| cx.new(|_| BottomListDemo::new()),
154        )
155        .unwrap();
156        cx.activate(true);
157    });
158}
159
160#[cfg(not(target_family = "wasm"))]
161fn main() {
162    run_example();
163}
164
165#[cfg(target_family = "wasm")]
166#[wasm_bindgen::prelude::wasm_bindgen(start)]
167pub fn start() {
168    gpui_platform::web_init();
169    run_example();
170}