mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-22 07:08:42 -06:00
Compare commits
33 commits
2db8d2f056
...
ec4404b26c
Author | SHA1 | Date | |
---|---|---|---|
griffi-gh | ec4404b26c | ||
griffi-gh | 9e9cf7d1c2 | ||
griffi-gh | bf7244ea33 | ||
griffi-gh | 787b20b3db | ||
griffi-gh | dd5af8b9e2 | ||
griffi-gh | cf106bb893 | ||
griffi-gh | adbc81e704 | ||
griffi-gh | 964cf22372 | ||
griffi-gh | d14f27a428 | ||
griffi-gh | 405963460d | ||
griffi-gh | dca0c0d2a4 | ||
griffi-gh | 4e4c16ce76 | ||
griffi-gh | 72ff23ac0b | ||
griffi-gh | 66ef58a131 | ||
griffi-gh | efe7326b4d | ||
griffi-gh | 55c908a3b9 | ||
griffi-gh | 6f7f3bc8b0 | ||
griffi-gh | d8b470d805 | ||
griffi-gh | c1be9bf22b | ||
griffi-gh | 3c6e6be754 | ||
griffi-gh | 6da1cc5d88 | ||
griffi-gh | 535a56a257 | ||
griffi-gh | bf0b4dcdf2 | ||
griffi-gh | 807c9b087d | ||
griffi-gh | bd9c3aec81 | ||
griffi-gh | 579b7c5484 | ||
griffi-gh | af0bf04ffc | ||
griffi-gh | 36e6fc50ec | ||
griffi-gh | 7daf4c44fa | ||
griffi-gh | edb7305d7e | ||
griffi-gh | 7b4772ca94 | ||
griffi-gh | 19ca54b1f3 | ||
griffi-gh | c0af88fee8 |
16
.devcontainer/devcontainer.json
Normal file
16
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/rust:1": {},
|
||||||
|
"ghcr.io/devcontainers/features/desktop-lite:1": {}
|
||||||
|
},
|
||||||
|
"forwardPorts": [6080, 5901],
|
||||||
|
"portsAttributes": {
|
||||||
|
"6080": {
|
||||||
|
"label": "desktop"
|
||||||
|
},
|
||||||
|
"5901": {
|
||||||
|
"label": "desktop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
.markdownlint.jsonc
Normal file
5
.markdownlint.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"MD041": false, //first-line-heading
|
||||||
|
"MD013": false, //line-length
|
||||||
|
"MD033": false //inline-html
|
||||||
|
}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.insertSpaces": true
|
"editor.insertSpaces": true,
|
||||||
|
"editor.wordWrap": "off"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["hui", "hui-examples", "hui-glium", "hui-winit"]
|
members = ["hui", "hui-derive", "hui-examples", "hui-glium", "hui-winit"]
|
||||||
|
|
40
README.md
40
README.md
|
@ -1,5 +1,5 @@
|
||||||
<p></p><p></p>
|
<p></p><p></p>
|
||||||
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/hui.svg" width="120" align="left">
|
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/hui.svg" width="120" align="left" alt="logo">
|
||||||
<h1>hUI</h1>
|
<h1>hUI</h1>
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
|
@ -17,22 +17,25 @@
|
||||||
|
|
||||||
<table align="center">
|
<table align="center">
|
||||||
<td>
|
<td>
|
||||||
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/demo0.gif" width="300">
|
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/demo0.gif" width="300" alt="example: mom_downloader">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/demo1.gif" width="300">
|
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/demo1.gif" width="300" alt="example: align_test">
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h2>Example</h2>
|
<h2>Example</h2>
|
||||||
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/exemplaris.png" height="175" align="right" float="right">
|
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/exemplaris.png"
|
||||||
|
height="175" align="right" float="right" alt="code result">
|
||||||
<pre lang="rust">Container::default()
|
<pre lang="rust">Container::default()
|
||||||
.with_size(size!(100%, 50%))
|
.with_size(size!(100%, 50%))
|
||||||
.with_align(Alignment::Center)
|
.with_align(Alignment::Center)
|
||||||
.with_padding(5.)
|
.with_padding(5.)
|
||||||
.with_gap(10.)
|
.with_gap(10.)
|
||||||
.with_corner_radius(10.)
|
.with_background(frame_rect! {
|
||||||
.with_background(color::WHITE)
|
color: (0.5, 0.5, 0.5, 1.),
|
||||||
|
corner_radius: 10.,
|
||||||
|
})
|
||||||
.with_children(|ui| {
|
.with_children(|ui| {
|
||||||
Text::default()
|
Text::default()
|
||||||
.with_text("Hello, world")
|
.with_text("Hello, world")
|
||||||
|
@ -41,8 +44,10 @@
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_padding((10., 20.))
|
.with_padding((10., 20.))
|
||||||
.with_corner_radius((2.5, 30., 2.5, 2.5))
|
.with_background(frame_rect! {
|
||||||
.with_background(color::DARK_RED)
|
color: color::DARK_RED,
|
||||||
|
corner_radius: (2.5, 30., 2.5, 2.5),
|
||||||
|
})
|
||||||
.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.")
|
||||||
|
@ -51,7 +56,7 @@
|
||||||
})
|
})
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_root(&mut hui, resolution);</pre>
|
.add_root(ui, size);</pre>
|
||||||
|
|
||||||
<h2>Backends</h2>
|
<h2>Backends</h2>
|
||||||
<p>
|
<p>
|
||||||
|
@ -89,6 +94,20 @@
|
||||||
</td>
|
</td>
|
||||||
<td align="center">(support planned)</td>
|
<td align="center">(support planned)</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<code>0.1.0-alpha.4</code>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<code>hui-glium = "0.1.0-alpha.4"</code><br>
|
||||||
|
<code>glium = "0.34"</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<code>hui-winit = "0.1.0-alpha.4"</code><br>
|
||||||
|
<code>winit = "0.29"</code>
|
||||||
|
</td>
|
||||||
|
<td align="center">N/A</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<code>0.1.0-alpha.3</code>
|
<code>0.1.0-alpha.3</code>
|
||||||
|
@ -140,3 +159,6 @@
|
||||||
<td align="center">-</td>
|
<td align="center">-</td>
|
||||||
</tr> -->
|
</tr> -->
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<h2>MSRV</h2>
|
||||||
|
1.75
|
||||||
|
|
23
hui-derive/Cargo.toml
Normal file
23
hui-derive/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "hui-derive"
|
||||||
|
description = "Derive macros for hUI"
|
||||||
|
repository = "https://github.com/griffi-gh/hui"
|
||||||
|
readme = "../README.md"
|
||||||
|
authors = ["griffi-gh <prasol258@gmail.com>"]
|
||||||
|
rust-version = "1.75"
|
||||||
|
version = "0.1.0-alpha.5"
|
||||||
|
edition = "2021"
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
publish = true
|
||||||
|
include = [
|
||||||
|
"assets/**/*",
|
||||||
|
"src/**/*.rs",
|
||||||
|
"Cargo.toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0"
|
||||||
|
syn = "2.0"
|
13
hui-derive/src/lib.rs
Normal file
13
hui-derive/src/lib.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
/// Implements `Signal` trait for the given type
|
||||||
|
#[proc_macro_derive(Signal)]
|
||||||
|
pub fn signal(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = input.ident;
|
||||||
|
quote!(impl ::hui::signal::Signal for #name {}).into()
|
||||||
|
}
|
|
@ -14,5 +14,6 @@ glium = "0.34"
|
||||||
winit = "0.29"
|
winit = "0.29"
|
||||||
glam = "0.27"
|
glam = "0.27"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
image = { version = "0.25", features = ["jpeg", "png"] }
|
||||||
|
|
||||||
#created as a workaround for rust-analyzer dependency cycle (which should be allowed)
|
#created as a workaround for rust-analyzer dependency cycle (which should be allowed)
|
||||||
|
|
BIN
hui-examples/assets/ninepatch_button.png
Normal file
BIN
hui-examples/assets/ninepatch_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 638 B |
|
@ -62,7 +62,7 @@ fn main() {
|
||||||
children: ElementList(vec![
|
children: ElementList(vec![
|
||||||
Box::new(ProgressBar {
|
Box::new(ProgressBar {
|
||||||
value: z,
|
value: z,
|
||||||
corner_radius: Corners::all(0.25 * ProgressBar::DEFAULT_HEIGHT),
|
// corner_radius: Corners::all(0.25 * ProgressBar::DEFAULT_HEIGHT),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
Box::new(Container {
|
Box::new(Container {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use hui::{
|
use hui::{
|
||||||
element::{
|
color, element::{
|
||||||
container::Container,
|
container::Container,
|
||||||
progress_bar::ProgressBar,
|
progress_bar::ProgressBar,
|
||||||
text::Text,
|
text::Text,
|
||||||
|
@ -43,7 +43,14 @@ ui_main!{
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
ProgressBar::default()
|
ProgressBar::default()
|
||||||
.with_value(mom_ratio)
|
.with_value(mom_ratio)
|
||||||
.with_corner_radius(0.125 * ProgressBar::DEFAULT_HEIGHT)
|
.with_background(frame_rect! {
|
||||||
|
color: color::BLACK,
|
||||||
|
corner_radius: 0.125 * ProgressBar::DEFAULT_HEIGHT
|
||||||
|
})
|
||||||
|
.with_foreground(frame_rect! {
|
||||||
|
color: color::BLUE,
|
||||||
|
corner_radius: 0.125 * ProgressBar::DEFAULT_HEIGHT
|
||||||
|
})
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_direction(Direction::Horizontal)
|
.with_direction(Direction::Horizontal)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use hui::{
|
use hui::{
|
||||||
color, size,
|
color, size,
|
||||||
draw::TextureFormat,
|
|
||||||
signal::Signal,
|
signal::Signal,
|
||||||
|
draw::TextureFormat,
|
||||||
layout::{Alignment, Direction},
|
layout::{Alignment, Direction},
|
||||||
element::{
|
element::{
|
||||||
container::Container,
|
container::Container,
|
||||||
|
@ -13,11 +13,11 @@ use hui::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Signal)]
|
||||||
enum CounterSignal {
|
enum CounterSignal {
|
||||||
Increment,
|
Increment,
|
||||||
Decrement,
|
Decrement,
|
||||||
}
|
}
|
||||||
impl Signal for CounterSignal {}
|
|
||||||
|
|
||||||
#[path = "../boilerplate.rs"]
|
#[path = "../boilerplate.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -8,15 +8,15 @@ use hui::{
|
||||||
text::Text,
|
text::Text,
|
||||||
UiElementExt,
|
UiElementExt,
|
||||||
},
|
},
|
||||||
layout::{Alignment, Direction},
|
|
||||||
signal::Signal,
|
signal::Signal,
|
||||||
|
layout::{Alignment, Direction},
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Signal)]
|
||||||
enum CounterSignal {
|
enum CounterSignal {
|
||||||
ChangeValue(u32)
|
ChangeValue(u32)
|
||||||
}
|
}
|
||||||
impl Signal for CounterSignal {}
|
|
||||||
|
|
||||||
#[path = "../boilerplate.rs"]
|
#[path = "../boilerplate.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -1,34 +1,92 @@
|
||||||
use std::time::Instant;
|
use glam::vec2;
|
||||||
use hui::{
|
use hui::{
|
||||||
color, element::{
|
color,
|
||||||
|
element::{
|
||||||
container::Container,
|
container::Container,
|
||||||
fill_rect::FillRect,
|
fill_rect::FillRect,
|
||||||
|
slider::Slider,
|
||||||
|
text::Text,
|
||||||
UiElementExt
|
UiElementExt
|
||||||
}, frame_rect, layout::{Alignment, Direction}, size
|
},
|
||||||
|
frame::nine_patch::{NinePatchAsset, NinePatchFrame},
|
||||||
|
layout::Alignment,
|
||||||
|
rect::Rect,
|
||||||
|
signal::Signal,
|
||||||
|
size,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "../boilerplate.rs"]
|
#[path = "../boilerplate.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod boilerplate;
|
mod boilerplate;
|
||||||
|
|
||||||
|
#[derive(Signal)]
|
||||||
|
struct SetValue(f32);
|
||||||
|
|
||||||
ui_main!(
|
ui_main!(
|
||||||
"hUI: 9-Patch demo",
|
"hUI: 9-Patch demo",
|
||||||
init: |_| {
|
init: |ui| {
|
||||||
|
(
|
||||||
|
NinePatchAsset {
|
||||||
|
image: ui.add_image_file_path("./hui-examples/assets/ninepatch_button.png").unwrap(),
|
||||||
|
size: (190, 49),
|
||||||
|
scalable_region: Rect {
|
||||||
|
position: vec2(8. / 190., 8. / 49.),
|
||||||
|
size: vec2(1. - 16. / 190., 1. - 18. / 49.),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0.33,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
run: |ui, size, _| {
|
run: |ui, size, (asset, value)| {
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_size(size!(100%))
|
.with_size(size!(100%))
|
||||||
.with_align(Alignment::Center)
|
.with_align(Alignment::Center)
|
||||||
|
.with_gap(5.)
|
||||||
.with_background(color::WHITE)
|
.with_background(color::WHITE)
|
||||||
.with_children(|ui| {
|
.with_children(|ui| {
|
||||||
FillRect::default()
|
Container::default()
|
||||||
.with_size(size!(300, 100))
|
.with_size(size!(300, 100))
|
||||||
.with_frame(frame_rect! {
|
.with_background(NinePatchFrame::from_asset(*asset).with_color(color::RED))
|
||||||
color: color::RED
|
.with_padding(10.)
|
||||||
|
.with_children(|ui| {
|
||||||
|
Text::new("Hello, world!\nThis is a 9-patch frame used as a background \nfor Container with a Text element.\nIt's scalable and looks great!\nBelow, there are two FillRects with the same \n9-patch frame used as the background.")
|
||||||
|
.with_text_size(16)
|
||||||
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
|
FillRect::default()
|
||||||
|
.with_size(size!(600, 75))
|
||||||
|
.with_frame(NinePatchFrame::from_asset(*asset).with_color(color::GREEN))
|
||||||
|
.add_child(ui);
|
||||||
|
Text::new("This one's fancy:")
|
||||||
|
.with_color(color::BLACK)
|
||||||
|
.with_text_size(32)
|
||||||
|
.add_child(ui);
|
||||||
|
FillRect::default()
|
||||||
|
.with_size(size!(700, 50))
|
||||||
|
.with_frame(NinePatchFrame::from_asset(*asset).with_color((
|
||||||
|
(1., 0., 1.),
|
||||||
|
(0., 1., 1.),
|
||||||
|
(1., 1., 0.),
|
||||||
|
(0., 0., 1.),
|
||||||
|
)))
|
||||||
|
.add_child(ui);
|
||||||
|
Text::new("Slider customized with `NinePatchFrame`s:")
|
||||||
|
.with_color(color::BLACK)
|
||||||
|
.with_text_size(32)
|
||||||
|
.add_child(ui);
|
||||||
|
Slider::new(*value)
|
||||||
|
.with_size(size!(50%, 30))
|
||||||
|
.with_track_height(1.)
|
||||||
|
.with_handle_size((20., 1.))
|
||||||
|
.with_handle(NinePatchFrame::from_asset(*asset).with_color(color::CYAN))
|
||||||
|
.with_track(NinePatchFrame::from_asset(*asset))
|
||||||
|
.with_track_active(NinePatchFrame::from_asset(*asset).with_color(color::SKY_BLUE))
|
||||||
|
.on_change(SetValue)
|
||||||
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_root(ui, size);
|
.add_root(ui, size);
|
||||||
|
|
||||||
|
ui.process_signals::<SetValue>(|signal| *value = signal.0);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -38,7 +38,7 @@ ui_main!(
|
||||||
.with_size(size!(100%, auto))
|
.with_size(size!(100%, auto))
|
||||||
.with_direction(Direction::Horizontal)
|
.with_direction(Direction::Horizontal)
|
||||||
.with_align((Alignment::Begin, Alignment::Center))
|
.with_align((Alignment::Begin, Alignment::Center))
|
||||||
.with_padding(8.)
|
.with_padding(5.)
|
||||||
.with_gap(15.)
|
.with_gap(15.)
|
||||||
.with_background(color::rgb_hex(0x3d3c3e))
|
.with_background(color::rgb_hex(0x3d3c3e))
|
||||||
.with_wrap(true) //XXX: not authentic but great for demostration
|
.with_wrap(true) //XXX: not authentic but great for demostration
|
||||||
|
@ -51,6 +51,15 @@ ui_main!(
|
||||||
.with_text_size(15)
|
.with_text_size(15)
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
}
|
}
|
||||||
|
Container::default()
|
||||||
|
.with_size(size!(100%=, 100%))
|
||||||
|
.with_align((Alignment::End, Alignment::Center))
|
||||||
|
.with_children(|ui| {
|
||||||
|
Text::new("- ×")
|
||||||
|
.with_text_size(32)
|
||||||
|
.add_child(ui);
|
||||||
|
})
|
||||||
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
FillRect::default()
|
FillRect::default()
|
||||||
|
@ -58,9 +67,10 @@ ui_main!(
|
||||||
.with_frame(color::rgb_hex(0x2d2d30))
|
.with_frame(color::rgb_hex(0x2d2d30))
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_size(size!(100%, 100%))
|
.with_size(size!(100%, 100%=))
|
||||||
.with_direction(Direction::Horizontal)
|
.with_direction(Direction::Horizontal)
|
||||||
.with_children(|ui| {
|
.with_children(|ui| {
|
||||||
|
// Sidebar:
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_size(size!(54, 100%))
|
.with_size(size!(54, 100%))
|
||||||
.with_background(color::rgb_hex(0x343334))
|
.with_background(color::rgb_hex(0x343334))
|
||||||
|
@ -69,6 +79,8 @@ ui_main!(
|
||||||
.with_size(size!(1, 100%))
|
.with_size(size!(1, 100%))
|
||||||
.with_frame(color::rgb_hex(0x2d2d30))
|
.with_frame(color::rgb_hex(0x2d2d30))
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
|
|
||||||
|
// Explorer pane:
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_size(size!(200, 100%))
|
.with_size(size!(200, 100%))
|
||||||
.with_padding((15., 8.))
|
.with_padding((15., 8.))
|
||||||
|
@ -78,20 +90,16 @@ ui_main!(
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
|
|
||||||
|
// "Code" pane
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_size(size!(100%, 100%))
|
.with_size(size!(100%=, 100%))
|
||||||
.with_background(color::rgb_hex(0x1f1e1f))
|
.with_background(color::rgb_hex(0x1f1e1f))
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
})
|
|
||||||
.add_root(ui, size);
|
|
||||||
|
|
||||||
//Bottom bar (yeah, it's basically fake/overlay)
|
//Status bar
|
||||||
Container::default()
|
|
||||||
.with_size(size!(100%))
|
|
||||||
.with_align((Alignment::Begin, Alignment::End))
|
|
||||||
.with_children(|ui| {
|
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_size(size!(100%, auto))
|
.with_size(size!(100%, auto))
|
||||||
.with_background(color::rgb_hex(0x0079cc))
|
.with_background(color::rgb_hex(0x0079cc))
|
||||||
|
@ -111,8 +119,8 @@ ui_main!(
|
||||||
.with_text_size(15)
|
.with_text_size(15)
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_root(ui, size);
|
.add_root(ui, size);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,8 @@ description = "glium render backend for `hui`"
|
||||||
repository = "https://github.com/griffi-gh/hui"
|
repository = "https://github.com/griffi-gh/hui"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
authors = ["griffi-gh <prasol258@gmail.com>"]
|
authors = ["griffi-gh <prasol258@gmail.com>"]
|
||||||
version = "0.1.0-alpha.4"
|
version = "0.1.0-alpha.5"
|
||||||
|
rust-version = "1.75"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
publish = true
|
publish = true
|
||||||
|
@ -15,7 +16,7 @@ include = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hui = { version = "=0.1.0-alpha.4", path = "../hui", default-features = false }
|
hui = { version = "=0.1.0-alpha.5", path = "../hui", default-features = false }
|
||||||
glium = { version = "0.34", default-features = false }
|
glium = { version = "0.34", default-features = false }
|
||||||
glam = "0.27"
|
glam = "0.27"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
13
hui-glium/shaders/fragment.150.frag
Normal file
13
hui-glium/shaders/fragment.150.frag
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#version 150 core
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp sampler2D;
|
||||||
|
|
||||||
|
out vec4 out_color;
|
||||||
|
in vec4 vtx_color;
|
||||||
|
in vec2 vtx_uv;
|
||||||
|
uniform sampler2D tex;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = texture(tex, vtx_uv) * vtx_color;
|
||||||
|
}
|
17
hui-glium/shaders/vertex.150.vert
Normal file
17
hui-glium/shaders/vertex.150.vert
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#version 150 core
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform vec2 resolution;
|
||||||
|
in vec2 uv;
|
||||||
|
in vec4 color;
|
||||||
|
in vec2 position;
|
||||||
|
out vec4 vtx_color;
|
||||||
|
out vec2 vtx_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vtx_color = color;
|
||||||
|
vtx_uv = uv;
|
||||||
|
vec2 pos2d = (vec2(2., -2.) * (position / resolution)) + vec2(-1, 1);
|
||||||
|
gl_Position = vec4(pos2d, 0., 1.);
|
||||||
|
}
|
|
@ -1,19 +1,17 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use glium::{
|
use glium::{
|
||||||
Blend, DrawParameters, IndexBuffer, Program, Surface, VertexBuffer,
|
backend::{Context, Facade}, implement_vertex, index::PrimitiveType, texture::{RawImage2d, Texture2d}, uniform, uniforms::{MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerBehavior, SamplerWrapFunction}, Api, Blend, DrawParameters, IndexBuffer, Program, Surface, VertexBuffer
|
||||||
implement_vertex, uniform,
|
|
||||||
backend::{Context, Facade},
|
|
||||||
index::PrimitiveType,
|
|
||||||
texture::{RawImage2d, Texture2d},
|
|
||||||
uniforms::{MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerBehavior, SamplerWrapFunction},
|
|
||||||
};
|
};
|
||||||
use hui::{
|
use hui::{
|
||||||
draw::{TextureAtlasMeta, UiDrawCall, UiVertex}, UiInstance
|
draw::{TextureAtlasMeta, UiDrawCall, UiVertex}, UiInstance
|
||||||
};
|
};
|
||||||
|
|
||||||
const VERTEX_SHADER: &str = include_str!("../shaders/vertex.vert");
|
const VERTEX_SHADER_GLES3: &str = include_str!("../shaders/vertex.es.vert");
|
||||||
const FRAGMENT_SHADER: &str = include_str!("../shaders/fragment.frag");
|
const FRAGMENT_SHADER_GLES3: &str = include_str!("../shaders/fragment.es.frag");
|
||||||
|
|
||||||
|
const VERTEX_SHADER_150: &str = include_str!("../shaders/vertex.150.vert");
|
||||||
|
const FRAGMENT_SHADER_150: &str = include_str!("../shaders/fragment.150.frag");
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -122,7 +120,10 @@ impl GliumUiRenderer {
|
||||||
pub fn new<F: Facade>(facade: &F) -> Self {
|
pub fn new<F: Facade>(facade: &F) -> Self {
|
||||||
log::info!("initializing hui-glium");
|
log::info!("initializing hui-glium");
|
||||||
Self {
|
Self {
|
||||||
program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(),
|
program: match facade.get_context().get_supported_glsl_version().0 {
|
||||||
|
Api::Gl => Program::from_source(facade, VERTEX_SHADER_150, FRAGMENT_SHADER_150, None).unwrap(),
|
||||||
|
Api::GlEs => Program::from_source(facade, VERTEX_SHADER_GLES3, FRAGMENT_SHADER_GLES3, None).unwrap(),
|
||||||
|
},
|
||||||
context: Rc::clone(facade.get_context()),
|
context: Rc::clone(facade.get_context()),
|
||||||
ui_texture: None,
|
ui_texture: None,
|
||||||
buffer_pair: None,
|
buffer_pair: None,
|
||||||
|
@ -183,7 +184,7 @@ impl GliumUiRenderer {
|
||||||
tex: Sampler(self.ui_texture.as_ref().unwrap(), SamplerBehavior {
|
tex: Sampler(self.ui_texture.as_ref().unwrap(), SamplerBehavior {
|
||||||
max_anisotropy: 1,
|
max_anisotropy: 1,
|
||||||
magnify_filter: MagnifySamplerFilter::Nearest,
|
magnify_filter: MagnifySamplerFilter::Nearest,
|
||||||
minify_filter: MinifySamplerFilter::NearestMipmapNearest,
|
minify_filter: MinifySamplerFilter::Linear,
|
||||||
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
|
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -4,7 +4,7 @@ description = "winit platform backend for `hui`"
|
||||||
repository = "https://github.com/griffi-gh/hui"
|
repository = "https://github.com/griffi-gh/hui"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
authors = ["griffi-gh <prasol258@gmail.com>"]
|
authors = ["griffi-gh <prasol258@gmail.com>"]
|
||||||
version = "0.1.0-alpha.4"
|
version = "0.1.0-alpha.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
publish = true
|
publish = true
|
||||||
|
@ -14,7 +14,7 @@ include = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hui = { version = "=0.1.0-alpha.4", path = "../hui", default-features = false }
|
hui = { version = "=0.1.0-alpha.5", path = "../hui", default-features = false }
|
||||||
winit = { version = "0.29", default-features = false }
|
winit = { version = "0.29", default-features = false }
|
||||||
glam = "0.27"
|
glam = "0.27"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -5,7 +5,7 @@ repository = "https://github.com/griffi-gh/hui"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
authors = ["griffi-gh <prasol258@gmail.com>"]
|
authors = ["griffi-gh <prasol258@gmail.com>"]
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
version = "0.1.0-alpha.4"
|
version = "0.1.0-alpha.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
publish = true
|
publish = true
|
||||||
|
@ -16,6 +16,7 @@ include = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
hui-derive = { version = "0.1.0-alpha.5", path = "../hui-derive", optional = true }
|
||||||
hashbrown = "0.14"
|
hashbrown = "0.14"
|
||||||
nohash-hasher = "0.2"
|
nohash-hasher = "0.2"
|
||||||
glam = "0.27"
|
glam = "0.27"
|
||||||
|
@ -29,15 +30,14 @@ tinyset = "0.4"
|
||||||
image = { version = "0.25", default-features = false, optional = true }
|
image = { version = "0.25", default-features = false, optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["el_all", "image", "builtin_font", "pixel_perfect_text"]
|
default = ["el_all", "image", "builtin_font", "pixel_perfect_text", "derive"]
|
||||||
|
|
||||||
#! Image loading support:
|
## Enable derive macros
|
||||||
|
derive = ["dep:hui-derive"]
|
||||||
|
|
||||||
## Enable image loading support using the `image` crate
|
## Enable image loading support using the `image` crate
|
||||||
image = ["dep:image"]
|
image = ["dep:image"]
|
||||||
|
|
||||||
#! #### Built-in font:
|
|
||||||
|
|
||||||
## Enable the built-in font (ProggyTiny, adds *35kb* to the executable)
|
## Enable the built-in font (ProggyTiny, adds *35kb* to the executable)
|
||||||
builtin_font = []
|
builtin_font = []
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
//! draw commands, tesselation and UI rendering.
|
//! draw commands, tesselation and UI rendering.
|
||||||
|
|
||||||
//TODO: 9-slice draw command
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
rect::Corners,
|
rect::Corners,
|
||||||
text::{FontHandle, TextRenderer}
|
text::{FontHandle, TextRenderer}
|
||||||
|
@ -35,6 +33,8 @@ pub enum UiDrawCommand {
|
||||||
color: Corners<Vec4>,
|
color: Corners<Vec4>,
|
||||||
///Texture
|
///Texture
|
||||||
texture: Option<ImageHandle>,
|
texture: Option<ImageHandle>,
|
||||||
|
///Sub-UV coordinates for the texture
|
||||||
|
texture_uv: Option<Corners<Vec2>>,
|
||||||
///Rounded corners
|
///Rounded corners
|
||||||
rounded_corners: Option<RoundedCorners>,
|
rounded_corners: Option<RoundedCorners>,
|
||||||
},
|
},
|
||||||
|
@ -161,10 +161,36 @@ impl UiDrawCall {
|
||||||
v.position += center;
|
v.position += center;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UiDrawCommand::Rectangle { position, size, color, texture, rounded_corners } => {
|
UiDrawCommand::Rectangle { position, size, color, texture, texture_uv, rounded_corners } => {
|
||||||
let uvs = texture
|
let uvs = texture
|
||||||
.map(|x| atlas.get_uv(x))
|
.map(|x| atlas.get_uv(x))
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.map(|guv| {
|
||||||
|
if let Some(texture_uv) = texture_uv {
|
||||||
|
//XXX: this may not work if the texture is rotated
|
||||||
|
//also is this slow?
|
||||||
|
|
||||||
|
let top = guv.top_left.lerp(guv.top_right, texture_uv.top_left.x);
|
||||||
|
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.top_left.x);
|
||||||
|
let top_left = top.lerp(bottom, texture_uv.top_left.y);
|
||||||
|
|
||||||
|
let top = guv.top_left.lerp(guv.top_right, texture_uv.top_right.x);
|
||||||
|
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.top_right.x);
|
||||||
|
let top_right = top.lerp(bottom, texture_uv.top_right.y);
|
||||||
|
|
||||||
|
let top = guv.top_left.lerp(guv.top_right, texture_uv.bottom_left.x);
|
||||||
|
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.bottom_left.x);
|
||||||
|
let bottom_left = top.lerp(bottom, texture_uv.bottom_left.y);
|
||||||
|
|
||||||
|
let top = guv.top_left.lerp(guv.top_right, texture_uv.bottom_right.x);
|
||||||
|
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.bottom_right.x);
|
||||||
|
let bottom_right = top.lerp(bottom, texture_uv.bottom_right.y);
|
||||||
|
|
||||||
|
Corners { top_left, top_right, bottom_left, bottom_right }
|
||||||
|
} else {
|
||||||
|
guv
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(Corners::all(Vec2::ZERO));
|
.unwrap_or(Corners::all(Vec2::ZERO));
|
||||||
|
|
||||||
let vidx = draw_call.vertices.len() as u32;
|
let vidx = draw_call.vertices.len() as u32;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::any::Any;
|
||||||
use crate::{
|
use crate::{
|
||||||
draw::{atlas::ImageCtx, UiDrawCommandList},
|
draw::{atlas::ImageCtx, UiDrawCommandList},
|
||||||
input::InputCtx,
|
input::InputCtx,
|
||||||
layout::LayoutInfo,
|
layout::{LayoutInfo, Size2d},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
signal::SignalStore,
|
signal::SignalStore,
|
||||||
state::StateRepo,
|
state::StateRepo,
|
||||||
|
@ -45,6 +45,11 @@ pub trait UiElement {
|
||||||
/// For example, "button" or "progress_bar"
|
/// For example, "button" or "progress_bar"
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Get the requested UiElement size
|
||||||
|
///
|
||||||
|
/// 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\
|
/// Get the unique id used for internal state management\
|
||||||
/// This value must be unique for each instance of the element
|
/// This value must be unique for each instance of the element
|
||||||
///
|
///
|
||||||
|
|
|
@ -4,10 +4,10 @@ use derive_setters::Setters;
|
||||||
use glam::{Vec2, vec2};
|
use glam::{Vec2, vec2};
|
||||||
use crate::{
|
use crate::{
|
||||||
element::{ElementList, MeasureContext, ProcessContext, UiElement},
|
element::{ElementList, MeasureContext, ProcessContext, UiElement},
|
||||||
layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d},
|
|
||||||
frame::{Frame, FrameRect},
|
frame::{Frame, FrameRect},
|
||||||
|
layout::{compute_size, Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d, WrapBehavior},
|
||||||
measure::{Hints, Response},
|
measure::{Hints, Response},
|
||||||
rect::{Sides, FillColor},
|
rect::Sides,
|
||||||
};
|
};
|
||||||
|
|
||||||
//XXX: add Order/Direction::Forward/Reverse or sth?
|
//XXX: add Order/Direction::Forward/Reverse or sth?
|
||||||
|
@ -19,6 +19,7 @@ use crate::{
|
||||||
struct CudLine {
|
struct CudLine {
|
||||||
start_idx: usize,
|
start_idx: usize,
|
||||||
content_size: Vec2,
|
content_size: Vec2,
|
||||||
|
remaining_space: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContainerUserData {
|
struct ContainerUserData {
|
||||||
|
@ -52,13 +53,9 @@ pub struct Container {
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub background_frame: Box<dyn Frame>,
|
pub background_frame: Box<dyn Frame>,
|
||||||
|
|
||||||
/// Set this to `true` to allow the elements wrap automatically
|
/// Controls if wrapping is enabled
|
||||||
///
|
#[setters(into)]
|
||||||
/// Disabling/enabling this does not affect explicit wrapping\
|
pub wrap: WrapBehavior,
|
||||||
/// (for example, `Br`, or any other element with `should_wrap` set to `true`)
|
|
||||||
///
|
|
||||||
/// This is an experimental feature and may not work as expected
|
|
||||||
pub wrap: bool,
|
|
||||||
|
|
||||||
/// List of children elements
|
/// List of children elements
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
|
@ -86,7 +83,7 @@ impl Default for Container {
|
||||||
padding: Sides::all(0.),
|
padding: Sides::all(0.),
|
||||||
align: Alignment2d::default(),
|
align: Alignment2d::default(),
|
||||||
background_frame: Box::<FrameRect>::default(),
|
background_frame: Box::<FrameRect>::default(),
|
||||||
wrap: false,
|
wrap: WrapBehavior::Allow,
|
||||||
children: ElementList(Vec::new()),
|
children: ElementList(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,19 +91,24 @@ impl Default for Container {
|
||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
pub fn measure_max_inner_size(&self, layout: &LayoutInfo) -> Vec2 {
|
pub fn measure_max_inner_size(&self, layout: &LayoutInfo) -> Vec2 {
|
||||||
let outer_size_x = match self.size.width {
|
// let outer_size_x = match self.size.width {
|
||||||
Size::Auto => layout.max_size.x,
|
// Size::Auto => layout.max_size.x,
|
||||||
Size::Relative(p) => layout.max_size.x * p,
|
// Size::Relative(p) => layout.max_size.x * p,
|
||||||
Size::Absolute(p) => p,
|
// Size::Absolute(p) => p,
|
||||||
};
|
// Size::Remaining(p) => match layout.direction {
|
||||||
let outer_size_y = match self.size.height {
|
// Direction::Horizontal => layout.remaining_space.unwrap_or(layout.max_size.x) * p,
|
||||||
Size::Auto => layout.max_size.y,
|
// Direction::Vertical => layout.max_size.x,
|
||||||
Size::Relative(p) => layout.max_size.y * p,
|
// }
|
||||||
Size::Absolute(p) => p,
|
// };
|
||||||
};
|
// let outer_size_y = match self.size.height {
|
||||||
|
// Size::Auto => layout.max_size.y,
|
||||||
|
// Size::Relative(p) => layout.max_size.y * p,
|
||||||
|
// Size::Absolute(p) => p,
|
||||||
|
// };
|
||||||
|
let outer_size = compute_size(layout, self.size, layout.max_size);
|
||||||
vec2(
|
vec2(
|
||||||
outer_size_x - (self.padding.left + self.padding.right),
|
outer_size.x - (self.padding.left + self.padding.right),
|
||||||
outer_size_y - (self.padding.top + self.padding.bottom),
|
outer_size.y - (self.padding.top + self.padding.bottom),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,6 +118,10 @@ impl UiElement for Container {
|
||||||
"container"
|
"container"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Option<Size2d> {
|
||||||
|
Some(self.size)
|
||||||
|
}
|
||||||
|
|
||||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||||
// XXX: If both axes are NOT set to auto, we should be able quickly return the size
|
// XXX: If both axes are NOT set to auto, we should be able quickly return the size
|
||||||
// ... but we can't, because we need to measure the children to get the inner_content_size and user_data values
|
// ... but we can't, because we need to measure the children to get the inner_content_size and user_data values
|
||||||
|
@ -130,11 +136,13 @@ impl UiElement for Container {
|
||||||
Size::Auto => ctx.layout.max_size.x,
|
Size::Auto => ctx.layout.max_size.x,
|
||||||
Size::Relative(p) => ctx.layout.max_size.x * p,
|
Size::Relative(p) => ctx.layout.max_size.x * p,
|
||||||
Size::Absolute(p) => p,
|
Size::Absolute(p) => p,
|
||||||
|
Size::Remaining(p) => ctx.layout.remaining_space.unwrap_or(ctx.layout.max_size.x) * p,
|
||||||
},
|
},
|
||||||
Direction::Vertical => match self.size.height {
|
Direction::Vertical => match self.size.height {
|
||||||
Size::Auto => ctx.layout.max_size.y,
|
Size::Auto => ctx.layout.max_size.y,
|
||||||
Size::Relative(p) => ctx.layout.max_size.y * p,
|
Size::Relative(p) => ctx.layout.max_size.y * p,
|
||||||
Size::Absolute(p) => p,
|
Size::Absolute(p) => p,
|
||||||
|
Size::Remaining(p) => ctx.layout.remaining_space.unwrap_or(ctx.layout.max_size.y) * p,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -160,10 +168,25 @@ impl UiElement for Container {
|
||||||
CudLine {
|
CudLine {
|
||||||
start_idx: 0,
|
start_idx: 0,
|
||||||
content_size: Vec2::ZERO,
|
content_size: Vec2::ZERO,
|
||||||
|
remaining_space: 0.,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//set to true if in the current line there is an element with Remaining size (line will have to be wrapped)
|
||||||
|
// let mut has_remaining = false;
|
||||||
|
|
||||||
for (idx, element) in self.children.0.iter().enumerate() {
|
for (idx, element) in self.children.0.iter().enumerate() {
|
||||||
|
if let Some(esize) = element.size() {
|
||||||
|
let pri_size = match self.direction {
|
||||||
|
Direction::Horizontal => esize.width,
|
||||||
|
Direction::Vertical => esize.height,
|
||||||
|
};
|
||||||
|
if matches!(pri_size, Size::Remaining(_)) {
|
||||||
|
//XXX: kinda a hack?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let measure = element.measure(MeasureContext{
|
let measure = element.measure(MeasureContext{
|
||||||
state: ctx.state,
|
state: ctx.state,
|
||||||
layout: &LayoutInfo {
|
layout: &LayoutInfo {
|
||||||
|
@ -176,6 +199,7 @@ impl UiElement for Container {
|
||||||
//TODO: subtract size already taken by previous children
|
//TODO: subtract size already taken by previous children
|
||||||
max_size: self.measure_max_inner_size(ctx.layout),
|
max_size: self.measure_max_inner_size(ctx.layout),
|
||||||
direction: self.direction,
|
direction: self.direction,
|
||||||
|
remaining_space: None,
|
||||||
},
|
},
|
||||||
text_measure: ctx.text_measure,
|
text_measure: ctx.text_measure,
|
||||||
current_font: ctx.current_font,
|
current_font: ctx.current_font,
|
||||||
|
@ -189,19 +213,30 @@ impl UiElement for Container {
|
||||||
};
|
};
|
||||||
|
|
||||||
//Wrap the element if it exceeds container's size and is not the first element in the line
|
//Wrap the element if it exceeds container's size and is not the first element in the line
|
||||||
if ((self.wrap && (end_pos_pri > max_line_pri)) || measure.should_wrap) && (line_element_count > 0) {
|
let should_wrap_overflow = self.wrap.is_enabled() && (end_pos_pri > max_line_pri);
|
||||||
|
if self.wrap.is_allowed() && line_element_count > 0 && (measure.should_wrap || should_wrap_overflow) {
|
||||||
// >>>>>>> WRAP THAT B*TCH!
|
// >>>>>>> WRAP THAT B*TCH!
|
||||||
|
|
||||||
//Negate the leftover gap from the previous element
|
//Negate the leftover gap from the previous element
|
||||||
line_size -= leftover_gap;
|
line_size -= leftover_gap;
|
||||||
|
|
||||||
//update the previous line metadata
|
//update the previous line metadata
|
||||||
lines.last_mut().unwrap().content_size = line_size;
|
{
|
||||||
|
let last_line = lines.last_mut().unwrap();
|
||||||
|
last_line.content_size = line_size;
|
||||||
|
//HACK: why? - self.gap, may be different for the last element or if it's the only element in the line
|
||||||
|
let will_produce_gap = if line_element_count > 1 { self.gap } else { 0. };
|
||||||
|
last_line.remaining_space = max_line_pri - will_produce_gap - match self.direction {
|
||||||
|
Direction::Horizontal => line_size.x + self.padding.left + self.padding.right,
|
||||||
|
Direction::Vertical => line_size.y + self.padding.top + self.padding.bottom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
//push the line metadata
|
//push the line metadata
|
||||||
lines.push(CudLine {
|
lines.push(CudLine {
|
||||||
start_idx: idx,
|
start_idx: idx,
|
||||||
content_size: Vec2::ZERO,
|
content_size: Vec2::ZERO,
|
||||||
|
remaining_space: 0.,
|
||||||
});
|
});
|
||||||
|
|
||||||
//Update the total size accordingly
|
//Update the total size accordingly
|
||||||
|
@ -252,7 +287,16 @@ impl UiElement for Container {
|
||||||
line_size -= leftover_gap;
|
line_size -= leftover_gap;
|
||||||
|
|
||||||
//Update the content size of the last line
|
//Update the content size of the last line
|
||||||
lines.last_mut().unwrap().content_size = line_size;
|
{
|
||||||
|
//HACK: why? - self.gap, may be different for the last element or if it's the only element in the line
|
||||||
|
let cur_line = lines.last_mut().unwrap();
|
||||||
|
cur_line.content_size = line_size;
|
||||||
|
let will_produce_gap = if line_element_count > 1 { self.gap } else { 0. };
|
||||||
|
cur_line.remaining_space = max_line_pri - will_produce_gap - match self.direction {
|
||||||
|
Direction::Horizontal => line_size.x + self.padding.left + self.padding.right,
|
||||||
|
Direction::Vertical => line_size.y + self.padding.top + self.padding.bottom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
//Update the total size according to the size of the last line
|
//Update the total size according to the size of the last line
|
||||||
match self.direction {
|
match self.direction {
|
||||||
|
@ -278,17 +322,27 @@ impl UiElement for Container {
|
||||||
self.padding.top + self.padding.bottom,
|
self.padding.top + self.padding.bottom,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let computed_size = compute_size(ctx.layout, self.size, total_size);
|
||||||
match self.size.width {
|
match self.size.width {
|
||||||
Size::Auto => (),
|
Size::Auto => (),
|
||||||
Size::Relative(percentage) => total_size.x = ctx.layout.max_size.x * percentage,
|
_ => total_size.x = computed_size.x,
|
||||||
Size::Absolute(pixels) => total_size.x = pixels,
|
|
||||||
}
|
}
|
||||||
match self.size.height {
|
match self.size.height {
|
||||||
Size::Auto => (),
|
Size::Auto => (),
|
||||||
Size::Relative(percentage) => total_size.y = ctx.layout.max_size.y * percentage,
|
_ => total_size.y = computed_size.y,
|
||||||
Size::Absolute(pixels) => total_size.y = pixels,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// match self.size.width {
|
||||||
|
// Size::Auto => (),
|
||||||
|
// Size::Relative(percentage) => total_size.x = ctx.layout.max_size.x * percentage,
|
||||||
|
// Size::Absolute(pixels) => total_size.x = pixels,
|
||||||
|
// }
|
||||||
|
// match self.size.height {
|
||||||
|
// Size::Auto => (),
|
||||||
|
// Size::Relative(percentage) => total_size.y = ctx.layout.max_size.y * percentage,
|
||||||
|
// Size::Absolute(pixels) => total_size.y = pixels,
|
||||||
|
// }
|
||||||
|
|
||||||
Response {
|
Response {
|
||||||
size: total_size,
|
size: total_size,
|
||||||
hints: Hints {
|
hints: Hints {
|
||||||
|
@ -385,6 +439,7 @@ impl UiElement for Container {
|
||||||
position: local_position,
|
position: local_position,
|
||||||
max_size: self.measure_max_inner_size(ctx.layout),
|
max_size: self.measure_max_inner_size(ctx.layout),
|
||||||
direction: self.direction,
|
direction: self.direction,
|
||||||
|
remaining_space: Some(cur_line.remaining_space),
|
||||||
};
|
};
|
||||||
|
|
||||||
//measure
|
//measure
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
draw::{RoundedCorners, UiDrawCommand},
|
draw::{RoundedCorners, UiDrawCommand},
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
frame::{Frame, FrameRect},
|
frame::{Frame, FrameRect},
|
||||||
layout::{Size, Size2d},
|
layout::{compute_size, Size, Size2d},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
size
|
size
|
||||||
};
|
};
|
||||||
|
@ -45,20 +45,13 @@ impl UiElement for FillRect {
|
||||||
"fill_rect"
|
"fill_rect"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Option<Size2d> {
|
||||||
|
Some(self.size)
|
||||||
|
}
|
||||||
|
|
||||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||||
Response {
|
Response {
|
||||||
size: vec2(
|
size: compute_size(ctx.layout, self.size, ctx.layout.max_size),
|
||||||
match self.size.width {
|
|
||||||
Size::Auto => ctx.layout.max_size.x,
|
|
||||||
Size::Relative(percentage) => ctx.layout.max_size.x * percentage,
|
|
||||||
Size::Absolute(pixels) => pixels,
|
|
||||||
},
|
|
||||||
match self.size.height {
|
|
||||||
Size::Auto => ctx.layout.max_size.y,
|
|
||||||
Size::Relative(percentage) => ctx.layout.max_size.y * percentage,
|
|
||||||
Size::Absolute(pixels) => pixels,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,10 @@ impl UiElement for Image {
|
||||||
"image"
|
"image"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Option<Size2d> {
|
||||||
|
Some(self.size)
|
||||||
|
}
|
||||||
|
|
||||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||||
let dim = ctx.images.get_size(self.image).expect("invalid image handle");
|
let dim = ctx.images.get_size(self.image).expect("invalid image handle");
|
||||||
let pre_size = compute_size(ctx.layout, self.size, dim.as_vec2());
|
let pre_size = compute_size(ctx.layout, self.size, dim.as_vec2());
|
||||||
|
@ -79,6 +83,7 @@ impl UiElement for Image {
|
||||||
size: ctx.measure.size,
|
size: ctx.measure.size,
|
||||||
color: self.color.corners(),
|
color: self.color.corners(),
|
||||||
texture: Some(self.image),
|
texture: Some(self.image),
|
||||||
|
texture_uv: None,
|
||||||
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)
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -48,6 +48,10 @@ impl UiElement for Interactable {
|
||||||
"interactable"
|
"interactable"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Option<crate::layout::Size2d> {
|
||||||
|
self.element.size()
|
||||||
|
}
|
||||||
|
|
||||||
fn measure(&self, ctx: MeasureContext) -> crate::measure::Response {
|
fn measure(&self, ctx: MeasureContext) -> crate::measure::Response {
|
||||||
self.element.measure(ctx)
|
self.element.measure(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use glam::{vec2, vec4};
|
use glam::vec2;
|
||||||
use crate::{
|
use crate::{
|
||||||
draw::{RoundedCorners, UiDrawCommand},
|
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
|
frame::{Frame, FrameRect},
|
||||||
layout::{compute_size, Size, Size2d},
|
layout::{compute_size, Size, Size2d},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
rect::{Corners, FillColor}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Setters)]
|
//TODO: Use Frames here instead of FillColor
|
||||||
|
|
||||||
|
#[derive(Setters)]
|
||||||
#[setters(prefix = "with_")]
|
#[setters(prefix = "with_")]
|
||||||
pub struct ProgressBar {
|
pub struct ProgressBar {
|
||||||
/// Current progress, should be in the range 0.0..=1.0
|
/// Current progress, should be in the range 0.0..=1.0
|
||||||
|
@ -19,20 +20,26 @@ pub struct ProgressBar {
|
||||||
pub size: Size2d,
|
pub size: Size2d,
|
||||||
|
|
||||||
/// Foreground (bar) color
|
/// Foreground (bar) color
|
||||||
#[setters(into)]
|
#[setters(skip)]
|
||||||
pub foreground: FillColor,
|
pub foreground: Box<dyn Frame>,
|
||||||
|
|
||||||
/// Background color
|
/// Background color
|
||||||
#[setters(into)]
|
#[setters(skip)]
|
||||||
pub background: FillColor,
|
pub background: Box<dyn Frame>,
|
||||||
|
|
||||||
/// Corner radius of the progress bar
|
|
||||||
#[setters(into)]
|
|
||||||
pub corner_radius: Corners<f32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgressBar {
|
impl ProgressBar {
|
||||||
pub const DEFAULT_HEIGHT: f32 = 20.0;
|
pub const DEFAULT_HEIGHT: f32 = 20.0;
|
||||||
|
|
||||||
|
pub fn with_background(mut self, frame: impl Frame + 'static) -> Self {
|
||||||
|
self.background = Box::new(frame);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_foreground(mut self, frame: impl Frame + 'static) -> Self {
|
||||||
|
self.foreground = Box::new(frame);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProgressBar {
|
impl Default for ProgressBar {
|
||||||
|
@ -40,9 +47,8 @@ impl Default for ProgressBar {
|
||||||
Self {
|
Self {
|
||||||
value: 0.,
|
value: 0.,
|
||||||
size: Size::Auto.into(),
|
size: Size::Auto.into(),
|
||||||
foreground: vec4(0.0, 0.0, 1.0, 1.0).into(),
|
foreground: Box::new(FrameRect::color((0.0, 0.0, 1.0, 1.0))),
|
||||||
background: vec4(0.0, 0.0, 0.0, 1.0).into(),
|
background: Box::new(FrameRect::color((0.0, 0.0, 0.0, 1.0))),
|
||||||
corner_radius: Corners::all(0.),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +61,7 @@ impl UiElement for ProgressBar {
|
||||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||||
Response {
|
Response {
|
||||||
size: compute_size(ctx.layout, self.size, vec2(
|
size: compute_size(ctx.layout, self.size, vec2(
|
||||||
ctx.layout.max_size.x.max(300.),
|
ctx.layout.max_size.x.max(300.), //XXX: remove .max(300)?
|
||||||
Self::DEFAULT_HEIGHT,
|
Self::DEFAULT_HEIGHT,
|
||||||
)),
|
)),
|
||||||
hints: Default::default(),
|
hints: Default::default(),
|
||||||
|
@ -66,38 +72,49 @@ impl UiElement for ProgressBar {
|
||||||
|
|
||||||
fn process(&self, ctx: ProcessContext) {
|
fn process(&self, ctx: ProcessContext) {
|
||||||
let value = self.value.clamp(0., 1.);
|
let value = self.value.clamp(0., 1.);
|
||||||
let rounded_corners =
|
|
||||||
(self.corner_radius.max_f32() > 0.).then_some({
|
//FIXME: these optimizations may not be valid
|
||||||
//HACK: fix clipping issues; //todo: get rid of this
|
if value < 1. || !self.foreground.covers_opaque() {
|
||||||
let mut radii = self.corner_radius;
|
self.background.draw(ctx.draw, ctx.layout.position, ctx.measure.size);
|
||||||
let width = ctx.measure.size.x * value;
|
|
||||||
if width <= radii.max_f32() * 2. {
|
|
||||||
radii.bottom_right = 0.;
|
|
||||||
radii.top_right = 0.;
|
|
||||||
}
|
|
||||||
if width <= radii.max_f32() {
|
|
||||||
radii.bottom_left = 0.;
|
|
||||||
radii.top_left = 0.;
|
|
||||||
}
|
|
||||||
RoundedCorners::from_radius(radii)
|
|
||||||
});
|
|
||||||
if value < 1. {
|
|
||||||
ctx.draw.add(UiDrawCommand::Rectangle {
|
|
||||||
position: ctx.layout.position,
|
|
||||||
size: ctx.measure.size,
|
|
||||||
color: self.background.corners(),
|
|
||||||
texture: None,
|
|
||||||
rounded_corners
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if value > 0. {
|
if value > 0. {
|
||||||
ctx.draw.add(UiDrawCommand::Rectangle {
|
self.foreground.draw(ctx.draw, ctx.layout.position, ctx.measure.size * vec2(value, 1.));
|
||||||
position: ctx.layout.position,
|
|
||||||
size: ctx.measure.size * vec2(value, 1.0),
|
|
||||||
color: self.foreground.corners(),
|
|
||||||
texture: None,
|
|
||||||
rounded_corners,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let rounded_corners =
|
||||||
|
// (self.corner_radius.max_f32() > 0.).then_some({
|
||||||
|
// //HACK: fix clipping issues; //todo: get rid of this
|
||||||
|
// let mut radii = self.corner_radius;
|
||||||
|
// let width = ctx.measure.size.x * value;
|
||||||
|
// if width <= radii.max_f32() * 2. {
|
||||||
|
// radii.bottom_right = 0.;
|
||||||
|
// radii.top_right = 0.;
|
||||||
|
// }
|
||||||
|
// if width <= radii.max_f32() {
|
||||||
|
// radii.bottom_left = 0.;
|
||||||
|
// radii.top_left = 0.;
|
||||||
|
// }
|
||||||
|
// RoundedCorners::from_radius(radii)
|
||||||
|
// });
|
||||||
|
// if value < 1. {
|
||||||
|
// ctx.draw.add(UiDrawCommand::Rectangle {
|
||||||
|
// position: ctx.layout.position,
|
||||||
|
// size: ctx.measure.size,
|
||||||
|
// color: self.background.corners(),
|
||||||
|
// texture: None,
|
||||||
|
// texture_uv: None,
|
||||||
|
// rounded_corners
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// if value > 0. {
|
||||||
|
// ctx.draw.add(UiDrawCommand::Rectangle {
|
||||||
|
// position: ctx.layout.position,
|
||||||
|
// size: ctx.measure.size * vec2(value, 1.0),
|
||||||
|
// color: self.foreground.corners(),
|
||||||
|
// texture: None,
|
||||||
|
// texture_uv: None,
|
||||||
|
// rounded_corners,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use glam::{vec2, Vec4};
|
||||||
use crate::{
|
use crate::{
|
||||||
draw::UiDrawCommand,
|
draw::UiDrawCommand,
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
layout::{Size, Size2d},
|
layout::{compute_size, Size, Size2d},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
text::FontHandle,
|
text::FontHandle,
|
||||||
};
|
};
|
||||||
|
@ -74,6 +74,10 @@ impl UiElement for Text {
|
||||||
"text"
|
"text"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Option<Size2d> {
|
||||||
|
Some(self.size)
|
||||||
|
}
|
||||||
|
|
||||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||||
let mut size = (0., 0.);
|
let mut size = (0., 0.);
|
||||||
if matches!(self.size.width, Size::Auto) || matches!(self.size.height, Size::Auto) {
|
if matches!(self.size.width, Size::Auto) || matches!(self.size.height, Size::Auto) {
|
||||||
|
@ -83,18 +87,7 @@ impl UiElement for Text {
|
||||||
size.1 = res.height;
|
size.1 = res.height;
|
||||||
}
|
}
|
||||||
Response {
|
Response {
|
||||||
size: vec2(
|
size: compute_size(ctx.layout, self.size, size.into()),
|
||||||
match self.size.width {
|
|
||||||
Size::Auto => size.0,
|
|
||||||
Size::Relative(percentage) => ctx.layout.max_size.x * percentage,
|
|
||||||
Size::Absolute(pixels) => pixels,
|
|
||||||
},
|
|
||||||
match self.size.height {
|
|
||||||
Size::Auto => size.1,
|
|
||||||
Size::Relative(percentage) => ctx.layout.max_size.y * percentage,
|
|
||||||
Size::Absolute(pixels) => pixels,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
|
//! modular procedural background system
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use crate::draw::UiDrawCommandList;
|
use crate::draw::UiDrawCommandList;
|
||||||
|
|
||||||
pub mod point;
|
pub mod point;
|
||||||
mod rect;
|
mod rect;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
|
pub mod nine_patch;
|
||||||
mod impls;
|
mod impls;
|
||||||
|
|
||||||
pub use rect::FrameRect;
|
pub use rect::FrameRect;
|
||||||
|
|
||||||
|
/// Trait for a drawable frame
|
||||||
pub trait Frame {
|
pub trait Frame {
|
||||||
/// Draw the frame at the given position and size
|
/// Draw the frame at the given position and size
|
||||||
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
|
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
|
||||||
|
|
|
@ -13,6 +13,7 @@ impl Frame for ImageHandle {
|
||||||
size: parent_size,
|
size: parent_size,
|
||||||
color: color::WHITE.into(),
|
color: color::WHITE.into(),
|
||||||
texture: Some(*self),
|
texture: Some(*self),
|
||||||
|
texture_uv: None,
|
||||||
rounded_corners: None,
|
rounded_corners: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -29,6 +30,7 @@ impl Frame for FillColor {
|
||||||
size: parent_size,
|
size: parent_size,
|
||||||
color: self.corners(),
|
color: self.corners(),
|
||||||
texture: None,
|
texture: None,
|
||||||
|
texture_uv: None,
|
||||||
rounded_corners: None,
|
rounded_corners: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
234
hui/src/frame/nine_patch.rs
Normal file
234
hui/src/frame/nine_patch.rs
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
//! nine-patch frame implementation
|
||||||
|
//!
|
||||||
|
//! A 9-patch image is an image that can be scaled in a way that preserves the corners and edges of the image while scaling the center.
|
||||||
|
//! 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 super::Frame;
|
||||||
|
|
||||||
|
/// Represents a 9-patch image asset
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct NinePatchAsset {
|
||||||
|
pub image: ImageHandle,
|
||||||
|
//TODO: remove this:
|
||||||
|
pub size: (u32, u32),
|
||||||
|
pub scalable_region: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO allow scaling/moving corners
|
||||||
|
|
||||||
|
/// A 9-patch frame
|
||||||
|
///
|
||||||
|
/// Can optionally be tinted with a color (works well with grayscale assets)
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct NinePatchFrame {
|
||||||
|
pub asset: NinePatchAsset,
|
||||||
|
pub color: FillColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NinePatchFrame {
|
||||||
|
pub fn from_asset(asset: NinePatchAsset) -> Self {
|
||||||
|
Self { asset, ..Default::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_color(mut self, color: impl Into<FillColor>) -> Self {
|
||||||
|
self.color = color.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NinePatchFrame {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
//This is not supposed to be left out as the default, so just set it to whatever :p
|
||||||
|
asset: NinePatchAsset { image: ImageHandle::default(), size: (0, 0), scalable_region: Rect::default() },
|
||||||
|
color: color::WHITE.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame for NinePatchFrame {
|
||||||
|
fn draw(&self, draw: &mut crate::draw::UiDrawCommandList, position: glam::Vec2, parent_size: glam::Vec2) {
|
||||||
|
// 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 img_sz = UVec2::from(self.asset.size).as_vec2();
|
||||||
|
|
||||||
|
//Color stuff
|
||||||
|
let interpolate_color_rect = |uvs: Corners<Vec2>| {
|
||||||
|
Corners {
|
||||||
|
top_left: self.color.interpolate(uvs.top_left),
|
||||||
|
top_right: self.color.interpolate(uvs.top_right),
|
||||||
|
bottom_left: self.color.interpolate(uvs.bottom_left),
|
||||||
|
bottom_right: self.color.interpolate(uvs.bottom_right),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inset coords, in UV space
|
||||||
|
let region_uv = self.asset.scalable_region.corners();
|
||||||
|
|
||||||
|
// Inset coords, in image (px) space
|
||||||
|
let corners_image_px = Corners {
|
||||||
|
top_left: img_sz * region_uv.top_left,
|
||||||
|
top_right: img_sz * region_uv.top_right,
|
||||||
|
bottom_left: img_sz * region_uv.bottom_left,
|
||||||
|
bottom_right: img_sz * region_uv.bottom_right,
|
||||||
|
};
|
||||||
|
|
||||||
|
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),
|
||||||
|
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),
|
||||||
|
img_sz.y - corners_image_px.bottom_left.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
//Top-left patch
|
||||||
|
let top_left_patch_uv = Corners {
|
||||||
|
top_left: vec2(0., 0.),
|
||||||
|
top_right: vec2(region_uv.top_left.x, 0.),
|
||||||
|
bottom_left: vec2(0., region_uv.top_left.y),
|
||||||
|
bottom_right: region_uv.top_left,
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position,
|
||||||
|
size: vec2(size_h.0, size_v.0),
|
||||||
|
color: interpolate_color_rect(top_left_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(top_left_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
//Top patch
|
||||||
|
let top_patch_uv = Corners {
|
||||||
|
top_left: vec2(region_uv.top_left.x, 0.),
|
||||||
|
top_right: vec2(region_uv.top_right.x, 0.),
|
||||||
|
bottom_left: region_uv.top_left,
|
||||||
|
bottom_right: region_uv.top_right,
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(size_h.0, 0.),
|
||||||
|
size: vec2(size_h.1, size_v.0),
|
||||||
|
color: interpolate_color_rect(top_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(top_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
//Top-right patch
|
||||||
|
let top_right_patch_uv = Corners {
|
||||||
|
top_left: vec2(region_uv.top_right.x, 0.),
|
||||||
|
top_right: vec2(1., 0.),
|
||||||
|
bottom_left: region_uv.top_right,
|
||||||
|
bottom_right: vec2(1., region_uv.top_right.y),
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(size_h.0 + size_h.1, 0.),
|
||||||
|
size: vec2(size_h.2, size_v.0),
|
||||||
|
color: interpolate_color_rect(top_right_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(top_right_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
//Left patch
|
||||||
|
let left_patch_uv = Corners {
|
||||||
|
top_left: vec2(0., region_uv.top_left.y),
|
||||||
|
top_right: region_uv.top_left,
|
||||||
|
bottom_left: vec2(0., region_uv.bottom_left.y),
|
||||||
|
bottom_right: region_uv.bottom_left,
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(0., size_v.0),
|
||||||
|
size: vec2(size_h.0, size_v.1),
|
||||||
|
color: interpolate_color_rect(left_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(left_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
// Center patch
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(size_h.0, size_v.0),
|
||||||
|
size: vec2(size_h.1, size_v.1),
|
||||||
|
color: interpolate_color_rect(region_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(region_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
//Right patch
|
||||||
|
let right_patch_uv = Corners {
|
||||||
|
top_left: region_uv.top_right,
|
||||||
|
top_right: vec2(1., region_uv.top_right.y),
|
||||||
|
bottom_left: region_uv.bottom_right,
|
||||||
|
bottom_right: vec2(1., region_uv.bottom_right.y),
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(size_h.0 + size_h.1, size_v.0),
|
||||||
|
size: vec2(size_h.2, size_v.1),
|
||||||
|
color: interpolate_color_rect(right_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(right_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
//Bottom-left patch
|
||||||
|
let bottom_left_patch_uv = Corners {
|
||||||
|
top_left: vec2(0., region_uv.bottom_left.y),
|
||||||
|
top_right: region_uv.bottom_left,
|
||||||
|
bottom_left: vec2(0., 1.),
|
||||||
|
bottom_right: vec2(region_uv.bottom_left.x, 1.),
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(0., size_v.0 + size_v.1),
|
||||||
|
size: vec2(size_h.0, size_v.2),
|
||||||
|
color: interpolate_color_rect(bottom_left_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(bottom_left_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
//Bottom patch
|
||||||
|
let bottom_patch_uv = Corners {
|
||||||
|
top_left: region_uv.bottom_left,
|
||||||
|
top_right: region_uv.bottom_right,
|
||||||
|
bottom_left: vec2(region_uv.bottom_left.x, 1.),
|
||||||
|
bottom_right: vec2(region_uv.bottom_right.x, 1.),
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(size_h.0, size_v.0 + size_v.1),
|
||||||
|
size: vec2(size_h.1, size_v.2),
|
||||||
|
color: interpolate_color_rect(bottom_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(bottom_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
|
||||||
|
//Bottom-right patch
|
||||||
|
let bottom_right_patch_uv = Corners {
|
||||||
|
top_left: region_uv.bottom_right,
|
||||||
|
top_right: vec2(1., region_uv.bottom_right.y),
|
||||||
|
bottom_left: vec2(region_uv.bottom_right.x, 1.),
|
||||||
|
bottom_right: vec2(1., 1.),
|
||||||
|
};
|
||||||
|
draw.add(UiDrawCommand::Rectangle {
|
||||||
|
position: position + vec2(size_h.0 + size_h.1, size_v.0 + size_v.1),
|
||||||
|
size: vec2(size_h.2, size_v.2),
|
||||||
|
color: interpolate_color_rect(bottom_right_patch_uv),
|
||||||
|
texture: Some(self.asset.image),
|
||||||
|
texture_uv: Some(bottom_right_patch_uv),
|
||||||
|
rounded_corners: None
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn covers_opaque(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! frame-relative positioning/size
|
||||||
|
|
||||||
use glam::{Vec2, vec2};
|
use glam::{Vec2, vec2};
|
||||||
use derive_more::{Add, AddAssign, Sub, SubAssign};
|
use derive_more::{Add, AddAssign, Sub, SubAssign};
|
||||||
use crate::layout::{Size, Size2d};
|
use crate::layout::{Size, Size2d};
|
||||||
|
@ -24,11 +26,13 @@ impl From<f32> for FramePoint {
|
||||||
impl From<Size> for FramePoint {
|
impl From<Size> for FramePoint {
|
||||||
/// Convert a `Size` into a `FramePoint`
|
/// Convert a `Size` into a `FramePoint`
|
||||||
///
|
///
|
||||||
/// This function behaves just as you would expect, but `Auto` is always treated as `BEGIN`
|
/// This function behaves just as you would expect, but:
|
||||||
|
/// - `Auto` is always treated as `BEGIN`
|
||||||
|
/// - `Remaining` is treated as `Relative`
|
||||||
fn from(size: Size) -> Self {
|
fn from(size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::Auto => Self::BEGIN,
|
Size::Auto => Self::BEGIN,
|
||||||
Size::Relative(value) => Self::relative(value),
|
Size::Relative(value) | Size::Remaining(value) => Self::relative(value),
|
||||||
Size::Absolute(value) => Self::absolute(value),
|
Size::Absolute(value) => Self::absolute(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,7 @@ impl Frame for FrameRect {
|
||||||
size: bottom_right - top_left,
|
size: bottom_right - top_left,
|
||||||
color: self.color.corners(),
|
color: self.color.corners(),
|
||||||
texture: self.image,
|
texture: self.image,
|
||||||
|
texture_uv: None,
|
||||||
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)
|
||||||
),
|
),
|
||||||
|
@ -140,6 +141,7 @@ impl Frame for FrameRect {
|
||||||
self.bottom_right.y.absolute >= 0. &&
|
self.bottom_right.y.absolute >= 0. &&
|
||||||
self.bottom_right.y.relative >= 1. &&
|
self.bottom_right.y.relative >= 1. &&
|
||||||
self.color.is_opaque() &&
|
self.color.is_opaque() &&
|
||||||
self.image.is_none()
|
self.image.is_none() &&
|
||||||
|
self.corner_radius.max_f32() == 0.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
//! allows stacking two frames on top of each other
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use crate::draw::UiDrawCommandList;
|
use crate::draw::UiDrawCommandList;
|
||||||
use super::Frame;
|
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>);
|
pub struct FrameStack(pub Box<dyn Frame>, pub Box<dyn Frame>);
|
||||||
|
|
||||||
impl Frame for FrameStack {
|
impl Frame for FrameStack {
|
||||||
|
@ -17,6 +20,7 @@ impl Frame for FrameStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FrameStackExt: Frame {
|
pub trait FrameStackExt: Frame {
|
||||||
|
/// Stack another frame on top of this one
|
||||||
fn stack(self, other: impl Frame + 'static) -> FrameStack;
|
fn stack(self, other: impl Frame + 'static) -> FrameStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,8 @@ pub enum KeyboardKey {
|
||||||
macro_rules! impl_fits64_for_keyboard_key {
|
macro_rules! impl_fits64_for_keyboard_key {
|
||||||
($($i:ident = $v:literal),*) => {
|
($($i:ident = $v:literal),*) => {
|
||||||
impl Fits64 for KeyboardKey {
|
impl Fits64 for KeyboardKey {
|
||||||
|
// SAFETY: not actually doing anything unsafe
|
||||||
|
#[allow(unsafe_code)]
|
||||||
unsafe fn from_u64(x: u64) -> Self {
|
unsafe fn from_u64(x: u64) -> Self {
|
||||||
match x {
|
match x {
|
||||||
$( $v => KeyboardKey::$i, )*
|
$( $v => KeyboardKey::$i, )*
|
||||||
|
|
|
@ -95,7 +95,7 @@ impl UiInstance {
|
||||||
/// (this will change to a soft error in the future)
|
/// (this will change to a soft error in the future)
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
pub fn add_image_file_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<ImageHandle, std::io::Error> {
|
pub fn add_image_file_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<ImageHandle, std::io::Error> {
|
||||||
use std::io::Read;
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
// Open the file (and wrap it in a bufreader)
|
// Open the file (and wrap it in a bufreader)
|
||||||
let mut file = std::io::BufReader::new(std::fs::File::open(path)?);
|
let mut file = std::io::BufReader::new(std::fs::File::open(path)?);
|
||||||
|
@ -106,6 +106,7 @@ impl UiInstance {
|
||||||
let mut magic = [0; 64];
|
let mut magic = [0; 64];
|
||||||
file.read_exact(&mut magic)?;
|
file.read_exact(&mut magic)?;
|
||||||
let format = image::guess_format(&magic).expect("Invalid image data (FORMAT)");
|
let format = image::guess_format(&magic).expect("Invalid image data (FORMAT)");
|
||||||
|
file.seek(std::io::SeekFrom::Start(0))?;
|
||||||
|
|
||||||
//Parse the image and read the raw uncompressed rgba data
|
//Parse the image and read the raw uncompressed rgba data
|
||||||
let image = image::load(file, format).expect("Invalid image data");
|
let image = image::load(file, format).expect("Invalid image data");
|
||||||
|
@ -156,6 +157,7 @@ impl UiInstance {
|
||||||
position: Vec2::ZERO,
|
position: Vec2::ZERO,
|
||||||
max_size,
|
max_size,
|
||||||
direction: Direction::Vertical,
|
direction: Direction::Vertical,
|
||||||
|
remaining_space: None,
|
||||||
};
|
};
|
||||||
let measure = element.measure(MeasureContext {
|
let measure = element.measure(MeasureContext {
|
||||||
state: &self.stateful_state,
|
state: &self.stateful_state,
|
||||||
|
|
|
@ -2,6 +2,46 @@
|
||||||
|
|
||||||
use glam::{vec2, Vec2};
|
use glam::{vec2, Vec2};
|
||||||
|
|
||||||
|
/// Controls wrapping behavior of elements
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord, Default)]
|
||||||
|
pub enum WrapBehavior {
|
||||||
|
/// No wrapping is allowed, even if explicit line breaks is requested by the element
|
||||||
|
Disable = 0,
|
||||||
|
|
||||||
|
/// Allow wrapping if the element explicitly requests it (default behavior)
|
||||||
|
#[default]
|
||||||
|
Allow = 1,
|
||||||
|
|
||||||
|
/// Elements will be wrapped automatically when they reach the maximum width/height of the container
|
||||||
|
Enable = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for WrapBehavior {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
match value {
|
||||||
|
true => Self::Enable,
|
||||||
|
false => Self::Disable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapBehavior {
|
||||||
|
/// Check if wrapping is allowed for the element
|
||||||
|
#[inline]
|
||||||
|
pub fn is_allowed(&self) -> bool {
|
||||||
|
*self != Self::Disable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if wrapping is enabled for the element
|
||||||
|
///
|
||||||
|
/// (Wrapping will be done automatically when the element reaches the maximum width/height)
|
||||||
|
#[inline]
|
||||||
|
pub fn is_enabled(&self) -> bool {
|
||||||
|
*self == Self::Enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Alignment along a single axis
|
/// Alignment along a single axis
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, PartialOrd, Ord)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, PartialOrd, Ord)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
|
@ -86,17 +126,24 @@ pub enum Size {
|
||||||
#[default]
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
|
|
||||||
/// Size as a ratio of parent size\
|
|
||||||
/// Valid range: 0.0-1.0 (0-100%)
|
|
||||||
///
|
|
||||||
/// Out of range values are allowed, but are not guaranteed to work as expected\
|
|
||||||
/// (especially with negative values)
|
|
||||||
Relative(f32),
|
|
||||||
|
|
||||||
//TODO FractionRemaining(f32),
|
|
||||||
|
|
||||||
/// Static size in pixels
|
/// Static size in pixels
|
||||||
Absolute(f32),
|
Absolute(f32),
|
||||||
|
|
||||||
|
/// Size as a ratio of parent element size
|
||||||
|
///
|
||||||
|
/// Expected range: `0.0..=1.0`
|
||||||
|
Relative(f32),
|
||||||
|
|
||||||
|
/// Size as a ratio of remaining space after all other elements have been laid out
|
||||||
|
///
|
||||||
|
/// Expected range: `0.0..=1.0`
|
||||||
|
///
|
||||||
|
/// - This feature is experimental and may not work as expected;\
|
||||||
|
/// Current `Container` implementation:
|
||||||
|
/// - Assumes that he line is fully filled if any element uses `Remaining` size, even if sum of remaining sizes is less than 1.0
|
||||||
|
/// - Does not support `Remaining` size in the secondary axis, it will be treated as `Relative`
|
||||||
|
/// - In cases where it's not applicable or not supported, it's defined to behave as `Relative`
|
||||||
|
Remaining(f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f32> for Size {
|
impl From<f32> for Size {
|
||||||
|
@ -159,6 +206,13 @@ pub struct LayoutInfo {
|
||||||
/// Current direction of the layout\
|
/// Current direction of the layout\
|
||||||
/// (Usually matches direction of the parent container)
|
/// (Usually matches direction of the parent container)
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
|
|
||||||
|
/// Remaining space in the primary axis\
|
||||||
|
///
|
||||||
|
/// This value is only available during the layout step and is only likely to be present if the element uses `Size::Remaining`
|
||||||
|
///
|
||||||
|
/// (Make sure that LayoutInfo::direction is set to the correct direction!)
|
||||||
|
pub remaining_space: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to calculate the size of an element based on its layout and size information\
|
/// Helper function to calculate the size of an element based on its layout and size information\
|
||||||
|
@ -168,11 +222,19 @@ pub fn compute_size(layout: &LayoutInfo, size: Size2d, comfy_size: Vec2) -> Vec2
|
||||||
Size::Auto => comfy_size.x,
|
Size::Auto => comfy_size.x,
|
||||||
Size::Relative(fraction) => layout.max_size.x * fraction,
|
Size::Relative(fraction) => layout.max_size.x * fraction,
|
||||||
Size::Absolute(size) => size,
|
Size::Absolute(size) => size,
|
||||||
|
Size::Remaining(fraction) => match layout.direction {
|
||||||
|
Direction::Horizontal => layout.remaining_space.unwrap_or(layout.max_size.x) * fraction,
|
||||||
|
Direction::Vertical => layout.max_size.x * fraction,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let height = match size.height {
|
let height = match size.height {
|
||||||
Size::Auto => comfy_size.y,
|
Size::Auto => comfy_size.y,
|
||||||
Size::Relative(fraction) => layout.max_size.y * fraction,
|
Size::Relative(fraction) => layout.max_size.y * fraction,
|
||||||
Size::Absolute(size) => size,
|
Size::Absolute(size) => size,
|
||||||
|
Size::Remaining(fraction) => match layout.direction {
|
||||||
|
Direction::Horizontal => layout.max_size.y * fraction,
|
||||||
|
Direction::Vertical => layout.remaining_space.unwrap_or(layout.max_size.y) * fraction,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
vec2(width, height)
|
vec2(width, height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
//! # Features
|
//! # Features
|
||||||
#![doc = document_features::document_features!()]
|
#![doc = document_features::document_features!()]
|
||||||
|
|
||||||
#![allow(unused_parens)]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
//#![forbid(unsafe_code)]
|
|
||||||
|
#![deny(unsafe_code)]
|
||||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||||
|
#![allow(unused_parens)]
|
||||||
|
|
||||||
mod instance;
|
mod instance;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
/// - `x` - `Size::Absolute(x)`
|
/// - `x` - `Size::Absolute(x)`
|
||||||
/// - `x%` - `Size::Relative(x / 100.)` *(literal only)*
|
/// - `x%` - `Size::Relative(x / 100.)` *(literal only)*
|
||||||
/// - `x/` - `Size::Relative(x)`
|
/// - `x/` - `Size::Relative(x)`
|
||||||
|
/// - `x%=` - `Size::Remaining(x / 100.)` *(literal only)*
|
||||||
|
/// - `x/=` - `Size::Remaining(x)`
|
||||||
///
|
///
|
||||||
/// ...where `x` is a literal, identifier or an expression wrapped in parentheses
|
/// ...where `x` is a literal, identifier or an expression wrapped in parentheses
|
||||||
///
|
///
|
||||||
|
@ -32,6 +34,12 @@ macro_rules! size {
|
||||||
($x:literal /) => {
|
($x:literal /) => {
|
||||||
$crate::layout::Size::Relative($x as f32)
|
$crate::layout::Size::Relative($x as f32)
|
||||||
};
|
};
|
||||||
|
($x:literal %=) => {
|
||||||
|
$crate::layout::Size::Remaining($x as f32 / 100.)
|
||||||
|
};
|
||||||
|
($x:literal /=) => {
|
||||||
|
$crate::layout::Size::Remaining($x as f32)
|
||||||
|
};
|
||||||
|
|
||||||
($x:ident) => {
|
($x:ident) => {
|
||||||
$crate::layout::Size::Absolute($x as f32)
|
$crate::layout::Size::Absolute($x as f32)
|
||||||
|
@ -39,6 +47,9 @@ macro_rules! size {
|
||||||
($x:ident /) => {
|
($x:ident /) => {
|
||||||
$crate::layout::Size::Relative($x as f32)
|
$crate::layout::Size::Relative($x as f32)
|
||||||
};
|
};
|
||||||
|
($x:ident /=) => {
|
||||||
|
$crate::layout::Size::Remaining($x as f32)
|
||||||
|
};
|
||||||
|
|
||||||
(($x:expr)) => {
|
(($x:expr)) => {
|
||||||
$crate::layout::Size::Absolute(($x) as f32)
|
$crate::layout::Size::Absolute(($x) as f32)
|
||||||
|
@ -46,6 +57,9 @@ macro_rules! size {
|
||||||
(($x:expr) /) => {
|
(($x:expr) /) => {
|
||||||
$crate::layout::Size::Relative(($x) as f32)
|
$crate::layout::Size::Relative(($x) as f32)
|
||||||
};
|
};
|
||||||
|
(($x:expr) /=) => {
|
||||||
|
$crate::layout::Size::Remaining(($x) as f32)
|
||||||
|
};
|
||||||
|
|
||||||
($x:tt , $y:tt $($ys:tt)?) => {
|
($x:tt , $y:tt $($ys:tt)?) => {
|
||||||
$crate::layout::Size2d {
|
$crate::layout::Size2d {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::Corners;
|
use super::Corners;
|
||||||
use glam::{Vec3, Vec4, vec4};
|
use glam::{Vec2, Vec3, Vec4, vec4};
|
||||||
|
|
||||||
/// Represents the fill color of a rectangle
|
/// Represents the fill color of a rectangle
|
||||||
///
|
///
|
||||||
|
@ -69,6 +69,14 @@ impl FillColor {
|
||||||
pub const fn corners(&self) -> Corners<Vec4> {
|
pub const fn corners(&self) -> Corners<Vec4> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interpolate color on position, assuming a linear gradient
|
||||||
|
pub fn interpolate(&self, uv: Vec2) -> Vec4 {
|
||||||
|
let c = self.corners();
|
||||||
|
let top = c.top_left.lerp(c.top_right, uv.x);
|
||||||
|
let bottom = c.bottom_left.lerp(c.bottom_right, uv.x);
|
||||||
|
top.lerp(bottom, uv.y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FillColor {
|
impl Default for FillColor {
|
||||||
|
|
|
@ -6,6 +6,9 @@ use nohash_hasher::BuildNoHashHasher;
|
||||||
|
|
||||||
pub mod trigger;
|
pub mod trigger;
|
||||||
|
|
||||||
|
#[cfg(feature = "derive")]
|
||||||
|
pub use hui_derive::Signal;
|
||||||
|
|
||||||
/// A marker trait for UI Signals
|
/// A marker trait for UI Signals
|
||||||
pub trait Signal: Any {}
|
pub trait Signal: Any {}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue