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 ColumnHorizontalAlignment {
Left,
Center,
Right,
}
#[derive(Clone)]
pub struct ColumnLayoutElement {
pub drawable: Rc<dyn Drawable>,
pub alignment: ColumnHorizontalAlignment,
}
pub struct ColumnLayout {
pub childs: RefCell<BTreeMap<String, ColumnLayoutElement>>,
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 ColumnLayout {
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: ColumnLayoutElement) -> 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: ColumnLayoutElement) {
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<ColumnLayoutElement> {
self.childs.borrow().get(id).cloned()
}
}
impl HasPlanner for ColumnLayout {
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 ColumnLayout {
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 total_fit_h = 0.0;
let mut fill_count = 0;
let mut none_count = 0;
for el in childs_borrow.values() {
if !el.drawable.is_enabled() {
continue;
}
match el.drawable.get_vertical_plan() {
Plan::Fit(h) => total_fit_h += h,
Plan::FillPositive => fill_count += 1,
Plan::FillNegative => none_count += 1,
}
}
if total_fit_h > parent_h {
anyhow::bail!(
"ColumnLayout Error: Total requested height of 'Fit' children ({}) exceeds available container height ({})",
total_fit_h,
parent_h
);
}
let remaining_h = parent_h - total_fit_h;
let fill_allocated_h = if fill_count > 0 {
remaining_h / fill_count as f32
} else {
0.0
};
let none_allocated_h = if fill_count == 0 && none_count > 0 {
remaining_h / none_count as f32
} else {
0.0
};
{
let mut current_y = parent_area.top;
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 child_h = match el.drawable.get_vertical_plan() {
Plan::Fit(h) => h,
Plan::FillPositive => fill_allocated_h,
Plan::FillNegative => none_allocated_h,
};
let child_w = match el.drawable.get_horizontal_plan() {
Plan::Fit(w) => w,
_ => parent_w, };
let child_x = match el.alignment {
ColumnHorizontalAlignment::Left => parent_area.left,
ColumnHorizontalAlignment::Center => {
parent_area.left + (parent_w - child_w) / 2.0
}
ColumnHorizontalAlignment::Right => parent_area.right - child_w,
};
let child_rect = Rect::from_xywh(child_x, current_y, child_w, child_h);
rects.insert(target_ptr, child_rect);
current_y += child_h;
}
}
for (key, el) in childs_borrow.iter() {
if !el.drawable.is_enabled() {
continue;
}
if let Err(e) = el.drawable.draw(canvas) {
println!("ColumnLayout 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);
}
_ => {}
}
}
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 total_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) => {
total_h += h;
}
_ => {}
}
}
let plan = if has_fill_positive {
Plan::FillPositive
} else {
Plan::Fit(total_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 ColumnLayout {
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 ColumnLayout {
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);
}
});
}
}