use crate::app_context::ContextManager;
use crate::layout::docking::DockPanel;
use crate::input::core::coordinator::LayerId;
use crate::input::{InputCoordinator, Sense, WidgetKind};
use crate::layout::{LayoutManager, LayoutNodeId, WidgetNode};
use crate::render::RenderContext;
use crate::types::{Rect, WidgetId, WidgetState};
use super::render::{draw_slider, SliderView};
use super::settings::SliderSettings;
use super::state::SliderDragState;
use super::types::{DualSliderHandle, SliderConfig, SliderTrackInfo};
pub fn register(
coord: &mut InputCoordinator,
id: impl Into<WidgetId>,
rect: Rect,
layer: &LayerId,
) {
coord.register_atomic(id, WidgetKind::Slider, rect, Sense::CLICK_AND_DRAG, layer);
}
pub fn pixel_to_value(x: f64, track_x: f64, track_width: f64, min: f64, max: f64) -> f64 {
if track_width <= 0.0 {
return min;
}
let t = ((x - track_x) / track_width).clamp(0.0, 1.0);
min + t * (max - min)
}
pub fn value_to_pixel(value: f64, track_x: f64, track_width: f64, min: f64, max: f64) -> f64 {
if max <= min {
return track_x;
}
let t = ((value - min) / (max - min)).clamp(0.0, 1.0);
track_x + t * track_width
}
pub fn clamp_step(value: f64, min: f64, max: f64, step: f64) -> f64 {
let clamped = value.clamp(min, max);
if step > 0.0 {
(clamped / step).round() * step
} else {
clamped
}
}
pub fn start_slider_drag(
drag_state: &mut SliderDragState,
field_id: impl Into<WidgetId>,
x: f64,
y: f64,
track: &SliderTrackInfo,
handle: Option<DualSliderHandle>,
) -> bool {
let hit = x >= track.track_x - 2.0
&& x <= track.track_x + track.track_width + 2.0
&& y >= track.track_y
&& y <= track.track_y + track.track_height;
if !hit {
return false;
}
let fid: WidgetId = field_id.into();
if let Some(h) = handle {
*drag_state = SliderDragState::start_dual(
fid,
track.track_x,
track.track_width,
track.min_val,
track.max_val,
h,
x,
);
} else {
*drag_state = SliderDragState::start_single(
fid,
track.track_x,
track.track_width,
track.min_val,
track.max_val,
);
let initial = pixel_to_value(x, track.track_x, track.track_width, track.min_val, track.max_val);
drag_state.update_floating(initial);
}
true
}
pub fn update_slider_drag_float(drag_state: &mut SliderDragState, x: f64) -> Option<f64> {
if !drag_state.is_active() {
return None;
}
let value = pixel_to_value(
x,
drag_state.track_x,
drag_state.track_width,
drag_state.min,
drag_state.max,
);
drag_state.update_floating(value);
Some(value)
}
pub fn end_slider_drag(
drag_state: &mut SliderDragState,
) -> Option<(WidgetId, f64, Option<DualSliderHandle>)> {
drag_state.take_value()
}
pub fn handle_slider_scroll(
drag_state: &SliderDragState,
delta: f64,
track_info: &SliderTrackInfo,
current_value: f64,
step: f64,
) -> Option<f64> {
let _ = drag_state;
let effective_step = if step > 0.0 {
step
} else {
let range = track_info.max_val - track_info.min_val;
if range > 100.0 {
1.0
} else if range > 10.0 {
0.1
} else {
0.01
}
};
let adjustment = -delta.signum() * effective_step;
let new_value = current_value + adjustment;
Some(clamp_step(new_value, track_info.min_val, track_info.max_val, step))
}
pub fn handle_slider_click(
track_info: &SliderTrackInfo,
x: f64,
_current_value: f64,
) -> Option<f64> {
let hit = x >= track_info.track_x - 2.0
&& x <= track_info.track_x + track_info.track_width + 2.0;
if !hit {
return None;
}
Some(pixel_to_value(
x,
track_info.track_x,
track_info.track_width,
track_info.min_val,
track_info.max_val,
))
}
pub fn handle_slider_text_input(
_field_id: &str,
text: &str,
config: &SliderConfig,
) -> Option<f64> {
text.trim()
.parse::<f64>()
.ok()
.map(|v| v.clamp(config.min, config.max))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrowDirection {
Left,
Right,
}
pub fn handle_slider_arrow_key(
current_value: f64,
direction: ArrowDirection,
step: f64,
min: f64,
max: f64,
) -> Option<f64> {
if step <= 0.0 {
return None;
}
let delta = match direction {
ArrowDirection::Left => -step,
ArrowDirection::Right => step,
};
Some(clamp_step(current_value + delta, min, max, step))
}
pub fn register_input_coordinator_slider(
coord: &mut InputCoordinator,
id: impl Into<WidgetId>,
rect: Rect,
layer: &LayerId,
state: &mut SliderDragState,
) {
let _ = state; register(coord, id, rect, layer);
}
pub fn register_context_manager_slider(
ctx: &mut ContextManager,
render: &mut dyn RenderContext,
id: impl Into<WidgetId>,
rect: Rect,
layer: &LayerId,
widget_state: WidgetState,
view: &SliderView,
settings: &SliderSettings,
) {
let id: WidgetId = id.into();
let state = ctx.registry.get_or_insert_with(id.clone(), SliderDragState::default);
register_input_coordinator_slider(&mut ctx.input, id, rect, layer, state);
draw_slider(render, rect, widget_state, view, settings);
}
pub fn register_layout_manager_slider<P: DockPanel>(
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
parent: LayoutNodeId,
id: impl Into<WidgetId>,
rect: Rect,
widget_state: WidgetState,
view: &SliderView,
settings: &SliderSettings,
) {
let id: WidgetId = id.into();
let layer = layout.compute_layer_for(parent);
layout.tree_mut().add_widget(parent, WidgetNode { id: id.clone(), kind: WidgetKind::Slider, rect, sense: Sense::CLICK_AND_DRAG, label: None });
register_context_manager_slider(
layout.ctx_mut(), render, id, rect, &layer, widget_state, view, settings,
);
}