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