use {
Backend,
Color,
Colorable,
Dimension,
IndexSlot,
Positionable,
Range,
Rect,
Rectangle,
Scalar,
Sizeable,
Ui,
};
use graph;
use std;
use utils;
use widget::{self, Widget};
use widget::scroll::{self, X, Y};
pub struct Scrollbar<A> {
common: widget::CommonBuilder,
style: Style,
widget: widget::Index,
axis: std::marker::PhantomData<A>,
}
pub trait Axis: scroll::Axis + Sized {
fn track_rect(container: Rect, thickness: Scalar) -> Rect;
fn handle_rect(perpendicular_track_range: Range, handle_range: Range) -> Rect;
fn scroll_state(widget: &graph::Container) -> Option<&scroll::State<Self>>;
fn default_x_dimension<B: Backend>(scrollbar: &Scrollbar<Self>, ui: &Ui<B>) -> Dimension;
fn default_y_dimension<B: Backend>(scrollbar: &Scrollbar<Self>, ui: &Ui<B>) -> Dimension;
fn to_2d(scalar: Scalar) -> [Scalar; 2];
}
pub const KIND: widget::Kind = "Scrollbar";
widget_style!{
KIND;
style Style {
- color: Color { theme.frame_color }
- thickness: Scalar { 10.0 }
- auto_hide: bool { false }
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct State {
track_idx: IndexSlot,
handle_idx: IndexSlot,
}
impl<A> Scrollbar<A> {
fn new(widget: widget::Index) -> Self {
Scrollbar {
common: widget::CommonBuilder::new(),
style: Style::new(),
widget: widget,
axis: std::marker::PhantomData,
}
}
pub fn auto_hide(mut self, auto_hide: bool) -> Self {
self.style.auto_hide = Some(auto_hide);
self
}
}
impl Scrollbar<X> {
pub fn x_axis<I: Into<widget::Index> + Copy>(widget: I) -> Self {
let widget = widget.into();
Scrollbar::new(widget)
.align_middle_x_of(widget)
.align_bottom_of(widget)
}
}
impl Scrollbar<Y> {
pub fn y_axis<I: Into<widget::Index> + Copy>(widget: I) -> Self {
let widget = widget.into();
Scrollbar::new(widget)
.align_middle_y_of(widget)
.align_right_of(widget)
}
}
impl<A> Widget for Scrollbar<A>
where A: Axis,
{
type State = State;
type Style = Style;
fn common(&self) -> &widget::CommonBuilder {
&self.common
}
fn common_mut(&mut self) -> &mut widget::CommonBuilder {
&mut self.common
}
fn unique_kind(&self) -> &'static str {
KIND
}
fn init_state(&self) -> State {
State {
track_idx: IndexSlot::new(),
handle_idx: IndexSlot::new(),
}
}
fn style(&self) -> Style {
self.style.clone()
}
fn default_x_dimension<B: Backend>(&self, ui: &Ui<B>) -> Dimension {
A::default_x_dimension(self, ui)
}
fn default_y_dimension<B: Backend>(&self, ui: &Ui<B>) -> Dimension {
A::default_y_dimension(self, ui)
}
fn update<B: Backend>(self, args: widget::UpdateArgs<Self, B>) {
let widget::UpdateArgs { idx, state, rect, style, mut ui, .. } = args;
let Scrollbar { widget, .. } = self;
let color = style.color(ui.theme());
let (offset_bounds, offset, scrollable_range_len, is_scrolling) =
match ui.widget_graph().widget(widget) {
Some(widget) => match A::scroll_state(widget) {
Some(scroll) =>
(scroll.offset_bounds,
scroll.offset,
scroll.scrollable_range_len,
scroll.is_scrolling),
None => return,
},
None => return,
};
let handle_rect = {
let perpendicular_track_range = A::perpendicular_range(rect);
let track_range = A::parallel_range(rect);
let track_len = track_range.len();
let len = track_len * (track_len / scrollable_range_len);
let handle_range = Range::from_pos_and_len(0.0, len);
let pos = {
let pos_min = handle_range.align_start_of(track_range).middle();
let pos_max = handle_range.align_end_of(track_range).middle();
let pos_bounds = Range::new(pos_min, pos_max);
offset_bounds.map_value_to(offset, &pos_bounds)
};
let range = Range::from_pos_and_len(pos, len);
A::handle_rect(perpendicular_track_range, range)
};
let handle_range = A::parallel_range(handle_rect);
let handle_pos_range_len = || {
let track_range = A::parallel_range(rect);
let handle_pos_at_start = handle_range.align_start_of(track_range).middle();
let handle_pos_at_end = handle_range.align_end_of(track_range).middle();
let handle_pos_range = Range::new(handle_pos_at_start, handle_pos_at_end);
handle_pos_range.len()
};
let mut additional_offset = 0.0;
for widget_event in ui.widget_input(idx).events() {
use event;
use input;
match widget_event {
event::Widget::Press(press) => {
if let event::Button::Mouse(input::MouseButton::Left, xy) = press.button {
let abs_xy = utils::vec2_add(xy, rect.xy());
if rect.is_over(abs_xy) && !handle_rect.is_over(abs_xy) {
let handle_pos_range_len = handle_pos_range_len();
let offset_range_len = offset_bounds.len();
let mouse_scalar = A::mouse_scalar(xy);
let pos_offset = mouse_scalar - handle_range.middle();
let offset = utils::map_range(pos_offset,
0.0, handle_pos_range_len,
0.0, offset_range_len);
additional_offset += -offset;
}
}
},
event::Widget::Drag(drag) if drag.button == input::MouseButton::Left => {
let handle_pos_range_len = handle_pos_range_len();
let offset_range_len = offset_bounds.len();
let from_scalar = A::mouse_scalar(drag.from);
let to_scalar = A::mouse_scalar(drag.to);
let pos_offset = to_scalar - from_scalar;
let offset = utils::map_range(pos_offset,
0.0, handle_pos_range_len,
0.0, offset_range_len);
additional_offset += -offset;
},
_ => (),
}
}
if additional_offset != 0.0 {
ui.scroll_widget(widget, A::to_2d(additional_offset));
}
let auto_hide = style.auto_hide(ui.theme());
if auto_hide {
let not_scrollable = offset_bounds.magnitude().is_sign_positive();
let no_offset = additional_offset == 0.0;
let no_mouse_interaction = ui.widget_input(idx).mouse().is_none();
if not_scrollable || (!is_scrolling && no_offset && no_mouse_interaction) {
return;
}
}
let track_idx = state.track_idx.get(&mut ui);
let track_color = color.alpha(0.25);
Rectangle::fill(rect.dim())
.xy(rect.xy())
.color(track_color)
.graphics_for(idx)
.parent(idx)
.set(track_idx, &mut ui);
let handle_idx = state.handle_idx.get(&mut ui);
Rectangle::fill(handle_rect.dim())
.xy(handle_rect.xy())
.color(color)
.graphics_for(idx)
.parent(idx)
.set(handle_idx, &mut ui);
}
}
impl Axis for X {
fn track_rect(container: Rect, thickness: Scalar) -> Rect {
let h = thickness;
let w = container.w();
let x = container.x();
Rect::from_xy_dim([x, 0.0], [w, h]).align_bottom_of(container)
}
fn handle_rect(perpendicular_track_range: Range, handle_range: Range) -> Rect {
Rect {
x: handle_range,
y: perpendicular_track_range,
}
}
fn scroll_state(widget: &graph::Container) -> Option<&scroll::State<Self>> {
widget.maybe_x_scroll_state.as_ref()
}
fn default_x_dimension<B: Backend>(scrollbar: &Scrollbar<Self>, _ui: &Ui<B>) -> Dimension {
Dimension::Of(scrollbar.widget, None)
}
fn default_y_dimension<B: Backend>(scrollbar: &Scrollbar<Self>, ui: &Ui<B>) -> Dimension {
Dimension::Absolute(scrollbar.style.thickness(&ui.theme))
}
fn to_2d(scalar: Scalar) -> [Scalar; 2] {
[scalar, 0.0]
}
}
impl Axis for Y {
fn track_rect(container: Rect, thickness: Scalar) -> Rect {
let w = thickness;
let h = container.h();
let y = container.y();
Rect::from_xy_dim([0.0, y], [w, h]).align_right_of(container)
}
fn handle_rect(perpendicular_track_range: Range, handle_range: Range) -> Rect {
Rect {
x: perpendicular_track_range,
y: handle_range,
}
}
fn scroll_state(widget: &graph::Container) -> Option<&scroll::State<Self>> {
widget.maybe_y_scroll_state.as_ref()
}
fn default_x_dimension<B: Backend>(scrollbar: &Scrollbar<Self>, ui: &Ui<B>) -> Dimension {
Dimension::Absolute(scrollbar.style.thickness(&ui.theme))
}
fn default_y_dimension<B: Backend>(scrollbar: &Scrollbar<Self>, _ui: &Ui<B>) -> Dimension {
Dimension::Of(scrollbar.widget, None)
}
fn to_2d(scalar: Scalar) -> [Scalar; 2] {
[0.0, scalar]
}
}
impl<A> Colorable for Scrollbar<A> {
builder_method!(color { style.color = Some(Color) });
}