gooey/widget/textbox/
mod.rs

1//! Display text data.
2//!
3//! ```text
4//! +--------+
5//! | frame  |
6//! +---+----+
7//!     |
8//! +---+-----+
9//! | textbox |
10//! +---------+
11//! ```
12
13use lazy_static::lazy_static;
14
15use crate::{Application, Presentation, TreeHelper};
16use crate::tree::{Tree, NodeId};
17use crate::interface::{
18  model, view, Action, Controller, Element, Interface, Model, View
19};
20use crate::interface::controller::{self, controls, Controls};
21use super::{Widget, Frame, build_and_get_node_ids, frame, set_option};
22
23mod make_table;
24
25pub use self::builder::{TextboxBuilder as Builder, FitBuilder, WithFrameBuilder};
26pub use self::make_table::{box_vec, make_table, mk_table};
27
28pub type Textbox <'element> = Widget <'element,
29  controller::component::Scroll, String, view::component::Body>;
30
31//
32//  controls
33//
34
35lazy_static!{
36  pub static ref CONTROLS : Controls = controls::Builder::new()
37    .buttons (vec![
38      controls::button::Builtin::TextboxScrollDown,
39      controls::button::Builtin::TextboxScrollUp,
40      controls::button::Builtin::TextboxScrollRight,
41      controls::button::Builtin::TextboxScrollLeft,
42      controls::button::Builtin::TextboxPageDown,
43      controls::button::Builtin::TextboxPageUp,
44      controls::button::Builtin::TextboxPageRight,
45      controls::button::Builtin::TextboxPageLeft
46    ].into_iter().map (Into::into).collect::<Vec <_>>().into())
47    .build();
48}
49
50/// Builtin textbox control ID 'TextboxScrollRight'
51pub fn scroll_right (
52  _             : &controls::button::Release,
53  elements      : &Tree <Element>,
54  textbox_id    : &NodeId,
55  action_buffer : &mut Vec <(NodeId, Action)>
56) {
57  log::trace!("scroll_right...");
58  scroll_helper (elements, textbox_id, action_buffer,
59    controller::component::Scroll::offset_move_right, 1);
60  log::trace!("...scroll_right");
61}
62
63/// Builtin textbox control ID 'TextboxScrollLeft'
64pub fn scroll_left (
65  _             : &controls::button::Release,
66  elements      : &Tree <Element>,
67  textbox_id    : &NodeId,
68  action_buffer : &mut Vec <(NodeId, Action)>
69) {
70  log::trace!("scroll_left...");
71  scroll_helper (elements, textbox_id, action_buffer,
72    controller::component::Scroll::offset_move_left, 1);
73  log::trace!("...scroll_left");
74}
75
76/// Builtin textbox control ID 'TextboxScrollUp'
77pub fn scroll_up (
78  _             : &controls::button::Release,
79  elements      : &Tree <Element>,
80  textbox_id    : &NodeId,
81  action_buffer : &mut Vec <(NodeId, Action)>
82) {
83  log::trace!("scroll_up...");
84  scroll_helper (elements, textbox_id, action_buffer,
85    controller::component::Scroll::offset_move_up, 1);
86  log::trace!("...scroll_up");
87}
88
89/// Builtin textbox control ID 'TextboxScrollDown'
90pub fn scroll_down (
91  _             : &controls::button::Release,
92  elements      : &Tree <Element>,
93  textbox_id    : &NodeId,
94  action_buffer : &mut Vec <(NodeId, Action)>
95) {
96  log::trace!("scroll_down...");
97  scroll_helper (elements, textbox_id, action_buffer,
98    controller::component::Scroll::offset_move_down, 1);
99  log::trace!("...scroll_down");
100}
101
102/// Builtin textbox control ID 'TextboxPageRight'
103pub fn page_right (
104  _             : &controls::button::Release,
105  elements      : &Tree <Element>,
106  textbox_id    : &NodeId,
107  action_buffer : &mut Vec <(NodeId, Action)>
108) {
109  log::trace!("page_right...");
110  // scroll by width - 1
111  let (width, _) = {
112    let Widget (_, _, canvas) =
113      Frame::try_get (elements, elements.get_parent_id (textbox_id)).unwrap();
114    canvas.body_wh()
115  };
116  scroll_helper (elements, textbox_id, action_buffer,
117    controller::component::Scroll::offset_move_right, width-1);
118  log::trace!("...page_right");
119}
120
121/// Builtin textbox control ID 'TextboxPageLeft'
122pub fn page_left (
123  _             : &controls::button::Release,
124  elements      : &Tree <Element>,
125  textbox_id    : &NodeId,
126  action_buffer : &mut Vec <(NodeId, Action)>
127) {
128  log::trace!("page_left...");
129  // scroll by width - 1
130  let (width, _) = {
131    let Widget (_, _, canvas) =
132      Frame::try_get (elements, elements.get_parent_id (textbox_id)).unwrap();
133    canvas.body_wh()
134  };
135  scroll_helper (elements, textbox_id, action_buffer,
136    controller::component::Scroll::offset_move_left, width-1);
137  log::trace!("...page_left");
138}
139
140/// Builtin textbox control ID 'TextboxPageUp'
141pub fn page_up (
142  _             : &controls::button::Release,
143  elements      : &Tree <Element>,
144  textbox_id    : &NodeId,
145  action_buffer : &mut Vec <(NodeId, Action)>
146) {
147  log::trace!("page_up...");
148  // scroll by height - 1
149  let (_, height) = {
150    let Widget (_, _, canvas) =
151      Frame::try_get (elements, elements.get_parent_id (textbox_id)).unwrap();
152    canvas.body_wh()
153  };
154  scroll_helper (elements, textbox_id, action_buffer,
155    controller::component::Scroll::offset_move_up, height-1);
156  log::trace!("...page_up");
157}
158
159/// Builtin textbox control ID 'TextboxPageDown'
160pub fn page_down (
161  _             : &controls::button::Release,
162  elements      : &Tree <Element>,
163  textbox_id    : &NodeId,
164  action_buffer : &mut Vec <(NodeId, Action)>
165) {
166  log::trace!("page_down...");
167  // scroll by height - 1
168  let (_, height) = {
169    let Widget (_, _, canvas) =
170      Frame::try_get (elements, elements.get_parent_id (textbox_id)).unwrap();
171    canvas.body_wh()
172  };
173  scroll_helper (elements, textbox_id, action_buffer,
174    controller::component::Scroll::offset_move_down, height-1);
175  log::trace!("...page_down");
176}
177
178//
179//  utils
180//
181
182/// Create a new textbox in the middle of the parent element and focus
183pub fn popup <A, P> (
184  interface   : &mut Interface <A, P>,
185  parent_id   : NodeId,
186  text        : String,
187  appearances : Option <controller::Appearances>,
188  border      : Option <view::Border>
189) -> [NodeId; 2] where
190  A : Application,
191  P : Presentation
192{
193  let mut builder = FitBuilder::<A>::new (interface.elements(), &parent_id)
194    .frame_anchor (controller::Alignment::middle_center())
195    .text (text);
196  set_option!(builder, frame_appearances, appearances);
197  set_option!(builder, frame_border, border);
198  let [frame_id, textbox_id] =
199    build_and_get_node_ids!(interface, builder, ["Frame", "Textbox"]);
200  let _ = interface.action (&frame_id, Action::Focus);
201  [frame_id, textbox_id]
202}
203
204/// Utility function to get the textbox contents
205pub fn get_contents (elements : &Tree <Element>, textbox_id : &NodeId)
206  -> String
207{
208  let Widget (_, contents, _) = Textbox::try_get (elements, textbox_id).unwrap();
209  contents.clone()
210}
211
212/// Utility function to set the textbox contents to the given string
213pub fn set_contents (
214  elements      : &Tree <Element>,
215  textbox_id    : &NodeId,
216  contents      : String,
217  action_buffer : &mut Vec <(NodeId, Action)>
218) {
219  let new_body = {
220    // TODO: we may need to update scroll here
221    let Widget (scroll, _, _) = Textbox::try_get (elements, textbox_id).unwrap();
222    let Widget (_, _, canvas) =
223      Frame::try_get (elements, elements.get_parent_id (textbox_id)).unwrap();
224    body (canvas, &scroll, &contents)
225  };
226  { // update model
227    let update_text =
228      Box::new (|model : &mut Model| model.component = contents.into());
229    action_buffer.push ((textbox_id.clone(), Action::ModifyModel (update_text)));
230  }
231  { // update view
232    let update_body =
233      Box::new (|view : &mut View| view.component = new_body.into());
234    action_buffer.push ((textbox_id.clone(), Action::ModifyView (update_body)));
235  }
236}
237
238/// Utility function to resize the textbox to the current contents
239pub fn fit_to_contents (
240  elements      : &Tree <Element>,
241  textbox_id    : &NodeId,
242  action_buffer : &mut Vec <(NodeId, Action)>
243) {
244  use controller::component::layout;
245  let (width, height) = {
246    let contents = get_contents (elements, textbox_id);
247    let lines    = contents.lines().map (str::len).collect::<Vec <_>>();
248    let height   = lines.len() as u32;
249    let width    = (*lines.iter().max().unwrap_or (&0)) as u32;
250    (width, height)
251  };
252  let frame_id = elements.get_parent_id (textbox_id);
253  let mut layout = {
254    let Widget (layout, _, _) = Frame::try_get (elements, frame_id).unwrap();
255    layout.clone()
256  };
257  match layout.variant {
258    layout::Variant::Free (ref mut free, _) => {
259      let old_size = free.size;
260      free.size.width  = width.into();
261      free.size.height = height.into();
262      if old_size != free.size {
263        frame::set_layout (elements, frame_id, layout, None, action_buffer);
264      }
265    }
266    layout::Variant::Tiled (_) =>
267      log::warn!("fit to contents called on tiled textbox frame")
268  }
269}
270
271/// Utility function to push a newline followed by the given string onto the
272/// textbox contents
273pub fn push_line (
274  elements      : &Tree <Element>,
275  textbox_id    : &NodeId,
276  line          : String,
277  action_buffer : &mut Vec <(NodeId, Action)>
278) {
279  let new_body = {
280    // TODO: we may need to update scroll here
281    let Widget (scroll, contents, _) = Textbox::try_get (elements, textbox_id)
282      .unwrap();
283    let mut contents = contents.clone();
284    contents.push ('\n');
285    contents.push_str (&line);
286    let Widget (_, _, canvas) =
287      Frame::try_get (elements, elements.get_parent_id (textbox_id)).unwrap();
288    body (canvas, &scroll, &contents)
289  };
290  { // update model
291    let update_text = Box::new (move |model : &mut Model| match model.component {
292      model::Component::Text (ref mut text) => {
293        text.push ('\n');
294        text.push_str (&line);
295      }
296      _ => unreachable!()
297    });
298    action_buffer.push ((textbox_id.clone(), Action::ModifyModel (update_text)));
299  }
300  { // update view
301    let update_body =
302      Box::new (|view : &mut View| view.component = new_body.into());
303    action_buffer.push ((textbox_id.clone(), Action::ModifyView (update_body)));
304  }
305}
306
307//
308//  crate
309//
310
311pub (crate) fn body (
312  canvas : &view::component::Canvas,
313  scroll : &controller::component::Scroll,
314  text   : &String
315) -> view::component::Body {
316  use controller::{alignment, offset};
317  log::trace!("body...");
318  let (body_width, body_height) = canvas.body_wh();
319  let lines   = text.lines().count() as u32;
320  let longest = text.lines().map (str::len).max().unwrap_or_default() as u32;
321  let scroll_horizontal = match scroll.offset.horizontal {
322    offset::Signed::Absolute (horizontal) => horizontal,
323    offset::Signed::Relative (horizontal) => {
324      let horizontal_max = longest.saturating_sub (body_width) as f32;
325      (*horizontal * horizontal_max).round() as i32
326    }
327  };
328  let scroll_vertical = match scroll.offset.vertical {
329    offset::Signed::Absolute (vertical) => vertical,
330    offset::Signed::Relative (vertical) => {
331      let vertical_max = lines.saturating_sub (body_height) as f32;
332      (*vertical * vertical_max).round() as i32
333    }
334  };
335  let mut body = String::new();
336  let (skip, body_top) = match scroll.alignment.vertical {
337    alignment::Vertical::Top    => (scroll_vertical as usize, 0),
338    alignment::Vertical::Bottom => {
339      for _ in 0..body_height.saturating_sub (lines) {
340        body.push ('\n');
341      }
342      ( (lines.saturating_sub (body_height) as i32 - scroll_vertical).max (0)
343          as usize,
344        0
345      )
346    }
347    alignment::Vertical::Middle => {
348      let center   = lines as i32 / 2;
349      let body_top = center - body_height as i32 / 2 - scroll_vertical;
350      for _ in body_top..0 {
351        body.push ('\n');
352      }
353      (body_top.max (0) as usize, body_top)
354    }
355  };
356  // NOTE: take at most body_height lines, but stops at the last line if too
357  // few; for middle scroll alignment subtract negative body_top from
358  // body_height
359  let take = (body_height as i32 + body_top.min (0)).max (0) as usize;
360  let mut lines = text.lines().skip (skip).take (take);
361  while let Some (line) = lines.next() {
362    let len = line.len();
363    let (space, start, end) = match scroll.alignment.horizontal {
364      alignment::Horizontal::Left   => (
365        0,
366        scroll_horizontal  as usize,
367        (scroll_horizontal as usize + body_width as usize).min (len)
368      ),
369      alignment::Horizontal::Center => {
370        let center     = longest as i32 / 2;
371        let body_left  = center - body_width as i32 / 2 + scroll_horizontal;
372        let body_right = center + body_width as i32 / 2 + scroll_horizontal;
373        let line_start = center - len as i32 / 2;
374        let line_end   = center + len as i32 / 2 - (len as i32 + 1) % 2;
375        let space      = (line_start - body_left).max (0)      as usize;
376        let start      = (body_left - line_start).max (0)      as usize;
377        let end        = len - (line_end - body_right).max (0) as usize;
378        (space, start, end)
379      }
380      alignment::Horizontal::Right  =>
381        ( (body_width as usize + scroll_horizontal as usize)
382            .saturating_sub (len),
383          len.saturating_sub (body_width as usize)
384            .saturating_sub (scroll_horizontal as usize),
385          len
386        )
387    };
388    if start < end {
389      for _ in 0..space {
390        body.push ('\0');
391      }
392      // NOTE: previously we sliced here but if the string contains non-ascii
393      // characters the byte boundary will not be aligned
394      for ch in line.chars().skip (start).take (end - start) {
395        body.push (ch);
396      }
397      body.push ('\n');
398    } else if start == end {
399      body.push ('\n');
400    }
401  }
402  log::trace!("...body");
403  view::component::Body (body)
404}
405
406//
407//  private
408//
409
410fn scroll_helper (
411  elements      : &Tree <Element>,
412  textbox_id    : &NodeId,
413  action_buffer : &mut Vec <(NodeId, Action)>,
414  scroll_fun    : fn (&mut controller::component::Scroll, u32),
415  count         : u32
416) {
417  use controller::{alignment, offset};
418  let Widget (scroll, text, _) = Textbox::try_get (elements, textbox_id)
419    .unwrap();
420  let Widget (_, _, canvas) =
421    Frame::try_get (elements, elements.get_parent_id (textbox_id)).unwrap();
422  let lines           = text.lines().count() as u32;
423  let longest         = text.lines().map (str::len).max().unwrap() as u32;
424  let (body_width, body_height) = canvas.body_wh();
425  let horizontal_max  = longest.saturating_sub (body_width) as i32;
426  let vertical_max    = lines.saturating_sub (body_height)  as i32;
427  let new_scroll      = {
428    let mut scroll = scroll.clone();
429    scroll_fun (&mut scroll, count);
430    match &mut scroll.offset.horizontal {
431      offset::Signed::Absolute (horizontal) => {
432        match scroll.alignment.horizontal {
433          alignment::Horizontal::Left | alignment::Horizontal::Right => {
434            *horizontal = (*horizontal).max (0);
435            *horizontal = (*horizontal).min (horizontal_max);
436          }
437          alignment::Horizontal::Center => {
438            *horizontal = (*horizontal)
439              .min (horizontal_max / 2 + (body_width+1) as i32 % 2)
440              .max (-horizontal_max / 2);
441          }
442        }
443      }
444      _ => {} // TODO: scroll relative ?
445    }
446    match &mut scroll.offset.vertical {
447      offset::Signed::Absolute (vertical) => {
448        match scroll.alignment.vertical {
449          alignment::Vertical::Top | alignment::Vertical::Bottom => {
450            *vertical   = (*vertical).max (0);
451            *vertical   = (*vertical).min (vertical_max);
452          }
453          alignment::Vertical::Middle => {
454            if vertical_max == 0 {
455              *vertical = 0;
456            } else {
457              *vertical   = (*vertical)
458                .min (vertical_max / 2 + body_height as i32 % 2)
459                .max (-vertical_max / 2);
460            }
461          }
462        }
463      }
464      _ => {} // TODO: scroll relative ?
465    }
466    scroll
467  };
468  if &new_scroll != scroll {
469    let new_body = body (canvas, &new_scroll, text);
470    { // update scroll
471      let update_scroll = Box::new (move |controller : &mut Controller|
472        controller.component = new_scroll.into());
473      action_buffer.push (
474        (textbox_id.clone(), Action::ModifyController (update_scroll)));
475    }
476    { // update body
477      let update_body = Box::new (move |view : &mut View|
478        view.component = new_body.into());
479      action_buffer.push (
480        (textbox_id.clone(), Action::ModifyView (update_body)));
481    }
482  }
483}
484
485//
486//  builder
487//
488
489mod builder {
490  use derive_builder::Builder;
491  use crate::prelude::*;
492  /*
493  use crate::Application;
494  use crate::interface::{controller, view, Action, Controller, Element, Model};
495  use crate::interface::widget::{self, set_option, BuildActions, BuildElement,
496    Widget};
497  use crate::tree::{Tree, NodeId};
498  */
499
500  #[derive(Builder)]
501  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
502  pub struct Textbox <'a, A : Application> {
503    elements          : &'a Tree <Element>,
504    parent_id         : &'a NodeId,
505    #[builder(default)]
506    appearances       : controller::Appearances,
507    #[builder(default)]
508    bindings          : Option <&'a controller::Bindings <A>>,
509    #[builder(default)]
510    frame_appearances : controller::Appearances,
511    #[builder(default)]
512    frame_border      : Option <view::Border>,
513    #[builder(default)]
514    frame_clear_color : Option <canvas::ClearColor>,
515    #[builder(default)]
516    frame_disabled    : bool,
517    #[builder(default)]
518    frame_layout      : controller::component::Layout,
519    #[builder(default)]
520    text              : String,
521    #[builder(default)]
522    scroll            : Option <controller::component::Scroll>
523  }
524
525  #[derive(Builder)]
526  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
527  pub struct Fit <'a, A : Application> {
528    elements          : &'a Tree <Element>,
529    parent_id         : &'a NodeId,
530    #[builder(default)]
531    appearances       : controller::Appearances,
532    #[builder(default)]
533    bindings          : Option <&'a controller::Bindings <A>>,
534    #[builder(default)]
535    frame_anchor      : controller::Alignment,
536    #[builder(default)]
537    frame_appearances : controller::Appearances,
538    #[builder(default)]
539    frame_area        : controller::Area,
540    #[builder(default)]
541    frame_border      : Option <view::Border>,
542    #[builder(default)]
543    frame_clear_color : Option <canvas::ClearColor>,
544    #[builder(default)]
545    frame_disabled    : bool,
546    #[builder(default)]
547    frame_offset      : controller::Offset,
548    #[builder(default)]
549    scroll            : Option <controller::component::Scroll>,
550    #[builder(default)]
551    text              : String
552  }
553
554  #[derive(Builder)]
555  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
556  pub struct WithFrame <'a, A : Application> {
557    elements     : &'a Tree <Element>,
558    frame_id     : &'a NodeId,
559    #[builder(default)]
560    appearances  : controller::Appearances,
561    #[builder(default)]
562    bindings     : Option <&'a controller::Bindings <A>>,
563    #[builder(default)]
564    scroll       : Option <controller::component::Scroll>,
565    #[builder(default)]
566    text         : String
567  }
568
569  impl <'a, A : Application> TextboxBuilder <'a, A> {
570    pub fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
571      TextboxBuilder {
572        elements:          Some (elements),
573        parent_id:         Some (parent_id),
574        appearances:       None,
575        bindings:          None,
576        frame_appearances: None,
577        frame_border:      None,
578        frame_clear_color: None,
579        frame_disabled:    None,
580        frame_layout:      None,
581        scroll:            None,
582        text:              None
583      }
584    }
585  }
586
587  impl <'a, A : Application> BuildActions for TextboxBuilder <'a, A> {
588    fn build_actions (self) -> Vec<(NodeId, Action)> {
589      use std::convert::TryInto;
590      use crate::tree::{InsertBehavior, Node};
591      use view::coordinates;
592      log::trace!("build actions...");
593      let Textbox {
594        elements, parent_id, appearances, bindings, frame_appearances,
595        frame_border, frame_clear_color, frame_disabled, frame_layout, text,
596        scroll
597      } = self.build()
598        .map_err(|err| log::error!("frame builder error: {:?}", err))
599        .unwrap();
600      let bindings_empty = Bindings::empty();
601      let bindings = bindings.unwrap_or (&bindings_empty);
602      { // only allow tile coordinate parents
603        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id)
604          .unwrap();
605        assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
606      }
607      let mut out = vec![];
608      let (mut subtree, order) = {
609        let mut actions = {
610          let mut frame = frame::Builder::new (elements, parent_id)
611            .appearances (frame_appearances)
612            .bindings (&bindings)
613            .disabled (frame_disabled)
614            .layout (frame_layout);
615          set_option!(frame, border, frame_border);
616          set_option!(frame, clear_color, frame_clear_color);
617          frame.build_actions()
618        };
619        out.extend (actions.drain (1..));
620        debug_assert_eq!(actions.len(), 1);
621        actions.pop().unwrap().1.try_into().unwrap()
622      };
623      let frame_id = subtree.root_node_id().unwrap().clone();
624      let textbox = {
625        let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id)
626          .unwrap();
627        let scroll     = scroll.unwrap_or (Scroll::default_tile_absolute());
628        let body       = super::body (canvas, &scroll, &text);
629        let controller = {
630          let mut controller = Controller::with_bindings (
631            &bindings.get_bindings (&super::CONTROLS));
632          controller.appearances = appearances;
633          controller.component   = scroll.into();
634          controller
635        };
636        let model = Model { component: text.into(), .. Model::default() };
637        let view = view::Component::from (body).into();
638        Element::new ("Textbox".to_string(), controller, model, view)
639      };
640      let _ = subtree
641        .insert (Node::new (textbox), InsertBehavior::UnderNode (&frame_id))
642        .unwrap();
643      out.push ((parent_id.clone(), Action::Create (subtree, order)));
644      log::trace!("...build actions");
645      out
646    }
647  }
648
649  impl <'a, A : Application> FitBuilder <'a, A> {
650    pub fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
651      FitBuilder {
652        elements:          Some (elements),
653        parent_id:         Some (parent_id),
654        appearances:       None,
655        bindings:          None,
656        frame_anchor:      None,
657        frame_appearances: None,
658        frame_area:        None,
659        frame_border:      None,
660        frame_clear_color: None,
661        frame_disabled:    None,
662        frame_offset:      None,
663        text:              None,
664        scroll:            None
665      }
666    }
667  }
668
669  impl <'a, A : Application> BuildActions for FitBuilder <'a, A> {
670    /// Create a parent frame with body size determined by the given text
671    fn build_actions (self) -> Vec<(NodeId, Action)> {
672      use std::convert::TryInto;
673      use crate::tree::InsertBehavior;
674      log::trace!("fit build actions...");
675      let Fit {
676        elements, parent_id, appearances, bindings, frame_anchor,
677        frame_appearances, frame_area, frame_border, frame_clear_color,
678        frame_disabled, frame_offset, scroll, text
679      } = self.build()
680        .map_err(|err| log::error!("frame builder error: {:?}", err))
681        .unwrap();
682      let bindings_empty = Bindings::empty();
683      let bindings = bindings.unwrap_or (&bindings_empty);
684      { // only allow tile coordinate parents
685        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id)
686          .unwrap();
687        assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
688      }
689      let mut out = vec![];
690      let (mut subtree, order) = {
691        let size   = {
692          let longest = text.lines().map (str::len).max().unwrap() as u32;
693          let lines   = text.lines().count() as u32;
694          let (border_w, border_h) = frame_border.as_ref()
695            .map (Border::total_wh).unwrap_or ((0,0));
696          let width   = (longest + border_w as u32).into();
697          let height  = (lines   + border_h as u32).into();
698          controller::Size { width, height }
699        };
700        let layout = layout::Variant::from ((
701          layout::Free { anchor: frame_anchor, offset: frame_offset, size },
702          frame_area
703        ));
704        let mut actions = {
705          let mut frame = frame::Builder::new (elements, parent_id)
706            .appearances (frame_appearances)
707            .bindings (bindings)
708            .disabled (frame_disabled)
709            .layout (layout.into());
710          set_option!(frame, border, frame_border);
711          set_option!(frame, clear_color, frame_clear_color);
712          frame.build_actions()
713        };
714        out.extend (actions.drain (1..));
715        debug_assert_eq!(actions.len(), 1);
716        actions.pop().unwrap().1.try_into().unwrap()
717      };
718      let frame_id = subtree.root_node_id().unwrap().clone();
719      let textbox = {
720        let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id)
721          .unwrap();
722        let scroll = scroll.unwrap_or (Scroll::default_tile_absolute());
723        let body   = super::body (canvas, &scroll, &text);
724        let controller = {
725          let mut controller     = Controller::with_bindings (&bindings);
726          controller.appearances = appearances;
727          controller.component   = scroll.into();
728          controller
729        };
730        let model  = Model {
731          component: text.clone().into(), .. Model::default()
732        };
733        let view   = view::Component::from (body).into();
734        Element::new ("Textbox".to_string(), controller, model, view)
735      };
736      let _ = subtree
737        .insert (Node::new (textbox), InsertBehavior::UnderNode (&frame_id))
738        .unwrap();
739      out.push ((parent_id.clone(), Action::Create (subtree, order)));
740      log::trace!("...fit build actions");
741      out
742    }
743  }
744
745  impl <'a, A : Application> WithFrameBuilder <'a, A> {
746    pub fn new (elements : &'a Tree <Element>, frame_id : &'a NodeId) -> Self {
747      WithFrameBuilder {
748        elements:    Some (elements),
749        frame_id:    Some (frame_id),
750        bindings:    None,
751        appearances: None,
752        text:        None,
753        scroll:      None
754      }
755    }
756  }
757
758  impl <'a, A : Application> BuildElement for WithFrameBuilder <'a, A> {
759    /// Build Textbox element targeting an existing Frame node; Frame must have
760    /// tile coordinates.
761    fn build_element (self) -> Element {
762      use view::coordinates;
763      log::trace!("with frame build element...");
764      let WithFrame {
765        elements, frame_id, bindings, appearances, text, scroll
766      } = self.build()
767        .map_err(|err| log::error!("frame builder error: {:?}", err)).unwrap();
768      let bindings_empty = Bindings::empty();
769      let bindings = bindings.unwrap_or (&bindings_empty)
770        .get_bindings (&super::CONTROLS);
771      let Widget (_, _, canvas) = Frame::try_get (elements, &frame_id)
772        .unwrap();
773      assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
774      let scroll = scroll.unwrap_or (Scroll::default_tile_absolute());
775      let body   = super::body (canvas, &scroll, &text);
776      let controller = {
777        let mut controller     = Controller::with_bindings (&bindings);
778        controller.appearances = appearances;
779        controller.component   = scroll.into();
780        controller
781      };
782      let model = Model { component: text.into(), .. Model::default() };
783      let view  = view::Component::from (body).into();
784      log::trace!("...with frame build element");
785      Element::new ("Textbox".to_string(), controller, model, view)
786    }
787  }
788}