Skip to content

Commit

Permalink
First version, some conf and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
RichoDemus committed Apr 19, 2021
1 parent 2d2e492 commit f2e7c1b
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Cargo.lock

# Created by https://www.toptal.com/developers/gitignore/api/rust,macos,windows,linux,vscode,intellij+all
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,macos,windows,linux,vscode,intellij+all
Expand Down
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "bevy_console"
version = "0.1.0"
edition = "2018"
authors = ["RichoDemus <git@richodemus.com>"]
homepage = "https://github.com/RichoDemus/bevy-console"
repository = "https://github.com/RichoDemus/bevy-console"
description = "dev console for bevy"
license = "MIT"
readme = "README.md"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bevy = "0.5.0"
bevy_egui = "0.4.0"
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# bevy console

A simple halflife2 style console
uses egui to draw the console

![Example image](doc/simple_example.png)

## Usage
Add `ConsolePlugin` and optionally the resource `ConsoleConfiguration`
```rust
fn main() {
App::build()
.add_plugin(ConsolePlugin)
.insert_resource(ConsoleConfiguration {
// override config here
..Default::default()
})
}
```
Create a system to listen to console events
```rust
fn listen_to_console_events(
mut console_events: EventReader<ConsoleCommandEntered>,
) {
for event in events.iter() {
// event has 2 fields, commands and args
}
}
```
If you want to send text to the console:
```rust
fn write_to_console(
mut console_line: EventWriter<PrintConsoleLine>,
) {
console_line.send(PrintConsoleLine::new("Hello".to_string()));
}
```
There's more examples in the examples directory
Binary file added doc/simple_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions examples/basic_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use bevy::prelude::*;
use bevy_console::*;

fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(ConsolePlugin)
.run();
}
70 changes: 70 additions & 0 deletions examples/bigger_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use bevy::app::AppExit;
use bevy::prelude::*;

use bevy_console::*;

fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(ConsolePlugin)
.insert_resource(ConsoleConfiguration {
help: vec![
HelpCommand::new(
"move_rect".to_string(),
"Usage: move_rect <up/down/left/right>".to_string(),
),
HelpCommand::new("quit".to_string(), "quits the app".to_string()),
],
..Default::default()
})
.add_startup_system(setup.system())
.add_system(listen_to_console_events.system())
.run();
}

struct MyRect;

fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
commands
.spawn_bundle(SpriteBundle {
material: materials.add(Color::rgb(0.5, 0.5, 1.0).into()),
transform: Transform::from_xyz(-600., 300., 0.),
sprite: Sprite::new(Vec2::new(10., 10.)),
..Default::default()
})
.insert(MyRect);
}

/// listens to `ConsoleCommandEntered` events
/// moves rect or quits based on events
fn listen_to_console_events(
mut events: EventReader<ConsoleCommandEntered>,
mut console_line: EventWriter<PrintConsoleLine>,
mut app_exit_events: EventWriter<AppExit>,
mut rect: Query<&mut Transform, With<MyRect>>,
) {
for event in events.iter() {
let event: &ConsoleCommandEntered = event;
info!("Commands: {:?}", event);
match event.command.as_str() {
"move_rect" => {
let mov = match event.args.as_str() {
"left" => Vec3::new(-30., 0., 0.),
"up" => Vec3::new(0., 30., 0.),
"down" => Vec3::new(0., -30., 0.),
"right" => Vec3::new(30., 0., 0.),
_ => continue,
};
if let Ok(mut transform) = rect.single_mut() {
transform.translation += mov;
}
}
"quit" => {
app_exit_events.send(AppExit);
}
_ => continue, // unknown command
}
console_line.send(PrintConsoleLine::new("Ok".to_string()));
}
}
13 changes: 13 additions & 0 deletions examples/change_console_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use bevy::prelude::*;
use bevy_console::*;

fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(ConsolePlugin)
.insert_resource(ConsoleConfiguration {
key: ToggleConsoleKey::ScanCode(41), // this is the console key on a swedish keyboard
..Default::default()
})
.run();
}
222 changes: 222 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use bevy::input::keyboard::KeyboardInput;
use bevy::prelude::*;
use bevy_egui::egui::{Align, ScrollArea};
use bevy_egui::{egui, EguiContext, EguiPlugin};

pub struct ConsolePlugin;

impl Plugin for ConsolePlugin {
fn build(&self, app: &mut AppBuilder) {
app.insert_resource(ConsoleState::default());
app.add_event::<ConsoleCommandEntered>();
app.add_event::<PrintConsoleLine>();
app.add_plugin(EguiPlugin);
// if there's other egui code we need to make sure they don't run at the same time
app.add_system(console_system.exclusive_system());
app.add_system(receive_console_line.system());
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConsoleCommandEntered {
pub command: String, // todo maybe enum?
pub args: String, // todo actual arg parsing probably
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrintConsoleLine {
pub line: String,
}

impl PrintConsoleLine {
pub const fn new(line: String) -> Self {
Self { line }
}
}

impl From<String> for ConsoleCommandEntered {
fn from(str: String) -> Self {
let separator = str.find(' ');

let index = match separator {
None => {
return Self {
command: str,
args: "".to_string(),
};
}
Some(index) => index,
};

let (cmd, args) = str.split_at(index);
let mut args = args.to_string();
args.replace_range(0..1, "");

Self {
command: cmd.to_string(),
args,
}
}
}

#[derive(Default)]
struct ConsoleState {
buf: String,
show: bool,
scrollback: Vec<String>,
}

#[derive(Copy, Clone)]
pub enum ToggleConsoleKey {
KeyCode(KeyCode),
ScanCode(u32),
}

#[derive(Clone)]
pub struct ConsoleConfiguration {
pub key: ToggleConsoleKey,
pub left_pos: f32,
pub top_pos: f32,
pub height: f32,
pub width: f32,
pub help: Vec<HelpCommand>,
}

impl Default for ConsoleConfiguration {
fn default() -> Self {
Self {
key: ToggleConsoleKey::KeyCode(KeyCode::Grave),
left_pos: 200.,
top_pos: 100.,
height: 400.,
width: 800.,
help: vec![],
}
}
}

#[derive(Clone)]
pub struct HelpCommand {
pub cmd: String,
pub description: String,
}

impl HelpCommand {
pub const fn new(cmd: String, description: String) -> Self {
Self { cmd, description }
}
}

// todo handle default values or something
// todo console flickers on keydown
// todo dont close console if typing, maybe?
fn console_system(
mut keyboard_input_events: EventReader<KeyboardInput>,
egui_context: Res<EguiContext>,
mut state: ResMut<ConsoleState>,
config: Res<ConsoleConfiguration>,
mut command_entered: EventWriter<ConsoleCommandEntered>,
) {
for code in keyboard_input_events.iter() {
let code: &KeyboardInput = code;

let is_right_key = match config.key {
ToggleConsoleKey::KeyCode(key) => match code.key_code {
None => false,
Some(pressed_key) => key == pressed_key,
},
ToggleConsoleKey::ScanCode(pressed_key) => code.scan_code == pressed_key,
};

if is_right_key && code.state.is_pressed() {
state.show = !state.show;
}
}
let scroll_height = config.height - 30.;
let mut open = state.show;
egui::Window::new("Console")
.open(&mut open)
.collapsible(false)
.fixed_rect(egui::Rect::from_two_pos(
egui::Pos2::new(config.left_pos, config.top_pos),
egui::Pos2::new(
config.left_pos + config.width,
config.top_pos + config.height,
),
))
.show(egui_context.ctx(), |ui| {
ui.set_min_height(config.height);
ui.set_min_width(config.width);
ScrollArea::from_max_height(scroll_height).show(ui, |ui| {
ui.vertical(|ui| {
ui.set_min_height(scroll_height);
for line in &state.scrollback {
ui.label(line);
}
});
ui.scroll_to_cursor(Align::BOTTOM);
});

ui.separator();
let response = ui.text_edit_singleline(&mut state.buf);
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
if state.buf.is_empty() {
state.scrollback.push(String::new());
} else {
if state.buf.eq("help") {
let mut input = state.buf.clone();
state.buf.clear();
input.insert(0, ' ');
input.insert(0, '$');
state.scrollback.push(input);
state.scrollback.push("available commands:".to_string());
for help_command in &config.help {
state.scrollback.push(format!(
"\t{} - {}",
help_command.cmd, help_command.description
));
}
} else {
let mut input = state.buf.clone();
state.buf.clear();
let command = input.clone().into(); // todo dont clone
command_entered.send(command);
input.insert(0, ' ');
input.insert(0, '$');
state.scrollback.push(input);
}
}
}
ui.memory().request_focus(response.id);
});
state.show = open;
}

fn receive_console_line(
mut console_state: ResMut<ConsoleState>,
mut events: EventReader<PrintConsoleLine>,
) {
for event in events.iter() {
let event: &PrintConsoleLine = event;
console_state.scrollback.push(event.line.clone());
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_command() {
let expected = ConsoleCommandEntered {
command: "my-cmd".to_string(),
args: "arg1 arg2".to_string(),
};

let input = "my-cmd arg1 arg2".to_string();

let result: ConsoleCommandEntered = input.into();

assert_eq!(result, expected);
}
}

0 comments on commit f2e7c1b

Please sign in to comment.