gooey/widget/frame/
tiled.rs

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