Skip to main content

veloren_voxygen/hud/settings_window/
controls.rs

1use super::{RESET_BUTTONS_HEIGHT, RESET_BUTTONS_WIDTH};
2
3use crate::{
4    GlobalState,
5    game_input::GameInput,
6    hud::{ERROR_COLOR, TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, img_ids::Imgs},
7    session::settings_change::Control::{self as ControlChange, *},
8    ui::{ToggleButton, fonts::Fonts},
9    window::{MenuInput, RemappingMode},
10};
11use conrod_core::{
12    Borderable, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, color,
13    position::Relative,
14    widget::{self, Button, DropDownList, Rectangle, Scrollbar, Text},
15    widget_ids,
16};
17use i18n::Localization;
18use std::sync::LazyLock;
19use strum::IntoEnumIterator;
20
21widget_ids! {
22    struct Ids {
23        window,
24        window_r,
25        window_scrollbar,
26        reset_controls_button,
27        keybind_helper,
28        gamepad_mode_button,
29        gamepad_option_dropdown,
30        controls_alignment_rectangle,
31        controls_texts[],
32        controls_buttons[],
33        gamelayer_mod1_checkbox,
34        gamelayer_mod2_checkbox,
35        gamelayer_mod1_text,
36        gamelayer_mod2_text,
37    }
38}
39
40#[derive(WidgetCommon)]
41pub struct Controls<'a> {
42    global_state: &'a GlobalState,
43    imgs: &'a Imgs,
44    fonts: &'a Fonts,
45    localized_strings: &'a Localization,
46    #[conrod(common_builder)]
47    common: widget::CommonBuilder,
48}
49impl<'a> Controls<'a> {
50    pub fn new(
51        global_state: &'a GlobalState,
52        imgs: &'a Imgs,
53        fonts: &'a Fonts,
54        localized_strings: &'a Localization,
55    ) -> Self {
56        Self {
57            global_state,
58            imgs,
59            fonts,
60            localized_strings,
61            common: widget::CommonBuilder::default(),
62        }
63    }
64}
65
66#[derive(PartialEq, Clone, Copy)]
67pub enum BindingMode {
68    Keyboard,
69    Gamepad,
70}
71#[derive(Clone, Copy)]
72pub enum GamepadBindingOption {
73    GameButtons,
74    GameLayers,
75    MenuButtons,
76}
77#[derive(Clone, Copy)]
78pub enum KeyMouseBindingOption {
79    KeyMouseButtons,
80    MenuButtons,
81}
82
83pub struct State {
84    ids: Ids,
85    pub binding_mode: BindingMode,
86    pub gamepad_binding_option: GamepadBindingOption,
87    pub keymouse_binding_option: KeyMouseBindingOption,
88}
89
90static SORTED_GAMEINPUTS: LazyLock<Vec<GameInput>> = LazyLock::new(|| {
91    let mut bindings_vec: Vec<GameInput> = GameInput::iter().collect();
92    bindings_vec.sort();
93    bindings_vec
94});
95static SORTED_MENUINPUTS: LazyLock<Vec<MenuInput>> = LazyLock::new(|| {
96    let mut bindings_vec: Vec<MenuInput> = MenuInput::iter().collect();
97    bindings_vec.sort();
98    bindings_vec
99});
100
101impl Widget for Controls<'_> {
102    type Event = Vec<ControlChange>;
103    type State = State;
104    type Style = ();
105
106    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
107        State {
108            ids: Ids::new(id_gen),
109            binding_mode: BindingMode::Keyboard,
110            gamepad_binding_option: GamepadBindingOption::GameButtons,
111            keymouse_binding_option: KeyMouseBindingOption::KeyMouseButtons,
112        }
113    }
114
115    fn style(&self) -> Self::Style {}
116
117    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
118        common_base::prof_span!("Controls::update");
119        let widget::UpdateArgs { state, ui, .. } = args;
120
121        let mut events = Vec::new();
122
123        Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT)
124            .xy(args.rect.xy())
125            .graphics_for(args.id)
126            .scroll_kids()
127            .scroll_kids_vertically()
128            .set(state.ids.window, ui);
129        Rectangle::fill_with([args.rect.w() / 2.0, args.rect.h()], color::TRANSPARENT)
130            .top_right()
131            .parent(state.ids.window)
132            .set(state.ids.window_r, ui);
133        Scrollbar::y_axis(state.ids.window)
134            .thickness(5.0)
135            .rgba(0.33, 0.33, 0.33, 1.0)
136            .set(state.ids.window_scrollbar, ui);
137
138        // These temporary variables exist so state is only borrowed by resize_ids.
139        let binding_mode = state.binding_mode;
140        let gamepad_binding_option = state.gamepad_binding_option;
141        let keymouse_binding_option = state.keymouse_binding_option;
142
143        // Button and Text resizing logic to be used by each binding type branch
144        let mut resize_ids = |len| {
145            if len > state.ids.controls_texts.len() || len > state.ids.controls_buttons.len() {
146                state.update(|s| {
147                    s.ids
148                        .controls_texts
149                        .resize(len, &mut ui.widget_id_generator());
150                    s.ids
151                        .controls_buttons
152                        .resize(len, &mut ui.widget_id_generator());
153                });
154            }
155        };
156
157        // Used for sequential placement in a flow-down pattern
158        let mut previous_element_id = None;
159
160        if let BindingMode::Gamepad = binding_mode {
161            match gamepad_binding_option {
162                GamepadBindingOption::GameButtons => {
163                    let gamepad_controls = &self.global_state.settings.controller;
164
165                    resize_ids(SORTED_GAMEINPUTS.len());
166
167                    // Loop all existing keybindings and the ids for text and button widgets
168                    for (game_input, (&text_id, &button_id)) in SORTED_GAMEINPUTS.iter().zip(
169                        state
170                            .ids
171                            .controls_texts
172                            .iter()
173                            .zip(state.ids.controls_buttons.iter()),
174                    ) {
175                        let (input_string, input_color) =
176                            if let RemappingMode::RemapGamepadButtons(r_input) =
177                                self.global_state.window.remapping_mode
178                                && r_input == *game_input
179                            {
180                                (
181                                    self.localized_strings
182                                        .get_msg("hud-settings-awaitingkey")
183                                        .into_owned(),
184                                    TEXT_COLOR,
185                                )
186                            } else if let Some(button) =
187                                gamepad_controls.get_game_button_binding(*game_input)
188                            {
189                                (
190                                    format!(
191                                        "{} {}",
192                                        button.display_string(self.localized_strings),
193                                        button
194                                            .try_shortened()
195                                            .map_or("".to_owned(), |short| format!("({})", short))
196                                    ),
197                                    if gamepad_controls.game_button_has_conflicting_bindings(button)
198                                    {
199                                        TEXT_BIND_CONFLICT_COLOR
200                                    } else {
201                                        TEXT_COLOR
202                                    },
203                                )
204                            } else {
205                                (
206                                    self.localized_strings
207                                        .get_msg("hud-settings-unbound")
208                                        .into_owned(),
209                                    ERROR_COLOR,
210                                )
211                            };
212                        let loc_key = self
213                            .localized_strings
214                            .get_msg(game_input.get_localization_key());
215                        let text_widget = Text::new(&loc_key)
216                            .color(TEXT_COLOR)
217                            .font_id(self.fonts.cyri.conrod_id)
218                            .font_size(self.fonts.cyri.scale(18));
219                        let button_widget = Button::new()
220                            .label(&input_string)
221                            .label_color(input_color)
222                            .label_font_id(self.fonts.cyri.conrod_id)
223                            .label_font_size(self.fonts.cyri.scale(15))
224                            .w(150.0)
225                            .rgba(0.0, 0.0, 0.0, 0.0)
226                            .border_rgba(0.0, 0.0, 0.0, 255.0)
227                            .label_y(Relative::Scalar(3.0));
228                        // Place top-left if it's the first text, else under the previous one
229                        let text_widget = match previous_element_id {
230                            None => {
231                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
232                            },
233                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
234                        };
235                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
236                        text_widget.set(text_id, ui);
237                        button_widget
238                            .right_from(text_id, 350.0 - text_width)
239                            .set(button_id, ui);
240
241                        for _ in ui.widget_input(button_id).clicks().left() {
242                            events.push(ChangeBindingGamepadButton(*game_input));
243                        }
244                        for _ in ui.widget_input(button_id).clicks().right() {
245                            events.push(RemoveBindingGamepadButton(*game_input));
246                        }
247                        // Set the previous id to the current one for the next cycle
248                        previous_element_id = Some(text_id);
249                    }
250                },
251                GamepadBindingOption::GameLayers => {
252                    let gamepad_controls = &self.global_state.settings.controller;
253
254                    resize_ids(SORTED_GAMEINPUTS.len());
255
256                    // Loop all existing keybindings and the ids for text and button widgets
257                    for (game_input, (&text_id, &button_id)) in SORTED_GAMEINPUTS.iter().zip(
258                        state
259                            .ids
260                            .controls_texts
261                            .iter()
262                            .zip(state.ids.controls_buttons.iter()),
263                    ) {
264                        let (input_string, input_color) =
265                            if let RemappingMode::RemapGamepadLayers(r_input) =
266                                self.global_state.window.remapping_mode
267                                && r_input == *game_input
268                            {
269                                (
270                                    self.localized_strings
271                                        .get_msg("hud-settings-awaitingkey")
272                                        .into_owned(),
273                                    TEXT_COLOR,
274                                )
275                            } else if let Some(entry) =
276                                gamepad_controls.get_layer_button_binding(*game_input)
277                            {
278                                (
279                                    entry.display_string(self.localized_strings),
280                                    if gamepad_controls.layer_entry_has_conflicting_bindings(entry)
281                                    {
282                                        TEXT_BIND_CONFLICT_COLOR
283                                    } else {
284                                        TEXT_COLOR
285                                    },
286                                )
287                            } else {
288                                (
289                                    self.localized_strings
290                                        .get_msg("hud-settings-unbound")
291                                        .into_owned(),
292                                    ERROR_COLOR,
293                                )
294                            };
295                        let loc_key = self
296                            .localized_strings
297                            .get_msg(game_input.get_localization_key());
298                        let text_widget = Text::new(&loc_key)
299                            .color(TEXT_COLOR)
300                            .font_id(self.fonts.cyri.conrod_id)
301                            .font_size(self.fonts.cyri.scale(18));
302                        let button_widget = Button::new()
303                            .label(&input_string)
304                            .label_color(input_color)
305                            .label_font_id(self.fonts.cyri.conrod_id)
306                            .label_font_size(self.fonts.cyri.scale(15))
307                            .w(150.0)
308                            .rgba(0.0, 0.0, 0.0, 0.0)
309                            .border_rgba(0.0, 0.0, 0.0, 255.0)
310                            .label_y(Relative::Scalar(3.0));
311                        // Place top-left if it's the first text, else under the previous one
312                        let text_widget = match previous_element_id {
313                            None => {
314                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
315                            },
316                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
317                        };
318                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
319                        text_widget.set(text_id, ui);
320                        button_widget
321                            .right_from(text_id, 350.0 - text_width)
322                            .set(button_id, ui);
323
324                        for _ in ui.widget_input(button_id).clicks().left() {
325                            events.push(ChangeBindingGamepadLayer(*game_input));
326                        }
327                        for _ in ui.widget_input(button_id).clicks().right() {
328                            events.push(RemoveBindingGamepadLayer(*game_input));
329                        }
330                        // Set the previous id to the current one for the next cycle
331                        previous_element_id = Some(text_id);
332                    }
333                },
334                GamepadBindingOption::MenuButtons => {
335                    let gamepad_controls = &self.global_state.settings.controller;
336
337                    resize_ids(SORTED_MENUINPUTS.len());
338
339                    // Loop all existing keybindings and the ids for text and button widgets
340                    for (menu_input, (&text_id, &button_id)) in SORTED_MENUINPUTS.iter().zip(
341                        state
342                            .ids
343                            .controls_texts
344                            .iter()
345                            .zip(state.ids.controls_buttons.iter()),
346                    ) {
347                        let (input_string, input_color) =
348                            if let RemappingMode::RemapGamepadMenu(r_input) =
349                                self.global_state.window.remapping_mode
350                                && r_input == *menu_input
351                            {
352                                (
353                                    self.localized_strings
354                                        .get_msg("hud-settings-awaitingkey")
355                                        .into_owned(),
356                                    TEXT_COLOR,
357                                )
358                            } else if let Some(button) =
359                                gamepad_controls.get_menu_button_binding(*menu_input)
360                            {
361                                (
362                                    format!(
363                                        "{} {}",
364                                        button.display_string(self.localized_strings),
365                                        button
366                                            .try_shortened()
367                                            .map_or("".to_owned(), |short| format!("({})", short))
368                                    ),
369                                    if gamepad_controls.menu_button_has_conflicting_bindings(button)
370                                    {
371                                        TEXT_BIND_CONFLICT_COLOR
372                                    } else {
373                                        TEXT_COLOR
374                                    },
375                                )
376                            } else {
377                                (
378                                    self.localized_strings
379                                        .get_msg("hud-settings-unbound")
380                                        .into_owned(),
381                                    ERROR_COLOR,
382                                )
383                            };
384                        let loc_key = self
385                            .localized_strings
386                            .get_msg(menu_input.get_localization_key());
387                        let text_widget = Text::new(&loc_key)
388                            .color(TEXT_COLOR)
389                            .font_id(self.fonts.cyri.conrod_id)
390                            .font_size(self.fonts.cyri.scale(18));
391                        let button_widget = Button::new()
392                            .label(&input_string)
393                            .label_color(input_color)
394                            .label_font_id(self.fonts.cyri.conrod_id)
395                            .label_font_size(self.fonts.cyri.scale(15))
396                            .w(150.0)
397                            .rgba(0.0, 0.0, 0.0, 0.0)
398                            .border_rgba(0.0, 0.0, 0.0, 255.0)
399                            .label_y(Relative::Scalar(3.0));
400                        // Place top-left if it's the first text, else under the previous one
401                        let text_widget = match previous_element_id {
402                            None => {
403                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
404                            },
405                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
406                        };
407                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
408                        text_widget.set(text_id, ui);
409                        button_widget
410                            .right_from(text_id, 350.0 - text_width)
411                            .set(button_id, ui);
412
413                        for _ in ui.widget_input(button_id).clicks().left() {
414                            events.push(ChangeBindingGamepadMenu(*menu_input));
415                        }
416                        for _ in ui.widget_input(button_id).clicks().right() {
417                            events.push(RemoveBindingGamepadMenu(*menu_input));
418                        }
419                        // Set the previous id to the current one for the next cycle
420                        previous_element_id = Some(text_id);
421                    }
422                },
423            }
424        } else {
425            match keymouse_binding_option {
426                KeyMouseBindingOption::MenuButtons => {
427                    let controls = &self.global_state.settings.controls;
428
429                    resize_ids(SORTED_MENUINPUTS.len());
430
431                    // Loop all existing keybindings and the ids for text and button widgets
432                    for (menu_input, (&text_id, &button_id)) in SORTED_MENUINPUTS.iter().zip(
433                        state
434                            .ids
435                            .controls_texts
436                            .iter()
437                            .zip(state.ids.controls_buttons.iter()),
438                    ) {
439                        let (key_string, key_color) =
440                            if let RemappingMode::RemapKeyboardMenu(r_input) =
441                                self.global_state.window.remapping_mode
442                                && r_input == *menu_input
443                            {
444                                (
445                                    self.localized_strings
446                                        .get_msg("hud-settings-awaitingkey")
447                                        .into_owned(),
448                                    TEXT_COLOR,
449                                )
450                            } else if let Some(key) = controls.get_menu_binding(*menu_input) {
451                                (
452                                    format!(
453                                        "{} {}",
454                                        key.display_string(),
455                                        key.try_shortened()
456                                            .map_or("".to_owned(), |short| format!("({})", short))
457                                    ),
458                                    if controls.has_conflicting_menu_bindings(key) {
459                                        TEXT_BIND_CONFLICT_COLOR
460                                    } else {
461                                        TEXT_COLOR
462                                    },
463                                )
464                            } else {
465                                (
466                                    self.localized_strings
467                                        .get_msg("hud-settings-unbound")
468                                        .into_owned(),
469                                    ERROR_COLOR,
470                                )
471                            };
472                        let loc_key = self
473                            .localized_strings
474                            .get_msg(menu_input.get_localization_key());
475                        let text_widget = Text::new(&loc_key)
476                            .color(TEXT_COLOR)
477                            .font_id(self.fonts.cyri.conrod_id)
478                            .font_size(self.fonts.cyri.scale(18));
479                        let button_widget = Button::new()
480                            .label(&key_string)
481                            .label_color(key_color)
482                            .label_font_id(self.fonts.cyri.conrod_id)
483                            .label_font_size(self.fonts.cyri.scale(15))
484                            .w(150.0)
485                            .rgba(0.0, 0.0, 0.0, 0.0)
486                            .border_rgba(0.0, 0.0, 0.0, 255.0)
487                            .label_y(Relative::Scalar(3.0));
488                        // Place top-left if it's the first text, else under the previous one
489                        let text_widget = match previous_element_id {
490                            None => {
491                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
492                            },
493                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
494                        };
495                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
496                        text_widget.set(text_id, ui);
497                        button_widget
498                            .right_from(text_id, 350.0 - text_width)
499                            .set(button_id, ui);
500
501                        for _ in ui.widget_input(button_id).clicks().left() {
502                            events.push(ChangeBindingKeyboardMenu(*menu_input));
503                        }
504                        for _ in ui.widget_input(button_id).clicks().right() {
505                            events.push(RemoveBindingKeyboardMenu(*menu_input));
506                        }
507                        // Set the previous id to the current one for the next cycle
508                        previous_element_id = Some(text_id);
509                    }
510                },
511                KeyMouseBindingOption::KeyMouseButtons => {
512                    let controls = &self.global_state.settings.controls;
513
514                    resize_ids(SORTED_GAMEINPUTS.len());
515
516                    // Loop all existing keybindings and the ids for text and button widgets
517                    for (game_input, (&text_id, &button_id)) in SORTED_GAMEINPUTS.iter().zip(
518                        state
519                            .ids
520                            .controls_texts
521                            .iter()
522                            .zip(state.ids.controls_buttons.iter()),
523                    ) {
524                        let (key_string, key_color) = if let RemappingMode::RemapKeyboard(r_input) =
525                            self.global_state.window.remapping_mode
526                            && r_input == *game_input
527                        {
528                            (
529                                self.localized_strings
530                                    .get_msg("hud-settings-awaitingkey")
531                                    .into_owned(),
532                                TEXT_COLOR,
533                            )
534                        } else if let Some(key) = controls.get_binding(*game_input) {
535                            (
536                                format!(
537                                    "{} {}",
538                                    key.display_string(),
539                                    key.try_shortened()
540                                        .map_or("".to_owned(), |short| format!("({})", short))
541                                ),
542                                if controls.has_conflicting_bindings(key) {
543                                    TEXT_BIND_CONFLICT_COLOR
544                                } else {
545                                    TEXT_COLOR
546                                },
547                            )
548                        } else {
549                            (
550                                self.localized_strings
551                                    .get_msg("hud-settings-unbound")
552                                    .into_owned(),
553                                ERROR_COLOR,
554                            )
555                        };
556                        let loc_key = self
557                            .localized_strings
558                            .get_msg(game_input.get_localization_key());
559                        let text_widget = Text::new(&loc_key)
560                            .color(TEXT_COLOR)
561                            .font_id(self.fonts.cyri.conrod_id)
562                            .font_size(self.fonts.cyri.scale(18));
563                        let button_widget = Button::new()
564                            .label(&key_string)
565                            .label_color(key_color)
566                            .label_font_id(self.fonts.cyri.conrod_id)
567                            .label_font_size(self.fonts.cyri.scale(15))
568                            .w(150.0)
569                            .rgba(0.0, 0.0, 0.0, 0.0)
570                            .border_rgba(0.0, 0.0, 0.0, 255.0)
571                            .label_y(Relative::Scalar(3.0));
572                        // Place top-left if it's the first text, else under the previous one
573                        let text_widget = match previous_element_id {
574                            None => {
575                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
576                            },
577                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
578                        };
579                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
580                        text_widget.set(text_id, ui);
581                        button_widget
582                            .right_from(text_id, 350.0 - text_width)
583                            .set(button_id, ui);
584
585                        for _ in ui.widget_input(button_id).clicks().left() {
586                            events.push(ChangeBindingKeyboard(*game_input));
587                        }
588                        for _ in ui.widget_input(button_id).clicks().right() {
589                            events.push(RemoveBindingKeyboard(*game_input));
590                        }
591                        // Set the previous id to the current one for the next cycle
592                        previous_element_id = Some(text_id);
593                    }
594                },
595            }
596        }
597
598        // Reset the KeyBindings settings to the default settings
599        if let Some(prev_id) = previous_element_id {
600            if Button::image(self.imgs.button)
601                .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
602                .hover_image(self.imgs.button_hover)
603                .press_image(self.imgs.button_press)
604                .down_from(prev_id, 20.0)
605                .label(
606                    &self
607                        .localized_strings
608                        .get_msg("hud-settings-reset_keybinds"),
609                )
610                .label_font_size(self.fonts.cyri.scale(14))
611                .label_color(TEXT_COLOR)
612                .label_font_id(self.fonts.cyri.conrod_id)
613                .label_y(Relative::Scalar(2.0))
614                .set(state.ids.reset_controls_button, ui)
615                .was_clicked()
616            {
617                if state.binding_mode != BindingMode::Gamepad {
618                    events.push(ResetKeyBindingsKeyboard);
619                } else {
620                    // resets all gamepad bindings no matter which tab you are in: buttons,
621                    // gamelayer, or menu
622                    events.push(ResetKeyBindingsGamepad);
623                }
624            }
625            previous_element_id = Some(state.ids.reset_controls_button)
626        }
627
628        let offset = ui
629            .widget_graph()
630            .widget(state.ids.window)
631            .and_then(|widget| {
632                widget
633                    .maybe_y_scroll_state
634                    .as_ref()
635                    .map(|scroll| scroll.offset)
636            })
637            .unwrap_or(0.0);
638
639        let keybind_helper_text = self
640            .localized_strings
641            .get_msg("hud-settings-keybind-helper");
642        let keybind_helper = Text::new(&keybind_helper_text)
643            .color(TEXT_COLOR)
644            .font_id(self.fonts.cyri.conrod_id)
645            .font_size(self.fonts.cyri.scale(18));
646        keybind_helper
647            .top_right_with_margins_on(state.ids.window, offset + 5.0, 10.0)
648            .set(state.ids.keybind_helper, ui);
649
650        // Drop down menu to select keyboard game bindings or menu bindings
651        if let BindingMode::Keyboard = state.binding_mode {
652            let keybindings = &self
653                .localized_strings
654                .get_msg("hud-settings-keyboard-binding");
655            let menu_keybindings = &self.localized_strings.get_msg("hud-settings-menu_buttons");
656
657            let binding_mode_list = [keybindings, menu_keybindings];
658            if let Some(clicked) = DropDownList::new(
659                &binding_mode_list,
660                Some(state.keymouse_binding_option as usize),
661            )
662            .label_color(TEXT_COLOR)
663            .label_font_id(self.fonts.cyri.conrod_id)
664            .label_font_size(self.fonts.cyri.scale(15))
665            .w_h(125.0, 35.0)
666            .rgba(0.0, 0.0, 0.0, 0.0)
667            .border_rgba(0.0, 0.0, 0.0, 255.0)
668            .label_y(Relative::Scalar(1.0))
669            .down_from(state.ids.gamepad_mode_button, 10.0)
670            .set(state.ids.gamepad_option_dropdown, ui)
671            {
672                match clicked {
673                    0 => {
674                        state.update(|s| {
675                            s.keymouse_binding_option = KeyMouseBindingOption::KeyMouseButtons
676                        });
677                        events.push(ResetBindingMode);
678                    },
679                    1 => {
680                        state.update(|s| {
681                            s.keymouse_binding_option = KeyMouseBindingOption::MenuButtons
682                        });
683                        events.push(ResetBindingMode);
684                    },
685                    _ => {
686                        state.update(|s| {
687                            s.keymouse_binding_option = KeyMouseBindingOption::KeyMouseButtons
688                        });
689                        events.push(ResetBindingMode);
690                    },
691                }
692            }
693        }
694
695        // Drop down menu to select gamepad game bindings or menu bindings
696        if let BindingMode::Gamepad = state.binding_mode {
697            let game_buttons = &self.localized_strings.get_msg("hud-settings-game_buttons");
698            let game_layers = &self.localized_strings.get_msg("hud-settings-game_layers");
699            let menu_buttons = &self.localized_strings.get_msg("hud-settings-menu_buttons");
700
701            let binding_mode_list = [game_buttons, game_layers, menu_buttons];
702            if let Some(clicked) = DropDownList::new(
703                &binding_mode_list,
704                Some(state.gamepad_binding_option as usize),
705            )
706            .label_color(TEXT_COLOR)
707            .label_font_id(self.fonts.cyri.conrod_id)
708            .label_font_size(self.fonts.cyri.scale(15))
709            .w_h(125.0, 35.0)
710            .rgba(0.0, 0.0, 0.0, 0.0)
711            .border_rgba(0.0, 0.0, 0.0, 255.0)
712            .label_y(Relative::Scalar(1.0))
713            .down_from(state.ids.gamepad_mode_button, 10.0)
714            .set(state.ids.gamepad_option_dropdown, ui)
715            {
716                match clicked {
717                    0 => {
718                        state.update(|s| {
719                            s.gamepad_binding_option = GamepadBindingOption::GameButtons
720                        });
721                        events.push(ResetBindingMode);
722                    },
723                    1 => {
724                        state.update(|s| {
725                            s.gamepad_binding_option = GamepadBindingOption::GameLayers
726                        });
727                        events.push(ResetBindingMode);
728                    },
729                    2 => {
730                        state.update(|s| {
731                            s.gamepad_binding_option = GamepadBindingOption::MenuButtons
732                        });
733                        events.push(ResetBindingMode);
734                    },
735                    _ => {
736                        state.update(|s| {
737                            s.gamepad_binding_option = GamepadBindingOption::GameButtons
738                        });
739                        events.push(ResetBindingMode);
740                    },
741                }
742            }
743
744            // game layer mod checkboxes
745            if let GamepadBindingOption::GameLayers = state.gamepad_binding_option {
746                // mod1 checkbox
747                let mod1_text = "RB";
748                let gamelayer_mod1 = ToggleButton::new(
749                    self.global_state.window.gamelayer_mod1,
750                    self.imgs.checkbox,
751                    self.imgs.checkbox_checked,
752                )
753                .down_from(state.ids.gamepad_option_dropdown, 10.0)
754                .w_h(18.0, 18.0)
755                .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
756                .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
757                .set(state.ids.gamelayer_mod1_checkbox, ui);
758                if self.global_state.window.gamelayer_mod1 != gamelayer_mod1 {
759                    events.push(GameLayerMod1(gamelayer_mod1));
760                }
761                Text::new(mod1_text)
762                    .right_from(state.ids.gamelayer_mod1_checkbox, 10.0)
763                    .font_size(self.fonts.cyri.scale(15))
764                    .font_id(self.fonts.cyri.conrod_id)
765                    .color(TEXT_COLOR)
766                    .set(state.ids.gamelayer_mod1_text, ui);
767
768                //mod2 checkbox
769                let mod2_text = "LB";
770                let gamelayer_mod2 = ToggleButton::new(
771                    self.global_state.window.gamelayer_mod2,
772                    self.imgs.checkbox,
773                    self.imgs.checkbox_checked,
774                )
775                .down_from(state.ids.gamelayer_mod1_checkbox, 10.0)
776                .w_h(18.0, 18.0)
777                .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
778                .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
779                .set(state.ids.gamelayer_mod2_checkbox, ui);
780                if self.global_state.window.gamelayer_mod2 != gamelayer_mod2 {
781                    events.push(GameLayerMod2(gamelayer_mod2));
782                }
783                Text::new(mod2_text)
784                    .right_from(state.ids.gamelayer_mod2_checkbox, 10.0)
785                    .font_size(self.fonts.cyri.scale(15))
786                    .font_id(self.fonts.cyri.conrod_id)
787                    .color(TEXT_COLOR)
788                    .set(state.ids.gamelayer_mod2_text, ui);
789            }
790        }
791
792        let gamepad = &self.localized_strings.get_msg("hud-settings-gamepad");
793        let keyboard = &self.localized_strings.get_msg("hud-settings-keyboard");
794
795        let binding_mode_toggle_widget = Button::new()
796            .label(if let BindingMode::Gamepad = state.binding_mode {
797                gamepad
798            } else {
799                keyboard
800            })
801            .label_color(TEXT_COLOR)
802            .label_font_id(self.fonts.cyri.conrod_id)
803            .label_font_size(self.fonts.cyri.scale(15))
804            .w_h(125.0, 35.0)
805            .rgba(0.0, 0.0, 0.0, 0.0)
806            .border_rgba(0.0, 0.0, 0.0, 255.0)
807            .label_y(Relative::Scalar(1.0));
808        if binding_mode_toggle_widget
809            .down_from(state.ids.keybind_helper, 10.0)
810            .align_right_of(state.ids.keybind_helper)
811            .set(state.ids.gamepad_mode_button, ui)
812            .was_clicked()
813        {
814            if let BindingMode::Keyboard = state.binding_mode {
815                state.update(|s| s.binding_mode = BindingMode::Gamepad);
816                events.push(ResetBindingMode);
817            } else {
818                state.update(|s| s.binding_mode = BindingMode::Keyboard);
819                events.push(ResetBindingMode);
820            }
821        }
822
823        // Add an empty text widget to simulate some bottom margin, because conrod sucks
824        if let Some(prev_id) = previous_element_id {
825            Rectangle::fill_with([1.0, 1.0], color::TRANSPARENT)
826                .down_from(prev_id, 10.0)
827                .set(state.ids.controls_alignment_rectangle, ui);
828        }
829
830        events
831    }
832}