Skip to main content

ui/
ui.rs

1use macroquad::prelude::*;
2
3use macroquad::ui::widgets::{ProgressBar, Slider};
4use macroquad::ui::{
5    hash, root_ui,
6    widgets::{self, Group},
7    Drag, Ui,
8};
9
10pub struct Slot {
11    id: u64,
12    item: Option<String>,
13}
14impl Slot {
15    fn new(id: u64) -> Slot {
16        Slot { id, item: None }
17    }
18}
19
20pub enum FittingCommand {
21    /// Remove item from this slot
22    Unfit { target_slot: u64 },
23    /// Fit item from inventory to slot
24    Fit { target_slot: u64, item: String },
25    /// Move item from one slot to another
26    Refit { target_slot: u64, origin_slot: u64 },
27}
28
29pub struct Data {
30    inventory: Vec<String>,
31    item_dragging: bool,
32    slots: Vec<(&'static str, Slot)>,
33    fit_command: Option<FittingCommand>,
34}
35impl Data {
36    pub fn new() -> Data {
37        Data {
38            inventory: vec![],
39            item_dragging: false,
40            slots: vec![
41                ("Left Mouse Button", Slot::new(hash!())),
42                ("Right Mouse Button", Slot::new(hash!())),
43                ("Middle Mouse Button", Slot::new(hash!())),
44                ("Space", Slot::new(hash!())),
45                ("\"1\"", Slot::new(hash!())),
46                ("\"2\"", Slot::new(hash!())),
47                ("\"3\"", Slot::new(hash!())),
48            ],
49            fit_command: None,
50        }
51    }
52
53    fn slots(&mut self, ui: &mut Ui) {
54        let item_dragging = &mut self.item_dragging;
55
56        let fit_command = &mut self.fit_command;
57        for (label, slot) in self.slots.iter_mut() {
58            Group::new(hash!("grp", slot.id, &label), Vec2::new(210., 55.)).ui(ui, |ui| {
59                let drag = Group::new(slot.id, Vec2::new(50., 50.))
60                    // slot without item is not draggable
61                    .draggable(slot.item.is_some())
62                    // but could be a target of drag
63                    .hoverable(*item_dragging)
64                    // and is highlighted with other color when some item is dragging
65                    .highlight(*item_dragging)
66                    .ui(ui, |ui| {
67                        if let Some(ref item) = slot.item {
68                            ui.label(Vec2::new(5., 10.), &item);
69                        }
70                    });
71
72                match drag {
73                    // there is some item in this slot and it was dragged to another slot
74                    Drag::Dropped(_, Some(id)) if slot.item.is_some() => {
75                        *fit_command = Some(FittingCommand::Refit {
76                            target_slot: id,
77                            origin_slot: slot.id,
78                        });
79                    }
80                    // there is some item in this slot and it was dragged out - unfit it
81                    Drag::Dropped(_, None) if slot.item.is_some() => {
82                        *fit_command = Some(FittingCommand::Unfit {
83                            target_slot: slot.id,
84                        });
85                    }
86                    // there is no item in this slot
87                    // this is impossible - slots without items are non-draggable
88                    Drag::Dropped(_, _) => unreachable!(),
89                    Drag::Dragging(pos, id) => {
90                        debug!("slots: pos: {:?}, id {:?}", pos, id);
91                        *item_dragging = true;
92                    }
93                    Drag::No => {}
94                }
95                ui.label(Vec2::new(60., 20.), label);
96            });
97        }
98    }
99
100    fn inventory(&mut self, ui: &mut Ui) {
101        let item_dragging = &mut self.item_dragging;
102        for (n, item) in self.inventory.iter().enumerate() {
103            let drag = Group::new(hash!("inventory", n), Vec2::new(50., 50.))
104                .draggable(true)
105                .ui(ui, |ui| {
106                    ui.label(Vec2::new(5., 10.), &item);
107                });
108
109            match drag {
110                Drag::Dropped(_, Some(id)) => {
111                    self.fit_command = Some(FittingCommand::Fit {
112                        target_slot: id,
113                        item: item.clone(),
114                    });
115                    *item_dragging = false;
116                }
117                Drag::Dropped(_, _) => {
118                    *item_dragging = false;
119                }
120                Drag::Dragging(pos, id) => {
121                    debug!("inventory: pos: {:?}, id {:?}", pos, id);
122                    *item_dragging = true;
123                }
124                _ => {}
125            }
126        }
127    }
128
129    fn set_item(&mut self, id: u64, item: Option<String>) {
130        if let Some(slot) = self.slots.iter_mut().find(|(_, slot)| slot.id == id) {
131            slot.1.item = item;
132        }
133    }
134}
135
136#[macroquad::main("UI showcase")]
137async fn main() {
138    let mut data = Data::new();
139
140    let mut data0 = String::new();
141    let mut data1 = String::new();
142
143    let mut text0 = String::new();
144    let mut text1 = String::new();
145
146    let mut number0 = 0.;
147    let mut number1 = 0.;
148    let mut number2 = 0.;
149
150    let texture: Texture2D = load_texture("examples/ferris.png").await.unwrap();
151
152    loop {
153        clear_background(WHITE);
154
155        widgets::Window::new(hash!(), vec2(400., 200.), vec2(320., 400.))
156            .label("Shop")
157            .titlebar(true)
158            .ui(&mut *root_ui(), |ui| {
159                for i in 0..30 {
160                    Group::new(hash!("shop", i), Vec2::new(300., 80.)).ui(ui, |ui| {
161                        ui.label(Vec2::new(10., 10.), &format!("Item N {i}"));
162                        ui.label(Vec2::new(260., 40.), "10/10");
163                        ui.label(Vec2::new(200., 58.), &format!("{} kr", 800));
164                        if ui.button(Vec2::new(260., 55.), "buy") {
165                            data.inventory.push(format!("Item {i}"));
166                        }
167                    });
168                }
169            });
170
171        widgets::Window::new(hash!(), vec2(100., 220.), vec2(542., 430.))
172            .label("Fitting window")
173            .titlebar(true)
174            .ui(&mut *root_ui(), |ui| {
175                Group::new(hash!(), Vec2::new(230., 400.)).ui(ui, |ui| {
176                    data.slots(ui);
177                });
178                Group::new(hash!(), Vec2::new(280., 400.)).ui(ui, |ui| {
179                    data.inventory(ui);
180                });
181            });
182
183        widgets::Window::new(hash!(), vec2(470., 50.), vec2(300., 300.))
184            .label("Megaui Showcase Window")
185            .ui(&mut *root_ui(), |ui| {
186                ui.tree_node(hash!(), "input", |ui| {
187                    ui.label(None, "Some random text");
188                    if ui.button(None, "click me") {
189                        println!("hi");
190                    }
191
192                    ui.separator();
193
194                    ui.label(None, "Some other random text");
195                    if ui.button(None, "other button") {
196                        println!("hi2");
197                    }
198
199                    ui.separator();
200
201                    ui.input_text(hash!(), "<- input text 1", &mut data0);
202                    ui.input_text(hash!(), "<- input text 2", &mut data1);
203                    ui.label(None, &format!("Text entered: \"{data0}\" and \"{data1}\""));
204
205                    ui.separator();
206                });
207                ui.tree_node(hash!(), "buttons", |ui| {
208                    widgets::Button::new(texture.clone())
209                        .size(vec2(120., 70.))
210                        .ui(ui);
211                    ui.same_line(0.);
212                    widgets::Button::new("Button").size(vec2(120., 70.)).ui(ui);
213                    widgets::Button::new("Button").size(vec2(120., 70.)).ui(ui);
214                    ui.same_line(0.);
215                    widgets::Button::new(texture.clone())
216                        .size(vec2(120., 70.))
217                        .ui(ui);
218                });
219                ui.tree_node(hash!(), "sliders and bars", |ui| {
220                    let range0 = -10f32..10f32;
221                    ui.slider(hash!(), "[-10 .. 10]", range0.clone(), &mut number0);
222                    let progress0 = number0.remap(range0.start, range0.end, 0., 1.);
223                    ProgressBar::new().label("first bar").ui(
224                        ui,
225                        progress0,
226                        format!("{:.0}%", progress0 * 100.).as_str(),
227                    );
228
229                    let range1 = 0f32..100f32;
230                    ui.slider(hash!(), "[0 .. 100]", range1.clone(), &mut number1);
231                    let progress1 = number1.remap(range1.start, range1.end, 0., 1.);
232                    ProgressBar::new().label("second bar").ui(
233                        ui,
234                        progress1,
235                        format!("{:.1}/{:.0}", number1, range1.end).as_str(),
236                    );
237
238                    let range2 = 0f32..1f32;
239                    Slider::new(hash!(), range2.clone())
240                        .label_width(200.)
241                        .label("slider with a long label")
242                        .ui(ui, &mut number2);
243                    let progress2 = number2;
244                    ProgressBar::new()
245                        .label("bar with a really long label")
246                        .label_width(240.)
247                        .ui(ui, progress2, "");
248                });
249                ui.tree_node(hash!(), "editbox 1", |ui| {
250                    ui.label(None, "This is editbox!");
251                    ui.editbox(hash!(), vec2(285., 165.), &mut text0);
252                });
253                ui.tree_node(hash!(), "editbox 2", |ui| {
254                    ui.label(None, "This is editbox!");
255                    ui.editbox(hash!(), vec2(285., 165.), &mut text1);
256                });
257            });
258
259        match data.fit_command.take() {
260            Some(FittingCommand::Unfit { target_slot }) => data.set_item(target_slot, None),
261            Some(FittingCommand::Fit { target_slot, item }) => {
262                data.set_item(target_slot, Some(item));
263            }
264            Some(FittingCommand::Refit {
265                target_slot,
266                origin_slot,
267            }) => {
268                let origin_item = data
269                    .slots
270                    .iter()
271                    .find_map(|(_, slot)| {
272                        if slot.id == origin_slot {
273                            Some(slot.item.clone())
274                        } else {
275                            None
276                        }
277                    })
278                    .flatten();
279                data.set_item(target_slot, origin_item);
280                data.set_item(origin_slot, None);
281            }
282            None => {}
283        };
284
285        next_frame().await;
286    }
287}