685 lines
17 KiB
Rust
685 lines
17 KiB
Rust
use std::ops::Range;
|
|
|
|
use crate::{
|
|
color::Color,
|
|
geometry::{
|
|
deserialize_vec2f,
|
|
rect::RectF,
|
|
vector::{vec2f, Vector2F},
|
|
},
|
|
json::ToJson,
|
|
platform::CursorStyle,
|
|
scene::{self, CornerRadii, CursorRegion, Quad},
|
|
AnyElement, Element, SizeConstraint, ViewContext,
|
|
};
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use serde_json::json;
|
|
|
|
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
|
|
pub struct ContainerStyle {
|
|
#[serde(default)]
|
|
pub margin: Margin,
|
|
#[serde(default)]
|
|
pub padding: Padding,
|
|
#[serde(rename = "background")]
|
|
pub background_color: Option<Color>,
|
|
#[serde(rename = "overlay")]
|
|
pub overlay_color: Option<Color>,
|
|
#[serde(default)]
|
|
pub border: Border,
|
|
#[serde(default)]
|
|
#[serde(alias = "corner_radius")]
|
|
pub corner_radii: CornerRadii,
|
|
#[serde(default)]
|
|
pub shadow: Option<Shadow>,
|
|
#[serde(default)]
|
|
pub cursor: Option<CursorStyle>,
|
|
}
|
|
|
|
impl ContainerStyle {
|
|
pub fn fill(color: Color) -> Self {
|
|
Self {
|
|
background_color: Some(color),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
pub fn additional_length(&self) -> f32 {
|
|
self.padding.left
|
|
+ self.padding.right
|
|
+ self.border.width * 2.
|
|
+ self.margin.left
|
|
+ self.margin.right
|
|
}
|
|
}
|
|
|
|
pub struct Container<V> {
|
|
child: AnyElement<V>,
|
|
style: ContainerStyle,
|
|
}
|
|
|
|
impl<V> Container<V> {
|
|
pub fn new(child: AnyElement<V>) -> Self {
|
|
Self {
|
|
child,
|
|
style: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn with_style(mut self, style: ContainerStyle) -> Self {
|
|
self.style = style;
|
|
self
|
|
}
|
|
|
|
pub fn with_margin_top(mut self, margin: f32) -> Self {
|
|
self.style.margin.top = margin;
|
|
self
|
|
}
|
|
|
|
pub fn with_margin_bottom(mut self, margin: f32) -> Self {
|
|
self.style.margin.bottom = margin;
|
|
self
|
|
}
|
|
|
|
pub fn with_margin_left(mut self, margin: f32) -> Self {
|
|
self.style.margin.left = margin;
|
|
self
|
|
}
|
|
|
|
pub fn with_margin_right(mut self, margin: f32) -> Self {
|
|
self.style.margin.right = margin;
|
|
self
|
|
}
|
|
|
|
pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
|
|
self.style.padding.left = padding;
|
|
self.style.padding.right = padding;
|
|
self
|
|
}
|
|
|
|
pub fn with_vertical_padding(mut self, padding: f32) -> Self {
|
|
self.style.padding.top = padding;
|
|
self.style.padding.bottom = padding;
|
|
self
|
|
}
|
|
|
|
pub fn with_uniform_padding(mut self, padding: f32) -> Self {
|
|
self.style.padding = Padding {
|
|
top: padding,
|
|
left: padding,
|
|
bottom: padding,
|
|
right: padding,
|
|
};
|
|
self
|
|
}
|
|
|
|
pub fn with_padding_left(mut self, padding: f32) -> Self {
|
|
self.style.padding.left = padding;
|
|
self
|
|
}
|
|
|
|
pub fn with_padding_right(mut self, padding: f32) -> Self {
|
|
self.style.padding.right = padding;
|
|
self
|
|
}
|
|
|
|
pub fn with_padding_top(mut self, padding: f32) -> Self {
|
|
self.style.padding.top = padding;
|
|
self
|
|
}
|
|
|
|
pub fn with_padding_bottom(mut self, padding: f32) -> Self {
|
|
self.style.padding.bottom = padding;
|
|
self
|
|
}
|
|
|
|
pub fn with_background_color(mut self, color: Color) -> Self {
|
|
self.style.background_color = Some(color);
|
|
self
|
|
}
|
|
|
|
pub fn with_overlay_color(mut self, color: Color) -> Self {
|
|
self.style.overlay_color = Some(color);
|
|
self
|
|
}
|
|
|
|
pub fn with_border(mut self, border: Border) -> Self {
|
|
self.style.border = border;
|
|
self
|
|
}
|
|
|
|
pub fn with_corner_radius(mut self, radius: f32) -> Self {
|
|
self.style.corner_radii.top_left = radius;
|
|
self.style.corner_radii.top_right = radius;
|
|
self.style.corner_radii.bottom_right = radius;
|
|
self.style.corner_radii.bottom_left = radius;
|
|
self
|
|
}
|
|
|
|
pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: Color) -> Self {
|
|
self.style.shadow = Some(Shadow {
|
|
offset,
|
|
blur,
|
|
color,
|
|
});
|
|
self
|
|
}
|
|
|
|
pub fn with_cursor(mut self, style: CursorStyle) -> Self {
|
|
self.style.cursor = Some(style);
|
|
self
|
|
}
|
|
|
|
fn margin_size(&self) -> Vector2F {
|
|
vec2f(
|
|
self.style.margin.left + self.style.margin.right,
|
|
self.style.margin.top + self.style.margin.bottom,
|
|
)
|
|
}
|
|
|
|
fn padding_size(&self) -> Vector2F {
|
|
vec2f(
|
|
self.style.padding.left + self.style.padding.right,
|
|
self.style.padding.top + self.style.padding.bottom,
|
|
)
|
|
}
|
|
|
|
fn border_size(&self) -> Vector2F {
|
|
let mut x = 0.0;
|
|
if self.style.border.left {
|
|
x += self.style.border.width;
|
|
}
|
|
if self.style.border.right {
|
|
x += self.style.border.width;
|
|
}
|
|
|
|
let mut y = 0.0;
|
|
if self.style.border.top {
|
|
y += self.style.border.width;
|
|
}
|
|
if self.style.border.bottom {
|
|
y += self.style.border.width;
|
|
}
|
|
|
|
vec2f(x, y)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, JsonSchema)]
|
|
pub struct Border {
|
|
pub color: Color,
|
|
pub width: f32,
|
|
pub overlay: bool,
|
|
pub top: bool,
|
|
pub bottom: bool,
|
|
pub left: bool,
|
|
pub right: bool,
|
|
}
|
|
|
|
impl Into<scene::Border> for Border {
|
|
fn into(self) -> scene::Border {
|
|
scene::Border {
|
|
color: self.color,
|
|
left: if self.left { self.width } else { 0.0 },
|
|
right: if self.right { self.width } else { 0.0 },
|
|
top: if self.top { self.width } else { 0.0 },
|
|
bottom: if self.bottom { self.width } else { 0.0 },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Border {
|
|
pub fn new(width: f32, color: Color) -> Self {
|
|
Self {
|
|
width,
|
|
color,
|
|
overlay: false,
|
|
top: false,
|
|
left: false,
|
|
bottom: false,
|
|
right: false,
|
|
}
|
|
}
|
|
|
|
pub fn all(width: f32, color: Color) -> Self {
|
|
Self {
|
|
width,
|
|
color,
|
|
overlay: false,
|
|
top: true,
|
|
left: true,
|
|
bottom: true,
|
|
right: true,
|
|
}
|
|
}
|
|
|
|
pub fn top(width: f32, color: Color) -> Self {
|
|
let mut border = Self::new(width, color);
|
|
border.top = true;
|
|
border
|
|
}
|
|
|
|
pub fn left(width: f32, color: Color) -> Self {
|
|
let mut border = Self::new(width, color);
|
|
border.left = true;
|
|
border
|
|
}
|
|
|
|
pub fn bottom(width: f32, color: Color) -> Self {
|
|
let mut border = Self::new(width, color);
|
|
border.bottom = true;
|
|
border
|
|
}
|
|
|
|
pub fn right(width: f32, color: Color) -> Self {
|
|
let mut border = Self::new(width, color);
|
|
border.right = true;
|
|
border
|
|
}
|
|
|
|
pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
|
|
self.top = top;
|
|
self.left = left;
|
|
self.bottom = bottom;
|
|
self.right = right;
|
|
self
|
|
}
|
|
|
|
pub fn top_width(&self) -> f32 {
|
|
if self.top {
|
|
self.width
|
|
} else {
|
|
0.0
|
|
}
|
|
}
|
|
|
|
pub fn left_width(&self) -> f32 {
|
|
if self.left {
|
|
self.width
|
|
} else {
|
|
0.0
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Border {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
#[derive(Deserialize)]
|
|
struct BorderData {
|
|
pub width: f32,
|
|
pub color: Color,
|
|
#[serde(default)]
|
|
pub overlay: bool,
|
|
#[serde(default)]
|
|
pub top: bool,
|
|
#[serde(default)]
|
|
pub right: bool,
|
|
#[serde(default)]
|
|
pub bottom: bool,
|
|
#[serde(default)]
|
|
pub left: bool,
|
|
}
|
|
|
|
let data = BorderData::deserialize(deserializer)?;
|
|
let mut border = Border {
|
|
width: data.width,
|
|
color: data.color,
|
|
overlay: data.overlay,
|
|
top: data.top,
|
|
bottom: data.bottom,
|
|
left: data.left,
|
|
right: data.right,
|
|
};
|
|
if !border.top && !border.bottom && !border.left && !border.right {
|
|
border.top = true;
|
|
border.bottom = true;
|
|
border.left = true;
|
|
border.right = true;
|
|
}
|
|
Ok(border)
|
|
}
|
|
}
|
|
|
|
impl ToJson for Border {
|
|
fn to_json(&self) -> serde_json::Value {
|
|
let mut value = json!({});
|
|
if self.top {
|
|
value["top"] = json!(self.width);
|
|
}
|
|
if self.right {
|
|
value["right"] = json!(self.width);
|
|
}
|
|
if self.bottom {
|
|
value["bottom"] = json!(self.width);
|
|
}
|
|
if self.left {
|
|
value["left"] = json!(self.width);
|
|
}
|
|
value
|
|
}
|
|
}
|
|
|
|
impl<V: 'static> Element<V> for Container<V> {
|
|
type LayoutState = ();
|
|
type PaintState = ();
|
|
|
|
fn layout(
|
|
&mut self,
|
|
constraint: SizeConstraint,
|
|
view: &mut V,
|
|
cx: &mut ViewContext<V>,
|
|
) -> (Vector2F, Self::LayoutState) {
|
|
let mut size_buffer = self.margin_size() + self.padding_size();
|
|
if !self.style.border.overlay {
|
|
size_buffer += self.border_size();
|
|
}
|
|
let child_constraint = SizeConstraint {
|
|
min: (constraint.min - size_buffer).max(Vector2F::zero()),
|
|
max: (constraint.max - size_buffer).max(Vector2F::zero()),
|
|
};
|
|
let child_size = self.child.layout(child_constraint, view, cx);
|
|
(child_size + size_buffer, ())
|
|
}
|
|
|
|
fn paint(
|
|
&mut self,
|
|
bounds: RectF,
|
|
visible_bounds: RectF,
|
|
_: &mut Self::LayoutState,
|
|
view: &mut V,
|
|
cx: &mut ViewContext<V>,
|
|
) -> Self::PaintState {
|
|
let quad_bounds = RectF::from_points(
|
|
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
|
|
bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
|
|
);
|
|
|
|
if let Some(shadow) = self.style.shadow.as_ref() {
|
|
cx.scene().push_shadow(scene::Shadow {
|
|
bounds: quad_bounds + shadow.offset,
|
|
corner_radii: self.style.corner_radii,
|
|
sigma: shadow.blur,
|
|
color: shadow.color,
|
|
});
|
|
}
|
|
|
|
if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
|
|
if let Some(style) = self.style.cursor {
|
|
cx.scene().push_cursor_region(CursorRegion {
|
|
bounds: hit_bounds,
|
|
style,
|
|
});
|
|
}
|
|
}
|
|
|
|
let child_origin =
|
|
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
|
|
|
|
if self.style.border.overlay {
|
|
cx.scene().push_quad(Quad {
|
|
bounds: quad_bounds,
|
|
background: self.style.background_color,
|
|
border: Default::default(),
|
|
corner_radii: self.style.corner_radii.into(),
|
|
});
|
|
|
|
self.child.paint(child_origin, visible_bounds, view, cx);
|
|
|
|
cx.scene().push_layer(None);
|
|
cx.scene().push_quad(Quad {
|
|
bounds: quad_bounds,
|
|
background: self.style.overlay_color,
|
|
border: self.style.border.into(),
|
|
corner_radii: self.style.corner_radii.into(),
|
|
});
|
|
cx.scene().pop_layer();
|
|
} else {
|
|
cx.scene().push_quad(Quad {
|
|
bounds: quad_bounds,
|
|
background: self.style.background_color,
|
|
border: self.style.border.into(),
|
|
corner_radii: self.style.corner_radii.into(),
|
|
});
|
|
|
|
let child_origin = child_origin
|
|
+ vec2f(
|
|
self.style.border.left_width(),
|
|
self.style.border.top_width(),
|
|
);
|
|
self.child.paint(child_origin, visible_bounds, view, cx);
|
|
|
|
if self.style.overlay_color.is_some() {
|
|
cx.scene().push_layer(None);
|
|
cx.scene().push_quad(Quad {
|
|
bounds: quad_bounds,
|
|
background: self.style.overlay_color,
|
|
border: Default::default(),
|
|
corner_radii: self.style.corner_radii.into(),
|
|
});
|
|
cx.scene().pop_layer();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn rect_for_text_range(
|
|
&self,
|
|
range_utf16: Range<usize>,
|
|
_: RectF,
|
|
_: RectF,
|
|
_: &Self::LayoutState,
|
|
_: &Self::PaintState,
|
|
view: &V,
|
|
cx: &ViewContext<V>,
|
|
) -> Option<RectF> {
|
|
self.child.rect_for_text_range(range_utf16, view, cx)
|
|
}
|
|
|
|
fn debug(
|
|
&self,
|
|
bounds: RectF,
|
|
_: &Self::LayoutState,
|
|
_: &Self::PaintState,
|
|
view: &V,
|
|
cx: &ViewContext<V>,
|
|
) -> serde_json::Value {
|
|
json!({
|
|
"type": "Container",
|
|
"bounds": bounds.to_json(),
|
|
"details": self.style.to_json(),
|
|
"child": self.child.debug(view, cx),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToJson for ContainerStyle {
|
|
fn to_json(&self) -> serde_json::Value {
|
|
json!({
|
|
"margin": self.margin.to_json(),
|
|
"padding": self.padding.to_json(),
|
|
"background_color": self.background_color.to_json(),
|
|
"border": self.border.to_json(),
|
|
"corner_radius": self.corner_radii,
|
|
"shadow": self.shadow.to_json(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, JsonSchema)]
|
|
pub struct Margin {
|
|
pub top: f32,
|
|
pub bottom: f32,
|
|
pub left: f32,
|
|
pub right: f32,
|
|
}
|
|
|
|
impl ToJson for Margin {
|
|
fn to_json(&self) -> serde_json::Value {
|
|
let mut value = json!({});
|
|
if self.top > 0. {
|
|
value["top"] = json!(self.top);
|
|
}
|
|
if self.right > 0. {
|
|
value["right"] = json!(self.right);
|
|
}
|
|
if self.bottom > 0. {
|
|
value["bottom"] = json!(self.bottom);
|
|
}
|
|
if self.left > 0. {
|
|
value["left"] = json!(self.left);
|
|
}
|
|
value
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, JsonSchema)]
|
|
pub struct Padding {
|
|
pub top: f32,
|
|
pub left: f32,
|
|
pub bottom: f32,
|
|
pub right: f32,
|
|
}
|
|
|
|
impl Padding {
|
|
pub fn horizontal(padding: f32) -> Self {
|
|
Self {
|
|
left: padding,
|
|
right: padding,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
pub fn vertical(padding: f32) -> Self {
|
|
Self {
|
|
top: padding,
|
|
bottom: padding,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Padding {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let spacing = Spacing::deserialize(deserializer)?;
|
|
Ok(match spacing {
|
|
Spacing::Uniform(size) => Padding {
|
|
top: size,
|
|
left: size,
|
|
bottom: size,
|
|
right: size,
|
|
},
|
|
Spacing::Specific {
|
|
top,
|
|
left,
|
|
bottom,
|
|
right,
|
|
} => Padding {
|
|
top,
|
|
left,
|
|
bottom,
|
|
right,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Margin {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let spacing = Spacing::deserialize(deserializer)?;
|
|
Ok(match spacing {
|
|
Spacing::Uniform(size) => Margin {
|
|
top: size,
|
|
left: size,
|
|
bottom: size,
|
|
right: size,
|
|
},
|
|
Spacing::Specific {
|
|
top,
|
|
left,
|
|
bottom,
|
|
right,
|
|
} => Margin {
|
|
top,
|
|
left,
|
|
bottom,
|
|
right,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
#[derive(Deserialize)]
|
|
#[serde(untagged)]
|
|
enum Spacing {
|
|
Uniform(f32),
|
|
Specific {
|
|
#[serde(default)]
|
|
top: f32,
|
|
#[serde(default)]
|
|
left: f32,
|
|
#[serde(default)]
|
|
bottom: f32,
|
|
#[serde(default)]
|
|
right: f32,
|
|
},
|
|
}
|
|
|
|
impl Padding {
|
|
pub fn uniform(padding: f32) -> Self {
|
|
Self {
|
|
top: padding,
|
|
left: padding,
|
|
bottom: padding,
|
|
right: padding,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToJson for Padding {
|
|
fn to_json(&self) -> serde_json::Value {
|
|
let mut value = json!({});
|
|
if self.top > 0. {
|
|
value["top"] = json!(self.top);
|
|
}
|
|
if self.right > 0. {
|
|
value["right"] = json!(self.right);
|
|
}
|
|
if self.bottom > 0. {
|
|
value["bottom"] = json!(self.bottom);
|
|
}
|
|
if self.left > 0. {
|
|
value["left"] = json!(self.left);
|
|
}
|
|
value
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
|
|
pub struct Shadow {
|
|
#[serde(default, deserialize_with = "deserialize_vec2f")]
|
|
#[schemars(with = "Vec::<f32>")]
|
|
offset: Vector2F,
|
|
#[serde(default)]
|
|
blur: f32,
|
|
#[serde(default)]
|
|
color: Color,
|
|
}
|
|
|
|
impl ToJson for Shadow {
|
|
fn to_json(&self) -> serde_json::Value {
|
|
json!({
|
|
"offset": self.offset.to_json(),
|
|
"blur": self.blur,
|
|
"color": self.color.to_json()
|
|
})
|
|
}
|
|
}
|