use std::convert::{TryFrom, TryInto};
use std::sync::LazyLock;
use crate::geometry;
use crate::prelude::*;
pub use self::builder::FrameBuilder as Builder;
pub mod free;
pub mod screen;
pub mod tiled;
pub type Frame <'element> = Widget <'element, Layout, model::Component, Canvas>;
pub static CONTROLS : LazyLock <Controls> = LazyLock::new (||
controls::Builder::new()
.buttons (vec![
controls::button::Builtin::ActivatePointer,
controls::button::Builtin::FrameClose,
controls::button::Builtin::FrameToggleOrientation
].into_iter().map (Into::into).collect::<Vec <_>>().into())
.build()
);
pub fn close (
_ : &controls::button::Release,
elements : &Tree <Element>,
node_id : &NodeId,
action_buffer : &mut Vec <(NodeId, Action)>
) {
use controller::component::layout;
log::trace!("close...");
let Widget (layout, _, _) = Frame::try_get (elements, node_id).unwrap();
match layout.variant {
layout::Variant::Tiled (_) => for (sibling_id, coordinates) in
tiled::destroy_coordinates (elements, node_id)
{
set_coordinates (elements, &sibling_id, &coordinates, None, action_buffer);
}
layout::Variant::Free (..) => {}
}
action_buffer.push ((node_id.clone(), Action::Destroy));
log::trace!("...close");
}
pub fn refresh (
_ : &controls::button::Release,
elements : &Tree <Element>,
node_id : &NodeId,
action_buffer : &mut Vec <(NodeId, Action)>
) {
log::trace!("refresh...");
let Widget (layout, _, canvas) = Frame::try_get (elements, node_id).unwrap();
let coordinate_kind = Some (canvas.coordinates.kind());
set_layout (elements, node_id, layout.clone(), coordinate_kind, action_buffer);
log::trace!("...refresh");
}
pub fn toggle_orientation (
_ : &controls::button::Release,
elements : &Tree <Element>,
node_id : &NodeId,
action_buffer : &mut Vec <(NodeId, Action)>
) {
log::trace!("toggle_orientation...");
let Widget (layout, _, canvas) = Frame::try_get (elements, node_id).unwrap();
let (mut orientation, area) = layout.orientation.clone();
orientation = orientation.toggle();
let layout = Layout {
orientation: (orientation, area),
.. layout.clone()
};
let coordinate_kind = Some (canvas.coordinates.kind());
set_layout (elements, node_id, layout, coordinate_kind, action_buffer);
log::trace!("...toggle_orientation");
}
pub fn activate_pointer (
release : &controls::button::Release,
elements : &Tree <Element>,
node_id : &NodeId,
action_buffer : &mut Vec <(NodeId, Action)>
) {
log::trace!("activate pointer...");
let pointer_position = {
let pointer = POINTER.read().unwrap();
[pointer.position_horizontal, pointer.position_vertical].into()
};
let mut last = None;
for element_id in elements.traverse_pre_order_ids (node_id).unwrap() {
let element = elements.get (&element_id).unwrap().data();
if let Ok (Widget (_, _, canvas)) = Frame::try_from (element) {
let canvas_aabb = {
let aabb = geometry::integer::Aabb2::from (canvas.coordinates);
let pixel_aabb =
if canvas.coordinates.kind() == coordinates::Kind::Tile {
coordinates::tile_to_pixel_aabb (aabb)
} else {
aabb
};
geometry::Aabb2::with_minmax (
pixel_aabb.min().numcast().unwrap(),
pixel_aabb.max().numcast().unwrap()
).unwrap()
};
if canvas_aabb.contains (pointer_position) {
last = Some (element_id);
}
}
}
if let Some (element_id) = last {
let element = elements.get (&element_id).unwrap().data();
if element.controller.state == State::Enabled {
action_buffer.push ((element_id.clone(), Action::Focus));
}
if element.controller.state != State::Disabled
&& let Some (child_id) = elements.children_ids (&element_id).unwrap().next()
{
let child = elements.get (child_id).unwrap().data();
if let Ok (Widget (switch, _, _)) = Button::try_from (child) {
if switch.toggle {
match switch.state {
switch::State::On =>
button::release (&None, elements, &element_id, action_buffer),
switch::State::Off =>
button::push (release, elements, &element_id, action_buffer)
}
} else {
button::push (release, elements, &element_id, action_buffer)
}
}
}
}
log::trace!("...activate pointer");
}
pub fn hot_pointer (
pointer : &input::Pointer,
elements : &Tree <Element>,
node_id : &NodeId,
action_buffer : &mut Vec <(NodeId, Action)>
) {
log::trace!("hot pointer...");
debug_assert_eq!(&*POINTER.read().unwrap(), pointer);
let pointer_position = [pointer.position_horizontal, pointer.position_vertical].into();
let mut last = None;
for element_id in elements.traverse_pre_order_ids (node_id).unwrap() {
let element = elements.get (&element_id).unwrap().data();
if let Ok (Widget (_, _, canvas)) = Frame::try_from (element) {
let canvas_aabb = {
let aabb = geometry::integer::Aabb2::from (canvas.coordinates);
let pixel_aabb = if canvas.coordinates.kind() == coordinates::Kind::Tile {
coordinates::tile_to_pixel_aabb (aabb)
} else {
aabb
};
geometry::Aabb2::with_minmax (
pixel_aabb.min().numcast().unwrap(),
pixel_aabb.max().numcast().unwrap()
).unwrap()
};
if canvas_aabb.contains (pointer_position) {
last = Some (element_id);
}
}
}
if let Some (element_id) = last {
let element = elements.get (&element_id).unwrap().data();
if element.controller.state == State::Enabled {
action_buffer.push ((element_id.clone(), Action::Focus));
}
}
log::trace!("...hot pointer");
}
pub (crate) fn set_layout (
elements : &Tree <Element>,
frame_id : &NodeId,
layout : Layout,
coord_kind_override : Option <coordinates::Kind>,
action_buffer : &mut Vec <(NodeId, Action)>
) {
log::trace!("set_layout...");
{ let new_layout = layout.clone();
let update_layout = Box::new (move |controller : &mut Controller|
controller.component = new_layout.into());
action_buffer.push ((frame_id.clone(), Action::ModifyController (update_layout)));
}
for (frame_id, coordinates) in
new_coordinates (elements, frame_id, &layout, coord_kind_override)
{
set_coordinates (elements, &frame_id, &coordinates,
Some (layout.orientation.clone()), action_buffer);
}
log::trace!("...set_layout");
}
fn new_coordinates (
elements : &Tree <Element>,
frame_id : &NodeId,
layout : &Layout,
coord_kind_override : Option <coordinates::Kind>
) -> Vec <(NodeId, Coordinates)> {
use controller::size;
use controller::component::layout;
use view::coordinates::dimensions;
use view::component::Canvas;
log::trace!("new_coordinates...");
let out = match &layout.variant {
layout::Variant::Free (free, area) => {
let parent_canvas = if elements.root_node_id().unwrap() == frame_id {
let Widget (_, _, canvas) = Frame::try_get (elements, frame_id).unwrap();
let (width, height) = {
let (free, _) = layout.variant.clone().try_into().unwrap();
let width = match free.size.width {
size::Unsigned::Absolute (w) => w,
size::Unsigned::Relative (_) => 1
};
let height = match free.size.height {
size::Unsigned::Absolute (h) => h,
size::Unsigned::Relative (_) => 1
};
(width, height)
};
let (mut parent, dimensions) =
if canvas.coordinates.kind() == coordinates::Kind::Tile {
( Canvas::default_tile(),
dimensions::Tile::new_rc (height, width).into()
)
} else {
debug_assert!(canvas.coordinates.kind() == coordinates::Kind::Pixel);
( Canvas::default_pixel(),
dimensions::Pixel::new_wh (width, height).into()
)
};
parent.coordinates.set_dimensions (dimensions);
parent
} else {
let Widget (_, _, parent_canvas) =
Frame::try_get (elements, elements.get_parent_id (frame_id)).unwrap();
parent_canvas.clone()
};
vec![(
frame_id.clone(),
free::coordinates (&parent_canvas, free, area, coord_kind_override)
)]
}
layout::Variant::Tiled (tiled) => {
debug_assert!(elements.root_node_id().unwrap() != frame_id,
"tiled root frame is unsupported");
tiled::new_coordinates (elements, frame_id, tiled)
}
};
log::trace!("...new_coordinates");
out
}
fn set_coordinates (
elements : &Tree <Element>,
frame_id : &NodeId,
coordinates : &Coordinates,
orientation : Option <(Orientation, Area)>,
action_buffer : &mut Vec <(NodeId, Action)>
) {
log::trace!("set_coordinates...");
{ let new_coordinates = *coordinates;
let update_canvas = Box::new (move |view : &mut View| {
use view::component::{Canvas, Kind};
let canvas = Canvas::try_ref_mut (&mut view.component).unwrap();
canvas.coordinates = new_coordinates;
});
action_buffer.push ((frame_id.clone(), Action::ModifyView (update_canvas)));
}
for (child_id, coordinates) in update_children (
elements, frame_id, coordinates, orientation, action_buffer
) {
set_coordinates (elements, &child_id, &coordinates, None, action_buffer);
}
log::trace!("...set_coordinates");
}
fn update_children (
elements : &Tree <Element>,
frame_id : &NodeId,
coordinates : &Coordinates,
orientation : Option <(Orientation, Area)>,
action_buffer : &mut Vec <(NodeId, Action)>
) -> Vec <(NodeId, Coordinates)> {
use view::component::{Body, Canvas, Image, Kind};
log::trace!("update_children...");
let mut child_coordinates = vec![];
let (mut tiled_ids, mut tiled_layouts) = (vec![], vec![]);
let node = elements.get (frame_id).unwrap();
let Widget (layout, _, canvas) = Frame::try_from (node.data()).unwrap();
let new_canvas = {
let coordinates = *coordinates;
Canvas { coordinates, .. canvas.clone() }
};
let orientation = orientation.unwrap_or (layout.orientation.clone());
for child_id in node.children().iter() {
if let Ok (Widget (layout, _, canvas)) = Frame::try_get (elements, child_id) {
use controller::component::layout;
match &layout.variant {
layout::Variant::Free (free, area) => {
let coordinate_kind = Some (canvas.coordinates.kind());
child_coordinates.push ((
child_id.clone(),
free::coordinates (&new_canvas, free, area, coordinate_kind)
))
}
layout::Variant::Tiled (tiled) => {
tiled_ids.push (child_id.clone());
tiled_layouts.push (tiled.clone());
}
}
} else if let Ok (Widget (scroll, text, _)) = Textbox::try_get (elements, child_id) {
let new_body = textbox::body (&new_canvas, scroll, text);
let update_body = Box::new (|view : &mut View|{
let body = Body::try_ref_mut (&mut view.component).unwrap();
*body = new_body;
});
action_buffer.push ((child_id.clone(), Action::ModifyView (update_body)));
} else if let Ok (Widget (scroll, pixmap, _)) =
Picture::try_get (elements, child_id)
{
let new_image = picture::image (&new_canvas, scroll, pixmap);
let update_image = Box::new (|view : &mut View|{
let image = Image::try_ref_mut (&mut view.component).unwrap();
*image = new_image;
});
action_buffer.push ((child_id.clone(), Action::ModifyView (update_image)));
}
}
child_coordinates.extend (
tiled_ids.into_iter().zip (
tiled::child_coordinates (&orientation, &new_canvas, tiled_layouts)));
log::trace!("...update_children");
child_coordinates
}
mod builder {
use derive_builder::Builder;
use crate::prelude::*;
#[derive(Builder)]
#[builder(public, pattern="owned", build_fn(private), setter(strip_option))]
struct Frame <'a, A : Application> {
elements : &'a Tree <Element>,
parent_id : &'a NodeId,
#[builder(default)]
appearances : Appearances,
#[builder(default)]
bindings : Option <&'a Bindings <A>>,
#[builder(default)]
border : Option <Border>,
#[builder(default)]
clear_color : canvas::ClearColor,
#[builder(default)]
disabled : bool,
#[builder(default)]
layout : Layout,
#[builder(default)]
name : Option <String>
}
impl <'a, A : Application> FrameBuilder <'a, A> {
pub const fn new (elements : &'a Tree <Element>, parent_id : &'a NodeId) -> Self {
FrameBuilder {
elements: Some (elements),
parent_id: Some (parent_id),
appearances: None,
bindings: None,
border: None,
clear_color: None,
disabled: None,
layout: None,
name: None
}
}
}
impl <A : Application> BuildActions for FrameBuilder <'_, A> {
fn build_actions (self) -> Vec<(NodeId, Action)> {
use controller::component::layout;
use crate::widget::{set_option, BuildElement};
log::trace!("build actions...");
let Frame {
elements, parent_id, appearances, bindings, border, clear_color, disabled,
layout, name
} = self.build()
.map_err(|err| log::error!("frame builder error: {err:?}")).unwrap();
let out = match layout.variant {
layout::Variant::Free (free, area) => {
let frame = {
let mut free = super::free::Builder::new (elements, parent_id)
.appearances (appearances)
.area (area)
.clear_color (clear_color)
.disabled (disabled)
.layout (free)
.orientation (layout.orientation);
set_option!(free, bindings, bindings);
set_option!(free, border, border);
set_option!(free, name, name);
free.build_element()
};
vec![(
parent_id.clone(),
Action::create_singleton (frame, CreateOrder::Append)
)]
}
layout::Variant::Tiled (tiled) => {
let mut tiled =
super::tiled::Builder::new (elements, parent_id)
.appearances (appearances)
.clear_color (clear_color)
.disabled (disabled)
.layout (tiled)
.orientation (layout.orientation);
set_option!(tiled, bindings, bindings);
set_option!(tiled, border, border);
set_option!(tiled, name, name);
tiled.build_actions()
}
};
log::trace!("...build actions");
out
}
}
}