use crate::ecs::world::World;
use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::components::*;
use crate::ecs::ui::state::{UiBase, UiStateTrait};
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::{Ab, Rl};
use super::range_slider_visuals::find_widget_content_in_defs;
use crate::prelude::*;
pub fn ui_mark_layout_dirty(world: &mut World) {
world.resources.retained_ui.dirty.layout_dirty = true;
}
pub fn ui_mark_render_dirty(world: &mut World) {
world.resources.retained_ui.dirty.render_dirty = true;
}
pub fn ui_mark_children_dirty(world: &mut World) {
world.resources.transform_state.children_cache_valid = false;
world.resources.retained_ui.dirty.layout_dirty = true;
}
pub fn ui_set_error(world: &mut World, entity: freecs::Entity, error: Option<&str>) {
if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
interaction.error_text = error.map(String::from);
interaction.tooltip_text = error.map(String::from);
}
}
pub fn ui_has_error(world: &World, entity: freecs::Entity) -> bool {
world
.ui
.get_ui_node_interaction(entity)
.is_some_and(|interaction| interaction.error_text.is_some())
}
pub fn ui_clear_error(world: &mut World, entity: freecs::Entity) {
ui_set_error(world, entity, None);
}
pub fn ui_add_validation_rule(
world: &mut World,
entity: freecs::Entity,
rule: crate::ecs::ui::components::ValidationRule,
) {
if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
interaction.validation_rules.push(rule);
}
}
pub fn ui_set_validation_rules(
world: &mut World,
entity: freecs::Entity,
rules: Vec<crate::ecs::ui::components::ValidationRule>,
) {
if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
interaction.validation_rules = rules;
}
}
pub fn ui_validate(world: &mut World, entity: freecs::Entity) -> bool {
let text = widget::<UiTextInputData>(world, entity)
.map(|d| d.text.clone())
.unwrap_or_default();
let rules: Vec<crate::ecs::ui::components::ValidationRule> = world
.ui
.get_ui_node_interaction(entity)
.map(|interaction| interaction.validation_rules.clone())
.unwrap_or_default();
let mut error: Option<String> = None;
for rule in &rules {
if let Err(message) = rule.validate(&text) {
error = Some(message);
break;
}
}
let is_valid = error.is_none();
if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
interaction.error_text = error.clone();
interaction.tooltip_text = error;
}
is_valid
}
pub fn ui_is_valid(world: &World, entity: freecs::Entity) -> bool {
world
.ui
.get_ui_node_interaction(entity)
.is_some_and(|interaction| interaction.error_text.is_none())
}
pub fn ui_set_toast_corner(world: &mut World, corner: crate::ecs::ui::resources::ToastCorner) {
if world.resources.retained_ui.overlays.toast_corner != corner {
world.resources.retained_ui.overlays.toast_corner = corner;
if let Some(container) = world.resources.retained_ui.overlays.toast_container.take() {
ui_despawn_node(world, container);
}
}
}
pub fn ui_show_toast(
world: &mut World,
message: &str,
severity: crate::ecs::ui::resources::ToastSeverity,
duration: f32,
) {
use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::ui::components::{UiAnimationPhase, UiAnimationType};
use crate::ecs::ui::resources::{ToastEntry, ToastSeverity};
let corner = world.resources.retained_ui.overlays.toast_corner;
let container = if let Some(entity) = world.resources.retained_ui.overlays.toast_container {
entity
} else {
use crate::ecs::ui::layout_types::{FlowAlignment, FlowDirection};
use crate::ecs::ui::resources::ToastCorner;
use crate::ecs::ui::units::UiValue;
const TOAST_WIDTH: f32 = 320.0;
const TOAST_MARGIN: f32 = 16.0;
const TOP_OFFSET: f32 = 60.0;
let (point_1, point_2, main_alignment): (UiValue<Vec2>, UiValue<Vec2>, FlowAlignment) =
match corner {
ToastCorner::TopLeft => (
Rl(Vec2::new(0.0, 0.0)) + Ab(Vec2::new(TOAST_MARGIN, TOP_OFFSET)),
Rl(Vec2::new(0.0, 100.0))
+ Ab(Vec2::new(TOAST_MARGIN + TOAST_WIDTH, -TOAST_MARGIN)),
FlowAlignment::Start,
),
ToastCorner::TopRight => (
Rl(Vec2::new(100.0, 0.0))
+ Ab(Vec2::new(-(TOAST_MARGIN + TOAST_WIDTH), TOP_OFFSET)),
Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-TOAST_MARGIN, -TOAST_MARGIN)),
FlowAlignment::Start,
),
ToastCorner::BottomLeft => (
Rl(Vec2::new(0.0, 0.0)) + Ab(Vec2::new(TOAST_MARGIN, TOP_OFFSET)),
Rl(Vec2::new(0.0, 100.0))
+ Ab(Vec2::new(TOAST_MARGIN + TOAST_WIDTH, -TOAST_MARGIN)),
FlowAlignment::End,
),
ToastCorner::BottomRight => (
Rl(Vec2::new(100.0, 0.0))
+ Ab(Vec2::new(-(TOAST_MARGIN + TOAST_WIDTH), TOP_OFFSET)),
Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-TOAST_MARGIN, -TOAST_MARGIN)),
FlowAlignment::End,
),
};
let mut tree = UiTreeBuilder::new(world);
let container = tree
.add_node()
.boundary(point_1, point_2)
.flow_with_alignment(
FlowDirection::Vertical,
0.0,
8.0,
main_alignment,
FlowAlignment::Start,
)
.with_depth(UiDepthMode::Set(50.0))
.entity();
tree.finish();
world.resources.retained_ui.overlays.toast_container = Some(container);
container
};
let accent = match severity {
ToastSeverity::Info => Vec4::new(0.3, 0.6, 1.0, 1.0),
ToastSeverity::Success => Vec4::new(0.2, 0.8, 0.3, 1.0),
ToastSeverity::Warning => Vec4::new(1.0, 0.7, 0.1, 1.0),
ToastSeverity::Error => Vec4::new(1.0, 0.25, 0.25, 1.0),
};
let text_slot = world.resources.text.cache.add_text(message);
let spawn_time = world.resources.retained_ui.timing.current_time;
let mut tree = UiTreeBuilder::from_parent(world, container);
let intro_kind = match corner {
crate::ecs::ui::resources::ToastCorner::TopLeft
| crate::ecs::ui::resources::ToastCorner::BottomLeft => UiAnimationType::SlideLeft,
crate::ecs::ui::resources::ToastCorner::TopRight
| crate::ecs::ui::resources::ToastCorner::BottomRight => UiAnimationType::SlideRight,
};
let toast = tree
.add_node()
.flow_child(Ab(Vec2::new(320.0, 44.0)))
.with_rect(6.0, 1.0, Vec4::new(0.15, 0.15, 0.22, 0.5))
.color_raw::<UiBase>(Vec4::new(0.06, 0.06, 0.1, 0.95))
.with_intro(intro_kind, 0.2)
.with_outro(UiAnimationType::Fade, 0.15)
.entity();
tree.push_parent(toast);
tree.add_node()
.boundary(
Rl(Vec2::new(0.0, 0.0)),
Ab(Vec2::new(4.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)),
)
.with_rect(6.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.color_raw::<UiBase>(accent);
tree.add_node()
.window(
Ab(Vec2::new(14.0, 18.0)),
Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(-20.0, 20.0)),
Anchor::CenterLeft,
)
.with_text_slot(text_slot, 13.0)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.color_raw::<UiBase>(Vec4::new(0.9, 0.9, 0.95, 1.0));
tree.pop_parent();
tree.finish_subtree();
if let Some(node) = world.ui.get_ui_layout_node_mut(toast) {
if let Some(animation) = &mut node.animation {
animation.phase = UiAnimationPhase::IntroPlaying;
animation.progress = 0.0;
}
node.visible = true;
}
ui_mark_layout_dirty(world);
ui_mark_render_dirty(world);
world
.resources
.retained_ui
.overlays
.toast_entries
.push(ToastEntry {
entity: toast,
spawn_time,
duration,
dismissing: false,
});
}
pub fn ui_tick_toasts(world: &mut World) {
let current_time = world.resources.retained_ui.timing.current_time;
let entries = std::mem::take(&mut world.resources.retained_ui.overlays.toast_entries);
let mut kept = Vec::new();
let mut to_despawn = Vec::new();
for mut entry in entries {
let elapsed = (current_time - entry.spawn_time) as f32;
if entry.dismissing {
let phase = world
.ui
.get_ui_layout_node(entry.entity)
.and_then(|node| node.animation.as_ref())
.map(|anim| anim.phase);
if phase == Some(crate::ecs::ui::components::UiAnimationPhase::OutroComplete)
|| !ui_node_visible(world, entry.entity)
{
to_despawn.push(entry.entity);
} else {
kept.push(entry);
}
} else if elapsed >= entry.duration {
entry.dismissing = true;
ui_set_visible(world, entry.entity, false);
kept.push(entry);
} else {
kept.push(entry);
}
}
world.resources.retained_ui.overlays.toast_entries = kept;
for entity in to_despawn {
ui_despawn_node(world, entity);
}
}
pub fn ui_set_selected(world: &mut World, entity: freecs::Entity, selected: bool) {
let accent = world
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
let selected_bg = Vec4::new(accent.x, accent.y, accent.z, 0.3);
if let Some(data) = world.ui.get_ui_selectable_label_mut(entity) {
data.selected = selected;
}
if let Some(color) = world.ui.get_ui_node_color_mut(entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] =
if selected { Some(selected_bg) } else { None };
}
if let Some(weights) = world.ui.get_ui_state_weights_mut(entity) {
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] =
if selected { 1.0 } else { 0.0 };
}
}
pub fn ui_drag_value_set_value(world: &mut World, entity: freecs::Entity, value: f32) {
let update = if let Some(data) = world.ui.get_ui_drag_value(entity) {
let clamped = value.clamp(data.min, data.max);
Some((
data.text_slot,
data.prefix.clone(),
data.suffix.clone(),
data.precision,
clamped,
))
} else {
None
};
if let Some((text_slot, prefix, suffix, precision, clamped)) = update {
let display = format!("{prefix}{clamped:.prec$}{suffix}", prec = precision);
world.resources.text.cache.set_text(text_slot, &display);
if let Some(data) = world.ui.get_ui_drag_value_mut(entity) {
data.value = clamped;
}
}
}
pub fn ui_show_context_menu(world: &mut World, entity: freecs::Entity, position: Vec2) {
if let Some(old_menu) = world.resources.retained_ui.overlays.active_context_menu
&& old_menu != entity
&& let Some(old_data) = world.ui.get_ui_context_menu(old_menu).cloned().as_ref()
{
if let Some(node) = world.ui.get_ui_layout_node_mut(old_data.popup_entity) {
node.visible = false;
}
if let Some(wd) = world.ui.get_ui_context_menu_mut(old_menu) {
wd.open = false;
}
}
let popup = world
.ui
.get_ui_context_menu(entity)
.map(|data| data.popup_entity);
if let Some(popup_entity) = popup {
let dpi_scale = crate::ecs::window::resources::window_scale_factor(world);
if let Some(node) = world.ui.get_ui_layout_node_mut(popup_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.base_layout.as_mut()
{
window.position = crate::ecs::ui::units::Ab(position / dpi_scale).into();
node.visible = true;
}
if let Some(data) = world.ui.get_ui_context_menu_mut(entity) {
data.open = true;
data.clicked_item = None;
}
world.resources.retained_ui.overlays.active_context_menu = Some(entity);
}
}
pub fn ui_context_menu_set_checked(
world: &mut World,
entity: freecs::Entity,
command_id: usize,
checked: bool,
) {
let Some(data) = world.ui.get_ui_context_menu_mut(entity) else {
return;
};
set_checkable_in_defs(
&mut data.item_defs,
command_id,
checked,
&mut world.resources.text.cache,
);
}
pub fn ui_context_menu_get_checked(
world: &World,
entity: freecs::Entity,
command_id: usize,
) -> Option<bool> {
let data = world.ui.get_ui_context_menu(entity)?;
find_checkable_in_defs(&data.item_defs, command_id)
}
pub fn ui_context_menu_widget_content(
world: &World,
entity: freecs::Entity,
command_id: usize,
) -> Option<freecs::Entity> {
if let Some(data) = world.ui.get_ui_context_menu(entity) {
find_widget_content_in_defs(&data.item_defs, command_id)
} else {
None
}
}
pub fn ui_close_context_menus(world: &mut World) {
if let Some(menu_entity) = world.resources.retained_ui.overlays.active_context_menu {
let popup = world
.ui
.get_ui_context_menu(menu_entity)
.map(|data| data.popup_entity);
if let Some(popup_entity) = popup
&& let Some(node) = world.ui.get_ui_layout_node_mut(popup_entity)
{
node.visible = false;
}
if let Some(data) = world.ui.get_ui_context_menu_mut(menu_entity) {
data.open = false;
}
world.resources.retained_ui.overlays.active_context_menu = None;
}
}
pub fn ui_tree_view_set_filter(world: &mut World, tree: freecs::Entity, text: &str) {
let tree_data = if let Some(data) = world.ui.get_ui_tree_view(tree) {
data.clone()
} else {
return;
};
let filter_lower = text.to_lowercase();
let filtering = !filter_lower.is_empty();
if filtering && !tree_data.filter_active {
let mut pre_filter = std::collections::HashMap::new();
for &node_entity in &tree_data.node_entities {
if let Some(nd) = world.ui.get_ui_tree_node(node_entity) {
pre_filter.insert(node_entity, nd.expanded);
}
}
if let Some(data) = world.ui.get_ui_tree_view_mut(tree) {
data.pre_filter_expanded = pre_filter;
}
}
if filtering {
let mut matching_nodes: std::collections::HashSet<freecs::Entity> =
std::collections::HashSet::new();
for &node_entity in &tree_data.node_entities {
if let Some(nd) = world.ui.get_ui_tree_node(node_entity)
&& nd.label.to_lowercase().contains(&filter_lower)
{
matching_nodes.insert(node_entity);
let mut ancestor = nd.parent_node;
while let Some(anc) = ancestor {
matching_nodes.insert(anc);
ancestor = if let Some(anc_data) = world.ui.get_ui_tree_node(anc) {
anc_data.parent_node
} else {
None
};
}
}
}
for &node_entity in &tree_data.node_entities {
let node_info = world
.ui
.get_ui_tree_node(node_entity)
.map(|nd| (nd.wrapper_entity, nd.parent_node.is_some()));
if let Some((wrapper, _has_parent)) = node_info {
let visible = matching_nodes.contains(&node_entity);
if let Some(node) = world.ui.get_ui_layout_node_mut(wrapper) {
node.visible = visible;
}
if visible {
ui_tree_node_set_expanded(world, node_entity, true);
}
}
}
} else if tree_data.filter_active {
let pre_filter = tree_data.pre_filter_expanded.clone();
for &node_entity in &tree_data.node_entities {
let wrapper = world
.ui
.get_ui_tree_node(node_entity)
.map(|nd| nd.wrapper_entity);
if let Some(wrapper) = wrapper
&& let Some(node) = world.ui.get_ui_layout_node_mut(wrapper)
{
node.visible = true;
}
let was_expanded = pre_filter.get(&node_entity).copied().unwrap_or(false);
ui_tree_node_set_expanded(world, node_entity, was_expanded);
}
}
if let Some(data) = world.ui.get_ui_tree_view_mut(tree) {
data.filter_text = text.to_string();
data.filter_active = filtering;
if !filtering {
data.pre_filter_expanded.clear();
}
}
}
pub fn ui_tree_view_clear_filter(world: &mut World, tree: freecs::Entity) {
ui_tree_view_set_filter(world, tree, "");
}
pub fn ui_tree_node_set_expanded(world: &mut World, entity: freecs::Entity, expanded: bool) {
let update = world
.ui
.get_ui_tree_node(entity)
.map(|data| (data.arrow_text_slot, data.children_container));
if let Some((arrow_slot, children)) = update {
world
.resources
.text
.cache
.set_text(arrow_slot, if expanded { "\u{25BC}" } else { "\u{25B6}" });
if let Some(node) = world.ui.get_ui_layout_node_mut(children) {
node.visible = expanded;
}
if let Some(data) = world.ui.get_ui_tree_node_mut(entity) {
data.expanded = expanded;
}
}
}
pub fn ui_tree_node_mark_loaded(world: &mut World, entity: freecs::Entity) {
if let Some(data) = world.ui.get_ui_tree_node_mut(entity) {
data.lazy_loaded = true;
}
}
pub fn ui_tree_view_select_node(world: &mut World, tree: freecs::Entity, node: freecs::Entity) {
let tree_data = if let Some(data) = world.ui.get_ui_tree_view(tree) {
data.clone()
} else {
return;
};
if !tree_data.node_entities.contains(&node) {
return;
}
for prev in &tree_data.selected_nodes {
if *prev == node {
continue;
}
if let Some(prev_node) = world.ui.get_ui_tree_node(*prev).cloned() {
if let Some(color) = world.ui.get_ui_node_color_mut(prev_node.row_entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] = None;
}
if let Some(weights) = world.ui.get_ui_state_weights_mut(prev_node.row_entity) {
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 0.0;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(*prev) {
wd.selected = false;
}
}
}
let row_entity = if let Some(target) = world.ui.get_ui_tree_node(node) {
target.row_entity
} else {
return;
};
let accent = world
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
let selected_bg = Vec4::new(accent.x, accent.y, accent.z, 0.3);
if let Some(color) = world.ui.get_ui_node_color_mut(row_entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] = Some(selected_bg);
}
if let Some(weights) = world.ui.get_ui_state_weights_mut(row_entity) {
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 1.0;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(node) {
wd.selected = true;
}
if let Some(wd) = world.ui.get_ui_tree_view_mut(tree) {
wd.selected_nodes = vec![node];
}
let mut ancestor = world.ui.get_ui_tree_node(node).and_then(|d| d.parent_node);
while let Some(anc) = ancestor {
ui_tree_node_set_expanded(world, anc, true);
ancestor = world.ui.get_ui_tree_node(anc).and_then(|d| d.parent_node);
}
}
pub fn ui_tree_view_clear_selection(world: &mut World, tree: freecs::Entity) {
let selected = if let Some(data) = world.ui.get_ui_tree_view(tree) {
data.selected_nodes.clone()
} else {
return;
};
for prev in selected {
if let Some(prev_node) = world.ui.get_ui_tree_node(prev).cloned() {
if let Some(color) = world.ui.get_ui_node_color_mut(prev_node.row_entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] = None;
}
if let Some(weights) = world.ui.get_ui_state_weights_mut(prev_node.row_entity) {
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 0.0;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(prev) {
wd.selected = false;
}
}
}
if let Some(data) = world.ui.get_ui_tree_view_mut(tree) {
data.selected_nodes.clear();
}
}
pub fn ui_tree_view_scroll_to_node(world: &mut World, tree: freecs::Entity, node: freecs::Entity) {
let (current_offset, node_entities) = if let Some(data) = world.ui.get_ui_tree_view(tree) {
(data.scroll_offset, data.node_entities.clone())
} else {
return;
};
if !node_entities.contains(&node) {
return;
}
let row_entity = if let Some(target) = world.ui.get_ui_tree_node(node) {
target.row_entity
} else {
return;
};
let row_rect = if let Some(layout) = world.ui.get_ui_layout_node(row_entity) {
layout.computed_rect
} else {
return;
};
let view_rect = if let Some(layout) = world.ui.get_ui_layout_node(tree) {
layout.computed_rect
} else {
return;
};
if view_rect.height() <= 0.0 || row_rect.height() <= 0.0 {
return;
}
let dpi_scale = crate::ecs::window::resources::window_scale_factor(world).max(1.0);
let mut scroll_offset = current_offset;
if row_rect.min.y < view_rect.min.y {
scroll_offset = (scroll_offset - (view_rect.min.y - row_rect.min.y) / dpi_scale).max(0.0);
} else if row_rect.max.y > view_rect.max.y {
scroll_offset += (row_rect.max.y - view_rect.max.y) / dpi_scale;
}
if let Some(data) = world.ui.get_ui_tree_view_mut(tree) {
data.scroll_offset = scroll_offset;
}
}
pub fn ui_show_modal(world: &mut World, entity: freecs::Entity) {
let backdrop = world
.ui
.get_ui_modal_dialog(entity)
.map(|data| data.backdrop_entity);
if let Some(backdrop_entity) = backdrop {
if let Some(node) = world.ui.get_ui_layout_node_mut(backdrop_entity) {
node.visible = true;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
node.visible = true;
}
if let Some(data) = world.ui.get_ui_modal_dialog_mut(entity) {
data.result = None;
}
world.resources.retained_ui.overlays.active_modal = Some(entity);
}
}
pub fn ui_widget_content(world: &World, entity: freecs::Entity) -> Option<freecs::Entity> {
if let Some(data) = world.ui.get_ui_collapsing_header(entity) {
return Some(data.content_entity);
}
if let Some(data) = world.ui.get_ui_scroll_area(entity) {
return Some(data.content_entity);
}
if let Some(data) = world.ui.get_ui_panel(entity) {
return Some(data.content_entity);
}
if let Some(data) = world.ui.get_ui_modal_dialog(entity) {
return Some(data.content_entity);
}
if let Some(data) = world.ui.get_ui_tree_view(entity) {
return Some(data.content_entity);
}
if let Some(data) = world.ui.get_ui_tile_container(entity) {
return Some(data.container_entity);
}
Some(entity)
}
pub fn ui_show_command_palette(world: &mut World, entity: freecs::Entity) {
let (backdrop, text_input) = if let Some(data) = world.ui.get_ui_command_palette(entity) {
(data.backdrop_entity, data.text_input_entity)
} else {
return;
};
if let Some(node) = world.ui.get_ui_layout_node_mut(backdrop) {
node.visible = true;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
node.visible = true;
}
ui_text_input_set_value(world, text_input, "");
world
.resources
.retained_ui
.interaction_for_active_mut()
.focused_entity = Some(text_input);
if let Some(data) = world.ui.get_ui_command_palette_mut(entity) {
data.open = true;
data.executed_command = None;
data.filter_text.clear();
data.selected_index = 0;
data.filtered_indices = (0..data.commands.len()).collect();
}
ui_command_palette_rebuild_results(world, entity);
}
pub fn ui_command_palette_register(
world: &mut World,
entity: freecs::Entity,
label: &str,
shortcut: &str,
category: &str,
) {
if let Some(data) = world.ui.get_ui_command_palette_mut(entity) {
data.commands.push(CommandEntry {
label: label.to_string(),
shortcut: shortcut.to_string(),
category: category.to_string(),
enabled: true,
});
data.filtered_indices = (0..data.commands.len()).collect();
}
}
pub fn ui_command_palette_clear(world: &mut World, entity: freecs::Entity) {
if let Some(data) = world.ui.get_ui_command_palette_mut(entity) {
data.commands.clear();
data.filtered_indices.clear();
data.selected_index = 0;
}
}
pub fn ui_command_palette_rebuild_results(world: &mut World, entity: freecs::Entity) {
let data = if let Some(data) = world.ui.get_ui_command_palette(entity) {
data.clone()
} else {
return;
};
for (pool_index, &row_entity) in data.result_entities.iter().enumerate() {
if pool_index < data.filtered_indices.len() {
let cmd_index = data.filtered_indices[pool_index];
let cmd = &data.commands[cmd_index];
let label_slot = data.result_text_slots[pool_index * 2];
let shortcut_slot = data.result_text_slots[pool_index * 2 + 1];
let display = if cmd.category.is_empty() {
cmd.label.clone()
} else {
format!("{}: {}", cmd.category, cmd.label)
};
world.resources.text.cache.set_text(label_slot, &display);
world
.resources
.text
.cache
.set_text(shortcut_slot, &cmd.shortcut);
if let Some(node) = world.ui.get_ui_layout_node_mut(row_entity) {
node.visible = true;
}
} else if let Some(node) = world.ui.get_ui_layout_node_mut(row_entity) {
node.visible = false;
}
}
}
pub fn ui_rich_text_set_span_text(
world: &mut World,
entity: freecs::Entity,
span_index: usize,
text: &str,
) {
if let Some(data) = world.ui.get_ui_rich_text(entity)
&& let Some(&text_slot) = data.span_text_slots.get(span_index)
{
world.resources.text.cache.set_text(text_slot, text);
}
}
pub fn ui_rich_text_set_span_color(
world: &mut World,
entity: freecs::Entity,
span_index: usize,
color: Vec4,
) {
if let Some(data) = world.ui.get_ui_rich_text(entity)
&& let Some(&span_entity) = data.span_entities.get(span_index)
&& let Some(node_color) = world.ui.get_ui_node_color_mut(span_entity)
{
node_color.colors[0] = Some(color);
}
}
pub fn ui_bubbled_events_for(
world: &World,
entity: freecs::Entity,
) -> Vec<crate::ecs::ui::resources::BubbledUiEvent> {
world
.resources
.retained_ui
.frame
.bubbled_events
.iter()
.filter(|event| event.ancestor == entity && !event.stopped)
.cloned()
.collect()
}
fn set_checkable_in_defs(
defs: &mut [crate::ecs::ui::components::ContextMenuItemDef],
command_id: usize,
new_checked: bool,
text_cache: &mut crate::ecs::text::resources::TextCache,
) -> bool {
for def in defs.iter_mut() {
let id = def.command_id;
if let crate::ecs::ui::components::ContextMenuItemKind::Checkable {
checked,
check_text_slot,
} = &mut def.kind
{
if id == Some(command_id) {
if *checked != new_checked {
*checked = new_checked;
text_cache
.set_text(*check_text_slot, if new_checked { "\u{2713}" } else { "" });
}
return true;
}
} else if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } =
&mut def.kind
&& set_checkable_in_defs(children, command_id, new_checked, text_cache)
{
return true;
}
}
false
}
fn find_checkable_in_defs(
defs: &[crate::ecs::ui::components::ContextMenuItemDef],
command_id: usize,
) -> Option<bool> {
for def in defs {
let id = def.command_id;
if let crate::ecs::ui::components::ContextMenuItemKind::Checkable { checked, .. } =
&def.kind
{
if id == Some(command_id) {
return Some(*checked);
}
} else if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } =
&def.kind
&& let Some(found) = find_checkable_in_defs(children, command_id)
{
return Some(found);
}
}
None
}