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::{FlowAlignment, FlowDirection};
use crate::ecs::ui::state::{UiBase, UiHover, UiStateTrait};
use crate::ecs::ui::types::{Anchor, Rect};
use crate::ecs::ui::units::{Ab, Rl};
use super::tile_builder::TileBuilder;
impl<'a> UiTreeBuilder<'a> {
pub fn add_property_grid(&mut self, label_width: f32) -> freecs::Entity {
let grid_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
.flow(FlowDirection::Vertical, 4.0, 2.0)
.with_interaction()
.entity();
self.world_mut().ui.set_ui_widget_state(
grid_entity,
UiWidgetState::PropertyGrid(UiPropertyGridData {
label_width,
row_entities: Vec::new(),
label_entities: Vec::new(),
resize_active: false,
resize_start_x: 0.0,
resize_start_width: 0.0,
}),
);
if let Some(interaction) = self.world_mut().ui.get_ui_node_interaction_mut(grid_entity) {
interaction.accessible_role = Some(AccessibleRole::Grid);
}
grid_entity
}
pub fn add_property_row(
&mut self,
grid: freecs::Entity,
parent: freecs::Entity,
label: &str,
) -> freecs::Entity {
let label_width = if let Some(UiWidgetState::PropertyGrid(data)) =
self.world_mut().ui.get_ui_widget_state(grid)
{
data.label_width
} else {
100.0
};
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let row_height = theme.button_height;
let label_slot = self.world_mut().resources.text_cache.add_text(label);
let row_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, row_height)))
.flow_with_alignment(
FlowDirection::Horizontal,
4.0,
4.0,
FlowAlignment::Start,
FlowAlignment::Center,
)
.without_pointer_events()
.entity();
if let Some(p) = self.world_mut().core.get_parent_mut(row_entity) {
*p = crate::ecs::transform::components::Parent(Some(parent));
}
self.world_mut().resources.children_cache_valid = false;
self.push_parent(row_entity);
let label_entity = self
.add_node()
.flow_child(Ab(Vec2::new(label_width, row_height)))
.with_text_slot(label_slot, font_size * 0.9)
.with_text_alignment(TextAlignment::Right, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::TextDisabled)
.without_pointer_events()
.done();
let widget_area = self
.add_node()
.flow_child(Ab(Vec2::new(0.0, row_height)))
.flex_grow(1.0)
.flow_with_alignment(
FlowDirection::Horizontal,
0.0,
4.0,
FlowAlignment::Start,
FlowAlignment::Center,
)
.without_pointer_events()
.entity();
self.pop_parent();
if let Some(UiWidgetState::PropertyGrid(data)) =
self.world_mut().ui.get_ui_widget_state_mut(grid)
{
data.row_entities.push(row_entity);
data.label_entities.push(label_entity);
}
widget_area
}
pub fn add_property_slider(
&mut self,
grid: freecs::Entity,
parent: freecs::Entity,
label: &str,
min: f32,
max: f32,
initial: f32,
) -> freecs::Entity {
let widget_area = self.add_property_row(grid, parent, label);
self.push_parent(widget_area);
let widget = self.add_slider(min, max, initial);
self.pop_parent();
widget
}
pub fn add_property_toggle(
&mut self,
grid: freecs::Entity,
parent: freecs::Entity,
label: &str,
initial: bool,
) -> freecs::Entity {
let widget_area = self.add_property_row(grid, parent, label);
self.push_parent(widget_area);
let widget = self.add_toggle(initial);
self.pop_parent();
widget
}
pub fn add_property_text_input(
&mut self,
grid: freecs::Entity,
parent: freecs::Entity,
label: &str,
placeholder: &str,
) -> freecs::Entity {
let widget_area = self.add_property_row(grid, parent, label);
self.push_parent(widget_area);
let widget = self.add_text_input(placeholder);
self.pop_parent();
widget
}
pub fn add_property_dropdown(
&mut self,
grid: freecs::Entity,
parent: freecs::Entity,
label: &str,
options: &[&str],
initial: usize,
) -> freecs::Entity {
let widget_area = self.add_property_row(grid, parent, label);
self.push_parent(widget_area);
let widget = self.add_dropdown(options, initial);
self.pop_parent();
widget
}
pub fn add_property_checkbox(
&mut self,
grid: freecs::Entity,
parent: freecs::Entity,
label: &str,
initial: bool,
) -> freecs::Entity {
let widget_area = self.add_property_row(grid, parent, label);
self.push_parent(widget_area);
let widget = self.add_checkbox(label, initial);
self.pop_parent();
widget
}
pub fn add_property_drag_value(
&mut self,
grid: freecs::Entity,
parent: freecs::Entity,
label: &str,
min: f32,
max: f32,
initial: f32,
) -> freecs::Entity {
let widget_area = self.add_property_row(grid, parent, label);
self.push_parent(widget_area);
let widget = self.add_drag_value(min, max, initial);
self.pop_parent();
widget
}
pub fn add_rich_text(&mut self, spans: &[TextSpan]) -> freecs::Entity {
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let text_color = theme.text_color;
let container = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)))
.auto_size(crate::ecs::ui::components::AutoSizeMode::Height)
.flow(FlowDirection::Horizontal, 0.0, 0.0)
.flow_wrap()
.without_pointer_events()
.entity();
self.push_parent(container);
let mut span_entities = Vec::with_capacity(spans.len());
let mut span_text_slots = Vec::with_capacity(spans.len());
for span in spans {
let text_slot = self.world_mut().resources.text_cache.add_text(&span.text);
let mut span_font_size = span.font_size_override.unwrap_or(font_size);
let span_color = span.color.unwrap_or(text_color);
let span_font_index = span.font_index.unwrap_or(0);
if span.bold {
span_font_size *= 1.05;
}
let outline_width = if span.bold { 0.4 } else { 0.0 };
let outline_color = if span.bold {
span_color
} else {
Vec4::new(0.0, 0.0, 0.0, 0.0)
};
let span_wrapper = self
.add_node()
.flow_child(Ab(Vec2::new(0.0, span_font_size * 1.5)))
.auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
.flow(FlowDirection::Vertical, 0.0, 0.0)
.without_pointer_events()
.entity();
self.push_parent(span_wrapper);
let entity = self.add_node().done();
if let Some(content) = self.world_mut().ui.get_ui_node_content_mut(entity) {
*content = crate::ecs::ui::components::UiNodeContent::Text {
text_slot,
font_index: span_font_index,
font_size_override: Some(span_font_size),
outline_color,
outline_width,
alignment: TextAlignment::Left,
vertical_alignment: VerticalAlignment::Middle,
overflow: crate::ecs::ui::components::TextOverflow::default(),
monospace_width: None,
};
}
if let Some(node) = self.world_mut().ui.get_ui_layout_node_mut(entity) {
node.flow_child_size = Some(Ab(Vec2::new(0.0, span_font_size * 1.5)).into());
node.auto_size = crate::ecs::ui::components::AutoSizeMode::Width;
node.pointer_events = false;
}
if let Some(color) = self.world_mut().ui.get_ui_node_color_mut(entity) {
color.colors[UiBase::INDEX] = Some(span_color);
}
if span.color.is_none() {
let mut binding = crate::ecs::ui::components::UiThemeBinding::default();
binding.color_roles[UiBase::INDEX] = Some(ThemeColor::Text);
self.world_mut().ui.set_ui_theme_binding(entity, binding);
}
if span.underline {
if span.color.is_none() {
self.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 1.0)))
.with_rect(0.0, 0.0, Vec4::zeros())
.with_theme_color::<UiBase>(ThemeColor::Text)
.without_pointer_events()
.done();
} else {
self.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 1.0)))
.with_rect(0.0, 0.0, Vec4::zeros())
.with_color::<UiBase>(span_color)
.without_pointer_events()
.done();
}
}
self.pop_parent();
span_entities.push(span_wrapper);
span_text_slots.push(text_slot);
}
self.pop_parent();
self.world_mut().ui.set_ui_widget_state(
container,
UiWidgetState::RichText(UiRichTextData {
span_entities,
span_text_slots,
}),
);
container
}
pub fn add_property_section(&mut self, parent: freecs::Entity, label: &str) -> freecs::Entity {
let header = self.add_collapsing_header(label, true);
let reparent_entity = self
.world_mut()
.core
.get_parent(header)
.and_then(|p| p.0)
.unwrap_or(header);
if let Some(p) = self.world_mut().core.get_parent_mut(reparent_entity) {
*p = crate::ecs::transform::components::Parent(Some(parent));
}
self.world_mut().resources.children_cache_valid = false;
self.world_mut()
.widget::<UiCollapsingHeaderData>(header)
.map(|d| d.content_entity)
.unwrap_or(header)
}
pub fn add_table(&mut self, headers: &[&str], widths: &[f32]) -> freecs::Entity {
let columns: Vec<DataGridColumn> = headers
.iter()
.zip(widths.iter())
.map(|(header, width)| DataGridColumn::new(header, *width))
.collect();
self.add_data_grid(&columns, 20)
}
pub fn add_data_grid(
&mut self,
columns: &[DataGridColumn],
pool_size: usize,
) -> freecs::Entity {
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let row_height = font_size * 1.8;
let total_width: f32 = columns.iter().map(|c| c.width).sum();
let root = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 100.0)))
.flow(FlowDirection::Vertical, 0.0, 0.0)
.with_rect(0.0, 0.0, Vec4::zeros())
.with_theme_color::<UiBase>(ThemeColor::Panel)
.entity();
self.push_parent(root);
let header_row = self
.add_node()
.flow_child(Ab(Vec2::new(total_width, row_height)))
.flow(FlowDirection::Horizontal, 0.0, 0.0)
.with_rect(0.0, 0.0, Vec4::zeros())
.with_theme_color::<UiBase>(ThemeColor::PanelHeader)
.without_pointer_events()
.entity();
self.push_parent(header_row);
let mut header_entities = Vec::with_capacity(columns.len());
let mut header_text_slots = Vec::with_capacity(columns.len());
for column in columns {
let text_slot = self
.world_mut()
.resources
.text_cache
.add_text(&column.label);
header_text_slots.push(text_slot);
let header_cell = if column.sortable {
let entity = self
.add_node()
.flow_child(Ab(Vec2::new(column.width, row_height)))
.with_text_slot(text_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Text)
.with_theme_color::<UiHover>(ThemeColor::TextAccent)
.with_interaction()
.entity();
if let Some(interaction) = self.world_mut().ui.get_ui_node_interaction_mut(entity) {
interaction.cursor_icon = Some(winit::window::CursorIcon::Pointer);
}
entity
} else {
self.add_node()
.flow_child(Ab(Vec2::new(column.width, row_height)))
.with_text_slot(text_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Text)
.without_pointer_events()
.done()
};
header_entities.push(header_cell);
}
self.pop_parent();
let scroll = self.add_scroll_area_fill(0.0, 0.0);
let scroll_entity = scroll;
let body_entity = self
.world_mut()
.widget::<UiScrollAreaData>(scroll)
.map(|d| d.content_entity)
.unwrap_or(scroll);
self.push_parent(body_entity);
let top_spacer = self
.add_node()
.flow_child(Ab(Vec2::new(total_width, 0.0)))
.without_pointer_events()
.done();
let mut pool_rows = Vec::with_capacity(pool_size);
let accent_color = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
let selected_bg = Vec4::new(accent_color.x, accent_color.y, accent_color.z, 0.3);
let has_editable = columns.iter().any(|c| c.editable);
for _ in 0..pool_size {
let mut row_builder = self
.add_node()
.flow_child(Ab(Vec2::new(total_width, row_height)))
.flow(FlowDirection::Horizontal, 0.0, 0.0)
.with_rect(0.0, 0.0, Vec4::zeros())
.with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_color::<crate::ecs::ui::state::UiSelected>(selected_bg)
.with_interaction();
if has_editable {
row_builder = row_builder.with_cursor_icon(winit::window::CursorIcon::Cell);
}
let row_entity = row_builder.entity();
self.push_parent(row_entity);
let mut cell_text_slots = Vec::with_capacity(columns.len());
let mut cell_entities = Vec::with_capacity(columns.len());
for column in columns {
let text_slot = self.world_mut().resources.text_cache.add_text("");
let cell_entity = self
.add_node()
.flow_child(Ab(Vec2::new(column.width, row_height)))
.with_text_slot(text_slot, font_size)
.with_text_alignment(column.alignment, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Text)
.without_pointer_events()
.entity();
cell_text_slots.push(text_slot);
cell_entities.push(cell_entity);
}
self.pop_parent();
if let Some(node) = self.world_mut().ui.get_ui_layout_node_mut(row_entity) {
node.visible = false;
}
pool_rows.push(DataGridPoolRow {
row_entity,
cell_text_slots,
cell_entities,
});
}
let bottom_spacer = self
.add_node()
.flow_child(Ab(Vec2::new(total_width, 0.0)))
.without_pointer_events()
.done();
self.pop_parent();
self.pop_parent();
self.push_parent(root);
let editing_input_entity = self.add_text_input("");
if let Some(node) = self
.world_mut()
.ui
.get_ui_layout_node_mut(editing_input_entity)
{
node.visible = false;
node.flow_child_size = None;
node.layouts[crate::ecs::ui::state::UiBase::INDEX] =
Some(crate::ecs::ui::layout_types::UiLayoutType::Window(
crate::ecs::ui::layout_types::WindowLayout {
position: Ab(Vec2::new(0.0, 0.0)).into(),
size: Ab(Vec2::new(100.0, row_height)).into(),
anchor: crate::ecs::ui::types::Anchor::TopLeft,
},
));
}
self.pop_parent();
self.world_mut().ui.set_ui_widget_state(
root,
UiWidgetState::DataGrid(UiDataGridData {
columns: columns.to_vec(),
row_height,
pool_size,
total_rows: 0,
scroll_entity,
body_entity,
header_entities,
header_text_slots,
top_spacer,
bottom_spacer,
pool_rows,
visible_start: 0,
sort_column: None,
sort_ascending: true,
sort_changed: false,
selected_rows: std::collections::HashSet::new(),
selection_anchor: None,
selection_changed: false,
focused: false,
resize_column: None,
resize_start_x: 0.0,
resize_start_width: 0.0,
filtered_indices: None,
header_divider_entities: Vec::new(),
filter_row_entity: None,
filter_input_entities: Vec::new(),
filter_texts: Vec::new(),
editing_cell: None,
editing_input_entity: Some(editing_input_entity),
}),
);
if let Some(interaction) = self.world_mut().ui.get_ui_node_interaction_mut(root) {
interaction.accessible_role = Some(AccessibleRole::Grid);
}
root
}
pub fn add_tile_container(&mut self, size: Vec2) -> freecs::Entity {
let container = self
.add_node()
.window(Ab(Vec2::new(0.0, 0.0)), Ab(size), Anchor::TopLeft)
.with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_theme_color::<UiBase>(ThemeColor::Panel)
.with_clip()
.done();
let root_tabs = TileNode::Tabs {
panes: Vec::new(),
active: 0,
};
let tiles: Vec<Option<TileNode>> = vec![Some(root_tabs)];
let data = UiTileContainerData {
tiles,
root: TileId(0),
rects: vec![Rect::default()],
dragging_splitter: None,
pending_tab_drag: None,
dragging_tab: None,
drop_preview: None,
container_entity: container,
splitter_width: 4.0,
tab_bar_height: 26.0,
next_free: Vec::new(),
hovered_close: None,
};
self.world_mut()
.ui
.set_ui_widget_state(container, UiWidgetState::TileContainer(data));
container
}
pub fn build_tiles(
&mut self,
container: freecs::Entity,
f: impl FnOnce(&mut TileBuilder<'_, 'a>),
) {
let mut builder = TileBuilder {
tree: self,
container,
};
f(&mut builder);
}
}