gooey/widget/
picture.rs

1//! Image display.
2//!
3//! ```text
4//! +--------+
5//! | frame  |
6//! +---+----+
7//!     |
8//! +---+-----+
9//! | picture |
10//! +---------+
11//! ```
12
13use lazy_static::lazy_static;
14use mat::Mat;
15
16use crate::prelude::*;
17
18pub use self::builder::{PictureBuilder as Builder, FitBuilder, WithFrameBuilder};
19
20pub type Picture <'element> = Widget <'element,
21  controller::component::Scroll,
22  model::component::Pixmap,
23  view::component::Image>;
24
25//
26//  controls
27//
28
29lazy_static!{
30  pub static ref CONTROLS : Controls = Controls::default();
31    /* TODO scroll controls
32    buttons: vec![
33      controls::button::Builtin::PictureScrollDown,
34      controls::button::Builtin::PictureScrollUp,
35      controls::button::Builtin::PictureScrollRight,
36      controls::button::Builtin::PictureScrollLeft,
37      controls::button::Builtin::PicturePageDown,
38      controls::button::Builtin::PicturePageUp,
39      controls::button::Builtin::PicturePageRight,
40      controls::button::Builtin::PicturePageLeft
41    ].into_iter().map (Into::into).collect()
42    */
43}
44
45/// Builtin picture control ID 'PictureScrollRight'
46pub fn scroll_right (
47  _              : &controls::button::Release,
48  _elements      : &Tree <Element>,
49  _node_id       : &NodeId,
50  _action_buffer : &mut Vec <(NodeId, Action)>
51) {
52  log::trace!("scroll_right...");
53  unimplemented!("TODO: picture scroll right")
54  //log::trace!("...scroll_right");
55}
56
57//
58//  crate
59//
60
61pub(crate) fn image (
62  canvas : &view::component::Canvas,
63  scroll : &controller::component::Scroll,
64  pixmap : &model::component::Pixmap
65) -> view::component::Image {
66  use model::component::Pixmap;
67  match &pixmap {
68    Pixmap::Pixmap8  (mat) =>
69      view::component::Image::Raw8  (make_image (canvas, scroll, &mat)),
70    Pixmap::Pixmap16 (mat) =>
71      view::component::Image::Raw16 (make_image (canvas, scroll, &mat)),
72    Pixmap::Pixmap32 (mat) =>
73      view::component::Image::Raw32 (make_image (canvas, scroll, &mat)),
74    Pixmap::Pixmap64 (mat) =>
75      view::component::Image::Raw64 (make_image (canvas, scroll, &mat)),
76    Pixmap::Texture { resource_id, index, .. } =>
77      view::component::Image::Texture (view::Texture {
78        resource: *resource_id,
79        index:    *index
80      })
81  }
82}
83
84//
85//  private
86//
87
88fn make_image <T : Copy> (
89  canvas : &view::component::Canvas,
90  _scroll : &controller::component::Scroll,
91  pixmap : &Mat <T>
92) -> Mat <T> {
93  let (image_width, image_height) = canvas.body_wh();
94  let columns = pixmap.width().min (image_width as usize);
95  let rows    = pixmap.height().min (image_height as usize);
96  // TODO: scroll
97  let v = if rows > 0 {
98    pixmap.rows().take (rows).flat_map (|row| &row[0..columns])
99      .cloned().collect::<Vec <T>>()
100  } else {
101    Vec::new()
102  };
103  Mat::from_vec ((rows, columns).into(), v).unwrap()
104}
105
106//
107//  builder
108//
109
110mod builder {
111  use derive_builder::Builder;
112  use crate::prelude::*;
113
114  #[derive(Builder)]
115  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
116  pub struct Picture <'a, A : Application> {
117    elements          : &'a Tree <Element>,
118    parent_id         : &'a NodeId,
119    #[builder(default)]
120    bindings          : Option <&'a controller::Bindings <A>>,
121    #[builder(default)]
122    frame_appearances : controller::Appearances,
123    #[builder(default)]
124    frame_border      : Option <view::Border>,
125    #[builder(default)]
126    frame_clear_color : Option <canvas::ClearColor>,
127    #[builder(default)]
128    frame_layout      : controller::component::Layout,
129    #[builder(default)]
130    pixmap            : model::component::Pixmap
131  }
132
133  #[derive(Builder)]
134  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
135  pub struct Fit <'a, A : Application> {
136    elements     : &'a Tree <Element>,
137    parent_id    : &'a NodeId,
138    #[builder(default)]
139    anchor       : controller::Alignment,
140    #[builder(default)]
141    appearances  : controller::Appearances,
142    #[builder(default)]
143    bindings     : Option <&'a controller::Bindings <A>>,
144    #[builder(default)]
145    border       : Option <view::Border>,
146    #[builder(default)]
147    clear_color  : Option <view::component::canvas::ClearColor>,
148    #[builder(default)]
149    coord_kind_override : Option <view::coordinates::Kind>,
150    #[builder(default)]
151    offset       : controller::Offset,
152    #[builder(default)]
153    pixmap       : model::component::Pixmap
154  }
155
156  #[derive(Builder)]
157  #[builder(pattern="owned", build_fn(private), setter(strip_option))]
158  pub struct WithFrame <'a, A : Application> {
159    elements : &'a Tree <Element>,
160    frame_id : &'a NodeId,
161    #[builder(default)]
162    bindings : Option <&'a controller::Bindings <A>>,
163    #[builder(default)]
164    pixmap   : model::component::Pixmap,
165  }
166
167  impl <'a, A : Application> PictureBuilder <'a, A> {
168    pub fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
169      PictureBuilder {
170        elements:          Some (elements),
171        parent_id:         Some (parent_id),
172        bindings:          None,
173        frame_appearances: None,
174        frame_border:      None,
175        frame_clear_color: None,
176        frame_layout:      None,
177        pixmap:            None
178      }
179    }
180  }
181
182  impl <'a, A : Application> BuildActions for PictureBuilder <'a, A> {
183    /// Parent must have tile coordinates
184    // TODO: image alignment
185    fn build_actions (self) -> Vec<(NodeId, Action)> {
186      use std::convert::TryInto;
187      use crate::tree::InsertBehavior;
188      log::trace!("build actions...");
189      let Picture {
190        elements, parent_id, bindings, frame_appearances, frame_border,
191        frame_clear_color, frame_layout, pixmap
192      } = self.build()
193        .map_err(|err| log::error!("frame builder error: {:?}", err))
194        .unwrap();
195      let bindings_empty = Bindings::empty();
196      let bindings = bindings.unwrap_or (&bindings_empty);
197      { // only allow tile coordinate parents
198        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id)
199          .unwrap();
200        assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
201      }
202      let mut out = vec![];
203      let (mut subtree, order) = {
204        let mut actions = {
205          let mut frame = frame::Builder::new (elements, parent_id)
206            .appearances (frame_appearances)
207            .bindings (bindings)
208            .layout (frame_layout);
209          set_option!(frame, border, frame_border);
210          set_option!(frame, clear_color, frame_clear_color);
211          frame.build_actions()
212        };
213        out.extend (actions.drain (1..));
214        debug_assert_eq!(actions.len(), 1);
215        actions.pop().unwrap().1.try_into().unwrap()
216      };
217      let frame_id = subtree.root_node_id().unwrap().clone();
218      let picture = {
219        let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id)
220          .unwrap();
221        let scroll = Scroll::default_pixel_absolute().into();
222        let image  = super::image (canvas, &scroll, &pixmap);
223        let controller = {
224          let mut controller   = Controller::with_bindings (
225            &bindings.get_bindings (&super::CONTROLS));
226          controller.component = scroll.into();
227          controller
228        };
229        let model = Model { component: pixmap.into(), .. Model::default() };
230        let view  = view::Component::from (image).into();
231        Element::new ("Picture".to_string(), controller, model, view)
232      };
233      let _ = subtree
234        .insert (Node::new (picture), InsertBehavior::UnderNode (&frame_id))
235        .unwrap();
236      out.push ((parent_id.clone(), Action::Create (subtree, order)));
237      log::trace!("...build actions");
238      out
239    }
240  }
241
242  impl <'a, A : Application> FitBuilder <'a, A> {
243    pub fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
244      FitBuilder {
245        elements:             Some (elements),
246        parent_id:            Some (parent_id),
247        anchor:               None,
248        appearances:          None,
249        bindings:             None,
250        border:               None,
251        clear_color:          None,
252        coord_kind_override:  None,
253        offset:               None,
254        pixmap:               None
255      }
256    }
257  }
258
259  impl <'a, A : Application> BuildActions for FitBuilder <'a, A> {
260    /// Create a Picture with a Frame that fits the image contents
261    // TODO: image alignment
262    fn build_actions (self) -> Vec<(NodeId, Action)> {
263      use crate::tree::InsertBehavior;
264      log::trace!("fit build actions...");
265      let Fit {
266        elements, parent_id, anchor, appearances, bindings, border, clear_color,
267        coord_kind_override, offset, pixmap
268      } = self.build()
269        .map_err(|err| log::error!("frame builder error: {:?}", err))
270        .unwrap();
271      let bindings_empty = Bindings::empty();
272      let bindings = bindings.unwrap_or (&bindings_empty);
273      let (body_width, body_height) = {
274        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id)
275          .unwrap();
276        if let Some (coord_kind) = coord_kind_override.clone() {
277          if canvas.coordinates.kind() == coord_kind {
278            (pixmap.width() as u32, pixmap.height() as u32)
279          } else {
280            match canvas.coordinates.kind() {
281              coordinates::Kind::Tile  => {
282                // tile frame with pixel image
283                let round_up = |dimension, tile|
284                  dimension as u32 / tile +
285                    if dimension as u32 % tile != 0 { 1 } else { 0 };
286                let [tile_w, tile_h] = *coordinates::TILE_WH;
287                ( round_up (pixmap.width(),  tile_w),
288                  round_up (pixmap.height(), tile_h)
289                )
290              }
291              coordinates::Kind::Pixel =>
292                // pixel frame with tile image
293                unimplemented!("TODO: pixel frame with tile image")
294            }
295          }
296        } else {
297          (pixmap.width() as u32, pixmap.height() as u32)
298        }
299      };
300      let mut out = vec![];
301      let frame = {
302        let size   = {
303          let (border_w, border_h) = border.as_ref().map (Border::total_wh)
304            .unwrap_or ((0,0));
305          let width   = (body_width  + border_w as u32).into();
306          let height  = (body_height + border_h as u32).into();
307          controller::Size { width, height }
308        };
309        let layout = layout::Free { anchor, offset, size };
310        let mut free = frame::free::Builder::new (elements, parent_id)
311          .appearances (appearances)
312          .bindings (bindings)
313          .layout (layout);
314        set_option!(free, border, border);
315        set_option!(free, clear_color, clear_color);
316        free.build_element()
317      };
318      let mut subtree = TreeBuilder::new().with_root (Node::new (frame))
319        .build();
320      let frame_id    = subtree.root_node_id().unwrap().clone();
321      let picture     = {
322        let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id)
323          .unwrap();
324        let scroll = Scroll::default_pixel_absolute().into();
325        let image  = super::image (canvas, &scroll, &pixmap);
326        let controller = {
327          let mut controller = Controller::with_bindings (
328            &bindings.get_bindings (&super::CONTROLS));
329          controller.component = scroll.into();
330          controller
331        };
332        let model = Model {
333          component: pixmap.clone().into(), .. Model::default()
334        };
335        let view  = view::Component::from (image).into();
336        Element::new ("Picture".to_string(), controller, model, view)
337      };
338      let _ = subtree
339        .insert (Node::new (picture), InsertBehavior::UnderNode (&frame_id))
340        .unwrap();
341      out.push ((
342        parent_id.clone(), Action::Create (subtree, CreateOrder::Append)
343      ));
344      log::trace!("...fit build actions");
345      out
346    }
347  }
348
349  impl <'a, A : Application> WithFrameBuilder <'a, A> {
350    pub fn new (elements : &'a Tree <Element>, frame_id : &'a NodeId) -> Self {
351      WithFrameBuilder {
352        elements: Some (elements),
353        frame_id: Some (frame_id),
354        bindings: None,
355        pixmap:   None
356      }
357    }
358  }
359
360  impl <'a, A : Application> BuildElement for WithFrameBuilder <'a, A> {
361    /// Build with target Frame element
362    fn build_element (self) -> Element {
363      log::trace!("with frame build actions...");
364      let WithFrame { elements, frame_id, bindings, pixmap } = self.build()
365        .map_err(|err| log::error!("with frame builder error: {:?}", err))
366        .unwrap();
367      let bindings_empty = Bindings::empty();
368      let bindings = bindings.unwrap_or (&bindings_empty)
369        .get_bindings (&super::CONTROLS);
370      let Widget (_, _, canvas) = Frame::try_get (elements, &frame_id).unwrap();
371      assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
372      let scroll     = Scroll::default_pixel_absolute().into();
373      let image      = super::image (canvas, &scroll, &pixmap);
374      let controller = {
375        let mut controller   = Controller::with_bindings (
376          &bindings.get_bindings (&super::CONTROLS));
377        controller.component = scroll.into();
378        controller
379      };
380      let model = Model { component: pixmap.into(), .. Model::default() };
381      let view  = view::Component::from (image).into();
382      log::trace!("...with_frame");
383      Element::new ("Picture".to_string(), controller, model, view)
384    }
385  }
386}