31 Commits

Author SHA1 Message Date
123f3e6e2b stuffff 2025-12-11 01:34:14 -05:00
baaeb6b027 FINALLY transition events to global; slow text sending bug tho 2025-12-11 01:21:36 -05:00
2537284372 "nvmnd" (event FnMut) 2025-12-10 13:56:38 -05:00
76f75192d5 fix event stuff 2025-12-10 13:29:36 -05:00
6156c66a20 initial global widget store 2025-12-10 01:42:39 -05:00
7f4846a2d3 change macro a bit 2025-12-09 02:03:54 -05:00
e44bb8eca4 learn how workspaces + proc macros work & restructure everything 2025-12-09 01:52:45 -05:00
434e3c3af7 view + a bunch of fixes or smth idek man 2025-12-08 00:12:11 -05:00
b66d4da5d7 fix sized bounds 2025-12-07 15:18:01 -05:00
7b3a79b1b0 clean up layout dir 2025-12-07 14:45:54 -05:00
c99d466b75 switch to Rc<RefCell<...>> for widget storage 2025-12-07 14:36:38 -05:00
38266debb6 stuff 2025-12-07 00:32:38 -05:00
62aa02847a redo event fn signature & add event_ctx macro 2025-12-06 20:48:10 -05:00
f6b1143665 nothing 2025-12-04 15:01:31 -05:00
28d17c49c6 shift code 2025-12-04 14:54:40 -05:00
23ae5b246e remove default window attrs (oops) 2025-12-04 14:53:08 -05:00
db888416b6 add minimal example 2025-12-04 14:46:34 -05:00
f7b100e00c add default winit framework 2025-12-04 14:31:07 -05:00
e5d0a7e592 fix on_id widget refcount leak (-> memory leak) 2025-12-04 02:59:05 -05:00
84c460a91f app work 2025-12-03 22:51:33 -05:00
d6a9711ceb fix mask render bug (didn't recreate bind group) 2025-11-28 16:09:23 -05:00
ee0616885f single line textedit 2025-11-22 22:04:46 -05:00
14a9da0553 hold shift to select text 2025-11-22 21:38:16 -05:00
8e08f67627 ctrl x 2025-11-22 21:15:50 -05:00
84b3bf9078 fix zalgotext highlight 2025-11-22 21:14:41 -05:00
bf3ade840b triple click to select line + fix highlighting 2025-11-22 20:49:38 -05:00
2aa5719166 added text edit history / undo (ctrl-z) 2025-11-22 20:37:37 -05:00
90c579d734 oopsie (orderlerss -> ordered rendering) 2025-11-22 20:22:26 -05:00
d757e805e8 rename z offset to layer offset 2025-11-22 18:45:01 -05:00
9deba3d9d7 fix wrapping text selection 2025-11-22 18:29:44 -05:00
c24c517c60 word selection 2025-11-22 15:33:28 -05:00
88 changed files with 3200 additions and 2533 deletions

505
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,30 @@
[package] [package]
name = "iris" name = "iris"
version = "0.1.0"
edition = "2024"
default-run = "test" 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [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" pollster = "0.4.0"
winit = "0.30.12" winit = "0.30.12"
wgpu = "27.0.1" wgpu = "27.0.1"
@@ -15,5 +33,6 @@ image = "0.25.6"
cosmic-text = "0.15.0" cosmic-text = "0.15.0"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
fxhash = "0.2.1" 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" }

3
TODO
View File

@@ -16,6 +16,7 @@ scaling
field could be best solution so redrawing stuff isn't needed & you can specify both as user field could be best solution so redrawing stuff isn't needed & you can specify both as user
WidgetRef<W> or smth instead of Id WidgetRef<W> or smth instead of Id
fyi this is not the same as what was just implemented
enum that's either an Id or an actual concrete instance of W enum that's either an Id or an actual concrete instance of W
painter takes them in instead of (or in addition to) id painter takes them in instead of (or in addition to) id
then type wrapper widgets to contain them then type wrapper widgets to contain them
@@ -36,3 +37,5 @@ don't forget I'm streaming
tags tags
vecs for each widget type? vecs for each widget type?
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??

12
core/Cargo.toml Normal file
View 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 }

View File

@@ -1,11 +1,11 @@
use crate::layout::{Ui, WidgetId, WidgetIdFn, WidgetLike}; use crate::layout::{Ui, WidgetIdFn, WidgetLike, WidgetRef};
pub trait WidgetAttr<W> { pub trait WidgetAttr<W: ?Sized> {
type Input; type Input;
fn run(ui: &mut Ui, id: &WidgetId<W>, input: Self::Input); fn run(ui: &mut Ui, id: WidgetRef<W>, input: Self::Input);
} }
pub trait Attrable<W, Tag> { pub trait Attrable<W: ?Sized, Tag> {
fn attr<A: WidgetAttr<W>>(self, input: A::Input) -> impl WidgetIdFn<W>; fn attr<A: WidgetAttr<W>>(self, input: A::Input) -> impl WidgetIdFn<W>;
} }
@@ -13,7 +13,7 @@ impl<WL: WidgetLike<Tag>, Tag> Attrable<WL::Widget, Tag> for WL {
fn attr<A: WidgetAttr<WL::Widget>>(self, input: A::Input) -> impl WidgetIdFn<WL::Widget> { fn attr<A: WidgetAttr<WL::Widget>>(self, input: A::Input) -> impl WidgetIdFn<WL::Widget> {
|ui| { |ui| {
let id = self.add(ui); let id = self.add(ui);
A::run(ui, &id, input); A::run(ui, id.weak(), input);
id id
} }
} }

209
core/src/layout/event.rs Normal file
View File

@@ -0,0 +1,209 @@
use crate::{
layout::{LayerId, UiId, Widget, WidgetId, WidgetInstance, WidgetRef},
util::{Handle, HashMap, RefGuardMut},
};
use std::{
any::{Any, TypeId},
ops::DerefMut,
sync::{Arc, LazyLock, Mutex},
};
pub trait Async: Send + Sync + 'static {}
impl<T: Send + Sync + 'static> Async for T {}
pub trait Event: Sized + Async + Clone {
type Data<'a> = ();
type State: Sync + Send + Default = ();
#[allow(unused_variables)]
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
true
}
/// THIS SHOULD NOT BE OVERWRITTEN; the default impl runs this event
fn run<Ctx: 'static, W>(id: WidgetRef<W>, data: &mut Self::Data<'_>, state: &mut Ctx) {
Events::<Self, Ctx>::run(id.id(), data, state);
}
}
pub trait EventAlias {
type Event: Event;
fn into_event(self) -> Self::Event;
}
impl<E: Event> EventAlias for E {
type Event = Self;
fn into_event(self) -> Self::Event {
self
}
}
type EventData<E, Ctx> = (E, Arc<dyn for<'a> EventFn<Ctx, <E as Event>::Data<'a>>>);
pub struct Events<E: Event, Ctx> {
active: HashMap<UiId, HashMap<LayerId, HashMap<WidgetId, E::State>>>,
map: HashMap<WidgetId, Vec<EventData<E, Ctx>>>,
}
pub static EVENT_TYPES: LazyLock<Mutex<HashMap<TypeId, Handle<dyn EventManager>>>> =
LazyLock::new(|| Mutex::new(HashMap::default()));
pub trait EventManager: Any + Send + Sync {
fn remove(&mut self, id: WidgetId) -> Box<dyn Any>;
fn draw(&mut self, ui: UiId, inst: &WidgetInstance);
fn undraw(&mut self, ui: UiId, inst: &WidgetInstance);
}
impl Handle<dyn EventManager> {
pub fn remove(&mut self, id: WidgetId) {
let mut s = self.get_mut();
// refer to s.remove documentation for why drop order
let contents = s.remove(id);
drop(s);
drop(contents);
}
pub fn draw(&mut self, ui: UiId, inst: &WidgetInstance) {
self.get_mut().draw(ui, inst);
}
pub fn undraw(&mut self, ui: UiId, inst: &WidgetInstance) {
self.get_mut().undraw(ui, inst);
}
}
impl<E: Event, Ctx: 'static> EventManager for Events<E, Ctx> {
/// so... there is a deadlock if this is called in WidgetData::drop
/// and some function in here owns another WidgetId
/// so for now you need to drop the lock on self, THEN drop the contents
/// which is implemented for Handle<dyn EventManager>
fn remove(&mut self, id: WidgetId) -> Box<dyn Any> {
let contents = self.map.remove(&id);
for ui in self.active.values_mut() {
for layer in ui.values_mut() {
layer.remove(&id);
}
}
Box::new(contents)
}
fn draw(&mut self, ui: UiId, inst: &WidgetInstance) {
self.active
.entry(ui)
.or_default()
.entry(inst.layer)
.or_default()
.entry(inst.id)
.or_default();
}
fn undraw(&mut self, ui: UiId, inst: &WidgetInstance) {
// rust pls
(|| {
self.active
.get_mut(&ui)?
.get_mut(&inst.layer)?
.remove(&inst.id);
Some(())
})();
}
}
pub type UiActive<State> = HashMap<LayerId, HashMap<WidgetId, State>>;
impl<E: Event, Ctx: 'static> Events<E, Ctx> {
fn id() -> TypeId {
TypeId::of::<Self>()
}
fn new() -> Self {
Self {
map: Default::default(),
active: Default::default(),
}
}
pub fn active(id: UiId) -> impl DerefMut<Target = UiActive<E::State>> {
RefGuardMut::map(Self::get(), |s| s.active.entry(id).or_default())
}
fn register_<W: Widget + ?Sized>(
id: WidgetRef<W>,
event: impl Into<E>,
f: impl for<'a> EventFn<Ctx, <E as Event>::Data<'a>>,
) {
let Some(mut data) = id.data_mut() else {
return;
};
data.event_managers.insert(Self::id());
Self::get()
.map
.entry(id.id())
.or_default()
.push((event.into(), Arc::new(f)));
}
pub fn register<W: Widget + ?Sized + 'static>(
id: WidgetRef<W>,
event: impl Into<E>,
f: impl for<'a> EventIdFn<Ctx, <E as Event>::Data<'a>, W>,
) {
Self::register_(id, event, move |ctx| {
f(EventIdCtx {
widget: id,
state: ctx.state,
data: ctx.data,
})
});
}
pub fn run(id: WidgetId, data: &mut E::Data<'_>, state: &mut Ctx) {
let s = Self::get();
if let Some(fs) = s.map.get(&id) {
let fs = fs.clone();
drop(s);
for (e, f) in fs {
if e.should_run(data) {
f(EventCtx { state, data })
}
}
}
}
pub fn run_all(data: &mut E::Data<'_>, state: &mut Ctx) {
let s = Self::get();
// TODO: should probably document that added events won't run same frame
let mut map = s.map.clone();
drop(s);
for fs in map.values_mut() {
for (e, f) in fs {
if e.should_run(data) {
f(EventCtx { state, data })
}
}
}
}
/// this some bull
fn get<'a>() -> RefGuardMut<'a, Self> {
let any = EVENT_TYPES
.lock()
.unwrap()
.entry(Self::id())
.or_insert_with(|| Handle::from(Self::new()))
.clone();
unsafe { any.downcast() }.get_take_mut()
}
}
pub struct EventCtx<'a, Ctx, Data> {
pub state: &'a mut Ctx,
pub data: &'a mut Data,
}
pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> {
pub widget: WidgetRef<W>,
pub state: &'a mut Ctx,
pub data: &'a mut Data,
}
pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + Async {}
impl<F: Fn(EventCtx<Ctx, Data>) + Async, Ctx, Data> EventFn<Ctx, Data> for F {}
pub trait EventIdFn<Ctx, Data, W: ?Sized>: Fn(EventIdCtx<Ctx, Data, W>) + Async {}
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + Async, Ctx, Data, W: ?Sized> EventIdFn<Ctx, Data, W> for F {}

View File

@@ -1,33 +1,24 @@
mod color; mod attr;
mod event; mod event;
mod id;
mod layer;
mod module; mod module;
mod num; mod num;
mod orientation; mod orientation;
mod painter; mod painter;
mod text; mod primitive;
mod texture;
mod ui; mod ui;
mod attr; mod view;
mod vec2;
mod widget; mod widget;
mod widgets;
pub use color::*; pub use attr::*;
pub use event::*; pub use event::*;
pub use id::*;
pub use layer::*;
pub use module::*; pub use module::*;
pub use num::*; pub use num::*;
pub use orientation::*; pub use orientation::*;
pub use painter::*; pub use painter::*;
pub use text::*; pub use primitive::*;
pub use texture::*;
pub use ui::*; pub use ui::*;
pub use attr::*; pub use view::*;
pub use vec2::*;
pub use widget::*; pub use widget::*;
pub use widgets::*;
pub use crate::util::Vec2;
pub type UiColor = Color<u8>; pub type UiColor = Color<u8>;

View File

@@ -1,15 +1,15 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use crate::{ use crate::{
layout::WidgetInstance, layout::{WidgetId, WidgetInstance},
util::{HashMap, Id}, util::HashMap,
}; };
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait UiModule: Any { pub trait UiModule: Any {
fn on_draw(&mut self, inst: &WidgetInstance) {} fn on_draw(&mut self, inst: &WidgetInstance) {}
fn on_undraw(&mut self, inst: &WidgetInstance) {} fn on_undraw(&mut self, inst: &WidgetInstance) {}
fn on_remove(&mut self, id: &Id) {} fn on_remove(&mut self, id: &WidgetId) {}
fn on_move(&mut self, inst: &WidgetInstance) {} fn on_move(&mut self, inst: &WidgetInstance) {}
} }

49
core/src/layout/num.rs Normal file
View 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
}
}
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<T: const UiNum + Copy> const From<T> for Vec2 {
fn from(v: T) -> Self {
Self {
x: v.to_f32(),
y: v.to_f32(),
}
}
}
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
Vec2::new(x.to_f32(), y.to_f32())
}

View File

@@ -6,6 +6,9 @@ pub enum Axis {
Y, Y,
} }
pub trait Axis_ {
}
impl std::ops::Not for Axis { impl std::ops::Not for Axis {
type Output = Self; type Output = Self;

View File

@@ -41,6 +41,11 @@ impl Size {
y: Len::ZERO, y: Len::ZERO,
}; };
pub const REST: Self = Self {
x: Len::REST,
y: Len::REST,
};
pub fn abs(v: Vec2) -> Self { pub fn abs(v: Vec2) -> Self {
Self { Self {
x: Len::abs(v.x), x: Len::abs(v.x),
@@ -97,6 +102,12 @@ impl Len {
rest: 0.0, rest: 0.0,
}; };
pub const REST: Self = Self {
abs: 0.0,
rel: 0.0,
rest: 1.0,
};
pub fn apply_rest(&self) -> UiScalar { pub fn apply_rest(&self) -> UiScalar {
UiScalar { UiScalar {
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 }, rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },

View File

@@ -3,8 +3,7 @@ mod axis;
mod len; mod len;
mod pos; mod pos;
use super::vec2::*; use super::Vec2;
pub use align::*; pub use align::*;
pub use axis::*; pub use axis::*;
pub use len::*; pub use len::*;

View File

@@ -1,83 +1,104 @@
use std::{marker::Unsize, sync::mpsc::Sender};
use crate::{ use crate::{
layout::{ layout::{
Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, RenderedText, Axis, LayerId, Len, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData,
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets, TextureHandle, Textures, UiId, UiRegion, UiVec2, Vec2, Widget, WidgetHandle, WidgetId,
WidgetUpdate,
}, },
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
util::{HashMap, HashSet, Id, TrackedArena}, util::{HashMap, HashSet, RefMap, TrackedArena},
}; };
/// makes your surfaces look pretty /// makes your surfaces look pretty
pub struct Painter<'a, 'c> { pub struct Painter<'a, 'c> {
ctx: &'a mut PainterCtx<'c>, ctx: &'a mut PainterCtx<'c>,
widget: WidgetHandle,
id: WidgetId,
region: UiRegion, region: UiRegion,
mask: MaskIdx, mask: MaskIdx,
textures: Vec<TextureHandle>, textures: Vec<TextureHandle>,
primitives: Vec<PrimitiveHandle>, primitives: Vec<PrimitiveHandle>,
children: Vec<Id>, children: Vec<WidgetId>,
children_width: HashMap<Id, (UiVec2, Len)>, children_width: HashMap<WidgetId, (UiVec2, Len)>,
children_height: HashMap<Id, (UiVec2, Len)>, children_height: HashMap<WidgetId, (UiVec2, Len)>,
pub layer: usize, pub layer: usize,
id: Id,
} }
/// context for a painter; lets you draw and redraw widgets /// context for a painter; lets you draw and redraw widgets
struct PainterCtx<'a> { struct PainterCtx<'a> {
pub widgets: &'a Widgets, ui_id: UiId,
pub active: &'a mut HashMap<Id, WidgetInstance>, pub active: &'a mut HashMap<WidgetId, WidgetInstance>,
pub layers: &'a mut Layers, pub layers: &'a mut PrimitiveLayers,
pub textures: &'a mut Textures, pub textures: &'a mut Textures,
pub masks: &'a mut TrackedArena<Mask, u32>, pub masks: &'a mut TrackedArena<Mask, u32>,
pub text: &'a mut TextData, pub text: &'a mut TextData,
pub output_size: Vec2, pub output_size: Vec2,
pub modules: &'a mut Modules, send: &'a Sender<WidgetUpdate>,
pub cache_width: HashMap<Id, (UiVec2, Len)>, pub cache_width: HashMap<WidgetId, (UiVec2, Len)>,
pub cache_height: HashMap<Id, (UiVec2, Len)>, pub cache_height: HashMap<WidgetId, (UiVec2, Len)>,
pub needs_redraw: HashSet<Id>, pub needs_redraw: HashSet<WidgetId>,
draw_started: HashSet<Id>, draw_started: HashSet<WidgetId>,
} }
/// stores information for children about the highest level parent that needed their size /// stores information for children about the highest level parent that needed their size
/// so that they can redraw the parent if their size changes /// so that they can redraw the parent if their size changes
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct ResizeRef { pub struct ResizeRef {
x: Option<(Id, (UiVec2, Len))>, x: Option<(WidgetId, (UiVec2, Len))>,
y: Option<(Id, (UiVec2, Len))>, y: Option<(WidgetId, (UiVec2, Len))>,
} }
/// important non rendering data for retained drawing /// important non rendering data for retained drawing
#[derive(Debug)] #[derive(Debug)]
pub struct WidgetInstance { pub struct WidgetInstance {
pub id: Id, pub id: WidgetId,
pub region: UiRegion, pub region: UiRegion,
pub parent: Option<Id>, pub parent: Option<WidgetId>,
pub textures: Vec<TextureHandle>, pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>, pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<Id>, pub children: Vec<WidgetId>,
pub resize: ResizeRef, pub resize: ResizeRef,
pub mask: MaskIdx, pub mask: MaskIdx,
pub layer: usize, pub layer: LayerId,
} }
/// data to be stored in Ui to create PainterCtxs easily /// data to be stored in Ui to create PainterCtxs easily
#[derive(Default)] /// TODO: actually use this LMAO
pub struct PainterData { pub struct PainterData {
pub widgets: Widgets, ui_id: UiId,
pub active: HashMap<Id, WidgetInstance>, pub active: HashMap<WidgetId, WidgetInstance>,
pub layers: Layers, pub layers: PrimitiveLayers,
pub textures: Textures, pub textures: Textures,
pub text: TextData, pub text: TextData,
pub output_size: Vec2, pub output_size: Vec2,
pub modules: Modules, pub px_dependent: HashSet<WidgetId>,
pub px_dependent: HashSet<Id>,
pub masks: TrackedArena<Mask, u32>, pub masks: TrackedArena<Mask, u32>,
send: Sender<WidgetUpdate>,
}
impl PainterData {
pub fn new(ui_id: UiId, send: Sender<WidgetUpdate>) -> Self {
Self {
ui_id,
active: Default::default(),
layers: Default::default(),
textures: Default::default(),
text: Default::default(),
output_size: Default::default(),
px_dependent: Default::default(),
masks: Default::default(),
send,
}
}
} }
impl<'a> PainterCtx<'a> { impl<'a> PainterCtx<'a> {
/// redraws a widget that's currently active (drawn) /// redraws a widget that's currently active (drawn)
/// can be called on something already drawn or removed, /// can be called on something already drawn or removed,
/// will just return if so /// will just return if so
pub fn redraw(&mut self, id: Id) { pub fn redraw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, widget: &WidgetHandle<W>) {
let id = widget.id();
self.needs_redraw.remove(&id); self.needs_redraw.remove(&id);
if self.draw_started.contains(&id) { if self.draw_started.contains(&id) {
return; return;
@@ -105,17 +126,18 @@ impl<'a> PainterCtx<'a> {
cache_height: &mut self.cache_height, cache_height: &mut self.cache_height,
text: self.text, text: self.text,
textures: self.textures, textures: self.textures,
widgets: self.widgets,
outer: *outer, outer: *outer,
output_size: self.output_size, output_size: self.output_size,
checked_width: &mut Default::default(), checked_width: &mut Default::default(),
checked_height: &mut Default::default(), checked_height: &mut Default::default(),
id, id,
} }
.width_inner(id); .width(widget);
if new_desired != *old_desired { if new_desired != *old_desired
&& let Some(w) = rid.strong()
{
// unsure if I need to walk down the tree here // unsure if I need to walk down the tree here
self.redraw(*rid); self.redraw(&w);
*old_desired = new_desired; *old_desired = new_desired;
if self.draw_started.contains(&id) { if self.draw_started.contains(&id) {
ret = true; ret = true;
@@ -131,16 +153,17 @@ impl<'a> PainterCtx<'a> {
cache_height: &mut self.cache_height, cache_height: &mut self.cache_height,
text: self.text, text: self.text,
textures: self.textures, textures: self.textures,
widgets: self.widgets,
outer: *outer, outer: *outer,
output_size: self.output_size, output_size: self.output_size,
checked_width: &mut Default::default(), checked_width: &mut Default::default(),
checked_height: &mut Default::default(), checked_height: &mut Default::default(),
id, id,
} }
.height_inner(id); .height(widget);
if new_desired != *old_desired { if new_desired != *old_desired
self.redraw(*rid); && let Some(w) = rid.strong()
{
self.redraw(&w);
*old_desired = new_desired; *old_desired = new_desired;
if self.draw_started.contains(&id) { if self.draw_started.contains(&id) {
ret = true; ret = true;
@@ -152,13 +175,13 @@ impl<'a> PainterCtx<'a> {
return finish(self, resize); return finish(self, resize);
} }
let Some(active) = self.remove(id) else { let Some(active) = self.remove(id, false) else {
return; return;
}; };
self.draw_inner( self.draw_inner(
active.layer, active.layer,
id, widget,
active.region, active.region,
active.parent, active.parent,
active.mask, active.mask,
@@ -167,15 +190,17 @@ impl<'a> PainterCtx<'a> {
finish(self, resize); finish(self, resize);
} }
fn draw_inner( fn draw_inner<W: Widget + ?Sized + Unsize<dyn Widget>>(
&mut self, &mut self,
layer: usize, layer: usize,
id: Id, widget: &WidgetHandle<W>,
region: UiRegion, region: UiRegion,
parent: Option<Id>, parent: Option<WidgetId>,
mask: MaskIdx, mask: MaskIdx,
old_children: Option<Vec<Id>>, old_children: Option<Vec<WidgetId>>,
) { ) {
let id = widget.id();
widget.data_mut().send = Some(self.send.clone());
// I have no idea if these checks work lol // I have no idea if these checks work lol
// the idea is u can't redraw stuff u already drew, // 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 // and if parent is different then there's another copy with a different parent
@@ -206,7 +231,7 @@ impl<'a> PainterCtx<'a> {
return; return;
} }
// if not, then maintain resize and track old children to remove unneeded // if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id).unwrap(); let active = self.remove(id, false).unwrap();
old_children = active.children; old_children = active.children;
resize = active.resize; resize = active.resize;
} }
@@ -218,6 +243,7 @@ impl<'a> PainterCtx<'a> {
region, region,
mask, mask,
layer, layer,
widget: widget.as_any(),
id, id,
textures: Vec::new(), textures: Vec::new(),
primitives: Vec::new(), primitives: Vec::new(),
@@ -227,7 +253,7 @@ impl<'a> PainterCtx<'a> {
children_height: Default::default(), children_height: Default::default(),
}; };
painter.ctx.widgets.get_dyn_dynamic(id).draw(&mut painter); widget.get_mut_quiet().draw(&mut painter);
let children_width = painter.children_width; let children_width = painter.children_width;
let children_height = painter.children_height; let children_height = painter.children_height;
@@ -267,23 +293,20 @@ impl<'a> PainterCtx<'a> {
} }
} }
// update modules // run events
for m in self.modules.iter_mut() { id.map_event_managers(|mut m| {
m.on_draw(&instance); m.draw(self.ui_id, &instance);
} });
self.active.insert(id, instance); self.active.insert(id, instance);
} }
fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) { fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
let active = self.active.get_mut(&id).unwrap(); let active = self.active.get_mut(&id).unwrap();
for h in &active.primitives { for h in &active.primitives {
let region = self.layers[h.layer].primitives.region_mut(h); let region = self.layers[h.layer].region_mut(h);
*region = region.outside(&from).within(&to); *region = region.outside(&from).within(&to);
} }
active.region = active.region.outside(&from).within(&to); active.region = active.region.outside(&from).within(&to);
for m in self.modules.iter_mut() {
m.on_move(active);
}
// children will not be changed, so this technically should not be needed // children will not be changed, so this technically should not be needed
// probably need unsafe // probably need unsafe
let children = active.children.clone(); let children = active.children.clone();
@@ -293,7 +316,7 @@ impl<'a> PainterCtx<'a> {
} }
/// NOTE: instance textures are cleared and self.textures freed /// NOTE: instance textures are cleared and self.textures freed
fn remove(&mut self, id: Id) -> Option<WidgetInstance> { fn remove(&mut self, id: WidgetId, undraw: bool) -> Option<WidgetInstance> {
let mut inst = self.active.remove(&id); let mut inst = self.active.remove(&id);
if let Some(inst) = &mut inst { if let Some(inst) = &mut inst {
for h in &inst.primitives { for h in &inst.primitives {
@@ -304,15 +327,17 @@ impl<'a> PainterCtx<'a> {
} }
inst.textures.clear(); inst.textures.clear();
self.textures.free(); self.textures.free();
for m in self.modules.iter_mut() { if undraw {
m.on_undraw(inst); id.map_event_managers(|mut m| {
m.undraw(self.ui_id, inst);
});
} }
} }
inst inst
} }
fn remove_rec(&mut self, id: Id) -> Option<WidgetInstance> { fn remove_rec(&mut self, id: WidgetId) -> Option<WidgetInstance> {
let inst = self.remove(id); let inst = self.remove(id, true);
if let Some(inst) = &inst { if let Some(inst) = &inst {
for c in &inst.children { for c in &inst.children {
self.remove_rec(*c); self.remove_rec(*c);
@@ -323,16 +348,16 @@ impl<'a> PainterCtx<'a> {
} }
impl PainterData { impl PainterData {
fn ctx(&mut self, needs_redraw: HashSet<Id>) -> PainterCtx<'_> { fn ctx(&mut self, needs_redraw: HashSet<WidgetId>) -> PainterCtx<'_> {
PainterCtx { PainterCtx {
widgets: &self.widgets, ui_id: self.ui_id,
active: &mut self.active, active: &mut self.active,
layers: &mut self.layers, layers: &mut self.layers,
textures: &mut self.textures, textures: &mut self.textures,
text: &mut self.text, text: &mut self.text,
output_size: self.output_size, output_size: self.output_size,
modules: &mut self.modules,
masks: &mut self.masks, masks: &mut self.masks,
send: &self.send,
cache_width: Default::default(), cache_width: Default::default(),
cache_height: Default::default(), cache_height: Default::default(),
draw_started: Default::default(), draw_started: Default::default(),
@@ -340,17 +365,19 @@ impl PainterData {
} }
} }
pub fn draw(&mut self, id: Id) { pub fn draw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetHandle<W>) {
let mut ctx = self.ctx(Default::default()); let mut ctx = self.ctx(Default::default());
ctx.draw_started.clear(); ctx.draw_started.clear();
ctx.layers.clear(); ctx.layers.clear();
ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None); ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None);
} }
pub fn redraw(&mut self, ids: HashSet<Id>) { pub fn redraw(&mut self, ids: HashSet<WidgetId>) {
let mut ctx = self.ctx(ids); let mut ctx = self.ctx(ids);
while let Some(&id) = ctx.needs_redraw.iter().next() { while let Some(&id) = ctx.needs_redraw.iter().next() {
ctx.redraw(id); if let Some(w) = id.strong() {
ctx.redraw(&w);
}
} }
} }
} }
@@ -360,7 +387,7 @@ impl<'a, 'c> Painter<'a, 'c> {
let h = self.ctx.layers.write( let h = self.ctx.layers.write(
self.layer, self.layer,
PrimitiveInst { PrimitiveInst {
id: self.id, id: self.id(),
primitive, primitive,
region, region,
mask_idx: self.mask, mask_idx: self.mask,
@@ -388,20 +415,28 @@ impl<'a, 'c> Painter<'a, 'c> {
} }
/// Draws a widget within this widget's region. /// Draws a widget within this widget's region.
pub fn widget<W>(&mut self, id: &WidgetId<W>) { pub fn widget<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetHandle<W>) {
self.widget_at(id, self.region); self.widget_at(id, self.region);
} }
/// Draws a widget somewhere within this one. /// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas. /// Useful for drawing child widgets in select areas.
pub fn widget_within<W>(&mut self, id: &WidgetId<W>, region: UiRegion) { pub fn widget_within<W: Widget + ?Sized + Unsize<dyn Widget>>(
&mut self,
id: &WidgetHandle<W>,
region: UiRegion,
) {
self.widget_at(id, region.within(&self.region)); self.widget_at(id, region.within(&self.region));
} }
fn widget_at<W>(&mut self, id: &WidgetId<W>, region: UiRegion) { fn widget_at<W: Widget + ?Sized + Unsize<dyn Widget>>(
self.children.push(id.id); &mut self,
id: &WidgetHandle<W>,
region: UiRegion,
) {
self.children.push(id.id());
self.ctx self.ctx
.draw_inner(self.layer, id.id, region, Some(self.id), self.mask, None); .draw_inner(self.layer, id, region, Some(self.id()), self.mask, None);
} }
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) { pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
@@ -421,18 +456,21 @@ impl<'a, 'c> Painter<'a, 'c> {
/// returns (handle, offset from top left) /// returns (handle, offset from top left)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.ctx.text.draw(buffer, attrs, self.ctx.textures) self.ctx
.text
.get_mut()
.draw(buffer, attrs, self.ctx.textures)
} }
pub fn region(&self) -> UiRegion { pub fn region(&self) -> UiRegion {
self.region self.region
} }
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size { pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>) -> Size {
self.size_ctx().size(id) self.size_ctx().size(id)
} }
pub fn len_axis<W>(&mut self, id: &WidgetId<W>, axis: Axis) -> Len { pub fn len_axis<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len {
match axis { match axis {
Axis::X => self.size_ctx().width(id), Axis::X => self.size_ctx().width(id),
Axis::Y => self.size_ctx().height(id), Axis::Y => self.size_ctx().height(id),
@@ -441,16 +479,15 @@ impl<'a, 'c> Painter<'a, 'c> {
pub fn size_ctx(&mut self) -> SizeCtx<'_> { pub fn size_ctx(&mut self) -> SizeCtx<'_> {
SizeCtx { SizeCtx {
source: self.id(),
id: self.id(),
text: self.ctx.text, text: self.ctx.text,
textures: self.ctx.textures, textures: self.ctx.textures,
widgets: self.ctx.widgets,
output_size: self.ctx.output_size, output_size: self.ctx.output_size,
checked_width: &mut self.children_width, checked_width: &mut self.children_width,
checked_height: &mut self.children_height, checked_height: &mut self.children_height,
cache_width: &mut self.ctx.cache_width, cache_width: &mut self.ctx.cache_width,
cache_height: &mut self.ctx.cache_height, cache_height: &mut self.ctx.cache_height,
source: self.id,
id: self.id,
outer: self.region.size(), outer: self.region.size(),
} }
} }
@@ -475,40 +512,39 @@ impl<'a, 'c> Painter<'a, 'c> {
self.layer = self.ctx.layers.next(self.layer); self.layer = self.ctx.layers.next(self.layer);
} }
pub fn label(&self) -> &str { pub fn label(&self) -> RefMap<'_, String> {
&self.ctx.widgets.data(&self.id).unwrap().label self.widget.get_label()
} }
pub fn id(&self) -> &Id { pub fn id(&self) -> WidgetId {
&self.id self.id
} }
} }
pub struct SizeCtx<'a> { pub struct SizeCtx<'a> {
pub text: &'a mut TextData, pub text: &'a mut TextData,
pub textures: &'a mut Textures, pub textures: &'a mut Textures,
source: Id, source: WidgetId,
widgets: &'a Widgets, cache_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
cache_width: &'a mut HashMap<Id, (UiVec2, Len)>, cache_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
cache_height: &'a mut HashMap<Id, (UiVec2, Len)>, checked_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
checked_width: &'a mut HashMap<Id, (UiVec2, Len)>, checked_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
checked_height: &'a mut HashMap<Id, (UiVec2, Len)>,
/// TODO: should this be pub? rn used for sized /// TODO: should this be pub? rn used for sized
pub outer: UiVec2, pub outer: UiVec2,
output_size: Vec2, output_size: Vec2,
id: Id, id: WidgetId,
} }
impl SizeCtx<'_> { impl SizeCtx<'_> {
pub fn id(&self) -> &Id { pub fn id(&self) -> &WidgetId {
&self.id &self.id
} }
pub fn source(&self) -> &Id { pub fn source(&self) -> &WidgetId {
&self.source &self.source
} }
fn width_inner(&mut self, id: Id) -> Len { pub fn width<W: Widget + ?Sized>(&mut self, widget: &WidgetHandle<W>) -> Len {
// first check cache // first check cache
// TODO: is this needed? broken rn bc does not store children during upper size check, // TODO: is this needed? broken rn bc does not store children during upper size check,
// so if something actually using check_* hits cache it fails to add them // so if something actually using check_* hits cache it fails to add them
@@ -519,11 +555,12 @@ impl SizeCtx<'_> {
// return len; // return len;
// } // }
// store self vars that need to be maintained // store self vars that need to be maintained
let id = widget.id();
let self_outer = self.outer; let self_outer = self.outer;
let self_id = self.id; let self_id = self.id;
// get size of input id // get size of input id
self.id = id; self.id = id;
let len = self.widgets.get_dyn_dynamic(id).desired_width(self); let len = widget.get_mut_quiet().desired_width(self);
// restore vars & update cache + checked // restore vars & update cache + checked
self.outer = self_outer; self.outer = self_outer;
self.id = self_id; self.id = self_id;
@@ -533,17 +570,18 @@ impl SizeCtx<'_> {
} }
// TODO: should be refactored to share code w width_inner // TODO: should be refactored to share code w width_inner
fn height_inner(&mut self, id: Id) -> Len { pub fn height<W: Widget + ?Sized>(&mut self, widget: &WidgetHandle<W>) -> Len {
// if let Some(&(outer, len)) = self.cache_height.get(&id) // if let Some(&(outer, len)) = self.cache_height.get(&id)
// && outer == self.outer // && outer == self.outer
// { // {
// self.checked_height.insert(id, (self.outer, len)); // self.checked_height.insert(id, (self.outer, len));
// return len; // return len;
// } // }
let id = widget.id();
let self_outer = self.outer; let self_outer = self.outer;
let self_id = self.id; let self_id = self.id;
self.id = id; self.id = id;
let len = self.widgets.get_dyn_dynamic(id).desired_height(self); let len = widget.get_mut_quiet().desired_height(self);
self.outer = self_outer; self.outer = self_outer;
self.id = self_id; self.id = self_id;
self.cache_height.insert(id, (self.outer, len)); self.cache_height.insert(id, (self.outer, len));
@@ -551,22 +589,14 @@ impl SizeCtx<'_> {
len len
} }
pub fn width<W>(&mut self, id: &WidgetId<W>) -> Len { pub fn len_axis<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len {
self.width_inner(id.id)
}
pub fn height<W>(&mut self, id: &WidgetId<W>) -> Len {
self.height_inner(id.id)
}
pub fn len_axis<W>(&mut self, id: &WidgetId<W>, axis: Axis) -> Len {
match axis { match axis {
Axis::X => self.width(id), Axis::X => self.width(id),
Axis::Y => self.height(id), Axis::Y => self.height(id),
} }
} }
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size { pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>) -> Size {
Size { Size {
x: self.width(id), x: self.width(id),
y: self.height(id), y: self.height(id),
@@ -582,10 +612,6 @@ impl SizeCtx<'_> {
} }
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.text.draw(buffer, attrs, self.textures) self.text.get_mut().draw(buffer, attrs, self.textures)
}
pub fn label(&self, id: &Id) -> &String {
self.widgets.label(id)
} }
} }

View File

@@ -1,5 +1,7 @@
#![allow(clippy::multiple_bound_locations)] #![allow(clippy::multiple_bound_locations)]
use std::marker::Destruct;
/// stored in linear for sane manipulation /// stored in linear for sane manipulation
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)] #[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
@@ -56,23 +58,47 @@ pub trait ColorNum {
const MAX: Self; const MAX: Self;
} }
impl<T: ColorNum + F32Conversion> Color<T> { macro_rules! map_rgb {
pub fn mul_rgb(self, amt: impl F32Conversion) -> Self { ($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(); 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(); 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) self.mul_rgb(1.0 - amt)
} }
pub fn brighter(self, amt: f32) -> Self { pub const fn brighter(self, amt: f32) -> Self {
self.map_rgb(|x| { map_rgb!(x, self, {
let x = x.to(); let x = x.to();
T::from(x + (1.0 - x) * amt) T::from(x + (1.0 - x) * amt)
}) })

View File

@@ -2,11 +2,14 @@ use std::ops::{Index, IndexMut};
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives}; use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
struct LayerNode { pub type LayerId = usize;
struct LayerNode<T> {
next: Ptr, next: Ptr,
prev: Ptr, prev: Ptr,
child: Option<Child>, child: Option<Child>,
data: Layer, depth: usize,
data: T,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@@ -21,26 +24,22 @@ enum Ptr {
/// TODO: currently this does not ever free layers /// TODO: currently this does not ever free layers
/// is that realistically desired? /// is that realistically desired?
pub struct Layers { pub struct Layers<T> {
vec: Vec<LayerNode>, vec: Vec<LayerNode<T>>,
/// index of last layer at top level (start at first = 0) /// index of last layer at top level (start at first = 0)
last: usize, last: usize,
} }
/// TODO: this can be replaced with Primitives itself atm
#[derive(Default)]
pub struct Layer {
pub primitives: Primitives,
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct Child { struct Child {
head: usize, head: usize,
tail: usize, tail: usize,
} }
impl Layers { pub type PrimitiveLayers = Layers<Primitives>;
pub fn new() -> Layers {
impl<T: Default> Layers<T> {
pub fn new() -> Layers<T> {
Self { Self {
vec: vec![LayerNode::head()], vec: vec![LayerNode::head()],
last: 0, last: 0,
@@ -52,7 +51,7 @@ impl Layers {
self.vec.push(LayerNode::head()); self.vec.push(LayerNode::head());
} }
fn push(&mut self, node: LayerNode) -> usize { fn push(&mut self, node: LayerNode<T>) -> usize {
let i = self.vec.len(); let i = self.vec.len();
self.vec.push(node); self.vec.push(node);
i i
@@ -63,9 +62,10 @@ impl Layers {
return i; return i;
} }
let i_new = self.push(LayerNode::new( let i_new = self.push(LayerNode::new(
Layer::default(), T::default(),
self.vec[i].next, self.vec[i].next,
Ptr::Next(i), Ptr::Next(i),
self.vec[i].depth,
)); ));
self.vec[i].next = Ptr::Next(i_new); self.vec[i].next = Ptr::Next(i_new);
self.vec[i_new].prev = Ptr::Next(i); self.vec[i_new].prev = Ptr::Next(i);
@@ -82,9 +82,10 @@ impl Layers {
return c.head; return c.head;
} }
let i_child = self.push(LayerNode::new( let i_child = self.push(LayerNode::new(
Layer::default(), T::default(),
Ptr::Parent(i), Ptr::Parent(i),
Ptr::Parent(i), Ptr::Parent(i),
self.vec[i].depth + 1,
)); ));
self.vec[i].child = Some(Child { self.vec[i].child = Some(Child {
head: i_child, head: i_child,
@@ -93,107 +94,115 @@ impl Layers {
i_child i_child
} }
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_> { pub fn iter_mut(&mut self) -> LayerIteratorMut<'_, T> {
LayerIteratorMut::new(&mut self.vec, self.last) LayerIteratorMut::new(&mut self.vec, self.last)
} }
pub fn iter_orderless_mut(&mut self) -> impl Iterator<Item = (usize, &mut Layer)> { pub fn iter_orderless_mut(&mut self) -> impl Iterator<Item = (usize, &mut T)> {
self.vec.iter_mut().map(|n| &mut n.data).enumerate() self.vec.iter_mut().map(|n| &mut n.data).enumerate()
} }
pub fn iter(&self) -> impl Iterator<Item = (usize, &Layer)> { pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
self.indices().map(|i| (i, &self.vec[i].data)) self.indices().map(|i| (i, &self.vec[i].data))
} }
pub fn indices(&self) -> LayerIndexIterator<'_> { pub fn iter_depth(&self) -> impl Iterator<Item = ((usize, 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) 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: usize, 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 { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl Index<usize> for Layers { impl<T> Index<usize> for Layers<T> {
type Output = Layer; type Output = T;
fn index(&self, index: usize) -> &Self::Output { fn index(&self, index: usize) -> &Self::Output {
&self.vec[index].data &self.vec[index].data
} }
} }
impl IndexMut<usize> for Layers { impl<T> IndexMut<usize> for Layers<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output { fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.vec[index].data &mut self.vec[index].data
} }
} }
impl LayerNode { impl<T: Default> LayerNode<T> {
pub fn new(data: Layer, next: Ptr, prev: Ptr) -> Self { pub fn new(data: T, next: Ptr, prev: Ptr, depth: usize) -> Self {
Self { Self {
next, next,
prev, prev,
child: None, child: None,
data, data,
depth,
} }
} }
pub fn head() -> Self { 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> { pub struct LayerIteratorMut<'a, T> {
inner: LayerIndexIterator<'a>, inner: LayerIndexIterator<'a, T>,
} }
impl<'a> Iterator for LayerIteratorMut<'a> { impl<'a, T> Iterator for LayerIteratorMut<'a, T> {
type Item = (usize, &'a mut Layer); type Item = (usize, &'a mut T);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let i = self.inner.next()?; let i = self.inner.next()?;
// SAFETY: requires index iterator to work properly // SAFETY: requires index iterator to work properly
#[allow(mutable_transmutes)] #[allow(mutable_transmutes)]
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) }; let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
Some((i, layer)) 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> { fn next_back(&mut self) -> Option<Self::Item> {
let i = self.inner.next_back()?; let i = self.inner.next_back()?;
// SAFETY: requires index iterator to work properly // SAFETY: requires index iterator to work properly
#[allow(mutable_transmutes)] #[allow(mutable_transmutes)]
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) }; let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
Some((i, layer)) Some((i, layer))
} }
} }
impl<'a> LayerIteratorMut<'a> { impl<'a, T> LayerIteratorMut<'a, T> {
fn new(vec: &'a mut Vec<LayerNode>, last: usize) -> Self { fn new(vec: &'a mut Vec<LayerNode<T>>, last: usize) -> Self {
Self { Self {
inner: LayerIndexIterator::new(vec, last), inner: LayerIndexIterator::new(vec, last),
} }
} }
} }
pub struct LayerIndexIterator<'a> { pub struct LayerIndexIterator<'a, T> {
next: Option<usize>, next: Option<usize>,
next_back: 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; type Item = usize;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@@ -224,7 +233,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> { fn next_back(&mut self) -> Option<Self::Item> {
let ret_i = self.next_back?; let ret_i = self.next_back?;
let node = &self.vec[ret_i]; let node = &self.vec[ret_i];
@@ -246,8 +255,8 @@ impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
} }
} }
impl<'a> LayerIndexIterator<'a> { impl<'a, T> LayerIndexIterator<'a, T> {
fn new(vec: &'a Vec<LayerNode>, last: usize) -> Self { fn new(vec: &'a Vec<LayerNode<T>>, last: usize) -> Self {
let mut last = last; let mut last = last;
while let Some(c) = vec[last].child { while let Some(c) = vec[last].child {
last = c.tail; last = c.tail;

View 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::*;

View File

@@ -1,6 +1,9 @@
use std::simd::{Simd, num::SimdUint}; use std::simd::{Simd, num::SimdUint};
use crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2}; use crate::{
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2},
util::Handle,
};
use cosmic_text::{ use cosmic_text::{
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache, Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
SwashContent, SwashContent,
@@ -12,13 +15,15 @@ pub mod text_lib {
pub use cosmic_text::*; pub use cosmic_text::*;
} }
pub struct TextData { pub type TextData = Handle<TextDataInner>;
pub struct TextDataInner {
pub font_system: FontSystem, pub font_system: FontSystem,
pub swash_cache: SwashCache, pub swash_cache: SwashCache,
glyph_cache: Vec<(Placement, CacheKey, Color)>, glyph_cache: Vec<(Placement, CacheKey, Color)>,
} }
impl Default for TextData { impl Default for TextDataInner {
fn default() -> Self { fn default() -> Self {
Self { Self {
font_system: FontSystem::new(), font_system: FontSystem::new(),
@@ -63,7 +68,7 @@ impl Default for TextAttrs {
Self { Self {
color: UiColor::WHITE, color: UiColor::WHITE,
font_size: size, font_size: size,
line_height: size * 1.2, line_height: size * LINE_HEIGHT_MULT,
family: Family::SansSerif, family: Family::SansSerif,
wrap: false, wrap: false,
align: Align::CENTER_LEFT, align: Align::CENTER_LEFT,
@@ -71,7 +76,9 @@ impl Default for TextAttrs {
} }
} }
impl TextData { pub const LINE_HEIGHT_MULT: f32 = 1.1;
impl TextDataInner {
pub fn draw( pub fn draw(
&mut self, &mut self,
buffer: &mut TextBuffer, buffer: &mut TextBuffer,

159
core/src/layout/ui.rs Normal file
View File

@@ -0,0 +1,159 @@
use image::DynamicImage;
use crate::{
layout::{
IdLike, PainterData, PixelRegion, TextureHandle, Vec2, WidgetHandle, WidgetId,
WidgetInstance, WidgetLike, WidgetUpdate,
},
util::{HashMap, HashSet, Id, StaticIdTracker},
};
use std::sync::mpsc::{Receiver, channel};
static ID_TRACKER: StaticIdTracker = StaticIdTracker::new();
pub type UiId = Id;
pub struct Ui {
id: UiId,
pub(crate) data: PainterData,
root: Option<WidgetHandle>,
updates: HashSet<WidgetId>,
recv: Receiver<WidgetUpdate>,
full_redraw: bool,
resized: bool,
}
impl Ui {
pub fn id(&self) -> UiId {
self.id
}
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
self.root = Some(w.add(self));
self.full_redraw = true;
}
pub fn new() -> Self {
Self::default()
}
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
self.data.textures.add(image)
}
pub fn resize(&mut self, size: impl Into<Vec2>) {
self.data.output_size = size.into();
self.resized = true;
}
fn redraw_all(&mut self) {
for (id, inst) in self.data.active.drain() {
id.map_event_managers(|mut m| {
m.undraw(self.id, &inst);
});
}
// free before bc nothing should exist
self.free();
if let Some(root) = &self.root {
self.data.draw(root);
}
}
pub fn update(&mut self) -> bool {
for id in self.recv.try_iter() {
self.updates.insert(id);
}
if self.full_redraw {
self.redraw_all();
self.full_redraw = false;
true
} else if self.resized {
self.resized = false;
self.redraw_all();
true
} else if !self.updates.is_empty() {
self.redraw_updates();
true
} else {
false
}
}
fn redraw_updates(&mut self) {
self.data.redraw(std::mem::take(&mut self.updates));
self.free();
}
/// free any resources that don't have references anymore
fn free(&mut self) {
self.data.textures.free();
}
pub fn needs_redraw(&self) -> bool {
self.full_redraw || !self.updates.is_empty()
}
pub fn active_widgets(&self) -> usize {
self.data.active.len()
}
pub fn active(&self) -> &HashMap<WidgetId, WidgetInstance> {
&self.data.active
}
pub fn debug_layers(&self) {
for ((idx, depth), primitives) in self.data.layers.iter_depth() {
let indent = " ".repeat(depth * 2);
let len = primitives.instances().len();
print!("{indent}{idx}: {len} primitives");
if len >= 1 {
print!(" ({})", primitives.instances()[0].binding);
}
println!();
}
}
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
let region = self.data.active.get(&id.id())?.region;
Some(region.to_px(self.data.output_size))
}
pub fn debug(&self, label: &str) -> impl Iterator<Item = &WidgetInstance> {
self.data.active.iter().filter_map(move |(id, inst)| {
let widget = id.strong().unwrap();
if widget.get_label().as_str() == label {
Some(inst)
} else {
None
}
})
}
pub fn data(&self) -> &PainterData {
&self.data
}
pub fn data_mut(&mut self) -> &mut PainterData {
&mut self.data
}
}
impl Default for Ui {
fn default() -> Self {
let (send, recv) = channel();
let id = ID_TRACKER.next();
Self {
id,
data: PainterData::new(id, send),
root: Default::default(),
updates: Default::default(),
full_redraw: false,
recv,
resized: false,
}
}
}
impl Drop for Ui {
fn drop(&mut self) {
ID_TRACKER.free(self.id);
}
}

17
core/src/layout/view.rs Normal file
View File

@@ -0,0 +1,17 @@
use crate::layout::{Widget, WidgetHandle, WidgetLike};
use std::marker::Unsize;
pub trait WidgetView {
type Widget: Widget + ?Sized + Unsize<dyn Widget> = dyn Widget;
fn view(self) -> WidgetHandle<Self::Widget>;
}
pub struct ViewTag;
impl<WV: WidgetView> WidgetLike<ViewTag> for WV {
type Widget = WV::Widget;
fn add(self, _ui: &mut super::Ui) -> WidgetHandle<Self::Widget> {
self.view()
}
}

View File

@@ -0,0 +1,31 @@
use std::{any::TypeId, sync::mpsc::Sender};
use crate::{
layout::{EVENT_TYPES, EventManager, WIDGETS, WidgetId, WidgetUpdate},
util::{Handle, HashSet},
};
pub struct WidgetData<W: ?Sized> {
pub id: WidgetId,
pub send: Option<Sender<WidgetUpdate>>,
pub label: String,
pub event_managers: HashSet<TypeId>,
pub widget: W,
}
impl<W: ?Sized> WidgetData<W> {
pub(crate) fn event_managers(&self) -> impl Iterator<Item = Handle<dyn EventManager>> {
self.event_managers
.iter()
.map(|id| EVENT_TYPES.lock().unwrap().get_mut(id).unwrap().clone())
}
}
impl<W: ?Sized> Drop for WidgetData<W> {
fn drop(&mut self) {
WIDGETS.remove(self.id);
for mut m in self.event_managers() {
m.remove(self.id);
}
}
}

View File

@@ -0,0 +1,244 @@
use std::{marker::Unsize, mem::MaybeUninit, ops::CoerceUnsized};
use crate::{
layout::{EventManager, IdFnTag, IdTag, Ui, WIDGETS, Widget, WidgetData, WidgetLike},
util::{
Handle, MappedRefGuard, MappedRefGuardMut, Ref, RefGuard, RefGuardMut, RefMap, RefMapMut,
RefMut, WeakHandle,
},
};
pub(super) type SlotTy = u32;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WidgetId {
pub(super) i: SlotTy,
pub(super) genr: SlotTy,
}
/// A strong handle to a widget. Cannot be cloned, only one may exist
/// Use .weak() to obtain weak references to this
pub struct WidgetHandle<W: ?Sized = dyn Widget>(WidgetId, Handle<WidgetData<W>>);
/// A weak handle to a widget. Implements Copy so you can easily pass into closures
pub struct WidgetRef<W: ?Sized = dyn Widget>(WidgetId, *const W);
pub type WidgetUpdate = WidgetId;
impl<W: Widget> WidgetHandle<W> {
pub(super) fn new(id: WidgetId, widget: W) -> Self {
let mut label = std::any::type_name::<W>().to_string();
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
}
Self(
id,
WidgetData {
id,
widget,
send: None,
label,
event_managers: Default::default(),
}
.into(),
)
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetHandle<W> {
pub(super) fn weak_inner(&self) -> WeakHandle<WidgetData<W>> {
self.1.weak()
}
pub fn any(self) -> WidgetHandle<dyn Widget> {
self
}
/// DO NOT CALL OTHER THAN TEMP IN PAINTER
pub(crate) fn as_any(&self) -> WidgetHandle<dyn Widget> {
WidgetHandle(self.0, self.1.clone())
}
}
impl<W: ?Sized> WidgetHandle<W> {
pub fn id(&self) -> WidgetId {
self.0
}
pub fn get(&self) -> RefMap<'_, W> {
Ref::map(self.1.get(), |i| &mut i.widget)
}
pub fn get_mut(&self) -> RefMapMut<'_, W> {
let inner = self.1.get_mut();
if let Some(send) = &inner.send {
let _ = send.send(self.0);
}
RefMut::map(inner, |i| &mut i.widget)
}
pub fn data(&self) -> Ref<'_, WidgetData<W>> {
self.1.get()
}
pub(crate) fn data_mut(&self) -> RefMut<'_, WidgetData<W>> {
self.1.get_mut()
}
pub(crate) fn get_mut_quiet(&self) -> RefMapMut<'_, W> {
RefMut::map(self.1.get_mut(), |i| &mut i.widget)
}
pub fn get_label(&self) -> RefMap<'_, String> {
Ref::map(self.1.get(), |i| &mut i.label)
}
pub fn set_label(&self, label: impl Into<String>) {
self.1.get_mut().label = label.into();
}
/// TODO: THIS SHOULD ALWAYS BE 1, remove this probably (weak count might be nice though)
pub fn refs(&self) -> usize {
self.1.refs()
}
pub fn weak(&self) -> WidgetRef<W> {
WidgetRef::new(self.0)
}
}
pub type WRef<'a, W> = MappedRefGuard<'a, WidgetData<W>, W>;
pub type WRefMut<'a, W> = MappedRefGuardMut<'a, WidgetData<W>, W>;
impl<W: Widget> WidgetRef<W> {
fn handle(&self) -> Handle<WidgetData<W>> {
WIDGETS.get_type(self.0).unwrap()
}
pub fn get<'a>(&self) -> WRef<'a, W> {
RefGuard::map(self.handle().get_take(), |i| &mut i.widget)
}
pub fn get_mut<'a>(&self) -> WRefMut<'a, W> {
let inner = self.handle().get_take_mut();
if let Some(send) = &inner.send {
let _ = send.send(self.0);
}
RefGuardMut::map(inner, |i| &mut i.widget)
}
}
impl<W: ?Sized + Widget> WidgetRef<W> {
pub fn data<'a>(&self) -> Option<RefGuard<'a, WidgetData<dyn Widget>>> {
Some(WIDGETS.get(self.0)?.get_take())
}
pub(crate) fn data_mut(&self) -> Option<RefGuardMut<'_, WidgetData<dyn Widget>>> {
Some(WIDGETS.get(self.0)?.get_take_mut())
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetRef<W> {
pub fn any(self) -> WidgetRef<dyn Widget> {
WidgetRef::new(self.0)
}
}
impl<W: ?Sized> WidgetRef<W> {
fn new(id: WidgetId) -> Self {
Self(id, unsafe { MaybeUninit::zeroed().assume_init() })
}
pub fn id(&self) -> WidgetId {
self.0
}
}
impl WidgetId {
/// THIS SHOULD ONLY BE CALLED FOR TEMP STUFF DURING PAINTING
pub(crate) fn strong(self) -> Option<WidgetHandle> {
Some(WidgetHandle(self, WIDGETS.get(self)?))
}
pub fn weak(self) -> WidgetRef {
WidgetRef::new(self)
}
pub(crate) fn map_event_managers(&self, f: impl Fn(Handle<dyn EventManager>)) {
let Some(data) = self.weak().data() else {
return;
};
for m in data.event_managers() {
f(m)
}
}
}
pub trait WidgetIdFn<W: ?Sized = dyn Widget>: FnOnce(&mut Ui) -> WidgetHandle<W> {}
impl<W: ?Sized, F: FnOnce(&mut Ui) -> WidgetHandle<W>> WidgetIdFn<W> for F {}
pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetHandle {}
impl<F: FnOnce(&mut Ui) -> WidgetHandle> WidgetRet for F {}
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static> WidgetLike<IdTag> for WidgetHandle<W> {
type Widget = W;
fn add(self, _: &mut Ui) -> WidgetHandle<W> {
self
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static, F: FnOnce(&mut Ui) -> WidgetHandle<W>>
WidgetLike<IdFnTag> for F
{
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetHandle<W> {
self(ui)
}
}
pub trait IdLike<W> {
fn id(&self) -> WidgetId;
}
impl<W> IdLike<W> for WidgetHandle<W> {
fn id(&self) -> WidgetId {
self.id()
}
}
impl<W> IdLike<W> for WidgetRef<W> {
fn id(&self) -> WidgetId {
self.id()
}
}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetHandle<U>> for WidgetHandle<T> {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<T> {}
impl<W> PartialEq for WidgetHandle<W> {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl<W> std::fmt::Debug for WidgetHandle<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.id().fmt(f)
}
}
impl<W: ?Sized> Clone for WidgetRef<W> {
fn clone(&self) -> Self {
*self
}
}
impl<W: ?Sized> Copy for WidgetRef<W> {}
impl<W: ?Sized> PartialEq for WidgetRef<W> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
unsafe impl<W: ?Sized> Send for WidgetRef<W> {}
unsafe impl<W: ?Sized> Sync for WidgetRef<W> {}

View File

@@ -0,0 +1,79 @@
use super::*;
use std::marker::Unsize;
pub trait WidgetLike<Tag> {
type Widget: Widget + ?Sized + Unsize<dyn Widget> + 'static;
fn add(self, ui: &mut Ui) -> WidgetHandle<Self::Widget>;
fn with_id<W2>(
self,
f: impl FnOnce(&mut Ui, WidgetHandle<Self::Widget>) -> WidgetHandle<W2>,
) -> impl WidgetIdFn<W2>
where
Self: Sized,
{
move |ui| {
let id = self.add(ui);
f(ui, id)
}
}
fn set_root(self, ui: &mut Ui)
where
Self: Sized,
{
ui.set_root(self);
}
}
pub struct WidgetArr<const LEN: usize> {
pub arr: [WidgetHandle; LEN],
}
impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [WidgetHandle; LEN]) -> Self {
Self { arr }
}
}
pub trait WidgetArrLike<const LEN: usize, Tag> {
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN>;
}
impl<const LEN: usize> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN> {
fn ui(self, _: &mut Ui) -> WidgetArr<LEN> {
self
}
}
// I hate this language it's so bad why do I even use it
macro_rules! impl_widget_arr {
($n:expr;$($W:ident)*) => {
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
};
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
impl<$($W: WidgetLike<$Tag>,$Tag,)*> WidgetArrLike<$n, ($($Tag,)*)> for ($($W,)*) {
fn ui(self, ui: &mut Ui) -> WidgetArr<$n> {
#[allow(non_snake_case)]
let ($($W,)*) = self;
WidgetArr::new(
[$($W.add(ui),)*],
)
}
}
};
}
impl_widget_arr!(1;A);
impl_widget_arr!(2;A B);
impl_widget_arr!(3;A B C);
impl_widget_arr!(4;A B C D);
impl_widget_arr!(5;A B C D E);
impl_widget_arr!(6;A B C D E F);
impl_widget_arr!(7;A B C D E F G);
impl_widget_arr!(8;A B C D E F G H);
impl_widget_arr!(9;A B C D E F G H I);
impl_widget_arr!(10;A B C D E F G H I J);
impl_widget_arr!(11;A B C D E F G H I J K);
impl_widget_arr!(12;A B C D E F G H I J K L);

View File

@@ -0,0 +1,65 @@
mod data;
mod handle;
mod like;
mod tag;
mod widgets;
pub use data::*;
pub use handle::*;
pub use like::*;
pub use tag::*;
pub use widgets::*;
use crate::layout::{Len, Painter, SizeCtx, Ui};
pub trait Widget: 'static + Send + Sync {
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
}
impl Widget for () {
fn draw(&mut self, _: &mut Painter) {}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
}
/// A function that returns a widget given a UI.
/// Useful for defining trait functions on widgets that create a parent widget so that the children
/// don't need to be IDs yet
pub trait WidgetFn<W: Widget + ?Sized>: FnOnce(&mut Ui) -> W {}
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetFn<W> for F {}
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetLike<FnTag> for F {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetHandle<W> {
self(ui).add(ui)
}
}
impl<W: Widget> WidgetLike<WidgetTag> for W {
type Widget = W;
fn add(self, _: &mut Ui) -> WidgetHandle<W> {
WIDGETS.insert(self)
}
}
pub trait WidgetOption {
fn get(self, ui: &mut Ui) -> Option<WidgetHandle>;
}
impl WidgetOption for () {
fn get(self, _: &mut Ui) -> Option<WidgetHandle> {
None
}
}
impl<F: FnOnce(&mut Ui) -> Option<WidgetHandle>> WidgetOption for F {
fn get(self, ui: &mut Ui) -> Option<WidgetHandle> {
self(ui)
}
}

View File

@@ -0,0 +1,5 @@
pub struct WidgetTag;
pub struct FnTag;
pub struct IdTag;
pub struct IdFnTag;
pub struct ArrTag;

View File

@@ -0,0 +1,100 @@
use std::sync::{Mutex, MutexGuard};
use crate::{
layout::{
Widget, WidgetData, WidgetHandle,
widget::{SlotTy, WidgetId},
},
util::{Handle, WeakHandle},
};
pub(crate) static WIDGETS: Widgets = Widgets::new();
pub(crate) struct Widgets(Mutex<Inner>);
pub fn total_widgets() -> usize {
WIDGETS.len()
}
pub struct WidgetSlot {
genr: SlotTy,
data: WeakHandle<WidgetData<dyn Widget + Sync + Send>>,
}
struct Inner {
cur_id: SlotTy,
vec: Vec<WidgetSlot>,
free: Vec<SlotTy>,
}
impl Widgets {
pub const fn new() -> Self {
Self(Mutex::new(Inner {
cur_id: 0,
vec: Vec::new(),
free: Vec::new(),
}))
}
fn expect(&self) -> MutexGuard<'_, Inner> {
self.0.lock().unwrap()
}
pub fn get_type<W: 'static>(&self, id: WidgetId) -> Option<Handle<WidgetData<W>>> {
let slot = &self.expect().vec[id.i as usize];
if slot.genr != id.genr {
None
} else {
Some(unsafe { slot.data.clone().downcast() }.clone().strong()?)
}
}
pub fn get(&self, id: WidgetId) -> Option<Handle<WidgetData<dyn Widget>>> {
let slot = &self.expect().vec[id.i as usize];
if slot.genr != id.genr {
None
} else {
Some(slot.data.clone().strong()?)
}
}
pub fn insert<W: Widget>(&self, widget: W) -> WidgetHandle<W> {
let mut s = self.expect();
let id = s
.free
.pop()
.map(|i| {
assert!(s.vec[i as usize].data.dropped());
WidgetId {
i,
genr: s.vec[i as usize].genr,
}
})
.unwrap_or_else(|| {
let i = s.cur_id;
s.cur_id += 1;
WidgetId { i, genr: 0 }
});
let handle = WidgetHandle::new(id, widget);
let slot = WidgetSlot {
genr: id.genr,
data: handle.weak_inner(),
};
if id.i == s.vec.len() as SlotTy {
s.vec.push(slot);
} else {
s.vec[id.i as usize] = slot;
}
handle
}
pub fn remove(&self, id: WidgetId) {
let mut s = self.expect();
s.vec[id.i as usize].genr += 1;
s.free.push(id.i);
}
pub fn len(&self) -> usize {
let s = self.expect();
s.vec.len() - s.free.len()
}
}

21
core/src/lib.rs Normal file
View File

@@ -0,0 +1,21 @@
#![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)]
#![feature(mapped_lock_guards)]
#![feature(ptr_metadata)]
pub mod layout;
pub mod render;
pub mod util;
pub use image;

View File

@@ -44,7 +44,7 @@ impl MaskIdx {
} }
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Mask { pub struct Mask {
pub region: UiRegion, pub region: UiRegion,
} }

View File

@@ -22,7 +22,7 @@ pub use primitive::*;
const SHAPE_SHADER: &str = include_str!("./shader.wgsl"); const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
pub struct UiRenderer { pub struct UiRenderNode {
uniform_group: BindGroup, uniform_group: BindGroup,
primitive_layout: BindGroupLayout, primitive_layout: BindGroupLayout,
rsc_layout: BindGroupLayout, rsc_layout: BindGroupLayout,
@@ -43,7 +43,7 @@ struct RenderLayer {
primitive_group: BindGroup, primitive_group: BindGroup,
} }
impl UiRenderer { impl UiRenderNode {
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
pass.set_pipeline(&self.pipeline); pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.uniform_group, &[]); pass.set_bind_group(0, &self.uniform_group, &[]);
@@ -61,9 +61,8 @@ impl UiRenderer {
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) { pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
self.active.clear(); self.active.clear();
for (i, ulayer) in ui.data.layers.iter_orderless_mut() { for (i, primitives) in ui.data.layers.iter_mut() {
self.active.push(i); self.active.push(i);
let primitives = &mut ulayer.primitives;
for change in primitives.apply_free() { for change in primitives.apply_free() {
if let Some(inst) = ui.data.active.get_mut(&change.id) { if let Some(inst) = ui.data.active.get_mut(&change.id) {
for h in &mut inst.primitives { for h in &mut inst.primitives {
@@ -97,15 +96,19 @@ impl UiRenderer {
device, device,
&self.primitive_layout, &self.primitive_layout,
rlayer.primitives.buffers(), rlayer.primitives.buffers(),
) );
primitives.updated = false;
} }
} }
if self.textures.update(&mut ui.data.textures) { let mut changed = false;
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks) changed |= self.textures.update(&mut ui.data.textures);
}
if ui.data.masks.changed { if ui.data.masks.changed {
ui.data.masks.changed = false; ui.data.masks.changed = false;
self.masks.update(device, queue, &ui.data.masks[..]); self.masks.update(device, queue, &ui.data.masks[..]);
changed = true;
}
if changed {
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks);
} }
} }

View File

@@ -1,19 +1,17 @@
use std::ops::{Deref, DerefMut};
use crate::{ use crate::{
layout::{Color, UiRegion}, layout::{Color, UiRegion, WidgetId},
render::{ render::{
ArrBuf, ArrBuf,
data::{MaskIdx, PrimitiveInstance}, data::{MaskIdx, PrimitiveInstance},
}, },
util::Id,
}; };
use bytemuck::Pod; use bytemuck::Pod;
use std::ops::{Deref, DerefMut};
use wgpu::*; use wgpu::*;
pub struct Primitives { pub struct Primitives {
instances: Vec<PrimitiveInstance>, instances: Vec<PrimitiveInstance>,
assoc: Vec<Id>, assoc: Vec<WidgetId>,
data: PrimitiveData, data: PrimitiveData,
free: Vec<usize>, free: Vec<usize>,
pub updated: bool, pub updated: bool,
@@ -40,7 +38,7 @@ macro_rules! primitives {
($($name:ident: $ty:ty => $binding:expr,)*) => { ($($name:ident: $ty:ty => $binding:expr,)*) => {
#[derive(Default)] #[derive(Default)]
pub struct PrimitiveData { pub struct PrimitiveData {
$($name: PrimitiveVec<$ty>,)* $(pub(crate) $name: PrimitiveVec<$ty>,)*
} }
pub struct PrimitiveBuffers { pub struct PrimitiveBuffers {
@@ -99,7 +97,7 @@ macro_rules! primitives {
} }
pub struct PrimitiveInst<P> { pub struct PrimitiveInst<P> {
pub id: Id, pub id: WidgetId,
pub primitive: P, pub primitive: P,
pub region: UiRegion, pub region: UiRegion,
pub mask_idx: MaskIdx, pub mask_idx: MaskIdx,
@@ -116,6 +114,7 @@ impl Primitives {
mask_idx, mask_idx,
}: PrimitiveInst<P>, }: PrimitiveInst<P>,
) -> PrimitiveHandle { ) -> PrimitiveHandle {
self.updated = true;
let vec = P::vec(&mut self.data); let vec = P::vec(&mut self.data);
let i = vec.add(primitive); let i = vec.add(primitive);
let inst = PrimitiveInstance { let inst = PrimitiveInstance {
@@ -153,6 +152,7 @@ impl Primitives {
} }
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx { pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self.updated = true;
self.data.free(h.binding, h.data_idx); self.data.free(h.binding, h.data_idx);
self.free.push(h.inst_idx); self.free.push(h.inst_idx);
self.instances[h.inst_idx].mask_idx self.instances[h.inst_idx].mask_idx
@@ -173,7 +173,7 @@ impl Primitives {
} }
pub struct PrimitiveChange { pub struct PrimitiveChange {
pub id: Id, pub id: WidgetId,
pub old: usize, pub old: usize,
pub new: usize, pub new: usize,
} }

View File

@@ -7,7 +7,7 @@ pub struct Arena<T, I> {
tracker: IdTracker<I>, tracker: IdTracker<I>,
} }
impl<T, I: IdNum> Arena<T, I> { impl<T, I: const IdNum> Arena<T, I> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
data: Vec::new(), data: Vec::new(),
@@ -36,7 +36,7 @@ impl<T, I: IdNum> Arena<T, I> {
} }
} }
impl<T, I: IdNum> Default for Arena<T, I> { impl<T, I: const IdNum> Default for Arena<T, I> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
@@ -48,7 +48,7 @@ pub struct TrackedArena<T, I> {
pub changed: bool, pub changed: bool,
} }
impl<T, I: IdNum> TrackedArena<T, I> { impl<T, I: const IdNum> TrackedArena<T, I> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
inner: Arena::default(), inner: Arena::default(),
@@ -86,7 +86,7 @@ impl<T, I: IdNum> TrackedArena<T, I> {
} }
} }
impl<T, I: IdNum> Default for TrackedArena<T, I> { impl<T, I: const IdNum> Default for TrackedArena<T, I> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }

227
core/src/util/handle.rs Normal file
View File

@@ -0,0 +1,227 @@
//! A helper type for Arc<Mutex<T>>
//! Currently there are mut versions (of Ref stuff) which don't do anything different under the hood,
//! leaving them in for now in case I decide RwLock is a desired feature
use std::{
marker::Unsize,
ops::{CoerceUnsized, Deref, DerefMut},
sync::{Arc, MappedMutexGuard, Mutex, MutexGuard, Weak},
};
pub struct Handle<T: ?Sized>(Arc<Mutex<T>>);
pub struct WeakHandle<T: ?Sized>(Weak<Mutex<T>>);
pub type Ref<'a, T> = MutexGuard<'a, T>;
pub type RefMut<'a, T> = MutexGuard<'a, T>;
pub type RefMap<'a, T> = MappedMutexGuard<'a, T>;
pub type RefMapMut<'a, T> = MappedMutexGuard<'a, T>;
impl<T: ?Sized> Handle<T> {
pub fn get(&self) -> Ref<'_, T> {
self.0.lock().unwrap()
}
pub fn get_mut(&self) -> RefMut<'_, T> {
self.0.lock().unwrap()
}
pub fn get_take<'a>(self) -> RefGuard<'a, T> {
let handle = self;
RefGuard {
guard: unsafe {
std::mem::transmute::<MutexGuard<'_, T>, MutexGuard<'_, T>>(
handle.0.lock().unwrap(),
)
},
handle,
}
}
pub fn get_take_mut<'a>(self) -> RefGuardMut<'a, T> {
let handle = self;
RefGuardMut {
guard: unsafe {
std::mem::transmute::<MutexGuard<'_, T>, MutexGuard<'_, T>>(
handle.0.lock().unwrap(),
)
},
handle,
}
}
pub fn refs(&self) -> usize {
Arc::strong_count(&self.0)
}
pub fn weak(&self) -> WeakHandle<T> {
WeakHandle(Arc::downgrade(&self.0))
}
/// # Safety
/// you must guarantee the type outside
/// ideally you check the typeid, but this is often used
/// when the trait object is wrapped one or more times
/// and you'd have to implement each one individually
pub unsafe fn downcast<U: 'static>(self) -> Handle<U>
where
T: 'static,
{
let raw: *const Mutex<T> = Arc::into_raw(self.0);
let raw: *const Mutex<U> = raw.cast();
Handle(unsafe { Arc::from_raw(raw) })
}
}
impl<T: ?Sized> WeakHandle<T> {
pub fn strong(&self) -> Option<Handle<T>> {
Some(Handle(self.0.upgrade()?))
}
pub fn dropped(&self) -> bool {
self.0.strong_count() == 0
}
/// # Safety
/// you must guarantee the type outside
pub unsafe fn downcast<U: 'static>(self) -> WeakHandle<U>
where
T: 'static,
{
let raw: *const Mutex<T> = self.0.into_raw();
let raw: *const Mutex<U> = raw.cast();
WeakHandle(unsafe { Weak::from_raw(raw) })
}
}
impl<T: ?Sized> Clone for Handle<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: ?Sized> Clone for WeakHandle<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Default> Default for Handle<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> From<T> for Handle<T> {
fn from(value: T) -> Self {
Self(Arc::new(Mutex::new(value)))
}
}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Handle<U>> for Handle<T> {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WeakHandle<U>> for WeakHandle<T> {}
// yucky
// TODO: is drop order important here?
// something stupid could happen that I don't know about
// if handle is dropped before guard
pub struct RefGuard<'a, T: ?Sized> {
guard: MutexGuard<'a, T>,
handle: Handle<T>,
}
pub struct RefGuardMut<'a, T: ?Sized> {
guard: MutexGuard<'a, T>,
handle: Handle<T>,
}
pub struct MappedRefGuard<'a, T: ?Sized, U> {
guard: MappedMutexGuard<'a, U>,
handle: Handle<T>,
}
pub struct MappedRefGuardMut<'a, T: ?Sized, U> {
guard: MappedMutexGuard<'a, U>,
handle: Handle<T>,
}
impl<'a, T: ?Sized> RefGuard<'a, T> {
pub fn map<U>(s: Self, f: impl FnOnce(&mut T) -> &mut U) -> MappedRefGuard<'a, T, U> {
MappedRefGuard {
guard: MutexGuard::map(s.guard, f),
handle: s.handle,
}
}
}
impl<'a, T: ?Sized> RefGuardMut<'a, T> {
pub fn map<U>(s: Self, f: impl FnOnce(&mut T) -> &mut U) -> MappedRefGuardMut<'a, T, U> {
MappedRefGuardMut {
guard: MutexGuard::map(s.guard, f),
handle: s.handle,
}
}
}
impl<'a, T: ?Sized, U> MappedRefGuard<'a, T, U> {
pub fn map<U2>(s: Self, f: impl FnOnce(&mut U) -> &mut U2) -> MappedRefGuard<'a, T, U2> {
MappedRefGuard {
guard: MappedMutexGuard::map(s.guard, f),
handle: s.handle,
}
}
}
impl<'a, T: ?Sized, U> MappedRefGuardMut<'a, T, U> {
pub fn map<U2>(s: Self, f: impl FnOnce(&mut U) -> &mut U2) -> MappedRefGuardMut<'a, T, U2> {
MappedRefGuardMut {
guard: MappedMutexGuard::map(s.guard, f),
handle: s.handle,
}
}
}
impl<T: ?Sized> Deref for RefGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
impl<T: ?Sized> Deref for RefGuardMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
impl<T: ?Sized> DerefMut for RefGuardMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard
}
}
impl<T: ?Sized, U> Deref for MappedRefGuard<'_, T, U> {
type Target = U;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
impl<T: ?Sized, U> Deref for MappedRefGuardMut<'_, T, U> {
type Target = U;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
impl<T: ?Sized, U> DerefMut for MappedRefGuardMut<'_, T, U> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard
}
}

View File

@@ -1,3 +1,5 @@
use std::sync::Mutex;
#[repr(C)] #[repr(C)]
#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)] #[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)]
pub struct Id<I = u64>(I); pub struct Id<I = u64>(I);
@@ -9,7 +11,14 @@ pub struct IdTracker<I = u64> {
cur: Id<I>, cur: Id<I>,
} }
impl<I: IdNum> IdTracker<I> { impl<I: const IdNum> IdTracker<I> {
pub const fn new() -> Self {
Self {
free: Vec::new(),
cur: Id(I::first()),
}
}
#[allow(clippy::should_implement_trait)] #[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Id<I> { pub fn next(&mut self) -> Id<I> {
if let Some(id) = self.free.pop() { if let Some(id) = self.free.pop() {
@@ -43,22 +52,19 @@ impl<I: IdNum> Id<I> {
} }
} }
impl<I: IdNum> Default for IdTracker<I> { impl<I: const IdNum> Default for IdTracker<I> {
fn default() -> Self { fn default() -> Self {
Self { Self::new()
free: Vec::new(),
cur: Id(I::first()),
}
} }
} }
pub trait IdNum { pub const trait IdNum {
fn first() -> Self; fn first() -> Self;
fn next(&self) -> Self; fn next(&self) -> Self;
fn idx(&self) -> usize; fn idx(&self) -> usize;
} }
impl IdNum for u64 { impl const IdNum for u64 {
fn first() -> Self { fn first() -> Self {
0 0
} }
@@ -72,7 +78,7 @@ impl IdNum for u64 {
} }
} }
impl IdNum for u32 { impl const IdNum for u32 {
fn first() -> Self { fn first() -> Self {
0 0
} }
@@ -85,3 +91,33 @@ impl IdNum for u32 {
*self as usize *self as usize
} }
} }
pub struct StaticIdTracker<I = u64>(Mutex<IdTracker<I>>);
impl<I: const IdNum> StaticIdTracker<I> {
pub const fn new() -> Self {
Self(Mutex::new(IdTracker::new()))
}
#[allow(clippy::should_implement_trait)]
pub fn next(&self) -> Id<I> {
let mut s = self.0.lock().unwrap();
if let Some(id) = s.free.pop() {
return id;
}
let next = s.cur.next();
std::mem::replace(&mut s.cur, next)
}
#[allow(dead_code)]
pub fn free(&self, id: Id<I>) {
let mut s = self.0.lock().unwrap();
s.free.push(id);
}
}
impl<I: const IdNum> Default for StaticIdTracker<I> {
fn default() -> Self {
Self::new()
}
}

View File

@@ -1,16 +1,18 @@
mod arena; mod arena;
mod borrow;
mod change; mod change;
mod handle;
mod id; mod id;
mod math; mod math;
mod refcount; mod refcount;
mod vec2;
pub(crate) use arena::*; pub use arena::*;
pub(crate) use borrow::*;
pub use change::*; pub use change::*;
pub(crate) use id::*; pub use handle::*;
pub(crate) use math::*; pub use id::*;
pub(crate) use refcount::*; pub use math::*;
pub use refcount::*;
pub use vec2::*;
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>; pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
pub type HashSet<K> = fxhash::FxHashSet<K>; pub type HashSet<K> = fxhash::FxHashSet<K>;

View File

@@ -10,6 +10,7 @@ impl RefCounter {
pub fn new() -> Self { pub fn new() -> Self {
Self(Arc::new(0.into())) Self(Arc::new(0.into()))
} }
#[allow(unused)]
pub fn refs(&self) -> u32 { pub fn refs(&self) -> u32 {
self.0.load(Ordering::Acquire) self.0.load(Ordering::Acquire)
} }
@@ -17,6 +18,16 @@ impl RefCounter {
let refs = self.0.fetch_sub(1, Ordering::Release); let refs = self.0.fetch_sub(1, Ordering::Release);
refs == 0 refs == 0
} }
#[allow(unused)]
pub fn quiet_clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Default for RefCounter {
fn default() -> Self {
Self::new()
}
} }
impl Clone for RefCounter { impl Clone for RefCounter {

View File

@@ -1,8 +1,5 @@
use crate::{ use crate::util::{DivOr, impl_op};
layout::UiNum, use std::{hash::Hash, ops::*};
util::{DivOr, impl_op},
};
use std::{hash::Hash, marker::Destruct, ops::*};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
@@ -20,10 +17,6 @@ impl Hash for Vec2 {
} }
} }
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
Vec2::new(x.to_f32(), y.to_f32())
}
impl Vec2 { impl Vec2 {
pub const ZERO: Self = Self::new(0.0, 0.0); pub const ZERO: Self = Self::new(0.0, 0.0);
pub const ONE: Self = Self::new(1.0, 1.0); pub const ONE: Self = Self::new(1.0, 1.0);
@@ -56,14 +49,15 @@ impl Vec2 {
pub const fn tuple(&self) -> (f32, f32) { pub const fn tuple(&self) -> (f32, f32) {
(self.x, self.y) (self.x, self.y)
} }
}
impl<T: const UiNum + Copy> const From<T> for Vec2 { pub const fn with_x(mut self, x: f32) -> Self {
fn from(v: T) -> Self { self.x = x;
Self { self
x: v.to_f32(), }
y: v.to_f32(),
} pub const fn with_y(mut self, y: f32) -> Self {
self.y = y;
self
} }
} }
@@ -92,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 { impl std::fmt::Debug for Vec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y) write!(f, "({}, {})", self.x, self.y)

14
examples/minimal.rs Normal file
View File

@@ -0,0 +1,14 @@
use iris::{prelude::*};
fn main() {
DefaultApp::<State>::run();
}
struct State;
impl DefaultAppState for State {
fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
rect(Color::RED).set_root(ui);
Self
}
}

11
macro/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "iris-macro"
version.workspace = true
edition.workspace = true
[dependencies]
quote = "1.0.42"
syn = { version = "2.0.111", features = ["full"] }
[lib]
proc-macro = true

77
macro/src/lib.rs Normal file
View File

@@ -0,0 +1,77 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{
Attribute, Block, Ident, ItemTrait, Signature, Token, Visibility,
parse::{Parse, ParseStream, Result},
parse_macro_input, parse_quote,
};
struct Input {
attrs: Vec<Attribute>,
vis: Visibility,
name: Ident,
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()?;
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,
fns,
})
}
}
#[proc_macro]
pub fn widget_trait(input: TokenStream) -> TokenStream {
let Input {
attrs,
vis,
name,
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 mut trai: ItemTrait = parse_quote!(
#vis trait #name<WL: WidgetLike<Tag>, Tag> {
#(#sigs;)*
}
);
trai.attrs = attrs;
TokenStream::from(quote! {
#trai
impl<WL: WidgetLike<Tag>, Tag> #name<WL, Tag> for WL {
#(#impls)*
}
})
}

View File

@@ -1,13 +1,13 @@
# iris # iris
my take on a rust ui library (also my first ui library) my fisrt attempt at a rust ui library
it's called iris because it's the structure around what you actually want to display and colorful it's called iris because it's the structure around what you actually want to display and colorful
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 can just `cargo run` to see it working
goals, in general order: goals, in general order:
1. does what I want it to (video, text, animations) 1. does what I want it to (text, images, video, animations)
2. very easy to use ignoring ergonomic ref counting 2. very easy to use ignoring ergonomic ref counting
3. reasonably fast / efficient (a lot faster than electron, save battery life) 3. reasonably fast / efficient (a lot faster than electron, save battery life)
@@ -17,10 +17,10 @@ general ideas trynna use rn / experiment with:
- retained mode - retained mode
- specifically designed around wgpu - specifically designed around wgpu
- postfix functions for most things to prevent unreadable indentation (going very well) - postfix functions for most things to prevent unreadable indentation (going very well)
- no macros in user code / actual LSP typechecking (variadic generics if you can hear me please save us) - almost no macros in user code & actual LSP typechecking (variadic generics if you can hear me please save us)
- relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout) - relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout)
- single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this) - single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this)
- 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) - 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 sizable chance it gets a rewrite once I know everything I need and what seems to work best

View File

@@ -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);
}
}

View File

@@ -1,77 +1,20 @@
use app::App;
use arboard::Clipboard;
use cosmic_text::Family; use cosmic_text::Family;
use input::Input;
use iris::prelude::*; use iris::prelude::*;
use len_fns::*; use len_fns::*;
use render::Renderer; use winit::event::WindowEvent;
use std::sync::Arc;
use winit::{
dpi::{LogicalPosition, LogicalSize},
event::{Ime, WindowEvent},
event_loop::ActiveEventLoop,
window::Window,
};
mod app;
mod input;
mod render;
fn main() { fn main() {
App::run(); DefaultApp::<Client>::run();
} }
pub struct Client { pub struct Client {
renderer: Renderer, info: WidgetRef<Text>,
window: Arc<Window>,
input: Input,
ui: Ui,
info: WidgetId<Text>,
focus: Option<WidgetId<TextEdit>>,
clipboard: Clipboard,
ime: usize,
} }
#[derive(Eq, PartialEq, Hash, Clone)] event_ctx!(Client);
struct Submit;
impl DefaultEvent for Submit { impl DefaultAppState for Client {
type Data = (); fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
}
pub struct Selectable;
impl WidgetAttr<TextEdit> for Selectable {
type Input = ();
fn run(ui: &mut Ui, id: &WidgetId<TextEdit>, _: Self::Input) {
let id = id.clone();
ui.register_event(
&id.clone(),
CursorSense::click_or_drag(),
move |client: &mut Client, data| {
client
.ui
.text(&id)
.select(data.cursor, data.size, data.sense.is_dragging());
if let Some(region) = client.ui.window_region(&id) {
client.window.set_ime_allowed(true);
client.window.set_ime_cursor_area(
LogicalPosition::<f32>::from(region.top_left.tuple()),
LogicalSize::<f32>::from(region.size().tuple()),
);
}
client.focus = Some(id.clone());
},
);
}
}
impl Client {
pub fn new(window: Arc<Window>) -> Self {
let renderer = Renderer::new(window.clone());
let mut ui = Ui::new();
let rrect = rect(Color::WHITE).radius(20); let rrect = rect(Color::WHITE).radius(20);
let pad_test = ( let pad_test = (
rrect.color(Color::BLUE), rrect.color(Color::BLUE),
@@ -94,7 +37,7 @@ impl Client {
.width(rest(3)), .width(rest(3)),
) )
.span(Dir::RIGHT) .span(Dir::RIGHT)
.add_static(&mut ui); .add(ui);
let span_test = ( let span_test = (
rrect.color(Color::GREEN).width(100), rrect.color(Color::GREEN).width(100),
@@ -105,35 +48,33 @@ impl Client {
rrect.color(Color::RED).width(100), rrect.color(Color::RED).width(100),
) )
.span(Dir::LEFT) .span(Dir::LEFT)
.add_static(&mut ui); .add(ui);
let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui); let span_add = Span::empty(Dir::RIGHT).add(ui);
let span_add_ = span_add.weak();
let add_button = rect(Color::LIME) let add_button = rect(Color::LIME)
.radius(30) .radius(30)
.on(CursorSense::click(), move |ctx: &mut Client, _| { .on(CursorSense::click(), move |ctx| {
let child = ctx let child = image(include_bytes!("assets/sungals.png"))
.ui .center()
.add(image(include_bytes!("assets/sungals.png")).center()) .add(ctx.data.ui);
.any(); span_add_.get_mut().children.push(child);
ctx.ui[span_add].children.push(child);
}) })
.sized((150, 150)) .sized((150, 150))
.align(Align::BOT_RIGHT); .align(Align::BOT_RIGHT);
let del_button = rect(Color::RED) let del_button = rect(Color::RED)
.radius(30) .radius(30)
.on(CursorSense::click(), move |ctx: &mut Client, _| { .on(CursorSense::click(), move |_| {
ctx.ui[span_add].children.pop(); span_add_.get_mut().children.pop();
}) })
.sized((150, 150)) .sized((150, 150))
.align(Align::BOT_LEFT); .align(Align::BOT_LEFT);
let span_add_test = (span_add, add_button, del_button) let span_add_test = (span_add, add_button, del_button).stack().add(ui);
.stack()
.add_static(&mut ui);
let btext = |content| text(content).size(30); let btext = |content| wtext(content).size(30);
let text_test = ( let text_test = (
btext("this is a").align(Align::LEFT), btext("this is a").align(Align::LEFT),
@@ -151,41 +92,43 @@ impl Client {
) )
.span(Dir::RIGHT) .span(Dir::RIGHT)
.center(), .center(),
text("pretty cool right?").size(50), wtext("pretty cool right?").size(50),
) )
.span(Dir::DOWN) .span(Dir::DOWN)
.add_static(&mut ui); .add(ui);
let texts = Span::empty(Dir::DOWN).gap(10).add_static(&mut ui); let texts = Span::empty(Dir::DOWN).gap(10).add(ui);
let texts_ = texts.weak();
let msg_area = texts.scroll().masked().background(rect(Color::SKY)); let msg_area = texts.scroll().masked().background(rect(Color::SKY));
let add_text = text("add") let add_text = wtext("add")
.editable() .editable(false)
.text_align(Align::LEFT) .text_align(Align::LEFT)
.size(30) .size(30)
.attr::<Selectable>(()) .attr::<Selectable>(())
.id_on(Submit, move |id, client: &mut Client, _| { .on(Submit, move |ctx| {
let content = client.ui.text(id).take(); let content = ctx.widget.get_mut().take();
let text = text(content) let text = wtext(content)
.editable() .editable(false)
.size(30) .size(30)
.text_align(Align::LEFT) .text_align(Align::LEFT)
.wrap(true) .wrap(true)
.attr::<Selectable>(()); .attr::<Selectable>(());
let msg_box = text let msg_box = text
.background(rect(Color::WHITE.darker(0.5))) .background(rect(Color::WHITE.darker(0.5)))
.add(&mut client.ui); .add(&mut Ui::new());
client.ui[texts].children.push(msg_box.any()); texts_.get_mut().children.push(msg_box);
}) })
.add(&mut ui); .add(ui);
let add_text_ = add_text.weak();
let text_edit_scroll = ( let text_edit_scroll = (
msg_area.height(rest(1)), msg_area.height(rest(1)),
( (
Rect::new(Color::WHITE.darker(0.9)), Rect::new(Color::WHITE.darker(0.9)),
( (
add_text.clone().width(rest(1)), add_text.width(rest(1)),
Rect::new(Color::GREEN) Rect::new(Color::GREEN)
.on(CursorSense::click(), move |client: &mut Client, _| { .on(CursorSense::click(), move |ctx| {
client.run_event(&add_text, Submit, ()); Events::<Submit, _>::run(add_text_.id(), &mut (), ctx.state);
}) })
.sized((40, 40)), .sized((40, 40)),
) )
@@ -194,151 +137,77 @@ impl Client {
) )
.stack() .stack()
.size(StackSize::Child(1)) .size(StackSize::Child(1))
.offset_layer(1) .layer_offset(1)
.align(Align::BOT), .align(Align::BOT),
) )
.span(Dir::DOWN) .span(Dir::DOWN)
.add_static(&mut ui); .add(ui);
let main = pad_test.pad(10).add_static(&mut ui); let main = pad_test.pad(10).add(ui);
let main_ = main.weak();
let switch_button = |color, to, label| { let tab_handles = Handle::from((0, Vec::new()));
let switch_button = |color, to: Option<WidgetHandle>, label| {
let tab_handles = tab_handles.clone();
let i = tab_handles.get().1.len();
tab_handles.get_mut().1.push(to);
let rect = rect(color) let rect = rect(color)
.id_on(CursorSense::click(), move |id, ui: &mut Ui, _| { .on(CursorSense::click(), move |ctx| {
ui[main].inner.set_static(to); let (prev, all) = &mut *tab_handles.get_mut();
ui[id].color = color.darker(0.3); if let Some(to) = &mut all[i] {
let mut main = main_.get_mut();
std::mem::swap(&mut main.inner, to);
all.swap(*prev, i);
*prev = i;
}
ctx.widget.get_mut().color = color.darker(0.3);
}) })
.edit_on( .on(
CursorSense::HoverStart | CursorSense::unclick(), CursorSense::HoverStart | CursorSense::unclick(),
move |r, _| { move |ctx| {
r.color = color.brighter(0.2); ctx.widget.get_mut().color = color.brighter(0.2);
}, },
) )
.edit_on(CursorSense::HoverEnd, move |r, _| { .on(CursorSense::HoverEnd, move |ctx| {
r.color = color; ctx.widget.get_mut().color = color;
}); });
(rect, text(label).size(30).text_align(Align::CENTER)).stack() (rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
}; };
let tabs = ( let tabs = (
switch_button(Color::RED, pad_test.any(), "pad"), switch_button(Color::RED, None, "pad"),
switch_button(Color::GREEN, span_test.any(), "span"), switch_button(Color::GREEN, Some(span_test), "span"),
switch_button(Color::BLUE, span_add_test.any(), "image span"), switch_button(Color::BLUE, Some(span_add_test), "image span"),
switch_button(Color::MAGENTA, text_test.any(), "text layout"), switch_button(Color::MAGENTA, Some(text_test), "text layout"),
switch_button( switch_button(
Color::YELLOW.mul_rgb(0.5), Color::YELLOW.mul_rgb(0.5),
text_edit_scroll.any(), Some(text_edit_scroll),
"text edit scroll", "text edit scroll",
), ),
) )
.span(Dir::RIGHT); .span(Dir::RIGHT);
let info = text("").add(&mut ui); let info_ = wtext("").add(ui);
let info_sect = info.clone().pad(10).align(Align::RIGHT); let info = info_.weak();
let info_sect = info_.pad(10).align(Align::RIGHT);
((tabs.height(40), main).span(Dir::DOWN), info_sect) ((tabs.height(40), main).span(Dir::DOWN), info_sect)
.stack() .stack()
.set_root(&mut ui); .set_root(ui);
Self { Self { info }
renderer,
window,
input: Input::default(),
ui,
info,
focus: None,
clipboard: Clipboard::new().unwrap(),
ime: 0,
}
} }
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) {
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::Copy(text) => {
if let Err(err) = self.clipboard.set_text(text) {
eprintln!("failed to copy text to clipboard: {err}")
}
}
TextInputResult::Unused | TextInputResult::Used => (),
}
}
}
WindowEvent::Ime(ime) => {
if let Some(sel) = &self.focus {
let mut text = self.ui.text(sel);
match ime {
Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => {
// TODO: highlight once that's real
text.replace(self.ime, &content);
self.ime = content.chars().count();
}
Ime::Commit(content) => {
text.insert(&content);
}
}
}
}
_ => (),
}
let new = format!( let new = format!(
"widgets: {}\nactive:{}\nviews: {}", "widgets: {}\nactive:{}\nviews: {}",
self.ui.num_widgets(), total_widgets(),
self.ui.active_widgets(), ui.active_widgets(),
self.renderer.ui.view_count() state.renderer.ui.view_count()
); );
if new != *self.ui[&self.info].content { if new != *self.info.get().content {
*self.ui[&self.info].content = new; *self.info.get_mut().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
} }
} }

View File

View File

@@ -1,386 +0,0 @@
use crate::prelude::*;
use std::{
ops::{BitOr, Deref, DerefMut},
rc::Rc,
};
use crate::{
layout::{UiModule, UiRegion, Vec2},
util::{HashMap, Id},
};
#[derive(Clone, Copy, PartialEq)]
pub enum Button {
Left,
Right,
Middle,
}
#[derive(Clone, Copy, 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 click_or_drag() -> CursorSenses {
Self::click() | Self::Pressing(Button::Left)
}
pub fn unclick() -> Self {
Self::PressEnd(Button::Left)
}
pub fn is_dragging(&self) -> bool {
matches!(self, CursorSense::Pressing(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,
/// the (first) sense that triggered this event
/// the senses are checked in order
pub sense: CursorSense,
}
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_px(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 let Some(sense) = 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,
sense,
};
(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,
) -> 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 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(),
}
}
}

53
src/default/app.rs Normal file
View 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();
}
}

64
src/default/attr.rs Normal file
View File

@@ -0,0 +1,64 @@
use crate::{default::UiState, prelude::*};
use std::time::{Duration, Instant};
use winit::dpi::{LogicalPosition, LogicalSize};
event_ctx!(UiState);
pub struct Selector;
impl<W: Widget + 'static> WidgetAttr<W> for Selector {
type Input = WidgetRef<TextEdit>;
fn run(ui: &mut Ui, container: WidgetRef<W>, id: Self::Input) {
container.on(CursorSense::click_or_drag(), move |ctx| {
let ui = &mut ctx.data.ui;
let region = ui.window_region(&id).unwrap();
let id_pos = region.top_left;
let container_pos = ui.window_region(&container).unwrap().top_left;
let pos = ctx.data.pos + container_pos - id_pos;
let size = region.size();
select(ui, id, ctx.state, pos, size, ctx.data.sense.is_dragging());
});
}
}
pub struct Selectable;
impl WidgetAttr<TextEdit> for Selectable {
type Input = ();
fn run(ui: &mut Ui, id: WidgetRef<TextEdit>, _: Self::Input) {
id.on(CursorSense::click_or_drag(), move |ctx| {
select(
ctx.data.ui,
id,
ctx.state,
ctx.data.pos,
ctx.data.size,
ctx.data.sense.is_dragging(),
);
});
}
}
fn select(
ui: &mut Ui,
id: WidgetRef<TextEdit>,
state: &mut UiState,
pos: Vec2,
size: Vec2,
dragging: bool,
) {
let now = Instant::now();
let recent = (now - state.last_click) < Duration::from_millis(300);
state.last_click = now;
id.get_mut().select(pos, size, dragging, recent);
if let Some(region) = ui.window_region(&id) {
state.window.set_ime_allowed(true);
state.window.set_ime_cursor_area(
LogicalPosition::<f32>::from(region.top_left.tuple()),
LogicalSize::<f32>::from(region.size().tuple()),
);
}
state.focus = Some(id);
}

9
src/default/event.rs Normal file
View File

@@ -0,0 +1,9 @@
use iris_core::layout::Event;
#[derive(Clone)]
pub struct Submit;
impl Event for Submit {}
#[derive(Clone)]
pub struct Edited;
impl Event for Edited {}

View File

@@ -1,7 +1,7 @@
use crate::Client; use crate::{
use iris::{ widget::{CursorState, Modifiers},
core::{CursorState, Modifiers},
layout::Vec2, layout::Vec2,
default::UiState,
}; };
use winit::{ use winit::{
event::{MouseButton, MouseScrollDelta, WindowEvent}, event::{MouseButton, MouseScrollDelta, WindowEvent},
@@ -32,10 +32,14 @@ impl Input {
} }
} }
WindowEvent::MouseWheel { delta, .. } => { WindowEvent::MouseWheel { delta, .. } => {
let delta = match *delta { let mut delta = match *delta {
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y), MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
MouseScrollDelta::PixelDelta(pos) => Vec2::new(pos.x as f32, pos.y as f32), 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; self.cursor.scroll_delta = delta;
} }
WindowEvent::CursorLeft { .. } => { WindowEvent::CursorLeft { .. } => {
@@ -66,7 +70,7 @@ impl Input {
} }
} }
impl Client { impl UiState {
pub fn window_size(&self) -> Vec2 { pub fn window_size(&self) -> Vec2 {
let size = self.renderer.window().inner_size(); let size = self.renderer.window().inner_size();
(size.width, size.height).into() (size.width, size.height).into()

180
src/default/mod.rs Normal file
View File

@@ -0,0 +1,180 @@
use crate::prelude::*;
use arboard::Clipboard;
use std::sync::Arc;
use std::time::Instant;
use winit::event::{Ime, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
use winit::window::{Window, WindowAttributes};
mod app;
mod attr;
mod event;
mod input;
mod render;
pub use app::*;
pub use attr::*;
pub use event::*;
pub use input::*;
pub use render::*;
pub type Proxy<Event> = EventLoopProxy<Event>;
pub type DefaultApp<Data> = App<DefaultState<Data>>;
pub struct DefaultState<AppState> {
ui: Ui,
ui_state: UiState,
app_state: AppState,
}
pub struct UiState {
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,
}
pub trait DefaultAppState: 'static {
type Event: 'static = ();
fn new(ui: &mut Ui, state: &UiState, proxy: Proxy<Self::Event>) -> Self;
#[allow(unused_variables)]
fn event(&mut self, event: Self::Event, ui: &mut Ui, state: &UiState) {}
#[allow(unused_variables)]
fn exit(&mut self, ui: &mut Ui, state: &UiState) {}
#[allow(unused_variables)]
fn window_event(&mut self, event: WindowEvent, ui: &mut Ui, state: &UiState) {}
fn window_attrs() -> WindowAttributes {
WindowAttributes::default()
}
}
impl<State: DefaultAppState> AppState for DefaultState<State> {
type Event = State::Event;
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
let window = Arc::new(
event_loop
.create_window(State::window_attrs())
.expect("failed to create window "),
);
let mut ui = Ui::new();
let ui_state = UiState {
renderer: UiRenderer::new(window.clone()),
window,
input: Input::default(),
clipboard: Clipboard::new().unwrap(),
ime: 0,
last_click: Instant::now(),
focus: None,
};
let app_state = State::new(&mut ui, &ui_state, proxy);
Self {
ui,
ui_state,
app_state,
}
}
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
self.app_state.event(event, &mut self.ui, &self.ui_state);
}
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
let Self {
ui,
ui_state,
app_state,
} = 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();
// call sensors with all 3 important contexts
// TODO: allow user to specify custom contexts?
// and give them both states in case they need both
ui.run_sensors(&mut (), &cursor_state, window_size);
ui.run_sensors(ui_state, &cursor_state, window_size);
ui.run_sensors(app_state, &cursor_state, window_size);
}
if old != ui_state.focus
&& let Some(old) = old
{
old.get_mut().deselect();
}
match &event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
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 res = sel.get_mut().apply_event(event, &ui_state.input.modifiers);
match res {
TextInputResult::Unfocus => {
ui_state.focus = None;
ui_state.window.set_ime_allowed(false);
}
TextInputResult::Submit => {
Events::<Submit, _>::run(sel.id(), &mut (), app_state);
}
TextInputResult::Paste => {
if let Ok(t) = ui_state.clipboard.get_text() {
sel.get_mut().insert(&t);
}
Events::<Edited, _>::run(sel.id(), &mut (), app_state);
}
TextInputResult::Copy(text) => {
if let Err(err) = ui_state.clipboard.set_text(text) {
eprintln!("failed to copy text to clipboard: {err}")
}
}
TextInputResult::Used => {
Events::<Edited, _>::run(sel.id(), &mut (), app_state);
}
TextInputResult::Unused => {}
}
}
}
WindowEvent::Ime(ime) => {
if let Some(sel) = &ui_state.focus {
match ime {
Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => {
// TODO: highlight once that's real
sel.get_mut().replace(ui_state.ime, content);
ui_state.ime = content.chars().count();
}
Ime::Commit(content) => {
sel.get_mut().insert(content);
}
}
}
}
_ => (),
}
app_state.window_event(event, ui, ui_state);
if ui.update() {
ui_state.renderer.window().request_redraw();
}
ui_state.input.end_frame();
}
fn exit(&mut self) {
self.app_state.exit(&mut self.ui, &self.ui_state);
}
}

View File

@@ -1,15 +1,15 @@
use crate::{
layout::Ui,
render::{UiLimits, UiRenderNode},
};
use pollster::FutureExt; use pollster::FutureExt;
use std::sync::Arc; use std::sync::Arc;
use iris::{
layout::Ui,
render::{UiLimits, UiRenderer},
};
use wgpu::{util::StagingBelt, *}; use wgpu::{util::StagingBelt, *};
use winit::{dpi::PhysicalSize, window::Window}; use winit::{dpi::PhysicalSize, window::Window};
pub const CLEAR_COLOR: Color = Color::BLACK; pub const CLEAR_COLOR: Color = Color::BLACK;
pub struct Renderer { pub struct UiRenderer {
window: Arc<Window>, window: Arc<Window>,
surface: Surface<'static>, surface: Surface<'static>,
device: Device, device: Device,
@@ -17,10 +17,10 @@ pub struct Renderer {
config: SurfaceConfiguration, config: SurfaceConfiguration,
encoder: CommandEncoder, encoder: CommandEncoder,
staging_belt: StagingBelt, staging_belt: StagingBelt,
pub ui: UiRenderer, pub ui: UiRenderNode,
} }
impl Renderer { impl UiRenderer {
pub fn update(&mut self, updates: &mut Ui) { pub fn update(&mut self, updates: &mut Ui) {
self.ui.update(&self.device, &self.queue, updates); self.ui.update(&self.device, &self.queue, updates);
} }
@@ -72,6 +72,7 @@ impl Renderer {
let instance = Instance::new(&InstanceDescriptor { let instance = Instance::new(&InstanceDescriptor {
backends: Backends::PRIMARY, backends: Backends::PRIMARY,
flags: InstanceFlags::empty(),
..Default::default() ..Default::default()
}); });
@@ -100,6 +101,7 @@ impl Renderer {
.max_binding_array_elements_per_shader_stage(), .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: ui_limits
.max_binding_array_sampler_elements_per_shader_stage(), .max_binding_array_sampler_elements_per_shader_stage(),
max_buffer_size: 1 << 30,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
@@ -131,7 +133,7 @@ impl Renderer {
let staging_belt = StagingBelt::new(4096 * 4); let staging_belt = StagingBelt::new(4096 * 4);
let encoder = Self::create_encoder(&device); 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 { Self {
surface, surface,

66
src/event.rs Normal file
View File

@@ -0,0 +1,66 @@
use crate::prelude::*;
use iris_macro::widget_trait;
pub mod eventable {
use super::*;
widget_trait! {
pub trait Eventable;
fn on<E: EventAlias, Ctx: 'static>(
self,
event: E,
f: impl for<'a> EventIdFn<Ctx, <E::Event as Event>::Data<'a>, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> {
move |ui| {
let id = self.add(ui);
Events::<E::Event, Ctx>::register(id.weak(), event.into_event(), f);
id
}
}
}
}
// TODO: naming in here is a bit weird like eventable
#[macro_export]
macro_rules! event_ctx {
($ty: ty) => {
mod local_event_trait {
use super::*;
#[allow(unused_imports)]
use $crate::prelude::*;
widget_trait! {
#[allow(unused)]
pub trait EventableCtx;
fn on<E: EventAlias>(
self,
event: E,
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> {
eventable::Eventable::on(self, event, f)
}
}
use std::marker::Sized;
#[allow(unused)]
pub trait EventableCtxRef<W: Widget + ?Sized> {
fn on<E: EventAlias>(
self,
event: E,
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, W>,
);
}
impl<W: Widget + ?Sized> EventableCtxRef<W> for WidgetRef<W> {
fn on<E: EventAlias>(
self,
event: E,
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, W>,
) {
Events::<E::Event, $ty>::register(self, event.into_event(), f);
}
}
}
use local_event_trait::*;
};
}
pub use event_ctx;

View File

@@ -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();
}
}
}

View File

@@ -1,199 +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.register_event(&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());
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -1,253 +0,0 @@
use image::DynamicImage;
use crate::{
core::{TextEdit, TextEditCtx},
layout::{
Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, StaticWidgetId,
TextureHandle, Vec2, Widget, WidgetId, WidgetInstance, WidgetLike,
},
util::{HashSet, 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: HashSet<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 register_event<W, E: Event, Ctx: 'static>(
&mut self,
id: &WidgetId<W>,
event: E,
f: impl EventFn<Ctx, E::Data>,
) {
self.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id, event, f);
}
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();
if let Some(root) = &self.root {
self.data.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) {
self.data.redraw(std::mem::take(&mut self.updates));
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.insert(id.id());
TextEditCtx {
text: self.data.widgets.get_mut(id).unwrap(),
font_system: &mut self.data.text.font_system,
}
}
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
let region = self.data.active.get(&id.id())?.region;
Some(region.to_px(self.data.output_size))
}
pub fn debug(&self, label: &str) -> impl Iterator<Item = &WidgetInstance> {
self.data.active.iter().filter_map(move |(id, inst)| {
let l = &self.data.widgets.label(id);
if *l == label { Some(inst) } else { None }
})
}
}
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.insert(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.insert(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,
}
}
}

View File

@@ -1,153 +0,0 @@
use crate::layout::{Len, Painter, SizeCtx, StaticWidgetId, Ui, WidgetId, WidgetIdFn};
use std::{any::Any, marker::PhantomData};
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;
}
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
}
}
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);
pub trait WidgetOption {
fn get(self, ui: &mut Ui) -> Option<WidgetId>;
}
impl WidgetOption for () {
fn get(self, _: &mut Ui) -> Option<WidgetId> {
None
}
}
impl<F: FnOnce(&mut Ui) -> Option<WidgetId>> WidgetOption for F {
fn get(self, ui: &mut Ui) -> Option<WidgetId> {
self(ui)
}
}

View File

@@ -1,107 +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) {
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.insert_any(id, Box::new(widget), label);
}
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>;

View File

@@ -1,21 +1,23 @@
#![feature(macro_metavar_expr_concat)]
#![feature(const_ops)]
#![feature(const_trait_impl)]
#![feature(const_convert)]
#![feature(map_try_insert)]
#![feature(unboxed_closures)] #![feature(unboxed_closures)]
#![feature(fn_traits)] #![feature(fn_traits)]
#![feature(const_cmp)] #![feature(gen_blocks)]
#![feature(const_destruct)] #![feature(associated_type_defaults)]
#![feature(portable_simd)]
pub mod core; mod default;
pub mod layout; mod event;
pub mod render; mod widget;
pub mod util;
pub use iris_core::*;
pub use iris_macro::*;
pub mod prelude { pub mod prelude {
pub use crate::core::*; pub use super::default::*;
pub use crate::layout::*; pub use super::event::*;
pub use crate::render::*; pub use super::widget::*;
pub use iris_core::layout::*;
pub use iris_core::render::*;
pub use iris_core::util::Handle;
pub use iris_macro::*;
} }

View File

@@ -1,35 +0,0 @@
use std::ops::{Deref, DerefMut};
pub struct DynBorrower<'a, T: ?Sized> {
data: &'a mut T,
borrowed: &'a mut bool,
}
impl<'a, T: ?Sized> DynBorrower<'a, T> {
pub fn new(data: &'a mut T, borrowed: &'a mut bool) -> Self {
if *borrowed {
panic!("tried to mutably borrow the same thing twice");
}
Self { data, borrowed }
}
}
impl<T: ?Sized> Drop for DynBorrower<'_, T> {
fn drop(&mut self) {
*self.borrowed = false;
}
}
impl<T: ?Sized> Deref for DynBorrower<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data
}
}
impl<T: ?Sized> DerefMut for DynBorrower<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.data
}
}

View File

@@ -1,5 +1,5 @@
use crate::prelude::*;
use image::DynamicImage; use image::DynamicImage;
use crate::prelude::*;
pub struct Image { pub struct Image {
handle: TextureHandle, handle: TextureHandle,

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Masked { pub struct Masked {
pub inner: WidgetId, pub inner: WidgetHandle,
} }
impl Widget for Masked { impl Widget for Masked {

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Aligned { pub struct Aligned {
pub inner: WidgetId, pub inner: WidgetHandle,
pub align: Align, pub align: Align,
} }

View File

@@ -0,0 +1,23 @@
use crate::prelude::*;
pub struct LayerOffset {
pub inner: WidgetHandle,
pub offset: usize,
}
impl Widget for LayerOffset {
fn draw(&mut self, painter: &mut Painter) {
for _ in 0..self.offset {
painter.next_layer();
}
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)
}
}

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct MaxSize { pub struct MaxSize {
pub inner: WidgetId, pub inner: WidgetHandle,
pub x: Option<Len>, pub x: Option<Len>,
pub y: Option<Len>, pub y: Option<Len>,
} }

View File

@@ -1,4 +1,5 @@
mod align; mod align;
mod layer;
mod max_size; mod max_size;
mod offset; mod offset;
mod pad; mod pad;
@@ -8,6 +9,7 @@ mod span;
mod stack; mod stack;
pub use align::*; pub use align::*;
pub use layer::*;
pub use max_size::*; pub use max_size::*;
pub use offset::*; pub use offset::*;
pub use pad::*; pub use pad::*;

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Offset { pub struct Offset {
pub inner: WidgetId, pub inner: WidgetHandle,
pub amt: UiVec2, pub amt: UiVec2,
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
pub struct Pad { pub struct Pad {
pub padding: Padding, pub padding: Padding,
pub inner: WidgetId, pub inner: WidgetHandle,
} }
impl Widget for Pad { impl Widget for Pad {
@@ -39,6 +39,13 @@ pub struct Padding {
} }
impl Padding { impl Padding {
pub const ZERO: Self = Self {
left: 0.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
};
pub fn uniform(amt: impl UiNum) -> Self { pub fn uniform(amt: impl UiNum) -> Self {
let amt = amt.to_f32(); let amt = amt.to_f32();
Self { Self {
@@ -75,6 +82,30 @@ impl Padding {
} }
} }
pub fn top(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.top = amt.to_f32();
s
}
pub fn bottom(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.bottom = amt.to_f32();
s
}
pub fn left(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.left = amt.to_f32();
s
}
pub fn right(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.right = amt.to_f32();
s
}
pub fn with_top(mut self, amt: impl UiNum) -> Self { pub fn with_top(mut self, amt: impl UiNum) -> Self {
self.top = amt.to_f32(); self.top = amt.to_f32();
self self

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Scroll { pub struct Scroll {
inner: WidgetId, inner: WidgetHandle,
axis: Axis, axis: Axis,
amt: f32, amt: f32,
snap_end: bool, snap_end: bool,
@@ -41,7 +41,7 @@ impl Widget for Scroll {
} }
impl Scroll { impl Scroll {
pub fn new(inner: WidgetId, axis: Axis) -> Self { pub fn new(inner: WidgetHandle, axis: Axis) -> Self {
Self { Self {
inner, inner,
axis, axis,

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Sized { pub struct Sized {
pub inner: WidgetId, pub inner: WidgetHandle,
pub x: Option<Len>, pub x: Option<Len>,
pub y: Option<Len>, pub y: Option<Len>,
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
pub struct Span { pub struct Span {
pub children: Vec<WidgetId>, pub children: Vec<WidgetHandle>,
pub dir: Dir, pub dir: Dir,
pub gap: f32, pub gap: f32,
} }
@@ -158,7 +158,7 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output { extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
Span { Span {
children: self.children.ui(args.0).arr.to_vec(), children: self.children.ui(args.0).arr.into_iter().collect(),
dir: self.dir, dir: self.dir,
gap: self.gap, gap: self.gap,
} }
@@ -180,3 +180,17 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Ta
self self
} }
} }
impl std::ops::Deref for Span {
type Target = Vec<WidgetHandle>;
fn deref(&self) -> &Self::Target {
&self.children
}
}
impl std::ops::DerefMut for Span {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.children
}
}

View File

@@ -3,16 +3,12 @@ use std::marker::PhantomData;
use crate::prelude::*; use crate::prelude::*;
pub struct Stack { pub struct Stack {
pub children: Vec<WidgetId>, pub children: Vec<WidgetHandle>,
pub size: StackSize, pub size: StackSize,
pub offset: usize,
} }
impl Widget for Stack { impl Widget for Stack {
fn draw(&mut self, painter: &mut Painter) { fn draw(&mut self, painter: &mut Painter) {
for _ in 0..self.offset {
painter.next_layer();
}
let mut iter = self.children.iter(); let mut iter = self.children.iter();
if let Some(child) = iter.next() { if let Some(child) = iter.next() {
painter.child_layer(); painter.child_layer();
@@ -49,7 +45,6 @@ pub enum StackSize {
pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> { pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
pub children: Wa, pub children: Wa,
pub size: StackSize, pub size: StackSize,
pub offset: usize,
_pd: PhantomData<Tag>, _pd: PhantomData<Tag>,
} }
@@ -60,8 +55,7 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output { extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
Stack { Stack {
children: self.children.ui(args.0).arr.to_vec(), children: self.children.ui(args.0).arr.into_iter().collect(),
offset: self.offset,
size: self.size, size: self.size,
} }
} }
@@ -72,7 +66,6 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, T
Self { Self {
children, children,
size: StackSize::default(), size: StackSize::default(),
offset: 0,
_pd: PhantomData, _pd: PhantomData,
} }
} }
@@ -81,9 +74,4 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, T
self.size = size; self.size = size;
self self
} }
pub fn offset_layer(mut self, offset: usize) -> Self {
self.offset = offset;
self
}
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
#[derive(Default)] #[derive(Default)]
pub struct WidgetPtr { pub struct WidgetPtr {
pub inner: Option<WidgetId>, pub inner: Option<WidgetHandle>,
} }
impl Widget for WidgetPtr { impl Widget for WidgetPtr {

298
src/widget/sense.rs Normal file
View File

@@ -0,0 +1,298 @@
use crate::layout::{UiRegion, Vec2};
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,
pub ui: &'a mut Ui,
/// the first sense that triggered this
pub sense: CursorSense,
}
pub trait SensorUi {
fn run_sensors<Ctx: 'static>(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2);
}
impl SensorUi for Ui {
fn run_sensors<Ctx: 'static>(
&mut self,
ctx: &mut Ctx,
cursor: &CursorState,
window_size: Vec2,
) {
let ui_id = self.id();
let layers = std::mem::take(&mut self.data_mut().layers);
let mut active = std::mem::take(&mut *Events::<CursorSenses, Ctx>::active(ui_id));
for i in layers.indices().rev() {
let mut sensed = false;
for (id, state) in active.get_mut(&i).into_iter().flatten() {
let shape = self.active().get(id).unwrap().region;
let region = shape.to_px(window_size);
let in_shape = cursor.exists && region.contains(cursor.pos);
state.hover.update(in_shape);
if state.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: state.hover,
cursor,
ui: self,
// this does not have any meaning;
// might wanna set up Event to have a prepare stage
sense: CursorSense::Hovering,
};
Events::<CursorSenses, Ctx>::run(*id, &mut data, ctx);
}
if sensed {
break;
}
}
*Events::<CursorSenses, Ctx>::active(ui_id) = active;
self.data_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 EventAlias for CursorSense {
type Event = CursorSenses;
fn into_event(self) -> Self::Event {
CursorSenses::from(self)
}
}
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
}
}

View File

@@ -1,18 +1,18 @@
use crate::prelude::*; use crate::prelude::*;
use cosmic_text::{Attrs, Family, Metrics}; use cosmic_text::{Attrs, Family, Metrics};
use std::marker::{PhantomData, Sized}; use std::marker::Sized;
pub struct TextBuilder<O = TextOutput, H: WidgetOption = ()> { pub struct TextBuilder<O = TextOutput, H: WidgetOption = ()> {
pub content: String, pub content: String,
pub attrs: TextAttrs, pub attrs: TextAttrs,
pub hint: H, pub hint: H,
_pd: PhantomData<O>, pub output: O,
} }
impl<O, H: WidgetOption> TextBuilder<O, H> { impl<O, H: WidgetOption> TextBuilder<O, H> {
pub fn size(mut self, size: impl UiNum) -> Self { pub fn size(mut self, size: impl UiNum) -> Self {
self.attrs.font_size = size.to_f32(); self.attrs.font_size = size.to_f32();
self.attrs.line_height = self.attrs.font_size * 1.1; self.attrs.line_height = self.attrs.font_size * LINE_HEIGHT_MULT;
self self
} }
pub fn color(mut self, color: UiColor) -> Self { pub fn color(mut self, color: UiColor) -> Self {
@@ -31,16 +31,20 @@ impl<O, H: WidgetOption> TextBuilder<O, H> {
self.attrs.align = align.into(); self.attrs.align = align.into();
self self
} }
pub fn center_text(mut self) -> Self {
self.attrs.align = Align::CENTER;
self
}
pub fn wrap(mut self, wrap: bool) -> Self { pub fn wrap(mut self, wrap: bool) -> Self {
self.attrs.wrap = wrap; self.attrs.wrap = wrap;
self self
} }
pub fn editable(self) -> TextBuilder<TextEditOutput, H> { pub fn editable(self, single_line: bool) -> TextBuilder<TextEditOutput, H> {
TextBuilder { TextBuilder {
content: self.content, content: self.content,
attrs: self.attrs, attrs: self.attrs,
hint: self.hint, hint: self.hint,
_pd: PhantomData, output: TextEditOutput { single_line },
} }
} }
} }
@@ -51,7 +55,7 @@ impl<O> TextBuilder<O> {
content: self.content, content: self.content,
attrs: self.attrs, attrs: self.attrs,
hint: move |ui: &mut Ui| Some(hint.add(ui).any()), hint: move |ui: &mut Ui| Some(hint.add(ui).any()),
_pd: PhantomData, output: self.output,
} }
} }
} }
@@ -71,7 +75,7 @@ impl TextBuilderOutput for TextOutput {
builder.attrs.line_height, builder.attrs.line_height,
)); ));
let hint = builder.hint.get(ui); let hint = builder.hint.get(ui);
let font_system = &mut ui.data.text.font_system; let font_system = &mut ui.data().text.get_mut().font_system;
buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
let mut text = Text { let mut text = Text {
content: builder.content.into(), content: builder.content.into(),
@@ -83,7 +87,9 @@ impl TextBuilderOutput for TextOutput {
} }
} }
pub struct TextEditOutput; pub struct TextEditOutput {
single_line: bool,
}
impl TextBuilderOutput for TextEditOutput { impl TextBuilderOutput for TextEditOutput {
type Output = TextEdit; type Output = TextEdit;
@@ -92,11 +98,12 @@ impl TextBuilderOutput for TextEditOutput {
builder.attrs.font_size, builder.attrs.font_size,
builder.attrs.line_height, builder.attrs.line_height,
)); ));
let mut text = TextEdit { let mut text = TextEdit::new(
view: TextView::new(buf, builder.attrs, builder.hint.get(ui)), TextView::new(buf, builder.attrs, builder.hint.get(ui)),
selection: Default::default(), builder.output.single_line,
}; ui.data().text.clone(),
let font_system = &mut ui.data.text.font_system; );
let font_system = &mut ui.data().text.get_mut().font_system;
text.buf text.buf
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); .set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
builder.attrs.apply(font_system, &mut text.buf, None); builder.attrs.apply(font_system, &mut text.buf, None);
@@ -112,11 +119,11 @@ impl<O: TextBuilderOutput, H: WidgetOption> FnOnce<(&mut Ui,)> for TextBuilder<O
} }
} }
pub fn text(content: impl Into<String>) -> TextBuilder { pub fn wtext(content: impl Into<String>) -> TextBuilder {
TextBuilder { TextBuilder {
content: content.into(), content: content.into(),
attrs: TextAttrs::default(), attrs: TextAttrs::default(),
hint: (), hint: (),
_pd: PhantomData, output: TextOutput,
} }
} }

View File

@@ -1,7 +1,7 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use crate::prelude::*; use crate::prelude::*;
use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, Motion}; use cosmic_text::{Affinity, Attrs, Cursor, LayoutRun, Motion};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use winit::{ use winit::{
event::KeyEvent, event::KeyEvent,
@@ -9,11 +9,25 @@ use winit::{
}; };
pub struct TextEdit { pub struct TextEdit {
pub(super) view: TextView, view: TextView,
pub(super) selection: TextSelection, selection: TextSelection,
history: Vec<(String, TextSelection)>,
double_hit: Option<Cursor>,
data: TextData,
pub single_line: bool,
} }
impl TextEdit { impl TextEdit {
pub fn new(view: TextView, single_line: bool, data: TextData) -> Self {
Self {
view,
selection: Default::default(),
history: Default::default(),
double_hit: None,
single_line,
data,
}
}
pub fn select_content(&self, start: Cursor, end: Cursor) -> String { pub fn select_content(&self, start: Cursor, end: Cursor) -> String {
let (start, end) = sort_cursors(start, end); let (start, end) = sort_cursors(start, end);
let mut iter = self.buf.lines.iter().skip(start.line); let mut iter = self.buf.lines.iter().skip(start.line);
@@ -30,164 +44,81 @@ impl TextEdit {
str str
} }
} }
}
impl Widget for TextEdit {
fn draw(&mut self, painter: &mut Painter) {
let base = painter.layer;
painter.child_layer();
let region = self.view.draw(painter);
painter.layer = base;
match &self.selection {
TextSelection::None => (),
TextSelection::Pos(cursor) => {
if let Some(offset) = cursor_pos(cursor, &self.buf) {
let size = vec2(1, self.attrs.line_height);
painter.primitive_within(
RectPrimitive::color(Color::WHITE),
size.align(Align::TOP_LEFT).offset(offset).within(&region),
);
}
}
TextSelection::Span { start, end } => {
let (start, end) = sort_cursors(*start, *end);
let line_height = self.attrs.line_height;
let mut highlight = |offset, width: f32| {
painter.primitive_within(
RectPrimitive::color(Color::SKY),
vec2(width, line_height)
.align(Align::TOP_LEFT)
.offset(offset)
.within(&region),
);
};
if let (Some(start_offset), Some(end_offset)) =
(cursor_pos(&start, &self.buf), cursor_pos(&end, &self.buf))
{
let mut iter = self.buf.layout_runs().skip(start.line);
if start.line == end.line {
highlight(start_offset, end_offset.x - start_offset.x);
} else {
let first = iter.next().unwrap();
highlight(start_offset, first.line_w - start_offset.x);
for i in (start.line + 1)..end.line {
let line = iter.next().unwrap();
let offset = vec2(0.0, i as f32 * line_height);
highlight(offset, line.line_w);
}
let offset = vec2(0.0, end.line as f32 * line_height);
highlight(offset, end_offset.x);
}
// painter.primitive_within(
// RectPrimitive::color(Color::WHITE),
// size.align(Align::TOP_LEFT)
// .offset(end_offset)
// .within(&region),
// );
let size = vec2(1, self.attrs.line_height);
painter.primitive_within(
RectPrimitive::color(Color::WHITE),
size.align(Align::TOP_LEFT)
.offset(end_offset)
.within(&region),
);
}
}
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
self.view.desired_width(ctx)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
self.view.desired_height(ctx)
}
}
/// copied & modified from fn found in Editor in cosmic_text
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<Vec2> {
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(vec2(run.line_w, run.line_top));
for glyph in run.glyphs.iter() {
if cursor.index == glyph.start {
return Some(vec2(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(vec2(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 { pub fn take(&mut self) -> String {
let text = self let text = self
.text
.buf .buf
.lines .lines
.drain(..) .drain(..)
.map(|l| l.into_text()) .map(|l| l.into_text())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
self.text self.set("");
.buf
.set_text(self.font_system, "", &Attrs::new(), SHAPING, None);
self.text.selection.clear();
text text
} }
pub fn motion(&mut self, motion: Motion) { pub fn set(&mut self, text: &str) {
if let TextSelection::Pos(cursor) = self.text.selection let text = self.string(text);
&& let Some((cursor, _)) = self.view.buf.set_text(
self.text &mut self.data.get_mut().font_system,
.buf &text,
.cursor_motion(self.font_system, cursor, None, motion) &Attrs::new(),
SHAPING,
None,
);
self.selection.clear();
}
pub fn motion(&mut self, motion: Motion, select: bool) {
if let TextSelection::Pos(cursor) = self.selection
&& let Some(new) = self.buf_motion(cursor, motion)
{ {
self.text.selection = TextSelection::Pos(cursor); self.selection = if select {
} else if let TextSelection::Span { start, end } = self.text.selection { TextSelection::Span {
let (start, end) = sort_cursors(start, end); start: cursor,
let sel = &mut self.text.selection; end: new,
match motion { }
Motion::Left | Motion::LeftWord => *sel = TextSelection::Pos(start), } else {
Motion::Right | Motion::RightWord => *sel = TextSelection::Pos(end), TextSelection::Pos(new)
_ => (), };
} else if let TextSelection::Span { start, end } = self.selection {
if select {
if let Some(cursor) = self.buf_motion(end, motion) {
self.selection = TextSelection::Span { start, end: cursor };
}
} else {
let (start, end) = sort_cursors(start, end);
match motion {
Motion::Left | Motion::LeftWord => self.selection = TextSelection::Pos(start),
Motion::Right | Motion::RightWord => self.selection = TextSelection::Pos(end),
_ => {
if let Some(cursor) = self.buf_motion(end, motion) {
self.selection = TextSelection::Pos(cursor);
}
}
}
} }
} }
} }
pub fn replace(&mut self, len: usize, text: &str) { pub fn replace(&mut self, len: usize, text: &str) {
let text = self.string(text);
for _ in 0..len { for _ in 0..len {
self.delete(false); self.delete(false);
} }
self.insert_inner(text, false); self.insert_inner(&text, false);
}
fn string(&self, text: &str) -> String {
if self.single_line {
text.replace('\n', "")
} else {
text.to_string()
}
} }
pub fn insert(&mut self, text: &str) { pub fn insert(&mut self, text: &str) {
let text = self.string(text);
let mut lines = text.split('\n'); let mut lines = text.split('\n');
let Some(first) = lines.next() else { let Some(first) = lines.next() else {
return; return;
@@ -200,10 +131,10 @@ impl<'a> TextEditCtx<'a> {
} }
pub fn clear_span(&mut self) -> bool { pub fn clear_span(&mut self) -> bool {
if let TextSelection::Span { start, end } = self.text.selection { if let TextSelection::Span { start, end } = self.selection {
self.delete_between(start, end); self.delete_between(start, end);
let (start, _) = sort_cursors(start, end); let (start, _) = sort_cursors(start, end);
self.text.selection = TextSelection::Pos(start); self.selection = TextSelection::Pos(start);
true true
} else { } else {
false false
@@ -211,7 +142,7 @@ impl<'a> TextEditCtx<'a> {
} }
pub fn delete_between(&mut self, start: Cursor, end: Cursor) { pub fn delete_between(&mut self, start: Cursor, end: Cursor) {
let lines = &mut self.text.view.buf.lines; let lines = &mut self.view.buf.lines;
let (start, end) = sort_cursors(start, end); let (start, end) = sort_cursors(start, end);
if start.line == end.line { if start.line == end.line {
let line = &mut lines[start.line]; let line = &mut lines[start.line];
@@ -234,23 +165,26 @@ impl<'a> TextEditCtx<'a> {
fn insert_inner(&mut self, text: &str, mov: bool) { fn insert_inner(&mut self, text: &str, mov: bool) {
self.clear_span(); self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection { if let TextSelection::Pos(cursor) = self.selection {
let line = &mut self.text.view.buf.lines[cursor.line]; let line = &mut self.view.buf.lines[cursor.line];
let mut line_text = line.text().to_string(); let mut line_text = line.text().to_string();
line_text.insert_str(cursor.index, text); line_text.insert_str(cursor.index, text);
edit_line(line, line_text); edit_line(line, line_text);
if mov { if mov {
for _ in 0..text.chars().count() { for _ in 0..text.chars().count() {
self.motion(Motion::Right); self.motion(Motion::Right, false);
} }
} }
} }
} }
pub fn newline(&mut self) { pub fn newline(&mut self) {
if self.single_line {
return;
}
self.clear_span(); self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection { if let TextSelection::Pos(cursor) = &mut self.selection {
let lines = &mut self.text.view.buf.lines; let lines = &mut self.view.buf.lines;
let line = &mut lines[cursor.line]; let line = &mut lines[cursor.line];
let new = line.split_off(cursor.index); let new = line.split_off(cursor.index);
cursor.line += 1; cursor.line += 1;
@@ -261,29 +195,26 @@ impl<'a> TextEditCtx<'a> {
pub fn backspace(&mut self, word: bool) { pub fn backspace(&mut self, word: bool) {
if !self.clear_span() if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.text.selection && let TextSelection::Pos(cursor) = &mut self.selection
&& (cursor.index != 0 || cursor.line != 0) && (cursor.index != 0 || cursor.line != 0)
{ {
self.motion(if word { Motion::LeftWord } else { Motion::Left }); self.motion(if word { Motion::LeftWord } else { Motion::Left }, false);
self.delete(word); self.delete(word);
} }
} }
pub fn delete(&mut self, word: bool) { pub fn delete(&mut self, word: bool) {
if !self.clear_span() if self.clear_span() {
&& let TextSelection::Pos(cursor) = &mut self.text.selection return;
{ }
if let TextSelection::Pos(cursor) = &mut self.selection {
if word { if word {
let start = *cursor; let start = *cursor;
if let Some((end, _)) = if let Some(end) = self.buf_motion(start, Motion::RightWord) {
self.text
.buf
.cursor_motion(self.font_system, start, None, Motion::RightWord)
{
self.delete_between(start, end); self.delete_between(start, end);
} }
} else { } else {
let lines = &mut self.text.view.buf.lines; let lines = &mut self.view.buf.lines;
let line = &mut lines[cursor.line]; let line = &mut lines[cursor.line];
if cursor.index == line.text().len() { if cursor.index == line.text().len() {
if cursor.line == lines.len() - 1 { if cursor.line == lines.len() - 1 {
@@ -303,10 +234,39 @@ impl<'a> TextEditCtx<'a> {
} }
} }
pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool) { fn buf_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
let pos = pos - self.text.region().top_left().to_abs(size); self.view
let hit = self.text.buf.hit(pos.x, pos.y); .buf
let sel = &mut self.text.selection; .cursor_motion(
&mut self.data.get_mut().font_system,
cursor,
None,
motion,
)
.map(|r| r.0)
}
pub fn select_word_at(&mut self, cursor: Cursor) {
if let (Some(start), Some(end)) = (
self.buf_motion(cursor, Motion::LeftWord),
self.buf_motion(cursor, Motion::RightWord),
) {
self.selection = TextSelection::Span { start, end };
}
}
pub fn select_line_at(&mut self, cursor: Cursor) {
let end = self.buf.lines[cursor.line].text().len();
self.selection = TextSelection::Span {
start: Cursor::new(cursor.line, 0),
end: Cursor::new(cursor.line, end),
}
}
pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool, recent: bool) {
let pos = pos - self.region().top_left().to_abs(size);
let hit = self.buf.hit(pos.x, pos.y);
let sel = &mut self.selection;
match sel { match sel {
TextSelection::None => { TextSelection::None => {
if !drag && let Some(hit) = hit { if !drag && let Some(hit) = hit {
@@ -316,13 +276,29 @@ impl<'a> TextEditCtx<'a> {
TextSelection::Pos(pos) => match (hit, drag) { TextSelection::Pos(pos) => match (hit, drag) {
(None, false) => *sel = TextSelection::None, (None, false) => *sel = TextSelection::None,
(None, true) => (), (None, true) => (),
(Some(hit), false) => *pos = hit, (Some(hit), false) => {
if recent && hit == *pos {
self.double_hit = Some(hit);
return self.select_word_at(hit);
} else {
*pos = hit
}
}
(Some(end), true) => *sel = TextSelection::Span { start: *pos, end }, (Some(end), true) => *sel = TextSelection::Span { start: *pos, end },
}, },
TextSelection::Span { start, end } => match (hit, drag) { TextSelection::Span { start, end } => match (hit, drag) {
(None, false) => *sel = TextSelection::None, (None, false) => *sel = TextSelection::None,
(None, true) => *sel = TextSelection::Pos(*start), (None, true) => *sel = TextSelection::Pos(*start),
(Some(hit), false) => *sel = TextSelection::Pos(hit), (Some(hit), false) => {
if recent
&& let Some(double) = self.double_hit
&& double == hit
{
return self.select_line_at(hit);
} else {
*sel = TextSelection::Pos(hit)
}
}
(Some(hit), true) => *end = hit, (Some(hit), true) => *end = hit,
}, },
} }
@@ -334,10 +310,28 @@ impl<'a> TextEditCtx<'a> {
} }
pub fn deselect(&mut self) { pub fn deselect(&mut self) {
self.text.selection = TextSelection::None; self.selection = TextSelection::None;
} }
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult { pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
let old = (self.content(), self.selection);
let mut undo = false;
let res = self.apply_event_inner(event, modifiers, &mut undo);
if undo && let Some((old, selection)) = self.history.pop() {
self.set(&old);
self.selection = selection;
} else if self.content() != old.0 {
self.history.push(old);
}
res
}
fn apply_event_inner(
&mut self,
event: &KeyEvent,
modifiers: &Modifiers,
undo: &mut bool,
) -> TextInputResult {
match &event.logical_key { match &event.logical_key {
Key::Named(named) => match named { Key::Named(named) => match named {
NamedKey::Backspace => self.backspace(modifiers.control), NamedKey::Backspace => self.backspace(modifiers.control),
@@ -352,20 +346,20 @@ impl<'a> TextEditCtx<'a> {
} }
NamedKey::ArrowRight => { NamedKey::ArrowRight => {
if modifiers.control { if modifiers.control {
self.motion(Motion::RightWord) self.motion(Motion::RightWord, modifiers.shift)
} else { } else {
self.motion(Motion::Right) self.motion(Motion::Right, modifiers.shift)
} }
} }
NamedKey::ArrowLeft => { NamedKey::ArrowLeft => {
if modifiers.control { if modifiers.control {
self.motion(Motion::LeftWord) self.motion(Motion::LeftWord, modifiers.shift)
} else { } else {
self.motion(Motion::Left) self.motion(Motion::Left, modifiers.shift)
} }
} }
NamedKey::ArrowUp => self.motion(Motion::Up), NamedKey::ArrowUp => self.motion(Motion::Up, modifiers.shift),
NamedKey::ArrowDown => self.motion(Motion::Down), NamedKey::ArrowDown => self.motion(Motion::Down, modifiers.shift),
NamedKey::Escape => { NamedKey::Escape => {
self.deselect(); self.deselect();
return TextInputResult::Unfocus; return TextInputResult::Unfocus;
@@ -377,24 +371,32 @@ impl<'a> TextEditCtx<'a> {
match text.as_str() { match text.as_str() {
"v" => return TextInputResult::Paste, "v" => return TextInputResult::Paste,
"c" => { "c" => {
if let TextSelection::Span { start, end } = self.text.selection { if let TextSelection::Span { start, end } = self.selection {
let content = self.text.select_content(start, end); let content = self.select_content(start, end);
return TextInputResult::Copy(content);
}
}
"x" => {
if let TextSelection::Span { start, end } = self.selection {
let content = self.select_content(start, end);
self.clear_span();
return TextInputResult::Copy(content); return TextInputResult::Copy(content);
} }
} }
"a" => { "a" => {
if !self.text.buf.lines[0].text().is_empty() if !self.buf.lines[0].text().is_empty() || self.buf.lines.len() > 1 {
|| self.text.buf.lines.len() > 1 let lines = &self.buf.lines;
{
let lines = &self.text.buf.lines;
let last_line = lines.len() - 1; let last_line = lines.len() - 1;
let last_idx = lines[last_line].text().len(); let last_idx = lines[last_line].text().len();
self.text.selection = TextSelection::Span { self.selection = TextSelection::Span {
start: Cursor::new(0, 0), start: Cursor::new(0, 0),
end: Cursor::new(last_line, last_idx), end: Cursor::new(last_line, last_idx),
}; };
} }
} }
"z" => {
*undo = true;
}
_ => self.insert(text), _ => self.insert(text),
} }
} else { } else {
@@ -407,6 +409,137 @@ impl<'a> TextEditCtx<'a> {
} }
} }
impl Widget for TextEdit {
fn draw(&mut self, painter: &mut Painter) {
let base = painter.layer;
painter.child_layer();
self.view.draw(painter);
painter.layer = base;
let region = self.region();
let size = vec2(1, self.attrs.line_height);
match self.selection {
TextSelection::None => (),
TextSelection::Pos(cursor) => {
if let Some(offset) = cursor_pos(cursor, &self.buf) {
painter.primitive_within(
RectPrimitive::color(Color::WHITE),
size.align(Align::TOP_LEFT).offset(offset).within(&region),
);
}
}
TextSelection::Span { start, end } => {
let (start, end) = sort_cursors(start, end);
for (l, x, width) in iter_layout_lines(start, end, &self.buf) {
let top_left = vec2(x, self.attrs.line_height * l as f32);
painter.primitive_within(
RectPrimitive::color(Color::SKY),
size.with_x(width)
.align(Align::TOP_LEFT)
.offset(top_left)
.within(&region),
);
}
if let Some(end_offset) = cursor_pos(end, &self.buf) {
painter.primitive_within(
RectPrimitive::color(Color::WHITE),
size.align(Align::TOP_LEFT)
.offset(end_offset)
.within(&region),
);
}
}
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
self.view.desired_width(ctx)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
self.view.desired_height(ctx)
}
}
/// provides top left + width
fn iter_layout_lines(
start: Cursor,
end: Cursor,
buf: &TextBuffer,
) -> impl Iterator<Item = (usize, f32, f32)> {
gen move {
let mut iter = buf.layout_runs().enumerate();
for (i, line) in iter.by_ref() {
if line.line_i == start.line
&& let Some(start_x) = index_x(&line, start.index)
{
if start.line == end.line
&& let Some(end_x) = index_x(&line, end.index)
{
yield (i, start_x, end_x - start_x);
return;
}
yield (i, start_x, line.line_w - start_x);
break;
}
}
for (i, line) in iter {
if line.line_i > end.line {
return;
}
if line.line_i == end.line
&& let Some(end_x) = index_x(&line, end.index)
{
yield (i, 0.0, end_x);
return;
}
yield (i, 0.0, line.line_w);
}
}
}
/// copied & modified from fn found in Editor in cosmic_text
/// returns x pos of a (non layout) index within an layout run
fn index_x(run: &LayoutRun, index: usize) -> Option<f32> {
for glyph in run.glyphs.iter() {
if index == glyph.start {
return Some(glyph.x);
} else if index > glyph.start && 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 < index {
before += 1;
}
total += 1;
}
let offset = glyph.w * (before as f32) / (total as f32);
return Some(glyph.x + offset);
}
}
None
}
/// returns top of line segment where cursor should visually select
fn cursor_pos(cursor: Cursor, buf: &TextBuffer) -> Option<Vec2> {
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(vec2(run.line_w, run.line_top));
if let Some(pos) = index_x(&run, cursor.index) {
return Some(vec2(pos, run.line_top));
}
}
prev
}
#[derive(Default)] #[derive(Default)]
pub struct Modifiers { pub struct Modifiers {
pub shift: bool, pub shift: bool,
@@ -429,7 +562,7 @@ pub enum TextInputResult {
Paste, Paste,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Clone, Copy)]
pub enum TextSelection { pub enum TextSelection {
#[default] #[default]
None, None,

View File

@@ -21,11 +21,11 @@ pub struct TextView {
// cache // cache
tex: Option<RenderedText>, tex: Option<RenderedText>,
width: Option<f32>, width: Option<f32>,
pub hint: Option<WidgetId>, pub hint: Option<WidgetHandle>,
} }
impl TextView { impl TextView {
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetId>) -> Self { pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetHandle>) -> Self {
Self { Self {
attrs: attrs.into(), attrs: attrs.into(),
buf: buf.into(), buf: buf.into(),
@@ -35,6 +35,8 @@ impl TextView {
} }
} }
/// region where the text should be draw
/// does not include extra height or width from weird unicode
pub fn region(&self) -> UiRegion { pub fn region(&self) -> UiRegion {
self.tex() self.tex()
.map(|t| t.size) .map(|t| t.size)
@@ -65,9 +67,12 @@ impl TextView {
return tex.clone(); return tex.clone();
} }
self.width = width; self.width = width;
let font_system = &mut ctx.text.font_system; let mut text_data = ctx.text.get_mut();
self.attrs.apply(font_system, &mut self.buf, width); self.attrs
self.buf.shape_until_scroll(font_system, false); .apply(&mut text_data.font_system, &mut self.buf, width);
self.buf
.shape_until_scroll(&mut text_data.font_system, false);
drop(text_data);
let tex = ctx.draw_text(&mut self.buf, &self.attrs); let tex = ctx.draw_text(&mut self.buf, &self.attrs);
self.tex = Some(tex.clone()); self.tex = Some(tex.clone());
self.attrs.changed = false; self.attrs.changed = false;
@@ -78,10 +83,24 @@ impl TextView {
self.tex.as_ref() self.tex.as_ref()
} }
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
Len::abs(self.render(ctx).size.x) if let Some(hint) = &self.hint
&& let [line] = &self.buf.lines[..]
&& line.text().is_empty()
{
ctx.width(hint)
} else {
Len::abs(self.render(ctx).size.x)
}
} }
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len { pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
Len::abs(self.render(ctx).size.y) if let Some(hint) = &self.hint
&& let [line] = &self.buf.lines[..]
&& line.text().is_empty()
{
ctx.height(hint)
} else {
Len::abs(self.render(ctx).size.y)
}
} }
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion { pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
let tex = self.render(&mut painter.size_ctx()); let tex = self.render(&mut painter.size_ctx());
@@ -91,8 +110,9 @@ impl TextView {
&& line.text().is_empty() && line.text().is_empty()
{ {
painter.widget(hint); painter.widget(hint);
} else {
painter.texture_within(&tex.handle, region);
} }
painter.texture_within(&tex.handle, region);
region region
} }
@@ -119,7 +139,7 @@ impl Text {
if self.content.changed { if self.content.changed {
self.content.changed = false; self.content.changed = false;
self.view.buf.set_text( self.view.buf.set_text(
&mut ctx.text.font_system, &mut ctx.text.get_mut().font_system,
&self.content, &self.content,
&Attrs::new().family(self.view.attrs.family), &Attrs::new().family(self.view.attrs.family),
SHAPING, SHAPING,

View File

@@ -1,34 +1,22 @@
use super::*;
use crate::prelude::*; use crate::prelude::*;
use iris_macro::widget_trait;
pub trait CoreWidget<W, Tag> { // these methods should "not require any context" (require unit) because they're in core
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>; event_ctx!(());
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned>;
fn center(self) -> impl WidgetFn<Aligned>; widget_trait! {
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W>; pub trait CoreWidget;
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 max_width(self, width: impl Into<Len>) -> impl WidgetFn<MaxSize>;
fn max_height(self, height: impl Into<Len>) -> impl WidgetFn<MaxSize>;
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset>;
fn scroll(self) -> impl WidgetIdFn<Scroll>;
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> { fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|ui| Pad { |ui| Pad {
padding: padding.into(), padding: padding.into(),
inner: self.add(ui).any(), inner: self.add(ui),
} }
} }
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned> { fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned> {
move |ui| Aligned { move |ui| Aligned {
inner: self.add(ui).any(), inner: self.add(ui),
align: align.into(), align: align.into(),
} }
} }
@@ -37,10 +25,10 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
self.align(Align::CENTER) self.align(Align::CENTER)
} }
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W::Widget> { fn label(self, label: impl Into<String>) -> impl WidgetIdFn<WL::Widget> {
|ui| { |ui| {
let id = self.add(ui); let id = self.add(ui);
ui.set_label(&id, label.into()); id.set_label(label);
id id
} }
} }
@@ -48,7 +36,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> { fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> {
let size = size.into(); let size = size.into();
move |ui| Sized { move |ui| Sized {
inner: self.add(ui).any(), inner: self.add(ui),
x: Some(size.x), x: Some(size.x),
y: Some(size.y), y: Some(size.y),
} }
@@ -57,7 +45,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> { fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
let len = len.into(); let len = len.into();
move |ui| MaxSize { move |ui| MaxSize {
inner: self.add(ui).any(), inner: self.add(ui),
x: Some(len), x: Some(len),
y: None, y: None,
} }
@@ -66,7 +54,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> { fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
let len = len.into(); let len = len.into();
move |ui| MaxSize { move |ui| MaxSize {
inner: self.add(ui).any(), inner: self.add(ui),
x: None, x: None,
y: Some(len), y: Some(len),
} }
@@ -75,7 +63,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> { fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
let len = len.into(); let len = len.into();
move |ui| Sized { move |ui| Sized {
inner: self.add(ui).any(), inner: self.add(ui),
x: Some(len), x: Some(len),
y: None, y: None,
} }
@@ -84,7 +72,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> { fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
let len = len.into(); let len = len.into();
move |ui| Sized { move |ui| Sized {
inner: self.add(ui).any(), inner: self.add(ui),
x: None, x: None,
y: Some(len), y: Some(len),
} }
@@ -92,16 +80,17 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> { fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> {
move |ui| Offset { move |ui| Offset {
inner: self.add(ui).any(), inner: self.add(ui),
amt: amt.into(), amt: amt.into(),
} }
} }
fn scroll(self) -> impl WidgetIdFn<Scroll> { fn scroll(self) -> impl WidgetIdFn<Scroll> {
move |ui| { move |ui| {
Scroll::new(self.add(ui).any(), Axis::Y) Scroll::new(self.add(ui), Axis::Y)
.edit_on(CursorSense::Scroll, |w, data| { .on(CursorSense::Scroll, |ctx| {
w.scroll(data.scroll_delta.y * 50.0); let s = &mut *ctx.widget.get_mut();
s.scroll(ctx.data.scroll_delta.y * 50.0);
}) })
.add(ui) .add(ui)
} }
@@ -109,25 +98,38 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn masked(self) -> impl WidgetFn<Masked> { fn masked(self) -> impl WidgetFn<Masked> {
move |ui| Masked { move |ui| Masked {
inner: self.add(ui).any(), inner: self.add(ui),
} }
} }
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> { fn background<T,>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
move |ui| Stack { move |ui| Stack {
children: vec![w.add(ui).any(), self.add(ui).any()], children: vec![w.add(ui), self.add(ui)],
size: StackSize::Child(1), size: StackSize::Child(1),
offset: 0,
} }
} }
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack> { fn foreground<T,>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
move |ui| Stack { move |ui| Stack {
children: vec![self.add(ui).any()], children: vec![self.add(ui), w.add(ui)],
size: StackSize::Child(0), size: StackSize::Child(0),
}
}
fn layer_offset(self, offset: usize) -> impl WidgetFn<LayerOffset> {
move |ui| LayerOffset {
inner: self.add(ui),
offset, offset,
} }
} }
fn to_any(self) -> impl WidgetRet {
|ui| self.add(ui)
}
fn set_ptr(self, ptr: &WidgetHandle<WidgetPtr>, ui: &mut Ui) {
ptr.get_mut().inner = Some(self.add(ui));
}
} }
pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> { pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {