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
78pub struct State {
79    ids: Ids,
80    pub binding_mode: BindingMode,
81    pub gamepad_binding_option: GamepadBindingOption,
82}
83
84static SORTED_GAMEINPUTS: LazyLock<Vec<GameInput>> = LazyLock::new(|| {
85    let mut bindings_vec: Vec<GameInput> = GameInput::iter().collect();
86    bindings_vec.sort();
87    bindings_vec
88});
89static SORTED_MENUINPUTS: LazyLock<Vec<MenuInput>> = LazyLock::new(|| {
90    let mut bindings_vec: Vec<MenuInput> = MenuInput::iter().collect();
91    bindings_vec.sort();
92    bindings_vec
93});
94
95impl Widget for Controls<'_> {
96    type Event = Vec<ControlChange>;
97    type State = State;
98    type Style = ();
99
100    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
101        State {
102            ids: Ids::new(id_gen),
103            binding_mode: BindingMode::Keyboard,
104            gamepad_binding_option: GamepadBindingOption::GameButtons,
105        }
106    }
107
108    fn style(&self) -> Self::Style {}
109
110    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
111        common_base::prof_span!("Controls::update");
112        let widget::UpdateArgs { state, ui, .. } = args;
113
114        let mut events = Vec::new();
115
116        Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT)
117            .xy(args.rect.xy())
118            .graphics_for(args.id)
119            .scroll_kids()
120            .scroll_kids_vertically()
121            .set(state.ids.window, ui);
122        Rectangle::fill_with([args.rect.w() / 2.0, args.rect.h()], color::TRANSPARENT)
123            .top_right()
124            .parent(state.ids.window)
125            .set(state.ids.window_r, ui);
126        Scrollbar::y_axis(state.ids.window)
127            .thickness(5.0)
128            .rgba(0.33, 0.33, 0.33, 1.0)
129            .set(state.ids.window_scrollbar, ui);
130
131        // These temporary variables exist so state is only borrowed by resize_ids.
132        let binding_mode = state.binding_mode;
133        let gamepad_binding_option = state.gamepad_binding_option;
134
135        // Button and Text resizing logic to be used by each binding type branch
136        let mut resize_ids = |len| {
137            if len > state.ids.controls_texts.len() || len > state.ids.controls_buttons.len() {
138                state.update(|s| {
139                    s.ids
140                        .controls_texts
141                        .resize(len, &mut ui.widget_id_generator());
142                    s.ids
143                        .controls_buttons
144                        .resize(len, &mut ui.widget_id_generator());
145                });
146            }
147        };
148
149        // Used for sequential placement in a flow-down pattern
150        let mut previous_element_id = None;
151
152        if let BindingMode::Gamepad = binding_mode {
153            match gamepad_binding_option {
154                GamepadBindingOption::GameButtons => {
155                    let gamepad_controls = &self.global_state.settings.controller;
156
157                    resize_ids(SORTED_GAMEINPUTS.len());
158
159                    // Loop all existing keybindings and the ids for text and button widgets
160                    for (game_input, (&text_id, &button_id)) in SORTED_GAMEINPUTS.iter().zip(
161                        state
162                            .ids
163                            .controls_texts
164                            .iter()
165                            .zip(state.ids.controls_buttons.iter()),
166                    ) {
167                        let (input_string, input_color) =
168                            if let RemappingMode::RemapGamepadButtons(r_input) =
169                                self.global_state.window.remapping_mode
170                                && r_input == *game_input
171                            {
172                                (
173                                    self.localized_strings
174                                        .get_msg("hud-settings-awaitingkey")
175                                        .into_owned(),
176                                    TEXT_COLOR,
177                                )
178                            } else if let Some(button) =
179                                gamepad_controls.get_game_button_binding(*game_input)
180                            {
181                                (
182                                    format!(
183                                        "{} {}",
184                                        button.display_string(self.localized_strings),
185                                        button
186                                            .try_shortened()
187                                            .map_or("".to_owned(), |short| format!("({})", short))
188                                    ),
189                                    if gamepad_controls.game_button_has_conflicting_bindings(button)
190                                    {
191                                        TEXT_BIND_CONFLICT_COLOR
192                                    } else {
193                                        TEXT_COLOR
194                                    },
195                                )
196                            } else {
197                                (
198                                    self.localized_strings
199                                        .get_msg("hud-settings-unbound")
200                                        .into_owned(),
201                                    ERROR_COLOR,
202                                )
203                            };
204                        let loc_key = self
205                            .localized_strings
206                            .get_msg(game_input.get_localization_key());
207                        let text_widget = Text::new(&loc_key)
208                            .color(TEXT_COLOR)
209                            .font_id(self.fonts.cyri.conrod_id)
210                            .font_size(self.fonts.cyri.scale(18));
211                        let button_widget = Button::new()
212                            .label(&input_string)
213                            .label_color(input_color)
214                            .label_font_id(self.fonts.cyri.conrod_id)
215                            .label_font_size(self.fonts.cyri.scale(15))
216                            .w(150.0)
217                            .rgba(0.0, 0.0, 0.0, 0.0)
218                            .border_rgba(0.0, 0.0, 0.0, 255.0)
219                            .label_y(Relative::Scalar(3.0));
220                        // Place top-left if it's the first text, else under the previous one
221                        let text_widget = match previous_element_id {
222                            None => {
223                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
224                            },
225                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
226                        };
227                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
228                        text_widget.set(text_id, ui);
229                        button_widget
230                            .right_from(text_id, 350.0 - text_width)
231                            .set(button_id, ui);
232
233                        for _ in ui.widget_input(button_id).clicks().left() {
234                            events.push(ChangeBindingGamepadButton(*game_input));
235                        }
236                        for _ in ui.widget_input(button_id).clicks().right() {
237                            events.push(RemoveBindingGamepadButton(*game_input));
238                        }
239                        // Set the previous id to the current one for the next cycle
240                        previous_element_id = Some(text_id);
241                    }
242                },
243                GamepadBindingOption::GameLayers => {
244                    let gamepad_controls = &self.global_state.settings.controller;
245
246                    resize_ids(SORTED_GAMEINPUTS.len());
247
248                    // Loop all existing keybindings and the ids for text and button widgets
249                    for (game_input, (&text_id, &button_id)) in SORTED_GAMEINPUTS.iter().zip(
250                        state
251                            .ids
252                            .controls_texts
253                            .iter()
254                            .zip(state.ids.controls_buttons.iter()),
255                    ) {
256                        let (input_string, input_color) =
257                            if let RemappingMode::RemapGamepadLayers(r_input) =
258                                self.global_state.window.remapping_mode
259                                && r_input == *game_input
260                            {
261                                (
262                                    self.localized_strings
263                                        .get_msg("hud-settings-awaitingkey")
264                                        .into_owned(),
265                                    TEXT_COLOR,
266                                )
267                            } else if let Some(entry) =
268                                gamepad_controls.get_layer_button_binding(*game_input)
269                            {
270                                (
271                                    entry.display_string(self.localized_strings),
272                                    if gamepad_controls.layer_entry_has_conflicting_bindings(entry)
273                                    {
274                                        TEXT_BIND_CONFLICT_COLOR
275                                    } else {
276                                        TEXT_COLOR
277                                    },
278                                )
279                            } else {
280                                (
281                                    self.localized_strings
282                                        .get_msg("hud-settings-unbound")
283                                        .into_owned(),
284                                    ERROR_COLOR,
285                                )
286                            };
287                        let loc_key = self
288                            .localized_strings
289                            .get_msg(game_input.get_localization_key());
290                        let text_widget = Text::new(&loc_key)
291                            .color(TEXT_COLOR)
292                            .font_id(self.fonts.cyri.conrod_id)
293                            .font_size(self.fonts.cyri.scale(18));
294                        let button_widget = Button::new()
295                            .label(&input_string)
296                            .label_color(input_color)
297                            .label_font_id(self.fonts.cyri.conrod_id)
298                            .label_font_size(self.fonts.cyri.scale(15))
299                            .w(150.0)
300                            .rgba(0.0, 0.0, 0.0, 0.0)
301                            .border_rgba(0.0, 0.0, 0.0, 255.0)
302                            .label_y(Relative::Scalar(3.0));
303                        // Place top-left if it's the first text, else under the previous one
304                        let text_widget = match previous_element_id {
305                            None => {
306                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
307                            },
308                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
309                        };
310                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
311                        text_widget.set(text_id, ui);
312                        button_widget
313                            .right_from(text_id, 350.0 - text_width)
314                            .set(button_id, ui);
315
316                        for _ in ui.widget_input(button_id).clicks().left() {
317                            events.push(ChangeBindingGamepadLayer(*game_input));
318                        }
319                        for _ in ui.widget_input(button_id).clicks().right() {
320                            events.push(RemoveBindingGamepadLayer(*game_input));
321                        }
322                        // Set the previous id to the current one for the next cycle
323                        previous_element_id = Some(text_id);
324                    }
325                },
326                GamepadBindingOption::MenuButtons => {
327                    let gamepad_controls = &self.global_state.settings.controller;
328
329                    resize_ids(SORTED_MENUINPUTS.len());
330
331                    // Loop all existing keybindings and the ids for text and button widgets
332                    for (menu_input, (&text_id, &button_id)) in SORTED_MENUINPUTS.iter().zip(
333                        state
334                            .ids
335                            .controls_texts
336                            .iter()
337                            .zip(state.ids.controls_buttons.iter()),
338                    ) {
339                        let (input_string, input_color) =
340                            if let RemappingMode::RemapGamepadMenu(r_input) =
341                                self.global_state.window.remapping_mode
342                                && r_input == *menu_input
343                            {
344                                (
345                                    self.localized_strings
346                                        .get_msg("hud-settings-awaitingkey")
347                                        .into_owned(),
348                                    TEXT_COLOR,
349                                )
350                            } else if let Some(button) =
351                                gamepad_controls.get_menu_button_binding(*menu_input)
352                            {
353                                (
354                                    format!(
355                                        "{} {}",
356                                        button.display_string(self.localized_strings),
357                                        button
358                                            .try_shortened()
359                                            .map_or("".to_owned(), |short| format!("({})", short))
360                                    ),
361                                    if gamepad_controls.menu_button_has_conflicting_bindings(button)
362                                    {
363                                        TEXT_BIND_CONFLICT_COLOR
364                                    } else {
365                                        TEXT_COLOR
366                                    },
367                                )
368                            } else {
369                                (
370                                    self.localized_strings
371                                        .get_msg("hud-settings-unbound")
372                                        .into_owned(),
373                                    ERROR_COLOR,
374                                )
375                            };
376                        let loc_key = self
377                            .localized_strings
378                            .get_msg(menu_input.get_localization_key());
379                        let text_widget = Text::new(&loc_key)
380                            .color(TEXT_COLOR)
381                            .font_id(self.fonts.cyri.conrod_id)
382                            .font_size(self.fonts.cyri.scale(18));
383                        let button_widget = Button::new()
384                            .label(&input_string)
385                            .label_color(input_color)
386                            .label_font_id(self.fonts.cyri.conrod_id)
387                            .label_font_size(self.fonts.cyri.scale(15))
388                            .w(150.0)
389                            .rgba(0.0, 0.0, 0.0, 0.0)
390                            .border_rgba(0.0, 0.0, 0.0, 255.0)
391                            .label_y(Relative::Scalar(3.0));
392                        // Place top-left if it's the first text, else under the previous one
393                        let text_widget = match previous_element_id {
394                            None => {
395                                text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0)
396                            },
397                            Some(prev_id) => text_widget.down_from(prev_id, 10.0),
398                        };
399                        let text_width = text_widget.get_w(ui).unwrap_or(0.0);
400                        text_widget.set(text_id, ui);
401                        button_widget
402                            .right_from(text_id, 350.0 - text_width)
403                            .set(button_id, ui);
404
405                        for _ in ui.widget_input(button_id).clicks().left() {
406                            events.push(ChangeBindingGamepadMenu(*menu_input));
407                        }
408                        for _ in ui.widget_input(button_id).clicks().right() {
409                            events.push(RemoveBindingGamepadMenu(*menu_input));
410                        }
411                        // Set the previous id to the current one for the next cycle
412                        previous_element_id = Some(text_id);
413                    }
414                },
415            }
416        } else {
417            let controls = &self.global_state.settings.controls;
418
419            resize_ids(SORTED_GAMEINPUTS.len());
420
421            // Loop all existing keybindings and the ids for text and button widgets
422            for (game_input, (&text_id, &button_id)) in SORTED_GAMEINPUTS.iter().zip(
423                state
424                    .ids
425                    .controls_texts
426                    .iter()
427                    .zip(state.ids.controls_buttons.iter()),
428            ) {
429                let (key_string, key_color) = if let RemappingMode::RemapKeyboard(r_input) =
430                    self.global_state.window.remapping_mode
431                    && r_input == *game_input
432                {
433                    (
434                        self.localized_strings
435                            .get_msg("hud-settings-awaitingkey")
436                            .into_owned(),
437                        TEXT_COLOR,
438                    )
439                } else if let Some(key) = controls.get_binding(*game_input) {
440                    (
441                        format!(
442                            "{} {}",
443                            key.display_string(),
444                            key.try_shortened()
445                                .map_or("".to_owned(), |short| format!("({})", short))
446                        ),
447                        if controls.has_conflicting_bindings(key) {
448                            TEXT_BIND_CONFLICT_COLOR
449                        } else {
450                            TEXT_COLOR
451                        },
452                    )
453                } else {
454                    (
455                        self.localized_strings
456                            .get_msg("hud-settings-unbound")
457                            .into_owned(),
458                        ERROR_COLOR,
459                    )
460                };
461                let loc_key = self
462                    .localized_strings
463                    .get_msg(game_input.get_localization_key());
464                let text_widget = Text::new(&loc_key)
465                    .color(TEXT_COLOR)
466                    .font_id(self.fonts.cyri.conrod_id)
467                    .font_size(self.fonts.cyri.scale(18));
468                let button_widget = Button::new()
469                    .label(&key_string)
470                    .label_color(key_color)
471                    .label_font_id(self.fonts.cyri.conrod_id)
472                    .label_font_size(self.fonts.cyri.scale(15))
473                    .w(150.0)
474                    .rgba(0.0, 0.0, 0.0, 0.0)
475                    .border_rgba(0.0, 0.0, 0.0, 255.0)
476                    .label_y(Relative::Scalar(3.0));
477                // Place top-left if it's the first text, else under the previous one
478                let text_widget = match previous_element_id {
479                    None => text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0),
480                    Some(prev_id) => text_widget.down_from(prev_id, 10.0),
481                };
482                let text_width = text_widget.get_w(ui).unwrap_or(0.0);
483                text_widget.set(text_id, ui);
484                button_widget
485                    .right_from(text_id, 350.0 - text_width)
486                    .set(button_id, ui);
487
488                for _ in ui.widget_input(button_id).clicks().left() {
489                    events.push(ChangeBindingKeyboard(*game_input));
490                }
491                for _ in ui.widget_input(button_id).clicks().right() {
492                    events.push(RemoveBindingKeyboard(*game_input));
493                }
494                // Set the previous id to the current one for the next cycle
495                previous_element_id = Some(text_id);
496            }
497        }
498
499        // Reset the KeyBindings settings to the default settings
500        if let Some(prev_id) = previous_element_id {
501            if Button::image(self.imgs.button)
502                .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
503                .hover_image(self.imgs.button_hover)
504                .press_image(self.imgs.button_press)
505                .down_from(prev_id, 20.0)
506                .label(
507                    &self
508                        .localized_strings
509                        .get_msg("hud-settings-reset_keybinds"),
510                )
511                .label_font_size(self.fonts.cyri.scale(14))
512                .label_color(TEXT_COLOR)
513                .label_font_id(self.fonts.cyri.conrod_id)
514                .label_y(Relative::Scalar(2.0))
515                .set(state.ids.reset_controls_button, ui)
516                .was_clicked()
517            {
518                if state.binding_mode != BindingMode::Gamepad {
519                    events.push(ResetKeyBindingsKeyboard);
520                } else {
521                    // resets all gamepad bindings no matter which tab you are in: buttons,
522                    // gamelayer, or menu
523                    events.push(ResetKeyBindingsGamepad);
524                }
525            }
526            previous_element_id = Some(state.ids.reset_controls_button)
527        }
528
529        let offset = ui
530            .widget_graph()
531            .widget(state.ids.window)
532            .and_then(|widget| {
533                widget
534                    .maybe_y_scroll_state
535                    .as_ref()
536                    .map(|scroll| scroll.offset)
537            })
538            .unwrap_or(0.0);
539
540        let keybind_helper_text = self
541            .localized_strings
542            .get_msg("hud-settings-keybind-helper");
543        let keybind_helper = Text::new(&keybind_helper_text)
544            .color(TEXT_COLOR)
545            .font_id(self.fonts.cyri.conrod_id)
546            .font_size(self.fonts.cyri.scale(18));
547        keybind_helper
548            .top_right_with_margins_on(state.ids.window, offset + 5.0, 10.0)
549            .set(state.ids.keybind_helper, ui);
550
551        if let BindingMode::Gamepad = state.binding_mode {
552            let game_buttons = &self.localized_strings.get_msg("hud-settings-game_buttons");
553            let game_layers = &self.localized_strings.get_msg("hud-settings-game_layers");
554            let menu_buttons = &self.localized_strings.get_msg("hud-settings-menu_buttons");
555
556            let binding_mode_list = [game_buttons, game_layers, menu_buttons];
557            if let Some(clicked) = DropDownList::new(
558                &binding_mode_list,
559                Some(state.gamepad_binding_option as usize),
560            )
561            .label_color(TEXT_COLOR)
562            .label_font_id(self.fonts.cyri.conrod_id)
563            .label_font_size(self.fonts.cyri.scale(15))
564            .w_h(125.0, 35.0)
565            .rgba(0.0, 0.0, 0.0, 0.0)
566            .border_rgba(0.0, 0.0, 0.0, 255.0)
567            .label_y(Relative::Scalar(1.0))
568            .down_from(state.ids.gamepad_mode_button, 10.0)
569            .set(state.ids.gamepad_option_dropdown, ui)
570            {
571                match clicked {
572                    0 => {
573                        state.update(|s| {
574                            s.gamepad_binding_option = GamepadBindingOption::GameButtons
575                        });
576                        events.push(ResetBindingMode);
577                    },
578                    1 => {
579                        state.update(|s| {
580                            s.gamepad_binding_option = GamepadBindingOption::GameLayers
581                        });
582                        events.push(ResetBindingMode);
583                    },
584                    2 => {
585                        state.update(|s| {
586                            s.gamepad_binding_option = GamepadBindingOption::MenuButtons
587                        });
588                        events.push(ResetBindingMode);
589                    },
590                    _ => {
591                        state.update(|s| {
592                            s.gamepad_binding_option = GamepadBindingOption::GameButtons
593                        });
594                        events.push(ResetBindingMode);
595                    },
596                }
597            }
598
599            // game layer mod checkboxes
600            if let GamepadBindingOption::GameLayers = state.gamepad_binding_option {
601                // mod1 checkbox
602                let mod1_text = "RB";
603                let gamelayer_mod1 = ToggleButton::new(
604                    self.global_state.window.gamelayer_mod1,
605                    self.imgs.checkbox,
606                    self.imgs.checkbox_checked,
607                )
608                .down_from(state.ids.gamepad_option_dropdown, 10.0)
609                .w_h(18.0, 18.0)
610                .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
611                .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
612                .set(state.ids.gamelayer_mod1_checkbox, ui);
613                if self.global_state.window.gamelayer_mod1 != gamelayer_mod1 {
614                    events.push(GameLayerMod1(gamelayer_mod1));
615                }
616                Text::new(mod1_text)
617                    .right_from(state.ids.gamelayer_mod1_checkbox, 10.0)
618                    .font_size(self.fonts.cyri.scale(15))
619                    .font_id(self.fonts.cyri.conrod_id)
620                    .color(TEXT_COLOR)
621                    .set(state.ids.gamelayer_mod1_text, ui);
622
623                //mod2 checkbox
624                let mod2_text = "LB";
625                let gamelayer_mod2 = ToggleButton::new(
626                    self.global_state.window.gamelayer_mod2,
627                    self.imgs.checkbox,
628                    self.imgs.checkbox_checked,
629                )
630                .down_from(state.ids.gamelayer_mod1_checkbox, 10.0)
631                .w_h(18.0, 18.0)
632                .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
633                .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
634                .set(state.ids.gamelayer_mod2_checkbox, ui);
635                if self.global_state.window.gamelayer_mod2 != gamelayer_mod2 {
636                    events.push(GameLayerMod2(gamelayer_mod2));
637                }
638                Text::new(mod2_text)
639                    .right_from(state.ids.gamelayer_mod2_checkbox, 10.0)
640                    .font_size(self.fonts.cyri.scale(15))
641                    .font_id(self.fonts.cyri.conrod_id)
642                    .color(TEXT_COLOR)
643                    .set(state.ids.gamelayer_mod2_text, ui);
644            }
645        }
646
647        let gamepad = &self.localized_strings.get_msg("hud-settings-gamepad");
648        let keyboard = &self.localized_strings.get_msg("hud-settings-keyboard");
649
650        let binding_mode_toggle_widget = Button::new()
651            .label(if let BindingMode::Gamepad = state.binding_mode {
652                gamepad
653            } else {
654                keyboard
655            })
656            .label_color(TEXT_COLOR)
657            .label_font_id(self.fonts.cyri.conrod_id)
658            .label_font_size(self.fonts.cyri.scale(15))
659            .w_h(125.0, 35.0)
660            .rgba(0.0, 0.0, 0.0, 0.0)
661            .border_rgba(0.0, 0.0, 0.0, 255.0)
662            .label_y(Relative::Scalar(1.0));
663        if binding_mode_toggle_widget
664            .down_from(state.ids.keybind_helper, 10.0)
665            .align_right_of(state.ids.keybind_helper)
666            .set(state.ids.gamepad_mode_button, ui)
667            .was_clicked()
668        {
669            if let BindingMode::Keyboard = state.binding_mode {
670                state.update(|s| s.binding_mode = BindingMode::Gamepad);
671                events.push(ResetBindingMode);
672            } else {
673                state.update(|s| s.binding_mode = BindingMode::Keyboard);
674                events.push(ResetBindingMode);
675            }
676        }
677
678        // Add an empty text widget to simulate some bottom margin, because conrod sucks
679        if let Some(prev_id) = previous_element_id {
680            Rectangle::fill_with([1.0, 1.0], color::TRANSPARENT)
681                .down_from(prev_id, 10.0)
682                .set(state.ids.controls_alignment_rectangle, ui);
683        }
684
685        events
686    }
687}