use crate::ecs::ui::state::UiStateTrait as _;
use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::ui::components::UiPanelKind;
use crate::ecs::world::World;
use super::snapshot_interaction;
use crate::prelude::*;
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(widget_data) = world.ui.get_ui_panel_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.base_layout.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.overlays.dock_indicator_active = true;
world.resources.retained_ui.overlays.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.base_layout.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.overlays.dock_indicator_active = true;
world.resources.retained_ui.overlays.dock_indicator_panel = Some(entity);
}
}
if mouse_just_released
&& world.resources.retained_ui.overlays.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 target_dock.is_none() {
let edge_threshold = 80.0;
target_dock = if mouse_position.y < edge_threshold {
Some(UiPanelKind::DockedTop)
} else if mouse_position.y > viewport_size.y - edge_threshold {
Some(UiPanelKind::DockedBottom)
} else if mouse_position.x < edge_threshold {
Some(UiPanelKind::DockedLeft)
} else if mouse_position.x > viewport_size.x - edge_threshold {
Some(UiPanelKind::DockedRight)
} else {
None
};
}
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.overlays.dock_indicator_active = false;
world.resources.retained_ui.overlays.dock_indicator_panel = None;
}
if !header_interaction.pressed && !header_interaction.dragging {
drag_offset = None;
if world.resources.retained_ui.overlays.dock_indicator_panel == Some(entity) {
world.resources.retained_ui.overlays.dock_indicator_active = false;
world.resources.retained_ui.overlays.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
.interaction_for_active_mut()
.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
.interaction_for_active_mut()
.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.base_layout.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(widget_data) = world.ui.get_ui_panel_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_wheel(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiColorWheelData,
mouse_position: Vec2,
) {
let mut hue = data.hue;
let mut saturation = data.saturation;
let mut changed_position = false;
let wheel_interaction = snapshot_interaction(world, data.wheel_entity);
if (wheel_interaction.pressed || wheel_interaction.dragging)
&& let Some(rect) = world
.ui
.get_ui_layout_node(data.wheel_entity)
.map(|node| node.computed_rect)
{
let center = rect.center();
let half = (rect.width().min(rect.height())) * 0.5;
if half > 0.0 {
let offset = mouse_position - center;
let raw_radius = nalgebra_glm::length(&offset);
let new_saturation = (raw_radius / half).clamp(0.0, 1.0);
let angle = offset.y.atan2(offset.x);
let new_hue = ((angle / std::f32::consts::TAU) + 0.5).rem_euclid(1.0);
if (new_hue - hue).abs() > f32::EPSILON
|| (new_saturation - saturation).abs() > f32::EPSILON
{
hue = new_hue;
saturation = new_saturation;
changed_position = true;
}
}
}
let mut value = data.value;
let mut alpha = data.alpha;
let mut changed_value = false;
let mut changed_alpha = false;
if let Some(slider_data) = world.ui.get_ui_slider(data.value_slider_entity) {
if slider_data.changed && (slider_data.value - value).abs() > f32::EPSILON {
changed_value = true;
}
value = slider_data.value;
}
if let Some(slider_data) = world.ui.get_ui_slider(data.alpha_slider_entity) {
if slider_data.changed && (slider_data.value - alpha).abs() > f32::EPSILON {
changed_alpha = true;
}
alpha = slider_data.value;
}
let any_changed = changed_position || changed_value || changed_alpha;
let hsva = crate::ecs::ui::color::Hsva {
hue: hue * 360.0,
saturation,
value,
alpha,
};
let rgba = hsva.to_rgba();
if changed_value && let Some(effect) = world.ui.get_ui_node_effect_mut(data.wheel_entity) {
let (kind, params) =
crate::ecs::ui::theme::RectEffect::ColorWheel { value, alpha: 1.0 }.encode();
effect.kind = kind;
effect.params = params;
}
if any_changed {
if let Some(swatch_color) = world.ui.get_ui_node_color_mut(data.swatch_entity) {
swatch_color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(rgba);
}
if changed_position {
let dot_size = 12.0_f32;
if let Some(rect) = world
.ui
.get_ui_layout_node(data.wheel_entity)
.map(|node| node.computed_rect)
{
let half = (rect.width().min(rect.height())) * 0.5;
if half > 0.0 {
let dpi = crate::ecs::window::resources::window_scale_factor(world).max(0.0001);
let logical_half = half / dpi;
let logical_dot = dot_size;
let theta = hue * std::f32::consts::TAU - std::f32::consts::PI;
let local_x =
logical_half + saturation * logical_half * theta.cos() - logical_dot * 0.5;
let local_y =
logical_half + saturation * logical_half * theta.sin() - logical_dot * 0.5;
if let Some(node) = world.ui.get_ui_layout_node_mut(data.dot_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.base_layout.as_mut()
{
window.position =
crate::ecs::ui::units::Ab(Vec2::new(local_x, local_y)).into();
}
}
}
}
}
if let Some(widget_data) = world.ui.get_ui_color_wheel_mut(entity) {
widget_data.hue = hue;
widget_data.saturation = saturation;
widget_data.value = value;
widget_data.alpha = alpha;
widget_data.color = rgba;
widget_data.changed = any_changed;
}
if any_changed {
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::ColorPickerChanged {
entity,
color: rgba,
},
);
}
}
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(slider_data) = world.ui.get_ui_slider(*slider_entity) {
if slider_data.changed {
any_changed = true;
}
slider_values[index] = slider_data.value;
}
}
let mut 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()
}
};
let mut popup_open = data.popup_open;
let swatch_interaction = snapshot_interaction(world, data.swatch_entity);
if swatch_interaction.clicked {
popup_open = !popup_open;
}
let popup_wheel_changed = world
.ui
.get_ui_color_wheel(data.popup_wheel_entity)
.map(|wheel| (wheel.changed, wheel.color))
.unwrap_or((false, color));
if popup_wheel_changed.0 {
color = popup_wheel_changed.1;
any_changed = true;
sync_color_picker_sliders(world, data, color);
}
if popup_open && !data.popup_open {
sync_color_wheel_to_color(world, data.popup_wheel_entity, color);
}
if let Some(node) = world.ui.get_ui_layout_node_mut(data.popup_entity) {
node.visible = popup_open;
}
if popup_open {
let mouse_position = world.resources.input.mouse.position;
let mouse_just_pressed = world
.resources
.input
.mouse
.state
.contains(crate::ecs::input::resources::MouseState::LEFT_JUST_PRESSED);
if mouse_just_pressed && !swatch_interaction.clicked {
let popup_rect = world
.ui
.get_ui_layout_node(data.popup_entity)
.map(|node| node.computed_rect);
let inside_popup = popup_rect.is_some_and(|rect| rect.contains(mouse_position));
if !inside_popup {
popup_open = false;
if let Some(node) = world.ui.get_ui_layout_node_mut(data.popup_entity) {
node.visible = false;
}
}
}
}
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(widget_data) = world.ui.get_ui_color_picker_mut(entity) {
widget_data.color = color;
widget_data.changed = any_changed;
widget_data.popup_open = popup_open;
}
if any_changed {
world
.resources
.retained_ui
.frame
.events
.push(crate::ecs::ui::resources::UiEvent::ColorPickerChanged { entity, color });
}
}
fn sync_color_picker_sliders(
world: &mut World,
data: &crate::ecs::ui::components::UiColorPickerData,
color: Vec4,
) {
match data.mode {
crate::ecs::ui::components::ColorPickerMode::Rgb => {
ui_slider_set_value(world, data.slider_entities[0], color.x);
ui_slider_set_value(world, data.slider_entities[1], color.y);
ui_slider_set_value(world, data.slider_entities[2], color.z);
ui_slider_set_value(world, data.slider_entities[3], color.w);
}
crate::ecs::ui::components::ColorPickerMode::Hsv => {
let hsv = crate::ecs::ui::color::Hsva::from_rgba(color);
ui_slider_set_value(world, data.slider_entities[0], hsv.hue);
ui_slider_set_value(world, data.slider_entities[1], hsv.saturation);
ui_slider_set_value(world, data.slider_entities[2], hsv.value);
ui_slider_set_value(world, data.slider_entities[3], color.w);
}
}
}
fn sync_color_wheel_to_color(world: &mut World, wheel_entity: freecs::Entity, color: Vec4) {
let wheel_data = match world.ui.get_ui_color_wheel(wheel_entity) {
Some(data) => data.clone(),
None => return,
};
let hsv = crate::ecs::ui::color::Hsva::from_rgba(color);
let hue_normalized = (hsv.hue / 360.0).clamp(0.0, 1.0);
let saturation = hsv.saturation.clamp(0.0, 1.0);
let value = hsv.value.clamp(0.0, 1.0);
let alpha = color.w.clamp(0.0, 1.0);
ui_slider_set_value(world, wheel_data.value_slider_entity, value);
ui_slider_set_value(world, wheel_data.alpha_slider_entity, alpha);
if let Some(swatch_color) = world.ui.get_ui_node_color_mut(wheel_data.swatch_entity) {
swatch_color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(color);
}
if let Some(effect) = world.ui.get_ui_node_effect_mut(wheel_data.wheel_entity) {
let (kind, params) =
crate::ecs::ui::theme::RectEffect::ColorWheel { value, alpha: 1.0 }.encode();
effect.kind = kind;
effect.params = params;
}
if let Some(rect) = world
.ui
.get_ui_layout_node(wheel_data.wheel_entity)
.map(|node| node.computed_rect)
{
let half = (rect.width().min(rect.height())) * 0.5;
if half > 0.0 {
let dpi = crate::ecs::window::resources::window_scale_factor(world).max(0.0001);
let logical_half = half / dpi;
let dot_size = 12.0_f32;
let theta = hue_normalized * std::f32::consts::TAU - std::f32::consts::PI;
let local_x = logical_half + saturation * logical_half * theta.cos() - dot_size * 0.5;
let local_y = logical_half + saturation * logical_half * theta.sin() - dot_size * 0.5;
if let Some(node) = world.ui.get_ui_layout_node_mut(wheel_data.dot_entity)
&& let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
node.base_layout.as_mut()
{
window.position = crate::ecs::ui::units::Ab(Vec2::new(local_x, local_y)).into();
}
}
}
if let Some(widget_data) = world.ui.get_ui_color_wheel_mut(wheel_entity) {
widget_data.color = color;
widget_data.hue = hue_normalized;
widget_data.saturation = saturation;
widget_data.value = value;
widget_data.alpha = alpha;
}
}