use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::ui::components::*;
use crate::ecs::ui::state::{UiBase, UiStateTrait};
use crate::ecs::ui::units::Rl;
use super::range_slider_visuals::{RangeSliderVisualUpdate, update_range_slider_visuals};
impl crate::ecs::world::World {
pub fn measure_ui_text_width(&self, text: &str, font_size: f32) -> f32 {
let best_idx = self
.resources
.text_cache
.font_manager
.best_bitmap_font_for_size(font_size);
let font_arc = self
.resources
.text_cache
.font_manager
.get_bitmap_font_arc(best_idx);
if let Some(atlas) = font_arc {
crate::ecs::ui::widget_systems::measure_text_width(&atlas, text, font_size)
} else {
0.0
}
}
pub fn widget<T: crate::ecs::ui::components::FromWidgetState>(
&self,
entity: freecs::Entity,
) -> Option<&T> {
T::from_widget_state(self.ui.get_ui_widget_state(entity)?)
}
pub fn ui_changed(&self, entity: freecs::Entity) -> bool {
self.ui
.get_ui_widget_state(entity)
.is_some_and(|s| s.changed())
}
pub fn ui_button_set_text(&mut self, entity: freecs::Entity, text: &str) {
if let Some(UiWidgetState::Button(data)) = self.ui.get_ui_widget_state(entity) {
let slot = data.text_slot;
self.resources.text_cache.set_text(slot, text);
}
}
pub fn ui_slider_set_value(&mut self, entity: freecs::Entity, value: f32) {
let update = if let Some(UiWidgetState::Slider(data)) = self.ui.get_ui_widget_state(entity)
{
let normalized = if data.logarithmic && data.min > 0.0 && data.max > data.min {
((value / data.min).ln() / (data.max / data.min).ln()).clamp(0.0, 1.0)
} else {
((value - data.min) / (data.max - data.min)).clamp(0.0, 1.0)
};
Some((
data.fill_entity,
data.text_slot,
data.min,
data.max,
normalized,
data.logarithmic,
))
} else {
None
};
if let Some((fill_entity, text_slot, min, max, normalized, logarithmic)) = update {
let clamped = if logarithmic && min > 0.0 && max > min {
min * (max / min).powf(normalized)
} else {
min + normalized * (max - min)
};
if let Some(fill_node) = self.ui.get_ui_layout_node_mut(fill_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary)) =
fill_node.layouts[UiBase::INDEX].as_mut()
{
boundary.position_2 = Rl(Vec2::new(normalized * 100.0, 100.0)).into();
}
let precision = if logarithmic { 3 } else { 1 };
self.resources
.text_cache
.set_text(text_slot, format!("{:.prec$}", clamped, prec = precision));
if let Some(UiWidgetState::Slider(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.value = clamped;
}
}
}
pub fn ui_range_slider_set_values(&mut self, entity: freecs::Entity, low: f32, high: f32) {
let update =
if let Some(UiWidgetState::RangeSlider(data)) = self.ui.get_ui_widget_state(entity) {
let range = data.max - data.min;
if range.abs() > f32::EPSILON {
let low_n = ((low - data.min) / range).clamp(0.0, 1.0);
let high_n = ((high - data.min) / range).clamp(0.0, 1.0);
Some((
data.fill_entity,
data.low_thumb_entity,
data.high_thumb_entity,
data.text_slot,
data.precision,
data.thumb_half_size,
low_n,
high_n,
data.min + low_n * range,
data.min + high_n * range,
))
} else {
None
}
} else {
None
};
if let Some((
fill_entity,
low_thumb,
high_thumb,
text_slot,
precision,
thumb_half,
low_n,
high_n,
clamped_low,
clamped_high,
)) = update
{
update_range_slider_visuals(
self,
&RangeSliderVisualUpdate {
fill_entity,
low_thumb,
high_thumb,
text_slot,
precision,
low_normalized: low_n,
high_normalized: high_n,
low_value: clamped_low,
high_value: clamped_high,
thumb_half,
},
);
if let Some(UiWidgetState::RangeSlider(data)) = self.ui.get_ui_widget_state_mut(entity)
{
data.low_value = clamped_low;
data.high_value = clamped_high;
}
}
}
pub fn ui_breadcrumb_set_segments(&mut self, entity: freecs::Entity, segments: &[&str]) {
let text_slots: Vec<usize> = if let Some(UiWidgetState::Breadcrumb(data)) =
self.ui.get_ui_widget_state_mut(entity)
{
data.segments = segments.iter().map(|s| s.to_string()).collect();
data.segment_text_slots.clone()
} else {
return;
};
for (index, text_slot) in text_slots.iter().enumerate() {
if index < segments.len() {
self.resources
.text_cache
.set_text(*text_slot, segments[index]);
}
}
}
pub fn ui_toggle_set_value(&mut self, entity: freecs::Entity, value: bool) {
if let Some(UiWidgetState::Toggle(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.value = value;
}
}
pub fn ui_radio_group_value(&self, group_id: u32) -> Option<usize> {
let entities: Vec<freecs::Entity> = self
.ui
.query_entities(crate::ecs::world::UI_WIDGET_STATE)
.collect();
for entity in entities {
if let Some(UiWidgetState::Radio(data)) = self.ui.get_ui_widget_state(entity)
&& data.group_id == group_id
&& data.selected
{
return Some(data.option_index);
}
}
None
}
pub fn ui_progress_bar_set_value(&mut self, entity: freecs::Entity, value: f32) {
let update =
if let Some(UiWidgetState::ProgressBar(data)) = self.ui.get_ui_widget_state(entity) {
Some(data.fill_entity)
} else {
None
};
if let Some(fill_entity) = update {
let normalized = value.clamp(0.0, 1.0);
if let Some(fill_node) = self.ui.get_ui_layout_node_mut(fill_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary)) =
fill_node.layouts[UiBase::INDEX].as_mut()
{
boundary.position_2 = Rl(Vec2::new(normalized * 100.0, 100.0)).into();
}
if let Some(UiWidgetState::ProgressBar(data)) = self.ui.get_ui_widget_state_mut(entity)
{
data.value = normalized;
}
}
}
pub fn ui_scroll_area_set_offset(&mut self, entity: freecs::Entity, offset: f32) {
if let Some(UiWidgetState::ScrollArea(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.scroll_offset = offset;
}
}
pub fn ui_scroll_area_set_snap(&mut self, entity: freecs::Entity, interval: Option<f32>) {
if let Some(UiWidgetState::ScrollArea(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.snap_interval = interval;
}
}
pub fn ui_virtual_list_set_count(&mut self, entity: freecs::Entity, count: usize) {
if let Some(UiWidgetState::VirtualList(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.total_items = count;
}
}
pub fn ui_virtual_list_selection_changed(&self, entity: freecs::Entity) -> bool {
if let Some(UiWidgetState::VirtualList(data)) = self.ui.get_ui_widget_state(entity) {
data.selection_changed
} else {
false
}
}
pub fn ui_tab_bar_set_value(&mut self, entity: freecs::Entity, index: usize) {
let update = if let Some(UiWidgetState::TabBar(data)) = self.ui.get_ui_widget_state(entity)
{
if index < data.tab_entities.len() && index != data.selected_tab {
Some((
data.tab_entities[data.selected_tab],
data.tab_entities[index],
))
} else {
None
}
} else {
None
};
if let Some((old_entity, new_entity)) = update {
let theme = self.resources.retained_ui.theme_state.active_theme();
let active_bg = theme.accent_color;
let inactive_bg = theme.background_color;
if let Some(color) = self.ui.get_ui_node_color_mut(old_entity) {
color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(inactive_bg);
}
if let Some(color) = self.ui.get_ui_node_color_mut(new_entity) {
color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(active_bg);
}
if let Some(UiWidgetState::TabBar(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.selected_tab = index;
}
}
}
pub fn ui_text_input_set_value(&mut self, entity: freecs::Entity, text: &str) {
let (text_slot, placeholder_entity) =
if let Some(UiWidgetState::TextInput(data)) = self.ui.get_ui_widget_state(entity) {
(Some(data.text_slot), data.placeholder_entity)
} else {
(None, None)
};
if let Some(slot) = text_slot {
self.resources.text_cache.set_text(slot, text);
if let Some(UiWidgetState::TextInput(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.text = text.to_string();
data.cursor_position = text.chars().count();
data.selection_start = None;
}
if let Some(ph_entity) = placeholder_entity
&& let Some(node) = self.ui.get_ui_layout_node_mut(ph_entity)
{
node.visible = text.is_empty();
}
}
}
pub fn ui_text_input_set_mask(
&mut self,
entity: freecs::Entity,
mask: crate::ecs::ui::components::InputMask,
) {
if let Some(UiWidgetState::TextInput(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.input_mask = mask;
}
}
pub fn ui_text_input_submitted(&self, entity: freecs::Entity) -> Option<String> {
for event in &self.resources.retained_ui.frame_events {
if let crate::ecs::ui::resources::UiEvent::TextInputSubmitted {
entity: event_entity,
text,
} = event
&& *event_entity == entity
{
return Some(text.clone());
}
}
None
}
pub fn ui_dropdown_set_value(&mut self, entity: freecs::Entity, index: usize) {
let update =
if let Some(UiWidgetState::Dropdown(data)) = self.ui.get_ui_widget_state(entity) {
if index < data.options.len() && index != data.selected_index {
Some((data.header_text_slot, data.options[index].clone()))
} else {
None
}
} else {
None
};
if let Some((text_slot, selected_text)) = update {
self.resources.text_cache.set_text(text_slot, selected_text);
if let Some(UiWidgetState::Dropdown(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.selected_index = index;
}
}
}
pub fn ui_panel_set_pinned(&mut self, entity: freecs::Entity, pinned: bool) {
if let Some(UiWidgetState::Panel(data)) = self.ui.get_ui_widget_state_mut(entity) {
data.pinned = pinned;
}
}
pub fn ui_panel_set_header_visible(&mut self, entity: freecs::Entity, visible: bool) {
if let Some(UiWidgetState::Panel(data)) = self.ui.get_ui_widget_state_mut(entity) {
let header = data.header_entity;
let content = data.content_entity;
data.header_visible = visible;
self.ui_set_visible(header, visible);
if let Some(node) = self.ui.get_ui_layout_node_mut(content)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(ref mut boundary)) =
node.layouts[UiBase::INDEX]
{
let offset_y = if visible { 32.0 } else { 0.0 };
boundary.position_1 = crate::ecs::ui::units::Ab(Vec2::new(0.0, offset_y)).into();
}
}
}
pub fn ui_reserved_areas(&self) -> &crate::ecs::ui::resources::ReservedAreas {
&self.resources.retained_ui.reserved_areas
}
pub fn ui_color_picker_set_value(&mut self, entity: freecs::Entity, color: Vec4) {
let update =
if let Some(UiWidgetState::ColorPicker(data)) = self.ui.get_ui_widget_state(entity) {
let sliders = data.slider_entities;
let mode = data.mode;
let swatch = data.swatch_entity;
Some((sliders, mode, swatch))
} else {
None
};
if let Some((sliders, mode, swatch)) = update {
match mode {
crate::ecs::ui::components::ColorPickerMode::Rgb => {
self.ui_slider_set_value(sliders[0], color.x);
self.ui_slider_set_value(sliders[1], color.y);
self.ui_slider_set_value(sliders[2], color.z);
self.ui_slider_set_value(sliders[3], color.w);
}
crate::ecs::ui::components::ColorPickerMode::Hsv => {
let hsv = crate::ecs::ui::color::Hsva::from_rgba(color);
self.ui_slider_set_value(sliders[0], hsv.hue);
self.ui_slider_set_value(sliders[1], hsv.saturation);
self.ui_slider_set_value(sliders[2], hsv.value);
self.ui_slider_set_value(sliders[3], color.w);
}
}
if let Some(swatch_color) = self.ui.get_ui_node_color_mut(swatch) {
swatch_color.colors[UiBase::INDEX] = Some(color);
}
if let Some(UiWidgetState::ColorPicker(data)) = self.ui.get_ui_widget_state_mut(entity)
{
data.color = color;
}
}
}
}