wip image, and required ctx stuff

This commit is contained in:
griffi-gh 2024-03-07 02:04:24 +01:00
parent 40c448276b
commit 1a6d79b2fc
9 changed files with 166 additions and 29 deletions

View file

@ -9,7 +9,7 @@ use crate::{
pub(crate) mod atlas; pub(crate) mod atlas;
use atlas::TextureAtlasManager; use atlas::TextureAtlasManager;
pub use atlas::{TextureHandle, TextureAtlasMeta, TextureFormat}; pub use atlas::{ImageHandle, TextureAtlasMeta, TextureFormat, ImageCtx};
mod corner_radius; mod corner_radius;
pub use corner_radius::RoundedCorners; pub use corner_radius::RoundedCorners;
@ -34,7 +34,7 @@ pub enum UiDrawCommand {
///Color (RGBA) ///Color (RGBA)
color: Corners<Vec4>, color: Corners<Vec4>,
///Texture ///Texture
texture: Option<TextureHandle>, texture: Option<ImageHandle>,
///Rounded corners ///Rounded corners
rounded_corners: Option<RoundedCorners>, rounded_corners: Option<RoundedCorners>,
}, },

View file

@ -46,21 +46,22 @@ pub struct TextureAtlasMeta<'a> {
/// ///
/// Internal value is an implementation detail and should not be relied upon. /// Internal value is an implementation detail and should not be relied upon.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct TextureHandle { pub struct ImageHandle {
//pub(crate) rc: Rc<()>, pub(crate) index: u32,
pub(crate) index: u32
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) struct TextureAllocation { pub(crate) struct TextureAllocation {
/// Position in the texture atlas /// Position in the texture atlas\
pub position: UVec2, /// (This is an implementation detail and should not be exposed to the user)
pub(crate) position: UVec2,
/// Requested texture size /// Requested texture size
pub size: UVec2, pub size: UVec2,
/// True if the texture was rotated by 90 degrees /// True if the texture was rotated by 90 degrees\
pub rotated: bool, /// (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\ /// 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\ /// 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\ /// Use `allocate` to allocate a texture and resize the atlas if necessary\
/// Does not modify the texture data /// Does not modify the texture data
fn try_allocate(&mut self, size: UVec2) -> Option<TextureHandle> { fn try_allocate(&mut self, size: UVec2) -> Option<ImageHandle> {
log::trace!("Allocating texture of size {:?}", size); log::trace!("Allocating texture of size {:?}", size);
let result = self.packer.pack(size.x as i32, size.y as i32, ALLOW_ROTATION)?; let result = self.packer.pack(size.x as i32, size.y as i32, ALLOW_ROTATION)?;
let index = self.count; let index = self.count;
@ -161,22 +162,22 @@ impl TextureAtlasManager {
rotated: ALLOW_ROTATION && (result.width != size.x as i32), rotated: ALLOW_ROTATION && (result.width != size.x as i32),
}; };
self.allocations.insert_unique_unchecked(index, allocation); 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\ /// Allocate a new texture region in the atlas and resize the atlas if necessary\
/// This function should never fail under normal circumstances.\ /// This function should never fail under normal circumstances.\
/// May modify the texture data if the atlas is resized /// 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.ensure_fits(size);
self.try_allocate(size).unwrap() self.try_allocate(size).unwrap()
} }
/// Allocate a new texture region in the atlas and copy the data into it\ /// 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. /// 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 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(); let allocation = self.allocations.get(&handle.index).unwrap();
assert!(!allocation.rotated, "Rotated textures are not implemented yet"); assert!(!allocation.rotated, "Rotated textures are not implemented yet");
for y in 0..size.y { 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)\ /// 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\ /// 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. /// 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 size = uvec2(width as u32, (data.len() / width) as u32);
let handle = self.allocate(size); let handle = self.allocate(size);
let allocation = self.allocations.get(&handle.index).unwrap(); let allocation = self.allocations.get(&handle.index).unwrap();
@ -211,26 +212,26 @@ impl TextureAtlasManager {
handle 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 { match format {
TextureFormat::Rgba => self.add_rgba(width, data), TextureFormat::Rgba => self.add_rgba(width, data),
TextureFormat::Grayscale => self.add_grayscale(width, data), TextureFormat::Grayscale => self.add_grayscale(width, data),
} }
} }
pub fn modify(&mut self, handle: TextureHandle) { pub fn modify(&mut self, handle: ImageHandle) {
todo!() todo!()
} }
pub fn remove(&mut self, handle: TextureHandle) { pub fn remove(&mut self, handle: ImageHandle) {
todo!() todo!()
} }
pub fn get(&self, handle: TextureHandle) -> Option<&TextureAllocation> { pub fn get(&self, handle: ImageHandle) -> Option<&TextureAllocation> {
self.allocations.get(&handle.index) self.allocations.get(&handle.index)
} }
pub(crate) fn get_uv(&self, handle: TextureHandle) -> Option<Corners<Vec2>> { pub(crate) fn get_uv(&self, handle: ImageHandle) -> Option<Corners<Vec2>> {
let info = self.get(handle)?; let info = self.get(handle)?;
let atlas_size = self.meta().size.as_vec2(); let atlas_size = self.meta().size.as_vec2();
let p0x = info.position.x as f32 / atlas_size.x; let p0x = info.position.x as f32 / atlas_size.x;
@ -257,6 +258,10 @@ impl TextureAtlasManager {
modified: self.modified, modified: self.modified,
} }
} }
pub fn context(&self) -> ImageCtx {
ImageCtx { atlas: self }
}
} }
impl Default for TextureAtlasManager { impl Default for TextureAtlasManager {
@ -265,3 +270,18 @@ impl Default for TextureAtlasManager {
Self::new(UVec2::new(512, 512)) 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<UVec2> {
self.atlas.get(handle).map(|a| a.size)
}
}

View file

@ -2,7 +2,7 @@
use std::any::Any; use std::any::Any;
use crate::{ use crate::{
draw::UiDrawCommandList, draw::{atlas::ImageCtx, UiDrawCommandList},
layout::LayoutInfo, layout::LayoutInfo,
measure::Response, measure::Response,
state::StateRepo, state::StateRepo,
@ -19,6 +19,7 @@ pub struct MeasureContext<'a> {
pub layout: &'a LayoutInfo, pub layout: &'a LayoutInfo,
pub text_measure: TextMeasure<'a>, pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle, pub current_font: FontHandle,
pub images: ImageCtx<'a>,
} }
/// Context for the `Element::process` function /// Context for the `Element::process` function
@ -29,6 +30,7 @@ pub struct ProcessContext<'a> {
pub draw: &'a mut UiDrawCommandList, pub draw: &'a mut UiDrawCommandList,
pub text_measure: TextMeasure<'a>, pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle, pub current_font: FontHandle,
pub images: ImageCtx<'a>,
} }
pub trait UiElement { pub trait UiElement {

View file

@ -16,5 +16,9 @@ pub mod text;
#[cfg(feature = "builtin_elements")] #[cfg(feature = "builtin_elements")]
pub mod transformer; 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: OverlayContainer (for simply laying multiple elements on top of each other)
//TODO add: Button, Checkbox, Dropdown, Input, Radio, Slider, Textarea, Toggle, etc. //TODO add: Button, Checkbox, Dropdown, Input, Radio, Slider, Textarea, Toggle, etc.

View file

@ -4,7 +4,7 @@ use derive_setters::Setters;
use glam::{Vec2, vec2}; use glam::{Vec2, vec2};
use crate::{ use crate::{
background::BackgroundColor, background::BackgroundColor,
draw::{RoundedCorners, TextureHandle, UiDrawCommand}, draw::{RoundedCorners, ImageHandle, UiDrawCommand},
element::{ElementList, MeasureContext, ProcessContext, UiElement}, element::{ElementList, MeasureContext, ProcessContext, UiElement},
layout::{Alignment, Alignment2d, LayoutInfo, Size, Size2d, UiDirection}, layout::{Alignment, Alignment2d, LayoutInfo, Size, Size2d, UiDirection},
measure::{Hints, Response}, measure::{Hints, Response},
@ -65,8 +65,12 @@ pub struct Container {
/// ///
/// Can be used in conjunction with the background color\ /// Can be used in conjunction with the background color\
/// In this case, the texture will be shaded by the 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)] #[setters(into)]
pub background_image: Option<TextureHandle>, pub background_image: Option<ImageHandle>,
/// Corner radius of the background rectangle /// Corner radius of the background rectangle
#[setters(into)] #[setters(into)]
@ -192,6 +196,7 @@ impl UiElement for Container {
}, },
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font, current_font: ctx.current_font,
images: ctx.images,
}); });
//Check the position of the side of element closest to the end on the primary axis //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, layout: &el_layout,
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font, current_font: ctx.current_font,
images: ctx.images,
}); });
//align (on sec. axis) //align (on sec. axis)
@ -445,6 +451,7 @@ impl UiElement for Container {
draw: ctx.draw, draw: ctx.draw,
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font, current_font: ctx.current_font,
images: ctx.images,
}); });
//layout //layout

View file

@ -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<f32>,
}
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)
}),
});
}
}
}

View file

@ -55,6 +55,7 @@ impl UiElement for Transformer {
draw: ctx.draw, draw: ctx.draw,
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font, current_font: ctx.current_font,
images: ctx.images,
}); });
ctx.draw.add(UiDrawCommand::PopTransform); ctx.draw.add(UiDrawCommand::PopTransform);
} }

View file

@ -1,7 +1,7 @@
use glam::Vec2; use glam::Vec2;
use crate::{ use crate::{
draw::{ 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} }, 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\ /// Add an image to the texture atlas\
/// Accepted texture formats are `Rgba` and `Grayscale` /// 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\ /// 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 /// 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) self.atlas.add(width, data, format)
} }
@ -115,6 +115,7 @@ impl UiInstance {
layout: &layout, layout: &layout,
text_measure: self.text_renderer.to_measure(), text_measure: self.text_renderer.to_measure(),
current_font: self.text_renderer.current_font(), current_font: self.text_renderer.current_font(),
images: self.atlas.context(),
}); });
element.process(ProcessContext { element.process(ProcessContext {
measure: &measure, measure: &measure,
@ -123,6 +124,7 @@ impl UiInstance {
draw: &mut self.draw_commands, draw: &mut self.draw_commands,
text_measure: self.text_renderer.to_measure(), text_measure: self.text_renderer.to_measure(),
current_font: self.text_renderer.current_font(), current_font: self.text_renderer.current_font(),
images: self.atlas.context(),
}); });
} }

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use fontdue::Metrics; use fontdue::Metrics;
use hashbrown::HashMap; use hashbrown::HashMap;
use crate::draw::atlas::{TextureAtlasManager, TextureHandle}; use crate::draw::atlas::{TextureAtlasManager, ImageHandle};
use super::font::{FontHandle, FontManager}; use super::font::{FontHandle, FontManager};
@ -14,7 +14,7 @@ struct GlyphCacheKey {
pub struct GlyphCacheEntry { pub struct GlyphCacheEntry {
pub metrics: Metrics, pub metrics: Metrics,
pub texture: TextureHandle, pub texture: ImageHandle,
} }
pub struct FontTextureManager { pub struct FontTextureManager {