Compare commits

...

14 commits

20 changed files with 393 additions and 336 deletions

View file

@ -9,8 +9,8 @@ use winit::{
}; };
use hui::{ use hui::{
element::{ element::{
container::Container, progress_bar::ProgressBar, fill_rect::FillRect, ElementList, UiElement container::Container, fill_rect::FillRect, progress_bar::ProgressBar, ElementList, UiElement
}, layout::{Alignment, Direction, Size}, rect::{Corners, Sides}, UiInstance }, frame::FrameRect, layout::{Alignment, Direction, Size}, rect::{Corners, Sides}, UiInstance
}; };
use hui_glium::GliumUiRenderer; use hui_glium::GliumUiRenderer;
@ -95,7 +95,7 @@ fn main() {
Box::new(Container { Box::new(Container {
gap: 5., gap: 5.,
padding: Sides::all(5.), padding: Sides::all(5.),
background: vec4(0., 0., 0., 0.5).into(), background_frame: Box::new(FrameRect::color(vec4(0., 0., 0., 0.5))),
direction: Direction::Horizontal, direction: Direction::Horizontal,
children: { children: {
let mut x: Vec<Box<dyn UiElement>> = vec![]; let mut x: Vec<Box<dyn UiElement>> = vec![];
@ -115,19 +115,18 @@ fn main() {
..Default::default() ..Default::default()
}), }),
Box::new(Container { Box::new(Container {
background: vec4(1., 0., 0., 1.).into(), background_frame: Box::new(FrameRect::color((1., 0., 0.)).with_corner_radius(Corners {
top_left: 0.,
top_right: 30.,
bottom_left: 0.,
bottom_right: 0.,
})),
padding: Sides { padding: Sides {
top: 10., top: 10.,
bottom: 20., bottom: 20.,
left: 30., left: 30.,
right: 40., right: 40.,
}, },
corner_radius: Corners {
top_left: 0.,
top_right: 30.,
bottom_left: 0.,
bottom_right: 0.,
},
children: ElementList(vec![ children: ElementList(vec![
Box::new(FillRect { Box::new(FillRect {
size: (Size::Absolute(50.), Size::Absolute(50.)).into(), size: (Size::Absolute(50.), Size::Absolute(50.)).into(),

View file

@ -1,13 +1,11 @@
use std::time::Instant; use std::time::Instant;
use hui::{ use hui::{
size,
layout::{Alignment, Direction},
element::{ element::{
container::Container, container::Container,
progress_bar::ProgressBar, progress_bar::ProgressBar,
text::Text, text::Text,
UiElementExt, UiElementExt,
}, }, frame::FrameRect, frame_rect, layout::{Alignment, Direction}, size
}; };
#[path = "../boilerplate.rs"] #[path = "../boilerplate.rs"]
@ -33,8 +31,10 @@ ui_main!{
.with_gap(5.) .with_gap(5.)
.with_padding(10.) .with_padding(10.)
.with_size(size!(450, auto)) .with_size(size!(450, auto))
.with_background((0.2, 0.2, 0.5)) .with_background(frame_rect! {
.with_corner_radius(8.) color: (0.2, 0.2, 0.5),
corner_radius: 8.
})
.with_children(|ui| { .with_children(|ui| {
if instant.elapsed().as_secs_f32() < 5. { if instant.elapsed().as_secs_f32() < 5. {
Text::default() Text::default()

View file

@ -1,141 +0,0 @@
//WARNING: THIS EXAMPLE IS EXTREMELY OUTDATED AND USES DEPRECATED API
use glam::{vec4, UVec2};
use glium::{backend::glutin::SimpleWindowBuilder, Surface};
use winit::{
event::{Event, WindowEvent},
event_loop::{EventLoopBuilder, ControlFlow}
};
use hui::{
element::{
container::Container,
text::Text, ElementList
},
layout::{Alignment, Direction, Size},
rect::{Corners, Sides},
UiInstance
};
use hui_glium::GliumUiRenderer;
fn main() {
kubi_logging::init();
let event_loop = EventLoopBuilder::new().build().unwrap();
let (_window, display) = SimpleWindowBuilder::new().build(&event_loop);
let mut hui = UiInstance::new();
let mut backend = GliumUiRenderer::new(&display);
event_loop.run(|event, window_target| {
window_target.set_control_flow(ControlFlow::Poll);
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
window_target.exit();
},
Event::AboutToWait => {
let mut frame = display.draw();
frame.clear_color_srgb(0.5, 0.5, 0.5, 0.);
let resolution = UVec2::from(display.get_framebuffer_dimensions()).as_vec2();
hui.begin();
hui.add(Container {
gap: 10.,
align: Alignment::Center.into(),
size: (Size::Relative(1.), Size::Relative(1.)).into(),
children: ElementList(vec![
Box::new(Container {
align: Alignment::Center.into(),
size: (Size::Relative(0.5), Size::Relative(0.5)).into(),
background: vec4(1., 0., 0., 1.).into(),
corner_radius: Corners {
top_left: 10.,
top_right: 20.,
bottom_left: 50.,
bottom_right: 80.
},
children: ElementList(vec![
Box::new(Container {
padding: Sides::all(20.),
direction: Direction::Horizontal,
align: Alignment::Center.into(),
size: (Size::Auto, Size::Auto).into(),
background: vec4(0.1, 0.1, 0.1, 0.5).into(),
corner_radius: Corners::all(8.),
children: ElementList(vec![
Box::new(Text {
text: "Corners".into(),
text_size: 50,
color: vec4(1., 1., 1., 1.),
..Default::default()
}),
Box::new(Text {
text: "!".into(),
text_size: 50,
color: vec4(1., 1., 0., 1.),
..Default::default()
}),
]),
..Default::default()
}),
]),
..Default::default()
}),
Box::new(Container {
gap: 10.,
direction: Direction::Horizontal,
children: ElementList(vec![
Box::new(Container {
size: (Size::Absolute(100.), Size::Absolute(100.)).into(),
background: Corners::left_right(
vec4(1., 0., 0., 1.),
vec4(0., 1., 0., 1.)
).into(),
corner_radius: Corners::all(0.),
..Default::default()
}),
Box::new(Container {
size: (Size::Absolute(100.), Size::Absolute(100.)).into(),
background: Corners::left_right(
vec4(1., 0., 0., 1.),
vec4(0., 1., 0., 1.)
).into(),
corner_radius: Corners::all(10.),
..Default::default()
}),
Box::new(Container {
size: (Size::Absolute(100.), Size::Absolute(100.)).into(),
background: Corners::left_right(
vec4(1., 0., 0., 1.),
vec4(0., 1., 0., 1.)
).into(),
corner_radius: Corners::all(20.),
..Default::default()
}),
Box::new(Container {
size: (Size::Absolute(100.), Size::Absolute(100.)).into(),
background: Corners::left_right(
vec4(1., 0., 0., 1.),
vec4(0., 1., 0., 1.)
).into(),
corner_radius: Corners::all(30.),
..Default::default()
}),
]),
..Default::default()
}),
]),
..Default::default()
}, resolution);
hui.end();
backend.update(&hui);
backend.draw(&mut frame, resolution);
frame.finish().unwrap();
}
_ => (),
}
}).unwrap();
}

View file

@ -10,7 +10,7 @@ use winit::{
use hui::{ use hui::{
element::{ element::{
container::Container, fill_rect::FillRect, spacer::Spacer, text::Text, ElementList container::Container, fill_rect::FillRect, spacer::Spacer, text::Text, ElementList
}, layout::Size, UiInstance }, frame::FrameRect, layout::Size, UiInstance
}; };
use hui_glium::GliumUiRenderer; use hui_glium::GliumUiRenderer;
@ -49,7 +49,7 @@ fn main() {
hui.add(Container { hui.add(Container {
size: (Size::Relative(1.), Size::Relative(1.)).into(), size: (Size::Relative(1.), Size::Relative(1.)).into(),
background: vec4(0.1, 0.1, 0.1, 1.).into(), background_frame: Box::new(FrameRect::color((0.1, 0.1, 0.1, 1.))),
children: elements(|elem| { children: elements(|elem| {
elem.push(Box::new(Text { elem.push(Box::new(Text {
text: "THIS LINE SHOULD BE SHARP!".into(), text: "THIS LINE SHOULD BE SHARP!".into(),

View file

@ -1,7 +1,5 @@
use hui::{ use hui::{
color, size, color, element::{container::Container, text::Text, UiElementExt}, frame::FrameRect, layout::Alignment, size
layout::Alignment,
element::{UiElementExt, container::Container, text::Text},
}; };
#[path = "../boilerplate.rs"] #[path = "../boilerplate.rs"]
@ -14,8 +12,10 @@ ui_main!(|ui, size, _| {
.with_align(Alignment::Center) .with_align(Alignment::Center)
.with_padding(5.) .with_padding(5.)
.with_gap(10.) .with_gap(10.)
.with_background(
FrameRect::color(color::WHITE)
.with_corner_radius(10.) .with_corner_radius(10.)
.with_background(color::WHITE) )
.with_children(|ui| { .with_children(|ui| {
Text::default() Text::default()
.with_text("Hello, world") .with_text("Hello, world")
@ -24,8 +24,10 @@ ui_main!(|ui, size, _| {
.add_child(ui); .add_child(ui);
Container::default() Container::default()
.with_padding((10., 20.)) .with_padding((10., 20.))
.with_background(
FrameRect::color(color::DARK_RED)
.with_corner_radius((2.5, 30., 2.5, 2.5)) .with_corner_radius((2.5, 30., 2.5, 2.5))
.with_background(color::DARK_RED) )
.with_children(|ui| { .with_children(|ui| {
Text::default() Text::default()
.with_text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.") .with_text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")

View file

@ -1,10 +1,17 @@
use glam::vec4; use glam::vec4;
use hui::{ use hui::{
color, size, size, frame_rect,
element::{container::Container, progress_bar::ProgressBar, text::Text, UiElementExt}, color,
element::{
container::Container,
progress_bar::ProgressBar,
text::Text,
UiElementExt
},
frame::FrameRect,
layout::Alignment, layout::Alignment,
rect::Corners, rect::Corners,
text::FontHandle, text::FontHandle
}; };
#[path = "../boilerplate.rs"] #[path = "../boilerplate.rs"]
@ -38,8 +45,10 @@ ui_main!(
.with_children(|ui| { .with_children(|ui| {
Container::default() Container::default()
.with_padding((10., 15.)) .with_padding((10., 15.))
.with_background(
FrameRect::color((0., 0., 0., 0.5))
.with_corner_radius(8.) .with_corner_radius(8.)
.with_background((0., 0., 0., 0.5)) )
.with_children(|ui| { .with_children(|ui| {
let flash = 1. - 0.5 * (4. * instant.elapsed().as_secs_f32()).sin().powi(2); let flash = 1. - 0.5 * (4. * instant.elapsed().as_secs_f32()).sin().powi(2);
Text::default() Text::default()
@ -61,8 +70,10 @@ ui_main!(
.with_align((Alignment::Center, Alignment::Begin)) .with_align((Alignment::Center, Alignment::Begin))
.with_padding(15.) .with_padding(15.)
.with_gap(10.) .with_gap(10.)
.with_corner_radius(8.) .with_background(frame_rect! {
.with_background((0., 0., 0., 0.5)) color: (0., 0., 0., 0.5),
corner_radius: 8.,
})
.with_children(|ui| { .with_children(|ui| {
Text::default() Text::default()
.with_text("Did you know?") .with_text("Did you know?")
@ -100,8 +111,10 @@ ui_main!(
.with_children(|ui| { .with_children(|ui| {
Container::default() Container::default()
.with_padding(10.) .with_padding(10.)
.with_background(
FrameRect::color((0., 0., 0., 0.5))
.with_corner_radius(8.) .with_corner_radius(8.)
.with_background((0., 0., 0., 0.5)) )
.with_children(|ui| { .with_children(|ui| {
Text::default() Text::default()
.with_text("Level 5") .with_text("Level 5")

View file

@ -7,11 +7,7 @@ use hui::{
text::Text, text::Text,
transformer::ElementTransformExt, transformer::ElementTransformExt,
UiElementExt UiElementExt
}, }, frame::FrameRect, frame_rect, layout::Alignment, rect::Corners, size, text::FontHandle
layout::Alignment,
rect::Corners,
text::FontHandle,
size,
}; };
#[path = "../boilerplate.rs"] #[path = "../boilerplate.rs"]
@ -41,8 +37,10 @@ ui_main!(
.with_align((Alignment::Center, Alignment::Begin)) .with_align((Alignment::Center, Alignment::Begin))
.with_padding(15.) .with_padding(15.)
.with_gap(10.) .with_gap(10.)
.with_corner_radius(8.) .with_background(frame_rect! {
.with_background((0., 0., 0., 0.5)) color: (0., 0., 0., 0.5),
corner_radius: 8.
})
.with_children(|ui| { .with_children(|ui| {
Text::default() Text::default()
.with_text("Did you know?") .with_text("Did you know?")

View file

@ -1,15 +1,16 @@
use hui::{ use hui::{
draw::TextureFormat, draw::TextureFormat,
signal::Signal,
layout::{Alignment, Direction},
element::{ element::{
container::Container,
text::Text,
image::Image,
br::Break, br::Break,
container::Container,
image::Image,
slider::Slider, slider::Slider,
text::Text,
UiElementExt, UiElementExt,
}, },
frame::FrameRect,
layout::{Alignment, Direction},
signal::Signal,
size, size,
}; };

View file

@ -26,7 +26,7 @@ document-features = "0.2"
derive_setters = "0.1" derive_setters = "0.1"
derive_more = "0.99" derive_more = "0.99"
tinyset = "0.4" tinyset = "0.4"
enum_dispatch = "0.3" #enum_dispatch = "0.3"
[features] [features]
default = ["builtin_elements", "builtin_font", "pixel_perfect_text"] default = ["builtin_elements", "builtin_font", "pixel_perfect_text"]

View file

@ -3,6 +3,7 @@ use crate::rect::Corners;
//TODO uneven corners (separate width/height for each corner) //TODO uneven corners (separate width/height for each corner)
/// Calculate the number of points based on the maximum corner radius
fn point_count(corners: Corners<f32>) -> NonZeroU16 { fn point_count(corners: Corners<f32>) -> NonZeroU16 {
//Increase for higher quality //Increase for higher quality
const VTX_PER_CORER_RADIUS_PIXEL: f32 = 0.5; const VTX_PER_CORER_RADIUS_PIXEL: f32 = 0.5;
@ -11,19 +12,31 @@ fn point_count(corners: Corners<f32>) -> NonZeroU16 {
).unwrap() ).unwrap()
} }
/// Low-level options for rendering rounded corners
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct RoundedCorners { pub struct RoundedCorners {
/// Corner radius of each corner
pub radius: Corners<f32>, pub radius: Corners<f32>,
/// Number of points to use for each corner
///
/// This value affects all corners, regardless of their individual radius
pub point_count: NonZeroU16, pub point_count: NonZeroU16,
} }
impl From<Corners<f32>> for RoundedCorners { impl From<Corners<f32>> for RoundedCorners {
/// Create a new `RoundedCorners` from [`Corners<f32>`](crate::rect::Corners)
///
/// Point count will be calculated automatically based on the maximum radius
fn from(radius: Corners<f32>) -> Self { fn from(radius: Corners<f32>) -> Self {
Self::from_radius(radius) Self::from_radius(radius)
} }
} }
impl RoundedCorners { impl RoundedCorners {
/// Create a new `RoundedCorners` from [`Corners<f32>`](crate::rect::Corners)
///
/// Point count will be calculated automatically based on the maximum radius
pub fn from_radius(radius: Corners<f32>) -> Self { pub fn from_radius(radius: Corners<f32>) -> Self {
Self { Self {
radius, radius,

View file

@ -3,18 +3,13 @@
use derive_setters::Setters; use derive_setters::Setters;
use glam::{Vec2, vec2}; use glam::{Vec2, vec2};
use crate::{ use crate::{
draw::{ImageHandle, RoundedCorners, UiDrawCommand},
element::{ElementList, MeasureContext, ProcessContext, UiElement}, element::{ElementList, MeasureContext, ProcessContext, UiElement},
layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d}, layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d},
frame::{Frame, FrameRect},
measure::{Hints, Response}, measure::{Hints, Response},
rect::{Corners, FillColor, Sides}, rect::{Sides, FillColor},
}; };
// pub struct Border {
// pub color: Vec4,
// pub width: f32,
// }
//XXX: add Order/Direction::Forward/Reverse or sth? //XXX: add Order/Direction::Forward/Reverse or sth?
//TODO: clip children flag //TODO: clip children flag
//TODO: borders //TODO: borders
@ -54,26 +49,8 @@ pub struct Container {
#[setters(into)] #[setters(into)]
pub align: Alignment2d, pub align: Alignment2d,
/// Background color of the container\ #[setters(skip)]
/// pub background_frame: Box<dyn Frame>,
/// If the container has a background texture, it will be multiplied by this color
#[setters(into)]
pub background: FillColor,
/// Background texture of the 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<ImageHandle>,
/// Corner radius of the background rectangle
#[setters(into)]
pub corner_radius: Corners<f32>,
/// Set this to `true` to allow the elements wrap automatically /// Set this to `true` to allow the elements wrap automatically
/// ///
@ -93,6 +70,11 @@ impl Container {
self.children.0.extend(ElementList::from_callback(ui).0); self.children.0.extend(ElementList::from_callback(ui).0);
self self
} }
pub fn with_background(mut self, frame: impl Frame + 'static) -> Self {
self.background_frame = Box::new(frame);
self
}
} }
impl Default for Container { impl Default for Container {
@ -103,11 +85,9 @@ impl Default for Container {
gap: 0., gap: 0.,
padding: Sides::all(0.), padding: Sides::all(0.),
align: Alignment2d::default(), align: Alignment2d::default(),
background: FillColor::transparent(), background_frame: Box::<FrameRect>::default(),
background_image: None,
children: ElementList(Vec::new()),
wrap: false, wrap: false,
corner_radius: Corners::all(0.), children: ElementList(Vec::new()),
} }
} }
} }
@ -328,18 +308,20 @@ impl UiElement for Container {
let mut position = ctx.layout.position; let mut position = ctx.layout.position;
//background //background
if !self.background.is_transparent() { // if !self.background.is_transparent() {
let corner_colors = self.background.corners(); // let corner_colors = self.background.corners();
ctx.draw.add(UiDrawCommand::Rectangle { // ctx.draw.add(UiDrawCommand::Rectangle {
position, // position,
size: ctx.measure.size, // size: ctx.measure.size,
color: corner_colors, // color: corner_colors,
texture: self.background_image, // texture: self.background_image,
rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({ // rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({
RoundedCorners::from_radius(self.corner_radius) // RoundedCorners::from_radius(self.corner_radius)
}), // }),
}); // });
} // }
self.background_frame.draw(ctx.draw, ctx.layout.position, ctx.measure.size);
//padding //padding
position += vec2(self.padding.left, self.padding.top); position += vec2(self.padding.left, self.padding.top);

View file

@ -16,6 +16,8 @@ use crate::{
//TODO: use state for slider? //TODO: use state for slider?
// ^ useful if the user only hanldes the drag end event or has large step sizes with relative mode // ^ useful if the user only hanldes the drag end event or has large step sizes with relative mode
//TODO: adopt frame api here
/// Follow mode for the slider /// Follow mode for the slider
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum SliderFollowMode { pub enum SliderFollowMode {

View file

@ -1,67 +1,13 @@
pub mod point;
pub mod layer;
use glam::Vec2; use glam::Vec2;
use layer::{FrameLayer, FrameLayerImpl};
use crate::draw::UiDrawCommandList; use crate::draw::UiDrawCommandList;
///XXX: this is not used yet, and also kinda a mess, simplify? pub mod point;
///Maybe limit to a single layer? (aka `Frame` will be just one of the options) mod rect;
///Because currently, this is just a duplicate of the dormal draw command system, but with a different name... pub mod stack;
///Then, there's no need for the positioning stuff too, which is a bit overkill and is kinda code duplication too! mod impls;
///aka Frame::Rectangle, Frame::NinePatch, ...
/// A frame, which can contain multiple layers pub use rect::FrameRect;
///
/// Use these to construct complex backgrounds
#[derive(Default, Clone)]
pub struct Frame {
/// Layers of the frame
layers: Vec<FrameLayer>
}
impl<T: Into<FrameLayer>> From<T> for Frame { pub trait Frame {
fn from(layer: T) -> Self { fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
let mut frame = Self::default();
frame.add(layer.into());
frame
}
}
impl Frame {
/// Get the layer with the given index
#[inline]
pub fn layer(&self, index: usize) -> Option<&FrameLayer> {
self.layers.get(index)
}
/// Get a mutable reference to the layer with the given index
#[inline]
pub fn layer_mut(&mut self, index: usize) -> Option<&mut FrameLayer> {
self.layers.get_mut(index)
}
/// Add a layer to the frame
#[inline]
pub fn add(&mut self, layer: impl Into<FrameLayer>) -> &mut Self {
self.layers.push(layer.into());
self
}
/// Add a layer to the back of the frame
#[inline]
pub fn add_back(&mut self, layer: impl Into<FrameLayer>) -> &mut Self {
self.layers.insert(0, layer.into());
self
}
#[inline]
pub fn finish(&mut self) -> Self {
self.clone()
}
pub(crate) fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
for layer in &self.layers {
layer.draw(draw, position, parent_size);
}
}
} }

125
hui/src/frame/impls.rs Normal file
View file

@ -0,0 +1,125 @@
use glam::{Vec2, Vec3, Vec4};
use super::Frame;
use crate::{
color,
draw::{ImageHandle, UiDrawCommand, UiDrawCommandList},
rect::{Corners, FillColor},
};
impl Frame for ImageHandle {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
draw.add(UiDrawCommand::Rectangle {
position,
size: parent_size,
color: color::WHITE.into(),
texture: Some(*self),
rounded_corners: None,
})
}
}
impl Frame for FillColor {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
draw.add(UiDrawCommand::Rectangle {
position,
size: parent_size,
color: self.corners(),
texture: None,
rounded_corners: None,
})
}
}
// impl for various types resembling colors
// Corners (RGBA):
impl Frame for Corners<Vec4> {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for (Vec4, Vec4, Vec4, Vec4) {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for ((f32, f32, f32, f32), (f32, f32, f32, f32), (f32, f32, f32, f32), (f32, f32, f32, f32)) {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for [[f32; 4]; 4] {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
// Corners (RGB):
impl Frame for Corners<Vec3> {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for (Vec3, Vec3, Vec3, Vec3) {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for ((f32, f32, f32), (f32, f32, f32), (f32, f32, f32), (f32, f32, f32)) {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for [[f32; 3]; 4] {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
// RGBA:
impl Frame for Vec4 {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for (f32, f32, f32, f32) {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for [f32; 4] {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
// RGB:
impl Frame for Vec3 {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for (f32, f32, f32) {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}
impl Frame for [f32; 3] {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
}
}

View file

@ -43,14 +43,14 @@ impl FramePoint {
/// Center of the frame axis /// Center of the frame axis
pub const CENTER: Self = Self { pub const CENTER: Self = Self {
absolute: 0.5, absolute: 0.0,
relative: 0.0, relative: 0.5,
}; };
/// End of the frame axis /// End of the frame axis
pub const END: Self = Self { pub const END: Self = Self {
absolute: 1.0, absolute: 0.0,
relative: 0.0, relative: 1.0,
}; };
/// Create a new absolutely positioned `FramePoint` /// Create a new absolutely positioned `FramePoint`

View file

@ -1,25 +1,13 @@
use glam::Vec2; use glam::Vec2;
use enum_dispatch::enum_dispatch;
use crate::{ use crate::{
color, color,
draw::{ImageHandle, RoundedCorners, UiDrawCommand, UiDrawCommandList}, draw::{ImageHandle, RoundedCorners, UiDrawCommand, UiDrawCommandList},
rect::{Corners, FillColor}, rect::{Corners, FillColor},
}; };
use super::point::FramePoint2d; use super::{Frame, point::FramePoint2d};
#[enum_dispatch]
pub(crate) trait FrameLayerImpl {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[enum_dispatch(FrameLayerImpl)] pub struct FrameRect {
pub enum FrameLayer {
Rect(RectFrame),
}
#[derive(Clone, Copy)]
pub struct RectFrame {
/// Background color of the frame\ /// Background color of the frame\
/// ///
/// If the container has a background texture, it will be multiplied by this color /// If the container has a background texture, it will be multiplied by this color
@ -44,29 +32,35 @@ pub struct RectFrame {
pub corner_radius: Corners<f32>, pub corner_radius: Corners<f32>,
} }
impl<T: Into<FillColor>> From<T> for RectFrame { // impl<T: Into<FillColor>> From<T> for FrameRect {
fn from(color: T) -> Self { // fn from(color: T) -> Self {
Self::from_color(color) // Self::from_color(color)
// }
// }
impl From<FillColor> for FrameRect {
fn from(color: FillColor) -> Self {
Self::color(color)
} }
} }
impl RectFrame { impl From<ImageHandle> for FrameRect {
pub fn from_color(color: impl Into<FillColor>) -> Self { fn from(image: ImageHandle) -> Self {
Self::image(image)
}
}
impl FrameRect {
/// Create a new [`FrameRect`] with the given color
pub fn color(color: impl Into<FillColor>) -> Self {
Self { Self {
color: color.into(), color: color.into(),
..Self::default() ..Self::default()
} }
} }
pub fn from_color_rounded(color: impl Into<FillColor>, corner_radius: impl Into<Corners<f32>>) -> Self { /// Create a new [`FrameRect`] with the given image
Self { pub fn image(image: ImageHandle) -> Self {
color: color.into(),
corner_radius: corner_radius.into(),
..Self::default()
}
}
pub fn from_image(image: ImageHandle) -> Self {
Self { Self {
color: color::WHITE.into(), color: color::WHITE.into(),
image: Some(image), image: Some(image),
@ -74,7 +68,8 @@ impl RectFrame {
} }
} }
pub fn from_color_image(color: impl Into<FillColor>, image: ImageHandle) -> Self { /// Create a new [`FrameRect`] with the given color and image
pub fn color_image(color: impl Into<FillColor>, image: ImageHandle) -> Self {
Self { Self {
color: color.into(), color: color.into(),
image: Some(image), image: Some(image),
@ -82,17 +77,18 @@ impl RectFrame {
} }
} }
pub fn from_color_image_rounded(color: impl Into<FillColor>, image: ImageHandle, corner_radius: impl Into<Corners<f32>>) -> Self { /// Set the corner radius of the [`FrameRect`]
pub fn with_corner_radius(self, radius: impl Into<Corners<f32>>) -> Self {
Self { Self {
color: color.into(), corner_radius: radius.into(),
image: Some(image), ..self
corner_radius: corner_radius.into(),
..Self::default()
} }
} }
/// Inset the rectangle by the given amount //TODO: deprecate and replace
pub fn inset(self, inset: f32) -> Self {
/// Inset the rectangle by the given amount in pixels
pub fn with_inset(self, inset: f32) -> Self {
Self { Self {
top_left: self.top_left + Vec2::splat(inset).into(), top_left: self.top_left + Vec2::splat(inset).into(),
bottom_right: self.bottom_right - Vec2::splat(inset).into(), bottom_right: self.bottom_right - Vec2::splat(inset).into(),
@ -101,10 +97,10 @@ impl RectFrame {
} }
} }
impl Default for RectFrame { impl Default for FrameRect {
fn default() -> Self { fn default() -> Self {
Self { Self {
color: FillColor::default(), color: FillColor::transparent(),
image: None, image: None,
top_left: FramePoint2d::TOP_LEFT, top_left: FramePoint2d::TOP_LEFT,
bottom_right: FramePoint2d::BOTTOM_RIGHT, bottom_right: FramePoint2d::BOTTOM_RIGHT,
@ -113,7 +109,7 @@ impl Default for RectFrame {
} }
} }
impl FrameLayerImpl for RectFrame { impl Frame for FrameRect {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) { fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
//TODO: handle bottom_right < top_left //TODO: handle bottom_right < top_left
let top_left = self.top_left.resolve(parent_size); let top_left = self.top_left.resolve(parent_size);

22
hui/src/frame/stack.rs Normal file
View file

@ -0,0 +1,22 @@
use glam::Vec2;
use crate::draw::UiDrawCommandList;
use super::Frame;
pub struct FrameStack(pub Box<dyn Frame>, pub Box<dyn Frame>);
impl Frame for FrameStack {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
self.0.draw(draw, position, parent_size);
self.1.draw(draw, position, parent_size);
}
}
pub trait FrameStackExt: Frame {
fn stack(self, other: impl Frame + 'static) -> FrameStack;
}
impl<T: Frame + 'static> FrameStackExt for T {
fn stack(self, other: impl Frame + 'static) -> FrameStack {
FrameStack(Box::new(self), Box::new(other))
}
}

View file

@ -60,3 +60,87 @@ macro_rules! size {
} }
}; };
} }
/// Helper macro for constructing a `FrameRect`
#[macro_export]
macro_rules! frame_rect {
{} => {
$crate::frame::FrameRect::default()
};
// () => {
// $crate::frame::FrameRect::default()
// };
($expr:expr) => {
{
let _frame_rect: $crate::frame::FrameRect = $crate::frame::FrameRect::from($expr);
_frame_rect
}
};
($image:expr, $color:expr) => {
$crate::frame::FrameRect::color_image($color, $image)
};
{$($ident:ident : $expr:expr),+$(,)?} => {
{
// ensure all identifiers are unique
#[allow(non_upper_case_globals)]
{$(const $ident: () = ();)+}
// construct the FrameRect
{
let mut frame_rect = $crate::frame::FrameRect::default();
let mut _color_is_set = false;
let mut _image_is_set = false;
$(
{
frame_rect.$ident = ($expr).into();
if stringify!($ident) == "image" {
_image_is_set = true;
}
if stringify!($ident) == "color" {
_color_is_set = true;
}
}
)+
if frame_rect.image.is_some() && _image_is_set && !_color_is_set {
frame_rect.color = (1., 1., 1., 1.).into();
}
frame_rect
}
}
};
// {$from:expr, $($ident:ident : $expr:expr),+$(,)?} => {
// {
// // ensure all identifiers are unique
// #[allow(non_upper_case_globals)]
// {
// $(
// const $ident: () = ();
// )+
// }
// // construct the FrameRect
// {
// let mut _frame_rect: $crate::frame::FrameRect = ($from).into();
// $(
// let $ident = ($expr).into();
// _frame_rect.$ident = $ident;
// )+
// _frame_rect
// }
// }
// };
}
// #[allow(unused)]
// fn test() {
// // let _ = frame_rect!(5, 6);
// let _ = frame_rect! {
// color: (0.2, 0.2, 0.3, 1.),
// corner_radius: 5.,
// };
// }

View file

@ -153,6 +153,17 @@ impl From<[[f32; 4]; 4]> for FillColor {
} }
} }
impl From<Corners<Vec3>> for FillColor {
fn from(corners: Corners<Vec3>) -> Self {
Self(Corners {
top_left: corners.top_left.extend(1.),
top_right: corners.top_right.extend(1.),
bottom_left: corners.bottom_left.extend(1.),
bottom_right: corners.bottom_right.extend(1.),
})
}
}
impl From<(Vec3, Vec3, Vec3, Vec3)> for FillColor { impl From<(Vec3, Vec3, Vec3, Vec3)> for FillColor {
fn from((top_left, top_right, bottom_left, bottom_right): (Vec3, Vec3, Vec3, Vec3)) -> Self { fn from((top_left, top_right, bottom_left, bottom_right): (Vec3, Vec3, Vec3, Vec3)) -> Self {
Self(Corners { Self(Corners {

View file

@ -8,6 +8,7 @@ mod font;
mod ftm; mod ftm;
mod stack; mod stack;
/// Built-in font handle
#[cfg(feature="builtin_font")] #[cfg(feature="builtin_font")]
pub use font::BUILTIN_FONT; pub use font::BUILTIN_FONT;
pub use font::FontHandle; pub use font::FontHandle;
@ -17,7 +18,7 @@ use ftm::FontTextureManager;
use ftm::GlyphCacheEntry; use ftm::GlyphCacheEntry;
use stack::FontStack; use stack::FontStack;
pub struct TextRenderer { pub(crate) struct TextRenderer {
manager: FontManager, manager: FontManager,
ftm: FontTextureManager, ftm: FontTextureManager,
stack: FontStack, stack: FontStack,
@ -63,15 +64,18 @@ impl Default for TextRenderer {
} }
} }
/// Size of measured text
pub struct TextMeasureResponse { pub struct TextMeasureResponse {
pub max_width: f32, pub max_width: f32,
pub height: f32, pub height: f32,
} }
/// Context for measuring text
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct TextMeasure<'a>(&'a TextRenderer); pub struct TextMeasure<'a>(&'a TextRenderer);
impl<'a> TextMeasure<'a> { impl<'a> TextMeasure<'a> {
/// Measure the given string of text with the given font and size
pub fn measure(&self, font: FontHandle, size: u16, text: &str) -> TextMeasureResponse { pub fn measure(&self, font: FontHandle, size: u16, text: &str) -> TextMeasureResponse {
use fontdue::layout::{Layout, CoordinateSystem, TextStyle}; use fontdue::layout::{Layout, CoordinateSystem, TextStyle};
let mut layout = Layout::new(CoordinateSystem::PositiveYDown); let mut layout = Layout::new(CoordinateSystem::PositiveYDown);