use cvkg_core::Rect;
use std::collections::HashMap;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
pub struct LayerId(pub u64);
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub enum Material {
#[default]
Opaque,
Glass {
blur_radius: f32,
depth_index: u32,
},
Overlay,
Multiply,
Screen,
BlendOverlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
Isolated,
ShaderEffect {
effect_name: String,
params_json: String,
},
}
#[derive(Debug, Clone)]
pub struct DrawCommand {
pub texture_id: Option<u32>,
pub scissor_rect: Option<Rect>,
pub index_start: u32,
pub index_count: u32,
pub instance_id: u32,
}
#[derive(Debug, Clone)]
pub struct Layer {
pub id: LayerId,
pub bounds: Rect,
pub transform: [f32; 16],
pub material: Material,
pub draw_list: Vec<DrawCommand>,
pub children: Vec<LayerId>,
pub visible: bool,
pub opacity: f32,
}
impl Default for Layer {
fn default() -> Self {
Self {
id: LayerId::default(),
bounds: Rect::zero(),
transform: [
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
],
material: Material::Opaque,
draw_list: Vec::new(),
children: Vec::new(),
visible: true,
opacity: 1.0,
}
}
}
pub struct LayerTree {
layers: HashMap<LayerId, Layer>,
roots: Vec<LayerId>,
next_id: u64,
generation: u64,
layer_generations: HashMap<LayerId, u64>,
}
impl Default for LayerTree {
fn default() -> Self {
Self::new()
}
}
impl LayerTree {
pub fn new() -> Self {
Self {
layers: HashMap::new(),
roots: Vec::new(),
next_id: 1,
generation: 0,
layer_generations: HashMap::new(),
}
}
pub fn allocate_id(&mut self) -> LayerId {
let id = LayerId(self.next_id);
self.next_id += 1;
id
}
pub fn insert_layer(&mut self, layer: Layer) {
let id = layer.id;
self.layer_generations.insert(id, self.generation);
self.layers.insert(id, layer);
}
pub fn remove_layer(&mut self, id: LayerId) -> Option<Layer> {
for layer in self.layers.values_mut() {
layer.children.retain(|&child_id| child_id != id);
}
self.roots.retain(|&root_id| root_id != id);
self.layer_generations.remove(&id);
self.layers.remove(&id)
}
pub fn get_layer(&self, id: LayerId) -> Option<&Layer> {
self.layers.get(&id)
}
pub fn get_layer_mut(&mut self, id: LayerId) -> Option<&mut Layer> {
self.layers.get_mut(&id)
}
pub fn roots(&self) -> &[LayerId] {
&self.roots
}
pub fn set_roots(&mut self, roots: Vec<LayerId>) {
self.roots = roots;
}
pub fn mark_dirty(&mut self, id: LayerId) {
self.layer_generations.insert(id, self.generation);
}
pub fn is_dirty(&self, id: LayerId, since_generation: u64) -> bool {
self.layer_generations
.get(&id)
.is_some_and(|&g| g > since_generation)
}
pub fn advance_generation(&mut self) {
self.generation += 1;
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn iter_layers(&self) -> impl Iterator<Item = &Layer> {
self.layers.values()
}
pub fn len(&self) -> usize {
self.layers.len()
}
pub fn is_empty(&self) -> bool {
self.layers.is_empty()
}
pub fn clear(&mut self) {
self.layers.clear();
self.roots.clear();
self.layer_generations.clear();
self.generation += 1;
}
pub fn move_to_front(&mut self, child_id: LayerId) -> bool {
let parent_id = self
.layers
.values()
.find(|l| l.children.contains(&child_id))
.map(|l| l.id);
match parent_id {
Some(parent_id) => {
let parent = self.layers.get_mut(&parent_id).unwrap();
if let Some(pos) = parent.children.iter().position(|&id| id == child_id) {
parent.children.remove(pos);
parent.children.push(child_id);
self.mark_dirty(child_id);
true
} else {
false
}
}
None => {
if let Some(pos) = self.roots.iter().position(|&id| id == child_id) {
self.roots.remove(pos);
self.roots.push(child_id);
self.mark_dirty(child_id);
true
} else {
false
}
}
}
}
pub fn move_to_back(&mut self, child_id: LayerId) -> bool {
let parent_id = self
.layers
.values()
.find(|l| l.children.contains(&child_id))
.map(|l| l.id);
match parent_id {
Some(parent_id) => {
let parent = self.layers.get_mut(&parent_id).unwrap();
if let Some(pos) = parent.children.iter().position(|&id| id == child_id) {
parent.children.remove(pos);
parent.children.insert(0, child_id);
self.mark_dirty(child_id);
true
} else {
false
}
}
None => {
if let Some(pos) = self.roots.iter().position(|&id| id == child_id) {
self.roots.remove(pos);
self.roots.insert(0, child_id);
self.mark_dirty(child_id);
true
} else {
false
}
}
}
}
pub fn layer_at_point(&self, x: f32, y: f32) -> Option<LayerId> {
self.layer_at_point_recursive(&self.roots, x, y)
}
fn layer_at_point_recursive(
&self,
children: &[LayerId],
x: f32,
y: f32,
) -> Option<LayerId> {
for &child_id in children.iter().rev() {
if let Some(layer) = self.layers.get(&child_id) {
if !layer.visible {
continue;
}
if let Some(found) = self.layer_at_point_recursive(&layer.children, x, y) {
return Some(found);
}
if layer.bounds.contains(x, y) {
return Some(child_id);
}
}
}
None
}
pub fn find_layers_by_material(&self, material: &Material) -> Vec<LayerId> {
self.layers
.values()
.filter(|l| &l.material == material)
.map(|l| l.id)
.collect()
}
pub fn validate_tree(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
for &root_id in &self.roots {
if !self.layers.contains_key(&root_id) {
errors.push(format!("root {} references non-existent layer", root_id.0));
}
}
for layer in self.layers.values() {
for &child_id in &layer.children {
if child_id == layer.id {
errors.push(format!(
"layer {} is its own child",
layer.id.0
));
}
if !self.layers.contains_key(&child_id) {
errors.push(format!(
"layer {} references non-existent child {}",
layer.id.0, child_id.0
));
}
}
}
let mut reachable = std::collections::HashSet::new();
let mut stack: Vec<LayerId> = self.roots.iter().copied().collect();
while let Some(id) = stack.pop() {
if !reachable.insert(id) {
continue;
}
if let Some(layer) = self.layers.get(&id) {
stack.extend(layer.children.iter().copied());
}
}
for &id in self.layers.keys() {
if !reachable.contains(&id) {
errors.push(format!("layer {} is orphaned (not reachable from any root)", id.0));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn dump_tree(&self) -> String {
let mut out = String::new();
for (i, &root_id) in self.roots.iter().enumerate() {
let last = i == self.roots.len() - 1;
self.dump_node(root_id, &mut out, "", last);
}
out
}
fn dump_node(&self, id: LayerId, out: &mut String, prefix: &str, last: bool) {
let connector = if last { "└── " } else { "├── " };
let layer = match self.layers.get(&id) {
Some(l) => l,
None => {
out.push_str(&format!("{}{}<missing {}>\n", prefix, connector, id.0));
return;
}
};
let vis = if layer.visible { "" } else { " [hidden]" };
let mat = match &layer.material {
Material::Opaque => "Opaque".into(),
Material::Overlay => "Overlay".into(),
Material::Glass { blur_radius, depth_index } => {
format!("Glass(blur={}, depth={})", blur_radius, depth_index)
}
other => format!("{:?}", other),
};
out.push_str(&format!(
"{}{}[{}] {} ({:.0}x{:.0} @{:.0},{:.0}){}\n",
prefix,
connector,
id.0,
mat,
layer.bounds.width,
layer.bounds.height,
layer.bounds.x,
layer.bounds.y,
vis,
));
let child_prefix = if last {
format!("{} ", prefix)
} else {
format!("{}│ ", prefix)
};
for (i, &child_id) in layer.children.iter().enumerate() {
let child_last = i == layer.children.len() - 1;
self.dump_node(child_id, out, &child_prefix, child_last);
}
}
}