game/sandbox/dashboards/
selector.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use geom::{Polygon, Pt2D};
5use widgetry::{
6    Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
7    VerticalAlignment, Widget,
8};
9
10use crate::app::{App, Transition};
11
12// TODO Lift to widgetry
13pub struct RectangularSelector {
14    panel: Panel,
15    region: Rc<RefCell<Option<Polygon>>>,
16    corners: Option<(Pt2D, Pt2D, bool)>,
17}
18
19impl RectangularSelector {
20    pub fn new_state(
21        ctx: &mut EventCtx,
22        region: Rc<RefCell<Option<Polygon>>>,
23    ) -> Box<dyn State<App>> {
24        Box::new(RectangularSelector {
25            panel: Panel::new_builder(Widget::col(vec![
26                Widget::row(vec![
27                    Line("Select a rectangular region")
28                        .small_heading()
29                        .into_widget(ctx),
30                    ctx.style().btn_close_widget(ctx),
31                ]),
32                Text::from_all(vec![
33                    Line("Hold "),
34                    Line(Key::LeftControl.describe()).fg(ctx.style().text_hotkey_color),
35                    Line(", then click and drag to draw"),
36                ])
37                .into_widget(ctx),
38                Widget::row(vec![
39                    ctx.style()
40                        .btn_solid_primary
41                        .text("Apply")
42                        .hotkey(Key::Enter)
43                        .build_def(ctx),
44                    ctx.style()
45                        .btn_solid_destructive
46                        .text("Clear")
47                        .build_def(ctx),
48                ]),
49            ]))
50            .aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
51            .build(ctx),
52            region,
53            corners: None,
54        })
55    }
56}
57
58impl State<App> for RectangularSelector {
59    fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
60        if ctx.is_key_down(Key::LeftControl) {
61            if ctx.input.left_mouse_button_released() {
62                if let Some((_, _, ref mut dragging)) = self.corners {
63                    *dragging = false;
64                }
65            }
66            if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
67                if ctx.input.left_mouse_button_pressed() {
68                    self.corners = Some((pt, pt, true));
69                }
70                if let Some((_, ref mut pt2, dragging)) = self.corners {
71                    if dragging {
72                        *pt2 = pt;
73                    }
74                }
75            }
76        } else {
77            ctx.canvas_movement();
78        }
79
80        if let Outcome::Clicked(x) = self.panel.event(ctx) {
81            match x.as_ref() {
82                "close" => {
83                    return Transition::Pop;
84                }
85                "Clear" => {
86                    self.region.replace(None);
87                    return Transition::Pop;
88                }
89                "Apply" => {
90                    if let Some(rect) = self
91                        .corners
92                        .and_then(|(pt1, pt2, _)| Polygon::rectangle_two_corners(pt1, pt2))
93                    {
94                        self.region.replace(Some(rect));
95                    }
96                    return Transition::Pop;
97                }
98                _ => unreachable!(),
99            }
100        }
101
102        Transition::Keep
103    }
104
105    fn draw(&self, g: &mut GfxCtx, _: &App) {
106        self.panel.draw(g);
107        if let Some(p) = self.region.borrow().clone() {
108            g.draw_polygon(Color::BLUE.alpha(0.5), p);
109        }
110        if let Some((pt1, pt2, _)) = self.corners {
111            if let Some(p) = Polygon::rectangle_two_corners(pt1, pt2) {
112                g.draw_polygon(Color::RED.alpha(0.5), p);
113            }
114        }
115    }
116}