mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-21 22:58: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 fontdue::layout::{Layout, CoordinateSystem, TextStyle};
|
||||
use glam::{Vec2, Vec4, vec2};
|
||||
use glam::{vec2, Vec2, Affine2, Vec4};
|
||||
|
||||
//TODO: circle draw command
|
||||
|
||||
|
@ -51,6 +51,10 @@ pub enum UiDrawCommand {
|
|||
///Font handle to use
|
||||
font: FontHandle,
|
||||
},
|
||||
/// Push a transformation matrix to the stack
|
||||
PushTransform(Affine2),
|
||||
/// Pop a transformation matrix from the stack
|
||||
PopTransform,
|
||||
}
|
||||
|
||||
/// List of draw commands
|
||||
|
@ -91,9 +95,45 @@ pub struct UiDrawCall {
|
|||
impl UiDrawCall {
|
||||
/// 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 {
|
||||
let mut trans_stack = Vec::new();
|
||||
let mut draw_call = UiDrawCall::default();
|
||||
for command in &draw_commands.commands {
|
||||
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 } => {
|
||||
let uvs = texture
|
||||
.map(|x| atlas.get_uv(x))
|
||||
|
@ -257,6 +297,8 @@ impl UiDrawCall {
|
|||
feature = "pixel_perfect_text",
|
||||
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)..] {
|
||||
vtx.position = vtx.position.round()
|
||||
}
|
||||
|
@ -265,6 +307,7 @@ impl UiDrawCall {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "pixel_perfect")]
|
||||
draw_call.vertices.iter_mut().for_each(|v| {
|
||||
v.position = v.position.round()
|
||||
|
|
|
@ -12,3 +12,8 @@ pub mod progress_bar;
|
|||
|
||||
#[cfg(feature = "builtin_elements")]
|
||||
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};
|
||||
|
||||
// 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