mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-12-22 12:28:19 -06:00
wip image, and required ctx stuff
This commit is contained in:
parent
40c448276b
commit
1a6d79b2fc
|
@ -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>,
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
101
hui/src/element/builtin/image.rs
Normal file
101
hui/src/element/builtin/image.rs
Normal 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)
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue