Skip to main content

egui_map/map/
objects.rs

1//! Data types consumed by the [`Map`](super::Map) widget.
2//!
3//! This module contains the geometry primitives ([`RawPoint`], [`RawLine`]),
4//! the map content types ([`MapPoint`], [`MapSegment`], [`MapLabel`]) and the
5//! customization points of the widget: [`MapSettings`], [`MapStyle`],
6//! [`VisibilitySetting`], [`ContextMenuManager`] and [`NodeTemplate`].
7
8use egui::{Align2, Color32, FontFamily, FontId, Pos2, Stroke, Ui};
9use rstar::AABB;
10use std::convert::{From, Into};
11use std::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub};
12use std::time::Instant;
13
14/// A point (or vector) in 2D map coordinates.
15///
16/// `RawPoint` supports component-wise arithmetic: [`Mul`], [`Div`],
17/// [`MulAssign`] and [`DivAssign`] with `f32` and the common integer types, and
18/// [`Add`]/[`Sub`] with other points (by value or by reference). It also
19/// converts from and to `[f32; 2]`, integer arrays and [`egui::Pos2`].
20///
21/// # Examples
22///
23/// ```
24/// use egui_map::map::objects::RawPoint;
25///
26/// let a = RawPoint::new(1.0, 2.0);
27/// let b = RawPoint::new(3.0, -1.0);
28///
29/// assert_eq!((a + b).components, [4.0, 1.0]);
30/// assert_eq!((a * 2.0f32).components, [2.0, 4.0]);
31/// ```
32#[derive(Copy, Clone, Debug, PartialEq)]
33pub struct RawPoint {
34    /// The `x` and `y` components of the point.
35    pub components: [f32; 2],
36}
37
38impl RawPoint {
39    /// Creates a point from its `x` and `y` coordinates.
40    pub fn new(x: f32, y: f32) -> Self {
41        Self { components: [x, y] }
42    }
43}
44
45impl rstar::Point for RawPoint {
46    type Scalar = f32;
47    const DIMENSIONS: usize = 2;
48
49    fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self {
50        let mut components = [0.0; 2];
51        for (i, component) in components.iter_mut().enumerate() {
52            *component = generator(i);
53        }
54        Self { components }
55    }
56
57    fn nth(&self, index: usize) -> Self::Scalar {
58        self.components[index]
59    }
60
61    fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar {
62        &mut self.components[index]
63    }
64}
65
66impl Default for RawPoint {
67    fn default() -> Self {
68        Self::new(0.00, 0.00)
69    }
70}
71
72impl Mul<i64> for RawPoint {
73    type Output = Self;
74
75    fn mul(self, rhs: i64) -> Self::Output {
76        Self {
77            components: [
78                self.components[0] * rhs as f32,
79                self.components[1] * rhs as f32,
80            ],
81        }
82    }
83}
84
85impl Mul<i32> for RawPoint {
86    type Output = Self;
87
88    fn mul(self, rhs: i32) -> Self::Output {
89        Self {
90            components: [
91                self.components[0] * rhs as f32,
92                self.components[1] * rhs as f32,
93            ],
94        }
95    }
96}
97
98impl Mul<u64> for RawPoint {
99    type Output = Self;
100
101    fn mul(self, rhs: u64) -> Self::Output {
102        Self {
103            components: [
104                self.components[0] * rhs as f32,
105                self.components[1] * rhs as f32,
106            ],
107        }
108    }
109}
110
111impl Mul<u32> for RawPoint {
112    type Output = Self;
113
114    fn mul(self, rhs: u32) -> Self::Output {
115        Self {
116            components: [
117                self.components[0] * rhs as f32,
118                self.components[1] * rhs as f32,
119            ],
120        }
121    }
122}
123
124impl Mul<f32> for RawPoint {
125    type Output = Self;
126
127    fn mul(self, rhs: f32) -> Self::Output {
128        Self {
129            components: [self.components[0] * rhs, self.components[1] * rhs],
130        }
131    }
132}
133
134impl MulAssign<i64> for RawPoint {
135    fn mul_assign(&mut self, rhs: i64) {
136        self.components[0] = self.components[0] * rhs as f32;
137        self.components[1] = self.components[1] * rhs as f32;
138    }
139}
140
141impl MulAssign<i32> for RawPoint {
142    fn mul_assign(&mut self, rhs: i32) {
143        self.components[0] = self.components[0] * rhs as f32;
144        self.components[1] = self.components[1] * rhs as f32;
145    }
146}
147
148impl MulAssign<u64> for RawPoint {
149    fn mul_assign(&mut self, rhs: u64) {
150        self.components[0] = self.components[0] * rhs as f32;
151        self.components[1] = self.components[1] * rhs as f32;
152    }
153}
154
155impl MulAssign<u32> for RawPoint {
156    fn mul_assign(&mut self, rhs: u32) {
157        self.components[0] = self.components[0] * rhs as f32;
158        self.components[1] = self.components[1] * rhs as f32;
159    }
160}
161
162impl MulAssign<f32> for RawPoint {
163    fn mul_assign(&mut self, rhs: f32) {
164        self.components[0] = self.components[0] * rhs;
165        self.components[1] = self.components[1] * rhs;
166    }
167}
168
169impl Div<i64> for RawPoint {
170    type Output = Self;
171
172    fn div(self, rhs: i64) -> Self::Output {
173        Self {
174            components: [
175                self.components[0] / rhs as f32,
176                self.components[1] / rhs as f32,
177            ],
178        }
179    }
180}
181
182impl Div<i32> for RawPoint {
183    type Output = Self;
184
185    fn div(self, rhs: i32) -> Self::Output {
186        Self {
187            components: [
188                self.components[0] / rhs as f32,
189                self.components[1] / rhs as f32,
190            ],
191        }
192    }
193}
194
195impl Div<u64> for RawPoint {
196    type Output = Self;
197
198    fn div(self, rhs: u64) -> Self::Output {
199        Self {
200            components: [
201                self.components[0] / rhs as f32,
202                self.components[1] / rhs as f32,
203            ],
204        }
205    }
206}
207
208impl Div<u32> for RawPoint {
209    type Output = Self;
210
211    fn div(self, rhs: u32) -> Self::Output {
212        Self {
213            components: [
214                self.components[0] / rhs as f32,
215                self.components[1] / rhs as f32,
216            ],
217        }
218    }
219}
220
221impl Div<f32> for RawPoint {
222    type Output = Self;
223
224    fn div(self, rhs: f32) -> Self::Output {
225        Self {
226            components: [self.components[0] / rhs, self.components[1] / rhs],
227        }
228    }
229}
230
231impl DivAssign<i64> for RawPoint {
232    fn div_assign(&mut self, rhs: i64) {
233        self.components[0] = self.components[0] / rhs as f32;
234        self.components[1] = self.components[1] / rhs as f32;
235    }
236}
237
238impl DivAssign<i32> for RawPoint {
239    fn div_assign(&mut self, rhs: i32) {
240        self.components[0] = self.components[0] / rhs as f32;
241        self.components[1] = self.components[1] / rhs as f32;
242    }
243}
244
245impl DivAssign<u64> for RawPoint {
246    fn div_assign(&mut self, rhs: u64) {
247        self.components[0] = self.components[0] / rhs as f32;
248        self.components[1] = self.components[1] / rhs as f32;
249    }
250}
251
252impl DivAssign<u32> for RawPoint {
253    fn div_assign(&mut self, rhs: u32) {
254        self.components[0] = self.components[0] / rhs as f32;
255        self.components[1] = self.components[1] / rhs as f32;
256    }
257}
258
259impl DivAssign<f32> for RawPoint {
260    fn div_assign(&mut self, rhs: f32) {
261        self.components[0] = self.components[0] / rhs;
262        self.components[1] = self.components[1] / rhs;
263    }
264}
265
266impl Add<RawPoint> for RawPoint {
267    type Output = RawPoint;
268    fn add(self, rhs: RawPoint) -> Self::Output {
269        Self {
270            components: [
271                self.components[0] + rhs.components[0],
272                self.components[1] + rhs.components[1],
273            ],
274        }
275    }
276}
277
278impl Sub<RawPoint> for RawPoint {
279    type Output = RawPoint;
280    fn sub(self, rhs: RawPoint) -> Self::Output {
281        Self {
282            components: [
283                self.components[0] - rhs.components[0],
284                self.components[1] - rhs.components[1],
285            ],
286        }
287    }
288}
289
290impl Add<&RawPoint> for RawPoint {
291    type Output = RawPoint;
292    fn add(self, rhs: &RawPoint) -> Self::Output {
293        Self {
294            components: [
295                self.components[0] + rhs.components[0],
296                self.components[1] + rhs.components[1],
297            ],
298        }
299    }
300}
301
302impl Sub<&RawPoint> for RawPoint {
303    type Output = RawPoint;
304    fn sub(self, rhs: &RawPoint) -> Self::Output {
305        Self {
306            components: [
307                self.components[0] - rhs.components[0],
308                self.components[1] - rhs.components[1],
309            ],
310        }
311    }
312}
313
314impl From<[f32; 2]> for RawPoint {
315    fn from(value: [f32; 2]) -> Self {
316        Self { components: value }
317    }
318}
319
320impl From<Pos2> for RawPoint {
321    fn from(value: Pos2) -> Self {
322        Self {
323            components: [value.x, value.y],
324        }
325    }
326}
327
328impl From<[i64; 2]> for RawPoint {
329    fn from(value: [i64; 2]) -> Self {
330        Self {
331            components: [value[0] as f32, value[1] as f32],
332        }
333    }
334}
335
336impl From<[i32; 2]> for RawPoint {
337    fn from(value: [i32; 2]) -> Self {
338        Self {
339            components: [value[0] as f32, value[1] as f32],
340        }
341    }
342}
343
344impl From<[i16; 2]> for RawPoint {
345    fn from(value: [i16; 2]) -> Self {
346        Self {
347            components: [value[0] as f32, value[1] as f32],
348        }
349    }
350}
351
352impl From<[i8; 2]> for RawPoint {
353    fn from(value: [i8; 2]) -> Self {
354        Self {
355            components: [value[0] as f32, value[1] as f32],
356        }
357    }
358}
359
360impl From<RawPoint> for [f32; 2] {
361    fn from(val: RawPoint) -> Self {
362        [val.components[0], val.components[1]]
363    }
364}
365
366impl From<RawPoint> for Pos2 {
367    fn from(val: RawPoint) -> Self {
368        Pos2::from(val.components)
369    }
370}
371
372/// A straight line segment between two [`RawPoint`]s.
373#[derive(Copy, Clone, Debug)]
374pub struct RawLine {
375    /// The two end points of the segment.
376    pub points: [RawPoint; 2],
377}
378
379impl RawLine {
380    /// Creates a segment between `a` and `b`.
381    pub fn new(a: RawPoint, b: RawPoint) -> Self {
382        Self { points: [a, b] }
383    }
384
385    /// Returns the Euclidean distance between the two end points.
386    pub fn distance(self) -> f32 {
387        let x = self.points[0].components[0] - self.points[1].components[0];
388        let y = self.points[0].components[1] - self.points[1].components[1];
389        (x.powi(2) + y.powi(2)).sqrt()
390    }
391
392    /// Returns the point halfway between the two end points.
393    pub fn midpoint(self) -> RawPoint {
394        let x = (self.points[0].components[0] + self.points[1].components[0]) / 2.0;
395        let y = (self.points[0].components[1] + self.points[1].components[1]) / 2.0;
396        RawPoint::new(x, y)
397    }
398
399    /// Returns the Euclidean distance from `point` to the closest point on
400    /// this segment.
401    ///
402    /// The closest point is the perpendicular projection of `point` onto the
403    /// segment's supporting line, clamped to the segment itself; for a
404    /// zero-length segment it is simply the distance to the endpoint.
405    ///
406    /// # Examples
407    ///
408    /// ```
409    /// use egui_map::map::objects::{RawLine, RawPoint};
410    ///
411    /// let line = RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(10.0, 0.0));
412    /// assert_eq!(line.distance_to_point(RawPoint::new(5.0, 3.0)), 3.0);
413    /// // Beyond the end of the segment, the endpoint is the closest point.
414    /// assert_eq!(line.distance_to_point(RawPoint::new(14.0, 0.0)), 4.0);
415    /// ```
416    pub fn distance_to_point(self, point: RawPoint) -> f32 {
417        let [a, b] = self.points;
418        let ab = b - a;
419        let ap = point - a;
420        let len_sq = ab.components[0].powi(2) + ab.components[1].powi(2);
421        if len_sq == 0.0 {
422            // Degenerate segment: both endpoints coincide.
423            return (ap.components[0].powi(2) + ap.components[1].powi(2)).sqrt();
424        }
425        let t = ((ap.components[0] * ab.components[0] + ap.components[1] * ab.components[1])
426            / len_sq)
427            .clamp(0.0, 1.0);
428        let closest = a + ab * t;
429        let d = point - closest;
430        (d.components[0].powi(2) + d.components[1].powi(2)).sqrt()
431    }
432}
433
434impl From<RawLine> for [Pos2; 2] {
435    fn from(val: RawLine) -> Self {
436        let position1 = val.points[0].into();
437        let position2 = val.points[1].into();
438        [position1, position2]
439    }
440}
441
442impl From<[[i64; 2]; 2]> for RawLine {
443    fn from(value: [[i64; 2]; 2]) -> Self {
444        Self {
445            points: [RawPoint::from(value[0]), RawPoint::from(value[1])],
446        }
447    }
448}
449
450/// Visual style used to paint the map under a given theme.
451///
452/// Multiplying or dividing a `MapStyle` by a number scales the stroke widths
453/// and the font size, leaving colors untouched; the widget uses this to scale
454/// the active style with the current zoom factor. Fields that are `None` are
455/// left untouched by those operators.
456#[derive(Clone, Debug)]
457pub struct MapStyle {
458    /// Stroke used for the widget border.
459    pub border: Option<Stroke>,
460    /// Stroke used for the connection lines between nodes.
461    pub line: Option<Stroke>,
462    /// Color used to fill node shapes.
463    pub fill_color: Color32,
464    /// Color used for text.
465    pub text_color: Color32,
466    /// Font used for map labels.
467    pub font: Option<FontId>,
468    /// Background color of the map canvas.
469    pub background_color: Color32,
470    /// Color used for notification pulse animations.
471    pub alert_color: Color32,
472}
473
474impl MapStyle {
475    /// Creates a fully transparent style with no border, line or font.
476    pub fn new() -> Self {
477        MapStyle {
478            border: None,
479            line: None,
480            fill_color: Color32::TRANSPARENT,
481            text_color: Color32::TRANSPARENT,
482            font: None,
483            background_color: Color32::TRANSPARENT,
484            alert_color: Color32::TRANSPARENT,
485        }
486    }
487}
488
489impl Default for MapStyle {
490    fn default() -> Self {
491        MapStyle::new()
492    }
493}
494
495impl MapStyle {
496    /// Returns a copy with the stroke widths and font size scaled by `factor`.
497    /// Fields that are `None` are left untouched.
498    fn scaled(mut self, factor: f32) -> Self {
499        if let Some(border) = self.border.as_mut() {
500            border.width *= factor;
501        }
502        if let Some(line) = self.line.as_mut() {
503            line.width *= factor;
504        }
505        if let Some(font) = self.font.as_mut() {
506            font.size *= factor;
507        }
508        self
509    }
510}
511
512impl Mul<i64> for MapStyle {
513    type Output = Self;
514
515    fn mul(self, rhs: i64) -> Self::Output {
516        self.scaled(rhs as f32)
517    }
518}
519
520impl Mul<i32> for MapStyle {
521    type Output = Self;
522
523    fn mul(self, rhs: i32) -> Self::Output {
524        self.scaled(rhs as f32)
525    }
526}
527
528impl Mul<f32> for MapStyle {
529    type Output = Self;
530
531    fn mul(self, rhs: f32) -> Self::Output {
532        self.scaled(rhs)
533    }
534}
535
536impl Mul<f64> for MapStyle {
537    type Output = Self;
538
539    fn mul(self, rhs: f64) -> Self::Output {
540        self.scaled(rhs as f32)
541    }
542}
543
544impl Div<i64> for MapStyle {
545    type Output = Self;
546
547    fn div(self, rhs: i64) -> Self::Output {
548        self.scaled(1.0 / rhs as f32)
549    }
550}
551
552impl Div<i32> for MapStyle {
553    type Output = Self;
554
555    fn div(self, rhs: i32) -> Self::Output {
556        self.scaled(1.0 / rhs as f32)
557    }
558}
559
560impl Div<f32> for MapStyle {
561    type Output = Self;
562
563    fn div(self, rhs: f32) -> Self::Output {
564        self.scaled(1.0 / rhs)
565    }
566}
567
568impl Div<f64> for MapStyle {
569    type Output = Self;
570
571    fn div(self, rhs: f64) -> Self::Output {
572        self.scaled(1.0 / rhs as f32)
573    }
574}
575
576/// A free-floating text label drawn on the map.
577///
578/// Labels are installed with [`Map::add_labels`](super::Map::add_labels).
579#[derive(Clone, Debug)]
580pub struct MapLabel {
581    /// The text to display.
582    pub text: String,
583    /// The position of the label's center.
584    pub center: Pos2,
585}
586
587impl Default for MapLabel {
588    fn default() -> Self {
589        MapLabel::new()
590    }
591}
592
593impl MapLabel {
594    /// Creates an empty label centered at the origin.
595    pub fn new() -> Self {
596        MapLabel {
597            text: String::new(),
598            center: Pos2::new(0.00, 0.00),
599        }
600    }
601}
602
603/// A connection line between two points on the map, ready to be stored in an
604/// [`rstar::RTree`].
605///
606/// Mirrors `sde::objects::SdeSegment`'s shape (`id`, `point1`, `point2`) so
607/// callers that already hold `sde` connection data can build one with a
608/// straight field-for-field copy; the only structural difference is `f32`
609/// instead of `f64` for the coordinates, matching `egui`'s own coordinate
610/// type (`egui` — and therefore this widget — doesn't work in `f64`).
611///
612/// Lines are installed with [`Map::add_hashmap_lines`](super::Map::add_hashmap_lines),
613/// keyed by an id that nodes reference through [`MapPoint::connections`].
614/// The bounding box the R-tree uses for broad-phase viewport culling and
615/// hit-testing is computed on demand from `point1`/`point2` in
616/// [`envelope`](rstar::RTreeObject::envelope) rather than cached on the
617/// struct, same as `SdeSegment`.
618#[derive(Clone, Copy, Debug, PartialEq)]
619pub struct MapSegment {
620    /// Identifier shared with the line key (and with the
621    /// [`MapPoint::connections`] of the endpoint nodes).
622    pub id: (usize, usize),
623    /// One endpoint of the segment, in map coordinates.
624    pub point1: [f32; 2],
625    /// The other endpoint of the segment, in map coordinates.
626    pub point2: [f32; 2],
627}
628
629impl MapSegment {
630    /// Creates a segment for `id` between `point1` and `point2`.
631    pub fn new(id: (usize, usize), point1: [f32; 2], point2: [f32; 2]) -> Self {
632        Self { id, point1, point2 }
633    }
634
635    /// The segment geometry as a [`RawLine`], for the distance/midpoint math
636    /// callers already get from that type.
637    pub(crate) fn raw_line(&self) -> RawLine {
638        RawLine::new(RawPoint::from(self.point1), RawPoint::from(self.point2))
639    }
640}
641
642impl rstar::RTreeObject for MapSegment {
643    type Envelope = AABB<[f32; 2]>;
644
645    fn envelope(&self) -> Self::Envelope {
646        AABB::from_corners(
647            [
648                self.point1[0].min(self.point2[0]),
649                self.point1[1].min(self.point2[1]),
650            ],
651            [
652                self.point1[0].max(self.point2[0]),
653                self.point1[1].max(self.point2[1]),
654            ],
655        )
656    }
657}
658
659/// A node on the map: an id, a 2D position and an optional display name.
660///
661/// Mirrors `sde::objects::SdePoint`'s shape (public `coords`/`id`/`name`/
662/// `connections` fields) so callers that already hold `sde` map-query data
663/// can build one with a straight field-for-field copy. Structural
664/// differences from `SdePoint`:
665///
666/// - `f32` instead of `f64` for `coords`, matching `egui`'s own coordinate
667///   type.
668/// - 2 components instead of 3: this widget only ever renders a 2D map, so
669///   there's no third axis to carry.
670/// - `id` is a plain `usize`, not `Option<usize>`. `SdePoint` uses `None`
671///   for a bare coordinate with no entity behind it (e.g. a bounding-box
672///   corner); every `MapPoint` loaded into the widget represents a real,
673///   placed node whose id is used directly as the point-set `HashMap` key
674///   and the kd-tree payload (see [`Map::add_hashmap_points`]), so an
675///   optional id would just push an `.unwrap()` (or a silently dropped
676///   node) into those call sites with no caller ever passing `None`.
677///
678/// Nodes are loaded into the widget through
679/// [`Map::add_hashmap_points`](super::Map::add_hashmap_points), keyed by their
680/// id.
681#[derive(Clone, Debug, PartialEq)]
682pub struct MapPoint {
683    /// Position of the node, in map coordinates.
684    pub coords: [f32; 2],
685    /// Node identifier, used for lookups, notifications and markers.
686    pub id: usize,
687    /// Display name shown next to the node; `None` if it was never set.
688    pub name: Option<String>,
689    /// Ids of the lines connecting this node with others.
690    ///
691    /// Each entry must match a key of the map passed to
692    /// [`Map::add_hashmap_lines`](super::Map::add_hashmap_lines) (and
693    /// [`MapSegment::id`]). The usual pattern is to push the same pair into
694    /// the `connections` of **both** endpoint nodes. Line visibility is
695    /// computed from the segment bounding boxes (R-tree), not from node
696    /// visibility, so a line is drawn whenever its bounding box intersects
697    /// the viewport.
698    pub connections: Vec<(usize, usize)>,
699    /// Persistent fill color for this node's default circle, in place of
700    /// [`NodeStyle::fill_color`](super::NodeStyle::fill_color). `None`
701    /// (the default) keeps today's behavior of every node sharing the
702    /// same style color.
703    ///
704    /// Only consulted by the built-in circle drawn when no
705    /// [`NodeTemplate`] is installed -- a custom template receives this
706    /// same `MapPoint` and decides for itself whether/how to use `color`.
707    pub color: Option<Color32>,
708}
709
710impl MapPoint {
711    /// Creates a node with the given `id` at the given map coordinates.
712    pub fn new(id: usize, coords: [f32; 2]) -> MapPoint {
713        MapPoint {
714            coords,
715            id,
716            connections: Vec::new(),
717            name: None,
718            color: None,
719        }
720    }
721
722    /// Returns the node identifier.
723    pub fn get_id(&self) -> usize {
724        self.id
725    }
726
727    /// Returns the node display name (empty if it was never set).
728    pub fn get_name(&self) -> String {
729        self.name.clone().unwrap_or_default()
730    }
731
732    /// Sets the node display name.
733    pub fn set_name(&mut self, value: String) {
734        self.name = Some(value);
735    }
736}
737
738impl From<std::collections::hash_map::OccupiedEntry<'_, usize, MapPoint>> for MapPoint {
739    fn from(value: std::collections::hash_map::OccupiedEntry<'_, usize, MapPoint>) -> Self {
740        let k = value.get();
741        k.clone()
742    }
743}
744
745#[derive(Clone)]
746pub(crate) struct MapBounds {
747    pub min: RawPoint,
748    pub max: RawPoint,
749    pub pos: RawPoint,
750    pub dist: f32,
751}
752
753impl MapBounds {
754    pub fn new() -> Self {
755        MapBounds {
756            min: RawPoint::default(),
757            max: RawPoint::default(),
758            pos: RawPoint::default(),
759            dist: 0.0,
760        }
761    }
762}
763
764impl Default for MapBounds {
765    fn default() -> Self {
766        MapBounds::new()
767    }
768}
769
770pub(crate) struct TextSettings {
771    pub position: RawPoint,
772    pub anchor: Align2,
773    pub text: String,
774    pub size: f32,
775    pub family: FontFamily,
776    pub text_color: Color32,
777}
778
779/// Configuration of a [`Map`](super::Map) widget.
780///
781/// [`MapSettings::default()`] provides sensible zoom limits plus a light and a
782/// dark theme; the widget picks the style to apply based on
783/// [`egui::Visuals::dark_mode`], using `styles[0]` in light mode and
784/// `styles[1]` in dark mode.
785#[derive(Clone, Debug)]
786pub struct MapSettings {
787    /// Maximum zoom factor.
788    pub max_zoom: f32,
789    /// Minimum zoom factor.
790    pub min_zoom: f32,
791    /// Zoom threshold above which connection lines become visible.
792    pub line_visible_zoom: f32,
793    /// Zoom threshold above which node names become visible when
794    /// [`node_text_visibility`](Self::node_text_visibility) is
795    /// [`VisibilitySetting::Always`].
796    pub label_visible_zoom: f32,
797    /// Controls when node names are displayed.
798    pub node_text_visibility: VisibilitySetting,
799    /// Effect drawn on nodes registered with
800    /// [`Map::update_marker`](super::Map::update_marker).
801    ///
802    /// Persistent, so it keeps the app repainting for as long as a marker
803    /// exists. Ignored when a [`NodeTemplate`] is installed.
804    ///
805    /// Node *state* set through [`NodeHandle`](super::NodeHandle) picks its own
806    /// effect per node and does not read this field.
807    pub marker_animation: SteadyAnimation,
808    /// Font size, **in screen pixels**, of the node names.
809    ///
810    /// This is a screen-space size: it deliberately does *not* scale with the
811    /// zoom factor, so a name stays exactly as readable when the map is zoomed
812    /// all the way out as when it is zoomed in. Because the nodes pack closer
813    /// together as you zoom out while the names keep their size, names take up
814    /// proportionally more of the view down there — use
815    /// [`label_visible_zoom`](Self::label_visible_zoom) or
816    /// [`node_text_visibility`](Self::node_text_visibility) to control when
817    /// they are worth showing at all.
818    pub node_text_size: f32,
819    /// Font size, **in screen pixels**, of the free-floating [`MapLabel`]s.
820    ///
821    /// Screen-space, exactly like [`node_text_size`](Self::node_text_size).
822    pub label_text_size: f32,
823    /// Per-theme styles; index `0` is used in light mode, index `1` in dark
824    /// mode.
825    pub styles: Vec<MapStyle>,
826}
827
828impl MapSettings {
829    /// Creates settings with all zoom thresholds set to `0.0` and a single
830    /// transparent style.
831    ///
832    /// Prefer [`MapSettings::default()`] unless you really need to build the
833    /// configuration from scratch.
834    pub fn new() -> Self {
835        MapSettings {
836            max_zoom: 0.0,
837            min_zoom: 0.0,
838            line_visible_zoom: 0.0,
839            label_visible_zoom: 0.0,
840            node_text_visibility: VisibilitySetting::Always,
841            marker_animation: SteadyAnimation::Blink,
842            node_text_size: 12.0,
843            label_text_size: 24.0,
844            styles: vec![MapStyle::new()],
845        }
846    }
847}
848
849impl Default for MapSettings {
850    /// Returns the default configuration: zoom from `0.1` to `2.0`, connection
851    /// lines visible above `0.2`, node names above `0.58`, and built-in light
852    /// and dark themes.
853    fn default() -> Self {
854        let mut obj = MapSettings {
855            max_zoom: 2.0,
856            min_zoom: 0.1,
857            line_visible_zoom: 0.2,
858            label_visible_zoom: 0.58,
859            node_text_visibility: VisibilitySetting::Always,
860            marker_animation: SteadyAnimation::Blink,
861            node_text_size: 12.0,
862            label_text_size: 24.0,
863            styles: Vec::new(),
864        };
865
866        // light Theme
867        obj.styles.push(MapStyle {
868            border: Some(egui::Stroke {
869                width: 2.0,
870                color: Color32::from_rgb(216, 142, 58),
871            }),
872            line: Some(egui::Stroke {
873                width: 2.0,
874                color: Color32::DARK_RED,
875            }),
876            fill_color: Color32::from_rgb(216, 142, 58),
877            text_color: Color32::DARK_GREEN,
878            font: Some(FontId::new(12.00, FontFamily::Proportional)),
879            background_color: Color32::WHITE,
880            alert_color: Color32::from_rgb(246, 30, 131),
881        });
882
883        // Dark Theme
884        obj.styles.push(MapStyle {
885            border: Some(egui::Stroke {
886                width: 2.0,
887                color: Color32::GOLD,
888            }),
889            line: Some(egui::Stroke {
890                width: 2.0,
891                color: Color32::LIGHT_RED,
892            }),
893            fill_color: Color32::GOLD,
894            text_color: Color32::LIGHT_GREEN,
895            font: Some(FontId::new(12.00, FontFamily::Proportional)),
896            background_color: Color32::DARK_GRAY,
897            alert_color: Color32::from_rgb(128, 12, 67),
898        });
899        obj
900    }
901}
902
903/// A built-in effect that plays once and ends.
904///
905/// Anchored to the [`Instant`] an event happened, these are the animations
906/// reached through [`NodeHandle`](super::NodeHandle): `map.node(id)?.ripple(t)`.
907/// The widget drops the notification and stops repainting once the effect
908/// finishes. See [`crate::map::animation`] for what each looks like and how
909/// long it runs.
910///
911/// Ignored when a [`NodeTemplate`] is installed — the template's
912/// `notification_ui` takes over. The effects stay reachable there through
913/// [`Animation`](crate::map::animation::Animation).
914#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
915pub enum NodeAnimation {
916    /// Expanding, fading disc. Reads as "one thing happened here".
917    #[default]
918    Pulse,
919    /// Three staggered expanding rings. Reads as "activity is ongoing".
920    Ripple,
921    /// A ring that empties clockwise. Reads as "how old is this information".
922    CountdownArc,
923    /// A disc that overshoots its size and settles. For nodes that just appeared.
924    ScaleIn,
925    /// Four ticks converging on the node. Reads as "target acquired".
926    Crosshair,
927}
928
929/// A built-in effect that runs until it is cleared.
930///
931/// Named after how long it lasts rather than after who uses it, because it has
932/// two consumers: node state set through [`NodeHandle`](super::NodeHandle)
933/// (`map.node(id)?.halo()`), and markers registered with
934/// [`Map::update_marker`](super::Map::update_marker), which pick their look
935/// with [`MapSettings::marker_animation`].
936///
937/// These never end, so the widget keeps requesting repaints for as long as one
938/// is active. That is fine for the handful of elements they are meant for, but
939/// it does keep the app redrawing — see [`crate::map::animation`].
940#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
941pub enum SteadyAnimation {
942    /// Thick ring blinking on and off. The long-standing marker look.
943    #[default]
944    Blink,
945    /// Ring whose opacity breathes in and out. Calmer than [`Self::Blink`].
946    Halo,
947    /// A dot circling the node. Reads as "under observation".
948    Orbit,
949}
950
951/// Controls when the name of a node is displayed next to it.
952#[derive(Clone, Debug, PartialEq)]
953pub enum VisibilitySetting {
954    /// Never show node names.
955    Hidden,
956    /// Only show the name of the node closest to the mouse pointer.
957    Hover,
958    /// Always show node names, subject to [`MapSettings::label_visible_zoom`].
959    Always,
960}
961
962/// Provides the contents of the widget's right-click context menu.
963///
964/// Install an implementation with
965/// [`Map::set_context_manager`](super::Map::set_context_manager).
966///
967/// # Examples
968///
969/// ```
970/// use egui_map::map::objects::ContextMenuManager;
971///
972/// struct MyMenu;
973///
974/// impl ContextMenuManager for MyMenu {
975///     fn ui(&self, ui: &mut egui::Ui) {
976///         ui.label("Hello from the map!");
977///     }
978/// }
979/// ```
980pub trait ContextMenuManager {
981    /// Builds the menu contents; called every frame while the menu is open.
982    fn ui(&self, ui: &mut Ui);
983}
984
985/// Customizes how nodes and their visual effects are rendered.
986///
987/// When a template is installed with
988/// [`Map::set_node_template`](super::Map::set_node_template), the widget
989/// delegates all node painting to it instead of using the built-in shapes and
990/// animations — including the node name labels, so draw the name yourself in
991/// [`NodeTemplate::node_ui`] if you need it.
992///
993/// The positions passed to these methods are in screen coordinates: already
994/// scaled by `zoom` and translated to the viewport origin. Multiply every size
995/// by `zoom` so your shapes scale together with the map.
996///
997/// # Animation idioms
998///
999/// egui only repaints on demand, so any method that animates (a blinking
1000/// marker, a fading notification, ...) must call
1001/// [`ui.ctx().request_repaint()`](egui::Context::request_repaint) to keep the
1002/// frames coming. Time-driven effects are usually computed from
1003/// [`Instant::now()`] (see `initial_time` in
1004/// [`NodeTemplate::notification_ui`]) or from the system clock.
1005///
1006/// # Examples
1007///
1008/// A node drawn as a rounded box with its name inside, plus a notification
1009/// animation that expands and fades out over two seconds:
1010///
1011/// ```
1012/// use egui_map::map::objects::{MapPoint, NodeTemplate};
1013/// use egui::{Align2, Color32, CornerRadius, FontId, Pos2, Rect, Stroke, Ui, Vec2};
1014/// use std::time::Instant;
1015///
1016/// struct BoxedNodes;
1017///
1018/// impl NodeTemplate for BoxedNodes {
1019///     fn node_ui(&self, ui: &mut Ui, position: Pos2, zoom: f32, point: &MapPoint) {
1020///         // Multiply every size by `zoom` so the node scales with the map.
1021///         let rect = Rect::from_center_size(position, Vec2::new(90.0 * zoom, 35.0 * zoom));
1022///         let rounding = CornerRadius::same((10.0 * zoom) as u8);
1023///         let painter = ui.painter();
1024///         painter.rect_filled(rect, rounding, ui.visuals().extreme_bg_color);
1025///         painter.rect_stroke(
1026///             rect,
1027///             rounding,
1028///             Stroke::new(4.0 * zoom, Color32::WHITE),
1029///             egui::StrokeKind::Middle,
1030///         );
1031///         painter.text(
1032///             position,
1033///             Align2::CENTER_CENTER,
1034///             point.get_name(),
1035///             FontId::proportional(12.0 * zoom),
1036///             Color32::WHITE,
1037///         );
1038///     }
1039///
1040///     fn notification_ui(
1041///         &self,
1042///         ui: &mut Ui,
1043///         position: Pos2,
1044///         zoom: f32,
1045///         initial_time: Instant,
1046///         color: Color32,
1047///     ) -> bool {
1048///         let secs = Instant::now().duration_since(initial_time).as_secs_f32();
1049///         // Expand the stroke and fade the color out over 2 seconds.
1050///         let alpha = (1.0 - secs / 2.0).clamp(0.0, 1.0);
1051///         let fading =
1052///             Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), (255.0 * alpha) as u8);
1053///         let rect = Rect::from_center_size(position, Vec2::new(90.0 * zoom, 35.0 * zoom));
1054///         ui.painter().rect_stroke(
1055///             rect,
1056///             CornerRadius::same((10.0 * zoom) as u8),
1057///             Stroke::new((4.0 + 25.0 * secs) * zoom, fading),
1058///             egui::StrokeKind::Middle,
1059///         );
1060///         // Keep the animation frames coming.
1061///         ui.ctx().request_repaint();
1062///         // Returning `false` removes the notification.
1063///         secs < 2.0
1064///     }
1065///     # fn selection_ui(&self, ui: &mut Ui, point: Pos2, zoom: f32) {
1066///     #     let rect = Rect::from_center_size(point, Vec2::new(94.0 * zoom, 39.0 * zoom));
1067///     #     ui.painter().rect_stroke(
1068///     #         rect,
1069///     #         CornerRadius::same((10.0 * zoom) as u8),
1070///     #         Stroke::new(3.0 * zoom, Color32::YELLOW),
1071///     #         egui::StrokeKind::Middle,
1072///     #     );
1073///     # }
1074///     # fn marker_ui(&self, ui: &mut Ui, point: Pos2, zoom: f32) {
1075///     #     ui.painter().circle_stroke(point, 6.0 * zoom, Stroke::new(2.0 * zoom, Color32::LIGHT_GREEN));
1076///     #     ui.ctx().request_repaint();
1077///     # }
1078/// }
1079/// ```
1080pub trait NodeTemplate {
1081    /// Draws a node, replacing the default filled circle.
1082    ///
1083    /// Called every frame for each visible node. The widget no longer draws
1084    /// the node name once a template is installed, so render it here (e.g.
1085    /// with [`Painter::text`](egui::Painter::text)) if you need it.
1086    fn node_ui(&self, ui: &mut Ui, _viewport_position: Pos2, _zoom: f32, _point: &MapPoint);
1087
1088    /// Draws the highlight over the node closest to the mouse pointer.
1089    ///
1090    /// The nearest node is only computed while the pointer is over the map and
1091    /// [`MapSettings::node_text_visibility`] is [`VisibilitySetting::Hover`].
1092    fn selection_ui(&self, ui: &mut Ui, _viewport_position: Pos2, _zoom: f32);
1093
1094    /// Draws the notification effect of a node notified at `initial_time`.
1095    ///
1096    /// Called every frame for each node passed to
1097    /// [`Map::notify`](super::Map::notify). Should return `true` while the
1098    /// animation is still playing — remember to call
1099    /// [`ui.ctx().request_repaint()`](egui::Context::request_repaint) —; once
1100    /// it returns `false` the notification is discarded.
1101    fn notification_ui(
1102        &self,
1103        ui: &mut Ui,
1104        _viewport_position: Pos2,
1105        _zoom: f32,
1106        initial_time: Instant,
1107        color: Color32,
1108    ) -> bool;
1109
1110    /// Draws a marker over the given node.
1111    ///
1112    /// Called every frame for each marker registered with
1113    /// [`Map::update_marker`](super::Map::update_marker). For animated markers
1114    /// (e.g. a blinking light), drive the effect from the system clock and
1115    /// call [`ui.ctx().request_repaint()`](egui::Context::request_repaint).
1116    fn marker_ui(&self, ui: &mut Ui, _viewport_position: Pos2, _zoom: f32);
1117}
1118
1119#[cfg(test)]
1120mod tests {
1121    use super::*;
1122    use std::collections::HashMap;
1123
1124    // ---------- RawPoint ----------
1125
1126    #[test]
1127    fn raw_point_new() {
1128        let p = RawPoint::new(3.5, -2.0);
1129        assert_eq!(p.components, [3.5, -2.0]);
1130    }
1131
1132    // ---------- MapSegment ----------
1133
1134    #[test]
1135    fn map_segment_new_computes_tight_aabb() {
1136        let seg = MapSegment::new((1, 2), [10.0, -5.0], [-2.0, 7.0]);
1137        assert_eq!(seg.id, (1, 2));
1138        let envelope: AABB<[f32; 2]> = rstar::RTreeObject::envelope(&seg);
1139        assert_eq!(envelope.lower(), [-2.0, -5.0]);
1140        assert_eq!(envelope.upper(), [10.0, 7.0]);
1141        assert_eq!(seg.raw_line().points[0].components, [10.0, -5.0]);
1142        assert_eq!(seg.raw_line().points[1].components, [-2.0, 7.0]);
1143    }
1144
1145    #[test]
1146    fn map_segment_envelope_returns_its_aabb() {
1147        let seg = MapSegment::new((1, 2), [0.0, 0.0], [4.0, 2.0]);
1148        let envelope: AABB<[f32; 2]> = rstar::RTreeObject::envelope(&seg);
1149        assert_eq!(envelope.lower(), [0.0, 0.0]);
1150        assert_eq!(envelope.upper(), [4.0, 2.0]);
1151    }
1152
1153    #[test]
1154    fn map_segment_degenerate_line_has_point_aabb() {
1155        // A zero-length segment must still produce a valid (empty-area) AABB.
1156        let seg = MapSegment::new((1, 2), [3.0, 3.0], [3.0, 3.0]);
1157        let envelope: AABB<[f32; 2]> = rstar::RTreeObject::envelope(&seg);
1158        assert_eq!(envelope.lower(), [3.0, 3.0]);
1159        assert_eq!(envelope.upper(), [3.0, 3.0]);
1160    }
1161
1162    #[test]
1163    fn raw_point_default() {
1164        let p = RawPoint::default();
1165        assert_eq!(p.components, [0.0, 0.0]);
1166    }
1167
1168    #[test]
1169    fn raw_point_mul_i64() {
1170        let p = RawPoint::new(2.0, -3.0) * 3i64;
1171        assert_eq!(p.components, [6.0, -9.0]);
1172    }
1173
1174    #[test]
1175    fn raw_point_mul_i32() {
1176        let p = RawPoint::new(2.0, -3.0) * 3i32;
1177        assert_eq!(p.components, [6.0, -9.0]);
1178    }
1179
1180    #[test]
1181    fn raw_point_mul_u64() {
1182        let p = RawPoint::new(2.0, -3.0) * 3u64;
1183        assert_eq!(p.components, [6.0, -9.0]);
1184    }
1185
1186    #[test]
1187    fn raw_point_mul_u32() {
1188        let p = RawPoint::new(2.0, -3.0) * 3u32;
1189        assert_eq!(p.components, [6.0, -9.0]);
1190    }
1191
1192    #[test]
1193    fn raw_point_mul_f32() {
1194        let p = RawPoint::new(2.0, -3.0) * 0.5f32;
1195        assert_eq!(p.components, [1.0, -1.5]);
1196    }
1197
1198    #[test]
1199    fn raw_point_mul_assign_i64() {
1200        let mut p = RawPoint::new(2.0, -3.0);
1201        p *= 3i64;
1202        assert_eq!(p.components, [6.0, -9.0]);
1203    }
1204
1205    #[test]
1206    fn raw_point_mul_assign_i32() {
1207        let mut p = RawPoint::new(2.0, -3.0);
1208        p *= 3i32;
1209        assert_eq!(p.components, [6.0, -9.0]);
1210    }
1211
1212    #[test]
1213    fn raw_point_mul_assign_u64() {
1214        let mut p = RawPoint::new(2.0, -3.0);
1215        p *= 3u64;
1216        assert_eq!(p.components, [6.0, -9.0]);
1217    }
1218
1219    #[test]
1220    fn raw_point_mul_assign_u32() {
1221        let mut p = RawPoint::new(2.0, -3.0);
1222        p *= 3u32;
1223        assert_eq!(p.components, [6.0, -9.0]);
1224    }
1225
1226    #[test]
1227    fn raw_point_mul_assign_f32() {
1228        let mut p = RawPoint::new(2.0, -3.0);
1229        p *= 0.5f32;
1230        assert_eq!(p.components, [1.0, -1.5]);
1231    }
1232
1233    #[test]
1234    fn raw_point_div_i64() {
1235        let p = RawPoint::new(6.0, -9.0) / 3i64;
1236        assert_eq!(p.components, [2.0, -3.0]);
1237    }
1238
1239    #[test]
1240    fn raw_point_div_i32() {
1241        let p = RawPoint::new(6.0, -9.0) / 3i32;
1242        assert_eq!(p.components, [2.0, -3.0]);
1243    }
1244
1245    #[test]
1246    fn raw_point_div_u64() {
1247        let p = RawPoint::new(6.0, -9.0) / 3u64;
1248        assert_eq!(p.components, [2.0, -3.0]);
1249    }
1250
1251    #[test]
1252    fn raw_point_div_u32() {
1253        let p = RawPoint::new(6.0, -9.0) / 3u32;
1254        assert_eq!(p.components, [2.0, -3.0]);
1255    }
1256
1257    #[test]
1258    fn raw_point_div_f32() {
1259        let p = RawPoint::new(1.0, -1.5) / 0.5f32;
1260        assert_eq!(p.components, [2.0, -3.0]);
1261    }
1262
1263    #[test]
1264    fn raw_point_div_assign_i64() {
1265        let mut p = RawPoint::new(6.0, -9.0);
1266        p /= 3i64;
1267        assert_eq!(p.components, [2.0, -3.0]);
1268    }
1269
1270    #[test]
1271    fn raw_point_div_assign_i32() {
1272        let mut p = RawPoint::new(6.0, -9.0);
1273        p /= 3i32;
1274        assert_eq!(p.components, [2.0, -3.0]);
1275    }
1276
1277    #[test]
1278    fn raw_point_div_assign_u64() {
1279        let mut p = RawPoint::new(6.0, -9.0);
1280        p /= 3u64;
1281        assert_eq!(p.components, [2.0, -3.0]);
1282    }
1283
1284    #[test]
1285    fn raw_point_div_assign_u32() {
1286        let mut p = RawPoint::new(6.0, -9.0);
1287        p /= 3u32;
1288        assert_eq!(p.components, [2.0, -3.0]);
1289    }
1290
1291    #[test]
1292    fn raw_point_div_assign_f32() {
1293        let mut p = RawPoint::new(1.0, -1.5);
1294        p /= 0.5f32;
1295        assert_eq!(p.components, [2.0, -3.0]);
1296    }
1297
1298    #[test]
1299    fn raw_point_add() {
1300        let a = RawPoint::new(1.0, 2.0);
1301        let b = RawPoint::new(3.0, -4.0);
1302        let c = a + b;
1303        assert_eq!(c.components, [4.0, -2.0]);
1304    }
1305
1306    #[test]
1307    fn raw_point_sub() {
1308        let a = RawPoint::new(1.0, 2.0);
1309        let b = RawPoint::new(3.0, -4.0);
1310        let c = a - b;
1311        assert_eq!(c.components, [-2.0, 6.0]);
1312    }
1313
1314    #[test]
1315    #[allow(clippy::op_ref)] // se prueba a propósito la impl Add<&RawPoint>
1316    fn raw_point_add_ref() {
1317        let a = RawPoint::new(1.0, 2.0);
1318        let b = RawPoint::new(3.0, -4.0);
1319        let c = a + &b;
1320        assert_eq!(c.components, [4.0, -2.0]);
1321        // b sigue siendo usable tras la suma por referencia
1322        assert_eq!(b.components, [3.0, -4.0]);
1323    }
1324
1325    #[test]
1326    #[allow(clippy::op_ref)] // se prueba a propósito la impl Sub<&RawPoint>
1327    fn raw_point_sub_ref() {
1328        let a = RawPoint::new(1.0, 2.0);
1329        let b = RawPoint::new(3.0, -4.0);
1330        let c = a - &b;
1331        assert_eq!(c.components, [-2.0, 6.0]);
1332        assert_eq!(b.components, [3.0, -4.0]);
1333    }
1334
1335    #[test]
1336    fn raw_point_from_f32_array() {
1337        let p = RawPoint::from([1.5f32, -2.5f32]);
1338        assert_eq!(p.components, [1.5, -2.5]);
1339    }
1340
1341    #[test]
1342    fn raw_point_from_i64_array() {
1343        let p = RawPoint::from([3i64, -4i64]);
1344        assert_eq!(p.components, [3.0, -4.0]);
1345    }
1346
1347    #[test]
1348    fn raw_point_from_i32_array() {
1349        let p = RawPoint::from([3i32, -4i32]);
1350        assert_eq!(p.components, [3.0, -4.0]);
1351    }
1352
1353    #[test]
1354    fn raw_point_from_i16_array() {
1355        let p = RawPoint::from([3i16, -4i16]);
1356        assert_eq!(p.components, [3.0, -4.0]);
1357    }
1358
1359    #[test]
1360    fn raw_point_from_i8_array() {
1361        let p = RawPoint::from([3i8, -4i8]);
1362        assert_eq!(p.components, [3.0, -4.0]);
1363    }
1364
1365    #[test]
1366    fn raw_point_from_pos2() {
1367        let p = RawPoint::from(Pos2::new(7.0, 8.0));
1368        assert_eq!(p.components, [7.0, 8.0]);
1369    }
1370
1371    #[test]
1372    fn raw_point_into_f32_array() {
1373        let arr: [f32; 2] = RawPoint::new(7.0, 8.0).into();
1374        assert_eq!(arr, [7.0, 8.0]);
1375    }
1376
1377    #[test]
1378    fn raw_point_into_pos2() {
1379        let pos: Pos2 = RawPoint::new(7.0, 8.0).into();
1380        assert_eq!(pos, Pos2::new(7.0, 8.0));
1381    }
1382
1383    // ---------- RawLine ----------
1384
1385    #[test]
1386    fn raw_line_new() {
1387        let a = RawPoint::new(1.0, 2.0);
1388        let b = RawPoint::new(3.0, 4.0);
1389        let line = RawLine::new(a, b);
1390        assert_eq!(line.points[0].components, [1.0, 2.0]);
1391        assert_eq!(line.points[1].components, [3.0, 4.0]);
1392    }
1393
1394    #[test]
1395    fn raw_line_distance() {
1396        // triángulo 3-4-5
1397        let line = RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(3.0, 4.0));
1398        assert_eq!(line.distance(), 5.0);
1399    }
1400
1401    #[test]
1402    fn raw_line_distance_zero() {
1403        let line = RawLine::new(RawPoint::new(2.0, 2.0), RawPoint::new(2.0, 2.0));
1404        assert_eq!(line.distance(), 0.0);
1405    }
1406
1407    #[test]
1408    fn raw_line_midpoint() {
1409        let line = RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(4.0, 6.0));
1410        let mid = line.midpoint();
1411        assert_eq!(mid.components, [2.0, 3.0]);
1412    }
1413
1414    #[test]
1415    fn raw_line_distance_to_point_on_segment() {
1416        let line = RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(10.0, 0.0));
1417        assert_eq!(line.distance_to_point(RawPoint::new(5.0, 3.0)), 3.0);
1418        assert_eq!(line.distance_to_point(RawPoint::new(5.0, 0.0)), 0.0);
1419    }
1420
1421    #[test]
1422    fn raw_line_distance_to_point_beyond_endpoints() {
1423        let line = RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(10.0, 0.0));
1424        // Past the end of the segment, the closest point is the endpoint.
1425        assert_eq!(line.distance_to_point(RawPoint::new(14.0, 0.0)), 4.0);
1426        assert_eq!(line.distance_to_point(RawPoint::new(-3.0, -4.0)), 5.0);
1427    }
1428
1429    #[test]
1430    fn raw_line_distance_to_point_degenerate_segment() {
1431        let line = RawLine::new(RawPoint::new(1.0, 1.0), RawPoint::new(1.0, 1.0));
1432        assert_eq!(line.distance_to_point(RawPoint::new(4.0, 5.0)), 5.0);
1433    }
1434
1435    #[test]
1436    fn raw_line_into_pos2_array() {
1437        let line = RawLine::new(RawPoint::new(1.0, 2.0), RawPoint::new(3.0, 4.0));
1438        let arr: [Pos2; 2] = line.into();
1439        assert_eq!(arr, [Pos2::new(1.0, 2.0), Pos2::new(3.0, 4.0)]);
1440    }
1441
1442    #[test]
1443    fn raw_line_from_i64_arrays() {
1444        let line = RawLine::from([[1i64, 2i64], [3i64, 4i64]]);
1445        assert_eq!(line.points[0].components, [1.0, 2.0]);
1446        assert_eq!(line.points[1].components, [3.0, 4.0]);
1447    }
1448
1449    // ---------- MapStyle ----------
1450
1451    fn full_style() -> MapStyle {
1452        MapStyle {
1453            border: Some(Stroke::new(2.0, Color32::RED)),
1454            line: Some(Stroke::new(4.0, Color32::BLUE)),
1455            fill_color: Color32::GREEN,
1456            text_color: Color32::WHITE,
1457            font: Some(FontId::new(10.0, FontFamily::Proportional)),
1458            background_color: Color32::BLACK,
1459            alert_color: Color32::YELLOW,
1460        }
1461    }
1462
1463    #[test]
1464    fn map_style_new() {
1465        let s = MapStyle::new();
1466        assert!(s.border.is_none());
1467        assert!(s.line.is_none());
1468        assert!(s.font.is_none());
1469        assert_eq!(s.fill_color, Color32::TRANSPARENT);
1470        assert_eq!(s.text_color, Color32::TRANSPARENT);
1471        assert_eq!(s.background_color, Color32::TRANSPARENT);
1472        assert_eq!(s.alert_color, Color32::TRANSPARENT);
1473    }
1474
1475    #[test]
1476    fn map_style_default_equals_new() {
1477        let s = MapStyle::default();
1478        assert!(s.border.is_none());
1479        assert!(s.line.is_none());
1480        assert!(s.font.is_none());
1481    }
1482
1483    #[test]
1484    fn map_style_mul_i64() {
1485        let s = full_style() * 2i64;
1486        assert_eq!(s.border.unwrap().width, 4.0);
1487        assert_eq!(s.line.unwrap().width, 8.0);
1488        assert_eq!(s.font.unwrap().size, 20.0);
1489    }
1490
1491    #[test]
1492    fn map_style_mul_i32() {
1493        let s = full_style() * 2i32;
1494        assert_eq!(s.border.unwrap().width, 4.0);
1495        assert_eq!(s.line.unwrap().width, 8.0);
1496        assert_eq!(s.font.unwrap().size, 20.0);
1497    }
1498
1499    #[test]
1500    fn map_style_mul_f32() {
1501        let s = full_style() * 0.5f32;
1502        assert_eq!(s.border.unwrap().width, 1.0);
1503        assert_eq!(s.line.unwrap().width, 2.0);
1504        assert_eq!(s.font.unwrap().size, 5.0);
1505    }
1506
1507    #[test]
1508    fn map_style_mul_f64() {
1509        let s = full_style() * 0.5f64;
1510        assert_eq!(s.border.unwrap().width, 1.0);
1511        assert_eq!(s.line.unwrap().width, 2.0);
1512        assert_eq!(s.font.unwrap().size, 5.0);
1513    }
1514
1515    #[test]
1516    fn map_style_div_i64() {
1517        let s = full_style() / 2i64;
1518        assert_eq!(s.border.unwrap().width, 1.0);
1519        assert_eq!(s.line.unwrap().width, 2.0);
1520        assert_eq!(s.font.unwrap().size, 5.0);
1521    }
1522
1523    #[test]
1524    fn map_style_div_i32() {
1525        let s = full_style() / 2i32;
1526        assert_eq!(s.border.unwrap().width, 1.0);
1527        assert_eq!(s.line.unwrap().width, 2.0);
1528        assert_eq!(s.font.unwrap().size, 5.0);
1529    }
1530
1531    #[test]
1532    fn map_style_div_f32() {
1533        let s = full_style() / 0.5f32;
1534        assert_eq!(s.border.unwrap().width, 4.0);
1535        assert_eq!(s.line.unwrap().width, 8.0);
1536        assert_eq!(s.font.unwrap().size, 20.0);
1537    }
1538
1539    #[test]
1540    fn map_style_div_f64() {
1541        let s = full_style() / 0.5f64;
1542        assert_eq!(s.border.unwrap().width, 4.0);
1543        assert_eq!(s.line.unwrap().width, 8.0);
1544        assert_eq!(s.font.unwrap().size, 20.0);
1545    }
1546
1547    // ---------- MapLabel ----------
1548
1549    #[test]
1550    fn map_label_new() {
1551        let l = MapLabel::new();
1552        assert_eq!(l.text, String::new());
1553        assert_eq!(l.center, Pos2::new(0.0, 0.0));
1554    }
1555
1556    #[test]
1557    fn map_label_default_equals_new() {
1558        let l = MapLabel::default();
1559        assert_eq!(l.text, String::new());
1560        assert_eq!(l.center, Pos2::new(0.0, 0.0));
1561    }
1562
1563    // ---------- MapPoint ----------
1564
1565    #[test]
1566    fn map_point_new() {
1567        let p = MapPoint::new(42, [1.0, 2.0]);
1568        assert_eq!(p.get_id(), 42);
1569        assert_eq!(p.coords, [1.0, 2.0]);
1570        assert!(p.connections.is_empty());
1571        assert_eq!(p.name, None);
1572        assert_eq!(p.get_name(), String::new());
1573    }
1574
1575    #[test]
1576    fn map_point_set_and_get_name() {
1577        let mut p = MapPoint::new(1, [0.0, 0.0]);
1578        p.set_name("Jita".to_string());
1579        assert_eq!(p.name, Some("Jita".to_string()));
1580        assert_eq!(p.get_name(), "Jita");
1581    }
1582
1583    #[test]
1584    fn map_point_from_occupied_entry() {
1585        let mut map: HashMap<usize, MapPoint> = HashMap::new();
1586        let mut original = MapPoint::new(7, [5.0, 6.0]);
1587        original.set_name("Amarr".to_string());
1588        map.insert(7, original);
1589
1590        use std::collections::hash_map::Entry;
1591        if let Entry::Occupied(entry) = map.entry(7) {
1592            let cloned = MapPoint::from(entry);
1593            assert_eq!(cloned.get_id(), 7);
1594            assert_eq!(cloned.get_name(), "Amarr");
1595            assert_eq!(cloned.coords, [5.0, 6.0]);
1596        } else {
1597            panic!("se esperaba una entrada ocupada");
1598        }
1599    }
1600
1601    // ---------- MapBounds ----------
1602
1603    #[test]
1604    fn map_bounds_new() {
1605        let b = MapBounds::new();
1606        assert_eq!(b.min.components, [0.0, 0.0]);
1607        assert_eq!(b.max.components, [0.0, 0.0]);
1608        assert_eq!(b.pos.components, [0.0, 0.0]);
1609        assert_eq!(b.dist, 0.0);
1610    }
1611
1612    #[test]
1613    fn map_bounds_default_equals_new() {
1614        let b = MapBounds::default();
1615        assert_eq!(b.dist, 0.0);
1616        assert_eq!(b.pos.components, [0.0, 0.0]);
1617    }
1618
1619    // ---------- MapSettings ----------
1620
1621    #[test]
1622    fn map_settings_new() {
1623        let s = MapSettings::new();
1624        assert_eq!(s.max_zoom, 0.0);
1625        assert_eq!(s.min_zoom, 0.0);
1626        assert_eq!(s.line_visible_zoom, 0.0);
1627        assert_eq!(s.label_visible_zoom, 0.0);
1628        assert_eq!(s.node_text_visibility, VisibilitySetting::Always);
1629        assert_eq!(s.marker_animation, SteadyAnimation::Blink);
1630        assert_eq!(s.node_text_size, 12.0);
1631        assert_eq!(s.label_text_size, 24.0);
1632        assert_eq!(s.styles.len(), 1);
1633    }
1634
1635    #[test]
1636    fn map_settings_default() {
1637        let s = MapSettings::default();
1638        assert_eq!(s.max_zoom, 2.0);
1639        assert_eq!(s.min_zoom, 0.1);
1640        assert_eq!(s.line_visible_zoom, 0.2);
1641        assert_eq!(s.label_visible_zoom, 0.58);
1642        assert_eq!(s.node_text_visibility, VisibilitySetting::Always);
1643        assert_eq!(s.marker_animation, SteadyAnimation::Blink);
1644        assert_eq!(s.node_text_size, 12.0);
1645        assert_eq!(s.label_text_size, 24.0);
1646        // light + dark themes
1647        assert_eq!(s.styles.len(), 2);
1648        // light theme
1649        assert_eq!(s.styles[0].background_color, Color32::WHITE);
1650        assert!(s.styles[0].border.is_some());
1651        assert!(s.styles[0].line.is_some());
1652        assert!(s.styles[0].font.is_some());
1653        // dark theme
1654        assert_eq!(s.styles[1].background_color, Color32::DARK_GRAY);
1655        assert!(s.styles[1].border.is_some());
1656        assert!(s.styles[1].line.is_some());
1657        assert!(s.styles[1].font.is_some());
1658    }
1659
1660    // ---------- VisibilitySetting ----------
1661
1662    #[test]
1663    fn visibility_setting_equality() {
1664        assert_eq!(VisibilitySetting::Hidden, VisibilitySetting::Hidden);
1665        assert_eq!(VisibilitySetting::Hover, VisibilitySetting::Hover);
1666        assert_eq!(VisibilitySetting::Always, VisibilitySetting::Always);
1667        assert_ne!(VisibilitySetting::Hidden, VisibilitySetting::Hover);
1668        assert_ne!(VisibilitySetting::Hover, VisibilitySetting::Always);
1669        assert_ne!(VisibilitySetting::Hidden, VisibilitySetting::Always);
1670    }
1671}