use bevy_app::prelude::*;
use bevy_ecs::{lifecycle::HookContext, prelude::*, world::DeferredWorld};
use bevy_ui::prelude::*;
use jonmo::{
SignalProcessing,
signal::{Signal, SignalExt},
};
use super::element::BuilderWrapper;
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash)]
pub enum AlignX {
#[default]
None,
Left,
Center,
Right,
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash)]
pub enum AlignY {
#[default]
None,
Top,
Center,
Bottom,
}
#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Debug)]
#[component(on_insert = on_alignment_insert, on_remove = on_alignment_remove)]
pub struct Alignment {
pub x: AlignX,
pub y: AlignY,
}
#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Debug)]
#[component(on_insert = on_content_alignment_insert, on_remove = on_content_alignment_remove)]
pub struct ContentAlignment {
pub x: AlignX,
pub y: AlignY,
}
pub type ApplyAlignmentFn = fn(&mut Node, &Alignment);
pub type ApplyContentAlignmentFn = fn(&mut Node, &ContentAlignment);
pub type ResetAlignmentFn = fn(&mut Node);
#[derive(Component, Clone, Copy)]
pub struct AlignmentHandler {
pub apply: ApplyAlignmentFn,
pub reset: ResetAlignmentFn,
}
#[derive(Component, Clone, Copy)]
pub struct ContentAlignmentHandler {
pub apply: ApplyContentAlignmentFn,
pub reset: ResetAlignmentFn,
}
#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Debug, Hash)]
#[component(on_insert = on_layout_direction_insert)]
pub enum LayoutDirection {
#[default]
Column,
Row,
Grid,
}
fn on_layout_direction_insert(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
if let Some(old_handler) = world.get::<AlignmentHandler>(entity).copied() {
let children: Vec<Entity> = world
.get::<Children>(entity)
.map(|c| c.iter().collect())
.unwrap_or_default();
for child in children {
if world.get::<Alignment>(child).is_some()
&& let Some(mut node) = world.get_mut::<Node>(child)
{
(old_handler.reset)(&mut node);
}
}
}
let direction = world.get::<LayoutDirection>(entity).copied().unwrap();
let (alignment_handler, content_alignment_handler) = match direction {
LayoutDirection::Column => (
AlignmentHandler {
apply: column::apply_alignment,
reset: column::reset_alignment,
},
ContentAlignmentHandler {
apply: column::apply_content_alignment,
reset: column::reset_content_alignment,
},
),
LayoutDirection::Row => (
AlignmentHandler {
apply: row::apply_alignment,
reset: row::reset_alignment,
},
ContentAlignmentHandler {
apply: row::apply_content_alignment,
reset: row::reset_content_alignment,
},
),
LayoutDirection::Grid => (
AlignmentHandler {
apply: grid::apply_alignment,
reset: grid::reset_alignment,
},
ContentAlignmentHandler {
apply: row::apply_content_alignment,
reset: row::reset_content_alignment,
},
),
};
world
.commands()
.entity(entity)
.insert((alignment_handler, content_alignment_handler));
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct Align {
x: AlignX,
y: AlignY,
}
impl Align {
pub fn new() -> Self {
Self::default()
}
pub fn center() -> Self {
Self {
x: AlignX::Center,
y: AlignY::Center,
}
}
pub fn center_x(mut self) -> Self {
self.x = AlignX::Center;
self
}
pub fn center_y(mut self) -> Self {
self.y = AlignY::Center;
self
}
pub fn top(mut self) -> Self {
self.y = AlignY::Top;
self
}
pub fn bottom(mut self) -> Self {
self.y = AlignY::Bottom;
self
}
pub fn left(mut self) -> Self {
self.x = AlignX::Left;
self
}
pub fn right(mut self) -> Self {
self.x = AlignX::Right;
self
}
fn to_alignment(self) -> Alignment {
Alignment { x: self.x, y: self.y }
}
fn to_content_alignment(self) -> ContentAlignment {
ContentAlignment { x: self.x, y: self.y }
}
}
pub trait Alignable: BuilderWrapper + Sized {
fn align(self, align_option: impl Into<Option<Align>>) -> Self {
if let Some(align) = align_option.into() {
let alignment = align.to_alignment();
self.with_builder(|builder| builder.insert(alignment))
} else {
self
}
}
fn align_signal<S>(self, align_option_signal_option: impl Into<Option<S>>) -> Self
where
S: Signal<Item = Option<Align>> + Send + Sync + 'static,
{
if let Some(align_option_signal) = align_option_signal_option.into() {
self.with_builder(|builder| {
builder.component_signal(
align_option_signal.map_in(|align_option| align_option.map(|align| align.to_alignment())),
)
})
} else {
self
}
}
fn align_content(self, align_option: impl Into<Option<Align>>) -> Self {
if let Some(align) = align_option.into() {
let content_alignment = align.to_content_alignment();
self.with_builder(|builder| builder.insert(content_alignment))
} else {
self
}
}
fn align_content_signal<S>(self, align_option_signal_option: impl Into<Option<S>>) -> Self
where
S: Signal<Item = Option<Align>> + Send + Sync + 'static,
{
if let Some(align_option_signal) = align_option_signal_option.into() {
self.with_builder(|builder| {
builder.component_signal(
align_option_signal.map_in(|align_option| align_option.map(|align| align.to_content_alignment())),
)
})
} else {
self
}
}
}
pub fn plugin(app: &mut App) {
app.add_systems(
PostUpdate,
(
apply_self_alignment,
apply_self_alignment_on_parent_change,
apply_content_alignment,
)
.after(SignalProcessing),
);
}
#[allow(clippy::type_complexity)]
fn apply_self_alignment(
mut data: Query<(&Alignment, &ChildOf, &mut Node), Or<(Changed<Alignment>, Added<Alignment>, Added<ChildOf>)>>,
handlers: Query<&AlignmentHandler>,
) {
for (alignment, child_of, mut node) in &mut data {
if let Ok(handler) = handlers.get(child_of.parent()) {
(handler.apply)(&mut node, alignment);
}
}
}
fn apply_self_alignment_on_parent_change(
mut children_query: Query<(&Alignment, &mut Node)>,
changed_parents: Query<(&AlignmentHandler, &Children), Changed<AlignmentHandler>>,
) {
for (handler, children) in &changed_parents {
for &child in children {
if let Ok((alignment, mut node)) = children_query.get_mut(child) {
(handler.apply)(&mut node, alignment);
}
}
}
}
fn on_alignment_insert(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let handler = world
.get::<ChildOf>(entity)
.and_then(|child_of| world.get::<AlignmentHandler>(child_of.parent()).copied());
if let Some(handler) = handler {
let alignment = world.get::<Alignment>(entity).copied().unwrap();
if let Some(mut node) = world.get_mut::<Node>(entity) {
(handler.apply)(&mut node, &alignment);
}
}
}
fn on_alignment_remove(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let handler = world
.get::<ChildOf>(entity)
.and_then(|child_of| world.get::<AlignmentHandler>(child_of.parent()).copied());
if let (Some(handler), Some(mut node)) = (handler, world.get_mut::<Node>(entity)) {
(handler.reset)(&mut node);
}
}
#[allow(clippy::type_complexity)]
fn apply_content_alignment(
mut data: Query<
(&ContentAlignment, &ContentAlignmentHandler, &mut Node),
Or<(
Changed<ContentAlignment>,
Added<ContentAlignment>,
Changed<ContentAlignmentHandler>,
)>,
>,
) {
for (content_alignment, handler, mut node) in &mut data {
(handler.apply)(&mut node, content_alignment);
}
}
fn on_content_alignment_insert(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let handler = world.get::<ContentAlignmentHandler>(entity).copied();
if let Some(handler) = handler {
let content_alignment = world.get::<ContentAlignment>(entity).copied().unwrap();
if let Some(mut node) = world.get_mut::<Node>(entity) {
(handler.apply)(&mut node, &content_alignment);
}
}
}
fn on_content_alignment_remove(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let handler = world.get::<ContentAlignmentHandler>(entity).copied();
if let (Some(handler), Some(mut node)) = (handler, world.get_mut::<Node>(entity)) {
(handler.reset)(&mut node);
}
}
pub mod column {
use super::*;
pub fn apply_alignment(node: &mut Node, alignment: &Alignment) {
node.align_self = match alignment.x {
AlignX::None => AlignSelf::DEFAULT,
AlignX::Left => AlignSelf::Start,
AlignX::Center => AlignSelf::Center,
AlignX::Right => AlignSelf::End,
};
(node.margin.top, node.margin.bottom) = match alignment.y {
AlignY::None => (Val::ZERO, Val::ZERO),
AlignY::Top => (Val::ZERO, Val::Auto),
AlignY::Center => (Val::Auto, Val::Auto),
AlignY::Bottom => (Val::Auto, Val::ZERO),
};
}
pub fn apply_content_alignment(node: &mut Node, content_alignment: &ContentAlignment) {
node.align_items = match content_alignment.x {
AlignX::None => AlignItems::DEFAULT,
AlignX::Left => AlignItems::Start,
AlignX::Center => AlignItems::Center,
AlignX::Right => AlignItems::End,
};
node.justify_content = match content_alignment.y {
AlignY::None => JustifyContent::DEFAULT,
AlignY::Top => JustifyContent::Start,
AlignY::Center => JustifyContent::Center,
AlignY::Bottom => JustifyContent::End,
};
}
pub fn reset_alignment(node: &mut Node) {
node.align_self = AlignSelf::DEFAULT;
node.margin.top = Val::ZERO;
node.margin.bottom = Val::ZERO;
}
pub fn reset_content_alignment(node: &mut Node) {
node.align_items = AlignItems::DEFAULT;
node.justify_content = JustifyContent::DEFAULT;
}
}
pub mod row {
use super::*;
pub fn apply_alignment(node: &mut Node, alignment: &Alignment) {
(node.margin.left, node.margin.right) = match alignment.x {
AlignX::None => (Val::ZERO, Val::ZERO),
AlignX::Left => (Val::ZERO, Val::Auto),
AlignX::Center => (Val::Auto, Val::Auto),
AlignX::Right => (Val::Auto, Val::ZERO),
};
node.align_self = match alignment.y {
AlignY::None => AlignSelf::DEFAULT,
AlignY::Top => AlignSelf::Start,
AlignY::Center => AlignSelf::Center,
AlignY::Bottom => AlignSelf::End,
};
}
pub fn apply_content_alignment(node: &mut Node, content_alignment: &ContentAlignment) {
node.justify_content = match content_alignment.x {
AlignX::None => JustifyContent::DEFAULT,
AlignX::Left => JustifyContent::Start,
AlignX::Center => JustifyContent::Center,
AlignX::Right => JustifyContent::End,
};
node.align_items = match content_alignment.y {
AlignY::None => AlignItems::DEFAULT,
AlignY::Top => AlignItems::Start,
AlignY::Center => AlignItems::Center,
AlignY::Bottom => AlignItems::End,
};
}
pub fn reset_alignment(node: &mut Node) {
node.margin.left = Val::ZERO;
node.margin.right = Val::ZERO;
node.align_self = AlignSelf::DEFAULT;
}
pub fn reset_content_alignment(node: &mut Node) {
node.justify_content = JustifyContent::DEFAULT;
node.align_items = AlignItems::DEFAULT;
}
}
pub mod grid {
use super::*;
pub fn apply_alignment(node: &mut Node, alignment: &Alignment) {
node.justify_self = match alignment.x {
AlignX::None => JustifySelf::DEFAULT,
AlignX::Left => JustifySelf::Start,
AlignX::Center => JustifySelf::Center,
AlignX::Right => JustifySelf::End,
};
node.align_self = match alignment.y {
AlignY::None => AlignSelf::DEFAULT,
AlignY::Top => AlignSelf::Start,
AlignY::Center => AlignSelf::Center,
AlignY::Bottom => AlignSelf::End,
};
}
pub fn reset_alignment(node: &mut Node) {
node.justify_self = JustifySelf::DEFAULT;
node.align_self = AlignSelf::DEFAULT;
}
}