use crate::ecs::ui::state::UiStateTrait as _;
use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::ui::components::{UiPanelKind, UiWidgetState};
use crate::ecs::world::World;
use super::snapshot_interaction;
pub(super) struct PanelContext {
pub(super) mouse_position: Vec2,
pub(super) mouse_just_pressed: bool,
pub(super) mouse_just_released: bool,
pub(super) mouse_down: bool,
pub(super) dpi_scale: f32,
pub(super) viewport_size: Vec2,
pub(super) mouse_occluded: bool,
}
pub(super) fn handle_panel(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiPanelData,
ctx: &PanelContext,
) {
use crate::ecs::ui::components::ResizeEdge;
use crate::ecs::ui::units::Ab;
let mouse_position = ctx.mouse_position;
let mouse_just_pressed = ctx.mouse_just_pressed;
let mouse_just_released = ctx.mouse_just_released;
let mouse_down = ctx.mouse_down;
let dpi_scale = ctx.dpi_scale;
let viewport_size = ctx.viewport_size;
let header_interaction = snapshot_interaction(world, data.header_entity);
let mut drag_offset = data.drag_offset;
let mut resize_edge = data.resize_edge;
let mut resize_start_rect = data.resize_start_rect;
let mut resize_start_mouse = data.resize_start_mouse;
let mut panel_kind = data.panel_kind;
let mut undocked_rect = data.undocked_rect;
let mut default_dock_size = data.default_dock_size;
let resizable = data.resizable;
if let Some(collapse_entity) = data.collapse_button_entity {
let collapse_interaction = snapshot_interaction(world, collapse_entity);
if collapse_interaction.clicked {
if panel_kind == UiPanelKind::Floating {
if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
node.visible = false;
}
} else {
let new_collapsed = !data.collapsed;
if let Some(node) = world.ui.get_ui_layout_node_mut(data.content_entity) {
node.visible = !new_collapsed;
}
if let Some(text_slot) = data.collapse_button_text_slot {
world
.resources
.text_cache
.set_text(text_slot, if new_collapsed { "+" } else { "-" });
}
if let Some(UiWidgetState::Panel(widget_data)) =
world.ui.get_ui_widget_state_mut(entity)
{
widget_data.collapsed = new_collapsed;
}
}
}
}
if header_interaction.dragging && panel_kind == UiPanelKind::Floating {
if drag_offset.is_none() {
let panel_rect = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect);
if let Some(rect) = panel_rect
&& let Some(drag_start) = header_interaction.drag_start
{
drag_offset = Some(drag_start - rect.min);
}
}
if let Some(offset) = drag_offset
&& let Some(node) = world.ui.get_ui_layout_node_mut(entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.layouts[crate::ecs::ui::state::UiBase::INDEX].as_mut()
{
let current_ab = window.position.absolute.unwrap_or(Vec2::new(0.0, 0.0));
let parent_min = node.computed_rect.min - current_ab * dpi_scale;
let new_pos = (mouse_position - offset - parent_min) / dpi_scale;
let rounded_pos = Vec2::new(new_pos.x.round(), new_pos.y.round());
window.position = Ab(rounded_pos).into();
}
world.resources.retained_ui.dock_indicator_active = true;
world.resources.retained_ui.dock_indicator_panel = Some(entity);
}
if header_interaction.dragging
&& panel_kind != UiPanelKind::Floating
&& !data.pinned
&& let Some(drag_start) = header_interaction.drag_start
{
let drag_distance = (mouse_position - drag_start).magnitude();
if drag_distance > 50.0 {
panel_kind = UiPanelKind::Floating;
let restored_size = undocked_rect
.map(|r| r.size())
.unwrap_or(Vec2::new(300.0, 400.0));
let new_pos = mouse_position - Vec2::new(restored_size.x * 0.5, 14.0);
if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
node.layer = Some(crate::render::wgpu::passes::geometry::UiLayer::FloatingPanels);
if let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.layouts[crate::ecs::ui::state::UiBase::INDEX].as_mut()
{
window.position = Ab(new_pos / dpi_scale).into();
window.size = Ab(restored_size / dpi_scale).into();
}
}
drag_offset = Some(Vec2::new(restored_size.x * 0.5, 14.0));
world.resources.retained_ui.dock_indicator_active = true;
world.resources.retained_ui.dock_indicator_panel = Some(entity);
}
}
if mouse_just_released
&& world.resources.retained_ui.dock_indicator_panel == Some(entity)
&& panel_kind == UiPanelKind::Floating
{
let indicator_size = 40.0;
let indicator_spacing = 4.0;
let center = viewport_size * 0.5;
let dock_buttons = [
(
crate::ecs::ui::types::Rect::from_center_size(
Vec2::new(center.x, center.y - indicator_size - indicator_spacing),
Vec2::new(indicator_size, indicator_size),
),
UiPanelKind::DockedTop,
),
(
crate::ecs::ui::types::Rect::from_center_size(
Vec2::new(center.x, center.y + indicator_size + indicator_spacing),
Vec2::new(indicator_size, indicator_size),
),
UiPanelKind::DockedBottom,
),
(
crate::ecs::ui::types::Rect::from_center_size(
Vec2::new(center.x - indicator_size - indicator_spacing, center.y),
Vec2::new(indicator_size, indicator_size),
),
UiPanelKind::DockedLeft,
),
(
crate::ecs::ui::types::Rect::from_center_size(
Vec2::new(center.x + indicator_size + indicator_spacing, center.y),
Vec2::new(indicator_size, indicator_size),
),
UiPanelKind::DockedRight,
),
];
let mut target_dock = None;
for (btn_rect, dock_kind) in &dock_buttons {
if btn_rect.contains(mouse_position) {
target_dock = Some(*dock_kind);
break;
}
}
if let Some(new_kind) = target_dock {
let panel_rect = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect);
if let Some(rect) = panel_rect {
undocked_rect = Some(rect);
}
panel_kind = new_kind;
default_dock_size = match new_kind {
UiPanelKind::DockedLeft | UiPanelKind::DockedRight => {
undocked_rect.map(|r| r.width()).unwrap_or(300.0) / dpi_scale
}
UiPanelKind::DockedTop | UiPanelKind::DockedBottom => {
undocked_rect.map(|r| r.height()).unwrap_or(300.0) / dpi_scale
}
UiPanelKind::Floating => 300.0,
};
if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
node.layer = Some(crate::render::wgpu::passes::geometry::UiLayer::DockedPanels);
}
}
world.resources.retained_ui.dock_indicator_active = false;
world.resources.retained_ui.dock_indicator_panel = None;
}
if !header_interaction.pressed && !header_interaction.dragging {
drag_offset = None;
if world.resources.retained_ui.dock_indicator_panel == Some(entity) {
world.resources.retained_ui.dock_indicator_active = false;
world.resources.retained_ui.dock_indicator_panel = None;
}
}
if !ctx.mouse_occluded && resizable {
let panel_rect = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect);
if let Some(rect) = panel_rect {
let edge_margin = 6.0 * dpi_scale;
let near_left = (mouse_position.x - rect.min.x).abs() < edge_margin;
let near_right = (mouse_position.x - rect.max.x).abs() < edge_margin;
let near_top = (mouse_position.y - rect.min.y).abs() < edge_margin;
let near_bottom = (mouse_position.y - rect.max.y).abs() < edge_margin;
let inside_x = mouse_position.x >= rect.min.x - edge_margin
&& mouse_position.x <= rect.max.x + edge_margin;
let inside_y = mouse_position.y >= rect.min.y - edge_margin
&& mouse_position.y <= rect.max.y + edge_margin;
let detected_edge = match panel_kind {
UiPanelKind::DockedLeft => {
if near_right && inside_y {
Some(ResizeEdge::Right)
} else {
None
}
}
UiPanelKind::DockedRight => {
if near_left && inside_y {
Some(ResizeEdge::Left)
} else {
None
}
}
UiPanelKind::DockedTop => {
if near_bottom && inside_x {
Some(ResizeEdge::Bottom)
} else {
None
}
}
UiPanelKind::DockedBottom => {
if near_top && inside_x {
Some(ResizeEdge::Top)
} else {
None
}
}
UiPanelKind::Floating => {
if near_top && near_left && inside_x && inside_y {
Some(ResizeEdge::TopLeft)
} else if near_top && near_right && inside_x && inside_y {
Some(ResizeEdge::TopRight)
} else if near_bottom && near_left && inside_x && inside_y {
Some(ResizeEdge::BottomLeft)
} else if near_bottom && near_right && inside_x && inside_y {
Some(ResizeEdge::BottomRight)
} else if near_left && inside_y {
Some(ResizeEdge::Left)
} else if near_right && inside_y {
Some(ResizeEdge::Right)
} else if near_top && inside_x {
Some(ResizeEdge::Top)
} else if near_bottom && inside_x {
Some(ResizeEdge::Bottom)
} else {
None
}
}
};
if let Some(edge) = detected_edge {
world.resources.retained_ui.requested_cursor = Some(match edge {
ResizeEdge::Left | ResizeEdge::Right => winit::window::CursorIcon::EwResize,
ResizeEdge::Top | ResizeEdge::Bottom => winit::window::CursorIcon::NsResize,
ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
winit::window::CursorIcon::NwseResize
}
ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
winit::window::CursorIcon::NeswResize
}
});
}
if !header_interaction.dragging && mouse_just_pressed && detected_edge.is_some() {
resize_edge = detected_edge;
resize_start_rect = Some(rect);
resize_start_mouse = Some(mouse_position);
}
}
}
if let Some(edge) = resize_edge {
world.resources.retained_ui.requested_cursor = Some(match edge {
ResizeEdge::Left | ResizeEdge::Right => winit::window::CursorIcon::EwResize,
ResizeEdge::Top | ResizeEdge::Bottom => winit::window::CursorIcon::NsResize,
ResizeEdge::TopLeft | ResizeEdge::BottomRight => winit::window::CursorIcon::NwseResize,
ResizeEdge::TopRight | ResizeEdge::BottomLeft => winit::window::CursorIcon::NeswResize,
});
if mouse_down {
if let (Some(start_rect), Some(start_mouse)) = (resize_start_rect, resize_start_mouse) {
let delta = mouse_position - start_mouse;
let min_size = data.min_size;
let mut new_pos = start_rect.min;
let mut new_size = start_rect.size();
match edge {
ResizeEdge::Right => {
new_size.x = (start_rect.width() + delta.x).max(min_size.x);
}
ResizeEdge::Bottom => {
new_size.y = (start_rect.height() + delta.y).max(min_size.y);
}
ResizeEdge::Left => {
let new_width = (start_rect.width() - delta.x).max(min_size.x);
new_pos.x = start_rect.max.x - new_width;
new_size.x = new_width;
}
ResizeEdge::Top => {
let new_height = (start_rect.height() - delta.y).max(min_size.y);
new_pos.y = start_rect.max.y - new_height;
new_size.y = new_height;
}
ResizeEdge::TopLeft => {
let new_width = (start_rect.width() - delta.x).max(min_size.x);
let new_height = (start_rect.height() - delta.y).max(min_size.y);
new_pos.x = start_rect.max.x - new_width;
new_pos.y = start_rect.max.y - new_height;
new_size.x = new_width;
new_size.y = new_height;
}
ResizeEdge::TopRight => {
let new_height = (start_rect.height() - delta.y).max(min_size.y);
new_pos.y = start_rect.max.y - new_height;
new_size.x = (start_rect.width() + delta.x).max(min_size.x);
new_size.y = new_height;
}
ResizeEdge::BottomLeft => {
let new_width = (start_rect.width() - delta.x).max(min_size.x);
new_pos.x = start_rect.max.x - new_width;
new_size.x = new_width;
new_size.y = (start_rect.height() + delta.y).max(min_size.y);
}
ResizeEdge::BottomRight => {
new_size.x = (start_rect.width() + delta.x).max(min_size.x);
new_size.y = (start_rect.height() + delta.y).max(min_size.y);
}
}
if panel_kind != UiPanelKind::Floating {
match panel_kind {
UiPanelKind::DockedLeft | UiPanelKind::DockedRight => {
default_dock_size = new_size.x / dpi_scale;
}
UiPanelKind::DockedTop | UiPanelKind::DockedBottom => {
default_dock_size = new_size.y / dpi_scale;
}
UiPanelKind::Floating => {}
}
} else if let Some(node) = world.ui.get_ui_layout_node_mut(entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.layouts[crate::ecs::ui::state::UiBase::INDEX].as_mut()
{
let current_ab = window.position.absolute.unwrap_or(Vec2::new(0.0, 0.0));
let parent_min = node.computed_rect.min - current_ab * dpi_scale;
let logical_pos = (new_pos - parent_min) / dpi_scale;
let logical_size = new_size / dpi_scale;
window.position =
Ab(Vec2::new(logical_pos.x.round(), logical_pos.y.round())).into();
window.size =
Ab(Vec2::new(logical_size.x.round(), logical_size.y.round())).into();
}
}
} else {
resize_edge = None;
resize_start_rect = None;
resize_start_mouse = None;
}
}
if let Some(UiWidgetState::Panel(widget_data)) = world.ui.get_ui_widget_state_mut(entity) {
widget_data.drag_offset = drag_offset;
widget_data.resize_edge = resize_edge;
widget_data.resize_start_rect = resize_start_rect;
widget_data.resize_start_mouse = resize_start_mouse;
widget_data.panel_kind = panel_kind;
widget_data.undocked_rect = undocked_rect;
widget_data.default_dock_size = default_dock_size;
}
}
pub(super) fn handle_color_picker(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiColorPickerData,
) {
let mode = data.mode;
let mut any_changed = false;
let mut slider_values = [0.0_f32; 4];
for (index, slider_entity) in data.slider_entities.iter().enumerate() {
if let Some(UiWidgetState::Slider(slider_data)) =
world.ui.get_ui_widget_state(*slider_entity)
{
if slider_data.changed {
any_changed = true;
}
slider_values[index] = slider_data.value;
}
}
let color = match mode {
crate::ecs::ui::components::ColorPickerMode::Rgb => Vec4::new(
slider_values[0],
slider_values[1],
slider_values[2],
slider_values[3],
),
crate::ecs::ui::components::ColorPickerMode::Hsv => {
let hsv = crate::ecs::ui::color::Hsva {
hue: slider_values[0],
saturation: slider_values[1],
value: slider_values[2],
alpha: slider_values[3],
};
hsv.to_rgba()
}
};
if any_changed && let Some(swatch_color) = world.ui.get_ui_node_color_mut(data.swatch_entity) {
swatch_color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(color);
}
if let Some(UiWidgetState::ColorPicker(widget_data)) = world.ui.get_ui_widget_state_mut(entity)
{
widget_data.color = color;
widget_data.changed = any_changed;
}
if any_changed {
world
.resources
.retained_ui
.frame_events
.push(crate::ecs::ui::resources::UiEvent::ColorPickerChanged { entity, color });
}
}