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