use std::borrow::Cow;
use super::align::Alignable;
use bevy_ecs::{component::*, lifecycle::HookContext, prelude::*, system::RunSystemOnce, world::DeferredWorld};
use bevy_log::warn;
use jonmo::prelude::*;
use bevy_ecs::system::IntoObserverSystem;
pub trait Element: BuilderWrapper + Alignable {}
impl<E: BuilderWrapper + Alignable> Element for E {}
pub trait IntoElement {
type EL: Element;
fn into_element(self) -> Self::EL;
}
impl<T: Element> IntoElement for T {
type EL = T;
fn into_element(self) -> Self::EL {
self
}
}
pub trait IntoOptionElement {
type EL: Element;
fn into_option_element(self) -> Option<Self::EL>;
}
impl<E: Element, IE: IntoElement<EL = E>> IntoOptionElement for Option<IE> {
type EL = E;
fn into_option_element(self) -> Option<Self::EL> {
self.map(|into_element| into_element.into_element())
}
}
impl<E: Element, IE: IntoElement<EL = E>> IntoOptionElement for IE {
type EL = E;
fn into_option_element(self) -> Option<Self::EL> {
Some(self.into_element())
}
}
pub trait BuilderWrapper: Sized {
fn builder_mut(&mut self) -> &mut jonmo::Builder;
fn with_builder(mut self, f: impl FnOnce(jonmo::Builder) -> jonmo::Builder) -> Self {
let builder = std::mem::take(self.builder_mut());
*self.builder_mut() = f(builder);
self
}
fn into_builder(mut self) -> jonmo::Builder {
std::mem::take(self.builder_mut())
}
}
pub trait Spawnable: BuilderWrapper {
fn spawn(self, world: &mut World) -> Entity {
self.into_builder().spawn(world)
}
}
impl<T: BuilderWrapper> Spawnable for T {}
pub trait ElementWrapper: Sized {
type EL: Element + Default;
fn element_mut(&mut self) -> &mut Self::EL;
fn into_el(mut self) -> Self::EL {
std::mem::take(self.element_mut())
}
}
impl<EW: ElementWrapper> BuilderWrapper for EW {
fn builder_mut(&mut self) -> &mut jonmo::Builder {
self.element_mut().builder_mut()
}
fn into_builder(self) -> jonmo::Builder {
self.into_el().into_builder()
}
}
impl<EW: ElementWrapper> Alignable for EW {}
fn warn_non_orphan_ui_root(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
world.commands().queue(move |world: &mut World| {
let _ = world.run_system_once(move |child_ofs: Query<&ChildOf>| {
if child_ofs.iter_ancestors(entity).count() > 0 {
warn!(
"entity {:?} is registered as a UiRoot but is not an orphan (has a parent); this may lead to unexpected behavior",
entity
);
}
});
})
}
#[derive(Component)]
#[component(on_add = warn_non_orphan_ui_root)]
pub struct UiRoot;
pub trait UiRootable: BuilderWrapper {
fn ui_root(self) -> Self {
self.with_builder(|builder| builder.insert(UiRoot))
}
}
pub trait Nameable: BuilderWrapper {
fn name<T: Into<Cow<'static, str>>>(mut self, name_option: impl Into<Option<T>>) -> Self {
if let Some(name) = name_option.into() {
self = self.with_builder(|builder| builder.insert(Name::new(name)));
}
self
}
fn name_signal<T, S>(mut self, name_option_signal_option: impl Into<Option<S>>) -> Self
where
T: Into<Cow<'static, str>> + Clone + 'static,
S: Signal<Item = Option<T>> + Send + Sync + 'static,
{
if let Some(name_option_signal) = name_option_signal_option.into() {
self = self.with_builder(|builder| {
builder.component_signal(name_option_signal.map_in(|name_option| name_option.map(Name::new)))
});
}
self
}
}
pub trait BuilderPassThrough: BuilderWrapper {
fn lazy_entity(self, entity: jonmo::utils::LazyEntity) -> Self {
self.with_builder(|builder| builder.lazy_entity(entity))
}
fn insert<T: Bundle>(self, bundle: T) -> Self {
self.with_builder(|builder| builder.insert(bundle))
}
fn observe<E: EntityEvent, B: Bundle, Marker>(
self,
observer: impl IntoObserverSystem<E, B, Marker> + Sync,
) -> Self {
self.with_builder(|builder| builder.observe(observer))
}
fn component_signal<C>(self, signal: impl Signal<Item = Option<C>>) -> Self
where
C: Component + Clone,
{
self.with_builder(|builder| builder.component_signal(signal))
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! clone_semantics_doc {
($type_name:literal) => {
concat!(
"# `Clone` semantics\n\n",
"This type implements [`Clone`] **only** to satisfy trait bounds required by signal combinators.\n",
"**Cloning `",
$type_name,
"`s at runtime is a bug.** See [`",
$type_name,
"::clone`] for details."
)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! clone_error_doc_base {
($type_name:literal) => {
concat!(
"# Error\n\n",
"This clone implementation exists **only** to satisfy trait bounds required by signal\n",
"combinators. **Cloning `",
$type_name,
"`s at runtime is a bug and will lead to unexpected behavior.**\n\n",
"Clones produce an empty element with no on-spawn hooks.\n\n",
"Use factory functions instead if you need reusable UI templates"
)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! clone_error_doc {
($type_name:literal) => {
concat!($crate::clone_error_doc_base!($type_name), ".")
};
($type_name:literal, $example_fn:ident, $example_method:literal) => {
concat!(
$crate::clone_error_doc_base!($type_name),
":\n\n",
"```ignore\n",
"use bevy_ui::prelude::*;\n",
"use haalka::prelude::*;\n\n",
"fn ",
stringify!($example_fn),
"(label: &str) -> ",
$type_name,
"<Node> {\n",
" ",
$type_name,
"::new()",
$example_method,
"\n",
"}\n\n",
"// Correct: each call creates a fresh element\n",
"let el1 = ",
stringify!($example_fn),
"(\"First\");\n",
"let el2 = ",
stringify!($example_fn),
"(\"Second\");\n",
"```"
)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! clone_error_msg {
($type_name:literal) => {
concat!(
"Cloning `",
$type_name,
"` at {} is a bug! Use a factory function instead."
)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_element_clone {
($type_name:literal, $type:ty, $example_fn:ident, $example_method:literal) => {
impl<NodeType> Clone for $type {
#[doc = $crate::clone_error_doc!($type_name, $example_fn, $example_method)]
#[track_caller]
fn clone(&self) -> Self {
let msg = format!(
$crate::clone_error_msg!($type_name),
std::panic::Location::caller()
);
if cfg!(debug_assertions) {
let backtrace = std::backtrace::Backtrace::force_capture();
panic!("{}\nBacktrace:\n{}", msg, backtrace);
}
bevy_log::error!("{}", msg);
Self {
builder: self.builder.clone(),
_node_type: std::marker::PhantomData,
}
}
}
};
(simple $type_name:literal, $type:ty) => {
impl Clone for $type {
#[doc = $crate::clone_error_doc!($type_name)]
#[track_caller]
fn clone(&self) -> Self {
let msg = format!(
$crate::clone_error_msg!($type_name),
std::panic::Location::caller()
);
if cfg!(debug_assertions) {
let backtrace = std::backtrace::Backtrace::force_capture();
panic!("{}\nBacktrace:\n{}", msg, backtrace);
}
bevy_log::error!("{}", msg);
Self(self.0.clone())
}
}
};
}
pub trait TypeEraseable {
fn type_erase(self) -> AlignabilityFacade;
}
impl<T: Alignable> TypeEraseable for T {
fn type_erase(self) -> AlignabilityFacade {
AlignabilityFacade(self.into_builder())
}
}
#[doc = crate::clone_semantics_doc!("AlignabilityFacade")]
pub struct AlignabilityFacade(jonmo::Builder);
impl_element_clone!(simple "AlignabilityFacade", AlignabilityFacade);
impl BuilderWrapper for AlignabilityFacade {
fn builder_mut(&mut self) -> &mut jonmo::Builder {
&mut self.0
}
}
impl Alignable for AlignabilityFacade {}
#[allow(missing_docs)]
pub enum ElementEither<L, R>
where
L: Element,
R: Element,
{
Left(L),
Right(R),
}
impl<L, R> Clone for ElementEither<L, R>
where
L: Element + Clone,
R: Element + Clone,
{
#[doc = crate::clone_error_doc!("ElementEither")]
#[track_caller]
fn clone(&self) -> Self {
let msg = format!(crate::clone_error_msg!("ElementEither"), std::panic::Location::caller());
if cfg!(debug_assertions) {
panic!("{}", msg);
}
bevy_log::error!("{}", msg);
match self {
Self::Left(left) => Self::Left(left.clone()),
Self::Right(right) => Self::Right(right.clone()),
}
}
}
impl<L, R> BuilderWrapper for ElementEither<L, R>
where
L: Element,
R: Element,
{
fn builder_mut(&mut self) -> &mut jonmo::Builder {
match self {
ElementEither::Left(left) => left.builder_mut(),
ElementEither::Right(right) => right.builder_mut(),
}
}
fn into_builder(self) -> jonmo::Builder {
match self {
ElementEither::Left(left) => left.into_builder(),
ElementEither::Right(right) => right.into_builder(),
}
}
}
impl<L, R> Alignable for ElementEither<L, R>
where
L: Element,
R: Element,
{
}
pub trait IntoElementEither: Sized
where
Self: Element,
{
fn left_either<R>(self) -> ElementEither<Self, R>
where
R: Element,
{
ElementEither::Left(self)
}
fn right_either<L>(self) -> ElementEither<L, Self>
where
L: Element,
{
ElementEither::Right(self)
}
}
impl<T: Element> IntoElementEither for T {}