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);
|
||||
}
|
||||
}
|
||||
}
|
||||