mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-25 08:28:42 -06:00
Compare commits
12 commits
bbc1d9f1ff
...
fb2f3c739e
Author | SHA1 | Date | |
---|---|---|---|
griffi-gh | fb2f3c739e | ||
griffi-gh | b12c62e06f | ||
griffi-gh | 3ac83e161a | ||
griffi-gh | d1e1325068 | ||
griffi-gh | 5ce9dda77b | ||
griffi-gh | fa994e659a | ||
griffi-gh | 328f745c39 | ||
griffi-gh | e22fa739c1 | ||
griffi-gh | 89037efebb | ||
griffi-gh | d347f8f7e9 | ||
griffi-gh | 85810a2e59 | ||
griffi-gh | 7bfd12749b |
|
@ -7,7 +7,7 @@ use hui::{
|
|||
container::Container,
|
||||
text::Text,
|
||||
image::Image,
|
||||
br::Br,
|
||||
br::Break,
|
||||
interactable::ElementInteractableExt,
|
||||
UiElementExt,
|
||||
},
|
||||
|
@ -44,7 +44,7 @@ ui_main!(
|
|||
Text::new("Number of images:")
|
||||
.with_text_size(24)
|
||||
.add_child(ui);
|
||||
Br.add_child(ui);
|
||||
Break.add_child(ui);
|
||||
Container::default()
|
||||
.with_padding(10.)
|
||||
.with_background(color::ORANGE)
|
||||
|
@ -53,7 +53,7 @@ ui_main!(
|
|||
.with_text_size(32)
|
||||
.add_child(ui);
|
||||
})
|
||||
.on_click(CounterSignal::Decrement)
|
||||
.on_click(|| CounterSignal::Decrement)
|
||||
.add_child(ui);
|
||||
Container::default()
|
||||
.with_size(size!(60, auto))
|
||||
|
@ -72,9 +72,9 @@ ui_main!(
|
|||
.with_text_size(32)
|
||||
.add_child(ui);
|
||||
})
|
||||
.on_click(CounterSignal::Increment)
|
||||
.on_click(|| CounterSignal::Increment)
|
||||
.add_child(ui);
|
||||
Br.add_child(ui);
|
||||
Break.add_child(ui);
|
||||
for _ in 0..*counter {
|
||||
Image::new(*image)
|
||||
.with_size(size!(48, 48))
|
||||
|
|
|
@ -6,7 +6,7 @@ use hui::{
|
|||
container::Container,
|
||||
text::Text,
|
||||
image::Image,
|
||||
br::Br,
|
||||
br::Break,
|
||||
slider::Slider,
|
||||
UiElementExt,
|
||||
},
|
||||
|
@ -43,22 +43,22 @@ ui_main!(
|
|||
Text::new(format!("Number of images: {counter}"))
|
||||
.with_text_size(32)
|
||||
.add_child(ui);
|
||||
Br.add_child(ui);
|
||||
Break.add_child(ui);
|
||||
Text::new("Absolute tracking slider:")
|
||||
.with_text_size(16)
|
||||
.add_child(ui);
|
||||
Br.add_child(ui);
|
||||
Break.add_child(ui);
|
||||
Slider::new(*counter as f32 / 100.)
|
||||
.with_size(size!(66%, 20))
|
||||
.on_change(|x| {
|
||||
CounterSignal::ChangeValue((x * 100.).round() as u32)
|
||||
})
|
||||
.add_child(ui);
|
||||
Br.add_child(ui);
|
||||
Break.add_child(ui);
|
||||
Text::new("Relative tracking slider (Experimental):")
|
||||
.with_text_size(16)
|
||||
.add_child(ui);
|
||||
Br.add_child(ui);
|
||||
Break.add_child(ui);
|
||||
Slider::new(*counter as f32 / 100.)
|
||||
.with_size(size!(66%, 20))
|
||||
.with_follow_mode(hui::element::slider::SliderFollowMode::Relative)
|
||||
|
@ -66,7 +66,7 @@ ui_main!(
|
|||
CounterSignal::ChangeValue((x * 100.).round() as u32)
|
||||
})
|
||||
.add_child(ui);
|
||||
Br.add_child(ui);
|
||||
Break.add_child(ui);
|
||||
for _ in 0..*counter {
|
||||
Image::new(*image)
|
||||
.with_size(size!(48, 48))
|
||||
|
|
|
@ -55,6 +55,7 @@ pub enum UiDrawCommand {
|
|||
PushTransform(Affine2),
|
||||
/// Pop a transformation matrix from the stack
|
||||
PopTransform,
|
||||
//TODO PushClip PopClip
|
||||
}
|
||||
|
||||
/// List of draw commands
|
||||
|
|
|
@ -17,6 +17,12 @@ pub struct RoundedCorners {
|
|||
pub point_count: NonZeroU16,
|
||||
}
|
||||
|
||||
impl From<Corners<f32>> for RoundedCorners {
|
||||
fn from(radius: Corners<f32>) -> Self {
|
||||
Self::from_radius(radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl RoundedCorners {
|
||||
pub fn from_radius(radius: Corners<f32>) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -40,7 +40,9 @@ pub struct ProcessContext<'a> {
|
|||
}
|
||||
|
||||
pub trait UiElement {
|
||||
/// Get the name of the element, for example "Button" or "ProgressBar"
|
||||
/// Get the name of the element (in lower case)
|
||||
///
|
||||
/// For example, "button" or "progress_bar"
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Get the unique id used for internal state management\
|
||||
|
|
|
@ -4,11 +4,11 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Br;
|
||||
pub struct Break;
|
||||
|
||||
impl UiElement for Br {
|
||||
impl UiElement for Break {
|
||||
fn name(&self) -> &'static str {
|
||||
"Br"
|
||||
"break"
|
||||
}
|
||||
|
||||
fn measure(&self, _: MeasureContext) -> Response {
|
||||
|
|
|
@ -133,7 +133,7 @@ impl Container {
|
|||
|
||||
impl UiElement for Container {
|
||||
fn name(&self) -> &'static str {
|
||||
"Container"
|
||||
"container"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -40,7 +40,7 @@ impl Default for FillRect {
|
|||
|
||||
impl UiElement for FillRect {
|
||||
fn name(&self) -> &'static str {
|
||||
"FillRect"
|
||||
"fill_rect"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -51,7 +51,7 @@ impl Image {
|
|||
|
||||
impl UiElement for Image {
|
||||
fn name(&self) -> &'static str {
|
||||
"Image"
|
||||
"image"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
use crate::{
|
||||
element::{MeasureContext, ProcessContext, UiElement},
|
||||
signal::Signal,
|
||||
signal::{trigger::SignalTrigger, Signal},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -15,10 +14,11 @@ pub enum InteractableEvent {
|
|||
#[default]
|
||||
Click,
|
||||
Hover,
|
||||
Active,
|
||||
}
|
||||
|
||||
/// Wrapper that allows adding click and hover events to any element
|
||||
pub struct Interactable<C: Signal + 'static> {
|
||||
pub struct Interactable {
|
||||
/// The wrapped element that will be interactable
|
||||
pub element: Box<dyn UiElement>,
|
||||
|
||||
|
@ -26,22 +26,26 @@ pub struct Interactable<C: Signal + 'static> {
|
|||
pub event: InteractableEvent,
|
||||
|
||||
/// Signal that will be called if the element was clicked in the current frame
|
||||
pub signal: RefCell<Option<C>>,
|
||||
pub signal: SignalTrigger,
|
||||
}
|
||||
|
||||
impl<C: Signal + 'static> Interactable<C> {
|
||||
pub fn new(element: Box<dyn UiElement>, event: InteractableEvent, signal: C) -> Self {
|
||||
impl Interactable {
|
||||
pub fn new<S: Signal, F: Fn() -> S + 'static>(
|
||||
element: Box<dyn UiElement>,
|
||||
event: InteractableEvent,
|
||||
signal: F
|
||||
) -> Self {
|
||||
Self {
|
||||
element,
|
||||
event,
|
||||
signal: RefCell::new(Some(signal)),
|
||||
signal: SignalTrigger::new(signal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Signal + 'static> UiElement for Interactable<C> {
|
||||
impl UiElement for Interactable {
|
||||
fn name(&self) -> &'static str {
|
||||
"Interactable"
|
||||
"interactable"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> crate::measure::Response {
|
||||
|
@ -56,12 +60,11 @@ impl<C: Signal + 'static> UiElement for Interactable<C> {
|
|||
//TODO: actually pass the response
|
||||
InteractableEvent::Click => ctx.input.check_click(rect).is_some(),
|
||||
InteractableEvent::Hover => ctx.input.check_hover(rect),
|
||||
InteractableEvent::Active => ctx.input.check_active(rect).is_some(),
|
||||
};
|
||||
|
||||
if event_happened {
|
||||
if let Some(sig) = self.signal.take() {
|
||||
ctx.signal.add(sig);
|
||||
}
|
||||
self.signal.fire(ctx.signal);
|
||||
}
|
||||
|
||||
self.element.process(ctx)
|
||||
|
@ -71,25 +74,32 @@ impl<C: Signal + 'static> UiElement for Interactable<C> {
|
|||
/// Extension trait for [`UiElement`] that adds methods to wrap the element in an [`Interactable`]
|
||||
pub trait ElementInteractableExt: UiElement {
|
||||
/// Wrap the element in an [`Interactable`] that will call the given signal when the specified event occurs
|
||||
fn into_interactable<C: Signal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C>;
|
||||
fn into_interactable<S: Signal, F: Fn() -> S + 'static>(self, event: InteractableEvent, signal: F) -> Interactable;
|
||||
|
||||
/// Wrap the element in an [`Interactable`] that will call the given signal when clicked
|
||||
fn on_click<C: Signal + 'static>(self, signal: C) -> Interactable<C>;
|
||||
fn on_click<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable;
|
||||
|
||||
/// Wrap the element in an [`Interactable`] that will call the given signal while hovered
|
||||
fn on_hover<C: Signal + 'static>(self, signal: C) -> Interactable<C>;
|
||||
/// Wrap the element in an [`Interactable`] that will call the given signal continuously while hovered
|
||||
fn on_hover<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable;
|
||||
|
||||
/// Wrap the element in an [`Interactable`] that will call the given signal continuously while active
|
||||
fn on_active<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable;
|
||||
}
|
||||
|
||||
impl<T: UiElement + 'static> ElementInteractableExt for T {
|
||||
fn into_interactable<C: Signal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C> {
|
||||
fn into_interactable<S: Signal, F: Fn() -> S + 'static>(self, event: InteractableEvent, signal: F) -> Interactable {
|
||||
Interactable::new(Box::new(self), event, signal)
|
||||
}
|
||||
|
||||
fn on_click<C: Signal + 'static>(self, signal: C) -> Interactable<C> {
|
||||
fn on_click<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable {
|
||||
self.into_interactable(InteractableEvent::Click, signal)
|
||||
}
|
||||
|
||||
fn on_hover<C: Signal + 'static>(self, signal: C) -> Interactable<C> {
|
||||
fn on_hover<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable {
|
||||
self.into_interactable(InteractableEvent::Hover, signal)
|
||||
}
|
||||
|
||||
fn on_active<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable {
|
||||
self.into_interactable(InteractableEvent::Active, signal)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ impl Default for ProgressBar {
|
|||
|
||||
impl UiElement for ProgressBar {
|
||||
fn name(&self) -> &'static str {
|
||||
"ProgressBar"
|
||||
"progress_bar"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -103,7 +103,7 @@ impl Slider {
|
|||
|
||||
impl UiElement for Slider {
|
||||
fn name(&self) -> &'static str {
|
||||
"Slider"
|
||||
"slider"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -19,7 +19,7 @@ impl Default for Spacer {
|
|||
|
||||
impl UiElement for Spacer {
|
||||
fn name(&self) -> &'static str {
|
||||
"Spacer"
|
||||
"spacer"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -71,7 +71,7 @@ impl Text {
|
|||
|
||||
impl UiElement for Text {
|
||||
fn name(&self) -> &'static str {
|
||||
"Text"
|
||||
"text"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -38,7 +38,7 @@ impl Transformer {
|
|||
|
||||
impl UiElement for Transformer {
|
||||
fn name(&self) -> &'static str {
|
||||
"Transformer"
|
||||
"transformer"
|
||||
}
|
||||
|
||||
fn measure(&self, ctx: MeasureContext) -> Response {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use crate::rect::FillColor;
|
||||
|
||||
pub mod point;
|
||||
pub mod layer;
|
||||
|
||||
use layer::{FrameLayer, RectLayer};
|
||||
use glam::Vec2;
|
||||
use layer::{FrameLayer, FrameLayerImpl};
|
||||
use crate::draw::UiDrawCommandList;
|
||||
|
||||
///XXX: this is not used yet, and also kinda a mess, simplify?
|
||||
///Maybe limit to a single layer? (aka `Frame` will be just one of the options)
|
||||
|
@ -20,23 +19,49 @@ pub struct Frame {
|
|||
layers: Vec<FrameLayer>
|
||||
}
|
||||
|
||||
impl<T: Into<FillColor>> From<T> for Frame {
|
||||
fn from(color: T) -> Self {
|
||||
impl<T: Into<FrameLayer>> From<T> for Frame {
|
||||
fn from(layer: T) -> Self {
|
||||
let mut frame = Self::default();
|
||||
frame.add(RectLayer::from_color(color));
|
||||
frame.add(layer.into());
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Get the layer with the given index
|
||||
#[inline]
|
||||
pub fn layer(&self, index: usize) -> Option<&FrameLayer> {
|
||||
self.layers.get(index)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the layer with the given index
|
||||
#[inline]
|
||||
pub fn layer_mut(&mut self, index: usize) -> Option<&mut FrameLayer> {
|
||||
self.layers.get_mut(index)
|
||||
}
|
||||
|
||||
/// Add a layer to the frame
|
||||
#[inline]
|
||||
pub fn add(&mut self, layer: impl Into<FrameLayer>) -> &mut Self {
|
||||
self.layers.push(layer.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a layer to the back of the frame
|
||||
#[inline]
|
||||
pub fn add_back(&mut self, layer: impl Into<FrameLayer>) -> &mut Self {
|
||||
self.layers.insert(0, layer.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn finish(&mut self) -> Self {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
|
||||
for layer in &self.layers {
|
||||
layer.draw(draw, position, parent_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,25 +9,48 @@ use super::point::FramePoint2d;
|
|||
|
||||
#[enum_dispatch]
|
||||
pub(crate) trait FrameLayerImpl {
|
||||
fn draw(&self, draw: &mut UiDrawCommandList, parent_size: Vec2);
|
||||
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[enum_dispatch(FrameLayerImpl)]
|
||||
pub enum FrameLayer {
|
||||
Rect(RectLayer),
|
||||
Rect(RectFrame),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RectLayer {
|
||||
color: FillColor,
|
||||
image: Option<ImageHandle>,
|
||||
top_left: FramePoint2d,
|
||||
bottom_right: FramePoint2d,
|
||||
corner_radius: Corners<f32>,
|
||||
pub struct RectFrame {
|
||||
/// Background color of the frame\
|
||||
///
|
||||
/// If the container has a background texture, it will be multiplied by this color
|
||||
pub color: FillColor,
|
||||
|
||||
/// Background texture of the frame
|
||||
///
|
||||
/// Can be used in conjunction with the background color\
|
||||
/// In this case, the texture will be shaded by the color
|
||||
///
|
||||
/// Please note that if the background color is NOT set (or set to transparent), the texture will NOT be visible\
|
||||
/// This is because the texture is multiplied by the color, and if the color is transparent, the texture will be too\
|
||||
pub image: Option<ImageHandle>,
|
||||
|
||||
/// Top left corner of the rectangle
|
||||
pub top_left: FramePoint2d,
|
||||
|
||||
/// Bottom right corner of the rectangle
|
||||
pub bottom_right: FramePoint2d,
|
||||
|
||||
/// Corner radius of the frame
|
||||
pub corner_radius: Corners<f32>,
|
||||
}
|
||||
|
||||
impl RectLayer {
|
||||
impl<T: Into<FillColor>> From<T> for RectFrame {
|
||||
fn from(color: T) -> Self {
|
||||
Self::from_color(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl RectFrame {
|
||||
pub fn from_color(color: impl Into<FillColor>) -> Self {
|
||||
Self {
|
||||
color: color.into(),
|
||||
|
@ -58,27 +81,45 @@ impl RectLayer {
|
|||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RectLayer {
|
||||
fn default() -> Self {
|
||||
pub fn from_color_image_rounded(color: impl Into<FillColor>, image: ImageHandle, corner_radius: impl Into<Corners<f32>>) -> Self {
|
||||
Self {
|
||||
color: FillColor::default(),
|
||||
image: None,
|
||||
top_left: FramePoint2d::default(),
|
||||
bottom_right: FramePoint2d::default(),
|
||||
corner_radius: Corners::default(),
|
||||
color: color.into(),
|
||||
image: Some(image),
|
||||
corner_radius: corner_radius.into(),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Inset the rectangle by the given amount
|
||||
pub fn inset(self, inset: f32) -> Self {
|
||||
Self {
|
||||
top_left: self.top_left + Vec2::splat(inset).into(),
|
||||
bottom_right: self.bottom_right - Vec2::splat(inset).into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameLayerImpl for RectLayer {
|
||||
fn draw(&self, draw: &mut UiDrawCommandList, parent_size: Vec2) {
|
||||
impl Default for RectFrame {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: FillColor::default(),
|
||||
image: None,
|
||||
top_left: FramePoint2d::TOP_LEFT,
|
||||
bottom_right: FramePoint2d::BOTTOM_RIGHT,
|
||||
corner_radius: Corners::all(0.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameLayerImpl for RectFrame {
|
||||
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
|
||||
//TODO: handle bottom_right < top_left
|
||||
let top_left = self.top_left.resolve(parent_size);
|
||||
let bottom_right = self.bottom_right.resolve(parent_size);
|
||||
draw.add(UiDrawCommand::Rectangle {
|
||||
position: top_left,
|
||||
position: position + top_left,
|
||||
size: bottom_right - top_left,
|
||||
color: self.color.corners(),
|
||||
texture: self.image,
|
||||
|
|
Loading…
Reference in a new issue