refactor project structure (start of redoing atomic branch without atomics)

This commit is contained in:
2025-12-11 05:25:58 -05:00
parent 38266debb6
commit 2dc5b0f62c
76 changed files with 540 additions and 442 deletions

28
Cargo.lock generated
View File

@@ -1042,16 +1042,36 @@ name = "iris"
version = "0.1.0"
dependencies = [
"arboard",
"bytemuck",
"cosmic-text",
"fxhash",
"image",
"iris-core",
"iris-macro",
"pollster",
"unicode-segmentation",
"wgpu",
"winit",
]
[[package]]
name = "iris-core"
version = "0.1.0"
dependencies = [
"bytemuck",
"cosmic-text",
"fxhash",
"image",
"wgpu",
"winit",
]
[[package]]
name = "iris-macro"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "itertools"
version = "0.12.1"
@@ -2401,9 +2421,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.110"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -1,12 +1,30 @@
[package]
name = "iris"
version = "0.1.0"
edition = "2024"
default-run = "test"
version.workspace = true
edition.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iris-core = { workspace = true }
iris-macro = { workspace = true }
cosmic-text = { workspace = true }
unicode-segmentation = { workspace = true }
winit = { workspace = true }
arboard = { workspace = true, features = ["wayland-data-control"] }
pollster = { workspace = true }
wgpu = { workspace = true }
image = { workspace = true }
[workspace]
members = ["core", "macro"]
[workspace.package]
version = "0.1.0"
edition = "2024"
[workspace.dependencies]
pollster = "0.4.0"
winit = "0.30.12"
wgpu = "27.0.1"
@@ -15,5 +33,6 @@ image = "0.25.6"
cosmic-text = "0.15.0"
unicode-segmentation = "1.12.0"
fxhash = "0.2.1"
arboard = { version = "3.6.1", features = ["wayland-data-control"] }
arboard = "3.6.1"
iris-core = { path = "core" }
iris-macro = { path = "macro" }

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,7 +1,7 @@
use std::{hash::Hash, rc::Rc};
use crate::{
layout::{IdFnTag, Ui, UiModule, WidgetId, WidgetIdFn, WidgetLike},
layout::{Ui, UiModule, WeakWidgetId, WidgetId},
util::{HashMap, Id},
};
@@ -18,7 +18,7 @@ pub struct EventCtx<'a, Ctx, Data> {
pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>;
pub struct EventIdCtx<'a, Ctx, Data, W> {
pub id: &'a WidgetId<W>,
pub id: &'a WeakWidgetId<W>,
pub ui: &'a mut Ui,
pub state: &'a mut Ctx,
pub data: Data,
@@ -30,74 +30,8 @@ impl<F: Fn(EventCtx<Ctx, Data>) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {
pub trait WidgetEventFn<Ctx, Data, W>: Fn(EventIdCtx<Ctx, Data, W>) + 'static {}
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W> WidgetEventFn<Ctx, Data, W> for F {}
// 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::*;
pub trait EventableCtx<W, 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)
}
}
}
use local_event_trait::*;
};
}
pub use event_ctx;
pub mod eventable {
use super::*;
pub trait Eventable<W, 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);
let id_ = id.weak();
ui.register_event(&id, event, move |ctx| {
f(EventIdCtx {
id: &id_.strong(),
state: ctx.state,
data: ctx.data,
ui: ctx.ui,
});
});
id
}
}
}
}
pub trait DefaultEvent: Hash + Eq + 'static {
type Data: Clone;
type Data: Clone = ();
}
impl<E: DefaultEvent> Event for E {
@@ -203,7 +137,7 @@ impl Ui {
.data
.modules
.get_mut::<E::Module<Ctx>>()
.run(&id.id, event)
.run(&id.id(), event)
{
f(EventCtx {
ui: self,

View File

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

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
}
}
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
Vec2::new(x.to_f32(), y.to_f32())
}
impl<T: const UiNum + Copy> const From<T> for Vec2 {
fn from(v: T) -> Self {
Self {
x: v.to_f32(),
y: v.to_f32(),
}
}
}
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
where
(T, U): const Destruct,
{
fn from((x, y): (T, U)) -> Self {
Self {
x: x.to_f32(),
y: y.to_f32(),
}
}
}

View File

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

View File

@@ -1,10 +1,10 @@
use crate::{
layout::{
Axis, Len, Modules, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData,
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets,
TextureHandle, Textures, UiRegion, UiVec2, WidgetId, Widgets,
},
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
util::{HashMap, HashSet, Id, TrackedArena},
util::{HashMap, HashSet, Id, TrackedArena, Vec2},
};
/// makes your surfaces look pretty
@@ -33,7 +33,7 @@ struct PainterCtx<'a> {
pub modules: &'a mut Modules,
pub cache_width: HashMap<Id, (UiVec2, Len)>,
pub cache_height: HashMap<Id, (UiVec2, Len)>,
pub needs_redraw: HashSet<Id>,
pub needs_redraw: &'a mut HashSet<Id>,
draw_started: HashSet<Id>,
}
@@ -323,7 +323,7 @@ impl<'a> PainterCtx<'a> {
}
impl PainterData {
fn ctx(&mut self, needs_redraw: HashSet<Id>) -> PainterCtx<'_> {
fn ctx<'a>(&'a mut self, needs_redraw: &'a mut HashSet<Id>) -> PainterCtx<'a> {
PainterCtx {
widgets: &self.widgets,
active: &mut self.active,
@@ -341,13 +341,14 @@ impl PainterData {
}
pub fn draw(&mut self, id: Id) {
let mut ctx = self.ctx(Default::default());
let mut need_redraw = HashSet::default();
let mut ctx = self.ctx(&mut need_redraw);
ctx.draw_started.clear();
ctx.layers.clear();
ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None);
}
pub fn redraw(&mut self, ids: HashSet<Id>) {
pub fn redraw(&mut self, ids: &mut HashSet<Id>) {
let mut ctx = self.ctx(ids);
while let Some(&id) = ctx.needs_redraw.iter().next() {
ctx.redraw(id);
@@ -399,9 +400,9 @@ impl<'a, 'c> Painter<'a, 'c> {
}
fn widget_at<W>(&mut self, id: &WidgetId<W>, region: UiRegion) {
self.children.push(id.id);
self.children.push(id.id());
self.ctx
.draw_inner(self.layer, id.id, region, Some(self.id), self.mask, None);
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None);
}
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
@@ -552,11 +553,11 @@ impl SizeCtx<'_> {
}
pub fn width<W>(&mut self, id: &WidgetId<W>) -> Len {
self.width_inner(id.id)
self.width_inner(id.id())
}
pub fn height<W>(&mut self, id: &WidgetId<W>) -> Len {
self.height_inner(id.id)
self.height_inner(id.id())
}
pub fn len_axis<W>(&mut self, id: &WidgetId<W>, axis: Axis) -> Len {

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,11 +1,13 @@
use std::simd::{Simd, num::SimdUint};
use crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2};
use crate::{
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor},
util::Vec2,
};
use cosmic_text::{
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
SwashContent,
};
use image::{GenericImageView, RgbaImage};
use std::simd::{Simd, num::SimdUint};
/// TODO: properly wrap this
pub mod text_lib {

View File

@@ -1,12 +1,13 @@
use crate::{
render::TexturePrimitive,
util::{RefCounter, Vec2},
};
use image::{DynamicImage, GenericImageView};
use std::{
ops::Index,
sync::mpsc::{Receiver, Sender, channel},
};
use image::{DynamicImage, GenericImageView};
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
#[derive(Debug, Clone)]
pub struct TextureHandle {
inner: TexturePrimitive,

View File

@@ -1,13 +1,11 @@
use image::DynamicImage;
use crate::{
core::{TextEdit, TextEditCtx},
layout::{
Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, TextureHandle, Vec2, Widget,
Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, TextureHandle, Widget,
WidgetId, WidgetInstance, WidgetLike,
},
util::{HashSet, Id},
util::{Id, Vec2},
};
use image::DynamicImage;
use std::{
any::{Any, TypeId},
ops::{Index, IndexMut},
@@ -16,9 +14,8 @@ use std::{
pub struct Ui {
// TODO: make this at least pub(super)
pub(crate) data: PainterData,
pub data: PainterData,
root: Option<WidgetId>,
updates: HashSet<Id>,
recv: Receiver<Id>,
pub(super) send: Sender<Id>,
full_redraw: bool,
@@ -32,11 +29,11 @@ impl Ui {
/// 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;
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
&self.data.widgets.data(&id.id()).unwrap().label
}
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetId<W> {
@@ -45,7 +42,7 @@ impl Ui {
pub fn push<W: Widget>(&mut self, w: W) -> WidgetId<W> {
let id = self.new_id();
self.data.widgets.insert(id.id, w);
self.data.widgets.insert(id.id(), w);
id
}
@@ -58,11 +55,11 @@ impl Ui {
Self::default()
}
pub fn get<W: Widget>(&self, id: &impl IdLike<W>) -> Option<&W> {
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget> {
self.data.widgets.get(id)
}
pub fn get_mut<W: Widget>(&mut self, id: &impl IdLike<W>) -> Option<&mut W> {
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget> {
self.data.widgets.get_mut(id)
}
@@ -87,7 +84,7 @@ impl Ui {
self.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id, event, f);
.register(id.id(), event, f);
}
pub fn resize(&mut self, size: impl Into<Vec2>) {
@@ -104,7 +101,7 @@ impl Ui {
// free before bc nothing should exist
self.free();
if let Some(root) = &self.root {
self.data.draw(root.id);
self.data.draw(root.id());
}
}
@@ -112,26 +109,19 @@ impl Ui {
if self.full_redraw {
self.redraw_all();
self.full_redraw = false;
} else if !self.updates.is_empty() {
} else if self.data.widgets.has_updates() {
self.redraw_updates();
}
if self.resized {
self.resized = false;
self.redraw_size();
self.redraw_all();
}
}
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));
let mut updates = std::mem::take(&mut self.data.widgets.updates);
self.data.redraw(&mut updates);
self.data.widgets.updates = updates;
self.free();
}
@@ -147,7 +137,7 @@ impl Ui {
}
pub fn needs_redraw(&self) -> bool {
self.full_redraw || !self.updates.is_empty()
self.full_redraw || self.data.widgets.has_updates()
}
pub fn num_widgets(&self) -> usize {
@@ -158,14 +148,6 @@ impl Ui {
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 debug_layers(&self) {
for ((idx, depth), primitives) in self.data.layers.iter_depth() {
let indent = " ".repeat(depth * 2);
@@ -178,7 +160,7 @@ impl Ui {
}
}
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
let region = self.data.active.get(&id.id())?.region;
Some(region.to_px(self.data.output_size))
}
@@ -191,17 +173,16 @@ impl Ui {
}
}
impl<W: Widget> Index<&WidgetId<W>> for Ui {
type Output = W;
impl<I: IdLike> Index<&I> for Ui {
type Output = I::Widget;
fn index(&self, id: &WidgetId<W>) -> &Self::Output {
fn index(&self, id: &I) -> &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);
impl<I: IdLike> IndexMut<&I> for Ui {
fn index_mut(&mut self, id: &I) -> &mut Self::Output {
self.get_mut(id).unwrap()
}
}
@@ -222,7 +203,6 @@ impl Default for Ui {
Self {
data: PainterData::default(),
root: Default::default(),
updates: Default::default(),
full_redraw: false,
send,
recv,

View File

@@ -1,7 +1,7 @@
use std::{any::TypeId, marker::PhantomData, sync::mpsc::Sender};
use crate::{
layout::{Ui, WidgetLike},
layout::{Ui, Widget},
util::{Id, RefCounter},
};
@@ -58,7 +58,7 @@ impl<W> Clone for WidgetId<W> {
}
impl<W> WidgetId<W> {
pub(super) fn new(id: Id, ty: TypeId, send: Sender<Id>) -> Self {
pub(crate) fn new(id: Id, ty: TypeId, send: Sender<Id>) -> Self {
Self {
ty,
id,
@@ -77,7 +77,7 @@ impl<W> WidgetId<W> {
unsafe { std::mem::transmute(self) }
}
pub fn key(&self) -> Id {
pub fn id(&self) -> Id {
self.id
}
@@ -108,26 +108,6 @@ impl<W> WidgetId<W> {
}
}
impl<W> WeakWidgetId<W> {
/// should guarantee that widget is still valid to prevent indexing failures
pub(crate) fn strong(&self) -> WidgetId<W> {
let Self {
ty,
id,
ref counter,
ref send,
_pd,
} = *self;
WidgetId {
ty,
id,
counter: counter.clone(),
send: send.clone(),
_pd,
}
}
}
impl<W> Drop for WidgetId<W> {
fn drop(&mut self) {
if self.counter.drop() {
@@ -136,29 +116,12 @@ impl<W> Drop for WidgetId<W> {
}
}
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 {}
pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetId<AnyWidget> {}
impl<F: FnOnce(&mut Ui) -> WidgetId<AnyWidget>> WidgetRet for F {}
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)
}
}
pub trait WidgetIdLike<W> {
fn id(self, send: &Sender<Id>) -> WidgetId<W>;
}
@@ -169,11 +132,20 @@ impl<W> WidgetIdLike<W> for &WidgetId<W> {
}
}
pub trait IdLike<W> {
pub trait IdLike {
type Widget: Widget + 'static;
fn id(&self) -> Id;
}
impl<W> IdLike<W> for WidgetId<W> {
impl<W: Widget> IdLike for WidgetId<W> {
type Widget = W;
fn id(&self) -> Id {
self.id
}
}
impl<W: Widget> IdLike for WeakWidgetId<W> {
type Widget = W;
fn id(&self) -> Id {
self.id
}

View File

@@ -1,28 +1,4 @@
use crate::{
core::WidgetPtr,
layout::{Len, Painter, SizeCtx, 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;
use super::*;
pub trait WidgetLike<Tag> {
type Widget: 'static;
@@ -48,50 +24,8 @@ pub trait WidgetLike<Tag> {
{
ui.set_root(self);
}
fn set_ptr(self, ptr: &WidgetId<WidgetPtr>, ui: &mut Ui)
where
Self: Sized,
{
ui[ptr].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>: 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>;
@@ -142,19 +76,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!(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

@@ -0,0 +1,64 @@
use crate::layout::{Len, Painter, SizeCtx, Ui};
use std::{any::Any, marker::PhantomData};
mod id;
mod like;
mod tag;
mod widgets;
pub use id::*;
pub use like::*;
pub use tag::*;
pub use widgets::*;
pub trait Widget: Any {
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
}
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>: FnOnce(&mut Ui) -> W {}
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetFn<W> for F {}
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 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

@@ -0,0 +1,36 @@
use super::*;
pub struct ArrTag;
pub struct WidgetTag;
impl<W: Widget> WidgetLike<WidgetTag> for W {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetId<W> {
ui.add_widget(self)
}
}
pub struct FnTag;
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)
}
}
pub struct IdTag;
impl<W: 'static> WidgetLike<IdTag> for WidgetId<W> {
type Widget = W;
fn add(self, _: &mut Ui) -> WidgetId<W> {
self
}
}
pub struct IdFnTag;
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)
}
}

View File

@@ -1,10 +1,11 @@
use crate::{
layout::{IdLike, Widget},
util::{DynBorrower, HashMap, Id, IdTracker},
util::{DynBorrower, HashMap, HashSet, Id, IdTracker},
};
#[derive(Default)]
pub struct Widgets {
pub updates: HashSet<Id>,
ids: IdTracker,
map: HashMap<Id, WidgetData>,
}
@@ -17,11 +18,8 @@ pub struct WidgetData {
}
impl Widgets {
pub fn new() -> Self {
Self {
ids: IdTracker::default(),
map: HashMap::default(),
}
pub fn has_updates(&self) -> bool {
!self.updates.is_empty()
}
pub fn get_dyn(&self, id: Id) -> Option<&dyn Widget> {
@@ -29,6 +27,7 @@ impl Widgets {
}
pub fn get_dyn_mut(&mut self, id: Id) -> Option<&mut dyn Widget> {
self.updates.insert(id);
Some(self.map.get_mut(&id)?.widget.as_mut())
}
@@ -47,11 +46,11 @@ impl Widgets {
WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed)
}
pub fn get<W: Widget>(&self, id: &impl IdLike<W>) -> Option<&W> {
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget> {
self.get_dyn(id.id())?.as_any().downcast_ref()
}
pub fn get_mut<W: Widget>(&mut self, id: &impl IdLike<W>) -> Option<&mut W> {
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget> {
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
}

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

@@ -0,0 +1,17 @@
#![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;

View File

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

View File

@@ -22,6 +22,12 @@ impl RefCounter {
}
}
impl Default for RefCounter {
fn default() -> Self {
Self::new()
}
}
impl Clone for RefCounter {
fn clone(&self) -> Self {
self.0.fetch_add(1, Ordering::Release);

View File

@@ -1,8 +1,5 @@
use crate::{
layout::UiNum,
util::{DivOr, impl_op},
};
use std::{hash::Hash, marker::Destruct, ops::*};
use crate::util::{DivOr, impl_op};
use std::{hash::Hash, ops::*};
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
@@ -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 {
pub const ZERO: Self = Self::new(0.0, 0.0);
pub const ONE: Self = Self::new(1.0, 1.0);
@@ -68,15 +61,6 @@ impl Vec2 {
}
}
impl<T: const UiNum + Copy> const From<T> for Vec2 {
fn from(v: T) -> Self {
Self {
x: v.to_f32(),
y: v.to_f32(),
}
}
}
// this version looks kinda cool... is it more readable? more annoying to copy and change though
impl_op!(impl Add for Vec2: add x y);
impl_op!(Vec2 Sub sub; x y);
@@ -102,18 +86,6 @@ impl Neg for Vec2 {
}
}
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
where
(T, U): const Destruct,
{
fn from((x, y): (T, U)) -> Self {
Self {
x: x.to_f32(),
y: y.to_f32(),
}
}
}
impl std::fmt::Debug for Vec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)

View File

@@ -1,4 +1,4 @@
use iris::{prelude::*, winit::*};
use iris::prelude::*;
fn main() {
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

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,8 +1,5 @@
use cosmic_text::Family;
use iris::{
prelude::*,
winit::{attr::Selectable, event::Submit, *},
};
use iris::prelude::*;
use len_fns::*;
use winit::event::WindowEvent;
@@ -110,7 +107,7 @@ impl DefaultAppState for Client {
.size(30)
.attr::<Selectable>(())
.on(Submit, move |ctx| {
let content = ctx.ui.text(ctx.id).take();
let content = ctx.id.edit(ctx.ui).take();
let text = wtext(content)
.editable(false)
.size(30)

View File

@@ -1,10 +1,10 @@
use crate::{prelude::*, winit::UiState};
use crate::{default::UiState, prelude::*};
use std::time::{Duration, Instant};
use winit::dpi::{LogicalPosition, LogicalSize};
pub struct Selector;
impl<W: 'static> WidgetAttr<W> for Selector {
impl<W: 'static + Widget> WidgetAttr<W> for Selector {
type Input = WidgetId<TextEdit>;
fn run(ui: &mut Ui, container: &WidgetId<W>, id: Self::Input) {
@@ -42,7 +42,7 @@ fn select(ui: &mut Ui, id: WidgetId<TextEdit>, state: &mut UiState, data: Cursor
let now = Instant::now();
let recent = (now - state.last_click) < Duration::from_millis(300);
state.last_click = now;
ui.text(&id)
id.edit(ui)
.select(data.cursor, data.size, data.sense.is_dragging(), recent);
if let Some(region) = ui.window_region(&id) {
state.window.set_ime_allowed(true);

View File

@@ -2,14 +2,8 @@ use crate::layout::DefaultEvent;
#[derive(Eq, PartialEq, Hash, Clone)]
pub struct Submit;
impl DefaultEvent for Submit {}
#[derive(Eq, PartialEq, Hash, Clone)]
pub struct Edited;
impl DefaultEvent for Submit {
type Data = ();
}
impl DefaultEvent for Edited {
type Data = ();
}
impl DefaultEvent for Edited {}

View File

@@ -1,8 +1,4 @@
use crate::{
core::{CursorState, Modifiers},
layout::Vec2,
winit::UiState,
};
use crate::prelude::*;
use winit::{
event::{MouseButton, MouseScrollDelta, WindowEvent},
keyboard::{Key, NamedKey},

View File

@@ -1,6 +1,4 @@
use crate::prelude::*;
use crate::winit::event::{Edited, Submit};
use crate::winit::{input::Input, render::UiRenderer};
use arboard::Clipboard;
use std::sync::Arc;
use std::time::Instant;
@@ -9,12 +7,16 @@ use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
use winit::window::{Window, WindowAttributes};
mod app;
pub mod attr;
pub mod event;
pub mod input;
pub mod render;
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>>;
@@ -105,7 +107,7 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
if old != ui_state.focus
&& let Some(old) = old
{
ui.text(&old).deselect();
old.edit(ui).deselect();
}
match &event {
WindowEvent::CloseRequested => event_loop.exit(),
@@ -123,7 +125,7 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
&& event.state.is_pressed()
{
let sel = &sel.clone();
let mut text = ui.text(sel);
let mut text = sel.edit(ui);
match text.apply_event(event, &ui_state.input.modifiers) {
TextInputResult::Unfocus => {
ui_state.focus = None;
@@ -152,7 +154,7 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
}
WindowEvent::Ime(ime) => {
if let Some(sel) = &ui_state.focus {
let mut text = ui.text(sel);
let mut text = sel.edit(ui);
match ime {
Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => {

67
src/event.rs Normal file
View File

@@ -0,0 +1,67 @@
use crate::prelude::*;
// 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::*;
pub trait EventableCtx<W, 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)
}
}
}
use local_event_trait::*;
};
}
pub use event_ctx;
pub mod eventable {
use super::*;
pub trait Eventable<W, 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);
let id_ = id.weak();
ui.register_event(&id, event, move |ctx| {
f(EventIdCtx {
id: &id_,
state: ctx.state,
data: ctx.data,
ui: ctx.ui,
});
});
id
}
}
}
}

View File

@@ -1,46 +0,0 @@
//! tree structure for masking
use crate::layout::UiRegion;
pub struct Masks {
data: Vec<MaskNode>,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MaskPtr(u32);
#[repr(C)]
pub struct MaskNode {
/// TODO: this is just a rect for now,
/// but would like to support arbitrary masks
/// at some point; custom shader
/// would probably handle that case
/// bc you'd need to render to a special target
/// anyways
region: UiRegion,
prev: MaskPtr,
}
impl MaskPtr {
const NONE: Self = Self(u32::MAX);
}
impl Masks {
pub fn push(&mut self, parent: MaskPtr, region: UiRegion) -> MaskPtr {
match parent.0 {
_ => {
}
u32::MAX => {
let i = self.data.len();
self.data.push(MaskNode {
region,
prev: parent,
});
MaskPtr(i as u32)
}
}
}
pub fn pop(&mut self, i: usize) {}
}

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,24 +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(fn_traits)]
#![feature(const_cmp)]
#![feature(const_destruct)]
#![feature(portable_simd)]
#![feature(gen_blocks)]
#![feature(associated_type_defaults)]
pub mod core;
pub mod layout;
pub mod render;
pub mod util;
pub mod winit;
mod default;
mod event;
mod widget;
pub use iris_core::*;
pub use iris_macro::*;
pub mod prelude {
pub use crate::core::*;
pub use crate::layout::*;
pub use crate::render::*;
use super::*;
pub use default::*;
pub use event::*;
pub use iris_macro::*;
pub use layout::*;
pub use render::*;
pub use widget::*;
pub use super::util::Vec2;
}

View File

@@ -6,8 +6,8 @@ use std::{
};
use crate::{
layout::{UiModule, UiRegion, Vec2},
util::{HashMap, Id},
layout::{UiModule, UiRegion},
util::{HashMap, Id, Vec2},
};
#[derive(Clone, Copy, PartialEq)]
@@ -166,8 +166,12 @@ impl<Ctx> CursorModule<Ctx> {
}
}
impl Ui {
pub fn run_sensors<Ctx: 'static>(
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,
@@ -205,7 +209,11 @@ impl<Ctx: 'static> CursorModule<Ctx> {
scroll_delta: cursor.scroll_delta,
sense,
};
(sensor.f)(EventCtx { ui, state: ctx, data });
(sensor.f)(EventCtx {
ui,
state: ctx,
data,
});
}
}
}

View File

@@ -610,3 +610,16 @@ impl DerefMut for TextEdit {
&mut self.view
}
}
pub trait TextEditable {
fn edit<'a>(&self, ui: &'a mut Ui) -> TextEditCtx<'a>;
}
impl<I: IdLike<Widget = TextEdit>> TextEditable for I {
fn edit<'a>(&self, ui: &'a mut Ui) -> TextEditCtx<'a> {
TextEditCtx {
text: ui.data.widgets.get_mut(self).unwrap(),
font_system: &mut ui.data.text.font_system,
}
}
}