1use std::convert::TryFrom;
14use std::sync::LazyLock;
15
16use crate::TreeHelper;
17use crate::tree::{Tree, NodeId};
18use crate::interface::{Action, Element, Model, View};
19use crate::interface::view::{self, input};
20use crate::interface::controller::{self, controls, Controls};
21use super::{Widget, Frame};
22
23pub use self::builder::FieldBuilder as Builder;
24
25pub type Field <'element> = Widget <'element,
26 controller::component::Cursor, String, view::component::Body>;
27
28pub static CONTROLS : LazyLock <Controls> = LazyLock::new (||
33 controls::Builder::new()
34 .buttons (vec![
35 controls::button::Builtin::FieldBackspace,
36 controls::button::Builtin::FormSubmitCallback
37 ].into_iter().map (Into::into).collect::<Vec <_>>().into())
38 .build()
39);
40
41pub fn line_entry (
46 input : &input::Text,
47 elements : &Tree <Element>,
48 node_id : &NodeId,
49 action_buffer : &mut Vec <(NodeId, Action)>
50) {
51 use std::convert::TryInto;
52 log::trace!("line_entry...");
53 let Widget (cursor, text, _) = Field::try_get (elements, node_id).unwrap();
54 if let Some (new_text) = match input {
55 input::Text::Char (ch) => match ch {
56 '\t' | '\n' => None,
57 c if c.is_ascii_control() => None,
58 c => if cursor.length.is_none_or (|length| (text.len() as u32) < length) {
59 let pushchar = ||{
60 let mut text = text.clone();
61 text.push (*ch);
62 Some (text)
63 };
64 if let Ok (keycode) = (*c).try_into() {
65 if cursor.ignore.contains (&keycode) {
66 None
67 } else {
68 pushchar()
69 }
70 } else {
71 pushchar()
72 }
73 } else {
74 None
75 }
76 }
77 input::Text::String (string) => {
78 let remaining = cursor.length.map_or_else (|| string.len(),
79 |length| (length as usize).saturating_sub (text.len()));
80 if remaining > 0 {
81 let mut text = text.clone();
82 text.extend (string.chars().take (remaining));
83 Some (text)
84 } else {
85 None
86 }
87 }
88 } {
89 set_contents (elements, node_id, new_text, None, action_buffer);
90 }
91 log::trace!("...line_entry");
92}
93
94pub fn backspace (
96 _ : &controls::button::Release,
97 elements : &Tree <Element>,
98 node_id : &NodeId,
99 action_buffer : &mut Vec <(NodeId, Action)>
100) {
101 log::trace!("backspace...");
102 let Widget (_, text, _) = Field::try_get (elements, node_id).unwrap();
103 if text.len() > 0 {
104 let mut new_text = text.clone();
105 let _ = new_text.pop().unwrap();
106 set_contents (elements, node_id, new_text, None, action_buffer);
107 }
108 log::trace!("...backspace");
109}
110
111pub fn set_contents (
117 elements : &Tree <Element>,
118 field_id : &NodeId,
119 contents : String,
120 focused : Option <bool>,
121 action_buffer : &mut Vec <(NodeId, Action)>
122) {
123 let new_body = {
124 let field = elements.get_element (field_id);
125 let Widget (cursor, _, _) = Field::try_get (elements, field_id).unwrap();
126 let Widget (_, _, canvas) =
127 Frame::try_get (elements, elements.get_parent_id (field_id)).unwrap();
128 let focused = focused
129 .unwrap_or (field.controller.state == controller::State::Focused);
130 body (canvas, cursor, &contents, focused)
131 };
132 { let update_text =
134 Box::new (|model : &mut Model| model.component = contents.into());
135 action_buffer.push ((field_id.clone(), Action::ModifyModel (update_text)));
136 }
137 { let update_body =
139 Box::new (|view : &mut View| view.component = new_body.into());
140 action_buffer.push ((field_id.clone(), Action::ModifyView (update_body)));
141 }
142}
143
144pub (crate) fn body (
149 canvas : &view::component::Canvas,
150 cursor : &controller::component::Cursor,
151 text : &str,
152 focused : bool
153) -> view::component::Body {
154 log::trace!("body...");
156 let body = {
157 let mut body = {
158 let (body_width, _body_height) = canvas.body_wh();
159 let end = text.len();
160 let begin = {
161 let mut begin = text.len().saturating_sub (body_width as usize-1);
162 if !focused {
163 begin = begin.saturating_sub (1);
164 }
165 begin
166 };
167 text[begin..end].to_string()
168 };
169 if focused {
170 let caret = char::try_from (cursor.caret).unwrap();
171 body.push (caret);
172 }
173 view::component::Body (body)
174 };
175 log::trace!("...body");
176 body
177}
178
179mod builder {
184 use derive_builder::Builder;
185 use crate::prelude::*;
186
187 #[derive(Builder)]
188 #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
189 struct Field <'a, A : Application> {
190 elements : &'a Tree <Element>,
191 parent_id : &'a NodeId,
192 #[builder(default)]
193 appearances : Appearances,
194 #[builder(default)]
195 bindings : Option <&'a Bindings <A>>,
196 #[builder(default)]
197 callback_id : Option <application::CallbackId>,
198 #[builder(default)]
199 contents : Option <String>,
200 #[builder(default)]
201 cursor : Cursor,
202 #[builder(default)]
203 frame_anchor : Alignment,
204 #[builder(default)]
205 frame_appearances : Appearances,
206 #[builder(default)]
207 frame_area : Area,
208 #[builder(default)]
209 frame_bindings : Option <&'a Bindings <A>>,
210 #[builder(default)]
211 frame_border : Option <Border>,
212 #[builder(default)]
213 frame_clear_color : Option <canvas::ClearColor>,
214 #[builder(default)]
215 frame_offset : Offset,
216 #[builder(default)]
219 frame_tiled : bool,
220 #[builder(default = "size::Unsigned::Absolute (24)")]
221 frame_width : size::Unsigned,
222 #[builder(default)]
224 text_control : Option <controls::Text>
225 }
226
227 impl <'a, A : Application> FieldBuilder <'a, A> {
228 pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
229 FieldBuilder {
230 elements: Some (elements),
231 parent_id: Some (parent_id),
232 appearances: None,
233 bindings: None,
234 callback_id: None,
235 contents: None,
236 cursor: None,
237 frame_anchor: None,
238 frame_appearances: None,
239 frame_area: None,
240 frame_bindings: None,
241 frame_border: None,
242 frame_clear_color: None,
243 frame_offset: None,
244 frame_tiled: None,
245 frame_width: None,
246 text_control: None
247 }
248 }
249 }
250
251 impl <A : Application> BuildActions for FieldBuilder <'_, A> {
252 fn build_actions (self) -> Vec<(NodeId, Action)> {
253 use std::convert::TryInto;
254 use crate::tree::{InsertBehavior, Node};
255 use controller::component::layout;
256 use view::coordinates;
257 log::trace!("build actions...");
258 let Field {
259 elements, parent_id, appearances, bindings, callback_id, contents,
260 cursor, frame_anchor, frame_appearances, frame_area, frame_bindings,
261 frame_border, frame_clear_color, frame_offset, frame_tiled, frame_width,
262 text_control
263 } = self.build()
264 .map_err(|err| log::error!("frame builder error: {err:?}")).unwrap();
265 let bindings_empty = Bindings::empty();
266 let bindings = {
267 let mut bindings =
268 bindings.unwrap_or (&bindings_empty).get_bindings (&super::CONTROLS);
269 bindings.text = text_control.map (Into::into)
270 .or (Some (controls::text::Builtin::FieldLineEntry.into()));
271 bindings
272 };
273 let frame_bindings = frame_bindings.unwrap_or (&bindings_empty);
274 { let Widget (_, _, canvas) = Frame::try_get (elements, parent_id)
276 .unwrap();
277 assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
278 }
279 let mut out = vec![];
280 let (mut subtree, order) = {
281 let (_, border_h) = frame_border.as_ref()
282 .map_or ((0, 0), Border::total_wh);
283 let height = border_h as u32 + 1;
284 let layout = if frame_tiled {
285 layout::Variant::from (layout::Tiled::Absolute (
286 std::num::NonZeroU32::new (height).unwrap()))
287 } else {
288 let anchor = frame_anchor;
289 let offset = frame_offset;
290 let size = {
291 let width = frame_width;
292 let height = height.into();
293 Size { width, height }
294 };
295 layout::Variant::from ((layout::Free { anchor, offset, size }, frame_area))
296 };
297 let mut actions = {
298 let mut frame = frame::Builder::new (elements, parent_id)
299 .appearances (frame_appearances)
300 .bindings (frame_bindings)
301 .layout (layout.into());
302 set_option!(frame, border, frame_border);
303 set_option!(frame, clear_color, frame_clear_color);
304 frame.build_actions()
305 };
306 out.extend (actions.drain (1..));
307 debug_assert_eq!(actions.len(), 1);
308 actions.pop().unwrap().1.try_into().unwrap()
309 };
310 let frame_id = subtree.root_node_id().unwrap().clone();
311 let field = {
312 let contents = contents.unwrap_or_else (String::new);
313 let controller = {
314 let component = cursor.clone().into();
315 let mut controller = Controller::with_bindings (&bindings);
316 controller.component = component;
317 controller.appearances = appearances;
318 controller
319 };
320 let model = Model { callback_id, component: contents.clone().into() };
321 let view = {
322 let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id).unwrap();
323 view::Component::from (super::body (canvas, &cursor, &contents, false)).into()
324 };
325 Element::new ("Field".to_string(), controller, model, view)
326 };
327 let _ = subtree.insert (Node::new (field), InsertBehavior::UnderNode (&frame_id))
328 .unwrap();
329 out.push ((parent_id.clone(), Action::Create (subtree, order)));
330 log::trace!("...build actions");
331 out
332 }
333 }
334}