diff --git a/src/core/frame.rs b/src/core/frame.rs index cf91df4..4f555e9 100644 --- a/src/core/frame.rs +++ b/src/core/frame.rs @@ -6,7 +6,7 @@ pub struct Regioned { } impl Widget for Regioned { - fn draw(&self, painter: &mut Painter) { + fn draw(&mut self, painter: &mut Painter) { painter.draw_within(&self.inner, self.region); } } diff --git a/src/core/image.rs b/src/core/image.rs index b14a4aa..7d5950f 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -2,34 +2,23 @@ use crate::prelude::*; use image::DynamicImage; pub struct Image { - handle: Option, + handle: TextureHandle, } impl Widget for Image { - fn draw(&self, painter: &mut Painter) { - if let Some(handle) = &self.handle { - painter.draw_texture(handle); - } else { - painter.write(RectPrimitive { - color: Color::MAGENTA, - inner_radius: 0.0, - radius: 0.0, - thickness: 0.0, - }); - } + fn draw(&mut self, painter: &mut Painter) { + painter.draw_texture(&self.handle); + } + + fn size(&mut self, _: SizeCtx) -> Vec2 { + self.handle.size() } } pub fn image(image: impl LoadableImage) -> WidgetFnRet!(Image) { - let image = match image.get_image() { - Ok(image) => Some(image), - Err(e) => { - println!("Failed to load image: {e}"); - None - } - }; + let image = image.get_image().expect("Failed to load image"); move |ui| Image { - handle: image.map(|image| ui.add_texture(image)), + handle: ui.add_texture(image), } } diff --git a/src/core/rect.rs b/src/core/rect.rs index 3c8b769..cca6c2b 100644 --- a/src/core/rect.rs +++ b/src/core/rect.rs @@ -28,7 +28,7 @@ impl Rect { } impl Widget for Rect { - fn draw(&self, painter: &mut Painter) { + fn draw(&mut self, painter: &mut Painter) { painter.write(RectPrimitive { color: self.color, radius: self.radius, diff --git a/src/core/span.rs b/src/core/span.rs index ae85a57..acf56c3 100644 --- a/src/core/span.rs +++ b/src/core/span.rs @@ -6,8 +6,8 @@ pub struct Span { } impl Widget for Span { - fn draw(&self, painter: &mut Painter) { - let total = self.sums(); + fn draw(&mut self, painter: &mut Painter) { + let total = self.setup(painter); let mut start = UIScalar::min(); for (child, length) in &self.children { let mut child_region = UiRegion::full(); @@ -29,6 +29,14 @@ impl Widget for Span { start.anchor += rel; axis.bot_right.set(start); } + SpanLen::Sized(size) => { + let offset = size.axis(self.dir.axis); + start.offset += offset; + *axis.bot_right.offset = start.offset; + child_region.bot_right.anchor = child_region.top_left.anchor; + let opposite = !self.dir.axis; + *child_region.bot_right.offset.axis_mut(opposite) += size.axis(opposite); + } } if self.dir.sign == Sign::Neg { child_region.flip(); @@ -53,18 +61,23 @@ impl Span { } } - pub fn sums(&self) -> SpanLenSums { - self.lengths().fold(SpanLenSums::default(), |mut s, l| { - match l { - SpanLen::Fixed(v) => s.fixed += v, - SpanLen::Ratio(v) => s.ratio += v, - SpanLen::Relative(v) => s.relative += v, - } - s - }) - } - pub fn lengths(&self) -> impl ExactSizeIterator { - self.children.iter().map(|(_, s)| s) + fn setup(&mut self, painter: &mut Painter) -> SpanLenSums { + self.children + .iter_mut() + .fold(SpanLenSums::default(), |mut s, (id, l)| { + match l { + SpanLen::Fixed(v) => s.fixed += *v, + SpanLen::Ratio(v) => s.ratio += *v, + SpanLen::Relative(v) => s.relative += *v, + SpanLen::Sized(v) => { + let size = painter.size(id); + let len = size.axis(self.dir.axis); + *v = size; + s.fixed += len; + } + } + s + }) } } @@ -78,6 +91,10 @@ pub enum SpanLen { /// relative to the total space (of the entire span) /// eg. 0.5 means 1/2 of the total space Relative(f32), + /// size determined by the child widget itself + /// the value is not used externally, I just don't wanna make a duplicate enum + /// there are util functions instead so + Sized(Vec2), } pub fn fixed(x: N) -> SpanLen { @@ -92,6 +109,10 @@ pub fn relative(x: N) -> SpanLen { SpanLen::Relative(x.to_f32()) } +pub fn sized() -> SpanLen { + SpanLen::Sized(Vec2::ZERO) +} + impl From for SpanLen { fn from(value: N) -> Self { Self::Ratio(value.to_f32()) diff --git a/src/core/stack.rs b/src/core/stack.rs index dcdbab6..b4634eb 100644 --- a/src/core/stack.rs +++ b/src/core/stack.rs @@ -5,7 +5,7 @@ pub struct Stack { } impl Widget for Stack { - fn draw(&self, painter: &mut Painter) { + fn draw(&mut self, painter: &mut Painter) { for child in &self.children { painter.draw(child); } diff --git a/src/core/text.rs b/src/core/text.rs index 09a6ec3..349502d 100644 --- a/src/core/text.rs +++ b/src/core/text.rs @@ -1,13 +1,17 @@ +use cosmic_text::Metrics; + use crate::prelude::*; pub struct Text { pub content: String, pub attrs: TextAttrs, + buf: TextBuffer, } impl Text { pub fn size(mut self, size: impl UiNum) -> Self { self.attrs.size = size.to_f32(); + self.attrs.line_height = self.attrs.size * 1.1; self } pub fn color(mut self, color: UiColor) -> Self { @@ -21,16 +25,31 @@ impl Text { } impl Widget for Text { - fn draw(&self, painter: &mut Painter) { + fn draw(&mut self, painter: &mut Painter) { + let (handle, offset) = painter.render_text(&mut self.buf, &self.content, &self.attrs); + let dims = handle.size(); + let size = offset.size(&handle); + let mut region = painter.region().center().expand(size); + region.top_left.offset += offset.top_left; + region.bot_right.offset = region.top_left.offset + dims; + painter.draw_texture_at(&handle, region); // TODO: when on_update is added to painter, - // return & store TextureHandle to reuse - painter.draw_text(&self.content, &self.attrs); + // reuse TextureHandle + } + + fn size(&mut self, ctx: SizeCtx) -> Vec2 { + let (handle, offset) = + ctx.text + .draw(&mut self.buf, &self.content, &self.attrs, ctx.textures); + offset.size(&handle) } } pub fn text(text: impl Into) -> Text { + let attrs = TextAttrs::default(); Text { content: text.into(), - attrs: TextAttrs::default(), + buf: TextBuffer::new_empty(Metrics::new(attrs.size, attrs.line_height)), + attrs, } } diff --git a/src/layout/orientation.rs b/src/layout/orientation.rs index 66b7244..c012ac9 100644 --- a/src/layout/orientation.rs +++ b/src/layout/orientation.rs @@ -1,3 +1,5 @@ +use std::ops::Not; + use crate::layout::{Vec2, vec2}; #[derive(Clone, Copy, Debug)] @@ -25,6 +27,17 @@ pub enum Axis { 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, @@ -47,3 +60,19 @@ 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, + } + } +} diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 82839db..4dafe03 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,9 +1,7 @@ -use image::GenericImageView; - use crate::{ layout::{ - Active, SensorMap, TextAttrs, TextData, TextureHandle, Textures, UiRegion, Vec2, WidgetId, - WidgetInstance, Widgets, + Active, SensorMap, SizeCtx, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, + Textures, UiRegion, Vec2, WidgetId, WidgetInstance, Widgets, }, render::{Primitive, PrimitiveHandle, Primitives}, util::Id, @@ -33,7 +31,7 @@ pub struct Painter<'a> { impl<'a> Painter<'a> { #[allow(clippy::too_many_arguments)] pub(super) fn new( - nodes: &'a Widgets, + widgets: &'a Widgets, primitives: &'a mut Primitives, sensors_map: &'a SensorMap, active: &'a mut Active, @@ -42,7 +40,7 @@ impl<'a> Painter<'a> { screen_size: Vec2, ) -> Self { Self { - widgets: nodes, + widgets, active, sensors_map, primitives, @@ -59,9 +57,14 @@ impl<'a> Painter<'a> { } } + /// Writes a primitive to be rendered + pub fn write_at(&mut self, data: P, region: UiRegion) -> PrimitiveHandle

{ + self.primitives.write(data, region) + } + /// Writes a primitive to be rendered pub fn write(&mut self, data: P) -> PrimitiveHandle

{ - self.primitives.write(data, self.state.region) + self.write_at(data, self.state.region) } /// Draws a widget within this widget's region. @@ -81,9 +84,6 @@ impl<'a> Painter<'a> { } fn draw_raw_at(&mut self, id: &Id, region: UiRegion) { - if self.active.widgets.contains_key(id) { - panic!("widget drawn twice!"); - } self.state.children.push(id.duplicate()); // &mut self is passed to avoid copying all of the "static" pointers in self @@ -100,7 +100,7 @@ impl<'a> Painter<'a> { // draw widgets let start_i = self.primitives.cur_pos(); - self.widgets.get_dyn(id).draw(self); + self.widgets.get_dyn_dynamic(id).draw(self); let end_i = self.primitives.cur_pos(); // restore state @@ -122,7 +122,7 @@ impl<'a> Painter<'a> { pub fn draw_texture(&mut self, handle: &TextureHandle) { self.state.textures.push(handle.clone()); - self.write(handle.inner); + self.write(handle.primitive()); } pub fn draw_texture_at(&mut self, handle: &TextureHandle, region: UiRegion) { @@ -132,11 +132,14 @@ impl<'a> Painter<'a> { self.state.region = old; } - pub fn draw_text(&mut self, content: &str, attrs: &TextAttrs) { - let handle = self.text.draw(content, attrs, self.textures); - let dims: Vec2 = self.textures[&handle].dimensions().into(); - let region = self.state.region.center().expand(dims); - self.draw_texture_at(&handle, region); + /// returns (handle, offset from top left) + pub fn render_text( + &mut self, + buffer: &mut TextBuffer, + content: &str, + attrs: &TextAttrs, + ) -> (TextureHandle, TextOffset) { + self.text.draw(buffer, content, attrs, self.textures) } pub fn region(&self) -> UiRegion { @@ -147,6 +150,14 @@ impl<'a> Painter<'a> { self.state.region.in_size(self.screen_size) } + pub fn size(&mut self, id: &WidgetId) -> Vec2 { + self.widgets.get_dyn_dynamic(&id.id).size(SizeCtx { + size: self.region().in_size(self.screen_size), + text: self.text, + textures: self.textures, + }) + } + pub(crate) fn redraw(&mut self, id: &Id) { if !self.active.widgets.contains_key(id) { return; diff --git a/src/layout/text.rs b/src/layout/text.rs index a9678cb..a3500fd 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -2,7 +2,7 @@ use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, SwashCache}; use image::{Rgba, RgbaImage}; use crate::{ - layout::{TextureHandle, Textures, UiColor}, + layout::{TextureHandle, Textures, UiColor, Vec2}, util::HashMap, }; @@ -20,12 +20,15 @@ impl Default for TextData { } } +#[derive(Clone, Copy)] pub struct TextAttrs { pub color: UiColor, pub size: f32, pub line_height: f32, } +pub type TextBuffer = Buffer; + impl Default for TextAttrs { fn default() -> Self { let size = 14.0; @@ -38,35 +41,57 @@ impl Default for TextAttrs { } impl TextData { + /// returns (handle, offset from top left) pub fn draw( &mut self, + buffer: &mut TextBuffer, content: &str, attrs: &TextAttrs, textures: &mut Textures, - ) -> TextureHandle { - let metrics = Metrics::new(attrs.size, attrs.line_height); - let mut buffer = Buffer::new(&mut self.font_system, metrics); - let mut buffer = buffer.borrow_with(&mut self.font_system); - buffer.set_text(content, &Attrs::new(), Shaping::Advanced); - - // dawg what is this api ??? + ) -> (TextureHandle, TextOffset) { + buffer.set_metrics( + &mut self.font_system, + Metrics::new(attrs.size, attrs.line_height), + ); + buffer.set_text( + &mut self.font_system, + content, + &Attrs::new(), + Shaping::Advanced, + ); let mut pixels = HashMap::new(); let mut min_x = 0; let mut min_y = 0; let mut max_x = 0; let mut max_y = 0; let c = attrs.color; - buffer.draw( - &mut self.swash_cache, - cosmic_text::Color::rgba(c.r, c.g, c.b, c.a), - |x, y, _, _, color| { - min_x = min_x.min(x); - min_y = min_y.min(y); - max_x = max_x.max(x); - max_y = max_y.max(y); - pixels.insert((x, y), Rgba(color.as_rgba())); - }, - ); + let mut max_width = 0.0f32; + 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_text::Color::rgba(c.r, c.g, c.b, c.a), + }; + + self.swash_cache.with_pixels( + &mut self.font_system, + physical_glyph.cache_key, + glyph_color, + |x, y, color| { + let x = physical_glyph.x + x; + let y = run.line_y as i32 + physical_glyph.y + y; + min_x = min_x.min(x); + min_y = min_y.min(y); + max_x = max_x.max(x); + max_y = max_y.max(y); + pixels.insert((x, y), Rgba(color.as_rgba())); + }, + ); + } + max_width = max_width.max(run.line_w); + } 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); @@ -75,6 +100,25 @@ impl TextData { let y = (y - min_y) as u32; image.put_pixel(x, y, color); } - textures.add(image) + let offset = TextOffset { + top_left: Vec2::new(min_x as f32, min_y as f32), + bot_right: Vec2::new( + max_width - max_x as f32, + attrs.line_height * buffer.lines.len() as f32 - max_y as f32, + ), + }; + (textures.add(image), offset) + } +} + +#[derive(Clone, Copy)] +pub struct TextOffset { + pub top_left: Vec2, + pub bot_right: Vec2, +} + +impl TextOffset { + pub fn size(&self, handle: &TextureHandle) -> Vec2 { + handle.size() - self.top_left + self.bot_right } } diff --git a/src/layout/texture.rs b/src/layout/texture.rs index 62e7ec0..c28e79f 100644 --- a/src/layout/texture.rs +++ b/src/layout/texture.rs @@ -3,13 +3,14 @@ use std::{ sync::mpsc::{Receiver, Sender, channel}, }; -use image::DynamicImage; +use image::{DynamicImage, GenericImageView}; -use crate::{render::TexturePrimitive, util::RefCounter}; +use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter}; #[derive(Clone)] pub struct TextureHandle { - pub inner: TexturePrimitive, + inner: TexturePrimitive, + size: Vec2, counter: RefCounter, send: Sender, } @@ -25,10 +26,11 @@ pub struct Textures { } pub enum TextureUpdate<'a> { - PushFree, Push(&'a DynamicImage), Set(u32, &'a DynamicImage), Free(u32), + PushFree, + SetFree, } enum Update { @@ -49,7 +51,9 @@ impl Textures { } } pub fn add(&mut self, image: impl Into) -> TextureHandle { - let view_idx = self.push(image.into()); + 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 { @@ -57,6 +61,7 @@ impl Textures { view_idx, sampler_idx, }, + size, counter: RefCounter::new(), send: self.send.clone(), } @@ -89,12 +94,24 @@ impl Textures { .as_ref() .map(TextureUpdate::Push) .unwrap_or(TextureUpdate::PushFree), - Update::Set(i) => TextureUpdate::Set(i, self.images[i as usize].as_ref().unwrap()), + 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() { diff --git a/src/layout/ui.rs b/src/layout/ui.rs index ca9500f..18f60f1 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -10,7 +10,7 @@ use crate::{ }; use std::{ any::{Any, TypeId}, - ops::{Index, IndexMut, Range}, + ops::{Deref, DerefMut, Index, IndexMut, Range}, sync::mpsc::{Receiver, Sender, channel}, }; @@ -35,7 +35,12 @@ pub struct Ui { #[derive(Default)] pub struct Widgets { ids: IdTracker, - map: HashMap>, + map: HashMap, +} + +pub struct WidgetData { + widget: Box, + borrowed: bool, } impl Ui { @@ -212,40 +217,57 @@ impl Widgets { } } - pub fn get_dyn(&self, id: &Id) -> &dyn Widget { - self.map.get(id).unwrap().as_ref() + 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<'_> { + // must guarantee no other mutable references to this widget exist + // done through the borrow variable + let data: &mut WidgetData = unsafe { std::mem::transmute(self.map.get(id)) }; + if data.borrowed { + panic!("tried to mutably borrow the same widget twice"); + } + WidgetWrapper { + widget: data.widget.as_mut(), + borrowed: &mut data.borrowed, + } } pub fn get_static(&self, id: &StaticWidgetId) -> Option<&W> { - self.map.get(&id.id.id()).unwrap().as_any().downcast_ref() + self.get_dyn(&id.id.id())?.as_any().downcast_ref() } pub fn get_static_mut(&mut self, id: &StaticWidgetId) -> Option<&mut W> { - self.map - .get_mut(&id.id.id()) - .unwrap() - .as_any_mut() - .downcast_mut() + self.get_dyn_mut(&id.id.id())?.as_any_mut().downcast_mut() } pub fn get(&self, id: &WidgetId) -> Option<&W> { - self.map.get(&id.id).unwrap().as_any().downcast_ref() + self.get_dyn(&id.id)?.as_any().downcast_ref() } pub fn get_mut(&mut self, id: &WidgetId) -> Option<&mut W> { - self.map - .get_mut(&id.id) - .unwrap() - .as_any_mut() - .downcast_mut() + self.get_dyn_mut(&id.id)?.as_any_mut().downcast_mut() } pub fn insert(&mut self, id: Id, widget: impl Widget) { - self.map.insert(id, Box::new(widget)); + self.insert_any(id, Box::new(widget)); } pub fn insert_any(&mut self, id: Id, widget: Box) { - self.map.insert(id, widget); + self.map.insert( + id, + WidgetData { + widget, + borrowed: false, + }, + ); } pub fn delete(&mut self, id: Id) { @@ -266,6 +288,37 @@ impl Widgets { } } +pub struct WidgetWrapper<'a> { + widget: &'a mut dyn Widget, + borrowed: &'a mut bool, +} + +impl<'a> WidgetWrapper<'a> { + pub fn new(widget: &'a mut dyn Widget, borrowed: &'a mut bool) -> Self { + Self { widget, borrowed } + } +} + +impl Drop for WidgetWrapper<'_> { + fn drop(&mut self) { + *self.borrowed = false; + } +} + +impl Deref for WidgetWrapper<'_> { + type Target = dyn Widget; + + fn deref(&self) -> &Self::Target { + self.widget + } +} + +impl DerefMut for WidgetWrapper<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.widget + } +} + impl dyn Widget { pub fn as_any(&self) -> &dyn Any { self diff --git a/src/layout/widget.rs b/src/layout/widget.rs index cc5049c..17aca0a 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -1,9 +1,18 @@ -use crate::layout::{Painter, Ui, WidgetId, WidgetIdFnRet}; +use crate::layout::{Painter, TextData, Textures, Ui, Vec2, WidgetId, WidgetIdFnRet}; use std::{any::Any, marker::PhantomData}; pub trait Widget: Any { - fn draw(&self, painter: &mut Painter); + fn draw(&mut self, painter: &mut Painter); + fn size(&mut self, ctx: SizeCtx) -> Vec2 { + ctx.size + } +} + +pub struct SizeCtx<'a> { + pub size: Vec2, + pub text: &'a mut TextData, + pub textures: &'a mut Textures, } pub struct WidgetTag; diff --git a/src/render/texture.rs b/src/render/texture.rs index 7bd81c8..30d6ecd 100644 --- a/src/render/texture.rs +++ b/src/render/texture.rs @@ -21,6 +21,7 @@ impl GpuTextures { 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(), } diff --git a/src/testing/mod.rs b/src/testing/mod.rs index dca7dff..e8b61e4 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -88,11 +88,25 @@ impl Client { (rect, text(label).size(30)).stack() }; + let text_test = ui.add_static( + ( + text("this is a").size(30), + text("teeeeeeeest").size(30), + text("okkk\nokkkkkk!").size(30), + text("hmm").size(30), + text("a").size(30), + text("'").size(30), + text("pretty cool right?").size(30), + ) + .span(Dir::RIGHT, [sized(); _]), + ); + let tabs = ui.add( ( 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"), ) .span(Dir::RIGHT, [1; _]), ); @@ -102,7 +116,7 @@ impl Client { let child = ui .add(image(include_bytes!("assets/sungals.png"))) .erase_type(); - ui[span_add].children.push((child, ratio(1))); + ui[span_add].children.push((child, sized())); }) .region( UiPos::corner(Corner::BotRight) @@ -134,7 +148,7 @@ impl Client { main, add_button.label("add button"), del_button.label("del button"), - info_sect.label("info sect"), + // info_sect.label("info sect"), ) .stack() .label("main stack"),