#[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter}; use crate::{ Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId, render::{ GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind, TexturePrimitive, }, util::Vec2, }; /// 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 /// about is the one recorded as its offer. pub(super) offered: Vec, /// The children whose size this widget read while drawing. pub(super) size_deps: Vec, /// Offered pixel axes which can affect the size this draw reports. pub(super) size_box_inputs: [bool; 2], pub(super) size_output_inputs: [bool; 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, self.region, 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> { let region = region.within(&self.region); self.widget_at(id, region, false) } /// 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); let region = region.within(&self.region); #[cfg(feature = "layout-diagnostics")] diag::placed(id.id(), self.id, region); self.widget_at(id, region, true) } fn widget_at<'s, W: ?Sized>( &'s mut self, id: &'s StrongWidget, region: UiRegion, slotted: bool, ) -> DrawResult<'s, 'a, W> { // A child listed twice would be moved twice. if !self.children.contains(&id.id()) { self.children.push(id.id()); } let size = self.state.draw_inner( self.layer, id.id(), region, Some(self.id), self.depth + 1, self.move_idx, slotted, self.mask, None, self.rsc, ); self.offer(id.id(), region); 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_hint(id); Some(hint) } None => { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::HintMisses); None } } } /// A retained child length valid under the region it is about to be /// offered. Unlike a hint, this is contextual: it is kept only when none /// of the offered pixel axes which produced it changed. pub fn known_len( &mut self, child: &StrongWidget, axis: Axis, region: UiRegion, ) -> Option { let region = region.within(&self.region); self.offer(child.id(), region); if let Some(hint) = self.size_hint(child, axis) { return Some(hint); } self.retained_size(child, region) .map(|size| size.axis(axis)) } /// `region` in this widget's own coordinates. fn retained_size( &mut self, child: &StrongWidget, region: UiRegion, ) -> Option { let (size, box_inputs, output_inputs) = self.state .retained_size(child.id(), region, self.move_idx, self.rsc.widgets())?; #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::RetainedSizeHits); self.depend_on_size_inputs(child, box_inputs, output_inputs); Some(size) } /// Records the box a child was first asked about in this draw. Any later /// box this draw gives it was decided knowing its answer, so a size the /// child measures there is not an answer to this widget's question. fn offer(&mut self, child: WidgetId, region: UiRegion) { if self.offered.contains(&child) { return; } self.offered.push(child); let px = self.state.px_of(self.move_idx, region); if let Some(active) = self.state.active.get_mut(&child) { active.offered_px = px; } } /// Depends on a length the child gave without being drawn. A hint is /// context-free, so this depends on the child but on no pixel axis. fn depend_on_hint(&mut self, child: &StrongWidget) { self.depend_on_size_inputs(child, [false; 2], [false; 2]); } /// Depends on a size the child produced by drawing, which carries /// whatever the child read to produce it. fn depend_on_drawn_size(&mut self, child: &StrongWidget) { let (box_inputs, output_inputs) = self .state .active .get(&child.id()) .map_or(([false; 2], [false; 2]), |active| { (active.size_box_inputs, active.size_output_inputs) }); self.depend_on_size_inputs(child, box_inputs, output_inputs); } fn depend_on_size_inputs( &mut self, child: &StrongWidget, box_inputs: [bool; 2], output_inputs: [bool; 2], ) { if !self.size_deps.contains(&child.id()) { self.size_deps.push(child.id()); } for (own, child) in self.size_box_inputs.iter_mut().zip(box_inputs) { *own |= child; } for (own, child) in self.size_output_inputs.iter_mut().zip(output_inputs) { *own |= child; } } 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 } /// The output's size in pixels. A widget that reads it draws again when /// the output changes, since nothing else can put that right. pub fn output_size(&mut self) -> Vec2 { self.size_output_inputs = [true; 2]; self.state.output_size } /// One axis of the output in pixels. Prefer this to [`Self::output_size`] /// when the other axis cannot affect the size this widget reports. pub fn output_len(&mut self, axis: Axis) -> f32 { self.size_output_inputs[axis as usize] = true; self.state.output_size.axis(axis) } /// This widget's box in pixels. Resolved against the output's size and /// the boxes it sits within, so a widget that reads it draws again when /// the output changes. pub fn px_size(&mut self) -> Vec2 { self.size_box_inputs = [true; 2]; let region = self.state.moves.resolve(self.move_idx, self.region); region.size().to_px(self.state.output_size) } /// One axis of this widget's box in pixels. Prefer this to /// [`Self::px_size`] when the other axis cannot affect the reported size. pub fn px_len(&mut self, axis: Axis) -> f32 { self.size_box_inputs[axis as usize] = true; self.px_len_for_draw(axis) } /// One axis of this widget's box in pixels, for a draw whose reported /// size does not follow from it -- a clamp or a position. Nothing records /// the read, so a size that does depend on it would go stale. pub fn px_len_for_draw(&self, axis: Axis) -> f32 { let region = self.state.moves.resolve(self.move_idx, self.region); region .size() .axis(axis) .to_px(self.state.output_size.axis(axis)) } 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_drawn_size(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() } }