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