Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cefc72f97 | ||
|
|
0f9f379cec | ||
|
|
7eb2b85425 | ||
|
|
3a74a04a5b | ||
|
|
e97aba30e0 | ||
|
|
d5efdd2b97 |
No files matched your search
@@ -79,7 +79,6 @@ type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>
|
|||||||
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
|
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
|
||||||
// TODO: reduce visiblity!!
|
// TODO: reduce visiblity!!
|
||||||
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
|
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
|
||||||
pub global: E::Global,
|
|
||||||
map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
|
map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +107,6 @@ impl<Rsc: HasEvents, E: Event> Default for TypeEventManager<Rsc, E> {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
active: Default::default(),
|
active: Default::default(),
|
||||||
global: Default::default(),
|
|
||||||
map: Default::default(),
|
map: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,13 +138,11 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
|||||||
pub fn run_fn<'a>(
|
pub fn run_fn<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: impl IdLike,
|
id: impl IdLike,
|
||||||
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) -> bool + 'a {
|
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) + 'a {
|
||||||
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
||||||
move |ctx, rsc| {
|
move |ctx, rsc| {
|
||||||
let mut consumed = false;
|
|
||||||
for (e, f) in fs {
|
for (e, f) in fs {
|
||||||
if let Some(data) = e.should_run(&ctx.data) {
|
if let Some(data) = e.should_run(&ctx.data) {
|
||||||
consumed |= e.consumes(&data);
|
|
||||||
f(
|
f(
|
||||||
EventCtx {
|
EventCtx {
|
||||||
state: ctx.state,
|
state: ctx.state,
|
||||||
@@ -156,7 +152,6 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
consumed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,19 +9,10 @@ pub use rsc::*;
|
|||||||
pub trait Event: Sized + 'static + Clone {
|
pub trait Event: Sized + 'static + Clone {
|
||||||
type Data<'a>: Clone = ();
|
type Data<'a>: Clone = ();
|
||||||
type State: Default = ();
|
type State: Default = ();
|
||||||
/// State the whole event type keeps, rather than one copy per widget.
|
|
||||||
type Global: Default = ();
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||||
Some(data.clone())
|
Some(data.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether having run on this data uses up whatever triggered it, so
|
|
||||||
/// nothing further should see it.
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn consumes(&self, data: &Self::Data<'_>) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EventLike {
|
pub trait EventLike {
|
||||||
|
|||||||
@@ -21,13 +21,12 @@ pub trait HasEvents: Sized + UiRsc + HasState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait RunEvents: HasEvents {
|
pub trait RunEvents: HasEvents {
|
||||||
/// Whether anything that ran used up what triggered it.
|
|
||||||
fn run_event<E: EventLike>(
|
fn run_event<E: EventLike>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: impl IdLike,
|
id: impl IdLike,
|
||||||
data: <E::Event as Event>::Data<'_>,
|
data: <E::Event as Event>::Data<'_>,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
) -> bool {
|
) {
|
||||||
let f = self.events_mut().get_type::<E>().run_fn(id);
|
let f = self.events_mut().get_type::<E>().run_fn(id);
|
||||||
f(EventCtx { state, data }, self)
|
f(EventCtx { state, data }, self)
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-55
@@ -56,6 +56,13 @@ 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,
|
||||||
@@ -202,12 +209,10 @@ impl UiScalar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undoes `within`, and `None` where the span has a fixed length: every
|
pub const fn outside(&self, span: &UiSpan) -> Self {
|
||||||
/// fraction of it lands on the same `rel`, so none can be told apart.
|
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel);
|
||||||
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);
|
||||||
Some(Self { rel, abs })
|
Self { rel, abs }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn within_len(&self, len: UiScalar) -> Self {
|
pub fn within_len(&self, len: UiScalar) -> Self {
|
||||||
@@ -278,11 +283,11 @@ impl UiSpan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outside(&self, parent: &Self) -> Option<Self> {
|
pub const fn outside(&self, parent: &Self) -> Self {
|
||||||
Some(Self {
|
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 {
|
||||||
@@ -319,7 +324,14 @@ impl UiRegion {
|
|||||||
y: self.y.within(&parent.y),
|
y: self.y.within(&parent.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const fn axis(&self, axis: Axis) -> &UiSpan {
|
pub const fn outside(&self, parent: &Self) -> Self {
|
||||||
|
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,
|
||||||
@@ -397,50 +409,6 @@ 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!(
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
use crate::{LayerId, MaskIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId};
|
use crate::{LayerId, MaskIdx, PrimitiveHandle, 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>,
|
|
||||||
/// 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,
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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,12 +3,15 @@ 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 {
|
||||||
|
|||||||
+25
-70
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||||
UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId,
|
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
||||||
render::{
|
render::{
|
||||||
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
||||||
TexturePrimitive,
|
TexturePrimitive,
|
||||||
@@ -18,9 +18,6 @@ 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) reads_output: bool,
|
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub(super) id: WidgetId,
|
pub(super) id: WidgetId,
|
||||||
}
|
}
|
||||||
@@ -71,31 +68,19 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
/// Draws a widget within this widget's region.
|
||||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
|
||||||
self.widget_at(id, self.region)
|
self.widget_at(id, self.region);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget somewhere within this one. Drawing one a second time
|
/// Draws a widget somewhere within this one.
|
||||||
/// gives it a new box, keeping the drawing it already has where it can.
|
/// Useful for drawing child widgets in select areas.
|
||||||
pub fn widget_within<'s, W: ?Sized>(
|
pub fn widget_within<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
||||||
&'s mut self,
|
self.widget_at(id, region.within(&self.region));
|
||||||
id: &'s StrongWidget<W>,
|
|
||||||
region: UiRegion,
|
|
||||||
) -> DrawResult<'s, 'a, W> {
|
|
||||||
let region = region.within(&self.region);
|
|
||||||
self.widget_at(id, region)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_at<'s, W: ?Sized>(
|
fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
||||||
&'s mut self,
|
self.children.push(id.id());
|
||||||
id: &'s StrongWidget<W>,
|
self.state.draw_inner(
|
||||||
region: UiRegion,
|
|
||||||
) -> DrawResult<'s, 'a, W> {
|
|
||||||
// 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(
|
|
||||||
self.layer,
|
self.layer,
|
||||||
id.id(),
|
id.id(),
|
||||||
region,
|
region,
|
||||||
@@ -104,25 +89,6 @@ impl<'a> Painter<'a> {
|
|||||||
None,
|
None,
|
||||||
self.rsc,
|
self.rsc,
|
||||||
);
|
);
|
||||||
DrawResult {
|
|
||||||
child: id,
|
|
||||||
painter: self,
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What a child says its length is without being drawn, if it can say.
|
|
||||||
/// Asking counts as reading its size.
|
|
||||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
|
||||||
let hint = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis)?;
|
|
||||||
self.depend_on_size(id);
|
|
||||||
Some(hint)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn depend_on_size<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
|
||||||
if !self.size_deps.contains(&child.id()) {
|
|
||||||
self.size_deps.push(child.id());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_text(
|
pub fn render_text(
|
||||||
@@ -163,17 +129,22 @@ impl<'a> Painter<'a> {
|
|||||||
self.region
|
self.region
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The output's size in pixels. A widget that reads it draws again when
|
pub fn size<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>) -> Size {
|
||||||
/// the output changes, since nothing else can put that right.
|
self.size_ctx().size(id)
|
||||||
pub fn output_size(&mut self) -> Vec2 {
|
}
|
||||||
self.reads_output = true;
|
|
||||||
|
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 {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,25 +167,9 @@ impl<'a> Painter<'a> {
|
|||||||
pub fn id(&self) -> &WidgetId {
|
pub fn id(&self) -> &WidgetId {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// A child that has just been drawn. Reading its size records that this
|
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
|
||||||
/// widget's own size depends on it; dropping it without reading draws the
|
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
||||||
/// child and leaves the parent independent of what it came to.
|
|
||||||
pub struct DrawResult<'p, 'a, W: ?Sized> {
|
|
||||||
painter: &'p mut Painter<'a>,
|
|
||||||
child: &'p StrongWidget<W>,
|
|
||||||
size: Size,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: ?Sized> DrawResult<'_, '_, W> {
|
|
||||||
pub fn size(self) -> Size {
|
|
||||||
self.painter.depend_on_size(self.child);
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(self, axis: Axis) -> Len {
|
|
||||||
self.size().axis(axis)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+63
-125
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Remap, Size,
|
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, SizeCtx, StrongWidget,
|
||||||
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
||||||
|
ui::cache::Cache,
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ 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,
|
||||||
@@ -19,6 +21,7 @@ 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,
|
||||||
@@ -53,21 +56,11 @@ impl UiRenderState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let root = root.into();
|
let root = root.into();
|
||||||
if self.root_changed(root) {
|
if self.needs_full_redraw(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());
|
||||||
} else if self.resized {
|
self.resized = false;
|
||||||
// A region is a fraction of the output plus an offset, resolved
|
} else if rsc.widgets().has_updates() {
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,11 +84,19 @@ 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 self.active.contains_key(&id) {
|
if let Some(active) = self.active.get_mut(&id)
|
||||||
if let Some(size) = self.try_reuse(id, region, rsc) {
|
&& !rsc.widgets().needs_redraw.contains(&id)
|
||||||
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();
|
||||||
@@ -103,7 +104,6 @@ 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 {
|
||||||
@@ -115,13 +115,11 @@ impl UiRenderState {
|
|||||||
textures: Vec::new(),
|
textures: Vec::new(),
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
size_deps: Vec::new(),
|
|
||||||
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);
|
||||||
let size = widget.draw(&mut painter);
|
widget.draw(&mut painter);
|
||||||
drop(widget);
|
drop(widget);
|
||||||
|
|
||||||
let Painter {
|
let Painter {
|
||||||
@@ -132,29 +130,18 @@ impl UiRenderState {
|
|||||||
textures,
|
textures,
|
||||||
primitives,
|
primitives,
|
||||||
children,
|
children,
|
||||||
size_deps,
|
|
||||||
reads_output,
|
|
||||||
layer,
|
layer,
|
||||||
id,
|
id,
|
||||||
} = painter;
|
} = painter;
|
||||||
|
|
||||||
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,
|
|
||||||
reads_output,
|
|
||||||
mask,
|
mask,
|
||||||
layer,
|
layer,
|
||||||
};
|
};
|
||||||
@@ -168,77 +155,19 @@ impl UiRenderState {
|
|||||||
|
|
||||||
rsc.on_draw(&active);
|
rsc.on_draw(&active);
|
||||||
self.active.insert(id, active);
|
self.active.insert(id, active);
|
||||||
size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The drawing a widget already has, kept for a new box if the box has not
|
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
||||||
/// 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, old) = (active.size, active.region);
|
|
||||||
if old == region {
|
|
||||||
return Some(size);
|
|
||||||
}
|
|
||||||
// TODO: epsilon?
|
|
||||||
if old.size() != region.size() && !self.reusable(id, region, rsc) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Its drawing stands, if the new box can be reached from the old one.
|
|
||||||
self.mov(id, &Remap::new(old, region)?);
|
|
||||||
Some(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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, 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;
|
|
||||||
};
|
|
||||||
[Axis::X, Axis::Y].into_iter().all(|axis| {
|
|
||||||
let offered = region.axis(axis).len();
|
|
||||||
let had = active.region.axis(axis).len();
|
|
||||||
match widget.on_resize(axis) {
|
|
||||||
OnResize::Scale => true,
|
|
||||||
// `Translate` is not acted on yet, and cannot be until a
|
|
||||||
// drawing can sit somewhere other than its box. `region` is
|
|
||||||
// both the box a widget was given and the box its primitives
|
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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, 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 = remap.apply(*region);
|
*region = region.outside(&from).within(&to);
|
||||||
}
|
}
|
||||||
active.region = remap.apply(active.region);
|
active.region = active.region.outside(&from).within(&to);
|
||||||
// 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, remap);
|
self.mov(*child, from, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,6 +191,7 @@ 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 {
|
||||||
@@ -275,6 +205,7 @@ 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();
|
||||||
@@ -291,12 +222,17 @@ 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.root_changed(root) || self.resized || widgets.has_updates()
|
self.needs_full_redraw(root) || widgets.has_updates()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_widgets(&self) -> usize {
|
pub fn active_widgets(&self) -> usize {
|
||||||
@@ -329,19 +265,22 @@ 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) {
|
||||||
self.draw_started.remove(&id);
|
|
||||||
// Whoever read this widget's size may be a different size now, so the
|
|
||||||
// highest reader is what draws. Everything between the two is marked
|
|
||||||
// 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);
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
|
self.draw_started.remove(&id);
|
||||||
|
// check if parent depends on the desired size of this, if so then redraw it first
|
||||||
|
for axis in [Axis::X, Axis::Y] {
|
||||||
|
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
|
||||||
|
&& let Some(current) = self.active.get(&id)
|
||||||
|
&& let Some(pid) = current.parent
|
||||||
|
{
|
||||||
|
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) {
|
||||||
return;
|
return;
|
||||||
@@ -362,23 +301,22 @@ impl UiRenderState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The furthest ancestor that read this widget's size, directly or through
|
pub(super) fn size_ctx<'b>(
|
||||||
/// widgets that did the same, marking everything below it on the way.
|
&'b mut self,
|
||||||
fn mark_readers(&self, id: WidgetId, rsc: &mut dyn UiRsc) -> Option<WidgetId> {
|
source: WidgetId,
|
||||||
let mut top = None;
|
outer: UiVec2,
|
||||||
let mut at = id;
|
rsc: &'b mut dyn UiRsc,
|
||||||
while let Some(active) = self.active.get(&at)
|
) -> SizeCtx<'b> {
|
||||||
&& let Some(parent) = active.parent
|
let ui = rsc.ui_mut();
|
||||||
&& self
|
SizeCtx {
|
||||||
.active
|
source,
|
||||||
.get(&parent)
|
cache: &mut self.cache,
|
||||||
.is_some_and(|p| p.size_deps.contains(&at))
|
text: &mut ui.text,
|
||||||
{
|
widgets: &ui.widgets,
|
||||||
rsc.widgets_mut().needs_redraw.insert(at);
|
outer,
|
||||||
top = Some(parent);
|
output_size: self.output_size,
|
||||||
at = parent;
|
id: source,
|
||||||
}
|
}
|
||||||
top
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-10
@@ -1,21 +1,33 @@
|
|||||||
pub const trait LerpUtil: Sized {
|
use std::ops::*;
|
||||||
|
|
||||||
|
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) -> Option<Self>;
|
fn lerp_inv(self, from: Self, to: Self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl LerpUtil for f32 {
|
pub const trait DivOr {
|
||||||
|
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, and `None` where `from` and `to` are the same point:
|
/// inverse of lerp
|
||||||
/// every input lerps to it, so there is no one answer to come back to.
|
fn lerp_inv(self, from: Self, to: Self) -> Self {
|
||||||
fn lerp_inv(self, from: Self, to: Self) -> Option<Self> {
|
(self - from).div_or(to - from, from)
|
||||||
match to == from {
|
|
||||||
true => None,
|
|
||||||
false => Some((self - from) / (to - from)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -1,4 +1,4 @@
|
|||||||
use crate::util::impl_op;
|
use crate::util::{DivOr, impl_op};
|
||||||
use std::{hash::Hash, ops::*};
|
use std::{hash::Hash, ops::*};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -67,6 +67,15 @@ 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;
|
||||||
|
|
||||||
|
|||||||
+19
-32
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Axis, Len, Painter, Size};
|
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
mod data;
|
mod data;
|
||||||
@@ -15,45 +15,32 @@ pub use tag::*;
|
|||||||
pub use view::*;
|
pub use view::*;
|
||||||
pub use widgets::*;
|
pub use widgets::*;
|
||||||
|
|
||||||
/// What may be done to a widget's drawing when the box it was given changes
|
pub trait Widget: Any {
|
||||||
/// on this axis, instead of drawing it again. Asked per axis, because wrapped
|
fn draw(&mut self, painter: &mut Painter);
|
||||||
/// text reads the width it is offered and not the height.
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
|
||||||
pub enum OnResize {
|
|
||||||
Scale,
|
|
||||||
Translate,
|
|
||||||
#[default]
|
|
||||||
Redraw,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Widget: Any {
|
pub trait WidgetAxisFns {
|
||||||
/// Draws the widget, and returns what it used of the box it was given.
|
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len;
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size;
|
}
|
||||||
|
|
||||||
/// An exact length the widget can give without a painter or its children.
|
impl<W: Widget + ?Sized> WidgetAxisFns for W {
|
||||||
/// Optional, and saves a draw rather than changing one: a hint that
|
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
/// disagrees with the eventual draw fails a debug assertion.
|
match A::get() {
|
||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
Axis::X => self.desired_width(ctx),
|
||||||
None
|
Axis::Y => self.desired_height(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _axis: Axis) -> OnResize {
|
|
||||||
OnResize::default()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for () {
|
impl Widget for () {
|
||||||
/// A gap: nothing drawn, at the default length, so a span gives it a share.
|
fn draw(&mut self, _: &mut Painter) {}
|
||||||
fn draw(&mut self, _: &mut Painter) -> Size {
|
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
Size::default()
|
Len::ZERO
|
||||||
}
|
}
|
||||||
|
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
Len::ZERO
|
||||||
Some(Len::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_resize(&self, _axis: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
//! 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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-1
@@ -235,7 +235,8 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
|||||||
ui_state.focus = None;
|
ui_state.focus = None;
|
||||||
}
|
}
|
||||||
if input_changed {
|
if input_changed {
|
||||||
render.run_sensors(rsc, state, cursor_state);
|
let window_size = ui_state.window_size();
|
||||||
|
render.run_sensors(rsc, state, cursor_state, window_size);
|
||||||
}
|
}
|
||||||
let ui_state = state.default_state_mut();
|
let ui_state = state.default_state_mut();
|
||||||
if old != ui_state.focus
|
if old != ui_state.focus
|
||||||
|
|||||||
+35
-103
@@ -4,14 +4,14 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CursorButton {
|
pub enum CursorButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Middle,
|
Middle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CursorSense {
|
pub enum CursorSense {
|
||||||
PressStart(CursorButton),
|
PressStart(CursorButton),
|
||||||
Pressing(CursorButton),
|
Pressing(CursorButton),
|
||||||
@@ -27,7 +27,7 @@ pub struct CursorSenses(Vec<CursorSense>);
|
|||||||
|
|
||||||
impl Event for CursorSenses {
|
impl Event for CursorSenses {
|
||||||
type Data<'a> = CursorData<'a>;
|
type Data<'a> = CursorData<'a>;
|
||||||
type Global = Hovered;
|
type State = SensorState;
|
||||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||||
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
|
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
|
||||||
let mut data = data.clone();
|
let mut data = data.clone();
|
||||||
@@ -37,24 +37,6 @@ impl Event for CursorSenses {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A press or a scroll is used up by whatever answered it, so it stops
|
|
||||||
/// there. Hovering is not: a cursor resting somewhere goes on resting.
|
|
||||||
fn consumes(&self, data: &Self::Data<'_>) -> bool {
|
|
||||||
!data.sense.position_only()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Who the cursor was inside, before and after an input. The difference is
|
|
||||||
/// whose hover has ended -- including a widget a higher layer has covered,
|
|
||||||
/// which the walk stops before reaching.
|
|
||||||
///
|
|
||||||
/// Two buffers that swap rather than one rebuilt, so an input allocates
|
|
||||||
/// nothing once they have grown.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Hovered {
|
|
||||||
was: Vec<WidgetId>,
|
|
||||||
now: Vec<WidgetId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorSense {
|
impl CursorSense {
|
||||||
@@ -70,12 +52,6 @@ impl CursorSense {
|
|||||||
pub fn is_dragging(&self) -> bool {
|
pub fn is_dragging(&self) -> bool {
|
||||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// False if the sense is a button or a scroll, true if it is only about
|
|
||||||
/// where the cursor is.
|
|
||||||
fn position_only(&self) -> bool {
|
|
||||||
matches!(self, Self::HoverStart | Self::Hovering | Self::HoverEnd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -120,12 +96,6 @@ impl CursorButtons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CursorState {
|
impl CursorState {
|
||||||
/// True if the cursor is only reporting where it is: no button and no
|
|
||||||
/// scroll this frame.
|
|
||||||
pub fn position_only(&self) -> bool {
|
|
||||||
self.scroll_delta == Vec2::ZERO && self.buttons.iter().all(|(_, state)| state.is_off())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
pub fn end_frame(&mut self) {
|
||||||
self.buttons.end_frame();
|
self.buttons.end_frame();
|
||||||
self.scroll_delta = Vec2::ZERO;
|
self.scroll_delta = Vec2::ZERO;
|
||||||
@@ -153,6 +123,11 @@ pub struct Sensor<Ctx: HasEvents, Data> {
|
|||||||
|
|
||||||
pub type SenseShape = UiRegion;
|
pub type SenseShape = UiRegion;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct SensorState {
|
||||||
|
pub hover: ActivationState,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CursorData<'a> {
|
pub struct CursorData<'a> {
|
||||||
/// where this widget was hit
|
/// where this widget was hit
|
||||||
@@ -172,6 +147,7 @@ pub trait SensorUi {
|
|||||||
rsc: &mut Rsc,
|
rsc: &mut Rsc,
|
||||||
state: &mut Rsc::State,
|
state: &mut Rsc::State,
|
||||||
cursor: CursorState,
|
cursor: CursorState,
|
||||||
|
window_size: Vec2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,98 +157,54 @@ impl SensorUi for UiRenderState {
|
|||||||
rsc: &mut Rsc,
|
rsc: &mut Rsc,
|
||||||
state: &mut Rsc::State,
|
state: &mut Rsc::State,
|
||||||
cursor: CursorState,
|
cursor: CursorState,
|
||||||
|
window_size: Vec2,
|
||||||
) {
|
) {
|
||||||
// in order to remove this take, need to store active list in UiRenderState somehow
|
// in order to remove this take, need to store active list in UiRenderState somehow
|
||||||
// this would probably be done through a generic parameter that adds yet another rsc /
|
// this would probably be done through a generic parameter that adds yet another rsc /
|
||||||
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
||||||
// update it there?
|
// update it there?
|
||||||
let active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||||
let mut hovered = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().global);
|
|
||||||
hovered.now.clear();
|
|
||||||
let position_only = cursor.position_only();
|
|
||||||
let region_of = |id| self.window_region(&id);
|
|
||||||
|
|
||||||
for layer in self.layers.indices().rev() {
|
for layer in self.layers.indices().rev() {
|
||||||
let mut consumed = false;
|
let mut sensed = false;
|
||||||
for id in active.get(&layer).into_flat_iter().map(|(id, _)| *id) {
|
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
|
||||||
let Some(region) = region_of(id) else {
|
let shape = self.active.get(id).unwrap().region;
|
||||||
continue;
|
let region = shape.to_px(window_size);
|
||||||
};
|
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||||
if !cursor.exists || !region.contains(cursor.pos) {
|
sensor.hover.update(in_shape);
|
||||||
|
if sensor.hover == ActivationState::Off {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
hovered.now.push(id);
|
sensed = true;
|
||||||
let hover = match hovered.was.contains(&id) {
|
|
||||||
true => ActivationState::On,
|
let cursor = cursor.clone();
|
||||||
false => ActivationState::Start,
|
|
||||||
|
let data = CursorData {
|
||||||
|
pos: cursor.pos - region.top_left,
|
||||||
|
size: region.bot_right - region.top_left,
|
||||||
|
scroll_delta: cursor.scroll_delta,
|
||||||
|
hover: sensor.hover,
|
||||||
|
cursor,
|
||||||
|
// this does not have any meaning;
|
||||||
|
// might wanna set up Event to have a prepare stage
|
||||||
|
sense: CursorSense::Hovering,
|
||||||
|
render: self,
|
||||||
};
|
};
|
||||||
// A press or a scroll stops where something answered it, so a
|
rsc.run_event::<CursorSense>(*id, data, state);
|
||||||
// button over a list does not swallow the list's scrolling.
|
|
||||||
consumed |= deliver(self, rsc, state, id, hover, &cursor, region);
|
|
||||||
// A cursor doing neither stops at whatever it is over, so
|
|
||||||
// hovering does not reach through.
|
|
||||||
consumed |= position_only;
|
|
||||||
}
|
}
|
||||||
// Applied after the layer, never during it: senses on one layer do
|
if sensed {
|
||||||
// not block each other.
|
|
||||||
if consumed {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rsc.events_mut().get_type::<CursorSense>().active = active;
|
||||||
// Whatever the cursor was inside and is not now, whether it left or a
|
|
||||||
// layer above took the input before the walk reached it. A widget that
|
|
||||||
// stopped being drawn has no region to report and is simply dropped.
|
|
||||||
for &id in &hovered.was {
|
|
||||||
if !hovered.now.contains(&id)
|
|
||||||
&& let Some(region) = region_of(id)
|
|
||||||
{
|
|
||||||
deliver(self, rsc, state, id, ActivationState::End, &cursor, region);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::mem::swap(&mut hovered.was, &mut hovered.now);
|
|
||||||
|
|
||||||
let senses = rsc.events_mut().get_type::<CursorSense>();
|
|
||||||
senses.active = active;
|
|
||||||
senses.global = hovered;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs one widget's cursor senses, and says whether they used up the input.
|
|
||||||
fn deliver<Rsc: HasEvents>(
|
|
||||||
render: &UiRenderState,
|
|
||||||
rsc: &mut Rsc,
|
|
||||||
state: &mut Rsc::State,
|
|
||||||
id: WidgetId,
|
|
||||||
hover: ActivationState,
|
|
||||||
cursor: &CursorState,
|
|
||||||
region: PixelRegion,
|
|
||||||
) -> bool {
|
|
||||||
let data = CursorData {
|
|
||||||
pos: cursor.pos - region.top_left,
|
|
||||||
size: region.bot_right - region.top_left,
|
|
||||||
scroll_delta: cursor.scroll_delta,
|
|
||||||
hover,
|
|
||||||
cursor: cursor.clone(),
|
|
||||||
// this does not have any meaning;
|
|
||||||
// might wanna set up Event to have a prepare stage
|
|
||||||
sense: CursorSense::Hovering,
|
|
||||||
render,
|
|
||||||
};
|
|
||||||
rsc.run_event::<CursorSense>(id, data, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn should_run(
|
pub fn should_run(
|
||||||
senses: &CursorSenses,
|
senses: &CursorSenses,
|
||||||
cursor: &CursorState,
|
cursor: &CursorState,
|
||||||
hover: ActivationState,
|
hover: ActivationState,
|
||||||
) -> Option<CursorSense> {
|
) -> Option<CursorSense> {
|
||||||
for sense in senses.iter() {
|
for sense in senses.iter() {
|
||||||
// A widget the cursor is no longer inside senses only its position:
|
|
||||||
// the press that ended its hover landed on something else.
|
|
||||||
if !hover.is_on() && !sense.position_only() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if match sense {
|
if match sense {
|
||||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||||
|
|||||||
+2
-1
@@ -173,8 +173,9 @@ impl Harness {
|
|||||||
/// window delivers input against too.
|
/// window delivers input against too.
|
||||||
fn sense(&mut self) {
|
fn sense(&mut self) {
|
||||||
let cursor = self.cursor.clone();
|
let cursor = self.cursor.clone();
|
||||||
|
let size = self.render.output_size();
|
||||||
self.render
|
self.render
|
||||||
.run_sensors(&mut self.rsc, &mut self.state, cursor);
|
.run_sensors(&mut self.rsc, &mut self.state, cursor, size);
|
||||||
self.cursor.end_frame();
|
self.cursor.end_frame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
-6
@@ -6,17 +6,16 @@ pub struct Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Image {
|
impl Widget for Image {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.primitive(&self.handle);
|
painter.primitive(&self.handle);
|
||||||
Size::abs(self.handle.size())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
Some(Len::abs(self.handle.size().axis(axis)))
|
Len::abs(self.handle.size().x)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
OnResize::Scale
|
Len::abs(self.handle.size().y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-5
@@ -5,13 +5,16 @@ pub struct Masked {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Masked {
|
impl Widget for Masked {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.set_mask(painter.region());
|
painter.set_mask(painter.region());
|
||||||
painter.widget(&self.inner).size()
|
painter.widget(&self.inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// It clips to the box it was given, not to the part its child used.
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
ctx.width(&self.inner)
|
||||||
OnResize::Redraw
|
}
|
||||||
|
|
||||||
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
ctx.height(&self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,17 +6,30 @@ pub struct Aligned {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Aligned {
|
impl Widget for Aligned {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
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)) => size.to_uivec2().align(RegionAlign { x, y }),
|
(Some(x), Some(y)) => painter
|
||||||
(Some(x), None) => UiRegion::new(size.x.apply_rest().align(x), UiSpan::FULL),
|
.size(&self.inner)
|
||||||
(None, Some(y)) => UiRegion::new(UiSpan::FULL, size.y.apply_rest().align(y)),
|
.to_uivec2()
|
||||||
|
.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.widget_within(&self.inner, region);
|
||||||
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,10 +6,18 @@ pub struct LayerOffset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for LayerOffset {
|
impl Widget for LayerOffset {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
for _ in 0..self.offset {
|
for _ in 0..self.offset {
|
||||||
painter.next_layer();
|
painter.next_layer();
|
||||||
}
|
}
|
||||||
painter.widget(&self.inner).size()
|
painter.widget(&self.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,20 +6,43 @@ pub struct MaxSize {
|
|||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for MaxSize {
|
impl MaxSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
||||||
let child = painter.widget(&self.inner).size();
|
if let Some(x) = self.x {
|
||||||
let output = painter.output_size();
|
ctx.outer.x.select_len(x.apply_rest());
|
||||||
Size {
|
}
|
||||||
x: capped(child.x, self.x, output.x),
|
if let Some(y) = self.y {
|
||||||
y: capped(child.y, self.y, output.y),
|
ctx.outer.y.select_len(y.apply_rest());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capped(len: Len, max: Option<Len>, output: f32) -> Len {
|
impl Widget for MaxSize {
|
||||||
match max {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
Some(max) if len.apply_rest().to_abs(output) > max.apply_rest().to_abs(output) => max,
|
painter.widget(&self.inner);
|
||||||
_ => len,
|
}
|
||||||
|
|
||||||
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
self.apply_to_outer(ctx);
|
||||||
|
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 {
|
||||||
|
self.apply_to_outer(ctx);
|
||||||
|
let height = ctx.height(&self.inner);
|
||||||
|
if let Some(y) = self.y {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,16 @@ pub struct Offset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Offset {
|
impl Widget for Offset {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
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).size()
|
painter.widget_within(&self.inner, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+22
-14
@@ -6,20 +6,28 @@ pub struct Pad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Pad {
|
impl Widget for Pad {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let inner = painter
|
painter.widget_within(&self.inner, self.padding.region());
|
||||||
.widget_within(&self.inner, self.padding.region())
|
}
|
||||||
.size();
|
|
||||||
Size {
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
x: Len {
|
let width = self.padding.left + self.padding.right;
|
||||||
abs: inner.x.abs + self.padding.left + self.padding.right,
|
let height = self.padding.top + self.padding.bottom;
|
||||||
..inner.x
|
ctx.outer.x.abs -= width;
|
||||||
},
|
ctx.outer.y.abs -= height;
|
||||||
y: Len {
|
let mut size = ctx.width(&self.inner);
|
||||||
abs: inner.y.abs + self.padding.top + self.padding.bottom,
|
size.abs += width;
|
||||||
..inner.y
|
size
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,11 @@ pub struct Scroll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Scroll {
|
impl Widget for Scroll {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
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();
|
||||||
// Drawn in the whole container to learn its length, then placed at
|
let content_len = painter
|
||||||
// the scrolled offset.
|
.len_axis(&self.inner, self.axis)
|
||||||
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);
|
||||||
@@ -32,7 +29,14 @@ 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.widget_within(&self.inner, region);
|
||||||
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,21 +6,29 @@ pub struct SetSize {
|
|||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for SetSize {
|
impl SetSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
||||||
let child = painter.widget(&self.inner).size();
|
if let Some(x) = self.x {
|
||||||
Size {
|
ctx.outer.x.select_len(x.apply_rest());
|
||||||
x: self.x.unwrap_or(child.x),
|
|
||||||
y: self.y.unwrap_or(child.y),
|
|
||||||
}
|
}
|
||||||
}
|
if let Some(y) = self.y {
|
||||||
|
ctx.outer.y.select_len(y.apply_rest());
|
||||||
/// A declared axis is known without looking at the child, which is what
|
|
||||||
/// lets a span lay out around `.height(rest(1))` without drawing it.
|
|
||||||
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Widget for SetSize {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
painter.widget(&self.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
self.apply_to_outer(ctx);
|
||||||
|
self.x.unwrap_or_else(|| ctx.width(&self.inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
self.apply_to_outer(ctx);
|
||||||
|
self.y.unwrap_or_else(|| ctx.height(&self.inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
+101
-32
@@ -8,27 +8,13 @@ pub struct Span {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Span {
|
impl Widget for Span {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let axis = self.dir.axis;
|
let total = self.len_sum(&mut painter.size_ctx());
|
||||||
// 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();
|
||||||
let mut ortho = Len::ZERO;
|
for child in &self.children {
|
||||||
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);
|
||||||
@@ -38,25 +24,27 @@ 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 region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
let mut child_region = UiRegion::from_axis(self.dir.axis, span, UiSpan::FULL);
|
||||||
if self.dir.sign == Sign::Neg {
|
if self.dir.sign == Sign::Neg {
|
||||||
region.flip(axis);
|
child_region.flip(self.dir.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
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let along = match total.rest == 0.0 && total.rel == 0.0 {
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
true => total,
|
match self.dir.axis {
|
||||||
false => Len::default(),
|
Axis::X => self.desired_len(ctx),
|
||||||
};
|
Axis::Y => self.desired_ortho(ctx),
|
||||||
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +69,87 @@ 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> {
|
||||||
|
|||||||
@@ -8,25 +8,30 @@ pub struct Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Stack {
|
impl Widget for Stack {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let sizing = match self.size {
|
let mut iter = self.children.iter();
|
||||||
StackSize::Default => None,
|
if let Some(child) = iter.next() {
|
||||||
StackSize::Child(i) => Some(i),
|
painter.child_layer();
|
||||||
};
|
painter.widget(child);
|
||||||
let mut size = Size::default();
|
}
|
||||||
for (i, child) in self.children.iter().enumerate() {
|
for child in iter {
|
||||||
match i {
|
painter.next_layer();
|
||||||
0 => painter.child_layer(),
|
painter.widget(child);
|
||||||
_ => painter.next_layer(),
|
}
|
||||||
}
|
}
|
||||||
let drawn = painter.widget(child);
|
|
||||||
// Only the child that sizes the stack is read, so the others
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
// changing size does not redraw it.
|
match self.size {
|
||||||
if sizing == Some(i) {
|
StackSize::Default => Len::default(),
|
||||||
size = drawn.size();
|
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]),
|
||||||
}
|
}
|
||||||
size
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -6,10 +6,25 @@ pub struct WidgetPtr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for WidgetPtr {
|
impl Widget for WidgetPtr {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
match &self.inner {
|
if let Some(id) = &self.inner {
|
||||||
Some(id) => painter.widget(id).size(),
|
painter.widget(id);
|
||||||
None => Size::default(),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-7
@@ -28,23 +28,21 @@ impl Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Rect {
|
impl Widget for Rect {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
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,
|
||||||
});
|
});
|
||||||
Size::REST
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, _: Axis) -> Option<Len> {
|
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
Some(Len::REST)
|
Len::rest(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Its box is its primitive's own region, so a new one is written there.
|
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
Len::rest(1)
|
||||||
OnResize::Scale
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-16
@@ -55,47 +55,44 @@ impl TextEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for TextEdit {
|
impl Widget for TextEdit {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let base = painter.layer;
|
let base = painter.layer;
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
let (_, size) = self.view.draw(painter);
|
self.view.draw(painter);
|
||||||
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 size;
|
return;
|
||||||
};
|
};
|
||||||
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 rect_size = vec2(rect.width() as f32, rect.height() as f32);
|
let 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),
|
||||||
rect_size
|
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||||
.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 caret_size = vec2(caret.width() as f32, caret.height() as f32);
|
let 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),
|
||||||
caret_size
|
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||||
.align(Align::TOP_LEFT)
|
|
||||||
.offset(top_left)
|
|
||||||
.within(®ion),
|
|
||||||
);
|
);
|
||||||
size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, axis: Axis) -> OnResize {
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
self.view.on_resize(axis)
|
self.view.desired_width(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
self.view.desired_height(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-36
@@ -52,15 +52,15 @@ impl TextView {
|
|||||||
.align(self.align)
|
.align(self.align)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, painter: &mut Painter) -> &RenderedText {
|
fn render(&mut self, ctx: &mut SizeCtx) -> &RenderedText {
|
||||||
let width = if self.attrs.wrap {
|
let width = if self.attrs.wrap {
|
||||||
Some(painter.px_size().x)
|
Some(ctx.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(painter.render_text(&mut self.buf, &self.attrs, width));
|
self.tex = Some(ctx.draw_text(&mut self.buf, &self.attrs, width));
|
||||||
self.attrs.changed = false;
|
self.attrs.changed = false;
|
||||||
self.buf.changed = false;
|
self.buf.changed = false;
|
||||||
}
|
}
|
||||||
@@ -69,40 +69,39 @@ impl TextView {
|
|||||||
pub fn tex(&self) -> Option<&RenderedText> {
|
pub fn tex(&self) -> Option<&RenderedText> {
|
||||||
self.tex.as_ref()
|
self.tex.as_ref()
|
||||||
}
|
}
|
||||||
/// Draws the text, and says where the glyphs went and what they use.
|
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
pub fn draw(&mut self, painter: &mut Painter) -> (UiRegion, Size) {
|
if self.is_empty()
|
||||||
|
&& 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(painter).size.align(align);
|
let region = self.render(&mut painter.size_ctx()).size.align(align);
|
||||||
let size = match &self.hint {
|
if let Some(hint) = &self.hint {
|
||||||
Some(hint) => painter.widget(hint).size(),
|
painter.widget(hint);
|
||||||
None => Size::ZERO,
|
}
|
||||||
};
|
return region;
|
||||||
return (region, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let tex = self.render(painter);
|
let tex = self.render(&mut painter.size_ctx());
|
||||||
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, size)
|
region
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapping reads the width it is offered, so a wider box reshapes it and
|
|
||||||
/// a taller one does not. Alignment matters too, and separately: glyphs
|
|
||||||
/// anchored to the start of an axis stay put when that extent changes,
|
|
||||||
/// but centred or end-aligned ones move even though the shaping stands.
|
|
||||||
pub fn on_resize(&self, axis: Axis) -> OnResize {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(&self) -> String {
|
pub fn content(&self) -> String {
|
||||||
@@ -118,7 +117,7 @@ impl Text {
|
|||||||
content: content.into(),
|
content: content.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn update_buf(&mut self) {
|
fn update_buf(&mut self, _ctx: &mut SizeCtx) {
|
||||||
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,13 +126,19 @@ impl Text {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Text {
|
impl Widget for Text {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
self.update_buf();
|
self.update_buf(&mut painter.size_ctx());
|
||||||
self.view.draw(painter).1
|
self.view.draw(painter);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, axis: Axis) -> OnResize {
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
self.view.on_resize(axis)
|
self.update_buf(ctx);
|
||||||
|
self.view.desired_width(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
self.update_buf(ctx);
|
||||||
|
self.view.desired_height(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,80 +32,3 @@ 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));
|
|
||||||
}
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
//! Input across layers: what stops at a layer, what passes through it, and
|
|
||||||
//! where hovering stops.
|
|
||||||
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
|
|
||||||
use iris::harness::Harness;
|
|
||||||
use iris::prelude::*;
|
|
||||||
|
|
||||||
const WINDOW: f32 = 100.0;
|
|
||||||
|
|
||||||
/// Every sense that has fired on one widget since it was last read.
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
struct Fired(Rc<RefCell<Vec<CursorSense>>>);
|
|
||||||
|
|
||||||
impl Fired {
|
|
||||||
fn take(&self) -> Vec<CursorSense> {
|
|
||||||
std::mem::take(&mut self.0.borrow_mut())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A widget filling whatever it is given, recording the senses it is sent.
|
|
||||||
fn listener(h: &mut Harness, senses: impl Into<CursorSenses>) -> (WeakWidget<Rect>, Fired) {
|
|
||||||
let fired = Fired::default();
|
|
||||||
let record = fired.clone();
|
|
||||||
let id = rect(Color::WHITE)
|
|
||||||
.on(senses.into(), move |ctx, _| {
|
|
||||||
record.0.borrow_mut().push(ctx.data.sense)
|
|
||||||
})
|
|
||||||
.add(&mut h.rsc);
|
|
||||||
(id, fired)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A widget with no senses of its own, to leave a gap beside one that has.
|
|
||||||
fn blank(h: &mut Harness) -> WeakWidget<Rect> {
|
|
||||||
rect(Color::WHITE).add(&mut h.rsc)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn harness() -> Harness {
|
|
||||||
Harness::new((WINDOW, WINDOW))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hover_stops_at_the_topmost_widget() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (bottom, bottom_hover) = listener(&mut h, CursorSense::HoverStart);
|
|
||||||
let (middle, middle_hover) = listener(&mut h, CursorSense::HoverStart);
|
|
||||||
let (top, top_hover) = listener(&mut h, CursorSense::HoverStart);
|
|
||||||
h.set_root((bottom, middle, top).stack());
|
|
||||||
|
|
||||||
h.move_to((50, 50));
|
|
||||||
|
|
||||||
assert_eq!(top_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
assert_eq!(
|
|
||||||
middle_hover.take(),
|
|
||||||
[],
|
|
||||||
"hover is not shared with a layer below"
|
|
||||||
);
|
|
||||||
assert_eq!(bottom_hover.take(), []);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (list, scrolled) = listener(&mut h, CursorSense::Scroll);
|
|
||||||
let (button, clicked) = listener(&mut h, CursorSense::click());
|
|
||||||
let (overlay, overlay_clicked) = listener(&mut h, CursorSense::click());
|
|
||||||
h.set_root((list, button, overlay).stack());
|
|
||||||
|
|
||||||
h.move_to((50, 50));
|
|
||||||
h.scroll((0, 10));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
scrolled.take(),
|
|
||||||
[CursorSense::Scroll],
|
|
||||||
"two layers of click-only widgets do not stop a scroll"
|
|
||||||
);
|
|
||||||
assert_eq!(clicked.take(), []);
|
|
||||||
assert_eq!(overlay_clicked.take(), []);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hovering_a_button_above_does_not_stop_a_later_scroll() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (list, scrolled) = listener(&mut h, CursorSense::Scroll);
|
|
||||||
let (button, _clicked) = listener(&mut h, CursorSense::click());
|
|
||||||
h.set_root((list, button).stack());
|
|
||||||
|
|
||||||
// The hover arrives in its own frame, as a window delivers it.
|
|
||||||
h.move_to((50, 50));
|
|
||||||
assert_eq!(scrolled.take(), []);
|
|
||||||
|
|
||||||
h.scroll((0, 10));
|
|
||||||
assert_eq!(
|
|
||||||
scrolled.take(),
|
|
||||||
[CursorSense::Scroll],
|
|
||||||
"a hover already resting on the button must not consume the wheel"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn only_the_topmost_listener_takes_a_press() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (below, below_clicked) = listener(&mut h, CursorSense::click());
|
|
||||||
let (above, above_clicked) = listener(&mut h, CursorSense::click());
|
|
||||||
h.set_root((below, above).stack());
|
|
||||||
|
|
||||||
h.click((50, 50));
|
|
||||||
|
|
||||||
assert_eq!(above_clicked.take(), [CursorSense::click()]);
|
|
||||||
assert_eq!(below_clicked.take(), [], "one press goes to one widget");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_press_beside_the_button_reaches_the_layer_below() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (list, list_clicked) = listener(&mut h, CursorSense::click());
|
|
||||||
// The row above the list covers it, but only its left half is the button.
|
|
||||||
let (button, button_clicked) = listener(&mut h, CursorSense::click());
|
|
||||||
let row = (button, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
|
||||||
h.set_root((list, row).stack());
|
|
||||||
|
|
||||||
h.click((20, 50));
|
|
||||||
assert_eq!(button_clicked.take(), [CursorSense::click()]);
|
|
||||||
assert_eq!(list_clicked.take(), []);
|
|
||||||
|
|
||||||
h.click((80, 50));
|
|
||||||
assert_eq!(button_clicked.take(), [], "the cursor is not on the button");
|
|
||||||
assert_eq!(
|
|
||||||
list_clicked.take(),
|
|
||||||
[CursorSense::click()],
|
|
||||||
"a press beside the button belongs to what is under it"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn leaving_a_widget_still_ends_its_hover() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (widget, hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
h.set_root(widget);
|
|
||||||
|
|
||||||
h.move_to((50, 50));
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
|
||||||
|
|
||||||
h.leave();
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn leaving_a_widget_does_not_block_the_layer_below() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (below, below_hover) = listener(&mut h, CursorSense::HoverStart);
|
|
||||||
// Only the left half of the layer above is a widget, so the cursor can
|
|
||||||
// leave it without leaving the one underneath.
|
|
||||||
let (above, above_hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
let row = (above, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
|
||||||
h.set_root((below, row).stack());
|
|
||||||
|
|
||||||
h.move_to((20, 50));
|
|
||||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
assert_eq!(below_hover.take(), [], "the layer above is over it");
|
|
||||||
|
|
||||||
h.move_to((80, 50));
|
|
||||||
assert_eq!(above_hover.take(), [CursorSense::HoverEnd]);
|
|
||||||
assert_eq!(
|
|
||||||
below_hover.take(),
|
|
||||||
[CursorSense::HoverStart],
|
|
||||||
"ending a hover above must not stop the hover below"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn covering_a_widget_ends_its_hover() {
|
|
||||||
let mut h = harness();
|
|
||||||
let (below, below_hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
let (above, above_hover) = listener(&mut h, CursorSense::HoverStart);
|
|
||||||
let row = (above, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
|
||||||
h.set_root((below, row).stack());
|
|
||||||
|
|
||||||
h.move_to((80, 50));
|
|
||||||
assert_eq!(below_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
|
|
||||||
h.move_to((20, 50));
|
|
||||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
assert_eq!(
|
|
||||||
below_hover.take(),
|
|
||||||
[CursorSense::HoverEnd],
|
|
||||||
"a widget covered by one that took the input is no longer hovered"
|
|
||||||
);
|
|
||||||
|
|
||||||
h.move_to((80, 50));
|
|
||||||
assert_eq!(
|
|
||||||
below_hover.take(),
|
|
||||||
[CursorSense::HoverStart],
|
|
||||||
"uncovering it hovers it again"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hover_starts_and_ends_once_each() {
|
|
||||||
let mut h = harness();
|
|
||||||
// Only the left half is the widget, so the cursor can leave it without
|
|
||||||
// leaving the window.
|
|
||||||
let (widget, hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
let row = (widget, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
|
||||||
h.set_root(row);
|
|
||||||
|
|
||||||
h.move_to((20, 50));
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
|
||||||
|
|
||||||
h.move_to((30, 50));
|
|
||||||
assert_eq!(hover.take(), [], "staying inside is not a second start");
|
|
||||||
|
|
||||||
h.move_to((80, 50));
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
|
||||||
|
|
||||||
h.move_to((90, 50));
|
|
||||||
assert_eq!(hover.take(), [], "an ended hover does not end again");
|
|
||||||
|
|
||||||
h.move_to((20, 50));
|
|
||||||
assert_eq!(
|
|
||||||
hover.take(),
|
|
||||||
[CursorSense::HoverStart],
|
|
||||||
"re-entering starts it"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
//! 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));
|
|
||||||
}
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
//! 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: OnResize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Counted {
|
|
||||||
fn draw(&mut self, _: &mut Painter) -> Size {
|
|
||||||
self.draws.set(self.draws.get() + 1);
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
self.dependence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Counts(Rc<Cell<usize>>);
|
|
||||||
|
|
||||||
impl Counts {
|
|
||||||
fn get(&self) -> usize {
|
|
||||||
self.0.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn counted(h: &mut Harness, size: Size, dependence: OnResize) -> (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: OnResize) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
|
||||||
let (first, _) = counted(h, Size::from((100, 200)), OnResize::Translate);
|
|
||||||
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, OnResize::Scale);
|
|
||||||
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, OnResize::Redraw);
|
|
||||||
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)), OnResize::Translate);
|
|
||||||
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
|
|
||||||
// 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, OnResize::Translate);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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