Skip to main content

fyrox_ui/
key.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! A set of editors for hot keys and key bindings. See [`HotKeyEditor`] and [`KeyBindingEditor`] widget's docs
22//! for more info and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::message::MessageData;
27use crate::text::Text;
28use crate::{
29    brush::Brush,
30    core::{
31        color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
32        visitor::prelude::*,
33    },
34    define_widget_deref,
35    draw::{CommandTexture, Draw, DrawingContext},
36    message::{KeyCode, KeyboardModifiers, MouseButton, UiMessage},
37    text::{TextBuilder, TextMessage},
38    widget::{Widget, WidgetBuilder, WidgetMessage},
39    BuildContext, Control, UiNode, UserInterface,
40};
41use fyrox_core::uuid_provider;
42use fyrox_core::variable::InheritableVariable;
43use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
44use serde::{Deserialize, Serialize};
45use std::fmt::{Display, Formatter};
46
47/// Hot key is a combination of a key code with an arbitrary set of keyboard modifiers (such as Ctrl, Shift, Alt keys).
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Reflect, Default, Visit)]
49pub enum HotKey {
50    /// Unset hot key. Does nothing. This is the default value.
51    #[default]
52    NotSet,
53    /// Some hot key.
54    Some {
55        /// Physical key code.
56        code: KeyCode,
57        /// A set of keyboard modifiers.
58        modifiers: KeyboardModifiers,
59    },
60}
61
62impl HotKey {
63    /// Creates a new hot key that consists of a single key, without any modifiers.
64    pub fn from_key_code(key: KeyCode) -> Self {
65        Self::Some {
66            code: key,
67            modifiers: Default::default(),
68        }
69    }
70
71    /// Creates a new hot key, that consists of combination `Ctrl + Key`.
72    pub fn ctrl_key(key: KeyCode) -> Self {
73        Self::Some {
74            code: key,
75            modifiers: KeyboardModifiers {
76                control: true,
77                ..Default::default()
78            },
79        }
80    }
81
82    /// Creates a new hot key, that consists of combination `Shift + Key`.
83    pub fn shift_key(key: KeyCode) -> Self {
84        Self::Some {
85            code: key,
86            modifiers: KeyboardModifiers {
87                shift: true,
88                ..Default::default()
89            },
90        }
91    }
92
93    /// Creates a new hot key, that consists of combination `Alt + Key`.
94    pub fn alt_key(key: KeyCode) -> Self {
95        Self::Some {
96            code: key,
97            modifiers: KeyboardModifiers {
98                shift: true,
99                ..Default::default()
100            },
101        }
102    }
103}
104
105impl Display for HotKey {
106    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
107        match self {
108            HotKey::NotSet => f.write_str("Not Set"),
109            HotKey::Some { code, modifiers } => {
110                if modifiers.control {
111                    f.write_str("Ctrl+")?;
112                }
113                if modifiers.alt {
114                    f.write_str("Alt+")?;
115                }
116                if modifiers.shift {
117                    f.write_str("Shift+")?;
118                }
119                if modifiers.system {
120                    f.write_str("Sys+")?;
121                }
122                write!(f, "{}", code.as_ref())
123            }
124        }
125    }
126}
127
128/// A set of messages, that is used to alternate the state of [`HotKeyEditor`] widget or to listen to its changes.
129#[derive(Debug, Clone, PartialEq)]
130pub enum HotKeyEditorMessage {
131    /// A message, that is either used to modify the current value of a [`HotKey`] widget instance (with [`crate::message::MessageDirection::ToWidget`])
132    /// or to listen to its changes (with [`crate::message::MessageDirection::FromWidget`]).
133    Value(HotKey),
134}
135impl MessageData for HotKeyEditorMessage {}
136
137/// Hot key editor is used to provide a unified way of editing an arbitrary combination of modifiers keyboard keys (such
138/// as Ctrl, Shift, Alt) with any other key. It could be used if you need a simple way to add an editor for [`HotKey`].
139///
140/// ## Examples
141///
142/// The following example creates a new hot key editor with a `Ctrl+C` hot key as default value:
143///
144/// ```rust
145/// # use fyrox_ui::{
146/// #     core::pool::Handle,
147/// #     key::{HotKey, HotKeyEditor, HotKeyEditorBuilder},
148/// #     message::{KeyCode, KeyboardModifiers},
149/// #     widget::WidgetBuilder,
150/// #     BuildContext, UiNode,
151/// # };
152/// #
153/// fn create_hot_key_editor(ctx: &mut BuildContext) -> Handle<HotKeyEditor> {
154///     HotKeyEditorBuilder::new(WidgetBuilder::new())
155///         .with_value(
156///             // Ctrl+C hot key.
157///             HotKey::Some {
158///                 code: KeyCode::KeyC,
159///                 modifiers: KeyboardModifiers {
160///                     control: true,
161///                     ..Default::default()
162///                 },
163///             },
164///         )
165///         .build(ctx)
166/// }
167/// ```
168///
169/// ## Messages
170///
171/// Use [`HotKeyEditorMessage`] message to alternate the state of a hot key widget, or to listen to its changes.
172#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
173#[reflect(derived_type = "UiNode")]
174pub struct HotKeyEditor {
175    widget: Widget,
176    text: InheritableVariable<Handle<Text>>,
177    value: InheritableVariable<HotKey>,
178    editing: InheritableVariable<bool>,
179}
180
181impl ConstructorProvider<UiNode, UserInterface> for HotKeyEditor {
182    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
183        GraphNodeConstructor::new::<Self>()
184            .with_variant("Hot Key Editor", |ui| {
185                HotKeyEditorBuilder::new(WidgetBuilder::new().with_name("Hot Key Editor"))
186                    .build(&mut ui.build_ctx())
187                    .to_base()
188                    .into()
189            })
190            .with_group("Input")
191    }
192}
193
194define_widget_deref!(HotKeyEditor);
195
196impl HotKeyEditor {
197    fn set_editing(&mut self, editing: bool, ui: &UserInterface) {
198        self.editing.set_value_and_mark_modified(editing);
199        let text = if *self.editing {
200            "[WAITING INPUT]".to_string()
201        } else {
202            format!("{}", *self.value)
203        };
204        ui.send(*self.text, TextMessage::Text(text));
205    }
206}
207
208uuid_provider!(HotKeyEditor = "7bc49843-1302-4e36-b901-63af5cea6c60");
209
210impl Control for HotKeyEditor {
211    fn draw(&self, drawing_context: &mut DrawingContext) {
212        // Make background clickable.
213        drawing_context.push_rect_filled(&self.bounding_rect(), None);
214        drawing_context.commit(
215            self.clip_bounds(),
216            Brush::Solid(Color::TRANSPARENT),
217            CommandTexture::None,
218            &self.material,
219            None,
220        );
221    }
222
223    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
224        self.widget.handle_routed_message(ui, message);
225
226        if let Some(msg) = message.data::<WidgetMessage>() {
227            match msg {
228                WidgetMessage::KeyDown(key) => {
229                    if *self.editing
230                        && !matches!(
231                            *key,
232                            KeyCode::ControlLeft
233                                | KeyCode::ControlRight
234                                | KeyCode::ShiftLeft
235                                | KeyCode::ShiftRight
236                                | KeyCode::AltLeft
237                                | KeyCode::AltRight
238                        )
239                    {
240                        ui.send(
241                            self.handle,
242                            HotKeyEditorMessage::Value(HotKey::Some {
243                                code: *key,
244                                modifiers: ui.keyboard_modifiers,
245                            }),
246                        );
247
248                        message.set_handled(true);
249                    }
250                }
251                WidgetMessage::MouseDown { button, .. } => {
252                    if *button == MouseButton::Left {
253                        if *self.editing {
254                            self.set_editing(false, ui);
255                        } else {
256                            self.set_editing(true, ui);
257                        }
258                    }
259                }
260                WidgetMessage::Unfocus => {
261                    if *self.editing {
262                        self.set_editing(false, ui);
263                    }
264                }
265                _ => (),
266            }
267        }
268
269        if let Some(HotKeyEditorMessage::Value(value)) = message.data_for(self.handle) {
270            if value != &*self.value {
271                self.value.set_value_and_mark_modified(value.clone());
272                ui.send(*self.text, TextMessage::Text(format!("{}", *self.value)));
273                ui.send_message(message.reverse());
274            }
275        }
276    }
277}
278
279/// Hot key editor builder creates [`HotKeyEditor`] widget instances and adds them to the user interface.
280pub struct HotKeyEditorBuilder {
281    widget_builder: WidgetBuilder,
282    value: HotKey,
283}
284
285impl HotKeyEditorBuilder {
286    /// Creates a new hot key editor builder.
287    pub fn new(widget_builder: WidgetBuilder) -> Self {
288        Self {
289            widget_builder,
290            value: HotKey::NotSet,
291        }
292    }
293
294    /// Sets the desired default value of the hot key editor.
295    pub fn with_value(mut self, hot_key: HotKey) -> Self {
296        self.value = hot_key;
297        self
298    }
299
300    /// Finishes widget building and adds it to the user interface, returning a handle to the new instance.
301    pub fn build(self, ctx: &mut BuildContext) -> Handle<HotKeyEditor> {
302        let text = TextBuilder::new(WidgetBuilder::new())
303            .with_text(format!("{}", self.value))
304            .build(ctx);
305
306        let editor = HotKeyEditor {
307            widget: self.widget_builder.with_child(text).build(ctx),
308            text: text.into(),
309            editing: false.into(),
310            value: self.value.into(),
311        };
312
313        ctx.add(editor)
314    }
315}
316
317/// Key binding is a simplified version of [`HotKey`] that consists of a single physical key code. It is usually
318/// used for "unconditional" (independent of modifier keys state) triggering of some action.
319#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Reflect, Visit, Default)]
320pub enum KeyBinding {
321    /// Unset key binding. Does nothing.
322    #[default]
323    NotSet,
324    /// Some physical key binding.
325    Some(KeyCode),
326}
327
328impl PartialEq<KeyCode> for KeyBinding {
329    fn eq(&self, other: &KeyCode) -> bool {
330        match self {
331            KeyBinding::NotSet => false,
332            KeyBinding::Some(code) => code == other,
333        }
334    }
335}
336
337impl KeyBinding {
338    /// Creates a new key binding from a physical key code.
339    pub fn from_key_code(key: KeyCode) -> Self {
340        Self::Some(key)
341    }
342}
343
344impl Display for KeyBinding {
345    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
346        match self {
347            Self::NotSet => f.write_str("Not Set"),
348            Self::Some(code) => write!(f, "{}", code.as_ref()),
349        }
350    }
351}
352
353/// A set of messages, that is used to modify [`KeyBindingEditor`] state or to listen to its changes.
354#[derive(Debug, Clone, PartialEq)]
355pub enum KeyBindingEditorMessage {
356    /// A message, that is used to fetch a new value of a key binding, or to set a new one.
357    Value(KeyBinding),
358}
359impl MessageData for KeyBindingEditorMessage {}
360
361/// Key binding editor is used to provide a unified way of setting a key binding.
362///
363/// ## Examples
364///
365/// The following example creates a new key binding editor with a `W` key binding as a value.
366///
367/// ```rust
368/// # use fyrox_ui::{
369/// #     core::pool::Handle,
370/// #     key::{KeyBinding, KeyBindingEditor, KeyBindingEditorBuilder},
371/// #     message::KeyCode,
372/// #     widget::WidgetBuilder,
373/// #     BuildContext, UiNode,
374/// # };
375/// #
376/// fn create_key_binding_editor(ctx: &mut BuildContext) -> Handle<KeyBindingEditor> {
377///     KeyBindingEditorBuilder::new(WidgetBuilder::new())
378///         .with_value(KeyBinding::Some(KeyCode::KeyW))
379///         .build(ctx)
380/// }
381/// ```
382///
383/// ## Messages
384///
385/// Use [`KeyBindingEditorMessage`] message to alternate the state of a key binding widget, or to listen to its changes.
386#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
387#[reflect(derived_type = "UiNode")]
388pub struct KeyBindingEditor {
389    widget: Widget,
390    text: InheritableVariable<Handle<Text>>,
391    value: InheritableVariable<KeyBinding>,
392    editing: InheritableVariable<bool>,
393}
394
395impl ConstructorProvider<UiNode, UserInterface> for KeyBindingEditor {
396    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
397        GraphNodeConstructor::new::<Self>()
398            .with_variant("Key Binding Editor", |ui| {
399                KeyBindingEditorBuilder::new(WidgetBuilder::new().with_name("Key Binding Editor"))
400                    .build(&mut ui.build_ctx())
401                    .to_base()
402                    .into()
403            })
404            .with_group("Input")
405    }
406}
407
408define_widget_deref!(KeyBindingEditor);
409
410impl KeyBindingEditor {
411    fn set_editing(&mut self, editing: bool, ui: &UserInterface) {
412        self.editing.set_value_and_mark_modified(editing);
413        ui.send(
414            *self.text,
415            TextMessage::Text(if *self.editing {
416                "[WAITING INPUT]".to_string()
417            } else {
418                format!("{}", *self.value)
419            }),
420        );
421    }
422}
423
424uuid_provider!(KeyBindingEditor = "150113ce-f95e-4c76-9ac9-4503e78b960f");
425
426impl Control for KeyBindingEditor {
427    fn draw(&self, drawing_context: &mut DrawingContext) {
428        // Make background clickable.
429        drawing_context.push_rect_filled(&self.bounding_rect(), None);
430        drawing_context.commit(
431            self.clip_bounds(),
432            Brush::Solid(Color::TRANSPARENT),
433            CommandTexture::None,
434            &self.material,
435            None,
436        );
437    }
438
439    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
440        self.widget.handle_routed_message(ui, message);
441
442        if let Some(msg) = message.data::<WidgetMessage>() {
443            match msg {
444                WidgetMessage::KeyDown(key) => {
445                    ui.send(
446                        self.handle,
447                        KeyBindingEditorMessage::Value(KeyBinding::Some(*key)),
448                    );
449
450                    message.set_handled(true);
451                }
452                WidgetMessage::MouseDown { button, .. } => {
453                    if *button == MouseButton::Left {
454                        if *self.editing {
455                            self.set_editing(false, ui);
456                        } else {
457                            self.set_editing(true, ui);
458                        }
459                    }
460                }
461                WidgetMessage::Unfocus => {
462                    if *self.editing {
463                        self.set_editing(false, ui);
464                    }
465                }
466                _ => (),
467            }
468        }
469
470        if let Some(KeyBindingEditorMessage::Value(value)) = message.data_for(self.handle) {
471            if value != &*self.value {
472                self.value.set_value_and_mark_modified(value.clone());
473                ui.send(*self.text, TextMessage::Text(format!("{}", *self.value)));
474                ui.send_message(message.reverse());
475            }
476        }
477    }
478}
479
480/// Key binding editor builder is used to create [`KeyBindingEditor`] widgets and add them to the user interface.
481pub struct KeyBindingEditorBuilder {
482    widget_builder: WidgetBuilder,
483    value: KeyBinding,
484}
485
486impl KeyBindingEditorBuilder {
487    /// Creates a new key binding editor builder.
488    pub fn new(widget_builder: WidgetBuilder) -> Self {
489        Self {
490            widget_builder,
491            value: KeyBinding::NotSet,
492        }
493    }
494
495    /// Sets the desired key binding value.
496    pub fn with_value(mut self, key_binding: KeyBinding) -> Self {
497        self.value = key_binding;
498        self
499    }
500
501    /// Finishes widget building and adds the new widget instance to the user interface, returning a handle of it.
502    pub fn build(self, ctx: &mut BuildContext) -> Handle<KeyBindingEditor> {
503        let text = TextBuilder::new(WidgetBuilder::new())
504            .with_text(format!("{}", self.value))
505            .build(ctx);
506
507        let editor = KeyBindingEditor {
508            widget: self.widget_builder.with_child(text).build(ctx),
509            text: text.into(),
510            editing: false.into(),
511            value: self.value.into(),
512        };
513
514        ctx.add(editor)
515    }
516}
517
518#[cfg(test)]
519mod test {
520    use crate::key::{HotKeyEditorBuilder, KeyBindingEditorBuilder};
521    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
522
523    #[test]
524    fn test_deletion() {
525        test_widget_deletion(|ctx| KeyBindingEditorBuilder::new(WidgetBuilder::new()).build(ctx));
526        test_widget_deletion(|ctx| HotKeyEditorBuilder::new(WidgetBuilder::new()).build(ctx));
527    }
528}