gooey/widget/
numbox.rs

1//! Number box.
2//!
3//! ```text
4//! +--------+
5//! | frame  |
6//! +---+----+
7//!     |
8//! +---+----+
9//! | numbox |
10//! +--------+
11//! ```
12
13use lazy_static::lazy_static;
14
15use crate::TreeHelper;
16use crate::interface::{view, Action, Element, Model, View};
17use crate::interface::controller::{self, controls, Controls};
18use crate::tree::{Tree, NodeId};
19
20use super::{Widget, Frame};
21
22pub use self::builder::NumboxBuilder as Builder;
23
24pub type Numbox <'element> = Widget <'element,
25  controller::component::Numeral, f64, view::component::Body>;
26
27//
28//  controls
29//
30
31lazy_static!{
32  pub static ref CONTROLS : Controls = controls::Builder::new()
33    .buttons (vec![
34      controls::button::Builtin::NumboxIncrease,
35      controls::button::Builtin::NumboxDecrease,
36      controls::button::Builtin::NumboxIncrease10x,
37      controls::button::Builtin::NumboxDecrease10x
38    ].into_iter().map (Into::into).collect::<Vec <_>>().into())
39    .build();
40}
41
42/// Builtin button control ID 'NumboxDecrease'
43pub fn decrease (
44  _             : &controls::button::Release,
45  elements      : &Tree <Element>,
46  node_id       : &NodeId,
47  action_buffer : &mut Vec <(NodeId, Action)>
48) {
49  log::trace!("decrease...");
50  let Widget (numeral, value, _) = Numbox::try_get (elements, node_id).unwrap();
51  let Widget (_, _, canvas) =
52    Frame::try_get (elements, elements.get_parent_id (node_id)).unwrap();
53  let new_value = numeral.modify (*value, -1.0);
54  let new_body  = body (canvas, &numeral, new_value);
55  { // update model
56    let update_value =
57      Box::new (move |model : &mut Model| model.component = new_value.into());
58    action_buffer.push ((node_id.clone(), Action::ModifyModel (update_value)));
59  }
60  { // update view
61    let update_body =
62      Box::new (|view : &mut View| view.component = new_body.into());
63    action_buffer.push ((node_id.clone(), Action::ModifyView (update_body)));
64  }
65  log::trace!("...decrease");
66}
67
68/// Builtin button control ID 'NumboxIncrease'
69pub fn increase (
70  _             : &controls::button::Release,
71  elements      : &Tree <Element>,
72  node_id       : &NodeId,
73  action_buffer : &mut Vec <(NodeId, Action)>
74) {
75  log::trace!("increase...");
76  let Widget (numeral, value, _) = Numbox::try_get (elements, node_id).unwrap();
77  let Widget (_, _, canvas) =
78    Frame::try_get (elements, elements.get_parent_id (node_id)).unwrap();
79  let new_value = numeral.modify (*value, 1.0);
80  let new_body  = body (canvas, &numeral, new_value);
81  { // update model
82    let update_value =
83      Box::new (move |model : &mut Model| model.component = new_value.into());
84    action_buffer.push ((node_id.clone(), Action::ModifyModel (update_value)));
85  }
86  { // update view
87    let update_body =
88      Box::new (|view : &mut View| view.component = new_body.into());
89    action_buffer.push ((node_id.clone(), Action::ModifyView (update_body)));
90  }
91  log::trace!("...increase");
92}
93
94/// Builtin button control ID 'NumboxDecrease10x'
95pub fn decrease_10x (
96  _             : &controls::button::Release,
97  elements      : &Tree <Element>,
98  node_id       : &NodeId,
99  action_buffer : &mut Vec <(NodeId, Action)>
100) {
101  log::trace!("decrease_10x...");
102  let Widget (numeral, value, _) = Numbox::try_get (elements, node_id).unwrap();
103  let Widget (_, _, canvas) =
104    Frame::try_get (elements, elements.get_parent_id (node_id)).unwrap();
105  let new_value = numeral.modify (*value, -10.0);
106  let new_body  = body (canvas, &numeral, new_value);
107  { // update model
108    let update_value =
109      Box::new (move |model : &mut Model| model.component = new_value.into());
110    action_buffer.push ((node_id.clone(), Action::ModifyModel (update_value)));
111  }
112  { // update view
113    let update_body =
114      Box::new (|view : &mut View| view.component = new_body.into());
115    action_buffer.push ((node_id.clone(), Action::ModifyView (update_body)));
116  }
117  log::trace!("...decrease_10x");
118}
119
120/// Builtin button control ID 'NumboxIncrease9x'
121pub fn increase_10x (
122  _             : &controls::button::Release,
123  elements      : &Tree <Element>,
124  node_id       : &NodeId,
125  action_buffer : &mut Vec <(NodeId, Action)>
126) {
127  log::trace!("increase_10x...");
128  let Widget (numeral, value, _) = Numbox::try_get (elements, node_id).unwrap();
129  let Widget (_, _, canvas) =
130    Frame::try_get (elements, elements.get_parent_id (node_id)).unwrap();
131  let new_value = numeral.modify (*value, 10.0);
132  let new_body  = body (canvas, &numeral, new_value);
133  { // update model
134    let update_value =
135      Box::new (move |model : &mut Model| model.component = new_value.into());
136    action_buffer.push ((node_id.clone(), Action::ModifyModel (update_value)));
137  }
138  { // update view
139    let update_body =
140      Box::new (|view : &mut View| view.component = new_body.into());
141    action_buffer.push ((node_id.clone(), Action::ModifyView (update_body)));
142  }
143  log::trace!("...increase_10x");
144}
145
146//
147//  utils
148//
149
150/// Utility function to set the umbox contents to the given value
151pub fn set_value (
152  elements      : &Tree <Element>,
153  numbox_id     : &NodeId,
154  value         : f64,
155  action_buffer : &mut Vec <(NodeId, Action)>
156) {
157  let Widget (numeral, _, _) = Numbox::try_get (elements, numbox_id).unwrap();
158  let Widget (_, _, canvas) =
159    Frame::try_get (elements, elements.get_parent_id (numbox_id)).unwrap();
160  let new_body = body (canvas, &numeral, value);
161  { // update model
162    let update_value =
163      Box::new (move |model : &mut Model| model.component = value.into());
164    action_buffer.push ((numbox_id.clone(), Action::ModifyModel (update_value)));
165  }
166  { // update view
167    let update_body =
168      Box::new (|view : &mut View| view.component = new_body.into());
169    action_buffer.push ((numbox_id.clone(), Action::ModifyView (update_body)));
170  }
171}
172
173//
174//  crate
175//
176
177pub (crate) fn body (
178  canvas  : &view::component::Canvas,
179  numeral : &controller::component::Numeral,
180  value   : f64
181) -> view::component::Body {
182  use controller::alignment::Horizontal;
183  log::trace!("body...");
184  let body = {
185    let body_width = canvas.body_wh().0 as usize;
186    let mut string = numeral.format.format_number (value);
187    let digits = match numeral.alignment {
188      Horizontal::Left => {
189        string.truncate (body_width);
190        string
191      }
192      Horizontal::Right =>
193        string.split_off (string.len().saturating_sub (body_width)),
194      Horizontal::Center => if string.len() > body_width {
195        let extra = string.len() - body_width;
196        string.truncate (string.len() - extra / 2);
197        let remaining = extra - extra / 2;
198        string.split_off (remaining)
199      } else {
200        string
201      }
202    };
203    debug_assert!(digits.len() <= body_width);
204    match numeral.alignment {
205      Horizontal::Left => view::component::Body (format!("{}", digits)),
206      Horizontal::Right =>
207        view::component::Body (format!("{:>1$}", digits, body_width)),
208      Horizontal::Center =>
209        view::component::Body (format!("{:>1$}", digits,
210          body_width - (body_width - digits.len() / 2)))
211    }
212  };
213  log::trace!("...body");
214  body
215}
216
217//
218//  private
219//
220
221fn canvas_size (frame_border : Option <&view::Border>, length : u32)
222  -> controller::Size
223{
224  let (border_w, border_h) = frame_border.map (view::Border::total_wh)
225    .unwrap_or ((0,0));
226  let width   = (border_w as u32 + length).into();
227  let height  = (border_h as u32 + 1).into();
228  controller::Size { width, height }
229}
230
231//
232//  builder
233//
234
235mod builder {
236  use derive_builder::Builder;
237  use crate::prelude::*;
238  use super::canvas_size;
239
240  #[derive(Builder)]
241  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
242  pub struct Numbox <'a, A : Application> {
243    elements          : &'a Tree <Element>,
244    parent_id         : &'a NodeId,
245    #[builder(default)]
246    appearances       : controller::Appearances,
247    #[builder(default)]
248    bindings          : Option <&'a controller::Bindings <A>>,
249    #[builder(default)]
250    frame_anchor      : controller::Alignment,
251    #[builder(default)]
252    frame_appearances : controller::Appearances,
253    #[builder(default)]
254    frame_area        : controller::Area,
255    #[builder(default)]
256    frame_bindings    : Option <&'a controller::Bindings <A>>,
257    #[builder(default)]
258    frame_border      : Option <view::Border>,
259    #[builder(default)]
260    frame_clear_color : Option <canvas::ClearColor>,
261    #[builder(default)]
262    frame_offset      : controller::Offset,
263    #[builder(default = "12")]
264    length            : u32,
265    #[builder(default)]
266    numeral           : controller::component::Numeral,
267    #[builder(default)]
268    value             : Option <f64>
269  }
270
271  impl <'a, A : Application> NumboxBuilder <'a, A> {
272    pub fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
273      NumboxBuilder {
274        elements:          Some (elements),
275        parent_id:         Some (parent_id),
276        appearances:       None,
277        bindings:          None,
278        frame_anchor:      None,
279        frame_appearances: None,
280        frame_area:        None,
281        frame_bindings:    None,
282        frame_border:      None,
283        frame_clear_color: None,
284        frame_offset:      None,
285        length:            None,
286        numeral:           None,
287        value:             None
288      }
289    }
290  }
291
292  impl <'a, A : Application> BuildActions for NumboxBuilder <'a, A> {
293    fn build_actions (self) -> Vec<(NodeId, Action)> {
294      use std::convert::TryInto;
295      use crate::tree::{InsertBehavior, Node};
296      use controller::component::layout;
297      log::trace!("build actions...");
298      let Numbox {
299        elements, parent_id, appearances, bindings, frame_anchor,
300        frame_appearances, frame_area, frame_bindings, frame_border,
301        frame_clear_color, frame_offset, length, numeral, value
302      } = self.build()
303        .map_err(|err| log::error!("frame builder error: {:?}", err))
304        .unwrap();
305      let bindings_empty = Bindings::empty();
306      let bindings = bindings.unwrap_or (&bindings_empty)
307        .get_bindings (&super::CONTROLS);
308      let frame_bindings = frame_bindings.unwrap_or (&bindings_empty);
309      if length == 0 {
310        log::warn!("zero length number box field");
311      }
312      { // only allow tile coordinate parents
313        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id)
314          .unwrap();
315        assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
316      }
317      let value   = value.unwrap_or (0.0);
318      let mut out = vec![];
319      let (mut subtree, order) = {
320        let layout = {
321          let anchor = frame_anchor;
322          let offset = frame_offset;
323          let size   = canvas_size (frame_border.as_ref(), length);
324          layout::Variant::from (
325            (layout::Free { anchor, offset, size }, frame_area)
326          )
327        };
328        let mut actions = {
329          let mut frame = frame::Builder::new (elements, parent_id)
330            .appearances (frame_appearances)
331            .bindings (frame_bindings)
332            .layout (layout.into());
333          set_option!(frame, border, frame_border);
334          set_option!(frame, clear_color, frame_clear_color);
335          frame.build_actions()
336        };
337        out.extend (actions.drain (1..));
338        debug_assert_eq!(actions.len(), 1);
339        actions.pop().unwrap().1.try_into().unwrap()
340      };
341      let frame_id = subtree.root_node_id().unwrap().clone();
342      let numbox = {
343        let controller = {
344          let component          = numeral.clone().into();
345          let mut controller     = Controller::with_bindings (&bindings);
346          controller.component   = component;
347          controller.appearances = appearances;
348          controller
349        };
350        let model = Model { component: value.into(), .. Model::default() };
351        let view  = {
352          let Widget (_, _, canvas) =
353            Frame::try_get (&subtree, &frame_id).unwrap();
354          view::Component::from (super::body (canvas, &numeral, value)).into()
355        };
356        Element::new ("Numbox".to_string(), controller, model, view)
357      };
358      let _ = subtree
359        .insert (Node::new (numbox), InsertBehavior::UnderNode (&frame_id))
360        .unwrap();
361      out.push ((parent_id.clone(), Action::Create (subtree, order)));
362      log::trace!("...build actions");
363      out
364    }
365  }
366}