769 lines
30 KiB
Rust
769 lines
30 KiB
Rust
#[cfg(feature = "layout-diagnostics")]
|
|
use crate::layout_diagnostics::{self as diag, Counter};
|
|
use crate::{
|
|
Axis, DrawRegion, Holds, LayoutHolds, LayoutLen, Len, Px, PxVec2, RegionAlign, RenderedText,
|
|
RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
|
UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
|
render::{
|
|
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
|
TexturePrimitive,
|
|
},
|
|
ui::render_state::DrawInfo,
|
|
};
|
|
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,
|
|
|
|
/// The box its parent gave it, in the coordinates of `move_idx`: what a
|
|
/// fraction of this widget's area is a fraction of, and what every region
|
|
/// it writes composes within. The same box on the ask that measures and
|
|
/// the ask that places, which is what keeps a fraction under it from
|
|
/// being resolved twice.
|
|
pub(super) region: UiRegion,
|
|
/// Where this widget's drawing sits inside that box, in the box's own
|
|
/// coordinates: `FULL` while its answer is not yet known, and the box
|
|
/// its answer or its parent chose once one of them has.
|
|
pub(super) placement: UiRegion,
|
|
/// Whether this draw read its placement, which makes the drawing one
|
|
/// that holds for that placement alone -- the way reading a length in
|
|
/// pixels makes it hold for that length.
|
|
pub(super) reads_placement: bool,
|
|
/// That box in pixels, which its children's are a length of: threaded
|
|
/// down from the box this widget was given rather than composed back up
|
|
/// the chain, so every length in layout is one multiply from its
|
|
/// parent's and [`Holds::through`] inverts exactly that.
|
|
pub(super) px: PxVec2,
|
|
pub(super) mask: MaskIdx,
|
|
pub(super) textures: Vec<TextureHandle>,
|
|
pub(super) primitives: Vec<RetainedPrimitive>,
|
|
pub(super) mask_region: Option<DrawRegion>,
|
|
pub(super) inherited_children: Vec<WidgetId>,
|
|
pub(super) extent_own: [Holds; 2],
|
|
/// Only children whose answers were read constrain this widget's answer.
|
|
pub(super) answer_under: LayoutHolds,
|
|
pub(super) children: Vec<WidgetId>,
|
|
/// 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<WidgetId>,
|
|
/// The lengths of the box this widget was first asked about in, in
|
|
/// pixels. Its children's offers are a fraction of it.
|
|
pub(super) offered_px: PxVec2,
|
|
/// Whether this draw is in a box of those lengths, 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<WidgetId>,
|
|
/// 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],
|
|
/// Dependencies of every child drawing, including unmeasured overlays.
|
|
pub(super) under: LayoutHolds,
|
|
/// The movable region this widget's primitives are positioned through:
|
|
/// its own when opted in, otherwise the nearest ancestor's.
|
|
pub(super) move_idx: MoveIdx,
|
|
pub layer: usize,
|
|
/// The layer this widget was entered on, which its children's layers are
|
|
/// counted from however far `layer` has walked.
|
|
pub(super) own_layer: usize,
|
|
pub(super) depth: usize,
|
|
pub(super) id: WidgetId,
|
|
}
|
|
|
|
impl<'a> Painter<'a> {
|
|
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: DrawRegion) {
|
|
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: DrawRegion) {
|
|
self.write_resolved(
|
|
kind,
|
|
primitive,
|
|
region,
|
|
region.resolve(self.region, self.placement),
|
|
);
|
|
}
|
|
|
|
fn write_resolved<P: Primitive>(
|
|
&mut self,
|
|
kind: PrimitiveKind<P>,
|
|
primitive: P,
|
|
region: DrawRegion,
|
|
resolved: UiRegion,
|
|
) {
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::bump(Counter::PrimitiveWrites);
|
|
let h = self.state.layers.write(
|
|
self.layer,
|
|
PrimitiveInst {
|
|
kind,
|
|
id: self.id,
|
|
primitive,
|
|
region: resolved,
|
|
mask_idx: self.mask,
|
|
move_idx: self.move_idx,
|
|
},
|
|
);
|
|
self.push_primitive(RetainedPrimitive { handle: h, region });
|
|
}
|
|
|
|
fn push_primitive(&mut self, h: RetainedPrimitive) {
|
|
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 over the whole of this widget's own box.
|
|
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
|
let at = DrawRegion::Extent(UiRegion::FULL);
|
|
let primitive = primitive.into_primitive(self);
|
|
self.primitive_at(primitive, at)
|
|
}
|
|
|
|
/// Writes in the frame by default. `DrawRegion::Extent` keeps the local
|
|
/// geometry attached to this widget's box without reading its placement.
|
|
pub fn primitive_within(
|
|
&mut self,
|
|
primitive: impl PrimitiveLike,
|
|
region: impl Into<DrawRegion>,
|
|
) {
|
|
let primitive = primitive.into_primitive(self);
|
|
self.primitive_at(primitive, region.into());
|
|
}
|
|
|
|
/// Sets a mask in the selected frame or extent coordinates.
|
|
pub fn set_mask(&mut self, region: impl Into<DrawRegion>) {
|
|
let region = region.into();
|
|
self.mask_region = Some(region);
|
|
assert!(self.mask == MaskIdx::NONE);
|
|
self.mask = self.rsc.ui_mut().masks.push(Mask {
|
|
region: region.resolve(self.region, self.placement),
|
|
move_idx: self.move_idx,
|
|
});
|
|
}
|
|
|
|
/// Draws a widget in the whole of this widget's own box: it gets the
|
|
/// same region -- the same area for its fractions to be of -- and is put
|
|
/// where this widget was put. What a container that is only a wrapper
|
|
/// around one child wants, since its box is the child's.
|
|
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
|
let own = self.placement;
|
|
self.widget_at_inner(id, UiRegion::FULL, [Some(own.x), Some(own.y)], true, false)
|
|
}
|
|
|
|
/// What a widget's rules declare its lengths to be, which whoever draws
|
|
/// it resolves into its box. Reading them depends on nothing -- the box
|
|
/// that comes of them is kept on the child, and `redraw` compares it
|
|
/// there.
|
|
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] {
|
|
declared_lens(self.rsc.widgets(), id.id())
|
|
}
|
|
|
|
/// 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<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
|
|
self.children.retain(|child| *child != id.id());
|
|
self.inherited_children.retain(|child| *child != id.id());
|
|
self.state.undraw_rec(id.id(), self.rsc);
|
|
}
|
|
|
|
/// Draws a child in `region`, relative to this widget's frame. The child
|
|
/// resolves declared lengths and reports against that frame, then places
|
|
/// its drawing by its own alignment.
|
|
pub fn widget_within<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
region: UiRegion,
|
|
) -> DrawResult<'s, 'a, W> {
|
|
self.widget_at(id, region, [None; 2])
|
|
}
|
|
|
|
/// Draws a widget in `region`, saying where in it the drawing goes.
|
|
///
|
|
/// `region` is the child's own area: what a fraction it declares or
|
|
/// reports is a fraction of, and the coordinates the regions it writes
|
|
/// compose within. It is the same box on the ask that measures and the
|
|
/// ask that places, which is what stops a fraction under it being
|
|
/// resolved twice.
|
|
///
|
|
/// `placement` is what of that region the child's drawing takes, per
|
|
/// axis, wherever this widget is choosing. `None` leaves the axis to the
|
|
/// child's own answer and alignment, which is what
|
|
/// [`Self::widget_within`] passes. A span passes the whole row as the
|
|
/// region, so `rel(0.5)` is half the row wherever the child sits in it,
|
|
/// and places the child by passing the slot along its axis.
|
|
pub fn widget_at<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
region: UiRegion,
|
|
placement: [Option<UiSpan>; 2],
|
|
) -> DrawResult<'s, 'a, W> {
|
|
self.widget_at_inner(id, region, placement, false, false)
|
|
}
|
|
|
|
fn widget_at_inner<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
region: UiRegion,
|
|
placement: [Option<UiSpan>; 2],
|
|
inherited: bool,
|
|
measuring: bool,
|
|
) -> DrawResult<'s, 'a, W> {
|
|
if inherited {
|
|
if !self.inherited_children.contains(&id.id()) {
|
|
self.inherited_children.push(id.id());
|
|
}
|
|
} else {
|
|
self.inherited_children.retain(|child| *child != id.id());
|
|
}
|
|
let region_node = self.rsc.widgets().is_region_node(id.id());
|
|
let declared = self.declared_lens(id);
|
|
let align = self.rsc.widgets().alignment(id.id());
|
|
let (local, placement) = ask_box(region, declared, align, placement);
|
|
let within = match local == UiRegion::FULL {
|
|
true => self.region,
|
|
false => local.within(&self.region),
|
|
};
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
if region_node {
|
|
diag::bump(Counter::RegionNodeDraws);
|
|
diag::region_node(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 given_len = local.size();
|
|
let offer_len = match first_ask {
|
|
true => given_len,
|
|
false => self
|
|
.state
|
|
.active
|
|
.get(&id.id())
|
|
.map_or(given_len, |a| a.offer_len),
|
|
};
|
|
let offer_placement = if first_ask {
|
|
placement
|
|
} else {
|
|
self.state
|
|
.active
|
|
.get(&id.id())
|
|
.map_or(placement, |a| a.offer_placement)
|
|
};
|
|
let px = given_len.to_px(self.px);
|
|
let offered_px = offer_len.to_px(self.offered_px);
|
|
// The answer and what it holds for, both about the box asked in. The
|
|
// child's record may say something else once its drawing has been
|
|
// placed: a drawing made again in its placed box holds for that box.
|
|
let (size, answer_holds, holds) = self.state.draw_inner(
|
|
id.id(),
|
|
within,
|
|
DrawInfo {
|
|
layer: self.layer,
|
|
parent: Some(self.id),
|
|
depth: self.depth + 1,
|
|
parent_move: self.move_idx,
|
|
region_node,
|
|
mask: self.mask,
|
|
given_region: local,
|
|
offer_len,
|
|
offer_placement,
|
|
px,
|
|
offered_px,
|
|
placement,
|
|
},
|
|
None,
|
|
measuring,
|
|
self.rsc,
|
|
);
|
|
let in_parent = |holds: LayoutHolds| {
|
|
let mut result = LayoutHolds::ANY;
|
|
for axis in AXES {
|
|
let n = axis as usize;
|
|
result.frame[n] = holds.frame[n].through(local.axis(axis).len());
|
|
if inherited && declared[n].is_none() {
|
|
result.extent[n] = holds.extent[n];
|
|
if holds.placement.is_some() {
|
|
result.placement = Some(self.placement);
|
|
}
|
|
} else {
|
|
let chosen = placement[n].unwrap_or(UiSpan::FULL).len();
|
|
result.frame[n] = result.frame[n].and(
|
|
holds.extent[n]
|
|
.through(chosen)
|
|
.through(local.axis(axis).len()),
|
|
);
|
|
}
|
|
}
|
|
result
|
|
};
|
|
self.under = self.under.and(in_parent(holds));
|
|
let answer_holds = in_parent(answer_holds);
|
|
DrawResult {
|
|
child: id,
|
|
painter: self,
|
|
size: in_parent_frame(size, local.size(), declared),
|
|
answer_holds,
|
|
}
|
|
}
|
|
|
|
/// What a child says its length is without being drawn, if it can say.
|
|
/// Asking counts as reading its size.
|
|
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
|
let widgets = self.rsc.widgets();
|
|
// A rule is the answer where there is one: it wins over whatever the
|
|
// widget would draw, so it has to win over what the widget says too.
|
|
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Measures a child's length from its hint, a retained answer, or `draw`.
|
|
/// A fresh draw evaluates the offer without placing its answer. The caller
|
|
/// must later place or undraw the child.
|
|
pub fn measure_len<W: ?Sized>(
|
|
&mut self,
|
|
child: &StrongWidget<W>,
|
|
axis: Axis,
|
|
region: UiRegion,
|
|
placement: [Option<UiSpan>; 2],
|
|
) -> LayoutLen {
|
|
let offered = placement;
|
|
let declared = self.declared_lens(child);
|
|
let align = self.rsc.widgets().alignment(child.id());
|
|
let (local, placement) = ask_box(region, declared, align, placement);
|
|
let first_ask = self.at_offer && !self.offered.contains(&child.id());
|
|
|
|
if let Some(hint) = self.size_hint(child, axis) {
|
|
return hint;
|
|
}
|
|
let px = local.size().to_px(self.px);
|
|
let retained =
|
|
self.state
|
|
.retained_size(child.id(), px, placement, self.move_idx, self.rsc.widgets());
|
|
let Some((size, holds)) = retained else {
|
|
return self
|
|
.widget_at_inner(child, region, offered, false, true)
|
|
.len(axis);
|
|
};
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::bump(Counter::RetainedSizeHits);
|
|
self.depend_on(child);
|
|
if first_ask {
|
|
self.offered.push(child.id());
|
|
let active = self.state.active.get_mut(&child.id()).unwrap();
|
|
active.offer_len = local.size();
|
|
active.offer_placement = placement;
|
|
}
|
|
let placement = UiRegion {
|
|
x: placement[0].unwrap_or(UiSpan::FULL),
|
|
y: placement[1].unwrap_or(UiSpan::FULL),
|
|
};
|
|
let holds = holds.in_frame(placement);
|
|
for (axis, under) in AXES.into_iter().zip(self.answer_under.frame.iter_mut()) {
|
|
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
|
}
|
|
in_parent_frame(size, local.size(), declared).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
|
|
}
|
|
|
|
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
|
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<f32>,
|
|
) -> &'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)
|
|
}
|
|
|
|
/// Writes glyphs in the selected frame or extent coordinates.
|
|
// TODO: merge the text methods into the primitive ones.
|
|
pub fn glyphs(&mut self, text: &RenderedText, origin: impl Into<DrawRegion>) {
|
|
let origin = origin.into();
|
|
// Glyph offsets and sizes are pixels, which compose additively.
|
|
// Only the shared origin needs the frame/extent composition.
|
|
let resolved = origin.resolve(self.region, self.placement);
|
|
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
|
for glyph in text.glyphs.iter() {
|
|
let place = |mut region: UiRegion| {
|
|
region.x.end = region.x.start;
|
|
region.y.end = region.y.start;
|
|
let mut region = region.offset(UiVec2::from_px(glyph.offset));
|
|
let size = PxVec2::new(
|
|
Px::from_int(glyph.entry.width as i32),
|
|
Px::from_int(glyph.entry.height as i32),
|
|
);
|
|
region.x.end = region.x.start.offset(size.x);
|
|
region.y.end = region.y.start.offset(size.y);
|
|
region
|
|
};
|
|
self.write_resolved(
|
|
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(),
|
|
},
|
|
origin.map(place),
|
|
place(resolved),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The box this widget's parent gave it, in the coordinates its own
|
|
/// primitives are written in -- so a region composed `within` it may be
|
|
/// drawn directly. Its own box is [`Self::placement`] of this one.
|
|
pub fn region(&self) -> UiRegion {
|
|
self.region
|
|
}
|
|
|
|
/// Where this widget's drawing goes inside the box it was given, in that
|
|
/// box's coordinates: what its own answer took of it, or what its parent
|
|
/// chose for it. `FULL` on the ask that measures, since nothing has been
|
|
/// placed yet.
|
|
///
|
|
/// Reading it is what says the drawing depends on it, so a widget that
|
|
/// positions its own content reads it and is drawn again once its box is
|
|
/// known, and one that fills whatever it is given never is.
|
|
pub fn placement(&mut self) -> UiRegion {
|
|
self.reads_placement = true;
|
|
self.placement
|
|
}
|
|
|
|
/// Where this widget sits in a box longer than the length it takes. A
|
|
/// widget that positions its own content reads it to place that content
|
|
/// the way the box around it would have placed the widget.
|
|
pub fn alignment(&self) -> RegionAlign {
|
|
self.rsc.widgets().alignment(self.id)
|
|
}
|
|
|
|
/// Whether a rule beside this widget gives its length on `axis` outright,
|
|
/// which makes whatever it reports for that axis moot. A rule that only
|
|
/// bounds the length is not one of these: the answer is still the
|
|
/// widget's to give, and something still has to work it out.
|
|
///
|
|
/// The widget under a rule does not otherwise learn of it -- this is for
|
|
/// a container deciding whether reading its children across an axis is
|
|
/// worth anything, since reading one is also what makes its own size
|
|
/// depend on it.
|
|
pub fn has_exact_size(&self, axis: Axis) -> bool {
|
|
self.rsc
|
|
.widgets()
|
|
.size_rules(self.id)
|
|
.axis(axis)
|
|
.exact()
|
|
.is_some()
|
|
}
|
|
|
|
/// The part of this widget's box that something of `size` takes, at the
|
|
/// near edge. A container that reports one child's size gives every child
|
|
/// this, so what it draws is inside what it says it occupies.
|
|
pub fn box_of(&self, size: Size) -> UiRegion {
|
|
let lens = placed_lens(size, [None; 2], [false; 2]);
|
|
placed_box(UiRegion::FULL, lens, RegionAlign::NEAR)
|
|
}
|
|
|
|
/// This widget's own 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) -> PxVec2 {
|
|
PxVec2::new(self.px_len(Axis::X), self.px_len(Axis::Y))
|
|
}
|
|
|
|
/// One axis of this widget's own 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) -> Px {
|
|
let part = self.placement.axis(axis).len();
|
|
let len = part.to_px(self.px.axis(axis));
|
|
let own = &mut self.extent_own[axis as usize];
|
|
if *own == Holds::ANY {
|
|
*own = Holds::at(len);
|
|
}
|
|
len
|
|
}
|
|
|
|
/// The lengths of this widget's own 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, holds: impl Into<Holds>) {
|
|
let part = self.placement.axis(axis).len();
|
|
let holds = holds.into();
|
|
debug_assert!(
|
|
holds.contains(part.to_px(self.px.axis(axis))),
|
|
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
|
self.label(),
|
|
self.id
|
|
);
|
|
self.extent_own[axis as usize] = holds;
|
|
}
|
|
|
|
/// One axis of the box this widget's parent gave it, in pixels -- what a
|
|
/// fraction of its area resolves against, and so what a container divides
|
|
/// among its children. Its own box is a part of this one.
|
|
pub fn region_px_len(&mut self, axis: Axis) -> Px {
|
|
let len = self.px.axis(axis);
|
|
let own = &mut self.own[axis as usize];
|
|
if *own == Holds::ANY {
|
|
*own = Holds::at(len);
|
|
}
|
|
len
|
|
}
|
|
|
|
/// [`Self::holds`] stated about the region rather than about this
|
|
/// widget's own box, for a container whose drawing turns on the box it
|
|
/// was given rather than on the part of it it took.
|
|
pub fn region_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
|
let holds = holds.into();
|
|
debug_assert!(
|
|
holds.contains(self.px.axis(axis)),
|
|
"'{}' ({:?}) says its drawing holds for lengths that leave out its region",
|
|
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);
|
|
}
|
|
|
|
/// The layer this widget's `n`th child draws on, addressed rather than
|
|
/// walked to. A container that measures one child by drawing it can ask
|
|
/// on the layer that child will end up on, and then the second ask is a
|
|
/// reuse rather than a second drawing on another layer.
|
|
pub fn child_layer_at(&mut self, n: usize) {
|
|
let mut at = self.state.layers.child(self.own_layer);
|
|
for _ in 0..n {
|
|
at = self.state.layers.next(at);
|
|
}
|
|
self.layer = at;
|
|
}
|
|
|
|
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<W>,
|
|
size: Size,
|
|
answer_holds: LayoutHolds,
|
|
}
|
|
|
|
impl<W: ?Sized> 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.painter.answer_under = self.painter.answer_under.and(self.answer_holds);
|
|
self.size
|
|
}
|
|
|
|
pub fn len(self, axis: Axis) -> LayoutLen {
|
|
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<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()
|
|
}
|
|
}
|
|
|
|
/// A child's answer as lengths of the parent's own region. A widget reports
|
|
/// a fraction of its own region, and `of` is that region as a length of this
|
|
/// one. Pixels come through untouched, being that many pixels wherever they
|
|
/// end up. A declared axis is already the parent's: it resolved the rule in
|
|
/// its own region, and the rule is what the report says.
|
|
fn in_parent_frame(size: Size, of: UiVec2, declared: [Option<LayoutLen>; 2]) -> Size {
|
|
let mut size = size;
|
|
for (axis, declared) in AXES.into_iter().zip(declared) {
|
|
if declared.is_none() {
|
|
*size.axis_mut(axis) = size.axis(axis).within_len(of.axis(axis));
|
|
}
|
|
}
|
|
size
|
|
}
|
|
|
|
/// 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_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLen>; 2] {
|
|
let rules = widgets.size_rules(id);
|
|
let widget = widgets.get_dyn(id);
|
|
AXES.map(|axis| {
|
|
rules.axis(axis).declared().or_else(|| {
|
|
// A hint still narrows the box where no rule does, which is how a
|
|
// widget with a natural pixel size -- an image, a gap -- gets that
|
|
// size rather than the whole offer. That is the offer's business
|
|
// rather than a declaration's, and this falls away once a widget
|
|
// occupies its reported size inside the box it was offered.
|
|
widget
|
|
.and_then(|widget| widget.size_hint(axis))
|
|
.filter(|len| len.leftover == Weight::ZERO)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Whether what a widget reported along an axis is the whole of the box it
|
|
/// is in rather than a part to be placed inside it. A share fills, because a
|
|
/// share is a length only to whoever divides one, and whoever did is the one
|
|
/// that handed down this box. A declared axis does too: the rule already gave
|
|
/// the region its length, and the rule's length is what the widget reports
|
|
/// there. And an axis the parent decided from the answer is
|
|
/// the answer already.
|
|
pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: bool) -> bool {
|
|
reported.leftover != Weight::ZERO || declared.is_some() || decided
|
|
}
|
|
|
|
/// What of the box it was given a widget's drawing occupies, as lengths of
|
|
/// that box: the size it reported wherever that is a part to be placed, and
|
|
/// the whole of the box wherever the answer fills it.
|
|
///
|
|
/// A reported fraction is a fraction of the box the widget drew in, where a
|
|
/// declared one is a fraction of the box its parent handed down -- a span
|
|
/// reporting `rel(1.0)` means all of what it was given, whatever that was a
|
|
/// fraction of. So this is a length of the box rather than a length composed
|
|
/// into it, and a box in pixels is this step from the given box's pixels.
|
|
pub(crate) fn placed_lens(
|
|
size: Size,
|
|
declared: [Option<LayoutLen>; 2],
|
|
decided: [bool; 2],
|
|
) -> UiVec2 {
|
|
let mut lens = UiVec2::FULL_SIZE;
|
|
for (axis, (declared, decided)) in AXES.into_iter().zip(declared.into_iter().zip(decided)) {
|
|
let reported = size.axis(axis);
|
|
if !fills(reported, declared, decided) {
|
|
*lens.axis_mut(axis) = Len::from_parts(reported.rel, reported.px);
|
|
}
|
|
}
|
|
lens
|
|
}
|
|
|
|
/// Where that drawing sits: those lengths taken of the box the widget was
|
|
/// asked in, on the side of it that the widget's alignment says.
|
|
pub(crate) fn placed_box(region: UiRegion, lens: UiVec2, align: RegionAlign) -> UiRegion {
|
|
let mut placed = region;
|
|
for axis in AXES {
|
|
// The whole of the box is already where it sits, and the arithmetic
|
|
// below is the identity for it.
|
|
if lens.axis(axis) == Len::FULL {
|
|
continue;
|
|
}
|
|
let span = placed.axis_mut(axis);
|
|
let len = lens.axis(axis).within_len(span.len());
|
|
span.start += (span.len() - len).scale(align.axis(axis).rel());
|
|
span.end = span.start + len;
|
|
}
|
|
placed
|
|
}
|
|
|
|
/// A declared axis gets a frame of that length, aligned within the parent's
|
|
/// slot (or the offer). Undeclared axes keep the offered frame and chosen
|
|
/// placement, so their reported fractions retain that reference.
|
|
pub(crate) fn ask_box(
|
|
mut region: UiRegion,
|
|
declared: [Option<LayoutLen>; 2],
|
|
align: RegionAlign,
|
|
placement: [Option<UiSpan>; 2],
|
|
) -> (UiRegion, [Option<UiSpan>; 2]) {
|
|
let mut placed = [None; 2];
|
|
for (axis, (len, chosen)) in AXES.into_iter().zip(declared.into_iter().zip(placement)) {
|
|
let Some(len) = len else {
|
|
placed[axis as usize] = chosen;
|
|
continue;
|
|
};
|
|
let span = region.axis_mut(axis);
|
|
let len = Len::from_parts(len.rel, len.px);
|
|
let slot = chosen.unwrap_or(*span);
|
|
span.start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
|
|
span.end = span.start + len;
|
|
}
|
|
(region, placed)
|
|
}
|