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