use crate::drawing::{Drawable, Plan};
use crate::interacting::{Event, Interactive};
use crate::layouts::{HasPlanner, Layout};
use skia_safe::{Canvas, ClipOp, Rect};
use std::any::Any;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Clone)]
pub struct ListLayoutElement {
pub drawable: Rc<dyn Drawable>,
pub index: usize,
}
pub struct ListLayout {
pub selected: AtomicUsize,
pub item_height: f32,
pub padding_x: f32,
pub padding_top: f32,
pub padding_bottom: f32,
pub childs: RefCell<BTreeMap<String, ListLayoutElement>>,
pub planner: RefCell<Option<Rc<dyn Layout>>>,
enabled: RefCell<bool>,
}
impl HasPlanner for ListLayout {
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 ListLayout {
fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
let area = self.get_planned_drawing_area(canvas);
let clip_rect = Rect::from_xywh(
area.left + self.padding_x,
area.top + self.padding_top,
(area.width() - self.padding_x * 2.0).max(0.0),
(area.height() - self.padding_top - self.padding_bottom).max(0.0),
);
if clip_rect.is_empty() {
return Ok(());
}
canvas.save();
canvas.clip_rect(clip_rect, ClipOp::Intersect, true);
let childs = self.childs.borrow();
let mut ordered_childs: Vec<_> = childs
.values()
.filter(|e| e.drawable.is_enabled())
.collect();
ordered_childs.sort_by_key(|e| e.index);
for element in ordered_childs {
let child_rect = self.measure_child_internal(&*element.drawable, &area);
if clip_rect.intersects(child_rect)
&& let Err(e) = element.drawable.draw(canvas)
{
println!(
"ListLayout render error on child {}: {:?}",
element.index, e
);
}
}
canvas.restore();
Ok(())
}
fn get_horizontal_plan(&self) -> Plan {
Plan::FillPositive
}
fn get_vertical_plan(&self) -> Plan {
Plan::FillPositive
}
fn replan(&self) {
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 ListLayout {
fn measure_child(&self, c: &dyn Drawable, canvas: &Canvas) -> Rect {
let parent_area = self.get_planned_drawing_area(canvas);
self.measure_child_internal(c, &parent_area)
}
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 ListLayout {
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);
}
});
}
}
impl ListLayout {
pub fn new(
item_height: f32,
padding_x: f32,
padding_top: f32,
padding_bottom: f32,
is_enabled: bool,
) -> Rc<Self> {
Rc::new(Self {
selected: AtomicUsize::new(0),
item_height,
padding_x,
padding_top,
padding_bottom,
childs: RefCell::new(BTreeMap::new()),
planner: RefCell::new(None),
enabled: RefCell::new(is_enabled),
})
}
pub fn add_child(self: Rc<Self>, id: &str, e: ListLayoutElement) -> Rc<Self> {
e.drawable.set_planner(Some(self.clone()));
self.childs.borrow_mut().insert(id.to_string(), e);
self
}
pub fn remove_child(&self, id: &str) {
if let Some(c) = self.childs.borrow_mut().remove(id) {
c.drawable.set_planner(None);
}
}
fn measure_child_internal(&self, c: &dyn Drawable, parent_area: &Rect) -> Rect {
let childs = self.childs.borrow();
let mut enabled_childs: Vec<_> = childs
.values()
.filter(|e| e.drawable.is_enabled())
.collect();
enabled_childs.sort_by_key(|e| e.index);
let mut target_visual_index = None;
let total_items = enabled_childs.len();
let c_ptr = c as *const dyn Drawable as *const ();
for (visual_index, element) in enabled_childs.iter().enumerate() {
let elem_ptr = &*element.drawable as *const dyn Drawable as *const ();
if std::ptr::eq(c_ptr, elem_ptr) {
target_visual_index = Some(visual_index);
break;
}
}
if let Some(visual_index) = target_visual_index {
let visible_height =
(parent_area.height() - self.padding_top - self.padding_bottom).max(0.0);
let total_height = total_items as f32 * self.item_height;
let selected = self.selected.load(Ordering::Relaxed) as f32;
let target_scroll_y =
(selected * self.item_height) - (visible_height / 2.0) + (self.item_height / 2.0);
let max_scroll = (total_height - visible_height).max(0.0);
let scroll_y = target_scroll_y.clamp(0.0, max_scroll);
let item_y = (visual_index as f32) * self.item_height;
let absolute_top = parent_area.top + self.padding_top + item_y - scroll_y;
Rect::from_xywh(
parent_area.left + self.padding_x,
absolute_top,
(parent_area.width() - self.padding_x * 2.0).max(0.0),
self.item_height,
)
} else {
Rect::new(0.0, 0.0, 0.0, 0.0)
}
}
}