use crate::axis::*;
use wrflib::*;
#[derive(Clone, Default)]
#[repr(C)]
struct ScrollBarIns {
base: QuadIns,
color: Vec4,
is_vertical: f32,
norm_handle: f32,
norm_scroll: f32,
}
static SHADER: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
QuadIns::SHADER,
code_fragment!(
r#"
instance color: vec4;
instance is_vertical: float;
instance norm_handle: float;
instance norm_scroll: float;
const border_radius: float = 1.5;
fn pixel() -> vec4 {
let df = Df::viewport(pos * rect_size);
if is_vertical > 0.5 {
df.box(1., rect_size.y * norm_scroll, rect_size.x * 0.5, rect_size.y * norm_handle, border_radius);
}
else {
df.box(rect_size.x * norm_scroll, 1., rect_size.x * norm_handle, rect_size.y * 0.5, border_radius);
}
return df.fill_keep(color);
}"#
),
],
..Shader::DEFAULT
};
#[derive(Debug)]
pub struct ScrollBar {
component_base: ComponentBase,
bg_area: Area,
bar_size: f32,
min_handle_size: f32, axis: Axis,
animator: Animator,
use_vertical_finger_scroll: bool,
smoothing: Option<f32>,
visible: bool,
bar_side_margin: f32,
view_area: Area,
view_total: f32, view_visible: f32, scroll_size: f32, scroll_pos: f32,
scroll_target: f32,
scroll_delta: f32,
drag_point: Option<f32>, }
#[derive(Clone, PartialEq, Debug)]
pub enum ScrollBarEvent {
None,
Scroll { scroll_pos: f32, view_total: f32, view_visible: f32 },
ScrollDone,
}
const ANIM_DEFAULT: Anim = Anim {
duration: 0.5,
tracks: &[Track::Vec4 { key_frames: &[(1.0, vec4(0.33, 0.33, 0.33, 1.))], ease: Ease::DEFAULT }],
..Anim::DEFAULT
};
const ANIM_OVER: Anim = Anim {
duration: 0.05,
tracks: &[Track::Vec4 { key_frames: &[(1.0, vec4(0.47, 0.47, 0.47, 1.))], ease: Ease::DEFAULT }],
..Anim::DEFAULT
};
const ANIM_DOWN: Anim = Anim {
duration: 0.05,
tracks: &[Track::Vec4 { key_frames: &[(1.0, vec4(0.6, 0.6, 0.6, 1.))], ease: Ease::DEFAULT }],
..Anim::DEFAULT
};
impl Default for ScrollBar {
fn default() -> Self {
Self {
component_base: Default::default(),
bar_size: 12.0,
min_handle_size: 30.0,
smoothing: None,
axis: Axis::Horizontal,
animator: Animator::default(),
bg_area: Area::Empty,
use_vertical_finger_scroll: false,
visible: false,
view_area: Area::Empty,
view_total: 0.0,
view_visible: 0.0,
bar_side_margin: 6.0,
scroll_size: 0.0,
scroll_pos: 0.0,
scroll_target: 0.0,
scroll_delta: 0.0,
drag_point: None,
}
}
}
impl ScrollBar {
fn animate(&mut self, cx: &mut Cx) {
let bg = self.bg_area.get_first_mut::<ScrollBarIns>(cx);
bg.color = self.animator.get_vec4(0);
}
#[must_use]
pub fn with_bar_size(self, bar_size: f32) -> Self {
Self { bar_size, ..self }
}
#[must_use]
pub fn with_smoothing(self, s: f32) -> Self {
Self { smoothing: Some(s), ..self }
}
#[must_use]
pub fn with_use_vertical_finger_scroll(self, use_vertical_finger_scroll: bool) -> Self {
Self { use_vertical_finger_scroll, ..self }
}
pub fn get_normalized_scroll_pos(&self) -> (f32, f32) {
let vy = self.view_visible / self.view_total;
if !self.visible {
return (0.0, 0.0);
}
let norm_handle = vy.max(self.min_handle_size / self.scroll_size);
let norm_scroll = (1. - norm_handle) * ((self.scroll_pos / self.view_total) / (1. - vy));
(norm_scroll, norm_handle)
}
pub fn set_scroll_pos_from_finger(&mut self, cx: &mut Cx, finger: f32) -> ScrollBarEvent {
let vy = self.view_visible / self.view_total;
let norm_handle = vy.max(self.min_handle_size / self.scroll_size);
let new_scroll_pos = ((self.view_total * (1. - vy) * (finger / self.scroll_size)) / (1. - norm_handle))
.max(0.)
.min(self.view_total - self.view_visible);
let changed = self.scroll_pos != new_scroll_pos;
self.scroll_pos = new_scroll_pos;
self.scroll_target = new_scroll_pos;
if changed {
self.update_shader_scroll_pos(cx);
return self.make_scroll_event();
}
ScrollBarEvent::None
}
fn update_shader_scroll_pos(&mut self, cx: &mut Cx) {
let bg = self.bg_area.get_first_mut::<ScrollBarIns>(cx);
bg.norm_scroll = self.get_normalized_scroll_pos().0;
}
pub fn make_scroll_event(&mut self) -> ScrollBarEvent {
ScrollBarEvent::Scroll { scroll_pos: self.scroll_pos, view_total: self.view_total, view_visible: self.view_visible }
}
pub fn move_towards_scroll_target(&mut self, cx: &mut Cx) -> bool {
if self.smoothing.is_none() {
return false;
}
if (self.scroll_target - self.scroll_pos).abs() < 0.01 {
return false;
}
if self.scroll_pos > self.scroll_target {
self.scroll_pos += (self.smoothing.unwrap() * self.scroll_delta).min(-1.);
if self.scroll_pos <= self.scroll_target {
self.scroll_pos = self.scroll_target;
self.update_shader_scroll_pos(cx);
return false;
}
} else {
self.scroll_pos += (self.smoothing.unwrap() * self.scroll_delta).max(1.);
if self.scroll_pos > self.scroll_target {
self.scroll_pos = self.scroll_target;
self.update_shader_scroll_pos(cx);
return false;
}
}
self.update_shader_scroll_pos(cx);
true
}
pub fn get_scroll_pos(&self) -> f32 {
self.scroll_pos
}
pub fn set_scroll_pos(&mut self, cx: &mut Cx, scroll_pos: f32) -> bool {
let scroll_pos = scroll_pos.min(self.view_total - self.view_visible).max(0.);
if self.scroll_pos != scroll_pos {
self.scroll_pos = scroll_pos;
self.scroll_target = scroll_pos;
self.update_shader_scroll_pos(cx);
cx.request_next_frame();
return true;
};
false
}
pub fn get_scroll_target(&mut self) -> f32 {
self.scroll_target
}
pub fn set_scroll_view_total(&mut self, _cx: &mut Cx, view_total: f32) {
self.view_total = view_total;
}
pub fn get_scroll_view_total(&self) -> f32 {
self.view_total
}
pub fn set_scroll_target(&mut self, cx: &mut Cx, scroll_pos_target: f32) -> bool {
let new_target = scroll_pos_target.min(self.view_total - self.view_visible).max(0.);
if self.scroll_target != new_target {
self.scroll_target = new_target;
self.scroll_delta = new_target - self.scroll_pos;
cx.request_next_frame();
return true;
};
false
}
pub fn scroll_into_view(&mut self, cx: &mut Cx, pos: f32, size: f32, smooth: bool) {
if pos < self.scroll_pos {
let scroll_to = pos;
if !smooth || self.smoothing.is_none() {
self.set_scroll_pos(cx, scroll_to);
} else {
self.set_scroll_target(cx, scroll_to);
}
} else if pos + size > self.scroll_pos + self.view_visible {
let scroll_to = (pos + size) - self.view_visible;
if pos + size > self.view_total {
self.view_total = pos + size;
}
if !smooth || self.smoothing.is_none() {
self.set_scroll_pos(cx, scroll_to);
} else {
self.set_scroll_target(cx, scroll_to);
}
}
}
pub fn handle(&mut self, cx: &mut Cx, event: &mut Event) -> ScrollBarEvent {
match event {
Event::FingerScroll(fe) => {
if !match self.axis {
Axis::Horizontal => fe.handled_x,
Axis::Vertical => fe.handled_y,
} {
let rect = self.view_area.get_rect_for_first_instance(cx);
if rect.contains(fe.abs) {
let scroll = match self.axis {
Axis::Horizontal => {
if self.use_vertical_finger_scroll {
fe.scroll.y
} else {
fe.scroll.x
}
}
Axis::Vertical => fe.scroll.y,
};
let scroll_pos = self.get_scroll_pos();
if self.set_scroll_pos(cx, scroll_pos + scroll) {
match self.axis {
Axis::Horizontal => fe.handled_x = true,
Axis::Vertical => fe.handled_y = true,
}
}
return self.make_scroll_event();
}
}
}
_ => (),
};
if self.visible {
if self.animator.handle(cx, event) {
self.animate(cx);
}
if let Event::NextFrame = event {
if self.move_towards_scroll_target(cx) {
cx.request_next_frame();
return self.make_scroll_event();
}
}
match event.hits(cx, &self.component_base, HitOpt::default()) {
Event::FingerDown(fe) => {
cx.keep_key_focus();
self.animator.play_anim(cx, ANIM_DOWN);
let rel = match self.axis {
Axis::Horizontal => fe.rel.x,
Axis::Vertical => fe.rel.y,
};
cx.set_down_mouse_cursor(MouseCursor::Default);
let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
let bar_start = norm_scroll * self.scroll_size;
let bar_size = norm_handle * self.scroll_size;
if rel < bar_start || rel > bar_start + bar_size {
self.drag_point = Some(bar_size * 0.5);
return self.set_scroll_pos_from_finger(cx, rel - self.drag_point.unwrap());
} else {
self.drag_point = Some(rel - bar_start); }
}
Event::FingerHover(fe) => {
if self.drag_point.is_none() {
cx.set_hover_mouse_cursor(MouseCursor::Default);
match fe.hover_state {
HoverState::In => {
self.animator.play_anim(cx, ANIM_OVER);
}
HoverState::Out => {
self.animator.play_anim(cx, ANIM_DEFAULT);
}
_ => (),
}
}
}
Event::FingerUp(fe) => {
self.drag_point = None;
if fe.is_over {
if fe.input_type.has_hovers() {
self.animator.play_anim(cx, ANIM_OVER);
} else {
self.animator.play_anim(cx, ANIM_DEFAULT);
}
} else {
self.animator.play_anim(cx, ANIM_DEFAULT);
}
return ScrollBarEvent::ScrollDone;
}
Event::FingerMove(fe) => {
if self.drag_point.is_none() {
} else {
match self.axis {
Axis::Horizontal => {
return self.set_scroll_pos_from_finger(cx, fe.rel.x - self.drag_point.unwrap());
}
Axis::Vertical => {
return self.set_scroll_pos_from_finger(cx, fe.rel.y - self.drag_point.unwrap());
}
}
}
}
_ => (),
};
}
ScrollBarEvent::None
}
pub fn draw(&mut self, cx: &mut Cx, axis: Axis, view_area: Area, view_rect: Rect, view_total: Vec2) -> f32 {
self.view_area = view_area;
self.axis = axis;
match self.axis {
Axis::Horizontal => {
self.visible = view_total.x > view_rect.size.x + 0.1;
self.scroll_size =
if view_total.y > view_rect.size.y + 0.1 { view_rect.size.x - self.bar_size } else { view_rect.size.x }
- self.bar_side_margin * 2.;
self.view_total = view_total.x;
self.view_visible = view_rect.size.x;
self.scroll_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
if self.visible {
let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
self.bg_area = cx.add_instances_with_scroll_sticky(
&SHADER,
&[ScrollBarIns {
base: QuadIns::from_rect(Rect {
pos: cx.get_box_origin() + vec2(self.bar_side_margin, view_rect.size.y - self.bar_size),
size: vec2(self.scroll_size, self.bar_size),
})
.with_draw_depth(2.5),
is_vertical: 0.0,
norm_handle,
norm_scroll,
..Default::default()
}],
true,
true,
);
}
}
Axis::Vertical => {
self.visible = view_total.y > view_rect.size.y + 0.1;
self.scroll_size =
if view_total.x > view_rect.size.x + 0.1 { view_rect.size.y - self.bar_size } else { view_rect.size.y }
- self.bar_side_margin * 2.;
self.view_total = view_total.y;
self.view_visible = view_rect.size.y;
self.scroll_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
if self.visible {
let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
self.bg_area = cx.add_instances_with_scroll_sticky(
&SHADER,
&[ScrollBarIns {
base: QuadIns::from_rect(Rect {
pos: cx.get_box_origin() + vec2(view_rect.size.x - self.bar_size, self.bar_side_margin),
size: vec2(self.bar_size, self.scroll_size),
})
.with_draw_depth(2.5),
is_vertical: 1.0,
norm_handle,
norm_scroll,
..Default::default()
}],
true,
true,
);
}
}
}
let clamped_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
if clamped_pos != self.scroll_pos {
self.scroll_pos = clamped_pos;
self.scroll_target = clamped_pos;
cx.request_next_frame();
}
if self.visible {
self.animator.draw(cx, ANIM_DEFAULT);
self.animate(cx);
}
self.component_base.register_component_area(cx, self.bg_area);
self.scroll_pos
}
}