diff --git a/hui-examples/examples/align_test.rs b/hui-examples/examples/align_test.rs index e0d340a..257423e 100644 --- a/hui-examples/examples/align_test.rs +++ b/hui-examples/examples/align_test.rs @@ -42,7 +42,7 @@ fn main() { gap: 5., padding: Sides::all(5.), align: (Alignment::Begin, Alignment::Center).into(), - size: (UiSize::Fraction(1.), UiSize::Fraction(1.)), + size: (UiSize::Fraction(1.), UiSize::Fraction(1.)).into(), children: ElementList(vec![ Box::new(ProgressBar { value: 0.5, @@ -56,7 +56,7 @@ fn main() { gap: 5., padding: Sides::all(5.), align: (Alignment::End, Alignment::Center).into(), - size: (UiSize::Fraction(1.), UiSize::Fraction(1.)), + size: (UiSize::Fraction(1.), UiSize::Fraction(1.)).into(), children: ElementList(vec![ Box::new(ProgressBar { value: z, @@ -64,7 +64,7 @@ fn main() { ..Default::default() }), Box::new(Container { - size: (UiSize::Fraction(1.), UiSize::Auto), + size: (UiSize::Fraction(1.), UiSize::Auto).into(), align: (Alignment::Center, Alignment::End).into(), padding: Sides::all(5.), gap: 10., diff --git a/hui-examples/examples/modern_testbed.rs b/hui-examples/examples/modern_testbed.rs index 5adbec0..9aa6099 100644 --- a/hui-examples/examples/modern_testbed.rs +++ b/hui-examples/examples/modern_testbed.rs @@ -1,19 +1,16 @@ -use std::time::Instant; -use glam::{vec3, vec4, UVec2}; +use glam::UVec2; use glium::{backend::glutin::SimpleWindowBuilder, Surface}; use winit::{ event::{Event, WindowEvent}, event_loop::{EventLoopBuilder, ControlFlow} }; use hui::{ - UiInstance, + UiInstance, color, size, + layout::Alignment, + rectangle::Corners, element::{ - ElementList, UiElementExt, - container::Container, - text::Text, + container::Container, text::Text, UiElementExt }, - layout::{Alignment, UiDirection, UiSize}, - rectangle::{Corners, Sides}, }; use hui_glium::GliumUiRenderer; @@ -41,14 +38,27 @@ fn main() { hui.begin(); Container::default() - .with_size((UiSize::Fraction(1.), UiSize::Fraction(1.))) - .with_padding(Sides::all(5.)) - .with_background(vec3(1., 0., 0.)) - .with_children(|ui: &mut ElementList| { + .with_size(size!(100%, 50%)) + .with_align(Alignment::Center) + .with_padding(5.) + .with_corner_radius(10.) + .with_background(color::RED) + .with_children(|ui| { Text::default() .with_text("Hello, world") - .with_text_size(120) - .with_color(vec4(0., 0., 0., 1.)) + .with_text_size(100) + .with_color(color::WHITE) + .add_child(ui); + Container::default() + .with_padding((10., 20.)) + .with_corner_radius((10., 20., 30., 40.)) + .with_background(color::DARK_RED) + .with_children(|ui| { + Text::default() + .with_text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.") + .with_text_size(24) + .add_child(ui); + }) .add_child(ui); }) .add_root(&mut hui, resolution); diff --git a/hui-examples/examples/mom_downloader.rs b/hui-examples/examples/mom_downloader.rs index 16d5637..643e962 100644 --- a/hui-examples/examples/mom_downloader.rs +++ b/hui-examples/examples/mom_downloader.rs @@ -55,12 +55,12 @@ fn main() { hui.add(Container { align: Alignment::Center.into(), - size: (UiSize::Fraction(1.), UiSize::Fraction(1.)), + size: (UiSize::Fraction(1.), UiSize::Fraction(1.)).into(), background: vec4(0.1, 0.1, 0.1, 1.).into(), children: ElementList(vec![Box::new(Container { gap: 5., padding: Sides::all(10.), - size: (UiSize::Static(450.), UiSize::Auto), + size: (UiSize::Static(450.), UiSize::Auto).into(), background: vec4(0.2, 0.2, 0.5, 1.).into(), corner_radius: Corners::all(8.), children: elements(|el| { @@ -79,7 +79,7 @@ fn main() { el.push(Box::new(Container { direction: UiDirection::Horizontal, align: (Alignment::End, Alignment::Center).into(), - size: (UiSize::Fraction(1.), UiSize::Auto), + size: (UiSize::Fraction(1.), UiSize::Auto).into(), children: ElementList(vec![Box::new(Text { text: format!("{:.2}% ({:.1} GB)", mom_ratio * 100., mom_ratio * 10000.).into(), font: font_handle, diff --git a/hui-examples/examples/rounded_rect.rs b/hui-examples/examples/rounded_rect.rs index b286c9c..b46e20a 100644 --- a/hui-examples/examples/rounded_rect.rs +++ b/hui-examples/examples/rounded_rect.rs @@ -40,11 +40,11 @@ fn main() { hui.add(Container { gap: 10., align: Alignment::Center.into(), - size: (UiSize::Fraction(1.), UiSize::Fraction(1.)), + size: (UiSize::Fraction(1.), UiSize::Fraction(1.)).into(), children: ElementList(vec![ Box::new(Container { align: Alignment::Center.into(), - size: (UiSize::Fraction(0.5), UiSize::Fraction(0.5)), + size: (UiSize::Fraction(0.5), UiSize::Fraction(0.5)).into(), background: vec4(1., 0., 0., 1.).into(), corner_radius: Corners { top_left: 10., @@ -57,7 +57,7 @@ fn main() { padding: Sides::all(20.), direction: UiDirection::Horizontal, align: Alignment::Center.into(), - size: (UiSize::Auto, UiSize::Auto), + size: (UiSize::Auto, UiSize::Auto).into(), background: vec4(0.1, 0.1, 0.1, 0.5).into(), corner_radius: Corners::all(8.), children: ElementList(vec![ @@ -84,7 +84,7 @@ fn main() { direction: UiDirection::Horizontal, children: ElementList(vec![ Box::new(Container { - size: (UiSize::Static(100.), UiSize::Static(100.)), + size: (UiSize::Static(100.), UiSize::Static(100.)).into(), background: Corners::left_right( vec4(1., 0., 0., 1.), vec4(0., 1., 0., 1.) @@ -93,7 +93,7 @@ fn main() { ..Default::default() }), Box::new(Container { - size: (UiSize::Static(100.), UiSize::Static(100.)), + size: (UiSize::Static(100.), UiSize::Static(100.)).into(), background: Corners::left_right( vec4(1., 0., 0., 1.), vec4(0., 1., 0., 1.) @@ -102,7 +102,7 @@ fn main() { ..Default::default() }), Box::new(Container { - size: (UiSize::Static(100.), UiSize::Static(100.)), + size: (UiSize::Static(100.), UiSize::Static(100.)).into(), background: Corners::left_right( vec4(1., 0., 0., 1.), vec4(0., 1., 0., 1.) @@ -111,7 +111,7 @@ fn main() { ..Default::default() }), Box::new(Container { - size: (UiSize::Static(100.), UiSize::Static(100.)), + size: (UiSize::Static(100.), UiSize::Static(100.)).into(), background: Corners::left_right( vec4(1., 0., 0., 1.), vec4(0., 1., 0., 1.) diff --git a/hui-examples/examples/text_weird.rs b/hui-examples/examples/text_weird.rs index 254be67..a799520 100644 --- a/hui-examples/examples/text_weird.rs +++ b/hui-examples/examples/text_weird.rs @@ -46,7 +46,7 @@ fn main() { hui.begin(); hui.add(Container { - size: (UiSize::Fraction(1.), UiSize::Fraction(1.)), + size: (UiSize::Fraction(1.), UiSize::Fraction(1.)).into(), background: vec4(0.1, 0.1, 0.1, 1.).into(), children: elements(|elem| { elem.push(Box::new(Text { diff --git a/hui/src/color.rs b/hui/src/color.rs new file mode 100644 index 0000000..0511945 --- /dev/null +++ b/hui/src/color.rs @@ -0,0 +1,75 @@ +use glam::{vec4, Vec4}; + +/// Transparent `#00000000` +pub const TRANSPARENT: Vec4 = vec4(0.0, 0.0, 0.0, 0.0); + +/// Black `#000000` +pub const BLACK: Vec4 = vec4(0.0, 0.0, 0.0, 1.0); + +/// White `#ffffff` +pub const WHITE: Vec4 = vec4(1.0, 1.0, 1.0, 1.0); + +/// Red `#ff0000` +pub const RED: Vec4 = vec4(1.0, 0.0, 0.0, 1.0); + +/// Dark red `#800000` +pub const DARK_RED: Vec4 = vec4(0.5, 0.0, 0.0, 1.0); + +/// Green `#00ff00` +pub const GREEN: Vec4 = vec4(0.0, 1.0, 0.0, 1.0); + +/// Dark green `#008000` +pub const DARK_GREEN: Vec4 = vec4(0.0, 0.5, 0.0, 1.0); + +/// Blue `#0000ff` +pub const BLUE: Vec4 = vec4(0.0, 0.0, 1.0, 1.0); + +/// Dark blue `#000080` +pub const DARK_BLUE: Vec4 = vec4(0.0, 0.0, 0.5, 1.0); + +/// Yellow `#ffff00` +pub const YELLOW: Vec4 = vec4(1.0, 1.0, 0.0, 1.0); + +/// Cyan `#00ffff` +pub const CYAN: Vec4 = vec4(0.0, 1.0, 1.0, 1.0); + +/// Magenta `#ff00ff` +pub const MAGENTA: Vec4 = vec4(1.0, 0.0, 1.0, 1.0); + +/// Gray `#808080` +pub const GRAY: Vec4 = vec4(0.5, 0.5, 0.5, 1.0); + +/// Light gray `#c0c0c0` +pub const LIGHT_GRAY: Vec4 = vec4(0.75, 0.75, 0.75, 1.0); + +/// Dark gray `#404040` +pub const DARK_GRAY: Vec4 = vec4(0.25, 0.25, 0.25, 1.0); + +/// Orange `#ff8000` +pub const ORANGE: Vec4 = vec4(1.0, 0.5, 0.0, 1.0); + +/// Brown `#804000` +pub const BROWN: Vec4 = vec4(0.5, 0.25, 0.0, 1.0); + +/// Pink `#ff80ff` +pub const PINK: Vec4 = vec4(1.0, 0.5, 1.0, 1.0); + +/// Purple `#800080` +pub const PURPLE: Vec4 = vec4(0.5, 0.0, 0.5, 1.0); + +/// Lime `#80ff00` +pub const LIME: Vec4 = vec4(0.5, 1.0, 0.0, 1.0); + +/// Teal `#008080` +pub const TEAL: Vec4 = vec4(0.0, 0.5, 0.5, 1.0); + +/// Indigo `#004080` +pub const INDIGO: Vec4 = vec4(0.0, 0.25, 0.5, 1.0); + +/// Olive `#808000` +pub const OLIVE: Vec4 = vec4(0.5, 0.5, 0.0, 1.0); + +/// Sky blue `#87ceeb` +pub const SKY_BLUE: Vec4 = vec4(0.53, 0.81, 0.92, 1.0); + +//TODO color macro diff --git a/hui/src/element.rs b/hui/src/element.rs index 46f0082..9b03e88 100644 --- a/hui/src/element.rs +++ b/hui/src/element.rs @@ -63,21 +63,33 @@ impl ElementList { pub fn add(&mut self, element: impl UiElement + 'static) { self.0.push(Box::new(element)) } -} -impl From for ElementList { - fn from(cb: T) -> Self { + pub(crate) fn from_callback(cb: impl FnOnce(&mut ElementList)) -> Self { let mut list = ElementList(Vec::new()); cb(&mut list); list } } -impl From>> for ElementList { - fn from(value: Vec>) -> Self { - Self(value) - } -} +// impl From for ElementList { +// fn from(cb: T) -> Self { +// let mut list = ElementList(Vec::new()); +// cb(&mut list); +// list +// } +// } + +// impl From for ElementList { +// fn from(value: T) -> Self { +// ElementList(vec![Box::new(value)]) +// } +// } + +// impl From>> for ElementList { +// fn from(value: Vec>) -> Self { +// Self(value) +// } +// } pub trait UiElementExt: UiElement { fn add_child(self, ui: &mut ElementList); diff --git a/hui/src/element/builtin/container.rs b/hui/src/element/builtin/container.rs index 6bd0c3c..a41d704 100644 --- a/hui/src/element/builtin/container.rs +++ b/hui/src/element/builtin/container.rs @@ -4,7 +4,7 @@ use crate::{ background::BackgroundColor, draw::{RoundedCorners, UiDrawCommand}, element::{ElementList, MeasureContext, ProcessContext, UiElement}, - layout::{Alignment, Alignment2d, LayoutInfo, UiDirection, UiSize}, + layout::{Alignment, Alignment2d, LayoutInfo, UiDirection, UiSize, UiSize2d}, measure::{Hints, Response}, rectangle::{Corners, Sides} }; @@ -19,27 +19,56 @@ use crate::{ //TODO: borders //TODO: min/max size -#[derive(Setters)] -#[setters(prefix = "with_")] pub struct Container { - pub size: (UiSize, UiSize), + pub size: UiSize2d, pub direction: UiDirection, pub gap: f32, pub padding: Sides, - #[setters(into)] pub align: Alignment2d, - #[setters(into)] pub background: BackgroundColor, - #[setters(into)] pub corner_radius: Corners, - #[setters(into)] pub children: ElementList, } +impl Container { + pub fn with_size(self, size: impl Into) -> Self { + Self { size: size.into(), ..self } + } + + pub fn with_direction(self, direction: UiDirection) -> Self { + Self { direction, ..self } + } + + pub fn with_gap(self, gap: f32) -> Self { + Self { gap, ..self } + } + + pub fn with_padding(self, padding: impl Into>) -> Self { + Self { padding: padding.into(), ..self } + } + + pub fn with_align(self, align: impl Into) -> Self { + Self { align: align.into(), ..self } + } + + pub fn with_background(self, background: impl Into) -> Self { + Self { background: background.into(), ..self } + } + + pub fn with_corner_radius(self, corner_radius: impl Into>) -> Self { + Self { corner_radius: corner_radius.into(), ..self } + } + + pub fn with_children(mut self, ui: impl FnOnce(&mut ElementList)) -> Self { + self.children.0.extend(ElementList::from_callback(ui).0); + self + } +} + impl Default for Container { fn default() -> Self { Self { - size: (UiSize::Auto, UiSize::Auto), + size: (UiSize::Auto, UiSize::Auto).into(), direction: UiDirection::Vertical, gap: 0., padding: Sides::all(0.), @@ -53,12 +82,12 @@ impl Default for Container { impl Container { pub fn measure_max_inner_size(&self, layout: &LayoutInfo) -> Vec2 { - let outer_size_x = match self.size.0 { + let outer_size_x = match self.size.width { UiSize::Auto => layout.max_size.x, UiSize::Fraction(p) => layout.max_size.x * p, UiSize::Static(p) => p, }; - let outer_size_y = match self.size.1 { + let outer_size_y = match self.size.height { UiSize::Auto => layout.max_size.y, UiSize::Fraction(p) => layout.max_size.y * p, UiSize::Static(p) => p, @@ -106,12 +135,12 @@ impl UiElement for Container { self.padding.top + self.padding.bottom, ); - match self.size.0 { + match self.size.width { UiSize::Auto => (), UiSize::Fraction(percentage) => size.x = ctx.layout.max_size.x * percentage, UiSize::Static(pixels) => size.x = pixels, } - match self.size.1 { + match self.size.height { UiSize::Auto => (), UiSize::Fraction(percentage) => size.y = ctx.layout.max_size.y * percentage, UiSize::Static(pixels) => size.y = pixels, diff --git a/hui/src/layout.rs b/hui/src/layout.rs index 7c7130a..bcd3376 100644 --- a/hui/src/layout.rs +++ b/hui/src/layout.rs @@ -76,14 +76,84 @@ impl From for Alignment2d { } } -#[derive(Default, Debug, Clone, Copy)] +#[derive(Default, Debug, Clone, Copy, PartialEq)] pub enum UiSize { #[default] + /// Automatically calculate size based on content Auto, + /// Size as a ratio of parent size\ + /// Valid range: 0.0-1.0 (0-100%) Fraction(f32), + /// Static size in pixels Static(f32), } +impl From for UiSize { + #[inline] + fn from(value: f32) -> Self { + Self::Static(value) + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub struct UiSize2d { + pub width: UiSize, + pub height: UiSize, +} + +impl From<(UiSize, UiSize)> for UiSize2d { + #[inline] + fn from((width, height): (UiSize, UiSize)) -> Self { + Self { width, height } + } +} + +//XXX: should this exist? +impl From for UiSize2d { + #[inline] + fn from(size: UiSize) -> Self { + Self { + width: size, + height: size, + } + } +} + +//TODO?: add `UiSize2d` from `(Into, Into)` or Into conversion + +/// Create a `UiSize` or `UiSize2d` from a literal +/// # Syntax: +/// - `auto` - `UiSize::Auto` +/// - `x` - `UiSize::Static(x)` +/// - `x%` - `UiSize::Fraction(x / 100.)` +/// +/// If two values are provided, it creates a `UiSize2d` with the first value as width and the second as height +#[macro_export] +macro_rules! size { + (auto) => { + $crate::layout::UiSize::Auto + }; + ($x:literal) => { + $crate::layout::UiSize::Static($x as f32) + }; + ($x:literal %) => { + $crate::layout::UiSize::Fraction($x as f32 / 100.) + }; + ($x:literal , $y:tt $($ys:tt)?) => { + $crate::layout::UiSize2d { + width: $crate::layout::size!($x), + height: $crate::layout::size!($y $($ys)?), + } + }; + ($x:literal $($xs:tt)? , $y:tt $($ys:tt)?) => { + $crate::layout::UiSize2d { + width: $crate::layout::size!($x $($xs)?), + height: $crate::layout::size!($y $($ys)?), + } + }; +} +pub use size; + #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum UiDirection { #[default] diff --git a/hui/src/lib.rs b/hui/src/lib.rs index 1cd8063..0a9d0ba 100644 --- a/hui/src/lib.rs +++ b/hui/src/lib.rs @@ -20,5 +20,6 @@ pub mod draw; pub mod measure; pub mod state; pub mod text; +pub mod color; pub use instance::UiInstance; diff --git a/hui/src/rectangle.rs b/hui/src/rectangle.rs index 5b8f0ec..277e8ba 100644 --- a/hui/src/rectangle.rs +++ b/hui/src/rectangle.rs @@ -31,6 +31,24 @@ impl Sides { } } +impl From for Sides { + fn from(value: T) -> Self { + Self::all(value) + } +} + +impl From<(T, T)> for Sides { + fn from((horizontal, vertical): (T, T)) -> Self { + Self::horizontal_vertical(horizontal, vertical) + } +} + +impl From<(T, T, T, T)> for Sides { + fn from((top, bottom, left, right): (T, T, T, T)) -> Self { + Self { top, bottom, left, right } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub struct Corners { pub top_left: T,