Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a widget for drawing outlines #191

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion crates/yakui-core/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::fmt;

use glam::Vec2;

use crate::dom::Dom;
use crate::dom::{Dom, DomNode};
use crate::event::EventResponse;
use crate::event::{EventInterest, WidgetEvent};
use crate::geometry::{Constraints, FlexFit};
Expand Down Expand Up @@ -119,6 +119,26 @@ pub trait Widget: 'static + fmt::Debug {
self.default_layout(ctx, constraints)
}

/// Tells the intrinsic width of the object, which is its width if the
/// widget were given unbounded constraints.
fn intrinsic_width(&self,node:&DomNode,dom:&Dom) -> f32 {
self.default_intrinsic_width(node, dom)
}

/// Default implementation of intrinsic width calculation.
/// Calculates the maximum of child widths
fn default_intrinsic_width(&self,node:&DomNode,dom:&Dom) -> f32 {
let mut width:f32 = 0.0;

for &child in &node.children {
let node=dom.get(child).unwrap();
let child_width=node.widget.intrinsic_width(&node,dom);
width = width.max(child_width);

}
width
}

/// A convenience method that always performs the default layout strategy
/// for a widget. This method is intended to be called from custom widget's
/// `layout` methods.
Expand Down Expand Up @@ -183,6 +203,9 @@ pub trait ErasedWidget: Any + fmt::Debug {
/// See [`Widget::flex`].
fn flex(&self) -> (u32, FlexFit);

/// See [`Widget::intrinsic_width`].
fn intrinsic_width(&self,node:&DomNode,dom:&Dom) -> f32;

/// See [`Widget::flow`].
fn flow(&self) -> Flow;

Expand All @@ -195,6 +218,7 @@ pub trait ErasedWidget: Any + fmt::Debug {
/// See [`Widget::event`].
fn event(&mut self, ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse;


/// Returns the type name of the widget, usable only for debugging.
fn type_name(&self) -> &'static str;
}
Expand All @@ -211,6 +235,10 @@ where
<T as Widget>::flex(self)
}

fn intrinsic_width(&self,node:&DomNode,dom:&Dom) -> f32 {
<T as Widget>::intrinsic_width(self,node,dom)
}

fn flow(&self) -> Flow {
<T as Widget>::flow(self)
}
Expand Down
16 changes: 13 additions & 3 deletions crates/yakui-widgets/src/shorthand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ use crate::widgets::{
CheckboxResponse, Circle, CircleResponse, ColoredBox, ColoredBoxResponse, ConstrainedBox,
ConstrainedBoxResponse, CountGrid, Divider, DividerResponse, Draggable, DraggableResponse,
Flexible, FlexibleResponse, Image, ImageResponse, List, ListResponse, MaxWidth,
MaxWidthResponse, NineSlice, Offset, OffsetResponse, Opaque, OpaqueResponse, Pad, PadResponse,
Reflow, ReflowResponse, Scrollable, ScrollableResponse, Slider, SliderResponse, Spacer, Stack,
StackResponse, State, StateResponse, Text, TextBox, TextBoxResponse, TextResponse,
MaxWidthResponse, NineSlice, Offset, OffsetResponse, Opaque, OpaqueResponse, Outline,
OutlineSide, Pad, PadResponse, Reflow, ReflowResponse, Scrollable, ScrollableResponse, Slider,
SliderResponse, Spacer, Stack, StackResponse, State, StateResponse, Text, TextBox,
TextBoxResponse, TextResponse,
};

/// See [List].
Expand Down Expand Up @@ -197,6 +198,15 @@ pub fn stack(children: impl FnOnce()) -> Response<StackResponse> {
Stack::new().show(children)
}

/// See [Outline].
pub fn outline<F: FnOnce()>(
color: Color,
width: f32,
side: OutlineSide,
children: F,
) -> Response<()> {
Outline::new(color, width, side).show(children)
}
pub fn use_state<F, T: 'static>(default: F) -> Response<StateResponse<T>>
where
F: FnOnce() -> T + 'static,
Expand Down
6 changes: 6 additions & 0 deletions crates/yakui-widgets/src/widgets/colored_box.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use yakui_core::dom::{Dom, DomNode};
use yakui_core::geometry::{Color, Constraints, Vec2};
use yakui_core::paint::PaintRect;
use yakui_core::widget::{LayoutContext, PaintContext, Widget};
Expand Down Expand Up @@ -47,6 +48,7 @@ impl ColoredBox {
pub fn show_children<F: FnOnce()>(self, children: F) -> Response<ColoredBoxResponse> {
widget_children::<ColoredBoxWidget, F>(children, self)
}

}

#[derive(Debug)]
Expand Down Expand Up @@ -82,6 +84,10 @@ impl Widget for ColoredBoxWidget {
input.constrain_min(size)
}

fn intrinsic_width(&self,node:&DomNode,dom:&Dom) -> f32 {
self.props.min_size.x.max(self.default_intrinsic_width(node,dom))
}

fn paint(&self, mut ctx: PaintContext<'_>) {
let node = ctx.dom.get_current();
let layout_node = ctx.layout.get(ctx.dom.current()).unwrap();
Expand Down
4 changes: 4 additions & 0 deletions crates/yakui-widgets/src/widgets/constrained_box.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use yakui_core::dom::{Dom, DomNode};
use yakui_core::geometry::{Constraints, Vec2};
use yakui_core::widget::{LayoutContext, Widget};
use yakui_core::Response;
Expand Down Expand Up @@ -65,4 +66,7 @@ impl Widget for ConstrainedBoxWidget {

input.constrain(constraints.constrain(size))
}
fn intrinsic_width(&self,node:&DomNode,dom:&Dom) -> f32 {
self.props.constraints.min.x.max(self.default_intrinsic_width(node,dom))
}
}
6 changes: 5 additions & 1 deletion crates/yakui-widgets/src/widgets/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use yakui_core::geometry::{Color, Constraints, Rect, Vec2};
use yakui_core::paint::PaintRect;
use yakui_core::widget::{LayoutContext, PaintContext, Widget};
use yakui_core::{Response, TextureId};

use yakui_core::dom::{Dom, DomNode};
use crate::util::widget;

/**
Expand Down Expand Up @@ -75,4 +75,8 @@ impl Widget for ImageWidget {
rect.add(ctx.paint);
}
}

fn intrinsic_width(&self, node: &DomNode, dom: &Dom) -> f32 {
self.props.size.x
}
}
76 changes: 76 additions & 0 deletions crates/yakui-widgets/src/widgets/intrinsic_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use yakui_core::dom::Dom;
use yakui_core::geometry::{Constraints, FlexFit, Vec2};
use yakui_core::widget::{LayoutContext, Widget};
use yakui_core::Response;

use crate::util::widget_children;

/**
A container that sizes its child to the child's intrinsic width

Responds with [IntrinsicWidthResponse].

Shorthand:
```rust

```
*/
#[derive(Debug)]
#[non_exhaustive]
#[must_use = "yakui widgets do nothing if you don't `show` them"]
pub struct IntrinsicWidth {

}

impl IntrinsicWidth {
pub fn new() -> Self {
Self {}
}

pub fn show<F: FnOnce()>(self, children: F) -> Response<IntrinsicWidthResponse> {
widget_children::<IntrinsicWidthWidget, F>(children, self)
}
}

#[derive(Debug)]
pub struct IntrinsicWidthWidget {
props: IntrinsicWidth,
}

pub type IntrinsicWidthResponse = ();

impl Widget for IntrinsicWidthWidget {
type Props<'a> = IntrinsicWidth;
type Response = IntrinsicWidthResponse;

fn new() -> Self {
Self {
props: IntrinsicWidth::new(),
}
}

fn update(&mut self, props: Self::Props<'_>) -> Self::Response {
self.props = props;
}

fn layout(&self, mut ctx: LayoutContext<'_>, input: Constraints) -> Vec2 {
let node = ctx.dom.get_current();

let intrinsic_width=self.intrinsic_width(&node,&ctx.dom);
let constraints = Constraints {
min: (input.min).max(Vec2::ZERO),
max: (input.max).max(Vec2::ZERO).min(Vec2::new(intrinsic_width,input.max.y)),
};

let mut size = Vec2::ZERO;

for &child in &node.children {
let child_size = ctx.calculate_layout(child, constraints);
size = size.max(child_size);
}

input.constrain(constraints.constrain(size))
}


}
22 changes: 20 additions & 2 deletions crates/yakui-widgets/src/widgets/list.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::util::widget_children;
use yakui_core::dom::{Dom, DomNode};
use yakui_core::geometry::{Constraints, FlexFit, Vec2};
use yakui_core::widget::{LayoutContext, Widget};
use yakui_core::{CrossAxisAlignment, Direction, Flow, MainAxisAlignment, MainAxisSize, Response};

use crate::util::widget_children;

/**
Lays out children in a single direction. Supports flex sizing.

Expand Down Expand Up @@ -90,6 +90,24 @@ impl Widget for ListWidget {
(flex, FlexFit::Tight)
}

fn intrinsic_width(&self,node:&DomNode, dom: &Dom) -> f32 {
if self.props.direction == Direction::Right {
let mut width:f32 = 0.0;

let total_item_spacing =
self.props.item_spacing * node.children.len().saturating_sub(1) as f32;

for &child in &node.children {
let node=dom.get(child).unwrap();
let child_width = node.widget.intrinsic_width(&node,dom);
width += child_width;
}
width+total_item_spacing
} else {
self.default_intrinsic_width(node,dom);
}
}

// This approach to layout is based on Flutter's Flex layout algorithm.
//
// https://api.flutter.dev/flutter/widgets/Flex-class.html#layout-algorithm
Expand Down
4 changes: 4 additions & 0 deletions crates/yakui-widgets/src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ mod text;
mod textbox;
mod unconstrained_box;
mod window;
mod outline;
mod intrinsic_width;

pub use self::align::*;
pub use self::button::*;
Expand Down Expand Up @@ -67,3 +69,5 @@ pub use self::text::*;
pub use self::textbox::*;
pub use self::unconstrained_box::*;
pub use self::window::*;
pub use self::outline::*;
pub use self::intrinsic_width::*;
76 changes: 76 additions & 0 deletions crates/yakui-widgets/src/widgets/outline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::widgets::PadResponse;
use crate::{shapes, shorthand::pad, util::widget_children, widgets::pad::Pad};
use yakui_core::geometry::Color;
use yakui_core::{
widget::{PaintContext, Widget},
Response,
};

/**
Applies a colored outline around its children.
*/
#[derive(Debug)]
#[must_use = "yakui widgets do nothing if you don't `show` them"]
pub struct Outline {
color: Color,
width: f32,
side: OutlineSide,
}

#[derive(Copy, Clone, Debug)]
pub enum OutlineSide {
Inside,
Outside,
}
impl Outline {
pub fn new(color: Color, width: f32, side: OutlineSide) -> Self {
Self { color, width, side }
}

pub fn show(self, children: impl FnOnce()) -> Response<()> {
let width = self.width;
let side = self.side;
widget_children::<OutlineWidget, _>(
|| match side {
OutlineSide::Inside => {
children();
}
OutlineSide::Outside => {
pad(Pad::all(width), children);
}
},
self,
)
}
}

#[derive(Debug)]
pub struct OutlineWidget {
props: Option<Outline>,
}

impl Widget for OutlineWidget {
type Props<'a> = Outline;
type Response = ();

fn new() -> Self {
Self { props: None }
}

fn update(&mut self, props: Self::Props<'_>) -> Self::Response {
self.props = Some(props);
}

fn paint(&self, mut ctx: PaintContext<'_>) {
let props = self.props.as_ref().unwrap();
let Outline { color, width, .. } = *props;

let node = ctx.dom.get_current();
for &child in &node.children {
ctx.paint(child);
}

let rect = ctx.layout.get(ctx.dom.current()).unwrap().rect;
shapes::outline(ctx.paint, rect, width, color);
}
}
7 changes: 6 additions & 1 deletion crates/yakui-widgets/src/widgets/pad.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use yakui_core::dom::{Dom, DomNode};
use yakui_core::geometry::{Constraints, Vec2};
use yakui_core::widget::{LayoutContext, Widget};
use yakui_core::Response;
use yakui_core::{Direction, Response};

use crate::util::widget_children;

Expand Down Expand Up @@ -110,4 +111,8 @@ impl Widget for PadWidget {
self_size = self_size.max(total_padding);
input.constrain_min(self_size)
}

fn intrinsic_width(&self, node: &DomNode, dom: &Dom) -> f32 {
self.default_intrinsic_width(node, dom) + self.props.left + self.props.right
}
}
Loading
Loading