#[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter}; use crate::{ Axis, Holds, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId, render::{ GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind, TexturePrimitive, }, ui::render_state::DrawInfo, util::Vec2, }; use std::ops::RangeInclusive; const AXES: [Axis; 2] = [Axis::X, Axis::Y]; /// makes your surfaces look pretty pub struct Painter<'a> { pub(super) state: &'a mut UiRenderState, pub(super) rsc: &'a mut dyn UiRsc, /// This widget's box, in the coordinates of `move_idx`. pub(super) region: UiRegion, pub(super) mask: MaskIdx, pub(super) textures: Vec, pub(super) primitives: Vec, pub(super) children: Vec, /// The children asked about so far, so the first box each was asked in /// is the one recorded as its offer. pub(super) offered: Vec, /// The box this widget was first asked about in, in pixels. pub(super) offered_px: Vec2, /// Whether this draw is in that box, which makes the questions it asks /// the ones a cold layout asks and their answers the ones to keep. pub(super) at_offer: bool, /// The children whose size this widget read while drawing. pub(super) size_deps: Vec, /// What this draw itself read of its box in pixels, per axis: every /// length until it reads one, then that one, unless it says otherwise. pub(super) own: [Holds; 2], /// What the children it asked about and drew keep it to. pub(super) under: [Holds; 2], /// The slot this widget's primitives are positioned through: its own if /// its parent placed it, otherwise the nearest ancestor that has one. pub(super) move_idx: MoveIdx, pub layer: usize, pub(super) depth: usize, pub(super) id: WidgetId, } impl<'a> Painter<'a> { fn primitive_at(&mut self, primitive: P, region: UiRegion) { let kind = self.rsc.ui_mut().primitives.kind::

(); self.write(kind, primitive, region); } /// Takes the kind, for a caller writing many of one primitive. fn write(&mut self, kind: PrimitiveKind

, primitive: P, region: UiRegion) { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::PrimitiveWrites); let h = self.state.layers.write( self.layer, PrimitiveInst { kind, id: self.id, primitive, region, mask_idx: self.mask, move_idx: self.move_idx, }, ); self.push_primitive(h); } fn push_primitive(&mut self, h: PrimitiveHandle) { if self.mask != MaskIdx::NONE { // TODO: I have no clue if this works at all :joy: self.rsc.ui_mut().masks.push_ref(self.mask); } self.primitives.push(h); } /// Writes a primitive to be rendered pub fn primitive(&mut self, primitive: impl PrimitiveLike) { let primitive = primitive.into_primitive(self); self.primitive_at(primitive, self.region) } pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) { let primitive = primitive.into_primitive(self); self.primitive_at(primitive, region.within(&self.region)); } pub fn set_mask(&mut self, region: UiRegion) { assert!(self.mask == MaskIdx::NONE); self.mask = self.rsc.ui_mut().masks.push(Mask { region, move_idx: self.move_idx, }); } /// Draws a widget within this widget's region. pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget) -> DrawResult<'s, 'a, W> { self.widget_at(id, UiRegion::FULL, false) } /// Draws a widget somewhere within this one. pub fn widget_within<'s, W: ?Sized>( &'s mut self, id: &'s StrongWidget, region: UiRegion, ) -> DrawResult<'s, 'a, W> { self.widget_at(id, region, false) } /// What a widget declares its lengths to be, which whoever draws it /// resolves into its box. `leftover` is not among them: a part of what is /// left over is only a length to the widget dividing one, so it passes /// up in the size instead. Reading it depends on nothing -- the box that /// comes of it is kept on the child, and `redraw` compares it there. fn declared_lens(&self, id: &StrongWidget) -> [Option; 2] { let Some(widget) = self.rsc.widgets().get_dyn(id.id()) else { return [None; 2]; }; AXES.map(|axis| declared_len(widget, axis)) } /// Draws a child this widget decides the box of, and may decide again /// once it knows what the child came to. The child gets a slot of its /// own, so placing it a second time writes one entry however much it /// drew -- moved or resized alike, since everything under the slot is /// held as a fraction of its box. A child drawn any other way has no slot /// and can only be given a different box by drawing again. pub fn place<'s, W: ?Sized>( &'s mut self, id: &'s StrongWidget, region: UiRegion, ) -> DrawResult<'s, 'a, W> { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::PlaceCalls); self.widget_at(id, region, true) } /// Takes back a child that was drawn only to find out how long it is. /// Its drawing is dropped and it is not one of this widget's children /// this frame; what it answered is still something this widget asked. pub fn undraw(&mut self, id: &StrongWidget) { self.children.retain(|child| *child != id.id()); self.state.undraw_rec(id.id(), self.rsc); } /// `region` in this widget's own coordinates, and with the child's /// declared lengths still to be taken. fn widget_at<'s, W: ?Sized>( &'s mut self, id: &'s StrongWidget, region: UiRegion, slotted: bool, ) -> DrawResult<'s, 'a, W> { let declared = self.declared_lens(id); // Composing `FULL` through a box is not quite the identity in f32, // so a child with nothing declared keeps the box it would have had. let local = match declared.iter().any(Option::is_some) { true => declared_box(region, declared), false => region, }; let within = match local == UiRegion::FULL { true => self.region, false => local.within(&self.region), }; #[cfg(feature = "layout-diagnostics")] if slotted { diag::placed(id.id(), self.id, within); } // A child listed twice would be moved twice. if !self.children.contains(&id.id()) { self.children.push(id.id()); } let first_ask = self.offer(id.id()); let offer = match first_ask { true => local, false => self.state.active.get(&id.id()).map_or(local, |a| a.offer), }; let answers_offer = self.at_offer && local == offer; let size = self.state.draw_inner( id.id(), within, DrawInfo { layer: self.layer, parent: Some(self.id), depth: self.depth + 1, parent_move: self.move_idx, slotted, mask: self.mask, offer, offered_px: self.px_within_offer(offer), }, None, self.rsc, ); let active = self.state.active.get_mut(&id.id()).unwrap(); active.declared = declared; if answers_offer { active.answer = (active.size, active.holds); } // Whatever the child's drawing holds for keeps this one to the boxes // that give the child a length inside it. for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) { *under = under.and(active.holds[axis as usize].through(local.axis(axis).len())); } DrawResult { child: id, painter: self, size, } } /// What a child says its length is without being drawn, if it can say. /// Asking counts as reading its size. pub fn size_hint(&mut self, id: &StrongWidget, axis: Axis) -> Option { let hint = self .rsc .widgets() .get_dyn(id.id()) .and_then(|widget| widget.size_hint(axis)); #[cfg(feature = "layout-diagnostics")] diag::hint_read(id.id(), self.id, axis, hint); match hint { Some(hint) => { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::HintHits); self.depend_on(id); Some(hint) } None => { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::HintMisses); None } } } /// A child's length in the box it is about to be offered, if it can be /// had without drawing it: from its hint, or from a drawing it already /// has that holds for that box. pub fn known_len( &mut self, child: &StrongWidget, axis: Axis, region: UiRegion, ) -> Option { let declared = self.declared_lens(child); let local = declared_box(region, declared); let within = local.within(&self.region); let first_ask = self.offer(child.id()); if first_ask && let Some(active) = self.state.active.get_mut(&child.id()) { active.offer = local; } if let Some(hint) = self.size_hint(child, axis) { return Some(hint); } let px = self.state.px_of(self.move_idx, within); let (size, holds) = self.state .retained_size(child.id(), px, self.move_idx, self.rsc.widgets())?; #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::RetainedSizeHits); self.depend_on(child); if first_ask { let active = self.state.active.get_mut(&child.id()).unwrap(); active.answer = (size, holds); } for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) { *under = under.and(holds[axis as usize].through(local.axis(axis).len())); } Some(size.axis(axis)) } /// Whether this is the first box a child is asked about in during a draw /// that is itself in the box it was asked in -- the question a cold /// layout asks, whose answer is the one to keep. fn offer(&mut self, child: WidgetId) -> bool { if !self.at_offer || self.offered.contains(&child) { return false; } self.offered.push(child); true } /// The pixel size of a part of the box this widget was asked in. fn px_within_offer(&self, local: UiRegion) -> Vec2 { let size = local.size(); Vec2::new( size.x.to_px(self.offered_px.x), size.y.to_px(self.offered_px.y), ) } fn depend_on(&mut self, child: &StrongWidget) { if !self.size_deps.contains(&child.id()) { self.size_deps.push(child.id()); } } pub fn render_text<'b>( &mut self, buffer: &'b mut TextBuffer, attrs: &TextAttrs, width: Option, ) -> &'b RenderedText { #[cfg(feature = "layout-diagnostics")] diag::render_text(self.id, self.rsc.widgets().label(self.id), width); let ui = self.rsc.ui_mut(); ui.text.render(buffer, attrs, width) } // TODO: merge the text methods into the primitive ones. pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) { let kind = self.rsc.ui_mut().primitives.kind::(); for glyph in text.glyphs.iter() { let mut region = origin; region.x.end = region.x.start; region.y.end = region.y.start; let mut region = region.offset(UiVec2::px(glyph.offset)); region.x.end = region.x.start + UiScalar::px(glyph.entry.width as f32); region.y.end = region.y.start + UiScalar::px(glyph.entry.height as f32); self.write( kind, GlyphPrimitive { uv_min: glyph.entry.uv_min, uv_max: glyph.entry.uv_max, layer: glyph.entry.layer, color: text.color, flags: glyph.entry.flags(), }, region, ); } } /// This widget's box, in the coordinates its own primitives are written /// in -- so a region composed `within` it may be drawn directly. pub fn region(&self) -> UiRegion { self.region } /// This widget's box in pixels. Reading it makes the drawing one that /// holds for this box only, until `holds` says how far it goes. pub fn px_size(&mut self) -> Vec2 { let px = self.state.px_of(self.move_idx, self.region); for (own, len) in self.own.iter_mut().zip([px.x, px.y]) { if *own == Holds::ANY { *own = Holds::at(len); } } px } /// One axis of this widget's box in pixels. Prefer this to /// [`Self::px_size`] when the other axis cannot affect the drawing. pub fn px_len(&mut self, axis: Axis) -> f32 { let len = self.state.px_of(self.move_idx, self.region).axis(axis); let own = &mut self.own[axis as usize]; if *own == Holds::ANY { *own = Holds::at(len); } len } /// The lengths of this widget's box on `axis` that what it is drawing /// holds for -- the same primitives, in the same fractions and offsets /// of the box, and the same reported size. A widget that read its /// length in pixels holds for that one alone until it says otherwise. pub fn holds(&mut self, axis: Axis, range: RangeInclusive) { let holds = Holds::from(range); debug_assert!( holds.contains(self.state.px_of(self.move_idx, self.region).axis(axis)), "'{}' ({:?}) says its drawing holds for lengths that leave out its own box", self.label(), self.id ); self.own[axis as usize] = holds; } pub fn text_data(&mut self) -> &mut TextData { &mut self.rsc.ui_mut().text } pub fn child_layer(&mut self) { self.layer = self.state.layers.child(self.layer); } pub fn next_layer(&mut self) { self.layer = self.state.layers.next(self.layer); } pub fn label(&self) -> &str { &self.rsc.widgets().data(self.id).unwrap().label } pub fn id(&self) -> &WidgetId { &self.id } } /// A child that has just been drawn. Reading its size records that this /// widget's own size depends on it; dropping it without reading draws the /// child and leaves the parent independent of what it came to. pub struct DrawResult<'p, 'a, W: ?Sized> { painter: &'p mut Painter<'a>, child: &'p StrongWidget, size: Size, } impl DrawResult<'_, '_, W> { pub fn size(self) -> Size { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::SizeReads); diag::size_read(self.child.id(), self.painter.id, self.size); } self.painter.depend_on(self.child); self.size } pub fn len(self, axis: Axis) -> Len { self.size().axis(axis) } } /// What `Painter::primitive` takes: a primitive, or something that yields one /// and does whatever else drawing it needs. pub trait PrimitiveLike { type Primitive: Primitive; fn into_primitive(self, painter: &mut Painter) -> Self::Primitive; } impl PrimitiveLike for P { type Primitive = P; fn into_primitive(self, _: &mut Painter) -> P { self } } impl PrimitiveLike for &TextureHandle { type Primitive = TexturePrimitive; /// Retains a share of the handle, so the slot the primitive names cannot /// be freed and reused while it is still drawn. fn into_primitive(self, painter: &mut Painter) -> TexturePrimitive { painter.textures.push(self.clone()); self.into() } } /// What a widget declares a length of its box to be. `leftover` is not one: a /// share of what is left over is only a length to the widget dividing one, /// so it passes up in the size instead. pub(crate) fn declared_len(widget: &dyn Widget, axis: Axis) -> Option { widget.size_hint(axis).filter(|len| len.leftover == 0.0) } /// Takes a widget's declared lengths in the box `region` is given in, since a /// fraction of a length means a fraction of that one. A caller that already /// reserved the space hands back the same length, so this is the identity /// for it. fn declared_box(mut region: UiRegion, declared: [Option; 2]) -> UiRegion { for (axis, len) in AXES.into_iter().zip(declared) { let Some(len) = len else { continue }; let span = region.axis_mut(axis); span.end = span.start + UiScalar::new(len.rel, len.px); } region }