Compare commits
35 Commits
246caffb34
...
atomics
| Author | SHA1 | Date | |
|---|---|---|---|
| 123f3e6e2b | |||
| baaeb6b027 | |||
| 2537284372 | |||
| 76f75192d5 | |||
| 6156c66a20 | |||
| 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 |
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]
|
||||
name = "iris"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
default-run = "test"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
iris-core = { workspace = true }
|
||||
iris-macro = { workspace = true }
|
||||
cosmic-text = { workspace = true }
|
||||
unicode-segmentation = { workspace = true }
|
||||
winit = { workspace = true }
|
||||
arboard = { workspace = true, features = ["wayland-data-control"] }
|
||||
pollster = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
image = { workspace = true }
|
||||
|
||||
[workspace]
|
||||
members = ["core", "macro"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
pollster = "0.4.0"
|
||||
winit = "0.30.12"
|
||||
wgpu = "27.0.1"
|
||||
@@ -15,5 +33,6 @@ image = "0.25.6"
|
||||
cosmic-text = "0.15.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
fxhash = "0.2.1"
|
||||
arboard = { version = "3.6.1", features = ["wayland-data-control"] }
|
||||
|
||||
arboard = "3.6.1"
|
||||
iris-core = { path = "core" }
|
||||
iris-macro = { path = "macro" }
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
painter takes them in instead of (or in addition to) id
|
||||
then type wrapper widgets to contain them
|
||||
@@ -36,3 +37,5 @@ don't forget I'm streaming
|
||||
|
||||
tags
|
||||
vecs for each widget type?
|
||||
|
||||
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||
|
||||
12
core/Cargo.toml
Normal file
12
core/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "iris-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
winit = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
bytemuck ={ workspace = true }
|
||||
image = { workspace = true }
|
||||
cosmic-text = { workspace = true }
|
||||
fxhash = { workspace = true }
|
||||
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.weak(), input);
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
209
core/src/layout/event.rs
Normal file
209
core/src/layout/event.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
use crate::{
|
||||
layout::{LayerId, UiId, Widget, WidgetId, WidgetInstance, WidgetRef},
|
||||
util::{Handle, HashMap, RefGuardMut},
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
ops::DerefMut,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
};
|
||||
|
||||
pub trait Async: Send + Sync + 'static {}
|
||||
impl<T: Send + Sync + 'static> Async for T {}
|
||||
|
||||
pub trait Event: Sized + Async + Clone {
|
||||
type Data<'a> = ();
|
||||
type State: Sync + Send + Default = ();
|
||||
#[allow(unused_variables)]
|
||||
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// THIS SHOULD NOT BE OVERWRITTEN; the default impl runs this event
|
||||
fn run<Ctx: 'static, W>(id: WidgetRef<W>, data: &mut Self::Data<'_>, state: &mut Ctx) {
|
||||
Events::<Self, Ctx>::run(id.id(), data, state);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventAlias {
|
||||
type Event: Event;
|
||||
fn into_event(self) -> Self::Event;
|
||||
}
|
||||
|
||||
impl<E: Event> EventAlias for E {
|
||||
type Event = Self;
|
||||
|
||||
fn into_event(self) -> Self::Event {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
type EventData<E, Ctx> = (E, Arc<dyn for<'a> EventFn<Ctx, <E as Event>::Data<'a>>>);
|
||||
pub struct Events<E: Event, Ctx> {
|
||||
active: HashMap<UiId, HashMap<LayerId, HashMap<WidgetId, E::State>>>,
|
||||
map: HashMap<WidgetId, Vec<EventData<E, Ctx>>>,
|
||||
}
|
||||
|
||||
pub static EVENT_TYPES: LazyLock<Mutex<HashMap<TypeId, Handle<dyn EventManager>>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::default()));
|
||||
|
||||
pub trait EventManager: Any + Send + Sync {
|
||||
fn remove(&mut self, id: WidgetId) -> Box<dyn Any>;
|
||||
fn draw(&mut self, ui: UiId, inst: &WidgetInstance);
|
||||
fn undraw(&mut self, ui: UiId, inst: &WidgetInstance);
|
||||
}
|
||||
|
||||
impl Handle<dyn EventManager> {
|
||||
pub fn remove(&mut self, id: WidgetId) {
|
||||
let mut s = self.get_mut();
|
||||
// refer to s.remove documentation for why drop order
|
||||
let contents = s.remove(id);
|
||||
drop(s);
|
||||
drop(contents);
|
||||
}
|
||||
pub fn draw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
self.get_mut().draw(ui, inst);
|
||||
}
|
||||
pub fn undraw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
self.get_mut().undraw(ui, inst);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event, Ctx: 'static> EventManager for Events<E, Ctx> {
|
||||
/// so... there is a deadlock if this is called in WidgetData::drop
|
||||
/// and some function in here owns another WidgetId
|
||||
/// so for now you need to drop the lock on self, THEN drop the contents
|
||||
/// which is implemented for Handle<dyn EventManager>
|
||||
fn remove(&mut self, id: WidgetId) -> Box<dyn Any> {
|
||||
let contents = self.map.remove(&id);
|
||||
for ui in self.active.values_mut() {
|
||||
for layer in ui.values_mut() {
|
||||
layer.remove(&id);
|
||||
}
|
||||
}
|
||||
Box::new(contents)
|
||||
}
|
||||
fn draw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
self.active
|
||||
.entry(ui)
|
||||
.or_default()
|
||||
.entry(inst.layer)
|
||||
.or_default()
|
||||
.entry(inst.id)
|
||||
.or_default();
|
||||
}
|
||||
fn undraw(&mut self, ui: UiId, inst: &WidgetInstance) {
|
||||
// rust pls
|
||||
(|| {
|
||||
self.active
|
||||
.get_mut(&ui)?
|
||||
.get_mut(&inst.layer)?
|
||||
.remove(&inst.id);
|
||||
Some(())
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
pub type UiActive<State> = HashMap<LayerId, HashMap<WidgetId, State>>;
|
||||
|
||||
impl<E: Event, Ctx: 'static> Events<E, Ctx> {
|
||||
fn id() -> TypeId {
|
||||
TypeId::of::<Self>()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
map: Default::default(),
|
||||
active: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active(id: UiId) -> impl DerefMut<Target = UiActive<E::State>> {
|
||||
RefGuardMut::map(Self::get(), |s| s.active.entry(id).or_default())
|
||||
}
|
||||
|
||||
fn register_<W: Widget + ?Sized>(
|
||||
id: WidgetRef<W>,
|
||||
event: impl Into<E>,
|
||||
f: impl for<'a> EventFn<Ctx, <E as Event>::Data<'a>>,
|
||||
) {
|
||||
let Some(mut data) = id.data_mut() else {
|
||||
return;
|
||||
};
|
||||
data.event_managers.insert(Self::id());
|
||||
Self::get()
|
||||
.map
|
||||
.entry(id.id())
|
||||
.or_default()
|
||||
.push((event.into(), Arc::new(f)));
|
||||
}
|
||||
|
||||
pub fn register<W: Widget + ?Sized + 'static>(
|
||||
id: WidgetRef<W>,
|
||||
event: impl Into<E>,
|
||||
f: impl for<'a> EventIdFn<Ctx, <E as Event>::Data<'a>, W>,
|
||||
) {
|
||||
Self::register_(id, event, move |ctx| {
|
||||
f(EventIdCtx {
|
||||
widget: id,
|
||||
state: ctx.state,
|
||||
data: ctx.data,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn run(id: WidgetId, data: &mut E::Data<'_>, state: &mut Ctx) {
|
||||
let s = Self::get();
|
||||
if let Some(fs) = s.map.get(&id) {
|
||||
let fs = fs.clone();
|
||||
drop(s);
|
||||
for (e, f) in fs {
|
||||
if e.should_run(data) {
|
||||
f(EventCtx { state, data })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_all(data: &mut E::Data<'_>, state: &mut Ctx) {
|
||||
let s = Self::get();
|
||||
// TODO: should probably document that added events won't run same frame
|
||||
let mut map = s.map.clone();
|
||||
drop(s);
|
||||
for fs in map.values_mut() {
|
||||
for (e, f) in fs {
|
||||
if e.should_run(data) {
|
||||
f(EventCtx { state, data })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// this some bull
|
||||
fn get<'a>() -> RefGuardMut<'a, Self> {
|
||||
let any = EVENT_TYPES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(Self::id())
|
||||
.or_insert_with(|| Handle::from(Self::new()))
|
||||
.clone();
|
||||
unsafe { any.downcast() }.get_take_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventCtx<'a, Ctx, Data> {
|
||||
pub state: &'a mut Ctx,
|
||||
pub data: &'a mut Data,
|
||||
}
|
||||
|
||||
pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> {
|
||||
pub widget: WidgetRef<W>,
|
||||
pub state: &'a mut Ctx,
|
||||
pub data: &'a mut Data,
|
||||
}
|
||||
|
||||
pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + Async {}
|
||||
impl<F: Fn(EventCtx<Ctx, Data>) + Async, Ctx, Data> EventFn<Ctx, Data> for F {}
|
||||
|
||||
pub trait EventIdFn<Ctx, Data, W: ?Sized>: Fn(EventIdCtx<Ctx, Data, W>) + Async {}
|
||||
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + Async, Ctx, Data, W: ?Sized> EventIdFn<Ctx, Data, W> for F {}
|
||||
@@ -1,31 +1,24 @@
|
||||
mod color;
|
||||
mod attr;
|
||||
mod event;
|
||||
mod id;
|
||||
mod layer;
|
||||
mod module;
|
||||
mod num;
|
||||
mod orientation;
|
||||
mod painter;
|
||||
mod text;
|
||||
mod texture;
|
||||
mod primitive;
|
||||
mod ui;
|
||||
mod vec2;
|
||||
mod view;
|
||||
mod widget;
|
||||
mod widgets;
|
||||
|
||||
pub use color::*;
|
||||
pub use attr::*;
|
||||
pub use event::*;
|
||||
pub use id::*;
|
||||
pub use layer::*;
|
||||
pub use module::*;
|
||||
pub use num::*;
|
||||
pub use orientation::*;
|
||||
pub use painter::*;
|
||||
pub use text::*;
|
||||
pub use texture::*;
|
||||
pub use primitive::*;
|
||||
pub use ui::*;
|
||||
pub use vec2::*;
|
||||
pub use view::*;
|
||||
pub use widget::*;
|
||||
pub use widgets::*;
|
||||
|
||||
pub use crate::util::Vec2;
|
||||
pub type UiColor = Color<u8>;
|
||||
@@ -1,15 +1,15 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use crate::{
|
||||
layout::WidgetInstance,
|
||||
util::{HashMap, Id},
|
||||
layout::{WidgetId, WidgetInstance},
|
||||
util::HashMap,
|
||||
};
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub trait UiModule: Any {
|
||||
fn on_draw(&mut self, inst: &WidgetInstance) {}
|
||||
fn on_undraw(&mut self, inst: &WidgetInstance) {}
|
||||
fn on_remove(&mut self, id: &Id) {}
|
||||
fn on_remove(&mut self, id: &WidgetId) {}
|
||||
fn on_move(&mut self, inst: &WidgetInstance) {}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
pub trait Axis_ {
|
||||
}
|
||||
|
||||
impl std::ops::Not for Axis {
|
||||
type Output = Self;
|
||||
|
||||
@@ -41,6 +41,11 @@ impl Size {
|
||||
y: Len::ZERO,
|
||||
};
|
||||
|
||||
pub const REST: Self = Self {
|
||||
x: Len::REST,
|
||||
y: Len::REST,
|
||||
};
|
||||
|
||||
pub fn abs(v: Vec2) -> Self {
|
||||
Self {
|
||||
x: Len::abs(v.x),
|
||||
@@ -97,6 +102,12 @@ impl Len {
|
||||
rest: 0.0,
|
||||
};
|
||||
|
||||
pub const REST: Self = Self {
|
||||
abs: 0.0,
|
||||
rel: 0.0,
|
||||
rest: 1.0,
|
||||
};
|
||||
|
||||
pub fn apply_rest(&self) -> UiScalar {
|
||||
UiScalar {
|
||||
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
|
||||
@@ -3,8 +3,7 @@ mod axis;
|
||||
mod len;
|
||||
mod pos;
|
||||
|
||||
use super::vec2::*;
|
||||
|
||||
use super::Vec2;
|
||||
pub use align::*;
|
||||
pub use axis::*;
|
||||
pub use len::*;
|
||||
@@ -1,85 +1,104 @@
|
||||
use std::{marker::Unsize, sync::mpsc::Sender};
|
||||
|
||||
use crate::{
|
||||
layout::{
|
||||
Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, RenderedText,
|
||||
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets,
|
||||
Axis, LayerId, Len, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, Textures, UiId, UiRegion, UiVec2, Vec2, Widget, WidgetHandle, WidgetId,
|
||||
WidgetUpdate,
|
||||
},
|
||||
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||
util::{HashMap, HashSet, Id, TrackedArena},
|
||||
util::{HashMap, HashSet, RefMap, TrackedArena},
|
||||
};
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
pub struct Painter<'a, 'c> {
|
||||
ctx: &'a mut PainterCtx<'c>,
|
||||
widget: WidgetHandle,
|
||||
id: WidgetId,
|
||||
region: UiRegion,
|
||||
mask: MaskIdx,
|
||||
textures: Vec<TextureHandle>,
|
||||
primitives: Vec<PrimitiveHandle>,
|
||||
children: Vec<Id>,
|
||||
children_width: 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
|
||||
children: Vec<WidgetId>,
|
||||
children_width: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
children_height: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
pub layer: usize,
|
||||
id: Id,
|
||||
}
|
||||
|
||||
/// context for a painter; lets you draw and redraw widgets
|
||||
struct PainterCtx<'a> {
|
||||
pub widgets: &'a Widgets,
|
||||
pub active: &'a mut HashMap<Id, WidgetInstance>,
|
||||
pub layers: &'a mut Layers,
|
||||
ui_id: UiId,
|
||||
pub active: &'a mut HashMap<WidgetId, WidgetInstance>,
|
||||
pub layers: &'a mut PrimitiveLayers,
|
||||
pub textures: &'a mut Textures,
|
||||
pub masks: &'a mut TrackedArena<Mask, u32>,
|
||||
pub text: &'a mut TextData,
|
||||
pub output_size: Vec2,
|
||||
pub modules: &'a mut Modules,
|
||||
pub cache_width: HashMap<Id, (UiVec2, Len)>,
|
||||
pub cache_height: HashMap<Id, (UiVec2, Len)>,
|
||||
pub needs_redraw: HashSet<Id>,
|
||||
draw_started: HashSet<Id>,
|
||||
send: &'a Sender<WidgetUpdate>,
|
||||
pub cache_width: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
pub cache_height: HashMap<WidgetId, (UiVec2, Len)>,
|
||||
pub needs_redraw: HashSet<WidgetId>,
|
||||
draw_started: HashSet<WidgetId>,
|
||||
}
|
||||
|
||||
/// stores information for children about the highest level parent that needed their size
|
||||
/// so that they can redraw the parent if their size changes
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ResizeRef {
|
||||
x: Option<(Id, (UiVec2, Len))>,
|
||||
y: Option<(Id, (UiVec2, Len))>,
|
||||
x: Option<(WidgetId, (UiVec2, Len))>,
|
||||
y: Option<(WidgetId, (UiVec2, Len))>,
|
||||
}
|
||||
|
||||
/// important non rendering data for retained drawing
|
||||
#[derive(Debug)]
|
||||
pub struct WidgetInstance {
|
||||
pub id: Id,
|
||||
pub id: WidgetId,
|
||||
pub region: UiRegion,
|
||||
pub parent: Option<Id>,
|
||||
pub parent: Option<WidgetId>,
|
||||
pub textures: Vec<TextureHandle>,
|
||||
pub primitives: Vec<PrimitiveHandle>,
|
||||
pub children: Vec<Id>,
|
||||
pub children: Vec<WidgetId>,
|
||||
pub resize: ResizeRef,
|
||||
pub mask: MaskIdx,
|
||||
pub layer: usize,
|
||||
pub layer: LayerId,
|
||||
}
|
||||
|
||||
/// data to be stored in Ui to create PainterCtxs easily
|
||||
#[derive(Default)]
|
||||
/// TODO: actually use this LMAO
|
||||
pub struct PainterData {
|
||||
pub widgets: Widgets,
|
||||
pub active: HashMap<Id, WidgetInstance>,
|
||||
pub layers: Layers,
|
||||
ui_id: UiId,
|
||||
pub active: HashMap<WidgetId, WidgetInstance>,
|
||||
pub layers: PrimitiveLayers,
|
||||
pub textures: Textures,
|
||||
pub text: TextData,
|
||||
pub output_size: Vec2,
|
||||
pub modules: Modules,
|
||||
pub px_dependent: HashSet<Id>,
|
||||
pub px_dependent: HashSet<WidgetId>,
|
||||
pub masks: TrackedArena<Mask, u32>,
|
||||
send: Sender<WidgetUpdate>,
|
||||
}
|
||||
|
||||
impl PainterData {
|
||||
pub fn new(ui_id: UiId, send: Sender<WidgetUpdate>) -> Self {
|
||||
Self {
|
||||
ui_id,
|
||||
active: Default::default(),
|
||||
layers: Default::default(),
|
||||
textures: Default::default(),
|
||||
text: Default::default(),
|
||||
output_size: Default::default(),
|
||||
px_dependent: Default::default(),
|
||||
masks: Default::default(),
|
||||
send,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PainterCtx<'a> {
|
||||
/// redraws a widget that's currently active (drawn)
|
||||
/// can be called on something already drawn or removed,
|
||||
/// will just return if so
|
||||
pub fn redraw(&mut self, id: Id) {
|
||||
pub fn redraw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, widget: &WidgetHandle<W>) {
|
||||
let id = widget.id();
|
||||
self.needs_redraw.remove(&id);
|
||||
if self.draw_started.contains(&id) {
|
||||
return;
|
||||
@@ -107,17 +126,18 @@ impl<'a> PainterCtx<'a> {
|
||||
cache_height: &mut self.cache_height,
|
||||
text: self.text,
|
||||
textures: self.textures,
|
||||
widgets: self.widgets,
|
||||
outer: *outer,
|
||||
output_size: self.output_size,
|
||||
checked_width: &mut Default::default(),
|
||||
checked_height: &mut Default::default(),
|
||||
id,
|
||||
}
|
||||
.width_inner(id);
|
||||
if new_desired != *old_desired {
|
||||
.width(widget);
|
||||
if new_desired != *old_desired
|
||||
&& let Some(w) = rid.strong()
|
||||
{
|
||||
// unsure if I need to walk down the tree here
|
||||
self.redraw(*rid);
|
||||
self.redraw(&w);
|
||||
*old_desired = new_desired;
|
||||
if self.draw_started.contains(&id) {
|
||||
ret = true;
|
||||
@@ -133,16 +153,17 @@ impl<'a> PainterCtx<'a> {
|
||||
cache_height: &mut self.cache_height,
|
||||
text: self.text,
|
||||
textures: self.textures,
|
||||
widgets: self.widgets,
|
||||
outer: *outer,
|
||||
output_size: self.output_size,
|
||||
checked_width: &mut Default::default(),
|
||||
checked_height: &mut Default::default(),
|
||||
id,
|
||||
}
|
||||
.height_inner(id);
|
||||
if new_desired != *old_desired {
|
||||
self.redraw(*rid);
|
||||
.height(widget);
|
||||
if new_desired != *old_desired
|
||||
&& let Some(w) = rid.strong()
|
||||
{
|
||||
self.redraw(&w);
|
||||
*old_desired = new_desired;
|
||||
if self.draw_started.contains(&id) {
|
||||
ret = true;
|
||||
@@ -154,13 +175,13 @@ impl<'a> PainterCtx<'a> {
|
||||
return finish(self, resize);
|
||||
}
|
||||
|
||||
let Some(active) = self.remove(id) else {
|
||||
let Some(active) = self.remove(id, false) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.draw_inner(
|
||||
active.layer,
|
||||
id,
|
||||
widget,
|
||||
active.region,
|
||||
active.parent,
|
||||
active.mask,
|
||||
@@ -169,15 +190,17 @@ impl<'a> PainterCtx<'a> {
|
||||
finish(self, resize);
|
||||
}
|
||||
|
||||
fn draw_inner(
|
||||
fn draw_inner<W: Widget + ?Sized + Unsize<dyn Widget>>(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
id: Id,
|
||||
widget: &WidgetHandle<W>,
|
||||
region: UiRegion,
|
||||
parent: Option<Id>,
|
||||
parent: Option<WidgetId>,
|
||||
mask: MaskIdx,
|
||||
old_children: Option<Vec<Id>>,
|
||||
old_children: Option<Vec<WidgetId>>,
|
||||
) {
|
||||
let id = widget.id();
|
||||
widget.data_mut().send = Some(self.send.clone());
|
||||
// I have no idea if these checks work lol
|
||||
// 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
|
||||
@@ -208,7 +231,7 @@ impl<'a> PainterCtx<'a> {
|
||||
return;
|
||||
}
|
||||
// if not, then maintain resize and track old children to remove unneeded
|
||||
let active = self.remove(id).unwrap();
|
||||
let active = self.remove(id, false).unwrap();
|
||||
old_children = active.children;
|
||||
resize = active.resize;
|
||||
}
|
||||
@@ -220,6 +243,7 @@ impl<'a> PainterCtx<'a> {
|
||||
region,
|
||||
mask,
|
||||
layer,
|
||||
widget: widget.as_any(),
|
||||
id,
|
||||
textures: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
@@ -229,7 +253,7 @@ impl<'a> PainterCtx<'a> {
|
||||
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_height = painter.children_height;
|
||||
@@ -269,23 +293,20 @@ impl<'a> PainterCtx<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// update modules
|
||||
for m in self.modules.iter_mut() {
|
||||
m.on_draw(&instance);
|
||||
}
|
||||
// run events
|
||||
id.map_event_managers(|mut m| {
|
||||
m.draw(self.ui_id, &instance);
|
||||
});
|
||||
self.active.insert(id, instance);
|
||||
}
|
||||
|
||||
fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) {
|
||||
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
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);
|
||||
}
|
||||
active.region = active.region.outside(&from).within(&to);
|
||||
for m in self.modules.iter_mut() {
|
||||
m.on_move(active);
|
||||
}
|
||||
// children will not be changed, so this technically should not be needed
|
||||
// probably need unsafe
|
||||
let children = active.children.clone();
|
||||
@@ -295,7 +316,7 @@ impl<'a> PainterCtx<'a> {
|
||||
}
|
||||
|
||||
/// NOTE: instance textures are cleared and self.textures freed
|
||||
fn remove(&mut self, id: Id) -> Option<WidgetInstance> {
|
||||
fn remove(&mut self, id: WidgetId, undraw: bool) -> Option<WidgetInstance> {
|
||||
let mut inst = self.active.remove(&id);
|
||||
if let Some(inst) = &mut inst {
|
||||
for h in &inst.primitives {
|
||||
@@ -306,15 +327,17 @@ impl<'a> PainterCtx<'a> {
|
||||
}
|
||||
inst.textures.clear();
|
||||
self.textures.free();
|
||||
for m in self.modules.iter_mut() {
|
||||
m.on_undraw(inst);
|
||||
if undraw {
|
||||
id.map_event_managers(|mut m| {
|
||||
m.undraw(self.ui_id, inst);
|
||||
});
|
||||
}
|
||||
}
|
||||
inst
|
||||
}
|
||||
|
||||
fn remove_rec(&mut self, id: Id) -> Option<WidgetInstance> {
|
||||
let inst = self.remove(id);
|
||||
fn remove_rec(&mut self, id: WidgetId) -> Option<WidgetInstance> {
|
||||
let inst = self.remove(id, true);
|
||||
if let Some(inst) = &inst {
|
||||
for c in &inst.children {
|
||||
self.remove_rec(*c);
|
||||
@@ -325,16 +348,16 @@ impl<'a> PainterCtx<'a> {
|
||||
}
|
||||
|
||||
impl PainterData {
|
||||
fn ctx(&mut self, needs_redraw: HashSet<Id>) -> PainterCtx<'_> {
|
||||
fn ctx(&mut self, needs_redraw: HashSet<WidgetId>) -> PainterCtx<'_> {
|
||||
PainterCtx {
|
||||
widgets: &self.widgets,
|
||||
ui_id: self.ui_id,
|
||||
active: &mut self.active,
|
||||
layers: &mut self.layers,
|
||||
textures: &mut self.textures,
|
||||
text: &mut self.text,
|
||||
output_size: self.output_size,
|
||||
modules: &mut self.modules,
|
||||
masks: &mut self.masks,
|
||||
send: &self.send,
|
||||
cache_width: Default::default(),
|
||||
cache_height: Default::default(),
|
||||
draw_started: Default::default(),
|
||||
@@ -342,17 +365,19 @@ impl PainterData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, id: Id) {
|
||||
pub fn draw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetHandle<W>) {
|
||||
let mut ctx = self.ctx(Default::default());
|
||||
ctx.draw_started.clear();
|
||||
ctx.layers.clear();
|
||||
ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None);
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self, ids: HashSet<Id>) {
|
||||
pub fn redraw(&mut self, ids: HashSet<WidgetId>) {
|
||||
let mut ctx = self.ctx(ids);
|
||||
while let Some(&id) = ctx.needs_redraw.iter().next() {
|
||||
ctx.redraw(id);
|
||||
if let Some(w) = id.strong() {
|
||||
ctx.redraw(&w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,7 +387,7 @@ impl<'a, 'c> Painter<'a, 'c> {
|
||||
let h = self.ctx.layers.write(
|
||||
self.layer,
|
||||
PrimitiveInst {
|
||||
id: self.id,
|
||||
id: self.id(),
|
||||
primitive,
|
||||
region,
|
||||
mask_idx: self.mask,
|
||||
@@ -390,20 +415,28 @@ impl<'a, 'c> Painter<'a, 'c> {
|
||||
}
|
||||
|
||||
/// Draws a widget within this widget's region.
|
||||
pub fn widget<W>(&mut self, id: &WidgetId<W>) {
|
||||
pub fn widget<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetHandle<W>) {
|
||||
self.widget_at(id, self.region);
|
||||
}
|
||||
|
||||
/// Draws a widget somewhere within this one.
|
||||
/// Useful for drawing child widgets in select areas.
|
||||
pub fn widget_within<W>(&mut self, id: &WidgetId<W>, region: UiRegion) {
|
||||
pub fn widget_within<W: Widget + ?Sized + Unsize<dyn Widget>>(
|
||||
&mut self,
|
||||
id: &WidgetHandle<W>,
|
||||
region: UiRegion,
|
||||
) {
|
||||
self.widget_at(id, region.within(&self.region));
|
||||
}
|
||||
|
||||
fn widget_at<W>(&mut self, id: &WidgetId<W>, region: UiRegion) {
|
||||
self.children.push(id.id);
|
||||
fn widget_at<W: Widget + ?Sized + Unsize<dyn Widget>>(
|
||||
&mut self,
|
||||
id: &WidgetHandle<W>,
|
||||
region: UiRegion,
|
||||
) {
|
||||
self.children.push(id.id());
|
||||
self.ctx
|
||||
.draw_inner(self.layer, id.id, region, Some(self.id), self.mask, None);
|
||||
.draw_inner(self.layer, id, region, Some(self.id()), self.mask, None);
|
||||
}
|
||||
|
||||
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||
@@ -423,18 +456,21 @@ impl<'a, 'c> Painter<'a, 'c> {
|
||||
|
||||
/// returns (handle, offset from top left)
|
||||
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 {
|
||||
self.region
|
||||
}
|
||||
|
||||
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
|
||||
pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>) -> Size {
|
||||
self.size_ctx().size(id)
|
||||
}
|
||||
|
||||
pub fn len_axis<W>(&mut self, id: &WidgetId<W>, axis: Axis) -> Len {
|
||||
pub fn len_axis<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len {
|
||||
match axis {
|
||||
Axis::X => self.size_ctx().width(id),
|
||||
Axis::Y => self.size_ctx().height(id),
|
||||
@@ -443,16 +479,15 @@ impl<'a, 'c> Painter<'a, 'c> {
|
||||
|
||||
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
|
||||
SizeCtx {
|
||||
source: self.id(),
|
||||
id: self.id(),
|
||||
text: self.ctx.text,
|
||||
textures: self.ctx.textures,
|
||||
widgets: self.ctx.widgets,
|
||||
output_size: self.ctx.output_size,
|
||||
checked_width: &mut self.children_width,
|
||||
checked_height: &mut self.children_height,
|
||||
cache_width: &mut self.ctx.cache_width,
|
||||
cache_height: &mut self.ctx.cache_height,
|
||||
source: self.id,
|
||||
id: self.id,
|
||||
outer: self.region.size(),
|
||||
}
|
||||
}
|
||||
@@ -477,40 +512,39 @@ impl<'a, 'c> Painter<'a, 'c> {
|
||||
self.layer = self.ctx.layers.next(self.layer);
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &str {
|
||||
&self.ctx.widgets.data(&self.id).unwrap().label
|
||||
pub fn label(&self) -> RefMap<'_, String> {
|
||||
self.widget.get_label()
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Id {
|
||||
&self.id
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SizeCtx<'a> {
|
||||
pub text: &'a mut TextData,
|
||||
pub textures: &'a mut Textures,
|
||||
source: Id,
|
||||
widgets: &'a Widgets,
|
||||
cache_width: &'a mut HashMap<Id, (UiVec2, Len)>,
|
||||
cache_height: &'a mut HashMap<Id, (UiVec2, Len)>,
|
||||
checked_width: &'a mut HashMap<Id, (UiVec2, Len)>,
|
||||
checked_height: &'a mut HashMap<Id, (UiVec2, Len)>,
|
||||
source: WidgetId,
|
||||
cache_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
cache_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
checked_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
checked_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
|
||||
/// TODO: should this be pub? rn used for sized
|
||||
pub outer: UiVec2,
|
||||
output_size: Vec2,
|
||||
id: Id,
|
||||
id: WidgetId,
|
||||
}
|
||||
|
||||
impl SizeCtx<'_> {
|
||||
pub fn id(&self) -> &Id {
|
||||
pub fn id(&self) -> &WidgetId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &Id {
|
||||
pub fn source(&self) -> &WidgetId {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn width_inner(&mut self, id: Id) -> Len {
|
||||
pub fn width<W: Widget + ?Sized>(&mut self, widget: &WidgetHandle<W>) -> Len {
|
||||
// first check cache
|
||||
// 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
|
||||
@@ -521,11 +555,12 @@ impl SizeCtx<'_> {
|
||||
// return len;
|
||||
// }
|
||||
// store self vars that need to be maintained
|
||||
let id = widget.id();
|
||||
let self_outer = self.outer;
|
||||
let self_id = self.id;
|
||||
// get size of input 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
|
||||
self.outer = self_outer;
|
||||
self.id = self_id;
|
||||
@@ -535,17 +570,18 @@ impl SizeCtx<'_> {
|
||||
}
|
||||
|
||||
// TODO: should be refactored to share code w width_inner
|
||||
fn height_inner(&mut self, id: Id) -> Len {
|
||||
pub fn height<W: Widget + ?Sized>(&mut self, widget: &WidgetHandle<W>) -> Len {
|
||||
// if let Some(&(outer, len)) = self.cache_height.get(&id)
|
||||
// && outer == self.outer
|
||||
// {
|
||||
// self.checked_height.insert(id, (self.outer, len));
|
||||
// return len;
|
||||
// }
|
||||
let id = widget.id();
|
||||
let self_outer = self.outer;
|
||||
let self_id = self.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.id = self_id;
|
||||
self.cache_height.insert(id, (self.outer, len));
|
||||
@@ -553,22 +589,14 @@ impl SizeCtx<'_> {
|
||||
len
|
||||
}
|
||||
|
||||
pub fn width<W>(&mut self, id: &WidgetId<W>) -> 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 {
|
||||
pub fn len_axis<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len {
|
||||
match axis {
|
||||
Axis::X => self.width(id),
|
||||
Axis::Y => self.height(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
|
||||
pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetHandle<W>) -> Size {
|
||||
Size {
|
||||
x: self.width(id),
|
||||
y: self.height(id),
|
||||
@@ -584,10 +612,6 @@ impl SizeCtx<'_> {
|
||||
}
|
||||
|
||||
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
|
||||
self.text.draw(buffer, attrs, self.textures)
|
||||
}
|
||||
|
||||
pub fn label(&self, id: &Id) -> &String {
|
||||
self.widgets.label(id)
|
||||
self.text.get_mut().draw(buffer, attrs, self.textures)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#![allow(clippy::multiple_bound_locations)]
|
||||
|
||||
use std::marker::Destruct;
|
||||
|
||||
/// stored in linear for sane manipulation
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
||||
@@ -56,23 +58,47 @@ pub trait ColorNum {
|
||||
const MAX: Self;
|
||||
}
|
||||
|
||||
impl<T: ColorNum + F32Conversion> Color<T> {
|
||||
pub fn mul_rgb(self, amt: impl F32Conversion) -> Self {
|
||||
macro_rules! map_rgb {
|
||||
($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();
|
||||
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();
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn brighter(self, amt: f32) -> Self {
|
||||
self.map_rgb(|x| {
|
||||
pub const fn brighter(self, amt: f32) -> Self {
|
||||
map_rgb!(x, self, {
|
||||
let x = x.to();
|
||||
T::from(x + (1.0 - x) * amt)
|
||||
})
|
||||
@@ -2,11 +2,14 @@ use std::ops::{Index, IndexMut};
|
||||
|
||||
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
|
||||
|
||||
struct LayerNode {
|
||||
pub type LayerId = usize;
|
||||
|
||||
struct LayerNode<T> {
|
||||
next: Ptr,
|
||||
prev: Ptr,
|
||||
child: Option<Child>,
|
||||
data: Layer,
|
||||
depth: usize,
|
||||
data: T,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -21,26 +24,22 @@ enum Ptr {
|
||||
|
||||
/// TODO: currently this does not ever free layers
|
||||
/// is that realistically desired?
|
||||
pub struct Layers {
|
||||
vec: Vec<LayerNode>,
|
||||
pub struct Layers<T> {
|
||||
vec: Vec<LayerNode<T>>,
|
||||
/// index of last layer at top level (start at first = 0)
|
||||
last: usize,
|
||||
}
|
||||
|
||||
/// TODO: this can be replaced with Primitives itself atm
|
||||
#[derive(Default)]
|
||||
pub struct Layer {
|
||||
pub primitives: Primitives,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Child {
|
||||
head: usize,
|
||||
tail: usize,
|
||||
}
|
||||
|
||||
impl Layers {
|
||||
pub fn new() -> Layers {
|
||||
pub type PrimitiveLayers = Layers<Primitives>;
|
||||
|
||||
impl<T: Default> Layers<T> {
|
||||
pub fn new() -> Layers<T> {
|
||||
Self {
|
||||
vec: vec![LayerNode::head()],
|
||||
last: 0,
|
||||
@@ -52,7 +51,7 @@ impl Layers {
|
||||
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();
|
||||
self.vec.push(node);
|
||||
i
|
||||
@@ -63,9 +62,10 @@ impl Layers {
|
||||
return i;
|
||||
}
|
||||
let i_new = self.push(LayerNode::new(
|
||||
Layer::default(),
|
||||
T::default(),
|
||||
self.vec[i].next,
|
||||
Ptr::Next(i),
|
||||
self.vec[i].depth,
|
||||
));
|
||||
self.vec[i].next = Ptr::Next(i_new);
|
||||
self.vec[i_new].prev = Ptr::Next(i);
|
||||
@@ -82,9 +82,10 @@ impl Layers {
|
||||
return c.head;
|
||||
}
|
||||
let i_child = self.push(LayerNode::new(
|
||||
Layer::default(),
|
||||
T::default(),
|
||||
Ptr::Parent(i),
|
||||
Ptr::Parent(i),
|
||||
self.vec[i].depth + 1,
|
||||
));
|
||||
self.vec[i].child = Some(Child {
|
||||
head: i_child,
|
||||
@@ -93,103 +94,115 @@ impl Layers {
|
||||
i_child
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_> {
|
||||
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_, T> {
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Layers {
|
||||
type Output = Layer;
|
||||
impl<T> Index<usize> for Layers<T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&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 {
|
||||
&mut self.vec[index].data
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerNode {
|
||||
pub fn new(data: Layer, next: Ptr, prev: Ptr) -> Self {
|
||||
impl<T: Default> LayerNode<T> {
|
||||
pub fn new(data: T, next: Ptr, prev: Ptr, depth: usize) -> Self {
|
||||
Self {
|
||||
next,
|
||||
prev,
|
||||
child: None,
|
||||
data,
|
||||
depth,
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
inner: LayerIndexIterator<'a>,
|
||||
pub struct LayerIteratorMut<'a, T> {
|
||||
inner: LayerIndexIterator<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LayerIteratorMut<'a> {
|
||||
type Item = (usize, &'a mut Layer);
|
||||
impl<'a, T> Iterator for LayerIteratorMut<'a, T> {
|
||||
type Item = (usize, &'a mut T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let i = self.inner.next()?;
|
||||
// SAFETY: requires index iterator to work properly
|
||||
#[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))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for LayerIteratorMut<'a> {
|
||||
impl<'a, T> DoubleEndedIterator for LayerIteratorMut<'a, T> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let i = self.inner.next_back()?;
|
||||
// SAFETY: requires index iterator to work properly
|
||||
#[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))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LayerIteratorMut<'a> {
|
||||
fn new(vec: &'a mut Vec<LayerNode>, last: usize) -> Self {
|
||||
impl<'a, T> LayerIteratorMut<'a, T> {
|
||||
fn new(vec: &'a mut Vec<LayerNode<T>>, last: usize) -> Self {
|
||||
Self {
|
||||
inner: LayerIndexIterator::new(vec, last),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayerIndexIterator<'a> {
|
||||
pub struct LayerIndexIterator<'a, T> {
|
||||
next: 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;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -220,7 +233,7 @@ impl<'a> Iterator for LayerIndexIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
|
||||
impl<'a, T> DoubleEndedIterator for LayerIndexIterator<'a, T> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let ret_i = self.next_back?;
|
||||
let node = &self.vec[ret_i];
|
||||
@@ -242,8 +255,8 @@ impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LayerIndexIterator<'a> {
|
||||
fn new(vec: &'a Vec<LayerNode>, last: usize) -> Self {
|
||||
impl<'a, T> LayerIndexIterator<'a, T> {
|
||||
fn new(vec: &'a Vec<LayerNode<T>>, last: usize) -> Self {
|
||||
let mut last = last;
|
||||
while let Some(c) = vec[last].child {
|
||||
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 crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2};
|
||||
use crate::{
|
||||
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2},
|
||||
util::Handle,
|
||||
};
|
||||
use cosmic_text::{
|
||||
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
|
||||
SwashContent,
|
||||
@@ -12,13 +15,15 @@ pub mod text_lib {
|
||||
pub use cosmic_text::*;
|
||||
}
|
||||
|
||||
pub struct TextData {
|
||||
pub type TextData = Handle<TextDataInner>;
|
||||
|
||||
pub struct TextDataInner {
|
||||
pub font_system: FontSystem,
|
||||
pub swash_cache: SwashCache,
|
||||
glyph_cache: Vec<(Placement, CacheKey, Color)>,
|
||||
}
|
||||
|
||||
impl Default for TextData {
|
||||
impl Default for TextDataInner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_system: FontSystem::new(),
|
||||
@@ -35,7 +40,7 @@ pub struct TextAttrs {
|
||||
pub line_height: f32,
|
||||
pub family: Family<'static>,
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -63,7 +68,7 @@ impl Default for TextAttrs {
|
||||
Self {
|
||||
color: UiColor::WHITE,
|
||||
font_size: size,
|
||||
line_height: size * 1.2,
|
||||
line_height: size * LINE_HEIGHT_MULT,
|
||||
family: Family::SansSerif,
|
||||
wrap: false,
|
||||
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(
|
||||
&mut self,
|
||||
buffer: &mut TextBuffer,
|
||||
159
core/src/layout/ui.rs
Normal file
159
core/src/layout/ui.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use image::DynamicImage;
|
||||
|
||||
use crate::{
|
||||
layout::{
|
||||
IdLike, PainterData, PixelRegion, TextureHandle, Vec2, WidgetHandle, WidgetId,
|
||||
WidgetInstance, WidgetLike, WidgetUpdate,
|
||||
},
|
||||
util::{HashMap, HashSet, Id, StaticIdTracker},
|
||||
};
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
|
||||
static ID_TRACKER: StaticIdTracker = StaticIdTracker::new();
|
||||
pub type UiId = Id;
|
||||
|
||||
pub struct Ui {
|
||||
id: UiId,
|
||||
pub(crate) data: PainterData,
|
||||
root: Option<WidgetHandle>,
|
||||
updates: HashSet<WidgetId>,
|
||||
recv: Receiver<WidgetUpdate>,
|
||||
full_redraw: bool,
|
||||
resized: bool,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn id(&self) -> UiId {
|
||||
self.id
|
||||
}
|
||||
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
|
||||
self.root = Some(w.add(self));
|
||||
self.full_redraw = true;
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
|
||||
self.data.textures.add(image)
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
||||
self.data.output_size = size.into();
|
||||
self.resized = true;
|
||||
}
|
||||
|
||||
fn redraw_all(&mut self) {
|
||||
for (id, inst) in self.data.active.drain() {
|
||||
id.map_event_managers(|mut m| {
|
||||
m.undraw(self.id, &inst);
|
||||
});
|
||||
}
|
||||
// free before bc nothing should exist
|
||||
self.free();
|
||||
if let Some(root) = &self.root {
|
||||
self.data.draw(root);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) -> bool {
|
||||
for id in self.recv.try_iter() {
|
||||
self.updates.insert(id);
|
||||
}
|
||||
if self.full_redraw {
|
||||
self.redraw_all();
|
||||
self.full_redraw = false;
|
||||
true
|
||||
} else if self.resized {
|
||||
self.resized = false;
|
||||
self.redraw_all();
|
||||
true
|
||||
} else if !self.updates.is_empty() {
|
||||
self.redraw_updates();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_updates(&mut self) {
|
||||
self.data.redraw(std::mem::take(&mut self.updates));
|
||||
self.free();
|
||||
}
|
||||
|
||||
/// free any resources that don't have references anymore
|
||||
fn free(&mut self) {
|
||||
self.data.textures.free();
|
||||
}
|
||||
|
||||
pub fn needs_redraw(&self) -> bool {
|
||||
self.full_redraw || !self.updates.is_empty()
|
||||
}
|
||||
|
||||
pub fn active_widgets(&self) -> usize {
|
||||
self.data.active.len()
|
||||
}
|
||||
|
||||
pub fn active(&self) -> &HashMap<WidgetId, WidgetInstance> {
|
||||
&self.data.active
|
||||
}
|
||||
|
||||
pub fn debug_layers(&self) {
|
||||
for ((idx, depth), primitives) in self.data.layers.iter_depth() {
|
||||
let indent = " ".repeat(depth * 2);
|
||||
let len = primitives.instances().len();
|
||||
print!("{indent}{idx}: {len} primitives");
|
||||
if len >= 1 {
|
||||
print!(" ({})", primitives.instances()[0].binding);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
|
||||
let region = self.data.active.get(&id.id())?.region;
|
||||
Some(region.to_px(self.data.output_size))
|
||||
}
|
||||
|
||||
pub fn debug(&self, label: &str) -> impl Iterator<Item = &WidgetInstance> {
|
||||
self.data.active.iter().filter_map(move |(id, inst)| {
|
||||
let widget = id.strong().unwrap();
|
||||
if widget.get_label().as_str() == label {
|
||||
Some(inst)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &PainterData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self) -> &mut PainterData {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Ui {
|
||||
fn default() -> Self {
|
||||
let (send, recv) = channel();
|
||||
let id = ID_TRACKER.next();
|
||||
Self {
|
||||
id,
|
||||
data: PainterData::new(id, send),
|
||||
root: Default::default(),
|
||||
updates: Default::default(),
|
||||
full_redraw: false,
|
||||
recv,
|
||||
resized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ui {
|
||||
fn drop(&mut self) {
|
||||
ID_TRACKER.free(self.id);
|
||||
}
|
||||
}
|
||||
17
core/src/layout/view.rs
Normal file
17
core/src/layout/view.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::layout::{Widget, WidgetHandle, WidgetLike};
|
||||
use std::marker::Unsize;
|
||||
|
||||
pub trait WidgetView {
|
||||
type Widget: Widget + ?Sized + Unsize<dyn Widget> = dyn Widget;
|
||||
fn view(self) -> WidgetHandle<Self::Widget>;
|
||||
}
|
||||
|
||||
pub struct ViewTag;
|
||||
|
||||
impl<WV: WidgetView> WidgetLike<ViewTag> for WV {
|
||||
type Widget = WV::Widget;
|
||||
|
||||
fn add(self, _ui: &mut super::Ui) -> WidgetHandle<Self::Widget> {
|
||||
self.view()
|
||||
}
|
||||
}
|
||||
31
core/src/layout/widget/data.rs
Normal file
31
core/src/layout/widget/data.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::{any::TypeId, sync::mpsc::Sender};
|
||||
|
||||
use crate::{
|
||||
layout::{EVENT_TYPES, EventManager, WIDGETS, WidgetId, WidgetUpdate},
|
||||
util::{Handle, HashSet},
|
||||
};
|
||||
|
||||
pub struct WidgetData<W: ?Sized> {
|
||||
pub id: WidgetId,
|
||||
pub send: Option<Sender<WidgetUpdate>>,
|
||||
pub label: String,
|
||||
pub event_managers: HashSet<TypeId>,
|
||||
pub widget: W,
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetData<W> {
|
||||
pub(crate) fn event_managers(&self) -> impl Iterator<Item = Handle<dyn EventManager>> {
|
||||
self.event_managers
|
||||
.iter()
|
||||
.map(|id| EVENT_TYPES.lock().unwrap().get_mut(id).unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Drop for WidgetData<W> {
|
||||
fn drop(&mut self) {
|
||||
WIDGETS.remove(self.id);
|
||||
for mut m in self.event_managers() {
|
||||
m.remove(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
244
core/src/layout/widget/handle.rs
Normal file
244
core/src/layout/widget/handle.rs
Normal file
@@ -0,0 +1,244 @@
|
||||
use std::{marker::Unsize, mem::MaybeUninit, ops::CoerceUnsized};
|
||||
|
||||
use crate::{
|
||||
layout::{EventManager, IdFnTag, IdTag, Ui, WIDGETS, Widget, WidgetData, WidgetLike},
|
||||
util::{
|
||||
Handle, MappedRefGuard, MappedRefGuardMut, Ref, RefGuard, RefGuardMut, RefMap, RefMapMut,
|
||||
RefMut, WeakHandle,
|
||||
},
|
||||
};
|
||||
|
||||
pub(super) type SlotTy = u32;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct WidgetId {
|
||||
pub(super) i: SlotTy,
|
||||
pub(super) genr: SlotTy,
|
||||
}
|
||||
|
||||
/// A strong handle to a widget. Cannot be cloned, only one may exist
|
||||
/// Use .weak() to obtain weak references to this
|
||||
pub struct WidgetHandle<W: ?Sized = dyn Widget>(WidgetId, Handle<WidgetData<W>>);
|
||||
/// A weak handle to a widget. Implements Copy so you can easily pass into closures
|
||||
pub struct WidgetRef<W: ?Sized = dyn Widget>(WidgetId, *const W);
|
||||
|
||||
pub type WidgetUpdate = WidgetId;
|
||||
|
||||
impl<W: Widget> WidgetHandle<W> {
|
||||
pub(super) fn new(id: WidgetId, widget: W) -> Self {
|
||||
let mut label = std::any::type_name::<W>().to_string();
|
||||
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
|
||||
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
|
||||
}
|
||||
Self(
|
||||
id,
|
||||
WidgetData {
|
||||
id,
|
||||
widget,
|
||||
send: None,
|
||||
label,
|
||||
event_managers: Default::default(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetHandle<W> {
|
||||
pub(super) fn weak_inner(&self) -> WeakHandle<WidgetData<W>> {
|
||||
self.1.weak()
|
||||
}
|
||||
|
||||
pub fn any(self) -> WidgetHandle<dyn Widget> {
|
||||
self
|
||||
}
|
||||
|
||||
/// DO NOT CALL OTHER THAN TEMP IN PAINTER
|
||||
pub(crate) fn as_any(&self) -> WidgetHandle<dyn Widget> {
|
||||
WidgetHandle(self.0, self.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetHandle<W> {
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn get(&self) -> RefMap<'_, W> {
|
||||
Ref::map(self.1.get(), |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> RefMapMut<'_, W> {
|
||||
let inner = self.1.get_mut();
|
||||
if let Some(send) = &inner.send {
|
||||
let _ = send.send(self.0);
|
||||
}
|
||||
RefMut::map(inner, |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn data(&self) -> Ref<'_, WidgetData<W>> {
|
||||
self.1.get()
|
||||
}
|
||||
|
||||
pub(crate) fn data_mut(&self) -> RefMut<'_, WidgetData<W>> {
|
||||
self.1.get_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut_quiet(&self) -> RefMapMut<'_, W> {
|
||||
RefMut::map(self.1.get_mut(), |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn get_label(&self) -> RefMap<'_, String> {
|
||||
Ref::map(self.1.get(), |i| &mut i.label)
|
||||
}
|
||||
|
||||
pub fn set_label(&self, label: impl Into<String>) {
|
||||
self.1.get_mut().label = label.into();
|
||||
}
|
||||
|
||||
/// TODO: THIS SHOULD ALWAYS BE 1, remove this probably (weak count might be nice though)
|
||||
pub fn refs(&self) -> usize {
|
||||
self.1.refs()
|
||||
}
|
||||
|
||||
pub fn weak(&self) -> WidgetRef<W> {
|
||||
WidgetRef::new(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub type WRef<'a, W> = MappedRefGuard<'a, WidgetData<W>, W>;
|
||||
pub type WRefMut<'a, W> = MappedRefGuardMut<'a, WidgetData<W>, W>;
|
||||
|
||||
impl<W: Widget> WidgetRef<W> {
|
||||
fn handle(&self) -> Handle<WidgetData<W>> {
|
||||
WIDGETS.get_type(self.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get<'a>(&self) -> WRef<'a, W> {
|
||||
RefGuard::map(self.handle().get_take(), |i| &mut i.widget)
|
||||
}
|
||||
|
||||
pub fn get_mut<'a>(&self) -> WRefMut<'a, W> {
|
||||
let inner = self.handle().get_take_mut();
|
||||
|
||||
if let Some(send) = &inner.send {
|
||||
let _ = send.send(self.0);
|
||||
}
|
||||
RefGuardMut::map(inner, |i| &mut i.widget)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized + Widget> WidgetRef<W> {
|
||||
pub fn data<'a>(&self) -> Option<RefGuard<'a, WidgetData<dyn Widget>>> {
|
||||
Some(WIDGETS.get(self.0)?.get_take())
|
||||
}
|
||||
|
||||
pub(crate) fn data_mut(&self) -> Option<RefGuardMut<'_, WidgetData<dyn Widget>>> {
|
||||
Some(WIDGETS.get(self.0)?.get_take_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetRef<W> {
|
||||
pub fn any(self) -> WidgetRef<dyn Widget> {
|
||||
WidgetRef::new(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> WidgetRef<W> {
|
||||
fn new(id: WidgetId) -> Self {
|
||||
Self(id, unsafe { MaybeUninit::zeroed().assume_init() })
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WidgetId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetId {
|
||||
/// THIS SHOULD ONLY BE CALLED FOR TEMP STUFF DURING PAINTING
|
||||
pub(crate) fn strong(self) -> Option<WidgetHandle> {
|
||||
Some(WidgetHandle(self, WIDGETS.get(self)?))
|
||||
}
|
||||
|
||||
pub fn weak(self) -> WidgetRef {
|
||||
WidgetRef::new(self)
|
||||
}
|
||||
|
||||
pub(crate) fn map_event_managers(&self, f: impl Fn(Handle<dyn EventManager>)) {
|
||||
let Some(data) = self.weak().data() else {
|
||||
return;
|
||||
};
|
||||
for m in data.event_managers() {
|
||||
f(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetIdFn<W: ?Sized = dyn Widget>: FnOnce(&mut Ui) -> WidgetHandle<W> {}
|
||||
impl<W: ?Sized, F: FnOnce(&mut Ui) -> WidgetHandle<W>> WidgetIdFn<W> for F {}
|
||||
|
||||
pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetHandle {}
|
||||
impl<F: FnOnce(&mut Ui) -> WidgetHandle> WidgetRet for F {}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static> WidgetLike<IdTag> for WidgetHandle<W> {
|
||||
type Widget = W;
|
||||
fn add(self, _: &mut Ui) -> WidgetHandle<W> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static, F: FnOnce(&mut Ui) -> WidgetHandle<W>>
|
||||
WidgetLike<IdFnTag> for F
|
||||
{
|
||||
type Widget = W;
|
||||
fn add(self, ui: &mut Ui) -> WidgetHandle<W> {
|
||||
self(ui)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IdLike<W> {
|
||||
fn id(&self) -> WidgetId;
|
||||
}
|
||||
|
||||
impl<W> IdLike<W> for WidgetHandle<W> {
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> IdLike<W> for WidgetRef<W> {
|
||||
fn id(&self) -> WidgetId {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetHandle<U>> for WidgetHandle<T> {}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<T> {}
|
||||
|
||||
impl<W> PartialEq for WidgetHandle<W> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id() == other.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> std::fmt::Debug for WidgetHandle<W> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.id().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Clone for WidgetRef<W> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ?Sized> Copy for WidgetRef<W> {}
|
||||
|
||||
impl<W: ?Sized> PartialEq for WidgetRef<W> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
unsafe impl<W: ?Sized> Send for WidgetRef<W> {}
|
||||
unsafe impl<W: ?Sized> Sync for WidgetRef<W> {}
|
||||
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) -> WidgetHandle<Self::Widget>;
|
||||
|
||||
fn with_id<W2>(
|
||||
self,
|
||||
f: impl FnOnce(&mut Ui, WidgetHandle<Self::Widget>) -> WidgetHandle<W2>,
|
||||
) -> impl WidgetIdFn<W2>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
move |ui| {
|
||||
let id = self.add(ui);
|
||||
f(ui, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_root(self, ui: &mut Ui)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ui.set_root(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WidgetArr<const LEN: usize> {
|
||||
pub arr: [WidgetHandle; LEN],
|
||||
}
|
||||
|
||||
impl<const LEN: usize> WidgetArr<LEN> {
|
||||
pub fn new(arr: [WidgetHandle; LEN]) -> Self {
|
||||
Self { arr }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetArrLike<const LEN: usize, Tag> {
|
||||
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN>;
|
||||
}
|
||||
|
||||
impl<const LEN: usize> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN> {
|
||||
fn ui(self, _: &mut Ui) -> WidgetArr<LEN> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// I hate this language it's so bad why do I even use it
|
||||
macro_rules! impl_widget_arr {
|
||||
($n:expr;$($W:ident)*) => {
|
||||
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
|
||||
};
|
||||
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
|
||||
impl<$($W: WidgetLike<$Tag>,$Tag,)*> WidgetArrLike<$n, ($($Tag,)*)> for ($($W,)*) {
|
||||
fn ui(self, ui: &mut Ui) -> WidgetArr<$n> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($W,)*) = self;
|
||||
WidgetArr::new(
|
||||
[$($W.add(ui),)*],
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_widget_arr!(1;A);
|
||||
impl_widget_arr!(2;A B);
|
||||
impl_widget_arr!(3;A B C);
|
||||
impl_widget_arr!(4;A B C D);
|
||||
impl_widget_arr!(5;A B C D E);
|
||||
impl_widget_arr!(6;A B C D E F);
|
||||
impl_widget_arr!(7;A B C D E F G);
|
||||
impl_widget_arr!(8;A B C D E F G H);
|
||||
impl_widget_arr!(9;A B C D E F G H I);
|
||||
impl_widget_arr!(10;A B C D E F G H I J);
|
||||
impl_widget_arr!(11;A B C D E F G H I J K);
|
||||
impl_widget_arr!(12;A B C D E F G H I J K L);
|
||||
65
core/src/layout/widget/mod.rs
Normal file
65
core/src/layout/widget/mod.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
mod data;
|
||||
mod handle;
|
||||
mod like;
|
||||
mod tag;
|
||||
mod widgets;
|
||||
|
||||
pub use data::*;
|
||||
pub use handle::*;
|
||||
pub use like::*;
|
||||
pub use tag::*;
|
||||
pub use widgets::*;
|
||||
|
||||
use crate::layout::{Len, Painter, SizeCtx, Ui};
|
||||
|
||||
pub trait Widget: 'static + Send + Sync {
|
||||
fn draw(&mut self, painter: &mut Painter);
|
||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
|
||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
|
||||
}
|
||||
|
||||
impl Widget for () {
|
||||
fn draw(&mut self, _: &mut Painter) {}
|
||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||
Len::ZERO
|
||||
}
|
||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
||||
Len::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that returns a widget given a UI.
|
||||
/// Useful for defining trait functions on widgets that create a parent widget so that the children
|
||||
/// don't need to be IDs yet
|
||||
pub trait WidgetFn<W: Widget + ?Sized>: FnOnce(&mut Ui) -> W {}
|
||||
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetFn<W> for F {}
|
||||
|
||||
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetLike<FnTag> for F {
|
||||
type Widget = W;
|
||||
fn add(self, ui: &mut Ui) -> WidgetHandle<W> {
|
||||
self(ui).add(ui)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget> WidgetLike<WidgetTag> for W {
|
||||
type Widget = W;
|
||||
fn add(self, _: &mut Ui) -> WidgetHandle<W> {
|
||||
WIDGETS.insert(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetOption {
|
||||
fn get(self, ui: &mut Ui) -> Option<WidgetHandle>;
|
||||
}
|
||||
|
||||
impl WidgetOption for () {
|
||||
fn get(self, _: &mut Ui) -> Option<WidgetHandle> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce(&mut Ui) -> Option<WidgetHandle>> WidgetOption for F {
|
||||
fn get(self, ui: &mut Ui) -> Option<WidgetHandle> {
|
||||
self(ui)
|
||||
}
|
||||
}
|
||||
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;
|
||||
100
core/src/layout/widget/widgets.rs
Normal file
100
core/src/layout/widget/widgets.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::{
|
||||
layout::{
|
||||
Widget, WidgetData, WidgetHandle,
|
||||
widget::{SlotTy, WidgetId},
|
||||
},
|
||||
util::{Handle, WeakHandle},
|
||||
};
|
||||
|
||||
pub(crate) static WIDGETS: Widgets = Widgets::new();
|
||||
pub(crate) struct Widgets(Mutex<Inner>);
|
||||
|
||||
pub fn total_widgets() -> usize {
|
||||
WIDGETS.len()
|
||||
}
|
||||
|
||||
pub struct WidgetSlot {
|
||||
genr: SlotTy,
|
||||
data: WeakHandle<WidgetData<dyn Widget + Sync + Send>>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
cur_id: SlotTy,
|
||||
vec: Vec<WidgetSlot>,
|
||||
free: Vec<SlotTy>,
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
pub const fn new() -> Self {
|
||||
Self(Mutex::new(Inner {
|
||||
cur_id: 0,
|
||||
vec: Vec::new(),
|
||||
free: Vec::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn expect(&self) -> MutexGuard<'_, Inner> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_type<W: 'static>(&self, id: WidgetId) -> Option<Handle<WidgetData<W>>> {
|
||||
let slot = &self.expect().vec[id.i as usize];
|
||||
if slot.genr != id.genr {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { slot.data.clone().downcast() }.clone().strong()?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, id: WidgetId) -> Option<Handle<WidgetData<dyn Widget>>> {
|
||||
let slot = &self.expect().vec[id.i as usize];
|
||||
if slot.genr != id.genr {
|
||||
None
|
||||
} else {
|
||||
Some(slot.data.clone().strong()?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert<W: Widget>(&self, widget: W) -> WidgetHandle<W> {
|
||||
let mut s = self.expect();
|
||||
let id = s
|
||||
.free
|
||||
.pop()
|
||||
.map(|i| {
|
||||
assert!(s.vec[i as usize].data.dropped());
|
||||
WidgetId {
|
||||
i,
|
||||
genr: s.vec[i as usize].genr,
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let i = s.cur_id;
|
||||
s.cur_id += 1;
|
||||
WidgetId { i, genr: 0 }
|
||||
});
|
||||
let handle = WidgetHandle::new(id, widget);
|
||||
let slot = WidgetSlot {
|
||||
genr: id.genr,
|
||||
data: handle.weak_inner(),
|
||||
};
|
||||
if id.i == s.vec.len() as SlotTy {
|
||||
s.vec.push(slot);
|
||||
} else {
|
||||
s.vec[id.i as usize] = slot;
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: WidgetId) {
|
||||
let mut s = self.expect();
|
||||
s.vec[id.i as usize].genr += 1;
|
||||
s.free.push(id.i);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let s = self.expect();
|
||||
s.vec.len() - s.free.len()
|
||||
}
|
||||
}
|
||||
21
core/src/lib.rs
Normal file
21
core/src/lib.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
#![feature(macro_metavar_expr_concat)]
|
||||
#![feature(const_ops)]
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(const_convert)]
|
||||
#![feature(map_try_insert)]
|
||||
#![feature(unboxed_closures)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(const_cmp)]
|
||||
#![feature(const_destruct)]
|
||||
#![feature(portable_simd)]
|
||||
#![feature(associated_type_defaults)]
|
||||
#![feature(unsize)]
|
||||
#![feature(coerce_unsized)]
|
||||
#![feature(mapped_lock_guards)]
|
||||
#![feature(ptr_metadata)]
|
||||
|
||||
pub mod layout;
|
||||
pub mod render;
|
||||
pub mod util;
|
||||
|
||||
pub use image;
|
||||
@@ -44,7 +44,7 @@ impl MaskIdx {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Mask {
|
||||
pub region: UiRegion,
|
||||
}
|
||||
@@ -22,7 +22,7 @@ pub use primitive::*;
|
||||
|
||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||
|
||||
pub struct UiRenderer {
|
||||
pub struct UiRenderNode {
|
||||
uniform_group: BindGroup,
|
||||
primitive_layout: BindGroupLayout,
|
||||
rsc_layout: BindGroupLayout,
|
||||
@@ -43,7 +43,7 @@ struct RenderLayer {
|
||||
primitive_group: BindGroup,
|
||||
}
|
||||
|
||||
impl UiRenderer {
|
||||
impl UiRenderNode {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
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) {
|
||||
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);
|
||||
let primitives = &mut ulayer.primitives;
|
||||
for change in primitives.apply_free() {
|
||||
if let Some(inst) = ui.data.active.get_mut(&change.id) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
@@ -97,15 +96,19 @@ impl UiRenderer {
|
||||
device,
|
||||
&self.primitive_layout,
|
||||
rlayer.primitives.buffers(),
|
||||
)
|
||||
);
|
||||
primitives.updated = false;
|
||||
}
|
||||
}
|
||||
if self.textures.update(&mut ui.data.textures) {
|
||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks)
|
||||
}
|
||||
let mut changed = false;
|
||||
changed |= self.textures.update(&mut ui.data.textures);
|
||||
if ui.data.masks.changed {
|
||||
ui.data.masks.changed = false;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::{
|
||||
layout::{Color, UiRegion},
|
||||
layout::{Color, UiRegion, WidgetId},
|
||||
render::{
|
||||
ArrBuf,
|
||||
data::{MaskIdx, PrimitiveInstance},
|
||||
},
|
||||
util::Id,
|
||||
};
|
||||
use bytemuck::Pod;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use wgpu::*;
|
||||
|
||||
pub struct Primitives {
|
||||
instances: Vec<PrimitiveInstance>,
|
||||
assoc: Vec<Id>,
|
||||
assoc: Vec<WidgetId>,
|
||||
data: PrimitiveData,
|
||||
free: Vec<usize>,
|
||||
pub updated: bool,
|
||||
@@ -40,7 +38,7 @@ macro_rules! primitives {
|
||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
||||
#[derive(Default)]
|
||||
pub struct PrimitiveData {
|
||||
$($name: PrimitiveVec<$ty>,)*
|
||||
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
||||
}
|
||||
|
||||
pub struct PrimitiveBuffers {
|
||||
@@ -99,7 +97,7 @@ macro_rules! primitives {
|
||||
}
|
||||
|
||||
pub struct PrimitiveInst<P> {
|
||||
pub id: Id,
|
||||
pub id: WidgetId,
|
||||
pub primitive: P,
|
||||
pub region: UiRegion,
|
||||
pub mask_idx: MaskIdx,
|
||||
@@ -116,6 +114,7 @@ impl Primitives {
|
||||
mask_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
let vec = P::vec(&mut self.data);
|
||||
let i = vec.add(primitive);
|
||||
let inst = PrimitiveInstance {
|
||||
@@ -153,6 +152,7 @@ impl Primitives {
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self.updated = true;
|
||||
self.data.free(h.binding, h.data_idx);
|
||||
self.free.push(h.inst_idx);
|
||||
self.instances[h.inst_idx].mask_idx
|
||||
@@ -173,7 +173,7 @@ impl Primitives {
|
||||
}
|
||||
|
||||
pub struct PrimitiveChange {
|
||||
pub id: Id,
|
||||
pub id: WidgetId,
|
||||
pub old: usize,
|
||||
pub new: usize,
|
||||
}
|
||||
@@ -7,7 +7,7 @@ pub struct Arena<T, I> {
|
||||
tracker: IdTracker<I>,
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> Arena<T, I> {
|
||||
impl<T, I: const IdNum> Arena<T, I> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Vec::new(),
|
||||
@@ -36,7 +36,7 @@ impl<T, I: IdNum> Arena<T, I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> Default for Arena<T, I> {
|
||||
impl<T, I: const IdNum> Default for Arena<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
@@ -48,7 +48,7 @@ pub struct TrackedArena<T, I> {
|
||||
pub changed: bool,
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> TrackedArena<T, I> {
|
||||
impl<T, I: const IdNum> TrackedArena<T, I> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arena::default(),
|
||||
@@ -86,7 +86,7 @@ impl<T, I: IdNum> TrackedArena<T, I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: IdNum> Default for TrackedArena<T, I> {
|
||||
impl<T, I: const IdNum> Default for TrackedArena<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
227
core/src/util/handle.rs
Normal file
227
core/src/util/handle.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
//! A helper type for Arc<Mutex<T>>
|
||||
//! Currently there are mut versions (of Ref stuff) which don't do anything different under the hood,
|
||||
//! leaving them in for now in case I decide RwLock is a desired feature
|
||||
|
||||
use std::{
|
||||
marker::Unsize,
|
||||
ops::{CoerceUnsized, Deref, DerefMut},
|
||||
sync::{Arc, MappedMutexGuard, Mutex, MutexGuard, Weak},
|
||||
};
|
||||
|
||||
pub struct Handle<T: ?Sized>(Arc<Mutex<T>>);
|
||||
pub struct WeakHandle<T: ?Sized>(Weak<Mutex<T>>);
|
||||
|
||||
pub type Ref<'a, T> = MutexGuard<'a, T>;
|
||||
pub type RefMut<'a, T> = MutexGuard<'a, T>;
|
||||
|
||||
pub type RefMap<'a, T> = MappedMutexGuard<'a, T>;
|
||||
pub type RefMapMut<'a, T> = MappedMutexGuard<'a, T>;
|
||||
|
||||
impl<T: ?Sized> Handle<T> {
|
||||
pub fn get(&self) -> Ref<'_, T> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> RefMut<'_, T> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_take<'a>(self) -> RefGuard<'a, T> {
|
||||
let handle = self;
|
||||
RefGuard {
|
||||
guard: unsafe {
|
||||
std::mem::transmute::<MutexGuard<'_, T>, MutexGuard<'_, T>>(
|
||||
handle.0.lock().unwrap(),
|
||||
)
|
||||
},
|
||||
handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_take_mut<'a>(self) -> RefGuardMut<'a, T> {
|
||||
let handle = self;
|
||||
RefGuardMut {
|
||||
guard: unsafe {
|
||||
std::mem::transmute::<MutexGuard<'_, T>, MutexGuard<'_, T>>(
|
||||
handle.0.lock().unwrap(),
|
||||
)
|
||||
},
|
||||
handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refs(&self) -> usize {
|
||||
Arc::strong_count(&self.0)
|
||||
}
|
||||
|
||||
pub fn weak(&self) -> WeakHandle<T> {
|
||||
WeakHandle(Arc::downgrade(&self.0))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// you must guarantee the type outside
|
||||
/// ideally you check the typeid, but this is often used
|
||||
/// when the trait object is wrapped one or more times
|
||||
/// and you'd have to implement each one individually
|
||||
pub unsafe fn downcast<U: 'static>(self) -> Handle<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let raw: *const Mutex<T> = Arc::into_raw(self.0);
|
||||
let raw: *const Mutex<U> = raw.cast();
|
||||
Handle(unsafe { Arc::from_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> WeakHandle<T> {
|
||||
pub fn strong(&self) -> Option<Handle<T>> {
|
||||
Some(Handle(self.0.upgrade()?))
|
||||
}
|
||||
|
||||
pub fn dropped(&self) -> bool {
|
||||
self.0.strong_count() == 0
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// you must guarantee the type outside
|
||||
pub unsafe fn downcast<U: 'static>(self) -> WeakHandle<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let raw: *const Mutex<T> = self.0.into_raw();
|
||||
let raw: *const Mutex<U> = raw.cast();
|
||||
WeakHandle(unsafe { Weak::from_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for Handle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for WeakHandle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Handle<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Handle<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(Arc::new(Mutex::new(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Handle<U>> for Handle<T> {}
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WeakHandle<U>> for WeakHandle<T> {}
|
||||
|
||||
// yucky
|
||||
|
||||
// TODO: is drop order important here?
|
||||
// something stupid could happen that I don't know about
|
||||
// if handle is dropped before guard
|
||||
pub struct RefGuard<'a, T: ?Sized> {
|
||||
guard: MutexGuard<'a, T>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
pub struct RefGuardMut<'a, T: ?Sized> {
|
||||
guard: MutexGuard<'a, T>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
pub struct MappedRefGuard<'a, T: ?Sized, U> {
|
||||
guard: MappedMutexGuard<'a, U>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
pub struct MappedRefGuardMut<'a, T: ?Sized, U> {
|
||||
guard: MappedMutexGuard<'a, U>,
|
||||
handle: Handle<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RefGuard<'a, T> {
|
||||
pub fn map<U>(s: Self, f: impl FnOnce(&mut T) -> &mut U) -> MappedRefGuard<'a, T, U> {
|
||||
MappedRefGuard {
|
||||
guard: MutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> RefGuardMut<'a, T> {
|
||||
pub fn map<U>(s: Self, f: impl FnOnce(&mut T) -> &mut U) -> MappedRefGuardMut<'a, T, U> {
|
||||
MappedRefGuardMut {
|
||||
guard: MutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, U> MappedRefGuard<'a, T, U> {
|
||||
pub fn map<U2>(s: Self, f: impl FnOnce(&mut U) -> &mut U2) -> MappedRefGuard<'a, T, U2> {
|
||||
MappedRefGuard {
|
||||
guard: MappedMutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, U> MappedRefGuardMut<'a, T, U> {
|
||||
pub fn map<U2>(s: Self, f: impl FnOnce(&mut U) -> &mut U2) -> MappedRefGuardMut<'a, T, U2> {
|
||||
MappedRefGuardMut {
|
||||
guard: MappedMutexGuard::map(s.guard, f),
|
||||
handle: s.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for RefGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for RefGuardMut<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> DerefMut for RefGuardMut<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U> Deref for MappedRefGuard<'_, T, U> {
|
||||
type Target = U;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U> Deref for MappedRefGuardMut<'_, T, U> {
|
||||
type Target = U;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, U> DerefMut for MappedRefGuardMut<'_, T, U> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)]
|
||||
pub struct Id<I = u64>(I);
|
||||
@@ -9,7 +11,14 @@ pub struct IdTracker<I = u64> {
|
||||
cur: Id<I>,
|
||||
}
|
||||
|
||||
impl<I: IdNum> IdTracker<I> {
|
||||
impl<I: const IdNum> IdTracker<I> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
free: Vec::new(),
|
||||
cur: Id(I::first()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> Id<I> {
|
||||
if let Some(id) = self.free.pop() {
|
||||
@@ -43,22 +52,19 @@ impl<I: IdNum> Id<I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: IdNum> Default for IdTracker<I> {
|
||||
impl<I: const IdNum> Default for IdTracker<I> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
free: Vec::new(),
|
||||
cur: Id(I::first()),
|
||||
}
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IdNum {
|
||||
pub const trait IdNum {
|
||||
fn first() -> Self;
|
||||
fn next(&self) -> Self;
|
||||
fn idx(&self) -> usize;
|
||||
}
|
||||
|
||||
impl IdNum for u64 {
|
||||
impl const IdNum for u64 {
|
||||
fn first() -> Self {
|
||||
0
|
||||
}
|
||||
@@ -72,7 +78,7 @@ impl IdNum for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl IdNum for u32 {
|
||||
impl const IdNum for u32 {
|
||||
fn first() -> Self {
|
||||
0
|
||||
}
|
||||
@@ -85,3 +91,33 @@ impl IdNum for u32 {
|
||||
*self as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StaticIdTracker<I = u64>(Mutex<IdTracker<I>>);
|
||||
|
||||
impl<I: const IdNum> StaticIdTracker<I> {
|
||||
pub const fn new() -> Self {
|
||||
Self(Mutex::new(IdTracker::new()))
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&self) -> Id<I> {
|
||||
let mut s = self.0.lock().unwrap();
|
||||
if let Some(id) = s.free.pop() {
|
||||
return id;
|
||||
}
|
||||
let next = s.cur.next();
|
||||
std::mem::replace(&mut s.cur, next)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn free(&self, id: Id<I>) {
|
||||
let mut s = self.0.lock().unwrap();
|
||||
s.free.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: const IdNum> Default for StaticIdTracker<I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
mod arena;
|
||||
mod borrow;
|
||||
mod change;
|
||||
mod handle;
|
||||
mod id;
|
||||
mod math;
|
||||
mod refcount;
|
||||
mod vec2;
|
||||
|
||||
pub(crate) use arena::*;
|
||||
pub(crate) use borrow::*;
|
||||
pub use arena::*;
|
||||
pub use change::*;
|
||||
pub(crate) use id::*;
|
||||
pub(crate) use math::*;
|
||||
pub(crate) use refcount::*;
|
||||
pub use handle::*;
|
||||
pub use id::*;
|
||||
pub use math::*;
|
||||
pub use refcount::*;
|
||||
pub use vec2::*;
|
||||
|
||||
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
|
||||
pub type HashSet<K> = fxhash::FxHashSet<K>;
|
||||
@@ -10,6 +10,7 @@ impl RefCounter {
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(0.into()))
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn refs(&self) -> u32 {
|
||||
self.0.load(Ordering::Acquire)
|
||||
}
|
||||
@@ -17,6 +18,16 @@ impl RefCounter {
|
||||
let refs = self.0.fetch_sub(1, Ordering::Release);
|
||||
refs == 0
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn quiet_clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RefCounter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RefCounter {
|
||||
@@ -1,8 +1,5 @@
|
||||
use crate::{
|
||||
layout::UiNum,
|
||||
util::{DivOr, impl_op},
|
||||
};
|
||||
use std::{hash::Hash, marker::Destruct, ops::*};
|
||||
use crate::util::{DivOr, impl_op};
|
||||
use std::{hash::Hash, ops::*};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
@@ -20,10 +17,6 @@ impl Hash for Vec2 {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
|
||||
Vec2::new(x.to_f32(), y.to_f32())
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
pub const ZERO: Self = Self::new(0.0, 0.0);
|
||||
pub const ONE: Self = Self::new(1.0, 1.0);
|
||||
@@ -56,14 +49,15 @@ impl Vec2 {
|
||||
pub const fn tuple(&self) -> (f32, f32) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
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 with_x(mut self, x: f32) -> Self {
|
||||
self.x = x;
|
||||
self
|
||||
}
|
||||
|
||||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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
|
||||
77
macro/src/lib.rs
Normal file
77
macro/src/lib.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Attribute, Block, Ident, ItemTrait, Signature, Token, Visibility,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input, parse_quote,
|
||||
};
|
||||
|
||||
struct Input {
|
||||
attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
name: Ident,
|
||||
fns: Vec<InputFn>,
|
||||
}
|
||||
|
||||
struct InputFn {
|
||||
sig: Signature,
|
||||
body: Block,
|
||||
}
|
||||
|
||||
impl Parse for Input {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let attrs = input.call(Attribute::parse_outer)?;
|
||||
let vis = input.parse()?;
|
||||
input.parse::<Token![trait]>()?;
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![;]>()?;
|
||||
let mut fns = Vec::new();
|
||||
while !input.is_empty() {
|
||||
let sig = input.parse()?;
|
||||
let body = input.parse()?;
|
||||
fns.push(InputFn { sig, body })
|
||||
}
|
||||
if !input.is_empty() {
|
||||
input.error("function expected");
|
||||
}
|
||||
Ok(Input {
|
||||
attrs,
|
||||
vis,
|
||||
name,
|
||||
fns,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn widget_trait(input: TokenStream) -> TokenStream {
|
||||
let Input {
|
||||
attrs,
|
||||
vis,
|
||||
name,
|
||||
fns,
|
||||
} = parse_macro_input!(input as Input);
|
||||
|
||||
let sigs: Vec<_> = fns.iter().map(|f| f.sig.clone()).collect();
|
||||
let impls: Vec<_> = fns
|
||||
.iter()
|
||||
.map(|InputFn { sig, body }| quote! { #sig #body })
|
||||
.collect();
|
||||
|
||||
let mut trai: ItemTrait = parse_quote!(
|
||||
#vis trait #name<WL: WidgetLike<Tag>, Tag> {
|
||||
#(#sigs;)*
|
||||
}
|
||||
);
|
||||
|
||||
trai.attrs = attrs;
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#trai
|
||||
|
||||
impl<WL: WidgetLike<Tag>, Tag> #name<WL, Tag> for WL {
|
||||
#(#impls)*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
# 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
|
||||
|
||||
there's a `main.rs` that runs a testing window, so can just `cargo run` to see it working
|
||||
|
||||
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
|
||||
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
|
||||
- specifically designed around wgpu
|
||||
- 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)
|
||||
- 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)
|
||||
|
||||
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
|
||||
|
||||
@@ -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 input::Input;
|
||||
use iris::prelude::*;
|
||||
use len_fns::*;
|
||||
use render::Renderer;
|
||||
use std::sync::Arc;
|
||||
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
|
||||
|
||||
mod app;
|
||||
mod input;
|
||||
mod render;
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
fn main() {
|
||||
App::run();
|
||||
DefaultApp::<Client>::run();
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
renderer: Renderer,
|
||||
input: Input,
|
||||
ui: Ui,
|
||||
info: WidgetId<Text>,
|
||||
focus: Option<WidgetId<TextEdit>>,
|
||||
clipboard: Clipboard,
|
||||
info: WidgetRef<Text>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
struct Submit;
|
||||
event_ctx!(Client);
|
||||
|
||||
impl DefaultEvent for Submit {
|
||||
type Data = ();
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
let renderer = Renderer::new(window);
|
||||
|
||||
let mut ui = Ui::new();
|
||||
impl DefaultAppState for Client {
|
||||
fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
|
||||
let rrect = rect(Color::WHITE).radius(20);
|
||||
let pad_test = (
|
||||
rrect.color(Color::BLUE),
|
||||
@@ -59,7 +37,7 @@ impl Client {
|
||||
.width(rest(3)),
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.add_static(&mut ui);
|
||||
.add(ui);
|
||||
|
||||
let span_test = (
|
||||
rrect.color(Color::GREEN).width(100),
|
||||
@@ -70,35 +48,33 @@ impl Client {
|
||||
rrect.color(Color::RED).width(100),
|
||||
)
|
||||
.span(Dir::LEFT)
|
||||
.add_static(&mut ui);
|
||||
.add(ui);
|
||||
|
||||
let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui);
|
||||
let span_add = Span::empty(Dir::RIGHT).add(ui);
|
||||
let span_add_ = span_add.weak();
|
||||
|
||||
let add_button = rect(Color::LIME)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |ctx: &mut Client, _| {
|
||||
let child = ctx
|
||||
.ui
|
||||
.add(image(include_bytes!("assets/sungals.png")).center())
|
||||
.any();
|
||||
ctx.ui[span_add].children.push(child);
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
let child = image(include_bytes!("assets/sungals.png"))
|
||||
.center()
|
||||
.add(ctx.data.ui);
|
||||
span_add_.get_mut().children.push(child);
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BOT_RIGHT);
|
||||
|
||||
let del_button = rect(Color::RED)
|
||||
.radius(30)
|
||||
.on(CursorSense::click(), move |ctx: &mut Client, _| {
|
||||
ctx.ui[span_add].children.pop();
|
||||
.on(CursorSense::click(), move |_| {
|
||||
span_add_.get_mut().children.pop();
|
||||
})
|
||||
.sized((150, 150))
|
||||
.align(Align::BOT_LEFT);
|
||||
|
||||
let span_add_test = (span_add, add_button, del_button)
|
||||
.stack()
|
||||
.add_static(&mut ui);
|
||||
let span_add_test = (span_add, add_button, del_button).stack().add(ui);
|
||||
|
||||
let btext = |content| text(content).size(30);
|
||||
let btext = |content| wtext(content).size(30);
|
||||
|
||||
let text_test = (
|
||||
btext("this is a").align(Align::LEFT),
|
||||
@@ -116,47 +92,43 @@ impl Client {
|
||||
)
|
||||
.span(Dir::RIGHT)
|
||||
.center(),
|
||||
text("pretty cool right?").size(50),
|
||||
wtext("pretty cool right?").size(50),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add_static(&mut ui);
|
||||
.add(ui);
|
||||
|
||||
let texts = Span::empty(Dir::DOWN).gap(10).add_static(&mut ui);
|
||||
let texts = Span::empty(Dir::DOWN).gap(10).add(ui);
|
||||
let texts_ = texts.weak();
|
||||
let msg_area = texts.scroll().masked().background(rect(Color::SKY));
|
||||
let add_text = text("add")
|
||||
.editable()
|
||||
let add_text = wtext("add")
|
||||
.editable(false)
|
||||
.text_align(Align::LEFT)
|
||||
.size(30)
|
||||
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
||||
client.ui.text(id).select(ctx.cursor, ctx.size);
|
||||
client.focus = Some(id.clone());
|
||||
})
|
||||
.id_on(Submit, move |id, client: &mut Client, _| {
|
||||
let content = client.ui.text(id).take();
|
||||
let text = text(content)
|
||||
.editable()
|
||||
.attr::<Selectable>(())
|
||||
.on(Submit, move |ctx| {
|
||||
let content = ctx.widget.get_mut().take();
|
||||
let text = wtext(content)
|
||||
.editable(false)
|
||||
.size(30)
|
||||
.text_align(Align::LEFT)
|
||||
.wrap(true)
|
||||
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
||||
client.ui.text(id).select(ctx.cursor, ctx.size);
|
||||
client.focus = Some(id.clone());
|
||||
});
|
||||
.attr::<Selectable>(());
|
||||
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::new());
|
||||
texts_.get_mut().children.push(msg_box);
|
||||
})
|
||||
.add(&mut ui);
|
||||
.add(ui);
|
||||
let add_text_ = add_text.weak();
|
||||
let text_edit_scroll = (
|
||||
msg_area.height(rest(1)),
|
||||
(
|
||||
Rect::new(Color::WHITE.darker(0.9)),
|
||||
(
|
||||
add_text.clone().width(rest(1)),
|
||||
add_text.width(rest(1)),
|
||||
Rect::new(Color::GREEN)
|
||||
.on(CursorSense::click(), move |client: &mut Client, _| {
|
||||
client.run_event(&add_text, Submit, ());
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
Events::<Submit, _>::run(add_text_.id(), &mut (), ctx.state);
|
||||
})
|
||||
.sized((40, 40)),
|
||||
)
|
||||
@@ -165,128 +137,77 @@ impl Client {
|
||||
)
|
||||
.stack()
|
||||
.size(StackSize::Child(1))
|
||||
.offset_layer(1)
|
||||
.layer_offset(1)
|
||||
.align(Align::BOT),
|
||||
)
|
||||
.span(Dir::DOWN)
|
||||
.add_static(&mut ui);
|
||||
.add(ui);
|
||||
|
||||
let main = pad_test.pad(10).add_static(&mut ui);
|
||||
let main = pad_test.pad(10).add(ui);
|
||||
let main_ = main.weak();
|
||||
|
||||
let switch_button = |color, to, label| {
|
||||
let tab_handles = Handle::from((0, Vec::new()));
|
||||
|
||||
let switch_button = |color, to: Option<WidgetHandle>, label| {
|
||||
let tab_handles = tab_handles.clone();
|
||||
let i = tab_handles.get().1.len();
|
||||
tab_handles.get_mut().1.push(to);
|
||||
let rect = rect(color)
|
||||
.id_on(CursorSense::click(), move |id, ui: &mut Ui, _| {
|
||||
ui[main].inner.set_static(to);
|
||||
ui[id].color = color.darker(0.3);
|
||||
.on(CursorSense::click(), move |ctx| {
|
||||
let (prev, all) = &mut *tab_handles.get_mut();
|
||||
if let Some(to) = &mut all[i] {
|
||||
let mut main = main_.get_mut();
|
||||
std::mem::swap(&mut main.inner, to);
|
||||
all.swap(*prev, i);
|
||||
*prev = i;
|
||||
}
|
||||
ctx.widget.get_mut().color = color.darker(0.3);
|
||||
})
|
||||
.edit_on(
|
||||
.on(
|
||||
CursorSense::HoverStart | CursorSense::unclick(),
|
||||
move |r, _| {
|
||||
r.color = color.brighter(0.2);
|
||||
move |ctx| {
|
||||
ctx.widget.get_mut().color = color.brighter(0.2);
|
||||
},
|
||||
)
|
||||
.edit_on(CursorSense::HoverEnd, move |r, _| {
|
||||
r.color = color;
|
||||
.on(CursorSense::HoverEnd, move |ctx| {
|
||||
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 = (
|
||||
switch_button(Color::RED, pad_test.any(), "pad"),
|
||||
switch_button(Color::GREEN, span_test.any(), "span"),
|
||||
switch_button(Color::BLUE, span_add_test.any(), "image span"),
|
||||
switch_button(Color::MAGENTA, text_test.any(), "text layout"),
|
||||
switch_button(Color::RED, None, "pad"),
|
||||
switch_button(Color::GREEN, Some(span_test), "span"),
|
||||
switch_button(Color::BLUE, Some(span_add_test), "image span"),
|
||||
switch_button(Color::MAGENTA, Some(text_test), "text layout"),
|
||||
switch_button(
|
||||
Color::YELLOW.mul_rgb(0.5),
|
||||
text_edit_scroll.any(),
|
||||
Some(text_edit_scroll),
|
||||
"text edit scroll",
|
||||
),
|
||||
)
|
||||
.span(Dir::RIGHT);
|
||||
|
||||
let info = text("").add(&mut ui);
|
||||
let info_sect = info.clone().pad(10).align(Align::RIGHT);
|
||||
let info_ = wtext("").add(ui);
|
||||
let info = info_.weak();
|
||||
let info_sect = info_.pad(10).align(Align::RIGHT);
|
||||
|
||||
((tabs.height(40), main).span(Dir::DOWN), info_sect)
|
||||
.stack()
|
||||
.set_root(&mut ui);
|
||||
.set_root(ui);
|
||||
|
||||
Self {
|
||||
renderer,
|
||||
input: Input::default(),
|
||||
ui,
|
||||
info,
|
||||
focus: None,
|
||||
clipboard: Clipboard::new().unwrap(),
|
||||
}
|
||||
Self { info }
|
||||
}
|
||||
|
||||
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||
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 => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) {
|
||||
let new = format!(
|
||||
"widgets: {}\nactive:{}\nviews: {}",
|
||||
self.ui.num_widgets(),
|
||||
self.ui.active_widgets(),
|
||||
self.renderer.ui.view_count()
|
||||
total_widgets(),
|
||||
ui.active_widgets(),
|
||||
state.renderer.ui.view_count()
|
||||
);
|
||||
if new != *self.ui[&self.info].content {
|
||||
*self.ui[&self.info].content = new;
|
||||
if new != *self.info.get().content {
|
||||
*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,372 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::{
|
||||
ops::{BitOr, Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
layout::{UiModule, UiRegion, Vec2},
|
||||
util::{HashMap, Id},
|
||||
};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum Button {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum CursorSense {
|
||||
PressStart(Button),
|
||||
Pressing(Button),
|
||||
PressEnd(Button),
|
||||
HoverStart,
|
||||
Hovering,
|
||||
HoverEnd,
|
||||
Scroll,
|
||||
}
|
||||
|
||||
pub struct CursorSenses(Vec<CursorSense>);
|
||||
|
||||
impl CursorSense {
|
||||
pub fn click() -> Self {
|
||||
Self::PressStart(Button::Left)
|
||||
}
|
||||
pub fn unclick() -> Self {
|
||||
Self::PressEnd(Button::Left)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorState {
|
||||
pub pos: Vec2,
|
||||
pub exists: bool,
|
||||
pub buttons: CursorButtons,
|
||||
pub scroll_delta: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorButtons {
|
||||
pub left: ActivationState,
|
||||
pub middle: ActivationState,
|
||||
pub right: ActivationState,
|
||||
}
|
||||
|
||||
impl CursorButtons {
|
||||
pub fn select(&self, button: &Button) -> &ActivationState {
|
||||
match button {
|
||||
Button::Left => &self.left,
|
||||
Button::Right => &self.right,
|
||||
Button::Middle => &self.middle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.left.end_frame();
|
||||
self.middle.end_frame();
|
||||
self.right.end_frame();
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorState {
|
||||
pub fn end_frame(&mut self) {
|
||||
self.buttons.end_frame();
|
||||
self.scroll_delta = Vec2::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
pub enum ActivationState {
|
||||
Start,
|
||||
On,
|
||||
End,
|
||||
#[default]
|
||||
Off,
|
||||
}
|
||||
|
||||
/// this and other similar stuff has a generic
|
||||
/// because I kind of want to make CursorModule generic
|
||||
/// or basically have some way to have custom senses
|
||||
/// that depend on active widget positions
|
||||
/// but I'm not sure how or if worth it
|
||||
pub struct Sensor<Ctx, Data> {
|
||||
pub senses: CursorSenses,
|
||||
pub f: Rc<dyn EventFn<Ctx, Data>>,
|
||||
}
|
||||
|
||||
pub type SensorMap<Ctx, Data> = HashMap<Id, SensorGroup<Ctx, Data>>;
|
||||
pub type SenseShape = UiRegion;
|
||||
pub struct SensorGroup<Ctx, Data> {
|
||||
pub hover: ActivationState,
|
||||
pub sensors: Vec<Sensor<Ctx, Data>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CursorData {
|
||||
pub cursor: Vec2,
|
||||
pub size: Vec2,
|
||||
pub scroll_delta: Vec2,
|
||||
}
|
||||
|
||||
pub struct CursorModule<Ctx> {
|
||||
map: SensorMap<Ctx, CursorData>,
|
||||
active: HashMap<usize, HashMap<Id, SenseShape>>,
|
||||
}
|
||||
|
||||
impl<Ctx: 'static> UiModule for CursorModule<Ctx> {
|
||||
fn on_draw(&mut self, inst: &WidgetInstance) {
|
||||
if self.map.contains_key(&inst.id) {
|
||||
self.active
|
||||
.entry(inst.layer)
|
||||
.or_default()
|
||||
.insert(inst.id, inst.region);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_undraw(&mut self, inst: &WidgetInstance) {
|
||||
if let Some(layer) = self.active.get_mut(&inst.layer) {
|
||||
layer.remove(&inst.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove(&mut self, id: &Id) {
|
||||
self.map.remove(id);
|
||||
for layer in self.active.values_mut() {
|
||||
layer.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_move(&mut self, inst: &WidgetInstance) {
|
||||
if let Some(map) = self.active.get_mut(&inst.layer)
|
||||
&& let Some(region) = map.get_mut(&inst.id)
|
||||
{
|
||||
*region = inst.region;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx> CursorModule<Ctx> {
|
||||
pub fn merge(&mut self, other: Self) {
|
||||
for (id, group) in other.map {
|
||||
for sensor in group.sensors {
|
||||
self.map.entry(id).or_default().sensors.push(sensor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SensorCtx: UiCtx {
|
||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
|
||||
}
|
||||
|
||||
impl<Ctx: UiCtx + 'static> SensorCtx for Ctx {
|
||||
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) {
|
||||
CursorModule::<Ctx>::run(self, cursor, window_size);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
|
||||
pub fn run(ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) {
|
||||
let layers = std::mem::take(&mut ctx.ui().data.layers);
|
||||
let mut module = std::mem::take(ctx.ui().data.modules.get_mut::<Self>());
|
||||
|
||||
for i in layers.indices().rev() {
|
||||
let Some(list) = module.active.get_mut(&i) else {
|
||||
continue;
|
||||
};
|
||||
let mut sensed = false;
|
||||
for (id, shape) in list.iter() {
|
||||
let group = module.map.get_mut(id).unwrap();
|
||||
let region = shape.to_px(window_size);
|
||||
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||
group.hover.update(in_shape);
|
||||
if group.hover == ActivationState::Off {
|
||||
continue;
|
||||
}
|
||||
sensed = true;
|
||||
|
||||
for sensor in &mut group.sensors {
|
||||
if should_run(&sensor.senses, cursor, group.hover) {
|
||||
let data = CursorData {
|
||||
cursor: cursor.pos - region.top_left,
|
||||
size: region.bot_right - region.top_left,
|
||||
scroll_delta: cursor.scroll_delta,
|
||||
};
|
||||
(sensor.f)(ctx, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if sensed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let ui_mod = ctx.ui().data.modules.get_mut::<Self>();
|
||||
std::mem::swap(ui_mod, &mut module);
|
||||
ui_mod.merge(module);
|
||||
ctx.ui().data.layers = layers;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_run(senses: &CursorSenses, cursor: &CursorState, hover: ActivationState) -> bool {
|
||||
for sense in senses.iter() {
|
||||
if match sense {
|
||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||
CursorSense::PressEnd(button) => cursor.buttons.select(button).is_end(),
|
||||
CursorSense::HoverStart => hover.is_start(),
|
||||
CursorSense::Hovering => hover.is_on(),
|
||||
CursorSense::HoverEnd => hover.is_end(),
|
||||
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
||||
} {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl ActivationState {
|
||||
pub fn is_start(&self) -> bool {
|
||||
*self == Self::Start
|
||||
}
|
||||
pub fn is_on(&self) -> bool {
|
||||
*self == Self::Start || *self == Self::On
|
||||
}
|
||||
pub fn is_end(&self) -> bool {
|
||||
*self == Self::End
|
||||
}
|
||||
pub fn is_off(&self) -> bool {
|
||||
*self == Self::End || *self == Self::Off
|
||||
}
|
||||
pub fn update(&mut self, on: bool) {
|
||||
*self = match *self {
|
||||
Self::Start => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::On => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::End => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
Self::Off => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
match self {
|
||||
Self::Start => *self = Self::On,
|
||||
Self::End => *self = Self::Off,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Event for CursorSenses {
|
||||
type Module<Ctx: 'static> = CursorModule<Ctx>;
|
||||
type Data = CursorData;
|
||||
}
|
||||
|
||||
impl Event for CursorSense {
|
||||
type Module<Ctx: 'static> = CursorModule<Ctx>;
|
||||
type Data = CursorData;
|
||||
}
|
||||
|
||||
impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static> EventModule<E, Ctx>
|
||||
for CursorModule<Ctx>
|
||||
{
|
||||
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
|
||||
// TODO: does not add to active if currently active
|
||||
self.map.entry(id).or_default().sensors.push(Sensor {
|
||||
senses: senses.into(),
|
||||
f: Rc::new(f),
|
||||
});
|
||||
}
|
||||
|
||||
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
|
||||
let senses = event.into();
|
||||
if let Some(group) = self.map.get(id) {
|
||||
let fs: Vec<_> = group
|
||||
.sensors
|
||||
.iter()
|
||||
.filter_map(|sensor| {
|
||||
if sensor.senses.iter().any(|s| senses.contains(s)) {
|
||||
Some(sensor.f.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(move |ctx: &mut Ctx, data: CursorData| {
|
||||
for f in &fs {
|
||||
f(ctx, data.clone());
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx, Data> Default for SensorGroup<Ctx, Data> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hover: Default::default(),
|
||||
sensors: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CursorSenses {
|
||||
type Target = Vec<CursorSense>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for CursorSenses {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorSense> for CursorSenses {
|
||||
fn from(val: CursorSense) -> Self {
|
||||
CursorSenses(vec![val])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for CursorSense {
|
||||
type Output = CursorSenses;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
CursorSenses(vec![self, rhs])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<CursorSense> for CursorSenses {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
|
||||
self.0.push(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx> Default for CursorModule<Ctx> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: Default::default(),
|
||||
active: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
64
src/default/attr.rs
Normal file
64
src/default/attr.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use crate::{default::UiState, prelude::*};
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
event_ctx!(UiState);
|
||||
|
||||
pub struct Selector;
|
||||
|
||||
impl<W: Widget + 'static> WidgetAttr<W> for Selector {
|
||||
type Input = WidgetRef<TextEdit>;
|
||||
|
||||
fn run(ui: &mut Ui, container: WidgetRef<W>, id: Self::Input) {
|
||||
container.on(CursorSense::click_or_drag(), move |ctx| {
|
||||
let ui = &mut ctx.data.ui;
|
||||
let region = ui.window_region(&id).unwrap();
|
||||
let id_pos = region.top_left;
|
||||
let container_pos = ui.window_region(&container).unwrap().top_left;
|
||||
let pos = ctx.data.pos + container_pos - id_pos;
|
||||
let size = region.size();
|
||||
select(ui, id, ctx.state, pos, size, ctx.data.sense.is_dragging());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Selectable;
|
||||
|
||||
impl WidgetAttr<TextEdit> for Selectable {
|
||||
type Input = ();
|
||||
|
||||
fn run(ui: &mut Ui, id: WidgetRef<TextEdit>, _: Self::Input) {
|
||||
id.on(CursorSense::click_or_drag(), move |ctx| {
|
||||
select(
|
||||
ctx.data.ui,
|
||||
id,
|
||||
ctx.state,
|
||||
ctx.data.pos,
|
||||
ctx.data.size,
|
||||
ctx.data.sense.is_dragging(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn select(
|
||||
ui: &mut Ui,
|
||||
id: WidgetRef<TextEdit>,
|
||||
state: &mut UiState,
|
||||
pos: Vec2,
|
||||
size: Vec2,
|
||||
dragging: bool,
|
||||
) {
|
||||
let now = Instant::now();
|
||||
let recent = (now - state.last_click) < Duration::from_millis(300);
|
||||
state.last_click = now;
|
||||
id.get_mut().select(pos, size, dragging, recent);
|
||||
if let Some(region) = ui.window_region(&id) {
|
||||
state.window.set_ime_allowed(true);
|
||||
state.window.set_ime_cursor_area(
|
||||
LogicalPosition::<f32>::from(region.top_left.tuple()),
|
||||
LogicalSize::<f32>::from(region.size().tuple()),
|
||||
);
|
||||
}
|
||||
state.focus = Some(id);
|
||||
}
|
||||
9
src/default/event.rs
Normal file
9
src/default/event.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use iris_core::layout::Event;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Submit;
|
||||
impl Event for Submit {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Edited;
|
||||
impl Event for Edited {}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::Client;
|
||||
use iris::{
|
||||
core::{CursorState, Modifiers},
|
||||
use crate::{
|
||||
widget::{CursorState, Modifiers},
|
||||
layout::Vec2,
|
||||
default::UiState,
|
||||
};
|
||||
use winit::{
|
||||
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
||||
@@ -32,10 +32,14 @@ impl Input {
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, .. } => {
|
||||
let delta = match *delta {
|
||||
let mut delta = match *delta {
|
||||
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
|
||||
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;
|
||||
}
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
@@ -66,7 +70,7 @@ impl Input {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
impl UiState {
|
||||
pub fn window_size(&self) -> Vec2 {
|
||||
let size = self.renderer.window().inner_size();
|
||||
(size.width, size.height).into()
|
||||
180
src/default/mod.rs
Normal file
180
src/default/mod.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use crate::prelude::*;
|
||||
use arboard::Clipboard;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use winit::event::{Ime, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
|
||||
use winit::window::{Window, WindowAttributes};
|
||||
|
||||
mod app;
|
||||
mod attr;
|
||||
mod event;
|
||||
mod input;
|
||||
mod render;
|
||||
|
||||
pub use app::*;
|
||||
pub use attr::*;
|
||||
pub use event::*;
|
||||
pub use input::*;
|
||||
pub use render::*;
|
||||
|
||||
pub type Proxy<Event> = EventLoopProxy<Event>;
|
||||
pub type DefaultApp<Data> = App<DefaultState<Data>>;
|
||||
|
||||
pub struct DefaultState<AppState> {
|
||||
ui: Ui,
|
||||
ui_state: UiState,
|
||||
app_state: AppState,
|
||||
}
|
||||
|
||||
pub struct UiState {
|
||||
pub renderer: UiRenderer,
|
||||
pub input: Input,
|
||||
pub focus: Option<WidgetRef<TextEdit>>,
|
||||
pub clipboard: Clipboard,
|
||||
pub window: Arc<Window>,
|
||||
pub ime: usize,
|
||||
pub last_click: Instant,
|
||||
}
|
||||
|
||||
pub trait DefaultAppState: 'static {
|
||||
type Event: 'static = ();
|
||||
fn new(ui: &mut Ui, state: &UiState, proxy: Proxy<Self::Event>) -> Self;
|
||||
#[allow(unused_variables)]
|
||||
fn event(&mut self, event: Self::Event, ui: &mut Ui, state: &UiState) {}
|
||||
#[allow(unused_variables)]
|
||||
fn exit(&mut self, ui: &mut Ui, state: &UiState) {}
|
||||
#[allow(unused_variables)]
|
||||
fn window_event(&mut self, event: WindowEvent, ui: &mut Ui, state: &UiState) {}
|
||||
fn window_attrs() -> WindowAttributes {
|
||||
WindowAttributes::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<State: DefaultAppState> AppState for DefaultState<State> {
|
||||
type Event = State::Event;
|
||||
|
||||
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(State::window_attrs())
|
||||
.expect("failed to create window "),
|
||||
);
|
||||
let mut ui = Ui::new();
|
||||
let ui_state = UiState {
|
||||
renderer: UiRenderer::new(window.clone()),
|
||||
window,
|
||||
input: Input::default(),
|
||||
clipboard: Clipboard::new().unwrap(),
|
||||
ime: 0,
|
||||
last_click: Instant::now(),
|
||||
focus: None,
|
||||
};
|
||||
let app_state = State::new(&mut ui, &ui_state, proxy);
|
||||
Self {
|
||||
ui,
|
||||
ui_state,
|
||||
app_state,
|
||||
}
|
||||
}
|
||||
|
||||
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
|
||||
self.app_state.event(event, &mut self.ui, &self.ui_state);
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||
let Self {
|
||||
ui,
|
||||
ui_state,
|
||||
app_state,
|
||||
} = self;
|
||||
|
||||
let input_changed = ui_state.input.event(&event);
|
||||
let cursor_state = ui_state.cursor_state().clone();
|
||||
let old = ui_state.focus;
|
||||
if cursor_state.buttons.left.is_start() {
|
||||
ui_state.focus = None;
|
||||
}
|
||||
if input_changed {
|
||||
let window_size = ui_state.window_size();
|
||||
// call sensors with all 3 important contexts
|
||||
// TODO: allow user to specify custom contexts?
|
||||
// and give them both states in case they need both
|
||||
ui.run_sensors(&mut (), &cursor_state, window_size);
|
||||
ui.run_sensors(ui_state, &cursor_state, window_size);
|
||||
ui.run_sensors(app_state, &cursor_state, window_size);
|
||||
}
|
||||
if old != ui_state.focus
|
||||
&& let Some(old) = old
|
||||
{
|
||||
old.get_mut().deselect();
|
||||
}
|
||||
match &event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
ui_state.renderer.update(ui);
|
||||
ui_state.renderer.draw();
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
ui.resize((size.width, size.height));
|
||||
ui_state.renderer.resize(size)
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
if let Some(sel) = ui_state.focus
|
||||
&& event.state.is_pressed()
|
||||
{
|
||||
let res = sel.get_mut().apply_event(event, &ui_state.input.modifiers);
|
||||
match res {
|
||||
TextInputResult::Unfocus => {
|
||||
ui_state.focus = None;
|
||||
ui_state.window.set_ime_allowed(false);
|
||||
}
|
||||
TextInputResult::Submit => {
|
||||
Events::<Submit, _>::run(sel.id(), &mut (), app_state);
|
||||
}
|
||||
TextInputResult::Paste => {
|
||||
if let Ok(t) = ui_state.clipboard.get_text() {
|
||||
sel.get_mut().insert(&t);
|
||||
}
|
||||
Events::<Edited, _>::run(sel.id(), &mut (), app_state);
|
||||
}
|
||||
TextInputResult::Copy(text) => {
|
||||
if let Err(err) = ui_state.clipboard.set_text(text) {
|
||||
eprintln!("failed to copy text to clipboard: {err}")
|
||||
}
|
||||
}
|
||||
TextInputResult::Used => {
|
||||
Events::<Edited, _>::run(sel.id(), &mut (), app_state);
|
||||
}
|
||||
TextInputResult::Unused => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::Ime(ime) => {
|
||||
if let Some(sel) = &ui_state.focus {
|
||||
match ime {
|
||||
Ime::Enabled | Ime::Disabled => (),
|
||||
Ime::Preedit(content, _pos) => {
|
||||
// TODO: highlight once that's real
|
||||
sel.get_mut().replace(ui_state.ime, content);
|
||||
ui_state.ime = content.chars().count();
|
||||
}
|
||||
Ime::Commit(content) => {
|
||||
sel.get_mut().insert(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
app_state.window_event(event, ui, ui_state);
|
||||
if ui.update() {
|
||||
ui_state.renderer.window().request_redraw();
|
||||
}
|
||||
ui_state.input.end_frame();
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.app_state.exit(&mut self.ui, &self.ui_state);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::{
|
||||
layout::Ui,
|
||||
render::{UiLimits, UiRenderNode},
|
||||
};
|
||||
use pollster::FutureExt;
|
||||
use std::sync::Arc;
|
||||
use iris::{
|
||||
layout::Ui,
|
||||
render::{UiLimits, UiRenderer},
|
||||
};
|
||||
use wgpu::{util::StagingBelt, *};
|
||||
use winit::{dpi::PhysicalSize, window::Window};
|
||||
|
||||
pub const CLEAR_COLOR: Color = Color::BLACK;
|
||||
|
||||
pub struct Renderer {
|
||||
pub struct UiRenderer {
|
||||
window: Arc<Window>,
|
||||
surface: Surface<'static>,
|
||||
device: Device,
|
||||
@@ -17,10 +17,10 @@ pub struct Renderer {
|
||||
config: SurfaceConfiguration,
|
||||
encoder: CommandEncoder,
|
||||
staging_belt: StagingBelt,
|
||||
pub ui: UiRenderer,
|
||||
pub ui: UiRenderNode,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
impl UiRenderer {
|
||||
pub fn update(&mut self, updates: &mut Ui) {
|
||||
self.ui.update(&self.device, &self.queue, updates);
|
||||
}
|
||||
@@ -72,6 +72,7 @@ impl Renderer {
|
||||
|
||||
let instance = Instance::new(&InstanceDescriptor {
|
||||
backends: Backends::PRIMARY,
|
||||
flags: InstanceFlags::empty(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -100,6 +101,7 @@ impl Renderer {
|
||||
.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(),
|
||||
max_buffer_size: 1 << 30,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -131,7 +133,7 @@ impl Renderer {
|
||||
let staging_belt = StagingBelt::new(4096 * 4);
|
||||
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 {
|
||||
surface,
|
||||
66
src/event.rs
Normal file
66
src/event.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::prelude::*;
|
||||
use iris_macro::widget_trait;
|
||||
|
||||
pub mod eventable {
|
||||
use super::*;
|
||||
|
||||
widget_trait! {
|
||||
pub trait Eventable;
|
||||
fn on<E: EventAlias, Ctx: 'static>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl for<'a> EventIdFn<Ctx, <E::Event as Event>::Data<'a>, WL::Widget>,
|
||||
) -> impl WidgetIdFn<WL::Widget> {
|
||||
move |ui| {
|
||||
let id = self.add(ui);
|
||||
Events::<E::Event, Ctx>::register(id.weak(), event.into_event(), f);
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: naming in here is a bit weird like eventable
|
||||
#[macro_export]
|
||||
macro_rules! event_ctx {
|
||||
($ty: ty) => {
|
||||
mod local_event_trait {
|
||||
use super::*;
|
||||
#[allow(unused_imports)]
|
||||
use $crate::prelude::*;
|
||||
|
||||
widget_trait! {
|
||||
#[allow(unused)]
|
||||
pub trait EventableCtx;
|
||||
fn on<E: EventAlias>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, WL::Widget>,
|
||||
) -> impl WidgetIdFn<WL::Widget> {
|
||||
eventable::Eventable::on(self, event, f)
|
||||
}
|
||||
}
|
||||
use std::marker::Sized;
|
||||
#[allow(unused)]
|
||||
pub trait EventableCtxRef<W: Widget + ?Sized> {
|
||||
fn on<E: EventAlias>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, W>,
|
||||
);
|
||||
}
|
||||
|
||||
impl<W: Widget + ?Sized> EventableCtxRef<W> for WidgetRef<W> {
|
||||
fn on<E: EventAlias>(
|
||||
self,
|
||||
event: E,
|
||||
f: impl for<'a> EventIdFn<$ty, <E::Event as Event>::Data<'a>, W>,
|
||||
) {
|
||||
Events::<E::Event, $ty>::register(self, event.into_event(), f);
|
||||
}
|
||||
}
|
||||
}
|
||||
use local_event_trait::*;
|
||||
};
|
||||
}
|
||||
pub use event_ctx;
|
||||
@@ -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(fn_traits)]
|
||||
#![feature(const_cmp)]
|
||||
#![feature(const_destruct)]
|
||||
#![feature(portable_simd)]
|
||||
#![feature(gen_blocks)]
|
||||
#![feature(associated_type_defaults)]
|
||||
|
||||
pub mod core;
|
||||
pub mod layout;
|
||||
pub mod render;
|
||||
pub mod util;
|
||||
mod default;
|
||||
mod event;
|
||||
mod widget;
|
||||
|
||||
pub use iris_core::*;
|
||||
pub use iris_macro::*;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::core::*;
|
||||
pub use crate::layout::*;
|
||||
pub use crate::render::*;
|
||||
pub use super::default::*;
|
||||
pub use super::event::*;
|
||||
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 crate::prelude::*;
|
||||
|
||||
pub struct Image {
|
||||
handle: TextureHandle,
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Masked {
|
||||
pub inner: WidgetId,
|
||||
pub inner: WidgetHandle,
|
||||
}
|
||||
|
||||
impl Widget for Masked {
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Aligned {
|
||||
pub inner: WidgetId,
|
||||
pub inner: WidgetHandle,
|
||||
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: WidgetHandle,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl Widget for LayerOffset {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
for _ in 0..self.offset {
|
||||
painter.next_layer();
|
||||
}
|
||||
painter.widget(&self.inner);
|
||||
}
|
||||
|
||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
ctx.width(&self.inner)
|
||||
}
|
||||
|
||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
ctx.height(&self.inner)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct MaxSize {
|
||||
pub inner: WidgetId,
|
||||
pub inner: WidgetHandle,
|
||||
pub x: Option<Len>,
|
||||
pub y: Option<Len>,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod align;
|
||||
mod layer;
|
||||
mod max_size;
|
||||
mod offset;
|
||||
mod pad;
|
||||
@@ -8,6 +9,7 @@ mod span;
|
||||
mod stack;
|
||||
|
||||
pub use align::*;
|
||||
pub use layer::*;
|
||||
pub use max_size::*;
|
||||
pub use offset::*;
|
||||
pub use pad::*;
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Offset {
|
||||
pub inner: WidgetId,
|
||||
pub inner: WidgetHandle,
|
||||
pub amt: UiVec2,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::prelude::*;
|
||||
|
||||
pub struct Pad {
|
||||
pub padding: Padding,
|
||||
pub inner: WidgetId,
|
||||
pub inner: WidgetHandle,
|
||||
}
|
||||
|
||||
impl Widget for Pad {
|
||||
@@ -39,6 +39,13 @@ pub struct 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 {
|
||||
let amt = amt.to_f32();
|
||||
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 {
|
||||
self.top = amt.to_f32();
|
||||
self
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Scroll {
|
||||
inner: WidgetId,
|
||||
inner: WidgetHandle,
|
||||
axis: Axis,
|
||||
amt: f32,
|
||||
snap_end: bool,
|
||||
@@ -41,7 +41,7 @@ impl Widget for Scroll {
|
||||
}
|
||||
|
||||
impl Scroll {
|
||||
pub fn new(inner: WidgetId, axis: Axis) -> Self {
|
||||
pub fn new(inner: WidgetHandle, axis: Axis) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
axis,
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Sized {
|
||||
pub inner: WidgetId,
|
||||
pub inner: WidgetHandle,
|
||||
pub x: Option<Len>,
|
||||
pub y: Option<Len>,
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use crate::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Span {
|
||||
pub children: Vec<WidgetId>,
|
||||
pub children: Vec<WidgetHandle>,
|
||||
pub dir: Dir,
|
||||
pub gap: f32,
|
||||
}
|
||||
@@ -158,7 +158,7 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
||||
Span {
|
||||
children: self.children.ui(args.0).arr.to_vec(),
|
||||
children: self.children.ui(args.0).arr.into_iter().collect(),
|
||||
dir: self.dir,
|
||||
gap: self.gap,
|
||||
}
|
||||
@@ -180,3 +180,17 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Ta
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Span {
|
||||
type Target = Vec<WidgetHandle>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Span {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,12 @@ use std::marker::PhantomData;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Stack {
|
||||
pub children: Vec<WidgetId>,
|
||||
pub children: Vec<WidgetHandle>,
|
||||
pub size: StackSize,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl Widget for Stack {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
for _ in 0..self.offset {
|
||||
painter.next_layer();
|
||||
}
|
||||
let mut iter = self.children.iter();
|
||||
if let Some(child) = iter.next() {
|
||||
painter.child_layer();
|
||||
@@ -49,7 +45,6 @@ pub enum StackSize {
|
||||
pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
||||
pub children: Wa,
|
||||
pub size: StackSize,
|
||||
pub offset: usize,
|
||||
_pd: PhantomData<Tag>,
|
||||
}
|
||||
|
||||
@@ -60,8 +55,7 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
||||
Stack {
|
||||
children: self.children.ui(args.0).arr.to_vec(),
|
||||
offset: self.offset,
|
||||
children: self.children.ui(args.0).arr.into_iter().collect(),
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
@@ -72,7 +66,6 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, T
|
||||
Self {
|
||||
children,
|
||||
size: StackSize::default(),
|
||||
offset: 0,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -81,9 +74,4 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, T
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn offset_layer(mut self, offset: usize) -> Self {
|
||||
self.offset = offset;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WidgetPtr {
|
||||
pub inner: Option<WidgetId>,
|
||||
pub inner: Option<WidgetHandle>,
|
||||
}
|
||||
|
||||
impl Widget for WidgetPtr {
|
||||
298
src/widget/sense.rs
Normal file
298
src/widget/sense.rs
Normal file
@@ -0,0 +1,298 @@
|
||||
use crate::layout::{UiRegion, Vec2};
|
||||
use crate::prelude::*;
|
||||
use std::{
|
||||
ops::{BitOr, Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum CursorButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum CursorSense {
|
||||
PressStart(CursorButton),
|
||||
Pressing(CursorButton),
|
||||
PressEnd(CursorButton),
|
||||
HoverStart,
|
||||
Hovering,
|
||||
HoverEnd,
|
||||
Scroll,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CursorSenses(Vec<CursorSense>);
|
||||
|
||||
impl Event for CursorSenses {
|
||||
type Data<'a> = CursorData<'a>;
|
||||
type State = SensorState;
|
||||
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
|
||||
if let Some(sense) = should_run(self, data.cursor, data.hover) {
|
||||
data.sense = sense;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorSense {
|
||||
pub fn click() -> Self {
|
||||
Self::PressStart(CursorButton::Left)
|
||||
}
|
||||
pub fn click_or_drag() -> CursorSenses {
|
||||
Self::click() | Self::Pressing(CursorButton::Left)
|
||||
}
|
||||
pub fn unclick() -> Self {
|
||||
Self::PressEnd(CursorButton::Left)
|
||||
}
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorState {
|
||||
pub pos: Vec2,
|
||||
pub exists: bool,
|
||||
pub buttons: CursorButtons,
|
||||
pub scroll_delta: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CursorButtons {
|
||||
pub left: ActivationState,
|
||||
pub middle: ActivationState,
|
||||
pub right: ActivationState,
|
||||
}
|
||||
|
||||
impl CursorButtons {
|
||||
pub fn select(&self, button: &CursorButton) -> &ActivationState {
|
||||
match button {
|
||||
CursorButton::Left => &self.left,
|
||||
CursorButton::Right => &self.right,
|
||||
CursorButton::Middle => &self.middle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.left.end_frame();
|
||||
self.middle.end_frame();
|
||||
self.right.end_frame();
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (CursorButton, &ActivationState)> {
|
||||
[
|
||||
CursorButton::Left,
|
||||
CursorButton::Middle,
|
||||
CursorButton::Right,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|b| (b, self.select(&b)))
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorState {
|
||||
pub fn end_frame(&mut self) {
|
||||
self.buttons.end_frame();
|
||||
self.scroll_delta = Vec2::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
pub enum ActivationState {
|
||||
Start,
|
||||
On,
|
||||
End,
|
||||
#[default]
|
||||
Off,
|
||||
}
|
||||
|
||||
/// this and other similar stuff has a generic
|
||||
/// because I kind of want to make CursorModule generic
|
||||
/// or basically have some way to have custom senses
|
||||
/// that depend on active widget positions
|
||||
/// but I'm not sure how or if worth it
|
||||
pub struct Sensor<Ctx, Data> {
|
||||
pub senses: CursorSenses,
|
||||
pub f: Rc<dyn EventFn<Ctx, Data>>,
|
||||
}
|
||||
|
||||
pub type SenseShape = UiRegion;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SensorState {
|
||||
pub hover: ActivationState,
|
||||
}
|
||||
|
||||
pub struct CursorData<'a> {
|
||||
/// where this widget was hit
|
||||
pub pos: Vec2,
|
||||
pub size: Vec2,
|
||||
pub scroll_delta: Vec2,
|
||||
pub hover: ActivationState,
|
||||
pub cursor: &'a CursorState,
|
||||
pub ui: &'a mut Ui,
|
||||
/// the first sense that triggered this
|
||||
pub sense: CursorSense,
|
||||
}
|
||||
|
||||
pub trait SensorUi {
|
||||
fn run_sensors<Ctx: 'static>(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2);
|
||||
}
|
||||
|
||||
impl SensorUi for Ui {
|
||||
fn run_sensors<Ctx: 'static>(
|
||||
&mut self,
|
||||
ctx: &mut Ctx,
|
||||
cursor: &CursorState,
|
||||
window_size: Vec2,
|
||||
) {
|
||||
let ui_id = self.id();
|
||||
let layers = std::mem::take(&mut self.data_mut().layers);
|
||||
let mut active = std::mem::take(&mut *Events::<CursorSenses, Ctx>::active(ui_id));
|
||||
for i in layers.indices().rev() {
|
||||
let mut sensed = false;
|
||||
for (id, state) in active.get_mut(&i).into_iter().flatten() {
|
||||
let shape = self.active().get(id).unwrap().region;
|
||||
let region = shape.to_px(window_size);
|
||||
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||
state.hover.update(in_shape);
|
||||
if state.hover == ActivationState::Off {
|
||||
continue;
|
||||
}
|
||||
sensed = true;
|
||||
|
||||
let mut data = CursorData {
|
||||
pos: cursor.pos - region.top_left,
|
||||
size: region.bot_right - region.top_left,
|
||||
scroll_delta: cursor.scroll_delta,
|
||||
hover: state.hover,
|
||||
cursor,
|
||||
ui: self,
|
||||
// this does not have any meaning;
|
||||
// might wanna set up Event to have a prepare stage
|
||||
sense: CursorSense::Hovering,
|
||||
};
|
||||
Events::<CursorSenses, Ctx>::run(*id, &mut data, ctx);
|
||||
}
|
||||
if sensed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*Events::<CursorSenses, Ctx>::active(ui_id) = active;
|
||||
self.data_mut().layers = layers;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_run(
|
||||
senses: &CursorSenses,
|
||||
cursor: &CursorState,
|
||||
hover: ActivationState,
|
||||
) -> Option<CursorSense> {
|
||||
for sense in senses.iter() {
|
||||
if match sense {
|
||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||
CursorSense::PressEnd(button) => cursor.buttons.select(button).is_end(),
|
||||
CursorSense::HoverStart => hover.is_start(),
|
||||
CursorSense::Hovering => hover.is_on(),
|
||||
CursorSense::HoverEnd => hover.is_end(),
|
||||
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
||||
} {
|
||||
return Some(*sense);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl ActivationState {
|
||||
pub fn is_start(&self) -> bool {
|
||||
*self == Self::Start
|
||||
}
|
||||
pub fn is_on(&self) -> bool {
|
||||
*self == Self::Start || *self == Self::On
|
||||
}
|
||||
pub fn is_end(&self) -> bool {
|
||||
*self == Self::End
|
||||
}
|
||||
pub fn is_off(&self) -> bool {
|
||||
*self == Self::End || *self == Self::Off
|
||||
}
|
||||
pub fn update(&mut self, on: bool) {
|
||||
*self = match *self {
|
||||
Self::Start => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::On => match on {
|
||||
true => Self::On,
|
||||
false => Self::End,
|
||||
},
|
||||
Self::End => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
Self::Off => match on {
|
||||
true => Self::Start,
|
||||
false => Self::Off,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
match self {
|
||||
Self::Start => *self = Self::On,
|
||||
Self::End => *self = Self::Off,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventAlias for CursorSense {
|
||||
type Event = CursorSenses;
|
||||
fn into_event(self) -> Self::Event {
|
||||
CursorSenses::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CursorSenses {
|
||||
type Target = Vec<CursorSense>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for CursorSenses {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorSense> for CursorSenses {
|
||||
fn from(val: CursorSense) -> Self {
|
||||
CursorSenses(vec![val])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for CursorSense {
|
||||
type Output = CursorSenses;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
CursorSenses(vec![self, rhs])
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<CursorSense> for CursorSenses {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
|
||||
self.0.push(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
use std::marker::{PhantomData, Sized};
|
||||
use crate::prelude::*;
|
||||
use cosmic_text::{Attrs, Family, Metrics};
|
||||
use std::marker::Sized;
|
||||
|
||||
pub struct TextBuilder<O = TextOutput, H: WidgetOption = ()> {
|
||||
pub content: String,
|
||||
pub attrs: TextAttrs,
|
||||
pub hint: H,
|
||||
_pd: PhantomData<O>,
|
||||
pub output: O,
|
||||
}
|
||||
|
||||
impl<O, H: WidgetOption> TextBuilder<O, H> {
|
||||
pub fn size(mut self, size: impl UiNum) -> Self {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
pub fn center_text(mut self) -> Self {
|
||||
self.attrs.align = Align::CENTER;
|
||||
self
|
||||
}
|
||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||
self.attrs.wrap = wrap;
|
||||
self
|
||||
}
|
||||
pub fn editable(self) -> TextBuilder<TextEditOutput, H> {
|
||||
pub fn editable(self, single_line: bool) -> TextBuilder<TextEditOutput, H> {
|
||||
TextBuilder {
|
||||
content: self.content,
|
||||
attrs: self.attrs,
|
||||
hint: self.hint,
|
||||
_pd: PhantomData,
|
||||
output: TextEditOutput { single_line },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +55,7 @@ impl<O> TextBuilder<O> {
|
||||
content: self.content,
|
||||
attrs: self.attrs,
|
||||
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,
|
||||
));
|
||||
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);
|
||||
let mut text = Text {
|
||||
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 {
|
||||
type Output = TextEdit;
|
||||
|
||||
@@ -92,11 +98,12 @@ impl TextBuilderOutput for TextEditOutput {
|
||||
builder.attrs.font_size,
|
||||
builder.attrs.line_height,
|
||||
));
|
||||
let mut text = TextEdit {
|
||||
view: TextView::new(buf, builder.attrs, builder.hint.get(ui)),
|
||||
cursor: None,
|
||||
};
|
||||
let font_system = &mut ui.data.text.font_system;
|
||||
let mut text = TextEdit::new(
|
||||
TextView::new(buf, builder.attrs, builder.hint.get(ui)),
|
||||
builder.output.single_line,
|
||||
ui.data().text.clone(),
|
||||
);
|
||||
let font_system = &mut ui.data().text.get_mut().font_system;
|
||||
text.buf
|
||||
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, 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 {
|
||||
content: content.into(),
|
||||
attrs: TextAttrs::default(),
|
||||
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::*;
|
||||
|
||||
use crate::{prelude::*, util::MutDetect};
|
||||
use cosmic_text::{Attrs, Metrics, Shaping};
|
||||
use cosmic_text::{Attrs, BufferLine, Cursor, Metrics, Shaping};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub const SHAPING: Shaping = Shaping::Advanced;
|
||||
@@ -21,11 +21,11 @@ pub struct TextView {
|
||||
// cache
|
||||
tex: Option<RenderedText>,
|
||||
width: Option<f32>,
|
||||
pub hint: Option<WidgetId>,
|
||||
pub hint: Option<WidgetHandle>,
|
||||
}
|
||||
|
||||
impl TextView {
|
||||
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetId>) -> Self {
|
||||
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetHandle>) -> Self {
|
||||
Self {
|
||||
attrs: attrs.into(),
|
||||
buf: buf.into(),
|
||||
@@ -34,6 +34,25 @@ impl TextView {
|
||||
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 {
|
||||
let width = if self.attrs.wrap {
|
||||
Some(ctx.px_size().x)
|
||||
@@ -48,9 +67,12 @@ impl TextView {
|
||||
return tex.clone();
|
||||
}
|
||||
self.width = width;
|
||||
let font_system = &mut ctx.text.font_system;
|
||||
self.attrs.apply(font_system, &mut self.buf, width);
|
||||
self.buf.shape_until_scroll(font_system, false);
|
||||
let mut text_data = ctx.text.get_mut();
|
||||
self.attrs
|
||||
.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);
|
||||
self.tex = Some(tex.clone());
|
||||
self.attrs.changed = false;
|
||||
@@ -61,27 +83,47 @@ impl TextView {
|
||||
self.tex.as_ref()
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
||||
let tex = self.render(&mut painter.size_ctx());
|
||||
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);
|
||||
let region = self.tex_region(&tex);
|
||||
if let Some(hint) = &self.hint
|
||||
&& let [line] = &self.buf.lines[..]
|
||||
&& line.text().is_empty()
|
||||
{
|
||||
painter.widget(hint);
|
||||
}
|
||||
} else {
|
||||
painter.texture_within(&tex.handle, region);
|
||||
}
|
||||
region
|
||||
}
|
||||
|
||||
pub fn content(&self) -> String {
|
||||
self.buf
|
||||
.lines
|
||||
.iter()
|
||||
.map(|l| l.text())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl Text {
|
||||
@@ -97,7 +139,7 @@ impl Text {
|
||||
if self.content.changed {
|
||||
self.content.changed = false;
|
||||
self.view.buf.set_text(
|
||||
&mut ctx.text.font_system,
|
||||
&mut ctx.text.get_mut().font_system,
|
||||
&self.content,
|
||||
&Attrs::new().family(self.view.attrs.family),
|
||||
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 {
|
||||
type Target = TextAttrs;
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
use iris_macro::widget_trait;
|
||||
|
||||
pub trait CoreWidget<W, Tag> {
|
||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>;
|
||||
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned>;
|
||||
fn center(self) -> impl WidgetFn<Aligned>;
|
||||
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W>;
|
||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized>;
|
||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
|
||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
|
||||
fn max_width(self, width: impl Into<Len>) -> impl WidgetFn<MaxSize>;
|
||||
fn max_height(self, height: impl Into<Len>) -> impl WidgetFn<MaxSize>;
|
||||
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset>;
|
||||
fn scroll(self) -> impl WidgetIdFn<Scroll>;
|
||||
fn masked(self) -> impl WidgetFn<Masked>;
|
||||
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack>;
|
||||
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack>;
|
||||
}
|
||||
// these methods should "not require any context" (require unit) because they're in core
|
||||
event_ctx!(());
|
||||
|
||||
widget_trait! {
|
||||
pub trait CoreWidget;
|
||||
|
||||
impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|
||||
|ui| Pad {
|
||||
padding: padding.into(),
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
}
|
||||
}
|
||||
|
||||
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned> {
|
||||
move |ui| Aligned {
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
align: align.into(),
|
||||
}
|
||||
}
|
||||
@@ -37,10 +25,10 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
||||
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| {
|
||||
let id = self.add(ui);
|
||||
ui.set_label(&id, label.into());
|
||||
id.set_label(label);
|
||||
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> {
|
||||
let size = size.into();
|
||||
move |ui| Sized {
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
x: Some(size.x),
|
||||
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> {
|
||||
let len = len.into();
|
||||
move |ui| MaxSize {
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
x: Some(len),
|
||||
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> {
|
||||
let len = len.into();
|
||||
move |ui| MaxSize {
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
x: None,
|
||||
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> {
|
||||
let len = len.into();
|
||||
move |ui| Sized {
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
x: Some(len),
|
||||
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> {
|
||||
let len = len.into();
|
||||
move |ui| Sized {
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
x: None,
|
||||
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> {
|
||||
move |ui| Offset {
|
||||
inner: self.add(ui).any(),
|
||||
inner: self.add(ui),
|
||||
amt: amt.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll(self) -> impl WidgetIdFn<Scroll> {
|
||||
move |ui| {
|
||||
Scroll::new(self.add(ui).any(), Axis::Y)
|
||||
.edit_on(CursorSense::Scroll, |w, data| {
|
||||
w.scroll(data.scroll_delta.y * 50.0);
|
||||
Scroll::new(self.add(ui), Axis::Y)
|
||||
.on(CursorSense::Scroll, |ctx| {
|
||||
let s = &mut *ctx.widget.get_mut();
|
||||
s.scroll(ctx.data.scroll_delta.y * 50.0);
|
||||
})
|
||||
.add(ui)
|
||||
}
|
||||
@@ -109,25 +98,38 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
||||
|
||||
fn masked(self) -> impl WidgetFn<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 {
|
||||
children: vec![w.add(ui).any(), self.add(ui).any()],
|
||||
children: vec![w.add(ui), self.add(ui)],
|
||||
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 {
|
||||
children: vec![self.add(ui).any()],
|
||||
children: vec![self.add(ui), w.add(ui)],
|
||||
size: StackSize::Child(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn layer_offset(self, offset: usize) -> impl WidgetFn<LayerOffset> {
|
||||
move |ui| LayerOffset {
|
||||
inner: self.add(ui),
|
||||
offset,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_any(self) -> impl WidgetRet {
|
||||
|ui| self.add(ui)
|
||||
}
|
||||
|
||||
fn set_ptr(self, ptr: &WidgetHandle<WidgetPtr>, ui: &mut Ui) {
|
||||
ptr.get_mut().inner = Some(self.add(ui));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
||||
Reference in New Issue
Block a user