use crate::{ layout::{ ActiveWidgets, Layers, Modules, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets, }, render::{Primitive, PrimitiveHandle}, util::{HashMap, HashSet, Id}, }; pub struct Painter<'a, 'c> { ctx: &'a mut PainterCtx<'c>, region: UiRegion, textures: Vec, primitives: Vec, children: Vec, sized_children: HashMap, /// whether this widget depends on region's final pixel size or not /// TODO: decide if point (pt) should be used here instead of px px_dependent: bool, pub layer: usize, id: Id, } pub struct PainterCtx<'a> { pub widgets: &'a Widgets, pub active: &'a mut ActiveWidgets, pub layers: &'a mut Layers, pub textures: &'a mut Textures, pub text: &'a mut TextData, pub screen_size: Vec2, pub modules: &'a mut Modules, 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 layer: usize, } impl<'a> PainterCtx<'a> { pub fn new( widgets: &'a Widgets, layers: &'a mut Layers, active: &'a mut ActiveWidgets, modules: &'a mut Modules, textures: &'a mut Textures, text: &'a mut TextData, screen_size: Vec2, ) -> Self { Self { widgets, active, layers, textures, text, screen_size, modules, drawing: HashSet::default(), } } pub fn redraw(&mut self, id: Id) { self.drawing.clear(); let Some(active) = self.active.get(&id) else { return; }; if let Some((rid, size)) = active.resize { let checked = &mut HashMap::default(); let mut ctx = SizeCtx { checked, text: self.text, textures: self.textures, widgets: self.widgets, region: UiRegion::full(), screen_size: self.screen_size, }; let desired = ctx.size_inner(id, active.region); if size != desired { self.redraw(rid); if self.drawing.contains(&id) { return; } } } let Some(active) = self.remove(id) else { return; }; self.draw_inner( active.layer, id, active.region, active.parent, Some(active.children), ); self.active.get_mut(&id).unwrap().resize = active.resize; } pub fn draw(&mut self, id: Id) { self.drawing.clear(); self.layers.clear(); self.draw_inner(0, id, UiRegion::full(), None, None); } fn draw_inner( &mut self, layer: usize, 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.get_mut(&id) { if active.parent != parent { panic!("Cannot draw the same widget twice (2)"); } if active.region == region { return; } else if active.region.size() == region.size() { // TODO: epsilon? let from = active.region; self.mov(id, from, region); return; } let active = self.remove(id).unwrap(); old_children = active.children; resize = active.resize; } self.drawing.insert(id); let mut painter = Painter { region, layer, id, textures: Vec::new(), primitives: Vec::new(), ctx: self, children: Vec::new(), sized_children: Default::default(), px_dependent: false, }; // draw widgets painter.ctx.widgets.get_dyn_dynamic(id).draw(&mut painter); let sized_children = painter.sized_children; // add to active let instance = WidgetInstance { id, region, parent, textures: painter.textures, primitives: painter.primitives, children: painter.children, resize, layer, }; for (cid, size) in sized_children { if let Some(w) = self.active.get_mut(&cid) { w.resize = Some((id, size)) } } for c in &old_children { if !instance.children.contains(c) { self.remove_rec(*c); } } for m in self.modules.iter_mut() { m.on_draw(&instance); } self.active.insert(id, instance); } fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) { let active = self.active.get_mut(&id).unwrap(); // children will not be changed, so this technically should not be needed // probably need unsafe for h in &active.primitives { let region = self.layers[h.layer].primitives.region_mut(h); *region = region.outside(&from).within(&to); } active.region = active.region.outside(&from).within(&to); for m in self.modules.iter_mut() { m.on_move(active); } let children = active.children.clone(); for child in children { self.mov(child, from, to); } } /// 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.layers.free(h); } inst.textures.clear(); self.textures.free(); for m in self.modules.iter_mut() { m.on_undraw(inst); } } 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 } } impl<'a, 'c> Painter<'a, 'c> { fn primitive_at(&mut self, primitive: P, region: UiRegion) { let h = self .ctx .layers .write(self.layer, self.id, 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); self.ctx .draw_inner(self.layer, id.id, region, Some(self.id), 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, screen_size: self.ctx.screen_size, region: self.region, } } pub fn px_size(&mut self) -> Vec2 { self.px_dependent = true; self.region.in_size(self.ctx.screen_size) } pub fn text_data(&mut self) -> &mut TextData { self.ctx.text } pub fn child_layer(&mut self) { self.layer = self.ctx.layers.child(self.layer); } pub fn next_layer(&mut self) { self.layer = self.ctx.layers.next(self.layer); } } pub struct SizeCtx<'a> { pub text: &'a mut TextData, pub textures: &'a mut Textures, widgets: &'a Widgets, checked: &'a mut HashMap, region: UiRegion, screen_size: Vec2, } impl SizeCtx<'_> { fn size_inner(&mut self, id: Id, region: UiRegion) -> UiVec2 { let self_region = self.region; self.region = region; let size = self.widgets.get_dyn_dynamic(id).desired_size(self); self.region = self_region; self.checked.insert(id, size); size } 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); // } self.size_inner(id.id, self.region) } pub fn size_within(&mut self, id: &WidgetId, region: UiRegion) -> UiVec2 { self.size_inner(id.id, region.within(&self.region)) } pub fn px_size(&self) -> Vec2 { self.region.in_size(self.screen_size) } pub fn draw_text( &mut self, buffer: &mut TextBuffer, attrs: &TextAttrs, ) -> (TextureHandle, TextOffset) { self.text.draw(buffer, attrs, self.textures) } }