use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::ui::components::*;
use crate::ecs::ui::layout_types::FlowDirection;
use crate::ecs::ui::state::{UiBase, UiHover};
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::{Ab, Rl};
use crate::render::wgpu::passes::geometry::UiLayer;
impl<'a> UiTreeBuilder<'a> {
pub fn add_modal_dialog(&mut self, title: &str, width: f32, height: f32) -> freecs::Entity {
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let corner_radius = theme.corner_radius;
let border_color = theme.border_color;
let title_slot = self.world_mut().resources.text_cache.add_text(title);
let backdrop_entity = self
.add_node()
.boundary(Rl(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
.with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.5))
.with_layer(UiLayer::Popups)
.with_depth(UiDepthMode::Set(40.0))
.with_interaction()
.with_visible(false)
.done();
let mut content_entity = freecs::Entity::default();
let dialog_entity = self
.add_node()
.window(
Rl(Vec2::new(50.0, 50.0)) + Ab(Vec2::new(-width / 2.0, -height / 2.0)),
Ab(Vec2::new(width, height)),
Anchor::TopLeft,
)
.with_rect(corner_radius, 1.0, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::Background)
.with_layer(UiLayer::Popups)
.with_depth(UiDepthMode::Set(41.0))
.with_visible(false)
.with_children(|tree| {
tree.add_node()
.boundary(
Rl(Vec2::new(0.0, 0.0)),
Ab(Vec2::new(0.0, 36.0)) + Rl(Vec2::new(100.0, 0.0)),
)
.with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_theme_color::<UiBase>(ThemeColor::BackgroundActive)
.without_pointer_events()
.with_children(|tree| {
tree.add_node()
.window(
Ab(Vec2::new(12.0, 18.0)),
Ab(Vec2::new(200.0, 20.0)),
Anchor::CenterLeft,
)
.with_text_slot(title_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Accent)
.without_pointer_events()
.done();
})
.done();
content_entity = tree
.add_node()
.boundary(Ab(Vec2::new(0.0, 36.0)), Rl(Vec2::new(100.0, 100.0)))
.flow(FlowDirection::Vertical, 12.0, 8.0)
.without_pointer_events()
.entity();
})
.done();
self.world_mut().ui.set_ui_widget_state(
dialog_entity,
UiWidgetState::ModalDialog(UiModalDialogData {
title_text_slot: title_slot,
content_entity,
backdrop_entity,
ok_button: None,
cancel_button: None,
result: None,
}),
);
dialog_entity
}
pub fn add_confirm_dialog(&mut self, title: &str, message: &str) -> freecs::Entity {
let dialog_entity = self.add_modal_dialog(title, 350.0, 180.0);
let content = self
.world_mut()
.widget::<UiModalDialogData>(dialog_entity)
.map(|d| d.content_entity)
.unwrap_or(dialog_entity);
let accent_color = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
let ok_button;
let cancel_button;
{
let mut subtree =
crate::ecs::ui::builder::UiTreeBuilder::from_parent(self.world_mut(), content);
subtree.add_label(message);
subtree.add_spacing(8.0);
let button_row = subtree
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 36.0)))
.flow(FlowDirection::Horizontal, 0.0, 8.0)
.without_pointer_events()
.entity();
subtree.push_parent(button_row);
cancel_button = subtree.add_button("Cancel");
ok_button = subtree.add_button_colored("OK", accent_color);
subtree.pop_parent();
if let Some(node) = subtree.world_mut().ui.get_ui_layout_node_mut(cancel_button) {
node.flow_child_size = Some(Rl(Vec2::new(50.0, 0.0)) + Ab(Vec2::new(-4.0, 36.0)));
}
if let Some(node) = subtree.world_mut().ui.get_ui_layout_node_mut(ok_button) {
node.flow_child_size = Some(Rl(Vec2::new(50.0, 0.0)) + Ab(Vec2::new(-4.0, 36.0)));
}
subtree.finish_subtree();
}
if let Some(UiWidgetState::ModalDialog(data)) =
self.world_mut().ui.get_ui_widget_state_mut(dialog_entity)
{
data.ok_button = Some(ok_button);
data.cancel_button = Some(cancel_button);
}
dialog_entity
}
pub fn add_command_palette(&mut self, pool_size: usize) -> freecs::Entity {
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let accent_color = theme.accent_color;
let corner_radius = theme.corner_radius;
let border_color = theme.border_color;
let row_height = font_size * 1.8;
let backdrop_entity = self
.add_node()
.boundary(Rl(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
.with_rect(0.0, 0.0, Vec4::zeros())
.with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.3))
.with_layer(UiLayer::Popups)
.with_depth(UiDepthMode::Set(42.0))
.with_interaction()
.with_visible(false)
.done();
let dialog_width = 500.0f32;
let dialog_height = (pool_size as f32 * row_height) + row_height + 8.0;
let mut text_input_entity = freecs::Entity::default();
let mut scroll_entity = freecs::Entity::default();
let mut result_entities = Vec::with_capacity(pool_size);
let mut result_text_slots = Vec::with_capacity(pool_size);
let dialog_entity = self
.add_node()
.window(
Rl(Vec2::new(50.0, 0.0)) + Ab(Vec2::new(-dialog_width / 2.0, 80.0)),
Ab(Vec2::new(dialog_width, dialog_height)),
Anchor::TopLeft,
)
.with_rect(corner_radius, 1.0, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::Background)
.with_layer(UiLayer::Popups)
.with_depth(UiDepthMode::Set(43.0))
.with_visible(false)
.flow(FlowDirection::Vertical, 0.0, 0.0)
.with_children(|tree| {
text_input_entity = tree.add_text_input("Type a command...");
if let Some(node) = tree
.world_mut()
.ui
.get_ui_layout_node_mut(text_input_entity)
{
node.flow_child_size =
Some(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, row_height)));
}
scroll_entity = tree.add_scroll_area_fill(0.0, 0.0);
let content = tree
.world_mut()
.widget::<UiScrollAreaData>(scroll_entity)
.map(|d| d.content_entity)
.unwrap_or(scroll_entity);
tree.push_parent(content);
for _ in 0..pool_size {
let label_slot = tree.world_mut().resources.text_cache.add_text("");
let shortcut_slot = tree.world_mut().resources.text_cache.add_text("");
let row = tree
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, row_height)))
.with_rect(2.0, 0.0, Vec4::zeros())
.with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_color::<UiHover>(Vec4::new(
accent_color.x,
accent_color.y,
accent_color.z,
0.2,
))
.with_interaction()
.with_transition::<UiHover>(8.0, 6.0)
.with_cursor_icon(winit::window::CursorIcon::Pointer)
.flow(FlowDirection::Horizontal, 8.0, 0.0)
.with_visible(false)
.with_children(|tree| {
tree.add_node()
.flow_child(Rl(Vec2::new(0.0, 100.0)))
.flex_grow(1.0)
.with_text_slot(label_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Text)
.without_pointer_events()
.done();
tree.add_node()
.flow_child(Rl(Vec2::new(0.0, 100.0)))
.auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
.auto_size_padding(Vec2::new(8.0, 0.0))
.with_text_slot(shortcut_slot, font_size * 0.85)
.with_text_alignment(
TextAlignment::Right,
VerticalAlignment::Middle,
)
.with_theme_color::<UiBase>(ThemeColor::TextDisabled)
.without_pointer_events()
.done();
})
.done();
result_entities.push(row);
result_text_slots.push(label_slot);
result_text_slots.push(shortcut_slot);
}
tree.pop_parent();
})
.done();
let palette_entity = dialog_entity;
self.world_mut()
.ui
.set_ui_node_interaction(palette_entity, UiNodeInteraction::default());
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(palette_entity)
{
interaction.accessible_role = Some(AccessibleRole::Dialog);
}
self.world_mut().ui.set_ui_widget_state(
palette_entity,
UiWidgetState::CommandPalette(UiCommandPaletteData {
commands: Vec::new(),
filter_text: String::new(),
filtered_indices: Vec::new(),
selected_index: 0,
open: false,
executed_command: None,
text_input_entity,
result_entities,
result_text_slots,
backdrop_entity,
dialog_entity,
pool_size,
scroll_entity,
}),
);
palette_entity
}
}