veloren_voxygen/hud/
map.rs

1use super::{
2    MapMarkers, QUALITY_COMMON, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE, TEXT_BG,
3    TEXT_BLUE_COLOR, TEXT_COLOR, TEXT_GRAY_COLOR, TEXT_VELORITE, UI_HIGHLIGHT_0, UI_MAIN,
4    img_ids::{Imgs, ImgsRot},
5};
6use crate::{
7    GlobalState,
8    game_input::GameInput,
9    session::settings_change::{Interface as InterfaceChange, Interface::*},
10    ui::{ImageFrame, Tooltip, TooltipManager, Tooltipable, fonts::Fonts, img_ids},
11    window::KeyMouse,
12};
13use client::{self, Client, SiteMarker};
14use common::{
15    comp,
16    comp::group::Role,
17    terrain::{CoordinateConversions, TerrainChunkSize},
18    trade::Good,
19    vol::RectVolSize,
20};
21use common_net::msg::world_msg::{Marker, MarkerKind, PoiKind, SiteId};
22use conrod_core::{
23    Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, color,
24    input::MouseButton as ConrodMouseButton,
25    position,
26    widget::{self, Button, Image, Rectangle, Text},
27    widget_ids,
28};
29use hashbrown::HashMap;
30use i18n::Localization;
31use specs::WorldExt;
32use std::borrow::Cow;
33use vek::*;
34use winit::event::MouseButton;
35
36widget_ids! {
37    struct Ids {
38        frame,
39        bg,
40        icon,
41        close,
42        title,
43        map_align,
44        qlog_align,
45        location_name,
46        indicator,
47        indicator_overlay,
48        map_layers[],
49        map_title,
50        qlog_title,
51        zoom_slider,
52        mmap_site_icons[],
53        mmap_poi_icons[],
54        mmap_poi_title_bgs[],
55        mmap_poi_titles[],
56        peaks_txt,
57        peaks_txt_bg,
58        site_difs[],
59        member_indicators[],
60        member_height_indicators[],
61        location_marker,
62        location_marker_group[],
63        map_settings_align,
64        show_towns_img,
65        show_towns_box,
66        show_towns_text,
67        show_sea_chapels_img,
68        show_sea_chapels_box,
69        show_sea_chapels_text,
70        show_castles_img,
71        show_castles_box,
72        show_castles_text,
73        show_bridges_img,
74        show_bridges_box,
75        show_bridges_text,
76        show_dungeons_img,
77        show_dungeons_box,
78        show_dungeons_text,
79        show_caves_img,
80        show_caves_box,
81        show_caves_text,
82        show_trees_img,
83        show_trees_box,
84        show_trees_text,
85        show_peaks_img,
86        show_peaks_box,
87        show_peaks_text,
88        show_biomes_img,
89        show_biomes_box,
90        show_biomes_text,
91        show_glider_courses_img,
92        show_glider_courses_box,
93        show_glider_courses_text,
94        show_voxel_map_img,
95        show_voxel_map_box,
96        show_voxel_map_text,
97        show_difficulty_img,
98        show_difficulty_box,
99        show_difficulty_text,
100        recenter_button,
101        drag_txt,
102        drag_ico,
103        zoom_txt,
104        zoom_ico,
105        waypoint_binding_txt,
106        waypoint_txt,
107        map_mode_btn,
108        map_mode_overlay,
109        minimap_mode_btn,
110        minimap_mode_overlay,
111
112    }
113}
114
115const SHOW_ECONOMY: bool = false; // turn this display off (for 0.9) until we have an improved look
116
117#[derive(WidgetCommon)]
118pub struct Map<'a> {
119    client: &'a Client,
120    world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
121    imgs: &'a Imgs,
122    fonts: &'a Fonts,
123    #[conrod(common_builder)]
124    common: widget::CommonBuilder,
125    _pulse: f32,
126    localized_strings: &'a Localization,
127    global_state: &'a GlobalState,
128    rot_imgs: &'a ImgsRot,
129    tooltip_manager: &'a mut TooltipManager,
130    location_markers: &'a MapMarkers,
131    map_drag: Vec2<f64>,
132    extra_markers: &'a HashMap<Vec2<i32>, Marker>,
133}
134impl<'a> Map<'a> {
135    pub fn new(
136        client: &'a Client,
137        imgs: &'a Imgs,
138        rot_imgs: &'a ImgsRot,
139        world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
140        fonts: &'a Fonts,
141        pulse: f32,
142        localized_strings: &'a Localization,
143        global_state: &'a GlobalState,
144        tooltip_manager: &'a mut TooltipManager,
145        location_markers: &'a MapMarkers,
146        map_drag: Vec2<f64>,
147        extra_markers: &'a HashMap<Vec2<i32>, Marker>,
148    ) -> Self {
149        Self {
150            imgs,
151            rot_imgs,
152            world_map,
153            client,
154            fonts,
155            common: widget::CommonBuilder::default(),
156            _pulse: pulse,
157            localized_strings,
158            global_state,
159            tooltip_manager,
160            location_markers,
161            map_drag,
162            extra_markers,
163        }
164    }
165}
166
167pub struct State {
168    ids: Ids,
169}
170
171pub enum Event {
172    SettingsChange(InterfaceChange),
173    Close,
174    RequestSiteInfo(SiteId),
175    SetLocationMarker(Vec2<i32>),
176    MapDrag(Vec2<f64>),
177    RemoveMarker,
178}
179
180fn get_site_economy(site: &SiteMarker) -> String {
181    if SHOW_ECONOMY {
182        if let Some(economy) = &site.economy {
183            use common::trade::Good::{Armor, Coin, Food, Ingredients, Potions, Tools};
184            let mut result = format!("\n\nPopulation {:?}", economy.population);
185            result += "\nStock";
186            for i in [Food, Potions, Ingredients, Coin, Tools, Armor].iter() {
187                result += &format!("\n  {:?}={:.3}", *i, *economy.stock.get(i).unwrap_or(&0.0));
188            }
189            result += "\nPrice";
190            for i in [Food, Potions, Ingredients, Coin, Tools, Armor].iter() {
191                result += &format!("\n  {:?}={:.3}", *i, *economy.values.get(i).unwrap_or(&0.0));
192            }
193
194            let mut trade_sorted: Vec<(&Good, &f32)> = economy.last_exports.iter().collect();
195            trade_sorted.sort_unstable_by(|a, b| a.1.partial_cmp(b.1).unwrap());
196            if !trade_sorted.is_empty() {
197                result += &format!("\nTrade {:.1} ", *(trade_sorted.first().unwrap().1));
198                for i in trade_sorted.iter().filter(|x| *x.1 != 0.0) {
199                    result += &format!("{:?} ", i.0);
200                }
201                result += &format!("{:.3}", *(trade_sorted.last().unwrap().1));
202            }
203            result
204        } else {
205            format!("\nloading economy for\n{:?}", site.marker.id)
206        }
207    } else {
208        "".into()
209    }
210}
211
212impl From<KeyMouse> for ConrodMouseButton {
213    fn from(key: KeyMouse) -> Self {
214        match key {
215            KeyMouse::Mouse(MouseButton::Left) => ConrodMouseButton::Left,
216            KeyMouse::Mouse(MouseButton::Right) => ConrodMouseButton::Right,
217            KeyMouse::Mouse(MouseButton::Middle) => ConrodMouseButton::Middle,
218            KeyMouse::Mouse(MouseButton::Other(0)) => ConrodMouseButton::X1,
219            KeyMouse::Mouse(MouseButton::Other(1)) => ConrodMouseButton::X2,
220            KeyMouse::Mouse(MouseButton::Other(2)) => ConrodMouseButton::Button6,
221            KeyMouse::Mouse(MouseButton::Other(3)) => ConrodMouseButton::Button7,
222            KeyMouse::Mouse(MouseButton::Other(4)) => ConrodMouseButton::Button8,
223            _ => conrod_core::input::MouseButton::Unknown,
224        }
225    }
226}
227
228impl Widget for Map<'_> {
229    type Event = Vec<Event>;
230    type State = State;
231    type Style = ();
232
233    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
234        State {
235            ids: Ids::new(id_gen),
236        }
237    }
238
239    fn style(&self) -> Self::Style {}
240
241    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
242        common_base::prof_span!("Map::update");
243        let widget::UpdateArgs { state, ui, .. } = args;
244        let zoom = self.global_state.settings.interface.map_zoom;
245        let show_difficulty = self.global_state.settings.interface.map_show_difficulty;
246        let show_towns = self.global_state.settings.interface.map_show_towns;
247        let show_dungeons = self.global_state.settings.interface.map_show_dungeons;
248        let show_castles = self.global_state.settings.interface.map_show_castles;
249        let show_bridges = self.global_state.settings.interface.map_show_bridges;
250        let show_caves = self.global_state.settings.interface.map_show_caves;
251        let show_trees = self.global_state.settings.interface.map_show_trees;
252        let show_peaks = self.global_state.settings.interface.map_show_peaks;
253        let show_biomes = self.global_state.settings.interface.map_show_biomes;
254        let show_glider_courses = self.global_state.settings.interface.map_show_glider_courses;
255        let show_voxel_map = self.global_state.settings.interface.map_show_voxel_map;
256        let show_topo_map = self.global_state.settings.interface.map_show_topo_map;
257        let location_marker_binding = self
258            .global_state
259            .settings
260            .controls
261            .keybindings
262            .get(&GameInput::MapSetMarker)
263            .cloned()
264            .flatten()
265            .unwrap_or(KeyMouse::Mouse(MouseButton::Middle));
266        let key_layout = &self.global_state.window.key_layout;
267        let mut events = Vec::new();
268        let i18n = &self.localized_strings;
269        // Tooltips
270        let site_tooltip = Tooltip::new({
271            // Edge images [t, b, r, l]
272            // Corner images [tr, tl, br, bl]
273            let edge = &self.rot_imgs.tt_side;
274            let corner = &self.rot_imgs.tt_corner;
275            ImageFrame::new(
276                [edge.cw180, edge.none, edge.cw270, edge.cw90],
277                [corner.none, corner.cw270, corner.cw90, corner.cw180],
278                Color::Rgba(0.08, 0.07, 0.04, 1.0),
279                5.0,
280            )
281        })
282        .title_font_size(self.fonts.cyri.scale(15))
283        .parent(ui.window)
284        .desc_font_size(self.fonts.cyri.scale(12))
285        .font_id(self.fonts.cyri.conrod_id)
286        .desc_text_color(TEXT_COLOR);
287        // Frame
288        Image::new(self.imgs.map_bg)
289            .w_h(1202.0, 886.0)
290            .mid_top_with_margin_on(ui.window, 5.0)
291            .color(Some(UI_MAIN))
292            .set(state.ids.bg, ui);
293
294        Image::new(self.imgs.map_frame)
295            .w_h(1202.0, 886.0)
296            .middle_of(state.ids.bg)
297            .color(Some(UI_HIGHLIGHT_0))
298            .set(state.ids.frame, ui);
299
300        // Map Content Alignment
301        Rectangle::fill_with([814.0, 834.0], color::TRANSPARENT)
302            .top_left_with_margins_on(state.ids.frame, 46.0, 240.0)
303            .set(state.ids.map_align, ui);
304
305        // Questlog Content Alignment
306        Rectangle::fill_with([232.0, 814.0], color::TRANSPARENT)
307            .top_left_with_margins_on(state.ids.frame, 44.0, 2.0)
308            .set(state.ids.qlog_align, ui);
309
310        // Icon
311        Image::new(self.imgs.map_icon)
312            .w_h(30.0, 30.0)
313            .top_left_with_margins_on(state.ids.frame, 6.0, 8.0)
314            .set(state.ids.icon, ui);
315
316        // Map Title
317        Text::new(&i18n.get_msg("hud-map-map_title"))
318            .mid_top_with_margin_on(state.ids.frame, 3.0)
319            .font_id(self.fonts.cyri.conrod_id)
320            .font_size(self.fonts.cyri.scale(29))
321            .color(TEXT_COLOR)
322            .set(state.ids.map_title, ui);
323
324        // Questlog Title
325        Text::new(&i18n.get_msg("hud-map-qlog_title"))
326            .mid_top_with_margin_on(state.ids.qlog_align, 6.0)
327            .font_id(self.fonts.cyri.conrod_id)
328            .font_size(self.fonts.cyri.scale(21))
329            .color(TEXT_COLOR)
330            .set(state.ids.qlog_title, ui);
331
332        // Location Name
333        /*match self.client.current_chunk() {
334            Some(chunk) => Text::new(chunk.meta().name())
335                .mid_top_with_margin_on(state.ids.bg, 55.0)
336                .font_size(self.fonts.alkhemi.scale(60))
337                .color(TEXT_COLOR)
338                .font_id(self.fonts.alkhemi.conrod_id)
339                .parent(state.ids.frame)
340                .set(state.ids.location_name, ui),
341            None => Text::new(" ")
342                .mid_top_with_margin_on(state.ids.bg, 3.0)
343                .font_size(self.fonts.alkhemi.scale(40))
344                .font_id(self.fonts.alkhemi.conrod_id)
345                .color(TEXT_COLOR)
346                .set(state.ids.location_name, ui),
347        }*/
348        // Map Layers
349        // It is assumed that there is at least one layer
350        if state.ids.map_layers.len() < self.world_map.0.len() {
351            state.update(|state| {
352                state
353                    .ids
354                    .map_layers
355                    .resize(self.world_map.0.len(), &mut ui.widget_id_generator())
356            });
357        }
358
359        // Map Size
360        let worldsize = self.world_map.1;
361
362        // Coordinates
363        let player_pos = self
364            .client
365            .state()
366            .ecs()
367            .read_storage::<comp::Pos>()
368            .get(self.client.entity())
369            .map_or(Vec3::zero(), |pos| pos.0);
370
371        let map_size = Vec2::new(760.0, 760.0);
372
373        let player_pos_chunks =
374            player_pos.xy().map(|x| x as f64) / TerrainChunkSize::RECT_SIZE.map(|x| x as f64);
375        let min_drag = player_pos_chunks - worldsize.map(|x| x as f64);
376        let max_drag = player_pos_chunks;
377        let drag = self.map_drag.clamped(min_drag, max_drag);
378
379        enum MarkerChange {
380            Pos(Vec2<f32>),
381            ClickPos,
382            Remove,
383        }
384
385        let handle_widget_mouse_events =
386            |widget, marker: MarkerChange, ui: &mut UiCell, events: &mut Vec<Event>, map_widget| {
387                // Handle Location Marking
388                if let Some(click) = ui
389                    .widget_input(widget)
390                    .clicks()
391                    .button(ConrodMouseButton::from(location_marker_binding))
392                    .next()
393                {
394                    match marker {
395                        MarkerChange::Pos(ref wpos) => {
396                            events.push(Event::SetLocationMarker(wpos.as_()))
397                        },
398                        MarkerChange::ClickPos => {
399                            let tmp: Vec2<f64> = Vec2::<f64>::from(click.xy) / zoom - drag;
400                            let wpos = tmp.as_::<f32>().cpos_to_wpos() + player_pos;
401                            events.push(Event::SetLocationMarker(wpos.as_()));
402                        },
403                        MarkerChange::Remove => events.push(Event::RemoveMarker),
404                    }
405                }
406
407                // Handle zooming with the mouse wheel
408                let scrolled: f64 = ui
409                    .widget_input(widget)
410                    .scrolls()
411                    .map(|scroll| scroll.y)
412                    .sum();
413                if scrolled != 0.0 {
414                    let min_zoom = map_size.x / worldsize.reduce_partial_max() as f64 / 2.0;
415                    let new_zoom_lvl: f64 = (f64::log2(zoom) - scrolled * 0.03)
416                        .exp2()
417                        .clamp(min_zoom, 16.0);
418                    events.push(Event::SettingsChange(MapZoom(new_zoom_lvl)));
419                    let cursor_mouse_pos = ui
420                        .widget_input(map_widget)
421                        .mouse()
422                        .map(|mouse| mouse.rel_xy());
423                    if let Some(cursor_pos) = cursor_mouse_pos {
424                        let mouse_pos = Vec2::from_slice(&cursor_pos);
425                        let drag_new = drag + mouse_pos * (1.0 / new_zoom_lvl - 1.0 / zoom);
426                        if drag_new != drag {
427                            events.push(Event::MapDrag(drag_new));
428                        }
429                    }
430                }
431
432                // Handle dragging
433                let dragged: Vec2<f64> = ui
434                    .widget_input(widget)
435                    .drags()
436                    .left()
437                    .map(|drag| Vec2::<f64>::from(drag.delta_xy))
438                    .sum();
439                // Drag represents offset of view from the player_pos in chunk coords
440                let drag_new = drag + dragged / zoom;
441                if drag_new != drag {
442                    events.push(Event::MapDrag(drag_new));
443                }
444            };
445
446        handle_widget_mouse_events(
447            state.ids.map_layers[0],
448            MarkerChange::ClickPos,
449            ui,
450            &mut events,
451            state.ids.map_layers[0],
452        );
453
454        let rect_src = position::Rect::from_xy_dim(
455            [
456                (player_pos.x as f64 / TerrainChunkSize::RECT_SIZE.x as f64) - drag.x,
457                (worldsize.y as f64 - (player_pos.y as f64 / TerrainChunkSize::RECT_SIZE.y as f64))
458                    + drag.y,
459            ],
460            [map_size.x / zoom, map_size.y / zoom],
461        );
462
463        // X-Button
464        if Button::image(self.imgs.close_button)
465            .w_h(24.0, 25.0)
466            .hover_image(self.imgs.close_btn_hover)
467            .press_image(self.imgs.close_btn_press)
468            .top_right_with_margins_on(state.ids.frame, 0.0, 0.0)
469            .set(state.ids.close, ui)
470            .was_clicked()
471        {
472            events.push(Event::Close);
473        }
474
475        // Map Layer Images
476        for (index, layer) in self.world_map.0.iter().enumerate() {
477            if index == 0 {
478                Button::image(layer.none)
479                    .mid_top_with_margin_on(state.ids.map_align, 10.0)
480                    .w_h(map_size.x, map_size.y)
481                    .parent(state.ids.bg)
482                    .source_rectangle(rect_src)
483                    .set(state.ids.map_layers[index], ui);
484            } else if show_topo_map {
485                Button::image(layer.none)
486                    .mid_top_with_margin_on(state.ids.map_align, 10.0)
487                    .w_h(map_size.x, map_size.y)
488                    .parent(state.ids.bg)
489                    .source_rectangle(rect_src)
490                    .graphics_for(state.ids.map_layers[0])
491                    .set(state.ids.map_layers[index], ui);
492            }
493        }
494
495        // Icon settings
496        // Alignment
497        Rectangle::fill_with([150.0, 200.0], color::TRANSPARENT)
498            .top_right_with_margins_on(state.ids.frame, 55.0, 10.0)
499            .set(state.ids.map_settings_align, ui);
500        // Checkboxes
501        // Show difficulties
502        Image::new(self.imgs.map_dif_icon)
503            .top_left_with_margins_on(state.ids.map_settings_align, 5.0, 5.0)
504            .w_h(20.0, 20.0)
505            .set(state.ids.show_difficulty_img, ui);
506        if Button::image(if show_difficulty {
507            self.imgs.checkbox_checked
508        } else {
509            self.imgs.checkbox
510        })
511        .w_h(18.0, 18.0)
512        .hover_image(if show_difficulty {
513            self.imgs.checkbox_checked_mo
514        } else {
515            self.imgs.checkbox_mo
516        })
517        .press_image(if show_difficulty {
518            self.imgs.checkbox_checked
519        } else {
520            self.imgs.checkbox_press
521        })
522        .right_from(state.ids.show_difficulty_img, 10.0)
523        .set(state.ids.show_difficulty_box, ui)
524        .was_clicked()
525        {
526            events.push(Event::SettingsChange(MapShowDifficulty(!show_difficulty)));
527        }
528        Text::new(&i18n.get_msg("hud-map-difficulty"))
529            .right_from(state.ids.show_difficulty_box, 10.0)
530            .font_size(self.fonts.cyri.scale(14))
531            .font_id(self.fonts.cyri.conrod_id)
532            .graphics_for(state.ids.show_difficulty_box)
533            .color(TEXT_COLOR)
534            .set(state.ids.show_difficulty_text, ui);
535        // Towns
536        Image::new(self.imgs.mmap_site_town)
537            .down_from(state.ids.show_difficulty_img, 10.0)
538            .w_h(20.0, 20.0)
539            .set(state.ids.show_towns_img, ui);
540        if Button::image(if show_towns {
541            self.imgs.checkbox_checked
542        } else {
543            self.imgs.checkbox
544        })
545        .w_h(18.0, 18.0)
546        .hover_image(if show_towns {
547            self.imgs.checkbox_checked_mo
548        } else {
549            self.imgs.checkbox_mo
550        })
551        .press_image(if show_towns {
552            self.imgs.checkbox_checked
553        } else {
554            self.imgs.checkbox_press
555        })
556        .right_from(state.ids.show_towns_img, 10.0)
557        .set(state.ids.show_towns_box, ui)
558        .was_clicked()
559        {
560            events.push(Event::SettingsChange(MapShowTowns(!show_towns)));
561        }
562        Text::new(&i18n.get_msg("hud-map-towns"))
563            .right_from(state.ids.show_towns_box, 10.0)
564            .font_size(self.fonts.cyri.scale(14))
565            .font_id(self.fonts.cyri.conrod_id)
566            .graphics_for(state.ids.show_towns_box)
567            .color(TEXT_COLOR)
568            .set(state.ids.show_towns_text, ui);
569        // Castles
570        Image::new(self.imgs.mmap_site_castle)
571            .down_from(state.ids.show_towns_img, 10.0)
572            .w_h(20.0, 20.0)
573            .set(state.ids.show_castles_img, ui);
574        if Button::image(if show_castles {
575            self.imgs.checkbox_checked
576        } else {
577            self.imgs.checkbox
578        })
579        .w_h(18.0, 18.0)
580        .hover_image(if show_castles {
581            self.imgs.checkbox_checked_mo
582        } else {
583            self.imgs.checkbox_mo
584        })
585        .press_image(if show_castles {
586            self.imgs.checkbox_checked
587        } else {
588            self.imgs.checkbox_press
589        })
590        .right_from(state.ids.show_castles_img, 10.0)
591        .set(state.ids.show_castles_box, ui)
592        .was_clicked()
593        {
594            events.push(Event::SettingsChange(MapShowCastles(!show_castles)));
595        }
596        Text::new(&i18n.get_msg("hud-map-castles"))
597            .right_from(state.ids.show_castles_box, 10.0)
598            .font_size(self.fonts.cyri.scale(14))
599            .font_id(self.fonts.cyri.conrod_id)
600            .graphics_for(state.ids.show_castles_box)
601            .color(TEXT_COLOR)
602            .set(state.ids.show_castles_text, ui);
603        // Bridges
604        Image::new(self.imgs.mmap_site_bridge)
605            .down_from(state.ids.show_castles_img, 10.0)
606            .w_h(20.0, 20.0)
607            .set(state.ids.show_bridges_img, ui);
608        if Button::image(if show_bridges {
609            self.imgs.checkbox_checked
610        } else {
611            self.imgs.checkbox
612        })
613        .w_h(18.0, 18.0)
614        .hover_image(if show_bridges {
615            self.imgs.checkbox_checked_mo
616        } else {
617            self.imgs.checkbox_mo
618        })
619        .press_image(if show_bridges {
620            self.imgs.checkbox_checked
621        } else {
622            self.imgs.checkbox_press
623        })
624        .right_from(state.ids.show_bridges_img, 10.0)
625        .set(state.ids.show_bridges_box, ui)
626        .was_clicked()
627        {
628            events.push(Event::SettingsChange(MapShowBridges(!show_bridges)));
629        }
630        Text::new(&i18n.get_msg("hud-map-bridges"))
631            .right_from(state.ids.show_bridges_box, 10.0)
632            .font_size(self.fonts.cyri.scale(14))
633            .font_id(self.fonts.cyri.conrod_id)
634            .graphics_for(state.ids.show_bridges_box)
635            .color(TEXT_COLOR)
636            .set(state.ids.show_bridges_text, ui);
637        // Dungeons
638        Image::new(self.imgs.mmap_site_dungeon)
639            .down_from(state.ids.show_bridges_img, 10.0)
640            .w_h(20.0, 20.0)
641            .set(state.ids.show_dungeons_img, ui);
642        if Button::image(if show_dungeons {
643            self.imgs.checkbox_checked
644        } else {
645            self.imgs.checkbox
646        })
647        .w_h(18.0, 18.0)
648        .hover_image(if show_dungeons {
649            self.imgs.checkbox_checked_mo
650        } else {
651            self.imgs.checkbox_mo
652        })
653        .press_image(if show_dungeons {
654            self.imgs.checkbox_checked
655        } else {
656            self.imgs.checkbox_press
657        })
658        .right_from(state.ids.show_dungeons_img, 10.0)
659        .set(state.ids.show_dungeons_box, ui)
660        .was_clicked()
661        {
662            events.push(Event::SettingsChange(MapShowDungeons(!show_dungeons)));
663        }
664        Text::new(&i18n.get_msg("hud-map-dungeons"))
665            .right_from(state.ids.show_dungeons_box, 10.0)
666            .font_size(self.fonts.cyri.scale(14))
667            .font_id(self.fonts.cyri.conrod_id)
668            .graphics_for(state.ids.show_dungeons_box)
669            .color(TEXT_COLOR)
670            .set(state.ids.show_dungeons_text, ui);
671        // Caves
672        Image::new(self.imgs.mmap_site_cave)
673            .down_from(state.ids.show_dungeons_img, 10.0)
674            .w_h(20.0, 20.0)
675            .set(state.ids.show_caves_img, ui);
676        if Button::image(if show_caves {
677            self.imgs.checkbox_checked
678        } else {
679            self.imgs.checkbox
680        })
681        .w_h(18.0, 18.0)
682        .hover_image(if show_caves {
683            self.imgs.checkbox_checked_mo
684        } else {
685            self.imgs.checkbox_mo
686        })
687        .press_image(if show_caves {
688            self.imgs.checkbox_checked
689        } else {
690            self.imgs.checkbox_press
691        })
692        .right_from(state.ids.show_caves_img, 10.0)
693        .set(state.ids.show_caves_box, ui)
694        .was_clicked()
695        {
696            events.push(Event::SettingsChange(MapShowCaves(!show_caves)));
697        }
698        Text::new(&i18n.get_msg("hud-map-caves"))
699            .right_from(state.ids.show_caves_box, 10.0)
700            .font_size(self.fonts.cyri.scale(14))
701            .font_id(self.fonts.cyri.conrod_id)
702            .graphics_for(state.ids.show_caves_box)
703            .color(TEXT_COLOR)
704            .set(state.ids.show_caves_text, ui);
705        // Trees
706        Image::new(self.imgs.mmap_site_tree)
707            .down_from(state.ids.show_caves_img, 10.0)
708            .w_h(20.0, 20.0)
709            .set(state.ids.show_trees_img, ui);
710        if Button::image(if show_trees {
711            self.imgs.checkbox_checked
712        } else {
713            self.imgs.checkbox
714        })
715        .w_h(18.0, 18.0)
716        .hover_image(if show_trees {
717            self.imgs.checkbox_checked_mo
718        } else {
719            self.imgs.checkbox_mo
720        })
721        .press_image(if show_trees {
722            self.imgs.checkbox_checked
723        } else {
724            self.imgs.checkbox_press
725        })
726        .right_from(state.ids.show_trees_img, 10.0)
727        .set(state.ids.show_trees_box, ui)
728        .was_clicked()
729        {
730            events.push(Event::SettingsChange(MapShowTrees(!show_trees)));
731        }
732        Text::new(&i18n.get_msg("hud-map-trees"))
733            .right_from(state.ids.show_trees_box, 10.0)
734            .font_size(self.fonts.cyri.scale(14))
735            .font_id(self.fonts.cyri.conrod_id)
736            .graphics_for(state.ids.show_trees_box)
737            .color(TEXT_COLOR)
738            .set(state.ids.show_trees_text, ui);
739        // Biomes
740        Image::new(self.imgs.mmap_poi_biome)
741            .down_from(state.ids.show_trees_img, 10.0)
742            .w_h(20.0, 20.0)
743            .set(state.ids.show_biomes_img, ui);
744        if Button::image(if show_biomes {
745            self.imgs.checkbox_checked
746        } else {
747            self.imgs.checkbox
748        })
749        .w_h(18.0, 18.0)
750        .hover_image(if show_biomes {
751            self.imgs.checkbox_checked_mo
752        } else {
753            self.imgs.checkbox_mo
754        })
755        .press_image(if show_biomes {
756            self.imgs.checkbox_checked
757        } else {
758            self.imgs.checkbox_press
759        })
760        .right_from(state.ids.show_biomes_img, 10.0)
761        .set(state.ids.show_biomes_box, ui)
762        .was_clicked()
763        {
764            events.push(Event::SettingsChange(MapShowBiomes(!show_biomes)));
765        }
766        Text::new(&i18n.get_msg("hud-map-biomes"))
767            .right_from(state.ids.show_biomes_box, 10.0)
768            .font_size(self.fonts.cyri.scale(14))
769            .font_id(self.fonts.cyri.conrod_id)
770            .graphics_for(state.ids.show_biomes_box)
771            .color(TEXT_COLOR)
772            .set(state.ids.show_biomes_text, ui);
773        // Peaks
774        Image::new(self.imgs.mmap_poi_peak)
775            .down_from(state.ids.show_biomes_img, 10.0)
776            .w_h(20.0, 20.0)
777            .set(state.ids.show_peaks_img, ui);
778        if Button::image(if show_peaks {
779            self.imgs.checkbox_checked
780        } else {
781            self.imgs.checkbox
782        })
783        .w_h(18.0, 18.0)
784        .hover_image(if show_peaks {
785            self.imgs.checkbox_checked_mo
786        } else {
787            self.imgs.checkbox_mo
788        })
789        .press_image(if show_peaks {
790            self.imgs.checkbox_checked
791        } else {
792            self.imgs.checkbox_press
793        })
794        .right_from(state.ids.show_peaks_img, 10.0)
795        .set(state.ids.show_peaks_box, ui)
796        .was_clicked()
797        {
798            events.push(Event::SettingsChange(MapShowPeaks(!show_peaks)));
799        }
800        Text::new(&i18n.get_msg("hud-map-peaks"))
801            .right_from(state.ids.show_peaks_box, 10.0)
802            .font_size(self.fonts.cyri.scale(14))
803            .font_id(self.fonts.cyri.conrod_id)
804            .graphics_for(state.ids.show_peaks_box)
805            .color(TEXT_COLOR)
806            .set(state.ids.show_peaks_text, ui);
807        // Glider Courses
808        Image::new(self.imgs.mmap_site_glider_course)
809            .down_from(state.ids.show_peaks_img, 10.0)
810            .w_h(20.0, 20.0)
811            .set(state.ids.show_glider_courses_img, ui);
812        if Button::image(if show_glider_courses {
813            self.imgs.checkbox_checked
814        } else {
815            self.imgs.checkbox
816        })
817        .w_h(18.0, 18.0)
818        .hover_image(if show_glider_courses {
819            self.imgs.checkbox_checked_mo
820        } else {
821            self.imgs.checkbox_mo
822        })
823        .press_image(if show_glider_courses {
824            self.imgs.checkbox_checked
825        } else {
826            self.imgs.checkbox_press
827        })
828        .right_from(state.ids.show_glider_courses_img, 10.0)
829        .set(state.ids.show_glider_courses_box, ui)
830        .was_clicked()
831        {
832            events.push(Event::SettingsChange(MapShowGliderCourses(
833                !show_glider_courses,
834            )));
835        }
836        Text::new(&i18n.get_msg("hud-map-glider_courses"))
837            .right_from(state.ids.show_glider_courses_box, 10.0)
838            .font_size(self.fonts.cyri.scale(14))
839            .font_id(self.fonts.cyri.conrod_id)
840            .graphics_for(state.ids.show_glider_courses_box)
841            .color(TEXT_COLOR)
842            .set(state.ids.show_glider_courses_text, ui);
843
844        const EXPOSE_VOXEL_MAP_TOGGLE_IN_UI: bool = false;
845        if EXPOSE_VOXEL_MAP_TOGGLE_IN_UI {
846            Image::new(self.imgs.mmap_poi_peak)
847                .down_from(state.ids.show_peaks_img, 10.0)
848                .w_h(20.0, 20.0)
849                .set(state.ids.show_voxel_map_img, ui);
850            if Button::image(if show_voxel_map {
851                self.imgs.checkbox_checked
852            } else {
853                self.imgs.checkbox
854            })
855            .w_h(18.0, 18.0)
856            .hover_image(if show_voxel_map {
857                self.imgs.checkbox_checked_mo
858            } else {
859                self.imgs.checkbox_mo
860            })
861            .press_image(if show_voxel_map {
862                self.imgs.checkbox_checked
863            } else {
864                self.imgs.checkbox_press
865            })
866            .right_from(state.ids.show_voxel_map_img, 10.0)
867            .set(state.ids.show_voxel_map_box, ui)
868            .was_clicked()
869            {
870                events.push(Event::SettingsChange(MapShowVoxelMap(!show_voxel_map)));
871            }
872            Text::new(&i18n.get_msg("hud-map-voxel_map"))
873                .right_from(state.ids.show_voxel_map_box, 10.0)
874                .font_size(self.fonts.cyri.scale(14))
875                .font_id(self.fonts.cyri.conrod_id)
876                .graphics_for(state.ids.show_voxel_map_box)
877                .color(TEXT_COLOR)
878                .set(state.ids.show_voxel_map_text, ui);
879        }
880        // Map icons
881        if state.ids.mmap_poi_icons.len() < self.client.pois().len() {
882            state.update(|state| {
883                state
884                    .ids
885                    .mmap_poi_icons
886                    .resize(self.client.pois().len(), &mut ui.widget_id_generator())
887            });
888            state.update(|state| {
889                state
890                    .ids
891                    .mmap_poi_titles
892                    .resize(self.client.pois().len(), &mut ui.widget_id_generator())
893            });
894            state.update(|state| {
895                state
896                    .ids
897                    .mmap_poi_title_bgs
898                    .resize(self.client.pois().len(), &mut ui.widget_id_generator())
899            });
900        }
901
902        let markers = self
903            .client
904            .markers()
905            .chain(self.extra_markers.values())
906            .collect::<Vec<_>>();
907
908        if state.ids.mmap_site_icons.len() < markers.len() {
909            state.update(|state| {
910                state
911                    .ids
912                    .mmap_site_icons
913                    .resize(markers.len(), &mut ui.widget_id_generator())
914            });
915        }
916        if state.ids.site_difs.len() < markers.len() {
917            state.update(|state| {
918                state
919                    .ids
920                    .site_difs
921                    .resize(markers.len(), &mut ui.widget_id_generator())
922            });
923        }
924
925        let wpos_to_rpos_fade =
926            |wpos: Vec2<f32>, bounding_rect_size: Vec2<f32>, fade_start: f32| {
927                // Site pos in world coordinates relative to the player
928                let rwpos = wpos - player_pos;
929                // Convert to chunk coordinates
930                let rcpos = rwpos.wpos_to_cpos()
931                // Add map dragging
932                + drag.map(|e| e as f32);
933                // Convert to relative pixel coordinates from the center of the map
934                // Accounting for zooming
935                let rpos = rcpos.map(|e| e * zoom as f32);
936
937                let dist_to_closest_map_edge =
938                    (rpos.map2(map_size, |e, sz| sz as f32 / 2.0 - e.abs()) - bounding_rect_size)
939                        .reduce_partial_min();
940                match dist_to_closest_map_edge {
941                    x if x <= 0.0 => None,
942                    x if x < fade_start => Some((
943                        rpos,
944                        // Easing function
945                        1.0 - 2.0_f32.powf(-10.0 * x / fade_start),
946                    )),
947                    _ => Some((rpos, 1.0)),
948                }
949            };
950
951        for (i, marker) in markers.iter().enumerate() {
952            let rside = zoom as f32 * 8.0 * 1.2;
953
954            let (rpos, fade) = match wpos_to_rpos_fade(
955                marker.wpos.map(|e| e as f32),
956                Vec2::from(rside / 2.0),
957                rside / 2.0,
958            ) {
959                Some(rpos) => rpos,
960                None => continue,
961            };
962
963            let title = marker
964                .name
965                .as_ref()
966                .map(|name| i18n.get_content(name))
967                .map(Cow::Owned)
968                .unwrap_or_else(|| match &marker.kind {
969                    MarkerKind::Unknown => i18n.get_msg("hud-map-unknown"),
970                    MarkerKind::Town => i18n.get_msg("hud-map-town"),
971                    MarkerKind::Castle => i18n.get_msg("hud-map-castle"),
972                    MarkerKind::Cave => i18n.get_msg("hud-map-cave"),
973                    MarkerKind::Tree => i18n.get_msg("hud-map-tree"),
974                    MarkerKind::Gnarling => i18n.get_msg("hud-map-gnarling"),
975                    MarkerKind::ChapelSite => i18n.get_msg("hud-map-chapel_site"),
976                    MarkerKind::Terracotta => i18n.get_msg("hud-map-terracotta"),
977                    MarkerKind::Bridge => i18n.get_msg("hud-map-bridge"),
978                    MarkerKind::GliderCourse => i18n.get_msg("hud-map-glider_course"),
979                    MarkerKind::Adlet => i18n.get_msg("hud-map-adlet"),
980                    MarkerKind::Haniwa => i18n.get_msg("hud-map-haniwa"),
981                    MarkerKind::Cultist => i18n.get_msg("hud-map-cultist"),
982                    MarkerKind::Sahagin => i18n.get_msg("hud-map-sahagin"),
983                    MarkerKind::Myrmidon => i18n.get_msg("hud-map-myrmidon"),
984                    MarkerKind::DwarvenMine => i18n.get_msg("hud-map-df_mine"),
985                    MarkerKind::VampireCastle => i18n.get_msg("hud-map-vampire_castle"),
986                });
987            let (difficulty, desc) = match &marker.kind {
988                MarkerKind::Unknown => (None, i18n.get_msg("hud-map-unknown")),
989                MarkerKind::Town => (None, i18n.get_msg("hud-map-town")),
990                MarkerKind::Castle => (None, i18n.get_msg("hud-map-castle")),
991                MarkerKind::Cave => (None, i18n.get_msg("hud-map-cave")),
992                MarkerKind::Tree => (None, i18n.get_msg("hud-map-tree")),
993                MarkerKind::Gnarling => (Some(0), i18n.get_msg("hud-map-gnarling")),
994                MarkerKind::Terracotta => (Some(5), i18n.get_msg("hud-map-terracotta")),
995                MarkerKind::ChapelSite => (Some(4), i18n.get_msg("hud-map-chapel_site")),
996                MarkerKind::Bridge => (None, i18n.get_msg("hud-map-bridge")),
997                MarkerKind::GliderCourse => (None, i18n.get_msg("hud-map-glider_course")),
998                MarkerKind::Adlet => (Some(1), i18n.get_msg("hud-map-adlet")),
999                MarkerKind::Haniwa => (Some(3), i18n.get_msg("hud-map-haniwa")),
1000                MarkerKind::Cultist => (Some(5), i18n.get_msg("hud-map-cultist")),
1001                MarkerKind::Sahagin => (Some(2), i18n.get_msg("hud-map-sahagin")),
1002                MarkerKind::Myrmidon => (Some(4), i18n.get_msg("hud-map-myrmidon")),
1003                MarkerKind::DwarvenMine => (Some(5), i18n.get_msg("hud-map-df_mine")),
1004                MarkerKind::VampireCastle => (Some(3), i18n.get_msg("hud-map-vampire_castle")),
1005            };
1006            let desc = if let Some(site_id) = marker.id
1007                && let Some(site) = self.client.sites().get(&site_id)
1008            {
1009                desc.into_owned() + &get_site_economy(site)
1010            } else {
1011                desc.into_owned()
1012            };
1013            let site_btn = Button::image(match &marker.kind {
1014                MarkerKind::Unknown => self.imgs.mmap_unknown,
1015                MarkerKind::Town => self.imgs.mmap_site_town,
1016                MarkerKind::ChapelSite => self.imgs.mmap_site_sea_chapel,
1017                MarkerKind::Terracotta => self.imgs.mmap_site_terracotta,
1018                MarkerKind::Castle => self.imgs.mmap_site_castle,
1019                MarkerKind::Cave => self.imgs.mmap_site_cave,
1020                MarkerKind::Tree => self.imgs.mmap_site_tree,
1021                MarkerKind::Gnarling => self.imgs.mmap_site_gnarling,
1022                MarkerKind::Adlet => self.imgs.mmap_site_adlet,
1023                MarkerKind::Haniwa => self.imgs.mmap_site_haniwa,
1024                MarkerKind::Cultist => self.imgs.mmap_site_cultist,
1025                MarkerKind::Sahagin => self.imgs.mmap_site_sahagin,
1026                MarkerKind::Myrmidon => self.imgs.mmap_site_myrmidon,
1027                MarkerKind::DwarvenMine => self.imgs.mmap_site_mine,
1028                MarkerKind::VampireCastle => self.imgs.mmap_site_vampire_castle,
1029
1030                MarkerKind::Bridge => self.imgs.mmap_site_bridge,
1031                MarkerKind::GliderCourse => self.imgs.mmap_site_glider_course,
1032            })
1033            .x_y_position_relative_to(
1034                state.ids.map_layers[0],
1035                position::Relative::Scalar(rpos.x as f64),
1036                position::Relative::Scalar(rpos.y as f64),
1037            )
1038            .w_h(rside as f64, rside as f64)
1039            .hover_image(match &marker.kind {
1040                MarkerKind::Unknown => self.imgs.mmap_unknown_hover,
1041                MarkerKind::Town => self.imgs.mmap_site_town_hover,
1042                MarkerKind::ChapelSite => self.imgs.mmap_site_sea_chapel_hover,
1043                MarkerKind::Terracotta => self.imgs.mmap_site_terracotta_hover,
1044                MarkerKind::Castle => self.imgs.mmap_site_castle_hover,
1045                MarkerKind::Cave => self.imgs.mmap_site_cave_hover,
1046                MarkerKind::Tree => self.imgs.mmap_site_tree_hover,
1047                MarkerKind::Gnarling => self.imgs.mmap_site_gnarling_hover,
1048                MarkerKind::Adlet => self.imgs.mmap_site_adlet_hover,
1049                MarkerKind::Haniwa => self.imgs.mmap_site_haniwa_hover,
1050                MarkerKind::Cultist => self.imgs.mmap_site_cultist_hover,
1051                MarkerKind::Sahagin => self.imgs.mmap_site_sahagin_hover,
1052                MarkerKind::Myrmidon => self.imgs.mmap_site_myrmidon_hover,
1053                MarkerKind::DwarvenMine => self.imgs.mmap_site_mine_hover,
1054                MarkerKind::VampireCastle => self.imgs.mmap_site_vampire_castle_hover,
1055                MarkerKind::Bridge => self.imgs.mmap_site_bridge_hover,
1056                MarkerKind::GliderCourse => self.imgs.mmap_site_glider_course_hover,
1057            })
1058            .image_color(UI_HIGHLIGHT_0.alpha(fade))
1059            .with_tooltip(
1060                self.tooltip_manager,
1061                &title,
1062                &desc,
1063                &site_tooltip,
1064                match &marker.kind {
1065                    MarkerKind::Gnarling
1066                    | MarkerKind::ChapelSite
1067                    | MarkerKind::Terracotta
1068                    | MarkerKind::Adlet
1069                    | MarkerKind::VampireCastle
1070                    | MarkerKind::Haniwa
1071                    | MarkerKind::Cultist
1072                    | MarkerKind::Sahagin
1073                    | MarkerKind::Myrmidon
1074                    | MarkerKind::DwarvenMine => match difficulty {
1075                        Some(0) => QUALITY_LOW,
1076                        Some(1) => QUALITY_COMMON,
1077                        Some(2) => QUALITY_MODERATE,
1078                        Some(3) => QUALITY_HIGH,
1079                        Some(4 | 5) => QUALITY_EPIC,
1080                        _ => TEXT_COLOR,
1081                    },
1082                    _ => TEXT_COLOR,
1083                },
1084            );
1085
1086            handle_widget_mouse_events(
1087                state.ids.mmap_site_icons[i],
1088                MarkerChange::Pos(marker.wpos.map(|e| e as f32)),
1089                ui,
1090                &mut events,
1091                state.ids.map_layers[0],
1092            );
1093
1094            // Only display sites that are toggled on
1095            let show_site = match &marker.kind {
1096                MarkerKind::Unknown => true,
1097                MarkerKind::Town => show_towns,
1098                MarkerKind::Gnarling
1099                | MarkerKind::ChapelSite
1100                | MarkerKind::DwarvenMine
1101                | MarkerKind::Haniwa
1102                | MarkerKind::Cultist
1103                | MarkerKind::Sahagin
1104                | MarkerKind::Myrmidon
1105                | MarkerKind::Terracotta
1106                | MarkerKind::Adlet
1107                | MarkerKind::VampireCastle => show_dungeons,
1108                MarkerKind::Castle => show_castles,
1109                MarkerKind::Cave => show_caves,
1110                MarkerKind::Tree => show_trees,
1111                MarkerKind::Bridge => show_bridges,
1112                MarkerKind::GliderCourse => show_glider_courses,
1113            };
1114            if show_site {
1115                let tooltip_visible = site_btn.set_ext(state.ids.mmap_site_icons[i], ui).1;
1116
1117                if SHOW_ECONOMY
1118                    && tooltip_visible
1119                    && let Some(site_id) = marker.id
1120                    && let Some(site) = self.client.sites().get(&site_id)
1121                    && site.economy.is_none()
1122                {
1123                    events.push(Event::RequestSiteInfo(site_id));
1124                }
1125            }
1126
1127            // Difficulty from 0-6
1128            // 0 = towns and places without a difficulty level
1129            if show_difficulty {
1130                let rsize = zoom * 2.4; // Size factor for difficulty indicators
1131                let dif_img = Image::new(match difficulty {
1132                    Some(0) => self.imgs.map_dif_1,
1133                    Some(1) => self.imgs.map_dif_2,
1134                    Some(2) => self.imgs.map_dif_3,
1135                    Some(3) => self.imgs.map_dif_4,
1136                    Some(4 | 5) => self.imgs.map_dif_5,
1137                    Some(_) => self.imgs.map_dif_unknown,
1138                    None => self.imgs.nothing,
1139                })
1140                .mid_top_with_margin_on(state.ids.mmap_site_icons[i], match difficulty {
1141                    Some(0 | 1) => -1.0 * rsize,
1142                    Some(_) => -2.0 * rsize,
1143                    _ => -1.0 * rsize,
1144                })
1145                .w(match difficulty {
1146                    Some(0) => 1.0 * rsize,
1147                    Some(1 | 2) => 2.0 * rsize,
1148                    Some(_) => 3.0 * rsize,
1149                    _ => 1.0 * rsize,
1150                })
1151                .h(match difficulty {
1152                    Some(0 | 1) => 1.0 * rsize,
1153                    Some(_) => 2.0 * rsize,
1154                    _ => 1.0 * rsize,
1155                })
1156                .color(Some(match difficulty {
1157                    Some(0) => QUALITY_LOW,
1158                    Some(1) => QUALITY_COMMON,
1159                    Some(2) => QUALITY_MODERATE,
1160                    Some(3) => QUALITY_HIGH,
1161                    Some(4 | 5) => QUALITY_EPIC, // Change this whenever difficulty is fixed
1162                    _ => TEXT_COLOR,
1163                }));
1164                match &marker.kind {
1165                    MarkerKind::Unknown => dif_img.set(state.ids.site_difs[i], ui),
1166                    MarkerKind::Town => {
1167                        if show_towns {
1168                            dif_img.set(state.ids.site_difs[i], ui)
1169                        }
1170                    },
1171                    MarkerKind::Gnarling
1172                    | MarkerKind::ChapelSite
1173                    | MarkerKind::Haniwa
1174                    | MarkerKind::Cultist
1175                    | MarkerKind::Sahagin
1176                    | MarkerKind::Myrmidon
1177                    | MarkerKind::Terracotta
1178                    | MarkerKind::Adlet
1179                    | MarkerKind::VampireCastle => {
1180                        if show_dungeons {
1181                            dif_img.set(state.ids.site_difs[i], ui)
1182                        }
1183                    },
1184                    MarkerKind::DwarvenMine => {
1185                        if show_dungeons {
1186                            dif_img.set(state.ids.site_difs[i], ui)
1187                        }
1188                    },
1189                    MarkerKind::Castle => {
1190                        if show_castles {
1191                            dif_img.set(state.ids.site_difs[i], ui)
1192                        }
1193                    },
1194                    MarkerKind::Cave => {
1195                        if show_caves {
1196                            dif_img.set(state.ids.site_difs[i], ui)
1197                        }
1198                    },
1199                    MarkerKind::Tree => {
1200                        if show_trees {
1201                            dif_img.set(state.ids.site_difs[i], ui)
1202                        }
1203                    },
1204                    MarkerKind::Bridge => {
1205                        if show_bridges {
1206                            dif_img.set(state.ids.site_difs[i], ui)
1207                        }
1208                    },
1209                    MarkerKind::GliderCourse => {
1210                        if show_glider_courses {
1211                            dif_img.set(state.ids.site_difs[i], ui)
1212                        }
1213                    },
1214                }
1215
1216                handle_widget_mouse_events(
1217                    state.ids.site_difs[i],
1218                    MarkerChange::Pos(marker.wpos.map(|e| e as f32)),
1219                    ui,
1220                    &mut events,
1221                    state.ids.map_layers[0],
1222                );
1223            }
1224        }
1225        for (i, poi) in self.client.pois().iter().enumerate() {
1226            // TODO: computation of text size to pass to wpos_to_rpos_fade, so it can
1227            // determine when it's going past the edge of the map screen
1228            let (rpos, fade) = match wpos_to_rpos_fade(
1229                poi.wpos.map(|e| e as f32),
1230                Vec2::from(zoom as f32 * 3.0),
1231                zoom as f32 * 5.0,
1232            ) {
1233                Some(rpos) => rpos,
1234                None => continue,
1235            };
1236            let title = &poi.name;
1237            match poi.kind {
1238                PoiKind::Peak(alt) => {
1239                    let height = format!("{} m", alt);
1240                    if show_peaks && zoom > 2.0 {
1241                        Text::new(title)
1242                            .x_y_position_relative_to(
1243                                state.ids.map_layers[0],
1244                                position::Relative::Scalar(rpos.x as f64),
1245                                position::Relative::Scalar(rpos.y as f64 + zoom * 4.0),
1246                            )
1247                            .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1248                            .font_id(self.fonts.cyri.conrod_id)
1249                            .graphics_for(state.ids.map_layers[0])
1250                            .color(TEXT_BG.alpha(fade))
1251                            .set(state.ids.mmap_poi_title_bgs[i], ui);
1252                        Text::new(title)
1253                                .bottom_left_with_margins_on(state.ids.mmap_poi_title_bgs[i], 1.0, 1.0)
1254                                .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1255                                .font_id(self.fonts.cyri.conrod_id)
1256                                //.graphics_for(state.ids.map_layers[0])
1257                                .color(TEXT_COLOR.alpha(fade))
1258                                .set(state.ids.mmap_poi_titles[i], ui);
1259
1260                        handle_widget_mouse_events(
1261                            state.ids.mmap_poi_titles[i],
1262                            MarkerChange::Pos(poi.wpos.map(|e| e as f32)),
1263                            ui,
1264                            &mut events,
1265                            state.ids.map_layers[0],
1266                        );
1267
1268                        // Show peak altitude
1269                        if ui
1270                            .widget_input(state.ids.mmap_poi_titles[i])
1271                            .mouse()
1272                            .is_some_and(|m| m.is_over())
1273                        {
1274                            Text::new(&height)
1275                                .mid_bottom_with_margin_on(
1276                                    state.ids.mmap_poi_title_bgs[i],
1277                                    zoom * 3.5,
1278                                )
1279                                .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1280                                .font_id(self.fonts.cyri.conrod_id)
1281                                .graphics_for(state.ids.map_layers[0])
1282                                .color(TEXT_BG.alpha(fade))
1283                                .set(state.ids.peaks_txt_bg, ui);
1284                            Text::new(&height)
1285                                .bottom_left_with_margins_on(state.ids.peaks_txt_bg, 1.0, 1.0)
1286                                .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1287                                .font_id(self.fonts.cyri.conrod_id)
1288                                .graphics_for(state.ids.map_layers[0])
1289                                .color(TEXT_COLOR.alpha(fade))
1290                                .set(state.ids.peaks_txt, ui);
1291                        }
1292                    }
1293                },
1294                PoiKind::Lake(size) => {
1295                    if show_biomes && zoom > 2.0 && zoom.powi(2) * size as f64 > 30.0 {
1296                        let font_scale_factor = if size > 20 {
1297                            size as f64 / 25.0
1298                        } else if size > 10 {
1299                            size as f64 / 10.0
1300                        } else if size > 5 {
1301                            size as f64 / 6.0
1302                        } else {
1303                            size as f64 / 2.5
1304                        };
1305                        Text::new(title)
1306                            .x_y_position_relative_to(
1307                                state.ids.map_layers[0],
1308                                position::Relative::Scalar(rpos.x as f64),
1309                                position::Relative::Scalar(rpos.y as f64),
1310                            )
1311                            .font_size(
1312                                self.fonts.cyri.scale(
1313                                    (2.0 + font_scale_factor * zoom).clamp(10.0, 18.0) as u32,
1314                                ),
1315                            )
1316                            .font_id(self.fonts.cyri.conrod_id)
1317                            .graphics_for(state.ids.map_layers[0])
1318                            .color(TEXT_BLUE_COLOR.alpha(fade))
1319                            .set(state.ids.mmap_poi_icons[i], ui);
1320                    }
1321                },
1322            }
1323        }
1324        // Group member indicators
1325        let client_state = self.client.state();
1326        let stats = client_state.ecs().read_storage::<comp::Stats>();
1327        let member_pos = client_state.ecs().read_storage::<comp::Pos>();
1328        let group_members = self
1329            .client
1330            .group_members()
1331            .iter()
1332            .filter_map(|(u, r)| match r {
1333                Role::Member => Some(u),
1334                Role::Pet => None,
1335            })
1336            .collect::<Vec<_>>();
1337        let group_size = group_members.len();
1338        //let in_group = !group_members.is_empty();
1339        let id_maps = client_state
1340            .ecs()
1341            .read_resource::<common_net::sync::IdMaps>();
1342        if state.ids.member_indicators.len() < group_size {
1343            state.update(|s| {
1344                s.ids
1345                    .member_indicators
1346                    .resize(group_size, &mut ui.widget_id_generator())
1347            })
1348        };
1349        for (i, &uid) in group_members.iter().copied().enumerate() {
1350            let entity = id_maps.uid_entity(uid);
1351            let member_pos = entity.and_then(|entity| member_pos.get(entity));
1352            let stats = entity.and_then(|entity| stats.get(entity));
1353            let name = if let Some(stats) = stats {
1354                stats.name.to_string()
1355            } else {
1356                "".to_string()
1357            };
1358
1359            if let Some(member_pos) = member_pos {
1360                let factor = 1.2;
1361                let side_length = 20.0 * factor;
1362
1363                let (rpos, fade) = match wpos_to_rpos_fade(
1364                    member_pos.0.xy(),
1365                    Vec2::from(side_length / 2.0),
1366                    side_length / 2.0,
1367                ) {
1368                    Some(x) => x,
1369                    None => continue,
1370                };
1371
1372                let z_comparison = (member_pos.0.z - player_pos.z) as i32;
1373
1374                Button::image(match z_comparison {
1375                    10..=i32::MAX => self.imgs.indicator_group_up,
1376                    i32::MIN..=-10 => self.imgs.indicator_group_down,
1377                    _ => self.imgs.indicator_group,
1378                })
1379                .x_y_position_relative_to(
1380                    state.ids.map_layers[0],
1381                    position::Relative::Scalar(rpos.x as f64),
1382                    position::Relative::Scalar(rpos.y as f64),
1383                )
1384                .w_h(side_length as f64, side_length as f64)
1385                .image_color(Color::Rgba(1.0, 1.0, 1.0, fade))
1386                .floating(true)
1387                .with_tooltip(self.tooltip_manager, &name, "", &site_tooltip, TEXT_COLOR)
1388                .set(state.ids.member_indicators[i], ui);
1389
1390                handle_widget_mouse_events(
1391                    state.ids.member_indicators[i],
1392                    MarkerChange::Pos(member_pos.0.xy()),
1393                    ui,
1394                    &mut events,
1395                    state.ids.map_layers[0],
1396                );
1397            }
1398        }
1399
1400        let factor = 1.4;
1401        let side_length = 20.0 * factor;
1402        // Groups location markers
1403        if state.ids.location_marker_group.len() < self.location_markers.group.len() {
1404            state.update(|s| {
1405                s.ids.location_marker_group.resize(
1406                    self.location_markers.group.len(),
1407                    &mut ui.widget_id_generator(),
1408                )
1409            })
1410        };
1411        for (i, (&uid, &rpos)) in self.location_markers.group.iter().enumerate() {
1412            let lm = rpos.as_();
1413            if let Some((rpos, fade)) =
1414                wpos_to_rpos_fade(lm, Vec2::from(side_length / 2.0), side_length / 2.0)
1415            {
1416                let name = self
1417                    .client
1418                    .player_list()
1419                    .get(&uid)
1420                    .map(|info| info.player_alias.as_str())
1421                    .or_else(|| {
1422                        id_maps
1423                            .uid_entity(uid)
1424                            .and_then(|entity| stats.get(entity))
1425                            .map(|stats| stats.name.as_str())
1426                    })
1427                    .unwrap_or("");
1428
1429                let image_id = match self.client.group_info().map(|info| info.1) {
1430                    Some(leader) if leader == uid => self.imgs.location_marker_group_leader,
1431                    _ => self.imgs.location_marker_group,
1432                };
1433
1434                Button::image(image_id)
1435                    .x_y_position_relative_to(
1436                        state.ids.map_layers[0],
1437                        position::Relative::Scalar(rpos.x as f64),
1438                        position::Relative::Scalar(rpos.y as f64 + 10.0 * factor as f64),
1439                    )
1440                    .w_h(side_length as f64, side_length as f64)
1441                    .image_color(Color::Rgba(1.0, 1.0, 1.0, fade))
1442                    .floating(true)
1443                    .with_tooltip(
1444                        self.tooltip_manager,
1445                        &i18n.get_msg("hud-map-marked_location"),
1446                        &format!(
1447                            "X: {}, Y: {}\n\n{}",
1448                            lm.x as i32,
1449                            lm.y as i32,
1450                            i18n.get_msg_ctx("hud-map-placed_by", &i18n::fluent_args! {
1451                                "name" => name
1452                            }),
1453                        ),
1454                        &site_tooltip,
1455                        TEXT_VELORITE,
1456                    )
1457                    .set(state.ids.location_marker_group[i], ui);
1458                handle_widget_mouse_events(
1459                    state.ids.location_marker_group[i],
1460                    MarkerChange::Pos(lm),
1461                    ui,
1462                    &mut events,
1463                    state.ids.map_layers[0],
1464                );
1465            }
1466        }
1467        // Location marker
1468        if let Some((lm, (rpos, fade))) = self.location_markers.owned.and_then(|lm| {
1469            let lm = lm.as_();
1470            Some(lm).zip(wpos_to_rpos_fade(
1471                lm,
1472                Vec2::from(side_length / 2.0),
1473                side_length / 2.0,
1474            ))
1475        }) {
1476            if Button::image(self.imgs.location_marker)
1477                .x_y_position_relative_to(
1478                    state.ids.map_layers[0],
1479                    position::Relative::Scalar(rpos.x as f64),
1480                    position::Relative::Scalar(rpos.y as f64 + 10.0 * factor as f64),
1481                )
1482                .w_h(side_length as f64, side_length as f64)
1483                .image_color(Color::Rgba(1.0, 1.0, 1.0, fade))
1484                .floating(true)
1485                .with_tooltip(
1486                    self.tooltip_manager,
1487                    &i18n.get_msg("hud-map-marked_location"),
1488                    &format!(
1489                        "X: {}, Y: {}\n\n{}",
1490                        lm.x as i32,
1491                        lm.y as i32,
1492                        i18n.get_msg("hud-map-marked_location_remove")
1493                    ),
1494                    &site_tooltip,
1495                    TEXT_VELORITE,
1496                )
1497                .set(state.ids.location_marker, ui)
1498                .was_clicked()
1499            {
1500                events.push(Event::RemoveMarker);
1501            }
1502
1503            handle_widget_mouse_events(
1504                state.ids.location_marker,
1505                MarkerChange::Remove,
1506                ui,
1507                &mut events,
1508                state.ids.map_layers[0],
1509            );
1510        }
1511
1512        // Cursor pos relative to playerpos and widget size
1513        // Cursor stops moving on an axis as soon as it's position exceeds the maximum
1514        // // size of the widget
1515
1516        // Don't show if outside or near the edge of the map
1517        let arrow_sz = {
1518            let scale = 0.5;
1519            Vec2::new(36.0, 37.0) * scale
1520        };
1521        // Hide if icon could go off of the edge of the map
1522        if let Some((rpos, fade)) =
1523            wpos_to_rpos_fade(player_pos.xy(), arrow_sz, arrow_sz.reduce_partial_min())
1524        {
1525            Image::new(self.rot_imgs.indicator_mmap_small.target_north)
1526                .x_y_position_relative_to(
1527                    state.ids.map_layers[0],
1528                    position::Relative::Scalar(rpos.x as f64),
1529                    position::Relative::Scalar(rpos.y as f64),
1530                )
1531                .w_h(arrow_sz.x as f64, arrow_sz.y as f64)
1532                .color(Some(UI_HIGHLIGHT_0.alpha(fade)))
1533                .set(state.ids.indicator, ui);
1534
1535            handle_widget_mouse_events(
1536                state.ids.indicator,
1537                MarkerChange::Pos(player_pos.xy()),
1538                ui,
1539                &mut events,
1540                state.ids.map_layers[0],
1541            );
1542        }
1543
1544        // Info about controls
1545        let icon_size = Vec2::new(25.6, 28.8);
1546        let recenter: bool = drag.x != 0.0 || drag.y != 0.0;
1547        if Button::image(self.imgs.button)
1548            .w_h(92.0, icon_size.y)
1549            .mid_bottom_with_margin_on(state.ids.map_layers[0], -36.0)
1550            .hover_image(if recenter {
1551                self.imgs.button_hover
1552            } else {
1553                self.imgs.button
1554            })
1555            .press_image(if recenter {
1556                self.imgs.button_press
1557            } else {
1558                self.imgs.button
1559            })
1560            .label(&i18n.get_msg("hud-map-recenter"))
1561            .label_y(position::Relative::Scalar(1.0))
1562            .label_color(if recenter {
1563                TEXT_COLOR
1564            } else {
1565                TEXT_GRAY_COLOR
1566            })
1567            .image_color(if recenter {
1568                TEXT_COLOR
1569            } else {
1570                TEXT_GRAY_COLOR
1571            })
1572            .label_font_size(self.fonts.cyri.scale(12))
1573            .label_font_id(self.fonts.cyri.conrod_id)
1574            .set(state.ids.recenter_button, ui)
1575            .was_clicked()
1576        {
1577            events.push(Event::MapDrag(Vec2::zero()));
1578        };
1579
1580        Image::new(self.imgs.m_move_ico)
1581            .bottom_left_with_margins_on(state.ids.map_layers[0], -36.0, 0.0)
1582            .w_h(icon_size.x, icon_size.y)
1583            .color(Some(UI_HIGHLIGHT_0))
1584            .set(state.ids.drag_ico, ui);
1585        Text::new(&i18n.get_msg("hud-map-drag"))
1586            .right_from(state.ids.drag_ico, 5.0)
1587            .font_size(self.fonts.cyri.scale(14))
1588            .font_id(self.fonts.cyri.conrod_id)
1589            .graphics_for(state.ids.map_layers[0])
1590            .color(TEXT_COLOR)
1591            .set(state.ids.drag_txt, ui);
1592        Image::new(self.imgs.m_scroll_ico)
1593            .right_from(state.ids.drag_txt, 5.0)
1594            .w_h(icon_size.x, icon_size.y)
1595            .color(Some(UI_HIGHLIGHT_0))
1596            .set(state.ids.zoom_ico, ui);
1597        Text::new(&i18n.get_msg("hud-map-zoom"))
1598            .right_from(state.ids.zoom_ico, 5.0)
1599            .font_size(self.fonts.cyri.scale(14))
1600            .font_id(self.fonts.cyri.conrod_id)
1601            .graphics_for(state.ids.map_layers[0])
1602            .color(TEXT_COLOR)
1603            .set(state.ids.zoom_txt, ui);
1604
1605        Text::new(&location_marker_binding.display_shortest(key_layout))
1606            .right_from(state.ids.zoom_txt, 15.0)
1607            .font_size(self.fonts.cyri.scale(14))
1608            .font_id(self.fonts.cyri.conrod_id)
1609            .graphics_for(state.ids.map_layers[0])
1610            .color(TEXT_COLOR)
1611            .set(state.ids.waypoint_binding_txt, ui);
1612
1613        Text::new(&i18n.get_msg("hud-map-mid_click"))
1614            .right_from(state.ids.waypoint_binding_txt, 5.0)
1615            .font_size(self.fonts.cyri.scale(14))
1616            .font_id(self.fonts.cyri.conrod_id)
1617            .graphics_for(state.ids.map_layers[0])
1618            .color(TEXT_COLOR)
1619            .set(state.ids.waypoint_txt, ui);
1620
1621        // Show topographic map
1622        if Button::image(self.imgs.button)
1623            .w_h(92.0, icon_size.y)
1624            .hover_image(self.imgs.button_hover)
1625            .press_image(self.imgs.button_press)
1626            .bottom_right_with_margins_on(state.ids.map_layers[0], -36.0, 0.0)
1627            .with_tooltip(
1628                self.tooltip_manager,
1629                &i18n.get_msg("hud-map-change_map_mode"),
1630                "",
1631                &site_tooltip,
1632                TEXT_COLOR,
1633            )
1634            .set(state.ids.map_mode_btn, ui)
1635            .was_clicked()
1636        {
1637            events.push(Event::SettingsChange(MapShowTopoMap(!show_topo_map)));
1638        };
1639        Button::image(self.imgs.map_mode_overlay)
1640            .w_h(92.0, icon_size.y)
1641            .graphics_for(state.ids.map_mode_btn)
1642            .middle_of(state.ids.map_mode_btn)
1643            .set(state.ids.map_mode_overlay, ui);
1644
1645        // Render voxel view on minimap
1646        if Button::image(self.imgs.button)
1647            .w_h(92.0, icon_size.y)
1648            .hover_image(self.imgs.button_hover)
1649            .press_image(self.imgs.button_press)
1650            .left_from(state.ids.map_mode_btn, 5.0)
1651            .with_tooltip(
1652                self.tooltip_manager,
1653                &i18n.get_msg("hud-map-toggle_minimap_voxel"),
1654                &i18n.get_msg("hud-map-zoom_minimap_explanation"),
1655                &site_tooltip,
1656                TEXT_COLOR,
1657            )
1658            .set(state.ids.minimap_mode_btn, ui)
1659            .was_clicked()
1660        {
1661            events.push(Event::SettingsChange(MapShowVoxelMap(!show_voxel_map)));
1662        };
1663        Button::image(self.imgs.minimap_mode_overlay)
1664            .w_h(92.0, icon_size.y)
1665            .graphics_for(state.ids.minimap_mode_btn)
1666            .middle_of(state.ids.minimap_mode_btn)
1667            .set(state.ids.minimap_mode_overlay, ui);
1668
1669        events
1670    }
1671}