6 Commits

84 changed files with 1782 additions and 1773 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" }

1
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

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,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>;
} }

View File

@@ -1,7 +1,7 @@
use std::{hash::Hash, rc::Rc}; use std::{hash::Hash, rc::Rc};
use crate::{ use crate::{
layout::{IdFnTag, Ui, UiModule, WidgetId, WidgetIdFn, WidgetLike}, layout::{IdFnTag, Ui, UiModule, WidgetIdFn, WidgetLike, WidgetRef},
util::{HashMap, Id}, util::{HashMap, Id},
}; };
@@ -17,8 +17,8 @@ pub struct EventCtx<'a, Ctx, Data> {
} }
pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>; pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>;
pub struct EventIdCtx<'a, Ctx, Data, W> { pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> {
pub id: &'a WidgetId<W>, pub widget: &'a WidgetRef<W>,
pub ui: &'a mut Ui, pub ui: &'a mut Ui,
pub state: &'a mut Ctx, pub state: &'a mut Ctx,
pub data: Data, pub data: Data,
@@ -27,72 +27,44 @@ pub struct EventIdCtx<'a, Ctx, Data, W> {
pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + 'static {} pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + 'static {}
impl<F: Fn(EventCtx<Ctx, Data>) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {} 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 {} pub trait WidgetEventFn<Ctx, Data, W: ?Sized>: Fn(EventIdCtx<Ctx, Data, W>) + 'static {}
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W> WidgetEventFn<Ctx, Data, W> for F {} impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W: ?Sized> 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> { impl Ui {
fn on<E: Event>( pub fn register_event<W: ?Sized, E: Event, Ctx: 'static>(
self, &mut self,
id: &WidgetRef<W>,
event: E,
f: impl EventFn<Ctx, E::Data>,
) {
self.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id(), event, f);
}
pub fn register_widget_event<W: ?Sized + 'static, E: Event, Ctx: 'static>(
&mut self,
id: &WidgetRef<W>,
event: E, event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>, 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(); let id_ = id.weak();
ui.register_event(&id, event, move |ctx| { self.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id(), event, move |ctx| {
f(EventIdCtx { f(EventIdCtx {
id: &id_.strong(), widget: &id_.expect_strong(),
ui: ctx.ui,
state: ctx.state, state: ctx.state,
data: ctx.data, data: ctx.data,
ui: ctx.ui, })
}); });
});
id
}
}
} }
} }
@@ -192,10 +164,10 @@ impl<E: Event + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
} }
impl Ui { impl Ui {
pub fn run_event<E: Event, Ctx: 'static, W>( pub fn run_event<E: Event, Ctx: 'static, W: ?Sized>(
&mut self, &mut self,
ctx: &mut Ctx, ctx: &mut Ctx,
id: &WidgetId<W>, id: &WidgetRef<W>,
event: E, event: E,
data: E::Data, data: E::Data,
) { ) {
@@ -203,7 +175,7 @@ impl Ui {
.data .data
.modules .modules
.get_mut::<E::Module<Ctx>>() .get_mut::<E::Module<Ctx>>()
.run(&id.id, event) .run(&id.id(), event)
{ {
f(EventCtx { f(EventCtx {
ui: self, ui: self,

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

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

@@ -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,7 +1,9 @@
use std::{cell::Ref, marker::Unsize, sync::mpsc::Sender};
use crate::{ use crate::{
layout::{ layout::{
Axis, Len, Modules, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData, Axis, Len, Modules, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData,
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets, TextureHandle, Textures, UiRegion, UiVec2, Vec2, Widget, WidgetRef, WidgetUpdate, Widgets,
}, },
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
util::{HashMap, HashSet, Id, TrackedArena}, util::{HashMap, HashSet, Id, TrackedArena},
@@ -10,6 +12,8 @@ use crate::{
/// 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: WidgetRef,
id: Id,
region: UiRegion, region: UiRegion,
mask: MaskIdx, mask: MaskIdx,
textures: Vec<TextureHandle>, textures: Vec<TextureHandle>,
@@ -18,7 +22,6 @@ pub struct Painter<'a, 'c> {
children_width: HashMap<Id, (UiVec2, Len)>, children_width: HashMap<Id, (UiVec2, Len)>,
children_height: HashMap<Id, (UiVec2, Len)>, children_height: HashMap<Id, (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
@@ -60,7 +63,6 @@ pub struct WidgetInstance {
} }
/// data to be stored in Ui to create PainterCtxs easily /// data to be stored in Ui to create PainterCtxs easily
#[derive(Default)]
pub struct PainterData { pub struct PainterData {
pub widgets: Widgets, pub widgets: Widgets,
pub active: HashMap<Id, WidgetInstance>, pub active: HashMap<Id, WidgetInstance>,
@@ -73,11 +75,28 @@ pub struct PainterData {
pub masks: TrackedArena<Mask, u32>, pub masks: TrackedArena<Mask, u32>,
} }
impl PainterData {
pub fn new(send: Sender<WidgetUpdate>) -> Self {
Self {
widgets: Widgets::new(send),
active: Default::default(),
layers: Default::default(),
textures: Default::default(),
text: Default::default(),
output_size: Default::default(),
modules: Default::default(),
px_dependent: Default::default(),
masks: Default::default(),
}
}
}
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: &WidgetRef<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 +124,16 @@ 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 {
// 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(&self.widgets.get(*rid));
*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 +149,15 @@ 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); self.redraw(&self.widgets.get(*rid));
*old_desired = new_desired; *old_desired = new_desired;
if self.draw_started.contains(&id) { if self.draw_started.contains(&id) {
ret = true; ret = true;
@@ -158,7 +175,7 @@ impl<'a> PainterCtx<'a> {
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 +184,16 @@ 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: &WidgetRef<W>,
region: UiRegion, region: UiRegion,
parent: Option<Id>, parent: Option<Id>,
mask: MaskIdx, mask: MaskIdx,
old_children: Option<Vec<Id>>, old_children: Option<Vec<Id>>,
) { ) {
let id = widget.id();
// 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
@@ -218,6 +236,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 +246,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;
@@ -340,7 +359,7 @@ impl PainterData {
} }
} }
pub fn draw(&mut self, id: Id) { pub fn draw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetRef<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();
@@ -350,7 +369,7 @@ impl PainterData {
pub fn redraw(&mut self, ids: HashSet<Id>) { pub fn redraw(&mut self, ids: HashSet<Id>) {
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); ctx.redraw(&ctx.widgets.get(id));
} }
} }
} }
@@ -360,7 +379,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 +407,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: &WidgetRef<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: &WidgetRef<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: &WidgetRef<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 +448,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: &WidgetRef<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: &WidgetRef<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 +471,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,12 +504,12 @@ 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) -> Ref<'_, String> {
&self.ctx.widgets.data(&self.id).unwrap().label self.widget.get_label()
} }
pub fn id(&self) -> &Id { pub fn id(&self) -> Id {
&self.id self.id
} }
} }
@@ -488,7 +517,6 @@ 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: Id,
widgets: &'a Widgets,
cache_width: &'a mut HashMap<Id, (UiVec2, Len)>, cache_width: &'a mut HashMap<Id, (UiVec2, Len)>,
cache_height: &'a mut HashMap<Id, (UiVec2, Len)>, cache_height: &'a mut HashMap<Id, (UiVec2, Len)>,
checked_width: &'a mut HashMap<Id, (UiVec2, Len)>, checked_width: &'a mut HashMap<Id, (UiVec2, Len)>,
@@ -508,7 +536,7 @@ impl SizeCtx<'_> {
&self.source &self.source
} }
fn width_inner(&mut self, id: Id) -> Len { pub fn width<W: Widget + ?Sized>(&mut self, widget: &WidgetRef<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 +547,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 +562,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: &WidgetRef<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 +581,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: &WidgetRef<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: &WidgetRef<W>) -> Size {
Size { Size {
x: self.width(id), x: self.width(id),
y: self.height(id), y: self.height(id),
@@ -582,10 +604,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) => {
let amt = amt.to(); #[allow(unused_braces)]
self.map_rgb(|x| T::from(x.to() * amt)) Self {
r: {
let $x = $self.r;
$e
},
g: {
let $x = $self.g;
$e
},
b: {
let $x = $self.b;
$e
},
a: $self.a,
}
};
} }
pub fn add_rgb(self, amt: impl F32Conversion) -> Self { 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 darker(self, amt: f32) -> Self { pub const fn add_rgb(self, amt: impl const F32Conversion) -> Self {
let amt = amt.to();
map_rgb!(x, self, { T::from(x.to() + amt) })
}
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

@@ -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(),
@@ -73,7 +78,7 @@ impl Default for TextAttrs {
pub const LINE_HEIGHT_MULT: f32 = 1.1; pub const LINE_HEIGHT_MULT: f32 = 1.1;
impl TextData { impl TextDataInner {
pub fn draw( pub fn draw(
&mut self, &mut self,
buffer: &mut TextBuffer, buffer: &mut TextBuffer,

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

@@ -0,0 +1,167 @@
use image::DynamicImage;
use crate::{
layout::{
IdLike, PainterData, PixelRegion, TextureHandle, Vec2, Widget, WidgetInstance, WidgetLike,
WidgetRef, WidgetUpdate,
},
util::{HashSet, Id},
};
use std::sync::mpsc::{Receiver, channel};
pub struct Ui {
pub(crate) data: PainterData,
root: Option<WidgetRef>,
updates: HashSet<Id>,
free: Vec<Id>,
recv: Receiver<WidgetUpdate>,
full_redraw: bool,
resized: bool,
}
impl Ui {
pub fn add<W: Widget, Tag>(&mut self, w: impl WidgetLike<Tag, Widget = W>) -> WidgetRef<W> {
w.add(self)
}
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetRef<W> {
self.data.widgets.insert(w)
}
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 (_, 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);
}
}
pub fn update(&mut self) -> bool {
for update in self.recv.try_iter() {
match update {
WidgetUpdate::Drop(id) => {
self.free.push(id);
}
WidgetUpdate::Mutate(id) => {
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) {
for id in self.free.drain(..) {
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 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 = &self.data.widgets.get(*id);
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();
Self {
data: PainterData::new(send),
root: Default::default(),
updates: Default::default(),
free: Default::default(),
full_redraw: false,
recv,
resized: false,
}
}
}

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

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

View File

@@ -0,0 +1,163 @@
use std::{
cell::{Ref, RefMut},
marker::Unsize,
ops::CoerceUnsized,
sync::mpsc::Sender,
};
use crate::{
layout::{IdFnTag, IdTag, Ui, Widget, WidgetLike},
util::{Handle, Id, WeakHandle},
};
/// An handle for a widget in a UI.
///
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
pub struct WidgetRef<W: ?Sized = dyn Widget>(Handle<Inner<W>>);
pub struct WeakWidgetRef<W: ?Sized = dyn Widget>(WeakHandle<Inner<W>>);
struct Inner<W: ?Sized> {
id: Id,
send: Sender<WidgetUpdate>,
label: String,
widget: W,
}
pub enum WidgetUpdate {
Drop(Id),
Mutate(Id),
}
impl<W> PartialEq for WidgetRef<W> {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl<W> std::fmt::Debug for WidgetRef<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(self.0.clone())
}
}
impl<W: Widget> WidgetRef<W> {
pub(super) fn new(id: Id, widget: W, send: Sender<WidgetUpdate>) -> 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(
Inner {
widget,
id,
send,
label,
}
.into(),
)
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetRef<W> {
pub fn any(self) -> WidgetRef<dyn Widget> {
WidgetRef(self.0)
}
pub fn as_any(&self) -> WidgetRef<dyn Widget> {
WidgetRef(self.0.clone())
}
}
impl<W: ?Sized> WidgetRef<W> {
pub fn id(&self) -> Id {
self.0.get().id
}
pub fn get(&self) -> Ref<'_, W> {
Ref::map(self.0.get(), |i| &i.widget)
}
pub fn get_mut(&self) -> RefMut<'_, W> {
let inner = self.0.get_mut();
let _ = inner.send.send(WidgetUpdate::Mutate(inner.id));
RefMut::map(inner, |i| &mut i.widget)
}
pub fn get_mut_quiet(&self) -> RefMut<'_, W> {
RefMut::map(self.0.get_mut(), |i| &mut i.widget)
}
pub fn get_label(&self) -> Ref<'_, String> {
Ref::map(self.0.get(), |i| &i.label)
}
pub fn set_label(&self, label: impl Into<String>) {
self.0.get_mut().label = label.into();
}
pub fn refs(&self) -> usize {
self.0.refs()
}
pub fn weak(&self) -> WeakWidgetRef<W> {
WeakWidgetRef(self.0.weak())
}
}
impl<W: ?Sized> WeakWidgetRef<W> {
/// should guarantee that widget is still valid to prevent indexing failures
pub(crate) fn expect_strong(&self) -> WidgetRef<W> {
WidgetRef(self.0.strong().expect("widget should not be dropped"))
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WeakWidgetRef<W> {
pub fn any(self) -> WeakWidgetRef<dyn Widget> {
WeakWidgetRef(self.0.clone())
}
}
impl<W: ?Sized> Drop for Inner<W> {
fn drop(&mut self) {
let _ = self.send.send(WidgetUpdate::Drop(self.id));
}
}
pub trait WidgetIdFn<W: ?Sized = dyn Widget>: FnOnce(&mut Ui) -> WidgetRef<W> {}
impl<W: ?Sized, F: FnOnce(&mut Ui) -> WidgetRef<W>> WidgetIdFn<W> for F {}
pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetRef {}
impl<F: FnOnce(&mut Ui) -> WidgetRef> WidgetRet for F {}
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static> WidgetLike<IdTag> for WidgetRef<W> {
type Widget = W;
fn add(self, _: &mut Ui) -> WidgetRef<W> {
self
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static, F: FnOnce(&mut Ui) -> WidgetRef<W>>
WidgetLike<IdFnTag> for F
{
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetRef<W> {
self(ui)
}
}
pub trait IdLike<W> {
fn id(&self) -> Id;
}
impl<W> IdLike<W> for WidgetRef<W> {
fn id(&self) -> Id {
self.id()
}
}
impl<W: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> 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) -> WidgetRef<Self::Widget>;
fn with_id<W2>(
self,
f: impl FnOnce(&mut Ui, WidgetRef<Self::Widget>) -> WidgetRef<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: [WidgetRef; LEN],
}
impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [WidgetRef; 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,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;

View File

@@ -0,0 +1,50 @@
use std::sync::mpsc::Sender;
use crate::{
layout::{WeakWidgetRef, Widget, WidgetRef, WidgetUpdate},
util::{HashMap, Id, IdTracker},
};
pub struct Widgets {
ids: IdTracker,
map: HashMap<Id, WeakWidgetRef>,
send: Sender<WidgetUpdate>,
}
impl Widgets {
pub fn new(send: Sender<WidgetUpdate>) -> Self {
Self {
ids: IdTracker::default(),
map: HashMap::default(),
send,
}
}
pub fn get(&self, id: Id) -> WidgetRef {
self.map.get(&id).unwrap().expect_strong()
}
pub fn insert<W: Widget>(&mut self, widget: W) -> WidgetRef<W> {
let id = self.ids.next();
let rf = WidgetRef::new(id, widget, self.send.clone());
self.map.insert(id, rf.weak().any());
rf
}
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()
}
}

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;

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

@@ -0,0 +1,60 @@
use std::{
cell::{Ref, RefCell, RefMut},
marker::Unsize,
ops::CoerceUnsized,
rc::{Rc, Weak},
};
pub struct Handle<T: ?Sized>(Rc<RefCell<T>>);
pub struct WeakHandle<T: ?Sized>(Weak<RefCell<T>>);
impl<T: ?Sized> Handle<T> {
pub fn get(&self) -> Ref<'_, T> {
self.0.borrow()
}
pub fn get_mut(&self) -> RefMut<'_, T> {
self.0.borrow_mut()
}
pub fn refs(&self) -> usize {
Rc::strong_count(&self.0)
}
pub fn weak(&self) -> WeakHandle<T> {
WeakHandle(Rc::downgrade(&self.0))
}
}
impl<T: ?Sized> WeakHandle<T> {
pub fn strong(&self) -> Option<Handle<T>> {
Some(Handle(self.0.upgrade()?))
}
}
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(Rc::new(RefCell::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> {}

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,7 @@ impl RefCounter {
let refs = self.0.fetch_sub(1, Ordering::Release); let refs = self.0.fetch_sub(1, Ordering::Release);
refs == 0 refs == 0
} }
#[allow(unused)]
pub fn quiet_clone(&self) -> Self { pub fn quiet_clone(&self) -> Self {
Self(self.0.clone()) Self(self.0.clone())
} }

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);
@@ -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 // 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!(impl Add for Vec2: add x y);
impl_op!(Vec2 Sub sub; 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 { 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)

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

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

@@ -0,0 +1,73 @@
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()?;
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 { 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

@@ -1,8 +1,5 @@
use cosmic_text::Family; use cosmic_text::Family;
use iris::{ use iris::prelude::*;
prelude::*,
winit::{attr::Selectable, event::Submit, *},
};
use len_fns::*; use len_fns::*;
use winit::event::WindowEvent; use winit::event::WindowEvent;
@@ -11,7 +8,7 @@ fn main() {
} }
pub struct Client { pub struct Client {
info: WidgetId<Text>, info: WidgetRef<Text>,
} }
event_ctx!(Client); event_ctx!(Client);
@@ -59,11 +56,10 @@ 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);
ctx.ui[&span_add_].children.push(child);
}) })
.sized((150, 150)) .sized((150, 150))
.align(Align::BOT_RIGHT); .align(Align::BOT_RIGHT);
@@ -71,8 +67,8 @@ impl DefaultAppState for Client {
let span_add_ = span_add.clone(); let span_add_ = span_add.clone();
let del_button = rect(Color::RED) let del_button = rect(Color::RED)
.radius(30) .radius(30)
.on(CursorSense::click(), move |ctx| { .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);
@@ -110,7 +106,7 @@ impl DefaultAppState for Client {
.size(30) .size(30)
.attr::<Selectable>(()) .attr::<Selectable>(())
.on(Submit, move |ctx| { .on(Submit, move |ctx| {
let content = ctx.ui.text(ctx.id).take(); let content = ctx.widget.get_mut().take();
let text = wtext(content) let text = wtext(content)
.editable(false) .editable(false)
.size(30) .size(30)
@@ -118,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);
ctx.ui[&texts].children.push(msg_box.any()); texts.get_mut().children.push(msg_box);
}) })
.add(ui); .add(ui);
let text_edit_scroll = ( let text_edit_scroll = (
@@ -146,33 +142,33 @@ impl DefaultAppState for Client {
let main = pad_test.clone().pad(10).add(ui); let main = pad_test.clone().pad(10).add(ui);
let switch_button = |color, to: WidgetId, label| { let switch_button = |color, to: WidgetRef, label| {
let main_ = main.clone(); let main_ = main.clone();
let rect = rect(color) let rect = rect(color)
.on(CursorSense::click(), move |ctx| { .on(CursorSense::click(), move |ctx| {
ctx.ui[&main_.clone()].inner = to.clone(); main_.get_mut().inner = to.clone();
ctx.ui[ctx.id].color = color.darker(0.3); ctx.widget.get_mut().color = color.darker(0.3);
}) })
.on( .on(
CursorSense::HoverStart | CursorSense::unclick(), CursorSense::HoverStart | CursorSense::unclick(),
move |ctx| { move |ctx| {
ctx.ui[ctx.id].color = color.brighter(0.2); ctx.widget.get_mut().color = color.brighter(0.2);
}, },
) )
.on(CursorSense::HoverEnd, move |ctx| { .on(CursorSense::HoverEnd, move |ctx| {
ctx.ui[ctx.id].color = color; ctx.widget.get_mut().color = color;
}); });
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack() (rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
}; };
let tabs = ( let tabs = (
switch_button(Color::RED, pad_test.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",
), ),
) )
@@ -195,11 +191,8 @@ impl DefaultAppState for Client {
ui.active_widgets(), ui.active_widgets(),
state.renderer.ui.view_count() state.renderer.ui.view_count()
); );
if new != *ui[&self.info].content { if new != *self.info.get().content {
*ui[&self.info].content = new; *self.info.get_mut().content = new;
}
if ui.needs_redraw() {
state.window.request_redraw();
} }
} }
} }

View File

@@ -1,13 +1,13 @@
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};
pub struct Selector; pub struct Selector;
impl<W: 'static> WidgetAttr<W> for Selector { impl<W: 'static> WidgetAttr<W> for Selector {
type Input = WidgetId<TextEdit>; type Input = WidgetRef<TextEdit>;
fn run(ui: &mut Ui, container: &WidgetId<W>, id: Self::Input) { fn run(ui: &mut Ui, container: &WidgetRef<W>, id: Self::Input) {
let container = container.clone(); let container = container.clone();
ui.register_event( ui.register_event(
&container.clone(), &container.clone(),
@@ -30,7 +30,7 @@ pub struct Selectable;
impl WidgetAttr<TextEdit> for Selectable { impl WidgetAttr<TextEdit> for Selectable {
type Input = (); type Input = ();
fn run(ui: &mut Ui, id: &WidgetId<TextEdit>, _: Self::Input) { fn run(ui: &mut Ui, id: &WidgetRef<TextEdit>, _: Self::Input) {
let id = id.clone(); let id = id.clone();
ui.register_event(&id.clone(), CursorSense::click_or_drag(), move |ctx| { ui.register_event(&id.clone(), CursorSense::click_or_drag(), move |ctx| {
select(ctx.ui, id.clone(), ctx.state, ctx.data); select(ctx.ui, id.clone(), ctx.state, ctx.data);
@@ -38,11 +38,11 @@ impl WidgetAttr<TextEdit> for Selectable {
} }
} }
fn select(ui: &mut Ui, id: WidgetId<TextEdit>, state: &mut UiState, data: CursorData) { fn select(ui: &mut Ui, id: WidgetRef<TextEdit>, state: &mut UiState, data: CursorData) {
let now = Instant::now(); let now = Instant::now();
let recent = (now - state.last_click) < Duration::from_millis(300); let recent = (now - state.last_click) < Duration::from_millis(300);
state.last_click = now; state.last_click = now;
ui.text(&id) id.get_mut()
.select(data.cursor, data.size, data.sense.is_dragging(), recent); .select(data.cursor, data.size, data.sense.is_dragging(), recent);
if let Some(region) = ui.window_region(&id) { if let Some(region) = ui.window_region(&id) {
state.window.set_ime_allowed(true); state.window.set_ime_allowed(true);

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,6 +1,4 @@
use crate::prelude::*; use crate::prelude::*;
use crate::winit::event::{Edited, Submit};
use crate::winit::{input::Input, render::UiRenderer};
use arboard::Clipboard; use arboard::Clipboard;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
@@ -9,12 +7,16 @@ use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
use winit::window::{Window, WindowAttributes}; use winit::window::{Window, WindowAttributes};
mod app; mod app;
pub mod attr; mod attr;
pub mod event; mod event;
pub mod input; mod input;
pub mod render; mod render;
pub use app::*; pub use app::*;
pub use attr::*;
pub use event::*;
pub use input::*;
pub use render::*;
pub type Proxy<Event> = EventLoopProxy<Event>; pub type Proxy<Event> = EventLoopProxy<Event>;
pub type DefaultApp<Data> = App<DefaultState<Data>>; pub type DefaultApp<Data> = App<DefaultState<Data>>;
@@ -28,7 +30,7 @@ pub struct DefaultState<AppState> {
pub struct UiState { pub struct UiState {
pub renderer: UiRenderer, pub renderer: UiRenderer,
pub input: Input, pub input: Input,
pub focus: Option<WidgetId<TextEdit>>, pub focus: Option<WidgetRef<TextEdit>>,
pub clipboard: Clipboard, pub clipboard: Clipboard,
pub window: Arc<Window>, pub window: Arc<Window>,
pub ime: usize, pub ime: usize,
@@ -105,12 +107,11 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
if old != ui_state.focus if old != ui_state.focus
&& let Some(old) = old && let Some(old) = old
{ {
ui.text(&old).deselect(); old.get_mut().deselect();
} }
match &event { match &event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
ui.update();
ui_state.renderer.update(ui); ui_state.renderer.update(ui);
ui_state.renderer.draw(); ui_state.renderer.draw();
} }
@@ -123,8 +124,8 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
&& event.state.is_pressed() && event.state.is_pressed()
{ {
let sel = &sel.clone(); let sel = &sel.clone();
let mut text = ui.text(sel); let res = sel.get_mut().apply_event(event, &ui_state.input.modifiers);
match text.apply_event(event, &ui_state.input.modifiers) { match res {
TextInputResult::Unfocus => { TextInputResult::Unfocus => {
ui_state.focus = None; ui_state.focus = None;
ui_state.window.set_ime_allowed(false); ui_state.window.set_ime_allowed(false);
@@ -134,7 +135,7 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
} }
TextInputResult::Paste => { TextInputResult::Paste => {
if let Ok(t) = ui_state.clipboard.get_text() { if let Ok(t) = ui_state.clipboard.get_text() {
text.insert(&t); sel.get_mut().insert(&t);
} }
ui.run_event(app_state, sel, Edited, ()); ui.run_event(app_state, sel, Edited, ());
} }
@@ -152,16 +153,15 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
} }
WindowEvent::Ime(ime) => { WindowEvent::Ime(ime) => {
if let Some(sel) = &ui_state.focus { if let Some(sel) = &ui_state.focus {
let mut text = ui.text(sel);
match ime { match ime {
Ime::Enabled | Ime::Disabled => (), Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => { Ime::Preedit(content, _pos) => {
// TODO: highlight once that's real // TODO: highlight once that's real
text.replace(ui_state.ime, content); sel.get_mut().replace(ui_state.ime, content);
ui_state.ime = content.chars().count(); ui_state.ime = content.chars().count();
} }
Ime::Commit(content) => { Ime::Commit(content) => {
text.insert(content); sel.get_mut().insert(content);
} }
} }
} }
@@ -169,7 +169,7 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
_ => (), _ => (),
} }
app_state.window_event(event, ui, ui_state); app_state.window_event(event, ui, ui_state);
if ui.needs_redraw() { if ui.update() {
ui_state.renderer.window().request_redraw(); ui_state.renderer.window().request_redraw();
} }
ui_state.input.end_frame(); ui_state.input.end_frame();

82
src/event.rs Normal file
View File

@@ -0,0 +1,82 @@
use crate::prelude::*;
use iris_macro::widget_trait;
pub mod eventable {
use super::*;
widget_trait! {
pub trait 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
}
}
}
}
// 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;

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,180 +0,0 @@
use std::{any::TypeId, marker::PhantomData, sync::mpsc::Sender};
use crate::{
layout::{Ui, WidgetLike},
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>,
_pd: PhantomData<W>,
}
#[repr(C)]
pub struct WeakWidgetId<W = AnyWidget> {
pub(super) ty: TypeId,
pub(super) id: Id,
counter: RefCounter,
send: Sender<Id>,
_pd: PhantomData<W>,
}
impl<W> PartialEq for WidgetId<W> {
fn eq(&self, other: &Self) -> bool {
self.ty == other.ty && self.id == other.id
}
}
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(),
_pd: PhantomData,
}
}
}
impl<W> WidgetId<W> {
pub(super) fn new(id: Id, ty: TypeId, send: Sender<Id>) -> Self {
Self {
ty,
id,
counter: RefCounter::new(),
send,
_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 weak(&self) -> WeakWidgetId<W> {
let Self {
ty,
id,
ref counter,
ref send,
_pd,
} = *self;
WeakWidgetId {
ty,
id,
counter: counter.quiet_clone(),
send: send.clone(),
_pd,
}
}
}
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() {
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 {}
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>;
}
impl<W> WidgetIdLike<W> for &WidgetId<W> {
fn id(self, _: &Sender<Id>) -> WidgetId<W> {
self.clone()
}
}
pub trait IdLike<W> {
fn id(&self) -> Id;
}
impl<W> IdLike<W> for WidgetId<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,232 +0,0 @@
use image::DynamicImage;
use crate::{
core::{TextEdit, TextEditCtx},
layout::{
Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, 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)
}
/// 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.new_id();
self.data.widgets.insert(id.id, w);
id
}
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)
}
fn new_id<W: Widget>(&mut self) -> WidgetId<W> {
WidgetId::new(
self.data.widgets.reserve(),
TypeId::of::<W>(),
self.send.clone(),
)
}
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 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 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 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,160 +0,0 @@
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;
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 set_root(self, ui: &mut Ui)
where
Self: Sized,
{
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>;
}
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,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(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)]
pub mod core; mod default;
pub mod layout; mod event;
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::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: WidgetRef,
} }
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: WidgetRef,
pub align: Align, pub align: Align,
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct LayerOffset { pub struct LayerOffset {
pub inner: WidgetId, pub inner: WidgetRef,
pub offset: usize, pub offset: usize,
} }

View File

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

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Offset { pub struct Offset {
pub inner: WidgetId, pub inner: WidgetRef,
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: WidgetRef,
} }
impl Widget for Pad { impl Widget for Pad {

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Scroll { pub struct Scroll {
inner: WidgetId, inner: WidgetRef,
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: WidgetRef, 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: WidgetRef,
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<WidgetRef>,
pub dir: Dir, pub dir: Dir,
pub gap: f32, pub gap: f32,
} }
@@ -182,7 +182,7 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Ta
} }
impl std::ops::Deref for Span { impl std::ops::Deref for Span {
type Target = Vec<WidgetId>; type Target = Vec<WidgetRef>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.children &self.children

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use crate::prelude::*; use crate::prelude::*;
pub struct Stack { pub struct Stack {
pub children: Vec<WidgetId>, pub children: Vec<WidgetRef>,
pub size: StackSize, pub size: StackSize,
} }

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<WidgetRef>,
} }
impl Widget for WidgetPtr { impl Widget for WidgetPtr {

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,11 +8,11 @@ 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)]
pub enum Button { pub enum CursorButton {
Left, Left,
Right, Right,
Middle, Middle,
@@ -19,9 +20,9 @@ pub enum Button {
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum CursorSense { pub enum CursorSense {
PressStart(Button), PressStart(CursorButton),
Pressing(Button), Pressing(CursorButton),
PressEnd(Button), PressEnd(CursorButton),
HoverStart, HoverStart,
Hovering, Hovering,
HoverEnd, HoverEnd,
@@ -32,16 +33,16 @@ pub struct CursorSenses(Vec<CursorSense>);
impl CursorSense { impl CursorSense {
pub fn click() -> Self { pub fn click() -> Self {
Self::PressStart(Button::Left) Self::PressStart(CursorButton::Left)
} }
pub fn click_or_drag() -> CursorSenses { pub fn click_or_drag() -> CursorSenses {
Self::click() | Self::Pressing(Button::Left) Self::click() | Self::Pressing(CursorButton::Left)
} }
pub fn unclick() -> Self { pub fn unclick() -> Self {
Self::PressEnd(Button::Left) Self::PressEnd(CursorButton::Left)
} }
pub fn is_dragging(&self) -> bool { pub fn is_dragging(&self) -> bool {
matches!(self, CursorSense::Pressing(Button::Left)) matches!(self, CursorSense::Pressing(CursorButton::Left))
} }
} }
@@ -61,11 +62,11 @@ pub struct CursorButtons {
} }
impl CursorButtons { impl CursorButtons {
pub fn select(&self, button: &Button) -> &ActivationState { pub fn select(&self, button: &CursorButton) -> &ActivationState {
match button { match button {
Button::Left => &self.left, CursorButton::Left => &self.left,
Button::Right => &self.right, CursorButton::Right => &self.right,
Button::Middle => &self.middle, CursorButton::Middle => &self.middle,
} }
} }
@@ -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 {
@@ -205,7 +210,11 @@ impl<Ctx: 'static> CursorModule<Ctx> {
scroll_delta: cursor.scroll_delta, scroll_delta: cursor.scroll_delta,
sense, sense,
}; };
(sensor.f)(EventCtx { ui, state: ctx, data }); (sensor.f)(EventCtx {
ui,
state: ctx,
data,
});
} }
} }
} }
@@ -214,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.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,8 +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(),
); );
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);

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, LayoutRun, 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,
@@ -13,17 +13,19 @@ pub struct TextEdit {
selection: TextSelection, selection: TextSelection,
history: Vec<(String, TextSelection)>, history: Vec<(String, TextSelection)>,
double_hit: Option<Cursor>, double_hit: Option<Cursor>,
data: TextData,
pub single_line: bool, pub single_line: bool,
} }
impl TextEdit { impl TextEdit {
pub fn new(view: TextView, single_line: bool) -> Self { pub fn new(view: TextView, single_line: bool, data: TextData) -> Self {
Self { Self {
view, view,
selection: Default::default(), selection: Default::default(),
history: Default::default(), history: Default::default(),
double_hit: None, double_hit: None,
single_line, single_line,
data,
} }
} }
pub fn select_content(&self, start: Cursor, end: Cursor) -> String { pub fn select_content(&self, start: Cursor, end: Cursor) -> String {
@@ -42,6 +44,369 @@ impl TextEdit {
str str
} }
} }
pub fn take(&mut self) -> String {
let text = self
.buf
.lines
.drain(..)
.map(|l| l.into_text())
.collect::<Vec<_>>()
.join("\n");
self.set("");
text
}
pub fn set(&mut self, text: &str) {
let text = self.string(text);
self.view.buf.set_text(
&mut self.data.get_mut().font_system,
&text,
&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.selection = if select {
TextSelection::Span {
start: cursor,
end: new,
}
} else {
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) {
let text = self.string(text);
for _ in 0..len {
self.delete(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) {
let text = self.string(text);
let mut lines = text.split('\n');
let Some(first) = lines.next() else {
return;
};
self.insert_inner(first, true);
for line in lines {
self.newline();
self.insert_inner(line, true);
}
}
pub fn clear_span(&mut self) -> bool {
if let TextSelection::Span { start, end } = self.selection {
self.delete_between(start, end);
let (start, _) = sort_cursors(start, end);
self.selection = TextSelection::Pos(start);
true
} else {
false
}
}
pub fn delete_between(&mut self, start: Cursor, end: Cursor) {
let lines = &mut self.view.buf.lines;
let (start, end) = sort_cursors(start, end);
if start.line == end.line {
let line = &mut lines[start.line];
let text = line.text();
let text = text[..start.index].to_string() + &text[end.index..];
edit_line(line, text);
} else {
// start
let start_text = lines[start.line].text()[..start.index].to_string();
let end_text = &lines[end.line].text()[end.index..];
let text = start_text + end_text;
edit_line(&mut lines[start.line], text);
}
// between
let range = (start.line + 1)..=end.line;
if !range.is_empty() {
lines.splice(range, None);
}
}
fn insert_inner(&mut self, text: &str, mov: bool) {
self.clear_span();
if let TextSelection::Pos(cursor) = self.selection {
let line = &mut self.view.buf.lines[cursor.line];
let mut line_text = line.text().to_string();
line_text.insert_str(cursor.index, text);
edit_line(line, line_text);
if mov {
for _ in 0..text.chars().count() {
self.motion(Motion::Right, false);
}
}
}
}
pub fn newline(&mut self) {
if self.single_line {
return;
}
self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.selection {
let lines = &mut self.view.buf.lines;
let line = &mut lines[cursor.line];
let new = line.split_off(cursor.index);
cursor.line += 1;
lines.insert(cursor.line, new);
cursor.index = 0;
}
}
pub fn backspace(&mut self, word: bool) {
if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.selection
&& (cursor.index != 0 || cursor.line != 0)
{
self.motion(if word { Motion::LeftWord } else { Motion::Left }, false);
self.delete(word);
}
}
pub fn delete(&mut self, word: bool) {
if self.clear_span() {
return;
}
if let TextSelection::Pos(cursor) = &mut self.selection {
if word {
let start = *cursor;
if let Some(end) = self.buf_motion(start, Motion::RightWord) {
self.delete_between(start, end);
}
} else {
let lines = &mut self.view.buf.lines;
let line = &mut lines[cursor.line];
if cursor.index == line.text().len() {
if cursor.line == lines.len() - 1 {
return;
}
let add = lines.remove(cursor.line + 1).into_text();
let line = &mut lines[cursor.line];
let mut cur = line.text().to_string();
cur.push_str(&add);
edit_line(line, cur);
} else {
let mut text = line.text().to_string();
text.remove(cursor.index);
edit_line(line, text);
}
}
}
}
fn buf_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
self.view
.buf
.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 {
TextSelection::None => {
if !drag && let Some(hit) = hit {
*sel = TextSelection::Pos(hit)
}
}
TextSelection::Pos(pos) => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => (),
(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 },
},
TextSelection::Span { start, end } => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => *sel = TextSelection::Pos(*start),
(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,
},
}
if let TextSelection::Span { start, end } = sel
&& start == end
{
*sel = TextSelection::Pos(*start);
}
}
pub fn deselect(&mut self) {
self.selection = TextSelection::None;
}
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 {
Key::Named(named) => match named {
NamedKey::Backspace => self.backspace(modifiers.control),
NamedKey::Delete => self.delete(modifiers.control),
NamedKey::Space => self.insert(" "),
NamedKey::Enter => {
if modifiers.shift {
self.newline();
} else {
return TextInputResult::Submit;
}
}
NamedKey::ArrowRight => {
if modifiers.control {
self.motion(Motion::RightWord, modifiers.shift)
} else {
self.motion(Motion::Right, modifiers.shift)
}
}
NamedKey::ArrowLeft => {
if modifiers.control {
self.motion(Motion::LeftWord, modifiers.shift)
} else {
self.motion(Motion::Left, modifiers.shift)
}
}
NamedKey::ArrowUp => self.motion(Motion::Up, modifiers.shift),
NamedKey::ArrowDown => self.motion(Motion::Down, modifiers.shift),
NamedKey::Escape => {
self.deselect();
return TextInputResult::Unfocus;
}
_ => return TextInputResult::Unused,
},
Key::Character(text) => {
if modifiers.control {
match text.as_str() {
"v" => return TextInputResult::Paste,
"c" => {
if let TextSelection::Span { start, end } = self.selection {
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);
}
}
"a" => {
if !self.buf.lines[0].text().is_empty() || self.buf.lines.len() > 1 {
let lines = &self.buf.lines;
let last_line = lines.len() - 1;
let last_idx = lines[last_line].text().len();
self.selection = TextSelection::Span {
start: Cursor::new(0, 0),
end: Cursor::new(last_line, last_idx),
};
}
}
"z" => {
*undo = true;
}
_ => self.insert(text),
}
} else {
self.insert(text);
}
}
_ => return TextInputResult::Unused,
}
TextInputResult::Used
}
} }
impl Widget for TextEdit { impl Widget for TextEdit {
@@ -175,373 +540,6 @@ fn cursor_pos(cursor: Cursor, buf: &TextBuffer) -> Option<Vec2> {
prev prev
} }
pub struct TextEditCtx<'a> {
pub text: &'a mut TextEdit,
pub font_system: &'a mut FontSystem,
}
impl<'a> TextEditCtx<'a> {
pub fn take(&mut self) -> String {
let text = self
.text
.buf
.lines
.drain(..)
.map(|l| l.into_text())
.collect::<Vec<_>>()
.join("\n");
self.text
.buf
.set_text(self.font_system, "", &Attrs::new(), SHAPING, None);
self.text.selection.clear();
text
}
pub fn set(&mut self, text: &str) {
let text = self.string(text);
self.text
.buf
.set_text(self.font_system, &text, &Attrs::new(), SHAPING, None);
self.text.selection.clear();
}
pub fn motion(&mut self, motion: Motion, select: bool) {
if let TextSelection::Pos(cursor) = self.text.selection
&& let Some(new) = self.buf_motion(cursor, motion)
{
if select {
self.text.selection = TextSelection::Span {
start: cursor,
end: new,
};
} else {
self.text.selection = TextSelection::Pos(new);
}
} else if let TextSelection::Span { start, end } = self.text.selection {
if select {
if let Some(cursor) = self.buf_motion(end, motion) {
self.text.selection = TextSelection::Span { start, end: cursor };
}
} else {
let (start, end) = sort_cursors(start, end);
let sel = &mut self.text.selection;
match motion {
Motion::Left | Motion::LeftWord => *sel = TextSelection::Pos(start),
Motion::Right | Motion::RightWord => *sel = TextSelection::Pos(end),
_ => {
if let Some(cursor) = self.buf_motion(end, motion) {
self.text.selection = TextSelection::Pos(cursor);
}
}
}
}
}
}
pub fn replace(&mut self, len: usize, text: &str) {
let text = self.string(text);
for _ in 0..len {
self.delete(false);
}
self.insert_inner(&text, false);
}
fn string(&self, text: &str) -> String {
if self.text.single_line {
text.replace('\n', "")
} else {
text.to_string()
}
}
pub fn insert(&mut self, text: &str) {
let text = self.string(text);
let mut lines = text.split('\n');
let Some(first) = lines.next() else {
return;
};
self.insert_inner(first, true);
for line in lines {
self.newline();
self.insert_inner(line, true);
}
}
pub fn clear_span(&mut self) -> bool {
if let TextSelection::Span { start, end } = self.text.selection {
self.delete_between(start, end);
let (start, _) = sort_cursors(start, end);
self.text.selection = TextSelection::Pos(start);
true
} else {
false
}
}
pub fn delete_between(&mut self, start: Cursor, end: Cursor) {
let lines = &mut self.text.view.buf.lines;
let (start, end) = sort_cursors(start, end);
if start.line == end.line {
let line = &mut lines[start.line];
let text = line.text();
let text = text[..start.index].to_string() + &text[end.index..];
edit_line(line, text);
} else {
// start
let start_text = lines[start.line].text()[..start.index].to_string();
let end_text = &lines[end.line].text()[end.index..];
let text = start_text + end_text;
edit_line(&mut lines[start.line], text);
}
// between
let range = (start.line + 1)..=end.line;
if !range.is_empty() {
lines.splice(range, None);
}
}
fn insert_inner(&mut self, text: &str, mov: bool) {
self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection {
let line = &mut self.text.view.buf.lines[cursor.line];
let mut line_text = line.text().to_string();
line_text.insert_str(cursor.index, text);
edit_line(line, line_text);
if mov {
for _ in 0..text.chars().count() {
self.motion(Motion::Right, false);
}
}
}
}
pub fn newline(&mut self) {
if self.text.single_line {
return;
}
self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection {
let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line];
let new = line.split_off(cursor.index);
cursor.line += 1;
lines.insert(cursor.line, new);
cursor.index = 0;
}
}
pub fn backspace(&mut self, word: bool) {
if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.text.selection
&& (cursor.index != 0 || cursor.line != 0)
{
self.motion(if word { Motion::LeftWord } else { Motion::Left }, false);
self.delete(word);
}
}
pub fn delete(&mut self, word: bool) {
if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.text.selection
{
if word {
let start = *cursor;
if let Some(end) = self.buf_motion(start, Motion::RightWord) {
self.delete_between(start, end);
}
} else {
let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line];
if cursor.index == line.text().len() {
if cursor.line == lines.len() - 1 {
return;
}
let add = lines.remove(cursor.line + 1).into_text();
let line = &mut lines[cursor.line];
let mut cur = line.text().to_string();
cur.push_str(&add);
edit_line(line, cur);
} else {
let mut text = line.text().to_string();
text.remove(cursor.index);
edit_line(line, text);
}
}
}
}
fn buf_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
self.text
.buf
.cursor_motion(self.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.text.selection = TextSelection::Span { start, end };
}
}
pub fn select_line_at(&mut self, cursor: Cursor) {
let end = self.text.buf.lines[cursor.line].text().len();
self.text.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.text.region().top_left().to_abs(size);
let hit = self.text.buf.hit(pos.x, pos.y);
let sel = &mut self.text.selection;
match sel {
TextSelection::None => {
if !drag && let Some(hit) = hit {
*sel = TextSelection::Pos(hit)
}
}
TextSelection::Pos(pos) => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => (),
(Some(hit), false) => {
if recent && hit == *pos {
self.text.double_hit = Some(hit);
return self.select_word_at(hit);
} else {
*pos = hit
}
}
(Some(end), true) => *sel = TextSelection::Span { start: *pos, end },
},
TextSelection::Span { start, end } => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => *sel = TextSelection::Pos(*start),
(Some(hit), false) => {
if recent
&& let Some(double) = self.text.double_hit
&& double == hit
{
return self.select_line_at(hit);
} else {
*sel = TextSelection::Pos(hit)
}
}
(Some(hit), true) => *end = hit,
},
}
if let TextSelection::Span { start, end } = sel
&& start == end
{
*sel = TextSelection::Pos(*start);
}
}
pub fn deselect(&mut self) {
self.text.selection = TextSelection::None;
}
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
let old = (self.text.content(), self.text.selection);
let mut undo = false;
let res = self.apply_event_inner(event, modifiers, &mut undo);
if undo && let Some((old, selection)) = self.text.history.pop() {
self.set(&old);
self.text.selection = selection;
} else if self.text.content() != old.0 {
self.text.history.push(old);
}
res
}
fn apply_event_inner(
&mut self,
event: &KeyEvent,
modifiers: &Modifiers,
undo: &mut bool,
) -> TextInputResult {
match &event.logical_key {
Key::Named(named) => match named {
NamedKey::Backspace => self.backspace(modifiers.control),
NamedKey::Delete => self.delete(modifiers.control),
NamedKey::Space => self.insert(" "),
NamedKey::Enter => {
if modifiers.shift {
self.newline();
} else {
return TextInputResult::Submit;
}
}
NamedKey::ArrowRight => {
if modifiers.control {
self.motion(Motion::RightWord, modifiers.shift)
} else {
self.motion(Motion::Right, modifiers.shift)
}
}
NamedKey::ArrowLeft => {
if modifiers.control {
self.motion(Motion::LeftWord, modifiers.shift)
} else {
self.motion(Motion::Left, modifiers.shift)
}
}
NamedKey::ArrowUp => self.motion(Motion::Up, modifiers.shift),
NamedKey::ArrowDown => self.motion(Motion::Down, modifiers.shift),
NamedKey::Escape => {
self.deselect();
return TextInputResult::Unfocus;
}
_ => return TextInputResult::Unused,
},
Key::Character(text) => {
if modifiers.control {
match text.as_str() {
"v" => return TextInputResult::Paste,
"c" => {
if let TextSelection::Span { start, end } = self.text.selection {
let content = self.text.select_content(start, end);
return TextInputResult::Copy(content);
}
}
"x" => {
if let TextSelection::Span { start, end } = self.text.selection {
let content = self.text.select_content(start, end);
self.clear_span();
return TextInputResult::Copy(content);
}
}
"a" => {
if !self.text.buf.lines[0].text().is_empty()
|| self.text.buf.lines.len() > 1
{
let lines = &self.text.buf.lines;
let last_line = lines.len() - 1;
let last_idx = lines[last_line].text().len();
self.text.selection = TextSelection::Span {
start: Cursor::new(0, 0),
end: Cursor::new(last_line, last_idx),
};
}
}
"z" => {
*undo = true;
}
_ => self.insert(text),
}
} else {
self.insert(text);
}
}
_ => return TextInputResult::Unused,
}
TextInputResult::Used
}
}
#[derive(Default)] #[derive(Default)]
pub struct Modifiers { pub struct Modifiers {
pub shift: bool, pub shift: bool,

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<WidgetRef>,
} }
impl TextView { impl TextView {
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetId>) -> Self { pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetRef>) -> Self {
Self { Self {
attrs: attrs.into(), attrs: attrs.into(),
buf: buf.into(), buf: buf.into(),
@@ -67,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;
@@ -136,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,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, Tag> { widget_trait! {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>; pub trait 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,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
} }
} }
@@ -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,16 +80,16 @@ 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.ui[ctx.id]; 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);
}) })
.add(ui) .add(ui)
@@ -115,33 +98,37 @@ 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));
} }
} }