mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-21 22:58:42 -06:00
add 9 patch rendering
This commit is contained in:
parent
d11f160d55
commit
1666b08873
|
@ -14,5 +14,6 @@ glium = "0.34"
|
|||
winit = "0.29"
|
||||
glam = "0.27"
|
||||
log = "0.4"
|
||||
image = { version = "0.25", features = ["jpeg", "png"] }
|
||||
|
||||
#created as a workaround for rust-analyzer dependency cycle (which should be allowed)
|
||||
|
|
BIN
hui-examples/assets/ninepatch_button.png
Normal file
BIN
hui-examples/assets/ninepatch_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 638 B |
|
@ -1,10 +1,9 @@
|
|||
use std::time::Instant;
|
||||
use glam::vec2;
|
||||
use hui::{
|
||||
color, element::{
|
||||
container::Container,
|
||||
fill_rect::FillRect,
|
||||
UiElementExt
|
||||
}, frame_rect, layout::{Alignment, Direction}, size
|
||||
container::Container, fill_rect::FillRect, text::Text, UiElementExt
|
||||
}, frame::nine_patch::{NinePatchAsset, NinePatchFrame}, frame_rect, layout::{Alignment, Direction}, rect::Rect, size
|
||||
};
|
||||
|
||||
#[path = "../boilerplate.rs"]
|
||||
|
@ -13,21 +12,50 @@ mod boilerplate;
|
|||
|
||||
ui_main!(
|
||||
"hUI: 9-Patch demo",
|
||||
init: |_| {
|
||||
|
||||
init: |ui| {
|
||||
NinePatchAsset {
|
||||
image: ui.add_image_file_path("./hui-examples/assets/ninepatch_button.png").unwrap(),
|
||||
size: (190, 49),
|
||||
scalable_region: Rect {
|
||||
position: vec2(8. / 190., 8. / 49.),
|
||||
size: vec2(1. - 16. / 190., 1. - 18. / 49.),
|
||||
},
|
||||
}
|
||||
},
|
||||
run: |ui, size, _| {
|
||||
run: |ui, size, asset| {
|
||||
Container::default()
|
||||
.with_size(size!(100%))
|
||||
.with_align(Alignment::Center)
|
||||
.with_gap(5.)
|
||||
.with_background(color::WHITE)
|
||||
.with_children(|ui| {
|
||||
FillRect::default()
|
||||
Container::default()
|
||||
.with_size(size!(300, 100))
|
||||
.with_frame(frame_rect! {
|
||||
color: color::RED
|
||||
.with_background(NinePatchFrame::from_asset(*asset).with_color(color::RED))
|
||||
.with_padding(10.)
|
||||
.with_children(|ui| {
|
||||
Text::new("Hello, world!\nThis is a 9-patch frame used as a background \nfor Container with a Text element.\nIt's scalable and looks great!\nBelow, there are two FillRects with the same \n9-patch frame used as the background.")
|
||||
.with_text_size(16)
|
||||
.add_child(ui);
|
||||
})
|
||||
.add_child(ui);
|
||||
FillRect::default()
|
||||
.with_size(size!(600, 75))
|
||||
.with_frame(NinePatchFrame::from_asset(*asset).with_color(color::GREEN))
|
||||
.add_child(ui);
|
||||
Text::new("This one's fancy:")
|
||||
.with_color(color::BLACK)
|
||||
.with_text_size(32)
|
||||
.add_child(ui);
|
||||
FillRect::default()
|
||||
.with_size(size!(800, 50))
|
||||
.with_frame(NinePatchFrame::from_asset(*asset).with_color((
|
||||
(1., 0., 1.),
|
||||
(0., 1., 1.),
|
||||
(1., 1., 0.),
|
||||
(0., 0., 1.),
|
||||
)))
|
||||
.add_child(ui);
|
||||
})
|
||||
.add_root(ui, size);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ pub enum UiDrawCommand {
|
|||
color: Corners<Vec4>,
|
||||
///Texture
|
||||
texture: Option<ImageHandle>,
|
||||
///Sub-UV coordinates for the texture
|
||||
texture_uv: Option<Corners<Vec2>>,
|
||||
///Rounded corners
|
||||
rounded_corners: Option<RoundedCorners>,
|
||||
},
|
||||
|
@ -161,10 +163,32 @@ impl UiDrawCall {
|
|||
v.position += center;
|
||||
}
|
||||
},
|
||||
UiDrawCommand::Rectangle { position, size, color, texture, rounded_corners } => {
|
||||
UiDrawCommand::Rectangle { position, size, color, texture, texture_uv, rounded_corners } => {
|
||||
let uvs = texture
|
||||
.map(|x| atlas.get_uv(x))
|
||||
.flatten()
|
||||
.map(|guv| {
|
||||
if let Some(texture_uv) = texture_uv {
|
||||
//XXX: this assumes that it's not rotated :p
|
||||
//hell will break loose if it is
|
||||
//seriously, i fvcking despise this code, and i hope to never touch this file ever again
|
||||
//FIXME: this is only valid if top_left is acutally the min (e.g. only for rectangular crops)
|
||||
//We currently only need rectangular crops so i don't give a fvck
|
||||
let uv_size = guv.bottom_right - guv.top_left;
|
||||
let mut uv_mapped = *texture_uv;
|
||||
uv_mapped.top_left *= uv_size;
|
||||
uv_mapped.top_right *= uv_size;
|
||||
uv_mapped.bottom_left *= uv_size;
|
||||
uv_mapped.bottom_right *= uv_size;
|
||||
uv_mapped.top_left += guv.top_left;
|
||||
uv_mapped.top_right += guv.top_left;
|
||||
uv_mapped.bottom_left += guv.top_left;
|
||||
uv_mapped.bottom_right += guv.top_left;
|
||||
uv_mapped
|
||||
} else {
|
||||
guv
|
||||
}
|
||||
})
|
||||
.unwrap_or(Corners::all(Vec2::ZERO));
|
||||
|
||||
let vidx = draw_call.vertices.len() as u32;
|
||||
|
|
|
@ -79,6 +79,7 @@ impl UiElement for Image {
|
|||
size: ctx.measure.size,
|
||||
color: self.color.corners(),
|
||||
texture: Some(self.image),
|
||||
texture_uv: None,
|
||||
rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({
|
||||
RoundedCorners::from_radius(self.corner_radius)
|
||||
}),
|
||||
|
|
|
@ -87,6 +87,7 @@ impl UiElement for ProgressBar {
|
|||
size: ctx.measure.size,
|
||||
color: self.background.corners(),
|
||||
texture: None,
|
||||
texture_uv: None,
|
||||
rounded_corners
|
||||
});
|
||||
}
|
||||
|
@ -96,6 +97,7 @@ impl UiElement for ProgressBar {
|
|||
size: ctx.measure.size * vec2(value, 1.0),
|
||||
color: self.foreground.corners(),
|
||||
texture: None,
|
||||
texture_uv: None,
|
||||
rounded_corners,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::draw::UiDrawCommandList;
|
|||
pub mod point;
|
||||
mod rect;
|
||||
pub mod stack;
|
||||
pub mod nine_patch;
|
||||
mod impls;
|
||||
|
||||
pub use rect::FrameRect;
|
||||
|
|
|
@ -13,6 +13,7 @@ impl Frame for ImageHandle {
|
|||
size: parent_size,
|
||||
color: color::WHITE.into(),
|
||||
texture: Some(*self),
|
||||
texture_uv: None,
|
||||
rounded_corners: None,
|
||||
})
|
||||
}
|
||||
|
@ -29,6 +30,7 @@ impl Frame for FillColor {
|
|||
size: parent_size,
|
||||
color: self.corners(),
|
||||
texture: None,
|
||||
texture_uv: None,
|
||||
rounded_corners: None,
|
||||
})
|
||||
}
|
||||
|
|
224
hui/src/frame/nine_patch.rs
Normal file
224
hui/src/frame/nine_patch.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
use glam::{vec2, UVec2, Vec2, Vec4};
|
||||
use crate::{color, draw::{ImageHandle, UiDrawCommand}, rect::{Corners, FillColor, Rect}};
|
||||
use super::Frame;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NinePatchAsset {
|
||||
pub image: ImageHandle,
|
||||
//TODO: remove this
|
||||
pub size: (u32, u32),
|
||||
pub scalable_region: Rect,
|
||||
}
|
||||
|
||||
//TODO allow scaling/moving corners
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NinePatchFrame {
|
||||
pub asset: NinePatchAsset,
|
||||
pub color: FillColor,
|
||||
}
|
||||
|
||||
impl NinePatchFrame {
|
||||
pub fn from_asset(asset: NinePatchAsset) -> Self {
|
||||
Self { asset, ..Default::default() }
|
||||
}
|
||||
|
||||
pub fn with_color(mut self, color: impl Into<FillColor>) -> Self {
|
||||
self.color = color.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NinePatchFrame {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
//This is not supposed to be left out as the default, so just set it to whatever :p
|
||||
asset: NinePatchAsset { image: ImageHandle::default(), size: (0, 0), scalable_region: Rect::default() },
|
||||
color: color::WHITE.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame for NinePatchFrame {
|
||||
fn draw(&self, draw: &mut crate::draw::UiDrawCommandList, position: glam::Vec2, parent_size: glam::Vec2) {
|
||||
// without this, shїt gets messed up when the position is not a whole number
|
||||
//XXX: should we round the size as well?
|
||||
let position = position.round();
|
||||
|
||||
let img_sz = UVec2::from(self.asset.size).as_vec2();
|
||||
|
||||
//Color stuff
|
||||
let interpolate_color_rect = |uvs: Corners<Vec2>| {
|
||||
Corners {
|
||||
top_left: self.color.interpolate(uvs.top_left),
|
||||
top_right: self.color.interpolate(uvs.top_right),
|
||||
bottom_left: self.color.interpolate(uvs.bottom_left),
|
||||
bottom_right: self.color.interpolate(uvs.bottom_right),
|
||||
}
|
||||
};
|
||||
|
||||
// Inset coords, in UV space
|
||||
let region_uv = self.asset.scalable_region.corners();
|
||||
|
||||
// Inset coords, in image (px) space
|
||||
let corners_image_px = Corners {
|
||||
top_left: img_sz * region_uv.top_left,
|
||||
top_right: img_sz * region_uv.top_right,
|
||||
bottom_left: img_sz * region_uv.bottom_left,
|
||||
bottom_right: img_sz * region_uv.bottom_right,
|
||||
};
|
||||
|
||||
let size_h = (
|
||||
corners_image_px.top_left.x,
|
||||
parent_size.x - corners_image_px.top_left.x - (img_sz.x - corners_image_px.top_right.x),
|
||||
img_sz.x - corners_image_px.top_right.x,
|
||||
);
|
||||
|
||||
let size_v = (
|
||||
corners_image_px.top_left.y,
|
||||
parent_size.y - corners_image_px.top_left.y - (img_sz.y - corners_image_px.bottom_left.y),
|
||||
img_sz.y - corners_image_px.bottom_left.y,
|
||||
);
|
||||
|
||||
//Top-left patch
|
||||
let top_left_patch_uv = Corners {
|
||||
top_left: vec2(0., 0.),
|
||||
top_right: vec2(region_uv.top_left.x, 0.),
|
||||
bottom_left: vec2(0., region_uv.top_left.y),
|
||||
bottom_right: region_uv.top_left,
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position,
|
||||
size: vec2(size_h.0, size_v.0),
|
||||
color: interpolate_color_rect(top_left_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(top_left_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
//Top patch
|
||||
let top_patch_uv = Corners {
|
||||
top_left: vec2(region_uv.top_left.x, 0.),
|
||||
top_right: vec2(region_uv.top_right.x, 0.),
|
||||
bottom_left: region_uv.top_left,
|
||||
bottom_right: region_uv.top_right,
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(size_h.0, 0.),
|
||||
size: vec2(size_h.1, size_v.0),
|
||||
color: interpolate_color_rect(top_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(top_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
//Top-right patch
|
||||
let top_right_patch_uv = Corners {
|
||||
top_left: vec2(region_uv.top_right.x, 0.),
|
||||
top_right: vec2(1., 0.),
|
||||
bottom_left: region_uv.top_right,
|
||||
bottom_right: vec2(1., region_uv.top_right.y),
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(size_h.0 + size_h.1, 0.),
|
||||
size: vec2(size_h.2, size_v.0),
|
||||
color: interpolate_color_rect(top_right_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(top_right_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
//Left patch
|
||||
let left_patch_uv = Corners {
|
||||
top_left: vec2(0., region_uv.top_left.y),
|
||||
top_right: region_uv.top_left,
|
||||
bottom_left: vec2(0., region_uv.bottom_left.y),
|
||||
bottom_right: region_uv.bottom_left,
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(0., size_v.0),
|
||||
size: vec2(size_h.0, size_v.1),
|
||||
color: interpolate_color_rect(left_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(left_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
// Center patch
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(size_h.0, size_v.0),
|
||||
size: vec2(size_h.1, size_v.1),
|
||||
color: interpolate_color_rect(region_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(region_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
//Right patch
|
||||
let right_patch_uv = Corners {
|
||||
top_left: region_uv.top_right,
|
||||
top_right: vec2(1., region_uv.top_right.y),
|
||||
bottom_left: region_uv.bottom_right,
|
||||
bottom_right: vec2(1., region_uv.bottom_right.y),
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(size_h.0 + size_h.1, size_v.0),
|
||||
size: vec2(size_h.2, size_v.1),
|
||||
color: interpolate_color_rect(right_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(right_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
//Bottom-left patch
|
||||
let bottom_left_patch_uv = Corners {
|
||||
top_left: vec2(0., region_uv.bottom_left.y),
|
||||
top_right: region_uv.bottom_left,
|
||||
bottom_left: vec2(0., 1.),
|
||||
bottom_right: vec2(region_uv.bottom_left.x, 1.),
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(0., size_v.0 + size_v.1),
|
||||
size: vec2(size_h.0, size_v.2),
|
||||
color: interpolate_color_rect(bottom_left_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(bottom_left_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
//Bottom patch
|
||||
let bottom_patch_uv = Corners {
|
||||
top_left: region_uv.bottom_left,
|
||||
top_right: region_uv.bottom_right,
|
||||
bottom_left: vec2(region_uv.bottom_left.x, 1.),
|
||||
bottom_right: vec2(region_uv.bottom_right.x, 1.),
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(size_h.0, size_v.0 + size_v.1),
|
||||
size: vec2(size_h.1, size_v.2),
|
||||
color: interpolate_color_rect(bottom_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(bottom_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
|
||||
//Bottom-right patch
|
||||
let bottom_right_patch_uv = Corners {
|
||||
top_left: region_uv.bottom_right,
|
||||
top_right: vec2(1., region_uv.bottom_right.y),
|
||||
bottom_left: vec2(region_uv.bottom_right.x, 1.),
|
||||
bottom_right: vec2(1., 1.),
|
||||
};
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: position + vec2(size_h.0 + size_h.1, size_v.0 + size_v.1),
|
||||
size: vec2(size_h.2, size_v.2),
|
||||
color: interpolate_color_rect(bottom_right_patch_uv),
|
||||
texture: Some(self.asset.image),
|
||||
texture_uv: Some(bottom_right_patch_uv),
|
||||
rounded_corners: None
|
||||
});
|
||||
}
|
||||
|
||||
fn covers_opaque(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ impl UiInstance {
|
|||
/// (this will change to a soft error in the future)
|
||||
#[cfg(feature = "image")]
|
||||
pub fn add_image_file_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<ImageHandle, std::io::Error> {
|
||||
use std::io::Read;
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
// Open the file (and wrap it in a bufreader)
|
||||
let mut file = std::io::BufReader::new(std::fs::File::open(path)?);
|
||||
|
@ -106,6 +106,7 @@ impl UiInstance {
|
|||
let mut magic = [0; 64];
|
||||
file.read_exact(&mut magic)?;
|
||||
let format = image::guess_format(&magic).expect("Invalid image data (FORMAT)");
|
||||
file.seek(std::io::SeekFrom::Start(0))?;
|
||||
|
||||
//Parse the image and read the raw uncompressed rgba data
|
||||
let image = image::load(file, format).expect("Invalid image data");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::Corners;
|
||||
use glam::{Vec3, Vec4, vec4};
|
||||
use glam::{Vec2, Vec3, Vec4, vec4};
|
||||
|
||||
/// Represents the fill color of a rectangle
|
||||
///
|
||||
|
@ -69,6 +69,14 @@ impl FillColor {
|
|||
pub const fn corners(&self) -> Corners<Vec4> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Interpolate color on position, assuming a linear gradient
|
||||
pub fn interpolate(&self, uv: Vec2) -> Vec4 {
|
||||
let c = self.corners();
|
||||
let top = c.top_left.lerp(c.top_right, uv.x);
|
||||
let bottom = c.bottom_left.lerp(c.bottom_right, uv.x);
|
||||
top.lerp(bottom, uv.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FillColor {
|
||||
|
|
Loading…
Reference in a new issue