512 lines
18 KiB
Rust
512 lines
18 KiB
Rust
use crate::{
|
|
Axis, Len, MoveOffset, PaintId, RegionAlign, RenderedText, Size, StrongWidget, TextHandle,
|
|
TextResources, TextureHandle, UiData, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2,
|
|
WidgetId,
|
|
render::{
|
|
Drawn, GlyphPrimitive, IMAGE_BINDING, Mask, MaskIdx, MoveIdx, NOT_DRAWN, Primitive,
|
|
PrimitiveHandle, PrimitiveInst, RectPrimitive,
|
|
},
|
|
ui::render_state::Retained,
|
|
util::Vec2,
|
|
};
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
pub struct Painter<'a> {
|
|
pub(super) render_state: &'a mut UiRenderState,
|
|
pub(super) rsc: &'a mut dyn UiRsc,
|
|
|
|
pub(super) region: UiRegion,
|
|
pub(super) mask: MaskIdx,
|
|
pub(super) move_slot: MoveIdx,
|
|
pub(super) child_move_slot: Option<MoveIdx>,
|
|
pub(super) own_mask: MaskIdx,
|
|
pub(super) textures: Vec<TextureHandle>,
|
|
pub(super) paints: Vec<PaintId>,
|
|
pub(super) primitives: Vec<PrimitiveHandle>,
|
|
pub(super) recycle: std::iter::Peekable<std::vec::IntoIter<PrimitiveHandle>>,
|
|
pub(super) children: Vec<WidgetId>,
|
|
pub(super) size_dependencies: Vec<WidgetId>,
|
|
pub(super) size: Option<Size>,
|
|
/// Whether a retained child's length on each axis is still valid. A
|
|
/// child's length may change when the parent's orthogonal extent changes
|
|
/// (most importantly, wrapped text gets taller when it gets narrower),
|
|
/// but not merely because a content-sized parent grew along that same
|
|
/// axis around one of its siblings.
|
|
pub(super) reuse_child_sizes: [bool; 2],
|
|
pub layer: usize,
|
|
pub(super) id: WidgetId,
|
|
}
|
|
|
|
pub struct DrawResult<'p, 'a> {
|
|
painter: &'p mut Painter<'a>,
|
|
child: WidgetId,
|
|
}
|
|
|
|
impl DrawResult<'_, '_> {
|
|
pub fn size(self) -> Size {
|
|
if !self.painter.size_dependencies.contains(&self.child) {
|
|
self.painter.size_dependencies.push(self.child);
|
|
}
|
|
self.painter.render_state.active[&self.child].size
|
|
}
|
|
}
|
|
|
|
impl<'a> Painter<'a> {
|
|
pub fn set_size(&mut self, size: Size) {
|
|
assert!(
|
|
self.size.replace(size).is_none(),
|
|
"a widget set its size more than once during one draw"
|
|
);
|
|
}
|
|
|
|
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
|
self.write_primitive(primitive, region, Drawn::Yes);
|
|
}
|
|
|
|
/// **Consumed strictly in order, and one mismatch ends recycling for
|
|
/// the rest of the draw.** A widget's `draw` is a function of its own
|
|
/// state, so a redraw writes the same sequence of primitives in the
|
|
/// same order in the overwhelmingly common case; searching the
|
|
/// remainder for a match would turn an O(1) step into an O(primitives)
|
|
/// one to rescue a case that means the widget's content changed shape
|
|
/// anyway. Stopping is also what keeps the invariant simple: every
|
|
/// handle from `recycled` on is untouched and gets freed together.
|
|
fn take_recycled(&mut self, binding: u32, drawn: Drawn) -> Option<PrimitiveHandle> {
|
|
let h = self.recycle.peek()?;
|
|
let drawn_matches = (h.pos == NOT_DRAWN) == (drawn == Drawn::No);
|
|
if h.binding != binding || h.layer != self.layer || !drawn_matches {
|
|
return None;
|
|
}
|
|
self.recycle.next()
|
|
}
|
|
|
|
fn write_primitive<P: Primitive>(
|
|
&mut self,
|
|
primitive: P,
|
|
region: UiRegion,
|
|
drawn: Drawn,
|
|
) -> u32 {
|
|
let inst = PrimitiveInst {
|
|
id: self.id,
|
|
primitive,
|
|
region,
|
|
mask_idx: self.mask,
|
|
move_idx: self.move_slot,
|
|
};
|
|
let h = match self.take_recycled(P::BINDING, drawn) {
|
|
Some(h) => {
|
|
self.render_state.primitives.recycle(&h, inst);
|
|
h
|
|
}
|
|
None => self.render_state.write_primitive(self.layer, drawn, inst),
|
|
};
|
|
if self.mask != MaskIdx::NONE {
|
|
self.rsc.ui_mut().masks.push_ref(self.mask);
|
|
}
|
|
let slot = h.slot;
|
|
self.own(h);
|
|
slot
|
|
}
|
|
|
|
/// Take ownership of a handle this widget just wrote.
|
|
fn own(&mut self, h: PrimitiveHandle) {
|
|
self.render_state
|
|
.primitives
|
|
.set_handle_index(h.slot, self.primitives.len() as u32);
|
|
self.primitives.push(h);
|
|
}
|
|
|
|
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
|
|
self.primitive_at(primitive, self.region)
|
|
}
|
|
|
|
/// Resolves a public paint handle to the compact index stored by a GPU
|
|
/// primitive and retains the handle for exactly as long as that draw.
|
|
pub fn paint(&mut self, paint: &PaintId) -> u32 {
|
|
if !self.paints.contains(paint) {
|
|
self.paints.push(paint.clone());
|
|
}
|
|
paint.slot()
|
|
}
|
|
|
|
pub fn paint_value(&mut self, paint: &mut crate::PaintValue) -> u32 {
|
|
let paint = paint.resolve(&mut self.rsc.ui_mut().paints).clone();
|
|
self.paint(&paint)
|
|
}
|
|
|
|
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
|
self.primitive_at(primitive, region.within(&self.region));
|
|
}
|
|
|
|
/// The slot is allocated once and **rewritten in place** on every
|
|
/// later draw rather than pushed again, because a descendant whose own
|
|
/// region did not change is not redrawn (`draw_inner`'s fast path) and
|
|
/// so keeps pointing at whichever slot it was drawn under. See
|
|
/// `ActiveData::own_mask` for what pushing a fresh one cost.
|
|
pub fn set_mask(&mut self, region: UiRegion) {
|
|
let paint = self.paint(&PaintId::NONE);
|
|
let shape = self.write_primitive(RectPrimitive::color(paint), region, Drawn::No);
|
|
self.set_mask_to(shape);
|
|
}
|
|
|
|
/// Clip everything this widget draws after this call to `shape`'s
|
|
/// own shape -- the first primitive `shape`'s subtree drew, which
|
|
/// must already have been drawn this frame
|
|
/// (`UiRenderState::first_primitive`). What `.masked_by()` uses to
|
|
/// clip a container's content to the rounded background it draws,
|
|
/// with no radius argument anywhere that could fall out of step with
|
|
/// the one being drawn.
|
|
pub fn set_mask_to_widget<W: ?Sized>(&mut self, shape: &StrongWidget<W>) {
|
|
let slot = self.render_state.first_primitive(shape.id()).unwrap_or_else(|| {
|
|
panic!(
|
|
"'{}' was given as a mask's shape but drew no primitive, so there is nothing to \
|
|
clip to",
|
|
self.rsc.widgets().label(shape.id()),
|
|
)
|
|
});
|
|
self.set_mask_to(slot);
|
|
}
|
|
|
|
fn set_mask_to(&mut self, shape: u32) {
|
|
assert!(
|
|
self.own_mask == MaskIdx::NONE || self.mask != self.own_mask,
|
|
"set_mask called twice while drawing one widget: the second would replace the first \
|
|
rather than nest inside it",
|
|
);
|
|
let binding = self.render_state.primitives.instance(shape).binding;
|
|
assert_eq!(
|
|
binding,
|
|
RectPrimitive::BINDING,
|
|
"a mask's shape must be a rect primitive; primitive {shape} is binding {binding}",
|
|
);
|
|
let parent = self.mask;
|
|
let mask = Mask {
|
|
primitive: shape,
|
|
parent,
|
|
};
|
|
let old_parent = if self.own_mask == MaskIdx::NONE {
|
|
let slot = self.rsc.ui_mut().masks.push(mask);
|
|
self.rsc.ui_mut().masks.push_ref(slot);
|
|
self.own_mask = slot;
|
|
MaskIdx::NONE
|
|
} else {
|
|
let old = self.rsc.ui().masks[self.own_mask.idx()].parent;
|
|
*self.rsc.ui_mut().masks.get_mut(self.own_mask) = mask;
|
|
old
|
|
};
|
|
if old_parent != parent {
|
|
if parent != MaskIdx::NONE {
|
|
self.rsc.ui_mut().masks.push_ref(parent);
|
|
}
|
|
if old_parent != MaskIdx::NONE {
|
|
self.rsc.ui_mut().masks.remove(old_parent);
|
|
}
|
|
}
|
|
self.mask = self.own_mask;
|
|
}
|
|
|
|
pub fn widget<'p, W: ?Sized>(&'p mut self, id: &StrongWidget<W>) -> DrawResult<'p, 'a> {
|
|
self.widget_at(id, self.region)
|
|
}
|
|
|
|
pub fn widget_within<'p, W: ?Sized>(
|
|
&'p mut self,
|
|
id: &StrongWidget<W>,
|
|
region: UiRegion,
|
|
) -> DrawResult<'p, 'a> {
|
|
self.widget_at(id, region.within(&self.region))
|
|
}
|
|
|
|
/// Translate this widget's children as one retained subtree, in output
|
|
/// pixels. The first call must happen before drawing a child, because the
|
|
/// slot becomes the parent of every direct child's ordinary move slot.
|
|
/// Once retained, it may be updated later in a redraw (for example after
|
|
/// measuring a changed child). All deeper descendants inherit it and the
|
|
/// CPU hit-test walk resolves the same translation as the shader.
|
|
pub fn set_child_offset(&mut self, offset: Vec2) {
|
|
let slot = match self.child_move_slot {
|
|
Some(slot) => slot,
|
|
None => {
|
|
assert!(
|
|
self.children.is_empty(),
|
|
"a child offset must be created before drawing a child"
|
|
);
|
|
let parent = self.move_slot.idx() as u32;
|
|
let slot = self
|
|
.rsc
|
|
.ui_mut()
|
|
.move_offsets
|
|
.push(MoveOffset::new([offset.x, offset.y], parent));
|
|
// One ref for this widget's ownership and one on the
|
|
// up-link. Direct children take their own refs when their
|
|
// move slots are allocated.
|
|
self.rsc.ui_mut().move_offsets.push_ref(slot);
|
|
self.rsc.ui_mut().move_offsets.push_ref(self.move_slot);
|
|
self.child_move_slot = Some(slot);
|
|
return;
|
|
}
|
|
};
|
|
let next = [offset.x, offset.y];
|
|
if self.rsc.ui().move_offsets[slot.idx()].delta != next {
|
|
self.rsc.ui_mut().move_offsets.get_mut(slot).delta = next;
|
|
self.render_state.note_move();
|
|
}
|
|
}
|
|
|
|
pub fn known_len<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
|
let len = if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
|
|
Some(len.fold_dp(self.density()))
|
|
} else if !self.reuse_child_sizes[match axis {
|
|
Axis::X => 0,
|
|
Axis::Y => 1,
|
|
}] || self.rsc.widgets().needs_redraw.contains(&id.id())
|
|
{
|
|
None
|
|
} else {
|
|
self.render_state
|
|
.active
|
|
.get(&id.id())
|
|
.map(|a| a.size.axis(axis))
|
|
};
|
|
if len.is_some() && !self.size_dependencies.contains(&id.id()) {
|
|
self.size_dependencies.push(id.id());
|
|
}
|
|
len
|
|
}
|
|
|
|
fn widget_at<'p, W: ?Sized>(
|
|
&'p mut self,
|
|
id: &StrongWidget<W>,
|
|
region: UiRegion,
|
|
) -> DrawResult<'p, 'a> {
|
|
self.children.push(id.id());
|
|
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
|
|
self.render_state.draw_inner(
|
|
self.layer,
|
|
id.id(),
|
|
region,
|
|
Some(self.id),
|
|
parent_move_slot.idx() as u32,
|
|
self.mask,
|
|
Retained::default(),
|
|
self.rsc,
|
|
);
|
|
DrawResult {
|
|
painter: self,
|
|
child: id.id(),
|
|
}
|
|
}
|
|
|
|
pub fn place<'p, W: ?Sized>(
|
|
&'p mut self,
|
|
id: &StrongWidget<W>,
|
|
region: UiRegion,
|
|
) -> DrawResult<'p, 'a> {
|
|
let region = region.within(&self.region);
|
|
let retained = self
|
|
.render_state
|
|
.active
|
|
.get(&id.id())
|
|
.map(|active| (active.layer, active.mask));
|
|
if self.render_state.place(id.id(), region, self.rsc).is_some() {
|
|
} else if let Some((layer, mask)) = retained {
|
|
self.children.push(id.id());
|
|
self.rsc.widgets_mut().needs_redraw.insert(id.id());
|
|
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
|
|
self.render_state.draw_inner(
|
|
layer,
|
|
id.id(),
|
|
region,
|
|
Some(self.id),
|
|
parent_move_slot.idx() as u32,
|
|
mask,
|
|
Retained::default(),
|
|
self.rsc,
|
|
);
|
|
} else {
|
|
self.children.push(id.id());
|
|
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
|
|
self.render_state.draw_inner(
|
|
self.layer,
|
|
id.id(),
|
|
region,
|
|
Some(self.id),
|
|
parent_move_slot.idx() as u32,
|
|
self.mask,
|
|
Retained::default(),
|
|
self.rsc,
|
|
);
|
|
}
|
|
DrawResult {
|
|
painter: self,
|
|
child: id.id(),
|
|
}
|
|
}
|
|
|
|
pub fn place_used<W: ?Sized>(
|
|
&mut self,
|
|
id: &StrongWidget<W>,
|
|
used: Size,
|
|
within: UiRegion,
|
|
) -> DrawResult<'_, 'a> {
|
|
let region = self.fit_region(used, within);
|
|
self.place(id, region)
|
|
}
|
|
|
|
pub fn fit_region(&mut self, used: Size, mut within: UiRegion) -> UiRegion {
|
|
let mut region = used
|
|
.to_uivec2(self.density())
|
|
.align(RegionAlign::TOP_LEFT)
|
|
.within(&within);
|
|
let output = self.output_size();
|
|
for axis in [Axis::X, Axis::Y] {
|
|
let mut actual = region.within(&self.region);
|
|
let mut available = within.within(&self.region);
|
|
if actual.axis(axis).len().to_abs(output.axis(axis))
|
|
> available.axis(axis).len().to_abs(output.axis(axis))
|
|
{
|
|
*region.axis_mut(axis) = *within.axis(axis);
|
|
}
|
|
}
|
|
region
|
|
}
|
|
|
|
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
|
self.textures.push(handle.clone());
|
|
self.write_image(handle.image_index(), region.within(&self.region));
|
|
}
|
|
|
|
pub fn texture(&mut self, handle: &TextureHandle) {
|
|
self.textures.push(handle.clone());
|
|
self.write_image(handle.image_index(), self.region);
|
|
}
|
|
|
|
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
|
self.textures.push(handle.clone());
|
|
self.write_image(handle.image_index(), region);
|
|
}
|
|
|
|
fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
|
|
let h = match self.take_recycled(IMAGE_BINDING, Drawn::Yes) {
|
|
Some(h) => {
|
|
self.render_state.primitives.recycle_image(
|
|
&h,
|
|
self.id,
|
|
texture_idx,
|
|
region,
|
|
self.mask,
|
|
self.move_slot,
|
|
);
|
|
h
|
|
}
|
|
None => self.render_state.write_image(
|
|
self.layer,
|
|
self.id,
|
|
texture_idx,
|
|
region,
|
|
self.mask,
|
|
self.move_slot,
|
|
),
|
|
};
|
|
if self.mask != MaskIdx::NONE {
|
|
self.rsc.ui_mut().masks.push_ref(self.mask);
|
|
}
|
|
self.own(h);
|
|
}
|
|
|
|
pub fn render_text(&mut self, text: &TextHandle, width: Option<f32>) -> RenderedText {
|
|
let density = self.render_state.density;
|
|
let ui: &mut UiData = self.rsc.ui_mut();
|
|
let (rendered, prepared) = text.render(width, self.id, &mut ui.textures, density);
|
|
self.render_state.shape_count += u64::from(prepared);
|
|
rendered
|
|
}
|
|
|
|
fn atlas_generation(&self) -> u64 {
|
|
self.rsc.ui().text.borrow().atlas.generation()
|
|
}
|
|
|
|
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
|
// A caller re-emitting quads placed against an atlas that has since
|
|
// been cleared draws every glyph from coordinates now holding
|
|
// something else. Caught at the submission rather than on screen,
|
|
// where it reads as fragments of unrelated letters. `assert_eq!`
|
|
// for R1's reason: two integers per laid-out string, not per
|
|
// glyph, and the failure is unreadable text on a release build.
|
|
assert_eq!(
|
|
text.generation,
|
|
self.atlas_generation(),
|
|
"glyphs placed against atlas generation {} submitted against {}: the holder did not \
|
|
re-render after the atlas was cleared",
|
|
text.generation,
|
|
self.atlas_generation(),
|
|
);
|
|
let flags_for = |is_color| {
|
|
if is_color {
|
|
GlyphPrimitive::IS_COLOR
|
|
} else {
|
|
0
|
|
}
|
|
};
|
|
for paint in text.paints.iter() {
|
|
self.paint(paint);
|
|
}
|
|
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.primitive_at(
|
|
GlyphPrimitive::new(
|
|
glyph.entry.uv_min,
|
|
glyph.entry.uv_max,
|
|
glyph.entry.layer,
|
|
glyph.paint,
|
|
flags_for(glyph.entry.is_color),
|
|
),
|
|
region,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn region(&self) -> UiRegion {
|
|
self.region
|
|
}
|
|
|
|
pub fn output_size(&self) -> Vec2 {
|
|
self.render_state.output_size
|
|
}
|
|
|
|
/// Physical pixels per `dp` -- see `UiRenderState::density`'s field
|
|
/// doc. What `Len::dp`'s `apply_rest` call resolves against.
|
|
pub fn density(&self) -> f32 {
|
|
self.render_state.density
|
|
}
|
|
|
|
pub fn px_size(&mut self) -> Vec2 {
|
|
self.region.size().to_abs(self.render_state.output_size)
|
|
}
|
|
|
|
pub fn text_resources(&mut self) -> Rc<RefCell<TextResources>> {
|
|
self.rsc.ui().text.clone()
|
|
}
|
|
|
|
pub fn child_layer(&mut self) {
|
|
self.layer = self.render_state.layers.child(self.layer);
|
|
}
|
|
|
|
pub fn next_layer(&mut self) {
|
|
self.layer = self.render_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
|
|
}
|
|
}
|