266 lines
7.8 KiB
Rust
266 lines
7.8 KiB
Rust
use std::collections::HashMap;
|
|
use std::net::SocketAddr;
|
|
use std::ops::DerefMut;
|
|
use std::rc::Rc;
|
|
|
|
use godot::classes::{
|
|
CanvasLayer, Engine, IPanel, InputEvent, Panel, RichTextLabel, ScrollContainer, TextEdit,
|
|
};
|
|
use godot::prelude::*;
|
|
use godot::tools::get_autoload_by_name;
|
|
|
|
pub enum CliColor {
|
|
Command,
|
|
Info,
|
|
Help,
|
|
Error,
|
|
}
|
|
|
|
impl CliColor {
|
|
pub fn as_str(&self) -> &str {
|
|
match self {
|
|
CliColor::Command => "#a2a2a2",
|
|
CliColor::Info => "#fff",
|
|
CliColor::Help => "#ffdc2e",
|
|
CliColor::Error => "#cc3535",
|
|
}
|
|
}
|
|
}
|
|
|
|
pub const CLI_GLOBAL_NAME: &str = "CommandLinePanelGlobal";
|
|
|
|
#[derive(GodotClass)]
|
|
#[class(base=Panel)]
|
|
pub struct CommandLinePanel {
|
|
#[export]
|
|
pub input_field: Option<Gd<TextEdit>>,
|
|
#[export]
|
|
pub text_field: Option<Gd<RichTextLabel>>,
|
|
#[export]
|
|
pub scroll_container: Option<Gd<ScrollContainer>>,
|
|
|
|
#[export]
|
|
pub opened: bool,
|
|
|
|
scroll_next_update: bool,
|
|
commands: HashMap<String, Rc<Box<dyn Command>>>,
|
|
|
|
idle_y: f32,
|
|
|
|
base: Base<Panel>,
|
|
}
|
|
|
|
#[godot_api]
|
|
impl IPanel for CommandLinePanel {
|
|
fn init(base: Base<Panel>) -> Self {
|
|
let mut commands: HashMap<String, Rc<Box<dyn Command>>> = HashMap::new();
|
|
commands.insert("help".to_owned(), Rc::new(Box::new(HelpCommand)));
|
|
commands.insert("host".to_string(), Rc::new(Box::new(HostCommand)));
|
|
commands.insert("join".to_string(), Rc::new(Box::new(JoinCommand)));
|
|
commands.insert(
|
|
"disconnect".to_string(),
|
|
Rc::new(Box::new(DisconnectCommand)),
|
|
);
|
|
|
|
CommandLinePanel {
|
|
input_field: None,
|
|
text_field: None,
|
|
scroll_container: None,
|
|
opened: false,
|
|
scroll_next_update: false,
|
|
idle_y: 0.,
|
|
commands,
|
|
base,
|
|
}
|
|
}
|
|
|
|
fn ready(&mut self) {
|
|
self.idle_y = self.base().get_position().y;
|
|
}
|
|
|
|
fn process(&mut self, _delta: f64) {
|
|
if let Some(scroll) = &mut self.scroll_container
|
|
&& self.scroll_next_update
|
|
{
|
|
scroll.set_v_scroll(i32::MAX);
|
|
self.scroll_next_update = false;
|
|
}
|
|
|
|
if let Some(input) = self.input_field.clone() {
|
|
let text = input.get_text();
|
|
if text.contains("\n") {
|
|
let parts = text.split("\n");
|
|
let first = parts.get(0).unwrap();
|
|
self.input_command(first.to_string());
|
|
}
|
|
}
|
|
|
|
let old_pos = self.base().get_position();
|
|
let mut target_pos = self.base().get_position();
|
|
if self.opened {
|
|
target_pos.y = 0.;
|
|
} else {
|
|
target_pos.y = self.idle_y;
|
|
}
|
|
let new_pos = old_pos.lerp(target_pos, 0.3);
|
|
self.base_mut().set_position(new_pos);
|
|
}
|
|
|
|
fn input(&mut self, event: Gd<InputEvent>) {
|
|
if event.is_action_pressed("toggle_cli") {
|
|
self.opened = !self.opened;
|
|
if self.opened {
|
|
self.signals().on_open().emit();
|
|
} else {
|
|
self.signals().on_close().emit();
|
|
}
|
|
if let Some(input_field) = &mut self.input_field {
|
|
if self.opened {
|
|
input_field.grab_focus();
|
|
input_field.set_editable(true);
|
|
self.run_deferred(|s| s.input_field.as_mut().unwrap().clear());
|
|
} else {
|
|
input_field.release_focus();
|
|
input_field.set_editable(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[godot_api]
|
|
impl CommandLinePanel {
|
|
#[signal]
|
|
pub fn on_host(port: u16);
|
|
#[signal]
|
|
pub fn on_join(port: String);
|
|
#[signal]
|
|
pub fn on_disconnect();
|
|
#[signal]
|
|
pub fn on_open();
|
|
#[signal]
|
|
pub fn on_close();
|
|
|
|
pub fn singleton() -> Gd<CommandLinePanel> {
|
|
get_autoload_by_name::<CanvasLayer>(CLI_GLOBAL_NAME)
|
|
.get_child(0)
|
|
.unwrap()
|
|
.cast()
|
|
}
|
|
|
|
pub fn input_command(&mut self, command: String) {
|
|
self.publish_message(format!("> {}", command), CliColor::Command);
|
|
if let Some(input) = &mut self.input_field {
|
|
input.clear();
|
|
}
|
|
self.handle_command(&command);
|
|
}
|
|
|
|
pub fn publish_message(&mut self, message: String, color: CliColor) {
|
|
if let Some(field) = &mut self.text_field {
|
|
if !field.get_parsed_text().is_empty() {
|
|
field.append_text("\n");
|
|
}
|
|
field.append_text(&format!("[color={}]", color.as_str()));
|
|
field.append_text(&message);
|
|
field.append_text("[/color]");
|
|
self.scroll_next_update = true;
|
|
}
|
|
}
|
|
|
|
fn handle_command(&mut self, text: &String) {
|
|
let mut command_and_params = text.split(" ").into_iter();
|
|
let command = command_and_params.next().clone().unwrap_or("");
|
|
let params = command_and_params.collect::<Vec<_>>();
|
|
if let Some(command) = self.commands.get(command) {
|
|
command.clone().execute(self, ¶ms);
|
|
} else {
|
|
self.publish_message(format!("Unknown command: {}", command), CliColor::Info);
|
|
}
|
|
}
|
|
}
|
|
|
|
trait Command {
|
|
fn execute(&self, cli: &mut CommandLinePanel, params: &[&str]);
|
|
fn help(&self) -> &str;
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct HelpCommand;
|
|
impl Command for HelpCommand {
|
|
fn execute(&self, cli: &mut CommandLinePanel, params: &[&str]) {
|
|
if let Some(command_name) = params.first() {
|
|
if let Some(command) = cli.commands.get(*command_name) {
|
|
cli.publish_message(command.help().to_string(), CliColor::Help);
|
|
} else {
|
|
cli.publish_message(format!("Unknown command: {}", command_name), CliColor::Help);
|
|
}
|
|
} else {
|
|
let mut keys = cli.commands.keys().cloned().collect::<Vec<_>>();
|
|
keys.sort();
|
|
for key in keys {
|
|
cli.publish_message(
|
|
format!("\t- {}: {}", key, cli.commands.get(&key).unwrap().help()),
|
|
CliColor::Help,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn help(&self) -> &str {
|
|
"Shows this message"
|
|
}
|
|
}
|
|
#[derive(Clone)]
|
|
struct HostCommand;
|
|
impl Command for HostCommand {
|
|
fn execute(&self, cli: &mut CommandLinePanel, params: &[&str]) {
|
|
if let Some(port_str) = params.first() {
|
|
if let Ok(port) = u16::from_str_radix(*port_str, 10) {
|
|
cli.signals().on_host().emit(port);
|
|
} else {
|
|
cli.publish_message(format!("Invalid port: {}", port_str), CliColor::Error);
|
|
}
|
|
} else {
|
|
cli.publish_message(format!("Missing port"), CliColor::Error);
|
|
cli.publish_message(format!("Usage: host [port]"), CliColor::Help);
|
|
}
|
|
}
|
|
|
|
fn help(&self) -> &str {
|
|
"Hosts a server at a given port"
|
|
}
|
|
}
|
|
#[derive(Clone)]
|
|
struct JoinCommand;
|
|
impl Command for JoinCommand {
|
|
fn execute(&self, cli: &mut CommandLinePanel, params: &[&str]) {
|
|
if let Some(address_str) = params.first() {
|
|
if let Ok(_) = address_str.parse::<SocketAddr>() {
|
|
cli.signals().on_join().emit(address_str.to_string());
|
|
} else {
|
|
cli.publish_message(format!("Invalid address: {}", address_str), CliColor::Error);
|
|
}
|
|
} else {
|
|
cli.publish_message(format!("Missing address"), CliColor::Error);
|
|
cli.publish_message(format!("Usage: join [address]:[port]"), CliColor::Help);
|
|
}
|
|
}
|
|
|
|
fn help(&self) -> &str {
|
|
"Joins server at a given address:port"
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct DisconnectCommand;
|
|
impl Command for DisconnectCommand {
|
|
fn execute(&self, cli: &mut CommandLinePanel, _params: &[&str]) {
|
|
cli.signals().on_disconnect().emit();
|
|
}
|
|
|
|
fn help(&self) -> &str {
|
|
"Disconnects from the server, or stops hosting the server"
|
|
}
|
|
}
|