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}