Resolve deferred size comparisons before allocating span slots
This commit is contained in:
1 parent
de1eb7e406
commit
8780b40bb7
23 files changed
+1471
-130
No files matched your search
@@ -51,6 +51,8 @@ pub struct ActiveData {
|
||||
/// An owned mask holds one reference independently of its primitives.
|
||||
pub mask_region: Option<UiRegion>,
|
||||
pub children: Vec<WidgetId>,
|
||||
pub request_deps: Vec<WidgetId>,
|
||||
pub(crate) scratch: DrawScratch,
|
||||
/// The movable region its primitives are positioned through: its own when
|
||||
/// opted in, otherwise the nearest ancestor's.
|
||||
pub move_idx: MoveIdx,
|
||||
@@ -101,3 +103,12 @@ pub struct Answer {
|
||||
pub size: Size,
|
||||
pub holds: LayoutHolds,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct DrawScratch {
|
||||
pub children: Vec<WidgetId>,
|
||||
pub size_deps: Vec<WidgetId>,
|
||||
pub under: Vec<(WidgetId, LayoutHolds)>,
|
||||
pub requests: Vec<crate::RequestedLen>,
|
||||
pub lengths: Vec<crate::Px>,
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,7 @@ pub use active::*;
|
||||
pub use holds::*;
|
||||
pub use layout_holds::*;
|
||||
pub use painter::{Painter, PrimitiveLike};
|
||||
pub use place::{PlaceDesc, PlaceDescAxis, RetainedPrimitive};
|
||||
pub use place::{PlaceDesc, PlaceDescAxis, PlaceFit, RetainedPrimitive};
|
||||
pub use render_state::*;
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
+161
-12
@@ -2,8 +2,9 @@
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Bounds, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign,
|
||||
Rel, RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
|
||||
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||
Rel, RenderedText, RequestArena, RequestedLen, RetainedPrimitive, Size, SizeRequests,
|
||||
StrongWidget, TextAttrs, TextBuffer, TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2,
|
||||
Weight, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||
TexturePrimitive,
|
||||
@@ -41,6 +42,8 @@ pub struct Painter<'a> {
|
||||
pub(super) children: Vec<WidgetId>,
|
||||
/// The children whose size this widget read while drawing.
|
||||
pub(super) size_deps: Vec<WidgetId>,
|
||||
pub(super) request_deps: Vec<WidgetId>,
|
||||
pub(super) scratch: crate::DrawScratch,
|
||||
/// What this draw itself reads, as against what its children's drawings
|
||||
/// hold for: every window and every length of its own region until it
|
||||
/// reads one, then that one unless it says otherwise, and the rel base or
|
||||
@@ -62,6 +65,106 @@ pub struct Painter<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Painter<'a> {
|
||||
/// Reuses this widget's allocation buffers across draws. Nested painters
|
||||
/// have independent buffers, so discovering a child cannot overwrite them.
|
||||
pub fn with_requests<T>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Self, &mut Vec<RequestedLen>, &mut Vec<Px>) -> T,
|
||||
) -> T {
|
||||
let mut requests = std::mem::take(&mut self.scratch.requests);
|
||||
let mut lengths = std::mem::take(&mut self.scratch.lengths);
|
||||
requests.clear();
|
||||
lengths.clear();
|
||||
let result = f(self, &mut requests, &mut lengths);
|
||||
self.scratch.requests = requests;
|
||||
self.scratch.lengths = lengths;
|
||||
result
|
||||
}
|
||||
|
||||
/// Discovers a composable request without painting a provisional box.
|
||||
pub fn size_request<W: ?Sized>(
|
||||
&mut self,
|
||||
child: &StrongWidget<W>,
|
||||
axis: Axis,
|
||||
) -> Option<RequestedLen> {
|
||||
self.request_deps.push(child.id());
|
||||
if self.rsc.widgets().size_rules(child.id())[axis].bound() == crate::Bound::ANY
|
||||
&& let Some(len) = self.size_hint(child, axis)
|
||||
{
|
||||
return Some(len.into());
|
||||
}
|
||||
let rel_base = self.rel_base(axis);
|
||||
let mut requests = SizeRequests {
|
||||
arena: &mut self.state.requests,
|
||||
measured: None,
|
||||
widgets: self.rsc.widgets(),
|
||||
dependencies: &mut self.request_deps,
|
||||
rel_base,
|
||||
};
|
||||
let request = requests.widget(child, axis)?;
|
||||
// An intrinsic fixed answer still draws in the offered room and is
|
||||
// moved afterwards. Only a declaration or a share chooses its ask box.
|
||||
(request.has_leftover()
|
||||
|| matches!(
|
||||
self.rsc.widgets().size_rules(child.id())[axis],
|
||||
crate::SizeRule::Request(_)
|
||||
))
|
||||
.then_some(request)
|
||||
}
|
||||
|
||||
/// Completes discovery after a child was measured. Only this call may use
|
||||
/// drawn answers: before the ask they could belong to an obsolete box.
|
||||
pub fn measured_request<W: ?Sized>(
|
||||
&mut self,
|
||||
child: &StrongWidget<W>,
|
||||
axis: Axis,
|
||||
len: LayoutLen,
|
||||
) -> RequestedLen {
|
||||
let rel_base = self.rel_base(axis);
|
||||
let bound = self.rsc.widgets().size_rules(child.id())[axis].bound();
|
||||
let mut requests = SizeRequests {
|
||||
arena: &mut self.state.requests,
|
||||
measured: Some(&self.state.active),
|
||||
widgets: self.rsc.widgets(),
|
||||
dependencies: &mut self.request_deps,
|
||||
rel_base,
|
||||
};
|
||||
match requests.widget(child, axis) {
|
||||
Some(request) if request.linear().is_none() && request.has_leftover() => request,
|
||||
_ if len.leftover > Weight::ZERO => requests.bounded(len.into(), bound),
|
||||
_ => len.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A deferred comparison reads this window when the allocation is solved.
|
||||
pub fn allocate(
|
||||
&mut self,
|
||||
requests: &[RequestedLen],
|
||||
room: Len,
|
||||
axis: Axis,
|
||||
output: &mut Vec<Px>,
|
||||
) {
|
||||
let window = self.window[axis];
|
||||
self.own[axis].window = self.own[axis].window.and(Holds::at(window));
|
||||
output.clear();
|
||||
output.extend(
|
||||
self.state
|
||||
.requests
|
||||
.allocate(requests, room.to_px(window), window),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn minimum_request(&mut self, request: &RequestedLen, axis: Axis) -> Len {
|
||||
match request.linear() {
|
||||
Some(len) => len.without_leftover(),
|
||||
None => {
|
||||
let window = self.window[axis];
|
||||
self.own[axis].window = self.own[axis].window.and(Holds::at(window));
|
||||
Len::from_parts(Rel::ZERO, self.state.requests.minimum(*request, window))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -195,9 +298,15 @@ impl<'a> Painter<'a> {
|
||||
declared,
|
||||
bounds,
|
||||
holds: ask_holds,
|
||||
} = self
|
||||
.placing()
|
||||
.ask(self.rsc.widgets(), self.window, id.id(), offer);
|
||||
inputs,
|
||||
} = self.placing().ask(
|
||||
self.rsc.widgets(),
|
||||
&mut self.state.requests,
|
||||
self.window,
|
||||
id.id(),
|
||||
offer,
|
||||
);
|
||||
self.own = self.own.and(inputs);
|
||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
if region_node {
|
||||
@@ -422,9 +531,10 @@ impl<'a> Painter<'a> {
|
||||
/// 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]
|
||||
.exact()
|
||||
.is_some()
|
||||
matches!(
|
||||
self.rsc.widgets().size_rules(self.id)[axis],
|
||||
crate::SizeRule::Exact(_) | crate::SizeRule::Request(_)
|
||||
)
|
||||
}
|
||||
|
||||
/// This widget's own box in pixels. Reading it makes the drawing one
|
||||
@@ -667,6 +777,9 @@ impl Widgets {
|
||||
/// share included, since a share is a length only to whoever divides one,
|
||||
/// and that is the parent rather than this widget.
|
||||
fn exact_len(&self, id: WidgetId, axis: Axis) -> Option<LayoutLen> {
|
||||
if matches!(self.size_rules(id)[axis], crate::SizeRule::Request(_)) {
|
||||
return None;
|
||||
}
|
||||
self.size_rules(id)[axis].exact().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
|
||||
@@ -706,6 +819,9 @@ pub(super) struct Ask {
|
||||
/// drawing it is part of. Kept on the widget asked about rather than on
|
||||
/// the asker because the root has no asker.
|
||||
pub holds: LayoutHolds,
|
||||
/// Inputs read against the parent before declarations choose a new base.
|
||||
/// These belong to the asker; the widget's own holds describe its output box.
|
||||
pub inputs: LayoutHolds,
|
||||
}
|
||||
|
||||
impl Placing {
|
||||
@@ -719,6 +835,7 @@ impl Placing {
|
||||
pub(super) fn ask(
|
||||
&self,
|
||||
widgets: &Widgets,
|
||||
requests: &mut RequestArena,
|
||||
window: PxVec2,
|
||||
id: WidgetId,
|
||||
mut place: PlaceDesc,
|
||||
@@ -726,10 +843,33 @@ impl Placing {
|
||||
let align = widgets.alignment(id);
|
||||
let rules = widgets.size_rules(id);
|
||||
let mut holds = LayoutHolds::ANY;
|
||||
let declared = widgets.declared_lens(id);
|
||||
let mut inputs = LayoutHolds::ANY;
|
||||
let mut declared = widgets.declared_lens(id);
|
||||
let mut bounds = Bounds::ANY;
|
||||
for axis in Axis::BOTH {
|
||||
let base = place.base(axis, self.rel_base);
|
||||
if let crate::SizeRule::Request(request) = &rules[axis] {
|
||||
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
||||
inputs[axis].region_len = Some(self.region[axis].len());
|
||||
inputs[axis].window = Holds::at(window[axis]);
|
||||
let offer = place.of(self.region, align)[axis].len();
|
||||
let len = if place[axis].fit == crate::PlaceFit::Allocated {
|
||||
offer
|
||||
} else {
|
||||
let request = requests.import(request, base);
|
||||
let len = requests
|
||||
.allocate(&[request], offer.to_px(window[axis]), window[axis])
|
||||
.next()
|
||||
.unwrap();
|
||||
holds[axis].window = Holds::at(window[axis]);
|
||||
Len::from_parts(Rel::ZERO, len)
|
||||
};
|
||||
let len = Len::from_parts(Rel::ZERO, len.to_px(window[axis]));
|
||||
place[axis].rel_base = RelBase::Len(len);
|
||||
declared[axis] = Some(len);
|
||||
holds[axis].rel_base = Some(len);
|
||||
continue;
|
||||
}
|
||||
// A share fills what the pixels and fraction beside it leave of
|
||||
// the box and overflows where they are longer, which is the rule
|
||||
// a span follows with one child. Only the overflow is a box of
|
||||
@@ -748,7 +888,15 @@ impl Placing {
|
||||
// anything. Resolved here because only the ask knows the rel base
|
||||
// a fraction in it is of. `MaxSize` is the box version, and it is
|
||||
// a widget because a widget is drawn again when its box changes.
|
||||
bounds[axis] = rules[axis].bound().within_len(base);
|
||||
let bound = rules[axis].bound();
|
||||
if [bound.min, bound.max]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|len| len.rel != Rel::ZERO)
|
||||
{
|
||||
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
||||
}
|
||||
bounds[axis] = bound.within_len(base);
|
||||
}
|
||||
let (rel_base, region) =
|
||||
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
||||
@@ -759,6 +907,7 @@ impl Placing {
|
||||
declared,
|
||||
bounds,
|
||||
holds,
|
||||
inputs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,7 +934,7 @@ impl Placing {
|
||||
// decided, and a parent that divides one has already given the share
|
||||
// whatever it was owed. Only an offer -- a box with the answer still
|
||||
// to be placed inside it -- is a box a share reads.
|
||||
if place[axis].fills {
|
||||
if place[axis].fit.fills() {
|
||||
return (None, Holds::ANY);
|
||||
}
|
||||
// A share with nothing beside it is the box whatever the box is, so
|
||||
@@ -838,7 +987,7 @@ impl PlaceDesc {
|
||||
let mut placed = region;
|
||||
for axis in Axis::BOTH {
|
||||
let reported = size[axis];
|
||||
if reported.fills(declared[axis], self[axis].fills) {
|
||||
if reported.fills(declared[axis], self[axis].fit.fills()) {
|
||||
continue;
|
||||
}
|
||||
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
|
||||
|
||||
+25
-5
@@ -12,10 +12,24 @@ use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct PlaceDescAxis {
|
||||
pub span: PlaceSpan,
|
||||
pub fills: bool,
|
||||
pub fit: PlaceFit,
|
||||
pub rel_base: RelBase,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum PlaceFit {
|
||||
Align,
|
||||
Fill,
|
||||
/// The parent has already evaluated the child's size request.
|
||||
Allocated,
|
||||
}
|
||||
|
||||
impl PlaceFit {
|
||||
pub fn fills(self) -> bool {
|
||||
self != Self::Align
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum PlaceSpan {
|
||||
Within(UiSpan),
|
||||
@@ -44,7 +58,13 @@ impl PlaceDescAxis {
|
||||
/// it again. A container uses it where it hands back exactly what the
|
||||
/// child asked for -- a row placing a child at the length it reported.
|
||||
pub const fn fills(mut self) -> Self {
|
||||
self.fills = true;
|
||||
self.fit = PlaceFit::Fill;
|
||||
self
|
||||
}
|
||||
|
||||
/// A final allocation, including any comparisons in the child's request.
|
||||
pub const fn allocated(mut self) -> Self {
|
||||
self.fit = PlaceFit::Allocated;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -153,7 +173,7 @@ impl UiSpan {
|
||||
pub const fn within_desc(self) -> PlaceDescAxis {
|
||||
PlaceDescAxis {
|
||||
span: PlaceSpan::Within(self),
|
||||
fills: false,
|
||||
fit: PlaceFit::Align,
|
||||
rel_base: RelBase::WithRegion,
|
||||
}
|
||||
}
|
||||
@@ -171,7 +191,7 @@ impl UiSpan {
|
||||
pub const fn shifted_desc(self) -> PlaceDescAxis {
|
||||
PlaceDescAxis {
|
||||
span: PlaceSpan::Shifted(self),
|
||||
fills: false,
|
||||
fit: PlaceFit::Align,
|
||||
rel_base: RelBase::Inherit,
|
||||
}
|
||||
}
|
||||
@@ -185,7 +205,7 @@ impl Len {
|
||||
pub const fn as_desc(self) -> PlaceDescAxis {
|
||||
PlaceDescAxis {
|
||||
span: PlaceSpan::Sized(self),
|
||||
fills: false,
|
||||
fit: PlaceFit::Align,
|
||||
rel_base: RelBase::Len(self),
|
||||
}
|
||||
}
|
||||
|
||||
+100
-28
@@ -94,7 +94,10 @@ pub struct UiRenderState {
|
||||
deferred: crate::util::HashSet<WidgetId>,
|
||||
/// What the walk has left to settle, deepest last. Ordered rather than
|
||||
/// searched for, so finding the next one is not a pass over the marks.
|
||||
pending: std::collections::BTreeSet<(usize, WidgetId)>,
|
||||
pending: std::collections::BinaryHeap<(usize, WidgetId)>,
|
||||
pub(super) requests: crate::RequestArena,
|
||||
changed: Vec<WidgetId>,
|
||||
request_readers: HashMap<WidgetId, crate::util::HashSet<WidgetId>>,
|
||||
pub moves: Moves,
|
||||
}
|
||||
|
||||
@@ -108,6 +111,9 @@ impl UiRenderState {
|
||||
slots: Default::default(),
|
||||
deferred: Default::default(),
|
||||
pending: Default::default(),
|
||||
requests: Default::default(),
|
||||
changed: Vec::new(),
|
||||
request_readers: Default::default(),
|
||||
moves: Default::default(),
|
||||
resized: false,
|
||||
}
|
||||
@@ -194,6 +200,7 @@ impl UiRenderState {
|
||||
weak widgets: {all:#?}"
|
||||
);
|
||||
}
|
||||
self.requests.reset();
|
||||
let root = root.into();
|
||||
if self.root_changed(root) {
|
||||
self.redraw_all(root, rsc);
|
||||
@@ -211,8 +218,13 @@ impl UiRenderState {
|
||||
let _layout = diag::timer(TimerKind::FullLayout);
|
||||
self.clear(rsc);
|
||||
if let Some(id) = root {
|
||||
let ask =
|
||||
Placing::WINDOW.ask(rsc.widgets(), self.output_size, id.id(), PlaceDesc::WHOLE);
|
||||
let ask = Placing::WINDOW.ask(
|
||||
rsc.widgets(),
|
||||
&mut self.requests,
|
||||
self.output_size,
|
||||
id.id(),
|
||||
PlaceDesc::WHOLE,
|
||||
);
|
||||
let info = self.root_info(&ask, rsc.widgets().is_region_node(id.id()));
|
||||
self.draw_inner(id.id(), info, None, rsc);
|
||||
}
|
||||
@@ -327,7 +339,19 @@ impl UiRenderState {
|
||||
let mask_slot = old
|
||||
.as_ref()
|
||||
.and_then(|old| old.mask_region.map(|_| old.mask));
|
||||
let old_children = old.map_or_else(Vec::new, |old| old.children);
|
||||
let (mut old_children, textures, primitives, request_deps, mut scratch) = match old {
|
||||
Some(old) => (
|
||||
old.children,
|
||||
old.textures,
|
||||
old.primitives,
|
||||
old.request_deps,
|
||||
old.scratch,
|
||||
),
|
||||
None => Default::default(),
|
||||
};
|
||||
let children = std::mem::take(&mut scratch.children);
|
||||
let size_deps = std::mem::take(&mut scratch.size_deps);
|
||||
let under = std::mem::take(&mut scratch.under);
|
||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||
let window = self.output_size;
|
||||
let mut painter = Painter {
|
||||
@@ -339,17 +363,19 @@ impl UiRenderState {
|
||||
layer: info.layer,
|
||||
own_layer: info.layer,
|
||||
id,
|
||||
textures: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
textures,
|
||||
primitives,
|
||||
mask_region: None,
|
||||
mask_slot,
|
||||
children: Vec::new(),
|
||||
size_deps: Vec::new(),
|
||||
children,
|
||||
size_deps,
|
||||
request_deps,
|
||||
scratch,
|
||||
// What the ask holds for is part of what the drawing holds for:
|
||||
// a box the widget's own rule took past the offer was decided in
|
||||
// this window, and at the root nobody else keeps that range.
|
||||
own: info.ask_holds,
|
||||
under: Vec::new(),
|
||||
under,
|
||||
answer_under: LayoutHolds::ANY,
|
||||
depth: info.depth,
|
||||
move_idx,
|
||||
@@ -381,8 +407,10 @@ impl UiRenderState {
|
||||
own,
|
||||
answer_under,
|
||||
children,
|
||||
size_deps,
|
||||
under,
|
||||
mut size_deps,
|
||||
request_deps,
|
||||
mut scratch,
|
||||
mut under,
|
||||
move_idx,
|
||||
layer,
|
||||
own_layer: _,
|
||||
@@ -402,14 +430,19 @@ impl UiRenderState {
|
||||
// resolved into the rel base when the widget was asked, and resolving
|
||||
// it again here would take the fraction of a fraction.
|
||||
let rules = rsc.widgets().size_rules(id);
|
||||
let ruled = |axis: Axis, reported: LayoutLen| match rules[axis].exact() {
|
||||
None => reported,
|
||||
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
|
||||
rel: info.rel_base[axis].rel,
|
||||
px: info.rel_base[axis].px,
|
||||
leftover: Weight::ZERO,
|
||||
},
|
||||
Some(len) => len.within_len(info.rel_base[axis]),
|
||||
let ruled = |axis: Axis, reported: LayoutLen| {
|
||||
if matches!(rules[axis], crate::SizeRule::Request(_)) {
|
||||
return info.rel_base[axis].into();
|
||||
}
|
||||
match rules[axis].exact() {
|
||||
None => reported,
|
||||
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
|
||||
rel: info.rel_base[axis].rel,
|
||||
px: info.rel_base[axis].px,
|
||||
leftover: Weight::ZERO,
|
||||
},
|
||||
Some(len) => len.within_len(info.rel_base[axis]),
|
||||
}
|
||||
};
|
||||
let mut size = Size {
|
||||
x: ruled(Axis::X, size.x),
|
||||
@@ -483,8 +516,8 @@ impl UiRenderState {
|
||||
}
|
||||
let answer_holds = own_holds.and(answer_under);
|
||||
let holds = under
|
||||
.into_iter()
|
||||
.fold(answer_holds, |holds, (_, child)| holds.and(child));
|
||||
.iter()
|
||||
.fold(answer_holds, |holds, (_, child)| holds.and(*child));
|
||||
debug_assert!(
|
||||
holds.contains(self.output_size, info.rel_base, region),
|
||||
"'{}' ({id:?}) drew in {}, outside the ranges it reported: {holds:?}",
|
||||
@@ -520,6 +553,15 @@ impl UiRenderState {
|
||||
}
|
||||
}
|
||||
|
||||
for &dep in &request_deps {
|
||||
self.request_readers.entry(dep).or_default().insert(id);
|
||||
}
|
||||
old_children.clear();
|
||||
size_deps.clear();
|
||||
under.clear();
|
||||
scratch.children = old_children;
|
||||
scratch.size_deps = size_deps;
|
||||
scratch.under = under;
|
||||
let active = ActiveData {
|
||||
id,
|
||||
placement: region,
|
||||
@@ -539,6 +581,8 @@ impl UiRenderState {
|
||||
primitives,
|
||||
mask_region,
|
||||
children,
|
||||
request_deps,
|
||||
scratch,
|
||||
declared: info.declared,
|
||||
bounds: info.bounds,
|
||||
own_align: rsc.widgets().alignment(id),
|
||||
@@ -585,6 +629,7 @@ impl UiRenderState {
|
||||
if !active.drawn
|
||||
|| active.is_region_node() != info.region_node
|
||||
|| active.parent_move != info.parent_move
|
||||
|| active.bounds != info.bounds
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -841,6 +886,11 @@ impl UiRenderState {
|
||||
fn remove(&mut self, id: WidgetId, undraw: bool, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
|
||||
let mut active = self.active.remove(&id);
|
||||
if let Some(active) = &mut active {
|
||||
for dep in active.request_deps.drain(..) {
|
||||
if let Some(readers) = self.request_readers.get_mut(&dep) {
|
||||
readers.remove(&id);
|
||||
}
|
||||
}
|
||||
for primitive in &active.primitives {
|
||||
let mask = self.layers.free(&primitive.handle);
|
||||
if mask != MaskIdx::NONE {
|
||||
@@ -914,6 +964,8 @@ impl UiRenderState {
|
||||
primitives: Vec::new(),
|
||||
mask_region: None,
|
||||
children: Vec::new(),
|
||||
request_deps: Vec::new(),
|
||||
scratch: Default::default(),
|
||||
move_idx: info.parent_move,
|
||||
declared: Declared::NONE,
|
||||
bounds: Bounds::ANY,
|
||||
@@ -927,6 +979,7 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
fn clear(&mut self, rsc: &mut dyn UiRsc) {
|
||||
self.request_readers.clear();
|
||||
for (_, active) in self.active.drain() {
|
||||
if active.drawn {
|
||||
rsc.on_undraw(&active);
|
||||
@@ -947,6 +1000,7 @@ impl UiRenderState {
|
||||
rsc.on_remove(id);
|
||||
self.remove(id, true, rsc);
|
||||
self.drop_slot(id);
|
||||
self.request_readers.remove(&id);
|
||||
}
|
||||
rsc.ui_mut().textures.free();
|
||||
}
|
||||
@@ -954,6 +1008,18 @@ impl UiRenderState {
|
||||
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _layout = diag::timer(TimerKind::IncrementalLayout);
|
||||
self.changed.clear();
|
||||
self.changed
|
||||
.extend(rsc.widgets().needs_redraw.iter().copied());
|
||||
while let Some(id) = self.changed.pop() {
|
||||
if let Some(readers) = self.request_readers.get(&id) {
|
||||
for &reader in readers {
|
||||
if rsc.widgets_mut().needs_redraw.insert(reader) {
|
||||
self.changed.push(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deepest first, and strictly: a widget that cannot settle where it
|
||||
// is defers to its parent rather than drawing the parent from
|
||||
// inside itself. It marks the parent, stays marked, and waits here
|
||||
@@ -965,21 +1031,21 @@ impl UiRenderState {
|
||||
// something below is about to change it -- which is the whole class
|
||||
// of defect where a widget settles inside its parent's draw, clears
|
||||
// its mark there, and tells nobody its answer moved.
|
||||
// The queue is that set, ordered: a mark made while the walk runs
|
||||
// queues itself through `mark`. What ends the walk is still the set
|
||||
// A mark made while the walk runs queues itself through `mark`.
|
||||
// What ends the walk is still the set
|
||||
// being spent, not the queue, so a mark that reached it another way
|
||||
// cannot be left for the next frame.
|
||||
loop {
|
||||
for &id in rsc.widgets().needs_redraw.iter() {
|
||||
if !self.deferred.contains(&id) {
|
||||
let depth = self.depth(id);
|
||||
self.pending.insert((depth, id));
|
||||
self.pending.push((depth, id));
|
||||
}
|
||||
}
|
||||
if self.pending.is_empty() {
|
||||
break;
|
||||
}
|
||||
while let Some((depth, id)) = self.pending.pop_last() {
|
||||
while let Some((depth, id)) = self.pending.pop() {
|
||||
// Settled inside an ancestor's draw, or deferred to one,
|
||||
// since the mark that queued it.
|
||||
if self.deferred.contains(&id) || !rsc.widgets().needs_redraw.contains(&id) {
|
||||
@@ -990,7 +1056,7 @@ impl UiRenderState {
|
||||
// depth it had under the parent it left.
|
||||
let now = self.depth(id);
|
||||
if now != depth {
|
||||
self.pending.insert((now, id));
|
||||
self.pending.push((now, id));
|
||||
continue;
|
||||
}
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
@@ -1007,7 +1073,7 @@ impl UiRenderState {
|
||||
fn mark(&mut self, id: WidgetId, widgets: &mut Widgets) {
|
||||
if widgets.needs_redraw.insert(id) && !self.deferred.contains(&id) {
|
||||
let depth = self.depth(id);
|
||||
self.pending.insert((depth, id));
|
||||
self.pending.push((depth, id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1114,7 +1180,13 @@ impl UiRenderState {
|
||||
Some(parent) => self.placing_of(parent, self.active[&parent].region),
|
||||
None => Placing::WINDOW,
|
||||
};
|
||||
let ask = at.ask(rsc.widgets(), self.output_size, id, active.asked);
|
||||
let ask = at.ask(
|
||||
rsc.widgets(),
|
||||
&mut self.requests,
|
||||
self.output_size,
|
||||
id,
|
||||
active.asked,
|
||||
);
|
||||
let active = &self.active[&id];
|
||||
let declared_changed = ask.declared != active.declared;
|
||||
let alignment_changed = rsc.widgets().alignment(id) != active.own_align;
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::any::Any;
|
||||
mod data;
|
||||
mod handle;
|
||||
mod like;
|
||||
mod request;
|
||||
mod size_rule;
|
||||
mod tag;
|
||||
mod view;
|
||||
@@ -12,6 +13,7 @@ mod widgets;
|
||||
pub use data::*;
|
||||
pub use handle::*;
|
||||
pub use like::*;
|
||||
pub use request::*;
|
||||
pub use size_rule::*;
|
||||
pub use tag::*;
|
||||
pub use view::*;
|
||||
@@ -21,6 +23,12 @@ pub trait Widget: Any {
|
||||
/// Draws the widget, and returns what it used of the box it was given.
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size;
|
||||
|
||||
/// Describes an axis before painting. Return `None` when discovering it
|
||||
/// needs a concrete box or work performed by `draw`.
|
||||
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
||||
self.size_hint(axis).map(|len| requests.length(len))
|
||||
}
|
||||
|
||||
/// An exact length the widget can give without a painter or its children.
|
||||
/// Optional, and saves a draw rather than changing one: a hint that
|
||||
/// disagrees with the eventual draw fails a debug assertion.
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
use crate::{Axis, LayoutLen, Len, Px, StrongWidget, Weight, WidgetId, Widgets};
|
||||
|
||||
impl<N: crate::UiNum> From<N> for SizeRequest {
|
||||
fn from(value: N) -> Self {
|
||||
LayoutLen::px(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutLen {
|
||||
pub fn min(self, other: impl Into<SizeRequest>) -> SizeRequest {
|
||||
SizeRequest::from(self).min(other)
|
||||
}
|
||||
|
||||
pub fn max(self, other: impl Into<SizeRequest>) -> SizeRequest {
|
||||
SizeRequest::from(self).max(other)
|
||||
}
|
||||
|
||||
pub fn clamp(self, min: impl Into<SizeRequest>, max: impl Into<SizeRequest>) -> SizeRequest {
|
||||
SizeRequest::from(self).clamp(min, max)
|
||||
}
|
||||
}
|
||||
|
||||
/// A size request before a container has divided its leftover space.
|
||||
/// Comparisons keep both operands until the share is known.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SizeRequest {
|
||||
Linear(LayoutLen),
|
||||
Sum(std::sync::Arc<(Self, Self)>),
|
||||
Min(std::sync::Arc<(Self, Self)>),
|
||||
Max(std::sync::Arc<(Self, Self)>),
|
||||
}
|
||||
|
||||
impl From<LayoutLen> for SizeRequest {
|
||||
fn from(len: LayoutLen) -> Self {
|
||||
Self::Linear(len)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Len> for SizeRequest {
|
||||
fn from(len: Len) -> Self {
|
||||
LayoutLen::from(len).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl SizeRequest {
|
||||
pub fn min(self, other: impl Into<Self>) -> Self {
|
||||
let other = other.into();
|
||||
if let (Self::Linear(a), Self::Linear(b)) = (&self, &other)
|
||||
&& let Some(order) = independent_order(*a, *b)
|
||||
{
|
||||
return if !order.is_gt() { self } else { other };
|
||||
}
|
||||
if self == other {
|
||||
self
|
||||
} else {
|
||||
Self::Min(std::sync::Arc::new((self, other)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max(self, other: impl Into<Self>) -> Self {
|
||||
let other = other.into();
|
||||
if let (Self::Linear(a), Self::Linear(b)) = (&self, &other)
|
||||
&& let Some(order) = independent_order(*a, *b)
|
||||
{
|
||||
return if !order.is_lt() { self } else { other };
|
||||
}
|
||||
if self == other {
|
||||
self
|
||||
} else {
|
||||
Self::Max(std::sync::Arc::new((self, other)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp(self, min: impl Into<Self>, max: impl Into<Self>) -> Self {
|
||||
self.max(min).min(max)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for SizeRequest {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Self::Linear(a), Self::Linear(b)) => Self::Linear(a + b),
|
||||
(a, b) => Self::Sum(std::sync::Arc::new((a, b))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A discovered length. Deferred values belong to the current layout pass;
|
||||
/// widgets must not retain them. Ordinary requests remain inline lengths.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct RequestedLen(RequestValue);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum RequestValue {
|
||||
Linear(LayoutLen),
|
||||
Deferred {
|
||||
index: usize,
|
||||
epoch: u64,
|
||||
leftover: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<LayoutLen> for RequestedLen {
|
||||
fn from(len: LayoutLen) -> Self {
|
||||
Self(RequestValue::Linear(len))
|
||||
}
|
||||
}
|
||||
impl From<Len> for RequestedLen {
|
||||
fn from(len: Len) -> Self {
|
||||
LayoutLen::from(len).into()
|
||||
}
|
||||
}
|
||||
impl RequestedLen {
|
||||
pub fn linear(self) -> Option<LayoutLen> {
|
||||
match self.0 {
|
||||
RequestValue::Linear(len) => Some(len),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn has_leftover(self) -> bool {
|
||||
match self.0 {
|
||||
RequestValue::Linear(len) => len.leftover > Weight::ZERO,
|
||||
RequestValue::Deferred { leftover, .. } => leftover,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Op {
|
||||
Sum,
|
||||
Min,
|
||||
Max,
|
||||
}
|
||||
struct Node {
|
||||
op: Op,
|
||||
a: RequestedLen,
|
||||
b: RequestedLen,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct RequestArena {
|
||||
nodes: Vec<Node>,
|
||||
epoch: u64,
|
||||
}
|
||||
|
||||
impl RequestArena {
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.nodes.clear();
|
||||
self.epoch = self
|
||||
.epoch
|
||||
.checked_add(1)
|
||||
.expect("layout generation exhausted");
|
||||
}
|
||||
pub(crate) fn import(&mut self, request: &SizeRequest, base: Len) -> RequestedLen {
|
||||
let (op, pair) = match request {
|
||||
SizeRequest::Linear(len) => return len.within_len(base).into(),
|
||||
SizeRequest::Sum(pair) => (Op::Sum, pair),
|
||||
SizeRequest::Min(pair) => (Op::Min, pair),
|
||||
SizeRequest::Max(pair) => (Op::Max, pair),
|
||||
};
|
||||
let a = self.import(&pair.0, base);
|
||||
let b = self.import(&pair.1, base);
|
||||
self.combine(op, a, b)
|
||||
}
|
||||
fn combine(&mut self, op: Op, a: RequestedLen, b: RequestedLen) -> RequestedLen {
|
||||
if let (Some(x), Some(y)) = (a.linear(), b.linear()) {
|
||||
if matches!(op, Op::Sum) {
|
||||
return (x + y).into();
|
||||
}
|
||||
let order = independent_order(x, y);
|
||||
if let Some(order) = order {
|
||||
let take_a = match op {
|
||||
Op::Min => !order.is_gt(),
|
||||
_ => !order.is_lt(),
|
||||
};
|
||||
return if take_a { a } else { b };
|
||||
}
|
||||
}
|
||||
if a == b && !matches!(op, Op::Sum) {
|
||||
return a;
|
||||
}
|
||||
let index = self.nodes.len();
|
||||
self.nodes.push(Node { op, a, b });
|
||||
RequestedLen(RequestValue::Deferred {
|
||||
index,
|
||||
epoch: self.epoch,
|
||||
leftover: a.has_leftover() || b.has_leftover(),
|
||||
})
|
||||
}
|
||||
pub(crate) fn minimum(&self, request: RequestedLen, window: Px) -> Px {
|
||||
Px::from_raw(self.segment(request, Ratio::ZERO, window).fixed as i32)
|
||||
}
|
||||
fn segment(&self, request: RequestedLen, at: Ratio, window: Px) -> Segment {
|
||||
match request.0 {
|
||||
RequestValue::Linear(len) => {
|
||||
assert!(
|
||||
len.leftover >= Weight::ZERO,
|
||||
"a leftover weight cannot be negative"
|
||||
);
|
||||
Segment {
|
||||
fixed: i64::from(len.without_leftover().to_px(window).raw()),
|
||||
weight: i64::from(len.leftover.raw()),
|
||||
end: None,
|
||||
}
|
||||
}
|
||||
RequestValue::Deferred { index, epoch, .. } => {
|
||||
assert_eq!(epoch, self.epoch, "request retained beyond its layout pass");
|
||||
let Node { op, a, b } = self.nodes[index];
|
||||
let a = self.segment(a, at, window);
|
||||
let b = self.segment(b, at, window);
|
||||
if matches!(op, Op::Sum) {
|
||||
return a + b;
|
||||
}
|
||||
// At a crossing choose the branch to its right, so the next
|
||||
// iteration advances rather than selecting that crossing again.
|
||||
let order = a.value(at).cmp(&b.value(at)).then(a.weight.cmp(&b.weight));
|
||||
let take_a = match op {
|
||||
Op::Min => !order.is_gt(),
|
||||
_ => !order.is_lt(),
|
||||
};
|
||||
let mut selected = if take_a { a } else { b };
|
||||
selected.end = first(a.end, b.end);
|
||||
if a.weight != b.weight {
|
||||
let crossing = Ratio::new(b.fixed - a.fixed, a.weight - b.weight);
|
||||
if crossing > at {
|
||||
selected.end = first(selected.end, Some(crossing));
|
||||
}
|
||||
}
|
||||
selected
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Allocates one scope of nonnegative shares. Floors can overflow; caps
|
||||
/// can leave unused room. Prefix rounding keeps adjacent slot edges equal.
|
||||
pub(crate) fn allocate<'a>(
|
||||
&'a self,
|
||||
requests: &'a [RequestedLen],
|
||||
room: Px,
|
||||
window: Px,
|
||||
) -> impl Iterator<Item = Px> + 'a {
|
||||
let mut at = Ratio::ZERO;
|
||||
loop {
|
||||
let total = requests.iter().fold(Segment::ZERO, |total, request| {
|
||||
total + self.segment(*request, at, window)
|
||||
});
|
||||
if total.value(at) >= i128::from(room.raw()) * i128::from(at.den) {
|
||||
break;
|
||||
}
|
||||
if total.weight != 0 {
|
||||
let solution = Ratio::new(i64::from(room.raw()) - total.fixed, total.weight);
|
||||
if total.end.is_none_or(|end| solution <= end) {
|
||||
at = solution;
|
||||
break;
|
||||
}
|
||||
}
|
||||
match total.end {
|
||||
Some(end) => at = end,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
let mut prefix = 0_i128;
|
||||
let mut previous = 0_i128;
|
||||
requests.iter().map(move |request| {
|
||||
prefix += self.segment(*request, at, window).value(at);
|
||||
let den = i128::from(at.den);
|
||||
let edge = prefix.signum() * ((prefix.abs() + den / 2) / den);
|
||||
let len = Px::from_raw((edge - previous) as i32);
|
||||
previous = edge;
|
||||
len
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq)]
|
||||
struct Ratio {
|
||||
num: i64,
|
||||
den: i64,
|
||||
}
|
||||
|
||||
impl PartialEq for Ratio {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cmp(other).is_eq()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ratio {
|
||||
const ZERO: Self = Self { num: 0, den: 1 };
|
||||
|
||||
fn new(num: i64, den: i64) -> Self {
|
||||
assert_ne!(den, 0);
|
||||
if den < 0 {
|
||||
Self {
|
||||
num: -num,
|
||||
den: -den,
|
||||
}
|
||||
} else {
|
||||
Self { num, den }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Ratio {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(i128::from(self.num) * i128::from(other.den))
|
||||
.cmp(&(i128::from(other.num) * i128::from(self.den)))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Ratio {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Segment {
|
||||
fixed: i64,
|
||||
weight: i64,
|
||||
end: Option<Ratio>,
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
const ZERO: Self = Self {
|
||||
fixed: 0,
|
||||
weight: 0,
|
||||
end: None,
|
||||
};
|
||||
|
||||
fn value(self, at: Ratio) -> i128 {
|
||||
i128::from(self.fixed) * i128::from(at.den) + i128::from(self.weight) * i128::from(at.num)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Segment {
|
||||
type Output = Self;
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
fixed: self.fixed + other.fixed,
|
||||
weight: self.weight + other.weight,
|
||||
end: first(self.end, other.end),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn first(a: Option<Ratio>, b: Option<Ratio>) -> Option<Ratio> {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => Some(a.min(b)),
|
||||
(a, b) => a.or(b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only discovery of requests through a widget's children. A request is
|
||||
/// expressed in window lengths; `rel_base` supplies the base for declarations.
|
||||
pub struct SizeRequests<'a> {
|
||||
pub(crate) arena: &'a mut RequestArena,
|
||||
pub(crate) measured: Option<&'a crate::util::HashMap<WidgetId, crate::ActiveData>>,
|
||||
pub(crate) widgets: &'a Widgets,
|
||||
pub(crate) dependencies: &'a mut Vec<WidgetId>,
|
||||
pub(crate) rel_base: Len,
|
||||
}
|
||||
|
||||
impl SizeRequests<'_> {
|
||||
pub fn sum(&mut self, a: RequestedLen, b: RequestedLen) -> RequestedLen {
|
||||
self.arena.combine(Op::Sum, a, b)
|
||||
}
|
||||
pub fn min(&mut self, a: RequestedLen, b: RequestedLen) -> RequestedLen {
|
||||
self.arena.combine(Op::Min, a, b)
|
||||
}
|
||||
pub fn max(&mut self, a: RequestedLen, b: RequestedLen) -> RequestedLen {
|
||||
self.arena.combine(Op::Max, a, b)
|
||||
}
|
||||
|
||||
pub fn widget<W: ?Sized>(
|
||||
&mut self,
|
||||
child: &StrongWidget<W>,
|
||||
axis: Axis,
|
||||
) -> Option<RequestedLen> {
|
||||
self.dependencies.push(child.id());
|
||||
let rules = self.widgets.size_rules(child.id());
|
||||
let rule = &rules[axis];
|
||||
if let crate::SizeRule::Request(request) = rule {
|
||||
return Some(self.arena.import(request, self.rel_base));
|
||||
}
|
||||
if let Some(exact) = rule.exact() {
|
||||
return Some(exact.within_len(self.rel_base).into());
|
||||
}
|
||||
let widget = self.widgets.get_dyn(child.id())?;
|
||||
let request = widget.size_request(self, axis).or_else(|| {
|
||||
self.measured?
|
||||
.get(&child.id())?
|
||||
.measured()
|
||||
.map(|size| size[axis].into())
|
||||
})?;
|
||||
Some(self.bounded(request, rule.bound()))
|
||||
}
|
||||
|
||||
pub(crate) fn bounded(&mut self, request: RequestedLen, bound: crate::Bound) -> RequestedLen {
|
||||
let bound = bound.within_len(self.rel_base);
|
||||
let request = match bound.min {
|
||||
Some(min) => self.max(request, min.into()),
|
||||
None => request,
|
||||
};
|
||||
match bound.max {
|
||||
Some(max) => self.min(request, max.into()),
|
||||
None => request,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn length(&self, len: LayoutLen) -> RequestedLen {
|
||||
len.within_len(self.rel_base).into()
|
||||
}
|
||||
|
||||
pub fn inset<W: ?Sized>(
|
||||
&mut self,
|
||||
child: &StrongWidget<W>,
|
||||
axis: Axis,
|
||||
padding: Px,
|
||||
) -> Option<RequestedLen> {
|
||||
let base = self.rel_base;
|
||||
self.rel_base.px -= padding;
|
||||
let request = self.widget(child, axis);
|
||||
self.rel_base = base;
|
||||
request.map(|request| self.sum(request, Len::from_parts(crate::Rel::ZERO, padding).into()))
|
||||
}
|
||||
}
|
||||
|
||||
// Equal fractions keep this valid even when padding makes a rel base negative.
|
||||
fn independent_order(a: LayoutLen, b: LayoutLen) -> Option<std::cmp::Ordering> {
|
||||
if a.rel == b.rel && a.leftover == b.leftover {
|
||||
Some(a.px.cmp(&b.px))
|
||||
} else if a.px == b.px && a.rel == b.rel {
|
||||
Some(a.leftover.cmp(&b.leftover))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Axis, LayoutLen, Len, Rel};
|
||||
use crate::{Axis, LayoutLen, Len, Rel, SizeRequest};
|
||||
|
||||
/// What a widget's length on one axis is, as a rule its parent applies where
|
||||
/// it draws it rather than an answer the widget gives about itself.
|
||||
@@ -10,16 +10,17 @@ use crate::{Axis, LayoutLen, Len, Rel};
|
||||
/// nobody has drawn yet, and it is why a rule lives beside the widget rather
|
||||
/// than inside it -- the widget under the rule never has to know about it.
|
||||
///
|
||||
/// A rule gives a length or bounds one, never both: a share that is also
|
||||
/// capped wants two widgets, one taking the share and one capping what is
|
||||
/// inside it.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
/// Exact expressions can bound a share before allocation. Bounds on an
|
||||
/// intrinsic answer are applied after that answer becomes known.
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub enum SizeRule {
|
||||
/// Whatever the widget reports from drawing.
|
||||
#[default]
|
||||
Free,
|
||||
/// This length, whatever the widget reports.
|
||||
Exact(LayoutLen),
|
||||
/// An exact request whose comparisons await the parent's allocation.
|
||||
Request(std::sync::Arc<SizeRequest>),
|
||||
/// At least this long, and otherwise whatever the box gives it.
|
||||
Min(Len),
|
||||
/// At most this long.
|
||||
@@ -33,7 +34,7 @@ impl SizeRule {
|
||||
/// outright.
|
||||
pub fn bound(&self) -> Bound {
|
||||
match *self {
|
||||
Self::Free | Self::Exact(_) => Bound::ANY,
|
||||
Self::Free | Self::Exact(_) | Self::Request(_) => Bound::ANY,
|
||||
Self::Min(min) => Bound {
|
||||
min: Some(min),
|
||||
max: None,
|
||||
@@ -66,10 +67,8 @@ impl SizeRule {
|
||||
match *self {
|
||||
Self::Free | Self::Min(_) => Self::Min(min),
|
||||
Self::Max(max) | Self::Clamp { max, .. } => Self::Clamp { min, max },
|
||||
Self::Exact(len) => {
|
||||
debug_assert!(false, "{len:?} is a length, so bounding it says nothing");
|
||||
Self::Min(min)
|
||||
}
|
||||
Self::Request(ref request) => request.as_ref().clone().max(min).into(),
|
||||
Self::Exact(len) => len.max(min).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +78,8 @@ impl SizeRule {
|
||||
match *self {
|
||||
Self::Free | Self::Max(_) => Self::Max(max),
|
||||
Self::Min(min) | Self::Clamp { min, .. } => Self::Clamp { min, max },
|
||||
Self::Exact(len) => {
|
||||
debug_assert!(false, "{len:?} is a length, so bounding it says nothing");
|
||||
Self::Max(max)
|
||||
}
|
||||
Self::Request(ref request) => request.as_ref().clone().min(max).into(),
|
||||
Self::Exact(len) => len.min(max).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,20 +97,18 @@ impl SizeRule {
|
||||
pub fn exact(&self) -> Option<LayoutLen> {
|
||||
match self {
|
||||
Self::Exact(len) => Some(*len),
|
||||
Self::Free | Self::Min(_) | Self::Max(_) | Self::Clamp { .. } => None,
|
||||
Self::Free | Self::Request(_) | Self::Min(_) | Self::Max(_) | Self::Clamp { .. } => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What a rule allows a length to be where it does not give one outright: a
|
||||
/// floor, a cap, or both. Each is a length of the rel base the widget is
|
||||
/// asked with, which is the base a declared length is a fraction of too, and
|
||||
/// a bound that binds is a declaration -- the box comes to what it says.
|
||||
/// asked with, which is the base a declared length is a fraction of too.
|
||||
///
|
||||
/// A bound is a [`Len`] and never a share. Which of a fixed and a relative
|
||||
/// child is longer, asked at the length the cap is itself deciding, admits
|
||||
/// several self-sizing fixed points, so a cap containing `leftover` has no
|
||||
/// one answer: see `docs/LAYOUT.md` under the failed hypotheses.
|
||||
/// A bound is a [`Len`]. Comparisons involving shares are [`SizeRequest`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Bound {
|
||||
pub min: Option<Len>,
|
||||
@@ -183,6 +178,15 @@ impl From<LayoutLen> for SizeRule {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SizeRequest> for SizeRule {
|
||||
fn from(request: SizeRequest) -> Self {
|
||||
match request {
|
||||
SizeRequest::Linear(len) => Self::Exact(len),
|
||||
request => Self::Request(std::sync::Arc::new(request)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<LayoutLen>> for SizeRule {
|
||||
fn from(len: Option<LayoutLen>) -> Self {
|
||||
len.map_or(Self::Free, Self::Exact)
|
||||
@@ -191,7 +195,7 @@ impl From<Option<LayoutLen>> for SizeRule {
|
||||
|
||||
/// One rule per axis, which is how a widget carries a length on one axis and
|
||||
/// leaves the other to whatever it draws.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct SizeRules {
|
||||
pub x: SizeRule,
|
||||
pub y: SizeRule,
|
||||
|
||||
@@ -129,7 +129,7 @@ impl Widgets {
|
||||
|
||||
/// The length rules whoever draws this widget applies to its box.
|
||||
pub fn size_rules(&self, id: impl IdLike) -> SizeRules {
|
||||
self.data(id).unwrap().size
|
||||
self.data(id).unwrap().size.clone()
|
||||
}
|
||||
|
||||
/// Sets one axis's rule. The widget is marked rather than its parent
|
||||
|
||||
Reference in new issue
Block a user