1use image;
4use position::{self, Align, Rect, Scalar};
5use text;
6use widget;
7use {Borderable, Color, Colorable, FontSize, Labelable, Positionable, Sizeable, UiCell, Widget};
8
9#[derive(Clone, WidgetCommon_)]
11pub struct Button<'a, S> {
12 #[conrod(common_builder)]
13 common: widget::CommonBuilder,
14 maybe_label: Option<&'a str>,
15 pub show: S,
17 pub style: Style,
19 enabled: bool,
21}
22
23#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
25pub struct Style {
26 #[conrod(default = "theme.shape_color")]
28 pub color: Option<Color>,
29 #[conrod(default = "theme.border_width")]
31 pub border: Option<Scalar>,
32 #[conrod(default = "theme.border_color")]
34 pub border_color: Option<Color>,
35 #[conrod(default = "theme.label_color")]
37 pub label_color: Option<Color>,
38 #[conrod(default = "theme.font_size_medium")]
40 pub label_font_size: Option<FontSize>,
41 #[conrod(default = "theme.font_id")]
43 pub label_font_id: Option<Option<text::font::Id>>,
44 #[conrod(default = "text::Justify::Center")]
46 pub label_justify: Option<text::Justify>,
47 #[conrod(default = "position::Relative::Align(Align::Middle)")]
49 pub label_x: Option<position::Relative>,
50 #[conrod(default = "position::Relative::Align(Align::Middle)")]
52 pub label_y: Option<position::Relative>,
53}
54
55widget_ids! {
56 #[allow(missing_docs, missing_copy_implementations)]
58 pub struct FlatIds {
59 rectangle,
60 label,
61 }
62}
63
64widget_ids! {
65 #[allow(missing_docs, missing_copy_implementations)]
67 pub struct ImageIds {
68 image,
69 label,
70 }
71}
72
73#[derive(Copy, Clone, Default, PartialEq, Debug)]
75pub struct Flat {
76 pub hover_color: Option<Color>,
80 pub press_color: Option<Color>,
84}
85
86#[derive(Copy, Clone)]
88pub struct Image {
89 pub image_id: image::Id,
91 pub hover_image_id: Option<image::Id>,
93 pub press_image_id: Option<image::Id>,
95 pub color: ImageColor,
97 pub src_rect: Option<Rect>,
99}
100
101#[derive(Copy, Clone, Debug)]
103pub enum ImageColor {
104 Normal(Color),
106 WithFeedback(Color),
110 None,
112}
113
114#[derive(Copy, Clone)]
115enum Interaction {
116 Idle,
117 Hover,
118 Press,
119}
120
121#[derive(Clone, Debug)]
126#[allow(missing_copy_implementations)]
127pub struct TimesClicked(pub u16);
128
129impl TimesClicked {
130 pub fn was_clicked(self) -> bool {
132 self.0 > 0
133 }
134}
135
136impl Iterator for TimesClicked {
137 type Item = ();
138 fn next(&mut self) -> Option<Self::Item> {
139 if self.0 > 0 {
140 self.0 -= 1;
141 Some(())
142 } else {
143 None
144 }
145 }
146}
147
148impl<'a> Button<'a, Image> {
149 pub fn image(image_id: image::Id) -> Self {
151 let image = Image {
152 image_id: image_id,
153 hover_image_id: None,
154 press_image_id: None,
155 src_rect: None,
156 color: ImageColor::None,
157 };
158 Self::new_internal(image)
159 }
160
161 pub fn source_rectangle(mut self, rect: Rect) -> Self {
165 self.show.src_rect = Some(rect);
166 self
167 }
168
169 pub fn image_color(mut self, color: Color) -> Self {
171 self.show.color = ImageColor::Normal(color);
172 self
173 }
174
175 pub fn image_color_with_feedback(mut self, color: Color) -> Self {
180 self.show.color = ImageColor::WithFeedback(color);
181 self
182 }
183
184 pub fn hover_image(mut self, id: image::Id) -> Self {
186 self.show.hover_image_id = Some(id);
187 self
188 }
189
190 pub fn press_image(mut self, id: image::Id) -> Self {
192 self.show.press_image_id = Some(id);
193 self
194 }
195}
196
197impl<'a> Button<'a, Flat> {
198 pub fn new() -> Self {
200 Self::new_internal(Flat::default())
201 }
202
203 pub fn with_style(mut self, s: Style) -> Self {
205 self.style = s;
206 self
207 }
208
209 pub fn hover_color(mut self, color: Color) -> Self {
213 self.show.hover_color = Some(color);
214 self
215 }
216
217 pub fn press_color(mut self, color: Color) -> Self {
221 self.show.press_color = Some(color);
222 self
223 }
224}
225
226impl<'a, S> Button<'a, S> {
227 fn new_internal(show: S) -> Self {
229 Button {
230 common: widget::CommonBuilder::default(),
231 show: show,
232 maybe_label: None,
233 style: Style::default(),
234 enabled: true,
235 }
236 }
237
238 pub fn label_font_id(mut self, font_id: text::font::Id) -> Self {
240 self.style.label_font_id = Some(Some(font_id));
241 self
242 }
243
244 pub fn left_justify_label(mut self) -> Self {
246 self.style.label_justify = Some(text::Justify::Left);
247 self
248 }
249
250 pub fn center_justify_label(mut self) -> Self {
254 self.style.label_justify = Some(text::Justify::Center);
255 self
256 }
257
258 pub fn right_justify_label(mut self) -> Self {
260 self.style.label_justify = Some(text::Justify::Right);
261 self
262 }
263
264 pub fn label_x(mut self, x: position::Relative) -> Self {
266 self.style.label_x = Some(x);
267 self
268 }
269
270 pub fn label_y(mut self, y: position::Relative) -> Self {
272 self.style.label_y = Some(y);
273 self
274 }
275
276 builder_methods! {
277 pub enabled { enabled = bool }
278 }
279}
280
281impl<'a> Widget for Button<'a, Flat> {
282 type State = FlatIds;
283 type Style = Style;
284 type Event = TimesClicked;
285
286 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
287 FlatIds::new(id_gen)
288 }
289
290 fn style(&self) -> Style {
291 self.style.clone()
292 }
293
294 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
296 let widget::UpdateArgs {
297 id,
298 state,
299 style,
300 rect,
301 ui,
302 ..
303 } = args;
304 let Button {
305 show, maybe_label, ..
306 } = self;
307
308 let (interaction, times_triggered) = interaction_and_times_triggered(id, ui);
309 let color = match interaction {
310 Interaction::Idle => style.color(&ui.theme),
311 Interaction::Hover => show
312 .hover_color
313 .unwrap_or_else(|| style.color(&ui.theme).highlighted()),
314 Interaction::Press => show
315 .press_color
316 .unwrap_or_else(|| style.color(&ui.theme).clicked()),
317 };
318
319 bordered_rectangle(id, state.rectangle, rect, color, style, ui);
320
321 if let Some(l) = maybe_label {
323 label(id, state.label, l, style, ui);
324 }
325
326 TimesClicked(times_triggered)
327 }
328}
329
330impl<'a> Widget for Button<'a, Image> {
331 type State = ImageIds;
332 type Style = Style;
333 type Event = TimesClicked;
334
335 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
336 ImageIds::new(id_gen)
337 }
338
339 fn style(&self) -> Style {
340 self.style.clone()
341 }
342
343 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
345 let widget::UpdateArgs {
346 id,
347 state,
348 style,
349 rect,
350 ui,
351 ..
352 } = args;
353 let Button {
354 show, maybe_label, ..
355 } = self;
356
357 let (interaction, times_triggered) = interaction_and_times_triggered(id, ui);
358
359 let Image {
361 image_id,
362 press_image_id,
363 hover_image_id,
364 src_rect,
365 color,
366 } = show;
367
368 let image_id = match interaction {
370 Interaction::Idle => image_id,
371 Interaction::Hover => hover_image_id.unwrap_or(image_id),
372 Interaction::Press => press_image_id.or(hover_image_id).unwrap_or(image_id),
373 };
374
375 let (x, y, w, h) = rect.x_y_w_h();
376 let mut image = widget::Image::new(image_id)
377 .x_y(x, y)
378 .w_h(w, h)
379 .parent(id)
380 .graphics_for(id);
381 image.src_rect = src_rect;
382 image.style.maybe_color = match color {
383 ImageColor::Normal(color) => Some(Some(color)),
384 ImageColor::WithFeedback(color) => ui
385 .widget_input(id)
386 .mouse()
387 .map(|mouse| {
388 if mouse.buttons.left().is_down() {
389 Some(color.clicked())
390 } else {
391 Some(color.highlighted())
392 }
393 })
394 .or(Some(Some(color))),
395 ImageColor::None => None,
396 };
397 image.set(state.image, ui);
398
399 if let Some(s) = maybe_label {
400 label(id, state.label, s, style, ui);
401 }
402
403 TimesClicked(times_triggered)
404 }
405}
406
407fn interaction_and_times_triggered(button_id: widget::Id, ui: &UiCell) -> (Interaction, u16) {
408 let input = ui.widget_input(button_id);
409 let mouse_interaction = input.mouse().map_or(Interaction::Idle, |mouse| {
410 if mouse.buttons.left().is_down() {
411 if ui.global_input().current.widget_under_mouse == Some(button_id) {
412 Interaction::Press
413 } else {
414 Interaction::Idle
415 }
416 } else {
417 Interaction::Hover
418 }
419 });
420 let interaction = match mouse_interaction {
421 Interaction::Idle | Interaction::Hover => {
422 let is_touch_press = ui
423 .global_input()
424 .current
425 .touch
426 .values()
427 .any(|t| t.start.widget == Some(button_id) && t.widget == Some(button_id));
428 if is_touch_press {
429 Interaction::Press
430 } else {
431 mouse_interaction
432 }
433 }
434 Interaction::Press => Interaction::Press,
435 };
436 let times_triggered = (input.clicks().left().count() + input.taps().count()) as u16;
437 (interaction, times_triggered)
438}
439
440fn bordered_rectangle(
441 button_id: widget::Id,
442 rectangle_id: widget::Id,
443 rect: Rect,
444 color: Color,
445 style: &Style,
446 ui: &mut UiCell,
447) {
448 let dim = rect.dim();
450 let border = style.border(&ui.theme);
451 let border_color = style.border_color(&ui.theme);
452 widget::BorderedRectangle::new(dim)
453 .middle_of(button_id)
454 .graphics_for(button_id)
455 .color(color)
456 .border(border)
457 .border_color(border_color)
458 .set(rectangle_id, ui);
459}
460
461fn label(button_id: widget::Id, label_id: widget::Id, label: &str, style: &Style, ui: &mut UiCell) {
462 let color = style.label_color(&ui.theme);
463 let font_size = style.label_font_size(&ui.theme);
464 let x = style.label_x(&ui.theme);
465 let y = style.label_y(&ui.theme);
466 let justify = style.label_justify(&ui.theme);
467 let font_id = style.label_font_id(&ui.theme).or(ui.fonts.ids().next());
468 widget::Text::new(label)
469 .and_then(font_id, widget::Text::font_id)
470 .x_position_relative_to(button_id, x)
471 .y_position_relative_to(button_id, y)
472 .justify(justify)
473 .parent(button_id)
474 .graphics_for(button_id)
475 .color(color)
476 .font_size(font_size)
477 .set(label_id, ui);
478}
479
480impl<'a, S> Colorable for Button<'a, S> {
481 builder_method!(color { style.color = Some(Color) });
482}
483
484impl<'a, S> Borderable for Button<'a, S> {
485 builder_methods! {
486 border { style.border = Some(Scalar) }
487 border_color { style.border_color = Some(Color) }
488 }
489}
490
491impl<'a, S> Labelable<'a> for Button<'a, S> {
492 builder_methods! {
493 label { maybe_label = Some(&'a str) }
494 label_color { style.label_color = Some(Color) }
495 label_font_size { style.label_font_size = Some(FontSize) }
496 }
497}