1use graph;
4use position::{Dimension, Range, Rect, Scalar};
5use std;
6use utils;
7use widget::scroll::{self, X, Y};
8use widget::{self, Widget};
9use {Color, Colorable, Positionable, Ui};
10
11#[derive(WidgetCommon_)]
13pub struct Scrollbar<A> {
14 #[conrod(common_builder)]
15 common: widget::CommonBuilder,
16 style: Style,
17 widget: widget::Id,
18 axis: std::marker::PhantomData<A>,
19}
20
21pub trait Axis: scroll::Axis + Sized {
23 fn track_rect(container: Rect, thickness: Scalar) -> Rect;
26 fn handle_rect(perpendicular_track_range: Range, handle_range: Range) -> Rect;
28 fn scroll_state(widget: &graph::Container) -> Option<&scroll::State<Self>>;
30 fn default_x_dimension(scrollbar: &Scrollbar<Self>, ui: &Ui) -> Dimension;
33 fn default_y_dimension(scrollbar: &Scrollbar<Self>, ui: &Ui) -> Dimension;
36 fn to_2d(scalar: Scalar) -> [Scalar; 2];
38}
39
40#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
42pub struct Style {
43 #[conrod(default = "theme.border_color")]
45 pub color: Option<Color>,
46 #[conrod(default = "10.0")]
48 pub thickness: Option<Scalar>,
49 #[conrod(default = "false")]
54 pub auto_hide: Option<bool>,
55}
56
57widget_ids! {
58 struct Ids {
59 track,
60 handle,
61 }
62}
63
64pub struct State {
66 ids: Ids,
67}
68
69impl<A> Scrollbar<A> {
70 fn new(widget: widget::Id) -> Self {
72 Scrollbar {
73 common: widget::CommonBuilder::default(),
74 style: Style::default(),
75 widget: widget,
76 axis: std::marker::PhantomData,
77 }
78 }
79
80 pub fn auto_hide(mut self, auto_hide: bool) -> Self {
90 self.style.auto_hide = Some(auto_hide);
91 self
92 }
93
94 pub fn thickness(mut self, thickness: Scalar) -> Self {
100 self.style.thickness = Some(thickness);
101 self
102 }
103}
104
105impl Scrollbar<X> {
106 pub fn x_axis(widget: widget::Id) -> Self {
109 Scrollbar::new(widget)
110 .align_middle_x_of(widget)
111 .align_bottom_of(widget)
112 }
113}
114
115impl Scrollbar<Y> {
116 pub fn y_axis(widget: widget::Id) -> Self {
119 Scrollbar::new(widget)
120 .align_middle_y_of(widget)
121 .align_right_of(widget)
122 }
123}
124
125impl<A> Widget for Scrollbar<A>
126where
127 A: Axis,
128{
129 type State = State;
130 type Style = Style;
131 type Event = ();
132
133 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
134 State {
135 ids: Ids::new(id_gen),
136 }
137 }
138
139 fn style(&self) -> Self::Style {
140 self.style.clone()
141 }
142
143 fn default_x_dimension(&self, ui: &Ui) -> Dimension {
144 A::default_x_dimension(self, ui)
145 }
146
147 fn default_y_dimension(&self, ui: &Ui) -> Dimension {
148 A::default_y_dimension(self, ui)
149 }
150
151 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
152 let widget::UpdateArgs {
153 id,
154 state,
155 rect,
156 style,
157 ui,
158 ..
159 } = args;
160 let Scrollbar { widget, .. } = self;
161
162 let (offset_bounds, offset, scrollable_range_len, is_scrolling) =
164 match ui.widget_graph().widget(widget) {
165 Some(widget) => match A::scroll_state(widget) {
166 Some(scroll) => (
167 scroll.offset_bounds,
168 scroll.offset,
169 scroll.scrollable_range_len,
170 scroll.is_scrolling,
171 ),
172 None => return,
173 },
174 None => return,
175 };
176
177 let handle_rect = {
180 let perpendicular_track_range = A::perpendicular_range(rect);
181 let track_range = A::parallel_range(rect);
182 let track_len = track_range.len();
183 let len = if scrollable_range_len == 0.0 {
184 track_len
185 } else {
186 utils::clamp(
187 track_len * (1.0 - offset_bounds.len() / scrollable_range_len),
188 0.0,
189 track_len,
190 )
191 };
192 let handle_range = Range::from_pos_and_len(0.0, len);
193 let pos = {
194 let pos_min = handle_range.align_start_of(track_range).middle();
195 let pos_max = handle_range.align_end_of(track_range).middle();
196 let pos_bounds = Range::new(pos_min, pos_max);
197 offset_bounds.map_value_to(offset, &pos_bounds)
198 };
199 let range = Range::from_pos_and_len(pos, len);
200 A::handle_rect(perpendicular_track_range, range)
201 };
202
203 let handle_range = A::parallel_range(handle_rect);
204 let handle_pos_range_len = || {
205 let track_range = A::parallel_range(rect);
206 let handle_pos_at_start = handle_range.align_start_of(track_range).middle();
207 let handle_pos_at_end = handle_range.align_end_of(track_range).middle();
208 let handle_pos_range = Range::new(handle_pos_at_start, handle_pos_at_end);
209 handle_pos_range.len()
210 };
211
212 let mut additional_offset = 0.0;
214 for widget_event in ui.widget_input(id).events() {
215 use event;
216 use input;
217
218 match widget_event {
219 event::Widget::Press(press) => {
222 if let event::Button::Mouse(input::MouseButton::Left, xy) = press.button {
223 let abs_xy = utils::vec2_add(xy, rect.xy());
224 if rect.is_over(abs_xy) && !handle_rect.is_over(abs_xy) {
225 let handle_pos_range_len = handle_pos_range_len();
226 let offset_range_len = offset_bounds.len();
227 let mouse_scalar = A::mouse_scalar(abs_xy);
228 let pos_offset = mouse_scalar - handle_range.middle();
229 let offset = utils::map_range(
230 pos_offset,
231 0.0,
232 handle_pos_range_len,
233 0.0,
234 offset_range_len,
235 );
236 additional_offset += -offset;
237 }
238 }
239 }
240
241 event::Widget::Drag(drag) if drag.button == input::MouseButton::Left => {
243 let handle_pos_range_len = handle_pos_range_len();
244 let offset_range_len = offset_bounds.len();
245 let from_scalar = A::mouse_scalar(drag.from);
246 let to_scalar = A::mouse_scalar(drag.to);
247 let pos_offset = to_scalar - from_scalar;
248 let offset = utils::map_range(
249 pos_offset,
250 0.0,
251 handle_pos_range_len,
252 0.0,
253 offset_range_len,
254 );
255 additional_offset += -offset;
256 }
257
258 _ => (),
259 }
260 }
261
262 if additional_offset != 0.0 && !additional_offset.is_nan() {
264 ui.scroll_widget(widget, A::to_2d(additional_offset));
265 }
266
267 let auto_hide = style.auto_hide(ui.theme());
269 let not_scrollable = offset_bounds.magnitude().is_sign_positive();
270 if auto_hide {
271 let no_offset = additional_offset == 0.0;
272 let no_mouse_interaction = ui.widget_input(id).mouse().is_none();
273 if not_scrollable || (!is_scrolling && no_offset && no_mouse_interaction) {
274 return;
275 }
276 }
277
278 let color = style.color(ui.theme());
279 let color = if not_scrollable {
280 color
281 } else {
282 ui.widget_input(id)
283 .mouse()
284 .map(|m| {
285 if m.buttons.left().is_down() {
286 color.clicked()
287 } else {
288 color.highlighted()
289 }
290 })
291 .unwrap_or_else(|| color)
292 };
293
294 let track_color = color.alpha(0.25);
296 widget::Rectangle::fill(rect.dim())
297 .xy(rect.xy())
298 .color(track_color)
299 .graphics_for(id)
300 .parent(id)
301 .set(state.ids.track, ui);
302
303 widget::Rectangle::fill(handle_rect.dim())
306 .xy(handle_rect.xy())
307 .color(color)
308 .graphics_for(id)
309 .parent(id)
310 .set(state.ids.handle, ui);
311 }
312}
313
314impl Axis for X {
315 fn track_rect(container: Rect, thickness: Scalar) -> Rect {
316 let h = thickness;
317 let w = container.w();
318 let x = container.x();
319 Rect::from_xy_dim([x, 0.0], [w, h]).align_bottom_of(container)
320 }
321
322 fn handle_rect(perpendicular_track_range: Range, handle_range: Range) -> Rect {
323 Rect {
324 x: handle_range,
325 y: perpendicular_track_range,
326 }
327 }
328
329 fn scroll_state(widget: &graph::Container) -> Option<&scroll::State<Self>> {
330 widget.maybe_x_scroll_state.as_ref()
331 }
332
333 fn default_x_dimension(scrollbar: &Scrollbar<Self>, _ui: &Ui) -> Dimension {
334 Dimension::Of(scrollbar.widget, None)
335 }
336
337 fn default_y_dimension(scrollbar: &Scrollbar<Self>, ui: &Ui) -> Dimension {
338 Dimension::Absolute(scrollbar.style.thickness(&ui.theme))
339 }
340
341 fn to_2d(scalar: Scalar) -> [Scalar; 2] {
342 [scalar, 0.0]
343 }
344}
345
346impl Axis for Y {
347 fn track_rect(container: Rect, thickness: Scalar) -> Rect {
348 let w = thickness;
349 let h = container.h();
350 let y = container.y();
351 Rect::from_xy_dim([0.0, y], [w, h]).align_right_of(container)
352 }
353
354 fn handle_rect(perpendicular_track_range: Range, handle_range: Range) -> Rect {
355 Rect {
356 x: perpendicular_track_range,
357 y: handle_range,
358 }
359 }
360
361 fn scroll_state(widget: &graph::Container) -> Option<&scroll::State<Self>> {
362 widget.maybe_y_scroll_state.as_ref()
363 }
364
365 fn default_x_dimension(scrollbar: &Scrollbar<Self>, ui: &Ui) -> Dimension {
366 Dimension::Absolute(scrollbar.style.thickness(&ui.theme))
367 }
368
369 fn default_y_dimension(scrollbar: &Scrollbar<Self>, _ui: &Ui) -> Dimension {
370 Dimension::Of(scrollbar.widget, None)
371 }
372
373 fn to_2d(scalar: Scalar) -> [Scalar; 2] {
374 [0.0, scalar]
375 }
376}
377
378impl<A> Colorable for Scrollbar<A> {
379 builder_method!(color { style.color = Some(Color) });
380}