zng_ext_window/
ime.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::sync::Arc;

use atomic::Atomic;
use zng_app::{
    event::{event, event_args},
    widget::info::{WidgetInfo, WidgetInfoBuilder, WidgetPath},
};
use zng_layout::unit::PxRect;
use zng_state_map::{static_id, StateId};
use zng_txt::Txt;

event_args! {
    /// Arguments for [`IME_EVENT`].
    pub struct ImeArgs {
        /// The enabled text input widget.
        pub target: WidgetPath,

        /// The text, preview or actual insert.
        pub txt: Txt,

        /// Caret/selection within the `txt` when it is preview.
        ///
        /// The indexes are in char byte offsets and indicate where the caret or selection must be placed on
        /// the inserted or preview `txt`, if not set the position is at the end of the insert.
        ///
        /// If this is `None` the text must [`commit`].
        ///
        /// [`commit`]: Self::commit
        pub preview_caret: Option<(usize, usize)>,

        ..

        /// Target.
        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
            list.insert_wgt(&self.target);
        }
    }
}
impl ImeArgs {
    /// If the text must be actually inserted.
    ///
    /// If `true` the [`txt`] must be actually inserted at the last non-preview caret/selection, the caret then must be moved to
    /// after the inserted text.
    ///
    /// If `false` the widget must visually adjust the text and caret to look as if the input has committed, but the
    /// actual text must not be altered, and if the [`txt`] is empty the previous caret/selection must be restored.
    /// Usually the preview text is rendered with an underline effect, otherwise it has the same appearance as the
    /// committed text.
    ///
    /// [`txt`]: Self::txt
    /// [`caret`]: Self::caret
    pub fn commit(&self) -> bool {
        self.preview_caret.is_none()
    }
}

event! {
    /// Input Method Editor event targeting a text input widget.
    pub static IME_EVENT: ImeArgs;
}

/// IME extension methods for [`WidgetInfo`].
///
/// [`WidgetInfo`]: zng_app::widget::info::WidgetInfo
pub trait WidgetInfoImeArea {
    /// IME exclusion area in the window space.
    ///
    /// Widgets are IME targets when they are focused and subscribe to [`IME_EVENT`]. This
    /// value is an area the IME window should avoid covering, by default it is the widget inner-bounds,
    /// but the widget can override it using [`set_ime_area`].
    ///
    /// This value can change after every render update.
    ///
    /// [`set_ime_area`]: WidgetInfoBuilderImeArea::set_ime_area
    fn ime_area(&self) -> PxRect;
}

/// IME extension methods for [`WidgetInfoBuilder`].
///
/// [`WidgetInfoBuilder`]: zng_app::widget::info::WidgetInfoBuilder
pub trait WidgetInfoBuilderImeArea {
    /// Set a custom [`ime_area`].
    ///
    /// The value can be updated every frame using interior mutability, without needing to rebuild the info.
    ///
    /// [`ime_area`]: WidgetInfoImeArea::ime_area
    fn set_ime_area(&mut self, area: Arc<Atomic<PxRect>>);
}

static_id! {
    static ref IME_AREA: StateId<Arc<Atomic<PxRect>>>;
}

impl WidgetInfoImeArea for WidgetInfo {
    fn ime_area(&self) -> PxRect {
        self.meta()
            .get(*IME_AREA)
            .map(|r| r.load(atomic::Ordering::Relaxed))
            .unwrap_or_else(|| self.inner_bounds())
    }
}

impl WidgetInfoBuilderImeArea for WidgetInfoBuilder {
    fn set_ime_area(&mut self, area: Arc<Atomic<PxRect>>) {
        self.set_meta(*IME_AREA, area);
    }
}