diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index 338f7f77b..b24be81af 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -305,9 +305,13 @@ pub fn status_bar(app: &mut RustGisApp, ui: &mut Ui) { // Pointer coordinates. let format = app.map.coord_format; let earth = app.session.map().body().is_earth(); + // Kept after the pointer leaves the map, so a click here still shows the position; + // dimmed then, to tell it from a live one. + let kept = app.map.kept_pointer_lonlat(app.session.map().instance_id(), app.session.opened); + let live = app.map.pointer_lonlat.is_some(); let resp = ui.add( egui::Label::new( - egui::RichText::new(format.readout(app.map.pointer_lonlat, earth)).monospace().size(11.5).color(t.text_dim), + egui::RichText::new(format.readout(kept, earth)).monospace().size(11.5).color(if live { t.text_dim } else { t.text_disabled }), ) .sense(Sense::click()), ); diff --git a/crates/ui-egui/src/mapview.rs b/crates/ui-egui/src/mapview.rs index cec3399d3..89eb8c1d8 100644 --- a/crates/ui-egui/src/mapview.rs +++ b/crates/ui-egui/src/mapview.rs @@ -302,6 +302,9 @@ pub struct MapView { /// A map or layout waiting for the user to confirm its deletion. pub deleting: Option, pub pointer_lonlat: Option, + /// The last pointer position seen over a map, kept for the status bar readout after the + /// pointer leaves the map; with the map's instance and the session's `opened` it belongs to. + kept_lonlat: Option<(Coord, u64, u64)>, /// Where the map's context menu was opened: the pointer leaves the map for the menu. pub menu_lonlat: Option, /// The text graphic the context menu was opened on (layer id, row), for Edit Text. @@ -403,6 +406,7 @@ impl Default for MapView { popup_rev: 0, deleting: None, pointer_lonlat: None, + kept_lonlat: None, menu_lonlat: None, menu_text: None, coord_format: crate::CoordReadout::Decimal, @@ -437,6 +441,18 @@ impl Default for MapView { } impl MapView { + /// The position the status bar shows: the last one over this map, kept while the pointer is + /// elsewhere. A position from another map or project no longer applies, so it is dropped. + pub fn kept_pointer_lonlat(&mut self, map_instance: u64, opened: u64) -> Option { + match self.kept_lonlat { + Some((c, m, o)) if m == map_instance && o == opened => Some(c), + _ => { + self.kept_lonlat = None; + None + } + } + } + /// Whether an exact frame is being composed off the UI thread, or the frame shown waits for /// tile pictures still to be drawn. pub fn drawing(&self) -> bool { @@ -2257,6 +2273,9 @@ fn interact(app: &mut RustGisApp, ui: &mut Ui, resp: &egui::Response, rect: Rect let time = now(ui); let hover = resp.hover_pos(); app.map.pointer_lonlat = hover.and_then(|p| screen_to_lonlat(app, rect, p)); + if let Some(c) = app.map.pointer_lonlat.filter(|c| c.is_finite()) { + app.map.kept_lonlat = Some((c, app.session.map().instance_id(), app.session.opened)); + } let tool = app.session.tool; cancel_changed_selection(app, rect); diff --git a/crates/ui-egui/tests/coordinate_readout.rs b/crates/ui-egui/tests/coordinate_readout.rs index c4ec1526a..00f6508c3 100644 --- a/crates/ui-egui/tests/coordinate_readout.rs +++ b/crates/ui-egui/tests/coordinate_readout.rs @@ -136,3 +136,79 @@ fn the_status_bar_reads_the_pointer_in_the_chosen_format_and_a_click_switches() hover_map(&mut h); h.get_by_label(&rustgis_engine::geom::format_lonlat(h.state().map.pointer_lonlat.unwrap())); } + +/// Moves the mouse onto the status bar, off the map, for a few frames. +fn hover_status_bar(h: &mut egui_kittest::Harness<'_, RustGisApp>) { + for _ in 0..3 { + h.input_mut().events.push(egui::Event::PointerMoved(egui::pos2(800.0, 892.0))); + h.step(); + } +} + +fn readout_at_center(h: &mut egui_kittest::Harness<'_, RustGisApp>) -> String { + h.state_mut().run("map.goToXY", json!({"lon": 44.4, "lat": 33.3, "scale": 10000})).unwrap(); + hover_map(h); + rustgis_engine::geom::format_lonlat(h.state().map.pointer_lonlat.expect("the pointer is over the map")) +} + +#[test] +fn the_readout_is_a_dash_until_the_pointer_has_been_over_the_map() { + let mut h = harness(RustGisApp::with_sample()); + h.step(); + h.get_by_label("—"); + hover_status_bar(&mut h); + h.get_by_label("—"); + assert!(h.state().map.pointer_lonlat.is_none()); +} + +#[test] +fn the_position_stays_when_the_pointer_leaves_the_map_and_a_click_switches_format() { + let mut h = harness(RustGisApp::with_sample()); + let decimal = readout_at_center(&mut h); + hover_status_bar(&mut h); + assert!(h.state().map.pointer_lonlat.is_none()); + h.get_by_label(&decimal); + // The pointer in the window's corner, or gone altogether, keeps it too. + h.input_mut().events.push(egui::Event::PointerGone); + h.step(); + h.get_by_label(&decimal); + // A click on the readout shows the same position in the next format. + h.get_by_label(&decimal).click(); + h.step(); + h.step(); + assert_eq!(h.state().map.coord_format, CoordReadout::Dms); + let at = h.state().map.pointer_lonlat; + assert!(at.is_none()); + h.get_by_label_contains("44°"); + // The pointer back over the map follows it again. + hover_map(&mut h); + assert!(h.state().map.pointer_lonlat.is_some()); +} + +#[test] +fn the_position_is_dropped_on_another_map_or_project() { + let mut h = harness(RustGisApp::with_sample()); + let decimal = readout_at_center(&mut h); + hover_status_bar(&mut h); + h.get_by_label(&decimal); + // Another map becoming the active one. + h.state_mut().run("map.new", json!({"name": "Second Map"})).unwrap(); + h.step(); + h.step(); + assert!(h.query_by_label(&decimal).is_none()); + h.get_by_label("—"); + // Back on the first map it is not remembered from before. + h.state_mut().run("map.activate", json!({"name": "World Atlas"})).ok(); + h.step(); + h.step(); + h.get_by_label("—"); + // A new position, then another project replacing this one. + let decimal = readout_at_center(&mut h); + hover_status_bar(&mut h); + h.get_by_label(&decimal); + h.state_mut().run("project.new", json!({})).unwrap(); + h.step(); + h.step(); + assert!(h.query_by_label(&decimal).is_none()); + h.get_by_label("—"); +} diff --git a/docs/user-guide/maps.md b/docs/user-guide/maps.md index 468a25209..204720709 100644 --- a/docs/user-guide/maps.md +++ b/docs/user-guide/maps.md @@ -138,8 +138,11 @@ and **View In Google Earth** open the place in your browser at about the same zo ### Coordinate readouts -The status bar shows the pointer's coordinates. Click them to step through four formats; the -choice is remembered and also sets the right-click menu's first row. The same place in each: +The status bar shows the pointer's coordinates while it is over the map, and keeps the last +position (dimmed) when it leaves, so you can move to the status bar and click it. It reads `—` +until the pointer has been over the map, and again after you switch to another map or open another +project. Click the coordinates to step through four formats; the choice is remembered and also +sets the right-click menu's first row. The same place in each: | Format | Example | | | --- | --- | --- |