use alloc::vec::Vec;
use crate::components::button::Button;
use crate::components::checkbox::Checkbox;
use crate::components::image::Image;
use crate::components::progress_bar::ProgressBar;
use crate::draw::command::DrawCommand;
use crate::draw::renderer::Renderer;
use crate::ecs::{Entity, World};
use crate::layout::{LayoutNode, compute_layout};
use crate::types::{Color, Point, Rect};
use super::{Children, Style, Text, Widget};
fn build_layout_tree(world: &World, entity: Entity) -> Option<LayoutNode> {
world.get::<Widget>(entity)?;
let style = world.get::<Style>(entity)?;
let mut node = LayoutNode::new(style.layout);
if let Some(children) = world.get::<Children>(entity) {
for &child in &children.0 {
if let Some(child_node) = build_layout_tree(world, child) {
node.add_child(child_node);
}
}
}
Some(node)
}
fn rects_intersect(a: &Rect, b: &Rect) -> bool {
a.x < b.x + b.w as i32
&& a.x + a.w as i32 > b.x
&& a.y < b.y + b.h as i32
&& a.y + a.h as i32 > b.y
}
fn count_nodes(node: &LayoutNode) -> usize {
1 + node.children.iter().map(count_nodes).sum::<usize>()
}
fn draw_tree(
node: &LayoutNode,
world: &World,
entities: &[Entity],
idx: &mut usize,
renderer: &mut dyn Renderer,
clip: &Rect,
) {
if !rects_intersect(&node.rect, clip) {
*idx += count_nodes(node);
return;
}
if *idx < entities.len() {
let entity = entities[*idx];
if let Some(style) = world.get::<Style>(entity) {
let bg = if let Some(btn) = world.get::<Button>(entity) {
Some(btn.current_color())
} else if let Some(cb) = world.get::<Checkbox>(entity) {
Some(cb.current_color())
} else {
style.bg_color
};
if let Some(color) = bg {
renderer.draw(
&DrawCommand::Fill {
area: node.rect,
color,
radius: style.border_radius,
opa: 255,
},
clip,
);
}
if let Some(border_color) = style.border_color {
if style.border_width > 0 {
renderer.draw(
&DrawCommand::Border {
area: node.rect,
color: border_color,
width: style.border_width,
radius: style.border_radius,
opa: 255,
},
clip,
);
}
}
if let Some(pb) = world.get::<ProgressBar>(entity) {
renderer.draw(
&DrawCommand::Fill {
area: node.rect,
color: pb.track_color,
radius: style.border_radius,
opa: 255,
},
clip,
);
let fill_w = ((node.rect.w as f32) * pb.value.clamp(0.0, 1.0)) as u16;
if fill_w > 0 {
renderer.draw(
&DrawCommand::Fill {
area: Rect {
x: node.rect.x,
y: node.rect.y,
w: fill_w,
h: node.rect.h,
},
color: pb.fill_color,
radius: style.border_radius,
opa: 255,
},
clip,
);
}
}
if let Some(img) = world.get::<Image>(entity) {
renderer.draw(
&DrawCommand::Blit {
pos: Point {
x: node.rect.x,
y: node.rect.y,
},
data: &img.data,
width: img.width,
height: img.height,
},
clip,
);
}
if let Some(text) = world.get::<Text>(entity) {
let color = style.text_color.unwrap_or(Color::rgb(255, 255, 255));
renderer.draw(
&DrawCommand::Label {
pos: Point {
x: node.rect.x + 2,
y: node.rect.y + 2,
},
text: &text.0,
color,
opa: 255,
},
clip,
);
}
}
}
*idx += 1;
let entity = if *idx > 0 && (*idx - 1) < entities.len() {
entities[*idx - 1]
} else {
Entity {
id: u32::MAX,
generation: 0,
}
};
let (child_clip, scroll_x, scroll_y) =
if let Some(scroll) = world.get::<crate::components::scroll::ScrollOffset>(entity) {
let cx = clip.x.max(node.rect.x);
let cy = clip.y.max(node.rect.y);
let cx2 = (clip.x + clip.w as i32).min(node.rect.x + node.rect.w as i32);
let cy2 = (clip.y + clip.h as i32).min(node.rect.y + node.rect.h as i32);
let new_clip = Rect {
x: cx,
y: cy,
w: (cx2 - cx).max(0) as u16,
h: (cy2 - cy).max(0) as u16,
};
let s = world
.resource::<crate::backend::DisplayInfo>()
.map(|d| d.scale as i32)
.unwrap_or(1);
(new_clip, scroll.x * s, scroll.y * s)
} else {
(*clip, 0, 0)
};
for child in &node.children {
draw_tree_offset(
child,
world,
entities,
idx,
renderer,
&child_clip,
scroll_x,
scroll_y,
);
}
}
#[allow(clippy::too_many_arguments)]
fn draw_tree_offset(
node: &LayoutNode,
world: &World,
entities: &[Entity],
idx: &mut usize,
renderer: &mut dyn Renderer,
clip: &Rect,
offset_x: i32,
offset_y: i32,
) {
let shifted_rect = Rect {
x: node.rect.x - offset_x,
y: node.rect.y - offset_y,
w: node.rect.w,
h: node.rect.h,
};
if !rects_intersect(&shifted_rect, clip) {
*idx += count_nodes(node);
return;
}
if *idx < entities.len() {
let entity = entities[*idx];
if let Some(style) = world.get::<Style>(entity) {
let bg = if let Some(btn) = world.get::<Button>(entity) {
Some(btn.current_color())
} else if let Some(cb) = world.get::<Checkbox>(entity) {
Some(cb.current_color())
} else {
style.bg_color
};
if let Some(color) = bg {
renderer.draw(
&DrawCommand::Fill {
area: shifted_rect,
color,
radius: style.border_radius,
opa: 255,
},
clip,
);
}
if let Some(border_color) = style.border_color {
if style.border_width > 0 {
renderer.draw(
&DrawCommand::Border {
area: shifted_rect,
color: border_color,
width: style.border_width,
radius: style.border_radius,
opa: 255,
},
clip,
);
}
}
if let Some(pb) = world.get::<ProgressBar>(entity) {
renderer.draw(
&DrawCommand::Fill {
area: shifted_rect,
color: pb.track_color,
radius: style.border_radius,
opa: 255,
},
clip,
);
let fill_w = ((shifted_rect.w as f32) * pb.value.clamp(0.0, 1.0)) as u16;
if fill_w > 0 {
renderer.draw(
&DrawCommand::Fill {
area: Rect {
x: shifted_rect.x,
y: shifted_rect.y,
w: fill_w,
h: shifted_rect.h,
},
color: pb.fill_color,
radius: style.border_radius,
opa: 255,
},
clip,
);
}
}
if let Some(img) = world.get::<Image>(entity) {
renderer.draw(
&DrawCommand::Blit {
pos: Point {
x: shifted_rect.x,
y: shifted_rect.y,
},
data: &img.data,
width: img.width,
height: img.height,
},
clip,
);
}
if let Some(text) = world.get::<Text>(entity) {
let color = style.text_color.unwrap_or(Color::rgb(255, 255, 255));
renderer.draw(
&DrawCommand::Label {
pos: Point {
x: shifted_rect.x + 2,
y: shifted_rect.y + 2,
},
text: &text.0,
color,
opa: 255,
},
clip,
);
}
}
}
*idx += 1;
let cur_entity = if *idx > 0 && (*idx - 1) < entities.len() {
entities[*idx - 1]
} else {
Entity {
id: u32::MAX,
generation: 0,
}
};
let (child_clip, sx, sy) =
if let Some(scroll) = world.get::<crate::components::scroll::ScrollOffset>(cur_entity) {
let cx = clip.x.max(shifted_rect.x);
let cy = clip.y.max(shifted_rect.y);
let cx2 = (clip.x + clip.w as i32).min(shifted_rect.x + shifted_rect.w as i32);
let cy2 = (clip.y + clip.h as i32).min(shifted_rect.y + shifted_rect.h as i32);
(
Rect {
x: cx,
y: cy,
w: (cx2 - cx).max(0) as u16,
h: (cy2 - cy).max(0) as u16,
},
offset_x
+ scroll.x
* world
.resource::<crate::backend::DisplayInfo>()
.map(|d| d.scale as i32)
.unwrap_or(1),
offset_y
+ scroll.y
* world
.resource::<crate::backend::DisplayInfo>()
.map(|d| d.scale as i32)
.unwrap_or(1),
)
} else {
(*clip, offset_x, offset_y)
};
for child in &node.children {
draw_tree_offset(child, world, entities, idx, renderer, &child_clip, sx, sy);
}
}
fn scale_rects(node: &mut LayoutNode, scale: u16) {
let s = scale as i32;
node.rect.x *= s;
node.rect.y *= s;
node.rect.w *= scale;
node.rect.h *= scale;
for child in &mut node.children {
scale_rects(child, scale);
}
}
fn collect_entities_preorder(world: &World, entity: Entity, out: &mut Vec<Entity>) {
out.push(entity);
if let Some(children) = world.get::<Children>(entity) {
let child_ids: Vec<Entity> = children.0.clone();
for child in child_ids {
collect_entities_preorder(world, child, out);
}
}
}
pub fn render(
world: &World,
root: Entity,
screen_w: u16,
screen_h: u16,
scale: u16,
renderer: &mut dyn Renderer,
) {
let scale = if scale == 0 { 1 } else { scale };
let logical_w = screen_w / scale;
let logical_h = screen_h / scale;
let Some(mut layout_tree) = build_layout_tree(world, root) else {
return;
};
compute_layout(&mut layout_tree, 0, 0, logical_w, logical_h);
scale_rects(&mut layout_tree, scale);
let clip = Rect {
x: 0,
y: 0,
w: screen_w,
h: screen_h,
};
let mut entities = Vec::new();
collect_entities_preorder(world, root, &mut entities);
let mut idx = 0;
draw_tree(&layout_tree, world, &entities, &mut idx, renderer, &clip);
}
pub fn update_layout(world: &mut World, root: Entity, screen_w: u16, screen_h: u16, scale: u16) {
let scale = if scale == 0 { 1 } else { scale };
let logical_w = screen_w / scale;
let logical_h = screen_h / scale;
let Some(mut layout_tree) = build_layout_tree(world, root) else {
return;
};
compute_layout(&mut layout_tree, 0, 0, logical_w, logical_h);
let mut entities = Vec::new();
collect_entities_preorder(world, root, &mut entities);
let mut idx = 0;
write_computed_rects(&layout_tree, world, &entities, &mut idx);
}
fn write_computed_rects(
node: &LayoutNode,
world: &mut World,
entities: &[Entity],
idx: &mut usize,
) {
if *idx < entities.len() {
world.insert(entities[*idx], super::ComputedRect(node.rect));
}
*idx += 1;
for child in &node.children {
write_computed_rects(child, world, entities, idx);
}
}
pub fn render_region(
world: &World,
root: Entity,
screen_w: u16,
screen_h: u16,
scale: u16,
dirty_rect: &Rect,
renderer: &mut dyn Renderer,
) {
let scale = if scale == 0 { 1 } else { scale };
let logical_w = screen_w / scale;
let logical_h = screen_h / scale;
let Some(mut layout_tree) = build_layout_tree(world, root) else {
return;
};
compute_layout(&mut layout_tree, 0, 0, logical_w, logical_h);
scale_rects(&mut layout_tree, scale);
let mut entities = Vec::new();
collect_entities_preorder(world, root, &mut entities);
let mut idx = 0;
draw_tree(
&layout_tree,
world,
&entities,
&mut idx,
renderer,
dirty_rect,
);
}
pub fn collect_dirty_region(
world: &mut World,
root: Entity,
screen_w: u16,
screen_h: u16,
scale: u16,
) -> Option<Rect> {
use super::dirty::Dirty;
let scale = if scale == 0 { 1 } else { scale };
let logical_w = screen_w / scale;
let logical_h = screen_h / scale;
let mut layout_tree = build_layout_tree(world, root)?;
compute_layout(&mut layout_tree, 0, 0, logical_w, logical_h);
scale_rects(&mut layout_tree, scale);
let mut entities = Vec::new();
collect_entities_preorder(world, root, &mut entities);
let mut min_x = screen_w as i32;
let mut min_y = screen_h as i32;
let mut max_x: i32 = -1;
let mut max_y: i32 = -1;
for (i, &entity) in entities.iter().enumerate() {
if world.get::<Dirty>(entity).is_some() {
if let Some(rect) = find_rect_at_index(&layout_tree, i, &mut 0) {
if rect.x < min_x {
min_x = rect.x;
}
if rect.y < min_y {
min_y = rect.y;
}
let rx = rect.x + rect.w as i32;
let ry = rect.y + rect.h as i32;
if rx > max_x {
max_x = rx;
}
if ry > max_y {
max_y = ry;
}
}
if let Some(prev) = world.remove::<super::dirty::PrevRect>(entity) {
let pr = prev.0;
let s = scale as i32;
let px = pr.x * s;
let py = pr.y * s;
let pw = pr.w as i32 * s;
let ph = pr.h as i32 * s;
if px < min_x {
min_x = px;
}
if py < min_y {
min_y = py;
}
if px + pw > max_x {
max_x = px + pw;
}
if py + ph > max_y {
max_y = py + ph;
}
}
world.remove::<Dirty>(entity);
}
}
if max_x < 0 {
None
} else {
Some(Rect {
x: min_x,
y: min_y,
w: (max_x - min_x) as u16,
h: (max_y - min_y) as u16,
})
}
}
fn find_rect_at_index(node: &LayoutNode, target: usize, idx: &mut usize) -> Option<Rect> {
if *idx == target {
return Some(node.rect);
}
*idx += 1;
for child in &node.children {
if let Some(r) = find_rect_at_index(child, target, idx) {
return Some(r);
}
}
None
}