Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
123f3e6e2b | ||
|
|
baaeb6b027 | ||
|
|
2537284372 | ||
|
|
76f75192d5 | ||
|
|
6156c66a20 | ||
|
|
7f4846a2d3 | ||
|
|
e44bb8eca4 | ||
|
|
434e3c3af7 | ||
|
|
b66d4da5d7 | ||
|
|
7b3a79b1b0 | ||
|
|
c99d466b75 |
No files matched your search
Generated
+453
-824
File diff suppressed because it is too large.
Load diff
+8
-23
@@ -1,35 +1,24 @@
|
||||
[package]
|
||||
name = "iris"
|
||||
default-run = "test"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
layout-diagnostics = ["iris-core/layout-diagnostics"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
iris-core = { workspace = true }
|
||||
iris-macro = { workspace = true }
|
||||
parley = { workspace = true }
|
||||
cosmic-text = { workspace = true }
|
||||
unicode-segmentation = { workspace = true }
|
||||
winit = { workspace = true }
|
||||
arboard = { workspace = true, features = ["wayland-data-control"] }
|
||||
pollster = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
image = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
|
||||
|
||||
[workspace]
|
||||
members = ["core", "macro", "rig-input"]
|
||||
|
||||
[profile.dev]
|
||||
debug = 1
|
||||
|
||||
[profile.test]
|
||||
debug = "line-tables-only"
|
||||
members = ["core", "macro"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
@@ -38,16 +27,12 @@ edition = "2024"
|
||||
[workspace.dependencies]
|
||||
pollster = "0.4.0"
|
||||
winit = "0.30.12"
|
||||
wgpu = "30.0.1"
|
||||
wgpu = "27.0.1"
|
||||
bytemuck = "1.23.1"
|
||||
image = "0.25.10"
|
||||
parley = "0.11.1"
|
||||
swash = "0.2.10"
|
||||
image = "0.25.6"
|
||||
cosmic-text = "0.15.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
fxhash = "0.2.1"
|
||||
log = "0.4.29"
|
||||
arboard = "3.6.1"
|
||||
iris-core = { path = "core" }
|
||||
iris-macro = { path = "macro" }
|
||||
tokio = "1.49.0"
|
||||
wayland-client = "0.31.15"
|
||||
wayland-protocols-wlr = { version = "0.3.12", features = ["client"] }
|
||||
@@ -1,8 +1,22 @@
|
||||
images
|
||||
settings (sampler)
|
||||
consider typed TextureHandle<T> variants for distinct texture uses
|
||||
|
||||
text
|
||||
figure out ways to speed up / what costs the most
|
||||
resizing (per frame) is really slow (assuming painter isn't griefing)
|
||||
j is weird / fix x offset
|
||||
|
||||
masks r just made to bare minimum work
|
||||
|
||||
scaling
|
||||
could be just a simple scaling factor that multiplies abs
|
||||
and need to ensure text uses raw abs and not scaled abs
|
||||
naming? (pt, px)
|
||||
want to keep (drawn) regions using px? or should I add another field to UiScalar/Vec
|
||||
field could be best solution so redrawing stuff isn't needed & you can specify both as user
|
||||
|
||||
WidgetRef<W> or smth instead of Id
|
||||
fyi this is not the same as what was just implemented
|
||||
enum that's either an Id or an actual concrete instance of W
|
||||
painter takes them in instead of (or in addition to) id
|
||||
then type wrapper widgets to contain them
|
||||
@@ -11,30 +25,17 @@ WidgetRef<W> or smth instead of Id
|
||||
maybe introduce InnerWidget trait to allow for editors to expose & modify inner type
|
||||
maybe could also store a parent widget and keep using InnerWidget trait? unsure if possible
|
||||
|
||||
really weird limitation:
|
||||
I don't think you can currently remove an element from a parent and put it in a child of the same parent
|
||||
because it removes the unused children after the entire parent redraw
|
||||
but the child gets drawn during that, so it will think the child is still active !!!
|
||||
or something like that idk, maybe I need a special enum for parent that includes a undecided state where it may or may not get redrawn by the parent
|
||||
or just do ref counting and ensure all drawn things == 1 afterwards (seems like best way)
|
||||
ok so I'm removing the limit for now
|
||||
|
||||
don't forget I'm streaming
|
||||
|
||||
tags
|
||||
vecs for each widget type?
|
||||
|
||||
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||
|
||||
transforms on a move entry (scale + rotation)
|
||||
an entry is a translation today; composing through one scales the rel
|
||||
part and passes px through untouched, so fixed-size content and glyphs
|
||||
do not follow a shortened entry
|
||||
want a real transform per entry, resolved in resolve_move the way the
|
||||
translation already is, so a whole subtree transforms with one buffer
|
||||
write and no redraw
|
||||
wanted for compose-style stretch at the end of a scroll area, and for
|
||||
rotation generally
|
||||
|
||||
a prepare stage on Event, so Data has no placeholder field
|
||||
run_sensors builds one CursorData per widget and has to put something in
|
||||
`sense` before anything knows which sense matched, so it writes
|
||||
CursorSense::Hovering and says in place that it means nothing;
|
||||
should_run then clones the whole thing to overwrite that one field
|
||||
the state is representable only because the type lets the caller say it:
|
||||
what the caller supplies and what matching adds are two different things
|
||||
wearing one struct
|
||||
the awkward part is doing it without the generics getting annoying --
|
||||
Data<'a> is already a GAT with a default, and splitting it in two adds
|
||||
another associated type to every Event impl for the sake of one field
|
||||
(Bryan, 2026-09-20; low priority, he wants a good answer rather than a
|
||||
quick one)
|
||||
+2
-6
@@ -3,14 +3,10 @@ name = "iris-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
layout-diagnostics = []
|
||||
|
||||
[dependencies]
|
||||
winit = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
bytemuck ={ workspace = true }
|
||||
image = { workspace = true }
|
||||
parley = { workspace = true }
|
||||
swash = { workspace = true }
|
||||
cosmic-text = { workspace = true }
|
||||
fxhash = { workspace = true }
|
||||
log = { workspace = true }
|
||||
@@ -1,23 +0,0 @@
|
||||
use crate::{UiRsc, WeakWidget, WidgetIdFn, WidgetLike};
|
||||
|
||||
pub trait WidgetAttr<Rsc, W: ?Sized> {
|
||||
type Input;
|
||||
fn run(rsc: &mut Rsc, id: WeakWidget<W>, input: Self::Input);
|
||||
}
|
||||
|
||||
pub trait Attrable<Rsc, W: ?Sized, Tag> {
|
||||
fn attr<A: WidgetAttr<Rsc, W>>(self, input: A::Input) -> impl WidgetIdFn<Rsc, W>;
|
||||
}
|
||||
|
||||
impl<Rsc: UiRsc, WL: WidgetLike<Rsc, Tag>, Tag> Attrable<Rsc, WL::Widget, Tag> for WL {
|
||||
fn attr<A: WidgetAttr<Rsc, WL::Widget>>(
|
||||
self,
|
||||
input: A::Input,
|
||||
) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||
|rsc| {
|
||||
let id = self.add(rsc);
|
||||
A::run(rsc, id, input);
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use crate::{HasEvents, WeakWidget, Widget};
|
||||
|
||||
pub struct EventCtx<'a, Rsc: HasEvents, Data> {
|
||||
pub state: &'a mut Rsc::State,
|
||||
pub data: Data,
|
||||
}
|
||||
|
||||
pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> {
|
||||
pub widget: WeakWidget<W>,
|
||||
pub state: &'a mut Rsc::State,
|
||||
pub data: Data,
|
||||
}
|
||||
|
||||
impl<Rsc: HasEvents, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> {
|
||||
pub fn widget<'a>(&self, rsc: &'a mut Rsc) -> &'a mut W {
|
||||
&mut rsc.ui_mut().widgets[self.widget]
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
use crate::{
|
||||
ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, HasEvents, IdLike, LayerId,
|
||||
WeakWidget, WidgetEventFn, WidgetId,
|
||||
util::{HashMap, HashSet, TypeMap},
|
||||
};
|
||||
use std::{any::TypeId, rc::Rc};
|
||||
|
||||
pub struct EventManager<Rsc> {
|
||||
widget_to_types: HashMap<WidgetId, HashSet<TypeId>>,
|
||||
types: TypeMap<dyn EventManagerLike<Rsc>>,
|
||||
}
|
||||
|
||||
impl<Rsc> Default for EventManager<Rsc> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
widget_to_types: Default::default(),
|
||||
types: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rsc: HasEvents + 'static> EventManager<Rsc> {
|
||||
pub fn get_type<E: EventLike>(&mut self) -> &mut TypeEventManager<Rsc, E::Event> {
|
||||
self.types.type_or_default()
|
||||
}
|
||||
|
||||
pub fn register<I: IdLike + 'static, E: EventLike>(
|
||||
&mut self,
|
||||
id: I,
|
||||
event: E,
|
||||
f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, I::Widget>,
|
||||
) {
|
||||
let i = id.id();
|
||||
self.get_type::<E>().register(id, event, f);
|
||||
self.widget_to_types
|
||||
.entry(i)
|
||||
.or_default()
|
||||
.insert(Self::type_key::<E>());
|
||||
}
|
||||
|
||||
pub fn type_key<E: EventLike>() -> TypeId {
|
||||
TypeId::of::<TypeEventManager<Rsc, E::Event>>()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventsLike {
|
||||
fn remove(&mut self, id: WidgetId);
|
||||
fn draw(&mut self, active: &ActiveData);
|
||||
fn undraw(&mut self, active: &ActiveData);
|
||||
}
|
||||
|
||||
impl<Rsc: HasEvents + 'static> EventsLike for EventManager<Rsc> {
|
||||
fn remove(&mut self, id: WidgetId) {
|
||||
for t in self.widget_to_types.get(&id).into_flat_iter() {
|
||||
self.types.get_mut(t).unwrap().remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, active: &ActiveData) {
|
||||
for t in self.widget_to_types.get(&active.id).into_flat_iter() {
|
||||
self.types.get_mut(t).unwrap().draw(active);
|
||||
}
|
||||
}
|
||||
|
||||
fn undraw(&mut self, active: &ActiveData) {
|
||||
for t in self.widget_to_types.get(&active.id).into_flat_iter() {
|
||||
self.types.get_mut(t).unwrap().undraw(active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventManagerLike<State> {
|
||||
fn remove(&mut self, id: WidgetId);
|
||||
fn draw(&mut self, data: &ActiveData);
|
||||
fn undraw(&mut self, data: &ActiveData);
|
||||
}
|
||||
|
||||
type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>>);
|
||||
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
|
||||
// TODO: reduce visiblity!!
|
||||
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
|
||||
pub global: E::Global,
|
||||
map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
|
||||
}
|
||||
|
||||
impl<Rsc: HasEvents, E: Event> EventManagerLike<Rsc> for TypeEventManager<Rsc, E> {
|
||||
fn remove(&mut self, id: WidgetId) {
|
||||
self.map.remove(&id);
|
||||
for layer in self.active.values_mut() {
|
||||
layer.remove(&id);
|
||||
}
|
||||
}
|
||||
fn draw(&mut self, data: &ActiveData) {
|
||||
self.active
|
||||
.entry(data.layer)
|
||||
.or_default()
|
||||
.entry(data.id)
|
||||
.or_default();
|
||||
}
|
||||
fn undraw(&mut self, data: &ActiveData) {
|
||||
if let Some(layer) = self.active.get_mut(&data.layer) {
|
||||
layer.remove(&data.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rsc: HasEvents, E: Event> Default for TypeEventManager<Rsc, E> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: Default::default(),
|
||||
global: Default::default(),
|
||||
map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
||||
fn register<I: IdLike + 'static>(
|
||||
&mut self,
|
||||
widget: I,
|
||||
event: impl EventLike<Event = E>,
|
||||
f: impl for<'a> WidgetEventFn<Rsc, E::Data<'a>, I::Widget>,
|
||||
) {
|
||||
let event = event.into_event();
|
||||
self.map.entry(widget.id()).or_default().push((
|
||||
event,
|
||||
Rc::new(move |ctx, rsc| {
|
||||
f(
|
||||
EventIdCtx {
|
||||
widget: WeakWidget::new(widget.id()),
|
||||
state: ctx.state,
|
||||
data: ctx.data,
|
||||
},
|
||||
rsc,
|
||||
);
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn run_fn<'a>(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) -> bool + 'a {
|
||||
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
||||
move |ctx, rsc| {
|
||||
let mut consumed = false;
|
||||
for (e, f) in fs {
|
||||
if let Some(data) = e.should_run(&ctx.data) {
|
||||
consumed |= e.consumes(&data);
|
||||
f(
|
||||
EventCtx {
|
||||
state: ctx.state,
|
||||
data,
|
||||
},
|
||||
rsc,
|
||||
)
|
||||
}
|
||||
}
|
||||
consumed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
mod ctx;
|
||||
mod manager;
|
||||
mod rsc;
|
||||
|
||||
pub use ctx::*;
|
||||
pub use manager::*;
|
||||
pub use rsc::*;
|
||||
|
||||
pub trait Event: Sized + 'static + Clone {
|
||||
type Data<'a>: Clone = ();
|
||||
type State: Default = ();
|
||||
/// State the whole event type keeps, rather than one copy per widget.
|
||||
type Global: Default = ();
|
||||
#[allow(unused_variables)]
|
||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||
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 {
|
||||
type Event: Event;
|
||||
fn into_event(self) -> Self::Event;
|
||||
}
|
||||
|
||||
impl<E: Event> EventLike for E {
|
||||
type Event = Self;
|
||||
|
||||
fn into_event(self) -> Self::Event {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventFn<Rsc: HasEvents, Data>: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static {}
|
||||
impl<Rsc: HasEvents, F: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static, Data> EventFn<Rsc, Data>
|
||||
for F
|
||||
{
|
||||
}
|
||||
|
||||
pub trait WidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>:
|
||||
Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static
|
||||
{
|
||||
}
|
||||
impl<Rsc: HasEvents, F: Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static, Data, W: ?Sized>
|
||||
WidgetEventFn<Rsc, Data, W> for F
|
||||
{
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use crate::{
|
||||
Event, EventCtx, EventLike, EventManager, IdLike, UiRsc, WeakWidget, Widget, WidgetEventFn,
|
||||
};
|
||||
|
||||
pub trait HasState: 'static {
|
||||
type State;
|
||||
}
|
||||
|
||||
pub trait HasEvents: Sized + UiRsc + HasState {
|
||||
fn events(&self) -> &EventManager<Self>;
|
||||
fn events_mut(&mut self) -> &mut EventManager<Self>;
|
||||
|
||||
fn register_event<W: Widget + ?Sized, E: EventLike>(
|
||||
&mut self,
|
||||
id: WeakWidget<W>,
|
||||
event: E,
|
||||
f: impl for<'a> WidgetEventFn<Self, <E::Event as Event>::Data<'a>, W>,
|
||||
) {
|
||||
self.events_mut().register(id, event, f);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RunEvents: HasEvents {
|
||||
/// Whether anything that ran used up what triggered it.
|
||||
fn run_event<E: EventLike>(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
data: <E::Event as Event>::Data<'_>,
|
||||
state: &mut Self::State,
|
||||
) -> bool {
|
||||
let f = self.events_mut().get_type::<E>().run_fn(id);
|
||||
f(EventCtx { state, data }, self)
|
||||
}
|
||||
}
|
||||
impl<T: HasEvents> RunEvents for T {}
|
||||
@@ -1,574 +0,0 @@
|
||||
use crate::{UiNum, util::Vec2};
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign},
|
||||
};
|
||||
|
||||
/// A number held as a whole count of `1 / 2^SHIFT`.
|
||||
///
|
||||
/// Layout reaches one place by more than one route -- a box composed down the
|
||||
/// chain, and the same box summed from what its children asked for -- and has
|
||||
/// to decide whether the two are the same place. In floats they land a few
|
||||
/// bits apart, which is a defect wherever the answer changes what is drawn
|
||||
/// rather than where. Here adding and subtracting are exact, a multiply
|
||||
/// drops to the step below, and a conversion between grids takes the nearest
|
||||
/// one, so two routes to one place land on one number and everything
|
||||
/// downstream compares for equality instead of for nearness.
|
||||
///
|
||||
/// `SHIFT` is the number of fractional bits, which is what makes the steps
|
||||
/// divide a whole number: a power of two also converts to `f32` without
|
||||
/// rounding while the value fits in its mantissa.
|
||||
///
|
||||
/// Arithmetic wraps at the ends of the range, the way the `i32` underneath
|
||||
/// does. Saturating instead was measured at a twelfth of layout's
|
||||
/// instructions -- five per add against one -- to keep the ordering of
|
||||
/// coordinates two million pixels out, where nothing draws anyway. A value
|
||||
/// off the end is a defect either way; wrapping makes it an obvious one.
|
||||
/// Only [`Self::from_f32`] clamps, since a float has further to come from.
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, bytemuck::Pod, bytemuck::Zeroable,
|
||||
)]
|
||||
pub struct Fixed<const SHIFT: u32>(i32);
|
||||
|
||||
/// A length or a coordinate in pixels, in steps of `1/1024`. Finer than
|
||||
/// anything a display can show, and exact in `f32` up to 16,384 px, which is
|
||||
/// what lets the same number reach the GPU.
|
||||
pub type Px = Fixed<PX_SHIFT>;
|
||||
|
||||
/// How many bits of a pixel a [`Px`] keeps. One place, because [`PxVec2`]
|
||||
/// and the shader's own decoding are the same grid or nothing lines up.
|
||||
pub const PX_SHIFT: u32 = 10;
|
||||
|
||||
/// A share of what a box has left over, which is a weight beside its
|
||||
/// siblings rather than a fraction of anything: a list divides its room by
|
||||
/// the total of these, so the range has to hold a whole list's worth and the
|
||||
/// precision only has to tell two weights apart.
|
||||
pub type Weight = Fixed<16>;
|
||||
|
||||
/// A fraction of a box. Twenty-four bits of it, which matches `f32` around a
|
||||
/// half and beats it above one -- where anchors actually sit -- and leaves
|
||||
/// +/-128 of range, enough to sum a hundred children each asking for a whole
|
||||
/// box. A `leftover` weight is not one of these: it is a share of what is
|
||||
/// left rather than a fraction of anything, and it sums over a whole list.
|
||||
pub type Rel = Fixed<REL_SHIFT>;
|
||||
|
||||
/// How many bits of a box a [`Rel`] keeps, beside [`PX_SHIFT`] and for the
|
||||
/// same reason.
|
||||
pub const REL_SHIFT: u32 = 24;
|
||||
|
||||
impl<const SHIFT: u32> Fixed<SHIFT> {
|
||||
pub const ZERO: Self = Self(0);
|
||||
pub const ONE: Self = Self::one();
|
||||
/// The gap between neighbouring values, which is also how far apart two
|
||||
/// numbers can be and still mean the same place.
|
||||
pub const STEP: Self = Self(1);
|
||||
/// Also what stands in for an unbounded end: compared against, never
|
||||
/// added to, since arithmetic wraps past it.
|
||||
pub const MIN: Self = Self(i32::MIN);
|
||||
pub const MAX: Self = Self(i32::MAX);
|
||||
|
||||
const fn one() -> Self {
|
||||
assert!(SHIFT < 31, "a Fixed needs a bit for the whole part");
|
||||
Self(1 << SHIFT)
|
||||
}
|
||||
|
||||
pub const fn from_raw(raw: i32) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
/// The count of steps, for a caller that needs the representation rather
|
||||
/// than the number.
|
||||
pub const fn raw(self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn from_int(v: i32) -> Self {
|
||||
Self(v.wrapping_mul(Self::one().0))
|
||||
}
|
||||
|
||||
/// Rounds to the nearest step, and clamps to the ends of the grid rather
|
||||
/// than wrapping: this is where a number from outside arrives, and a float
|
||||
/// has the range to be anywhere. A NaN has no nearest step and becomes
|
||||
/// zero, which is a caller's mistake rather than a value worth carrying.
|
||||
///
|
||||
/// Half-away is written out rather than called through `f32::round`,
|
||||
/// which is not `const`: a layout constant has to stay a constant.
|
||||
pub const fn from_f32(v: f32) -> Self {
|
||||
debug_assert!(!v.is_nan(), "a NaN has no place on the grid");
|
||||
let scaled = v * Self::one().0 as f32;
|
||||
// Above 2^23 an `f32` has no fractional part left to round, and
|
||||
// adding a half there rounds the number itself up instead. The cast
|
||||
// saturates at both ends and sends NaN to zero, which is the
|
||||
// behaviour wanted at both.
|
||||
const WHOLE: f32 = (1 << 23) as f32;
|
||||
Self(match (scaled >= WHOLE, scaled <= -WHOLE, scaled < 0.0) {
|
||||
(true, _, _) | (_, true, _) => scaled as i32,
|
||||
(_, _, true) => (scaled - 0.5) as i32,
|
||||
_ => (scaled + 0.5) as i32,
|
||||
})
|
||||
}
|
||||
|
||||
/// The first step at or above `v`, where [`Self::from_f32`] takes the
|
||||
/// nearest one and is below it half the time. For a bound that has to
|
||||
/// admit the value it came from: a measurement rounded down is a bound
|
||||
/// that leaves out the thing it was measured from.
|
||||
pub const fn ceil_from_f32(v: f32) -> Self {
|
||||
let nearest = Self::from_f32(v);
|
||||
match nearest.to_f32() < v {
|
||||
true => nearest.next_up(),
|
||||
false => nearest,
|
||||
}
|
||||
}
|
||||
|
||||
/// From a number as it is written in source -- `16`, `1.5` -- which is
|
||||
/// the other place a value enters the grid.
|
||||
pub fn from_num(v: impl UiNum) -> Self {
|
||||
Self::from_f32(v.to_f32())
|
||||
}
|
||||
|
||||
pub const fn to_f32(self) -> f32 {
|
||||
self.0 as f32 / Self::one().0 as f32
|
||||
}
|
||||
|
||||
/// The same value on another grid, rounded where the new one is coarser.
|
||||
pub const fn to_scale<const TO: u32>(self) -> Fixed<TO> {
|
||||
Fixed(match TO >= SHIFT {
|
||||
true => self.0 << (TO - SHIFT),
|
||||
false => shift_round(self.0 as i64, SHIFT - TO) as i32,
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn add(self, rhs: Self) -> Self {
|
||||
Self(self.0.wrapping_add(rhs.0))
|
||||
}
|
||||
|
||||
pub const fn sub(self, rhs: Self) -> Self {
|
||||
Self(self.0.wrapping_sub(rhs.0))
|
||||
}
|
||||
|
||||
pub const fn neg(self) -> Self {
|
||||
Self(self.0.wrapping_neg())
|
||||
}
|
||||
|
||||
/// Scaled by a number on any grid, which is how a length takes a fraction
|
||||
/// of itself and keeps being a length: the product is measured in the
|
||||
/// receiver's steps.
|
||||
///
|
||||
/// Dropped to the step below rather than taken to the nearest one
|
||||
/// (Bryan, 2026-09-16), which costs a share a thousandth of a pixel of
|
||||
/// its row -- less than an even number of pixels draws. Toward negative
|
||||
/// infinity on both sides of zero, since that is a shift and nothing
|
||||
/// else: a value and its negation therefore land different distances
|
||||
/// from where they came, so a flipped span can sit a step from its
|
||||
/// mirror image.
|
||||
pub const fn mul<const BY: u32>(self, by: Fixed<BY>) -> Self {
|
||||
Self(((self.0 as i64 * by.0 as i64) >> BY) as i32)
|
||||
}
|
||||
|
||||
/// Repeated a whole number of times, which no grid rounds.
|
||||
pub const fn mul_int(self, by: i32) -> Self {
|
||||
Self(self.0.wrapping_mul(by))
|
||||
}
|
||||
|
||||
/// Divided into a whole number of parts, rounded to the nearest step.
|
||||
pub const fn div_int(self, by: i32) -> Self {
|
||||
debug_assert!(by != 0, "no part of nothing");
|
||||
if by == 0 {
|
||||
return Self::ZERO;
|
||||
}
|
||||
Self(div_round(self.0 as i64, by as i64) as i32)
|
||||
}
|
||||
|
||||
/// Divided by a number on any grid. A zero divisor is a caller bug -- a
|
||||
/// box of no length has no fraction of itself -- and answers with the end
|
||||
/// of the range so that a release build lays out something absurd rather
|
||||
/// than dying.
|
||||
pub const fn div<const BY: u32>(self, by: Fixed<BY>) -> Self {
|
||||
debug_assert!(by.0 != 0, "dividing by a length of zero");
|
||||
if by.0 == 0 {
|
||||
return match self.0 < 0 {
|
||||
true => Self::MIN,
|
||||
false => Self::MAX,
|
||||
};
|
||||
}
|
||||
Self(div_round((self.0 as i64) << BY, by.0 as i64) as i32)
|
||||
}
|
||||
|
||||
/// `num / den` on *this* grid rather than on theirs, for weights coarser
|
||||
/// than the share they divide.
|
||||
pub const fn ratio<const OF: u32>(num: Fixed<OF>, den: Fixed<OF>) -> Self {
|
||||
debug_assert!(den.0 != 0, "no part of a whole of nothing");
|
||||
if den.0 == 0 {
|
||||
return Self::ZERO;
|
||||
}
|
||||
Self(div_round((num.0 as i64) << SHIFT, den.0 as i64) as i32)
|
||||
}
|
||||
|
||||
/// `from` and `to` a fraction of the way apart, the fraction being the
|
||||
/// receiver -- the argument order [`crate::util::LerpUtil`] already uses.
|
||||
pub const fn lerp<const OF: u32>(self, from: Fixed<OF>, to: Fixed<OF>) -> Fixed<OF> {
|
||||
from.add(to.sub(from).mul(self))
|
||||
}
|
||||
|
||||
pub const fn min(self, other: Self) -> Self {
|
||||
match self.0 < other.0 {
|
||||
true => self,
|
||||
false => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn max(self, other: Self) -> Self {
|
||||
match self.0 > other.0 {
|
||||
true => self,
|
||||
false => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn abs(self) -> Self {
|
||||
Self(self.0.wrapping_abs())
|
||||
}
|
||||
|
||||
pub const fn clamp(self, lo: Self, hi: Self) -> Self {
|
||||
debug_assert!(lo.0 <= hi.0, "an empty clamp has no answer");
|
||||
self.max(lo).min(hi)
|
||||
}
|
||||
|
||||
/// The next value along, for an interval that must not admit its own
|
||||
/// boundary. The step is the whole gap, so there is nothing to exclude
|
||||
/// between this and the boundary itself.
|
||||
pub const fn next_up(self) -> Self {
|
||||
Self(self.0.wrapping_add(1))
|
||||
}
|
||||
|
||||
pub const fn next_down(self) -> Self {
|
||||
Self(self.0.wrapping_sub(1))
|
||||
}
|
||||
}
|
||||
|
||||
/// Back to a single step, rounding halves away from zero so that a value and
|
||||
/// its negation round to the same distance.
|
||||
const fn shift_round(v: i64, bits: u32) -> i64 {
|
||||
let half = (1i64 << bits) >> 1;
|
||||
match v < 0 {
|
||||
true => -((-v + half) >> bits),
|
||||
false => (v + half) >> bits,
|
||||
}
|
||||
}
|
||||
|
||||
const fn div_round(num: i64, den: i64) -> i64 {
|
||||
let (q, rem) = (num / den, num % den);
|
||||
match rem.unsigned_abs() * 2 >= den.unsigned_abs() {
|
||||
true => match (num < 0) == (den < 0) {
|
||||
true => q + 1,
|
||||
false => q - 1,
|
||||
},
|
||||
false => q,
|
||||
}
|
||||
}
|
||||
|
||||
/// Toward positive infinity when `up`, toward negative infinity otherwise.
|
||||
pub(crate) const fn div_toward(num: i64, den: i64, up: bool) -> i64 {
|
||||
let (q, rem) = (num / den, num % den);
|
||||
if rem == 0 {
|
||||
return q;
|
||||
}
|
||||
match (rem < 0) == (den < 0) {
|
||||
true => q + up as i64,
|
||||
false => q - !up as i64,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clamped to the ends, unlike a [`Fixed`]'s own arithmetic: a range of box
|
||||
/// lengths that runs past `i32` really is unbounded.
|
||||
pub(crate) const fn narrow(v: i64) -> i32 {
|
||||
if v > i32::MAX as i64 {
|
||||
return i32::MAX;
|
||||
}
|
||||
if v < i32::MIN as i64 {
|
||||
return i32::MIN;
|
||||
}
|
||||
v as i32
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> Add for Fixed<SHIFT> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Fixed::add(self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> Sub for Fixed<SHIFT> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Fixed::sub(self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> Neg for Fixed<SHIFT> {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Fixed::neg(self)
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> AddAssign for Fixed<SHIFT> {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = Fixed::add(*self, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> SubAssign for Fixed<SHIFT> {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = Fixed::sub(*self, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32, const BY: u32> Mul<Fixed<BY>> for Fixed<SHIFT> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Fixed<BY>) -> Self {
|
||||
Fixed::mul(self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32, const BY: u32> Div<Fixed<BY>> for Fixed<SHIFT> {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: Fixed<BY>) -> Self {
|
||||
Fixed::div(self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SHIFT: u32> Display for Fixed<SHIFT> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.to_f32(), f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the number rather than the count of steps: a failing layout test
|
||||
/// reports boxes, and `1126` is not a height anybody can read.
|
||||
impl<const SHIFT: u32> Debug for Fixed<SHIFT> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.to_f32(), f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Two of them, for the places a size or a position needs both axes: a
|
||||
/// window, a box in pixels, a pointer. Held apart from [`crate::util::Vec2`]
|
||||
/// because that one is what the GPU and the platform speak.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct FixedVec2<const SHIFT: u32> {
|
||||
pub x: Fixed<SHIFT>,
|
||||
pub y: Fixed<SHIFT>,
|
||||
}
|
||||
|
||||
pub type PxVec2 = FixedVec2<PX_SHIFT>;
|
||||
|
||||
impl<const SHIFT: u32> FixedVec2<SHIFT> {
|
||||
pub const ZERO: Self = Self::splat(Fixed::ZERO);
|
||||
|
||||
pub const fn new(x: Fixed<SHIFT>, y: Fixed<SHIFT>) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn splat(v: Fixed<SHIFT>) -> Self {
|
||||
Self { x: v, y: v }
|
||||
}
|
||||
|
||||
pub fn from_f32(v: Vec2) -> Self {
|
||||
Self::new(Fixed::from_f32(v.x), Fixed::from_f32(v.y))
|
||||
}
|
||||
|
||||
/// The first step at or above each part, for a measurement reported as a
|
||||
/// box: what it occupies is not less than what was measured.
|
||||
pub fn ceil_from_f32(v: Vec2) -> Self {
|
||||
Self::new(Fixed::ceil_from_f32(v.x), Fixed::ceil_from_f32(v.y))
|
||||
}
|
||||
|
||||
pub fn to_f32(self) -> Vec2 {
|
||||
Vec2::new(self.x.to_f32(), self.y.to_f32())
|
||||
}
|
||||
|
||||
pub const fn div_int(self, by: i32) -> Self {
|
||||
Self::new(self.x.div_int(by), self.y.div_int(by))
|
||||
}
|
||||
|
||||
pub const fn min(self, other: Self) -> Self {
|
||||
Self::new(self.x.min(other.x), self.y.min(other.y))
|
||||
}
|
||||
|
||||
pub const fn max(self, other: Self) -> Self {
|
||||
Self::new(self.x.max(other.x), self.y.max(other.y))
|
||||
}
|
||||
}
|
||||
|
||||
// `impl_op!` names one concrete type, and this one is generic.
|
||||
const impl<const SHIFT: u32> Add for FixedVec2<SHIFT> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Self::new(self.x.add(rhs.x), self.y.add(rhs.y))
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> Sub for FixedVec2<SHIFT> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self::new(self.x.sub(rhs.x), self.y.sub(rhs.y))
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> AddAssign for FixedVec2<SHIFT> {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = Add::add(*self, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
const impl<const SHIFT: u32> SubAssign for FixedVec2<SHIFT> {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = Sub::sub(*self, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SHIFT: u32> Debug for FixedVec2<SHIFT> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SHIFT: u32> Display for FixedVec2<SHIFT> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_sum_of_steps_does_not_drift() {
|
||||
let mut at = Px::ZERO;
|
||||
for _ in 0..20_000 {
|
||||
at += Px::from_raw(3);
|
||||
}
|
||||
assert_eq!(at, Px::from_raw(60_000));
|
||||
for _ in 0..20_000 {
|
||||
at -= Px::from_raw(3);
|
||||
}
|
||||
assert_eq!(at, Px::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pixel_survives_the_trip_through_f32() {
|
||||
for raw in [0, 1, -1, 64, -1000, 16_777_215, -16_777_215] {
|
||||
let px = Px::from_raw(raw);
|
||||
assert_eq!(Px::from_f32(px.to_f32()), px);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_fraction_of_a_length_is_a_length() {
|
||||
let half = Px::from_int(100) * Rel::from_f32(0.5);
|
||||
assert_eq!(half, Px::from_int(50));
|
||||
assert_eq!(Px::from_int(100) * Rel::ONE, Px::from_int(100));
|
||||
assert_eq!(Px::from_int(100) * Rel::ZERO, Px::ZERO);
|
||||
}
|
||||
|
||||
/// Toward negative infinity on both sides of zero, which is what makes
|
||||
/// it a shift rather than a shift and a sign branch -- and what makes a
|
||||
/// value and its negation land different distances from where they came,
|
||||
/// so a flipped span can sit a step from its mirror image.
|
||||
#[test]
|
||||
fn a_multiply_drops_to_the_step_below_on_both_sides_of_zero() {
|
||||
// A step and a half of one, which has no step of its own.
|
||||
let step_and_a_half = Rel::from_f32(1.5).div_int(Px::ONE.raw());
|
||||
assert_eq!(Px::ONE * step_and_a_half, Px::from_raw(1));
|
||||
assert_eq!(Px::ONE.neg() * step_and_a_half, Px::from_raw(-2));
|
||||
}
|
||||
|
||||
/// A division rounds to the nearest step, so it cannot put back the
|
||||
/// steps a truncating multiply dropped: a round trip comes back short,
|
||||
/// never long, and by the few steps the two operations gave up.
|
||||
#[test]
|
||||
fn dividing_by_a_fraction_cannot_undo_a_truncating_multiply() {
|
||||
let third = Rel::ONE / Rel::from_int(3);
|
||||
let len = Px::from_int(300);
|
||||
let back = len * third / third;
|
||||
assert!(back <= len, "{back:?} is longer than {len:?}");
|
||||
assert!(len - back <= Px::from_raw(3), "{back:?} against {len:?}");
|
||||
assert_eq!(Px::from_int(100) / Rel::from_f32(0.5), Px::from_int(200));
|
||||
}
|
||||
|
||||
/// The bound a greedy line break needs: the width it was measured at is
|
||||
/// not on the grid, and the narrowest box the break still holds for is
|
||||
/// the step at or above it, never the one below.
|
||||
#[test]
|
||||
fn a_ceiling_never_lands_below_the_number_it_came_from() {
|
||||
let step = 1.0 / (1 << PX_SHIFT) as f32;
|
||||
for n in 0..64 {
|
||||
let v = 189.0 + n as f32 * step / 3.0;
|
||||
let up = Px::ceil_from_f32(v);
|
||||
assert!(up.to_f32() >= v, "{up:?} is below {v}");
|
||||
assert!(
|
||||
up.to_f32() - v < step,
|
||||
"{up:?} is more than a step above {v}"
|
||||
);
|
||||
}
|
||||
// An exact step is its own ceiling.
|
||||
assert_eq!(Px::ceil_from_f32(189.5), Px::from_f32(189.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_number_from_outside_is_clamped_to_the_grid() {
|
||||
assert_eq!(Px::from_f32(1e12), Px::MAX);
|
||||
assert_eq!(Px::from_f32(-1e12), Px::MIN);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_coarser_grid_rounds_and_a_finer_one_does_not() {
|
||||
// A third, which neither grid holds exactly.
|
||||
let third = Rel::ONE / Rel::from_int(3);
|
||||
assert_eq!(third.to_scale::<6>(), Fixed::<6>::from_raw(21));
|
||||
let coarse = Fixed::<6>::from_raw(21);
|
||||
assert_eq!(coarse.to_scale::<24>().to_scale::<6>(), coarse);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lerp_takes_the_fraction_as_the_receiver() {
|
||||
let (from, to) = (Px::from_int(10), Px::from_int(20));
|
||||
assert_eq!(Rel::ZERO.lerp(from, to), from);
|
||||
assert_eq!(Rel::ONE.lerp(from, to), to);
|
||||
assert_eq!(Rel::from_f32(0.5).lerp(from, to), Px::from_int(15));
|
||||
assert_eq!(Rel::from_f32(0.5).lerp(to, from), Px::from_int(15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_ratio_is_finer_than_the_weights_it_divides() {
|
||||
let (one, three) = (Weight::ONE, Weight::from_int(3));
|
||||
// A third, which the weights' own grid could only hold to 1/65536.
|
||||
assert_eq!(Rel::ratio(one, three), Rel::from_raw(5592405));
|
||||
assert_eq!(Rel::ratio(three, three), Rel::ONE);
|
||||
assert_eq!(Rel::ratio(Weight::ZERO, three), Rel::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nothing_sits_between_a_value_and_the_next_one() {
|
||||
let at = Px::from_int(3);
|
||||
assert_eq!(at.next_up().next_down(), at);
|
||||
assert_eq!(at.next_up().raw() - at.raw(), 1);
|
||||
assert!(at.next_down() < at && at < at.next_up());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_prints_the_number_rather_than_the_steps() {
|
||||
assert_eq!(format!("{:?}", Px::from_f32(17.59375)), "17.59375");
|
||||
assert_eq!(format!("{}", Px::from_int(-2)), "-2");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::layout::{Ui, WidgetIdFn, WidgetLike, WidgetRef};
|
||||
|
||||
pub trait WidgetAttr<W: ?Sized> {
|
||||
type Input;
|
||||
fn run(ui: &mut Ui, id: WidgetRef<W>, input: Self::Input);
|
||||
}
|
||||
|
||||
pub trait Attrable<W: ?Sized, Tag> {
|
||||
fn attr<A: WidgetAttr<W>>(self, input: A::Input) -> impl WidgetIdFn<W>;
|
||||
}
|
||||
|
||||
impl<WL: WidgetLike<Tag>, Tag> Attrable<WL::Widget, Tag> for WL {
|
||||
fn attr<A: WidgetAttr<WL::Widget>>(self, input: A::Input) -> impl WidgetIdFn<WL::Widget> {
|
||||
|ui| {
|
||||
let id = self.add(ui);
|
||||
A::run(ui, id.weak(), input);
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
use crate::{
|
||||
layout::{LayerId, UiId, Widget, WidgetId, WidgetInstance, WidgetRef},
|
||||
util::{Handle, HashMap, RefGuardMut},
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
ops::DerefMut,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
};
|
||||
|
||||
pub trait Async: Send + Sync + 'static {}
|
||||
impl<T: Send + Sync + 'static> Async for T {}
|
||||
|
||||
pub trait Event: Sized + Async + Clone {
|
||||
type Data<'a> = ();
|
||||
type State: Sync + Send + Default = ();
|
||||
#[allow(unused_variables)]
|
||||
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// THIS SHOULD NOT BE OVERWRITTEN; the default impl runs this event
|
||||
fn run<Ctx: 'static, W>(id: WidgetRef<W>, data: &mut Self::Data<'_>, state: &mut Ctx) {
|
||||
Events::<Self, Ctx>::run(id.id(), data, state);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventAlias {
|
||||
type Event: Event;
|
||||
fn into_event(self) -> Self::Event;
|
||||
}
|
||||
|
||||
impl<E: Event> EventAlias for E {
|
||||
type Event = Self;
|
||||
|
||||
fn into_event(self) -> Self::Event {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
type EventData<E, Ctx> = (E, Arc<dyn for<'a> EventFn<Ctx, <E as Event>::Data<'a>>>);
|
||||
pub struct Events<E: Event, Ctx> {
|
||||
active: HashMap<UiId, HashMap<LayerId, HashMap<WidgetId, E::State>>>,
|
||||
map: HashMap<WidgetId, Vec<EventData<E, Ctx>>>,
|
||||
}
|
||||
|
||||
pub static EVENT_TYPES: LazyLock<Mutex<HashMap<TypeId, Handle<dyn EventManager>>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::default()));
|
||||
|
||||
pub trait EventManager: Any + Send + Sync {
|
||||
fn remove(&mut self, id: WidgetId) -> Box<dyn Any>;
|
||||
fn draw(&mut self, ui: UiId, inst: &WidgetInstance);
|
||||
fn undraw(&mut self, ui: UiId, inst: &WidgetInstance);
|
||||
}
|
||||
|
||||
impl Handle<dyn EventManager> {
|
||||
pub fn remove(&mut self, id: WidgetId) {
|
||||
let mut s = self.get_mut();
|
||||
// refer to s.remove documentation for why drop order
|
||||
let contents = s.remove(id);
|
||||
drop(s);
|
||||
drop(contents);
|
||||
}
|
||||
pub fn draw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
self.get_mut().draw(ui, inst);
|
||||
}
|
||||
pub fn undraw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
self.get_mut().undraw(ui, inst);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event, Ctx: 'static> EventManager for Events<E, Ctx> {
|
||||
/// so... there is a deadlock if this is called in WidgetData::drop
|
||||
/// and some function in here owns another WidgetId
|
||||
/// so for now you need to drop the lock on self, THEN drop the contents
|
||||
/// which is implemented for Handle<dyn EventManager>
|
||||
fn remove(&mut self, id: WidgetId) -> Box<dyn Any> {
|
||||
let contents = self.map.remove(&id);
|
||||
for ui in self.active.values_mut() {
|
||||
for layer in ui.values_mut() {
|
||||
layer.remove(&id);
|
||||
}
|
||||
}
|
||||
Box::new(contents)
|
||||
}
|
||||
fn draw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
self.active
|
||||
.entry(ui)
|
||||
.or_default()
|
||||
.entry(inst.layer)
|
||||
.or_default()
|
||||
.entry(inst.id)
|
||||
.or_default();
|
||||
}
|
||||
fn undraw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
// rust pls
|
||||
(|| {
|
||||
self.active
|
||||
.get_mut(&ui)?
|
||||
.get_mut(&inst.layer)?
|
||||
.remove(&inst.id);
|
||||
Some(())
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
pub type UiActive<State> = HashMap<LayerId, HashMap<WidgetId, State>>;
|
||||
|
||||
impl<E: Event, Ctx: 'static> Events<E, Ctx> {
|
||||
fn id() -> TypeId {
|
||||
TypeId::of::<Self>()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
map: Default::default(),
|
||||
active: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active(id: UiId) -> impl DerefMut<Target = UiActive<E::State>> {
|
||||
RefGuardMut::map(Self::get(), |s| s.active.entry(id).or_default())
|
||||
}
|
||||
|
||||
fn register_<W: Widget + ?Sized>(
|
||||
id: WidgetRef<W>,
|
||||
event: impl Into<E>,
|
||||
f: impl for<'a> EventFn<Ctx, <E as Event>::Data<'a>>,
|
||||
) {
|
||||
let Some(mut data) = id.data_mut() else {
|
||||
return;
|
||||
};
|
||||
data.event_managers.insert(Self::id());
|
||||
Self::get()
|
||||
.map
|
||||
.entry(id.id())
|
||||
.or_default()
|
||||
.push((event.into(), Arc::new(f)));
|
||||
}
|
||||
|
||||
pub fn register<W: Widget + ?Sized + 'static>(
|
||||
id: WidgetRef<W>,
|
||||
event: impl Into<E>,
|
||||
f: impl for<'a> EventIdFn<Ctx, <E as Event>::Data<'a>, W>,
|
||||
) {
|
||||
Self::register_(id, event, move |ctx| {
|
||||
f(EventIdCtx {
|
||||
widget: id,
|
||||
state: ctx.state,
|
||||
data: ctx.data,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn run(id: WidgetId, data: &mut E::Data<'_>, state: &mut Ctx) {
|
||||
let s = Self::get();
|
||||
if let Some(fs) = s.map.get(&id) {
|
||||
let fs = fs.clone();
|
||||
drop(s);
|
||||
for (e, f) in fs {
|
||||
if e.should_run(data) {
|
||||
f(EventCtx { state, data })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_all(data: &mut E::Data<'_>, state: &mut Ctx) {
|
||||
let s = Self::get();
|
||||
// TODO: should probably document that added events won't run same frame
|
||||
let mut map = s.map.clone();
|
||||
drop(s);
|
||||
for fs in map.values_mut() {
|
||||
for (e, f) in fs {
|
||||
if e.should_run(data) {
|
||||
f(EventCtx { state, data })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// this some bull
|
||||
fn get<'a>() -> RefGuardMut<'a, Self> {
|
||||
let any = EVENT_TYPES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(Self::id())
|
||||
.or_insert_with(|| Handle::from(Self::new()))
|
||||
.clone();
|
||||
unsafe { any.downcast() }.get_take_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventCtx<'a, Ctx, Data> {
|
||||
pub state: &'a mut Ctx,
|
||||
pub data: &'a mut Data,
|
||||
}
|
||||
|
||||
pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> {
|
||||
pub widget: WidgetRef<W>,
|
||||
pub state: &'a mut Ctx,
|
||||
pub data: &'a mut Data,
|
||||
}
|
||||
|
||||
pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + Async {}
|
||||
impl<F: Fn(EventCtx<Ctx, Data>) + Async, Ctx, Data> EventFn<Ctx, Data> for F {}
|
||||
|
||||
pub trait EventIdFn<Ctx, Data, W: ?Sized>: Fn(EventIdCtx<Ctx, Data, W>) + Async {}
|
||||
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + Async, Ctx, Data, W: ?Sized> EventIdFn<Ctx, Data, W> for F {}
|
||||
@@ -0,0 +1,24 @@
|
||||
mod attr;
|
||||
mod event;
|
||||
mod module;
|
||||
mod num;
|
||||
mod orientation;
|
||||
mod painter;
|
||||
mod primitive;
|
||||
mod ui;
|
||||
mod view;
|
||||
mod widget;
|
||||
|
||||
pub use attr::*;
|
||||
pub use event::*;
|
||||
pub use module::*;
|
||||
pub use num::*;
|
||||
pub use orientation::*;
|
||||
pub use painter::*;
|
||||
pub use primitive::*;
|
||||
pub use ui::*;
|
||||
pub use view::*;
|
||||
pub use widget::*;
|
||||
|
||||
pub use crate::util::Vec2;
|
||||
pub type UiColor = Color<u8>;
|
||||
@@ -0,0 +1,35 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use crate::{
|
||||
layout::{WidgetId, WidgetInstance},
|
||||
util::HashMap,
|
||||
};
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub trait UiModule: Any {
|
||||
fn on_draw(&mut self, inst: &WidgetInstance) {}
|
||||
fn on_undraw(&mut self, inst: &WidgetInstance) {}
|
||||
fn on_remove(&mut self, id: &WidgetId) {}
|
||||
fn on_move(&mut self, inst: &WidgetInstance) {}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Modules {
|
||||
map: HashMap<TypeId, Box<dyn UiModule>>,
|
||||
}
|
||||
|
||||
impl Modules {
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (dyn UiModule + 'static)> {
|
||||
self.map.values_mut().map(|m| m.as_mut())
|
||||
}
|
||||
|
||||
pub fn get_mut<M: UiModule + Default>(&mut self) -> &mut M {
|
||||
let rf = self
|
||||
.map
|
||||
.entry(TypeId::of::<M>())
|
||||
.or_insert_with(|| Box::new(M::default()))
|
||||
.as_mut();
|
||||
let any: &mut dyn Any = &mut *rf;
|
||||
any.downcast_mut().unwrap()
|
||||
}
|
||||
}
|
||||
@@ -5,38 +5,25 @@ pub const trait UiNum {
|
||||
fn to_f32(self) -> f32;
|
||||
}
|
||||
|
||||
const impl UiNum for f32 {
|
||||
impl const UiNum for f32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
const impl UiNum for u32 {
|
||||
impl const UiNum for u32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
|
||||
const impl UiNum for i32 {
|
||||
impl const UiNum for i32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
|
||||
Vec2::new(x.to_f32(), y.to_f32())
|
||||
}
|
||||
|
||||
const impl<T: const UiNum + Copy> From<T> for Vec2 {
|
||||
fn from(v: T) -> Self {
|
||||
Self {
|
||||
x: v.to_f32(),
|
||||
y: v.to_f32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const impl<T: const UiNum, U: const UiNum> From<(T, U)> for Vec2
|
||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
|
||||
where
|
||||
(T, U): const Destruct,
|
||||
{
|
||||
@@ -47,3 +34,16 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: const UiNum + Copy> const From<T> for Vec2 {
|
||||
fn from(v: T) -> Self {
|
||||
Self {
|
||||
x: v.to_f32(),
|
||||
y: v.to_f32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
|
||||
Vec2::new(x.to_f32(), y.to_f32())
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Px, Rel};
|
||||
use crate::layout::vec2;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Align {
|
||||
pub x: Option<AxisAlign>,
|
||||
pub y: Option<AxisAlign>,
|
||||
@@ -31,32 +30,20 @@ impl Align {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a widget sits in a box longer than it is. The default is the middle,
|
||||
/// because the two edges are the ones that assume a direction: which of them
|
||||
/// is the near one depends on the writing system and on which way a container
|
||||
/// runs, and the middle is the same either way.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct AxisAlign(Rel);
|
||||
|
||||
impl AxisAlign {
|
||||
pub const NEG: Self = Self::new(0.0);
|
||||
pub const CENTER: Self = Self::new(0.5);
|
||||
pub const POS: Self = Self::new(1.0);
|
||||
|
||||
pub const fn new(rel: f32) -> Self {
|
||||
Self(Rel::from_f32(rel))
|
||||
}
|
||||
|
||||
/// A fraction of the room left over, which is what the layout reads: the
|
||||
/// three constants are the familiar places along it, not the only ones.
|
||||
pub const fn rel(&self) -> Rel {
|
||||
self.0
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AxisAlign {
|
||||
Neg,
|
||||
Center,
|
||||
Pos,
|
||||
}
|
||||
|
||||
impl Default for AxisAlign {
|
||||
fn default() -> Self {
|
||||
Self::CENTER
|
||||
impl AxisAlign {
|
||||
pub const fn rel(&self) -> f32 {
|
||||
match self {
|
||||
Self::Neg => 0.0,
|
||||
Self::Center => 0.5,
|
||||
Self::Pos => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,38 +53,41 @@ pub struct CardinalAlign {
|
||||
}
|
||||
|
||||
impl CardinalAlign {
|
||||
pub const LEFT: Self = Self::new(Axis::X, AxisAlign::NEG);
|
||||
pub const H_CENTER: Self = Self::new(Axis::X, AxisAlign::CENTER);
|
||||
pub const RIGHT: Self = Self::new(Axis::X, AxisAlign::POS);
|
||||
pub const TOP: Self = Self::new(Axis::Y, AxisAlign::NEG);
|
||||
pub const V_CENTER: Self = Self::new(Axis::Y, AxisAlign::CENTER);
|
||||
pub const BOT: Self = Self::new(Axis::Y, AxisAlign::POS);
|
||||
pub const LEFT: Self = Self::new(Axis::X, AxisAlign::Neg);
|
||||
pub const H_CENTER: Self = Self::new(Axis::X, AxisAlign::Center);
|
||||
pub const RIGHT: Self = Self::new(Axis::X, AxisAlign::Pos);
|
||||
pub const TOP: Self = Self::new(Axis::Y, AxisAlign::Neg);
|
||||
pub const V_CENTER: Self = Self::new(Axis::Y, AxisAlign::Center);
|
||||
pub const BOT: Self = Self::new(Axis::Y, AxisAlign::Pos);
|
||||
|
||||
pub const fn new(axis: Axis, align: AxisAlign) -> Self {
|
||||
Self { axis, align }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RegionAlign {
|
||||
pub x: AxisAlign,
|
||||
pub y: AxisAlign,
|
||||
}
|
||||
|
||||
impl RegionAlign {
|
||||
pub const TOP_LEFT: Self = Self::new(AxisAlign::NEG, AxisAlign::NEG);
|
||||
pub const TOP_CENTER: Self = Self::new(AxisAlign::CENTER, AxisAlign::NEG);
|
||||
pub const TOP_RIGHT: Self = Self::new(AxisAlign::POS, AxisAlign::NEG);
|
||||
pub const CENTER_LEFT: Self = Self::new(AxisAlign::NEG, AxisAlign::CENTER);
|
||||
pub const CENTER: Self = Self::new(AxisAlign::CENTER, AxisAlign::CENTER);
|
||||
pub const CENTER_RIGHT: Self = Self::new(AxisAlign::POS, AxisAlign::CENTER);
|
||||
pub const BOT_LEFT: Self = Self::new(AxisAlign::NEG, AxisAlign::POS);
|
||||
pub const BOT_CENTER: Self = Self::new(AxisAlign::CENTER, AxisAlign::POS);
|
||||
pub const BOT_RIGHT: Self = Self::new(AxisAlign::POS, AxisAlign::POS);
|
||||
pub const TOP_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Neg);
|
||||
pub const TOP_CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Neg);
|
||||
pub const TOP_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Neg);
|
||||
pub const CENTER_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Center);
|
||||
pub const CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Center);
|
||||
pub const CENTER_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Center);
|
||||
pub const BOT_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Pos);
|
||||
pub const BOT_CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Pos);
|
||||
pub const BOT_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Pos);
|
||||
|
||||
pub const fn new(x: AxisAlign, y: AxisAlign) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
pub const fn rel(&self) -> Vec2 {
|
||||
vec2(self.x.rel(), self.y.rel())
|
||||
}
|
||||
}
|
||||
|
||||
impl UiVec2 {
|
||||
@@ -150,15 +140,16 @@ impl Vec2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Len {
|
||||
impl UiScalar {
|
||||
pub const fn align(&self, align: AxisAlign) -> UiSpan {
|
||||
let rel = align.rel();
|
||||
let rest = Rel::ONE.sub(rel);
|
||||
let at = Len::from_parts(rel, Px::ZERO);
|
||||
UiSpan {
|
||||
start: Len::from_parts(at.rel.sub(self.rel.mul(rel)), at.px.sub(self.px.mul(rel))),
|
||||
end: Len::from_parts(at.rel.add(self.rel.mul(rest)), at.px.add(self.px.mul(rest))),
|
||||
}
|
||||
let mut start = UiScalar::rel(rel);
|
||||
start.abs -= self.abs * rel;
|
||||
start.rel -= self.rel * rel;
|
||||
let mut end = UiScalar::rel(rel);
|
||||
end.abs += self.abs * (1.0 - rel);
|
||||
end.rel += self.rel * (1.0 - rel);
|
||||
UiSpan { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +165,8 @@ impl From<RegionAlign> for Align {
|
||||
impl From<Align> for RegionAlign {
|
||||
fn from(align: Align) -> Self {
|
||||
Self {
|
||||
x: align.x.unwrap_or(AxisAlign::CENTER),
|
||||
y: align.y.unwrap_or(AxisAlign::CENTER),
|
||||
x: align.x.unwrap_or(AxisAlign::Center),
|
||||
y: align.y.unwrap_or(AxisAlign::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,12 +187,9 @@ impl From<CardinalAlign> for Align {
|
||||
}
|
||||
}
|
||||
|
||||
const impl From<RegionAlign> for UiVec2 {
|
||||
impl const From<RegionAlign> for UiVec2 {
|
||||
fn from(align: RegionAlign) -> Self {
|
||||
Self::new(
|
||||
Len::from_parts(align.x.rel(), Px::ZERO),
|
||||
Len::from_parts(align.y.rel(), Px::ZERO),
|
||||
)
|
||||
Self::rel(align.rel())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,5 +198,3 @@ impl RegionAlign {
|
||||
UiVec2::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(RegionAlign => AxisAlign);
|
||||
@@ -1,16 +1,12 @@
|
||||
use super::*;
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Fixed, FixedVec2};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Axis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
/// Both of them, for the layout code that asks the same question of each.
|
||||
pub const BOTH: [Self; 2] = [Self::X, Self::Y];
|
||||
pub trait Axis_ {
|
||||
}
|
||||
|
||||
impl std::ops::Not for Axis {
|
||||
@@ -47,16 +43,21 @@ pub enum Sign {
|
||||
Pos,
|
||||
}
|
||||
|
||||
impl<const SHIFT: u32> FixedVec2<SHIFT> {
|
||||
pub const fn from_axis(axis: Axis, aligned: Fixed<SHIFT>, ortho: Fixed<SHIFT>) -> Self {
|
||||
impl Vec2 {
|
||||
pub fn axis(&self, axis: Axis) -> f32 {
|
||||
match axis {
|
||||
Axis::X => Self::new(aligned, ortho),
|
||||
Axis::Y => Self::new(ortho, aligned),
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
|
||||
Self {
|
||||
x: match axis {
|
||||
@@ -70,51 +71,3 @@ impl Vec2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const trait AxisT {
|
||||
fn get() -> Axis;
|
||||
}
|
||||
|
||||
pub struct XAxis;
|
||||
const impl AxisT for XAxis {
|
||||
fn get() -> Axis {
|
||||
Axis::X
|
||||
}
|
||||
}
|
||||
|
||||
pub struct YAxis;
|
||||
const impl AxisT for YAxis {
|
||||
fn get() -> Axis {
|
||||
Axis::Y
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BothAxis<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T> BothAxis<T> {
|
||||
pub const fn axis<A: const AxisT>(&mut self) -> &mut T {
|
||||
match A::get() {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
pub fn take_axis<A: const AxisT>(self) -> T {
|
||||
match A::get() {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
pub fn axis_dyn(&mut self, axis: Axis) -> &mut T {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!({const SHIFT: u32} FixedVec2<SHIFT> => Fixed<SHIFT>);
|
||||
impl_axis_index!(Vec2 => f32);
|
||||
@@ -0,0 +1,198 @@
|
||||
use super::*;
|
||||
use crate::{layout::UiNum, util::impl_op};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Size {
|
||||
pub x: Len,
|
||||
pub y: Len,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Len {
|
||||
pub abs: f32,
|
||||
pub rel: f32,
|
||||
pub rest: f32,
|
||||
}
|
||||
|
||||
impl<N: UiNum> From<N> for Len {
|
||||
fn from(value: N) -> Self {
|
||||
Len::abs(value.to_f32())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
|
||||
fn from((x, y): (Nx, Ny)) -> Self {
|
||||
Self {
|
||||
x: x.into(),
|
||||
y: y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Len> for Size {
|
||||
fn from(value: Len) -> Self {
|
||||
Self { x: value, y: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub const ZERO: Self = Self {
|
||||
x: Len::ZERO,
|
||||
y: Len::ZERO,
|
||||
};
|
||||
|
||||
pub const REST: Self = Self {
|
||||
x: Len::REST,
|
||||
y: Len::REST,
|
||||
};
|
||||
|
||||
pub fn abs(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: Len::abs(v.x),
|
||||
y: Len::abs(v.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rel(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: Len::rel(v.x),
|
||||
y: Len::rel(v.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rest(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: Len::rest(v.x),
|
||||
y: Len::rest(v.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_uivec2(self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.apply_rest(),
|
||||
y: self.y.apply_rest(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
|
||||
match axis {
|
||||
Axis::X => Self {
|
||||
x: aligned,
|
||||
y: ortho,
|
||||
},
|
||||
Axis::Y => Self {
|
||||
x: ortho,
|
||||
y: aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis(&self, axis: Axis) -> Len {
|
||||
match axis {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Len {
|
||||
pub const ZERO: Self = Self {
|
||||
abs: 0.0,
|
||||
rel: 0.0,
|
||||
rest: 0.0,
|
||||
};
|
||||
|
||||
pub const REST: Self = Self {
|
||||
abs: 0.0,
|
||||
rel: 0.0,
|
||||
rest: 1.0,
|
||||
};
|
||||
|
||||
pub fn apply_rest(&self) -> UiScalar {
|
||||
UiScalar {
|
||||
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
|
||||
abs: self.abs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn abs(abs: impl UiNum) -> Self {
|
||||
Self {
|
||||
abs: abs.to_f32(),
|
||||
rel: 0.0,
|
||||
rest: 0.0,
|
||||
}
|
||||
}
|
||||
pub fn rel(rel: impl UiNum) -> Self {
|
||||
Self {
|
||||
abs: 0.0,
|
||||
rel: rel.to_f32(),
|
||||
rest: 0.0,
|
||||
}
|
||||
}
|
||||
pub fn rest(ratio: impl UiNum) -> Self {
|
||||
Self {
|
||||
abs: 0.0,
|
||||
rel: 0.0,
|
||||
rest: ratio.to_f32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod len_fns {
|
||||
use super::*;
|
||||
|
||||
pub fn abs(abs: impl UiNum) -> Len {
|
||||
Len {
|
||||
abs: abs.to_f32(),
|
||||
rel: 0.0,
|
||||
rest: 0.0,
|
||||
}
|
||||
}
|
||||
pub fn rel(rel: impl UiNum) -> Len {
|
||||
Len {
|
||||
abs: 0.0,
|
||||
rel: rel.to_f32(),
|
||||
rest: 0.0,
|
||||
}
|
||||
}
|
||||
pub fn rest(ratio: impl UiNum) -> Len {
|
||||
Len {
|
||||
abs: 0.0,
|
||||
rel: 0.0,
|
||||
rest: ratio.to_f32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(Len Add add; abs rel rest);
|
||||
impl_op!(Len Sub sub; abs rel rest);
|
||||
|
||||
impl_op!(Size Add add; x y);
|
||||
impl_op!(Size Sub sub; x y);
|
||||
|
||||
impl Default for Len {
|
||||
fn default() -> Self {
|
||||
Self::rest(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Size {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Len {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.abs != 0.0 {
|
||||
write!(f, "{} abs;", self.abs)?;
|
||||
}
|
||||
if self.rel != 0.0 {
|
||||
write!(f, "{} rel;", self.rel)?;
|
||||
}
|
||||
if self.rest != 0.0 {
|
||||
write!(f, "{} rest;", self.rest)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@ mod axis;
|
||||
mod len;
|
||||
mod pos;
|
||||
|
||||
use crate::util::Vec2;
|
||||
|
||||
use super::Vec2;
|
||||
pub use align::*;
|
||||
pub use axis::*;
|
||||
pub use len::*;
|
||||
@@ -0,0 +1,464 @@
|
||||
use std::{fmt::Display, hash::Hash, marker::Destruct};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
layout::UiNum,
|
||||
util::{LerpUtil, impl_op},
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct UiVec2 {
|
||||
pub x: UiScalar,
|
||||
pub y: UiScalar,
|
||||
}
|
||||
|
||||
impl UiVec2 {
|
||||
pub const ZERO: Self = Self {
|
||||
x: UiScalar::ZERO,
|
||||
y: UiScalar::ZERO,
|
||||
};
|
||||
|
||||
pub const fn new(x: UiScalar, y: UiScalar) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn abs(abs: impl const Into<Vec2>) -> Self {
|
||||
let abs = abs.into();
|
||||
Self {
|
||||
x: UiScalar::abs(abs.x),
|
||||
y: UiScalar::abs(abs.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
|
||||
let rel = rel.into();
|
||||
Self {
|
||||
x: UiScalar::rel(rel.x),
|
||||
y: UiScalar::rel(rel.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn shift(&mut self, offset: impl const Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
*self += offset;
|
||||
}
|
||||
|
||||
pub const fn offset(mut self, offset: impl const Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn within(&self, region: &UiRegion) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.within(®ion.x),
|
||||
y: self.y.within(®ion.y),
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis(&self, axis: Axis) -> UiScalar {
|
||||
match axis {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_abs(&self, rel: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x.to_abs(rel.x),
|
||||
y: self.y.to_abs(rel.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const FULL_SIZE: Self = Self::rel(Vec2::ONE);
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self {
|
||||
match axis {
|
||||
Axis::X => Self {
|
||||
x: aligned,
|
||||
y: ortho,
|
||||
},
|
||||
Axis::Y => Self {
|
||||
x: ortho,
|
||||
y: aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_abs(&self) -> Vec2 {
|
||||
(self.x.abs, self.y.abs).into()
|
||||
}
|
||||
|
||||
pub fn get_rel(&self) -> Vec2 {
|
||||
(self.x.rel, self.y.rel).into()
|
||||
}
|
||||
|
||||
pub fn abs_mut(&mut self) -> Vec2View<'_> {
|
||||
Vec2View {
|
||||
x: &mut self.x.abs,
|
||||
y: &mut self.y.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiVec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "rel{};abs{}", self.get_rel(), self.get_abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(UiVec2 Add add; x y);
|
||||
impl_op!(UiVec2 Sub sub; x y);
|
||||
|
||||
impl const From<Vec2> for UiVec2 {
|
||||
fn from(abs: Vec2) -> Self {
|
||||
Self::abs(abs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for UiVec2
|
||||
where
|
||||
(T, U): const Destruct,
|
||||
{
|
||||
fn from(abs: (T, U)) -> Self {
|
||||
Self::abs(abs)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, Default, bytemuck::Zeroable)]
|
||||
pub struct UiScalar {
|
||||
pub rel: f32,
|
||||
pub abs: f32,
|
||||
}
|
||||
|
||||
impl Eq for UiScalar {}
|
||||
impl Hash for UiScalar {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u32(self.rel.to_bits());
|
||||
state.write_u32(self.abs.to_bits());
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(UiScalar Add add; rel abs);
|
||||
impl_op!(UiScalar Sub sub; rel abs);
|
||||
|
||||
impl UiScalar {
|
||||
pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 };
|
||||
pub const FULL: Self = Self { rel: 1.0, abs: 0.0 };
|
||||
|
||||
pub const fn new(rel: f32, abs: f32) -> Self {
|
||||
Self { rel, abs }
|
||||
}
|
||||
|
||||
pub const fn rel(rel: f32) -> Self {
|
||||
Self { rel, abs: 0.0 }
|
||||
}
|
||||
|
||||
pub const fn abs(abs: f32) -> Self {
|
||||
Self { rel: 0.0, abs }
|
||||
}
|
||||
|
||||
pub const fn rel_min() -> Self {
|
||||
Self::new(0.0, 0.0)
|
||||
}
|
||||
|
||||
pub const fn rel_max() -> Self {
|
||||
Self::new(1.0, 0.0)
|
||||
}
|
||||
|
||||
pub const fn max(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.max(other.rel),
|
||||
abs: self.abs.max(other.abs),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn min(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.min(other.rel),
|
||||
abs: self.abs.min(other.abs),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn offset(mut self, amt: f32) -> Self {
|
||||
self.abs += amt;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn within(&self, span: &UiSpan) -> Self {
|
||||
let anchor = self.rel.lerp(span.start.rel, span.end.rel);
|
||||
let offset = self.abs + self.rel.lerp(span.start.abs, span.end.abs);
|
||||
Self {
|
||||
rel: anchor,
|
||||
abs: offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, span: &UiSpan) -> 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);
|
||||
Self { rel, abs }
|
||||
}
|
||||
|
||||
pub fn within_len(&self, len: UiScalar) -> Self {
|
||||
self.within(&UiSpan {
|
||||
start: UiScalar::ZERO,
|
||||
end: len,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_len(&self, len: UiScalar) -> Self {
|
||||
len.within_len(*self)
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self) {
|
||||
self.rel = 1.0 - self.rel;
|
||||
self.abs = -self.abs;
|
||||
}
|
||||
|
||||
pub const fn to(&self, end: Self) -> UiSpan {
|
||||
UiSpan { start: *self, end }
|
||||
}
|
||||
|
||||
pub const fn to_abs(&self, rel: f32) -> f32 {
|
||||
self.rel * rel + self.abs
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UiSpan {
|
||||
pub start: UiScalar,
|
||||
pub end: UiScalar,
|
||||
}
|
||||
|
||||
impl UiSpan {
|
||||
pub const FULL: Self = Self {
|
||||
start: UiScalar::ZERO,
|
||||
end: UiScalar::FULL,
|
||||
};
|
||||
|
||||
pub const fn rel(rel: f32) -> Self {
|
||||
Self {
|
||||
start: UiScalar::rel(rel),
|
||||
end: UiScalar::rel(rel),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new(start: UiScalar, end: UiScalar) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self) {
|
||||
self.start.flip();
|
||||
self.end.flip();
|
||||
std::mem::swap(&mut self.start.rel, &mut self.end.rel);
|
||||
std::mem::swap(&mut self.start.abs, &mut self.end.abs);
|
||||
}
|
||||
|
||||
pub const fn shift(&mut self, offset: UiScalar) {
|
||||
self.start += offset;
|
||||
self.end += offset;
|
||||
}
|
||||
|
||||
pub const fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
start: self.start.within(parent),
|
||||
end: self.end.within(parent),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
start: self.start.outside(parent),
|
||||
end: self.end.outside(parent),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn len(&self) -> UiScalar {
|
||||
self.end - self.start
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UiRegion {
|
||||
pub x: UiSpan,
|
||||
pub y: UiSpan,
|
||||
}
|
||||
|
||||
impl UiRegion {
|
||||
pub const FULL: Self = Self {
|
||||
x: UiSpan::FULL,
|
||||
y: UiSpan::FULL,
|
||||
};
|
||||
|
||||
pub const fn new(x: UiSpan, y: UiSpan) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn rel(rel: Vec2) -> Self {
|
||||
Self {
|
||||
x: UiSpan::rel(rel.x),
|
||||
y: UiSpan::rel(rel.y),
|
||||
}
|
||||
}
|
||||
pub const fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
x: self.x.within(&parent.x),
|
||||
y: self.y.within(&parent.y),
|
||||
}
|
||||
}
|
||||
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 {
|
||||
Axis::X => &self.x,
|
||||
Axis::Y => &self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn axis_mut(&mut self, axis: Axis) -> &mut UiSpan {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self, axis: Axis) {
|
||||
match axis {
|
||||
Axis::X => self.x.flip(),
|
||||
Axis::Y => self.y.flip(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shift(&mut self, offset: impl Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
self.x.shift(offset.x);
|
||||
self.y.shift(offset.y);
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: impl Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_px(&self, size: Vec2) -> PixelRegion {
|
||||
PixelRegion {
|
||||
top_left: self.top_left().get_rel() * size + self.top_left().get_abs(),
|
||||
bot_right: self.bot_right().get_rel() * size + self.bot_right().get_abs(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn center(&self) -> UiVec2 {
|
||||
Align::CENTER.pos().within(self)
|
||||
}
|
||||
|
||||
pub const fn size(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.len(),
|
||||
y: self.y.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn top_left(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.start,
|
||||
y: self.y.start,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn bot_right(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.end,
|
||||
y: self.y.end,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: UiSpan, ortho: UiSpan) -> Self {
|
||||
Self {
|
||||
x: match axis {
|
||||
Axis::X => aligned,
|
||||
Axis::Y => ortho,
|
||||
},
|
||||
y: match axis {
|
||||
Axis::X => ortho,
|
||||
Axis::Y => aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} -> {} (size: {})",
|
||||
self.top_left(),
|
||||
self.bot_right(),
|
||||
self.size()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PixelRegion {
|
||||
pub top_left: Vec2,
|
||||
pub bot_right: Vec2,
|
||||
}
|
||||
|
||||
impl PixelRegion {
|
||||
pub fn contains(&self, pos: Vec2) -> bool {
|
||||
pos.x >= self.top_left.x
|
||||
&& pos.x <= self.bot_right.x
|
||||
&& pos.y >= self.top_left.y
|
||||
&& pos.y <= self.bot_right.y
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.bot_right - self.top_left
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PixelRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} -> {}", self.top_left, self.bot_right)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Vec2View<'a> {
|
||||
pub x: &'a mut f32,
|
||||
pub y: &'a mut f32,
|
||||
}
|
||||
|
||||
impl Vec2View<'_> {
|
||||
pub fn set(&mut self, other: Vec2) {
|
||||
*self.x = other.x;
|
||||
*self.y = other.y;
|
||||
}
|
||||
|
||||
pub fn add(&mut self, other: Vec2) {
|
||||
*self.x += other.x;
|
||||
*self.y += other.y;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
use std::{marker::Unsize, sync::mpsc::Sender};
|
||||
|
||||
use crate::{
|
||||
layout::{
|
||||
Axis, LayerId, Len, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, Textures, UiId, UiRegion, UiVec2, Vec2, Widget, WidgetHandle, WidgetId,
|
||||
WidgetUpdate,
|
||||
},
|
||||
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||
util::{HashMap, HashSet, RefMap, TrackedArena},
|
||||
};
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
pub struct Painter<'a, 'c> {
|
||||
ctx: &'a mut PainterCtx<'c>,
|
||||
widget: WidgetHandle,
|
||||
id: WidgetId,
|
||||
region: UiRegion,
|
||||
mask: MaskIdx,
|
||||
textures: Vec<TextureHandle>,
|
||||
primitives: Vec<PrimitiveHandle>,
|
||||
children: Vec<WidgetId>,
|
||||
children_width: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
children_height: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
pub layer: usize,
|
||||
}
|
||||
|
||||
/// context for a painter; lets you draw and redraw widgets
|
||||
struct PainterCtx<'a> {
|
||||
ui_id: UiId,
|
||||
pub active: &'a mut HashMap<WidgetId, WidgetInstance>,
|
||||
pub layers: &'a mut PrimitiveLayers,
|
||||
pub textures: &'a mut Textures,
|
||||
pub masks: &'a mut TrackedArena<Mask, u32>,
|
||||
pub text: &'a mut TextData,
|
||||
pub output_size: Vec2,
|
||||
send: &'a Sender<WidgetUpdate>,
|
||||
pub cache_width: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
pub cache_height: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
pub needs_redraw: HashSet<WidgetId>,
|
||||
draw_started: HashSet<WidgetId>,
|
||||
}
|
||||
|
||||
/// stores information for children about the highest level parent that needed their size
|
||||
/// so that they can redraw the parent if their size changes
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ResizeRef {
|
||||
x: Option<(WidgetId, (UiVec2, Len))>,
|
||||
y: Option<(WidgetId, (UiVec2, Len))>,
|
||||
}
|
||||
|
||||
/// important non rendering data for retained drawing
|
||||
#[derive(Debug)]
|
||||
pub struct WidgetInstance {
|
||||
pub id: WidgetId,
|
||||
pub region: UiRegion,
|
||||
pub parent: Option<WidgetId>,
|
||||
pub textures: Vec<TextureHandle>,
|
||||
pub primitives: Vec<PrimitiveHandle>,
|
||||
pub children: Vec<WidgetId>,
|
||||
pub resize: ResizeRef,
|
||||
pub mask: MaskIdx,
|
||||
pub layer: LayerId,
|
||||
}
|
||||
|
||||
/// data to be stored in Ui to create PainterCtxs easily
|
||||
/// TODO: actually use this LMAO
|
||||
pub struct PainterData {
|
||||
ui_id: UiId,
|
||||
pub active: HashMap<WidgetId, WidgetInstance>,
|
||||
pub layers: PrimitiveLayers,
|
||||
pub textures: Textures,
|
||||
pub text: TextData,
|
||||
pub output_size: Vec2,
|
||||
pub px_dependent: HashSet<WidgetId>,
|
||||
pub masks: TrackedArena<Mask, u32>,
|
||||
send: Sender<WidgetUpdate>,
|
||||
}
|
||||
|
||||
impl PainterData {
|
||||
pub fn new(ui_id: UiId, send: Sender<WidgetUpdate>) -> Self {
|
||||
Self {
|
||||
ui_id,
|
||||
active: Default::default(),
|
||||
layers: Default::default(),
|
||||
textures: Default::default(),
|
||||
text: Default::default(),
|
||||
output_size: Default::default(),
|
||||
px_dependent: Default::default(),
|
||||
masks: Default::default(),
|
||||
send,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PainterCtx<'a> {
|
||||
/// redraws a widget that's currently active (drawn)
|
||||
/// can be called on something already drawn or removed,
|
||||
/// will just return if so
|
||||
pub fn redraw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, widget: &WidgetHandle<W>) {
|
||||
let id = widget.id();
|
||||
self.needs_redraw.remove(&id);
|
||||
if self.draw_started.contains(&id) {
|
||||
return;
|
||||
}
|
||||
let Some(active) = self.active.get(&id) else {
|
||||
return;
|
||||
};
|
||||
let mut resize = active.resize;
|
||||
|
||||
// set resize back after redrawing
|
||||
let finish = |s: &mut Self, resize| {
|
||||
if let Some(active) = s.active.get_mut(&id) {
|
||||
// might need to get_or_insert here instead of just assuming
|
||||
active.resize = resize;
|
||||
}
|
||||
};
|
||||
|
||||
// check if a parent depends on the desired size of this, if so then redraw it first
|
||||
// TODO: this is stupid having 2 of these, don't ask me what the consequences are
|
||||
let mut ret = false;
|
||||
if let Some((rid, (outer, old_desired))) = &mut resize.x {
|
||||
let new_desired = SizeCtx {
|
||||
source: id,
|
||||
cache_width: &mut self.cache_width,
|
||||
cache_height: &mut self.cache_height,
|
||||
text: self.text,
|
||||
textures: self.textures,
|
||||
outer: *outer,
|
||||
output_size: self.output_size,
|
||||
checked_width: &mut Default::default(),
|
||||
checked_height: &mut Default::default(),
|
||||
id,
|
||||
}
|
||||
.width(widget);
|
||||
if new_desired != *old_desired
|
||||
&& let Some(w) = rid.strong()
|
||||
{
|
||||
// unsure if I need to walk down the tree here
|
||||
self.redraw(&w);
|
||||
*old_desired = new_desired;
|
||||
if self.draw_started.contains(&id) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((rid, (outer, old_desired))) = &mut resize.y {
|
||||
// NOTE: might need hack in Span here (or also do it properly here)
|
||||
let new_desired = SizeCtx {
|
||||
source: id,
|
||||
cache_width: &mut self.cache_width,
|
||||
cache_height: &mut self.cache_height,
|
||||
text: self.text,
|
||||
textures: self.textures,
|
||||
outer: *outer,
|
||||
output_size: self.output_size,
|
||||
checked_width: &mut Default::default(),
|
||||
checked_height: &mut Default::default(),
|
||||
id,
|
||||
}
|
||||
.height(widget);
|
||||
if new_desired != *old_desired
|
||||
&& let Some(w) = rid.strong()
|
||||
{
|
||||
self.redraw(&w);
|
||||
*old_desired = new_desired;
|
||||
if self.draw_started.contains(&id) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ret {
|
||||
return finish(self, resize);
|
||||
}
|
||||
|
||||
let Some(active) = self.remove(id, false) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.draw_inner(
|
||||
active.layer,
|
||||
widget,
|
||||
active.region,
|
||||
active.parent,
|
||||
active.mask,
|
||||
Some(active.children),
|
||||
);
|
||||
finish(self, resize);
|
||||
}
|
||||
|
||||
fn draw_inner<W: Widget + ?Sized + Unsize<dyn Widget>>(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
widget: &WidgetHandle<W>,
|
||||
region: UiRegion,
|
||||
parent: Option<WidgetId>,
|
||||
mask: MaskIdx,
|
||||
old_children: Option<Vec<WidgetId>>,
|
||||
) {
|
||||
let id = widget.id();
|
||||
widget.data_mut().send = Some(self.send.clone());
|
||||
// I have no idea if these checks work lol
|
||||
// the idea is u can't redraw stuff u already drew,
|
||||
// and if parent is different then there's another copy with a different parent
|
||||
// but this has a very weird issue where you can't move widgets unless u remove first
|
||||
// so swapping is impossible rn I think?
|
||||
// there's definitely better solutions like a counter (>1 = panic) but don't care rn
|
||||
// if self.draw_started.contains(&id) {
|
||||
// panic!(
|
||||
// "Cannot draw the same widget ({}) twice (1)",
|
||||
// self.widgets.data(&id).unwrap().label
|
||||
// );
|
||||
// }
|
||||
let mut old_children = old_children.unwrap_or_default();
|
||||
let mut resize = ResizeRef::default();
|
||||
if let Some(active) = self.active.get_mut(&id)
|
||||
&& !self.needs_redraw.contains(&id)
|
||||
{
|
||||
// check to see if we can skip drawing first
|
||||
if active.parent != parent {
|
||||
panic!("Cannot draw the same widget twice (2)");
|
||||
}
|
||||
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
|
||||
let active = self.remove(id, false).unwrap();
|
||||
old_children = active.children;
|
||||
resize = active.resize;
|
||||
}
|
||||
|
||||
// draw widget
|
||||
self.draw_started.insert(id);
|
||||
|
||||
let mut painter = Painter {
|
||||
region,
|
||||
mask,
|
||||
layer,
|
||||
widget: widget.as_any(),
|
||||
id,
|
||||
textures: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
ctx: self,
|
||||
children: Vec::new(),
|
||||
children_width: Default::default(),
|
||||
children_height: Default::default(),
|
||||
};
|
||||
|
||||
widget.get_mut_quiet().draw(&mut painter);
|
||||
|
||||
let children_width = painter.children_width;
|
||||
let children_height = painter.children_height;
|
||||
|
||||
// add to active
|
||||
let instance = WidgetInstance {
|
||||
id,
|
||||
region,
|
||||
parent,
|
||||
textures: painter.textures,
|
||||
primitives: painter.primitives,
|
||||
children: painter.children,
|
||||
resize,
|
||||
mask: painter.mask,
|
||||
layer,
|
||||
};
|
||||
// set resize for children who's size this widget depends on
|
||||
for (cid, outer) in children_width {
|
||||
if let Some(w) = self.active.get_mut(&cid)
|
||||
&& w.resize.x.is_none()
|
||||
{
|
||||
w.resize.x = Some((id, outer))
|
||||
}
|
||||
}
|
||||
for (cid, outer) in children_height {
|
||||
if let Some(w) = self.active.get_mut(&cid)
|
||||
&& w.resize.y.is_none()
|
||||
{
|
||||
w.resize.y = Some((id, outer))
|
||||
}
|
||||
}
|
||||
|
||||
// remove old children that weren't kept
|
||||
for c in &old_children {
|
||||
if !instance.children.contains(c) {
|
||||
self.remove_rec(*c);
|
||||
}
|
||||
}
|
||||
|
||||
// run events
|
||||
id.map_event_managers(|mut m| {
|
||||
m.draw(self.ui_id, &instance);
|
||||
});
|
||||
self.active.insert(id, instance);
|
||||
}
|
||||
|
||||
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
for h in &active.primitives {
|
||||
let region = self.layers[h.layer].region_mut(h);
|
||||
*region = region.outside(&from).within(&to);
|
||||
}
|
||||
active.region = active.region.outside(&from).within(&to);
|
||||
// children will not be changed, so this technically should not be needed
|
||||
// probably need unsafe
|
||||
let children = active.children.clone();
|
||||
for child in children {
|
||||
self.mov(child, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE: instance textures are cleared and self.textures freed
|
||||
fn remove(&mut self, id: WidgetId, undraw: bool) -> Option<WidgetInstance> {
|
||||
let mut inst = self.active.remove(&id);
|
||||
if let Some(inst) = &mut inst {
|
||||
for h in &inst.primitives {
|
||||
let mask = self.layers.free(h);
|
||||
if mask != MaskIdx::NONE {
|
||||
self.masks.remove(mask);
|
||||
}
|
||||
}
|
||||
inst.textures.clear();
|
||||
self.textures.free();
|
||||
if undraw {
|
||||
id.map_event_managers(|mut m| {
|
||||
m.undraw(self.ui_id, inst);
|
||||
});
|
||||
}
|
||||
}
|
||||
inst
|
||||
}
|
||||
|
||||
fn remove_rec(&mut self, id: WidgetId) -> Option<WidgetInstance> {
|
||||
let inst = self.remove(id, true);
|
||||
if let Some(inst) = &inst {
|
||||
for c in &inst.children {
|
||||
self.remove_rec(*c);
|
||||
}
|
||||
}
|
||||
inst
|
||||
}
|
||||
}
|
||||
|
||||
impl PainterData {
|
||||
fn ctx(&mut self, needs_redraw: HashSet<WidgetId>) -> PainterCtx<'_> {
|
||||
PainterCtx {
|
||||
ui_id: self.ui_id,
|
||||
active: &mut self.active,
|
||||
layers: &mut self.layers,
|
||||
textures: &mut self.textures,
|
||||
text: &mut self.text,
|
||||
output_size: self.output_size,
|
||||
masks: &mut self.masks,
|
||||
send: &self.send,
|
||||
cache_width: Default::default(),
|
||||
cache_height: Default::default(),
|
||||
draw_started: Default::default(),
|
||||
needs_redraw,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetHandle<W>) {
|
||||
let mut ctx = self.ctx(Default::default());
|
||||
ctx.draw_started.clear();
|
||||
ctx.layers.clear();
|
||||
ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None);
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self, ids: HashSet<WidgetId>) {
|
||||
let mut ctx = self.ctx(ids);
|
||||
while let Some(&id) = ctx.needs_redraw.iter().next() {
|
||||
if let Some(w) = id.strong() {
|
||||
ctx.redraw(&w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'c> Painter<'a, 'c> {
|
||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||
let h = self.ctx.layers.write(
|
||||
self.layer,
|
||||
PrimitiveInst {
|
||||
id: self.id(),
|
||||
primitive,
|
||||
region,
|
||||
mask_idx: self.mask,
|
||||
},
|
||||
);
|
||||
if self.mask != MaskIdx::NONE {
|
||||
// TODO: I have no clue if this works at all :joy:
|
||||
self.ctx.masks.push_ref(self.mask);
|
||||
}
|
||||
self.primitives.push(h);
|
||||
}
|
||||
|
||||
/// Writes a primitive to be rendered
|
||||
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
|
||||
self.primitive_at(primitive, self.region)
|
||||
}
|
||||
|
||||
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||
self.primitive_at(primitive, region.within(&self.region));
|
||||
}
|
||||
|
||||
pub fn set_mask(&mut self, region: UiRegion) {
|
||||
assert!(self.mask == MaskIdx::NONE);
|
||||
self.mask = self.ctx.masks.push(Mask { region });
|
||||
}
|
||||
|
||||
/// Draws a widget within this widget's region.
|
||||
pub fn widget<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetHandle<W>) {
|
||||
self.widget_at(id, self.region);
|
||||
}
|
||||
|
||||
/// Draws a widget somewhere within this one.
|
||||
/// Useful for drawing child widgets in select areas.
|
||||
pub fn widget_within<W: Widget + ?Sized + Unsize<dyn Widget>>(
|
||||
&mut self,
|
||||
id: &WidgetHandle<W>,
|
||||
region: UiRegion,
|
||||
) {
|
||||
self.widget_at(id, region.within(&self.region));
|
||||
}
|
||||
|
||||
fn widget_at<W: Widget + ?Sized + Unsize<dyn Widget>>(
|
||||
&mut self,
|
||||
id: &WidgetHandle<W>,
|
||||
region: UiRegion,
|
||||
) {
|
||||
self.children.push(id.id());
|
||||
self.ctx
|
||||
.draw_inner(self.layer, id, region, Some(self.id()), self.mask, None);
|
||||
}
|
||||
|
||||
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||
self.textures.push(handle.clone());
|
||||
self.primitive_at(handle.primitive(), region.within(&self.region));
|
||||
}
|
||||
|
||||
pub fn texture(&mut self, handle: &TextureHandle) {
|
||||
self.textures.push(handle.clone());
|
||||
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.ctx
|
||||
.text
|
||||
.get_mut()
|
||||
.draw(buffer, attrs, self.ctx.textures)
|
||||
}
|
||||
|
||||
pub fn region(&self) -> UiRegion {
|
||||
self.region
|
||||
}
|
||||
|
||||
pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>) -> Size {
|
||||
self.size_ctx().size(id)
|
||||
}
|
||||
|
||||
pub fn len_axis<W: Widget + ?Sized>(&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 size_ctx(&mut self) -> SizeCtx<'_> {
|
||||
SizeCtx {
|
||||
source: self.id(),
|
||||
id: self.id(),
|
||||
text: self.ctx.text,
|
||||
textures: self.ctx.textures,
|
||||
output_size: self.ctx.output_size,
|
||||
checked_width: &mut self.children_width,
|
||||
checked_height: &mut self.children_height,
|
||||
cache_width: &mut self.ctx.cache_width,
|
||||
cache_height: &mut self.ctx.cache_height,
|
||||
outer: self.region.size(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_size(&self) -> Vec2 {
|
||||
self.ctx.output_size
|
||||
}
|
||||
|
||||
pub fn px_size(&mut self) -> Vec2 {
|
||||
self.region.size().to_abs(self.ctx.output_size)
|
||||
}
|
||||
|
||||
pub fn text_data(&mut self) -> &mut TextData {
|
||||
self.ctx.text
|
||||
}
|
||||
|
||||
pub fn child_layer(&mut self) {
|
||||
self.layer = self.ctx.layers.child(self.layer);
|
||||
}
|
||||
|
||||
pub fn next_layer(&mut self) {
|
||||
self.layer = self.ctx.layers.next(self.layer);
|
||||
}
|
||||
|
||||
pub fn label(&self) -> RefMap<'_, String> {
|
||||
self.widget.get_label()
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SizeCtx<'a> {
|
||||
pub text: &'a mut TextData,
|
||||
pub textures: &'a mut Textures,
|
||||
source: WidgetId,
|
||||
cache_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
cache_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
checked_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
checked_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
/// TODO: should this be pub? rn used for sized
|
||||
pub outer: UiVec2,
|
||||
output_size: Vec2,
|
||||
id: WidgetId,
|
||||
}
|
||||
|
||||
impl SizeCtx<'_> {
|
||||
pub fn id(&self) -> &WidgetId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &WidgetId {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn width<W: Widget + ?Sized>(&mut self, widget: &WidgetHandle<W>) -> Len {
|
||||
// first check cache
|
||||
// TODO: is this needed? broken rn bc does not store children during upper size check,
|
||||
// so if something actually using check_* hits cache it fails to add them
|
||||
// if let Some(&(outer, len)) = self.cache_width.get(&id)
|
||||
// && outer == self.outer
|
||||
// {
|
||||
// self.checked_width.insert(id, (self.outer, len));
|
||||
// return len;
|
||||
// }
|
||||
// store self vars that need to be maintained
|
||||
let id = widget.id();
|
||||
let self_outer = self.outer;
|
||||
let self_id = self.id;
|
||||
// get size of input id
|
||||
self.id = id;
|
||||
let len = widget.get_mut_quiet().desired_width(self);
|
||||
// restore vars & update cache + checked
|
||||
self.outer = self_outer;
|
||||
self.id = self_id;
|
||||
self.cache_width.insert(id, (self.outer, len));
|
||||
self.checked_width.insert(id, (self.outer, len));
|
||||
len
|
||||
}
|
||||
|
||||
// TODO: should be refactored to share code w width_inner
|
||||
pub fn height<W: Widget + ?Sized>(&mut self, widget: &WidgetHandle<W>) -> Len {
|
||||
// if let Some(&(outer, len)) = self.cache_height.get(&id)
|
||||
// && outer == self.outer
|
||||
// {
|
||||
// self.checked_height.insert(id, (self.outer, len));
|
||||
// return len;
|
||||
// }
|
||||
let id = widget.id();
|
||||
let self_outer = self.outer;
|
||||
let self_id = self.id;
|
||||
self.id = id;
|
||||
let len = widget.get_mut_quiet().desired_height(self);
|
||||
self.outer = self_outer;
|
||||
self.id = self_id;
|
||||
self.cache_height.insert(id, (self.outer, len));
|
||||
self.checked_height.insert(id, (self.outer, len));
|
||||
len
|
||||
}
|
||||
|
||||
pub fn len_axis<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len {
|
||||
match axis {
|
||||
Axis::X => self.width(id),
|
||||
Axis::Y => self.height(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>) -> Size {
|
||||
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.get_mut().draw(buffer, attrs, self.textures)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
#![allow(clippy::multiple_bound_locations)]
|
||||
|
||||
use std::marker::Destruct;
|
||||
|
||||
/// stored in linear for sane manipulation
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
||||
pub struct Color<T> {
|
||||
pub struct Color<T: ColorNum> {
|
||||
pub r: T,
|
||||
pub g: T,
|
||||
pub b: T,
|
||||
pub a: T,
|
||||
}
|
||||
|
||||
impl<T: ColorNum> Default for Color<T> {
|
||||
fn default() -> Self {
|
||||
Self::BLACK
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ColorNum> Color<T> {
|
||||
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
|
||||
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
|
||||
@@ -150,7 +146,7 @@ impl ColorNum for f32 {
|
||||
|
||||
unsafe impl bytemuck::Pod for Color<u8> {}
|
||||
|
||||
const impl F32Conversion for f32 {
|
||||
impl const F32Conversion for f32 {
|
||||
fn to(self) -> f32 {
|
||||
self
|
||||
}
|
||||
@@ -159,7 +155,7 @@ const impl F32Conversion for f32 {
|
||||
}
|
||||
}
|
||||
|
||||
const impl F32Conversion for u8 {
|
||||
impl const F32Conversion for u8 {
|
||||
fn to(self) -> f32 {
|
||||
self as f32 / 255.0
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use crate::{
|
||||
render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||
util::to_mut,
|
||||
};
|
||||
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
|
||||
|
||||
pub type LayerId = usize;
|
||||
|
||||
@@ -39,7 +36,7 @@ struct Child {
|
||||
tail: usize,
|
||||
}
|
||||
|
||||
pub type DrawLayers = Layers<LayerDraws>;
|
||||
pub type PrimitiveLayers = Layers<Primitives>;
|
||||
|
||||
impl<T: Default> Layers<T> {
|
||||
pub fn new() -> Layers<T> {
|
||||
@@ -54,13 +51,13 @@ impl<T: Default> Layers<T> {
|
||||
self.vec.push(LayerNode::head());
|
||||
}
|
||||
|
||||
fn push(&mut self, node: LayerNode<T>) -> LayerId {
|
||||
fn push(&mut self, node: LayerNode<T>) -> usize {
|
||||
let i = self.vec.len();
|
||||
self.vec.push(node);
|
||||
i
|
||||
}
|
||||
|
||||
pub fn next(&mut self, i: LayerId) -> LayerId {
|
||||
pub fn next(&mut self, i: usize) -> usize {
|
||||
if let Ptr::Next(i) = self.vec[i].next {
|
||||
return i;
|
||||
}
|
||||
@@ -80,7 +77,7 @@ impl<T: Default> Layers<T> {
|
||||
i_new
|
||||
}
|
||||
|
||||
pub fn child(&mut self, i: LayerId) -> LayerId {
|
||||
pub fn child(&mut self, i: usize) -> usize {
|
||||
if let Some(c) = self.vec[i].child {
|
||||
return c.head;
|
||||
}
|
||||
@@ -105,11 +102,11 @@ impl<T: Default> Layers<T> {
|
||||
self.vec.iter_mut().map(|n| &mut n.data).enumerate()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (LayerId, &T)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
|
||||
self.indices().map(|i| (i, &self.vec[i].data))
|
||||
}
|
||||
|
||||
pub fn iter_depth(&self) -> impl Iterator<Item = ((LayerId, usize), &T)> {
|
||||
pub fn iter_depth(&self) -> impl Iterator<Item = ((usize, usize), &T)> {
|
||||
self.indices()
|
||||
.map(|i| ((i, self.vec[i].depth), &self.vec[i].data))
|
||||
}
|
||||
@@ -119,16 +116,8 @@ impl<T: Default> Layers<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawLayers {
|
||||
/// Inlined on purpose: it is one call per glyph, the innermost thing a
|
||||
/// frame does, and whether the inliner takes it turns out to depend on
|
||||
/// unrelated code elsewhere in the crate -- 12% of a resize frame.
|
||||
#[inline]
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: LayerId,
|
||||
info: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
impl PrimitiveLayers {
|
||||
pub fn write<P: Primitive>(&mut self, layer: usize, info: PrimitiveInst<P>) -> PrimitiveHandle {
|
||||
self[layer].write(layer, info)
|
||||
}
|
||||
|
||||
@@ -143,16 +132,16 @@ impl<T: Default> Default for Layers<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<LayerId> for Layers<T> {
|
||||
impl<T> Index<usize> for Layers<T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: LayerId) -> &Self::Output {
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.vec[index].data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<LayerId> for Layers<T> {
|
||||
fn index_mut(&mut self, index: LayerId) -> &mut Self::Output {
|
||||
impl<T> IndexMut<usize> for Layers<T> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.vec[index].data
|
||||
}
|
||||
}
|
||||
@@ -183,7 +172,8 @@ impl<'a, T> Iterator for LayerIteratorMut<'a, T> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let i = self.inner.next()?;
|
||||
// SAFETY: requires index iterator to work properly
|
||||
let layer = unsafe { to_mut(&self.inner.vec[i].data) };
|
||||
#[allow(mutable_transmutes)]
|
||||
let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
|
||||
Some((i, layer))
|
||||
}
|
||||
}
|
||||
@@ -192,7 +182,8 @@ impl<'a, T> DoubleEndedIterator for LayerIteratorMut<'a, T> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let i = self.inner.next_back()?;
|
||||
// SAFETY: requires index iterator to work properly
|
||||
let layer = unsafe { to_mut(&self.inner.vec[i].data) };
|
||||
#[allow(mutable_transmutes)]
|
||||
let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
|
||||
Some((i, layer))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//! tree structure for masking
|
||||
|
||||
use crate::layout::UiRegion;
|
||||
|
||||
pub struct Masks {
|
||||
data: Vec<MaskNode>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MaskPtr(u32);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MaskNode {
|
||||
/// TODO: this is just a rect for now,
|
||||
/// but would like to support arbitrary masks
|
||||
/// at some point; custom shader
|
||||
/// would probably handle that case
|
||||
/// bc you'd need to render to a special target
|
||||
/// anyways
|
||||
region: UiRegion,
|
||||
prev: MaskPtr,
|
||||
}
|
||||
|
||||
impl MaskPtr {
|
||||
const NONE: Self = Self(u32::MAX);
|
||||
}
|
||||
|
||||
impl Masks {
|
||||
pub fn push(&mut self, parent: MaskPtr, region: UiRegion) -> MaskPtr {
|
||||
match parent.0 {
|
||||
_ => {
|
||||
}
|
||||
u32::MAX => {
|
||||
let i = self.data.len();
|
||||
self.data.push(MaskNode {
|
||||
region,
|
||||
prev: parent,
|
||||
});
|
||||
MaskPtr(i as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop(&mut self, i: usize) {}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,193 @@
|
||||
use std::simd::{Simd, num::SimdUint};
|
||||
|
||||
use crate::{
|
||||
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2},
|
||||
util::Handle,
|
||||
};
|
||||
use cosmic_text::{
|
||||
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
|
||||
SwashContent,
|
||||
};
|
||||
use image::{GenericImageView, RgbaImage};
|
||||
|
||||
/// TODO: properly wrap this
|
||||
pub mod text_lib {
|
||||
pub use cosmic_text::*;
|
||||
}
|
||||
|
||||
pub type TextData = Handle<TextDataInner>;
|
||||
|
||||
pub struct TextDataInner {
|
||||
pub font_system: FontSystem,
|
||||
pub swash_cache: SwashCache,
|
||||
glyph_cache: Vec<(Placement, CacheKey, Color)>,
|
||||
}
|
||||
|
||||
impl Default for TextDataInner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_system: FontSystem::new(),
|
||||
swash_cache: SwashCache::new(),
|
||||
glyph_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TextAttrs {
|
||||
pub color: UiColor,
|
||||
pub font_size: f32,
|
||||
pub line_height: f32,
|
||||
pub family: Family<'static>,
|
||||
pub wrap: bool,
|
||||
/// inner alignment of text region (within where it's drawn)
|
||||
pub align: RegionAlign,
|
||||
}
|
||||
|
||||
impl TextAttrs {
|
||||
pub fn apply(&self, font_system: &mut FontSystem, buf: &mut Buffer, width: Option<f32>) {
|
||||
buf.set_metrics_and_size(
|
||||
font_system,
|
||||
Metrics::new(self.font_size, self.line_height),
|
||||
width,
|
||||
None,
|
||||
);
|
||||
let attrs = Attrs::new().family(self.family);
|
||||
let list = AttrsList::new(&attrs);
|
||||
for line in &mut buf.lines {
|
||||
line.set_attrs_list(list.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type TextBuffer = Buffer;
|
||||
|
||||
impl Default for TextAttrs {
|
||||
fn default() -> Self {
|
||||
let size = 14.0;
|
||||
Self {
|
||||
color: UiColor::WHITE,
|
||||
font_size: size,
|
||||
line_height: size * LINE_HEIGHT_MULT,
|
||||
family: Family::SansSerif,
|
||||
wrap: false,
|
||||
align: Align::CENTER_LEFT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const LINE_HEIGHT_MULT: f32 = 1.1;
|
||||
|
||||
impl TextDataInner {
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
buffer: &mut TextBuffer,
|
||||
attrs: &TextAttrs,
|
||||
textures: &mut Textures,
|
||||
) -> RenderedText {
|
||||
// TODO: either this or the layout stuff (or both) is super slow,
|
||||
// should probably do texture packing and things if possible.
|
||||
// very visible if you add just a couple of wrapping texts and resize window
|
||||
// should also be timed to figure out exactly what points need to be sped up
|
||||
// let mut pixels = HashMap::<_, [u8; 4]>::default();
|
||||
let mut min_x = 0;
|
||||
let mut min_y = 0;
|
||||
let mut max_x = 0;
|
||||
let mut max_y = 0;
|
||||
let text_color = {
|
||||
let c = attrs.color;
|
||||
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
|
||||
};
|
||||
let mut max_width = 0.0f32;
|
||||
let mut height = 0.0;
|
||||
|
||||
for run in buffer.layout_runs() {
|
||||
for glyph in run.glyphs.iter() {
|
||||
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
||||
|
||||
let glyph_color = match glyph.color_opt {
|
||||
Some(some) => some,
|
||||
None => text_color,
|
||||
};
|
||||
|
||||
if let Some(img) = self
|
||||
.swash_cache
|
||||
.get_image(&mut self.font_system, physical_glyph.cache_key)
|
||||
{
|
||||
let mut pos = img.placement;
|
||||
pos.left += physical_glyph.x;
|
||||
pos.top = physical_glyph.y + run.line_y as i32 - pos.top;
|
||||
min_x = min_x.min(pos.left);
|
||||
min_y = min_y.min(pos.top);
|
||||
max_x = max_x.max(pos.left + pos.width as i32);
|
||||
max_y = max_y.max(pos.top + pos.height as i32);
|
||||
self.glyph_cache
|
||||
.push((pos, physical_glyph.cache_key, glyph_color));
|
||||
}
|
||||
}
|
||||
max_width = max_width.max(run.line_w);
|
||||
height += run.line_height;
|
||||
}
|
||||
let img_width = (max_x - min_x + 1) as u32;
|
||||
let img_height = (max_y - min_y + 1) as u32;
|
||||
let mut image = RgbaImage::new(img_width, img_height);
|
||||
|
||||
for (pos, key, color) in self.glyph_cache.drain(..) {
|
||||
let img = self
|
||||
.swash_cache
|
||||
.get_image(&mut self.font_system, key)
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let mut merge = |i, color: [u8; 4]| {
|
||||
let i = i as i32;
|
||||
let x = (i % pos.width as i32 + pos.left - min_x) as u32;
|
||||
let y = (i / pos.width as i32 + pos.top - min_y) as u32;
|
||||
let pixel = &mut image[(x, y)].0;
|
||||
// TODO: no clue if proper alpha blending should be done
|
||||
*pixel = Simd::from(color).saturating_add(Simd::from(*pixel)).into();
|
||||
};
|
||||
|
||||
match img.content {
|
||||
SwashContent::Mask => {
|
||||
for (i, a) in img.data.iter().enumerate() {
|
||||
let mut color = color.as_rgba();
|
||||
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
|
||||
merge(i, color);
|
||||
}
|
||||
}
|
||||
SwashContent::SubpixelMask => todo!("subpixel mask text rendering"),
|
||||
SwashContent::Color => {
|
||||
let (colors, _) = img.data.as_chunks::<4>();
|
||||
for (i, color) in colors.iter().enumerate() {
|
||||
merge(i, *color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let max_dim = 8192;
|
||||
if image.width() > max_dim || image.height() > max_dim {
|
||||
let width = image.width().min(max_dim);
|
||||
let height = image.height().min(max_dim);
|
||||
eprintln!(
|
||||
"WARNING: image of size {:?} cropped to {:?} (texture too big)",
|
||||
image.dimensions(),
|
||||
(width, height)
|
||||
);
|
||||
image = image.view(0, 0, width, height).to_image();
|
||||
}
|
||||
|
||||
RenderedText {
|
||||
handle: textures.add(image),
|
||||
top_left_offset: Vec2::new(min_x as f32, min_y as f32),
|
||||
size: Vec2::new(max_width, height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderedText {
|
||||
pub handle: TextureHandle,
|
||||
pub top_left_offset: Vec2,
|
||||
pub size: Vec2,
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
use crate::util::{RefCounter, Vec2};
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
use std::{
|
||||
ops::Index,
|
||||
sync::mpsc::{Receiver, Sender, channel},
|
||||
};
|
||||
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
|
||||
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextureHandle {
|
||||
slot: u32,
|
||||
inner: TexturePrimitive,
|
||||
size: Vec2,
|
||||
counter: RefCounter,
|
||||
send: Sender<u32>,
|
||||
@@ -26,26 +28,14 @@ pub struct Textures {
|
||||
pub enum TextureUpdate<'a> {
|
||||
Push(&'a DynamicImage),
|
||||
Set(u32, &'a DynamicImage),
|
||||
Patch(u32, PatchRect, &'a DynamicImage),
|
||||
Free(u32),
|
||||
/// Added and freed before the renderer drained either update. It still has
|
||||
/// to push a slot to stay lined up with `images`; `Free` then empties it.
|
||||
PushFree,
|
||||
SetFree,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PatchRect {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
enum Update {
|
||||
Push(u32),
|
||||
Set(u32),
|
||||
Patch(u32, PatchRect),
|
||||
Free(u32),
|
||||
}
|
||||
|
||||
@@ -63,8 +53,14 @@ impl Textures {
|
||||
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
||||
let image = image.into();
|
||||
let size = image.dimensions().into();
|
||||
let view_idx = self.push(image);
|
||||
// 0 == default in renderer; TODO: actually create samplers here
|
||||
let sampler_idx = 0;
|
||||
TextureHandle {
|
||||
slot: self.push(image),
|
||||
inner: TexturePrimitive {
|
||||
view_idx,
|
||||
sampler_idx,
|
||||
},
|
||||
size,
|
||||
counter: RefCounter::new(),
|
||||
send: self.send.clone(),
|
||||
@@ -84,23 +80,6 @@ impl Textures {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_mut(&mut self, handle: &TextureHandle) -> &mut DynamicImage {
|
||||
self.images[handle.slot as usize]
|
||||
.as_mut()
|
||||
.expect("texture was freed while still held")
|
||||
}
|
||||
|
||||
/// Queue an upload of just `rect`, after writing it with `image_mut`.
|
||||
pub fn patch(&mut self, handle: &TextureHandle, rect: PatchRect) {
|
||||
self.updates.push(Update::Patch(handle.slot, rect));
|
||||
}
|
||||
|
||||
/// How many textures are live, which is what a ui can ask; the renderer's
|
||||
/// copies follow from the updates it drains.
|
||||
pub fn count(&self) -> usize {
|
||||
self.images.iter().flatten().count()
|
||||
}
|
||||
|
||||
pub fn free(&mut self) {
|
||||
for idx in self.recv.try_iter() {
|
||||
self.images[idx as usize] = None;
|
||||
@@ -119,19 +98,14 @@ impl Textures {
|
||||
.as_ref()
|
||||
.map(|img| TextureUpdate::Set(i, img))
|
||||
.unwrap_or(TextureUpdate::SetFree),
|
||||
Update::Patch(i, rect) => self.images[i as usize]
|
||||
.as_ref()
|
||||
.map(|img| TextureUpdate::Patch(i, rect, img))
|
||||
.unwrap_or(TextureUpdate::SetFree),
|
||||
Update::Free(i) => TextureUpdate::Free(i),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureHandle {
|
||||
/// Index into `Textures`, and into the renderer's parallel slots.
|
||||
pub fn slot(&self) -> u32 {
|
||||
self.slot
|
||||
pub fn primitive(&self) -> TexturePrimitive {
|
||||
self.inner
|
||||
}
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.size
|
||||
@@ -141,7 +115,7 @@ impl TextureHandle {
|
||||
impl Drop for TextureHandle {
|
||||
fn drop(&mut self) {
|
||||
if self.counter.drop() {
|
||||
let _ = self.send.send(self.slot);
|
||||
let _ = self.send.send(self.inner.view_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +124,7 @@ impl Index<&TextureHandle> for Textures {
|
||||
type Output = DynamicImage;
|
||||
|
||||
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
||||
self.images[index.slot as usize].as_ref().unwrap()
|
||||
self.images[index.inner.view_idx as usize].as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
use image::DynamicImage;
|
||||
|
||||
use crate::{
|
||||
layout::{
|
||||
IdLike, PainterData, PixelRegion, TextureHandle, Vec2, WidgetHandle, WidgetId,
|
||||
WidgetInstance, WidgetLike, WidgetUpdate,
|
||||
},
|
||||
util::{HashMap, HashSet, Id, StaticIdTracker},
|
||||
};
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
|
||||
static ID_TRACKER: StaticIdTracker = StaticIdTracker::new();
|
||||
pub type UiId = Id;
|
||||
|
||||
pub struct Ui {
|
||||
id: UiId,
|
||||
pub(crate) data: PainterData,
|
||||
root: Option<WidgetHandle>,
|
||||
updates: HashSet<WidgetId>,
|
||||
recv: Receiver<WidgetUpdate>,
|
||||
full_redraw: bool,
|
||||
resized: bool,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn id(&self) -> UiId {
|
||||
self.id
|
||||
}
|
||||
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
|
||||
self.root = Some(w.add(self));
|
||||
self.full_redraw = true;
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
|
||||
self.data.textures.add(image)
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
||||
self.data.output_size = size.into();
|
||||
self.resized = true;
|
||||
}
|
||||
|
||||
fn redraw_all(&mut self) {
|
||||
for (id, inst) in self.data.active.drain() {
|
||||
id.map_event_managers(|mut m| {
|
||||
m.undraw(self.id, &inst);
|
||||
});
|
||||
}
|
||||
// free before bc nothing should exist
|
||||
self.free();
|
||||
if let Some(root) = &self.root {
|
||||
self.data.draw(root);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) -> bool {
|
||||
for id in self.recv.try_iter() {
|
||||
self.updates.insert(id);
|
||||
}
|
||||
if self.full_redraw {
|
||||
self.redraw_all();
|
||||
self.full_redraw = false;
|
||||
true
|
||||
} else if self.resized {
|
||||
self.resized = false;
|
||||
self.redraw_all();
|
||||
true
|
||||
} else if !self.updates.is_empty() {
|
||||
self.redraw_updates();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_updates(&mut self) {
|
||||
self.data.redraw(std::mem::take(&mut self.updates));
|
||||
self.free();
|
||||
}
|
||||
|
||||
/// free any resources that don't have references anymore
|
||||
fn free(&mut self) {
|
||||
self.data.textures.free();
|
||||
}
|
||||
|
||||
pub fn needs_redraw(&self) -> bool {
|
||||
self.full_redraw || !self.updates.is_empty()
|
||||
}
|
||||
|
||||
pub fn active_widgets(&self) -> usize {
|
||||
self.data.active.len()
|
||||
}
|
||||
|
||||
pub fn active(&self) -> &HashMap<WidgetId, WidgetInstance> {
|
||||
&self.data.active
|
||||
}
|
||||
|
||||
pub fn debug_layers(&self) {
|
||||
for ((idx, depth), primitives) in self.data.layers.iter_depth() {
|
||||
let indent = " ".repeat(depth * 2);
|
||||
let len = primitives.instances().len();
|
||||
print!("{indent}{idx}: {len} primitives");
|
||||
if len >= 1 {
|
||||
print!(" ({})", primitives.instances()[0].binding);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
|
||||
let region = self.data.active.get(&id.id())?.region;
|
||||
Some(region.to_px(self.data.output_size))
|
||||
}
|
||||
|
||||
pub fn debug(&self, label: &str) -> impl Iterator<Item = &WidgetInstance> {
|
||||
self.data.active.iter().filter_map(move |(id, inst)| {
|
||||
let widget = id.strong().unwrap();
|
||||
if widget.get_label().as_str() == label {
|
||||
Some(inst)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &PainterData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self) -> &mut PainterData {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Ui {
|
||||
fn default() -> Self {
|
||||
let (send, recv) = channel();
|
||||
let id = ID_TRACKER.next();
|
||||
Self {
|
||||
id,
|
||||
data: PainterData::new(id, send),
|
||||
root: Default::default(),
|
||||
updates: Default::default(),
|
||||
full_redraw: false,
|
||||
recv,
|
||||
resized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ui {
|
||||
fn drop(&mut self) {
|
||||
ID_TRACKER.free(self.id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::layout::{Widget, WidgetHandle, WidgetLike};
|
||||
use std::marker::Unsize;
|
||||
|
||||
pub trait WidgetView {
|
||||
type Widget: Widget + ?Sized + Unsize<dyn Widget> = dyn Widget;
|
||||
fn view(self) -> WidgetHandle<Self::Widget>;
|
||||
}
|
||||
|
||||
pub struct ViewTag;
|
||||
|
||||
impl<WV: WidgetView> WidgetLike<ViewTag> for WV {
|
||||
type Widget = WV::Widget;
|
||||
|
||||
fn add(self, _ui: &mut super::Ui) -> WidgetHandle<Self::Widget> {
|
||||
self.view()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use std::{any::TypeId, sync::mpsc::Sender};
|
||||
|
||||
use crate::{
|
||||
layout::{EVENT_TYPES, EventManager, WIDGETS, WidgetId, WidgetUpdate},
|
||||
util::{Handle, HashSet},
|
||||
};
|
||||
|
||||
pub struct WidgetData<W: ?Sized> {
|
||||
pub id: WidgetId,
|
||||
pub send: Option<Sender<WidgetUpdate>>,
|
||||
pub label: String,
|
||||
pub event_managers: HashSet<TypeId>,
|
||||
pub widget: W,
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetData<W> {
|
||||
pub(crate) fn event_managers(&self) -> impl Iterator<Item = Handle<dyn EventManager>> {
|
||||
self.event_managers
|
||||
.iter()
|
||||
.map(|id| EVENT_TYPES.lock().unwrap().get_mut(id).unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Drop for WidgetData<W> {
|
||||
fn drop(&mut self) {
|
||||
WIDGETS.remove(self.id);
|
||||
for mut m in self.event_managers() {
|
||||
m.remove(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
use std::{marker::Unsize, mem::MaybeUninit, ops::CoerceUnsized};
|
||||
|
||||
use crate::{
|
||||
layout::{EventManager, IdFnTag, IdTag, Ui, WIDGETS, Widget, WidgetData, WidgetLike},
|
||||
util::{
|
||||
Handle, MappedRefGuard, MappedRefGuardMut, Ref, RefGuard, RefGuardMut, RefMap, RefMapMut,
|
||||
RefMut, WeakHandle,
|
||||
},
|
||||
};
|
||||
|
||||
pub(super) type SlotTy = u32;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct WidgetId {
|
||||
pub(super) i: SlotTy,
|
||||
pub(super) genr: SlotTy,
|
||||
}
|
||||
|
||||
/// A strong handle to a widget. Cannot be cloned, only one may exist
|
||||
/// Use .weak() to obtain weak references to this
|
||||
pub struct WidgetHandle<W: ?Sized = dyn Widget>(WidgetId, Handle<WidgetData<W>>);
|
||||
/// A weak handle to a widget. Implements Copy so you can easily pass into closures
|
||||
pub struct WidgetRef<W: ?Sized = dyn Widget>(WidgetId, *const W);
|
||||
|
||||
pub type WidgetUpdate = WidgetId;
|
||||
|
||||
impl<W: Widget> WidgetHandle<W> {
|
||||
pub(super) fn new(id: WidgetId, widget: W) -> Self {
|
||||
let mut label = std::any::type_name::<W>().to_string();
|
||||
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
|
||||
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
|
||||
}
|
||||
Self(
|
||||
id,
|
||||
WidgetData {
|
||||
id,
|
||||
widget,
|
||||
send: None,
|
||||
label,
|
||||
event_managers: Default::default(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetHandle<W> {
|
||||
pub(super) fn weak_inner(&self) -> WeakHandle<WidgetData<W>> {
|
||||
self.1.weak()
|
||||
}
|
||||
|
||||
pub fn any(self) -> WidgetHandle<dyn Widget> {
|
||||
self
|
||||
}
|
||||
|
||||
/// DO NOT CALL OTHER THAN TEMP IN PAINTER
|
||||
pub(crate) fn as_any(&self) -> WidgetHandle<dyn Widget> {
|
||||
WidgetHandle(self.0, self.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetHandle<W> {
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn get(&self) -> RefMap<'_, W> {
|
||||
Ref::map(self.1.get(), |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> RefMapMut<'_, W> {
|
||||
let inner = self.1.get_mut();
|
||||
if let Some(send) = &inner.send {
|
||||
let _ = send.send(self.0);
|
||||
}
|
||||
RefMut::map(inner, |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn data(&self) -> Ref<'_, WidgetData<W>> {
|
||||
self.1.get()
|
||||
}
|
||||
|
||||
pub(crate) fn data_mut(&self) -> RefMut<'_, WidgetData<W>> {
|
||||
self.1.get_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut_quiet(&self) -> RefMapMut<'_, W> {
|
||||
RefMut::map(self.1.get_mut(), |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn get_label(&self) -> RefMap<'_, String> {
|
||||
Ref::map(self.1.get(), |i| &mut i.label)
|
||||
}
|
||||
|
||||
pub fn set_label(&self, label: impl Into<String>) {
|
||||
self.1.get_mut().label = label.into();
|
||||
}
|
||||
|
||||
/// TODO: THIS SHOULD ALWAYS BE 1, remove this probably (weak count might be nice though)
|
||||
pub fn refs(&self) -> usize {
|
||||
self.1.refs()
|
||||
}
|
||||
|
||||
pub fn weak(&self) -> WidgetRef<W> {
|
||||
WidgetRef::new(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub type WRef<'a, W> = MappedRefGuard<'a, WidgetData<W>, W>;
|
||||
pub type WRefMut<'a, W> = MappedRefGuardMut<'a, WidgetData<W>, W>;
|
||||
|
||||
impl<W: Widget> WidgetRef<W> {
|
||||
fn handle(&self) -> Handle<WidgetData<W>> {
|
||||
WIDGETS.get_type(self.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get<'a>(&self) -> WRef<'a, W> {
|
||||
RefGuard::map(self.handle().get_take(), |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn get_mut<'a>(&self) -> WRefMut<'a, W> {
|
||||
let inner = self.handle().get_take_mut();
|
||||
|
||||
if let Some(send) = &inner.send {
|
||||
let _ = send.send(self.0);
|
||||
}
|
||||
RefGuardMut::map(inner, |i| &mut i.widget)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized + Widget> WidgetRef<W> {
|
||||
pub fn data<'a>(&self) -> Option<RefGuard<'a, WidgetData<dyn Widget>>> {
|
||||
Some(WIDGETS.get(self.0)?.get_take())
|
||||
}
|
||||
|
||||
pub(crate) fn data_mut(&self) -> Option<RefGuardMut<'_, WidgetData<dyn Widget>>> {
|
||||
Some(WIDGETS.get(self.0)?.get_take_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetRef<W> {
|
||||
pub fn any(self) -> WidgetRef<dyn Widget> {
|
||||
WidgetRef::new(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetRef<W> {
|
||||
fn new(id: WidgetId) -> Self {
|
||||
Self(id, unsafe { MaybeUninit::zeroed().assume_init() })
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetId {
|
||||
/// THIS SHOULD ONLY BE CALLED FOR TEMP STUFF DURING PAINTING
|
||||
pub(crate) fn strong(self) -> Option<WidgetHandle> {
|
||||
Some(WidgetHandle(self, WIDGETS.get(self)?))
|
||||
}
|
||||
|
||||
pub fn weak(self) -> WidgetRef {
|
||||
WidgetRef::new(self)
|
||||
}
|
||||
|
||||
pub(crate) fn map_event_managers(&self, f: impl Fn(Handle<dyn EventManager>)) {
|
||||
let Some(data) = self.weak().data() else {
|
||||
return;
|
||||
};
|
||||
for m in data.event_managers() {
|
||||
f(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetIdFn<W: ?Sized = dyn Widget>: FnOnce(&mut Ui) -> WidgetHandle<W> {}
|
||||
impl<W: ?Sized, F: FnOnce(&mut Ui) -> WidgetHandle<W>> WidgetIdFn<W> for F {}
|
||||
|
||||
pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetHandle {}
|
||||
impl<F: FnOnce(&mut Ui) -> WidgetHandle> WidgetRet for F {}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static> WidgetLike<IdTag> for WidgetHandle<W> {
|
||||
type Widget = W;
|
||||
fn add(self, _: &mut Ui) -> WidgetHandle<W> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static, F: FnOnce(&mut Ui) -> WidgetHandle<W>>
|
||||
WidgetLike<IdFnTag> for F
|
||||
{
|
||||
type Widget = W;
|
||||
fn add(self, ui: &mut Ui) -> WidgetHandle<W> {
|
||||
self(ui)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IdLike<W> {
|
||||
fn id(&self) -> WidgetId;
|
||||
}
|
||||
|
||||
impl<W> IdLike<W> for WidgetHandle<W> {
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> IdLike<W> for WidgetRef<W> {
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetHandle<U>> for WidgetHandle<T> {}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<T> {}
|
||||
|
||||
impl<W> PartialEq for WidgetHandle<W> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id() == other.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> std::fmt::Debug for WidgetHandle<W> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.id().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Clone for WidgetRef<W> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Copy for WidgetRef<W> {}
|
||||
|
||||
impl<W: ?Sized> PartialEq for WidgetRef<W> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
unsafe impl<W: ?Sized> Send for WidgetRef<W> {}
|
||||
unsafe impl<W: ?Sized> Sync for WidgetRef<W> {}
|
||||
@@ -0,0 +1,79 @@
|
||||
use super::*;
|
||||
use std::marker::Unsize;
|
||||
|
||||
pub trait WidgetLike<Tag> {
|
||||
type Widget: Widget + ?Sized + Unsize<dyn Widget> + 'static;
|
||||
|
||||
fn add(self, ui: &mut Ui) -> WidgetHandle<Self::Widget>;
|
||||
|
||||
fn with_id<W2>(
|
||||
self,
|
||||
f: impl FnOnce(&mut Ui, WidgetHandle<Self::Widget>) -> WidgetHandle<W2>,
|
||||
) -> impl WidgetIdFn<W2>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
move |ui| {
|
||||
let id = self.add(ui);
|
||||
f(ui, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_root(self, ui: &mut Ui)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ui.set_root(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WidgetArr<const LEN: usize> {
|
||||
pub arr: [WidgetHandle; LEN],
|
||||
}
|
||||
|
||||
impl<const LEN: usize> WidgetArr<LEN> {
|
||||
pub fn new(arr: [WidgetHandle; LEN]) -> Self {
|
||||
Self { arr }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetArrLike<const LEN: usize, Tag> {
|
||||
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN>;
|
||||
}
|
||||
|
||||
impl<const LEN: usize> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN> {
|
||||
fn ui(self, _: &mut Ui) -> WidgetArr<LEN> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// I hate this language it's so bad why do I even use it
|
||||
macro_rules! impl_widget_arr {
|
||||
($n:expr;$($W:ident)*) => {
|
||||
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
|
||||
};
|
||||
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
|
||||
impl<$($W: WidgetLike<$Tag>,$Tag,)*> WidgetArrLike<$n, ($($Tag,)*)> for ($($W,)*) {
|
||||
fn ui(self, ui: &mut Ui) -> WidgetArr<$n> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($W,)*) = self;
|
||||
WidgetArr::new(
|
||||
[$($W.add(ui),)*],
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_widget_arr!(1;A);
|
||||
impl_widget_arr!(2;A B);
|
||||
impl_widget_arr!(3;A B C);
|
||||
impl_widget_arr!(4;A B C D);
|
||||
impl_widget_arr!(5;A B C D E);
|
||||
impl_widget_arr!(6;A B C D E F);
|
||||
impl_widget_arr!(7;A B C D E F G);
|
||||
impl_widget_arr!(8;A B C D E F G H);
|
||||
impl_widget_arr!(9;A B C D E F G H I);
|
||||
impl_widget_arr!(10;A B C D E F G H I J);
|
||||
impl_widget_arr!(11;A B C D E F G H I J K);
|
||||
impl_widget_arr!(12;A B C D E F G H I J K L);
|
||||
@@ -0,0 +1,65 @@
|
||||
mod data;
|
||||
mod handle;
|
||||
mod like;
|
||||
mod tag;
|
||||
mod widgets;
|
||||
|
||||
pub use data::*;
|
||||
pub use handle::*;
|
||||
pub use like::*;
|
||||
pub use tag::*;
|
||||
pub use widgets::*;
|
||||
|
||||
use crate::layout::{Len, Painter, SizeCtx, Ui};
|
||||
|
||||
pub trait Widget: 'static + Send + Sync {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that returns a widget given a UI.
|
||||
/// Useful for defining trait functions on widgets that create a parent widget so that the children
|
||||
/// don't need to be IDs yet
|
||||
pub trait WidgetFn<W: Widget + ?Sized>: FnOnce(&mut Ui) -> W {}
|
||||
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetFn<W> for F {}
|
||||
|
||||
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetLike<FnTag> for F {
|
||||
type Widget = W;
|
||||
fn add(self, ui: &mut Ui) -> WidgetHandle<W> {
|
||||
self(ui).add(ui)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> WidgetLike<WidgetTag> for W {
|
||||
type Widget = W;
|
||||
fn add(self, _: &mut Ui) -> WidgetHandle<W> {
|
||||
WIDGETS.insert(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetOption {
|
||||
fn get(self, ui: &mut Ui) -> Option<WidgetHandle>;
|
||||
}
|
||||
|
||||
impl WidgetOption for () {
|
||||
fn get(self, _: &mut Ui) -> Option<WidgetHandle> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce(&mut Ui) -> Option<WidgetHandle>> WidgetOption for F {
|
||||
fn get(self, ui: &mut Ui) -> Option<WidgetHandle> {
|
||||
self(ui)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub struct WidgetTag;
|
||||
pub struct FnTag;
|
||||
pub struct IdTag;
|
||||
pub struct IdFnTag;
|
||||
pub struct ArrTag;
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::{
|
||||
layout::{
|
||||
Widget, WidgetData, WidgetHandle,
|
||||
widget::{SlotTy, WidgetId},
|
||||
},
|
||||
util::{Handle, WeakHandle},
|
||||
};
|
||||
|
||||
pub(crate) static WIDGETS: Widgets = Widgets::new();
|
||||
pub(crate) struct Widgets(Mutex<Inner>);
|
||||
|
||||
pub fn total_widgets() -> usize {
|
||||
WIDGETS.len()
|
||||
}
|
||||
|
||||
pub struct WidgetSlot {
|
||||
genr: SlotTy,
|
||||
data: WeakHandle<WidgetData<dyn Widget + Sync + Send>>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
cur_id: SlotTy,
|
||||
vec: Vec<WidgetSlot>,
|
||||
free: Vec<SlotTy>,
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
pub const fn new() -> Self {
|
||||
Self(Mutex::new(Inner {
|
||||
cur_id: 0,
|
||||
vec: Vec::new(),
|
||||
free: Vec::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn expect(&self) -> MutexGuard<'_, Inner> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_type<W: 'static>(&self, id: WidgetId) -> Option<Handle<WidgetData<W>>> {
|
||||
let slot = &self.expect().vec[id.i as usize];
|
||||
if slot.genr != id.genr {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { slot.data.clone().downcast() }.clone().strong()?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, id: WidgetId) -> Option<Handle<WidgetData<dyn Widget>>> {
|
||||
let slot = &self.expect().vec[id.i as usize];
|
||||
if slot.genr != id.genr {
|
||||
None
|
||||
} else {
|
||||
Some(slot.data.clone().strong()?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert<W: Widget>(&self, widget: W) -> WidgetHandle<W> {
|
||||
let mut s = self.expect();
|
||||
let id = s
|
||||
.free
|
||||
.pop()
|
||||
.map(|i| {
|
||||
assert!(s.vec[i as usize].data.dropped());
|
||||
WidgetId {
|
||||
i,
|
||||
genr: s.vec[i as usize].genr,
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let i = s.cur_id;
|
||||
s.cur_id += 1;
|
||||
WidgetId { i, genr: 0 }
|
||||
});
|
||||
let handle = WidgetHandle::new(id, widget);
|
||||
let slot = WidgetSlot {
|
||||
genr: id.genr,
|
||||
data: handle.weak_inner(),
|
||||
};
|
||||
if id.i == s.vec.len() as SlotTy {
|
||||
s.vec.push(slot);
|
||||
} else {
|
||||
s.vec[id.i as usize] = slot;
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: WidgetId) {
|
||||
let mut s = self.expect();
|
||||
s.vec[id.i as usize].genr += 1;
|
||||
s.free.push(id.i);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let s = self.expect();
|
||||
s.vec.len() - s.free.len()
|
||||
}
|
||||
}
|
||||
@@ -1,493 +0,0 @@
|
||||
//! Opt-in counters and coarse timers for explaining CPU layout cost.
|
||||
//!
|
||||
//! Enable the `layout-diagnostics` feature. With it disabled, none of the
|
||||
//! instrumentation is compiled into Iris. The retained rig in
|
||||
//! `tests/layout_diagnostics.rs` is the ordinary entry point.
|
||||
//!
|
||||
//! Timers are inclusive: `update total` contains `full layout` or
|
||||
//! `incremental layout`, and `text render` contains shaping and glyph
|
||||
//! placement. They locate cost within one instrumented run and must not be
|
||||
//! added together. Use an uninstrumented build under `perf` for final CPU
|
||||
//! totals; counting every primitive and distinct widget deliberately perturbs
|
||||
//! the instrumented run.
|
||||
//!
|
||||
//! Call [`trace_widget`] before a frame to retain the ordered constraint,
|
||||
//! reuse, size, placement, and text events for one suspicious widget. The
|
||||
//! selection is a set and survives [`take`] until cleared.
|
||||
|
||||
use crate::{Axis, LayoutHolds, LayoutLen, PxVec2, Size, UiRegion, UiVec2, WidgetId};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Write,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
/// Declares a counter or timer kind beside the name its report prints. Two
|
||||
/// lists in the same order was one list too many: a variant inserted without
|
||||
/// its label moving with it renames every total after it, and nothing says
|
||||
/// so.
|
||||
macro_rules! labelled {
|
||||
($(#[$meta:meta])* $vis:vis enum $Name:ident { $($variant:ident = $label:literal,)* }) => {
|
||||
$(#[$meta])*
|
||||
#[derive(Clone, Copy)]
|
||||
$vis enum $Name { $($variant,)* }
|
||||
|
||||
impl $Name {
|
||||
const COUNT: usize = [$($label,)*].len();
|
||||
const NAMES: [&'static str; Self::COUNT] = [$($label,)*];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
labelled! {
|
||||
pub(crate) enum Counter {
|
||||
Updates = "updates",
|
||||
DrawRequests = "draw requests",
|
||||
WidgetDraws = "widget draws",
|
||||
RegionNodeDraws = "region-node draws",
|
||||
SizeReads = "draw-result size reads",
|
||||
HintHits = "hint hits",
|
||||
HintMisses = "hint misses",
|
||||
ReuseAttempts = "reuse attempts",
|
||||
ReuseExact = "reuse exact",
|
||||
ReuseMoved = "reuse moved",
|
||||
ReuseDirty = "reuse: dirty",
|
||||
ReuseUndrawn = "reuse: nothing drawn to keep",
|
||||
ReuseWrongParent = "reuse: wrong parent",
|
||||
ReuseRemapped = "reuse remapped",
|
||||
ReuseOutside = "reuse: outside what it holds for",
|
||||
ReuseWrongLayer = "reuse: another layer",
|
||||
ReuseWrongNode = "reuse: region-node choice changed",
|
||||
ReuseWrongMask = "reuse: a different inherited mask",
|
||||
QueuePops = "redraw queue pops",
|
||||
DepthReads = "depth reads",
|
||||
LocalRedraws = "local redraws",
|
||||
SizeChanges = "size changes",
|
||||
ReaderEdges = "reader edges",
|
||||
PrimitiveWrites = "primitive writes",
|
||||
TextRenders = "text renders",
|
||||
TextShapeHits = "text shape hits",
|
||||
TextShapes = "text shapes",
|
||||
TextBreaks = "text line breaks",
|
||||
GlyphPlacements = "glyph placements",
|
||||
OutsidePinnedLen = "reuse outside: the length it was pinned to",
|
||||
OutsideWindow = "reuse outside: this window",
|
||||
OutsideRelBase = "reuse outside: a rel base",
|
||||
OutsideRegion = "reuse outside: a region length",
|
||||
}
|
||||
}
|
||||
|
||||
labelled! {
|
||||
pub(crate) enum TimerKind {
|
||||
Update = "update total",
|
||||
FullLayout = "full layout",
|
||||
IncrementalLayout = "incremental layout",
|
||||
TextRender = "text render",
|
||||
TextShape = "text shape",
|
||||
TextBreak = "text line break",
|
||||
GlyphPlacement = "glyph placement",
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Report {
|
||||
counters: [u64; Counter::COUNT],
|
||||
nanos: [u64; TimerKind::COUNT],
|
||||
distinct_widgets: usize,
|
||||
distinct_text_widgets: usize,
|
||||
hot_widgets: Vec<Callsite>,
|
||||
hot_text: Vec<Callsite>,
|
||||
traces: Vec<TraceEvent>,
|
||||
}
|
||||
|
||||
impl Default for Report {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
counters: [0; Counter::COUNT],
|
||||
nanos: [0; TimerKind::COUNT],
|
||||
distinct_widgets: 0,
|
||||
distinct_text_widgets: 0,
|
||||
hot_widgets: Vec::new(),
|
||||
hot_text: Vec::new(),
|
||||
traces: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Report {
|
||||
pub fn counters(&self) -> impl Iterator<Item = (&'static str, u64)> + '_ {
|
||||
Counter::NAMES.into_iter().zip(self.counters)
|
||||
}
|
||||
|
||||
/// Inclusive elapsed time accumulated for each targeted operation.
|
||||
pub fn timings_ns(&self) -> impl Iterator<Item = (&'static str, u64)> + '_ {
|
||||
TimerKind::NAMES.into_iter().zip(self.nanos)
|
||||
}
|
||||
|
||||
pub fn distinct_widgets(&self) -> usize {
|
||||
self.distinct_widgets
|
||||
}
|
||||
|
||||
pub fn distinct_text_widgets(&self) -> usize {
|
||||
self.distinct_text_widgets
|
||||
}
|
||||
|
||||
pub fn hot_widgets(&self) -> &[Callsite] {
|
||||
&self.hot_widgets
|
||||
}
|
||||
|
||||
pub fn hot_text(&self) -> &[Callsite] {
|
||||
&self.hot_text
|
||||
}
|
||||
|
||||
/// Ordered layout events for widgets selected with [`trace_widget`].
|
||||
pub fn traces(&self) -> &[TraceEvent] {
|
||||
&self.traces
|
||||
}
|
||||
|
||||
/// Formats nonzero totals divided by `frames`.
|
||||
pub fn per_frame(&self, frames: usize) -> String {
|
||||
let divisor = frames.max(1) as f64;
|
||||
let mut out = String::new();
|
||||
for (name, value) in self.counters() {
|
||||
if value != 0 {
|
||||
let _ = writeln!(out, " {name:<27} {:>12.2}", value as f64 / divisor);
|
||||
}
|
||||
}
|
||||
if self.distinct_widgets != 0 {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" {:<27} {:>12}",
|
||||
"distinct widgets", self.distinct_widgets
|
||||
);
|
||||
}
|
||||
if self.distinct_text_widgets != 0 {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" {:<27} {:>12}",
|
||||
"distinct text widgets", self.distinct_text_widgets
|
||||
);
|
||||
}
|
||||
for (name, nanos) in self.timings_ns() {
|
||||
if nanos != 0 {
|
||||
let ms = nanos as f64 / divisor / 1_000_000.0;
|
||||
let _ = writeln!(out, " {name:<27} {ms:>12.3} ms");
|
||||
}
|
||||
}
|
||||
if !self.hot_widgets.is_empty() {
|
||||
let _ = writeln!(out, " hottest widget draws:");
|
||||
for callsite in &self.hot_widgets {
|
||||
let calls = callsite.calls as f64 / divisor;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" {calls:>9.2} {:?} {}",
|
||||
callsite.id, callsite.label
|
||||
);
|
||||
}
|
||||
}
|
||||
if !self.hot_text.is_empty() {
|
||||
let _ = writeln!(out, " hottest text renders:");
|
||||
for callsite in &self.hot_text {
|
||||
let calls = callsite.calls as f64 / divisor;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" {calls:>9.2} {:>3} widths {:?} {}",
|
||||
callsite.distinct_widths, callsite.id, callsite.label
|
||||
);
|
||||
}
|
||||
}
|
||||
if !self.traces.is_empty() {
|
||||
let _ = writeln!(out, " targeted layout trace:");
|
||||
for event in &self.traces {
|
||||
let _ = writeln!(out, " {event:?}");
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Callsite {
|
||||
pub id: WidgetId,
|
||||
pub label: String,
|
||||
pub calls: u64,
|
||||
pub distinct_widths: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ReuseOutcome {
|
||||
Exact,
|
||||
Moved,
|
||||
Dirty,
|
||||
WrongParent,
|
||||
WrongLayer,
|
||||
WrongMask,
|
||||
WrongNode,
|
||||
Remapped,
|
||||
Outside,
|
||||
Undrawn,
|
||||
}
|
||||
|
||||
/// One targeted layout event. Events are retained in execution order, making
|
||||
/// repeated constraint paths visible without logging every widget globally.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TraceEvent {
|
||||
DrawRequest {
|
||||
id: WidgetId,
|
||||
parent: Option<WidgetId>,
|
||||
region: UiRegion,
|
||||
region_px: PxVec2,
|
||||
region_node: bool,
|
||||
},
|
||||
Reuse {
|
||||
id: WidgetId,
|
||||
outcome: ReuseOutcome,
|
||||
},
|
||||
SizeReported {
|
||||
id: WidgetId,
|
||||
size: Size,
|
||||
},
|
||||
RegionNode {
|
||||
id: WidgetId,
|
||||
parent: WidgetId,
|
||||
region: UiRegion,
|
||||
},
|
||||
SizeRead {
|
||||
id: WidgetId,
|
||||
reader: WidgetId,
|
||||
size: Size,
|
||||
},
|
||||
HintRead {
|
||||
id: WidgetId,
|
||||
reader: WidgetId,
|
||||
axis: Axis,
|
||||
hint: Option<LayoutLen>,
|
||||
},
|
||||
TextRendered {
|
||||
id: WidgetId,
|
||||
width: Option<f32>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Calls {
|
||||
label: String,
|
||||
count: u64,
|
||||
widths: HashSet<Option<u32>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Current {
|
||||
report: Report,
|
||||
widgets: HashMap<WidgetId, Calls>,
|
||||
text_widgets: HashMap<WidgetId, Calls>,
|
||||
traced: HashSet<WidgetId>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static CURRENT: RefCell<Current> = RefCell::new(Current::default());
|
||||
}
|
||||
|
||||
pub(crate) fn bump(counter: Counter) {
|
||||
CURRENT.with_borrow_mut(|current| current.report.counters[counter as usize] += 1);
|
||||
}
|
||||
|
||||
pub(crate) fn draw_widget(id: WidgetId, label: &str) {
|
||||
CURRENT.with_borrow_mut(|current| {
|
||||
let calls = current.widgets.entry(id).or_default();
|
||||
if calls.label.is_empty() {
|
||||
calls.label = label.to_owned();
|
||||
}
|
||||
calls.count += 1;
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a widget to the targeted trace set. Selection survives [`take`]
|
||||
/// until explicitly removed or cleared.
|
||||
pub fn trace_widget(id: impl Into<WidgetId>) {
|
||||
CURRENT.with_borrow_mut(|current| {
|
||||
current.traced.insert(id.into());
|
||||
});
|
||||
}
|
||||
|
||||
pub fn untrace_widget(id: impl Into<WidgetId>) {
|
||||
CURRENT.with_borrow_mut(|current| {
|
||||
current.traced.remove(&id.into());
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clear_traced_widgets() {
|
||||
CURRENT.with_borrow_mut(|current| current.traced.clear());
|
||||
}
|
||||
|
||||
fn trace(id: WidgetId, event: TraceEvent) {
|
||||
CURRENT.with_borrow_mut(|current| {
|
||||
if current.traced.contains(&id) {
|
||||
current.report.traces.push(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn draw_request(
|
||||
id: WidgetId,
|
||||
parent: Option<WidgetId>,
|
||||
region: UiRegion,
|
||||
region_px: PxVec2,
|
||||
region_node: bool,
|
||||
) {
|
||||
trace(
|
||||
id,
|
||||
TraceEvent::DrawRequest {
|
||||
id,
|
||||
parent,
|
||||
region,
|
||||
region_px,
|
||||
region_node,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn reuse(id: WidgetId, outcome: ReuseOutcome) {
|
||||
trace(id, TraceEvent::Reuse { id, outcome });
|
||||
}
|
||||
|
||||
/// A drawing that cannot be reused because the box on offer is outside what
|
||||
/// it holds for, and which of the four contracts said so. They overlap: a
|
||||
/// drawing can be outside two of them at once, and counting each is what
|
||||
/// says where a rel base redrawing more than it should is coming from.
|
||||
pub(crate) fn outside(
|
||||
id: WidgetId,
|
||||
holds: LayoutHolds,
|
||||
region: UiRegion,
|
||||
rel_base: UiVec2,
|
||||
window: PxVec2,
|
||||
) {
|
||||
for axis in Axis::BOTH {
|
||||
let holds = holds[axis];
|
||||
let len = region[axis].len();
|
||||
let window = window[axis];
|
||||
if holds.region_len.is_some_and(|pinned| pinned != len) {
|
||||
bump(Counter::OutsidePinnedLen);
|
||||
}
|
||||
if !holds.window.contains(window) {
|
||||
bump(Counter::OutsideWindow);
|
||||
}
|
||||
if holds
|
||||
.rel_base
|
||||
.is_some_and(|pinned| pinned != rel_base[axis])
|
||||
{
|
||||
bump(Counter::OutsideRelBase);
|
||||
}
|
||||
if !holds.region.contains(len.to_px(window)) {
|
||||
bump(Counter::OutsideRegion);
|
||||
}
|
||||
}
|
||||
bump(Counter::ReuseOutside);
|
||||
reuse(id, ReuseOutcome::Outside);
|
||||
}
|
||||
|
||||
pub(crate) fn size_reported(id: WidgetId, size: Size) {
|
||||
trace(id, TraceEvent::SizeReported { id, size });
|
||||
}
|
||||
|
||||
pub(crate) fn region_node(id: WidgetId, parent: WidgetId, region: UiRegion) {
|
||||
trace(id, TraceEvent::RegionNode { id, parent, region });
|
||||
}
|
||||
|
||||
pub(crate) fn size_read(id: WidgetId, reader: WidgetId, size: Size) {
|
||||
trace(id, TraceEvent::SizeRead { id, reader, size });
|
||||
}
|
||||
|
||||
pub(crate) fn hint_read(id: WidgetId, reader: WidgetId, axis: Axis, hint: Option<LayoutLen>) {
|
||||
trace(
|
||||
id,
|
||||
TraceEvent::HintRead {
|
||||
id,
|
||||
reader,
|
||||
axis,
|
||||
hint,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn render_text(id: WidgetId, label: &str, width: Option<f32>) {
|
||||
CURRENT.with_borrow_mut(|current| {
|
||||
let calls = current.text_widgets.entry(id).or_default();
|
||||
if calls.label.is_empty() {
|
||||
calls.label = label.to_owned();
|
||||
}
|
||||
calls.count += 1;
|
||||
calls.widths.insert(width.map(f32::to_bits));
|
||||
if current.traced.contains(&id) {
|
||||
current
|
||||
.report
|
||||
.traces
|
||||
.push(TraceEvent::TextRendered { id, width });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) struct Timer {
|
||||
kind: TimerKind,
|
||||
start: Instant,
|
||||
}
|
||||
|
||||
pub(crate) fn timer(kind: TimerKind) -> Timer {
|
||||
Timer {
|
||||
kind,
|
||||
start: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Timer {
|
||||
fn drop(&mut self) {
|
||||
let nanos = self.start.elapsed().as_nanos().min(u64::MAX as u128) as u64;
|
||||
CURRENT.with_borrow_mut(|current| current.report.nanos[self.kind as usize] += nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes all diagnostics accumulated on this thread and resets them.
|
||||
pub fn take() -> Report {
|
||||
CURRENT.with_borrow_mut(|current| {
|
||||
current.report.distinct_widgets = current.widgets.len();
|
||||
current.report.distinct_text_widgets = current.text_widgets.len();
|
||||
current.report.hot_widgets = hottest(¤t.widgets);
|
||||
current.report.hot_text = hottest(¤t.text_widgets);
|
||||
let report = std::mem::take(&mut current.report);
|
||||
current.widgets.clear();
|
||||
current.text_widgets.clear();
|
||||
report
|
||||
})
|
||||
}
|
||||
|
||||
fn hottest(calls: &HashMap<WidgetId, Calls>) -> Vec<Callsite> {
|
||||
let mut calls: Vec<_> = calls
|
||||
.iter()
|
||||
.map(|(&id, calls)| Callsite {
|
||||
id,
|
||||
label: calls.label.clone(),
|
||||
calls: calls.count,
|
||||
distinct_widths: calls.widths.len(),
|
||||
})
|
||||
.collect();
|
||||
calls.sort_by(|a, b| b.calls.cmp(&a.calls).then_with(|| a.label.cmp(&b.label)));
|
||||
calls.truncate(8);
|
||||
calls
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn taking_a_report_resets_its_counters() {
|
||||
let _ = take();
|
||||
bump(Counter::Updates);
|
||||
bump(Counter::Updates);
|
||||
|
||||
let report = take();
|
||||
assert_eq!(report.counters().next(), Some(("updates", 2)));
|
||||
assert!(take().counters().all(|(_, count)| count == 0));
|
||||
}
|
||||
}
|
||||
+8
-26
@@ -2,38 +2,20 @@
|
||||
#![feature(const_ops)]
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(const_convert)]
|
||||
#![feature(map_try_insert)]
|
||||
#![feature(unboxed_closures)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(const_cmp)]
|
||||
#![feature(const_destruct)]
|
||||
#![feature(portable_simd)]
|
||||
#![feature(associated_type_defaults)]
|
||||
#![feature(unsize)]
|
||||
#![feature(coerce_unsized)]
|
||||
#![feature(option_into_flat_iter)]
|
||||
#![feature(const_index)]
|
||||
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
pub mod layout_diagnostics;
|
||||
|
||||
mod attr;
|
||||
mod event;
|
||||
mod fixed;
|
||||
mod num;
|
||||
mod orientation;
|
||||
mod primitive;
|
||||
mod render;
|
||||
mod ui;
|
||||
mod widget;
|
||||
#![feature(mapped_lock_guards)]
|
||||
#![feature(ptr_metadata)]
|
||||
|
||||
pub mod layout;
|
||||
pub mod render;
|
||||
pub mod util;
|
||||
|
||||
pub use attr::*;
|
||||
pub use event::*;
|
||||
pub use fixed::*;
|
||||
pub use num::*;
|
||||
pub use orientation::*;
|
||||
pub use primitive::*;
|
||||
pub use render::*;
|
||||
pub use ui::*;
|
||||
pub use widget::*;
|
||||
|
||||
pub type UiColor = primitive::Color<u8>;
|
||||
pub use image;
|
||||
@@ -1,255 +0,0 @@
|
||||
use super::*;
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Px, PxVec2, Rel, UiNum, Weight, util::impl_op};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Size {
|
||||
pub x: LayoutLen,
|
||||
pub y: LayoutLen,
|
||||
}
|
||||
|
||||
/// What a widget asks for along one axis: a [`Len`] -- pixels and a fraction
|
||||
/// of the box it is given -- plus a share of whatever is left over once
|
||||
/// everything fixed has been taken. The parts add up rather than choosing
|
||||
/// between one another.
|
||||
///
|
||||
/// Only a container dividing its room can answer a share, so a length nobody
|
||||
/// divides is a `Len`: a position, a padding, a cap, anything already
|
||||
/// resolved.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LayoutLen {
|
||||
pub px: Px,
|
||||
pub rel: Rel,
|
||||
pub leftover: Weight,
|
||||
}
|
||||
|
||||
impl<N: UiNum> From<N> for LayoutLen {
|
||||
fn from(value: N) -> Self {
|
||||
LayoutLen::px(value.to_f32())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
|
||||
fn from((x, y): (Nx, Ny)) -> Self {
|
||||
Self {
|
||||
x: x.into(),
|
||||
y: y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A length with no share in it is a length a container does not have to
|
||||
/// divide, which is one it can always give.
|
||||
impl From<Len> for LayoutLen {
|
||||
fn from(len: Len) -> Self {
|
||||
Self {
|
||||
px: len.px,
|
||||
rel: len.rel,
|
||||
leftover: Weight::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayoutLen> for Size {
|
||||
fn from(value: LayoutLen) -> Self {
|
||||
Self { x: value, y: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub const ZERO: Self = Self {
|
||||
x: LayoutLen::ZERO,
|
||||
y: LayoutLen::ZERO,
|
||||
};
|
||||
|
||||
pub const LEFTOVER: Self = Self {
|
||||
x: LayoutLen::LEFTOVER,
|
||||
y: LayoutLen::LEFTOVER,
|
||||
};
|
||||
|
||||
/// From something measured outside layout -- a texture, a shaped line --
|
||||
/// which is where a size in floats comes from.
|
||||
pub fn px(v: Vec2) -> Self {
|
||||
Self::from_px(PxVec2::from_f32(v))
|
||||
}
|
||||
|
||||
pub const fn from_px(v: PxVec2) -> Self {
|
||||
Self {
|
||||
x: LayoutLen {
|
||||
px: v.x,
|
||||
..LayoutLen::ZERO
|
||||
},
|
||||
y: LayoutLen {
|
||||
px: v.y,
|
||||
..LayoutLen::ZERO
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rel(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: LayoutLen::rel(v.x),
|
||||
y: LayoutLen::rel(v.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leftover(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: LayoutLen::leftover(v.x),
|
||||
y: LayoutLen::leftover(v.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_uivec2(self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.apply_leftover(),
|
||||
y: self.y.apply_leftover(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_axis(axis: Axis, aligned: LayoutLen, ortho: LayoutLen) -> Self {
|
||||
match axis {
|
||||
Axis::X => Self {
|
||||
x: aligned,
|
||||
y: ortho,
|
||||
},
|
||||
Axis::Y => Self {
|
||||
x: ortho,
|
||||
y: aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutLen {
|
||||
pub const ZERO: Self = Self {
|
||||
px: Px::ZERO,
|
||||
rel: Rel::ZERO,
|
||||
leftover: Weight::ZERO,
|
||||
};
|
||||
|
||||
pub const LEFTOVER: Self = Self {
|
||||
px: Px::ZERO,
|
||||
rel: Rel::ZERO,
|
||||
leftover: Weight::ONE,
|
||||
};
|
||||
|
||||
/// The whole of what is left over counts as the whole box, which is what
|
||||
/// a length means to something that is not dividing a box between
|
||||
/// siblings -- a scroll asking how long its content is.
|
||||
pub fn apply_leftover(&self) -> Len {
|
||||
let share = match self.leftover > Weight::ZERO {
|
||||
true => Rel::ONE,
|
||||
false => Rel::ZERO,
|
||||
};
|
||||
Len::from_parts(self.rel.add(share), self.px)
|
||||
}
|
||||
|
||||
/// Only pixels: the same number of them whatever box it lands in, and
|
||||
/// whatever anyone else in the row asks for. A length that is any part
|
||||
/// of a box or of what is left over is not one.
|
||||
pub fn is_px(&self) -> bool {
|
||||
self.rel == Rel::ZERO && self.leftover == Weight::ZERO
|
||||
}
|
||||
|
||||
/// Nothing but a claim on what is left over, so there is no length here
|
||||
/// at all where nothing is.
|
||||
pub fn is_only_leftover(&self) -> bool {
|
||||
self.leftover > Weight::ZERO && self.without_leftover() == Len::ZERO
|
||||
}
|
||||
|
||||
/// This as a length of a box, where it is one. `leftover` is not: a
|
||||
/// share of what is left over is a length only to whoever divides one,
|
||||
/// so it passes up in the reported size instead and is resolved there.
|
||||
pub fn declared(&self) -> Option<Len> {
|
||||
(self.leftover == Weight::ZERO).then(|| self.without_leftover())
|
||||
}
|
||||
|
||||
/// What this takes whatever is left over: the reading of a length for
|
||||
/// anyone not dividing a box between siblings, where a share is a claim
|
||||
/// on someone else's room rather than a length of its own.
|
||||
/// [`Self::apply_leftover`] is the opposite reading of the same value.
|
||||
pub const fn without_leftover(self) -> Len {
|
||||
Len::from_parts(self.rel, self.px)
|
||||
}
|
||||
|
||||
/// This length, given as a part of a box `len` long, as a part of the
|
||||
/// box `len` is itself a part of. The share is untouched: it is a claim
|
||||
/// on whoever divides the room, not a fraction of anything.
|
||||
pub const fn within_len(self, len: Len) -> Self {
|
||||
let part = self.without_leftover().within_len(len);
|
||||
Self {
|
||||
px: part.px,
|
||||
rel: part.rel,
|
||||
leftover: self.leftover,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn px(px: impl UiNum) -> Self {
|
||||
Self {
|
||||
px: Px::from_num(px),
|
||||
..Self::ZERO
|
||||
}
|
||||
}
|
||||
pub fn rel(rel: impl UiNum) -> Self {
|
||||
Self {
|
||||
rel: Rel::from_num(rel),
|
||||
..Self::ZERO
|
||||
}
|
||||
}
|
||||
pub fn leftover(ratio: impl UiNum) -> Self {
|
||||
Self {
|
||||
leftover: Weight::from_num(ratio),
|
||||
..Self::ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod len_fns {
|
||||
use super::*;
|
||||
|
||||
pub fn px(px: impl UiNum) -> LayoutLen {
|
||||
LayoutLen::px(px)
|
||||
}
|
||||
pub fn rel(rel: impl UiNum) -> LayoutLen {
|
||||
LayoutLen::rel(rel)
|
||||
}
|
||||
pub fn leftover(ratio: impl UiNum) -> LayoutLen {
|
||||
LayoutLen::leftover(ratio)
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(same LayoutLen Add add; px rel leftover);
|
||||
impl_op!(same LayoutLen Sub sub; px rel leftover);
|
||||
|
||||
impl_op!(same Size Add add; x y);
|
||||
impl_op!(same Size Sub sub; x y);
|
||||
|
||||
impl Default for LayoutLen {
|
||||
fn default() -> Self {
|
||||
Self::leftover(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Size {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LayoutLen {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.px != Px::ZERO {
|
||||
write!(f, "{} px;", self.px)?;
|
||||
}
|
||||
if self.rel != Rel::ZERO {
|
||||
write!(f, "{} rel;", self.rel)?;
|
||||
}
|
||||
if self.leftover != Weight::ZERO {
|
||||
write!(f, "{} leftover;", self.leftover)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(Size => LayoutLen);
|
||||
@@ -1,437 +0,0 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use std::{fmt::Display, marker::Destruct};
|
||||
|
||||
use super::*;
|
||||
use crate::{Px, PxVec2, Rel, UiNum, util::impl_op};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct UiVec2 {
|
||||
pub x: Len,
|
||||
pub y: Len,
|
||||
}
|
||||
|
||||
impl UiVec2 {
|
||||
pub const ZERO: Self = Self {
|
||||
x: Len::ZERO,
|
||||
y: Len::ZERO,
|
||||
};
|
||||
|
||||
pub const fn new(x: Len, y: Len) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn px(px: impl const Into<Vec2>) -> Self {
|
||||
let px = px.into();
|
||||
Self {
|
||||
x: Len::px(px.x),
|
||||
y: Len::px(px.y),
|
||||
}
|
||||
}
|
||||
|
||||
/// From lengths already on the grid, with no fraction of a box.
|
||||
pub const fn from_px(px: PxVec2) -> Self {
|
||||
Self {
|
||||
x: Len::from_parts(Rel::ZERO, px.x),
|
||||
y: Len::from_parts(Rel::ZERO, px.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
|
||||
let rel = rel.into();
|
||||
Self {
|
||||
x: Len::rel(rel.x),
|
||||
y: Len::rel(rel.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn shift(&mut self, offset: impl const Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
*self += offset;
|
||||
}
|
||||
|
||||
pub const fn offset(mut self, offset: impl const Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn within(&self, region: &UiRegion) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.within(®ion.x),
|
||||
y: self.y.within(®ion.y),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolved against a box of `size`, which is where a fraction stops
|
||||
/// being one and becomes a place.
|
||||
pub fn to_px(&self, size: PxVec2) -> PxVec2 {
|
||||
PxVec2::new(self.x.to_px(size.x), self.y.to_px(size.y))
|
||||
}
|
||||
|
||||
pub const FULL_SIZE: Self = Self::rel(Vec2::ONE);
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
|
||||
match axis {
|
||||
Axis::X => Self {
|
||||
x: aligned,
|
||||
y: ortho,
|
||||
},
|
||||
Axis::Y => Self {
|
||||
x: ortho,
|
||||
y: aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_px(&self) -> Vec2 {
|
||||
(self.x.px.to_f32(), self.y.px.to_f32()).into()
|
||||
}
|
||||
|
||||
pub fn get_rel(&self) -> Vec2 {
|
||||
(self.x.rel.to_f32(), self.y.rel.to_f32()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiVec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "rel{};px{}", self.get_rel(), self.get_px())
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(same UiVec2 Add add; x y);
|
||||
impl_op!(same UiVec2 Sub sub; x y);
|
||||
|
||||
const impl From<Vec2> for UiVec2 {
|
||||
fn from(px: Vec2) -> Self {
|
||||
Self::px(px)
|
||||
}
|
||||
}
|
||||
|
||||
const impl<T: const UiNum, U: const UiNum> From<(T, U)> for UiVec2
|
||||
where
|
||||
(T, U): const Destruct,
|
||||
{
|
||||
fn from(px: (T, U)) -> Self {
|
||||
Self::px(px)
|
||||
}
|
||||
}
|
||||
|
||||
/// A length along one axis: a fraction of the box it is measured in plus an
|
||||
/// offset, `rel * box + px`. A position is the same number -- the length from
|
||||
/// the start of the box to the point -- which is why a [`UiSpan`] is two of
|
||||
/// these. Both parts are fixed point, so composing one through a chain of
|
||||
/// boxes rounds only where it multiplies, and lands on the same number as any
|
||||
/// other route to the same place.
|
||||
///
|
||||
/// It carries no claim on what a container has left over. That is
|
||||
/// [`crate::LayoutLen`], which is this plus a weight, and which means nothing
|
||||
/// to anyone but whoever divides the room.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, Default, bytemuck::Zeroable)]
|
||||
pub struct Len {
|
||||
pub rel: Rel,
|
||||
pub px: Px,
|
||||
}
|
||||
|
||||
impl_op!(same Len Add add; rel px);
|
||||
impl_op!(same Len Sub sub; rel px);
|
||||
|
||||
impl Len {
|
||||
pub const ZERO: Self = Self {
|
||||
rel: Rel::ZERO,
|
||||
px: Px::ZERO,
|
||||
};
|
||||
pub const FULL: Self = Self {
|
||||
rel: Rel::ONE,
|
||||
px: Px::ZERO,
|
||||
};
|
||||
|
||||
pub const fn new(rel: f32, px: f32) -> Self {
|
||||
Self::from_parts(Rel::from_f32(rel), Px::from_f32(px))
|
||||
}
|
||||
|
||||
/// From parts already on the grid, rather than numbers to be put on it.
|
||||
pub const fn from_parts(rel: Rel, px: Px) -> Self {
|
||||
Self { rel, px }
|
||||
}
|
||||
|
||||
pub const fn rel(rel: f32) -> Self {
|
||||
Self::from_parts(Rel::from_f32(rel), Px::ZERO)
|
||||
}
|
||||
|
||||
pub const fn px(px: f32) -> Self {
|
||||
Self::from_parts(Rel::ZERO, Px::from_f32(px))
|
||||
}
|
||||
|
||||
pub const fn max(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.max(other.rel),
|
||||
px: self.px.max(other.px),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn min(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.min(other.rel),
|
||||
px: self.px.min(other.px),
|
||||
}
|
||||
}
|
||||
|
||||
/// Both parts by the same fraction, which is what a part of a length
|
||||
/// means when the length is part pixels and part a fraction of a box.
|
||||
pub const fn scale(&self, by: Rel) -> Self {
|
||||
Self {
|
||||
rel: self.rel.mul(by),
|
||||
px: self.px.mul(by),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn offset(mut self, amt: Px) -> Self {
|
||||
self.px = self.px.add(amt);
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn within(&self, span: &UiSpan) -> Self {
|
||||
Self {
|
||||
rel: self.rel.lerp(span.start.rel, span.end.rel),
|
||||
px: self.px.add(self.rel.lerp(span.start.px, span.end.px)),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn within_len(&self, len: Len) -> Self {
|
||||
self.within(&UiSpan {
|
||||
start: Len::ZERO,
|
||||
end: len,
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self) {
|
||||
self.rel = Rel::ONE.sub(self.rel);
|
||||
self.px = self.px.neg();
|
||||
}
|
||||
|
||||
pub const fn to(&self, end: Self) -> UiSpan {
|
||||
UiSpan { start: *self, end }
|
||||
}
|
||||
|
||||
/// Resolved against a box of `len`, which is the only place a fraction
|
||||
/// becomes a number of pixels.
|
||||
pub const fn to_px(&self, len: Px) -> Px {
|
||||
self.px.add(len.mul(self.rel))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UiSpan {
|
||||
pub start: Len,
|
||||
pub end: Len,
|
||||
}
|
||||
|
||||
impl UiSpan {
|
||||
pub const FULL: Self = Self {
|
||||
start: Len::ZERO,
|
||||
end: Len::FULL,
|
||||
};
|
||||
|
||||
pub const fn rel(rel: f32) -> Self {
|
||||
Self {
|
||||
start: Len::rel(rel),
|
||||
end: Len::rel(rel),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new(start: Len, end: Len) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self) {
|
||||
self.start.flip();
|
||||
self.end.flip();
|
||||
std::mem::swap(&mut self.start.rel, &mut self.end.rel);
|
||||
std::mem::swap(&mut self.start.px, &mut self.end.px);
|
||||
}
|
||||
|
||||
pub const fn shift(&mut self, offset: Len) {
|
||||
self.start += offset;
|
||||
self.end += offset;
|
||||
}
|
||||
|
||||
/// Composing a box through the one it sits in, and the hottest line in
|
||||
/// layout. It used to skip the multiplies where a span was the whole of
|
||||
/// its parent or the parent the whole of its own; both come out of the
|
||||
/// multiply unchanged anyway, and the body those comparisons cost was
|
||||
/// what kept the inliner from taking this at all.
|
||||
pub const fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
start: self.start.within(parent),
|
||||
end: self.end.within(parent),
|
||||
}
|
||||
}
|
||||
|
||||
/// A box `len` long inside this one, on the side `align` says. Both must
|
||||
/// be lengths of the same rel base: it subtracts one from the other
|
||||
/// rather than composing it in, which is what keeps a fraction the same
|
||||
/// fraction however long this box turns out to be.
|
||||
pub const fn place(self, len: Len, align: AxisAlign) -> Self {
|
||||
let start = self.start + (self.len() - len).scale(align.rel());
|
||||
Self::new(start, start + len)
|
||||
}
|
||||
|
||||
pub const fn len(&self) -> Len {
|
||||
self.end - self.start
|
||||
}
|
||||
|
||||
/// Both ends by the same amount, which is what moving a box without
|
||||
/// changing its length does to every part of it.
|
||||
pub const fn translated(self, by: Len) -> Self {
|
||||
Self {
|
||||
start: self.start + by,
|
||||
end: self.end + by,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UiRegion {
|
||||
pub x: UiSpan,
|
||||
pub y: UiSpan,
|
||||
}
|
||||
|
||||
impl UiRegion {
|
||||
/// Every part of the box by the same amount on each axis. Done to the
|
||||
/// whole region rather than an end at a time, because that is what it is
|
||||
/// -- and because four adds in a row are four adds, where four asked for
|
||||
/// separately are four sequences.
|
||||
pub const fn translated(self, x: Len, y: Len) -> Self {
|
||||
Self {
|
||||
x: self.x.translated(x),
|
||||
y: self.y.translated(y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const FULL: Self = Self {
|
||||
x: UiSpan::FULL,
|
||||
y: UiSpan::FULL,
|
||||
};
|
||||
|
||||
pub const fn new(x: UiSpan, y: UiSpan) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn rel(rel: Vec2) -> Self {
|
||||
Self {
|
||||
x: UiSpan::rel(rel.x),
|
||||
y: UiSpan::rel(rel.y),
|
||||
}
|
||||
}
|
||||
pub const fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
x: self.x.within(&parent.x),
|
||||
y: self.y.within(&parent.y),
|
||||
}
|
||||
}
|
||||
pub const fn flip(&mut self, axis: Axis) {
|
||||
match axis {
|
||||
Axis::X => self.x.flip(),
|
||||
Axis::Y => self.y.flip(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shift(&mut self, offset: impl Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
self.x.shift(offset.x);
|
||||
self.y.shift(offset.y);
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: impl Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_px(&self, size: PxVec2) -> PixelRegion {
|
||||
PixelRegion {
|
||||
top_left: self.top_left().to_px(size),
|
||||
bot_right: self.bot_right().to_px(size),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn center(&self) -> UiVec2 {
|
||||
Align::CENTER.pos().within(self)
|
||||
}
|
||||
|
||||
pub const fn size(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.len(),
|
||||
y: self.y.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn top_left(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.start,
|
||||
y: self.y.start,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn bot_right(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.end,
|
||||
y: self.y.end,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: UiSpan, ortho: UiSpan) -> Self {
|
||||
Self {
|
||||
x: match axis {
|
||||
Axis::X => aligned,
|
||||
Axis::Y => ortho,
|
||||
},
|
||||
y: match axis {
|
||||
Axis::X => ortho,
|
||||
Axis::Y => aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} -> {} (size: {})",
|
||||
self.top_left(),
|
||||
self.bot_right(),
|
||||
self.size()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PixelRegion {
|
||||
pub top_left: PxVec2,
|
||||
pub bot_right: PxVec2,
|
||||
}
|
||||
|
||||
impl PixelRegion {
|
||||
pub fn contains(&self, pos: PxVec2) -> bool {
|
||||
pos.x >= self.top_left.x
|
||||
&& pos.x <= self.bot_right.x
|
||||
&& pos.y >= self.top_left.y
|
||||
&& pos.y <= self.bot_right.y
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PxVec2 {
|
||||
self.bot_right - self.top_left
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PixelRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} -> {}", self.top_left, self.bot_right)
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(UiVec2 => Len);
|
||||
impl_axis_index!(UiRegion => UiSpan);
|
||||
@@ -1,440 +0,0 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, TimerKind};
|
||||
use crate::{
|
||||
Align, GlyphAtlas, GlyphEntry, GlyphKey, PlacedGlyph, Px, PxVec2, RegionAlign, UiColor,
|
||||
util::Vec2,
|
||||
};
|
||||
use parley::{
|
||||
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
||||
LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
|
||||
};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
};
|
||||
use swash::{
|
||||
FontRef,
|
||||
scale::{Render, ScaleContext, Source, StrikeWith},
|
||||
zeno::{Format, Vector},
|
||||
};
|
||||
|
||||
pub struct TextData {
|
||||
pub font_ctx: FontContext,
|
||||
pub layout_ctx: LayoutContext<UiColor>,
|
||||
scale_ctx: ScaleContext,
|
||||
pub atlas: GlyphAtlas,
|
||||
spare: VecDeque<Placed>,
|
||||
}
|
||||
|
||||
/// The glyphs of one text at one width. A buffer holds the ones it is drawn
|
||||
/// as; these are the ones it had before, kept because a container measures a
|
||||
/// child by drawing it in a box it may not keep, and so comes back to widths
|
||||
/// it has already asked for.
|
||||
struct Placed {
|
||||
/// Where the glyphs land is a function of these three and nothing else,
|
||||
/// so no widget or buffer identity is involved and two texts of the same
|
||||
/// words share an answer.
|
||||
text: String,
|
||||
key: LayoutKey,
|
||||
glyphs: RenderedText,
|
||||
}
|
||||
|
||||
/// How many to keep. Bounding the whole store rather than each buffer is what
|
||||
/// makes this a fixed cost instead of one a tree of ten thousand texts pays
|
||||
/// ten thousand times; the re-asks come from laying out one subtree, so they
|
||||
/// are close together and few are needed. Instructions over 500 resize frames
|
||||
/// of `tests/revision_cost.rs`, both the repeating widths and the sweep that
|
||||
/// cannot hit across frames: 13.7B at 32, 12.1B at 64, 10.4B and 12.1B at 128,
|
||||
/// and nothing past that -- so 128, which is no worse in the case that never
|
||||
/// repeats and better in the one that does.
|
||||
const SPARE_PLACED: usize = 128;
|
||||
|
||||
impl Default for TextData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_ctx: FontContext::new(),
|
||||
layout_ctx: LayoutContext::new(),
|
||||
scale_ctx: ScaleContext::new(),
|
||||
atlas: GlyphAtlas::default(),
|
||||
spare: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Family {
|
||||
SansSerif,
|
||||
Serif,
|
||||
Monospace,
|
||||
Named(String),
|
||||
}
|
||||
|
||||
impl Family {
|
||||
fn family(&self) -> FontFamily<'_> {
|
||||
let name = match self {
|
||||
Self::SansSerif => FontFamilyName::Generic(GenericFamily::SansSerif),
|
||||
Self::Serif => FontFamilyName::Generic(GenericFamily::Serif),
|
||||
Self::Monospace => FontFamilyName::Generic(GenericFamily::Monospace),
|
||||
Self::Named(name) => FontFamilyName::Named(name.as_str().into()),
|
||||
};
|
||||
FontFamily::Single(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct TextAttrs {
|
||||
pub color: UiColor,
|
||||
pub font_size: f32,
|
||||
pub line_height: f32,
|
||||
pub family: Family,
|
||||
pub wrap: bool,
|
||||
pub align: RegionAlign,
|
||||
}
|
||||
|
||||
pub const LINE_HEIGHT_MULT: f32 = 1.1;
|
||||
|
||||
impl Default for TextAttrs {
|
||||
fn default() -> Self {
|
||||
let size = 16.0;
|
||||
Self {
|
||||
color: UiColor::WHITE,
|
||||
font_size: size,
|
||||
line_height: size * LINE_HEIGHT_MULT,
|
||||
family: Family::SansSerif,
|
||||
wrap: false,
|
||||
align: Align::CENTER_LEFT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps text and its corresponding layout from getting out of sync.
|
||||
pub struct TextBuffer {
|
||||
text: String,
|
||||
layout: Layout<UiColor>,
|
||||
layout_key: Option<LayoutKey>,
|
||||
/// The glyphs placed from `layout`, so drawing this text again at the
|
||||
/// width it already has places them once.
|
||||
placed: Option<RenderedText>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct LayoutKey {
|
||||
attrs: TextAttrs,
|
||||
max_width: Option<f32>,
|
||||
}
|
||||
|
||||
impl TextBuffer {
|
||||
pub fn new(text: impl Into<String>) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
layout: Layout::new(),
|
||||
layout_key: None,
|
||||
placed: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self::new("")
|
||||
}
|
||||
|
||||
pub fn text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> &Layout<UiColor> {
|
||||
&self.layout
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.text.is_empty()
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: impl Into<String>) {
|
||||
let text = text.into();
|
||||
if text != self.text {
|
||||
self.text = text;
|
||||
self.layout_key = None;
|
||||
self.placed = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidates the layout and returns the underlying string for editing.
|
||||
pub fn edit(&mut self) -> &mut String {
|
||||
self.layout_key = None;
|
||||
self.placed = None;
|
||||
&mut self.text
|
||||
}
|
||||
|
||||
/// The glyphs of the shaping it is drawn as, once they are placed.
|
||||
pub fn rendered(&self) -> Option<&RenderedText> {
|
||||
self.placed.as_ref()
|
||||
}
|
||||
|
||||
/// The width its shaping wraps at, and `None` where it does not wrap or
|
||||
/// has not been shaped.
|
||||
pub fn wrap_width(&self) -> Option<f32> {
|
||||
self.layout_key.as_ref()?.max_width
|
||||
}
|
||||
|
||||
/// Widths covered by the current line breaks, including a wider shaping
|
||||
/// retained when a later draw requested a narrower box.
|
||||
pub fn width_holds(&self) -> crate::Holds {
|
||||
let Some(width) = self.wrap_width() else {
|
||||
return crate::Holds::ANY;
|
||||
};
|
||||
let width = Px::from_f32(width);
|
||||
let soft_wrapped = self.layout.lines().any(|line| {
|
||||
matches!(
|
||||
line.break_reason(),
|
||||
parley::layout::BreakReason::Regular | parley::layout::BreakReason::Emergency
|
||||
)
|
||||
});
|
||||
let upper = if soft_wrapped { width } else { Px::MAX };
|
||||
crate::Holds::from(Px::ceil_from_f32(self.layout.width()).min(width)..=upper)
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Vec2 {
|
||||
Vec2::new(self.layout.width(), self.layout.height())
|
||||
}
|
||||
|
||||
pub fn shape(&mut self, data: &mut TextData, attrs: &TextAttrs, width: Option<f32>) {
|
||||
let layout_key = LayoutKey {
|
||||
attrs: attrs.clone(),
|
||||
max_width: width,
|
||||
};
|
||||
if self.layout_key.as_ref() == Some(&layout_key) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::TextShapeHits);
|
||||
return;
|
||||
}
|
||||
// A greedy break at one width is the same break at every width down
|
||||
// to the longest line it produced: each line still fits, and none can
|
||||
// take a word that would not fit in the wider box. So the layout in
|
||||
// hand already answers, and re-breaking would only be work.
|
||||
//
|
||||
// At the longest line exactly, with no margin below it. A narrower
|
||||
// width really does break differently, so answering one from the
|
||||
// break in hand is how a warm tree keeps lines a cold tree would
|
||||
// never produce. The margin was here because a text reports the
|
||||
// width it used and a parent hands that back; the report is the step
|
||||
// at or above its longest line now, so what comes back fits.
|
||||
if let Some(key) = &self.layout_key
|
||||
&& key.attrs == *attrs
|
||||
&& let (Some(broke_at), Some(want)) = (key.max_width, width)
|
||||
&& want <= broke_at
|
||||
&& want >= self.layout.width()
|
||||
{
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::TextShapeHits);
|
||||
return;
|
||||
}
|
||||
let same_shaping = self
|
||||
.layout_key
|
||||
.as_ref()
|
||||
.is_some_and(|key| key.attrs == *attrs);
|
||||
let old_key = self.layout_key.replace(layout_key);
|
||||
// The glyphs it holds are of the width it held, which the layout may
|
||||
// well come back to.
|
||||
if let Some(key) = old_key
|
||||
&& let Some(glyphs) = self.placed.take()
|
||||
{
|
||||
data.keep_placed(Placed {
|
||||
text: self.text.clone(),
|
||||
key,
|
||||
glyphs,
|
||||
});
|
||||
}
|
||||
// Only the line breaking depends on the width: the shaped runs under
|
||||
// it are a function of the text and the attrs, and parley re-breaks
|
||||
// them in place. So a new width is a break, not a shaping.
|
||||
if same_shaping {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::TextBreaks);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _break = diag::timer(TimerKind::TextBreak);
|
||||
self.break_lines(width);
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::TextShapes);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _shape = diag::timer(TimerKind::TextShape);
|
||||
let mut builder = data
|
||||
.layout_ctx
|
||||
.ranged_builder(&mut data.font_ctx, &self.text, 1.0, true);
|
||||
builder.push_default(StyleProperty::FontFamily(attrs.family.family()));
|
||||
builder.push_default(StyleProperty::FontSize(attrs.font_size));
|
||||
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
|
||||
attrs.line_height,
|
||||
)));
|
||||
builder.push_default(StyleProperty::Brush(attrs.color));
|
||||
builder.build_into(&mut self.layout, &self.text);
|
||||
self.break_lines(width);
|
||||
}
|
||||
|
||||
fn break_lines(&mut self, width: Option<f32>) {
|
||||
self.layout.break_all_lines(width);
|
||||
self.layout
|
||||
.align(Alignment::Start, AlignmentOptions::default());
|
||||
}
|
||||
}
|
||||
|
||||
impl TextData {
|
||||
pub fn place(&mut self, buffer: &TextBuffer) -> Vec<PlacedGlyph> {
|
||||
let mut placed = Vec::new();
|
||||
for line in buffer.layout.lines() {
|
||||
for item in line.items() {
|
||||
let PositionedLayoutItem::GlyphRun(run) = item else {
|
||||
continue;
|
||||
};
|
||||
let font = run.run().font();
|
||||
let font_size = run.run().font_size();
|
||||
let coords = run.run().normalized_coords();
|
||||
let Some(font_ref) = FontRef::from_index(font.data.as_ref(), font.index as usize)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let coords_hash = hash_coords(coords);
|
||||
let font_id = font.data.id();
|
||||
|
||||
for glyph in run.positioned_glyphs() {
|
||||
let subpixel = ((glyph.x.fract() * 4.0).round() as i32).rem_euclid(4) as u8;
|
||||
let key = GlyphKey {
|
||||
font: font_id,
|
||||
glyph: glyph.id,
|
||||
size: glyph_size_key(font_size),
|
||||
subpixel,
|
||||
coords: coords_hash,
|
||||
};
|
||||
let Some(entry) = self.glyph_entry(GlyphRaster {
|
||||
key,
|
||||
font: font_ref,
|
||||
font_size,
|
||||
coords,
|
||||
subpixel,
|
||||
glyph_id: glyph.id,
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
placed.push(PlacedGlyph {
|
||||
entry,
|
||||
offset: PxVec2::new(
|
||||
Px::from_int(glyph.x.floor() as i32 + entry.left),
|
||||
Px::from_int(glyph.y.floor() as i32 - entry.top),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
placed
|
||||
}
|
||||
|
||||
fn glyph_entry(&mut self, glyph: GlyphRaster<'_>) -> Option<GlyphEntry> {
|
||||
if let Some(entry) = self.atlas.get(&glyph.key) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
let mut scaler = self
|
||||
.scale_ctx
|
||||
.builder(glyph.font)
|
||||
.size(glyph.font_size)
|
||||
.hint(true)
|
||||
.normalized_coords(glyph.coords)
|
||||
.build();
|
||||
let image = Render::new(&[
|
||||
Source::ColorOutline(0),
|
||||
Source::ColorBitmap(StrikeWith::BestFit),
|
||||
Source::Outline,
|
||||
])
|
||||
.format(Format::Alpha)
|
||||
.offset(Vector::new(glyph.subpixel as f32 / 4.0, 0.0))
|
||||
.render(&mut scaler, glyph.glyph_id as u16);
|
||||
|
||||
if let Some(image) = image {
|
||||
self.atlas.insert(glyph.key, &image)
|
||||
} else {
|
||||
self.atlas.insert_empty(glyph.key);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GlyphRaster<'a> {
|
||||
key: GlyphKey,
|
||||
font: FontRef<'a>,
|
||||
font_size: f32,
|
||||
coords: &'a [i16],
|
||||
subpixel: u8,
|
||||
glyph_id: u32,
|
||||
}
|
||||
|
||||
fn hash_coords(coords: &[i16]) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
coords.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
const GLYPH_SIZE_STEPS_PER_PIXEL: f32 = 16.0;
|
||||
|
||||
fn glyph_size_key(font_size: f32) -> u32 {
|
||||
(font_size * GLYPH_SIZE_STEPS_PER_PIXEL).round() as u32
|
||||
}
|
||||
|
||||
pub struct RenderedText {
|
||||
pub glyphs: Vec<PlacedGlyph>,
|
||||
pub size: Vec2,
|
||||
pub color: UiColor,
|
||||
}
|
||||
|
||||
impl TextData {
|
||||
/// The glyphs of this text at this width, taken out of what is kept.
|
||||
fn take_placed(&mut self, text: &str, key: &LayoutKey) -> Option<RenderedText> {
|
||||
// From the newest, since a re-ask is usually of something recent.
|
||||
let at = self
|
||||
.spare
|
||||
.iter()
|
||||
.rposition(|spare| spare.key == *key && spare.text == text)?;
|
||||
self.spare.remove(at).map(|spare| spare.glyphs)
|
||||
}
|
||||
|
||||
fn keep_placed(&mut self, placed: Placed) {
|
||||
if self.spare.len() >= SPARE_PLACED {
|
||||
self.spare.pop_front();
|
||||
}
|
||||
self.spare.push_back(placed);
|
||||
}
|
||||
|
||||
pub fn render<'b>(
|
||||
&mut self,
|
||||
buffer: &'b mut TextBuffer,
|
||||
attrs: &TextAttrs,
|
||||
width: Option<f32>,
|
||||
) -> &'b RenderedText {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::TextRenders);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _render = diag::timer(TimerKind::TextRender);
|
||||
buffer.shape(self, attrs, width);
|
||||
// Only asked for when the buffer no longer holds them: taking one out
|
||||
// of the store to then drop it would throw an answer away.
|
||||
let placed = buffer.placed.take().or_else(|| {
|
||||
let key = buffer.layout_key.as_ref()?;
|
||||
self.take_placed(&buffer.text, key)
|
||||
});
|
||||
let placed = match placed {
|
||||
Some(placed) => placed,
|
||||
None => {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::GlyphPlacements);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _place = diag::timer(TimerKind::GlyphPlacement);
|
||||
RenderedText {
|
||||
glyphs: self.place(buffer),
|
||||
size: buffer.size(),
|
||||
color: attrs.color,
|
||||
}
|
||||
}
|
||||
};
|
||||
buffer.placed.insert(placed)
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
use crate::{
|
||||
PatchRect, PxVec2,
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
use image::RgbaImage;
|
||||
use swash::scale::image::{Content, Image};
|
||||
|
||||
/// Side of one page, and so of every layer of `render::page`'s array texture.
|
||||
pub(crate) const PAGE: u32 = 1024;
|
||||
|
||||
/// Transparent margin kept around every glyph, so that sampling one cannot
|
||||
/// pick up its neighbour along a shared edge.
|
||||
const PAD: u32 = 1;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct GlyphKey {
|
||||
pub font: u64,
|
||||
pub glyph: u32,
|
||||
/// Font size in 1/16 px, so sizes that round to the same pixels share a
|
||||
/// raster instead of filling the atlas with near-duplicates.
|
||||
pub size: u32,
|
||||
/// Horizontal subpixel phase, in 1/4 px.
|
||||
pub subpixel: u8,
|
||||
/// Hash of the variation coordinates; a variable font at two weights is two
|
||||
/// different sets of pixels from one glyph id.
|
||||
pub coords: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GlyphEntry {
|
||||
pub uv_min: Vec2,
|
||||
pub uv_max: Vec2,
|
||||
/// Offset from the glyph's pen position to the top-left of its pixels.
|
||||
pub left: i32,
|
||||
pub top: i32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub is_colored: bool,
|
||||
/// Which atlas array layer this glyph is on.
|
||||
pub layer: u32,
|
||||
}
|
||||
|
||||
impl GlyphEntry {
|
||||
const IS_COLORED: u32 = 1;
|
||||
|
||||
pub(crate) fn flags(&self) -> u32 {
|
||||
if self.is_colored { Self::IS_COLORED } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct Page {
|
||||
image: RgbaImage,
|
||||
x: u32,
|
||||
y: u32,
|
||||
shelf_height: u32,
|
||||
}
|
||||
|
||||
/// A rectangle of one page the renderer has not uploaded yet.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PageUpload {
|
||||
pub layer: u32,
|
||||
pub rect: PatchRect,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GlyphAtlas {
|
||||
pages: Vec<Page>,
|
||||
/// `None` for a glyph that rasterised to nothing -- a space, say. Cached
|
||||
/// too, so it is not re-rasterised on every layout.
|
||||
entries: HashMap<GlyphKey, Option<GlyphEntry>>,
|
||||
uploads: Vec<PageUpload>,
|
||||
}
|
||||
|
||||
impl GlyphAtlas {
|
||||
pub fn get(&self, key: &GlyphKey) -> Option<Option<GlyphEntry>> {
|
||||
self.entries.get(key).copied()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: GlyphKey, image: &Image) -> Option<GlyphEntry> {
|
||||
let w = image.placement.width;
|
||||
let h = image.placement.height;
|
||||
if w == 0 || h == 0 {
|
||||
log::warn!(
|
||||
"glyph {} in font {} rasterized at {w}x{h}; skipping it",
|
||||
key.glyph,
|
||||
key.font,
|
||||
);
|
||||
self.entries.insert(key, None);
|
||||
return None;
|
||||
}
|
||||
if w > PAGE - PAD * 2 || h > PAGE - PAD * 2 {
|
||||
log::warn!(
|
||||
"glyph {} in font {} rasterized at {w}x{h}, too large for the {PAGE}x{PAGE} atlas; skipping it",
|
||||
key.glyph,
|
||||
key.font,
|
||||
);
|
||||
self.entries.insert(key, None);
|
||||
return None;
|
||||
}
|
||||
|
||||
let upload = self.allocate(w, h);
|
||||
let PatchRect { x, y, .. } = upload.rect;
|
||||
write_glyph(&mut self.pages[upload.layer as usize].image, image, x, y);
|
||||
self.uploads.push(upload);
|
||||
|
||||
let scale = 1.0 / PAGE as f32;
|
||||
let entry = GlyphEntry {
|
||||
uv_min: Vec2::new(x as f32 * scale, y as f32 * scale),
|
||||
uv_max: Vec2::new((x + w) as f32 * scale, (y + h) as f32 * scale),
|
||||
left: image.placement.left,
|
||||
top: image.placement.top,
|
||||
width: w,
|
||||
height: h,
|
||||
is_colored: matches!(image.content, Content::Color),
|
||||
layer: upload.layer,
|
||||
};
|
||||
self.entries.insert(key, Some(entry));
|
||||
Some(entry)
|
||||
}
|
||||
|
||||
/// Reserves room for a `w` by `h` glyph, adding a page if none has it.
|
||||
fn allocate(&mut self, w: u32, h: u32) -> PageUpload {
|
||||
let rect = |x, y| PatchRect {
|
||||
x,
|
||||
y,
|
||||
width: w,
|
||||
height: h,
|
||||
};
|
||||
if let Some((i, (x, y))) = self
|
||||
.pages
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position)))
|
||||
{
|
||||
return PageUpload {
|
||||
layer: i as u32,
|
||||
rect: rect(x, y),
|
||||
};
|
||||
}
|
||||
|
||||
self.pages.push(Page {
|
||||
image: RgbaImage::new(PAGE, PAGE),
|
||||
x: PAD + w + PAD,
|
||||
y: PAD,
|
||||
shelf_height: h + PAD,
|
||||
});
|
||||
PageUpload {
|
||||
layer: self.pages.len() as u32 - 1,
|
||||
rect: rect(PAD, PAD),
|
||||
}
|
||||
}
|
||||
|
||||
/// Drains what has been written since the last call, for the renderer to
|
||||
/// upload. A new page needs nothing more: wgpu leaves the rest of a fresh
|
||||
/// layer transparent, which is what an atlas wants.
|
||||
pub fn uploads(&mut self) -> impl Iterator<Item = (PageUpload, &RgbaImage)> {
|
||||
let pages = &self.pages;
|
||||
self.uploads
|
||||
.drain(..)
|
||||
.map(|upload| (upload, &pages[upload.layer as usize].image))
|
||||
}
|
||||
|
||||
pub fn insert_empty(&mut self, key: GlyphKey) {
|
||||
self.entries.insert(key, None);
|
||||
}
|
||||
|
||||
pub fn page_count(&self) -> u32 {
|
||||
self.pages.len() as u32
|
||||
}
|
||||
|
||||
pub fn glyph_count(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
fn allocate(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
|
||||
let need_w = w + PAD;
|
||||
let need_h = h + PAD;
|
||||
if self.x + need_w > PAGE {
|
||||
if need_w + PAD > PAGE || self.y + self.shelf_height + need_h > PAGE {
|
||||
return None;
|
||||
}
|
||||
self.y += self.shelf_height;
|
||||
self.x = PAD;
|
||||
self.shelf_height = 0;
|
||||
} else if self.y + need_h > PAGE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let position = (self.x, self.y);
|
||||
self.x += need_w;
|
||||
self.shelf_height = self.shelf_height.max(need_h);
|
||||
Some(position)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mask glyphs keep coverage in alpha so their raster can be tinted at draw time.
|
||||
fn write_glyph(page: &mut RgbaImage, image: &Image, x: u32, y: u32) {
|
||||
let width = image.placement.width as usize;
|
||||
let height = image.placement.height as usize;
|
||||
let page_stride = page.width() as usize * 4;
|
||||
let x = x as usize * 4;
|
||||
let y = y as usize;
|
||||
let page = page.as_mut();
|
||||
|
||||
for row in 0..height {
|
||||
let start = (y + row) * page_stride + x;
|
||||
let target = &mut page[start..start + width * 4];
|
||||
match image.content {
|
||||
Content::Color => {
|
||||
let start = row * width * 4;
|
||||
target.copy_from_slice(&image.data[start..start + width * 4]);
|
||||
}
|
||||
Content::Mask => {
|
||||
let start = row * width;
|
||||
for (target, &alpha) in target
|
||||
.as_chunks_mut::<4>()
|
||||
.0
|
||||
.iter_mut()
|
||||
.zip(&image.data[start..start + width])
|
||||
{
|
||||
target.copy_from_slice(&[255, 255, 255, alpha]);
|
||||
}
|
||||
}
|
||||
Content::SubpixelMask => {
|
||||
let start = row * width * 4;
|
||||
for (target, source) in target
|
||||
.as_chunks_mut::<4>()
|
||||
.0
|
||||
.iter_mut()
|
||||
.zip(image.data[start..start + width * 4].as_chunks::<4>().0)
|
||||
{
|
||||
target.copy_from_slice(&[255, 255, 255, source[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PlacedGlyph {
|
||||
pub entry: GlyphEntry,
|
||||
/// Whole pixels from the origin of the text to this glyph's top-left,
|
||||
/// on the grid once here rather than on every frame that draws it.
|
||||
pub offset: PxVec2,
|
||||
}
|
||||
+11
-51
@@ -1,30 +1,31 @@
|
||||
use crate::{UiRegion, util::Id, util::Vec2};
|
||||
use crate::{layout::UiRegion, util::Id};
|
||||
use wgpu::*;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct WindowUniform {
|
||||
pub dim: Vec2,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct PrimitiveInstance {
|
||||
pub region: UiRegion,
|
||||
pub binding: u32,
|
||||
pub idx: u32,
|
||||
pub mask_idx: MaskIdx,
|
||||
pub move_idx: MoveIdx,
|
||||
}
|
||||
|
||||
impl PrimitiveInstance {
|
||||
// The region's four scalars, each a `Rel` beside a `Px`: whole counts
|
||||
// that the shader decodes, rather than the numbers themselves.
|
||||
const ATTRIBS: [VertexAttribute; 6] = vertex_attr_array![
|
||||
0 => Sint32x2,
|
||||
1 => Sint32x2,
|
||||
2 => Sint32x2,
|
||||
3 => Sint32x2,
|
||||
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
|
||||
0 => Float32x2,
|
||||
1 => Float32x2,
|
||||
2 => Float32x2,
|
||||
3 => Float32x2,
|
||||
4 => Uint32,
|
||||
5 => Uint32,
|
||||
6 => Uint32,
|
||||
];
|
||||
|
||||
pub fn desc() -> VertexBufferLayout<'static> {
|
||||
@@ -46,45 +47,4 @@ impl MaskIdx {
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Mask {
|
||||
pub region: UiRegion,
|
||||
pub move_idx: MoveIdx,
|
||||
}
|
||||
|
||||
/// Its own type rather than another `Id<u32>`, because it sits beside
|
||||
/// `MaskIdx` in an instance and the two must not be swappable.
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct MoveIdx(u32);
|
||||
|
||||
impl MoveIdx {
|
||||
pub const NONE: Self = Self(u32::MAX);
|
||||
|
||||
pub(crate) fn slot(idx: usize) -> Self {
|
||||
Self(idx as u32)
|
||||
}
|
||||
|
||||
pub(crate) fn idx(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// One link of the chain a primitive's position is resolved through: the box
|
||||
/// its contents are placed within, given in the coordinates of the slot it
|
||||
/// names. Moving or resizing a subtree writes its own slot and nothing else.
|
||||
///
|
||||
/// The identity is `UiRegion::FULL`, not zero: a zeroed entry is a box of no
|
||||
/// extent, which collapses everything under it to a point.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MoveOffset {
|
||||
pub region: UiRegion,
|
||||
pub parent: MoveIdx,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for MoveOffset {}
|
||||
unsafe impl bytemuck::Zeroable for MoveOffset {}
|
||||
|
||||
impl MoveOffset {
|
||||
pub fn new(parent: MoveIdx, region: UiRegion) -> Self {
|
||||
Self { region, parent }
|
||||
}
|
||||
}
|
||||
+203
-294
@@ -1,255 +1,204 @@
|
||||
use std::num::NonZero;
|
||||
|
||||
use crate::{
|
||||
UiData, UiRenderState,
|
||||
render::{data::PrimitiveInstance, util::ArrBuf},
|
||||
util::{HashMap, Vec2},
|
||||
layout::Ui,
|
||||
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
||||
util::HashMap,
|
||||
};
|
||||
use data::WindowUniform;
|
||||
use wgpu::{
|
||||
util::{BufferInitDescriptor, DeviceExt},
|
||||
*,
|
||||
};
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
mod atlas;
|
||||
mod data;
|
||||
mod page;
|
||||
mod primitive;
|
||||
mod texture;
|
||||
mod util;
|
||||
|
||||
pub use atlas::*;
|
||||
pub use data::{Mask, MaskIdx, MoveIdx, MoveOffset};
|
||||
pub use data::{Mask, MaskIdx};
|
||||
pub use primitive::*;
|
||||
|
||||
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
||||
|
||||
fn module_source(wgsl: &str) -> String {
|
||||
// The steps come from the same constants the CPU counts in, rather than
|
||||
// a second copy of them written into the shader: a grid the two disagree
|
||||
// about puts every coordinate somewhere else.
|
||||
format!(
|
||||
"const PX_STEP: f32 = 1.0 / {}.0;\nconst REL_STEP: f32 = 1.0 / {}.0;\n{PRELUDE}\n{wgsl}",
|
||||
1u32 << crate::PX_SHIFT,
|
||||
1u32 << crate::REL_SHIFT,
|
||||
)
|
||||
}
|
||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||
|
||||
pub struct UiRenderNode {
|
||||
shared_layout: BindGroupLayout,
|
||||
shared_group: BindGroup,
|
||||
format: TextureFormat,
|
||||
uniform_group: BindGroup,
|
||||
primitive_layout: BindGroupLayout,
|
||||
rsc_layout: BindGroupLayout,
|
||||
rsc_group: BindGroup,
|
||||
|
||||
/// One per registered primitive, in id order.
|
||||
primitives: Vec<PrimitivePipeline>,
|
||||
pipeline: RenderPipeline,
|
||||
|
||||
layers: HashMap<usize, RenderLayer>,
|
||||
active: Vec<usize>,
|
||||
window_buffer: Buffer,
|
||||
textures: GpuTextures,
|
||||
masks: ArrBuf<Mask>,
|
||||
moves: ArrBuf<MoveOffset>,
|
||||
}
|
||||
|
||||
struct RenderLayer {
|
||||
/// One per registered primitive, `None` where this layer draws none.
|
||||
primitives: Vec<Option<ListBuffers>>,
|
||||
}
|
||||
|
||||
/// What draws one registered primitive.
|
||||
struct PrimitivePipeline {
|
||||
data_layout: BindGroupLayout,
|
||||
pipeline: RenderPipeline,
|
||||
render: Box<dyn PrimitiveRender>,
|
||||
}
|
||||
|
||||
/// One list's vertex buffer and the data its shader reads.
|
||||
struct ListBuffers {
|
||||
instance: ArrBuf<PrimitiveInstance>,
|
||||
data: ArrBuf<u8>,
|
||||
group: Option<BindGroup>,
|
||||
/// What the primitive asked to keep per instance, if anything.
|
||||
bindings: Vec<u32>,
|
||||
primitives: PrimitiveBuffers,
|
||||
primitive_group: BindGroup,
|
||||
}
|
||||
|
||||
impl UiRenderNode {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_bind_group(0, &self.shared_group, &[]);
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_group, &[]);
|
||||
pass.set_bind_group(2, &self.rsc_group, &[]);
|
||||
for i in &self.active {
|
||||
let layer = &self.layers[i];
|
||||
for (id, list) in layer.primitives.iter().enumerate() {
|
||||
let Some(list) = list else { continue };
|
||||
let Some(group) = &list.group else { continue };
|
||||
let primitive = &self.primitives[id];
|
||||
pass.set_pipeline(&primitive.pipeline);
|
||||
pass.set_bind_group(1, group, &[]);
|
||||
pass.set_vertex_buffer(0, list.instance.buffer.slice(..));
|
||||
primitive.render.draw(
|
||||
pass,
|
||||
ListDraw {
|
||||
instances: list.instance.len() as u32,
|
||||
bindings: &list.bindings,
|
||||
},
|
||||
);
|
||||
if layer.instance.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
||||
pass.draw(0..4, 0..layer.instance.len() as u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
device: &Device,
|
||||
queue: &Queue,
|
||||
ui: &mut UiData,
|
||||
ui_render: &mut UiRenderState,
|
||||
) {
|
||||
// Before the layers: each list is given its pipeline's data layout.
|
||||
self.build_pipelines(device, queue, &ui.primitives);
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
|
||||
self.active.clear();
|
||||
for (i, draws) in ui_render.layers.iter_mut() {
|
||||
for (i, primitives) in ui.data.layers.iter_mut() {
|
||||
self.active.push(i);
|
||||
for change in draws.apply_free() {
|
||||
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
||||
for primitive in &mut inst.primitives {
|
||||
let h = &mut primitive.handle;
|
||||
if h.layer == i && h.kind == change.kind && h.inst_idx == change.old {
|
||||
for change in primitives.apply_free() {
|
||||
if let Some(inst) = ui.data.active.get_mut(&change.id) {
|
||||
for h in &mut inst.primitives {
|
||||
if h.layer == i && h.inst_idx == change.old {
|
||||
h.inst_idx = change.new;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let rlayer = self.layers.entry(i).or_insert_with(RenderLayer::new);
|
||||
if draws.updated {
|
||||
let lists = draws.primitives();
|
||||
// The zip would otherwise skip a list with no pipeline.
|
||||
assert!(lists.len() <= self.primitives.len());
|
||||
rlayer.primitives.resize_with(lists.len(), || None);
|
||||
for ((buffers, list), primitive) in rlayer
|
||||
.primitives
|
||||
.iter_mut()
|
||||
.zip(lists)
|
||||
.zip(&self.primitives)
|
||||
{
|
||||
let Some(list) = list else {
|
||||
continue;
|
||||
};
|
||||
buffers
|
||||
.get_or_insert_with(|| ListBuffers::new(device))
|
||||
.update(device, queue, primitive, list);
|
||||
let rlayer = self.layers.entry(i).or_insert_with(|| {
|
||||
let primitives = PrimitiveBuffers::new(device);
|
||||
let primitive_group =
|
||||
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
|
||||
RenderLayer {
|
||||
instance: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||
"instance",
|
||||
),
|
||||
primitives,
|
||||
primitive_group,
|
||||
}
|
||||
draws.updated = false;
|
||||
});
|
||||
if primitives.updated {
|
||||
rlayer
|
||||
.instance
|
||||
.update(device, queue, primitives.instances());
|
||||
rlayer.primitives.update(device, queue, primitives.data());
|
||||
rlayer.primitive_group = Self::primitive_group(
|
||||
device,
|
||||
&self.primitive_layout,
|
||||
rlayer.primitives.buffers(),
|
||||
);
|
||||
primitives.updated = false;
|
||||
}
|
||||
}
|
||||
for primitive in &mut self.primitives {
|
||||
primitive.render.update(ui);
|
||||
let mut changed = false;
|
||||
changed |= self.textures.update(&mut ui.data.textures);
|
||||
if ui.data.masks.changed {
|
||||
ui.data.masks.changed = false;
|
||||
self.masks.update(device, queue, &ui.data.masks[..]);
|
||||
changed = true;
|
||||
}
|
||||
let mut regroup = false;
|
||||
if ui.masks.changed {
|
||||
ui.masks.changed = false;
|
||||
regroup |= self.masks.update(device, queue, &ui.masks[..]);
|
||||
}
|
||||
if ui_render.moves.changed {
|
||||
ui_render.moves.changed = false;
|
||||
regroup |= self.moves.update(device, queue, ui_render.moves.entries());
|
||||
}
|
||||
if regroup {
|
||||
self.shared_group = Self::shared_group(
|
||||
device,
|
||||
&self.shared_layout,
|
||||
&self.window_buffer,
|
||||
&self.masks,
|
||||
&self.moves,
|
||||
);
|
||||
if changed {
|
||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>, queue: &Queue) {
|
||||
let size = size.into();
|
||||
let slice = &[WindowUniform { dim: size }];
|
||||
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &Queue) {
|
||||
let slice = &[WindowUniform {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
}];
|
||||
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
||||
}
|
||||
|
||||
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
||||
let window_uniform = WindowUniform {
|
||||
dim: Vec2::new(config.width as f32, config.height as f32),
|
||||
};
|
||||
pub fn new(
|
||||
device: &Device,
|
||||
queue: &Queue,
|
||||
config: &SurfaceConfiguration,
|
||||
limits: UiLimits,
|
||||
) -> Self {
|
||||
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
||||
label: Some("UI Shape Shader"),
|
||||
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
||||
});
|
||||
|
||||
let window_uniform = WindowUniform::default();
|
||||
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||
label: Some("window"),
|
||||
contents: bytemuck::cast_slice(&[window_uniform]),
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let shared_layout = Self::shared_layout(device);
|
||||
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("window"),
|
||||
});
|
||||
|
||||
let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer);
|
||||
|
||||
let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| {
|
||||
BindGroupLayoutEntry {
|
||||
binding: i as u32,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
}),
|
||||
label: Some("primitive"),
|
||||
});
|
||||
|
||||
let tex_manager = GpuTextures::new(device, queue);
|
||||
let masks = ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
"ui masks",
|
||||
);
|
||||
let moves = ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
"ui move offsets",
|
||||
);
|
||||
let shared_group =
|
||||
Self::shared_group(device, &shared_layout, &window_buffer, &masks, &moves);
|
||||
|
||||
Self {
|
||||
shared_layout,
|
||||
shared_group,
|
||||
format: config.format,
|
||||
primitives: Vec::new(),
|
||||
window_buffer,
|
||||
layers: HashMap::default(),
|
||||
active: Vec::new(),
|
||||
masks,
|
||||
moves,
|
||||
}
|
||||
}
|
||||
let rsc_layout = Self::rsc_layout(device, &limits);
|
||||
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
|
||||
|
||||
/// Compiles a pipeline for every primitive registered since the last call.
|
||||
/// Sources only ever arrive at the end, so an id keeps its pipeline.
|
||||
fn build_pipelines(&mut self, device: &Device, queue: &Queue, registry: &PrimitiveRegistry) {
|
||||
for source in ®istry.sources()[self.primitives.len()..] {
|
||||
let render = (source.render)(device, queue);
|
||||
let data_layout = Self::data_layout(device, source.stride);
|
||||
let mut groups = vec![Some(&self.shared_layout), Some(&data_layout)];
|
||||
groups.extend(render.layout().map(Some));
|
||||
let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||
label: Some(source.label),
|
||||
bind_group_layouts: &groups,
|
||||
immediate_size: 0,
|
||||
});
|
||||
let pipeline = Self::pipeline(device, &layout, self.format, source.wgsl, source.label);
|
||||
self.primitives.push(PrimitivePipeline {
|
||||
data_layout,
|
||||
pipeline,
|
||||
render,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn pipeline(
|
||||
device: &Device,
|
||||
layout: &PipelineLayout,
|
||||
format: TextureFormat,
|
||||
wgsl: &str,
|
||||
label: &str,
|
||||
) -> RenderPipeline {
|
||||
let module = device.create_shader_module(ShaderModuleDescriptor {
|
||||
label: Some(label),
|
||||
source: ShaderSource::Wgsl(module_source(wgsl).into()),
|
||||
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||
label: Some("UI Shape Pipeline Layout"),
|
||||
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||
label: Some(label),
|
||||
layout: Some(layout),
|
||||
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||
label: Some("UI Shape Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: VertexState {
|
||||
module: &module,
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[Some(PrimitiveInstance::desc())],
|
||||
buffers: &[PrimitiveInstance::desc()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: &module,
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(ColorTargetState {
|
||||
format,
|
||||
format: config.format,
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
@@ -270,178 +219,138 @@ impl UiRenderNode {
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
uniform_group,
|
||||
primitive_layout,
|
||||
rsc_layout,
|
||||
rsc_group,
|
||||
pipeline,
|
||||
window_buffer,
|
||||
layers: HashMap::default(),
|
||||
active: Vec::new(),
|
||||
textures: tex_manager,
|
||||
masks,
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_group_0(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
window_buffer: &Buffer,
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: window_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("ui window"),
|
||||
})
|
||||
}
|
||||
|
||||
/// What every draw in the ui is given: the window, the masks and the
|
||||
/// move chain every position is resolved through.
|
||||
fn shared_layout(device: &Device) -> BindGroupLayout {
|
||||
fn primitive_group(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
buffers: [(u32, &Buffer); PrimitiveBuffers::LEN],
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &buffers.map(|(binding, buf)| BindGroupEntry {
|
||||
binding,
|
||||
resource: buf.as_entire_binding(),
|
||||
}),
|
||||
label: Some("ui primitives"),
|
||||
})
|
||||
}
|
||||
|
||||
fn rsc_layout(device: &Device, limits: &UiLimits) -> BindGroupLayout {
|
||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: BufferSize::new(size_of::<WindowUniform>() as u64),
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
sample_type: TextureSampleType::Float { filterable: false },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
count: Some(NonZero::new(limits.max_textures).unwrap()),
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: BufferSize::new(size_of::<Mask>() as u64),
|
||||
},
|
||||
count: None,
|
||||
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
||||
count: Some(NonZero::new(limits.max_samplers).unwrap()),
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: BufferSize::new(size_of::<MoveOffset>() as u64),
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("ui shared"),
|
||||
label: Some("ui rsc"),
|
||||
})
|
||||
}
|
||||
|
||||
fn shared_group(
|
||||
fn rsc_group(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
window: &Buffer,
|
||||
tex_manager: &GpuTextures,
|
||||
masks: &ArrBuf<Mask>,
|
||||
moves: &ArrBuf<MoveOffset>,
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: window.as_entire_binding(),
|
||||
resource: BindingResource::TextureViewArray(&tex_manager.views()),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: masks.buffer.as_entire_binding(),
|
||||
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: moves.buffer.as_entire_binding(),
|
||||
resource: masks.buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: Some("ui shared"),
|
||||
label: Some("ui rsc"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Layout for a list of one primitive's data. Every size in the ui is
|
||||
/// stated, so "is the buffer big enough for one entry?" is answered when
|
||||
/// the bind group is made; a `None` size is wgpu's to check on every draw.
|
||||
fn data_layout(device: &Device, stride: u64) -> BindGroupLayout {
|
||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: BufferSize::new(stride),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("ui primitive data"),
|
||||
})
|
||||
pub fn view_count(&self) -> usize {
|
||||
self.textures.view_count()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderLayer {
|
||||
fn new() -> Self {
|
||||
pub struct UiLimits {
|
||||
max_textures: u32,
|
||||
max_samplers: u32,
|
||||
}
|
||||
|
||||
impl Default for UiLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
primitives: Vec::new(),
|
||||
max_textures: 100000,
|
||||
max_samplers: 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListBuffers {
|
||||
fn new(device: &Device) -> Self {
|
||||
Self {
|
||||
instance: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||
"instance",
|
||||
),
|
||||
data: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
"primitive data",
|
||||
),
|
||||
group: None,
|
||||
bindings: Vec::new(),
|
||||
}
|
||||
impl UiLimits {
|
||||
pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 {
|
||||
self.max_textures + self.max_samplers
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
device: &Device,
|
||||
queue: &Queue,
|
||||
primitive: &PrimitivePipeline,
|
||||
list: &InstanceList,
|
||||
) {
|
||||
self.bindings.clear();
|
||||
primitive.render.instance_bindings(list, &mut self.bindings);
|
||||
self.instance.update(device, queue, list.instances());
|
||||
let resized = self.data.update(device, queue, list.data());
|
||||
if list.instances().is_empty() {
|
||||
self.group = None;
|
||||
} else if resized || self.group.is_none() {
|
||||
self.group = Some(device.create_bind_group(&BindGroupDescriptor {
|
||||
layout: &primitive.data_layout,
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: self.data.buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("ui primitive data"),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::module_source;
|
||||
use wgpu::naga::{
|
||||
front::wgsl,
|
||||
valid::{Capabilities, ValidationFlags, Validator},
|
||||
};
|
||||
|
||||
/// Every shader file, composed as the renderer composes it, parses and
|
||||
/// validates with no device -- so an edit that breaks one fails here and
|
||||
/// not in the first window opened.
|
||||
#[test]
|
||||
fn every_shader_validates() {
|
||||
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/src/render/shader");
|
||||
let mut checked = 0;
|
||||
for entry in std::fs::read_dir(dir).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
if path.extension().is_none_or(|e| e != "wgsl") || path.ends_with("prelude.wgsl") {
|
||||
continue;
|
||||
}
|
||||
let source = module_source(&std::fs::read_to_string(&path).unwrap());
|
||||
let module = wgsl::parse_str(&source)
|
||||
.unwrap_or_else(|e| panic!("{}: {}", path.display(), e.emit_to_string(&source)));
|
||||
Validator::new(ValidationFlags::all(), Capabilities::all())
|
||||
.validate(&module)
|
||||
.unwrap_or_else(|e| panic!("{}: {e:?}", path.display()));
|
||||
checked += 1;
|
||||
}
|
||||
assert!(checked > 0, "no shaders found in {dir}");
|
||||
pub fn max_binding_array_sampler_elements_per_shader_stage(&self) -> u32 {
|
||||
self.max_samplers
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
use wgpu::*;
|
||||
|
||||
use crate::{GlyphAtlas, UiData};
|
||||
|
||||
use super::{
|
||||
atlas::PAGE,
|
||||
primitive::{ListDraw, PrimitiveRender},
|
||||
texture::{default_sampler, sampled_group, sampled_layout, write_region},
|
||||
};
|
||||
|
||||
/// Draws glyphs from the atlas, which it owns: one array texture bound once
|
||||
/// for a whole list, since every glyph in it reads the same pages.
|
||||
pub struct GlyphRender {
|
||||
pages: GpuPages,
|
||||
layout: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
}
|
||||
|
||||
impl GlyphRender {
|
||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||
let layout = sampled_layout(device, TextureViewDimension::D2Array, "ui atlas");
|
||||
let sampler = default_sampler(device);
|
||||
Self {
|
||||
pages: GpuPages::new(device, queue, &layout, &sampler),
|
||||
layout,
|
||||
sampler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveRender for GlyphRender {
|
||||
fn layout(&self) -> Option<&BindGroupLayout> {
|
||||
Some(&self.layout)
|
||||
}
|
||||
|
||||
fn update(&mut self, ui: &mut UiData) {
|
||||
self.pages
|
||||
.update(&mut ui.text.atlas, &self.layout, &self.sampler);
|
||||
}
|
||||
|
||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
||||
pass.set_bind_group(2, self.pages.group(), &[]);
|
||||
pass.draw(0..4, 0..list.instances);
|
||||
}
|
||||
}
|
||||
|
||||
/// The glyph atlas on the GPU: one array texture whose layers are the pages
|
||||
/// `GlyphAtlas` packs.
|
||||
///
|
||||
/// One array rather than a texture per page because a layer index is ordinary
|
||||
/// Vulkan 1.0 / GLES sampling, where a `binding_array` would need
|
||||
/// `VK_EXT_descriptor_indexing`, which a real share of Android GPUs lack.
|
||||
pub struct GpuPages {
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
texture: Texture,
|
||||
group: BindGroup,
|
||||
}
|
||||
|
||||
impl GpuPages {
|
||||
pub fn new(
|
||||
device: &Device,
|
||||
queue: &Queue,
|
||||
layout: &BindGroupLayout,
|
||||
sampler: &Sampler,
|
||||
) -> Self {
|
||||
let texture = create_array(device, 1);
|
||||
Self {
|
||||
device: device.clone(),
|
||||
queue: queue.clone(),
|
||||
group: atlas_group(device, layout, &texture, sampler),
|
||||
texture,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, atlas: &mut GlyphAtlas, layout: &BindGroupLayout, sampler: &Sampler) {
|
||||
if atlas.page_count() > self.texture.depth_or_array_layers() {
|
||||
self.grow(atlas.page_count(), layout, sampler);
|
||||
}
|
||||
for (upload, page) in atlas.uploads() {
|
||||
let dst = TexelCopyTextureInfo {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d {
|
||||
x: upload.rect.x,
|
||||
y: upload.rect.y,
|
||||
z: upload.layer,
|
||||
},
|
||||
aspect: TextureAspect::All,
|
||||
};
|
||||
write_region(&self.queue, dst, page, upload.rect);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group(&self) -> &BindGroup {
|
||||
&self.group
|
||||
}
|
||||
|
||||
/// Doubles until `needed` fits and copies the old layers across GPU side.
|
||||
/// The new texture stales the group, so that is rebuilt here.
|
||||
fn grow(&mut self, needed: u32, layout: &BindGroupLayout, sampler: &Sampler) {
|
||||
let old = self.texture.depth_or_array_layers();
|
||||
let mut layers = old;
|
||||
while layers < needed {
|
||||
layers *= 2;
|
||||
}
|
||||
let texture = create_array(&self.device, layers);
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&CommandEncoderDescriptor {
|
||||
label: Some("atlas grow"),
|
||||
});
|
||||
encoder.copy_texture_to_texture(
|
||||
self.texture.as_image_copy(),
|
||||
texture.as_image_copy(),
|
||||
Extent3d {
|
||||
width: PAGE,
|
||||
height: PAGE,
|
||||
depth_or_array_layers: old,
|
||||
},
|
||||
);
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
self.group = atlas_group(&self.device, layout, &texture, sampler);
|
||||
self.texture = texture;
|
||||
}
|
||||
}
|
||||
|
||||
fn atlas_group(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
texture: &Texture,
|
||||
sampler: &Sampler,
|
||||
) -> BindGroup {
|
||||
let view = texture.create_view(&TextureViewDescriptor {
|
||||
dimension: Some(TextureViewDimension::D2Array),
|
||||
..Default::default()
|
||||
});
|
||||
sampled_group(device, layout, &view, sampler, "ui atlas")
|
||||
}
|
||||
|
||||
fn create_array(device: &Device, layers: u32) -> Texture {
|
||||
device.create_texture(&TextureDescriptor {
|
||||
label: Some("glyph atlas"),
|
||||
size: Extent3d {
|
||||
width: PAGE,
|
||||
height: PAGE,
|
||||
depth_or_array_layers: layers,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
})
|
||||
}
|
||||
+205
-325
@@ -1,321 +1,179 @@
|
||||
use std::{any::TypeId, marker::PhantomData};
|
||||
|
||||
use crate::{
|
||||
Color, TextureHandle, UiData, UiRegion, WidgetId,
|
||||
layout::{Color, UiRegion, WidgetId},
|
||||
render::{
|
||||
data::{MaskIdx, MoveIdx, PrimitiveInstance},
|
||||
page::GlyphRender,
|
||||
texture::ImageRender,
|
||||
ArrBuf,
|
||||
data::{MaskIdx, PrimitiveInstance},
|
||||
},
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
use bytemuck::Pod;
|
||||
use wgpu::{BindGroupLayout, Device, Queue, RenderPass};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use wgpu::*;
|
||||
|
||||
/// One instance of a primitive, laid out as the struct its shader reads.
|
||||
///
|
||||
/// The type carries its own shader, so drawing one is all the wiring it needs:
|
||||
/// its list, free list, buffers and pipeline follow from being registered.
|
||||
pub trait Primitive: Pod + 'static {
|
||||
/// Compiled after `prelude.wgsl`, which states what it declares and what
|
||||
/// it is given.
|
||||
const WGSL: &'static str;
|
||||
|
||||
/// Made once, the first time the renderer sees this primitive. It owns
|
||||
/// whatever the shader samples and records the primitive's own draws; the
|
||||
/// default owns nothing and draws every instance in one call.
|
||||
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let _ = (device, queue);
|
||||
Box::new(Instanced)
|
||||
}
|
||||
}
|
||||
|
||||
/// The renderer's half of a primitive: what it samples, what it uploads, and
|
||||
/// what draws it records.
|
||||
///
|
||||
/// Everything a draw shares -- the pipeline, the window and masks, the list's
|
||||
/// own data and instance buffer -- is set before this is called. What is left
|
||||
/// is what only this primitive knows: its group 2, and how many draws its
|
||||
/// instances are.
|
||||
pub trait PrimitiveRender {
|
||||
/// The layout its shader reads at group 2. `None` for a primitive whose
|
||||
/// shader samples nothing, whose pipeline then has no group 2 at all.
|
||||
fn layout(&self) -> Option<&BindGroupLayout> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Uploads whatever this primitive owns, once a frame, before any draw.
|
||||
fn update(&mut self, ui: &mut UiData) {
|
||||
let _ = ui;
|
||||
}
|
||||
|
||||
/// Keeps what the primitive needs per instance at draw time, read from
|
||||
/// the list's own data. A primitive that binds nothing per instance --
|
||||
/// most of them -- leaves this empty and draws in one call.
|
||||
fn instance_bindings(&self, list: &InstanceList, out: &mut Vec<u32>) {
|
||||
let _ = (list, out);
|
||||
}
|
||||
|
||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>);
|
||||
}
|
||||
|
||||
/// What a `PrimitiveRender` draws: this list's instances, and whatever
|
||||
/// `instance_bindings` kept for them.
|
||||
pub struct ListDraw<'a> {
|
||||
pub instances: u32,
|
||||
pub bindings: &'a [u32],
|
||||
}
|
||||
|
||||
/// The default: nothing sampled, every instance in one call.
|
||||
pub struct Instanced;
|
||||
|
||||
impl PrimitiveRender for Instanced {
|
||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
||||
pass.draw(0..4, 0..list.instances);
|
||||
}
|
||||
}
|
||||
|
||||
/// Which registered primitive an instance is.
|
||||
pub struct PrimitiveKind<P> {
|
||||
id: u32,
|
||||
_p: PhantomData<fn(P)>,
|
||||
}
|
||||
|
||||
impl<P> PrimitiveKind<P> {
|
||||
fn new(id: u32) -> Self {
|
||||
Self {
|
||||
id,
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Clone for PrimitiveKind<P> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Copy for PrimitiveKind<P> {}
|
||||
|
||||
/// Every primitive a ui can draw, in the order they were first drawn.
|
||||
#[derive(Default)]
|
||||
pub struct PrimitiveRegistry {
|
||||
kinds: Vec<PrimitiveSource>,
|
||||
ids: HashMap<TypeId, u32>,
|
||||
}
|
||||
|
||||
pub struct PrimitiveSource {
|
||||
pub wgsl: &'static str,
|
||||
pub label: &'static str,
|
||||
/// Size of one instance's entry, stated as the data binding's minimum.
|
||||
pub stride: u64,
|
||||
pub render: fn(&Device, &Queue) -> Box<dyn PrimitiveRender>,
|
||||
}
|
||||
|
||||
impl PrimitiveRegistry {
|
||||
/// Registers `P` if this is the first time it has been drawn.
|
||||
pub fn kind<P: Primitive>(&mut self) -> PrimitiveKind<P> {
|
||||
let Self { kinds, ids } = self;
|
||||
let id = *ids.entry(TypeId::of::<P>()).or_insert_with(|| {
|
||||
kinds.push(PrimitiveSource {
|
||||
wgsl: P::WGSL,
|
||||
label: std::any::type_name::<P>(),
|
||||
stride: size_of::<P>() as u64,
|
||||
render: P::render,
|
||||
});
|
||||
kinds.len() as u32 - 1
|
||||
});
|
||||
PrimitiveKind::new(id)
|
||||
}
|
||||
|
||||
pub fn sources(&self) -> &[PrimitiveSource] {
|
||||
&self.kinds
|
||||
}
|
||||
}
|
||||
|
||||
/// One registered primitive's instances in one layer. Everything per-instance
|
||||
/// rides here, so it stays in step through a `swap_remove`.
|
||||
pub struct InstanceList {
|
||||
pub struct Primitives {
|
||||
instances: Vec<PrimitiveInstance>,
|
||||
/// The widget each instance belongs to, for renumbering its handles.
|
||||
assoc: Vec<WidgetId>,
|
||||
data: PrimitiveData,
|
||||
free: Vec<usize>,
|
||||
/// `stride` bytes of the primitive's own data per instance.
|
||||
data: Vec<u8>,
|
||||
/// From the type the list was made for, so a write is never checked.
|
||||
stride: usize,
|
||||
}
|
||||
|
||||
impl InstanceList {
|
||||
fn new<P: Primitive>() -> Self {
|
||||
Self {
|
||||
instances: Vec::new(),
|
||||
assoc: Vec::new(),
|
||||
free: Vec::new(),
|
||||
data: Vec::new(),
|
||||
stride: size_of::<P>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instances(&self) -> &[PrimitiveInstance] {
|
||||
&self.instances
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn stride(&self) -> usize {
|
||||
self.stride
|
||||
}
|
||||
|
||||
fn push(&mut self, id: WidgetId, inst: PrimitiveInstance, data: &[u8]) -> usize {
|
||||
if let Some(i) = self.free.pop() {
|
||||
self.instances[i] = inst;
|
||||
self.assoc[i] = id;
|
||||
self.data[i * self.stride..][..self.stride].copy_from_slice(data);
|
||||
i
|
||||
} else {
|
||||
let i = self.instances.len();
|
||||
self.instances.push(inst);
|
||||
self.assoc.push(id);
|
||||
self.data.extend_from_slice(data);
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
fn free(&mut self, i: usize) -> MaskIdx {
|
||||
self.free.push(i);
|
||||
self.instances[i].mask_idx
|
||||
}
|
||||
|
||||
fn apply_free(&mut self, kind: u32) -> impl Iterator<Item = PrimitiveChange> {
|
||||
self.free.sort_by(|a, b| b.cmp(a));
|
||||
let instances = &mut self.instances;
|
||||
let assoc = &mut self.assoc;
|
||||
let data = &mut self.data;
|
||||
let stride = self.stride;
|
||||
self.free.drain(..).filter_map(move |i| {
|
||||
instances.swap_remove(i);
|
||||
assoc.swap_remove(i);
|
||||
let last = instances.len();
|
||||
data.copy_within(last * stride..(last + 1) * stride, i * stride);
|
||||
data.truncate(last * stride);
|
||||
if i == last {
|
||||
return None;
|
||||
}
|
||||
let id = assoc[i];
|
||||
Some(PrimitiveChange {
|
||||
id,
|
||||
kind,
|
||||
old: last,
|
||||
new: i,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything one layer draws, one list per registered primitive.
|
||||
pub struct LayerDraws {
|
||||
/// `None` until this layer draws that primitive, because only the write
|
||||
/// knows the type the list is for.
|
||||
primitives: Vec<Option<InstanceList>>,
|
||||
pub updated: bool,
|
||||
}
|
||||
|
||||
impl Default for LayerDraws {
|
||||
impl Default for Primitives {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
primitives: Vec::new(),
|
||||
instances: Default::default(),
|
||||
assoc: Default::default(),
|
||||
data: Default::default(),
|
||||
free: Vec::new(),
|
||||
updated: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerDraws {
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
PrimitiveInst {
|
||||
kind,
|
||||
id,
|
||||
primitive,
|
||||
region,
|
||||
mask_idx,
|
||||
move_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
// Grown on first use rather than sized from the registry, which a
|
||||
// layer cannot see.
|
||||
if self.primitives.len() <= kind.id as usize {
|
||||
self.primitives.resize_with(kind.id as usize + 1, || None);
|
||||
pub trait Primitive: Pod {
|
||||
const BINDING: u32;
|
||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
|
||||
}
|
||||
|
||||
macro_rules! primitives {
|
||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
||||
#[derive(Default)]
|
||||
pub struct PrimitiveData {
|
||||
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
||||
}
|
||||
let inst_idx = self.primitives[kind.id as usize]
|
||||
.get_or_insert_with(InstanceList::new::<P>)
|
||||
.push(
|
||||
id,
|
||||
PrimitiveInstance {
|
||||
region,
|
||||
mask_idx,
|
||||
move_idx,
|
||||
},
|
||||
bytemuck::bytes_of(&primitive),
|
||||
);
|
||||
PrimitiveHandle {
|
||||
layer,
|
||||
kind: kind.id,
|
||||
inst_idx,
|
||||
|
||||
pub struct PrimitiveBuffers {
|
||||
$($name: ArrBuf<$ty>,)*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primitives(&self) -> &[Option<InstanceList>] {
|
||||
&self.primitives
|
||||
}
|
||||
impl PrimitiveBuffers {
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
|
||||
$(self.$name.update(device, queue, &data.$name);)*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||
self.primitives
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter_map(|(kind, list)| Some((kind as u32, list.as_mut()?)))
|
||||
.flat_map(|(kind, list)| list.apply_free(kind))
|
||||
}
|
||||
impl PrimitiveBuffers {
|
||||
pub const LEN: usize = primitives!(@count $($name)*);
|
||||
pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] {
|
||||
[
|
||||
$((<$ty>::BINDING, &self.$name.buffer),)*
|
||||
]
|
||||
}
|
||||
pub fn new(device: &Device) -> Self {
|
||||
Self {
|
||||
$($name: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
stringify!($name),
|
||||
),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self.updated = true;
|
||||
self.list(h).free(h.inst_idx)
|
||||
}
|
||||
impl PrimitiveData {
|
||||
pub fn clear(&mut self) {
|
||||
$(self.$name.clear();)*
|
||||
}
|
||||
pub fn free(&mut self, binding: u32, idx: usize) {
|
||||
match binding {
|
||||
$(<$ty>::BINDING => self.$name.free(idx),)*
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||
self.updated = true;
|
||||
&mut self.list(h).instances[h.inst_idx].region
|
||||
}
|
||||
|
||||
/// A handle is only ever made by `write`, which is what created the list.
|
||||
fn list(&mut self, h: &PrimitiveHandle) -> &mut InstanceList {
|
||||
self.primitives[h.kind as usize]
|
||||
.as_mut()
|
||||
.expect("handle names a primitive this layer never drew")
|
||||
}
|
||||
$(
|
||||
unsafe impl bytemuck::Pod for $ty {}
|
||||
unsafe impl bytemuck::Zeroable for $ty {}
|
||||
impl Primitive for $ty {
|
||||
const BINDING: u32 = $binding;
|
||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self> {
|
||||
&mut data.$name
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t),+) };
|
||||
(@count $t:tt) => { 1 };
|
||||
}
|
||||
|
||||
pub struct PrimitiveInst<P> {
|
||||
pub kind: PrimitiveKind<P>,
|
||||
pub id: WidgetId,
|
||||
pub primitive: P,
|
||||
pub region: UiRegion,
|
||||
pub mask_idx: MaskIdx,
|
||||
pub move_idx: MoveIdx,
|
||||
}
|
||||
|
||||
impl Primitives {
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
PrimitiveInst {
|
||||
id,
|
||||
primitive,
|
||||
region,
|
||||
mask_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
let vec = P::vec(&mut self.data);
|
||||
let i = vec.add(primitive);
|
||||
let inst = PrimitiveInstance {
|
||||
region,
|
||||
idx: i as u32,
|
||||
mask_idx,
|
||||
binding: P::BINDING,
|
||||
};
|
||||
let inst_i = if let Some(i) = self.free.pop() {
|
||||
self.instances[i] = inst;
|
||||
self.assoc[i] = id;
|
||||
i
|
||||
} else {
|
||||
let i = self.instances.len();
|
||||
self.instances.push(inst);
|
||||
self.assoc.push(id);
|
||||
i
|
||||
};
|
||||
PrimitiveHandle::new::<P>(layer, inst_i, i)
|
||||
}
|
||||
|
||||
/// returns (old index, new index)
|
||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||
self.free.sort_by(|a, b| b.cmp(a));
|
||||
self.free.drain(..).filter_map(|i| {
|
||||
self.instances.swap_remove(i);
|
||||
self.assoc.swap_remove(i);
|
||||
if i == self.instances.len() {
|
||||
return None;
|
||||
}
|
||||
let id = self.assoc[i];
|
||||
let old = self.instances.len();
|
||||
Some(PrimitiveChange { id, old, new: i })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self.updated = true;
|
||||
self.data.free(h.binding, h.data_idx);
|
||||
self.free.push(h.inst_idx);
|
||||
self.instances[h.inst_idx].mask_idx
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &PrimitiveData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
||||
&self.instances
|
||||
}
|
||||
|
||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||
self.updated = true;
|
||||
&mut self.instances[h.inst_idx].region
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimitiveChange {
|
||||
pub id: WidgetId,
|
||||
/// Which registered primitive's list moved, since they index separately.
|
||||
pub kind: u32,
|
||||
pub old: usize,
|
||||
pub new: usize,
|
||||
}
|
||||
@@ -323,12 +181,29 @@ pub struct PrimitiveChange {
|
||||
#[derive(Debug)]
|
||||
pub struct PrimitiveHandle {
|
||||
pub layer: usize,
|
||||
pub kind: u32,
|
||||
pub inst_idx: usize,
|
||||
pub data_idx: usize,
|
||||
pub binding: u32,
|
||||
}
|
||||
|
||||
impl PrimitiveHandle {
|
||||
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self {
|
||||
Self {
|
||||
layer,
|
||||
inst_idx,
|
||||
data_idx,
|
||||
binding: P::BINDING,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
primitives!(
|
||||
rects: RectPrimitive => 0,
|
||||
textures: TexturePrimitive => 1,
|
||||
);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct RectPrimitive {
|
||||
pub color: Color<u8>,
|
||||
pub radius: f32,
|
||||
@@ -336,10 +211,6 @@ pub struct RectPrimitive {
|
||||
pub inner_radius: f32,
|
||||
}
|
||||
|
||||
impl Primitive for RectPrimitive {
|
||||
const WGSL: &'static str = include_str!("shader/rect.wgsl");
|
||||
}
|
||||
|
||||
impl RectPrimitive {
|
||||
pub fn color(color: Color<u8>) -> Self {
|
||||
Self {
|
||||
@@ -351,51 +222,60 @@ impl RectPrimitive {
|
||||
}
|
||||
}
|
||||
|
||||
/// `color` is multiplied by the atlas alpha for a mask glyph; a colour glyph
|
||||
/// takes the texel unchanged, which `GlyphEntry::IS_COLORED` selects.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct GlyphPrimitive {
|
||||
pub uv_min: Vec2,
|
||||
pub uv_max: Vec2,
|
||||
/// Which atlas array layer this glyph is on.
|
||||
pub layer: u32,
|
||||
pub color: Color<u8>,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
// Manual rather than derived: `Vec2`'s alignment leaves four bytes of padding
|
||||
// here, which is how WGSL lays the struct out.
|
||||
unsafe impl bytemuck::Pod for GlyphPrimitive {}
|
||||
unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
|
||||
impl Primitive for GlyphPrimitive {
|
||||
const WGSL: &'static str = include_str!("shader/glyph.wgsl");
|
||||
|
||||
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender> {
|
||||
Box::new(GlyphRender::new(device, queue))
|
||||
}
|
||||
}
|
||||
|
||||
/// One drawn image. Its shader reads nothing per instance; the slot names the
|
||||
/// texture to bind for it.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct TexturePrimitive {
|
||||
pub slot: u32,
|
||||
pub view_idx: u32,
|
||||
pub sampler_idx: u32,
|
||||
}
|
||||
|
||||
impl Primitive for TexturePrimitive {
|
||||
const WGSL: &'static str = include_str!("shader/texture.wgsl");
|
||||
|
||||
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender> {
|
||||
Box::new(ImageRender::new(device, queue))
|
||||
}
|
||||
pub struct PrimitiveVec<T> {
|
||||
vec: Vec<T>,
|
||||
free: Vec<usize>,
|
||||
}
|
||||
|
||||
impl From<&TextureHandle> for TexturePrimitive {
|
||||
fn from(handle: &TextureHandle) -> Self {
|
||||
impl<T> PrimitiveVec<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
slot: handle.slot(),
|
||||
vec: Vec::new(),
|
||||
free: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn add(&mut self, t: T) -> usize {
|
||||
if let Some(i) = self.free.pop() {
|
||||
self.vec[i] = t;
|
||||
i
|
||||
} else {
|
||||
let i = self.vec.len();
|
||||
self.vec.push(t);
|
||||
i
|
||||
}
|
||||
}
|
||||
pub fn free(&mut self, i: usize) {
|
||||
self.free.push(i);
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.free.clear();
|
||||
self.vec.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for PrimitiveVec<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for PrimitiveVec<T> {
|
||||
type Target = Vec<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.vec
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for PrimitiveVec<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.vec
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
const RECT: u32 = 0u;
|
||||
const TEXTURE: u32 = 1u;
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> window: WindowUniform;
|
||||
@group(1) @binding(RECT)
|
||||
var<storage> rects: array<Rect>;
|
||||
@group(1) @binding(TEXTURE)
|
||||
var<storage> textures: array<TextureInfo>;
|
||||
|
||||
struct Rect {
|
||||
color: u32,
|
||||
radius: f32,
|
||||
thickness: f32,
|
||||
inner_radius: f32,
|
||||
}
|
||||
|
||||
struct TextureInfo {
|
||||
view_idx: u32,
|
||||
sampler_idx: u32,
|
||||
}
|
||||
|
||||
struct Mask {
|
||||
x: UiSpan,
|
||||
y: UiSpan,
|
||||
}
|
||||
|
||||
struct UiSpan {
|
||||
start: UiScalar,
|
||||
end: UiScalar,
|
||||
}
|
||||
|
||||
struct UiScalar {
|
||||
rel: f32,
|
||||
abs: f32,
|
||||
}
|
||||
|
||||
struct UiVec2 {
|
||||
rel: vec2<f32>,
|
||||
abs: vec2<f32>,
|
||||
}
|
||||
|
||||
@group(2) @binding(0)
|
||||
var views: binding_array<texture_2d<f32>>;
|
||||
@group(2) @binding(1)
|
||||
var samplers: binding_array<sampler>;
|
||||
@group(2) @binding(2)
|
||||
var<storage> masks: array<Mask>;
|
||||
|
||||
struct WindowUniform {
|
||||
dim: vec2<f32>,
|
||||
};
|
||||
|
||||
struct InstanceInput {
|
||||
@location(0) x_start: vec2<f32>,
|
||||
@location(1) x_end: vec2<f32>,
|
||||
@location(2) y_start: vec2<f32>,
|
||||
@location(3) y_end: vec2<f32>,
|
||||
@location(4) binding: u32,
|
||||
@location(5) idx: u32,
|
||||
@location(6) mask_idx: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@location(0) top_left: vec2<f32>,
|
||||
@location(1) bot_right: vec2<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
@location(3) binding: u32,
|
||||
@location(4) idx: u32,
|
||||
@location(5) mask_idx: u32,
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Region {
|
||||
pos: vec2<f32>,
|
||||
uv: vec2<f32>,
|
||||
top_left: vec2<f32>,
|
||||
bot_right: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vi: u32,
|
||||
in: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
|
||||
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
||||
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
||||
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
||||
|
||||
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
||||
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
||||
let size = bot_right - top_left;
|
||||
|
||||
let uv = vec2<f32>(
|
||||
f32(vi % 2u),
|
||||
f32(vi / 2u)
|
||||
);
|
||||
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||
out.uv = uv;
|
||||
out.binding = in.binding;
|
||||
out.idx = in.idx;
|
||||
out.top_left = top_left;
|
||||
out.bot_right = bot_right;
|
||||
out.mask_idx = in.mask_idx;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(
|
||||
in: VertexOutput
|
||||
) -> @location(0) vec4<f32> {
|
||||
let pos = in.clip_position.xy;
|
||||
let region = Region(pos, in.uv, in.top_left, in.bot_right);
|
||||
let i = in.idx;
|
||||
var color: vec4<f32>;
|
||||
switch in.binding {
|
||||
case RECT: {
|
||||
color = draw_rounded_rect(region, rects[i]);
|
||||
}
|
||||
case TEXTURE: {
|
||||
color = draw_texture(region, textures[i]);
|
||||
}
|
||||
default: {
|
||||
color = vec4(1.0, 0.0, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
if in.mask_idx != 4294967295u {
|
||||
let mask = masks[in.mask_idx];
|
||||
let tl = UiVec2(vec2(mask.x.start.rel, mask.y.start.rel), vec2(mask.x.start.abs, mask.y.start.abs));
|
||||
let br = UiVec2(vec2(mask.x.end.rel, mask.y.end.rel), vec2(mask.x.end.abs, mask.y.end.abs));
|
||||
|
||||
let top_left = floor(tl.rel * window.dim) + floor(tl.abs);
|
||||
let bot_right = floor(br.rel * window.dim) + floor(br.abs);
|
||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
||||
color *= 0.0;
|
||||
}
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
// TODO: this seems really inefficient (per frag indexing)?
|
||||
fn draw_texture(region: Region, info: TextureInfo) -> vec4<f32> {
|
||||
return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv);
|
||||
}
|
||||
|
||||
fn draw_rounded_rect(region: Region, rect: Rect) -> vec4<f32> {
|
||||
var color = unpack4x8unorm(rect.color);
|
||||
|
||||
let edge = 0.5;
|
||||
|
||||
let size = region.bot_right - region.top_left;
|
||||
let corner = size / 2.0;
|
||||
let center = region.top_left + corner;
|
||||
|
||||
let dist = distance_from_rect(region.pos, center, corner, rect.radius);
|
||||
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
||||
|
||||
if rect.thickness > 0.0 {
|
||||
let dist2 = distance_from_rect(region.pos, center, corner - rect.thickness, rect.inner_radius);
|
||||
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
||||
// vec from center to pixel
|
||||
let p = pixel_pos - rect_center;
|
||||
// vec from inner rect corner to pixel
|
||||
let q = abs(p) - (rect_corner - radius);
|
||||
return length(max(q, vec2(0.0))) - radius;
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Matches `GlyphEntry::IS_COLORED`.
|
||||
const COLORED: u32 = 1u;
|
||||
|
||||
// The glyph atlas, whose array layers are its pages.
|
||||
@group(2) @binding(0)
|
||||
var atlas: texture_2d_array<f32>;
|
||||
@group(2) @binding(1)
|
||||
var samp: sampler;
|
||||
|
||||
struct GlyphInfo {
|
||||
uv_min: vec2<f32>,
|
||||
uv_max: vec2<f32>,
|
||||
// Which layer of the atlas array this glyph's page is.
|
||||
layer: u32,
|
||||
color: u32,
|
||||
flags: u32,
|
||||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<storage> glyphs: array<GlyphInfo>;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let g = glyphs[in.idx];
|
||||
let uv = mix(g.uv_min, g.uv_max, in.uv);
|
||||
let texel = textureSample(atlas, samp, uv, i32(g.layer));
|
||||
if (g.flags & COLORED) != 0u {
|
||||
return masked(in, texel);
|
||||
}
|
||||
var color = unpack4x8unorm(g.color);
|
||||
color.a *= texel.a;
|
||||
return masked(in, color);
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
// Prepended to every primitive's shader, which declares its own instance data
|
||||
// as `var<storage> <name>: array<T>` at group 1 binding 0, and an `fs_main`
|
||||
// shading one instance of it. What it samples, if anything, is bound at group
|
||||
// 2: the texture at binding 0 and the sampler at binding 1.
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> window: WindowUniform;
|
||||
@group(0) @binding(1)
|
||||
var<storage> masks: array<Mask>;
|
||||
@group(0) @binding(2)
|
||||
var<storage> move_offsets: array<MoveOffset>;
|
||||
|
||||
struct WindowUniform {
|
||||
dim: vec2<f32>,
|
||||
};
|
||||
|
||||
struct Mask {
|
||||
x: RawSpan,
|
||||
y: RawSpan,
|
||||
move_idx: u32,
|
||||
}
|
||||
|
||||
struct MoveOffset {
|
||||
x: RawSpan,
|
||||
y: RawSpan,
|
||||
parent: u32,
|
||||
}
|
||||
|
||||
// `PX_STEP` and `REL_STEP` are prepended from `iris_core`'s own constants:
|
||||
// what it stores is a whole count of each, both powers of two, so decoding
|
||||
// is exact and the number here is the number the CPU decided.
|
||||
|
||||
// Every coordinate the CPU decided is a whole count of `PX_STEP`, so one that
|
||||
// composes to within half a step of a pixel boundary is on that boundary and
|
||||
// belongs to the pixel above it. Flooring the product instead drops a pixel
|
||||
// wherever a fraction divides a window exactly: a fifth of 1920 comes out of
|
||||
// `REL_STEP` as 383.99998, and five tabs each lose their last column.
|
||||
//
|
||||
// Taken over the whole coordinate, fraction and pixels summed, since a floor
|
||||
// does not distribute over a sum: floored apart, a half of one and a half of
|
||||
// the other lose the pixel the two together make.
|
||||
fn snap_floor(v: vec2<f32>) -> vec2<f32> {
|
||||
return floor(v + PX_STEP * 0.5);
|
||||
}
|
||||
|
||||
struct RawScalar {
|
||||
rel: i32,
|
||||
px: i32,
|
||||
}
|
||||
|
||||
struct RawSpan {
|
||||
start: RawScalar,
|
||||
end: RawScalar,
|
||||
}
|
||||
|
||||
fn scalar_of(raw: RawScalar) -> Len {
|
||||
return Len(f32(raw.rel) * REL_STEP, f32(raw.px) * PX_STEP);
|
||||
}
|
||||
|
||||
fn span_of(raw: RawSpan) -> UiSpan {
|
||||
return UiSpan(scalar_of(raw.start), scalar_of(raw.end));
|
||||
}
|
||||
|
||||
fn scalar_of_pair(raw: vec2<i32>) -> Len {
|
||||
return Len(f32(raw.x) * REL_STEP, f32(raw.y) * PX_STEP);
|
||||
}
|
||||
|
||||
struct Region {
|
||||
x: UiSpan,
|
||||
y: UiSpan,
|
||||
}
|
||||
|
||||
const MOVE_NONE: u32 = 4294967295u;
|
||||
// Keep in step with `iris_core::CHAIN_LIMIT`. It bounds a malformed cycle
|
||||
// rather than any real tree, and the CPU walk uses the same number so both
|
||||
// resolve a deep one the same way.
|
||||
const CHAIN_LIMIT: u32 = 64u;
|
||||
|
||||
// The same expression `Len::within` uses, in floats rather than on the
|
||||
// CPU's grid: a move is resolved here so that scrolling a subtree writes one
|
||||
// entry instead of walking it. What has to hold is that this agrees with
|
||||
// itself frame to frame, not that it matches the CPU to the last bit.
|
||||
fn scalar_within(s: Len, p: UiSpan) -> Len {
|
||||
return Len(
|
||||
p.start.rel + (p.end.rel - p.start.rel) * s.rel,
|
||||
s.px + (p.start.px + (p.end.px - p.start.px) * s.rel),
|
||||
);
|
||||
}
|
||||
|
||||
fn span_within(s: UiSpan, p: UiSpan) -> UiSpan {
|
||||
return UiSpan(scalar_within(s.start, p), scalar_within(s.end, p));
|
||||
}
|
||||
|
||||
fn resolve_move(idx: u32, local: Region) -> Region {
|
||||
var r = local;
|
||||
var at = idx;
|
||||
for (var step = 0u; step < CHAIN_LIMIT; step++) {
|
||||
if at == MOVE_NONE {
|
||||
break;
|
||||
}
|
||||
let entry = move_offsets[at];
|
||||
r = Region(span_within(r.x, span_of(entry.x)), span_within(r.y, span_of(entry.y)));
|
||||
at = entry.parent;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
struct UiSpan {
|
||||
start: Len,
|
||||
end: Len,
|
||||
}
|
||||
|
||||
struct Len {
|
||||
rel: f32,
|
||||
px: f32,
|
||||
}
|
||||
|
||||
struct InstanceInput {
|
||||
@location(0) x_start: vec2<i32>,
|
||||
@location(1) x_end: vec2<i32>,
|
||||
@location(2) y_start: vec2<i32>,
|
||||
@location(3) y_end: vec2<i32>,
|
||||
@location(4) mask_idx: u32,
|
||||
@location(5) move_idx: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@location(0) top_left: vec2<f32>,
|
||||
@location(1) bot_right: vec2<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
@location(3) @interpolate(flat) mask_idx: u32,
|
||||
@location(4) @interpolate(flat) idx: u32,
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vi: u32,
|
||||
@builtin(instance_index) ii: u32,
|
||||
in: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let local = Region(
|
||||
UiSpan(scalar_of_pair(in.x_start), scalar_of_pair(in.x_end)),
|
||||
UiSpan(scalar_of_pair(in.y_start), scalar_of_pair(in.y_end)),
|
||||
);
|
||||
let r = resolve_move(in.move_idx, local);
|
||||
let top_left_rel = vec2(r.x.start.rel, r.y.start.rel);
|
||||
let top_left_px = vec2(r.x.start.px, r.y.start.px);
|
||||
let bot_right_rel = vec2(r.x.end.rel, r.y.end.rel);
|
||||
let bot_right_px = vec2(r.x.end.px, r.y.end.px);
|
||||
|
||||
let top_left = snap_floor(top_left_rel * window.dim + top_left_px);
|
||||
let bot_right = snap_floor(bot_right_rel * window.dim + bot_right_px);
|
||||
let size = bot_right - top_left;
|
||||
|
||||
let uv = vec2<f32>(
|
||||
f32(vi % 2u),
|
||||
f32(vi / 2u)
|
||||
);
|
||||
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||
out.uv = uv;
|
||||
out.top_left = top_left;
|
||||
out.bot_right = bot_right;
|
||||
out.mask_idx = in.mask_idx;
|
||||
out.idx = ii;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
|
||||
if in.mask_idx == 4294967295u {
|
||||
return color;
|
||||
}
|
||||
let mask = masks[in.mask_idx];
|
||||
// Its own chain, not the drawn primitive's, so a stationary viewport
|
||||
// clips content that moves inside it.
|
||||
let m = resolve_move(mask.move_idx, Region(span_of(mask.x), span_of(mask.y)));
|
||||
let tl = vec2(m.x.start.rel, m.y.start.rel);
|
||||
let tl_px = vec2(m.x.start.px, m.y.start.px);
|
||||
let br = vec2(m.x.end.rel, m.y.end.rel);
|
||||
let br_px = vec2(m.x.end.px, m.y.end.px);
|
||||
|
||||
let top_left = snap_floor(tl * window.dim + tl_px);
|
||||
let bot_right = snap_floor(br * window.dim + br_px);
|
||||
let pos = in.clip_position.xy;
|
||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
||||
return color * 0.0;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
struct Rect {
|
||||
color: u32,
|
||||
radius: f32,
|
||||
thickness: f32,
|
||||
inner_radius: f32,
|
||||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<storage> rects: array<Rect>;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let rect = rects[in.idx];
|
||||
var color = unpack4x8unorm(rect.color);
|
||||
|
||||
let edge = 0.5;
|
||||
let size = in.bot_right - in.top_left;
|
||||
let corner = size / 2.0;
|
||||
let center = in.top_left + corner;
|
||||
let pos = in.clip_position.xy;
|
||||
|
||||
let dist = distance_from_rect(pos, center, corner, rect.radius);
|
||||
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
||||
|
||||
if rect.thickness > 0.0 {
|
||||
let dist2 = distance_from_rect(pos, center, corner - rect.thickness, rect.inner_radius);
|
||||
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
||||
}
|
||||
|
||||
return masked(in, color);
|
||||
}
|
||||
|
||||
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
||||
// vec from center to pixel
|
||||
let p = pixel_pos - rect_center;
|
||||
// vec from inner rect corner to pixel
|
||||
let q = abs(p) - (rect_corner - radius);
|
||||
return length(max(q, vec2(0.0))) - radius;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// The image this instance draws, bound for it alone.
|
||||
@group(2) @binding(0)
|
||||
var image: texture_2d<f32>;
|
||||
@group(2) @binding(1)
|
||||
var samp: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return masked(in, textureSample(image, samp, in.uv));
|
||||
}
|
||||
+84
-198
@@ -1,119 +1,59 @@
|
||||
use image::{DynamicImage, EncodableLayout, GenericImageView, RgbaImage};
|
||||
use image::{DynamicImage, EncodableLayout};
|
||||
use wgpu::{util::DeviceExt, *};
|
||||
|
||||
use crate::{
|
||||
PatchRect, TextureUpdate, Textures, UiData,
|
||||
render::{
|
||||
TexturePrimitive,
|
||||
primitive::{ListDraw, PrimitiveRender},
|
||||
},
|
||||
};
|
||||
use crate::layout::{TextureUpdate, Textures};
|
||||
|
||||
/// Draws standalone images, which it owns. Each is its own texture, so each
|
||||
/// instance binds its own and is a draw of its own.
|
||||
pub struct ImageRender {
|
||||
textures: GpuTextures,
|
||||
layout: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
}
|
||||
|
||||
impl ImageRender {
|
||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||
Self {
|
||||
textures: GpuTextures::new(device, queue),
|
||||
layout: sampled_layout(device, TextureViewDimension::D2, "ui image"),
|
||||
sampler: default_sampler(device),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveRender for ImageRender {
|
||||
fn layout(&self) -> Option<&BindGroupLayout> {
|
||||
Some(&self.layout)
|
||||
}
|
||||
|
||||
fn update(&mut self, ui: &mut UiData) {
|
||||
self.textures
|
||||
.update(&mut ui.textures, &self.layout, &self.sampler);
|
||||
}
|
||||
|
||||
fn instance_bindings(&self, list: &super::InstanceList, out: &mut Vec<u32>) {
|
||||
let slots = list
|
||||
.data()
|
||||
.chunks_exact(list.stride())
|
||||
.map(|data| bytemuck::pod_read_unaligned::<TexturePrimitive>(data).slot);
|
||||
out.extend(slots);
|
||||
}
|
||||
|
||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
||||
for (i, &slot) in list.bindings.iter().enumerate() {
|
||||
let Some(image) = self.textures.group(slot) else {
|
||||
continue;
|
||||
};
|
||||
pass.set_bind_group(2, image, &[]);
|
||||
pass.draw(0..4, i as u32..i as u32 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The standalone images a ui draws, each its own texture and bind group --
|
||||
/// unlike the glyph atlas in `super::page`, which is one array they share.
|
||||
pub struct GpuTextures {
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
slots: Vec<Option<ImageGpu>>,
|
||||
}
|
||||
|
||||
struct ImageGpu {
|
||||
/// Kept for `patch`, which needs the texture rather than the view.
|
||||
texture: Texture,
|
||||
group: BindGroup,
|
||||
views: Vec<TextureView>,
|
||||
view_count: usize,
|
||||
samplers: Vec<Sampler>,
|
||||
null_view: TextureView,
|
||||
no_views: Vec<TextureView>,
|
||||
}
|
||||
|
||||
impl GpuTextures {
|
||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||
Self {
|
||||
device: device.clone(),
|
||||
queue: queue.clone(),
|
||||
slots: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, textures: &mut Textures, layout: &BindGroupLayout, sampler: &Sampler) {
|
||||
pub fn update(&mut self, textures: &mut Textures) -> bool {
|
||||
let mut changed = false;
|
||||
for update in textures.updates() {
|
||||
changed = true;
|
||||
match update {
|
||||
TextureUpdate::Push(image) => {
|
||||
let image = self.create(image, layout, sampler);
|
||||
self.slots.push(Some(image));
|
||||
}
|
||||
TextureUpdate::Set(i, image) => {
|
||||
let image = self.create(image, layout, sampler);
|
||||
self.slots[i as usize] = Some(image);
|
||||
}
|
||||
TextureUpdate::Patch(i, rect, image) => self.patch(i, rect, image),
|
||||
TextureUpdate::PushFree => self.slots.push(None),
|
||||
TextureUpdate::SetFree => {}
|
||||
TextureUpdate::Free(i) => self.slots[i as usize] = None,
|
||||
TextureUpdate::Push(image) => self.push(image),
|
||||
TextureUpdate::Set(i, image) => self.set(i, image),
|
||||
TextureUpdate::SetFree => self.view_count += 1,
|
||||
TextureUpdate::Free(i) => self.free(i),
|
||||
TextureUpdate::PushFree => self.push_free(),
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
fn set(&mut self, i: u32, image: &DynamicImage) {
|
||||
self.view_count += 1;
|
||||
let view = self.create_view(image);
|
||||
self.views[i as usize] = view;
|
||||
}
|
||||
fn free(&mut self, i: u32) {
|
||||
self.view_count -= 1;
|
||||
self.views[i as usize] = self.null_view.clone();
|
||||
}
|
||||
fn push(&mut self, image: &DynamicImage) {
|
||||
self.view_count += 1;
|
||||
let view = self.create_view(image);
|
||||
self.views.push(view);
|
||||
}
|
||||
fn push_free(&mut self) {
|
||||
self.view_count += 1;
|
||||
self.views.push(self.null_view.clone());
|
||||
}
|
||||
|
||||
pub fn group(&self, slot: u32) -> Option<&BindGroup> {
|
||||
self.slots.get(slot as usize)?.as_ref().map(|i| &i.group)
|
||||
}
|
||||
|
||||
fn create(
|
||||
&self,
|
||||
image: &DynamicImage,
|
||||
layout: &BindGroupLayout,
|
||||
sampler: &Sampler,
|
||||
) -> ImageGpu {
|
||||
let rgba = image.to_rgba8();
|
||||
let (width, height) = rgba.dimensions();
|
||||
fn create_view(&self, image: &DynamicImage) -> TextureView {
|
||||
let image = image.to_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let texture = self.device.create_texture_with_data(
|
||||
&self.queue,
|
||||
&TextureDescriptor {
|
||||
label: Some("image"),
|
||||
label: None,
|
||||
size: Extent3d {
|
||||
width,
|
||||
height,
|
||||
@@ -123,119 +63,65 @@ impl GpuTextures {
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
|
||||
usage: TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
},
|
||||
wgt::TextureDataOrder::MipMajor,
|
||||
rgba.as_bytes(),
|
||||
image.as_bytes(),
|
||||
);
|
||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
||||
let group = sampled_group(&self.device, layout, &view, sampler, "ui image");
|
||||
ImageGpu { texture, group }
|
||||
texture.create_view(&TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
fn patch(&mut self, i: u32, rect: PatchRect, image: &DynamicImage) {
|
||||
let Some(Some(slot)) = self.slots.get(i as usize) else {
|
||||
return;
|
||||
};
|
||||
let dst = TexelCopyTextureInfo {
|
||||
texture: &slot.texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
z: 0,
|
||||
},
|
||||
aspect: TextureAspect::All,
|
||||
};
|
||||
match image.as_rgba8() {
|
||||
Some(rgba) => write_region(&self.queue, dst, rgba, rect),
|
||||
// The texture is rgba8, so any other layout has to be converted --
|
||||
// and converting the rectangle is cheaper than the whole image.
|
||||
None => {
|
||||
let sub = image
|
||||
.view(rect.x, rect.y, rect.width, rect.height)
|
||||
.to_image();
|
||||
write_region(&self.queue, dst, &sub, PatchRect { x: 0, y: 0, ..rect });
|
||||
}
|
||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||
let null_view = null_texture_view(device);
|
||||
Self {
|
||||
device: device.clone(),
|
||||
queue: queue.clone(),
|
||||
views: Vec::new(),
|
||||
samplers: vec![default_sampler(device)],
|
||||
no_views: vec![null_view.clone()],
|
||||
null_view,
|
||||
view_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_region(queue: &Queue, dst: TexelCopyTextureInfo, src: &RgbaImage, rect: PatchRect) {
|
||||
if rect.width == 0 || rect.height == 0 {
|
||||
return;
|
||||
pub fn views(&self) -> Vec<&TextureView> {
|
||||
if self.views.is_empty() {
|
||||
&self.no_views
|
||||
} else {
|
||||
&self.views
|
||||
}
|
||||
.iter()
|
||||
.by_ref()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn samplers(&self) -> Vec<&Sampler> {
|
||||
self.samplers.iter().by_ref().collect()
|
||||
}
|
||||
|
||||
pub fn view_count(&self) -> usize {
|
||||
self.view_count
|
||||
}
|
||||
let stride = src.width() * 4;
|
||||
queue.write_texture(
|
||||
dst,
|
||||
src.as_bytes(),
|
||||
TexelCopyBufferLayout {
|
||||
offset: (rect.y * stride + rect.x * 4) as u64,
|
||||
bytes_per_row: Some(stride),
|
||||
rows_per_image: Some(rect.height),
|
||||
},
|
||||
Extent3d {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// What a primitive that samples binds: a texture, and the sampler that reads
|
||||
/// it.
|
||||
pub fn sampled_group(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
view: &TextureView,
|
||||
sampler: &Sampler,
|
||||
label: &'static str,
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(view),
|
||||
pub fn null_texture_view(device: &Device) -> TextureView {
|
||||
device
|
||||
.create_texture(&TextureDescriptor {
|
||||
label: Some("null"),
|
||||
size: Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(sampler),
|
||||
},
|
||||
],
|
||||
label: Some(label),
|
||||
})
|
||||
}
|
||||
|
||||
/// The layout for one of those. The dimension differs -- the atlas is an
|
||||
/// array of pages and an image is not -- and nothing else does.
|
||||
pub fn sampled_layout(
|
||||
device: &Device,
|
||||
dimension: TextureViewDimension,
|
||||
label: &'static str,
|
||||
) -> BindGroupLayout {
|
||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
sample_type: TextureSampleType::Float { filterable: false },
|
||||
view_dimension: dimension,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some(label),
|
||||
})
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
})
|
||||
.create_view(&TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
pub fn default_sampler(device: &Device) -> Sampler {
|
||||
|
||||
@@ -21,25 +21,17 @@ impl<T: Pod> ArrBuf<T> {
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
/// Returns whether the `Buffer` was recreated, which stales any cached
|
||||
/// `BindGroup` holding it.
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) -> bool {
|
||||
let resized = self.len != data.len();
|
||||
if resized {
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) {
|
||||
if self.len != data.len() {
|
||||
self.len = data.len();
|
||||
self.buffer =
|
||||
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
||||
}
|
||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
||||
resized
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||
let mut size = size as u64;
|
||||
if usage.contains(BufferUsages::STORAGE) {
|
||||
// A binding cannot be empty or under the layout's minimum.
|
||||
size = size.max(std::mem::size_of::<T>() as u64);
|
||||
}
|
||||
device.create_buffer(&BufferDescriptor {
|
||||
@@ -49,4 +41,8 @@ impl<T: Pod> ArrBuf<T> {
|
||||
usage,
|
||||
})
|
||||
}
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
use crate::{
|
||||
Declared, LayerId, LayoutHolds, MaskIdx, MoveIdx, PlaceDesc, RegionAlign, RetainedPrimitive,
|
||||
Size, TextureHandle, UiRegion, UiVec2, WidgetId,
|
||||
};
|
||||
|
||||
/// What is kept of a widget its parent has asked about. `drawn` says whether
|
||||
/// it currently draws; one that does not is kept so that a change to it, or
|
||||
/// under it, still reaches whoever asked.
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveData {
|
||||
pub id: WidgetId,
|
||||
/// Where its drawing goes, in its region node's coordinates.
|
||||
pub placement: UiRegion,
|
||||
/// What a fraction declared or reported under this widget is a fraction
|
||||
/// of, as a length of the window.
|
||||
pub rel_base: UiVec2,
|
||||
/// Where its drawing was put, and where it was asked. The two differ
|
||||
/// where a container asks in one place and puts the answer in another --
|
||||
/// a row measures from its cursor and puts the child in its slot. Each
|
||||
/// carries the rel base that ask stated, so asking again from either is
|
||||
/// the same question it was.
|
||||
pub placed: PlaceDesc,
|
||||
pub asked: PlaceDesc,
|
||||
/// The box it was asked in, in the parent's region-node coordinates: the
|
||||
/// box its drawing was made in and the one its contract is about. Its
|
||||
/// drawing is placed elsewhere by re-expression, never by asking again.
|
||||
pub region: UiRegion,
|
||||
/// The measured answer and its dependencies. A hint-only dependency or
|
||||
/// a widget first encountered during placement has no measurement yet.
|
||||
pub answer: Option<Answer>,
|
||||
/// Asked more than once in its parent's last draw -- measured in one box
|
||||
/// and then asked in the one the parent decided. The parent's layout
|
||||
/// rests on the first answer and its drawing on the last, so only the
|
||||
/// parent can ask either again.
|
||||
pub re_asked: bool,
|
||||
/// What the widget reported, in window-unit lengths.
|
||||
pub size: Size,
|
||||
/// The window and region reads that this drawing holds for, and the
|
||||
/// rel base and region it pinned.
|
||||
pub holds: LayoutHolds,
|
||||
pub drawn: bool,
|
||||
pub parent: Option<WidgetId>,
|
||||
/// How far down the tree it was drawn, the root being 1. Carried down a
|
||||
/// draw rather than worked out by walking up, so it is right for every
|
||||
/// widget a frame visits and cannot drift while one is being drawn.
|
||||
pub depth: usize,
|
||||
pub textures: Vec<TextureHandle>,
|
||||
/// Its primitives, each keeping the box it was written in -- in this
|
||||
/// widget's placement coordinates, which is what a move recomposes from.
|
||||
pub primitives: Vec<RetainedPrimitive>,
|
||||
/// An owned mask holds one reference independently of its primitives.
|
||||
pub mask_region: Option<UiRegion>,
|
||||
pub children: Vec<WidgetId>,
|
||||
/// The movable region its primitives are positioned through: its own when
|
||||
/// opted in, otherwise the nearest ancestor's.
|
||||
pub move_idx: MoveIdx,
|
||||
/// The declared lengths whoever drew this widget resolved into its rel base.
|
||||
/// A change to one moves a box this widget cannot fix by drawing again,
|
||||
/// and comparing them is what says so.
|
||||
pub declared: Declared,
|
||||
/// Its alignment when it was last drawn, which a change to the property
|
||||
/// is found against.
|
||||
pub own_align: RegionAlign,
|
||||
/// The movable region whose coordinates its placement is in when this
|
||||
/// widget does not own a region node.
|
||||
pub parent_move: MoveIdx,
|
||||
/// The mask its drawing is clipped to: one it set itself, or the one it
|
||||
/// inherited from whoever drew it.
|
||||
pub mask: MaskIdx,
|
||||
/// That inherited one. The two differ exactly where the widget set a
|
||||
/// mask of its own, which is the one it owns and the one a move rewrites
|
||||
/// -- and the one a redraw of it must not be handed back, since setting
|
||||
/// a mask asserts there is none.
|
||||
pub parent_mask: MaskIdx,
|
||||
pub layer: LayerId,
|
||||
}
|
||||
|
||||
impl ActiveData {
|
||||
/// What it answered when its parent asked, where it has been asked at
|
||||
/// all. Not `size`, which is what its last drawing reported: a drawing
|
||||
/// re-expressed in the box that answer chose is not a second answer.
|
||||
pub fn measured(&self) -> Option<Size> {
|
||||
self.answer.map(|answer| answer.size)
|
||||
}
|
||||
|
||||
/// Whether it owns a region node rather than sharing the one it was drawn
|
||||
/// under, which is what its two move indices being different says.
|
||||
pub fn is_region_node(&self) -> bool {
|
||||
self.move_idx != self.parent_move
|
||||
}
|
||||
}
|
||||
|
||||
/// What a widget answered when it was asked: the size it reported, and the
|
||||
/// boxes and windows that answer holds for.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Answer {
|
||||
pub size: Size,
|
||||
pub holds: LayoutHolds,
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
use crate::{Len, Px, REL_SHIFT, fixed::div_toward, fixed::narrow};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
|
||||
/// give the widget any box in this range and it draws the same thing and
|
||||
/// reports the same size. A widget that never reads its box in pixels holds
|
||||
/// for every length; one that does holds for the one it read unless it says
|
||||
/// otherwise, and a parent holds for whatever keeps every child it asked
|
||||
/// about or drew inside its own range.
|
||||
///
|
||||
/// The ends are lengths on the grid rather than floats with a tolerance
|
||||
/// around them: a box offered back at the length a widget reported comes back
|
||||
/// as the same number, so a range means what it says. The one place a range
|
||||
/// is wider than the length it came from is [`Self::through`], and what it is
|
||||
/// wider by is the floor that inverting a fraction undoes.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Holds {
|
||||
pub lo: Px,
|
||||
pub hi: Px,
|
||||
}
|
||||
|
||||
impl Holds {
|
||||
pub const ANY: Self = Self {
|
||||
lo: Px::MIN,
|
||||
hi: Px::MAX,
|
||||
};
|
||||
|
||||
pub const fn at(len: Px) -> Self {
|
||||
Self { lo: len, hi: len }
|
||||
}
|
||||
|
||||
pub const fn contains(&self, len: Px) -> bool {
|
||||
len.raw() >= self.lo.raw() && len.raw() <= self.hi.raw()
|
||||
}
|
||||
|
||||
/// Every length `other` holds for is one this holds for, so a drawing
|
||||
/// made under this range is still good wherever `other` is.
|
||||
pub const fn covers(&self, other: Self) -> bool {
|
||||
self.lo.raw() <= other.lo.raw() && self.hi.raw() >= other.hi.raw()
|
||||
}
|
||||
|
||||
pub const fn and(&self, other: Self) -> Self {
|
||||
Self {
|
||||
lo: self.lo.max(other.lo),
|
||||
hi: self.hi.min(other.hi),
|
||||
}
|
||||
}
|
||||
|
||||
/// What a box has to be for a part of it, `len` of the box long, to stay
|
||||
/// in this range: the exact preimage of `px + floor(rel * box)`, which is
|
||||
/// the one way a box in pixels is reached. A part with no relative extent
|
||||
/// is a fixed length -- it was drawn at that length and any box keeps it
|
||||
/// there.
|
||||
///
|
||||
/// The answer is an interval even where this range is a single length,
|
||||
/// because the multiply on the way in drops to the step below and many
|
||||
/// boxes therefore give one length. That is a floor rather than an
|
||||
/// allowance: inverting it is two divisions and nothing else, and the
|
||||
/// whole of a box maps back to itself.
|
||||
pub const fn through(&self, len: Len) -> Self {
|
||||
if self.lo.raw() == Px::MIN.raw() && self.hi.raw() == Px::MAX.raw() {
|
||||
return Self::ANY;
|
||||
}
|
||||
let rel = len.rel.raw() as i64;
|
||||
if rel == 0 {
|
||||
return Self::ANY;
|
||||
}
|
||||
let px = len.px.raw() as i64;
|
||||
// `floor(rel * box) >= lo - px` is `rel * box >= (lo - px) << REL`, and
|
||||
// `floor(rel * box) <= hi - px` is `rel * box < (hi - px + 1) << REL`.
|
||||
let lo = (self.lo.raw() as i64 - px) << REL_SHIFT;
|
||||
let hi = (((self.hi.raw() as i64 - px) + 1) << REL_SHIFT) - 1;
|
||||
// Dividing by a negative fraction turns the ends around, so which
|
||||
// bound each comes from is decided before dividing rather than by
|
||||
// taking the min and max of four divisions.
|
||||
match rel > 0 {
|
||||
true => Self::raws(div_toward(lo, rel, true), div_toward(hi, rel, false)),
|
||||
false => Self::raws(div_toward(hi, rel, true), div_toward(lo, rel, false)),
|
||||
}
|
||||
}
|
||||
|
||||
const fn raws(lo: i64, hi: i64) -> Self {
|
||||
Self {
|
||||
lo: Px::from_raw(narrow(lo)),
|
||||
hi: Px::from_raw(narrow(hi)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<Px>> for Holds {
|
||||
fn from(range: RangeInclusive<Px>) -> Self {
|
||||
Self {
|
||||
lo: *range.start(),
|
||||
hi: *range.end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Rel;
|
||||
|
||||
#[test]
|
||||
fn an_unrestricted_range_stays_unrestricted_through_any_length() {
|
||||
for rel in [-2.0, -0.5, 0.0, 0.5, 1.0, 2.0] {
|
||||
for px in [-8, 0, 8] {
|
||||
let len = Len::from_parts(Rel::from_f32(rel), Px::from_int(px));
|
||||
assert_eq!(Holds::ANY.through(len), Holds::ANY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn through_reverses_a_range_for_a_negative_fraction() {
|
||||
// `10 - box / 2` is between 20 and 40 for boxes from -60 to -20.
|
||||
let part = Len::from_parts(Rel::from_f32(-0.5), Px::from_int(10));
|
||||
let holds = Holds::from(Px::from_int(20)..=Px::from_int(40)).through(part);
|
||||
assert!(holds.contains(Px::from_int(-60)) && holds.contains(Px::from_int(-20)));
|
||||
assert!(!holds.contains(Px::from_int(-61)) && !holds.contains(Px::from_int(-19)));
|
||||
}
|
||||
|
||||
/// The case the widening is for: a part that holds only for the length it
|
||||
/// was drawn at has to hold for the box it was drawn in, and a third of a
|
||||
/// box is not a whole number of steps.
|
||||
#[test]
|
||||
fn a_part_maps_back_onto_the_box_it_was_measured_in() {
|
||||
let part = Len::from_parts(Rel::from_f32(1.0 / 3.0), Px::from_int(-146));
|
||||
for box_len in (440..460).map(Px::from_int) {
|
||||
let holds = Holds::at(part.to_px(box_len)).through(part);
|
||||
assert!(holds.contains(box_len), "{box_len:?} left out by {holds:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget handed the whole of its parent's box, with or without pixels
|
||||
/// taken off it, has no fraction to invert: multiplying by one is exact
|
||||
/// and taking the pixels off again is too, so the box maps back to
|
||||
/// itself. Allowing for anything here compounded a step a level down a
|
||||
/// chain of widgets each taking the whole of its parent.
|
||||
#[test]
|
||||
fn the_whole_of_a_box_maps_back_to_itself() {
|
||||
let at = Px::from_int(956);
|
||||
assert_eq!(Holds::at(at).through(Len::FULL), Holds::at(at));
|
||||
let less_eight = Len::from_parts(Rel::ONE, Px::from_int(-8));
|
||||
assert_eq!(
|
||||
Holds::at(at).through(less_eight),
|
||||
Holds::at(at + Px::from_int(8))
|
||||
);
|
||||
}
|
||||
|
||||
/// The range is the exact preimage at both ends, so a box one step
|
||||
/// outside it really does give a length outside this range. What a wider
|
||||
/// range costs is a drawing reused where it does not hold.
|
||||
#[test]
|
||||
fn a_box_one_step_outside_the_range_is_outside_it() {
|
||||
let part = Len::from_parts(Rel::from_f32(1.0 / 3.0), Px::from_int(-146));
|
||||
let at = Px::from_int(300);
|
||||
let holds = Holds::at(at).through(part);
|
||||
for inside in [holds.lo, holds.hi] {
|
||||
assert_eq!(part.to_px(inside), at, "{inside:?} left out of {holds:?}");
|
||||
}
|
||||
for outside in [holds.lo.next_down(), holds.hi.next_up()] {
|
||||
assert_ne!(part.to_px(outside), at, "{outside:?} admitted by {holds:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A truncating multiply only ever drops, so the step it needs allowing
|
||||
/// for on the way in belongs at the top of the range and not the bottom.
|
||||
#[test]
|
||||
fn a_fraction_widens_further_up_than_down() {
|
||||
let half = Len::from_parts(Rel::from_f32(0.5), Px::ZERO);
|
||||
let holds = Holds::at(Px::from_int(100)).through(half);
|
||||
let box_len = Px::from_int(200);
|
||||
assert!(holds.hi - box_len > box_len - holds.lo, "{holds:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_boundary_the_next_step_along_does_not_admit_it() {
|
||||
let boundary = Px::from_int(10);
|
||||
let above = Holds::from(boundary.next_up()..=Px::MAX);
|
||||
assert!(!above.contains(boundary));
|
||||
assert!(above.contains(boundary.next_up()));
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Axis, Holds, Len, Px, PxVec2, UiRegion, UiVec2};
|
||||
|
||||
/// What one evaluation of a widget depends on along one axis: the window
|
||||
/// lengths its reads hold for, the pixel lengths of its own box, and the
|
||||
/// symbolic lengths of that box and of its rel base where either one is what
|
||||
/// it was expressed in.
|
||||
///
|
||||
/// The symbolic lengths are pins rather than ranges: a container places its
|
||||
/// children as lengths of its rel base measured from where its own box starts,
|
||||
/// so what it draws turns on that box's length and on nothing about where it
|
||||
/// is. A box pin reaches the parent only where the box it pinned is the
|
||||
/// parent's own; anywhere else the parent chose that length itself, and a
|
||||
/// widget pinned this way is checked when it is re-placed.
|
||||
///
|
||||
/// A rel base pin says the answer or the drawing is a fraction of the rel base,
|
||||
/// which is a different length wherever the rel base is a different one -- at
|
||||
/// the same window size, so no range of window pixels can say it. A length
|
||||
/// of the rel base that is only pixels is not one: it is that many pixels
|
||||
/// whatever the rel base turns out to be.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct AxisHolds {
|
||||
pub window: Holds,
|
||||
pub rel_base: Option<Len>,
|
||||
pub region: Holds,
|
||||
pub region_len: Option<Len>,
|
||||
}
|
||||
|
||||
impl AxisHolds {
|
||||
pub const ANY: Self = Self {
|
||||
window: Holds::ANY,
|
||||
rel_base: None,
|
||||
region: Holds::ANY,
|
||||
region_len: None,
|
||||
};
|
||||
|
||||
pub fn and(&self, other: Self) -> Self {
|
||||
// Two pins of the same length disagreeing would mean one drawing was
|
||||
// a fraction of two different lengths at once.
|
||||
debug_assert!(
|
||||
self.region_len.is_none()
|
||||
|| other.region_len.is_none()
|
||||
|| self.region_len == other.region_len
|
||||
);
|
||||
debug_assert!(
|
||||
self.rel_base.is_none() || other.rel_base.is_none() || self.rel_base == other.rel_base
|
||||
);
|
||||
Self {
|
||||
window: self.window.and(other.window),
|
||||
rel_base: self.rel_base.or(other.rel_base),
|
||||
region: self.region.and(other.region),
|
||||
region_len: self.region_len.or(other.region_len),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn covers(&self, other: Self) -> bool {
|
||||
self.window.covers(other.window)
|
||||
&& self.region.covers(other.region)
|
||||
&& self
|
||||
.region_len
|
||||
.is_none_or(|len| other.region_len == Some(len))
|
||||
&& self.rel_base.is_none_or(|len| other.rel_base == Some(len))
|
||||
}
|
||||
|
||||
/// Whether a widget in a box `len` long, with that rel base, in that
|
||||
/// window, is one this drawing holds for.
|
||||
pub fn contains(&self, window: Px, rel_base: Len, len: Len) -> bool {
|
||||
self.window.contains(window)
|
||||
&& self.rel_base.is_none_or(|pinned| pinned == rel_base)
|
||||
&& self.region.contains(len.to_px(window))
|
||||
&& self.region_len.is_none_or(|pinned| pinned == len)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`AxisHolds`] on both axes. Every question asked of it is asked of one
|
||||
/// axis at a time, since a widget that read one length holds for any length
|
||||
/// of the other.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct LayoutHolds {
|
||||
pub x: AxisHolds,
|
||||
pub y: AxisHolds,
|
||||
}
|
||||
|
||||
impl LayoutHolds {
|
||||
pub const ANY: Self = Self {
|
||||
x: AxisHolds::ANY,
|
||||
y: AxisHolds::ANY,
|
||||
};
|
||||
|
||||
pub fn and(&self, other: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.and(other.x),
|
||||
y: self.y.and(other.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn covers(&self, other: Self) -> bool {
|
||||
self.x.covers(other.x) && self.y.covers(other.y)
|
||||
}
|
||||
|
||||
pub fn contains(&self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
|
||||
Axis::BOTH
|
||||
.into_iter()
|
||||
.all(|axis| self[axis].contains(window[axis], rel_base[axis], region[axis].len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(LayoutHolds => AxisHolds);
|
||||
@@ -1,149 +0,0 @@
|
||||
use crate::{
|
||||
Mask, MoveIdx, MoveOffset, PrimitiveRegistry, TextData, Textures, UiRegion, WeakWidget,
|
||||
WidgetId, Widgets,
|
||||
util::{Arena, Id, TrackedArena},
|
||||
};
|
||||
|
||||
/// How far the shader will walk a move chain. It bounds a malformed cycle
|
||||
/// rather than any real tree; `Moves::resolve` uses the same number so the
|
||||
/// two agree on what a deep tree resolves to.
|
||||
pub const CHAIN_LIMIT: u32 = 64;
|
||||
|
||||
mod active;
|
||||
mod holds;
|
||||
mod layout_holds;
|
||||
mod painter;
|
||||
mod place;
|
||||
mod render_state;
|
||||
|
||||
pub use active::*;
|
||||
pub use holds::*;
|
||||
pub use layout_holds::*;
|
||||
pub use painter::{Painter, PrimitiveLike};
|
||||
pub use place::{PlaceDesc, PlaceDescAxis, RetainedPrimitive};
|
||||
pub use render_state::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UiData {
|
||||
pub widgets: Widgets,
|
||||
/// Every primitive this ui can draw.
|
||||
pub primitives: PrimitiveRegistry,
|
||||
pub textures: Textures,
|
||||
pub text: TextData,
|
||||
pub masks: TrackedArena<Mask, u32>,
|
||||
}
|
||||
|
||||
/// Where each widget's drawing sits relative to its parent's slot, so moving
|
||||
/// a subtree writes one entry rather than every descendant's primitives.
|
||||
#[derive(Default)]
|
||||
pub struct Moves {
|
||||
arena: Arena<MoveOffset, u32>,
|
||||
pub changed: bool,
|
||||
}
|
||||
|
||||
impl Moves {
|
||||
pub fn push(&mut self, parent: MoveIdx, region: UiRegion) -> MoveIdx {
|
||||
self.changed = true;
|
||||
MoveIdx::slot(self.arena.push(MoveOffset::new(parent, region)).idx())
|
||||
}
|
||||
|
||||
/// Re-points a slot at a different parent, for a widget drawn somewhere
|
||||
/// else in the tree than it was.
|
||||
pub fn set_parent(&mut self, idx: MoveIdx, parent: MoveIdx) {
|
||||
let entry = self.arena.get_mut(Id::preset(idx.idx() as u32));
|
||||
if entry.parent != parent {
|
||||
entry.parent = parent;
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, idx: MoveIdx) {
|
||||
self.changed = true;
|
||||
self.arena.remove(Id::preset(idx.idx() as u32));
|
||||
}
|
||||
|
||||
/// Sets the box a slot's contents are placed within, itself given in the
|
||||
/// coordinates of its parent slot.
|
||||
pub fn set(&mut self, idx: MoveIdx, region: UiRegion) {
|
||||
let entry = self.arena.get_mut(Id::preset(idx.idx() as u32));
|
||||
if entry.region != region {
|
||||
entry.region = region;
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// The same walk the vertex shader does, in the same `Len` the shader is
|
||||
/// handed, for asking where a drawing will actually land -- hit testing,
|
||||
/// and nothing layout decides on. Layout threads its lengths down the
|
||||
/// draw instead, so no box it compares is composed back up this chain.
|
||||
pub fn resolve(&self, idx: MoveIdx, local: UiRegion) -> UiRegion {
|
||||
let mut region = local;
|
||||
self.walk(idx, |entry| region = region.within(entry));
|
||||
region
|
||||
}
|
||||
|
||||
fn walk(&self, idx: MoveIdx, mut step: impl FnMut(&UiRegion)) {
|
||||
let mut at = idx;
|
||||
for _ in 0..CHAIN_LIMIT {
|
||||
if at == MoveIdx::NONE {
|
||||
return;
|
||||
}
|
||||
let entry = &self.arena[at.idx()];
|
||||
step(&entry.region);
|
||||
at = entry.parent;
|
||||
}
|
||||
debug_assert!(
|
||||
at == MoveIdx::NONE,
|
||||
"a move chain longer than {CHAIN_LIMIT} resolves to the wrong place, \
|
||||
and the shader stops at the same depth"
|
||||
);
|
||||
}
|
||||
|
||||
/// How many slots a region in `idx` is composed through, which is what
|
||||
/// the shader's walk costs per primitive.
|
||||
pub fn depth(&self, idx: MoveIdx) -> usize {
|
||||
let mut depth = 0;
|
||||
let mut at = idx;
|
||||
while at != MoveIdx::NONE && depth < CHAIN_LIMIT as usize {
|
||||
at = self.arena[at.idx()].parent;
|
||||
depth += 1;
|
||||
}
|
||||
depth
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> &[MoveOffset] {
|
||||
&self.arena
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.changed = true;
|
||||
self.arena = Arena::default();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UiRsc {
|
||||
fn ui(&self) -> &UiData;
|
||||
fn ui_mut(&mut self) -> &mut UiData;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn on_add(&mut self, id: WeakWidget) {}
|
||||
#[allow(unused_variables)]
|
||||
fn on_remove(&mut self, id: WidgetId) {}
|
||||
#[allow(unused_variables)]
|
||||
fn on_draw(&mut self, active: &ActiveData) {}
|
||||
#[allow(unused_variables)]
|
||||
fn on_undraw(&mut self, active: &ActiveData) {}
|
||||
|
||||
fn widgets(&self) -> &Widgets {
|
||||
&self.ui().widgets
|
||||
}
|
||||
fn widgets_mut(&mut self) -> &mut Widgets {
|
||||
&mut self.ui_mut().widgets
|
||||
}
|
||||
fn free(&mut self) {
|
||||
while let Some(id) = self.widgets_mut().free_next() {
|
||||
self.on_remove(id);
|
||||
}
|
||||
self.ui_mut().textures.free();
|
||||
}
|
||||
}
|
||||
@@ -1,746 +0,0 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel,
|
||||
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
|
||||
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||
TexturePrimitive,
|
||||
},
|
||||
ui::{
|
||||
place::{PlaceSpan, RelBase},
|
||||
render_state::{DrawInfo, Placing},
|
||||
},
|
||||
};
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
pub struct Painter<'a> {
|
||||
pub(super) state: &'a mut UiRenderState,
|
||||
pub(super) rsc: &'a mut dyn UiRsc,
|
||||
|
||||
/// This widget's rel base, per axis: a length of the window, and what a
|
||||
/// fraction it or anything under it declares or reports is a fraction
|
||||
/// of. A length rather than a box, so padding can take from both the
|
||||
/// rel base and the box without either becoming the other.
|
||||
pub(super) rel_base: UiVec2,
|
||||
/// The box this widget was asked in, in its region node's coordinates:
|
||||
/// what it draws in, and what its children's places are parts of.
|
||||
pub(super) region: UiRegion,
|
||||
/// The window in pixels. Frames and boxes become pixels against this one
|
||||
/// unit, regardless of region-node boundaries.
|
||||
pub(super) window: PxVec2,
|
||||
pub(super) mask: MaskIdx,
|
||||
pub(super) textures: Vec<TextureHandle>,
|
||||
pub(super) primitives: Vec<RetainedPrimitive>,
|
||||
pub(super) mask_region: Option<UiRegion>,
|
||||
/// The previous drawing's owned mask, available for this draw to reclaim.
|
||||
pub(super) mask_slot: Option<MaskIdx>,
|
||||
/// Only children whose answers were read constrain this widget's answer.
|
||||
pub(super) answer_under: LayoutHolds,
|
||||
pub(super) children: Vec<WidgetId>,
|
||||
/// The children whose size this widget read while drawing.
|
||||
pub(super) size_deps: Vec<WidgetId>,
|
||||
/// What this draw itself reads, as against what its children's drawings
|
||||
/// hold for: every window and every length of its own region until it
|
||||
/// reads one, then that one unless it says otherwise, and the rel base or
|
||||
/// region length it read symbolically, each of which makes the drawing
|
||||
/// hold for that length alone.
|
||||
pub(super) own: LayoutHolds,
|
||||
/// What each child's drawing depends on. Asking a child again replaces
|
||||
/// its drawing, so it replaces this too rather than narrowing it.
|
||||
pub(super) under: Vec<(WidgetId, LayoutHolds)>,
|
||||
/// The movable region this widget's primitives are positioned through:
|
||||
/// its own when opted in, otherwise the nearest ancestor's.
|
||||
pub(super) move_idx: MoveIdx,
|
||||
pub layer: usize,
|
||||
/// The layer this widget was entered on, which its children's layers are
|
||||
/// counted from however far `layer` has walked.
|
||||
pub(super) own_layer: usize,
|
||||
pub(super) depth: usize,
|
||||
pub(super) id: WidgetId,
|
||||
}
|
||||
|
||||
impl<'a> Painter<'a> {
|
||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||
let kind = self.rsc.ui_mut().primitives.kind::<P>();
|
||||
self.write(kind, primitive, region);
|
||||
}
|
||||
|
||||
/// Takes the kind, for a caller writing many of one primitive.
|
||||
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
||||
self.write_resolved(kind, primitive, region, self.resolve(region));
|
||||
}
|
||||
|
||||
/// A box in this widget's region, composed into its region node's
|
||||
/// coordinates.
|
||||
fn resolve(&self, region: UiRegion) -> UiRegion {
|
||||
region.within(&self.region)
|
||||
}
|
||||
|
||||
fn write_resolved<P: Primitive>(
|
||||
&mut self,
|
||||
kind: PrimitiveKind<P>,
|
||||
primitive: P,
|
||||
region: UiRegion,
|
||||
resolved: UiRegion,
|
||||
) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::PrimitiveWrites);
|
||||
let h = self.state.layers.write(
|
||||
self.layer,
|
||||
PrimitiveInst {
|
||||
kind,
|
||||
id: self.id,
|
||||
primitive,
|
||||
region: resolved,
|
||||
mask_idx: self.mask,
|
||||
move_idx: self.move_idx,
|
||||
},
|
||||
);
|
||||
self.push_primitive(RetainedPrimitive { handle: h, region });
|
||||
}
|
||||
|
||||
fn push_primitive(&mut self, h: RetainedPrimitive) {
|
||||
if self.mask != MaskIdx::NONE {
|
||||
self.rsc.ui_mut().masks.push_ref(self.mask);
|
||||
}
|
||||
self.primitives.push(h);
|
||||
}
|
||||
|
||||
/// Writes a primitive over the whole of this widget's own box.
|
||||
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
||||
let primitive = primitive.into_primitive(self);
|
||||
self.primitive_at(primitive, UiRegion::FULL)
|
||||
}
|
||||
|
||||
/// Writes a primitive in a part of this widget's own box, in that box's
|
||||
/// coordinates.
|
||||
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) {
|
||||
let primitive = primitive.into_primitive(self);
|
||||
self.primitive_at(primitive, region);
|
||||
}
|
||||
|
||||
/// Sets a mask, in this widget's own box's coordinates.
|
||||
pub fn set_mask(&mut self, region: UiRegion) {
|
||||
self.mask_region = Some(region);
|
||||
assert!(self.mask == MaskIdx::NONE);
|
||||
let resolved = self.resolve(region);
|
||||
let move_idx = self.move_idx;
|
||||
let mask = Mask {
|
||||
region: resolved,
|
||||
move_idx,
|
||||
};
|
||||
let masks = &mut self.rsc.ui_mut().masks;
|
||||
self.mask = match self.mask_slot.take() {
|
||||
Some(idx) => {
|
||||
*masks.get_mut(idx) = mask;
|
||||
idx
|
||||
}
|
||||
None => {
|
||||
let idx = masks.push(mask);
|
||||
// The owner keeps the slot alive even with no primitives.
|
||||
masks.push_ref(idx);
|
||||
idx
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Draws a widget in the whole of this widget's own box, with the rel
|
||||
/// base forwarded unchanged: what a container that is only a wrapper
|
||||
/// around one child wants.
|
||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at(id, UiRegion::FULL)
|
||||
}
|
||||
|
||||
/// Resolves what the place says about the child's rel base into a length,
|
||||
/// where that is this widget's own narrowed the way the region is. An
|
||||
/// axis the region leaves whole is not read at all, so a wrapper that
|
||||
/// only moves its child does not pin its drawing to a rel base.
|
||||
fn resolve_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
|
||||
for axis in Axis::BOTH {
|
||||
let at = &mut place[axis];
|
||||
let (RelBase::WithRegion, PlaceSpan::Within(span)) = (at.rel_base, at.span) else {
|
||||
continue;
|
||||
};
|
||||
let len = span.len();
|
||||
at.rel_base = match len == Len::FULL {
|
||||
true => RelBase::Inherit,
|
||||
false => RelBase::Len(len.within_len(self.rel_base(axis))),
|
||||
};
|
||||
}
|
||||
place
|
||||
}
|
||||
|
||||
/// Asks a child, saying what its fractions are of and where it is asked.
|
||||
///
|
||||
/// `place` says where the child goes and what its fractions are of:
|
||||
/// see [`PlaceDesc`]. A `UiRegion` converts into the common case, which
|
||||
/// is a box of this widget's own with the answer placed inside it.
|
||||
///
|
||||
/// The child draws once, in the region that comes of it, and its answer
|
||||
/// is placed inside that region by re-expressing the drawing. Nothing is
|
||||
/// drawn again in a box an answer chose; a container that puts the
|
||||
/// answer somewhere else says so with [`Self::place_at`].
|
||||
pub fn widget_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
place: impl Into<PlaceDesc>,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let place = self.resolve_rel_base(place.into());
|
||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||
let declared = self.declared_lens(id);
|
||||
let align = self.rsc.widgets().alignment(id.id());
|
||||
let (rel_base, region) =
|
||||
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
if region_node {
|
||||
diag::bump(Counter::RegionNodeDraws);
|
||||
diag::region_node(id.id(), self.id, region);
|
||||
}
|
||||
// A child listed twice would be moved twice.
|
||||
let re_asked = self.children.contains(&id.id());
|
||||
if !re_asked {
|
||||
self.children.push(id.id());
|
||||
}
|
||||
let drawn = self.state.draw_inner(
|
||||
id.id(),
|
||||
DrawInfo {
|
||||
layer: self.layer,
|
||||
parent: Some(self.id),
|
||||
depth: self.depth + 1,
|
||||
parent_move: self.move_idx,
|
||||
region_node,
|
||||
mask: self.mask,
|
||||
rel_base,
|
||||
region,
|
||||
placed: place,
|
||||
asked: place,
|
||||
re_asked,
|
||||
},
|
||||
None,
|
||||
self.rsc,
|
||||
);
|
||||
let holds = self.in_parent(drawn.drawing_holds, region, place, declared);
|
||||
let answer_holds = self.in_parent(drawn.answer.holds, region, place, declared);
|
||||
match self.under.iter_mut().find(|(child, _)| *child == id.id()) {
|
||||
Some((_, kept)) => *kept = holds,
|
||||
None => self.under.push((id.id(), holds)),
|
||||
}
|
||||
DrawResult {
|
||||
child: id,
|
||||
painter: self,
|
||||
size: drawn.answer.size,
|
||||
answer_holds,
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes back a child that was drawn only to find out how long it is.
|
||||
/// Its drawing is dropped and it is not one of this widget's children
|
||||
/// this frame; what it answered is still something this widget asked.
|
||||
pub fn undraw<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
|
||||
self.children.retain(|child| *child != id.id());
|
||||
self.under.retain(|(child, _)| *child != id.id());
|
||||
self.state.undraw_rec(id.id(), self.rsc);
|
||||
}
|
||||
|
||||
/// Puts a child in `place` of this widget's box, where that box is the
|
||||
/// answer the child already gave: the drawing is re-expressed there
|
||||
/// rather than made again -- what a row does once it knows every slot,
|
||||
/// having measured each child from its cursor.
|
||||
///
|
||||
/// A child this draw has not asked about, and one whose rel base this
|
||||
/// narrows, is asked here instead: there is no answer to re-express, or
|
||||
/// the question has changed. So a container that places every child the
|
||||
/// same way says it once, and which of the two happens is this widget's
|
||||
/// business rather than the caller's.
|
||||
pub fn place_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
place: impl Into<PlaceDesc>,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let place = self.resolve_rel_base(place.into());
|
||||
let states_rel_base = Axis::BOTH
|
||||
.iter()
|
||||
.any(|&axis| matches!(place[axis].rel_base, RelBase::Len(_)));
|
||||
if states_rel_base || !self.children.contains(&id.id()) {
|
||||
return self.widget_at(id, place);
|
||||
}
|
||||
let at = self.placing();
|
||||
self.state.place_in(id.id(), &at, place, self.rsc);
|
||||
let active = &self.state.active[&id.id()];
|
||||
let size = active.measured().unwrap_or(active.size);
|
||||
DrawResult {
|
||||
child: id,
|
||||
painter: self,
|
||||
size,
|
||||
// Read where it was asked; moving it is not a second answer.
|
||||
answer_holds: LayoutHolds::ANY,
|
||||
}
|
||||
}
|
||||
|
||||
/// This widget as the thing its children are placed within.
|
||||
fn placing(&self) -> Placing {
|
||||
Placing {
|
||||
id: self.id,
|
||||
region: self.region,
|
||||
rel_base: self.rel_base,
|
||||
depth: self.depth,
|
||||
move_idx: self.move_idx,
|
||||
mask: self.mask,
|
||||
}
|
||||
}
|
||||
|
||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||
/// it resolves into its rel base. Reading them depends on nothing -- the box
|
||||
/// that comes of them is kept on the child, and `redraw` compares it
|
||||
/// there.
|
||||
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> Declared {
|
||||
self.rsc.widgets().declared_lens(id.id())
|
||||
}
|
||||
|
||||
/// What a child says its length is without being drawn, if it can say,
|
||||
/// as the length its draw would report: a fraction in it is resolved
|
||||
/// against this widget's rel base, which is the rel base a child asked with
|
||||
/// nothing narrowed gets. Asking counts as reading its size.
|
||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
||||
let widgets = self.rsc.widgets();
|
||||
// A rule is the answer where there is one: it wins over whatever the
|
||||
// widget would draw, so it has to win over what the widget says too.
|
||||
let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
|
||||
widgets
|
||||
.get_dyn(id.id())
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
});
|
||||
let rel_base = self.rel_base[axis];
|
||||
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
diag::hint_read(id.id(), self.id, axis, resolved);
|
||||
diag::bump(match resolved {
|
||||
Some(_) => Counter::HintHits,
|
||||
None => Counter::HintMisses,
|
||||
});
|
||||
}
|
||||
if let Some(hint) = hint {
|
||||
self.depend_on(id);
|
||||
// Resolving a fraction against this rel base makes this draw a
|
||||
// function of the rel base's length. The fraction to ask about is
|
||||
// the child's own: resolved against a rel base of pixels, none is
|
||||
// left to see it by.
|
||||
if hint.rel != Rel::ZERO {
|
||||
self.own[axis].rel_base = Some(rel_base);
|
||||
}
|
||||
}
|
||||
resolved
|
||||
}
|
||||
|
||||
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||
if !self.size_deps.contains(&child.id()) {
|
||||
self.size_deps.push(child.id());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_text<'b>(
|
||||
&mut self,
|
||||
buffer: &'b mut TextBuffer,
|
||||
attrs: &TextAttrs,
|
||||
width: Option<f32>,
|
||||
) -> &'b RenderedText {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::render_text(self.id, self.rsc.widgets().label(self.id), width);
|
||||
let ui = self.rsc.ui_mut();
|
||||
ui.text.render(buffer, attrs, width)
|
||||
}
|
||||
|
||||
/// Writes glyphs in the selected rel base or region coordinates.
|
||||
// TODO: merge the text methods into the primitive ones.
|
||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||
// Glyph offsets and sizes are pixels, which compose additively.
|
||||
// Only the shared origin needs composing through the region.
|
||||
let resolved = self.resolve(origin);
|
||||
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
||||
for glyph in text.glyphs.iter() {
|
||||
let place = |mut region: UiRegion| {
|
||||
region.x.end = region.x.start;
|
||||
region.y.end = region.y.start;
|
||||
let mut region = region.offset(UiVec2::from_px(glyph.offset));
|
||||
let size = PxVec2::new(
|
||||
Px::from_int(glyph.entry.width as i32),
|
||||
Px::from_int(glyph.entry.height as i32),
|
||||
);
|
||||
region.x.end = region.x.start.offset(size.x);
|
||||
region.y.end = region.y.start.offset(size.y);
|
||||
region
|
||||
};
|
||||
self.write_resolved(
|
||||
kind,
|
||||
GlyphPrimitive {
|
||||
uv_min: glyph.entry.uv_min,
|
||||
uv_max: glyph.entry.uv_max,
|
||||
layer: glyph.entry.layer,
|
||||
color: text.color,
|
||||
flags: glyph.entry.flags(),
|
||||
},
|
||||
place(origin),
|
||||
place(resolved),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The symbolic length of this widget's own box along one axis, in the
|
||||
/// lengths of its rel base that it places its children in. Reading it pins
|
||||
/// the drawing to that length -- and to nothing about where the box
|
||||
/// starts, which is what lets a container move without being drawn
|
||||
/// again. One axis at a time, because a container that divides one axis
|
||||
/// holds for any length of the other.
|
||||
pub fn region_len(&mut self, axis: Axis) -> Len {
|
||||
let len = self.region[axis].len();
|
||||
self.own[axis].region_len = Some(len);
|
||||
len
|
||||
}
|
||||
|
||||
/// This widget's rel base along one axis: what a fraction it or anything
|
||||
/// under it declares or reports is a fraction of. A container reads it
|
||||
/// to hand a length of it down -- padding, which takes its pixels off.
|
||||
/// Reading it pins the drawing to that rel base, the way
|
||||
/// [`Self::region_len`] pins it to the box.
|
||||
pub fn rel_base(&mut self, axis: Axis) -> Len {
|
||||
let len = self.rel_base[axis];
|
||||
self.own[axis].rel_base = Some(len);
|
||||
len
|
||||
}
|
||||
|
||||
/// Where this widget sits in a box longer than the length it takes. A
|
||||
/// widget that positions its own content reads it to place that content
|
||||
/// the way the box around it would have placed the widget.
|
||||
pub fn alignment(&self) -> RegionAlign {
|
||||
self.rsc.widgets().alignment(self.id)
|
||||
}
|
||||
|
||||
/// Whether a rule beside this widget gives its length on `axis` outright,
|
||||
/// which makes whatever it reports for that axis moot. A rule that only
|
||||
/// bounds the length is not one of these: the answer is still the
|
||||
/// widget's to give, and something still has to work it out.
|
||||
///
|
||||
/// The widget under a rule does not otherwise learn of it -- this is for
|
||||
/// a container deciding whether reading its children across an axis is
|
||||
/// worth anything, since reading one is also what makes its own size
|
||||
/// depend on it.
|
||||
pub fn has_exact_size(&self, axis: Axis) -> bool {
|
||||
self.rsc.widgets().size_rules(self.id)[axis]
|
||||
.exact()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// This widget's own box in pixels. Reading it makes the drawing one
|
||||
/// that holds for this box only, until `holds` says how far it goes.
|
||||
pub fn px_size(&mut self) -> PxVec2 {
|
||||
PxVec2::new(self.px_len(Axis::X), self.px_len(Axis::Y))
|
||||
}
|
||||
|
||||
/// One axis of this widget's own box in pixels. Prefer this to
|
||||
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
||||
pub fn px_len(&mut self, axis: Axis) -> Px {
|
||||
let len = self.region[axis].len();
|
||||
let px = len.to_px(self.window[axis]);
|
||||
let own = &mut self.own[axis].region;
|
||||
if *own == Holds::ANY {
|
||||
*own = Holds::at(px);
|
||||
}
|
||||
px
|
||||
}
|
||||
|
||||
/// The lengths of this widget's own box on `axis` that what it is drawing
|
||||
/// holds for -- the same primitives, in the same fractions and offsets
|
||||
/// of the box, and the same reported size. A widget that read its length
|
||||
/// in pixels holds for that one alone until it says otherwise.
|
||||
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
let len = self.region[axis].len();
|
||||
let holds = holds.into();
|
||||
debug_assert!(
|
||||
holds.contains(len.to_px(self.window[axis])),
|
||||
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
||||
self.label(),
|
||||
self.id
|
||||
);
|
||||
self.own[axis].region = holds;
|
||||
}
|
||||
|
||||
/// A window length in pixels, which is what every length in layout is
|
||||
/// measured in. Reading one pins the drawing to this window wherever the
|
||||
/// length is a fraction of it; one that is only pixels is that many
|
||||
/// pixels in any window and pins nothing.
|
||||
pub fn to_px(&mut self, len: Len, axis: Axis) -> Px {
|
||||
let window = self.window[axis];
|
||||
if len.rel != Rel::ZERO {
|
||||
let own = &mut self.own[axis].window;
|
||||
if *own == Holds::ANY {
|
||||
*own = Holds::at(window);
|
||||
}
|
||||
}
|
||||
len.to_px(window)
|
||||
}
|
||||
|
||||
/// The windows this drawing holds for, stated rather than taken: a
|
||||
/// container that branched on a length in pixels says which side of the
|
||||
/// boundary it was on, which is wider than the one window reading that
|
||||
/// length pins, and replaces it.
|
||||
pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
let holds = holds.into();
|
||||
debug_assert!(
|
||||
holds.contains(self.window[axis]),
|
||||
"'{}' ({:?}) says its drawing holds for windows that leave out this one",
|
||||
self.label(),
|
||||
self.id
|
||||
);
|
||||
self.own[axis].window = holds;
|
||||
}
|
||||
|
||||
pub fn child_layer(&mut self) {
|
||||
self.layer = self.state.layers.child(self.layer);
|
||||
}
|
||||
|
||||
/// The layer this widget's `n`th child draws on, addressed rather than
|
||||
/// walked to. A container that measures one child by drawing it can ask
|
||||
/// on the layer that child will end up on, and then the second ask is a
|
||||
/// reuse rather than a second drawing on another layer.
|
||||
pub fn child_layer_at(&mut self, n: usize) {
|
||||
let mut at = self.state.layers.child(self.own_layer);
|
||||
for _ in 0..n {
|
||||
at = self.state.layers.next(at);
|
||||
}
|
||||
self.layer = at;
|
||||
}
|
||||
|
||||
pub fn next_layer(&mut self) {
|
||||
self.layer = self.state.layers.next(self.layer);
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &str {
|
||||
&self.rsc.widgets().data(self.id).unwrap().label
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &WidgetId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// A child that has just been drawn. Reading its size records that this
|
||||
/// widget's own size depends on it; dropping it without reading draws the
|
||||
/// 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,
|
||||
answer_holds: LayoutHolds,
|
||||
}
|
||||
|
||||
impl<W: ?Sized> DrawResult<'_, '_, W> {
|
||||
pub fn size(self) -> Size {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
diag::bump(Counter::SizeReads);
|
||||
diag::size_read(self.child.id(), self.painter.id, self.size);
|
||||
}
|
||||
self.painter.depend_on(self.child);
|
||||
self.painter.answer_under = self.painter.answer_under.and(self.answer_holds);
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn len(self, axis: Axis) -> LayoutLen {
|
||||
self.size()[axis]
|
||||
}
|
||||
}
|
||||
|
||||
/// What `Painter::primitive` takes: a primitive, or something that yields one
|
||||
/// and does whatever else drawing it needs.
|
||||
pub trait PrimitiveLike {
|
||||
type Primitive: Primitive;
|
||||
fn into_primitive(self, painter: &mut Painter) -> Self::Primitive;
|
||||
}
|
||||
|
||||
impl<P: Primitive> PrimitiveLike for P {
|
||||
type Primitive = P;
|
||||
fn into_primitive(self, _: &mut Painter) -> P {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveLike for &TextureHandle {
|
||||
type Primitive = TexturePrimitive;
|
||||
|
||||
/// Retains a share of the handle, so the slot the primitive names cannot
|
||||
/// be freed and reused while it is still drawn.
|
||||
fn into_primitive(self, painter: &mut Painter) -> TexturePrimitive {
|
||||
painter.textures.push(self.clone());
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Painter<'_> {
|
||||
/// Moves what a child depends on into this widget's own terms, taking
|
||||
/// only what the child was asked with.
|
||||
///
|
||||
/// Window ranges are already about the one unit and combine directly.
|
||||
/// A rel base pin becomes this widget's own rel base wherever a length of it
|
||||
/// is what reached the child; where only pixels did, no length of this
|
||||
/// rel base can change the child's and the pin stops here.
|
||||
///
|
||||
/// A child's validity maps back through the part of this widget's box,
|
||||
/// where the box the child was asked in is that part; a declared length
|
||||
/// places the box inside the part instead, and then only that length
|
||||
/// reaches the child. A narrowed rel base is not one of these: it decides
|
||||
/// what fractions under the child mean and leaves the box the part it
|
||||
/// was given.
|
||||
fn in_parent(
|
||||
&self,
|
||||
holds: LayoutHolds,
|
||||
region: UiRegion,
|
||||
place: PlaceDesc,
|
||||
declared: Declared,
|
||||
) -> LayoutHolds {
|
||||
let mut result = LayoutHolds::ANY;
|
||||
for axis in Axis::BOTH {
|
||||
let declared = declared[axis];
|
||||
let holds = holds[axis];
|
||||
let at = place[axis];
|
||||
let result = &mut result[axis];
|
||||
// Every read became pixels against the window, so a range on
|
||||
// it is already in this widget's terms.
|
||||
result.window = holds.window;
|
||||
// A length this widget named -- a resolved share, a box a sibling
|
||||
// decided, a box it sized outright, which is its own base -- is
|
||||
// not a length of this widget's rel base, so a pin on it stops
|
||||
// here. So does a declaration in pixels: no length of either base
|
||||
// is in it to see.
|
||||
let reaches = !matches!(at.rel_base, RelBase::Len(_))
|
||||
&& declared.is_none_or(|len| len.rel != Rel::ZERO);
|
||||
result.rel_base = holds.rel_base.and(reaches.then(|| self.rel_base[axis]));
|
||||
match (at.span, declared) {
|
||||
// Its box is a part of this widget's own box, in that box's
|
||||
// own lengths, so what it holds for maps back through that
|
||||
// part into a range on this widget's box. A length it pinned
|
||||
// is this widget's length less the part's pixels where the
|
||||
// part is the whole of the box less pixels, which is the one
|
||||
// shape that inverts exactly; any other part pins this
|
||||
// widget's own length.
|
||||
(PlaceSpan::Within(span), None) => {
|
||||
let part_len = span.len();
|
||||
result.region = holds.region.through(part_len);
|
||||
result.region_len = holds.region_len.map(|pinned| match part_len.rel {
|
||||
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
|
||||
_ => self.region[axis].len(),
|
||||
});
|
||||
}
|
||||
// Its box is a length this widget decided, from its own
|
||||
// rel base or from a sibling's answer: no length of this
|
||||
// widget's box reaches it, so what it holds for is a range
|
||||
// on the window and none of it on that box.
|
||||
_ => {
|
||||
result.window = result.window.and(holds.region.through(region[axis].len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
/// What a widget's box is where a rule or its own hint says so outright.
|
||||
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
||||
let rules = self.size_rules(id);
|
||||
let widget = self.get_dyn(id);
|
||||
Declared::from_axes(|axis| {
|
||||
rules[axis].declared().or_else(|| {
|
||||
// A hint still narrows the box where no rule does, which is
|
||||
// how a widget with a natural pixel size -- an image, a gap
|
||||
// -- gets that size rather than the whole offer. That is the
|
||||
// offer's business rather than a declaration's, and this
|
||||
// falls away once a widget occupies its reported size inside
|
||||
// the box it was offered.
|
||||
widget
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
.and_then(|len| len.declared())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutLen {
|
||||
/// Whether what a widget reported along an axis is the whole of the box
|
||||
/// it is in rather than a part to be placed inside it. A share fills,
|
||||
/// because a share is a length only to whoever divides one, and whoever
|
||||
/// did is the one that handed down this box. A declared axis does too:
|
||||
/// the rule already gave the region its length, and the rule's length is
|
||||
/// what the widget reports there. And an axis the parent decided from
|
||||
/// the answer is the answer already.
|
||||
pub(super) fn fills(&self, declared: Option<Len>, decided: bool) -> bool {
|
||||
self.leftover != Weight::ZERO || declared.is_some() || decided
|
||||
}
|
||||
}
|
||||
|
||||
impl PlaceDesc {
|
||||
/// Where a widget's drawing goes inside the part its parent gave it: what
|
||||
/// it reported, on the side of the part its alignment says, and the whole
|
||||
/// part wherever the answer fills it.
|
||||
///
|
||||
/// The length it reported is a length of its rel base, and the part is one
|
||||
/// too, so this takes one from the other rather than composing it into the
|
||||
/// part. That is what makes a fraction the same fraction wherever the part
|
||||
/// it is placed in sits and however long it is -- the fraction is resolved
|
||||
/// once, here, against the rel base it was reported of.
|
||||
pub(super) fn placement(
|
||||
self,
|
||||
region: UiRegion,
|
||||
size: Size,
|
||||
declared: Declared,
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
let mut placed = region;
|
||||
for axis in Axis::BOTH {
|
||||
let reported = size[axis];
|
||||
if reported.fills(declared[axis], self[axis].fills) {
|
||||
continue;
|
||||
}
|
||||
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
|
||||
}
|
||||
placed
|
||||
}
|
||||
|
||||
/// The rel base length and the box a child is asked in, in the coordinates the
|
||||
/// widget asking draws in.
|
||||
///
|
||||
/// `own` is that widget's own box, and `place` what of it the child is
|
||||
/// given, including any rel base it states -- a row's slot, or padding's rel
|
||||
/// base less its pixels. That is a window length, like every other length
|
||||
/// here, since a slot of a row is not a fraction of anything the row can
|
||||
/// name. The child's declaration is a fraction of whichever reached it, and
|
||||
/// is the only one that also places the box: a box the caller decided is
|
||||
/// what `place` names.
|
||||
pub(super) fn rel_base_and_region(
|
||||
self,
|
||||
own: UiRegion,
|
||||
parent_rel_base: UiVec2,
|
||||
declared: Declared,
|
||||
align: RegionAlign,
|
||||
) -> (UiVec2, UiRegion) {
|
||||
let given = self.of(own, align);
|
||||
let mut rel_base = parent_rel_base;
|
||||
let mut region = given;
|
||||
for axis in Axis::BOTH {
|
||||
let base = match self[axis].rel_base {
|
||||
RelBase::Len(len) => len,
|
||||
RelBase::Inherit | RelBase::WithRegion => parent_rel_base[axis],
|
||||
};
|
||||
let len = declared[axis]
|
||||
.map(|len| len.within_len(base))
|
||||
.unwrap_or(base);
|
||||
rel_base[axis] = len;
|
||||
if declared[axis].is_some() {
|
||||
region[axis] = given[axis].place(len, align[axis]);
|
||||
}
|
||||
}
|
||||
(rel_base, region)
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan};
|
||||
|
||||
/// How a child's region along one axis comes from the region of the widget
|
||||
/// asking, and what its fractions are of.
|
||||
///
|
||||
/// The three ways of saying a region are the three the geometry already has:
|
||||
/// a span composed into the caller's box, a span shifted to where that box
|
||||
/// starts, and a length placed in it by alignment. Which one is meant cannot
|
||||
/// be read off the numbers, since two of them take the same span and apply
|
||||
/// it differently, so it is said here.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct PlaceDescAxis {
|
||||
pub span: PlaceSpan,
|
||||
pub fills: bool,
|
||||
pub rel_base: RelBase,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum PlaceSpan {
|
||||
Within(UiSpan),
|
||||
Shifted(UiSpan),
|
||||
Sized(Len),
|
||||
}
|
||||
|
||||
/// What a child's fractions are of. [`PlaceSpan::Sized`] is a length the
|
||||
/// caller named, which is always its own base, so nothing here constructs one
|
||||
/// beside anything but [`Self::Len`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum RelBase {
|
||||
/// The caller's own, unchanged.
|
||||
Inherit,
|
||||
/// The caller's own, narrowed the way the region is.
|
||||
WithRegion,
|
||||
/// This length of the window.
|
||||
Len(Len),
|
||||
}
|
||||
|
||||
impl PlaceDescAxis {
|
||||
/// The whole of the caller's box.
|
||||
pub const WHOLE: Self = UiSpan::FULL.within_desc();
|
||||
|
||||
/// This region is the child's placement: its answer is not placed inside
|
||||
/// it again. A container uses it where it hands back exactly what the
|
||||
/// child asked for -- a row placing a child at the length it reported.
|
||||
pub const fn fills(mut self) -> Self {
|
||||
self.fills = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// This along `axis`, and the whole of the caller's box across it: what
|
||||
/// a container dividing one axis says, since nothing divides the other.
|
||||
/// [`PlaceDesc::from_axis`] says the across one where it is not the
|
||||
/// whole.
|
||||
pub const fn on_axis(self, axis: Axis) -> PlaceDesc {
|
||||
PlaceDesc::from_axis(axis, self, Self::WHOLE)
|
||||
}
|
||||
|
||||
/// What the child's fractions are of, as a length of the window: a
|
||||
/// resolved share, or a box a sibling's answer decided.
|
||||
pub const fn rel_base(mut self, len: Len) -> Self {
|
||||
self.rel_base = RelBase::Len(len);
|
||||
self
|
||||
}
|
||||
|
||||
/// Where it lands in the coordinates `own` is in.
|
||||
pub fn of(self, own: UiSpan, align: AxisAlign) -> UiSpan {
|
||||
match self.span {
|
||||
PlaceSpan::Within(span) => span.within(&own),
|
||||
PlaceSpan::Shifted(mut span) => {
|
||||
span.shift(own.start);
|
||||
span
|
||||
}
|
||||
PlaceSpan::Sized(len) => own.place(len, align),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a child is asked, on both axes. A [`UiRegion`] converts into the
|
||||
/// common case: that box of the caller's own, the answer placed inside it.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct PlaceDesc {
|
||||
pub x: PlaceDescAxis,
|
||||
pub y: PlaceDescAxis,
|
||||
}
|
||||
|
||||
impl PlaceDesc {
|
||||
/// The whole of the caller's box, on both axes.
|
||||
pub const WHOLE: Self = Self::splat(PlaceDescAxis::WHOLE);
|
||||
|
||||
pub const fn new(x: PlaceDescAxis, y: PlaceDescAxis) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// The same on both axes.
|
||||
pub const fn splat(place: PlaceDescAxis) -> Self {
|
||||
Self { x: place, y: place }
|
||||
}
|
||||
|
||||
/// A description per axis, where the two differ and neither is the
|
||||
/// axis a container divides.
|
||||
pub fn from_axes(f: impl Fn(Axis) -> PlaceDescAxis) -> Self {
|
||||
Self::new(f(Axis::X), f(Axis::Y))
|
||||
}
|
||||
|
||||
/// `aligned` on `axis` and `ortho` on the other, which is how a
|
||||
/// container that divides one axis says what it is doing.
|
||||
pub const fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
|
||||
match axis {
|
||||
Axis::X => Self::new(aligned, ortho),
|
||||
Axis::Y => Self::new(ortho, aligned),
|
||||
}
|
||||
}
|
||||
|
||||
/// Both regions are the child's placement. See [`PlaceDescAxis::fills`].
|
||||
pub const fn fills(self) -> Self {
|
||||
Self::new(self.x.fills(), self.y.fills())
|
||||
}
|
||||
|
||||
/// The child's rel base on one axis. See [`PlaceDescAxis::rel_base`].
|
||||
pub const fn rel_base(mut self, axis: Axis, len: Len) -> Self {
|
||||
self[axis] = self[axis].rel_base(len);
|
||||
self
|
||||
}
|
||||
|
||||
/// The box each axis names, in the coordinates `own` is in.
|
||||
pub fn of(self, own: UiRegion, align: RegionAlign) -> UiRegion {
|
||||
UiRegion::new(self.x.of(own.x, align.x), self.y.of(own.y, align.y))
|
||||
}
|
||||
}
|
||||
|
||||
impl UiSpan {
|
||||
/// This span composed into the caller's own box, so it moves and scales
|
||||
/// with it: [`UiSpan::within`], which is what a container that insets
|
||||
/// one speaks. Taking eleven pixels off the end needs no length, where
|
||||
/// saying the same thing in window lengths would make the container read
|
||||
/// its own box -- and a box chosen from its own answer then feeds back
|
||||
/// into the answer.
|
||||
///
|
||||
/// The child's rel base is narrowed the same way, so padding takes its
|
||||
/// pixels off both and `rel(1)` under it fills the caller rather than
|
||||
/// overflowing it.
|
||||
pub const fn within_desc(self) -> PlaceDescAxis {
|
||||
PlaceDescAxis {
|
||||
span: PlaceSpan::Within(self),
|
||||
fills: false,
|
||||
rel_base: RelBase::WithRegion,
|
||||
}
|
||||
}
|
||||
|
||||
/// This span shifted to where the caller's own box starts: window
|
||||
/// lengths along a cursor, which is what a container dividing room among
|
||||
/// its children speaks. A child's report is a window length, so the
|
||||
/// cursor that sums those reports is one too, and a moved box re-places
|
||||
/// every child by re-adding its start, exactly.
|
||||
///
|
||||
/// The child's rel base passes through: how far along the cursor a child
|
||||
/// sits says nothing about what a fraction under it is of. The same span
|
||||
/// says [`Self::within_desc`] as a part of that box instead, and which is
|
||||
/// meant cannot be read off the numbers.
|
||||
pub const fn shifted_desc(self) -> PlaceDescAxis {
|
||||
PlaceDescAxis {
|
||||
span: PlaceSpan::Shifted(self),
|
||||
fills: false,
|
||||
rel_base: RelBase::Inherit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Len {
|
||||
/// A box this long, placed in the caller's own by the child's alignment:
|
||||
/// the rule that places an answer, with the length given from above
|
||||
/// rather than reported. What a stack's sizing child decides for the
|
||||
/// rest. It is the child's rel base too.
|
||||
pub const fn as_desc(self) -> PlaceDescAxis {
|
||||
PlaceDescAxis {
|
||||
span: PlaceSpan::Sized(self),
|
||||
fills: false,
|
||||
rel_base: RelBase::Len(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UiRegion> for PlaceDesc {
|
||||
fn from(region: UiRegion) -> Self {
|
||||
Self::new(region.x.within_desc(), region.y.within_desc())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlaceDescAxis> for PlaceDesc {
|
||||
fn from(place: PlaceDescAxis) -> Self {
|
||||
Self::splat(place)
|
||||
}
|
||||
}
|
||||
|
||||
/// A primitive as it was written: its box in the widget's own box's
|
||||
/// coordinates, which is what a move of that box re-composes from.
|
||||
#[derive(Debug)]
|
||||
pub struct RetainedPrimitive {
|
||||
pub handle: PrimitiveHandle,
|
||||
pub region: UiRegion,
|
||||
}
|
||||
|
||||
impl_axis_index!(PlaceDesc => PlaceDescAxis);
|
||||
File diff suppressed because it is too large.
Load diff
+4
-13
@@ -7,7 +7,7 @@ pub struct Arena<T, I> {
|
||||
tracker: IdTracker<I>,
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> Arena<T, I> {
|
||||
impl<T, I: const IdNum> Arena<T, I> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Vec::new(),
|
||||
@@ -34,13 +34,9 @@ impl<T, I: IdNum> Arena<T, I> {
|
||||
self.tracker.free(id);
|
||||
self.data[i]
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||
&mut self.data[id.idx()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> Default for Arena<T, I> {
|
||||
impl<T, I: const IdNum> Default for Arena<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
@@ -52,7 +48,7 @@ pub struct TrackedArena<T, I> {
|
||||
pub changed: bool,
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> TrackedArena<T, I> {
|
||||
impl<T, I: const IdNum> TrackedArena<T, I> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arena::default(),
|
||||
@@ -75,11 +71,6 @@ impl<T, I: IdNum> TrackedArena<T, I> {
|
||||
self.refs[i.idx()] += 1;
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||
self.changed = true;
|
||||
self.inner.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, id: Id<I>) -> T
|
||||
where
|
||||
T: Copy,
|
||||
@@ -95,7 +86,7 @@ impl<T, I: IdNum> TrackedArena<T, I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> Default for TrackedArena<T, I> {
|
||||
impl<T, I: const IdNum> Default for TrackedArena<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub struct DynBorrower<'a, T: ?Sized> {
|
||||
data: &'a mut T,
|
||||
borrowed: &'a mut bool,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> DynBorrower<'a, T> {
|
||||
pub fn new(data: &'a mut T, borrowed: &'a mut bool) -> Self {
|
||||
if *borrowed {
|
||||
panic!("tried to mutably borrow the same thing twice");
|
||||
}
|
||||
Self { data, borrowed }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Drop for DynBorrower<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
*self.borrowed = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for DynBorrower<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> DerefMut for DynBorrower<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
//! A helper type for Arc<Mutex<T>>
|
||||
//! Currently there are mut versions (of Ref stuff) which don't do anything different under the hood,
|
||||
//! leaving them in for now in case I decide RwLock is a desired feature
|
||||
|
||||
use std::{
|
||||
marker::Unsize,
|
||||
ops::{CoerceUnsized, Deref, DerefMut},
|
||||
sync::{Arc, MappedMutexGuard, Mutex, MutexGuard, Weak},
|
||||
};
|
||||
|
||||
pub struct Handle<T: ?Sized>(Arc<Mutex<T>>);
|
||||
pub struct WeakHandle<T: ?Sized>(Weak<Mutex<T>>);
|
||||
|
||||
pub type Ref<'a, T> = MutexGuard<'a, T>;
|
||||
pub type RefMut<'a, T> = MutexGuard<'a, T>;
|
||||
|
||||
pub type RefMap<'a, T> = MappedMutexGuard<'a, T>;
|
||||
pub type RefMapMut<'a, T> = MappedMutexGuard<'a, T>;
|
||||
|
||||
impl<T: ?Sized> Handle<T> {
|
||||
pub fn get(&self) -> Ref<'_, T> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> RefMut<'_, T> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_take<'a>(self) -> RefGuard<'a, T> {
|
||||
let handle = self;
|
||||
RefGuard {
|
||||
guard: unsafe {
|
||||
std::mem::transmute::<MutexGuard<'_, T>, MutexGuard<'_, T>>(
|
||||
handle.0.lock().unwrap(),
|
||||
)
|
||||
},
|
||||
handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_take_mut<'a>(self) -> RefGuardMut<'a, T> {
|
||||
let handle = self;
|
||||
RefGuardMut {
|
||||
guard: unsafe {
|
||||
std::mem::transmute::<MutexGuard<'_, T>, MutexGuard<'_, T>>(
|
||||
handle.0.lock().unwrap(),
|
||||
)
|
||||
},
|
||||
handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refs(&self) -> usize {
|
||||
Arc::strong_count(&self.0)
|
||||
}
|
||||
|
||||
pub fn weak(&self) -> WeakHandle<T> {
|
||||
WeakHandle(Arc::downgrade(&self.0))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// you must guarantee the type outside
|
||||
/// ideally you check the typeid, but this is often used
|
||||
/// when the trait object is wrapped one or more times
|
||||
/// and you'd have to implement each one individually
|
||||
pub unsafe fn downcast<U: 'static>(self) -> Handle<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let raw: *const Mutex<T> = Arc::into_raw(self.0);
|
||||
let raw: *const Mutex<U> = raw.cast();
|
||||
Handle(unsafe { Arc::from_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> WeakHandle<T> {
|
||||
pub fn strong(&self) -> Option<Handle<T>> {
|
||||
Some(Handle(self.0.upgrade()?))
|
||||
}
|
||||
|
||||
pub fn dropped(&self) -> bool {
|
||||
self.0.strong_count() == 0
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// you must guarantee the type outside
|
||||
pub unsafe fn downcast<U: 'static>(self) -> WeakHandle<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let raw: *const Mutex<T> = self.0.into_raw();
|
||||
let raw: *const Mutex<U> = raw.cast();
|
||||
WeakHandle(unsafe { Weak::from_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for Handle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for WeakHandle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Handle<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Handle<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(Arc::new(Mutex::new(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Handle<U>> for Handle<T> {}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WeakHandle<U>> for WeakHandle<T> {}
|
||||
|
||||
// yucky
|
||||
|
||||
// TODO: is drop order important here?
|
||||
// something stupid could happen that I don't know about
|
||||
// if handle is dropped before guard
|
||||
pub struct RefGuard<'a, T: ?Sized> {
|
||||
guard: MutexGuard<'a, T>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
pub struct RefGuardMut<'a, T: ?Sized> {
|
||||
guard: MutexGuard<'a, T>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
pub struct MappedRefGuard<'a, T: ?Sized, U> {
|
||||
guard: MappedMutexGuard<'a, U>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
pub struct MappedRefGuardMut<'a, T: ?Sized, U> {
|
||||
guard: MappedMutexGuard<'a, U>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RefGuard<'a, T> {
|
||||
pub fn map<U>(s: Self, f: impl FnOnce(&mut T) -> &mut U) -> MappedRefGuard<'a, T, U> {
|
||||
MappedRefGuard {
|
||||
guard: MutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RefGuardMut<'a, T> {
|
||||
pub fn map<U>(s: Self, f: impl FnOnce(&mut T) -> &mut U) -> MappedRefGuardMut<'a, T, U> {
|
||||
MappedRefGuardMut {
|
||||
guard: MutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, U> MappedRefGuard<'a, T, U> {
|
||||
pub fn map<U2>(s: Self, f: impl FnOnce(&mut U) -> &mut U2) -> MappedRefGuard<'a, T, U2> {
|
||||
MappedRefGuard {
|
||||
guard: MappedMutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, U> MappedRefGuardMut<'a, T, U> {
|
||||
pub fn map<U2>(s: Self, f: impl FnOnce(&mut U) -> &mut U2) -> MappedRefGuardMut<'a, T, U2> {
|
||||
MappedRefGuardMut {
|
||||
guard: MappedMutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for RefGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for RefGuardMut<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> DerefMut for RefGuardMut<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U> Deref for MappedRefGuard<'_, T, U> {
|
||||
type Target = U;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U> Deref for MappedRefGuardMut<'_, T, U> {
|
||||
type Target = U;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U> DerefMut for MappedRefGuardMut<'_, T, U> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
+45
-9
@@ -1,3 +1,5 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)]
|
||||
pub struct Id<I = u64>(I);
|
||||
@@ -9,7 +11,14 @@ pub struct IdTracker<I = u64> {
|
||||
cur: Id<I>,
|
||||
}
|
||||
|
||||
impl<I: IdNum> IdTracker<I> {
|
||||
impl<I: const IdNum> IdTracker<I> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
free: Vec::new(),
|
||||
cur: Id(I::first()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> Id<I> {
|
||||
if let Some(id) = self.free.pop() {
|
||||
@@ -43,22 +52,19 @@ impl<I: IdNum> Id<I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: IdNum> Default for IdTracker<I> {
|
||||
impl<I: const IdNum> Default for IdTracker<I> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
free: Vec::new(),
|
||||
cur: Id(I::first()),
|
||||
}
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IdNum {
|
||||
pub const trait IdNum {
|
||||
fn first() -> Self;
|
||||
fn next(&self) -> Self;
|
||||
fn idx(&self) -> usize;
|
||||
}
|
||||
|
||||
impl IdNum for u64 {
|
||||
impl const IdNum for u64 {
|
||||
fn first() -> Self {
|
||||
0
|
||||
}
|
||||
@@ -72,7 +78,7 @@ impl IdNum for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl IdNum for u32 {
|
||||
impl const IdNum for u32 {
|
||||
fn first() -> Self {
|
||||
0
|
||||
}
|
||||
@@ -85,3 +91,33 @@ impl IdNum for u32 {
|
||||
*self as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StaticIdTracker<I = u64>(Mutex<IdTracker<I>>);
|
||||
|
||||
impl<I: const IdNum> StaticIdTracker<I> {
|
||||
pub const fn new() -> Self {
|
||||
Self(Mutex::new(IdTracker::new()))
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&self) -> Id<I> {
|
||||
let mut s = self.0.lock().unwrap();
|
||||
if let Some(id) = s.free.pop() {
|
||||
return id;
|
||||
}
|
||||
let next = s.cur.next();
|
||||
std::mem::replace(&mut s.cur, next)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn free(&self, id: Id<I>) {
|
||||
let mut s = self.0.lock().unwrap();
|
||||
s.free.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: const IdNum> Default for StaticIdTracker<I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
+26
-62
@@ -1,13 +1,33 @@
|
||||
use std::ops::*;
|
||||
|
||||
pub const trait LerpUtil {
|
||||
fn lerp(self, from: Self, to: Self) -> 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;
|
||||
}
|
||||
|
||||
impl const DivOr for f32 {
|
||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
||||
let res = self / rhs;
|
||||
if res.is_nan() { other } else { res }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: const Add<Output = T> + const Sub<Output = T> + const Mul<Output = T> + const DivOr + Copy> const
|
||||
LerpUtil for T
|
||||
{
|
||||
/// linear interpolation
|
||||
/// from * (1.0 - self) + to * self
|
||||
fn lerp(self, from: Self, to: Self) -> Self {
|
||||
from + (to - from) * self
|
||||
}
|
||||
/// inverse of lerp
|
||||
fn lerp_inv(self, from: Self, to: Self) -> Self {
|
||||
(self - from).div_or(to - from, from)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_op {
|
||||
@@ -17,7 +37,7 @@ macro_rules! impl_op {
|
||||
use super::*;
|
||||
#[allow(unused_imports)]
|
||||
use std::ops::*;
|
||||
const impl $op for $T {
|
||||
impl const $op for $T {
|
||||
type Output = Self;
|
||||
|
||||
fn $fn(self, rhs: Self) -> Self::Output {
|
||||
@@ -26,12 +46,12 @@ macro_rules! impl_op {
|
||||
}
|
||||
}
|
||||
}
|
||||
const impl $opa for $T {
|
||||
impl const $opa for $T {
|
||||
fn $fna(&mut self, rhs: Self) {
|
||||
*self = self.$fn(rhs);
|
||||
}
|
||||
}
|
||||
const impl $op<f32> for $T {
|
||||
impl const $op<f32> for $T {
|
||||
type Output = Self;
|
||||
|
||||
fn $fn(self, rhs: f32) -> Self::Output {
|
||||
@@ -40,7 +60,7 @@ macro_rules! impl_op {
|
||||
}
|
||||
}
|
||||
}
|
||||
const impl $op<$T> for f32 {
|
||||
impl const $op<$T> for f32 {
|
||||
type Output = $T;
|
||||
|
||||
fn $fn(self, rhs: $T) -> Self::Output {
|
||||
@@ -49,41 +69,13 @@ macro_rules! impl_op {
|
||||
}
|
||||
}
|
||||
}
|
||||
const impl $opa<f32> for $T {
|
||||
impl const $opa<f32> for $T {
|
||||
fn $fna(&mut self, rhs: f32) {
|
||||
*self = self.$fn(rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Without the `f32` operations, for a type whose fields are not all the
|
||||
// same kind of number: there is nothing a bare float means to a fraction
|
||||
// and an offset at once.
|
||||
(same $T:ident $op:ident $fn:ident $opa:ident $fna:ident; $($field:ident)*) => {
|
||||
#[allow(non_snake_case)]
|
||||
mod ${concat($T, _op_, $fn, _same_impl)} {
|
||||
use super::*;
|
||||
#[allow(unused_imports)]
|
||||
use std::ops::*;
|
||||
const impl $op for $T {
|
||||
type Output = Self;
|
||||
|
||||
fn $fn(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
$($field: self.$field.$fn(rhs.$field),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
const impl $opa for $T {
|
||||
fn $fna(&mut self, rhs: Self) {
|
||||
*self = self.$fn(rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(same $T:ident $op:ident $fn:ident; $($field:ident)*) => {
|
||||
impl_op!(same $T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*);
|
||||
};
|
||||
($T:ident $op:ident $fn:ident; $($field:ident)*) => {
|
||||
impl_op!($T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*);
|
||||
};
|
||||
@@ -93,31 +85,3 @@ macro_rules! impl_op {
|
||||
}
|
||||
|
||||
pub(crate) use impl_op;
|
||||
|
||||
/// `Index<Axis>` for a pair, which is how every pair here is read by axis.
|
||||
/// The generics clause is given in braces where the type has one.
|
||||
macro_rules! impl_axis_index {
|
||||
($({$($gen:tt)*})? $T:ty => $Out:ty) => {
|
||||
const impl $(<$($gen)*>)? std::ops::Index<crate::Axis> for $T {
|
||||
type Output = $Out;
|
||||
|
||||
fn index(&self, axis: crate::Axis) -> &$Out {
|
||||
match axis {
|
||||
crate::Axis::X => &self.x,
|
||||
crate::Axis::Y => &self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const impl $(<$($gen)*>)? std::ops::IndexMut<crate::Axis> for $T {
|
||||
fn index_mut(&mut self, axis: crate::Axis) -> &mut $Out {
|
||||
match axis {
|
||||
crate::Axis::X => &mut self.x,
|
||||
crate::Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use impl_axis_index;
|
||||
@@ -1,23 +1,17 @@
|
||||
mod arena;
|
||||
mod borrow;
|
||||
mod change;
|
||||
mod handle;
|
||||
mod id;
|
||||
mod math;
|
||||
mod refcount;
|
||||
mod slot;
|
||||
mod trust;
|
||||
mod typemap;
|
||||
mod vec2;
|
||||
|
||||
pub use arena::*;
|
||||
pub use borrow::*;
|
||||
pub use change::*;
|
||||
pub use handle::*;
|
||||
pub use id::*;
|
||||
pub use math::*;
|
||||
pub use refcount::*;
|
||||
pub use slot::*;
|
||||
pub(crate) use trust::*;
|
||||
pub use typemap::*;
|
||||
pub use vec2::*;
|
||||
|
||||
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
|
||||
|
||||
@@ -10,6 +10,7 @@ impl RefCounter {
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(0.into()))
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn refs(&self) -> u32 {
|
||||
self.0.load(Ordering::Acquire)
|
||||
}
|
||||
@@ -17,6 +18,7 @@ impl RefCounter {
|
||||
let refs = self.0.fetch_sub(1, Ordering::Release);
|
||||
refs == 0
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn quiet_clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct SlotId {
|
||||
idx: u32,
|
||||
genr: u32,
|
||||
}
|
||||
|
||||
pub struct SlotVec<T> {
|
||||
data: Vec<(u32, Option<T>)>,
|
||||
free: Vec<u32>,
|
||||
}
|
||||
|
||||
impl<T> SlotVec<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Default::default(),
|
||||
free: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, x: T) -> SlotId {
|
||||
if let Some(idx) = self.free.pop() {
|
||||
let (genr, data) = &mut self.data[idx as usize];
|
||||
*data = Some(x);
|
||||
SlotId { idx, genr: *genr }
|
||||
} else {
|
||||
let idx = self.data.len() as u32;
|
||||
let genr = 0;
|
||||
self.data.push((genr, Some(x)));
|
||||
SlotId { idx, genr }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free(&mut self, id: SlotId) {
|
||||
let (genr, data) = &mut self.data[id.idx as usize];
|
||||
*genr += 1;
|
||||
*data = None;
|
||||
self.free.push(id.idx);
|
||||
}
|
||||
|
||||
pub fn get(&self, id: SlotId) -> Option<&T> {
|
||||
let slot = &self.data[id.idx as usize];
|
||||
if slot.0 != id.genr {
|
||||
return None;
|
||||
}
|
||||
slot.1.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: SlotId) -> Option<&mut T> {
|
||||
let slot = &mut self.data[id.idx as usize];
|
||||
if slot.0 != id.genr {
|
||||
return None;
|
||||
}
|
||||
slot.1.as_mut()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len() - self.free.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for SlotVec<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub(crate) unsafe fn forget_mut<'a, T>(x: &mut T) -> &'a mut T {
|
||||
unsafe { std::mem::transmute::<&mut T, &mut T>(x) }
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref, clippy::missing_safety_doc)]
|
||||
pub(crate) unsafe fn to_mut<T>(x: &T) -> &mut T {
|
||||
#[allow(mutable_transmutes)]
|
||||
unsafe {
|
||||
std::mem::transmute::<&T, &mut T>(x)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use crate::util::HashMap;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
marker::Unsize,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
pub struct TypeMap<Trait: ?Sized> {
|
||||
map: HashMap<TypeId, Box<Trait>>,
|
||||
}
|
||||
|
||||
impl<Trait: ?Sized> TypeMap<Trait> {
|
||||
pub fn set_type<T: Unsize<Trait> + 'static>(&mut self, val: T) {
|
||||
self.map
|
||||
.insert(TypeId::of::<T>(), Box::new(val) as Box<Trait>);
|
||||
}
|
||||
|
||||
pub fn type_mut<T: Unsize<Trait> + 'static>(&mut self) -> Option<&mut T> {
|
||||
Some(Self::convert_mut(self.map.get_mut(&TypeId::of::<T>())?))
|
||||
}
|
||||
|
||||
pub fn type_or_default<T: Default + Unsize<Trait> + 'static>(&mut self) -> &mut T {
|
||||
Self::convert_mut(
|
||||
self.map
|
||||
.entry(TypeId::of::<T>())
|
||||
.or_insert(Box::new(T::default()) as Box<Trait>),
|
||||
)
|
||||
}
|
||||
|
||||
fn convert_mut<T: Unsize<Trait>>(entry: &mut Box<Trait>) -> &mut T {
|
||||
// allegedly this is just what Any does...
|
||||
unsafe { &mut *(entry.as_mut() as *mut Trait as *mut T) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for TypeMap<T> {
|
||||
type Target = HashMap<TypeId, Box<T>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.map
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> DerefMut for TypeMap<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.map
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Default for TypeMap<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-6
@@ -1,11 +1,7 @@
|
||||
use crate::util::impl_op;
|
||||
use crate::util::{DivOr, impl_op};
|
||||
use std::{hash::Hash, ops::*};
|
||||
|
||||
/// `align(8)` because that is WGSL's alignment for a `vec2<f32>`, so any GPU
|
||||
/// struct holding one is laid out the way its shader reads it without having
|
||||
/// to say so itself. Those structs still need a manual `unsafe impl Pod`,
|
||||
/// since the trailing padding this introduces is what `derive(Pod)` refuses.
|
||||
#[repr(C, align(8))]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Vec2 {
|
||||
pub x: f32,
|
||||
@@ -71,6 +67,15 @@ impl_op!(Vec2 Sub sub; x y);
|
||||
impl_op!(Vec2 Mul mul; x y);
|
||||
impl_op!(Vec2 Div div; x y);
|
||||
|
||||
impl const 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 {
|
||||
type Output = Self;
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
use crate::{RegionAlign, SizeRules, Widget};
|
||||
|
||||
pub struct WidgetData {
|
||||
pub widget: Box<dyn Widget>,
|
||||
pub label: String,
|
||||
pub(super) region_node: bool,
|
||||
pub(super) size: SizeRules,
|
||||
pub(super) align: RegionAlign,
|
||||
/// dynamic borrow checking
|
||||
pub borrowed: bool,
|
||||
}
|
||||
|
||||
impl WidgetData {
|
||||
pub fn new<W: Widget>(widget: W) -> Self {
|
||||
let mut label = std::any::type_name::<W>().to_string();
|
||||
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
|
||||
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
|
||||
}
|
||||
Self {
|
||||
widget: Box::new(widget),
|
||||
label,
|
||||
region_node: false,
|
||||
size: SizeRules::default(),
|
||||
align: RegionAlign::default(),
|
||||
borrowed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
use std::{marker::Unsize, ops::CoerceUnsized, sync::mpsc::Sender};
|
||||
|
||||
use crate::{
|
||||
UiRsc, Widget,
|
||||
util::{RefCounter, SlotId},
|
||||
};
|
||||
|
||||
pub type WidgetId = SlotId;
|
||||
|
||||
/// An identifier for a widget that can index a UI or event ctx to get it.
|
||||
/// This is a strong handle that does not impl Clone, and when it is dropped,
|
||||
/// a signal is sent to the owning UI to clean up the resources.
|
||||
///
|
||||
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
|
||||
pub struct StrongWidget<W: ?Sized = dyn Widget> {
|
||||
pub(super) id: WidgetId,
|
||||
counter: RefCounter,
|
||||
send: Sender<WidgetId>,
|
||||
ty: *const W,
|
||||
}
|
||||
|
||||
/// A weak handle to a widget.
|
||||
/// Will not keep it alive, but can still be used for indexing like WidgetHandle.
|
||||
pub struct WeakWidget<W: ?Sized = dyn Widget> {
|
||||
pub(super) id: WidgetId,
|
||||
#[allow(unused)]
|
||||
ty: *const W,
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget>> StrongWidget<W> {
|
||||
pub fn any(self) -> StrongWidget<dyn Widget> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> StrongWidget<W> {
|
||||
pub(crate) fn new(id: WidgetId, send: Sender<WidgetId>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
counter: RefCounter::new(),
|
||||
send,
|
||||
ty: null_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn refs(&self) -> u32 {
|
||||
self.counter.refs()
|
||||
}
|
||||
|
||||
pub fn weak(&self) -> WeakWidget<W> {
|
||||
let Self { ty, id, .. } = *self;
|
||||
WeakWidget { ty, id }
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WeakWidget<W> {
|
||||
pub(crate) fn new(id: WidgetId) -> Self {
|
||||
Self { id, ty: null_ptr() }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn upgrade(self, ui: &mut impl UiRsc) -> StrongWidget<W> {
|
||||
ui.widgets_mut().upgrade(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Drop for StrongWidget<W> {
|
||||
fn drop(&mut self) {
|
||||
if self.counter.drop() {
|
||||
let _ = self.send.send(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetIdFn<Rsc, W: ?Sized = dyn Widget>: FnOnce(&mut Rsc) -> WeakWidget<W> {}
|
||||
impl<Rsc, W: ?Sized, F: FnOnce(&mut Rsc) -> WeakWidget<W>> WidgetIdFn<Rsc, W> for F {}
|
||||
|
||||
pub trait IdLike {
|
||||
type Widget: ?Sized;
|
||||
fn id(&self) -> WidgetId;
|
||||
}
|
||||
|
||||
impl<W: ?Sized> IdLike for &StrongWidget<W> {
|
||||
type Widget = W;
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> IdLike for StrongWidget<W> {
|
||||
type Widget = W;
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> IdLike for WeakWidget<W> {
|
||||
type Widget = W;
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl IdLike for WidgetId {
|
||||
type Widget = dyn Widget;
|
||||
fn id(&self) -> WidgetId {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<StrongWidget<U>> for StrongWidget<T> {}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WeakWidget<U>> for WeakWidget<T> {}
|
||||
|
||||
impl<W: ?Sized> Clone for WeakWidget<W> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
impl<W: ?Sized> Copy for WeakWidget<W> {}
|
||||
impl<W: ?Sized> PartialEq for WeakWidget<W> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> PartialEq for StrongWidget<W> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> std::fmt::Debug for StrongWidget<W> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.id.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Widget + 'a, State: UiRsc> FnOnce<(&'a mut State,)> for WeakWidget<W> {
|
||||
type Output = &'a mut W;
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (&'a mut State,)) -> Self::Output {
|
||||
&mut args.0.widgets_mut()[self]
|
||||
}
|
||||
}
|
||||
|
||||
fn null_ptr<W: ?Sized>() -> *const W {
|
||||
if size_of::<&W>() == size_of::<*const dyn Widget>() {
|
||||
let w: *const dyn Widget = &();
|
||||
unsafe { std::mem::transmute_copy(&w) }
|
||||
} else {
|
||||
unsafe { std::mem::transmute_copy(&[0usize; 1]) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<W: ?Sized> Send for WeakWidget<W> {}
|
||||
unsafe impl<W: ?Sized> Sync for WeakWidget<W> {}
|
||||
@@ -1,75 +0,0 @@
|
||||
use crate::UiRsc;
|
||||
|
||||
use super::*;
|
||||
use std::marker::Unsize;
|
||||
|
||||
pub trait WidgetLike<Rsc: UiRsc, Tag>: Sized {
|
||||
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
|
||||
|
||||
fn add(self, rsc: &mut Rsc) -> WeakWidget<Self::Widget>;
|
||||
|
||||
fn add_strong(self, rsc: &mut Rsc) -> StrongWidget<Self::Widget> {
|
||||
self.add(rsc).upgrade(rsc)
|
||||
}
|
||||
|
||||
fn with_id<W2>(
|
||||
self,
|
||||
f: impl FnOnce(&mut Rsc, WeakWidget<Self::Widget>) -> WeakWidget<W2>,
|
||||
) -> impl WidgetIdFn<Rsc, W2> {
|
||||
move |state| {
|
||||
let id = self.add(state);
|
||||
f(state, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_root(self, rsc: &mut Rsc, root: &mut impl HasRoot) {
|
||||
let id = self.add_strong(rsc);
|
||||
root.set_root(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasRoot {
|
||||
fn set_root(&mut self, root: StrongWidget);
|
||||
}
|
||||
|
||||
pub trait WidgetArrLike<Rsc, const LEN: usize, Tag> {
|
||||
#[track_caller]
|
||||
fn add(self, state: &mut Rsc) -> WidgetArr<LEN>;
|
||||
}
|
||||
|
||||
impl<Rsc, const LEN: usize> WidgetArrLike<Rsc, LEN, ArrTag> for WidgetArr<LEN> {
|
||||
fn add(self, _: &mut Rsc) -> WidgetArr<LEN> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// variadic generics please save us
|
||||
macro_rules! impl_widget_arr {
|
||||
($n:expr;$($W:ident)*) => {
|
||||
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
|
||||
};
|
||||
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
|
||||
impl<Rsc: UiRsc, $($W: WidgetLike<Rsc, $Tag>,$Tag,)*> WidgetArrLike<Rsc, $n, ($($Tag,)*)> for ($($W,)*) {
|
||||
fn add(self, rsc: &mut Rsc) -> WidgetArr<$n> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($W,)*) = self;
|
||||
WidgetArr::new(
|
||||
[$($W.add(rsc).upgrade(rsc),)*],
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_widget_arr!(1;A);
|
||||
impl_widget_arr!(2;A B);
|
||||
impl_widget_arr!(3;A B C);
|
||||
impl_widget_arr!(4;A B C D);
|
||||
impl_widget_arr!(5;A B C D E);
|
||||
impl_widget_arr!(6;A B C D E F);
|
||||
impl_widget_arr!(7;A B C D E F G);
|
||||
impl_widget_arr!(8;A B C D E F G H);
|
||||
impl_widget_arr!(9;A B C D E F G H I);
|
||||
impl_widget_arr!(10;A B C D E F G H I J);
|
||||
impl_widget_arr!(11;A B C D E F G H I J K);
|
||||
impl_widget_arr!(12;A B C D E F G H I J K L);
|
||||
@@ -1,83 +0,0 @@
|
||||
use crate::{Axis, LayoutLen, Painter, Size};
|
||||
use std::any::Any;
|
||||
|
||||
mod data;
|
||||
mod handle;
|
||||
mod like;
|
||||
mod size_rule;
|
||||
mod tag;
|
||||
mod view;
|
||||
mod widgets;
|
||||
|
||||
pub use data::*;
|
||||
pub use handle::*;
|
||||
pub use like::*;
|
||||
pub use size_rule::*;
|
||||
pub use tag::*;
|
||||
pub use view::*;
|
||||
pub use widgets::*;
|
||||
|
||||
pub trait Widget: Any {
|
||||
/// Draws the widget, and returns what it used of the box it was given.
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size;
|
||||
|
||||
/// An exact length the widget can give without a painter or its children.
|
||||
/// Optional, and saves a draw rather than changing one: a hint that
|
||||
/// disagrees with the eventual draw fails a debug assertion.
|
||||
fn size_hint(&self, _axis: Axis) -> Option<LayoutLen> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for () {
|
||||
/// A gap: nothing drawn, at the default length, so a span gives it a share.
|
||||
fn draw(&mut self, _: &mut Painter) -> Size {
|
||||
Size::default()
|
||||
}
|
||||
|
||||
fn size_hint(&self, _axis: Axis) -> Option<LayoutLen> {
|
||||
Some(LayoutLen::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn Widget {
|
||||
pub fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that returns a widget given a UI.
|
||||
/// Useful for defining trait functions on widgets that create a parent widget so that the children
|
||||
/// don't need to be IDs yet
|
||||
pub trait WidgetFn<State, W: Widget + ?Sized>: FnOnce(&mut State) -> W {}
|
||||
impl<State, W: Widget + ?Sized, F: FnOnce(&mut State) -> W> WidgetFn<State, W> for F {}
|
||||
|
||||
pub struct WidgetArr<const LEN: usize> {
|
||||
pub arr: [StrongWidget; LEN],
|
||||
}
|
||||
|
||||
impl<const LEN: usize> WidgetArr<LEN> {
|
||||
pub fn new(arr: [StrongWidget; LEN]) -> Self {
|
||||
Self { arr }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetOption<State> {
|
||||
fn get(self, state: &mut State) -> Option<StrongWidget>;
|
||||
}
|
||||
|
||||
impl<State> WidgetOption<State> for () {
|
||||
fn get(self, _: &mut State) -> Option<StrongWidget> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<State, F: FnOnce(&mut State) -> Option<StrongWidget>> WidgetOption<State> for F {
|
||||
fn get(self, state: &mut State) -> Option<StrongWidget> {
|
||||
self(state)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Axis, LayoutLen, Len};
|
||||
|
||||
/// What a widget's length on one axis is, as a rule its parent applies where
|
||||
/// it draws it rather than an answer the widget gives about itself.
|
||||
///
|
||||
/// A rule and a drawn size are not two opinions to reconcile: a rule wins on
|
||||
/// the axis it names, and the `Size` returned by `draw` answers only the axes
|
||||
/// with no rule. That is what lets a span divide its space around a length
|
||||
/// nobody has drawn yet, and it is why a rule lives beside the widget rather
|
||||
/// than inside it -- the widget under the rule never has to know about it.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub enum SizeRule {
|
||||
/// Whatever the widget reports from drawing.
|
||||
#[default]
|
||||
Free,
|
||||
/// This length, whatever the widget reports.
|
||||
Exact(LayoutLen),
|
||||
}
|
||||
|
||||
impl SizeRule {
|
||||
/// The length this rule gives without the widget being drawn, if it can
|
||||
/// give one.
|
||||
pub fn declared(&self) -> Option<Len> {
|
||||
self.exact().and_then(|len| len.declared())
|
||||
}
|
||||
|
||||
/// The length this rule gives outright, whatever the widget reports --
|
||||
/// which makes the widget's answer on that axis moot. A share counts: it
|
||||
/// is a length the widget's parent still has to divide, so it is exact
|
||||
/// here and resolved there, unlike `declared`, which is only the ones
|
||||
/// that give a box directly.
|
||||
pub fn exact(&self) -> Option<LayoutLen> {
|
||||
match self {
|
||||
Self::Free => None,
|
||||
Self::Exact(len) => Some(*len),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayoutLen> for SizeRule {
|
||||
fn from(len: LayoutLen) -> Self {
|
||||
Self::Exact(len)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<LayoutLen>> for SizeRule {
|
||||
fn from(len: Option<LayoutLen>) -> Self {
|
||||
len.map_or(Self::Free, Self::Exact)
|
||||
}
|
||||
}
|
||||
|
||||
/// One rule per axis, which is how a widget carries a length on one axis and
|
||||
/// leaves the other to whatever it draws.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct SizeRules {
|
||||
pub x: SizeRule,
|
||||
pub y: SizeRule,
|
||||
}
|
||||
|
||||
impl_axis_index!(SizeRules => SizeRule);
|
||||
|
||||
/// What a widget's box is on each axis where something says so outright,
|
||||
/// before it is drawn: a rule beside it, or a hint it gives about itself.
|
||||
/// Whoever draws the widget resolves these against its rel base.
|
||||
///
|
||||
/// A [`Len`] rather than a [`LayoutLen`], because a share can never be one
|
||||
/// -- see [`LayoutLen::declared`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Declared {
|
||||
pub x: Option<Len>,
|
||||
pub y: Option<Len>,
|
||||
}
|
||||
|
||||
impl Declared {
|
||||
pub const NONE: Self = Self { x: None, y: None };
|
||||
|
||||
pub fn from_axes(f: impl Fn(Axis) -> Option<Len>) -> Self {
|
||||
Self {
|
||||
x: f(Axis::X),
|
||||
y: f(Axis::Y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_axis_index!(Declared => Option<Len>);
|
||||
@@ -1,64 +0,0 @@
|
||||
use super::*;
|
||||
use crate::UiRsc;
|
||||
use std::marker::Unsize;
|
||||
|
||||
pub struct WidgetTag;
|
||||
impl<Rsc: UiRsc, W: Widget> WidgetLike<Rsc, WidgetTag> for W {
|
||||
type Widget = W;
|
||||
fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
|
||||
let w = rsc.ui_mut().widgets.add_weak(self);
|
||||
rsc.on_add(w);
|
||||
w
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnTag;
|
||||
impl<Rsc: UiRsc, W: Widget, F: FnOnce(&mut Rsc) -> W> WidgetLike<Rsc, FnTag> for F {
|
||||
type Widget = W;
|
||||
fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
|
||||
self(rsc).add(rsc)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetFnTrait<Rsc> {
|
||||
type Widget: Widget;
|
||||
fn run(self, rsc: &mut Rsc) -> Self::Widget;
|
||||
}
|
||||
pub struct FnTraitTag;
|
||||
impl<Rsc: UiRsc, T: WidgetFnTrait<Rsc>> WidgetLike<Rsc, FnTraitTag> for T {
|
||||
type Widget = T::Widget;
|
||||
#[track_caller]
|
||||
fn add(self, rsc: &mut Rsc) -> WeakWidget<T::Widget> {
|
||||
self.run(rsc).add(rsc)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RefTag;
|
||||
impl<Rsc: UiRsc, W: ?Sized + Widget + Unsize<dyn Widget>> WidgetLike<Rsc, RefTag>
|
||||
for WeakWidget<W>
|
||||
{
|
||||
type Widget = W;
|
||||
fn add(self, _: &mut Rsc) -> WeakWidget<W> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RefFnTag;
|
||||
impl<Rsc: UiRsc, W: ?Sized + Widget + Unsize<dyn Widget>, F: FnOnce(&mut Rsc) -> WeakWidget<W>>
|
||||
WidgetLike<Rsc, RefFnTag> for F
|
||||
{
|
||||
type Widget = W;
|
||||
fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
|
||||
self(rsc)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewTag;
|
||||
impl<Rsc: UiRsc, V: WidgetView> WidgetLike<Rsc, ViewTag> for V {
|
||||
type Widget = V::Widget;
|
||||
fn add(self, _: &mut Rsc) -> WeakWidget<Self::Widget> {
|
||||
self.root()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArrTag;
|
||||
@@ -1,24 +0,0 @@
|
||||
use std::marker::Unsize;
|
||||
|
||||
use crate::{IdLike, WeakWidget, Widget};
|
||||
|
||||
pub trait WidgetView {
|
||||
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
|
||||
fn root(&self) -> WeakWidget<Self::Widget>;
|
||||
}
|
||||
|
||||
pub trait HasWidget {
|
||||
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
|
||||
}
|
||||
|
||||
impl<W: Widget + Unsize<dyn Widget> + ?Sized> HasWidget for WeakWidget<W> {
|
||||
type Widget = W;
|
||||
}
|
||||
|
||||
impl<WV: WidgetView> IdLike for WV {
|
||||
type Widget = WV::Widget;
|
||||
|
||||
fn id(&self) -> super::WidgetId {
|
||||
self.root().id
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||
|
||||
use crate::{
|
||||
Axis, AxisAlign, IdLike, RegionAlign, SizeRule, SizeRules, StrongWidget, WeakWidget, Widget,
|
||||
WidgetData, WidgetId,
|
||||
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
|
||||
};
|
||||
|
||||
pub struct Widgets {
|
||||
pub needs_redraw: HashSet<WidgetId>,
|
||||
vec: SlotVec<WidgetData>,
|
||||
send: Sender<WidgetId>,
|
||||
recv: Receiver<WidgetId>,
|
||||
pub(crate) waiting: HashSet<WidgetId>,
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
pub fn new() -> Self {
|
||||
let (send, recv) = channel();
|
||||
Self {
|
||||
needs_redraw: Default::default(),
|
||||
vec: Default::default(),
|
||||
waiting: Default::default(),
|
||||
send,
|
||||
recv,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_updates(&self) -> bool {
|
||||
!self.needs_redraw.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_dyn(&self, id: WidgetId) -> Option<&dyn Widget> {
|
||||
Some(self.vec.get(id)?.widget.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_dyn_mut(&mut self, id: WidgetId) -> Option<&mut dyn Widget> {
|
||||
self.needs_redraw.insert(id);
|
||||
Some(self.vec.get_mut(id)?.widget.as_mut())
|
||||
}
|
||||
|
||||
/// get_dyn but dynamic borrow checking of widgets
|
||||
/// lets you do recursive (tree) operations, like the painter does
|
||||
pub(crate) fn get_dyn_dynamic<'a>(&self, id: WidgetId) -> WidgetWrapper<'a> {
|
||||
// SAFETY: must guarantee no other mutable references to this widget exist
|
||||
// done through the borrow variable
|
||||
let data = unsafe { forget_mut(to_mut(self.vec.get(id).unwrap())) };
|
||||
if data.borrowed {
|
||||
panic!("tried to mutably borrow the same widget twice");
|
||||
}
|
||||
WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed)
|
||||
}
|
||||
|
||||
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
self.get_dyn(id.id())?.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
|
||||
}
|
||||
|
||||
pub fn add_strong<W: Widget>(&mut self, widget: W) -> StrongWidget<W> {
|
||||
let id = self.vec.add(WidgetData::new(widget));
|
||||
StrongWidget::new(id, self.send.clone())
|
||||
}
|
||||
|
||||
pub fn add_weak<W: Widget>(&mut self, widget: W) -> WeakWidget<W> {
|
||||
let id = self.vec.add(WidgetData::new(widget));
|
||||
self.waiting.insert(id);
|
||||
WeakWidget::new(id)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn upgrade<W: ?Sized>(&mut self, rf: WeakWidget<W>) -> StrongWidget<W> {
|
||||
if !self.waiting.remove(&rf.id()) {
|
||||
let label = self.label(rf);
|
||||
let id = rf.id();
|
||||
panic!(
|
||||
"widget '{label}' ({id:?}) was already added\ncannot add a widget twice; consider creating two"
|
||||
)
|
||||
}
|
||||
StrongWidget::new(rf.id(), self.send.clone())
|
||||
}
|
||||
|
||||
pub fn data(&self, id: impl IdLike) -> Option<&WidgetData> {
|
||||
self.vec.get(id.id())
|
||||
}
|
||||
|
||||
pub fn label(&self, id: impl IdLike) -> &String {
|
||||
&self.data(id.id()).unwrap().label
|
||||
}
|
||||
|
||||
/// useful for debugging
|
||||
pub fn set_label(&mut self, id: impl IdLike, label: String) {
|
||||
self.data_mut(id.id()).unwrap().label = label;
|
||||
}
|
||||
|
||||
/// Whether this widget owns a movable retained region.
|
||||
pub fn is_region_node(&self, id: impl IdLike) -> bool {
|
||||
self.data(id).unwrap().region_node
|
||||
}
|
||||
|
||||
/// Chooses whether this widget's retained drawing has one movable region
|
||||
/// of its own. Changing the boundary redraws the subtree once so every
|
||||
/// primitive names the right coordinate space.
|
||||
pub fn set_region_node(&mut self, id: impl IdLike, region_node: bool) {
|
||||
let id = id.id();
|
||||
let data = self.data_mut(id).unwrap();
|
||||
if data.region_node == region_node {
|
||||
return;
|
||||
}
|
||||
data.region_node = region_node;
|
||||
self.needs_redraw.insert(id);
|
||||
}
|
||||
|
||||
/// The length rules whoever draws this widget applies to its box.
|
||||
pub fn size_rules(&self, id: impl IdLike) -> SizeRules {
|
||||
self.data(id).unwrap().size
|
||||
}
|
||||
|
||||
/// Sets one axis's rule. The widget is marked rather than its parent
|
||||
/// because the parent is not known here; `redraw` escalates a changed
|
||||
/// declared length to whoever resolves it.
|
||||
pub fn set_size_rule(&mut self, id: impl IdLike, axis: Axis, rule: SizeRule) {
|
||||
let id = id.id();
|
||||
let data = self.data_mut(id).unwrap();
|
||||
if data.size[axis] == rule {
|
||||
return;
|
||||
}
|
||||
data.size[axis] = rule;
|
||||
self.needs_redraw.insert(id);
|
||||
}
|
||||
|
||||
/// Where this widget sits in a box longer than the length it takes.
|
||||
pub fn alignment(&self, id: impl IdLike) -> RegionAlign {
|
||||
self.data(id).unwrap().align
|
||||
}
|
||||
|
||||
/// Sets one axis's alignment. Which box a widget ends up in is its
|
||||
/// parent's to decide, so this is escalated the way a length rule is.
|
||||
pub fn set_alignment(&mut self, id: impl IdLike, axis: Axis, align: AxisAlign) {
|
||||
let id = id.id();
|
||||
let data = self.data_mut(id).unwrap();
|
||||
if data.align[axis] == align {
|
||||
return;
|
||||
}
|
||||
data.align[axis] = align;
|
||||
self.needs_redraw.insert(id);
|
||||
}
|
||||
|
||||
/// Both axes at once, for a caller holding a pair.
|
||||
pub fn set_size_rules(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
x: impl Into<SizeRule>,
|
||||
y: impl Into<SizeRule>,
|
||||
) {
|
||||
let id = id.id();
|
||||
self.set_size_rule(id, Axis::X, x.into());
|
||||
self.set_size_rule(id, Axis::Y, y.into());
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self, id: impl IdLike) -> Option<&mut WidgetData> {
|
||||
self.vec.get_mut(id.id())
|
||||
}
|
||||
|
||||
pub fn free_next(&mut self) -> Option<WidgetId> {
|
||||
let next = self.recv.try_recv().ok()?;
|
||||
self.vec.free(next);
|
||||
Some(next)
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.vec.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Widgets {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;
|
||||
|
||||
impl<I: IdLike> std::ops::Index<I> for Widgets
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
type Output = I::Widget;
|
||||
|
||||
fn index(&self, id: I) -> &Self::Output {
|
||||
self.get(&id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: IdLike> std::ops::IndexMut<I> for Widgets
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
fn index_mut(&mut self, id: I) -> &mut Self::Output {
|
||||
self.get_mut(&id).unwrap()
|
||||
}
|
||||
}
|
||||
+5
-8
@@ -1,17 +1,14 @@
|
||||
use iris::prelude::*;
|
||||
use iris::{prelude::*};
|
||||
|
||||
fn main() {
|
||||
DefaultApp::<State>::run();
|
||||
}
|
||||
|
||||
#[derive(DefaultUiState)]
|
||||
struct State {
|
||||
ui_state: DefaultUiState,
|
||||
}
|
||||
struct State;
|
||||
|
||||
impl DefaultAppState for State {
|
||||
fn new(mut ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self>) -> Self {
|
||||
rect(Color::RED).set_root(rsc, &mut ui_state);
|
||||
Self { ui_state }
|
||||
fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
|
||||
rect(Color::RED).set_root(ui);
|
||||
Self
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
//! The seeded random tree `tests/generated.rs` checks, drawn so it can be
|
||||
//! looked at. `IRIS_SEED` and `IRIS_DEPTH` choose which one.
|
||||
|
||||
use iris::prelude::*;
|
||||
use iris::random::Edits;
|
||||
|
||||
fn env(name: &str, fallback: u64) -> u64 {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(fallback)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
DefaultApp::<State>::run();
|
||||
}
|
||||
|
||||
#[derive(DefaultUiState)]
|
||||
struct State {
|
||||
ui_state: DefaultUiState,
|
||||
}
|
||||
|
||||
impl DefaultAppState for State {
|
||||
fn new(mut ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self>) -> Self {
|
||||
let seed = env("IRIS_SEED", 1);
|
||||
let depth = env("IRIS_DEPTH", 4) as usize;
|
||||
let (root, _) = iris::random::grow(rsc, seed, depth, &Edits::default());
|
||||
ui_state.set_root(root);
|
||||
Self { ui_state }
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use iris::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
DefaultApp::<State>::run();
|
||||
}
|
||||
|
||||
#[derive(DefaultUiState)]
|
||||
struct State {
|
||||
ui_state: DefaultUiState,
|
||||
}
|
||||
|
||||
impl DefaultAppState for State {
|
||||
fn new(mut ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self>) -> Self {
|
||||
let rect = rect(Color::RED).add(rsc);
|
||||
rect.task_on(CursorSense::click(), async move |mut ctx| {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
ctx.update(move |_, rsc| {
|
||||
let rect = rect(rsc);
|
||||
if rect.color == Color::RED {
|
||||
rect.color = Color::BLUE;
|
||||
} else {
|
||||
rect.color = Color::RED;
|
||||
}
|
||||
});
|
||||
})
|
||||
.set_root(rsc, &mut ui_state);
|
||||
Self { ui_state }
|
||||
}
|
||||
}
|
||||
@@ -1,67 +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());
|
||||
|
||||
// Each one takes the whole width, because `text_align` puts the
|
||||
// glyphs somewhere in the box the text is given and a text that
|
||||
// reports the width of its own glyphs is given exactly that.
|
||||
let label = |text: &str, align| wtext(text).size(24).text_align(align).width(rel(1.0));
|
||||
let aligned = (
|
||||
label("left", Align::LEFT),
|
||||
label("centred", Align::H_CENTER),
|
||||
label("right", 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 }
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use iris::prelude::*;
|
||||
|
||||
fn main() {
|
||||
DefaultApp::<State>::run();
|
||||
}
|
||||
|
||||
#[derive(DefaultUiState)]
|
||||
struct State {
|
||||
ui_state: DefaultUiState,
|
||||
}
|
||||
|
||||
type Rsc = DefaultRsc<State>;
|
||||
|
||||
#[derive(Clone, Copy, WidgetView)]
|
||||
struct Test {
|
||||
#[root]
|
||||
root: WeakWidget<Rect>,
|
||||
cur: WeakState<bool>,
|
||||
}
|
||||
|
||||
impl Test {
|
||||
pub fn new(rsc: &mut Rsc) -> Self {
|
||||
let root = rect(Color::RED).add(rsc);
|
||||
let cur = rsc.create_state(root, false);
|
||||
Self { root, cur }
|
||||
}
|
||||
pub fn toggle(&self, rsc: &mut Rsc) {
|
||||
let cur = &mut rsc[self.cur];
|
||||
*cur = !*cur;
|
||||
if *cur {
|
||||
rsc[self.root].color = Color::BLUE;
|
||||
} else {
|
||||
rsc[self.root].color = Color::RED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultAppState for State {
|
||||
fn new(mut ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self>) -> Self {
|
||||
let test = Test::new(rsc);
|
||||
|
||||
test.on(CursorSense::click(), move |_, rsc| {
|
||||
test.toggle(rsc);
|
||||
})
|
||||
.set_root(rsc, &mut ui_state);
|
||||
|
||||
Self { ui_state }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.103"
|
||||
quote = "1.0.42"
|
||||
syn = { version = "2.0.111", features = ["full"] }
|
||||
|
||||
|
||||
+5
-124
@@ -2,18 +2,15 @@ extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Attribute, Block, Error, GenericParam, Generics, Ident, ItemStruct, ItemTrait, Signature,
|
||||
Token, Type, Visibility,
|
||||
Attribute, Block, Ident, ItemTrait, Signature, Token, Visibility,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input, parse_quote,
|
||||
spanned::Spanned,
|
||||
};
|
||||
|
||||
struct Input {
|
||||
attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
name: Ident,
|
||||
generics: Generics,
|
||||
fns: Vec<InputFn>,
|
||||
}
|
||||
|
||||
@@ -28,7 +25,6 @@ impl Parse for Input {
|
||||
let vis = input.parse()?;
|
||||
input.parse::<Token![trait]>()?;
|
||||
let name = input.parse()?;
|
||||
let generics = input.parse::<Generics>()?;
|
||||
input.parse::<Token![;]>()?;
|
||||
let mut fns = Vec::new();
|
||||
while !input.is_empty() {
|
||||
@@ -43,7 +39,6 @@ impl Parse for Input {
|
||||
attrs,
|
||||
vis,
|
||||
name,
|
||||
generics,
|
||||
fns,
|
||||
})
|
||||
}
|
||||
@@ -55,7 +50,6 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
|
||||
attrs,
|
||||
vis,
|
||||
name,
|
||||
mut generics,
|
||||
fns,
|
||||
} = parse_macro_input!(input as Input);
|
||||
|
||||
@@ -65,132 +59,19 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
|
||||
.map(|InputFn { sig, body }| quote! { #sig #body })
|
||||
.collect();
|
||||
|
||||
let Some(GenericParam::Type(state)) = generics.params.first() else {
|
||||
return Error::new(name.span(), "expected state generic parameter")
|
||||
.into_compile_error()
|
||||
.into();
|
||||
};
|
||||
|
||||
let state = &state.ident;
|
||||
|
||||
generics
|
||||
.params
|
||||
.push(parse_quote!(WL: WidgetLike<#state, Tag>));
|
||||
generics.params.push(parse_quote!(Tag));
|
||||
|
||||
let mut trai: ItemTrait = parse_quote!(
|
||||
#vis trait #name #generics {
|
||||
#vis trait #name<WL: WidgetLike<Tag>, Tag> {
|
||||
#(#sigs;)*
|
||||
}
|
||||
);
|
||||
|
||||
trai.attrs = attrs;
|
||||
|
||||
quote! {
|
||||
TokenStream::from(quote! {
|
||||
#trai
|
||||
|
||||
impl #generics #name<Rsc, WL, Tag> for WL {
|
||||
impl<WL: WidgetLike<Tag>, Tag> #name<WL, Tag> for WL {
|
||||
#(#impls)*
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(DefaultUiState, attributes(default_ui_state))]
|
||||
pub fn derive_default_ui_state(input: TokenStream) -> TokenStream {
|
||||
let mut output = proc_macro2::TokenStream::new();
|
||||
|
||||
let state: ItemStruct = parse_macro_input!(input);
|
||||
|
||||
let mut found_attr = false;
|
||||
let mut state_field = None;
|
||||
for field in &state.fields {
|
||||
if !found_attr
|
||||
&& let Type::Path(path) = &field.ty
|
||||
&& path.path.is_ident("DefaultUiState")
|
||||
{
|
||||
state_field = Some(field);
|
||||
}
|
||||
let Some(attr) = field
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|a| a.path().is_ident("default_ui_state"))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if found_attr {
|
||||
output.extend(
|
||||
Error::new(
|
||||
attr.span(),
|
||||
"cannot have more than one default_ui_state attribute",
|
||||
)
|
||||
.into_compile_error(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
found_attr = true;
|
||||
state_field = Some(field);
|
||||
}
|
||||
let Some(field) = state_field else {
|
||||
output.extend(
|
||||
Error::new(state.ident.span(), "no DefaultUiState field found").into_compile_error(),
|
||||
);
|
||||
return output.into();
|
||||
};
|
||||
let sname = &state.ident;
|
||||
let fname = field.ident.as_ref().unwrap();
|
||||
output.extend(quote! {
|
||||
impl iris::default::HasDefaultUiState for #sname {
|
||||
fn default_state(&self) -> &iris::default::DefaultUiState {
|
||||
&self.#fname
|
||||
}
|
||||
fn default_state_mut(&mut self) -> &mut iris::default::DefaultUiState {
|
||||
&mut self.#fname
|
||||
}
|
||||
}
|
||||
});
|
||||
output.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(WidgetView, attributes(root))]
|
||||
pub fn derive_widget_view(input: TokenStream) -> TokenStream {
|
||||
let mut output = proc_macro2::TokenStream::new();
|
||||
|
||||
let state: ItemStruct = parse_macro_input!(input);
|
||||
|
||||
let mut found_attr = false;
|
||||
let mut state_field = None;
|
||||
for field in &state.fields {
|
||||
let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("root")) else {
|
||||
continue;
|
||||
};
|
||||
if found_attr {
|
||||
output.extend(
|
||||
Error::new(attr.span(), "cannot have more than one root widget")
|
||||
.into_compile_error(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
found_attr = true;
|
||||
state_field = Some(field);
|
||||
}
|
||||
let Some(field) = state_field else {
|
||||
output.extend(
|
||||
Error::new(state.ident.span(), "no root widget field found (#[root])")
|
||||
.into_compile_error(),
|
||||
);
|
||||
return output.into();
|
||||
};
|
||||
let sname = &state.ident;
|
||||
let fname = field.ident.as_ref().unwrap();
|
||||
let fty = &field.ty;
|
||||
output.extend(quote! {
|
||||
impl iris::core::WidgetView for #sname {
|
||||
type Widget = <#fty as iris::core::HasWidget>::Widget;
|
||||
fn root(&self) -> #fty {
|
||||
self.#fname
|
||||
}
|
||||
}
|
||||
});
|
||||
output.into()
|
||||
})
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
# iris
|
||||
|
||||
My experimental attempt at a rust ui library (also my first ui library).
|
||||
my fisrt attempt at a rust ui library
|
||||
|
||||
It's currently designed around using retained data structures (widgets), rather than diffing generated trees from data like xilem or iced. This is an experiment and I'm not sure if it's a good idea or not.
|
||||
it's called iris because it's the structure around what you actually want to display and colorful
|
||||
|
||||
Examples are in `examples`, eg. `cargo run --example tabs`.
|
||||
there's a `main.rs` that runs a testing window, so can just `cargo run` to see it working
|
||||
|
||||
Goals, in general order:
|
||||
goals, in general order:
|
||||
1. does what I want it to (text, images, video, animations)
|
||||
2. very easy to use ignoring ergonomic ref counting
|
||||
3. reasonably fast / efficient (a lot faster than electron, save battery life, try to beat iced and xilem)
|
||||
3. reasonably fast / efficient (a lot faster than electron, save battery life)
|
||||
|
||||
## dev details
|
||||
|
||||
not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps / not need a web browser
|
||||
not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps
|
||||
|
||||
general ideas trynna use rn / experiment with:
|
||||
- retained mode
|
||||
- specifically designed around wgpu so there's no translation
|
||||
- specifically designed around wgpu
|
||||
- postfix functions for most things to prevent unreadable indentation (going very well)
|
||||
- events can be done directly where you draw the widgets
|
||||
- almost no macros in user code & actual LSP typechecking (variadic generics if you can hear me please save us)
|
||||
- relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout)
|
||||
- single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this)
|
||||
@@ -27,5 +24,3 @@ general ideas trynna use rn / experiment with:
|
||||
|
||||
under heavy initial development so not gonna try to explain status, maybe check TODO for that;
|
||||
sizable chance it gets a rewrite once I know everything I need and what seems to work best
|
||||
|
||||
it's called iris because it's the structure around what you actually want to display and colorful
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "rig-input"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# Replays `.touch` recordings through Wayland's virtual-pointer protocol;
|
||||
# headless sway has no input devices for coordinate-driving tools to move.
|
||||
[[bin]]
|
||||
name = "replay-touch"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Share the harness parser so both ways of replaying read a file the same.
|
||||
iris = { path = ".." }
|
||||
wayland-client = { workspace = true }
|
||||
wayland-protocols-wlr = { workspace = true }
|
||||
@@ -1,141 +0,0 @@
|
||||
use iris::harness::{TouchAction, TouchScript};
|
||||
use std::time::Duration;
|
||||
use wayland_client::protocol::wl_pointer::ButtonState;
|
||||
use wayland_client::protocol::{wl_registry, wl_seat};
|
||||
use wayland_client::{Connection, Dispatch, QueueHandle, delegate_noop};
|
||||
use wayland_protocols_wlr::virtual_pointer::v1::client::{
|
||||
zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1,
|
||||
zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1,
|
||||
};
|
||||
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
|
||||
const SETTLE: Duration = Duration::from_millis(200);
|
||||
|
||||
#[derive(Default)]
|
||||
struct Globals {
|
||||
seat: Option<wl_seat::WlSeat>,
|
||||
manager: Option<ZwlrVirtualPointerManagerV1>,
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for Globals {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
let wl_registry::Event::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
else {
|
||||
return;
|
||||
};
|
||||
match interface.as_str() {
|
||||
"wl_seat" => {
|
||||
state.seat = Some(registry.bind(name, version.min(7), qh, ()));
|
||||
}
|
||||
"zwlr_virtual_pointer_manager_v1" => {
|
||||
state.manager = Some(registry.bind(name, version.min(2), qh, ()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_noop!(Globals: ignore wl_seat::WlSeat);
|
||||
delegate_noop!(Globals: ZwlrVirtualPointerManagerV1);
|
||||
delegate_noop!(Globals: ZwlrVirtualPointerV1);
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let [width, height, path] = args.as_slice() else {
|
||||
eprintln!("usage: replay-touch WIDTH HEIGHT FILE");
|
||||
std::process::exit(2);
|
||||
};
|
||||
let (width, height) = (parse(width, "WIDTH"), parse(height, "HEIGHT"));
|
||||
let text = std::fs::read_to_string(path)
|
||||
.unwrap_or_else(|e| fail(&format!("could not read {path}: {e}")));
|
||||
let script = TouchScript::parse(&text).unwrap_or_else(|e| fail(&e));
|
||||
|
||||
let conn = Connection::connect_to_env().unwrap_or_else(|e| {
|
||||
fail(&format!(
|
||||
"no wayland display ({e}); is WAYLAND_DISPLAY set?"
|
||||
))
|
||||
});
|
||||
let mut queue = conn.new_event_queue();
|
||||
let qh = queue.handle();
|
||||
let display = conn.display();
|
||||
display.get_registry(&qh, ());
|
||||
let mut globals = Globals::default();
|
||||
queue
|
||||
.roundtrip(&mut globals)
|
||||
.unwrap_or_else(|e| fail(&format!("wayland roundtrip failed: {e}")));
|
||||
|
||||
let manager = globals.manager.as_ref().unwrap_or_else(|| {
|
||||
fail(
|
||||
"this compositor does not offer zwlr_virtual_pointer_manager_v1, so a pointer cannot \
|
||||
be synthesised; sway and every wlroots compositor do",
|
||||
)
|
||||
});
|
||||
let pointer = manager.create_virtual_pointer(globals.seat.as_ref(), &qh, ());
|
||||
|
||||
// Put the pointer where the gesture starts and let the compositor
|
||||
// settle before anything is pressed. Without this the press is
|
||||
// dropped: sway has just learned about this pointer, and a button
|
||||
// sent in the same breath as the motion that first puts it over a
|
||||
// window arrives before there is a focused surface to send it to --
|
||||
// winit sees `CursorEntered`, the moves and the *release*, never the
|
||||
// press, so the gesture reads as a hover and nothing scrolls. Found
|
||||
// by printing winit's own events; the settle is what fixed it.
|
||||
if let Some(first) = script.samples.first() {
|
||||
pointer.motion_absolute(0, first.pos.x as u32, first.pos.y as u32, width, height);
|
||||
pointer.frame();
|
||||
conn.flush()
|
||||
.unwrap_or_else(|e| fail(&format!("flush: {e}")));
|
||||
std::thread::sleep(SETTLE);
|
||||
}
|
||||
|
||||
let mut previous = 0;
|
||||
for sample in &script.samples {
|
||||
std::thread::sleep(Duration::from_millis(sample.t_ms - previous));
|
||||
previous = sample.t_ms;
|
||||
let t = sample.t_ms as u32;
|
||||
pointer.motion_absolute(t, sample.pos.x as u32, sample.pos.y as u32, width, height);
|
||||
pointer.frame();
|
||||
// The button goes in a frame of its own, *after* the motion has
|
||||
// been committed. Sent in the same frame as the motion that
|
||||
// first puts the pointer over the window, sway drops it: the
|
||||
// client sees `CursorEntered` and the moves but never a
|
||||
// `MouseInput { state: Pressed }`, so the whole gesture reads as
|
||||
// a hover and nothing scrolls. Found exactly that way, by
|
||||
// printing winit's events.
|
||||
let state = match sample.action {
|
||||
TouchAction::Down => Some(ButtonState::Pressed),
|
||||
TouchAction::Up => Some(ButtonState::Released),
|
||||
TouchAction::Move => None,
|
||||
};
|
||||
if let Some(state) = state {
|
||||
pointer.button(t, BTN_LEFT, state);
|
||||
pointer.frame();
|
||||
}
|
||||
conn.flush()
|
||||
.unwrap_or_else(|e| fail(&format!("flush: {e}")));
|
||||
}
|
||||
pointer.destroy();
|
||||
conn.flush().ok();
|
||||
}
|
||||
|
||||
fn parse(text: &str, what: &str) -> u32 {
|
||||
text.parse()
|
||||
.unwrap_or_else(|_| fail(&format!("{what} is not a whole number: {text:?}")))
|
||||
}
|
||||
|
||||
fn fail(message: &str) -> ! {
|
||||
eprintln!("replay-touch: {message}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["clippy", "rustfmt"]
|
||||
@@ -1,14 +0,0 @@
|
||||
# The compositor `scripts/run-headless.sh` starts, so that an example has a
|
||||
# surface where there is no display. Nothing here is meant to be looked at
|
||||
# directly; `grim` is.
|
||||
#
|
||||
# No Xwayland: winit talks Wayland natively, so an X server is a second thing
|
||||
# to go wrong for no gain.
|
||||
xwayland disable
|
||||
|
||||
# The default output, overridden per run by `--mode`. Larger than the window
|
||||
# an example opens, so nothing is scaled or clipped.
|
||||
output HEADLESS-1 mode 1920x1200@60Hz
|
||||
|
||||
default_border none
|
||||
focus_follows_mouse no
|
||||
@@ -1,182 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Run an iris example on a machine with no display.
|
||||
#
|
||||
# ./scripts/run-headless.sh tabs
|
||||
# ./scripts/run-headless.sh tabs --shot /tmp/tabs.png --seconds 4
|
||||
# ./scripts/run-headless.sh tabs --replay taps.touch --shot /tmp/tabs.png
|
||||
# ./scripts/run-headless.sh app --dir ../elsewhere --mode 1080x2424@120Hz
|
||||
#
|
||||
# `--dir DIR` names the workspace to build in, defaulting to this one, so a
|
||||
# project that depends on iris can be run through the same rig. `--bin` runs a
|
||||
# crate binary rather than an example, and takes its own argv from
|
||||
# `$RUN_HEADLESS_ARGS`, word-split on purpose.
|
||||
#
|
||||
# `--mode` sets the output, for running something at a size other than a
|
||||
# desktop's -- a phone's, say. Set every run rather than only when it changes:
|
||||
# the compositor is reused between runs, so a default-shaped run after a
|
||||
# custom one would otherwise inherit the other's output and quietly screenshot
|
||||
# the wrong size.
|
||||
#
|
||||
# `--resize WxH@Hz` changes the output under the app once it is up, then
|
||||
# screenshots. A resize is its own case: what it has to match is a cold start
|
||||
# at that size, byte for byte, and nothing in `cargo test` can see it.
|
||||
#
|
||||
# `--replay FILE` drives a `.touch` recording into the window through
|
||||
# `replay-touch`, which reads it with the same parser `iris::harness` uses. A
|
||||
# recording is `<ms> down|move|up <x> <y>` in the output's own pixels. With
|
||||
# `--shot` it also writes `<shot>-before.png` from just before the gesture,
|
||||
# since "it moved" is a claim about two pictures.
|
||||
#
|
||||
# What it supplies is a compositor for winit to open a surface on: a headless
|
||||
# sway, and `grim` to screenshot it. Sway gets its own socket and runtime
|
||||
# directory rather than joining whatever else is running, because it tiles --
|
||||
# adding a window to someone else's compositor resizes theirs.
|
||||
set -eu
|
||||
|
||||
need() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "run-headless: $1 is not installed ($2)" >&2
|
||||
exit 127
|
||||
}
|
||||
}
|
||||
need sway "the compositor an example opens its window on"
|
||||
need swaymsg "sway's control socket"
|
||||
|
||||
scripts=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(cd "$scripts/.." && pwd)
|
||||
workdir="$root"
|
||||
cd "$root"
|
||||
run="${XDG_RUNTIME_DIR:-/tmp}/iris-headless"
|
||||
seconds=3
|
||||
shot=""
|
||||
replay=""
|
||||
resize=""
|
||||
example=""
|
||||
kind=example
|
||||
mode=1920x1200@60Hz
|
||||
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--shot) shot=$2; shift 2 ;;
|
||||
--seconds) seconds=$2; shift 2 ;;
|
||||
--bin) kind=bin; shift ;;
|
||||
--mode) mode=$2; shift 2 ;;
|
||||
--resize) resize=$2; shift 2 ;;
|
||||
--replay) replay=$2; shift 2 ;;
|
||||
--dir) workdir=$(cd "$2" && pwd); shift 2 ;;
|
||||
--) shift; break ;;
|
||||
*) example=$1; shift ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$example" ] || { echo "usage: $0 NAME [--bin] [--dir DIR] [--mode WxH@Hz] [--resize WxH@Hz] [--replay TOUCH] [--shot PNG] [--seconds N] [-- cargo args]" >&2; exit 2; }
|
||||
[ -z "$replay" ] || [ -f "$replay" ] || { echo "run-headless: no touch script at $replay" >&2; exit 2; }
|
||||
[ -z "$shot" ] || need grim "the screenshot --shot writes"
|
||||
|
||||
mkdir -p "$run"
|
||||
export SWAYSOCK="$run/sway.sock"
|
||||
|
||||
# Named rather than left to sway's pid-based default, so a second run reuses
|
||||
# this compositor instead of starting another beside it.
|
||||
if ! swaymsg -t get_version >/dev/null 2>&1; then
|
||||
rm -f "$SWAYSOCK"
|
||||
WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 LIBSEAT_BACKEND=noop \
|
||||
setsid sway -c "$scripts/headless.conf" >"$run/sway.log" 2>&1 &
|
||||
i=0
|
||||
while [ $i -lt 20 ]; do
|
||||
swaymsg -t get_version >/dev/null 2>&1 && break
|
||||
i=$((i + 1)); sleep 0.5
|
||||
done
|
||||
swaymsg -t get_version >/dev/null 2>&1 || {
|
||||
echo "run-headless: compositor did not start; see $run/sway.log" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
rm -f "$run/display"
|
||||
swaymsg exec -- "sh -c 'printf %s \"\$WAYLAND_DISPLAY\" > $run/display'" >/dev/null
|
||||
i=0
|
||||
while [ $i -lt 20 ]; do
|
||||
[ -s "$run/display" ] && break
|
||||
i=$((i + 1)); sleep 0.5
|
||||
done
|
||||
[ -s "$run/display" ] || { echo "run-headless: could not read WAYLAND_DISPLAY" >&2; exit 1; }
|
||||
WAYLAND_DISPLAY=$(cat "$run/display")
|
||||
export WAYLAND_DISPLAY
|
||||
|
||||
echo "run-headless: $WAYLAND_DISPLAY (sway $(swaymsg -t get_version --raw | sed -n 's/.*"human_readable":"\([^"]*\)".*/\1/p'))" >&2
|
||||
|
||||
swaymsg output HEADLESS-1 mode "$mode" >/dev/null
|
||||
# The extent `replay-touch` positions against, so a script's coordinates
|
||||
# are the output's own pixels.
|
||||
out_w=${mode%x*}
|
||||
out_h=${mode#*x}; out_h=${out_h%@*}
|
||||
|
||||
# Built before the app starts, so a compile error is not reported as a
|
||||
# window that failed to move.
|
||||
[ -z "$replay" ] || (cd "$root" && cargo build --bin replay-touch -p rig-input) >&2
|
||||
|
||||
cd "$workdir"
|
||||
if [ "$kind" = bin ]; then
|
||||
cargo build --bin "$example" "$@" >&2
|
||||
bin="$workdir/target/debug/$example"
|
||||
else
|
||||
cargo build --example "$example" "$@" >&2
|
||||
bin="$workdir/target/debug/examples/$example"
|
||||
fi
|
||||
|
||||
# Deliberately word-split: this is the binary's own argv, not a single path.
|
||||
# shellcheck disable=SC2086
|
||||
"$bin" ${RUN_HEADLESS_ARGS:-} >"$run/$example.log" 2>&1 &
|
||||
pid=$!
|
||||
trap 'kill "$pid" 2>/dev/null || true' EXIT INT TERM
|
||||
|
||||
# Wait for the window to be mapped rather than for a number of seconds. A
|
||||
# fixed sleep took an all-black screenshot the first time this ran, when sway
|
||||
# had started in the same invocation and had not composited its output yet --
|
||||
# which is indistinguishable from an app that draws nothing.
|
||||
i=0
|
||||
while [ $i -lt 40 ]; do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
swaymsg -t get_tree --raw 2>/dev/null | grep -q "\"pid\":$pid," && break
|
||||
i=$((i + 1)); sleep 0.25
|
||||
done
|
||||
|
||||
i=0
|
||||
while [ $i -lt "$((seconds * 2))" ]; do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
i=$((i + 1)); sleep 0.5
|
||||
done
|
||||
|
||||
if [ -n "$resize" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
swaymsg output HEADLESS-1 mode "$resize" >/dev/null
|
||||
echo "run-headless: resized to $resize" >&2
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
if [ -n "$replay" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
if [ -n "$shot" ]; then
|
||||
grim "${shot%.png}-before.png"
|
||||
echo "run-headless: wrote ${shot%.png}-before.png (before the gesture)" >&2
|
||||
fi
|
||||
"$root/target/debug/replay-touch" "$out_w" "$out_h" "$replay"
|
||||
# A fling outlives the finger: the gesture's own last sample is not
|
||||
# when the list stops. Long enough for Android's spline to settle
|
||||
# (`FlingCalculator::duration` tops out around a second and a half).
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
[ -n "$shot" ] && grim "$shot" && echo "run-headless: wrote $shot" >&2
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
status=0
|
||||
else
|
||||
wait "$pid" 2>/dev/null || status=$?
|
||||
echo "run-headless: $example exited early (status ${status:-0})" >&2
|
||||
status=${status:-1}
|
||||
fi
|
||||
|
||||
echo "--- $example output ---" >&2
|
||||
cat "$run/$example.log" >&2
|
||||
exit "$status"
|
||||
File renamed without changes.
@@ -1,48 +1,43 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
use cosmic_text::Family;
|
||||
use iris::prelude::*;
|
||||
type ClientRsc = DefaultRsc<Client>;
|
||||
use len_fns::*;
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
fn main() {
|
||||
DefaultApp::<Client>::run();
|
||||
}
|
||||
|
||||
#[derive(DefaultUiState)]
|
||||
pub struct Client {
|
||||
ui_state: DefaultUiState,
|
||||
info: WeakWidget<Text>,
|
||||
info: WidgetRef<Text>,
|
||||
}
|
||||
|
||||
event_ctx!(Client);
|
||||
|
||||
impl DefaultAppState for Client {
|
||||
fn new(mut ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self>) -> Self {
|
||||
fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
|
||||
let rrect = rect(Color::WHITE).radius(20);
|
||||
let pad_test = (
|
||||
rrect.color(Color::BLUE),
|
||||
(
|
||||
// The square is one widget and the two shares of the row it
|
||||
// sits centred in are another: a length is a property of a
|
||||
// widget, so `.width` here would overwrite the `.sized`.
|
||||
rrect
|
||||
.color(Color::RED)
|
||||
.sized((100, 100))
|
||||
.center()
|
||||
.wrapper()
|
||||
.width(leftover(2)),
|
||||
.width(rest(2)),
|
||||
(
|
||||
rrect.color(Color::ORANGE),
|
||||
rrect.color(Color::LIME).pad(10.0),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.width(leftover(2)),
|
||||
.width(rest(2)),
|
||||
rrect.color(Color::YELLOW),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.pad(10)
|
||||
.width(leftover(3)),
|
||||
.width(rest(3)),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.add(rsc);
|
||||
.add(ui);
|
||||
|
||||
let span_test = (
|
||||
rrect.color(Color::GREEN).width(100),
|
||||
@@ -53,30 +48,31 @@ impl DefaultAppState for Client {
|
||||
rrect.color(Color::RED).width(100),
|
||||
)
|
||||
.span(Dir::LEFT)
|
||||
.add(rsc);
|
||||
.add(ui);
|
||||
|
||||
let span_add = Span::empty(Dir::RIGHT).add(rsc);
|
||||
let span_add = Span::empty(Dir::RIGHT).add(ui);
|
||||
let span_add_ = span_add.weak();
|
||||
|
||||
let add_button = rect(Color::LIME)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |_, rsc| {
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
let child = image(include_bytes!("assets/sungals.png"))
|
||||
.center()
|
||||
.add_strong(rsc);
|
||||
span_add(rsc).push(child);
|
||||
.add(ctx.data.ui);
|
||||
span_add_.get_mut().children.push(child);
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BOT_RIGHT);
|
||||
|
||||
let del_button = rect(Color::RED)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |_, rsc| {
|
||||
span_add(rsc).pop();
|
||||
.on(CursorSense::click(), move |_| {
|
||||
span_add_.get_mut().children.pop();
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BOT_LEFT);
|
||||
|
||||
let span_add_test = (span_add, add_button, del_button).stack().add(rsc);
|
||||
let span_add_test = (span_add, add_button, del_button).stack().add(ui);
|
||||
|
||||
let btext = |content| wtext(content).size(30);
|
||||
|
||||
@@ -99,40 +95,40 @@ impl DefaultAppState for Client {
|
||||
wtext("pretty cool right?").size(50),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add(rsc);
|
||||
.add(ui);
|
||||
|
||||
let texts = Span::empty(Dir::DOWN).gap(10).add(rsc);
|
||||
let msg_area = texts.scrollable().masked().background(rect(Color::SKY));
|
||||
let texts = Span::empty(Dir::DOWN).gap(10).add(ui);
|
||||
let texts_ = texts.weak();
|
||||
let msg_area = texts.scroll().masked().background(rect(Color::SKY));
|
||||
let add_text = wtext("add")
|
||||
.editable(EditMode::MultiLine)
|
||||
.editable(false)
|
||||
.text_align(Align::LEFT)
|
||||
.size(30)
|
||||
.attr::<Selectable>(())
|
||||
.on(Submit, move |ctx, rsc| {
|
||||
let w = ctx.widget;
|
||||
let content = w.edit(rsc).take();
|
||||
.on(Submit, move |ctx| {
|
||||
let content = ctx.widget.get_mut().take();
|
||||
let text = wtext(content)
|
||||
.editable(EditMode::MultiLine)
|
||||
.editable(false)
|
||||
.size(30)
|
||||
.text_align(Align::LEFT)
|
||||
.wrap(true)
|
||||
.attr::<Selectable>(());
|
||||
let msg_box = text
|
||||
.background(rect(Color::WHITE.darker(0.5)))
|
||||
.add_strong(rsc);
|
||||
texts(rsc).push(msg_box);
|
||||
.add(&mut Ui::new());
|
||||
texts_.get_mut().children.push(msg_box);
|
||||
})
|
||||
.add(rsc);
|
||||
|
||||
.add(ui);
|
||||
let add_text_ = add_text.weak();
|
||||
let text_edit_scroll = (
|
||||
msg_area.height(leftover(1)),
|
||||
msg_area.height(rest(1)),
|
||||
(
|
||||
Rect::new(Color::WHITE.darker(0.9)),
|
||||
(
|
||||
add_text.width(leftover(1)),
|
||||
add_text.width(rest(1)),
|
||||
Rect::new(Color::GREEN)
|
||||
.on(CursorSense::click(), move |ctx, rsc: &mut ClientRsc| {
|
||||
rsc.run_event::<Submit>(add_text, (), ctx.state);
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
Events::<Submit, _>::run(add_text_.id(), &mut (), ctx.state);
|
||||
})
|
||||
.sized((40, 40)),
|
||||
)
|
||||
@@ -145,80 +141,73 @@ impl DefaultAppState for Client {
|
||||
.align(Align::BOT),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add(rsc);
|
||||
.add(ui);
|
||||
|
||||
let main = Wrapper::new().add(rsc);
|
||||
let main = pad_test.pad(10).add(ui);
|
||||
let main_ = main.weak();
|
||||
|
||||
let vals = Rc::new(RefCell::new((0, Vec::new())));
|
||||
let mut switch_button = |color, to: WeakWidget, label| {
|
||||
let to = to.upgrade(rsc);
|
||||
let vec = &mut vals.borrow_mut().1;
|
||||
let i = vec.len();
|
||||
if vec.is_empty() {
|
||||
vec.push(None);
|
||||
main(rsc).set(to);
|
||||
} else {
|
||||
vec.push(Some(to));
|
||||
}
|
||||
let vals = vals.clone();
|
||||
let tab_handles = Handle::from((0, Vec::new()));
|
||||
|
||||
let switch_button = |color, to: Option<WidgetHandle>, label| {
|
||||
let tab_handles = tab_handles.clone();
|
||||
let i = tab_handles.get().1.len();
|
||||
tab_handles.get_mut().1.push(to);
|
||||
let rect = rect(color)
|
||||
.on(CursorSense::click(), move |ctx, rsc| {
|
||||
let (prev, vec) = &mut *vals.borrow_mut();
|
||||
if let Some(h) = vec[i].take() {
|
||||
vec[*prev] = main(rsc).replace(h);
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
let (prev, all) = &mut *tab_handles.get_mut();
|
||||
if let Some(to) = &mut all[i] {
|
||||
let mut main = main_.get_mut();
|
||||
std::mem::swap(&mut main.inner, to);
|
||||
all.swap(*prev, i);
|
||||
*prev = i;
|
||||
}
|
||||
ctx.widget(rsc).color = color.darker(0.3);
|
||||
ctx.widget.get_mut().color = color.darker(0.3);
|
||||
})
|
||||
.on(
|
||||
CursorSense::HoverStart | CursorSense::unclick(),
|
||||
move |ctx, rsc| {
|
||||
ctx.widget(rsc).color = color.brighter(0.2);
|
||||
move |ctx| {
|
||||
ctx.widget.get_mut().color = color.brighter(0.2);
|
||||
},
|
||||
)
|
||||
.on(CursorSense::HoverEnd, move |ctx, rsc| {
|
||||
ctx.widget(rsc).color = color;
|
||||
.on(CursorSense::HoverEnd, move |ctx| {
|
||||
ctx.widget.get_mut().color = color;
|
||||
});
|
||||
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
|
||||
};
|
||||
|
||||
let tabs = (
|
||||
switch_button(Color::RED, pad_test, "pad"),
|
||||
switch_button(Color::GREEN, span_test, "span"),
|
||||
switch_button(Color::BLUE, span_add_test, "image span"),
|
||||
switch_button(Color::MAGENTA, text_test, "text layout"),
|
||||
switch_button(Color::RED, None, "pad"),
|
||||
switch_button(Color::GREEN, Some(span_test), "span"),
|
||||
switch_button(Color::BLUE, Some(span_add_test), "image span"),
|
||||
switch_button(Color::MAGENTA, Some(text_test), "text layout"),
|
||||
switch_button(
|
||||
Color::YELLOW.mul_rgb(0.5),
|
||||
text_edit_scroll,
|
||||
Some(text_edit_scroll),
|
||||
"text edit scroll",
|
||||
),
|
||||
)
|
||||
.span(Dir::RIGHT);
|
||||
|
||||
let info = wtext("").add(rsc);
|
||||
let info_sect = info.pad(10).align(Align::RIGHT);
|
||||
let info_ = wtext("").add(ui);
|
||||
let info = info_.weak();
|
||||
let info_sect = info_.pad(10).align(Align::RIGHT);
|
||||
|
||||
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect)
|
||||
((tabs.height(40), main).span(Dir::DOWN), info_sect)
|
||||
.stack()
|
||||
.set_root(rsc, &mut ui_state);
|
||||
.set_root(ui);
|
||||
|
||||
Self { ui_state, info }
|
||||
Self { info }
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
_: WindowEvent,
|
||||
rsc: &mut DefaultRsc<Self>,
|
||||
render: &mut UiRenderState,
|
||||
) {
|
||||
fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) {
|
||||
let new = format!(
|
||||
"widgets: {}\nactive: {}\ntextures: {}",
|
||||
rsc.widgets().len(),
|
||||
render.active_widgets(),
|
||||
rsc.ui().textures.count(),
|
||||
"widgets: {}\nactive:{}\nviews: {}",
|
||||
total_widgets(),
|
||||
ui.active_widgets(),
|
||||
state.renderer.ui.view_count()
|
||||
);
|
||||
if new != *rsc.widgets()[self.info].content {
|
||||
*rsc.widgets_mut()[self.info].content = new;
|
||||
if new != *self.info.get().content {
|
||||
*self.info.get_mut().content = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,6 @@ pub trait AppState {
|
||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
|
||||
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
|
||||
fn exit(&mut self);
|
||||
|
||||
fn run()
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
App::<Self>::run();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct App<State: AppState> {
|
||||
|
||||
+25
-41
@@ -1,52 +1,38 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{default::UiState, prelude::*};
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
event_ctx!(UiState);
|
||||
|
||||
pub struct Selector;
|
||||
|
||||
impl<Rsc: HasEvents, W: Widget + 'static> WidgetAttr<Rsc, W> for Selector
|
||||
where
|
||||
Rsc::State: HasDefaultUiState,
|
||||
{
|
||||
type Input = WeakWidget<TextEdit>;
|
||||
impl<W: Widget + 'static> WidgetAttr<W> for Selector {
|
||||
type Input = WidgetRef<TextEdit>;
|
||||
|
||||
fn run(rsc: &mut Rsc, container: WeakWidget<W>, id: Self::Input) {
|
||||
rsc.register_event(container, CursorSense::click_or_drag(), move |ctx, rsc| {
|
||||
let region = ctx.data.render.window_region(&id).unwrap();
|
||||
fn run(ui: &mut Ui, container: WidgetRef<W>, id: Self::Input) {
|
||||
container.on(CursorSense::click_or_drag(), move |ctx| {
|
||||
let ui = &mut ctx.data.ui;
|
||||
let region = ui.window_region(&id).unwrap();
|
||||
let id_pos = region.top_left;
|
||||
let container_pos = ctx.data.render.window_region(&container).unwrap().top_left;
|
||||
// The pointer arrives from the platform in floats; everything
|
||||
// it is compared against is on the grid.
|
||||
let pos = (PxVec2::from_f32(ctx.data.pos) + container_pos - id_pos).to_f32();
|
||||
let size = region.size().to_f32();
|
||||
select(
|
||||
rsc,
|
||||
ctx.data.render,
|
||||
ctx.state,
|
||||
id,
|
||||
pos,
|
||||
size,
|
||||
ctx.data.sense.is_dragging(),
|
||||
);
|
||||
let container_pos = ui.window_region(&container).unwrap().top_left;
|
||||
let pos = ctx.data.pos + container_pos - id_pos;
|
||||
let size = region.size();
|
||||
select(ui, id, ctx.state, pos, size, ctx.data.sense.is_dragging());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Selectable;
|
||||
|
||||
impl<Rsc: HasEvents> WidgetAttr<Rsc, TextEdit> for Selectable
|
||||
where
|
||||
Rsc::State: HasDefaultUiState,
|
||||
{
|
||||
impl WidgetAttr<TextEdit> for Selectable {
|
||||
type Input = ();
|
||||
|
||||
fn run(rsc: &mut Rsc, id: WeakWidget<TextEdit>, _: Self::Input) {
|
||||
rsc.register_event(id, CursorSense::click_or_drag(), move |ctx, rsc| {
|
||||
fn run(ui: &mut Ui, id: WidgetRef<TextEdit>, _: Self::Input) {
|
||||
id.on(CursorSense::click_or_drag(), move |ctx| {
|
||||
select(
|
||||
rsc,
|
||||
ctx.data.render,
|
||||
ctx.state,
|
||||
ctx.data.ui,
|
||||
id,
|
||||
ctx.state,
|
||||
ctx.data.pos,
|
||||
ctx.data.size,
|
||||
ctx.data.sense.is_dragging(),
|
||||
@@ -56,24 +42,22 @@ where
|
||||
}
|
||||
|
||||
fn select(
|
||||
rsc: &mut impl UiRsc,
|
||||
render: &UiRenderState,
|
||||
state: &mut impl HasDefaultUiState,
|
||||
id: WeakWidget<TextEdit>,
|
||||
ui: &mut Ui,
|
||||
id: WidgetRef<TextEdit>,
|
||||
state: &mut UiState,
|
||||
pos: Vec2,
|
||||
size: Vec2,
|
||||
dragging: bool,
|
||||
) {
|
||||
let state = state.default_state_mut();
|
||||
let now = Instant::now();
|
||||
let recent = (now - state.last_click) < Duration::from_millis(300);
|
||||
state.last_click = now;
|
||||
id.edit(rsc).select(pos, size, dragging, recent);
|
||||
if let Some(region) = render.window_region(&id) {
|
||||
id.get_mut().select(pos, size, dragging, recent);
|
||||
if let Some(region) = ui.window_region(&id) {
|
||||
state.window.set_ime_allowed(true);
|
||||
state.window.set_ime_cursor_area(
|
||||
LogicalPosition::<f32>::from(region.top_left.to_f32().tuple()),
|
||||
LogicalSize::<f32>::from(region.size().to_f32().tuple()),
|
||||
LogicalPosition::<f32>::from(region.top_left.tuple()),
|
||||
LogicalSize::<f32>::from(region.size().tuple()),
|
||||
);
|
||||
}
|
||||
state.focus = Some(id);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use iris_core::Event;
|
||||
use iris_core::layout::Event;
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct Submit;
|
||||
impl Event for Submit {}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct Edited;
|
||||
impl Event for Edited {}
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
widget::{CursorState, Modifiers},
|
||||
layout::Vec2,
|
||||
default::UiState,
|
||||
};
|
||||
use winit::{
|
||||
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
||||
keyboard::{Key, NamedKey},
|
||||
@@ -66,7 +70,7 @@ impl Input {
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultUiState {
|
||||
impl UiState {
|
||||
pub fn window_size(&self) -> Vec2 {
|
||||
let size = self.renderer.window().inner_size();
|
||||
(size.width, size.height).into()
|
||||
|
||||
+71
-258
@@ -1,82 +1,67 @@
|
||||
use crate::prelude::*;
|
||||
use arboard::Clipboard;
|
||||
use std::{marker::PhantomData, sync::Arc, time::Instant};
|
||||
use winit::{
|
||||
event::{Ime, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||
window::{Window, WindowAttributes},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use winit::event::{Ime, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
|
||||
use winit::window::{Window, WindowAttributes};
|
||||
|
||||
mod app;
|
||||
mod attr;
|
||||
mod event;
|
||||
mod input;
|
||||
mod render;
|
||||
mod sense;
|
||||
mod state;
|
||||
mod task;
|
||||
|
||||
pub use app::*;
|
||||
pub use attr::*;
|
||||
pub use event::*;
|
||||
pub use input::*;
|
||||
pub use render::*;
|
||||
pub use sense::*;
|
||||
pub use state::*;
|
||||
pub use task::*;
|
||||
|
||||
/// Sends an application's own events to its event loop. It wraps the proxy
|
||||
/// rather than being one because task updates travel the same way: what an
|
||||
/// application sends is its `Event`, not the loop's whole message type.
|
||||
pub struct Proxy<State: DefaultAppState>(EventLoopProxy<DefaultEvent<State>>);
|
||||
pub type Proxy<Event> = EventLoopProxy<Event>;
|
||||
pub type DefaultApp<Data> = App<DefaultState<Data>>;
|
||||
|
||||
impl<State: DefaultAppState> Clone for Proxy<State> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
pub struct DefaultState<AppState> {
|
||||
ui: Ui,
|
||||
ui_state: UiState,
|
||||
app_state: AppState,
|
||||
}
|
||||
|
||||
impl<State: DefaultAppState> Proxy<State> {
|
||||
pub fn send_event(&self, event: State::Event) {
|
||||
let _ = self.0.send_event(DefaultEvent::User(event));
|
||||
}
|
||||
}
|
||||
|
||||
/// What the event loop carries: the application's own events, and the
|
||||
/// updates tasks send back to the ui thread.
|
||||
pub enum DefaultEvent<State: DefaultAppState> {
|
||||
User(State::Event),
|
||||
Update(Box<dyn TaskUpdate<DefaultRsc<State>>>),
|
||||
}
|
||||
|
||||
impl<State: DefaultAppState> TaskQueue<DefaultRsc<State>> for Proxy<State> {
|
||||
fn send(&self, update: Box<dyn TaskUpdate<DefaultRsc<State>>>) {
|
||||
let _ = self.0.send_event(DefaultEvent::Update(update));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultUiState {
|
||||
pub root: Option<StrongWidget>,
|
||||
pub struct UiState {
|
||||
pub renderer: UiRenderer,
|
||||
pub input: Input,
|
||||
pub focus: Option<WeakWidget<TextEdit>>,
|
||||
pub focus: Option<WidgetRef<TextEdit>>,
|
||||
pub clipboard: Clipboard,
|
||||
pub window: Arc<Window>,
|
||||
pub ime: usize,
|
||||
pub last_click: Instant,
|
||||
}
|
||||
|
||||
impl HasRoot for DefaultUiState {
|
||||
fn set_root(&mut self, root: StrongWidget) {
|
||||
self.root = Some(root);
|
||||
pub trait DefaultAppState: 'static {
|
||||
type Event: 'static = ();
|
||||
fn new(ui: &mut Ui, state: &UiState, proxy: Proxy<Self::Event>) -> Self;
|
||||
#[allow(unused_variables)]
|
||||
fn event(&mut self, event: Self::Event, ui: &mut Ui, state: &UiState) {}
|
||||
#[allow(unused_variables)]
|
||||
fn exit(&mut self, ui: &mut Ui, state: &UiState) {}
|
||||
#[allow(unused_variables)]
|
||||
fn window_event(&mut self, event: WindowEvent, ui: &mut Ui, state: &UiState) {}
|
||||
fn window_attrs() -> WindowAttributes {
|
||||
WindowAttributes::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultUiState {
|
||||
pub fn new(window: impl Into<Arc<Window>>) -> Self {
|
||||
let window = window.into();
|
||||
Self {
|
||||
root: None,
|
||||
impl<State: DefaultAppState> AppState for DefaultState<State> {
|
||||
type Event = State::Event;
|
||||
|
||||
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(State::window_attrs())
|
||||
.expect("failed to create window "),
|
||||
);
|
||||
let mut ui = Ui::new();
|
||||
let ui_state = UiState {
|
||||
renderer: UiRenderer::new(window.clone()),
|
||||
window,
|
||||
input: Input::default(),
|
||||
@@ -84,150 +69,26 @@ impl DefaultUiState {
|
||||
ime: 0,
|
||||
last_click: Instant::now(),
|
||||
focus: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasDefaultUiState: Sized + 'static {
|
||||
fn default_state(&self) -> &DefaultUiState;
|
||||
fn default_state_mut(&mut self) -> &mut DefaultUiState;
|
||||
}
|
||||
|
||||
pub trait DefaultAppState: HasDefaultUiState {
|
||||
type Event: Send = ();
|
||||
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, proxy: Proxy<Self>) -> Self;
|
||||
#[allow(unused_variables)]
|
||||
fn event(
|
||||
&mut self,
|
||||
event: Self::Event,
|
||||
rsc: &mut DefaultRsc<Self>,
|
||||
render: &mut UiRenderState,
|
||||
) {
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn exit(&mut self, rsc: &mut DefaultRsc<Self>, render: &mut UiRenderState) {}
|
||||
#[allow(unused_variables)]
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event: WindowEvent,
|
||||
rsc: &mut DefaultRsc<Self>,
|
||||
render: &mut UiRenderState,
|
||||
) {
|
||||
}
|
||||
fn window_attributes() -> WindowAttributes {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultRsc<State: 'static> {
|
||||
pub ui: UiData,
|
||||
pub events: EventManager<Self>,
|
||||
pub tasks: Tasks<Self>,
|
||||
pub state: WidgetState,
|
||||
_state: PhantomData<State>,
|
||||
}
|
||||
|
||||
impl<State> DefaultRsc<State> {
|
||||
pub fn init(queue: Arc<dyn TaskQueue<Self>>) -> Self {
|
||||
};
|
||||
let app_state = State::new(&mut ui, &ui_state, proxy);
|
||||
Self {
|
||||
ui: Default::default(),
|
||||
events: Default::default(),
|
||||
tasks: Tasks::init(queue),
|
||||
state: Default::default(),
|
||||
_state: Default::default(),
|
||||
ui,
|
||||
ui_state,
|
||||
app_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_state<T: 'static>(&mut self, id: impl IdLike, data: T) -> WeakState<T> {
|
||||
self.state.add(id.id(), data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<State> UiRsc for DefaultRsc<State> {
|
||||
fn ui(&self) -> &UiData {
|
||||
&self.ui
|
||||
}
|
||||
|
||||
fn ui_mut(&mut self) -> &mut UiData {
|
||||
&mut self.ui
|
||||
}
|
||||
|
||||
fn on_draw(&mut self, active: &ActiveData) {
|
||||
self.events.draw(active);
|
||||
}
|
||||
|
||||
fn on_undraw(&mut self, active: &ActiveData) {
|
||||
self.events.undraw(active);
|
||||
}
|
||||
|
||||
fn on_remove(&mut self, id: WidgetId) {
|
||||
self.events.remove(id);
|
||||
self.state.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: 'static> HasState for DefaultRsc<State> {
|
||||
type State = State;
|
||||
}
|
||||
|
||||
impl<State: 'static> HasEvents for DefaultRsc<State> {
|
||||
fn events(&self) -> &EventManager<Self> {
|
||||
&self.events
|
||||
}
|
||||
|
||||
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
||||
&mut self.events
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: 'static> HasTasks for DefaultRsc<State> {
|
||||
fn tasks_mut(&mut self) -> &mut Tasks<Self> {
|
||||
&mut self.tasks
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: 'static> HasWidgetState for DefaultRsc<State> {
|
||||
fn widget_state(&self) -> &WidgetState {
|
||||
&self.state
|
||||
}
|
||||
|
||||
fn widget_state_mut(&mut self) -> &mut WidgetState {
|
||||
&mut self.state
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultApp<State: DefaultAppState> {
|
||||
rsc: DefaultRsc<State>,
|
||||
render: UiRenderState,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
||||
type Event = DefaultEvent<State>;
|
||||
|
||||
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
|
||||
let window = event_loop
|
||||
.create_window(State::window_attributes())
|
||||
.unwrap();
|
||||
let default_state = DefaultUiState::new(window);
|
||||
let mut rsc = DefaultRsc::init(Arc::new(Proxy(proxy.clone())));
|
||||
let state = State::new(default_state, &mut rsc, Proxy(proxy));
|
||||
let render = UiRenderState::new();
|
||||
Self { rsc, state, render }
|
||||
}
|
||||
|
||||
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
|
||||
match event {
|
||||
DefaultEvent::User(event) => self.state.event(event, &mut self.rsc, &mut self.render),
|
||||
DefaultEvent::Update(update) => update(&mut self.state, &mut self.rsc),
|
||||
}
|
||||
self.request_redraw_if_needed();
|
||||
self.app_state.event(event, &mut self.ui, &self.ui_state);
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||
let Self { rsc, render, state } = self;
|
||||
let Self {
|
||||
ui,
|
||||
ui_state,
|
||||
app_state,
|
||||
} = self;
|
||||
|
||||
let ui_state = state.default_state_mut();
|
||||
let input_changed = ui_state.input.event(&event);
|
||||
let cursor_state = ui_state.cursor_state().clone();
|
||||
let old = ui_state.focus;
|
||||
@@ -235,43 +96,47 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
||||
ui_state.focus = None;
|
||||
}
|
||||
if input_changed {
|
||||
render.run_sensors(rsc, state, cursor_state);
|
||||
let window_size = ui_state.window_size();
|
||||
// call sensors with all 3 important contexts
|
||||
// TODO: allow user to specify custom contexts?
|
||||
// and give them both states in case they need both
|
||||
ui.run_sensors(&mut (), &cursor_state, window_size);
|
||||
ui.run_sensors(ui_state, &cursor_state, window_size);
|
||||
ui.run_sensors(app_state, &cursor_state, window_size);
|
||||
}
|
||||
let ui_state = state.default_state_mut();
|
||||
if old != ui_state.focus
|
||||
&& let Some(old) = old
|
||||
{
|
||||
old.edit(rsc).deselect();
|
||||
old.get_mut().deselect();
|
||||
}
|
||||
match &event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
render.update(&ui_state.root, rsc);
|
||||
ui_state.renderer.update(&mut rsc.ui, render);
|
||||
ui_state.renderer.update(ui);
|
||||
ui_state.renderer.draw();
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
render.resize((size.width, size.height), rsc.widgets_mut());
|
||||
ui.resize((size.width, size.height));
|
||||
ui_state.renderer.resize(size)
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
if let Some(sel) = ui_state.focus
|
||||
&& event.state.is_pressed()
|
||||
{
|
||||
let mut text = sel.edit(rsc);
|
||||
match text.apply_event(event, &ui_state.input.modifiers) {
|
||||
let res = sel.get_mut().apply_event(event, &ui_state.input.modifiers);
|
||||
match res {
|
||||
TextInputResult::Unfocus => {
|
||||
ui_state.focus = None;
|
||||
ui_state.window.set_ime_allowed(false);
|
||||
}
|
||||
TextInputResult::Submit => {
|
||||
rsc.run_event::<Submit>(sel, (), state);
|
||||
Events::<Submit, _>::run(sel.id(), &mut (), app_state);
|
||||
}
|
||||
TextInputResult::Paste => {
|
||||
if let Ok(t) = ui_state.clipboard.get_text() {
|
||||
text.insert(&t);
|
||||
sel.get_mut().insert(&t);
|
||||
}
|
||||
rsc.run_event::<Edited>(sel, (), state);
|
||||
Events::<Edited, _>::run(sel.id(), &mut (), app_state);
|
||||
}
|
||||
TextInputResult::Copy(text) => {
|
||||
if let Err(err) = ui_state.clipboard.set_text(text) {
|
||||
@@ -279,89 +144,37 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
||||
}
|
||||
}
|
||||
TextInputResult::Used => {
|
||||
rsc.run_event::<Edited>(sel, (), state);
|
||||
Events::<Edited, _>::run(sel.id(), &mut (), app_state);
|
||||
}
|
||||
TextInputResult::Unused => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::Ime(ime) => {
|
||||
if let Some(sel) = ui_state.focus {
|
||||
let mut text = sel.edit(rsc);
|
||||
if let Some(sel) = &ui_state.focus {
|
||||
match ime {
|
||||
Ime::Enabled | Ime::Disabled => (),
|
||||
Ime::Preedit(content, _pos) => {
|
||||
// TODO: highlight once that's real
|
||||
text.replace(ui_state.ime, content);
|
||||
sel.get_mut().replace(ui_state.ime, content);
|
||||
ui_state.ime = content.chars().count();
|
||||
}
|
||||
Ime::Commit(content) => {
|
||||
text.insert(content);
|
||||
sel.get_mut().insert(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
state.window_event(event, rsc, render);
|
||||
self.request_redraw_if_needed();
|
||||
self.state.default_state_mut().input.end_frame();
|
||||
app_state.window_event(event, ui, ui_state);
|
||||
if ui.update() {
|
||||
ui_state.renderer.window().request_redraw();
|
||||
}
|
||||
ui_state.input.end_frame();
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.state.exit(&mut self.rsc, &mut self.render);
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: DefaultAppState> DefaultApp<State> {
|
||||
fn request_redraw_if_needed(&mut self) {
|
||||
let ui_state = self.state.default_state_mut();
|
||||
if self.render.needs_redraw(&ui_state.root, self.rsc.widgets()) {
|
||||
ui_state.renderer.window().request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RscIdx<Rsc> {
|
||||
type Output;
|
||||
fn get(self, rsc: &Rsc) -> &Self::Output;
|
||||
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output;
|
||||
}
|
||||
|
||||
impl<State: 'static, I: RscIdx<DefaultRsc<State>>> std::ops::Index<I> for DefaultRsc<State> {
|
||||
type Output = I::Output;
|
||||
|
||||
fn index(&self, index: I) -> &Self::Output {
|
||||
index.get(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: 'static, I: RscIdx<DefaultRsc<State>>> std::ops::IndexMut<I> for DefaultRsc<State> {
|
||||
fn index_mut(&mut self, index: I) -> &mut Self::Output {
|
||||
index.get_mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget, Rsc: UiRsc> RscIdx<Rsc> for WeakWidget<W> {
|
||||
type Output = W;
|
||||
|
||||
fn get(self, rsc: &Rsc) -> &Self::Output {
|
||||
&rsc.ui().widgets[self]
|
||||
}
|
||||
|
||||
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output {
|
||||
&mut rsc.ui_mut().widgets[self]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, Rsc: HasWidgetState> RscIdx<Rsc> for WeakState<T> {
|
||||
type Output = T;
|
||||
|
||||
fn get(self, rsc: &Rsc) -> &Self::Output {
|
||||
rsc.widget_state().get(self)
|
||||
}
|
||||
|
||||
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output {
|
||||
rsc.widget_state_mut().get_mut(self)
|
||||
self.app_state.exit(&mut self.ui, &self.ui_state);
|
||||
}
|
||||
}
|
||||
+29
-31
@@ -1,7 +1,10 @@
|
||||
use iris_core::{UiData, UiRenderNode, UiRenderState};
|
||||
use crate::{
|
||||
layout::Ui,
|
||||
render::{UiLimits, UiRenderNode},
|
||||
};
|
||||
use pollster::FutureExt;
|
||||
use std::sync::Arc;
|
||||
use wgpu::*;
|
||||
use wgpu::{util::StagingBelt, *};
|
||||
use winit::{dpi::PhysicalSize, window::Window};
|
||||
|
||||
pub const CLEAR_COLOR: Color = Color::BLACK;
|
||||
@@ -13,29 +16,17 @@ pub struct UiRenderer {
|
||||
queue: Queue,
|
||||
config: SurfaceConfiguration,
|
||||
encoder: CommandEncoder,
|
||||
staging_belt: StagingBelt,
|
||||
pub ui: UiRenderNode,
|
||||
}
|
||||
|
||||
impl UiRenderer {
|
||||
pub fn update(&mut self, ui: &mut UiData, render: &mut UiRenderState) {
|
||||
self.ui.update(&self.device, &self.queue, ui, render);
|
||||
pub fn update(&mut self, updates: &mut Ui) {
|
||||
self.ui.update(&self.device, &self.queue, updates);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) {
|
||||
let (output, suboptimal) = match self.surface.get_current_texture() {
|
||||
CurrentSurfaceTexture::Success(texture) => (texture, false),
|
||||
// Used for this frame, and the swapchain rebuilt after it has
|
||||
// been presented: configuring the surface while a texture it
|
||||
// handed out is still alive panics.
|
||||
CurrentSurfaceTexture::Suboptimal(texture) => (texture, true),
|
||||
CurrentSurfaceTexture::Outdated | CurrentSurfaceTexture::Lost => {
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
return;
|
||||
}
|
||||
CurrentSurfaceTexture::Timeout
|
||||
| CurrentSurfaceTexture::Occluded
|
||||
| CurrentSurfaceTexture::Validation => return,
|
||||
};
|
||||
let output = self.surface.get_current_texture().unwrap();
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&TextureViewDescriptor::default());
|
||||
@@ -58,18 +49,16 @@ impl UiRenderer {
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
self.window.pre_present_notify();
|
||||
self.queue.present(output);
|
||||
if suboptimal {
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
self.staging_belt.finish();
|
||||
output.present();
|
||||
self.staging_belt.recall();
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
||||
self.config.width = size.width;
|
||||
self.config.height = size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
self.ui.resize((size.width, size.height), &self.queue);
|
||||
self.ui.resize(size, &self.queue);
|
||||
}
|
||||
|
||||
fn create_encoder(device: &Device) -> CommandEncoder {
|
||||
@@ -81,10 +70,10 @@ impl UiRenderer {
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = Instance::new(InstanceDescriptor {
|
||||
let instance = Instance::new(&InstanceDescriptor {
|
||||
backends: Backends::PRIMARY,
|
||||
display: Some(Box::new(window.clone())),
|
||||
..InstanceDescriptor::new_without_display_handle()
|
||||
flags: InstanceFlags::empty(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let surface = instance
|
||||
@@ -96,14 +85,22 @@ impl UiRenderer {
|
||||
power_preference: PowerPreference::default(),
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
apply_limit_buckets: false,
|
||||
})
|
||||
.block_on()
|
||||
.expect("Could not get adapter!");
|
||||
|
||||
let ui_limits = UiLimits::default();
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&DeviceDescriptor {
|
||||
required_features: Features::TEXTURE_BINDING_ARRAY
|
||||
| Features::PARTIALLY_BOUND_BINDING_ARRAY
|
||||
| Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
||||
required_limits: Limits {
|
||||
max_binding_array_elements_per_shader_stage: ui_limits
|
||||
.max_binding_array_elements_per_shader_stage(),
|
||||
max_binding_array_sampler_elements_per_shader_stage: ui_limits
|
||||
.max_binding_array_sampler_elements_per_shader_stage(),
|
||||
max_buffer_size: 1 << 30,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -123,7 +120,6 @@ impl UiRenderer {
|
||||
let config = SurfaceConfiguration {
|
||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface_format,
|
||||
color_space: SurfaceColorSpace::Auto,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: PresentMode::AutoVsync,
|
||||
@@ -134,9 +130,10 @@ impl UiRenderer {
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let staging_belt = StagingBelt::new(4096 * 4);
|
||||
let encoder = Self::create_encoder(&device);
|
||||
|
||||
let ui = UiRenderNode::new(&device, &config);
|
||||
let shape_pipeline = UiRenderNode::new(&device, &queue, &config, ui_limits);
|
||||
|
||||
Self {
|
||||
surface,
|
||||
@@ -144,7 +141,8 @@ impl UiRenderer {
|
||||
queue,
|
||||
config,
|
||||
encoder,
|
||||
ui,
|
||||
staging_belt,
|
||||
ui: shape_pipeline,
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
use iris_core::{
|
||||
WidgetId,
|
||||
util::{HashMap, HashSet},
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct Key {
|
||||
id: WidgetId,
|
||||
ty: TypeId,
|
||||
i: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WidgetState {
|
||||
widgets: HashMap<WidgetId, HashSet<(TypeId, usize)>>,
|
||||
counts: HashMap<(WidgetId, TypeId), usize>,
|
||||
map: HashMap<Key, Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl WidgetState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn add<T: 'static>(&mut self, id: WidgetId, data: T) -> WeakState<T> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let count = self.counts.entry((id, ty)).or_default();
|
||||
let i = *count;
|
||||
let key = Key { ty, i, id };
|
||||
self.map.insert(key, Box::new(data));
|
||||
self.widgets.entry(id).or_default().insert((ty, i));
|
||||
*count += 1;
|
||||
WeakState {
|
||||
key,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, id: WidgetId) {
|
||||
for &(ty, i) in self.widgets.get(&id).into_iter().flatten() {
|
||||
self.map.remove(&Key { id, ty, i });
|
||||
}
|
||||
}
|
||||
pub fn get<T: 'static>(&self, state: WeakState<T>) -> &T {
|
||||
self.map.get(&state.key).unwrap().downcast_ref().unwrap()
|
||||
}
|
||||
pub fn get_mut<T: 'static>(&mut self, state: WeakState<T>) -> &mut T {
|
||||
self.map
|
||||
.get_mut(&state.key)
|
||||
.unwrap()
|
||||
.downcast_mut()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct WeakState<T> {
|
||||
key: Key,
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub trait HasWidgetState {
|
||||
fn widget_state(&self) -> &WidgetState;
|
||||
fn widget_state_mut(&mut self) -> &mut WidgetState;
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> FnOnce<(&'a mut WidgetState,)> for WeakState<T> {
|
||||
type Output = &'a mut T;
|
||||
|
||||
extern "rust-call" fn call_once(self, (state,): (&'a mut WidgetState,)) -> Self::Output {
|
||||
state.get_mut(self)
|
||||
}
|
||||
}
|
||||
Loaded 100 of 150 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user