The update loop zips all three and asserts up front that no list is left without a pipeline, instead of indexing the pipelines to get that guarantee. The comment on the data layout claimed a `None` minimum takes its value from the first pipeline built against the layout. That is not documented and does not reproduce -- one shared layout with `None` renders the tabs example correctly today. What is documented is that a stated size is checked when the bind group and pipeline are created, and `None` is checked on every draw, so that is what the comment says now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
200 lines
6.1 KiB
Rust
200 lines
6.1 KiB
Rust
use crate::{
|
|
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
|
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
|
render::{
|
|
GlyphPrimitive, Mask, MaskIdx, 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,
|
|
|
|
pub(super) region: UiRegion,
|
|
pub(super) mask: MaskIdx,
|
|
pub(super) textures: Vec<TextureHandle>,
|
|
pub(super) primitives: Vec<PrimitiveHandle>,
|
|
pub(super) children: Vec<WidgetId>,
|
|
pub layer: usize,
|
|
pub(super) id: WidgetId,
|
|
}
|
|
|
|
impl<'a> Painter<'a> {
|
|
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
|
let kind = self.rsc.ui_mut().primitives.kind::<P>();
|
|
self.write(kind, primitive, region);
|
|
}
|
|
|
|
/// Takes the kind, for a caller writing many of one primitive.
|
|
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
|
let h = self.state.layers.write(
|
|
self.layer,
|
|
PrimitiveInst {
|
|
kind,
|
|
id: self.id,
|
|
primitive,
|
|
region,
|
|
mask_idx: self.mask,
|
|
},
|
|
);
|
|
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 });
|
|
}
|
|
|
|
/// Draws a widget within this widget's region.
|
|
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
|
|
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<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
|
self.widget_at(id, region.within(&self.region));
|
|
}
|
|
|
|
fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
|
self.children.push(id.id());
|
|
self.state.draw_inner(
|
|
self.layer,
|
|
id.id(),
|
|
region,
|
|
Some(self.id),
|
|
self.mask,
|
|
None,
|
|
self.rsc,
|
|
);
|
|
}
|
|
|
|
pub fn render_text(
|
|
&mut self,
|
|
buffer: &mut TextBuffer,
|
|
attrs: &TextAttrs,
|
|
width: Option<f32>,
|
|
) -> RenderedText {
|
|
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::<GlyphPrimitive>();
|
|
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::abs(glyph.offset));
|
|
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
|
region.y.end = region.y.start + UiScalar::abs(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,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn region(&self) -> UiRegion {
|
|
self.region
|
|
}
|
|
|
|
pub fn size<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>) -> Size {
|
|
self.size_ctx().size(id)
|
|
}
|
|
|
|
pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Len {
|
|
match axis {
|
|
Axis::X => self.size_ctx().width(id),
|
|
Axis::Y => self.size_ctx().height(id),
|
|
}
|
|
}
|
|
|
|
pub fn output_size(&self) -> Vec2 {
|
|
self.state.output_size
|
|
}
|
|
|
|
pub fn px_size(&mut self) -> Vec2 {
|
|
self.region.size().to_abs(self.state.output_size)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
|
|
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
|
}
|
|
}
|
|
|
|
/// 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<P: Primitive> 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()
|
|
}
|
|
}
|