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().cloned());
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
124/// target 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) =
133    Frame::try_get (elements, parent_id).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)
146        .ok()?;
147      if let controller::component::layout::Variant::Tiled (tiled) =
148        &child_layout.variant
149      {
150        add_layout (tiled);
151        Some ((child_id.clone(), tiled))
152      } else {
153        None
154      }
155    }
156  }).collect::<Vec <_>>();
157  let mut out = vec![];
158  let mut start_weight = 0;
159  let mut start_absolute = 0;
160  for (id, layout) in tiled_children.into_iter() {
161    let coordinates = coordinates (
162      &parent_layout.orientation, parent_canvas, layout, &mut start_weight,
163      &mut start_absolute, total_weights, total_absolute);
164    out.push ((id, coordinates));
165  }
166  log::trace!("...destroy_coordinates");
167  out
168}
169
170/// Compute the child coordinates for the given parent layout and orientation
171/// and list of tiled child weights
172pub (crate) fn child_coordinates (
173  orientation : &(Orientation, Area),
174  canvas      : &Canvas,
175  layouts     : Vec <layout::Tiled>
176) -> Vec <Coordinates> {
177  use controller::component::layout;
178  let mut child_coordinates = vec![];
179  let mut total_weights  = 0;
180  let mut total_absolute = 0;
181  let add_layout = |layout : &layout::Tiled| match layout {
182    layout::Tiled::Weighted (weight) => total_weights  += weight.get(),
183    layout::Tiled::Absolute (size)   => total_absolute += size.get()
184  };
185  layouts.iter().for_each (add_layout);
186  let mut start_weight = 0;
187  let mut start_absolute = 0;
188  for layout in layouts {
189    child_coordinates.push (
190      coordinates (orientation, canvas, &layout, &mut start_weight,
191        &mut start_absolute, total_weights, total_absolute));
192  }
193  child_coordinates
194}
195
196//
197//  private
198//
199
200fn coordinates (
201  (parent_orientation, parent_area) :
202    &(Orientation, Area),
203  parent_canvas  : &Canvas,
204  child_layout   : &layout::Tiled,
205  start_weight   : &mut u32,
206  start_absolute : &mut u32,
207  total_weights  : u32,
208  total_absolute : u32
209) -> Coordinates {
210  use controller::component::layout;
211  use view::{coordinates, dimensions, position};
212  log::trace!("coordinates...");
213  let parent_coordinates = match parent_area {
214    Area::Exterior => parent_canvas.coordinates,
215    Area::Interior => parent_canvas.body_coordinates()
216  };
217  let (position_horizontal, position_vertical, width, height) =
218    match parent_orientation {
219      Orientation::Horizontal => {
220        let height = parent_coordinates.dimensions_vertical();
221        let parent_width = parent_coordinates.dimensions_horizontal();
222        let position_vertical = parent_coordinates.position_vertical();
223        let parent_position_horizontal = parent_coordinates.position_horizontal();
224        let position_horizontal = parent_position_horizontal +
225          *start_absolute as i32 +
226          ((*start_weight as f64 / total_weights as f64) *
227           parent_width.saturating_sub (total_absolute) as f64)
228          as i32;
229        let width = match child_layout {
230          layout::Tiled::Weighted (weight) => {
231            let weight = weight.get();
232            *start_weight += weight;
233            let next_position = parent_position_horizontal +
234              *start_absolute as i32 +
235              ((*start_weight as f64 / total_weights as f64) *
236               parent_width.saturating_sub (total_absolute) as f64)
237              as i32;
238            (next_position - position_horizontal) as u32
239          }
240          layout::Tiled::Absolute (size) => {
241            let size = size.get();
242            *start_absolute += size;
243            size
244          }
245        };
246        (position_horizontal, position_vertical, width, height)
247      }
248      Orientation::Vertical => {
249        let width = parent_coordinates.dimensions_horizontal();
250        let parent_height = parent_coordinates.dimensions_vertical();
251        let position_horizontal = parent_coordinates.position_horizontal();
252        let parent_position_vertical = parent_coordinates.position_vertical();
253        let position_vertical = parent_position_vertical +
254          *start_absolute as i32 +
255          ((*start_weight as f64 / total_weights as f64) *
256            parent_height.saturating_sub (total_absolute) as f64)
257          as i32;
258        let height = match child_layout {
259          layout::Tiled::Weighted (weight) => {
260            let weight = weight.get();
261            *start_weight += weight;
262            let next_position = parent_position_vertical +
263              *start_absolute as i32 +
264              ((*start_weight as f64 / total_weights as f64) *
265                parent_height.saturating_sub (total_absolute) as f64)
266              as i32;
267            (next_position - position_vertical) as u32
268          }
269          layout::Tiled::Absolute (size) => {
270            let size = size.get();
271            *start_absolute += size;
272            size
273          }
274        };
275        (position_horizontal, position_vertical, width, height)
276      }
277    };
278  let out = if parent_coordinates.kind() == coordinates::Kind::Tile {
279    ( position::Tile::new_rc (position_vertical, position_horizontal),
280      dimensions::Tile::new_rc (height, width)
281    ).into()
282  } else {
283    debug_assert!(parent_coordinates.kind() == coordinates::Kind::Pixel);
284    ( position::Pixel::new_xy (position_horizontal, position_vertical),
285      dimensions::Pixel::new_wh (width, height)
286    ).into()
287  };
288  log::trace!("...coordinates");
289  out
290}
291
292//
293//  builder
294//
295
296mod builder {
297  use derive_builder::Builder;
298  use crate::prelude::*;
299  use frame::set_coordinates;
300
301  #[derive(Builder)]
302  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
303  struct Tiled <'a, A : Application> {
304    elements     : &'a Tree <Element>,
305    parent_id    : &'a NodeId,
306    #[builder(default)]
307    appearances  : Appearances,
308    #[builder(default)]
309    bindings     : Option <&'a Bindings <A>>,
310    #[builder(default)]
311    border       : Option <Border>,
312    #[builder(default)]
313    clear_color  : canvas::ClearColor,
314    #[builder(default)]
315    create_order : CreateOrder,
316    #[builder(default)]
317    disabled     : bool,
318    #[builder(default)]
319    layout       : layout::Tiled,
320    #[builder(default)]
321    orientation  : (Orientation, Area),
322  }
323
324  impl <'a, A : Application> TiledBuilder <'a, A> {
325    pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
326      TiledBuilder {
327        elements:     Some (elements),
328        parent_id:    Some (parent_id),
329        appearances:  None,
330        bindings:     None,
331        border:       None,
332        clear_color:  None,
333        create_order: None,
334        disabled:     None,
335        layout:       None,
336        orientation:  None
337      }
338    }
339  }
340
341  impl <A : Application> BuildActions for TiledBuilder <'_, A> {
342    /// Create a new tiled frame that is attached to the given parent frame.
343    ///
344    /// This returns the creation action, and any modifications required to
345    /// tiled sibling frames attached to the same parent.
346    fn build_actions (self) -> Vec <(NodeId, Action)> {
347      use view::component::Canvas;
348      log::trace!("build actions...");
349      let Tiled {
350        elements, parent_id, appearances, bindings, border, clear_color,
351        create_order, disabled, layout, orientation
352      } = self.build()
353        .map_err(|err| log::error!("frame free builder error: {err:?}"))
354        .unwrap();
355      let bindings_empty = Bindings::empty();
356      let bindings = bindings.unwrap_or (&bindings_empty)
357        .get_bindings (&super::CONTROLS);
358      let (new_coordinates, sibling_coordinates) =
359        create_coordinates (elements, parent_id, &layout, create_order);
360      let frame = {
361        use controller::component::Layout;
362        let controller = {
363          let mut controller     = Controller::with_bindings (&bindings);
364          controller.component   = Layout {
365            orientation, variant: layout.into()
366          }.into();
367          controller.appearances = appearances;
368          controller
369        };
370        let view = {
371          let coordinates = new_coordinates;
372          let appearance  = controller.get_appearance().clone();
373          let component   = Canvas { coordinates, clear_color, border }.into();
374          View { component, appearance, .. View::default() }
375        };
376        let mut frame =
377          Element::new ("Frame".to_string(), controller, Model::default(), view);
378        let parent_node = elements.get (parent_id).unwrap();
379        if parent_node.data().controller.state == State::Disabled ||
380          disabled
381        {
382          frame.disable()
383        }
384        frame
385      };
386      let mut out = vec![
387        (parent_id.clone(), Action::create_singleton (frame, create_order))
388      ];
389      for (id, coordinates) in sibling_coordinates.into_iter() {
390        set_coordinates (elements, &id, &coordinates, None, &mut out);
391      }
392      log::trace!("...build actions");
393      out
394    }
395  }
396
397  /// Returns the `NodeId` and coordinates of any tiled child frames of the given parent
398  /// frame resulting from creating a new child frame with the given tiled weight.
399  fn create_coordinates (
400    elements        : &Tree <Element>,
401    parent_frame_id : &NodeId,
402    layout          : &layout::Tiled,
403    order           : CreateOrder
404  ) -> (Coordinates, Vec <(NodeId, Coordinates)>) {
405    use controller::component::layout;
406    log::trace!("create_coordinates...");
407    let Widget (parent_layout, _, parent_canvas) =
408      Frame::try_get (elements, parent_frame_id).unwrap();
409    let parent_node = elements.get (parent_frame_id).unwrap();
410    let mut total_weights  = 0;
411    let mut total_absolute = 0;
412    let mut add_layout     = |layout : &layout::Tiled| match layout {
413      layout::Tiled::Weighted (weight) => total_weights  += weight.get(),
414      layout::Tiled::Absolute (size)   => total_absolute += size.get()
415    };
416    add_layout (layout);
417    let sibling_layouts = parent_node.children().iter().map (|child_id| {
418      let Widget (child_layout, _, _) = Frame::try_get (elements, child_id).ok()?;
419      if let layout::Variant::Tiled (tiled) = &child_layout.variant {
420        add_layout (tiled);
421        Some ((child_id, tiled))
422      } else {
423        None
424      }
425    }).collect::<Vec <_>>();
426    let order_index = match order {
427      CreateOrder::Prepend        => Some (0),
428      CreateOrder::NthSibling (n) => Some (n),
429      CreateOrder::Append         => None
430    };
431    let mut sibling_coordinates = vec![];
432    let mut start_weight = 0;
433    let mut start_absolute = 0;
434    let mut new_coordinates = None;
435    let mut index = 0;
436    // sibling tiled frames
437    for (i, maybe_tiled) in sibling_layouts.into_iter().enumerate() {
438      if order_index == Some (i as u32) {
439        new_coordinates = Some (super::coordinates (
440          &parent_layout.orientation, parent_canvas, layout, &mut start_weight,
441          &mut start_absolute, total_weights, total_absolute
442        ));
443      }
444      if let Some ((id, tiled)) = maybe_tiled {
445        let coordinates = super::coordinates (
446          &parent_layout.orientation, parent_canvas, tiled, &mut start_weight,
447          &mut start_absolute, total_weights, total_absolute);
448        sibling_coordinates.push ((id.clone(), coordinates));
449      }
450      index = i + 1;
451    }
452    if new_coordinates.is_none() {
453      if cfg!(debug_assertions) && let Some (n) = order_index {
454        debug_assert_eq!(n, index as u32);
455      }
456      new_coordinates = Some (super::coordinates (
457        &parent_layout.orientation, parent_canvas, layout,
458        &mut start_weight, &mut start_absolute, total_weights, total_absolute
459      ));
460    }
461    log::trace!("...create_coordinates");
462    (new_coordinates.unwrap(), sibling_coordinates)
463  }
464}