use crate::ecs::ui::state::UiStateTrait as _;
use nalgebra_glm::Vec2;
use winit::keyboard::KeyCode;
use crate::ecs::ui::components::UiWidgetState;
use crate::ecs::world::World;
use super::InteractionSnapshot;
pub(super) fn handle_slider(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiSliderData,
mouse_position: Vec2,
focused_entity: Option<freecs::Entity>,
frame_keys: &[(KeyCode, bool)],
) {
let mut new_value = data.value;
let mut changed = false;
if focused_entity == Some(entity) {
let range = data.max - data.min;
let step = if data.logarithmic {
range * 0.01
} else {
let natural_step = 10.0_f32.powf((range.log10() - 1.0).floor());
natural_step.max(0.01)
};
for (key, is_pressed) in frame_keys {
if !is_pressed {
continue;
}
match key {
KeyCode::ArrowRight | KeyCode::ArrowUp => {
new_value = (new_value + step).min(data.max);
changed = true;
}
KeyCode::ArrowLeft | KeyCode::ArrowDown => {
new_value = (new_value - step).max(data.min);
changed = true;
}
KeyCode::Home => {
new_value = data.min;
changed = true;
}
KeyCode::End => {
new_value = data.max;
changed = true;
}
_ => {}
}
}
}
if interaction.pressed || interaction.dragging {
let track_rect = world
.ui
.get_ui_layout_node(entity)
.map(|node| node.computed_rect);
if let Some(rect) = track_rect {
let normalized = ((mouse_position.x - rect.min.x) / rect.width()).clamp(0.0, 1.0);
new_value = if data.logarithmic && data.min > 0.0 && data.max > data.min {
data.min * (data.max / data.min).powf(normalized)
} else {
data.min + normalized * (data.max - data.min)
};
if (new_value - data.value).abs() > f32::EPSILON {
changed = true;
}
let fill_percent = normalized * 100.0;
if let Some(fill_node) = world.ui.get_ui_layout_node_mut(data.fill_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary)) =
fill_node.layouts[crate::ecs::ui::state::UiBase::INDEX].as_mut()
{
boundary.position_2 =
crate::ecs::ui::units::Rl(Vec2::new(fill_percent, 100.0)).into();
}
world.resources.text_cache.set_text(
data.text_slot,
format!(
"{}{:.prec$}{}",
data.prefix,
new_value,
data.suffix,
prec = data.precision
),
);
}
}
if changed && !(interaction.pressed || interaction.dragging) {
let range = data.max - data.min;
let normalized = if data.logarithmic && data.min > 0.0 && range > 0.0 {
((new_value / data.min).ln() / (data.max / data.min).ln()).clamp(0.0, 1.0)
} else if range.abs() > f32::EPSILON {
((new_value - data.min) / range).clamp(0.0, 1.0)
} else {
0.0
};
let fill_percent = normalized * 100.0;
if let Some(fill_node) = world.ui.get_ui_layout_node_mut(data.fill_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary)) =
fill_node.layouts[crate::ecs::ui::state::UiBase::INDEX].as_mut()
{
boundary.position_2 = crate::ecs::ui::units::Rl(Vec2::new(fill_percent, 100.0)).into();
}
world.resources.text_cache.set_text(
data.text_slot,
format!(
"{}{:.prec$}{}",
data.prefix,
new_value,
data.suffix,
prec = data.precision
),
);
}
if let Some(UiWidgetState::Slider(widget_data)) = world.ui.get_ui_widget_state_mut(entity) {
widget_data.value = new_value;
widget_data.changed = changed;
}
if changed {
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::SliderChanged {
entity,
value: new_value,
},
);
}
}
pub(super) fn handle_splitter(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiSplitterData,
mouse_position: Vec2,
) {
use crate::ecs::ui::components::SplitDirection;
let divider_dragging = world
.ui
.get_ui_node_interaction(data.divider_entity)
.map(|interaction| interaction.dragging)
.unwrap_or(false);
if !divider_dragging {
if let Some(crate::ecs::ui::components::UiWidgetState::Splitter(splitter_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
splitter_data.changed = false;
}
return;
}
let container_rect = world
.ui
.get_ui_layout_node(entity)
.map(|node| node.computed_rect);
let Some(container_rect) = container_rect else {
return;
};
let new_ratio = match data.direction {
SplitDirection::Horizontal => {
let width = container_rect.width();
if width > 0.0 {
((mouse_position.x - container_rect.min.x) / width)
.clamp(data.min_ratio, data.max_ratio)
} else {
data.ratio
}
}
SplitDirection::Vertical => {
let height = container_rect.height();
if height > 0.0 {
((mouse_position.y - container_rect.min.y) / height)
.clamp(data.min_ratio, data.max_ratio)
} else {
data.ratio
}
}
};
if (new_ratio - data.ratio).abs() < 0.001 {
return;
}
let divider_thickness = 4.0;
let (p1_1, p1_2, d1, d2, p2_1, p2_2) = match data.direction {
SplitDirection::Horizontal => {
let half = divider_thickness * 0.5;
(
crate::ecs::ui::units::Rl(Vec2::new(0.0, 0.0)),
crate::ecs::ui::units::Rl(Vec2::new(new_ratio * 100.0, 100.0))
+ crate::ecs::ui::units::Ab(Vec2::new(-half, 0.0)),
crate::ecs::ui::units::Rl(Vec2::new(new_ratio * 100.0, 0.0))
+ crate::ecs::ui::units::Ab(Vec2::new(-half, 0.0)),
crate::ecs::ui::units::Rl(Vec2::new(new_ratio * 100.0, 100.0))
+ crate::ecs::ui::units::Ab(Vec2::new(half, 0.0)),
crate::ecs::ui::units::Rl(Vec2::new(new_ratio * 100.0, 0.0))
+ crate::ecs::ui::units::Ab(Vec2::new(half, 0.0)),
crate::ecs::ui::units::Rl(Vec2::new(100.0, 100.0)),
)
}
SplitDirection::Vertical => {
let half = divider_thickness * 0.5;
(
crate::ecs::ui::units::Rl(Vec2::new(0.0, 0.0)),
crate::ecs::ui::units::Rl(Vec2::new(100.0, new_ratio * 100.0))
+ crate::ecs::ui::units::Ab(Vec2::new(0.0, -half)),
crate::ecs::ui::units::Rl(Vec2::new(0.0, new_ratio * 100.0))
+ crate::ecs::ui::units::Ab(Vec2::new(0.0, -half)),
crate::ecs::ui::units::Rl(Vec2::new(100.0, new_ratio * 100.0))
+ crate::ecs::ui::units::Ab(Vec2::new(0.0, half)),
crate::ecs::ui::units::Rl(Vec2::new(0.0, new_ratio * 100.0))
+ crate::ecs::ui::units::Ab(Vec2::new(0.0, half)),
crate::ecs::ui::units::Rl(Vec2::new(100.0, 100.0)),
)
}
};
let first_pane = data.first_pane;
let second_pane = data.second_pane;
let divider_entity = data.divider_entity;
if let Some(node) = world.ui.get_ui_layout_node_mut(first_pane)
&& let Some(Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary))) =
node.layouts.get_mut(0)
{
boundary.position_1 = p1_1.into();
boundary.position_2 = p1_2;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(divider_entity)
&& let Some(Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary))) =
node.layouts.get_mut(0)
{
boundary.position_1 = d1;
boundary.position_2 = d2;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(second_pane)
&& let Some(Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary))) =
node.layouts.get_mut(0)
{
boundary.position_1 = p2_1;
boundary.position_2 = p2_2.into();
}
if let Some(crate::ecs::ui::components::UiWidgetState::Splitter(splitter_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
splitter_data.ratio = new_ratio;
splitter_data.changed = true;
}
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::SplitterChanged {
entity,
ratio: new_ratio,
},
);
}
pub(super) fn handle_breadcrumb(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiBreadcrumbData,
) {
let mut clicked_segment = None;
for (index, segment_entity) in data.segment_entities.iter().enumerate() {
let clicked = world
.ui
.get_ui_node_interaction(*segment_entity)
.map(|interaction| interaction.clicked)
.unwrap_or(false);
if clicked {
clicked_segment = Some(index);
break;
}
}
if let Some(index) = clicked_segment {
if let Some(UiWidgetState::Breadcrumb(breadcrumb_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
breadcrumb_data.clicked_segment = Some(index);
breadcrumb_data.changed = true;
}
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::BreadcrumbClicked {
entity,
segment_index: index,
},
);
} else if let Some(UiWidgetState::Breadcrumb(breadcrumb_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
breadcrumb_data.clicked_segment = None;
breadcrumb_data.changed = false;
}
}
pub(super) fn handle_range_slider(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiRangeSliderData,
mouse_position: Vec2,
focused_entity: Option<freecs::Entity>,
frame_keys: &[(KeyCode, bool)],
) {
let mut low_value = data.low_value;
let mut high_value = data.high_value;
let mut changed = false;
let mut active_thumb = data.active_thumb;
let range = data.max - data.min;
if range.abs() < f32::EPSILON {
if let Some(UiWidgetState::RangeSlider(widget_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
widget_data.changed = false;
}
return;
}
if interaction.pressed {
let track_rect = world
.ui
.get_ui_layout_node(entity)
.map(|node| node.computed_rect);
if let Some(rect) = track_rect {
let normalized = ((mouse_position.x - rect.min.x) / rect.width()).clamp(0.0, 1.0);
let mouse_value = data.min + normalized * range;
let dist_to_low = (mouse_value - low_value).abs();
let dist_to_high = (mouse_value - high_value).abs();
active_thumb = Some(if dist_to_low <= dist_to_high {
crate::ecs::ui::components::RangeSliderThumb::Low
} else {
crate::ecs::ui::components::RangeSliderThumb::High
});
}
}
if (interaction.pressed || interaction.dragging)
&& let Some(thumb) = active_thumb
{
let track_rect = world
.ui
.get_ui_layout_node(entity)
.map(|node| node.computed_rect);
if let Some(rect) = track_rect {
let normalized = ((mouse_position.x - rect.min.x) / rect.width()).clamp(0.0, 1.0);
let new_value = data.min + normalized * range;
match thumb {
crate::ecs::ui::components::RangeSliderThumb::Low => {
low_value = new_value.min(high_value);
}
crate::ecs::ui::components::RangeSliderThumb::High => {
high_value = new_value.max(low_value);
}
}
if (low_value - data.low_value).abs() > f32::EPSILON
|| (high_value - data.high_value).abs() > f32::EPSILON
{
changed = true;
}
let low_n = ((low_value - data.min) / range).clamp(0.0, 1.0);
let high_n = ((high_value - data.min) / range).clamp(0.0, 1.0);
crate::ecs::ui::widgets::update_range_slider_visuals(
world,
&crate::ecs::ui::widgets::RangeSliderVisualUpdate {
fill_entity: data.fill_entity,
low_thumb: data.low_thumb_entity,
high_thumb: data.high_thumb_entity,
text_slot: data.text_slot,
precision: data.precision,
low_normalized: low_n,
high_normalized: high_n,
low_value,
high_value,
thumb_half: data.thumb_half_size,
},
);
}
}
if !interaction.pressed && !interaction.dragging {
active_thumb = None;
}
if focused_entity == Some(entity) {
let step = range * 0.01;
for &(key, pressed) in frame_keys {
if !pressed {
continue;
}
match key {
KeyCode::Tab => {
active_thumb = match active_thumb {
Some(crate::ecs::ui::components::RangeSliderThumb::Low) => {
Some(crate::ecs::ui::components::RangeSliderThumb::High)
}
_ => Some(crate::ecs::ui::components::RangeSliderThumb::Low),
};
}
KeyCode::ArrowLeft | KeyCode::ArrowDown => {
let thumb =
active_thumb.unwrap_or(crate::ecs::ui::components::RangeSliderThumb::Low);
match thumb {
crate::ecs::ui::components::RangeSliderThumb::Low => {
low_value = (low_value - step).max(data.min).min(high_value);
}
crate::ecs::ui::components::RangeSliderThumb::High => {
high_value = (high_value - step).max(low_value).max(data.min);
}
}
if active_thumb.is_none() {
active_thumb = Some(thumb);
}
changed = true;
}
KeyCode::ArrowRight | KeyCode::ArrowUp => {
let thumb =
active_thumb.unwrap_or(crate::ecs::ui::components::RangeSliderThumb::High);
match thumb {
crate::ecs::ui::components::RangeSliderThumb::Low => {
low_value = (low_value + step).min(high_value).min(data.max);
}
crate::ecs::ui::components::RangeSliderThumb::High => {
high_value = (high_value + step).min(data.max);
}
}
if active_thumb.is_none() {
active_thumb = Some(thumb);
}
changed = true;
}
_ => {}
}
}
if changed && !interaction.pressed && !interaction.dragging {
let low_n = ((low_value - data.min) / range).clamp(0.0, 1.0);
let high_n = ((high_value - data.min) / range).clamp(0.0, 1.0);
crate::ecs::ui::widgets::update_range_slider_visuals(
world,
&crate::ecs::ui::widgets::RangeSliderVisualUpdate {
fill_entity: data.fill_entity,
low_thumb: data.low_thumb_entity,
high_thumb: data.high_thumb_entity,
text_slot: data.text_slot,
precision: data.precision,
low_normalized: low_n,
high_normalized: high_n,
low_value,
high_value,
thumb_half: data.thumb_half_size,
},
);
}
}
if let Some(UiWidgetState::RangeSlider(widget_data)) = world.ui.get_ui_widget_state_mut(entity)
{
widget_data.low_value = low_value;
widget_data.high_value = high_value;
widget_data.changed = changed;
widget_data.active_thumb = active_thumb;
}
if changed {
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::RangeSliderChanged {
entity,
low: low_value,
high: high_value,
},
);
}
}