This commit is contained in:
2025-12-29 20:11:35 -05:00
parent 93291badc1
commit 1e4a7f8cf5
10 changed files with 60 additions and 288 deletions

View File

@@ -2,7 +2,7 @@ use crate::vec2;
use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Align {
pub x: Option<AxisAlign>,
pub y: Option<AxisAlign>,
@@ -43,7 +43,7 @@ impl Align {
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AxisAlign {
Neg,
Center,

View File

@@ -1,14 +1,16 @@
use crate::{LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
use crate::{Align, LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
/// important non rendering data for retained drawing
#[derive(Debug)]
pub struct ActiveData {
pub id: WidgetId,
pub region: UiRegion,
pub region_used: UiRegion,
pub parent: Option<WidgetId>,
pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<WidgetId>,
pub mask: MaskIdx,
pub layer: LayerId,
pub align: Align,
}

View File

@@ -1,19 +0,0 @@
use crate::{BothAxis, Len, UiRegion, UiVec2, WidgetId, util::HashMap};
#[derive(Default)]
pub struct Cache {
pub size: BothAxis<HashMap<WidgetId, (UiVec2, Len)>>,
pub moves: HashMap<WidgetId, UiRegion>,
}
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();
}
}

View File

@@ -1,5 +1,5 @@
use crate::{
ActiveData, Align, Axis, EventsLike, Painter, SizeCtx, Ui, UiRegion, UiVec2, WidgetId,
ActiveData, Align, Axis, EventsLike, Painter, Ui, UiRegion, WidgetId,
render::MaskIdx,
util::{HashSet, forget_ref},
};
@@ -25,58 +25,27 @@ impl<'a> DrawState<'a> {
while let Some(&id) = self.widgets.needs_redraw.iter().next() {
self.redraw(id);
}
self.apply_moves();
self.ui.free(self.events);
}
/// redraws a widget that's currently active (drawn)
pub fn redraw(&mut self, id: WidgetId) {
self.widgets.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).len_axis(id, axis);
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
if new != old {
self.redraw(pid);
}
}
}
if self.draw_started.contains(&id) {
return;
}
let Some(active) = self.remove(id, false) else {
let Some(active) = self.active.get(&id) else {
return;
};
self.draw_inner(
active.layer,
let ActiveData {
id,
active.region,
active.parent,
active.mask,
Some(active.children),
);
}
region,
parent,
mask,
layer,
align,
..
} = *active;
pub(super) fn size_ctx<'b>(&'b mut self, source: WidgetId, outer: UiVec2) -> SizeCtx<'b> {
SizeCtx {
source,
cache: &mut self.ui.cache,
text: &mut self.ui.text,
textures: &mut self.ui.textures,
widgets: &self.ui.widgets,
outer,
output_size: self.ui.output_size,
id: source,
}
self.draw_inner(layer, id, region, parent, mask, align);
}
pub fn redraw_all(&mut self) {
@@ -84,23 +53,13 @@ impl<'a> DrawState<'a> {
for (_, active) in self.ui.active.drain() {
self.events.undraw(&active);
}
self.ui.cache.clear();
self.ui.free(self.events);
self.layers.clear();
self.widgets.needs_redraw.clear();
if let Some(id) = &self.ui.root {
self.draw_inner(
0,
id.id(),
UiRegion::FULL,
None,
MaskIdx::NONE,
None,
Align::NONE,
);
self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, Align::NONE);
}
self.apply_moves();
}
pub(super) fn draw_inner(
@@ -110,21 +69,19 @@ impl<'a> DrawState<'a> {
region: UiRegion,
parent: Option<WidgetId>,
mask: MaskIdx,
old_children: Option<Vec<WidgetId>>,
align: Align,
) -> UiRegion {
let mut old_children = old_children.unwrap_or_default();
if let Some(active) = self.ui.active.get_mut(&id)
&& !self.ui.widgets.needs_redraw.contains(&id)
{
) {
let mut old_children = Vec::new();
if let Some(active) = self.ui.active.get_mut(&id) {
// check to see if we can skip drawing first, and just need to move
if active.region == region {
return region;
} else if active.region.size() == region.size() {
self.ui.cache.moves.insert(id, region);
return region;
if !self.ui.widgets.needs_redraw.contains(&id) {
if active.region == region {
return;
} else if active.region.size() == region.size() {
self.mov(id, active.region, region);
return;
}
}
// if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id, false).unwrap();
old_children = active.children;
}
@@ -161,47 +118,37 @@ impl<'a> DrawState<'a> {
id,
} = painter;
// TODO: unsure if there's a better way, currently using epsilon
// in PartialEq impl for UiScalar
let target = region_used.size().align(align);
if region_used != target {
TODO; // THIS WILL NOT WORK to avoid removing, need to look at children?
self.ui.cache.moves.insert(id, target);
}
// add to active
let active = ActiveData {
id,
region,
parent,
textures,
primitives,
children,
mask,
layer,
};
// remove old children that weren't kept
for c in &old_children {
if !active.children.contains(c) {
if !children.contains(c) {
self.remove_rec(*c);
}
}
// update modules
self.events.draw(&active);
self.active.insert(id, active);
// add to active
self.active.insert(
id,
ActiveData {
id,
region,
parent,
textures,
primitives,
children,
region_used,
mask,
layer,
},
);
region_used
}
fn apply_moves(&mut self) {
let mut moves = std::mem::take(&mut self.ui.cache.moves);
for (id, to) in moves.drain() {
let from = self.active.get(&id).unwrap().region;
// TODO: unsure if there's a better way, currently using epsilon
// in PartialEq impl for UiScalar
let target = region_used.size().align(align);
if region_used != target {
self.mov(id, from, to);
}
self.ui.cache.moves = moves;
self.events.draw(self.active.get(&id).unwrap());
}
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
@@ -238,7 +185,6 @@ impl<'a> DrawState<'a> {
}
fn remove_rec(&mut self, id: WidgetId) -> Option<ActiveData> {
self.cache.remove(id);
let inst = self.remove(id, true);
if let Some(inst) = &inst {
for c in &inst.children {

View File

@@ -11,16 +11,12 @@ use std::{
};
mod active;
mod cache;
mod draw_state;
mod painter;
mod size;
mod state;
pub use active::*;
use cache::*;
pub use painter::Painter;
pub use size::*;
pub struct Ui {
// TODO: edit visibilities
@@ -33,7 +29,6 @@ pub struct Ui {
pub text: TextData,
output_size: Vec2,
pub masks: TrackedArena<Mask, u32>,
pub cache: Cache,
pub root: Option<WidgetHandle>,
old_root: Option<WidgetId>,
@@ -206,7 +201,6 @@ impl Default for Ui {
masks: Default::default(),
text: Default::default(),
textures: Default::default(),
cache: Default::default(),
output_size: Vec2::ZERO,
root: None,
old_root: None,

View File

@@ -1,6 +1,5 @@
use crate::{
Axis, Len, RenderedText, Size, SizeCtx, TextAttrs, TextBuffer, TextData, TextureHandle,
UiRegion, Widget, WidgetHandle, WidgetId,
RenderedText, TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, WidgetHandle, WidgetId,
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
ui::draw_state::DrawState,
util::Vec2,
@@ -57,6 +56,10 @@ impl<'a, 'c> Painter<'a, 'c> {
self.widget_at(id, self.region);
}
pub fn rest_hint(&self, id: &WidgetHandle) -> f32 {
self.state.widgets.data(id).unwrap().align
}
/// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas.
pub fn widget_within<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
@@ -66,7 +69,7 @@ impl<'a, 'c> Painter<'a, 'c> {
fn widget_at<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
self.children.push(id.id());
self.state
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None);
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask);
}
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
@@ -79,11 +82,6 @@ impl<'a, 'c> Painter<'a, 'c> {
self.primitive(handle.primitive());
}
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.primitive_at(handle.primitive(), region);
}
/// returns (handle, offset from top left)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.state
@@ -96,17 +94,6 @@ impl<'a, 'c> Painter<'a, 'c> {
self.region
}
pub fn size<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<W>) -> Size {
self.size_ctx().size(id)
}
pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<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
}
@@ -134,8 +121,4 @@ impl<'a, 'c> Painter<'a, 'c> {
pub fn id(&self) -> &WidgetId {
&self.id
}
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
self.state.size_ctx(self.id, self.region.size())
}
}

View File

@@ -1,86 +0,0 @@
use crate::{
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures,
UiVec2, WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
};
pub struct SizeCtx<'a> {
pub text: &'a mut TextData,
pub textures: &'a mut Textures,
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(id)
.desired_len::<A>(&mut SizeCtx {
text: self.text,
textures: self.textures,
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) -> RenderedText {
self.text.draw(buffer, attrs, self.textures)
}
pub fn label(&self, id: WidgetId) -> &String {
self.widgets.label(id)
}
}

View File

@@ -6,6 +6,7 @@ pub struct WidgetData {
/// alignment used if this does not fill up container
/// and there is no enforced alignment from parent
pub align: RegionAlign,
pub rest_len: Option<f32>,
/// dynamic borrow checking
pub borrowed: bool,
}
@@ -20,6 +21,7 @@ impl WidgetData {
widget: Box::new(widget),
align: RegionAlign::CENTER,
label,
rest_len: None,
borrowed: false,
}
}

View File

@@ -1,4 +1,4 @@
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
use crate::Painter;
use std::any::Any;
mod data;
@@ -15,31 +15,10 @@ pub use widgets::*;
pub trait Widget: Any {
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
}
pub trait WidgetAxisFns {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len;
}
impl<W: Widget + ?Sized> WidgetAxisFns for W {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len {
match A::get() {
Axis::X => self.desired_width(ctx),
Axis::Y => self.desired_height(ctx),
}
}
}
impl Widget for () {
fn draw(&mut self, _: &mut Painter) {}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
}
impl dyn Widget {

View File

@@ -9,11 +9,12 @@ pub struct Span {
impl Widget for Span {
fn draw(&mut self, painter: &mut Painter) {
let total = self.len_sum(&mut painter.size_ctx());
let total = self.len_sum();
let mut start = UiScalar::rel_min();
for child in &self.children {
let mut span = UiSpan::FULL;
span.start = start;
painter.widget(id);
let len = painter.len_axis(child, self.dir.axis);
if len.rest > 0.0 {
let offset = UiScalar::new(total.rel, total.abs);
@@ -32,20 +33,6 @@ impl Widget for Span {
start.abs += self.gap;
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
match self.dir.axis {
Axis::X => self.desired_len(ctx),
Axis::Y => self.desired_ortho(ctx),
}
}
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),
}
}
}
impl Span {
@@ -70,22 +57,6 @@ impl Span {
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 {