mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-25 08:28:42 -06:00
ui transforms
This commit is contained in:
parent
da61904a5a
commit
b46db55f1b
63
hui-examples/examples/ui_test3.rs
Normal file
63
hui-examples/examples/ui_test3.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use glam::{vec4, Vec2};
|
||||||
|
use hui::{
|
||||||
|
element::{
|
||||||
|
container::Container,
|
||||||
|
text::Text,
|
||||||
|
transformer::ElementTransformExt,
|
||||||
|
UiElementExt
|
||||||
|
},
|
||||||
|
layout::Alignment,
|
||||||
|
rectangle::Corners,
|
||||||
|
text::FontHandle,
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[path = "../boilerplate.rs"]
|
||||||
|
#[macro_use]
|
||||||
|
mod boilerplate;
|
||||||
|
|
||||||
|
ui_main!(
|
||||||
|
init: |ui| {
|
||||||
|
let font = ui.add_font(include_bytes!("../assets/blink/Blink-ynYZ.otf"));
|
||||||
|
ui.push_font(font);
|
||||||
|
(std::time::Instant::now(),)
|
||||||
|
},
|
||||||
|
run: |ui, size, (instant,)| {
|
||||||
|
let elapsed_sec = instant.elapsed().as_secs_f32();
|
||||||
|
Container::default()
|
||||||
|
.with_background(Corners {
|
||||||
|
top_left: vec4(0.2, 0.2, 0.3, 1.),
|
||||||
|
top_right: vec4(0.3, 0.3, 0.4, 1.),
|
||||||
|
bottom_left: vec4(0.2, 0.3, 0.2, 1.),
|
||||||
|
bottom_right: vec4(0.5, 0.4, 0.4, 1.),
|
||||||
|
})
|
||||||
|
.with_size(size!(100%))
|
||||||
|
.with_align(Alignment::Center)
|
||||||
|
.with_children(|ui| {
|
||||||
|
Container::default()
|
||||||
|
.with_align((Alignment::Center, Alignment::Begin))
|
||||||
|
.with_padding(15.)
|
||||||
|
.with_gap(10.)
|
||||||
|
.with_corner_radius(8.)
|
||||||
|
.with_background((0., 0., 0., 0.5))
|
||||||
|
.with_children(|ui| {
|
||||||
|
Text::default()
|
||||||
|
.with_text("Did you know?")
|
||||||
|
.with_text_size(18)
|
||||||
|
.add_child(ui);
|
||||||
|
Text::default()
|
||||||
|
.with_text("You can die by jumping into the spike pit! :D\nCheck out the tutorial section for more tips.")
|
||||||
|
.with_text_size(24)
|
||||||
|
.with_font(FontHandle::default())
|
||||||
|
.add_child(ui);
|
||||||
|
})
|
||||||
|
.add_child(ui);
|
||||||
|
})
|
||||||
|
.transform()
|
||||||
|
.scale(Vec2::splat(elapsed_sec.sin() * 0.1 + 1.))
|
||||||
|
.rotate(elapsed_sec * PI / 4.)
|
||||||
|
.add_root(ui, size);
|
||||||
|
}
|
||||||
|
);
|
|
@ -16,7 +16,7 @@ pub use corner_radius::RoundedCorners;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use fontdue::layout::{Layout, CoordinateSystem, TextStyle};
|
use fontdue::layout::{Layout, CoordinateSystem, TextStyle};
|
||||||
use glam::{Vec2, Vec4, vec2};
|
use glam::{vec2, Vec2, Affine2, Vec4};
|
||||||
|
|
||||||
//TODO: circle draw command
|
//TODO: circle draw command
|
||||||
|
|
||||||
|
@ -51,6 +51,10 @@ pub enum UiDrawCommand {
|
||||||
///Font handle to use
|
///Font handle to use
|
||||||
font: FontHandle,
|
font: FontHandle,
|
||||||
},
|
},
|
||||||
|
/// Push a transformation matrix to the stack
|
||||||
|
PushTransform(Affine2),
|
||||||
|
/// Pop a transformation matrix from the stack
|
||||||
|
PopTransform,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of draw commands
|
/// List of draw commands
|
||||||
|
@ -91,9 +95,45 @@ pub struct UiDrawCall {
|
||||||
impl UiDrawCall {
|
impl UiDrawCall {
|
||||||
/// Tesselate the UI and build a complete draw plan from a list of draw commands
|
/// Tesselate the UI and build a complete draw plan from a list of draw commands
|
||||||
pub(crate) fn build(draw_commands: &UiDrawCommandList, atlas: &mut TextureAtlasManager, text_renderer: &mut TextRenderer) -> Self {
|
pub(crate) fn build(draw_commands: &UiDrawCommandList, atlas: &mut TextureAtlasManager, text_renderer: &mut TextRenderer) -> Self {
|
||||||
|
let mut trans_stack = Vec::new();
|
||||||
let mut draw_call = UiDrawCall::default();
|
let mut draw_call = UiDrawCall::default();
|
||||||
for command in &draw_commands.commands {
|
for command in &draw_commands.commands {
|
||||||
match command {
|
match command {
|
||||||
|
UiDrawCommand::PushTransform(trans) => {
|
||||||
|
//Take note of the current index, and the transformation matrix\
|
||||||
|
//We will actually apply the transformation matrix when we pop it,
|
||||||
|
//to all vertices between the current index and the index we pushed
|
||||||
|
trans_stack.push((trans, draw_call.vertices.len() as u32));
|
||||||
|
},
|
||||||
|
UiDrawCommand::PopTransform => {
|
||||||
|
//Pop the transformation matrix and apply it to all vertices between the current index and the index we pushed
|
||||||
|
let (&trans, idx) = trans_stack.pop().expect("Unbalanced push/pop transform");
|
||||||
|
|
||||||
|
//If Push is immediately followed by a pop (which is dumb but possible), we don't need to do anything
|
||||||
|
//(this can also happen if push and pop are separated by a draw command that doesn't add any vertices, like a text command with an empty string)
|
||||||
|
if idx == draw_call.vertices.len() as u32 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Kinda a hack:
|
||||||
|
//We want to apply the transform aronnd the center, so we need to compute the center of the vertices
|
||||||
|
//We won't actually do that, we will compute the center of the bounding box of the vertices
|
||||||
|
let mut min = Vec2::splat(std::f32::INFINITY);
|
||||||
|
let mut max = Vec2::splat(std::f32::NEG_INFINITY);
|
||||||
|
for v in &draw_call.vertices[idx as usize..] {
|
||||||
|
min = min.min(v.position);
|
||||||
|
max = max.max(v.position);
|
||||||
|
}
|
||||||
|
//TODO: make the point of transform configurable
|
||||||
|
let center = (min + max) / 2.;
|
||||||
|
|
||||||
|
//Apply trans mtx to all vertices between idx and the current index
|
||||||
|
for v in &mut draw_call.vertices[idx as usize..] {
|
||||||
|
v.position -= center;
|
||||||
|
v.position = trans.transform_point2(v.position);
|
||||||
|
v.position += center;
|
||||||
|
}
|
||||||
|
},
|
||||||
UiDrawCommand::Rectangle { position, size, color, texture, rounded_corners } => {
|
UiDrawCommand::Rectangle { position, size, color, texture, rounded_corners } => {
|
||||||
let uvs = texture
|
let uvs = texture
|
||||||
.map(|x| atlas.get_uv(x))
|
.map(|x| atlas.get_uv(x))
|
||||||
|
@ -257,6 +297,8 @@ impl UiDrawCall {
|
||||||
feature = "pixel_perfect_text",
|
feature = "pixel_perfect_text",
|
||||||
not(feature = "pixel_perfect")
|
not(feature = "pixel_perfect")
|
||||||
))] {
|
))] {
|
||||||
|
//Round the position of the vertices to the nearest pixel, unless any transformations are active
|
||||||
|
if trans_stack.is_empty() {
|
||||||
for vtx in &mut draw_call.vertices[(vidx as usize)..] {
|
for vtx in &mut draw_call.vertices[(vidx as usize)..] {
|
||||||
vtx.position = vtx.position.round()
|
vtx.position = vtx.position.round()
|
||||||
}
|
}
|
||||||
|
@ -265,6 +307,7 @@ impl UiDrawCall {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[cfg(feature = "pixel_perfect")]
|
#[cfg(feature = "pixel_perfect")]
|
||||||
draw_call.vertices.iter_mut().for_each(|v| {
|
draw_call.vertices.iter_mut().for_each(|v| {
|
||||||
v.position = v.position.round()
|
v.position = v.position.round()
|
||||||
|
|
|
@ -12,3 +12,8 @@ pub mod progress_bar;
|
||||||
|
|
||||||
#[cfg(feature = "builtin_elements")]
|
#[cfg(feature = "builtin_elements")]
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
#[cfg(feature = "builtin_elements")]
|
||||||
|
pub mod transformer;
|
||||||
|
|
||||||
|
//TODO add: Button, Checkbox, Dropdown, Input, Radio, Slider, Textarea, Toggle, etc.
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
//TODO this thing?
|
||||||
|
//not sure if this is a good idea...
|
||||||
|
//but having the ability to add a click event to any element would be nice, and this is a naive way to do it
|
||||||
|
|
||||||
// use crate::element::{UiElement, MeasureContext, ProcessContext};
|
// use crate::element::{UiElement, MeasureContext, ProcessContext};
|
||||||
|
|
||||||
// pub struct Interactable<T: UiElement> {
|
// pub struct Interactable<T: UiElement> {
|
||||||
|
|
68
hui/src/element/builtin/transformer.rs
Normal file
68
hui/src/element/builtin/transformer.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use glam::{Affine2, Vec2};
|
||||||
|
use crate::{
|
||||||
|
draw::UiDrawCommand, element::{MeasureContext, ProcessContext, UiElement}, measure::Response
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Transformer {
|
||||||
|
pub transform: Affine2,
|
||||||
|
pub element: Box<dyn UiElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transformer {
|
||||||
|
pub fn new(element: Box<dyn UiElement>) -> Self {
|
||||||
|
Self {
|
||||||
|
transform: Affine2::IDENTITY,
|
||||||
|
element,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate(mut self, v: impl Into<Vec2>) -> Self {
|
||||||
|
self.transform *= Affine2::from_translation(v.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale(mut self, v: impl Into<Vec2>) -> Self {
|
||||||
|
self.transform *= Affine2::from_scale(v.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate(mut self, radians: f32) -> Self {
|
||||||
|
self.transform *= Affine2::from_angle(radians);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiElement for Transformer {
|
||||||
|
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||||
|
self.element.measure(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(&self, ctx: ProcessContext) {
|
||||||
|
ctx.draw.add(UiDrawCommand::PushTransform(self.transform));
|
||||||
|
//This is stupid:
|
||||||
|
self.element.process(ProcessContext {
|
||||||
|
measure: ctx.measure,
|
||||||
|
state: ctx.state,
|
||||||
|
layout: ctx.layout,
|
||||||
|
draw: ctx.draw,
|
||||||
|
text_measure: ctx.text_measure,
|
||||||
|
current_font: ctx.current_font,
|
||||||
|
});
|
||||||
|
ctx.draw.add(UiDrawCommand::PopTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ElementTransformExt {
|
||||||
|
fn transform(self) -> Transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UiElement + 'static> ElementTransformExt for T {
|
||||||
|
/// Wrap the element in a [`Transformer`]
|
||||||
|
///
|
||||||
|
/// This allows you to apply various transformations to the element, such as translation, rotation, or scaling\
|
||||||
|
/// Use sparingly, as this is an experimental feature and may not work as expected\
|
||||||
|
/// Transform is applied around the center of the element's bounding box.
|
||||||
|
fn transform(self) -> Transformer {
|
||||||
|
Transformer::new(Box::new(self))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue