gooey-rs 0.12.1

Tile-based UI library with audio support
Documentation
//! Image display.
//!
//! ```text
//! +--------+
//! | frame  |
//! +---+----+
//!     |
//! +---+-----+
//! | picture |
//! +---------+
//! ```

use std::sync::LazyLock;

use mat::Mat;

use crate::prelude::*;

pub use self::builder::{PictureBuilder as Builder, FitBuilder, WithFrameBuilder};

pub type Picture <'element> = Widget <'element, Scroll, Pixmap, Image>;

//
//  controls
//

pub static CONTROLS : LazyLock <Controls> = LazyLock::new (Controls::default);
  /* TODO scroll controls
  buttons: vec![
    controls::button::Builtin::PictureScrollDown,
    controls::button::Builtin::PictureScrollUp,
    controls::button::Builtin::PictureScrollRight,
    controls::button::Builtin::PictureScrollLeft,
    controls::button::Builtin::PicturePageDown,
    controls::button::Builtin::PicturePageUp,
    controls::button::Builtin::PicturePageRight,
    controls::button::Builtin::PicturePageLeft
  ].into_iter().map (Into::into).collect()
  */

/// Builtin picture control ID `PictureScrollRight`
pub fn scroll_right (
  _              : &controls::button::Release,
  _elements      : &Tree <Element>,
  _node_id       : &NodeId,
  _action_buffer : &mut Vec <(NodeId, Action)>
) {
  log::trace!("scroll_right...");
  unimplemented!("TODO: picture scroll right")
  //log::trace!("...scroll_right");
}

//
//  crate
//

pub(crate) fn image (
  canvas : &Canvas,
  scroll : &Scroll,
  pixmap : &Pixmap
) -> Image {
  use model::component::Pixmap;
  match &pixmap {
    Pixmap::Pixmap8  (mat) => Image::Raw8  (make_image (canvas, scroll, mat)),
    Pixmap::Pixmap16 (mat) => Image::Raw16 (make_image (canvas, scroll, mat)),
    Pixmap::Pixmap32 (mat) => Image::Raw32 (make_image (canvas, scroll, mat)),
    Pixmap::Pixmap64 (mat) => Image::Raw64 (make_image (canvas, scroll, mat)),
    Pixmap::Texture { resource_id, index, .. } =>
      Image::Texture (Texture {
        resource: *resource_id,
        index:    *index
      })
  }
}

//
//  private
//

fn make_image <T : Copy> (
  canvas : &Canvas,
  _scroll : &Scroll,
  pixmap : &Mat <T>
) -> Mat <T> {
  let (image_width, image_height) = canvas.body_wh();
  let columns = pixmap.width().min (image_width as usize);
  let rows    = pixmap.height().min (image_height as usize);
  // TODO: scroll
  let v = if rows > 0 {
    pixmap.rows().take (rows).flat_map (|row| &row[0..columns]).copied()
      .collect::<Vec <T>>()
  } else {
    Vec::new()
  };
  Mat::from_vec ((rows, columns).into(), v).unwrap()
}

//
//  builder
//

mod builder {
  use derive_builder::Builder;
  use crate::prelude::*;

  #[derive(Builder)]
  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
  struct Picture <'a, A : Application> {
    elements          : &'a Tree <Element>,
    parent_id         : &'a NodeId,
    #[builder(default)]
    bindings          : Option <&'a Bindings <A>>,
    #[builder(default)]
    frame_appearances : Appearances,
    #[builder(default)]
    frame_border      : Option <Border>,
    #[builder(default)]
    frame_clear_color : Option <canvas::ClearColor>,
    #[builder(default)]
    frame_layout      : Layout,
    #[builder(default)]
    name              : Option <String>,
    #[builder(default)]
    pixmap            : Pixmap
  }

  #[derive(Builder)]
  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
  struct Fit <'a, A : Application> {
    elements     : &'a Tree <Element>,
    parent_id    : &'a NodeId,
    #[builder(default)]
    anchor       : Alignment,
    #[builder(default)]
    appearances  : Appearances,
    #[builder(default)]
    bindings     : Option <&'a Bindings <A>>,
    #[builder(default)]
    border       : Option <Border>,
    #[builder(default)]
    clear_color  : Option <canvas::ClearColor>,
    #[builder(default)]
    coord_kind_override : Option <coordinates::Kind>,
    #[builder(default)]
    name         : Option <String>,
    #[builder(default)]
    offset       : Offset,
    #[builder(default)]
    pixmap       : Pixmap
  }

  #[derive(Builder)]
  #[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
  struct WithFrame <'a, A : Application> {
    elements : &'a Tree <Element>,
    frame_id : &'a NodeId,
    #[builder(default)]
    bindings : Option <&'a Bindings <A>>,
    #[builder(default)]
    name     : Option <String>,
    #[builder(default)]
    pixmap   : Pixmap,
  }

  impl <'a, A : Application> PictureBuilder <'a, A> {
    pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
      PictureBuilder {
        elements:          Some (elements),
        parent_id:         Some (parent_id),
        bindings:          None,
        frame_appearances: None,
        frame_border:      None,
        frame_clear_color: None,
        frame_layout:      None,
        name:              None,
        pixmap:            None
      }
    }
  }

  impl <A : Application> BuildActions for PictureBuilder <'_, A> {
    /// Parent must have tile coordinates
    // TODO: image alignment
    fn build_actions (self) -> Vec<(NodeId, Action)> {
      use std::convert::TryInto;
      use crate::tree::InsertBehavior;
      log::trace!("build actions...");
      let Picture {
        elements, parent_id, bindings, frame_appearances, frame_border,
        frame_clear_color, frame_layout, name, pixmap
      } = self.build()
        .map_err(|err| log::error!("frame builder error: {err:?}"))
        .unwrap();
      let bindings_empty = Bindings::empty();
      let bindings = bindings.unwrap_or (&bindings_empty);
      { // only allow tile coordinate parents
        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id).unwrap();
        assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
      }
      let mut out = vec![];
      let (mut subtree, order) = {
        let mut actions = {
          let mut frame = frame::Builder::new (elements, parent_id)
            .appearances (frame_appearances)
            .bindings (bindings)
            .layout (frame_layout);
          set_option!(frame, border, frame_border);
          set_option!(frame, clear_color, frame_clear_color);
          frame.build_actions()
        };
        out.extend (actions.drain (1..));
        debug_assert_eq!(actions.len(), 1);
        actions.pop().unwrap().1.try_into().unwrap()
      };
      let frame_id = subtree.root_node_id().unwrap().clone();
      let picture = {
        let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id).unwrap();
        let scroll = Scroll::default_pixel_absolute();
        let image  = super::image (canvas, &scroll, &pixmap);
        let controller = {
          let mut controller   = Controller::with_bindings (
            &bindings.get_bindings (&super::CONTROLS));
          controller.component = scroll.into();
          controller
        };
        let model = Model { component: pixmap.into(), .. Model::default() };
        let view  = view::Component::from (image).into();
        let name  = name.unwrap_or ("Picture".to_string());
        Element::new (name, controller, model, view)
      };
      let _ = subtree
        .insert (Node::new (picture), InsertBehavior::UnderNode (&frame_id))
        .unwrap();
      out.push ((parent_id.clone(), Action::Create (subtree, order)));
      log::trace!("...build actions");
      out
    }
  }

  impl <'a, A : Application> FitBuilder <'a, A> {
    pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
      FitBuilder {
        elements:             Some (elements),
        parent_id:            Some (parent_id),
        anchor:               None,
        appearances:          None,
        bindings:             None,
        border:               None,
        clear_color:          None,
        coord_kind_override:  None,
        name:                 None,
        offset:               None,
        pixmap:               None
      }
    }
  }

  impl <A : Application> BuildActions for FitBuilder <'_, A> {
    /// Create a Picture with a Frame that fits the image contents
    // TODO: image alignment
    fn build_actions (self) -> Vec<(NodeId, Action)> {
      use crate::tree::InsertBehavior;
      log::trace!("fit build actions...");
      let Fit {
        elements, parent_id, anchor, appearances, bindings, border, clear_color,
        coord_kind_override, name, offset, pixmap
      } = self.build().map_err (|err| log::error!("frame builder error: {err:?}"))
        .unwrap();
      let bindings_empty = Bindings::empty();
      let bindings = bindings.unwrap_or (&bindings_empty);
      let (body_width, body_height) = {
        let Widget (_, _, canvas) = Frame::try_get (elements, parent_id).unwrap();
        if let Some (coord_kind) = coord_kind_override {
          if canvas.coordinates.kind() == coord_kind {
            (pixmap.width() as u32, pixmap.height() as u32)
          } else {
            match canvas.coordinates.kind() {
              coordinates::Kind::Tile  => {
                // tile frame with pixel image
                let round_up = |dimension, tile|
                  dimension as u32 / tile + u32::from (dimension as u32 % tile != 0);
                let [tile_w, tile_h] = *coordinates::TILE_WH;
                ( round_up (pixmap.width(),  tile_w),
                  round_up (pixmap.height(), tile_h)
                )
              }
              coordinates::Kind::Pixel =>
                // pixel frame with tile image
                unimplemented!("TODO: pixel frame with tile image")
            }
          }
        } else {
          (pixmap.width() as u32, pixmap.height() as u32)
        }
      };
      let mut out = vec![];
      let frame = {
        let size = {
          let (border_w, border_h) = border.as_ref().map_or ((0, 0), Border::total_wh);
          let width   = (body_width  + border_w as u32).into();
          let height  = (body_height + border_h as u32).into();
          Size { width, height }
        };
        let layout = layout::Free { anchor, offset, size };
        let mut free = frame::free::Builder::new (elements, parent_id)
          .appearances (appearances)
          .bindings (bindings)
          .layout (layout);
        set_option!(free, border, border);
        set_option!(free, clear_color, clear_color);
        free.build_element()
      };
      let mut subtree = TreeBuilder::new().with_root (Node::new (frame)).build();
      let frame_id = subtree.root_node_id().unwrap().clone();
      let picture = {
        let Widget (_, _, canvas) = Frame::try_get (&subtree, &frame_id).unwrap();
        let scroll = Scroll::default_pixel_absolute();
        let image  = super::image (canvas, &scroll, &pixmap);
        let controller = {
          let mut controller =
            Controller::with_bindings (&bindings.get_bindings (&super::CONTROLS));
          controller.component = scroll.into();
          controller
        };
        let model = Model { component: pixmap.into(), .. Model::default() };
        let view  = view::Component::from (image).into();
        let name  = name.unwrap_or ("Picture".to_string());
        Element::new (name, controller, model, view)
      };
      let _ = subtree
        .insert (Node::new (picture), InsertBehavior::UnderNode (&frame_id))
        .unwrap();
      out.push ((parent_id.clone(), Action::Create (subtree, CreateOrder::Append)));
      log::trace!("...fit build actions");
      out
    }
  }

  impl <'a, A : Application> WithFrameBuilder <'a, A> {
    pub const fn new (elements : &'a Tree <Element>, frame_id : &'a NodeId) -> Self {
      WithFrameBuilder {
        elements: Some (elements),
        frame_id: Some (frame_id),
        bindings: None,
        name:     None,
        pixmap:   None
      }
    }
  }

  impl <A : Application> BuildElement for WithFrameBuilder <'_, A> {
    /// Build with target Frame element
    fn build_element (self) -> Element {
      log::trace!("with frame build actions...");
      let WithFrame { elements, frame_id, bindings, name, pixmap } = self.build()
        .map_err (|err| log::error!("with frame builder error: {err:?}"))
        .unwrap();
      let bindings_empty = Bindings::empty();
      let bindings = bindings.unwrap_or (&bindings_empty)
        .get_bindings (&super::CONTROLS);
      let Widget (_, _, canvas) = Frame::try_get (elements, frame_id).unwrap();
      assert!(canvas.coordinates.kind() == coordinates::Kind::Tile);
      let scroll     = Scroll::default_pixel_absolute();
      let image      = super::image (canvas, &scroll, &pixmap);
      let controller = {
        let mut controller   =
          Controller::with_bindings (&bindings.get_bindings (&super::CONTROLS));
        controller.component = scroll.into();
        controller
      };
      let model = Model { component: pixmap.into(), .. Model::default() };
      let view  = view::Component::from (image).into();
      let name  = name.unwrap_or ("Picture".to_string());
      log::trace!("...with_frame");
      Element::new (name, controller, model, view)
    }
  }
}