gooey/widget/frame/
mod.rs

1//! Primary container and layout widget.
2//!
3//! A Frame element is a node with a Layout controller and a Canvas view.
4//!
5//! The screen is usually represented by a root frame node. A new screen Frame
6//! can be created with the `screen::tile()` or `screen::pixel()` functions.
7//! This will create a Frame element with zero Canvas coordinates and a free
8//! absolute Layout component. This node can be optionally initialized with a
9//! System control ID for handling system input events such as resizes.
10//!
11//! Child frames can be created with the `free::new()` and `tiled::new()`
12//! functions.
13
14use std::convert::{TryFrom, TryInto};
15use lazy_static::lazy_static;
16
17use crate::geometry;
18use crate::prelude::*;
19
20pub use self::builder::FrameBuilder as Builder;
21
22pub mod free;
23pub mod screen;
24pub mod tiled;
25
26pub type Frame <'element> = Widget <'element, Layout, model::Component, Canvas>;
27
28//
29//  controls
30//
31
32lazy_static!{
33  pub static ref CONTROLS : Controls = controls::Builder::new()
34    .buttons (vec![
35      controls::button::Builtin::ActivatePointer,
36      controls::button::Builtin::FrameClose,
37      controls::button::Builtin::FrameToggleOrientation
38    ].into_iter().map (Into::into).collect::<Vec <_>>().into())
39    .build();
40}
41
42/// Builtin button control 'FrameClose'
43///
44/// Closing a Frame with a Tiled Layout will produce actions to resize sibling
45/// Tiled Frames. The last Action will be the Destroy action for the target
46/// Frame.
47pub fn close (
48  _             : &controls::button::Release,
49  elements      : &Tree <Element>,
50  node_id       : &NodeId,
51  action_buffer : &mut Vec <(NodeId, Action)>
52) {
53  use controller::component::layout;
54  log::trace!("close...");
55  let Widget (layout, _, _) = Frame::try_get (elements, node_id).unwrap();
56  match layout.variant {
57    layout::Variant::Tiled (_) => for (sibling_id, coordinates) in
58      tiled::destroy_coordinates (elements, node_id)
59    {
60      set_coordinates (elements, &sibling_id, &coordinates, None,
61        action_buffer);
62    }
63    layout::Variant::Free (..)  => {}
64  }
65  action_buffer.push ((node_id.clone(), Action::Destroy));
66  log::trace!("...close");
67}
68
69/// Builtin button control 'FrameRefresh'.
70///
71/// Sets the current Layout and refreshes the Coordinates of the target Frame
72/// and any child Frames recursively.
73pub fn refresh (
74  _             : &controls::button::Release,
75  elements      : &Tree <Element>,
76  node_id       : &NodeId,
77  action_buffer : &mut Vec <(NodeId, Action)>
78) {
79  log::trace!("refresh...");
80  let Widget (layout, _, canvas) = Frame::try_get (elements, node_id).unwrap();
81  let coordinate_kind = Some (canvas.coordinates.kind());
82  set_layout (elements, node_id, layout.clone(), coordinate_kind, action_buffer);
83  log::trace!("...refresh");
84}
85
86/// Builtin button control 'FrameToggleOrientation'.
87///
88/// Switches the Orientation (arrangement of Tiled child elements) between
89/// horizontal and vertical.
90pub fn toggle_orientation (
91  _             : &controls::button::Release,
92  elements      : &Tree <Element>,
93  node_id       : &NodeId,
94  action_buffer : &mut Vec <(NodeId, Action)>
95) {
96  log::trace!("toggle_orientation...");
97  let Widget (layout, _, canvas) = Frame::try_get (elements, node_id).unwrap();
98  let (mut orientation, area) = layout.orientation.clone();
99  orientation = orientation.toggle();
100  let layout  = controller::component::Layout {
101    orientation: (orientation, area),
102    .. layout.clone()
103  };
104  let coordinate_kind = Some (canvas.coordinates.kind());
105  set_layout (elements, node_id, layout, coordinate_kind, action_buffer);
106  log::trace!("...toggle_orientation");
107}
108
109/// Builtin button control 'ActivatePointer'.
110///
111/// Given the target node, traverses subtree to find the top-most element that
112/// intersects with the pointer; if the element is in state `Enabled`, then it
113/// is focused. If the element is also a button, then push the button.
114///
115/// Note that this control does not need to be bound to a frame widget; it can
116/// be bound to any arbitrary widget.
117pub fn activate_pointer (
118  release       : &controls::button::Release,
119  elements      : &Tree <Element>,
120  node_id       : &NodeId,
121  action_buffer : &mut Vec <(NodeId, Action)>
122) {
123  log::trace!("activate pointer...");
124  let pointer_position = {
125    let pointer = interface::POINTER.read().unwrap();
126    [pointer.position_horizontal, pointer.position_vertical].into()
127  };
128  let mut last = None;
129  for element_id in elements.traverse_pre_order_ids (node_id).unwrap() {
130    let element = elements.get (&element_id).unwrap().data();
131    if let Ok (Widget (_, _, canvas)) = Frame::try_from (element) {
132      let canvas_aabb = {
133        let aabb = geometry::integer::Aabb2::from (canvas.coordinates);
134        let pixel_aabb =
135          if canvas.coordinates.kind() == coordinates::Kind::Tile {
136            coordinates::tile_to_pixel_aabb (aabb)
137          } else {
138            aabb
139          };
140        geometry::Aabb2::with_minmax (
141          pixel_aabb.min().numcast().unwrap(),
142          pixel_aabb.max().numcast().unwrap())
143      };
144      if canvas_aabb.contains (&pointer_position) {
145        last = Some (element_id);
146      }
147    }
148  }
149  if let Some (element_id) = last {
150    let element = elements.get (&element_id).unwrap().data();
151    if element.controller.state == State::Enabled {
152      action_buffer.push ((element_id.clone(), Action::Focus));
153    }
154    if element.controller.state != State::Disabled {
155      if let Some (child_id) =
156        elements.children_ids (&element_id).unwrap().next()
157      {
158        let child = elements.get (&child_id).unwrap().data();
159        if let Ok (Widget (switch, _, _)) = Button::try_from (child) {
160          if switch.toggle {
161            match switch.state {
162              switch::State::On  =>
163                button::release (&None, elements, &element_id, action_buffer),
164              switch::State::Off =>
165                button::push (release, elements, &element_id, action_buffer)
166            }
167          } else {
168            button::push (release, elements, &element_id, action_buffer)
169          }
170        }
171      }
172    }
173  }
174  log::trace!("...activate pointer");
175}
176
177/// Builtin pointer control 'HotPointer'.
178///
179/// Given the target node, traverses subtree to find the top-most element that
180/// intersects with the pointer; if the element is in state `Enabled`, then it
181/// is focused.
182///
183/// Note that this control does not need to be bound to a frame widget; it can
184/// be bound to any arbitrary widget.
185pub fn hot_pointer (
186  pointer       : &input::Pointer,
187  elements      : &Tree <Element>,
188  node_id       : &NodeId,
189  action_buffer : &mut Vec <(NodeId, Action)>
190) {
191  log::trace!("hot pointer...");
192  debug_assert_eq!(&*interface::POINTER.read().unwrap(), pointer);
193  let pointer_position =
194    [pointer.position_horizontal, pointer.position_vertical].into();
195  let mut last = None;
196  for element_id in elements.traverse_pre_order_ids (node_id).unwrap() {
197    let element = elements.get (&element_id).unwrap().data();
198    if let Ok (Widget (_, _, canvas)) = Frame::try_from (element) {
199      let canvas_aabb = {
200        let aabb = geometry::integer::Aabb2::from (canvas.coordinates);
201        let pixel_aabb =
202          if canvas.coordinates.kind() == coordinates::Kind::Tile {
203            coordinates::tile_to_pixel_aabb (aabb)
204          } else {
205            aabb
206          };
207        geometry::Aabb2::with_minmax (
208          pixel_aabb.min().numcast().unwrap(),
209          pixel_aabb.max().numcast().unwrap())
210      };
211      if canvas_aabb.contains (&pointer_position) {
212        last = Some (element_id);
213      }
214    }
215  }
216  if let Some (element_id) = last {
217    let element = elements.get (&element_id).unwrap().data();
218    if element.controller.state == controller::State::Enabled {
219      action_buffer.push ((element_id.clone(), Action::Focus));
220    }
221  }
222  log::trace!("...hot pointer");
223}
224
225//
226//  crate
227//
228
229/// Sets the given layout of the target frame and refreshes coordinates of the
230/// target frame and any child frames recursively
231pub (crate) fn set_layout (
232  elements            : &Tree <Element>,
233  frame_id            : &NodeId,
234  layout              : controller::component::Layout,
235  coord_kind_override : Option <view::coordinates::Kind>,
236  action_buffer       : &mut Vec <(NodeId, Action)>
237) {
238  log::trace!("set_layout...");
239  { // update layout
240    let new_layout    = layout.clone();
241    let update_layout = Box::new (move |controller : &mut Controller|
242      controller.component = new_layout.into());
243    action_buffer.push (
244      (frame_id.clone(), Action::ModifyController (update_layout)));
245  }
246  // update canvases
247  for (frame_id, coordinates) in
248    new_coordinates (elements, frame_id, &layout, coord_kind_override)
249  {
250    set_coordinates (elements, &frame_id, &coordinates,
251      Some (layout.orientation.clone()), action_buffer);
252  }
253  log::trace!("...set_layout");
254}
255
256//
257//  private
258//
259
260/// Compute the coordinates of the target frame with the given layout, and any
261/// effected siblings (in the case of a tiled layout).
262///
263/// For a `Free` layout, this only depends on the parent frame coordinates.  A
264/// root frame is handled specially: default "parent" coordinates (zero position
265/// and dimensions equal to the new child layout size, or else '1' for relative
266/// sizes) are used since there is no parent.
267///
268/// For a `Tiled` layout, the coordinates will depend on the parent frame
269/// coordinates and orientation, and the weights of any other sibling frames
270/// with `Tiled` layout. It is an error of a `Tiled` layout is used with a root
271/// frame since it has no parent.
272///
273/// TODO: update documentation for supplied coordinates kind that doesn't match
274/// parent coordinates
275fn new_coordinates (
276  elements            : &Tree <Element>,
277  frame_id            : &NodeId,
278  layout              : &controller::component::Layout,
279  coord_kind_override : Option <view::coordinates::Kind>
280) -> Vec <(NodeId, view::Coordinates)> {
281  use controller::size;
282  use controller::component::layout;
283  use view::coordinates::dimensions;
284  use view::component::Canvas;
285  log::trace!("new_coordinates...");
286  let out = match layout.variant {
287    layout::Variant::Free (ref free, ref area) => {
288      let parent_canvas = if elements.root_node_id().unwrap() == frame_id {
289        // root node is handled specially since it has no parent
290        let Widget (_, _, canvas) = Frame::try_get (elements, frame_id).unwrap();
291        let (width, height) = {
292          // root node must not be Tiled layout
293          let (free, _) = layout.variant.clone().try_into().unwrap();
294          let width  = match free.size.width {
295            size::Unsigned::Absolute (w) => w,
296            size::Unsigned::Relative (_) => 1
297          };
298          let height = match free.size.height {
299            size::Unsigned::Absolute (h) => h,
300            size::Unsigned::Relative (_) => 1
301          };
302          (width, height)
303        };
304        let (mut parent, dimensions) =
305          if canvas.coordinates.kind() == coordinates::Kind::Tile {
306            ( Canvas::default_tile(),
307              dimensions::Tile::new_rc (height, width).into()
308            )
309          } else {
310            debug_assert!(canvas.coordinates.kind() == coordinates::Kind::Pixel);
311            ( Canvas::default_pixel(),
312              dimensions::Pixel::new_wh (width, height).into()
313            )
314          };
315        parent.coordinates.set_dimensions (dimensions);
316        parent
317      } else {
318        let Widget (_, _, parent_canvas) =
319          Frame::try_get (elements, elements.get_parent_id (frame_id)).unwrap();
320        parent_canvas.clone()
321      };
322      vec![(
323        frame_id.clone(),
324        free::coordinates (&parent_canvas, &free, &area, coord_kind_override)
325      )]
326    }
327    layout::Variant::Tiled (ref tiled) => {
328      debug_assert!(elements.root_node_id().unwrap() != frame_id,
329        "tiled root frame is unsupported");
330      tiled::new_coordinates (&elements, frame_id, &tiled)
331    }
332  };
333  log::trace!("...new_coordinates");
334  out
335}
336
337/// Updates coordinates of the given frame from the given parent coordinates,
338/// and recursively refreshes all descendents' coordinates
339fn set_coordinates (
340  elements      : &Tree <Element>,
341  frame_id      : &NodeId,
342  coordinates   : &view::Coordinates,
343  orientation   : Option <(controller::Orientation, controller::Area)>,
344  action_buffer : &mut Vec <(NodeId, Action)>
345) {
346  log::trace!("set_coordinates...");
347  { // update canvas
348    let new_coordinates = coordinates.clone();
349    let update_canvas   = Box::new (move |view : &mut View| {
350      use view::component::{Canvas, Kind};
351      let canvas = Canvas::try_ref_mut (&mut view.component).unwrap();
352      canvas.coordinates = new_coordinates;
353    });
354    action_buffer.push ((frame_id.clone(), Action::ModifyView (update_canvas)));
355  }
356  // update children
357  for (child_id, coordinates) in update_children (
358    elements, frame_id, coordinates, orientation, action_buffer
359  ) {
360    set_coordinates (elements, &child_id, &coordinates, None, action_buffer);
361  }
362  log::trace!("...set_coordinates");
363}
364
365/// Returns new coordinates for child frames and push any update actions for
366/// other child nodes resulting from setting the given new coordinates to the
367/// target frame
368fn update_children (
369  elements      : &Tree <Element>,
370  frame_id      : &NodeId,
371  coordinates   : &view::Coordinates,
372  orientation   : Option <(controller::Orientation, controller::Area)>,
373  action_buffer : &mut Vec <(NodeId, Action)>
374) -> Vec <(NodeId, view::Coordinates)> {
375  use view::component::{Body, Canvas, Image, Kind};
376  log::trace!("update_children...");
377  let mut child_coordinates = vec![];
378  let (mut tiled_ids, mut tiled_layouts) = (vec![], vec![]);
379  let node = elements.get (frame_id).unwrap();
380  let Widget (layout, _, canvas) = Frame::try_from (node.data()).unwrap();
381  let new_canvas  = {
382    let coordinates = *coordinates;
383    Canvas { coordinates, .. canvas.clone() }
384  };
385  let orientation = orientation.unwrap_or (layout.orientation.clone());
386  for child_id in node.children().iter() {
387    if let Ok (Widget (layout, _, canvas)) = Frame::try_get (elements, child_id) {
388      // frames
389      use controller::component::layout;
390      match layout.variant {
391        layout::Variant::Free  (ref free, ref area)  => {
392          let coordinate_kind = Some (canvas.coordinates.kind());
393          child_coordinates.push ((
394            child_id.clone(),
395            free::coordinates (&new_canvas, &free, &area, coordinate_kind)
396          ))
397        }
398        layout::Variant::Tiled (ref tiled) => {
399          tiled_ids.push (child_id.clone());
400          tiled_layouts.push (tiled.clone());
401        }
402      }
403    } else if let Ok (Widget (scroll, text, _)) =
404      Textbox::try_get (elements, child_id)
405    {
406      // TODO: enforce scroll limits
407      let new_body    = textbox::body (&new_canvas, scroll, text);
408      let update_body = Box::new (|view : &mut View|{
409        let body = Body::try_ref_mut (&mut view.component).unwrap();
410        *body = new_body;
411      });
412      action_buffer.push ((child_id.clone(), Action::ModifyView (update_body)));
413    } else if let Ok (Widget (scroll, pixmap, _)) =
414      Picture::try_get (elements, child_id)
415    {
416      let new_image    = picture::image (&new_canvas, &scroll, &pixmap);
417      let update_image = Box::new (|view : &mut View|{
418        let image = Image::try_ref_mut (&mut view.component).unwrap();
419        *image = new_image;
420      });
421      action_buffer.push ((child_id.clone(), Action::ModifyView (update_image)));
422    }
423  }
424  child_coordinates.extend (
425    tiled_ids.into_iter().zip (
426      tiled::child_coordinates (&orientation, &new_canvas, tiled_layouts)));
427  log::trace!("...update_children");
428  child_coordinates
429}
430
431//
432//  builder
433//
434
435mod builder {
436  use derive_builder::Builder;
437  use crate::prelude::*;
438
439  #[derive(Builder)]
440  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
441  pub struct Frame <'a, A : Application> {
442    elements    : &'a Tree <Element>,
443    parent_id   : &'a NodeId,
444    #[builder(default)]
445    appearances : controller::Appearances,
446    #[builder(default)]
447    bindings    : Option <&'a controller::Bindings <A>>,
448    #[builder(default)]
449    border      : Option <view::Border>,
450    #[builder(default)]
451    clear_color : canvas::ClearColor,
452    #[builder(default)]
453    disabled    : bool,
454    #[builder(default)]
455    layout      : controller::component::Layout
456  }
457
458  impl <'a, A : Application> FrameBuilder <'a, A> {
459    pub fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
460      FrameBuilder {
461        elements:    Some (elements),
462        parent_id:   Some (parent_id),
463        appearances: None,
464        bindings:    None,
465        border:      None,
466        clear_color: None,
467        disabled:    None,
468        layout:      None
469      }
470    }
471  }
472
473  impl <'a, A : Application> BuildActions for FrameBuilder <'a, A> {
474    /// Returns a create action and any sibling update actions (tile layout
475    /// only).
476    fn build_actions (self) -> Vec<(NodeId, Action)> {
477      use controller::component::layout;
478      use crate::widget::{set_option, BuildElement};
479      log::trace!("build actions...");
480      let Frame {
481        elements, parent_id, appearances, bindings, border, clear_color,
482        disabled, layout
483      } = self.build()
484        .map_err(|err| log::error!("frame builder error: {:?}", err)).unwrap();
485      let out = match layout.variant {
486        layout::Variant::Free (free, area) => {
487          let frame = {
488            let mut free = super::free::Builder::new (elements, parent_id)
489              .appearances (appearances)
490              .area (area)
491              .clear_color (clear_color)
492              .disabled (disabled)
493              .layout (free)
494              .orientation (layout.orientation);
495            set_option!(free, bindings, bindings);
496            set_option!(free, border, border);
497            free.build_element()
498          };
499          vec![(
500            parent_id.clone(),
501            Action::create_singleton (frame, CreateOrder::Append)
502          )]
503        }
504        layout::Variant::Tiled (tiled) => {
505          let mut tiled =
506            super::tiled::Builder::new (elements, parent_id)
507              .appearances (appearances)
508              .clear_color (clear_color)
509              .disabled (disabled)
510              .layout (tiled)
511              .orientation (layout.orientation);
512            set_option!(tiled, bindings, bindings);
513            set_option!(tiled, border, border);
514          tiled.build_actions()
515        }
516      };
517      log::trace!("...build actions");
518      out
519    }
520  }
521}