ferroui_controls/primitives/
access_text.rs1use crate::TopLevel;
2use crate::{ControlImpl, TextBlock, TextBlockImpl, TextBlockImplExt};
3use ferroui_base::input::{AccessKeyHandler, IAccessKeyHandler, InputElementImpl};
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::LayoutableImpl;
6use ferroui_base::media::text_formatting::TextLayout;
7use ferroui_base::media::{DrawingContext, IPen, Pen};
8use ferroui_base::reactive::ObservableExt;
9use ferroui_base::{
10 ferro_class, ferro_property, instantiate, AttachedProperty, FerroObjectExtensions, FerroObjectImpl,
11 FerroObject, FerroObjectImplExt, Point, Ref, StyledElementImpl, StyledProperty, Visual, VisualImpl, VisualImplExt,
12 VisualTreeAttachmentEventArgs,
13};
14use std::cell::RefCell;
15use std::rc::Rc;
16
17#[repr(C)]
20pub struct AccessText {
21 base: TextBlock,
22 access_key: RefCell<Option<String>>,
23 access_keys: RefCell<Option<Rc<dyn IAccessKeyHandler>>>,
24}
25
26ferro_class!(AccessText: TextBlock);
27ferroui_base::ferro_class_info!(AccessText { new: AccessText::new });
28
29ferroui_base::ferro_impl_classes!(AccessText: StyledElementImpl);
30ferroui_base::ferro_impl_classes!(AccessText: LayoutableImpl);
31ferroui_base::ferro_impl_classes!(AccessText: InteractiveImpl);
32ferroui_base::ferro_impl_classes!(AccessText: InputElementImpl);
33impl ControlImpl for AccessText {
34 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
35 crate::automation::peers::NoneAutomationPeer::new(this).upcast()
36 }
37}
38
39impl FerroObjectImpl for AccessText {
40 fn constructed(this: &Self) {
41 Self::parent_constructed(this);
42
43 let weak = this.to_ref().downgrade();
44 FerroObjectExtensions::get_observable::<StyledProperty<Option<String>>>(as_object(this), TextBlock::text_property()).subscribe_fn(move |text| {
45 if let Some(this) = weak.upgrade() {
46 this.text_changed(text.as_deref());
47 }
48 });
49 }
50}
51
52impl VisualImpl for AccessText {
53 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
54 Self::parent_on_attached_to_visual_tree(this, e);
55 let access_keys = TopLevel::get_top_level(Some(this)).and_then(|top_level| top_level.access_key_handler());
56 *this.access_keys.borrow_mut() = access_keys.clone();
57
58 if let (Some(access_keys), Some(access_key)) = (access_keys, this.access_key()) {
59 if !access_key.is_empty() {
60 access_keys.register(&access_key, &this.to_ref().upcast());
61 }
62 }
63 }
64
65 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
66 Self::parent_on_detached_from_visual_tree(this, e);
67
68 let access_keys = this.access_keys.borrow().clone();
69 if let Some(access_keys) = access_keys {
70 if this.access_key().is_some_and(|access_key| !access_key.is_empty()) {
71 access_keys.unregister(this);
72 *this.access_keys.borrow_mut() = None;
73 }
74 }
75 }
76}
77
78impl TextBlockImpl for AccessText {
79 fn render_core(this: &Self, context: &mut DrawingContext) {
81 Self::parent_render_core(this, context);
82
83 let underscore = this.text().and_then(|text| utf16_index_of_underscore(&text));
84
85 if let Some(underscore) = underscore {
86 if this.show_access_key() {
87 let rect = this.text_layout().hit_test_text_position(underscore);
88
89 let x1 = rect.left().round();
90 let x2 = rect.right().round();
91 let y = (rect.bottom() + this.layout_rounding_offset()).round() - 1.5;
94
95 let pen: Rc<dyn IPen> = Pen::with_brush(this.foreground(), 1.0).into();
96
97 context.draw_line(&pen, Point::new(x1, y), Point::new(x2, y));
98 }
99 }
100 }
101
102 fn create_text_layout(this: &Self, text: Option<&str>) -> Rc<TextLayout> {
103 let text = Self::remove_access_key_marker(text);
104 Self::parent_create_text_layout(this, text.as_deref())
105 }
106}
107
108fn as_object(object: &FerroObject) -> &FerroObject {
109 object
110}
111
112fn utf16_index_of_underscore(text: &str) -> Option<i32> {
114 text.find('_').map(|index| text[..index].encode_utf16().count() as i32)
115}
116
117ferroui_base::ferro_properties! { impl AccessText {
118 ferro_property!(
119 pub fn show_access_key_property() -> AttachedProperty<bool> {
121 AccessKeyHandler::show_access_key_property().add_owner::<AccessText>()
122 }
123 );
124} }
125
126impl AccessText {
127 fn static_constructor() {
128 Visual::affects_render::<AccessText>(&[Self::show_access_key_property().as_property()]);
129 }
130
131 pub fn construct() -> Self {
133 Self { base: TextBlock::construct(), access_key: RefCell::new(None), access_keys: RefCell::new(None) }
134 }
135
136 pub fn new() -> Ref<Self> {
138 instantiate(Self::construct())
139 }
140
141 pub fn access_key(&self) -> Option<String> {
143 self.access_key.borrow().clone()
144 }
145
146 pub fn show_access_key(&self) -> bool {
148 self.get_value(Self::show_access_key_property())
149 }
150
151 pub fn set_show_access_key(&self, value: bool) {
152 self.set_value(Self::show_access_key_property(), value)
153 }
154
155 pub fn remove_access_key_marker(text: Option<&str>) -> Option<String> {
158 let text = text?;
159 let mut text = text.to_owned();
160
161 if !text.is_empty() {
162 if let Some(index) = Self::find_access_key_marker(&text) {
163 if index < text.len() - 1 {
164 text.remove(index);
165 }
166 }
167 text = text.replace("__", "_");
168 }
169
170 Some(text)
171 }
172
173 fn find_access_key_marker(text: &str) -> Option<usize> {
176 let bytes = text.as_bytes();
177 let length = bytes.len();
178 let mut start_index = 0;
179
180 while start_index < length {
181 let index = start_index + bytes[start_index..].iter().position(|b| *b == b'_')?;
182 if index + 1 < length && bytes[index + 1] != b'_' {
183 return Some(index);
184 }
185 start_index = index + 2;
186 }
187
188 None
189 }
190
191 fn text_changed(&self, text: Option<&str>) {
193 let mut key: Option<String> = None;
194
195 if let Some(text) = text {
196 if let Some(underscore) = text.find('_') {
197 if let Some(rune) = text[underscore + 1..].chars().next() {
199 key = Some(rune.to_string());
200 }
201 }
202 }
203
204 *self.access_key.borrow_mut() = key;
205
206 let access_keys = self.access_keys.borrow().clone();
207 if let (Some(access_keys), Some(access_key)) = (access_keys, self.access_key()) {
208 if !access_key.is_empty() {
209 access_keys.register(&access_key, &self.to_ref().upcast());
210 }
211 }
212 }
213}