gooey/widget/frame/
screen.rs

1use std::convert::TryInto;
2use derive_builder::Builder;
3use log;
4
5use crate::prelude::*;
6use super::{Frame, set_layout};
7
8//
9//  controls
10//
11
12/// Builtin system control 'FrameScreenResize'.
13///
14/// Frame must have free layout.
15pub fn resize (
16  system        : &input::System,
17  elements      : &Tree <Element>,
18  node_id       : &NodeId,
19  action_buffer : &mut Vec <(NodeId, Action)>
20) {
21  log::trace!("resize...");
22  match system {
23    &input::System::Resized { width, height } => {
24      *coordinates::SCREEN_WH.write().unwrap() = [width, height];
25      let Widget (layout, _, canvas) = Frame::try_get (elements, node_id)
26        .unwrap();
27      let coordinate_kind = Some (canvas.coordinates.kind());
28      let layout = {
29        use controller::component::Layout;
30        let (mut free, area) = layout.variant.clone().try_into().unwrap();
31        free.size   = {
32          let width  = width.into();
33          let height = height.into();
34          controller::Size { width, height }
35        };
36        free.offset = if canvas.coordinates.kind() == coordinates::Kind::Tile {
37          tile_offset (free.size, free.anchor)
38        } else {
39          pixel_offset (free.size, free.anchor)
40        };
41        Layout { variant: (free, area).into(), .. layout.clone() }
42      };
43      // use the new layout size to recursively refresh child canvas coordinates
44      set_layout (elements, node_id, layout, coordinate_kind, action_buffer);
45    }
46    _ => {}
47  }
48  log::trace!("...resize");
49}
50
51//
52//  builder
53//
54
55#[derive(Builder)]
56#[builder(pattern="owned", build_fn(private), setter(strip_option))]
57#[builder(public)]
58struct Pixel <'a, A : Application> {
59  #[builder(default)]
60  anchor          : Option <controller::Alignment>,
61  #[builder(default)]
62  appearances     : Option <controller::Appearances>,
63  #[builder(default)]
64  bindings        : Option <&'a controller::Bindings <A>>,
65  #[builder(default)]
66  border          : Option <view::Border>,
67  #[builder(default)]
68  clear_color     : canvas::ClearColor,
69  #[builder(default)]
70  dimensions      : Option <view::dimensions::Pixel>,
71  #[builder(default)]
72  orientation     : (controller::Orientation, controller::Area),
73  #[builder(default)]
74  system_control  : Option <controls::System>
75}
76
77#[derive(Builder)]
78#[builder(pattern="owned", build_fn(private), setter(strip_option))]
79#[builder(public)]
80struct Tile <'a, A : Application> {
81  #[builder(default)]
82  anchor          : Option <controller::Alignment>,
83  #[builder(default)]
84  appearances     : Option <controller::Appearances>,
85  #[builder(default)]
86  bindings        : Option <&'a controller::Bindings <A>>,
87  #[builder(default)]
88  border          : Option <view::Border>,
89  #[builder(default)]
90  clear_color     : canvas::ClearColor,
91  #[builder(default)]
92  dimensions      : Option <view::dimensions::Tile>,
93  #[builder(default)]
94  orientation     : (controller::Orientation, controller::Area),
95  #[builder(default)]
96  system_control  : Option <controls::System>
97}
98
99impl <'a, A : Application> PixelBuilder <'a, A> {
100  pub fn new() -> Self {
101    PixelBuilder {
102      anchor:          None,
103      appearances:     None,
104      bindings:        None,
105      border:          None,
106      clear_color:     None,
107      dimensions:      None,
108      orientation:     None,
109      system_control:  None
110    }
111  }
112}
113
114impl <'a, A : Application> BuildElement for PixelBuilder <'a, A> {
115  /// Create a frame widget representing the root screen surface for a
116  /// pixel-addressable GUI.
117  ///
118  /// By default if no bindings are provided, the left mouse button (Mouse1)
119  /// will be bound to ActivePointer.
120  fn build_element (self) -> Element {
121    log::trace!("pixel build element...");
122    let Pixel {
123      anchor, appearances, bindings, border, clear_color, dimensions,
124      orientation, system_control
125    } = self.build()
126      .map_err(|err| log::error!("frame screen tile builder error: {:?}", err))
127      .unwrap();
128    let controller = {
129      let layout = {
130        use controller::{Alignment, Area, Size};
131        use controller::component::{layout, Layout};
132        let size        = dimensions.map_or (
133          Size::default_absolute(),
134          |dimensions| Size {
135            width:  dimensions.width().into(),
136            height: dimensions.height().into()
137          }
138        );
139        let anchor = anchor.unwrap_or (Alignment::pixel());
140        let offset = pixel_offset (size, anchor);
141        let free   = layout::Free { size, offset, anchor };
142        Layout { orientation, .. layout::Variant::from ((free, Area::default())).into() }
143      };
144      let bindings_empty = controller::Bindings::empty();
145      let bindings       = {
146        let mut bindings =
147          bindings.unwrap_or (&bindings_empty).get_bindings (&super::CONTROLS);
148        bindings.system  = system_control.map (Into::into)
149          .or (Some (controls::system::Builtin::FrameScreenResize.into()));
150        bindings
151      };
152      let mut controller     = Controller::with_bindings (&bindings);
153      controller.component   = layout.into();
154      controller.appearances = appearances.unwrap_or_default();
155      controller
156    };
157    let view = {
158      let coordinates = view::Coordinates::Pixel (
159        view::position::Pixel::origin(),
160        dimensions.unwrap_or (view::dimensions::Pixel::default()));
161      let width  = coordinates.dimensions_horizontal();
162      let height = coordinates.dimensions_vertical();
163      *coordinates::SCREEN_WH.write().unwrap() = [width, height];
164      let canvas = view::component::Canvas { coordinates, clear_color, border };
165      let appearance = controller.get_appearance().clone();
166      View { appearance, .. view::Component::from (canvas).into() }
167    };
168    log::trace!("...pixel build element");
169    Element::new ("Frame".to_string(), controller, Model::default(), view)
170  }
171}
172
173impl <'a, A : Application> TileBuilder <'a, A> {
174  pub fn new() -> Self {
175    TileBuilder {
176      anchor:         None,
177      appearances:    None,
178      bindings:       None,
179      border:         None,
180      clear_color:    None,
181      dimensions:     None,
182      orientation:    None,
183      system_control: None
184    }
185  }
186}
187
188impl <'a, A : Application> BuildElement for TileBuilder <'a, A> {
189  /// Create a frame widget representing the root screen for a text-mode GUI.
190  fn build_element (self) -> Element {
191    log::trace!("tile build element...");
192    let Tile {
193      anchor, appearances, bindings, border, clear_color, dimensions,
194      orientation, system_control
195    } = self.build()
196      .map_err(|err| log::error!("frame screen tile builder error: {:?}", err))
197      .unwrap();
198    let controller = {
199      let layout = {
200        use controller::{Alignment, Area, Size};
201        use controller::component::{layout, Layout};
202        let size = dimensions.map_or (Size::default_absolute(),
203          |dimensions| Size {
204            width:  dimensions.columns().into(),
205            height: dimensions.rows().into()
206          }
207        );
208        let anchor = anchor.unwrap_or (Alignment::tile());
209        let offset = tile_offset (size, anchor);
210        let free   = layout::Free { size, offset, anchor };
211        let area   = Area::default();
212        Layout { orientation, .. layout::Variant::from ((free, area)).into() }
213      };
214      let bindings_empty = controller::Bindings::empty();
215      let bindings = {
216        let mut bindings =
217          bindings.unwrap_or (&bindings_empty).get_bindings (&super::CONTROLS);
218        bindings.system = system_control.map (Into::into)
219          .or (Some (controls::system::Builtin::FrameScreenResize.into()));
220        bindings
221      };
222      let mut controller = Controller::with_bindings (&bindings);
223      controller.component   = layout.into();
224      controller.appearances = appearances.unwrap_or_default();
225      controller
226    };
227    let view = {
228      let coordinates = view::Coordinates::Tile (
229        view::position::Tile::origin(),
230        dimensions.unwrap_or (view::dimensions::Tile::default()));
231      let width  = coordinates.dimensions_horizontal();
232      let height = coordinates.dimensions_vertical();
233      *coordinates::SCREEN_WH.write().unwrap() = [width, height];
234      let canvas = view::component::Canvas { coordinates, clear_color, border };
235      let appearance  = controller.get_appearance().clone();
236      View { appearance, .. view::Component::from (canvas).into() }
237    };
238    log::trace!("...tile build element");
239    Element::new ("Frame".to_string(), controller, Model::default(), view)
240  }
241}
242
243//
244//  private
245//
246/// Special handling of alignment for screen canvases: when size is set, the
247/// alignment anchor determines where the offset will be. The default pixel
248/// alignment is (Bottom, Left), in which case the offset will be (0,0).
249/// For a centered alignment (Middle, Center), the offset will be half of the
250/// size width and height. For a (Top, Right) alignment, the offset will be
251/// negative both width and height.
252pub fn pixel_offset (size : controller::Size, anchor : controller::Alignment)
253  -> controller::Offset
254{
255  use controller::{alignment, offset, Offset};
256  let horizontal = match anchor.horizontal {
257    alignment::Horizontal::Left   => 0.into(),
258    alignment::Horizontal::Center => -offset::Signed::from (size.width).half(),
259    alignment::Horizontal::Right  => -offset::Signed::from (size.width)
260  };
261  let vertical   = match anchor.vertical {
262    alignment::Vertical::Bottom => 0.into(),
263    alignment::Vertical::Middle => -offset::Signed::from (size.height).half(),
264    alignment::Vertical::Top    => -offset::Signed::from (size.height)
265  };
266  Offset { horizontal, vertical }
267}
268
269/// Special handling of alignment for screen canvases: when size is set, the
270/// alignment anchor determines where the offset will be. The default tile
271/// alignment is (Top, Left), in which case the offset will be (0,0).
272/// For a centered alignment (Middle, Center), the offset will be half of the
273/// size width and height. For a (Bottom, Right) alignment, the offset will be
274/// negative both width and height.
275pub fn tile_offset (size : controller::Size, anchor : controller::Alignment)
276  -> controller::Offset
277{
278  use controller::{alignment, offset, Offset};
279  let horizontal = match anchor.horizontal {
280    alignment::Horizontal::Left   => 0.into(),
281    alignment::Horizontal::Center => -offset::Signed::from (size.width).half(),
282    alignment::Horizontal::Right  => -offset::Signed::from (size.width)
283  };
284  let vertical   = match anchor.vertical {
285    alignment::Vertical::Top    => 0.into(),
286    alignment::Vertical::Middle => -offset::Signed::from (size.height).half(),
287    alignment::Vertical::Bottom => -offset::Signed::from (size.height)
288  };
289  Offset { horizontal, vertical }
290}