use crate::ui::{Ui, DrawCommand, RectDraw, OverlayRectDraw};
use crate::container::{Direction, Wrap};
use zenthra_core::{Id, Color};
use zenthra_render::RectInstance;
use zenthra_platform::event::PlatformEvent;
pub struct LazyContainerBuilder<'u, 'a> {
ui: &'u mut Ui<'a>,
item_width: f32,
item_height: f32,
count: usize,
gap: f32,
padding: f32,
bg: Option<Color>,
radius: f32,
direction: Direction,
wrap_strategy: Wrap,
id: Option<Id>,
}
impl<'u, 'a> LazyContainerBuilder<'u, 'a> {
pub fn new(ui: &'u mut Ui<'a>) -> Self {
Self {
ui,
item_width: 100.0,
item_height: 100.0,
count: 0,
gap: 15.0,
padding: 0.0,
bg: None,
radius: 0.0,
direction: Direction::Column,
wrap_strategy: Wrap::NoWrap,
id: None,
}
}
pub fn item_size(mut self, w: f32, h: f32) -> Self {
self.item_width = w;
self.item_height = h;
self
}
pub fn count(mut self, count: usize) -> Self {
self.count = count;
self
}
pub fn gap(mut self, gap: f32) -> Self {
self.gap = gap;
self
}
pub fn padding(mut self, t: f32, _r: f32, _b: f32, _l: f32) -> Self {
self.padding = t; self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn radius(mut self, tl: f32, _tr: f32, _br: f32, _bl: f32) -> Self {
self.radius = tl; self
}
pub fn row(mut self) -> Self {
self.direction = Direction::Row;
self
}
pub fn column(mut self) -> Self {
self.direction = Direction::Column;
self
}
pub fn wrap(mut self, strategy: Wrap) -> Self {
self.wrap_strategy = strategy;
self
}
pub fn id(mut self, id: impl std::hash::Hash) -> Self {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
id.hash(&mut hasher);
self.id = Some(Id::from_u64(hasher.finish()));
self
}
pub fn show<F>(self, mut f: F)
where
F: FnMut(&mut Ui, usize),
{
use std::hash::{Hash, Hasher};
let id = self.id.unwrap_or_else(|| {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
"lazy_container".hash(&mut hasher);
self.ui.id_counter.hash(&mut hasher);
Id::from_u64(hasher.finish())
});
let ui = self.ui;
let ox = ui.cursor_x;
let oy = ui.cursor_y;
let viewport_w = ui.available_width;
let viewport_h = (ui.current_viewport.origin.y + ui.current_viewport.size.height - oy).max(0.0);
let usable_w = viewport_w - self.padding * 2.0;
let items_per_row = match (self.direction, self.wrap_strategy) {
(Direction::Row, Wrap::Wrap) | (Direction::Row, Wrap::WrapReverse) => {
let mut count = 0usize;
let mut current_w = 0.0f32;
for _ in 0..self.count.min(1000) {
let needed = if count == 0 {
self.item_width
} else {
current_w + self.gap + self.item_width
};
if needed > usable_w && count > 0 {
break;
}
current_w = needed;
count += 1;
}
count.max(1)
}
(Direction::Row, _) => self.count.max(1), (Direction::Column, _) => 1, (Direction::Stack, _) => 1,
};
let row_count = (self.count as f32 / items_per_row as f32).ceil() as usize;
let total_content_h = row_count as f32 * self.item_height
+ row_count.saturating_sub(1) as f32 * self.gap;
let total_scroll_h = total_content_h + self.padding * 2.0;
let (mut scroll_x, mut scroll_y) =
*ui.scroll_state.get(&id).unwrap_or(&(0.0, 0.0));
let is_hover = ui.mouse_x >= ox
&& ui.mouse_x <= ox + viewport_w
&& ui.mouse_y >= oy
&& ui.mouse_y <= oy + viewport_h;
if is_hover {
let events: Vec<_> = ui.input_events.iter().cloned().collect();
for event in &events {
if let PlatformEvent::MouseWheel { delta_y, delta_x, .. } = event {
scroll_y -= delta_y * 15.0;
scroll_x -= delta_x * 15.0;
ui.needs_redraw = true;
}
}
}
let max_sy = (total_scroll_h - viewport_h).max(0.0);
scroll_y = scroll_y.clamp(0.0, max_sy);
scroll_x = scroll_x.clamp(0.0, 0.0); ui.scroll_state.insert(id, (scroll_x, scroll_y));
let row_h = self.item_height + self.gap;
let bleed = 2usize; let start_row = (scroll_y / row_h).floor() as usize;
let start_row = start_row.saturating_sub(bleed);
let end_row = ((scroll_y + viewport_h) / row_h).ceil() as usize + bleed;
let start_idx = (start_row * items_per_row).min(self.count);
let end_idx = (end_row * items_per_row).min(self.count);
let clip = [ox, oy, viewport_w, viewport_h];
let prev_dir = ui.direction;
let prev_line_h = ui.line_height;
let prev_base_x = ui.base_x;
let prev_base_y = ui.base_y;
let prev_max_x = ui.max_x;
let prev_max_y = ui.max_y;
let prev_avail_w = ui.available_width;
let prev_offset_x = ui.offset_x;
let prev_offset_y = ui.offset_y;
let prev_child_sizes = std::mem::take(&mut ui.child_sizes);
let prev_child_ranges = std::mem::take(&mut ui.child_draw_ranges);
let prev_child_origins = std::mem::take(&mut ui.child_origins);
let prev_id_ranges = std::mem::take(&mut ui.id_ranges);
let prev_id_log = std::mem::take(&mut ui.id_log);
let parent_draws = std::mem::take(&mut ui.draws);
for i in start_idx..end_idx {
let row = i / items_per_row;
let col = i % items_per_row;
let item_x = ox + self.padding + col as f32 * (self.item_width + self.gap);
let item_y = oy + self.padding + row as f32 * row_h - scroll_y;
ui.cursor_x = item_x;
ui.cursor_y = item_y;
ui.base_x = item_x;
ui.base_y = item_y;
ui.direction = Direction::Column;
ui.line_height = 0.0;
ui.max_x = item_x + self.item_width;
ui.max_y = item_y + self.item_height;
ui.available_width = self.item_width;
let draw_start = ui.draws.len();
f(ui, i);
for draw in &mut ui.draws[draw_start..] {
apply_clip(draw, clip);
}
ui.child_sizes.clear();
ui.child_draw_ranges.clear();
ui.id_ranges.clear();
}
let item_draws = std::mem::replace(&mut ui.draws, parent_draws);
ui.direction = prev_dir;
ui.line_height = prev_line_h;
ui.base_x = prev_base_x;
ui.base_y = prev_base_y;
ui.max_x = prev_max_x;
ui.max_y = prev_max_y;
ui.available_width = prev_avail_w;
ui.offset_x = prev_offset_x;
ui.offset_y = prev_offset_y;
ui.cursor_x = ox;
ui.cursor_y = oy;
ui.child_sizes = prev_child_sizes;
ui.child_draw_ranges = prev_child_ranges;
ui.child_origins = prev_child_origins;
ui.id_ranges = prev_id_ranges;
ui.id_log = prev_id_log;
let draw_start = ui.draws.len();
if let Some(bg) = self.bg {
ui.draws.push(DrawCommand::Rect(RectDraw {
instance: RectInstance {
pos: [ox, oy],
size: [viewport_w, viewport_h],
color: bg.to_array(),
radius: [self.radius; 4],
border_width: 0.0,
border_color: Color::TRANSPARENT.to_array(),
shadow_color: Color::TRANSPARENT.to_array(),
shadow_offset:[0.0, 0.0],
shadow_blur: 0.0,
clip_rect: [-100000.0, -100000.0, 2000000.0, 2000000.0],
grayscale: 0.0,
brightness: 1.0,
opacity: 1.0,
..Default::default()
},
}));
}
for draw in item_draws {
ui.draws.push(draw);
}
if max_sy > 0.0 {
let bar_thickness = 6.0;
let bar_margin = 2.0;
let thumb_h = (viewport_h / total_scroll_h * viewport_h).max(20.0);
let scroll_ratio = scroll_y / max_sy;
let thumb_y = oy + (viewport_h - thumb_h) * scroll_ratio;
let thumb_x = ox + viewport_w - bar_thickness - bar_margin;
let is_thumb_hover = ui.mouse_in_rect(thumb_x - 2.0, thumb_y, bar_thickness + 4.0, thumb_h);
let color = if is_thumb_hover {
Color::rgba(1.0, 1.0, 1.0, 0.6)
} else {
Color::rgba(1.0, 1.0, 1.0, 0.25)
};
ui.draws.push(DrawCommand::OverlayRect(OverlayRectDraw {
x: thumb_x, y: thumb_y,
width: bar_thickness, height: thumb_h,
color,
clip,
}));
}
ui.advance(viewport_w, viewport_h, draw_start);
}
}
fn apply_clip(cmd: &mut DrawCommand, clip: [f32; 4]) {
match cmd {
DrawCommand::Rect(r) => r.instance.clip_rect = clip,
DrawCommand::Text(t) => t.clip = clip,
DrawCommand::OverlayRect(o) => o.clip = clip,
DrawCommand::Image(i) => i.instance.clip_rect = clip,
DrawCommand::BackdropBlur(b) => b.clip_rect = clip,
DrawCommand::CustomPostProcess(b) => b.clip_rect = clip,
}
}