widgetry/widgets/
just_draw.rs

1use geom::Polygon;
2
3use crate::{
4    ClickOutcome, Drawable, EventCtx, GeomBatch, GfxCtx, Outcome, ScreenDims, ScreenPt,
5    ScreenRectangle, Text, Widget, WidgetImpl, WidgetOutput,
6};
7
8// Just draw something, no interaction.
9pub struct JustDraw {
10    pub draw: Drawable,
11
12    pub top_left: ScreenPt,
13    pub dims: ScreenDims,
14}
15
16impl JustDraw {
17    pub(crate) fn wrap(ctx: &EventCtx, batch: GeomBatch) -> Widget {
18        Widget::new(Box::new(JustDraw {
19            dims: batch.get_dims(),
20            draw: ctx.upload(batch),
21            top_left: ScreenPt::new(0.0, 0.0),
22        }))
23    }
24}
25
26impl WidgetImpl for JustDraw {
27    fn get_dims(&self) -> ScreenDims {
28        self.dims
29    }
30
31    fn set_pos(&mut self, top_left: ScreenPt) {
32        self.top_left = top_left;
33    }
34
35    fn event(&mut self, _: &mut EventCtx, _: &mut WidgetOutput) {}
36
37    fn draw(&self, g: &mut GfxCtx) {
38        g.redraw_at(self.top_left, &self.draw);
39    }
40}
41
42pub struct DrawWithTooltips {
43    draw: Drawable,
44    tooltips: Vec<(Polygon, Text, Option<ClickOutcome>)>,
45    hover: Box<dyn Fn(&Polygon) -> GeomBatch>,
46    hovering_on_idx: Option<usize>,
47
48    top_left: ScreenPt,
49    dims: ScreenDims,
50}
51
52impl DrawWithTooltips {
53    /// `batch`: the `GeomBatch` to draw
54    /// `tooltips`: (hitbox, text, clickable action) tuples where each `text` is shown when the
55    ///             user hovers over the respective `hitbox`. If an action is present and the user
56    ///             clicks the `hitbox`, then it acts like a button click. It's assumed the
57    ///             hitboxes are non-overlapping.
58    /// `hover`: returns a GeomBatch to render upon hovering. Return an `GeomBox::new()` if
59    ///          you want hovering to be a no-op
60    pub fn new_widget(
61        ctx: &EventCtx,
62        batch: GeomBatch,
63        tooltips: Vec<(Polygon, Text, Option<ClickOutcome>)>,
64        hover: Box<dyn Fn(&Polygon) -> GeomBatch>,
65    ) -> Widget {
66        Widget::new(Box::new(DrawWithTooltips {
67            dims: batch.get_dims(),
68            top_left: ScreenPt::new(0.0, 0.0),
69            hover,
70            hovering_on_idx: None,
71            draw: ctx.upload(batch),
72            tooltips,
73        }))
74    }
75}
76
77impl WidgetImpl for DrawWithTooltips {
78    fn get_dims(&self) -> ScreenDims {
79        self.dims
80    }
81
82    fn set_pos(&mut self, top_left: ScreenPt) {
83        self.top_left = top_left;
84    }
85
86    fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput) {
87        if ctx.redo_mouseover() {
88            self.hovering_on_idx = None;
89            if let Some(cursor) = ctx.canvas.get_cursor_in_screen_space() {
90                if !ScreenRectangle::top_left(self.top_left, self.dims).contains(cursor) {
91                    return;
92                }
93                let translated =
94                    ScreenPt::new(cursor.x - self.top_left.x, cursor.y - self.top_left.y).to_pt();
95                for (idx, (hitbox, _, _)) in self.tooltips.iter().enumerate() {
96                    if hitbox.contains_pt(translated) {
97                        self.hovering_on_idx = Some(idx);
98                        break;
99                    }
100                }
101            }
102        }
103
104        if let Some(idx) = self.hovering_on_idx {
105            if ctx.normal_left_click() {
106                if let Some(ref label) = self.tooltips[idx].2 {
107                    output.outcome = match label {
108                        ClickOutcome::Label(label) => Outcome::Clicked(label.clone()),
109                        ClickOutcome::Custom(data) => Outcome::ClickCustom(data.clone()),
110                    }
111                }
112            }
113        }
114    }
115
116    fn draw(&self, g: &mut GfxCtx) {
117        g.redraw_at(self.top_left, &self.draw);
118        if let Some(idx) = self.hovering_on_idx {
119            let (hitbox, txt, _) = &self.tooltips[idx];
120            let extra = g.upload((self.hover)(hitbox));
121            g.redraw_at(self.top_left, &extra);
122            g.draw_mouse_tooltip(txt.clone());
123        }
124    }
125}
126
127// TODO Name is bad. Lay out JustDraw stuff with flexbox, just to consume it and produce one big
128// GeomBatch.
129pub struct DeferDraw {
130    pub batch: GeomBatch,
131
132    pub top_left: ScreenPt,
133    dims: ScreenDims,
134}
135
136impl DeferDraw {
137    pub fn new_widget(batch: GeomBatch) -> Widget {
138        Widget::new(Box::new(DeferDraw {
139            dims: batch.get_dims(),
140            batch,
141            top_left: ScreenPt::new(0.0, 0.0),
142        }))
143    }
144}
145
146impl WidgetImpl for DeferDraw {
147    fn get_dims(&self) -> ScreenDims {
148        self.dims
149    }
150
151    fn set_pos(&mut self, top_left: ScreenPt) {
152        self.top_left = top_left;
153    }
154
155    fn event(&mut self, _: &mut EventCtx, _: &mut WidgetOutput) {
156        unreachable!()
157    }
158
159    fn draw(&self, _: &mut GfxCtx) {
160        unreachable!()
161    }
162}