gooey/widget/
picture.rs

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