#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use macroquad::{
prelude::*,
ui::{
hash, root_ui,
widgets::{Group, Label},
Layout, Skin,
},
};
use cvars::SetGet;
use cvars_console::Console;
#[derive(Debug, Clone, Default)]
pub struct MacroquadConsole {
is_open: bool,
console: Console,
config: Config,
input: ConsoleInput,
input_prev: ConsoleInput,
}
impl MacroquadConsole {
pub fn new() -> Self {
Self {
is_open: false,
console: Console::new(),
config: Config::default(),
input: ConsoleInput::new(),
input_prev: ConsoleInput::new(),
}
}
pub fn update(&mut self, cvars: &mut dyn SetGet) {
self.input_prev = self.input;
self.input = get_input();
self.open_close();
if self.is_open {
self.process_input();
self.draw_console();
if !self.input_prev.enter && self.input.enter && !self.console.prompt.is_empty() {
self.console.enter(cvars);
}
}
}
fn open_close(&mut self) {
let pressed_console = !self.input_prev.console && self.input.console;
let pressed_escape = !self.input_prev.escape && self.input.escape;
if !self.is_open && pressed_console {
self.is_open = true;
show_mouse(true);
} else if self.is_open && (pressed_console || pressed_escape) {
self.is_open = false;
show_mouse(false);
}
}
fn process_input(&mut self) {
self.console.prompt = self.console.prompt.replace(';', "");
let pressed_up = !self.input_prev.up && self.input.up;
let pressed_down = !self.input_prev.down && self.input.down;
let pressed_page_up = !self.input_prev.page_up && self.input.page_up;
let pressed_page_down = !self.input_prev.page_down && self.input.page_down;
if pressed_up {
self.console.history_back();
}
if pressed_down {
self.console.history_forward();
}
let count = 10; if pressed_page_up {
self.console.history_scroll_up(count);
}
if pressed_page_down {
self.console.history_scroll_down(count);
}
}
fn draw_console(&mut self) {
let console_height = (screen_height() * self.config.height_fraction).floor();
draw_rectangle(
0.0,
0.0,
screen_width(),
console_height,
Color::new(0.0, 0.0, 0.0, self.config.background_alpha),
);
draw_line(
0.0,
console_height,
screen_width(),
console_height,
1.0,
RED,
);
if self.console.history_view_end >= 1 {
let mut i = self.console.history_view_end - 1;
let mut y = console_height - self.config.history_y_offset;
loop {
let text = if self.console.history[i].is_input {
format!("> {}", self.console.history[i].text)
} else {
self.console.history[i].text.clone()
};
draw_text(
&text,
self.config.history_x,
y,
self.config.history_line_font_size,
WHITE,
);
if i == 0 || y < 0.0 {
break;
}
i -= 1;
y -= self.config.history_line_height;
}
}
let bg_image = Image::gen_image_color(1, 1, BLANK);
let style = root_ui()
.style_builder()
.background(bg_image)
.color(BLANK) .text_color(WHITE)
.build();
let skin = Skin {
label_style: style.clone(),
editbox_style: style.clone(),
group_style: style,
..root_ui().default_skin()
};
root_ui().push_skin(&skin);
let id_prompt = 0;
let label_y = console_height - self.config.prompt_label_y_offset;
Label::new(">")
.position(vec2(self.config.prompt_label_x, label_y))
.ui(&mut root_ui());
let group_y =
screen_height() * self.config.height_fraction - self.config.prompt_group_y_offset;
Group::new(hash!(), vec2(screen_width() - 8.0, 20.0))
.position(vec2(self.config.prompt_group_x, group_y))
.layout(Layout::Horizontal)
.ui(&mut root_ui(), |ui| {
ui.input_text(id_prompt, "", &mut self.console.prompt);
});
root_ui().set_input_focus(id_prompt);
}
pub fn is_open(&self) -> bool {
self.is_open
}
}
#[derive(Debug, Clone)]
struct Config {
background_alpha: f32,
prompt_group_x: f32,
prompt_group_y_offset: f32,
height_fraction: f32,
history_line_font_size: f32,
history_line_height: f32,
history_x: f32,
history_y_offset: f32,
prompt_label_x: f32,
prompt_label_y_offset: f32,
}
impl Default for Config {
fn default() -> Self {
Self {
background_alpha: 0.8,
prompt_group_x: 16.0,
prompt_group_y_offset: 26.0,
height_fraction: 0.45,
history_line_font_size: 16.0,
history_line_height: 14.0,
history_x: 8.0,
history_y_offset: 25.0,
prompt_label_x: 8.0,
prompt_label_y_offset: 22.0,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
struct ConsoleInput {
console: bool,
escape: bool,
enter: bool,
up: bool,
down: bool,
page_up: bool,
page_down: bool,
}
impl ConsoleInput {
fn new() -> Self {
Self::default()
}
}
fn get_input() -> ConsoleInput {
let mut input = ConsoleInput::new();
if are_keys_pressed(&[KeyCode::GraveAccent, KeyCode::Semicolon]) {
input.console = true;
}
if are_keys_pressed(&[KeyCode::Escape]) {
input.escape = true;
}
if are_keys_pressed(&[KeyCode::Enter, KeyCode::KpEnter]) {
input.enter = true;
}
if are_keys_pressed(&[KeyCode::Up]) {
input.up = true;
}
if are_keys_pressed(&[KeyCode::Down]) {
input.down = true;
}
if are_keys_pressed(&[KeyCode::PageUp]) {
input.page_up = true;
}
if are_keys_pressed(&[KeyCode::PageDown]) {
input.page_down = true;
}
input
}
fn are_keys_pressed(key_codes: &[KeyCode]) -> bool {
for &key_code in key_codes {
if is_key_pressed(key_code) {
return true;
}
}
false
}