This commit is contained in:
griffi-gh 2024-02-29 16:02:05 +01:00
parent cd589d29ae
commit 45bbdd57fd
17 changed files with 305 additions and 191 deletions

View file

@ -0,0 +1,54 @@
use glam::{UVec2, Vec2};
use glium::{backend::glutin::SimpleWindowBuilder, Surface};
use winit::{
event::{Event, WindowEvent},
event_loop::{EventLoopBuilder, ControlFlow}
};
use hui::UiInstance;
use hui_glium::GliumUiRenderer;
/// Generates a `main` function that initializes glium renderer, `UiInstance`, and runs the event loop.
macro_rules! ui_main {
($closure: expr) => {
fn main() {
$crate::boilerplate::ui($closure);
}
};
}
/// Initializes glium renderer, `UiInstance`, and runs the event loop.
pub fn ui(mut x: impl FnMut(&mut UiInstance, Vec2)) {
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.);
hui.begin();
let size = UVec2::from(display.get_framebuffer_dimensions()).as_vec2();
x(&mut hui, size);
hui.end();
backend.update(&hui);
backend.draw(&mut frame, size);
frame.finish().unwrap();
}
_ => (),
}
}).unwrap();
}

View file

@ -1,3 +1,5 @@
//WARNING: THIS EXAMPLE IS EXTREMELY OUTDATED AND USES DEPRECATED API
use std::time::Instant;
use glam::{UVec2, vec4};
use glium::{backend::glutin::SimpleWindowBuilder, Surface};
@ -7,7 +9,7 @@ use winit::{
};
use hui::{
element::{
container::Container, progress_bar::ProgressBar, rect::Rect, ElementList, UiElement
container::Container, progress_bar::ProgressBar, fill_rect::FillRect, ElementList, UiElement
}, layout::{Alignment, UiDirection, Size}, rectangle::{Corners, Sides}, UiInstance
};
use hui_glium::GliumUiRenderer;
@ -69,23 +71,26 @@ fn main() {
padding: Sides::all(5.),
gap: 10.,
children: ElementList(vec![
Box::new(Rect {
size: (Size::Fraction(0.5), Size::Static(30.)),
color: vec4(0.75, 0., 0., 1.).into()
Box::new(FillRect {
size: (Size::Fraction(0.5), Size::Static(30.)).into(),
background: vec4(0.75, 0., 0., 1.).into(),
..Default::default()
}),
Box::new(Rect {
size: (Size::Fraction(z / 2. + 0.5), Size::Static(30.)),
color: Corners::left_right(
Box::new(FillRect {
size: (Size::Fraction(z / 2. + 0.5), Size::Static(30.)).into(),
background: Corners::left_right(
vec4(1., 0., 0., 1.),
vec4(0., 1., 0., 1.)
).into(),
..Default::default()
}),
]),
..Default::default()
}),
Box::new(Rect {
size: (Size::Fraction(z / 2. + 0.5), Size::Static(30.)),
color: vec4(0., 0.75, 0., 1.).into()
Box::new(FillRect {
size: (Size::Fraction(z / 2. + 0.5), Size::Static(30.)).into(),
background: vec4(0., 0.75, 0., 1.).into(),
..Default::default()
}),
Box::new(Container {
gap: 5.,
@ -95,13 +100,14 @@ fn main() {
children: {
let mut x: Vec<Box<dyn UiElement>> = vec![];
for i in 0..10 {
x.push(Box::new(Rect {
size: (Size::Static(50.), Size::Static(50.)),
color: if i == 1 {
x.push(Box::new(FillRect {
size: (Size::Static(50.), Size::Static(50.)).into(),
background: if i == 1 {
vec4(0.75, 0.75, 0.75, 0.75).into()
} else {
vec4(0.5, 0.5, 0.5, 0.75).into()
}
},
..Default::default()
}));
}
ElementList(x)
@ -123,9 +129,10 @@ fn main() {
bottom_right: 0.,
},
children: ElementList(vec![
Box::new(Rect {
size: (Size::Static(50.), Size::Static(50.)),
color: vec4(1., 1., 1., 0.75).into()
Box::new(FillRect {
size: (Size::Static(50.), Size::Static(50.)).into(),
background: vec4(1., 1., 1., 0.75).into(),
..Default::default()
}),
]),
..Default::default()

View file

@ -1,76 +0,0 @@
use glam::UVec2;
use glium::{backend::glutin::SimpleWindowBuilder, Surface};
use winit::{
event::{Event, WindowEvent},
event_loop::{EventLoopBuilder, ControlFlow}
};
use hui::{
UiInstance, color, size,
layout::Alignment,
element::{
container::Container, text::Text, UiElementExt
},
};
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();
Container::default()
.with_size(size!(100%, 50%))
.with_align(Alignment::Center)
.with_padding(5.)
.with_gap(10.)
.with_corner_radius(10.)
.with_background(color::WHITE)
.with_children(|ui| {
Text::default()
.with_text("Hello, world")
.with_text_size(100)
.with_color(color::BLACK)
.add_child(ui);
Container::default()
.with_padding((10., 20.))
.with_corner_radius((2.5, 30., 2.5, 2.5))
.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);
hui.end();
backend.update(&hui);
backend.draw(&mut frame, resolution);
frame.finish().unwrap();
}
_ => (),
}
}).unwrap();
}

View file

@ -1,3 +1,5 @@
//WARNING: THIS EXAMPLE IS EXTREMELY OUTDATED AND USES DEPRECATED API
use std::time::Instant;
use glam::{UVec2, vec4};
use glium::{backend::glutin::SimpleWindowBuilder, Surface};

View file

@ -1,3 +1,5 @@
//WARNING: THIS EXAMPLE IS EXTREMELY OUTDATED AND USES DEPRECATED API
use glam::{vec4, UVec2};
use glium::{backend::glutin::SimpleWindowBuilder, Surface};
use winit::{

View file

@ -1,3 +1,5 @@
//WARNING: THIS EXAMPLE IS EXTREMELY OUTDATED AND USES DEPRECATED API
use std::time::Instant;
use glam::{UVec2, vec4};
use glium::{backend::glutin::SimpleWindowBuilder, Surface};
@ -7,7 +9,7 @@ use winit::{
};
use hui::{
element::{
container::Container, rect::Rect, spacer::Spacer, text::Text, ElementList
container::Container, fill_rect::FillRect, spacer::Spacer, text::Text, ElementList
}, layout::Size, UiInstance
};
use hui_glium::GliumUiRenderer;
@ -69,13 +71,15 @@ fn main() {
..Default::default()
}));
}
elem.push(Box::new(Rect {
size: (Size::Fraction(1.), Size::Static(10.)),
color: vec4(0., 0., 1., 1.).into(),
elem.push(Box::new(FillRect {
size: (Size::Fraction(1.), Size::Static(10.)).into(),
background: vec4(0., 0., 1., 1.).into(),
..Default::default()
}));
elem.push(Box::new(Rect {
size: (Size::Fraction(1.), Size::Static(10.)),
color: vec4(1., 1., 0., 1.).into(),
elem.push(Box::new(FillRect {
size: (Size::Fraction(1.), Size::Static(10.)).into(),
background: vec4(1., 1., 0., 1.).into(),
..Default::default()
}));
elem.push(Box::new(Text {
text: "Hello, world!\nżółty liść. życie nie ma sensu i wszyscy zginemy;\nтест кирилиці їїїїїїїїїїї\njapanese text: テスト".into(),
@ -84,13 +88,15 @@ fn main() {
..Default::default()
}));
if instant.elapsed().as_secs() & 1 != 0 {
elem.push(Box::new(Rect {
size: (Size::Fraction(1.), Size::Static(10.)),
color: vec4(1., 0., 0., 1.).into(),
elem.push(Box::new(FillRect {
size: (Size::Fraction(1.), Size::Static(10.)).into(),
background: vec4(1., 0., 0., 1.).into(),
..Default::default()
}));
elem.push(Box::new(Rect {
size: (Size::Fraction(1.), Size::Static(10.)),
color: vec4(0., 0., 0., 1.).into(),
elem.push(Box::new(FillRect {
size: (Size::Fraction(1.), Size::Static(10.)).into(),
background: vec4(0., 0., 0., 1.).into(),
..Default::default()
}));
elem.push(Box::new(Spacer(100.)));
elem.push(Box::new(Text {

View file

@ -0,0 +1,38 @@
use hui::{
color, size,
layout::Alignment,
element::{UiElementExt, container::Container, text::Text},
};
#[path = "../boilerplate.rs"]
#[macro_use]
mod boilerplate;
ui_main!(|ui, size| {
Container::default()
.with_size(size!(100%, 50%))
.with_align(Alignment::Center)
.with_padding(5.)
.with_gap(10.)
.with_corner_radius(10.)
.with_background(color::WHITE)
.with_children(|ui| {
Text::default()
.with_text("Hello, world")
.with_text_size(100)
.with_color(color::BLACK)
.add_child(ui);
Container::default()
.with_padding((10., 20.))
.with_corner_radius((2.5, 30., 2.5, 2.5))
.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(ui, size);
});

View file

@ -26,7 +26,7 @@ nz = "0.3"
document-features = "0.2"
derive_setters = "0.1"
#smallvec = "1.13"
#tinyset = "0.4"
tinyset = "0.4"
[features]
default = ["builtin_elements", "builtin_font", "pixel_perfect_text"]

View file

@ -9,27 +9,28 @@ use crate::rectangle::Corners;
// pub texture: Option<TextureH>
// }
//TODO: move this into the color module?
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum BackgroundColor {
pub enum RectBackground {
#[default]
Transparent,
Solid(Vec4),
Gradient(Corners<Vec4>),
}
impl From<(f32, f32, f32, f32)> for BackgroundColor {
impl From<(f32, f32, f32, f32)> for RectBackground {
fn from(color: (f32, f32, f32, f32)) -> Self {
Self::Solid(vec4(color.0, color.1, color.2, color.3))
}
}
impl From<Corners<Vec4>> for BackgroundColor {
impl From<Corners<Vec4>> for RectBackground {
fn from(corners: Corners<Vec4>) -> Self {
Self::Gradient(corners)
}
}
impl From<Option<Vec4>> for BackgroundColor {
impl From<Option<Vec4>> for RectBackground {
fn from(color: Option<Vec4>) -> Self {
match color {
Some(color) => Self::Solid(color),
@ -38,19 +39,19 @@ impl From<Option<Vec4>> for BackgroundColor {
}
}
impl From<Vec4> for BackgroundColor {
impl From<Vec4> for RectBackground {
fn from(color: Vec4) -> Self {
Self::Solid(color)
}
}
impl From<(f32, f32, f32)> for BackgroundColor {
impl From<(f32, f32, f32)> for RectBackground {
fn from(color: (f32, f32, f32)) -> Self {
Self::Solid(vec4(color.0, color.1, color.2, 1.))
}
}
impl From<Corners<Vec3>> for BackgroundColor {
impl From<Corners<Vec3>> for RectBackground {
fn from(corners: Corners<Vec3>) -> Self {
Self::Gradient(Corners {
top_left: corners.top_left.extend(1.),
@ -61,7 +62,7 @@ impl From<Corners<Vec3>> for BackgroundColor {
}
}
impl From<Option<Vec3>> for BackgroundColor {
impl From<Option<Vec3>> for RectBackground {
fn from(color: Option<Vec3>) -> Self {
match color {
Some(color) => Self::Solid(color.extend(1.)),
@ -70,13 +71,13 @@ impl From<Option<Vec3>> for BackgroundColor {
}
}
impl From<Vec3> for BackgroundColor {
impl From<Vec3> for RectBackground {
fn from(color: Vec3) -> Self {
Self::Solid(color.extend(1.))
}
}
impl BackgroundColor {
impl RectBackground {
/// Currently, never returns None.\
/// `Option` has been added in preparation for future changes.\
/// (`Background::Texture` etc)

View file

@ -2,7 +2,7 @@
pub mod container;
#[cfg(feature = "builtin_elements")]
pub mod rect;
pub mod fill_rect;
#[cfg(feature = "builtin_elements")]
pub mod spacer;

View file

@ -1,7 +1,7 @@
use derive_setters::Setters;
use glam::{Vec2, vec2};
use crate::{
background::BackgroundColor,
background::RectBackground,
draw::{RoundedCorners, UiDrawCommand},
element::{ElementList, MeasureContext, ProcessContext, UiElement},
layout::{Alignment, Alignment2d, LayoutInfo, UiDirection, Size, Size2d},
@ -31,7 +31,7 @@ pub struct Container {
#[setters(into)]
pub align: Alignment2d,
#[setters(into)]
pub background: BackgroundColor,
pub background: RectBackground,
#[setters(into)]
pub corner_radius: Corners<f32>,
#[setters(skip)]

View file

@ -0,0 +1,75 @@
//! Simple filled rectangle with the specified size, background and corner radius
use derive_setters::Setters;
use glam::{vec2, Vec4};
use crate::{
background::RectBackground,
draw::{UiDrawCommand, RoundedCorners},
element::{UiElement, MeasureContext, ProcessContext},
layout::{Size, Size2d},
measure::Response,
rectangle::Corners,
size,
};
/// Simple filled rectangle with the specified size, background, and corner radius
#[derive(Debug, Clone, Copy, Setters)]
#[setters(prefix = "with_")]
pub struct FillRect {
/// Size of the rectangle
#[setters(into)]
pub size: Size2d,
/// Background color of the rectangle
#[setters(into)]
pub background: RectBackground,
/// Corner radius of the rectangle
#[setters(into)]
pub corner_radius: Corners<f32>,
}
impl Default for FillRect {
fn default() -> Self {
Self {
size: size!(10, 10),
background: Vec4::new(0., 0., 0., 0.5).into(),
corner_radius: Corners::all(0.),
}
}
}
impl UiElement for FillRect {
fn measure(&self, ctx: MeasureContext) -> Response {
Response {
size: vec2(
match self.size.width {
Size::Auto => ctx.layout.max_size.x,
Size::Fraction(percentage) => ctx.layout.max_size.x * percentage,
Size::Static(pixels) => pixels,
},
match self.size.height {
Size::Auto => ctx.layout.max_size.y,
Size::Fraction(percentage) => ctx.layout.max_size.y * percentage,
Size::Static(pixels) => pixels,
},
),
hints: Default::default(),
user_data: None
}
}
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().unwrap(),
texture: None,
rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({
RoundedCorners::from_radius(self.corner_radius)
}),
});
}
}
}

View file

@ -1,14 +1,25 @@
use glam::{vec2, Vec4, vec4};
use derive_setters::Setters;
use glam::{vec2, vec4};
use crate::{
draw::{RoundedCorners, UiDrawCommand}, element::{MeasureContext, ProcessContext, UiElement}, layout::Size, measure::Response, rectangle::Corners
background::RectBackground,
draw::{RoundedCorners, UiDrawCommand},
element::{MeasureContext, ProcessContext, UiElement},
layout::{Size, Size2d},
measure::Response,
rectangle::Corners
};
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Setters)]
#[setters(prefix = "with_")]
pub struct ProgressBar {
pub size: (Size, Size),
pub value: f32,
pub color_foreground: Vec4,
pub color_background: Vec4,
#[setters(into)]
pub size: Size2d,
#[setters(into)]
pub foreground: RectBackground,
#[setters(into)]
pub background: RectBackground,
#[setters(into)]
pub corner_radius: Corners<f32>,
}
@ -19,10 +30,10 @@ impl ProgressBar {
impl Default for ProgressBar {
fn default() -> Self {
Self {
size: (Size::Auto, Size::Auto),
value: 0.,
color_foreground: vec4(0.0, 0.0, 1.0, 1.0),
color_background: vec4(0.0, 0.0, 0.0, 1.0),
size: Size::Auto.into(),
foreground: vec4(0.0, 0.0, 1.0, 1.0).into(),
background: vec4(0.0, 0.0, 0.0, 1.0).into(),
corner_radius: Corners::all(0.),
}
}
@ -34,12 +45,12 @@ impl UiElement for ProgressBar {
fn measure(&self, ctx: MeasureContext) -> Response {
Response {
size: vec2(
match self.size.0 {
match self.size.width {
Size::Auto => ctx.layout.max_size.x.max(300.),
Size::Fraction(p) => ctx.layout.max_size.x * p,
Size::Static(p) => p,
},
match self.size.1 {
match self.size.height {
Size::Auto => Self::DEFAULT_HEIGHT,
Size::Fraction(p) => ctx.layout.max_size.y * p,
Size::Static(p) => p,
@ -71,7 +82,7 @@ impl UiElement for ProgressBar {
ctx.draw.add(UiDrawCommand::Rectangle {
position: ctx.layout.position,
size: ctx.measure.size,
color: Corners::all(self.color_background),
color: self.background.corners().unwrap(),
texture: None,
rounded_corners
});
@ -80,7 +91,7 @@ impl UiElement for ProgressBar {
ctx.draw.add(UiDrawCommand::Rectangle {
position: ctx.layout.position,
size: ctx.measure.size * vec2(value, 1.0),
color: Corners::all(self.color_foreground),
color: self.foreground.corners().unwrap(),
texture: None,
rounded_corners,
});

View file

@ -1,55 +0,0 @@
use glam::{vec2, Vec4};
use crate::{
background::BackgroundColor,
draw::UiDrawCommand,
element::{MeasureContext, ProcessContext, UiElement},
layout::Size,
measure::Response
};
pub struct Rect {
pub size: (Size, Size),
pub color: BackgroundColor,
}
impl Default for Rect {
fn default() -> Self {
Self {
size: (Size::Static(10.), Size::Static(10.)),
color: Vec4::new(0., 0., 0., 0.5).into(),
}
}
}
impl UiElement for Rect {
fn measure(&self, ctx: MeasureContext) -> Response {
Response {
size: vec2(
match self.size.0 {
Size::Auto => ctx.layout.max_size.x,
Size::Fraction(percentage) => ctx.layout.max_size.x * percentage,
Size::Static(pixels) => pixels,
},
match self.size.1 {
Size::Auto => ctx.layout.max_size.y,
Size::Fraction(percentage) => ctx.layout.max_size.y * percentage,
Size::Static(pixels) => pixels,
},
),
hints: Default::default(),
user_data: None
}
}
fn process(&self, ctx: ProcessContext) {
if !self.color.is_transparent() {
ctx.draw.add(UiDrawCommand::Rectangle {
position: ctx.layout.position,
size: ctx.measure.size,
color: self.color.corners().unwrap(),
texture: None,
rounded_corners: None,
});
}
}
}

View file

@ -1,3 +1,5 @@
//! Adds spacing between elements in a layout
use glam::vec2;
use crate::{
element::{MeasureContext, ProcessContext, UiElement},
@ -5,6 +7,8 @@ use crate::{
layout::UiDirection
};
/// Adds spacing between elements in a layout\
/// (depending on the current layout direction)
pub struct Spacer(pub f32);
impl Default for Spacer {

View file

@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher};
use glam::Vec2;
use hashbrown::HashMap;
use nohash_hasher::BuildNoHashHasher;
use tinyset::{SetU32, SetUsize};
use crate::rectangle::Rect;
/// Represents a mouse button.
@ -110,6 +111,15 @@ pub(crate) enum Pointer {
TouchFinger(TouchFinger),
}
impl Pointer {
pub fn current_position(&self) -> Vec2 {
match self {
Pointer::MousePointer(mouse) => mouse.current_position,
Pointer::TouchFinger(touch) => touch.current_position,
}
}
}
impl ActiveMouseButton {
/// Check if the pointer (mouse or touch) was just pressed\
/// (i.e. it was not pressed in the previous frame, but is pressed now)
@ -128,8 +138,38 @@ impl ActiveMouseButton {
}
}
pub struct PointerQuery<'a> {
pointers: &'a [Pointer],
/// Set of pointer IDs to filter **out**
filter_out: SetUsize,
}
impl<'a> PointerQuery<'a> {
fn new(pointers: &'a [Pointer]) -> Self {
Self {
pointers,
filter_out: SetUsize::new(),
}
}
/// Filter pointers that are *currently* located within the specified rectangle
pub fn within_rect(&mut self, rect: Rect) -> &mut Self {
for (idx, pointer) in self.pointers.iter().enumerate() {
if !rect.contains_point(pointer.current_position()) {
self.filter_out.insert(idx);
}
}
self
}
/// Check if any pointers matched the filter
pub fn any_matched(&self) -> bool {
self.filter_out.len() != self.pointers.len()
}
}
pub(crate) struct UiInputState {
pointers: Vec<ActiveMouseButton>,
pointers: Vec<Pointer>,
}
impl UiInputState {
@ -139,7 +179,7 @@ impl UiInputState {
}
}
pub fn query_pointer(&self, area: Rect) -> bool {
todo!()
pub fn query_pointer(&self) -> PointerQuery {
PointerQuery::new(&self.pointers)
}
}

View file

@ -8,6 +8,11 @@ pub struct Rect {
pub size: Vec2,
}
impl Rect {
pub fn contains_point(&self, point: Vec2) -> bool {
point.cmpge(self.position).all() && point.cmple(self.position + self.size).all()
}
}
/// Represents 4 sides of a rectangular shape.
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Sides<T> {