Skip to main content

ferroui_diagnostics/diagnostics/controls/
commit_text_box.rs

1//! Port of `Diagnostics/Controls/CommitTextBox.cs`.
2//!
3//! The original notes that `UpdateSourceTrigger` and the validation rules of a binding could
4//! remove the need for this control.
5
6use ferroui_base::FerroObjectImplExt;
7use ferroui_base::input::InputElementImplExt;
8use ferroui_base::input::{FocusChangedEventArgs, InputElementImpl, Key, KeyEventArgs};
9use ferroui_base::interactivity::InteractiveImpl;
10use ferroui_base::layout::LayoutableImpl;
11use ferroui_base::{
12    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, DirectProperty, FerroObjectImpl,
13    FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, TypeInfo, VisualImpl,
14};
15use ferroui_controls::primitives::TemplatedControlImpl;
16use ferroui_controls::{ControlImpl, DataValidationErrors, TextBox, TextBoxImpl};
17use std::cell::RefCell;
18
19/// A text box whose text is committed to [`committed_text`](Self::committed_text) when Enter is
20/// pressed or the focus leaves it, and reverted when Escape is pressed: the text of an edit that
21/// does not convert is never committed.
22#[repr(C)]
23pub struct CommitTextBox {
24    base: TextBox,
25    committed_text: RefCell<Option<String>>,
26}
27
28ferro_class!(CommitTextBox: TextBox);
29ferro_impl_classes!(
30    CommitTextBox: VisualImpl,
31    LayoutableImpl,
32    InteractiveImpl,
33    ControlImpl,
34    TemplatedControlImpl,
35    TextBoxImpl
36);
37ferro_class_info!(CommitTextBox { new: CommitTextBox::new });
38
39ferro_properties! {
40    impl CommitTextBox {
41        /// Defines the `CommittedText` property.
42        pub fn committed_text_property() -> DirectProperty<CommitTextBox, Option<String>> {
43            FerroProperty::register_direct::<CommitTextBox, _>(
44                "CommittedText",
45                |o| o.committed_text(),
46                Some(|o, v| o.set_committed_text(v)),
47                None,
48            )
49        }
50    }
51}
52
53impl StyledElementImpl for CommitTextBox {
54    fn style_key_override(_this: &Self) -> &'static TypeInfo {
55        TextBox::TYPE
56    }
57}
58
59impl FerroObjectImpl for CommitTextBox {
60    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
61        Self::parent_on_property_changed(this, change);
62
63        if change.property() == Self::committed_text_property().as_property() {
64            this.set_text(this.committed_text().as_deref());
65        }
66    }
67}
68
69impl InputElementImpl for CommitTextBox {
70    fn on_key_up(this: &Self, e: &KeyEventArgs) {
71        Self::parent_on_key_up(this, e);
72
73        match e.key {
74            Key::Enter => {
75                this.try_commit();
76
77                e.set_handled(true);
78            }
79
80            Key::Escape => {
81                this.cancel();
82
83                e.set_handled(true);
84            }
85
86            _ => {}
87        }
88    }
89
90    fn on_lost_focus(this: &Self, e: &FocusChangedEventArgs) {
91        Self::parent_on_lost_focus(this, e);
92
93        this.try_commit();
94    }
95}
96
97impl CommitTextBox {
98    pub fn construct() -> Self {
99        Self { base: TextBox::construct(), committed_text: RefCell::new(None) }
100    }
101
102    pub fn new() -> Ref<Self> {
103        instantiate(Self::construct())
104    }
105
106    pub fn committed_text(&self) -> Option<String> {
107        self.committed_text.borrow().clone()
108    }
109
110    pub fn set_committed_text(&self, value: Option<String>) {
111        self.set_and_raise(Self::committed_text_property(), &self.committed_text, value);
112    }
113
114    fn cancel(&self) {
115        self.set_text(self.committed_text().as_deref());
116        DataValidationErrors::clear_errors(self);
117    }
118
119    fn try_commit(&self) {
120        if !DataValidationErrors::get_has_errors(self) {
121            self.set_committed_text(self.text());
122        } else {
123            self.set_text(self.committed_text().as_deref());
124            DataValidationErrors::clear_errors(self);
125        }
126    }
127}