use tessera_ui::{Constraint, DimensionValue, Dp, ParentConstraint, Px, PxPosition};
use super::{HANDLE_GAP, MIN_TOUCH_TARGET, STOP_INDICATOR_DIAMETER, SliderArgs, SliderSize};
struct SliderSpecs {
track_height: Dp,
handle_height: Dp,
track_corner_radius: Dp,
icon_size: Option<Dp>,
}
fn get_slider_specs(size: SliderSize) -> SliderSpecs {
match size {
SliderSize::ExtraSmall => SliderSpecs {
track_height: Dp(16.0),
handle_height: Dp(44.0),
track_corner_radius: Dp(8.0),
icon_size: None,
},
SliderSize::Small => SliderSpecs {
track_height: Dp(24.0),
handle_height: Dp(44.0),
track_corner_radius: Dp(8.0),
icon_size: None,
},
SliderSize::Medium => SliderSpecs {
track_height: Dp(40.0),
handle_height: Dp(52.0),
track_corner_radius: Dp(12.0),
icon_size: Some(Dp(24.0)),
},
SliderSize::Large => SliderSpecs {
track_height: Dp(56.0),
handle_height: Dp(68.0),
track_corner_radius: Dp(16.0),
icon_size: Some(Dp(24.0)),
},
SliderSize::ExtraLarge => SliderSpecs {
track_height: Dp(96.0),
handle_height: Dp(108.0),
track_corner_radius: Dp(28.0),
icon_size: Some(Dp(32.0)),
},
}
}
const INNER_CORNER_RADIUS: Dp = Dp(2.0);
#[derive(Clone, Copy)]
pub(super) struct SliderLayout {
pub component_width: Px,
pub component_height: Px,
pub track_total_width: Px,
pub track_height: Px,
pub track_corner_radius: Dp,
pub inner_corner_radius: Dp,
pub track_y: Px,
pub handle_width: Px,
pub handle_height: Px,
pub handle_y: Px,
pub handle_gap: Px,
pub stop_indicator_diameter: Px,
pub stop_indicator_y: Px,
pub show_stop_indicator: bool,
pub icon_size: Option<Dp>,
}
impl SliderLayout {
pub fn active_width(&self, value: f32) -> Px {
let clamped = value.clamp(0.0, 1.0);
Px::saturating_from_f32(self.track_total_width.to_f32() * clamped)
}
pub fn inactive_width(&self, value: f32) -> Px {
let active = self.active_width(value);
Px((self.track_total_width.0 - active.0).max(0))
}
pub fn center_child_offset(&self, width: Px) -> Px {
Px(width.0 / 2)
}
pub fn handle_center(&self, value: f32) -> PxPosition {
let active_width = self.active_width(value);
let center_x =
active_width.to_f32() + self.handle_gap.to_f32() + self.handle_width.to_f32() / 2.0;
let max_x = (self.component_width.to_f32() - self.handle_width.to_f32() / 2.0).max(0.0);
let clamped_x = center_x.clamp(self.handle_width.to_f32() / 2.0, max_x);
PxPosition::new(
Px(clamped_x.round() as i32),
Px(self.component_height.0 / 2),
)
}
}
#[derive(Clone, Copy)]
pub(super) struct CenteredSliderLayout {
pub base: SliderLayout,
}
pub(super) struct CenteredSegments {
pub left_inactive: (Px, Px), pub active: (Px, Px), pub right_inactive: (Px, Px), pub handle_center: PxPosition,
}
impl CenteredSliderLayout {
pub fn segments(&self, value: f32) -> CenteredSegments {
let value = value.clamp(0.0, 1.0);
let w = self.base.component_width.to_f32();
let h_w = self.base.handle_width.to_f32();
let h_gap = self.base.handle_gap.to_f32(); let center_x_track = w / 2.0;
let track_total_length = self.base.track_total_width.to_f32();
let handle_center_x_raw = (value * track_total_length) + h_gap + (h_w / 2.0);
let min_handle_center_x = h_w / 2.0; let max_handle_center_x = w - h_w / 2.0; let handle_center_x = handle_center_x_raw.clamp(min_handle_center_x, max_handle_center_x);
let handle_left = handle_center_x - h_w / 2.0;
let handle_right = handle_center_x + h_w / 2.0;
let (li_x, li_w, a_x, a_w, ri_x, ri_w): (f32, f32, f32, f32, f32, f32) = if value > 0.5 {
let li_x_calc = 0.0;
let li_w_calc = (center_x_track - h_gap / 2.0).max(0.0);
let a_x_calc = center_x_track + h_gap / 2.0;
let a_w_calc = (handle_left - h_gap - a_x_calc).max(0.0);
let ri_x_calc = handle_right + h_gap;
let ri_w_calc = (w - ri_x_calc).max(0.0);
(
li_x_calc, li_w_calc, a_x_calc, a_w_calc, ri_x_calc, ri_w_calc,
)
} else {
let li_x_calc = 0.0;
let li_w_calc = (handle_left - h_gap).max(0.0);
let a_x_calc = handle_right + h_gap;
let a_w_calc = (center_x_track - h_gap / 2.0 - a_x_calc).max(0.0);
let ri_x_calc = center_x_track + h_gap / 2.0;
let ri_w_calc = (w - ri_x_calc).max(0.0);
(
li_x_calc, li_w_calc, a_x_calc, a_w_calc, ri_x_calc, ri_w_calc,
)
};
CenteredSegments {
left_inactive: (Px(li_x.round() as i32), Px(li_w.round() as i32)),
active: (Px(a_x.round() as i32), Px(a_w.round() as i32)),
right_inactive: (Px(ri_x.round() as i32), Px(ri_w.round() as i32)),
handle_center: PxPosition::new(
Px(handle_center_x.round() as i32),
Px(self.base.component_height.0 / 2),
),
}
}
pub fn stop_indicator_offset(&self) -> Px {
self.base.track_corner_radius.to_px()
}
}
pub(super) fn resolve_component_width(
args: &SliderArgs,
parent_constraint: ParentConstraint<'_>,
) -> Px {
let specs = get_slider_specs(args.size);
let fallback = Dp(260.0).to_px();
let merged = Constraint::new(
parent_constraint.width(),
DimensionValue::Fixed(specs.track_height.to_px()),
)
.merge(parent_constraint);
match merged.width {
DimensionValue::Fixed(px) => px,
DimensionValue::Fill { max, .. } | DimensionValue::Wrap { max, .. } => {
max.unwrap_or(fallback)
}
}
}
pub(super) fn fallback_component_width(args: &SliderArgs) -> Px {
let _ = args;
Dp(260.0).to_px()
}
fn slider_layout_from_parts(
size: SliderSize,
show_stop_indicator: bool,
component_width: Px,
handle_width: Px,
) -> SliderLayout {
let specs = get_slider_specs(size);
let track_height = specs.track_height.to_px();
let touch_target_height = MIN_TOUCH_TARGET.to_px();
let handle_gap = HANDLE_GAP.to_px();
let handle_height = specs.handle_height.to_px();
let stop_indicator_diameter = STOP_INDICATOR_DIAMETER.to_px();
let track_corner_radius = specs.track_corner_radius;
let track_total_width = Px((component_width.0 - handle_width.0 - handle_gap.0 * 2).max(0));
let component_height = Px(track_height
.0
.max(handle_height.0)
.max(touch_target_height.0));
let track_y = Px((component_height.0 - track_height.0) / 2);
SliderLayout {
component_width,
component_height,
track_total_width,
track_height,
track_corner_radius,
inner_corner_radius: INNER_CORNER_RADIUS,
track_y,
handle_width,
handle_height,
handle_gap,
handle_y: Px((component_height.0 - handle_height.0) / 2),
stop_indicator_diameter,
stop_indicator_y: Px((component_height.0 - stop_indicator_diameter.0) / 2),
show_stop_indicator,
icon_size: specs.icon_size,
}
}
pub(super) fn slider_layout_with_handle_width(
args: &SliderArgs,
component_width: Px,
handle_width: Px,
) -> SliderLayout {
slider_layout_from_parts(
args.size,
args.show_stop_indicator,
component_width,
handle_width,
)
}
#[derive(Clone, Copy)]
pub(super) struct RangeSliderLayout {
pub base: SliderLayout,
}
pub(super) struct RangeSegments {
pub left_inactive: (Px, Px), pub active: (Px, Px), pub right_inactive: (Px, Px), pub start_handle_center: PxPosition,
pub end_handle_center: PxPosition,
}
impl RangeSliderLayout {
pub fn segments(
&self,
start: f32,
end: f32,
start_handle_width: Px,
end_handle_width: Px,
) -> RangeSegments {
let start = start.clamp(0.0, 1.0);
let end = end.clamp(start, 1.0);
let w = self.base.component_width.to_f32();
let gap = self.base.handle_gap.to_f32();
let start_half = start_handle_width.to_f32() / 2.0;
let end_half = end_handle_width.to_f32() / 2.0;
let track_total = (w - start_half - end_half - gap * 2.0).max(0.0);
let start_center_raw = (start * track_total) + gap + start_half;
let end_center_raw = (end * track_total) + gap + start_half;
let start_min = gap + start_half;
let start_max = (w - gap - start_half).max(start_min);
let end_min = start_min;
let end_max = (w - gap - end_half).max(end_min);
let start_handle_center_x = start_center_raw.clamp(start_min, start_max);
let end_handle_center_x = end_center_raw.clamp(end_min, end_max);
let start_handle_right = start_handle_center_x + start_half;
let end_handle_left = end_handle_center_x - end_half;
let start_handle_left = start_handle_center_x - start_half;
let li_end = (start_handle_left - gap).max(0.0);
let li_w = li_end;
let li_x: f32 = 0.0;
let a_start = start_handle_right + gap;
let a_end = (end_handle_left - gap).max(a_start);
let a_w = a_end - a_start;
let a_x = a_start;
let end_handle_right = end_handle_center_x + end_half;
let ri_start = end_handle_right + gap;
let ri_end = w;
let ri_w = (ri_end - ri_start).max(0.0);
let ri_x = ri_start;
RangeSegments {
left_inactive: (Px(li_x.round() as i32), Px(li_w.round() as i32)),
active: (Px(a_x.round() as i32), Px(a_w.round() as i32)),
right_inactive: (Px(ri_x.round() as i32), Px(ri_w.round() as i32)),
start_handle_center: PxPosition::new(
Px(start_handle_center_x.round() as i32),
Px(self.base.component_height.0 / 2),
),
end_handle_center: PxPosition::new(
Px(end_handle_center_x.round() as i32),
Px(self.base.component_height.0 / 2),
),
}
}
}
pub(super) fn range_slider_layout(
args: &super::RangeSliderArgs,
component_width: Px,
) -> RangeSliderLayout {
RangeSliderLayout {
base: slider_layout_from_parts(
args.size,
args.show_stop_indicator,
component_width,
args.thumb_diameter.to_px(),
),
}
}