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