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
125
126
use geom::Polygon;

use crate::{
    text, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, ScreenDims, ScreenPt, ScreenRectangle,
    Text, WidgetImpl, WidgetOutput,
};

// TODO right now, only a single line

pub struct TextBox {
    line: String,
    cursor_x: usize,
    has_focus: bool,
    hovering: bool,
    autofocus: bool,

    top_left: ScreenPt,
    dims: ScreenDims,
}

impl TextBox {
    pub fn new(ctx: &EventCtx, max_chars: usize, prefilled: String, autofocus: bool) -> TextBox {
        TextBox {
            cursor_x: prefilled.len(),
            line: prefilled,
            has_focus: false,
            hovering: false,
            autofocus,

            top_left: ScreenPt::new(0.0, 0.0),
            dims: ScreenDims::new(
                (max_chars as f64) * text::MAX_CHAR_WIDTH,
                ctx.default_line_height(),
            ),
        }
    }

    fn calculate_text(&self) -> Text {
        let mut txt = Text::from(Line(&self.line[0..self.cursor_x]));
        if self.cursor_x < self.line.len() {
            // TODO This "cursor" looks awful!
            txt.append_all(vec![
                Line("|").fg(text::SELECTED_COLOR),
                Line(&self.line[self.cursor_x..=self.cursor_x]),
                Line(&self.line[self.cursor_x + 1..]),
            ]);
        } else {
            txt.append(Line("|").fg(text::SELECTED_COLOR));
        }
        txt
    }

    pub fn get_line(&self) -> String {
        self.line.clone()
    }
}

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

    fn set_pos(&mut self, top_left: ScreenPt) {
        self.top_left = top_left;
    }

    fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput) {
        if ctx.redo_mouseover() {
            if let Some(pt) = ctx.canvas.get_cursor_in_screen_space() {
                self.hovering = ScreenRectangle::top_left(self.top_left, self.dims).contains(pt);
            } else {
                self.hovering = false;
            }
        }

        if ctx.normal_left_click() {
            // Let all textboxes see this event, so they can deactivate their own focus.
            // TODO But if a button is clicked before this textbox, that event isn't seen here...
            ctx.input.unconsume_event();
            self.has_focus = self.hovering;
        }

        if !self.has_focus && !self.autofocus {
            return;
        }
        if let Some(key) = ctx.input.any_pressed() {
            match key {
                Key::LeftArrow => {
                    if self.cursor_x > 0 {
                        self.cursor_x -= 1;
                    }
                }
                Key::RightArrow => {
                    self.cursor_x = (self.cursor_x + 1).min(self.line.len());
                }
                Key::Backspace => {
                    if self.cursor_x > 0 {
                        output.outcome = Outcome::Changed;
                        self.line.remove(self.cursor_x - 1);
                        self.cursor_x -= 1;
                    }
                }
                _ => {
                    if let Some(c) = key.to_char(ctx.is_key_down(Key::LeftShift)) {
                        output.outcome = Outcome::Changed;
                        self.line.insert(self.cursor_x, c);
                        self.cursor_x += 1;
                    } else {
                        ctx.input.unconsume_event();
                    }
                }
            };
        }
    }

    fn draw(&self, g: &mut GfxCtx) {
        // TODO Cache
        let mut batch = GeomBatch::from(vec![(
            text::BG_COLOR,
            Polygon::rectangle(self.dims.width, self.dims.height),
        )]);
        batch.append(self.calculate_text().render_autocropped(g));
        let draw = g.upload(batch);
        g.redraw_at(self.top_left, &draw);
    }
}