Skip to main content

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