gooey/widget/
button.rs

1//! Button widget.
2//!
3//! ```text
4//! +--------+
5//! | frame  |
6//! +---+----+
7//!     | \----------\
8//! +---+----+  +-----+-----+
9//! | button |  | (textbox) |
10//! +--------+  +-----------+
11//! ```
12//!
13//! If a `label` string is provided, a `Textbox` label will be created as a sibling to
14//! the `Button` element.
15//!
16//! The `push` and `release` control functions target the *frame* node, and assume that
17//! the first child is the button node.
18
19use std::sync::LazyLock;
20
21use crate::prelude::*;
22
23pub use self::builder::ButtonBuilder as Builder;
24
25pub type Button <'element> = Widget <'element,
26  Switch, model::Component, view::Component>;
27
28//
29//  controls
30//
31
32pub static CONTROLS : LazyLock <Controls> = LazyLock::new (||
33  controls::Builder::new()
34    .buttons (vec![
35      controls::button::Builtin::ButtonPush,
36      controls::button::Builtin::ButtonRelease
37    ].into_iter().map (Into::into).collect::<Vec <_>>().into())
38    .build()
39);
40
41/// Builtin button control ID `ButtonPush`
42pub fn push (
43  release       : &controls::button::Release,
44  elements      : &Tree <Element>,
45  frame_id      : &NodeId,
46  action_buffer : &mut Vec <(NodeId, Action)>
47) {
48  log::trace!("push...");
49  let frame = elements.get_element (frame_id);
50  let mut frame_children = elements.children_ids (frame_id).unwrap();
51  let button_id = frame_children.next().unwrap();
52  let label_id  = frame_children
53    .find (|node_id| Textbox::try_get (elements, node_id).is_ok());
54  let Widget (switch, _, _) = Button::try_get (elements, button_id).unwrap();
55  let toggle    = switch.toggle;
56  if toggle && switch.state == switch::State::On {
57    return self::release (&None, elements, frame_id, action_buffer)
58  }
59  let mut switch = switch.clone();
60  let (exit, (enter, appearances, label)) = switch.on();
61  // NOTE: we update the switch and appearance before processing trigger events
62  // in case the trigger destroys the node
63  // switch
64  let update_switch = Box::new (
65    |controller : &mut Controller| controller.component = switch.into());
66  action_buffer
67    .push ((button_id.clone(), Action::ModifyController (update_switch)));
68  // appearance
69  let appearance = Appearance {
70    sound: None, .. appearances.get (frame.controller.state.clone()).clone()
71  };
72  let update_controller = Box::new (
73    |controller : &mut Controller| controller.appearances = appearances);
74  let update_view = Box::new (
75    |view : &mut View| view.appearance = appearance);
76  action_buffer
77    .push ((frame_id.clone(), Action::ModifyController (update_controller)));
78  action_buffer
79    .push ((frame_id.clone(), Action::ModifyView (update_view)));
80  // exit
81  if let Some (trigger) = exit {
82    let target = trigger.target.as_ref().unwrap_or (button_id);
83    trigger.control_fun.0 (release, elements, target, action_buffer)
84  }
85  // remove old label
86  if let Some (textbox_id) = label_id {
87    action_buffer.push ((textbox_id.clone(), Action::Destroy))
88  }
89  // create label
90  if let Some (label) = label {
91    let textbox =
92      textbox::WithFrameBuilder::<application::Default>::new (
93        elements, frame_id
94      ) .text (label)
95        .build_element();
96    action_buffer.push ((frame_id.clone(),
97      Action::create_singleton (textbox, CreateOrder::Append)))
98  }
99  // enter
100  if let Some (trigger) = enter {
101    let target = trigger.target.as_ref().unwrap_or (button_id);
102    trigger.control_fun.0 (release, elements, target, action_buffer)
103  }
104  if !toggle {
105    // return release action
106    if let Some (release) = release {
107      release.borrow_mut().push (
108        (controls::button::Builtin::ButtonRelease.into(), frame_id.clone()));
109    }
110  }
111  log::trace!("...push");
112}
113
114/// Builtin button control ID `ButtonRelease`
115pub fn release (
116  _             : &controls::button::Release,
117  elements      : &Tree <Element>,
118  frame_id      : &NodeId,
119  action_buffer : &mut Vec <(NodeId, Action)>
120) {
121  log::trace!("release...");
122  let frame = elements.get_element (frame_id);
123  let mut frame_children = elements.children_ids (frame_id).unwrap();
124  let button_id = frame_children.next().unwrap();
125  let label_id  = frame_children
126    .find (|node_id| Textbox::try_get (elements, node_id).is_ok());
127  let Widget (switch, _, _) = Button::try_get (elements, button_id).unwrap();
128  let mut switch = switch.clone();
129  let (exit, (enter, appearances, label)) = switch.off();
130  // exit
131  if let Some (trigger) = exit {
132    let target = trigger.target.as_ref().unwrap_or (button_id);
133    trigger.control_fun.0 (&None, elements, target, action_buffer)
134  }
135  // remove old label
136  if let Some (textbox_id) = label_id {
137    action_buffer.push ((textbox_id.clone(), Action::Destroy))
138  }
139  // create label
140  if let Some (label) = label {
141    let textbox =
142      textbox::WithFrameBuilder::<application::Default>::new (
143        elements, frame_id
144      ) .text (label)
145        .build_element();
146    action_buffer.push ((frame_id.clone(),
147      Action::create_singleton (textbox, CreateOrder::Append)))
148  }
149  // enter
150  if let Some (trigger) = enter {
151    let target = trigger.target.as_ref().unwrap_or (button_id);
152    trigger.control_fun.0 (&None, elements, target, action_buffer)
153  }
154  // switch
155  let update_switch = Box::new (
156    |controller : &mut Controller| controller.component = switch.into());
157  action_buffer
158    .push ((button_id.clone(), Action::ModifyController (update_switch)));
159  // appearance
160  let appearance = Appearance {
161    sound: None,
162    .. appearances.get (frame.controller.state.clone()).clone()
163  };
164  let update_controller = Box::new (
165    |controller : &mut Controller| controller.appearances = appearances);
166  let update_view = Box::new (
167    |view : &mut View| view.appearance = appearance);
168  action_buffer
169    .push ((frame_id.clone(), Action::ModifyController (update_controller)));
170  action_buffer
171    .push ((frame_id.clone(), Action::ModifyView (update_view)));
172  log::trace!("...release");
173}
174
175//
176//  builder
177//
178
179mod builder {
180  use derive_builder::Builder;
181  use crate::prelude::*;
182
183  #[derive(Builder)]
184  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
185  struct Button <'a, A : Application> {
186    elements     : &'a Tree <Element>,
187    parent_id    : &'a NodeId,
188    #[builder(default)]
189    bindings     : Option <&'a Bindings <A>>,
190    #[builder(default)]
191    callback_id  : Option <application::CallbackId>,
192    #[builder(default)]
193    controls     : Option <Controls>,
194    #[builder(default)]
195    frame_layout : Layout,
196    #[builder(default)]
197    frame_border : Option <Border>,
198    #[builder(default)]
199    model_data   : Option <model::Component>,
200    #[builder(default)]
201    switch       : Switch
202  }
203
204  impl <'a, A : Application> ButtonBuilder <'a, A> {
205    pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
206      ButtonBuilder {
207        elements:          Some (elements),
208        parent_id:         Some (parent_id),
209        bindings:          None,
210        callback_id:       None,
211        controls:          None,
212        frame_border:      None,
213        frame_layout:      None,
214        model_data:        None,
215        switch:            None
216      }
217    }
218  }
219
220  impl <A : Application> BuildActions for ButtonBuilder <'_, A> {
221    /// Creates a new button with the given switch state, frame layout and border,
222    /// and optional text label.
223    ///
224    /// The `Create` action will be returned as the last action.
225    fn build_actions (self) -> Vec<(NodeId, Action)> {
226      use std::convert::TryInto;
227      use crate::tree::InsertBehavior;
228      log::trace!("build actions...");
229      let Button {
230        elements, parent_id, bindings, callback_id, controls, frame_border,
231        frame_layout, model_data, switch
232      } = self.build()
233        .map_err(|err| log::error!("button builder error: {err:?}")).unwrap();
234      let bindings_empty = Bindings::empty();
235      let bindings = bindings.unwrap_or (&bindings_empty);
236      let mut out = vec![];
237      let (mut subtree, order) = {
238        let mut actions = {
239          let mut frame = frame::Builder::new (elements, parent_id)
240            .appearances (switch.appearances().clone())
241            .bindings (bindings)
242            .layout (frame_layout);
243          set_option!(frame, border, frame_border);
244          frame.build_actions()
245        };
246        out.extend (actions.drain (1..));
247        debug_assert_eq!(actions.len(), 1);
248        actions.pop().unwrap().1.try_into().unwrap()
249      };
250      let frame_id = subtree.root_node_id().unwrap().clone();
251      subtree.get_mut (&frame_id).unwrap().data_mut().controller.add_bindings (
252        &bindings.get_bindings (controls.as_ref().unwrap_or (&super::CONTROLS))
253      );
254      let label    = switch.label().clone();
255      let button   = {
256        let controller = {
257          let mut controller   = Controller::default();
258          controller.component = switch.into();
259          controller.focus_top = false;
260          controller
261        };
262        let model = {
263          let component = model_data.unwrap_or_else (Default::default);
264          Model { callback_id, component }
265        };
266        Element::new ("Button".to_string(), controller, model, View::default())
267      };
268      let _ = subtree
269        .insert (Node::new (button), InsertBehavior::UnderNode (&frame_id))
270        .unwrap();
271      if let Some (label) = label {
272        let textbox = textbox::WithFrameBuilder::<A>::new (
273          &subtree, &frame_id
274        ) .text (label)
275          .build_element();
276        let _ = subtree
277          .insert (Node::new (textbox), InsertBehavior::UnderNode (&frame_id))
278          .unwrap();
279      }
280      out.push ((parent_id.clone(), Action::Create (subtree, order)));
281      log::trace!("...build actions");
282      out
283    }
284  }
285}