use crate::annotation_system::annotation_support::AnnotationKey;
use crate::gui_system::internal_math::MAXIMUM_ZOOM_LEVEL;
use crate::gui_system::latitude_longitude::LatitudeLongitude;
use crate::gui_system::map_widget::focus::FocalPoint;
use crate::gui_system::map_widget::map_widget_core::{MapWidget, STANDARD_RECTANGLE};
use crate::gui_system::map_widget::map_widget_support::AnnotationInteractionState;
use iced::advanced::image::Image;
use iced::mouse::{Cursor, Interaction, ScrollDelta};
use iced::widget::canvas::Geometry;
use iced::widget::{Action, canvas};
use iced::{Color, Event, Point, Rectangle, Renderer, Theme, mouse, window};
const SCROLLING_SPEED: f32 = 0.05;
pub(crate) const FONT_SIZE: f32 = 15.0;
pub(crate) const TEXT_COLOR: Color = Color::BLACK;
#[derive(Debug, Clone)]
pub struct MapInteractionCommand {
pub(crate) client_id: u32,
pub(crate) command: SpecificInteractionCommand,
}
#[derive(Debug, Clone)]
pub(crate) enum SpecificInteractionCommand {
SetFocalPoint(FocalPoint, Rectangle),
UserMoved(FocalPoint, Rectangle),
AnnotationClicked(AnnotationKey, LatitudeLongitude),
AnnotationHovered(AnnotationKey, LatitudeLongitude),
ArrowClicked(AnnotationKey),
AnimationFrame {
focal_point: FocalPoint,
bounds: Rectangle,
generation: u64,
finished: bool,
},
MapPointClicked(LatitudeLongitude),
}
#[derive(Default)]
pub(crate) struct InteractionState {
drag_origin: Option<Point>,
}
impl canvas::Program<MapInteractionCommand> for MapWidget {
type State = InteractionState;
fn update(
&self,
state: &mut Self::State,
event: &Event,
bounds: Rectangle,
cursor: Cursor,
) -> Option<Action<MapInteractionCommand>> {
if self.focus_request.is_pending() {
return self.publish(self.focal_point_to_publish(bounds), bounds);
}
if let Some(animation) = &self.animation {
return match event {
Event::Window(window::Event::RedrawRequested(now)) => {
let (focal_point, finished) = animation.sample(*now);
Some(Action::publish(MapInteractionCommand {
client_id: self.client_id,
command: SpecificInteractionCommand::AnimationFrame {
focal_point,
bounds,
generation: self.animation_generation,
finished,
},
}))
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
if state.drag_origin.is_some() {
state.drag_origin = cursor.position_in(bounds);
}
None
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => {
state.drag_origin = None;
None
}
_ => None,
};
}
if self.view_size() != Some(bounds.size()) {
return self.publish(self.focal_point_to_publish(bounds), bounds);
}
match event {
Event::Mouse(mouse::Event::WheelScrolled {
delta: ScrollDelta::Lines { y, .. },
}) if cursor.is_over(bounds) => {
let zoom = (self.focal_point.continuous_zoom_level + y * SCROLLING_SPEED)
.clamp(0.0, MAXIMUM_ZOOM_LEVEL as f32);
self.publish_user_move(
FocalPoint {
continuous_zoom_level: zoom,
..self.focal_point
},
bounds,
)
}
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => {
state.drag_origin = cursor.position_in(bounds);
None
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => {
state.drag_origin = None;
None
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
let origin = state.drag_origin?;
let now = cursor.position_in(bounds)?;
let converter = self.position_converter.as_ref()?;
let delta = now - origin;
if delta.x == 0.0 && delta.y == 0.0 {
return None;
}
state.drag_origin = Some(now);
self.publish_user_move(
FocalPoint {
position: converter.get_new_coord_for_mouse_delta(delta),
..self.focal_point
},
bounds,
)
}
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let position = cursor.position_in(bounds)?;
let converter = self.position_converter.as_ref()?;
let conv_pos = converter.get_latitude_longitude_for_pixel_point(position);
Some(Action::publish(MapInteractionCommand {
client_id: self.client_id,
command: SpecificInteractionCommand::MapPointClicked(conv_pos),
}))
}
_ => None,
}
}
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
) -> Vec<Geometry<Renderer>> {
let Some(converter) = &self.position_converter else {
return vec![];
};
let content = self
.tile_drawing_cache
.draw(renderer, bounds.size(), |frame| {
for tile_and_pos in self.fallback_tiles.iter().chain(self.drawing_tiles.iter()) {
let Some(draw) = converter.get_draw_instruction(tile_and_pos.position.into())
else {
continue;
};
frame.with_save(|frame| {
frame.translate(draw.offset);
frame.scale(draw.scale);
frame.draw_image(
STANDARD_RECTANGLE,
Image::new(tile_and_pos.image.clone()).snap(true),
);
})
}
});
vec![content]
}
fn mouse_interaction(
&self,
state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
) -> Interaction {
if state.drag_origin.is_some() {
return Interaction::Grabbing;
}
let hovers_something = cursor.position_in(bounds).is_some_and(|position| {
self.arrow_at(position).is_some() || self.annotation_at(position).is_some()
});
match hovers_something {
true => Interaction::Pointer,
false => Interaction::None,
}
}
}
pub(crate) struct AnnotationOverlay<'a> {
widget: &'a MapWidget,
}
impl<'a> AnnotationOverlay<'a> {
pub(crate) fn new(widget: &'a MapWidget) -> Self {
Self { widget }
}
}
impl canvas::Program<MapInteractionCommand> for AnnotationOverlay<'_> {
type State = AnnotationInteractionState;
fn update(
&self,
state: &mut Self::State,
event: &Event,
bounds: Rectangle,
cursor: Cursor,
) -> Option<Action<MapInteractionCommand>> {
match event {
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
let position = cursor.position_in(bounds);
state.aim(
position.and_then(|position| self.widget.hover_target_at(position)),
self.widget.client_id,
)
}
Event::Mouse(mouse::Event::CursorLeft) => state.aim(None, self.widget.client_id),
Event::Window(window::Event::RedrawRequested(now)) => state.settle(*now),
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let position = cursor.position_in(bounds)?;
let command = match self.widget.arrow_at(position) {
Some(key) => SpecificInteractionCommand::ArrowClicked(key),
None => SpecificInteractionCommand::AnnotationClicked(
self.widget.annotation_at(position)?,
self.widget
.position_converter()?
.get_latitude_longitude_for_pixel_point(position),
),
};
Some(
Action::publish(MapInteractionCommand {
client_id: self.widget.client_id,
command,
})
.and_capture(),
)
}
_ => None,
}
}
fn draw(
&self,
state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
cursor: Cursor,
) -> Vec<Geometry<Renderer>> {
vec![
self.widget.draw_annotation_symbols(renderer, bounds),
self.widget.draw_annotation_interaction(
renderer,
bounds,
state,
cursor.position_in(bounds),
),
self.widget.draw_copyright(renderer, bounds),
self.widget.draw_scale(renderer, bounds),
]
}
fn mouse_interaction(
&self,
_state: &Self::State,
_bounds: Rectangle,
_cursor: Cursor,
) -> Interaction {
Interaction::None
}
}