Compare commits

1 Commits

Author SHA1 Message Date
e44bb8eca4 learn how workspaces + proc macros work & restructure everything 2025-12-09 01:52:45 -05:00
76 changed files with 610 additions and 576 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" }

13
core/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[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,4 +1,4 @@
use crate::layout::{Ui, WidgetRef, WidgetIdFn, WidgetLike}; use crate::layout::{Ui, WidgetIdFn, WidgetLike, WidgetRef};
pub trait WidgetAttr<W: ?Sized> { pub trait WidgetAttr<W: ?Sized> {
type Input; type Input;

View File

@@ -33,92 +33,6 @@ impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W: ?Sized> WidgetEven
{ {
} }
// TODO: naming in here is a bit weird like eventable
#[macro_export]
macro_rules! event_ctx {
($ty: ty) => {
mod local_event_trait {
use super::*;
use std::marker::Sized;
#[allow(unused_imports)]
use $crate::prelude::*;
#[allow(unused)]
pub trait EventableCtx<W: ?Sized, Tag, Ctx: 'static> {
fn on<E: Event>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
) -> impl WidgetIdFn<W> + EventableCtx<W, IdFnTag, Ctx>;
}
impl<WL: WidgetLike<Tag>, Tag> EventableCtx<WL::Widget, Tag, $ty> for WL {
fn on<E: Event>(
self,
event: E,
f: impl WidgetEventFn<$ty, E::Data, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> + EventableCtx<WL::Widget, IdFnTag, $ty> {
eventable::Eventable::on(self, event, f)
}
}
#[allow(unused)]
pub trait EventableCtxUi<W: ?Sized, Tag, Ctx: 'static>
where
WidgetRef<W>: EventableCtx<W, Tag, Ctx>,
{
fn on<E: Event>(
&mut self,
widget: &WidgetRef<W>,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
);
}
impl<W: ?Sized + 'static, Tag> EventableCtxUi<W, Tag, $ty> for Ui
where
WidgetRef<W>: EventableCtx<W, Tag, $ty>,
{
fn on<E: Event>(
&mut self,
widget: &WidgetRef<W>,
event: E,
f: impl WidgetEventFn<$ty, E::Data, W>,
) {
self.register_widget_event(&widget, event, f);
}
}
}
use local_event_trait::*;
};
}
pub use event_ctx;
pub mod eventable {
use super::*;
pub trait Eventable<W: ?Sized, Tag> {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>;
}
impl<WL: WidgetLike<Tag>, Tag> Eventable<WL::Widget, Tag> for WL {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> {
move |ui| {
let id = self.add(ui);
ui.register_widget_event(&id, event, f);
id
}
}
}
}
impl Ui { impl Ui {
pub fn register_event<W: ?Sized, E: Event, Ctx: 'static>( pub fn register_event<W: ?Sized, E: Event, Ctx: 'static>(

View File

@@ -1,34 +1,24 @@
mod attr; mod attr;
mod color;
mod event; mod event;
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 view; mod view;
mod widget; mod widget;
mod widget_ref;
mod widgets;
pub use attr::*; pub use attr::*;
pub use color::*;
pub use event::*; pub use event::*;
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 view::*; pub use view::*;
pub use widget::*; pub use widget::*;
pub use widget_ref::*;
pub use widgets::*;
pub use crate::util::Vec2; pub use crate::util::Vec2;
pub type UiColor = Color<u8>; pub type UiColor = Color<u8>;

View File

@@ -1,6 +1,5 @@
use std::marker::Destruct;
use crate::util::Vec2; use crate::util::Vec2;
use std::marker::Destruct;
pub const trait UiNum { pub const trait UiNum {
fn to_f32(self) -> f32; fn to_f32(self) -> 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

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

@@ -2,15 +2,14 @@ use image::DynamicImage;
use crate::{ use crate::{
layout::{ layout::{
Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, TextureHandle, Vec2, Widget, IdLike, PainterData, PixelRegion, TextureHandle, Vec2, Widget, WidgetInstance, WidgetLike,
WidgetEventFn, WidgetInstance, WidgetLike, WidgetRef, WidgetUpdate, WidgetRef, WidgetUpdate,
}, },
util::{HashSet, Id}, util::{HashSet, Id},
}; };
use std::sync::mpsc::{Receiver, channel}; use std::sync::mpsc::{Receiver, channel};
pub struct Ui { pub struct Ui {
// TODO: make this at least pub(super)
pub(crate) data: PainterData, pub(crate) data: PainterData,
root: Option<WidgetRef>, root: Option<WidgetRef>,
updates: HashSet<Id>, updates: HashSet<Id>,
@@ -30,7 +29,7 @@ impl Ui {
} }
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) { pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
self.root = Some(w.add(self).any()); self.root = Some(w.add(self));
self.full_redraw = true; self.full_redraw = true;
} }
@@ -142,6 +141,14 @@ impl Ui {
} }
}) })
} }
pub fn data(&self) -> &PainterData {
&self.data
}
pub fn data_mut(&mut self) -> &mut PainterData {
&mut self.data
}
} }
impl Default for Ui { impl Default for Ui {

View File

@@ -1,12 +1,12 @@
use std::{ use std::{
cell::{Ref, RefMut}, cell::{Ref, RefMut},
marker::Unsize, marker::Unsize,
rc::Rc, ops::CoerceUnsized,
sync::mpsc::Sender, sync::mpsc::Sender,
}; };
use crate::{ use crate::{
layout::{Ui, Widget, WidgetLike}, layout::{IdFnTag, IdTag, Ui, Widget, WidgetLike},
util::{Handle, Id, WeakHandle}, util::{Handle, Id, WeakHandle},
}; };
@@ -128,10 +128,7 @@ impl<W: ?Sized> Drop for Inner<W> {
} }
} }
pub struct IdTag; pub trait WidgetIdFn<W: ?Sized = dyn Widget>: FnOnce(&mut Ui) -> WidgetRef<W> {}
pub struct IdFnTag;
pub trait WidgetIdFn<W: ?Sized>: FnOnce(&mut Ui) -> WidgetRef<W> {}
impl<W: ?Sized, F: FnOnce(&mut Ui) -> WidgetRef<W>> WidgetIdFn<W> for F {} impl<W: ?Sized, F: FnOnce(&mut Ui) -> WidgetRef<W>> WidgetIdFn<W> for F {}
pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetRef {} pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetRef {}
@@ -162,3 +159,5 @@ impl<W> IdLike<W> for WidgetRef<W> {
self.id() self.id()
} }
} }
impl<W: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<W> {}

View File

@@ -1,28 +1,5 @@
use crate::{ use super::*;
core::WidgetPtr, use std::marker::Unsize;
layout::{Len, Painter, SizeCtx, Ui, WidgetIdFn, WidgetRef},
};
use std::{any::Any, marker::Unsize};
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> { pub trait WidgetLike<Tag> {
type Widget: Widget + ?Sized + Unsize<dyn Widget> + 'static; type Widget: Widget + ?Sized + Unsize<dyn Widget> + 'static;
@@ -48,34 +25,6 @@ pub trait WidgetLike<Tag> {
{ {
ui.set_root(self); ui.set_root(self);
} }
fn set_ptr(self, ptr: &WidgetRef<WidgetPtr>, ui: &mut Ui)
where
Self: Sized,
Self::Widget: Widget,
{
ptr.get_mut().inner = Some(self.add(ui).any());
}
}
/// 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) -> WidgetRef<W> {
self(ui).add(ui)
}
}
impl<W: Widget> WidgetLike<WidgetTag> for W {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetRef<W> {
ui.add_widget(self)
}
} }
pub struct WidgetArr<const LEN: usize> { pub struct WidgetArr<const LEN: usize> {
@@ -88,7 +37,6 @@ impl<const LEN: usize> WidgetArr<LEN> {
} }
} }
pub struct ArrTag;
pub trait WidgetArrLike<const LEN: usize, Tag> { pub trait WidgetArrLike<const LEN: usize, Tag> {
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN>; fn ui(self, ui: &mut Ui) -> WidgetArr<LEN>;
} }
@@ -110,7 +58,7 @@ macro_rules! impl_widget_arr {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let ($($W,)*) = self; let ($($W,)*) = self;
WidgetArr::new( WidgetArr::new(
[$($W.add(ui).any(),)*], [$($W.add(ui),)*],
) )
} }
} }
@@ -129,19 +77,3 @@ 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!(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!(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); 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<WidgetRef>;
}
impl WidgetOption for () {
fn get(self, _: &mut Ui) -> Option<WidgetRef> {
None
}
}
impl<F: FnOnce(&mut Ui) -> Option<WidgetRef>> WidgetOption for F {
fn get(self, ui: &mut Ui) -> Option<WidgetRef> {
self(ui)
}
}

View File

@@ -0,0 +1,63 @@
mod handle;
mod like;
mod tag;
mod widgets;
pub use handle::*;
pub use like::*;
pub use tag::*;
pub use widgets::*;
use crate::layout::{Len, Painter, SizeCtx, Ui};
pub trait Widget: 'static {
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) -> WidgetRef<W> {
self(ui).add(ui)
}
}
impl<W: Widget> WidgetLike<WidgetTag> for W {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetRef<W> {
ui.add_widget(self)
}
}
pub trait WidgetOption {
fn get(self, ui: &mut Ui) -> Option<WidgetRef>;
}
impl WidgetOption for () {
fn get(self, _: &mut Ui) -> Option<WidgetRef> {
None
}
}
impl<F: FnOnce(&mut Ui) -> Option<WidgetRef>> WidgetOption for F {
fn get(self, ui: &mut Ui) -> Option<WidgetRef> {
self(ui)
}
}

View File

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

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

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

View File

@@ -9,7 +9,7 @@ pub struct Handle<T: ?Sized>(Rc<RefCell<T>>);
pub struct WeakHandle<T: ?Sized>(Weak<RefCell<T>>); pub struct WeakHandle<T: ?Sized>(Weak<RefCell<T>>);
impl<T: ?Sized> Handle<T> { impl<T: ?Sized> Handle<T> {
pub fn get(&self) -> Ref<T> { pub fn get(&self) -> Ref<'_, T> {
self.0.borrow() self.0.borrow()
} }
@@ -44,7 +44,7 @@ impl<T: ?Sized> Clone for WeakHandle<T> {
} }
} }
impl<T: ?Sized + Default> Default for Handle<T> { impl<T: Default> Default for Handle<T> {
fn default() -> Self { fn default() -> Self {
Self(Default::default()) Self(Default::default())
} }

View File

@@ -1,18 +1,18 @@
mod arena; mod arena;
mod change; mod change;
mod handle;
mod id; mod id;
mod math; mod math;
mod refcount; mod refcount;
mod vec2; mod vec2;
mod handle;
pub(crate) use arena::*; pub use arena::*;
pub use change::*; pub use change::*;
pub(crate) use id::*;
pub(crate) use math::*;
pub(crate) use refcount::*;
pub use vec2::*;
pub use handle::*; pub use handle::*;
pub use id::*;
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

@@ -1,4 +1,4 @@
use iris::{prelude::*, winit::*}; use iris::{prelude::*};
fn main() { fn main() {
DefaultApp::<State>::run(); DefaultApp::<State>::run();

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

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

@@ -0,0 +1,72 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{
Block, Ident, Signature, Token, Visibility,
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
struct Input {
vis: Visibility,
name: Ident,
fns: Vec<InputFn>,
}
struct InputFn {
sig: Signature,
body: Block,
}
impl Parse for Input {
fn parse(input: ParseStream) -> Result<Self> {
let vis = input.parse()?;
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 { vis, name, fns })
}
}
// pub trait $name<W: WidgetLike<Tag>, Tag> {
// $(
// fn $fn $(<$($T $(: $TT $(+ $TL)?)?,)*>)?($self $(, $arg: $ty)*) -> $ret;
// )*
// }
//
// impl<W: WidgetLike<Tag>, Tag> $name<W, Tag> for W {
// $(
// fn $fn $(<$($T $(: $TT $(+ $TL)?)?,)*>)?($self $(, $arg: $ty)*) -> $ret {
// $code
// }
// )*
// }
#[proc_macro]
pub fn widget_trait(input: TokenStream) -> TokenStream {
let Input { 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();
TokenStream::from(quote! {
#vis trait #name<WL: WidgetLike<Tag>, Tag> {
#(#sigs;)*
}
impl<WL: WidgetLike<Tag>, Tag> #name<WL, Tag> for WL {
#(#impls)*
}
})
}

View File

@@ -1,6 +1,6 @@
# 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

View File

@@ -56,10 +56,9 @@ impl DefaultAppState for Client {
let add_button = rect(Color::LIME) let add_button = rect(Color::LIME)
.radius(30) .radius(30)
.on(CursorSense::click(), move |ctx| { .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.ui);
.any();
span_add_.get_mut().children.push(child); span_add_.get_mut().children.push(child);
}) })
.sized((150, 150)) .sized((150, 150))
@@ -106,7 +105,7 @@ impl DefaultAppState for Client {
.text_align(Align::LEFT) .text_align(Align::LEFT)
.size(30) .size(30)
.attr::<Selectable>(()) .attr::<Selectable>(())
.on(iris::winit::Submit, move |ctx| { .on(Submit, move |ctx| {
let content = ctx.widget.get_mut().take(); let content = ctx.widget.get_mut().take();
let text = wtext(content) let text = wtext(content)
.editable(false) .editable(false)
@@ -115,7 +114,7 @@ impl DefaultAppState for Client {
.wrap(true) .wrap(true)
.attr::<Selectable>(()); .attr::<Selectable>(());
let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui); let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui);
texts.get_mut().children.push(msg_box.any()); texts.get_mut().children.push(msg_box);
}) })
.add(ui); .add(ui);
let text_edit_scroll = ( let text_edit_scroll = (
@@ -163,13 +162,13 @@ impl DefaultAppState for Client {
}; };
let tabs = ( let tabs = (
switch_button(Color::RED, pad_test.any(), "pad"), switch_button(Color::RED, pad_test, "pad"),
switch_button(Color::GREEN, span_test.any(), "span"), switch_button(Color::GREEN, span_test, "span"),
switch_button(Color::BLUE, span_add_test.any(), "image span"), switch_button(Color::BLUE, span_add_test, "image span"),
switch_button(Color::MAGENTA, text_test.any(), "text layout"), switch_button(Color::MAGENTA, text_test, "text layout"),
switch_button( switch_button(
Color::YELLOW.mul_rgb(0.5), Color::YELLOW.mul_rgb(0.5),
text_edit_scroll.any(), text_edit_scroll,
"text edit scroll", "text edit scroll",
), ),
) )

View File

@@ -1,4 +1,4 @@
use crate::{prelude::*, winit::UiState}; use crate::{prelude::*, default::UiState};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use winit::dpi::{LogicalPosition, LogicalSize}; use winit::dpi::{LogicalPosition, LogicalSize};

View File

@@ -1,7 +1,7 @@
use crate::{ use crate::{
core::{CursorState, Modifiers}, widget::{CursorState, Modifiers},
layout::Vec2, layout::Vec2,
winit::UiState, default::UiState,
}; };
use winit::{ use winit::{
event::{MouseButton, MouseScrollDelta, WindowEvent}, event::{MouseButton, MouseScrollDelta, WindowEvent},

View File

@@ -1,28 +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(const_destruct)]
#![feature(portable_simd)]
#![feature(gen_blocks)] #![feature(gen_blocks)]
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#![feature(unsize)]
#![feature(coerce_unsized)]
pub mod core; mod default;
pub mod layout; mod traits;
pub mod render; mod widget;
pub mod util;
pub mod winit; 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::traits::*;
pub use crate::render::*; pub use super::widget::*;
pub use crate::util::Handle;
pub use crate::winit::*; pub use iris_core::layout::*;
pub use iris_core::render::*;
pub use iris_core::util::Handle;
pub use iris_macro::*;
} }

82
src/traits.rs Normal file
View File

@@ -0,0 +1,82 @@
use crate::prelude::*;
use iris_macro::widget_trait;
// TODO: naming in here is a bit weird like eventable
#[macro_export]
macro_rules! event_ctx {
($ty: ty) => {
mod local_event_trait {
use super::*;
use std::marker::Sized;
#[allow(unused_imports)]
use $crate::prelude::*;
#[allow(unused)]
pub trait EventableCtx<W: ?Sized, Tag, Ctx: 'static> {
fn on<E: Event>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
) -> impl WidgetIdFn<W> + EventableCtx<W, IdFnTag, Ctx>;
}
impl<WL: WidgetLike<Tag>, Tag> EventableCtx<WL::Widget, Tag, $ty> for WL {
fn on<E: Event>(
self,
event: E,
f: impl WidgetEventFn<$ty, E::Data, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> + EventableCtx<WL::Widget, IdFnTag, $ty> {
eventable::Eventable::on(self, event, f)
}
}
#[allow(unused)]
pub trait EventableCtxUi<W: ?Sized, Tag, Ctx: 'static>
where
WidgetRef<W>: EventableCtx<W, Tag, Ctx>,
{
fn on<E: Event>(
&mut self,
widget: &WidgetRef<W>,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
);
}
impl<W: ?Sized + 'static, Tag> EventableCtxUi<W, Tag, $ty> for Ui
where
WidgetRef<W>: EventableCtx<W, Tag, $ty>,
{
fn on<E: Event>(
&mut self,
widget: &WidgetRef<W>,
event: E,
f: impl WidgetEventFn<$ty, E::Data, W>,
) {
self.register_widget_event(&widget, event, f);
}
}
}
use local_event_trait::*;
};
}
pub use event_ctx;
pub mod eventable {
use super::*;
widget_trait!(
pub Eventable;
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> {
move |ui| {
let id = self.add(ui);
ui.register_widget_event(&id, event, f);
id
}
}
);
}

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,4 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use crate::util::Id;
use std::{ use std::{
ops::{BitOr, Deref, DerefMut}, ops::{BitOr, Deref, DerefMut},
@@ -7,7 +8,7 @@ use std::{
use crate::{ use crate::{
layout::{UiModule, UiRegion, Vec2}, layout::{UiModule, UiRegion, Vec2},
util::{HashMap, Id}, util::HashMap,
}; };
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
@@ -166,8 +167,12 @@ impl<Ctx> CursorModule<Ctx> {
} }
} }
impl Ui { pub trait SensorUi {
pub fn run_sensors<Ctx: 'static>( 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, &mut self,
ctx: &mut Ctx, ctx: &mut Ctx,
cursor: &CursorState, cursor: &CursorState,
@@ -179,8 +184,8 @@ impl Ui {
impl<Ctx: 'static> CursorModule<Ctx> { impl<Ctx: 'static> CursorModule<Ctx> {
pub fn run(ui: &mut Ui, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) { pub fn run(ui: &mut Ui, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) {
let layers = std::mem::take(&mut ui.data.layers); let layers = std::mem::take(&mut ui.data_mut().layers);
let mut module = std::mem::take(ui.data.modules.get_mut::<Self>()); let mut module = std::mem::take(ui.data_mut().modules.get_mut::<Self>());
for i in layers.indices().rev() { for i in layers.indices().rev() {
let Some(list) = module.active.get_mut(&i) else { let Some(list) = module.active.get_mut(&i) else {
@@ -218,10 +223,10 @@ impl<Ctx: 'static> CursorModule<Ctx> {
} }
} }
let ui_mod = ui.data.modules.get_mut::<Self>(); let ui_mod = ui.data_mut().modules.get_mut::<Self>();
std::mem::swap(ui_mod, &mut module); std::mem::swap(ui_mod, &mut module);
ui_mod.merge(module); ui_mod.merge(module);
ui.data.layers = layers; ui.data_mut().layers = layers;
} }
} }

View File

@@ -75,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.get_mut().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(),
@@ -101,9 +101,9 @@ impl TextBuilderOutput for TextEditOutput {
let mut text = TextEdit::new( let mut text = TextEdit::new(
TextView::new(buf, builder.attrs, builder.hint.get(ui)), TextView::new(buf, builder.attrs, builder.hint.get(ui)),
builder.output.single_line, builder.output.single_line,
ui.data.text.clone(), ui.data().text.clone(),
); );
let font_system = &mut ui.data.text.get_mut().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);

View File

@@ -1,39 +1,22 @@
use super::*;
use crate::prelude::*; use crate::prelude::*;
use iris_macro::widget_trait;
// these methods should "not require any context" (require unit) because they're in core // these methods should "not require any context" (require unit) because they're in core
event_ctx!(()); event_ctx!(());
pub trait CoreWidget<W: ?std::marker::Sized, Tag> { widget_trait!(
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>; pub CoreWidget;
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned>;
fn center(self) -> impl WidgetFn<Aligned>;
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W>;
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized>;
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
fn 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 foreground<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack>;
fn layer_offset(self, offset: usize) -> impl WidgetFn<LayerOffset>;
fn to_any(self) -> impl WidgetRet;
}
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(),
} }
} }
@@ -42,7 +25,7 @@ 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);
id.set_label(label); id.set_label(label);
@@ -53,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),
} }
@@ -62,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,
} }
@@ -71,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),
} }
@@ -80,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,
} }
@@ -89,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),
} }
@@ -97,14 +80,14 @@ 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)
.on(CursorSense::Scroll, |ctx| { .on(CursorSense::Scroll, |ctx| {
let s = &mut *ctx.widget.get_mut(); let s = &mut *ctx.widget.get_mut();
s.scroll(ctx.data.scroll_delta.y * 50.0); s.scroll(ctx.data.scroll_delta.y * 50.0);
@@ -115,35 +98,39 @@ 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),
} }
} }
fn foreground<T>(self, w: impl WidgetLike<T>) -> 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(), w.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> { fn layer_offset(self, offset: usize) -> impl WidgetFn<LayerOffset> {
move |ui| LayerOffset { move |ui| LayerOffset {
inner: self.add(ui).any(), inner: self.add(ui),
offset, offset,
} }
} }
fn to_any(self) -> impl WidgetRet { fn to_any(self) -> impl WidgetRet {
|ui| self.add(ui).any() |ui| self.add(ui)
} }
fn set_ptr(self, ptr: &WidgetRef<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> {
fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag>; fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag>;