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