Compare commits
77 Commits
c7b255be4f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 54534c4c34 | |||
| f0fe671bd8 | |||
| 1ccf947220 | |||
| c00ded78c0 | |||
| 32ca4ec5a6 | |||
| fabc7d0b90 | |||
| bae17235c6 | |||
| 30bc55c78e | |||
| 7e6369029f | |||
| 70ac0fbcb2 | |||
| 1363f31fcd | |||
| ecbb9e56e2 | |||
| ac9571b29f | |||
| 71f3beaf94 | |||
| 2183fbd3cb | |||
| 486ed0ffd7 | |||
| 8d1a810483 | |||
| 0b8a93c5ce | |||
| dc2be7f688 | |||
| 9d8ca8fa72 | |||
| a2a32b4322 | |||
| 37b1987aa8 | |||
| a708813ce7 | |||
| a70d09e162 | |||
| 966b6a2ac2 | |||
| 2dad409300 | |||
| 36668c82f4 | |||
| a85e129026 | |||
| 174c447706 | |||
| 2dc5b0f62c | |||
| 38266debb6 | |||
| 62aa02847a | |||
| f6b1143665 | |||
| 28d17c49c6 | |||
| 23ae5b246e | |||
| db888416b6 | |||
| f7b100e00c | |||
| e5d0a7e592 | |||
| 84c460a91f | |||
| d6a9711ceb | |||
| ee0616885f | |||
| 14a9da0553 | |||
| 8e08f67627 | |||
| 84b3bf9078 | |||
| bf3ade840b | |||
| 2aa5719166 | |||
| 90c579d734 | |||
| d757e805e8 | |||
| 9deba3d9d7 | |||
| c24c517c60 | |||
| fc89826794 | |||
| 140be50baa | |||
| 1c6fc99f57 | |||
| 1cec56e847 | |||
| 246caffb34 | |||
| 31ff17c21a | |||
| 23c5abe5a9 | |||
| 97b284e81e | |||
| c428de8fd5 | |||
| 5785352ac0 | |||
| 172e7157be | |||
| e3b1ddc993 | |||
| 5aef8c2201 | |||
| acd67179b7 | |||
| dff72d2c43 | |||
| f6f9ebbe51 | |||
| 6251c23d37 | |||
| 96ef0c529b | |||
| a952b34a72 | |||
| db248de8f4 | |||
| 9febd03067 | |||
| 38d7ca3090 | |||
| 126c442706 | |||
| 6b7719539e | |||
| bc829397c8 | |||
| 4981bd739a | |||
| 7e257fd042 |
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -1042,16 +1042,37 @@ name = "iris"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"bytemuck",
|
||||
"cosmic-text",
|
||||
"fxhash",
|
||||
"image",
|
||||
"iris-core",
|
||||
"iris-macro",
|
||||
"pollster",
|
||||
"unicode-segmentation",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iris-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cosmic-text",
|
||||
"fxhash",
|
||||
"image",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iris-macro"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@@ -2401,9 +2422,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.110"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
28
Cargo.toml
28
Cargo.toml
@@ -1,11 +1,30 @@
|
||||
[package]
|
||||
name = "iris"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
default-run = "test"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
iris-core = { workspace = true }
|
||||
iris-macro = { workspace = true }
|
||||
cosmic-text = { workspace = true }
|
||||
unicode-segmentation = { workspace = true }
|
||||
winit = { workspace = true }
|
||||
arboard = { workspace = true, features = ["wayland-data-control"] }
|
||||
pollster = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
image = { workspace = true }
|
||||
|
||||
[workspace]
|
||||
members = ["core", "macro"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
pollster = "0.4.0"
|
||||
winit = "0.30.12"
|
||||
wgpu = "27.0.1"
|
||||
@@ -14,5 +33,6 @@ image = "0.25.6"
|
||||
cosmic-text = "0.15.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
fxhash = "0.2.1"
|
||||
arboard = { version = "3.6.1", features = ["wayland-data-control"] }
|
||||
|
||||
arboard = "3.6.1"
|
||||
iris-core = { path = "core" }
|
||||
iris-macro = { path = "macro" }
|
||||
|
||||
8
TODO
8
TODO
@@ -4,8 +4,7 @@ images
|
||||
text
|
||||
figure out ways to speed up / what costs the most
|
||||
resizing (per frame) is really slow (assuming painter isn't griefing)
|
||||
bug where x offset doesn't shift texture correctly (eg typing a vs j)
|
||||
bug (?) where if you spam a key it slows down at a certain point, seems to be line based
|
||||
j is weird / fix x offset
|
||||
|
||||
masks r just made to bare minimum work
|
||||
|
||||
@@ -31,6 +30,11 @@ really weird limitation:
|
||||
but the child gets drawn during that, so it will think the child is still active !!!
|
||||
or something like that idk, maybe I need a special enum for parent that includes a undecided state where it may or may not get redrawn by the parent
|
||||
or just do ref counting and ensure all drawn things == 1 afterwards (seems like best way)
|
||||
ok so I'm removing the limit for now
|
||||
|
||||
don't forget I'm streaming
|
||||
|
||||
tags
|
||||
vecs for each widget type?
|
||||
|
||||
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||
|
||||
12
core/Cargo.toml
Normal file
12
core/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "iris-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
winit = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
bytemuck ={ workspace = true }
|
||||
image = { workspace = true }
|
||||
cosmic-text = { workspace = true }
|
||||
fxhash = { workspace = true }
|
||||
25
core/src/attr.rs
Normal file
25
core/src/attr.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::{HasUi, StateLike, WidgetIdFn, WidgetLike, WidgetRef};
|
||||
|
||||
pub trait WidgetAttr<State, W: ?Sized> {
|
||||
type Input;
|
||||
fn run(state: &mut State, id: WidgetRef<W>, input: Self::Input);
|
||||
}
|
||||
|
||||
pub trait Attrable<State, W: ?Sized, Tag> {
|
||||
fn attr<A: WidgetAttr<State, W>>(self, input: A::Input) -> impl WidgetIdFn<State, W>;
|
||||
}
|
||||
|
||||
impl<State: HasUi + StateLike<State>, WL: WidgetLike<State, Tag>, Tag>
|
||||
Attrable<State, WL::Widget, Tag> for WL
|
||||
{
|
||||
fn attr<A: WidgetAttr<State, WL::Widget>>(
|
||||
self,
|
||||
input: A::Input,
|
||||
) -> impl WidgetIdFn<State, WL::Widget> {
|
||||
|state| {
|
||||
let id = self.add(state);
|
||||
A::run(state, id, input);
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
116
core/src/event/ctx.rs
Normal file
116
core/src/event/ctx.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use crate::{HasEvents, HasState, HasUi, StateLike, Ui, Widget, WidgetRef};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub struct EventCtx<'a, State, Data> {
|
||||
pub state: &'a mut State,
|
||||
pub data: &'a mut Data,
|
||||
}
|
||||
|
||||
pub struct EventIdCtx<'a, State, Data, W: ?Sized> {
|
||||
pub widget: WidgetRef<W>,
|
||||
pub state: &'a mut State,
|
||||
pub data: &'a mut Data,
|
||||
}
|
||||
|
||||
impl<State: HasUi, Data, W: ?Sized> Deref for EventIdCtx<'_, State, Data, W> {
|
||||
type Target = State;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: HasUi, Data, W: ?Sized> DerefMut for EventIdCtx<'_, State, Data, W> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: HasUi, Data, W: Widget> EventIdCtx<'_, State, Data, W> {
|
||||
pub fn widget(&mut self) -> &mut W {
|
||||
&mut self.state.get_mut()[self.widget]
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: HasUi, Data, W: Widget> HasUi for EventIdCtx<'_, State, Data, W> {
|
||||
fn get(&self) -> &Ui {
|
||||
self.state.ui()
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut Ui {
|
||||
self.state.ui_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: HasUi, Data, W: Widget> HasState for EventIdCtx<'_, State, Data, W> {
|
||||
type State = State;
|
||||
}
|
||||
|
||||
impl<State: HasEvents<State = State>, Data, W: Widget> HasEvents
|
||||
for EventIdCtx<'_, State, Data, W>
|
||||
{
|
||||
fn get(&self) -> &super::EventManager<Self::State> {
|
||||
self.state.events()
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut super::EventManager<Self::State> {
|
||||
self.state.events_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<State, Data, W: Widget> StateLike<State> for EventIdCtx<'_, State, Data, W> {
|
||||
fn as_state(&mut self) -> &mut State {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
// fn test() {
|
||||
// use crate::*;
|
||||
// struct ClientRsc;
|
||||
// impl<State, Data> HasUi for EventCtx<'_, State, Data> {
|
||||
// fn get(&self) -> &Ui {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn get_mut(&mut self) -> &mut Ui {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
// fn on(_: impl for<'a> EventFn<ClientRsc, &'a mut i32>) {}
|
||||
//
|
||||
// pub trait WidgetLike<State: HasUi, Tag>: Sized {
|
||||
// type Widget: Widget + ?Sized + std::marker::Unsize<dyn Widget>;
|
||||
//
|
||||
// fn add(self, state: &mut State) -> WidgetHandle<Self::Widget>;
|
||||
//
|
||||
// fn with_id<W2>(
|
||||
// self,
|
||||
// f: impl FnOnce(&mut State, WidgetHandle<Self::Widget>) -> WidgetHandle<W2>,
|
||||
// ) -> impl WidgetIdFn<State, W2> {
|
||||
// move |state| {
|
||||
// let id = self.add(state);
|
||||
// f(state, id)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn set_root(self, state: &mut State) {
|
||||
// state.get_mut().root = Some(self.add(state));
|
||||
// }
|
||||
//
|
||||
// fn handles(self, state: &mut State) -> WidgetHandles<Self::Widget> {
|
||||
// self.add(state).handles()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pub struct WidgetTag;
|
||||
// impl<State: HasUi, W: Widget> WidgetLike<State, WidgetTag> for W {
|
||||
// type Widget = W;
|
||||
// fn add(self, state: &mut State) -> WidgetHandle<W> {
|
||||
// state.get_mut().add_widget(self)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// on(move |ctx| {
|
||||
// ().add(ctx);
|
||||
// });
|
||||
// }
|
||||
145
core/src/event/manager.rs
Normal file
145
core/src/event/manager.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use crate::{
|
||||
ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, IdLike, LayerId, Widget,
|
||||
WidgetData, WidgetEventFn, WidgetId, WidgetRef,
|
||||
util::{HashMap, TypeMap},
|
||||
};
|
||||
use std::{any::TypeId, rc::Rc};
|
||||
|
||||
pub struct EventManager<State> {
|
||||
types: TypeMap<dyn EventManagerLike<State>>,
|
||||
}
|
||||
|
||||
impl<State> Default for EventManager<State> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
types: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: 'static> EventManager<State> {
|
||||
pub fn get_type<E: EventLike>(&mut self) -> &mut TypeEventManager<State, E::Event> {
|
||||
self.types.type_or_default()
|
||||
}
|
||||
|
||||
pub fn register<W: Widget + ?Sized, E: EventLike>(
|
||||
&mut self,
|
||||
id: WidgetRef<W>,
|
||||
event: E,
|
||||
f: impl for<'a> WidgetEventFn<State, <E::Event as Event>::Data<'a>, W>,
|
||||
) {
|
||||
self.get_type::<E>().register(id, event, f);
|
||||
}
|
||||
|
||||
pub fn type_key<E: EventLike>() -> TypeId {
|
||||
TypeId::of::<TypeEventManager<State, E::Event>>()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventsLike {
|
||||
fn remove(&mut self, id: WidgetId);
|
||||
fn draw(&mut self, data: &WidgetData, active: &ActiveData);
|
||||
fn undraw(&mut self, data: &WidgetData, active: &ActiveData);
|
||||
}
|
||||
|
||||
impl<State: 'static> EventsLike for EventManager<State> {
|
||||
fn remove(&mut self, id: WidgetId) {
|
||||
for m in self.types.values_mut() {
|
||||
m.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, data: &WidgetData, active: &ActiveData) {
|
||||
for t in &data.event_mgrs {
|
||||
self.types.get_mut(t).unwrap().draw(active);
|
||||
}
|
||||
}
|
||||
|
||||
fn undraw(&mut self, data: &WidgetData, active: &ActiveData) {
|
||||
for t in &data.event_mgrs {
|
||||
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<State, E> = (E, Rc<dyn for<'a> EventFn<State, <E as Event>::Data<'a>>>);
|
||||
pub struct TypeEventManager<State, E: Event> {
|
||||
// TODO: reduce visiblity!!
|
||||
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
|
||||
map: HashMap<WidgetId, Vec<EventData<State, E>>>,
|
||||
}
|
||||
|
||||
impl<State, E: Event> EventManagerLike<State> for TypeEventManager<State, 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<State, E: Event> Default for TypeEventManager<State, E> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: Default::default(),
|
||||
map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: 'static, E: Event> TypeEventManager<State, E> {
|
||||
fn register<W: Widget + ?Sized>(
|
||||
&mut self,
|
||||
widget: WidgetRef<W>,
|
||||
event: impl EventLike<Event = E>,
|
||||
f: impl for<'a> WidgetEventFn<State, E::Data<'a>, W>,
|
||||
) {
|
||||
let event = event.into_event();
|
||||
self.map.entry(widget.id()).or_default().push((
|
||||
event,
|
||||
Rc::new(move |ctx| {
|
||||
let mut test = EventIdCtx {
|
||||
widget,
|
||||
state: ctx.state,
|
||||
data: ctx.data,
|
||||
};
|
||||
f(&mut test);
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn run_fn<'a>(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
) -> impl for<'b> FnOnce(EventCtx<'_, State, E::Data<'b>>) + 'a {
|
||||
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
||||
move |ctx| {
|
||||
for (e, f) in fs {
|
||||
if e.should_run(ctx.data) {
|
||||
f(&mut EventCtx {
|
||||
state: ctx.state,
|
||||
data: ctx.data,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
core/src/event/mod.rs
Normal file
41
core/src/event/mod.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
mod ctx;
|
||||
mod manager;
|
||||
mod rsc;
|
||||
|
||||
pub use ctx::*;
|
||||
pub use manager::*;
|
||||
pub use rsc::*;
|
||||
|
||||
pub trait Event: Sized + 'static + Clone {
|
||||
type Data<'a> = ();
|
||||
type State: Default = ();
|
||||
#[allow(unused_variables)]
|
||||
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
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<State, Data>: Fn(&mut EventCtx<State, Data>) + 'static {}
|
||||
impl<State, F: Fn(&mut EventCtx<State, Data>) + 'static, Data> EventFn<State, Data> for F {}
|
||||
|
||||
pub trait WidgetEventFn<State, Data, W: ?Sized>:
|
||||
Fn(&mut EventIdCtx<State, Data, W>) + 'static
|
||||
{
|
||||
}
|
||||
impl<State, F: Fn(&mut EventIdCtx<State, Data, W>) + 'static, Data, W: ?Sized>
|
||||
WidgetEventFn<State, Data, W> for F
|
||||
{
|
||||
}
|
||||
47
core/src/event/rsc.rs
Normal file
47
core/src/event/rsc.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::{
|
||||
Event, EventCtx, EventLike, EventManager, HasUi, IdLike, Widget, WidgetEventFn, WidgetRef,
|
||||
};
|
||||
|
||||
pub trait HasState {
|
||||
type State: HasUi;
|
||||
}
|
||||
|
||||
pub trait HasEvents: Sized + HasState + HasUi {
|
||||
fn get(&self) -> &EventManager<Self::State>;
|
||||
fn get_mut(&mut self) -> &mut EventManager<Self::State>;
|
||||
fn events(&self) -> &EventManager<Self::State> {
|
||||
HasEvents::get(self)
|
||||
}
|
||||
fn events_mut(&mut self) -> &mut EventManager<Self::State> {
|
||||
HasEvents::get_mut(self)
|
||||
}
|
||||
|
||||
fn register_event<W: Widget + ?Sized, E: EventLike>(
|
||||
&mut self,
|
||||
id: WidgetRef<W>,
|
||||
event: E,
|
||||
f: impl for<'a> WidgetEventFn<Self::State, <E::Event as Event>::Data<'a>, W>,
|
||||
) where
|
||||
Self::State: 'static,
|
||||
{
|
||||
self.events_mut().register(id, event, f);
|
||||
self.ui_mut()
|
||||
.widgets
|
||||
.data_mut(id)
|
||||
.unwrap()
|
||||
.event_mgrs
|
||||
.insert(EventManager::<Self::State>::type_key::<E>());
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RunEvents: HasEvents + HasState<State = Self> + 'static {
|
||||
fn run_event<E: EventLike>(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
data: &mut <E::Event as Event>::Data<'_>,
|
||||
) {
|
||||
let f = self.events_mut().get_type::<E>().run_fn(id);
|
||||
f(EventCtx { state: self, data })
|
||||
}
|
||||
}
|
||||
impl<T: HasEvents + HasState<State = Self> + 'static> RunEvents for T {}
|
||||
35
core/src/lib.rs
Normal file
35
core/src/lib.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
#![feature(macro_metavar_expr_concat)]
|
||||
#![feature(const_ops)]
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(const_convert)]
|
||||
#![feature(map_try_insert)]
|
||||
#![feature(unboxed_closures)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(const_cmp)]
|
||||
#![feature(const_destruct)]
|
||||
#![feature(portable_simd)]
|
||||
#![feature(associated_type_defaults)]
|
||||
#![feature(unsize)]
|
||||
#![feature(coerce_unsized)]
|
||||
|
||||
mod attr;
|
||||
mod event;
|
||||
mod num;
|
||||
mod orientation;
|
||||
mod primitive;
|
||||
mod render;
|
||||
mod ui;
|
||||
mod widget;
|
||||
|
||||
pub mod util;
|
||||
|
||||
pub use attr::*;
|
||||
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>;
|
||||
49
core/src/num.rs
Normal file
49
core/src/num.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::util::Vec2;
|
||||
use std::marker::Destruct;
|
||||
|
||||
pub const trait UiNum {
|
||||
fn to_f32(self) -> f32;
|
||||
}
|
||||
|
||||
impl const UiNum for f32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl const UiNum for u32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl const UiNum for i32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
|
||||
Vec2::new(x.to_f32(), y.to_f32())
|
||||
}
|
||||
|
||||
impl<T: const UiNum + Copy> const From<T> for Vec2 {
|
||||
fn from(v: T) -> Self {
|
||||
Self {
|
||||
x: v.to_f32(),
|
||||
y: v.to_f32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
|
||||
where
|
||||
(T, U): const Destruct,
|
||||
{
|
||||
fn from((x, y): (T, U)) -> Self {
|
||||
Self {
|
||||
x: x.to_f32(),
|
||||
y: y.to_f32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
200
core/src/orientation/align.rs
Normal file
200
core/src/orientation/align.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
use crate::vec2;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Align {
|
||||
pub x: Option<AxisAlign>,
|
||||
pub y: Option<AxisAlign>,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
pub const TOP_LEFT: RegionAlign = RegionAlign::TOP_LEFT;
|
||||
pub const TOP_CENTER: RegionAlign = RegionAlign::TOP_CENTER;
|
||||
pub const TOP_RIGHT: RegionAlign = RegionAlign::TOP_RIGHT;
|
||||
pub const CENTER_LEFT: RegionAlign = RegionAlign::CENTER_LEFT;
|
||||
pub const CENTER: RegionAlign = RegionAlign::CENTER;
|
||||
pub const CENTER_RIGHT: RegionAlign = RegionAlign::CENTER_RIGHT;
|
||||
pub const BOT_LEFT: RegionAlign = RegionAlign::BOT_LEFT;
|
||||
pub const BOT_CENTER: RegionAlign = RegionAlign::BOT_CENTER;
|
||||
pub const BOT_RIGHT: RegionAlign = RegionAlign::BOT_RIGHT;
|
||||
pub const LEFT: CardinalAlign = CardinalAlign::LEFT;
|
||||
pub const H_CENTER: CardinalAlign = CardinalAlign::H_CENTER;
|
||||
pub const RIGHT: CardinalAlign = CardinalAlign::RIGHT;
|
||||
pub const TOP: CardinalAlign = CardinalAlign::TOP;
|
||||
pub const V_CENTER: CardinalAlign = CardinalAlign::V_CENTER;
|
||||
pub const BOT: CardinalAlign = CardinalAlign::BOT;
|
||||
|
||||
pub fn tuple(&self) -> (Option<AxisAlign>, Option<AxisAlign>) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AxisAlign {
|
||||
Neg,
|
||||
Center,
|
||||
Pos,
|
||||
}
|
||||
|
||||
impl AxisAlign {
|
||||
pub const fn rel(&self) -> f32 {
|
||||
match self {
|
||||
Self::Neg => 0.0,
|
||||
Self::Center => 0.5,
|
||||
Self::Pos => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CardinalAlign {
|
||||
axis: Axis,
|
||||
align: AxisAlign,
|
||||
}
|
||||
|
||||
impl CardinalAlign {
|
||||
pub const LEFT: Self = Self::new(Axis::X, AxisAlign::Neg);
|
||||
pub const H_CENTER: Self = Self::new(Axis::X, AxisAlign::Center);
|
||||
pub const RIGHT: Self = Self::new(Axis::X, AxisAlign::Pos);
|
||||
pub const TOP: Self = Self::new(Axis::Y, AxisAlign::Neg);
|
||||
pub const V_CENTER: Self = Self::new(Axis::Y, AxisAlign::Center);
|
||||
pub const BOT: Self = Self::new(Axis::Y, AxisAlign::Pos);
|
||||
|
||||
pub const fn new(axis: Axis, align: AxisAlign) -> Self {
|
||||
Self { axis, align }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RegionAlign {
|
||||
pub x: AxisAlign,
|
||||
pub y: AxisAlign,
|
||||
}
|
||||
|
||||
impl RegionAlign {
|
||||
pub const TOP_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Neg);
|
||||
pub const TOP_CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Neg);
|
||||
pub const TOP_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Neg);
|
||||
pub const CENTER_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Center);
|
||||
pub const CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Center);
|
||||
pub const CENTER_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Center);
|
||||
pub const BOT_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Pos);
|
||||
pub const BOT_CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Pos);
|
||||
pub const BOT_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Pos);
|
||||
|
||||
pub const fn new(x: AxisAlign, y: AxisAlign) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
pub const fn rel(&self) -> Vec2 {
|
||||
vec2(self.x.rel(), self.y.rel())
|
||||
}
|
||||
}
|
||||
|
||||
impl UiVec2 {
|
||||
pub fn partial_align(&self, align: Align) -> UiRegion {
|
||||
UiRegion {
|
||||
x: if let Some(align) = align.x {
|
||||
self.x.align(align)
|
||||
} else {
|
||||
UiSpan::FULL
|
||||
},
|
||||
y: if let Some(align) = align.y {
|
||||
self.y.align(align)
|
||||
} else {
|
||||
UiSpan::FULL
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn align(&self, align: RegionAlign) -> UiRegion {
|
||||
UiRegion {
|
||||
x: self.x.align(align.x),
|
||||
y: self.y.align(align.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
pub fn partial_align(&self, align: Align) -> UiRegion {
|
||||
let s = UiVec2::from(*self);
|
||||
UiRegion {
|
||||
x: if let Some(align) = align.x {
|
||||
s.x.align(align)
|
||||
} else {
|
||||
UiSpan::FULL
|
||||
},
|
||||
y: if let Some(align) = align.y {
|
||||
s.y.align(align)
|
||||
} else {
|
||||
UiSpan::FULL
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn align(&self, align: RegionAlign) -> UiRegion {
|
||||
let s = UiVec2::from(*self);
|
||||
UiRegion {
|
||||
x: s.x.align(align.x),
|
||||
y: s.y.align(align.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UiScalar {
|
||||
pub const fn align(&self, align: AxisAlign) -> UiSpan {
|
||||
let rel = align.rel();
|
||||
let mut start = UiScalar::rel(rel);
|
||||
start.abs -= self.abs * rel;
|
||||
start.rel -= self.rel * rel;
|
||||
let mut end = UiScalar::rel(rel);
|
||||
end.abs += self.abs * (1.0 - rel);
|
||||
end.rel += self.rel * (1.0 - rel);
|
||||
UiSpan { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegionAlign> for Align {
|
||||
fn from(region: RegionAlign) -> Self {
|
||||
Self {
|
||||
x: Some(region.x),
|
||||
y: Some(region.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Align> for RegionAlign {
|
||||
fn from(align: Align) -> Self {
|
||||
Self {
|
||||
x: align.x.unwrap_or(AxisAlign::Center),
|
||||
y: align.y.unwrap_or(AxisAlign::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CardinalAlign> for RegionAlign {
|
||||
fn from(align: CardinalAlign) -> Self {
|
||||
Align::from(align).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CardinalAlign> for Align {
|
||||
fn from(cardinal: CardinalAlign) -> Self {
|
||||
let align = Some(cardinal.align);
|
||||
match cardinal.axis {
|
||||
Axis::X => Self { x: align, y: None },
|
||||
Axis::Y => Self { x: None, y: align },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl const From<RegionAlign> for UiVec2 {
|
||||
fn from(align: RegionAlign) -> Self {
|
||||
Self::rel(align.rel())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegionAlign {
|
||||
pub const fn pos(self) -> UiVec2 {
|
||||
UiVec2::from(self)
|
||||
}
|
||||
}
|
||||
115
core/src/orientation/axis.rs
Normal file
115
core/src/orientation/axis.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Axis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl std::ops::Not for Axis {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
match self {
|
||||
Self::X => Self::Y,
|
||||
Self::Y => Self::X,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct Dir {
|
||||
pub axis: Axis,
|
||||
pub sign: Sign,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub const fn new(axis: Axis, dir: Sign) -> Self {
|
||||
Self { axis, sign: dir }
|
||||
}
|
||||
|
||||
pub const LEFT: Self = Self::new(Axis::X, Sign::Neg);
|
||||
pub const RIGHT: Self = Self::new(Axis::X, Sign::Pos);
|
||||
pub const UP: Self = Self::new(Axis::Y, Sign::Neg);
|
||||
pub const DOWN: Self = Self::new(Axis::Y, Sign::Pos);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Sign {
|
||||
Neg,
|
||||
Pos,
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
pub fn axis(&self, axis: Axis) -> f32 {
|
||||
match axis {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
|
||||
Self {
|
||||
x: match axis {
|
||||
Axis::X => aligned,
|
||||
Axis::Y => ortho,
|
||||
},
|
||||
y: match axis {
|
||||
Axis::Y => aligned,
|
||||
Axis::X => ortho,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const trait AxisT {
|
||||
fn get() -> Axis;
|
||||
}
|
||||
|
||||
pub struct XAxis;
|
||||
impl const AxisT for XAxis {
|
||||
fn get() -> Axis {
|
||||
Axis::X
|
||||
}
|
||||
}
|
||||
|
||||
pub struct YAxis;
|
||||
impl const 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,107 +1,5 @@
|
||||
use std::ops::Not;
|
||||
|
||||
use crate::{
|
||||
layout::{UiNum, UiScalar, UiVec2, Vec2, vec2},
|
||||
util::impl_op,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Axis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Not for Axis {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
match self {
|
||||
Self::X => Self::Y,
|
||||
Self::Y => Self::X,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct Dir {
|
||||
pub axis: Axis,
|
||||
pub sign: Sign,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub const fn new(axis: Axis, dir: Sign) -> Self {
|
||||
Self { axis, sign: dir }
|
||||
}
|
||||
|
||||
pub const LEFT: Self = Self::new(Axis::X, Sign::Neg);
|
||||
pub const RIGHT: Self = Self::new(Axis::X, Sign::Pos);
|
||||
pub const UP: Self = Self::new(Axis::Y, Sign::Neg);
|
||||
pub const DOWN: Self = Self::new(Axis::Y, Sign::Pos);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Sign {
|
||||
Neg,
|
||||
Pos,
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
pub fn axis(&self, axis: Axis) -> f32 {
|
||||
match axis {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
|
||||
Self {
|
||||
x: match axis {
|
||||
Axis::X => aligned,
|
||||
Axis::Y => ortho,
|
||||
},
|
||||
y: match axis {
|
||||
Axis::Y => aligned,
|
||||
Axis::X => ortho,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Align {
|
||||
TopLeft,
|
||||
Top,
|
||||
TopRight,
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
BotLeft,
|
||||
Bot,
|
||||
BotRight,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
pub const fn rel(&self) -> Vec2 {
|
||||
match self {
|
||||
Self::TopLeft => vec2(0.0, 0.0),
|
||||
Self::Top => vec2(0.5, 0.0),
|
||||
Self::TopRight => vec2(1.0, 0.0),
|
||||
Self::Left => vec2(0.0, 0.5),
|
||||
Self::Center => vec2(0.5, 0.5),
|
||||
Self::Right => vec2(1.0, 0.5),
|
||||
Self::BotLeft => vec2(0.0, 1.0),
|
||||
Self::Bot => vec2(0.5, 1.0),
|
||||
Self::BotRight => vec2(1.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
use super::*;
|
||||
use crate::{UiNum, util::impl_op};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Size {
|
||||
@@ -131,11 +29,23 @@ impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Len> for Size {
|
||||
fn from(value: Len) -> Self {
|
||||
Self { x: value, y: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub const ZERO: Self = Self {
|
||||
x: Len::ZERO,
|
||||
y: Len::ZERO,
|
||||
};
|
||||
|
||||
pub const REST: Self = Self {
|
||||
x: Len::REST,
|
||||
y: Len::REST,
|
||||
};
|
||||
|
||||
pub fn abs(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: Len::abs(v.x),
|
||||
@@ -143,8 +53,25 @@ impl Size {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_uivec2(self, rel_len: Vec2) -> UiVec2 {
|
||||
UiVec2::from_scalars(self.x.apply_rest(rel_len.x), self.y.apply_rest(rel_len.y))
|
||||
pub fn rel(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: Len::rel(v.x),
|
||||
y: Len::rel(v.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rest(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: Len::rest(v.x),
|
||||
y: Len::rest(v.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_uivec2(self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.apply_rest(),
|
||||
y: self.y.apply_rest(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
|
||||
@@ -175,14 +102,16 @@ impl Len {
|
||||
rest: 0.0,
|
||||
};
|
||||
|
||||
pub fn apply_rest(&self, rel_len: f32) -> UiScalar {
|
||||
pub const REST: Self = Self {
|
||||
abs: 0.0,
|
||||
rel: 0.0,
|
||||
rest: 1.0,
|
||||
};
|
||||
|
||||
pub fn apply_rest(&self) -> UiScalar {
|
||||
UiScalar {
|
||||
rel: if self.rest > 0.0 {
|
||||
self.rel.max(1.0)
|
||||
} else {
|
||||
self.rel
|
||||
} * rel_len,
|
||||
abs: if self.rest > 0.0 { 0.0 } else { self.abs },
|
||||
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
|
||||
abs: self.abs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,10 +188,10 @@ impl std::fmt::Display for Len {
|
||||
write!(f, "{} abs;", self.abs)?;
|
||||
}
|
||||
if self.rel != 0.0 {
|
||||
write!(f, "{} rel;", self.abs)?;
|
||||
write!(f, "{} rel;", self.rel)?;
|
||||
}
|
||||
if self.rest != 0.0 {
|
||||
write!(f, "{} leftover;", self.abs)?;
|
||||
write!(f, "{} rest;", self.rest)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
11
core/src/orientation/mod.rs
Normal file
11
core/src/orientation/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
mod align;
|
||||
mod axis;
|
||||
mod len;
|
||||
mod pos;
|
||||
|
||||
use crate::util::Vec2;
|
||||
|
||||
pub use align::*;
|
||||
pub use axis::*;
|
||||
pub use len::*;
|
||||
pub use pos::*;
|
||||
464
core/src/orientation/pos.rs
Normal file
464
core/src/orientation/pos.rs
Normal file
@@ -0,0 +1,464 @@
|
||||
use std::{fmt::Display, hash::Hash, marker::Destruct};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
UiNum,
|
||||
util::{LerpUtil, impl_op},
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct UiVec2 {
|
||||
pub x: UiScalar,
|
||||
pub y: UiScalar,
|
||||
}
|
||||
|
||||
impl UiVec2 {
|
||||
pub const ZERO: Self = Self {
|
||||
x: UiScalar::ZERO,
|
||||
y: UiScalar::ZERO,
|
||||
};
|
||||
|
||||
pub const fn new(x: UiScalar, y: UiScalar) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn abs(abs: impl const Into<Vec2>) -> Self {
|
||||
let abs = abs.into();
|
||||
Self {
|
||||
x: UiScalar::abs(abs.x),
|
||||
y: UiScalar::abs(abs.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
|
||||
let rel = rel.into();
|
||||
Self {
|
||||
x: UiScalar::rel(rel.x),
|
||||
y: UiScalar::rel(rel.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn shift(&mut self, offset: impl const Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
*self += offset;
|
||||
}
|
||||
|
||||
pub const fn offset(mut self, offset: impl const Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn within(&self, region: &UiRegion) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.within(®ion.x),
|
||||
y: self.y.within(®ion.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, region: &UiRegion) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.outside(®ion.x),
|
||||
y: self.y.outside(®ion.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut UiScalar {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis(&self, axis: Axis) -> UiScalar {
|
||||
match axis {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_abs(&self, rel: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x.to_abs(rel.x),
|
||||
y: self.y.to_abs(rel.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const FULL_SIZE: Self = Self::rel(Vec2::ONE);
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self {
|
||||
match axis {
|
||||
Axis::X => Self {
|
||||
x: aligned,
|
||||
y: ortho,
|
||||
},
|
||||
Axis::Y => Self {
|
||||
x: ortho,
|
||||
y: aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_abs(&self) -> Vec2 {
|
||||
(self.x.abs, self.y.abs).into()
|
||||
}
|
||||
|
||||
pub fn get_rel(&self) -> Vec2 {
|
||||
(self.x.rel, self.y.rel).into()
|
||||
}
|
||||
|
||||
pub fn abs_mut(&mut self) -> Vec2View<'_> {
|
||||
Vec2View {
|
||||
x: &mut self.x.abs,
|
||||
y: &mut self.y.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiVec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "rel{};abs{}", self.get_rel(), self.get_abs())
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(UiVec2 Add add; x y);
|
||||
impl_op!(UiVec2 Sub sub; x y);
|
||||
|
||||
impl const From<Vec2> for UiVec2 {
|
||||
fn from(abs: Vec2) -> Self {
|
||||
Self::abs(abs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for UiVec2
|
||||
where
|
||||
(T, U): const Destruct,
|
||||
{
|
||||
fn from(abs: (T, U)) -> Self {
|
||||
Self::abs(abs)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, Default, bytemuck::Zeroable)]
|
||||
pub struct UiScalar {
|
||||
pub rel: f32,
|
||||
pub abs: f32,
|
||||
}
|
||||
|
||||
impl Eq for UiScalar {}
|
||||
impl Hash for UiScalar {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u32(self.rel.to_bits());
|
||||
state.write_u32(self.abs.to_bits());
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(UiScalar Add add; rel abs);
|
||||
impl_op!(UiScalar Sub sub; rel abs);
|
||||
|
||||
impl UiScalar {
|
||||
pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 };
|
||||
pub const FULL: Self = Self { rel: 1.0, abs: 0.0 };
|
||||
|
||||
pub const fn new(rel: f32, abs: f32) -> Self {
|
||||
Self { rel, abs }
|
||||
}
|
||||
|
||||
pub const fn rel(rel: f32) -> Self {
|
||||
Self { rel, abs: 0.0 }
|
||||
}
|
||||
|
||||
pub const fn abs(abs: f32) -> Self {
|
||||
Self { rel: 0.0, abs }
|
||||
}
|
||||
|
||||
pub const fn rel_min() -> Self {
|
||||
Self::new(0.0, 0.0)
|
||||
}
|
||||
|
||||
pub const fn rel_max() -> Self {
|
||||
Self::new(1.0, 0.0)
|
||||
}
|
||||
|
||||
pub const fn max(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.max(other.rel),
|
||||
abs: self.abs.max(other.abs),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn min(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.min(other.rel),
|
||||
abs: self.abs.min(other.abs),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn offset(mut self, amt: f32) -> Self {
|
||||
self.abs += amt;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn within(&self, span: &UiSpan) -> Self {
|
||||
let anchor = self.rel.lerp(span.start.rel, span.end.rel);
|
||||
let offset = self.abs + self.rel.lerp(span.start.abs, span.end.abs);
|
||||
Self {
|
||||
rel: anchor,
|
||||
abs: offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, span: &UiSpan) -> Self {
|
||||
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel);
|
||||
let abs = self.abs - rel.lerp(span.start.abs, span.end.abs);
|
||||
Self { rel, abs }
|
||||
}
|
||||
|
||||
pub fn within_len(&self, len: UiScalar) -> Self {
|
||||
self.within(&UiSpan {
|
||||
start: UiScalar::ZERO,
|
||||
end: len,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_len(&self, len: UiScalar) -> Self {
|
||||
len.within_len(*self)
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self) {
|
||||
self.rel = 1.0 - self.rel;
|
||||
self.abs = -self.abs;
|
||||
}
|
||||
|
||||
pub const fn to(&self, end: Self) -> UiSpan {
|
||||
UiSpan { start: *self, end }
|
||||
}
|
||||
|
||||
pub const fn to_abs(&self, rel: f32) -> f32 {
|
||||
self.rel * rel + self.abs
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UiSpan {
|
||||
pub start: UiScalar,
|
||||
pub end: UiScalar,
|
||||
}
|
||||
|
||||
impl UiSpan {
|
||||
pub const FULL: Self = Self {
|
||||
start: UiScalar::ZERO,
|
||||
end: UiScalar::FULL,
|
||||
};
|
||||
|
||||
pub const fn rel(rel: f32) -> Self {
|
||||
Self {
|
||||
start: UiScalar::rel(rel),
|
||||
end: UiScalar::rel(rel),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new(start: UiScalar, end: UiScalar) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self) {
|
||||
self.start.flip();
|
||||
self.end.flip();
|
||||
std::mem::swap(&mut self.start.rel, &mut self.end.rel);
|
||||
std::mem::swap(&mut self.start.abs, &mut self.end.abs);
|
||||
}
|
||||
|
||||
pub const fn shift(&mut self, offset: UiScalar) {
|
||||
self.start += offset;
|
||||
self.end += offset;
|
||||
}
|
||||
|
||||
pub const fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
start: self.start.within(parent),
|
||||
end: self.end.within(parent),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
start: self.start.outside(parent),
|
||||
end: self.end.outside(parent),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn len(&self) -> UiScalar {
|
||||
self.end - self.start
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UiRegion {
|
||||
pub x: UiSpan,
|
||||
pub y: UiSpan,
|
||||
}
|
||||
|
||||
impl UiRegion {
|
||||
pub const FULL: Self = Self {
|
||||
x: UiSpan::FULL,
|
||||
y: UiSpan::FULL,
|
||||
};
|
||||
|
||||
pub const fn new(x: UiSpan, y: UiSpan) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn rel(rel: Vec2) -> Self {
|
||||
Self {
|
||||
x: UiSpan::rel(rel.x),
|
||||
y: UiSpan::rel(rel.y),
|
||||
}
|
||||
}
|
||||
pub const fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
x: self.x.within(&parent.x),
|
||||
y: self.y.within(&parent.y),
|
||||
}
|
||||
}
|
||||
pub const fn outside(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
x: self.x.outside(&parent.x),
|
||||
y: self.y.outside(&parent.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn axis(&mut self, axis: Axis) -> &UiSpan {
|
||||
match axis {
|
||||
Axis::X => &self.x,
|
||||
Axis::Y => &self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn axis_mut(&mut self, axis: Axis) -> &mut UiSpan {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self, axis: Axis) {
|
||||
match axis {
|
||||
Axis::X => self.x.flip(),
|
||||
Axis::Y => self.y.flip(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shift(&mut self, offset: impl Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
self.x.shift(offset.x);
|
||||
self.y.shift(offset.y);
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: impl Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_px(&self, size: Vec2) -> PixelRegion {
|
||||
PixelRegion {
|
||||
top_left: self.top_left().get_rel() * size + self.top_left().get_abs(),
|
||||
bot_right: self.bot_right().get_rel() * size + self.bot_right().get_abs(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn center(&self) -> UiVec2 {
|
||||
Align::CENTER.pos().within(self)
|
||||
}
|
||||
|
||||
pub const fn size(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.len(),
|
||||
y: self.y.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn top_left(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.start,
|
||||
y: self.y.start,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn bot_right(&self) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.end,
|
||||
y: self.y.end,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: UiSpan, ortho: UiSpan) -> Self {
|
||||
Self {
|
||||
x: match axis {
|
||||
Axis::X => aligned,
|
||||
Axis::Y => ortho,
|
||||
},
|
||||
y: match axis {
|
||||
Axis::X => ortho,
|
||||
Axis::Y => aligned,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} -> {} (size: {})",
|
||||
self.top_left(),
|
||||
self.bot_right(),
|
||||
self.size()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PixelRegion {
|
||||
pub top_left: Vec2,
|
||||
pub bot_right: Vec2,
|
||||
}
|
||||
|
||||
impl PixelRegion {
|
||||
pub fn contains(&self, pos: Vec2) -> bool {
|
||||
pos.x >= self.top_left.x
|
||||
&& pos.x <= self.bot_right.x
|
||||
&& pos.y >= self.top_left.y
|
||||
&& pos.y <= self.bot_right.y
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.bot_right - self.top_left
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PixelRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} -> {}", self.top_left, self.bot_right)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Vec2View<'a> {
|
||||
pub x: &'a mut f32,
|
||||
pub y: &'a mut f32,
|
||||
}
|
||||
|
||||
impl Vec2View<'_> {
|
||||
pub fn set(&mut self, other: Vec2) {
|
||||
*self.x = other.x;
|
||||
*self.y = other.y;
|
||||
}
|
||||
|
||||
pub fn add(&mut self, other: Vec2) {
|
||||
*self.x += other.x;
|
||||
*self.y += other.y;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::multiple_bound_locations)]
|
||||
use std::marker::Destruct;
|
||||
|
||||
/// stored in linear for sane manipulation
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Zeroable, Debug)]
|
||||
pub struct Color<T: ColorNum> {
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
||||
pub struct Color<T> {
|
||||
pub r: T,
|
||||
pub g: T,
|
||||
pub b: T,
|
||||
@@ -13,6 +13,7 @@ pub struct Color<T: ColorNum> {
|
||||
impl<T: ColorNum> Color<T> {
|
||||
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
|
||||
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
|
||||
pub const GRAY: Self = Self::rgb(T::MID, T::MID, T::MID);
|
||||
|
||||
pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN);
|
||||
pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN);
|
||||
@@ -55,23 +56,47 @@ pub trait ColorNum {
|
||||
const MAX: Self;
|
||||
}
|
||||
|
||||
impl<T: ColorNum + F32Conversion> Color<T> {
|
||||
pub fn mul_rgb(self, amt: impl F32Conversion) -> Self {
|
||||
macro_rules! map_rgb {
|
||||
($x:ident,$self:ident, $e:tt) => {
|
||||
#[allow(unused_braces)]
|
||||
Self {
|
||||
r: {
|
||||
let $x = $self.r;
|
||||
$e
|
||||
},
|
||||
g: {
|
||||
let $x = $self.g;
|
||||
$e
|
||||
},
|
||||
b: {
|
||||
let $x = $self.b;
|
||||
$e
|
||||
},
|
||||
a: $self.a,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<T: ColorNum + const F32Conversion> Color<T>
|
||||
where
|
||||
Self: const Destruct,
|
||||
{
|
||||
pub const fn mul_rgb(self, amt: impl const F32Conversion) -> Self {
|
||||
let amt = amt.to();
|
||||
self.map_rgb(|x| T::from(x.to() * amt))
|
||||
map_rgb!(x, self, { T::from(x.to() * amt) })
|
||||
}
|
||||
|
||||
pub fn add_rgb(self, amt: impl F32Conversion) -> Self {
|
||||
pub const fn add_rgb(self, amt: impl const F32Conversion) -> Self {
|
||||
let amt = amt.to();
|
||||
self.map_rgb(|x| T::from(x.to() + amt))
|
||||
map_rgb!(x, self, { T::from(x.to() + amt) })
|
||||
}
|
||||
|
||||
pub fn darker(self, amt: f32) -> Self {
|
||||
pub const fn darker(self, amt: f32) -> Self {
|
||||
self.mul_rgb(1.0 - amt)
|
||||
}
|
||||
|
||||
pub fn brighter(self, amt: f32) -> Self {
|
||||
self.map_rgb(|x| {
|
||||
pub const fn brighter(self, amt: f32) -> Self {
|
||||
map_rgb!(x, self, {
|
||||
let x = x.to();
|
||||
T::from(x + (1.0 - x) * amt)
|
||||
})
|
||||
@@ -1,12 +1,18 @@
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
|
||||
use crate::{
|
||||
render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
|
||||
util::to_mut,
|
||||
};
|
||||
|
||||
struct LayerNode {
|
||||
pub type LayerId = usize;
|
||||
|
||||
struct LayerNode<T> {
|
||||
next: Ptr,
|
||||
prev: Ptr,
|
||||
child: Option<Child>,
|
||||
data: Layer,
|
||||
depth: usize,
|
||||
data: T,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -21,26 +27,22 @@ enum Ptr {
|
||||
|
||||
/// TODO: currently this does not ever free layers
|
||||
/// is that realistically desired?
|
||||
pub struct Layers {
|
||||
vec: Vec<LayerNode>,
|
||||
pub struct Layers<T> {
|
||||
vec: Vec<LayerNode<T>>,
|
||||
/// index of last layer at top level (start at first = 0)
|
||||
last: usize,
|
||||
}
|
||||
|
||||
/// TODO: this can be replaced with Primitives itself atm
|
||||
#[derive(Default)]
|
||||
pub struct Layer {
|
||||
pub primitives: Primitives,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Child {
|
||||
head: usize,
|
||||
tail: usize,
|
||||
}
|
||||
|
||||
impl Layers {
|
||||
pub fn new() -> Layers {
|
||||
pub type PrimitiveLayers = Layers<Primitives>;
|
||||
|
||||
impl<T: Default> Layers<T> {
|
||||
pub fn new() -> Layers<T> {
|
||||
Self {
|
||||
vec: vec![LayerNode::head()],
|
||||
last: 0,
|
||||
@@ -52,20 +54,21 @@ impl Layers {
|
||||
self.vec.push(LayerNode::head());
|
||||
}
|
||||
|
||||
fn push(&mut self, node: LayerNode) -> usize {
|
||||
fn push(&mut self, node: LayerNode<T>) -> LayerId {
|
||||
let i = self.vec.len();
|
||||
self.vec.push(node);
|
||||
i
|
||||
}
|
||||
|
||||
pub fn next(&mut self, i: usize) -> usize {
|
||||
pub fn next(&mut self, i: LayerId) -> LayerId {
|
||||
if let Ptr::Next(i) = self.vec[i].next {
|
||||
return i;
|
||||
}
|
||||
let i_new = self.push(LayerNode::new(
|
||||
Layer::default(),
|
||||
T::default(),
|
||||
self.vec[i].next,
|
||||
Ptr::Next(i),
|
||||
self.vec[i].depth,
|
||||
));
|
||||
self.vec[i].next = Ptr::Next(i_new);
|
||||
self.vec[i_new].prev = Ptr::Next(i);
|
||||
@@ -77,14 +80,15 @@ impl Layers {
|
||||
i_new
|
||||
}
|
||||
|
||||
pub fn child(&mut self, i: usize) -> usize {
|
||||
pub fn child(&mut self, i: LayerId) -> LayerId {
|
||||
if let Some(c) = self.vec[i].child {
|
||||
return c.head;
|
||||
}
|
||||
let i_child = self.push(LayerNode::new(
|
||||
Layer::default(),
|
||||
T::default(),
|
||||
Ptr::Parent(i),
|
||||
Ptr::Parent(i),
|
||||
self.vec[i].depth + 1,
|
||||
));
|
||||
self.vec[i].child = Some(Child {
|
||||
head: i_child,
|
||||
@@ -93,103 +97,117 @@ impl Layers {
|
||||
i_child
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_> {
|
||||
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_, T> {
|
||||
LayerIteratorMut::new(&mut self.vec, self.last)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (usize, &Layer)> {
|
||||
pub fn iter_orderless_mut(&mut self) -> impl Iterator<Item = (usize, &mut T)> {
|
||||
self.vec.iter_mut().map(|n| &mut n.data).enumerate()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (LayerId, &T)> {
|
||||
self.indices().map(|i| (i, &self.vec[i].data))
|
||||
}
|
||||
|
||||
pub fn indices(&self) -> LayerIndexIterator<'_> {
|
||||
pub fn iter_depth(&self) -> impl Iterator<Item = ((LayerId, usize), &T)> {
|
||||
self.indices()
|
||||
.map(|i| ((i, self.vec[i].depth), &self.vec[i].data))
|
||||
}
|
||||
|
||||
pub fn indices(&self) -> LayerIndexIterator<'_, T> {
|
||||
LayerIndexIterator::new(&self.vec, self.last)
|
||||
}
|
||||
|
||||
pub fn write<P: Primitive>(&mut self, layer: usize, info: PrimitiveInst<P>) -> PrimitiveHandle {
|
||||
self[layer].primitives.write(layer, info)
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self[h.layer].primitives.free(h)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Layers {
|
||||
impl PrimitiveLayers {
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: LayerId,
|
||||
info: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
self[layer].write(layer, info)
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self[h.layer].free(h)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Layers<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Layers {
|
||||
type Output = Layer;
|
||||
impl<T> Index<LayerId> for Layers<T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
fn index(&self, index: LayerId) -> &Self::Output {
|
||||
&self.vec[index].data
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for Layers {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
impl<T> IndexMut<LayerId> for Layers<T> {
|
||||
fn index_mut(&mut self, index: LayerId) -> &mut Self::Output {
|
||||
&mut self.vec[index].data
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerNode {
|
||||
pub fn new(data: Layer, next: Ptr, prev: Ptr) -> Self {
|
||||
impl<T: Default> LayerNode<T> {
|
||||
pub fn new(data: T, next: Ptr, prev: Ptr, depth: usize) -> Self {
|
||||
Self {
|
||||
next,
|
||||
prev,
|
||||
child: None,
|
||||
data,
|
||||
depth,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn head() -> Self {
|
||||
Self::new(Layer::default(), Ptr::None, Ptr::None)
|
||||
Self::new(T::default(), Ptr::None, Ptr::None, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayerIteratorMut<'a> {
|
||||
inner: LayerIndexIterator<'a>,
|
||||
pub struct LayerIteratorMut<'a, T> {
|
||||
inner: LayerIndexIterator<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LayerIteratorMut<'a> {
|
||||
type Item = (usize, &'a mut Layer);
|
||||
impl<'a, T> Iterator for LayerIteratorMut<'a, T> {
|
||||
type Item = (usize, &'a mut T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let i = self.inner.next()?;
|
||||
// SAFETY: requires index iterator to work properly
|
||||
#[allow(mutable_transmutes)]
|
||||
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) };
|
||||
let layer = unsafe { to_mut(&self.inner.vec[i].data) };
|
||||
Some((i, layer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for LayerIteratorMut<'a> {
|
||||
impl<'a, T> DoubleEndedIterator for LayerIteratorMut<'a, T> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let i = self.inner.next_back()?;
|
||||
// SAFETY: requires index iterator to work properly
|
||||
#[allow(mutable_transmutes)]
|
||||
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) };
|
||||
let layer = unsafe { to_mut(&self.inner.vec[i].data) };
|
||||
Some((i, layer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LayerIteratorMut<'a> {
|
||||
fn new(vec: &'a mut Vec<LayerNode>, last: usize) -> Self {
|
||||
impl<'a, T> LayerIteratorMut<'a, T> {
|
||||
fn new(vec: &'a mut Vec<LayerNode<T>>, last: usize) -> Self {
|
||||
Self {
|
||||
inner: LayerIndexIterator::new(vec, last),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayerIndexIterator<'a> {
|
||||
pub struct LayerIndexIterator<'a, T> {
|
||||
next: Option<usize>,
|
||||
next_back: Option<usize>,
|
||||
vec: &'a Vec<LayerNode>,
|
||||
vec: &'a Vec<LayerNode<T>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LayerIndexIterator<'a> {
|
||||
impl<'a, T> Iterator for LayerIndexIterator<'a, T> {
|
||||
type Item = usize;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -220,7 +238,7 @@ impl<'a> Iterator for LayerIndexIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
|
||||
impl<'a, T> DoubleEndedIterator for LayerIndexIterator<'a, T> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let ret_i = self.next_back?;
|
||||
let node = &self.vec[ret_i];
|
||||
@@ -242,8 +260,8 @@ impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LayerIndexIterator<'a> {
|
||||
fn new(vec: &'a Vec<LayerNode>, last: usize) -> Self {
|
||||
impl<'a, T> LayerIndexIterator<'a, T> {
|
||||
fn new(vec: &'a Vec<LayerNode<T>>, last: usize) -> Self {
|
||||
let mut last = last;
|
||||
while let Some(c) = vec[last].child {
|
||||
last = c.tail;
|
||||
9
core/src/primitive/mod.rs
Normal file
9
core/src/primitive/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod color;
|
||||
mod layer;
|
||||
mod text;
|
||||
mod texture;
|
||||
|
||||
pub use color::*;
|
||||
pub use layer::*;
|
||||
pub use text::*;
|
||||
pub use texture::*;
|
||||
187
core/src/primitive/text.rs
Normal file
187
core/src/primitive/text.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
use crate::{Align, RegionAlign, TextureHandle, Textures, UiColor, util::Vec2};
|
||||
use cosmic_text::{
|
||||
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
|
||||
SwashContent,
|
||||
};
|
||||
use image::{GenericImageView, RgbaImage};
|
||||
use std::simd::{Simd, num::SimdUint};
|
||||
|
||||
/// TODO: properly wrap this
|
||||
pub mod text_lib {
|
||||
pub use cosmic_text::*;
|
||||
}
|
||||
|
||||
pub struct TextData {
|
||||
pub font_system: FontSystem,
|
||||
pub swash_cache: SwashCache,
|
||||
glyph_cache: Vec<(Placement, CacheKey, Color)>,
|
||||
}
|
||||
|
||||
impl Default for TextData {
|
||||
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 = 16.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 TextData {
|
||||
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,14 @@
|
||||
use crate::{
|
||||
render::TexturePrimitive,
|
||||
util::{RefCounter, Vec2},
|
||||
};
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
use std::{
|
||||
ops::Index,
|
||||
sync::mpsc::{Receiver, Sender, channel},
|
||||
};
|
||||
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
|
||||
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextureHandle {
|
||||
inner: TexturePrimitive,
|
||||
size: Vec2,
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{layout::UiRegion, util::Id};
|
||||
use crate::{UiRegion, util::Id};
|
||||
use wgpu::*;
|
||||
|
||||
#[repr(C)]
|
||||
@@ -44,7 +44,7 @@ impl MaskIdx {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Mask {
|
||||
pub region: UiRegion,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::num::NonZero;
|
||||
|
||||
use crate::{
|
||||
layout::Ui,
|
||||
Ui,
|
||||
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
||||
util::HashMap,
|
||||
};
|
||||
@@ -22,7 +22,7 @@ pub use primitive::*;
|
||||
|
||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||
|
||||
pub struct UiRenderer {
|
||||
pub struct UiRenderNode {
|
||||
uniform_group: BindGroup,
|
||||
primitive_layout: BindGroupLayout,
|
||||
rsc_layout: BindGroupLayout,
|
||||
@@ -43,7 +43,7 @@ struct RenderLayer {
|
||||
primitive_group: BindGroup,
|
||||
}
|
||||
|
||||
impl UiRenderer {
|
||||
impl UiRenderNode {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_group, &[]);
|
||||
@@ -61,13 +61,12 @@ impl UiRenderer {
|
||||
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
|
||||
self.active.clear();
|
||||
for (i, ulayer) in ui.data.layers.iter_mut() {
|
||||
for (i, primitives) in ui.layers.iter_mut() {
|
||||
self.active.push(i);
|
||||
let primitives = &mut ulayer.primitives;
|
||||
for change in primitives.apply_free() {
|
||||
if let Some(inst) = ui.data.active.get_mut(&change.id) {
|
||||
if let Some(inst) = ui.active.get_mut(&change.id) {
|
||||
for h in &mut inst.primitives {
|
||||
if h.inst_idx == change.old {
|
||||
if h.layer == i && h.inst_idx == change.old {
|
||||
h.inst_idx = change.new;
|
||||
break;
|
||||
}
|
||||
@@ -97,15 +96,19 @@ impl UiRenderer {
|
||||
device,
|
||||
&self.primitive_layout,
|
||||
rlayer.primitives.buffers(),
|
||||
)
|
||||
);
|
||||
primitives.updated = false;
|
||||
}
|
||||
}
|
||||
if self.textures.update(&mut ui.data.textures) {
|
||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks)
|
||||
let mut changed = false;
|
||||
changed |= self.textures.update(&mut ui.textures);
|
||||
if ui.masks.changed {
|
||||
ui.masks.changed = false;
|
||||
self.masks.update(device, queue, &ui.masks[..]);
|
||||
changed = true;
|
||||
}
|
||||
if ui.data.masks.changed {
|
||||
ui.data.masks.changed = false;
|
||||
self.masks.update(device, queue, &ui.data.masks[..]);
|
||||
if changed {
|
||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::{
|
||||
layout::{Color, UiRegion},
|
||||
Color, UiRegion, WidgetId,
|
||||
render::{
|
||||
ArrBuf,
|
||||
data::{MaskIdx, PrimitiveInstance},
|
||||
},
|
||||
util::Id,
|
||||
};
|
||||
use bytemuck::Pod;
|
||||
use wgpu::*;
|
||||
|
||||
pub struct Primitives {
|
||||
instances: Vec<PrimitiveInstance>,
|
||||
assoc: Vec<Id>,
|
||||
assoc: Vec<WidgetId>,
|
||||
data: PrimitiveData,
|
||||
free: Vec<usize>,
|
||||
pub updated: bool,
|
||||
@@ -40,7 +39,7 @@ macro_rules! primitives {
|
||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
||||
#[derive(Default)]
|
||||
pub struct PrimitiveData {
|
||||
$($name: PrimitiveVec<$ty>,)*
|
||||
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
||||
}
|
||||
|
||||
pub struct PrimitiveBuffers {
|
||||
@@ -99,7 +98,7 @@ macro_rules! primitives {
|
||||
}
|
||||
|
||||
pub struct PrimitiveInst<P> {
|
||||
pub id: Id,
|
||||
pub id: WidgetId,
|
||||
pub primitive: P,
|
||||
pub region: UiRegion,
|
||||
pub mask_idx: MaskIdx,
|
||||
@@ -116,6 +115,7 @@ impl Primitives {
|
||||
mask_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
let vec = P::vec(&mut self.data);
|
||||
let i = vec.add(primitive);
|
||||
let inst = PrimitiveInstance {
|
||||
@@ -153,6 +153,7 @@ impl Primitives {
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self.updated = true;
|
||||
self.data.free(h.binding, h.data_idx);
|
||||
self.free.push(h.inst_idx);
|
||||
self.instances[h.inst_idx].mask_idx
|
||||
@@ -173,7 +174,7 @@ impl Primitives {
|
||||
}
|
||||
|
||||
pub struct PrimitiveChange {
|
||||
pub id: Id,
|
||||
pub id: WidgetId,
|
||||
pub old: usize,
|
||||
pub new: usize,
|
||||
}
|
||||
@@ -223,7 +224,7 @@ impl RectPrimitive {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TexturePrimitive {
|
||||
pub view_idx: u32,
|
||||
pub sampler_idx: u32,
|
||||
@@ -21,8 +21,18 @@ struct TextureInfo {
|
||||
}
|
||||
|
||||
struct Mask {
|
||||
top_left: UiVec2,
|
||||
bot_right: UiVec2,
|
||||
x: UiSpan,
|
||||
y: UiSpan,
|
||||
}
|
||||
|
||||
struct UiSpan {
|
||||
start: UiScalar,
|
||||
end: UiScalar,
|
||||
}
|
||||
|
||||
struct UiScalar {
|
||||
rel: f32,
|
||||
abs: f32,
|
||||
}
|
||||
|
||||
struct UiVec2 {
|
||||
@@ -42,10 +52,10 @@ struct WindowUniform {
|
||||
};
|
||||
|
||||
struct InstanceInput {
|
||||
@location(0) top_left_anchor: vec2<f32>,
|
||||
@location(1) top_left_offset: vec2<f32>,
|
||||
@location(2) bottom_right_anchor: vec2<f32>,
|
||||
@location(3) bottom_right_offset: vec2<f32>,
|
||||
@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,
|
||||
@@ -75,8 +85,13 @@ fn vs_main(
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let top_left = floor(in.top_left_anchor * window.dim) + floor(in.top_left_offset);
|
||||
let bot_right = floor(in.bottom_right_anchor * window.dim) + floor(in.bottom_right_offset);
|
||||
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>(
|
||||
@@ -116,8 +131,11 @@ fn fs_main(
|
||||
}
|
||||
if in.mask_idx != 4294967295u {
|
||||
let mask = masks[in.mask_idx];
|
||||
let top_left = floor(mask.top_left.rel * window.dim) + floor(mask.top_left.abs);
|
||||
let bot_right = floor(mask.bot_right.rel * window.dim) + floor(mask.bot_right.abs);
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use image::{DynamicImage, EncodableLayout};
|
||||
use wgpu::{util::DeviceExt, *};
|
||||
|
||||
use crate::layout::{TextureUpdate, Textures};
|
||||
use crate::{TextureUpdate, Textures};
|
||||
|
||||
pub struct GpuTextures {
|
||||
device: Device,
|
||||
14
core/src/ui/active.rs
Normal file
14
core/src/ui/active.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
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,
|
||||
}
|
||||
18
core/src/ui/cache.rs
Normal file
18
core/src/ui/cache.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::{BothAxis, Len, UiVec2, WidgetId, util::HashMap};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cache {
|
||||
pub size: BothAxis<HashMap<WidgetId, (UiVec2, Len)>>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn remove(&mut self, id: WidgetId) {
|
||||
self.size.x.remove(&id);
|
||||
self.size.y.remove(&id);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.size.x.clear();
|
||||
self.size.y.clear();
|
||||
}
|
||||
}
|
||||
235
core/src/ui/draw_state.rs
Normal file
235
core/src/ui/draw_state.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use crate::{
|
||||
ActiveData, Axis, EventsLike, Painter, SizeCtx, Ui, UiRegion, UiVec2, WidgetId,
|
||||
render::MaskIdx,
|
||||
util::{HashSet, forget_ref},
|
||||
};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// state maintained between widgets during painting
|
||||
pub struct DrawState<'a> {
|
||||
pub(super) ui: &'a mut Ui,
|
||||
pub(super) events: &'a mut dyn EventsLike,
|
||||
draw_started: HashSet<WidgetId>,
|
||||
}
|
||||
|
||||
impl<'a> DrawState<'a> {
|
||||
pub fn new(ui: &'a mut Ui, events: &'a mut dyn EventsLike) -> Self {
|
||||
Self {
|
||||
ui,
|
||||
events,
|
||||
draw_started: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redraw_updates(&mut self) {
|
||||
while let Some(&id) = self.widgets.needs_redraw.iter().next() {
|
||||
self.redraw(id);
|
||||
}
|
||||
self.ui.free(self.events);
|
||||
}
|
||||
|
||||
/// redraws a widget that's currently active (drawn)
|
||||
pub fn redraw(&mut self, id: WidgetId) {
|
||||
self.widgets.needs_redraw.remove(&id);
|
||||
self.draw_started.remove(&id);
|
||||
// check if parent depends on the desired size of this, if so then redraw it first
|
||||
for axis in [Axis::X, Axis::Y] {
|
||||
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
|
||||
&& let Some(current) = self.active.get(&id)
|
||||
&& let Some(pid) = current.parent
|
||||
{
|
||||
self.cache.size.axis_dyn(axis).remove(&id);
|
||||
let new = self.size_ctx(id, outer).len_axis(id, axis);
|
||||
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
|
||||
if new != old {
|
||||
self.redraw(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.draw_started.contains(&id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(active) = self.remove(id, false) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.draw_inner(
|
||||
active.layer,
|
||||
id,
|
||||
active.region,
|
||||
active.parent,
|
||||
active.mask,
|
||||
Some(active.children),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn size_ctx<'b>(&'b mut self, source: WidgetId, outer: UiVec2) -> SizeCtx<'b> {
|
||||
SizeCtx {
|
||||
source,
|
||||
cache: &mut self.ui.cache,
|
||||
text: &mut self.ui.text,
|
||||
textures: &mut self.ui.textures,
|
||||
widgets: &self.ui.widgets,
|
||||
outer,
|
||||
output_size: self.ui.output_size,
|
||||
id: source,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redraw_all(&mut self) {
|
||||
// free all resources & cache
|
||||
for (id, active) in self.ui.active.drain() {
|
||||
let data = self.ui.widgets.data(id).unwrap();
|
||||
self.events.undraw(data, &active);
|
||||
}
|
||||
self.ui.cache.clear();
|
||||
self.ui.free(self.events);
|
||||
self.layers.clear();
|
||||
self.widgets.needs_redraw.clear();
|
||||
|
||||
if let Some(id) = &self.ui.root {
|
||||
self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn draw_inner(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
id: WidgetId,
|
||||
region: UiRegion,
|
||||
parent: Option<WidgetId>,
|
||||
mask: MaskIdx,
|
||||
old_children: Option<Vec<WidgetId>>,
|
||||
) {
|
||||
let mut old_children = old_children.unwrap_or_default();
|
||||
if let Some(active) = self.ui.active.get_mut(&id)
|
||||
&& !self.ui.widgets.needs_redraw.contains(&id)
|
||||
{
|
||||
// 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).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(),
|
||||
};
|
||||
|
||||
let mut widget = painter.state.widgets.get_dyn_dynamic(id);
|
||||
widget.draw(&mut painter);
|
||||
drop(widget);
|
||||
|
||||
let Painter {
|
||||
state: _,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// update modules
|
||||
let data = self.ui.widgets.data(id).unwrap();
|
||||
self.events.draw(data, &active);
|
||||
self.active.insert(id, active);
|
||||
}
|
||||
|
||||
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
||||
let active = self.ui.active.get_mut(&id).unwrap();
|
||||
for h in &active.primitives {
|
||||
let region = self.ui.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) -> 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 {
|
||||
self.masks.remove(mask);
|
||||
}
|
||||
}
|
||||
active.textures.clear();
|
||||
self.textures.free();
|
||||
if undraw {
|
||||
let data = self.ui.widgets.data(id).unwrap();
|
||||
self.events.undraw(data, active);
|
||||
}
|
||||
}
|
||||
active
|
||||
}
|
||||
|
||||
fn remove_rec(&mut self, id: WidgetId) -> Option<ActiveData> {
|
||||
self.cache.remove(id);
|
||||
let inst = self.remove(id, true);
|
||||
if let Some(inst) = &inst {
|
||||
for c in &inst.children {
|
||||
self.remove_rec(*c);
|
||||
}
|
||||
}
|
||||
inst
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DrawState<'_> {
|
||||
type Target = Ui;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.ui
|
||||
}
|
||||
}
|
||||
impl DerefMut for DrawState<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.ui
|
||||
}
|
||||
}
|
||||
217
core/src/ui/mod.rs
Normal file
217
core/src/ui/mod.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use crate::{
|
||||
EventsLike, IdLike, Mask, PixelRegion, PrimitiveLayers, TextData, TextureHandle, Textures,
|
||||
Widget, WidgetHandle, WidgetId, Widgets,
|
||||
ui::draw_state::DrawState,
|
||||
util::{HashMap, TrackedArena, Vec2},
|
||||
};
|
||||
use image::DynamicImage;
|
||||
use std::{
|
||||
ops::{Index, IndexMut},
|
||||
sync::mpsc::{Receiver, channel},
|
||||
};
|
||||
|
||||
mod active;
|
||||
mod cache;
|
||||
mod draw_state;
|
||||
mod painter;
|
||||
mod size;
|
||||
mod state;
|
||||
|
||||
pub use active::*;
|
||||
use cache::*;
|
||||
pub use painter::Painter;
|
||||
pub use size::*;
|
||||
|
||||
pub struct Ui {
|
||||
// TODO: edit visibilities
|
||||
pub widgets: Widgets,
|
||||
|
||||
// retained painter state
|
||||
pub active: HashMap<WidgetId, ActiveData>,
|
||||
pub layers: PrimitiveLayers,
|
||||
pub textures: Textures,
|
||||
pub text: TextData,
|
||||
output_size: Vec2,
|
||||
pub masks: TrackedArena<Mask, u32>,
|
||||
pub cache: Cache,
|
||||
|
||||
pub root: Option<WidgetHandle>,
|
||||
old_root: Option<WidgetId>,
|
||||
recv: Receiver<WidgetId>,
|
||||
resized: bool,
|
||||
}
|
||||
|
||||
pub trait HasUi: Sized {
|
||||
fn get(&self) -> &Ui;
|
||||
fn get_mut(&mut self) -> &mut Ui;
|
||||
fn ui(&self) -> &Ui {
|
||||
self.get()
|
||||
}
|
||||
fn ui_mut(&mut self) -> &mut Ui {
|
||||
self.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasUi for Ui {
|
||||
fn get(&self) -> &Ui {
|
||||
self
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut Ui {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
/// useful for debugging
|
||||
pub fn set_label(&mut self, id: impl IdLike, label: String) {
|
||||
self.widgets.data_mut(id.id()).unwrap().label = label;
|
||||
}
|
||||
|
||||
pub fn label(&self, id: impl IdLike) -> &String {
|
||||
&self.widgets.data(id.id()).unwrap().label
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
self.widgets.get(id)
|
||||
}
|
||||
|
||||
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
self.widgets.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
|
||||
self.textures.add(image)
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
||||
self.output_size = size.into();
|
||||
self.resized = true;
|
||||
}
|
||||
|
||||
pub fn update(&mut self, events: &mut dyn EventsLike) {
|
||||
if !self.widgets.waiting.is_empty() {
|
||||
let len = self.widgets.waiting.len();
|
||||
let all: Vec<_> = self
|
||||
.widgets
|
||||
.waiting
|
||||
.iter()
|
||||
.map(|&w| format!("'{}' ({w:?})", self.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:#?}"
|
||||
);
|
||||
}
|
||||
if self.root_changed() {
|
||||
DrawState::new(self, events).redraw_all();
|
||||
self.old_root = self.root.as_ref().map(|r| r.id());
|
||||
} else if self.widgets.has_updates() {
|
||||
DrawState::new(self, events).redraw_updates();
|
||||
}
|
||||
if self.resized {
|
||||
self.resized = false;
|
||||
DrawState::new(self, events).redraw_all();
|
||||
}
|
||||
}
|
||||
|
||||
/// free any resources that don't have references anymore
|
||||
fn free(&mut self, events: &mut dyn EventsLike) {
|
||||
for id in self.recv.try_iter() {
|
||||
events.remove(id);
|
||||
self.widgets.delete(id);
|
||||
}
|
||||
self.textures.free();
|
||||
}
|
||||
|
||||
pub fn root_changed(&self) -> bool {
|
||||
self.root.as_ref().map(|r| r.id()) != self.old_root
|
||||
}
|
||||
|
||||
pub fn needs_redraw(&self) -> bool {
|
||||
self.root_changed() || self.widgets.has_updates()
|
||||
}
|
||||
|
||||
pub fn num_widgets(&self) -> usize {
|
||||
self.widgets.len()
|
||||
}
|
||||
|
||||
pub fn active_widgets(&self) -> usize {
|
||||
self.active.len()
|
||||
}
|
||||
|
||||
pub fn debug_layers(&self) {
|
||||
for ((idx, depth), primitives) in self.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(&self, id: &impl IdLike) -> Option<PixelRegion> {
|
||||
let region = self.active.get(&id.id())?.region;
|
||||
Some(region.to_px(self.output_size))
|
||||
}
|
||||
|
||||
pub fn debug(&self, label: &str) -> impl Iterator<Item = &ActiveData> {
|
||||
self.active.iter().filter_map(move |(&id, inst)| {
|
||||
let l = self.widgets.label(id);
|
||||
if l == label { Some(inst) } else { None }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: IdLike> Index<I> for Ui
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
type Output = I::Widget;
|
||||
|
||||
fn index(&self, id: I) -> &Self::Output {
|
||||
self.get(&id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: IdLike> IndexMut<I> for Ui
|
||||
where
|
||||
I::Widget: Sized + Widget,
|
||||
{
|
||||
fn index_mut(&mut self, id: I) -> &mut Self::Output {
|
||||
self.get_mut(&id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Ui {
|
||||
fn default() -> Self {
|
||||
let (send, recv) = channel();
|
||||
Self {
|
||||
widgets: Widgets::new(send),
|
||||
active: Default::default(),
|
||||
layers: Default::default(),
|
||||
masks: Default::default(),
|
||||
text: Default::default(),
|
||||
textures: Default::default(),
|
||||
cache: Default::default(),
|
||||
output_size: Vec2::ZERO,
|
||||
root: None,
|
||||
old_root: None,
|
||||
recv,
|
||||
resized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
140
core/src/ui/painter.rs
Normal file
140
core/src/ui/painter.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use crate::{
|
||||
Axis, Len, RenderedText, Size, SizeCtx, TextAttrs, TextBuffer, TextData, TextureHandle,
|
||||
UiRegion, Widget, WidgetHandle, WidgetId,
|
||||
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||
ui::draw_state::DrawState,
|
||||
util::Vec2,
|
||||
};
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
pub struct Painter<'a, 'b> {
|
||||
pub(super) state: &'a mut DrawState<'b>,
|
||||
|
||||
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, 'c> Painter<'a, 'c> {
|
||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||
let h = self.state.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.state.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.state.masks.push(Mask { region });
|
||||
}
|
||||
|
||||
/// Draws a widget within this widget's region.
|
||||
pub fn widget<W: ?Sized>(&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: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
|
||||
self.widget_at(id, region.within(&self.region));
|
||||
}
|
||||
|
||||
fn widget_at<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
|
||||
self.children.push(id.id());
|
||||
self.state
|
||||
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None);
|
||||
}
|
||||
|
||||
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.state
|
||||
.ui
|
||||
.text
|
||||
.draw(buffer, attrs, &mut self.state.ui.textures)
|
||||
}
|
||||
|
||||
pub fn region(&self) -> UiRegion {
|
||||
self.region
|
||||
}
|
||||
|
||||
pub fn size<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<W>) -> Size {
|
||||
self.size_ctx().size(id)
|
||||
}
|
||||
|
||||
pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len {
|
||||
match axis {
|
||||
Axis::X => self.size_ctx().width(id),
|
||||
Axis::Y => self.size_ctx().height(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_size(&self) -> Vec2 {
|
||||
self.state.output_size
|
||||
}
|
||||
|
||||
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.state.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.state.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())
|
||||
}
|
||||
}
|
||||
86
core/src/ui/size.rs
Normal file
86
core/src/ui/size.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use crate::{
|
||||
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures,
|
||||
UiVec2, WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
|
||||
};
|
||||
|
||||
pub struct SizeCtx<'a> {
|
||||
pub text: &'a mut TextData,
|
||||
pub textures: &'a mut Textures,
|
||||
pub(super) source: WidgetId,
|
||||
pub(super) widgets: &'a Widgets,
|
||||
pub(super) cache: &'a mut Cache,
|
||||
/// TODO: should this be pub? rn used for sized
|
||||
pub outer: UiVec2,
|
||||
pub(super) output_size: Vec2,
|
||||
pub(super) id: WidgetId,
|
||||
}
|
||||
|
||||
impl SizeCtx<'_> {
|
||||
pub fn id(&self) -> &WidgetId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &WidgetId {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub(super) fn len_inner<A: const AxisT>(&mut self, id: WidgetId) -> Len {
|
||||
if let Some((_, len)) = self.cache.size.axis::<A>().get(&id) {
|
||||
return *len;
|
||||
}
|
||||
let len = self
|
||||
.widgets
|
||||
.get_dyn_dynamic(id)
|
||||
.desired_len::<A>(&mut SizeCtx {
|
||||
text: self.text,
|
||||
textures: self.textures,
|
||||
source: self.source,
|
||||
widgets: self.widgets,
|
||||
cache: self.cache,
|
||||
outer: self.outer,
|
||||
output_size: self.output_size,
|
||||
id,
|
||||
});
|
||||
self.cache.size.axis::<A>().insert(id, (self.outer, len));
|
||||
len
|
||||
}
|
||||
|
||||
pub fn width(&mut self, id: impl IdLike) -> Len {
|
||||
self.len_inner::<XAxis>(id.id())
|
||||
}
|
||||
|
||||
pub fn height(&mut self, id: impl IdLike) -> Len {
|
||||
self.len_inner::<YAxis>(id.id())
|
||||
}
|
||||
|
||||
pub fn len_axis(&mut self, id: impl IdLike, axis: Axis) -> Len {
|
||||
match axis {
|
||||
Axis::X => self.width(id),
|
||||
Axis::Y => self.height(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&mut self, id: impl IdLike) -> Size {
|
||||
let id = id.id();
|
||||
Size {
|
||||
x: self.width(id),
|
||||
y: self.height(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn px_size(&mut self) -> Vec2 {
|
||||
self.outer.to_abs(self.output_size)
|
||||
}
|
||||
|
||||
pub fn output_size(&mut self) -> Vec2 {
|
||||
self.output_size
|
||||
}
|
||||
|
||||
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
|
||||
self.text.draw(buffer, attrs, self.textures)
|
||||
}
|
||||
|
||||
pub fn label(&self, id: WidgetId) -> &String {
|
||||
self.widgets.label(id)
|
||||
}
|
||||
}
|
||||
3
core/src/ui/state.rs
Normal file
3
core/src/ui/state.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// pub struct DynState {
|
||||
//
|
||||
// }
|
||||
@@ -26,6 +26,12 @@ impl<I: IdNum> IdTracker<I> {
|
||||
}
|
||||
|
||||
impl<I: IdNum> Id<I> {
|
||||
#[allow(dead_code)]
|
||||
/// for debug purposes; should this be exposed?
|
||||
/// generally you want to use labels with widgets
|
||||
pub(crate) fn raw(id: I) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
pub fn idx(&self) -> usize {
|
||||
self.0.idx()
|
||||
}
|
||||
@@ -48,7 +48,7 @@ macro_rules! impl_op {
|
||||
}
|
||||
impl const $opa for $T {
|
||||
fn $fna(&mut self, rhs: Self) {
|
||||
$(self.$field.$fna(rhs.$field);)*
|
||||
*self = self.$fn(rhs);
|
||||
}
|
||||
}
|
||||
impl const $op<f32> for $T {
|
||||
@@ -71,7 +71,7 @@ macro_rules! impl_op {
|
||||
}
|
||||
impl const $opa<f32> for $T {
|
||||
fn $fna(&mut self, rhs: f32) {
|
||||
$(self.$field.$fna(rhs);)*
|
||||
*self = self.$fn(rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
core/src/util/mod.rs
Normal file
24
core/src/util/mod.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
mod arena;
|
||||
mod borrow;
|
||||
mod change;
|
||||
mod id;
|
||||
mod math;
|
||||
mod refcount;
|
||||
mod slot;
|
||||
mod trust;
|
||||
mod typemap;
|
||||
mod vec2;
|
||||
|
||||
pub use arena::*;
|
||||
pub use borrow::*;
|
||||
pub use change::*;
|
||||
pub use id::*;
|
||||
pub use math::*;
|
||||
pub use refcount::*;
|
||||
pub use slot::*;
|
||||
pub use trust::*;
|
||||
pub use typemap::*;
|
||||
pub use vec2::*;
|
||||
|
||||
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
|
||||
pub type HashSet<K> = fxhash::FxHashSet<K>;
|
||||
@@ -3,6 +3,7 @@ use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RefCounter(Arc<AtomicU32>);
|
||||
|
||||
impl RefCounter {
|
||||
@@ -16,6 +17,15 @@ impl RefCounter {
|
||||
let refs = self.0.fetch_sub(1, Ordering::Release);
|
||||
refs == 0
|
||||
}
|
||||
pub fn quiet_clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RefCounter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RefCounter {
|
||||
69
core/src/util/slot.rs
Normal file
69
core/src/util/slot.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
17
core/src/util/trust.rs
Normal file
17
core/src/util/trust.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn forget_ref<'a, T>(x: &T) -> &'a T {
|
||||
unsafe { std::mem::transmute::<&T, &T>(x) }
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub 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 unsafe fn to_mut<T>(x: &T) -> &mut T {
|
||||
#[allow(mutable_transmutes)]
|
||||
unsafe {
|
||||
std::mem::transmute::<&T, &mut T>(x)
|
||||
}
|
||||
}
|
||||
56
core/src/util/typemap.rs
Normal file
56
core/src/util/typemap.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
use crate::{
|
||||
layout::UiNum,
|
||||
util::{DivOr, impl_op},
|
||||
};
|
||||
use std::{marker::Destruct, ops::*};
|
||||
use crate::util::{DivOr, impl_op};
|
||||
use std::{hash::Hash, ops::*};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
@@ -11,8 +8,13 @@ pub struct Vec2 {
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
|
||||
Vec2::new(x.to_f32(), y.to_f32())
|
||||
impl Eq for Vec2 {}
|
||||
|
||||
impl Hash for Vec2 {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u32(self.x.to_bits());
|
||||
state.write_u32(self.y.to_bits());
|
||||
}
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
@@ -43,14 +45,19 @@ impl Vec2 {
|
||||
y: self.y.ceil(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 tuple(&self) -> (f32, f32) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
|
||||
pub const fn with_x(mut self, x: f32) -> Self {
|
||||
self.x = x;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_y(mut self, y: f32) -> Self {
|
||||
self.y = y;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,18 +86,6 @@ impl Neg for Vec2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
|
||||
where
|
||||
(T, U): const Destruct,
|
||||
{
|
||||
fn from((x, y): (T, U)) -> Self {
|
||||
Self {
|
||||
x: x.to_f32(),
|
||||
y: y.to_f32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Vec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
26
core/src/widget/data.rs
Normal file
26
core/src/widget/data.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::any::TypeId;
|
||||
|
||||
use crate::{Widget, util::HashSet};
|
||||
|
||||
pub struct WidgetData {
|
||||
pub widget: Box<dyn Widget>,
|
||||
pub label: String,
|
||||
pub event_mgrs: HashSet<TypeId>,
|
||||
/// 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,
|
||||
event_mgrs: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
171
core/src/widget/handle.rs
Normal file
171
core/src/widget/handle.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use std::{marker::Unsize, ops::CoerceUnsized, sync::mpsc::Sender};
|
||||
|
||||
use crate::{
|
||||
HasUi, 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 WidgetHandle<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 WidgetRef<W: ?Sized = dyn Widget> {
|
||||
pub(super) id: WidgetId,
|
||||
#[allow(unused)]
|
||||
ty: *const W,
|
||||
}
|
||||
|
||||
pub struct WidgetHandles<W: ?Sized = dyn Widget> {
|
||||
pub h: WidgetHandle<W>,
|
||||
pub r: WidgetRef<W>,
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetHandle<W> {
|
||||
pub fn any(self) -> WidgetHandle<dyn Widget> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetHandle<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) -> WidgetRef<W> {
|
||||
let Self { ty, id, .. } = *self;
|
||||
WidgetRef { ty, id }
|
||||
}
|
||||
|
||||
pub fn handles(self) -> WidgetHandles<W> {
|
||||
let r = self.weak();
|
||||
WidgetHandles { h: self, r }
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetRef<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 HasUi) -> WidgetHandle<W> {
|
||||
ui.ui_mut().widgets.upgrade(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Drop for WidgetHandle<W> {
|
||||
fn drop(&mut self) {
|
||||
if self.counter.drop() {
|
||||
let _ = self.send.send(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetIdFn<State, W: ?Sized = dyn Widget>: FnOnce(&mut State) -> WidgetRef<W> {}
|
||||
impl<State, W: ?Sized, F: FnOnce(&mut State) -> WidgetRef<W>> WidgetIdFn<State, W> for F {}
|
||||
|
||||
pub trait IdLike {
|
||||
type Widget: ?Sized;
|
||||
fn id(&self) -> WidgetId;
|
||||
}
|
||||
|
||||
impl<W: ?Sized> IdLike for &WidgetHandle<W> {
|
||||
type Widget = W;
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> IdLike for WidgetHandle<W> {
|
||||
type Widget = W;
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> IdLike for WidgetRef<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<WidgetHandle<U>> for WidgetHandle<T> {}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<T> {}
|
||||
|
||||
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.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
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<'a, W: Widget + 'a, State: HasUi> FnOnce<(&'a mut State,)> for WidgetRef<W> {
|
||||
type Output = &'a mut W;
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (&'a mut State,)) -> Self::Output {
|
||||
&mut args.0.ui_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]) }
|
||||
}
|
||||
}
|
||||
86
core/src/widget/like.rs
Normal file
86
core/src/widget/like.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use crate::{HasUi, Ui};
|
||||
|
||||
use super::*;
|
||||
use std::marker::Unsize;
|
||||
|
||||
pub trait StateLike<State> {
|
||||
fn as_state(&mut self) -> &mut State;
|
||||
}
|
||||
|
||||
impl StateLike<Ui> for Ui {
|
||||
fn as_state(&mut self) -> &mut Ui {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetLike<State: HasUi + StateLike<State>, Tag>: Sized {
|
||||
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
|
||||
|
||||
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<Self::Widget>;
|
||||
|
||||
fn add_strong(self, state: &mut impl StateLike<State>) -> WidgetHandle<Self::Widget> {
|
||||
self.add(state).upgrade(state.as_state().ui_mut())
|
||||
}
|
||||
|
||||
fn with_id<W2>(
|
||||
self,
|
||||
f: impl FnOnce(&mut State, WidgetRef<Self::Widget>) -> WidgetRef<W2>,
|
||||
) -> impl WidgetIdFn<State, W2> {
|
||||
move |state| {
|
||||
let id = self.add(state);
|
||||
f(state, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_root(self, state: &mut impl StateLike<State>) {
|
||||
let id = self.add(state);
|
||||
let ui = state.as_state().ui_mut();
|
||||
ui.root = Some(id.upgrade(ui));
|
||||
}
|
||||
|
||||
fn handles(self, state: &mut impl StateLike<State>) -> WidgetHandles<Self::Widget> {
|
||||
self.add(state).upgrade(state.as_state().ui_mut()).handles()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetArrLike<State, const LEN: usize, Tag> {
|
||||
#[track_caller]
|
||||
fn add(self, state: &mut impl StateLike<State>) -> WidgetArr<LEN>;
|
||||
}
|
||||
|
||||
impl<State, const LEN: usize> WidgetArrLike<State, LEN, ArrTag> for WidgetArr<LEN> {
|
||||
fn add(self, _: &mut impl StateLike<State>) -> 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<State: HasUi + StateLike<State>, $($W: WidgetLike<State, $Tag>,$Tag,)*> WidgetArrLike<State, $n, ($($Tag,)*)> for ($($W,)*) {
|
||||
fn add(self, state: &mut impl StateLike<State>) -> WidgetArr<$n> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($W,)*) = self;
|
||||
WidgetArr::new(
|
||||
[$($W.add(state).upgrade(state.as_state().ui_mut()),)*],
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
85
core/src/widget/mod.rs
Normal file
85
core/src/widget/mod.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
|
||||
use std::any::Any;
|
||||
|
||||
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::*;
|
||||
|
||||
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: [WidgetHandle; LEN],
|
||||
}
|
||||
|
||||
impl<const LEN: usize> WidgetArr<LEN> {
|
||||
pub fn new(arr: [WidgetHandle; LEN]) -> Self {
|
||||
Self { arr }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetOption<State> {
|
||||
fn get(self, state: &mut State) -> Option<WidgetHandle>;
|
||||
}
|
||||
|
||||
impl<State> WidgetOption<State> for () {
|
||||
fn get(self, _: &mut State) -> Option<WidgetHandle> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<State, F: FnOnce(&mut State) -> Option<WidgetHandle>> WidgetOption<State> for F {
|
||||
fn get(self, state: &mut State) -> Option<WidgetHandle> {
|
||||
self(state)
|
||||
}
|
||||
}
|
||||
59
core/src/widget/tag.rs
Normal file
59
core/src/widget/tag.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use super::*;
|
||||
use crate::HasUi;
|
||||
use std::marker::Unsize;
|
||||
|
||||
pub struct WidgetTag;
|
||||
impl<State: HasUi + StateLike<State>, W: Widget> WidgetLike<State, WidgetTag> for W {
|
||||
type Widget = W;
|
||||
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
|
||||
state.as_state().get_mut().widgets.add_weak(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnTag;
|
||||
impl<State: HasUi + StateLike<State>, W: Widget, F: FnOnce(&mut State) -> W>
|
||||
WidgetLike<State, FnTag> for F
|
||||
{
|
||||
type Widget = W;
|
||||
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
|
||||
self(state.as_state()).add(state)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetFnTrait<State> {
|
||||
type Widget: Widget;
|
||||
fn run(self, state: &mut State) -> Self::Widget;
|
||||
}
|
||||
pub struct FnTraitTag;
|
||||
impl<State: HasUi + StateLike<State>, T: WidgetFnTrait<State>> WidgetLike<State, FnTraitTag> for T {
|
||||
type Widget = T::Widget;
|
||||
#[track_caller]
|
||||
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<T::Widget> {
|
||||
self.run(state.as_state()).add(state)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdTag;
|
||||
impl<State: HasUi + StateLike<State>, W: ?Sized + Widget + Unsize<dyn Widget>>
|
||||
WidgetLike<State, IdTag> for WidgetRef<W>
|
||||
{
|
||||
type Widget = W;
|
||||
fn add(self, _: &mut impl StateLike<State>) -> WidgetRef<W> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdFnTag;
|
||||
impl<
|
||||
State: HasUi + StateLike<State>,
|
||||
W: ?Sized + Widget + Unsize<dyn Widget>,
|
||||
F: FnOnce(&mut State) -> WidgetRef<W>,
|
||||
> WidgetLike<State, IdFnTag> for F
|
||||
{
|
||||
type Widget = W;
|
||||
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
|
||||
self(state.as_state())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArrTag;
|
||||
109
core/src/widget/widgets.rs
Normal file
109
core/src/widget/widgets.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use crate::{
|
||||
IdLike, Widget, WidgetData, WidgetHandle, WidgetId, WidgetRef,
|
||||
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
|
||||
};
|
||||
|
||||
pub struct Widgets {
|
||||
pub needs_redraw: HashSet<WidgetId>,
|
||||
vec: SlotVec<WidgetData>,
|
||||
send: Sender<WidgetId>,
|
||||
pub(crate) waiting: HashSet<WidgetId>,
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
pub fn new(send: Sender<WidgetId>) -> Self {
|
||||
Self {
|
||||
needs_redraw: Default::default(),
|
||||
vec: Default::default(),
|
||||
waiting: Default::default(),
|
||||
send,
|
||||
}
|
||||
}
|
||||
|
||||
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) -> WidgetHandle<W> {
|
||||
let id = self.vec.add(WidgetData::new(widget));
|
||||
WidgetHandle::new(id, self.send.clone())
|
||||
}
|
||||
|
||||
pub fn add_weak<W: Widget>(&mut self, widget: W) -> WidgetRef<W> {
|
||||
let id = self.vec.add(WidgetData::new(widget));
|
||||
self.waiting.insert(id);
|
||||
WidgetRef::new(id)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn upgrade<W: ?Sized>(&mut self, rf: WidgetRef<W>) -> WidgetHandle<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")
|
||||
}
|
||||
WidgetHandle::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
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self, id: impl IdLike) -> Option<&mut WidgetData> {
|
||||
self.vec.get_mut(id.id())
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, id: impl IdLike) {
|
||||
self.vec.free(id.id());
|
||||
// not sure if there's any point in this
|
||||
// self.updates.remove(&id);
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.vec.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;
|
||||
20
examples/minimal.rs
Normal file
20
examples/minimal.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use iris::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::<State>::run();
|
||||
}
|
||||
|
||||
#[default_ui_state]
|
||||
struct State {}
|
||||
|
||||
impl DefaultAppState for State {
|
||||
fn new(ui_state: DefaultUiState, _proxy: Proxy<Self::Event>) -> Self {
|
||||
let mut ui = Ui::new();
|
||||
rect(Color::RED).set_root(&mut ui);
|
||||
Self {
|
||||
ui,
|
||||
ui_state,
|
||||
events: EventManager::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
12
macro/Cargo.toml
Normal file
12
macro/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "iris-macro"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.103"
|
||||
quote = "1.0.42"
|
||||
syn = { version = "2.0.111", features = ["full"] }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
192
macro/src/lib.rs
Normal file
192
macro/src/lib.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Attribute, Block, Error, Fields, FieldsNamed, GenericParam, Generics, Ident, ItemStruct,
|
||||
ItemTrait, Meta, Signature, Token, Visibility,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input, parse_quote,
|
||||
spanned::Spanned,
|
||||
};
|
||||
|
||||
struct Input {
|
||||
attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
name: Ident,
|
||||
generics: Generics,
|
||||
fns: Vec<InputFn>,
|
||||
}
|
||||
|
||||
struct InputFn {
|
||||
sig: Signature,
|
||||
body: Block,
|
||||
}
|
||||
|
||||
impl Parse for Input {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let attrs = input.call(Attribute::parse_outer)?;
|
||||
let vis = input.parse()?;
|
||||
input.parse::<Token![trait]>()?;
|
||||
let name = input.parse()?;
|
||||
let generics = input.parse::<Generics>()?;
|
||||
input.parse::<Token![;]>()?;
|
||||
let mut fns = Vec::new();
|
||||
while !input.is_empty() {
|
||||
let sig = input.parse()?;
|
||||
let body = input.parse()?;
|
||||
fns.push(InputFn { sig, body })
|
||||
}
|
||||
if !input.is_empty() {
|
||||
input.error("function expected");
|
||||
}
|
||||
Ok(Input {
|
||||
attrs,
|
||||
vis,
|
||||
name,
|
||||
generics,
|
||||
fns,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn widget_trait(input: TokenStream) -> TokenStream {
|
||||
let Input {
|
||||
attrs,
|
||||
vis,
|
||||
name,
|
||||
mut generics,
|
||||
fns,
|
||||
} = parse_macro_input!(input as Input);
|
||||
|
||||
let sigs: Vec<_> = fns.iter().map(|f| f.sig.clone()).collect();
|
||||
let impls: Vec<_> = fns
|
||||
.iter()
|
||||
.map(|InputFn { sig, body }| quote! { #sig #body })
|
||||
.collect();
|
||||
|
||||
let Some(GenericParam::Type(state)) = generics.params.first() else {
|
||||
return Error::new(name.span(), "expected state generic parameter")
|
||||
.into_compile_error()
|
||||
.into();
|
||||
};
|
||||
|
||||
let state = &state.ident;
|
||||
|
||||
generics
|
||||
.params
|
||||
.push(parse_quote!(WL: WidgetLike<#state, Tag>));
|
||||
generics.params.push(parse_quote!(Tag));
|
||||
|
||||
let mut trai: ItemTrait = parse_quote!(
|
||||
#vis trait #name #generics {
|
||||
#(#sigs;)*
|
||||
}
|
||||
);
|
||||
|
||||
trai.attrs = attrs;
|
||||
|
||||
quote! {
|
||||
#trai
|
||||
|
||||
impl #generics #name<State, WL, Tag> for WL {
|
||||
#(#impls)*
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(UiState, attributes(rsc))]
|
||||
pub fn derive_ui_state(input: TokenStream) -> TokenStream {
|
||||
let mut output = proc_macro2::TokenStream::new();
|
||||
|
||||
let state: ItemStruct = parse_macro_input!(input);
|
||||
let sname = state.ident;
|
||||
let rscname = Ident::new(&(sname.to_string() + "Rsc"), sname.span());
|
||||
let mut rsc_fields = Vec::new();
|
||||
|
||||
for field in state.fields {
|
||||
let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("rsc")) else {
|
||||
continue;
|
||||
};
|
||||
let Meta::List(list) = &attr.meta else {
|
||||
output.extend(Error::new(attr.span(), "invalid attr syntax").into_compile_error());
|
||||
continue;
|
||||
};
|
||||
let tname: Ident = match list.parse_args::<Ident>() {
|
||||
Ok(ident) => ident,
|
||||
Err(err) => {
|
||||
output.extend(err.to_compile_error());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let fty = &field.ty;
|
||||
let fname = &field.ident.unwrap();
|
||||
rsc_fields.extend(quote! {#fname: #fty,});
|
||||
output.extend(quote! {
|
||||
impl #tname for #sname {
|
||||
fn get(&self) -> &#fty {
|
||||
&self.#fname
|
||||
}
|
||||
fn get_mut(&mut self) -> &mut #fty {
|
||||
&mut self.#fname
|
||||
}
|
||||
}
|
||||
impl #tname for #rscname {
|
||||
fn get(&self) -> &#fty {
|
||||
&self.#fname
|
||||
}
|
||||
fn get_mut(&mut self) -> &mut #fty {
|
||||
&mut self.#fname
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
let vis = state.vis;
|
||||
output.extend(quote! {
|
||||
#vis struct #rscname {
|
||||
#(#rsc_fields)*
|
||||
}
|
||||
|
||||
impl HasState for #sname {
|
||||
type State = #sname;
|
||||
}
|
||||
|
||||
impl HasState for #rscname {
|
||||
type State = #sname;
|
||||
}
|
||||
|
||||
impl StateLike<#sname> for #sname {
|
||||
fn as_state(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl StateLike<#rscname> for #rscname {
|
||||
fn as_state(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
});
|
||||
output.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn default_ui_state(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut state: ItemStruct = parse_macro_input!(input);
|
||||
let Fields::Named(fields) = &mut state.fields else {
|
||||
panic!("must be on named fields struct");
|
||||
};
|
||||
let name = &state.ident;
|
||||
state.attrs.push(parse_quote! {#[derive(UiState)]});
|
||||
let new: FieldsNamed = parse_quote! {{
|
||||
#[rsc(HasUi)]
|
||||
pub ui: Ui,
|
||||
#[rsc(HasDefaultUiState)]
|
||||
pub ui_state: DefaultUiState,
|
||||
#[rsc(HasEvents)]
|
||||
pub events: iris::prelude::EventManager<#name>,
|
||||
}};
|
||||
fields.named.extend(new.named);
|
||||
quote! {#state}.into()
|
||||
}
|
||||
23
readme.md
23
readme.md
@@ -1,26 +1,31 @@
|
||||
# iris
|
||||
|
||||
my take on a rust ui library (also my first ui library)
|
||||
My experimental attempt at a rust ui library (also my first ui library).
|
||||
|
||||
it's called iris because it's the structure around what you actually want to display and colorful
|
||||
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.
|
||||
|
||||
there's a `main.rs` that runs a testing window, so can just `cargo run` to see it working
|
||||
There's a `main.rs` that runs a testing window, so you can just `cargo run` to see it working.
|
||||
|
||||
goals, in general order:
|
||||
1. does what I want it to (video, text, animations)
|
||||
Goals, in general order:
|
||||
1. does what I want it to (text, images, video, animations)
|
||||
2. very easy to use ignoring ergonomic ref counting
|
||||
3. reasonably fast / efficient (a lot faster than electron, save battery life)
|
||||
|
||||
not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps
|
||||
## dev details
|
||||
|
||||
not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps / not need a web browser
|
||||
|
||||
general ideas trynna use rn / experiment with:
|
||||
- retained mode
|
||||
- specifically designed around wgpu
|
||||
- specifically designed around wgpu so there's no translation
|
||||
- postfix functions for most things to prevent unreadable indentation (going very well)
|
||||
- no macros in user code / actual LSP typechecking (variadic generics if you can hear me please save us)
|
||||
- events can be done directly where you draw the widgets
|
||||
- almost no macros in user code & actual LSP typechecking (variadic generics if you can hear me please save us)
|
||||
- relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout)
|
||||
- single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this)
|
||||
- widgets store outside of the actual rendering so they can be moved around and swapped easily (unsure about this but seems to work good for now)
|
||||
|
||||
under heavy initial development so not gonna try to explain status, check TODO for that maybe
|
||||
under heavy initial development so not gonna try to explain status, maybe check TODO for that;
|
||||
sizable chance it gets a rewrite once I know everything I need and what seems to work best
|
||||
|
||||
it's called iris because it's the structure around what you actually want to display and colorful
|
||||
|
||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
224
src/bin/test/main.rs
Normal file
224
src/bin/test/main.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
use cosmic_text::Family;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
iris::state_prelude!(ClientRsc);
|
||||
|
||||
fn main() {
|
||||
App::<Client>::run();
|
||||
}
|
||||
|
||||
#[default_ui_state]
|
||||
pub struct Client {
|
||||
info: WidgetRef<Text>,
|
||||
}
|
||||
|
||||
impl DefaultAppState for Client {
|
||||
fn new(ui_state: DefaultUiState, _proxy: Proxy<Self::Event>) -> Self {
|
||||
let mut rsc = ClientRsc {
|
||||
ui: Ui::new(),
|
||||
ui_state,
|
||||
events: EventManager::default(),
|
||||
};
|
||||
|
||||
let rrect = rect(Color::WHITE).radius(20);
|
||||
let pad_test = (
|
||||
rrect.color(Color::BLUE),
|
||||
(
|
||||
rrect
|
||||
.color(Color::RED)
|
||||
.sized((100, 100))
|
||||
.center()
|
||||
.width(rest(2)),
|
||||
(
|
||||
rrect.color(Color::ORANGE),
|
||||
rrect.color(Color::LIME).pad(10.0),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.width(rest(2)),
|
||||
rrect.color(Color::YELLOW),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.pad(10)
|
||||
.width(rest(3)),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.add(&mut rsc);
|
||||
|
||||
let span_test = (
|
||||
rrect.color(Color::GREEN).width(100),
|
||||
rrect.color(Color::ORANGE),
|
||||
rrect.color(Color::CYAN),
|
||||
rrect.color(Color::BLUE).width(rel(0.5)),
|
||||
rrect.color(Color::MAGENTA).width(100),
|
||||
rrect.color(Color::RED).width(100),
|
||||
)
|
||||
.span(Dir::LEFT)
|
||||
.add(&mut rsc);
|
||||
|
||||
let span_add = Span::empty(Dir::RIGHT).add(&mut rsc);
|
||||
|
||||
let add_button = rect(Color::LIME)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
let child = image(include_bytes!("assets/sungals.png"))
|
||||
.center()
|
||||
.add_strong(ctx);
|
||||
span_add(ctx).push(child);
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BOT_RIGHT);
|
||||
|
||||
let del_button = rect(Color::RED)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
span_add(ctx).pop();
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BOT_LEFT);
|
||||
|
||||
let span_add_test = (span_add, add_button, del_button).stack().add(&mut rsc);
|
||||
|
||||
let btext = |content| wtext(content).size(30);
|
||||
|
||||
let text_test = (
|
||||
btext("this is a").align(Align::LEFT),
|
||||
btext("teeeeeeeest").align(Align::RIGHT),
|
||||
btext("okkk\nokkkkkk!").align(Align::LEFT),
|
||||
btext("hmm"),
|
||||
btext("a"),
|
||||
(
|
||||
btext("'").family(Family::Monospace).align(Align::TOP),
|
||||
btext("'").family(Family::Monospace),
|
||||
btext(":gamer mode").family(Family::Monospace),
|
||||
rect(Color::CYAN).sized((10, 10)).center(),
|
||||
rect(Color::RED).sized((100, 100)).center(),
|
||||
rect(Color::PURPLE).sized((50, 50)).align(Align::TOP),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.center(),
|
||||
wtext("pretty cool right?").size(50),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add(&mut rsc);
|
||||
|
||||
let texts = Span::empty(Dir::DOWN).gap(10).add(&mut rsc);
|
||||
let msg_area = texts.scrollable().masked().background(rect(Color::SKY));
|
||||
let add_text = wtext("add")
|
||||
.editable(EditMode::MultiLine)
|
||||
.text_align(Align::LEFT)
|
||||
.size(30)
|
||||
.attr::<Selectable>(())
|
||||
.on(Submit, move |ctx| {
|
||||
let w = ctx.widget;
|
||||
let content = w.edit(ctx).take();
|
||||
let text = wtext(content)
|
||||
.editable(EditMode::MultiLine)
|
||||
.size(30)
|
||||
.text_align(Align::LEFT)
|
||||
.wrap(true)
|
||||
.attr::<Selectable>(());
|
||||
let msg_box = text
|
||||
.background(rect(Color::WHITE.darker(0.5)))
|
||||
.add_strong(ctx);
|
||||
texts(ctx).push(msg_box);
|
||||
})
|
||||
.add(&mut rsc);
|
||||
let text_edit_scroll = (
|
||||
msg_area.height(rest(1)),
|
||||
(
|
||||
Rect::new(Color::WHITE.darker(0.9)),
|
||||
(
|
||||
add_text.width(rest(1)),
|
||||
Rect::new(Color::GREEN)
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
ctx.state.run_event::<Submit>(add_text, &mut ());
|
||||
})
|
||||
.sized((40, 40)),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.pad(10),
|
||||
)
|
||||
.stack()
|
||||
.size(StackSize::Child(1))
|
||||
.layer_offset(1)
|
||||
.align(Align::BOT),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add(&mut rsc);
|
||||
|
||||
let main = WidgetPtr::new().add(&mut rsc);
|
||||
|
||||
let vals = Rc::new(RefCell::new((0, Vec::new())));
|
||||
let mut switch_button = |color, to: WidgetRef, label| {
|
||||
let to = to.upgrade(&mut rsc);
|
||||
let vec = &mut vals.borrow_mut().1;
|
||||
let i = vec.len();
|
||||
if vec.is_empty() {
|
||||
vec.push(None);
|
||||
rsc.ui[main].set(to);
|
||||
} else {
|
||||
vec.push(Some(to));
|
||||
}
|
||||
let vals = vals.clone();
|
||||
let rect = rect(color)
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
let (prev, vec) = &mut *vals.borrow_mut();
|
||||
if let Some(h) = vec[i].take() {
|
||||
vec[*prev] = main(ctx).replace(h);
|
||||
*prev = i;
|
||||
}
|
||||
ctx.widget().color = color.darker(0.3);
|
||||
})
|
||||
.on(
|
||||
CursorSense::HoverStart | CursorSense::unclick(),
|
||||
move |ctx| {
|
||||
ctx.widget().color = color.brighter(0.2);
|
||||
},
|
||||
)
|
||||
.on(CursorSense::HoverEnd, move |ctx| {
|
||||
ctx.widget().color = color;
|
||||
});
|
||||
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
|
||||
};
|
||||
|
||||
let tabs = (
|
||||
switch_button(Color::RED, pad_test, "pad"),
|
||||
switch_button(Color::GREEN, span_test, "span"),
|
||||
switch_button(Color::BLUE, span_add_test, "image span"),
|
||||
switch_button(Color::MAGENTA, text_test, "text layout"),
|
||||
switch_button(
|
||||
Color::YELLOW.mul_rgb(0.5),
|
||||
text_edit_scroll,
|
||||
"text edit scroll",
|
||||
),
|
||||
)
|
||||
.span(Dir::RIGHT);
|
||||
|
||||
let info = wtext("").add(&mut rsc);
|
||||
let info_sect = info.pad(10).align(Align::RIGHT);
|
||||
|
||||
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect)
|
||||
.stack()
|
||||
.set_root(&mut rsc);
|
||||
|
||||
Self {
|
||||
ui: rsc.ui,
|
||||
ui_state: rsc.ui_state,
|
||||
events: rsc.events,
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(&mut self, _: WindowEvent) {
|
||||
let new = format!(
|
||||
"widgets: {}\nactive:{}\nviews: {}",
|
||||
self.ui.num_widgets(),
|
||||
self.ui.active_widgets(),
|
||||
self.ui_state.renderer.ui.view_count()
|
||||
);
|
||||
if new != *self.ui[self.info].content {
|
||||
*self.ui[self.info].content = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Masked {
|
||||
pub inner: WidgetId,
|
||||
}
|
||||
|
||||
impl Widget for Masked {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
painter.set_mask(painter.region());
|
||||
painter.widget(&self.inner);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Aligned {
|
||||
pub inner: WidgetId,
|
||||
pub align: Align,
|
||||
}
|
||||
|
||||
impl Widget for Aligned {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
let region = UiRegion::from_ui_size_align(painter.region_size(&self.inner), self.align);
|
||||
painter.widget_within(&self.inner, region);
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
ctx.size(&self.inner)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Offset {
|
||||
pub inner: WidgetId,
|
||||
pub amt: UiVec2,
|
||||
}
|
||||
|
||||
impl Widget for Offset {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
let region = UiRegion::full().offset(self.amt);
|
||||
painter.widget_within(&self.inner, region);
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
ctx.size(&self.inner)
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Pad {
|
||||
pub padding: Padding,
|
||||
pub inner: WidgetId,
|
||||
}
|
||||
|
||||
impl Widget for Pad {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
painter.widget_within(&self.inner, self.padding.region());
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
let mut size = ctx.size(&self.inner);
|
||||
if size.x.rest == 0.0 {
|
||||
size.x.abs += self.padding.left + self.padding.right;
|
||||
}
|
||||
if size.y.rest == 0.0 {
|
||||
size.y.abs += self.padding.top + self.padding.bottom;
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Padding {
|
||||
pub left: f32,
|
||||
pub right: f32,
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
pub fn uniform(amt: impl UiNum) -> Self {
|
||||
let amt = amt.to_f32();
|
||||
Self {
|
||||
left: amt,
|
||||
right: amt,
|
||||
top: amt,
|
||||
bottom: amt,
|
||||
}
|
||||
}
|
||||
pub fn region(&self) -> UiRegion {
|
||||
let mut region = UiRegion::full();
|
||||
region.top_left.abs.x += self.left;
|
||||
region.top_left.abs.y += self.top;
|
||||
region.bot_right.abs.x -= self.right;
|
||||
region.bot_right.abs.y -= self.bottom;
|
||||
region
|
||||
}
|
||||
pub fn x(amt: impl UiNum) -> Self {
|
||||
let amt = amt.to_f32();
|
||||
Self {
|
||||
left: amt,
|
||||
right: amt,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
}
|
||||
}
|
||||
pub fn y(amt: impl UiNum) -> Self {
|
||||
let amt = amt.to_f32();
|
||||
Self {
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: amt,
|
||||
bottom: amt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UiNum> From<T> for Padding {
|
||||
fn from(amt: T) -> Self {
|
||||
Self::uniform(amt.to_f32())
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Sized {
|
||||
pub inner: WidgetId,
|
||||
pub x: Option<Len>,
|
||||
pub y: Option<Len>,
|
||||
}
|
||||
|
||||
impl Widget for Sized {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
painter.widget(&self.inner);
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
let rel = ctx.size.rel;
|
||||
if let Some(x) = self.x {
|
||||
ctx.size.axis_mut(Axis::X).set(x.apply_rest(rel.x));
|
||||
}
|
||||
if let Some(y) = self.y {
|
||||
ctx.size.axis_mut(Axis::Y).set(y.apply_rest(rel.y));
|
||||
}
|
||||
Size {
|
||||
x: self.x.unwrap_or_else(|| ctx.size(&self.inner).x),
|
||||
y: self.y.unwrap_or_else(|| ctx.size(&self.inner).y),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Span {
|
||||
pub children: Vec<WidgetId>,
|
||||
pub dir: Dir,
|
||||
pub gap: f32,
|
||||
}
|
||||
|
||||
impl Widget for Span {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
let total = self.len_sum(&mut painter.size_ctx());
|
||||
let mut start = UiScalar::rel_min();
|
||||
for child in &self.children {
|
||||
let mut child_region = UiRegion::full();
|
||||
let mut axis = child_region.axis_mut(self.dir.axis);
|
||||
axis.top_left.set(start);
|
||||
let len = painter.size(child).axis(self.dir.axis);
|
||||
if len.rest > 0.0 {
|
||||
let offset = UiScalar::new(total.rel, total.abs);
|
||||
let rel_end = UiScalar::from_anchor(len.rest / total.rest);
|
||||
start = rel_end.within(start, (UiScalar::rel_max() + start) - offset);
|
||||
}
|
||||
start.abs += len.abs;
|
||||
start.rel += len.rel;
|
||||
axis.bot_right.set(start);
|
||||
if self.dir.sign == Sign::Neg {
|
||||
child_region.flip(self.dir.axis);
|
||||
}
|
||||
painter.widget_within(child, child_region);
|
||||
start.abs += self.gap;
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
let mut sums = self.len_sum(ctx);
|
||||
let dir_len = if sums.rest == 0.0 && sums.rel == 0.0 {
|
||||
sums.abs += self.gap * self.children.len().saturating_sub(1) as f32;
|
||||
sums
|
||||
} else {
|
||||
Len::default()
|
||||
};
|
||||
let mut max_ortho = Len::ZERO;
|
||||
for child in &self.children {
|
||||
let len = ctx.size(child).axis(!self.dir.axis);
|
||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
||||
if len.rel > 0.0 || len.rest > 0.0 {
|
||||
max_ortho.rest = 1.0;
|
||||
max_ortho.abs = 0.0;
|
||||
break;
|
||||
}
|
||||
max_ortho.abs = max_ortho.abs.max(len.abs);
|
||||
}
|
||||
Size::from_axis(self.dir.axis, dir_len, max_ortho)
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn empty(dir: Dir) -> Self {
|
||||
Self {
|
||||
children: Vec::new(),
|
||||
dir,
|
||||
gap: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
||||
self.gap = gap.to_f32();
|
||||
self
|
||||
}
|
||||
|
||||
fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
self.children.iter_mut().fold(Len::ZERO, |mut s, id| {
|
||||
s += ctx.size(id).axis(self.dir.axis);
|
||||
s
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpanBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
||||
pub children: Wa,
|
||||
pub dir: Dir,
|
||||
pub gap: f32,
|
||||
_pd: PhantomData<Tag>,
|
||||
}
|
||||
|
||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
|
||||
for SpanBuilder<LEN, Wa, Tag>
|
||||
{
|
||||
type Output = Span;
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
||||
Span {
|
||||
children: self.children.ui(args.0).arr.to_vec(),
|
||||
dir: self.dir,
|
||||
gap: self.gap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Tag> {
|
||||
pub fn new(children: Wa, dir: Dir) -> Self {
|
||||
Self {
|
||||
children,
|
||||
dir,
|
||||
gap: 0.0,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
||||
self.gap = gap.to_f32();
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WidgetPtr {
|
||||
pub inner: Option<WidgetId>,
|
||||
}
|
||||
|
||||
impl Widget for WidgetPtr {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
if let Some(id) = &self.inner {
|
||||
painter.widget(id);
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
if let Some(id) = &self.inner {
|
||||
ctx.size(id)
|
||||
} else {
|
||||
Size::ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::{
|
||||
ops::{BitOr, Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
layout::{UiModule, UiRegion, Vec2},
|
||||
util::{HashMap, Id},
|
||||
};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum Button {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum CursorSense {
|
||||
PressStart(Button),
|
||||
Pressing(Button),
|
||||
PressEnd(Button),
|
||||
HoverStart,
|
||||
Hovering,
|
||||
HoverEnd,
|
||||
Scroll,
|
||||
}
|
||||
|
||||
pub struct CursorSenses(Vec<CursorSense>);
|
||||
|
||||
impl CursorSense {
|
||||
pub fn click() -> Self {
|
||||
Self::PressStart(Button::Left)
|
||||
}
|
||||
pub fn unclick() -> Self {
|
||||
Self::PressEnd(Button::Left)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorState {
|
||||
pub pos: Vec2,
|
||||
pub exists: bool,
|
||||
pub buttons: CursorButtons,
|
||||
pub scroll_delta: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorButtons {
|
||||
pub left: ActivationState,
|
||||
pub middle: ActivationState,
|
||||
pub right: ActivationState,
|
||||
}
|
||||
|
||||
impl CursorButtons {
|
||||
pub fn select(&self, button: &Button) -> &ActivationState {
|
||||
match button {
|
||||
Button::Left => &self.left,
|
||||
Button::Right => &self.right,
|
||||
Button::Middle => &self.middle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.left.end_frame();
|
||||
self.middle.end_frame();
|
||||
self.right.end_frame();
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorState {
|
||||
pub fn end_frame(&mut self) {
|
||||
self.buttons.end_frame();
|
||||
self.scroll_delta = Vec2::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
pub enum ActivationState {
|
||||
Start,
|
||||
On,
|
||||
End,
|
||||
#[default]
|
||||
Off,
|
||||
}
|
||||
|
||||
/// this and other similar stuff has a generic
|
||||
/// because I kind of want to make CursorModule generic
|
||||
/// or basically have some way to have custom senses
|
||||
/// that depend on active widget positions
|
||||
/// but I'm not sure how or if worth it
|
||||
pub struct Sensor<Ctx, Data> {
|
||||
pub senses: CursorSenses,
|
||||
pub f: Rc<dyn EventFn<Ctx, Data>>,
|
||||
}
|
||||
|
||||
pub type SensorMap<Ctx, Data> = HashMap<Id, SensorGroup<Ctx, Data>>;
|
||||
pub type SenseShape = UiRegion;
|
||||
pub struct SensorGroup<Ctx, Data> {
|
||||
pub hover: ActivationState,
|
||||
pub sensors: Vec<Sensor<Ctx, Data>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CursorData {
|
||||
pub cursor: Vec2,
|
||||
pub size: Vec2,
|
||||
pub scroll_delta: Vec2,
|
||||
}
|
||||
|
||||
pub struct CursorModule<Ctx> {
|
||||
map: SensorMap<Ctx, CursorData>,
|
||||
active: HashMap<usize, HashMap<Id, SenseShape>>,
|
||||
}
|
||||
|
||||
impl<Ctx: 'static> UiModule for CursorModule<Ctx> {
|
||||
fn on_draw(&mut self, inst: &WidgetInstance) {
|
||||
if self.map.contains_key(&inst.id) {
|
||||
self.active
|
||||
.entry(inst.layer)
|
||||
.or_default()
|
||||
.insert(inst.id, inst.region);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_undraw(&mut self, inst: &WidgetInstance) {
|
||||
if let Some(layer) = self.active.get_mut(&inst.layer) {
|
||||
layer.remove(&inst.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove(&mut self, id: &Id) {
|
||||
self.map.remove(id);
|
||||
for layer in self.active.values_mut() {
|
||||
layer.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_move(&mut self, inst: &WidgetInstance) {
|
||||
if let Some(map) = self.active.get_mut(&inst.layer)
|
||||
&& let Some(region) = map.get_mut(&inst.id)
|
||||
{
|
||||
*region = inst.region;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx> CursorModule<Ctx> {
|
||||
pub fn merge(&mut self, other: Self) {
|
||||
for (id, group) in other.map {
|
||||
for sensor in group.sensors {
|
||||
self.map.entry(id).or_default().sensors.push(sensor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SensorCtx: UiCtx {
|
||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
|
||||
}
|
||||
|
||||
impl<Ctx: UiCtx + 'static> SensorCtx for Ctx {
|
||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) {
|
||||
CursorModule::<Ctx>::run(self, cursor, window_size);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
|
||||
pub fn run(ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) {
|
||||
let layers = std::mem::take(&mut ctx.ui().data.layers);
|
||||
let mut module = std::mem::take(ctx.ui().data.modules.get_mut::<Self>());
|
||||
|
||||
for i in layers.indices().rev() {
|
||||
let Some(list) = module.active.get_mut(&i) else {
|
||||
continue;
|
||||
};
|
||||
let mut sensed = false;
|
||||
for (id, shape) in list.iter() {
|
||||
let group = module.map.get_mut(id).unwrap();
|
||||
let region = shape.to_screen(window_size);
|
||||
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||
group.hover.update(in_shape);
|
||||
if group.hover == ActivationState::Off {
|
||||
continue;
|
||||
}
|
||||
sensed = true;
|
||||
|
||||
for sensor in &mut group.sensors {
|
||||
if should_run(&sensor.senses, cursor, group.hover) {
|
||||
let data = CursorData {
|
||||
cursor: cursor.pos - region.top_left,
|
||||
size: region.bot_right - region.top_left,
|
||||
scroll_delta: cursor.scroll_delta,
|
||||
};
|
||||
(sensor.f)(ctx, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if sensed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let ui_mod = ctx.ui().data.modules.get_mut::<Self>();
|
||||
std::mem::swap(ui_mod, &mut module);
|
||||
ui_mod.merge(module);
|
||||
ctx.ui().data.layers = layers;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_run(senses: &CursorSenses, cursor: &CursorState, hover: ActivationState) -> bool {
|
||||
for sense in senses.iter() {
|
||||
if match sense {
|
||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||
CursorSense::PressEnd(button) => cursor.buttons.select(button).is_end(),
|
||||
CursorSense::HoverStart => hover.is_start(),
|
||||
CursorSense::Hovering => hover.is_on(),
|
||||
CursorSense::HoverEnd => hover.is_end(),
|
||||
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
||||
} {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl ActivationState {
|
||||
pub fn is_start(&self) -> bool {
|
||||
*self == Self::Start
|
||||
}
|
||||
pub fn is_on(&self) -> bool {
|
||||
*self == Self::Start || *self == Self::On
|
||||
}
|
||||
pub fn is_end(&self) -> bool {
|
||||
*self == Self::End
|
||||
}
|
||||
pub fn is_off(&self) -> bool {
|
||||
*self == Self::End || *self == Self::Off
|
||||
}
|
||||
pub fn update(&mut self, on: bool) {
|
||||
*self = match *self {
|
||||
Self::Start => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::On => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::End => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
Self::Off => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
match self {
|
||||
Self::Start => *self = Self::On,
|
||||
Self::End => *self = Self::Off,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Event for CursorSenses {
|
||||
type Module<Ctx: 'static> = CursorModule<Ctx>;
|
||||
type Data = CursorData;
|
||||
}
|
||||
|
||||
impl Event for CursorSense {
|
||||
type Module<Ctx: 'static> = CursorModule<Ctx>;
|
||||
type Data = CursorData;
|
||||
}
|
||||
|
||||
impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static> EventModule<E, Ctx>
|
||||
for CursorModule<Ctx>
|
||||
{
|
||||
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
|
||||
// TODO: does not add to active if currently active
|
||||
self.map.entry(id).or_default().sensors.push(Sensor {
|
||||
senses: senses.into(),
|
||||
f: Rc::new(f),
|
||||
});
|
||||
}
|
||||
|
||||
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
|
||||
let senses = event.into();
|
||||
if let Some(group) = self.map.get(id) {
|
||||
let fs: Vec<_> = group
|
||||
.sensors
|
||||
.iter()
|
||||
.filter_map(|sensor| {
|
||||
if sensor.senses.iter().any(|s| senses.contains(s)) {
|
||||
Some(sensor.f.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(move |ctx: &mut Ctx, data: CursorData| {
|
||||
for f in &fs {
|
||||
f(ctx, data.clone());
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx, Data> Default for SensorGroup<Ctx, Data> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hover: Default::default(),
|
||||
sensors: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CursorSenses {
|
||||
type Target = Vec<CursorSense>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for CursorSenses {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorSense> for CursorSenses {
|
||||
fn from(val: CursorSense) -> Self {
|
||||
CursorSenses(vec![val])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for CursorSense {
|
||||
type Output = CursorSenses;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
CursorSenses(vec![self, rhs])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<CursorSense> for CursorSenses {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
|
||||
self.0.push(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx> Default for CursorModule<Ctx> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: Default::default(),
|
||||
active: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
use std::marker::{PhantomData, Sized};
|
||||
|
||||
use crate::prelude::*;
|
||||
use cosmic_text::{Attrs, Family, Metrics, Shaping};
|
||||
|
||||
pub trait TextBuilderOutput: Sized {
|
||||
type Output;
|
||||
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output;
|
||||
}
|
||||
|
||||
pub struct TextBuilder<O = TextOutput> {
|
||||
pub content: String,
|
||||
pub attrs: TextAttrs,
|
||||
_pd: PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<O> TextBuilder<O> {
|
||||
pub fn size(mut self, size: impl UiNum) -> Self {
|
||||
self.attrs.font_size = size.to_f32();
|
||||
self.attrs.line_height = self.attrs.font_size * 1.1;
|
||||
self
|
||||
}
|
||||
pub fn color(mut self, color: UiColor) -> Self {
|
||||
self.attrs.color = color;
|
||||
self
|
||||
}
|
||||
pub fn family(mut self, family: Family<'static>) -> Self {
|
||||
self.attrs.family = family;
|
||||
self
|
||||
}
|
||||
pub fn line_height(mut self, height: f32) -> Self {
|
||||
self.attrs.line_height = height;
|
||||
self
|
||||
}
|
||||
pub fn text_align(mut self, align: Align) -> Self {
|
||||
self.attrs.align = align;
|
||||
self
|
||||
}
|
||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||
self.attrs.wrap = wrap;
|
||||
self
|
||||
}
|
||||
pub fn editable(self) -> TextBuilder<TextEditOutput> {
|
||||
TextBuilder {
|
||||
content: self.content,
|
||||
attrs: self.attrs,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextOutput;
|
||||
impl TextBuilderOutput for TextOutput {
|
||||
type Output = Text;
|
||||
|
||||
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
|
||||
let mut buf = TextBuffer::new_empty(Metrics::new(
|
||||
builder.attrs.font_size,
|
||||
builder.attrs.line_height,
|
||||
));
|
||||
let font_system = &mut ui.data.text.font_system;
|
||||
buf.set_text(
|
||||
font_system,
|
||||
&builder.content,
|
||||
&Attrs::new(),
|
||||
Shaping::Advanced,
|
||||
None,
|
||||
);
|
||||
let mut text = Text {
|
||||
content: builder.content.into(),
|
||||
view: TextView::new(buf, builder.attrs),
|
||||
};
|
||||
text.content.changed = false;
|
||||
builder.attrs.apply(font_system, &mut text.view.buf, None);
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextEditOutput;
|
||||
impl TextBuilderOutput for TextEditOutput {
|
||||
type Output = TextEdit;
|
||||
|
||||
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
|
||||
let buf = TextBuffer::new_empty(Metrics::new(
|
||||
builder.attrs.font_size,
|
||||
builder.attrs.line_height,
|
||||
));
|
||||
let mut text = TextEdit {
|
||||
view: TextView::new(buf, builder.attrs),
|
||||
cursor: None,
|
||||
};
|
||||
let font_system = &mut ui.data.text.font_system;
|
||||
text.buf.set_text(
|
||||
font_system,
|
||||
&builder.content,
|
||||
&Attrs::new(),
|
||||
Shaping::Advanced,
|
||||
None,
|
||||
);
|
||||
builder.attrs.apply(font_system, &mut text.buf, None);
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: TextBuilderOutput> FnOnce<(&mut Ui,)> for TextBuilder<O> {
|
||||
type Output = O::Output;
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
||||
O::run(args.0, self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(content: impl Into<String>) -> TextBuilder {
|
||||
TextBuilder {
|
||||
content: content.into(),
|
||||
attrs: TextAttrs::default(),
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::prelude::*;
|
||||
use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, Motion, Shaping};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use winit::{
|
||||
event::KeyEvent,
|
||||
keyboard::{Key, NamedKey},
|
||||
};
|
||||
|
||||
pub struct TextEdit {
|
||||
pub(super) view: TextView,
|
||||
pub(super) cursor: Option<Cursor>,
|
||||
}
|
||||
|
||||
impl TextEdit {
|
||||
pub fn region(&self) -> UiRegion {
|
||||
UiRegion::from_size_align(
|
||||
self.tex().map(|t| t.size()).unwrap_or(Vec2::ZERO),
|
||||
self.align,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn content(&self) -> String {
|
||||
self.buf
|
||||
.lines
|
||||
.iter()
|
||||
.map(|l| l.text())
|
||||
// why is this needed?? what??
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for TextEdit {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
let tex = self.view.draw(&mut painter.size_ctx());
|
||||
let region = text_region(&tex, self.align);
|
||||
painter.texture_within(&tex.handle, region);
|
||||
|
||||
if let Some(cursor) = &self.cursor
|
||||
&& let Some(offset) = cursor_pos(cursor, &self.buf)
|
||||
{
|
||||
let size = vec2(1, self.attrs.line_height);
|
||||
painter.primitive_within(
|
||||
RectPrimitive::color(Color::WHITE),
|
||||
UiRegion::from_size_align(size, Align::TopLeft)
|
||||
.offset(offset)
|
||||
.within(®ion),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
Size::abs(self.view.draw(ctx).size())
|
||||
}
|
||||
}
|
||||
|
||||
/// copied & modified from fn found in Editor in cosmic_text
|
||||
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> {
|
||||
let mut prev = None;
|
||||
for run in buf
|
||||
.layout_runs()
|
||||
.skip_while(|r| r.line_i < cursor.line)
|
||||
.take_while(|r| r.line_i == cursor.line)
|
||||
{
|
||||
prev = Some((run.line_w, run.line_top));
|
||||
for glyph in run.glyphs.iter() {
|
||||
if cursor.index == glyph.start {
|
||||
return Some((glyph.x, run.line_top));
|
||||
} else if cursor.index > glyph.start && cursor.index < glyph.end {
|
||||
// Guess x offset based on characters
|
||||
let mut before = 0;
|
||||
let mut total = 0;
|
||||
|
||||
let cluster = &run.text[glyph.start..glyph.end];
|
||||
for (i, _) in cluster.grapheme_indices(true) {
|
||||
if glyph.start + i < cursor.index {
|
||||
before += 1;
|
||||
}
|
||||
total += 1;
|
||||
}
|
||||
|
||||
let offset = glyph.w * (before as f32) / (total as f32);
|
||||
return Some((glyph.x + offset, run.line_top));
|
||||
}
|
||||
}
|
||||
}
|
||||
prev
|
||||
}
|
||||
|
||||
pub struct TextEditCtx<'a> {
|
||||
pub text: &'a mut TextEdit,
|
||||
pub font_system: &'a mut FontSystem,
|
||||
}
|
||||
|
||||
impl<'a> TextEditCtx<'a> {
|
||||
pub fn take(&mut self) -> String {
|
||||
let text = self
|
||||
.text
|
||||
.buf
|
||||
.lines
|
||||
.drain(..)
|
||||
.map(|l| l.into_text())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
self.text
|
||||
.buf
|
||||
.set_text(self.font_system, "", &Attrs::new(), Shaping::Advanced, None);
|
||||
if let Some(cursor) = &mut self.text.cursor {
|
||||
cursor.line = 0;
|
||||
cursor.index = 0;
|
||||
cursor.affinity = Affinity::default();
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
pub fn motion(&mut self, motion: Motion) {
|
||||
if let Some(cursor) = self.text.cursor
|
||||
&& let Some((cursor, _)) =
|
||||
self.text
|
||||
.buf
|
||||
.cursor_motion(self.font_system, cursor, None, motion)
|
||||
{
|
||||
self.text.cursor = Some(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, text: &str) {
|
||||
let mut lines = text.split('\n');
|
||||
let Some(first) = lines.next() else {
|
||||
return;
|
||||
};
|
||||
self.insert_inner(first);
|
||||
for line in lines {
|
||||
self.newline();
|
||||
self.insert_inner(line);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_inner(&mut self, text: &str) {
|
||||
if let Some(cursor) = &mut self.text.cursor {
|
||||
let line = &mut self.text.view.buf.lines[cursor.line];
|
||||
let mut line_text = line.text().to_string();
|
||||
line_text.insert_str(cursor.index, text);
|
||||
line.set_text(line_text, line.ending(), line.attrs_list().clone());
|
||||
for _ in 0..text.len() {
|
||||
self.motion(Motion::Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newline(&mut self) {
|
||||
if let Some(cursor) = &mut self.text.cursor {
|
||||
let lines = &mut self.text.view.buf.lines;
|
||||
let line = &mut lines[cursor.line];
|
||||
let new = line.split_off(cursor.index);
|
||||
cursor.line += 1;
|
||||
lines.insert(cursor.line, new);
|
||||
cursor.index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backspace(&mut self) {
|
||||
if let Some(cursor) = &mut self.text.cursor
|
||||
&& (cursor.index != 0 || cursor.line != 0)
|
||||
{
|
||||
self.motion(Motion::Left);
|
||||
self.delete();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&mut self) {
|
||||
if let Some(cursor) = &mut self.text.cursor {
|
||||
let lines = &mut self.text.view.buf.lines;
|
||||
let line = &mut lines[cursor.line];
|
||||
if cursor.index == line.text().len() {
|
||||
if cursor.line == lines.len() - 1 {
|
||||
return;
|
||||
}
|
||||
let add = lines.remove(cursor.line + 1).into_text();
|
||||
let line = &mut lines[cursor.line];
|
||||
let mut cur = line.text().to_string();
|
||||
cur.push_str(&add);
|
||||
line.set_text(cur, line.ending(), line.attrs_list().clone());
|
||||
} else {
|
||||
let mut text = line.text().to_string();
|
||||
text.remove(cursor.index);
|
||||
line.set_text(text, line.ending(), line.attrs_list().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select(&mut self, pos: Vec2, size: Vec2) {
|
||||
let pos = pos - self.text.region().top_left.to_abs(size);
|
||||
self.text.cursor = self.text.buf.hit(pos.x, pos.y);
|
||||
}
|
||||
|
||||
pub fn deselect(&mut self) {
|
||||
self.text.cursor = None;
|
||||
}
|
||||
|
||||
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
|
||||
match &event.logical_key {
|
||||
Key::Named(named) => match named {
|
||||
NamedKey::Backspace => self.backspace(),
|
||||
NamedKey::Delete => self.delete(),
|
||||
NamedKey::Space => self.insert(" "),
|
||||
NamedKey::Enter => {
|
||||
if modifiers.shift {
|
||||
self.newline();
|
||||
} else {
|
||||
return TextInputResult::Submit;
|
||||
}
|
||||
}
|
||||
NamedKey::ArrowRight => self.motion(Motion::Right),
|
||||
NamedKey::ArrowLeft => self.motion(Motion::Left),
|
||||
NamedKey::ArrowUp => self.motion(Motion::Up),
|
||||
NamedKey::ArrowDown => self.motion(Motion::Down),
|
||||
NamedKey::Escape => {
|
||||
self.deselect();
|
||||
return TextInputResult::Unfocus;
|
||||
}
|
||||
_ => return TextInputResult::Unused,
|
||||
},
|
||||
Key::Character(text) => {
|
||||
if modifiers.control && text == "v" {
|
||||
return TextInputResult::Paste;
|
||||
} else {
|
||||
self.insert(text)
|
||||
}
|
||||
}
|
||||
_ => return TextInputResult::Unused,
|
||||
}
|
||||
TextInputResult::Used
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Modifiers {
|
||||
pub shift: bool,
|
||||
pub control: bool,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
pub fn clear(&mut self) {
|
||||
self.shift = false;
|
||||
self.control = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TextInputResult {
|
||||
Used,
|
||||
Unused,
|
||||
Unfocus,
|
||||
Submit,
|
||||
Paste,
|
||||
}
|
||||
|
||||
impl TextInputResult {
|
||||
pub fn unfocus(&self) -> bool {
|
||||
matches!(self, TextInputResult::Unfocus)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TextEdit {
|
||||
type Target = TextView;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TextEdit {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.view
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
mod build;
|
||||
mod edit;
|
||||
|
||||
pub use build::*;
|
||||
pub use edit::*;
|
||||
|
||||
use crate::{prelude::*, util::MutDetect};
|
||||
use cosmic_text::{Attrs, Metrics, Shaping};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub struct Text {
|
||||
pub content: MutDetect<String>,
|
||||
view: TextView,
|
||||
}
|
||||
|
||||
pub struct TextView {
|
||||
pub attrs: MutDetect<TextAttrs>,
|
||||
pub buf: MutDetect<TextBuffer>,
|
||||
// cache
|
||||
tex: Option<TextTexture>,
|
||||
width: Option<f32>,
|
||||
}
|
||||
|
||||
impl TextView {
|
||||
pub fn new(buf: TextBuffer, attrs: TextAttrs) -> Self {
|
||||
Self {
|
||||
attrs: attrs.into(),
|
||||
buf: buf.into(),
|
||||
tex: None,
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
pub fn draw(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
||||
let width = if self.attrs.wrap {
|
||||
Some(ctx.px_size().x)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if width == self.width
|
||||
&& let Some(tex) = &self.tex
|
||||
&& !self.attrs.changed
|
||||
&& !self.buf.changed
|
||||
{
|
||||
return tex.clone();
|
||||
}
|
||||
self.width = width;
|
||||
let font_system = &mut ctx.text.font_system;
|
||||
self.attrs.apply(font_system, &mut self.buf, width);
|
||||
self.buf.shape_until_scroll(font_system, false);
|
||||
let tex = ctx.draw_text(&mut self.buf, &self.attrs);
|
||||
self.tex = Some(tex.clone());
|
||||
self.attrs.changed = false;
|
||||
self.buf.changed = false;
|
||||
tex
|
||||
}
|
||||
pub fn tex(&self) -> Option<&TextTexture> {
|
||||
self.tex.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub fn new(content: impl Into<String>) -> Self {
|
||||
let attrs = TextAttrs::default();
|
||||
let buf = TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height));
|
||||
Self {
|
||||
content: content.into().into(),
|
||||
view: TextView::new(buf, attrs),
|
||||
}
|
||||
}
|
||||
fn update_buf(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
||||
if self.content.changed {
|
||||
self.content.changed = false;
|
||||
self.view.buf.set_text(
|
||||
&mut ctx.text.font_system,
|
||||
&self.content,
|
||||
&Attrs::new().family(self.view.attrs.family),
|
||||
Shaping::Advanced,
|
||||
None,
|
||||
);
|
||||
}
|
||||
self.view.draw(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Text {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
let tex = self.update_buf(&mut painter.size_ctx());
|
||||
let region = text_region(&tex, self.align);
|
||||
painter.texture_within(&tex.handle, region);
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size {
|
||||
Size::abs(self.update_buf(ctx).size())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_region(tex: &TextTexture, align: Align) -> UiRegion {
|
||||
let tex_dims = tex.handle.size();
|
||||
let mut region = UiRegion::from_size_align(tex.size(), align);
|
||||
region.top_left.abs += tex.top_left;
|
||||
region.bot_right.abs = region.top_left.abs + tex_dims;
|
||||
region
|
||||
}
|
||||
|
||||
impl Deref for Text {
|
||||
type Target = TextAttrs;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Text {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.view
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TextView {
|
||||
type Target = TextAttrs;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.attrs
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TextView {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.attrs
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub trait CoreWidget<W, Tag> {
|
||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>;
|
||||
fn align(self, align: Align) -> impl WidgetFn<Aligned>;
|
||||
fn center(self) -> impl WidgetFn<Aligned>;
|
||||
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W>;
|
||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized>;
|
||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
|
||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
|
||||
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset>;
|
||||
fn scroll(self) -> impl WidgetIdFn<Offset>;
|
||||
fn masked(self) -> impl WidgetFn<Masked>;
|
||||
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack>;
|
||||
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack>;
|
||||
}
|
||||
|
||||
impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|
||||
|ui| Pad {
|
||||
padding: padding.into(),
|
||||
inner: self.add(ui).any(),
|
||||
}
|
||||
}
|
||||
|
||||
fn align(self, align: Align) -> impl WidgetFn<Aligned> {
|
||||
move |ui| Aligned {
|
||||
inner: self.add(ui).any(),
|
||||
align,
|
||||
}
|
||||
}
|
||||
|
||||
fn center(self) -> impl WidgetFn<Aligned> {
|
||||
self.align(Align::Center)
|
||||
}
|
||||
|
||||
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W::Widget> {
|
||||
|ui| {
|
||||
let id = self.add(ui);
|
||||
ui.set_label(&id, label.into());
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> {
|
||||
let size = size.into();
|
||||
move |ui| Sized {
|
||||
inner: self.add(ui).any(),
|
||||
x: Some(size.x),
|
||||
y: Some(size.y),
|
||||
}
|
||||
}
|
||||
|
||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
||||
let len = len.into();
|
||||
move |ui| Sized {
|
||||
inner: self.add(ui).any(),
|
||||
x: Some(len),
|
||||
y: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
||||
let len = len.into();
|
||||
move |ui| Sized {
|
||||
inner: self.add(ui).any(),
|
||||
x: None,
|
||||
y: Some(len),
|
||||
}
|
||||
}
|
||||
|
||||
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> {
|
||||
move |ui| Offset {
|
||||
inner: self.add(ui).any(),
|
||||
amt: amt.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll(self) -> impl WidgetIdFn<Offset> {
|
||||
self.offset(UiVec2::ZERO)
|
||||
.edit_on(CursorSense::Scroll, |w, data| {
|
||||
w.amt += UiVec2::abs(data.scroll_delta * 50.0);
|
||||
})
|
||||
}
|
||||
|
||||
fn masked(self) -> impl WidgetFn<Masked> {
|
||||
move |ui| Masked {
|
||||
inner: self.add(ui).any(),
|
||||
}
|
||||
}
|
||||
|
||||
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
|
||||
move |ui| Stack {
|
||||
children: vec![w.add(ui).any(), self.add(ui).any()],
|
||||
size: StackSize::Child(1),
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack> {
|
||||
move |ui| Stack {
|
||||
children: vec![self.add(ui).any()],
|
||||
size: StackSize::Child(0),
|
||||
offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
||||
fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag>;
|
||||
fn stack(self) -> StackBuilder<LEN, Wa, Tag>;
|
||||
}
|
||||
|
||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> CoreWidgetArr<LEN, Wa, Tag> for Wa {
|
||||
fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag> {
|
||||
SpanBuilder::new(self, dir)
|
||||
}
|
||||
fn stack(self) -> StackBuilder<LEN, Wa, Tag> {
|
||||
StackBuilder::new(self)
|
||||
}
|
||||
}
|
||||
53
src/default/app.rs
Normal file
53
src/default/app.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
event::WindowEvent,
|
||||
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
pub trait AppState {
|
||||
type Event: 'static;
|
||||
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self;
|
||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
|
||||
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
|
||||
fn exit(&mut self);
|
||||
}
|
||||
|
||||
pub struct App<State: AppState> {
|
||||
state: Option<State>,
|
||||
proxy: EventLoopProxy<State::Event>,
|
||||
}
|
||||
|
||||
impl<State: AppState> App<State> {
|
||||
pub fn run() {
|
||||
let event_loop = EventLoop::with_user_event().build().unwrap();
|
||||
let proxy = event_loop.create_proxy();
|
||||
event_loop
|
||||
.run_app(&mut App::<State> { state: None, proxy })
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: AppState> ApplicationHandler<State::Event> for App<State> {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if self.state.is_none() {
|
||||
let state = State::new(event_loop, self.proxy.clone());
|
||||
self.state = Some(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||
let state = self.state.as_mut().unwrap();
|
||||
state.window_event(event, event_loop);
|
||||
}
|
||||
|
||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: State::Event) {
|
||||
let state = self.state.as_mut().unwrap();
|
||||
state.event(event, event_loop);
|
||||
}
|
||||
|
||||
fn exiting(&mut self, _: &ActiveEventLoop) {
|
||||
let state = self.state.as_mut().unwrap();
|
||||
state.exit();
|
||||
}
|
||||
}
|
||||
66
src/default/attr.rs
Normal file
66
src/default/attr.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::prelude::*;
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
pub struct Selector;
|
||||
|
||||
impl<State: HasEvents, W: Widget + 'static> WidgetAttr<State, W> for Selector
|
||||
where
|
||||
State::State: HasDefaultUiState,
|
||||
{
|
||||
type Input = WidgetRef<TextEdit>;
|
||||
|
||||
fn run(state: &mut State, container: WidgetRef<W>, id: Self::Input) {
|
||||
state.register_event(container, CursorSense::click_or_drag(), move |ctx| {
|
||||
let region = ctx.ui().window_region(&id).unwrap();
|
||||
let id_pos = region.top_left;
|
||||
let container_pos = ctx.state.ui().window_region(&container).unwrap().top_left;
|
||||
let pos = ctx.data.pos + container_pos - id_pos;
|
||||
let size = region.size();
|
||||
select(ctx.state, id, pos, size, ctx.data.sense.is_dragging());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Selectable;
|
||||
|
||||
impl<State: HasEvents> WidgetAttr<State, TextEdit> for Selectable
|
||||
where
|
||||
State::State: HasDefaultUiState,
|
||||
{
|
||||
type Input = ();
|
||||
|
||||
fn run(state: &mut State, id: WidgetRef<TextEdit>, _: Self::Input) {
|
||||
state.register_event(id, CursorSense::click_or_drag(), move |ctx| {
|
||||
select(
|
||||
ctx.state,
|
||||
id,
|
||||
ctx.data.pos,
|
||||
ctx.data.size,
|
||||
ctx.data.sense.is_dragging(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn select(
|
||||
state: &mut impl HasDefaultUiState,
|
||||
id: WidgetRef<TextEdit>,
|
||||
pos: Vec2,
|
||||
size: Vec2,
|
||||
dragging: bool,
|
||||
) {
|
||||
let (ui, state) = HasDefaultUiState::ui_with_state(state);
|
||||
let now = Instant::now();
|
||||
let recent = (now - state.last_click) < Duration::from_millis(300);
|
||||
state.last_click = now;
|
||||
id.edit(ui).select(pos, size, dragging, recent);
|
||||
if let Some(region) = ui.window_region(&id) {
|
||||
state.window.set_ime_allowed(true);
|
||||
state.window.set_ime_cursor_area(
|
||||
LogicalPosition::<f32>::from(region.top_left.tuple()),
|
||||
LogicalSize::<f32>::from(region.size().tuple()),
|
||||
);
|
||||
}
|
||||
state.focus = Some(id);
|
||||
}
|
||||
9
src/default/event.rs
Normal file
9
src/default/event.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use iris_core::Event;
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
pub struct Submit;
|
||||
impl Event for Submit {}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
pub struct Edited;
|
||||
impl Event for Edited {}
|
||||
@@ -1,14 +1,9 @@
|
||||
use iris::{
|
||||
core::{CursorState, Modifiers},
|
||||
layout::Vec2,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use winit::{
|
||||
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
||||
keyboard::{Key, NamedKey},
|
||||
};
|
||||
|
||||
use crate::testing::Client;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Input {
|
||||
cursor: CursorState,
|
||||
@@ -33,10 +28,14 @@ impl Input {
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, .. } => {
|
||||
let delta = match *delta {
|
||||
let mut delta = match *delta {
|
||||
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
|
||||
MouseScrollDelta::PixelDelta(pos) => Vec2::new(pos.x as f32, pos.y as f32),
|
||||
};
|
||||
if delta.x == 0.0 && self.modifiers.shift {
|
||||
delta.x = delta.y;
|
||||
delta.y = 0.0;
|
||||
}
|
||||
self.cursor.scroll_delta = delta;
|
||||
}
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
@@ -67,7 +66,7 @@ impl Input {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
impl DefaultUiState {
|
||||
pub fn window_size(&self) -> Vec2 {
|
||||
let size = self.renderer.window().inner_size();
|
||||
(size.width, size.height).into()
|
||||
186
src/default/mod.rs
Normal file
186
src/default/mod.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use crate::prelude::*;
|
||||
use arboard::Clipboard;
|
||||
use iris_core::util::forget_mut;
|
||||
use std::{marker::Sized, sync::Arc, time::Instant};
|
||||
use winit::{
|
||||
event::{Ime, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||
window::{Window, WindowAttributes},
|
||||
};
|
||||
|
||||
mod app;
|
||||
mod attr;
|
||||
mod event;
|
||||
mod input;
|
||||
mod render;
|
||||
mod sense;
|
||||
|
||||
pub use app::*;
|
||||
pub use attr::*;
|
||||
pub use event::*;
|
||||
pub use input::*;
|
||||
pub use render::*;
|
||||
pub use sense::*;
|
||||
|
||||
pub type Proxy<Event> = EventLoopProxy<Event>;
|
||||
|
||||
pub struct DefaultUiState {
|
||||
pub renderer: UiRenderer,
|
||||
pub input: Input,
|
||||
pub focus: Option<WidgetRef<TextEdit>>,
|
||||
pub clipboard: Clipboard,
|
||||
pub window: Arc<Window>,
|
||||
pub ime: usize,
|
||||
pub last_click: Instant,
|
||||
}
|
||||
|
||||
impl DefaultUiState {
|
||||
pub fn new(window: impl Into<Arc<Window>>) -> Self {
|
||||
let window = window.into();
|
||||
Self {
|
||||
renderer: UiRenderer::new(window.clone()),
|
||||
window,
|
||||
input: Input::default(),
|
||||
clipboard: Clipboard::new().unwrap(),
|
||||
ime: 0,
|
||||
last_click: Instant::now(),
|
||||
focus: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasDefaultUiState: Sized + 'static + HasUi {
|
||||
fn get(&self) -> &DefaultUiState;
|
||||
fn get_mut(&mut self) -> &mut DefaultUiState;
|
||||
fn ui_state(&self) -> &DefaultUiState {
|
||||
HasDefaultUiState::get(self)
|
||||
}
|
||||
fn ui_state_mut(&mut self) -> &mut DefaultUiState {
|
||||
HasDefaultUiState::get_mut(self)
|
||||
}
|
||||
fn ui_with_state(&mut self) -> (&mut Ui, &mut DefaultUiState) {
|
||||
// as long as you're not doing anything actually unhinged this should always work safely
|
||||
(
|
||||
unsafe { forget_mut(self.ui_mut()) },
|
||||
HasDefaultUiState::get_mut(self),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DefaultAppState: RunEvents + HasDefaultUiState {
|
||||
type Event: 'static = ();
|
||||
fn new(ui_state: DefaultUiState, proxy: Proxy<Self::Event>) -> Self;
|
||||
#[allow(unused_variables)]
|
||||
fn event(&mut self, event: Self::Event) {}
|
||||
#[allow(unused_variables)]
|
||||
fn exit(&mut self) {}
|
||||
#[allow(unused_variables)]
|
||||
fn window_event(&mut self, event: WindowEvent) {}
|
||||
fn window_attributes() -> WindowAttributes {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: DefaultAppState> AppState for State {
|
||||
type Event = State::Event;
|
||||
|
||||
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
|
||||
let window = event_loop.create_window(Self::window_attributes()).unwrap();
|
||||
Self::new(DefaultUiState::new(window), proxy)
|
||||
}
|
||||
|
||||
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
|
||||
self.event(event);
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||
let events = unsafe { forget_mut(self.events_mut()) };
|
||||
let ui_state = HasDefaultUiState::get_mut(self);
|
||||
let input_changed = ui_state.input.event(&event);
|
||||
let cursor_state = ui_state.cursor_state().clone();
|
||||
let old = ui_state.focus;
|
||||
if cursor_state.buttons.left.is_start() {
|
||||
ui_state.focus = None;
|
||||
}
|
||||
if input_changed {
|
||||
let window_size = ui_state.window_size();
|
||||
self.run_sensors(&cursor_state, window_size);
|
||||
}
|
||||
let (mut ui, mut ui_state) = self.ui_with_state();
|
||||
if old != ui_state.focus
|
||||
&& let Some(old) = old
|
||||
{
|
||||
old.edit(ui).deselect();
|
||||
}
|
||||
match &event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
ui.update(events);
|
||||
ui_state.renderer.update(ui);
|
||||
ui_state.renderer.draw();
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
ui.resize((size.width, size.height));
|
||||
ui_state.renderer.resize(size)
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
if let Some(sel) = ui_state.focus
|
||||
&& event.state.is_pressed()
|
||||
{
|
||||
let mut text = sel.edit(ui);
|
||||
match text.apply_event(event, &ui_state.input.modifiers) {
|
||||
TextInputResult::Unfocus => {
|
||||
ui_state.focus = None;
|
||||
ui_state.window.set_ime_allowed(false);
|
||||
}
|
||||
TextInputResult::Submit => {
|
||||
self.run_event::<Submit>(sel, &mut ());
|
||||
}
|
||||
TextInputResult::Paste => {
|
||||
if let Ok(t) = ui_state.clipboard.get_text() {
|
||||
text.insert(&t);
|
||||
}
|
||||
self.run_event::<Edited>(sel, &mut ());
|
||||
}
|
||||
TextInputResult::Copy(text) => {
|
||||
if let Err(err) = ui_state.clipboard.set_text(text) {
|
||||
eprintln!("failed to copy text to clipboard: {err}")
|
||||
}
|
||||
}
|
||||
TextInputResult::Used => {
|
||||
self.run_event::<Edited>(sel, &mut ());
|
||||
}
|
||||
TextInputResult::Unused => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::Ime(ime) => {
|
||||
if let Some(sel) = ui_state.focus {
|
||||
let mut text = sel.edit(ui);
|
||||
match ime {
|
||||
Ime::Enabled | Ime::Disabled => (),
|
||||
Ime::Preedit(content, _pos) => {
|
||||
// TODO: highlight once that's real
|
||||
text.replace(ui_state.ime, content);
|
||||
ui_state.ime = content.chars().count();
|
||||
}
|
||||
Ime::Commit(content) => {
|
||||
text.insert(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
self.window_event(event);
|
||||
(ui, ui_state) = self.ui_with_state();
|
||||
if ui.needs_redraw() {
|
||||
ui_state.renderer.window().request_redraw();
|
||||
}
|
||||
ui_state.input.end_frame();
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.exit();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
use iris_core::{Ui, UiLimits, UiRenderNode};
|
||||
use pollster::FutureExt;
|
||||
use std::sync::Arc;
|
||||
use iris::{
|
||||
layout::Ui,
|
||||
render::{UiLimits, UiRenderer},
|
||||
};
|
||||
use wgpu::{util::StagingBelt, *};
|
||||
use winit::{dpi::PhysicalSize, window::Window};
|
||||
|
||||
pub const CLEAR_COLOR: Color = Color::BLACK;
|
||||
|
||||
pub struct Renderer {
|
||||
pub struct UiRenderer {
|
||||
window: Arc<Window>,
|
||||
surface: Surface<'static>,
|
||||
device: Device,
|
||||
@@ -17,12 +14,12 @@ pub struct Renderer {
|
||||
config: SurfaceConfiguration,
|
||||
encoder: CommandEncoder,
|
||||
staging_belt: StagingBelt,
|
||||
pub ui: UiRenderer,
|
||||
pub ui: UiRenderNode,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn update(&mut self, updates: &mut Ui) {
|
||||
self.ui.update(&self.device, &self.queue, updates);
|
||||
impl UiRenderer {
|
||||
pub fn update(&mut self, ui: &mut Ui) {
|
||||
self.ui.update(&self.device, &self.queue, ui);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) {
|
||||
@@ -100,6 +97,7 @@ impl Renderer {
|
||||
.max_binding_array_elements_per_shader_stage(),
|
||||
max_binding_array_sampler_elements_per_shader_stage: ui_limits
|
||||
.max_binding_array_sampler_elements_per_shader_stage(),
|
||||
max_buffer_size: 1 << 30,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -120,7 +118,7 @@ impl Renderer {
|
||||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: PresentMode::AutoVsync,
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
desired_maximum_frame_latency: 2,
|
||||
view_formats: vec![],
|
||||
@@ -131,7 +129,7 @@ impl Renderer {
|
||||
let staging_belt = StagingBelt::new(4096 * 4);
|
||||
let encoder = Self::create_encoder(&device);
|
||||
|
||||
let shape_pipeline = UiRenderer::new(&device, &queue, &config, ui_limits);
|
||||
let shape_pipeline = UiRenderNode::new(&device, &queue, &config, ui_limits);
|
||||
|
||||
Self {
|
||||
surface,
|
||||
288
src/default/sense.rs
Normal file
288
src/default/sense.rs
Normal file
@@ -0,0 +1,288 @@
|
||||
use crate::prelude::*;
|
||||
use std::{
|
||||
ops::{BitOr, Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum CursorButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum CursorSense {
|
||||
PressStart(CursorButton),
|
||||
Pressing(CursorButton),
|
||||
PressEnd(CursorButton),
|
||||
HoverStart,
|
||||
Hovering,
|
||||
HoverEnd,
|
||||
Scroll,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CursorSenses(Vec<CursorSense>);
|
||||
|
||||
impl Event for CursorSenses {
|
||||
type Data<'a> = CursorData<'a>;
|
||||
type State = SensorState;
|
||||
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
|
||||
if let Some(sense) = should_run(self, data.cursor, data.hover) {
|
||||
data.sense = sense;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorSense {
|
||||
pub fn click() -> Self {
|
||||
Self::PressStart(CursorButton::Left)
|
||||
}
|
||||
pub fn click_or_drag() -> CursorSenses {
|
||||
Self::click() | Self::Pressing(CursorButton::Left)
|
||||
}
|
||||
pub fn unclick() -> Self {
|
||||
Self::PressEnd(CursorButton::Left)
|
||||
}
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorState {
|
||||
pub pos: Vec2,
|
||||
pub exists: bool,
|
||||
pub buttons: CursorButtons,
|
||||
pub scroll_delta: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorButtons {
|
||||
pub left: ActivationState,
|
||||
pub middle: ActivationState,
|
||||
pub right: ActivationState,
|
||||
}
|
||||
|
||||
impl CursorButtons {
|
||||
pub fn select(&self, button: &CursorButton) -> &ActivationState {
|
||||
match button {
|
||||
CursorButton::Left => &self.left,
|
||||
CursorButton::Right => &self.right,
|
||||
CursorButton::Middle => &self.middle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.left.end_frame();
|
||||
self.middle.end_frame();
|
||||
self.right.end_frame();
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (CursorButton, &ActivationState)> {
|
||||
[
|
||||
CursorButton::Left,
|
||||
CursorButton::Middle,
|
||||
CursorButton::Right,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|b| (b, self.select(&b)))
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorState {
|
||||
pub fn end_frame(&mut self) {
|
||||
self.buttons.end_frame();
|
||||
self.scroll_delta = Vec2::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
pub enum ActivationState {
|
||||
Start,
|
||||
On,
|
||||
End,
|
||||
#[default]
|
||||
Off,
|
||||
}
|
||||
|
||||
/// this and other similar stuff has a generic
|
||||
/// because I kind of want to make CursorModule generic
|
||||
/// or basically have some way to have custom senses
|
||||
/// that depend on active widget positions
|
||||
/// but I'm not sure how or if worth it
|
||||
pub struct Sensor<Ctx, Data> {
|
||||
pub senses: CursorSenses,
|
||||
pub f: Rc<dyn EventFn<Ctx, Data>>,
|
||||
}
|
||||
|
||||
pub type SenseShape = UiRegion;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SensorState {
|
||||
pub hover: ActivationState,
|
||||
}
|
||||
|
||||
pub struct CursorData<'a> {
|
||||
/// where this widget was hit
|
||||
pub pos: Vec2,
|
||||
pub size: Vec2,
|
||||
pub scroll_delta: Vec2,
|
||||
pub hover: ActivationState,
|
||||
pub cursor: &'a CursorState,
|
||||
/// the first sense that triggered this
|
||||
pub sense: CursorSense,
|
||||
}
|
||||
|
||||
pub trait SensorUi<State> {
|
||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
|
||||
}
|
||||
|
||||
impl<State: RunEvents> SensorUi<State> for State {
|
||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) {
|
||||
let layers = std::mem::take(&mut self.ui_mut().layers);
|
||||
let mut active = std::mem::take(&mut self.events_mut().get_type::<CursorSense>().active);
|
||||
for layer in layers.indices().rev() {
|
||||
let mut sensed = false;
|
||||
for (id, sensor) in active.get_mut(&layer).into_iter().flatten() {
|
||||
let shape = self.ui().active.get(id).unwrap().region;
|
||||
let region = shape.to_px(window_size);
|
||||
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||
sensor.hover.update(in_shape);
|
||||
if sensor.hover == ActivationState::Off {
|
||||
continue;
|
||||
}
|
||||
sensed = true;
|
||||
|
||||
let mut data = CursorData {
|
||||
pos: cursor.pos - region.top_left,
|
||||
size: region.bot_right - region.top_left,
|
||||
scroll_delta: cursor.scroll_delta,
|
||||
hover: sensor.hover,
|
||||
cursor,
|
||||
// this does not have any meaning;
|
||||
// might wanna set up Event to have a prepare stage
|
||||
sense: CursorSense::Hovering,
|
||||
};
|
||||
self.run_event::<CursorSense>(*id, &mut data);
|
||||
}
|
||||
if sensed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.events_mut().get_type::<CursorSense>().active = active;
|
||||
self.ui_mut().layers = layers;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_run(
|
||||
senses: &CursorSenses,
|
||||
cursor: &CursorState,
|
||||
hover: ActivationState,
|
||||
) -> Option<CursorSense> {
|
||||
for sense in senses.iter() {
|
||||
if match sense {
|
||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||
CursorSense::PressEnd(button) => cursor.buttons.select(button).is_end(),
|
||||
CursorSense::HoverStart => hover.is_start(),
|
||||
CursorSense::Hovering => hover.is_on(),
|
||||
CursorSense::HoverEnd => hover.is_end(),
|
||||
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
||||
} {
|
||||
return Some(*sense);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl ActivationState {
|
||||
pub fn is_start(&self) -> bool {
|
||||
*self == Self::Start
|
||||
}
|
||||
pub fn is_on(&self) -> bool {
|
||||
*self == Self::Start || *self == Self::On
|
||||
}
|
||||
pub fn is_end(&self) -> bool {
|
||||
*self == Self::End
|
||||
}
|
||||
pub fn is_off(&self) -> bool {
|
||||
*self == Self::End || *self == Self::Off
|
||||
}
|
||||
pub fn update(&mut self, on: bool) {
|
||||
*self = match *self {
|
||||
Self::Start => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::On => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::End => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
Self::Off => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
match self {
|
||||
Self::Start => *self = Self::On,
|
||||
Self::End => *self = Self::Off,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLike for CursorSense {
|
||||
type Event = CursorSenses;
|
||||
fn into_event(self) -> Self::Event {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CursorSenses {
|
||||
type Target = Vec<CursorSense>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for CursorSenses {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorSense> for CursorSenses {
|
||||
fn from(val: CursorSense) -> Self {
|
||||
CursorSenses(vec![val])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for CursorSense {
|
||||
type Output = CursorSenses;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
CursorSenses(vec![self, rhs])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<CursorSense> for CursorSenses {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
|
||||
self.0.push(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
25
src/event.rs
Normal file
25
src/event.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub mod eventable {
|
||||
use super::*;
|
||||
widget_trait! {
|
||||
pub trait Eventable<State: HasEvents + StateLike<State> + 'static>;
|
||||
fn on<E: EventLike>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl for<'a> WidgetEventFn<State::State, <E::Event as Event>::Data<'a>, WL::Widget>,
|
||||
) -> impl WidgetIdFn<State, WL::Widget> {
|
||||
move |state| {
|
||||
let id = self.add(state);
|
||||
state.register_event(id, event.into_event(), move |ctx| {
|
||||
f(&mut EventIdCtx {
|
||||
widget: id,
|
||||
state: ctx.state,
|
||||
data: ctx.data,
|
||||
});
|
||||
});
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use crate::util::{HashMap, Id};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UiData {
|
||||
map: HashMap<TypeId, Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl UiData {
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.map
|
||||
.get(&TypeId::of::<T>())
|
||||
.map(|d| d.downcast_ref().unwrap())
|
||||
}
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.map
|
||||
.get_mut(&TypeId::of::<T>())
|
||||
.map(|d| d.downcast_mut().unwrap())
|
||||
}
|
||||
|
||||
pub fn emit_remove(&mut self, id: &Id) {
|
||||
for (tid, f) in &mut self.on_remove {
|
||||
let data = self.map.get_mut(tid).unwrap().downcast_ref().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
use std::{hash::Hash, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
layout::{IdFnTag, Ui, UiModule, Widget, WidgetId, WidgetIdFn, WidgetLike},
|
||||
util::{HashMap, Id},
|
||||
};
|
||||
|
||||
pub trait UiCtx {
|
||||
fn ui(&mut self) -> &mut Ui;
|
||||
}
|
||||
|
||||
impl UiCtx for Ui {
|
||||
fn ui(&mut self) -> &mut Ui {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Event: Sized {
|
||||
type Module<Ctx: 'static>: EventModule<Self, Ctx>;
|
||||
type Data: Clone;
|
||||
}
|
||||
|
||||
pub trait EventFn<Ctx, Data>: Fn(&mut Ctx, Data) + 'static {}
|
||||
impl<F: Fn(&mut Ctx, Data) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
|
||||
|
||||
pub trait Eventable<W, Tag> {
|
||||
fn on<E: Event, Ctx: 'static>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl EventFn<Ctx, E::Data>,
|
||||
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>;
|
||||
|
||||
fn id_on<E: Event, Ctx: 'static>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl Fn(&WidgetId<W>, &mut Ctx, E::Data) + 'static,
|
||||
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
|
||||
where
|
||||
W: Widget;
|
||||
|
||||
fn edit_on<E: Event>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl Fn(&mut W, E::Data) + 'static,
|
||||
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
|
||||
where
|
||||
W: Widget;
|
||||
}
|
||||
|
||||
impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
|
||||
fn on<E: Event, Ctx: 'static>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl EventFn<Ctx, E::Data>,
|
||||
) -> impl WidgetIdFn<W::Widget> {
|
||||
move |ui| {
|
||||
let id = self.add(ui);
|
||||
ui.data
|
||||
.modules
|
||||
.get_mut::<E::Module<Ctx>>()
|
||||
.register(id.id, event, f);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
fn id_on<E: Event, Ctx: 'static>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl Fn(&WidgetId<W::Widget>, &mut Ctx, E::Data) + 'static,
|
||||
) -> impl WidgetIdFn<W::Widget>
|
||||
where
|
||||
W::Widget: Widget,
|
||||
{
|
||||
self.with_id(move |ui, id| {
|
||||
let id2 = id.clone();
|
||||
id.on(event, move |ctx, pos| f(&id2, ctx, pos)).add(ui)
|
||||
})
|
||||
}
|
||||
|
||||
fn edit_on<E: Event>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl Fn(&mut W::Widget, E::Data) + 'static,
|
||||
) -> impl WidgetIdFn<W::Widget>
|
||||
where
|
||||
W::Widget: Widget,
|
||||
{
|
||||
self.id_on(event, move |id, ui: &mut Ui, pos| f(&mut ui[id], pos))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DefaultEvent: Hash + Eq + 'static {
|
||||
type Data: Clone;
|
||||
}
|
||||
|
||||
impl<E: DefaultEvent> Event for E {
|
||||
type Module<Ctx: 'static> = DefaultEventModule<E, Ctx>;
|
||||
type Data = E::Data;
|
||||
}
|
||||
|
||||
pub trait EventModule<E: Event, Ctx>: UiModule + Default {
|
||||
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, E::Data>);
|
||||
fn run<'a>(
|
||||
&self,
|
||||
id: &Id,
|
||||
event: E,
|
||||
) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, Self, E, Ctx>>;
|
||||
}
|
||||
|
||||
type EventFnMap<Ctx, Data> = HashMap<Id, Vec<Rc<dyn EventFn<Ctx, Data>>>>;
|
||||
pub struct DefaultEventModule<E: Event, Ctx> {
|
||||
map: HashMap<E, EventFnMap<Ctx, <E as Event>::Data>>,
|
||||
}
|
||||
|
||||
impl<E: Event + 'static, Ctx: 'static> UiModule for DefaultEventModule<E, Ctx> {
|
||||
fn on_remove(&mut self, id: &Id) {
|
||||
for map in self.map.values_mut() {
|
||||
map.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HashableEvent: Event + Hash + Eq + 'static {}
|
||||
impl<E: Event + Hash + Eq + 'static> HashableEvent for E {}
|
||||
|
||||
impl<E: HashableEvent, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<E, Ctx> {
|
||||
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
|
||||
self.map
|
||||
.entry(event)
|
||||
.or_default()
|
||||
.entry(id)
|
||||
.or_default()
|
||||
.push(Rc::new(f));
|
||||
}
|
||||
|
||||
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
|
||||
if let Some(map) = self.map.get(&event)
|
||||
&& let Some(fs) = map.get(id)
|
||||
{
|
||||
let fs = fs.clone();
|
||||
Some(move |ctx: &mut Ctx, data: E::Data| {
|
||||
for f in &fs {
|
||||
f(ctx, data.clone())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: HashableEvent, Ctx: 'static> DefaultEventModule<E, Ctx> {
|
||||
pub fn run_all(&self, ctx: &mut Ctx, event: E, data: E::Data)
|
||||
where
|
||||
E::Data: Clone,
|
||||
{
|
||||
if let Some(map) = self.map.get(&event) {
|
||||
for fs in map.values() {
|
||||
for f in fs {
|
||||
f(ctx, data.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn run_event<E: Event, Ctx: UiCtx + 'static, W>(
|
||||
ctx: &mut Ctx,
|
||||
id: &WidgetId<W>,
|
||||
event: E,
|
||||
data: E::Data,
|
||||
) {
|
||||
if let Some(f) = ctx
|
||||
.ui()
|
||||
.data
|
||||
.modules
|
||||
.get_mut::<E::Module<Ctx>>()
|
||||
.run(&id.id, event)
|
||||
{
|
||||
f(ctx, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventCtx: UiCtx {
|
||||
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data);
|
||||
}
|
||||
|
||||
impl<Ctx: UiCtx + 'static> EventCtx for Ctx {
|
||||
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data) {
|
||||
Ui::run_event(self, id, event.clone(), data.clone());
|
||||
}
|
||||
}
|
||||
250
src/layout/id.rs
250
src/layout/id.rs
@@ -1,250 +0,0 @@
|
||||
use std::{
|
||||
any::TypeId,
|
||||
marker::PhantomData,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::Sender,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
layout::{FnTag, Ui, Widget, WidgetLike, WidgetTag},
|
||||
util::{Id, RefCounter},
|
||||
};
|
||||
|
||||
pub struct AnyWidget;
|
||||
|
||||
/// An identifier for a widget that can index a UI to get the associated widget.
|
||||
/// It should always remain valid; it keeps a ref count and removes the widget from the UI if all
|
||||
/// references are dropped.
|
||||
///
|
||||
/// W does not need to implement widget so that AnyWidget is valid;
|
||||
/// Instead, add generic bounds on methods that take an ID if they need specific data.
|
||||
///
|
||||
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
|
||||
#[repr(C)]
|
||||
pub struct WidgetId<W = AnyWidget> {
|
||||
pub(super) ty: TypeId,
|
||||
pub(super) id: Id,
|
||||
counter: RefCounter,
|
||||
send: Sender<Id>,
|
||||
is_static: Arc<AtomicBool>,
|
||||
_pd: PhantomData<W>,
|
||||
}
|
||||
|
||||
/// A WidgetId for a static widget that cannot be removed from a Ui.
|
||||
/// Useful because ergonomic clones don't exist yet so you can easily use these in closures.
|
||||
/// Do not use this if you want the widget to be freeable.
|
||||
///
|
||||
/// This is currently not perfectly efficient and just creates new WidgetIds every time it's used,
|
||||
/// but they don't send drop messages to Ui.
|
||||
/// Ideally I'd have an enum or something that lets you use either, but that doesn't seem worth it
|
||||
/// right now; it's good enough and relatively cheap.
|
||||
#[repr(C)]
|
||||
pub struct StaticWidgetId<W = AnyWidget> {
|
||||
pub(super) ty: TypeId,
|
||||
pub(super) id: Id,
|
||||
_pd: PhantomData<W>,
|
||||
}
|
||||
|
||||
impl<W> std::fmt::Debug for WidgetId<W> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.id.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Clone for WidgetId<W> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
ty: self.ty,
|
||||
counter: self.counter.clone(),
|
||||
send: self.send.clone(),
|
||||
is_static: self.is_static.clone(),
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> WidgetId<W> {
|
||||
pub(super) fn new(id: Id, ty: TypeId, send: Sender<Id>, is_static: bool) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
id,
|
||||
counter: RefCounter::new(),
|
||||
send,
|
||||
is_static: Arc::new(is_static.into()),
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn any(self) -> WidgetId<AnyWidget> {
|
||||
self.cast_type()
|
||||
}
|
||||
|
||||
pub fn as_any(&self) -> &WidgetId<AnyWidget> {
|
||||
// SAFETY: self is repr(C) and generic only used for phantom data
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
|
||||
pub fn key(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub(super) fn cast_type<W2>(self) -> WidgetId<W2> {
|
||||
// SAFETY: self is repr(C) and generic only used for phantom data
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
|
||||
pub fn refs(&self) -> u32 {
|
||||
self.counter.refs()
|
||||
}
|
||||
|
||||
pub fn into_static(self) -> StaticWidgetId<W> {
|
||||
self.is_static.store(true, Ordering::Release);
|
||||
StaticWidgetId {
|
||||
ty: self.ty,
|
||||
id: self.id,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetId {
|
||||
pub fn set_static<W>(&mut self, other: StaticWidgetId<W>) {
|
||||
let send = self.send.clone();
|
||||
drop(std::mem::replace(
|
||||
self,
|
||||
Self::new(other.id, self.ty, send, true),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Drop for WidgetId<W> {
|
||||
fn drop(&mut self) {
|
||||
if self.counter.drop() && !self.is_static.load(Ordering::Acquire) {
|
||||
let _ = self.send.send(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdTag;
|
||||
pub struct IdFnTag;
|
||||
|
||||
pub trait WidgetIdFn<W>: FnOnce(&mut Ui) -> WidgetId<W> {}
|
||||
impl<W, F: FnOnce(&mut Ui) -> WidgetId<W>> WidgetIdFn<W> for F {}
|
||||
|
||||
/// TODO: does this ever make sense to use? it allows for invalid ids
|
||||
pub trait Idable<Tag> {
|
||||
type Widget: Widget;
|
||||
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>);
|
||||
fn id(self, id: &WidgetId<Self::Widget>) -> impl WidgetIdFn<Self::Widget>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let id = id.clone();
|
||||
move |ui| {
|
||||
self.set(ui, &id);
|
||||
id
|
||||
}
|
||||
}
|
||||
fn id_static(self, id: StaticWidgetId<Self::Widget>) -> impl WidgetIdFn<Self::Widget>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
move |ui| {
|
||||
let id = id.id(&ui.send);
|
||||
self.set(ui, &id);
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> Idable<WidgetTag> for W {
|
||||
type Widget = W;
|
||||
|
||||
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>) {
|
||||
ui.set(id, self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce(&mut Ui) -> W, W: Widget> Idable<FnTag> for F {
|
||||
type Widget = W;
|
||||
|
||||
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>) {
|
||||
let w = self(ui);
|
||||
ui.set(id, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: 'static> WidgetLike<IdTag> for WidgetId<W> {
|
||||
type Widget = W;
|
||||
fn add(self, _: &mut Ui) -> WidgetId<W> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: 'static, F: FnOnce(&mut Ui) -> WidgetId<W>> WidgetLike<IdFnTag> for F {
|
||||
type Widget = W;
|
||||
fn add(self, ui: &mut Ui) -> WidgetId<W> {
|
||||
self(ui)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> StaticWidgetId<W> {
|
||||
pub fn to_id(&self, send: &Sender<Id>) -> WidgetId<W> {
|
||||
WidgetId::new(self.id, self.ty, send.clone(), true)
|
||||
}
|
||||
pub fn any(self) -> StaticWidgetId<AnyWidget> {
|
||||
// SAFETY: self is repr(C)
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: 'static> WidgetLike<IdTag> for StaticWidgetId<W> {
|
||||
type Widget = W;
|
||||
fn add(self, ui: &mut Ui) -> WidgetId<W> {
|
||||
self.id(&ui.send)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Clone for StaticWidgetId<W> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Copy for StaticWidgetId<W> {}
|
||||
|
||||
pub trait WidgetIdLike<W> {
|
||||
fn id(self, send: &Sender<Id>) -> WidgetId<W>;
|
||||
}
|
||||
|
||||
impl<W> WidgetIdLike<W> for &WidgetId<W> {
|
||||
fn id(self, _: &Sender<Id>) -> WidgetId<W> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> WidgetIdLike<W> for StaticWidgetId<W> {
|
||||
fn id(self, send: &Sender<Id>) -> WidgetId<W> {
|
||||
self.to_id(send)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IdLike<W> {
|
||||
fn id(&self) -> Id;
|
||||
}
|
||||
|
||||
impl<W> IdLike<W> for WidgetId<W> {
|
||||
fn id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> IdLike<W> for StaticWidgetId<W> {
|
||||
fn id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
//! 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) {}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
mod color;
|
||||
mod event;
|
||||
mod id;
|
||||
mod layer;
|
||||
mod module;
|
||||
mod num;
|
||||
mod orientation;
|
||||
mod painter;
|
||||
mod pos;
|
||||
mod text;
|
||||
mod texture;
|
||||
mod ui;
|
||||
mod vec2;
|
||||
mod widget;
|
||||
mod widgets;
|
||||
|
||||
pub use color::*;
|
||||
pub use event::*;
|
||||
pub use id::*;
|
||||
pub use layer::*;
|
||||
pub use module::*;
|
||||
pub use num::*;
|
||||
pub use orientation::*;
|
||||
pub use painter::*;
|
||||
pub use pos::*;
|
||||
pub use text::*;
|
||||
pub use texture::*;
|
||||
pub use ui::*;
|
||||
pub use vec2::*;
|
||||
pub use widget::*;
|
||||
pub use widgets::*;
|
||||
|
||||
pub type UiColor = Color<u8>;
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use crate::{
|
||||
layout::WidgetInstance,
|
||||
util::{HashMap, Id},
|
||||
};
|
||||
|
||||
#[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: &Id) {}
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
pub const trait UiNum {
|
||||
fn to_f32(self) -> f32;
|
||||
}
|
||||
|
||||
impl const UiNum for f32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl const UiNum for u32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl const UiNum for i32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
use crate::{
|
||||
layout::{
|
||||
Layers, Modules, Size, TextAttrs, TextBuffer, TextData, TextTexture, TextureHandle,
|
||||
Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets,
|
||||
},
|
||||
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||
util::{HashMap, HashSet, Id, TrackedArena},
|
||||
};
|
||||
|
||||
pub struct Painter<'a, 'c> {
|
||||
ctx: &'a mut PainterCtx<'c>,
|
||||
region: UiRegion,
|
||||
mask: MaskIdx,
|
||||
textures: Vec<TextureHandle>,
|
||||
primitives: Vec<PrimitiveHandle>,
|
||||
children: Vec<Id>,
|
||||
sized_children: HashMap<Id, Size>,
|
||||
/// whether this widget depends on region's final pixel size or not
|
||||
/// TODO: decide if point (pt) should be used here instead of px
|
||||
pub layer: usize,
|
||||
id: Id,
|
||||
}
|
||||
|
||||
pub struct PainterCtx<'a> {
|
||||
pub widgets: &'a Widgets,
|
||||
pub active: &'a mut HashMap<Id, WidgetInstance>,
|
||||
pub layers: &'a mut Layers,
|
||||
pub textures: &'a mut Textures,
|
||||
pub masks: &'a mut TrackedArena<Mask, u32>,
|
||||
pub text: &'a mut TextData,
|
||||
pub screen_size: Vec2,
|
||||
pub modules: &'a mut Modules,
|
||||
pub px_dependent: &'a mut HashSet<Id>,
|
||||
draw_started: HashSet<Id>,
|
||||
}
|
||||
|
||||
pub struct WidgetInstance {
|
||||
pub id: Id,
|
||||
pub region: UiRegion,
|
||||
pub parent: Option<Id>,
|
||||
pub textures: Vec<TextureHandle>,
|
||||
pub primitives: Vec<PrimitiveHandle>,
|
||||
pub children: Vec<Id>,
|
||||
pub resize: Option<Id>,
|
||||
pub mask: MaskIdx,
|
||||
pub layer: usize,
|
||||
pub desired_size: Size,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PainterData {
|
||||
pub widgets: Widgets,
|
||||
pub active: HashMap<Id, WidgetInstance>,
|
||||
pub layers: Layers,
|
||||
pub textures: Textures,
|
||||
pub text: TextData,
|
||||
pub output_size: Vec2,
|
||||
pub modules: Modules,
|
||||
pub px_dependent: HashSet<Id>,
|
||||
pub masks: TrackedArena<Mask, u32>,
|
||||
}
|
||||
|
||||
impl<'a> PainterCtx<'a> {
|
||||
pub fn new(data: &'a mut PainterData) -> Self {
|
||||
Self {
|
||||
widgets: &data.widgets,
|
||||
active: &mut data.active,
|
||||
layers: &mut data.layers,
|
||||
textures: &mut data.textures,
|
||||
text: &mut data.text,
|
||||
screen_size: data.output_size,
|
||||
modules: &mut data.modules,
|
||||
px_dependent: &mut data.px_dependent,
|
||||
masks: &mut data.masks,
|
||||
draw_started: HashSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self, id: Id) {
|
||||
if self.draw_started.contains(&id) {
|
||||
return;
|
||||
}
|
||||
let Some(active) = self.active.get(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(rid) = active.resize {
|
||||
let desired = SizeCtx {
|
||||
checked: &mut Default::default(),
|
||||
text: self.text,
|
||||
textures: self.textures,
|
||||
widgets: self.widgets,
|
||||
size: UiVec2::FULL_SIZE,
|
||||
screen_size: self.screen_size,
|
||||
px_dependent: &mut Default::default(),
|
||||
id,
|
||||
}
|
||||
.size_inner(id, active.region.size());
|
||||
if active.desired_size != desired {
|
||||
self.redraw(rid);
|
||||
if self.draw_started.contains(&id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(active) = self.remove(id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.draw_inner(
|
||||
active.layer,
|
||||
id,
|
||||
active.region,
|
||||
active.parent,
|
||||
active.mask,
|
||||
Some(active.children),
|
||||
);
|
||||
self.active.get_mut(&id).unwrap().resize = active.resize;
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, id: Id) {
|
||||
self.draw_started.clear();
|
||||
self.layers.clear();
|
||||
self.draw_inner(0, id, UiRegion::full(), None, MaskIdx::NONE, None);
|
||||
}
|
||||
|
||||
fn draw_inner(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
id: Id,
|
||||
region: UiRegion,
|
||||
parent: Option<Id>,
|
||||
mask: MaskIdx,
|
||||
old_children: Option<Vec<Id>>,
|
||||
) {
|
||||
// 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 = None;
|
||||
if let Some(active) = self.active.get_mut(&id) {
|
||||
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;
|
||||
}
|
||||
let active = self.remove(id).unwrap();
|
||||
old_children = active.children;
|
||||
resize = active.resize;
|
||||
}
|
||||
|
||||
self.draw_started.insert(id);
|
||||
|
||||
let desired_size = SizeCtx {
|
||||
text: self.text,
|
||||
textures: self.textures,
|
||||
widgets: self.widgets,
|
||||
checked: &mut Default::default(),
|
||||
screen_size: self.screen_size,
|
||||
px_dependent: &mut Default::default(),
|
||||
id,
|
||||
size: region.size(),
|
||||
}
|
||||
.size_raw(id);
|
||||
|
||||
let mut painter = Painter {
|
||||
region,
|
||||
mask,
|
||||
layer,
|
||||
id,
|
||||
textures: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
ctx: self,
|
||||
children: Vec::new(),
|
||||
sized_children: Default::default(),
|
||||
};
|
||||
|
||||
// draw widgets
|
||||
painter.ctx.widgets.get_dyn_dynamic(id).draw(&mut painter);
|
||||
|
||||
let sized_children = painter.sized_children;
|
||||
|
||||
// add to active
|
||||
let instance = WidgetInstance {
|
||||
id,
|
||||
region,
|
||||
parent,
|
||||
textures: painter.textures,
|
||||
primitives: painter.primitives,
|
||||
children: painter.children,
|
||||
resize,
|
||||
mask: painter.mask,
|
||||
desired_size,
|
||||
layer,
|
||||
};
|
||||
for cid in sized_children.keys() {
|
||||
if let Some(w) = self.active.get_mut(cid)
|
||||
&& w.resize.is_none()
|
||||
{
|
||||
w.resize = Some(id)
|
||||
}
|
||||
}
|
||||
for c in &old_children {
|
||||
if !instance.children.contains(c) {
|
||||
self.remove_rec(*c);
|
||||
}
|
||||
}
|
||||
|
||||
for m in self.modules.iter_mut() {
|
||||
m.on_draw(&instance);
|
||||
}
|
||||
self.active.insert(id, instance);
|
||||
}
|
||||
|
||||
fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
// children will not be changed, so this technically should not be needed
|
||||
// probably need unsafe
|
||||
for h in &active.primitives {
|
||||
let region = self.layers[h.layer].primitives.region_mut(h);
|
||||
*region = region.outside(&from).within(&to);
|
||||
}
|
||||
active.region = active.region.outside(&from).within(&to);
|
||||
for m in self.modules.iter_mut() {
|
||||
m.on_move(active);
|
||||
}
|
||||
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: Id) -> 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();
|
||||
for m in self.modules.iter_mut() {
|
||||
m.on_undraw(inst);
|
||||
}
|
||||
}
|
||||
self.px_dependent.remove(&id);
|
||||
inst
|
||||
}
|
||||
|
||||
fn remove_rec(&mut self, id: Id) -> Option<WidgetInstance> {
|
||||
let inst = self.remove(id);
|
||||
if let Some(inst) = &inst {
|
||||
for c in &inst.children {
|
||||
self.remove_rec(*c);
|
||||
}
|
||||
}
|
||||
inst
|
||||
}
|
||||
}
|
||||
|
||||
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>(&mut self, id: &WidgetId<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>(&mut self, id: &WidgetId<W>, region: UiRegion) {
|
||||
self.widget_at(id, region.within(&self.region));
|
||||
}
|
||||
|
||||
fn widget_at<W>(&mut self, id: &WidgetId<W>, region: UiRegion) {
|
||||
self.children.push(id.id);
|
||||
self.ctx
|
||||
.draw_inner(self.layer, id.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) -> TextTexture {
|
||||
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
|
||||
}
|
||||
|
||||
pub fn region(&self) -> UiRegion {
|
||||
self.region
|
||||
}
|
||||
|
||||
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
|
||||
self.size_ctx().size(id)
|
||||
}
|
||||
|
||||
pub fn region_size<W>(&mut self, id: &WidgetId<W>) -> UiVec2 {
|
||||
self.size_ctx().size(id).to_uivec2(self.region.size().rel)
|
||||
}
|
||||
|
||||
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
|
||||
SizeCtx {
|
||||
text: self.ctx.text,
|
||||
textures: self.ctx.textures,
|
||||
widgets: self.ctx.widgets,
|
||||
checked: &mut self.sized_children,
|
||||
screen_size: self.ctx.screen_size,
|
||||
px_dependent: self.ctx.px_dependent,
|
||||
id: self.id,
|
||||
size: self.region.size(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn px_size(&mut self) -> Vec2 {
|
||||
self.ctx.px_dependent.insert(self.id);
|
||||
self.region.size().to_abs(self.ctx.screen_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 struct SizeCtx<'a> {
|
||||
pub text: &'a mut TextData,
|
||||
pub textures: &'a mut Textures,
|
||||
widgets: &'a Widgets,
|
||||
px_dependent: &'a mut HashSet<Id>,
|
||||
checked: &'a mut HashMap<Id, Size>,
|
||||
/// TODO: should this be pub? rn used for sized
|
||||
pub size: UiVec2,
|
||||
screen_size: Vec2,
|
||||
id: Id,
|
||||
}
|
||||
|
||||
impl SizeCtx<'_> {
|
||||
fn size_inner(&mut self, id: Id, size: UiVec2) -> Size {
|
||||
let self_size = self.size;
|
||||
self.size = size;
|
||||
let size = self.widgets.get_dyn_dynamic(id).desired_size(self);
|
||||
self.size = self_size;
|
||||
self.checked.insert(id, size);
|
||||
size
|
||||
}
|
||||
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
|
||||
if let Some(&size) = self.checked.get(&id.id) {
|
||||
return size;
|
||||
}
|
||||
self.size_inner(id.id, self.size)
|
||||
}
|
||||
fn size_raw(&mut self, id: Id) -> Size {
|
||||
self.size_inner(id, self.size)
|
||||
}
|
||||
pub fn px_size(&mut self) -> Vec2 {
|
||||
self.px_dependent.insert(self.id);
|
||||
self.size.to_abs(self.screen_size)
|
||||
}
|
||||
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
|
||||
self.text.draw(buffer, attrs, self.textures)
|
||||
}
|
||||
pub fn label(&self) -> &String {
|
||||
self.widgets.label(&self.id)
|
||||
}
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
use std::{fmt::Display, marker::Destruct};
|
||||
|
||||
use crate::{
|
||||
layout::{Align, Axis, UiNum, Vec2},
|
||||
util::{LerpUtil, impl_op},
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct UiVec2 {
|
||||
pub rel: Vec2,
|
||||
pub abs: Vec2,
|
||||
}
|
||||
|
||||
impl UiVec2 {
|
||||
pub const ZERO: Self = Self {
|
||||
rel: Vec2::ZERO,
|
||||
abs: Vec2::ZERO,
|
||||
};
|
||||
|
||||
/// expands this position into a sized region centered at self
|
||||
pub fn expand(&self, size: impl Into<Vec2>) -> UiRegion {
|
||||
let size = size.into();
|
||||
UiRegion {
|
||||
top_left: self.offset(-size / 2.0),
|
||||
bot_right: self.offset(size / 2.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn abs(abs: impl const Into<Vec2>) -> Self {
|
||||
Self {
|
||||
rel: Vec2::ZERO,
|
||||
abs: abs.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
|
||||
Self {
|
||||
rel: rel.into(),
|
||||
abs: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn shift(&mut self, offset: impl const Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
*self += offset;
|
||||
}
|
||||
|
||||
pub const fn offset(mut self, offset: impl const Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn within(&self, region: &UiRegion) -> UiVec2 {
|
||||
let rel = self.rel.lerp(region.top_left.rel, region.bot_right.rel);
|
||||
let abs = self.abs + self.rel.lerp(region.top_left.abs, region.bot_right.abs);
|
||||
UiVec2 { rel, abs }
|
||||
}
|
||||
|
||||
pub const fn outside(&self, region: &UiRegion) -> UiVec2 {
|
||||
let rel = self.rel.lerp_inv(region.top_left.rel, region.bot_right.rel);
|
||||
let abs = self.abs - rel.lerp(region.top_left.abs, region.bot_right.abs);
|
||||
UiVec2 { rel, abs }
|
||||
}
|
||||
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> UiScalarView<'_> {
|
||||
match axis {
|
||||
Axis::X => UiScalarView {
|
||||
rel: &mut self.rel.x,
|
||||
abs: &mut self.abs.x,
|
||||
},
|
||||
Axis::Y => UiScalarView {
|
||||
rel: &mut self.rel.y,
|
||||
abs: &mut self.abs.y,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis(&self, axis: Axis) -> UiScalar {
|
||||
match axis {
|
||||
Axis::X => UiScalar {
|
||||
rel: self.rel.x,
|
||||
abs: self.abs.x,
|
||||
},
|
||||
Axis::Y => UiScalar {
|
||||
rel: self.rel.y,
|
||||
abs: self.abs.y,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// reflection about an axis
|
||||
pub fn flip(&mut self, axis: Axis) {
|
||||
*self.rel.axis_mut(axis) = 1.0 - self.rel.axis(axis);
|
||||
*self.abs.axis_mut(axis) = -self.abs.axis(axis);
|
||||
}
|
||||
|
||||
pub fn flipped(mut self, axis: Axis) -> Self {
|
||||
self.flip(axis);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_abs(&self, rel: Vec2) -> Vec2 {
|
||||
self.rel * rel + self.abs
|
||||
}
|
||||
|
||||
pub const FULL_SIZE: Self = Self::rel(Vec2::ONE);
|
||||
|
||||
pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self {
|
||||
Self {
|
||||
rel: Vec2::from_axis(axis, aligned.rel, ortho.rel),
|
||||
abs: Vec2::from_axis(axis, aligned.abs, ortho.abs),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_scalars(x: UiScalar, y: UiScalar) -> Self {
|
||||
Self {
|
||||
rel: Vec2 { x: x.rel, y: y.rel },
|
||||
abs: Vec2 { x: x.abs, y: y.abs },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiVec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "rel {} / abs {}", self.rel, self.abs)
|
||||
}
|
||||
}
|
||||
|
||||
impl_op!(UiVec2 Add add; rel abs);
|
||||
impl_op!(UiVec2 Sub sub; rel abs);
|
||||
|
||||
impl const From<Align> for UiVec2 {
|
||||
fn from(align: Align) -> Self {
|
||||
Self::rel(align.rel())
|
||||
}
|
||||
}
|
||||
|
||||
impl Align {
|
||||
pub fn pos(self) -> UiVec2 {
|
||||
UiVec2::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl const From<Vec2> for UiVec2 {
|
||||
fn from(abs: Vec2) -> Self {
|
||||
Self::abs(abs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for UiVec2
|
||||
where
|
||||
(T, U): const Destruct,
|
||||
{
|
||||
fn from(abs: (T, U)) -> Self {
|
||||
Self::abs(abs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct UiScalar {
|
||||
pub rel: f32,
|
||||
pub abs: f32,
|
||||
}
|
||||
|
||||
impl_op!(UiScalar Add add; rel abs);
|
||||
impl_op!(UiScalar Sub sub; rel abs);
|
||||
|
||||
impl UiScalar {
|
||||
pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 };
|
||||
|
||||
pub fn new(rel: f32, abs: f32) -> Self {
|
||||
Self { rel, abs }
|
||||
}
|
||||
pub fn rel_min() -> Self {
|
||||
Self::new(0.0, 0.0)
|
||||
}
|
||||
|
||||
pub fn rel_max() -> Self {
|
||||
Self::new(1.0, 0.0)
|
||||
}
|
||||
|
||||
pub fn max(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.max(other.rel),
|
||||
abs: self.abs.max(other.abs),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.min(other.rel),
|
||||
abs: self.abs.min(other.abs),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_anchor(anchor: f32) -> Self {
|
||||
Self::new(anchor, 0.0)
|
||||
}
|
||||
|
||||
pub fn offset(mut self, amt: f32) -> Self {
|
||||
self.abs += amt;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn within(&self, start: UiScalar, end: UiScalar) -> Self {
|
||||
let anchor = self.rel.lerp(start.rel, end.rel);
|
||||
let offset = self.abs + self.rel.lerp(start.abs, end.abs);
|
||||
Self {
|
||||
rel: anchor,
|
||||
abs: offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UiRegion {
|
||||
pub top_left: UiVec2,
|
||||
pub bot_right: UiVec2,
|
||||
}
|
||||
|
||||
impl UiRegion {
|
||||
pub const fn full() -> Self {
|
||||
Self {
|
||||
top_left: Align::TopLeft.into(),
|
||||
bot_right: Align::BotRight.into(),
|
||||
}
|
||||
}
|
||||
pub fn rel(anchor: Vec2) -> Self {
|
||||
Self {
|
||||
top_left: UiVec2::rel(anchor),
|
||||
bot_right: UiVec2::rel(anchor),
|
||||
}
|
||||
}
|
||||
pub fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
top_left: self.top_left.within(parent),
|
||||
bot_right: self.bot_right.within(parent),
|
||||
}
|
||||
}
|
||||
pub fn outside(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
top_left: self.top_left.outside(parent),
|
||||
bot_right: self.bot_right.outside(parent),
|
||||
}
|
||||
}
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> {
|
||||
UIRegionAxisView {
|
||||
top_left: self.top_left.axis_mut(axis),
|
||||
bot_right: self.bot_right.axis_mut(axis),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flip(&mut self, axis: Axis) {
|
||||
self.top_left.flip(axis);
|
||||
self.bot_right.flip(axis);
|
||||
let tl = self.top_left.axis_mut(axis);
|
||||
let br = self.bot_right.axis_mut(axis);
|
||||
std::mem::swap(tl.rel, br.rel);
|
||||
std::mem::swap(tl.abs, br.abs);
|
||||
}
|
||||
|
||||
pub fn shift(&mut self, offset: impl Into<UiVec2>) {
|
||||
let offset = offset.into();
|
||||
self.top_left.shift(offset);
|
||||
self.bot_right.shift(offset);
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: impl Into<UiVec2>) -> Self {
|
||||
self.shift(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_screen(&self, size: Vec2) -> ScreenRegion {
|
||||
ScreenRegion {
|
||||
top_left: self.top_left.rel * size + self.top_left.abs,
|
||||
bot_right: self.bot_right.rel * size + self.bot_right.abs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center(&self) -> UiVec2 {
|
||||
Align::Center.pos().within(self)
|
||||
}
|
||||
|
||||
pub fn size(&self) -> UiVec2 {
|
||||
self.bot_right - self.top_left
|
||||
}
|
||||
|
||||
pub fn select_aligned(&self, size: Vec2, align: Align) -> Self {
|
||||
Self::from_size_align(size, align).within(self)
|
||||
}
|
||||
|
||||
pub fn from_size_align(size: Vec2, align: Align) -> Self {
|
||||
let mut top_left = UiVec2::from(align);
|
||||
top_left.abs -= size * align.rel();
|
||||
let mut bot_right = UiVec2::from(align);
|
||||
bot_right.abs += size * (Vec2::ONE - align.rel());
|
||||
Self {
|
||||
top_left,
|
||||
bot_right,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_ui_size_align(size: UiVec2, align: Align) -> Self {
|
||||
let mut top_left = UiVec2::from(align);
|
||||
top_left.abs -= size.abs * align.rel();
|
||||
top_left.rel -= size.rel * align.rel();
|
||||
let mut bot_right = UiVec2::from(align);
|
||||
bot_right.abs += size.abs * (Vec2::ONE - align.rel());
|
||||
bot_right.rel += size.rel * (Vec2::ONE - align.rel());
|
||||
Self {
|
||||
top_left,
|
||||
bot_right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} -> {} (size: {})",
|
||||
self.top_left,
|
||||
self.bot_right,
|
||||
self.size()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScreenRegion {
|
||||
pub top_left: Vec2,
|
||||
pub bot_right: Vec2,
|
||||
}
|
||||
impl ScreenRegion {
|
||||
pub fn contains(&self, pos: Vec2) -> bool {
|
||||
pos.x >= self.top_left.x
|
||||
&& pos.x <= self.bot_right.x
|
||||
&& pos.y >= self.top_left.y
|
||||
&& pos.y <= self.bot_right.y
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UIRegionAxisView<'a> {
|
||||
pub top_left: UiScalarView<'a>,
|
||||
pub bot_right: UiScalarView<'a>,
|
||||
}
|
||||
|
||||
pub struct UiScalarView<'a> {
|
||||
pub rel: &'a mut f32,
|
||||
pub abs: &'a mut f32,
|
||||
}
|
||||
|
||||
impl UiScalarView<'_> {
|
||||
pub fn set(&mut self, scalar: UiScalar) {
|
||||
*self.rel = scalar.rel;
|
||||
*self.abs = scalar.abs;
|
||||
}
|
||||
pub fn get(self) -> UiScalar {
|
||||
UiScalar {
|
||||
rel: *self.rel,
|
||||
abs: *self.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
use cosmic_text::{Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCache};
|
||||
use image::{Rgba, RgbaImage};
|
||||
|
||||
use crate::{
|
||||
layout::{Align, TextureHandle, Textures, UiColor, Vec2},
|
||||
util::HashMap,
|
||||
};
|
||||
|
||||
/// TODO: properly wrap this
|
||||
pub mod text_lib {
|
||||
pub use cosmic_text::*;
|
||||
}
|
||||
|
||||
pub struct TextData {
|
||||
pub font_system: FontSystem,
|
||||
pub swash_cache: SwashCache,
|
||||
}
|
||||
|
||||
impl Default for TextData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_system: FontSystem::new(),
|
||||
swash_cache: SwashCache::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 its drawn)
|
||||
pub align: Align,
|
||||
}
|
||||
|
||||
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 * 1.2,
|
||||
family: Family::SansSerif,
|
||||
wrap: false,
|
||||
align: Align::Center,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextData {
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
buffer: &mut TextBuffer,
|
||||
attrs: &TextAttrs,
|
||||
textures: &mut Textures,
|
||||
) -> TextTexture {
|
||||
// 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::default();
|
||||
let mut min_x = 0;
|
||||
let mut min_y = 0;
|
||||
let mut max_x = 0;
|
||||
let mut max_y = 0;
|
||||
let cosmic_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 => cosmic_color,
|
||||
};
|
||||
|
||||
self.swash_cache.with_pixels(
|
||||
&mut self.font_system,
|
||||
physical_glyph.cache_key,
|
||||
glyph_color,
|
||||
|x, y, color| {
|
||||
let x = physical_glyph.x + x;
|
||||
let y = run.line_y as i32 + physical_glyph.y + y;
|
||||
min_x = min_x.min(x);
|
||||
min_y = min_y.min(y);
|
||||
max_x = max_x.max(x);
|
||||
max_y = max_y.max(y);
|
||||
pixels.insert((x, y), Rgba(color.as_rgba()));
|
||||
},
|
||||
);
|
||||
}
|
||||
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 ((x, y), color) in pixels {
|
||||
let x = (x - min_x) as u32;
|
||||
let y = (y - min_y) as u32;
|
||||
image.put_pixel(x, y, color);
|
||||
}
|
||||
TextTexture {
|
||||
handle: textures.add(image),
|
||||
top_left: Vec2::new(min_x as f32, min_y as f32),
|
||||
bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TextTexture {
|
||||
pub handle: TextureHandle,
|
||||
pub top_left: Vec2,
|
||||
pub bot_right: Vec2,
|
||||
}
|
||||
|
||||
impl TextTexture {
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.handle.size() - self.top_left + self.bot_right
|
||||
}
|
||||
}
|
||||
249
src/layout/ui.rs
249
src/layout/ui.rs
@@ -1,249 +0,0 @@
|
||||
use image::DynamicImage;
|
||||
|
||||
use crate::{
|
||||
core::{TextEdit, TextEditCtx},
|
||||
layout::{
|
||||
IdLike, PainterCtx, PainterData, StaticWidgetId, TextureHandle, Vec2, Widget, WidgetId,
|
||||
WidgetLike,
|
||||
},
|
||||
util::Id,
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
ops::{Index, IndexMut},
|
||||
sync::mpsc::{Receiver, Sender, channel},
|
||||
};
|
||||
|
||||
pub struct Ui {
|
||||
// TODO: make this at least pub(super)
|
||||
pub(crate) data: PainterData,
|
||||
root: Option<WidgetId>,
|
||||
updates: Vec<Id>,
|
||||
recv: Receiver<Id>,
|
||||
pub(super) send: Sender<Id>,
|
||||
full_redraw: bool,
|
||||
resized: bool,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn add<W: Widget, Tag>(&mut self, w: impl WidgetLike<Tag, Widget = W>) -> WidgetId<W> {
|
||||
w.add(self)
|
||||
}
|
||||
|
||||
pub fn add_static<W: Widget, Tag>(
|
||||
&mut self,
|
||||
w: impl WidgetLike<Tag, Widget = W>,
|
||||
) -> StaticWidgetId<W> {
|
||||
let id = w.add(self);
|
||||
id.into_static()
|
||||
}
|
||||
|
||||
/// useful for debugging
|
||||
pub fn set_label<W>(&mut self, id: &WidgetId<W>, label: String) {
|
||||
self.data.widgets.data_mut(&id.id).unwrap().label = label;
|
||||
}
|
||||
|
||||
pub fn label<W>(&self, id: &WidgetId<W>) -> &String {
|
||||
&self.data.widgets.data(&id.id).unwrap().label
|
||||
}
|
||||
|
||||
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetId<W> {
|
||||
self.push(w)
|
||||
}
|
||||
|
||||
pub fn push<W: Widget>(&mut self, w: W) -> WidgetId<W> {
|
||||
let id = self.id();
|
||||
self.data.widgets.insert(id.id, w);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn set<W: Widget>(&mut self, id: &WidgetId<W>, w: W) {
|
||||
self.data.widgets.insert(id.id, w);
|
||||
}
|
||||
|
||||
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
|
||||
self.root = Some(w.add(self).any());
|
||||
self.full_redraw = true;
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get<W: Widget>(&self, id: &impl IdLike<W>) -> Option<&W> {
|
||||
self.data.widgets.get(id)
|
||||
}
|
||||
|
||||
pub fn get_mut<W: Widget>(&mut self, id: &impl IdLike<W>) -> Option<&mut W> {
|
||||
self.data.widgets.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn id<W: Widget>(&mut self) -> WidgetId<W> {
|
||||
WidgetId::new(
|
||||
self.data.widgets.reserve(),
|
||||
TypeId::of::<W>(),
|
||||
self.send.clone(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn id_static<W: Widget>(&mut self) -> StaticWidgetId<W> {
|
||||
let id = self.id();
|
||||
id.into_static()
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn redraw_all(&mut self) {
|
||||
for (_, inst) in self.data.active.drain() {
|
||||
for m in self.data.modules.iter_mut() {
|
||||
m.on_undraw(&inst);
|
||||
}
|
||||
}
|
||||
// free before bc nothing should exist
|
||||
self.free();
|
||||
let mut ctx = PainterCtx::new(&mut self.data);
|
||||
if let Some(root) = &self.root {
|
||||
ctx.draw(root.id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
if self.full_redraw {
|
||||
self.redraw_all();
|
||||
self.full_redraw = false;
|
||||
} else if !self.updates.is_empty() {
|
||||
self.redraw_updates();
|
||||
}
|
||||
if self.resized {
|
||||
self.resized = false;
|
||||
self.redraw_size();
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_size(&mut self) {
|
||||
// let mut ctx = PainterCtx::new(&mut self.data);
|
||||
// let dep = ctx.px_dependent.clone();
|
||||
// for id in dep {
|
||||
// ctx.redraw(id);
|
||||
// }
|
||||
self.redraw_all();
|
||||
}
|
||||
|
||||
fn redraw_updates(&mut self) {
|
||||
// if self.updates.drain(..).next().is_some() {
|
||||
// self.redraw_all();
|
||||
// }
|
||||
let mut ctx = PainterCtx::new(&mut self.data);
|
||||
for id in self.updates.drain(..) {
|
||||
ctx.redraw(id);
|
||||
}
|
||||
self.free();
|
||||
}
|
||||
|
||||
/// free any resources that don't have references anymore
|
||||
fn free(&mut self) {
|
||||
for id in self.recv.try_iter() {
|
||||
for m in self.data.modules.iter_mut() {
|
||||
m.on_remove(&id);
|
||||
}
|
||||
self.data.widgets.delete(id);
|
||||
}
|
||||
self.data.textures.free();
|
||||
}
|
||||
|
||||
pub fn needs_redraw(&self) -> bool {
|
||||
self.full_redraw || !self.updates.is_empty()
|
||||
}
|
||||
|
||||
pub fn num_widgets(&self) -> usize {
|
||||
self.data.widgets.len()
|
||||
}
|
||||
|
||||
pub fn active_widgets(&self) -> usize {
|
||||
self.data.active.len()
|
||||
}
|
||||
|
||||
pub fn text(&mut self, id: &impl IdLike<TextEdit>) -> TextEditCtx<'_> {
|
||||
self.updates.push(id.id());
|
||||
TextEditCtx {
|
||||
text: self.data.widgets.get_mut(id).unwrap(),
|
||||
font_system: &mut self.data.text.font_system,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug(&self, label: &str) {
|
||||
for (id, inst) in &self.data.active {
|
||||
let l = &self.data.widgets.data(id).unwrap().label;
|
||||
if l != label {
|
||||
continue;
|
||||
}
|
||||
println!("\"{label}\" {{");
|
||||
println!(" region: {}", inst.region);
|
||||
println!(" desired_size: {}", inst.desired_size);
|
||||
println!("}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> Index<&WidgetId<W>> for Ui {
|
||||
type Output = W;
|
||||
|
||||
fn index(&self, id: &WidgetId<W>) -> &Self::Output {
|
||||
self.get(id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> IndexMut<&WidgetId<W>> for Ui {
|
||||
fn index_mut(&mut self, id: &WidgetId<W>) -> &mut Self::Output {
|
||||
self.updates.push(id.id);
|
||||
self.get_mut(id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> Index<StaticWidgetId<W>> for Ui {
|
||||
type Output = W;
|
||||
|
||||
fn index(&self, id: StaticWidgetId<W>) -> &Self::Output {
|
||||
self.data.widgets.get(&id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> IndexMut<StaticWidgetId<W>> for Ui {
|
||||
fn index_mut(&mut self, id: StaticWidgetId<W>) -> &mut Self::Output {
|
||||
self.updates.push(id.id);
|
||||
self.data.widgets.get_mut(&id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn Widget {
|
||||
pub fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Ui {
|
||||
fn default() -> Self {
|
||||
let (send, recv) = channel();
|
||||
Self {
|
||||
data: PainterData::default(),
|
||||
root: Default::default(),
|
||||
updates: Default::default(),
|
||||
full_redraw: false,
|
||||
send,
|
||||
recv,
|
||||
resized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
use crate::layout::{Painter, Size, SizeCtx, StaticWidgetId, Ui, WidgetId, WidgetIdFn};
|
||||
|
||||
use std::{any::Any, marker::PhantomData};
|
||||
|
||||
pub trait Widget: Any {
|
||||
fn draw(&mut self, painter: &mut Painter);
|
||||
fn desired_size(&mut self, _: &mut SizeCtx) -> Size {
|
||||
Size::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for () {
|
||||
fn draw(&mut self, _: &mut Painter) {}
|
||||
}
|
||||
|
||||
pub struct WidgetTag;
|
||||
pub struct FnTag;
|
||||
|
||||
pub trait WidgetLike<Tag> {
|
||||
type Widget: 'static;
|
||||
fn add(self, ui: &mut Ui) -> WidgetId<Self::Widget>;
|
||||
fn with_id<W2>(
|
||||
self,
|
||||
f: impl FnOnce(&mut Ui, WidgetId<Self::Widget>) -> WidgetId<W2>,
|
||||
) -> impl WidgetIdFn<W2>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
move |ui| {
|
||||
let id = self.add(ui);
|
||||
f(ui, id)
|
||||
}
|
||||
}
|
||||
fn add_static(self, ui: &mut Ui) -> StaticWidgetId<Self::Widget>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.add(ui).into_static()
|
||||
}
|
||||
fn set_root(self, ui: &mut Ui)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ui.set_root(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<W: Widget>: 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) -> WidgetId<W> {
|
||||
self(ui).add(ui)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> WidgetLike<WidgetTag> for W {
|
||||
type Widget = W;
|
||||
fn add(self, ui: &mut Ui) -> WidgetId<W> {
|
||||
ui.add_widget(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WidgetArr<const LEN: usize, Ws> {
|
||||
pub arr: [WidgetId; LEN],
|
||||
_pd: PhantomData<Ws>,
|
||||
}
|
||||
|
||||
impl<const LEN: usize, Ws> WidgetArr<LEN, Ws> {
|
||||
pub fn new(arr: [WidgetId; LEN]) -> Self {
|
||||
Self {
|
||||
arr,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArrTag;
|
||||
pub trait WidgetArrLike<const LEN: usize, Tag> {
|
||||
type Ws;
|
||||
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN, Self::Ws>;
|
||||
}
|
||||
|
||||
impl<const LEN: usize, Ws> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN, Ws> {
|
||||
type Ws = Ws;
|
||||
fn ui(self, _: &mut Ui) -> WidgetArr<LEN, Ws> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: WidgetLike<WidgetTag>> WidgetArrLike<1, WidgetTag> for W {
|
||||
type Ws = (W::Widget,);
|
||||
fn ui(self, ui: &mut Ui) -> WidgetArr<1, (W::Widget,)> {
|
||||
WidgetArr::new([self.add(ui).any()])
|
||||
}
|
||||
}
|
||||
|
||||
// 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,)*) {
|
||||
type Ws = ($($W::Widget,)*);
|
||||
fn ui(self, ui: &mut Ui) -> WidgetArr<$n, ($($W::Widget,)*)> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($W,)*) = self;
|
||||
WidgetArr::new(
|
||||
[$($W.add(ui).cast_type(),)*],
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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,103 +0,0 @@
|
||||
use crate::{
|
||||
layout::{IdLike, Widget},
|
||||
util::{DynBorrower, HashMap, Id, IdTracker},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Widgets {
|
||||
ids: IdTracker,
|
||||
map: HashMap<Id, WidgetData>,
|
||||
}
|
||||
|
||||
pub struct WidgetData {
|
||||
pub widget: Box<dyn Widget>,
|
||||
pub label: String,
|
||||
/// dynamic borrow checking
|
||||
pub borrowed: bool,
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ids: IdTracker::default(),
|
||||
map: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dyn(&self, id: Id) -> Option<&dyn Widget> {
|
||||
Some(self.map.get(&id)?.widget.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_dyn_mut(&mut self, id: Id) -> Option<&mut dyn Widget> {
|
||||
Some(self.map.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 fn get_dyn_dynamic(&self, id: Id) -> WidgetWrapper<'_> {
|
||||
// SAFETY: must guarantee no other mutable references to this widget exist
|
||||
// done through the borrow variable
|
||||
#[allow(mutable_transmutes)]
|
||||
let data = unsafe {
|
||||
std::mem::transmute::<&WidgetData, &mut WidgetData>(self.map.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<W: Widget>(&self, id: &impl IdLike<W>) -> Option<&W> {
|
||||
self.get_dyn(id.id())?.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
pub fn get_mut<W: Widget>(&mut self, id: &impl IdLike<W>) -> Option<&mut W> {
|
||||
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
|
||||
}
|
||||
|
||||
pub fn insert<W: Widget>(&mut self, id: Id, widget: W) {
|
||||
self.insert_any(id, Box::new(widget), std::any::type_name::<W>().to_string());
|
||||
}
|
||||
|
||||
pub fn data(&self, id: &Id) -> Option<&WidgetData> {
|
||||
self.map.get(id)
|
||||
}
|
||||
|
||||
pub fn label(&self, id: &Id) -> &String {
|
||||
&self.data(id).unwrap().label
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self, id: &Id) -> Option<&mut WidgetData> {
|
||||
self.map.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn insert_any(&mut self, id: Id, widget: Box<dyn Widget>, label: String) {
|
||||
self.map.insert(
|
||||
id,
|
||||
WidgetData {
|
||||
widget,
|
||||
label,
|
||||
borrowed: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, id: Id) {
|
||||
self.map.remove(&id);
|
||||
self.ids.free(id);
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self) -> Id {
|
||||
self.ids.next()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;
|
||||
46
src/lib.rs
46
src/lib.rs
@@ -1,20 +1,38 @@
|
||||
#![feature(macro_metavar_expr_concat)]
|
||||
#![feature(const_ops)]
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(const_convert)]
|
||||
#![feature(map_try_insert)]
|
||||
#![feature(unboxed_closures)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(const_cmp)]
|
||||
#![feature(const_destruct)]
|
||||
#![feature(gen_blocks)]
|
||||
#![feature(associated_type_defaults)]
|
||||
#![feature(unsize)]
|
||||
|
||||
pub mod core;
|
||||
pub mod layout;
|
||||
pub mod render;
|
||||
pub mod util;
|
||||
pub mod default;
|
||||
pub mod event;
|
||||
pub mod typed;
|
||||
pub mod widget;
|
||||
|
||||
pub use iris_core as core;
|
||||
pub use iris_macro as macros;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! state_prelude {
|
||||
($vis:vis $state:ty) => {
|
||||
iris::event_state!($vis $state);
|
||||
$vis use iris::{
|
||||
default::*,
|
||||
core::{len_fns::*, util::Vec2, *},
|
||||
macros::*,
|
||||
widget::*,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::core::*;
|
||||
pub use crate::layout::*;
|
||||
pub use crate::render::*;
|
||||
use super::*;
|
||||
pub use default::*;
|
||||
pub use event::*;
|
||||
pub use iris_core::*;
|
||||
pub use iris_macro::*;
|
||||
pub use widget::*;
|
||||
|
||||
pub use iris_core::util::Vec2;
|
||||
pub use typed::*;
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
mod testing;
|
||||
|
||||
fn main() {
|
||||
testing::main();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
event::WindowEvent,
|
||||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
window::{Window, WindowId},
|
||||
};
|
||||
|
||||
use super::Client;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct App {
|
||||
client: Option<Client>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn run() {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
event_loop.run_app(&mut App::default()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for App {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if self.client.is_none() {
|
||||
let window = event_loop
|
||||
.create_window(Window::default_attributes())
|
||||
.unwrap();
|
||||
let client = Client::new(window.into());
|
||||
self.client = Some(client);
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||
let client = self.client.as_mut().unwrap();
|
||||
client.event(event, event_loop);
|
||||
}
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use app::App;
|
||||
use arboard::Clipboard;
|
||||
use cosmic_text::Family;
|
||||
use iris::prelude::*;
|
||||
use render::Renderer;
|
||||
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
|
||||
|
||||
use crate::testing::input::Input;
|
||||
use len_fns::*;
|
||||
|
||||
mod app;
|
||||
mod input;
|
||||
mod render;
|
||||
|
||||
pub fn main() {
|
||||
App::run();
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
renderer: Renderer,
|
||||
input: Input,
|
||||
ui: Ui,
|
||||
info: WidgetId<Text>,
|
||||
focus: Option<WidgetId<TextEdit>>,
|
||||
clipboard: Clipboard,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
struct Submit;
|
||||
|
||||
impl DefaultEvent for Submit {
|
||||
type Data = ();
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
let renderer = Renderer::new(window);
|
||||
|
||||
let mut ui = Ui::new();
|
||||
let rrect = rect(Color::WHITE).radius(20);
|
||||
let pad_test = (
|
||||
rrect.color(Color::BLUE),
|
||||
(
|
||||
rrect
|
||||
.color(Color::RED)
|
||||
.sized((100, 100))
|
||||
.center()
|
||||
.width(rest(2)),
|
||||
(
|
||||
rrect.color(Color::ORANGE),
|
||||
rrect.color(Color::LIME).pad(10.0),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.width(rest(2)),
|
||||
rrect.color(Color::YELLOW),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.pad(10)
|
||||
.width(rest(3)),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.add_static(&mut ui);
|
||||
|
||||
let span_test = (
|
||||
rrect.color(Color::GREEN).width(100),
|
||||
rrect.color(Color::ORANGE),
|
||||
rrect.color(Color::CYAN),
|
||||
rrect.color(Color::BLUE).width(rel(0.5)),
|
||||
rrect.color(Color::MAGENTA).width(100),
|
||||
rrect.color(Color::RED).width(100),
|
||||
)
|
||||
.span(Dir::LEFT)
|
||||
.add_static(&mut ui);
|
||||
|
||||
let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui);
|
||||
|
||||
let add_button = rect(Color::LIME)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |ctx: &mut Client, _| {
|
||||
let child = ctx
|
||||
.ui
|
||||
.add(image(include_bytes!("assets/sungals.png")).center())
|
||||
.any();
|
||||
ctx.ui[span_add].children.push(child);
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BotRight);
|
||||
|
||||
let del_button = rect(Color::RED)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |ctx: &mut Client, _| {
|
||||
ctx.ui[span_add].children.pop();
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BotLeft);
|
||||
|
||||
let span_add_test = (span_add, add_button, del_button)
|
||||
.stack()
|
||||
.add_static(&mut ui);
|
||||
|
||||
let main = pad_test.pad(10).add_static(&mut ui);
|
||||
|
||||
let btext = |content| text(content).size(30);
|
||||
|
||||
let text_test = (
|
||||
btext("this is a").align(Align::Left),
|
||||
btext("teeeeeeeest").align(Align::Right),
|
||||
btext("okkk\nokkkkkk!").align(Align::Left),
|
||||
btext("hmm"),
|
||||
btext("a"),
|
||||
(
|
||||
btext("'").family(Family::Monospace).align(Align::Top),
|
||||
btext("'").family(Family::Monospace),
|
||||
btext(":gamer mode").family(Family::Monospace),
|
||||
rect(Color::CYAN).sized((10, 10)).center(),
|
||||
rect(Color::RED).sized((100, 100)).center(),
|
||||
rect(Color::PURPLE).sized((50, 50)).align(Align::Top),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.center(),
|
||||
text("pretty cool right?").size(50),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add_static(&mut ui);
|
||||
|
||||
let texts = Span::empty(Dir::DOWN).gap(10).add_static(&mut ui);
|
||||
let msg_area = (Rect::new(Color::SKY), texts.scroll().masked()).stack();
|
||||
let add_text = text("add")
|
||||
.editable()
|
||||
.text_align(Align::Left)
|
||||
.size(30)
|
||||
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
||||
client.ui.text(id).select(ctx.cursor, ctx.size);
|
||||
client.focus = Some(id.clone());
|
||||
})
|
||||
.id_on(Submit, move |id, client: &mut Client, _| {
|
||||
let content = client.ui.text(id).take();
|
||||
let text = text(content)
|
||||
.editable()
|
||||
.size(30)
|
||||
.text_align(Align::Left)
|
||||
.wrap(true)
|
||||
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
||||
client.ui.text(id).select(ctx.cursor, ctx.size);
|
||||
client.focus = Some(id.clone());
|
||||
});
|
||||
let msg_box = (rect(Color::WHITE.darker(0.5)), text)
|
||||
.stack()
|
||||
.size(StackSize::Child(1))
|
||||
.add(&mut client.ui);
|
||||
client.ui[texts].children.push(msg_box.any());
|
||||
})
|
||||
.add(&mut ui);
|
||||
let text_edit_scroll = (
|
||||
msg_area,
|
||||
(
|
||||
Rect::new(Color::WHITE.darker(0.9)),
|
||||
(
|
||||
add_text.clone().width(rest(1)),
|
||||
Rect::new(Color::GREEN)
|
||||
.on(CursorSense::click(), move |client: &mut Client, _| {
|
||||
client.run_event(&add_text, Submit, ());
|
||||
})
|
||||
.sized((40, 40)),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.pad(10),
|
||||
)
|
||||
.stack()
|
||||
.size(StackSize::Child(1))
|
||||
.offset_layer(1)
|
||||
.align(Align::Bot),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add_static(&mut ui);
|
||||
|
||||
let switch_button = |color, to, label| {
|
||||
let rect = rect(color)
|
||||
.id_on(CursorSense::click(), move |id, ui: &mut Ui, _| {
|
||||
ui[main].inner.set_static(to);
|
||||
ui[id].color = color.darker(0.3);
|
||||
})
|
||||
.edit_on(
|
||||
CursorSense::HoverStart | CursorSense::unclick(),
|
||||
move |r, _| {
|
||||
r.color = color.brighter(0.2);
|
||||
},
|
||||
)
|
||||
.edit_on(CursorSense::HoverEnd, move |r, _| {
|
||||
r.color = color;
|
||||
});
|
||||
(rect, text(label).size(30)).stack()
|
||||
};
|
||||
|
||||
let tabs = (
|
||||
switch_button(Color::RED, pad_test.any(), "pad"),
|
||||
switch_button(Color::GREEN, span_test.any(), "span"),
|
||||
switch_button(Color::BLUE, span_add_test.any(), "image span"),
|
||||
switch_button(Color::MAGENTA, text_test.any(), "text layout"),
|
||||
switch_button(
|
||||
Color::YELLOW.mul_rgb(0.5),
|
||||
text_edit_scroll.any(),
|
||||
"text edit scroll",
|
||||
),
|
||||
)
|
||||
.span(Dir::RIGHT);
|
||||
|
||||
let info = text("").add(&mut ui);
|
||||
let info_sect = info.clone().pad(10).align(Align::Right);
|
||||
|
||||
((tabs.height(40), main).span(Dir::DOWN), info_sect)
|
||||
.stack()
|
||||
.set_root(&mut ui);
|
||||
|
||||
Self {
|
||||
renderer,
|
||||
input: Input::default(),
|
||||
ui,
|
||||
info,
|
||||
focus: None,
|
||||
clipboard: Clipboard::new().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||
let input_changed = self.input.event(&event);
|
||||
let cursor_state = self.cursor_state().clone();
|
||||
if let Some(focus) = &self.focus
|
||||
&& cursor_state.buttons.left.is_start()
|
||||
{
|
||||
self.ui.text(focus).deselect();
|
||||
self.focus = None;
|
||||
}
|
||||
if input_changed {
|
||||
let window_size = self.window_size();
|
||||
self.run_sensors(&cursor_state, window_size);
|
||||
self.ui.run_sensors(&cursor_state, window_size);
|
||||
}
|
||||
match event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
self.ui.update();
|
||||
self.renderer.update(&mut self.ui);
|
||||
self.renderer.draw()
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
self.ui.resize((size.width, size.height));
|
||||
self.renderer.resize(&size)
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
if let Some(sel) = &self.focus
|
||||
&& event.state.is_pressed()
|
||||
{
|
||||
let mut text = self.ui.text(sel);
|
||||
match text.apply_event(&event, &self.input.modifiers) {
|
||||
TextInputResult::Unfocus => {
|
||||
self.focus = None;
|
||||
}
|
||||
TextInputResult::Submit => {
|
||||
self.run_event(&sel.clone(), Submit, ());
|
||||
}
|
||||
TextInputResult::Paste => {
|
||||
if let Ok(t) = self.clipboard.get_text() {
|
||||
text.insert(&t);
|
||||
}
|
||||
}
|
||||
TextInputResult::Unused | TextInputResult::Used => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let new = format!(
|
||||
"widgets: {}\nactive:{}\nviews: {}",
|
||||
self.ui.num_widgets(),
|
||||
self.ui.active_widgets(),
|
||||
self.renderer.ui.view_count()
|
||||
);
|
||||
if new != *self.ui[&self.info].content {
|
||||
*self.ui[&self.info].content = new;
|
||||
}
|
||||
if self.ui.needs_redraw() {
|
||||
self.renderer.window().request_redraw();
|
||||
}
|
||||
self.input.end_frame();
|
||||
}
|
||||
}
|
||||
|
||||
impl UiCtx for Client {
|
||||
fn ui(&mut self) -> &mut Ui {
|
||||
&mut self.ui
|
||||
}
|
||||
}
|
||||
37
src/typed.rs
Normal file
37
src/typed.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
#[macro_export]
|
||||
macro_rules! event_state {
|
||||
($vis:vis $state:ty) => {
|
||||
mod local_event_trait {
|
||||
use super::*;
|
||||
#[allow(unused_imports)]
|
||||
use $crate::prelude::*;
|
||||
pub trait EventableCtx<WL: WidgetLike<$state, Tag>, Tag> {
|
||||
fn on<E: EventLike>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl for<'a> WidgetEventFn<
|
||||
<$state as HasState>::State,
|
||||
<E::Event as Event>::Data<'a>,
|
||||
WL::Widget
|
||||
>,
|
||||
) -> impl WidgetIdFn<$state, WL::Widget>;
|
||||
}
|
||||
impl<WL: WidgetLike<$state, Tag>, Tag> EventableCtx<WL, Tag> for WL {
|
||||
fn on<E: EventLike>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl for<'a> WidgetEventFn<
|
||||
<$state as HasState>::State,
|
||||
<E::Event as Event>::Data<'a>,
|
||||
WL::Widget
|
||||
>,
|
||||
) -> impl WidgetIdFn<$state, WL::Widget> {
|
||||
eventable::Eventable::on(self, event, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
$vis type EventManager = $crate::prelude::EventManager<<$state as HasState>::State>;
|
||||
$vis use local_event_trait::*;
|
||||
};
|
||||
}
|
||||
pub use event_state;
|
||||
@@ -1,16 +0,0 @@
|
||||
mod arena;
|
||||
mod borrow;
|
||||
mod change;
|
||||
mod id;
|
||||
mod math;
|
||||
mod refcount;
|
||||
|
||||
pub(crate) use arena::*;
|
||||
pub(crate) use borrow::*;
|
||||
pub use change::*;
|
||||
pub(crate) use id::*;
|
||||
pub(crate) use math::*;
|
||||
pub(crate) use refcount::*;
|
||||
|
||||
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
|
||||
pub type HashSet<K> = fxhash::FxHashSet<K>;
|
||||
@@ -10,15 +10,19 @@ impl Widget for Image {
|
||||
painter.texture(&self.handle);
|
||||
}
|
||||
|
||||
fn desired_size(&mut self, _: &mut SizeCtx) -> Size {
|
||||
Size::abs(self.handle.size())
|
||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||
Len::abs(self.handle.size().x)
|
||||
}
|
||||
|
||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
||||
Len::abs(self.handle.size().y)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image(image: impl LoadableImage) -> impl WidgetFn<Image> {
|
||||
pub fn image<State: HasUi>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
||||
let image = image.get_image().expect("Failed to load image");
|
||||
move |ui| Image {
|
||||
handle: ui.add_texture(image),
|
||||
move |state| Image {
|
||||
handle: state.get_mut().add_texture(image),
|
||||
}
|
||||
}
|
||||
|
||||
20
src/widget/mask.rs
Normal file
20
src/widget/mask.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Masked {
|
||||
pub inner: WidgetHandle,
|
||||
}
|
||||
|
||||
impl Widget for Masked {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
painter.set_mask(painter.region());
|
||||
painter.widget(&self.inner);
|
||||
}
|
||||
|
||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
ctx.width(&self.inner)
|
||||
}
|
||||
|
||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
ctx.height(&self.inner)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ mod mask;
|
||||
mod position;
|
||||
mod ptr;
|
||||
mod rect;
|
||||
mod sense;
|
||||
mod text;
|
||||
mod trait_fns;
|
||||
|
||||
@@ -12,6 +11,5 @@ pub use mask::*;
|
||||
pub use position::*;
|
||||
pub use ptr::*;
|
||||
pub use rect::*;
|
||||
pub use sense::*;
|
||||
pub use text::*;
|
||||
pub use trait_fns::*;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user