1use std::collections::HashMap;
2use std::fmt::Debug;
3use std::hash::Hash;
4
5use geom::{Bounds, Circle, Distance, Polygon, Pt2D, QuadTree};
6
7use crate::mapspace::{ToggleZoomed, ToggleZoomedBuilder};
8use crate::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, MultiKey, RewriteColor, Text};
9
10pub struct World<ID: ObjectID> {
19 objects: HashMap<ID, Object<ID>>,
21 quadtree: QuadTree<ID>,
22
23 draw_master_batches: Vec<ToggleZoomed>,
24
25 hovering: Option<ID>,
26 draw_hovering: Option<Drawable>,
28 dragging_from: Option<(Pt2D, bool)>,
31}
32
33#[derive(Clone)]
35pub enum WorldOutcome<ID: ObjectID> {
36 ClickedFreeSpace(Pt2D),
38 Dragging {
42 obj: ID,
43 dx: f64,
44 dy: f64,
45 cursor: Pt2D,
46 },
47 Keypress(&'static str, ID),
49 ClickedObject(ID),
51 HoverChanged(Option<ID>, Option<ID>),
58 Nothing,
60}
61
62impl<I: ObjectID> WorldOutcome<I> {
63 pub fn maybe_map_id<O: ObjectID, F: Fn(I) -> Option<O>>(self, f: F) -> Option<WorldOutcome<O>> {
67 match self {
68 WorldOutcome::ClickedFreeSpace(pt) => Some(WorldOutcome::ClickedFreeSpace(pt)),
69 WorldOutcome::Dragging {
70 obj,
71 dx,
72 dy,
73 cursor,
74 } => Some(WorldOutcome::Dragging {
75 obj: f(obj)?,
76 dx,
77 dy,
78 cursor,
79 }),
80 WorldOutcome::Keypress(action, id) => Some(WorldOutcome::Keypress(action, f(id)?)),
81 WorldOutcome::ClickedObject(id) => Some(WorldOutcome::ClickedObject(f(id)?)),
82 WorldOutcome::HoverChanged(before, after) => {
83 let before = match before {
86 Some(x) => Some(f(x)?),
87 None => None,
88 };
89 let after = match after {
90 Some(x) => Some(f(x)?),
91 None => None,
92 };
93 Some(WorldOutcome::HoverChanged(before, after))
94 }
95 WorldOutcome::Nothing => Some(WorldOutcome::Nothing),
96 }
97 }
98}
99
100pub trait ObjectID: Clone + Copy + Debug + Eq + Hash {}
102
103pub struct ObjectBuilder<'a, ID: ObjectID> {
105 world: &'a mut World<ID>,
106
107 id: ID,
108 hitboxes: Vec<Polygon>,
109 zorder: usize,
110 draw_normal: Option<ToggleZoomedBuilder>,
111 draw_hover: Option<HoverBuilder>,
112 tooltip: Option<Text>,
113 clickable: bool,
114 draggable: bool,
115 keybindings: Vec<(MultiKey, &'static str)>,
116}
117
118enum HoverBuilder {
119 ColorHitbox(Color),
120 Custom(ToggleZoomedBuilder),
121}
122
123impl<'a, ID: ObjectID> ObjectBuilder<'a, ID> {
124 pub fn hitbox(mut self, polygon: Polygon) -> Self {
126 assert!(self.hitboxes.is_empty(), "called hitbox twice");
127 self.hitboxes = vec![polygon];
128 self
129 }
130
131 pub fn hitboxes(mut self, polygons: Vec<Polygon>) -> Self {
133 assert!(self.hitboxes.is_empty(), "called hitbox twice");
134 assert!(!polygons.is_empty(), "not specifying any hitboxes");
135 self.hitboxes = polygons;
136 self
137 }
138
139 pub fn zorder(mut self, zorder: usize) -> Self {
141 assert!(self.zorder == 0, "called zorder twice");
142 self.zorder = zorder;
143 self
144 }
145
146 pub fn draw<I: Into<ToggleZoomedBuilder>>(mut self, normal: I) -> Self {
148 assert!(
149 self.draw_normal.is_none(),
150 "already specified how to draw normally"
151 );
152 self.draw_normal = Some(normal.into());
153 self
154 }
155
156 pub fn draw_color(self, color: Color) -> Self {
158 assert!(!self.hitboxes.is_empty(), "call hitbox first");
159 let mut batch = GeomBatch::new();
160 batch.extend(color, self.hitboxes.clone());
161 self.draw(batch)
162 }
163
164 pub fn draw_color_unzoomed(self, color: Color) -> Self {
166 assert!(!self.hitboxes.is_empty(), "call hitbox first");
167 let mut draw = ToggleZoomed::builder();
168 draw.unzoomed.extend(color, self.hitboxes.clone());
169 self.draw(draw)
170 }
171
172 pub fn drawn_in_master_batch(self) -> Self {
175 assert!(
176 self.draw_normal.is_none(),
177 "object is already drawn normally"
178 );
179 self.draw(GeomBatch::new())
180 }
181
182 pub fn draw_hovered<I: Into<ToggleZoomedBuilder>>(mut self, hovered: I) -> Self {
185 assert!(
186 self.draw_hover.is_none(),
187 "already specified how to draw hovered"
188 );
189 self.draw_hover = Some(HoverBuilder::Custom(hovered.into()));
190 self
191 }
192
193 pub fn draw_hover_rewrite(self, rewrite: RewriteColor) -> Self {
195 let hovered = self
196 .draw_normal
197 .clone()
198 .expect("first specify how to draw normally")
199 .color(rewrite);
200 self.draw_hovered(hovered)
201 }
202
203 pub fn hover_alpha(self, alpha: f32) -> Self {
205 self.draw_hover_rewrite(RewriteColor::ChangeAlpha(alpha))
206 }
207
208 pub fn hover_outline(self, color: Color, thickness: Distance) -> Self {
212 let mut draw = self
213 .draw_normal
214 .clone()
215 .expect("first specify how to draw normally")
216 .draw_differently_zoomed();
217 let mut unzoomed = Vec::new();
218 let mut zoomed = Vec::new();
219 assert!(!self.hitboxes.is_empty(), "call hitbox first");
220 for polygon in &self.hitboxes {
221 unzoomed.push(polygon.to_outline(thickness));
222 zoomed.push(polygon.to_outline(thickness / 2.0));
223 }
224 if unzoomed.len() == zoomed.len() && unzoomed.len() == self.hitboxes.len() {
225 draw.unzoomed.extend(color, unzoomed);
226 draw.zoomed.extend(color.multiply_alpha(0.5), zoomed);
227 } else {
228 warn!(
229 "Can't hover_outline for {:?}. Falling back to a colored polygon",
230 self.id
231 );
232 let mut batch = GeomBatch::new();
233 batch.extend(color.multiply_alpha(0.5), self.hitboxes.clone());
234 draw = batch.into();
235 }
236 self.draw_hovered(draw)
237 }
238
239 pub fn hover_color(mut self, color: Color) -> Self {
242 assert!(!self.hitboxes.is_empty(), "call hitbox first");
243 self.draw_hover = Some(HoverBuilder::ColorHitbox(color));
244 self
245 }
246
247 pub fn invisibly_hoverable(self) -> Self {
249 self.draw_hovered(GeomBatch::new())
250 }
251
252 pub fn maybe_tooltip(self, txt: Option<Text>) -> Self {
254 if let Some(txt) = txt {
255 self.tooltip(txt)
256 } else {
257 self
258 }
259 }
260
261 pub fn tooltip(mut self, txt: Text) -> Self {
263 assert!(self.tooltip.is_none(), "already specified tooltip");
264 assert!(
267 self.draw_hover.is_some(),
268 "first specify how to draw hovered"
269 );
270 self.tooltip = Some(txt);
271 self
272 }
273
274 pub fn clickable(mut self) -> Self {
276 assert!(!self.clickable, "called clickable twice");
277 self.clickable = true;
278 self
279 }
280
281 pub fn set_clickable(mut self, clickable: bool) -> Self {
283 self.clickable = clickable;
284 self
285 }
286
287 pub fn draggable(mut self) -> Self {
293 assert!(!self.draggable, "called draggable twice");
294 self.draggable = true;
295 self
296 }
297
298 pub fn hotkey<I: Into<MultiKey>>(mut self, key: I, action: &'static str) -> Self {
301 self.keybindings.push((key.into(), action));
303 self
304 }
305
306 pub fn build(mut self, ctx: &EventCtx) {
308 assert!(!self.hitboxes.is_empty(), "didn't specify hitbox");
309 let bounds = Bounds::from_polygons(&self.hitboxes);
310 self.world.quadtree.insert_with_box(self.id, bounds);
311
312 self.world.objects.insert(
313 self.id,
314 Object {
315 _id: self.id,
316 hitboxes: self.hitboxes,
317 zorder: self.zorder,
318 draw_normal: self
319 .draw_normal
320 .expect("didn't specify how to draw normally")
321 .build(ctx),
322 draw_hover: self.draw_hover.take().map(|draw| match draw {
323 HoverBuilder::Custom(draw) => DrawHover::Custom(draw.build(ctx)),
324 HoverBuilder::ColorHitbox(color) => DrawHover::ColorHitbox(color),
325 }),
326 tooltip: self.tooltip,
327 clickable: self.clickable,
328 draggable: self.draggable,
329 keybindings: self.keybindings,
330 },
331 );
332 }
333}
334
335struct Object<ID: ObjectID> {
336 _id: ID,
337 hitboxes: Vec<Polygon>,
338 zorder: usize,
339 draw_normal: ToggleZoomed,
340 draw_hover: Option<DrawHover>,
341 tooltip: Option<Text>,
342 clickable: bool,
343 draggable: bool,
344 keybindings: Vec<(MultiKey, &'static str)>,
347}
348
349enum DrawHover {
350 ColorHitbox(Color),
351 Custom(ToggleZoomed),
352}
353
354impl<ID: ObjectID> World<ID> {
355 pub fn new() -> World<ID> {
357 World {
358 objects: HashMap::new(),
359 quadtree: QuadTree::new(),
360
361 draw_master_batches: Vec::new(),
362
363 hovering: None,
364 draw_hovering: None,
365 dragging_from: None,
366 }
367 }
368
369 pub fn add(&mut self, id: ID) -> ObjectBuilder<'_, ID> {
372 assert!(!self.objects.contains_key(&id), "duplicate object added");
373 ObjectBuilder {
374 world: self,
375
376 id,
377 hitboxes: Vec::new(),
378 zorder: 0,
379 draw_normal: None,
380 draw_hover: None,
381 tooltip: None,
382 clickable: false,
383 draggable: false,
384 keybindings: Vec::new(),
385 }
386 }
387
388 pub fn delete(&mut self, id: ID) {
391 if self.hovering == Some(id) {
392 self.hovering = None;
393 self.draw_hovering = None;
394 if self.dragging_from.is_some() {
395 panic!("Can't delete {:?} mid-drag", id);
396 }
397 }
398
399 self.delete_before_replacement(id);
400 }
401
402 pub fn delete_before_replacement(&mut self, id: ID) {
405 if self.objects.remove(&id).is_some() {
406 if self.quadtree.remove(id).is_none() {
407 warn!("{:?} wasn't in the quadtree", id);
410 }
411 } else {
412 panic!("Can't delete {:?}; it's not in the World", id);
413 }
414 }
415
416 pub fn maybe_delete(&mut self, id: ID) {
418 if self.hovering == Some(id) {
419 self.hovering = None;
420 self.draw_hovering = None;
421 if self.dragging_from.is_some() {
422 panic!("Can't delete {:?} mid-drag", id);
423 }
424 }
425
426 if self.objects.remove(&id).is_some() {
427 if self.quadtree.remove(id).is_none() {
428 warn!("{:?} wasn't in the quadtree", id);
431 }
432 }
433 }
434
435 pub fn initialize_hover(&mut self, ctx: &EventCtx) {
442 self.hovering = ctx
443 .canvas
444 .get_cursor_in_map_space()
445 .and_then(|cursor| self.calculate_hover(cursor));
446 self.redraw_hovering(ctx);
447 }
448
449 pub fn hack_unset_hovering(&mut self) {
452 self.hovering = None;
453 self.draw_hovering = None;
454 }
455
456 pub fn rebuilt_during_drag(&mut self, ctx: &EventCtx, prev_world: &World<ID>) {
464 if prev_world.dragging_from.is_some() {
465 self.dragging_from = prev_world.dragging_from;
466 self.hovering = prev_world.hovering;
467 assert!(self.objects.contains_key(self.hovering.as_ref().unwrap()));
468 self.redraw_hovering(ctx);
469 }
470 }
471
472 pub fn draw_master_batch<I: Into<ToggleZoomedBuilder>>(&mut self, ctx: &EventCtx, draw: I) {
475 self.draw_master_batches.push(draw.into().build(ctx));
476 }
477
478 pub fn draw_master_batch_built(&mut self, draw: ToggleZoomed) {
480 self.draw_master_batches.push(draw);
481 }
482
483 pub fn event(&mut self, ctx: &mut EventCtx) -> WorldOutcome<ID> {
485 if let Some((drag_from, moved)) = self.dragging_from {
486 if ctx.input.left_mouse_button_released() {
487 self.dragging_from = None;
488 if !moved && self.objects[&self.hovering.unwrap()].clickable {
491 return WorldOutcome::ClickedObject(self.hovering.unwrap());
492 }
493
494 let before = self.hovering;
495 self.hovering = ctx
496 .canvas
497 .get_cursor_in_map_space()
498 .and_then(|cursor| self.calculate_hover(cursor));
499 return if before == self.hovering {
500 WorldOutcome::Nothing
501 } else {
502 self.redraw_hovering(ctx);
503 WorldOutcome::HoverChanged(before, self.hovering)
504 };
505 }
506 if let Some((_, dy)) = ctx.input.get_mouse_scroll() {
508 ctx.canvas.zoom(dy, ctx.canvas.get_cursor());
509 }
510
511 if ctx.redo_mouseover() {
512 if let Some(cursor) = ctx.canvas.get_cursor_in_map_space() {
513 let dx = cursor.x() - drag_from.x();
514 let dy = cursor.y() - drag_from.y();
515 self.dragging_from = Some((cursor, true));
516 return WorldOutcome::Dragging {
517 obj: self.hovering.unwrap(),
518 dx,
519 dy,
520 cursor,
521 };
522 }
523 }
524
525 return WorldOutcome::Nothing;
526 }
527
528 let cursor = if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
529 pt
530 } else {
531 let before = self.hovering.take();
532 return if before.is_some() {
533 WorldOutcome::HoverChanged(before, None)
534 } else {
535 WorldOutcome::Nothing
536 };
537 };
538
539 let mut neutral_outcome = WorldOutcome::Nothing;
541 if ctx.redo_mouseover() {
542 let before = self.hovering;
543 self.hovering = self.calculate_hover(cursor);
544 if before != self.hovering {
545 self.redraw_hovering(ctx);
546 neutral_outcome = WorldOutcome::HoverChanged(before, self.hovering);
547 }
548 }
549
550 let mut allow_panning = true;
552 if let Some(id) = self.hovering {
553 let obj = &self.objects[&id];
554
555 if obj.clickable && ctx.normal_left_click() {
558 return WorldOutcome::ClickedObject(id);
559 }
560
561 if obj.draggable {
562 allow_panning = false;
563 if ctx.input.left_mouse_button_pressed() {
564 self.dragging_from = Some((cursor, false));
565 return neutral_outcome;
566 }
567 }
568
569 for (key, action) in &obj.keybindings {
570 if ctx.input.pressed(key.clone()) {
571 return WorldOutcome::Keypress(action, id);
572 }
573 }
574 }
575
576 if allow_panning {
577 ctx.canvas_movement();
578
579 if self.hovering.is_none() && ctx.normal_left_click() {
580 return WorldOutcome::ClickedFreeSpace(cursor);
581 }
582 } else if let Some((_, dy)) = ctx.input.get_mouse_scroll() {
583 ctx.canvas.zoom(dy, ctx.canvas.get_cursor());
584 }
585
586 neutral_outcome
587 }
588
589 fn calculate_hover(&self, cursor: Pt2D) -> Option<ID> {
590 let mut objects = Vec::new();
591 for id in self.quadtree.query_bbox(
592 Circle::new(cursor, Distance::meters(3.0)).get_bounds(),
595 ) {
596 objects.push(id);
597 }
598 objects.sort_by_key(|id| self.objects[id].zorder);
599 objects.reverse();
600
601 for id in objects {
602 let obj = &self.objects[&id];
603 if obj.draw_hover.is_some() && obj.hitboxes.iter().any(|poly| poly.contains_pt(cursor))
604 {
605 return Some(id);
606 }
607 }
608 None
609 }
610
611 pub fn draw(&self, g: &mut GfxCtx) {
613 for draw in &self.draw_master_batches {
615 draw.draw(g);
616 }
617
618 let mut objects = Vec::new();
619 for id in self.quadtree.query_bbox(g.get_screen_bounds()) {
620 objects.push(id);
621 }
622 objects.sort_by_key(|id| self.objects[id].zorder);
623
624 for id in objects {
625 let mut drawn = false;
626 let obj = &self.objects[&id];
627 if Some(id) == self.hovering {
628 if let Some(ref draw) = obj.draw_hover {
629 match draw {
630 DrawHover::Custom(draw) => draw.draw(g),
631 DrawHover::ColorHitbox(_) => self.draw_hovering.as_ref().unwrap().draw(g),
632 }
633 drawn = true;
634 }
635 if let Some(ref txt) = obj.tooltip {
636 g.draw_mouse_tooltip(txt.clone());
637 }
638 }
639 if !drawn {
640 obj.draw_normal.draw(g);
641 }
642 }
643 }
644
645 pub fn get_hovering(&self) -> Option<ID> {
647 self.hovering
648 }
649
650 pub fn override_tooltip(&mut self, id: &ID, tooltip: Option<Text>) -> bool {
652 if let Some(obj) = self.objects.get_mut(id) {
653 obj.tooltip = tooltip;
654 true
655 } else {
656 false
657 }
658 }
659
660 pub fn calculate_hovering(&self, ctx: &EventCtx) -> Option<ID> {
664 ctx.canvas
667 .get_cursor_in_map_space()
668 .and_then(|cursor| self.calculate_hover(cursor))
669 }
670
671 pub fn get_hovered_keybindings(&self) -> Option<&Vec<(MultiKey, &'static str)>> {
674 Some(&self.objects[&self.hovering?].keybindings)
675 }
676
677 fn redraw_hovering(&mut self, ctx: &EventCtx) {
678 self.draw_hovering = None;
679 if let Some(id) = self.hovering {
680 let obj = &self.objects[&id];
681 if let Some(DrawHover::ColorHitbox(color)) = obj.draw_hover {
682 let mut batch = GeomBatch::new();
683 batch.extend(color, obj.hitboxes.clone());
684 self.draw_hovering = Some(batch.upload(ctx));
685 }
686 }
687 }
688}
689
690#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
692pub struct DummyID(usize);
693impl ObjectID for DummyID {}
694
695impl World<DummyID> {
696 pub fn add_unnamed(&mut self) -> ObjectBuilder<'_, DummyID> {
703 self.add(DummyID(self.objects.len()))
704 }
705}