Skip to main content

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> =
26  Widget <'element, 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 (elements, frame_id)
93        .text (label)
94        .build_element();
95    action_buffer.push ((frame_id.clone(),
96      Action::create_singleton (textbox, CreateOrder::Append)))
97  }
98  // enter
99  if let Some (trigger) = enter {
100    let target = trigger.target.as_ref().unwrap_or (button_id);
101    trigger.control_fun.0 (release, elements, target, action_buffer)
102  }
103  if !toggle {
104    // return release action
105    if let Some (release) = release {
106      release.borrow_mut().push (
107        (controls::button::Builtin::ButtonRelease.into(), frame_id.clone()));
108    }
109  }
110  log::trace!("...push");
111}
112
113/// Builtin button control ID `ButtonRelease`
114pub fn release (
115  _             : &controls::button::Release,
116  elements      : &Tree <Element>,
117  frame_id      : &NodeId,
118  action_buffer : &mut Vec <(NodeId, Action)>
119) {
120  log::trace!("release...");
121  let frame = elements.get_element (frame_id);
122  let mut frame_children = elements.children_ids (frame_id).unwrap();
123  let button_id = frame_children.next().unwrap();
124  let label_id  = frame_children
125    .find (|node_id| Textbox::try_get (elements, node_id).is_ok());
126  let Widget (switch, _, _) = Button::try_get (elements, button_id).unwrap();
127  let mut switch = switch.clone();
128  let (exit, (enter, appearances, label)) = switch.off();
129  // exit
130  if let Some (trigger) = exit {
131    let target = trigger.target.as_ref().unwrap_or (button_id);
132    trigger.control_fun.0 (&None, elements, target, action_buffer)
133  }
134  // remove old label
135  if let Some (textbox_id) = label_id {
136    action_buffer.push ((textbox_id.clone(), Action::Destroy))
137  }
138  // create label
139  if let Some (label) = label {
140    let textbox =
141      textbox::WithFrameBuilder::<application::Default>::new (elements, frame_id)
142        .text (label)
143        .build_element();
144    action_buffer.push ((frame_id.clone(),
145      Action::create_singleton (textbox, CreateOrder::Append)))
146  }
147  // enter
148  if let Some (trigger) = enter {
149    let target = trigger.target.as_ref().unwrap_or (button_id);
150    trigger.control_fun.0 (&None, elements, target, action_buffer)
151  }
152  // switch
153  let update_switch = Box::new (
154    |controller : &mut Controller| controller.component = switch.into());
155  action_buffer
156    .push ((button_id.clone(), Action::ModifyController (update_switch)));
157  // appearance
158  let appearance = Appearance {
159    sound: None,
160    .. appearances.get (frame.controller.state.clone()).clone()
161  };
162  let update_controller = Box::new (
163    |controller : &mut Controller| controller.appearances = appearances);
164  let update_view = Box::new (
165    |view : &mut View| view.appearance = appearance);
166  action_buffer
167    .push ((frame_id.clone(), Action::ModifyController (update_controller)));
168  action_buffer
169    .push ((frame_id.clone(), Action::ModifyView (update_view)));
170  log::trace!("...release");
171}
172
173//
174//  builder
175//
176
177mod builder {
178  use derive_builder::Builder;
179  use crate::prelude::*;
180
181  #[derive(Builder)]
182  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
183  struct Button <'a, A : Application> {
184    elements     : &'a Tree <Element>,
185    parent_id    : &'a NodeId,
186    #[builder(default)]
187    bindings     : Option <&'a Bindings <A>>,
188    #[builder(default)]
189    callback_id  : Option <application::CallbackId>,
190    #[builder(default)]
191    controls     : Option <Controls>,
192    #[builder(default)]
193    frame_layout : Layout,
194    #[builder(default)]
195    frame_border : Option <Border>,
196    #[builder(default)]
197    model_data   : Option <model::Component>,
198    #[builder(default)]
199    name         : Option <String>,
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        name:              None,
216        switch:            None
217      }
218    }
219  }
220
221  impl <A : Application> BuildActions for ButtonBuilder <'_, A> {
222    /// Creates a new button with the given switch state, frame layout and border, and
223    /// optional text label.
224    ///
225    /// The `Create` action will be returned as the last action.
226    fn build_actions (self) -> Vec<(NodeId, Action)> {
227      use std::convert::TryInto;
228      use crate::tree::InsertBehavior;
229      log::trace!("build actions...");
230      let Button {
231        elements, parent_id, bindings, callback_id, controls, frame_border,
232        frame_layout, model_data, name, switch
233      } = self.build()
234        .map_err(|err| log::error!("button builder error: {err:?}")).unwrap();
235      let bindings_empty = Bindings::empty();
236      let bindings = bindings.unwrap_or (&bindings_empty);
237      let mut out = vec![];
238      let (mut subtree, order) = {
239        let mut actions = {
240          let mut frame = frame::Builder::new (elements, parent_id)
241            .appearances (switch.appearances().clone())
242            .bindings (bindings)
243            .layout (frame_layout);
244          set_option!(frame, border, frame_border);
245          frame.build_actions()
246        };
247        out.extend (actions.drain (1..));
248        debug_assert_eq!(actions.len(), 1);
249        actions.pop().unwrap().1.try_into().unwrap()
250      };
251      let frame_id = subtree.root_node_id().unwrap().clone();
252      subtree.get_mut (&frame_id).unwrap().data_mut().controller.add_bindings (
253        &bindings.get_bindings (controls.as_ref().unwrap_or (&super::CONTROLS))
254      );
255      let label    = switch.label().clone();
256      let button   = {
257        let controller = Controller {
258          component: switch.into(),
259          focus_top: false,
260          .. Controller::default()
261        };
262        let model = {
263          let component = model_data.unwrap_or_else (Default::default);
264          Model { callback_id, component }
265        };
266        let name = name.unwrap_or ("Button".to_string());
267        Element::new (name, controller, model, View::default())
268      };
269      let _ = subtree
270        .insert (Node::new (button), InsertBehavior::UnderNode (&frame_id))
271        .unwrap();
272      if let Some (label) = label {
273        let textbox = textbox::WithFrameBuilder::<A>::new (&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}