Size a widget while drawing it, not in a pass of its own
`desired_width`/`desired_height`, `WidgetAxisFns`, `SizeCtx` and the size cache are gone. A widget states what it used with `Painter::set_size` while it draws, and `Painter::widget` hands back a `DrawResult` whose `size()` both reads the child and records that this widget's size depends on it. Reading nothing keeps the parent independent of what the child came to. `Span` is what the change is for. It takes each child's `size_hint` where there is one, draws only the children that cannot answer, allocates the flexible space, then places everything -- which deletes `desired_ortho`, whose own comment said it "literally copies draw ... which makes this slow and not cool". Invalidation follows the dependency edges the draw recorded: a widget that needs redrawing hands off to the highest ancestor that read its size, instead of re-running a measurement to find out whether anything changed. `Widget::size_dependence(axis)` says how much of its box a widget's drawing depends on -- none of it, its own extent, or the whole box -- so the retained path can keep a drawing and write a new box into it. Asked per axis, because wrapped text depends on the width it is offered and not on the height. `Internal` does not yet buy more than `External`: keeping a drawing when only the room around it changed is a translation, which waits for the move chain. `tests/retained.rs` covers the second frame rather than the first, which is where the bugs were: a placed child that was not recorded as one got pruned as departed on the next draw. `examples/text.rs` is new, since wrapping was the one thing here with no way to see it on its own.
This commit is contained in:
1 parent
43ce8c7d02
commit
f192f75b25
24 files changed
+606
-519
No files matched your search
@@ -1,14 +1,18 @@
|
|||||||
use crate::{LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
|
use crate::{LayerId, MaskIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId};
|
||||||
|
|
||||||
/// important non rendering data for retained drawing
|
/// important non rendering data for retained drawing
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ActiveData {
|
pub struct ActiveData {
|
||||||
pub id: WidgetId,
|
pub id: WidgetId,
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
|
/// What the widget said it used of `region`, the last time it drew.
|
||||||
|
pub size: Size,
|
||||||
pub parent: Option<WidgetId>,
|
pub parent: Option<WidgetId>,
|
||||||
pub textures: Vec<TextureHandle>,
|
pub textures: Vec<TextureHandle>,
|
||||||
pub primitives: Vec<PrimitiveHandle>,
|
pub primitives: Vec<PrimitiveHandle>,
|
||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
|
/// The children whose size this widget read while drawing.
|
||||||
|
pub size_deps: Vec<WidgetId>,
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
pub layer: LayerId,
|
pub layer: LayerId,
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
use crate::{BothAxis, Len, UiVec2, WidgetId, util::HashMap};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Cache {
|
|
||||||
pub size: BothAxis<HashMap<WidgetId, (UiVec2, Len)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cache {
|
|
||||||
pub fn remove(&mut self, id: WidgetId) {
|
|
||||||
self.size.x.remove(&id);
|
|
||||||
self.size.y.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.size.x.clear();
|
|
||||||
self.size.y.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,15 +3,12 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod active;
|
mod active;
|
||||||
mod cache;
|
|
||||||
mod painter;
|
mod painter;
|
||||||
mod render_state;
|
mod render_state;
|
||||||
mod size;
|
|
||||||
|
|
||||||
pub use active::*;
|
pub use active::*;
|
||||||
pub use painter::{Painter, PrimitiveLike};
|
pub use painter::{Painter, PrimitiveLike};
|
||||||
pub use render_state::*;
|
pub use render_state::*;
|
||||||
pub use size::*;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UiData {
|
pub struct UiData {
|
||||||
|
|||||||
+87
-21
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
||||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId,
|
||||||
render::{
|
render::{
|
||||||
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
||||||
TexturePrimitive,
|
TexturePrimitive,
|
||||||
@@ -18,6 +18,9 @@ pub struct Painter<'a> {
|
|||||||
pub(super) textures: Vec<TextureHandle>,
|
pub(super) textures: Vec<TextureHandle>,
|
||||||
pub(super) primitives: Vec<PrimitiveHandle>,
|
pub(super) primitives: Vec<PrimitiveHandle>,
|
||||||
pub(super) children: Vec<WidgetId>,
|
pub(super) children: Vec<WidgetId>,
|
||||||
|
/// The children whose size this widget read while drawing.
|
||||||
|
pub(super) size_deps: Vec<WidgetId>,
|
||||||
|
pub(super) size: Option<Size>,
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub(super) id: WidgetId,
|
pub(super) id: WidgetId,
|
||||||
}
|
}
|
||||||
@@ -67,20 +70,40 @@ impl<'a> Painter<'a> {
|
|||||||
self.mask = self.rsc.ui_mut().masks.push(Mask { region });
|
self.mask = self.rsc.ui_mut().masks.push(Mask { region });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// States what this widget uses of the box it was handed, once per draw.
|
||||||
|
/// This is the size a parent reads.
|
||||||
|
pub fn set_size(&mut self, size: impl Into<Size>) {
|
||||||
|
debug_assert!(
|
||||||
|
self.size.is_none(),
|
||||||
|
"{} set its size twice in one draw",
|
||||||
|
self.label(),
|
||||||
|
);
|
||||||
|
self.size = Some(size.into());
|
||||||
|
}
|
||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
/// Draws a widget within this widget's region.
|
||||||
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
|
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) -> DrawResult<'_, 'a> {
|
||||||
self.widget_at(id, self.region);
|
self.widget_at(id, self.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget somewhere within this one.
|
/// Draws a widget somewhere within this one.
|
||||||
/// Useful for drawing child widgets in select areas.
|
/// Useful for drawing child widgets in select areas.
|
||||||
pub fn widget_within<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
pub fn widget_within<W: ?Sized>(
|
||||||
self.widget_at(id, region.within(&self.region));
|
&mut self,
|
||||||
|
id: &StrongWidget<W>,
|
||||||
|
region: UiRegion,
|
||||||
|
) -> DrawResult<'_, 'a> {
|
||||||
|
let region = region.within(&self.region);
|
||||||
|
self.widget_at(id, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
fn widget_at<W: ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
id: &StrongWidget<W>,
|
||||||
|
region: UiRegion,
|
||||||
|
) -> DrawResult<'_, 'a> {
|
||||||
self.children.push(id.id());
|
self.children.push(id.id());
|
||||||
self.state.draw_inner(
|
let size = self.state.draw_inner(
|
||||||
self.layer,
|
self.layer,
|
||||||
id.id(),
|
id.id(),
|
||||||
region,
|
region,
|
||||||
@@ -89,6 +112,44 @@ impl<'a> Painter<'a> {
|
|||||||
None,
|
None,
|
||||||
self.rsc,
|
self.rsc,
|
||||||
);
|
);
|
||||||
|
DrawResult {
|
||||||
|
child: id.id(),
|
||||||
|
painter: self,
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What a child says its length is without being drawn, if it can say.
|
||||||
|
pub fn size_hint<W: ?Sized>(&self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
||||||
|
self.rsc.widgets().get_dyn(id.id())?.size_hint(axis)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives a child its final box once this widget knows what that is,
|
||||||
|
/// keeping the drawing it already has where it can.
|
||||||
|
pub fn place<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
|
||||||
|
let region = region.within(&self.region);
|
||||||
|
let id = id.id();
|
||||||
|
// Choosing a child's box depends on its size, from a draw or a hint.
|
||||||
|
// Claiming it is separate: one left out of `children` is removed.
|
||||||
|
if !self.size_deps.contains(&id) {
|
||||||
|
self.size_deps.push(id);
|
||||||
|
}
|
||||||
|
if !self.children.contains(&id) {
|
||||||
|
self.children.push(id);
|
||||||
|
}
|
||||||
|
if self.state.active.contains_key(&id) {
|
||||||
|
return self.state.place(id, region, self.rsc);
|
||||||
|
}
|
||||||
|
// Not drawn yet: its length came from a hint, so this is its one draw.
|
||||||
|
self.state.draw_inner(
|
||||||
|
self.layer,
|
||||||
|
id,
|
||||||
|
region,
|
||||||
|
Some(self.id),
|
||||||
|
self.mask,
|
||||||
|
None,
|
||||||
|
self.rsc,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_text(
|
pub fn render_text(
|
||||||
@@ -129,17 +190,6 @@ impl<'a> Painter<'a> {
|
|||||||
self.region
|
self.region
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>) -> Size {
|
|
||||||
self.size_ctx().size(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.size_ctx().width(id),
|
|
||||||
Axis::Y => self.size_ctx().height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_size(&self) -> Vec2 {
|
pub fn output_size(&self) -> Vec2 {
|
||||||
self.state.output_size
|
self.state.output_size
|
||||||
}
|
}
|
||||||
@@ -167,9 +217,25 @@ impl<'a> Painter<'a> {
|
|||||||
pub fn id(&self) -> &WidgetId {
|
pub fn id(&self) -> &WidgetId {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
|
/// A child that has just been drawn. Reading its size records that this
|
||||||
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
/// 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> {
|
||||||
|
painter: &'p mut Painter<'a>,
|
||||||
|
child: WidgetId,
|
||||||
|
size: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawResult<'_, '_> {
|
||||||
|
pub fn size(self) -> Size {
|
||||||
|
self.painter.size_deps.push(self.child);
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(self, axis: Axis) -> Len {
|
||||||
|
self.size().axis(axis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+124
-47
@@ -1,7 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, SizeCtx, StrongWidget,
|
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, Size, SizeDependence,
|
||||||
UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||||
ui::cache::Cache,
|
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -9,7 +8,6 @@ pub struct UiRenderState {
|
|||||||
pub active: HashMap<WidgetId, ActiveData>,
|
pub active: HashMap<WidgetId, ActiveData>,
|
||||||
pub layers: DrawLayers,
|
pub layers: DrawLayers,
|
||||||
pub(super) output_size: Vec2,
|
pub(super) output_size: Vec2,
|
||||||
pub cache: Cache,
|
|
||||||
|
|
||||||
old_root: Option<WidgetId>,
|
old_root: Option<WidgetId>,
|
||||||
resized: bool,
|
resized: bool,
|
||||||
@@ -21,7 +19,6 @@ impl UiRenderState {
|
|||||||
Self {
|
Self {
|
||||||
active: Default::default(),
|
active: Default::default(),
|
||||||
layers: Default::default(),
|
layers: Default::default(),
|
||||||
cache: Default::default(),
|
|
||||||
output_size: Vec2::ZERO,
|
output_size: Vec2::ZERO,
|
||||||
old_root: None,
|
old_root: None,
|
||||||
resized: false,
|
resized: false,
|
||||||
@@ -84,19 +81,11 @@ impl UiRenderState {
|
|||||||
mask: MaskIdx,
|
mask: MaskIdx,
|
||||||
old_children: Option<Vec<WidgetId>>,
|
old_children: Option<Vec<WidgetId>>,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
) {
|
) -> Size {
|
||||||
let mut old_children = old_children.unwrap_or_default();
|
let mut old_children = old_children.unwrap_or_default();
|
||||||
if let Some(active) = self.active.get_mut(&id)
|
if self.active.contains_key(&id) {
|
||||||
&& !rsc.widgets().needs_redraw.contains(&id)
|
if let Some(size) = self.try_reuse(id, region, rsc) {
|
||||||
{
|
return size;
|
||||||
// check to see if we can skip drawing first
|
|
||||||
if active.region == region {
|
|
||||||
return;
|
|
||||||
} else if active.region.size() == region.size() {
|
|
||||||
// TODO: epsilon?
|
|
||||||
let from = active.region;
|
|
||||||
self.mov(id, from, region);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// if not, then maintain resize and track old children to remove unneeded
|
// if not, then maintain resize and track old children to remove unneeded
|
||||||
let active = self.remove(id, false, rsc).unwrap();
|
let active = self.remove(id, false, rsc).unwrap();
|
||||||
@@ -115,6 +104,8 @@ impl UiRenderState {
|
|||||||
textures: Vec::new(),
|
textures: Vec::new(),
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
size_deps: Vec::new(),
|
||||||
|
size: None,
|
||||||
rsc,
|
rsc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,18 +121,34 @@ impl UiRenderState {
|
|||||||
textures,
|
textures,
|
||||||
primitives,
|
primitives,
|
||||||
children,
|
children,
|
||||||
|
size_deps,
|
||||||
|
size,
|
||||||
layer,
|
layer,
|
||||||
id,
|
id,
|
||||||
} = painter;
|
} = painter;
|
||||||
|
|
||||||
|
let size = size.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"'{}' ({id:?}) drew without a size; every widget calls Painter::set_size",
|
||||||
|
rsc.widgets().label(id)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
debug_assert!(
|
||||||
|
Self::hints_agree(id, size, rsc),
|
||||||
|
"'{}' ({id:?}) drew a size its size_hint disagrees with",
|
||||||
|
rsc.widgets().label(id)
|
||||||
|
);
|
||||||
|
|
||||||
// add to active
|
// add to active
|
||||||
let active = ActiveData {
|
let active = ActiveData {
|
||||||
id,
|
id,
|
||||||
region,
|
region,
|
||||||
|
size,
|
||||||
parent,
|
parent,
|
||||||
textures,
|
textures,
|
||||||
primitives,
|
primitives,
|
||||||
children,
|
children,
|
||||||
|
size_deps,
|
||||||
mask,
|
mask,
|
||||||
layer,
|
layer,
|
||||||
};
|
};
|
||||||
@@ -155,6 +162,86 @@ impl UiRenderState {
|
|||||||
|
|
||||||
rsc.on_draw(&active);
|
rsc.on_draw(&active);
|
||||||
self.active.insert(id, active);
|
self.active.insert(id, active);
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives an already drawn widget a new box, keeping its drawing if it can
|
||||||
|
/// and drawing it again if it cannot.
|
||||||
|
pub(super) fn place(&mut self, id: WidgetId, region: UiRegion, rsc: &mut dyn UiRsc) -> Size {
|
||||||
|
if let Some(size) = self.try_reuse(id, region, rsc) {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
let Some(active) = self.remove(id, false, rsc) else {
|
||||||
|
return Size::ZERO;
|
||||||
|
};
|
||||||
|
self.draw_inner(
|
||||||
|
active.layer,
|
||||||
|
id,
|
||||||
|
region,
|
||||||
|
active.parent,
|
||||||
|
active.mask,
|
||||||
|
Some(active.children),
|
||||||
|
rsc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The drawing a widget already has, kept for a new box if the box has not
|
||||||
|
/// changed in a way it depends on.
|
||||||
|
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
|
||||||
|
if rsc.widgets().needs_redraw.contains(&id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let active = self.active.get(&id)?;
|
||||||
|
let (size, was) = (active.size, active.region);
|
||||||
|
if was == region {
|
||||||
|
return Some(size);
|
||||||
|
}
|
||||||
|
// TODO: epsilon?
|
||||||
|
if was.size() == region.size() {
|
||||||
|
self.mov(id, was, region);
|
||||||
|
return Some(size);
|
||||||
|
}
|
||||||
|
if self.reusable(id, region, rsc) {
|
||||||
|
// Its drawing stands; the box is written into the primitives.
|
||||||
|
self.mov(id, was, region);
|
||||||
|
return Some(size);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the widget can keep the drawing it has and be given `region`
|
||||||
|
/// instead, asked one axis at a time: a change on an axis it does not
|
||||||
|
/// depend on costs nothing, whatever it depends on elsewhere.
|
||||||
|
fn reusable(&self, id: WidgetId, mut region: UiRegion, rsc: &dyn UiRsc) -> bool {
|
||||||
|
let Some(active) = self.active.get(&id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let mut was = active.region;
|
||||||
|
[Axis::X, Axis::Y].into_iter().all(|axis| {
|
||||||
|
let offered = region.axis_mut(axis).len();
|
||||||
|
let had = was.axis_mut(axis).len();
|
||||||
|
match widget.size_dependence(axis) {
|
||||||
|
SizeDependence::None => true,
|
||||||
|
// `Internal` could also keep its drawing when only the room
|
||||||
|
// around it changed, but that is a translation rather than a
|
||||||
|
// remap, so it waits for the move chain.
|
||||||
|
SizeDependence::Internal | SizeDependence::External => offered == had,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
||||||
|
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
[Axis::X, Axis::Y].into_iter().all(|axis| {
|
||||||
|
widget
|
||||||
|
.size_hint(axis)
|
||||||
|
.is_none_or(|hint| hint == size.axis(axis))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
||||||
@@ -191,7 +278,6 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_rec(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
|
fn remove_rec(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
|
||||||
self.cache.remove(id);
|
|
||||||
let inst = self.remove(id, true, rsc);
|
let inst = self.remove(id, true, rsc);
|
||||||
if let Some(inst) = &inst {
|
if let Some(inst) = &inst {
|
||||||
for c in &inst.children {
|
for c in &inst.children {
|
||||||
@@ -205,7 +291,6 @@ impl UiRenderState {
|
|||||||
for (_, active) in self.active.drain() {
|
for (_, active) in self.active.drain() {
|
||||||
rsc.on_undraw(&active);
|
rsc.on_undraw(&active);
|
||||||
}
|
}
|
||||||
self.cache.clear();
|
|
||||||
self.layers.clear();
|
self.layers.clear();
|
||||||
rsc.widgets_mut().needs_redraw.clear();
|
rsc.widgets_mut().needs_redraw.clear();
|
||||||
rsc.free();
|
rsc.free();
|
||||||
@@ -267,19 +352,11 @@ impl UiRenderState {
|
|||||||
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
|
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
self.draw_started.remove(&id);
|
self.draw_started.remove(&id);
|
||||||
// check if parent depends on the desired size of this, if so then redraw it first
|
// Whoever read this widget's size may be a different size now, so the
|
||||||
for axis in [Axis::X, Axis::Y] {
|
// highest reader is what draws; it reaches this one on the way down.
|
||||||
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
|
if let Some(top) = self.highest_reader(id) {
|
||||||
&& let Some(current) = self.active.get(&id)
|
self.redraw(top, rsc);
|
||||||
&& let Some(pid) = current.parent
|
return;
|
||||||
{
|
|
||||||
self.cache.size.axis_dyn(axis).remove(&id);
|
|
||||||
let new = self.size_ctx(id, outer, rsc).len_axis(id, axis);
|
|
||||||
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
|
|
||||||
if new != old {
|
|
||||||
self.redraw(pid, rsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.draw_started.contains(&id) {
|
if self.draw_started.contains(&id) {
|
||||||
@@ -301,22 +378,22 @@ impl UiRenderState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn size_ctx<'b>(
|
/// The furthest ancestor that read this widget's size, directly or through
|
||||||
&'b mut self,
|
/// widgets that did the same.
|
||||||
source: WidgetId,
|
fn highest_reader(&self, id: WidgetId) -> Option<WidgetId> {
|
||||||
outer: UiVec2,
|
let mut top = None;
|
||||||
rsc: &'b mut dyn UiRsc,
|
let mut at = id;
|
||||||
) -> SizeCtx<'b> {
|
while let Some(active) = self.active.get(&at)
|
||||||
let ui = rsc.ui_mut();
|
&& let Some(parent) = active.parent
|
||||||
SizeCtx {
|
&& self
|
||||||
source,
|
.active
|
||||||
cache: &mut self.cache,
|
.get(&parent)
|
||||||
text: &mut ui.text,
|
.is_some_and(|p| p.size_deps.contains(&at))
|
||||||
widgets: &ui.widgets,
|
{
|
||||||
outer,
|
top = Some(parent);
|
||||||
output_size: self.output_size,
|
at = parent;
|
||||||
id: source,
|
|
||||||
}
|
}
|
||||||
|
top
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, UiVec2,
|
|
||||||
WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct SizeCtx<'a> {
|
|
||||||
pub text: &'a mut TextData,
|
|
||||||
pub(super) source: WidgetId,
|
|
||||||
pub(super) widgets: &'a Widgets,
|
|
||||||
pub(super) cache: &'a mut Cache,
|
|
||||||
/// TODO: should this be pub? rn used for sized
|
|
||||||
pub outer: UiVec2,
|
|
||||||
pub(super) output_size: Vec2,
|
|
||||||
pub(super) id: WidgetId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizeCtx<'_> {
|
|
||||||
pub fn id(&self) -> &WidgetId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source(&self) -> &WidgetId {
|
|
||||||
&self.source
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn len_inner<A: const AxisT>(&mut self, id: WidgetId) -> Len {
|
|
||||||
if let Some((_, len)) = self.cache.size.axis::<A>().get(&id) {
|
|
||||||
return *len;
|
|
||||||
}
|
|
||||||
let len = self
|
|
||||||
.widgets
|
|
||||||
.get_dyn_dynamic(id)
|
|
||||||
.desired_len::<A>(&mut SizeCtx {
|
|
||||||
text: self.text,
|
|
||||||
source: self.source,
|
|
||||||
widgets: self.widgets,
|
|
||||||
cache: self.cache,
|
|
||||||
outer: self.outer,
|
|
||||||
output_size: self.output_size,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
self.cache.size.axis::<A>().insert(id, (self.outer, len));
|
|
||||||
len
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&mut self, id: impl IdLike) -> Len {
|
|
||||||
self.len_inner::<XAxis>(id.id())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn height(&mut self, id: impl IdLike) -> Len {
|
|
||||||
self.len_inner::<YAxis>(id.id())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len_axis(&mut self, id: impl IdLike, axis: Axis) -> Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.width(id),
|
|
||||||
Axis::Y => self.height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&mut self, id: impl IdLike) -> Size {
|
|
||||||
let id = id.id();
|
|
||||||
Size {
|
|
||||||
x: self.width(id),
|
|
||||||
y: self.height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn px_size(&mut self) -> Vec2 {
|
|
||||||
self.outer.to_abs(self.output_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_size(&mut self) -> Vec2 {
|
|
||||||
self.output_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_text(
|
|
||||||
&mut self,
|
|
||||||
buffer: &mut TextBuffer,
|
|
||||||
attrs: &TextAttrs,
|
|
||||||
width: Option<f32>,
|
|
||||||
) -> RenderedText {
|
|
||||||
self.text.render(buffer, attrs, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn label(&self, id: WidgetId) -> &String {
|
|
||||||
self.widgets.label(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+35
-18
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
|
use crate::{Axis, Len, Painter, Size};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
mod data;
|
mod data;
|
||||||
@@ -15,32 +15,49 @@ pub use tag::*;
|
|||||||
pub use view::*;
|
pub use view::*;
|
||||||
pub use widgets::*;
|
pub use widgets::*;
|
||||||
|
|
||||||
|
/// How much of the box a widget was handed its drawing depends on, and so
|
||||||
|
/// what has to change before it must be drawn again. Asked per axis, because
|
||||||
|
/// wrapped text depends on the width it is offered and not on the height.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum SizeDependence {
|
||||||
|
/// None of it: the box only says where the primitives go, so a new one is
|
||||||
|
/// written into them instead of drawn.
|
||||||
|
None,
|
||||||
|
/// Its own extent, whatever box that sits in. Reusable in any box that
|
||||||
|
/// leaves that extent unchanged, including a larger one it does not fill.
|
||||||
|
#[default]
|
||||||
|
Internal,
|
||||||
|
/// The extent of the box itself, used or not.
|
||||||
|
External,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Widget: Any {
|
pub trait Widget: Any {
|
||||||
|
/// Draws the widget, and states what it used with `Painter::set_size`.
|
||||||
fn draw(&mut self, painter: &mut Painter);
|
fn draw(&mut self, painter: &mut Painter);
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> 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.
|
||||||
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WidgetAxisFns {
|
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
|
||||||
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len;
|
SizeDependence::Internal
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget + ?Sized> WidgetAxisFns for W {
|
|
||||||
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match A::get() {
|
|
||||||
Axis::X => self.desired_width(ctx),
|
|
||||||
Axis::Y => self.desired_height(ctx),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for () {
|
impl Widget for () {
|
||||||
fn draw(&mut self, _: &mut Painter) {}
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
painter.set_size(Size::ZERO);
|
||||||
Len::ZERO
|
|
||||||
}
|
}
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::ZERO
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
|
Some(Len::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
|
||||||
|
SizeDependence::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//! Text sizing: wrapped text reads the width it is offered, fixed text does
|
||||||
|
//! not, and both report a height their container lays out around.
|
||||||
|
|
||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
DefaultApp::<State>::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DefaultUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: DefaultUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SAMPLE: &str = "Wrapping shapes one source into as many lines as its container \
|
||||||
|
leaves room for, so the height of a paragraph is an answer rather than a setting, and \
|
||||||
|
the same words in a narrower box come back taller. Resize the window and watch the \
|
||||||
|
text below reflow into a different number of lines while nothing about it changes.";
|
||||||
|
|
||||||
|
impl DefaultAppState for State {
|
||||||
|
fn new(mut ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self>) -> Self {
|
||||||
|
let panel = || rect(Color::WHITE.darker(0.85));
|
||||||
|
|
||||||
|
let wrapped = wtext(SAMPLE)
|
||||||
|
.size(28)
|
||||||
|
.wrap(true)
|
||||||
|
.text_align(Align::LEFT)
|
||||||
|
.pad(16)
|
||||||
|
.background(panel());
|
||||||
|
|
||||||
|
let aligned = (
|
||||||
|
wtext("left").size(24).text_align(Align::LEFT),
|
||||||
|
wtext("centred").size(24).text_align(Align::CENTER),
|
||||||
|
wtext("right").size(24).text_align(Align::RIGHT),
|
||||||
|
)
|
||||||
|
.span(Dir::DOWN)
|
||||||
|
.gap(8)
|
||||||
|
.pad(16)
|
||||||
|
.background(panel());
|
||||||
|
|
||||||
|
// The same words in half the width, which is a different number of
|
||||||
|
// lines and so a different height. A declared width only holds along
|
||||||
|
// a span's own axis, hence the row.
|
||||||
|
let narrow = (
|
||||||
|
wtext(SAMPLE)
|
||||||
|
.size(20)
|
||||||
|
.wrap(true)
|
||||||
|
.pad(16)
|
||||||
|
.background(panel())
|
||||||
|
.align(Align::TOP)
|
||||||
|
.width(rel(0.5)),
|
||||||
|
rect(Color::WHITE.darker(0.95)),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT);
|
||||||
|
|
||||||
|
(wrapped, aligned, narrow)
|
||||||
|
.span(Dir::DOWN)
|
||||||
|
.gap(12)
|
||||||
|
.pad(12)
|
||||||
|
.set_root(rsc, &mut ui_state);
|
||||||
|
Self { ui_state }
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-4
@@ -8,14 +8,15 @@ pub struct Image {
|
|||||||
impl Widget for Image {
|
impl Widget for Image {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.primitive(&self.handle);
|
painter.primitive(&self.handle);
|
||||||
|
painter.set_size(Size::abs(self.handle.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
||||||
Len::abs(self.handle.size().x)
|
Some(Len::abs(self.handle.size().axis(axis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
||||||
Len::abs(self.handle.size().y)
|
SizeDependence::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-7
@@ -7,14 +7,12 @@ pub struct Masked {
|
|||||||
impl Widget for Masked {
|
impl Widget for Masked {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.set_mask(painter.region());
|
painter.set_mask(painter.region());
|
||||||
painter.widget(&self.inner);
|
let size = painter.widget(&self.inner).size();
|
||||||
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
/// It clips to the box it was given, not to the part its child used.
|
||||||
ctx.width(&self.inner)
|
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
||||||
}
|
SizeDependence::External
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.height(&self.inner)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,29 +7,16 @@ pub struct Aligned {
|
|||||||
|
|
||||||
impl Widget for Aligned {
|
impl Widget for Aligned {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
// Drawn where it may be too big, then given its aligned box once its
|
||||||
|
// size is known.
|
||||||
|
let size = painter.widget(&self.inner).size();
|
||||||
let region = match self.align.tuple() {
|
let region = match self.align.tuple() {
|
||||||
(Some(x), Some(y)) => painter
|
(Some(x), Some(y)) => size.to_uivec2().align(RegionAlign { x, y }),
|
||||||
.size(&self.inner)
|
(Some(x), None) => UiRegion::new(size.x.apply_rest().align(x), UiSpan::FULL),
|
||||||
.to_uivec2()
|
(None, Some(y)) => UiRegion::new(UiSpan::FULL, size.y.apply_rest().align(y)),
|
||||||
.align(RegionAlign { x, y }),
|
|
||||||
(Some(x), None) => {
|
|
||||||
let x = painter.size_ctx().width(&self.inner).apply_rest().align(x);
|
|
||||||
UiRegion::new(x, UiSpan::FULL)
|
|
||||||
}
|
|
||||||
(None, Some(y)) => {
|
|
||||||
let y = painter.size_ctx().height(&self.inner).apply_rest().align(y);
|
|
||||||
UiRegion::new(UiSpan::FULL, y)
|
|
||||||
}
|
|
||||||
(None, None) => UiRegion::FULL,
|
(None, None) => UiRegion::FULL,
|
||||||
};
|
};
|
||||||
painter.widget_within(&self.inner, region);
|
painter.place(&self.inner, region);
|
||||||
}
|
painter.set_size(size);
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.width(&self.inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.height(&self.inner)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,14 +10,7 @@ impl Widget for LayerOffset {
|
|||||||
for _ in 0..self.offset {
|
for _ in 0..self.offset {
|
||||||
painter.next_layer();
|
painter.next_layer();
|
||||||
}
|
}
|
||||||
painter.widget(&self.inner);
|
let size = painter.widget(&self.inner).size();
|
||||||
}
|
painter.set_size(size);
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.width(&self.inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.height(&self.inner)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,43 +6,22 @@ pub struct MaxSize {
|
|||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaxSize {
|
impl MaxSize {}
|
||||||
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
|
||||||
if let Some(x) = self.x {
|
|
||||||
ctx.outer.x.select_len(x.apply_rest());
|
|
||||||
}
|
|
||||||
if let Some(y) = self.y {
|
|
||||||
ctx.outer.y.select_len(y.apply_rest());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for MaxSize {
|
impl Widget for MaxSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.widget(&self.inner);
|
let child = painter.widget(&self.inner).size();
|
||||||
}
|
let output = painter.output_size();
|
||||||
|
painter.set_size(Size {
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
x: capped(child.x, self.x, output.x),
|
||||||
self.apply_to_outer(ctx);
|
y: capped(child.y, self.y, output.y),
|
||||||
let width = ctx.width(&self.inner);
|
});
|
||||||
if let Some(x) = self.x {
|
|
||||||
let width_px = width.apply_rest().to_abs(ctx.output_size().x);
|
|
||||||
let x_px = x.apply_rest().to_abs(ctx.output_size().x);
|
|
||||||
if width_px > x_px { x } else { width }
|
|
||||||
} else {
|
|
||||||
width
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
fn capped(len: Len, max: Option<Len>, output: f32) -> Len {
|
||||||
self.apply_to_outer(ctx);
|
match max {
|
||||||
let height = ctx.height(&self.inner);
|
Some(max) if len.apply_rest().to_abs(output) > max.apply_rest().to_abs(output) => max,
|
||||||
if let Some(y) = self.y {
|
_ => len,
|
||||||
let height_px = height.apply_rest().to_abs(ctx.output_size().y);
|
|
||||||
let y_px = y.apply_rest().to_abs(ctx.output_size().y);
|
|
||||||
if height_px > y_px { y } else { height }
|
|
||||||
} else {
|
|
||||||
height
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,14 +8,7 @@ pub struct Offset {
|
|||||||
impl Widget for Offset {
|
impl Widget for Offset {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let region = UiRegion::FULL.offset(self.amt);
|
let region = UiRegion::FULL.offset(self.amt);
|
||||||
painter.widget_within(&self.inner, region);
|
let size = painter.widget_within(&self.inner, region).size();
|
||||||
}
|
painter.set_size(size);
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.width(&self.inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.height(&self.inner)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+13
-21
@@ -7,27 +7,19 @@ pub struct Pad {
|
|||||||
|
|
||||||
impl Widget for Pad {
|
impl Widget for Pad {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.widget_within(&self.inner, self.padding.region());
|
let inner = painter
|
||||||
}
|
.widget_within(&self.inner, self.padding.region())
|
||||||
|
.size();
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
painter.set_size(Size {
|
||||||
let width = self.padding.left + self.padding.right;
|
x: Len {
|
||||||
let height = self.padding.top + self.padding.bottom;
|
abs: inner.x.abs + self.padding.left + self.padding.right,
|
||||||
ctx.outer.x.abs -= width;
|
..inner.x
|
||||||
ctx.outer.y.abs -= height;
|
},
|
||||||
let mut size = ctx.width(&self.inner);
|
y: Len {
|
||||||
size.abs += width;
|
abs: inner.y.abs + self.padding.top + self.padding.bottom,
|
||||||
size
|
..inner.y
|
||||||
}
|
},
|
||||||
|
});
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
let width = self.padding.left + self.padding.right;
|
|
||||||
let height = self.padding.top + self.padding.bottom;
|
|
||||||
ctx.outer.x.abs -= width;
|
|
||||||
ctx.outer.y.abs -= height;
|
|
||||||
let mut size = ctx.height(&self.inner);
|
|
||||||
size.abs += height;
|
|
||||||
size
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ impl Widget for Scroll {
|
|||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let output_len = painter.output_size().axis(self.axis);
|
let output_len = painter.output_size().axis(self.axis);
|
||||||
let container_len = painter.region().axis(self.axis).len();
|
let container_len = painter.region().axis(self.axis).len();
|
||||||
let content_len = painter
|
// Drawn in the whole container to learn its length, then placed at
|
||||||
.len_axis(&self.inner, self.axis)
|
// the scrolled offset.
|
||||||
|
let child = painter.widget(&self.inner).size();
|
||||||
|
let content_len = child
|
||||||
|
.axis(self.axis)
|
||||||
.apply_rest()
|
.apply_rest()
|
||||||
.within_len(container_len)
|
.within_len(container_len)
|
||||||
.to_abs(output_len);
|
.to_abs(output_len);
|
||||||
@@ -28,15 +31,8 @@ impl Widget for Scroll {
|
|||||||
|
|
||||||
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
|
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
|
||||||
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
||||||
painter.widget_within(&self.inner, region);
|
painter.place(&self.inner, region);
|
||||||
}
|
painter.set_size(child);
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.width(&self.inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
ctx.height(&self.inner)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,29 +6,23 @@ pub struct SetSize {
|
|||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetSize {
|
impl SetSize {}
|
||||||
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
|
||||||
if let Some(x) = self.x {
|
|
||||||
ctx.outer.x.select_len(x.apply_rest());
|
|
||||||
}
|
|
||||||
if let Some(y) = self.y {
|
|
||||||
ctx.outer.y.select_len(y.apply_rest());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for SetSize {
|
impl Widget for SetSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.widget(&self.inner);
|
let child = painter.widget(&self.inner).size();
|
||||||
|
painter.set_size(Size {
|
||||||
|
x: self.x.unwrap_or(child.x),
|
||||||
|
y: self.y.unwrap_or(child.y),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
/// A declared axis is known without looking at the child, which is what
|
||||||
self.apply_to_outer(ctx);
|
/// lets a span lay out around `.height(rest(1))` without drawing it.
|
||||||
self.x.unwrap_or_else(|| ctx.width(&self.inner))
|
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
||||||
|
match axis {
|
||||||
|
Axis::X => self.x,
|
||||||
|
Axis::Y => self.y,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
self.apply_to_outer(ctx);
|
|
||||||
self.y.unwrap_or_else(|| ctx.height(&self.inner))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+31
-100
@@ -9,12 +9,26 @@ pub struct Span {
|
|||||||
|
|
||||||
impl Widget for Span {
|
impl Widget for Span {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let total = self.len_sum(&mut painter.size_ctx());
|
let axis = self.dir.axis;
|
||||||
|
// A length for every child before any is placed: from its own hint
|
||||||
|
// where it has one, and from drawing it where it does not.
|
||||||
|
let lens: Vec<Len> = self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|child| match painter.size_hint(child, axis) {
|
||||||
|
Some(len) => len,
|
||||||
|
None => painter.widget(child).len(axis),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
||||||
|
let total = lens.iter().fold(Len::abs(gap), |sum, len| sum + *len);
|
||||||
|
|
||||||
let mut start = UiScalar::rel_min();
|
let mut start = UiScalar::rel_min();
|
||||||
for child in &self.children {
|
let mut ortho = Len::ZERO;
|
||||||
|
for (child, len) in self.children.iter().zip(&lens) {
|
||||||
let mut span = UiSpan::FULL;
|
let mut span = UiSpan::FULL;
|
||||||
span.start = start;
|
span.start = start;
|
||||||
let len = painter.len_axis(child, self.dir.axis);
|
|
||||||
if len.rest > 0.0 {
|
if len.rest > 0.0 {
|
||||||
let offset = UiScalar::new(total.rel, total.abs);
|
let offset = UiScalar::new(total.rel, total.abs);
|
||||||
let rel_end = UiScalar::rel(len.rest / total.rest);
|
let rel_end = UiScalar::rel(len.rest / total.rest);
|
||||||
@@ -24,27 +38,25 @@ impl Widget for Span {
|
|||||||
start.abs += len.abs;
|
start.abs += len.abs;
|
||||||
start.rel += len.rel;
|
start.rel += len.rel;
|
||||||
span.end = start;
|
span.end = start;
|
||||||
let mut child_region = UiRegion::from_axis(self.dir.axis, span, UiSpan::FULL);
|
let mut region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
||||||
if self.dir.sign == Sign::Neg {
|
if self.dir.sign == Sign::Neg {
|
||||||
child_region.flip(self.dir.axis);
|
region.flip(axis);
|
||||||
|
}
|
||||||
|
let used = painter.place(child, region).axis(!axis);
|
||||||
|
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
||||||
|
if used.rel > 0.0 || used.rest > 0.0 {
|
||||||
|
ortho = Len::REST;
|
||||||
|
} else if ortho.rest == 0.0 {
|
||||||
|
ortho.abs = ortho.abs.max(used.abs);
|
||||||
}
|
}
|
||||||
painter.widget_within(child, child_region);
|
|
||||||
start.abs += self.gap;
|
start.abs += self.gap;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
let along = match total.rest == 0.0 && total.rel == 0.0 {
|
||||||
match self.dir.axis {
|
true => total,
|
||||||
Axis::X => self.desired_len(ctx),
|
false => Len::default(),
|
||||||
Axis::Y => self.desired_ortho(ctx),
|
};
|
||||||
}
|
painter.set_size(Size::from_axis(axis, along, ortho));
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match self.dir.axis {
|
|
||||||
Axis::X => self.desired_ortho(ctx),
|
|
||||||
Axis::Y => self.desired_len(ctx),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,87 +81,6 @@ impl Span {
|
|||||||
pub fn pop(&mut self) -> Option<StrongWidget> {
|
pub fn pop(&mut self) -> Option<StrongWidget> {
|
||||||
self.children.pop()
|
self.children.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
|
||||||
self.children.iter().fold(Len::abs(gap), |mut s, id| {
|
|
||||||
// it's tempting to subtract the abs & rel from the ctx outer,
|
|
||||||
// but that would create inconsistent sizing if you put
|
|
||||||
// a rest first vs last & only speed up in one direction.
|
|
||||||
// I think this is only solvable by restricting how you can
|
|
||||||
// compute size, bc currently you need child to define parent's
|
|
||||||
// sectioning and you need parent's sectioning to define child.
|
|
||||||
// Fortunately, that doesn't matter in most cases
|
|
||||||
let len = ctx.len_axis(id, self.dir.axis);
|
|
||||||
s += len;
|
|
||||||
s
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_len(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
let len = self.len_sum(ctx);
|
|
||||||
if len.rest == 0.0 && len.rel == 0.0 {
|
|
||||||
len
|
|
||||||
} else {
|
|
||||||
Len::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_ortho(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
// this is a weird hack to get text wrapping to work properly when in a downward span
|
|
||||||
// the correct solution here is to add a function to widget that lets them
|
|
||||||
// request that ctx.outer has an axis "resolved" before checking the other,
|
|
||||||
// and panicking or warning if two request opposite axis (unsolvable in that case)
|
|
||||||
let outer = ctx.outer.axis(self.dir.axis);
|
|
||||||
if self.dir.axis == Axis::X {
|
|
||||||
// so....... this literally copies draw so that the lengths are correctly set in the
|
|
||||||
// context, which makes this slow and not cool
|
|
||||||
let total = self.len_sum(ctx);
|
|
||||||
let mut start = UiScalar::rel_min();
|
|
||||||
let mut ortho_len = Len::ZERO;
|
|
||||||
for child in &self.children {
|
|
||||||
let mut span = UiSpan::FULL;
|
|
||||||
span.start = start;
|
|
||||||
let len = ctx.len_axis(child, self.dir.axis);
|
|
||||||
if len.rest > 0.0 {
|
|
||||||
let offset = UiScalar::new(total.rel, total.abs);
|
|
||||||
let rel_end = UiScalar::rel(len.rest / total.rest);
|
|
||||||
let end = (UiScalar::rel_max() + start) - offset;
|
|
||||||
start = rel_end.within(&start.to(end));
|
|
||||||
}
|
|
||||||
start.abs += len.abs;
|
|
||||||
start.rel += len.rel;
|
|
||||||
span.end = start;
|
|
||||||
|
|
||||||
let scalar = span.len();
|
|
||||||
*ctx.outer.axis_mut(self.dir.axis) = outer.select_len(scalar);
|
|
||||||
let ortho = ctx.len_axis(child, !self.dir.axis);
|
|
||||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
|
||||||
if ortho.rel > 0.0 || ortho.rest > 0.0 {
|
|
||||||
ortho_len.rest = 1.0;
|
|
||||||
ortho_len.abs = 0.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ortho_len.abs = ortho_len.abs.max(ortho.abs);
|
|
||||||
start.abs += self.gap;
|
|
||||||
}
|
|
||||||
ortho_len
|
|
||||||
} else {
|
|
||||||
let mut ortho_len = Len::ZERO;
|
|
||||||
let ortho = !self.dir.axis;
|
|
||||||
for child in &self.children {
|
|
||||||
let len = ctx.len_axis(child, ortho);
|
|
||||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
|
||||||
if len.rel > 0.0 || len.rest > 0.0 {
|
|
||||||
ortho_len.rest = 1.0;
|
|
||||||
ortho_len.abs = 0.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ortho_len.abs = ortho_len.abs.max(len.abs);
|
|
||||||
}
|
|
||||||
ortho_len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> {
|
pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> {
|
||||||
|
|||||||
@@ -9,29 +9,24 @@ pub struct Stack {
|
|||||||
|
|
||||||
impl Widget for Stack {
|
impl Widget for Stack {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let mut iter = self.children.iter();
|
let sizing = match self.size {
|
||||||
if let Some(child) = iter.next() {
|
StackSize::Default => None,
|
||||||
painter.child_layer();
|
StackSize::Child(i) => Some(i),
|
||||||
painter.widget(child);
|
};
|
||||||
|
let mut size = Size::default();
|
||||||
|
for (i, child) in self.children.iter().enumerate() {
|
||||||
|
match i {
|
||||||
|
0 => painter.child_layer(),
|
||||||
|
_ => painter.next_layer(),
|
||||||
}
|
}
|
||||||
for child in iter {
|
let drawn = painter.widget(child);
|
||||||
painter.next_layer();
|
// Only the child that sizes the stack is read, so the others
|
||||||
painter.widget(child);
|
// changing size does not redraw it.
|
||||||
|
if sizing == Some(i) {
|
||||||
|
size = drawn.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
painter.set_size(size);
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match self.size {
|
|
||||||
StackSize::Default => Len::default(),
|
|
||||||
StackSize::Child(i) => ctx.width(&self.children[i]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match self.size {
|
|
||||||
StackSize::Default => Len::default(),
|
|
||||||
StackSize::Child(i) => ctx.height(&self.children[i]),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-19
@@ -7,25 +7,11 @@ pub struct WidgetPtr {
|
|||||||
|
|
||||||
impl Widget for WidgetPtr {
|
impl Widget for WidgetPtr {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
if let Some(id) = &self.inner {
|
let size = match &self.inner {
|
||||||
painter.widget(id);
|
Some(id) => painter.widget(id).size(),
|
||||||
}
|
None => Size::ZERO,
|
||||||
}
|
};
|
||||||
|
painter.set_size(size);
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
if let Some(id) = &self.inner {
|
|
||||||
ctx.width(id)
|
|
||||||
} else {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
if let Some(id) = &self.inner {
|
|
||||||
ctx.height(id)
|
|
||||||
} else {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -35,14 +35,16 @@ impl Widget for Rect {
|
|||||||
thickness: self.thickness,
|
thickness: self.thickness,
|
||||||
inner_radius: self.inner_radius,
|
inner_radius: self.inner_radius,
|
||||||
});
|
});
|
||||||
|
painter.set_size(Size::REST);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
fn size_hint(&self, _: Axis) -> Option<Len> {
|
||||||
Len::rest(1)
|
Some(Len::REST)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
/// Its box is its primitive's own region, so a new one is written there.
|
||||||
Len::rest(1)
|
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
||||||
|
SizeDependence::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ impl Widget for TextEdit {
|
|||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let base = painter.layer;
|
let base = painter.layer;
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
self.view.draw(painter);
|
let (_, size) = self.view.draw(painter);
|
||||||
|
painter.set_size(size);
|
||||||
painter.layer = base;
|
painter.layer = base;
|
||||||
let region = self.region();
|
let region = self.region();
|
||||||
|
|
||||||
@@ -87,12 +88,8 @@ impl Widget for TextEdit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
fn size_dependence(&self, axis: Axis) -> SizeDependence {
|
||||||
self.view.desired_width(ctx)
|
self.view.size_dependence(axis)
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
self.view.desired_height(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-40
@@ -52,15 +52,15 @@ impl TextView {
|
|||||||
.align(self.align)
|
.align(self.align)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, ctx: &mut SizeCtx) -> &RenderedText {
|
fn render(&mut self, painter: &mut Painter) -> &RenderedText {
|
||||||
let width = if self.attrs.wrap {
|
let width = if self.attrs.wrap {
|
||||||
Some(ctx.px_size().x)
|
Some(painter.px_size().x)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if width != self.width || self.tex.is_none() || self.attrs.changed || self.buf.changed {
|
if width != self.width || self.tex.is_none() || self.attrs.changed || self.buf.changed {
|
||||||
self.width = width;
|
self.width = width;
|
||||||
self.tex = Some(ctx.draw_text(&mut self.buf, &self.attrs, width));
|
self.tex = Some(painter.render_text(&mut self.buf, &self.attrs, width));
|
||||||
self.attrs.changed = false;
|
self.attrs.changed = false;
|
||||||
self.buf.changed = false;
|
self.buf.changed = false;
|
||||||
}
|
}
|
||||||
@@ -69,39 +69,33 @@ impl TextView {
|
|||||||
pub fn tex(&self) -> Option<&RenderedText> {
|
pub fn tex(&self) -> Option<&RenderedText> {
|
||||||
self.tex.as_ref()
|
self.tex.as_ref()
|
||||||
}
|
}
|
||||||
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
/// Draws the text, and says where the glyphs went and what they use.
|
||||||
if self.is_empty()
|
pub fn draw(&mut self, painter: &mut Painter) -> (UiRegion, Size) {
|
||||||
&& let Some(hint) = &self.hint
|
|
||||||
{
|
|
||||||
ctx.width(hint)
|
|
||||||
} else {
|
|
||||||
Len::abs(self.render(ctx).size.x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
if self.is_empty()
|
|
||||||
&& let Some(hint) = &self.hint
|
|
||||||
{
|
|
||||||
ctx.height(hint)
|
|
||||||
} else {
|
|
||||||
Len::abs(self.render(ctx).size.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
|
||||||
let align = self.align;
|
let align = self.align;
|
||||||
if self.is_empty() && self.hint.is_some() {
|
if self.is_empty() && self.hint.is_some() {
|
||||||
let region = self.render(&mut painter.size_ctx()).size.align(align);
|
let region = self.render(painter).size.align(align);
|
||||||
if let Some(hint) = &self.hint {
|
let size = match &self.hint {
|
||||||
painter.widget(hint);
|
Some(hint) => painter.widget(hint).size(),
|
||||||
}
|
None => Size::ZERO,
|
||||||
return region;
|
};
|
||||||
|
return (region, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tex = self.render(&mut painter.size_ctx());
|
let tex = self.render(painter);
|
||||||
let region = tex.size.align(align);
|
let region = tex.size.align(align);
|
||||||
|
let size = Size::abs(tex.size);
|
||||||
let within = region.within(&painter.region());
|
let within = region.within(&painter.region());
|
||||||
painter.glyphs(tex, within);
|
painter.glyphs(tex, within);
|
||||||
region
|
(region, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapping reads the width it is offered, so a wider box reshapes it; a
|
||||||
|
/// taller one never does.
|
||||||
|
pub fn size_dependence(&self, axis: Axis) -> SizeDependence {
|
||||||
|
match axis == Axis::X && self.attrs.wrap {
|
||||||
|
true => SizeDependence::External,
|
||||||
|
false => SizeDependence::Internal,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(&self) -> String {
|
pub fn content(&self) -> String {
|
||||||
@@ -117,7 +111,7 @@ impl Text {
|
|||||||
content: content.into(),
|
content: content.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn update_buf(&mut self, _ctx: &mut SizeCtx) {
|
fn update_buf(&mut self) {
|
||||||
if self.content.changed {
|
if self.content.changed {
|
||||||
self.content.changed = false;
|
self.content.changed = false;
|
||||||
self.view.buf.set_text(self.content.as_str());
|
self.view.buf.set_text(self.content.as_str());
|
||||||
@@ -127,18 +121,13 @@ impl Text {
|
|||||||
|
|
||||||
impl Widget for Text {
|
impl Widget for Text {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
self.update_buf(&mut painter.size_ctx());
|
self.update_buf();
|
||||||
self.view.draw(painter);
|
let (_, size) = self.view.draw(painter);
|
||||||
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
fn size_dependence(&self, axis: Axis) -> SizeDependence {
|
||||||
self.update_buf(ctx);
|
self.view.size_dependence(axis)
|
||||||
self.view.desired_width(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
self.update_buf(ctx);
|
|
||||||
self.view.desired_height(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
//! What a second frame draws again, and what it keeps.
|
||||||
|
|
||||||
|
use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
|
use iris::harness::{Harness, assert_corners};
|
||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
/// A leaf that counts its draws and reports whatever size it is given, so a
|
||||||
|
/// test can see what the retained path skipped.
|
||||||
|
struct Counted {
|
||||||
|
draws: Rc<Cell<usize>>,
|
||||||
|
size: Size,
|
||||||
|
dependence: SizeDependence,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Counted {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
self.draws.set(self.draws.get() + 1);
|
||||||
|
painter.set_size(self.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
||||||
|
self.dependence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Counts(Rc<Cell<usize>>);
|
||||||
|
|
||||||
|
impl Counts {
|
||||||
|
fn get(&self) -> usize {
|
||||||
|
self.0.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn counted(
|
||||||
|
h: &mut Harness,
|
||||||
|
size: Size,
|
||||||
|
dependence: SizeDependence,
|
||||||
|
) -> (WeakWidget<Counted>, Counts) {
|
||||||
|
let draws = Rc::new(Cell::new(0));
|
||||||
|
let id = Counted {
|
||||||
|
draws: draws.clone(),
|
||||||
|
size,
|
||||||
|
dependence,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
(id, Counts(draws))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fixed-width leaf beside one that takes the rest, so changing the first
|
||||||
|
/// hands the second a different box without the output changing.
|
||||||
|
fn pair(h: &mut Harness, rest: SizeDependence) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
||||||
|
let (first, _) = counted(h, Size::from((100, 200)), SizeDependence::Internal);
|
||||||
|
let (second, draws) = counted(h, Size::REST, rest);
|
||||||
|
h.set_root((first, second).span(Dir::RIGHT));
|
||||||
|
(first, draws, second.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let (first, draws, second) = pair(&mut h, SizeDependence::None);
|
||||||
|
let settled = draws.get();
|
||||||
|
assert_corners!(h, second, (100, 0), (400, 200));
|
||||||
|
|
||||||
|
h.rsc[first].size = Size::from((150, 200));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
draws.get(),
|
||||||
|
settled,
|
||||||
|
"its box is a field to write, not a reason to draw"
|
||||||
|
);
|
||||||
|
assert_corners!(h, second, (150, 0), (400, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let (first, draws, second) = pair(&mut h, SizeDependence::External);
|
||||||
|
let settled = draws.get();
|
||||||
|
|
||||||
|
h.rsc[first].size = Size::from((150, 200));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
// Twice: once for the span to measure it, once for its real box. A child
|
||||||
|
// that can hint its length is spared the first, and a smaller number here
|
||||||
|
// means someone has made that cheaper rather than broken it.
|
||||||
|
assert_eq!(draws.get(), settled + 2);
|
||||||
|
assert_corners!(h, second, (150, 0), (400, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_span_child_that_declares_its_length_is_drawn_once() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), SizeDependence::Internal);
|
||||||
|
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), SizeDependence::Internal);
|
||||||
|
// The span takes one child's length from its hint and has to draw the
|
||||||
|
// other to find out, so only the second is drawn before it is placed.
|
||||||
|
let hinted = told.width(100).add(&mut h.rsc);
|
||||||
|
h.set_root((hinted, asked).span(Dir::RIGHT));
|
||||||
|
|
||||||
|
assert_eq!(told_draws.get(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
asked_draws.get(),
|
||||||
|
2,
|
||||||
|
"drawn to be measured, then again to be placed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_span_relays_out_when_a_child_it_measured_changes() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let (first, _, second) = pair(&mut h, SizeDependence::Internal);
|
||||||
|
|
||||||
|
h.rsc[first].size = Size::from((250, 200));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_corners!(h, first, (0, 0), (250, 200));
|
||||||
|
assert_corners!(h, second, (250, 0), (400, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_placed_child_survives_the_next_frame() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
// Both children declare a length, so the span places them from their hints
|
||||||
|
// rather than drawing them to find out.
|
||||||
|
let top = rect(Color::RED).height(80).add(&mut h.rsc);
|
||||||
|
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
||||||
|
h.set_root((top, bottom).span(Dir::DOWN));
|
||||||
|
|
||||||
|
h.rsc.widgets_mut().get_dyn_mut(top.id());
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_corners!(h, top, (0, 0), (400, 80));
|
||||||
|
assert_corners!(h, bottom, (0, 80), (400, 200));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user