use nalgebra_glm::Vec2;
use crate::ecs::ui::components::*;
use crate::ecs::ui::state::{UiBase, UiStateTrait};
impl crate::ecs::world::World {
pub fn ui_clipboard_text(&self) -> &str {
&self.resources.retained_ui.clipboard_text
}
pub fn ui_set_clipboard_text(&mut self, text: impl Into<String>) {
self.resources.retained_ui.clipboard_text = text.into();
}
pub fn ui_events(&self) -> &[crate::ecs::ui::resources::UiEvent] {
&self.resources.retained_ui.frame_events
}
pub fn ui_events_for(
&self,
entity: freecs::Entity,
) -> impl Iterator<Item = &crate::ecs::ui::resources::UiEvent> {
use crate::ecs::ui::resources::UiEvent;
self.resources
.retained_ui
.frame_events
.iter()
.filter(move |event| match event {
UiEvent::ButtonClicked(event_entity)
| UiEvent::SliderChanged {
entity: event_entity,
..
}
| UiEvent::ToggleChanged {
entity: event_entity,
..
}
| UiEvent::CheckboxChanged {
entity: event_entity,
..
}
| UiEvent::RadioChanged {
entity: event_entity,
..
}
| UiEvent::TabChanged {
entity: event_entity,
..
}
| UiEvent::TextInputChanged {
entity: event_entity,
..
}
| UiEvent::TextInputSubmitted {
entity: event_entity,
..
}
| UiEvent::DropdownChanged {
entity: event_entity,
..
}
| UiEvent::MenuItemClicked {
entity: event_entity,
..
}
| UiEvent::ColorPickerChanged {
entity: event_entity,
..
}
| UiEvent::SelectableLabelClicked {
entity: event_entity,
..
}
| UiEvent::DragValueChanged {
entity: event_entity,
..
}
| UiEvent::ContextMenuItemClicked {
entity: event_entity,
..
}
| UiEvent::ModalClosed {
entity: event_entity,
..
}
| UiEvent::CommandPaletteExecuted {
entity: event_entity,
..
} => *event_entity == entity,
UiEvent::TreeNodeSelected {
tree: event_entity, ..
}
| UiEvent::TreeNodeToggled {
tree: event_entity, ..
}
| UiEvent::TreeNodeContextMenu {
tree: event_entity, ..
}
| UiEvent::TreeNodeExpandRequested {
tree: event_entity, ..
} => *event_entity == entity,
UiEvent::TileTabActivated {
container: event_entity,
..
}
| UiEvent::TileTabClosed {
container: event_entity,
..
}
| UiEvent::TileSplitterMoved {
container: event_entity,
..
} => *event_entity == entity,
UiEvent::DragStarted {
source: event_entity,
..
}
| UiEvent::DragCancelled {
source: event_entity,
..
} => *event_entity == entity,
UiEvent::DragDropped {
target: event_entity,
..
}
| UiEvent::CanvasClicked {
entity: event_entity,
..
} => *event_entity == entity,
UiEvent::DragEnter {
target: event_entity,
..
}
| UiEvent::DragOver {
target: event_entity,
..
}
| UiEvent::DragLeave {
target: event_entity,
..
} => *event_entity == entity,
UiEvent::ShortcutTriggered { .. } => false,
UiEvent::VirtualListItemClicked {
entity: event_entity,
..
}
| UiEvent::TextAreaChanged {
entity: event_entity,
..
}
| UiEvent::RichTextEditorChanged {
entity: event_entity,
..
}
| UiEvent::DataGridFilterChanged {
entity: event_entity,
..
}
| UiEvent::RangeSliderChanged {
entity: event_entity,
..
}
| UiEvent::DataGridCellEdited {
entity: event_entity,
..
}
| UiEvent::BreadcrumbClicked {
entity: event_entity,
..
}
| UiEvent::SplitterChanged {
entity: event_entity,
..
}
| UiEvent::MultiSelectChanged {
entity: event_entity,
..
}
| UiEvent::DatePickerChanged {
entity: event_entity,
..
} => *event_entity == entity,
})
}
pub fn ui_despawn_node(&mut self, entity: freecs::Entity) {
if !self.resources.children_cache_valid {
self.validate_and_rebuild_children_cache();
}
let mut stack = vec![entity];
let mut all_entities = Vec::new();
while let Some(current) = stack.pop() {
all_entities.push(current);
if let Some(children) = self.resources.children_cache.get(¤t) {
for child in children {
stack.push(*child);
}
}
}
for descendant in &all_entities {
if let Some(content) = self.ui.get_ui_node_content(*descendant)
&& let crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. } = content
{
let slot = *text_slot;
self.resources.text_cache.remove_text(slot);
}
if let Some(widget) = self.ui.get_ui_widget_state(*descendant).cloned() {
match &widget {
UiWidgetState::Radio(data) => {
if let Some(group) = self
.resources
.retained_ui
.radio_groups
.get_mut(&data.group_id)
{
group.retain(|e| *e != *descendant);
}
}
UiWidgetState::SelectableLabel(data) => {
if let Some(gid) = data.group_id
&& let Some(group) = self
.resources
.retained_ui
.selectable_label_groups
.get_mut(&gid)
{
group.retain(|e| *e != *descendant);
}
}
_ => {}
}
let slots: Vec<usize> = match &widget {
UiWidgetState::Button(data) => vec![data.text_slot],
UiWidgetState::Slider(data) => vec![data.text_slot],
UiWidgetState::TextInput(data) => vec![data.text_slot],
UiWidgetState::CollapsingHeader(data) => vec![data.arrow_text_slot],
UiWidgetState::Dropdown(data) => vec![data.header_text_slot],
UiWidgetState::Menu(data) => vec![data.label_text_slot],
UiWidgetState::Panel(data) => {
let mut s = vec![data.title_text_slot];
if let Some(slot) = data.collapse_button_text_slot {
s.push(slot);
}
s
}
UiWidgetState::SelectableLabel(data) => vec![data.text_slot],
UiWidgetState::DragValue(data) => vec![data.text_slot],
UiWidgetState::TreeNode(data) => {
vec![data.text_slot, data.arrow_text_slot]
}
UiWidgetState::ModalDialog(data) => vec![data.title_text_slot],
UiWidgetState::TabBar(data) => data.tab_text_slots.clone(),
UiWidgetState::TextArea(data) => vec![data.text_slot],
UiWidgetState::RichText(data) => data.span_text_slots.clone(),
UiWidgetState::DataGrid(data) => {
let mut s = data.header_text_slots.clone();
for row in &data.pool_rows {
s.extend_from_slice(&row.cell_text_slots);
}
s
}
UiWidgetState::CommandPalette(data) => data.result_text_slots.clone(),
UiWidgetState::RichTextEditor(data) => vec![data.text_slot],
UiWidgetState::Breadcrumb(data) => data.segment_text_slots.clone(),
UiWidgetState::MultiSelect(data) => vec![data.header_text_slot],
UiWidgetState::DatePicker(data) => {
let mut s = vec![data.header_text_slot, data.month_label_slot];
s.extend_from_slice(&data.day_text_slots);
s
}
_ => vec![],
};
for slot in slots {
self.resources.text_cache.remove_text(slot);
}
}
}
crate::ecs::world::commands::despawn_recursive_immediate(self, entity);
self.resources.children_cache_valid = false;
self.resources.retained_ui.layout_dirty = true;
}
pub fn ui_clear_children(&mut self, parent: freecs::Entity) {
if !self.resources.children_cache_valid {
self.validate_and_rebuild_children_cache();
}
let children: Vec<freecs::Entity> = self
.resources
.children_cache
.get(&parent)
.cloned()
.unwrap_or_default();
for child in children {
self.ui_despawn_node(child);
}
self.resources.retained_ui.layout_dirty = true;
}
pub fn ui_rebuild_children(
&mut self,
parent: freecs::Entity,
build: impl FnOnce(&mut crate::ecs::ui::builder::UiTreeBuilder),
) {
self.ui_clear_children(parent);
let content = self.ui_widget_content(parent).unwrap_or(parent);
let mut builder = crate::ecs::ui::builder::UiTreeBuilder::from_parent(self, content);
build(&mut builder);
builder.finish_subtree();
}
pub fn ui_focus(&mut self, entity: freecs::Entity) {
self.resources.retained_ui.focused_entity = Some(entity);
}
pub fn ui_set_disabled(&mut self, entity: freecs::Entity, disabled: bool) {
if let Some(interaction) = self.ui.get_ui_node_interaction_mut(entity) {
interaction.disabled = disabled;
}
if disabled {
let base_color = self
.ui
.get_ui_node_color(entity)
.and_then(|color| color.colors[UiBase::INDEX]);
if let Some(base) = base_color
&& let Some(color_comp) = self.ui.get_ui_node_color_mut(entity)
{
color_comp.colors[crate::ecs::ui::state::UiDisabled::INDEX] = Some(
nalgebra_glm::Vec4::new(base.x, base.y, base.z, base.w * 0.4),
);
}
} else if let Some(color_comp) = self.ui.get_ui_node_color_mut(entity) {
color_comp.colors[crate::ecs::ui::state::UiDisabled::INDEX] = None;
}
}
pub fn ui_set_disabled_recursive(&mut self, entity: freecs::Entity, disabled: bool) {
if !self.resources.children_cache_valid {
self.validate_and_rebuild_children_cache();
}
let mut stack = vec![entity];
while let Some(current) = stack.pop() {
self.ui_set_disabled(current, disabled);
if let Some(children) = self.resources.children_cache.get(¤t) {
for child in children {
stack.push(*child);
}
}
}
}
pub fn ui_is_disabled(&self, entity: freecs::Entity) -> bool {
self.ui
.get_ui_node_interaction(entity)
.is_some_and(|interaction| interaction.disabled)
}
pub fn ui_set_test_id(&mut self, entity: freecs::Entity, test_id: &str) {
if self.ui.get_ui_node_interaction(entity).is_none() {
self.ui.set_ui_node_interaction(
entity,
crate::ecs::ui::components::UiNodeInteraction::default(),
);
}
if let Some(interaction) = self.ui.get_ui_node_interaction_mut(entity) {
interaction.test_id = Some(test_id.to_string());
}
self.resources
.retained_ui
.test_id_map
.insert(test_id.to_string(), entity);
}
pub fn ui_set_visible(&mut self, entity: freecs::Entity, visible: bool) {
if let Some(node) = self.ui.get_ui_layout_node_mut(entity) {
if let Some(animation) = &mut node.animation {
if visible {
if animation.intro.is_some() {
animation.phase =
crate::ecs::ui::components::UiAnimationPhase::IntroPlaying;
animation.progress = 0.0;
}
node.visible = true;
} else if animation.outro.is_some() {
animation.phase = crate::ecs::ui::components::UiAnimationPhase::OutroPlaying;
animation.progress = 0.0;
} else {
animation.phase = crate::ecs::ui::components::UiAnimationPhase::Idle;
node.visible = false;
}
} else {
node.visible = visible;
}
}
}
pub fn ui_set_visible_exclusive(
&mut self,
entities: &[freecs::Entity],
active: freecs::Entity,
) {
for &entity in entities {
self.ui_set_visible(entity, entity == active);
}
}
pub fn ui_node_visible(&self, entity: freecs::Entity) -> bool {
self.ui
.get_ui_layout_node(entity)
.is_some_and(|node| node.visible)
}
pub fn ui_node_effectively_visible(&self, entity: freecs::Entity) -> bool {
let Some(node) = self.ui.get_ui_layout_node(entity) else {
return false;
};
if !node.visible {
return false;
}
let mut current = entity;
while let Some(crate::ecs::transform::components::Parent(Some(parent))) =
self.core.get_parent(current)
{
let parent = *parent;
if let Some(parent_node) = self.ui.get_ui_layout_node(parent)
&& !parent_node.visible
{
return false;
}
current = parent;
}
true
}
pub fn ui_rect(&self, entity: freecs::Entity) -> Option<crate::ecs::ui::types::Rect> {
self.ui
.get_ui_layout_node(entity)
.map(|node| node.computed_rect)
}
pub fn ui_size(&self, entity: freecs::Entity) -> Option<Vec2> {
self.ui
.get_ui_layout_node(entity)
.map(|node| node.computed_rect.size())
}
pub fn ui_set_text(&mut self, entity: freecs::Entity, text: &str) {
let slot = self.ui_text_slot_for_entity(entity);
if let Some(text_slot) = slot {
self.resources.text_cache.set_text(text_slot, text);
}
}
pub fn ui_set_label_text(&mut self, entity: freecs::Entity, text: &str) {
self.ui_set_text(entity, text);
}
pub fn ui_label_text(&self, entity: freecs::Entity) -> Option<String> {
self.ui_text_slot_for_entity(entity)
.and_then(|slot| self.resources.text_cache.get_text(slot).map(String::from))
}
pub fn ui_text_slot_for_entity(&self, entity: freecs::Entity) -> Option<usize> {
if let Some(crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. }) =
self.ui.get_ui_node_content(entity)
{
return Some(*text_slot);
}
match self.ui.get_ui_widget_state(entity) {
Some(UiWidgetState::Button(data)) => Some(data.text_slot),
Some(UiWidgetState::Slider(data)) => Some(data.text_slot),
Some(UiWidgetState::TextInput(data)) => Some(data.text_slot),
Some(UiWidgetState::SelectableLabel(data)) => Some(data.text_slot),
Some(UiWidgetState::DragValue(data)) => Some(data.text_slot),
_ => None,
}
}
pub fn ui_set_tooltip_entity(
&mut self,
entity: freecs::Entity,
tooltip_entity: Option<freecs::Entity>,
) {
if let Some(interaction) = self.ui.get_ui_node_interaction_mut(entity) {
interaction.tooltip_entity = tooltip_entity;
}
}
pub fn ui_set_tooltip_text(&mut self, entity: freecs::Entity, text: Option<&str>) {
if let Some(interaction) = self.ui.get_ui_node_interaction_mut(entity) {
interaction.tooltip_text = text.map(String::from);
}
}
}