mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-25 08:28: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.tabSize": 2,
|
||||
"editor.insertSpaces": true
|
||||
"editor.insertSpaces": true,
|
||||
"editor.wordWrap": "off"
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[workspace]
|
||||
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>
|
||||
<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>
|
||||
<div>
|
||||
<span>
|
||||
|
@ -17,22 +17,25 @@
|
|||
|
||||
<table align="center">
|
||||
<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>
|
||||
<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>
|
||||
</table>
|
||||
|
||||
<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()
|
||||
.with_size(size!(100%, 50%))
|
||||
.with_align(Alignment::Center)
|
||||
.with_padding(5.)
|
||||
.with_gap(10.)
|
||||
.with_corner_radius(10.)
|
||||
.with_background(color::WHITE)
|
||||
.with_background(frame_rect! {
|
||||
color: (0.5, 0.5, 0.5, 1.),
|
||||
corner_radius: 10.,
|
||||
})
|
||||
.with_children(|ui| {
|
||||
Text::default()
|
||||
.with_text("Hello, world")
|
||||
|
@ -41,8 +44,10 @@
|
|||
.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_background(frame_rect! {
|
||||
color: color::DARK_RED,
|
||||
corner_radius: (2.5, 30., 2.5, 2.5),
|
||||
})
|
||||
.with_children(|ui| {
|
||||
Text::default()
|
||||
.with_text("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
|
||||
|
@ -51,7 +56,7 @@
|
|||
})
|
||||
.add_child(ui);
|
||||
})
|
||||
.add_root(&mut hui, resolution);</pre>
|
||||
.add_root(ui, size);</pre>
|
||||
|
||||
<h2>Backends</h2>
|
||||
<p>
|
||||
|
@ -89,6 +94,20 @@
|
|||
</td>
|
||||
<td align="center">(support planned)</td>
|
||||
</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>
|
||||
<td align="center">
|
||||
<code>0.1.0-alpha.3</code>
|
||||
|
@ -140,3 +159,6 @@
|
|||
<td align="center">-</td>
|
||||
</tr> -->
|
||||
</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"
|
||||
glam = "0.27"
|
||||
log = "0.4"
|
||||
image = { version = "0.25", features = ["jpeg", "png"] }
|
||||
|
||||
#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![
|
||||
Box::new(ProgressBar {
|
||||
value: z,
|
||||
corner_radius: Corners::all(0.25 * ProgressBar::DEFAULT_HEIGHT),
|
||||
// corner_radius: Corners::all(0.25 * ProgressBar::DEFAULT_HEIGHT),
|
||||
..Default::default()
|
||||
}),
|
||||
Box::new(Container {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Instant;
|
||||
use hui::{
|
||||
element::{
|
||||
color, element::{
|
||||
container::Container,
|
||||
progress_bar::ProgressBar,
|
||||
text::Text,
|
||||
|
@ -43,7 +43,14 @@ ui_main!{
|
|||
.add_child(ui);
|
||||
ProgressBar::default()
|
||||
.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);
|
||||
Container::default()
|
||||
.with_direction(Direction::Horizontal)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use hui::{
|
||||
color, size,
|
||||
draw::TextureFormat,
|
||||
signal::Signal,
|
||||
draw::TextureFormat,
|
||||
layout::{Alignment, Direction},
|
||||
element::{
|
||||
container::Container,
|
||||
|
@ -13,11 +13,11 @@ use hui::{
|
|||
},
|
||||
};
|
||||
|
||||
#[derive(Signal)]
|
||||
enum CounterSignal {
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
impl Signal for CounterSignal {}
|
||||
|
||||
#[path = "../boilerplate.rs"]
|
||||
#[macro_use]
|
||||
|
|
|
@ -8,15 +8,15 @@ use hui::{
|
|||
text::Text,
|
||||
UiElementExt,
|
||||
},
|
||||
layout::{Alignment, Direction},
|
||||
signal::Signal,
|
||||
layout::{Alignment, Direction},
|
||||
size,
|
||||
};
|
||||
|
||||
#[derive(Signal)]
|
||||
enum CounterSignal {
|
||||
ChangeValue(u32)
|
||||
}
|
||||
impl Signal for CounterSignal {}
|
||||
|
||||
#[path = "../boilerplate.rs"]
|
||||
#[macro_use]
|
||||
|
|
|
@ -1,34 +1,92 @@
|
|||
use std::time::Instant;
|
||||
use glam::vec2;
|
||||
use hui::{
|
||||
color, element::{
|
||||
color,
|
||||
element::{
|
||||
container::Container,
|
||||
fill_rect::FillRect,
|
||||
slider::Slider,
|
||||
text::Text,
|
||||
UiElementExt
|
||||
}, frame_rect, layout::{Alignment, Direction}, size
|
||||
},
|
||||
frame::nine_patch::{NinePatchAsset, NinePatchFrame},
|
||||
layout::Alignment,
|
||||
rect::Rect,
|
||||
signal::Signal,
|
||||
size,
|
||||
};
|
||||
|
||||
#[path = "../boilerplate.rs"]
|
||||
#[macro_use]
|
||||
mod boilerplate;
|
||||
|
||||
#[derive(Signal)]
|
||||
struct SetValue(f32);
|
||||
|
||||
ui_main!(
|
||||
"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.),
|
||||
},
|
||||
run: |ui, size, _| {
|
||||
},
|
||||
0.33,
|
||||
)
|
||||
},
|
||||
run: |ui, size, (asset, value)| {
|
||||
Container::default()
|
||||
.with_size(size!(100%))
|
||||
.with_align(Alignment::Center)
|
||||
.with_gap(5.)
|
||||
.with_background(color::WHITE)
|
||||
.with_children(|ui| {
|
||||
FillRect::default()
|
||||
Container::default()
|
||||
.with_size(size!(300, 100))
|
||||
.with_frame(frame_rect! {
|
||||
color: color::RED
|
||||
.with_background(NinePatchFrame::from_asset(*asset).with_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);
|
||||
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);
|
||||
|
||||
ui.process_signals::<SetValue>(|signal| *value = signal.0);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -38,7 +38,7 @@ ui_main!(
|
|||
.with_size(size!(100%, auto))
|
||||
.with_direction(Direction::Horizontal)
|
||||
.with_align((Alignment::Begin, Alignment::Center))
|
||||
.with_padding(8.)
|
||||
.with_padding(5.)
|
||||
.with_gap(15.)
|
||||
.with_background(color::rgb_hex(0x3d3c3e))
|
||||
.with_wrap(true) //XXX: not authentic but great for demostration
|
||||
|
@ -51,6 +51,15 @@ ui_main!(
|
|||
.with_text_size(15)
|
||||
.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);
|
||||
FillRect::default()
|
||||
|
@ -58,9 +67,10 @@ ui_main!(
|
|||
.with_frame(color::rgb_hex(0x2d2d30))
|
||||
.add_child(ui);
|
||||
Container::default()
|
||||
.with_size(size!(100%, 100%))
|
||||
.with_size(size!(100%, 100%=))
|
||||
.with_direction(Direction::Horizontal)
|
||||
.with_children(|ui| {
|
||||
// Sidebar:
|
||||
Container::default()
|
||||
.with_size(size!(54, 100%))
|
||||
.with_background(color::rgb_hex(0x343334))
|
||||
|
@ -69,6 +79,8 @@ ui_main!(
|
|||
.with_size(size!(1, 100%))
|
||||
.with_frame(color::rgb_hex(0x2d2d30))
|
||||
.add_child(ui);
|
||||
|
||||
// Explorer pane:
|
||||
Container::default()
|
||||
.with_size(size!(200, 100%))
|
||||
.with_padding((15., 8.))
|
||||
|
@ -78,20 +90,16 @@ ui_main!(
|
|||
.add_child(ui);
|
||||
})
|
||||
.add_child(ui);
|
||||
|
||||
// "Code" pane
|
||||
Container::default()
|
||||
.with_size(size!(100%, 100%))
|
||||
.with_size(size!(100%=, 100%))
|
||||
.with_background(color::rgb_hex(0x1f1e1f))
|
||||
.add_child(ui);
|
||||
})
|
||||
.add_child(ui);
|
||||
})
|
||||
.add_root(ui, size);
|
||||
|
||||
//Bottom bar (yeah, it's basically fake/overlay)
|
||||
Container::default()
|
||||
.with_size(size!(100%))
|
||||
.with_align((Alignment::Begin, Alignment::End))
|
||||
.with_children(|ui| {
|
||||
//Status bar
|
||||
Container::default()
|
||||
.with_size(size!(100%, auto))
|
||||
.with_background(color::rgb_hex(0x0079cc))
|
||||
|
|
|
@ -4,7 +4,8 @@ description = "glium render backend for `hui`"
|
|||
repository = "https://github.com/griffi-gh/hui"
|
||||
readme = "../README.md"
|
||||
authors = ["griffi-gh <prasol258@gmail.com>"]
|
||||
version = "0.1.0-alpha.4"
|
||||
version = "0.1.0-alpha.5"
|
||||
rust-version = "1.75"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
publish = true
|
||||
|
@ -15,7 +16,7 @@ include = [
|
|||
]
|
||||
|
||||
[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 }
|
||||
glam = "0.27"
|
||||
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 glam::Vec2;
|
||||
use glium::{
|
||||
Blend, DrawParameters, IndexBuffer, Program, Surface, VertexBuffer,
|
||||
implement_vertex, uniform,
|
||||
backend::{Context, Facade},
|
||||
index::PrimitiveType,
|
||||
texture::{RawImage2d, Texture2d},
|
||||
uniforms::{MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerBehavior, SamplerWrapFunction},
|
||||
backend::{Context, Facade}, implement_vertex, index::PrimitiveType, texture::{RawImage2d, Texture2d}, uniform, uniforms::{MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerBehavior, SamplerWrapFunction}, Api, Blend, DrawParameters, IndexBuffer, Program, Surface, VertexBuffer
|
||||
};
|
||||
use hui::{
|
||||
draw::{TextureAtlasMeta, UiDrawCall, UiVertex}, UiInstance
|
||||
};
|
||||
|
||||
const VERTEX_SHADER: &str = include_str!("../shaders/vertex.vert");
|
||||
const FRAGMENT_SHADER: &str = include_str!("../shaders/fragment.frag");
|
||||
const VERTEX_SHADER_GLES3: &str = include_str!("../shaders/vertex.es.vert");
|
||||
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)]
|
||||
#[repr(C)]
|
||||
|
@ -122,7 +120,10 @@ impl GliumUiRenderer {
|
|||
pub fn new<F: Facade>(facade: &F) -> Self {
|
||||
log::info!("initializing hui-glium");
|
||||
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()),
|
||||
ui_texture: None,
|
||||
buffer_pair: None,
|
||||
|
@ -183,7 +184,7 @@ impl GliumUiRenderer {
|
|||
tex: Sampler(self.ui_texture.as_ref().unwrap(), SamplerBehavior {
|
||||
max_anisotropy: 1,
|
||||
magnify_filter: MagnifySamplerFilter::Nearest,
|
||||
minify_filter: MinifySamplerFilter::NearestMipmapNearest,
|
||||
minify_filter: MinifySamplerFilter::Linear,
|
||||
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
|
||||
..Default::default()
|
||||
}),
|
||||
|
|
|
@ -4,7 +4,7 @@ description = "winit platform backend for `hui`"
|
|||
repository = "https://github.com/griffi-gh/hui"
|
||||
readme = "../README.md"
|
||||
authors = ["griffi-gh <prasol258@gmail.com>"]
|
||||
version = "0.1.0-alpha.4"
|
||||
version = "0.1.0-alpha.5"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
publish = true
|
||||
|
@ -14,7 +14,7 @@ include = [
|
|||
]
|
||||
|
||||
[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 }
|
||||
glam = "0.27"
|
||||
log = "0.4"
|
||||
|
|
|
@ -5,7 +5,7 @@ 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.4"
|
||||
version = "0.1.0-alpha.5"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
publish = true
|
||||
|
@ -16,6 +16,7 @@ include = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
hui-derive = { version = "0.1.0-alpha.5", path = "../hui-derive", optional = true }
|
||||
hashbrown = "0.14"
|
||||
nohash-hasher = "0.2"
|
||||
glam = "0.27"
|
||||
|
@ -29,15 +30,14 @@ tinyset = "0.4"
|
|||
image = { version = "0.25", default-features = false, optional = true }
|
||||
|
||||
[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
|
||||
image = ["dep:image"]
|
||||
|
||||
#! #### Built-in font:
|
||||
|
||||
## Enable the built-in font (ProggyTiny, adds *35kb* to the executable)
|
||||
builtin_font = []
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
//! draw commands, tesselation and UI rendering.
|
||||
|
||||
//TODO: 9-slice draw command
|
||||
|
||||
use crate::{
|
||||
rect::Corners,
|
||||
text::{FontHandle, TextRenderer}
|
||||
|
@ -35,6 +33,8 @@ pub enum UiDrawCommand {
|
|||
color: Corners<Vec4>,
|
||||
///Texture
|
||||
texture: Option<ImageHandle>,
|
||||
///Sub-UV coordinates for the texture
|
||||
texture_uv: Option<Corners<Vec2>>,
|
||||
///Rounded corners
|
||||
rounded_corners: Option<RoundedCorners>,
|
||||
},
|
||||
|
@ -161,10 +161,36 @@ impl UiDrawCall {
|
|||
v.position += center;
|
||||
}
|
||||
},
|
||||
UiDrawCommand::Rectangle { position, size, color, texture, rounded_corners } => {
|
||||
UiDrawCommand::Rectangle { position, size, color, texture, texture_uv, rounded_corners } => {
|
||||
let uvs = texture
|
||||
.map(|x| atlas.get_uv(x))
|
||||
.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));
|
||||
|
||||
let vidx = draw_call.vertices.len() as u32;
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::any::Any;
|
|||
use crate::{
|
||||
draw::{atlas::ImageCtx, UiDrawCommandList},
|
||||
input::InputCtx,
|
||||
layout::LayoutInfo,
|
||||
layout::{LayoutInfo, Size2d},
|
||||
measure::Response,
|
||||
signal::SignalStore,
|
||||
state::StateRepo,
|
||||
|
@ -45,6 +45,11 @@ pub trait UiElement {
|
|||
/// For example, "button" or "progress_bar"
|
||||
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\
|
||||
/// This value must be unique for each instance of the element
|
||||
///
|
||||
|
|
|
@ -4,10 +4,10 @@ use derive_setters::Setters;
|
|||
use glam::{Vec2, vec2};
|
||||
use crate::{
|
||||
element::{ElementList, MeasureContext, ProcessContext, UiElement},
|
||||
layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d},
|
||||
frame::{Frame, FrameRect},
|
||||
layout::{compute_size, Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d, WrapBehavior},
|
||||
measure::{Hints, Response},
|
||||
rect::{Sides, FillColor},
|
||||
rect::Sides,
|
||||
};
|
||||
|
||||
//XXX: add Order/Direction::Forward/Reverse or sth?
|
||||
|
@ -19,6 +19,7 @@ use crate::{
|
|||
struct CudLine {
|
||||
start_idx: usize,
|
||||
content_size: Vec2,
|
||||
remaining_space: f32,
|
||||
}
|
||||
|
||||
struct ContainerUserData {
|
||||
|
@ -52,13 +53,9 @@ pub struct Container {
|
|||
#[setters(skip)]
|
||||
pub background_frame: Box<dyn Frame>,
|
||||
|
||||
/// Set this to `true` to allow the elements wrap automatically
|
||||
///
|
||||
/// Disabling/enabling this does not affect explicit wrapping\
|
||||
/// (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,
|
||||
/// Controls if wrapping is enabled
|
||||
#[setters(into)]
|
||||
pub wrap: WrapBehavior,
|
||||
|
||||
/// List of children elements
|
||||
#[setters(skip)]
|
||||
|
@ -86,7 +83,7 @@ impl Default for Container {
|
|||
padding: Sides::all(0.),
|
||||
align: Alignment2d::default(),
|
||||
background_frame: Box::<FrameRect>::default(),
|
||||
wrap: false,
|
||||
wrap: WrapBehavior::Allow,
|
||||
children: ElementList(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
@ -94,19 +91,24 @@ impl Default for Container {
|
|||
|
||||
impl Container {
|
||||
pub fn measure_max_inner_size(&self, layout: &LayoutInfo) -> Vec2 {
|
||||
let outer_size_x = match self.size.width {
|
||||
Size::Auto => layout.max_size.x,
|
||||
Size::Relative(p) => layout.max_size.x * 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_x = match self.size.width {
|
||||
// Size::Auto => layout.max_size.x,
|
||||
// Size::Relative(p) => layout.max_size.x * p,
|
||||
// Size::Absolute(p) => p,
|
||||
// Size::Remaining(p) => match layout.direction {
|
||||
// Direction::Horizontal => layout.remaining_space.unwrap_or(layout.max_size.x) * p,
|
||||
// Direction::Vertical => layout.max_size.x,
|
||||
// }
|
||||
// };
|
||||
// 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(
|
||||
outer_size_x - (self.padding.left + self.padding.right),
|
||||
outer_size_y - (self.padding.top + self.padding.bottom),
|
||||
outer_size.x - (self.padding.left + self.padding.right),
|
||||
outer_size.y - (self.padding.top + self.padding.bottom),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +118,10 @@ impl UiElement for Container {
|
|||
"container"
|
||||
}
|
||||
|
||||
fn size(&self) -> Option<Size2d> {
|
||||
Some(self.size)
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
// 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
|
||||
|
@ -130,11 +136,13 @@ impl UiElement for Container {
|
|||
Size::Auto => ctx.layout.max_size.x,
|
||||
Size::Relative(p) => ctx.layout.max_size.x * 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 {
|
||||
Size::Auto => ctx.layout.max_size.y,
|
||||
Size::Relative(p) => ctx.layout.max_size.y * 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 {
|
||||
start_idx: 0,
|
||||
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() {
|
||||
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{
|
||||
state: ctx.state,
|
||||
layout: &LayoutInfo {
|
||||
|
@ -176,6 +199,7 @@ impl UiElement for Container {
|
|||
//TODO: subtract size already taken by previous children
|
||||
max_size: self.measure_max_inner_size(ctx.layout),
|
||||
direction: self.direction,
|
||||
remaining_space: None,
|
||||
},
|
||||
text_measure: ctx.text_measure,
|
||||
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
|
||||
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!
|
||||
|
||||
//Negate the leftover gap from the previous element
|
||||
line_size -= leftover_gap;
|
||||
|
||||
//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
|
||||
lines.push(CudLine {
|
||||
start_idx: idx,
|
||||
content_size: Vec2::ZERO,
|
||||
remaining_space: 0.,
|
||||
});
|
||||
|
||||
//Update the total size accordingly
|
||||
|
@ -252,7 +287,16 @@ impl UiElement for Container {
|
|||
line_size -= leftover_gap;
|
||||
|
||||
//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
|
||||
match self.direction {
|
||||
|
@ -278,17 +322,27 @@ impl UiElement for Container {
|
|||
self.padding.top + self.padding.bottom,
|
||||
);
|
||||
|
||||
let computed_size = compute_size(ctx.layout, self.size, total_size);
|
||||
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,
|
||||
_ => total_size.x = computed_size.x,
|
||||
}
|
||||
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,
|
||||
_ => total_size.y = computed_size.y,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
size: total_size,
|
||||
hints: Hints {
|
||||
|
@ -385,6 +439,7 @@ impl UiElement for Container {
|
|||
position: local_position,
|
||||
max_size: self.measure_max_inner_size(ctx.layout),
|
||||
direction: self.direction,
|
||||
remaining_space: Some(cur_line.remaining_space),
|
||||
};
|
||||
|
||||
//measure
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
draw::{RoundedCorners, UiDrawCommand},
|
||||
element::{MeasureContext, ProcessContext, UiElement},
|
||||
frame::{Frame, FrameRect},
|
||||
layout::{Size, Size2d},
|
||||
layout::{compute_size, Size, Size2d},
|
||||
measure::Response,
|
||||
size
|
||||
};
|
||||
|
@ -45,20 +45,13 @@ impl UiElement for FillRect {
|
|||
"fill_rect"
|
||||
}
|
||||
|
||||
fn size(&self) -> Option<Size2d> {
|
||||
Some(self.size)
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
Response {
|
||||
size: vec2(
|
||||
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,
|
||||
},
|
||||
),
|
||||
size: compute_size(ctx.layout, self.size, ctx.layout.max_size),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,10 @@ impl UiElement for Image {
|
|||
"image"
|
||||
}
|
||||
|
||||
fn size(&self) -> Option<Size2d> {
|
||||
Some(self.size)
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
let dim = ctx.images.get_size(self.image).expect("invalid image handle");
|
||||
let pre_size = compute_size(ctx.layout, self.size, dim.as_vec2());
|
||||
|
@ -79,6 +83,7 @@ impl UiElement for Image {
|
|||
size: ctx.measure.size,
|
||||
color: self.color.corners(),
|
||||
texture: Some(self.image),
|
||||
texture_uv: None,
|
||||
rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({
|
||||
RoundedCorners::from_radius(self.corner_radius)
|
||||
}),
|
||||
|
|
|
@ -48,6 +48,10 @@ impl UiElement for Interactable {
|
|||
"interactable"
|
||||
}
|
||||
|
||||
fn size(&self) -> Option<crate::layout::Size2d> {
|
||||
self.element.size()
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> crate::measure::Response {
|
||||
self.element.measure(ctx)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use derive_setters::Setters;
|
||||
use glam::{vec2, vec4};
|
||||
use glam::vec2;
|
||||
use crate::{
|
||||
draw::{RoundedCorners, UiDrawCommand},
|
||||
element::{MeasureContext, ProcessContext, UiElement},
|
||||
frame::{Frame, FrameRect},
|
||||
layout::{compute_size, Size, Size2d},
|
||||
measure::Response,
|
||||
rect::{Corners, FillColor}
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Setters)]
|
||||
//TODO: Use Frames here instead of FillColor
|
||||
|
||||
#[derive(Setters)]
|
||||
#[setters(prefix = "with_")]
|
||||
pub struct ProgressBar {
|
||||
/// Current progress, should be in the range 0.0..=1.0
|
||||
|
@ -19,20 +20,26 @@ pub struct ProgressBar {
|
|||
pub size: Size2d,
|
||||
|
||||
/// Foreground (bar) color
|
||||
#[setters(into)]
|
||||
pub foreground: FillColor,
|
||||
#[setters(skip)]
|
||||
pub foreground: Box<dyn Frame>,
|
||||
|
||||
/// Background color
|
||||
#[setters(into)]
|
||||
pub background: FillColor,
|
||||
|
||||
/// Corner radius of the progress bar
|
||||
#[setters(into)]
|
||||
pub corner_radius: Corners<f32>,
|
||||
#[setters(skip)]
|
||||
pub background: Box<dyn Frame>,
|
||||
}
|
||||
|
||||
impl ProgressBar {
|
||||
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 {
|
||||
|
@ -40,9 +47,8 @@ impl Default for ProgressBar {
|
|||
Self {
|
||||
value: 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.),
|
||||
foreground: Box::new(FrameRect::color((0.0, 0.0, 1.0, 1.0))),
|
||||
background: Box::new(FrameRect::color((0.0, 0.0, 0.0, 1.0))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +61,7 @@ impl UiElement for ProgressBar {
|
|||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
Response {
|
||||
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,
|
||||
)),
|
||||
hints: Default::default(),
|
||||
|
@ -66,38 +72,49 @@ impl UiElement for ProgressBar {
|
|||
|
||||
fn process(&self, ctx: ProcessContext) {
|
||||
let value = self.value.clamp(0., 1.);
|
||||
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,
|
||||
rounded_corners
|
||||
});
|
||||
|
||||
//FIXME: these optimizations may not be valid
|
||||
if value < 1. || !self.foreground.covers_opaque() {
|
||||
self.background.draw(ctx.draw, ctx.layout.position, ctx.measure.size);
|
||||
}
|
||||
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,
|
||||
rounded_corners,
|
||||
});
|
||||
}
|
||||
self.foreground.draw(ctx.draw, ctx.layout.position, ctx.measure.size * vec2(value, 1.));
|
||||
}
|
||||
|
||||
// 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::{
|
||||
draw::UiDrawCommand,
|
||||
element::{MeasureContext, ProcessContext, UiElement},
|
||||
layout::{Size, Size2d},
|
||||
layout::{compute_size, Size, Size2d},
|
||||
measure::Response,
|
||||
text::FontHandle,
|
||||
};
|
||||
|
@ -74,6 +74,10 @@ impl UiElement for Text {
|
|||
"text"
|
||||
}
|
||||
|
||||
fn size(&self) -> Option<Size2d> {
|
||||
Some(self.size)
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
let mut size = (0., 0.);
|
||||
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;
|
||||
}
|
||||
Response {
|
||||
size: vec2(
|
||||
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,
|
||||
},
|
||||
),
|
||||
size: compute_size(ctx.layout, self.size, size.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
//! modular procedural background system
|
||||
|
||||
use glam::Vec2;
|
||||
use crate::draw::UiDrawCommandList;
|
||||
|
||||
pub mod point;
|
||||
mod rect;
|
||||
pub mod stack;
|
||||
pub mod nine_patch;
|
||||
mod impls;
|
||||
|
||||
pub use rect::FrameRect;
|
||||
|
||||
/// Trait for a drawable frame
|
||||
pub trait Frame {
|
||||
/// Draw the frame at the given position and size
|
||||
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
|
||||
|
|
|
@ -13,6 +13,7 @@ impl Frame for ImageHandle {
|
|||
size: parent_size,
|
||||
color: color::WHITE.into(),
|
||||
texture: Some(*self),
|
||||
texture_uv: None,
|
||||
rounded_corners: None,
|
||||
})
|
||||
}
|
||||
|
@ -29,6 +30,7 @@ impl Frame for FillColor {
|
|||
size: parent_size,
|
||||
color: self.corners(),
|
||||
texture: None,
|
||||
texture_uv: 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 derive_more::{Add, AddAssign, Sub, SubAssign};
|
||||
use crate::layout::{Size, Size2d};
|
||||
|
@ -24,11 +26,13 @@ impl From<f32> for FramePoint {
|
|||
impl From<Size> for 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 {
|
||||
match size {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,6 +124,7 @@ impl Frame for FrameRect {
|
|||
size: bottom_right - top_left,
|
||||
color: self.color.corners(),
|
||||
texture: self.image,
|
||||
texture_uv: None,
|
||||
rounded_corners: (self.corner_radius.max_f32() > 0.).then_some(
|
||||
RoundedCorners::from_radius(self.corner_radius)
|
||||
),
|
||||
|
@ -140,6 +141,7 @@ impl Frame for FrameRect {
|
|||
self.bottom_right.y.absolute >= 0. &&
|
||||
self.bottom_right.y.relative >= 1. &&
|
||||
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 crate::draw::UiDrawCommandList;
|
||||
use super::Frame;
|
||||
|
||||
/// A frame that draws two frames on top of each other
|
||||
pub struct FrameStack(pub Box<dyn Frame>, pub Box<dyn Frame>);
|
||||
|
||||
impl Frame for FrameStack {
|
||||
|
@ -17,6 +20,7 @@ impl Frame for FrameStack {
|
|||
}
|
||||
|
||||
pub trait FrameStackExt: Frame {
|
||||
/// Stack another frame on top of this one
|
||||
fn stack(self, other: impl Frame + 'static) -> FrameStack;
|
||||
}
|
||||
|
||||
|
|
|
@ -101,6 +101,8 @@ pub enum KeyboardKey {
|
|||
macro_rules! impl_fits64_for_keyboard_key {
|
||||
($($i:ident = $v:literal),*) => {
|
||||
impl Fits64 for KeyboardKey {
|
||||
// SAFETY: not actually doing anything unsafe
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn from_u64(x: u64) -> Self {
|
||||
match x {
|
||||
$( $v => KeyboardKey::$i, )*
|
||||
|
|
|
@ -95,7 +95,7 @@ impl UiInstance {
|
|||
/// (this will change to a soft error in the future)
|
||||
#[cfg(feature = "image")]
|
||||
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)
|
||||
let mut file = std::io::BufReader::new(std::fs::File::open(path)?);
|
||||
|
@ -106,6 +106,7 @@ impl UiInstance {
|
|||
let mut magic = [0; 64];
|
||||
file.read_exact(&mut magic)?;
|
||||
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
|
||||
let image = image::load(file, format).expect("Invalid image data");
|
||||
|
@ -156,6 +157,7 @@ impl UiInstance {
|
|||
position: Vec2::ZERO,
|
||||
max_size,
|
||||
direction: Direction::Vertical,
|
||||
remaining_space: None,
|
||||
};
|
||||
let measure = element.measure(MeasureContext {
|
||||
state: &self.stateful_state,
|
||||
|
|
|
@ -2,6 +2,46 @@
|
|||
|
||||
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
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, PartialOrd, Ord)]
|
||||
pub enum Alignment {
|
||||
|
@ -86,17 +126,24 @@ pub enum Size {
|
|||
#[default]
|
||||
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
|
||||
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 {
|
||||
|
@ -159,6 +206,13 @@ pub struct LayoutInfo {
|
|||
/// Current direction of the layout\
|
||||
/// (Usually matches direction of the parent container)
|
||||
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\
|
||||
|
@ -168,11 +222,19 @@ pub fn compute_size(layout: &LayoutInfo, size: Size2d, comfy_size: Vec2) -> Vec2
|
|||
Size::Auto => comfy_size.x,
|
||||
Size::Relative(fraction) => layout.max_size.x * fraction,
|
||||
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 {
|
||||
Size::Auto => comfy_size.y,
|
||||
Size::Relative(fraction) => layout.max_size.y * fraction,
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
//! # Features
|
||||
#![doc = document_features::document_features!()]
|
||||
|
||||
#![allow(unused_parens)]
|
||||
//#![forbid(unsafe_code)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
#![allow(unused_parens)]
|
||||
|
||||
mod instance;
|
||||
mod macros;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
/// - `x` - `Size::Absolute(x)`
|
||||
/// - `x%` - `Size::Relative(x / 100.)` *(literal only)*
|
||||
/// - `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
|
||||
///
|
||||
|
@ -32,6 +34,12 @@ macro_rules! size {
|
|||
($x:literal /) => {
|
||||
$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) => {
|
||||
$crate::layout::Size::Absolute($x as f32)
|
||||
|
@ -39,6 +47,9 @@ macro_rules! size {
|
|||
($x:ident /) => {
|
||||
$crate::layout::Size::Relative($x as f32)
|
||||
};
|
||||
($x:ident /=) => {
|
||||
$crate::layout::Size::Remaining($x as f32)
|
||||
};
|
||||
|
||||
(($x:expr)) => {
|
||||
$crate::layout::Size::Absolute(($x) as f32)
|
||||
|
@ -46,6 +57,9 @@ macro_rules! size {
|
|||
(($x:expr) /) => {
|
||||
$crate::layout::Size::Relative(($x) as f32)
|
||||
};
|
||||
(($x:expr) /=) => {
|
||||
$crate::layout::Size::Remaining(($x) as f32)
|
||||
};
|
||||
|
||||
($x:tt , $y:tt $($ys:tt)?) => {
|
||||
$crate::layout::Size2d {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::Corners;
|
||||
use glam::{Vec3, Vec4, vec4};
|
||||
use glam::{Vec2, Vec3, Vec4, vec4};
|
||||
|
||||
/// Represents the fill color of a rectangle
|
||||
///
|
||||
|
@ -69,6 +69,14 @@ impl FillColor {
|
|||
pub const fn corners(&self) -> Corners<Vec4> {
|
||||
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 {
|
||||
|
|
|
@ -6,6 +6,9 @@ use nohash_hasher::BuildNoHashHasher;
|
|||
|
||||
pub mod trigger;
|
||||
|
||||
#[cfg(feature = "derive")]
|
||||
pub use hui_derive::Signal;
|
||||
|
||||
/// A marker trait for UI Signals
|
||||
pub trait Signal: Any {}
|
||||
|
||||
|
|
Loading…
Reference in a new issue