Compare commits

...

10 commits

27 changed files with 342 additions and 309 deletions

View file

@ -32,7 +32,7 @@
.with_align(Alignment::Center)
.with_padding(5.)
.with_gap(10.)
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0.5, 0.5, 0.5, 1.),
corner_radius: 10.,
})
@ -44,7 +44,7 @@
.add_child(ui);
Container::default()
.with_padding((10., 20.))
.with_background(frame_rect! {
.with_background(rect_frame! {
color: color::DARK_RED,
corner_radius: (2.5, 30., 2.5, 2.5),
})

View file

@ -9,8 +9,8 @@ use winit::{
};
use hui::{
element::{
container::Container, fill_rect::FillRect, progress_bar::ProgressBar, ElementList, UiElement
}, frame::FrameRect, layout::{Alignment, Direction, Size}, rect::{Corners, Sides}, UiInstance
container::Container, frame_view::FrameView, progress_bar::ProgressBar, ElementList, UiElement
}, frame::RectFrame, layout::{Alignment, Direction, Size}, rect::{Corners, Sides}, UiInstance
};
use hui_glium::GliumUiRenderer;
@ -71,11 +71,11 @@ fn main() {
padding: Sides::all(5.),
gap: 10.,
children: ElementList(vec![
Box::new(FillRect {
Box::new(FrameView {
size: (Size::Relative(0.5), Size::Absolute(30.)).into(),
frame: Box::new(vec4(0.75, 0., 0., 1.)),
}),
Box::new(FillRect {
Box::new(FrameView {
size: (Size::Relative(z / 2. + 0.5), Size::Absolute(30.)).into(),
frame: Box::new(Corners::left_right(
vec4(1., 0., 0., 1.),
@ -85,19 +85,19 @@ fn main() {
]),
..Default::default()
}),
Box::new(FillRect {
Box::new(FrameView {
size: (Size::Relative(z / 2. + 0.5), Size::Absolute(30.)).into(),
frame: Box::new(vec4(0., 0.75, 0., 1.)),
}),
Box::new(Container {
gap: 5.,
padding: Sides::all(5.),
background_frame: Box::new(FrameRect::color(vec4(0., 0., 0., 0.5))),
background_frame: Box::new(RectFrame::color(vec4(0., 0., 0., 0.5))),
direction: Direction::Horizontal,
children: {
let mut x: Vec<Box<dyn UiElement>> = vec![];
for i in 0..10 {
x.push(Box::new(FillRect {
x.push(Box::new(FrameView {
size: (Size::Absolute(50.), Size::Absolute(50.)).into(),
frame: Box::new(if i == 1 {
vec4(0.75, 0.75, 0.75, 0.75)
@ -111,7 +111,7 @@ fn main() {
..Default::default()
}),
Box::new(Container {
background_frame: Box::new(FrameRect::color((1., 0., 0.)).with_corner_radius(Corners {
background_frame: Box::new(RectFrame::color((1., 0., 0.)).with_corner_radius(Corners {
top_left: 0.,
top_right: 30.,
bottom_left: 0.,
@ -124,7 +124,7 @@ fn main() {
right: 40.,
},
children: ElementList(vec![
Box::new(FillRect {
Box::new(FrameView {
size: (Size::Absolute(50.), Size::Absolute(50.)).into(),
frame: Box::new(vec4(1., 1., 1., 0.75)),
}),

View file

@ -5,7 +5,7 @@ use hui::{
progress_bar::ProgressBar,
text::Text,
UiElementExt,
}, frame::FrameRect, frame_rect, layout::{Alignment, Direction}, size
}, frame::RectFrame, rect_frame, layout::{Alignment, Direction}, size
};
#[path = "../boilerplate.rs"]
@ -31,7 +31,7 @@ ui_main!{
.with_gap(5.)
.with_padding(10.)
.with_size(size!(450, auto))
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0.2, 0.2, 0.5),
corner_radius: 8.
})
@ -43,11 +43,11 @@ ui_main!{
.add_child(ui);
ProgressBar::default()
.with_value(mom_ratio)
.with_background(frame_rect! {
.with_background(rect_frame! {
color: color::BLACK,
corner_radius: 0.125 * ProgressBar::DEFAULT_HEIGHT
})
.with_foreground(frame_rect! {
.with_foreground(rect_frame! {
color: color::BLUE,
corner_radius: 0.125 * ProgressBar::DEFAULT_HEIGHT
})

View file

@ -9,8 +9,8 @@ use winit::{
};
use hui::{
element::{
container::Container, fill_rect::FillRect, spacer::Spacer, text::Text, ElementList
}, frame::FrameRect, layout::Size, UiInstance
container::Container, frame_view::FrameView, spacer::Spacer, text::Text, ElementList
}, frame::RectFrame, layout::Size, UiInstance
};
use hui_glium::GliumUiRenderer;
@ -49,7 +49,7 @@ fn main() {
hui.add(Container {
size: (Size::Relative(1.), Size::Relative(1.)).into(),
background_frame: Box::new(FrameRect::color((0.1, 0.1, 0.1, 1.))),
background_frame: Box::new(RectFrame::color((0.1, 0.1, 0.1, 1.))),
children: elements(|elem| {
elem.push(Box::new(Text {
text: "THIS LINE SHOULD BE SHARP!".into(),
@ -71,11 +71,11 @@ fn main() {
..Default::default()
}));
}
elem.push(Box::new(FillRect {
elem.push(Box::new(FrameView {
size: (Size::Relative(1.), Size::Absolute(10.)).into(),
frame: Box::new(vec4(0., 0., 1., 1.)),
}));
elem.push(Box::new(FillRect {
elem.push(Box::new(FrameView {
size: (Size::Relative(1.), Size::Absolute(10.)).into(),
frame: Box::new(vec4(1., 1., 0., 1.)),
}));
@ -86,11 +86,11 @@ fn main() {
..Default::default()
}));
if instant.elapsed().as_secs() & 1 != 0 {
elem.push(Box::new(FillRect {
elem.push(Box::new(FrameView {
size: (Size::Relative(1.), Size::Absolute(10.)).into(),
frame: Box::new(vec4(1., 0., 0., 1.)),
}));
elem.push(Box::new(FillRect {
elem.push(Box::new(FrameView {
size: (Size::Relative(1.), Size::Absolute(10.)).into(),
frame: Box::new(vec4(0., 0., 0., 1.)),
}));

View file

@ -1,7 +1,7 @@
use hui::{
color, size, frame_rect,
color, size, rect_frame,
element::{container::Container, text::Text, UiElementExt},
frame::FrameRect,
frame::RectFrame,
layout::Alignment,
};
@ -15,7 +15,7 @@ ui_main!(|ui, size, _| {
.with_align(Alignment::Center)
.with_padding(5.)
.with_gap(10.)
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0.5, 0.5, 0.5, 1.),
corner_radius: 10.,
})
@ -27,7 +27,7 @@ ui_main!(|ui, size, _| {
.add_child(ui);
Container::default()
.with_padding((10., 20.))
.with_background(frame_rect! {
.with_background(rect_frame! {
color: color::DARK_RED,
corner_radius: (2.5, 30., 2.5, 2.5),
})

View file

@ -1,6 +1,6 @@
use glam::vec4;
use hui::{
size, frame_rect,
size, rect_frame,
color,
element::{
container::Container,
@ -8,7 +8,7 @@ use hui::{
text::Text,
UiElementExt
},
frame::FrameRect,
frame::RectFrame,
layout::Alignment,
rect::Corners,
text::FontHandle
@ -45,7 +45,7 @@ ui_main!(
.with_children(|ui| {
Container::default()
.with_padding((10., 15.))
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0., 0., 0., 0.5),
corner_radius: 8.,
})
@ -70,7 +70,7 @@ ui_main!(
.with_align((Alignment::Center, Alignment::Begin))
.with_padding(15.)
.with_gap(10.)
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0., 0., 0., 0.5),
corner_radius: 8.,
})
@ -111,7 +111,7 @@ ui_main!(
.with_children(|ui| {
Container::default()
.with_padding(10.)
.with_background(frame_rect!{
.with_background(rect_frame!{
color: (0., 0., 0., 0.5),
corner_radius: 8.,
})

View file

@ -7,7 +7,7 @@ use hui::{
text::Text,
transformer::ElementTransformExt,
UiElementExt
}, frame::FrameRect, frame_rect, layout::Alignment, rect::Corners, size, text::FontHandle
}, frame::RectFrame, rect_frame, layout::Alignment, rect::Corners, size, text::FontHandle
};
#[path = "../boilerplate.rs"]
@ -37,7 +37,7 @@ ui_main!(
.with_align((Alignment::Center, Alignment::Begin))
.with_padding(15.)
.with_gap(10.)
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0., 0., 0., 0.5),
corner_radius: 8.
})

View file

@ -2,9 +2,9 @@ use std::time::Instant;
use hui::{
color, element::{
container::Container,
fill_rect::FillRect,
frame_view::FrameView,
UiElementExt
}, frame_rect, layout::{Alignment, Direction}, size
}, rect_frame, layout::{Alignment, Direction}, size
};
#[path = "../boilerplate.rs"]
@ -28,9 +28,9 @@ ui_main!(
.with_wrap(true)
.with_children(|ui| {
for i in 0..10 {
FillRect::default()
FrameView::default()
.with_size(size!((40 + i * 10)))
.with_frame(frame_rect! {
.with_frame(rect_frame! {
color: color::DARK_RED,
corner_radius: 8.
})

View file

@ -3,7 +3,7 @@ use hui::{
color,
element::{
container::Container,
fill_rect::FillRect,
frame_view::FrameView,
slider::Slider,
text::Text,
UiElementExt
@ -54,7 +54,7 @@ ui_main!(
.add_child(ui);
})
.add_child(ui);
FillRect::default()
FrameView::default()
.with_size(size!(600, 75))
.with_frame(NinePatchFrame::from_asset(*asset).with_color(color::GREEN))
.add_child(ui);
@ -62,7 +62,7 @@ ui_main!(
.with_color(color::BLACK)
.with_text_size(32)
.add_child(ui);
FillRect::default()
FrameView::default()
.with_size(size!(700, 50))
.with_frame(NinePatchFrame::from_asset(*asset).with_color((
(1., 0., 1.),

View file

@ -6,7 +6,7 @@ use hui::{
layout::{Alignment, Direction},
element::{
container::Container,
fill_rect::FillRect,
frame_view::FrameView,
image::Image,
text::Text,
UiElementExt
@ -62,7 +62,7 @@ ui_main!(
.add_child(ui);
})
.add_child(ui);
FillRect::default()
FrameView::default()
.with_size(size!(100%, 1))
.with_frame(color::rgb_hex(0x2d2d30))
.add_child(ui);
@ -75,7 +75,7 @@ ui_main!(
.with_size(size!(54, 100%))
.with_background(color::rgb_hex(0x343334))
.add_child(ui);
FillRect::default()
FrameView::default()
.with_size(size!(1, 100%))
.with_frame(color::rgb_hex(0x2d2d30))
.add_child(ui);

View file

@ -58,7 +58,7 @@ pixel_perfect_text = []
## Enable all built-in elements
el_all = [
"el_container",
"el_fill_rect",
"el_frame_view",
"el_spacer",
"el_br",
"el_text",
@ -72,8 +72,8 @@ el_all = [
## Enable the built-in `Container` element
el_container = []
## Enable the built-in `FillRect` element
el_fill_rect = []
## Enable the built-in `FrameView` element
el_frame_view = []
## Enable the built-in `Spacer` element
el_spacer = []

View file

@ -38,6 +38,8 @@ pub struct TextureAtlasMeta<'a> {
/// Texture handle, stores the internal index of a texture within the texture atlas and can be cheaply copied.
///
/// Please note that dropping a handle does not deallocate the texture from the atlas, you must do it manually.
///
/// Only valid for the `UiInstance` that created it.\
/// Using it with other instances may result in panics or unexpected behavior.
///

View file

@ -1,11 +1,11 @@
//! element API and built-in elements like `Container`, `Button`, `Text`, etc.
use std::any::Any;
use crate::{
draw::{atlas::ImageCtx, UiDrawCommandList},
input::InputCtx,
layout::{LayoutInfo, Size2d},
measure::Response,
rect::Rect,
signal::SignalStore,
state::StateRepo,
text::{FontHandle, TextMeasure},
@ -17,8 +17,8 @@ pub use builtin::*;
/// Context for the `Element::measure` function
pub struct MeasureContext<'a> {
pub state: &'a StateRepo,
pub layout: &'a LayoutInfo,
pub state: &'a StateRepo,
pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle,
pub images: ImageCtx<'a>,
@ -29,9 +29,9 @@ pub struct MeasureContext<'a> {
/// Context for the `Element::process` function
pub struct ProcessContext<'a> {
pub measure: &'a Response,
pub state: &'a mut StateRepo,
pub layout: &'a LayoutInfo,
pub draw: &'a mut UiDrawCommandList,
pub state: &'a mut StateRepo,
pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle,
pub images: ImageCtx<'a>,
@ -50,27 +50,6 @@ pub trait UiElement {
/// You should implement this function whenever possible, otherwise some features may not work at all, such as the `Remaining` size
fn size(&self) -> Option<Size2d> { None }
/// Get the unique id used for internal state management\
/// This value must be unique for each instance of the element
///
/// If the element is stateless, this function should return `None`
fn state_id(&self) -> Option<u64> { None }
/// Check if the element has state.\
/// Should not be overridden
fn is_stateful(&self) -> bool { self.state_id().is_some() }
/// Check if the element has no state\
/// Should not be overridden
fn is_stateless(&self) -> bool { !self.is_stateful() }
/// Initialize the state of the element\
/// This function should be called exactly once during the lifetime of the element,
/// or if the state gets reset
///
/// This function will not get called for stateless elements
fn init_state(&self) -> Option<Box<dyn Any>> { None }
/// Measure step, guaranteed to be called before the `process` step\
/// May be called multiple times per single frame, so it should not contain any expensive calls\
/// This function may not mutate any state.\
@ -108,7 +87,7 @@ pub trait UiElementExt: UiElement {
fn add_child(self, ui: &mut ElementList);
/// Add element as a ui root.
fn add_root(self, ui: &mut UiInstance, max_size: glam::Vec2);
fn add_root(self, ui: &mut UiInstance, max_size: impl Into<Rect>);
}
impl<T: UiElement + 'static> UiElementExt for T {
@ -116,7 +95,7 @@ impl<T: UiElement + 'static> UiElementExt for T {
ui.add(self)
}
fn add_root(self, ui: &mut UiInstance, max_size: glam::Vec2) {
ui.add(self, max_size);
fn add_root(self, ui: &mut UiInstance, rect: impl Into<Rect>) {
ui.add(self, rect);
}
}

View file

@ -3,8 +3,8 @@
#[cfg(feature = "el_container")]
pub mod container;
#[cfg(feature = "el_fill_rect")]
pub mod fill_rect;
#[cfg(feature = "el_frame_view")]
pub mod frame_view;
#[cfg(feature = "el_spacer")]
pub mod spacer;

View file

@ -4,7 +4,7 @@ use derive_setters::Setters;
use glam::{Vec2, vec2};
use crate::{
element::{ElementList, MeasureContext, ProcessContext, UiElement},
frame::{Frame, FrameRect},
frame::{Frame, RectFrame},
layout::{compute_size, Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d, WrapBehavior},
measure::{Hints, Response},
rect::Sides,
@ -82,7 +82,7 @@ impl Default for Container {
gap: 0.,
padding: Sides::all(0.),
align: Alignment2d::default(),
background_frame: Box::<FrameRect>::default(),
background_frame: Box::<RectFrame>::default(),
wrap: WrapBehavior::Allow,
children: ElementList(Vec::new()),
}
@ -375,7 +375,7 @@ impl UiElement for Container {
// });
// }
self.background_frame.draw(ctx.draw, ctx.layout.position, ctx.measure.size);
self.background_frame.draw(ctx.draw, (ctx.layout.position, ctx.measure.size).into());
//padding
position += vec2(self.padding.left, self.padding.top);
@ -444,8 +444,8 @@ impl UiElement for Container {
//measure
let el_measure = element.measure(MeasureContext {
state: ctx.state,
layout: &el_layout,
state: ctx.state,
text_measure: ctx.text_measure,
current_font: ctx.current_font,
images: ctx.images,
@ -486,15 +486,13 @@ impl UiElement for Container {
//process
element.process(ProcessContext {
measure: &el_measure,
state: ctx.state,
layout: &el_layout,
draw: ctx.draw,
state: ctx.state,
text_measure: ctx.text_measure,
current_font: ctx.current_font,
images: ctx.images,
input: ctx.input,
//HACK: i have no idea what to do with this
//this sucks
signal: ctx.signal,
});

View file

@ -1,73 +0,0 @@
//! Simple filled rectangle with the specified size, background and corner radius
use derive_setters::Setters;
use glam::vec2;
use crate::{
draw::{RoundedCorners, UiDrawCommand},
element::{MeasureContext, ProcessContext, UiElement},
frame::{Frame, FrameRect},
layout::{compute_size, Size, Size2d},
measure::Response,
size
};
/// Simple filled rectangle with the specified size, background, and corner radius
#[derive(Setters)]
#[setters(prefix = "with_")]
pub struct FillRect {
/// Size of the rectangle
#[setters(into)]
pub size: Size2d,
/// Frame
#[setters(skip)]
pub frame: Box<dyn Frame>,
}
impl FillRect {
pub fn with_frame(mut self, frame: impl Frame + 'static) -> Self {
self.frame = Box::new(frame);
self
}
}
impl Default for FillRect {
fn default() -> Self {
Self {
size: size!(10, 10),
frame: Box::new(FrameRect::color((0., 0., 0., 0.5))),
}
}
}
impl UiElement for FillRect {
fn name(&self) -> &'static str {
"fill_rect"
}
fn size(&self) -> Option<Size2d> {
Some(self.size)
}
fn measure(&self, ctx: MeasureContext) -> Response {
Response {
size: compute_size(ctx.layout, self.size, ctx.layout.max_size),
..Default::default()
}
}
fn process(&self, ctx: ProcessContext) {
// if !self.background.is_transparent() {
// ctx.draw.add(UiDrawCommand::Rectangle {
// position: ctx.layout.position,
// size: ctx.measure.size,
// color: self.background.corners(),
// texture: None,
// rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({
// RoundedCorners::from_radius(self.corner_radius)
// }),
// });
// }
self.frame.draw(ctx.draw, ctx.layout.position, ctx.measure.size);
}
}

View file

@ -0,0 +1,68 @@
//! Simple element that displays the specified frame
use derive_setters::Setters;
use crate::{
element::{MeasureContext, ProcessContext, UiElement},
frame::{Frame, RectFrame},
layout::{compute_size, Size2d},
measure::Response,
size
};
/// Simple rectangle that displays the specified frame
#[derive(Setters)]
#[setters(prefix = "with_")]
pub struct FrameView {
/// Size of the rectangle
#[setters(into)]
pub size: Size2d,
/// Frame
#[setters(skip)]
pub frame: Box<dyn Frame>,
}
impl FrameView {
pub fn new(frame: impl Frame + 'static) -> Self {
Self {
size: size!(10, 10),
frame: Box::new(frame),
}
}
//setters:
pub fn with_frame(mut self, frame: impl Frame + 'static) -> Self {
self.frame = Box::new(frame);
self
}
}
impl Default for FrameView {
fn default() -> Self {
Self {
size: size!(10, 10),
frame: Box::new(RectFrame::color((0., 0., 0., 0.5))),
}
}
}
impl UiElement for FrameView {
fn name(&self) -> &'static str {
"frame_view"
}
fn size(&self) -> Option<Size2d> {
Some(self.size)
}
fn measure(&self, ctx: MeasureContext) -> Response {
Response {
size: compute_size(ctx.layout, self.size, ctx.layout.max_size),
..Default::default()
}
}
fn process(&self, ctx: ProcessContext) {
self.frame.draw(ctx.draw, (ctx.layout.position, ctx.measure.size).into());
}
}

View file

@ -2,7 +2,7 @@ use derive_setters::Setters;
use glam::vec2;
use crate::{
element::{MeasureContext, ProcessContext, UiElement},
frame::{Frame, FrameRect},
frame::{Frame, RectFrame},
layout::{compute_size, Size, Size2d},
measure::Response,
};
@ -47,8 +47,8 @@ impl Default for ProgressBar {
Self {
value: 0.,
size: Size::Auto.into(),
foreground: Box::new(FrameRect::color((0.0, 0.0, 1.0, 1.0))),
background: Box::new(FrameRect::color((0.0, 0.0, 0.0, 1.0))),
foreground: Box::new(RectFrame::color((0.0, 0.0, 1.0, 1.0))),
background: Box::new(RectFrame::color((0.0, 0.0, 0.0, 1.0))),
}
}
}
@ -75,10 +75,10 @@ impl UiElement for ProgressBar {
//FIXME: these optimizations may not be valid
if value < 1. || !self.foreground.covers_opaque() {
self.background.draw(ctx.draw, ctx.layout.position, ctx.measure.size);
self.background.draw(ctx.draw, (ctx.layout.position, ctx.measure.size).into());
}
if value > 0. {
self.foreground.draw(ctx.draw, ctx.layout.position, ctx.measure.size * vec2(value, 1.));
self.foreground.draw(ctx.draw, (ctx.layout.position, ctx.measure.size * vec2(value, 1.)).into());
}
// let rounded_corners =

View file

@ -4,7 +4,7 @@ use derive_setters::Setters;
use glam::{Vec2, vec2};
use crate::{
draw::UiDrawCommand, element::{MeasureContext, ProcessContext, UiElement}, frame::{Frame, FrameRect}, layout::{compute_size, Size2d}, measure::Response, rect::FillColor, signal::{trigger::SignalTriggerArg, Signal}
draw::UiDrawCommand, element::{MeasureContext, ProcessContext, UiElement}, frame::{Frame, RectFrame}, layout::{compute_size, Size2d}, measure::Response, rect::FillColor, signal::{trigger::SignalTriggerArg, Signal}
};
@ -79,9 +79,9 @@ impl Default for Slider {
Self {
value: 0.0,
size: Size2d::default(),
handle: Box::new(FrameRect::color((0.0, 0.0, 1.))),
track: Box::new(FrameRect::color((0.5, 0.5, 0.5))),
track_active: Box::new(FrameRect::color((0.0, 0.0, 0.75))),
handle: Box::new(RectFrame::color((0.0, 0.0, 1.))),
track: Box::new(RectFrame::color((0.5, 0.5, 0.5))),
track_active: Box::new(RectFrame::color((0.0, 0.0, 0.75))),
track_height: 0.25,
handle_size: (15.0, 1.),
follow_mode: SliderFollowMode::default(),
@ -156,8 +156,10 @@ impl UiElement for Slider {
if !(self.track_active.covers_opaque() && self.handle.covers_opaque() && (self.handle_size.1 >= self.track_height) && self.value >= 1.) {
self.track.draw(
ctx.draw,
(
ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - self.track_height / 2.),
ctx.measure.size * vec2(1., self.track_height),
).into()
);
}
@ -168,8 +170,10 @@ impl UiElement for Slider {
if !(self.handle.covers_opaque() && (self.handle_size.1 >= self.track_height) && self.value <= 0.) {
self.track_active.draw(
ctx.draw,
(
ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - self.track_height / 2.),
(ctx.measure.size - handle_size * Vec2::X) * vec2(self.value, self.track_height) + handle_size * Vec2::X / 2.,
).into()
);
}
@ -187,10 +191,12 @@ impl UiElement for Slider {
if (self.handle_size.0 > 0. && self.handle_size.1 > 0.) {
self.handle.draw(
ctx.draw,
(
ctx.layout.position +
((ctx.measure.size.x - handle_size.x) * self.value) * Vec2::X +
ctx.measure.size.y * ((1. - self.handle_size.1) * 0.5) * Vec2::Y,
handle_size,
).into()
);
}

View file

@ -1,7 +1,6 @@
//! modular procedural background system
use glam::Vec2;
use crate::draw::UiDrawCommandList;
use crate::{draw::UiDrawCommandList, rect::Rect};
pub mod point;
mod rect;
@ -9,12 +8,12 @@ pub mod stack;
pub mod nine_patch;
mod impls;
pub use rect::FrameRect;
pub use rect::RectFrame;
/// Trait for a drawable frame
pub trait Frame {
/// Draw the frame at the given position and size
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
/// Draw the frame at the given rect's position and size
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect);
/// Check if the frame is guaranteed to be fully opaque and fully cover the parent frame regardless of it's size
///

View file

@ -3,14 +3,14 @@ use super::Frame;
use crate::{
color,
draw::{ImageHandle, UiDrawCommand, UiDrawCommandList},
rect::{Corners, FillColor},
rect::{Rect, Corners, FillColor},
};
impl Frame for ImageHandle {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
draw.add(UiDrawCommand::Rectangle {
position,
size: parent_size,
position: rect.position,
size: rect.size,
color: color::WHITE.into(),
texture: Some(*self),
texture_uv: None,
@ -24,10 +24,10 @@ impl Frame for ImageHandle {
}
impl Frame for FillColor {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
draw.add(UiDrawCommand::Rectangle {
position,
size: parent_size,
position: rect.position,
size: rect.size,
color: self.corners(),
texture: None,
texture_uv: None,
@ -45,8 +45,8 @@ impl Frame for FillColor {
// 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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
@ -54,8 +54,8 @@ impl Frame for Corners<Vec4> {
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
@ -63,8 +63,8 @@ impl Frame for (Vec4, Vec4, Vec4, Vec4) {
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
@ -72,8 +72,8 @@ impl Frame for ((f32, f32, f32, f32), (f32, f32, f32, f32), (f32, f32, f32, f32)
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
@ -83,46 +83,46 @@ impl Frame for [[f32; 4]; 4] {
// 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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
true
}
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
true
}
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
true
}
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
true
}
}
// RGBA:
impl Frame for Vec4 {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
@ -130,8 +130,8 @@ impl Frame for Vec4 {
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
@ -139,8 +139,8 @@ impl Frame for (f32, f32, f32, f32) {
}
impl Frame for [f32; 4] {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
@ -150,28 +150,28 @@ impl Frame for [f32; 4] {
// RGB:
impl Frame for Vec3 {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
true
}
}
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)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
true
}
}
impl Frame for [f32; 3] {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
FillColor::from(*self).draw(draw, position, parent_size)
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
FillColor::from(*self).draw(draw, rect)
}
fn covers_opaque(&self) -> bool {
FillColor::from(*self).is_opaque()
true
}
}

View file

@ -4,7 +4,11 @@
//! This is useful for creating scalable UI elements like buttons, windows, etc.
use glam::{vec2, UVec2, Vec2};
use crate::{color, draw::{ImageHandle, UiDrawCommand}, rect::{Corners, FillColor, Rect}};
use crate::{
color,
draw::{ImageHandle, UiDrawCommand, UiDrawCommandList},
rect::{Rect, Corners, FillColor}
};
use super::Frame;
/// Represents a 9-patch image asset
@ -49,10 +53,10 @@ impl Default for NinePatchFrame {
}
impl Frame for NinePatchFrame {
fn draw(&self, draw: &mut crate::draw::UiDrawCommandList, position: glam::Vec2, parent_size: glam::Vec2) {
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
// 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 position = rect.position.round();
let img_sz = UVec2::from(self.asset.size).as_vec2();
@ -79,13 +83,13 @@ impl Frame for NinePatchFrame {
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),
rect.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),
rect.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,
);

View file

@ -2,7 +2,7 @@ use glam::Vec2;
use crate::{
color,
draw::{ImageHandle, RoundedCorners, UiDrawCommand, UiDrawCommandList},
rect::{Corners, FillColor},
rect::{Rect, Corners, FillColor},
};
use super::{Frame, point::FramePoint2d};
@ -10,7 +10,7 @@ use super::{Frame, point::FramePoint2d};
///
/// Can optionally be tinted, textured, and have rounded corners
#[derive(Clone, Copy)]
pub struct FrameRect {
pub struct RectFrame {
/// Background color of the frame\
///
/// If the container has a background texture, it will be multiplied by this color
@ -35,26 +35,26 @@ pub struct FrameRect {
pub corner_radius: Corners<f32>,
}
// impl<T: Into<FillColor>> From<T> for FrameRect {
// impl<T: Into<FillColor>> From<T> for RectFrame {
// fn from(color: T) -> Self {
// Self::from_color(color)
// }
// }
impl From<FillColor> for FrameRect {
impl From<FillColor> for RectFrame {
fn from(color: FillColor) -> Self {
Self::color(color)
}
}
impl From<ImageHandle> for FrameRect {
impl From<ImageHandle> for RectFrame {
fn from(image: ImageHandle) -> Self {
Self::image(image)
}
}
impl FrameRect {
/// Create a new [`FrameRect`] with the given color
impl RectFrame {
/// Create a new [`RectFrame`] with the given color
pub fn color(color: impl Into<FillColor>) -> Self {
Self {
color: color.into(),
@ -62,7 +62,7 @@ impl FrameRect {
}
}
/// Create a new [`FrameRect`] with the given image\
/// Create a new [`RectFrame`] with the given image\
///
/// Color will be set to [`WHITE`](crate::color::WHITE) to ensure the image is visible
pub fn image(image: ImageHandle) -> Self {
@ -73,7 +73,7 @@ impl FrameRect {
}
}
/// Create a new [`FrameRect`] with the given color and image
/// Create a new [`RectFrame`] with the given color and image
pub fn color_image(color: impl Into<FillColor>, image: ImageHandle) -> Self {
Self {
color: color.into(),
@ -82,7 +82,7 @@ impl FrameRect {
}
}
/// Set the corner radius of the [`FrameRect`]
/// Set the corner radius of the [`RectFrame`]
pub fn with_corner_radius(self, radius: impl Into<Corners<f32>>) -> Self {
Self {
corner_radius: radius.into(),
@ -102,7 +102,7 @@ impl FrameRect {
}
}
impl Default for FrameRect {
impl Default for RectFrame {
fn default() -> Self {
Self {
color: FillColor::transparent(),
@ -114,13 +114,13 @@ impl Default for FrameRect {
}
}
impl Frame for FrameRect {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
impl Frame for RectFrame {
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
//TODO: handle bottom_right < top_left
let top_left = self.top_left.resolve(parent_size);
let bottom_right = self.bottom_right.resolve(parent_size);
let top_left = self.top_left.resolve(rect.size);
let bottom_right = self.bottom_right.resolve(rect.size);
draw.add(UiDrawCommand::Rectangle {
position: position + top_left,
position: rect.position + top_left,
size: bottom_right - top_left,
color: self.color.corners(),
texture: self.image,
@ -132,14 +132,14 @@ impl Frame for FrameRect {
}
fn covers_opaque(&self) -> bool {
self.top_left.x.absolute <= 0. &&
self.top_left.x.relative <= 0. &&
self.top_left.y.absolute <= 0. &&
self.top_left.x.absolute <= 0. &&
self.top_left.y.relative <= 0. &&
self.bottom_right.x.absolute >= 0. &&
self.top_left.y.absolute <= 0. &&
self.bottom_right.x.relative >= 1. &&
self.bottom_right.y.absolute >= 0. &&
self.bottom_right.x.absolute >= 0. &&
self.bottom_right.y.relative >= 1. &&
self.bottom_right.y.absolute >= 0. &&
self.color.is_opaque() &&
self.image.is_none() &&
self.corner_radius.max_f32() == 0.

View file

@ -1,16 +1,15 @@
//! allows stacking two frames on top of each other
use glam::Vec2;
use crate::draw::UiDrawCommandList;
use crate::{draw::UiDrawCommandList, rect::Rect};
use super::Frame;
/// A frame that draws two frames on top of each other
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);
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect) {
self.0.draw(draw, rect);
self.1.draw(draw, rect);
}
fn covers_opaque(&self) -> bool {
@ -22,10 +21,17 @@ impl Frame for FrameStack {
pub trait FrameStackExt: Frame {
/// Stack another frame on top of this one
fn stack(self, other: impl Frame + 'static) -> FrameStack;
/// Stack another frame below this one
fn stack_bottom(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))
}
fn stack_bottom(self, other: impl Frame + 'static) -> FrameStack {
FrameStack(Box::new(other), Box::new(self))
}
}

View file

@ -1,16 +1,20 @@
use glam::Vec2;
use crate::{
element::{MeasureContext, ProcessContext, UiElement},
layout::{Direction, LayoutInfo},
text::{FontHandle, TextRenderer},
draw::{
ImageHandle, TextureFormat, UiDrawCall, UiDrawCommandList,
ImageHandle,
TextureFormat,
UiDrawCall,
UiDrawCommandList,
atlas::{TextureAtlasManager, TextureAtlasMeta},
},
element::{MeasureContext, ProcessContext, UiElement},
signal::{Signal, SignalStore},
event::{EventQueue, UiEvent},
input::UiInputState,
layout::{Direction, LayoutInfo},
signal::{SignalStore, Signal},
rect::Rect,
state::StateRepo,
text::{FontHandle, TextRenderer}
};
/// The main instance of the UI system.
@ -144,16 +148,17 @@ impl UiInstance {
/// Add an element or an element tree to the UI
///
/// Use the `max_size` parameter to specify the maximum size of the element\
/// Use the `rect` parameter to specify the position and size of the element\
/// (usually, the size of the window/screen)
///
/// ## Panics:
/// If called while the UI is not active (call [`UiInstance::begin`] first)
pub fn add<T: UiElement>(&mut self, element: T, max_size: Vec2) {
pub fn add(&mut self, element: impl UiElement, rect: impl Into<Rect>) {
assert!(self.state, "must call UiInstance::begin before adding elements");
let rect: Rect = rect.into();
let layout = LayoutInfo {
position: Vec2::ZERO,
max_size,
position: rect.position,
max_size: rect.size,
direction: Direction::Vertical,
remaining_space: None,
};

View file

@ -75,11 +75,11 @@ macro_rules! size {
};
}
/// Helper macro for constructing a `FrameRect`
/// Helper macro for constructing a `RectFrame`
///
/// # Example:
/// ```
/// frame_rect! {
/// _frame! {
/// color: (0.2, 0.2, 0.3, 1.),
/// corner_radius: 5.,
/// };
@ -89,24 +89,24 @@ macro_rules! size {
/// - If the `image` field is set, but not `color`, the `color` field will default to [`WHITE`](crate::color::WHITE) (to ensure visibility)
/// - If both `color` and `image` are not set, the `color` field will default to [`TRANSPARENT`](crate::color::TRANSPARENT)
#[macro_export]
macro_rules! frame_rect {
macro_rules! rect_frame {
{} => {
$crate::frame::FrameRect::default()
$crate::frame::RectFrame::default()
};
// () => {
// $crate::frame::FrameRect::default()
// $crate::frame::RectFrame::default()
// };
($expr:expr) => {
{
let _frame_rect: $crate::frame::FrameRect = $crate::frame::FrameRect::from($expr);
_frame_rect
let __frame: $crate::frame::RectFrame = $crate::frame::RectFrame::from($expr);
__frame
}
};
($image:expr, $color:expr) => {
$crate::frame::FrameRect::color_image($color, $image)
$crate::frame::RectFrame::color_image($color, $image)
};
{$($ident:ident : $expr:expr),+$(,)?} => {
@ -115,58 +115,24 @@ macro_rules! frame_rect {
#[allow(non_upper_case_globals)]
{$(const $ident: () = ();)+}
// construct the FrameRect
// construct the RectFrame
{
let mut frame_rect = $crate::frame::FrameRect::default();
let mut _frame = $crate::frame::RectFrame::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;
}
_frame.$ident = ($expr).into();
_image_is_set |= stringify!($ident) == "image";
_color_is_set |= stringify!($ident) == "color";
}
)+
if frame_rect.image.is_some() && _image_is_set && !_color_is_set {
frame_rect.color = (1., 1., 1., 1.).into();
// set color to white if image is explicitly set to Some(...) but color is left as the default
if _frame.image.is_some() && _image_is_set && !_color_is_set {
_frame.color = (1., 1., 1., 1.).into();
}
frame_rect
_frame
}
}
};
// {$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

@ -11,6 +11,24 @@ pub struct Rect {
}
impl Rect {
pub const fn new(position: Vec2, size: Vec2) -> Self {
Self { position, size }
}
pub const fn from_position(position: Vec2) -> Self {
Self {
position,
size: Vec2::ZERO,
}
}
pub const fn from_size(size: Vec2) -> Self {
Self {
position: Vec2::ZERO,
size,
}
}
/// Check if the rect contains a point.
pub fn contains_point(&self, point: Vec2) -> bool {
point.cmpge(self.position).all() && point.cmple(self.position + self.size).all()
@ -63,3 +81,58 @@ impl Rect {
}
}
}
impl From<Vec2> for Rect {
/// Create a new `Rect` from a `Vec2`, where x and y are the width and height of the rect respectively.
fn from(size: Vec2) -> Self {
Self::from_size(size)
}
}
impl From<(Vec2, Vec2)> for Rect {
/// Create a new `Rect` from a tuple of two `Vec2`s, where the first `Vec2` is the position and the second `Vec2` is the size.
fn from((position, size): (Vec2, Vec2)) -> Self {
Self { position, size }
}
}
impl From<(f32, f32, f32, f32)> for Rect {
/// Create a new `Rect` from a tuple of 4 `f32`s, where the first two `f32`s are the x and y positions of the top-left corner and the last two `f32`s are the width and height of the rect respectively.
fn from((x, y, width, height): (f32, f32, f32, f32)) -> Self {
Self {
position: Vec2::new(x, y),
size: Vec2::new(width, height),
}
}
}
impl From<[f32; 4]> for Rect {
/// Create a new `Rect` from an array of 4 `f32`s, where the first two `f32`s are the x and y positions of the top-left corner and the last two `f32`s are the width and height of the rect respectively.
fn from([x, y, width, height]: [f32; 4]) -> Self {
Self {
position: Vec2::new(x, y),
size: Vec2::new(width, height),
}
}
}
impl From<Rect> for (Vec2, Vec2) {
/// Convert a `Rect` into a tuple of two `Vec2`s, where the first `Vec2` is the position and the second `Vec2` is the size.
fn from(rect: Rect) -> Self {
(rect.position, rect.size)
}
}
impl From<Rect> for (f32, f32, f32, f32) {
/// Convert a `Rect` into a tuple of 4 `f32`s, where the first two `f32`s are the x and y positions of the top-left corner and the last two `f32`s are the width and height of the rect respectively.
fn from(rect: Rect) -> Self {
(rect.position.x, rect.position.y, rect.size.x, rect.size.y)
}
}
impl From<Rect> for [f32; 4] {
/// Convert a `Rect` into an array of 4 `f32`s, where the first two `f32`s are the x and y positions of the top-left corner and the last two `f32`s are the width and height of the rect respectively.
fn from(rect: Rect) -> Self {
[rect.position.x, rect.position.y, rect.size.x, rect.size.y]
}
}