pub mod editable;
pub mod image_object;
pub mod scrollable;
mod svg_object;
use crate as deft;
use crate::base::{EventContext, Rect};
use crate::canvas_util::CanvasHelper;
use crate::element::scroll::ScrollBarStrategy;
use crate::element::ElementWeak;
use crate::event::{Event, MouseDownEvent, MouseMoveEvent, MouseUpEvent, WheelEvent};
use crate::render::RenderFn;
use crate::timer::{set_interval, set_timeout, TimerHandle};
use deft_macros::mrc_object;
use skia_safe::{Color, Paint, PaintStyle};
pub enum ScrollBarDirection {
Horizontal,
Vertical,
}
#[mrc_object]
pub struct ScrollBar {
direction: ScrollBarDirection,
length: f32,
scroll_length: f32,
thickness: f32,
thumb_rect: Rect,
thumb_background_color: Color,
track_rect: Rect,
track_background_color: Color,
scroll_offset: f32,
scroll_begin_info: Option<(f32, f32)>,
padding: f32,
strategy: ScrollBarStrategy,
scroll_callback: Box<dyn FnMut(f32)>,
auto_scroll_timer: Option<TimerHandle>,
}
impl ScrollBar {
pub fn new_horizontal() -> Self {
Self::new(ScrollBarDirection::Horizontal)
}
pub fn new_vertical() -> Self {
Self::new(ScrollBarDirection::Vertical)
}
fn new(direction: ScrollBarDirection) -> Self {
ScrollBarData {
direction,
thickness: 14.0,
length: 0.0,
scroll_length: 0.0,
thumb_rect: Rect::new(0.0, 0.0, 0.0, 0.0),
scroll_offset: 0.0,
scroll_begin_info: None,
padding: 0.0,
track_rect: Rect::new(0.0, 0.0, 0.0, 0.0),
strategy: ScrollBarStrategy::Never,
scroll_callback: Box::new(|_| {}),
thumb_background_color: Color::from_rgb(0xC1, 0xC1, 0xC1),
track_background_color: Color::from_rgb(0xE1, 0xE1, 0xE1),
auto_scroll_timer: None,
}
.to_ref()
}
pub fn is_scrollable(&self) -> bool {
self.scroll_length > self.length
}
pub fn get_max_scroll_offset(&self) -> f32 {
(self.scroll_length - self.length).max(0.0)
}
pub fn set_scroll_callback<F: FnMut(f32) + 'static>(&mut self, cb: F) {
self.scroll_callback = Box::new(cb);
}
pub fn set_length(&mut self, length: f32, scroll_length: f32, view_length: f32) {
self.length = length;
self.scroll_length = scroll_length;
let visible_thickness = match self.strategy {
ScrollBarStrategy::Never => 0.0,
ScrollBarStrategy::Auto => {
if self.is_scrollable() {
self.thickness
} else {
0.0
}
}
ScrollBarStrategy::Always => self.thickness,
};
self.padding = view_length - visible_thickness;
let (width, height, x, y) = match self.direction {
ScrollBarDirection::Horizontal => (self.length, visible_thickness, 0.0, self.padding),
ScrollBarDirection::Vertical => (visible_thickness, self.length, self.padding, 0.0),
};
self.track_rect = Rect::new(x, y, width, height);
self.update_scroll_offset(self.scroll_offset.min(self.get_max_scroll_offset()));
self.update_thumb_rect();
}
pub fn set_thickness(&mut self, thickness: f32) {
self.thickness = thickness;
}
pub fn set_strategy(&mut self, strategy: ScrollBarStrategy) {
self.strategy = strategy;
}
pub fn thickness(&self) -> f32 {
self.thickness
}
pub fn visible_thickness(&self) -> f32 {
match self.direction {
ScrollBarDirection::Horizontal => self.track_rect.height,
ScrollBarDirection::Vertical => self.track_rect.width,
}
}
pub fn scroll_offset(&self) -> f32 {
self.scroll_offset
}
pub fn set_scroll_offset(&mut self, scroll_offset: f32) {
self.update_scroll_offset(scroll_offset);
}
pub fn scroll_into_view(&mut self, offset: f32, length: f32) -> bool {
let start = offset - self.scroll_offset;
if start < 0.0 {
self.update_scroll_offset(offset);
return true;
}
let end = offset + length - self.scroll_offset;
if end > self.length {
self.update_scroll_offset(self.scroll_offset + end - self.length);
return true;
}
false
}
fn update_thumb_rect(&mut self) {
let thumb_length = f32::max(20.0, self.length / self.scroll_length * self.length);
let thumb_offset =
self.scroll_offset / (self.scroll_length - self.length) * (self.length - thumb_length);
let thumb_offset = thumb_offset.max(0.0).min(self.length - thumb_length);
let (x, y, thumb_width, thumb_height) = match self.direction {
ScrollBarDirection::Horizontal => {
(thumb_offset, self.padding, thumb_length, self.thickness)
}
ScrollBarDirection::Vertical => {
(self.padding, thumb_offset, self.thickness, thumb_length)
}
};
self.thumb_rect = Rect::new(x, y, thumb_width, thumb_height);
}
pub fn is_mouse_over(&self, x: f32, y: f32) -> bool {
self.track_rect.contains_point(x, y)
}
pub fn on_event(&mut self, event: &Event, _ctx: &mut EventContext<ElementWeak>) -> bool {
if let Some(e) = MouseDownEvent::cast(event) {
let d = e.0;
self.on_mouse_down(d.offset_x, d.offset_y)
} else if let Some(e) = MouseUpEvent::cast(event) {
let d = e.0;
self.on_mouse_up(d.offset_x, d.offset_y)
} else if let Some(e) = MouseMoveEvent::cast(event) {
let d = e.0;
self.on_mouse_move(d.offset_x, d.offset_y)
} else if let Some(e) = WheelEvent::cast(event) {
if self.is_scrollable() {
let delta_y = match e.delta_mode {
WheelEvent::DELTA_MODE_PIXEL => e.delta_y,
WheelEvent::DELTA_MODE_LINE => 40.0 * e.delta_y,
_ => 0.0,
};
let new_scroll_top = self.scroll_offset - delta_y;
self.update_scroll_offset(new_scroll_top);
true
} else {
false
}
} else {
false
}
}
pub fn on_mouse_down(&mut self, x: f32, y: f32) -> bool {
if self.thumb_rect.contains_point(x, y) {
let mouse_offset = match self.direction {
ScrollBarDirection::Horizontal => x,
ScrollBarDirection::Vertical => y,
};
self.scroll_begin_info = Some((mouse_offset, self.scroll_offset));
true
} else if self.track_rect.contains_point(x, y) {
let is_up = match self.direction {
ScrollBarDirection::Horizontal => x < self.thumb_rect.x,
ScrollBarDirection::Vertical => y < self.thumb_rect.y,
};
let pages = if is_up { -1.0 } else { 1.0 };
self.scroll_page(pages);
self.start_auto_scroll(pages * 3.0);
true
} else {
false
}
}
pub fn on_mouse_move(&mut self, x: f32, y: f32) -> bool {
if let Some((mouse_offset, scroll_offset)) = self.scroll_begin_info {
let (move_distance, thumb_length) = match self.direction {
ScrollBarDirection::Horizontal => (x - mouse_offset, self.thumb_rect.width),
ScrollBarDirection::Vertical => (y - mouse_offset, self.thumb_rect.height),
};
let scroll_distance =
move_distance / (self.length - thumb_length) * (self.scroll_length - self.length);
self.update_scroll_offset(scroll_offset + scroll_distance);
self.update_thumb_rect();
true
} else {
false
}
}
pub fn on_mouse_up(&mut self, _x: f32, _y: f32) -> bool {
if self.scroll_begin_info.is_some() {
self.scroll_begin_info = None;
true
} else {
self.auto_scroll_timer.take().is_some()
}
}
pub fn set_track_background_color(&mut self, color: Color) {
self.track_background_color = color;
}
pub fn set_thumb_background_color(&mut self, color: Color) {
self.thumb_background_color = color;
}
pub fn render(&self) -> RenderFn {
let bar_rect = self.track_rect.clone();
if bar_rect.is_empty() {
return RenderFn::empty();
}
let mut bar_paint = Paint::default();
bar_paint.set_style(PaintStyle::Fill);
bar_paint.set_color(self.track_background_color);
let thumb_rect = self.thumb_rect.clone();
let mut thumb_paint = Paint::default();
thumb_paint.set_style(PaintStyle::Fill);
thumb_paint.set_color(self.thumb_background_color);
RenderFn::new(move |painter| {
painter.canvas.session(|c| {
c.draw_rect(&bar_rect.to_skia_rect(), &bar_paint);
c.draw_rect(&thumb_rect.to_skia_rect(), &thumb_paint);
});
})
}
fn update_scroll_offset(&mut self, scroll_offset: f32) {
let new_scroll_offset = scroll_offset.clamp(0.0, self.get_max_scroll_offset());
if self.scroll_offset != new_scroll_offset {
self.scroll_offset = new_scroll_offset;
self.update_thumb_rect();
(self.scroll_callback)(new_scroll_offset);
}
}
fn scroll_page(&mut self, pages: f32) {
let new_scroll_offset = self.scroll_offset + self.length * pages;
self.update_scroll_offset(new_scroll_offset);
}
fn start_auto_scroll(&mut self, speed: f32) {
let mut me = self.clone();
let timer_handle = set_timeout(
move || {
let interval_timer = {
let me = me.clone();
set_interval(
move || {
me.clone().scroll_page(speed / 10.0);
},
100,
)
};
me.auto_scroll_timer = Some(interval_timer);
},
300,
);
self.auto_scroll_timer = Some(timer_handle);
}
}