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;
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<Vec4>,
///Texture
texture: Option<TextureHandle>,
texture: Option<ImageHandle>,
///Rounded corners
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.
#[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<TextureHandle> {
fn try_allocate(&mut self, size: UVec2) -> Option<ImageHandle> {
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<Corners<Vec2>> {
pub(crate) fn get_uv(&self, handle: ImageHandle) -> Option<Corners<Vec2>> {
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<UVec2> {
self.atlas.get(handle).map(|a| a.size)
}
}

View file

@ -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 {

View file

@ -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.

View file

@ -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<TextureHandle>,
pub background_image: Option<ImageHandle>,
/// 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

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,
text_measure: ctx.text_measure,
current_font: ctx.current_font,
images: ctx.images,
});
ctx.draw.add(UiDrawCommand::PopTransform);
}

View file

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

View file

@ -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 {