gooey-rs 0.12.1

Tile-based UI library with audio support
Documentation
//! Button widget.
//!
//! ```text
//! +--------+
//! | frame  |
//! +---+----+
//!     | \----------\
//! +---+----+  +-----+-----+
//! | button |  | (textbox) |
//! +--------+  +-----------+
//! ```
//!
//! If a `label` string is provided, a `Textbox` label will be created as a sibling to
//! the `Button` element.
//!
//! The `push` and `release` control functions target the *frame* node, and assume that
//! the first child is the button node.

use std::sync::LazyLock;

use crate::prelude::*;

pub use self::builder::ButtonBuilder as Builder;

pub type Button <'element> =
  Widget <'element, Switch, model::Component, view::Component>;

//
//  controls
//

pub static CONTROLS : LazyLock <Controls> = LazyLock::new (||
  controls::Builder::new()
    .buttons (vec![
      controls::button::Builtin::ButtonPush,
      controls::button::Builtin::ButtonRelease
    ].into_iter().map (Into::into).collect::<Vec <_>>().into())
    .build()
);

/// Builtin button control ID `ButtonPush`
pub fn push (
  release       : &controls::button::Release,
  elements      : &Tree <Element>,
  frame_id      : &NodeId,
  action_buffer : &mut Vec <(NodeId, Action)>
) {
  log::trace!("push...");
  let frame = elements.get_element (frame_id);
  let mut frame_children = elements.children_ids (frame_id).unwrap();
  let button_id = frame_children.next().unwrap();
  let label_id  = frame_children
    .find (|node_id| Textbox::try_get (elements, node_id).is_ok());
  let Widget (switch, _, _) = Button::try_get (elements, button_id).unwrap();
  let toggle    = switch.toggle;
  if toggle && switch.state == switch::State::On {
    return self::release (&None, elements, frame_id, action_buffer)
  }
  let mut switch = switch.clone();
  let (exit, (enter, appearances, label)) = switch.on();
  // NOTE: we update the switch and appearance before processing trigger events
  // in case the trigger destroys the node
  // switch
  let update_switch = Box::new (
    |controller : &mut Controller| controller.component = switch.into());
  action_buffer
    .push ((button_id.clone(), Action::ModifyController (update_switch)));
  // appearance
  let appearance = Appearance {
    sound: None, .. appearances.get (frame.controller.state.clone()).clone()
  };
  let update_controller = Box::new (
    |controller : &mut Controller| controller.appearances = appearances);
  let update_view = Box::new (
    |view : &mut View| view.appearance = appearance);
  action_buffer
    .push ((frame_id.clone(), Action::ModifyController (update_controller)));
  action_buffer
    .push ((frame_id.clone(), Action::ModifyView (update_view)));
  // exit
  if let Some (trigger) = exit {
    let target = trigger.target.as_ref().unwrap_or (button_id);
    trigger.control_fun.0 (release, elements, target, action_buffer)
  }
  // remove old label
  if let Some (textbox_id) = label_id {
    action_buffer.push ((textbox_id.clone(), Action::Destroy))
  }
  // create label
  if let Some (label) = label {
    let textbox =
      textbox::WithFrameBuilder::<application::Default>::new (elements, frame_id)
        .text (label)
        .build_element();
    action_buffer.push ((frame_id.clone(),
      Action::create_singleton (textbox, CreateOrder::Append)))
  }
  // enter
  if let Some (trigger) = enter {
    let target = trigger.target.as_ref().unwrap_or (button_id);
    trigger.control_fun.0 (release, elements, target, action_buffer)
  }
  if !toggle {
    // return release action
    if let Some (release) = release {
      release.borrow_mut().push (
        (controls::button::Builtin::ButtonRelease.into(), frame_id.clone()));
    }
  }
  log::trace!("...push");
}

/// Builtin button control ID `ButtonRelease`
pub fn release (
  _             : &controls::button::Release,
  elements      : &Tree <Element>,
  frame_id      : &NodeId,
  action_buffer : &mut Vec <(NodeId, Action)>
) {
  log::trace!("release...");
  let frame = elements.get_element (frame_id);
  let mut frame_children = elements.children_ids (frame_id).unwrap();
  let button_id = frame_children.next().unwrap();
  let label_id  = frame_children
    .find (|node_id| Textbox::try_get (elements, node_id).is_ok());
  let Widget (switch, _, _) = Button::try_get (elements, button_id).unwrap();
  let mut switch = switch.clone();
  let (exit, (enter, appearances, label)) = switch.off();
  // exit
  if let Some (trigger) = exit {
    let target = trigger.target.as_ref().unwrap_or (button_id);
    trigger.control_fun.0 (&None, elements, target, action_buffer)
  }
  // remove old label
  if let Some (textbox_id) = label_id {
    action_buffer.push ((textbox_id.clone(), Action::Destroy))
  }
  // create label
  if let Some (label) = label {
    let textbox =
      textbox::WithFrameBuilder::<application::Default>::new (elements, frame_id)
        .text (label)
        .build_element();
    action_buffer.push ((frame_id.clone(),
      Action::create_singleton (textbox, CreateOrder::Append)))
  }
  // enter
  if let Some (trigger) = enter {
    let target = trigger.target.as_ref().unwrap_or (button_id);
    trigger.control_fun.0 (&None, elements, target, action_buffer)
  }
  // switch
  let update_switch = Box::new (
    |controller : &mut Controller| controller.component = switch.into());
  action_buffer
    .push ((button_id.clone(), Action::ModifyController (update_switch)));
  // appearance
  let appearance = Appearance {
    sound: None,
    .. appearances.get (frame.controller.state.clone()).clone()
  };
  let update_controller = Box::new (
    |controller : &mut Controller| controller.appearances = appearances);
  let update_view = Box::new (
    |view : &mut View| view.appearance = appearance);
  action_buffer
    .push ((frame_id.clone(), Action::ModifyController (update_controller)));
  action_buffer
    .push ((frame_id.clone(), Action::ModifyView (update_view)));
  log::trace!("...release");
}

//
//  builder
//

mod builder {
  use derive_builder::Builder;
  use crate::prelude::*;

  #[derive(Builder)]
  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
  struct Button <'a, A : Application> {
    elements     : &'a Tree <Element>,
    parent_id    : &'a NodeId,
    #[builder(default)]
    bindings     : Option <&'a Bindings <A>>,
    #[builder(default)]
    callback_id  : Option <application::CallbackId>,
    #[builder(default)]
    controls     : Option <Controls>,
    #[builder(default)]
    frame_layout : Layout,
    #[builder(default)]
    frame_border : Option <Border>,
    #[builder(default)]
    model_data   : Option <model::Component>,
    #[builder(default)]
    name         : Option <String>,
    #[builder(default)]
    switch       : Switch
  }

  impl <'a, A : Application> ButtonBuilder <'a, A> {
    pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
      ButtonBuilder {
        elements:          Some (elements),
        parent_id:         Some (parent_id),
        bindings:          None,
        callback_id:       None,
        controls:          None,
        frame_border:      None,
        frame_layout:      None,
        model_data:        None,
        name:              None,
        switch:            None
      }
    }
  }

  impl <A : Application> BuildActions for ButtonBuilder <'_, A> {
    /// Creates a new button with the given switch state, frame layout and border, and
    /// optional text label.
    ///
    /// The `Create` action will be returned as the last action.
    fn build_actions (self) -> Vec<(NodeId, Action)> {
      use std::convert::TryInto;
      use crate::tree::InsertBehavior;
      log::trace!("build actions...");
      let Button {
        elements, parent_id, bindings, callback_id, controls, frame_border,
        frame_layout, model_data, name, switch
      } = self.build()
        .map_err(|err| log::error!("button builder error: {err:?}")).unwrap();
      let bindings_empty = Bindings::empty();
      let bindings = bindings.unwrap_or (&bindings_empty);
      let mut out = vec![];
      let (mut subtree, order) = {
        let mut actions = {
          let mut frame = frame::Builder::new (elements, parent_id)
            .appearances (switch.appearances().clone())
            .bindings (bindings)
            .layout (frame_layout);
          set_option!(frame, border, frame_border);
          frame.build_actions()
        };
        out.extend (actions.drain (1..));
        debug_assert_eq!(actions.len(), 1);
        actions.pop().unwrap().1.try_into().unwrap()
      };
      let frame_id = subtree.root_node_id().unwrap().clone();
      subtree.get_mut (&frame_id).unwrap().data_mut().controller.add_bindings (
        &bindings.get_bindings (controls.as_ref().unwrap_or (&super::CONTROLS))
      );
      let label    = switch.label().clone();
      let button   = {
        let controller = Controller {
          component: switch.into(),
          focus_top: false,
          .. Controller::default()
        };
        let model = {
          let component = model_data.unwrap_or_else (Default::default);
          Model { callback_id, component }
        };
        let name = name.unwrap_or ("Button".to_string());
        Element::new (name, controller, model, View::default())
      };
      let _ = subtree
        .insert (Node::new (button), InsertBehavior::UnderNode (&frame_id))
        .unwrap();
      if let Some (label) = label {
        let textbox = textbox::WithFrameBuilder::<A>::new (&subtree, &frame_id)
          .text (label)
          .build_element();
        let _ = subtree
          .insert (Node::new (textbox), InsertBehavior::UnderNode (&frame_id))
          .unwrap();
      }
      out.push ((parent_id.clone(), Action::Create (subtree, order)));
      log::trace!("...build actions");
      out
    }
  }
}