button_controller/
lib.rs

1//! A Piston library for handling button state and events
2
3#![deny(missing_docs)]
4
5extern crate vecmath;
6extern crate input;
7
8use std::time::Instant;
9
10use math::{Matrix2d, Rectangle};
11use input::GenericEvent;
12
13pub mod math;
14
15/// Button event signals.
16#[derive(Clone, Copy, PartialEq, Debug)]
17pub enum ButtonEvent {
18    /// Mouse entered button.
19    MouseEnter,
20    /// Mouse leaved button.
21    MouseLeave,
22    /// Button was pressed.
23    Press,
24    /// The button was clicked.
25    Click,
26    /// The button press was canceled.
27    Cancel,
28}
29
30/// Stores state and events of a button.
31pub struct ButtonController {
32    /// Whether mouse cursor is inside button.
33    pub mouse_cursor_inside: bool,
34    /// Whether mouse cursor was inside.
35    pub was_inside: bool,
36    /// Whether button is pressed.
37    pub pressed: bool,
38    /// When button was pressed.
39    pub pressed_instant: Option<Instant>,
40    /// Stores button events.
41    pub events: Vec<ButtonEvent>,
42}
43
44impl ButtonController {
45    /// Creates a new button controller.
46    pub fn new() -> ButtonController {
47        ButtonController {
48            mouse_cursor_inside: false,
49            was_inside: false,
50            pressed: false,
51            pressed_instant: None,
52            events: vec![],
53        }
54    }
55
56    /// Handles event.
57    pub fn event<E: GenericEvent>(&mut self, layout: Rectangle, transform: Matrix2d, e: &E) {
58        use math::is_inside;
59        use input::MouseButton;
60
61        self.events.clear();
62        if let Some(pos) = e.mouse_cursor_args() {
63            let inside = is_inside(pos, transform, layout);
64            if inside {
65                if !self.mouse_cursor_inside {
66                    self.mouse_cursor_inside = true;
67                    self.events.push(ButtonEvent::MouseEnter);
68                }
69            } else {
70                if self.mouse_cursor_inside {
71                    self.mouse_cursor_inside = false;
72                    self.events.push(ButtonEvent::MouseLeave);
73                }
74            }
75        }
76
77        if let Some(input::Button::Mouse(MouseButton::Left)) = e.press_args() {
78            if self.mouse_cursor_inside {
79                self.pressed = true;
80                self.was_inside = true;
81                self.events.push(ButtonEvent::Press);
82                self.pressed_instant = Some(Instant::now());
83            }
84        }
85        if let Some(input::Button::Mouse(MouseButton::Left)) = e.release_args() {
86            self.pressed = false;
87            if self.mouse_cursor_inside {
88                self.events.push(ButtonEvent::Click);
89            } else if self.was_inside {
90                self.events.push(ButtonEvent::Cancel);
91            }
92            self.was_inside = false;
93        }
94    }
95
96    /// Handle touch events.
97    pub fn touch_event<E: GenericEvent, S: Into<[u32; 2]>>(&mut self,
98                                                           layout: Rectangle,
99                                                           transform: Matrix2d,
100                                                           window_size: S,
101                                                           e: &E) {
102        use input::Touch;
103        use math::is_inside;
104
105        self.events.clear();
106        let window_size = window_size.into();
107        if let Some(args) = e.touch_args() {
108            let pos = args.position();
109            let pos = [pos[0] * window_size[0] as f64, pos[1] * window_size[1] as f64];
110            let inside = is_inside(pos, transform, layout);
111            match args.touch {
112                Touch::Start => {
113                    if inside {
114                        self.pressed = true;
115                        self.was_inside = true;
116                        self.mouse_cursor_inside = true;
117                        self.events.push(ButtonEvent::Press);
118                        self.pressed_instant = Some(Instant::now());
119                    }
120                }
121                Touch::Move => {
122                    if inside {
123                        if !self.mouse_cursor_inside {
124                            self.mouse_cursor_inside = true;
125                            self.events.push(ButtonEvent::MouseEnter);
126                        }
127                    } else {
128                        if self.mouse_cursor_inside {
129                            self.mouse_cursor_inside = false;
130                            self.events.push(ButtonEvent::MouseLeave);
131                        }
132                    }
133                }
134                Touch::End => {
135                    self.pressed = false;
136                    if self.mouse_cursor_inside {
137                        self.events.push(ButtonEvent::Click);
138                    } else if self.was_inside {
139                        self.events.push(ButtonEvent::Cancel);
140                    }
141                    self.was_inside = false;
142                    self.mouse_cursor_inside = false;
143                }
144                Touch::Cancel => {}
145            }
146        }
147    }
148
149    /// Returns `true` if keep pressed appearance for some duration to give user feedback.
150    pub fn appear_pressed(&self, pressed_duration_secs: f64) -> bool {
151        if let Some(ref instant) = self.pressed_instant {
152            if math::duration_to_secs(&instant.elapsed()) < pressed_duration_secs {
153                true
154            } else {
155                self.pressed
156            }
157        } else {
158            self.pressed
159        }
160    }
161
162    /// Returns the visual button state.
163    pub fn state(&self, pressed_duration_secs: f64) -> ButtonState {
164        ButtonVisual {
165            appear_pressed: self.appear_pressed(pressed_duration_secs),
166            mouse_cursor_inside: self.mouse_cursor_inside,
167        }.state()
168    }
169}
170
171/// Stores the current state of button.
172#[derive(Clone, Copy, PartialEq, Debug)]
173pub enum ButtonState {
174    /// Show inactive visual state.
175    Inactive,
176    /// Show hover visual state.
177    Hover,
178    /// Show press visual state.
179    Press,
180    /// Show cancel visual state.
181    Cancel,
182}
183
184/// Stores the current visual state of button.
185#[derive(Clone, Copy, PartialEq, Debug)]
186pub struct ButtonVisual {
187    /// Whether the button appears pressed.
188    pub appear_pressed: bool,
189    /// Whether mouse cursor is inside button layout.
190    pub mouse_cursor_inside: bool,
191}
192
193impl ButtonVisual {
194    /// Gets the current button state.
195    pub fn state(&self) -> ButtonState {
196        use ButtonState as S;
197
198        match (self.appear_pressed, self.mouse_cursor_inside) {
199            (true, true) => S::Press,
200            (true, false) => S::Cancel,
201            (false, false) => S::Inactive,
202            (false, true) => S::Hover,
203        }
204    }
205}