From 1a6d79b2fcca303e22395c5670cdd9a30cb38eb5 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 7 Mar 2024 02:04:24 +0100 Subject: [PATCH] wip image, and required ctx stuff --- hui/src/draw.rs | 4 +- hui/src/draw/atlas.rs | 58 +++++++++----- hui/src/element.rs | 4 +- hui/src/element/builtin.rs | 4 + hui/src/element/builtin/container.rs | 11 ++- hui/src/element/builtin/image.rs | 101 +++++++++++++++++++++++++ hui/src/element/builtin/transformer.rs | 1 + hui/src/instance.rs | 8 +- hui/src/text/ftm.rs | 4 +- 9 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 hui/src/element/builtin/image.rs diff --git a/hui/src/draw.rs b/hui/src/draw.rs index a8b089d..3772d74 100644 --- a/hui/src/draw.rs +++ b/hui/src/draw.rs @@ -9,7 +9,7 @@ use crate::{ pub(crate) mod atlas; use atlas::TextureAtlasManager; -pub use atlas::{TextureHandle, TextureAtlasMeta, TextureFormat}; +pub use atlas::{ImageHandle, TextureAtlasMeta, TextureFormat, ImageCtx}; mod corner_radius; pub use corner_radius::RoundedCorners; @@ -34,7 +34,7 @@ pub enum UiDrawCommand { ///Color (RGBA) color: Corners, ///Texture - texture: Option, + texture: Option, ///Rounded corners rounded_corners: Option, }, diff --git a/hui/src/draw/atlas.rs b/hui/src/draw/atlas.rs index 919d08d..a283b02 100644 --- a/hui/src/draw/atlas.rs +++ b/hui/src/draw/atlas.rs @@ -46,21 +46,22 @@ pub struct TextureAtlasMeta<'a> { /// /// Internal value is an implementation detail and should not be relied upon. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] -pub struct TextureHandle { - //pub(crate) rc: Rc<()>, - pub(crate) index: u32 +pub struct ImageHandle { + pub(crate) index: u32, } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub(crate) struct TextureAllocation { - /// Position in the texture atlas - pub position: UVec2, + /// Position in the texture atlas\ + /// (This is an implementation detail and should not be exposed to the user) + pub(crate) position: UVec2, /// Requested texture size pub size: UVec2, - /// True if the texture was rotated by 90 degrees - pub rotated: bool, + /// True if the texture was rotated by 90 degrees\ + /// (This is an implementation detail and should not be exposed to the user) + pub(crate) rotated: bool, } /// Manages a texture atlas and the allocation of space within it\ @@ -149,7 +150,7 @@ impl TextureAtlasManager { /// Returns None if the texture could not be allocated due to lack of space\ /// Use `allocate` to allocate a texture and resize the atlas if necessary\ /// Does not modify the texture data - fn try_allocate(&mut self, size: UVec2) -> Option { + fn try_allocate(&mut self, size: UVec2) -> Option { log::trace!("Allocating texture of size {:?}", size); let result = self.packer.pack(size.x as i32, size.y as i32, ALLOW_ROTATION)?; let index = self.count; @@ -161,22 +162,22 @@ impl TextureAtlasManager { rotated: ALLOW_ROTATION && (result.width != size.x as i32), }; self.allocations.insert_unique_unchecked(index, allocation); - Some(TextureHandle { index }) + Some(ImageHandle { index }) } /// Allocate a new texture region in the atlas and resize the atlas if necessary\ /// This function should never fail under normal circumstances.\ /// May modify the texture data if the atlas is resized - pub fn allocate(&mut self, size: UVec2) -> TextureHandle { + pub fn allocate(&mut self, size: UVec2) -> ImageHandle { self.ensure_fits(size); self.try_allocate(size).unwrap() } /// Allocate a new texture region in the atlas and copy the data into it\ /// This function may resize the atlas as needed, and should never fail under normal circumstances. - pub(crate) fn add_rgba(&mut self, width: usize, data: &[u8]) -> TextureHandle { + pub(crate) fn add_rgba(&mut self, width: usize, data: &[u8]) -> ImageHandle { let size = uvec2(width as u32, (data.len() / (width * RGBA_CHANNEL_COUNT as usize)) as u32); - let handle: TextureHandle = self.allocate(size); + let handle: ImageHandle = self.allocate(size); let allocation = self.allocations.get(&handle.index).unwrap(); assert!(!allocation.rotated, "Rotated textures are not implemented yet"); for y in 0..size.y { @@ -195,7 +196,7 @@ impl TextureAtlasManager { /// Works the same way as [`TextureAtlasManager::add`], but the input data is assumed to be grayscale (1 channel per pixel)\ /// The data is copied into the alpha channel of the texture, while all the other channels are set to 255\ /// May resize the atlas as needed, and should never fail under normal circumstances. - pub(crate) fn add_grayscale(&mut self, width: usize, data: &[u8]) -> TextureHandle { + pub(crate) fn add_grayscale(&mut self, width: usize, data: &[u8]) -> ImageHandle { let size = uvec2(width as u32, (data.len() / width) as u32); let handle = self.allocate(size); let allocation = self.allocations.get(&handle.index).unwrap(); @@ -211,26 +212,26 @@ impl TextureAtlasManager { handle } - pub fn add(&mut self, width: usize, data: &[u8], format: TextureFormat) -> TextureHandle { + pub fn add(&mut self, width: usize, data: &[u8], format: TextureFormat) -> ImageHandle { match format { TextureFormat::Rgba => self.add_rgba(width, data), TextureFormat::Grayscale => self.add_grayscale(width, data), } } - pub fn modify(&mut self, handle: TextureHandle) { + pub fn modify(&mut self, handle: ImageHandle) { todo!() } - pub fn remove(&mut self, handle: TextureHandle) { + pub fn remove(&mut self, handle: ImageHandle) { todo!() } - pub fn get(&self, handle: TextureHandle) -> Option<&TextureAllocation> { + pub fn get(&self, handle: ImageHandle) -> Option<&TextureAllocation> { self.allocations.get(&handle.index) } - pub(crate) fn get_uv(&self, handle: TextureHandle) -> Option> { + pub(crate) fn get_uv(&self, handle: ImageHandle) -> Option> { let info = self.get(handle)?; let atlas_size = self.meta().size.as_vec2(); let p0x = info.position.x as f32 / atlas_size.x; @@ -257,6 +258,10 @@ impl TextureAtlasManager { modified: self.modified, } } + + pub fn context(&self) -> ImageCtx { + ImageCtx { atlas: self } + } } impl Default for TextureAtlasManager { @@ -265,3 +270,18 @@ impl Default for TextureAtlasManager { Self::new(UVec2::new(512, 512)) } } + +/// Context that allows read-only accss to image metadata +#[derive(Clone, Copy)] +pub struct ImageCtx<'a> { + pub(crate) atlas: &'a TextureAtlasManager, +} + +impl ImageCtx<'_> { + /// Get size of the image with the specified handle + /// + /// Returns None if the handle is invalid for the current context + pub fn get_size(&self, handle: ImageHandle) -> Option { + self.atlas.get(handle).map(|a| a.size) + } +} diff --git a/hui/src/element.rs b/hui/src/element.rs index aa6017d..cd3e2dc 100644 --- a/hui/src/element.rs +++ b/hui/src/element.rs @@ -2,7 +2,7 @@ use std::any::Any; use crate::{ - draw::UiDrawCommandList, + draw::{atlas::ImageCtx, UiDrawCommandList}, layout::LayoutInfo, measure::Response, state::StateRepo, @@ -19,6 +19,7 @@ pub struct MeasureContext<'a> { pub layout: &'a LayoutInfo, pub text_measure: TextMeasure<'a>, pub current_font: FontHandle, + pub images: ImageCtx<'a>, } /// Context for the `Element::process` function @@ -29,6 +30,7 @@ pub struct ProcessContext<'a> { pub draw: &'a mut UiDrawCommandList, pub text_measure: TextMeasure<'a>, pub current_font: FontHandle, + pub images: ImageCtx<'a>, } pub trait UiElement { diff --git a/hui/src/element/builtin.rs b/hui/src/element/builtin.rs index 311166a..469a5ca 100644 --- a/hui/src/element/builtin.rs +++ b/hui/src/element/builtin.rs @@ -16,5 +16,9 @@ pub mod text; #[cfg(feature = "builtin_elements")] pub mod transformer; +#[cfg(feature = "builtin_elements")] +pub mod image; + +//TODO add: Image //TODO add: OverlayContainer (for simply laying multiple elements on top of each other) //TODO add: Button, Checkbox, Dropdown, Input, Radio, Slider, Textarea, Toggle, etc. diff --git a/hui/src/element/builtin/container.rs b/hui/src/element/builtin/container.rs index 2afd944..9d06800 100644 --- a/hui/src/element/builtin/container.rs +++ b/hui/src/element/builtin/container.rs @@ -4,7 +4,7 @@ use derive_setters::Setters; use glam::{Vec2, vec2}; use crate::{ background::BackgroundColor, - draw::{RoundedCorners, TextureHandle, UiDrawCommand}, + draw::{RoundedCorners, ImageHandle, UiDrawCommand}, element::{ElementList, MeasureContext, ProcessContext, UiElement}, layout::{Alignment, Alignment2d, LayoutInfo, Size, Size2d, UiDirection}, measure::{Hints, Response}, @@ -65,8 +65,12 @@ pub struct Container { /// /// Can be used in conjunction with the background color\ /// In this case, the texture will be shaded by the color + /// + /// Please note that if the background color is NOT set (or set to transparent), the texture will NOT be visible\ + /// This is because the texture is multiplied by the color, and if the color is transparent, the texture will be too\ + //TODO: fix this flaw, if background_image is called for the first time, bg wasnt explicitly set and background is transparent, set it to white #[setters(into)] - pub background_image: Option, + pub background_image: Option, /// Corner radius of the background rectangle #[setters(into)] @@ -192,6 +196,7 @@ impl UiElement for Container { }, text_measure: ctx.text_measure, current_font: ctx.current_font, + images: ctx.images, }); //Check the position of the side of element closest to the end on the primary axis @@ -403,6 +408,7 @@ impl UiElement for Container { layout: &el_layout, text_measure: ctx.text_measure, current_font: ctx.current_font, + images: ctx.images, }); //align (on sec. axis) @@ -445,6 +451,7 @@ impl UiElement for Container { draw: ctx.draw, text_measure: ctx.text_measure, current_font: ctx.current_font, + images: ctx.images, }); //layout diff --git a/hui/src/element/builtin/image.rs b/hui/src/element/builtin/image.rs new file mode 100644 index 0000000..0b7511a --- /dev/null +++ b/hui/src/element/builtin/image.rs @@ -0,0 +1,101 @@ +use derive_setters::Setters; +use glam::vec2; +use crate::{ + background::BackgroundColor, + draw::{ImageHandle, RoundedCorners, UiDrawCommand}, + element::{MeasureContext, ProcessContext, UiElement}, + layout::{Size, Size2d}, + measure::Response, + rectangle::Corners, +}; + +#[derive(Setters)] +pub struct Image { + /// Image handle to draw + #[setters(skip)] + pub image: ImageHandle, + + /// Size of the image. + /// + /// - If one of the dimensions is `Size::Auto`, the image will be scaled to fit the other dimension\ + /// (aspect ratio is preserved) + /// - If both dimensions are `Size::Auto`, the image will be drawn at its original size + /// - All other values behave as expected + #[setters(into)] + pub size: Size2d, + + /// Color of the image + /// + /// Image will get multiplied/tinted by this color or gradient + #[setters(into)] + pub color: BackgroundColor, + + /// Corner radius of the image + #[setters(into)] + pub corner_radius: Corners, +} + +impl Image { + pub fn new(handle: ImageHandle) -> Self { + Self { + image: handle, + size: Size2d { + width: Size::Auto, + height: Size::Auto, + }, + color: BackgroundColor::from((1., 1., 1., 1.)), + corner_radius: Corners::all(0.), + } + } +} + +impl UiElement for Image { + fn name(&self) -> &'static str { + "Image" + } + + fn measure(&self, ctx: MeasureContext) -> Response { + let dim = ctx.images.get_size(self.image).expect("invalid image handle"); + Response { + size: vec2( + match self.size.width { + Size::Auto => { + match self.size.height { + Size::Auto => dim.x as f32, + Size::Fraction(f) => ((f * ctx.layout.max_size.y) / dim.y as f32) * dim.x as f32, + Size::Static(pixels) => (pixels / dim.y as f32) * dim.x as f32, + } + }, + Size::Fraction(percentage) => ctx.layout.max_size.x * percentage, + Size::Static(pixels) => pixels, + }, + match self.size.height { + Size::Auto => { + match self.size.width { + Size::Auto => dim.y as f32, + Size::Fraction(f) => ((f * ctx.layout.max_size.x) / dim.x as f32) * dim.y as f32, + Size::Static(pixels) => (pixels / dim.x as f32) * dim.y as f32, + } + }, + Size::Fraction(percentage) => ctx.layout.max_size.y * percentage, + Size::Static(pixels) => pixels, + }, + ), + ..Default::default() + } + } + + fn process(&self, ctx: ProcessContext) { + if !self.color.is_transparent() { + ctx.draw.add(UiDrawCommand::Rectangle { + position: ctx.layout.position, + size: ctx.measure.size, + color: self.color.corners(), + texture: Some(self.image), + rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({ + RoundedCorners::from_radius(self.corner_radius) + }), + }); + } + } +} diff --git a/hui/src/element/builtin/transformer.rs b/hui/src/element/builtin/transformer.rs index 5389c2b..6645ac2 100644 --- a/hui/src/element/builtin/transformer.rs +++ b/hui/src/element/builtin/transformer.rs @@ -55,6 +55,7 @@ impl UiElement for Transformer { draw: ctx.draw, text_measure: ctx.text_measure, current_font: ctx.current_font, + images: ctx.images, }); ctx.draw.add(UiDrawCommand::PopTransform); } diff --git a/hui/src/instance.rs b/hui/src/instance.rs index c595ae2..e8cefcb 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -1,7 +1,7 @@ use glam::Vec2; use crate::{ draw::{ - atlas::{TextureAtlasManager, TextureAtlasMeta}, TextureFormat, TextureHandle, UiDrawCall, UiDrawCommandList + atlas::{TextureAtlasManager, TextureAtlasMeta}, TextureFormat, ImageHandle, UiDrawCall, UiDrawCommandList }, element::{MeasureContext, ProcessContext, UiElement}, event::{EventQueue, UiEvent}, input::UiInputState, layout::{LayoutInfo, UiDirection}, state::StateRepo, text::{FontHandle, TextRenderer} }; @@ -67,10 +67,10 @@ impl UiInstance { /// Add an image to the texture atlas\ /// Accepted texture formats are `Rgba` and `Grayscale` /// - /// Returns a texture handle ([`TextureHandle`])\ + /// Returns an image handle ([`ImageHandle`])\ /// This handle can be used to reference the texture in draw commands\ /// It's a light reference and can be cloned/copied freely, but will not be cleaned up even when dropped - pub fn add_image(&mut self, format: TextureFormat, data: &[u8], width: usize) -> TextureHandle { + pub fn add_image(&mut self, format: TextureFormat, data: &[u8], width: usize) -> ImageHandle { self.atlas.add(width, data, format) } @@ -115,6 +115,7 @@ impl UiInstance { layout: &layout, text_measure: self.text_renderer.to_measure(), current_font: self.text_renderer.current_font(), + images: self.atlas.context(), }); element.process(ProcessContext { measure: &measure, @@ -123,6 +124,7 @@ impl UiInstance { draw: &mut self.draw_commands, text_measure: self.text_renderer.to_measure(), current_font: self.text_renderer.current_font(), + images: self.atlas.context(), }); } diff --git a/hui/src/text/ftm.rs b/hui/src/text/ftm.rs index 4f20f25..4bb9693 100644 --- a/hui/src/text/ftm.rs +++ b/hui/src/text/ftm.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use fontdue::Metrics; use hashbrown::HashMap; -use crate::draw::atlas::{TextureAtlasManager, TextureHandle}; +use crate::draw::atlas::{TextureAtlasManager, ImageHandle}; use super::font::{FontHandle, FontManager}; @@ -14,7 +14,7 @@ struct GlyphCacheKey { pub struct GlyphCacheEntry { pub metrics: Metrics, - pub texture: TextureHandle, + pub texture: ImageHandle, } pub struct FontTextureManager {