Skip to main content

ui_shop/
ui_shop.rs

1//! A retained shop: item grid, scrolling, input and modal confirmation.
2use ponko::*;
3use ui_layout::prelude::*;
4
5fn fixed(width: f32, height: f32) -> Style {
6    Style {
7        size: Size {
8            width: length(width),
9            height: length(height),
10        },
11        flex_shrink: 0.0,
12        ..Default::default()
13    }
14}
15fn main() {
16    Game::new("Shop", 900, 700).run(|ctx| {
17        // ANCHOR: shop_tree
18        let ui = &mut ctx.ui_retained;
19        let root = ui.add(
20            UiWidget::Panel,
21            Style {
22                size: Size {
23                    width: percent(1.0),
24                    height: percent(1.0),
25                },
26                flex_direction: FlexDirection::Column,
27                padding: ui_layout::Rect::length(24.0),
28                gap: Size::length(12.0),
29                ..Default::default()
30            },
31        );
32        let title = ui.add_child(root, UiWidget::Label, fixed(400.0, 32.0));
33        ui.set_text(title, "General store — choose an item");
34        let search = ui.add_child(root, UiWidget::TextInput, fixed(360.0, 36.0));
35        ui.set_text(search, "Your order note");
36        let scroll = ui.add_child(
37            root,
38            UiWidget::Scroll,
39            Style {
40                size: Size {
41                    width: percent(1.0),
42                    height: length(360.0),
43                },
44                min_size: Size::length(0.0),
45                flex_shrink: 1.0,
46                ..Default::default()
47            },
48        );
49        let grid = ui.add_child(
50            scroll,
51            UiWidget::Panel,
52            Style {
53                display: Display::Grid,
54                size: Size {
55                    width: percent(0.95),
56                    height: auto(),
57                },
58                grid_template_columns: vec![fr(1.0), fr(1.0), fr(1.0)],
59                grid_auto_rows: vec![length(76.0)],
60                gap: Size::length(8.0),
61                flex_shrink: 0.0,
62                ..Default::default()
63            },
64        );
65        let mut items = Vec::new();
66        for i in 0..36 {
67            let item = ui.add_child(
68                grid,
69                UiWidget::Button,
70                Style {
71                    min_size: Size::length(0.0),
72                    padding: ui_layout::Rect::length(8.0),
73                    ..Default::default()
74                },
75            );
76            ui.set_text(item, format!("Item {}\n{} coins", i + 1, (i + 1) * 3));
77            ui.set_paint(
78                item,
79                UiPaint {
80                    wrap: true,
81                    border_width: 1.0,
82                    ..Default::default()
83                },
84            );
85            items.push(item);
86        }
87        let gift = ui.add_child(root, UiWidget::Toggle, fixed(220.0, 28.0));
88        ui.set_text(gift, "Gift wrap");
89        let buy = ui.add_child(root, UiWidget::Button, fixed(220.0, 40.0));
90        ui.set_text(buy, "Buy selected item");
91        ui.set_enabled(buy, false);
92        ui.push_screen(root, false);
93        // ANCHOR_END: shop_tree
94
95        // ANCHOR: shop_dialog
96        // A detached screen can be reused after it is popped.
97        let dialog = ui.add(
98            UiWidget::Panel,
99            Style {
100                size: Size {
101                    width: length(360.0),
102                    height: length(180.0),
103                },
104                margin: ui_layout::Rect {
105                    left: auto(),
106                    right: auto(),
107                    top: length(180.0),
108                    bottom: auto(),
109                },
110                padding: ui_layout::Rect::length(20.0),
111                flex_direction: FlexDirection::Column,
112                gap: Size::length(12.0),
113                ..Default::default()
114            },
115        );
116        let message = ui.add_child(dialog, UiWidget::Label, fixed(320.0, 40.0));
117        let confirm = ui.add_child(dialog, UiWidget::Button, fixed(240.0, 36.0));
118        ui.set_text(confirm, "Confirm purchase");
119        let cancel = ui.add_child(dialog, UiWidget::Button, fixed(240.0, 30.0));
120        ui.set_text(cancel, "Back");
121        // ANCHOR_END: shop_dialog
122
123        let mut selected = None;
124        // ANCHOR: shop_events
125        move |ctx: &mut Ctx| {
126            // UI input/layout ran before this closure. Drain first, then mutate.
127            let mut clicked = None;
128            for event in ctx.ui_retained.events() {
129                if let UiEvent::Clicked(id) = event {
130                    clicked = Some(id);
131                }
132            }
133            if let Some(id) = clicked {
134                if let Some(index) = items.iter().position(|&item| item == id) {
135                    selected = Some(index);
136                    ctx.ui_retained.set_enabled(buy, true);
137                    ctx.ui_retained.set_text(buy, format!("Buy item {}", index + 1));
138                } else if id == buy {
139                    ctx.ui_retained
140                        .set_text(message, format!("Buy item {}?", selected.unwrap() + 1));
141                    ctx.ui_retained.push_screen(dialog, true);
142                } else if id == confirm || id == cancel {
143                    ctx.ui_retained.pop_screen();
144                    if id == confirm {
145                        ctx.ui_retained.set_text(title, "Purchase complete!");
146                    }
147                }
148            }
149            // Tab/arrows or d-pad navigate; Enter/South confirms; Escape/East
150            // pops the top screen. A modal screen also owns the outside pointer.
151            if !ctx.mouse_over_ui() { /* world pointer actions */ }
152            if !ctx.ui_wants_keyboard() { /* world keyboard/gamepad actions */ }
153        }
154        // ANCHOR_END: shop_events
155    });
156}