From 709a2d0e17b8bfe9048bb95d10adf5276172a122 Mon Sep 17 00:00:00 2001 From: Shadow Cat Date: Tue, 9 Sep 2025 21:53:32 -0400 Subject: [PATCH] preparation --- src/core/frame.rs | 15 +++-- src/core/image.rs | 2 +- src/core/sense.rs | 26 ++++--- src/core/text.rs | 30 +++++++-- src/core/trait_fns.rs | 58 +++++----------- src/layout/color.rs | 4 ++ src/layout/id.rs | 46 ++++++------- src/layout/painter.rs | 11 +-- src/layout/pos.rs | 12 ++-- src/layout/sense.rs | 40 ++++------- src/layout/text.rs | 42 +++++++++++- src/layout/ui.rs | 39 ++++++----- src/layout/widget.rs | 34 +++++++--- src/testing/mod.rs | 153 +++++++++++++++++++++++------------------- 14 files changed, 292 insertions(+), 220 deletions(-) diff --git a/src/core/frame.rs b/src/core/frame.rs index 4f555e9..0ef19fe 100644 --- a/src/core/frame.rs +++ b/src/core/frame.rs @@ -1,13 +1,20 @@ use crate::prelude::*; -pub struct Regioned { - pub region: UiRegion, +pub struct Padded { + pub padding: Padding, pub inner: WidgetId, } -impl Widget for Regioned { +impl Widget for Padded { fn draw(&mut self, painter: &mut Painter) { - painter.draw_within(&self.inner, self.region); + painter.draw_within(&self.inner, self.padding.region()); + } + + fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { + let mut size = ctx.size(&self.inner); + size.x += self.padding.left + self.padding.right; + size.y += self.padding.top + self.padding.bottom; + size } } diff --git a/src/core/image.rs b/src/core/image.rs index 2c4f5c3..5df0991 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -15,7 +15,7 @@ impl Widget for Image { } } -pub fn image(image: impl LoadableImage) -> WidgetFn!(Image) { +pub fn image(image: impl LoadableImage) -> impl WidgetFn { let image = image.get_image().expect("Failed to load image"); move |ui| Image { handle: ui.add_texture(image), diff --git a/src/core/sense.rs b/src/core/sense.rs index 2739101..462a0f0 100644 --- a/src/core/sense.rs +++ b/src/core/sense.rs @@ -1,21 +1,25 @@ use crate::prelude::*; pub trait Sensable { - fn on(self, sense: Senses, f: impl SenseFn) -> WidgetIdFn!(W); + fn on(self, sense: Senses, f: impl SenseFn) -> impl WidgetIdFn; fn id_on( self, senses: Senses, - f: impl FnMut(&WidgetId, &mut Ui) + 'static + Clone, - ) -> WidgetIdFn!(W) + f: impl FnMut(&WidgetId, &mut Ui, SenseCtx) + 'static, + ) -> impl WidgetIdFn where W: Widget; - fn edit_on(self, senses: Senses, f: impl FnMut(&mut W) + 'static + Clone) -> WidgetIdFn!(W) + fn edit_on( + self, + senses: Senses, + f: impl FnMut(&mut W, SenseCtx) + 'static, + ) -> impl WidgetIdFn where W: Widget; } impl, Tag> Sensable for W { - fn on(self, senses: Senses, f: impl SenseFn) -> WidgetIdFn!(W::Widget) { + fn on(self, senses: Senses, f: impl SenseFn) -> impl WidgetIdFn { move |ui| { let id = self.add(ui); ui.add_sensor( @@ -31,24 +35,24 @@ impl, Tag> Sensable for W { fn id_on( self, senses: Senses, - mut f: impl FnMut(&WidgetId, &mut Ui) + 'static + Clone, - ) -> WidgetIdFn!(W::Widget) + mut f: impl FnMut(&WidgetId, &mut Ui, SenseCtx) + 'static, + ) -> impl WidgetIdFn where W::Widget: Widget, { self.with_id(move |ui, id| { let id2 = id.clone(); - ui.add(id.on(senses, move |ui| f(&id2, ui))) + ui.add(id.on(senses, move |ui, pos| f(&id2, ui, pos))) }) } fn edit_on( self, senses: Senses, - mut f: impl FnMut(&mut W::Widget) + 'static + Clone, - ) -> WidgetIdFn!(W::Widget) + mut f: impl FnMut(&mut W::Widget, SenseCtx) + 'static, + ) -> impl WidgetIdFn where W::Widget: Widget, { - self.id_on(senses, move |id, ui| f(&mut ui[id])) + self.id_on(senses, move |id, ui, pos| f(&mut ui[id], pos)) } } diff --git a/src/core/text.rs b/src/core/text.rs index 04bcc2d..a93e5ee 100644 --- a/src/core/text.rs +++ b/src/core/text.rs @@ -5,7 +5,10 @@ use crate::prelude::*; pub struct Text { pub content: String, pub attrs: TextAttrs, + pub align: Align, buf: TextBuffer, + cursor: Cursor, + size: Vec2, } impl Text { @@ -32,16 +35,34 @@ impl Text { content: content.into(), buf: TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height)), attrs, + align: Align::Center, + cursor: Cursor::None, + size: Vec2::ZERO, } } + pub fn select(&mut self, pos: Vec2, size: Vec2) { + let pos = pos - self.region().top_left.to_size(size); + let Some(cursor) = self.buf.hit(pos.x, pos.y) else { + return; + }; + self.cursor = Cursor::Select { + line: cursor.line, + idx: cursor.index, + }; + } + + pub fn region(&self) -> UiRegion { + UiRegion::from_size_align(self.size, self.align) + } } impl Widget for Text { fn draw(&mut self, painter: &mut Painter) { - let (handle, offset) = painter.render_text(&mut self.buf, &self.content, &self.attrs); + let (handle, offset) = + painter.render_text(&mut self.buf, &self.content, &self.attrs, &self.cursor); let dims = handle.size(); - let size = offset.size(&handle); - let mut region = Align::Center.pos().expand(size); + self.size = offset.size(&handle); + let mut region = self.region(); region.top_left.offset += offset.top_left; region.bot_right.offset = region.top_left.offset + dims; painter.draw_texture_within(&handle, region); @@ -49,8 +70,7 @@ impl Widget for Text { fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { let (handle, offset) = - ctx.text - .draw(&mut self.buf, &self.content, &self.attrs, ctx.textures); + ctx.draw_text(&mut self.buf, &self.content, &self.attrs, &self.cursor); offset.size(&handle) } } diff --git a/src/core/trait_fns.rs b/src/core/trait_fns.rs index d1f30b0..40fd29d 100644 --- a/src/core/trait_fns.rs +++ b/src/core/trait_fns.rs @@ -2,41 +2,33 @@ use super::*; use crate::prelude::*; pub trait CoreWidget { - fn pad(self, padding: impl Into) -> WidgetFn!(Regioned); - fn align(self, align: Align) -> WidgetFn!(Aligned); - fn center(self) -> WidgetFn!(Aligned); - fn region(self, region: UiRegion) -> WidgetFn!(Regioned); - fn label(self, label: impl Into) -> WidgetIdFn!(W); - fn size(self, size: impl Into) -> WidgetFn!(Sized); + fn pad(self, padding: impl Into) -> impl WidgetFn; + fn align(self, align: Align) -> impl WidgetFn; + fn center(self) -> impl WidgetFn; + fn label(self, label: impl Into) -> impl WidgetIdFn; + fn size(self, size: impl Into) -> impl WidgetFn; } impl, Tag> CoreWidget for W { - fn pad(self, padding: impl Into) -> WidgetFn!(Regioned) { - |ui| Regioned { - region: padding.into().region(), + fn pad(self, padding: impl Into) -> impl WidgetFn { + |ui| Padded { + padding: padding.into(), inner: self.add(ui).erase_type(), } } - fn align(self, align: Align) -> WidgetFn!(Aligned) { + fn align(self, align: Align) -> impl WidgetFn { move |ui| Aligned { inner: self.add(ui).erase_type(), align, } } - fn center(self) -> WidgetFn!(Aligned) { + fn center(self) -> impl WidgetFn { self.align(Align::Center) } - fn region(self, region: UiRegion) -> WidgetFn!(Regioned) { - move |ui| Regioned { - region, - inner: self.add(ui).erase_type(), - } - } - - fn label(self, label: impl Into) -> WidgetIdFn!(W::Widget) { + fn label(self, label: impl Into) -> impl WidgetIdFn { |ui| { let id = self.add(ui); ui.set_label(&id, label.into()); @@ -44,7 +36,7 @@ impl, Tag> CoreWidget for W { } } - fn size(self, size: impl Into) -> WidgetFn!(Sized) { + fn size(self, size: impl Into) -> impl WidgetFn { move |ui| Sized { inner: self.add(ui).erase_type(), size: size.into(), @@ -53,43 +45,25 @@ impl, Tag> CoreWidget for W { } pub trait CoreWidgetArr { - fn span(self, dir: Dir, lengths: impl IntoSpanLens) -> WidgetFn!(Span); - fn stack(self) -> WidgetFn!(Stack); + fn span(self, dir: Dir, lengths: impl IntoSpanLens) -> impl WidgetFn; + fn stack(self) -> impl WidgetFn; } impl, Tag> CoreWidgetArr for Wa { - fn span(self, dir: Dir, lengths: impl IntoSpanLens) -> WidgetFn!(Span) { + fn span(self, dir: Dir, lengths: impl IntoSpanLens) -> impl WidgetFn { let lengths = lengths.into_lens(); move |ui| Span { children: self.ui(ui).arr.into_iter().zip(lengths).collect(), dir, } } - fn stack(self) -> WidgetFn!(Stack) { + fn stack(self) -> impl WidgetFn { move |ui| Stack { children: self.ui(ui).arr.to_vec(), } } } -// pub struct SpanBuilder> { -// children: Wa, -// dir: Dir, -// align: Align, -// } -// -// impl WidgetLike for SpanBuilder { -// type Widget = Span; -// -// fn add(self, ui: &mut Ui) -> WidgetId { -// ui.add_widget(Span { -// children: self.children, -// dir: self.dir, -// align: self.align, -// }) -// } -// } - pub trait IntoSpanLens { fn into_lens(self) -> [SpanLen; LEN]; } diff --git a/src/layout/color.rs b/src/layout/color.rs index 215348d..4d0de1a 100644 --- a/src/layout/color.rs +++ b/src/layout/color.rs @@ -35,6 +35,10 @@ impl Color { self.a = a; self } + + pub fn as_arr(self) -> [T; 4] { + [self.r, self.g, self.b, self.a] + } } pub trait ColorNum { diff --git a/src/layout/id.rs b/src/layout/id.rs index f58635e..0bc779c 100644 --- a/src/layout/id.rs +++ b/src/layout/id.rs @@ -1,4 +1,12 @@ -use std::{any::TypeId, marker::PhantomData, sync::mpsc::Sender}; +use std::{ + any::TypeId, + marker::PhantomData, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + mpsc::Sender, + }, +}; use crate::{ layout::{FnTag, Ui, Widget, WidgetLike, WidgetTag}, @@ -21,7 +29,7 @@ pub struct WidgetId { pub(super) id: Id, counter: RefCounter, send: Sender, - is_static: bool, + is_static: Arc, _pd: PhantomData, } @@ -52,7 +60,7 @@ impl Clone for WidgetId { ty: self.ty, counter: self.counter.clone(), send: self.send.clone(), - is_static: self.is_static, + is_static: self.is_static.clone(), _pd: PhantomData, } } @@ -65,7 +73,7 @@ impl WidgetId { id, counter: RefCounter::new(), send, - is_static, + is_static: Arc::new(is_static.into()), _pd: PhantomData, } } @@ -88,11 +96,11 @@ impl WidgetId { self.counter.refs() } - pub(super) fn into_static(self) -> StaticWidgetId { - let s = std::mem::ManuallyDrop::new(self); + pub fn into_static(self) -> StaticWidgetId { + self.is_static.store(true, Ordering::Release); StaticWidgetId { - ty: s.ty, - id: s.id.copyable(), + ty: self.ty, + id: self.id.copyable(), _pd: PhantomData, } } @@ -110,7 +118,7 @@ impl WidgetId { impl Drop for WidgetId { fn drop(&mut self) { - if self.counter.drop() && !self.is_static { + if self.counter.drop() && !self.is_static.load(Ordering::Acquire) { let _ = self.send.send(self.id.duplicate()); } } @@ -119,21 +127,14 @@ impl Drop for WidgetId { pub struct IdTag; pub struct IdFnTag; -// pub trait WidgetIdFn = FnOnce(&mut Ui) -> WidgetId; -macro_rules! WidgetIdFn { - ($W:ty) => { - impl FnOnce(&mut $crate::layout::Ui) -> $crate::layout::WidgetId<$W> - }; - ($W:ty, $($use:tt)*) => { - impl FnOnce(&mut $crate::layout::Ui) -> $crate::layout::WidgetId<$W> + use<$($use)*> - }; -} -pub(crate) use WidgetIdFn; +pub trait WidgetIdFn: FnOnce(&mut Ui) -> WidgetId {} +impl WidgetId> WidgetIdFn for F {} +/// TODO: does this ever make sense to use? it allows for invalid ids pub trait Idable { type Widget: Widget; fn set(self, ui: &mut Ui, id: &WidgetId); - fn id<'a>(self, id: &WidgetId) -> WidgetIdFn!(Self::Widget, 'a, Self, Tag) + fn id(self, id: &WidgetId) -> impl WidgetIdFn where Self: Sized, { @@ -143,10 +144,7 @@ pub trait Idable { id } } - fn id_static<'a>( - self, - id: StaticWidgetId, - ) -> WidgetIdFn!(Self::Widget, 'a, Self, Tag) + fn id_static(self, id: StaticWidgetId) -> impl WidgetIdFn where Self: Sized, { diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 70d64d6..78b2d77 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -2,8 +2,8 @@ use std::ops::Range; use crate::{ layout::{ - Active, SensorMap, SizeCtx, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, - Textures, UiRegion, Vec2, WidgetId, Widgets, + Active, Cursor, SensorMap, SizeCtx, TextAttrs, TextBuffer, TextData, TextOffset, + TextureHandle, Textures, UiRegion, Vec2, WidgetId, Widgets, }, render::{Primitive, PrimitiveHandle, Primitives}, util::{HashSet, Id}, @@ -251,14 +251,15 @@ impl<'a, 'c> Painter<'a, 'c> { buffer: &mut TextBuffer, content: &str, attrs: &TextAttrs, + cursor: &Cursor, ) -> (TextureHandle, TextOffset) { self.ctx .text - .draw(buffer, content, attrs, self.ctx.textures) + .draw(buffer, content, attrs, cursor, self.ctx.textures) } - pub fn region_size(&self) -> Vec2 { - self.region.in_size(self.ctx.screen_size) + pub fn region(&self) -> UiRegion { + self.region } pub fn size(&mut self, id: &WidgetId) -> Vec2 { diff --git a/src/layout/pos.rs b/src/layout/pos.rs index 35987b4..a3a676f 100644 --- a/src/layout/pos.rs +++ b/src/layout/pos.rs @@ -181,8 +181,8 @@ impl UiRegion { self } - pub fn to_screen(&self, size: Vec2) -> ScreenRect { - ScreenRect { + pub fn to_screen(&self, size: Vec2) -> ScreenRegion { + ScreenRegion { top_left: self.top_left.anchor * size + self.top_left.offset, bot_right: self.bot_right.anchor * size + self.bot_right.offset, } @@ -213,11 +213,11 @@ impl UiRegion { } #[derive(Debug)] -pub struct ScreenRect { - top_left: Vec2, - bot_right: Vec2, +pub struct ScreenRegion { + pub top_left: Vec2, + pub bot_right: Vec2, } -impl ScreenRect { +impl ScreenRegion { pub fn contains(&self, pos: Vec2) -> bool { pos.x >= self.top_left.x && pos.x <= self.bot_right.x diff --git a/src/layout/sense.rs b/src/layout/sense.rs index 272cee2..5fbe3a3 100644 --- a/src/layout/sense.rs +++ b/src/layout/sense.rs @@ -1,3 +1,5 @@ +use std::any::Any; + use crate::{ layout::{Ui, UiRegion, Vec2, WidgetId}, util::{HashMap, Id, bitflags}, @@ -42,15 +44,15 @@ pub struct SensorGroup { pub cursor: ActivationState, pub sensors: Vec, } -pub trait SenseFn: FnMut(&mut Ui) + 'static { - fn box_clone(&self) -> Box; -} -impl SenseFn for F { - fn box_clone(&self) -> Box { - Box::new(self.clone()) - } + +pub struct SenseCtx { + pub cursor: Vec2, + pub size: Vec2, } +pub trait SenseFn: FnMut(&mut Ui, SenseCtx) + 'static {} +impl SenseFn for F {} + impl Ui { pub fn add_sensor(&mut self, id: &WidgetId, f: Sensor) { self.sensor_map @@ -72,7 +74,11 @@ impl Ui { for sensor in &mut group.sensors { if should_run(sensor.senses, group.cursor, group.hover) { - (sensor.f.box_clone())(self); + let ctx = SenseCtx { + cursor: cursor.pos - region.top_left, + size: region.bot_right - region.top_left, + }; + (sensor.f)(self, ctx); } } } @@ -132,21 +138,3 @@ impl ActivationState { } } } - -impl Clone for SensorGroup { - fn clone(&self) -> Self { - Self { - hover: self.hover, - cursor: self.cursor, - sensors: self.sensors.clone(), - } - } -} -impl Clone for Sensor { - fn clone(&self) -> Self { - Self { - senses: self.senses, - f: self.f.box_clone(), - } - } -} diff --git a/src/layout/text.rs b/src/layout/text.rs index f14db96..a9d2b01 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -28,6 +28,16 @@ pub struct TextAttrs { pub family: Family<'static>, } +#[derive(Default)] +pub enum Cursor { + #[default] + None, + Select { + line: usize, + idx: usize, + }, +} + pub type TextBuffer = Buffer; impl Default for TextAttrs { @@ -48,6 +58,7 @@ impl TextData { buffer: &mut TextBuffer, content: &str, attrs: &TextAttrs, + cursor: &Cursor, textures: &mut Textures, ) -> (TextureHandle, TextOffset) { buffer.set_metrics( @@ -67,7 +78,14 @@ impl TextData { let mut max_y = 0; let c = attrs.color; let mut max_width = 0.0f32; - for run in buffer.layout_runs() { + let mut i = 0; + let mut cursor_x = 0; + for (run_i, run) in buffer.layout_runs().enumerate() { + if let Cursor::Select { line, .. } = cursor + && *line == run_i + { + cursor_x = run.line_w as i32; + } for glyph in run.glyphs.iter() { let physical_glyph = glyph.physical((0., 0.), 1.0); @@ -76,6 +94,14 @@ impl TextData { None => cosmic_text::Color::rgba(c.r, c.g, c.b, c.a), }; + if let Cursor::Select { idx, line } = cursor + && *line == run_i + && *idx == i + { + cursor_x = physical_glyph.x; + } + i += 1; + self.swash_cache.with_pixels( &mut self.font_system, physical_glyph.cache_key, @@ -93,6 +119,11 @@ impl TextData { } max_width = max_width.max(run.line_w); } + if let &Cursor::Select { line, .. } = cursor { + let y = (attrs.line_height * (line + 1) as f32) as i32 - 1; + max_y = max_y.max(y); + max_x = max_x.max(cursor_x); + } let width = (max_x - min_x + 1) as u32; let height = (max_y - min_y + 1) as u32; let mut image = RgbaImage::new(width, height); @@ -101,6 +132,15 @@ impl TextData { let y = (y - min_y) as u32; image.put_pixel(x, y, color); } + if let &Cursor::Select { line, .. } = cursor { + for y in 0..attrs.line_height as u32 { + // no clue if this is good or bad for non integer values + // depends on how the layouting is actually done + let x = (cursor_x - min_x) as u32; + let y = (y as f32 + attrs.line_height * line as f32 - min_y as f32) as u32; + image.put_pixel(x, y, Rgba(c.as_arr())); + } + } let offset = TextOffset { top_left: Vec2::new(min_x as f32, min_y as f32), bot_right: Vec2::new( diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 45db3a8..d6d0ece 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -15,9 +15,8 @@ use std::{ }; pub struct Ui { - base: Option, + root: Option, widgets: Widgets, - labels: HashMap, updates: Vec, recv: Receiver, pub(super) send: Sender, @@ -41,6 +40,7 @@ pub struct Widgets { pub struct WidgetData { widget: Box, borrowed: bool, + label: String, } impl Ui { @@ -58,7 +58,7 @@ impl Ui { /// useful for debugging pub fn set_label(&mut self, id: &WidgetId, label: String) { - self.labels.insert(id.id.duplicate(), label); + *self.widgets.label_mut(id).unwrap() = label; } pub fn add_widget(&mut self, w: W) -> WidgetId { @@ -67,8 +67,6 @@ impl Ui { pub fn push(&mut self, w: W) -> WidgetId { let id = self.id(); - self.labels - .insert(id.id.duplicate(), std::any::type_name::().to_string()); self.widgets.insert(id.id.duplicate(), w); id } @@ -77,8 +75,8 @@ impl Ui { self.widgets.insert(id.id.duplicate(), w); } - pub fn set_base(&mut self, w: impl WidgetLike) { - self.base = Some(w.add(self).erase_type()); + pub fn set_root(&mut self, w: impl WidgetLike) { + self.root = Some(w.add(self).erase_type()); self.full_redraw = true; } @@ -129,8 +127,8 @@ impl Ui { &mut self.text, self.size, ); - if let Some(base) = &self.base { - ctx.draw(&base.id); + if let Some(root) = &self.root { + ctx.draw(&root.id); } } @@ -162,7 +160,6 @@ impl Ui { /// free any resources that don't have references anymore fn free(&mut self) { for id in self.recv.try_iter() { - self.labels.remove(&id); self.widgets.delete(id); } self.textures.free(); @@ -175,6 +172,10 @@ impl Ui { pub fn num_widgets(&self) -> usize { self.widgets.len() } + + pub fn active_widgets(&self) -> usize { + self.active.widgets.len() + } } impl Index<&WidgetId> for Ui { @@ -254,16 +255,25 @@ impl Widgets { self.get_dyn_mut(&id.id)?.as_any_mut().downcast_mut() } - pub fn insert(&mut self, id: Id, widget: impl Widget) { - self.insert_any(id, Box::new(widget)); + pub fn insert(&mut self, id: Id, widget: W) { + self.insert_any(id, Box::new(widget), std::any::type_name::().to_string()); } - pub fn insert_any(&mut self, id: Id, widget: Box) { + pub fn label(&self, id: &WidgetId) -> Option<&String> { + self.map.get(&id.id).map(|d| &d.label) + } + + pub fn label_mut(&mut self, id: &WidgetId) -> Option<&mut String> { + self.map.get_mut(&id.id).map(|d| &mut d.label) + } + + pub fn insert_any(&mut self, id: Id, widget: Box, label: String) { self.map.insert( id, WidgetData { widget, borrowed: false, + label, }, ); } @@ -331,9 +341,8 @@ impl Default for Ui { fn default() -> Self { let (send, recv) = channel(); Self { - base: Default::default(), + root: Default::default(), widgets: Widgets::new(), - labels: Default::default(), updates: Default::default(), primitives: Default::default(), textures: Textures::new(), diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 714bbb9..6131021 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -1,4 +1,7 @@ -use crate::layout::{Painter, TextData, Textures, Ui, Vec2, WidgetId, WidgetIdFn, Widgets}; +use crate::layout::{ + Cursor, Painter, StaticWidgetId, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, + Textures, Ui, Vec2, WidgetId, WidgetIdFn, Widgets, +}; use std::{any::Any, marker::PhantomData}; @@ -20,6 +23,16 @@ impl SizeCtx<'_> { pub fn size(&mut self, id: &WidgetId) -> Vec2 { self.widgets.get_dyn_dynamic(&id.id).get_size(self) } + pub fn draw_text( + &mut self, + buffer: &mut TextBuffer, + content: &str, + attrs: &TextAttrs, + cursor: &Cursor, + ) -> (TextureHandle, TextOffset) { + self.text + .draw(buffer, content, attrs, cursor, self.textures) + } } pub struct WidgetTag; @@ -31,7 +44,7 @@ pub trait WidgetLike { fn with_id( self, f: impl FnOnce(&mut Ui, WidgetId) -> WidgetId, - ) -> WidgetIdFn!(W2) + ) -> impl WidgetIdFn where Self: Sized, { @@ -40,20 +53,19 @@ pub trait WidgetLike { f(ui, id) } } + fn add_static(self, ui: &mut Ui) -> StaticWidgetId + where + Self: Sized, + { + self.add(ui).into_static() + } } -// pub trait WidgetFn, Ctx> = FnOnce(&mut Ui) -> W; - /// A function that returns a widget given a UI. /// Useful for defining trait functions on widgets that create a parent widget so that the children /// don't need to be IDs yet -/// currently a macro for rust analyzer (doesn't support trait aliases atm) -macro_rules! WidgetFn { - ($W:ty) => { - impl FnOnce(&mut $crate::layout::Ui) -> $W - }; -} -pub(crate) use WidgetFn; +pub trait WidgetFn: FnOnce(&mut Ui) -> W {} +impl W> WidgetFn for F {} impl W> WidgetLike for F { type Widget = W; diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 2b2c255..6cb6b1f 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -22,6 +22,7 @@ pub struct Client { input: Input, ui: Ui, info: WidgetId, + selected: Option>, } impl Client { @@ -30,58 +31,58 @@ impl Client { let mut ui = Ui::new(); let rrect = rect(Color::WHITE).radius(20); - let pad_test = ui.add_static( + let pad_test = ( + rrect.color(Color::BLUE), ( - rrect.color(Color::BLUE), + rrect.color(Color::RED).size(100).center(), ( - rrect.color(Color::RED).size(100).center(), - ( - rrect.color(Color::ORANGE), - rrect.color(Color::LIME).pad(10.0), - ) - .span(Dir::RIGHT, [1, 1]), - rrect.color(Color::YELLOW), + rrect.color(Color::ORANGE), + rrect.color(Color::LIME).pad(10.0), ) - .span(Dir::RIGHT, [2, 2, 1]) - .pad(10), + .span(Dir::RIGHT, [1, 1]), + rrect.color(Color::YELLOW), ) - .span(Dir::RIGHT, [1, 3]), - ); - let span_test = ui.add_static( - ( - rrect.color(Color::GREEN), - rrect.color(Color::ORANGE), - rrect.color(Color::CYAN), - rrect.color(Color::BLUE), - rrect.color(Color::MAGENTA), - rrect.color(Color::RED), - ) - .span( - Dir::LEFT, - [ - fixed(100), - ratio(1), - ratio(1), - relative(0.5), - fixed(100), - fixed(100), - ], - ), - ); - let span_add = ui.add_static(Span::empty(Dir::RIGHT)); + .span(Dir::RIGHT, [2, 2, 1]) + .pad(10), + ) + .span(Dir::RIGHT, [1, 3]) + .add_static(&mut ui); - let main = ui.add_static(pad_test.pad(10)); + let span_test = ( + rrect.color(Color::GREEN), + rrect.color(Color::ORANGE), + rrect.color(Color::CYAN), + rrect.color(Color::BLUE), + rrect.color(Color::MAGENTA), + rrect.color(Color::RED), + ) + .span( + Dir::LEFT, + [ + fixed(100), + ratio(1), + ratio(1), + relative(0.5), + fixed(100), + fixed(100), + ], + ) + .add_static(&mut ui); + + let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui); + + let main = pad_test.pad(10).add_static(&mut ui); let switch_button = |color, to, label| { let rect = rect(color) - .id_on(PRESS_START, move |id, ui| { + .id_on(PRESS_START, move |id, ui, _| { ui[main].inner.set_static(to); ui[id].color = color.add_rgb(-0.2); }) - .edit_on(HOVER_START | PRESS_END, move |r| { + .edit_on(HOVER_START | PRESS_END, move |r, _| { r.color = color.add_rgb(0.4); }) - .edit_on(HOVER_END, move |r| { + .edit_on(HOVER_END, move |r, _| { r.color = color; }); (rect, text(label).font_size(30)).stack() @@ -89,38 +90,52 @@ impl Client { let btext = |content| text(content).font_size(30); - let text_test = ui.add_static( + 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("this is a").align(Align::Left), - btext("teeeeeeeest").align(Align::Left), - btext("okkk\nokkkkkk!").align(Align::Left), - btext("hmm"), - btext("a"), - ( - btext("'").family(Family::Monospace).align(Align::Top), - btext("'").family(Family::Monospace), - btext(":gamer mode").family(Family::Monospace), - rect(Color::CYAN).size(10).center(), - rect(Color::RED).size(100).center(), - ) - .span(Dir::RIGHT, sized()) - .center(), - text("pretty cool right?").font_size(50), + btext("'").family(Family::Monospace).align(Align::Top), + btext("'").family(Family::Monospace), + btext(":gamer mode").family(Family::Monospace), + rect(Color::CYAN).size(10).center(), + rect(Color::RED).size(100).center(), ) - .span(Dir::DOWN, sized()), - ); + .span(Dir::RIGHT, sized()) + .center(), + text("pretty cool right?").font_size(50), + ) + .span(Dir::DOWN, sized()) + .add_static(&mut ui); + + let texts = Span::empty(Dir::DOWN).add(&mut ui); + let text_edit_scroll = ( + texts, + text("add").font_size(30).edit_on(PRESS_START, |text, ctx| { + text.select(ctx.cursor, ctx.size); + }), + ) + .span(Dir::DOWN, [ratio(1), fixed(40)]) + .add_static(&mut ui); let tabs = ( - switch_button(Color::RED, pad_test, "pad test"), - switch_button(Color::GREEN, span_test, "span test"), - switch_button(Color::BLUE, span_add, "span add test"), - switch_button(Color::MAGENTA, text_test, "text test"), + switch_button(Color::RED, pad_test, "pad"), + switch_button(Color::GREEN, span_test, "span"), + switch_button(Color::BLUE, span_add, "image span"), + switch_button(Color::MAGENTA, text_test, "text layout"), + switch_button( + Color::YELLOW.mul_rgb(0.5), + text_edit_scroll, + "text edit scroll", + ), ) .span(Dir::RIGHT, ratio(1)); let add_button = rect(Color::LIME) .radius(30) - .on(PRESS_START, move |ui| { + .on(PRESS_START, move |ui, _| { let child = ui .add(image(include_bytes!("assets/sungals.png")).center()) .erase_type(); @@ -131,17 +146,15 @@ impl Client { let del_button = rect(Color::RED) .radius(30) - .on(PRESS_START, move |ui| { + .on(PRESS_START, move |ui, _| { ui[span_add].children.pop(); }) .size(150) .align(Align::BotLeft); - let info = ui.add(text("")); - let info_sect = info - .clone() - .region(Align::TopRight.pos().expand((150, 150)).shifted((-75, 0))); - ui.set_base( + let info = text("").add(&mut ui); + let info_sect = info.clone().pad(10).align(Align::BotLeft); + ui.set_root( ( tabs.label("tabs"), ( @@ -162,6 +175,7 @@ impl Client { input: Input::default(), ui, info, + selected: None, } } @@ -184,8 +198,9 @@ impl Client { _ => (), } let new = format!( - "widgets: {}\nviews: {}", + "widgets: {}\nactive:{}\nviews: {}", self.ui.num_widgets(), + self.ui.active_widgets(), self.renderer.ui.view_count() ); if new != self.ui[&self.info].content {