Files
Ely-GPUI-Components/examples/gallery/pages/maps/mod.rs
T
ZacharyZhang-NY e3a96b04e0 Maps · Layers: routes, GeoJSON, heat and clusters
MapView takes layers over its tiles and under its pins. RouteLine runs
along places the short way across the date line. read_geojson reads RFC
7946 to features, strictly: a position past the globe, a line of one
position or a ring that does not close fails with its reason, and a
feature placed nowhere keeps no geometry. GeoJsonLayer washes areas as
one path per polygon, so holes stay open, outlines them, strokes lines
and dots points. GeoHeatmap spreads weighted places in a quartic kernel
over small cells, scaled to the densest spot in view. Lines and areas
unwrap across the date line and repeat on every world in view.
cluster_markers gathers pins by squares of the world, so a pan keeps
each group; a cluster shows its count, and its press shows its members
whole, never farther out. A zoom that may regroup them hands a focused
pin's or cluster's focus to the map.
2026-09-27 22:35:40 -04:00

161 lines
5.3 KiB
Rust

use std::path::PathBuf;
use ely_gpui_component::{
maps::{
CoordFormat, CoordinateDisplay, LatLon, LocationPicker, MapMarker, MapPopup, MapView,
MapViewport, Tile,
},
theme::ActiveTheme,
};
use gpui::{
AnyElement, App, ImageSource, IntoElement, ParentElement, SharedString, Styled, Window, div, px,
};
mod layers;
use super::Page;
use crate::probe::probe;
use crate::script::Step;
use crate::ui::{keep, section, set, specimen, specimens};
pub const PAGE: Page = Page {
number: 41,
slug: "maps",
title: "Maps",
summary: "Tiles the host supplies, pins with a popup, a picker's crosshair, routes, GeoJSON, heat and clusters. The land is Natural Earth's.",
render,
script: &[
Step::Click("world-map"),
Step::Key("tab"),
Step::Key("enter"),
Step::Wait(300),
Step::Shot("popup"),
Step::Key("escape"),
Step::Key("="),
Step::Wait(300),
Step::Shot("zoomed"),
Step::Click("picker"),
Step::Key("right"),
Step::Key("down"),
Step::Wait(300),
Step::Shot("picked"),
Step::Click("cluster-map"),
Step::Key("tab"),
Step::Key("enter"),
Step::Wait(300),
Step::Shot("cluster-opened"),
],
};
const TILES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/gallery/assets/tiles");
pub(super) const ATTRIBUTION: &str = "Land: Natural Earth";
const CITIES: [(&str, &str, f64, f64, usize); 6] = [
("london", "London", 51.5072, -0.1276, 0),
("new-york", "New York", 40.7128, -74.006, 3),
("sao-paulo", "São Paulo", -23.5505, -46.6333, 5),
("cape-town", "Cape Town", -33.9249, 18.4241, 2),
("tokyo", "Tokyo", 35.6762, 139.6503, 4),
("sydney", "Sydney", -33.8688, 151.2093, 1),
];
/// The gallery's tiles, cut from Natural Earth by `scripts/tiles.py`.
pub(super) fn tile(tile: Tile) -> ImageSource {
PathBuf::from(format!("{TILES}/{}/{}/{}.png", tile.z, tile.x, tile.y)).into()
}
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
div()
.child(world(window, cx))
.child(coordinates(cx))
.child(picker(cx))
.child(layers::routes(cx))
.child(layers::heat(cx))
.child(layers::clusters(cx))
.into_any_element()
}
fn world(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let open = keep("map-open", || None::<SharedString>, window, cx);
let shown = open.read(cx).clone();
let markers = CITIES.map(|(key, name, lat, lon, hue)| {
MapMarker::new(key, LatLon::new(lat, lon))
.label(name)
.hue(hue)
});
let popup = shown.as_ref().map(|key| {
let (_, name, lat, lon, _) = *CITIES
.iter()
.find(|(each, ..)| *each == key.as_ref())
.expect("a listed city");
let closed = open.clone();
MapPopup::new(LatLon::new(lat, lon), name)
.child(
CoordinateDisplay::new("map-popup-place", LatLon::new(lat, lon))
.format(CoordFormat::Dms),
)
.on_close(move |_, cx| set(&closed, None, cx))
});
let map = MapView::new("world-map", MapViewport::new(LatLon::new(20.0, 10.0), 1.2))
.tiles(tile)
.tile_zooms(0, 3)
.attribution(ATTRIBUTION)
.markers(markers)
.on_marker(move |key, _, cx| set(&open, Some(key.clone()), cx));
let map = match popup {
Some(popup) => map.popup(popup),
None => map,
};
section(
"MapView · MapMarker · MapPopup",
"Tiles come from the host by zoom, column and row; past the zooms it cuts, the nearest scales. Drag or scroll to pan, Command-scroll or the buttons to zoom; focused, the arrows pan and + and - zoom. A pin opens its popup; Escape closes it.",
cx,
)
.child(probe("world-map", div().w(px(720.0)).h(px(360.0)).child(map)))
}
fn coordinates(cx: &App) -> impl IntoElement + use<> {
let place = LatLon::new(-33.9249, 18.4241);
section(
"CoordinateDisplay",
"A place in degrees or in degrees, minutes and seconds, with its hemispheres; figures keep one width, and the button copies it.",
cx,
)
.child(
specimens()
.child(specimen("decimal", CoordinateDisplay::new("coords-decimal", place), cx))
.child(specimen(
"degrees, minutes, seconds",
CoordinateDisplay::new("coords-dms", place).format(CoordFormat::Dms),
cx,
)),
)
}
fn picker(cx: &App) -> impl IntoElement + use<> {
let muted = cx.theme().colors.fg_muted;
let map = MapView::new(
"picker-map",
MapViewport::new(LatLon::new(48.8566, 2.3522), 3.0),
)
.tiles(tile)
.tile_zooms(0, 3)
.attribution(ATTRIBUTION);
section(
"LocationPicker",
"The place under the crosshair is the pick: pan by pointer or keys, and the owner hears each move.",
cx,
)
.child(
probe(
"picker",
div().w(px(480.0)).h(px(320.0)).child(
LocationPicker::new("picker", map)
.format(CoordFormat::Dms)
.on_pick(|at, _, _| log::info!("maps demo: picked {:.4}, {:.4}", at.lat, at.lon)),
),
),
)
.child(div().text_color(muted).child("The pick reaches the owner as it moves; the log shows it."))
}