Skip to main content

veloren_voxygen/hud/settings_window/
video.rs

1use super::{RESET_BUTTONS_HEIGHT, RESET_BUTTONS_WIDTH};
2
3use crate::{
4    GlobalState,
5    hud::{
6        CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR, MENU_BG, STAMINA_COLOR, TEXT_COLOR,
7        UI_HIGHLIGHT_0, UI_MAIN, UI_SUBTLE, img_ids::Imgs,
8    },
9    render::{
10        AaMode, BloomConfig, BloomFactor, BloomMode, CloudMode, FluidMode, LightingMode,
11        PresentMode, ReflectionMode, RenderMode, ShadowMapMode, ShadowMode, UpscaleMode,
12    },
13    session::settings_change::Graphics as GraphicsChange,
14    settings::{Fps, GraphicsSettings},
15    ui::{ImageSlider, ToggleButton, fonts::Fonts},
16    window::{FullScreenSettings, FullscreenMode},
17};
18use conrod_core::{
19    Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, color,
20    position::Relative,
21    widget::{self, Button, DropDownList, Rectangle, Scrollbar, Text},
22    widget_ids,
23};
24use core::convert::TryFrom;
25use i18n::Localization;
26
27use itertools::Itertools;
28use std::{iter::once, rc::Rc};
29use winit::monitor::VideoModeHandle;
30
31widget_ids! {
32    struct Ids {
33        window,
34        window_r,
35        window_scrollbar,
36        reset_graphics_button,
37        minimal_graphics_button,
38        low_graphics_button,
39        medium_graphics_button,
40        high_graphics_button,
41        ultra_graphics_button,
42        fps_counter,
43        pipeline_recreation_text,
44        terrain_vd_slider,
45        terrain_vd_text,
46        terrain_vd_value,
47        entity_vd_slider,
48        entity_vd_text,
49        entity_vd_value,
50        ld_slider,
51        ld_text,
52        ld_value,
53        lod_detail_slider,
54        lod_detail_text,
55        lod_detail_value,
56        sprite_dist_slider,
57        sprite_dist_text,
58        sprite_dist_value,
59        figure_dist_slider,
60        figure_dist_text,
61        figure_dist_value,
62        max_fps_slider,
63        max_fps_text,
64        max_fps_value,
65        max_background_fps_slider,
66        max_background_fps_text,
67        max_background_fps_value,
68        present_mode_text,
69        present_mode_list,
70        fov_slider,
71        fov_text,
72        fov_value,
73        gamma_slider,
74        gamma_text,
75        gamma_value,
76        exposure_slider,
77        exposure_text,
78        exposure_value,
79        ambiance_slider,
80        ambiance_text,
81        ambiance_value,
82        aa_mode_text,
83        aa_mode_list,
84        //
85        bloom_intensity_text,
86        bloom_intensity_slider,
87        bloom_intensity_value,
88        point_glow_text,
89        point_glow_slider,
90        point_glow_value,
91        //
92        upscale_factor_text,
93        upscale_factor_list,
94        cloud_mode_text,
95        cloud_mode_list,
96        fluid_mode_text,
97        fluid_mode_list,
98        reflection_mode_text,
99        reflection_mode_list,
100        fullscreen_mode_text,
101        fullscreen_mode_list,
102        //
103        resolution,
104        resolution_label,
105        bit_depth,
106        bit_depth_label,
107        refresh_rate,
108        refresh_rate_label,
109        //
110        gpu_profiler_button,
111        gpu_profiler_label,
112        //
113        particles_button,
114        particles_label,
115        weapon_trails_button,
116        weapon_trails_label,
117        flashing_lights_button,
118        flashing_lights_label,
119        flashing_lights_info_label,
120        //
121        fullscreen_button,
122        fullscreen_label,
123        lighting_mode_text,
124        lighting_mode_list,
125        shadow_mode_text,
126        shadow_mode_list,
127        shadow_mode_map_resolution_text,
128        shadow_mode_map_resolution_slider,
129        shadow_mode_map_resolution_value,
130        rain_button,
131        rain_label,
132        rain_map_resolution_text,
133        rain_map_resolution_slider,
134        rain_map_resolution_value
135    }
136}
137
138#[derive(WidgetCommon)]
139pub struct Video<'a> {
140    global_state: &'a GlobalState,
141    imgs: &'a Imgs,
142    fonts: &'a Fonts,
143    localized_strings: &'a Localization,
144    server_view_distance_limit: Option<u32>,
145    fps: f32,
146    #[conrod(common_builder)]
147    common: widget::CommonBuilder,
148}
149impl<'a> Video<'a> {
150    pub fn new(
151        global_state: &'a GlobalState,
152        imgs: &'a Imgs,
153        fonts: &'a Fonts,
154        localized_strings: &'a Localization,
155        server_view_distance_limit: Option<u32>,
156        fps: f32,
157    ) -> Self {
158        Self {
159            global_state,
160            imgs,
161            fonts,
162            localized_strings,
163            server_view_distance_limit,
164            fps,
165            common: widget::CommonBuilder::default(),
166        }
167    }
168}
169
170pub struct State {
171    ids: Ids,
172    // Resolution, Bit Depth and Refresh Rate
173    video_modes: Vec<VideoModeHandle>,
174}
175const FPS_CHOICES: [Fps; 17] = [
176    Fps::Max(15),
177    Fps::Max(30),
178    Fps::Max(40),
179    Fps::Max(50),
180    Fps::Max(60),
181    Fps::Max(75),
182    Fps::Max(90),
183    Fps::Max(100),
184    Fps::Max(120),
185    Fps::Max(144),
186    Fps::Max(165),
187    Fps::Max(200),
188    Fps::Max(240),
189    Fps::Max(280),
190    Fps::Max(360),
191    Fps::Max(500),
192    Fps::Unlimited,
193];
194const BG_FPS_CHOICES: [Fps; 20] = [
195    Fps::Max(5),
196    Fps::Max(10),
197    Fps::Max(15),
198    Fps::Max(20),
199    Fps::Max(30),
200    Fps::Max(40),
201    Fps::Max(50),
202    Fps::Max(60),
203    Fps::Max(75),
204    Fps::Max(90),
205    Fps::Max(100),
206    Fps::Max(120),
207    Fps::Max(144),
208    Fps::Max(165),
209    Fps::Max(200),
210    Fps::Max(240),
211    Fps::Max(280),
212    Fps::Max(360),
213    Fps::Max(500),
214    Fps::Unlimited,
215];
216
217impl Widget for Video<'_> {
218    type Event = Vec<GraphicsChange>;
219    type State = State;
220    type Style = ();
221
222    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
223        let video_modes = self
224            .global_state
225            .window
226            .window()
227            .current_monitor()
228            .map(|monitor| monitor.video_modes().collect())
229            .unwrap_or_default();
230
231        State {
232            ids: Ids::new(id_gen),
233            video_modes,
234        }
235    }
236
237    fn style(&self) -> Self::Style {}
238
239    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
240        common_base::prof_span!("Video::update");
241        let widget::UpdateArgs { state, ui, .. } = args;
242
243        let mut events = Vec::new();
244
245        Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT)
246            .xy(args.rect.xy())
247            .graphics_for(args.id)
248            .scroll_kids()
249            .scroll_kids_vertically()
250            .set(state.ids.window, ui);
251        Rectangle::fill_with([args.rect.w() / 2.0, args.rect.h()], color::TRANSPARENT)
252            .top_right()
253            .parent(state.ids.window)
254            .set(state.ids.window_r, ui);
255        Scrollbar::y_axis(state.ids.window)
256            .thickness(5.0)
257            .rgba(0.33, 0.33, 0.33, 1.0)
258            .set(state.ids.window_scrollbar, ui);
259
260        // FPS/TPS Counter
261        //let text_col = match
262        let fps_col = match self.fps as i32 {
263            0..=14 => CRITICAL_HP_COLOR,
264            15..=29 => LOW_HP_COLOR,
265            30..=50 => HP_COLOR,
266            _ => STAMINA_COLOR,
267        };
268        Text::new(&format!("FPS: {:.0}", self.fps))
269            .color(fps_col)
270            .top_right_with_margins_on(state.ids.window_r, 10.0, 10.0)
271            .font_id(self.fonts.cyri.conrod_id)
272            .font_size(self.fonts.cyri.scale(18))
273            .set(state.ids.fps_counter, ui);
274
275        // Pipeline recreation status
276        if let Some((total, complete)) = self
277            .global_state
278            .window
279            .renderer()
280            .pipeline_recreation_status()
281        {
282            Text::new(&format!("Rebuilding pipelines: ({}/{})", complete, total))
283                .down_from(state.ids.fps_counter, 10.0)
284                .align_right_of(state.ids.fps_counter)
285                .font_size(self.fonts.cyri.scale(14))
286                .font_id(self.fonts.cyri.conrod_id)
287                // TODO: make color pulse or something
288                .color(TEXT_COLOR)
289                .set(state.ids.pipeline_recreation_text, ui);
290        }
291
292        // Reset the graphics settings to the default settings
293        if Button::image(self.imgs.button)
294            .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
295            .hover_image(self.imgs.button_hover)
296            .press_image(self.imgs.button_press)
297            .top_left_with_margins_on(state.ids.window, 10.0, 10.0)
298            .label(
299                &self
300                    .localized_strings
301                    .get_msg("hud-settings-reset_graphics"),
302            )
303            .label_font_size(self.fonts.cyri.scale(14))
304            .label_color(TEXT_COLOR)
305            .label_font_id(self.fonts.cyri.conrod_id)
306            .label_y(Relative::Scalar(2.0))
307            .set(state.ids.reset_graphics_button, ui)
308            .was_clicked()
309        {
310            events.push(GraphicsChange::ResetGraphicsSettings);
311        }
312
313        // Graphics presets buttons
314        let preset_buttons: [(_, _, fn(_) -> _); 5] = [
315            (
316                "hud-settings-minimal_graphics",
317                state.ids.minimal_graphics_button,
318                GraphicsSettings::into_minimal,
319            ),
320            (
321                "hud-settings-low_graphics",
322                state.ids.low_graphics_button,
323                GraphicsSettings::into_low,
324            ),
325            (
326                "hud-settings-medium_graphics",
327                state.ids.medium_graphics_button,
328                GraphicsSettings::into_medium,
329            ),
330            (
331                "hud-settings-high_graphics",
332                state.ids.high_graphics_button,
333                GraphicsSettings::into_high,
334            ),
335            (
336                "hud-settings-ultra_graphics",
337                state.ids.ultra_graphics_button,
338                GraphicsSettings::into_ultra,
339            ),
340        ];
341
342        let mut lhs = state.ids.reset_graphics_button;
343
344        for (msg, id, change_fn) in preset_buttons {
345            if Button::new()
346                .label(&self.localized_strings.get_msg(msg))
347                .w_h(80.0, 34.0)
348                .color(UI_SUBTLE)
349                .hover_color(UI_MAIN)
350                .press_color(UI_HIGHLIGHT_0)
351                .right_from(lhs, 12.0)
352                .label_font_size(self.fonts.cyri.scale(14))
353                .label_color(TEXT_COLOR)
354                .label_font_id(self.fonts.cyri.conrod_id)
355                .label_y(Relative::Scalar(2.0))
356                .set(id, ui)
357                .was_clicked()
358            {
359                events.push(GraphicsChange::ChangeGraphicsSettings(Rc::new(change_fn)));
360            }
361            lhs = id;
362        }
363
364        // View Distance
365        Text::new(&self.localized_strings.get_msg("hud-settings-view_distance"))
366            .down_from(state.ids.reset_graphics_button, 10.0)
367            .font_size(self.fonts.cyri.scale(14))
368            .font_id(self.fonts.cyri.conrod_id)
369            .color(TEXT_COLOR)
370            .set(state.ids.terrain_vd_text, ui);
371
372        let terrain_view_distance = self.global_state.settings.graphics.terrain_view_distance;
373        let server_view_distance_limit = self.server_view_distance_limit.unwrap_or(u32::MAX);
374        if let Some(new_val) = ImageSlider::discrete(
375            terrain_view_distance,
376            1,
377            client::MAX_SELECTABLE_VIEW_DISTANCE,
378            self.imgs.slider_indicator,
379            self.imgs.slider,
380        )
381        .w_h(104.0, 22.0)
382        .down_from(state.ids.terrain_vd_text, 8.0)
383        .track_breadth(12.0)
384        .slider_length(10.0)
385        .soft_max(server_view_distance_limit)
386        .pad_track((5.0, 5.0))
387        .set(state.ids.terrain_vd_slider, ui)
388        {
389            events.push(GraphicsChange::AdjustTerrainViewDistance(new_val));
390        }
391
392        Text::new(&if terrain_view_distance <= server_view_distance_limit {
393            format!("{terrain_view_distance}")
394        } else {
395            format!("{terrain_view_distance} ({server_view_distance_limit})")
396        })
397        .right_from(state.ids.terrain_vd_slider, 8.0)
398        .font_size(self.fonts.cyri.scale(14))
399        .font_id(self.fonts.cyri.conrod_id)
400        .color(TEXT_COLOR)
401        .set(state.ids.terrain_vd_value, ui);
402
403        // Entity View Distance
404        let soft_entity_vd_max = self
405            .server_view_distance_limit
406            .unwrap_or(u32::MAX)
407            .min(terrain_view_distance);
408        let entity_view_distance = self.global_state.settings.graphics.entity_view_distance;
409        if let Some(new_val) = ImageSlider::discrete(
410            entity_view_distance,
411            1,
412            client::MAX_SELECTABLE_VIEW_DISTANCE,
413            self.imgs.slider_indicator,
414            self.imgs.slider,
415        )
416        .w_h(104.0, 22.0)
417        .right_from(state.ids.terrain_vd_slider, 70.0)
418        .track_breadth(12.0)
419        .slider_length(10.0)
420        .soft_max(soft_entity_vd_max)
421        .pad_track((5.0, 5.0))
422        .set(state.ids.entity_vd_slider, ui)
423        {
424            events.push(GraphicsChange::AdjustEntityViewDistance(new_val));
425        }
426
427        Text::new(
428            &self
429                .localized_strings
430                .get_msg("hud-settings-entity_view_distance"),
431        )
432        .up_from(state.ids.entity_vd_slider, 10.0)
433        .font_size(self.fonts.cyri.scale(14))
434        .font_id(self.fonts.cyri.conrod_id)
435        .color(TEXT_COLOR)
436        .set(state.ids.entity_vd_text, ui);
437
438        Text::new(&if entity_view_distance <= soft_entity_vd_max {
439            format!("{entity_view_distance}")
440        } else {
441            format!("{entity_view_distance} ({soft_entity_vd_max})")
442        })
443        .right_from(state.ids.entity_vd_slider, 8.0)
444        .font_size(self.fonts.cyri.scale(14))
445        .font_id(self.fonts.cyri.conrod_id)
446        .color(TEXT_COLOR)
447        .set(state.ids.entity_vd_value, ui);
448
449        // Sprites VD
450        if let Some(new_val) = ImageSlider::discrete(
451            self.global_state.settings.graphics.sprite_render_distance,
452            50,
453            500,
454            self.imgs.slider_indicator,
455            self.imgs.slider,
456        )
457        .w_h(104.0, 22.0)
458        .right_from(state.ids.entity_vd_slider, 70.0)
459        .track_breadth(12.0)
460        .slider_length(10.0)
461        .pad_track((5.0, 5.0))
462        .set(state.ids.sprite_dist_slider, ui)
463        {
464            events.push(GraphicsChange::AdjustSpriteRenderDistance(new_val));
465        }
466        Text::new(
467            &self
468                .localized_strings
469                .get_msg("hud-settings-sprites_view_distance"),
470        )
471        .up_from(state.ids.sprite_dist_slider, 10.0)
472        .font_size(self.fonts.cyri.scale(14))
473        .font_id(self.fonts.cyri.conrod_id)
474        .color(TEXT_COLOR)
475        .set(state.ids.sprite_dist_text, ui);
476
477        Text::new(&format!(
478            "{}",
479            self.global_state.settings.graphics.sprite_render_distance
480        ))
481        .right_from(state.ids.sprite_dist_slider, 8.0)
482        .font_size(self.fonts.cyri.scale(14))
483        .font_id(self.fonts.cyri.conrod_id)
484        .color(TEXT_COLOR)
485        .set(state.ids.sprite_dist_value, ui);
486
487        // LoD Distance
488        Text::new(&self.localized_strings.get_msg("hud-settings-lod_distance"))
489            .down_from(state.ids.terrain_vd_slider, 10.0)
490            .font_size(self.fonts.cyri.scale(14))
491            .font_id(self.fonts.cyri.conrod_id)
492            .color(TEXT_COLOR)
493            .set(state.ids.ld_text, ui);
494
495        if let Some(new_val) = ImageSlider::discrete(
496            self.global_state.settings.graphics.lod_distance,
497            0,
498            500,
499            self.imgs.slider_indicator,
500            self.imgs.slider,
501        )
502        .w_h(104.0, 22.0)
503        .down_from(state.ids.ld_text, 8.0)
504        .track_breadth(12.0)
505        .slider_length(10.0)
506        .pad_track((5.0, 5.0))
507        .set(state.ids.ld_slider, ui)
508        {
509            events.push(GraphicsChange::AdjustLodDistance(new_val));
510        }
511
512        Text::new(&format!(
513            "{}",
514            self.global_state.settings.graphics.lod_distance
515        ))
516        .right_from(state.ids.ld_slider, 8.0)
517        .font_size(self.fonts.cyri.scale(14))
518        .font_id(self.fonts.cyri.conrod_id)
519        .color(TEXT_COLOR)
520        .set(state.ids.ld_value, ui);
521
522        // Figure LOD distance
523        if let Some(new_val) = ImageSlider::discrete(
524            self.global_state
525                .settings
526                .graphics
527                .figure_lod_render_distance,
528            50,
529            500,
530            self.imgs.slider_indicator,
531            self.imgs.slider,
532        )
533        .w_h(104.0, 22.0)
534        .right_from(state.ids.ld_slider, 70.0)
535        .track_breadth(12.0)
536        .slider_length(10.0)
537        .pad_track((5.0, 5.0))
538        .set(state.ids.figure_dist_slider, ui)
539        {
540            events.push(GraphicsChange::AdjustFigureLoDRenderDistance(new_val));
541        }
542        Text::new(
543            &self
544                .localized_strings
545                .get_msg("hud-settings-entities_detail_distance"),
546        )
547        .up_from(state.ids.figure_dist_slider, 8.0)
548        .font_size(self.fonts.cyri.scale(14))
549        .font_id(self.fonts.cyri.conrod_id)
550        .color(TEXT_COLOR)
551        .set(state.ids.figure_dist_text, ui);
552
553        Text::new(&format!(
554            "{}",
555            self.global_state
556                .settings
557                .graphics
558                .figure_lod_render_distance
559        ))
560        .right_from(state.ids.figure_dist_slider, 8.0)
561        .font_size(self.fonts.cyri.scale(14))
562        .font_id(self.fonts.cyri.conrod_id)
563        .color(TEXT_COLOR)
564        .set(state.ids.figure_dist_value, ui);
565
566        // Max FPS
567        Text::new(&self.localized_strings.get_msg("hud-settings-maximum_fps"))
568            .down_from(state.ids.ld_slider, 10.0)
569            .font_size(self.fonts.cyri.scale(14))
570            .font_id(self.fonts.cyri.conrod_id)
571            .color(TEXT_COLOR)
572            .set(state.ids.max_fps_text, ui);
573
574        if let Some(which) = ImageSlider::discrete(
575            FPS_CHOICES
576                .iter()
577                .position(|&x| x == self.global_state.settings.graphics.max_fps)
578                .unwrap_or(5),
579            0,
580            FPS_CHOICES.len() - 1,
581            self.imgs.slider_indicator,
582            self.imgs.slider,
583        )
584        .w_h(104.0, 22.0)
585        .down_from(state.ids.max_fps_text, 8.0)
586        .track_breadth(12.0)
587        .slider_length(10.0)
588        .pad_track((5.0, 5.0))
589        .set(state.ids.max_fps_slider, ui)
590        {
591            events.push(GraphicsChange::ChangeMaxFPS(FPS_CHOICES[which]));
592        }
593
594        Text::new(&self.global_state.settings.graphics.max_fps.to_string())
595            .right_from(state.ids.max_fps_slider, 8.0)
596            .font_size(self.fonts.cyri.scale(14))
597            .font_id(self.fonts.cyri.conrod_id)
598            .color(TEXT_COLOR)
599            .set(state.ids.max_fps_value, ui);
600
601        // Max Background FPS
602        Text::new(
603            &self
604                .localized_strings
605                .get_msg("hud-settings-background_fps"),
606        )
607        .down_from(state.ids.ld_slider, 10.0)
608        .right_from(state.ids.max_fps_value, 44.0)
609        .font_size(self.fonts.cyri.scale(14))
610        .font_id(self.fonts.cyri.conrod_id)
611        .color(TEXT_COLOR)
612        .set(state.ids.max_background_fps_text, ui);
613
614        if let Some(which) = ImageSlider::discrete(
615            BG_FPS_CHOICES
616                .iter()
617                .position(|&x| x == self.global_state.settings.graphics.max_background_fps)
618                .unwrap_or(5),
619            0,
620            BG_FPS_CHOICES.len() - 1,
621            self.imgs.slider_indicator,
622            self.imgs.slider,
623        )
624        .w_h(104.0, 22.0)
625        .down_from(state.ids.max_background_fps_text, 8.0)
626        .track_breadth(12.0)
627        .slider_length(10.0)
628        .pad_track((5.0, 5.0))
629        .set(state.ids.max_background_fps_slider, ui)
630        {
631            events.push(GraphicsChange::ChangeMaxBackgroundFPS(
632                BG_FPS_CHOICES[which],
633            ));
634        }
635
636        Text::new(
637            &self
638                .global_state
639                .settings
640                .graphics
641                .max_background_fps
642                .to_string(),
643        )
644        .right_from(state.ids.max_background_fps_slider, 8.0)
645        .font_size(self.fonts.cyri.scale(14))
646        .font_id(self.fonts.cyri.conrod_id)
647        .color(TEXT_COLOR)
648        .set(state.ids.max_background_fps_value, ui);
649
650        // Get render mode
651        let render_mode = &self.global_state.settings.graphics.render_mode;
652
653        // Present Mode
654        Text::new(&self.localized_strings.get_msg("hud-settings-present_mode"))
655            .down_from(state.ids.ld_slider, 10.0)
656            .right_from(state.ids.max_background_fps_value, 40.0)
657            .font_size(self.fonts.cyri.scale(14))
658            .font_id(self.fonts.cyri.conrod_id)
659            .color(TEXT_COLOR)
660            .set(state.ids.present_mode_text, ui);
661
662        let mode_list = self.global_state.window.renderer().present_modes();
663        let mode_label_list = mode_list
664            .iter()
665            .map(|mode| {
666                self.localized_strings.get_msg(match mode {
667                    PresentMode::Fifo => "hud-settings-present_mode-vsync_capped",
668                    PresentMode::FifoRelaxed => "hud-settings-present_mode-vsync_adaptive",
669                    PresentMode::Mailbox => "hud-settings-present_mode-vsync_uncapped",
670                    PresentMode::Immediate => "hud-settings-present_mode-vsync_off",
671                })
672            })
673            .collect::<Vec<_>>();
674
675        // Get which present mode is currently active
676        let selected = mode_list
677            .iter()
678            .position(|x| *x == render_mode.present_mode);
679
680        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
681            .w_h(150.0, 26.0)
682            .color(MENU_BG)
683            .label_color(TEXT_COLOR)
684            .label_font_id(self.fonts.cyri.conrod_id)
685            .down_from(state.ids.present_mode_text, 8.0)
686            .align_middle_x()
687            .set(state.ids.present_mode_list, ui)
688        {
689            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
690                present_mode: mode_list[clicked],
691                ..render_mode.clone()
692            })));
693        }
694
695        // FOV
696        Text::new(&self.localized_strings.get_msg("hud-settings-fov"))
697            .down_from(state.ids.max_fps_slider, 10.0)
698            .font_size(self.fonts.cyri.scale(14))
699            .font_id(self.fonts.cyri.conrod_id)
700            .color(TEXT_COLOR)
701            .set(state.ids.fov_text, ui);
702
703        if let Some(new_val) = ImageSlider::discrete(
704            self.global_state.settings.graphics.fov,
705            30,
706            120,
707            self.imgs.slider_indicator,
708            self.imgs.slider,
709        )
710        .w_h(104.0, 22.0)
711        .down_from(state.ids.fov_text, 8.0)
712        .track_breadth(12.0)
713        .slider_length(10.0)
714        .pad_track((5.0, 5.0))
715        .set(state.ids.fov_slider, ui)
716        {
717            events.push(GraphicsChange::ChangeFOV(new_val));
718        }
719
720        Text::new(&format!("{}", self.global_state.settings.graphics.fov))
721            .right_from(state.ids.fov_slider, 8.0)
722            .font_size(self.fonts.cyri.scale(14))
723            .font_id(self.fonts.cyri.conrod_id)
724            .color(TEXT_COLOR)
725            .set(state.ids.fov_value, ui);
726
727        // LoD detail
728        Text::new(&self.localized_strings.get_msg("hud-settings-lod_detail"))
729            .down_from(state.ids.fov_slider, 10.0)
730            .font_size(self.fonts.cyri.scale(14))
731            .font_id(self.fonts.cyri.conrod_id)
732            .color(TEXT_COLOR)
733            .set(state.ids.lod_detail_text, ui);
734
735        if let Some(new_val) = ImageSlider::discrete(
736            ((self.global_state.settings.graphics.lod_detail as f32 / 100.0).log(5.0) * 10.0)
737                .round() as i32,
738            0,
739            20,
740            self.imgs.slider_indicator,
741            self.imgs.slider,
742        )
743        .w_h(104.0, 22.0)
744        .down_from(state.ids.lod_detail_text, 8.0)
745        .track_breadth(12.0)
746        .slider_length(10.0)
747        .pad_track((5.0, 5.0))
748        .set(state.ids.lod_detail_slider, ui)
749        {
750            events.push(GraphicsChange::AdjustLodDetail(
751                (5.0f32.powf(new_val as f32 / 20.0) * 100.0) as u32,
752            ));
753        }
754
755        Text::new(&format!(
756            "{}",
757            self.global_state.settings.graphics.lod_detail
758        ))
759        .right_from(state.ids.lod_detail_slider, 8.0)
760        .font_size(self.fonts.cyri.scale(14))
761        .font_id(self.fonts.cyri.conrod_id)
762        .color(TEXT_COLOR)
763        .set(state.ids.lod_detail_value, ui);
764
765        // Gamma
766        Text::new(&self.localized_strings.get_msg("hud-settings-gamma"))
767            .down_from(state.ids.lod_detail_slider, 10.0)
768            .font_size(self.fonts.cyri.scale(14))
769            .font_id(self.fonts.cyri.conrod_id)
770            .color(TEXT_COLOR)
771            .set(state.ids.gamma_text, ui);
772
773        if let Some(new_val) = ImageSlider::discrete(
774            (self.global_state.settings.graphics.gamma.log2() * 8.0).round() as i32,
775            8,
776            -8,
777            self.imgs.slider_indicator,
778            self.imgs.slider,
779        )
780        .w_h(104.0, 22.0)
781        .down_from(state.ids.gamma_text, 8.0)
782        .track_breadth(12.0)
783        .slider_length(10.0)
784        .pad_track((5.0, 5.0))
785        .set(state.ids.gamma_slider, ui)
786        {
787            events.push(GraphicsChange::ChangeGamma(
788                2.0f32.powf(new_val as f32 / 8.0),
789            ));
790        }
791
792        Text::new(&format!("{:.2}", self.global_state.settings.graphics.gamma))
793            .right_from(state.ids.gamma_slider, 8.0)
794            .font_size(self.fonts.cyri.scale(14))
795            .font_id(self.fonts.cyri.conrod_id)
796            .color(TEXT_COLOR)
797            .set(state.ids.gamma_value, ui);
798
799        // Exposure
800        if let Some(new_val) = ImageSlider::discrete(
801            (self.global_state.settings.graphics.exposure * 16.0) as i32,
802            0,
803            32,
804            self.imgs.slider_indicator,
805            self.imgs.slider,
806        )
807        .w_h(104.0, 22.0)
808        .right_from(state.ids.gamma_slider, 50.0)
809        .track_breadth(12.0)
810        .slider_length(10.0)
811        .pad_track((5.0, 5.0))
812        .set(state.ids.exposure_slider, ui)
813        {
814            events.push(GraphicsChange::ChangeExposure(new_val as f32 / 16.0));
815        }
816
817        Text::new(&self.localized_strings.get_msg("hud-settings-exposure"))
818            .up_from(state.ids.exposure_slider, 8.0)
819            .font_size(self.fonts.cyri.scale(14))
820            .font_id(self.fonts.cyri.conrod_id)
821            .color(TEXT_COLOR)
822            .set(state.ids.exposure_text, ui);
823
824        Text::new(&format!(
825            "{:.2}",
826            self.global_state.settings.graphics.exposure
827        ))
828        .right_from(state.ids.exposure_slider, 8.0)
829        .font_size(self.fonts.cyri.scale(14))
830        .font_id(self.fonts.cyri.conrod_id)
831        .color(TEXT_COLOR)
832        .set(state.ids.exposure_value, ui);
833
834        //Ambiance Brightness
835        if let Some(new_val) = ImageSlider::discrete(
836            (self.global_state.settings.graphics.ambiance * 100.0).round() as i32,
837            0,
838            100,
839            self.imgs.slider_indicator,
840            self.imgs.slider,
841        )
842        .w_h(104.0, 22.0)
843        .right_from(state.ids.exposure_slider, 50.0)
844        .track_breadth(12.0)
845        .slider_length(10.0)
846        .pad_track((5.0, 5.0))
847        .set(state.ids.ambiance_slider, ui)
848        {
849            events.push(GraphicsChange::ChangeAmbiance(new_val as f32 / 100.0));
850        }
851        Text::new(&self.localized_strings.get_msg("hud-settings-ambiance"))
852            .up_from(state.ids.ambiance_slider, 8.0)
853            .font_size(self.fonts.cyri.scale(14))
854            .font_id(self.fonts.cyri.conrod_id)
855            .color(TEXT_COLOR)
856            .set(state.ids.ambiance_text, ui);
857        Text::new(&format!(
858            "{:.0}%",
859            (self.global_state.settings.graphics.ambiance * 100.0).round()
860        ))
861        .right_from(state.ids.ambiance_slider, 8.0)
862        .font_size(self.fonts.cyri.scale(14))
863        .font_id(self.fonts.cyri.conrod_id)
864        .color(TEXT_COLOR)
865        .set(state.ids.ambiance_value, ui);
866
867        // AaMode
868        Text::new(
869            &self
870                .localized_strings
871                .get_msg("hud-settings-antialiasing_mode"),
872        )
873        .down_from(state.ids.gamma_slider, 8.0)
874        .font_size(self.fonts.cyri.scale(14))
875        .font_id(self.fonts.cyri.conrod_id)
876        .color(TEXT_COLOR)
877        .set(state.ids.aa_mode_text, ui);
878
879        // NOTE: MSAA modes are currently disabled from the UI due to poor
880        // interaction with greedy meshing, and may eventually be removed.
881        let mode_list = [
882            AaMode::None,
883            AaMode::Bilinear,
884            AaMode::Fxaa,
885            /* AaMode::MsaaX4,
886            AaMode::MsaaX8,
887            AaMode::MsaaX16, */
888            AaMode::FxUpscale,
889            AaMode::Hqx,
890        ];
891        let mode_label_list = [
892            "None",
893            "Bilinear",
894            "FXAA",
895            /* "MSAA x4",
896            "MSAA x8",
897            "MSAA x16 (experimental)", */
898            "FXUpscale",
899            "HQX",
900        ];
901
902        // Get which AA mode is currently active
903        let selected = mode_list.iter().position(|x| *x == render_mode.aa);
904
905        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
906            .w_h(400.0, 22.0)
907            .color(MENU_BG)
908            .label_color(TEXT_COLOR)
909            .label_font_id(self.fonts.cyri.conrod_id)
910            .down_from(state.ids.aa_mode_text, 8.0)
911            .set(state.ids.aa_mode_list, ui)
912        {
913            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
914                aa: mode_list[clicked],
915                ..render_mode.clone()
916            })));
917        }
918
919        // Bloom
920        let bloom_intensity = match render_mode.bloom {
921            BloomMode::Off => 0.0,
922            BloomMode::On(bloom) => bloom.factor.fraction(),
923        };
924        let max_bloom = 0.3;
925
926        Text::new(&self.localized_strings.get_msg("hud-settings-bloom"))
927            .font_size(self.fonts.cyri.scale(14))
928            .font_id(self.fonts.cyri.conrod_id)
929            .down_from(state.ids.aa_mode_list, 10.0)
930            .color(TEXT_COLOR)
931            .set(state.ids.bloom_intensity_text, ui);
932        if let Some(new_val) = ImageSlider::continuous(
933            bloom_intensity,
934            0.0,
935            max_bloom,
936            self.imgs.slider_indicator,
937            self.imgs.slider,
938        )
939        .w_h(104.0, 22.0)
940        .down_from(state.ids.bloom_intensity_text, 8.0)
941        .track_breadth(12.0)
942        .slider_length(10.0)
943        .pad_track((5.0, 5.0))
944        .set(state.ids.bloom_intensity_slider, ui)
945        {
946            if new_val > f32::EPSILON {
947                // Toggle Bloom On and set Custom value to new_val
948                events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
949                    bloom: {
950                        BloomMode::On(BloomConfig {
951                            factor: BloomFactor::Custom(new_val),
952                            // TODO: Decide if this should be a separate setting
953                            uniform_blur: false,
954                        })
955                    },
956                    ..render_mode.clone()
957                })))
958            } else {
959                // Toggle Bloom Off if the value is near 0
960                events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
961                    bloom: { BloomMode::Off },
962                    ..render_mode.clone()
963                })))
964            }
965        }
966        Text::new(&if bloom_intensity <= f32::EPSILON {
967            "Off".to_string()
968        } else {
969            format!("{}%", (bloom_intensity * 100.0 / max_bloom) as i32)
970        })
971        .right_from(state.ids.bloom_intensity_slider, 8.0)
972        .font_size(self.fonts.cyri.scale(14))
973        .font_id(self.fonts.cyri.conrod_id)
974        .color(TEXT_COLOR)
975        .set(state.ids.bloom_intensity_value, ui);
976
977        // Point Glow
978        Text::new(&self.localized_strings.get_msg("hud-settings-point_glow"))
979            .font_size(self.fonts.cyri.scale(14))
980            .font_id(self.fonts.cyri.conrod_id)
981            .down_from(state.ids.aa_mode_list, 10.0)
982            .right_from(state.ids.bloom_intensity_value, 10.0)
983            .color(TEXT_COLOR)
984            .set(state.ids.point_glow_text, ui);
985        if let Some(new_val) = ImageSlider::continuous(
986            render_mode.point_glow,
987            0.0,
988            1.0,
989            self.imgs.slider_indicator,
990            self.imgs.slider,
991        )
992        .w_h(104.0, 22.0)
993        .down_from(state.ids.point_glow_text, 8.0)
994        .track_breadth(12.0)
995        .slider_length(10.0)
996        .pad_track((5.0, 5.0))
997        .set(state.ids.point_glow_slider, ui)
998        {
999            // Toggle Bloom On and set Custom value to new_val
1000            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1001                point_glow: new_val,
1002                ..render_mode.clone()
1003            })));
1004        }
1005        Text::new(&if render_mode.point_glow <= f32::EPSILON {
1006            "Off".to_string()
1007        } else {
1008            format!("{}%", (render_mode.point_glow * 100.0) as i32)
1009        })
1010        .right_from(state.ids.point_glow_slider, 8.0)
1011        .font_size(self.fonts.cyri.scale(14))
1012        .font_id(self.fonts.cyri.conrod_id)
1013        .color(TEXT_COLOR)
1014        .set(state.ids.point_glow_value, ui);
1015
1016        // Upscaling factor
1017        Text::new(
1018            &self
1019                .localized_strings
1020                .get_msg("hud-settings-upscale_factor"),
1021        )
1022        .down_from(state.ids.bloom_intensity_slider, 8.0)
1023        .font_size(self.fonts.cyri.scale(14))
1024        .font_id(self.fonts.cyri.conrod_id)
1025        .color(TEXT_COLOR)
1026        .set(state.ids.upscale_factor_text, ui);
1027
1028        let upscale_factors = [
1029            // Upscaling
1030            0.1, 0.15, 0.2, 0.25, 0.35, 0.5, 0.65, 0.75, 0.85, 1.0,
1031            // Downscaling (equivalent to SSAA)
1032            1.25, 1.5, 1.75, 2.0,
1033        ];
1034
1035        // Get which upscale factor is currently active
1036        let selected = upscale_factors
1037            .iter()
1038            .position(|factor| (*factor - render_mode.upscale_mode.factor).abs() < 0.001);
1039
1040        if let Some(clicked) = DropDownList::new(
1041            &upscale_factors
1042                .iter()
1043                .map(|factor| format!("{n:.*}", 3, n = factor))
1044                .collect::<Vec<String>>(),
1045            selected,
1046        )
1047        .w_h(400.0, 22.0)
1048        .color(MENU_BG)
1049        .label_color(TEXT_COLOR)
1050        .label_font_id(self.fonts.cyri.conrod_id)
1051        .down_from(state.ids.upscale_factor_text, 8.0)
1052        .set(state.ids.upscale_factor_list, ui)
1053        {
1054            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1055                upscale_mode: UpscaleMode {
1056                    factor: upscale_factors[clicked],
1057                },
1058                ..render_mode.clone()
1059            })));
1060        }
1061
1062        // CloudMode
1063        Text::new(
1064            &self
1065                .localized_strings
1066                .get_msg("hud-settings-cloud_rendering_mode"),
1067        )
1068        .down_from(state.ids.upscale_factor_list, 8.0)
1069        .font_size(self.fonts.cyri.scale(14))
1070        .font_id(self.fonts.cyri.conrod_id)
1071        .color(TEXT_COLOR)
1072        .set(state.ids.cloud_mode_text, ui);
1073
1074        let mode_list = [
1075            CloudMode::Flat,
1076            CloudMode::Minimal,
1077            CloudMode::Low,
1078            CloudMode::Medium,
1079            CloudMode::High,
1080            CloudMode::Ultra,
1081        ];
1082        let mode_label_list = [
1083            self.localized_strings
1084                .get_msg("hud-settings-cloud_rendering_mode-flat"),
1085            self.localized_strings
1086                .get_msg("hud-settings-cloud_rendering_mode-minimal"),
1087            self.localized_strings
1088                .get_msg("hud-settings-cloud_rendering_mode-low"),
1089            self.localized_strings
1090                .get_msg("hud-settings-cloud_rendering_mode-medium"),
1091            self.localized_strings
1092                .get_msg("hud-settings-cloud_rendering_mode-high"),
1093            self.localized_strings
1094                .get_msg("hud-settings-cloud_rendering_mode-ultra"),
1095        ];
1096
1097        // Get which cloud rendering mode is currently active
1098        let selected = mode_list.iter().position(|x| *x == render_mode.cloud);
1099
1100        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
1101            .w_h(400.0, 22.0)
1102            .color(MENU_BG)
1103            .label_color(TEXT_COLOR)
1104            .label_font_id(self.fonts.cyri.conrod_id)
1105            .down_from(state.ids.cloud_mode_text, 8.0)
1106            .set(state.ids.cloud_mode_list, ui)
1107        {
1108            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1109                cloud: mode_list[clicked],
1110                ..render_mode.clone()
1111            })));
1112        }
1113
1114        // FluidMode
1115        Text::new(
1116            &self
1117                .localized_strings
1118                .get_msg("hud-settings-fluid_rendering_mode"),
1119        )
1120        .down_from(state.ids.cloud_mode_list, 8.0)
1121        .font_size(self.fonts.cyri.scale(14))
1122        .font_id(self.fonts.cyri.conrod_id)
1123        .color(TEXT_COLOR)
1124        .set(state.ids.fluid_mode_text, ui);
1125
1126        let mode_list = [FluidMode::Low, FluidMode::Medium, FluidMode::High];
1127        let mode_label_list = [
1128            self.localized_strings
1129                .get_msg("hud-settings-fluid_rendering_mode-low"),
1130            self.localized_strings
1131                .get_msg("hud-settings-fluid_rendering_mode-medium"),
1132            self.localized_strings
1133                .get_msg("hud-settings-fluid_rendering_mode-high"),
1134        ];
1135
1136        // Get which fluid rendering mode is currently active
1137        let selected = mode_list.iter().position(|x| *x == render_mode.fluid);
1138
1139        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
1140            .w_h(400.0, 22.0)
1141            .color(MENU_BG)
1142            .label_color(TEXT_COLOR)
1143            .label_font_id(self.fonts.cyri.conrod_id)
1144            .down_from(state.ids.fluid_mode_text, 8.0)
1145            .set(state.ids.fluid_mode_list, ui)
1146        {
1147            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1148                fluid: mode_list[clicked],
1149                ..render_mode.clone()
1150            })));
1151        }
1152
1153        // ReflectionMode
1154        Text::new(
1155            &self
1156                .localized_strings
1157                .get_msg("hud-settings-reflection_rendering_mode"),
1158        )
1159        .down_from(state.ids.fluid_mode_list, 8.0)
1160        .font_size(self.fonts.cyri.scale(14))
1161        .font_id(self.fonts.cyri.conrod_id)
1162        .color(TEXT_COLOR)
1163        .set(state.ids.reflection_mode_text, ui);
1164
1165        let mode_list = [
1166            ReflectionMode::Low,
1167            ReflectionMode::Medium,
1168            ReflectionMode::High,
1169        ];
1170        let mode_label_list = [
1171            self.localized_strings
1172                .get_msg("hud-settings-reflection_rendering_mode-low"),
1173            self.localized_strings
1174                .get_msg("hud-settings-reflection_rendering_mode-medium"),
1175            self.localized_strings
1176                .get_msg("hud-settings-reflection_rendering_mode-high"),
1177        ];
1178
1179        // Get which fluid rendering mode is currently active
1180        let selected = mode_list.iter().position(|x| *x == render_mode.reflection);
1181
1182        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
1183            .w_h(400.0, 22.0)
1184            .color(MENU_BG)
1185            .label_color(TEXT_COLOR)
1186            .label_font_id(self.fonts.cyri.conrod_id)
1187            .down_from(state.ids.reflection_mode_text, 8.0)
1188            .set(state.ids.reflection_mode_list, ui)
1189        {
1190            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1191                reflection: mode_list[clicked],
1192                ..render_mode.clone()
1193            })));
1194        }
1195
1196        // LightingMode
1197        Text::new(
1198            &self
1199                .localized_strings
1200                .get_msg("hud-settings-lighting_rendering_mode"),
1201        )
1202        .down_from(state.ids.reflection_mode_list, 8.0)
1203        .font_size(self.fonts.cyri.scale(14))
1204        .font_id(self.fonts.cyri.conrod_id)
1205        .color(TEXT_COLOR)
1206        .set(state.ids.lighting_mode_text, ui);
1207
1208        let mode_list = [
1209            LightingMode::Ashikhmin,
1210            LightingMode::BlinnPhong,
1211            LightingMode::Lambertian,
1212        ];
1213        let mode_label_list = [
1214            self.localized_strings
1215                .get_msg("hud-settings-lighting_rendering_mode-ashikhmin"),
1216            self.localized_strings
1217                .get_msg("hud-settings-lighting_rendering_mode-blinnphong"),
1218            self.localized_strings
1219                .get_msg("hud-settings-lighting_rendering_mode-lambertian"),
1220        ];
1221
1222        // Get which lighting rendering mode is currently active
1223        let selected = mode_list.iter().position(|x| *x == render_mode.lighting);
1224
1225        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
1226            .w_h(400.0, 22.0)
1227            .color(MENU_BG)
1228            .label_color(TEXT_COLOR)
1229            .label_font_id(self.fonts.cyri.conrod_id)
1230            .down_from(state.ids.lighting_mode_text, 8.0)
1231            .set(state.ids.lighting_mode_list, ui)
1232        {
1233            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1234                lighting: mode_list[clicked],
1235                ..render_mode.clone()
1236            })));
1237        }
1238
1239        // ShadowMode
1240        Text::new(
1241            &self
1242                .localized_strings
1243                .get_msg("hud-settings-shadow_rendering_mode"),
1244        )
1245        .down_from(state.ids.lighting_mode_list, 8.0)
1246        .font_size(self.fonts.cyri.scale(14))
1247        .font_id(self.fonts.cyri.conrod_id)
1248        .color(TEXT_COLOR)
1249        .set(state.ids.shadow_mode_text, ui);
1250
1251        let shadow_map_mode = ShadowMapMode::try_from(render_mode.shadow).ok();
1252        let mode_list = [
1253            ShadowMode::None,
1254            ShadowMode::Cheap,
1255            ShadowMode::Map(shadow_map_mode.unwrap_or_default()),
1256        ];
1257        let mode_label_list = [
1258            self.localized_strings
1259                .get_msg("hud-settings-shadow_rendering_mode-none"),
1260            self.localized_strings
1261                .get_msg("hud-settings-shadow_rendering_mode-cheap"),
1262            self.localized_strings
1263                .get_msg("hud-settings-shadow_rendering_mode-map"),
1264        ];
1265
1266        // Get which shadow rendering mode is currently active
1267        let selected = mode_list.iter().position(|x| *x == render_mode.shadow);
1268
1269        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
1270            .w_h(400.0, 22.0)
1271            .color(MENU_BG)
1272            .label_color(TEXT_COLOR)
1273            .label_font_id(self.fonts.cyri.conrod_id)
1274            .down_from(state.ids.shadow_mode_text, 8.0)
1275            .set(state.ids.shadow_mode_list, ui)
1276        {
1277            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1278                shadow: mode_list[clicked],
1279                ..render_mode.clone()
1280            })));
1281        }
1282
1283        if let Some(shadow_map_mode) = shadow_map_mode {
1284            // Display the shadow map mode if selected.
1285            Text::new(
1286                &self
1287                    .localized_strings
1288                    .get_msg("hud-settings-shadow_rendering_mode-map-resolution"),
1289            )
1290            .right_from(state.ids.shadow_mode_list, 10.0)
1291            .font_size(self.fonts.cyri.scale(14))
1292            .font_id(self.fonts.cyri.conrod_id)
1293            .color(TEXT_COLOR)
1294            .set(state.ids.shadow_mode_map_resolution_text, ui);
1295
1296            if let Some(new_val) = ImageSlider::discrete(
1297                (shadow_map_mode.resolution.log2() * 4.0).round() as i8,
1298                -8,
1299                8,
1300                self.imgs.slider_indicator,
1301                self.imgs.slider,
1302            )
1303            .w_h(104.0, 22.0)
1304            .right_from(state.ids.shadow_mode_map_resolution_text, 8.0)
1305            .track_breadth(12.0)
1306            .slider_length(10.0)
1307            .pad_track((5.0, 5.0))
1308            .set(state.ids.shadow_mode_map_resolution_slider, ui)
1309            {
1310                events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1311                    shadow: ShadowMode::Map(ShadowMapMode {
1312                        resolution: 2.0f32.powf(f32::from(new_val) / 4.0),
1313                    }),
1314                    ..render_mode.clone()
1315                })));
1316            }
1317
1318            // TODO: Consider fixing to avoid allocation (it's probably not a bottleneck but
1319            // there's no reason to allocate for numbers).
1320            Text::new(&format!("{}", shadow_map_mode.resolution))
1321                .right_from(state.ids.shadow_mode_map_resolution_slider, 8.0)
1322                .font_size(self.fonts.cyri.scale(14))
1323                .font_id(self.fonts.cyri.conrod_id)
1324                .color(TEXT_COLOR)
1325                .set(state.ids.shadow_mode_map_resolution_value, ui);
1326        }
1327
1328        // Disable rain
1329        Text::new(&self.localized_strings.get_msg("hud-settings-rain"))
1330            .font_size(self.fonts.cyri.scale(14))
1331            .font_id(self.fonts.cyri.conrod_id)
1332            .down_from(state.ids.shadow_mode_list, 10.0)
1333            .color(TEXT_COLOR)
1334            .set(state.ids.rain_label, ui);
1335
1336        let rain_enabled = ToggleButton::new(
1337            self.global_state.settings.graphics.render_mode.rain_enabled,
1338            self.imgs.checkbox,
1339            self.imgs.checkbox_checked,
1340        )
1341        .w_h(18.0, 18.0)
1342        .right_from(state.ids.rain_label, 10.0)
1343        .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
1344        .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
1345        .set(state.ids.rain_button, ui);
1346
1347        if self.global_state.settings.graphics.render_mode.rain_enabled != rain_enabled {
1348            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1349                rain_enabled,
1350                ..render_mode.clone()
1351            })));
1352        }
1353
1354        // Rain occlusion texture size
1355        // Display the shadow map mode if selected.
1356        Text::new(
1357            &self
1358                .localized_strings
1359                .get_msg("hud-settings-rain_occlusion-resolution"),
1360        )
1361        .right_from(state.ids.rain_button, 64.0)
1362        .font_size(self.fonts.cyri.scale(14))
1363        .font_id(self.fonts.cyri.conrod_id)
1364        .color(TEXT_COLOR)
1365        .set(state.ids.rain_map_resolution_text, ui);
1366
1367        if let Some(new_val) = ImageSlider::discrete(
1368            (render_mode.rain_occlusion.resolution.log2() * 4.0).round() as i8,
1369            -8,
1370            8,
1371            self.imgs.slider_indicator,
1372            self.imgs.slider,
1373        )
1374        .w_h(104.0, 22.0)
1375        .right_from(state.ids.rain_map_resolution_text, 8.0)
1376        .track_breadth(12.0)
1377        .slider_length(10.0)
1378        .pad_track((5.0, 5.0))
1379        .set(state.ids.rain_map_resolution_slider, ui)
1380        {
1381            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1382                rain_occlusion: ShadowMapMode {
1383                    resolution: 2.0f32.powf(f32::from(new_val) / 4.0),
1384                },
1385                ..render_mode.clone()
1386            })));
1387        }
1388        Text::new(&format!("{}", render_mode.rain_occlusion.resolution))
1389            .right_from(state.ids.rain_map_resolution_slider, 8.0)
1390            .font_size(self.fonts.cyri.scale(14))
1391            .font_id(self.fonts.cyri.conrod_id)
1392            .color(TEXT_COLOR)
1393            .set(state.ids.rain_map_resolution_value, ui);
1394
1395        // GPU Profiler
1396        Text::new(&self.localized_strings.get_msg("hud-settings-gpu_profiler"))
1397            .font_size(self.fonts.cyri.scale(14))
1398            .font_id(self.fonts.cyri.conrod_id)
1399            .down_from(state.ids.rain_label, 8.0)
1400            .color(TEXT_COLOR)
1401            .set(state.ids.gpu_profiler_label, ui);
1402
1403        let gpu_profiler_enabled = ToggleButton::new(
1404            render_mode.profiler_enabled,
1405            self.imgs.checkbox,
1406            self.imgs.checkbox_checked,
1407        )
1408        .w_h(18.0, 18.0)
1409        .right_from(state.ids.gpu_profiler_label, 10.0)
1410        .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
1411        .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
1412        .set(state.ids.gpu_profiler_button, ui);
1413
1414        if render_mode.profiler_enabled != gpu_profiler_enabled {
1415            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1416                profiler_enabled: gpu_profiler_enabled,
1417                ..render_mode.clone()
1418            })));
1419        }
1420
1421        // Particles
1422        Text::new(&self.localized_strings.get_msg("hud-settings-particles"))
1423            .font_size(self.fonts.cyri.scale(14))
1424            .font_id(self.fonts.cyri.conrod_id)
1425            .down_from(state.ids.gpu_profiler_label, 8.0)
1426            .color(TEXT_COLOR)
1427            .set(state.ids.particles_label, ui);
1428
1429        let particles_enabled = ToggleButton::new(
1430            self.global_state.settings.graphics.particles_enabled,
1431            self.imgs.checkbox,
1432            self.imgs.checkbox_checked,
1433        )
1434        .w_h(18.0, 18.0)
1435        .right_from(state.ids.particles_label, 10.0)
1436        .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
1437        .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
1438        .set(state.ids.particles_button, ui);
1439
1440        if self.global_state.settings.graphics.particles_enabled != particles_enabled {
1441            events.push(GraphicsChange::ToggleParticlesEnabled(particles_enabled));
1442        }
1443
1444        // Weapon trails
1445        Text::new(&self.localized_strings.get_msg("hud-settings-weapon_trails"))
1446            .font_size(self.fonts.cyri.scale(14))
1447            .font_id(self.fonts.cyri.conrod_id)
1448            .right_from(state.ids.particles_label, 64.0)
1449            .color(TEXT_COLOR)
1450            .set(state.ids.weapon_trails_label, ui);
1451
1452        let weapon_trails_enabled = ToggleButton::new(
1453            self.global_state.settings.graphics.weapon_trails_enabled,
1454            self.imgs.checkbox,
1455            self.imgs.checkbox_checked,
1456        )
1457        .w_h(18.0, 18.0)
1458        .right_from(state.ids.weapon_trails_label, 10.0)
1459        .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
1460        .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
1461        .set(state.ids.weapon_trails_button, ui);
1462
1463        if self.global_state.settings.graphics.weapon_trails_enabled != weapon_trails_enabled {
1464            events.push(GraphicsChange::ToggleWeaponTrailsEnabled(
1465                weapon_trails_enabled,
1466            ));
1467        }
1468
1469        // Disable flashing lights
1470        Text::new(
1471            &self
1472                .localized_strings
1473                .get_msg("hud-settings-flashing_lights"),
1474        )
1475        .font_size(self.fonts.cyri.scale(14))
1476        .font_id(self.fonts.cyri.conrod_id)
1477        .down_from(state.ids.particles_label, 25.0)
1478        .color(TEXT_COLOR)
1479        .set(state.ids.flashing_lights_label, ui);
1480
1481        let flashing_lights_enabled = ToggleButton::new(
1482            self.global_state
1483                .settings
1484                .graphics
1485                .render_mode
1486                .flashing_lights_enabled,
1487            self.imgs.checkbox,
1488            self.imgs.checkbox_checked,
1489        )
1490        .w_h(18.0, 18.0)
1491        .right_from(state.ids.flashing_lights_label, 10.0)
1492        .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
1493        .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
1494        .set(state.ids.flashing_lights_button, ui);
1495
1496        Text::new(
1497            &self
1498                .localized_strings
1499                .get_msg("hud-settings-flashing_lights_info"),
1500        )
1501        .font_size(self.fonts.cyri.scale(14))
1502        .font_id(self.fonts.cyri.conrod_id)
1503        .right_from(state.ids.flashing_lights_label, 32.0)
1504        .color(TEXT_COLOR)
1505        .set(state.ids.flashing_lights_info_label, ui);
1506
1507        if self
1508            .global_state
1509            .settings
1510            .graphics
1511            .render_mode
1512            .flashing_lights_enabled
1513            != flashing_lights_enabled
1514        {
1515            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
1516                flashing_lights_enabled,
1517                ..render_mode.clone()
1518            })));
1519        }
1520
1521        // Resolution
1522        let resolutions: Vec<[u16; 2]> = state
1523            .video_modes
1524            .iter()
1525            .sorted_by_key(|mode| mode.size().height)
1526            .sorted_by_key(|mode| mode.size().width)
1527            .map(|mode| [mode.size().width as u16, mode.size().height as u16])
1528            .dedup()
1529            .collect();
1530
1531        Text::new(&self.localized_strings.get_msg("hud-settings-resolution"))
1532            .font_size(self.fonts.cyri.scale(14))
1533            .font_id(self.fonts.cyri.conrod_id)
1534            .down_from(state.ids.flashing_lights_label, 25.0)
1535            .color(TEXT_COLOR)
1536            .set(state.ids.resolution_label, ui);
1537
1538        if let Some(clicked) = DropDownList::new(
1539            resolutions
1540                .iter()
1541                .map(|res| format!("{}x{}", res[0], res[1]))
1542                .collect::<Vec<String>>()
1543                .as_slice(),
1544            resolutions
1545                .iter()
1546                .position(|res| res == &self.global_state.settings.graphics.fullscreen.resolution),
1547        )
1548        .w_h(128.0, 22.0)
1549        .color(MENU_BG)
1550        .label_color(TEXT_COLOR)
1551        .label_font_id(self.fonts.universal.conrod_id)
1552        .down_from(state.ids.resolution_label, 10.0)
1553        .set(state.ids.resolution, ui)
1554        {
1555            events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings {
1556                resolution: resolutions[clicked],
1557                ..self.global_state.settings.graphics.fullscreen
1558            }));
1559        }
1560
1561        // Bit Depth and Refresh Rate
1562        let correct_res: Vec<&VideoModeHandle> = state
1563            .video_modes
1564            .iter()
1565            .filter(|mode| {
1566                mode.size().width
1567                    == self.global_state.settings.graphics.fullscreen.resolution[0] as u32
1568            })
1569            .filter(|mode| {
1570                mode.size().height
1571                    == self.global_state.settings.graphics.fullscreen.resolution[1] as u32
1572            })
1573            .collect();
1574
1575        // Bit Depth
1576        let bit_depths: Vec<u16> = correct_res
1577            .iter()
1578            .filter(
1579                |mode| match self.global_state.settings.graphics.fullscreen.refresh_rate_millihertz {
1580                    Some(refresh_rate) => mode.refresh_rate_millihertz() == refresh_rate,
1581                    None => true,
1582                },
1583            )
1584            // TODO: why do we sort by this and then map to it?
1585            .sorted_by_key(|mode| mode.bit_depth())
1586            .map(|mode| mode.bit_depth())
1587            .rev()
1588            .dedup()
1589            .collect();
1590
1591        Text::new(&self.localized_strings.get_msg("hud-settings-bit_depth"))
1592            .font_size(self.fonts.cyri.scale(14))
1593            .font_id(self.fonts.cyri.conrod_id)
1594            .down_from(state.ids.flashing_lights_label, 25.0)
1595            .right_from(state.ids.resolution, 8.0)
1596            .color(TEXT_COLOR)
1597            .set(state.ids.bit_depth_label, ui);
1598
1599        if let Some(clicked) = DropDownList::new(
1600            once(String::from(
1601                self.localized_strings.get_msg("common-automatic"),
1602            ))
1603            .chain(bit_depths.iter().map(|depth| format!("{}", depth)))
1604            .collect::<Vec<String>>()
1605            .as_slice(),
1606            match self.global_state.settings.graphics.fullscreen.bit_depth {
1607                Some(bit_depth) => bit_depths
1608                    .iter()
1609                    .position(|depth| depth == &bit_depth)
1610                    .map(|index| index + 1),
1611                None => Some(0),
1612            },
1613        )
1614        .w_h(128.0, 22.0)
1615        .color(MENU_BG)
1616        .label_color(TEXT_COLOR)
1617        .label_font_id(self.fonts.universal.conrod_id)
1618        .down_from(state.ids.bit_depth_label, 10.0)
1619        .right_from(state.ids.resolution, 8.0)
1620        .set(state.ids.bit_depth, ui)
1621        {
1622            events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings {
1623                bit_depth: if clicked == 0 {
1624                    None
1625                } else {
1626                    Some(bit_depths[clicked - 1])
1627                },
1628                ..self.global_state.settings.graphics.fullscreen
1629            }));
1630        }
1631
1632        // Refresh Rate
1633        let refresh_rates: Vec<u32> = correct_res
1634            .into_iter()
1635            .filter(
1636                |mode| match self.global_state.settings.graphics.fullscreen.bit_depth {
1637                    Some(bit_depth) => mode.bit_depth() == bit_depth,
1638                    None => true,
1639                },
1640            )
1641            .map(|mode| mode.refresh_rate_millihertz())
1642            .sorted()
1643            .rev()
1644            .dedup()
1645            .collect();
1646
1647        Text::new(&self.localized_strings.get_msg("hud-settings-refresh_rate"))
1648            .font_size(self.fonts.cyri.scale(14))
1649            .font_id(self.fonts.cyri.conrod_id)
1650            .down_from(state.ids.flashing_lights_label, 25.0)
1651            .right_from(state.ids.bit_depth, 8.0)
1652            .color(TEXT_COLOR)
1653            .set(state.ids.refresh_rate_label, ui);
1654
1655        if let Some(clicked) = DropDownList::new(
1656            once(String::from(
1657                self.localized_strings.get_msg("common-automatic"),
1658            ))
1659            .chain(
1660                refresh_rates
1661                    .iter()
1662                    .map(|&rate| format!("{:.1}", rate as f32 / 1000.0)),
1663            )
1664            .collect::<Vec<String>>()
1665            .as_slice(),
1666            match self
1667                .global_state
1668                .settings
1669                .graphics
1670                .fullscreen
1671                .refresh_rate_millihertz
1672            {
1673                Some(refresh_rate) => refresh_rates
1674                    .iter()
1675                    .position(|rate| rate == &refresh_rate)
1676                    .map(|index| index + 1),
1677                None => Some(0),
1678            },
1679        )
1680        .w_h(128.0, 22.0)
1681        .color(MENU_BG)
1682        .label_color(TEXT_COLOR)
1683        .label_font_id(self.fonts.universal.conrod_id)
1684        .down_from(state.ids.refresh_rate_label, 10.0)
1685        .right_from(state.ids.bit_depth, 8.0)
1686        .set(state.ids.refresh_rate, ui)
1687        {
1688            events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings {
1689                refresh_rate_millihertz: if clicked == 0 {
1690                    None
1691                } else {
1692                    Some(refresh_rates[clicked - 1])
1693                },
1694                ..self.global_state.settings.graphics.fullscreen
1695            }));
1696        }
1697
1698        // Fullscreen
1699        Text::new(&self.localized_strings.get_msg("hud-settings-fullscreen"))
1700            .font_size(self.fonts.cyri.scale(14))
1701            .font_id(self.fonts.cyri.conrod_id)
1702            .down_from(state.ids.resolution, 8.0)
1703            .color(TEXT_COLOR)
1704            .set(state.ids.fullscreen_label, ui);
1705
1706        let enabled = ToggleButton::new(
1707            self.global_state.settings.graphics.fullscreen.enabled,
1708            self.imgs.checkbox,
1709            self.imgs.checkbox_checked,
1710        )
1711        .w_h(18.0, 18.0)
1712        .right_from(state.ids.fullscreen_label, 10.0)
1713        .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
1714        .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
1715        .set(state.ids.fullscreen_button, ui);
1716
1717        if self.global_state.settings.graphics.fullscreen.enabled != enabled {
1718            events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings {
1719                enabled,
1720                ..self.global_state.settings.graphics.fullscreen
1721            }));
1722        }
1723
1724        // Fullscreen Mode
1725        Text::new(
1726            &self
1727                .localized_strings
1728                .get_msg("hud-settings-fullscreen_mode"),
1729        )
1730        .down_from(state.ids.fullscreen_label, 8.0)
1731        .font_size(self.fonts.cyri.scale(14))
1732        .font_id(self.fonts.cyri.conrod_id)
1733        .color(TEXT_COLOR)
1734        .set(state.ids.fullscreen_mode_text, ui);
1735
1736        let mode_list = [FullscreenMode::Exclusive, FullscreenMode::Borderless];
1737        let mode_label_list = [
1738            &self
1739                .localized_strings
1740                .get_msg("hud-settings-fullscreen_mode-exclusive"),
1741            &self
1742                .localized_strings
1743                .get_msg("hud-settings-fullscreen_mode-borderless"),
1744        ];
1745
1746        // Get which fullscreen mode is currently active
1747        let selected = mode_list
1748            .iter()
1749            .position(|x| *x == self.global_state.settings.graphics.fullscreen.mode);
1750
1751        if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
1752            .w_h(400.0, 22.0)
1753            .color(MENU_BG)
1754            .label_color(TEXT_COLOR)
1755            .label_font_id(self.fonts.cyri.conrod_id)
1756            .down_from(state.ids.fullscreen_mode_text, 8.0)
1757            .set(state.ids.fullscreen_mode_list, ui)
1758        {
1759            events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings {
1760                mode: mode_list[clicked],
1761                ..self.global_state.settings.graphics.fullscreen
1762            }));
1763        }
1764
1765        events
1766    }
1767}