#![doc = include_str!("../README.md")]
#![allow(clippy::type_complexity)]
pub(crate) use std::any::TypeId;
use bevy::app::PluginGroupBuilder;
pub(crate) use bevy::prelude::*;
pub(crate) use bevy::sprite::SpriteSource;
pub(crate) use bevy::text::TextLayoutInfo;
pub(crate) use bevy::utils::HashMap;
pub(crate) use bevy::render::view::RenderLayers;
pub(crate) use colored::Colorize;
pub use bevy::sprite::Anchor;
#[cfg(feature = "text3d")]
pub use bevy_rich_text3d::*;
#[cfg(feature = "text3d")]
pub use bevy::text::cosmic_text::Weight;
mod cursor;
pub use cursor::*;
mod layouts;
pub use layouts::*;
mod picking;
pub use picking::*;
mod states;
pub use states::*;
mod units;
pub use units::*;
#[derive(Component, Reflect, Deref, DerefMut, Default, Clone, PartialEq, Debug)]
pub struct Dimension(pub Vec2);
impl <T: Into<Vec2>> From<T> for Dimension {
fn from(value: T) -> Self {
Dimension(value.into())
}
}
pub fn system_pipe_sprite_size_from_dimension(
mut query: Query<(&mut Sprite, &Dimension), Changed<Dimension>>,
) {
for (mut sprite, dimension) in &mut query {
sprite.custom_size = Some(**dimension)
}
}
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
pub struct UiEmbedding;
pub fn system_embedd_resize(
query: Query<(&Sprite, &Dimension), (With<UiEmbedding>, Changed<Dimension>)>,
mut images: ResMut<Assets<Image>>,
) {
for (sprite, dimension) in &query {
if let Some(image) = images.get_mut(&sprite.image) {
if **dimension != Vec2::ZERO {
image.resize(bevy::render::render_resource::Extent3d { width: dimension.x as u32, height: dimension.y as u32, ..default() });
}
}
}
}
pub trait ImageTextureConstructor {
fn clear_render_texture() -> Image {
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages};
use bevy::asset::RenderAssetUsages;
let mut image = Image::new_fill(
Extent3d {
width: 512,
height: 512,
..default()
},
TextureDimension::D2,
&[0, 0, 0, 0],
TextureFormat::Bgra8UnormSrgb,
RenderAssetUsages::default(),
);
image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT;
image
}
}
impl ImageTextureConstructor for Image {}
pub trait CameraTextureRenderConstructor {
fn clear_render_to(handle: Handle<Image>) -> Camera {
use bevy::render::camera::RenderTarget;
Camera {
target: RenderTarget::Image(handle),
clear_color: ClearColorConfig::Custom(Color::srgba(0.0, 0.0, 0.0, 0.0)),
..default()
}
}
fn with_order(self, order: isize) -> Self;
}
impl CameraTextureRenderConstructor for Camera {
fn with_order(mut self, order: isize) -> Self {
self.order = order;
self
}
}
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
#[require(Visibility, Transform, Dimension)]
pub struct UiLayoutRoot {
abs_scale: f32,
}
impl UiLayoutRoot {
pub fn new_2d() -> Self {
Self { abs_scale: 1.0 }
}
pub fn new_3d() -> Self {
Self { abs_scale: 0.001 }
}
}
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
pub struct UiRoot3d;
pub fn system_mark_3d(
root_query: Query<(Has<UiRoot3d>, &Children), (With<UiLayoutRoot>, Without<UiLayout>, Changed<UiLayoutRoot>)>,
node_query: Query<(Entity, Has<UiRoot3d>, Option<&Children>), (With<UiLayout>, Without<UiLayoutRoot>)>,
mut commands: Commands,
) {
for (is_root_3d, root_children) in &root_query {
let mut stack: Vec<(Entity, usize)> = root_children.iter().map(|&child| (child, 1)).rev().collect();
while let Some((current_entity, depth)) = stack.pop() {
if let Ok((node, is_node_3d, node_children_option)) = node_query.get(current_entity) {
if is_root_3d != is_node_3d {
if is_root_3d {
commands.entity(node).insert(UiRoot3d);
} else {
commands.entity(node).remove::<UiRoot3d>();
}
}
if let Some(node_children) = node_children_option {
for &child in node_children.iter().rev() {
stack.push((child, depth + 1));
}
}
}
}
}
}
#[derive(Event)]
pub struct RecomputeUiLayout;
pub fn observer_touch_layout_root(
_trigger: Trigger<RecomputeUiLayout>,
mut query: Query<&mut UiLayoutRoot>,
){
for mut root in &mut query {
root.as_mut();
}
}
pub fn system_debug_draw_gizmo_2d(
query: Query<(&GlobalTransform, &Dimension), (Or<(With<UiLayout>, With<UiLayoutRoot>)>, Without<UiRoot3d>)>,
mut gizmos: Gizmos<LunexGizmoGroup2d>
) {
for (transform, dimension) in &query {
gizmos.rect(
Isometry3d::from(transform.translation()),
**dimension,
Color::linear_rgb(0.0, 1.0, 0.0),
);
}
}
pub fn system_debug_draw_gizmo_3d(
query: Query<(&GlobalTransform, &Dimension), (Or<(With<UiLayout>, With<UiLayoutRoot>)>, With<UiRoot3d>)>,
mut gizmos: Gizmos<LunexGizmoGroup3d>
) {
for (transform, dimension) in &query {
gizmos.rect(
Isometry3d::from(transform.translation()),
**dimension,
Color::linear_rgb(0.0, 1.0, 0.0),
);
}
}
pub fn system_debug_print_data(
root_query: Query<(&UiLayoutRoot, NameOrEntity, &Dimension, &Children), (Without<UiLayout>, Or<(Changed<UiLayoutRoot>, Changed<Dimension>)>)>,
node_query: Query<(&UiLayout, &UiState, NameOrEntity, &Dimension, &Transform, Option<&Children>), Without<UiLayoutRoot>>,
) {
for (_, root_name, root_dimension, root_children) in &root_query {
let mut output_string = format!("▶ {}", format!("{root_name}").bold().underline().magenta());
output_string += " ⇒ ";
output_string += &format!("[w: {}, h: {}]", format!("{:.02}", root_dimension.x).green(), format!("{:.02}", root_dimension.y).green());
output_string += "\n";
let mut stack: Vec<(Entity, usize, bool)> = root_children
.iter()
.enumerate()
.map(|(i, &child)| (child, 1, i == root_children.len() - 1)) .rev()
.collect();
let mut last_child_levels: Vec<bool> = Vec::new();
while let Some((current_entity, depth, is_last)) = stack.pop() {
if let Ok((node_layout, _node_state, node_name, node_dimension, node_transform, node_children_option)) = node_query.get(current_entity) {
if last_child_levels.len() < depth {
last_child_levels.push(is_last);
} else {
last_child_levels[depth - 1] = is_last;
}
for &last in &last_child_levels[..depth - 1] {
output_string += &if last { format!("{}", " ┆".black()) } else { " │".to_string() };
}
output_string += if is_last { " └" } else { " ├" };
if node_name.name.is_some() {
output_string += &format!("─ {}", format!("{node_name}").bold().yellow());
} else {
output_string += &format!("─ {}", format!("{node_name}").yellow());
}
output_string += " ⇒ ";
output_string += &format!("[w: {}, h: {}, d: {}]",
format!("{:.02}", node_dimension.x).green(),
format!("{:.02}", node_dimension.y).green(),
format!("{:.00}", node_transform.translation.z).green(),
);
match node_layout.layouts.get(&UiBase::id()).unwrap() {
UiLayoutType::Boundary(boundary) => {
output_string += &format!(" ➜ {} {} p1: {}, p2: {} {}",
"Boundary".bold(),
"{",
boundary.pos1.to_nicestr(),
boundary.pos2.to_nicestr(),
"}",
);
},
UiLayoutType::Window(window) => {
output_string += &format!(" ➜ {} {} p: {}, s: {}, a: {} {}",
"Window".bold(),
"{",
window.pos.to_nicestr(),
window.size.to_nicestr(),
window.anchor.to_nicestr(),
"}",
);
},
UiLayoutType::Solid(solid) => {
output_string += &format!(" ➜ {} {} s: {}, ax: {}, ay: {}, scl: {} {}",
"Solid".bold(),
"{",
solid.size.to_nicestr(),
format!("{:.02}", solid.align_x.0).green(),
format!("{:.02}", solid.align_y.0).green(),
format!("{:?}", solid.scaling).green(),
"}",
);
},
}
output_string += "\n";
if let Some(node_children) = node_children_option {
let child_count = node_children.len();
for (i, &child) in node_children.iter().enumerate().rev() {
stack.push((child, depth + 1, i == child_count - 1));
}
}
}
}
info!("UiLayout change detected:\n{}", output_string);
}
}
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
#[require(Visibility, SpriteSource, Transform, Dimension, UiState, UiDepth)]
pub struct UiLayout {
pub layouts: HashMap<TypeId, UiLayoutType>
}
impl UiLayout {
pub fn boundary() -> UiLayoutTypeBoundary {
UiLayoutTypeBoundary::new()
}
pub fn window() -> UiLayoutTypeWindow {
UiLayoutTypeWindow::new()
}
pub fn solid() -> UiLayoutTypeSolid {
UiLayoutTypeSolid::new()
}
pub fn new(value: Vec<(TypeId, impl Into<UiLayoutType>)>) -> Self {
let mut map = HashMap::new();
for (state, layout) in value {
map.insert(state, layout.into());
}
Self { layouts: map }
}
}
impl From<UiLayoutType> for UiLayout {
fn from(value: UiLayoutType) -> Self {
let mut map = HashMap::new();
map.insert(UiBase::id(), value);
Self {
layouts: map,
}
}
}
impl From<UiLayoutTypeBoundary> for UiLayout {
fn from(value: UiLayoutTypeBoundary) -> Self {
let value: UiLayoutType = value.into();
UiLayout::from(value)
}
}
impl From<UiLayoutTypeWindow> for UiLayout {
fn from(value: UiLayoutTypeWindow) -> Self {
let value: UiLayoutType = value.into();
UiLayout::from(value)
}
}
impl From<UiLayoutTypeSolid> for UiLayout {
fn from(value: UiLayoutTypeSolid) -> Self {
let value: UiLayoutType = value.into();
UiLayout::from(value)
}
}
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
pub enum UiDepth {
Add(f32),
Set(f32),
}
impl Default for UiDepth {
fn default() -> Self {
UiDepth::Add(1.0)
}
}
pub fn system_layout_compute(
root_query: Query<(&UiLayoutRoot, &Transform, &Dimension, &Children), (Without<UiLayout>, Or<(Changed<UiLayoutRoot>, Changed<Dimension>)>)>,
mut node_query: Query<(&UiLayout, &UiDepth, &UiState, &mut Transform, &mut Dimension, Option<&Children>), Without<UiLayoutRoot>>,
) {
for (root, root_transform, root_dimension, root_children) in &root_query {
let root_rectangle = Rectangle2D {
pos: root_transform.translation.xy(),
size: **root_dimension,
};
let mut stack: Vec<(Entity, Rectangle2D, f32)> = root_children.iter().map(|&child| (child, root_rectangle, 0.0)).rev().collect();
while let Some((current_entity, parent_rectangle, depth)) = stack.pop() {
if let Ok((node_layout, node_depth, node_state, mut node_transform, mut node_dimension, node_children_option)) = node_query.get_mut(current_entity) {
let mut computed_rectangles = Vec::with_capacity(node_layout.layouts.len());
for (state, layout) in &node_layout.layouts {
computed_rectangles.push((state, layout.compute(&parent_rectangle, root.abs_scale, root_rectangle.size, 16.0)));
}
let mut total_weight = 0.0;
for (state, _) in &node_layout.layouts {
if let Some(weight) = node_state.states.get(state) {
total_weight += weight;
}
}
let mut node_rectangle = Rectangle2D::EMPTY;
if total_weight == 0.0 {
node_rectangle.pos += computed_rectangles[0].1.pos;
node_rectangle.size += computed_rectangles[0].1.size;
} else {
for (state, rectangle) in computed_rectangles {
if let Some(weight) = node_state.states.get(state) {
node_rectangle.pos += rectangle.pos * (weight / total_weight);
node_rectangle.size += rectangle.size * (weight / total_weight);
}
}
}
node_transform.translation.x = node_rectangle.pos.x;
node_transform.translation.y = -node_rectangle.pos.y;
let depth = match node_depth {
UiDepth::Add(v) => {depth + v},
UiDepth::Set(v) => {*v},
};
node_transform.translation.z = depth * root.abs_scale;
**node_dimension = node_rectangle.size;
if let Some(node_children) = node_children_option {
stack.extend(node_children.iter().map(|&child| (child, node_rectangle, depth)));
}
}
}
}
}
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
pub struct UiState {
states: HashMap<TypeId, f32>,
}
impl Default for UiState {
fn default() -> Self {
let mut map = HashMap::new();
map.insert(UiBase::id(), 1.0);
Self {
states: map,
}
}
}
pub fn system_state_base_balancer(
mut query: Query<&mut UiState, Changed<UiState>>,
) {
for mut manager in &mut query {
let mut total_nonbase_weight = 0.0;
for (state, value) in &manager.states {
if *state == UiBase::id() { continue; }
total_nonbase_weight += value;
}
if let Some(value) = manager.states.get_mut(&UiBase::id()) {
*value = (1.0 - total_nonbase_weight).clamp(0.0, 1.0);
}
}
}
pub fn system_state_pipe_into_manager<S: UiStateTrait + Component>(
mut commads: Commands,
mut query: Query<(&mut UiState, &S), Changed<S>>,
) {
for (mut manager, state) in &mut query {
if let Some(value) = manager.states.get_mut(&S::id()) {
*value = state.value();
} else {
manager.states.insert(S::id(), state.value());
}
commads.trigger(RecomputeUiLayout);
}
}
pub trait UiStateTrait: Send + Sync + 'static {
fn id() -> TypeId {
TypeId::of::<Self>()
}
fn value(&self) -> f32;
}
#[derive(Clone, PartialEq, Debug)]
pub struct UiBase;
impl UiStateTrait for UiBase {
fn value(&self) -> f32 {
1.0
}
}
#[derive(Component, Reflect, Deref, DerefMut, Default, Clone, PartialEq, Debug)]
pub struct UiTextSize (pub UiValue<f32>);
impl <T: Into<UiValue<f32>>> From<T> for UiTextSize {
fn from(value: T) -> Self {
UiTextSize(value.into())
}
}
pub fn system_text_size_from_dimension(
mut commands: Commands,
mut query: Query<(&mut Transform, &Dimension, &TextLayoutInfo), Changed<Dimension>>,
) {
for (mut transform, dimension, text_info) in &mut query {
if text_info.size.y == 0.0 {
commands.trigger(RecomputeUiLayout);
}
let scale = **dimension / text_info.size;
transform.scale.x = scale.x;
transform.scale.y = scale.x;
}
}
pub fn system_text_size_to_layout(
mut commands: Commands,
mut query: Query<(&mut UiLayout, &TextLayoutInfo, &UiTextSize), Changed<TextLayoutInfo>>,
) {
for (mut layout, text_info, text_size) in &mut query {
if text_info.size.y == 0.0 {
commands.trigger(RecomputeUiLayout);
}
match layout.layouts.get_mut(&UiBase::id()).expect("UiBase state not found for Text") {
UiLayoutType::Window(window) => {
window.set_height(**text_size);
window.set_width(**text_size * (text_info.size.x / text_info.size.y));
},
UiLayoutType::Solid(solid) => {
solid.set_size(Ab(text_info.size));
},
_ => {},
}
}
}
#[cfg(feature = "text3d")]
pub fn system_text_3d_size_to_layout(
mut commands: Commands,
mut query: Query<(&mut UiLayout, &Text3dDimensionOut, &UiTextSize), Changed<Text3dDimensionOut>>,
) {
for (mut layout, text_info, text_size) in &mut query {
if text_info.dimension.y == 0.0 {
commands.trigger(RecomputeUiLayout);
continue;
}
match layout.layouts.get_mut(&UiBase::id()).expect("UiBase state not found for Text") {
UiLayoutType::Window(window) => {
window.set_height(**text_size);
window.set_width(**text_size * (text_info.dimension.x / text_info.dimension.y));
},
UiLayoutType::Solid(solid) => {
solid.set_size(Ab(text_info.dimension));
},
_ => {},
}
}
}
#[cfg(feature = "text3d")]
pub fn system_text_3d_size_from_dimension(
mut commands: Commands,
mut query: Query<(&mut Transform, &Dimension, &Text3dDimensionOut), Changed<Dimension>>,
) {
for (mut transform, dimension, text_info) in &mut query {
if text_info.dimension.y == 0.0 {
commands.trigger(RecomputeUiLayout);
continue;
}
let scale = dimension.x / text_info.dimension.x;
transform.scale.x = scale;
transform.scale.y = scale;
}
}
#[derive(Component, Reflect, Default, Clone, PartialEq, Debug)]
#[require(Mesh3d)]
pub struct UiMeshPlane3d;
#[derive(Component, Reflect, Default, Clone, PartialEq, Debug)]
#[require(Mesh2d)]
pub struct UiMeshPlane2d;
pub fn system_mesh_3d_reconstruct_from_dimension(
mut query: Query<(&Dimension, &mut Mesh3d), (With<UiMeshPlane3d>, Changed<Dimension>)>,
mut meshes: ResMut<Assets<Mesh>>,
) {
for (dimension, mut mesh) in &mut query {
let plane_mesh = meshes.add(Rectangle::new(dimension.x, dimension.y));
mesh.0 = plane_mesh;
}
}
pub fn system_mesh_2d_reconstruct_from_dimension(
mut query: Query<(&Dimension, &mut Mesh2d), (With<UiMeshPlane2d>, Changed<Dimension>)>,
mut meshes: ResMut<Assets<Mesh>>,
) {
for (dimension, mut mesh) in &mut query {
let plane_mesh = meshes.add(Rectangle::new(dimension.x, dimension.y));
mesh.0 = plane_mesh;
}
}
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
pub struct UiFetchFromCamera<const INDEX: usize>;
#[derive(Component, Reflect, Clone, PartialEq, Debug)]
pub struct UiSourceCamera<const INDEX: usize>;
pub fn system_fetch_dimension_from_camera<const INDEX: usize>(
src_query: Query<(&Camera, Option<&OrthographicProjection>), (With<UiSourceCamera<INDEX>>, Changed<Camera>)>,
mut dst_query: Query<&mut Dimension, (With<UiLayoutRoot>, With<UiFetchFromCamera<INDEX>>)>,
) {
if src_query.is_empty() { return; }
let Ok((camera, projection_option)) = src_query.get_single() else {
warn_once!("Multiple UiSourceCamera<{INDEX}> exist at once! Ignoring all camera inputs to avoid unexpected behavior!");
return;
};
if let Some(cam_size) = camera.logical_viewport_size() {
for mut size in &mut dst_query {
**size = Vec2::from((cam_size.x, cam_size.y)) * if let Some(p) = projection_option { p.scale } else { 1.0 };
}
}
}
pub fn system_touch_camera_if_fetch_added<const INDEX: usize>(
query: Query<Entity, Added<UiFetchFromCamera<INDEX>>>,
mut cameras: Query<&mut Camera, With<UiSourceCamera<INDEX>>>,
){
if !query.is_empty() {
for mut camera in &mut cameras {
camera.as_mut();
}
}
}
#[derive(Component, Reflect, Deref, DerefMut, Default, Clone, PartialEq, Debug)]
pub struct UiColor {
colors: HashMap<TypeId, Color>
}
impl UiColor {
pub fn new(value: Vec<(TypeId, impl Into<Color>)>) -> Self {
let mut map = HashMap::new();
for (state, layout) in value {
map.insert(state, layout.into());
}
Self { colors: map }
}
}
impl <T: Into<Color>> From<T> for UiColor {
fn from(value: T) -> Self {
let mut map = HashMap::new();
map.insert(UiBase::id(), value.into());
Self {
colors: map,
}
}
}
pub fn system_color(
mut query: Query<(Option<&mut Sprite>, Option<&mut TextColor>, &UiColor, &UiState), Or<(Changed<UiColor>, Changed<UiState>)>>,
) {
for (node_sprite_option, node_text_option, node_color, node_state) in &mut query {
let mut total_weight = 0.0;
for (state, _) in &node_color.colors {
if let Some(weight) = node_state.states.get(state) {
total_weight += weight;
}
}
let mut blend_color = Hsla::new(0.0, 0.0, 0.0, 0.0);
if total_weight == 0.0 {
if let Some(color) = node_color.colors.get(&UiBase::id()) {
blend_color = (*color).into();
}
} else {
for (state, color) in &node_color.colors {
if let Some(weight) = node_state.states.get(state) {
let converted: Hsla = (*color).into();
if blend_color.alpha == 0.0 {
blend_color.hue = converted.hue;
} else {
blend_color.hue = lerp_hue(blend_color.hue, converted.hue, weight / total_weight);
}
blend_color.saturation += converted.saturation * (weight / total_weight);
blend_color.lightness += converted.lightness * (weight / total_weight);
blend_color.alpha += converted.alpha * (weight / total_weight);
}
}
}
if let Some(mut sprite) = node_sprite_option {
sprite.color = blend_color.into();
}
if let Some(mut text) = node_text_option {
**text = blend_color.into();
}
}
}
fn lerp_hue(h1: f32, h2: f32, t: f32) -> f32 {
let diff = (h2 - h1 + 540.0) % 360.0 - 180.0; (h1 + diff * t + 360.0) % 360.0
}
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub enum UiSystems {
PreCompute,
Compute,
PostCompute,
}
#[derive(GizmoConfigGroup, Default, Reflect, Clone, Debug)]
pub struct LunexGizmoGroup2d;
#[derive(GizmoConfigGroup, Default, Reflect, Clone, Debug)]
pub struct LunexGizmoGroup3d;
#[derive(Debug, Default, Clone)]
pub struct UiLunexPlugin;
impl Plugin for UiLunexPlugin {
fn build(&self, app: &mut App) {
app.configure_sets(PostUpdate, (
UiSystems::PreCompute.before(UiSystems::Compute),
UiSystems::PostCompute.after(UiSystems::Compute).before(bevy::transform::TransformSystem::TransformPropagate),
));
app.add_observer(observer_touch_layout_root);
app.add_systems(PostUpdate, (
system_state_base_balancer,
system_text_size_to_layout.after(bevy::text::update_text2d_layout),
).in_set(UiSystems::PreCompute));
#[cfg(feature = "text3d")]
app.add_systems(PostUpdate,
system_text_3d_size_to_layout
.after(bevy_rich_text3d::Text3dSet)
.in_set(UiSystems::PreCompute)
);
app.add_systems(PostUpdate, (
system_layout_compute,
).in_set(UiSystems::Compute));
app.add_systems(PostUpdate, (
system_color,
system_mark_3d,
system_pipe_sprite_size_from_dimension.before(bevy::sprite::SpriteSystem::ComputeSlices),
system_text_size_from_dimension,
system_mesh_3d_reconstruct_from_dimension,
system_mesh_2d_reconstruct_from_dimension,
system_embedd_resize,
).in_set(UiSystems::PostCompute));
#[cfg(feature = "text3d")]
app.add_systems(PostUpdate,
system_text_3d_size_from_dimension
.in_set(UiSystems::PostCompute)
);
app.add_plugins((
CursorPlugin,
UiLunexStatePlugin,
UiLunexPickingPlugin,
UiLunexIndexPlugin::<0>,
UiLunexIndexPlugin::<1>,
UiLunexIndexPlugin::<2>,
UiLunexIndexPlugin::<3>,
));
}
}
#[derive(Debug, Default, Clone)]
pub struct UiLunexDebugPlugin<const GIZMO_2D_LAYER: usize = 0, const GIZMO_3D_LAYER: usize = 0>;
impl <const GIZMO_2D_LAYER: usize, const GIZMO_3D_LAYER: usize> Plugin for UiLunexDebugPlugin<GIZMO_2D_LAYER, GIZMO_3D_LAYER> {
fn build(&self, app: &mut App) {
app .init_gizmo_group::<LunexGizmoGroup2d>()
.init_gizmo_group::<LunexGizmoGroup3d>()
.add_systems(Startup, |mut config_store: ResMut<GizmoConfigStore>| {
let (my_config, _) = config_store.config_mut::<LunexGizmoGroup2d>();
my_config.render_layers = RenderLayers::layer(GIZMO_2D_LAYER);
let (my_config, _) = config_store.config_mut::<LunexGizmoGroup3d>();
my_config.render_layers = RenderLayers::layer(GIZMO_3D_LAYER);
});
app.add_systems(PostUpdate, (
system_debug_draw_gizmo_2d,
system_debug_draw_gizmo_3d,
));
app.add_systems(PostUpdate, (
system_debug_print_data,
).in_set(UiSystems::PostCompute));
}
}
#[derive(Debug, Default, Clone)]
pub struct UiLunexIndexPlugin<const INDEX: usize>;
impl <const INDEX: usize> Plugin for UiLunexIndexPlugin<INDEX> {
fn build(&self, app: &mut App) {
app.add_systems(PostUpdate, (
system_fetch_dimension_from_camera::<INDEX>,
system_touch_camera_if_fetch_added::<INDEX>,
).in_set(UiSystems::PreCompute));
}
}
pub struct UiLunexPlugins;
impl PluginGroup for UiLunexPlugins {
fn build(self) -> PluginGroupBuilder {
let mut builder = PluginGroupBuilder::start::<Self>();
#[cfg(feature = "text3d")] {
builder = builder.add(Text3dPlugin {
load_system_fonts: true,
..Default::default()
});
}
builder = builder.add(UiLunexPlugin);
builder
}
}