kas_widgets/
text.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Text widgets
7
8use kas::prelude::*;
9use kas::text::format::FormattableText;
10use kas::theme::{self, TextClass};
11
12#[impl_self]
13mod Text {
14    /// A text label (derived from data)
15    ///
16    /// `Text` derives its contents from input data. Use [`Label`](crate::Label)
17    /// instead for fixed contents.
18    ///
19    /// See also macros [`format_data`](super::format_data) and
20    /// [`format_value`](super::format_value) which construct a
21    /// `Text` widget.
22    ///
23    /// Vertical alignment defaults to centred, horizontal alignment depends on
24    /// the script direction if not specified. Line-wrapping is enabled by
25    /// default.
26    #[widget]
27    #[layout(self.text)]
28    pub struct Text<A, T: Default + FormattableText + 'static> {
29        core: widget_core!(),
30        text: theme::Text<T>,
31        text_fn: Box<dyn Fn(&ConfigCx, &A) -> T>,
32    }
33
34    impl Default for Self
35    where
36        for<'a> &'a A: Into<T>,
37    {
38        fn default() -> Self {
39            Text {
40                core: Default::default(),
41                text: theme::Text::new(T::default(), TextClass::Label(true)),
42                text_fn: Box::new(|_, data| data.into()),
43            }
44        }
45    }
46
47    impl Self {
48        /// Construct with a data binding
49        #[inline]
50        pub fn new(text_fn: impl Fn(&ConfigCx, &A) -> T + 'static) -> Self {
51            Text {
52                core: Default::default(),
53                text: theme::Text::new(T::default(), TextClass::Label(true)),
54                text_fn: Box::new(text_fn),
55            }
56        }
57
58        /// Get text class
59        #[inline]
60        pub fn class(&self) -> TextClass {
61            self.text.class()
62        }
63
64        /// Set text class
65        ///
66        /// Default: `TextClass::Label(true)`
67        #[inline]
68        pub fn set_class(&mut self, class: TextClass) {
69            self.text.set_class(class);
70        }
71
72        /// Set text class (inline)
73        ///
74        /// Default: `TextClass::Label(true)`
75        #[inline]
76        pub fn with_class(mut self, class: TextClass) -> Self {
77            self.text.set_class(class);
78            self
79        }
80
81        /// Get whether line-wrapping is enabled
82        #[inline]
83        pub fn wrap(&self) -> bool {
84            self.class().multi_line()
85        }
86
87        /// Enable/disable line wrapping
88        ///
89        /// This is equivalent to `label.set_class(TextClass::Label(wrap))`.
90        ///
91        /// By default this is enabled.
92        #[inline]
93        pub fn set_wrap(&mut self, wrap: bool) {
94            self.text.set_class(TextClass::Label(wrap));
95        }
96
97        /// Enable/disable line wrapping (inline)
98        #[inline]
99        pub fn with_wrap(mut self, wrap: bool) -> Self {
100            self.text.set_class(TextClass::Label(wrap));
101            self
102        }
103
104        /// Get read access to the text object
105        #[inline]
106        pub fn text(&self) -> &theme::Text<T> {
107            &self.text
108        }
109
110        /// Read the text contents as an `str`
111        #[inline]
112        pub fn as_str(&self) -> &str {
113            self.text.as_str()
114        }
115    }
116
117    impl Layout for Self {
118        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
119            self.text
120                .set_rect(cx, rect, hints.combine(AlignHints::VERT_CENTER));
121        }
122    }
123
124    impl Tile for Self {
125        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
126            Role::Label(self.text.as_str())
127        }
128    }
129
130    impl Events for Self {
131        type Data = A;
132
133        fn configure(&mut self, cx: &mut ConfigCx) {
134            cx.text_configure(&mut self.text);
135        }
136
137        fn update(&mut self, cx: &mut ConfigCx, data: &A) {
138            let text = (self.text_fn)(cx, data);
139            if text.as_str() == self.text.as_str() {
140                // NOTE(opt): avoiding re-preparation of text is a *huge*
141                // optimisation. Move into kas-text?
142                return;
143            }
144            self.text.set_text(text);
145            let action = self.text.reprepare_action();
146            cx.action(self, action);
147        }
148    }
149}
150
151/// A [`Text`] widget which formats a value from input
152///
153/// Examples:
154/// ```
155/// use kas_widgets::Text;
156/// let _: Text<i32, _> = kas_widgets::format_data!(data, "Data value: {data}");
157/// let _ = kas_widgets::format_data!(data: &i32, "Data value: {data}");
158/// ```
159// TODO: a more fancy macro could determine the data fields used and wrap with
160// a node testing for changes to these fields before calling update().
161#[macro_export]
162macro_rules! format_data {
163    ($data:ident, $($arg:tt)*) => {
164        $crate::Text::new(move |_, $data| format!($($arg)*))
165    };
166    ($data:ident : $data_ty:ty , $($arg:tt)*) => {
167        $crate::Text::new(move |_, $data : $data_ty| format!($($arg)*))
168    };
169}
170
171/// A [`Text`] widget which formats a value from input
172///
173/// Example:
174/// ```
175/// use kas_widgets::Text;
176/// let _: Text<i32, String> = kas_widgets::format_value!("Data value: {}");
177/// ```
178#[macro_export]
179macro_rules! format_value {
180    ($($arg:tt)*) => {
181        $crate::Text::new(move |_, data| format!($($arg)*, data))
182    };
183}