use crate::drawing::{Drawable, Plan};
use crate::interacting::{Event, Interactive};
use crate::layouts::{HasPlanner, Layout};
use skia_safe::{Canvas, Rect};
use std::any::Any;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BoxArrangement {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Center,
}
#[derive(Clone, Debug)]
pub struct BoxPadding {
pub left: f32,
pub right: f32,
pub top: f32,
pub bottom: f32,
}
impl BoxPadding {
pub fn new(left: f32, right: f32, top: f32, bottom: f32) -> Self {
Self {
left,
right,
top,
bottom,
}
}
pub fn zero() -> Self {
Self {
left: 0.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
}
}
}
#[derive(Clone)]
pub struct BoxLayoutElement {
pub drawable: Rc<dyn Drawable>,
pub arrangement: BoxArrangement,
pub padding: BoxPadding,
}
pub struct BoxLayout {
pub childs: RefCell<BTreeMap<String, BoxLayoutElement>>,
pub planner: RefCell<Option<Rc<dyn Layout>>>,
calculated_rects: RefCell<BTreeMap<usize, Rect>>,
enabled: RefCell<bool>,
cached_h_plan: RefCell<Option<Plan>>,
cached_v_plan: RefCell<Option<Plan>>,
}
impl BoxLayout {
pub fn new(is_enabled: bool) -> Rc<Self> {
Rc::new(Self {
childs: RefCell::new(BTreeMap::new()),
planner: RefCell::new(None),
calculated_rects: RefCell::new(BTreeMap::new()),
enabled: RefCell::new(is_enabled),
cached_h_plan: RefCell::new(None),
cached_v_plan: RefCell::new(None),
})
}
pub fn add_child(self: Rc<Self>, id: &str, e: BoxLayoutElement) -> Rc<Self> {
e.drawable.set_planner(Some(self.clone()));
self.childs.borrow_mut().insert(id.to_string(), e);
self.replan();
self
}
pub fn add_child_inplace(self: &Rc<Self>, id: &str, e: BoxLayoutElement) {
e.drawable.set_planner(Some(self.clone()));
self.childs.borrow_mut().insert(id.to_string(), e);
}
pub fn remove_child(&self, id: &str) {
if let Some(c) = self.lookup_child(id) {
c.drawable.set_planner(None);
self.childs.borrow_mut().remove(id);
self.replan();
}
}
pub fn lookup_child(&self, id: &str) -> Option<BoxLayoutElement> {
self.childs.borrow().get(id).cloned()
}
#[allow(clippy::too_many_arguments)]
pub fn rewrite_child(&self, id: &str, r: BoxArrangement, p: BoxPadding) -> anyhow::Result<()> {
if let Some(v) = self.childs.borrow_mut().get_mut(id) {
v.arrangement = r;
v.padding = p;
self.replan();
return Ok(());
}
anyhow::bail!("Id not found")
}
}
impl HasPlanner for BoxLayout {
fn planner(&self) -> &RefCell<Option<Rc<dyn Layout>>> {
&self.planner
}
fn set_planner(&self, p: Option<Rc<dyn Layout>>) {
*self.planner.borrow_mut() = p;
}
}
impl Drawable for BoxLayout {
fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
let parent_area = self.get_planned_drawing_area(canvas);
let parent_w = parent_area.width();
let parent_h = parent_area.height();
if parent_w <= 0.0 || parent_h <= 0.0 {
return Ok(());
}
let childs_borrow = self.childs.borrow();
let mut min_x: Option<f32> = None;
let mut max_x: Option<f32> = None;
let mut min_y: Option<f32> = None;
let mut max_y: Option<f32> = None;
let mut update_x = |min: f32, max: f32| {
min_x = Some(min_x.map_or(min, |v| v.min(min)));
max_x = Some(max_x.map_or(max, |v| v.max(max)));
};
let mut update_y = |min: f32, max: f32| {
min_y = Some(min_y.map_or(min, |v| v.min(min)));
max_y = Some(max_y.map_or(max, |v| v.max(max)));
};
for el in childs_borrow.values() {
if !el.drawable.is_enabled() {
continue;
}
let h_plan = el.drawable.get_horizontal_plan();
let v_plan = el.drawable.get_vertical_plan();
let pad = &el.padding;
let max_aw = (parent_w - pad.left - pad.right).max(0.0);
let max_ah = (parent_h - pad.top - pad.bottom).max(0.0);
let cw = match h_plan {
Plan::Fit(w) => w,
_ => max_aw,
};
let ch = match v_plan {
Plan::Fit(h) => h,
_ => max_ah,
};
let (x, y) = match el.arrangement {
BoxArrangement::TopLeft => (parent_area.left + pad.left, parent_area.top + pad.top),
BoxArrangement::TopRight => (
parent_area.right - pad.right - cw,
parent_area.top + pad.top,
),
BoxArrangement::BottomLeft => (
parent_area.left + pad.left,
parent_area.bottom - pad.bottom - ch,
),
BoxArrangement::BottomRight => (
parent_area.right - pad.right - cw,
parent_area.bottom - pad.bottom - ch,
),
BoxArrangement::Center => {
let cx = parent_area.left + pad.left + (max_aw - cw) / 2.0;
let cy = parent_area.top + pad.top + (max_ah - ch) / 2.0;
(cx, cy)
}
};
if h_plan != Plan::FillNegative {
update_x(x - pad.left, x + cw + pad.right);
}
if v_plan != Plan::FillNegative {
update_y(y - pad.top, y + ch + pad.bottom);
}
}
let union_left = min_x.unwrap_or(parent_area.left);
let union_right = max_x.unwrap_or(parent_area.right);
let union_top = min_y.unwrap_or(parent_area.top);
let union_bottom = max_y.unwrap_or(parent_area.bottom);
let union_w = (union_right - union_left).max(0.0);
let union_h = (union_bottom - union_top).max(0.0);
{
let mut rects = self.calculated_rects.borrow_mut();
rects.clear();
for el in childs_borrow.values() {
if !el.drawable.is_enabled() {
continue;
}
let target_ptr = el.drawable.as_ref() as *const _ as *const () as usize;
let h_plan = el.drawable.get_horizontal_plan();
let v_plan = el.drawable.get_vertical_plan();
let pad = &el.padding;
let max_aw = (parent_w - pad.left - pad.right).max(0.0);
let max_ah = (parent_h - pad.top - pad.bottom).max(0.0);
let cw = match h_plan {
Plan::Fit(w) => w,
Plan::FillPositive => max_aw,
Plan::FillNegative => union_w,
};
let ch = match v_plan {
Plan::Fit(h) => h,
Plan::FillPositive => max_ah,
Plan::FillNegative => union_h,
};
let (mut x, mut y) = match el.arrangement {
BoxArrangement::TopLeft => {
(parent_area.left + pad.left, parent_area.top + pad.top)
}
BoxArrangement::TopRight => (
parent_area.right - pad.right - cw,
parent_area.top + pad.top,
),
BoxArrangement::BottomLeft => (
parent_area.left + pad.left,
parent_area.bottom - pad.bottom - ch,
),
BoxArrangement::BottomRight => (
parent_area.right - pad.right - cw,
parent_area.bottom - pad.bottom - ch,
),
BoxArrangement::Center => {
let cx = parent_area.left + pad.left + (max_aw - cw) / 2.0;
let cy = parent_area.top + pad.top + (max_ah - ch) / 2.0;
(cx, cy)
}
};
if h_plan == Plan::FillNegative {
x = union_left;
}
if v_plan == Plan::FillNegative {
y = union_top;
}
rects.insert(target_ptr, Rect::from_xywh(x, y, cw, ch));
}
}
for (key, el) in childs_borrow.iter() {
if !el.drawable.is_enabled() {
continue;
}
if let Err(e) = el.drawable.draw(canvas) {
println!("BoxLayout draw error at child {}: {:?}", key, e);
}
}
Ok(())
}
fn get_horizontal_plan(&self) -> Plan {
if let Some(plan) = *self.cached_h_plan.borrow() {
return plan;
}
let mut max_w = 0.0f32;
let mut has_fill_positive = false;
for el in self.childs.borrow().values() {
if !el.drawable.is_enabled() {
continue;
}
match el.drawable.get_horizontal_plan() {
Plan::FillPositive => {
has_fill_positive = true;
break;
}
Plan::Fit(w) => {
max_w = max_w.max(w + el.padding.left + el.padding.right);
}
Plan::FillNegative => {
max_w = max_w.max(el.padding.left + el.padding.right);
}
}
}
let plan = if has_fill_positive {
Plan::FillPositive
} else {
Plan::Fit(max_w)
};
*self.cached_h_plan.borrow_mut() = Some(plan);
plan
}
fn get_vertical_plan(&self) -> Plan {
if let Some(plan) = *self.cached_v_plan.borrow() {
return plan;
}
let mut max_h = 0.0f32;
let mut has_fill_positive = false;
for el in self.childs.borrow().values() {
if !el.drawable.is_enabled() {
continue;
}
match el.drawable.get_vertical_plan() {
Plan::FillPositive => {
has_fill_positive = true;
break;
}
Plan::Fit(h) => {
max_h = max_h.max(h + el.padding.top + el.padding.bottom);
}
Plan::FillNegative => {
max_h = max_h.max(el.padding.top + el.padding.bottom);
}
}
}
let plan = if has_fill_positive {
Plan::FillPositive
} else {
Plan::Fit(max_h)
};
*self.cached_v_plan.borrow_mut() = Some(plan);
plan
}
fn replan(&self) {
*self.cached_h_plan.borrow_mut() = None;
*self.cached_v_plan.borrow_mut() = None;
for el in self.childs.borrow().values() {
el.drawable.replan();
}
}
fn try_to_interactive(&self) -> Option<&dyn Interactive> {
Some(self)
}
fn is_enabled(&self) -> bool {
*self.enabled.borrow()
}
fn set_enabled(&self, e: bool) {
*self.enabled.borrow_mut() = e
}
fn as_drawable(&self) -> &dyn Drawable {
self
}
}
impl Layout for BoxLayout {
fn measure_child(&self, c: &dyn Drawable, _canvas: &Canvas) -> Rect {
let target_ptr = c as *const _ as *const () as usize;
self.calculated_rects
.borrow()
.get(&target_ptr)
.cloned()
.unwrap_or_else(|| Rect::new(0.0, 0.0, 0.0, 0.0))
}
fn as_layout(&self) -> &dyn Layout {
self
}
fn foreach_child(&self, f: &mut dyn FnMut(&dyn Drawable)) {
let ch = self.childs.borrow();
for i in ch.iter() {
f(i.1.drawable.as_ref());
}
}
}
impl Interactive for BoxLayout {
fn handle_and_route(&self, e: &Event, u: &dyn Any) {
self.foreach_child(&mut |x| {
if x.is_enabled()
&& let Some(i) = x.try_to_interactive()
{
i.handle_and_route(e, u);
}
});
}
}