Compare commits
31 Commits
31ff17c21a
...
7f4846a2d3
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f4846a2d3 | |||
| e44bb8eca4 | |||
| 434e3c3af7 | |||
| b66d4da5d7 | |||
| 7b3a79b1b0 | |||
| c99d466b75 | |||
| 38266debb6 | |||
| 62aa02847a | |||
| f6b1143665 | |||
| 28d17c49c6 | |||
| 23ae5b246e | |||
| db888416b6 | |||
| f7b100e00c | |||
| e5d0a7e592 | |||
| 84c460a91f | |||
| d6a9711ceb | |||
| ee0616885f | |||
| 14a9da0553 | |||
| 8e08f67627 | |||
| 84b3bf9078 | |||
| bf3ade840b | |||
| 2aa5719166 | |||
| 90c579d734 | |||
| d757e805e8 | |||
| 9deba3d9d7 | |||
| c24c517c60 | |||
| fc89826794 | |||
| 140be50baa | |||
| 1c6fc99f57 | |||
| 1cec56e847 | |||
| 246caffb34 |
505
Cargo.lock
generated
505
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@@ -1,12 +1,30 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iris"
|
name = "iris"
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
default-run = "test"
|
default-run = "test"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
iris-core = { workspace = true }
|
||||||
|
iris-macro = { workspace = true }
|
||||||
|
cosmic-text = { workspace = true }
|
||||||
|
unicode-segmentation = { workspace = true }
|
||||||
|
winit = { workspace = true }
|
||||||
|
arboard = { workspace = true, features = ["wayland-data-control"] }
|
||||||
|
pollster = { workspace = true }
|
||||||
|
wgpu = { workspace = true }
|
||||||
|
image = { workspace = true }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["core", "macro"]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
winit = "0.30.12"
|
winit = "0.30.12"
|
||||||
wgpu = "27.0.1"
|
wgpu = "27.0.1"
|
||||||
@@ -15,5 +33,6 @@ image = "0.25.6"
|
|||||||
cosmic-text = "0.15.0"
|
cosmic-text = "0.15.0"
|
||||||
unicode-segmentation = "1.12.0"
|
unicode-segmentation = "1.12.0"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
arboard = { version = "3.6.1", features = ["wayland-data-control"] }
|
arboard = "3.6.1"
|
||||||
|
iris-core = { path = "core" }
|
||||||
|
iris-macro = { path = "macro" }
|
||||||
|
|||||||
3
TODO
3
TODO
@@ -16,6 +16,7 @@ scaling
|
|||||||
field could be best solution so redrawing stuff isn't needed & you can specify both as user
|
field could be best solution so redrawing stuff isn't needed & you can specify both as user
|
||||||
|
|
||||||
WidgetRef<W> or smth instead of Id
|
WidgetRef<W> or smth instead of Id
|
||||||
|
fyi this is not the same as what was just implemented
|
||||||
enum that's either an Id or an actual concrete instance of W
|
enum that's either an Id or an actual concrete instance of W
|
||||||
painter takes them in instead of (or in addition to) id
|
painter takes them in instead of (or in addition to) id
|
||||||
then type wrapper widgets to contain them
|
then type wrapper widgets to contain them
|
||||||
@@ -36,3 +37,5 @@ don't forget I'm streaming
|
|||||||
|
|
||||||
tags
|
tags
|
||||||
vecs for each widget type?
|
vecs for each widget type?
|
||||||
|
|
||||||
|
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||||
|
|||||||
13
core/Cargo.toml
Normal file
13
core/Cargo.toml
Normal 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 }
|
||||||
|
|
||||||
20
core/src/layout/attr.rs
Normal file
20
core/src/layout/attr.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use crate::layout::{Ui, WidgetIdFn, WidgetLike, WidgetRef};
|
||||||
|
|
||||||
|
pub trait WidgetAttr<W: ?Sized> {
|
||||||
|
type Input;
|
||||||
|
fn run(ui: &mut Ui, id: &WidgetRef<W>, input: Self::Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Attrable<W: ?Sized, Tag> {
|
||||||
|
fn attr<A: WidgetAttr<W>>(self, input: A::Input) -> impl WidgetIdFn<W>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<WL: WidgetLike<Tag>, Tag> Attrable<WL::Widget, Tag> for WL {
|
||||||
|
fn attr<A: WidgetAttr<WL::Widget>>(self, input: A::Input) -> impl WidgetIdFn<WL::Widget> {
|
||||||
|
|ui| {
|
||||||
|
let id = self.add(ui);
|
||||||
|
A::run(ui, &id, input);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
core/src/layout/event.rs
Normal file
187
core/src/layout/event.rs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
use std::{hash::Hash, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
layout::{IdFnTag, Ui, UiModule, WidgetIdFn, WidgetLike, WidgetRef},
|
||||||
|
util::{HashMap, Id},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Event: Sized {
|
||||||
|
type Module<Ctx: 'static>: EventModule<Self, Ctx>;
|
||||||
|
type Data: Clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EventCtx<'a, Ctx, Data> {
|
||||||
|
pub ui: &'a mut Ui,
|
||||||
|
pub state: &'a mut Ctx,
|
||||||
|
pub data: Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>;
|
||||||
|
pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> {
|
||||||
|
pub widget: &'a WidgetRef<W>,
|
||||||
|
pub ui: &'a mut Ui,
|
||||||
|
pub state: &'a mut Ctx,
|
||||||
|
pub data: Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + 'static {}
|
||||||
|
impl<F: Fn(EventCtx<Ctx, Data>) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
|
||||||
|
|
||||||
|
pub trait WidgetEventFn<Ctx, Data, W: ?Sized>: Fn(EventIdCtx<Ctx, Data, W>) + 'static {}
|
||||||
|
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W: ?Sized> WidgetEventFn<Ctx, Data, W>
|
||||||
|
for F
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Ui {
|
||||||
|
pub fn register_event<W: ?Sized, E: Event, Ctx: 'static>(
|
||||||
|
&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,
|
||||||
|
f: impl WidgetEventFn<Ctx, E::Data, W>,
|
||||||
|
) {
|
||||||
|
let id_ = id.weak();
|
||||||
|
self.data
|
||||||
|
.modules
|
||||||
|
.get_mut::<E::Module<Ctx>>()
|
||||||
|
.register(id.id(), event, move |ctx| {
|
||||||
|
f(EventIdCtx {
|
||||||
|
widget: &id_.expect_strong(),
|
||||||
|
ui: ctx.ui,
|
||||||
|
state: ctx.state,
|
||||||
|
data: ctx.data,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DefaultEvent: Hash + Eq + 'static {
|
||||||
|
type Data: Clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: DefaultEvent> Event for E {
|
||||||
|
type Module<Ctx: 'static> = DefaultEventModule<E, Ctx>;
|
||||||
|
type Data = E::Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait EventModule<E: Event, Ctx>: UiModule + Default {
|
||||||
|
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, E::Data>);
|
||||||
|
fn run<'a>(
|
||||||
|
&self,
|
||||||
|
id: &Id,
|
||||||
|
event: E,
|
||||||
|
) -> Option<impl Fn(EventCtx<Ctx, E::Data>) + use<'a, Self, E, Ctx>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventFnMap<Ctx, Data> = HashMap<Id, Vec<Rc<dyn EventFn<Ctx, Data>>>>;
|
||||||
|
pub struct DefaultEventModule<E: Event, Ctx> {
|
||||||
|
map: HashMap<E, EventFnMap<Ctx, <E as Event>::Data>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Event + 'static, Ctx: 'static> UiModule for DefaultEventModule<E, Ctx> {
|
||||||
|
fn on_remove(&mut self, id: &Id) {
|
||||||
|
for map in self.map.values_mut() {
|
||||||
|
map.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HashableEvent: Event + Hash + Eq + 'static {}
|
||||||
|
impl<E: Event + Hash + Eq + 'static> HashableEvent for E {}
|
||||||
|
|
||||||
|
impl<E: HashableEvent, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<E, Ctx> {
|
||||||
|
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
|
||||||
|
self.map
|
||||||
|
.entry(event)
|
||||||
|
.or_default()
|
||||||
|
.entry(id)
|
||||||
|
.or_default()
|
||||||
|
.push(Rc::new(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run<'a>(
|
||||||
|
&self,
|
||||||
|
id: &Id,
|
||||||
|
event: E,
|
||||||
|
) -> Option<impl Fn(EventCtx<Ctx, E::Data>) + use<'a, E, Ctx>> {
|
||||||
|
if let Some(map) = self.map.get(&event)
|
||||||
|
&& let Some(fs) = map.get(id)
|
||||||
|
{
|
||||||
|
let fs = fs.clone();
|
||||||
|
Some(move |ctx: EventCtx<Ctx, <E as Event>::Data>| {
|
||||||
|
for f in &fs {
|
||||||
|
f(EventCtx {
|
||||||
|
ui: ctx.ui,
|
||||||
|
state: ctx.state,
|
||||||
|
data: ctx.data.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: HashableEvent, Ctx: 'static> DefaultEventModule<E, Ctx> {
|
||||||
|
pub fn run_all(&self, event: E, ctx: EventCtx<Ctx, E::Data>)
|
||||||
|
where
|
||||||
|
E::Data: Clone,
|
||||||
|
{
|
||||||
|
if let Some(map) = self.map.get(&event) {
|
||||||
|
for fs in map.values() {
|
||||||
|
for f in fs {
|
||||||
|
f(EventCtx {
|
||||||
|
ui: ctx.ui,
|
||||||
|
state: ctx.state,
|
||||||
|
data: ctx.data.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Event + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
map: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui {
|
||||||
|
pub fn run_event<E: Event, Ctx: 'static, W: ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
id: &WidgetRef<W>,
|
||||||
|
event: E,
|
||||||
|
data: E::Data,
|
||||||
|
) {
|
||||||
|
if let Some(f) = self
|
||||||
|
.data
|
||||||
|
.modules
|
||||||
|
.get_mut::<E::Module<Ctx>>()
|
||||||
|
.run(&id.id(), event)
|
||||||
|
{
|
||||||
|
f(EventCtx {
|
||||||
|
ui: self,
|
||||||
|
state: ctx,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +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 vec2;
|
mod view;
|
||||||
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 vec2::*;
|
pub use view::*;
|
||||||
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
49
core/src/layout/num.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use crate::util::Vec2;
|
||||||
|
use std::marker::Destruct;
|
||||||
|
|
||||||
|
pub const trait UiNum {
|
||||||
|
fn to_f32(self) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl const UiNum for f32 {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl const UiNum for u32 {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl const UiNum for i32 {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -41,6 +41,11 @@ impl Size {
|
|||||||
y: Len::ZERO,
|
y: Len::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const REST: Self = Self {
|
||||||
|
x: Len::REST,
|
||||||
|
y: Len::REST,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn abs(v: Vec2) -> Self {
|
pub fn abs(v: Vec2) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: Len::abs(v.x),
|
x: Len::abs(v.x),
|
||||||
@@ -97,6 +102,12 @@ impl Len {
|
|||||||
rest: 0.0,
|
rest: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const REST: Self = Self {
|
||||||
|
abs: 0.0,
|
||||||
|
rel: 0.0,
|
||||||
|
rest: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn apply_rest(&self) -> UiScalar {
|
pub fn apply_rest(&self) -> UiScalar {
|
||||||
UiScalar {
|
UiScalar {
|
||||||
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
|
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
|
||||||
@@ -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::*;
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
use std::{cell::Ref, marker::Unsize, sync::mpsc::Sender};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{
|
layout::{
|
||||||
Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, RenderedText,
|
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>,
|
||||||
@@ -17,17 +21,14 @@ pub struct Painter<'a, 'c> {
|
|||||||
children: Vec<Id>,
|
children: Vec<Id>,
|
||||||
children_width: HashMap<Id, (UiVec2, Len)>,
|
children_width: HashMap<Id, (UiVec2, Len)>,
|
||||||
children_height: HashMap<Id, (UiVec2, Len)>,
|
children_height: HashMap<Id, (UiVec2, Len)>,
|
||||||
/// whether this widget depends on region's final pixel size or not
|
|
||||||
/// TODO: decide if point (pt) should be used here instead of px
|
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
id: Id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// context for a painter; lets you draw and redraw widgets
|
/// context for a painter; lets you draw and redraw widgets
|
||||||
struct PainterCtx<'a> {
|
struct PainterCtx<'a> {
|
||||||
pub widgets: &'a Widgets,
|
pub widgets: &'a Widgets,
|
||||||
pub active: &'a mut HashMap<Id, WidgetInstance>,
|
pub active: &'a mut HashMap<Id, WidgetInstance>,
|
||||||
pub layers: &'a mut Layers,
|
pub layers: &'a mut PrimitiveLayers,
|
||||||
pub textures: &'a mut Textures,
|
pub textures: &'a mut Textures,
|
||||||
pub masks: &'a mut TrackedArena<Mask, u32>,
|
pub masks: &'a mut TrackedArena<Mask, u32>,
|
||||||
pub text: &'a mut TextData,
|
pub text: &'a mut TextData,
|
||||||
@@ -62,11 +63,10 @@ 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>,
|
||||||
pub layers: Layers,
|
pub layers: PrimitiveLayers,
|
||||||
pub textures: Textures,
|
pub textures: Textures,
|
||||||
pub text: TextData,
|
pub text: TextData,
|
||||||
pub output_size: Vec2,
|
pub output_size: Vec2,
|
||||||
@@ -75,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;
|
||||||
@@ -107,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;
|
||||||
@@ -133,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;
|
||||||
@@ -160,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,
|
||||||
@@ -169,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
|
||||||
@@ -220,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(),
|
||||||
@@ -229,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;
|
||||||
@@ -279,7 +296,7 @@ impl<'a> PainterCtx<'a> {
|
|||||||
fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) {
|
fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) {
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
for h in &active.primitives {
|
for h in &active.primitives {
|
||||||
let region = self.layers[h.layer].primitives.region_mut(h);
|
let region = self.layers[h.layer].region_mut(h);
|
||||||
*region = region.outside(&from).within(&to);
|
*region = region.outside(&from).within(&to);
|
||||||
}
|
}
|
||||||
active.region = active.region.outside(&from).within(&to);
|
active.region = active.region.outside(&from).within(&to);
|
||||||
@@ -342,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();
|
||||||
@@ -352,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,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,
|
||||||
@@ -390,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) {
|
||||||
@@ -423,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),
|
||||||
@@ -443,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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,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)>,
|
||||||
@@ -510,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
|
||||||
@@ -521,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;
|
||||||
@@ -535,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));
|
||||||
@@ -553,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),
|
||||||
@@ -584,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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#![allow(clippy::multiple_bound_locations)]
|
#![allow(clippy::multiple_bound_locations)]
|
||||||
|
|
||||||
|
use std::marker::Destruct;
|
||||||
|
|
||||||
/// stored in linear for sane manipulation
|
/// stored in linear for sane manipulation
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
||||||
@@ -56,23 +58,47 @@ pub trait ColorNum {
|
|||||||
const MAX: Self;
|
const MAX: Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ColorNum + F32Conversion> Color<T> {
|
macro_rules! map_rgb {
|
||||||
pub fn mul_rgb(self, amt: impl F32Conversion) -> Self {
|
($x:ident,$self:ident, $e:tt) => {
|
||||||
|
#[allow(unused_braces)]
|
||||||
|
Self {
|
||||||
|
r: {
|
||||||
|
let $x = $self.r;
|
||||||
|
$e
|
||||||
|
},
|
||||||
|
g: {
|
||||||
|
let $x = $self.g;
|
||||||
|
$e
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
let $x = $self.b;
|
||||||
|
$e
|
||||||
|
},
|
||||||
|
a: $self.a,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ColorNum + const F32Conversion> Color<T>
|
||||||
|
where
|
||||||
|
Self: const Destruct,
|
||||||
|
{
|
||||||
|
pub const fn mul_rgb(self, amt: impl const F32Conversion) -> Self {
|
||||||
let amt = amt.to();
|
let amt = amt.to();
|
||||||
self.map_rgb(|x| T::from(x.to() * amt))
|
map_rgb!(x, self, { T::from(x.to() * amt) })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_rgb(self, amt: impl F32Conversion) -> Self {
|
pub const fn add_rgb(self, amt: impl const F32Conversion) -> Self {
|
||||||
let amt = amt.to();
|
let amt = amt.to();
|
||||||
self.map_rgb(|x| T::from(x.to() + amt))
|
map_rgb!(x, self, { T::from(x.to() + amt) })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn darker(self, amt: f32) -> Self {
|
pub const fn darker(self, amt: f32) -> Self {
|
||||||
self.mul_rgb(1.0 - amt)
|
self.mul_rgb(1.0 - amt)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn brighter(self, amt: f32) -> Self {
|
pub const fn brighter(self, amt: f32) -> Self {
|
||||||
self.map_rgb(|x| {
|
map_rgb!(x, self, {
|
||||||
let x = x.to();
|
let x = x.to();
|
||||||
T::from(x + (1.0 - x) * amt)
|
T::from(x + (1.0 - x) * amt)
|
||||||
})
|
})
|
||||||
@@ -2,11 +2,12 @@ use std::ops::{Index, IndexMut};
|
|||||||
|
|
||||||
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
|
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
|
||||||
|
|
||||||
struct LayerNode {
|
struct LayerNode<T> {
|
||||||
next: Ptr,
|
next: Ptr,
|
||||||
prev: Ptr,
|
prev: Ptr,
|
||||||
child: Option<Child>,
|
child: Option<Child>,
|
||||||
data: Layer,
|
depth: usize,
|
||||||
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -21,26 +22,22 @@ enum Ptr {
|
|||||||
|
|
||||||
/// TODO: currently this does not ever free layers
|
/// TODO: currently this does not ever free layers
|
||||||
/// is that realistically desired?
|
/// is that realistically desired?
|
||||||
pub struct Layers {
|
pub struct Layers<T> {
|
||||||
vec: Vec<LayerNode>,
|
vec: Vec<LayerNode<T>>,
|
||||||
/// index of last layer at top level (start at first = 0)
|
/// index of last layer at top level (start at first = 0)
|
||||||
last: usize,
|
last: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: this can be replaced with Primitives itself atm
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Layer {
|
|
||||||
pub primitives: Primitives,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct Child {
|
struct Child {
|
||||||
head: usize,
|
head: usize,
|
||||||
tail: usize,
|
tail: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layers {
|
pub type PrimitiveLayers = Layers<Primitives>;
|
||||||
pub fn new() -> Layers {
|
|
||||||
|
impl<T: Default> Layers<T> {
|
||||||
|
pub fn new() -> Layers<T> {
|
||||||
Self {
|
Self {
|
||||||
vec: vec![LayerNode::head()],
|
vec: vec![LayerNode::head()],
|
||||||
last: 0,
|
last: 0,
|
||||||
@@ -52,7 +49,7 @@ impl Layers {
|
|||||||
self.vec.push(LayerNode::head());
|
self.vec.push(LayerNode::head());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, node: LayerNode) -> usize {
|
fn push(&mut self, node: LayerNode<T>) -> usize {
|
||||||
let i = self.vec.len();
|
let i = self.vec.len();
|
||||||
self.vec.push(node);
|
self.vec.push(node);
|
||||||
i
|
i
|
||||||
@@ -63,9 +60,10 @@ impl Layers {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
let i_new = self.push(LayerNode::new(
|
let i_new = self.push(LayerNode::new(
|
||||||
Layer::default(),
|
T::default(),
|
||||||
self.vec[i].next,
|
self.vec[i].next,
|
||||||
Ptr::Next(i),
|
Ptr::Next(i),
|
||||||
|
self.vec[i].depth,
|
||||||
));
|
));
|
||||||
self.vec[i].next = Ptr::Next(i_new);
|
self.vec[i].next = Ptr::Next(i_new);
|
||||||
self.vec[i_new].prev = Ptr::Next(i);
|
self.vec[i_new].prev = Ptr::Next(i);
|
||||||
@@ -82,9 +80,10 @@ impl Layers {
|
|||||||
return c.head;
|
return c.head;
|
||||||
}
|
}
|
||||||
let i_child = self.push(LayerNode::new(
|
let i_child = self.push(LayerNode::new(
|
||||||
Layer::default(),
|
T::default(),
|
||||||
Ptr::Parent(i),
|
Ptr::Parent(i),
|
||||||
Ptr::Parent(i),
|
Ptr::Parent(i),
|
||||||
|
self.vec[i].depth + 1,
|
||||||
));
|
));
|
||||||
self.vec[i].child = Some(Child {
|
self.vec[i].child = Some(Child {
|
||||||
head: i_child,
|
head: i_child,
|
||||||
@@ -93,103 +92,115 @@ impl Layers {
|
|||||||
i_child
|
i_child
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_> {
|
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_, T> {
|
||||||
LayerIteratorMut::new(&mut self.vec, self.last)
|
LayerIteratorMut::new(&mut self.vec, self.last)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (usize, &Layer)> {
|
pub fn iter_orderless_mut(&mut self) -> impl Iterator<Item = (usize, &mut T)> {
|
||||||
|
self.vec.iter_mut().map(|n| &mut n.data).enumerate()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
|
||||||
self.indices().map(|i| (i, &self.vec[i].data))
|
self.indices().map(|i| (i, &self.vec[i].data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn indices(&self) -> LayerIndexIterator<'_> {
|
pub fn iter_depth(&self) -> impl Iterator<Item = ((usize, usize), &T)> {
|
||||||
|
self.indices()
|
||||||
|
.map(|i| ((i, self.vec[i].depth), &self.vec[i].data))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indices(&self) -> LayerIndexIterator<'_, T> {
|
||||||
LayerIndexIterator::new(&self.vec, self.last)
|
LayerIndexIterator::new(&self.vec, self.last)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<P: Primitive>(&mut self, layer: usize, info: PrimitiveInst<P>) -> PrimitiveHandle {
|
|
||||||
self[layer].primitives.write(layer, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
|
||||||
self[h.layer].primitives.free(h)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Layers {
|
impl PrimitiveLayers {
|
||||||
|
pub fn write<P: Primitive>(&mut self, layer: usize, info: PrimitiveInst<P>) -> PrimitiveHandle {
|
||||||
|
self[layer].write(layer, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||||
|
self[h.layer].free(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default> Default for Layers<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<usize> for Layers {
|
impl<T> Index<usize> for Layers<T> {
|
||||||
type Output = Layer;
|
type Output = T;
|
||||||
|
|
||||||
fn index(&self, index: usize) -> &Self::Output {
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
&self.vec[index].data
|
&self.vec[index].data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexMut<usize> for Layers {
|
impl<T> IndexMut<usize> for Layers<T> {
|
||||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
&mut self.vec[index].data
|
&mut self.vec[index].data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerNode {
|
impl<T: Default> LayerNode<T> {
|
||||||
pub fn new(data: Layer, next: Ptr, prev: Ptr) -> Self {
|
pub fn new(data: T, next: Ptr, prev: Ptr, depth: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
next,
|
next,
|
||||||
prev,
|
prev,
|
||||||
child: None,
|
child: None,
|
||||||
data,
|
data,
|
||||||
|
depth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn head() -> Self {
|
pub fn head() -> Self {
|
||||||
Self::new(Layer::default(), Ptr::None, Ptr::None)
|
Self::new(T::default(), Ptr::None, Ptr::None, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LayerIteratorMut<'a> {
|
pub struct LayerIteratorMut<'a, T> {
|
||||||
inner: LayerIndexIterator<'a>,
|
inner: LayerIndexIterator<'a, T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for LayerIteratorMut<'a> {
|
impl<'a, T> Iterator for LayerIteratorMut<'a, T> {
|
||||||
type Item = (usize, &'a mut Layer);
|
type Item = (usize, &'a mut T);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let i = self.inner.next()?;
|
let i = self.inner.next()?;
|
||||||
// SAFETY: requires index iterator to work properly
|
// SAFETY: requires index iterator to work properly
|
||||||
#[allow(mutable_transmutes)]
|
#[allow(mutable_transmutes)]
|
||||||
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) };
|
let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
|
||||||
Some((i, layer))
|
Some((i, layer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DoubleEndedIterator for LayerIteratorMut<'a> {
|
impl<'a, T> DoubleEndedIterator for LayerIteratorMut<'a, T> {
|
||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
let i = self.inner.next_back()?;
|
let i = self.inner.next_back()?;
|
||||||
// SAFETY: requires index iterator to work properly
|
// SAFETY: requires index iterator to work properly
|
||||||
#[allow(mutable_transmutes)]
|
#[allow(mutable_transmutes)]
|
||||||
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) };
|
let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
|
||||||
Some((i, layer))
|
Some((i, layer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LayerIteratorMut<'a> {
|
impl<'a, T> LayerIteratorMut<'a, T> {
|
||||||
fn new(vec: &'a mut Vec<LayerNode>, last: usize) -> Self {
|
fn new(vec: &'a mut Vec<LayerNode<T>>, last: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: LayerIndexIterator::new(vec, last),
|
inner: LayerIndexIterator::new(vec, last),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LayerIndexIterator<'a> {
|
pub struct LayerIndexIterator<'a, T> {
|
||||||
next: Option<usize>,
|
next: Option<usize>,
|
||||||
next_back: Option<usize>,
|
next_back: Option<usize>,
|
||||||
vec: &'a Vec<LayerNode>,
|
vec: &'a Vec<LayerNode<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for LayerIndexIterator<'a> {
|
impl<'a, T> Iterator for LayerIndexIterator<'a, T> {
|
||||||
type Item = usize;
|
type Item = usize;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
@@ -220,7 +231,7 @@ impl<'a> Iterator for LayerIndexIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
|
impl<'a, T> DoubleEndedIterator for LayerIndexIterator<'a, T> {
|
||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
let ret_i = self.next_back?;
|
let ret_i = self.next_back?;
|
||||||
let node = &self.vec[ret_i];
|
let node = &self.vec[ret_i];
|
||||||
@@ -242,8 +253,8 @@ impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LayerIndexIterator<'a> {
|
impl<'a, T> LayerIndexIterator<'a, T> {
|
||||||
fn new(vec: &'a Vec<LayerNode>, last: usize) -> Self {
|
fn new(vec: &'a Vec<LayerNode<T>>, last: usize) -> Self {
|
||||||
let mut last = last;
|
let mut last = last;
|
||||||
while let Some(c) = vec[last].child {
|
while let Some(c) = vec[last].child {
|
||||||
last = c.tail;
|
last = c.tail;
|
||||||
9
core/src/layout/primitive/mod.rs
Normal file
9
core/src/layout/primitive/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod color;
|
||||||
|
mod layer;
|
||||||
|
mod text;
|
||||||
|
mod texture;
|
||||||
|
|
||||||
|
pub use color::*;
|
||||||
|
pub use layer::*;
|
||||||
|
pub use text::*;
|
||||||
|
pub use texture::*;
|
||||||
@@ -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(),
|
||||||
@@ -35,7 +40,7 @@ pub struct TextAttrs {
|
|||||||
pub line_height: f32,
|
pub line_height: f32,
|
||||||
pub family: Family<'static>,
|
pub family: Family<'static>,
|
||||||
pub wrap: bool,
|
pub wrap: bool,
|
||||||
/// inner alignment of text region (within where its drawn)
|
/// inner alignment of text region (within where it's drawn)
|
||||||
pub align: RegionAlign,
|
pub align: RegionAlign,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +68,7 @@ impl Default for TextAttrs {
|
|||||||
Self {
|
Self {
|
||||||
color: UiColor::WHITE,
|
color: UiColor::WHITE,
|
||||||
font_size: size,
|
font_size: size,
|
||||||
line_height: size * 1.2,
|
line_height: size * LINE_HEIGHT_MULT,
|
||||||
family: Family::SansSerif,
|
family: Family::SansSerif,
|
||||||
wrap: false,
|
wrap: false,
|
||||||
align: Align::CENTER_LEFT,
|
align: Align::CENTER_LEFT,
|
||||||
@@ -71,7 +76,9 @@ impl Default for TextAttrs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextData {
|
pub const LINE_HEIGHT_MULT: f32 = 1.1;
|
||||||
|
|
||||||
|
impl TextDataInner {
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut TextBuffer,
|
buffer: &mut TextBuffer,
|
||||||
167
core/src/layout/ui.rs
Normal file
167
core/src/layout/ui.rs
Normal 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
17
core/src/layout/view.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
163
core/src/layout/widget/handle.rs
Normal file
163
core/src/layout/widget/handle.rs
Normal 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> {}
|
||||||
79
core/src/layout/widget/like.rs
Normal file
79
core/src/layout/widget/like.rs
Normal 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);
|
||||||
63
core/src/layout/widget/mod.rs
Normal file
63
core/src/layout/widget/mod.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
core/src/layout/widget/tag.rs
Normal file
5
core/src/layout/widget/tag.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub struct WidgetTag;
|
||||||
|
pub struct FnTag;
|
||||||
|
pub struct IdTag;
|
||||||
|
pub struct IdFnTag;
|
||||||
|
pub struct ArrTag;
|
||||||
50
core/src/layout/widget/widgets.rs
Normal file
50
core/src/layout/widget/widgets.rs
Normal 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
19
core/src/lib.rs
Normal 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;
|
||||||
@@ -44,7 +44,7 @@ impl MaskIdx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct Mask {
|
pub struct Mask {
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ pub use primitive::*;
|
|||||||
|
|
||||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||||
|
|
||||||
pub struct UiRenderer {
|
pub struct UiRenderNode {
|
||||||
uniform_group: BindGroup,
|
uniform_group: BindGroup,
|
||||||
primitive_layout: BindGroupLayout,
|
primitive_layout: BindGroupLayout,
|
||||||
rsc_layout: BindGroupLayout,
|
rsc_layout: BindGroupLayout,
|
||||||
@@ -43,7 +43,7 @@ struct RenderLayer {
|
|||||||
primitive_group: BindGroup,
|
primitive_group: BindGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiRenderer {
|
impl UiRenderNode {
|
||||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_pipeline(&self.pipeline);
|
||||||
pass.set_bind_group(0, &self.uniform_group, &[]);
|
pass.set_bind_group(0, &self.uniform_group, &[]);
|
||||||
@@ -61,13 +61,12 @@ impl UiRenderer {
|
|||||||
|
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
|
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
|
||||||
self.active.clear();
|
self.active.clear();
|
||||||
for (i, ulayer) in ui.data.layers.iter_mut() {
|
for (i, primitives) in ui.data.layers.iter_mut() {
|
||||||
self.active.push(i);
|
self.active.push(i);
|
||||||
let primitives = &mut ulayer.primitives;
|
|
||||||
for change in primitives.apply_free() {
|
for change in primitives.apply_free() {
|
||||||
if let Some(inst) = ui.data.active.get_mut(&change.id) {
|
if let Some(inst) = ui.data.active.get_mut(&change.id) {
|
||||||
for h in &mut inst.primitives {
|
for h in &mut inst.primitives {
|
||||||
if h.inst_idx == change.old {
|
if h.layer == i && h.inst_idx == change.old {
|
||||||
h.inst_idx = change.new;
|
h.inst_idx = change.new;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -97,15 +96,19 @@ impl UiRenderer {
|
|||||||
device,
|
device,
|
||||||
&self.primitive_layout,
|
&self.primitive_layout,
|
||||||
rlayer.primitives.buffers(),
|
rlayer.primitives.buffers(),
|
||||||
)
|
);
|
||||||
|
primitives.updated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.textures.update(&mut ui.data.textures) {
|
let mut changed = false;
|
||||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks)
|
changed |= self.textures.update(&mut ui.data.textures);
|
||||||
}
|
|
||||||
if ui.data.masks.changed {
|
if ui.data.masks.changed {
|
||||||
ui.data.masks.changed = false;
|
ui.data.masks.changed = false;
|
||||||
self.masks.update(device, queue, &ui.data.masks[..]);
|
self.masks.update(device, queue, &ui.data.masks[..]);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ macro_rules! primitives {
|
|||||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PrimitiveData {
|
pub struct PrimitiveData {
|
||||||
$($name: PrimitiveVec<$ty>,)*
|
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveBuffers {
|
pub struct PrimitiveBuffers {
|
||||||
@@ -116,6 +116,7 @@ impl Primitives {
|
|||||||
mask_idx,
|
mask_idx,
|
||||||
}: PrimitiveInst<P>,
|
}: PrimitiveInst<P>,
|
||||||
) -> PrimitiveHandle {
|
) -> PrimitiveHandle {
|
||||||
|
self.updated = true;
|
||||||
let vec = P::vec(&mut self.data);
|
let vec = P::vec(&mut self.data);
|
||||||
let i = vec.add(primitive);
|
let i = vec.add(primitive);
|
||||||
let inst = PrimitiveInstance {
|
let inst = PrimitiveInstance {
|
||||||
@@ -153,6 +154,7 @@ impl Primitives {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||||
|
self.updated = true;
|
||||||
self.data.free(h.binding, h.data_idx);
|
self.data.free(h.binding, h.data_idx);
|
||||||
self.free.push(h.inst_idx);
|
self.free.push(h.inst_idx);
|
||||||
self.instances[h.inst_idx].mask_idx
|
self.instances[h.inst_idx].mask_idx
|
||||||
60
core/src/util/handle.rs
Normal file
60
core/src/util/handle.rs
Normal 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> {}
|
||||||
@@ -26,6 +26,9 @@ impl<I: IdNum> IdTracker<I> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<I: IdNum> Id<I> {
|
impl<I: IdNum> Id<I> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// for debug purposes; should this be exposed?
|
||||||
|
/// generally you want to use labels with widgets
|
||||||
pub(crate) fn raw(id: I) -> Self {
|
pub(crate) fn raw(id: I) -> Self {
|
||||||
Self(id)
|
Self(id)
|
||||||
}
|
}
|
||||||
@@ -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>;
|
||||||
@@ -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,10 @@ impl RefCounter {
|
|||||||
let refs = self.0.fetch_sub(1, Ordering::Release);
|
let refs = self.0.fetch_sub(1, Ordering::Release);
|
||||||
refs == 0
|
refs == 0
|
||||||
}
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn quiet_clone(&self) -> Self {
|
||||||
|
Self(self.0.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for RefCounter {
|
impl Clone for RefCounter {
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
use crate::{
|
use crate::util::{DivOr, impl_op};
|
||||||
layout::UiNum,
|
use std::{hash::Hash, ops::*};
|
||||||
util::{DivOr, impl_op},
|
|
||||||
};
|
|
||||||
use std::{hash::Hash, marker::Destruct, ops::*};
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
@@ -20,10 +17,6 @@ impl Hash for Vec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
|
|
||||||
Vec2::new(x.to_f32(), y.to_f32())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vec2 {
|
impl Vec2 {
|
||||||
pub const ZERO: Self = Self::new(0.0, 0.0);
|
pub const ZERO: Self = Self::new(0.0, 0.0);
|
||||||
pub const ONE: Self = Self::new(1.0, 1.0);
|
pub const ONE: Self = Self::new(1.0, 1.0);
|
||||||
@@ -56,14 +49,15 @@ impl Vec2 {
|
|||||||
pub const fn tuple(&self) -> (f32, f32) {
|
pub const fn tuple(&self) -> (f32, f32) {
|
||||||
(self.x, self.y)
|
(self.x, self.y)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: const UiNum + Copy> const From<T> for Vec2 {
|
pub const fn with_x(mut self, x: f32) -> Self {
|
||||||
fn from(v: T) -> Self {
|
self.x = x;
|
||||||
Self {
|
self
|
||||||
x: v.to_f32(),
|
|
||||||
y: v.to_f32(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn with_y(mut self, y: f32) -> Self {
|
||||||
|
self.y = y;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,18 +86,6 @@ impl Neg for Vec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
|
|
||||||
where
|
|
||||||
(T, U): const Destruct,
|
|
||||||
{
|
|
||||||
fn from((x, y): (T, U)) -> Self {
|
|
||||||
Self {
|
|
||||||
x: x.to_f32(),
|
|
||||||
y: y.to_f32(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Vec2 {
|
impl std::fmt::Debug for Vec2 {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "({}, {})", self.x, self.y)
|
write!(f, "({}, {})", self.x, self.y)
|
||||||
14
examples/minimal.rs
Normal file
14
examples/minimal.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use iris::{prelude::*};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
DefaultApp::<State>::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State;
|
||||||
|
|
||||||
|
impl DefaultAppState for State {
|
||||||
|
fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
|
||||||
|
rect(Color::RED).set_root(ui);
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
11
macro/Cargo.toml
Normal file
11
macro/Cargo.toml
Normal 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
73
macro/src/lib.rs
Normal 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)*
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
# iris
|
# iris
|
||||||
|
|
||||||
my take on a rust ui library (also my first ui library)
|
my fisrt attempt at a rust ui library
|
||||||
|
|
||||||
it's called iris because it's the structure around what you actually want to display and colorful
|
it's called iris because it's the structure around what you actually want to display and colorful
|
||||||
|
|
||||||
there's a `main.rs` that runs a testing window, so can just `cargo run` to see it working
|
there's a `main.rs` that runs a testing window, so can just `cargo run` to see it working
|
||||||
|
|
||||||
goals, in general order:
|
goals, in general order:
|
||||||
1. does what I want it to (video, text, animations)
|
1. does what I want it to (text, images, video, animations)
|
||||||
2. very easy to use ignoring ergonomic ref counting
|
2. very easy to use ignoring ergonomic ref counting
|
||||||
3. reasonably fast / efficient (a lot faster than electron, save battery life)
|
3. reasonably fast / efficient (a lot faster than electron, save battery life)
|
||||||
|
|
||||||
@@ -17,10 +17,10 @@ general ideas trynna use rn / experiment with:
|
|||||||
- retained mode
|
- retained mode
|
||||||
- specifically designed around wgpu
|
- specifically designed around wgpu
|
||||||
- postfix functions for most things to prevent unreadable indentation (going very well)
|
- postfix functions for most things to prevent unreadable indentation (going very well)
|
||||||
- no macros in user code / actual LSP typechecking (variadic generics if you can hear me please save us)
|
- almost no macros in user code & actual LSP typechecking (variadic generics if you can hear me please save us)
|
||||||
- relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout)
|
- relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout)
|
||||||
- single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this)
|
- single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this)
|
||||||
- widgets store outside of the actual rendering so they can be moved around and swapped easily (unsure about this but seems to work good for now)
|
- widgets store outside of the actual rendering so they can be moved around and swapped easily (unsure about this but seems to work good for now)
|
||||||
|
|
||||||
under heavy initial development so not gonna try to explain status, check TODO for that maybe
|
under heavy initial development so not gonna try to explain status, maybe check TODO for that;
|
||||||
sizable chance it gets a rewrite once I know everything I need and what seems to work best
|
sizable chance it gets a rewrite once I know everything I need and what seems to work best
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
application::ApplicationHandler,
|
|
||||||
event::WindowEvent,
|
|
||||||
event_loop::{ActiveEventLoop, EventLoop},
|
|
||||||
window::{Window, WindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::Client;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct App {
|
|
||||||
client: Option<Client>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn run() {
|
|
||||||
let event_loop = EventLoop::new().unwrap();
|
|
||||||
event_loop.run_app(&mut App::default()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler for App {
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
if self.client.is_none() {
|
|
||||||
let window = event_loop
|
|
||||||
.create_window(Window::default_attributes())
|
|
||||||
.unwrap();
|
|
||||||
let client = Client::new(window.into());
|
|
||||||
self.client = Some(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
|
||||||
let client = self.client.as_mut().unwrap();
|
|
||||||
client.event(event, event_loop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +1,20 @@
|
|||||||
use app::App;
|
|
||||||
use arboard::Clipboard;
|
|
||||||
use cosmic_text::Family;
|
use cosmic_text::Family;
|
||||||
use input::Input;
|
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use len_fns::*;
|
use len_fns::*;
|
||||||
use render::Renderer;
|
use winit::event::WindowEvent;
|
||||||
use std::sync::Arc;
|
|
||||||
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
|
|
||||||
|
|
||||||
mod app;
|
|
||||||
mod input;
|
|
||||||
mod render;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::run();
|
DefaultApp::<Client>::run();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
renderer: Renderer,
|
info: WidgetRef<Text>,
|
||||||
input: Input,
|
|
||||||
ui: Ui,
|
|
||||||
info: WidgetId<Text>,
|
|
||||||
focus: Option<WidgetId<TextEdit>>,
|
|
||||||
clipboard: Clipboard,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
event_ctx!(Client);
|
||||||
struct Submit;
|
|
||||||
|
|
||||||
impl DefaultEvent for Submit {
|
impl DefaultAppState for Client {
|
||||||
type Data = ();
|
fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn new(window: Arc<Window>) -> Self {
|
|
||||||
let renderer = Renderer::new(window);
|
|
||||||
|
|
||||||
let mut ui = Ui::new();
|
|
||||||
let rrect = rect(Color::WHITE).radius(20);
|
let rrect = rect(Color::WHITE).radius(20);
|
||||||
let pad_test = (
|
let pad_test = (
|
||||||
rrect.color(Color::BLUE),
|
rrect.color(Color::BLUE),
|
||||||
@@ -59,7 +37,7 @@ impl Client {
|
|||||||
.width(rest(3)),
|
.width(rest(3)),
|
||||||
)
|
)
|
||||||
.span(Dir::RIGHT)
|
.span(Dir::RIGHT)
|
||||||
.add_static(&mut ui);
|
.add(ui);
|
||||||
|
|
||||||
let span_test = (
|
let span_test = (
|
||||||
rrect.color(Color::GREEN).width(100),
|
rrect.color(Color::GREEN).width(100),
|
||||||
@@ -70,35 +48,34 @@ impl Client {
|
|||||||
rrect.color(Color::RED).width(100),
|
rrect.color(Color::RED).width(100),
|
||||||
)
|
)
|
||||||
.span(Dir::LEFT)
|
.span(Dir::LEFT)
|
||||||
.add_static(&mut ui);
|
.add(ui);
|
||||||
|
|
||||||
let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui);
|
let span_add = Span::empty(Dir::RIGHT).add(ui);
|
||||||
|
let span_add_ = span_add.clone();
|
||||||
|
|
||||||
let add_button = rect(Color::LIME)
|
let add_button = rect(Color::LIME)
|
||||||
.radius(30)
|
.radius(30)
|
||||||
.on(CursorSense::click(), move |ctx: &mut Client, _| {
|
.on(CursorSense::click(), move |ctx| {
|
||||||
let child = ctx
|
let child = image(include_bytes!("assets/sungals.png"))
|
||||||
.ui
|
.center()
|
||||||
.add(image(include_bytes!("assets/sungals.png")).center())
|
.add(ctx.ui);
|
||||||
.any();
|
span_add_.get_mut().children.push(child);
|
||||||
ctx.ui[span_add].children.push(child);
|
|
||||||
})
|
})
|
||||||
.sized((150, 150))
|
.sized((150, 150))
|
||||||
.align(Align::BOT_RIGHT);
|
.align(Align::BOT_RIGHT);
|
||||||
|
|
||||||
|
let 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: &mut Client, _| {
|
.on(CursorSense::click(), move |_| {
|
||||||
ctx.ui[span_add].children.pop();
|
span_add_.get_mut().children.pop();
|
||||||
})
|
})
|
||||||
.sized((150, 150))
|
.sized((150, 150))
|
||||||
.align(Align::BOT_LEFT);
|
.align(Align::BOT_LEFT);
|
||||||
|
|
||||||
let span_add_test = (span_add, add_button, del_button)
|
let span_add_test = (span_add, add_button, del_button).stack().add(ui);
|
||||||
.stack()
|
|
||||||
.add_static(&mut ui);
|
|
||||||
|
|
||||||
let btext = |content| text(content).size(30);
|
let btext = |content| wtext(content).size(30);
|
||||||
|
|
||||||
let text_test = (
|
let text_test = (
|
||||||
btext("this is a").align(Align::LEFT),
|
btext("this is a").align(Align::LEFT),
|
||||||
@@ -116,38 +93,30 @@ impl Client {
|
|||||||
)
|
)
|
||||||
.span(Dir::RIGHT)
|
.span(Dir::RIGHT)
|
||||||
.center(),
|
.center(),
|
||||||
text("pretty cool right?").size(50),
|
wtext("pretty cool right?").size(50),
|
||||||
)
|
)
|
||||||
.span(Dir::DOWN)
|
.span(Dir::DOWN)
|
||||||
.add_static(&mut ui);
|
.add(ui);
|
||||||
|
|
||||||
let texts = Span::empty(Dir::DOWN).gap(10).add_static(&mut ui);
|
let texts = Span::empty(Dir::DOWN).gap(10).add(ui);
|
||||||
let msg_area = texts.scroll().masked().background(rect(Color::SKY));
|
let msg_area = texts.clone().scroll().masked().background(rect(Color::SKY));
|
||||||
let add_text = text("add")
|
let add_text = wtext("add")
|
||||||
.editable()
|
.editable(false)
|
||||||
.text_align(Align::LEFT)
|
.text_align(Align::LEFT)
|
||||||
.size(30)
|
.size(30)
|
||||||
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
.attr::<Selectable>(())
|
||||||
client.ui.text(id).select(ctx.cursor, ctx.size);
|
.on(Submit, move |ctx| {
|
||||||
client.focus = Some(id.clone());
|
let content = ctx.widget.get_mut().take();
|
||||||
})
|
let text = wtext(content)
|
||||||
.id_on(Submit, move |id, client: &mut Client, _| {
|
.editable(false)
|
||||||
let content = client.ui.text(id).take();
|
|
||||||
let text = text(content)
|
|
||||||
.editable()
|
|
||||||
.size(30)
|
.size(30)
|
||||||
.text_align(Align::LEFT)
|
.text_align(Align::LEFT)
|
||||||
.wrap(true)
|
.wrap(true)
|
||||||
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
.attr::<Selectable>(());
|
||||||
client.ui.text(id).select(ctx.cursor, ctx.size);
|
let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui);
|
||||||
client.focus = Some(id.clone());
|
texts.get_mut().children.push(msg_box);
|
||||||
});
|
|
||||||
let msg_box = text
|
|
||||||
.background(rect(Color::WHITE.darker(0.5)))
|
|
||||||
.add(&mut client.ui);
|
|
||||||
client.ui[texts].children.push(msg_box.any());
|
|
||||||
})
|
})
|
||||||
.add(&mut ui);
|
.add(ui);
|
||||||
let text_edit_scroll = (
|
let text_edit_scroll = (
|
||||||
msg_area.height(rest(1)),
|
msg_area.height(rest(1)),
|
||||||
(
|
(
|
||||||
@@ -155,8 +124,8 @@ impl Client {
|
|||||||
(
|
(
|
||||||
add_text.clone().width(rest(1)),
|
add_text.clone().width(rest(1)),
|
||||||
Rect::new(Color::GREEN)
|
Rect::new(Color::GREEN)
|
||||||
.on(CursorSense::click(), move |client: &mut Client, _| {
|
.on(CursorSense::click(), move |ctx| {
|
||||||
client.run_event(&add_text, Submit, ());
|
ctx.ui.run_event(ctx.state, &add_text, Submit, ());
|
||||||
})
|
})
|
||||||
.sized((40, 40)),
|
.sized((40, 40)),
|
||||||
)
|
)
|
||||||
@@ -165,128 +134,65 @@ impl Client {
|
|||||||
)
|
)
|
||||||
.stack()
|
.stack()
|
||||||
.size(StackSize::Child(1))
|
.size(StackSize::Child(1))
|
||||||
.offset_layer(1)
|
.layer_offset(1)
|
||||||
.align(Align::BOT),
|
.align(Align::BOT),
|
||||||
)
|
)
|
||||||
.span(Dir::DOWN)
|
.span(Dir::DOWN)
|
||||||
.add_static(&mut ui);
|
.add(ui);
|
||||||
|
|
||||||
let main = pad_test.pad(10).add_static(&mut ui);
|
let main = pad_test.clone().pad(10).add(ui);
|
||||||
|
|
||||||
let switch_button = |color, to, label| {
|
let switch_button = |color, to: WidgetRef, label| {
|
||||||
|
let main_ = main.clone();
|
||||||
let rect = rect(color)
|
let rect = rect(color)
|
||||||
.id_on(CursorSense::click(), move |id, ui: &mut Ui, _| {
|
.on(CursorSense::click(), move |ctx| {
|
||||||
ui[main].inner.set_static(to);
|
main_.get_mut().inner = to.clone();
|
||||||
ui[id].color = color.darker(0.3);
|
ctx.widget.get_mut().color = color.darker(0.3);
|
||||||
})
|
})
|
||||||
.edit_on(
|
.on(
|
||||||
CursorSense::HoverStart | CursorSense::unclick(),
|
CursorSense::HoverStart | CursorSense::unclick(),
|
||||||
move |r, _| {
|
move |ctx| {
|
||||||
r.color = color.brighter(0.2);
|
ctx.widget.get_mut().color = color.brighter(0.2);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.edit_on(CursorSense::HoverEnd, move |r, _| {
|
.on(CursorSense::HoverEnd, move |ctx| {
|
||||||
r.color = color;
|
ctx.widget.get_mut().color = color;
|
||||||
});
|
});
|
||||||
(rect, text(label).size(30).text_align(Align::CENTER)).stack()
|
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
|
||||||
};
|
};
|
||||||
|
|
||||||
let tabs = (
|
let tabs = (
|
||||||
switch_button(Color::RED, pad_test.any(), "pad"),
|
switch_button(Color::RED, 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",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.span(Dir::RIGHT);
|
.span(Dir::RIGHT);
|
||||||
|
|
||||||
let info = text("").add(&mut ui);
|
let info = wtext("").add(ui);
|
||||||
let info_sect = info.clone().pad(10).align(Align::RIGHT);
|
let info_sect = info.clone().pad(10).align(Align::RIGHT);
|
||||||
|
|
||||||
((tabs.height(40), main).span(Dir::DOWN), info_sect)
|
((tabs.height(40), main).span(Dir::DOWN), info_sect)
|
||||||
.stack()
|
.stack()
|
||||||
.set_root(&mut ui);
|
.set_root(ui);
|
||||||
|
|
||||||
Self {
|
Self { info }
|
||||||
renderer,
|
|
||||||
input: Input::default(),
|
|
||||||
ui,
|
|
||||||
info,
|
|
||||||
focus: None,
|
|
||||||
clipboard: Clipboard::new().unwrap(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) {
|
||||||
let input_changed = self.input.event(&event);
|
|
||||||
let cursor_state = self.cursor_state().clone();
|
|
||||||
if let Some(focus) = &self.focus
|
|
||||||
&& cursor_state.buttons.left.is_start()
|
|
||||||
{
|
|
||||||
self.ui.text(focus).deselect();
|
|
||||||
self.focus = None;
|
|
||||||
}
|
|
||||||
if input_changed {
|
|
||||||
let window_size = self.window_size();
|
|
||||||
self.run_sensors(&cursor_state, window_size);
|
|
||||||
self.ui.run_sensors(&cursor_state, window_size);
|
|
||||||
}
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => event_loop.exit(),
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
self.ui.update();
|
|
||||||
self.renderer.update(&mut self.ui);
|
|
||||||
self.renderer.draw()
|
|
||||||
}
|
|
||||||
WindowEvent::Resized(size) => {
|
|
||||||
self.ui.resize((size.width, size.height));
|
|
||||||
self.renderer.resize(&size)
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
if let Some(sel) = &self.focus
|
|
||||||
&& event.state.is_pressed()
|
|
||||||
{
|
|
||||||
let mut text = self.ui.text(sel);
|
|
||||||
match text.apply_event(&event, &self.input.modifiers) {
|
|
||||||
TextInputResult::Unfocus => {
|
|
||||||
self.focus = None;
|
|
||||||
}
|
|
||||||
TextInputResult::Submit => {
|
|
||||||
self.run_event(&sel.clone(), Submit, ());
|
|
||||||
}
|
|
||||||
TextInputResult::Paste => {
|
|
||||||
if let Ok(t) = self.clipboard.get_text() {
|
|
||||||
text.insert(&t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TextInputResult::Unused | TextInputResult::Used => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
let new = format!(
|
let new = format!(
|
||||||
"widgets: {}\nactive:{}\nviews: {}",
|
"widgets: {}\nactive:{}\nviews: {}",
|
||||||
self.ui.num_widgets(),
|
ui.num_widgets(),
|
||||||
self.ui.active_widgets(),
|
ui.active_widgets(),
|
||||||
self.renderer.ui.view_count()
|
state.renderer.ui.view_count()
|
||||||
);
|
);
|
||||||
if new != *self.ui[&self.info].content {
|
if new != *self.info.get().content {
|
||||||
*self.ui[&self.info].content = new;
|
*self.info.get_mut().content = new;
|
||||||
}
|
}
|
||||||
if self.ui.needs_redraw() {
|
|
||||||
self.renderer.window().request_redraw();
|
|
||||||
}
|
|
||||||
self.input.end_frame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiCtx for Client {
|
|
||||||
fn ui(&mut self) -> &mut Ui {
|
|
||||||
&mut self.ui
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, Motion};
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
|
||||||
use winit::{
|
|
||||||
event::KeyEvent,
|
|
||||||
keyboard::{Key, NamedKey},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct TextEdit {
|
|
||||||
pub(super) view: TextView,
|
|
||||||
pub(super) cursor: Option<Cursor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextEdit {
|
|
||||||
pub fn region(&self) -> UiRegion {
|
|
||||||
self.tex()
|
|
||||||
.map(|t| t.size)
|
|
||||||
.unwrap_or(Vec2::ZERO)
|
|
||||||
.align(self.align)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn content(&self) -> String {
|
|
||||||
self.buf
|
|
||||||
.lines
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.text())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for TextEdit {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
let region = self.view.draw(painter);
|
|
||||||
|
|
||||||
if let Some(cursor) = &self.cursor
|
|
||||||
&& let Some(offset) = cursor_pos(cursor, &self.buf)
|
|
||||||
{
|
|
||||||
let size = vec2(1, self.attrs.line_height);
|
|
||||||
painter.primitive_within(
|
|
||||||
RectPrimitive::color(Color::WHITE),
|
|
||||||
size.align(Align::TOP_LEFT).offset(offset).within(®ion),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
self.view.desired_width(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
self.view.desired_height(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// copied & modified from fn found in Editor in cosmic_text
|
|
||||||
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> {
|
|
||||||
let mut prev = None;
|
|
||||||
for run in buf
|
|
||||||
.layout_runs()
|
|
||||||
.skip_while(|r| r.line_i < cursor.line)
|
|
||||||
.take_while(|r| r.line_i == cursor.line)
|
|
||||||
{
|
|
||||||
prev = Some((run.line_w, run.line_top));
|
|
||||||
for glyph in run.glyphs.iter() {
|
|
||||||
if cursor.index == glyph.start {
|
|
||||||
return Some((glyph.x, run.line_top));
|
|
||||||
} else if cursor.index > glyph.start && cursor.index < glyph.end {
|
|
||||||
// Guess x offset based on characters
|
|
||||||
let mut before = 0;
|
|
||||||
let mut total = 0;
|
|
||||||
|
|
||||||
let cluster = &run.text[glyph.start..glyph.end];
|
|
||||||
for (i, _) in cluster.grapheme_indices(true) {
|
|
||||||
if glyph.start + i < cursor.index {
|
|
||||||
before += 1;
|
|
||||||
}
|
|
||||||
total += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = glyph.w * (before as f32) / (total as f32);
|
|
||||||
return Some((glyph.x + offset, run.line_top));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prev
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextEditCtx<'a> {
|
|
||||||
pub text: &'a mut TextEdit,
|
|
||||||
pub font_system: &'a mut FontSystem,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TextEditCtx<'a> {
|
|
||||||
pub fn take(&mut self) -> String {
|
|
||||||
let text = self
|
|
||||||
.text
|
|
||||||
.buf
|
|
||||||
.lines
|
|
||||||
.drain(..)
|
|
||||||
.map(|l| l.into_text())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
self.text
|
|
||||||
.buf
|
|
||||||
.set_text(self.font_system, "", &Attrs::new(), SHAPING, None);
|
|
||||||
if let Some(cursor) = &mut self.text.cursor {
|
|
||||||
cursor.line = 0;
|
|
||||||
cursor.index = 0;
|
|
||||||
cursor.affinity = Affinity::default();
|
|
||||||
}
|
|
||||||
text
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn motion(&mut self, motion: Motion) {
|
|
||||||
if let Some(cursor) = self.text.cursor
|
|
||||||
&& let Some((cursor, _)) =
|
|
||||||
self.text
|
|
||||||
.buf
|
|
||||||
.cursor_motion(self.font_system, cursor, None, motion)
|
|
||||||
{
|
|
||||||
self.text.cursor = Some(cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace(&mut self, len: usize, text: &str) {
|
|
||||||
for _ in 0..len {
|
|
||||||
self.delete();
|
|
||||||
}
|
|
||||||
self.insert_inner(text, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, text: &str) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_inner(&mut self, text: &str, mov: bool) {
|
|
||||||
if let Some(cursor) = &mut self.text.cursor {
|
|
||||||
let line = &mut self.text.view.buf.lines[cursor.line];
|
|
||||||
let mut line_text = line.text().to_string();
|
|
||||||
line_text.insert_str(cursor.index, text);
|
|
||||||
line.set_text(line_text, line.ending(), line.attrs_list().clone());
|
|
||||||
if mov {
|
|
||||||
for _ in 0..text.chars().count() {
|
|
||||||
self.motion(Motion::Right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn newline(&mut self) {
|
|
||||||
if let Some(cursor) = &mut self.text.cursor {
|
|
||||||
let lines = &mut self.text.view.buf.lines;
|
|
||||||
let line = &mut lines[cursor.line];
|
|
||||||
let new = line.split_off(cursor.index);
|
|
||||||
cursor.line += 1;
|
|
||||||
lines.insert(cursor.line, new);
|
|
||||||
cursor.index = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn backspace(&mut self) {
|
|
||||||
if let Some(cursor) = &mut self.text.cursor
|
|
||||||
&& (cursor.index != 0 || cursor.line != 0)
|
|
||||||
{
|
|
||||||
self.motion(Motion::Left);
|
|
||||||
self.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete(&mut self) {
|
|
||||||
if let Some(cursor) = &mut self.text.cursor {
|
|
||||||
let lines = &mut self.text.view.buf.lines;
|
|
||||||
let line = &mut lines[cursor.line];
|
|
||||||
if cursor.index == line.text().len() {
|
|
||||||
if cursor.line == lines.len() - 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let add = lines.remove(cursor.line + 1).into_text();
|
|
||||||
let line = &mut lines[cursor.line];
|
|
||||||
let mut cur = line.text().to_string();
|
|
||||||
cur.push_str(&add);
|
|
||||||
line.set_text(cur, line.ending(), line.attrs_list().clone());
|
|
||||||
} else {
|
|
||||||
let mut text = line.text().to_string();
|
|
||||||
text.remove(cursor.index);
|
|
||||||
line.set_text(text, line.ending(), line.attrs_list().clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(&mut self, pos: Vec2, size: Vec2) {
|
|
||||||
let pos = pos - self.text.region().top_left().to_abs(size);
|
|
||||||
self.text.cursor = self.text.buf.hit(pos.x, pos.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deselect(&mut self) {
|
|
||||||
self.text.cursor = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
|
|
||||||
match &event.logical_key {
|
|
||||||
Key::Named(named) => match named {
|
|
||||||
NamedKey::Backspace => self.backspace(),
|
|
||||||
NamedKey::Delete => self.delete(),
|
|
||||||
NamedKey::Space => self.insert(" "),
|
|
||||||
NamedKey::Enter => {
|
|
||||||
if modifiers.shift {
|
|
||||||
self.newline();
|
|
||||||
} else {
|
|
||||||
return TextInputResult::Submit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NamedKey::ArrowRight => self.motion(Motion::Right),
|
|
||||||
NamedKey::ArrowLeft => self.motion(Motion::Left),
|
|
||||||
NamedKey::ArrowUp => self.motion(Motion::Up),
|
|
||||||
NamedKey::ArrowDown => self.motion(Motion::Down),
|
|
||||||
NamedKey::Escape => {
|
|
||||||
self.deselect();
|
|
||||||
return TextInputResult::Unfocus;
|
|
||||||
}
|
|
||||||
_ => return TextInputResult::Unused,
|
|
||||||
},
|
|
||||||
Key::Character(text) => {
|
|
||||||
if modifiers.control && text == "v" {
|
|
||||||
return TextInputResult::Paste;
|
|
||||||
} else {
|
|
||||||
self.insert(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return TextInputResult::Unused,
|
|
||||||
}
|
|
||||||
TextInputResult::Used
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Modifiers {
|
|
||||||
pub shift: bool,
|
|
||||||
pub control: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Modifiers {
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.shift = false;
|
|
||||||
self.control = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum TextInputResult {
|
|
||||||
Used,
|
|
||||||
Unused,
|
|
||||||
Unfocus,
|
|
||||||
Submit,
|
|
||||||
Paste,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextInputResult {
|
|
||||||
pub fn unfocus(&self) -> bool {
|
|
||||||
matches!(self, TextInputResult::Unfocus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for TextEdit {
|
|
||||||
type Target = TextView;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for TextEdit {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53
src/default/app.rs
Normal file
53
src/default/app.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use winit::{
|
||||||
|
application::ApplicationHandler,
|
||||||
|
event::WindowEvent,
|
||||||
|
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
|
||||||
|
window::WindowId,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait AppState {
|
||||||
|
type Event: 'static;
|
||||||
|
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self;
|
||||||
|
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
|
||||||
|
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
|
||||||
|
fn exit(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App<State: AppState> {
|
||||||
|
state: Option<State>,
|
||||||
|
proxy: EventLoopProxy<State::Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State: AppState> App<State> {
|
||||||
|
pub fn run() {
|
||||||
|
let event_loop = EventLoop::with_user_event().build().unwrap();
|
||||||
|
let proxy = event_loop.create_proxy();
|
||||||
|
event_loop
|
||||||
|
.run_app(&mut App::<State> { state: None, proxy })
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State: AppState> ApplicationHandler<State::Event> for App<State> {
|
||||||
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
if self.state.is_none() {
|
||||||
|
let state = State::new(event_loop, self.proxy.clone());
|
||||||
|
self.state = Some(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||||
|
let state = self.state.as_mut().unwrap();
|
||||||
|
state.window_event(event, event_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: State::Event) {
|
||||||
|
let state = self.state.as_mut().unwrap();
|
||||||
|
state.event(event, event_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exiting(&mut self, _: &ActiveEventLoop) {
|
||||||
|
let state = self.state.as_mut().unwrap();
|
||||||
|
state.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/default/attr.rs
Normal file
55
src/default/attr.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use crate::{prelude::*, default::UiState};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
|
||||||
|
pub struct Selector;
|
||||||
|
|
||||||
|
impl<W: 'static> WidgetAttr<W> for Selector {
|
||||||
|
type Input = WidgetRef<TextEdit>;
|
||||||
|
|
||||||
|
fn run(ui: &mut Ui, container: &WidgetRef<W>, id: Self::Input) {
|
||||||
|
let container = container.clone();
|
||||||
|
ui.register_event(
|
||||||
|
&container.clone(),
|
||||||
|
CursorSense::click_or_drag(),
|
||||||
|
move |mut ctx| {
|
||||||
|
let ui = ctx.ui;
|
||||||
|
let region = ui.window_region(&id).unwrap();
|
||||||
|
let id_pos = region.top_left;
|
||||||
|
let container_pos = ui.window_region(&container).unwrap().top_left;
|
||||||
|
ctx.data.cursor += container_pos - id_pos;
|
||||||
|
ctx.data.size = region.size();
|
||||||
|
select(ui, id.clone(), ctx.state, ctx.data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Selectable;
|
||||||
|
|
||||||
|
impl WidgetAttr<TextEdit> for Selectable {
|
||||||
|
type Input = ();
|
||||||
|
|
||||||
|
fn run(ui: &mut Ui, id: &WidgetRef<TextEdit>, _: Self::Input) {
|
||||||
|
let id = id.clone();
|
||||||
|
ui.register_event(&id.clone(), CursorSense::click_or_drag(), move |ctx| {
|
||||||
|
select(ctx.ui, id.clone(), ctx.state, ctx.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select(ui: &mut Ui, id: WidgetRef<TextEdit>, state: &mut UiState, data: CursorData) {
|
||||||
|
let now = Instant::now();
|
||||||
|
let recent = (now - state.last_click) < Duration::from_millis(300);
|
||||||
|
state.last_click = now;
|
||||||
|
id.get_mut()
|
||||||
|
.select(data.cursor, data.size, data.sense.is_dragging(), recent);
|
||||||
|
if let Some(region) = ui.window_region(&id) {
|
||||||
|
state.window.set_ime_allowed(true);
|
||||||
|
state.window.set_ime_cursor_area(
|
||||||
|
LogicalPosition::<f32>::from(region.top_left.tuple()),
|
||||||
|
LogicalSize::<f32>::from(region.size().tuple()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
state.focus = Some(id);
|
||||||
|
}
|
||||||
15
src/default/event.rs
Normal file
15
src/default/event.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use crate::layout::DefaultEvent;
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||||
|
pub struct Submit;
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||||
|
pub struct Edited;
|
||||||
|
|
||||||
|
impl DefaultEvent for Submit {
|
||||||
|
type Data = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultEvent for Edited {
|
||||||
|
type Data = ();
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::Client;
|
use crate::{
|
||||||
use iris::{
|
widget::{CursorState, Modifiers},
|
||||||
core::{CursorState, Modifiers},
|
|
||||||
layout::Vec2,
|
layout::Vec2,
|
||||||
|
default::UiState,
|
||||||
};
|
};
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
||||||
@@ -32,10 +32,14 @@ impl Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, .. } => {
|
WindowEvent::MouseWheel { delta, .. } => {
|
||||||
let delta = match *delta {
|
let mut delta = match *delta {
|
||||||
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
|
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
|
||||||
MouseScrollDelta::PixelDelta(pos) => Vec2::new(pos.x as f32, pos.y as f32),
|
MouseScrollDelta::PixelDelta(pos) => Vec2::new(pos.x as f32, pos.y as f32),
|
||||||
};
|
};
|
||||||
|
if delta.x == 0.0 && self.modifiers.shift {
|
||||||
|
delta.x = delta.y;
|
||||||
|
delta.y = 0.0;
|
||||||
|
}
|
||||||
self.cursor.scroll_delta = delta;
|
self.cursor.scroll_delta = delta;
|
||||||
}
|
}
|
||||||
WindowEvent::CursorLeft { .. } => {
|
WindowEvent::CursorLeft { .. } => {
|
||||||
@@ -66,7 +70,7 @@ impl Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl UiState {
|
||||||
pub fn window_size(&self) -> Vec2 {
|
pub fn window_size(&self) -> Vec2 {
|
||||||
let size = self.renderer.window().inner_size();
|
let size = self.renderer.window().inner_size();
|
||||||
(size.width, size.height).into()
|
(size.width, size.height).into()
|
||||||
181
src/default/mod.rs
Normal file
181
src/default/mod.rs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use arboard::Clipboard;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Instant;
|
||||||
|
use winit::event::{Ime, WindowEvent};
|
||||||
|
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
|
||||||
|
use winit::window::{Window, WindowAttributes};
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
mod attr;
|
||||||
|
mod event;
|
||||||
|
mod input;
|
||||||
|
mod render;
|
||||||
|
|
||||||
|
pub use app::*;
|
||||||
|
pub use attr::*;
|
||||||
|
pub use event::*;
|
||||||
|
pub use input::*;
|
||||||
|
pub use render::*;
|
||||||
|
|
||||||
|
pub type Proxy<Event> = EventLoopProxy<Event>;
|
||||||
|
pub type DefaultApp<Data> = App<DefaultState<Data>>;
|
||||||
|
|
||||||
|
pub struct DefaultState<AppState> {
|
||||||
|
ui: Ui,
|
||||||
|
ui_state: UiState,
|
||||||
|
app_state: AppState,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UiState {
|
||||||
|
pub renderer: UiRenderer,
|
||||||
|
pub input: Input,
|
||||||
|
pub focus: Option<WidgetRef<TextEdit>>,
|
||||||
|
pub clipboard: Clipboard,
|
||||||
|
pub window: Arc<Window>,
|
||||||
|
pub ime: usize,
|
||||||
|
pub last_click: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DefaultAppState: 'static {
|
||||||
|
type Event: 'static = ();
|
||||||
|
fn new(ui: &mut Ui, state: &UiState, proxy: Proxy<Self::Event>) -> Self;
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn event(&mut self, event: Self::Event, ui: &mut Ui, state: &UiState) {}
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn exit(&mut self, ui: &mut Ui, state: &UiState) {}
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn window_event(&mut self, event: WindowEvent, ui: &mut Ui, state: &UiState) {}
|
||||||
|
fn window_attrs() -> WindowAttributes {
|
||||||
|
WindowAttributes::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State: DefaultAppState> AppState for DefaultState<State> {
|
||||||
|
type Event = State::Event;
|
||||||
|
|
||||||
|
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
|
||||||
|
let window = Arc::new(
|
||||||
|
event_loop
|
||||||
|
.create_window(State::window_attrs())
|
||||||
|
.expect("failed to create window "),
|
||||||
|
);
|
||||||
|
let mut ui = Ui::new();
|
||||||
|
let ui_state = UiState {
|
||||||
|
renderer: UiRenderer::new(window.clone()),
|
||||||
|
window,
|
||||||
|
input: Input::default(),
|
||||||
|
clipboard: Clipboard::new().unwrap(),
|
||||||
|
ime: 0,
|
||||||
|
last_click: Instant::now(),
|
||||||
|
focus: None,
|
||||||
|
};
|
||||||
|
let app_state = State::new(&mut ui, &ui_state, proxy);
|
||||||
|
Self {
|
||||||
|
ui,
|
||||||
|
ui_state,
|
||||||
|
app_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
|
||||||
|
self.app_state.event(event, &mut self.ui, &self.ui_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||||
|
let Self {
|
||||||
|
ui,
|
||||||
|
ui_state,
|
||||||
|
app_state,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let input_changed = ui_state.input.event(&event);
|
||||||
|
let cursor_state = ui_state.cursor_state().clone();
|
||||||
|
let old = ui_state.focus.clone();
|
||||||
|
if cursor_state.buttons.left.is_start() {
|
||||||
|
ui_state.focus = None;
|
||||||
|
}
|
||||||
|
if input_changed {
|
||||||
|
let window_size = ui_state.window_size();
|
||||||
|
// call sensors with all 3 important contexts
|
||||||
|
// TODO: allow user to specify custom contexts?
|
||||||
|
// and give them both states in case they need both
|
||||||
|
ui.run_sensors(&mut (), &cursor_state, window_size);
|
||||||
|
ui.run_sensors(ui_state, &cursor_state, window_size);
|
||||||
|
ui.run_sensors(app_state, &cursor_state, window_size);
|
||||||
|
}
|
||||||
|
if old != ui_state.focus
|
||||||
|
&& let Some(old) = old
|
||||||
|
{
|
||||||
|
old.get_mut().deselect();
|
||||||
|
}
|
||||||
|
match &event {
|
||||||
|
WindowEvent::CloseRequested => event_loop.exit(),
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
ui_state.renderer.update(ui);
|
||||||
|
ui_state.renderer.draw();
|
||||||
|
}
|
||||||
|
WindowEvent::Resized(size) => {
|
||||||
|
ui.resize((size.width, size.height));
|
||||||
|
ui_state.renderer.resize(size)
|
||||||
|
}
|
||||||
|
WindowEvent::KeyboardInput { event, .. } => {
|
||||||
|
if let Some(sel) = &ui_state.focus
|
||||||
|
&& event.state.is_pressed()
|
||||||
|
{
|
||||||
|
let sel = &sel.clone();
|
||||||
|
let res = sel.get_mut().apply_event(event, &ui_state.input.modifiers);
|
||||||
|
match res {
|
||||||
|
TextInputResult::Unfocus => {
|
||||||
|
ui_state.focus = None;
|
||||||
|
ui_state.window.set_ime_allowed(false);
|
||||||
|
}
|
||||||
|
TextInputResult::Submit => {
|
||||||
|
ui.run_event(app_state, sel, Submit, ());
|
||||||
|
}
|
||||||
|
TextInputResult::Paste => {
|
||||||
|
if let Ok(t) = ui_state.clipboard.get_text() {
|
||||||
|
sel.get_mut().insert(&t);
|
||||||
|
}
|
||||||
|
ui.run_event(app_state, sel, Edited, ());
|
||||||
|
}
|
||||||
|
TextInputResult::Copy(text) => {
|
||||||
|
if let Err(err) = ui_state.clipboard.set_text(text) {
|
||||||
|
eprintln!("failed to copy text to clipboard: {err}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextInputResult::Used => {
|
||||||
|
ui.run_event(app_state, sel, Edited, ());
|
||||||
|
}
|
||||||
|
TextInputResult::Unused => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::Ime(ime) => {
|
||||||
|
if let Some(sel) = &ui_state.focus {
|
||||||
|
match ime {
|
||||||
|
Ime::Enabled | Ime::Disabled => (),
|
||||||
|
Ime::Preedit(content, _pos) => {
|
||||||
|
// TODO: highlight once that's real
|
||||||
|
sel.get_mut().replace(ui_state.ime, content);
|
||||||
|
ui_state.ime = content.chars().count();
|
||||||
|
}
|
||||||
|
Ime::Commit(content) => {
|
||||||
|
sel.get_mut().insert(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
app_state.window_event(event, ui, ui_state);
|
||||||
|
if ui.update() {
|
||||||
|
ui_state.renderer.window().request_redraw();
|
||||||
|
}
|
||||||
|
ui_state.input.end_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit(&mut self) {
|
||||||
|
self.app_state.exit(&mut self.ui, &self.ui_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
|
use crate::{
|
||||||
|
layout::Ui,
|
||||||
|
render::{UiLimits, UiRenderNode},
|
||||||
|
};
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use iris::{
|
|
||||||
layout::Ui,
|
|
||||||
render::{UiLimits, UiRenderer},
|
|
||||||
};
|
|
||||||
use wgpu::{util::StagingBelt, *};
|
use wgpu::{util::StagingBelt, *};
|
||||||
use winit::{dpi::PhysicalSize, window::Window};
|
use winit::{dpi::PhysicalSize, window::Window};
|
||||||
|
|
||||||
pub const CLEAR_COLOR: Color = Color::BLACK;
|
pub const CLEAR_COLOR: Color = Color::BLACK;
|
||||||
|
|
||||||
pub struct Renderer {
|
pub struct UiRenderer {
|
||||||
window: Arc<Window>,
|
window: Arc<Window>,
|
||||||
surface: Surface<'static>,
|
surface: Surface<'static>,
|
||||||
device: Device,
|
device: Device,
|
||||||
@@ -17,10 +17,10 @@ pub struct Renderer {
|
|||||||
config: SurfaceConfiguration,
|
config: SurfaceConfiguration,
|
||||||
encoder: CommandEncoder,
|
encoder: CommandEncoder,
|
||||||
staging_belt: StagingBelt,
|
staging_belt: StagingBelt,
|
||||||
pub ui: UiRenderer,
|
pub ui: UiRenderNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl UiRenderer {
|
||||||
pub fn update(&mut self, updates: &mut Ui) {
|
pub fn update(&mut self, updates: &mut Ui) {
|
||||||
self.ui.update(&self.device, &self.queue, updates);
|
self.ui.update(&self.device, &self.queue, updates);
|
||||||
}
|
}
|
||||||
@@ -72,6 +72,7 @@ impl Renderer {
|
|||||||
|
|
||||||
let instance = Instance::new(&InstanceDescriptor {
|
let instance = Instance::new(&InstanceDescriptor {
|
||||||
backends: Backends::PRIMARY,
|
backends: Backends::PRIMARY,
|
||||||
|
flags: InstanceFlags::empty(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ impl Renderer {
|
|||||||
.max_binding_array_elements_per_shader_stage(),
|
.max_binding_array_elements_per_shader_stage(),
|
||||||
max_binding_array_sampler_elements_per_shader_stage: ui_limits
|
max_binding_array_sampler_elements_per_shader_stage: ui_limits
|
||||||
.max_binding_array_sampler_elements_per_shader_stage(),
|
.max_binding_array_sampler_elements_per_shader_stage(),
|
||||||
|
max_buffer_size: 1 << 30,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -131,7 +133,7 @@ impl Renderer {
|
|||||||
let staging_belt = StagingBelt::new(4096 * 4);
|
let staging_belt = StagingBelt::new(4096 * 4);
|
||||||
let encoder = Self::create_encoder(&device);
|
let encoder = Self::create_encoder(&device);
|
||||||
|
|
||||||
let shape_pipeline = UiRenderer::new(&device, &queue, &config, ui_limits);
|
let shape_pipeline = UiRenderNode::new(&device, &queue, &config, ui_limits);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
surface,
|
surface,
|
||||||
82
src/event.rs
Normal file
82
src/event.rs
Normal 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;
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
use std::any::{Any, TypeId};
|
|
||||||
|
|
||||||
use crate::util::{HashMap, Id};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct UiData {
|
|
||||||
map: HashMap<TypeId, Box<dyn Any>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiData {
|
|
||||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
|
||||||
self.map
|
|
||||||
.get(&TypeId::of::<T>())
|
|
||||||
.map(|d| d.downcast_ref().unwrap())
|
|
||||||
}
|
|
||||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
|
||||||
self.map
|
|
||||||
.get_mut(&TypeId::of::<T>())
|
|
||||||
.map(|d| d.downcast_mut().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emit_remove(&mut self, id: &Id) {
|
|
||||||
for (tid, f) in &mut self.on_remove {
|
|
||||||
let data = self.map.get_mut(tid).unwrap().downcast_ref().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
use std::{hash::Hash, rc::Rc};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
layout::{IdFnTag, Ui, UiModule, Widget, WidgetId, WidgetIdFn, WidgetLike},
|
|
||||||
util::{HashMap, Id},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait UiCtx {
|
|
||||||
fn ui(&mut self) -> &mut Ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiCtx for Ui {
|
|
||||||
fn ui(&mut self) -> &mut Ui {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Event: Sized {
|
|
||||||
type Module<Ctx: 'static>: EventModule<Self, Ctx>;
|
|
||||||
type Data: Clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait EventFn<Ctx, Data>: Fn(&mut Ctx, Data) + 'static {}
|
|
||||||
impl<F: Fn(&mut Ctx, Data) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
|
|
||||||
|
|
||||||
pub trait Eventable<W, Tag> {
|
|
||||||
fn on<E: Event, Ctx: 'static>(
|
|
||||||
self,
|
|
||||||
event: E,
|
|
||||||
f: impl EventFn<Ctx, E::Data>,
|
|
||||||
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>;
|
|
||||||
|
|
||||||
fn id_on<E: Event, Ctx: 'static>(
|
|
||||||
self,
|
|
||||||
event: E,
|
|
||||||
f: impl Fn(&WidgetId<W>, &mut Ctx, E::Data) + 'static,
|
|
||||||
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
|
|
||||||
where
|
|
||||||
W: Widget;
|
|
||||||
|
|
||||||
fn edit_on<E: Event>(
|
|
||||||
self,
|
|
||||||
event: E,
|
|
||||||
f: impl Fn(&mut W, E::Data) + 'static,
|
|
||||||
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
|
|
||||||
where
|
|
||||||
W: Widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
|
|
||||||
fn on<E: Event, Ctx: 'static>(
|
|
||||||
self,
|
|
||||||
event: E,
|
|
||||||
f: impl EventFn<Ctx, E::Data>,
|
|
||||||
) -> impl WidgetIdFn<W::Widget> {
|
|
||||||
move |ui| {
|
|
||||||
let id = self.add(ui);
|
|
||||||
ui.data
|
|
||||||
.modules
|
|
||||||
.get_mut::<E::Module<Ctx>>()
|
|
||||||
.register(id.id, event, f);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id_on<E: Event, Ctx: 'static>(
|
|
||||||
self,
|
|
||||||
event: E,
|
|
||||||
f: impl Fn(&WidgetId<W::Widget>, &mut Ctx, E::Data) + 'static,
|
|
||||||
) -> impl WidgetIdFn<W::Widget>
|
|
||||||
where
|
|
||||||
W::Widget: Widget,
|
|
||||||
{
|
|
||||||
self.with_id(move |ui, id| {
|
|
||||||
let id2 = id.clone();
|
|
||||||
id.on(event, move |ctx, pos| f(&id2, ctx, pos)).add(ui)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn edit_on<E: Event>(
|
|
||||||
self,
|
|
||||||
event: E,
|
|
||||||
f: impl Fn(&mut W::Widget, E::Data) + 'static,
|
|
||||||
) -> impl WidgetIdFn<W::Widget>
|
|
||||||
where
|
|
||||||
W::Widget: Widget,
|
|
||||||
{
|
|
||||||
self.id_on(event, move |id, ui: &mut Ui, pos| f(&mut ui[id], pos))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DefaultEvent: Hash + Eq + 'static {
|
|
||||||
type Data: Clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: DefaultEvent> Event for E {
|
|
||||||
type Module<Ctx: 'static> = DefaultEventModule<E, Ctx>;
|
|
||||||
type Data = E::Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait EventModule<E: Event, Ctx>: UiModule + Default {
|
|
||||||
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, E::Data>);
|
|
||||||
fn run<'a>(
|
|
||||||
&self,
|
|
||||||
id: &Id,
|
|
||||||
event: E,
|
|
||||||
) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, Self, E, Ctx>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventFnMap<Ctx, Data> = HashMap<Id, Vec<Rc<dyn EventFn<Ctx, Data>>>>;
|
|
||||||
pub struct DefaultEventModule<E: Event, Ctx> {
|
|
||||||
map: HashMap<E, EventFnMap<Ctx, <E as Event>::Data>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Event + 'static, Ctx: 'static> UiModule for DefaultEventModule<E, Ctx> {
|
|
||||||
fn on_remove(&mut self, id: &Id) {
|
|
||||||
for map in self.map.values_mut() {
|
|
||||||
map.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HashableEvent: Event + Hash + Eq + 'static {}
|
|
||||||
impl<E: Event + Hash + Eq + 'static> HashableEvent for E {}
|
|
||||||
|
|
||||||
impl<E: HashableEvent, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<E, Ctx> {
|
|
||||||
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
|
|
||||||
self.map
|
|
||||||
.entry(event)
|
|
||||||
.or_default()
|
|
||||||
.entry(id)
|
|
||||||
.or_default()
|
|
||||||
.push(Rc::new(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
|
|
||||||
if let Some(map) = self.map.get(&event)
|
|
||||||
&& let Some(fs) = map.get(id)
|
|
||||||
{
|
|
||||||
let fs = fs.clone();
|
|
||||||
Some(move |ctx: &mut Ctx, data: E::Data| {
|
|
||||||
for f in &fs {
|
|
||||||
f(ctx, data.clone())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: HashableEvent, Ctx: 'static> DefaultEventModule<E, Ctx> {
|
|
||||||
pub fn run_all(&self, ctx: &mut Ctx, event: E, data: E::Data)
|
|
||||||
where
|
|
||||||
E::Data: Clone,
|
|
||||||
{
|
|
||||||
if let Some(map) = self.map.get(&event) {
|
|
||||||
for fs in map.values() {
|
|
||||||
for f in fs {
|
|
||||||
f(ctx, data.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Event + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
map: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ui {
|
|
||||||
pub fn run_event<E: Event, Ctx: UiCtx + 'static, W>(
|
|
||||||
ctx: &mut Ctx,
|
|
||||||
id: &WidgetId<W>,
|
|
||||||
event: E,
|
|
||||||
data: E::Data,
|
|
||||||
) {
|
|
||||||
if let Some(f) = ctx
|
|
||||||
.ui()
|
|
||||||
.data
|
|
||||||
.modules
|
|
||||||
.get_mut::<E::Module<Ctx>>()
|
|
||||||
.run(&id.id, event)
|
|
||||||
{
|
|
||||||
f(ctx, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait EventCtx: UiCtx {
|
|
||||||
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Ctx: UiCtx + 'static> EventCtx for Ctx {
|
|
||||||
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data) {
|
|
||||||
Ui::run_event(self, id, event.clone(), data.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
250
src/layout/id.rs
250
src/layout/id.rs
@@ -1,250 +0,0 @@
|
|||||||
use std::{
|
|
||||||
any::TypeId,
|
|
||||||
marker::PhantomData,
|
|
||||||
sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
mpsc::Sender,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
layout::{FnTag, Ui, Widget, WidgetLike, WidgetTag},
|
|
||||||
util::{Id, RefCounter},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct AnyWidget;
|
|
||||||
|
|
||||||
/// An identifier for a widget that can index a UI to get the associated widget.
|
|
||||||
/// It should always remain valid; it keeps a ref count and removes the widget from the UI if all
|
|
||||||
/// references are dropped.
|
|
||||||
///
|
|
||||||
/// W does not need to implement widget so that AnyWidget is valid;
|
|
||||||
/// Instead, add generic bounds on methods that take an ID if they need specific data.
|
|
||||||
///
|
|
||||||
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct WidgetId<W = AnyWidget> {
|
|
||||||
pub(super) ty: TypeId,
|
|
||||||
pub(super) id: Id,
|
|
||||||
counter: RefCounter,
|
|
||||||
send: Sender<Id>,
|
|
||||||
is_static: Arc<AtomicBool>,
|
|
||||||
_pd: PhantomData<W>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A WidgetId for a static widget that cannot be removed from a Ui.
|
|
||||||
/// Useful because ergonomic clones don't exist yet so you can easily use these in closures.
|
|
||||||
/// Do not use this if you want the widget to be freeable.
|
|
||||||
///
|
|
||||||
/// This is currently not perfectly efficient and just creates new WidgetIds every time it's used,
|
|
||||||
/// but they don't send drop messages to Ui.
|
|
||||||
/// Ideally I'd have an enum or something that lets you use either, but that doesn't seem worth it
|
|
||||||
/// right now; it's good enough and relatively cheap.
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct StaticWidgetId<W = AnyWidget> {
|
|
||||||
pub(super) ty: TypeId,
|
|
||||||
pub(super) id: Id,
|
|
||||||
_pd: PhantomData<W>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> std::fmt::Debug for WidgetId<W> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.id.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> Clone for WidgetId<W> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
id: self.id,
|
|
||||||
ty: self.ty,
|
|
||||||
counter: self.counter.clone(),
|
|
||||||
send: self.send.clone(),
|
|
||||||
is_static: self.is_static.clone(),
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> WidgetId<W> {
|
|
||||||
pub(super) fn new(id: Id, ty: TypeId, send: Sender<Id>, is_static: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
ty,
|
|
||||||
id,
|
|
||||||
counter: RefCounter::new(),
|
|
||||||
send,
|
|
||||||
is_static: Arc::new(is_static.into()),
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any(self) -> WidgetId<AnyWidget> {
|
|
||||||
self.cast_type()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_any(&self) -> &WidgetId<AnyWidget> {
|
|
||||||
// SAFETY: self is repr(C) and generic only used for phantom data
|
|
||||||
unsafe { std::mem::transmute(self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key(&self) -> Id {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn cast_type<W2>(self) -> WidgetId<W2> {
|
|
||||||
// SAFETY: self is repr(C) and generic only used for phantom data
|
|
||||||
unsafe { std::mem::transmute(self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refs(&self) -> u32 {
|
|
||||||
self.counter.refs()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_static(self) -> StaticWidgetId<W> {
|
|
||||||
self.is_static.store(true, Ordering::Release);
|
|
||||||
StaticWidgetId {
|
|
||||||
ty: self.ty,
|
|
||||||
id: self.id,
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetId {
|
|
||||||
pub fn set_static<W>(&mut self, other: StaticWidgetId<W>) {
|
|
||||||
let send = self.send.clone();
|
|
||||||
drop(std::mem::replace(
|
|
||||||
self,
|
|
||||||
Self::new(other.id, self.ty, send, true),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> Drop for WidgetId<W> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.counter.drop() && !self.is_static.load(Ordering::Acquire) {
|
|
||||||
let _ = self.send.send(self.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IdTag;
|
|
||||||
pub struct IdFnTag;
|
|
||||||
|
|
||||||
pub trait WidgetIdFn<W>: FnOnce(&mut Ui) -> WidgetId<W> {}
|
|
||||||
impl<W, F: FnOnce(&mut Ui) -> WidgetId<W>> WidgetIdFn<W> for F {}
|
|
||||||
|
|
||||||
/// TODO: does this ever make sense to use? it allows for invalid ids
|
|
||||||
pub trait Idable<Tag> {
|
|
||||||
type Widget: Widget;
|
|
||||||
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>);
|
|
||||||
fn id(self, id: &WidgetId<Self::Widget>) -> impl WidgetIdFn<Self::Widget>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
let id = id.clone();
|
|
||||||
move |ui| {
|
|
||||||
self.set(ui, &id);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn id_static(self, id: StaticWidgetId<Self::Widget>) -> impl WidgetIdFn<Self::Widget>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
move |ui| {
|
|
||||||
let id = id.id(&ui.send);
|
|
||||||
self.set(ui, &id);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget> Idable<WidgetTag> for W {
|
|
||||||
type Widget = W;
|
|
||||||
|
|
||||||
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>) {
|
|
||||||
ui.set(id, self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F: FnOnce(&mut Ui) -> W, W: Widget> Idable<FnTag> for F {
|
|
||||||
type Widget = W;
|
|
||||||
|
|
||||||
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>) {
|
|
||||||
let w = self(ui);
|
|
||||||
ui.set(id, w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: 'static> WidgetLike<IdTag> for WidgetId<W> {
|
|
||||||
type Widget = W;
|
|
||||||
fn add(self, _: &mut Ui) -> WidgetId<W> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: 'static, F: FnOnce(&mut Ui) -> WidgetId<W>> WidgetLike<IdFnTag> for F {
|
|
||||||
type Widget = W;
|
|
||||||
fn add(self, ui: &mut Ui) -> WidgetId<W> {
|
|
||||||
self(ui)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> StaticWidgetId<W> {
|
|
||||||
pub fn to_id(&self, send: &Sender<Id>) -> WidgetId<W> {
|
|
||||||
WidgetId::new(self.id, self.ty, send.clone(), true)
|
|
||||||
}
|
|
||||||
pub fn any(self) -> StaticWidgetId<AnyWidget> {
|
|
||||||
// SAFETY: self is repr(C)
|
|
||||||
unsafe { std::mem::transmute(self) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: 'static> WidgetLike<IdTag> for StaticWidgetId<W> {
|
|
||||||
type Widget = W;
|
|
||||||
fn add(self, ui: &mut Ui) -> WidgetId<W> {
|
|
||||||
self.id(&ui.send)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> Clone for StaticWidgetId<W> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> Copy for StaticWidgetId<W> {}
|
|
||||||
|
|
||||||
pub trait WidgetIdLike<W> {
|
|
||||||
fn id(self, send: &Sender<Id>) -> WidgetId<W>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> WidgetIdLike<W> for &WidgetId<W> {
|
|
||||||
fn id(self, _: &Sender<Id>) -> WidgetId<W> {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> WidgetIdLike<W> for StaticWidgetId<W> {
|
|
||||||
fn id(self, send: &Sender<Id>) -> WidgetId<W> {
|
|
||||||
self.to_id(send)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IdLike<W> {
|
|
||||||
fn id(&self) -> Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> IdLike<W> for WidgetId<W> {
|
|
||||||
fn id(&self) -> Id {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W> IdLike<W> for StaticWidgetId<W> {
|
|
||||||
fn id(&self) -> Id {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
241
src/layout/ui.rs
241
src/layout/ui.rs
@@ -1,241 +0,0 @@
|
|||||||
use image::DynamicImage;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
core::{TextEdit, TextEditCtx},
|
|
||||||
layout::{
|
|
||||||
IdLike, PainterData, PixelRegion, StaticWidgetId, TextureHandle, Vec2, Widget,
|
|
||||||
WidgetId, WidgetInstance, WidgetLike,
|
|
||||||
},
|
|
||||||
util::{HashSet, Id},
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
any::{Any, TypeId},
|
|
||||||
ops::{Index, IndexMut},
|
|
||||||
sync::mpsc::{Receiver, Sender, channel},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Ui {
|
|
||||||
// TODO: make this at least pub(super)
|
|
||||||
pub(crate) data: PainterData,
|
|
||||||
root: Option<WidgetId>,
|
|
||||||
updates: HashSet<Id>,
|
|
||||||
recv: Receiver<Id>,
|
|
||||||
pub(super) send: Sender<Id>,
|
|
||||||
full_redraw: bool,
|
|
||||||
resized: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ui {
|
|
||||||
pub fn add<W: Widget, Tag>(&mut self, w: impl WidgetLike<Tag, Widget = W>) -> WidgetId<W> {
|
|
||||||
w.add(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_static<W: Widget, Tag>(
|
|
||||||
&mut self,
|
|
||||||
w: impl WidgetLike<Tag, Widget = W>,
|
|
||||||
) -> StaticWidgetId<W> {
|
|
||||||
let id = w.add(self);
|
|
||||||
id.into_static()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// useful for debugging
|
|
||||||
pub fn set_label<W>(&mut self, id: &WidgetId<W>, label: String) {
|
|
||||||
self.data.widgets.data_mut(&id.id).unwrap().label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn label<W>(&self, id: &WidgetId<W>) -> &String {
|
|
||||||
&self.data.widgets.data(&id.id).unwrap().label
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetId<W> {
|
|
||||||
self.push(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push<W: Widget>(&mut self, w: W) -> WidgetId<W> {
|
|
||||||
let id = self.id();
|
|
||||||
self.data.widgets.insert(id.id, w);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set<W: Widget>(&mut self, id: &WidgetId<W>, w: W) {
|
|
||||||
self.data.widgets.insert(id.id, w);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
|
|
||||||
self.root = Some(w.add(self).any());
|
|
||||||
self.full_redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<W: Widget>(&self, id: &impl IdLike<W>) -> Option<&W> {
|
|
||||||
self.data.widgets.get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut<W: Widget>(&mut self, id: &impl IdLike<W>) -> Option<&mut W> {
|
|
||||||
self.data.widgets.get_mut(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id<W: Widget>(&mut self) -> WidgetId<W> {
|
|
||||||
WidgetId::new(
|
|
||||||
self.data.widgets.reserve(),
|
|
||||||
TypeId::of::<W>(),
|
|
||||||
self.send.clone(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id_static<W: Widget>(&mut self) -> StaticWidgetId<W> {
|
|
||||||
let id = self.id();
|
|
||||||
id.into_static()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
|
|
||||||
self.data.textures.add(image)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
|
||||||
self.data.output_size = size.into();
|
|
||||||
self.resized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redraw_all(&mut self) {
|
|
||||||
for (_, inst) in self.data.active.drain() {
|
|
||||||
for m in self.data.modules.iter_mut() {
|
|
||||||
m.on_undraw(&inst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// free before bc nothing should exist
|
|
||||||
self.free();
|
|
||||||
if let Some(root) = &self.root {
|
|
||||||
self.data.draw(root.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
if self.full_redraw {
|
|
||||||
self.redraw_all();
|
|
||||||
self.full_redraw = false;
|
|
||||||
} else if !self.updates.is_empty() {
|
|
||||||
self.redraw_updates();
|
|
||||||
}
|
|
||||||
if self.resized {
|
|
||||||
self.resized = false;
|
|
||||||
self.redraw_size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redraw_size(&mut self) {
|
|
||||||
// let mut ctx = PainterCtx::new(&mut self.data);
|
|
||||||
// let dep = ctx.px_dependent.clone();
|
|
||||||
// for id in dep {
|
|
||||||
// ctx.redraw(id);
|
|
||||||
// }
|
|
||||||
self.redraw_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redraw_updates(&mut self) {
|
|
||||||
self.data.redraw(std::mem::take(&mut self.updates));
|
|
||||||
self.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// free any resources that don't have references anymore
|
|
||||||
fn free(&mut self) {
|
|
||||||
for id in self.recv.try_iter() {
|
|
||||||
for m in self.data.modules.iter_mut() {
|
|
||||||
m.on_remove(&id);
|
|
||||||
}
|
|
||||||
self.data.widgets.delete(id);
|
|
||||||
}
|
|
||||||
self.data.textures.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn needs_redraw(&self) -> bool {
|
|
||||||
self.full_redraw || !self.updates.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn num_widgets(&self) -> usize {
|
|
||||||
self.data.widgets.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_widgets(&self) -> usize {
|
|
||||||
self.data.active.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(&mut self, id: &impl IdLike<TextEdit>) -> TextEditCtx<'_> {
|
|
||||||
self.updates.insert(id.id());
|
|
||||||
TextEditCtx {
|
|
||||||
text: self.data.widgets.get_mut(id).unwrap(),
|
|
||||||
font_system: &mut self.data.text.font_system,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
|
|
||||||
let region = self.data.active.get(&id.id())?.region;
|
|
||||||
Some(region.to_px(self.data.output_size))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug(&self, label: &str) -> impl Iterator<Item = &WidgetInstance> {
|
|
||||||
self.data.active.iter().filter_map(move |(id, inst)| {
|
|
||||||
let l = &self.data.widgets.label(id);
|
|
||||||
if *l == label { Some(inst) } else { None }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget> Index<&WidgetId<W>> for Ui {
|
|
||||||
type Output = W;
|
|
||||||
|
|
||||||
fn index(&self, id: &WidgetId<W>) -> &Self::Output {
|
|
||||||
self.get(id).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget> IndexMut<&WidgetId<W>> for Ui {
|
|
||||||
fn index_mut(&mut self, id: &WidgetId<W>) -> &mut Self::Output {
|
|
||||||
self.updates.insert(id.id);
|
|
||||||
self.get_mut(id).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget> Index<StaticWidgetId<W>> for Ui {
|
|
||||||
type Output = W;
|
|
||||||
|
|
||||||
fn index(&self, id: StaticWidgetId<W>) -> &Self::Output {
|
|
||||||
self.data.widgets.get(&id).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget> IndexMut<StaticWidgetId<W>> for Ui {
|
|
||||||
fn index_mut(&mut self, id: StaticWidgetId<W>) -> &mut Self::Output {
|
|
||||||
self.updates.insert(id.id);
|
|
||||||
self.data.widgets.get_mut(&id).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl dyn Widget {
|
|
||||||
pub fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Ui {
|
|
||||||
fn default() -> Self {
|
|
||||||
let (send, recv) = channel();
|
|
||||||
Self {
|
|
||||||
data: PainterData::default(),
|
|
||||||
root: Default::default(),
|
|
||||||
updates: Default::default(),
|
|
||||||
full_redraw: false,
|
|
||||||
send,
|
|
||||||
recv,
|
|
||||||
resized: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
use crate::layout::{Len, Painter, SizeCtx, StaticWidgetId, Ui, WidgetId, WidgetIdFn};
|
|
||||||
|
|
||||||
use std::{any::Any, marker::PhantomData};
|
|
||||||
|
|
||||||
pub trait Widget: Any {
|
|
||||||
fn draw(&mut self, painter: &mut Painter);
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for () {
|
|
||||||
fn draw(&mut self, _: &mut Painter) {}
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WidgetTag;
|
|
||||||
pub struct FnTag;
|
|
||||||
|
|
||||||
pub trait WidgetLike<Tag> {
|
|
||||||
type Widget: 'static;
|
|
||||||
fn add(self, ui: &mut Ui) -> WidgetId<Self::Widget>;
|
|
||||||
fn with_id<W2>(
|
|
||||||
self,
|
|
||||||
f: impl FnOnce(&mut Ui, WidgetId<Self::Widget>) -> WidgetId<W2>,
|
|
||||||
) -> impl WidgetIdFn<W2>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
move |ui| {
|
|
||||||
let id = self.add(ui);
|
|
||||||
f(ui, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn add_static(self, ui: &mut Ui) -> StaticWidgetId<Self::Widget>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.add(ui).into_static()
|
|
||||||
}
|
|
||||||
fn set_root(self, ui: &mut Ui)
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
ui.set_root(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A function that returns a widget given a UI.
|
|
||||||
/// Useful for defining trait functions on widgets that create a parent widget so that the children
|
|
||||||
/// don't need to be IDs yet
|
|
||||||
pub trait WidgetFn<W: Widget>: FnOnce(&mut Ui) -> W {}
|
|
||||||
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetFn<W> for F {}
|
|
||||||
|
|
||||||
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetLike<FnTag> for F {
|
|
||||||
type Widget = W;
|
|
||||||
fn add(self, ui: &mut Ui) -> WidgetId<W> {
|
|
||||||
self(ui).add(ui)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Widget> WidgetLike<WidgetTag> for W {
|
|
||||||
type Widget = W;
|
|
||||||
fn add(self, ui: &mut Ui) -> WidgetId<W> {
|
|
||||||
ui.add_widget(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WidgetArr<const LEN: usize, Ws> {
|
|
||||||
pub arr: [WidgetId; LEN],
|
|
||||||
_pd: PhantomData<Ws>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize, Ws> WidgetArr<LEN, Ws> {
|
|
||||||
pub fn new(arr: [WidgetId; LEN]) -> Self {
|
|
||||||
Self {
|
|
||||||
arr,
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ArrTag;
|
|
||||||
pub trait WidgetArrLike<const LEN: usize, Tag> {
|
|
||||||
type Ws;
|
|
||||||
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN, Self::Ws>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize, Ws> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN, Ws> {
|
|
||||||
type Ws = Ws;
|
|
||||||
fn ui(self, _: &mut Ui) -> WidgetArr<LEN, Ws> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: WidgetLike<WidgetTag>> WidgetArrLike<1, WidgetTag> for W {
|
|
||||||
type Ws = (W::Widget,);
|
|
||||||
fn ui(self, ui: &mut Ui) -> WidgetArr<1, (W::Widget,)> {
|
|
||||||
WidgetArr::new([self.add(ui).any()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I hate this language it's so bad why do I even use it
|
|
||||||
macro_rules! impl_widget_arr {
|
|
||||||
($n:expr;$($W:ident)*) => {
|
|
||||||
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
|
|
||||||
};
|
|
||||||
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
|
|
||||||
impl<$($W: WidgetLike<$Tag>,$Tag,)*> WidgetArrLike<$n, ($($Tag,)*)> for ($($W,)*) {
|
|
||||||
type Ws = ($($W::Widget,)*);
|
|
||||||
fn ui(self, ui: &mut Ui) -> WidgetArr<$n, ($($W::Widget,)*)> {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
let ($($W,)*) = self;
|
|
||||||
WidgetArr::new(
|
|
||||||
[$($W.add(ui).cast_type(),)*],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_widget_arr!(1;A);
|
|
||||||
impl_widget_arr!(2;A B);
|
|
||||||
impl_widget_arr!(3;A B C);
|
|
||||||
impl_widget_arr!(4;A B C D);
|
|
||||||
impl_widget_arr!(5;A B C D E);
|
|
||||||
impl_widget_arr!(6;A B C D E F);
|
|
||||||
impl_widget_arr!(7;A B C D E F G);
|
|
||||||
impl_widget_arr!(8;A B C D E F G H);
|
|
||||||
impl_widget_arr!(9;A B C D E F G H I);
|
|
||||||
impl_widget_arr!(10;A B C D E F G H I J);
|
|
||||||
impl_widget_arr!(11;A B C D E F G H I J K);
|
|
||||||
impl_widget_arr!(12;A B C D E F G H I J K L);
|
|
||||||
|
|
||||||
pub trait WidgetOption {
|
|
||||||
fn get(self, ui: &mut Ui) -> Option<WidgetId>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetOption for () {
|
|
||||||
fn get(self, _: &mut Ui) -> Option<WidgetId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F: FnOnce(&mut Ui) -> Option<WidgetId>> WidgetOption for F {
|
|
||||||
fn get(self, ui: &mut Ui) -> Option<WidgetId> {
|
|
||||||
self(ui)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>;
|
|
||||||
32
src/lib.rs
32
src/lib.rs
@@ -1,21 +1,23 @@
|
|||||||
#![feature(macro_metavar_expr_concat)]
|
|
||||||
#![feature(const_ops)]
|
|
||||||
#![feature(const_trait_impl)]
|
|
||||||
#![feature(const_convert)]
|
|
||||||
#![feature(map_try_insert)]
|
|
||||||
#![feature(unboxed_closures)]
|
#![feature(unboxed_closures)]
|
||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
#![feature(const_cmp)]
|
#![feature(gen_blocks)]
|
||||||
#![feature(const_destruct)]
|
#![feature(associated_type_defaults)]
|
||||||
#![feature(portable_simd)]
|
|
||||||
|
|
||||||
pub mod core;
|
mod default;
|
||||||
pub mod layout;
|
mod event;
|
||||||
pub mod render;
|
mod widget;
|
||||||
pub mod util;
|
|
||||||
|
pub use iris_core::*;
|
||||||
|
pub use iris_macro::*;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::core::*;
|
pub use super::default::*;
|
||||||
pub use crate::layout::*;
|
pub use super::event::*;
|
||||||
pub use crate::render::*;
|
pub use super::widget::*;
|
||||||
|
|
||||||
|
pub use iris_core::layout::*;
|
||||||
|
pub use iris_core::render::*;
|
||||||
|
pub use iris_core::util::Handle;
|
||||||
|
|
||||||
|
pub use iris_macro::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
@@ -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 {
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
23
src/widget/position/layer.rs
Normal file
23
src/widget/position/layer.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub struct LayerOffset {
|
||||||
|
pub inner: WidgetRef,
|
||||||
|
pub offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for LayerOffset {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
for _ in 0..self.offset {
|
||||||
|
painter.next_layer();
|
||||||
|
}
|
||||||
|
painter.widget(&self.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
ctx.width(&self.inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
ctx.height(&self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
mod align;
|
mod align;
|
||||||
|
mod layer;
|
||||||
mod max_size;
|
mod max_size;
|
||||||
mod offset;
|
mod offset;
|
||||||
mod pad;
|
mod pad;
|
||||||
@@ -8,6 +9,7 @@ mod span;
|
|||||||
mod stack;
|
mod stack;
|
||||||
|
|
||||||
pub use align::*;
|
pub use align::*;
|
||||||
|
pub use layer::*;
|
||||||
pub use max_size::*;
|
pub use max_size::*;
|
||||||
pub use offset::*;
|
pub use offset::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -39,6 +39,13 @@ pub struct Padding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Padding {
|
impl Padding {
|
||||||
|
pub const ZERO: Self = Self {
|
||||||
|
left: 0.0,
|
||||||
|
right: 0.0,
|
||||||
|
top: 0.0,
|
||||||
|
bottom: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn uniform(amt: impl UiNum) -> Self {
|
pub fn uniform(amt: impl UiNum) -> Self {
|
||||||
let amt = amt.to_f32();
|
let amt = amt.to_f32();
|
||||||
Self {
|
Self {
|
||||||
@@ -75,6 +82,30 @@ impl Padding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn top(amt: impl UiNum) -> Self {
|
||||||
|
let mut s = Self::ZERO;
|
||||||
|
s.top = amt.to_f32();
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bottom(amt: impl UiNum) -> Self {
|
||||||
|
let mut s = Self::ZERO;
|
||||||
|
s.bottom = amt.to_f32();
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left(amt: impl UiNum) -> Self {
|
||||||
|
let mut s = Self::ZERO;
|
||||||
|
s.left = amt.to_f32();
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right(amt: impl UiNum) -> Self {
|
||||||
|
let mut s = Self::ZERO;
|
||||||
|
s.right = amt.to_f32();
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_top(mut self, amt: impl UiNum) -> Self {
|
pub fn with_top(mut self, amt: impl UiNum) -> Self {
|
||||||
self.top = amt.to_f32();
|
self.top = amt.to_f32();
|
||||||
self
|
self
|
||||||
@@ -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,
|
||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
@@ -180,3 +180,17 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Ta
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Span {
|
||||||
|
type Target = Vec<WidgetRef>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for Span {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.children
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,16 +3,12 @@ use std::marker::PhantomData;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Stack {
|
pub struct Stack {
|
||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetRef>,
|
||||||
pub size: StackSize,
|
pub size: StackSize,
|
||||||
pub offset: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Stack {
|
impl Widget for Stack {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
for _ in 0..self.offset {
|
|
||||||
painter.next_layer();
|
|
||||||
}
|
|
||||||
let mut iter = self.children.iter();
|
let mut iter = self.children.iter();
|
||||||
if let Some(child) = iter.next() {
|
if let Some(child) = iter.next() {
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
@@ -49,7 +45,6 @@ pub enum StackSize {
|
|||||||
pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
||||||
pub children: Wa,
|
pub children: Wa,
|
||||||
pub size: StackSize,
|
pub size: StackSize,
|
||||||
pub offset: usize,
|
|
||||||
_pd: PhantomData<Tag>,
|
_pd: PhantomData<Tag>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +56,6 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
|
|||||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
||||||
Stack {
|
Stack {
|
||||||
children: self.children.ui(args.0).arr.to_vec(),
|
children: self.children.ui(args.0).arr.to_vec(),
|
||||||
offset: self.offset,
|
|
||||||
size: self.size,
|
size: self.size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +66,6 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, T
|
|||||||
Self {
|
Self {
|
||||||
children,
|
children,
|
||||||
size: StackSize::default(),
|
size: StackSize::default(),
|
||||||
offset: 0,
|
|
||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,9 +74,4 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, T
|
|||||||
self.size = size;
|
self.size = size;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn offset_layer(mut self, offset: usize) -> Self {
|
|
||||||
self.offset = offset;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
@@ -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,21 +8,21 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{UiModule, UiRegion, Vec2},
|
layout::{UiModule, UiRegion, Vec2},
|
||||||
util::{HashMap, Id},
|
util::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum Button {
|
pub enum CursorButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Middle,
|
Middle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(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,10 +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 {
|
||||||
|
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 {
|
||||||
|
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +115,9 @@ pub struct CursorData {
|
|||||||
pub cursor: Vec2,
|
pub cursor: Vec2,
|
||||||
pub size: Vec2,
|
pub size: Vec2,
|
||||||
pub scroll_delta: Vec2,
|
pub scroll_delta: Vec2,
|
||||||
|
/// the (first) sense that triggered this event
|
||||||
|
/// the senses are checked in order
|
||||||
|
pub sense: CursorSense,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CursorModule<Ctx> {
|
pub struct CursorModule<Ctx> {
|
||||||
@@ -157,20 +167,25 @@ impl<Ctx> CursorModule<Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SensorCtx: UiCtx {
|
pub trait SensorUi {
|
||||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
|
fn run_sensors<Ctx: 'static>(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: UiCtx + 'static> SensorCtx for Ctx {
|
impl SensorUi for Ui {
|
||||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) {
|
fn run_sensors<Ctx: 'static>(
|
||||||
CursorModule::<Ctx>::run(self, cursor, window_size);
|
&mut self,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
cursor: &CursorState,
|
||||||
|
window_size: Vec2,
|
||||||
|
) {
|
||||||
|
CursorModule::<Ctx>::run(self, ctx, cursor, window_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
|
impl<Ctx: 'static> CursorModule<Ctx> {
|
||||||
pub fn run(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 ctx.ui().data.layers);
|
let layers = std::mem::take(&mut ui.data_mut().layers);
|
||||||
let mut module = std::mem::take(ctx.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 {
|
||||||
@@ -188,13 +203,18 @@ impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
|
|||||||
sensed = true;
|
sensed = true;
|
||||||
|
|
||||||
for sensor in &mut group.sensors {
|
for sensor in &mut group.sensors {
|
||||||
if should_run(&sensor.senses, cursor, group.hover) {
|
if let Some(sense) = should_run(&sensor.senses, cursor, group.hover) {
|
||||||
let data = CursorData {
|
let data = CursorData {
|
||||||
cursor: cursor.pos - region.top_left,
|
cursor: cursor.pos - region.top_left,
|
||||||
size: region.bot_right - region.top_left,
|
size: region.bot_right - region.top_left,
|
||||||
scroll_delta: cursor.scroll_delta,
|
scroll_delta: cursor.scroll_delta,
|
||||||
|
sense,
|
||||||
};
|
};
|
||||||
(sensor.f)(ctx, data);
|
(sensor.f)(EventCtx {
|
||||||
|
ui,
|
||||||
|
state: ctx,
|
||||||
|
data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,14 +223,18 @@ impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ui_mod = ctx.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);
|
||||||
ctx.ui().data.layers = layers;
|
ui.data_mut().layers = layers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_run(senses: &CursorSenses, cursor: &CursorState, hover: ActivationState) -> bool {
|
pub fn should_run(
|
||||||
|
senses: &CursorSenses,
|
||||||
|
cursor: &CursorState,
|
||||||
|
hover: ActivationState,
|
||||||
|
) -> Option<CursorSense> {
|
||||||
for sense in senses.iter() {
|
for sense in senses.iter() {
|
||||||
if match sense {
|
if match sense {
|
||||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||||
@@ -221,10 +245,10 @@ pub fn should_run(senses: &CursorSenses, cursor: &CursorState, hover: Activation
|
|||||||
CursorSense::HoverEnd => hover.is_end(),
|
CursorSense::HoverEnd => hover.is_end(),
|
||||||
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
||||||
} {
|
} {
|
||||||
return true;
|
return Some(*sense);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActivationState {
|
impl ActivationState {
|
||||||
@@ -280,8 +304,8 @@ impl Event for CursorSense {
|
|||||||
type Data = CursorData;
|
type Data = CursorData;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static> EventModule<E, Ctx>
|
impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static>
|
||||||
for CursorModule<Ctx>
|
EventModule<E, Ctx> for CursorModule<Ctx>
|
||||||
{
|
{
|
||||||
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
|
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
|
||||||
// TODO: does not add to active if currently active
|
// TODO: does not add to active if currently active
|
||||||
@@ -291,7 +315,11 @@ impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: '
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
|
fn run<'a>(
|
||||||
|
&self,
|
||||||
|
id: &Id,
|
||||||
|
event: E,
|
||||||
|
) -> Option<impl Fn(EventCtx<Ctx, <E as Event>::Data>) + use<'a, E, Ctx>> {
|
||||||
let senses = event.into();
|
let senses = event.into();
|
||||||
if let Some(group) = self.map.get(id) {
|
if let Some(group) = self.map.get(id) {
|
||||||
let fs: Vec<_> = group
|
let fs: Vec<_> = group
|
||||||
@@ -305,9 +333,13 @@ impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: '
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Some(move |ctx: &mut Ctx, data: CursorData| {
|
Some(move |ctx: EventCtx<Ctx, CursorData>| {
|
||||||
for f in &fs {
|
for f in &fs {
|
||||||
f(ctx, data.clone());
|
f(EventCtx {
|
||||||
|
state: ctx.state,
|
||||||
|
ui: ctx.ui,
|
||||||
|
data: ctx.data.clone(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
use std::marker::{PhantomData, Sized};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use cosmic_text::{Attrs, Family, Metrics};
|
use cosmic_text::{Attrs, Family, Metrics};
|
||||||
|
use std::marker::Sized;
|
||||||
|
|
||||||
pub struct TextBuilder<O = TextOutput, H: WidgetOption = ()> {
|
pub struct TextBuilder<O = TextOutput, H: WidgetOption = ()> {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub attrs: TextAttrs,
|
pub attrs: TextAttrs,
|
||||||
pub hint: H,
|
pub hint: H,
|
||||||
_pd: PhantomData<O>,
|
pub output: O,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<O, H: WidgetOption> TextBuilder<O, H> {
|
impl<O, H: WidgetOption> TextBuilder<O, H> {
|
||||||
pub fn size(mut self, size: impl UiNum) -> Self {
|
pub fn size(mut self, size: impl UiNum) -> Self {
|
||||||
self.attrs.font_size = size.to_f32();
|
self.attrs.font_size = size.to_f32();
|
||||||
self.attrs.line_height = self.attrs.font_size * 1.1;
|
self.attrs.line_height = self.attrs.font_size * LINE_HEIGHT_MULT;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn color(mut self, color: UiColor) -> Self {
|
pub fn color(mut self, color: UiColor) -> Self {
|
||||||
@@ -31,16 +31,20 @@ impl<O, H: WidgetOption> TextBuilder<O, H> {
|
|||||||
self.attrs.align = align.into();
|
self.attrs.align = align.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn center_text(mut self) -> Self {
|
||||||
|
self.attrs.align = Align::CENTER;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||||
self.attrs.wrap = wrap;
|
self.attrs.wrap = wrap;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn editable(self) -> TextBuilder<TextEditOutput, H> {
|
pub fn editable(self, single_line: bool) -> TextBuilder<TextEditOutput, H> {
|
||||||
TextBuilder {
|
TextBuilder {
|
||||||
content: self.content,
|
content: self.content,
|
||||||
attrs: self.attrs,
|
attrs: self.attrs,
|
||||||
hint: self.hint,
|
hint: self.hint,
|
||||||
_pd: PhantomData,
|
output: TextEditOutput { single_line },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +55,7 @@ impl<O> TextBuilder<O> {
|
|||||||
content: self.content,
|
content: self.content,
|
||||||
attrs: self.attrs,
|
attrs: self.attrs,
|
||||||
hint: move |ui: &mut Ui| Some(hint.add(ui).any()),
|
hint: move |ui: &mut Ui| Some(hint.add(ui).any()),
|
||||||
_pd: PhantomData,
|
output: self.output,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +75,7 @@ impl TextBuilderOutput for TextOutput {
|
|||||||
builder.attrs.line_height,
|
builder.attrs.line_height,
|
||||||
));
|
));
|
||||||
let hint = builder.hint.get(ui);
|
let hint = builder.hint.get(ui);
|
||||||
let font_system = &mut ui.data.text.font_system;
|
let font_system = &mut ui.data().text.get_mut().font_system;
|
||||||
buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
|
buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
|
||||||
let mut text = Text {
|
let mut text = Text {
|
||||||
content: builder.content.into(),
|
content: builder.content.into(),
|
||||||
@@ -83,7 +87,9 @@ impl TextBuilderOutput for TextOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextEditOutput;
|
pub struct TextEditOutput {
|
||||||
|
single_line: bool,
|
||||||
|
}
|
||||||
impl TextBuilderOutput for TextEditOutput {
|
impl TextBuilderOutput for TextEditOutput {
|
||||||
type Output = TextEdit;
|
type Output = TextEdit;
|
||||||
|
|
||||||
@@ -92,11 +98,12 @@ impl TextBuilderOutput for TextEditOutput {
|
|||||||
builder.attrs.font_size,
|
builder.attrs.font_size,
|
||||||
builder.attrs.line_height,
|
builder.attrs.line_height,
|
||||||
));
|
));
|
||||||
let mut text = TextEdit {
|
let mut text = TextEdit::new(
|
||||||
view: TextView::new(buf, builder.attrs, builder.hint.get(ui)),
|
TextView::new(buf, builder.attrs, builder.hint.get(ui)),
|
||||||
cursor: None,
|
builder.output.single_line,
|
||||||
};
|
ui.data().text.clone(),
|
||||||
let font_system = &mut ui.data.text.font_system;
|
);
|
||||||
|
let font_system = &mut ui.data().text.get_mut().font_system;
|
||||||
text.buf
|
text.buf
|
||||||
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
|
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
|
||||||
builder.attrs.apply(font_system, &mut text.buf, None);
|
builder.attrs.apply(font_system, &mut text.buf, None);
|
||||||
@@ -112,11 +119,11 @@ impl<O: TextBuilderOutput, H: WidgetOption> FnOnce<(&mut Ui,)> for TextBuilder<O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(content: impl Into<String>) -> TextBuilder {
|
pub fn wtext(content: impl Into<String>) -> TextBuilder {
|
||||||
TextBuilder {
|
TextBuilder {
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
attrs: TextAttrs::default(),
|
attrs: TextAttrs::default(),
|
||||||
hint: (),
|
hint: (),
|
||||||
_pd: PhantomData,
|
output: TextOutput,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
610
src/widget/text/edit.rs
Normal file
610
src/widget/text/edit.rs
Normal file
@@ -0,0 +1,610 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use cosmic_text::{Affinity, Attrs, Cursor, LayoutRun, Motion};
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
use winit::{
|
||||||
|
event::KeyEvent,
|
||||||
|
keyboard::{Key, NamedKey},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TextEdit {
|
||||||
|
view: TextView,
|
||||||
|
selection: TextSelection,
|
||||||
|
history: Vec<(String, TextSelection)>,
|
||||||
|
double_hit: Option<Cursor>,
|
||||||
|
data: TextData,
|
||||||
|
pub single_line: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEdit {
|
||||||
|
pub fn new(view: TextView, single_line: bool, data: TextData) -> Self {
|
||||||
|
Self {
|
||||||
|
view,
|
||||||
|
selection: Default::default(),
|
||||||
|
history: Default::default(),
|
||||||
|
double_hit: None,
|
||||||
|
single_line,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn select_content(&self, start: Cursor, end: Cursor) -> String {
|
||||||
|
let (start, end) = sort_cursors(start, end);
|
||||||
|
let mut iter = self.buf.lines.iter().skip(start.line);
|
||||||
|
let first = iter.next().unwrap();
|
||||||
|
if start.line == end.line {
|
||||||
|
first.text()[start.index..end.index].to_string()
|
||||||
|
} else {
|
||||||
|
let mut str = first.text()[start.index..].to_string();
|
||||||
|
for _ in (start.line + 1)..end.line {
|
||||||
|
str = str + "\n" + iter.next().unwrap().text();
|
||||||
|
}
|
||||||
|
let last = iter.next().unwrap();
|
||||||
|
str = str + "\n" + &last.text()[..end.index];
|
||||||
|
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 {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
let base = painter.layer;
|
||||||
|
painter.child_layer();
|
||||||
|
self.view.draw(painter);
|
||||||
|
painter.layer = base;
|
||||||
|
let region = self.region();
|
||||||
|
|
||||||
|
let size = vec2(1, self.attrs.line_height);
|
||||||
|
match self.selection {
|
||||||
|
TextSelection::None => (),
|
||||||
|
TextSelection::Pos(cursor) => {
|
||||||
|
if let Some(offset) = cursor_pos(cursor, &self.buf) {
|
||||||
|
painter.primitive_within(
|
||||||
|
RectPrimitive::color(Color::WHITE),
|
||||||
|
size.align(Align::TOP_LEFT).offset(offset).within(®ion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextSelection::Span { start, end } => {
|
||||||
|
let (start, end) = sort_cursors(start, end);
|
||||||
|
for (l, x, width) in iter_layout_lines(start, end, &self.buf) {
|
||||||
|
let top_left = vec2(x, self.attrs.line_height * l as f32);
|
||||||
|
painter.primitive_within(
|
||||||
|
RectPrimitive::color(Color::SKY),
|
||||||
|
size.with_x(width)
|
||||||
|
.align(Align::TOP_LEFT)
|
||||||
|
.offset(top_left)
|
||||||
|
.within(®ion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(end_offset) = cursor_pos(end, &self.buf) {
|
||||||
|
painter.primitive_within(
|
||||||
|
RectPrimitive::color(Color::WHITE),
|
||||||
|
size.align(Align::TOP_LEFT)
|
||||||
|
.offset(end_offset)
|
||||||
|
.within(®ion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
self.view.desired_width(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
self.view.desired_height(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// provides top left + width
|
||||||
|
fn iter_layout_lines(
|
||||||
|
start: Cursor,
|
||||||
|
end: Cursor,
|
||||||
|
buf: &TextBuffer,
|
||||||
|
) -> impl Iterator<Item = (usize, f32, f32)> {
|
||||||
|
gen move {
|
||||||
|
let mut iter = buf.layout_runs().enumerate();
|
||||||
|
for (i, line) in iter.by_ref() {
|
||||||
|
if line.line_i == start.line
|
||||||
|
&& let Some(start_x) = index_x(&line, start.index)
|
||||||
|
{
|
||||||
|
if start.line == end.line
|
||||||
|
&& let Some(end_x) = index_x(&line, end.index)
|
||||||
|
{
|
||||||
|
yield (i, start_x, end_x - start_x);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yield (i, start_x, line.line_w - start_x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i, line) in iter {
|
||||||
|
if line.line_i > end.line {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if line.line_i == end.line
|
||||||
|
&& let Some(end_x) = index_x(&line, end.index)
|
||||||
|
{
|
||||||
|
yield (i, 0.0, end_x);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yield (i, 0.0, line.line_w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// copied & modified from fn found in Editor in cosmic_text
|
||||||
|
/// returns x pos of a (non layout) index within an layout run
|
||||||
|
fn index_x(run: &LayoutRun, index: usize) -> Option<f32> {
|
||||||
|
for glyph in run.glyphs.iter() {
|
||||||
|
if index == glyph.start {
|
||||||
|
return Some(glyph.x);
|
||||||
|
} else if index > glyph.start && index < glyph.end {
|
||||||
|
// Guess x offset based on characters
|
||||||
|
let mut before = 0;
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
let cluster = &run.text[glyph.start..glyph.end];
|
||||||
|
for (i, _) in cluster.grapheme_indices(true) {
|
||||||
|
if glyph.start + i < index {
|
||||||
|
before += 1;
|
||||||
|
}
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = glyph.w * (before as f32) / (total as f32);
|
||||||
|
return Some(glyph.x + offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns top of line segment where cursor should visually select
|
||||||
|
fn cursor_pos(cursor: Cursor, buf: &TextBuffer) -> Option<Vec2> {
|
||||||
|
let mut prev = None;
|
||||||
|
for run in buf
|
||||||
|
.layout_runs()
|
||||||
|
.skip_while(|r| r.line_i < cursor.line)
|
||||||
|
.take_while(|r| r.line_i == cursor.line)
|
||||||
|
{
|
||||||
|
prev = Some(vec2(run.line_w, run.line_top));
|
||||||
|
if let Some(pos) = index_x(&run, cursor.index) {
|
||||||
|
return Some(vec2(pos, run.line_top));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Modifiers {
|
||||||
|
pub shift: bool,
|
||||||
|
pub control: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Modifiers {
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.shift = false;
|
||||||
|
self.control = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TextInputResult {
|
||||||
|
Used,
|
||||||
|
Unused,
|
||||||
|
Unfocus,
|
||||||
|
Submit,
|
||||||
|
Copy(String),
|
||||||
|
Paste,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub enum TextSelection {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Pos(Cursor),
|
||||||
|
Span {
|
||||||
|
start: Cursor,
|
||||||
|
end: Cursor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextSelection {
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
match self {
|
||||||
|
TextSelection::None => (),
|
||||||
|
TextSelection::Pos(cursor) => {
|
||||||
|
cursor.line = 0;
|
||||||
|
cursor.index = 0;
|
||||||
|
cursor.affinity = Affinity::default();
|
||||||
|
}
|
||||||
|
TextSelection::Span { start: _, end: _ } => {
|
||||||
|
*self = TextSelection::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextInputResult {
|
||||||
|
pub fn unfocus(&self) -> bool {
|
||||||
|
matches!(self, TextInputResult::Unfocus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for TextEdit {
|
||||||
|
type Target = TextView;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for TextEdit {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ pub use build::*;
|
|||||||
pub use edit::*;
|
pub use edit::*;
|
||||||
|
|
||||||
use crate::{prelude::*, util::MutDetect};
|
use crate::{prelude::*, util::MutDetect};
|
||||||
use cosmic_text::{Attrs, Metrics, Shaping};
|
use cosmic_text::{Attrs, BufferLine, Cursor, Metrics, Shaping};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
pub const SHAPING: Shaping = Shaping::Advanced;
|
pub const SHAPING: Shaping = Shaping::Advanced;
|
||||||
@@ -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(),
|
||||||
@@ -34,6 +34,25 @@ impl TextView {
|
|||||||
hint,
|
hint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// region where the text should be draw
|
||||||
|
/// does not include extra height or width from weird unicode
|
||||||
|
pub fn region(&self) -> UiRegion {
|
||||||
|
self.tex()
|
||||||
|
.map(|t| t.size)
|
||||||
|
.unwrap_or(Vec2::ZERO)
|
||||||
|
.align(self.align)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tex_region(&self, tex: &RenderedText) -> UiRegion {
|
||||||
|
let region = tex.size.align(self.align);
|
||||||
|
let dims = tex.handle.size();
|
||||||
|
let mut region = region.offset(tex.top_left_offset);
|
||||||
|
region.x.end = region.x.start + UiScalar::abs(dims.x);
|
||||||
|
region.y.end = region.y.start + UiScalar::abs(dims.y);
|
||||||
|
region
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, ctx: &mut SizeCtx) -> RenderedText {
|
fn render(&mut self, ctx: &mut SizeCtx) -> RenderedText {
|
||||||
let width = if self.attrs.wrap {
|
let width = if self.attrs.wrap {
|
||||||
Some(ctx.px_size().x)
|
Some(ctx.px_size().x)
|
||||||
@@ -48,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;
|
||||||
@@ -61,27 +83,47 @@ impl TextView {
|
|||||||
self.tex.as_ref()
|
self.tex.as_ref()
|
||||||
}
|
}
|
||||||
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
if let Some(hint) = &self.hint
|
||||||
|
&& let [line] = &self.buf.lines[..]
|
||||||
|
&& line.text().is_empty()
|
||||||
|
{
|
||||||
|
ctx.width(hint)
|
||||||
|
} else {
|
||||||
Len::abs(self.render(ctx).size.x)
|
Len::abs(self.render(ctx).size.x)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
if let Some(hint) = &self.hint
|
||||||
|
&& let [line] = &self.buf.lines[..]
|
||||||
|
&& line.text().is_empty()
|
||||||
|
{
|
||||||
|
ctx.height(hint)
|
||||||
|
} else {
|
||||||
Len::abs(self.render(ctx).size.y)
|
Len::abs(self.render(ctx).size.y)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
||||||
let tex = self.render(&mut painter.size_ctx());
|
let tex = self.render(&mut painter.size_ctx());
|
||||||
let region = tex.size.align(self.align);
|
let region = self.tex_region(&tex);
|
||||||
let dims = tex.handle.size();
|
|
||||||
let mut region = region.offset(tex.top_left_offset);
|
|
||||||
region.x.end = region.x.start + UiScalar::abs(dims.x);
|
|
||||||
region.y.end = region.y.start + UiScalar::abs(dims.y);
|
|
||||||
if let Some(hint) = &self.hint
|
if let Some(hint) = &self.hint
|
||||||
&& let [line] = &self.buf.lines[..]
|
&& let [line] = &self.buf.lines[..]
|
||||||
&& line.text().is_empty()
|
&& line.text().is_empty()
|
||||||
{
|
{
|
||||||
painter.widget(hint);
|
painter.widget(hint);
|
||||||
}
|
} else {
|
||||||
painter.texture_within(&tex.handle, region);
|
painter.texture_within(&tex.handle, region);
|
||||||
|
}
|
||||||
region
|
region
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn content(&self) -> String {
|
||||||
|
self.buf
|
||||||
|
.lines
|
||||||
|
.iter()
|
||||||
|
.map(|l| l.text())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
@@ -97,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,
|
||||||
@@ -124,6 +166,16 @@ impl Widget for Text {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sort_cursors(a: Cursor, b: Cursor) -> (Cursor, Cursor) {
|
||||||
|
let start = a.min(b);
|
||||||
|
let end = a.max(b);
|
||||||
|
(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edit_line(line: &mut BufferLine, text: String) {
|
||||||
|
line.set_text(text, line.ending(), line.attrs_list().clone());
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for Text {
|
impl Deref for Text {
|
||||||
type Target = TextAttrs;
|
type Target = TextAttrs;
|
||||||
|
|
||||||
@@ -1,34 +1,22 @@
|
|||||||
use super::*;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use iris_macro::widget_trait;
|
||||||
|
|
||||||
pub trait CoreWidget<W, Tag> {
|
// these methods should "not require any context" (require unit) because they're in core
|
||||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>;
|
event_ctx!(());
|
||||||
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned>;
|
|
||||||
fn center(self) -> impl WidgetFn<Aligned>;
|
widget_trait! {
|
||||||
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W>;
|
pub trait CoreWidget;
|
||||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized>;
|
|
||||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
|
|
||||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
|
|
||||||
fn max_width(self, width: impl Into<Len>) -> impl WidgetFn<MaxSize>;
|
|
||||||
fn max_height(self, height: impl Into<Len>) -> impl WidgetFn<MaxSize>;
|
|
||||||
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset>;
|
|
||||||
fn scroll(self) -> impl WidgetIdFn<Scroll>;
|
|
||||||
fn masked(self) -> impl WidgetFn<Masked>;
|
|
||||||
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack>;
|
|
||||||
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|
||||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|
||||||
|ui| Pad {
|
|ui| Pad {
|
||||||
padding: padding.into(),
|
padding: padding.into(),
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned> {
|
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned> {
|
||||||
move |ui| Aligned {
|
move |ui| Aligned {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
align: align.into(),
|
align: align.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,10 +25,10 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
self.align(Align::CENTER)
|
self.align(Align::CENTER)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W::Widget> {
|
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<WL::Widget> {
|
||||||
|ui| {
|
|ui| {
|
||||||
let id = self.add(ui);
|
let id = self.add(ui);
|
||||||
ui.set_label(&id, label.into());
|
id.set_label(label);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +36,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> {
|
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> {
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
move |ui| Sized {
|
move |ui| Sized {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
x: Some(size.x),
|
x: Some(size.x),
|
||||||
y: Some(size.y),
|
y: Some(size.y),
|
||||||
}
|
}
|
||||||
@@ -57,7 +45,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
|
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |ui| MaxSize {
|
move |ui| MaxSize {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
x: Some(len),
|
x: Some(len),
|
||||||
y: None,
|
y: None,
|
||||||
}
|
}
|
||||||
@@ -66,7 +54,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
|
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |ui| MaxSize {
|
move |ui| MaxSize {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
x: None,
|
x: None,
|
||||||
y: Some(len),
|
y: Some(len),
|
||||||
}
|
}
|
||||||
@@ -75,7 +63,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |ui| Sized {
|
move |ui| Sized {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
x: Some(len),
|
x: Some(len),
|
||||||
y: None,
|
y: None,
|
||||||
}
|
}
|
||||||
@@ -84,7 +72,7 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |ui| Sized {
|
move |ui| Sized {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
x: None,
|
x: None,
|
||||||
y: Some(len),
|
y: Some(len),
|
||||||
}
|
}
|
||||||
@@ -92,16 +80,17 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
|
|
||||||
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> {
|
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> {
|
||||||
move |ui| Offset {
|
move |ui| Offset {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
amt: amt.into(),
|
amt: amt.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll(self) -> impl WidgetIdFn<Scroll> {
|
fn scroll(self) -> impl WidgetIdFn<Scroll> {
|
||||||
move |ui| {
|
move |ui| {
|
||||||
Scroll::new(self.add(ui).any(), Axis::Y)
|
Scroll::new(self.add(ui), Axis::Y)
|
||||||
.edit_on(CursorSense::Scroll, |w, data| {
|
.on(CursorSense::Scroll, |ctx| {
|
||||||
w.scroll(data.scroll_delta.y * 50.0);
|
let s = &mut *ctx.widget.get_mut();
|
||||||
|
s.scroll(ctx.data.scroll_delta.y * 50.0);
|
||||||
})
|
})
|
||||||
.add(ui)
|
.add(ui)
|
||||||
}
|
}
|
||||||
@@ -109,25 +98,38 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|||||||
|
|
||||||
fn masked(self) -> impl WidgetFn<Masked> {
|
fn masked(self) -> impl WidgetFn<Masked> {
|
||||||
move |ui| Masked {
|
move |ui| Masked {
|
||||||
inner: self.add(ui).any(),
|
inner: self.add(ui),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
|
fn background<T,>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
|
||||||
move |ui| Stack {
|
move |ui| Stack {
|
||||||
children: vec![w.add(ui).any(), self.add(ui).any()],
|
children: vec![w.add(ui), self.add(ui)],
|
||||||
size: StackSize::Child(1),
|
size: StackSize::Child(1),
|
||||||
offset: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack> {
|
fn foreground<T,>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
|
||||||
move |ui| Stack {
|
move |ui| Stack {
|
||||||
children: vec![self.add(ui).any()],
|
children: vec![self.add(ui), w.add(ui)],
|
||||||
size: StackSize::Child(0),
|
size: StackSize::Child(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layer_offset(self, offset: usize) -> impl WidgetFn<LayerOffset> {
|
||||||
|
move |ui| LayerOffset {
|
||||||
|
inner: self.add(ui),
|
||||||
offset,
|
offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_any(self) -> impl WidgetRet {
|
||||||
|
|ui| self.add(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ptr(self, ptr: &WidgetRef<WidgetPtr>, ui: &mut Ui) {
|
||||||
|
ptr.get_mut().inner = Some(self.add(ui));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
||||||
Reference in New Issue
Block a user