split_controller/
lib.rs

1//! A Piston library for handling split state and events.
2
3extern crate input;
4extern crate vecmath;
5
6use input::{Button, GenericEvent, MouseButton};
7
8use self::math::{is_inside, inside_pos, Matrix2d, Rectangle};
9
10mod math;
11
12const LEFT: u8 = 0x1;
13const RIGHT: u8 = 0x2;
14const TOP: u8 = 0x4;
15const BOTTOM: u8 = 0x8;
16
17/// Stores split layout settings.
18#[derive(Copy, Clone, PartialEq, Debug)]
19pub struct SplitLayoutSettings {
20    /// The border width.
21    pub border: f64,
22    /// The minimum size of center.
23    pub center_min_size: [f64; 2],
24    /// The initial value for left split.
25    pub left_value: f64,
26    /// The minimum value for left split.
27    pub left_min_value: f64,
28    /// The initial value for right split.
29    pub right_value: f64,
30    /// The minimum value for right split.
31    pub right_min_value: f64,
32    /// The initial value for top split.
33    pub top_value: f64,
34    /// The minimum value for top split.
35    pub top_min_value: f64,
36    /// The initial value for bottom split.
37    pub bottom_value: f64,
38    /// The minimum value for bottom split.
39    pub bottom_min_value: f64,
40    /// Locks left split.
41    pub lock_left: bool,
42    /// Locks right split.
43    pub lock_right: bool,
44    /// Locks top split.
45    pub lock_top: bool,
46    /// Locks bottom split.
47    pub lock_bottom: bool,
48}
49
50impl SplitLayoutSettings {
51    /// Creates a new `SplitLayoutSettings` object with values set to minimum.
52    ///
53    /// Work area minimum size is set to 1x1.
54    pub fn new(border: f64, min_value: f64) -> SplitLayoutSettings {
55        SplitLayoutSettings {
56            border: border,
57            center_min_size: [1.0; 2],
58            left_value: min_value,
59            left_min_value: min_value,
60            right_value: min_value,
61            right_min_value: min_value,
62            top_value: min_value,
63            top_min_value: min_value,
64            bottom_value: min_value,
65            bottom_min_value: min_value,
66            lock_left: false,
67            lock_right: false,
68            lock_top: false,
69            lock_bottom: false,
70        }
71    }
72
73    /// Set the value and minimum value for left split.
74    pub fn left(mut self, value: f64, min_value: f64) -> SplitLayoutSettings {
75        self.left_value = value;
76        self.left_min_value = min_value;
77        self
78    }
79
80    /// Sets the value and minimum value for right split.
81    pub fn right(mut self, value: f64, min_value: f64) -> SplitLayoutSettings {
82        self.right_value = value;
83        self.right_min_value = min_value;
84        self
85    }
86
87    /// Sets the value and minimum value for top split.
88    pub fn top(mut self, value: f64, min_value: f64) -> SplitLayoutSettings {
89        self.top_value = value;
90        self.top_min_value = min_value;
91        self
92    }
93
94    /// Sets the value and minimum value for bottom split.
95    pub fn bottom(mut self, value: f64, min_value: f64) -> SplitLayoutSettings {
96        self.bottom_value = value;
97        self.bottom_min_value = min_value;
98        self
99    }
100
101    /// Locks left split, sets the minimum value at the same time.
102    pub fn lock_left(mut self, value: f64) -> SplitLayoutSettings {
103        self.lock_left = true;
104        self.left_value = value;
105        self.left_min_value = value;
106        self
107    }
108
109    /// Locks right split, sets the minimum value at the same time.
110    pub fn lock_right(mut self, value: f64) -> SplitLayoutSettings {
111        self.lock_right = true;
112        self.right_value = value;
113        self.right_min_value = value;
114        self
115    }
116
117    /// Locks top split, sets the minimum value at the same time.
118    pub fn lock_top(mut self, value: f64) -> SplitLayoutSettings {
119        self.lock_top = true;
120        self.top_value = value;
121        self.top_min_value = value;
122        self
123    }
124
125    /// Locks bottom split, sets the minimum value at the same time.
126    pub fn lock_bottom(mut self, value: f64) -> SplitLayoutSettings {
127        self.lock_bottom = true;
128        self.bottom_value = value;
129        self.bottom_min_value = value;
130        self
131    }
132}
133
134/// Stores information about split layout.
135///
136/// The layout is split into left, right, top and bottom panel.
137pub struct SplitLayoutController {
138    /// The left split controller.
139    pub left: SplitController,
140    /// The right split controller.
141    pub right: SplitController,
142    /// The top split controller.
143    pub top: SplitController,
144    /// The bottom split controller.
145    pub bottom: SplitController,
146    // Center minimum size.
147    center_min_size: [f64; 2],
148    // Which splits are dragged.
149    drag_splits: u8,
150    // Which splits are locked.
151    lock_splits: u8,
152}
153
154impl SplitLayoutController {
155    /// Creates a new `SplitLayoutController`.
156    pub fn new(settings: &SplitLayoutSettings) -> SplitLayoutController {
157        SplitLayoutController {
158            left: SplitController::new(settings.left_value, settings.left_min_value,
159                                       settings.border, SplitOrientation::Left),
160            right: SplitController::new(settings.right_value, settings.right_min_value,
161                                        settings.border, SplitOrientation::Right),
162            top: SplitController::new(settings.top_value, settings.top_min_value,
163                                      settings.border, SplitOrientation::Top),
164            bottom: SplitController::new(settings.bottom_value, settings.bottom_min_value,
165                                         settings.border, SplitOrientation::Bottom),
166            center_min_size: settings.center_min_size,
167            drag_splits: 0,
168            lock_splits: if settings.lock_left {LEFT} else {0} |
169                         if settings.lock_right {RIGHT} else {0} |
170                         if settings.lock_top {TOP} else {0} |
171                         if settings.lock_bottom {BOTTOM} else {0},
172        }
173    }
174
175    /// Handles event.
176    pub fn event<E: GenericEvent>(&mut self, rect: Rectangle, transform: Matrix2d, e: &E) {
177        let bounds = self.bounds(rect);
178
179        if (self.lock_splits & TOP) != TOP {
180            if self.drag_splits == 0 || (self.drag_splits & TOP) == TOP {
181                let layout = self.top_bottom_layout();
182                let max_value = bounds[3] - self.bottom.value - self.center_min_size[1] -
183                          self.top.border - self.bottom.border;
184                self.top.event(layout, max_value, bounds, transform, e);
185            }
186        }
187        if (self.lock_splits & BOTTOM) != BOTTOM {
188            if self.drag_splits == 0 || (self.drag_splits & BOTTOM) == BOTTOM {
189                let layout = self.top_bottom_layout();
190                let max_value = bounds[3] - self.top.value - self.center_min_size[1] -
191                                self.bottom.border - self.top.border;
192                self.bottom.event(layout, max_value, bounds, transform, e);
193            }
194        }
195        if (self.lock_splits & LEFT) != LEFT {
196            if self.drag_splits == 0 || (self.drag_splits & LEFT) == LEFT {
197                let layout = self.left_right_layout(SplitLayoutPurpose::Event);
198                let max_value = bounds[2] - self.right.value - self.center_min_size[0] -
199                                self.left.border - self.right.border;
200                self.left.event(layout, max_value, bounds, transform, e);
201            }
202        }
203        if (self.lock_splits & RIGHT) != RIGHT {
204            if self.drag_splits == 0 || (self.drag_splits & RIGHT) == RIGHT {
205                let layout = self.left_right_layout(SplitLayoutPurpose::Event);
206                let max_value = bounds[2] - self.left.value - self.center_min_size[0] -
207                                self.right.border - self.left.border;
208                self.right.event(layout, max_value, bounds, transform, e);
209            }
210        }
211
212        self.drag_splits = if self.top.is_dragging() {TOP} else {0} |
213                          if self.bottom.is_dragging() {BOTTOM} else {0} |
214                          if self.left.is_dragging() {LEFT} else {0} |
215                          if self.right.is_dragging() {RIGHT} else {0};
216    }
217
218    /// Returns the left/right split layout.
219    ///
220    /// The left/right split layout depends on whether your purpose is to draw something or
221    /// handle events. When handling events, the rectangle overlaps with the top and bottom split.
222    pub fn left_right_layout(&self, purpose: SplitLayoutPurpose) -> SplitLayout {
223        let sign = purpose.sign();
224        SplitLayout {
225            start: self.top.value + sign * self.top.border,
226            end: self.bottom.value + sign * self.bottom.border
227        }
228    }
229
230    /// Returns the top/bottom split layout.
231    pub fn top_bottom_layout(&self) -> SplitLayout {
232        SplitLayout {start: 0.0, end: 0.0}
233    }
234
235    /// Computes split rectangles for drawing `[left, right, top, bottom]`.
236    pub fn rectangles(&self, rect: Rectangle) -> [Rectangle; 4] {
237        let bounds = self.bounds(rect);
238        let top_bottom_layout = self.top_bottom_layout();
239        let left_right_layout = self.left_right_layout(SplitLayoutPurpose::Draw);
240        [
241            self.left.line_rect(left_right_layout, bounds),
242            self.right.line_rect(left_right_layout, bounds),
243            self.top.line_rect(top_bottom_layout, bounds),
244            self.bottom.line_rect(top_bottom_layout, bounds),
245        ]
246    }
247
248    /// Returns the split controller states `[left, right, top, bottom]`.
249    pub fn states(&self) -> [SplitState; 4] {
250        [self.left.state(), self.right.state(), self.top.state(), self.bottom.state()]
251    }
252
253    /// Computes panel rectangles for layout `[left, right, top, bottom, center]`.
254    pub fn panel_rectangles(&self, rect: Rectangle) -> [Rectangle; 5] {
255        let bounds = self.bounds(rect);
256        let left_right_y = bounds[1] + self.top.value + self.top.border;
257        let left_right_h = bounds[3] - self.top.value - self.top.border -
258                           self.bottom.value - self.bottom.border;
259        [
260            [bounds[0], left_right_y, self.left.value, left_right_h],
261            [bounds[0] + bounds[2] - self.right.value, left_right_y,
262             self.right.value, left_right_h],
263            [bounds[0], bounds[1], bounds[2], self.top.value],
264            [bounds[0], bounds[1] + bounds[3] - self.bottom.value, bounds[2], self.bottom.value],
265            [bounds[0] + self.left.value + self.left.border, left_right_y,
266             bounds[2] - self.right.value - self.right.border -
267             self.left.value - self.left.border, left_right_h],
268        ]
269    }
270
271    /// Computes the minimum size using current values in split controls.
272    ///
273    /// The current values in the split controls are used instead of the minimum values,
274    /// because the splits should appear visually with the same current value.
275    pub fn min_size(&self) -> [f64; 2] {
276        [
277            self.left.value + self.left.border + self.right.value + self.right.border +
278            self.center_min_size[0],
279            self.top.value + self.top.border + self.bottom.value + self.bottom.border +
280            self.center_min_size[1]
281        ]
282    }
283
284    /// Computes the bounds from window bounds `[x, y, w, h]`.
285    ///
286    /// Does not get less in size than specified by `min_size`.
287    pub fn bounds(&self, rect: Rectangle) -> Rectangle {
288        let min_size = self.min_size();
289        [rect[0], rect[1], rect[2].max(min_size[0]), rect[3].max(min_size[1])]
290    }
291}
292
293/// Stores information about an UI split.
294pub struct SplitController {
295    /// Whether the mouse is hovering over the split.
296    mouse_hover: bool,
297    /// Whether the user is dragging the split.
298    dragging: bool,
299    /// The value of split.
300    pub value: f64,
301    /// The minimum value of split.
302    pub min_value: f64,
303    /// The border width.
304    pub border: f64,
305    /// The orientation of split.
306    pub orientation: SplitOrientation,
307}
308
309impl SplitController {
310    /// Creates a new `SplitController`.
311    pub fn new(
312        value: f64,
313        min_value: f64,
314        border: f64,
315        orientation: SplitOrientation
316    ) -> SplitController {
317        SplitController {
318            mouse_hover: false,
319            dragging: false,
320            value: value,
321            min_value: min_value,
322            border: border,
323            orientation: orientation,
324        }
325    }
326
327    /// Gets whether the split is currently being dragged by the user.
328    pub fn is_dragging(&self) -> bool {self.dragging}
329
330    /// Handles event.
331    pub fn event<E: GenericEvent>(
332        &mut self,
333        layout: SplitLayout,
334        max_value: f64,
335        rect: Rectangle,
336        transform: Matrix2d,
337        e: &E
338    ) {
339        if let Some(pos) = e.mouse_cursor_args() {
340            let pos = inside_pos(pos, transform);
341            if self.dragging {
342                match self.orientation {
343                    SplitOrientation::Left => {
344                        self.value = (pos[0] - rect[0] - 0.5 * self.border)
345                            .max(self.min_value)
346                            .min(max_value);
347                    }
348                    SplitOrientation::Right => {
349                        self.value = (rect[2] - pos[0] + rect[0] - 0.5 * self.border)
350                            .max(self.min_value)
351                            .min(max_value);
352                    }
353                    SplitOrientation::Top => {
354                        self.value = (pos[1] - rect[1] - 0.5 * self.border)
355                            .max(self.min_value)
356                            .min(max_value);
357                    }
358                    SplitOrientation::Bottom => {
359                        self.value = (rect[1] + rect[3] - pos[1] - 0.5 * self.border)
360                            .max(self.min_value)
361                            .min(max_value);
362                    }
363                }
364            }
365            let line_rect = self.line_rect(layout, rect);
366            self.mouse_hover = is_inside(pos, line_rect);
367        }
368
369        if let Some(Button::Mouse(MouseButton::Left)) = e.press_args() {
370            if self.mouse_hover {
371                self.dragging = true;
372            }
373        }
374
375        if let Some(Button::Mouse(MouseButton::Left)) = e.release_args() {
376            self.dragging = false;
377        }
378    }
379
380    /// Gets the current state of split.
381    pub fn state(&self) -> SplitState {
382        match (self.mouse_hover, self.dragging) {
383            (false, false) => SplitState::Inactive,
384            (true, false) => SplitState::Hover,
385            (true, true) => SplitState::Drag,
386            (false, true) => SplitState::DragNotFollowing,
387        }
388    }
389
390    /// Gets line rectangle `[x, y, w, h]` from rectangle `[x, y, w, h]` of parent panel.
391    pub fn line_rect(&self, layout: SplitLayout, rect: Rectangle) -> Rectangle {
392        match self.orientation {
393            SplitOrientation::Left => {
394                [rect[0] + self.value, rect[1] + layout.start,
395                 self.border, rect[3] - layout.start - layout.end]
396            }
397            SplitOrientation::Right => {
398                [rect[0] + rect[2] - self.value - self.border, rect[1] + layout.start,
399                 self.border, rect[3] - layout.start - layout.end]
400            }
401            SplitOrientation::Top => {
402                [rect[0] + layout.start, rect[1] + self.value,
403                 rect[2] - layout.start - layout.end, self.border]
404            }
405            SplitOrientation::Bottom => {
406                [rect[0] + layout.start, rect[1] + rect[3] - self.value - self.border,
407                 rect[2] - layout.start - layout.end, self.border]
408            }
409        }
410    }
411}
412
413/// Stores split layout.
414#[derive(Copy, Clone, PartialEq, Debug)]
415pub struct SplitLayout {
416    /// The start of split from edge of parent panel.
417    pub start: f64,
418    /// The end of split from edge of parent panel.
419    pub end: f64,
420}
421
422/// Stores split layout purpose.
423#[derive(Copy, Clone, PartialEq, Debug)]
424pub enum SplitLayoutPurpose {
425    /// For drawing.
426    Draw,
427    /// For handling events.
428    Event,
429}
430
431impl SplitLayoutPurpose {
432    fn sign(self) -> f64 {if let SplitLayoutPurpose::Draw = self {1.0} else {0.0}}
433}
434
435/// Orients split from an edge of parent panel.
436#[derive(Copy, Clone, PartialEq, Eq, Debug)]
437pub enum SplitOrientation {
438    /// Splits from left edge of parent panel.
439    Left,
440    /// Splits from right edge of parent panel.
441    Right,
442    /// Splits from top edge of parent panel.
443    Top,
444    /// Splits from bottom edge of parent panel.
445    Bottom,
446}
447
448/// Gets the state of split.
449#[derive(Copy, Clone, PartialEq, Eq, Debug)]
450pub enum SplitState {
451    /// Split is inactive.
452    Inactive,
453    /// Mouse cursor is hovering above split.
454    Hover,
455    /// User is dragging the split.
456    Drag,
457    /// User is dragging, but split is not following.
458    DragNotFollowing,
459}