use std::collections::{HashMap, HashSet};
use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::ui::types::Rect;
use super::data_types::{
CanvasCommand, CharStyle, ColorPickerMode, CommandEntry, ContextMenuItem, ContextMenuItemDef,
DataGridColumn, DataGridPoolRow, InputMask, RangeSliderThumb, ResizeEdge, RichTextSnapshot,
SplitDirection, TextSnapshot, TextSpan, UiPanelKind, UndoStack, VirtualListPoolItem,
};
#[derive(Clone, Debug)]
pub struct UiButtonData {
pub clicked: bool,
pub text_slot: usize,
}
#[derive(Clone, Debug)]
pub struct UiSliderData {
pub min: f32,
pub max: f32,
pub value: f32,
pub changed: bool,
pub fill_entity: freecs::Entity,
pub text_slot: usize,
pub logarithmic: bool,
pub precision: usize,
pub prefix: String,
pub suffix: String,
}
#[derive(Clone, Debug)]
pub struct UiRangeSliderData {
pub min: f32,
pub max: f32,
pub low_value: f32,
pub high_value: f32,
pub changed: bool,
pub fill_entity: freecs::Entity,
pub low_thumb_entity: freecs::Entity,
pub high_thumb_entity: freecs::Entity,
pub text_slot: usize,
pub active_thumb: Option<RangeSliderThumb>,
pub precision: usize,
pub thumb_half_size: f32,
}
#[derive(Clone, Debug)]
pub struct UiToggleData {
pub value: bool,
pub changed: bool,
pub animated_position: f32,
pub knob_entity: freecs::Entity,
}
#[derive(Clone, Debug)]
pub struct UiCheckboxData {
pub value: bool,
pub changed: bool,
pub inner_entity: freecs::Entity,
}
#[derive(Clone, Debug)]
pub struct UiRadioData {
pub group_id: u32,
pub option_index: usize,
pub selected: bool,
pub changed: bool,
pub inner_entity: freecs::Entity,
}
#[derive(Clone, Debug)]
pub struct UiProgressBarData {
pub value: f32,
pub fill_entity: freecs::Entity,
}
#[derive(Clone, Debug)]
pub struct UiCollapsingHeaderData {
pub open: bool,
pub changed: bool,
pub content_entity: freecs::Entity,
pub arrow_text_slot: usize,
}
#[derive(Clone, Debug)]
pub struct UiScrollAreaData {
pub scroll_offset: f32,
pub content_entity: freecs::Entity,
pub content_height: f32,
pub visible_height: f32,
pub thumb_entity: freecs::Entity,
pub track_entity: freecs::Entity,
pub thumb_dragging: bool,
pub thumb_drag_start_offset: f32,
pub snap_interval: Option<f32>,
}
#[derive(Clone, Debug)]
pub struct UiTabBarData {
pub selected_tab: usize,
pub changed: bool,
pub tab_entities: Vec<freecs::Entity>,
pub tab_text_slots: Vec<usize>,
}
#[derive(Clone, Debug)]
pub struct UiBreadcrumbData {
pub segments: Vec<String>,
pub clicked_segment: Option<usize>,
pub changed: bool,
pub segment_entities: Vec<freecs::Entity>,
pub segment_text_slots: Vec<usize>,
}
#[derive(Clone, Debug)]
pub struct UiSplitterData {
pub direction: SplitDirection,
pub ratio: f32,
pub changed: bool,
pub first_pane: freecs::Entity,
pub second_pane: freecs::Entity,
pub divider_entity: freecs::Entity,
pub min_ratio: f32,
pub max_ratio: f32,
}
#[derive(Clone, Debug)]
pub struct UiTextInputData {
pub text: String,
pub cursor_position: usize,
pub selection_start: Option<usize>,
pub changed: bool,
pub text_slot: usize,
pub cursor_entity: freecs::Entity,
pub selection_entity: freecs::Entity,
pub scroll_offset: f32,
pub cursor_blink_timer: f64,
pub placeholder_entity: Option<freecs::Entity>,
pub undo_stack: UndoStack<TextSnapshot>,
pub input_mask: InputMask,
pub max_length: Option<usize>,
}
#[derive(Clone, Debug)]
pub struct UiDropdownData {
pub options: Vec<String>,
pub selected_index: usize,
pub changed: bool,
pub open: bool,
pub header_text_slot: usize,
pub popup_entities: Vec<freecs::Entity>,
pub popup_container_entity: freecs::Entity,
pub hovered_index: Option<usize>,
pub is_theme_dropdown: bool,
pub searchable: bool,
pub filter_text: String,
pub filter_input_entity: Option<freecs::Entity>,
pub filtered_indices: Vec<usize>,
}
#[derive(Clone, Debug)]
pub struct UiMultiSelectData {
pub options: Vec<String>,
pub selected_indices: HashSet<usize>,
pub changed: bool,
pub open: bool,
pub header_text_slot: usize,
pub popup_entities: Vec<freecs::Entity>,
pub popup_container_entity: freecs::Entity,
pub hovered_index: Option<usize>,
pub check_entities: Vec<freecs::Entity>,
}
#[derive(Clone, Debug)]
pub struct UiDatePickerData {
pub year: i32,
pub month: u32,
pub day: u32,
pub changed: bool,
pub open: bool,
pub header_text_slot: usize,
pub popup_entity: freecs::Entity,
pub day_entities: Vec<freecs::Entity>,
pub day_text_slots: Vec<usize>,
pub month_label_slot: usize,
pub prev_month_entity: freecs::Entity,
pub next_month_entity: freecs::Entity,
pub selected_day_entity: Option<freecs::Entity>,
}
#[derive(Clone, Debug)]
pub struct UiMenuData {
pub items: Vec<String>,
pub clicked_item: Option<usize>,
pub open: bool,
pub label_text_slot: usize,
pub popup_entities: Vec<freecs::Entity>,
pub popup_container_entity: freecs::Entity,
}
#[derive(Clone, Debug)]
pub struct UiPanelData {
pub title: String,
pub title_text_slot: usize,
pub content_entity: freecs::Entity,
pub header_entity: freecs::Entity,
pub collapsed: bool,
pub panel_kind: UiPanelKind,
pub focus_order: i32,
pub pinned: bool,
pub min_size: Vec2,
pub drag_offset: Option<Vec2>,
pub resize_edge: Option<ResizeEdge>,
pub resize_start_rect: Option<Rect>,
pub resize_start_mouse: Option<Vec2>,
pub undocked_rect: Option<Rect>,
pub default_dock_size: f32,
pub collapse_button_entity: Option<freecs::Entity>,
pub collapse_button_text_slot: Option<usize>,
pub header_visible: bool,
pub resizable: bool,
}
#[derive(Clone, Debug)]
pub struct UiColorPickerData {
pub color: Vec4,
pub changed: bool,
pub swatch_entity: freecs::Entity,
pub slider_entities: [freecs::Entity; 4],
pub mode: ColorPickerMode,
}
#[derive(Clone, Debug)]
pub struct UiSelectableLabelData {
pub selected: bool,
pub changed: bool,
pub text_slot: usize,
pub group_id: Option<u32>,
}
#[derive(Clone, Debug)]
pub struct UiDragValueData {
pub value: f32,
pub min: f32,
pub max: f32,
pub speed: f32,
pub precision: usize,
pub prefix: String,
pub suffix: String,
pub changed: bool,
pub text_slot: usize,
pub editing: bool,
pub cursor_entity: freecs::Entity,
pub selection_entity: freecs::Entity,
pub edit_text: String,
pub cursor_position: usize,
pub selection_start: Option<usize>,
pub cursor_blink_timer: f64,
pub scroll_offset: f32,
pub drag_start_value: f32,
pub undo_stack: UndoStack<TextSnapshot>,
pub up_entity: Option<freecs::Entity>,
pub down_entity: Option<freecs::Entity>,
pub step: f32,
}
#[derive(Clone, Debug)]
pub struct UiContextMenuData {
pub items: Vec<ContextMenuItem>,
pub open: bool,
pub clicked_item: Option<usize>,
pub popup_entity: freecs::Entity,
pub item_entities: Vec<freecs::Entity>,
pub item_defs: Vec<ContextMenuItemDef>,
}
#[derive(Clone, Debug)]
pub struct UiTreeNodeData {
pub label: String,
pub text_slot: usize,
pub depth: usize,
pub expanded: bool,
pub selected: bool,
pub row_entity: freecs::Entity,
pub arrow_entity: freecs::Entity,
pub arrow_text_slot: usize,
pub children_container: freecs::Entity,
pub user_data: u64,
pub parent_node: Option<freecs::Entity>,
pub wrapper_entity: freecs::Entity,
pub lazy: bool,
pub lazy_loaded: bool,
}
#[derive(Clone, Debug)]
pub struct UiTreeViewData {
pub selected_nodes: Vec<freecs::Entity>,
pub multi_select: bool,
pub node_entities: Vec<freecs::Entity>,
pub changed: bool,
pub content_entity: freecs::Entity,
pub context_menu_node: Option<freecs::Entity>,
pub filter_text: String,
pub filter_active: bool,
pub pre_filter_expanded: HashMap<freecs::Entity, bool>,
}
#[derive(Clone, Debug)]
pub struct UiModalDialogData {
pub title_text_slot: usize,
pub content_entity: freecs::Entity,
pub backdrop_entity: freecs::Entity,
pub ok_button: Option<freecs::Entity>,
pub cancel_button: Option<freecs::Entity>,
pub result: Option<bool>,
}
#[derive(Clone, Debug)]
pub struct UiRichTextData {
pub span_entities: Vec<freecs::Entity>,
pub span_text_slots: Vec<usize>,
}
#[derive(Clone, Debug)]
pub struct UiDataGridData {
pub columns: Vec<DataGridColumn>,
pub row_height: f32,
pub pool_size: usize,
pub total_rows: usize,
pub scroll_entity: freecs::Entity,
pub body_entity: freecs::Entity,
pub header_entities: Vec<freecs::Entity>,
pub header_text_slots: Vec<usize>,
pub top_spacer: freecs::Entity,
pub bottom_spacer: freecs::Entity,
pub pool_rows: Vec<DataGridPoolRow>,
pub visible_start: usize,
pub sort_column: Option<usize>,
pub sort_ascending: bool,
pub sort_changed: bool,
pub selected_rows: HashSet<usize>,
pub selection_anchor: Option<usize>,
pub selection_changed: bool,
pub focused: bool,
pub resize_column: Option<usize>,
pub resize_start_x: f32,
pub resize_start_width: f32,
pub filtered_indices: Option<Vec<usize>>,
pub header_divider_entities: Vec<freecs::Entity>,
pub filter_row_entity: Option<freecs::Entity>,
pub filter_input_entities: Vec<freecs::Entity>,
pub filter_texts: Vec<String>,
pub editing_cell: Option<(usize, usize)>,
pub editing_input_entity: Option<freecs::Entity>,
}
#[derive(Clone, Debug)]
pub struct UiPropertyGridData {
pub label_width: f32,
pub row_entities: Vec<freecs::Entity>,
pub label_entities: Vec<freecs::Entity>,
pub resize_active: bool,
pub resize_start_x: f32,
pub resize_start_width: f32,
}
#[derive(Clone, Debug)]
pub struct UiCommandPaletteData {
pub commands: Vec<CommandEntry>,
pub filter_text: String,
pub filtered_indices: Vec<usize>,
pub selected_index: usize,
pub open: bool,
pub executed_command: Option<usize>,
pub text_input_entity: freecs::Entity,
pub result_entities: Vec<freecs::Entity>,
pub result_text_slots: Vec<usize>,
pub backdrop_entity: freecs::Entity,
pub dialog_entity: freecs::Entity,
pub pool_size: usize,
pub scroll_entity: freecs::Entity,
}
#[derive(Clone, Debug)]
pub struct UiVirtualListData {
pub item_height: f32,
pub pool_size: usize,
pub total_items: usize,
pub visible_start: usize,
pub scroll_entity: freecs::Entity,
pub body_entity: freecs::Entity,
pub top_spacer: freecs::Entity,
pub bottom_spacer: freecs::Entity,
pub pool_items: Vec<VirtualListPoolItem>,
pub selection: Option<usize>,
pub selection_changed: bool,
}
#[derive(Clone, Debug)]
pub struct UiCanvasData {
pub commands: Vec<CanvasCommand>,
pub hit_test_enabled: bool,
pub command_bounds: Vec<(u32, crate::ecs::ui::types::Rect)>,
}
#[derive(Clone, Debug)]
pub struct UiTextAreaData {
pub text: String,
pub cursor_position: usize,
pub selection_start: Option<usize>,
pub changed: bool,
pub text_slot: usize,
pub cursor_entity: freecs::Entity,
pub selection_pool: Vec<freecs::Entity>,
pub scroll_offset_y: f32,
pub cursor_blink_timer: f64,
pub placeholder_entity: Option<freecs::Entity>,
pub line_height: f32,
pub visible_rows: usize,
pub syntax_language: Option<String>,
pub undo_stack: UndoStack<TextSnapshot>,
pub max_length: Option<usize>,
}
#[derive(Clone, Debug)]
pub struct UiRichTextEditorData {
pub text: String,
pub char_styles: Vec<CharStyle>,
pub current_style: CharStyle,
pub cursor_position: usize,
pub selection_start: Option<usize>,
pub changed: bool,
pub text_slot: usize,
pub cursor_entity: freecs::Entity,
pub selection_pool: Vec<freecs::Entity>,
pub scroll_offset_y: f32,
pub cursor_blink_timer: f64,
pub line_height: f32,
pub visible_rows: usize,
pub placeholder_entity: Option<freecs::Entity>,
pub undo_stack: UndoStack<RichTextSnapshot>,
}
impl UiRichTextEditorData {
pub fn spans(&self) -> Vec<TextSpan> {
if self.text.is_empty() {
return Vec::new();
}
let chars: Vec<char> = self.text.chars().collect();
let mut spans = Vec::new();
let mut run_start = 0;
let default_style = CharStyle::default();
for index in 1..=chars.len() {
let current_style = self.char_styles.get(index).unwrap_or(&default_style);
let prev_style = self.char_styles.get(run_start).unwrap_or(&default_style);
if index == chars.len() || current_style != prev_style {
let run_text: String = chars[run_start..index].iter().collect();
let style = self.char_styles.get(run_start).cloned().unwrap_or_default();
let mut span = TextSpan::new(&run_text);
span.bold = style.bold;
span.italic = style.italic;
span.underline = style.underline;
span.color = style.color;
spans.push(span);
run_start = index;
}
}
spans
}
}
#[derive(Clone, Debug)]
pub enum UiWidgetState {
Button(UiButtonData),
Slider(UiSliderData),
Toggle(UiToggleData),
Checkbox(UiCheckboxData),
Radio(UiRadioData),
ProgressBar(UiProgressBarData),
CollapsingHeader(UiCollapsingHeaderData),
ScrollArea(UiScrollAreaData),
TabBar(UiTabBarData),
TextInput(UiTextInputData),
Dropdown(UiDropdownData),
Menu(UiMenuData),
Panel(UiPanelData),
ColorPicker(UiColorPickerData),
SelectableLabel(UiSelectableLabelData),
DragValue(UiDragValueData),
ContextMenu(UiContextMenuData),
TreeView(UiTreeViewData),
TreeNode(UiTreeNodeData),
ModalDialog(UiModalDialogData),
RichText(UiRichTextData),
DataGrid(UiDataGridData),
PropertyGrid(UiPropertyGridData),
CommandPalette(UiCommandPaletteData),
Canvas(UiCanvasData),
TextArea(UiTextAreaData),
TileContainer(super::tiles::UiTileContainerData),
VirtualList(UiVirtualListData),
RichTextEditor(UiRichTextEditorData),
RangeSlider(UiRangeSliderData),
Breadcrumb(UiBreadcrumbData),
Splitter(UiSplitterData),
MultiSelect(UiMultiSelectData),
DatePicker(UiDatePickerData),
}
impl Default for UiWidgetState {
fn default() -> Self {
Self::Button(UiButtonData {
clicked: false,
text_slot: 0,
})
}
}
pub trait FromWidgetState {
fn from_widget_state(state: &UiWidgetState) -> Option<&Self>;
}
macro_rules! impl_from_widget_state {
($variant:ident, $data:ty) => {
impl FromWidgetState for $data {
fn from_widget_state(state: &UiWidgetState) -> Option<&Self> {
match state {
UiWidgetState::$variant(d) => Some(d),
_ => None,
}
}
}
};
}
impl_from_widget_state!(Button, UiButtonData);
impl_from_widget_state!(Slider, UiSliderData);
impl_from_widget_state!(Toggle, UiToggleData);
impl_from_widget_state!(Checkbox, UiCheckboxData);
impl_from_widget_state!(Radio, UiRadioData);
impl_from_widget_state!(ProgressBar, UiProgressBarData);
impl_from_widget_state!(CollapsingHeader, UiCollapsingHeaderData);
impl_from_widget_state!(ScrollArea, UiScrollAreaData);
impl_from_widget_state!(TabBar, UiTabBarData);
impl_from_widget_state!(TextInput, UiTextInputData);
impl_from_widget_state!(Dropdown, UiDropdownData);
impl_from_widget_state!(Menu, UiMenuData);
impl_from_widget_state!(Panel, UiPanelData);
impl_from_widget_state!(ColorPicker, UiColorPickerData);
impl_from_widget_state!(SelectableLabel, UiSelectableLabelData);
impl_from_widget_state!(DragValue, UiDragValueData);
impl_from_widget_state!(ContextMenu, UiContextMenuData);
impl_from_widget_state!(TreeView, UiTreeViewData);
impl_from_widget_state!(TreeNode, UiTreeNodeData);
impl_from_widget_state!(ModalDialog, UiModalDialogData);
impl_from_widget_state!(RichText, UiRichTextData);
impl_from_widget_state!(DataGrid, UiDataGridData);
impl_from_widget_state!(PropertyGrid, UiPropertyGridData);
impl_from_widget_state!(CommandPalette, UiCommandPaletteData);
impl_from_widget_state!(Canvas, UiCanvasData);
impl_from_widget_state!(TextArea, UiTextAreaData);
impl_from_widget_state!(TileContainer, super::tiles::UiTileContainerData);
impl_from_widget_state!(VirtualList, UiVirtualListData);
impl_from_widget_state!(RichTextEditor, UiRichTextEditorData);
impl_from_widget_state!(RangeSlider, UiRangeSliderData);
impl_from_widget_state!(Breadcrumb, UiBreadcrumbData);
impl_from_widget_state!(Splitter, UiSplitterData);
impl_from_widget_state!(MultiSelect, UiMultiSelectData);
impl_from_widget_state!(DatePicker, UiDatePickerData);
impl UiWidgetState {
pub fn changed(&self) -> bool {
match self {
Self::Slider(d) => d.changed,
Self::RangeSlider(d) => d.changed,
Self::Toggle(d) => d.changed,
Self::Checkbox(d) => d.changed,
Self::Radio(d) => d.changed,
Self::TabBar(d) => d.changed,
Self::Breadcrumb(d) => d.changed,
Self::Splitter(d) => d.changed,
Self::TextInput(d) => d.changed,
Self::Dropdown(d) => d.changed,
Self::MultiSelect(d) => d.changed,
Self::DatePicker(d) => d.changed,
Self::ColorPicker(d) => d.changed,
Self::SelectableLabel(d) => d.changed,
Self::DragValue(d) => d.changed,
Self::TreeView(d) => d.changed,
Self::TextArea(d) => d.changed,
Self::RichTextEditor(d) => d.changed,
_ => false,
}
}
pub fn child_entities(&self) -> Vec<freecs::Entity> {
match self {
Self::Button(_) => Vec::new(),
Self::Slider(data) => vec![data.fill_entity],
Self::Toggle(data) => vec![data.knob_entity],
Self::Checkbox(data) => vec![data.inner_entity],
Self::Radio(data) => vec![data.inner_entity],
Self::ProgressBar(data) => vec![data.fill_entity],
Self::CollapsingHeader(data) => vec![data.content_entity],
Self::ScrollArea(data) => {
vec![data.content_entity, data.thumb_entity, data.track_entity]
}
Self::TabBar(data) => data.tab_entities.clone(),
Self::TextInput(data) => {
let mut entities = vec![data.cursor_entity, data.selection_entity];
if let Some(placeholder) = data.placeholder_entity {
entities.push(placeholder);
}
entities
}
Self::Dropdown(data) => {
let mut entities = vec![data.popup_container_entity];
entities.extend(&data.popup_entities);
entities
}
Self::Menu(data) => {
let mut entities = vec![data.popup_container_entity];
entities.extend(&data.popup_entities);
entities
}
Self::Panel(data) => {
let mut entities = vec![data.content_entity, data.header_entity];
if let Some(collapse_btn) = data.collapse_button_entity {
entities.push(collapse_btn);
}
entities
}
Self::ColorPicker(data) => {
let mut entities = vec![data.swatch_entity];
entities.extend(data.slider_entities);
entities
}
Self::SelectableLabel(_) => Vec::new(),
Self::DragValue(data) => vec![data.cursor_entity, data.selection_entity],
Self::ContextMenu(data) => {
let mut entities = vec![data.popup_entity];
entities.extend(&data.item_entities);
entities
}
Self::TreeView(data) => {
let mut entities = vec![data.content_entity];
entities.extend(&data.node_entities);
entities
}
Self::TreeNode(data) => {
vec![
data.row_entity,
data.arrow_entity,
data.children_container,
data.wrapper_entity,
]
}
Self::ModalDialog(data) => {
let mut entities = vec![data.content_entity, data.backdrop_entity];
if let Some(ok) = data.ok_button {
entities.push(ok);
}
if let Some(cancel) = data.cancel_button {
entities.push(cancel);
}
entities
}
Self::RichText(data) => data.span_entities.clone(),
Self::DataGrid(data) => {
let mut entities = vec![
data.scroll_entity,
data.body_entity,
data.top_spacer,
data.bottom_spacer,
];
entities.extend(&data.header_entities);
entities.extend(&data.header_divider_entities);
entities.extend(&data.filter_input_entities);
entities.extend(data.filter_row_entity);
entities.extend(data.editing_input_entity);
for row in &data.pool_rows {
entities.push(row.row_entity);
entities.extend(&row.cell_entities);
}
entities
}
Self::PropertyGrid(data) => {
let mut entities = Vec::new();
entities.extend(&data.row_entities);
entities.extend(&data.label_entities);
entities
}
Self::CommandPalette(data) => {
let mut entities = vec![
data.text_input_entity,
data.backdrop_entity,
data.dialog_entity,
data.scroll_entity,
];
entities.extend(&data.result_entities);
entities
}
Self::Canvas(_) => Vec::new(),
Self::TextArea(data) => {
let mut entities = vec![data.cursor_entity];
entities.extend(&data.selection_pool);
if let Some(placeholder) = data.placeholder_entity {
entities.push(placeholder);
}
entities
}
Self::TileContainer(data) => data.collect_pane_entities(),
Self::VirtualList(data) => {
let mut entities = vec![
data.scroll_entity,
data.body_entity,
data.top_spacer,
data.bottom_spacer,
];
for item in &data.pool_items {
entities.push(item.container_entity);
}
entities
}
Self::RichTextEditor(data) => {
let mut entities = vec![data.cursor_entity];
entities.extend(&data.selection_pool);
entities.extend(data.placeholder_entity);
entities
}
Self::RangeSlider(data) => {
vec![
data.fill_entity,
data.low_thumb_entity,
data.high_thumb_entity,
]
}
Self::Breadcrumb(data) => data.segment_entities.clone(),
Self::Splitter(data) => {
vec![data.first_pane, data.second_pane, data.divider_entity]
}
Self::MultiSelect(data) => {
let mut entities = vec![data.popup_container_entity];
entities.extend(&data.popup_entities);
entities.extend(&data.check_entities);
entities
}
Self::DatePicker(data) => {
let mut entities = vec![
data.popup_entity,
data.prev_month_entity,
data.next_month_entity,
];
entities.extend(&data.day_entities);
entities
}
}
}
}