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