# egui-map
An [`egui`](https://github.com/emilk/egui) widget that renders an interactive 2D map and displays information about it.
## Features
- Pan with click & drag, and zoom with the mouse wheel (hold `Ctrl` — or `Cmd` on macOS — to zoom faster) or the built-in slider.
- Spatial indexing via kd-tree: only the nodes inside the viewport are painted each frame.
- Node names with configurable visibility rules (always / on hover / hidden).
- Connection lines between nodes and free-floating text labels.
- Text is sized in **screen pixels** (`MapSettings::node_text_size`, `MapSettings::label_text_size`), so names stay readable at any zoom level instead of shrinking away as you zoom out.
- Animations attached per node through `map.node(id)`: one-off events that end on their own (`pulse`, `ripple`, `countdown`, `scale_in`, `crosshair`) and lasting state that runs until `clear()` (`halo`, `blink`, `orbit`), each with an optional `color()`. The effects live in `map::animation::Animation` and can be reused from your own `NodeTemplate`.
- Custom node rendering and right-click context menus through the `NodeTemplate` and `ContextMenuManager` traits.
- Built-in light and dark themes, customizable through `MapSettings`.
## Usage
Add the dependency:
```toml
[dependencies]
egui-map = "0.0"
```
Feed the map a set of nodes and add it to your UI:
```rust
use egui_map::map::Map;
use egui_map::map::objects::{MapPoint, RawPoint};
use std::collections::HashMap;
// Build the node set, keyed by node id.
let mut points: HashMap<usize, MapPoint> = HashMap::new();
points.insert(1, MapPoint::new(1, RawPoint::new(0.0, 0.0)));
points.insert(2, MapPoint::new(2, RawPoint::new(100.0, 50.0)));
let mut map = Map::new();
map.add_hashmap_points(points);
// Then, on every frame of your egui update loop:
// ui.add(&mut map);
```
### Connecting nodes with lines
Lines are wired in three steps: create the nodes, register a unique connection id in the `connections` of **both** endpoints, and load the line geometry keyed by that same id:
```rust
use egui_map::map::objects::{MapPoint, RawLine, RawPoint};
use std::collections::HashMap;
let mut points: HashMap<usize, MapPoint> = HashMap::new();
points.insert(1, MapPoint::new(1, RawPoint::new(0.0, 0.0)));
points.insert(2, MapPoint::new(2, RawPoint::new(10.0, 10.0)));
// Register the connection id on both endpoints.
for id in [1, 2] {
points.get_mut(&id).unwrap().connections.push("1-2".to_string());
}
map.add_hashmap_points(points);
// Line geometry, keyed by the same connection id.
let mut lines: HashMap<String, RawLine> = HashMap::new();
lines.insert("1-2".to_string(), RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(10.0, 10.0)));
map.add_lines(lines);
```
A line is only drawn while the zoom level is above `MapSettings::line_visible_zoom` and its bounding box intersects the viewport. Segments are culled broad-phase with an R-tree, so long lines crossing the view are drawn even when both endpoints lie outside of it.
### Custom node rendering and animations
Implement `NodeTemplate` to take over how nodes, selection highlights, notifications and markers are drawn — including the name labels, which the widget no longer paints once a template is installed:
```rust
use egui_map::map::objects::{MapPoint, NodeTemplate};
use egui::{Color32, Pos2, Ui};
use std::time::Instant;
struct MyTemplate;
impl NodeTemplate for MyTemplate {
fn node_ui(&self, ui: &mut Ui, position: Pos2, zoom: f32, point: &MapPoint) {
// `point` is the node's screen position; scale every size by `zoom`.
ui.painter().circle_filled(position, 6.0 * zoom, Color32::GOLD);
}
fn notification_ui(&self, ui: &mut Ui, position: Pos2, zoom: f32, start: Instant, color: Color32) -> bool {
// ... draw a time-driven effect computed from `start.elapsed()` ...
ui.ctx().request_repaint(); // keep the animation frames coming
start.elapsed().as_secs_f32() < 2.0 // returning false removes the notification
}
fn selection_ui(&self, _ui: &mut Ui, _position: Pos2, _zoom: f32) {}
fn marker_ui(&self, _ui: &mut Ui, _position: Pos2, _zoom: f32) {}
}
map.set_node_template(std::rc::Rc::new(MyTemplate));
```
See the `NodeTemplate` rustdoc for a complete example with a custom node shape and an animated notification.
## Crate features
- `debug_overlay`: adds a read-out of the widget's internal viewport state (bounds, current position, distance, zoom, node counts, pointer position). It stays out of the way: a dim `dbg` toggle in the map's top-left corner, collapsed by default and with no background of its own, that you click open when you need the numbers. egui remembers the open/closed state per widget, and the overlay never affects the map's layout.
## Profiling
The widget's hot paths (rendering, viewport culling, point/line loading) are instrumented with [`tracing`](https://docs.rs/tracing) spans. `tracing` is a normal, unconditional dependency of this crate, and the spans are cheap no-ops unless a subscriber is installed somewhere in your binary -- `egui-map` never installs one itself.
To see these spans in the [Tracy](https://github.com/wolfpld/tracy) profiler, install a `tracing_tracy::TracyLayer` in your own `main`, e.g.:
```rust
tracing_subscriber::registry()
.with(tracing_tracy::TracyLayer::default())
.init();
```
The `profile` feature pulls in `tracing-subscriber` and `tracing-tracy` so `examples/tracy_profile.rs` can demonstrate exactly this. Run it (with a Tracy capture window already listening) with:
```sh
cargo run --example tracy_profile --features profile
```
## License
MIT. See [LICENSE.md](LICENSE.md).