Skip to content

Commit

Permalink
Merge pull request #31 from podusowski/inertial-movement
Browse files Browse the repository at this point in the history
Inertial movement
  • Loading branch information
podusowski authored Aug 29, 2023
2 parents 86ac570 + a9c161a commit 60059ff
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 45 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://geoportal.gov.pl>.
Expand Down
35 changes: 16 additions & 19 deletions examples/myapp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct MyApp {
tiles: Tiles,
geoportal_tiles: Tiles,
map_memory: MapMemory,
satellite: bool,
}

impl MyApp {
Expand All @@ -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,
}
}
}
Expand All @@ -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();
Expand All @@ -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);
}
});
Expand Down Expand Up @@ -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")
Expand All @@ -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");
});
}

Expand All @@ -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)
Expand Down
101 changes: 75 additions & 26 deletions src/map.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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<Position> {
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.
Expand Down

0 comments on commit 60059ff

Please sign in to comment.