Redesign span layout around retained placement
This commit is contained in:
1 parent
4b69f3cc6b
commit
992482414f
23 files changed
+426
-1005
No files matched your search
+68
-106
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
Color, DrawMode, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId,
|
||||
Axis, Color, Len, RegionAlign, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer,
|
||||
TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId,
|
||||
render::{
|
||||
Drawn, GlyphPrimitive, IMAGE_BINDING, Mask, MaskIdx, MoveIdx, NOT_DRAWN, Primitive,
|
||||
PrimitiveHandle, PrimitiveInst, RectPrimitive,
|
||||
@@ -9,7 +9,6 @@ use crate::{
|
||||
util::Vec2,
|
||||
};
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
pub struct Painter<'a> {
|
||||
pub(super) state: &'a mut UiRenderState,
|
||||
pub(super) rsc: &'a mut dyn UiRsc,
|
||||
@@ -17,43 +16,19 @@ pub struct Painter<'a> {
|
||||
pub(super) region: UiRegion,
|
||||
pub(super) mask: MaskIdx,
|
||||
pub(super) move_slot: MoveIdx,
|
||||
/// This widget's own mask slot, reused across redraws -- see
|
||||
/// `ActiveData::own_mask`. `MaskIdx::NONE` until `set_mask` is called
|
||||
/// for the first time in this widget's life.
|
||||
/// This widget's retained mask slot.
|
||||
pub(super) own_mask: MaskIdx,
|
||||
pub(super) textures: Vec<TextureHandle>,
|
||||
pub(super) primitives: Vec<PrimitiveHandle>,
|
||||
/// The handles this widget owned before *this* draw, offered back to
|
||||
/// it in the order it wrote them last time -- see
|
||||
/// [`Self::take_recycled`]. Empty for a widget being drawn for the
|
||||
/// first time. Whatever is left when the draw ends is genuinely gone,
|
||||
/// and `UiRenderState::draw_inner` frees the remainder.
|
||||
///
|
||||
/// An iterator rather than a vec and a cursor because a
|
||||
/// `PrimitiveHandle` is an ownership token and deliberately not
|
||||
/// `Clone`: `peek` asks whether the next one fits without taking it,
|
||||
/// `next` takes it, and what is left is exactly what nothing claimed.
|
||||
/// Previous handles, consumed in draw order and freed if left over.
|
||||
pub(super) recycle: std::iter::Peekable<std::vec::IntoIter<PrimitiveHandle>>,
|
||||
pub(super) children: Vec<WidgetId>,
|
||||
pub(super) reuse_child_sizes: bool,
|
||||
pub layer: usize,
|
||||
pub(super) id: WidgetId,
|
||||
/// Whether this draw produces what goes on screen or only a size --
|
||||
/// see [`crate::DrawMode`]. Inherited by every child this widget
|
||||
/// draws, so one `measure` at the top makes the whole subtree
|
||||
/// write-free.
|
||||
pub(super) mode: DrawMode,
|
||||
}
|
||||
|
||||
impl<'a> Painter<'a> {
|
||||
/// True while this draw is only being asked how big the widget would
|
||||
/// be. **Every method here that writes anything must return early on
|
||||
/// it** -- a widget's own `draw` never has to check, which is the
|
||||
/// point: measuring is a property of the painter, not something each
|
||||
/// widget re-implements.
|
||||
pub fn measuring(&self) -> bool {
|
||||
self.mode == DrawMode::Measure
|
||||
}
|
||||
|
||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||
self.write_primitive(primitive, region, Drawn::Yes);
|
||||
}
|
||||
@@ -74,8 +49,6 @@ impl<'a> Painter<'a> {
|
||||
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 {
|
||||
// Left un-taken deliberately: this handle and everything after
|
||||
// it is freed together when the draw ends.
|
||||
return None;
|
||||
}
|
||||
self.recycle.next()
|
||||
@@ -89,9 +62,6 @@ impl<'a> Painter<'a> {
|
||||
region: UiRegion,
|
||||
drawn: Drawn,
|
||||
) -> u32 {
|
||||
if self.measuring() {
|
||||
return u32::MAX;
|
||||
}
|
||||
let inst = PrimitiveInst {
|
||||
id: self.id,
|
||||
primitive,
|
||||
@@ -107,7 +77,6 @@ impl<'a> Painter<'a> {
|
||||
None => self.state.write_primitive(self.layer, drawn, inst),
|
||||
};
|
||||
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);
|
||||
}
|
||||
let slot = h.slot;
|
||||
@@ -157,13 +126,6 @@ impl<'a> Painter<'a> {
|
||||
/// 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) {
|
||||
// Clipping changes no widget's reported size, so a measurement
|
||||
// skips it whole -- not just the shape primitive, but the mask
|
||||
// slot and its refs, which would otherwise be a leaked slot per
|
||||
// masked widget per measured frame.
|
||||
if self.measuring() {
|
||||
return;
|
||||
}
|
||||
let shape = self.write_primitive(RectPrimitive::color(Color::NONE), region, Drawn::No);
|
||||
self.set_mask_to(shape);
|
||||
}
|
||||
@@ -176,12 +138,6 @@ impl<'a> Painter<'a> {
|
||||
/// 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>) {
|
||||
// Same as `set_mask`, and doubly so: a measurement leaves nothing
|
||||
// in `active`, so the shape widget has drawn no primitive to
|
||||
// point at and this would panic on its own message.
|
||||
if self.measuring() {
|
||||
return;
|
||||
}
|
||||
let slot = self.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 \
|
||||
@@ -261,6 +217,16 @@ impl<'a> Painter<'a> {
|
||||
self.widget_at(id, region.within(&self.region))
|
||||
}
|
||||
|
||||
pub fn known_len<W: ?Sized>(&self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
||||
if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
|
||||
return Some(len.fold_dp(self.density()));
|
||||
}
|
||||
if !self.reuse_child_sizes || self.rsc.widgets().needs_redraw.contains(&id.id()) {
|
||||
return None;
|
||||
}
|
||||
self.state.active.get(&id.id()).map(|a| a.size.axis(axis))
|
||||
}
|
||||
|
||||
fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
|
||||
self.children.push(id.id());
|
||||
// Passed directly rather than looked up from `self.active`: this
|
||||
@@ -277,66 +243,65 @@ impl<'a> Painter<'a> {
|
||||
Some(self.id),
|
||||
self.move_slot.idx() as u32,
|
||||
self.mask,
|
||||
self.mode,
|
||||
Retained::default(),
|
||||
self.rsc,
|
||||
)
|
||||
}
|
||||
|
||||
/// Ask `widget` how big it would be in `region`, **writing nothing**
|
||||
/// -- see [`DrawMode::Measure`]. For the container that cannot choose
|
||||
/// what to offer a child without already knowing the child's size:
|
||||
/// measure, work out the real region, then draw it for real.
|
||||
///
|
||||
/// ```ignore
|
||||
/// let used = painter.measure(&child, generous);
|
||||
/// painter.widget_within(&child, self.box_for(used));
|
||||
/// ```
|
||||
///
|
||||
/// This replaced a `draw_twice(child, first, |used| second)`, which
|
||||
/// made the same two draws but had the caller express the second
|
||||
/// region as a closure returning it -- so the interesting arithmetic
|
||||
/// happened inside a callback and anything it wanted to keep had to
|
||||
/// be written out through a captured `&mut`. Two statements say the
|
||||
/// same thing in the order it happens (CODE_RULES' "compose
|
||||
/// linearly"), and the measurement costs no arena slot now rather
|
||||
/// than allocating one and freeing it.
|
||||
///
|
||||
/// The measured widget is left exactly as it was -- not in `active`
|
||||
/// if it was not there before, and untouched if it was -- so the draw
|
||||
/// that follows is an ordinary one and cannot be short-circuited by
|
||||
/// the measurement having "already drawn" it at that region.
|
||||
pub fn measure<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
|
||||
self.state.draw_inner(
|
||||
self.layer,
|
||||
id.id(),
|
||||
region.within(&self.region),
|
||||
Some(self.id),
|
||||
self.move_slot.idx() as u32,
|
||||
self.mask,
|
||||
DrawMode::Measure,
|
||||
Retained::default(),
|
||||
self.rsc,
|
||||
)
|
||||
}
|
||||
|
||||
/// Move an already-drawn child from wherever it currently sits to
|
||||
/// `region` (resolved against this widget's own region, matching
|
||||
/// `widget_within`) without a second draw -- an O(1) offset write via
|
||||
/// `UiRenderState::mov`. For a container that draws a child
|
||||
/// provisionally to learn its size (e.g. `Aligned`) and then places it
|
||||
/// for real. Only valid when the target keeps the child's drawn size;
|
||||
/// if the shape actually changes, the normal `widget_within` dispatch
|
||||
/// (which detects that from the stored region) does the right thing
|
||||
/// instead.
|
||||
pub fn reposition<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
||||
// Moves an *already-drawn* child, of which a measurement has
|
||||
// none.
|
||||
if self.measuring() {
|
||||
return;
|
||||
}
|
||||
/// Place an already-drawn child's used area, redrawing only if its size changes.
|
||||
pub fn place<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
|
||||
let region = region.within(&self.region);
|
||||
self.state.reposition(id.id(), region, self.rsc);
|
||||
let retained = self
|
||||
.state
|
||||
.active
|
||||
.get(&id.id())
|
||||
.map(|active| (active.layer, active.mask));
|
||||
if let Some(size) = self.state.place(id.id(), region, self.rsc) {
|
||||
size
|
||||
} else if let Some((layer, mask)) = retained {
|
||||
self.children.push(id.id());
|
||||
self.rsc.widgets_mut().needs_redraw.insert(id.id());
|
||||
self.state.draw_inner(
|
||||
layer,
|
||||
id.id(),
|
||||
region,
|
||||
Some(self.id),
|
||||
self.move_slot.idx() as u32,
|
||||
mask,
|
||||
Retained::default(),
|
||||
self.rsc,
|
||||
)
|
||||
} else {
|
||||
self.widget_at(id, region)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place_used<W: ?Sized>(
|
||||
&mut self,
|
||||
id: &StrongWidget<W>,
|
||||
used: Size,
|
||||
within: UiRegion,
|
||||
) -> Size {
|
||||
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) {
|
||||
@@ -358,9 +323,6 @@ impl<'a> Painter<'a> {
|
||||
/// the layer's one instanced draw, so it goes through
|
||||
/// `Primitives::write_image` instead of `primitive_at`/`Primitive::vec`.
|
||||
fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
|
||||
if self.measuring() {
|
||||
return;
|
||||
}
|
||||
let h = match self.take_recycled(IMAGE_BINDING, Drawn::Yes) {
|
||||
Some(h) => {
|
||||
self.state.primitives.recycle_image(
|
||||
|
||||
Reference in new issue
Block a user