use super::layout_highlighted_chunks; use crate::{ color::Color, fonts::HighlightStyle, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, json::{json, ToJson}, scene, serde_json::Value, text_layout::{Line, ShapedBoundary}, AnyElement, AppContext, Element, LayoutContext, PaintContext, Quad, SceneBuilder, SizeConstraint, Vector2FExt, View, ViewContext, }; use derive_more::Add; use length::{Length, Rems}; use log::warn; use optional_struct::*; use std::{ any::Any, borrow::Cow, f32, ops::{Add, Range}, sync::Arc, }; pub struct Node { style: NodeStyle, children: Vec>, } pub fn node(child: impl Element) -> Node { Node::default().child(child) } pub fn column() -> Node { Node::default() } pub fn row() -> Node { Node { style: NodeStyle { axis: Axis3d::X, ..Default::default() }, children: Default::default(), } } pub fn stack() -> Node { Node { style: NodeStyle { axis: Axis3d::Z, ..Default::default() }, children: Default::default(), } } impl Default for Node { fn default() -> Self { Self { style: Default::default(), children: Default::default(), } } } impl Node { pub fn child(mut self, child: impl Element) -> Self { self.children.push(child.into_any()); self } pub fn children(mut self, children: I) -> Self where I: IntoIterator, E: Element, { self.children .extend(children.into_iter().map(|child| child.into_any())); self } pub fn width(mut self, width: impl Into) -> Self { self.style.size.width = width.into(); self } pub fn height(mut self, height: impl Into) -> Self { self.style.size.height = height.into(); self } pub fn fill(mut self, fill: impl Into) -> Self { self.style.fill = fill.into(); self } pub fn text_size(mut self, text_size: Rems) -> Self { self.style.text.size = Some(text_size); self } pub fn margins( mut self, top_bottom: impl Into, left_right: impl Into, ) -> Self { let top_bottom = top_bottom.into(); let left_right = left_right.into(); self.style.margins = Edges { top: top_bottom.top, bottom: top_bottom.bottom, left: left_right.left, right: left_right.right, }; self } pub fn margin_top(mut self, top: Length) -> Self { self.style.margins.top = top; self } pub fn margin_bottom(mut self, bottom: Length) -> Self { self.style.margins.bottom = bottom; self } pub fn margin_left(mut self, left: impl Into) -> Self { self.style.margins.left = left.into(); self } pub fn margin_right(mut self, right: impl Into) -> Self { self.style.margins.right = right.into(); self } fn layout_xy( &mut self, axis: Axis2d, max_size: Vector2F, rem_pixels: f32, layout: &mut NodeLayout, view: &mut V, cx: &mut LayoutContext, ) -> Vector2F { layout.margins = self.style.margins.fixed_pixels(rem_pixels); layout.padding = self.style.padding.fixed_pixels(rem_pixels); let padded_max = max_size - layout.margins.size() - self.style.borders.width - layout.padding.size(); let mut remaining_length = padded_max.get(axis); // Pass 1: Total up flex units and layout inflexible children. // // Consume the remaining length as we layout inflexible children, so that any // remaining length can be distributed among flexible children in the next pass. let mut remaining_flex: f32 = 0.; let mut cross_axis_max: f32 = 0.; let cross_axis = axis.rotate(); // Fixed children are unconstrained along the primary axis, and constrained to // the padded max size along the cross axis. let mut child_constraint = SizeConstraint::loose(Vector2F::infinity().set(cross_axis, padded_max.get(cross_axis))); for child in &mut self.children { if let Some(child_flex) = child .metadata::() .and_then(|style| style.flex(axis)) { remaining_flex += child_flex; } else { let child_size = child.layout(child_constraint, view, cx); cross_axis_max = cross_axis_max.max(child_size.get(cross_axis)); remaining_length -= child_size.get(axis); } } // Pass 2: Allocate the remaining space among flexible lengths along the primary axis. if remaining_flex > 0. { // Add flex pixels from margin and padding. *layout.margins.start_mut(axis) += self.style.margins.start(axis).flex_pixels( rem_pixels, &mut remaining_flex, &mut remaining_length, ); *layout.padding.start_mut(axis) += self.style.padding.start(axis).flex_pixels( rem_pixels, &mut remaining_flex, &mut remaining_length, ); // Lay out the flexible children let mut child_max = padded_max; for child in &mut self.children { if let Some(child_flex) = child .metadata::() .and_then(|style| style.flex(axis)) { child_max.set(axis, child_flex / remaining_flex * remaining_length); let child_size = child.layout(SizeConstraint::loose(child_max), view, cx); remaining_flex -= child_flex; remaining_length -= child_size.get(axis); cross_axis_max = child_size.get(cross_axis).max(cross_axis_max); } } // Add flex pixels from margin and padding. *layout.margins.end_mut(axis) += self.style.margins.end(axis).flex_pixels( rem_pixels, &mut remaining_flex, &mut remaining_length, ); *layout.padding.end_mut(axis) += self.style.padding.end(axis).flex_pixels( rem_pixels, &mut remaining_flex, &mut remaining_length, ); } let mut size = max_size; match self.style.size.get(axis) { Length::Hug => { size.set(axis, max_size.get(axis) - remaining_length); } Length::Fixed(_) => {} Length::Auto { flex, min, max } => todo!(), }; let width = match self.style.size.width { Length::Hug => match axis { Axis2d::X => max_size.get(axis) - remaining_length, Axis2d::Y => { cross_axis_max + layout.padding.size().get(cross_axis) + self.style.borders.size().get(cross_axis) + layout.margins.size().get(cross_axis) } }, Length::Fixed(width) => width.to_pixels(rem_pixels), Length::Auto { flex, min, max } => max_size .x() .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)), }; let height = match self.style.size.height { Length::Hug => match axis { Axis2d::Y => max_size.get(axis) - remaining_length, Axis2d::X => { cross_axis_max + layout.padding.size().get(cross_axis) + self.style.borders.size().get(cross_axis) + layout.margins.size().get(cross_axis) } }, Length::Fixed(height) => height.to_pixels(rem_pixels), Length::Auto { flex, min, max } => max_size .y() .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)), }; let length = max_size.get(axis) - remaining_length; match axis { Axis2d::X => vec2f(length, cross_axis_max), Axis2d::Y => vec2f(cross_axis_max, length), } } fn paint_children_xy( &mut self, scene: &mut SceneBuilder, axis: Axis2d, bounds: RectF, visible_bounds: RectF, layout: &mut NodeLayout, view: &mut V, cx: &mut ViewContext, ) { let parent_size = bounds.size(); let mut child_origin = bounds.origin(); // Align all children together along the primary axis let mut align_horizontally = false; let mut align_vertically = false; match axis { Axis2d::X => align_horizontally = true, Axis2d::Y => align_vertically = true, } align_child( &mut child_origin, parent_size, layout.content_size, self.style.align.0, align_horizontally, align_vertically, ); for child in &mut self.children { // Align each child along the cross axis align_horizontally = !align_horizontally; align_vertically = !align_vertically; align_child( &mut child_origin, parent_size, child.size(), self.style.align.0, align_horizontally, align_vertically, ); child.paint(scene, child_origin, visible_bounds, view, cx); // Advance along the primary axis by the size of this child match axis { Axis2d::X => child_origin.set_x(child_origin.x() + child.size().x()), Axis2d::Y => child_origin.set_y(child_origin.y() + child.size().y()), } } } // fn layout_stacked_children( // &mut self, // constraint: SizeConstraint, // view: &mut V, // cx: &mut LayoutContext, // ) -> Vector2F { // let mut size = Vector2F::zero(); // for child in &mut self.children { // let child_size = child.layout(constraint, view, cx); // size.set_x(size.x().max(child_size.x())); // size.set_y(size.y().max(child_size.y())); // } // size // } // fn inset_size(&self, rem_size: f32) -> Vector2F { // todo!() // // self.padding_size(rem_size) + self.border_size() + self.margin_size(rem_size) // } // // fn margin_fixed_size(&self, rem_size: f32) -> Vector2F { // self.style.margins.fixed().to_pixels(rem_size) // } // fn padding_size(&self, rem_size: f32) -> Vector2F { // // We need to account for auto padding // todo!() // // vec2f( // // (self.style.padding.left + self.style.padding.right).to_pixels(rem_size), // // (self.style.padding.top + self.style.padding.bottom).to_pixels(rem_size), // // ) // } // fn border_size(&self) -> Vector2F { // let mut x = 0.0; // if self.style.borders.left { // x += self.style.borders.width; // } // if self.style.borders.right { // x += self.style.borders.width; // } // let mut y = 0.0; // if self.style.borders.top { // y += self.style.borders.width; // } // if self.style.borders.bottom { // y += self.style.borders.width; // } // vec2f(x, y) // } } impl Element for Node { type LayoutState = NodeLayout; type PaintState = (); fn layout( &mut self, constraint: SizeConstraint, view: &mut V, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let mut layout = NodeLayout::default(); let size = if let Some(axis) = self.style.axis.to_2d() { self.layout_xy(axis, constraint.max, cx.rem_pixels(), &mut layout, view, cx) } else { todo!() }; (size, layout) } fn paint( &mut self, scene: &mut SceneBuilder, bounds: RectF, visible_bounds: RectF, layout: &mut NodeLayout, view: &mut V, cx: &mut PaintContext, ) -> Self::PaintState { let rem_pixels = cx.rem_pixels(); let margined_bounds = RectF::from_points( bounds.origin() + vec2f(layout.margins.left, layout.margins.top), bounds.lower_right() - vec2f(layout.margins.right, layout.margins.bottom), ); // Paint drop shadow for shadow in &self.style.shadows { scene.push_shadow(scene::Shadow { bounds: margined_bounds + shadow.offset, corner_radius: self.style.corner_radius, sigma: shadow.blur, color: shadow.color, }); } // // Paint cursor style // if let Some(hit_bounds) = content_bounds.intersection(visible_bounds) { // if let Some(style) = self.style.cursor { // scene.push_cursor_region(CursorRegion { // bounds: hit_bounds, // style, // }); // } // } // Render the background and/or the border. let Fill::Color(fill_color) = self.style.fill; let is_fill_visible = !fill_color.is_fully_transparent(); if is_fill_visible || self.style.borders.is_visible() { scene.push_quad(Quad { bounds: margined_bounds, background: is_fill_visible.then_some(fill_color), border: scene::Border { width: self.style.borders.width, color: self.style.borders.color, overlay: false, top: self.style.borders.top, right: self.style.borders.right, bottom: self.style.borders.bottom, left: self.style.borders.left, }, corner_radius: self.style.corner_radius, }); } if !self.children.is_empty() { // Account for padding first. let borders = &self.style.borders; let padded_bounds = RectF::from_points( margined_bounds.origin() + vec2f( borders.left_width() + layout.padding.left, borders.top_width() + layout.padding.top, ), margined_bounds.lower_right() - vec2f( layout.padding.right + borders.right_width(), layout.padding.bottom + borders.bottom_width(), ), ); if let Some(axis) = self.style.axis.to_2d() { self.paint_children_xy(scene, axis, padded_bounds, visible_bounds, layout, view, cx) } else { todo!(); } } } fn rect_for_text_range( &self, range_utf16: Range, _: RectF, _: RectF, _: &Self::LayoutState, _: &Self::PaintState, view: &V, cx: &ViewContext, ) -> Option { self.children .iter() .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) } fn debug( &self, bounds: RectF, _: &Self::LayoutState, _: &Self::PaintState, view: &V, cx: &ViewContext, ) -> Value { json!({ "type": "Node", "bounds": bounds.to_json(), // TODO! // "children": self.content.iter().map(|child| child.debug(view, cx)).collect::>() }) } fn metadata(&self) -> Option<&dyn Any> { Some(&self.style) } } pub struct TopBottom { top: Length, bottom: Length, } impl> From<(T, T)> for TopBottom { fn from((top, bottom): (T, T)) -> Self { Self { top: top.into(), bottom: bottom.into(), } } } impl> From for TopBottom { fn from(both: T) -> Self { Self { top: both.into(), bottom: both.into(), } } } pub struct LeftRight { left: Length, right: Length, } impl From<(Length, Length)> for LeftRight { fn from((left, right): (Length, Length)) -> Self { Self { left, right } } } impl From for LeftRight { fn from(both: Length) -> Self { Self { left: both, right: both, } } } fn align_child( child_origin: &mut Vector2F, parent_size: Vector2F, child_size: Vector2F, alignment: Vector2F, horizontal: bool, vertical: bool, ) { let parent_center = parent_size / 2.; let parent_target = parent_center + parent_center * alignment; let child_center = child_size / 2.; let child_target = child_center + child_center * alignment; if horizontal { child_origin.set_x(child_origin.x() + parent_target.x() - child_target.x()) } if vertical { child_origin.set_y(child_origin.y() + parent_target.y() - child_target.y()); } } struct Interactive