Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
123f3e6e2b | ||
|
|
baaeb6b027 | ||
|
|
2537284372 | ||
|
|
76f75192d5 | ||
|
|
6156c66a20 | ||
|
|
7f4846a2d3 | ||
|
|
e44bb8eca4 | ||
|
|
434e3c3af7 | ||
|
|
b66d4da5d7 | ||
|
|
7b3a79b1b0 | ||
|
|
c99d466b75 |
No files matched your search
Generated
+451
-813
File diff suppressed because it is too large.
Load diff
+7
-11
@@ -1,5 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iris"
|
name = "iris"
|
||||||
|
default-run = "test"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
@@ -8,16 +9,13 @@ edition.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
iris-core = { workspace = true }
|
iris-core = { workspace = true }
|
||||||
iris-macro = { workspace = true }
|
iris-macro = { workspace = true }
|
||||||
parley = { workspace = true }
|
cosmic-text = { workspace = true }
|
||||||
|
unicode-segmentation = { workspace = true }
|
||||||
winit = { workspace = true }
|
winit = { workspace = true }
|
||||||
arboard = { workspace = true, features = ["wayland-data-control"] }
|
arboard = { workspace = true, features = ["wayland-data-control"] }
|
||||||
pollster = { workspace = true }
|
pollster = { workspace = true }
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
image = { 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]
|
[workspace]
|
||||||
members = ["core", "macro"]
|
members = ["core", "macro"]
|
||||||
@@ -29,14 +27,12 @@ edition = "2024"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
winit = "0.30.12"
|
winit = "0.30.12"
|
||||||
wgpu = "30.0.1"
|
wgpu = "27.0.1"
|
||||||
bytemuck = "1.23.1"
|
bytemuck = "1.23.1"
|
||||||
image = "0.25.10"
|
image = "0.25.6"
|
||||||
parley = "0.11.1"
|
cosmic-text = "0.15.0"
|
||||||
swash = "0.2.10"
|
unicode-segmentation = "1.12.0"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
log = "0.4.29"
|
|
||||||
arboard = "3.6.1"
|
arboard = "3.6.1"
|
||||||
iris-core = { path = "core" }
|
iris-core = { path = "core" }
|
||||||
iris-macro = { path = "macro" }
|
iris-macro = { path = "macro" }
|
||||||
tokio = "1.49.0"
|
|
||||||
@@ -1,8 +1,22 @@
|
|||||||
images
|
images
|
||||||
settings (sampler)
|
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
|
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
|
enum that's either an Id or an actual concrete instance of W
|
||||||
painter takes them in instead of (or in addition to) id
|
painter takes them in instead of (or in addition to) id
|
||||||
then type wrapper widgets to contain them
|
then type wrapper widgets to contain them
|
||||||
@@ -11,6 +25,17 @@ WidgetRef<W> or smth instead of Id
|
|||||||
maybe introduce InnerWidget trait to allow for editors to expose & modify inner type
|
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
|
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?
|
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..??
|
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..??
|
||||||
+2
-3
@@ -4,10 +4,9 @@ version.workspace = true
|
|||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
winit = { workspace = true }
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
bytemuck ={ workspace = true }
|
bytemuck ={ workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
parley = { workspace = true }
|
cosmic-text = { workspace = true }
|
||||||
swash = { workspace = true }
|
|
||||||
fxhash = { 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,157 +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>>,
|
|
||||||
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(),
|
|
||||||
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) + 'a {
|
|
||||||
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
|
||||||
move |ctx, rsc| {
|
|
||||||
for (e, f) in fs {
|
|
||||||
if let Some(data) = e.should_run(&ctx.data) {
|
|
||||||
f(
|
|
||||||
EventCtx {
|
|
||||||
state: ctx.state,
|
|
||||||
data,
|
|
||||||
},
|
|
||||||
rsc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +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 = ();
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
|
||||||
Some(data.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,34 +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 {
|
|
||||||
fn run_event<E: EventLike>(
|
|
||||||
&mut self,
|
|
||||||
id: impl IdLike,
|
|
||||||
data: <E::Event as Event>::Data<'_>,
|
|
||||||
state: &mut Self::State,
|
|
||||||
) {
|
|
||||||
let f = self.events_mut().get_type::<E>().run_fn(id);
|
|
||||||
f(EventCtx { state, data }, self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: HasEvents> RunEvents for T {}
|
|
||||||
@@ -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;
|
fn to_f32(self) -> f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl UiNum for f32 {
|
impl const UiNum for f32 {
|
||||||
fn to_f32(self) -> f32 {
|
fn to_f32(self) -> f32 {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl UiNum for u32 {
|
impl const UiNum for u32 {
|
||||||
fn to_f32(self) -> f32 {
|
fn to_f32(self) -> f32 {
|
||||||
self as f32
|
self as f32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl UiNum for i32 {
|
impl const UiNum for i32 {
|
||||||
fn to_f32(self) -> f32 {
|
fn to_f32(self) -> f32 {
|
||||||
self as f32
|
self as f32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
|
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for 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
|
|
||||||
where
|
where
|
||||||
(T, U): const Destruct,
|
(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,4 +1,4 @@
|
|||||||
use crate::vec2;
|
use crate::layout::vec2;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ impl From<CardinalAlign> for Align {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl From<RegionAlign> for UiVec2 {
|
impl const From<RegionAlign> for UiVec2 {
|
||||||
fn from(align: RegionAlign) -> Self {
|
fn from(align: RegionAlign) -> Self {
|
||||||
Self::rel(align.rel())
|
Self::rel(align.rel())
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,9 @@ pub enum Axis {
|
|||||||
Y,
|
Y,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Axis_ {
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Not for Axis {
|
impl std::ops::Not for Axis {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
@@ -68,48 +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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{UiNum, util::impl_op};
|
use crate::{layout::UiNum, util::impl_op};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
@@ -3,8 +3,7 @@ mod axis;
|
|||||||
mod len;
|
mod len;
|
||||||
mod pos;
|
mod pos;
|
||||||
|
|
||||||
use crate::util::Vec2;
|
use super::Vec2;
|
||||||
|
|
||||||
pub use align::*;
|
pub use align::*;
|
||||||
pub use axis::*;
|
pub use axis::*;
|
||||||
pub use len::*;
|
pub use len::*;
|
||||||
@@ -2,7 +2,7 @@ use std::{fmt::Display, hash::Hash, marker::Destruct};
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
UiNum,
|
layout::UiNum,
|
||||||
util::{LerpUtil, impl_op},
|
util::{LerpUtil, impl_op},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,13 +124,13 @@ impl Display for UiVec2 {
|
|||||||
impl_op!(UiVec2 Add add; x y);
|
impl_op!(UiVec2 Add add; x y);
|
||||||
impl_op!(UiVec2 Sub sub; x y);
|
impl_op!(UiVec2 Sub sub; x y);
|
||||||
|
|
||||||
const impl From<Vec2> for UiVec2 {
|
impl const From<Vec2> for UiVec2 {
|
||||||
fn from(abs: Vec2) -> Self {
|
fn from(abs: Vec2) -> Self {
|
||||||
Self::abs(abs)
|
Self::abs(abs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl<T: const UiNum, U: const UiNum> From<(T, U)> for UiVec2
|
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for UiVec2
|
||||||
where
|
where
|
||||||
(T, U): const Destruct,
|
(T, U): const Destruct,
|
||||||
{
|
{
|
||||||
@@ -421,7 +421,7 @@ impl Display for UiRegion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub struct PixelRegion {
|
pub struct PixelRegion {
|
||||||
pub top_left: Vec2,
|
pub top_left: Vec2,
|
||||||
pub bot_right: Vec2,
|
pub bot_right: Vec2,
|
||||||
@@ -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;
|
use std::marker::Destruct;
|
||||||
|
|
||||||
/// stored in linear for sane manipulation
|
/// stored in linear for sane manipulation
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
||||||
pub struct Color<T> {
|
pub struct Color<T: ColorNum> {
|
||||||
pub r: T,
|
pub r: T,
|
||||||
pub g: T,
|
pub g: T,
|
||||||
pub b: T,
|
pub b: T,
|
||||||
pub a: T,
|
pub a: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ColorNum> Default for Color<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::BLACK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ColorNum> Color<T> {
|
impl<T: ColorNum> Color<T> {
|
||||||
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
|
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
|
||||||
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
|
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> {}
|
unsafe impl bytemuck::Pod for Color<u8> {}
|
||||||
|
|
||||||
const impl F32Conversion for f32 {
|
impl const F32Conversion for f32 {
|
||||||
fn to(self) -> f32 {
|
fn to(self) -> f32 {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -159,7 +155,7 @@ const impl F32Conversion for f32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl F32Conversion for u8 {
|
impl const F32Conversion for u8 {
|
||||||
fn to(self) -> f32 {
|
fn to(self) -> f32 {
|
||||||
self as f32 / 255.0
|
self as f32 / 255.0
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
|
||||||
render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
|
||||||
util::to_mut,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type LayerId = usize;
|
pub type LayerId = usize;
|
||||||
|
|
||||||
@@ -39,7 +36,7 @@ struct Child {
|
|||||||
tail: usize,
|
tail: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DrawLayers = Layers<LayerDraws>;
|
pub type PrimitiveLayers = Layers<Primitives>;
|
||||||
|
|
||||||
impl<T: Default> Layers<T> {
|
impl<T: Default> Layers<T> {
|
||||||
pub fn new() -> Layers<T> {
|
pub fn new() -> Layers<T> {
|
||||||
@@ -54,13 +51,13 @@ impl<T: Default> Layers<T> {
|
|||||||
self.vec.push(LayerNode::head());
|
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();
|
let i = self.vec.len();
|
||||||
self.vec.push(node);
|
self.vec.push(node);
|
||||||
i
|
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 {
|
if let Ptr::Next(i) = self.vec[i].next {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -80,7 +77,7 @@ impl<T: Default> Layers<T> {
|
|||||||
i_new
|
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 {
|
if let Some(c) = self.vec[i].child {
|
||||||
return c.head;
|
return c.head;
|
||||||
}
|
}
|
||||||
@@ -105,11 +102,11 @@ impl<T: Default> Layers<T> {
|
|||||||
self.vec.iter_mut().map(|n| &mut n.data).enumerate()
|
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))
|
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()
|
self.indices()
|
||||||
.map(|i| ((i, self.vec[i].depth), &self.vec[i].data))
|
.map(|i| ((i, self.vec[i].depth), &self.vec[i].data))
|
||||||
}
|
}
|
||||||
@@ -119,12 +116,8 @@ impl<T: Default> Layers<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawLayers {
|
impl PrimitiveLayers {
|
||||||
pub fn write<P: Primitive>(
|
pub fn write<P: Primitive>(&mut self, layer: usize, info: PrimitiveInst<P>) -> PrimitiveHandle {
|
||||||
&mut self,
|
|
||||||
layer: LayerId,
|
|
||||||
info: PrimitiveInst<P>,
|
|
||||||
) -> PrimitiveHandle {
|
|
||||||
self[layer].write(layer, info)
|
self[layer].write(layer, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,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;
|
type Output = T;
|
||||||
|
|
||||||
fn index(&self, index: LayerId) -> &Self::Output {
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
&self.vec[index].data
|
&self.vec[index].data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> IndexMut<LayerId> for Layers<T> {
|
impl<T> IndexMut<usize> for Layers<T> {
|
||||||
fn index_mut(&mut self, index: LayerId) -> &mut Self::Output {
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
&mut self.vec[index].data
|
&mut self.vec[index].data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +172,8 @@ impl<'a, T> Iterator for LayerIteratorMut<'a, T> {
|
|||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let i = self.inner.next()?;
|
let i = self.inner.next()?;
|
||||||
// SAFETY: requires index iterator to work properly
|
// 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))
|
Some((i, layer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +182,8 @@ impl<'a, T> DoubleEndedIterator for LayerIteratorMut<'a, T> {
|
|||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
let i = self.inner.next_back()?;
|
let i = self.inner.next_back()?;
|
||||||
// SAFETY: requires index iterator to work properly
|
// 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))
|
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::{
|
use std::{
|
||||||
ops::Index,
|
ops::Index,
|
||||||
sync::mpsc::{Receiver, Sender, channel},
|
sync::mpsc::{Receiver, Sender, channel},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use image::{DynamicImage, GenericImageView};
|
||||||
|
|
||||||
|
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextureHandle {
|
pub struct TextureHandle {
|
||||||
slot: u32,
|
inner: TexturePrimitive,
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
counter: RefCounter,
|
counter: RefCounter,
|
||||||
send: Sender<u32>,
|
send: Sender<u32>,
|
||||||
@@ -26,26 +28,14 @@ pub struct Textures {
|
|||||||
pub enum TextureUpdate<'a> {
|
pub enum TextureUpdate<'a> {
|
||||||
Push(&'a DynamicImage),
|
Push(&'a DynamicImage),
|
||||||
Set(u32, &'a DynamicImage),
|
Set(u32, &'a DynamicImage),
|
||||||
Patch(u32, PatchRect, &'a DynamicImage),
|
|
||||||
Free(u32),
|
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,
|
PushFree,
|
||||||
SetFree,
|
SetFree,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct PatchRect {
|
|
||||||
pub x: u32,
|
|
||||||
pub y: u32,
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Update {
|
enum Update {
|
||||||
Push(u32),
|
Push(u32),
|
||||||
Set(u32),
|
Set(u32),
|
||||||
Patch(u32, PatchRect),
|
|
||||||
Free(u32),
|
Free(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,8 +53,14 @@ impl Textures {
|
|||||||
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
||||||
let image = image.into();
|
let image = image.into();
|
||||||
let size = image.dimensions().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 {
|
TextureHandle {
|
||||||
slot: self.push(image),
|
inner: TexturePrimitive {
|
||||||
|
view_idx,
|
||||||
|
sampler_idx,
|
||||||
|
},
|
||||||
size,
|
size,
|
||||||
counter: RefCounter::new(),
|
counter: RefCounter::new(),
|
||||||
send: self.send.clone(),
|
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) {
|
pub fn free(&mut self) {
|
||||||
for idx in self.recv.try_iter() {
|
for idx in self.recv.try_iter() {
|
||||||
self.images[idx as usize] = None;
|
self.images[idx as usize] = None;
|
||||||
@@ -119,19 +98,14 @@ impl Textures {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|img| TextureUpdate::Set(i, img))
|
.map(|img| TextureUpdate::Set(i, img))
|
||||||
.unwrap_or(TextureUpdate::SetFree),
|
.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),
|
Update::Free(i) => TextureUpdate::Free(i),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureHandle {
|
impl TextureHandle {
|
||||||
/// Index into `Textures`, and into the renderer's parallel slots.
|
pub fn primitive(&self) -> TexturePrimitive {
|
||||||
pub fn slot(&self) -> u32 {
|
self.inner
|
||||||
self.slot
|
|
||||||
}
|
}
|
||||||
pub fn size(&self) -> Vec2 {
|
pub fn size(&self) -> Vec2 {
|
||||||
self.size
|
self.size
|
||||||
@@ -141,7 +115,7 @@ impl TextureHandle {
|
|||||||
impl Drop for TextureHandle {
|
impl Drop for TextureHandle {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.counter.drop() {
|
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;
|
type Output = DynamicImage;
|
||||||
|
|
||||||
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-20
@@ -2,32 +2,20 @@
|
|||||||
#![feature(const_ops)]
|
#![feature(const_ops)]
|
||||||
#![feature(const_trait_impl)]
|
#![feature(const_trait_impl)]
|
||||||
#![feature(const_convert)]
|
#![feature(const_convert)]
|
||||||
|
#![feature(map_try_insert)]
|
||||||
#![feature(unboxed_closures)]
|
#![feature(unboxed_closures)]
|
||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
|
#![feature(const_cmp)]
|
||||||
#![feature(const_destruct)]
|
#![feature(const_destruct)]
|
||||||
|
#![feature(portable_simd)]
|
||||||
#![feature(associated_type_defaults)]
|
#![feature(associated_type_defaults)]
|
||||||
#![feature(unsize)]
|
#![feature(unsize)]
|
||||||
#![feature(coerce_unsized)]
|
#![feature(coerce_unsized)]
|
||||||
#![feature(option_into_flat_iter)]
|
#![feature(mapped_lock_guards)]
|
||||||
|
#![feature(ptr_metadata)]
|
||||||
mod attr;
|
|
||||||
mod event;
|
|
||||||
mod num;
|
|
||||||
mod orientation;
|
|
||||||
mod primitive;
|
|
||||||
mod render;
|
|
||||||
mod ui;
|
|
||||||
mod widget;
|
|
||||||
|
|
||||||
|
pub mod layout;
|
||||||
|
pub mod render;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
pub use attr::*;
|
pub use image;
|
||||||
pub use event::*;
|
|
||||||
pub use num::*;
|
|
||||||
pub use orientation::*;
|
|
||||||
pub use primitive::*;
|
|
||||||
pub use render::*;
|
|
||||||
pub use ui::*;
|
|
||||||
pub use widget::*;
|
|
||||||
|
|
||||||
pub type UiColor = primitive::Color<u8>;
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
Align, GlyphAtlas, GlyphEntry, GlyphKey, PlacedGlyph, RegionAlign, UiColor, util::Vec2,
|
|
||||||
};
|
|
||||||
use parley::{
|
|
||||||
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
|
||||||
LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
|
|
||||||
};
|
|
||||||
use std::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,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TextData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
font_ctx: FontContext::new(),
|
|
||||||
layout_ctx: LayoutContext::new(),
|
|
||||||
scale_ctx: ScaleContext::new(),
|
|
||||||
atlas: GlyphAtlas::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invalidates the layout and returns the underlying string for editing.
|
|
||||||
pub fn edit(&mut self) -> &mut String {
|
|
||||||
self.layout_key = None;
|
|
||||||
&mut self.text
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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.layout.break_all_lines(width);
|
|
||||||
self.layout
|
|
||||||
.align(Alignment::Start, AlignmentOptions::default());
|
|
||||||
self.layout_key = Some(layout_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: Vec2::new(
|
|
||||||
glyph.x.floor() + entry.left as f32,
|
|
||||||
glyph.y.floor() - entry.top as f32,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
pub fn render(
|
|
||||||
&mut self,
|
|
||||||
buffer: &mut TextBuffer,
|
|
||||||
attrs: &TextAttrs,
|
|
||||||
width: Option<f32>,
|
|
||||||
) -> RenderedText {
|
|
||||||
buffer.shape(self, attrs, width);
|
|
||||||
let glyphs = self.place(buffer);
|
|
||||||
RenderedText {
|
|
||||||
glyphs,
|
|
||||||
size: buffer.size(),
|
|
||||||
color: attrs.color,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
PatchRect,
|
|
||||||
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,
|
|
||||||
pub offset: Vec2,
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{UiRegion, util::Id};
|
use crate::{layout::UiRegion, util::Id};
|
||||||
use wgpu::*;
|
use wgpu::*;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -12,16 +12,20 @@ pub struct WindowUniform {
|
|||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct PrimitiveInstance {
|
pub struct PrimitiveInstance {
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
|
pub binding: u32,
|
||||||
|
pub idx: u32,
|
||||||
pub mask_idx: MaskIdx,
|
pub mask_idx: MaskIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveInstance {
|
impl PrimitiveInstance {
|
||||||
const ATTRIBS: [VertexAttribute; 5] = vertex_attr_array![
|
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
|
||||||
0 => Float32x2,
|
0 => Float32x2,
|
||||||
1 => Float32x2,
|
1 => Float32x2,
|
||||||
2 => Float32x2,
|
2 => Float32x2,
|
||||||
3 => Float32x2,
|
3 => Float32x2,
|
||||||
4 => Uint32,
|
4 => Uint32,
|
||||||
|
5 => Uint32,
|
||||||
|
6 => Uint32,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn desc() -> VertexBufferLayout<'static> {
|
pub fn desc() -> VertexBufferLayout<'static> {
|
||||||
|
|||||||
+206
-226
@@ -1,232 +1,204 @@
|
|||||||
|
use std::num::NonZero;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
UiData, UiRenderState,
|
layout::Ui,
|
||||||
render::{data::PrimitiveInstance, util::ArrBuf},
|
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
||||||
util::{HashMap, Vec2},
|
util::HashMap,
|
||||||
};
|
};
|
||||||
use data::WindowUniform;
|
use data::WindowUniform;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
util::{BufferInitDescriptor, DeviceExt},
|
util::{BufferInitDescriptor, DeviceExt},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
use winit::dpi::PhysicalSize;
|
||||||
|
|
||||||
mod atlas;
|
|
||||||
mod data;
|
mod data;
|
||||||
mod page;
|
|
||||||
mod primitive;
|
mod primitive;
|
||||||
mod texture;
|
mod texture;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use atlas::*;
|
|
||||||
pub use data::{Mask, MaskIdx};
|
pub use data::{Mask, MaskIdx};
|
||||||
pub use primitive::*;
|
pub use primitive::*;
|
||||||
|
|
||||||
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||||
|
|
||||||
pub struct UiRenderNode {
|
pub struct UiRenderNode {
|
||||||
shared_layout: BindGroupLayout,
|
uniform_group: BindGroup,
|
||||||
shared_group: BindGroup,
|
primitive_layout: BindGroupLayout,
|
||||||
format: TextureFormat,
|
rsc_layout: BindGroupLayout,
|
||||||
|
rsc_group: BindGroup,
|
||||||
|
|
||||||
/// One per registered primitive, in id order.
|
pipeline: RenderPipeline,
|
||||||
primitives: Vec<PrimitivePipeline>,
|
|
||||||
|
|
||||||
layers: HashMap<usize, RenderLayer>,
|
layers: HashMap<usize, RenderLayer>,
|
||||||
active: Vec<usize>,
|
active: Vec<usize>,
|
||||||
window_buffer: Buffer,
|
window_buffer: Buffer,
|
||||||
|
textures: GpuTextures,
|
||||||
masks: ArrBuf<Mask>,
|
masks: ArrBuf<Mask>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RenderLayer {
|
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>,
|
instance: ArrBuf<PrimitiveInstance>,
|
||||||
data: ArrBuf<u8>,
|
primitives: PrimitiveBuffers,
|
||||||
group: Option<BindGroup>,
|
primitive_group: BindGroup,
|
||||||
/// What the primitive asked to keep per instance, if anything.
|
|
||||||
bindings: Vec<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiRenderNode {
|
impl UiRenderNode {
|
||||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
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 {
|
for i in &self.active {
|
||||||
let layer = &self.layers[i];
|
let layer = &self.layers[i];
|
||||||
for (id, list) in layer.primitives.iter().enumerate() {
|
if layer.instance.len() == 0 {
|
||||||
let Some(list) = list else { continue };
|
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,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
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(
|
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
|
||||||
&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);
|
|
||||||
self.active.clear();
|
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);
|
self.active.push(i);
|
||||||
for change in draws.apply_free() {
|
for change in primitives.apply_free() {
|
||||||
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
if let Some(inst) = ui.data.active.get_mut(&change.id) {
|
||||||
for h in &mut inst.primitives {
|
for h in &mut inst.primitives {
|
||||||
if h.layer == i && h.kind == change.kind && h.inst_idx == change.old {
|
if h.layer == i && h.inst_idx == change.old {
|
||||||
h.inst_idx = change.new;
|
h.inst_idx = change.new;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rlayer = self.layers.entry(i).or_insert_with(RenderLayer::new);
|
let rlayer = self.layers.entry(i).or_insert_with(|| {
|
||||||
if draws.updated {
|
let primitives = PrimitiveBuffers::new(device);
|
||||||
let lists = draws.primitives();
|
let primitive_group =
|
||||||
// The zip would otherwise skip a list with no pipeline.
|
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
|
||||||
assert!(lists.len() <= self.primitives.len());
|
RenderLayer {
|
||||||
rlayer.primitives.resize_with(lists.len(), || None);
|
instance: ArrBuf::new(
|
||||||
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);
|
|
||||||
}
|
|
||||||
draws.updated = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for primitive in &mut self.primitives {
|
|
||||||
primitive.render.update(ui);
|
|
||||||
}
|
|
||||||
if ui.masks.changed {
|
|
||||||
ui.masks.changed = false;
|
|
||||||
if self.masks.update(device, queue, &ui.masks[..]) {
|
|
||||||
self.shared_group = Self::shared_group(
|
|
||||||
device,
|
device,
|
||||||
&self.shared_layout,
|
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||||
&self.window_buffer,
|
"instance",
|
||||||
&self.masks,
|
),
|
||||||
);
|
primitives,
|
||||||
|
primitive_group,
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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) {
|
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &Queue) {
|
||||||
let size = size.into();
|
|
||||||
let slice = &[WindowUniform {
|
let slice = &[WindowUniform {
|
||||||
width: size.x,
|
width: size.width as f32,
|
||||||
height: size.y,
|
height: size.height as f32,
|
||||||
}];
|
}];
|
||||||
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
pub fn new(
|
||||||
let window_uniform = WindowUniform {
|
device: &Device,
|
||||||
width: config.width as f32,
|
queue: &Queue,
|
||||||
height: config.height as f32,
|
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 {
|
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||||
label: Some("window"),
|
label: Some("window"),
|
||||||
contents: bytemuck::cast_slice(&[window_uniform]),
|
contents: bytemuck::cast_slice(&[window_uniform]),
|
||||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
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(
|
let masks = ArrBuf::new(
|
||||||
device,
|
device,
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
"ui masks",
|
"ui masks",
|
||||||
);
|
);
|
||||||
let shared_group = Self::shared_group(device, &shared_layout, &window_buffer, &masks);
|
|
||||||
|
|
||||||
Self {
|
let rsc_layout = Self::rsc_layout(device, &limits);
|
||||||
shared_layout,
|
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
|
||||||
shared_group,
|
|
||||||
format: config.format,
|
|
||||||
primitives: Vec::new(),
|
|
||||||
window_buffer,
|
|
||||||
layers: HashMap::default(),
|
|
||||||
active: Vec::new(),
|
|
||||||
masks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compiles a pipeline for every primitive registered since the last call.
|
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||||
/// Sources only ever arrive at the end, so an id keeps its pipeline.
|
label: Some("UI Shape Pipeline Layout"),
|
||||||
fn build_pipelines(&mut self, device: &Device, queue: &Queue, registry: &PrimitiveRegistry) {
|
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
|
||||||
for source in ®istry.sources()[self.primitives.len()..] {
|
push_constant_ranges: &[],
|
||||||
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);
|
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||||
self.primitives.push(PrimitivePipeline {
|
label: Some("UI Shape Pipeline"),
|
||||||
data_layout,
|
layout: Some(&pipeline_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(format!("{PRELUDE}\n{wgsl}").into()),
|
|
||||||
});
|
|
||||||
device.create_render_pipeline(&RenderPipelineDescriptor {
|
|
||||||
label: Some(label),
|
|
||||||
layout: Some(layout),
|
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
module: &module,
|
module: &shader,
|
||||||
entry_point: Some("vs_main"),
|
entry_point: Some("vs_main"),
|
||||||
buffers: &[Some(PrimitiveInstance::desc())],
|
buffers: &[PrimitiveInstance::desc()],
|
||||||
compilation_options: Default::default(),
|
compilation_options: Default::default(),
|
||||||
},
|
},
|
||||||
fragment: Some(FragmentState {
|
fragment: Some(FragmentState {
|
||||||
module: &module,
|
module: &shader,
|
||||||
entry_point: Some("fs_main"),
|
entry_point: Some("fs_main"),
|
||||||
targets: &[Some(ColorTargetState {
|
targets: &[Some(ColorTargetState {
|
||||||
format,
|
format: config.format,
|
||||||
blend: Some(BlendState::ALPHA_BLENDING),
|
blend: Some(BlendState::ALPHA_BLENDING),
|
||||||
write_mask: ColorWrites::ALL,
|
write_mask: ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
@@ -247,44 +219,92 @@ impl UiRenderNode {
|
|||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
multiview_mask: None,
|
multiview: None,
|
||||||
cache: 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 and the masks.
|
fn primitive_group(
|
||||||
fn shared_layout(device: &Device) -> BindGroupLayout {
|
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 {
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
visibility: ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Texture {
|
||||||
ty: BufferBindingType::Uniform,
|
sample_type: TextureSampleType::Float { filterable: false },
|
||||||
has_dynamic_offset: false,
|
view_dimension: TextureViewDimension::D2,
|
||||||
min_binding_size: BufferSize::new(size_of::<WindowUniform>() as u64),
|
multisampled: false,
|
||||||
},
|
},
|
||||||
count: None,
|
count: Some(NonZero::new(limits.max_textures).unwrap()),
|
||||||
},
|
},
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
visibility: ShaderStages::FRAGMENT,
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
||||||
|
count: Some(NonZero::new(limits.max_samplers).unwrap()),
|
||||||
|
},
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
min_binding_size: BufferSize::new(size_of::<Mask>() as u64),
|
min_binding_size: None,
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui shared"),
|
label: Some("ui rsc"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shared_group(
|
fn rsc_group(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
window: &Buffer,
|
tex_manager: &GpuTextures,
|
||||||
masks: &ArrBuf<Mask>,
|
masks: &ArrBuf<Mask>,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
@@ -292,85 +312,45 @@ impl UiRenderNode {
|
|||||||
entries: &[
|
entries: &[
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: window.as_entire_binding(),
|
resource: BindingResource::TextureViewArray(&tex_manager.views()),
|
||||||
},
|
},
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
|
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
resource: masks.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
|
pub fn view_count(&self) -> usize {
|
||||||
/// stated, so "is the buffer big enough for one entry?" is answered when
|
self.textures.view_count()
|
||||||
/// 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"),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderLayer {
|
pub struct UiLimits {
|
||||||
fn new() -> Self {
|
max_textures: u32,
|
||||||
|
max_samplers: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UiLimits {
|
||||||
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
primitives: Vec::new(),
|
max_textures: 100000,
|
||||||
|
max_samplers: 1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListBuffers {
|
impl UiLimits {
|
||||||
fn new(device: &Device) -> Self {
|
pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 {
|
||||||
Self {
|
self.max_textures + self.max_samplers
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
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: &[],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+195
-309
@@ -1,247 +1,113 @@
|
|||||||
use std::{any::TypeId, marker::PhantomData};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Color, TextureHandle, UiData, UiRegion, WidgetId,
|
layout::{Color, UiRegion, WidgetId},
|
||||||
render::{
|
render::{
|
||||||
|
ArrBuf,
|
||||||
data::{MaskIdx, PrimitiveInstance},
|
data::{MaskIdx, PrimitiveInstance},
|
||||||
page::GlyphRender,
|
|
||||||
texture::ImageRender,
|
|
||||||
},
|
},
|
||||||
util::{HashMap, Vec2},
|
|
||||||
};
|
};
|
||||||
use bytemuck::Pod;
|
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.
|
pub struct Primitives {
|
||||||
///
|
|
||||||
/// 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 {
|
|
||||||
instances: Vec<PrimitiveInstance>,
|
instances: Vec<PrimitiveInstance>,
|
||||||
/// The widget each instance belongs to, for renumbering its handles.
|
|
||||||
assoc: Vec<WidgetId>,
|
assoc: Vec<WidgetId>,
|
||||||
|
data: PrimitiveData,
|
||||||
free: Vec<usize>,
|
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,
|
pub updated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LayerDraws {
|
impl Default for Primitives {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
primitives: Vec::new(),
|
instances: Default::default(),
|
||||||
|
assoc: Default::default(),
|
||||||
|
data: Default::default(),
|
||||||
|
free: Vec::new(),
|
||||||
updated: true,
|
updated: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerDraws {
|
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>,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrimitiveBuffers {
|
||||||
|
$($name: ArrBuf<$ty>,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveBuffers {
|
||||||
|
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
|
||||||
|
$(self.$name.update(device, queue, &data.$name);)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
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 id: WidgetId,
|
||||||
|
pub primitive: P,
|
||||||
|
pub region: UiRegion,
|
||||||
|
pub mask_idx: MaskIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Primitives {
|
||||||
pub fn write<P: Primitive>(
|
pub fn write<P: Primitive>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layer: usize,
|
layer: usize,
|
||||||
PrimitiveInst {
|
PrimitiveInst {
|
||||||
kind,
|
|
||||||
id,
|
id,
|
||||||
primitive,
|
primitive,
|
||||||
region,
|
region,
|
||||||
@@ -249,67 +115,65 @@ impl LayerDraws {
|
|||||||
}: PrimitiveInst<P>,
|
}: PrimitiveInst<P>,
|
||||||
) -> PrimitiveHandle {
|
) -> PrimitiveHandle {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
// Grown on first use rather than sized from the registry, which a
|
let vec = P::vec(&mut self.data);
|
||||||
// layer cannot see.
|
let i = vec.add(primitive);
|
||||||
if self.primitives.len() <= kind.id as usize {
|
let inst = PrimitiveInstance {
|
||||||
self.primitives.resize_with(kind.id as usize + 1, || None);
|
region,
|
||||||
}
|
idx: i as u32,
|
||||||
let inst_idx = self.primitives[kind.id as usize]
|
mask_idx,
|
||||||
.get_or_insert_with(InstanceList::new::<P>)
|
binding: P::BINDING,
|
||||||
.push(
|
};
|
||||||
id,
|
let inst_i = if let Some(i) = self.free.pop() {
|
||||||
PrimitiveInstance { region, mask_idx },
|
self.instances[i] = inst;
|
||||||
bytemuck::bytes_of(&primitive),
|
self.assoc[i] = id;
|
||||||
);
|
i
|
||||||
PrimitiveHandle {
|
} else {
|
||||||
layer,
|
let i = self.instances.len();
|
||||||
kind: kind.id,
|
self.instances.push(inst);
|
||||||
inst_idx,
|
self.assoc.push(id);
|
||||||
}
|
i
|
||||||
}
|
};
|
||||||
|
PrimitiveHandle::new::<P>(layer, inst_i, i)
|
||||||
pub fn primitives(&self) -> &[Option<InstanceList>] {
|
|
||||||
&self.primitives
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns (old index, new index)
|
||||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||||
self.primitives
|
self.free.sort_by(|a, b| b.cmp(a));
|
||||||
.iter_mut()
|
self.free.drain(..).filter_map(|i| {
|
||||||
.enumerate()
|
self.instances.swap_remove(i);
|
||||||
.filter_map(|(kind, list)| Some((kind as u32, list.as_mut()?)))
|
self.assoc.swap_remove(i);
|
||||||
.flat_map(|(kind, list)| list.apply_free(kind))
|
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 {
|
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
self.list(h).free(h.inst_idx)
|
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 {
|
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
&mut self.list(h).instances[h.inst_idx].region
|
&mut self.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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveInst<P> {
|
|
||||||
pub kind: PrimitiveKind<P>,
|
|
||||||
pub id: WidgetId,
|
|
||||||
pub primitive: P,
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub mask_idx: MaskIdx,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveChange {
|
pub struct PrimitiveChange {
|
||||||
pub id: WidgetId,
|
pub id: WidgetId,
|
||||||
/// Which registered primitive's list moved, since they index separately.
|
|
||||||
pub kind: u32,
|
|
||||||
pub old: usize,
|
pub old: usize,
|
||||||
pub new: usize,
|
pub new: usize,
|
||||||
}
|
}
|
||||||
@@ -317,12 +181,29 @@ pub struct PrimitiveChange {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PrimitiveHandle {
|
pub struct PrimitiveHandle {
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub kind: u32,
|
|
||||||
pub inst_idx: usize,
|
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)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct RectPrimitive {
|
pub struct RectPrimitive {
|
||||||
pub color: Color<u8>,
|
pub color: Color<u8>,
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
@@ -330,10 +211,6 @@ pub struct RectPrimitive {
|
|||||||
pub inner_radius: f32,
|
pub inner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive for RectPrimitive {
|
|
||||||
const WGSL: &'static str = include_str!("shader/rect.wgsl");
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RectPrimitive {
|
impl RectPrimitive {
|
||||||
pub fn color(color: Color<u8>) -> Self {
|
pub fn color(color: Color<u8>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -345,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, align(8))]
|
|
||||||
#[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: the align(8) leaves four bytes of padding, 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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct TexturePrimitive {
|
pub struct TexturePrimitive {
|
||||||
pub slot: u32,
|
pub view_idx: u32,
|
||||||
|
pub sampler_idx: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive for TexturePrimitive {
|
pub struct PrimitiveVec<T> {
|
||||||
const WGSL: &'static str = include_str!("shader/texture.wgsl");
|
vec: Vec<T>,
|
||||||
|
free: Vec<usize>,
|
||||||
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender> {
|
|
||||||
Box::new(ImageRender::new(device, queue))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TextureHandle> for TexturePrimitive {
|
impl<T> PrimitiveVec<T> {
|
||||||
fn from(handle: &TextureHandle) -> Self {
|
pub fn new() -> Self {
|
||||||
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,96 +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>;
|
|
||||||
|
|
||||||
struct WindowUniform {
|
|
||||||
dim: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Mask {
|
|
||||||
x: UiSpan,
|
|
||||||
y: UiSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiSpan {
|
|
||||||
start: UiScalar,
|
|
||||||
end: UiScalar,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiScalar {
|
|
||||||
rel: f32,
|
|
||||||
abs: 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) mask_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 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.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];
|
|
||||||
let tl = vec2(mask.x.start.rel, mask.y.start.rel);
|
|
||||||
let tl_abs = vec2(mask.x.start.abs, mask.y.start.abs);
|
|
||||||
let br = vec2(mask.x.end.rel, mask.y.end.rel);
|
|
||||||
let br_abs = vec2(mask.x.end.abs, mask.y.end.abs);
|
|
||||||
|
|
||||||
let top_left = floor(tl * window.dim) + floor(tl_abs);
|
|
||||||
let bot_right = floor(br * window.dim) + floor(br_abs);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
+83
-197
@@ -1,119 +1,59 @@
|
|||||||
use image::{DynamicImage, EncodableLayout, GenericImageView, RgbaImage};
|
use image::{DynamicImage, EncodableLayout};
|
||||||
use wgpu::{util::DeviceExt, *};
|
use wgpu::{util::DeviceExt, *};
|
||||||
|
|
||||||
use crate::{
|
use crate::layout::{TextureUpdate, Textures};
|
||||||
PatchRect, TextureUpdate, Textures, UiData,
|
|
||||||
render::{
|
|
||||||
TexturePrimitive,
|
|
||||||
primitive::{ListDraw, PrimitiveRender},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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 {
|
pub struct GpuTextures {
|
||||||
device: Device,
|
device: Device,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
slots: Vec<Option<ImageGpu>>,
|
views: Vec<TextureView>,
|
||||||
}
|
view_count: usize,
|
||||||
|
samplers: Vec<Sampler>,
|
||||||
struct ImageGpu {
|
null_view: TextureView,
|
||||||
/// Kept for `patch`, which needs the texture rather than the view.
|
no_views: Vec<TextureView>,
|
||||||
texture: Texture,
|
|
||||||
group: BindGroup,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuTextures {
|
impl GpuTextures {
|
||||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
pub fn update(&mut self, textures: &mut Textures) -> bool {
|
||||||
Self {
|
let mut changed = false;
|
||||||
device: device.clone(),
|
|
||||||
queue: queue.clone(),
|
|
||||||
slots: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, textures: &mut Textures, layout: &BindGroupLayout, sampler: &Sampler) {
|
|
||||||
for update in textures.updates() {
|
for update in textures.updates() {
|
||||||
|
changed = true;
|
||||||
match update {
|
match update {
|
||||||
TextureUpdate::Push(image) => {
|
TextureUpdate::Push(image) => self.push(image),
|
||||||
let image = self.create(image, layout, sampler);
|
TextureUpdate::Set(i, image) => self.set(i, image),
|
||||||
self.slots.push(Some(image));
|
TextureUpdate::SetFree => self.view_count += 1,
|
||||||
}
|
TextureUpdate::Free(i) => self.free(i),
|
||||||
TextureUpdate::Set(i, image) => {
|
TextureUpdate::PushFree => self.push_free(),
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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> {
|
fn create_view(&self, image: &DynamicImage) -> TextureView {
|
||||||
self.slots.get(slot as usize)?.as_ref().map(|i| &i.group)
|
let image = image.to_rgba8();
|
||||||
}
|
let (width, height) = image.dimensions();
|
||||||
|
|
||||||
fn create(
|
|
||||||
&self,
|
|
||||||
image: &DynamicImage,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
sampler: &Sampler,
|
|
||||||
) -> ImageGpu {
|
|
||||||
let rgba = image.to_rgba8();
|
|
||||||
let (width, height) = rgba.dimensions();
|
|
||||||
let texture = self.device.create_texture_with_data(
|
let texture = self.device.create_texture_with_data(
|
||||||
&self.queue,
|
&self.queue,
|
||||||
&TextureDescriptor {
|
&TextureDescriptor {
|
||||||
label: Some("image"),
|
label: None,
|
||||||
size: Extent3d {
|
size: Extent3d {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -123,119 +63,65 @@ impl GpuTextures {
|
|||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: TextureDimension::D2,
|
dimension: TextureDimension::D2,
|
||||||
format: TextureFormat::Rgba8Unorm,
|
format: TextureFormat::Rgba8Unorm,
|
||||||
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
|
usage: TextureUsages::TEXTURE_BINDING,
|
||||||
view_formats: &[],
|
view_formats: &[],
|
||||||
},
|
},
|
||||||
wgt::TextureDataOrder::MipMajor,
|
wgt::TextureDataOrder::MipMajor,
|
||||||
rgba.as_bytes(),
|
image.as_bytes(),
|
||||||
);
|
);
|
||||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
texture.create_view(&TextureViewDescriptor::default())
|
||||||
let group = sampled_group(&self.device, layout, &view, sampler, "ui image");
|
|
||||||
ImageGpu { texture, group }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patch(&mut self, i: u32, rect: PatchRect, image: &DynamicImage) {
|
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||||
let Some(Some(slot)) = self.slots.get(i as usize) else {
|
let null_view = null_texture_view(device);
|
||||||
return;
|
Self {
|
||||||
};
|
device: device.clone(),
|
||||||
let dst = TexelCopyTextureInfo {
|
queue: queue.clone(),
|
||||||
texture: &slot.texture,
|
views: Vec::new(),
|
||||||
mip_level: 0,
|
samplers: vec![default_sampler(device)],
|
||||||
origin: Origin3d {
|
no_views: vec![null_view.clone()],
|
||||||
x: rect.x,
|
null_view,
|
||||||
y: rect.y,
|
view_count: 0,
|
||||||
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 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_region(queue: &Queue, dst: TexelCopyTextureInfo, src: &RgbaImage, rect: PatchRect) {
|
pub fn null_texture_view(device: &Device) -> TextureView {
|
||||||
if rect.width == 0 || rect.height == 0 {
|
device
|
||||||
return;
|
.create_texture(&TextureDescriptor {
|
||||||
}
|
label: Some("null"),
|
||||||
let stride = src.width() * 4;
|
size: Extent3d {
|
||||||
queue.write_texture(
|
width: 1,
|
||||||
dst,
|
height: 1,
|
||||||
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,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
);
|
mip_level_count: 1,
|
||||||
}
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
/// What a primitive that samples binds: a texture, and the sampler that reads
|
format: TextureFormat::Rgba8Unorm,
|
||||||
/// it.
|
usage: TextureUsages::TEXTURE_BINDING,
|
||||||
pub fn sampled_group(
|
view_formats: &[],
|
||||||
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),
|
|
||||||
},
|
|
||||||
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),
|
|
||||||
})
|
})
|
||||||
|
.create_view(&TextureViewDescriptor::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_sampler(device: &Device) -> Sampler {
|
pub fn default_sampler(device: &Device) -> Sampler {
|
||||||
|
|||||||
@@ -21,25 +21,17 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns whether the `Buffer` was recreated, which stales any cached
|
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) {
|
||||||
/// `BindGroup` holding it.
|
if self.len != data.len() {
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) -> bool {
|
|
||||||
let resized = self.len != data.len();
|
|
||||||
if resized {
|
|
||||||
self.len = data.len();
|
self.len = data.len();
|
||||||
self.buffer =
|
self.buffer =
|
||||||
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
||||||
}
|
}
|
||||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
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 {
|
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||||
let mut size = size as u64;
|
let mut size = size as u64;
|
||||||
if usage.contains(BufferUsages::STORAGE) {
|
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);
|
size = size.max(std::mem::size_of::<T>() as u64);
|
||||||
}
|
}
|
||||||
device.create_buffer(&BufferDescriptor {
|
device.create_buffer(&BufferDescriptor {
|
||||||
@@ -49,4 +41,8 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
usage,
|
usage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
use crate::{LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
|
|
||||||
|
|
||||||
/// important non rendering data for retained drawing
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ActiveData {
|
|
||||||
pub id: WidgetId,
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub parent: Option<WidgetId>,
|
|
||||||
pub textures: Vec<TextureHandle>,
|
|
||||||
pub primitives: Vec<PrimitiveHandle>,
|
|
||||||
pub children: Vec<WidgetId>,
|
|
||||||
pub mask: MaskIdx,
|
|
||||||
pub layer: LayerId,
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
use crate::{BothAxis, Len, UiVec2, WidgetId, util::HashMap};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Cache {
|
|
||||||
pub size: BothAxis<HashMap<WidgetId, (UiVec2, Len)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cache {
|
|
||||||
pub fn remove(&mut self, id: WidgetId) {
|
|
||||||
self.size.x.remove(&id);
|
|
||||||
self.size.y.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.size.x.clear();
|
|
||||||
self.size.y.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
Mask, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod active;
|
|
||||||
mod cache;
|
|
||||||
mod painter;
|
|
||||||
mod render_state;
|
|
||||||
mod size;
|
|
||||||
|
|
||||||
pub use active::*;
|
|
||||||
pub use painter::{Painter, PrimitiveLike};
|
|
||||||
pub use render_state::*;
|
|
||||||
pub use size::*;
|
|
||||||
|
|
||||||
#[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>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,199 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
|
||||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
|
||||||
render::{
|
|
||||||
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
|
||||||
TexturePrimitive,
|
|
||||||
},
|
|
||||||
util::Vec2,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// makes your surfaces look pretty
|
|
||||||
pub struct Painter<'a> {
|
|
||||||
pub(super) state: &'a mut UiRenderState,
|
|
||||||
pub(super) rsc: &'a mut dyn UiRsc,
|
|
||||||
|
|
||||||
pub(super) region: UiRegion,
|
|
||||||
pub(super) mask: MaskIdx,
|
|
||||||
pub(super) textures: Vec<TextureHandle>,
|
|
||||||
pub(super) primitives: Vec<PrimitiveHandle>,
|
|
||||||
pub(super) children: Vec<WidgetId>,
|
|
||||||
pub layer: 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) {
|
|
||||||
let h = self.state.layers.write(
|
|
||||||
self.layer,
|
|
||||||
PrimitiveInst {
|
|
||||||
kind,
|
|
||||||
id: self.id,
|
|
||||||
primitive,
|
|
||||||
region,
|
|
||||||
mask_idx: self.mask,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.push_primitive(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_primitive(&mut self, h: PrimitiveHandle) {
|
|
||||||
if self.mask != MaskIdx::NONE {
|
|
||||||
// TODO: I have no clue if this works at all :joy:
|
|
||||||
self.rsc.ui_mut().masks.push_ref(self.mask);
|
|
||||||
}
|
|
||||||
self.primitives.push(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a primitive to be rendered
|
|
||||||
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
|
||||||
let primitive = primitive.into_primitive(self);
|
|
||||||
self.primitive_at(primitive, self.region)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) {
|
|
||||||
let primitive = primitive.into_primitive(self);
|
|
||||||
self.primitive_at(primitive, region.within(&self.region));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_mask(&mut self, region: UiRegion) {
|
|
||||||
assert!(self.mask == MaskIdx::NONE);
|
|
||||||
self.mask = self.rsc.ui_mut().masks.push(Mask { region });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
|
||||||
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<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: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
|
||||||
self.widget_at(id, region.within(&self.region));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
|
|
||||||
self.children.push(id.id());
|
|
||||||
self.state.draw_inner(
|
|
||||||
self.layer,
|
|
||||||
id.id(),
|
|
||||||
region,
|
|
||||||
Some(self.id),
|
|
||||||
self.mask,
|
|
||||||
None,
|
|
||||||
self.rsc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_text(
|
|
||||||
&mut self,
|
|
||||||
buffer: &mut TextBuffer,
|
|
||||||
attrs: &TextAttrs,
|
|
||||||
width: Option<f32>,
|
|
||||||
) -> RenderedText {
|
|
||||||
let ui = self.rsc.ui_mut();
|
|
||||||
ui.text.render(buffer, attrs, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: merge the text methods into the primitive ones.
|
|
||||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
|
||||||
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
|
||||||
for glyph in text.glyphs.iter() {
|
|
||||||
let mut region = origin;
|
|
||||||
region.x.end = region.x.start;
|
|
||||||
region.y.end = region.y.start;
|
|
||||||
let mut region = region.offset(UiVec2::abs(glyph.offset));
|
|
||||||
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
|
||||||
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
|
|
||||||
self.write(
|
|
||||||
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(),
|
|
||||||
},
|
|
||||||
region,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn region(&self) -> UiRegion {
|
|
||||||
self.region
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>) -> Size {
|
|
||||||
self.size_ctx().size(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.size_ctx().width(id),
|
|
||||||
Axis::Y => self.size_ctx().height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_size(&self) -> Vec2 {
|
|
||||||
self.state.output_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn px_size(&mut self) -> Vec2 {
|
|
||||||
self.region.size().to_abs(self.state.output_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_data(&mut self) -> &mut TextData {
|
|
||||||
&mut self.rsc.ui_mut().text
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn child_layer(&mut self) {
|
|
||||||
self.layer = self.state.layers.child(self.layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
|
|
||||||
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, SizeCtx, StrongWidget,
|
|
||||||
UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
|
||||||
ui::cache::Cache,
|
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct UiRenderState {
|
|
||||||
pub active: HashMap<WidgetId, ActiveData>,
|
|
||||||
pub layers: DrawLayers,
|
|
||||||
pub(super) output_size: Vec2,
|
|
||||||
pub cache: Cache,
|
|
||||||
|
|
||||||
old_root: Option<WidgetId>,
|
|
||||||
resized: bool,
|
|
||||||
draw_started: HashSet<WidgetId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiRenderState {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
active: Default::default(),
|
|
||||||
layers: Default::default(),
|
|
||||||
cache: Default::default(),
|
|
||||||
output_size: Vec2::ZERO,
|
|
||||||
old_root: None,
|
|
||||||
resized: false,
|
|
||||||
draw_started: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
|
||||||
self.output_size = size.into();
|
|
||||||
self.resized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_size(&self) -> Vec2 {
|
|
||||||
self.output_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update<'a>(&mut self, root: impl Into<Option<&'a StrongWidget>>, rsc: &mut dyn UiRsc) {
|
|
||||||
// safety mechanism for memory leaks; might wanna return a result instead so user can
|
|
||||||
// decide whether to panic or not
|
|
||||||
if !rsc.widgets().waiting.is_empty() {
|
|
||||||
let widgets = rsc.widgets();
|
|
||||||
let len = widgets.waiting.len();
|
|
||||||
let all: Vec<_> = widgets
|
|
||||||
.waiting
|
|
||||||
.iter()
|
|
||||||
.map(|&w| format!("'{}' ({w:?})", widgets.label(w)))
|
|
||||||
.collect();
|
|
||||||
panic!(
|
|
||||||
"{len} widget(s) were never upgraded\n\
|
|
||||||
this is likely a memory leak; consider upgrading to strong if you plan on using it later\n\
|
|
||||||
weak widgets: {all:#?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let root = root.into();
|
|
||||||
if self.needs_full_redraw(root) {
|
|
||||||
self.redraw_all(root, rsc);
|
|
||||||
self.old_root = root.map(|r| r.id());
|
|
||||||
self.resized = false;
|
|
||||||
} else if rsc.widgets().has_updates() {
|
|
||||||
self.redraw_updates(rsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) {
|
|
||||||
self.clear(rsc);
|
|
||||||
// free all resources & cache
|
|
||||||
if let Some(id) = root {
|
|
||||||
self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None, rsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should prolly make a DrawInfo struct or smth for everything other than rsc
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub(super) fn draw_inner(
|
|
||||||
&mut self,
|
|
||||||
layer: usize,
|
|
||||||
id: WidgetId,
|
|
||||||
region: UiRegion,
|
|
||||||
parent: Option<WidgetId>,
|
|
||||||
mask: MaskIdx,
|
|
||||||
old_children: Option<Vec<WidgetId>>,
|
|
||||||
rsc: &mut dyn UiRsc,
|
|
||||||
) {
|
|
||||||
let mut old_children = old_children.unwrap_or_default();
|
|
||||||
if let Some(active) = self.active.get_mut(&id)
|
|
||||||
&& !rsc.widgets().needs_redraw.contains(&id)
|
|
||||||
{
|
|
||||||
// check to see if we can skip drawing first
|
|
||||||
if active.region == region {
|
|
||||||
return;
|
|
||||||
} else if active.region.size() == region.size() {
|
|
||||||
// TODO: epsilon?
|
|
||||||
let from = active.region;
|
|
||||||
self.mov(id, from, region);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// if not, then maintain resize and track old children to remove unneeded
|
|
||||||
let active = self.remove(id, false, rsc).unwrap();
|
|
||||||
old_children = active.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw widget
|
|
||||||
self.draw_started.insert(id);
|
|
||||||
|
|
||||||
let mut painter = Painter {
|
|
||||||
state: self,
|
|
||||||
region,
|
|
||||||
mask,
|
|
||||||
layer,
|
|
||||||
id,
|
|
||||||
textures: Vec::new(),
|
|
||||||
primitives: Vec::new(),
|
|
||||||
children: Vec::new(),
|
|
||||||
rsc,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut widget = painter.rsc.widgets().get_dyn_dynamic(id);
|
|
||||||
widget.draw(&mut painter);
|
|
||||||
drop(widget);
|
|
||||||
|
|
||||||
let Painter {
|
|
||||||
state: _,
|
|
||||||
rsc: _,
|
|
||||||
region,
|
|
||||||
mask,
|
|
||||||
textures,
|
|
||||||
primitives,
|
|
||||||
children,
|
|
||||||
layer,
|
|
||||||
id,
|
|
||||||
} = painter;
|
|
||||||
|
|
||||||
// add to active
|
|
||||||
let active = ActiveData {
|
|
||||||
id,
|
|
||||||
region,
|
|
||||||
parent,
|
|
||||||
textures,
|
|
||||||
primitives,
|
|
||||||
children,
|
|
||||||
mask,
|
|
||||||
layer,
|
|
||||||
};
|
|
||||||
|
|
||||||
// remove old children that weren't kept
|
|
||||||
for c in &old_children {
|
|
||||||
if !active.children.contains(c) {
|
|
||||||
self.remove_rec(*c, rsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rsc.on_draw(&active);
|
|
||||||
self.active.insert(id, active);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
// SAFETY: children cannot be recursive
|
|
||||||
let children = unsafe { forget_ref(&active.children) };
|
|
||||||
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, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
|
|
||||||
let mut active = self.active.remove(&id);
|
|
||||||
if let Some(active) = &mut active {
|
|
||||||
for h in &active.primitives {
|
|
||||||
let mask = self.layers.free(h);
|
|
||||||
if mask != MaskIdx::NONE {
|
|
||||||
rsc.ui_mut().masks.remove(mask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
active.textures.clear();
|
|
||||||
rsc.ui_mut().textures.free();
|
|
||||||
if undraw {
|
|
||||||
rsc.on_undraw(active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
active
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_rec(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
|
|
||||||
self.cache.remove(id);
|
|
||||||
let inst = self.remove(id, true, rsc);
|
|
||||||
if let Some(inst) = &inst {
|
|
||||||
for c in &inst.children {
|
|
||||||
self.remove_rec(*c, rsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inst
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self, rsc: &mut dyn UiRsc) {
|
|
||||||
for (_, active) in self.active.drain() {
|
|
||||||
rsc.on_undraw(&active);
|
|
||||||
}
|
|
||||||
self.cache.clear();
|
|
||||||
self.layers.clear();
|
|
||||||
rsc.widgets_mut().needs_redraw.clear();
|
|
||||||
rsc.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
|
|
||||||
while let Some(&id) = rsc.widgets().needs_redraw.iter().next() {
|
|
||||||
self.redraw(id, rsc);
|
|
||||||
}
|
|
||||||
rsc.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn root_changed<'a>(&self, root: impl Into<Option<&'a StrongWidget>>) -> bool {
|
|
||||||
root.into().map(|r| r.id()) != self.old_root
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scheduling and drawing must use the same full-redraw predicate.
|
|
||||||
fn needs_full_redraw<'a>(&self, root: impl Into<Option<&'a StrongWidget>>) -> bool {
|
|
||||||
self.root_changed(root) || self.resized
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn needs_redraw<'a>(
|
|
||||||
&self,
|
|
||||||
root: impl Into<Option<&'a StrongWidget>>,
|
|
||||||
widgets: &Widgets,
|
|
||||||
) -> bool {
|
|
||||||
self.needs_full_redraw(root) || widgets.has_updates()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_widgets(&self) -> usize {
|
|
||||||
self.active.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug(&self, widgets: &Widgets, label: &str) -> impl Iterator<Item = &ActiveData> {
|
|
||||||
self.active.iter().filter_map(move |(&id, inst)| {
|
|
||||||
let l = widgets.label(id);
|
|
||||||
if l == label { Some(inst) } else { None }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug_layers(&self) {
|
|
||||||
for ((idx, depth), draws) in self.layers.iter_depth() {
|
|
||||||
let indent = " ".repeat(depth * 2);
|
|
||||||
let counts: Vec<String> = draws
|
|
||||||
.primitives()
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.as_ref().map_or(0, |l| l.instances().len()).to_string())
|
|
||||||
.collect();
|
|
||||||
println!("{indent}{idx}: [{}]", counts.join(", "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
|
|
||||||
let region = self.active.get(&id.id())?.region;
|
|
||||||
Some(region.to_px(self.output_size))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// redraws a widget that's currently active (drawn)
|
|
||||||
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
|
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
|
||||||
self.draw_started.remove(&id);
|
|
||||||
// check if parent depends on the desired size of this, if so then redraw it first
|
|
||||||
for axis in [Axis::X, Axis::Y] {
|
|
||||||
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
|
|
||||||
&& let Some(current) = self.active.get(&id)
|
|
||||||
&& let Some(pid) = current.parent
|
|
||||||
{
|
|
||||||
self.cache.size.axis_dyn(axis).remove(&id);
|
|
||||||
let new = self.size_ctx(id, outer, rsc).len_axis(id, axis);
|
|
||||||
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
|
|
||||||
if new != old {
|
|
||||||
self.redraw(pid, rsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.draw_started.contains(&id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(active) = self.remove(id, false, rsc) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.draw_inner(
|
|
||||||
active.layer,
|
|
||||||
id,
|
|
||||||
active.region,
|
|
||||||
active.parent,
|
|
||||||
active.mask,
|
|
||||||
Some(active.children),
|
|
||||||
rsc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn size_ctx<'b>(
|
|
||||||
&'b mut self,
|
|
||||||
source: WidgetId,
|
|
||||||
outer: UiVec2,
|
|
||||||
rsc: &'b mut dyn UiRsc,
|
|
||||||
) -> SizeCtx<'b> {
|
|
||||||
let ui = rsc.ui_mut();
|
|
||||||
SizeCtx {
|
|
||||||
source,
|
|
||||||
cache: &mut self.cache,
|
|
||||||
text: &mut ui.text,
|
|
||||||
widgets: &ui.widgets,
|
|
||||||
outer,
|
|
||||||
output_size: self.output_size,
|
|
||||||
id: source,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for UiRenderState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, UiVec2,
|
|
||||||
WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct SizeCtx<'a> {
|
|
||||||
pub text: &'a mut TextData,
|
|
||||||
pub(super) source: WidgetId,
|
|
||||||
pub(super) widgets: &'a Widgets,
|
|
||||||
pub(super) cache: &'a mut Cache,
|
|
||||||
/// TODO: should this be pub? rn used for sized
|
|
||||||
pub outer: UiVec2,
|
|
||||||
pub(super) output_size: Vec2,
|
|
||||||
pub(super) id: WidgetId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizeCtx<'_> {
|
|
||||||
pub fn id(&self) -> &WidgetId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source(&self) -> &WidgetId {
|
|
||||||
&self.source
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn len_inner<A: const AxisT>(&mut self, id: WidgetId) -> Len {
|
|
||||||
if let Some((_, len)) = self.cache.size.axis::<A>().get(&id) {
|
|
||||||
return *len;
|
|
||||||
}
|
|
||||||
let len = self
|
|
||||||
.widgets
|
|
||||||
.get_dyn_dynamic(id)
|
|
||||||
.desired_len::<A>(&mut SizeCtx {
|
|
||||||
text: self.text,
|
|
||||||
source: self.source,
|
|
||||||
widgets: self.widgets,
|
|
||||||
cache: self.cache,
|
|
||||||
outer: self.outer,
|
|
||||||
output_size: self.output_size,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
self.cache.size.axis::<A>().insert(id, (self.outer, len));
|
|
||||||
len
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&mut self, id: impl IdLike) -> Len {
|
|
||||||
self.len_inner::<XAxis>(id.id())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn height(&mut self, id: impl IdLike) -> Len {
|
|
||||||
self.len_inner::<YAxis>(id.id())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len_axis(&mut self, id: impl IdLike, axis: Axis) -> Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.width(id),
|
|
||||||
Axis::Y => self.height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&mut self, id: impl IdLike) -> Size {
|
|
||||||
let id = id.id();
|
|
||||||
Size {
|
|
||||||
x: self.width(id),
|
|
||||||
y: self.height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn px_size(&mut self) -> Vec2 {
|
|
||||||
self.outer.to_abs(self.output_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_size(&mut self) -> Vec2 {
|
|
||||||
self.output_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_text(
|
|
||||||
&mut self,
|
|
||||||
buffer: &mut TextBuffer,
|
|
||||||
attrs: &TextAttrs,
|
|
||||||
width: Option<f32>,
|
|
||||||
) -> RenderedText {
|
|
||||||
self.text.render(buffer, attrs, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn label(&self, id: WidgetId) -> &String {
|
|
||||||
self.widgets.label(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ pub struct Arena<T, I> {
|
|||||||
tracker: IdTracker<I>,
|
tracker: IdTracker<I>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, I: IdNum> Arena<T, I> {
|
impl<T, I: const IdNum> Arena<T, I> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: Vec::new(),
|
data: Vec::new(),
|
||||||
@@ -36,7 +36,7 @@ impl<T, I: IdNum> Arena<T, I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, I: IdNum> Default for Arena<T, I> {
|
impl<T, I: const IdNum> Default for Arena<T, I> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ pub struct TrackedArena<T, I> {
|
|||||||
pub changed: bool,
|
pub changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, I: IdNum> TrackedArena<T, I> {
|
impl<T, I: const IdNum> TrackedArena<T, I> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arena::default(),
|
inner: Arena::default(),
|
||||||
@@ -86,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 {
|
fn default() -> Self {
|
||||||
Self::new()
|
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)]
|
#[repr(C)]
|
||||||
#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)]
|
#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)]
|
||||||
pub struct Id<I = u64>(I);
|
pub struct Id<I = u64>(I);
|
||||||
@@ -9,7 +11,14 @@ pub struct IdTracker<I = u64> {
|
|||||||
cur: Id<I>,
|
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)]
|
#[allow(clippy::should_implement_trait)]
|
||||||
pub fn next(&mut self) -> Id<I> {
|
pub fn next(&mut self) -> Id<I> {
|
||||||
if let Some(id) = self.free.pop() {
|
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 {
|
fn default() -> Self {
|
||||||
Self {
|
Self::new()
|
||||||
free: Vec::new(),
|
|
||||||
cur: Id(I::first()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IdNum {
|
pub const trait IdNum {
|
||||||
fn first() -> Self;
|
fn first() -> Self;
|
||||||
fn next(&self) -> Self;
|
fn next(&self) -> Self;
|
||||||
fn idx(&self) -> usize;
|
fn idx(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdNum for u64 {
|
impl const IdNum for u64 {
|
||||||
fn first() -> Self {
|
fn first() -> Self {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@@ -72,7 +78,7 @@ impl IdNum for u64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdNum for u32 {
|
impl const IdNum for u32 {
|
||||||
fn first() -> Self {
|
fn first() -> Self {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@@ -85,3 +91,33 @@ impl IdNum for u32 {
|
|||||||
*self as usize
|
*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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,16 +9,15 @@ pub const trait DivOr {
|
|||||||
fn div_or(self, rhs: Self, other: Self) -> Self;
|
fn div_or(self, rhs: Self, other: Self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl DivOr for f32 {
|
impl const DivOr for f32 {
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
fn div_or(self, rhs: Self, other: Self) -> Self {
|
||||||
let res = self / rhs;
|
let res = self / rhs;
|
||||||
if res.is_nan() { other } else { res }
|
if res.is_nan() { other } else { res }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl<
|
impl<T: const Add<Output = T> + const Sub<Output = T> + const Mul<Output = T> + const DivOr + Copy> const
|
||||||
T: const Add<Output = T> + const Sub<Output = T> + const Mul<Output = T> + const DivOr + Copy,
|
LerpUtil for T
|
||||||
> LerpUtil for T
|
|
||||||
{
|
{
|
||||||
/// linear interpolation
|
/// linear interpolation
|
||||||
/// from * (1.0 - self) + to * self
|
/// from * (1.0 - self) + to * self
|
||||||
@@ -38,7 +37,7 @@ macro_rules! impl_op {
|
|||||||
use super::*;
|
use super::*;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
const impl $op for $T {
|
impl const $op for $T {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn $fn(self, rhs: Self) -> Self::Output {
|
fn $fn(self, rhs: Self) -> Self::Output {
|
||||||
@@ -47,12 +46,12 @@ macro_rules! impl_op {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const impl $opa for $T {
|
impl const $opa for $T {
|
||||||
fn $fna(&mut self, rhs: Self) {
|
fn $fna(&mut self, rhs: Self) {
|
||||||
*self = self.$fn(rhs);
|
*self = self.$fn(rhs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const impl $op<f32> for $T {
|
impl const $op<f32> for $T {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn $fn(self, rhs: f32) -> Self::Output {
|
fn $fn(self, rhs: f32) -> Self::Output {
|
||||||
@@ -61,7 +60,7 @@ macro_rules! impl_op {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const impl $op<$T> for f32 {
|
impl const $op<$T> for f32 {
|
||||||
type Output = $T;
|
type Output = $T;
|
||||||
|
|
||||||
fn $fn(self, rhs: $T) -> Self::Output {
|
fn $fn(self, rhs: $T) -> Self::Output {
|
||||||
@@ -70,7 +69,7 @@ macro_rules! impl_op {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const impl $opa<f32> for $T {
|
impl const $opa<f32> for $T {
|
||||||
fn $fna(&mut self, rhs: f32) {
|
fn $fna(&mut self, rhs: f32) {
|
||||||
*self = self.$fn(rhs);
|
*self = self.$fn(rhs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
mod arena;
|
mod arena;
|
||||||
mod borrow;
|
|
||||||
mod change;
|
mod change;
|
||||||
|
mod handle;
|
||||||
mod id;
|
mod id;
|
||||||
mod math;
|
mod math;
|
||||||
mod refcount;
|
mod refcount;
|
||||||
mod slot;
|
|
||||||
mod trust;
|
|
||||||
mod typemap;
|
|
||||||
mod vec2;
|
mod vec2;
|
||||||
|
|
||||||
pub use arena::*;
|
pub use arena::*;
|
||||||
pub use borrow::*;
|
|
||||||
pub use change::*;
|
pub use change::*;
|
||||||
|
pub use handle::*;
|
||||||
pub use id::*;
|
pub use id::*;
|
||||||
pub use math::*;
|
pub use math::*;
|
||||||
pub use refcount::*;
|
pub use refcount::*;
|
||||||
pub use slot::*;
|
|
||||||
pub(crate) use trust::*;
|
|
||||||
pub use typemap::*;
|
|
||||||
pub use vec2::*;
|
pub use vec2::*;
|
||||||
|
|
||||||
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
|
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ impl RefCounter {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Arc::new(0.into()))
|
Self(Arc::new(0.into()))
|
||||||
}
|
}
|
||||||
|
#[allow(unused)]
|
||||||
pub fn refs(&self) -> u32 {
|
pub fn refs(&self) -> u32 {
|
||||||
self.0.load(Ordering::Acquire)
|
self.0.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
@@ -17,6 +18,7 @@ impl RefCounter {
|
|||||||
let refs = self.0.fetch_sub(1, Ordering::Release);
|
let refs = self.0.fetch_sub(1, Ordering::Release);
|
||||||
refs == 0
|
refs == 0
|
||||||
}
|
}
|
||||||
|
#[allow(unused)]
|
||||||
pub fn quiet_clone(&self) -> Self {
|
pub fn quiet_clone(&self) -> Self {
|
||||||
Self(self.0.clone())
|
Self(self.0.clone())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
||||||
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,17 +0,0 @@
|
|||||||
#[allow(clippy::missing_safety_doc)]
|
|
||||||
pub(crate) unsafe fn forget_ref<'a, T>(x: &T) -> &'a T {
|
|
||||||
unsafe { std::mem::transmute::<&T, &T>(x) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -67,7 +67,7 @@ impl_op!(Vec2 Sub sub; x y);
|
|||||||
impl_op!(Vec2 Mul mul; x y);
|
impl_op!(Vec2 Mul mul; x y);
|
||||||
impl_op!(Vec2 Div div; x y);
|
impl_op!(Vec2 Div div; x y);
|
||||||
|
|
||||||
const impl DivOr for Vec2 {
|
impl const DivOr for Vec2 {
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
fn div_or(self, rhs: Self, other: Self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x.div_or(rhs.x, other.x),
|
x: self.x.div_or(rhs.x, other.x),
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
use crate::Widget;
|
|
||||||
|
|
||||||
pub struct WidgetData {
|
|
||||||
pub widget: Box<dyn Widget>,
|
|
||||||
pub label: String,
|
|
||||||
/// 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,
|
|
||||||
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,87 +0,0 @@
|
|||||||
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
|
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
mod data;
|
|
||||||
mod handle;
|
|
||||||
mod like;
|
|
||||||
mod tag;
|
|
||||||
mod view;
|
|
||||||
mod widgets;
|
|
||||||
|
|
||||||
pub use data::*;
|
|
||||||
pub use handle::*;
|
|
||||||
pub use like::*;
|
|
||||||
pub use tag::*;
|
|
||||||
pub use view::*;
|
|
||||||
pub use widgets::*;
|
|
||||||
|
|
||||||
pub trait Widget: Any {
|
|
||||||
fn draw(&mut self, painter: &mut Painter);
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait WidgetAxisFns {
|
|
||||||
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget + ?Sized> WidgetAxisFns for W {
|
|
||||||
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match A::get() {
|
|
||||||
Axis::X => self.desired_width(ctx),
|
|
||||||
Axis::Y => self.desired_height(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for () {
|
|
||||||
fn draw(&mut self, _: &mut Painter) {}
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl dyn Widget {
|
|
||||||
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,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,145 +0,0 @@
|
|||||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
IdLike, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
fn main() {
|
||||||
DefaultApp::<State>::run();
|
DefaultApp::<State>::run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DefaultUiState)]
|
struct State;
|
||||||
struct State {
|
|
||||||
ui_state: DefaultUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DefaultAppState for State {
|
impl DefaultAppState for State {
|
||||||
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 {
|
||||||
rect(Color::RED).set_root(rsc, &mut ui_state);
|
rect(Color::RED).set_root(ui);
|
||||||
Self { ui_state }
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,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
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0.103"
|
|
||||||
quote = "1.0.42"
|
quote = "1.0.42"
|
||||||
syn = { version = "2.0.111", features = ["full"] }
|
syn = { version = "2.0.111", features = ["full"] }
|
||||||
|
|
||||||
|
|||||||
+5
-124
@@ -2,18 +2,15 @@ extern crate proc_macro;
|
|||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
Attribute, Block, Error, GenericParam, Generics, Ident, ItemStruct, ItemTrait, Signature,
|
Attribute, Block, Ident, ItemTrait, Signature, Token, Visibility,
|
||||||
Token, Type, Visibility,
|
|
||||||
parse::{Parse, ParseStream, Result},
|
parse::{Parse, ParseStream, Result},
|
||||||
parse_macro_input, parse_quote,
|
parse_macro_input, parse_quote,
|
||||||
spanned::Spanned,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Input {
|
struct Input {
|
||||||
attrs: Vec<Attribute>,
|
attrs: Vec<Attribute>,
|
||||||
vis: Visibility,
|
vis: Visibility,
|
||||||
name: Ident,
|
name: Ident,
|
||||||
generics: Generics,
|
|
||||||
fns: Vec<InputFn>,
|
fns: Vec<InputFn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +25,6 @@ impl Parse for Input {
|
|||||||
let vis = input.parse()?;
|
let vis = input.parse()?;
|
||||||
input.parse::<Token![trait]>()?;
|
input.parse::<Token![trait]>()?;
|
||||||
let name = input.parse()?;
|
let name = input.parse()?;
|
||||||
let generics = input.parse::<Generics>()?;
|
|
||||||
input.parse::<Token![;]>()?;
|
input.parse::<Token![;]>()?;
|
||||||
let mut fns = Vec::new();
|
let mut fns = Vec::new();
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
@@ -43,7 +39,6 @@ impl Parse for Input {
|
|||||||
attrs,
|
attrs,
|
||||||
vis,
|
vis,
|
||||||
name,
|
name,
|
||||||
generics,
|
|
||||||
fns,
|
fns,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -55,7 +50,6 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
|
|||||||
attrs,
|
attrs,
|
||||||
vis,
|
vis,
|
||||||
name,
|
name,
|
||||||
mut generics,
|
|
||||||
fns,
|
fns,
|
||||||
} = parse_macro_input!(input as Input);
|
} = parse_macro_input!(input as Input);
|
||||||
|
|
||||||
@@ -65,132 +59,19 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
|
|||||||
.map(|InputFn { sig, body }| quote! { #sig #body })
|
.map(|InputFn { sig, body }| quote! { #sig #body })
|
||||||
.collect();
|
.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!(
|
let mut trai: ItemTrait = parse_quote!(
|
||||||
#vis trait #name #generics {
|
#vis trait #name<WL: WidgetLike<Tag>, Tag> {
|
||||||
#(#sigs;)*
|
#(#sigs;)*
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
trai.attrs = attrs;
|
trai.attrs = attrs;
|
||||||
|
|
||||||
quote! {
|
TokenStream::from(quote! {
|
||||||
#trai
|
#trai
|
||||||
|
|
||||||
impl #generics #name<Rsc, WL, Tag> for WL {
|
impl<WL: WidgetLike<Tag>, Tag> #name<WL, Tag> for WL {
|
||||||
#(#impls)*
|
#(#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
|
# 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)
|
1. does what I want it to (text, images, video, animations)
|
||||||
2. very easy to use ignoring ergonomic ref counting
|
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 targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps / not need a web browser
|
|
||||||
|
|
||||||
general ideas trynna use rn / experiment with:
|
general ideas trynna use rn / experiment with:
|
||||||
- retained mode
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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;
|
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
|
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,3 +0,0 @@
|
|||||||
[toolchain]
|
|
||||||
channel = "nightly"
|
|
||||||
components = ["clippy", "rustfmt"]
|
|
||||||
File renamed without changes.
@@ -1,21 +1,20 @@
|
|||||||
use std::{cell::RefCell, rc::Rc};
|
use cosmic_text::Family;
|
||||||
use winit::event::WindowEvent;
|
|
||||||
|
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
type ClientRsc = DefaultRsc<Client>;
|
use len_fns::*;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
DefaultApp::<Client>::run();
|
DefaultApp::<Client>::run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DefaultUiState)]
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
ui_state: DefaultUiState,
|
info: WidgetRef<Text>,
|
||||||
info: WeakWidget<Text>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event_ctx!(Client);
|
||||||
|
|
||||||
impl DefaultAppState for 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 rrect = rect(Color::WHITE).radius(20);
|
||||||
let pad_test = (
|
let pad_test = (
|
||||||
rrect.color(Color::BLUE),
|
rrect.color(Color::BLUE),
|
||||||
@@ -38,7 +37,7 @@ impl DefaultAppState for Client {
|
|||||||
.width(rest(3)),
|
.width(rest(3)),
|
||||||
)
|
)
|
||||||
.span(Dir::RIGHT)
|
.span(Dir::RIGHT)
|
||||||
.add(rsc);
|
.add(ui);
|
||||||
|
|
||||||
let span_test = (
|
let span_test = (
|
||||||
rrect.color(Color::GREEN).width(100),
|
rrect.color(Color::GREEN).width(100),
|
||||||
@@ -49,30 +48,31 @@ impl DefaultAppState for Client {
|
|||||||
rrect.color(Color::RED).width(100),
|
rrect.color(Color::RED).width(100),
|
||||||
)
|
)
|
||||||
.span(Dir::LEFT)
|
.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)
|
let add_button = rect(Color::LIME)
|
||||||
.radius(30)
|
.radius(30)
|
||||||
.on(CursorSense::click(), move |_, rsc| {
|
.on(CursorSense::click(), move |ctx| {
|
||||||
let child = image(include_bytes!("assets/sungals.png"))
|
let child = image(include_bytes!("assets/sungals.png"))
|
||||||
.center()
|
.center()
|
||||||
.add_strong(rsc);
|
.add(ctx.data.ui);
|
||||||
span_add(rsc).push(child);
|
span_add_.get_mut().children.push(child);
|
||||||
})
|
})
|
||||||
.sized((150, 150))
|
.sized((150, 150))
|
||||||
.align(Align::BOT_RIGHT);
|
.align(Align::BOT_RIGHT);
|
||||||
|
|
||||||
let del_button = rect(Color::RED)
|
let del_button = rect(Color::RED)
|
||||||
.radius(30)
|
.radius(30)
|
||||||
.on(CursorSense::click(), move |_, rsc| {
|
.on(CursorSense::click(), move |_| {
|
||||||
span_add(rsc).pop();
|
span_add_.get_mut().children.pop();
|
||||||
})
|
})
|
||||||
.sized((150, 150))
|
.sized((150, 150))
|
||||||
.align(Align::BOT_LEFT);
|
.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);
|
let btext = |content| wtext(content).size(30);
|
||||||
|
|
||||||
@@ -95,31 +95,31 @@ impl DefaultAppState for Client {
|
|||||||
wtext("pretty cool right?").size(50),
|
wtext("pretty cool right?").size(50),
|
||||||
)
|
)
|
||||||
.span(Dir::DOWN)
|
.span(Dir::DOWN)
|
||||||
.add(rsc);
|
.add(ui);
|
||||||
|
|
||||||
let texts = Span::empty(Dir::DOWN).gap(10).add(rsc);
|
let texts = Span::empty(Dir::DOWN).gap(10).add(ui);
|
||||||
let msg_area = texts.scrollable().masked().background(rect(Color::SKY));
|
let texts_ = texts.weak();
|
||||||
|
let msg_area = texts.scroll().masked().background(rect(Color::SKY));
|
||||||
let add_text = wtext("add")
|
let add_text = wtext("add")
|
||||||
.editable(EditMode::MultiLine)
|
.editable(false)
|
||||||
.text_align(Align::LEFT)
|
.text_align(Align::LEFT)
|
||||||
.size(30)
|
.size(30)
|
||||||
.attr::<Selectable>(())
|
.attr::<Selectable>(())
|
||||||
.on(Submit, move |ctx, rsc| {
|
.on(Submit, move |ctx| {
|
||||||
let w = ctx.widget;
|
let content = ctx.widget.get_mut().take();
|
||||||
let content = w.edit(rsc).take();
|
|
||||||
let text = wtext(content)
|
let text = wtext(content)
|
||||||
.editable(EditMode::MultiLine)
|
.editable(false)
|
||||||
.size(30)
|
.size(30)
|
||||||
.text_align(Align::LEFT)
|
.text_align(Align::LEFT)
|
||||||
.wrap(true)
|
.wrap(true)
|
||||||
.attr::<Selectable>(());
|
.attr::<Selectable>(());
|
||||||
let msg_box = text
|
let msg_box = text
|
||||||
.background(rect(Color::WHITE.darker(0.5)))
|
.background(rect(Color::WHITE.darker(0.5)))
|
||||||
.add_strong(rsc);
|
.add(&mut Ui::new());
|
||||||
texts(rsc).push(msg_box);
|
texts_.get_mut().children.push(msg_box);
|
||||||
})
|
})
|
||||||
.add(rsc);
|
.add(ui);
|
||||||
|
let add_text_ = add_text.weak();
|
||||||
let text_edit_scroll = (
|
let text_edit_scroll = (
|
||||||
msg_area.height(rest(1)),
|
msg_area.height(rest(1)),
|
||||||
(
|
(
|
||||||
@@ -127,8 +127,8 @@ impl DefaultAppState for Client {
|
|||||||
(
|
(
|
||||||
add_text.width(rest(1)),
|
add_text.width(rest(1)),
|
||||||
Rect::new(Color::GREEN)
|
Rect::new(Color::GREEN)
|
||||||
.on(CursorSense::click(), move |ctx, rsc: &mut ClientRsc| {
|
.on(CursorSense::click(), move |ctx| {
|
||||||
rsc.run_event::<Submit>(add_text, (), ctx.state);
|
Events::<Submit, _>::run(add_text_.id(), &mut (), ctx.state);
|
||||||
})
|
})
|
||||||
.sized((40, 40)),
|
.sized((40, 40)),
|
||||||
)
|
)
|
||||||
@@ -141,80 +141,73 @@ impl DefaultAppState for Client {
|
|||||||
.align(Align::BOT),
|
.align(Align::BOT),
|
||||||
)
|
)
|
||||||
.span(Dir::DOWN)
|
.span(Dir::DOWN)
|
||||||
.add(rsc);
|
.add(ui);
|
||||||
|
|
||||||
let main = WidgetPtr::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 tab_handles = Handle::from((0, Vec::new()));
|
||||||
let mut switch_button = |color, to: WeakWidget, label| {
|
|
||||||
let to = to.upgrade(rsc);
|
let switch_button = |color, to: Option<WidgetHandle>, label| {
|
||||||
let vec = &mut vals.borrow_mut().1;
|
let tab_handles = tab_handles.clone();
|
||||||
let i = vec.len();
|
let i = tab_handles.get().1.len();
|
||||||
if vec.is_empty() {
|
tab_handles.get_mut().1.push(to);
|
||||||
vec.push(None);
|
|
||||||
main(rsc).set(to);
|
|
||||||
} else {
|
|
||||||
vec.push(Some(to));
|
|
||||||
}
|
|
||||||
let vals = vals.clone();
|
|
||||||
let rect = rect(color)
|
let rect = rect(color)
|
||||||
.on(CursorSense::click(), move |ctx, rsc| {
|
.on(CursorSense::click(), move |ctx| {
|
||||||
let (prev, vec) = &mut *vals.borrow_mut();
|
let (prev, all) = &mut *tab_handles.get_mut();
|
||||||
if let Some(h) = vec[i].take() {
|
if let Some(to) = &mut all[i] {
|
||||||
vec[*prev] = main(rsc).replace(h);
|
let mut main = main_.get_mut();
|
||||||
|
std::mem::swap(&mut main.inner, to);
|
||||||
|
all.swap(*prev, i);
|
||||||
*prev = i;
|
*prev = i;
|
||||||
}
|
}
|
||||||
ctx.widget(rsc).color = color.darker(0.3);
|
ctx.widget.get_mut().color = color.darker(0.3);
|
||||||
})
|
})
|
||||||
.on(
|
.on(
|
||||||
CursorSense::HoverStart | CursorSense::unclick(),
|
CursorSense::HoverStart | CursorSense::unclick(),
|
||||||
move |ctx, rsc| {
|
move |ctx| {
|
||||||
ctx.widget(rsc).color = color.brighter(0.2);
|
ctx.widget.get_mut().color = color.brighter(0.2);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.on(CursorSense::HoverEnd, move |ctx, rsc| {
|
.on(CursorSense::HoverEnd, move |ctx| {
|
||||||
ctx.widget(rsc).color = color;
|
ctx.widget.get_mut().color = color;
|
||||||
});
|
});
|
||||||
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
|
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
|
||||||
};
|
};
|
||||||
|
|
||||||
let tabs = (
|
let tabs = (
|
||||||
switch_button(Color::RED, pad_test, "pad"),
|
switch_button(Color::RED, None, "pad"),
|
||||||
switch_button(Color::GREEN, span_test, "span"),
|
switch_button(Color::GREEN, Some(span_test), "span"),
|
||||||
switch_button(Color::BLUE, span_add_test, "image span"),
|
switch_button(Color::BLUE, Some(span_add_test), "image span"),
|
||||||
switch_button(Color::MAGENTA, text_test, "text layout"),
|
switch_button(Color::MAGENTA, Some(text_test), "text layout"),
|
||||||
switch_button(
|
switch_button(
|
||||||
Color::YELLOW.mul_rgb(0.5),
|
Color::YELLOW.mul_rgb(0.5),
|
||||||
text_edit_scroll,
|
Some(text_edit_scroll),
|
||||||
"text edit scroll",
|
"text edit scroll",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.span(Dir::RIGHT);
|
.span(Dir::RIGHT);
|
||||||
|
|
||||||
let info = wtext("").add(rsc);
|
let info_ = wtext("").add(ui);
|
||||||
let info_sect = info.pad(10).align(Align::RIGHT);
|
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()
|
.stack()
|
||||||
.set_root(rsc, &mut ui_state);
|
.set_root(ui);
|
||||||
|
|
||||||
Self { ui_state, info }
|
Self { info }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(
|
fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) {
|
||||||
&mut self,
|
|
||||||
_: WindowEvent,
|
|
||||||
rsc: &mut DefaultRsc<Self>,
|
|
||||||
render: &mut UiRenderState,
|
|
||||||
) {
|
|
||||||
let new = format!(
|
let new = format!(
|
||||||
"widgets: {}\nactive: {}\ntextures: {}",
|
"widgets: {}\nactive:{}\nviews: {}",
|
||||||
rsc.widgets().len(),
|
total_widgets(),
|
||||||
render.active_widgets(),
|
ui.active_widgets(),
|
||||||
rsc.ui().textures.count(),
|
state.renderer.ui.view_count()
|
||||||
);
|
);
|
||||||
if new != *rsc.widgets()[self.info].content {
|
if new != *self.info.get().content {
|
||||||
*rsc.widgets_mut()[self.info].content = new;
|
*self.info.get_mut().content = new;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,13 +11,6 @@ pub trait AppState {
|
|||||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
|
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
|
||||||
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
|
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
|
||||||
fn exit(&mut self);
|
fn exit(&mut self);
|
||||||
|
|
||||||
fn run()
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
App::<Self>::run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App<State: AppState> {
|
pub struct App<State: AppState> {
|
||||||
|
|||||||
+21
-35
@@ -1,50 +1,38 @@
|
|||||||
use crate::prelude::*;
|
use crate::{default::UiState, prelude::*};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use winit::dpi::{LogicalPosition, LogicalSize};
|
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
|
||||||
|
event_ctx!(UiState);
|
||||||
|
|
||||||
pub struct Selector;
|
pub struct Selector;
|
||||||
|
|
||||||
impl<Rsc: HasEvents, W: Widget + 'static> WidgetAttr<Rsc, W> for Selector
|
impl<W: Widget + 'static> WidgetAttr<W> for Selector {
|
||||||
where
|
type Input = WidgetRef<TextEdit>;
|
||||||
Rsc::State: HasDefaultUiState,
|
|
||||||
{
|
|
||||||
type Input = WeakWidget<TextEdit>;
|
|
||||||
|
|
||||||
fn run(rsc: &mut Rsc, container: WeakWidget<W>, id: Self::Input) {
|
fn run(ui: &mut Ui, container: WidgetRef<W>, id: Self::Input) {
|
||||||
rsc.register_event(container, CursorSense::click_or_drag(), move |ctx, rsc| {
|
container.on(CursorSense::click_or_drag(), move |ctx| {
|
||||||
let region = ctx.data.render.window_region(&id).unwrap();
|
let ui = &mut ctx.data.ui;
|
||||||
|
let region = ui.window_region(&id).unwrap();
|
||||||
let id_pos = region.top_left;
|
let id_pos = region.top_left;
|
||||||
let container_pos = ctx.data.render.window_region(&container).unwrap().top_left;
|
let container_pos = ui.window_region(&container).unwrap().top_left;
|
||||||
let pos = ctx.data.pos + container_pos - id_pos;
|
let pos = ctx.data.pos + container_pos - id_pos;
|
||||||
let size = region.size();
|
let size = region.size();
|
||||||
select(
|
select(ui, id, ctx.state, pos, size, ctx.data.sense.is_dragging());
|
||||||
rsc,
|
|
||||||
ctx.data.render,
|
|
||||||
ctx.state,
|
|
||||||
id,
|
|
||||||
pos,
|
|
||||||
size,
|
|
||||||
ctx.data.sense.is_dragging(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Selectable;
|
pub struct Selectable;
|
||||||
|
|
||||||
impl<Rsc: HasEvents> WidgetAttr<Rsc, TextEdit> for Selectable
|
impl WidgetAttr<TextEdit> for Selectable {
|
||||||
where
|
|
||||||
Rsc::State: HasDefaultUiState,
|
|
||||||
{
|
|
||||||
type Input = ();
|
type Input = ();
|
||||||
|
|
||||||
fn run(rsc: &mut Rsc, id: WeakWidget<TextEdit>, _: Self::Input) {
|
fn run(ui: &mut Ui, id: WidgetRef<TextEdit>, _: Self::Input) {
|
||||||
rsc.register_event(id, CursorSense::click_or_drag(), move |ctx, rsc| {
|
id.on(CursorSense::click_or_drag(), move |ctx| {
|
||||||
select(
|
select(
|
||||||
rsc,
|
ctx.data.ui,
|
||||||
ctx.data.render,
|
|
||||||
ctx.state,
|
|
||||||
id,
|
id,
|
||||||
|
ctx.state,
|
||||||
ctx.data.pos,
|
ctx.data.pos,
|
||||||
ctx.data.size,
|
ctx.data.size,
|
||||||
ctx.data.sense.is_dragging(),
|
ctx.data.sense.is_dragging(),
|
||||||
@@ -54,20 +42,18 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn select(
|
fn select(
|
||||||
rsc: &mut impl UiRsc,
|
ui: &mut Ui,
|
||||||
render: &UiRenderState,
|
id: WidgetRef<TextEdit>,
|
||||||
state: &mut impl HasDefaultUiState,
|
state: &mut UiState,
|
||||||
id: WeakWidget<TextEdit>,
|
|
||||||
pos: Vec2,
|
pos: Vec2,
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
dragging: bool,
|
dragging: bool,
|
||||||
) {
|
) {
|
||||||
let state = state.default_state_mut();
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let recent = (now - state.last_click) < Duration::from_millis(300);
|
let recent = (now - state.last_click) < Duration::from_millis(300);
|
||||||
state.last_click = now;
|
state.last_click = now;
|
||||||
id.edit(rsc).select(pos, size, dragging, recent);
|
id.get_mut().select(pos, size, dragging, recent);
|
||||||
if let Some(region) = render.window_region(&id) {
|
if let Some(region) = ui.window_region(&id) {
|
||||||
state.window.set_ime_allowed(true);
|
state.window.set_ime_allowed(true);
|
||||||
state.window.set_ime_cursor_area(
|
state.window.set_ime_cursor_area(
|
||||||
LogicalPosition::<f32>::from(region.top_left.tuple()),
|
LogicalPosition::<f32>::from(region.top_left.tuple()),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use iris_core::Event;
|
use iris_core::layout::Event;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Submit;
|
pub struct Submit;
|
||||||
impl Event for Submit {}
|
impl Event for Submit {}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Edited;
|
pub struct Edited;
|
||||||
impl Event for Edited {}
|
impl Event for Edited {}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::{
|
||||||
|
widget::{CursorState, Modifiers},
|
||||||
|
layout::Vec2,
|
||||||
|
default::UiState,
|
||||||
|
};
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
||||||
keyboard::{Key, NamedKey},
|
keyboard::{Key, NamedKey},
|
||||||
@@ -66,7 +70,7 @@ impl Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultUiState {
|
impl UiState {
|
||||||
pub fn window_size(&self) -> Vec2 {
|
pub fn window_size(&self) -> Vec2 {
|
||||||
let size = self.renderer.window().inner_size();
|
let size = self.renderer.window().inner_size();
|
||||||
(size.width, size.height).into()
|
(size.width, size.height).into()
|
||||||
|
|||||||
+70
-258
@@ -1,82 +1,67 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use arboard::Clipboard;
|
use arboard::Clipboard;
|
||||||
use std::{marker::PhantomData, sync::Arc, time::Instant};
|
use std::sync::Arc;
|
||||||
use winit::{
|
use std::time::Instant;
|
||||||
event::{Ime, WindowEvent},
|
use winit::event::{Ime, WindowEvent};
|
||||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
|
||||||
window::{Window, WindowAttributes},
|
use winit::window::{Window, WindowAttributes};
|
||||||
};
|
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod attr;
|
mod attr;
|
||||||
mod event;
|
mod event;
|
||||||
mod input;
|
mod input;
|
||||||
mod render;
|
mod render;
|
||||||
mod sense;
|
|
||||||
mod state;
|
|
||||||
mod task;
|
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use attr::*;
|
pub use attr::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use render::*;
|
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
|
pub type Proxy<Event> = EventLoopProxy<Event>;
|
||||||
/// rather than being one because task updates travel the same way: what an
|
pub type DefaultApp<Data> = App<DefaultState<Data>>;
|
||||||
/// application sends is its `Event`, not the loop's whole message type.
|
|
||||||
pub struct Proxy<State: DefaultAppState>(EventLoopProxy<DefaultEvent<State>>);
|
|
||||||
|
|
||||||
impl<State: DefaultAppState> Clone for Proxy<State> {
|
pub struct DefaultState<AppState> {
|
||||||
fn clone(&self) -> Self {
|
ui: Ui,
|
||||||
Self(self.0.clone())
|
ui_state: UiState,
|
||||||
}
|
app_state: AppState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<State: DefaultAppState> Proxy<State> {
|
pub struct UiState {
|
||||||
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 renderer: UiRenderer,
|
pub renderer: UiRenderer,
|
||||||
pub input: Input,
|
pub input: Input,
|
||||||
pub focus: Option<WeakWidget<TextEdit>>,
|
pub focus: Option<WidgetRef<TextEdit>>,
|
||||||
pub clipboard: Clipboard,
|
pub clipboard: Clipboard,
|
||||||
pub window: Arc<Window>,
|
pub window: Arc<Window>,
|
||||||
pub ime: usize,
|
pub ime: usize,
|
||||||
pub last_click: Instant,
|
pub last_click: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasRoot for DefaultUiState {
|
pub trait DefaultAppState: 'static {
|
||||||
fn set_root(&mut self, root: StrongWidget) {
|
type Event: 'static = ();
|
||||||
self.root = Some(root);
|
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 {
|
impl<State: DefaultAppState> AppState for DefaultState<State> {
|
||||||
pub fn new(window: impl Into<Arc<Window>>) -> Self {
|
type Event = State::Event;
|
||||||
let window = window.into();
|
|
||||||
Self {
|
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
|
||||||
root: None,
|
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()),
|
renderer: UiRenderer::new(window.clone()),
|
||||||
window,
|
window,
|
||||||
input: Input::default(),
|
input: Input::default(),
|
||||||
@@ -84,150 +69,26 @@ impl DefaultUiState {
|
|||||||
ime: 0,
|
ime: 0,
|
||||||
last_click: Instant::now(),
|
last_click: Instant::now(),
|
||||||
focus: None,
|
focus: None,
|
||||||
}
|
};
|
||||||
}
|
let app_state = State::new(&mut ui, &ui_state, proxy);
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
Self {
|
Self {
|
||||||
ui: Default::default(),
|
ui,
|
||||||
events: Default::default(),
|
ui_state,
|
||||||
tasks: Tasks::init(queue),
|
app_state,
|
||||||
state: Default::default(),
|
|
||||||
_state: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
|
||||||
match event {
|
self.app_state.event(event, &mut self.ui, &self.ui_state);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
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 input_changed = ui_state.input.event(&event);
|
||||||
let cursor_state = ui_state.cursor_state().clone();
|
let cursor_state = ui_state.cursor_state().clone();
|
||||||
let old = ui_state.focus;
|
let old = ui_state.focus;
|
||||||
@@ -236,43 +97,46 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
|||||||
}
|
}
|
||||||
if input_changed {
|
if input_changed {
|
||||||
let window_size = ui_state.window_size();
|
let window_size = ui_state.window_size();
|
||||||
render.run_sensors(rsc, state, cursor_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
|
if old != ui_state.focus
|
||||||
&& let Some(old) = old
|
&& let Some(old) = old
|
||||||
{
|
{
|
||||||
old.edit(rsc).deselect();
|
old.get_mut().deselect();
|
||||||
}
|
}
|
||||||
match &event {
|
match &event {
|
||||||
WindowEvent::CloseRequested => event_loop.exit(),
|
WindowEvent::CloseRequested => event_loop.exit(),
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
render.update(&ui_state.root, rsc);
|
ui_state.renderer.update(ui);
|
||||||
ui_state.renderer.update(&mut rsc.ui, render);
|
|
||||||
ui_state.renderer.draw();
|
ui_state.renderer.draw();
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(size) => {
|
WindowEvent::Resized(size) => {
|
||||||
render.resize((size.width, size.height));
|
ui.resize((size.width, size.height));
|
||||||
ui_state.renderer.resize(size)
|
ui_state.renderer.resize(size)
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
WindowEvent::KeyboardInput { event, .. } => {
|
||||||
if let Some(sel) = ui_state.focus
|
if let Some(sel) = ui_state.focus
|
||||||
&& event.state.is_pressed()
|
&& event.state.is_pressed()
|
||||||
{
|
{
|
||||||
let mut text = sel.edit(rsc);
|
let res = sel.get_mut().apply_event(event, &ui_state.input.modifiers);
|
||||||
match text.apply_event(event, &ui_state.input.modifiers) {
|
match res {
|
||||||
TextInputResult::Unfocus => {
|
TextInputResult::Unfocus => {
|
||||||
ui_state.focus = None;
|
ui_state.focus = None;
|
||||||
ui_state.window.set_ime_allowed(false);
|
ui_state.window.set_ime_allowed(false);
|
||||||
}
|
}
|
||||||
TextInputResult::Submit => {
|
TextInputResult::Submit => {
|
||||||
rsc.run_event::<Submit>(sel, (), state);
|
Events::<Submit, _>::run(sel.id(), &mut (), app_state);
|
||||||
}
|
}
|
||||||
TextInputResult::Paste => {
|
TextInputResult::Paste => {
|
||||||
if let Ok(t) = ui_state.clipboard.get_text() {
|
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) => {
|
TextInputResult::Copy(text) => {
|
||||||
if let Err(err) = ui_state.clipboard.set_text(text) {
|
if let Err(err) = ui_state.clipboard.set_text(text) {
|
||||||
@@ -280,89 +144,37 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextInputResult::Used => {
|
TextInputResult::Used => {
|
||||||
rsc.run_event::<Edited>(sel, (), state);
|
Events::<Edited, _>::run(sel.id(), &mut (), app_state);
|
||||||
}
|
}
|
||||||
TextInputResult::Unused => {}
|
TextInputResult::Unused => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::Ime(ime) => {
|
WindowEvent::Ime(ime) => {
|
||||||
if let Some(sel) = ui_state.focus {
|
if let Some(sel) = &ui_state.focus {
|
||||||
let mut text = sel.edit(rsc);
|
|
||||||
match ime {
|
match ime {
|
||||||
Ime::Enabled | Ime::Disabled => (),
|
Ime::Enabled | Ime::Disabled => (),
|
||||||
Ime::Preedit(content, _pos) => {
|
Ime::Preedit(content, _pos) => {
|
||||||
// TODO: highlight once that's real
|
// 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();
|
ui_state.ime = content.chars().count();
|
||||||
}
|
}
|
||||||
Ime::Commit(content) => {
|
Ime::Commit(content) => {
|
||||||
text.insert(content);
|
sel.get_mut().insert(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
state.window_event(event, rsc, render);
|
app_state.window_event(event, ui, ui_state);
|
||||||
self.request_redraw_if_needed();
|
if ui.update() {
|
||||||
self.state.default_state_mut().input.end_frame();
|
ui_state.renderer.window().request_redraw();
|
||||||
|
}
|
||||||
|
ui_state.input.end_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit(&mut self) {
|
fn exit(&mut self) {
|
||||||
self.state.exit(&mut self.rsc, &mut self.render);
|
self.app_state.exit(&mut self.ui, &self.ui_state);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+29
-28
@@ -1,7 +1,10 @@
|
|||||||
use iris_core::{UiData, UiRenderNode, UiRenderState};
|
use crate::{
|
||||||
|
layout::Ui,
|
||||||
|
render::{UiLimits, UiRenderNode},
|
||||||
|
};
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu::*;
|
use wgpu::{util::StagingBelt, *};
|
||||||
use winit::{dpi::PhysicalSize, window::Window};
|
use winit::{dpi::PhysicalSize, window::Window};
|
||||||
|
|
||||||
pub const CLEAR_COLOR: Color = Color::BLACK;
|
pub const CLEAR_COLOR: Color = Color::BLACK;
|
||||||
@@ -13,29 +16,17 @@ pub struct UiRenderer {
|
|||||||
queue: Queue,
|
queue: Queue,
|
||||||
config: SurfaceConfiguration,
|
config: SurfaceConfiguration,
|
||||||
encoder: CommandEncoder,
|
encoder: CommandEncoder,
|
||||||
|
staging_belt: StagingBelt,
|
||||||
pub ui: UiRenderNode,
|
pub ui: UiRenderNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiRenderer {
|
impl UiRenderer {
|
||||||
pub fn update(&mut self, ui: &mut UiData, render: &mut UiRenderState) {
|
pub fn update(&mut self, updates: &mut Ui) {
|
||||||
self.ui.update(&self.device, &self.queue, ui, render);
|
self.ui.update(&self.device, &self.queue, updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
pub fn draw(&mut self) {
|
||||||
let output = match self.surface.get_current_texture() {
|
let output = self.surface.get_current_texture().unwrap();
|
||||||
CurrentSurfaceTexture::Success(texture) => texture,
|
|
||||||
CurrentSurfaceTexture::Suboptimal(texture) => {
|
|
||||||
self.surface.configure(&self.device, &self.config);
|
|
||||||
texture
|
|
||||||
}
|
|
||||||
CurrentSurfaceTexture::Outdated | CurrentSurfaceTexture::Lost => {
|
|
||||||
self.surface.configure(&self.device, &self.config);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CurrentSurfaceTexture::Timeout
|
|
||||||
| CurrentSurfaceTexture::Occluded
|
|
||||||
| CurrentSurfaceTexture::Validation => return,
|
|
||||||
};
|
|
||||||
let view = output
|
let view = output
|
||||||
.texture
|
.texture
|
||||||
.create_view(&TextureViewDescriptor::default());
|
.create_view(&TextureViewDescriptor::default());
|
||||||
@@ -58,15 +49,16 @@ impl UiRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
self.window.pre_present_notify();
|
self.staging_belt.finish();
|
||||||
self.queue.present(output);
|
output.present();
|
||||||
|
self.staging_belt.recall();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
||||||
self.config.width = size.width;
|
self.config.width = size.width;
|
||||||
self.config.height = size.height;
|
self.config.height = size.height;
|
||||||
self.surface.configure(&self.device, &self.config);
|
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 {
|
fn create_encoder(device: &Device) -> CommandEncoder {
|
||||||
@@ -78,10 +70,10 @@ impl UiRenderer {
|
|||||||
pub fn new(window: Arc<Window>) -> Self {
|
pub fn new(window: Arc<Window>) -> Self {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
|
|
||||||
let instance = Instance::new(InstanceDescriptor {
|
let instance = Instance::new(&InstanceDescriptor {
|
||||||
backends: Backends::PRIMARY,
|
backends: Backends::PRIMARY,
|
||||||
display: Some(Box::new(window.clone())),
|
flags: InstanceFlags::empty(),
|
||||||
..InstanceDescriptor::new_without_display_handle()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let surface = instance
|
let surface = instance
|
||||||
@@ -93,14 +85,22 @@ impl UiRenderer {
|
|||||||
power_preference: PowerPreference::default(),
|
power_preference: PowerPreference::default(),
|
||||||
compatible_surface: Some(&surface),
|
compatible_surface: Some(&surface),
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
apply_limit_buckets: false,
|
|
||||||
})
|
})
|
||||||
.block_on()
|
.block_on()
|
||||||
.expect("Could not get adapter!");
|
.expect("Could not get adapter!");
|
||||||
|
|
||||||
|
let ui_limits = UiLimits::default();
|
||||||
|
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(&DeviceDescriptor {
|
.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 {
|
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,
|
max_buffer_size: 1 << 30,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -120,7 +120,6 @@ impl UiRenderer {
|
|||||||
let config = SurfaceConfiguration {
|
let config = SurfaceConfiguration {
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: surface_format,
|
format: surface_format,
|
||||||
color_space: SurfaceColorSpace::Auto,
|
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
present_mode: PresentMode::AutoVsync,
|
present_mode: PresentMode::AutoVsync,
|
||||||
@@ -131,9 +130,10 @@ impl UiRenderer {
|
|||||||
|
|
||||||
surface.configure(&device, &config);
|
surface.configure(&device, &config);
|
||||||
|
|
||||||
|
let staging_belt = StagingBelt::new(4096 * 4);
|
||||||
let encoder = Self::create_encoder(&device);
|
let encoder = Self::create_encoder(&device);
|
||||||
|
|
||||||
let ui = UiRenderNode::new(&device, &config);
|
let shape_pipeline = UiRenderNode::new(&device, &queue, &config, ui_limits);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
surface,
|
surface,
|
||||||
@@ -141,7 +141,8 @@ impl UiRenderer {
|
|||||||
queue,
|
queue,
|
||||||
config,
|
config,
|
||||||
encoder,
|
encoder,
|
||||||
ui,
|
staging_belt,
|
||||||
|
ui: shape_pipeline,
|
||||||
window,
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
use iris_core::HasState;
|
|
||||||
use std::{pin::Pin, sync::Arc};
|
|
||||||
use tokio::{
|
|
||||||
runtime::Runtime,
|
|
||||||
sync::mpsc::{
|
|
||||||
UnboundedReceiver as AsyncReceiver, UnboundedSender as AsyncSender,
|
|
||||||
unbounded_channel as async_channel,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait TaskUpdate<Rsc: HasState>: FnOnce(&mut Rsc::State, &mut Rsc) + Send {}
|
|
||||||
impl<F: FnOnce(&mut Rsc::State, &mut Rsc) + Send, Rsc: HasState> TaskUpdate<Rsc> for F {}
|
|
||||||
|
|
||||||
/// Hands an update from a task to the thread that owns the ui. Delivery and
|
|
||||||
/// waking are one act: a host posts the update as a message its loop already
|
|
||||||
/// carries, so nothing has to wake the loop separately, or claim a redraw to
|
|
||||||
/// be looked at.
|
|
||||||
pub trait TaskQueue<Rsc: HasState>: Send + Sync + 'static {
|
|
||||||
fn send(&self, update: Box<dyn TaskUpdate<Rsc>>);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Tasks<Rsc: HasState> {
|
|
||||||
start: AsyncSender<BoxTask>,
|
|
||||||
queue: Arc<dyn TaskQueue<Rsc>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TaskCtx<Rsc: HasState> {
|
|
||||||
queue: Arc<dyn TaskQueue<Rsc>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Rsc: HasState> TaskCtx<Rsc> {
|
|
||||||
pub fn update(&mut self, f: impl TaskUpdate<Rsc> + 'static) {
|
|
||||||
self.queue.send(Box::new(f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoxTask = Pin<Box<dyn Future<Output = ()> + Send>>;
|
|
||||||
|
|
||||||
impl<Rsc: HasState> Tasks<Rsc> {
|
|
||||||
pub fn init(queue: Arc<dyn TaskQueue<Rsc>>) -> Self {
|
|
||||||
let (start, start_recv) = async_channel();
|
|
||||||
std::thread::spawn(|| {
|
|
||||||
let rt = Runtime::new().unwrap();
|
|
||||||
rt.block_on(listen(start_recv))
|
|
||||||
});
|
|
||||||
Self { start, queue }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn<F: AsyncFnOnce(TaskCtx<Rsc>) + 'static + std::marker::Send>(&mut self, task: F)
|
|
||||||
where
|
|
||||||
F::CallOnceFuture: Send,
|
|
||||||
{
|
|
||||||
let queue = self.queue.clone();
|
|
||||||
let _ = self.start.send(Box::pin(async move {
|
|
||||||
task(TaskCtx { queue }).await;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn listen(mut recv: AsyncReceiver<BoxTask>) {
|
|
||||||
while let Some(task) = recv.recv().await {
|
|
||||||
tokio::spawn(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+53
-74
@@ -1,87 +1,66 @@
|
|||||||
use iris_core::*;
|
use crate::prelude::*;
|
||||||
use iris_macro::*;
|
use iris_macro::widget_trait;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::default::{TaskCtx, TaskUpdate, Tasks};
|
pub mod eventable {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
pub trait Eventable<Rsc: HasEvents, Tag>: WidgetLike<Rsc, Tag> {
|
widget_trait! {
|
||||||
fn on<E: EventLike>(
|
pub trait Eventable;
|
||||||
|
fn on<E: EventAlias, Ctx: 'static>(
|
||||||
self,
|
self,
|
||||||
event: E,
|
event: E,
|
||||||
f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, Self::Widget>,
|
f: impl for<'a> EventIdFn<Ctx, <E::Event as Event>::Data<'a>, WL::Widget>,
|
||||||
) -> impl WidgetIdFn<Rsc, Self::Widget> {
|
) -> impl WidgetIdFn<WL::Widget> {
|
||||||
move |rsc| {
|
move |ui| {
|
||||||
let id = self.add(rsc);
|
let id = self.add(ui);
|
||||||
rsc.register_event(id, event.into_event(), move |ctx, rsc| {
|
Events::<E::Event, Ctx>::register(id.weak(), event.into_event(), f);
|
||||||
f(
|
id
|
||||||
EventIdCtx {
|
}
|
||||||
widget: id,
|
}
|
||||||
state: ctx.state,
|
}
|
||||||
data: ctx.data,
|
}
|
||||||
},
|
|
||||||
rsc,
|
// TODO: naming in here is a bit weird like eventable
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! event_ctx {
|
||||||
|
($ty: ty) => {
|
||||||
|
mod local_event_trait {
|
||||||
|
use super::*;
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use $crate::prelude::*;
|
||||||
|
|
||||||
|
widget_trait! {
|
||||||
|
#[allow(unused)]
|
||||||
|
pub trait EventableCtx;
|
||||||
|
fn on<E: EventAlias>(
|
||||||
|
self,
|
||||||
|
event: E,
|
||||||
|
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, WL::Widget>,
|
||||||
|
) -> impl WidgetIdFn<WL::Widget> {
|
||||||
|
eventable::Eventable::on(self, event, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use std::marker::Sized;
|
||||||
|
#[allow(unused)]
|
||||||
|
pub trait EventableCtxRef<W: Widget + ?Sized> {
|
||||||
|
fn on<E: EventAlias>(
|
||||||
|
self,
|
||||||
|
event: E,
|
||||||
|
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, W>,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<WL: WidgetLike<Rsc, Tag>, Rsc: HasEvents, Tag> Eventable<Rsc, Tag> for WL {}
|
|
||||||
|
|
||||||
widget_trait! {
|
impl<W: Widget + ?Sized> EventableCtxRef<W> for WidgetRef<W> {
|
||||||
pub trait TaskEventable<Rsc: HasEvents + HasTasks>;
|
fn on<E: EventAlias>(
|
||||||
fn task_on<'a, E: EventLike, F: AsyncWidgetEventFn<Rsc, WL::Widget>>(
|
|
||||||
self,
|
self,
|
||||||
event: E,
|
event: E,
|
||||||
f: F,
|
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, W>,
|
||||||
) -> impl WidgetIdFn<Rsc, WL::Widget>
|
) {
|
||||||
where <E::Event as Event>::Data<'a>: Send,
|
Events::<E::Event, $ty>::register(self, event.into_event(), f);
|
||||||
for<'b> F::CallRefFuture<'b>: Send,
|
|
||||||
{
|
|
||||||
let f = Arc::new(f);
|
|
||||||
move |rsc| {
|
|
||||||
let id = self.add(rsc);
|
|
||||||
rsc.register_event(id, event.into_event(), move |_, rsc| {
|
|
||||||
let f = f.clone();
|
|
||||||
rsc.tasks_mut().spawn(async move |task| {
|
|
||||||
f(AsyncEventIdCtx {
|
|
||||||
widget: id,
|
|
||||||
task,
|
|
||||||
}).await;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasTasks: Sized + HasState + HasEvents {
|
|
||||||
fn tasks_mut(&mut self) -> &mut Tasks<Self>;
|
|
||||||
|
|
||||||
fn spawn_task<F: AsyncFnOnce(TaskCtx<Self>) + 'static + std::marker::Send>(&mut self, task: F)
|
|
||||||
where
|
|
||||||
F::CallOnceFuture: Send,
|
|
||||||
{
|
|
||||||
self.tasks_mut().spawn(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AsyncWidgetEventFn<Rsc: HasEvents, W: ?Sized>:
|
|
||||||
AsyncFn(AsyncEventIdCtx<Rsc, W>) + Send + Sync + 'static
|
|
||||||
{
|
|
||||||
}
|
|
||||||
impl<Rsc: HasEvents, F: AsyncFn(AsyncEventIdCtx<Rsc, W>) + Send + Sync + 'static, W: ?Sized>
|
|
||||||
AsyncWidgetEventFn<Rsc, W> for F
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AsyncEventIdCtx<Rsc: HasEvents, W: ?Sized> {
|
|
||||||
pub widget: WeakWidget<W>,
|
|
||||||
task: TaskCtx<Rsc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Rsc: HasEvents, W: ?Sized> AsyncEventIdCtx<Rsc, W> {
|
|
||||||
pub fn update(&mut self, f: impl TaskUpdate<Rsc> + 'static) {
|
|
||||||
self.task.update(f);
|
|
||||||
}
|
}
|
||||||
|
use local_event_trait::*;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
pub use event_ctx;
|
||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
//! A ui with no window: build a tree, run frames, move a pointer, and read
|
|
||||||
//! back where widgets landed.
|
|
||||||
//!
|
|
||||||
//! It does not draw. A claim about pixels still needs a real surface.
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use std::{
|
|
||||||
sync::{
|
|
||||||
Arc,
|
|
||||||
mpsc::{Receiver, SyncSender, sync_channel},
|
|
||||||
},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// There is no loop here to post to, so updates queue until the test asks
|
|
||||||
/// for them.
|
|
||||||
struct Queue(SyncSender<Box<dyn TaskUpdate<DefaultRsc<HarnessState>>>>);
|
|
||||||
|
|
||||||
impl TaskQueue<DefaultRsc<HarnessState>> for Queue {
|
|
||||||
fn send(&self, update: Box<dyn TaskUpdate<DefaultRsc<HarnessState>>>) {
|
|
||||||
let _ = self.0.send(update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `assert_eq!` for where a frame put a widget, written as its two corners.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! assert_corners {
|
|
||||||
($harness:expr, $id:expr, ($x0:expr, $y0:expr), ($x1:expr, $y1:expr)) => {
|
|
||||||
assert_eq!(
|
|
||||||
$harness.region(&$id).expect("widget drew nothing"),
|
|
||||||
$crate::core::PixelRegion {
|
|
||||||
top_left: $crate::core::util::Vec2::new($x0 as f32, $y0 as f32),
|
|
||||||
bot_right: $crate::core::util::Vec2::new($x1 as f32, $y1 as f32),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub use crate::assert_corners;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct HarnessState {
|
|
||||||
pub root: Option<StrongWidget>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasRoot for HarnessState {
|
|
||||||
fn set_root(&mut self, root: StrongWidget) {
|
|
||||||
self.root = Some(root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Harness {
|
|
||||||
pub rsc: DefaultRsc<HarnessState>,
|
|
||||||
pub render: UiRenderState,
|
|
||||||
pub state: HarnessState,
|
|
||||||
updates: Receiver<Box<dyn TaskUpdate<DefaultRsc<HarnessState>>>>,
|
|
||||||
cursor: CursorState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Harness {
|
|
||||||
/// `size` is the output in physical pixels.
|
|
||||||
pub fn new(size: impl Into<Vec2>) -> Self {
|
|
||||||
// A `TaskQueue` must be `Sync`, which `mpsc::Sender` is not; the
|
|
||||||
// bound that comes with `SyncSender` is far past anything a test
|
|
||||||
// leaves unread.
|
|
||||||
let (send, updates) = sync_channel(1024);
|
|
||||||
let rsc = DefaultRsc::init(Arc::new(Queue(send)));
|
|
||||||
let mut render = UiRenderState::new();
|
|
||||||
render.resize(size);
|
|
||||||
Self {
|
|
||||||
rsc,
|
|
||||||
render,
|
|
||||||
state: HarnessState::default(),
|
|
||||||
updates,
|
|
||||||
cursor: CursorState::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> Vec2 {
|
|
||||||
self.render.output_size()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
|
||||||
self.render.resize(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the root and lays it out, so a pointer event has something to hit.
|
|
||||||
pub fn set_root<T>(&mut self, widget: impl WidgetLike<DefaultRsc<HarnessState>, T>) {
|
|
||||||
widget.set_root(&mut self.rsc, &mut self.state);
|
|
||||||
self.frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn needs_redraw(&self) -> bool {
|
|
||||||
self.render
|
|
||||||
.needs_redraw(&self.state.root, self.rsc.widgets())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_updates(&mut self) -> usize {
|
|
||||||
let mut applied = 0;
|
|
||||||
while let Ok(update) = self.updates.try_recv() {
|
|
||||||
update(&mut self.state, &mut self.rsc);
|
|
||||||
applied += 1;
|
|
||||||
}
|
|
||||||
applied
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Waits for a task's first update, then applies everything waiting.
|
|
||||||
/// False if none arrived in time.
|
|
||||||
#[must_use]
|
|
||||||
pub fn await_update(&mut self, timeout: Duration) -> bool {
|
|
||||||
let Ok(update) = self.updates.recv_timeout(timeout) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
update(&mut self.state, &mut self.rsc);
|
|
||||||
self.apply_updates();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lays the tree out and builds its primitives.
|
|
||||||
pub fn frame(&mut self) {
|
|
||||||
self.apply_updates();
|
|
||||||
self.render.update(&self.state.root, &mut self.rsc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where the last frame put a widget, or `None` if it drew nothing.
|
|
||||||
pub fn region(&self, id: &impl IdLike) -> Option<PixelRegion> {
|
|
||||||
self.render.window_region(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_to(&mut self, pos: impl Into<Vec2>) {
|
|
||||||
self.cursor.pos = pos.into();
|
|
||||||
self.cursor.exists = true;
|
|
||||||
self.sense();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn leave(&mut self) {
|
|
||||||
self.cursor.exists = false;
|
|
||||||
self.sense();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn press(&mut self, button: CursorButton) {
|
|
||||||
self.button(button).update(true);
|
|
||||||
self.sense();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release(&mut self, button: CursorButton) {
|
|
||||||
self.button(button).update(false);
|
|
||||||
self.sense();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wheel carries no position, so this goes wherever the cursor was last
|
|
||||||
/// moved to -- nowhere, until it has been moved.
|
|
||||||
pub fn scroll(&mut self, delta: impl Into<Vec2>) {
|
|
||||||
self.cursor.scroll_delta = delta.into();
|
|
||||||
self.sense();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn click(&mut self, pos: impl Into<Vec2>) {
|
|
||||||
self.move_to(pos);
|
|
||||||
self.press(CursorButton::Left);
|
|
||||||
self.release(CursorButton::Left);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn button(&mut self, button: CursorButton) -> &mut ActivationState {
|
|
||||||
let buttons = &mut self.cursor.buttons;
|
|
||||||
match button {
|
|
||||||
CursorButton::Left => &mut buttons.left,
|
|
||||||
CursorButton::Middle => &mut buttons.middle,
|
|
||||||
CursorButton::Right => &mut buttons.right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dispatches against the layout of the last frame, which is what a
|
|
||||||
/// window delivers input against too.
|
|
||||||
fn sense(&mut self) {
|
|
||||||
let cursor = self.cursor.clone();
|
|
||||||
let size = self.render.output_size();
|
|
||||||
self.render
|
|
||||||
.run_sensors(&mut self.rsc, &mut self.state, cursor, size);
|
|
||||||
self.cursor.end_frame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+14
-17
@@ -1,26 +1,23 @@
|
|||||||
#![feature(unboxed_closures)]
|
#![feature(unboxed_closures)]
|
||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
|
#![feature(gen_blocks)]
|
||||||
#![feature(associated_type_defaults)]
|
#![feature(associated_type_defaults)]
|
||||||
#![feature(unsize)]
|
|
||||||
#![feature(option_into_flat_iter)]
|
|
||||||
#![feature(async_fn_traits)]
|
|
||||||
|
|
||||||
pub mod default;
|
mod default;
|
||||||
pub mod event;
|
mod event;
|
||||||
pub mod harness;
|
mod widget;
|
||||||
pub mod widget;
|
|
||||||
|
|
||||||
pub use iris_core as core;
|
pub use iris_core::*;
|
||||||
pub use iris_macro as macros;
|
pub use iris_macro::*;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
use super::*;
|
pub use super::default::*;
|
||||||
pub use default::*;
|
pub use super::event::*;
|
||||||
pub use event::*;
|
pub use super::widget::*;
|
||||||
pub use iris_core::*;
|
|
||||||
pub use iris_macro::*;
|
|
||||||
pub use widget::*;
|
|
||||||
|
|
||||||
pub use iris_core::util::Vec2;
|
pub use iris_core::layout::*;
|
||||||
pub use len_fns::*;
|
pub use iris_core::render::*;
|
||||||
|
pub use iris_core::util::Handle;
|
||||||
|
|
||||||
|
pub use iris_macro::*;
|
||||||
}
|
}
|
||||||
Whitespace-only changes.
+5
-5
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
handle: TextureHandle,
|
handle: TextureHandle,
|
||||||
@@ -7,7 +7,7 @@ pub struct Image {
|
|||||||
|
|
||||||
impl Widget for Image {
|
impl Widget for Image {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.primitive(&self.handle);
|
painter.texture(&self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
@@ -19,10 +19,10 @@ impl Widget for Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
pub fn image(image: impl LoadableImage) -> impl WidgetFn<Image> {
|
||||||
let image = image.get_image().expect("Failed to load image");
|
let image = image.get_image().expect("Failed to load image");
|
||||||
move |state| Image {
|
move |ui| Image {
|
||||||
handle: state.ui_mut().textures.add(image),
|
handle: ui.add_texture(image),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Masked {
|
pub struct Masked {
|
||||||
pub inner: StrongWidget,
|
pub inner: WidgetHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Masked {
|
impl Widget for Masked {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod mask;
|
|||||||
mod position;
|
mod position;
|
||||||
mod ptr;
|
mod ptr;
|
||||||
mod rect;
|
mod rect;
|
||||||
|
mod sense;
|
||||||
mod text;
|
mod text;
|
||||||
mod trait_fns;
|
mod trait_fns;
|
||||||
|
|
||||||
@@ -11,5 +12,6 @@ pub use mask::*;
|
|||||||
pub use position::*;
|
pub use position::*;
|
||||||
pub use ptr::*;
|
pub use ptr::*;
|
||||||
pub use rect::*;
|
pub use rect::*;
|
||||||
|
pub use sense::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
pub use trait_fns::*;
|
pub use trait_fns::*;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Aligned {
|
pub struct Aligned {
|
||||||
pub inner: StrongWidget,
|
pub inner: WidgetHandle,
|
||||||
pub align: Align,
|
pub align: Align,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct LayerOffset {
|
pub struct LayerOffset {
|
||||||
pub inner: StrongWidget,
|
pub inner: WidgetHandle,
|
||||||
pub offset: usize,
|
pub offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct MaxSize {
|
pub struct MaxSize {
|
||||||
pub inner: StrongWidget,
|
pub inner: WidgetHandle,
|
||||||
pub x: Option<Len>,
|
pub x: Option<Len>,
|
||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod max_size;
|
|||||||
mod offset;
|
mod offset;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod set_size;
|
mod sized;
|
||||||
mod span;
|
mod span;
|
||||||
mod stack;
|
mod stack;
|
||||||
|
|
||||||
@@ -14,6 +14,6 @@ pub use max_size::*;
|
|||||||
pub use offset::*;
|
pub use offset::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use scroll::*;
|
pub use scroll::*;
|
||||||
pub use set_size::*;
|
pub use sized::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user