diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cb68cd5..8563576f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file. ### Breaking + * New `Center` variant - `Inertia`. It means that the map is moving due to inertia and + soon will stop with `Center` switching to `Exact`. To keep things easy, `Center` now + has new method `detached`, will returns a position the map is currently at, or `None` + if it just follows `my_position`. * `openstreetmap` is now in `walkers::providers` module. * `osm` example is now called `myapp` and it shows a small windows with an orthophotomap layer from . diff --git a/examples/myapp.rs b/examples/myapp.rs index af3d45f0..6fdbb1ed 100644 --- a/examples/myapp.rs +++ b/examples/myapp.rs @@ -14,6 +14,7 @@ struct MyApp { tiles: Tiles, geoportal_tiles: Tiles, map_memory: MapMemory, + satellite: bool, } impl MyApp { @@ -22,6 +23,7 @@ impl MyApp { tiles: Tiles::new(walkers::providers::openstreetmap, egui_ctx.to_owned()), geoportal_tiles: Tiles::new(walkers::providers::geoportal, egui_ctx), map_memory: MapMemory::default(), + satellite: false, } } } @@ -39,8 +41,15 @@ impl eframe::App for MyApp { // Typically this would be a GPS acquired position which is tracked by the map. let my_position = places::wroclaw_glowny(); + // Select either OSM standard map or satellite. + let tiles = if self.satellite { + &mut self.geoportal_tiles + } else { + &mut self.tiles + }; + // In egui, widgets are constructed and consumed in each frame. - let map = Map::new(Some(&mut self.tiles), &mut self.map_memory, my_position); + let map = Map::new(Some(tiles), &mut self.map_memory, my_position); // Optionally, a function which draw custom stuff on the map can be attached. let ctx_clone = ctx.clone(); @@ -57,14 +66,7 @@ impl eframe::App for MyApp { zoom(ui, &mut self.map_memory); go_to_my_position(ui, &mut self.map_memory); - - orthophotomap( - ui, - &mut self.geoportal_tiles, - &mut self.map_memory, - my_position, - ); - + satellite(ui, &mut self.satellite); acknowledge(ui); } }); @@ -122,7 +124,7 @@ fn draw_custom_shapes(ctx: Context, painter: Painter, projector: &Projector) { mod windows { use egui::{Align2, RichText, Ui, Window}; - use walkers::{Center, Map, MapMemory, Position, Tiles}; + use walkers::{Center, MapMemory}; pub fn acknowledge(ui: &Ui) { Window::new("Acknowledge") @@ -139,20 +141,15 @@ mod windows { }); } - pub fn orthophotomap( - ui: &Ui, - tiles: &mut Tiles, - map_memory: &mut MapMemory, - my_position: Position, - ) { - Window::new("Orthophotomap") + pub fn satellite(ui: &Ui, satellite: &mut bool) { + Window::new("Satellite") .collapsible(false) .resizable(false) .title_bar(false) .anchor(Align2::RIGHT_TOP, [-10., 10.]) .fixed_size([150., 150.]) .show(ui.ctx(), |ui| { - ui.add(Map::new(Some(tiles), map_memory, my_position)); + ui.checkbox(satellite, "satellite view"); }); } @@ -178,7 +175,7 @@ mod windows { /// When map is "detached", show a windows with an option to go back to my position. pub fn go_to_my_position(ui: &Ui, map_memory: &mut MapMemory) { - if let Center::Exact(position) = map_memory.center_mode { + if let Some(position) = map_memory.center_mode.detached() { Window::new("Center") .collapsible(false) .resizable(false) diff --git a/src/map.rs b/src/map.rs index 4fe17abe..14482342 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,6 +1,6 @@ use std::collections::{hash_map::Entry, HashMap}; -use egui::{Mesh, Painter, Pos2, Rect, Response, Sense, Ui, Vec2, Widget}; +use egui::{Context, Mesh, Painter, Pos2, Rect, Response, Sense, Ui, Vec2, Widget}; use crate::{ mercator::{screen_to_position, PositionExt, TileId}, @@ -83,22 +83,27 @@ impl Widget for Map<'_, '_> { fn ui(self, ui: &mut Ui) -> Response { let (rect, response) = ui.allocate_exact_size(ui.available_size(), Sense::drag()); - if response.hovered() { - let zoom_delta = ui.input(|input| input.zoom_delta()); + //if response.hovered() { + let zoom_delta = ui.input(|input| input.zoom_delta()); - // Zooming and dragging need to be exclusive, otherwise the map will get dragged when - // pinch gesture is used. - if !(0.99..=1.01).contains(&zoom_delta) { - // Shift by 1 because of the values given by zoom_delta(). Multiple by 2, because - // then it felt right with both mouse wheel, and an Android phone. - self.memory.zoom.zoom_by((zoom_delta - 1.) * 2.); - } else { - self.memory - .center_mode - .drag(&response, self.my_position, self.memory.zoom.round()); - } + // Zooming and dragging need to be exclusive, otherwise the map will get dragged when + // pinch gesture is used. + if !(0.99..=1.01).contains(&zoom_delta) { + // Shift by 1 because of the values given by zoom_delta(). Multiple by 2, because + // then it felt right with both mouse wheel, and an Android phone. + self.memory.zoom.zoom_by((zoom_delta - 1.) * 2.); + } else { + self.memory + .center_mode + .recalculate_drag(&response, self.my_position); } + self.memory.center_mode.recalculate_inertial_movement( + ui.ctx(), + self.my_position, + self.memory.zoom.round(), + ); + let map_center = self.memory.center_mode.position(self.my_position); let painter = ui.painter().with_clip_rect(rect); @@ -140,32 +145,76 @@ impl Widget for Map<'_, '_> { /// [`Center::MyPosition`]. #[derive(Clone, PartialEq, Default)] pub enum Center { - /// Center at `my_position` argument of the [`Map::new()`] function. + /// Centered at `my_position` argument of the [`Map::new()`] function. #[default] MyPosition, - /// Center at the exact position. + /// Centered at the exact position. Exact(Position), + + /// Map's currently moving due to inertia, and will slow down and stop after a short while. + Inertia { + position: Position, + direction: Vec2, + amount: f32, + }, } impl Center { - fn drag(&mut self, response: &Response, my_position: Position, zoom: u8) { + fn recalculate_drag(&mut self, response: &Response, my_position: Position) { if response.dragged_by(egui::PointerButton::Primary) { - // We always end up in some exact, "detached" position, regardless of the current mode. - *self = Center::Exact(screen_to_position( - self.position(my_position).project(zoom) - response.drag_delta(), - zoom, - )); + *self = Center::Inertia { + position: self.position(my_position), + direction: response.drag_delta(), + amount: 1.0, + }; } } - /// Get the real position at the map's center. - pub fn position(&self, my_position: Position) -> Position { + fn recalculate_inertial_movement(&mut self, ctx: &Context, my_position: Position, zoom: u8) { + if let Center::Inertia { + position, + direction, + amount, + } = &self + { + *self = if amount <= &mut 0.0 { + Center::Exact(*position) + } else { + Center::Inertia { + position: screen_to_position( + self.position(my_position).project(zoom) - (*direction * *amount), + zoom, + ), + direction: *direction, + amount: *amount - 0.03, + } + }; + + // Map is moving due to interia, therefore we need to recalculate in the next frame. + log::trace!("Requesting repaint due to non-zero inertia."); + ctx.request_repaint(); + } + } + + /// Returns exact position if map is detached (i.e. not following `my_position`), + /// `None` otherwise. + pub fn detached(&self) -> Option { match self { - Center::MyPosition => my_position, - Center::Exact(position) => *position, + Center::MyPosition => None, + Center::Exact(position) => Some(*position), + Center::Inertia { + position, + direction: _, + amount: _, + } => Some(*position), } } + + /// Get the real position at the map's center. + pub fn position(&self, my_position: Position) -> Position { + self.detached().unwrap_or(my_position) + } } /// State of the map widget which must persist between frames.