amberwindow 0.3.14

An easy to use ImmediateMode gui library for Rust.
Documentation
use macroquad::prelude::*;

#[derive(Clone, Default, PartialEq)]
pub enum InputState {
    #[default]
    Default,
    Hovering,
    Pressed,
}

#[derive(Clone, Default)]
pub struct TextBoxBase {
    pub rect: Rect,
    pub color: Color,
    pub text: String,
    pub state: InputState,
    pub editing: bool,
    pub placeholder: String,
    pub edit_color: Color,
    pub last_key: (Option<KeyCode>, i32),
    font: Option<Font>,
    pub last_click_time: f64,
}

impl TextBoxBase {
    pub fn new(rect: Rect, placeholder: &str, font: Option<Font>) -> Self {
        Self {
            font,
            rect,
            color: Color::from_vec(Vec4::splat(0.3)),
            edit_color: Color::new(0.4, 0.3, 0.2, 0.5),
            // text: "Amogus".into(),
            placeholder: placeholder.to_string(),
            ..Default::default()
        }
    }

    pub fn update(&mut self, selected: bool) {
        self.update_state(selected);
        if self.editing {
            self.take_input()
        }
    }

    fn update_state(&mut self, selected: bool) {
        if is_mouse_button_released(MouseButton::Left) {
            self.state = InputState::Default;
        }

        if self.rect.contains(mouse_position().into()) {
            if is_mouse_button_pressed(MouseButton::Left) && selected {
                self.state = InputState::Pressed;
                self.editing = true;
                self.last_click_time = get_time() * 2.;
            } else {
                if !is_mouse_button_down(MouseButton::Left) {
                    self.state = InputState::Hovering;
                }
            }
        } else {
            if self.state != InputState::Pressed {
                self.state = InputState::Default
            }
            if is_mouse_button_pressed(MouseButton::Left) {
                self.editing = false;
            }
        }
    }

    fn take_input(&mut self) {
        if let Some(key) = get_last_key_pressed() {
            self.last_key.1 = -30;
            self.last_key.0 = Some(key);

            if let Some(ch) = get_char_pressed() {
                self.key_pressed(key, ch.is_uppercase());
            } else {
                self.key_pressed(key, false);
            }
        }

        self.last_key.1 += 1;
        if let Some(key) = self.last_key.0 {
            if self.last_key.1 > 3 && is_key_down(key) {
                if let Some(ch) = get_char_pressed() {
                    self.key_pressed(key, ch.is_uppercase());
                } else {
                    self.key_pressed(key, false);
                }
            }
        }
    }

    pub fn key_pressed(&mut self, key: KeyCode, upper: bool) {
        let key_name = format!("{:?}", key).to_lowercase();
        if key_name == "backspace" {
            self.text.pop();
            return;
        }

        let str_key = Self::handle_shift(
            &key_name,
            match key_name.as_str() {
                "space" => " ",
                "minus" => "-",
                "equal" => "=",
                "leftbracket" => "[",
                "rightbracket" => "]",
                "semicolon" => ";",
                "apostrophe" => "'",
                "backslash" => "\\",
                "comma" => ",",
                "period" => ".",
                "slash" => "/",
                "graveaccent" => "`",
                other => other,
            },
            upper,
        );

        let character = str_key.chars().last().unwrap();
        if str_key.starts_with("key") || str_key.len() == 1 {
            self.text.push(character);
        }
    }

    pub fn handle_shift(key_name: &String, str_key: &str, upper: bool) -> String {
        if !(upper || is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift)) {
            return str_key.to_string();
        }
        match key_name.as_str() {
            "key1" => "!",
            "key2" => "@",
            "key3" => "#",
            "key4" => "$",
            "key5" => "%",
            "key6" => "^",
            "key7" => "&",
            "key8" => "*",
            "key9" => "(",
            "key0" => ")",

            // Letters
            "a" => "A",
            "b" => "B",
            "c" => "C",
            "d" => "D",
            "e" => "E",
            "f" => "F",
            "g" => "G",
            "h" => "H",
            "i" => "I",
            "j" => "J",
            "k" => "K",
            "l" => "L",
            "m" => "M",
            "n" => "N",
            "o" => "O",
            "p" => "P",
            "q" => "Q",
            "r" => "R",
            "s" => "S",
            "t" => "T",
            "u" => "U",
            "v" => "V",
            "w" => "W",
            "x" => "X",
            "y" => "Y",
            "z" => "Z",

            // Special characters
            "minus" => "_",
            "equal" => "+",
            "leftbracket" => "{",
            "rightbracket" => "}",
            "semicolon" => ":",
            "apostrophe" => "\"",
            "backslash" => "|",
            "comma" => "<",
            "period" => ">",
            "slash" => "?",
            "graveaccent" => "~",

            _ => str_key,
        }
        .to_string()
    }

    pub fn render(&self) {
        // DRAW BACKGROUND
        let mut bg_color = match self.state {
            InputState::Hovering => mul_color(self.color, 1.3),
            InputState::Pressed => mul_color(self.color, 0.7),
            _ => self.color,
        };

        if self.editing {
            bg_color = self.edit_color
        }

        draw_rectangle(self.rect.x, self.rect.y, self.rect.w, self.rect.h, bg_color);
        draw_rectangle_lines(
            self.rect.x,
            self.rect.y,
            self.rect.w,
            self.rect.h,
            2.,
            BLACK,
        );

        // DRAW TEXT

        let drawn_text = match self.text.len() > 0 {
            true => &self.text,
            _ => &self.placeholder
        };

        let font_size = self.rect.h as u16;
        if let Some(_) = self.font {
            draw_text_ex(
                drawn_text,
                (self.rect.x + 10.).floor(),
                (self.rect.y + self.rect.h - 5.).floor(),
                TextParams {
                    font_size: font_size - 5,
                    color: match self.text.len() > 0 {
                        true => WHITE,
                        _ => Color::new(1.,1.,1.,0.5)
                    },
                    font: Some(&self.font.clone().unwrap()),
                    ..Default::default()
                },
            );
        } else {
            draw_text(
                drawn_text,
                (self.rect.x + 10.).floor(),
                (self.rect.y + self.rect.h - 5.).floor(),
                font_size as f32,
                match self.text.len() > 0 {
                    true => WHITE,
                    _ => Color::new(1.,1.,1.,0.5)
                }
            );
        }

        // DRAW CARET

        if self.editing {
            if (get_time() * 2. + self.last_click_time) as usize % 2 > 0 {
                let text_dimensions = match &self.font {
                    Some(f) => measure_text(
                        &self.text,
                        Some(&f.clone()),
                        font_size - 5,
                        1.,
                    ),
                    _ => measure_text(
                        &self.text,
                        None,
                        font_size,
                        1.,
                    )
                };
                let caret_x = self.rect.x + 10. + text_dimensions.width;
                draw_line(
                    caret_x,
                    self.rect.y + self.rect.h - 3.0,
                    caret_x,
                    self.rect.y + 3.0,
                    1.,
                    WHITE,
                );
            }
        }
    }
}

fn mul_color(color: Color, mul: f32) -> Color {
    Color::from_vec(color.to_vec() * vec4(mul, mul, mul, 1.))
}