1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use crate::{
    text, Btn, Button, EventCtx, GeomBatch, GfxCtx, Line, Outcome, ScreenDims, ScreenPt,
    ScreenRectangle, Text, Widget, WidgetImpl, WidgetOutput,
};
use geom::{Polygon, Pt2D};

// TODO MAX_CHAR_WIDTH is a hardcoded nonsense value
const TEXT_WIDTH: f64 = 2.0 * text::MAX_CHAR_WIDTH;

// TODO Allow text entry
// TODO Allow click and hold
// TODO Grey out the buttons when we're maxed out
pub struct Spinner {
    low: isize,
    high: isize,
    pub current: isize,

    up: Button,
    down: Button,

    top_left: ScreenPt,
    dims: ScreenDims,
}

impl Spinner {
    pub fn new(ctx: &EventCtx, (low, high): (isize, isize), current: isize) -> Widget {
        let up = Btn::text_fg("↑")
            .build(ctx, "increase value", None)
            .take_btn();
        let down = Btn::text_fg("↓")
            .build(ctx, "decrease value", None)
            .take_btn();

        let dims = ScreenDims::new(
            TEXT_WIDTH + up.get_dims().width,
            up.get_dims().height + down.get_dims().height,
        );

        Widget::new(Box::new(Spinner {
            low,
            high,
            current,

            up,
            down,

            top_left: ScreenPt::new(0.0, 0.0),
            dims,
        }))
    }
}

impl WidgetImpl for Spinner {
    fn get_dims(&self) -> ScreenDims {
        self.dims
    }

    fn set_pos(&mut self, top_left: ScreenPt) {
        // TODO This works, but it'd be kind of cool if we could construct a tiny little Composite
        // here and use that. Wait, why can't we? ...
        self.top_left = top_left;
        self.up
            .set_pos(ScreenPt::new(top_left.x + TEXT_WIDTH, top_left.y));
        self.down.set_pos(ScreenPt::new(
            top_left.x + TEXT_WIDTH,
            top_left.y + self.up.get_dims().height,
        ));
    }

    fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput) {
        self.up.event(ctx, output);
        if let Outcome::Clicked(_) = output.outcome {
            output.outcome = Outcome::Changed;
            if self.current != self.high {
                self.current += 1;
            }
            ctx.no_op_event(true, |ctx| self.up.event(ctx, output));
            return;
        }

        self.down.event(ctx, output);
        if let Outcome::Clicked(_) = output.outcome {
            output.outcome = Outcome::Changed;
            if self.current != self.low {
                self.current -= 1;
            }
            ctx.no_op_event(true, |ctx| self.down.event(ctx, output));
            return;
        }

        if let Some(pt) = ctx.canvas.get_cursor_in_screen_space() {
            if ScreenRectangle::top_left(self.top_left, self.dims).contains(pt) {
                if let Some((_, dy)) = ctx.input.get_mouse_scroll() {
                    if dy > 0.0 && self.current != self.high {
                        self.current += 1;
                        output.outcome = Outcome::Changed;
                    }
                    if dy < 0.0 && self.current != self.low {
                        self.current -= 1;
                        output.outcome = Outcome::Changed;
                    }
                }
            }
        }
    }

    fn draw(&self, g: &mut GfxCtx) {
        // TODO Cache
        let mut batch = GeomBatch::from(vec![(
            text::BG_COLOR,
            Polygon::rounded_rectangle(self.dims.width, self.dims.height, Some(5.0)),
        )]);
        batch.append(
            Text::from(Line(self.current.to_string()))
                .render_to_batch(g.prerender)
                .centered_on(Pt2D::new(TEXT_WIDTH / 2.0, self.dims.height / 2.0)),
        );
        let draw = g.upload(batch);
        g.redraw_at(self.top_left, &draw);

        self.up.draw(g);
        self.down.draw(g);
    }
}