1 use crate::{Key, ScreenDims, ScreenPt, ScreenRectangle, UpdateType, UserInput};
2 use abstutil::Timer;
3 use geom::{Bounds, Pt2D};
4 use serde::{Deserialize, Serialize};
5 use std::cell::RefCell;
6 
7 // Click and release counts as a normal click, not a drag, if the distance between click and
8 // release is less than this.
9 const DRAG_THRESHOLD: f64 = 5.0;
10 
11 const PAN_SPEED: f64 = 15.0;
12 
13 const PANNING_THRESHOLD: f64 = 25.0;
14 
15 pub struct Canvas {
16     // All of these f64's are in screen-space, so do NOT use Pt2D.
17     // Public for saving/loading... should probably do better
18     pub cam_x: f64,
19     pub cam_y: f64,
20     pub cam_zoom: f64,
21 
22     // TODO Should this become Option<ScreenPt>?
23     pub(crate) cursor: ScreenPt,
24     pub(crate) window_has_cursor: bool,
25 
26     // Only for drags starting on the map. Only used to pan the map. (Last event, original)
27     pub(crate) drag_canvas_from: Option<(ScreenPt, ScreenPt)>,
28     pub(crate) drag_just_ended: bool,
29 
30     pub window_width: f64,
31     pub window_height: f64,
32 
33     // TODO Proper API for setting these
34     pub map_dims: (f64, f64),
35     pub invert_scroll: bool,
36     pub touchpad_to_move: bool,
37     pub edge_auto_panning: bool,
38     pub keys_to_pan: bool,
39     pub gui_scroll_speed: usize,
40 
41     // TODO Bit weird and hacky to mutate inside of draw() calls.
42     pub(crate) covered_areas: RefCell<Vec<ScreenRectangle>>,
43 
44     // Kind of just widgetry state awkwardly stuck here...
45     pub(crate) lctrl_held: bool,
46     pub(crate) lshift_held: bool,
47 }
48 
49 impl Canvas {
new(initial_dims: ScreenDims) -> Canvas50     pub(crate) fn new(initial_dims: ScreenDims) -> Canvas {
51         Canvas {
52             cam_x: 0.0,
53             cam_y: 0.0,
54             cam_zoom: 1.0,
55 
56             cursor: ScreenPt::new(0.0, 0.0),
57             window_has_cursor: true,
58 
59             drag_canvas_from: None,
60             drag_just_ended: false,
61 
62             window_width: initial_dims.width,
63             window_height: initial_dims.height,
64 
65             map_dims: (0.0, 0.0),
66             invert_scroll: false,
67             touchpad_to_move: false,
68             edge_auto_panning: false,
69             keys_to_pan: false,
70             gui_scroll_speed: 5,
71 
72             covered_areas: RefCell::new(Vec::new()),
73 
74             lctrl_held: false,
75             lshift_held: false,
76         }
77     }
78 
min_zoom(&self) -> f6479     pub fn min_zoom(&self) -> f64 {
80         let percent_window = 0.8;
81         (percent_window * self.window_width / self.map_dims.0)
82             .min(percent_window * self.window_height / self.map_dims.1)
83     }
84 
handle_event(&mut self, input: &mut UserInput) -> Option<UpdateType>85     pub(crate) fn handle_event(&mut self, input: &mut UserInput) -> Option<UpdateType> {
86         // Can't start dragging or zooming on top of covered area
87         if self.get_cursor_in_map_space().is_some() {
88             if self.touchpad_to_move {
89                 if let Some((scroll_x, scroll_y)) = input.get_mouse_scroll() {
90                     if self.lctrl_held {
91                         self.zoom(scroll_y, self.cursor);
92                     } else {
93                         // Woo, inversion is different for the two. :P
94                         self.cam_x += scroll_x * PAN_SPEED;
95                         self.cam_y -= scroll_y * PAN_SPEED;
96                     }
97                 }
98             } else {
99                 if input.left_mouse_button_pressed() {
100                     self.drag_canvas_from = Some((self.get_cursor(), self.get_cursor()));
101                 }
102 
103                 if let Some((_, scroll)) = input.get_mouse_scroll() {
104                     self.zoom(scroll, self.cursor);
105                 }
106             }
107 
108             if self.keys_to_pan {
109                 if input.key_pressed(Key::LeftArrow) {
110                     self.cam_x -= PAN_SPEED;
111                 }
112                 if input.key_pressed(Key::RightArrow) {
113                     self.cam_x += PAN_SPEED;
114                 }
115                 if input.key_pressed(Key::UpArrow) {
116                     self.cam_y -= PAN_SPEED;
117                 }
118                 if input.key_pressed(Key::DownArrow) {
119                     self.cam_y += PAN_SPEED;
120                 }
121                 if input.key_pressed(Key::Q) {
122                     self.zoom(
123                         1.0,
124                         ScreenPt::new(self.window_width / 2.0, self.window_height / 2.0),
125                     );
126                 }
127                 if input.key_pressed(Key::W) {
128                     self.zoom(
129                         -1.0,
130                         ScreenPt::new(self.window_width / 2.0, self.window_height / 2.0),
131                     );
132                 }
133             }
134         }
135 
136         // If we start the drag on the map and move the mouse off the map, keep dragging.
137         if let Some((click, orig)) = self.drag_canvas_from {
138             let pt = self.get_cursor();
139             self.cam_x += click.x - pt.x;
140             self.cam_y += click.y - pt.y;
141             self.drag_canvas_from = Some((pt, orig));
142 
143             if input.left_mouse_button_released() {
144                 let (_, orig) = self.drag_canvas_from.take().unwrap();
145                 let dist = ((pt.x - orig.x).powi(2) + (pt.y - orig.y).powi(2)).sqrt();
146                 if dist > DRAG_THRESHOLD {
147                     self.drag_just_ended = true;
148                 }
149             }
150         } else if self.drag_just_ended {
151             self.drag_just_ended = false;
152         } else {
153             let cursor_screen_pt = self.get_cursor().to_pt();
154             let cursor_map_pt = self.screen_to_map(self.get_cursor());
155             let inner_bounds = self.get_inner_bounds();
156             let map_bounds = self.get_map_bounds();
157             if self.edge_auto_panning
158                 && !inner_bounds.contains(cursor_screen_pt)
159                 && map_bounds.contains(cursor_map_pt)
160                 && input.nonblocking_is_update_event().is_some()
161             {
162                 let center_pt = self.center_to_screen_pt().to_pt();
163                 let displacement_x = cursor_screen_pt.x() - center_pt.x();
164                 let displacement_y = cursor_screen_pt.y() - center_pt.y();
165                 let displacement_magnitude =
166                     f64::sqrt(displacement_x.powf(2.0) + displacement_y.powf(2.0));
167                 let displacement_unit_x = displacement_x / displacement_magnitude;
168                 let displacement_unit_y = displacement_y / displacement_magnitude;
169                 // Add displacement along each axis
170                 self.cam_x += displacement_unit_x * PAN_SPEED;
171                 self.cam_y += displacement_unit_y * PAN_SPEED;
172                 return Some(UpdateType::Pan);
173             }
174         }
175         None
176     }
177 
zoom(&mut self, delta: f64, focus: ScreenPt)178     fn zoom(&mut self, delta: f64, focus: ScreenPt) {
179         let old_zoom = self.cam_zoom;
180         // By popular request, some limits ;)
181         self.cam_zoom = 1.1_f64
182             .powf(old_zoom.log(1.1) + delta)
183             .max(self.min_zoom())
184             .min(150.0);
185 
186         // Make screen_to_map of the focus point still point to the same thing after
187         // zooming.
188         self.cam_x = ((self.cam_zoom / old_zoom) * (focus.x + self.cam_x)) - focus.x;
189         self.cam_y = ((self.cam_zoom / old_zoom) * (focus.y + self.cam_y)) - focus.y;
190     }
191 
start_drawing(&self)192     pub(crate) fn start_drawing(&self) {
193         self.covered_areas.borrow_mut().clear();
194     }
195 
196     // TODO Only public for the OSD. :(
mark_covered_area(&self, rect: ScreenRectangle)197     pub fn mark_covered_area(&self, rect: ScreenRectangle) {
198         self.covered_areas.borrow_mut().push(rect);
199     }
200 
201     // Might be hovering anywhere.
get_cursor(&self) -> ScreenPt202     pub fn get_cursor(&self) -> ScreenPt {
203         self.cursor
204     }
205 
get_cursor_in_screen_space(&self) -> Option<ScreenPt>206     pub fn get_cursor_in_screen_space(&self) -> Option<ScreenPt> {
207         if self.window_has_cursor && self.get_cursor_in_map_space().is_none() {
208             Some(self.get_cursor())
209         } else {
210             None
211         }
212     }
213 
get_cursor_in_map_space(&self) -> Option<Pt2D>214     pub fn get_cursor_in_map_space(&self) -> Option<Pt2D> {
215         if self.window_has_cursor {
216             let pt = self.get_cursor();
217 
218             for rect in self.covered_areas.borrow().iter() {
219                 if rect.contains(pt) {
220                     return None;
221                 }
222             }
223 
224             Some(self.screen_to_map(pt))
225         } else {
226             None
227         }
228     }
229 
screen_to_map(&self, pt: ScreenPt) -> Pt2D230     pub fn screen_to_map(&self, pt: ScreenPt) -> Pt2D {
231         Pt2D::new(
232             (pt.x + self.cam_x) / self.cam_zoom,
233             (pt.y + self.cam_y) / self.cam_zoom,
234         )
235     }
236 
center_to_screen_pt(&self) -> ScreenPt237     pub fn center_to_screen_pt(&self) -> ScreenPt {
238         ScreenPt::new(self.window_width / 2.0, self.window_height / 2.0)
239     }
240 
center_to_map_pt(&self) -> Pt2D241     pub fn center_to_map_pt(&self) -> Pt2D {
242         self.screen_to_map(self.center_to_screen_pt())
243     }
244 
center_on_map_pt(&mut self, pt: Pt2D)245     pub fn center_on_map_pt(&mut self, pt: Pt2D) {
246         self.cam_x = (pt.x() * self.cam_zoom) - (self.window_width / 2.0);
247         self.cam_y = (pt.y() * self.cam_zoom) - (self.window_height / 2.0);
248     }
249 
map_to_screen(&self, pt: Pt2D) -> ScreenPt250     pub fn map_to_screen(&self, pt: Pt2D) -> ScreenPt {
251         ScreenPt::new(
252             (pt.x() * self.cam_zoom) - self.cam_x,
253             (pt.y() * self.cam_zoom) - self.cam_y,
254         )
255     }
256 
257     //the inner bound tells us whether auto-panning should or should not take place
get_inner_bounds(&self) -> Bounds258     fn get_inner_bounds(&self) -> Bounds {
259         let mut b = Bounds::new();
260         b.update(ScreenPt::new(PANNING_THRESHOLD, PANNING_THRESHOLD).to_pt());
261         b.update(
262             ScreenPt::new(
263                 self.window_width - PANNING_THRESHOLD,
264                 self.window_height - PANNING_THRESHOLD,
265             )
266             .to_pt(),
267         );
268         b
269     }
270 
get_window_dims(&self) -> ScreenDims271     pub fn get_window_dims(&self) -> ScreenDims {
272         ScreenDims::new(self.window_width, self.window_height)
273     }
274 
get_map_bounds(&self) -> Bounds275     fn get_map_bounds(&self) -> Bounds {
276         let mut b = Bounds::new();
277         b.update(Pt2D::new(0.0, 0.0));
278         b.update(Pt2D::new(self.map_dims.0, self.map_dims.1));
279         b
280     }
281 
get_screen_bounds(&self) -> Bounds282     pub fn get_screen_bounds(&self) -> Bounds {
283         let mut b = Bounds::new();
284         b.update(self.screen_to_map(ScreenPt::new(0.0, 0.0)));
285         b.update(self.screen_to_map(ScreenPt::new(self.window_width, self.window_height)));
286         b
287     }
288 
save_camera_state(&self, map_name: &str)289     pub fn save_camera_state(&self, map_name: &str) {
290         let state = CameraState {
291             cam_x: self.cam_x,
292             cam_y: self.cam_y,
293             cam_zoom: self.cam_zoom,
294         };
295         abstutil::write_json(abstutil::path_camera_state(map_name), &state);
296     }
297 
298     // True if this succeeds
load_camera_state(&mut self, map_name: &str) -> bool299     pub fn load_camera_state(&mut self, map_name: &str) -> bool {
300         match abstutil::maybe_read_json::<CameraState>(
301             abstutil::path_camera_state(map_name),
302             &mut Timer::throwaway(),
303         ) {
304             Ok(ref loaded) => {
305                 self.cam_x = loaded.cam_x;
306                 self.cam_y = loaded.cam_y;
307                 self.cam_zoom = loaded.cam_zoom;
308                 true
309             }
310             _ => false,
311         }
312     }
313 
align_window( &self, dims: ScreenDims, horiz: HorizontalAlignment, vert: VerticalAlignment, ) -> ScreenPt314     pub(crate) fn align_window(
315         &self,
316         dims: ScreenDims,
317         horiz: HorizontalAlignment,
318         vert: VerticalAlignment,
319     ) -> ScreenPt {
320         let x1 = match horiz {
321             HorizontalAlignment::Left => 0.0,
322             HorizontalAlignment::Center => (self.window_width - dims.width) / 2.0,
323             HorizontalAlignment::Right => self.window_width - dims.width,
324             HorizontalAlignment::Percent(pct) => pct * self.window_width,
325             HorizontalAlignment::Centered(x) => x - (dims.width / 2.0),
326         };
327         let y1 = match vert {
328             VerticalAlignment::Top => 0.0,
329             VerticalAlignment::Center => (self.window_height - dims.height) / 2.0,
330             VerticalAlignment::Bottom => self.window_height - dims.height,
331             // TODO Hack
332             VerticalAlignment::BottomAboveOSD => self.window_height - dims.height - 60.0,
333             VerticalAlignment::Percent(pct) => pct * self.window_height,
334             VerticalAlignment::Above(y) => y - dims.height,
335             VerticalAlignment::Below(y) => y,
336         };
337         ScreenPt::new(x1, y1)
338     }
339 }
340 
341 #[derive(Clone, Copy)]
342 pub enum HorizontalAlignment {
343     Left,
344     Center,
345     Right,
346     Percent(f64),
347     Centered(f64),
348 }
349 
350 #[derive(Clone, Copy)]
351 pub enum VerticalAlignment {
352     Top,
353     Center,
354     Bottom,
355     BottomAboveOSD,
356     Percent(f64),
357     Above(f64),
358     Below(f64),
359 }
360 
361 #[derive(Serialize, Deserialize, Debug)]
362 pub struct CameraState {
363     cam_x: f64,
364     cam_y: f64,
365     cam_zoom: f64,
366 }
367