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