Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46547563c1 | ||
|
|
53e61289f5 | ||
|
|
984f482a7f | ||
|
|
9520996623 | ||
|
|
b108645240 | ||
|
|
ec012c4552 |
No files matched your search
+55
-23
@@ -56,13 +56,6 @@ impl UiVec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn outside(&self, region: &UiRegion) -> UiVec2 {
|
|
||||||
UiVec2 {
|
|
||||||
x: self.x.outside(®ion.x),
|
|
||||||
y: self.y.outside(®ion.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut UiScalar {
|
pub fn axis_mut(&mut self, axis: Axis) -> &mut UiScalar {
|
||||||
match axis {
|
match axis {
|
||||||
Axis::X => &mut self.x,
|
Axis::X => &mut self.x,
|
||||||
@@ -209,10 +202,12 @@ impl UiScalar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn outside(&self, span: &UiSpan) -> Self {
|
/// Undoes `within`, and `None` where the span has a fixed length: every
|
||||||
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel);
|
/// fraction of it lands on the same `rel`, so none can be told apart.
|
||||||
|
pub fn outside(&self, span: &UiSpan) -> Option<Self> {
|
||||||
|
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel)?;
|
||||||
let abs = self.abs - rel.lerp(span.start.abs, span.end.abs);
|
let abs = self.abs - rel.lerp(span.start.abs, span.end.abs);
|
||||||
Self { rel, abs }
|
Some(Self { rel, abs })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn within_len(&self, len: UiScalar) -> Self {
|
pub fn within_len(&self, len: UiScalar) -> Self {
|
||||||
@@ -283,11 +278,11 @@ impl UiSpan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn outside(&self, parent: &Self) -> Self {
|
pub fn outside(&self, parent: &Self) -> Option<Self> {
|
||||||
Self {
|
Some(Self {
|
||||||
start: self.start.outside(parent),
|
start: self.start.outside(parent)?,
|
||||||
end: self.end.outside(parent),
|
end: self.end.outside(parent)?,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn len(&self) -> UiScalar {
|
pub const fn len(&self) -> UiScalar {
|
||||||
@@ -324,14 +319,7 @@ impl UiRegion {
|
|||||||
y: self.y.within(&parent.y),
|
y: self.y.within(&parent.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const fn outside(&self, parent: &Self) -> Self {
|
pub const fn axis(&self, axis: Axis) -> &UiSpan {
|
||||||
Self {
|
|
||||||
x: self.x.outside(&parent.x),
|
|
||||||
y: self.y.outside(&parent.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn axis(&mut self, axis: Axis) -> &UiSpan {
|
|
||||||
match axis {
|
match axis {
|
||||||
Axis::X => &self.x,
|
Axis::X => &self.x,
|
||||||
Axis::Y => &self.y,
|
Axis::Y => &self.y,
|
||||||
@@ -409,6 +397,50 @@ impl UiRegion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Taking a drawing out of one box and putting it in another, checked once
|
||||||
|
/// for a whole subtree so that applying it cannot fail.
|
||||||
|
///
|
||||||
|
/// A box of a fixed length holds each part as an offset from its start rather
|
||||||
|
/// than as a fraction of it, so those parts can be carried to a box of the
|
||||||
|
/// same length but never stretched to a different one.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub struct Remap {
|
||||||
|
from: UiRegion,
|
||||||
|
to: UiRegion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Remap {
|
||||||
|
pub fn new(from: UiRegion, to: UiRegion) -> Option<Self> {
|
||||||
|
[Axis::X, Axis::Y]
|
||||||
|
.into_iter()
|
||||||
|
.all(|axis| {
|
||||||
|
let (from, to) = (from.axis(axis), to.axis(axis));
|
||||||
|
from.start.rel != from.end.rel || from.len() == to.len()
|
||||||
|
})
|
||||||
|
.then_some(Self { from, to })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&self, region: UiRegion) -> UiRegion {
|
||||||
|
UiRegion {
|
||||||
|
x: Self::span(region.x, self.from.x, self.to.x),
|
||||||
|
y: Self::span(region.y, self.from.y, self.to.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span(span: UiSpan, from: UiSpan, to: UiSpan) -> UiSpan {
|
||||||
|
match span.outside(&from) {
|
||||||
|
Some(out) => out.within(&to),
|
||||||
|
// `new` admits this only where the two are the same length, so
|
||||||
|
// the difference between their starts is the whole move.
|
||||||
|
None => {
|
||||||
|
let mut span = span;
|
||||||
|
span.shift(to.start - from.start);
|
||||||
|
span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for UiRegion {
|
impl Display for UiRegion {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ pub struct ActiveData {
|
|||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub size_deps: Vec<WidgetId>,
|
pub size_deps: Vec<WidgetId>,
|
||||||
|
/// Whether it read the output's size, and so is wrong when that changes.
|
||||||
|
pub reads_output: bool,
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
pub layer: LayerId,
|
pub layer: LayerId,
|
||||||
}
|
}
|
||||||
+36
-57
@@ -20,7 +20,7 @@ pub struct Painter<'a> {
|
|||||||
pub(super) children: Vec<WidgetId>,
|
pub(super) children: Vec<WidgetId>,
|
||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub(super) size_deps: Vec<WidgetId>,
|
pub(super) size_deps: Vec<WidgetId>,
|
||||||
pub(super) size: Option<Size>,
|
pub(super) reads_output: bool,
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub(super) id: WidgetId,
|
pub(super) id: WidgetId,
|
||||||
}
|
}
|
||||||
@@ -70,39 +70,31 @@ 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>) -> DrawResult<'_, 'a> {
|
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||||
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. Drawing one a second time
|
||||||
/// Useful for drawing child widgets in select areas.
|
/// gives it a new box, keeping the drawing it already has where it can.
|
||||||
pub fn widget_within<W: ?Sized>(
|
pub fn widget_within<'s, W: ?Sized>(
|
||||||
&mut self,
|
&'s mut self,
|
||||||
id: &StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
) -> DrawResult<'_, 'a> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
let region = region.within(&self.region);
|
let region = region.within(&self.region);
|
||||||
self.widget_at(id, region)
|
self.widget_at(id, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_at<W: ?Sized>(
|
fn widget_at<'s, W: ?Sized>(
|
||||||
&mut self,
|
&'s mut self,
|
||||||
id: &StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
) -> DrawResult<'_, 'a> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
self.children.push(id.id());
|
// A child listed twice would be moved twice.
|
||||||
|
if !self.children.contains(&id.id()) {
|
||||||
|
self.children.push(id.id());
|
||||||
|
}
|
||||||
let size = self.state.draw_inner(
|
let size = self.state.draw_inner(
|
||||||
self.layer,
|
self.layer,
|
||||||
id.id(),
|
id.id(),
|
||||||
@@ -113,43 +105,24 @@ impl<'a> Painter<'a> {
|
|||||||
self.rsc,
|
self.rsc,
|
||||||
);
|
);
|
||||||
DrawResult {
|
DrawResult {
|
||||||
child: id.id(),
|
child: id,
|
||||||
painter: self,
|
painter: self,
|
||||||
size,
|
size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a child says its length is without being drawn, if it can say.
|
/// 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> {
|
/// Asking counts as reading its size.
|
||||||
self.rsc.widgets().get_dyn(id.id())?.size_hint(axis)
|
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
||||||
|
let hint = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis)?;
|
||||||
|
self.depend_on_size(id);
|
||||||
|
Some(hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives a child its final box once this widget knows what that is,
|
fn depend_on_size<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||||
/// keeping the drawing it already has where it can.
|
if !self.size_deps.contains(&child.id()) {
|
||||||
pub fn place<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
|
self.size_deps.push(child.id());
|
||||||
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(
|
||||||
@@ -190,11 +163,17 @@ impl<'a> Painter<'a> {
|
|||||||
self.region
|
self.region
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn output_size(&self) -> Vec2 {
|
/// The output's size in pixels. A widget that reads it draws again when
|
||||||
|
/// the output changes, since nothing else can put that right.
|
||||||
|
pub fn output_size(&mut self) -> Vec2 {
|
||||||
|
self.reads_output = true;
|
||||||
self.state.output_size
|
self.state.output_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This widget's box in pixels. Resolved against the output's size, so a
|
||||||
|
/// widget that reads it draws again when the output changes.
|
||||||
pub fn px_size(&mut self) -> Vec2 {
|
pub fn px_size(&mut self) -> Vec2 {
|
||||||
|
self.reads_output = true;
|
||||||
self.region.size().to_abs(self.state.output_size)
|
self.region.size().to_abs(self.state.output_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,15 +201,15 @@ impl<'a> Painter<'a> {
|
|||||||
/// A child that has just been drawn. Reading its size records that this
|
/// A child that has just been drawn. Reading its size records that this
|
||||||
/// widget's own size depends on it; dropping it without reading draws the
|
/// widget's own size depends on it; dropping it without reading draws the
|
||||||
/// child and leaves the parent independent of what it came to.
|
/// child and leaves the parent independent of what it came to.
|
||||||
pub struct DrawResult<'p, 'a> {
|
pub struct DrawResult<'p, 'a, W: ?Sized> {
|
||||||
painter: &'p mut Painter<'a>,
|
painter: &'p mut Painter<'a>,
|
||||||
child: WidgetId,
|
child: &'p StrongWidget<W>,
|
||||||
size: Size,
|
size: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawResult<'_, '_> {
|
impl<W: ?Sized> DrawResult<'_, '_, W> {
|
||||||
pub fn size(self) -> Size {
|
pub fn size(self) -> Size {
|
||||||
self.painter.size_deps.push(self.child);
|
self.painter.depend_on_size(self.child);
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+54
-69
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, Size, SizeDependence,
|
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Remap, Size,
|
||||||
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||||
};
|
};
|
||||||
@@ -53,11 +53,21 @@ impl UiRenderState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let root = root.into();
|
let root = root.into();
|
||||||
if self.needs_full_redraw(root) {
|
if self.root_changed(root) {
|
||||||
self.redraw_all(root, rsc);
|
self.redraw_all(root, rsc);
|
||||||
self.old_root = root.map(|r| r.id());
|
self.old_root = root.map(|r| r.id());
|
||||||
self.resized = false;
|
} else if self.resized {
|
||||||
} else if rsc.widgets().has_updates() {
|
// A region is a fraction of the output plus an offset, resolved
|
||||||
|
// against the window in the shader, so a resize moves the whole
|
||||||
|
// drawing on its own. Only a widget that read pixels can be wrong.
|
||||||
|
for (&id, active) in &self.active {
|
||||||
|
if active.reads_output {
|
||||||
|
rsc.widgets_mut().needs_redraw.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.resized = false;
|
||||||
|
if rsc.widgets().has_updates() {
|
||||||
self.redraw_updates(rsc);
|
self.redraw_updates(rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +103,7 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// draw widget
|
// draw widget
|
||||||
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
self.draw_started.insert(id);
|
self.draw_started.insert(id);
|
||||||
|
|
||||||
let mut painter = Painter {
|
let mut painter = Painter {
|
||||||
@@ -105,12 +116,12 @@ impl UiRenderState {
|
|||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
size_deps: Vec::new(),
|
size_deps: Vec::new(),
|
||||||
size: None,
|
reads_output: false,
|
||||||
rsc,
|
rsc,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut widget = painter.rsc.widgets().get_dyn_dynamic(id);
|
let mut widget = painter.rsc.widgets().get_dyn_dynamic(id);
|
||||||
widget.draw(&mut painter);
|
let size = widget.draw(&mut painter);
|
||||||
drop(widget);
|
drop(widget);
|
||||||
|
|
||||||
let Painter {
|
let Painter {
|
||||||
@@ -122,17 +133,11 @@ impl UiRenderState {
|
|||||||
primitives,
|
primitives,
|
||||||
children,
|
children,
|
||||||
size_deps,
|
size_deps,
|
||||||
size,
|
reads_output,
|
||||||
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!(
|
debug_assert!(
|
||||||
Self::hints_agree(id, size, rsc),
|
Self::hints_agree(id, size, rsc),
|
||||||
"'{}' ({id:?}) drew a size its size_hint disagrees with",
|
"'{}' ({id:?}) drew a size its size_hint disagrees with",
|
||||||
@@ -149,6 +154,7 @@ impl UiRenderState {
|
|||||||
primitives,
|
primitives,
|
||||||
children,
|
children,
|
||||||
size_deps,
|
size_deps,
|
||||||
|
reads_output,
|
||||||
mask,
|
mask,
|
||||||
layer,
|
layer,
|
||||||
};
|
};
|
||||||
@@ -165,26 +171,6 @@ impl UiRenderState {
|
|||||||
size
|
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
|
/// The drawing a widget already has, kept for a new box if the box has not
|
||||||
/// changed in a way it depends on.
|
/// changed in a way it depends on.
|
||||||
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
|
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
|
||||||
@@ -192,43 +178,41 @@ impl UiRenderState {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let active = self.active.get(&id)?;
|
let active = self.active.get(&id)?;
|
||||||
let (size, was) = (active.size, active.region);
|
let (size, old) = (active.size, active.region);
|
||||||
if was == region {
|
if old == region {
|
||||||
return Some(size);
|
return Some(size);
|
||||||
}
|
}
|
||||||
// TODO: epsilon?
|
// TODO: epsilon?
|
||||||
if was.size() == region.size() {
|
if old.size() != region.size() && !self.reusable(id, region, rsc) {
|
||||||
self.mov(id, was, region);
|
return None;
|
||||||
return Some(size);
|
|
||||||
}
|
}
|
||||||
if self.reusable(id, region, rsc) {
|
// Its drawing stands, if the new box can be reached from the old one.
|
||||||
// Its drawing stands; the box is written into the primitives.
|
self.mov(id, &Remap::new(old, region)?);
|
||||||
self.mov(id, was, region);
|
Some(size)
|
||||||
return Some(size);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the widget can keep the drawing it has and be given `region`
|
/// 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
|
/// instead, asked one axis at a time: a change on an axis it does not
|
||||||
/// depend on costs nothing, whatever it depends on elsewhere.
|
/// depend on costs nothing, whatever it depends on elsewhere.
|
||||||
fn reusable(&self, id: WidgetId, mut region: UiRegion, rsc: &dyn UiRsc) -> bool {
|
fn reusable(&self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> bool {
|
||||||
let Some(active) = self.active.get(&id) else {
|
let Some(active) = self.active.get(&id) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let mut was = active.region;
|
|
||||||
[Axis::X, Axis::Y].into_iter().all(|axis| {
|
[Axis::X, Axis::Y].into_iter().all(|axis| {
|
||||||
let offered = region.axis_mut(axis).len();
|
let offered = region.axis(axis).len();
|
||||||
let had = was.axis_mut(axis).len();
|
let had = active.region.axis(axis).len();
|
||||||
match widget.size_dependence(axis) {
|
match widget.on_resize(axis) {
|
||||||
SizeDependence::None => true,
|
OnResize::Scale => true,
|
||||||
// `Internal` could also keep its drawing when only the room
|
// `Translate` is not acted on yet, and cannot be until a
|
||||||
// around it changed, but that is a translation rather than a
|
// drawing can sit somewhere other than its box. `region` is
|
||||||
// remap, so it waits for the move chain.
|
// both the box a widget was given and the box its primitives
|
||||||
SizeDependence::Internal | SizeDependence::External => offered == had,
|
// are in, and `mov` remaps from it -- so carrying a drawing at
|
||||||
|
// its old size while the box grows makes the next move stretch
|
||||||
|
// it. The offset chain is what separates the two.
|
||||||
|
OnResize::Translate | OnResize::Redraw => offered == had,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -244,17 +228,17 @@ impl UiRenderState {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
fn mov(&mut self, id: WidgetId, remap: &Remap) {
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
for h in &active.primitives {
|
for h in &active.primitives {
|
||||||
let region = self.layers[h.layer].region_mut(h);
|
let region = self.layers[h.layer].region_mut(h);
|
||||||
*region = region.outside(&from).within(&to);
|
*region = remap.apply(*region);
|
||||||
}
|
}
|
||||||
active.region = active.region.outside(&from).within(&to);
|
active.region = remap.apply(active.region);
|
||||||
// SAFETY: children cannot be recursive
|
// SAFETY: children cannot be recursive
|
||||||
let children = unsafe { forget_ref(&active.children) };
|
let children = unsafe { forget_ref(&active.children) };
|
||||||
for child in children {
|
for child in children {
|
||||||
self.mov(*child, from, to);
|
self.mov(*child, remap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,17 +291,12 @@ impl UiRenderState {
|
|||||||
root.into().map(|r| r.id()) != self.old_root
|
root.into().map(|r| r.id()) != self.old_root
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheduling and drawing must use the same full-redraw predicate.
|
|
||||||
fn needs_full_redraw<'a>(&self, root: impl Into<Option<&'a StrongWidget>>) -> bool {
|
|
||||||
self.root_changed(root) || self.resized
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn needs_redraw<'a>(
|
pub fn needs_redraw<'a>(
|
||||||
&self,
|
&self,
|
||||||
root: impl Into<Option<&'a StrongWidget>>,
|
root: impl Into<Option<&'a StrongWidget>>,
|
||||||
widgets: &Widgets,
|
widgets: &Widgets,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.needs_full_redraw(root) || widgets.has_updates()
|
self.root_changed(root) || self.resized || widgets.has_updates()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_widgets(&self) -> usize {
|
pub fn active_widgets(&self) -> usize {
|
||||||
@@ -350,14 +329,19 @@ impl UiRenderState {
|
|||||||
|
|
||||||
/// redraws a widget that's currently active (drawn)
|
/// redraws a widget that's currently active (drawn)
|
||||||
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);
|
|
||||||
self.draw_started.remove(&id);
|
self.draw_started.remove(&id);
|
||||||
// Whoever read this widget's size may be a different size now, so the
|
// Whoever read this widget's size may be a different size now, so the
|
||||||
// highest reader is what draws; it reaches this one on the way down.
|
// highest reader is what draws. Everything between the two is marked
|
||||||
if let Some(top) = self.highest_reader(id) {
|
// as well: their own boxes have not changed, so the mark is the only
|
||||||
|
// thing stopping the draw reusing its way past this widget.
|
||||||
|
if let Some(top) = self.mark_readers(id, rsc) {
|
||||||
self.redraw(top, rsc);
|
self.redraw(top, rsc);
|
||||||
|
// Cleared by that draw if it reached here; if it did not, this is
|
||||||
|
// no longer drawn and asking again would not end.
|
||||||
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
|
|
||||||
if self.draw_started.contains(&id) {
|
if self.draw_started.contains(&id) {
|
||||||
return;
|
return;
|
||||||
@@ -379,8 +363,8 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The furthest ancestor that read this widget's size, directly or through
|
/// The furthest ancestor that read this widget's size, directly or through
|
||||||
/// widgets that did the same.
|
/// widgets that did the same, marking everything below it on the way.
|
||||||
fn highest_reader(&self, id: WidgetId) -> Option<WidgetId> {
|
fn mark_readers(&self, id: WidgetId, rsc: &mut dyn UiRsc) -> Option<WidgetId> {
|
||||||
let mut top = None;
|
let mut top = None;
|
||||||
let mut at = id;
|
let mut at = id;
|
||||||
while let Some(active) = self.active.get(&at)
|
while let Some(active) = self.active.get(&at)
|
||||||
@@ -390,6 +374,7 @@ impl UiRenderState {
|
|||||||
.get(&parent)
|
.get(&parent)
|
||||||
.is_some_and(|p| p.size_deps.contains(&at))
|
.is_some_and(|p| p.size_deps.contains(&at))
|
||||||
{
|
{
|
||||||
|
rsc.widgets_mut().needs_redraw.insert(at);
|
||||||
top = Some(parent);
|
top = Some(parent);
|
||||||
at = parent;
|
at = parent;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-22
@@ -1,33 +1,21 @@
|
|||||||
use std::ops::*;
|
pub const trait LerpUtil: Sized {
|
||||||
|
|
||||||
pub const trait LerpUtil {
|
|
||||||
fn lerp(self, from: Self, to: Self) -> Self;
|
fn lerp(self, from: Self, to: Self) -> Self;
|
||||||
fn lerp_inv(self, from: Self, to: Self) -> Self;
|
fn lerp_inv(self, from: Self, to: Self) -> Option<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const trait DivOr {
|
const impl LerpUtil for f32 {
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
const impl DivOr for f32 {
|
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
|
||||||
let res = self / rhs;
|
|
||||||
if res.is_nan() { other } else { res }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const impl<
|
|
||||||
T: const Add<Output = T> + const Sub<Output = T> + const Mul<Output = T> + const DivOr + Copy,
|
|
||||||
> LerpUtil for T
|
|
||||||
{
|
|
||||||
/// linear interpolation
|
/// linear interpolation
|
||||||
/// from * (1.0 - self) + to * self
|
/// from * (1.0 - self) + to * self
|
||||||
fn lerp(self, from: Self, to: Self) -> Self {
|
fn lerp(self, from: Self, to: Self) -> Self {
|
||||||
from + (to - from) * self
|
from + (to - from) * self
|
||||||
}
|
}
|
||||||
/// inverse of lerp
|
/// inverse of lerp, and `None` where `from` and `to` are the same point:
|
||||||
fn lerp_inv(self, from: Self, to: Self) -> Self {
|
/// every input lerps to it, so there is no one answer to come back to.
|
||||||
(self - from).div_or(to - from, from)
|
fn lerp_inv(self, from: Self, to: Self) -> Option<Self> {
|
||||||
|
match to == from {
|
||||||
|
true => None,
|
||||||
|
false => Some((self - from) / (to - from)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-10
@@ -1,4 +1,4 @@
|
|||||||
use crate::util::{DivOr, impl_op};
|
use crate::util::impl_op;
|
||||||
use std::{hash::Hash, ops::*};
|
use std::{hash::Hash, ops::*};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -67,15 +67,6 @@ impl_op!(Vec2 Sub sub; x y);
|
|||||||
impl_op!(Vec2 Mul mul; x y);
|
impl_op!(Vec2 Mul mul; x y);
|
||||||
impl_op!(Vec2 Div div; x y);
|
impl_op!(Vec2 Div div; x y);
|
||||||
|
|
||||||
const impl DivOr for Vec2 {
|
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
|
||||||
Self {
|
|
||||||
x: self.x.div_or(rhs.x, other.x),
|
|
||||||
y: self.y.div_or(rhs.y, other.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Neg for Vec2 {
|
impl Neg for Vec2 {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
|||||||
+17
-21
@@ -15,25 +15,20 @@ 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 may be done to a widget's drawing when the box it was given changes
|
||||||
/// what has to change before it must be drawn again. Asked per axis, because
|
/// on this axis, instead of drawing it again. Asked per axis, because wrapped
|
||||||
/// wrapped text depends on the width it is offered and not on the height.
|
/// text reads the width it is offered and not the height.
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum SizeDependence {
|
pub enum OnResize {
|
||||||
/// None of it: the box only says where the primitives go, so a new one is
|
Scale,
|
||||||
/// written into them instead of drawn.
|
Translate,
|
||||||
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]
|
#[default]
|
||||||
Internal,
|
Redraw,
|
||||||
/// 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`.
|
/// Draws the widget, and returns what it used of the box it was given.
|
||||||
fn draw(&mut self, painter: &mut Painter);
|
fn draw(&mut self, painter: &mut Painter) -> Size;
|
||||||
|
|
||||||
/// An exact length the widget can give without a painter or its children.
|
/// 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
|
/// Optional, and saves a draw rather than changing one: a hint that
|
||||||
@@ -42,22 +37,23 @@ pub trait Widget: Any {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
|
fn on_resize(&self, _axis: Axis) -> OnResize {
|
||||||
SizeDependence::Internal
|
OnResize::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for () {
|
impl Widget for () {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
/// A gap: nothing drawn, at the default length, so a span gives it a share.
|
||||||
painter.set_size(Size::ZERO);
|
fn draw(&mut self, _: &mut Painter) -> Size {
|
||||||
|
Size::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
Some(Len::ZERO)
|
Some(Len::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
|
fn on_resize(&self, _axis: Axis) -> OnResize {
|
||||||
SizeDependence::None
|
OnResize::Scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -6,17 +6,17 @@ 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) -> Size {
|
||||||
painter.primitive(&self.handle);
|
painter.primitive(&self.handle);
|
||||||
painter.set_size(Size::abs(self.handle.size()));
|
Size::abs(self.handle.size())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
||||||
Some(Len::abs(self.handle.size().axis(axis)))
|
Some(Len::abs(self.handle.size().axis(axis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
fn on_resize(&self, _: Axis) -> OnResize {
|
||||||
SizeDependence::None
|
OnResize::Scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -5,14 +5,13 @@ 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) -> Size {
|
||||||
painter.set_mask(painter.region());
|
painter.set_mask(painter.region());
|
||||||
let size = painter.widget(&self.inner).size();
|
painter.widget(&self.inner).size()
|
||||||
painter.set_size(size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// It clips to the box it was given, not to the part its child used.
|
/// It clips to the box it was given, not to the part its child used.
|
||||||
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
fn on_resize(&self, _: Axis) -> OnResize {
|
||||||
SizeDependence::External
|
OnResize::Redraw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ 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) -> Size {
|
||||||
// Drawn where it may be too big, then given its aligned box once its
|
// Drawn where it may be too big, then given its aligned box once its
|
||||||
// size is known.
|
// size is known.
|
||||||
let size = painter.widget(&self.inner).size();
|
let size = painter.widget(&self.inner).size();
|
||||||
@@ -16,7 +16,7 @@ impl Widget for Aligned {
|
|||||||
(None, Some(y)) => UiRegion::new(UiSpan::FULL, size.y.apply_rest().align(y)),
|
(None, Some(y)) => UiRegion::new(UiSpan::FULL, size.y.apply_rest().align(y)),
|
||||||
(None, None) => UiRegion::FULL,
|
(None, None) => UiRegion::FULL,
|
||||||
};
|
};
|
||||||
painter.place(&self.inner, region);
|
painter.widget_within(&self.inner, region);
|
||||||
painter.set_size(size);
|
size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,10 @@ pub struct LayerOffset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for LayerOffset {
|
impl Widget for LayerOffset {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
for _ in 0..self.offset {
|
for _ in 0..self.offset {
|
||||||
painter.next_layer();
|
painter.next_layer();
|
||||||
}
|
}
|
||||||
let size = painter.widget(&self.inner).size();
|
painter.widget(&self.inner).size()
|
||||||
painter.set_size(size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,16 +6,14 @@ pub struct MaxSize {
|
|||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaxSize {}
|
|
||||||
|
|
||||||
impl Widget for MaxSize {
|
impl Widget for MaxSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let child = painter.widget(&self.inner).size();
|
let child = painter.widget(&self.inner).size();
|
||||||
let output = painter.output_size();
|
let output = painter.output_size();
|
||||||
painter.set_size(Size {
|
Size {
|
||||||
x: capped(child.x, self.x, output.x),
|
x: capped(child.x, self.x, output.x),
|
||||||
y: capped(child.y, self.y, output.y),
|
y: capped(child.y, self.y, output.y),
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ 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) -> Size {
|
||||||
let region = UiRegion::FULL.offset(self.amt);
|
let region = UiRegion::FULL.offset(self.amt);
|
||||||
let size = painter.widget_within(&self.inner, region).size();
|
painter.widget_within(&self.inner, region).size()
|
||||||
painter.set_size(size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,11 @@ 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) -> Size {
|
||||||
let inner = painter
|
let inner = painter
|
||||||
.widget_within(&self.inner, self.padding.region())
|
.widget_within(&self.inner, self.padding.region())
|
||||||
.size();
|
.size();
|
||||||
painter.set_size(Size {
|
Size {
|
||||||
x: Len {
|
x: Len {
|
||||||
abs: inner.x.abs + self.padding.left + self.padding.right,
|
abs: inner.x.abs + self.padding.left + self.padding.right,
|
||||||
..inner.x
|
..inner.x
|
||||||
@@ -19,7 +19,7 @@ impl Widget for Pad {
|
|||||||
abs: inner.y.abs + self.padding.top + self.padding.bottom,
|
abs: inner.y.abs + self.padding.top + self.padding.bottom,
|
||||||
..inner.y
|
..inner.y
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub struct Scroll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Scroll {
|
impl Widget for Scroll {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
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();
|
||||||
// Drawn in the whole container to learn its length, then placed at
|
// Drawn in the whole container to learn its length, then placed at
|
||||||
@@ -31,8 +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.place(&self.inner, region);
|
painter.widget_within(&self.inner, region);
|
||||||
painter.set_size(child);
|
child
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,13 @@ pub struct SetSize {
|
|||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetSize {}
|
|
||||||
|
|
||||||
impl Widget for SetSize {
|
impl Widget for SetSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let child = painter.widget(&self.inner).size();
|
let child = painter.widget(&self.inner).size();
|
||||||
painter.set_size(Size {
|
Size {
|
||||||
x: self.x.unwrap_or(child.x),
|
x: self.x.unwrap_or(child.x),
|
||||||
y: self.y.unwrap_or(child.y),
|
y: self.y.unwrap_or(child.y),
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A declared axis is known without looking at the child, which is what
|
/// A declared axis is known without looking at the child, which is what
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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) -> Size {
|
||||||
let axis = self.dir.axis;
|
let axis = self.dir.axis;
|
||||||
// A length for every child before any is placed: from its own hint
|
// 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.
|
// where it has one, and from drawing it where it does not.
|
||||||
@@ -42,7 +42,7 @@ impl Widget for Span {
|
|||||||
if self.dir.sign == Sign::Neg {
|
if self.dir.sign == Sign::Neg {
|
||||||
region.flip(axis);
|
region.flip(axis);
|
||||||
}
|
}
|
||||||
let used = painter.place(child, region).axis(!axis);
|
let used = painter.widget_within(child, region).size().axis(!axis);
|
||||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
||||||
if used.rel > 0.0 || used.rest > 0.0 {
|
if used.rel > 0.0 || used.rest > 0.0 {
|
||||||
ortho = Len::REST;
|
ortho = Len::REST;
|
||||||
@@ -56,7 +56,7 @@ impl Widget for Span {
|
|||||||
true => total,
|
true => total,
|
||||||
false => Len::default(),
|
false => Len::default(),
|
||||||
};
|
};
|
||||||
painter.set_size(Size::from_axis(axis, along, ortho));
|
Size::from_axis(axis, along, ortho)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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) -> Size {
|
||||||
let sizing = match self.size {
|
let sizing = match self.size {
|
||||||
StackSize::Default => None,
|
StackSize::Default => None,
|
||||||
StackSize::Child(i) => Some(i),
|
StackSize::Child(i) => Some(i),
|
||||||
@@ -26,7 +26,7 @@ impl Widget for Stack {
|
|||||||
size = drawn.size();
|
size = drawn.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
painter.set_size(size);
|
size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -6,12 +6,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) -> Size {
|
||||||
let size = match &self.inner {
|
match &self.inner {
|
||||||
Some(id) => painter.widget(id).size(),
|
Some(id) => painter.widget(id).size(),
|
||||||
None => Size::ZERO,
|
None => Size::default(),
|
||||||
};
|
}
|
||||||
painter.set_size(size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -28,14 +28,14 @@ impl Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Rect {
|
impl Widget for Rect {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
painter.primitive(RectPrimitive {
|
painter.primitive(RectPrimitive {
|
||||||
color: self.color,
|
color: self.color,
|
||||||
radius: self.radius,
|
radius: self.radius,
|
||||||
thickness: self.thickness,
|
thickness: self.thickness,
|
||||||
inner_radius: self.inner_radius,
|
inner_radius: self.inner_radius,
|
||||||
});
|
});
|
||||||
painter.set_size(Size::REST);
|
Size::REST
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, _: Axis) -> Option<Len> {
|
fn size_hint(&self, _: Axis) -> Option<Len> {
|
||||||
@@ -43,8 +43,8 @@ impl Widget for Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Its box is its primitive's own region, so a new one is written there.
|
/// Its box is its primitive's own region, so a new one is written there.
|
||||||
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
fn on_resize(&self, _: Axis) -> OnResize {
|
||||||
SizeDependence::None
|
OnResize::Scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-9
@@ -55,41 +55,47 @@ impl TextEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for TextEdit {
|
impl Widget for TextEdit {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let base = painter.layer;
|
let base = painter.layer;
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
let (_, size) = 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();
|
||||||
|
|
||||||
let Some(selection) = self.selection else {
|
let Some(selection) = self.selection else {
|
||||||
return;
|
return size;
|
||||||
};
|
};
|
||||||
let layout = self.view.buf.layout();
|
let layout = self.view.buf.layout();
|
||||||
|
|
||||||
// parley reports selection as boxes in layout space, so bidi and
|
// parley reports selection as boxes in layout space, so bidi and
|
||||||
// wrapped lines come out right without this code knowing about either.
|
// wrapped lines come out right without this code knowing about either.
|
||||||
for (rect, _) in selection.geometry(layout) {
|
for (rect, _) in selection.geometry(layout) {
|
||||||
let size = vec2(rect.width() as f32, rect.height() as f32);
|
let rect_size = vec2(rect.width() as f32, rect.height() as f32);
|
||||||
let top_left = vec2(rect.x0 as f32, rect.y0 as f32);
|
let top_left = vec2(rect.x0 as f32, rect.y0 as f32);
|
||||||
painter.primitive_within(
|
painter.primitive_within(
|
||||||
RectPrimitive::color(Color::SKY),
|
RectPrimitive::color(Color::SKY),
|
||||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
rect_size
|
||||||
|
.align(Align::TOP_LEFT)
|
||||||
|
.offset(top_left)
|
||||||
|
.within(®ion),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let caret = selection.focus().geometry(layout, CARET_WIDTH);
|
let caret = selection.focus().geometry(layout, CARET_WIDTH);
|
||||||
let size = vec2(caret.width() as f32, caret.height() as f32);
|
let caret_size = vec2(caret.width() as f32, caret.height() as f32);
|
||||||
let top_left = vec2(caret.x0 as f32, caret.y0 as f32);
|
let top_left = vec2(caret.x0 as f32, caret.y0 as f32);
|
||||||
painter.primitive_within(
|
painter.primitive_within(
|
||||||
RectPrimitive::color(Color::WHITE),
|
RectPrimitive::color(Color::WHITE),
|
||||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
caret_size
|
||||||
|
.align(Align::TOP_LEFT)
|
||||||
|
.offset(top_left)
|
||||||
|
.within(®ion),
|
||||||
);
|
);
|
||||||
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_dependence(&self, axis: Axis) -> SizeDependence {
|
fn on_resize(&self, axis: Axis) -> OnResize {
|
||||||
self.view.size_dependence(axis)
|
self.view.on_resize(axis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-11
@@ -89,12 +89,19 @@ impl TextView {
|
|||||||
(region, size)
|
(region, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapping reads the width it is offered, so a wider box reshapes it; a
|
/// Wrapping reads the width it is offered, so a wider box reshapes it and
|
||||||
/// taller one never does.
|
/// a taller one does not. Alignment matters too, and separately: glyphs
|
||||||
pub fn size_dependence(&self, axis: Axis) -> SizeDependence {
|
/// anchored to the start of an axis stay put when that extent changes,
|
||||||
match axis == Axis::X && self.attrs.wrap {
|
/// but centred or end-aligned ones move even though the shaping stands.
|
||||||
true => SizeDependence::External,
|
pub fn on_resize(&self, axis: Axis) -> OnResize {
|
||||||
false => SizeDependence::Internal,
|
let reshapes = axis == Axis::X && self.attrs.wrap;
|
||||||
|
let anchored = match axis {
|
||||||
|
Axis::X => self.align.x,
|
||||||
|
Axis::Y => self.align.y,
|
||||||
|
} == AxisAlign::Neg;
|
||||||
|
match reshapes || !anchored {
|
||||||
|
true => OnResize::Redraw,
|
||||||
|
false => OnResize::Translate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,14 +127,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) -> Size {
|
||||||
self.update_buf();
|
self.update_buf();
|
||||||
let (_, size) = self.view.draw(painter);
|
self.view.draw(painter).1
|
||||||
painter.set_size(size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_dependence(&self, axis: Axis) -> SizeDependence {
|
fn on_resize(&self, axis: Axis) -> OnResize {
|
||||||
self.view.size_dependence(axis)
|
self.view.on_resize(axis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,3 +32,80 @@ fn resizing_relays_out_against_the_new_output() {
|
|||||||
assert_corners!(h, left, (0, 0), (100, 100));
|
assert_corners!(h, left, (0, 0), (100, 100));
|
||||||
assert_corners!(h, right, (100, 0), (800, 100));
|
assert_corners!(h, right, (100, 0), (800, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_empty_widget_takes_a_share_of_a_span() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let gap = ().add(&mut h.rsc);
|
||||||
|
let right = rect(Color::BLUE).width(100).add(&mut h.rsc);
|
||||||
|
h.set_root((gap, right).span(Dir::RIGHT));
|
||||||
|
|
||||||
|
assert_corners!(h, gap, (0, 0), (300, 200));
|
||||||
|
assert_corners!(h, right, (300, 0), (400, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_child_drawn_twice_moves_once() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
// `Aligned` draws its child twice; listing it twice would move it twice.
|
||||||
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let centered = inner.center().width(200).add(&mut h.rsc);
|
||||||
|
let left = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
|
h.set_root((left, centered).span(Dir::RIGHT));
|
||||||
|
assert_corners!(h, inner, (100, 0), (300, 200));
|
||||||
|
|
||||||
|
h.rsc[left].x = Some(Len::abs(150));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_corners!(h, inner, (150, 0), (350, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_resize_lands_where_a_cold_start_would() {
|
||||||
|
let build = |h: &mut Harness| {
|
||||||
|
let para = wtext(
|
||||||
|
"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.",
|
||||||
|
)
|
||||||
|
.size(20)
|
||||||
|
.wrap(true)
|
||||||
|
.pad(16)
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let below = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
let root = (para, below).span(Dir::DOWN).pad(12);
|
||||||
|
h.set_root(root);
|
||||||
|
(para, below)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cold = Harness::new((900, 1200));
|
||||||
|
let (cold_para, cold_below) = build(&mut cold);
|
||||||
|
|
||||||
|
let mut resized = Harness::new((1920, 1200));
|
||||||
|
let (para, below) = build(&mut resized);
|
||||||
|
resized.resize((900, 1200));
|
||||||
|
resized.frame();
|
||||||
|
|
||||||
|
assert_eq!(resized.region(¶), cold.region(&cold_para), "paragraph");
|
||||||
|
assert_eq!(resized.region(&below), cold.region(&cold_below), "below");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_fixed_box_is_drawn_again_rather_than_stretched() {
|
||||||
|
let mut h = Harness::new((400, 400));
|
||||||
|
// The panel fills a stack sized by its sibling, so it is drawn in the
|
||||||
|
// whole box and then placed in the shorter one. Reusing it in that fixed
|
||||||
|
// box afterwards would leave it whatever height it happened to have.
|
||||||
|
let panel = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let leaf = rect(Color::RED).height(100).add(&mut h.rsc);
|
||||||
|
let stack = (panel, leaf)
|
||||||
|
.stack()
|
||||||
|
.size(StackSize::Child(1))
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(stack.align(Align::TOP));
|
||||||
|
assert_corners!(h, panel, (0, 0), (400, 100));
|
||||||
|
|
||||||
|
h.rsc[leaf].y = Some(Len::abs(250));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_corners!(h, panel, (0, 0), (400, 250));
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//! What a drawing can be taken out of, and what it cannot.
|
||||||
|
|
||||||
|
use iris::core::{Remap, UiRegion, UiScalar, UiSpan};
|
||||||
|
|
||||||
|
/// A box `size` tall whose top is `rel` of the way down the window.
|
||||||
|
fn fixed(rel: f32, size: f32) -> UiRegion {
|
||||||
|
UiRegion::new(
|
||||||
|
UiSpan::FULL,
|
||||||
|
UiSpan::new(UiScalar { rel, abs: 0.0 }, UiScalar { rel, abs: size }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_fixed_box_can_be_carried_but_not_stretched() {
|
||||||
|
let from = fixed(0.0, 164.0);
|
||||||
|
assert!(Remap::new(from, UiRegion::FULL).is_none());
|
||||||
|
assert!(Remap::new(from, fixed(0.5, 164.0)).is_some());
|
||||||
|
assert!(Remap::new(from, fixed(0.0, 98.0)).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_relative_box_can_be_stretched_to_any_other() {
|
||||||
|
let remap = Remap::new(UiRegion::FULL, fixed(0.0, 98.0)).expect("relative boxes remap");
|
||||||
|
// A part that filled the window keeps filling what replaced it, which is
|
||||||
|
// exactly what `outside` could not say for a box of a fixed length.
|
||||||
|
assert_eq!(remap.apply(UiRegion::FULL), fixed(0.0, 98.0));
|
||||||
|
}
|
||||||
+131
-16
@@ -10,16 +10,16 @@ use iris::prelude::*;
|
|||||||
struct Counted {
|
struct Counted {
|
||||||
draws: Rc<Cell<usize>>,
|
draws: Rc<Cell<usize>>,
|
||||||
size: Size,
|
size: Size,
|
||||||
dependence: SizeDependence,
|
dependence: OnResize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Counted {
|
impl Widget for Counted {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, _: &mut Painter) -> Size {
|
||||||
self.draws.set(self.draws.get() + 1);
|
self.draws.set(self.draws.get() + 1);
|
||||||
painter.set_size(self.size);
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_dependence(&self, _: Axis) -> SizeDependence {
|
fn on_resize(&self, _: Axis) -> OnResize {
|
||||||
self.dependence
|
self.dependence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,11 +32,7 @@ impl Counts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn counted(
|
fn counted(h: &mut Harness, size: Size, dependence: OnResize) -> (WeakWidget<Counted>, Counts) {
|
||||||
h: &mut Harness,
|
|
||||||
size: Size,
|
|
||||||
dependence: SizeDependence,
|
|
||||||
) -> (WeakWidget<Counted>, Counts) {
|
|
||||||
let draws = Rc::new(Cell::new(0));
|
let draws = Rc::new(Cell::new(0));
|
||||||
let id = Counted {
|
let id = Counted {
|
||||||
draws: draws.clone(),
|
draws: draws.clone(),
|
||||||
@@ -49,8 +45,8 @@ fn counted(
|
|||||||
|
|
||||||
/// A fixed-width leaf beside one that takes the rest, so changing the first
|
/// A fixed-width leaf beside one that takes the rest, so changing the first
|
||||||
/// hands the second a different box without the output changing.
|
/// hands the second a different box without the output changing.
|
||||||
fn pair(h: &mut Harness, rest: SizeDependence) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
fn pair(h: &mut Harness, rest: OnResize) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
||||||
let (first, _) = counted(h, Size::from((100, 200)), SizeDependence::Internal);
|
let (first, _) = counted(h, Size::from((100, 200)), OnResize::Translate);
|
||||||
let (second, draws) = counted(h, Size::REST, rest);
|
let (second, draws) = counted(h, Size::REST, rest);
|
||||||
h.set_root((first, second).span(Dir::RIGHT));
|
h.set_root((first, second).span(Dir::RIGHT));
|
||||||
(first, draws, second.id())
|
(first, draws, second.id())
|
||||||
@@ -59,7 +55,7 @@ fn pair(h: &mut Harness, rest: SizeDependence) -> (WeakWidget<Counted>, Counts,
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, draws, second) = pair(&mut h, SizeDependence::None);
|
let (first, draws, second) = pair(&mut h, OnResize::Scale);
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
assert_corners!(h, second, (100, 0), (400, 200));
|
assert_corners!(h, second, (100, 0), (400, 200));
|
||||||
|
|
||||||
@@ -77,7 +73,7 @@ fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, draws, second) = pair(&mut h, SizeDependence::External);
|
let (first, draws, second) = pair(&mut h, OnResize::Redraw);
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
|
|
||||||
h.rsc[first].size = Size::from((150, 200));
|
h.rsc[first].size = Size::from((150, 200));
|
||||||
@@ -93,8 +89,8 @@ fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_span_child_that_declares_its_length_is_drawn_once() {
|
fn a_span_child_that_declares_its_length_is_drawn_once() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), SizeDependence::Internal);
|
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
|
||||||
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), SizeDependence::Internal);
|
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
|
||||||
// The span takes one child's length from its hint and has to draw the
|
// 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.
|
// other to find out, so only the second is drawn before it is placed.
|
||||||
let hinted = told.width(100).add(&mut h.rsc);
|
let hinted = told.width(100).add(&mut h.rsc);
|
||||||
@@ -111,7 +107,7 @@ fn a_span_child_that_declares_its_length_is_drawn_once() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_span_relays_out_when_a_child_it_measured_changes() {
|
fn a_span_relays_out_when_a_child_it_measured_changes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, _, second) = pair(&mut h, SizeDependence::Internal);
|
let (first, _, second) = pair(&mut h, OnResize::Translate);
|
||||||
|
|
||||||
h.rsc[first].size = Size::from((250, 200));
|
h.rsc[first].size = Size::from((250, 200));
|
||||||
h.frame();
|
h.frame();
|
||||||
@@ -135,3 +131,122 @@ fn a_placed_child_survives_the_next_frame() {
|
|||||||
assert_corners!(h, top, (0, 0), (400, 80));
|
assert_corners!(h, top, (0, 0), (400, 80));
|
||||||
assert_corners!(h, bottom, (0, 80), (400, 200));
|
assert_corners!(h, bottom, (0, 80), (400, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lays its child out from the hint alone, never reading what it drew.
|
||||||
|
struct FromHint {
|
||||||
|
inner: StrongWidget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for FromHint {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
|
||||||
|
let mut region = UiRegion::FULL;
|
||||||
|
region.y.end = region.y.start.offset(len.abs);
|
||||||
|
painter.widget_within(&self.inner, region);
|
||||||
|
Size::REST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_parent_that_only_read_a_hint_relays_out_when_the_hint_changes() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let inner = rect(Color::RED).height(80).add(&mut h.rsc);
|
||||||
|
let parent = FromHint {
|
||||||
|
inner: inner.add_strong(&mut h.rsc),
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(parent);
|
||||||
|
assert_corners!(h, inner, (0, 0), (400, 80));
|
||||||
|
|
||||||
|
h.rsc[inner].y = Some(Len::abs(120));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_corners!(h, inner, (0, 0), (400, 120));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the output's size, which nothing but its own draw can put right.
|
||||||
|
struct ReadsOutput {
|
||||||
|
draws: Rc<Cell<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for ReadsOutput {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
self.draws.set(self.draws.get() + 1);
|
||||||
|
Size::abs(painter.output_size() / 4.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_resize_does_not_redraw_what_the_shader_can_move() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let (leaf, draws) = counted(&mut h, Size::REST, OnResize::Redraw);
|
||||||
|
h.set_root(leaf);
|
||||||
|
let settled = draws.get();
|
||||||
|
|
||||||
|
h.resize((800, 100));
|
||||||
|
assert!(h.needs_redraw());
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
draws.get(),
|
||||||
|
settled,
|
||||||
|
"its box is the same fraction of a different output"
|
||||||
|
);
|
||||||
|
assert_corners!(h, leaf, (0, 0), (800, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_resize_redraws_what_read_the_output() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let draws = Rc::new(Cell::new(0));
|
||||||
|
let leaf = ReadsOutput {
|
||||||
|
draws: draws.clone(),
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(leaf);
|
||||||
|
let settled = draws.get();
|
||||||
|
|
||||||
|
h.resize((800, 100));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_eq!(draws.get(), settled + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn narrowing_the_output_reflows_text_and_relays_out_around_it() {
|
||||||
|
let mut h = Harness::new((600, 400));
|
||||||
|
let para = wtext(
|
||||||
|
"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.",
|
||||||
|
)
|
||||||
|
.size(20)
|
||||||
|
.wrap(true)
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let below = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
h.set_root((para, below).span(Dir::DOWN));
|
||||||
|
let top = h.region(&below).expect("drew nothing").top_left.y;
|
||||||
|
|
||||||
|
h.resize((300, 400));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
let lower = h.region(&below).expect("drew nothing").top_left.y;
|
||||||
|
assert!(lower > top, "same words, half the width: {top} -> {lower}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_change_two_levels_under_its_reader_still_reaches_it() {
|
||||||
|
let mut h = Harness::new((400, 400));
|
||||||
|
// Every wrapper up to the outer pad read the size below it, so the outer
|
||||||
|
// pad is what draws again -- and the span it hands the box to is the same
|
||||||
|
// size as before, which is what lets a draw reuse its way past the leaf.
|
||||||
|
let (leaf, _) = counted(&mut h, Size::abs((100, 100).into()), OnResize::Redraw);
|
||||||
|
let padded = leaf.pad(10).add(&mut h.rsc);
|
||||||
|
let below = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
h.set_root((padded, below).span(Dir::DOWN).pad(12));
|
||||||
|
assert_corners!(h, below, (12, 132), (388, 388));
|
||||||
|
|
||||||
|
h.rsc[leaf].size = Size::abs((100, 200).into());
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_corners!(h, below, (12, 232), (388, 388));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user