Skip to main content

gooey/widget/frame/
tiled.rs

1use std::convert::TryFrom;
2use std::sync::LazyLock;
3use log;
4
5use crate::prelude::*;
6
7use super::set_layout;
8
9pub use self::builder::TiledBuilder as Builder;
10
11//
12//  controls
13//
14
15pub static CONTROLS : LazyLock <Controls> = LazyLock::new (||
16  controls::Builder::new()
17    .buttons ({
18      let mut v : Vec <controls::Button> = vec![
19        controls::button::Builtin::FrameTiledIncreaseSize,
20        controls::button::Builtin::FrameTiledDecreaseSize
21      ].into_iter().map (Into::into).collect();
22      v.extend (super::CONTROLS.buttons.iter().copied());
23      v.into()
24    })
25    .build()
26);
27
28/// Builtin button control `FrameTiledIncreaseSize`.
29///
30/// Frame must have tiled layout.
31pub fn increase_size (
32  _             : &controls::button::Release,
33  elements      : &Tree <Element>,
34  node_id       : &NodeId,
35  action_buffer : &mut Vec <(NodeId, Action)>
36) {
37  log::trace!("increase_size...");
38  let Widget (layout, _, canvas) = Frame::try_get (elements, node_id).unwrap();
39  let coordinate_kind = Some (canvas.coordinates.kind());
40  let layout = {
41    use controller::component::{layout, Layout};
42    let mut tiled = layout::Tiled::try_from (layout.variant.clone()).unwrap();
43    tiled.modify_size (1);
44    Layout { variant: tiled.into(), .. layout.clone() }
45  };
46  set_layout (elements, node_id, layout, coordinate_kind, action_buffer);
47  log::trace!("...increase_size");
48}
49
50/// Builtin button control `FrameTiledDecreaseSize`.
51///
52/// Frame must have tiled layout.
53pub fn decrease_size (
54  _             : &controls::button::Release,
55  elements      : &Tree <Element>,
56  node_id       : &NodeId,
57  action_buffer : &mut Vec <(NodeId, Action)>
58) {
59  log::trace!("decrease_size...");
60  let Widget (layout, _, canvas) = Frame::try_get (elements, node_id).unwrap();
61  let coordinate_kind = Some (canvas.coordinates.kind());
62  let layout  = {
63    use controller::component::{layout, Layout};
64    let mut tiled = layout::Tiled::try_from (layout.variant.clone()).unwrap();
65    tiled.modify_size (-1);
66    Layout { variant: tiled.into(), .. layout.clone() }
67  };
68  set_layout (elements, node_id, layout, coordinate_kind, action_buffer);
69  log::trace!("...decrease_size");
70}
71
72//
73//  crate
74//
75
76/// Returns the new coordinates of the target frame and any tiled siblings as
77/// the result of setting the tiled layout
78pub (crate) fn new_coordinates (
79  elements : &Tree <Element>,
80  frame_id : &NodeId,
81  layout   : &layout::Tiled
82) -> Vec <(NodeId, Coordinates)> {
83  use controller::component::layout;
84  log::trace!("coordinates...");
85  let parent_id   = elements.get_parent_id (frame_id);
86  let Widget (parent_layout, _, parent_canvas) = Frame::try_get (elements, parent_id)
87    .unwrap();
88  let parent_node = elements.get (parent_id).unwrap();
89  let mut total_weights  = 0;
90  let mut total_absolute = 0;
91  let mut add_layout     = |layout : &layout::Tiled| match layout {
92    layout::Tiled::Weighted (weight) => total_weights  += weight.get(),
93    layout::Tiled::Absolute (size)   => total_absolute += size.get()
94  };
95  let tiled_children = parent_node.children().iter().filter_map (|child_id| {
96    let Widget (child_layout, _, _) = Frame::try_get (elements, child_id).ok()?;
97    if child_id == frame_id {
98      // use the given tiled layout for the target frame
99      add_layout (layout);
100      Some ((child_id.clone(), layout))
101    } else if let controller::component::layout::Variant::Tiled (tiled) =
102      &child_layout.variant
103    {
104      add_layout (tiled);
105      Some ((child_id.clone(), tiled))
106    } else {
107      None
108    }
109  }).collect::<Vec<_>>();
110  let mut out = vec![];
111  let mut start_weight   = 0;
112  let mut start_absolute = 0;
113  for (id, layout) in tiled_children.into_iter() {
114    let coordinates = coordinates (
115      &parent_layout.orientation, parent_canvas, layout, &mut start_weight,
116      &mut start_absolute, total_weights, total_absolute);
117    out.push ((id, coordinates));
118  }
119  log::trace!("...coordinates");
120  out
121}
122
123/// Returns the coordinates of any tiled siblings resulting from destroying the target
124/// tiled frame.
125pub (crate) fn destroy_coordinates (
126  elements : &Tree <Element>,
127  frame_id : &NodeId
128) -> Vec <(NodeId, Coordinates)> {
129  use controller::component::layout;
130  log::trace!("destroy_coordinates...");
131  let parent_id = elements.get_parent_id (frame_id);
132  let Widget (parent_layout, _, parent_canvas) = Frame::try_get (elements, parent_id)
133    .unwrap();
134  let parent_node = elements.get (parent_id).unwrap();
135  let mut total_weights  = 0;
136  let mut total_absolute = 0;
137  let mut add_layout     = |layout : &layout::Tiled| match layout {
138    layout::Tiled::Weighted (weight) => total_weights  += weight.get(),
139    layout::Tiled::Absolute (size)   => total_absolute += size.get()
140  };
141  let tiled_children = parent_node.children().iter().filter_map (|child_id| {
142    if child_id == frame_id {
143      None
144    } else {
145      let Widget (child_layout, _, _) = Frame::try_get (elements, child_id).ok()?;
146      if let controller::component::layout::Variant::Tiled (tiled) =
147        &child_layout.variant
148      {
149        add_layout (tiled);
150        Some ((child_id.clone(), tiled))
151      } else {
152        None
153      }
154    }
155  }).collect::<Vec <_>>();
156  let mut out = vec![];
157  let mut start_weight = 0;
158  let mut start_absolute = 0;
159  for (id, layout) in tiled_children.into_iter() {
160    let coordinates = coordinates (
161      &parent_layout.orientation, parent_canvas, layout, &mut start_weight,
162      &mut start_absolute, total_weights, total_absolute);
163    out.push ((id, coordinates));
164  }
165  log::trace!("...destroy_coordinates");
166  out
167}
168
169/// Compute the child coordinates for the given parent layout and orientation and list
170/// of tiled child weights
171pub (crate) fn child_coordinates (
172  orientation : &(Orientation, Area),
173  canvas      : &Canvas,
174  layouts     : Vec <layout::Tiled>
175) -> Vec <Coordinates> {
176  use controller::component::layout;
177  let mut child_coordinates = vec![];
178  let mut total_weights  = 0;
179  let mut total_absolute = 0;
180  let add_layout = |layout : &layout::Tiled| match layout {
181    layout::Tiled::Weighted (weight) => total_weights  += weight.get(),
182    layout::Tiled::Absolute (size)   => total_absolute += size.get()
183  };
184  layouts.iter().for_each (add_layout);
185  let mut start_weight = 0;
186  let mut start_absolute = 0;
187  for layout in layouts {
188    child_coordinates.push (
189      coordinates (orientation, canvas, &layout, &mut start_weight,
190        &mut start_absolute, total_weights, total_absolute));
191  }
192  child_coordinates
193}
194
195//
196//  private
197//
198
199fn coordinates (
200  (parent_orientation, parent_area) : &(Orientation, Area),
201  parent_canvas  : &Canvas,
202  child_layout   : &layout::Tiled,
203  start_weight   : &mut u32,
204  start_absolute : &mut u32,
205  total_weights  : u32,
206  total_absolute : u32
207) -> Coordinates {
208  use controller::component::layout;
209  use view::{coordinates, dimensions, position};
210  log::trace!("coordinates...");
211  let parent_coordinates = match parent_area {
212    Area::Exterior => parent_canvas.coordinates,
213    Area::Interior => parent_canvas.body_coordinates()
214  };
215  let (position_horizontal, position_vertical, width, height) =
216    match parent_orientation {
217      Orientation::Horizontal => {
218        let height = parent_coordinates.dimensions_vertical();
219        let parent_width = parent_coordinates.dimensions_horizontal();
220        let position_vertical = parent_coordinates.position_vertical();
221        let parent_position_horizontal = parent_coordinates.position_horizontal();
222        let position_horizontal = parent_position_horizontal + *start_absolute as i32 +
223          ( (*start_weight as f64 / total_weights as f64) *
224            parent_width.saturating_sub (total_absolute) as f64
225          ) as i32;
226        let width = match child_layout {
227          layout::Tiled::Weighted (weight) => {
228            let weight = weight.get();
229            *start_weight += weight;
230            let next_position = parent_position_horizontal + *start_absolute as i32 +
231              ( (*start_weight as f64 / total_weights as f64) *
232                parent_width.saturating_sub (total_absolute) as f64
233              ) as i32;
234            (next_position - position_horizontal) as u32
235          }
236          layout::Tiled::Absolute (size) => {
237            let size = size.get();
238            *start_absolute += size;
239            size
240          }
241        };
242        (position_horizontal, position_vertical, width, height)
243      }
244      Orientation::Vertical => {
245        let width = parent_coordinates.dimensions_horizontal();
246        let parent_height = parent_coordinates.dimensions_vertical();
247        let position_horizontal = parent_coordinates.position_horizontal();
248        let parent_position_vertical = parent_coordinates.position_vertical();
249        let position_vertical = parent_position_vertical + *start_absolute as i32 +
250          ( (*start_weight as f64 / total_weights as f64) *
251            parent_height.saturating_sub (total_absolute) as f64
252          ) as i32;
253        let height = match child_layout {
254          layout::Tiled::Weighted (weight) => {
255            let weight = weight.get();
256            *start_weight += weight;
257            let next_position = parent_position_vertical + *start_absolute as i32 +
258              ( (*start_weight as f64 / total_weights as f64) *
259                parent_height.saturating_sub (total_absolute) as f64
260              ) as i32;
261            (next_position - position_vertical) as u32
262          }
263          layout::Tiled::Absolute (size) => {
264            let size = size.get();
265            *start_absolute += size;
266            size
267          }
268        };
269        (position_horizontal, position_vertical, width, height)
270      }
271    };
272  let out = if parent_coordinates.kind() == coordinates::Kind::Tile {
273    ( position::Tile::new_rc (position_vertical, position_horizontal),
274      dimensions::Tile::new_rc (height, width)
275    ).into()
276  } else {
277    debug_assert!(parent_coordinates.kind() == coordinates::Kind::Pixel);
278    ( position::Pixel::new_xy (position_horizontal, position_vertical),
279      dimensions::Pixel::new_wh (width, height)
280    ).into()
281  };
282  log::trace!("...coordinates");
283  out
284}
285
286//
287//  builder
288//
289
290mod builder {
291  use derive_builder::Builder;
292  use crate::prelude::*;
293  use frame::set_coordinates;
294
295  #[derive(Builder)]
296  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
297  struct Tiled <'a, A : Application> {
298    elements     : &'a Tree <Element>,
299    parent_id    : &'a NodeId,
300    #[builder(default)]
301    appearances  : Appearances,
302    #[builder(default)]
303    bindings     : Option <&'a Bindings <A>>,
304    #[builder(default)]
305    border       : Option <Border>,
306    #[builder(default)]
307    clear_color  : canvas::ClearColor,
308    #[builder(default)]
309    create_order : CreateOrder,
310    #[builder(default)]
311    disabled     : bool,
312    #[builder(default)]
313    layout       : layout::Tiled,
314    #[builder(default)]
315    name         : Option <String>,
316    #[builder(default)]
317    orientation  : (Orientation, Area),
318  }
319
320  impl <'a, A : Application> TiledBuilder <'a, A> {
321    pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
322      TiledBuilder {
323        elements:     Some (elements),
324        parent_id:    Some (parent_id),
325        appearances:  None,
326        bindings:     None,
327        border:       None,
328        clear_color:  None,
329        create_order: None,
330        disabled:     None,
331        layout:       None,
332        name:         None,
333        orientation:  None
334      }
335    }
336  }
337
338  impl <A : Application> BuildActions for TiledBuilder <'_, A> {
339    /// Create a new tiled frame that is attached to the given parent frame.
340    ///
341    /// This returns the creation action, and any modifications required to tiled
342    /// sibling frames attached to the same parent.
343    fn build_actions (self) -> Vec <(NodeId, Action)> {
344      use view::component::Canvas;
345      log::trace!("build actions...");
346      let Tiled {
347        elements, parent_id, appearances, bindings, border, clear_color,
348        create_order, disabled, layout, name, orientation
349      } = self.build()
350        .map_err(|err| log::error!("frame free builder error: {err:?}"))
351        .unwrap();
352      let bindings_empty = Bindings::empty();
353      let bindings = bindings.unwrap_or (&bindings_empty)
354        .get_bindings (&super::CONTROLS);
355      let (new_coordinates, sibling_coordinates) =
356        create_coordinates (elements, parent_id, &layout, create_order);
357      let frame = {
358        use controller::component::Layout;
359        let controller = {
360          let mut controller     = Controller::with_bindings (&bindings);
361          controller.component   = Layout {
362            orientation, variant: layout.into()
363          }.into();
364          controller.appearances = appearances;
365          controller
366        };
367        let view = {
368          let coordinates = new_coordinates;
369          let appearance  = controller.get_appearance().clone();
370          let component   = Canvas { coordinates, clear_color, border }.into();
371          View { component, appearance, .. View::default() }
372        };
373        let name = name.unwrap_or ("Frame".to_string());
374        let mut frame = Element::new (name, controller, Model::default(), view);
375        let parent_node = elements.get (parent_id).unwrap();
376        if parent_node.data().controller.state == State::Disabled || disabled {
377          frame.disable()
378        }
379        frame
380      };
381      let mut out = vec![
382        (parent_id.clone(), Action::create_singleton (frame, create_order))
383      ];
384      for (id, coordinates) in sibling_coordinates.into_iter() {
385        set_coordinates (elements, &id, &coordinates, None, &mut out);
386      }
387      log::trace!("...build actions");
388      out
389    }
390  }
391
392  /// Returns the `NodeId` and coordinates of any tiled child frames of the given parent
393  /// frame resulting from creating a new child frame with the given tiled weight.
394  fn create_coordinates (
395    elements        : &Tree <Element>,
396    parent_frame_id : &NodeId,
397    layout          : &layout::Tiled,
398    order           : CreateOrder
399  ) -> (Coordinates, Vec <(NodeId, Coordinates)>) {
400    use controller::component::layout;
401    log::trace!("create_coordinates...");
402    let Widget (parent_layout, _, parent_canvas) =
403      Frame::try_get (elements, parent_frame_id).unwrap();
404    let parent_node = elements.get (parent_frame_id).unwrap();
405    let mut total_weights  = 0;
406    let mut total_absolute = 0;
407    let mut add_layout     = |layout : &layout::Tiled| match layout {
408      layout::Tiled::Weighted (weight) => total_weights  += weight.get(),
409      layout::Tiled::Absolute (size)   => total_absolute += size.get()
410    };
411    add_layout (layout);
412    let sibling_layouts = parent_node.children().iter().map (|child_id| {
413      let Widget (child_layout, _, _) = Frame::try_get (elements, child_id).ok()?;
414      if let layout::Variant::Tiled (tiled) = &child_layout.variant {
415        add_layout (tiled);
416        Some ((child_id, tiled))
417      } else {
418        None
419      }
420    }).collect::<Vec <_>>();
421    let order_index = match order {
422      CreateOrder::Prepend        => Some (0),
423      CreateOrder::NthSibling (n) => Some (n),
424      CreateOrder::Append         => None
425    };
426    let mut sibling_coordinates = vec![];
427    let mut start_weight = 0;
428    let mut start_absolute = 0;
429    let mut new_coordinates = None;
430    let mut index = 0;
431    // sibling tiled frames
432    for (i, maybe_tiled) in sibling_layouts.into_iter().enumerate() {
433      if order_index == Some (i as u32) {
434        new_coordinates = Some (super::coordinates (
435          &parent_layout.orientation, parent_canvas, layout, &mut start_weight,
436          &mut start_absolute, total_weights, total_absolute
437        ));
438      }
439      if let Some ((id, tiled)) = maybe_tiled {
440        let coordinates = super::coordinates (
441          &parent_layout.orientation, parent_canvas, tiled, &mut start_weight,
442          &mut start_absolute, total_weights, total_absolute);
443        sibling_coordinates.push ((id.clone(), coordinates));
444      }
445      index = i + 1;
446    }
447    if new_coordinates.is_none() {
448      if cfg!(debug_assertions) && let Some (n) = order_index {
449        debug_assert_eq!(n, index as u32);
450      }
451      new_coordinates = Some (super::coordinates (
452        &parent_layout.orientation, parent_canvas, layout,
453        &mut start_weight, &mut start_absolute, total_weights, total_absolute
454      ));
455    }
456    log::trace!("...create_coordinates");
457    (new_coordinates.unwrap(), sibling_coordinates)
458  }
459}