Make alignment a widget property
This commit is contained in:
1 parent
8220a78d4a
commit
d3b0ebf90c
21 files changed
+823
-300
No files matched your search
+99
-28
@@ -1,8 +1,8 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Holds, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId, Widgets,
|
||||
Axis, Holds, Len, RegionAlign, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer,
|
||||
TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
||||
PrimitiveKind, TexturePrimitive,
|
||||
@@ -99,16 +99,7 @@ impl<'a> Painter<'a> {
|
||||
|
||||
/// Draws a widget within this widget's region.
|
||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at(id, UiRegion::FULL)
|
||||
}
|
||||
|
||||
/// Draws a widget somewhere within this one.
|
||||
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)
|
||||
self.widget_within(id, UiRegion::FULL)
|
||||
}
|
||||
|
||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||
@@ -127,19 +118,45 @@ impl<'a> Painter<'a> {
|
||||
self.state.undraw_rec(id.id(), self.rsc);
|
||||
}
|
||||
|
||||
/// `region` in this widget's own coordinates, and with the child's
|
||||
/// declared lengths still to be taken.
|
||||
fn widget_at<'s, W: ?Sized>(
|
||||
/// Draws a widget somewhere within this one. `region` is in this widget's
|
||||
/// own coordinates, and the child's declared lengths are still to be
|
||||
/// taken from it. Where the child's drawing sits inside what it is given
|
||||
/// is the child's alignment, applied where the child is drawn, so a
|
||||
/// container positions a child either by handing it a box of exactly its
|
||||
/// length or by leaving it room and letting its alignment decide.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Draws a widget with an alignment chosen by its container rather than
|
||||
/// the widget's property. Containers use this when the box they hand down
|
||||
/// already expresses the size they report around the child.
|
||||
pub fn widget_aligned<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
align: RegionAlign,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at(id, region, Some(align))
|
||||
}
|
||||
|
||||
fn widget_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
align_override: Option<RegionAlign>,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||
let declared = self.declared_lens(id);
|
||||
let align = align_override.unwrap_or_else(|| self.rsc.widgets().alignment(id.id()));
|
||||
// Composing `FULL` through a box is not quite the identity in f32,
|
||||
// so a child with nothing declared keeps the box it would have had.
|
||||
let local = match declared.iter().any(Option::is_some) {
|
||||
true => declared_box(region, declared),
|
||||
true => declared_box(region, declared, align),
|
||||
false => region,
|
||||
};
|
||||
let within = match local == UiRegion::FULL {
|
||||
@@ -161,7 +178,10 @@ impl<'a> Painter<'a> {
|
||||
false => self.state.active.get(&id.id()).map_or(local, |a| a.offer),
|
||||
};
|
||||
let answers_offer = self.at_offer && local == offer;
|
||||
let size = self.state.draw_inner(
|
||||
// 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, holds) = self.state.draw_inner(
|
||||
id.id(),
|
||||
within,
|
||||
DrawInfo {
|
||||
@@ -173,19 +193,18 @@ impl<'a> Painter<'a> {
|
||||
mask: self.mask,
|
||||
offer,
|
||||
offered_px: self.px_within_offer(offer),
|
||||
align: align_override,
|
||||
},
|
||||
None,
|
||||
self.rsc,
|
||||
);
|
||||
let active = self.state.active.get_mut(&id.id()).unwrap();
|
||||
active.declared = declared;
|
||||
if answers_offer {
|
||||
active.answer = (active.size, active.holds);
|
||||
self.state.active.get_mut(&id.id()).unwrap().answer = (size, holds);
|
||||
}
|
||||
// Whatever the child's drawing holds for keeps this one to the boxes
|
||||
// Whatever the child's answer holds for keeps this one to the boxes
|
||||
// that give the child a length inside it.
|
||||
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
||||
*under = under.and(active.holds[axis as usize].through(local.axis(axis).len()));
|
||||
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
||||
}
|
||||
DrawResult {
|
||||
child: id,
|
||||
@@ -232,7 +251,8 @@ impl<'a> Painter<'a> {
|
||||
region: UiRegion,
|
||||
) -> Option<Len> {
|
||||
let declared = self.declared_lens(child);
|
||||
let local = declared_box(region, declared);
|
||||
let align = self.rsc.widgets().alignment(child.id());
|
||||
let local = declared_box(region, declared, align);
|
||||
let within = local.within(&self.region);
|
||||
let first_ask = self.offer(child.id());
|
||||
if first_ask && let Some(active) = self.state.active.get_mut(&child.id()) {
|
||||
@@ -326,6 +346,20 @@ impl<'a> Painter<'a> {
|
||||
self.region
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
placed_box(UiRegion::FULL, size, RegionAlign::NEAR, [None; 2])
|
||||
}
|
||||
|
||||
/// This widget's 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) -> Vec2 {
|
||||
@@ -455,15 +489,52 @@ pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<Len>; 2]
|
||||
})
|
||||
}
|
||||
|
||||
/// The box a drawing occupies: the size the widget reported, on the side of
|
||||
/// the box it was asked in that its alignment says. An axis reported as 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 is
|
||||
/// left alone too: `declared_box` already placed it, in the parent's box,
|
||||
/// and the rule's length is what the widget reports there.
|
||||
///
|
||||
/// 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 scales by the box rather than composing into it.
|
||||
pub(crate) fn placed_box(
|
||||
region: UiRegion,
|
||||
size: Size,
|
||||
align: RegionAlign,
|
||||
declared: [Option<Len>; 2],
|
||||
) -> UiRegion {
|
||||
let mut placed = region;
|
||||
for (axis, declared) in AXES.into_iter().zip(declared) {
|
||||
let reported = size.axis(axis);
|
||||
if reported.leftover != 0.0 || declared.is_some() {
|
||||
continue;
|
||||
}
|
||||
let span = placed.axis_mut(axis);
|
||||
let len = span.len().scale(reported.rel) + UiScalar::px(reported.px);
|
||||
span.start += (span.len() - len).scale(align.axis(axis).rel());
|
||||
span.end = span.start + len;
|
||||
}
|
||||
placed
|
||||
}
|
||||
|
||||
/// Takes a widget's declared lengths in the box `region` is given in, since a
|
||||
/// fraction of a length means a fraction of that one. A caller that already
|
||||
/// reserved the space hands back the same length, so this is the identity
|
||||
/// for it.
|
||||
fn declared_box(mut region: UiRegion, declared: [Option<Len>; 2]) -> UiRegion {
|
||||
/// fraction of a length means a fraction of that one, and puts what is left
|
||||
/// over on the side its alignment says. A caller that already reserved the
|
||||
/// space hands back the same length, so this is the identity for it.
|
||||
pub(crate) fn declared_box(
|
||||
mut region: UiRegion,
|
||||
declared: [Option<Len>; 2],
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
for (axis, len) in AXES.into_iter().zip(declared) {
|
||||
let Some(len) = len else { continue };
|
||||
let span = region.axis_mut(axis);
|
||||
span.end = span.start + UiScalar::new(len.rel, len.px);
|
||||
let len = UiScalar::new(len.rel, len.px);
|
||||
span.start += (span.len() - len).scale(align.axis(axis).rel());
|
||||
span.end = span.start + len;
|
||||
}
|
||||
region
|
||||
}
|
||||
Reference in new issue
Block a user