Compare commits
1 Commits
96ef0c529b
...
tuple_atte
| Author | SHA1 | Date | |
|---|---|---|---|
| 14435de6a5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
/target
|
/target
|
||||||
perf.data*
|
|
||||||
|
|||||||
1820
Cargo.lock
generated
1820
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@@ -1,18 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iris"
|
name = "gui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
winit = "0.30.12"
|
winit = "0.30.11"
|
||||||
wgpu = "27.0.1"
|
wgpu = "26.0.1"
|
||||||
bytemuck = "1.23.1"
|
bytemuck = "1.23.1"
|
||||||
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"] }
|
|
||||||
|
|
||||||
|
|||||||
34
TODO
34
TODO
@@ -1,34 +0,0 @@
|
|||||||
images
|
|
||||||
settings (sampler)
|
|
||||||
|
|
||||||
text
|
|
||||||
figure out ways to speed up / what costs the most
|
|
||||||
resizing (per frame) is really slow (assuming painter isn't griefing)
|
|
||||||
|
|
||||||
masks r just made to bare minimum work
|
|
||||||
|
|
||||||
scaling
|
|
||||||
could be just a simple scaling factor that multiplies abs
|
|
||||||
and need to ensure text uses raw abs and not scaled abs
|
|
||||||
naming? (pt, px)
|
|
||||||
want to keep (drawn) regions using px? or should I add another field to UiScalar/Vec
|
|
||||||
field could be best solution so redrawing stuff isn't needed & you can specify both as user
|
|
||||||
|
|
||||||
WidgetRef<W> or smth instead of Id
|
|
||||||
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
|
|
||||||
allows for compile time optimization if a widget wrapper's inner is known at compile time
|
|
||||||
and the id of inner is not needed anywhere
|
|
||||||
maybe introduce InnerWidget trait to allow for editors to expose & modify inner type
|
|
||||||
maybe could also store a parent widget and keep using InnerWidget trait? unsure if possible
|
|
||||||
|
|
||||||
really weird limitation:
|
|
||||||
I don't think you can currently remove an element from a parent and put it in a child of the same parent
|
|
||||||
because it removes the unused children after the entire parent redraw
|
|
||||||
but the child gets drawn during that, so it will think the child is still active !!!
|
|
||||||
or something like that idk, maybe I need a special enum for parent that includes a undecided state where it may or may not get redrawn by the parent
|
|
||||||
or just do ref counting and ensure all drawn things == 1 afterwards (seems like best way)
|
|
||||||
|
|
||||||
tags
|
|
||||||
vecs for each widget type?
|
|
||||||
26
readme.md
26
readme.md
@@ -1,26 +0,0 @@
|
|||||||
# iris
|
|
||||||
|
|
||||||
my take on a rust ui library (also my first 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)
|
|
||||||
2. very easy to use ignoring ergonomic ref counting
|
|
||||||
3. reasonably fast / efficient (a lot faster than electron, save battery life)
|
|
||||||
|
|
||||||
not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps
|
|
||||||
|
|
||||||
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)
|
|
||||||
- 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
|
|
||||||
sizable chance it gets a rewrite once I know everything I need and what seems to work best
|
|
||||||
165
src/base/mod.rs
Normal file
165
src/base/mod.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
primitive::{Axis, Painter, RoundedRectData, UIRegion},
|
||||||
|
UIColor, Widget, WidgetArrLike, WidgetFn, WidgetId, WidgetLike,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RoundedRect {
|
||||||
|
pub color: UIColor,
|
||||||
|
pub radius: f32,
|
||||||
|
pub thickness: f32,
|
||||||
|
pub inner_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoundedRect {
|
||||||
|
pub fn color(mut self, color: UIColor) -> Self {
|
||||||
|
self.color = color;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for RoundedRect {
|
||||||
|
fn draw(&self, painter: &mut Painter) {
|
||||||
|
painter.write(RoundedRectData {
|
||||||
|
color: self.color,
|
||||||
|
radius: self.radius,
|
||||||
|
thickness: self.thickness,
|
||||||
|
inner_radius: self.inner_radius,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Span {
|
||||||
|
pub elements: Vec<(Range<f32>, WidgetId)>,
|
||||||
|
pub axis: Axis,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Span {
|
||||||
|
fn draw(&self, painter: &mut Painter) {
|
||||||
|
for (span, child) in &self.elements {
|
||||||
|
let mut sub_region = UIRegion::full();
|
||||||
|
let view = sub_region.axis_mut(self.axis);
|
||||||
|
*view.top_left.anchor = span.start;
|
||||||
|
*view.bot_right.anchor = span.end;
|
||||||
|
painter.draw_within(child, sub_region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub fn proportioned(
|
||||||
|
axis: Axis,
|
||||||
|
elements: impl IntoIterator<Item = (WidgetId, impl UINum)>,
|
||||||
|
) -> Self {
|
||||||
|
// TODO: update
|
||||||
|
let elements = elements
|
||||||
|
.into_iter()
|
||||||
|
.map(|(w, r)| (w, r.to_f32()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let total: f32 = elements.iter().map(|(_, r)| r).sum();
|
||||||
|
let mut start = 0.0;
|
||||||
|
Self {
|
||||||
|
elements: elements
|
||||||
|
.into_iter()
|
||||||
|
.map(|(e, r)| {
|
||||||
|
let end = start + r / total;
|
||||||
|
let res = (start..end, e);
|
||||||
|
start = end;
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
axis,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Regioned {
|
||||||
|
region: UIRegion,
|
||||||
|
inner: WidgetId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Regioned {
|
||||||
|
fn draw(&self, painter: &mut Painter) {
|
||||||
|
painter.region.select(&self.region);
|
||||||
|
painter.draw(&self.inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Padding {
|
||||||
|
left: f32,
|
||||||
|
right: f32,
|
||||||
|
top: f32,
|
||||||
|
bottom: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Padding {
|
||||||
|
pub fn uniform(amt: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
left: amt,
|
||||||
|
right: amt,
|
||||||
|
top: amt,
|
||||||
|
bottom: amt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn region(&self) -> UIRegion {
|
||||||
|
let mut region = UIRegion::full();
|
||||||
|
region.top_left.offset.x += self.left;
|
||||||
|
region.top_left.offset.y += self.top;
|
||||||
|
region.bot_right.offset.x -= self.right;
|
||||||
|
region.bot_right.offset.y -= self.bottom;
|
||||||
|
region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UINum> From<T> for Padding {
|
||||||
|
fn from(amt: T) -> Self {
|
||||||
|
Self::uniform(amt.to_f32())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait WidgetUtil<W> {
|
||||||
|
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: WidgetLike> WidgetUtil<W::Widget> for W {
|
||||||
|
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned> {
|
||||||
|
WidgetFn(|ui| Regioned {
|
||||||
|
region: padding.into().region(),
|
||||||
|
inner: self.id(ui).erase_type(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait WidgetArrUtil<Wa: WidgetArrLike> {
|
||||||
|
fn span(self, axis: Axis, ratios: [impl UINum; Wa::LEN]) -> impl WidgetLike<Widget = Span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Wa: WidgetArrLike> WidgetArrUtil<Wa> for Wa {
|
||||||
|
fn span(self, axis: Axis, ratios: [impl UINum; Wa::LEN]) -> impl WidgetLike<Widget = Span> {
|
||||||
|
WidgetFn(move |ui| Span::proportioned(axis, self.ui(ui).ids().into_iter().zip(ratios)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UINum {
|
||||||
|
fn to_f32(self) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UINum for f32 {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UINum for u32 {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UINum for i32 {
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
self as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
use image::DynamicImage;
|
|
||||||
|
|
||||||
pub struct Image {
|
|
||||||
handle: TextureHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Image {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
painter.texture(&self.handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::abs(self.handle.size().x)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::abs(self.handle.size().y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn image(image: impl LoadableImage) -> impl WidgetFn<Image> {
|
|
||||||
let image = image.get_image().expect("Failed to load image");
|
|
||||||
move |ui| Image {
|
|
||||||
handle: ui.add_texture(image),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait LoadableImage {
|
|
||||||
fn get_image(self) -> Result<DynamicImage, String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LoadableImage for &str {
|
|
||||||
fn get_image(self) -> Result<DynamicImage, String> {
|
|
||||||
image::open(self).map_err(|e| format!("{e:?}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LoadableImage for String {
|
|
||||||
fn get_image(self) -> Result<DynamicImage, String> {
|
|
||||||
image::open(self).map_err(|e| format!("{e:?}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize> LoadableImage for &[u8; LEN] {
|
|
||||||
fn get_image(self) -> Result<DynamicImage, String> {
|
|
||||||
image::load_from_memory(self).map_err(|e| format!("{e:?}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LoadableImage for DynamicImage {
|
|
||||||
fn get_image(self) -> Result<DynamicImage, String> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct Masked {
|
|
||||||
pub inner: WidgetId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Masked {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
painter.set_mask(painter.region());
|
|
||||||
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,17 +0,0 @@
|
|||||||
mod image;
|
|
||||||
mod mask;
|
|
||||||
mod position;
|
|
||||||
mod ptr;
|
|
||||||
mod rect;
|
|
||||||
mod sense;
|
|
||||||
mod text;
|
|
||||||
mod trait_fns;
|
|
||||||
|
|
||||||
pub use image::*;
|
|
||||||
pub use mask::*;
|
|
||||||
pub use position::*;
|
|
||||||
pub use ptr::*;
|
|
||||||
pub use rect::*;
|
|
||||||
pub use sense::*;
|
|
||||||
pub use text::*;
|
|
||||||
pub use trait_fns::*;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct Aligned {
|
|
||||||
pub inner: WidgetId,
|
|
||||||
pub align: Align,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Aligned {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
let region =
|
|
||||||
UiRegion::from_ui_size_align(painter.size(&self.inner).to_uivec2(), self.align);
|
|
||||||
painter.widget_within(&self.inner, region);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,13 +0,0 @@
|
|||||||
mod align;
|
|
||||||
mod offset;
|
|
||||||
mod pad;
|
|
||||||
mod sized;
|
|
||||||
mod span;
|
|
||||||
mod stack;
|
|
||||||
|
|
||||||
pub use align::*;
|
|
||||||
pub use offset::*;
|
|
||||||
pub use pad::*;
|
|
||||||
pub use sized::*;
|
|
||||||
pub use span::*;
|
|
||||||
pub use stack::*;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct Offset {
|
|
||||||
pub inner: WidgetId,
|
|
||||||
pub amt: UiVec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Offset {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
let region = UiRegion::full().offset(self.amt);
|
|
||||||
painter.widget_within(&self.inner, region);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,83 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct Pad {
|
|
||||||
pub padding: Padding,
|
|
||||||
pub inner: WidgetId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Pad {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
painter.widget_within(&self.inner, self.padding.region());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
let width = self.padding.left + self.padding.right;
|
|
||||||
let height = self.padding.top + self.padding.bottom;
|
|
||||||
ctx.outer.abs.x -= width;
|
|
||||||
ctx.outer.abs.y -= height;
|
|
||||||
let mut size = ctx.width(&self.inner);
|
|
||||||
size.abs += width;
|
|
||||||
size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
let width = self.padding.left + self.padding.right;
|
|
||||||
let height = self.padding.top + self.padding.bottom;
|
|
||||||
ctx.outer.abs.x -= width;
|
|
||||||
ctx.outer.abs.y -= height;
|
|
||||||
let mut size = ctx.height(&self.inner);
|
|
||||||
size.abs += height;
|
|
||||||
size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Padding {
|
|
||||||
pub left: f32,
|
|
||||||
pub right: f32,
|
|
||||||
pub top: f32,
|
|
||||||
pub bottom: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Padding {
|
|
||||||
pub fn uniform(amt: impl UiNum) -> Self {
|
|
||||||
let amt = amt.to_f32();
|
|
||||||
Self {
|
|
||||||
left: amt,
|
|
||||||
right: amt,
|
|
||||||
top: amt,
|
|
||||||
bottom: amt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn region(&self) -> UiRegion {
|
|
||||||
let mut region = UiRegion::full();
|
|
||||||
region.top_left.abs.x += self.left;
|
|
||||||
region.top_left.abs.y += self.top;
|
|
||||||
region.bot_right.abs.x -= self.right;
|
|
||||||
region.bot_right.abs.y -= self.bottom;
|
|
||||||
region
|
|
||||||
}
|
|
||||||
pub fn x(amt: impl UiNum) -> Self {
|
|
||||||
let amt = amt.to_f32();
|
|
||||||
Self {
|
|
||||||
left: amt,
|
|
||||||
right: amt,
|
|
||||||
top: 0.0,
|
|
||||||
bottom: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn y(amt: impl UiNum) -> Self {
|
|
||||||
let amt = amt.to_f32();
|
|
||||||
Self {
|
|
||||||
left: 0.0,
|
|
||||||
right: 0.0,
|
|
||||||
top: amt,
|
|
||||||
bottom: amt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: UiNum> From<T> for Padding {
|
|
||||||
fn from(amt: T) -> Self {
|
|
||||||
Self::uniform(amt.to_f32())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct Sized {
|
|
||||||
pub inner: WidgetId,
|
|
||||||
pub x: Option<Len>,
|
|
||||||
pub y: Option<Len>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sized {
|
|
||||||
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
|
||||||
if let Some(x) = self.x {
|
|
||||||
let outer = ctx.outer.axis(Axis::X);
|
|
||||||
ctx.outer
|
|
||||||
.axis_mut(Axis::X)
|
|
||||||
.set(x.apply_rest().within_len(outer));
|
|
||||||
}
|
|
||||||
if let Some(y) = self.y {
|
|
||||||
let outer = ctx.outer.axis(Axis::Y);
|
|
||||||
ctx.outer
|
|
||||||
.axis_mut(Axis::Y)
|
|
||||||
.set(y.apply_rest().within_len(outer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Sized {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
painter.widget(&self.inner);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
self.apply_to_outer(ctx);
|
|
||||||
self.x.unwrap_or_else(|| ctx.width(&self.inner))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
self.apply_to_outer(ctx);
|
|
||||||
self.y.unwrap_or_else(|| ctx.height(&self.inner))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct Span {
|
|
||||||
pub children: Vec<WidgetId>,
|
|
||||||
pub dir: Dir,
|
|
||||||
pub gap: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Span {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
let total = self.len_sum(&mut painter.size_ctx());
|
|
||||||
let mut start = UiScalar::rel_min();
|
|
||||||
for child in &self.children {
|
|
||||||
let mut child_region = UiRegion::full();
|
|
||||||
let mut axis = child_region.axis_mut(self.dir.axis);
|
|
||||||
axis.top_left.set(start);
|
|
||||||
let len = painter.len_axis(child, self.dir.axis);
|
|
||||||
if len.rest > 0.0 {
|
|
||||||
let offset = UiScalar::new(total.rel, total.abs);
|
|
||||||
let rel_end = UiScalar::from_anchor(len.rest / total.rest);
|
|
||||||
start = rel_end.within(start, (UiScalar::rel_max() + start) - offset);
|
|
||||||
}
|
|
||||||
start.abs += len.abs;
|
|
||||||
start.rel += len.rel;
|
|
||||||
axis.bot_right.set(start);
|
|
||||||
if self.dir.sign == Sign::Neg {
|
|
||||||
child_region.flip(self.dir.axis);
|
|
||||||
}
|
|
||||||
painter.widget_within(child, child_region);
|
|
||||||
start.abs += self.gap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match self.dir.axis {
|
|
||||||
Axis::X => self.desired_len(ctx),
|
|
||||||
Axis::Y => self.desired_ortho(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match self.dir.axis {
|
|
||||||
Axis::X => self.desired_ortho(ctx),
|
|
||||||
Axis::Y => self.desired_len(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Span {
|
|
||||||
pub fn empty(dir: Dir) -> Self {
|
|
||||||
Self {
|
|
||||||
children: Vec::new(),
|
|
||||||
dir,
|
|
||||||
gap: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
|
||||||
self.gap = gap.to_f32();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
|
||||||
self.children.iter().fold(Len::abs(gap), |mut s, id| {
|
|
||||||
// it's tempting to subtract the abs & rel from the ctx outer,
|
|
||||||
// but that would create inconsistent sizing if you put
|
|
||||||
// a rest first vs last & only speed up in one direction.
|
|
||||||
// I think this is only solvable by restricting how you can
|
|
||||||
// compute size, bc currently you need child to define parent's
|
|
||||||
// sectioning and you need parent's sectioning to define child.
|
|
||||||
// Fortunately, that doesn't matter in most cases
|
|
||||||
let len = ctx.len_axis(id, self.dir.axis);
|
|
||||||
s += len;
|
|
||||||
s
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_len(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
let len = self.len_sum(ctx);
|
|
||||||
if len.rest == 0.0 && len.rel == 0.0 {
|
|
||||||
len
|
|
||||||
} else {
|
|
||||||
Len::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_ortho(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
// this is an awful hack to get text wrapping to work properly when in a downward span
|
|
||||||
if self.dir.axis == Axis::X {
|
|
||||||
// so....... this literally copies draw so that the lengths are correctly set in the
|
|
||||||
// context, which makes this slow and not cool
|
|
||||||
let total = self.len_sum(ctx);
|
|
||||||
let mut start = UiScalar::rel_min();
|
|
||||||
let outer = ctx.outer.axis(self.dir.axis);
|
|
||||||
let mut ortho_len = Len::ZERO;
|
|
||||||
for child in &self.children {
|
|
||||||
let mut child_region = UiRegion::full();
|
|
||||||
let mut axis = child_region.axis_mut(self.dir.axis);
|
|
||||||
axis.top_left.set(start);
|
|
||||||
let len = ctx.len_axis(child, self.dir.axis);
|
|
||||||
if len.rest > 0.0 {
|
|
||||||
let offset = UiScalar::new(total.rel, total.abs);
|
|
||||||
let rel_end = UiScalar::from_anchor(len.rest / total.rest);
|
|
||||||
start = rel_end.within(start, (UiScalar::rel_max() + start) - offset);
|
|
||||||
}
|
|
||||||
start.abs += len.abs;
|
|
||||||
start.rel += len.rel;
|
|
||||||
axis.bot_right.set(start);
|
|
||||||
// if self.dir.sign == Sign::Neg {
|
|
||||||
// child_region.flip(self.dir.axis);
|
|
||||||
// }
|
|
||||||
let scalar = child_region.size().axis(self.dir.axis);
|
|
||||||
ctx.outer
|
|
||||||
.axis_mut(self.dir.axis)
|
|
||||||
.set(scalar.within_len(outer));
|
|
||||||
let ortho = ctx.len_axis(child, !self.dir.axis);
|
|
||||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
|
||||||
if ortho.rel > 0.0 || ortho.rest > 0.0 {
|
|
||||||
ortho_len.rest = 1.0;
|
|
||||||
ortho_len.abs = 0.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ortho_len.abs = ortho_len.abs.max(ortho.abs);
|
|
||||||
start.abs += self.gap;
|
|
||||||
}
|
|
||||||
ortho_len
|
|
||||||
} else {
|
|
||||||
let mut ortho_len = Len::ZERO;
|
|
||||||
let ortho = !self.dir.axis;
|
|
||||||
for child in &self.children {
|
|
||||||
let len = ctx.len_axis(child, ortho);
|
|
||||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
|
||||||
if len.rel > 0.0 || len.rest > 0.0 {
|
|
||||||
ortho_len.rest = 1.0;
|
|
||||||
ortho_len.abs = 0.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ortho_len.abs = ortho_len.abs.max(len.abs);
|
|
||||||
}
|
|
||||||
ortho_len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SpanBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
|
||||||
pub children: Wa,
|
|
||||||
pub dir: Dir,
|
|
||||||
pub gap: f32,
|
|
||||||
_pd: PhantomData<Tag>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
|
|
||||||
for SpanBuilder<LEN, Wa, Tag>
|
|
||||||
{
|
|
||||||
type Output = Span;
|
|
||||||
|
|
||||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
|
||||||
Span {
|
|
||||||
children: self.children.ui(args.0).arr.to_vec(),
|
|
||||||
dir: self.dir,
|
|
||||||
gap: self.gap,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Tag> {
|
|
||||||
pub fn new(children: Wa, dir: Dir) -> Self {
|
|
||||||
Self {
|
|
||||||
children,
|
|
||||||
dir,
|
|
||||||
gap: 0.0,
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
|
||||||
self.gap = gap.to_f32();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct Stack {
|
|
||||||
pub children: Vec<WidgetId>,
|
|
||||||
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();
|
|
||||||
painter.widget(child);
|
|
||||||
}
|
|
||||||
for child in iter {
|
|
||||||
painter.next_layer();
|
|
||||||
painter.widget(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match self.size {
|
|
||||||
StackSize::Default => Len::default(),
|
|
||||||
StackSize::Child(i) => ctx.width(&self.children[i]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
match self.size {
|
|
||||||
StackSize::Default => Len::default(),
|
|
||||||
StackSize::Child(i) => ctx.height(&self.children[i]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub enum StackSize {
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
Child(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
|
||||||
pub children: Wa,
|
|
||||||
pub size: StackSize,
|
|
||||||
pub offset: usize,
|
|
||||||
_pd: PhantomData<Tag>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
|
|
||||||
for StackBuilder<LEN, Wa, Tag>
|
|
||||||
{
|
|
||||||
type Output = Stack;
|
|
||||||
|
|
||||||
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,
|
|
||||||
size: self.size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, Tag> {
|
|
||||||
pub fn new(children: Wa) -> Self {
|
|
||||||
Self {
|
|
||||||
children,
|
|
||||||
size: StackSize::default(),
|
|
||||||
offset: 0,
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(mut self, size: StackSize) -> Self {
|
|
||||||
self.size = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset_layer(mut self, offset: usize) -> Self {
|
|
||||||
self.offset = offset;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct WidgetPtr {
|
|
||||||
pub inner: Option<WidgetId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for WidgetPtr {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
if let Some(id) = &self.inner {
|
|
||||||
painter.widget(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
if let Some(id) = &self.inner {
|
|
||||||
ctx.width(id)
|
|
||||||
} else {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
if let Some(id) = &self.inner {
|
|
||||||
ctx.height(id)
|
|
||||||
} else {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct Rect {
|
|
||||||
pub color: UiColor,
|
|
||||||
pub radius: f32,
|
|
||||||
pub thickness: f32,
|
|
||||||
pub inner_radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Rect {
|
|
||||||
pub fn new(color: UiColor) -> Self {
|
|
||||||
Self {
|
|
||||||
color,
|
|
||||||
radius: 0.0,
|
|
||||||
inner_radius: 0.0,
|
|
||||||
thickness: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn color(mut self, color: UiColor) -> Self {
|
|
||||||
self.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn radius(mut self, radius: impl UiNum) -> Self {
|
|
||||||
self.radius = radius.to_f32();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Rect {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
painter.primitive(RectPrimitive {
|
|
||||||
color: self.color,
|
|
||||||
radius: self.radius,
|
|
||||||
thickness: self.thickness,
|
|
||||||
inner_radius: self.inner_radius,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::rest(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::rest(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rect(color: UiColor) -> Rect {
|
|
||||||
Rect::new(color)
|
|
||||||
}
|
|
||||||
@@ -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,108 +0,0 @@
|
|||||||
use std::marker::{PhantomData, Sized};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use cosmic_text::{Attrs, Family, Metrics};
|
|
||||||
|
|
||||||
pub trait TextBuilderOutput: Sized {
|
|
||||||
type Output;
|
|
||||||
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextBuilder<O = TextOutput> {
|
|
||||||
pub content: String,
|
|
||||||
pub attrs: TextAttrs,
|
|
||||||
_pd: PhantomData<O>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<O> TextBuilder<O> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
pub fn color(mut self, color: UiColor) -> Self {
|
|
||||||
self.attrs.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn family(mut self, family: Family<'static>) -> Self {
|
|
||||||
self.attrs.family = family;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn line_height(mut self, height: f32) -> Self {
|
|
||||||
self.attrs.line_height = height;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn text_align(mut self, align: Align) -> Self {
|
|
||||||
self.attrs.align = align;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
|
||||||
self.attrs.wrap = wrap;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn editable(self) -> TextBuilder<TextEditOutput> {
|
|
||||||
TextBuilder {
|
|
||||||
content: self.content,
|
|
||||||
attrs: self.attrs,
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextOutput;
|
|
||||||
impl TextBuilderOutput for TextOutput {
|
|
||||||
type Output = Text;
|
|
||||||
|
|
||||||
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
|
|
||||||
let mut buf = TextBuffer::new_empty(Metrics::new(
|
|
||||||
builder.attrs.font_size,
|
|
||||||
builder.attrs.line_height,
|
|
||||||
));
|
|
||||||
let font_system = &mut ui.data.text.font_system;
|
|
||||||
buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
|
|
||||||
let mut text = Text {
|
|
||||||
content: builder.content.into(),
|
|
||||||
view: TextView::new(buf, builder.attrs),
|
|
||||||
};
|
|
||||||
text.content.changed = false;
|
|
||||||
builder.attrs.apply(font_system, &mut text.view.buf, None);
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextEditOutput;
|
|
||||||
impl TextBuilderOutput for TextEditOutput {
|
|
||||||
type Output = TextEdit;
|
|
||||||
|
|
||||||
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
|
|
||||||
let buf = TextBuffer::new_empty(Metrics::new(
|
|
||||||
builder.attrs.font_size,
|
|
||||||
builder.attrs.line_height,
|
|
||||||
));
|
|
||||||
let mut text = TextEdit {
|
|
||||||
view: TextView::new(buf, builder.attrs),
|
|
||||||
cursor: None,
|
|
||||||
};
|
|
||||||
let font_system = &mut ui.data.text.font_system;
|
|
||||||
text.buf
|
|
||||||
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
|
|
||||||
builder.attrs.apply(font_system, &mut text.buf, None);
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<O: TextBuilderOutput> FnOnce<(&mut Ui,)> for TextBuilder<O> {
|
|
||||||
type Output = O::Output;
|
|
||||||
|
|
||||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
|
||||||
O::run(args.0, self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(content: impl Into<String>) -> TextBuilder {
|
|
||||||
TextBuilder {
|
|
||||||
content: content.into(),
|
|
||||||
attrs: TextAttrs::default(),
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,291 +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 {
|
|
||||||
UiRegion::from_size_align(
|
|
||||||
self.tex().map(|t| t.size()).unwrap_or(Vec2::ZERO),
|
|
||||||
self.align,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn content(&self) -> String {
|
|
||||||
self.buf
|
|
||||||
.lines
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.text())
|
|
||||||
// why is this needed?? what??
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for TextEdit {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
let tex = self.view.draw(&mut painter.size_ctx());
|
|
||||||
let region = text_region(&tex, self.align);
|
|
||||||
painter.texture_within(&tex.handle, region);
|
|
||||||
|
|
||||||
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),
|
|
||||||
UiRegion::from_size_align(size, Align::TopLeft)
|
|
||||||
.offset(offset)
|
|
||||||
.within(®ion),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
Len::abs(self.view.draw(ctx).size().x)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
Len::abs(self.view.draw(ctx).size().y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
mod build;
|
|
||||||
mod edit;
|
|
||||||
|
|
||||||
pub use build::*;
|
|
||||||
pub use edit::*;
|
|
||||||
|
|
||||||
use crate::{prelude::*, util::MutDetect};
|
|
||||||
use cosmic_text::{Attrs, Metrics, Shaping};
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
pub const SHAPING: Shaping = Shaping::Advanced;
|
|
||||||
|
|
||||||
pub struct Text {
|
|
||||||
pub content: MutDetect<String>,
|
|
||||||
view: TextView,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextView {
|
|
||||||
pub attrs: MutDetect<TextAttrs>,
|
|
||||||
pub buf: MutDetect<TextBuffer>,
|
|
||||||
// cache
|
|
||||||
tex: Option<TextTexture>,
|
|
||||||
width: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextView {
|
|
||||||
pub fn new(buf: TextBuffer, attrs: TextAttrs) -> Self {
|
|
||||||
Self {
|
|
||||||
attrs: attrs.into(),
|
|
||||||
buf: buf.into(),
|
|
||||||
tex: None,
|
|
||||||
width: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn draw(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
|
||||||
let width = if self.attrs.wrap {
|
|
||||||
Some(ctx.px_size().x)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if width == self.width
|
|
||||||
&& let Some(tex) = &self.tex
|
|
||||||
&& !self.attrs.changed
|
|
||||||
&& !self.buf.changed
|
|
||||||
{
|
|
||||||
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 tex = ctx.draw_text(&mut self.buf, &self.attrs);
|
|
||||||
self.tex = Some(tex.clone());
|
|
||||||
self.attrs.changed = false;
|
|
||||||
self.buf.changed = false;
|
|
||||||
tex
|
|
||||||
}
|
|
||||||
pub fn tex(&self) -> Option<&TextTexture> {
|
|
||||||
self.tex.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Text {
|
|
||||||
pub fn new(content: impl Into<String>) -> Self {
|
|
||||||
let attrs = TextAttrs::default();
|
|
||||||
let buf = TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height));
|
|
||||||
Self {
|
|
||||||
content: content.into().into(),
|
|
||||||
view: TextView::new(buf, attrs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn update_buf(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
|
||||||
if self.content.changed {
|
|
||||||
self.content.changed = false;
|
|
||||||
self.view.buf.set_text(
|
|
||||||
&mut ctx.text.font_system,
|
|
||||||
&self.content,
|
|
||||||
&Attrs::new().family(self.view.attrs.family),
|
|
||||||
SHAPING,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.view.draw(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Text {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
let tex = self.update_buf(&mut painter.size_ctx());
|
|
||||||
let region = text_region(&tex, self.align);
|
|
||||||
painter.texture_within(&tex.handle, region);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
Len::abs(self.update_buf(ctx).size().x)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
||||||
Len::abs(self.update_buf(ctx).size().y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_region(tex: &TextTexture, align: Align) -> UiRegion {
|
|
||||||
let tex_dims = tex.handle.size();
|
|
||||||
let mut region = UiRegion::from_size_align(tex.size(), align);
|
|
||||||
region.top_left.abs += tex.top_left;
|
|
||||||
region.bot_right.abs = region.top_left.abs + tex_dims;
|
|
||||||
region
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Text {
|
|
||||||
type Target = TextAttrs;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Text {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for TextView {
|
|
||||||
type Target = TextAttrs;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.attrs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for TextView {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.attrs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub trait CoreWidget<W, Tag> {
|
|
||||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>;
|
|
||||||
fn align(self, align: 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 offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset>;
|
|
||||||
fn scroll(self) -> impl WidgetIdFn<Offset>;
|
|
||||||
fn masked(self) -> impl WidgetFn<Masked>;
|
|
||||||
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack>;
|
|
||||||
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
|
|
||||||
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|
|
||||||
|ui| Pad {
|
|
||||||
padding: padding.into(),
|
|
||||||
inner: self.add(ui).any(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn align(self, align: Align) -> impl WidgetFn<Aligned> {
|
|
||||||
move |ui| Aligned {
|
|
||||||
inner: self.add(ui).any(),
|
|
||||||
align,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn center(self) -> impl WidgetFn<Aligned> {
|
|
||||||
self.align(Align::Center)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W::Widget> {
|
|
||||||
|ui| {
|
|
||||||
let id = self.add(ui);
|
|
||||||
ui.set_label(&id, label.into());
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> {
|
|
||||||
let size = size.into();
|
|
||||||
move |ui| Sized {
|
|
||||||
inner: self.add(ui).any(),
|
|
||||||
x: Some(size.x),
|
|
||||||
y: Some(size.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
|
||||||
let len = len.into();
|
|
||||||
move |ui| Sized {
|
|
||||||
inner: self.add(ui).any(),
|
|
||||||
x: Some(len),
|
|
||||||
y: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
|
|
||||||
let len = len.into();
|
|
||||||
move |ui| Sized {
|
|
||||||
inner: self.add(ui).any(),
|
|
||||||
x: None,
|
|
||||||
y: Some(len),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> {
|
|
||||||
move |ui| Offset {
|
|
||||||
inner: self.add(ui).any(),
|
|
||||||
amt: amt.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll(self) -> impl WidgetIdFn<Offset> {
|
|
||||||
self.offset(UiVec2::ZERO)
|
|
||||||
.edit_on(CursorSense::Scroll, |w, data| {
|
|
||||||
w.amt += UiVec2::abs(data.scroll_delta * 50.0);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn masked(self) -> impl WidgetFn<Masked> {
|
|
||||||
move |ui| Masked {
|
|
||||||
inner: self.add(ui).any(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
|
|
||||||
move |ui| Stack {
|
|
||||||
children: vec![w.add(ui).any(), self.add(ui).any()],
|
|
||||||
size: StackSize::Child(1),
|
|
||||||
offset: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack> {
|
|
||||||
move |ui| Stack {
|
|
||||||
children: vec![self.add(ui).any()],
|
|
||||||
size: StackSize::Child(0),
|
|
||||||
offset,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
|
|
||||||
fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag>;
|
|
||||||
fn stack(self) -> StackBuilder<LEN, Wa, Tag>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> CoreWidgetArr<LEN, Wa, Tag> for Wa {
|
|
||||||
fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag> {
|
|
||||||
SpanBuilder::new(self, dir)
|
|
||||||
}
|
|
||||||
fn stack(self) -> StackBuilder<LEN, Wa, Tag> {
|
|
||||||
StackBuilder::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
#![allow(clippy::multiple_bound_locations)]
|
|
||||||
|
|
||||||
/// stored in linear for sane manipulation
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, bytemuck::Zeroable, Debug)]
|
|
||||||
pub struct Color<T: ColorNum> {
|
|
||||||
pub r: T,
|
|
||||||
pub g: T,
|
|
||||||
pub b: T,
|
|
||||||
pub a: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ColorNum> Color<T> {
|
|
||||||
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
|
|
||||||
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
|
|
||||||
|
|
||||||
pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN);
|
|
||||||
pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN);
|
|
||||||
pub const YELLOW: Self = Self::rgb(T::MAX, T::MAX, T::MIN);
|
|
||||||
pub const LIME: Self = Self::rgb(T::MID, T::MAX, T::MIN);
|
|
||||||
pub const GREEN: Self = Self::rgb(T::MIN, T::MAX, T::MIN);
|
|
||||||
pub const TURQUOISE: Self = Self::rgb(T::MIN, T::MAX, T::MID);
|
|
||||||
pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX);
|
|
||||||
pub const SKY: Self = Self::rgb(T::MIN, T::MID, T::MAX);
|
|
||||||
pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX);
|
|
||||||
pub const PURPLE: Self = Self::rgb(T::MID, T::MIN, T::MAX);
|
|
||||||
pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX);
|
|
||||||
|
|
||||||
pub const NONE: Self = Self::new(T::MIN, T::MIN, T::MIN, T::MIN);
|
|
||||||
|
|
||||||
pub const fn new(r: T, g: T, b: T, a: T) -> Self {
|
|
||||||
Self { r, g, b, a }
|
|
||||||
}
|
|
||||||
pub const fn rgb(r: T, g: T, b: T) -> Self {
|
|
||||||
Self { r, g, b, a: T::MAX }
|
|
||||||
}
|
|
||||||
pub fn alpha(mut self, a: T) -> Self {
|
|
||||||
self.a = a;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_arr(self) -> [T; 4] {
|
|
||||||
[self.r, self.g, self.b, self.a]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const trait F32Conversion {
|
|
||||||
fn to(self) -> f32;
|
|
||||||
fn from(x: f32) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ColorNum {
|
|
||||||
const MIN: Self;
|
|
||||||
const MID: Self;
|
|
||||||
const MAX: Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ColorNum + F32Conversion> Color<T> {
|
|
||||||
pub fn mul_rgb(self, amt: impl F32Conversion) -> Self {
|
|
||||||
let amt = amt.to();
|
|
||||||
self.map_rgb(|x| T::from(x.to() * amt))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_rgb(self, amt: impl F32Conversion) -> Self {
|
|
||||||
let amt = amt.to();
|
|
||||||
self.map_rgb(|x| T::from(x.to() + amt))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn darker(self, amt: f32) -> Self {
|
|
||||||
self.mul_rgb(1.0 - amt)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn brighter(self, amt: f32) -> Self {
|
|
||||||
self.map_rgb(|x| {
|
|
||||||
let x = x.to();
|
|
||||||
T::from(x + (1.0 - x) * amt)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map_rgb(self, f: impl Fn(T) -> T) -> Self {
|
|
||||||
Self {
|
|
||||||
r: f(self.r),
|
|
||||||
g: f(self.g),
|
|
||||||
b: f(self.b),
|
|
||||||
a: self.a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn srgb(r: T, g: T, b: T) -> Self {
|
|
||||||
Self {
|
|
||||||
r: s_to_l(r),
|
|
||||||
g: s_to_l(g),
|
|
||||||
b: s_to_l(b),
|
|
||||||
a: T::MAX,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn s_to_l<T: F32Conversion>(x: T) -> T {
|
|
||||||
let x = x.to();
|
|
||||||
T::from(if x <= 0.0405 {
|
|
||||||
x / 12.92
|
|
||||||
} else {
|
|
||||||
((x + 0.055) / 1.055).powf(2.4)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ColorNum for u8 {
|
|
||||||
const MIN: Self = u8::MIN;
|
|
||||||
const MID: Self = u8::MAX / 2;
|
|
||||||
const MAX: Self = u8::MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ColorNum for f32 {
|
|
||||||
const MIN: Self = 0.0;
|
|
||||||
const MID: Self = 0.5;
|
|
||||||
const MAX: Self = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl bytemuck::Pod for Color<u8> {}
|
|
||||||
|
|
||||||
impl const F32Conversion for f32 {
|
|
||||||
fn to(self) -> f32 {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn from(x: f32) -> Self {
|
|
||||||
x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl const F32Conversion for u8 {
|
|
||||||
fn to(self) -> f32 {
|
|
||||||
self as f32 / 255.0
|
|
||||||
}
|
|
||||||
fn from(x: f32) -> Self {
|
|
||||||
(x * 255.0).clamp(0.0, 255.0) as Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,257 +0,0 @@
|
|||||||
use std::ops::{Index, IndexMut};
|
|
||||||
|
|
||||||
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
|
|
||||||
|
|
||||||
struct LayerNode {
|
|
||||||
next: Ptr,
|
|
||||||
prev: Ptr,
|
|
||||||
child: Option<Child>,
|
|
||||||
data: Layer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
enum Ptr {
|
|
||||||
/// continue on same level
|
|
||||||
Next(usize),
|
|
||||||
/// go back to parent
|
|
||||||
Parent(usize),
|
|
||||||
/// end
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TODO: currently this does not ever free layers
|
|
||||||
/// is that realistically desired?
|
|
||||||
pub struct Layers {
|
|
||||||
vec: Vec<LayerNode>,
|
|
||||||
/// 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 {
|
|
||||||
Self {
|
|
||||||
vec: vec![LayerNode::head()],
|
|
||||||
last: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.vec.clear();
|
|
||||||
self.vec.push(LayerNode::head());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, node: LayerNode) -> usize {
|
|
||||||
let i = self.vec.len();
|
|
||||||
self.vec.push(node);
|
|
||||||
i
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&mut self, i: usize) -> usize {
|
|
||||||
if let Ptr::Next(i) = self.vec[i].next {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
let i_new = self.push(LayerNode::new(
|
|
||||||
Layer::default(),
|
|
||||||
self.vec[i].next,
|
|
||||||
Ptr::Next(i),
|
|
||||||
));
|
|
||||||
self.vec[i].next = Ptr::Next(i_new);
|
|
||||||
self.vec[i_new].prev = Ptr::Next(i);
|
|
||||||
match self.vec[i_new].next {
|
|
||||||
Ptr::Next(i) => self.vec[i].prev = Ptr::Next(i_new),
|
|
||||||
Ptr::Parent(i) => self.vec[i].child.as_mut().unwrap().tail = i_new,
|
|
||||||
Ptr::None => self.last = i_new,
|
|
||||||
}
|
|
||||||
i_new
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn child(&mut self, i: usize) -> usize {
|
|
||||||
if let Some(c) = self.vec[i].child {
|
|
||||||
return c.head;
|
|
||||||
}
|
|
||||||
let i_child = self.push(LayerNode::new(
|
|
||||||
Layer::default(),
|
|
||||||
Ptr::Parent(i),
|
|
||||||
Ptr::Parent(i),
|
|
||||||
));
|
|
||||||
self.vec[i].child = Some(Child {
|
|
||||||
head: i_child,
|
|
||||||
tail: i_child,
|
|
||||||
});
|
|
||||||
i_child
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_> {
|
|
||||||
LayerIteratorMut::new(&mut self.vec, self.last)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (usize, &Layer)> {
|
|
||||||
self.indices().map(|i| (i, &self.vec[i].data))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn indices(&self) -> LayerIndexIterator<'_> {
|
|
||||||
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 {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Index<usize> for Layers {
|
|
||||||
type Output = Layer;
|
|
||||||
|
|
||||||
fn index(&self, index: usize) -> &Self::Output {
|
|
||||||
&self.vec[index].data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IndexMut<usize> for Layers {
|
|
||||||
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 {
|
|
||||||
Self {
|
|
||||||
next,
|
|
||||||
prev,
|
|
||||||
child: None,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn head() -> Self {
|
|
||||||
Self::new(Layer::default(), Ptr::None, Ptr::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LayerIteratorMut<'a> {
|
|
||||||
inner: LayerIndexIterator<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for LayerIteratorMut<'a> {
|
|
||||||
type Item = (usize, &'a mut Layer);
|
|
||||||
|
|
||||||
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) };
|
|
||||||
Some((i, layer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DoubleEndedIterator for LayerIteratorMut<'a> {
|
|
||||||
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) };
|
|
||||||
Some((i, layer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LayerIteratorMut<'a> {
|
|
||||||
fn new(vec: &'a mut Vec<LayerNode>, last: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: LayerIndexIterator::new(vec, last),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LayerIndexIterator<'a> {
|
|
||||||
next: Option<usize>,
|
|
||||||
next_back: Option<usize>,
|
|
||||||
vec: &'a Vec<LayerNode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for LayerIndexIterator<'a> {
|
|
||||||
type Item = usize;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let ret_i = self.next?;
|
|
||||||
let node = &self.vec[ret_i];
|
|
||||||
self.next = if let Some(c) = node.child {
|
|
||||||
Some(c.head)
|
|
||||||
} else if let Ptr::Next(i) = node.next {
|
|
||||||
Some(i)
|
|
||||||
} else if let Ptr::Parent(i) = node.next {
|
|
||||||
let mut node = &self.vec[i];
|
|
||||||
while let Ptr::Parent(i) = node.next {
|
|
||||||
node = &self.vec[i];
|
|
||||||
}
|
|
||||||
if let Ptr::Next(i) = node.next {
|
|
||||||
Some(i)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if self.next_back.unwrap() == ret_i {
|
|
||||||
self.next = None;
|
|
||||||
self.next_back = None;
|
|
||||||
}
|
|
||||||
Some(ret_i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
|
|
||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
|
||||||
let ret_i = self.next_back?;
|
|
||||||
let node = &self.vec[ret_i];
|
|
||||||
self.next_back = if let Ptr::Next(mut i) = node.prev {
|
|
||||||
while let Some(c) = self.vec[i].child {
|
|
||||||
i = c.tail
|
|
||||||
}
|
|
||||||
Some(i)
|
|
||||||
} else if let Ptr::Parent(i) = node.prev {
|
|
||||||
Some(i)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if self.next.unwrap() == ret_i {
|
|
||||||
self.next = None;
|
|
||||||
self.next_back = None;
|
|
||||||
}
|
|
||||||
Some(ret_i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LayerIndexIterator<'a> {
|
|
||||||
fn new(vec: &'a Vec<LayerNode>, last: usize) -> Self {
|
|
||||||
let mut last = last;
|
|
||||||
while let Some(c) = vec[last].child {
|
|
||||||
last = c.tail;
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
next: Some(0),
|
|
||||||
next_back: Some(last),
|
|
||||||
vec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
//! tree structure for masking
|
|
||||||
|
|
||||||
use crate::layout::UiRegion;
|
|
||||||
|
|
||||||
pub struct Masks {
|
|
||||||
data: Vec<MaskNode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct MaskPtr(u32);
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct MaskNode {
|
|
||||||
/// TODO: this is just a rect for now,
|
|
||||||
/// but would like to support arbitrary masks
|
|
||||||
/// at some point; custom shader
|
|
||||||
/// would probably handle that case
|
|
||||||
/// bc you'd need to render to a special target
|
|
||||||
/// anyways
|
|
||||||
region: UiRegion,
|
|
||||||
prev: MaskPtr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MaskPtr {
|
|
||||||
const NONE: Self = Self(u32::MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Masks {
|
|
||||||
pub fn push(&mut self, parent: MaskPtr, region: UiRegion) -> MaskPtr {
|
|
||||||
match parent.0 {
|
|
||||||
_ => {
|
|
||||||
}
|
|
||||||
u32::MAX => {
|
|
||||||
let i = self.data.len();
|
|
||||||
self.data.push(MaskNode {
|
|
||||||
region,
|
|
||||||
prev: parent,
|
|
||||||
});
|
|
||||||
MaskPtr(i as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(&mut self, i: usize) {}
|
|
||||||
}
|
|
||||||
@@ -1,33 +1,8 @@
|
|||||||
mod color;
|
|
||||||
mod event;
|
|
||||||
mod id;
|
|
||||||
mod layer;
|
|
||||||
mod module;
|
|
||||||
mod num;
|
|
||||||
mod orientation;
|
|
||||||
mod painter;
|
|
||||||
mod pos;
|
|
||||||
mod text;
|
|
||||||
mod texture;
|
|
||||||
mod ui;
|
mod ui;
|
||||||
mod vec2;
|
|
||||||
mod widget;
|
mod widget;
|
||||||
mod widgets;
|
|
||||||
|
|
||||||
pub use color::*;
|
|
||||||
pub use event::*;
|
|
||||||
pub use id::*;
|
|
||||||
pub use layer::*;
|
|
||||||
pub use module::*;
|
|
||||||
pub use num::*;
|
|
||||||
pub use orientation::*;
|
|
||||||
pub use painter::*;
|
|
||||||
pub use pos::*;
|
|
||||||
pub use text::*;
|
|
||||||
pub use texture::*;
|
|
||||||
pub use ui::*;
|
pub use ui::*;
|
||||||
pub use vec2::*;
|
|
||||||
pub use widget::*;
|
pub use widget::*;
|
||||||
pub use widgets::*;
|
|
||||||
|
|
||||||
pub type UiColor = Color<u8>;
|
use crate::primitive::Color;
|
||||||
|
pub type UIColor = Color<u8>;
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
use std::any::{Any, TypeId};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
layout::WidgetInstance,
|
|
||||||
util::{HashMap, Id},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[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_move(&mut self, inst: &WidgetInstance) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Modules {
|
|
||||||
map: HashMap<TypeId, Box<dyn UiModule>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Modules {
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (dyn UiModule + 'static)> {
|
|
||||||
self.map.values_mut().map(|m| m.as_mut())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut<M: UiModule + Default>(&mut self) -> &mut M {
|
|
||||||
let rf = self
|
|
||||||
.map
|
|
||||||
.entry(TypeId::of::<M>())
|
|
||||||
.or_insert_with(|| Box::new(M::default()))
|
|
||||||
.as_mut();
|
|
||||||
let any: &mut dyn Any = &mut *rf;
|
|
||||||
any.downcast_mut().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
use std::ops::Not;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
layout::{UiNum, UiScalar, UiVec2, Vec2, vec2},
|
|
||||||
util::impl_op,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum Axis {
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Not for Axis {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn not(self) -> Self::Output {
|
|
||||||
match self {
|
|
||||||
Self::X => Self::Y,
|
|
||||||
Self::Y => Self::X,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
||||||
pub struct Dir {
|
|
||||||
pub axis: Axis,
|
|
||||||
pub sign: Sign,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dir {
|
|
||||||
pub const fn new(axis: Axis, dir: Sign) -> Self {
|
|
||||||
Self { axis, sign: dir }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const LEFT: Self = Self::new(Axis::X, Sign::Neg);
|
|
||||||
pub const RIGHT: Self = Self::new(Axis::X, Sign::Pos);
|
|
||||||
pub const UP: Self = Self::new(Axis::Y, Sign::Neg);
|
|
||||||
pub const DOWN: Self = Self::new(Axis::Y, Sign::Pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
||||||
pub enum Sign {
|
|
||||||
Neg,
|
|
||||||
Pos,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vec2 {
|
|
||||||
pub fn axis(&self, axis: Axis) -> f32 {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
x: match axis {
|
|
||||||
Axis::X => aligned,
|
|
||||||
Axis::Y => ortho,
|
|
||||||
},
|
|
||||||
y: match axis {
|
|
||||||
Axis::Y => aligned,
|
|
||||||
Axis::X => ortho,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Align {
|
|
||||||
TopLeft,
|
|
||||||
Top,
|
|
||||||
TopRight,
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
Right,
|
|
||||||
BotLeft,
|
|
||||||
Bot,
|
|
||||||
BotRight,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Align {
|
|
||||||
pub const fn rel(&self) -> Vec2 {
|
|
||||||
match self {
|
|
||||||
Self::TopLeft => vec2(0.0, 0.0),
|
|
||||||
Self::Top => vec2(0.5, 0.0),
|
|
||||||
Self::TopRight => vec2(1.0, 0.0),
|
|
||||||
Self::Left => vec2(0.0, 0.5),
|
|
||||||
Self::Center => vec2(0.5, 0.5),
|
|
||||||
Self::Right => vec2(1.0, 0.5),
|
|
||||||
Self::BotLeft => vec2(0.0, 1.0),
|
|
||||||
Self::Bot => vec2(0.5, 1.0),
|
|
||||||
Self::BotRight => vec2(1.0, 1.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
|
||||||
pub struct Size {
|
|
||||||
pub x: Len,
|
|
||||||
pub y: Len,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct Len {
|
|
||||||
pub abs: f32,
|
|
||||||
pub rel: f32,
|
|
||||||
pub rest: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: UiNum> From<N> for Len {
|
|
||||||
fn from(value: N) -> Self {
|
|
||||||
Len::abs(value.to_f32())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
|
|
||||||
fn from((x, y): (Nx, Ny)) -> Self {
|
|
||||||
Self {
|
|
||||||
x: x.into(),
|
|
||||||
y: y.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Size {
|
|
||||||
pub const ZERO: Self = Self {
|
|
||||||
x: Len::ZERO,
|
|
||||||
y: Len::ZERO,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn abs(v: Vec2) -> Self {
|
|
||||||
Self {
|
|
||||||
x: Len::abs(v.x),
|
|
||||||
y: Len::abs(v.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rel(v: Vec2) -> Self {
|
|
||||||
Self {
|
|
||||||
x: Len::rel(v.x),
|
|
||||||
y: Len::rel(v.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rest(v: Vec2) -> Self {
|
|
||||||
Self {
|
|
||||||
x: Len::rest(v.x),
|
|
||||||
y: Len::rest(v.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_uivec2(self) -> UiVec2 {
|
|
||||||
UiVec2::from_scalars(self.x.apply_rest(), self.y.apply_rest())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
|
|
||||||
match axis {
|
|
||||||
Axis::X => Self {
|
|
||||||
x: aligned,
|
|
||||||
y: ortho,
|
|
||||||
},
|
|
||||||
Axis::Y => Self {
|
|
||||||
x: ortho,
|
|
||||||
y: aligned,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis(&self, axis: Axis) -> Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Len {
|
|
||||||
pub const ZERO: Self = Self {
|
|
||||||
abs: 0.0,
|
|
||||||
rel: 0.0,
|
|
||||||
rest: 0.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn apply_rest(&self) -> UiScalar {
|
|
||||||
UiScalar {
|
|
||||||
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
|
|
||||||
abs: self.abs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn abs(abs: impl UiNum) -> Self {
|
|
||||||
Self {
|
|
||||||
abs: abs.to_f32(),
|
|
||||||
rel: 0.0,
|
|
||||||
rest: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn rel(rel: impl UiNum) -> Self {
|
|
||||||
Self {
|
|
||||||
abs: 0.0,
|
|
||||||
rel: rel.to_f32(),
|
|
||||||
rest: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn rest(ratio: impl UiNum) -> Self {
|
|
||||||
Self {
|
|
||||||
abs: 0.0,
|
|
||||||
rel: 0.0,
|
|
||||||
rest: ratio.to_f32(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod len_fns {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub fn abs(abs: impl UiNum) -> Len {
|
|
||||||
Len {
|
|
||||||
abs: abs.to_f32(),
|
|
||||||
rel: 0.0,
|
|
||||||
rest: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn rel(rel: impl UiNum) -> Len {
|
|
||||||
Len {
|
|
||||||
abs: 0.0,
|
|
||||||
rel: rel.to_f32(),
|
|
||||||
rest: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn rest(ratio: impl UiNum) -> Len {
|
|
||||||
Len {
|
|
||||||
abs: 0.0,
|
|
||||||
rel: 0.0,
|
|
||||||
rest: ratio.to_f32(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_op!(Len Add add; abs rel rest);
|
|
||||||
impl_op!(Len Sub sub; abs rel rest);
|
|
||||||
|
|
||||||
impl_op!(Size Add add; x y);
|
|
||||||
impl_op!(Size Sub sub; x y);
|
|
||||||
|
|
||||||
impl Default for Len {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::rest(1.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Size {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "({}, {})", self.x, self.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Len {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
if self.abs != 0.0 {
|
|
||||||
write!(f, "{} abs;", self.abs)?;
|
|
||||||
}
|
|
||||||
if self.rel != 0.0 {
|
|
||||||
write!(f, "{} rel;", self.abs)?;
|
|
||||||
}
|
|
||||||
if self.rest != 0.0 {
|
|
||||||
write!(f, "{} leftover;", self.abs)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,538 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
layout::{
|
|
||||||
Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, TextTexture,
|
|
||||||
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets,
|
|
||||||
},
|
|
||||||
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
|
||||||
util::{HashMap, HashSet, Id, TrackedArena},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Painter<'a, 'c> {
|
|
||||||
ctx: &'a mut PainterCtx<'c>,
|
|
||||||
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
|
|
||||||
pub layer: usize,
|
|
||||||
id: Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PainterCtx<'a> {
|
|
||||||
pub widgets: &'a Widgets,
|
|
||||||
pub active: &'a mut HashMap<Id, WidgetInstance>,
|
|
||||||
pub layers: &'a mut Layers,
|
|
||||||
pub textures: &'a mut Textures,
|
|
||||||
pub masks: &'a mut TrackedArena<Mask, u32>,
|
|
||||||
pub text: &'a mut TextData,
|
|
||||||
pub screen_size: Vec2,
|
|
||||||
pub modules: &'a mut Modules,
|
|
||||||
pub px_dependent: &'a mut HashSet<Id>,
|
|
||||||
pub cache_width: HashMap<Id, (UiVec2, Len)>,
|
|
||||||
pub cache_height: HashMap<Id, (UiVec2, Len)>,
|
|
||||||
draw_started: HashSet<Id>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ResizeRef {
|
|
||||||
x: Option<(Id, (UiVec2, Len))>,
|
|
||||||
y: Option<(Id, (UiVec2, Len))>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WidgetInstance {
|
|
||||||
pub id: Id,
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub parent: Option<Id>,
|
|
||||||
pub textures: Vec<TextureHandle>,
|
|
||||||
pub primitives: Vec<PrimitiveHandle>,
|
|
||||||
pub children: Vec<Id>,
|
|
||||||
pub resize: ResizeRef,
|
|
||||||
pub mask: MaskIdx,
|
|
||||||
pub layer: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PainterData {
|
|
||||||
pub widgets: Widgets,
|
|
||||||
pub active: HashMap<Id, WidgetInstance>,
|
|
||||||
pub layers: Layers,
|
|
||||||
pub textures: Textures,
|
|
||||||
pub text: TextData,
|
|
||||||
pub output_size: Vec2,
|
|
||||||
pub modules: Modules,
|
|
||||||
pub px_dependent: HashSet<Id>,
|
|
||||||
pub masks: TrackedArena<Mask, u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PainterCtx<'a> {
|
|
||||||
pub fn new(data: &'a mut PainterData) -> Self {
|
|
||||||
Self {
|
|
||||||
widgets: &data.widgets,
|
|
||||||
active: &mut data.active,
|
|
||||||
layers: &mut data.layers,
|
|
||||||
textures: &mut data.textures,
|
|
||||||
text: &mut data.text,
|
|
||||||
screen_size: data.output_size,
|
|
||||||
modules: &mut data.modules,
|
|
||||||
px_dependent: &mut data.px_dependent,
|
|
||||||
masks: &mut data.masks,
|
|
||||||
cache_width: Default::default(),
|
|
||||||
cache_height: Default::default(),
|
|
||||||
draw_started: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redraw(&mut self, id: Id) {
|
|
||||||
if self.draw_started.contains(&id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(active) = self.active.get(&id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: this is stupid having 2 of these
|
|
||||||
if let Some((rid, (outer, old_desired))) = active.resize.x {
|
|
||||||
let new_desired = SizeCtx {
|
|
||||||
source: id,
|
|
||||||
cache_width: &mut self.cache_width,
|
|
||||||
cache_height: &mut self.cache_height,
|
|
||||||
text: self.text,
|
|
||||||
textures: self.textures,
|
|
||||||
widgets: self.widgets,
|
|
||||||
outer,
|
|
||||||
output_size: self.screen_size,
|
|
||||||
checked_width: &mut Default::default(),
|
|
||||||
checked_height: &mut Default::default(),
|
|
||||||
px_dependent: &mut Default::default(),
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
.width_inner(id);
|
|
||||||
if new_desired != old_desired {
|
|
||||||
self.redraw(rid);
|
|
||||||
if self.draw_started.contains(&id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(active) = self.active.get(&id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if let Some((rid, (outer, old_desired))) = active.resize.y {
|
|
||||||
let new_desired = SizeCtx {
|
|
||||||
source: id,
|
|
||||||
cache_width: &mut self.cache_width,
|
|
||||||
cache_height: &mut self.cache_height,
|
|
||||||
text: self.text,
|
|
||||||
textures: self.textures,
|
|
||||||
widgets: self.widgets,
|
|
||||||
outer,
|
|
||||||
output_size: self.screen_size,
|
|
||||||
checked_width: &mut Default::default(),
|
|
||||||
checked_height: &mut Default::default(),
|
|
||||||
px_dependent: &mut Default::default(),
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
.height_inner(id);
|
|
||||||
if new_desired != old_desired {
|
|
||||||
self.redraw(rid);
|
|
||||||
if self.draw_started.contains(&id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(active) = self.remove(id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.draw_inner(
|
|
||||||
active.layer,
|
|
||||||
id,
|
|
||||||
active.region,
|
|
||||||
active.parent,
|
|
||||||
active.mask,
|
|
||||||
Some(active.children),
|
|
||||||
);
|
|
||||||
self.active.get_mut(&id).unwrap().resize = active.resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(&mut self, id: Id) {
|
|
||||||
self.draw_started.clear();
|
|
||||||
self.layers.clear();
|
|
||||||
self.draw_inner(0, id, UiRegion::full(), None, MaskIdx::NONE, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_inner(
|
|
||||||
&mut self,
|
|
||||||
layer: usize,
|
|
||||||
id: Id,
|
|
||||||
region: UiRegion,
|
|
||||||
parent: Option<Id>,
|
|
||||||
mask: MaskIdx,
|
|
||||||
old_children: Option<Vec<Id>>,
|
|
||||||
) {
|
|
||||||
// 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
|
|
||||||
// but this has a very weird issue where you can't move widgets unless u remove first
|
|
||||||
// so swapping is impossible rn I think?
|
|
||||||
// there's definitely better solutions like a counter (>1 = panic) but don't care rn
|
|
||||||
if self.draw_started.contains(&id) {
|
|
||||||
panic!(
|
|
||||||
"Cannot draw the same widget ({}) twice (1)",
|
|
||||||
self.widgets.data(&id).unwrap().label
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut old_children = old_children.unwrap_or_default();
|
|
||||||
let mut resize = ResizeRef::default();
|
|
||||||
if let Some(active) = self.active.get_mut(&id) {
|
|
||||||
if active.parent != parent {
|
|
||||||
panic!("Cannot draw the same widget twice (2)");
|
|
||||||
}
|
|
||||||
if active.region == region {
|
|
||||||
return;
|
|
||||||
} else if active.region.size() == region.size() {
|
|
||||||
// TODO: epsilon?
|
|
||||||
let from = active.region;
|
|
||||||
self.mov(id, from, region);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let active = self.remove(id).unwrap();
|
|
||||||
old_children = active.children;
|
|
||||||
resize = active.resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.draw_started.insert(id);
|
|
||||||
|
|
||||||
let mut painter = Painter {
|
|
||||||
region,
|
|
||||||
mask,
|
|
||||||
layer,
|
|
||||||
id,
|
|
||||||
textures: Vec::new(),
|
|
||||||
primitives: Vec::new(),
|
|
||||||
ctx: self,
|
|
||||||
children: Vec::new(),
|
|
||||||
children_width: Default::default(),
|
|
||||||
children_height: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// draw widgets
|
|
||||||
painter.ctx.widgets.get_dyn_dynamic(id).draw(&mut painter);
|
|
||||||
|
|
||||||
let children_width = painter.children_width;
|
|
||||||
let children_height = painter.children_height;
|
|
||||||
|
|
||||||
// add to active
|
|
||||||
let instance = WidgetInstance {
|
|
||||||
id,
|
|
||||||
region,
|
|
||||||
parent,
|
|
||||||
textures: painter.textures,
|
|
||||||
primitives: painter.primitives,
|
|
||||||
children: painter.children,
|
|
||||||
resize,
|
|
||||||
mask: painter.mask,
|
|
||||||
layer,
|
|
||||||
};
|
|
||||||
for (cid, outer) in children_width {
|
|
||||||
if let Some(w) = self.active.get_mut(&cid)
|
|
||||||
&& w.resize.x.is_none()
|
|
||||||
{
|
|
||||||
w.resize.x = Some((id, outer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (cid, outer) in children_height {
|
|
||||||
if let Some(w) = self.active.get_mut(&cid)
|
|
||||||
&& w.resize.y.is_none()
|
|
||||||
{
|
|
||||||
w.resize.y = Some((id, outer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for c in &old_children {
|
|
||||||
if !instance.children.contains(c) {
|
|
||||||
self.remove_rec(*c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for m in self.modules.iter_mut() {
|
|
||||||
m.on_draw(&instance);
|
|
||||||
}
|
|
||||||
self.active.insert(id, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mov(&mut self, id: Id, 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);
|
|
||||||
*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();
|
|
||||||
for child in children {
|
|
||||||
self.mov(child, from, to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: instance textures are cleared and self.textures freed
|
|
||||||
fn remove(&mut self, id: Id) -> Option<WidgetInstance> {
|
|
||||||
let mut inst = self.active.remove(&id);
|
|
||||||
if let Some(inst) = &mut inst {
|
|
||||||
for h in &inst.primitives {
|
|
||||||
let mask = self.layers.free(h);
|
|
||||||
if mask != MaskIdx::NONE {
|
|
||||||
self.masks.remove(mask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inst.textures.clear();
|
|
||||||
self.textures.free();
|
|
||||||
for m in self.modules.iter_mut() {
|
|
||||||
m.on_undraw(inst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.px_dependent.remove(&id);
|
|
||||||
inst
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_rec(&mut self, id: Id) -> Option<WidgetInstance> {
|
|
||||||
let inst = self.remove(id);
|
|
||||||
if let Some(inst) = &inst {
|
|
||||||
for c in &inst.children {
|
|
||||||
self.remove_rec(*c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inst
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'c> Painter<'a, 'c> {
|
|
||||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
|
||||||
let h = self.ctx.layers.write(
|
|
||||||
self.layer,
|
|
||||||
PrimitiveInst {
|
|
||||||
id: self.id,
|
|
||||||
primitive,
|
|
||||||
region,
|
|
||||||
mask_idx: self.mask,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if self.mask != MaskIdx::NONE {
|
|
||||||
// TODO: I have no clue if this works at all :joy:
|
|
||||||
self.ctx.masks.push_ref(self.mask);
|
|
||||||
}
|
|
||||||
self.primitives.push(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a primitive to be rendered
|
|
||||||
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
|
|
||||||
self.primitive_at(primitive, self.region)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
|
||||||
self.primitive_at(primitive, region.within(&self.region));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_mask(&mut self, region: UiRegion) {
|
|
||||||
assert!(self.mask == MaskIdx::NONE);
|
|
||||||
self.mask = self.ctx.masks.push(Mask { region });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
|
||||||
pub fn widget<W>(&mut self, id: &WidgetId<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) {
|
|
||||||
self.widget_at(id, region.within(&self.region));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn widget_at<W>(&mut self, id: &WidgetId<W>, region: UiRegion) {
|
|
||||||
self.children.push(id.id);
|
|
||||||
self.ctx
|
|
||||||
.draw_inner(self.layer, id.id, region, Some(self.id), self.mask, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
|
||||||
self.textures.push(handle.clone());
|
|
||||||
self.primitive_at(handle.primitive(), region.within(&self.region));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture(&mut self, handle: &TextureHandle) {
|
|
||||||
self.textures.push(handle.clone());
|
|
||||||
self.primitive(handle.primitive());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
|
||||||
self.textures.push(handle.clone());
|
|
||||||
self.primitive_at(handle.primitive(), region);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns (handle, offset from top left)
|
|
||||||
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
|
|
||||||
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn region(&self) -> UiRegion {
|
|
||||||
self.region
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
|
|
||||||
self.size_ctx().size(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len_axis<W>(&mut self, id: &WidgetId<W>, axis: Axis) -> Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.size_ctx().width(id),
|
|
||||||
Axis::Y => self.size_ctx().height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
|
|
||||||
SizeCtx {
|
|
||||||
text: self.ctx.text,
|
|
||||||
textures: self.ctx.textures,
|
|
||||||
widgets: self.ctx.widgets,
|
|
||||||
output_size: self.ctx.screen_size,
|
|
||||||
px_dependent: self.ctx.px_dependent,
|
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn px_size(&mut self) -> Vec2 {
|
|
||||||
self.ctx.px_dependent.insert(self.id);
|
|
||||||
self.region.size().to_abs(self.ctx.screen_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_data(&mut self) -> &mut TextData {
|
|
||||||
self.ctx.text
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn child_layer(&mut self) {
|
|
||||||
self.layer = self.ctx.layers.child(self.layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_layer(&mut self) {
|
|
||||||
self.layer = self.ctx.layers.next(self.layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn label(&self) -> &str {
|
|
||||||
&self.ctx.widgets.data(&self.id).unwrap().label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SizeCtx<'a> {
|
|
||||||
pub text: &'a mut TextData,
|
|
||||||
pub textures: &'a mut Textures,
|
|
||||||
source: Id,
|
|
||||||
widgets: &'a Widgets,
|
|
||||||
px_dependent: &'a mut HashSet<Id>,
|
|
||||||
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)>,
|
|
||||||
/// TODO: should this be pub? rn used for sized
|
|
||||||
pub outer: UiVec2,
|
|
||||||
output_size: Vec2,
|
|
||||||
id: Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizeCtx<'_> {
|
|
||||||
pub fn id(&self) -> &Id {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source(&self) -> &Id {
|
|
||||||
&self.source
|
|
||||||
}
|
|
||||||
|
|
||||||
fn width_inner(&mut self, id: Id) -> Len {
|
|
||||||
if let Some(&(outer, len)) = self.cache_width.get(&id)
|
|
||||||
&& outer == self.outer
|
|
||||||
{
|
|
||||||
self.checked_width.insert(id, (self.outer, len));
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
let self_outer = self.outer;
|
|
||||||
let self_id = self.id;
|
|
||||||
self.id = id;
|
|
||||||
let len = self.widgets.get_dyn_dynamic(id).desired_width(self);
|
|
||||||
self.outer = self_outer;
|
|
||||||
self.id = self_id;
|
|
||||||
self.cache_width.insert(id, (self.outer, len));
|
|
||||||
self.checked_width.insert(id, (self.outer, len));
|
|
||||||
len
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should be refactored to share code w width_inner
|
|
||||||
fn height_inner(&mut self, id: Id) -> 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 self_outer = self.outer;
|
|
||||||
let self_id = self.id;
|
|
||||||
self.id = id;
|
|
||||||
let len = self.widgets.get_dyn_dynamic(id).desired_height(self);
|
|
||||||
self.outer = self_outer;
|
|
||||||
self.id = self_id;
|
|
||||||
self.cache_height.insert(id, (self.outer, len));
|
|
||||||
self.checked_height.insert(id, (self.outer, len));
|
|
||||||
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 {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.width(id),
|
|
||||||
Axis::Y => self.height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
|
|
||||||
Size {
|
|
||||||
x: self.width(id),
|
|
||||||
y: self.height(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn px_size(&mut self) -> Vec2 {
|
|
||||||
// WARNING IF UNCOMMENT: self.id is no longer valid
|
|
||||||
// self.px_dependent.insert(self.id);
|
|
||||||
self.outer.to_abs(self.output_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
|
|
||||||
self.text.draw(buffer, attrs, self.textures)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn label(&self, id: &Id) -> &String {
|
|
||||||
self.widgets.label(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,382 +0,0 @@
|
|||||||
use std::{fmt::Display, marker::Destruct};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
layout::{Align, Axis, UiNum, Vec2},
|
|
||||||
util::{LerpUtil, impl_op},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
|
||||||
pub struct UiVec2 {
|
|
||||||
pub rel: Vec2,
|
|
||||||
pub abs: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiVec2 {
|
|
||||||
pub const ZERO: Self = Self {
|
|
||||||
rel: Vec2::ZERO,
|
|
||||||
abs: Vec2::ZERO,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// expands this position into a sized region centered at self
|
|
||||||
pub fn expand(&self, size: impl Into<Vec2>) -> UiRegion {
|
|
||||||
let size = size.into();
|
|
||||||
UiRegion {
|
|
||||||
top_left: self.offset(-size / 2.0),
|
|
||||||
bot_right: self.offset(size / 2.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn abs(abs: impl const Into<Vec2>) -> Self {
|
|
||||||
Self {
|
|
||||||
rel: Vec2::ZERO,
|
|
||||||
abs: abs.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
|
|
||||||
Self {
|
|
||||||
rel: rel.into(),
|
|
||||||
abs: Vec2::ZERO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn shift(&mut self, offset: impl const Into<UiVec2>) {
|
|
||||||
let offset = offset.into();
|
|
||||||
*self += offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn offset(mut self, offset: impl const Into<UiVec2>) -> Self {
|
|
||||||
self.shift(offset);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn within(&self, region: &UiRegion) -> UiVec2 {
|
|
||||||
let rel = self.rel.lerp(region.top_left.rel, region.bot_right.rel);
|
|
||||||
let abs = self.abs + self.rel.lerp(region.top_left.abs, region.bot_right.abs);
|
|
||||||
UiVec2 { rel, abs }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn outside(&self, region: &UiRegion) -> UiVec2 {
|
|
||||||
let rel = self.rel.lerp_inv(region.top_left.rel, region.bot_right.rel);
|
|
||||||
let abs = self.abs - rel.lerp(region.top_left.abs, region.bot_right.abs);
|
|
||||||
UiVec2 { rel, abs }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> UiScalarView<'_> {
|
|
||||||
match axis {
|
|
||||||
Axis::X => UiScalarView {
|
|
||||||
rel: &mut self.rel.x,
|
|
||||||
abs: &mut self.abs.x,
|
|
||||||
},
|
|
||||||
Axis::Y => UiScalarView {
|
|
||||||
rel: &mut self.rel.y,
|
|
||||||
abs: &mut self.abs.y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis(&self, axis: Axis) -> UiScalar {
|
|
||||||
match axis {
|
|
||||||
Axis::X => UiScalar {
|
|
||||||
rel: self.rel.x,
|
|
||||||
abs: self.abs.x,
|
|
||||||
},
|
|
||||||
Axis::Y => UiScalar {
|
|
||||||
rel: self.rel.y,
|
|
||||||
abs: self.abs.y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// reflection about an axis
|
|
||||||
pub fn flip(&mut self, axis: Axis) {
|
|
||||||
*self.rel.axis_mut(axis) = 1.0 - self.rel.axis(axis);
|
|
||||||
*self.abs.axis_mut(axis) = -self.abs.axis(axis);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flipped(mut self, axis: Axis) -> Self {
|
|
||||||
self.flip(axis);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_abs(&self, rel: Vec2) -> Vec2 {
|
|
||||||
self.rel * rel + self.abs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const FULL_SIZE: Self = Self::rel(Vec2::ONE);
|
|
||||||
|
|
||||||
pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self {
|
|
||||||
Self {
|
|
||||||
rel: Vec2::from_axis(axis, aligned.rel, ortho.rel),
|
|
||||||
abs: Vec2::from_axis(axis, aligned.abs, ortho.abs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from_scalars(x: UiScalar, y: UiScalar) -> Self {
|
|
||||||
Self {
|
|
||||||
rel: Vec2 { x: x.rel, y: y.rel },
|
|
||||||
abs: Vec2 { x: x.abs, y: y.abs },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for UiVec2 {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "rel{};abs{}", self.rel, self.abs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_op!(UiVec2 Add add; rel abs);
|
|
||||||
impl_op!(UiVec2 Sub sub; rel abs);
|
|
||||||
|
|
||||||
impl const From<Align> for UiVec2 {
|
|
||||||
fn from(align: Align) -> Self {
|
|
||||||
Self::rel(align.rel())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Align {
|
|
||||||
pub fn pos(self) -> UiVec2 {
|
|
||||||
UiVec2::from(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl const From<Vec2> for UiVec2 {
|
|
||||||
fn from(abs: Vec2) -> Self {
|
|
||||||
Self::abs(abs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for UiVec2
|
|
||||||
where
|
|
||||||
(T, U): const Destruct,
|
|
||||||
{
|
|
||||||
fn from(abs: (T, U)) -> Self {
|
|
||||||
Self::abs(abs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub struct UiScalar {
|
|
||||||
pub rel: f32,
|
|
||||||
pub abs: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_op!(UiScalar Add add; rel abs);
|
|
||||||
impl_op!(UiScalar Sub sub; rel abs);
|
|
||||||
|
|
||||||
impl UiScalar {
|
|
||||||
pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 };
|
|
||||||
pub const FULL: Self = Self { rel: 1.0, abs: 0.0 };
|
|
||||||
|
|
||||||
pub fn new(rel: f32, abs: f32) -> Self {
|
|
||||||
Self { rel, abs }
|
|
||||||
}
|
|
||||||
pub fn rel_min() -> Self {
|
|
||||||
Self::new(0.0, 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rel_max() -> Self {
|
|
||||||
Self::new(1.0, 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max(&self, other: Self) -> Self {
|
|
||||||
Self {
|
|
||||||
rel: self.rel.max(other.rel),
|
|
||||||
abs: self.abs.max(other.abs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn min(&self, other: Self) -> Self {
|
|
||||||
Self {
|
|
||||||
rel: self.rel.min(other.rel),
|
|
||||||
abs: self.abs.min(other.abs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_anchor(anchor: f32) -> Self {
|
|
||||||
Self::new(anchor, 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset(mut self, amt: f32) -> Self {
|
|
||||||
self.abs += amt;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn within(&self, start: UiScalar, end: UiScalar) -> Self {
|
|
||||||
let anchor = self.rel.lerp(start.rel, end.rel);
|
|
||||||
let offset = self.abs + self.rel.lerp(start.abs, end.abs);
|
|
||||||
Self {
|
|
||||||
rel: anchor,
|
|
||||||
abs: offset,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn within_len(&self, len: UiScalar) -> Self {
|
|
||||||
self.within(UiScalar::ZERO, len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct UiRegion {
|
|
||||||
pub top_left: UiVec2,
|
|
||||||
pub bot_right: UiVec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiRegion {
|
|
||||||
pub const fn full() -> Self {
|
|
||||||
Self {
|
|
||||||
top_left: Align::TopLeft.into(),
|
|
||||||
bot_right: Align::BotRight.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn rel(anchor: Vec2) -> Self {
|
|
||||||
Self {
|
|
||||||
top_left: UiVec2::rel(anchor),
|
|
||||||
bot_right: UiVec2::rel(anchor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn within(&self, parent: &Self) -> Self {
|
|
||||||
Self {
|
|
||||||
top_left: self.top_left.within(parent),
|
|
||||||
bot_right: self.bot_right.within(parent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn outside(&self, parent: &Self) -> Self {
|
|
||||||
Self {
|
|
||||||
top_left: self.top_left.outside(parent),
|
|
||||||
bot_right: self.bot_right.outside(parent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> {
|
|
||||||
UIRegionAxisView {
|
|
||||||
top_left: self.top_left.axis_mut(axis),
|
|
||||||
bot_right: self.bot_right.axis_mut(axis),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flip(&mut self, axis: Axis) {
|
|
||||||
self.top_left.flip(axis);
|
|
||||||
self.bot_right.flip(axis);
|
|
||||||
let tl = self.top_left.axis_mut(axis);
|
|
||||||
let br = self.bot_right.axis_mut(axis);
|
|
||||||
std::mem::swap(tl.rel, br.rel);
|
|
||||||
std::mem::swap(tl.abs, br.abs);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shift(&mut self, offset: impl Into<UiVec2>) {
|
|
||||||
let offset = offset.into();
|
|
||||||
self.top_left.shift(offset);
|
|
||||||
self.bot_right.shift(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset(mut self, offset: impl Into<UiVec2>) -> Self {
|
|
||||||
self.shift(offset);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_px(&self, size: Vec2) -> PixelRegion {
|
|
||||||
PixelRegion {
|
|
||||||
top_left: self.top_left.rel * size + self.top_left.abs,
|
|
||||||
bot_right: self.bot_right.rel * size + self.bot_right.abs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn center(&self) -> UiVec2 {
|
|
||||||
Align::Center.pos().within(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> UiVec2 {
|
|
||||||
self.bot_right - self.top_left
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select_aligned(&self, size: Vec2, align: Align) -> Self {
|
|
||||||
Self::from_size_align(size, align).within(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_size_align(size: Vec2, align: Align) -> Self {
|
|
||||||
let mut top_left = UiVec2::from(align);
|
|
||||||
top_left.abs -= size * align.rel();
|
|
||||||
let mut bot_right = UiVec2::from(align);
|
|
||||||
bot_right.abs += size * (Vec2::ONE - align.rel());
|
|
||||||
Self {
|
|
||||||
top_left,
|
|
||||||
bot_right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_ui_size_align(size: UiVec2, align: Align) -> Self {
|
|
||||||
let mut top_left = UiVec2::from(align);
|
|
||||||
top_left.abs -= size.abs * align.rel();
|
|
||||||
top_left.rel -= size.rel * align.rel();
|
|
||||||
let mut bot_right = UiVec2::from(align);
|
|
||||||
bot_right.abs += size.abs * (Vec2::ONE - align.rel());
|
|
||||||
bot_right.rel += size.rel * (Vec2::ONE - align.rel());
|
|
||||||
Self {
|
|
||||||
top_left,
|
|
||||||
bot_right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for UiRegion {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} -> {} (size: {})",
|
|
||||||
self.top_left,
|
|
||||||
self.bot_right,
|
|
||||||
self.size()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PixelRegion {
|
|
||||||
pub top_left: Vec2,
|
|
||||||
pub bot_right: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PixelRegion {
|
|
||||||
pub fn contains(&self, pos: Vec2) -> bool {
|
|
||||||
pos.x >= self.top_left.x
|
|
||||||
&& pos.x <= self.bot_right.x
|
|
||||||
&& pos.y >= self.top_left.y
|
|
||||||
&& pos.y <= self.bot_right.y
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> Vec2 {
|
|
||||||
self.bot_right - self.top_left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for PixelRegion {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{} -> {}", self.top_left, self.bot_right)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UIRegionAxisView<'a> {
|
|
||||||
pub top_left: UiScalarView<'a>,
|
|
||||||
pub bot_right: UiScalarView<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UiScalarView<'a> {
|
|
||||||
pub rel: &'a mut f32,
|
|
||||||
pub abs: &'a mut f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiScalarView<'_> {
|
|
||||||
pub fn set(&mut self, scalar: UiScalar) {
|
|
||||||
*self.rel = scalar.rel;
|
|
||||||
*self.abs = scalar.abs;
|
|
||||||
}
|
|
||||||
pub fn get(self) -> UiScalar {
|
|
||||||
UiScalar {
|
|
||||||
rel: *self.rel,
|
|
||||||
abs: *self.abs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
use cosmic_text::{
|
|
||||||
Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCache, SwashContent,
|
|
||||||
};
|
|
||||||
use image::{Rgba, RgbaImage};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
layout::{Align, TextureHandle, Textures, UiColor, Vec2},
|
|
||||||
util::HashMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// TODO: properly wrap this
|
|
||||||
pub mod text_lib {
|
|
||||||
pub use cosmic_text::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextData {
|
|
||||||
pub font_system: FontSystem,
|
|
||||||
pub swash_cache: SwashCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TextData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
font_system: FontSystem::new(),
|
|
||||||
swash_cache: SwashCache::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct TextAttrs {
|
|
||||||
pub color: UiColor,
|
|
||||||
pub font_size: f32,
|
|
||||||
pub line_height: f32,
|
|
||||||
pub family: Family<'static>,
|
|
||||||
pub wrap: bool,
|
|
||||||
/// inner alignment of text region (within where its drawn)
|
|
||||||
pub align: Align,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextAttrs {
|
|
||||||
pub fn apply(&self, font_system: &mut FontSystem, buf: &mut Buffer, width: Option<f32>) {
|
|
||||||
buf.set_metrics_and_size(
|
|
||||||
font_system,
|
|
||||||
Metrics::new(self.font_size, self.line_height),
|
|
||||||
width,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let attrs = Attrs::new().family(self.family);
|
|
||||||
let list = AttrsList::new(&attrs);
|
|
||||||
for line in &mut buf.lines {
|
|
||||||
line.set_attrs_list(list.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TextBuffer = Buffer;
|
|
||||||
|
|
||||||
impl Default for TextAttrs {
|
|
||||||
fn default() -> Self {
|
|
||||||
let size = 14.0;
|
|
||||||
Self {
|
|
||||||
color: UiColor::WHITE,
|
|
||||||
font_size: size,
|
|
||||||
line_height: size * 1.2,
|
|
||||||
family: Family::SansSerif,
|
|
||||||
wrap: false,
|
|
||||||
align: Align::Center,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextData {
|
|
||||||
pub fn draw(
|
|
||||||
&mut self,
|
|
||||||
buffer: &mut TextBuffer,
|
|
||||||
attrs: &TextAttrs,
|
|
||||||
textures: &mut Textures,
|
|
||||||
) -> TextTexture {
|
|
||||||
// TODO: either this or the layout stuff (or both) is super slow,
|
|
||||||
// should probably do texture packing and things if possible.
|
|
||||||
// very visible if you add just a couple of wrapping texts and resize window
|
|
||||||
// should also be timed to figure out exactly what points need to be sped up
|
|
||||||
let mut pixels = HashMap::<_, [u8; 4]>::default();
|
|
||||||
let mut min_x = 0;
|
|
||||||
let mut min_y = 0;
|
|
||||||
let mut max_x = 0;
|
|
||||||
let mut max_y = 0;
|
|
||||||
let cosmic_color = {
|
|
||||||
let c = attrs.color;
|
|
||||||
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
|
|
||||||
};
|
|
||||||
let mut max_width = 0.0f32;
|
|
||||||
let mut height = 0.0;
|
|
||||||
for run in buffer.layout_runs() {
|
|
||||||
for glyph in run.glyphs.iter() {
|
|
||||||
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
|
||||||
|
|
||||||
let glyph_color = match glyph.color_opt {
|
|
||||||
Some(some) => some,
|
|
||||||
None => cosmic_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(img) = self
|
|
||||||
.swash_cache
|
|
||||||
.get_image(&mut self.font_system, physical_glyph.cache_key)
|
|
||||||
{
|
|
||||||
let mut pos = img.placement;
|
|
||||||
pos.left += physical_glyph.x;
|
|
||||||
pos.top = physical_glyph.y + run.line_y as i32 - pos.top;
|
|
||||||
min_x = min_x.min(pos.left);
|
|
||||||
min_y = min_y.min(pos.top);
|
|
||||||
max_x = max_x.max(pos.left + pos.width as i32);
|
|
||||||
max_y = max_y.max(pos.top + pos.height as i32);
|
|
||||||
let mut merge = |i, color: [u8; 4]| {
|
|
||||||
let x = i % pos.width as usize;
|
|
||||||
let y = i / pos.width as usize;
|
|
||||||
let pos = (x as i32 + pos.left, y as i32 + pos.top);
|
|
||||||
if let Some(pixel) = pixels.get_mut(&pos) {
|
|
||||||
for i in 0..4 {
|
|
||||||
// TODO: no clue if proper alpha blending should be done
|
|
||||||
pixel[i] = color[i].saturating_add(pixel[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pixels.insert(pos, color);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match img.content {
|
|
||||||
SwashContent::Mask => {
|
|
||||||
for (i, a) in img.data.iter().enumerate() {
|
|
||||||
let mut color = glyph_color.as_rgba();
|
|
||||||
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
|
|
||||||
merge(i, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SwashContent::SubpixelMask => todo!(),
|
|
||||||
SwashContent::Color => {
|
|
||||||
let (colors, _) = img.data.as_chunks::<4>();
|
|
||||||
for (i, color) in colors.iter().enumerate() {
|
|
||||||
merge(i, *color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max_width = max_width.max(run.line_w);
|
|
||||||
height += run.line_height;
|
|
||||||
}
|
|
||||||
let img_width = (max_x - min_x + 1) as u32;
|
|
||||||
let img_height = (max_y - min_y + 1) as u32;
|
|
||||||
let mut image = RgbaImage::new(img_width, img_height);
|
|
||||||
|
|
||||||
for ((x, y), color) in pixels {
|
|
||||||
let x = (x - min_x) as u32;
|
|
||||||
let y = (y - min_y) as u32;
|
|
||||||
image.put_pixel(x, y, Rgba(color));
|
|
||||||
}
|
|
||||||
TextTexture {
|
|
||||||
handle: textures.add(image),
|
|
||||||
top_left: Vec2::new(min_x as f32, min_y as f32),
|
|
||||||
bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TextTexture {
|
|
||||||
pub handle: TextureHandle,
|
|
||||||
pub top_left: Vec2,
|
|
||||||
pub bot_right: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextTexture {
|
|
||||||
pub fn size(&self) -> Vec2 {
|
|
||||||
self.handle.size() - self.top_left + self.bot_right
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
use std::{
|
|
||||||
ops::Index,
|
|
||||||
sync::mpsc::{Receiver, Sender, channel},
|
|
||||||
};
|
|
||||||
|
|
||||||
use image::{DynamicImage, GenericImageView};
|
|
||||||
|
|
||||||
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TextureHandle {
|
|
||||||
inner: TexturePrimitive,
|
|
||||||
size: Vec2,
|
|
||||||
counter: RefCounter,
|
|
||||||
send: Sender<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a texture manager for a ui
|
|
||||||
/// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped
|
|
||||||
pub struct Textures {
|
|
||||||
free: Vec<u32>,
|
|
||||||
images: Vec<Option<DynamicImage>>,
|
|
||||||
updates: Vec<Update>,
|
|
||||||
send: Sender<u32>,
|
|
||||||
recv: Receiver<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum TextureUpdate<'a> {
|
|
||||||
Push(&'a DynamicImage),
|
|
||||||
Set(u32, &'a DynamicImage),
|
|
||||||
Free(u32),
|
|
||||||
PushFree,
|
|
||||||
SetFree,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Update {
|
|
||||||
Push(u32),
|
|
||||||
Set(u32),
|
|
||||||
Free(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Textures {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (send, recv) = channel();
|
|
||||||
Self {
|
|
||||||
free: Vec::new(),
|
|
||||||
images: Vec::new(),
|
|
||||||
updates: Vec::new(),
|
|
||||||
send,
|
|
||||||
recv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
|
||||||
let image = image.into();
|
|
||||||
let size = image.dimensions().into();
|
|
||||||
let view_idx = self.push(image);
|
|
||||||
// 0 == default in renderer; TODO: actually create samplers here
|
|
||||||
let sampler_idx = 0;
|
|
||||||
TextureHandle {
|
|
||||||
inner: TexturePrimitive {
|
|
||||||
view_idx,
|
|
||||||
sampler_idx,
|
|
||||||
},
|
|
||||||
size,
|
|
||||||
counter: RefCounter::new(),
|
|
||||||
send: self.send.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, image: DynamicImage) -> u32 {
|
|
||||||
if let Some(i) = self.free.pop() {
|
|
||||||
self.images[i as usize] = Some(image);
|
|
||||||
self.updates.push(Update::Set(i));
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
let i = self.images.len() as u32;
|
|
||||||
self.images.push(Some(image));
|
|
||||||
self.updates.push(Update::Push(i));
|
|
||||||
i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free(&mut self) {
|
|
||||||
for idx in self.recv.try_iter() {
|
|
||||||
self.images[idx as usize] = None;
|
|
||||||
self.updates.push(Update::Free(idx));
|
|
||||||
self.free.push(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updates(&mut self) -> impl Iterator<Item = TextureUpdate<'_>> {
|
|
||||||
self.updates.drain(..).map(|u| match u {
|
|
||||||
Update::Push(i) => self.images[i as usize]
|
|
||||||
.as_ref()
|
|
||||||
.map(TextureUpdate::Push)
|
|
||||||
.unwrap_or(TextureUpdate::PushFree),
|
|
||||||
Update::Set(i) => self.images[i as usize]
|
|
||||||
.as_ref()
|
|
||||||
.map(|img| TextureUpdate::Set(i, img))
|
|
||||||
.unwrap_or(TextureUpdate::SetFree),
|
|
||||||
Update::Free(i) => TextureUpdate::Free(i),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextureHandle {
|
|
||||||
pub fn primitive(&self) -> TexturePrimitive {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
pub fn size(&self) -> Vec2 {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for TextureHandle {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.counter.drop() {
|
|
||||||
let _ = self.send.send(self.inner.view_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Index<&TextureHandle> for Textures {
|
|
||||||
type Output = DynamicImage;
|
|
||||||
|
|
||||||
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
|
||||||
self.images[index.inner.view_idx as usize].as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Textures {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
270
src/layout/ui.rs
270
src/layout/ui.rs
@@ -1,257 +1,101 @@
|
|||||||
use image::DynamicImage;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{TextEdit, TextEditCtx},
|
primitive::{Painter, Primitives},
|
||||||
layout::{
|
util::{IDTracker, ID},
|
||||||
IdLike, PainterCtx, PainterData, PixelRegion, StaticWidgetId, TextureHandle, Vec2, Widget,
|
HashMap, Widget, WidgetId, WidgetLike, WidgetRef,
|
||||||
WidgetId, WidgetLike,
|
|
||||||
},
|
|
||||||
util::{HashSet, Id},
|
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
ops::{Index, IndexMut},
|
cell::RefCell,
|
||||||
sync::mpsc::{Receiver, Sender, channel},
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Ui {
|
pub struct UI {
|
||||||
// TODO: make this at least pub(super)
|
ids: IDTracker,
|
||||||
pub(crate) data: PainterData,
|
base: Option<WidgetId>,
|
||||||
root: Option<WidgetId>,
|
pub widgets: Widgets,
|
||||||
updates: HashSet<Id>,
|
|
||||||
recv: Receiver<Id>,
|
|
||||||
pub(super) send: Sender<Id>,
|
|
||||||
full_redraw: bool,
|
|
||||||
resized: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
pub struct Widgets(HashMap<ID, Box<dyn Widget>>);
|
||||||
pub fn add<W: Widget, Tag>(&mut self, w: impl WidgetLike<Tag, Widget = W>) -> WidgetId<W> {
|
|
||||||
w.add(self)
|
#[derive(Clone)]
|
||||||
|
pub struct UIBuilder {
|
||||||
|
ui: Rc<RefCell<UI>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_static<W: Widget, Tag>(
|
impl From<UI> for UIBuilder {
|
||||||
&mut self,
|
fn from(ui: UI) -> Self {
|
||||||
w: impl WidgetLike<Tag, Widget = W>,
|
UIBuilder {
|
||||||
) -> StaticWidgetId<W> {
|
ui: Rc::new(RefCell::new(ui)),
|
||||||
let id = w.add(self);
|
}
|
||||||
id.into_static()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// useful for debugging
|
impl UIBuilder {
|
||||||
pub fn set_label<W>(&mut self, id: &WidgetId<W>, label: String) {
|
pub fn add<W: Widget>(&mut self, w: W) -> WidgetRef<W> {
|
||||||
self.data.widgets.data_mut(&id.id).unwrap().label = label;
|
WidgetRef::new(self.clone(), (self.push(w), ()))
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
pub fn push<W: Widget>(&mut self, w: W) -> WidgetId<W> {
|
||||||
let id = self.id();
|
let mut ui = self.ui.borrow_mut();
|
||||||
self.data.widgets.insert(id.id, w);
|
let id = ui.ids.next();
|
||||||
id
|
ui.widgets.insert(id.duplicate(), w);
|
||||||
|
WidgetId::new(id, TypeId::of::<W>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set<W: Widget>(&mut self, id: &WidgetId<W>, w: W) {
|
pub fn finish<WL: WidgetLike>(mut self, base: WL) -> UI {
|
||||||
self.data.widgets.insert(id.id, w);
|
let base = base.id(&mut self).erase_type();
|
||||||
}
|
let mut ui = Rc::into_inner(self.ui).unwrap().into_inner();
|
||||||
|
ui.base = Some(base);
|
||||||
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
|
ui
|
||||||
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();
|
|
||||||
let mut ctx = PainterCtx::new(&mut self.data);
|
|
||||||
if let Some(root) = &self.root {
|
|
||||||
ctx.draw(root.id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
impl UI {
|
||||||
if self.full_redraw {
|
pub fn build() -> UIBuilder {
|
||||||
self.redraw_all();
|
Self::empty().into()
|
||||||
self.full_redraw = false;
|
|
||||||
} else if !self.updates.is_empty() {
|
|
||||||
self.redraw_updates();
|
|
||||||
}
|
}
|
||||||
if self.resized {
|
|
||||||
self.resized = false;
|
pub fn empty() -> Self {
|
||||||
self.redraw_size();
|
Self {
|
||||||
|
ids: IDTracker::new(),
|
||||||
|
base: None,
|
||||||
|
widgets: Widgets::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redraw_size(&mut self) {
|
pub fn to_primitives(&self) -> Primitives {
|
||||||
// let mut ctx = PainterCtx::new(&mut self.data);
|
let mut painter = Painter::new(&self.widgets);
|
||||||
// let dep = ctx.px_dependent.clone();
|
if let Some(base) = &self.base {
|
||||||
// for id in dep {
|
painter.draw(base);
|
||||||
// ctx.redraw(id);
|
|
||||||
// }
|
|
||||||
self.redraw_all();
|
|
||||||
}
|
}
|
||||||
|
painter.finish()
|
||||||
fn redraw_updates(&mut self) {
|
|
||||||
// if self.updates.drain(..).next().is_some() {
|
|
||||||
// self.redraw_all();
|
|
||||||
// }
|
|
||||||
let mut ctx = PainterCtx::new(&mut self.data);
|
|
||||||
for id in self.updates.drain() {
|
|
||||||
ctx.redraw(id);
|
|
||||||
}
|
|
||||||
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> {
|
impl Widgets {
|
||||||
let region = self.data.active.get(&id.id())?.region;
|
fn new() -> Self {
|
||||||
Some(region.to_px(self.data.output_size))
|
Self(HashMap::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug(&self, label: &str) {
|
pub fn get(&self, id: &WidgetId) -> &dyn Widget {
|
||||||
for (id, inst) in &self.data.active {
|
self.0.get(&id.id).unwrap().as_ref()
|
||||||
let l = &self.data.widgets.data(id).unwrap().label;
|
|
||||||
if l != label {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
println!("\"{label}\" {{");
|
|
||||||
println!(" region: {}", inst.region);
|
|
||||||
println!(
|
|
||||||
" pixel region: {}",
|
|
||||||
inst.region.to_px(self.data.output_size)
|
|
||||||
);
|
|
||||||
println!("}}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Widget> Index<&WidgetId<W>> for Ui {
|
pub fn get_mut<W: Widget>(&mut self, id: &WidgetId<W>) -> Option<&mut W> {
|
||||||
type Output = W;
|
self.0.get_mut(&id.id).unwrap().as_any_mut().downcast_mut()
|
||||||
|
|
||||||
fn index(&self, id: &WidgetId<W>) -> &Self::Output {
|
|
||||||
self.get(id).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Widget> IndexMut<&WidgetId<W>> for Ui {
|
pub fn insert(&mut self, id: ID, widget: impl Widget) {
|
||||||
fn index_mut(&mut self, id: &WidgetId<W>) -> &mut Self::Output {
|
self.0.insert(id, Box::new(widget));
|
||||||
self.updates.insert(id.id);
|
|
||||||
self.get_mut(id).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Widget> Index<StaticWidgetId<W>> for Ui {
|
pub fn insert_any(&mut self, id: ID, widget: Box<dyn Widget>) {
|
||||||
type Output = W;
|
self.0.insert(id, widget);
|
||||||
|
|
||||||
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 {
|
impl dyn Widget {
|
||||||
pub fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_any_mut(&mut self) -> &mut dyn Any {
|
pub fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
self
|
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,117 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
layout::UiNum,
|
|
||||||
util::{DivOr, impl_op},
|
|
||||||
};
|
|
||||||
use std::{hash::Hash, marker::Destruct, ops::*};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct Vec2 {
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Vec2 {}
|
|
||||||
|
|
||||||
impl Hash for Vec2 {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
state.write_u32(self.x.to_bits());
|
|
||||||
state.write_u32(self.y.to_bits());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
pub const fn new(x: f32, y: f32) -> Self {
|
|
||||||
Self { x, y }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn round(self) -> Self {
|
|
||||||
Self {
|
|
||||||
x: self.x.round(),
|
|
||||||
y: self.y.round(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn floor(self) -> Self {
|
|
||||||
Self {
|
|
||||||
x: self.x.floor(),
|
|
||||||
y: self.y.floor(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn ceil(self) -> Self {
|
|
||||||
Self {
|
|
||||||
x: self.x.ceil(),
|
|
||||||
y: self.y.ceil(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this version looks kinda cool... is it more readable? more annoying to copy and change though
|
|
||||||
impl_op!(impl Add for Vec2: add x y);
|
|
||||||
impl_op!(Vec2 Sub sub; x y);
|
|
||||||
impl_op!(Vec2 Mul mul; x y);
|
|
||||||
impl_op!(Vec2 Div div; x y);
|
|
||||||
|
|
||||||
impl const DivOr for Vec2 {
|
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
|
||||||
Self {
|
|
||||||
x: self.x.div_or(rhs.x, other.x),
|
|
||||||
y: self.y.div_or(rhs.y, other.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Neg for Vec2 {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn neg(mut self) -> Self::Output {
|
|
||||||
self.x = -self.x;
|
|
||||||
self.y = -self.y;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Vec2 {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "({}, {})", self.x, self.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +1,186 @@
|
|||||||
use crate::layout::{Len, Painter, SizeCtx, StaticWidgetId, Ui, WidgetId, WidgetIdFn};
|
use std::{
|
||||||
|
any::{Any, TypeId},
|
||||||
|
marker::PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
use std::{any::Any, marker::PhantomData};
|
use crate::{
|
||||||
|
primitive::Painter,
|
||||||
|
util::{IntoTupleList, TupleList, ID},
|
||||||
|
UIBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
pub trait Widget: Any {
|
pub trait Widget: 'static + Any {
|
||||||
fn draw(&mut self, painter: &mut Painter);
|
fn draw(&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 () {
|
impl<W: Widget> Widget for (W,) {
|
||||||
fn draw(&mut self, _: &mut Painter) {}
|
fn draw(&self, painter: &mut Painter) {
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
self.0.draw(painter);
|
||||||
Len::ZERO
|
|
||||||
}
|
|
||||||
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
|
|
||||||
Len::ZERO
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WidgetTag;
|
#[derive(Eq, Hash, PartialEq, Debug)]
|
||||||
pub struct FnTag;
|
pub struct WidgetId<W = ()> {
|
||||||
|
pub(super) ty: TypeId,
|
||||||
pub trait WidgetLike<Tag> {
|
pub(super) id: ID,
|
||||||
type Widget: 'static;
|
_pd: PhantomData<W>,
|
||||||
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.
|
// TODO: temp
|
||||||
/// Useful for defining trait functions on widgets that create a parent widget so that the children
|
impl<W> Clone for WidgetId<W> {
|
||||||
/// don't need to be IDs yet
|
fn clone(&self) -> Self {
|
||||||
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 {
|
Self {
|
||||||
arr,
|
ty: self.ty,
|
||||||
|
id: self.id.duplicate(),
|
||||||
|
_pd: self._pd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W> WidgetId<W> {
|
||||||
|
pub(super) fn new(id: ID, ty: TypeId) -> Self {
|
||||||
|
Self {
|
||||||
|
ty,
|
||||||
|
id,
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn erase_type(self) -> WidgetId<()> {
|
||||||
|
self.cast_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_type<W2>(self) -> WidgetId<W2> {
|
||||||
|
WidgetId {
|
||||||
|
ty: self.ty,
|
||||||
|
id: self.id,
|
||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ArrTag;
|
pub trait WidgetLike {
|
||||||
pub trait WidgetArrLike<const LEN: usize, Tag> {
|
type Widget;
|
||||||
type Ws;
|
fn id(self, ui: &mut UIBuilder) -> WidgetId<Self::Widget>;
|
||||||
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN, Self::Ws>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const LEN: usize, Ws> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN, Ws> {
|
/// wouldn't be needed if negative trait bounds & disjoint impls existed
|
||||||
type Ws = Ws;
|
pub struct WidgetFn<F: FnOnce(&mut UIBuilder) -> W, W>(pub F);
|
||||||
fn ui(self, _: &mut Ui) -> WidgetArr<LEN, Ws> {
|
|
||||||
|
impl<W: Widget, F: FnOnce(&mut UIBuilder) -> W> WidgetLike for WidgetFn<F, W> {
|
||||||
|
type Widget = W;
|
||||||
|
fn id(self, ui: &mut UIBuilder) -> WidgetId<W> {
|
||||||
|
let w = (self.0)(ui);
|
||||||
|
ui.add(w).to_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Widget> WidgetLike for W {
|
||||||
|
type Widget = W;
|
||||||
|
fn id(self, ui: &mut UIBuilder) -> WidgetId<W> {
|
||||||
|
ui.add(self).to_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W> WidgetLike for WidgetId<W> {
|
||||||
|
type Widget = W;
|
||||||
|
fn id(self, _: &mut UIBuilder) -> WidgetId<W> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: WidgetLike<WidgetTag>> WidgetArrLike<1, WidgetTag> for W {
|
pub struct WidgetArr<Ws: WidgetList> {
|
||||||
type Ws = (W::Widget,);
|
pub ui: UIBuilder,
|
||||||
fn ui(self, ui: &mut Ui) -> WidgetArr<1, (W::Widget,)> {
|
pub arr: Ws::Ids,
|
||||||
WidgetArr::new([self.add(ui).any()])
|
}
|
||||||
|
|
||||||
|
impl<Ws: WidgetList> WidgetArr<Ws> {
|
||||||
|
pub fn new(ui: UIBuilder, arr: Ws::Ids) -> Self {
|
||||||
|
Self { ui, arr }
|
||||||
|
}
|
||||||
|
pub fn ids(self) -> Vec<WidgetId> {
|
||||||
|
self.arr.all()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// I hate this language it's so bad why do I even use it
|
pub type WidgetRef<W> = WidgetArr<(W, ())>;
|
||||||
macro_rules! impl_widget_arr {
|
|
||||||
($n:expr;$($W:ident)*) => {
|
impl<W> WidgetRef<W> {
|
||||||
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
|
pub fn handle(&self) -> WidgetId<W> {
|
||||||
};
|
self.arr.head().clone()
|
||||||
($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(),)*],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
pub fn to_id(self) -> WidgetId<W> {
|
||||||
|
self.arr.into_head()
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_widget_arr!(1;A);
|
impl<W> WidgetLike for WidgetRef<W> {
|
||||||
impl_widget_arr!(2;A B);
|
type Widget = W;
|
||||||
impl_widget_arr!(3;A B C);
|
fn id(self, _: &mut UIBuilder) -> WidgetId<W> {
|
||||||
impl_widget_arr!(4;A B C D);
|
self.arr.into_head()
|
||||||
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);
|
pub trait WidgetArrLike {
|
||||||
impl_widget_arr!(9;A B C D E F G H I);
|
const LEN: usize;
|
||||||
impl_widget_arr!(10;A B C D E F G H I J);
|
type Ws: WidgetList;
|
||||||
impl_widget_arr!(11;A B C D E F G H I J K);
|
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<Self::Ws>;
|
||||||
impl_widget_arr!(12;A B C D E F G H I J K L);
|
}
|
||||||
|
|
||||||
|
impl<Ws: WidgetList> WidgetArrLike for WidgetArr<Ws> {
|
||||||
|
const LEN: usize = Ws::LEN;
|
||||||
|
type Ws = Ws;
|
||||||
|
fn ui(self, _: &mut UIBuilder) -> WidgetArr<Ws> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: WidgetLike> WidgetArrLike for (W, ())
|
||||||
|
where
|
||||||
|
Self: TupleList,
|
||||||
|
{
|
||||||
|
const LEN: usize = <Self as TupleList>::LEN;
|
||||||
|
type Ws = (W::Widget, ());
|
||||||
|
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<(W::Widget, ())> {
|
||||||
|
WidgetArr::new(ui.clone(), (self.0.id(ui), ()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<W: WidgetLike, T: WidgetArrLike> WidgetArrLike for (W, T)
|
||||||
|
where
|
||||||
|
Self: TupleList,
|
||||||
|
{
|
||||||
|
const LEN: usize = <Self as TupleList>::LEN;
|
||||||
|
type Ws = (W::Widget, T::Ws);
|
||||||
|
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<(W::Widget, T::Ws)> {
|
||||||
|
WidgetArr::new(ui.clone(), (self.0.id(ui), self.1.ui(ui).arr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait WidgetList: TupleList {
|
||||||
|
type Ids: IdList;
|
||||||
|
}
|
||||||
|
impl<H> WidgetList for (H, ()) {
|
||||||
|
type Ids = (WidgetId<H>, ());
|
||||||
|
}
|
||||||
|
impl<H, T: WidgetList> WidgetList for (H, T) {
|
||||||
|
type Ids = (WidgetId<H>, T::Ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IdList: TupleList {
|
||||||
|
fn iter(self) -> impl Iterator<Item = WidgetId>;
|
||||||
|
fn all(self) -> Vec<WidgetId>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.iter().collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H> IdList for (WidgetId<H>, ()) {
|
||||||
|
fn iter(self) -> impl Iterator<Item = WidgetId> {
|
||||||
|
core::iter::once(self.0.erase_type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H, T: IdList> IdList for (WidgetId<H>, T) {
|
||||||
|
fn iter(self) -> impl Iterator<Item = WidgetId> {
|
||||||
|
core::iter::once(self.0.erase_type()).chain(self.1.iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,103 +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) {
|
|
||||||
self.insert_any(id, Box::new(widget), std::any::type_name::<W>().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
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>;
|
|
||||||
25
src/lib.rs
25
src/lib.rs
@@ -1,20 +1,17 @@
|
|||||||
#![feature(macro_metavar_expr_concat)]
|
#![feature(macro_metavar_expr_concat)]
|
||||||
#![feature(const_ops)]
|
#![feature(const_ops)]
|
||||||
#![feature(const_trait_impl)]
|
#![feature(const_trait_impl)]
|
||||||
#![feature(const_convert)]
|
#![feature(const_from)]
|
||||||
#![feature(map_try_insert)]
|
#![feature(trait_alias)]
|
||||||
#![feature(unboxed_closures)]
|
#![feature(generic_const_exprs)]
|
||||||
#![feature(fn_traits)]
|
|
||||||
#![feature(const_cmp)]
|
|
||||||
#![feature(const_destruct)]
|
|
||||||
|
|
||||||
pub mod core;
|
mod layout;
|
||||||
pub mod layout;
|
mod render;
|
||||||
pub mod render;
|
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
mod base;
|
||||||
|
|
||||||
pub mod prelude {
|
pub use layout::*;
|
||||||
pub use crate::core::*;
|
pub use render::*;
|
||||||
pub use crate::layout::*;
|
pub use base::*;
|
||||||
pub use crate::render::*;
|
|
||||||
}
|
pub type HashMap<K, V> = std::collections::HashMap<K, V>;
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
use crate::{layout::UiRegion, util::Id};
|
use crate::primitive::UIRegion;
|
||||||
use wgpu::*;
|
use wgpu::VertexAttribute;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct PrimitiveInstance {
|
||||||
|
pub region: UIRegion,
|
||||||
|
pub ptr: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||||
@@ -8,43 +15,20 @@ pub struct WindowUniform {
|
|||||||
pub height: f32,
|
pub height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct PrimitiveInstance {
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub binding: u32,
|
|
||||||
pub idx: u32,
|
|
||||||
pub mask_idx: MaskIdx,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveInstance {
|
impl PrimitiveInstance {
|
||||||
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
|
const ATTRIBS: [VertexAttribute; 5] = wgpu::vertex_attr_array![
|
||||||
0 => Float32x2,
|
0 => Float32x2,
|
||||||
1 => Float32x2,
|
1 => Float32x2,
|
||||||
2 => Float32x2,
|
2 => Float32x2,
|
||||||
3 => Float32x2,
|
3 => Float32x2,
|
||||||
4 => Uint32,
|
4 => Uint32,
|
||||||
5 => Uint32,
|
|
||||||
6 => Uint32,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn desc() -> VertexBufferLayout<'static> {
|
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||||
VertexBufferLayout {
|
wgpu::VertexBufferLayout {
|
||||||
array_stride: std::mem::size_of::<Self>() as BufferAddress,
|
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||||
step_mode: VertexStepMode::Instance,
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
attributes: &Self::ATTRIBS,
|
attributes: &Self::ATTRIBS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type MaskIdx = Id<u32>;
|
|
||||||
|
|
||||||
impl MaskIdx {
|
|
||||||
pub const NONE: Self = Self::preset(u32::MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct Mask {
|
|
||||||
pub region: UiRegion,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use std::num::NonZero;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::Ui,
|
render::{data::PrimitiveInstance, util::ArrBuf},
|
||||||
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
UI,
|
||||||
util::HashMap,
|
|
||||||
};
|
};
|
||||||
use data::WindowUniform;
|
use data::WindowUniform;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
@@ -13,101 +10,42 @@ use wgpu::{
|
|||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
|
|
||||||
mod data;
|
mod data;
|
||||||
mod primitive;
|
pub mod primitive;
|
||||||
mod texture;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use data::{Mask, MaskIdx};
|
|
||||||
pub use primitive::*;
|
|
||||||
|
|
||||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||||
|
|
||||||
pub struct UiRenderer {
|
pub struct UIRenderNode {
|
||||||
uniform_group: BindGroup,
|
bind_group_layout: BindGroupLayout,
|
||||||
primitive_layout: BindGroupLayout,
|
bind_group: BindGroup,
|
||||||
rsc_layout: BindGroupLayout,
|
|
||||||
rsc_group: BindGroup,
|
|
||||||
|
|
||||||
pipeline: RenderPipeline,
|
pipeline: RenderPipeline,
|
||||||
|
|
||||||
layers: HashMap<usize, RenderLayer>,
|
|
||||||
active: Vec<usize>,
|
|
||||||
window_buffer: Buffer,
|
window_buffer: Buffer,
|
||||||
textures: GpuTextures,
|
|
||||||
masks: ArrBuf<Mask>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RenderLayer {
|
|
||||||
instance: ArrBuf<PrimitiveInstance>,
|
instance: ArrBuf<PrimitiveInstance>,
|
||||||
primitives: PrimitiveBuffers,
|
data: ArrBuf<u32>,
|
||||||
primitive_group: BindGroup,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiRenderer {
|
impl UIRenderNode {
|
||||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_pipeline(&self.pipeline);
|
||||||
pass.set_bind_group(0, &self.uniform_group, &[]);
|
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
pass.set_bind_group(2, &self.rsc_group, &[]);
|
if self.instance.len() != 0 {
|
||||||
for i in &self.active {
|
pass.set_vertex_buffer(0, self.instance.buffer.slice(..));
|
||||||
let layer = &self.layers[i];
|
pass.draw(0..4, 0..self.instance.len() as u32);
|
||||||
if layer.instance.len() == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
|
||||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
|
||||||
pass.draw(0..4, 0..layer.instance.len() as u32);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
|
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &UI) {
|
||||||
self.active.clear();
|
let primitives = ui.to_primitives();
|
||||||
for (i, ulayer) in ui.data.layers.iter_mut() {
|
self.instance.update(device, queue, &primitives.instances);
|
||||||
self.active.push(i);
|
self.data.update(device, queue, &primitives.data);
|
||||||
let primitives = &mut ulayer.primitives;
|
self.bind_group = Self::bind_group(
|
||||||
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 {
|
|
||||||
h.inst_idx = change.new;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let rlayer = self.layers.entry(i).or_insert_with(|| {
|
|
||||||
let primitives = PrimitiveBuffers::new(device);
|
|
||||||
let primitive_group =
|
|
||||||
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
|
|
||||||
RenderLayer {
|
|
||||||
instance: ArrBuf::new(
|
|
||||||
device,
|
device,
|
||||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
&self.bind_group_layout,
|
||||||
"instance",
|
&self.window_buffer,
|
||||||
),
|
&self.data.buffer,
|
||||||
primitives,
|
|
||||||
primitive_group,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if primitives.updated {
|
|
||||||
rlayer
|
|
||||||
.instance
|
|
||||||
.update(device, queue, primitives.instances());
|
|
||||||
rlayer.primitives.update(device, queue, primitives.data());
|
|
||||||
rlayer.primitive_group = Self::primitive_group(
|
|
||||||
device,
|
|
||||||
&self.primitive_layout,
|
|
||||||
rlayer.primitives.buffers(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if self.textures.update(&mut ui.data.textures) {
|
|
||||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks)
|
|
||||||
}
|
|
||||||
if ui.data.masks.changed {
|
|
||||||
ui.data.masks.changed = false;
|
|
||||||
self.masks.update(device, queue, &ui.data.masks[..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &Queue) {
|
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &Queue) {
|
||||||
let slice = &[WindowUniform {
|
let slice = &[WindowUniform {
|
||||||
@@ -117,12 +55,7 @@ impl UiRenderer {
|
|||||||
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
||||||
device: &Device,
|
|
||||||
queue: &Queue,
|
|
||||||
config: &SurfaceConfiguration,
|
|
||||||
limits: UiLimits,
|
|
||||||
) -> Self {
|
|
||||||
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
||||||
label: Some("UI Shape Shader"),
|
label: Some("UI Shape Shader"),
|
||||||
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
||||||
@@ -130,31 +63,36 @@ impl UiRenderer {
|
|||||||
|
|
||||||
let window_uniform = WindowUniform::default();
|
let window_uniform = WindowUniform::default();
|
||||||
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||||
label: Some("window"),
|
label: Some("Camera Buffer"),
|
||||||
contents: bytemuck::cast_slice(&[window_uniform]),
|
contents: bytemuck::cast_slice(&[window_uniform]),
|
||||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||||
});
|
});
|
||||||
|
|
||||||
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
let instance = ArrBuf::new(
|
||||||
entries: &[BindGroupLayoutEntry {
|
device,
|
||||||
|
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||||
|
"instance",
|
||||||
|
);
|
||||||
|
let data = ArrBuf::new(
|
||||||
|
device,
|
||||||
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
|
"data",
|
||||||
|
);
|
||||||
|
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
visibility: ShaderStages::VERTEX,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
ty: BufferBindingType::Uniform,
|
ty: BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
min_binding_size: None,
|
min_binding_size: None,
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
}],
|
},
|
||||||
label: Some("window"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer);
|
|
||||||
|
|
||||||
let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
||||||
entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| {
|
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: i as u32,
|
binding: 1,
|
||||||
visibility: ShaderStages::FRAGMENT,
|
visibility: ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
@@ -162,24 +100,16 @@ impl UiRenderer {
|
|||||||
min_binding_size: None,
|
min_binding_size: None,
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
}
|
},
|
||||||
}),
|
],
|
||||||
label: Some("primitive"),
|
label: Some("camera_bind_group_layout"),
|
||||||
});
|
});
|
||||||
|
|
||||||
let tex_manager = GpuTextures::new(device, queue);
|
let bind_group = Self::bind_group(device, &bind_group_layout, &window_buffer, &data.buffer);
|
||||||
let masks = ArrBuf::new(
|
|
||||||
device,
|
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
|
||||||
"ui masks",
|
|
||||||
);
|
|
||||||
|
|
||||||
let rsc_layout = Self::rsc_layout(device, &limits);
|
|
||||||
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
|
|
||||||
|
|
||||||
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||||
label: Some("UI Shape Pipeline Layout"),
|
label: Some("UI Shape Pipeline Layout"),
|
||||||
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
push_constant_ranges: &[],
|
push_constant_ranges: &[],
|
||||||
});
|
});
|
||||||
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||||
@@ -221,133 +151,34 @@ impl UiRenderer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
uniform_group,
|
bind_group_layout,
|
||||||
primitive_layout,
|
bind_group,
|
||||||
rsc_layout,
|
|
||||||
rsc_group,
|
|
||||||
pipeline,
|
pipeline,
|
||||||
window_buffer,
|
window_buffer,
|
||||||
layers: HashMap::default(),
|
instance,
|
||||||
active: Vec::new(),
|
data,
|
||||||
textures: tex_manager,
|
|
||||||
masks,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind_group_0(
|
pub fn bind_group(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
window_buffer: &Buffer,
|
window_buffer: &Buffer,
|
||||||
|
data: &Buffer,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
layout,
|
layout,
|
||||||
entries: &[BindGroupEntry {
|
entries: &[
|
||||||
|
BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: window_buffer.as_entire_binding(),
|
resource: window_buffer.as_entire_binding(),
|
||||||
}],
|
|
||||||
label: Some("ui window"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn primitive_group(
|
|
||||||
device: &Device,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
buffers: [(u32, &Buffer); PrimitiveBuffers::LEN],
|
|
||||||
) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &buffers.map(|(binding, buf)| BindGroupEntry {
|
|
||||||
binding,
|
|
||||||
resource: buf.as_entire_binding(),
|
|
||||||
}),
|
|
||||||
label: Some("ui primitives"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rsc_layout(device: &Device, limits: &UiLimits) -> BindGroupLayout {
|
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
||||||
entries: &[
|
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Texture {
|
|
||||||
sample_type: TextureSampleType::Float { filterable: false },
|
|
||||||
view_dimension: TextureViewDimension::D2,
|
|
||||||
multisampled: false,
|
|
||||||
},
|
|
||||||
count: Some(NonZero::new(limits.max_textures).unwrap()),
|
|
||||||
},
|
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: 1,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
|
||||||
count: Some(NonZero::new(limits.max_samplers).unwrap()),
|
|
||||||
},
|
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: 2,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: None,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
label: Some("ui rsc"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rsc_group(
|
|
||||||
device: &Device,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
tex_manager: &GpuTextures,
|
|
||||||
masks: &ArrBuf<Mask>,
|
|
||||||
) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &[
|
|
||||||
BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: BindingResource::TextureViewArray(&tex_manager.views()),
|
|
||||||
},
|
},
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
|
resource: data.as_entire_binding(),
|
||||||
},
|
|
||||||
BindGroupEntry {
|
|
||||||
binding: 2,
|
|
||||||
resource: masks.buffer.as_entire_binding(),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui rsc"),
|
label: Some("ui_bind_group"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_count(&self) -> usize {
|
|
||||||
self.textures.view_count()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UiLimits {
|
|
||||||
max_textures: u32,
|
|
||||||
max_samplers: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for UiLimits {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
max_textures: 100000,
|
|
||||||
max_samplers: 1000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiLimits {
|
|
||||||
pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 {
|
|
||||||
self.max_textures + self.max_samplers
|
|
||||||
}
|
|
||||||
pub fn max_binding_array_sampler_elements_per_shader_stage(&self) -> u32 {
|
|
||||||
self.max_samplers
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,281 +0,0 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
layout::{Color, UiRegion},
|
|
||||||
render::{
|
|
||||||
ArrBuf,
|
|
||||||
data::{MaskIdx, PrimitiveInstance},
|
|
||||||
},
|
|
||||||
util::Id,
|
|
||||||
};
|
|
||||||
use bytemuck::Pod;
|
|
||||||
use wgpu::*;
|
|
||||||
|
|
||||||
pub struct Primitives {
|
|
||||||
instances: Vec<PrimitiveInstance>,
|
|
||||||
assoc: Vec<Id>,
|
|
||||||
data: PrimitiveData,
|
|
||||||
free: Vec<usize>,
|
|
||||||
pub updated: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Primitives {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
instances: Default::default(),
|
|
||||||
assoc: Default::default(),
|
|
||||||
data: Default::default(),
|
|
||||||
free: Vec::new(),
|
|
||||||
updated: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Primitive: Pod {
|
|
||||||
const BINDING: u32;
|
|
||||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! primitives {
|
|
||||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PrimitiveData {
|
|
||||||
$($name: PrimitiveVec<$ty>,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveBuffers {
|
|
||||||
$($name: ArrBuf<$ty>,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveBuffers {
|
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
|
|
||||||
$(self.$name.update(device, queue, &data.$name);)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveBuffers {
|
|
||||||
pub const LEN: usize = primitives!(@count $($name)*);
|
|
||||||
pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] {
|
|
||||||
[
|
|
||||||
$((<$ty>::BINDING, &self.$name.buffer),)*
|
|
||||||
]
|
|
||||||
}
|
|
||||||
pub fn new(device: &Device) -> Self {
|
|
||||||
Self {
|
|
||||||
$($name: ArrBuf::new(
|
|
||||||
device,
|
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
|
||||||
stringify!($name),
|
|
||||||
),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveData {
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
$(self.$name.clear();)*
|
|
||||||
}
|
|
||||||
pub fn free(&mut self, binding: u32, idx: usize) {
|
|
||||||
match binding {
|
|
||||||
$(<$ty>::BINDING => self.$name.free(idx),)*
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(
|
|
||||||
unsafe impl bytemuck::Pod for $ty {}
|
|
||||||
unsafe impl bytemuck::Zeroable for $ty {}
|
|
||||||
impl Primitive for $ty {
|
|
||||||
const BINDING: u32 = $binding;
|
|
||||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self> {
|
|
||||||
&mut data.$name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
(@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t),+) };
|
|
||||||
(@count $t:tt) => { 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveInst<P> {
|
|
||||||
pub id: Id,
|
|
||||||
pub primitive: P,
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub mask_idx: MaskIdx,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Primitives {
|
|
||||||
pub fn write<P: Primitive>(
|
|
||||||
&mut self,
|
|
||||||
layer: usize,
|
|
||||||
PrimitiveInst {
|
|
||||||
id,
|
|
||||||
primitive,
|
|
||||||
region,
|
|
||||||
mask_idx,
|
|
||||||
}: PrimitiveInst<P>,
|
|
||||||
) -> PrimitiveHandle {
|
|
||||||
let vec = P::vec(&mut self.data);
|
|
||||||
let i = vec.add(primitive);
|
|
||||||
let inst = PrimitiveInstance {
|
|
||||||
region,
|
|
||||||
idx: i as u32,
|
|
||||||
mask_idx,
|
|
||||||
binding: P::BINDING,
|
|
||||||
};
|
|
||||||
let inst_i = if let Some(i) = self.free.pop() {
|
|
||||||
self.instances[i] = inst;
|
|
||||||
self.assoc[i] = id;
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
let i = self.instances.len();
|
|
||||||
self.instances.push(inst);
|
|
||||||
self.assoc.push(id);
|
|
||||||
i
|
|
||||||
};
|
|
||||||
PrimitiveHandle::new::<P>(layer, inst_i, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns (old index, new index)
|
|
||||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
|
||||||
self.free.sort_by(|a, b| b.cmp(a));
|
|
||||||
self.free.drain(..).filter_map(|i| {
|
|
||||||
self.instances.swap_remove(i);
|
|
||||||
self.assoc.swap_remove(i);
|
|
||||||
if i == self.instances.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let id = self.assoc[i];
|
|
||||||
let old = self.instances.len();
|
|
||||||
Some(PrimitiveChange { id, old, new: i })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
|
||||||
self.data.free(h.binding, h.data_idx);
|
|
||||||
self.free.push(h.inst_idx);
|
|
||||||
self.instances[h.inst_idx].mask_idx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data(&self) -> &PrimitiveData {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
|
||||||
&self.instances
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
|
||||||
self.updated = true;
|
|
||||||
&mut self.instances[h.inst_idx].region
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveChange {
|
|
||||||
pub id: Id,
|
|
||||||
pub old: usize,
|
|
||||||
pub new: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PrimitiveHandle {
|
|
||||||
pub layer: usize,
|
|
||||||
pub inst_idx: usize,
|
|
||||||
pub data_idx: usize,
|
|
||||||
pub binding: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveHandle {
|
|
||||||
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
layer,
|
|
||||||
inst_idx,
|
|
||||||
data_idx,
|
|
||||||
binding: P::BINDING,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
primitives!(
|
|
||||||
rects: RectPrimitive => 0,
|
|
||||||
textures: TexturePrimitive => 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct RectPrimitive {
|
|
||||||
pub color: Color<u8>,
|
|
||||||
pub radius: f32,
|
|
||||||
pub thickness: f32,
|
|
||||||
pub inner_radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RectPrimitive {
|
|
||||||
pub fn color(color: Color<u8>) -> Self {
|
|
||||||
Self {
|
|
||||||
color,
|
|
||||||
radius: 0.0,
|
|
||||||
thickness: 0.0,
|
|
||||||
inner_radius: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct TexturePrimitive {
|
|
||||||
pub view_idx: u32,
|
|
||||||
pub sampler_idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveVec<T> {
|
|
||||||
vec: Vec<T>,
|
|
||||||
free: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PrimitiveVec<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
vec: Vec::new(),
|
|
||||||
free: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn add(&mut self, t: T) -> usize {
|
|
||||||
if let Some(i) = self.free.pop() {
|
|
||||||
self.vec[i] = t;
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
let i = self.vec.len();
|
|
||||||
self.vec.push(t);
|
|
||||||
i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn free(&mut self, i: usize) {
|
|
||||||
self.free.push(i);
|
|
||||||
}
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.free.clear();
|
|
||||||
self.vec.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for PrimitiveVec<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for PrimitiveVec<T> {
|
|
||||||
type Target = Vec<T>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.vec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DerefMut for PrimitiveVec<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.vec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
src/render/primitive/color.rs
Normal file
47
src/render/primitive/color.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#![allow(clippy::multiple_bound_locations)]
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, bytemuck::Zeroable)]
|
||||||
|
pub struct Color<T: ColorNum> {
|
||||||
|
r: T,
|
||||||
|
g: T,
|
||||||
|
b: T,
|
||||||
|
a: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ColorNum> Color<T> {
|
||||||
|
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
|
||||||
|
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
|
||||||
|
|
||||||
|
pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN);
|
||||||
|
pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN);
|
||||||
|
pub const YELLOW: Self = Self::rgb(T::MAX, T::MAX, T::MIN);
|
||||||
|
pub const LIME: Self = Self::rgb(T::MID, T::MAX, T::MIN);
|
||||||
|
pub const GREEN: Self = Self::rgb(T::MIN, T::MAX, T::MIN);
|
||||||
|
pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX);
|
||||||
|
pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX);
|
||||||
|
pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ColorNum> Color<T> {
|
||||||
|
pub const fn new(r: T, g: T, b: T, a: T) -> Self {
|
||||||
|
Self { r, g, b, a }
|
||||||
|
}
|
||||||
|
pub const fn rgb(r: T, g: T, b: T) -> Self {
|
||||||
|
Self { r, g, b, a: T::MAX }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ColorNum {
|
||||||
|
const MIN: Self;
|
||||||
|
const MID: Self;
|
||||||
|
const MAX: Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorNum for u8 {
|
||||||
|
const MIN: Self = u8::MIN;
|
||||||
|
const MID: Self = u8::MAX / 2;
|
||||||
|
const MAX: Self = u8::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl bytemuck::Pod for Color<u8> {}
|
||||||
14
src/render/primitive/def.rs
Normal file
14
src/render/primitive/def.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::primitive::{Color, PrimitiveData};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct RoundedRectData {
|
||||||
|
pub color: Color<u8>,
|
||||||
|
pub radius: f32,
|
||||||
|
pub thickness: f32,
|
||||||
|
pub inner_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveData for RoundedRectData {
|
||||||
|
const DISCRIM: u32 = 0;
|
||||||
|
}
|
||||||
97
src/render/primitive/format.rs
Normal file
97
src/render/primitive/format.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use crate::primitive::{point::point, Point};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||||
|
pub struct UIPos {
|
||||||
|
pub anchor: Point,
|
||||||
|
pub offset: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UIPos {
|
||||||
|
pub const fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
anchor: point(anchor_x, anchor_y),
|
||||||
|
offset: point(offset_x, offset_y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn top_left() -> Self {
|
||||||
|
Self::anchor_offset(0.0, 0.0, 0.0, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn bottom_right() -> Self {
|
||||||
|
Self::anchor_offset(1.0, 1.0, 0.0, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn within(&self, region: &UIRegion) -> UIPos {
|
||||||
|
let range = region.bot_right.anchor - region.top_left.anchor;
|
||||||
|
let region_offset = region
|
||||||
|
.top_left
|
||||||
|
.offset
|
||||||
|
.lerp(region.bot_right.offset, self.anchor);
|
||||||
|
UIPos {
|
||||||
|
anchor: region.top_left.anchor + self.anchor * range,
|
||||||
|
offset: self.offset + region_offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn axis_mut(&mut self, axis: Axis) -> UIPosAxisView<'_> {
|
||||||
|
match axis {
|
||||||
|
Axis::X => UIPosAxisView {
|
||||||
|
anchor: &mut self.anchor.x,
|
||||||
|
offset: &mut self.offset.x,
|
||||||
|
},
|
||||||
|
Axis::Y => UIPosAxisView {
|
||||||
|
anchor: &mut self.anchor.y,
|
||||||
|
offset: &mut self.offset.y,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UIPosAxisView<'a> {
|
||||||
|
pub anchor: &'a mut f32,
|
||||||
|
pub offset: &'a mut f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct UIRegion {
|
||||||
|
pub top_left: UIPos,
|
||||||
|
pub bot_right: UIPos,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UIRegion {
|
||||||
|
pub const fn full() -> Self {
|
||||||
|
Self {
|
||||||
|
top_left: UIPos::top_left(),
|
||||||
|
bot_right: UIPos::bottom_right(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn within(&self, parent: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
top_left: self.top_left.within(parent),
|
||||||
|
bot_right: self.bot_right.within(parent),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn select(&mut self, inner: &Self) {
|
||||||
|
*self = inner.within(self);
|
||||||
|
}
|
||||||
|
pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> {
|
||||||
|
UIRegionAxisView {
|
||||||
|
top_left: self.top_left.axis_mut(axis),
|
||||||
|
bot_right: self.bot_right.axis_mut(axis),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UIRegionAxisView<'a> {
|
||||||
|
pub top_left: UIPosAxisView<'a>,
|
||||||
|
pub bot_right: UIPosAxisView<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum Axis {
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
}
|
||||||
63
src/render/primitive/mod.rs
Normal file
63
src/render/primitive/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
mod color;
|
||||||
|
mod def;
|
||||||
|
mod format;
|
||||||
|
mod point;
|
||||||
|
|
||||||
|
pub use color::*;
|
||||||
|
pub use def::*;
|
||||||
|
pub use format::*;
|
||||||
|
pub use point::*;
|
||||||
|
|
||||||
|
use crate::{render::data::PrimitiveInstance, WidgetId, Widgets};
|
||||||
|
use bytemuck::Pod;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Primitives {
|
||||||
|
pub instances: Vec<PrimitiveInstance>,
|
||||||
|
pub data: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Painter<'a> {
|
||||||
|
nodes: &'a Widgets,
|
||||||
|
primitives: Primitives,
|
||||||
|
pub region: UIRegion,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: Self must have at least u32 alignment
|
||||||
|
pub trait PrimitiveData: Pod {
|
||||||
|
const DISCRIM: u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Painter<'a> {
|
||||||
|
pub fn new(nodes: &'a Widgets) -> Self {
|
||||||
|
Self {
|
||||||
|
nodes,
|
||||||
|
primitives: Primitives::default(),
|
||||||
|
region: UIRegion::full(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn write<Data: PrimitiveData>(&mut self, data: Data) {
|
||||||
|
let ptr = self.primitives.data.len() as u32;
|
||||||
|
let region = self.region;
|
||||||
|
self.primitives
|
||||||
|
.instances
|
||||||
|
.push(PrimitiveInstance { region, ptr });
|
||||||
|
self.primitives.data.push(Data::DISCRIM);
|
||||||
|
self.primitives
|
||||||
|
.data
|
||||||
|
.extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data]));
|
||||||
|
}
|
||||||
|
pub fn draw(&mut self, node: &WidgetId) {
|
||||||
|
self.nodes.get(node).draw(self);
|
||||||
|
}
|
||||||
|
pub fn draw_within(&mut self, node: &WidgetId, region: UIRegion) {
|
||||||
|
let old = self.region;
|
||||||
|
self.region.select(®ion);
|
||||||
|
self.draw(node);
|
||||||
|
self.region = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> Primitives {
|
||||||
|
self.primitives
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/render/primitive/point.rs
Normal file
84
src/render/primitive/point.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use std::ops::*;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct Point {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn point(x: f32, y: f32) -> Point {
|
||||||
|
Point::new(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
pub const fn new(x: f32, y: f32) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn lerp(self, to: Self, amt: impl const Into<Self>) -> Self {
|
||||||
|
let amt = amt.into();
|
||||||
|
Self {
|
||||||
|
x: lerp(self.x, to.x, amt.x),
|
||||||
|
y: lerp(self.y, to.y, amt.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn lerp(x: f32, y: f32, amt: f32) -> f32 {
|
||||||
|
(1.0 - amt) * x + y * amt
|
||||||
|
}
|
||||||
|
|
||||||
|
impl const From<f32> for Point {
|
||||||
|
fn from(v: f32) -> Self {
|
||||||
|
Self { x: v, y: v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_op_inner {
|
||||||
|
($op:ident $fn:ident $opa:ident $fna:ident) => {
|
||||||
|
impl const $op for Point {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn $fn(self, rhs: Self) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
x: self.x.$fn(rhs.x),
|
||||||
|
y: self.y.$fn(rhs.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl $opa for Point {
|
||||||
|
fn $fna(&mut self, rhs: Self) {
|
||||||
|
self.x.$fna(rhs.x);
|
||||||
|
self.y.$fna(rhs.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl const $op<f32> for Point {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn $fn(self, rhs: f32) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
x: self.x.$fn(rhs),
|
||||||
|
y: self.y.$fn(rhs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl $opa<f32> for Point {
|
||||||
|
fn $fna(&mut self, rhs: f32) {
|
||||||
|
self.x.$fna(rhs);
|
||||||
|
self.y.$fna(rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_op {
|
||||||
|
($op:ident $fn:ident) => {
|
||||||
|
impl_op_inner!($op $fn ${concat($op,Assign)} ${concat($fn,_assign)});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_op!(Add add);
|
||||||
|
impl_op!(Sub sub);
|
||||||
|
impl_op!(Mul mul);
|
||||||
|
impl_op!(Div div);
|
||||||
@@ -1,41 +1,7 @@
|
|||||||
const RECT: u32 = 0u;
|
|
||||||
const TEXTURE: u32 = 1u;
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
var<uniform> window: WindowUniform;
|
var<uniform> window: WindowUniform;
|
||||||
@group(1) @binding(RECT)
|
@group(0) @binding(1)
|
||||||
var<storage> rects: array<Rect>;
|
var<storage> data: array<u32>;
|
||||||
@group(1) @binding(TEXTURE)
|
|
||||||
var<storage> textures: array<TextureInfo>;
|
|
||||||
|
|
||||||
struct Rect {
|
|
||||||
color: u32,
|
|
||||||
radius: f32,
|
|
||||||
thickness: f32,
|
|
||||||
inner_radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TextureInfo {
|
|
||||||
view_idx: u32,
|
|
||||||
sampler_idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Mask {
|
|
||||||
top_left: UiVec2,
|
|
||||||
bot_right: UiVec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiVec2 {
|
|
||||||
rel: vec2<f32>,
|
|
||||||
abs: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(2) @binding(0)
|
|
||||||
var views: binding_array<texture_2d<f32>>;
|
|
||||||
@group(2) @binding(1)
|
|
||||||
var samplers: binding_array<sampler>;
|
|
||||||
@group(2) @binding(2)
|
|
||||||
var<storage> masks: array<Mask>;
|
|
||||||
|
|
||||||
struct WindowUniform {
|
struct WindowUniform {
|
||||||
dim: vec2<f32>,
|
dim: vec2<f32>,
|
||||||
@@ -46,24 +12,25 @@ struct InstanceInput {
|
|||||||
@location(1) top_left_offset: vec2<f32>,
|
@location(1) top_left_offset: vec2<f32>,
|
||||||
@location(2) bottom_right_anchor: vec2<f32>,
|
@location(2) bottom_right_anchor: vec2<f32>,
|
||||||
@location(3) bottom_right_offset: vec2<f32>,
|
@location(3) bottom_right_offset: vec2<f32>,
|
||||||
@location(4) binding: u32,
|
@location(4) pointer: u32,
|
||||||
@location(5) idx: u32,
|
}
|
||||||
@location(6) mask_idx: u32,
|
|
||||||
|
struct RoundedRect {
|
||||||
|
color: u32,
|
||||||
|
radius: f32,
|
||||||
|
thickness: f32,
|
||||||
|
inner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@location(0) top_left: vec2<f32>,
|
@location(0) pointer: u32,
|
||||||
@location(1) bot_right: vec2<f32>,
|
@location(1) top_left: vec2<f32>,
|
||||||
@location(2) uv: vec2<f32>,
|
@location(2) bot_right: vec2<f32>,
|
||||||
@location(3) binding: u32,
|
|
||||||
@location(4) idx: u32,
|
|
||||||
@location(5) mask_idx: u32,
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Region {
|
struct Region {
|
||||||
pos: vec2<f32>,
|
pos: vec2<f32>,
|
||||||
uv: vec2<f32>,
|
|
||||||
top_left: vec2<f32>,
|
top_left: vec2<f32>,
|
||||||
bot_right: vec2<f32>,
|
bot_right: vec2<f32>,
|
||||||
}
|
}
|
||||||
@@ -75,22 +42,19 @@ fn vs_main(
|
|||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
|
|
||||||
let top_left = floor(in.top_left_anchor * window.dim) + floor(in.top_left_offset);
|
let top_left = in.top_left_anchor * window.dim + in.top_left_offset;
|
||||||
let bot_right = floor(in.bottom_right_anchor * window.dim) + floor(in.bottom_right_offset);
|
let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset;
|
||||||
let size = bot_right - top_left;
|
let size = bot_right - top_left;
|
||||||
|
|
||||||
let uv = vec2<f32>(
|
var pos = top_left + vec2<f32>(
|
||||||
f32(vi % 2u),
|
f32(vi % 2u),
|
||||||
f32(vi / 2u)
|
f32(vi / 2u)
|
||||||
);
|
) * size;
|
||||||
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
pos = pos / window.dim * 2.0 - 1.0;
|
||||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||||
out.uv = uv;
|
out.pointer = in.pointer;
|
||||||
out.binding = in.binding;
|
|
||||||
out.idx = in.idx;
|
|
||||||
out.top_left = top_left;
|
out.top_left = top_left;
|
||||||
out.bot_right = bot_right;
|
out.bot_right = bot_right;
|
||||||
out.mask_idx = in.mask_idx;
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -100,37 +64,24 @@ fn fs_main(
|
|||||||
in: VertexOutput
|
in: VertexOutput
|
||||||
) -> @location(0) vec4<f32> {
|
) -> @location(0) vec4<f32> {
|
||||||
let pos = in.clip_position.xy;
|
let pos = in.clip_position.xy;
|
||||||
let region = Region(pos, in.uv, in.top_left, in.bot_right);
|
let ty = data[in.pointer];
|
||||||
let i = in.idx;
|
let dp = in.pointer + 1u;
|
||||||
var color: vec4<f32>;
|
let region = Region(pos, in.top_left, in.bot_right);
|
||||||
switch in.binding {
|
switch ty {
|
||||||
case RECT: {
|
case 0u: {
|
||||||
color = draw_rounded_rect(region, rects[i]);
|
return draw_rounded_rect(region, RoundedRect(
|
||||||
|
data[dp + 0u],
|
||||||
|
bitcast<f32>(data[dp + 1u]),
|
||||||
|
bitcast<f32>(data[dp + 2u]),
|
||||||
|
bitcast<f32>(data[dp + 3u]),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
case TEXTURE: {
|
default: {}
|
||||||
color = draw_texture(region, textures[i]);
|
|
||||||
}
|
}
|
||||||
default: {
|
return vec4(1.0, 0.0, 1.0, 1.0);
|
||||||
color = vec4(1.0, 0.0, 1.0, 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if in.mask_idx != 4294967295u {
|
|
||||||
let mask = masks[in.mask_idx];
|
|
||||||
let top_left = floor(mask.top_left.rel * window.dim) + floor(mask.top_left.abs);
|
|
||||||
let bot_right = floor(mask.bot_right.rel * window.dim) + floor(mask.bot_right.abs);
|
|
||||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
|
||||||
color *= 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this seems really inefficient (per frag indexing)?
|
fn draw_rounded_rect(region: Region, rect: RoundedRect) -> vec4<f32> {
|
||||||
fn draw_texture(region: Region, info: TextureInfo) -> vec4<f32> {
|
|
||||||
return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_rounded_rect(region: Region, rect: Rect) -> vec4<f32> {
|
|
||||||
var color = unpack4x8unorm(rect.color);
|
var color = unpack4x8unorm(rect.color);
|
||||||
|
|
||||||
let edge = 0.5;
|
let edge = 0.5;
|
||||||
@@ -155,6 +106,5 @@ fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner:
|
|||||||
let p = pixel_pos - rect_center;
|
let p = pixel_pos - rect_center;
|
||||||
// vec from inner rect corner to pixel
|
// vec from inner rect corner to pixel
|
||||||
let q = abs(p) - (rect_corner - radius);
|
let q = abs(p) - (rect_corner - radius);
|
||||||
return length(max(q, vec2(0.0))) - radius;
|
return length(max(q, vec2<f32>(0.0, 0.0))) - radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
use image::{DynamicImage, EncodableLayout};
|
|
||||||
use wgpu::{util::DeviceExt, *};
|
|
||||||
|
|
||||||
use crate::layout::{TextureUpdate, Textures};
|
|
||||||
|
|
||||||
pub struct GpuTextures {
|
|
||||||
device: Device,
|
|
||||||
queue: Queue,
|
|
||||||
views: Vec<TextureView>,
|
|
||||||
view_count: usize,
|
|
||||||
samplers: Vec<Sampler>,
|
|
||||||
null_view: TextureView,
|
|
||||||
no_views: Vec<TextureView>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GpuTextures {
|
|
||||||
pub fn update(&mut self, textures: &mut Textures) -> bool {
|
|
||||||
let mut changed = false;
|
|
||||||
for update in textures.updates() {
|
|
||||||
changed = true;
|
|
||||||
match update {
|
|
||||||
TextureUpdate::Push(image) => self.push(image),
|
|
||||||
TextureUpdate::Set(i, image) => self.set(i, image),
|
|
||||||
TextureUpdate::SetFree => self.view_count += 1,
|
|
||||||
TextureUpdate::Free(i) => self.free(i),
|
|
||||||
TextureUpdate::PushFree => self.push_free(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
fn set(&mut self, i: u32, image: &DynamicImage) {
|
|
||||||
self.view_count += 1;
|
|
||||||
let view = self.create_view(image);
|
|
||||||
self.views[i as usize] = view;
|
|
||||||
}
|
|
||||||
fn free(&mut self, i: u32) {
|
|
||||||
self.view_count -= 1;
|
|
||||||
self.views[i as usize] = self.null_view.clone();
|
|
||||||
}
|
|
||||||
fn push(&mut self, image: &DynamicImage) {
|
|
||||||
self.view_count += 1;
|
|
||||||
let view = self.create_view(image);
|
|
||||||
self.views.push(view);
|
|
||||||
}
|
|
||||||
fn push_free(&mut self) {
|
|
||||||
self.view_count += 1;
|
|
||||||
self.views.push(self.null_view.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_view(&self, image: &DynamicImage) -> TextureView {
|
|
||||||
let image = image.to_rgba8();
|
|
||||||
let (width, height) = image.dimensions();
|
|
||||||
let texture = self.device.create_texture_with_data(
|
|
||||||
&self.queue,
|
|
||||||
&TextureDescriptor {
|
|
||||||
label: None,
|
|
||||||
size: Extent3d {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: TextureDimension::D2,
|
|
||||||
format: TextureFormat::Rgba8Unorm,
|
|
||||||
usage: TextureUsages::TEXTURE_BINDING,
|
|
||||||
view_formats: &[],
|
|
||||||
},
|
|
||||||
wgt::TextureDataOrder::MipMajor,
|
|
||||||
image.as_bytes(),
|
|
||||||
);
|
|
||||||
texture.create_view(&TextureViewDescriptor::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
|
||||||
let null_view = null_texture_view(device);
|
|
||||||
Self {
|
|
||||||
device: device.clone(),
|
|
||||||
queue: queue.clone(),
|
|
||||||
views: Vec::new(),
|
|
||||||
samplers: vec![default_sampler(device)],
|
|
||||||
no_views: vec![null_view.clone()],
|
|
||||||
null_view,
|
|
||||||
view_count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn views(&self) -> Vec<&TextureView> {
|
|
||||||
if self.views.is_empty() {
|
|
||||||
&self.no_views
|
|
||||||
} else {
|
|
||||||
&self.views
|
|
||||||
}
|
|
||||||
.iter()
|
|
||||||
.by_ref()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn samplers(&self) -> Vec<&Sampler> {
|
|
||||||
self.samplers.iter().by_ref().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_count(&self) -> usize {
|
|
||||||
self.view_count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn null_texture_view(device: &Device) -> TextureView {
|
|
||||||
device
|
|
||||||
.create_texture(&TextureDescriptor {
|
|
||||||
label: Some("null"),
|
|
||||||
size: Extent3d {
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: TextureDimension::D2,
|
|
||||||
format: TextureFormat::Rgba8Unorm,
|
|
||||||
usage: TextureUsages::TEXTURE_BINDING,
|
|
||||||
view_formats: &[],
|
|
||||||
})
|
|
||||||
.create_view(&TextureViewDescriptor::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_sampler(device: &Device) -> Sampler {
|
|
||||||
device.create_sampler(&SamplerDescriptor::default())
|
|
||||||
}
|
|
||||||
@@ -32,7 +32,7 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||||
let mut size = size as u64;
|
let mut size = size as u64;
|
||||||
if usage.contains(BufferUsages::STORAGE) {
|
if usage.contains(BufferUsages::STORAGE) {
|
||||||
size = size.max(std::mem::size_of::<T>() as u64);
|
size = size.max(1);
|
||||||
}
|
}
|
||||||
device.create_buffer(&BufferDescriptor {
|
device.create_buffer(&BufferDescriptor {
|
||||||
label: Some(label),
|
label: Some(label),
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ impl ApplicationHandler for App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||||
let client = self.client.as_mut().unwrap();
|
self.client.as_mut().unwrap().event(event, event_loop);
|
||||||
client.event(event, event_loop);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.7 KiB |
@@ -1,79 +0,0 @@
|
|||||||
use iris::{
|
|
||||||
core::{CursorState, Modifiers},
|
|
||||||
layout::Vec2,
|
|
||||||
};
|
|
||||||
use winit::{
|
|
||||||
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
|
||||||
keyboard::{Key, NamedKey},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::testing::Client;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Input {
|
|
||||||
cursor: CursorState,
|
|
||||||
pub modifiers: Modifiers,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Input {
|
|
||||||
pub fn event(&mut self, event: &WindowEvent) -> bool {
|
|
||||||
match event {
|
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
|
||||||
self.cursor.pos = Vec2::new(position.x as f32, position.y as f32);
|
|
||||||
self.cursor.exists = true;
|
|
||||||
}
|
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
|
||||||
let buttons = &mut self.cursor.buttons;
|
|
||||||
let pressed = state.is_pressed();
|
|
||||||
match button {
|
|
||||||
MouseButton::Left => buttons.left.update(pressed),
|
|
||||||
MouseButton::Right => buttons.right.update(pressed),
|
|
||||||
MouseButton::Middle => buttons.middle.update(pressed),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::MouseWheel { delta, .. } => {
|
|
||||||
let delta = match *delta {
|
|
||||||
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
|
|
||||||
MouseScrollDelta::PixelDelta(pos) => Vec2::new(pos.x as f32, pos.y as f32),
|
|
||||||
};
|
|
||||||
self.cursor.scroll_delta = delta;
|
|
||||||
}
|
|
||||||
WindowEvent::CursorLeft { .. } => {
|
|
||||||
self.cursor.exists = false;
|
|
||||||
self.modifiers.clear();
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
if let Key::Named(named) = event.logical_key {
|
|
||||||
let pressed = event.state.is_pressed();
|
|
||||||
match named {
|
|
||||||
NamedKey::Control => {
|
|
||||||
self.modifiers.control = pressed;
|
|
||||||
}
|
|
||||||
NamedKey::Shift => {
|
|
||||||
self.modifiers.shift = pressed;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
|
||||||
self.cursor.end_frame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn window_size(&self) -> Vec2 {
|
|
||||||
let size = self.renderer.window().inner_size();
|
|
||||||
(size.width, size.height).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor_state(&self) -> &CursorState {
|
|
||||||
&self.input.cursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
|
use gui::util::IntoTupleList;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use arboard::Clipboard;
|
use gui::{primitive::Axis, RoundedRect, UIColor, WidgetArrUtil, WidgetUtil, UI};
|
||||||
use cosmic_text::Family;
|
|
||||||
use iris::prelude::*;
|
|
||||||
use render::Renderer;
|
use render::Renderer;
|
||||||
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
|
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
|
||||||
|
|
||||||
use crate::testing::input::Input;
|
|
||||||
use len_fns::*;
|
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod input;
|
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
@@ -20,276 +15,57 @@ pub fn main() {
|
|||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
input: Input,
|
|
||||||
ui: Ui,
|
|
||||||
info: WidgetId<Text>,
|
|
||||||
focus: Option<WidgetId<TextEdit>>,
|
|
||||||
clipboard: Clipboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
|
||||||
struct Submit;
|
|
||||||
|
|
||||||
impl DefaultEvent for Submit {
|
|
||||||
type Data = ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(window: Arc<Window>) -> Self {
|
pub fn new(window: Arc<Window>) -> Self {
|
||||||
let renderer = Renderer::new(window);
|
let mut renderer = Renderer::new(window);
|
||||||
|
let rect = RoundedRect {
|
||||||
let mut ui = Ui::new();
|
color: UIColor::WHITE,
|
||||||
let rrect = rect(Color::WHITE).radius(20);
|
radius: 10.0,
|
||||||
let pad_test = (
|
thickness: 0.0,
|
||||||
rrect.color(Color::BLUE),
|
inner_radius: 0.0,
|
||||||
|
};
|
||||||
|
let mut ui = UI::build();
|
||||||
|
let blue = ui.add(rect.color(UIColor::BLUE));
|
||||||
|
let handle = blue.handle();
|
||||||
|
let mut ui = ui.finish(
|
||||||
(
|
(
|
||||||
rrect
|
|
||||||
.color(Color::RED)
|
|
||||||
.sized((100, 100))
|
|
||||||
.center()
|
|
||||||
.width(rest(2)),
|
|
||||||
(
|
(
|
||||||
rrect.color(Color::ORANGE),
|
blue,
|
||||||
rrect.color(Color::LIME).pad(10.0),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.width(rest(2)),
|
|
||||||
rrect.color(Color::YELLOW),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.pad(10)
|
|
||||||
.width(rest(3)),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.add_static(&mut ui);
|
|
||||||
|
|
||||||
let span_test = (
|
|
||||||
rrect.color(Color::GREEN).width(100),
|
|
||||||
rrect.color(Color::ORANGE),
|
|
||||||
rrect.color(Color::CYAN),
|
|
||||||
rrect.color(Color::BLUE).width(rel(0.5)),
|
|
||||||
rrect.color(Color::MAGENTA).width(100),
|
|
||||||
rrect.color(Color::RED).width(100),
|
|
||||||
)
|
|
||||||
.span(Dir::LEFT)
|
|
||||||
.add_static(&mut ui);
|
|
||||||
|
|
||||||
let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui);
|
|
||||||
|
|
||||||
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);
|
|
||||||
})
|
|
||||||
.sized((150, 150))
|
|
||||||
.align(Align::BotRight);
|
|
||||||
|
|
||||||
let del_button = rect(Color::RED)
|
|
||||||
.radius(30)
|
|
||||||
.on(CursorSense::click(), move |ctx: &mut Client, _| {
|
|
||||||
ctx.ui[span_add].children.pop();
|
|
||||||
})
|
|
||||||
.sized((150, 150))
|
|
||||||
.align(Align::BotLeft);
|
|
||||||
|
|
||||||
let span_add_test = (span_add, add_button, del_button)
|
|
||||||
.stack()
|
|
||||||
.add_static(&mut ui);
|
|
||||||
|
|
||||||
let main = pad_test.pad(10).add_static(&mut ui);
|
|
||||||
|
|
||||||
let btext = |content| text(content).size(30);
|
|
||||||
|
|
||||||
let text_test = (
|
|
||||||
btext("this is a").align(Align::Left),
|
|
||||||
btext("teeeeeeeest").align(Align::Right),
|
|
||||||
btext("okkk\nokkkkkk!").align(Align::Left),
|
|
||||||
btext("hmm"),
|
|
||||||
btext("a"),
|
|
||||||
(
|
(
|
||||||
btext("'").family(Family::Monospace).align(Align::Top),
|
rect.color(UIColor::RED),
|
||||||
btext("'").family(Family::Monospace),
|
|
||||||
btext(":gamer mode").family(Family::Monospace),
|
|
||||||
rect(Color::CYAN).sized((10, 10)).center(),
|
|
||||||
rect(Color::RED).sized((100, 100)).center(),
|
|
||||||
rect(Color::PURPLE).sized((50, 50)).align(Align::Top),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.center(),
|
|
||||||
text("pretty cool right?").size(50),
|
|
||||||
)
|
|
||||||
.span(Dir::DOWN)
|
|
||||||
.add_static(&mut ui);
|
|
||||||
|
|
||||||
let texts = Span::empty(Dir::DOWN).gap(10).add_static(&mut ui);
|
|
||||||
let msg_area = (Rect::new(Color::SKY), texts.scroll().masked()).stack();
|
|
||||||
let add_text = text("add")
|
|
||||||
.editable()
|
|
||||||
.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()
|
|
||||||
.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());
|
|
||||||
});
|
|
||||||
let msg_box = (rect(Color::WHITE.darker(0.5)), text)
|
|
||||||
.stack()
|
|
||||||
.size(StackSize::Child(1))
|
|
||||||
.add(&mut client.ui);
|
|
||||||
client.ui[texts].children.push(msg_box.any());
|
|
||||||
})
|
|
||||||
.add(&mut ui);
|
|
||||||
let text_edit_scroll = (
|
|
||||||
msg_area,
|
|
||||||
(
|
(
|
||||||
Rect::new(Color::WHITE.darker(0.9)),
|
rect.color(UIColor::ORANGE),
|
||||||
(
|
rect.color(UIColor::LIME).pad(10.0),
|
||||||
add_text.clone().width(rest(1)),
|
|
||||||
Rect::new(Color::GREEN)
|
|
||||||
.on(CursorSense::click(), move |client: &mut Client, _| {
|
|
||||||
client.run_event(&add_text, Submit, ());
|
|
||||||
})
|
|
||||||
.sized((40, 40)),
|
|
||||||
)
|
)
|
||||||
.span(Dir::RIGHT)
|
.into_list()
|
||||||
|
.span(Axis::Y, [1, 1].into()),
|
||||||
|
rect.color(UIColor::YELLOW),
|
||||||
|
)
|
||||||
|
.into_list()
|
||||||
|
.span(Axis::X, [2, 2, 1])
|
||||||
.pad(10),
|
.pad(10),
|
||||||
)
|
)
|
||||||
.stack()
|
.into_list()
|
||||||
.size(StackSize::Child(1))
|
.span(Axis::X, [1, 3]),
|
||||||
.offset_layer(1)
|
rect.color(UIColor::GREEN),
|
||||||
.align(Align::Bot),
|
|
||||||
)
|
)
|
||||||
.span(Dir::DOWN)
|
.into_list()
|
||||||
.add_static(&mut ui);
|
.span(Axis::Y, [3, 1])
|
||||||
|
.pad(10),
|
||||||
let switch_button = |color, to, label| {
|
);
|
||||||
let rect = rect(color)
|
ui.widgets.get_mut(&handle).unwrap().color = UIColor::MAGENTA;
|
||||||
.id_on(CursorSense::click(), move |id, ui: &mut Ui, _| {
|
renderer.update(&ui);
|
||||||
ui[main].inner.set_static(to);
|
Self { renderer }
|
||||||
ui[id].color = color.darker(0.3);
|
|
||||||
})
|
|
||||||
.edit_on(
|
|
||||||
CursorSense::HoverStart | CursorSense::unclick(),
|
|
||||||
move |r, _| {
|
|
||||||
r.color = color.brighter(0.2);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.edit_on(CursorSense::HoverEnd, move |r, _| {
|
|
||||||
r.color = color;
|
|
||||||
});
|
|
||||||
(rect, text(label).size(30)).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::YELLOW.mul_rgb(0.5),
|
|
||||||
text_edit_scroll.any(),
|
|
||||||
"text edit scroll",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT);
|
|
||||||
|
|
||||||
let info = text("").add(&mut ui);
|
|
||||||
let info_sect = info.clone().pad(10).align(Align::Right);
|
|
||||||
|
|
||||||
((tabs.height(40), main).span(Dir::DOWN), info_sect)
|
|
||||||
.stack()
|
|
||||||
.set_root(&mut ui);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
renderer,
|
|
||||||
input: Input::default(),
|
|
||||||
ui,
|
|
||||||
info,
|
|
||||||
focus: None,
|
|
||||||
clipboard: Clipboard::new().unwrap(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
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 {
|
match event {
|
||||||
WindowEvent::CloseRequested => event_loop.exit(),
|
WindowEvent::CloseRequested => event_loop.exit(),
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => self.renderer.draw(),
|
||||||
self.ui.update();
|
WindowEvent::Resized(size) => self.renderer.resize(&size),
|
||||||
self.renderer.update(&mut self.ui);
|
|
||||||
self.renderer.draw()
|
|
||||||
}
|
|
||||||
WindowEvent::Resized(size) => {
|
|
||||||
self.ui.resize((size.width, size.height));
|
|
||||||
self.renderer.resize(&size)
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
if let Some(sel) = &self.focus
|
|
||||||
&& event.state.is_pressed()
|
|
||||||
{
|
|
||||||
let mut text = self.ui.text(sel);
|
|
||||||
match text.apply_event(&event, &self.input.modifiers) {
|
|
||||||
TextInputResult::Unfocus => {
|
|
||||||
self.focus = None;
|
|
||||||
}
|
|
||||||
TextInputResult::Submit => {
|
|
||||||
self.run_event(&sel.clone(), Submit, ());
|
|
||||||
}
|
|
||||||
TextInputResult::Paste => {
|
|
||||||
if let Ok(t) = self.clipboard.get_text() {
|
|
||||||
text.insert(&t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TextInputResult::Unused | TextInputResult::Used => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
let new = format!(
|
|
||||||
"widgets: {}\nactive:{}\nviews: {}",
|
|
||||||
self.ui.num_widgets(),
|
|
||||||
self.ui.active_widgets(),
|
|
||||||
self.renderer.ui.view_count()
|
|
||||||
);
|
|
||||||
if new != *self.ui[&self.info].content {
|
|
||||||
*self.ui[&self.info].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,51 +1,47 @@
|
|||||||
|
use gui::{UIRenderNode, UI};
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use iris::{
|
use wgpu::util::StagingBelt;
|
||||||
layout::Ui,
|
|
||||||
render::{UiLimits, UiRenderer},
|
|
||||||
};
|
|
||||||
use wgpu::{util::StagingBelt, *};
|
|
||||||
use winit::{dpi::PhysicalSize, window::Window};
|
use winit::{dpi::PhysicalSize, window::Window};
|
||||||
|
|
||||||
pub const CLEAR_COLOR: Color = Color::BLACK;
|
pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK;
|
||||||
|
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
window: Arc<Window>,
|
surface: wgpu::Surface<'static>,
|
||||||
surface: Surface<'static>,
|
device: wgpu::Device,
|
||||||
device: Device,
|
queue: wgpu::Queue,
|
||||||
queue: Queue,
|
config: wgpu::SurfaceConfiguration,
|
||||||
config: SurfaceConfiguration,
|
encoder: wgpu::CommandEncoder,
|
||||||
encoder: CommandEncoder,
|
|
||||||
staging_belt: StagingBelt,
|
staging_belt: StagingBelt,
|
||||||
pub ui: UiRenderer,
|
ui_node: UIRenderNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub fn update(&mut self, updates: &mut Ui) {
|
pub fn update(&mut self, ui: &UI) {
|
||||||
self.ui.update(&self.device, &self.queue, updates);
|
self.ui_node.update(&self.device, &self.queue, ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
pub fn draw(&mut self) {
|
||||||
let output = self.surface.get_current_texture().unwrap();
|
let output = self.surface.get_current_texture().unwrap();
|
||||||
let view = output
|
let view = output
|
||||||
.texture
|
.texture
|
||||||
.create_view(&TextureViewDescriptor::default());
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
|
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
|
||||||
{
|
{
|
||||||
let render_pass = &mut encoder.begin_render_pass(&RenderPassDescriptor {
|
let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
color_attachments: &[Some(RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: &view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: Operations {
|
ops: wgpu::Operations {
|
||||||
load: LoadOp::Clear(CLEAR_COLOR),
|
load: wgpu::LoadOp::Clear(CLEAR_COLOR),
|
||||||
store: StoreOp::Store,
|
store: wgpu::StoreOp::Store,
|
||||||
},
|
},
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
})],
|
})],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
self.ui.draw(render_pass);
|
self.ui_node.draw(render_pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
@@ -58,11 +54,11 @@ impl Renderer {
|
|||||||
self.config.width = size.width;
|
self.config.width = size.width;
|
||||||
self.config.height = size.height;
|
self.config.height = size.height;
|
||||||
self.surface.configure(&self.device, &self.config);
|
self.surface.configure(&self.device, &self.config);
|
||||||
self.ui.resize(size, &self.queue);
|
self.ui_node.resize(size, &self.queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_encoder(device: &Device) -> CommandEncoder {
|
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
|
||||||
device.create_command_encoder(&CommandEncoderDescriptor {
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
label: Some("Render Encoder"),
|
label: Some("Render Encoder"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -70,8 +66,8 @@ impl Renderer {
|
|||||||
pub fn new(window: Arc<Window>) -> Self {
|
pub fn new(window: Arc<Window>) -> Self {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
|
|
||||||
let instance = Instance::new(&InstanceDescriptor {
|
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||||
backends: Backends::PRIMARY,
|
backends: wgpu::Backends::PRIMARY,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,28 +76,18 @@ impl Renderer {
|
|||||||
.expect("Could not create window surface!");
|
.expect("Could not create window surface!");
|
||||||
|
|
||||||
let adapter = instance
|
let adapter = instance
|
||||||
.request_adapter(&RequestAdapterOptions {
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
power_preference: PowerPreference::default(),
|
power_preference: wgpu::PowerPreference::default(),
|
||||||
compatible_surface: Some(&surface),
|
compatible_surface: Some(&surface),
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
})
|
})
|
||||||
.block_on()
|
.block_on()
|
||||||
.expect("Could not get adapter!");
|
.expect("Could not get adapter!");
|
||||||
|
|
||||||
let ui_limits = UiLimits::default();
|
|
||||||
|
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(&DeviceDescriptor {
|
.request_device(&wgpu::DeviceDescriptor {
|
||||||
required_features: Features::TEXTURE_BINDING_ARRAY
|
required_features: wgpu::Features::empty(),
|
||||||
| Features::PARTIALLY_BOUND_BINDING_ARRAY
|
required_limits: wgpu::Limits::default(),
|
||||||
| Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
|
||||||
required_limits: Limits {
|
|
||||||
max_binding_array_elements_per_shader_stage: ui_limits
|
|
||||||
.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(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.block_on()
|
.block_on()
|
||||||
@@ -115,12 +101,12 @@ impl Renderer {
|
|||||||
.find(|f| f.is_srgb())
|
.find(|f| f.is_srgb())
|
||||||
.unwrap_or(surface_caps.formats[0]);
|
.unwrap_or(surface_caps.formats[0]);
|
||||||
|
|
||||||
let config = SurfaceConfiguration {
|
let config = wgpu::SurfaceConfiguration {
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: surface_format,
|
format: surface_format,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
present_mode: PresentMode::AutoVsync,
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
alpha_mode: surface_caps.alpha_modes[0],
|
alpha_mode: surface_caps.alpha_modes[0],
|
||||||
desired_maximum_frame_latency: 2,
|
desired_maximum_frame_latency: 2,
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
@@ -131,7 +117,7 @@ impl Renderer {
|
|||||||
let staging_belt = StagingBelt::new(4096 * 4);
|
let staging_belt = StagingBelt::new(4096 * 4);
|
||||||
let encoder = Self::create_encoder(&device);
|
let encoder = Self::create_encoder(&device);
|
||||||
|
|
||||||
let shape_pipeline = UiRenderer::new(&device, &queue, &config, ui_limits);
|
let shape_pipeline = UIRenderNode::new(&device, &config);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
surface,
|
surface,
|
||||||
@@ -140,12 +126,7 @@ impl Renderer {
|
|||||||
config,
|
config,
|
||||||
encoder,
|
encoder,
|
||||||
staging_belt,
|
staging_belt,
|
||||||
ui: shape_pipeline,
|
ui_node: shape_pipeline,
|
||||||
window,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn window(&self) -> &Window {
|
|
||||||
self.window.as_ref()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::util::{Id, IdNum, IdTracker};
|
|
||||||
|
|
||||||
pub struct Arena<T, I> {
|
|
||||||
data: Vec<T>,
|
|
||||||
tracker: IdTracker<I>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, I: IdNum> Arena<T, I> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
data: Vec::new(),
|
|
||||||
tracker: IdTracker::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, value: T) -> Id<I> {
|
|
||||||
let id = self.tracker.next();
|
|
||||||
let i = id.idx();
|
|
||||||
if i == self.data.len() {
|
|
||||||
self.data.push(value);
|
|
||||||
} else {
|
|
||||||
self.data[i] = value;
|
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, id: Id<I>) -> T
|
|
||||||
where
|
|
||||||
T: Copy,
|
|
||||||
{
|
|
||||||
let i = id.idx();
|
|
||||||
self.tracker.free(id);
|
|
||||||
self.data[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, I: IdNum> Default for Arena<T, I> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TrackedArena<T, I> {
|
|
||||||
inner: Arena<T, I>,
|
|
||||||
refs: Vec<u32>,
|
|
||||||
pub changed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, I: IdNum> TrackedArena<T, I> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Arena::default(),
|
|
||||||
refs: Vec::new(),
|
|
||||||
changed: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, value: T) -> Id<I> {
|
|
||||||
self.changed = true;
|
|
||||||
let id = self.inner.push(value);
|
|
||||||
let i = id.idx();
|
|
||||||
if i == self.refs.len() {
|
|
||||||
self.refs.push(0);
|
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_ref(&mut self, i: Id<I>) {
|
|
||||||
self.refs[i.idx()] += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, id: Id<I>) -> T
|
|
||||||
where
|
|
||||||
T: Copy,
|
|
||||||
{
|
|
||||||
let i = id.idx();
|
|
||||||
self.refs[i] -= 1;
|
|
||||||
if self.refs[i] == 0 {
|
|
||||||
self.changed = true;
|
|
||||||
self.inner.remove(id)
|
|
||||||
} else {
|
|
||||||
self[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, I: IdNum> Default for TrackedArena<T, I> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, I> Deref for TrackedArena<T, I> {
|
|
||||||
type Target = Vec<T>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.inner.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, I> Deref for Arena<T, I> {
|
|
||||||
type Target = Vec<T>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,30 +0,0 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
pub struct MutDetect<T> {
|
|
||||||
inner: T,
|
|
||||||
pub changed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for MutDetect<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DerefMut for MutDetect<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.changed = true;
|
|
||||||
&mut self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for MutDetect<T> {
|
|
||||||
fn from(inner: T) -> Self {
|
|
||||||
MutDetect {
|
|
||||||
inner,
|
|
||||||
changed: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +1,44 @@
|
|||||||
#[repr(C)]
|
/// intentionally does not implement copy or clone
|
||||||
#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)]
|
/// which should make it harder to misuse;
|
||||||
pub struct Id<I = u64>(I);
|
/// the idea is to generally try to guarantee all IDs
|
||||||
|
/// point to something valid, although duplicate
|
||||||
|
/// gets around this if needed
|
||||||
|
#[derive(Eq, Hash, PartialEq, Debug)]
|
||||||
|
pub struct ID(usize);
|
||||||
|
|
||||||
unsafe impl<I: Copy + bytemuck::Zeroable + 'static> bytemuck::Pod for Id<I> {}
|
#[derive(Default)]
|
||||||
|
pub struct IDTracker {
|
||||||
pub struct IdTracker<I = u64> {
|
free: Vec<ID>,
|
||||||
free: Vec<Id<I>>,
|
cur: usize,
|
||||||
cur: Id<I>,
|
}
|
||||||
|
|
||||||
|
impl IDTracker {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: IdNum> IdTracker<I> {
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
#[allow(clippy::should_implement_trait)]
|
||||||
pub fn next(&mut self) -> Id<I> {
|
pub fn next(&mut self) -> ID {
|
||||||
if let Some(id) = self.free.pop() {
|
if let Some(id) = self.free.pop() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
let next = self.cur.next();
|
let id = ID(self.cur);
|
||||||
std::mem::replace(&mut self.cur, next)
|
self.cur += 1;
|
||||||
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn free(&mut self, id: Id<I>) {
|
pub fn free(&mut self, id: ID) {
|
||||||
self.free.push(id);
|
self.free.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: IdNum> Id<I> {
|
impl ID {
|
||||||
pub fn idx(&self) -> usize {
|
/// this must be used carefully to make sure
|
||||||
self.0.idx()
|
/// all IDs are still valid references;
|
||||||
}
|
/// named weirdly to indicate this.
|
||||||
pub fn next(&self) -> Id<I> {
|
/// generally should not be used in "user" code
|
||||||
Self(self.0.next())
|
pub fn duplicate(&self) -> Self {
|
||||||
}
|
Self(self.0)
|
||||||
pub const fn preset(value: I) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: IdNum> Default for IdTracker<I> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
free: Vec::new(),
|
|
||||||
cur: Id(I::first()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IdNum {
|
|
||||||
fn first() -> Self;
|
|
||||||
fn next(&self) -> Self;
|
|
||||||
fn idx(&self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IdNum for u64 {
|
|
||||||
fn first() -> Self {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&self) -> Self {
|
|
||||||
self + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn idx(&self) -> usize {
|
|
||||||
*self as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IdNum for u32 {
|
|
||||||
fn first() -> Self {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&self) -> Self {
|
|
||||||
self + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn idx(&self) -> usize {
|
|
||||||
*self as usize
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
use std::ops::*;
|
|
||||||
|
|
||||||
pub const trait LerpUtil {
|
|
||||||
fn lerp(self, from: Self, to: Self) -> Self;
|
|
||||||
fn lerp_inv(self, from: Self, to: Self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const trait DivOr {
|
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl const DivOr for f32 {
|
|
||||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
|
||||||
let res = self / rhs;
|
|
||||||
if res.is_nan() { other } else { res }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: const Add<Output = T> + const Sub<Output = T> + const Mul<Output = T> + const DivOr + Copy> const
|
|
||||||
LerpUtil for T
|
|
||||||
{
|
|
||||||
/// linear interpolation
|
|
||||||
/// from * (1.0 - self) + to * self
|
|
||||||
fn lerp(self, from: Self, to: Self) -> Self {
|
|
||||||
from + (to - from) * self
|
|
||||||
}
|
|
||||||
/// inverse of lerp
|
|
||||||
fn lerp_inv(self, from: Self, to: Self) -> Self {
|
|
||||||
(self - from).div_or(to - from, from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_op {
|
|
||||||
($T:ident $op:ident $fn:ident $opa:ident $fna:ident; $($field:ident)*) => {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
mod ${concat($T, _op_, $fn, _impl)} {
|
|
||||||
use super::*;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std::ops::*;
|
|
||||||
impl const $op for $T {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn $fn(self, rhs: Self) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
$($field: self.$field.$fn(rhs.$field),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl const $opa for $T {
|
|
||||||
fn $fna(&mut self, rhs: Self) {
|
|
||||||
$(self.$field.$fna(rhs.$field);)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl const $op<f32> for $T {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn $fn(self, rhs: f32) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
$($field: self.$field.$fn(rhs),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl const $op<$T> for f32 {
|
|
||||||
type Output = $T;
|
|
||||||
|
|
||||||
fn $fn(self, rhs: $T) -> Self::Output {
|
|
||||||
$T {
|
|
||||||
$($field: self.$fn(rhs.$field),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl const $opa<f32> for $T {
|
|
||||||
fn $fna(&mut self, rhs: f32) {
|
|
||||||
$(self.$field.$fna(rhs);)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($T:ident $op:ident $fn:ident; $($field:ident)*) => {
|
|
||||||
impl_op!($T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*);
|
|
||||||
};
|
|
||||||
(impl $op:ident for $T:ident: $fn:ident $($field:ident)*) => {
|
|
||||||
impl_op!($T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use impl_op;
|
|
||||||
@@ -1,16 +1,5 @@
|
|||||||
mod arena;
|
|
||||||
mod borrow;
|
|
||||||
mod change;
|
|
||||||
mod id;
|
mod id;
|
||||||
mod math;
|
mod tuple;
|
||||||
mod refcount;
|
|
||||||
|
|
||||||
pub(crate) use arena::*;
|
pub use id::*;
|
||||||
pub(crate) use borrow::*;
|
pub use tuple::*;
|
||||||
pub use change::*;
|
|
||||||
pub(crate) use id::*;
|
|
||||||
pub(crate) use math::*;
|
|
||||||
pub(crate) use refcount::*;
|
|
||||||
|
|
||||||
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
|
|
||||||
pub type HashSet<K> = fxhash::FxHashSet<K>;
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
use std::sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::{AtomicU32, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct RefCounter(Arc<AtomicU32>);
|
|
||||||
|
|
||||||
impl RefCounter {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(Arc::new(0.into()))
|
|
||||||
}
|
|
||||||
pub fn refs(&self) -> u32 {
|
|
||||||
self.0.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
pub fn drop(&mut self) -> bool {
|
|
||||||
let refs = self.0.fetch_sub(1, Ordering::Release);
|
|
||||||
refs == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for RefCounter {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
self.0.fetch_add(1, Ordering::Release);
|
|
||||||
Self(self.0.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
70
src/util/tuple.rs
Normal file
70
src/util/tuple.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
pub trait TupleList {
|
||||||
|
const LEN: usize;
|
||||||
|
type Head;
|
||||||
|
type Tail;
|
||||||
|
fn into_head(self) -> Self::Head;
|
||||||
|
fn head(&self) -> &Self::Head;
|
||||||
|
fn split(self) -> (Self::Head, Self::Tail);
|
||||||
|
}
|
||||||
|
impl TupleList for () {
|
||||||
|
const LEN: usize = 0;
|
||||||
|
type Head = ();
|
||||||
|
type Tail = ();
|
||||||
|
fn head(&self) -> &Self::Head {
|
||||||
|
&()
|
||||||
|
}
|
||||||
|
fn into_head(self) -> Self::Head {}
|
||||||
|
fn split(self) -> (Self::Head, Self::Tail) {
|
||||||
|
((), ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T, Rest: TupleList> TupleList for (T, Rest) {
|
||||||
|
const LEN: usize = Rest::LEN + 1;
|
||||||
|
type Head = T;
|
||||||
|
type Tail = Rest;
|
||||||
|
fn head(&self) -> &Self::Head {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
fn into_head(self) -> Self::Head {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
fn split(self) -> (Self::Head, Self::Tail) {
|
||||||
|
(self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoTupleList {
|
||||||
|
type List;
|
||||||
|
fn into_list(self) -> Self::List;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoTupleList for () {
|
||||||
|
type List = ();
|
||||||
|
fn into_list(self) -> Self::List {}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_tuple {
|
||||||
|
($H:tt $($T:tt)*) => {
|
||||||
|
impl<$H, $($T,)*> IntoTupleList for ($H, $($T,)*) {
|
||||||
|
type List = ($H, <($($T,)*) as IntoTupleList>::List);
|
||||||
|
fn into_list(self) -> Self::List {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let ($H, $($T,)*) = self;
|
||||||
|
($H, ($($T,)*).into_list())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_tuple!(A);
|
||||||
|
impl_tuple!(A B);
|
||||||
|
impl_tuple!(A B C);
|
||||||
|
impl_tuple!(A B C D);
|
||||||
|
impl_tuple!(A B C D E);
|
||||||
|
impl_tuple!(A B C D E F);
|
||||||
|
impl_tuple!(A B C D E F G);
|
||||||
|
impl_tuple!(A B C D E F G H);
|
||||||
|
impl_tuple!(A B C D E F G H I);
|
||||||
|
impl_tuple!(A B C D E F G H I J);
|
||||||
|
impl_tuple!(A B C D E F G H I J K);
|
||||||
|
impl_tuple!(A B C D E F G H I J K L);
|
||||||
Reference in New Issue
Block a user