gooey/widget/
numbox.rs

1//! Number box.
2//!
3//! ```text
4//! +--------+
5//! | frame  |
6//! +---+----+
7//!     |
8//! +---+----+
9//! | numbox |
10//! +--------+
11//! ```
12
13use std::sync::LazyLock;
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
31pub static CONTROLS : LazyLock <Controls> = LazyLock::new (||
32  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 = Box::new (|view : &mut View| view.component = new_body.into());
168    action_buffer.push ((numbox_id.clone(), Action::ModifyView (update_body)));
169  }
170}
171
172//
173//  crate
174//
175
176pub (crate) fn body (
177  canvas  : &view::component::Canvas,
178  numeral : &controller::component::Numeral,
179  value   : f64
180) -> view::component::Body {
181  use controller::alignment::Horizontal;
182  log::trace!("body...");
183  let body = {
184    let body_width = canvas.body_wh().0 as usize;
185    let mut string = numeral.format.format_number (value);
186    let digits = match numeral.alignment {
187      Horizontal::Left => {
188        string.truncate (body_width);
189        string
190      }
191      Horizontal::Right => string.split_off (string.len().saturating_sub (body_width)),
192      Horizontal::Center => if string.len() > body_width {
193        let extra = string.len() - body_width;
194        string.truncate (string.len() - extra / 2);
195        let remaining = extra - extra / 2;
196        string.split_off (remaining)
197      } else {
198        string
199      }
200    };
201    debug_assert!(digits.len() <= body_width);
202    match numeral.alignment {
203      Horizontal::Left => view::component::Body (digits),
204      Horizontal::Right => view::component::Body (format!("{digits:>body_width$}")),
205      Horizontal::Center => view::component::Body (format!("{:>1$}", digits,
206        body_width - (body_width - digits.len() / 2)))
207    }
208  };
209  log::trace!("...body");
210  body
211}
212
213//
214//  private
215//
216
217fn canvas_size (frame_border : Option <&view::Border>, length : u32)
218  -> controller::Size
219{
220  let (border_w, border_h) = frame_border.map_or ((0, 0), view::Border::total_wh);
221  let width  = (border_w as u32 + length).into();
222  let height = (border_h as u32 + 1).into();
223  controller::Size { width, height }
224}
225
226//
227//  builder
228//
229
230mod builder {
231  use derive_builder::Builder;
232  use crate::prelude::*;
233  use super::canvas_size;
234
235  #[derive(Builder)]
236  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
237  struct Numbox <'a, A : Application> {
238    elements          : &'a Tree <Element>,
239    parent_id         : &'a NodeId,
240    #[builder(default)]
241    appearances       : Appearances,
242    #[builder(default)]
243    bindings          : Option <&'a Bindings <A>>,
244    #[builder(default)]
245    frame_anchor      : Alignment,
246    #[builder(default)]
247    frame_appearances : Appearances,
248    #[builder(default)]
249    frame_area        : Area,
250    #[builder(default)]
251    frame_bindings    : Option <&'a Bindings <A>>,
252    #[builder(default)]
253    frame_border      : Option <Border>,
254    #[builder(default)]
255    frame_clear_color : Option <canvas::ClearColor>,
256    #[builder(default)]
257    frame_offset      : Offset,
258    #[builder(default = "12")]
259    length            : u32,
260    #[builder(default)]
261    numeral           : Numeral,
262    #[builder(default)]
263    value             : Option <f64>
264  }
265
266  impl <'a, A : Application> NumboxBuilder <'a, A> {
267    pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
268      NumboxBuilder {
269        elements:          Some (elements),
270        parent_id:         Some (parent_id),
271        appearances:       None,
272        bindings:          None,
273        frame_anchor:      None,
274        frame_appearances: None,
275        frame_area:        None,
276        frame_bindings:    None,
277        frame_border:      None,
278        frame_clear_color: None,
279        frame_offset:      None,
280        length:            None,
281        numeral:           None,
282        value:             None
283      }
284    }
285  }
286
287  impl <A : Application> BuildActions for NumboxBuilder <'_, A> {
288    fn build_actions (self) -> Vec<(NodeId, Action)> {
289      use std::convert::TryInto;
290      use crate::tree::{InsertBehavior, Node};
291      use controller::component::layout;
292      log::trace!("build actions...");
293      let Numbox {
294        elements, parent_id, appearances, bindings, frame_anchor,
295        frame_appearances, frame_area, frame_bindings, frame_border,
296        frame_clear_color, frame_offset, length, numeral, value
297      } = self.build()
298        .map_err(|err| log::error!("frame builder error: {err:?}"))
299        .unwrap();
300      let bindings_empty = Bindings::empty();
301      let bindings = bindings.unwrap_or (&bindings_empty)
302        .get_bindings (&super::CONTROLS);
303      let frame_bindings = frame_bindings.unwrap_or (&bindings_empty);
304      if length == 0 {
305        log::warn!("zero length number box field");
306      }
307      { // only allow tile coordinate parents
308        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id)
309          .unwrap();
310        assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
311      }
312      let value   = value.unwrap_or (0.0);
313      let mut out = vec![];
314      let (mut subtree, order) = {
315        let layout = {
316          let anchor = frame_anchor;
317          let offset = frame_offset;
318          let size   = canvas_size (frame_border.as_ref(), length);
319          layout::Variant::from (
320            (layout::Free { anchor, offset, size }, frame_area)
321          )
322        };
323        let mut actions = {
324          let mut frame = frame::Builder::new (elements, parent_id)
325            .appearances (frame_appearances)
326            .bindings (frame_bindings)
327            .layout (layout.into());
328          set_option!(frame, border, frame_border);
329          set_option!(frame, clear_color, frame_clear_color);
330          frame.build_actions()
331        };
332        out.extend (actions.drain (1..));
333        debug_assert_eq!(actions.len(), 1);
334        actions.pop().unwrap().1.try_into().unwrap()
335      };
336      let frame_id = subtree.root_node_id().unwrap().clone();
337      let numbox = {
338        let controller = {
339          let component          = numeral.clone().into();
340          let mut controller     = Controller::with_bindings (&bindings);
341          controller.component   = component;
342          controller.appearances = appearances;
343          controller
344        };
345        let model = Model { component: value.into(), .. Model::default() };
346        let view  = {
347          let Widget (_, _, canvas) =
348            Frame::try_get (&subtree, &frame_id).unwrap();
349          view::Component::from (super::body (canvas, &numeral, value)).into()
350        };
351        Element::new ("Numbox".to_string(), controller, model, view)
352      };
353      let _ = subtree
354        .insert (Node::new (numbox), InsertBehavior::UnderNode (&frame_id))
355        .unwrap();
356      out.push ((parent_id.clone(), Action::Create (subtree, order)));
357      log::trace!("...build actions");
358      out
359    }
360  }
361}