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 as ControlChange, Control::*},
8    ui::fonts::Fonts,
9};
10use conrod_core::{
11    Borderable, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, color,
12    position::Relative,
13    widget::{self, Button, Rectangle, Scrollbar, Text},
14    widget_ids,
15};
16use i18n::Localization;
17use strum::IntoEnumIterator;
18
19widget_ids! {
20    struct Ids {
21        window,
22        window_r,
23        window_scrollbar,
24        reset_controls_button,
25        keybind_helper,
26        controls_alignment_rectangle,
27        controls_texts[],
28        controls_buttons[],
29    }
30}
31
32#[derive(WidgetCommon)]
33pub struct Controls<'a> {
34    global_state: &'a GlobalState,
35    imgs: &'a Imgs,
36    fonts: &'a Fonts,
37    localized_strings: &'a Localization,
38    #[conrod(common_builder)]
39    common: widget::CommonBuilder,
40}
41impl<'a> Controls<'a> {
42    pub fn new(
43        global_state: &'a GlobalState,
44        imgs: &'a Imgs,
45        fonts: &'a Fonts,
46        localized_strings: &'a Localization,
47    ) -> Self {
48        Self {
49            global_state,
50            imgs,
51            fonts,
52            localized_strings,
53            common: widget::CommonBuilder::default(),
54        }
55    }
56}
57
58pub struct State {
59    ids: Ids,
60}
61
62impl Widget for Controls<'_> {
63    type Event = Vec<ControlChange>;
64    type State = State;
65    type Style = ();
66
67    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
68        State {
69            ids: Ids::new(id_gen),
70        }
71    }
72
73    fn style(&self) -> Self::Style {}
74
75    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
76        common_base::prof_span!("Controls::update");
77        let widget::UpdateArgs { state, ui, .. } = args;
78
79        let mut events = Vec::new();
80        let key_layout = &self.global_state.window.key_layout;
81
82        Rectangle::fill_with(args.rect.dim(), color::TRANSPARENT)
83            .xy(args.rect.xy())
84            .graphics_for(args.id)
85            .scroll_kids()
86            .scroll_kids_vertically()
87            .set(state.ids.window, ui);
88        Rectangle::fill_with([args.rect.w() / 2.0, args.rect.h()], color::TRANSPARENT)
89            .top_right()
90            .parent(state.ids.window)
91            .set(state.ids.window_r, ui);
92        Scrollbar::y_axis(state.ids.window)
93            .thickness(5.0)
94            .rgba(0.33, 0.33, 0.33, 1.0)
95            .set(state.ids.window_scrollbar, ui);
96
97        // Used for sequential placement in a flow-down pattern
98        let mut previous_element_id = None;
99        let mut keybindings_vec: Vec<GameInput> = GameInput::iter().collect();
100        keybindings_vec.sort();
101
102        let controls = &self.global_state.settings.controls;
103        if keybindings_vec.len() > state.ids.controls_texts.len()
104            || keybindings_vec.len() > state.ids.controls_buttons.len()
105        {
106            state.update(|s| {
107                s.ids
108                    .controls_texts
109                    .resize(keybindings_vec.len(), &mut ui.widget_id_generator());
110                s.ids
111                    .controls_buttons
112                    .resize(keybindings_vec.len(), &mut ui.widget_id_generator());
113            });
114        }
115
116        // Loop all existing keybindings and the ids for text and button widgets
117        for (game_input, (&text_id, &button_id)) in keybindings_vec.into_iter().zip(
118            state
119                .ids
120                .controls_texts
121                .iter()
122                .zip(state.ids.controls_buttons.iter()),
123        ) {
124            let (key_string, key_color) =
125                if self.global_state.window.remapping_keybindings == Some(game_input) {
126                    (
127                        self.localized_strings
128                            .get_msg("hud-settings-awaitingkey")
129                            .into_owned(),
130                        TEXT_COLOR,
131                    )
132                } else if let Some(key) = controls.get_binding(game_input) {
133                    (
134                        format!(
135                            "{} {}",
136                            key.display_string(key_layout),
137                            key.try_shortened(key_layout)
138                                .map_or("".to_owned(), |short| format!("({})", short))
139                        ),
140                        if controls.has_conflicting_bindings(key) {
141                            TEXT_BIND_CONFLICT_COLOR
142                        } else {
143                            TEXT_COLOR
144                        },
145                    )
146                } else {
147                    (
148                        self.localized_strings
149                            .get_msg("hud-settings-unbound")
150                            .into_owned(),
151                        ERROR_COLOR,
152                    )
153                };
154            let loc_key = self
155                .localized_strings
156                .get_msg(game_input.get_localization_key());
157            let text_widget = Text::new(&loc_key)
158                .color(TEXT_COLOR)
159                .font_id(self.fonts.cyri.conrod_id)
160                .font_size(self.fonts.cyri.scale(18));
161            let button_widget = Button::new()
162                .label(&key_string)
163                .label_color(key_color)
164                .label_font_id(self.fonts.cyri.conrod_id)
165                .label_font_size(self.fonts.cyri.scale(15))
166                .w(150.0)
167                .rgba(0.0, 0.0, 0.0, 0.0)
168                .border_rgba(0.0, 0.0, 0.0, 255.0)
169                .label_y(Relative::Scalar(3.0));
170            // Place top-left if it's the first text, else under the previous one
171            let text_widget = match previous_element_id {
172                None => text_widget.top_left_with_margins_on(state.ids.window, 10.0, 5.0),
173                Some(prev_id) => text_widget.down_from(prev_id, 10.0),
174            };
175            let text_width = text_widget.get_w(ui).unwrap_or(0.0);
176            text_widget.set(text_id, ui);
177            button_widget
178                .right_from(text_id, 350.0 - text_width)
179                .set(button_id, ui);
180
181            for _ in ui.widget_input(button_id).clicks().left() {
182                events.push(ChangeBinding(game_input));
183            }
184            for _ in ui.widget_input(button_id).clicks().right() {
185                events.push(RemoveBinding(game_input));
186            }
187            // Set the previous id to the current one for the next cycle
188            previous_element_id = Some(text_id);
189        }
190
191        // Reset the KeyBindings settings to the default settings
192        if let Some(prev_id) = previous_element_id {
193            if Button::image(self.imgs.button)
194                .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
195                .hover_image(self.imgs.button_hover)
196                .press_image(self.imgs.button_press)
197                .down_from(prev_id, 20.0)
198                .label(
199                    &self
200                        .localized_strings
201                        .get_msg("hud-settings-reset_keybinds"),
202                )
203                .label_font_size(self.fonts.cyri.scale(14))
204                .label_color(TEXT_COLOR)
205                .label_font_id(self.fonts.cyri.conrod_id)
206                .label_y(Relative::Scalar(2.0))
207                .set(state.ids.reset_controls_button, ui)
208                .was_clicked()
209            {
210                events.push(ResetKeyBindings);
211            }
212            previous_element_id = Some(state.ids.reset_controls_button)
213        }
214
215        let offset = ui
216            .widget_graph()
217            .widget(state.ids.window)
218            .and_then(|widget| {
219                widget
220                    .maybe_y_scroll_state
221                    .as_ref()
222                    .map(|scroll| scroll.offset)
223            })
224            .unwrap_or(0.0);
225
226        let keybind_helper_text = self
227            .localized_strings
228            .get_msg("hud-settings-keybind-helper");
229        let keybind_helper = Text::new(&keybind_helper_text)
230            .color(TEXT_COLOR)
231            .font_id(self.fonts.cyri.conrod_id)
232            .font_size(self.fonts.cyri.scale(18));
233        keybind_helper
234            .top_right_with_margins_on(state.ids.window, offset + 5.0, 10.0)
235            .set(state.ids.keybind_helper, ui);
236
237        // Add an empty text widget to simulate some bottom margin, because conrod sucks
238        if let Some(prev_id) = previous_element_id {
239            Rectangle::fill_with([1.0, 1.0], color::TRANSPARENT)
240                .down_from(prev_id, 10.0)
241                .set(state.ids.controls_alignment_rectangle, ui);
242        }
243
244        events
245    }
246}