zng_ext_window/ime.rs
1use std::sync::Arc;
2
3use atomic::Atomic;
4use zng_app::{
5 event::{event, event_args},
6 widget::info::{WidgetInfo, WidgetInfoBuilder, WidgetPath},
7};
8use zng_layout::unit::PxRect;
9use zng_state_map::{StateId, static_id};
10use zng_txt::Txt;
11
12event_args! {
13 /// Arguments for [`IME_EVENT`].
14 pub struct ImeArgs {
15 /// The enabled text input widget.
16 pub target: WidgetPath,
17
18 /// The text, preview or actual insert.
19 pub txt: Txt,
20
21 /// Caret/selection within the `txt` when it is preview.
22 ///
23 /// The indexes are in char byte offsets and indicate where the caret or selection must be placed on
24 /// the inserted or preview `txt`, if not set the position is at the end of the insert.
25 ///
26 /// If this is `None` the text must [`commit`].
27 ///
28 /// [`commit`]: Self::commit
29 pub preview_caret: Option<(usize, usize)>,
30
31 ..
32
33 /// Target.
34 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
35 list.insert_wgt(&self.target);
36 }
37 }
38}
39impl ImeArgs {
40 /// If the text must be actually inserted.
41 ///
42 /// If `true` the [`txt`] must be actually inserted at the last non-preview caret/selection, the caret then must be moved to
43 /// after the inserted text.
44 ///
45 /// If `false` the widget must visually adjust the text and caret to look as if the input has committed, but the
46 /// actual text must not be altered, and if the [`txt`] is empty the previous caret/selection must be restored.
47 /// Usually the preview text is rendered with an underline effect, otherwise it has the same appearance as the
48 /// committed text.
49 ///
50 /// [`txt`]: Self::txt
51 pub fn commit(&self) -> bool {
52 self.preview_caret.is_none()
53 }
54}
55
56event! {
57 /// Input Method Editor event targeting a text input widget.
58 pub static IME_EVENT: ImeArgs;
59}
60
61/// IME extension methods for [`WidgetInfo`].
62///
63/// [`WidgetInfo`]: zng_app::widget::info::WidgetInfo
64pub trait WidgetInfoImeArea {
65 /// IME exclusion area in the window space.
66 ///
67 /// Widgets are IME targets when they are focused and subscribe to [`IME_EVENT`]. This
68 /// value is an area the IME window should avoid covering, by default it is the widget inner-bounds,
69 /// but the widget can override it using [`set_ime_area`].
70 ///
71 /// This value can change after every render update.
72 ///
73 /// [`set_ime_area`]: WidgetInfoBuilderImeArea::set_ime_area
74 fn ime_area(&self) -> PxRect;
75}
76
77/// IME extension methods for [`WidgetInfoBuilder`].
78///
79/// [`WidgetInfoBuilder`]: zng_app::widget::info::WidgetInfoBuilder
80pub trait WidgetInfoBuilderImeArea {
81 /// Set a custom [`ime_area`].
82 ///
83 /// The value can be updated every frame using interior mutability, without needing to rebuild the info.
84 ///
85 /// [`ime_area`]: WidgetInfoImeArea::ime_area
86 fn set_ime_area(&mut self, area: Arc<Atomic<PxRect>>);
87}
88
89static_id! {
90 static ref IME_AREA: StateId<Arc<Atomic<PxRect>>>;
91}
92
93impl WidgetInfoImeArea for WidgetInfo {
94 fn ime_area(&self) -> PxRect {
95 self.meta()
96 .get(*IME_AREA)
97 .map(|r| r.load(atomic::Ordering::Relaxed))
98 .unwrap_or_else(|| self.inner_bounds())
99 }
100}
101
102impl WidgetInfoBuilderImeArea for WidgetInfoBuilder {
103 fn set_ime_area(&mut self, area: Arc<Atomic<PxRect>>) {
104 self.set_meta(*IME_AREA, area);
105 }
106}