use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use bevy_ecs::prelude::*;
use taffy::Display;
use taffy::GridTemplateComponent;
use taffy::style_helpers::fr;
use taffy::style_helpers::length;
use taffy::style_helpers::zero;
use crate::keys::Key;
use crate::layout::ContainerNode;
use crate::layout::DisplayNode;
use crate::layout::FlexDirection;
use crate::layout::Gap;
use crate::layout::GridTemplateColumns;
use crate::layout::GridTemplateRows;
use crate::layout::RootNode;
use crate::message::Click;
use crate::message::DoubleClick;
use crate::message::KeyPress;
use crate::message::Message;
use crate::message::Scroll;
use crate::point::Point;
use crate::renderer::Command;
use crate::renderer::DrawCommand;
use crate::renderer::RenderTarget;
use crate::renderer::Renderable;
use crate::renderer::Renderer;
use crate::style::BackgroundColor;
use crate::style::ContainerStyle;
use crate::style::ContainerStyleExt;
use crate::style::Rectangle;
use crate::widget::Widget;
use crate::widget::WidgetNode;
pub struct FlexContainer;
pub struct GridContainer;
pub struct Root;
impl Root {
pub fn flex<'widget, M>() -> Container<'widget, M, FlexContainer> {
Self::flex_row()
}
pub fn flex_row<'widget, M>() -> Container<'widget, M, FlexContainer> {
Container {
is_root: true,
direction: FlexDirection::Row,
..Default::default()
}
}
pub fn flex_column<'widget, M>() -> Container<'widget, M, FlexContainer> {
Container {
is_root: true,
direction: FlexDirection::Column,
..Default::default()
}
}
pub fn grid<'widget, M>() -> Container<'widget, M, GridContainer> {
Container {
is_root: true,
..Default::default()
}
}
}
pub struct TrackBuilder {
tracks: Vec<GridTemplateComponent<String>>,
}
impl TrackBuilder {
fn new() -> Self {
TrackBuilder { tracks: Vec::new() }
}
pub fn length(mut self, pixels: f32) -> Self {
self.tracks.push(length(pixels));
self
}
pub fn fr(mut self, fraction: f32) -> Self {
self.tracks.push(fr(fraction));
self
}
fn build(self) -> Vec<GridTemplateComponent<String>> {
self.tracks
}
}
pub struct Container<'widget, M, T = ()> {
is_root: bool,
pub direction: FlexDirection,
pub grid_template_rows: Option<Vec<GridTemplateComponent<String>>>,
pub grid_template_columns: Option<Vec<GridTemplateComponent<String>>>,
pub gap: Gap,
pub style: ContainerStyle,
pub inner_size: Rectangle,
pub items: Vec<Box<dyn Widget<'widget, M> + 'widget>>,
pub on_click: Option<M>,
pub on_double_click: Option<M>,
pub on_scroll: Option<Arc<dyn Fn(Point) -> M + Send + Sync>>,
pub on_key_press: HashMap<Key, M>,
_marker: std::marker::PhantomData<T>,
}
impl<'widget, M> Debug for Container<'widget, M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Container")
.field("is_root", &self.is_root)
.field("direction", &self.direction)
.field("grid_template_rows", &self.grid_template_rows)
.field("grid_template_columns", &self.grid_template_columns)
.field("gap", &self.gap)
.field("style", &self.style)
.field("inner_size", &self.inner_size)
.finish()
}
}
impl<'widget, M> Debug for Container<'widget, M, FlexContainer> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Container")
.field("is_root", &self.is_root)
.field("direction", &self.direction)
.field("grid_template_rows", &self.grid_template_rows)
.field("grid_template_columns", &self.grid_template_columns)
.field("gap", &self.gap)
.field("style", &self.style)
.field("inner_size", &self.inner_size)
.finish()
}
}
impl<'widget, M> Debug for Container<'widget, M, GridContainer> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Container")
.field("is_root", &self.is_root)
.field("direction", &self.direction)
.field("grid_template_rows", &self.grid_template_rows)
.field("grid_template_columns", &self.grid_template_columns)
.field("gap", &self.gap)
.field("style", &self.style)
.field("inner_size", &self.inner_size)
.finish()
}
}
impl<'widget, M> Default for Container<'widget, M, FlexContainer> {
fn default() -> Self {
Container {
is_root: false,
direction: FlexDirection::Row,
grid_template_rows: None,
grid_template_columns: None,
gap: Gap(zero()),
style: ContainerStyle::default(),
inner_size: Rectangle::zero(),
items: Vec::new(),
on_click: None,
on_double_click: None,
on_scroll: None,
on_key_press: HashMap::new(),
_marker: std::marker::PhantomData,
}
}
}
impl<'widget, M> Default for Container<'widget, M, GridContainer> {
fn default() -> Self {
Container {
is_root: false,
direction: FlexDirection::Row,
grid_template_rows: Some(vec![fr(1.0)]),
grid_template_columns: Some(vec![fr(1.0)]),
gap: Gap(zero()),
style: ContainerStyle::default(),
inner_size: Rectangle::zero(),
items: Vec::new(),
on_click: None,
on_double_click: None,
on_scroll: None,
on_key_press: HashMap::new(),
_marker: std::marker::PhantomData,
}
}
}
impl<'widget, M: Clone + Send + Sync + 'static> Container<'widget, M> {
pub fn flex() -> Container<'widget, M, FlexContainer> {
Self::flex_row()
}
pub fn flex_row() -> Container<'widget, M, FlexContainer> {
Container {
direction: FlexDirection::Row,
..Default::default()
}
}
pub fn flex_column() -> Container<'widget, M, FlexContainer> {
Container {
direction: FlexDirection::Column,
..Default::default()
}
}
pub fn grid() -> Container<'widget, M, GridContainer> {
Container {
is_root: false,
..Default::default()
}
}
}
impl<'widget, M: Clone + Send + Sync + 'static> Container<'widget, M, FlexContainer> {
pub fn gap(mut self, gap: f32) -> Self {
self.gap = Gap(length(gap));
self
}
pub fn child(mut self, item: impl Widget<'widget, M> + 'widget) -> Self {
self.items.push(Box::new(item));
self
}
pub fn children(mut self, items: Vec<impl Widget<'widget, M> + 'widget>) -> Self {
for item in items {
self.items.push(Box::new(item));
}
self
}
pub fn on_click(mut self, on_click: M) -> Self {
self.on_click = Some(on_click);
self
}
pub fn on_double_click(mut self, on_double_click: M) -> Self {
self.on_double_click = Some(on_double_click);
self
}
pub fn on_scroll(mut self, on_scroll: impl Fn(Point) -> M + Send + Sync + 'static) -> Self {
self.on_scroll = Some(Arc::new(on_scroll));
self
}
pub fn on_key_press(mut self, key: impl Into<Key>, message: M) -> Self {
self.on_key_press.insert(key.into(), message);
self
}
}
impl<'widget, M: Clone + Send + Sync + 'static> Container<'widget, M, GridContainer> {
pub fn rows(mut self, builder: impl FnOnce(TrackBuilder) -> TrackBuilder) -> Self {
let track_builder = builder(TrackBuilder::new());
let tracks = track_builder.build();
self.grid_template_rows = Some(tracks);
self
}
pub fn columns(mut self, builder: impl FnOnce(TrackBuilder) -> TrackBuilder) -> Self {
let track_builder = builder(TrackBuilder::new());
let tracks = track_builder.build();
self.grid_template_columns = Some(tracks);
self
}
pub fn gap(mut self, gap: f32) -> Self {
self.gap = Gap(length(gap));
self
}
pub fn child(mut self, item: impl Widget<'widget, M> + 'widget) -> Self {
self.items.push(Box::new(item));
self
}
pub fn children(mut self, items: Vec<impl Widget<'widget, M> + 'widget>) -> Self {
for item in items {
self.items.push(Box::new(item));
}
self
}
pub fn on_click(mut self, on_click: M) -> Self {
self.on_click = Some(on_click);
self
}
pub fn on_double_click(mut self, on_double_click: M) -> Self {
self.on_double_click = Some(on_double_click);
self
}
pub fn on_scroll(mut self, on_scroll: impl Fn(Point) -> M + Send + Sync + 'static) -> Self {
self.on_scroll = Some(Arc::new(on_scroll));
self
}
pub fn on_key_press(mut self, key: impl Into<Key>, message: M) -> Self {
self.on_key_press.insert(key.into(), message);
self
}
}
impl<'widget, M: Clone + Send + Sync + 'static> ContainerStyleExt<'widget, M>
for Container<'widget, M>
{
fn style(&self) -> ContainerStyle {
self.style
}
fn set_style(mut self, style: ContainerStyle) -> Self {
self.style = style;
self
}
}
impl<'widget, M: Clone + Send + Sync + 'static> ContainerStyleExt<'widget, M>
for Container<'widget, M, FlexContainer>
{
fn style(&self) -> ContainerStyle {
self.style
}
fn set_style(mut self, style: ContainerStyle) -> Self {
self.style = style;
self
}
}
impl<'widget, M: Clone + Send + Sync + 'static> ContainerStyleExt<'widget, M>
for Container<'widget, M, GridContainer>
{
fn style(&self) -> ContainerStyle {
self.style
}
fn set_style(mut self, style: ContainerStyle) -> Self {
self.style = style;
self
}
}
impl<'widget, M: Clone + Send + Sync + 'static> Widget<'widget, M> for Container<'widget, M> {
fn spawn<'a, 'b>(&self, _entity_commands: &'a mut EntityWorldMut<'b>)
where
'b: 'a,
{
unreachable!("Container widget should not be spawned")
}
}
fn spawn_container<M: Clone + Send + Sync + 'static, T>(
container: &Container<'_, M, T>,
entity_commands: &mut EntityWorldMut,
) {
entity_commands.insert((
WidgetNode,
container.direction.clone(),
container.gap.clone(),
));
if let Some(ref on_click) = container.on_click {
entity_commands.insert(Message::<Click, M>::new(on_click.clone()));
} else {
entity_commands.remove::<Message<Click, M>>();
}
if let Some(ref on_double_click) = container.on_double_click {
entity_commands.insert(Message::<DoubleClick, M>::new(on_double_click.clone()));
} else {
entity_commands.remove::<Message<DoubleClick, M>>();
}
if let Some(on_scroll) = container.on_scroll.clone() {
entity_commands.insert(Message::<Scroll, M>::from_event(move |scroll: &Scroll| {
Some(on_scroll(scroll.1))
}));
} else {
entity_commands.remove::<Message<Scroll, M>>();
}
let on_key_press = container.on_key_press.clone();
entity_commands.insert(Message::<KeyPress, M>::from_event(
move |event: &KeyPress| {
on_key_press.iter().find_map(|(key, message)| {
let key = *key;
let message = message.clone();
if event.0 == key {
Some(message.clone())
} else {
None
}
})
},
));
if container.is_root {
entity_commands.insert(RootNode);
} else {
entity_commands.insert(ContainerNode);
}
}
impl<'widget, M: Clone + Send + Sync + 'static> Widget<'widget, M>
for Container<'widget, M, FlexContainer>
{
fn get_children<'a>(&'a self, f: &mut dyn FnMut(&'a (dyn Widget<'widget, M> + 'widget))) {
for item in self.items.iter() {
f(item.as_ref());
}
}
fn spawn<'a, 'b>(&self, entity_commands: &'a mut EntityWorldMut<'b>)
where
'b: 'a,
{
let style = self.style();
entity_commands.insert((DisplayNode(Display::Flex), style.bundle()));
spawn_container(self, entity_commands);
}
}
impl<'widget, M: Clone + Send + Sync + 'static> Widget<'widget, M>
for Container<'widget, M, GridContainer>
{
fn get_children<'a>(&'a self, f: &mut dyn FnMut(&'a (dyn Widget<'widget, M> + 'widget))) {
for item in self.items.iter() {
f(item.as_ref());
}
}
fn spawn<'a, 'b>(&self, entity_commands: &'a mut EntityWorldMut<'b>)
where
'b: 'a,
{
let style = self.style();
entity_commands.insert((
DisplayNode(Display::Grid),
style.bundle(),
self.grid_template_rows
.clone()
.map(GridTemplateRows)
.unwrap(),
self.grid_template_columns
.clone()
.map(GridTemplateColumns)
.unwrap(),
));
spawn_container(self, entity_commands);
}
}
#[derive(Component, Clone)]
pub struct ContainerNodeRenderer {
background_color: BackgroundColor,
last_commands: Option<(RenderTarget, Vec<Command>)>,
}
impl ContainerNodeRenderer {
fn sync(&mut self, background_color: BackgroundColor) -> bool {
let did_change = self.background_color != background_color;
self.background_color = background_color;
if did_change {
self.last_commands = None;
}
did_change
}
}
impl Renderable for ContainerNodeRenderer {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn render(&mut self, render_target: RenderTarget) -> Vec<DrawCommand> {
if let Some((last_render_target, last_commands)) = &self.last_commands {
if *last_render_target == render_target {
return vec![DrawCommand {
render_target,
commands: last_commands.clone(),
}];
}
}
if let Some(color) = self.background_color.0 {
let commands = vec![Command::Rectangle {
rectangle: render_target.rectangle.normalize(),
color,
}];
self.last_commands = Some((render_target.clone(), commands.clone()));
vec![DrawCommand {
render_target,
commands,
}]
} else {
vec![]
}
}
}
fn on_insert_container<T: Component>(
insert: On<Insert, T>,
mut commands: Commands,
mut roots: Query<(&BackgroundColor, Option<&mut Renderer>), With<T>>,
) {
tracing::trace!("Container node inserted: {:?}", insert.entity);
let (background_color, renderer) = roots.get_mut(insert.entity).unwrap();
if let Some(mut renderer) = renderer {
if let Some(container_renderer) = renderer
.0
.as_any_mut()
.downcast_mut::<ContainerNodeRenderer>()
{
container_renderer.sync(*background_color);
return;
}
}
let container_renderer = ContainerNodeRenderer {
background_color: *background_color,
last_commands: None,
};
let renderer = Renderer(Box::new(container_renderer));
commands.entity(insert.entity).insert(renderer);
}
pub fn container_setup(world: &mut World) {
world.add_observer(on_insert_container::<RootNode>);
world.add_observer(on_insert_container::<ContainerNode>);
}