use std::fmt::Debug;
use super::DragHandle;
use kas::prelude::*;
#[handler(send=noauto, msg = u32)]
#[derive(Clone, Debug, Default, Widget)]
pub struct ScrollBar<D: Directional> {
#[widget_core]
core: CoreData,
direction: D,
min_handle_len: u32,
handle_len: u32,
handle_value: u32, max_value: u32,
value: u32,
#[widget]
handle: DragHandle,
}
impl<D: Directional + Default> ScrollBar<D> {
pub fn new() -> Self {
ScrollBar::new_with_direction(D::default())
}
}
impl<D: Directional> ScrollBar<D> {
#[inline]
pub fn new_with_direction(direction: D) -> Self {
ScrollBar {
core: Default::default(),
direction,
min_handle_len: 0,
handle_len: 0,
handle_value: 1,
max_value: 0,
value: 0,
handle: DragHandle::new(),
}
}
#[inline]
pub fn with_limits(mut self, max_value: u32, handle_value: u32) -> Self {
let _ = self.set_limits(max_value, handle_value);
self
}
#[inline]
pub fn with_value(mut self, value: u32) -> Self {
self.value = value;
self
}
pub fn set_limits(&mut self, max_value: u32, handle_value: u32) -> TkAction {
self.handle_value = handle_value.max(1);
self.max_value = max_value;
self.value = self.value.min(self.max_value);
self.update_handle()
}
#[inline]
pub fn value(&self) -> u32 {
self.value
}
pub fn set_value(&mut self, value: u32) -> TkAction {
let value = value.min(self.max_value);
if value == self.value {
TkAction::None
} else {
self.value = value;
self.handle.set_offset(self.offset()).1
}
}
#[inline]
fn len(&self) -> u32 {
match self.direction.is_vertical() {
false => self.core.rect.size.0,
true => self.core.rect.size.1,
}
}
fn update_handle(&mut self) -> TkAction {
let len = self.len();
let total = self.max_value as u64 + self.handle_value as u64;
let handle_len = self.handle_value as u64 * len as u64 / total;
self.handle_len = (handle_len as u32).max(self.min_handle_len).min(len);
let mut size = self.core.rect.size;
if self.direction.is_horizontal() {
size.0 = self.handle_len;
} else {
size.1 = self.handle_len;
}
self.handle.set_size_and_offset(size, self.offset())
}
fn offset(&self) -> Coord {
let len = self.len() - self.handle_len;
let lhs = self.value as u64 * len as u64;
let rhs = self.max_value as u64;
let mut pos = if rhs == 0 {
0
} else {
(((lhs + (rhs / 2)) / rhs) as u32).min(len)
};
if self.direction.is_reversed() {
pos = len - pos;
}
match self.direction.is_vertical() {
false => Coord(pos as i32, 0),
true => Coord(0, pos as i32),
}
}
fn set_offset(&mut self, offset: Coord) -> bool {
let len = self.len() - self.handle_len;
let mut offset = match self.direction.is_vertical() {
false => offset.0,
true => offset.1,
} as u32;
if self.direction.is_reversed() {
offset = len - offset;
}
let lhs = offset as u64 * self.max_value as u64;
let rhs = len as u64;
if rhs == 0 {
debug_assert_eq!(self.value, 0);
return false;
}
let value = ((lhs + (rhs / 2)) / rhs) as u32;
let value = value.min(self.max_value);
if value != self.value {
self.value = value;
return true;
}
false
}
}
impl<D: Directional> Layout for ScrollBar<D> {
fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules {
let (size, min_len) = size_handle.scrollbar();
self.min_handle_len = size.0;
let margins = (0, 0);
if self.direction.is_vertical() == axis.is_vertical() {
SizeRules::new(min_len, min_len, margins, StretchPolicy::HighUtility)
} else {
SizeRules::fixed(size.1, margins)
}
}
fn set_rect(&mut self, rect: Rect, align: AlignHints) {
self.core.rect = rect;
self.handle.set_rect(rect, align);
let _ = self.update_handle();
}
fn find_id(&self, coord: Coord) -> Option<WidgetId> {
if !self.rect().contains(coord) {
return None;
}
self.handle.find_id(coord).or(Some(self.id()))
}
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
let dir = self.direction.as_direction();
let state = self.handle.input_state(mgr, disabled);
draw_handle.scrollbar(self.core.rect, self.handle.rect(), dir, state);
}
}
impl<D: Directional> event::SendEvent for ScrollBar<D> {
fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response<Self::Msg> {
if self.is_disabled() {
return Response::Unhandled(event);
}
let offset = if id <= self.handle.id() {
match self.handle.send(mgr, id, event).try_into() {
Ok(res) => return res,
Err(offset) => offset,
}
} else {
match event {
Event::PressStart { source, coord, .. } => {
self.handle.handle_press_on_track(mgr, source, coord)
}
ev @ _ => return Response::Unhandled(ev),
}
};
if self.set_offset(offset) {
mgr.redraw(self.handle.id());
Response::Msg(self.value)
} else {
Response::None
}
}
}