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