initial
4510
Cargo.lock
generated
Normal file
17
Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "enxos"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
dirs = "6.0.0"
|
||||||
|
ggez = "0.9.3"
|
||||||
|
hex = "0.4.3"
|
||||||
|
once_cell = "1.21.3"
|
||||||
|
rand = "0.9.2"
|
||||||
|
reqwest = { version = "0.12.24", features = ["json"] }
|
||||||
|
rust-embed = "8.7.2"
|
||||||
|
serde = "1.0.228"
|
||||||
|
serde_json = "1.0.145"
|
||||||
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
BIN
assets/bg.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
assets/button_bg_32x16.png
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
assets/button_bg_50x50.png
Normal file
|
After Width: | Height: | Size: 529 B |
BIN
assets/button_bg_65x30.png
Normal file
|
After Width: | Height: | Size: 503 B |
BIN
assets/close_icon.png
Normal file
|
After Width: | Height: | Size: 318 B |
BIN
assets/cursor.png
Normal file
|
After Width: | Height: | Size: 792 B |
BIN
assets/dialog_icon.png
Normal file
|
After Width: | Height: | Size: 405 B |
BIN
assets/fonts/MSW98UI-Bold.ttf
Normal file
BIN
assets/fonts/MSW98UI-Regular.ttf
Normal file
BIN
assets/games_icon.png
Normal file
|
After Width: | Height: | Size: 532 B |
BIN
assets/games_icon2.png
Normal file
|
After Width: | Height: | Size: 421 B |
BIN
assets/minimize_button.png
Normal file
|
After Width: | Height: | Size: 168 B |
BIN
assets/network.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/no.png
Normal file
|
After Width: | Height: | Size: 402 B |
BIN
assets/settings_icon.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
assets/start_button.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/start_menu.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
154
src/main.rs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
mod net;
|
||||||
|
mod ui;
|
||||||
|
use crate::{
|
||||||
|
net::networkmanager::NETWORK_MANAGER,
|
||||||
|
ui::{
|
||||||
|
elements::{
|
||||||
|
button::{Button, START_BUTTON},
|
||||||
|
start_menu::StartMenu,
|
||||||
|
},
|
||||||
|
utils::assets::*,
|
||||||
|
windows::window_manager::{self, WindowManager},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{env, path};
|
||||||
|
|
||||||
|
use ggez::{
|
||||||
|
Context, GameError, GameResult, event,
|
||||||
|
graphics::{self, Color, DrawParam, FontData, Image},
|
||||||
|
input::mouse::{self},
|
||||||
|
mint::Point2,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
cursor: Image,
|
||||||
|
bg: Image,
|
||||||
|
network: Image,
|
||||||
|
network_no: Image,
|
||||||
|
window_manager: WindowManager,
|
||||||
|
start_button: Button,
|
||||||
|
mouse_pos: Point2<f32>,
|
||||||
|
start_menu: StartMenu,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new(ctx: &mut Context) -> GameResult<State> {
|
||||||
|
let bg = Image::from_bytes(ctx, &get_asset("bg.png").expect("Couldn't load bg.png"))?;
|
||||||
|
let cursor = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("cursor.png").expect("Couldn't load cursor.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let network = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("network.png").expect("Couldn't load network.png"),
|
||||||
|
)?;
|
||||||
|
let network_no =
|
||||||
|
Image::from_bytes(ctx, &get_asset("no.png").expect("Couldn't load no.png"))?;
|
||||||
|
|
||||||
|
let start_button =
|
||||||
|
Button::new(START_BUTTON, ctx, None, None).expect("Error initializing start button")?;
|
||||||
|
|
||||||
|
let start_menu = StartMenu::new(ctx)?;
|
||||||
|
|
||||||
|
let window_manager = WindowManager::new();
|
||||||
|
|
||||||
|
Ok(State {
|
||||||
|
cursor,
|
||||||
|
bg,
|
||||||
|
network,
|
||||||
|
network_no,
|
||||||
|
window_manager,
|
||||||
|
start_button,
|
||||||
|
mouse_pos: Point2::from([0.0, 0.0]),
|
||||||
|
start_menu,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl event::EventHandler<GameError> for State {
|
||||||
|
fn update(&mut self, ctx: &mut Context) -> GameResult<()> {
|
||||||
|
self.mouse_pos = mouse::MouseContext::position(&ctx.mouse);
|
||||||
|
|
||||||
|
if ctx.mouse.button_just_pressed(mouse::MouseButton::Left) {
|
||||||
|
let res = self.start_button.update(&ctx.mouse);
|
||||||
|
if res {
|
||||||
|
self.start_menu.toggle();
|
||||||
|
} else {
|
||||||
|
if self.start_menu.visible() && !self.start_menu.click_in_bounds(self.mouse_pos) {
|
||||||
|
self.start_menu.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.start_menu.visible() {
|
||||||
|
self.start_menu.update(ctx, &mut self.window_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.window_manager.update(ctx);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
|
||||||
|
let mut canvas = graphics::Canvas::from_frame(ctx, Color::from([0.0, 0.0, 0.0, 1.0]));
|
||||||
|
|
||||||
|
canvas.draw(&self.bg, DrawParam::default());
|
||||||
|
|
||||||
|
self.start_menu.draw(&mut canvas);
|
||||||
|
|
||||||
|
self.window_manager.draw(&mut canvas);
|
||||||
|
|
||||||
|
self.start_button.draw(&mut canvas);
|
||||||
|
|
||||||
|
let network_drawparam = DrawParam::default().dest([750.0, 565.0]);
|
||||||
|
canvas.draw(&self.network, network_drawparam);
|
||||||
|
if !NETWORK_MANAGER.is_online() {
|
||||||
|
canvas.draw(&self.network_no, network_drawparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor is drawn last
|
||||||
|
canvas.draw(
|
||||||
|
&self.cursor,
|
||||||
|
DrawParam::default().dest([self.mouse_pos.x, self.mouse_pos.y]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(canvas.finish(ctx)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> GameResult {
|
||||||
|
//unsafe { env::set_var("RUST_BACKTRACE", "1") };
|
||||||
|
|
||||||
|
let resource_path = if env::var_os("CARGO_MANIFEST_DIR").is_some() {
|
||||||
|
path::PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
|
||||||
|
} else {
|
||||||
|
path::PathBuf::from(".")
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mut ctx, event_loop) = ggez::ContextBuilder::new("enxos", "enx")
|
||||||
|
.window_setup(ggez::conf::WindowSetup::default().title("enxos"))
|
||||||
|
.window_mode(
|
||||||
|
ggez::conf::WindowMode::default()
|
||||||
|
.dimensions(800.0, 600.0)
|
||||||
|
.resizable(false),
|
||||||
|
)
|
||||||
|
.add_resource_path(resource_path)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
//mouse::set_cursor_grabbed(&mut ctx, true)?;
|
||||||
|
mouse::set_cursor_hidden(&mut ctx, true);
|
||||||
|
|
||||||
|
let msw98reg = FontData::from_vec(
|
||||||
|
get_asset_vec("fonts/MSW98UI-Regular.ttf").expect("Couldn't load font."),
|
||||||
|
)?;
|
||||||
|
let msw98bold =
|
||||||
|
FontData::from_vec(get_asset_vec("fonts/MSW98UI-Bold.ttf").expect("Couldn't load font."))?;
|
||||||
|
|
||||||
|
ctx.gfx.add_font("msw98-bold", msw98bold);
|
||||||
|
ctx.gfx.add_font("msw98-reg", msw98reg);
|
||||||
|
|
||||||
|
let state = State::new(&mut ctx)?;
|
||||||
|
|
||||||
|
event::run(ctx, event_loop, state);
|
||||||
|
}
|
||||||
1
src/net/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod networkmanager;
|
||||||
120
src/net/networkmanager.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::Read,
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{runtime::Runtime, time};
|
||||||
|
|
||||||
|
pub(crate) static NETWORK_MANAGER: Lazy<Arc<NetworkManager>> =
|
||||||
|
Lazy::new(|| Arc::new(NetworkManager::new()));
|
||||||
|
|
||||||
|
pub(crate) static SERVER_STATUS: Lazy<Arc<AtomicBool>> =
|
||||||
|
Lazy::new(|| Arc::new(AtomicBool::new(false)));
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct UserName {
|
||||||
|
pub(crate) username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct UID {
|
||||||
|
user_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetworkManager {
|
||||||
|
client: Client,
|
||||||
|
runtime: Runtime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkManager {
|
||||||
|
fn new() -> Self {
|
||||||
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let nwm = Self {
|
||||||
|
client: Client::new(),
|
||||||
|
runtime,
|
||||||
|
};
|
||||||
|
|
||||||
|
nwm
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_user_data(&self) -> Result<UserName, Box<dyn std::error::Error>> {
|
||||||
|
let mut userid_path = dirs::home_dir().expect("Couldn't find home directory");
|
||||||
|
userid_path.push(".enxos");
|
||||||
|
fs::create_dir_all(&userid_path)?;
|
||||||
|
userid_path.push(".userid");
|
||||||
|
let user_id = if userid_path.exists() {
|
||||||
|
let mut file = File::open(&userid_path)?;
|
||||||
|
let mut user_id = String::new();
|
||||||
|
file.read_to_string(&mut user_id)?;
|
||||||
|
user_id.trim().to_string()
|
||||||
|
} else {
|
||||||
|
// no ~/.enxos/.userid folder, user is not registered, we need to register to the
|
||||||
|
// server
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.post("http://localhost:8801/u/register")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let body_bytes = response.bytes().await?;
|
||||||
|
let response: UID = serde_json::from_slice(&body_bytes).expect("json error");
|
||||||
|
|
||||||
|
fs::write(&userid_path, &response.user_id)?;
|
||||||
|
response.user_id
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.post("http://localhost:8801/u/profile")
|
||||||
|
.json(&UID { user_id })
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let body_bytes = response.bytes().await?;
|
||||||
|
let user_profile: UserName = serde_json::from_slice(&body_bytes).expect("json error");
|
||||||
|
|
||||||
|
Ok(user_profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ping(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
let response = self.client.get("http://localhost:8801/").send().await?;
|
||||||
|
|
||||||
|
println!("response : {}", response.status().as_str());
|
||||||
|
|
||||||
|
Ok(response.status().as_str().contains("200"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_user_data(&self) -> UserName {
|
||||||
|
let user_profile = self.fetch_user_data();
|
||||||
|
SERVER_STATUS.store(true, Ordering::SeqCst);
|
||||||
|
self.runtime.block_on(async {
|
||||||
|
match user_profile.await {
|
||||||
|
Ok(up) => {
|
||||||
|
SERVER_STATUS.store(true, Ordering::SeqCst);
|
||||||
|
up
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
SERVER_STATUS.store(false, Ordering::SeqCst);
|
||||||
|
UserName {
|
||||||
|
username: "offline".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_online(&self) -> bool {
|
||||||
|
SERVER_STATUS.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
}
|
||||||
298
src/ui/elements/button.rs
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
use ggez::{
|
||||||
|
Context, GameResult,
|
||||||
|
graphics::{Canvas, Color, DrawParam, Image, Text, TextFragment},
|
||||||
|
input::mouse::MouseContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::ui::utils::assets::*;
|
||||||
|
|
||||||
|
pub(crate) static START_BUTTON: i32 = 1000;
|
||||||
|
pub(crate) static GAMES_BUTTON: i32 = 1001;
|
||||||
|
pub(crate) static SETTINGS_BUTTON: i32 = 1002;
|
||||||
|
pub(crate) static SHUTDOWN_BUTTON: i32 = 1003;
|
||||||
|
pub(crate) static PROFILE_BUTTON: i32 = 1004;
|
||||||
|
pub(crate) static YES_BUTTON: i32 = 1005;
|
||||||
|
pub(crate) static NO_BUTTON: i32 = 1006;
|
||||||
|
|
||||||
|
pub struct Button {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
w: f32,
|
||||||
|
h: f32,
|
||||||
|
bid: i32,
|
||||||
|
sprite: Image,
|
||||||
|
text: Option<Text>,
|
||||||
|
last_press: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_start_button(ctx: &mut Context) -> GameResult<Button> {
|
||||||
|
let x = 0.0;
|
||||||
|
let y = 564.0;
|
||||||
|
let w = 76.0;
|
||||||
|
let h = 32.0;
|
||||||
|
let frag = TextFragment::new("Start")
|
||||||
|
.font("msw98-bold")
|
||||||
|
.scale(15.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
|
||||||
|
let text = Some(Text::new(frag));
|
||||||
|
|
||||||
|
let sprite = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("start_button.png").expect("Couldn't load bg.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Button {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
bid: START_BUTTON,
|
||||||
|
sprite,
|
||||||
|
text,
|
||||||
|
last_press: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_games_button(ctx: &mut Context) -> GameResult<Button> {
|
||||||
|
let x = 6.0;
|
||||||
|
let y = 514.0;
|
||||||
|
let w = 32.0;
|
||||||
|
let h = 32.0;
|
||||||
|
let frag = TextFragment::new("Games")
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(12.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
|
||||||
|
let text = Some(Text::new(frag));
|
||||||
|
|
||||||
|
let sprite = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("games_icon.png").expect("Couldn't load games_icon.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Button {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
bid: GAMES_BUTTON,
|
||||||
|
sprite,
|
||||||
|
text,
|
||||||
|
last_press: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_settings_button(ctx: &mut Context) -> GameResult<Button> {
|
||||||
|
let x = 54.0;
|
||||||
|
let y = 514.0;
|
||||||
|
let w = 32.0;
|
||||||
|
let h = 32.0;
|
||||||
|
let frag = TextFragment::new("Settings")
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(12.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
|
||||||
|
let text = Some(Text::new(frag));
|
||||||
|
|
||||||
|
let sprite = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("settings_icon.png").expect("Couldn't load setings_icon.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Button {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
bid: SETTINGS_BUTTON,
|
||||||
|
sprite,
|
||||||
|
text,
|
||||||
|
last_press: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_shutdown_button(ctx: &mut Context) -> GameResult<Button> {
|
||||||
|
let x = 229.0;
|
||||||
|
let y = 520.0;
|
||||||
|
let w = 65.0;
|
||||||
|
let h = 30.0;
|
||||||
|
let frag = TextFragment::new("Shutdown")
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(13.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
|
||||||
|
let text = Some(Text::new(frag));
|
||||||
|
|
||||||
|
let sprite = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("button_bg_65x30.png").expect("Couldn't load button_bg_65x30.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Button {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
bid: SHUTDOWN_BUTTON,
|
||||||
|
sprite,
|
||||||
|
text,
|
||||||
|
last_press: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_profile_button(ctx: &mut Context) -> GameResult<Button> {
|
||||||
|
let x = 6.0;
|
||||||
|
let y = 206.0;
|
||||||
|
let w = 50.0;
|
||||||
|
let h = 50.0;
|
||||||
|
let text = None;
|
||||||
|
|
||||||
|
let sprite = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("button_bg_50x50.png").expect("Couldn't load button_bg_50x50.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Button {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
bid: PROFILE_BUTTON,
|
||||||
|
sprite,
|
||||||
|
text,
|
||||||
|
last_press: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_yes_button(ctx: &mut Context, x: f32, y: f32) -> GameResult<Button> {
|
||||||
|
let w = 32.0;
|
||||||
|
let h = 16.0;
|
||||||
|
let frag = TextFragment::new("Yes")
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(12.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
let text = Some(Text::new(frag));
|
||||||
|
|
||||||
|
let sprite = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("button_bg_32x16.png").expect("Couldn't load button_bg_32x16.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Button {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
bid: YES_BUTTON,
|
||||||
|
sprite,
|
||||||
|
text,
|
||||||
|
last_press: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_no_button(ctx: &mut Context, x: f32, y: f32) -> GameResult<Button> {
|
||||||
|
let w = 32.0;
|
||||||
|
let h = 16.0;
|
||||||
|
let frag = TextFragment::new("No")
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(12.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
let text = Some(Text::new(frag));
|
||||||
|
|
||||||
|
let sprite = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("button_bg_32x16.png").expect("Couldn't load button_bg_32x16.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Button {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
bid: NO_BUTTON,
|
||||||
|
sprite,
|
||||||
|
text,
|
||||||
|
last_press: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Button {
|
||||||
|
pub fn new(
|
||||||
|
bid: i32,
|
||||||
|
ctx: &mut Context,
|
||||||
|
x: Option<f32>,
|
||||||
|
y: Option<f32>,
|
||||||
|
) -> Option<GameResult<Button>> {
|
||||||
|
match bid {
|
||||||
|
1000 => Some(create_start_button(ctx)),
|
||||||
|
1001 => Some(create_games_button(ctx)),
|
||||||
|
1002 => Some(create_settings_button(ctx)),
|
||||||
|
1003 => Some(create_shutdown_button(ctx)),
|
||||||
|
1004 => Some(create_profile_button(ctx)),
|
||||||
|
1005 => Some(create_yes_button(ctx, x.unwrap(), y.unwrap())),
|
||||||
|
1006 => Some(create_no_button(ctx, x.unwrap(), y.unwrap())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, mouse: &MouseContext) -> bool {
|
||||||
|
let mouse_pos = mouse.position();
|
||||||
|
let in_bounds = mouse_pos.x >= self.x
|
||||||
|
&& mouse_pos.x < self.x + self.w
|
||||||
|
&& mouse_pos.y >= self.y
|
||||||
|
&& mouse_pos.y < self.y + self.h;
|
||||||
|
|
||||||
|
in_bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, canvas: &mut Canvas) {
|
||||||
|
let actual_x = self.x;
|
||||||
|
let actual_y = self.y;
|
||||||
|
|
||||||
|
canvas.draw(
|
||||||
|
&self.sprite,
|
||||||
|
DrawParam::default().dest([actual_x, actual_y]),
|
||||||
|
);
|
||||||
|
|
||||||
|
match self.bid {
|
||||||
|
1000 => {
|
||||||
|
canvas.draw(
|
||||||
|
self.text.as_ref().expect("Couldn't draw Start Text"),
|
||||||
|
DrawParam::default().dest([actual_x + 35.0, actual_y + 12.0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
1001 => {
|
||||||
|
canvas.draw(
|
||||||
|
self.text.as_ref().expect("Couldn't draw Games Text"),
|
||||||
|
DrawParam::default().dest([actual_x - 1.0, actual_y + self.h]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
1002 => {
|
||||||
|
canvas.draw(
|
||||||
|
self.text.as_ref().expect("Couldn't draw Settings Text"),
|
||||||
|
DrawParam::default().dest([actual_x - 6.0, actual_y + self.h]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
1003 => {
|
||||||
|
canvas.draw(
|
||||||
|
self.text.as_ref().expect("Couldn't draw Shutdown Text"),
|
||||||
|
DrawParam::default().dest([actual_x + 5.0, actual_y + 9.0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
1005 => {
|
||||||
|
canvas.draw(
|
||||||
|
self.text.as_ref().expect("Couldn't draw Yes Text"),
|
||||||
|
DrawParam::default().dest([actual_x + 6.0, actual_y + 4.0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
1006 => {
|
||||||
|
canvas.draw(
|
||||||
|
self.text.as_ref().expect("Couldn't draw No Text"),
|
||||||
|
DrawParam::default().dest([actual_x + 9.0, actual_y + 4.0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/ui/elements/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub(crate) mod button;
|
||||||
|
pub(crate) mod start_menu;
|
||||||
127
src/ui/elements/start_menu.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use ggez::{
|
||||||
|
Context, GameResult,
|
||||||
|
context::Has,
|
||||||
|
graphics::{Canvas, Color, DrawParam, Drawable, Image, Text, TextFragment},
|
||||||
|
input::mouse,
|
||||||
|
mint::Point2,
|
||||||
|
};
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
use crate::net::networkmanager::{self, NETWORK_MANAGER, UserName};
|
||||||
|
|
||||||
|
use crate::ui::{
|
||||||
|
elements::button::*, utils::assets::get_asset, windows::window_manager::WindowManager,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct StartMenu {
|
||||||
|
visible: bool,
|
||||||
|
background: Image,
|
||||||
|
games_button: Button,
|
||||||
|
settings_button: Button,
|
||||||
|
profile_button: Button,
|
||||||
|
shutdown_button: Button,
|
||||||
|
username: Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StartMenu {
|
||||||
|
pub fn new(ctx: &mut Context) -> GameResult<StartMenu> {
|
||||||
|
let games_button =
|
||||||
|
Button::new(GAMES_BUTTON, ctx, None, None).expect("Couldn't create games button")?;
|
||||||
|
let settings_button = Button::new(SETTINGS_BUTTON, ctx, None, None)
|
||||||
|
.expect("Couldn't create settings button")?;
|
||||||
|
let shutdown_button = Button::new(SHUTDOWN_BUTTON, ctx, None, None)
|
||||||
|
.expect("Couldn't create shutdown button")?;
|
||||||
|
let profile_button = Button::new(PROFILE_BUTTON, ctx, None, None)
|
||||||
|
.expect("Couldn't create profile button")?;
|
||||||
|
|
||||||
|
let background = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("start_menu.png").expect("Couldn't load start_menu.png"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let user_profile = NETWORK_MANAGER.get_user_data();
|
||||||
|
|
||||||
|
let frag = TextFragment::new(user_profile.username)
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(20.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
|
||||||
|
let username = Text::new(frag);
|
||||||
|
|
||||||
|
let visible = false;
|
||||||
|
Ok(StartMenu {
|
||||||
|
visible,
|
||||||
|
background,
|
||||||
|
games_button,
|
||||||
|
settings_button,
|
||||||
|
profile_button,
|
||||||
|
shutdown_button,
|
||||||
|
username,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle(&mut self) {
|
||||||
|
self.visible = !self.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visible(&self) -> bool {
|
||||||
|
self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn click_in_bounds(&self, mouse_pos: Point2<f32>) -> bool {
|
||||||
|
mouse_pos.x >= 0.0
|
||||||
|
&& mouse_pos.x < 300.0
|
||||||
|
&& mouse_pos.y >= 200.0
|
||||||
|
&& mouse_pos.y < 200.0 + 361.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh_user_profile(&mut self) {
|
||||||
|
let user_profile = NETWORK_MANAGER.get_user_data();
|
||||||
|
|
||||||
|
let frag = TextFragment::new(user_profile.username)
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(20.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
|
||||||
|
self.username.clear();
|
||||||
|
self.username.add(frag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, ctx: &mut Context, wm: &mut WindowManager) {
|
||||||
|
if ctx.mouse.button_just_pressed(mouse::MouseButton::Left) {
|
||||||
|
let mut res = self.shutdown_button.update(&ctx.mouse);
|
||||||
|
if res {
|
||||||
|
let on_yes = Box::new(|ctx: &mut Context| ctx.request_quit());
|
||||||
|
wm.launch_yes_no_dialog(ctx, on_yes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res = self.games_button.update(&ctx.mouse);
|
||||||
|
if res {
|
||||||
|
// Launch games window
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res = self.settings_button.update(&ctx.mouse);
|
||||||
|
if res {
|
||||||
|
// Launch settings window
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res = self.profile_button.update(&ctx.mouse);
|
||||||
|
if res {
|
||||||
|
self.refresh_user_profile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&mut self, canvas: &mut Canvas) {
|
||||||
|
if self.visible {
|
||||||
|
canvas.draw(&self.background, DrawParam::default().dest([0.0, 200.0]));
|
||||||
|
self.games_button.draw(canvas);
|
||||||
|
self.settings_button.draw(canvas);
|
||||||
|
self.profile_button.draw(canvas);
|
||||||
|
self.shutdown_button.draw(canvas);
|
||||||
|
self.username
|
||||||
|
.draw(canvas, DrawParam::default().dest([64.0, 222.5]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/ui/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub(crate) mod elements;
|
||||||
|
pub(crate) mod utils;
|
||||||
|
pub(crate) mod windows;
|
||||||
13
src/ui/utils/assets.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "assets"]
|
||||||
|
struct Asset;
|
||||||
|
pub fn get_asset(path: &str) -> Option<Cow<'static, [u8]>> {
|
||||||
|
Asset::get(path).map(|file| file.data.into_owned().into())
|
||||||
|
}
|
||||||
|
pub fn get_asset_vec(path: &str) -> Option<Vec<u8>> {
|
||||||
|
Asset::get(path).map(|file| file.data.to_vec())
|
||||||
|
}
|
||||||
1
src/ui/utils/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod assets;
|
||||||
1
src/ui/windows/dialogs/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod yes_no_dialog;
|
||||||
139
src/ui/windows/dialogs/yes_no_dialog.rs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
use ggez::graphics::{Color, DrawParam, Image, Mesh, Rect, Text, TextFragment};
|
||||||
|
use ggez::{Context, graphics};
|
||||||
|
|
||||||
|
use crate::ui::elements::button::{Button, NO_BUTTON, YES_BUTTON};
|
||||||
|
use crate::ui::utils::assets::get_asset;
|
||||||
|
use crate::ui::windows::window::Window;
|
||||||
|
|
||||||
|
pub struct YesNoDialog {
|
||||||
|
pos: (f32, f32),
|
||||||
|
dimensions: (f32, f32), // Width, Height
|
||||||
|
background: Mesh,
|
||||||
|
title: Text,
|
||||||
|
icon: Image,
|
||||||
|
areusure: Text,
|
||||||
|
closed: bool,
|
||||||
|
on_yes: Box<dyn Fn(&mut Context)>,
|
||||||
|
yes_button: Button,
|
||||||
|
no_button: Button,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YesNoDialog {
|
||||||
|
pub fn new(
|
||||||
|
ctx: &mut Context,
|
||||||
|
pos: (f32, f32),
|
||||||
|
on_yes: Box<dyn Fn(&mut Context)>,
|
||||||
|
) -> YesNoDialog {
|
||||||
|
let mut frag = TextFragment::new("Confirm")
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(13.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
|
||||||
|
let title = Text::new(frag);
|
||||||
|
|
||||||
|
frag = TextFragment::new("Are you sure?")
|
||||||
|
.font("msw98-reg")
|
||||||
|
.scale(13.0)
|
||||||
|
.color(Color::BLACK);
|
||||||
|
let areusure = Text::new(frag);
|
||||||
|
|
||||||
|
let icon = Image::from_bytes(
|
||||||
|
ctx,
|
||||||
|
&get_asset("settings_icon.png").expect("Couldn't load dialog icon."),
|
||||||
|
)
|
||||||
|
.expect("Couldn't load dialog icon.");
|
||||||
|
|
||||||
|
let yes_button = Button::new(YES_BUTTON, ctx, Some(pos.0 + 60.0), Some(pos.1 + 80.0))
|
||||||
|
.expect("Couldn't build yes button")
|
||||||
|
.expect("Couldn't build yes button");
|
||||||
|
let no_button = Button::new(NO_BUTTON, ctx, Some(pos.0 + 110.0), Some(pos.1 + 80.0))
|
||||||
|
.expect("Couldn't build yes button")
|
||||||
|
.expect("Couldn't build yes button");
|
||||||
|
|
||||||
|
YesNoDialog {
|
||||||
|
pos,
|
||||||
|
dimensions: (200.0, 100.0),
|
||||||
|
background: Mesh::new_rectangle(
|
||||||
|
ctx,
|
||||||
|
graphics::DrawMode::fill(),
|
||||||
|
{
|
||||||
|
let rect_x = 0.0;
|
||||||
|
let rect_y = 0.0;
|
||||||
|
let rect_width = 200.0;
|
||||||
|
let rect_height = 100.0;
|
||||||
|
Rect::new(rect_x, rect_y, rect_width, rect_height)
|
||||||
|
},
|
||||||
|
Color::from_rgb(168, 168, 168),
|
||||||
|
)
|
||||||
|
.expect("Couldn't create background mesh for confirm dialog."),
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
areusure,
|
||||||
|
closed: false,
|
||||||
|
on_yes,
|
||||||
|
yes_button,
|
||||||
|
no_button,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window for YesNoDialog {
|
||||||
|
fn update(&mut self, ctx: &mut ggez::Context) {
|
||||||
|
if ctx
|
||||||
|
.mouse
|
||||||
|
.button_just_pressed(ggez::event::MouseButton::Left)
|
||||||
|
&& self.click_in_bounds(ctx.mouse.position())
|
||||||
|
{
|
||||||
|
if self.yes_button.update(&ctx.mouse) {
|
||||||
|
(self.on_yes)(ctx)
|
||||||
|
} else if self.no_button.update(&ctx.mouse) {
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, canvas: &mut ggez::graphics::Canvas) {
|
||||||
|
canvas.draw(
|
||||||
|
&self.background,
|
||||||
|
DrawParam::default().dest([self.pos.0, self.pos.1]),
|
||||||
|
);
|
||||||
|
canvas.draw(
|
||||||
|
&self.icon,
|
||||||
|
DrawParam::default().dest([self.pos.0, self.pos.1]),
|
||||||
|
);
|
||||||
|
canvas.draw(
|
||||||
|
&self.title,
|
||||||
|
DrawParam::default().dest([self.pos.0 + 82.0, self.pos.1 + 10.0]),
|
||||||
|
);
|
||||||
|
canvas.draw(
|
||||||
|
&self.areusure,
|
||||||
|
DrawParam::default().dest([self.pos.0 + 65.0, self.pos.1 + 30.0]),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.yes_button.draw(canvas);
|
||||||
|
self.no_button.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn click_in_bounds(&self, mouse_pos: ggez::mint::Point2<f32>) -> bool {
|
||||||
|
mouse_pos.x > self.pos.0
|
||||||
|
&& mouse_pos.x <= self.pos.0 + self.dimensions.0
|
||||||
|
&& mouse_pos.y > self.pos.1
|
||||||
|
&& mouse_pos.y <= self.pos.1 + self.dimensions.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closed(&self) -> bool {
|
||||||
|
self.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) {
|
||||||
|
self.closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn minimize(&mut self) {}
|
||||||
|
|
||||||
|
// Unfocusable
|
||||||
|
fn focused(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn set_focused(&mut self, _value: bool) {}
|
||||||
|
}
|
||||||
0
src/ui/windows/games/mod.rs
Normal file
6
src/ui/windows/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
pub(crate) mod dialogs;
|
||||||
|
pub(crate) mod games;
|
||||||
|
pub(crate) mod settings;
|
||||||
|
pub(crate) mod social;
|
||||||
|
pub(crate) mod window;
|
||||||
|
pub(crate) mod window_manager;
|
||||||
0
src/ui/windows/settings/mod.rs
Normal file
0
src/ui/windows/social/mod.rs
Normal file
12
src/ui/windows/window.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
use ggez::{Context, graphics::Canvas, mint::Point2};
|
||||||
|
|
||||||
|
pub(crate) trait Window {
|
||||||
|
fn update(&mut self, ctx: &mut Context);
|
||||||
|
fn draw(&self, canvas: &mut Canvas);
|
||||||
|
fn click_in_bounds(&self, mouse_pos: Point2<f32>) -> bool;
|
||||||
|
fn closed(&self) -> bool;
|
||||||
|
fn close(&mut self);
|
||||||
|
fn minimize(&mut self);
|
||||||
|
fn focused(&self) -> bool;
|
||||||
|
fn set_focused(&mut self, value: bool);
|
||||||
|
}
|
||||||
42
src/ui/windows/window_manager.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use ggez::{Context, graphics::Canvas};
|
||||||
|
|
||||||
|
use crate::ui::windows::{dialogs::yes_no_dialog::YesNoDialog, window::Window};
|
||||||
|
|
||||||
|
pub struct WindowManager {
|
||||||
|
windows: Vec<Box<dyn Window>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
WindowManager {
|
||||||
|
windows: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn launch_yes_no_dialog(&mut self, ctx: &mut Context, on_yes: Box<dyn Fn(&mut Context)>) {
|
||||||
|
self.windows
|
||||||
|
.push(Box::new(YesNoDialog::new(ctx, (300.0, 250.0), on_yes)));
|
||||||
|
|
||||||
|
for window in &mut self.windows {
|
||||||
|
window.set_focused(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, ctx: &mut Context) {
|
||||||
|
// Remove any closed windows
|
||||||
|
self.windows.retain(|window| !window.closed());
|
||||||
|
|
||||||
|
// Update focused window
|
||||||
|
for window in &mut self.windows {
|
||||||
|
if window.focused() {
|
||||||
|
window.update(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, canvas: &mut Canvas) {
|
||||||
|
for window in &self.windows {
|
||||||
|
window.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||