use std::ops::Range; use crate::{ layout::{ Active, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets, }, render::{Primitive, PrimitiveHandle, Primitives}, util::{HashMap, HashSet, Id, IdUtil}, }; pub struct Painter<'a, 'c> { ctx: &'a mut PainterCtx<'c>, region: UiRegion, textures: Vec, primitives: Vec, children: Vec, sized_children: HashMap, id: Id, } pub struct PainterCtx<'a> { pub widgets: &'a Widgets, pub active: &'a mut Active, pub primitives: &'a mut Primitives, pub textures: &'a mut Textures, pub text: &'a mut TextData, pub screen_size: Vec2, drawing: HashSet, } pub struct WidgetInstance { pub id: Id, pub region: UiRegion, pub parent: Option, pub textures: Vec, pub primitives: Vec, pub children: Vec, pub resize: Option<(Id, UiVec2)>, pub span: Range, } impl<'a> PainterCtx<'a> { pub fn new( widgets: &'a Widgets, primitives: &'a mut Primitives, active: &'a mut Active, textures: &'a mut Textures, text: &'a mut TextData, screen_size: Vec2, ) -> Self { Self { widgets, active, primitives, textures, text, screen_size, drawing: HashSet::new(), } } pub fn redraw(&mut self, id: &Id) { self.drawing.clear(); let Some(active) = self.active.widgets.get(id) else { return; }; if let Some((rid, size)) = &active.resize { self.redraw(&rid.duplicate()); if self.drawing.contains(id) { return; } } let Some(active) = self.remove(id) else { return; }; self.primitives.set_pos(active.span.start); self.draw_inner( id, active.region, active.parent.duplicate(), Some(active.children), ); self.active.widgets.get_mut(id).unwrap().resize = active.resize; let delta = self.primitives.apply(active.span.clone()); if delta != 0 && let Some(parent) = active.parent { self.shift_parent(id, parent, active.span.start, delta); } } pub fn draw(&mut self, id: &Id) { self.drawing.clear(); self.primitives.clear(); self.draw_inner(id, UiRegion::full(), None, None); self.primitives.replace(); } fn draw_inner( &mut self, id: &Id, region: UiRegion, parent: Option, old_children: Option>, ) { // I have no idea if these checks work lol // the idea is u can't redraw stuff u already drew, // and if parent is different then there's another copy with a different parent // but this has a very weird issue where you can't move widgets unless u remove first // so swapping is impossible rn I think? // there's definitely better solutions like a counter (>1 = panic) but don't care rn if self.drawing.contains(id) { panic!("Cannot draw the same widget twice (1)"); } let mut old_children = old_children.unwrap_or_default(); let mut resize = None; if let Some(active) = self.active.widgets.get_mut(id) { if active.parent != parent { panic!("Cannot draw the same widget twice (2)"); } if active.region == region { self.primitives.skip(&mut active.span); return; } // TODO: // else if active.region.size() == region.size() { move } let active = self.remove(id).unwrap(); old_children = active.children; resize = active.resize; } let mut painter = Painter { region, id: id.duplicate(), textures: Vec::new(), primitives: Vec::new(), ctx: self, children: Vec::new(), sized_children: Default::default(), }; // draw widgets let start_i = painter.ctx.primitives.cur_pos(); painter.ctx.widgets.get_dyn_dynamic(id).draw(&mut painter); let end_i = painter.ctx.primitives.cur_pos(); let sized_children = painter.sized_children; // add to active let instance = WidgetInstance { id: id.duplicate(), region, parent, textures: painter.textures, span: start_i..end_i, primitives: painter.primitives, children: painter.children, resize, }; for (cid, size) in sized_children { if let Some(w) = self.active.widgets.get_mut(&cid) { w.resize = Some((id.duplicate(), size)) } } for c in &old_children { if !instance.children.contains(c) { self.remove_rec(c); } } self.active.add(id, instance, self.widgets); } /// NOTE: instance textures are cleared and self.textures freed fn remove(&mut self, id: &Id) -> Option { let mut inst = self.active.remove(id); if let Some(inst) = &mut inst { for h in &inst.primitives { self.primitives.free(h); } inst.textures.clear(); self.textures.free(); } inst } fn remove_rec(&mut self, id: &Id) -> Option { let inst = self.remove(id); if let Some(inst) = &inst { for c in &inst.children { self.remove_rec(c); } } inst } /// shifts the primitive spans for all widgets above this one in the tree /// also goes into children of them and modifies those that come after this one /// should be done after applying primitives to ensure active spans are correct fn shift_parent(&mut self, original: &Id, parent: Id, start: usize, delta: isize) { let instance = self.active.widgets.get_mut(&parent).unwrap(); let end = &mut instance.span.end; *end = end.strict_add_signed(delta); let parent_parent = instance.parent.duplicate(); for child in instance .children .iter() // skip original .skip_while(|id| *id != original) .skip(1) .map(|id| id.duplicate()) .collect::>() { self.shift_child(&child, start, delta); } if let Some(parent_parent) = parent_parent { self.shift_parent(&parent, parent_parent, start, delta); } } fn shift_child(&mut self, child: &Id, start: usize, delta: isize) { let instance = self.active.widgets.get_mut(child).unwrap(); // = also prevents the original id from getting shifted if instance.span.start < start { return; } instance.span.start = instance.span.start.strict_add_signed(delta); instance.span.end = instance.span.end.strict_add_signed(delta); for child in instance .children .iter() .map(|id| id.duplicate()) .collect::>() { self.shift_child(&child, start, delta); } } } impl<'a, 'c> Painter<'a, 'c> { fn primitive_at(&mut self, primitive: P, region: UiRegion) { let h = self .ctx .primitives .write(self.id.duplicate(), primitive, region); self.primitives.push(h); } /// Writes a primitive to be rendered pub fn primitive(&mut self, primitive: P) { self.primitive_at(primitive, self.region) } pub fn primitive_within(&mut self, primitive: P, region: UiRegion) { self.primitive_at(primitive, region.within(&self.region)); } /// Draws a widget within this widget's region. pub fn widget(&mut self, id: &WidgetId) { self.widget_at(id, self.region); } /// Draws a widget somewhere within this one. /// Useful for drawing child widgets in select areas. pub fn widget_within(&mut self, id: &WidgetId, region: UiRegion) { self.widget_at(id, region.within(&self.region)); } fn widget_at(&mut self, id: &WidgetId, region: UiRegion) { self.children.push(id.id.duplicate()); self.ctx .draw_inner(&id.id, region, Some(self.id.duplicate()), None); } pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) { self.textures.push(handle.clone()); self.primitive_at(handle.primitive(), region.within(&self.region)); } pub fn texture(&mut self, handle: &TextureHandle) { self.textures.push(handle.clone()); self.primitive(handle.primitive()); } pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) { self.textures.push(handle.clone()); self.primitive_at(handle.primitive(), region); } /// returns (handle, offset from top left) pub fn render_text( &mut self, buffer: &mut TextBuffer, attrs: &TextAttrs, ) -> (TextureHandle, TextOffset) { self.ctx.text.draw(buffer, attrs, self.ctx.textures) } pub fn region(&self) -> UiRegion { self.region } pub fn size(&mut self, id: &WidgetId) -> UiVec2 { self.size_ctx().size(id) } pub fn size_ctx(&mut self) -> SizeCtx<'_> { SizeCtx { text: self.ctx.text, textures: self.ctx.textures, widgets: self.ctx.widgets, checked: &mut self.sized_children, } } pub fn text_data(&mut self) -> &mut TextData { self.ctx.text } } pub struct SizeCtx<'a> { pub text: &'a mut TextData, pub textures: &'a mut Textures, widgets: &'a Widgets, checked: &'a mut HashMap, } impl SizeCtx<'_> { pub fn size(&mut self, id: &WidgetId) -> UiVec2 { // TODO: determine if this is useful // if let Some(size) = self.checked.get(&id.id) { // return Some(*size); // } let size = self.widgets.get_dyn_dynamic(&id.id).desired_size(self); self.checked.insert(id.id.duplicate(), size); size } pub fn draw_text( &mut self, buffer: &mut TextBuffer, attrs: &TextAttrs, ) -> (TextureHandle, TextOffset) { self.text.draw(buffer, attrs, self.textures) } }