merge: enumpattern 996cb404cd -> main (#3)

This commit is contained in:
Muhammad Nauman Raza 2025-02-21 11:53:43 +00:00
commit 37bd87abde
Signed by: devraza
GPG key ID: 91EAD6081011574B
5 changed files with 206 additions and 151 deletions

96
client/src/game.rs Normal file
View file

@ -0,0 +1,96 @@
use color_eyre::Result;
use ratatui::{
DefaultTerminal,
buffer::Buffer,
crossterm::event::{self, Event, KeyEvent, KeyEventKind},
layout::{Constraint, Flex, Layout, Rect},
widgets::Widget,
};
use crate::menu;
#[derive(Clone)]
pub enum Mode {
Exit,
MainMenu(menu::OptionList),
FortyL,
Blitz,
TxLadder,
Config,
}
impl Default for Mode {
fn default() -> Self {
Mode::MainMenu(menu::OptionList::from_iter(["40L", "Blitz", "txLadder", "Config"]))
}
}
impl Mode {
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
loop {
match self {
Mode::Exit => {
break
}
_ => {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
if let Event::Key(key) = event::read()? {
self = self.handle_key(key);
};
}
}
}
Ok(())
}
fn handle_key(self, key: KeyEvent) -> Mode {
if key.kind != KeyEventKind::Press {
return self
}
match self {
Mode::MainMenu(menu_list) => menu_list.handle_key(key),
_ => Mode::Exit,
}
}
}
fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect {
let [area] = Layout::horizontal([horizontal])
.flex(Flex::Center)
.areas(area);
let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area);
area
}
impl Widget for &mut Mode {
fn render(self, area: Rect, buf: &mut Buffer) {
let [header_area, main_area, footer_area] = Layout::vertical([
Constraint::Length(1),
Constraint::Fill(1),
Constraint::Length(2),
])
.areas(area);
let center_area = center(
main_area,
Constraint::Length(50),
Constraint::Percentage(60),
);
let [profile_area, list_outer_area] =
Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(center_area);
menu::render_header(header_area, buf);
match self {
Mode::MainMenu(menu_list) => {
menu::render_profile(profile_area, buf);
menu_list.render(list_outer_area, buf);
}
_ => {},
}
menu::render_footer(footer_area, buf);
}
}

View file

@ -1,33 +1,12 @@
use color_eyre::Result;
use ratatui::{
DefaultTerminal,
crossterm::event::{self, Event},
};
mod game;
mod menu;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
let app_result = game::Mode::default().run(terminal);
ratatui::restore();
app_result
}
pub struct App {
should_exit: bool,
menu: menu::MenuList,
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
while !self.should_exit {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
if let Event::Key(key) = event::read()? {
self.handle_key(key);
};
}
Ok(())
}
}

View file

@ -1,63 +1,42 @@
use crate::App;
// Main menu sections
use ratatui::{
buffer::Buffer,
crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind},
layout::{Constraint, Flex, Layout, Rect},
style::{Modifier, Style, Stylize, palette::tailwind::*},
crossterm::event::{self, KeyCode, KeyEvent},
layout::Rect,
text::{Line, Span},
style::{Modifier, Style, Stylize, palette::tailwind::*},
widgets::{
Block, Borders, HighlightSpacing, List, ListState, Padding, Paragraph, StatefulWidget,
Widget,
Block, Borders, HighlightSpacing, List,
ListState, Padding, StatefulWidget, Paragraph
},
};
use ratatui::prelude::*;
use crate::game;
const GAME_HEADER_STYLE: Style = Style::new()
.fg(NEUTRAL.c100)
.fg(ZINC.c100)
.bg(BLUE.c600)
.add_modifier(Modifier::BOLD);
const PROFILE_HEADER_STYLE: Style = Style::new()
.fg(NEUTRAL.c100)
.fg(ZINC.c100)
.bg(VIOLET.c600)
.add_modifier(Modifier::BOLD);
const HEADER_STYLE: Style = Style::new().fg(ROSE.c500).add_modifier(Modifier::BOLD);
const SELECTED_STYLE: Style = Style::new().bg(NEUTRAL.c700).add_modifier(Modifier::BOLD);
const SELECTED_STYLE: Style = Style::new().bg(ZINC.c700).add_modifier(Modifier::BOLD);
const FOOTER_LEFT_STYLE: Style = Style::new().fg(PURPLE.c400).add_modifier(Modifier::BOLD);
const FOOTER_RIGHT_STYLE: Style = Style::new().fg(LIME.c400);
pub struct MenuList {
items: Vec<&'static str>,
state: ListState,
}
impl FromIterator<&'static str> for MenuList {
fn from_iter<I: IntoIterator<Item = &'static str>>(iter: I) -> Self {
let items = iter.into_iter().collect();
let state = ListState::default();
Self { items, state }
}
}
impl Default for App {
fn default() -> Self {
Self {
should_exit: false,
menu: MenuList::from_iter(["40L", "Blitz", "txLadder", "Config"]),
}
}
}
impl App {
fn render_header(&mut self, area: Rect, buf: &mut Buffer) {
pub fn render_header(area: Rect, buf: &mut Buffer) {
Block::new()
.title(Line::raw(" txtris ").centered().style(HEADER_STYLE))
.borders(Borders::TOP)
.render(area, buf);
}
fn render_footer(&mut self, area: Rect, buf: &mut Buffer) {
pub fn render_footer(area: Rect, buf: &mut Buffer) {
Block::new()
.title(
Line::raw(" Atiran © 2025 ")
@ -73,31 +52,14 @@ impl App {
.render(area, buf);
}
fn render_list(&mut self, area: Rect, buf: &mut Buffer) {
let block = Block::new()
.title(Line::raw(" Game ").centered().style(GAME_HEADER_STYLE))
.padding(Padding::symmetric(2, 1))
.borders(Borders::ALL);
let items: Vec<&'static str> = self.menu.items.clone();
let list = List::new(items)
.block(block)
.highlight_style(SELECTED_STYLE)
.highlight_symbol(" ")
.highlight_spacing(HighlightSpacing::Always);
StatefulWidget::render(list, area, buf, &mut self.menu.state);
}
fn render_profile(&mut self, area: Rect, buf: &mut Buffer) {
pub fn render_profile(area: Rect, buf: &mut Buffer) {
let block = Block::new()
.title(
Line::raw(" Profile ")
.centered()
.style(PROFILE_HEADER_STYLE),
)
.padding(Padding::symmetric(3, 1))
.padding(Padding::symmetric(2, 1))
.borders(Borders::ALL);
let text = vec![
@ -119,61 +81,65 @@ impl App {
Paragraph::new(text).block(block).render(area, buf);
}
pub fn handle_key(&mut self, key: KeyEvent) {
if key.kind != KeyEventKind::Press {
return;
#[derive(Clone)]
pub struct OptionList {
items: Vec<&'static str>,
state: ListState,
}
impl FromIterator<&'static str> for OptionList {
fn from_iter<I: IntoIterator<Item = &'static str>>(iter: I) -> Self {
let items = iter.into_iter().collect();
let state = ListState::default();
Self { items, state }
}
}
impl OptionList {
pub fn handle_key(mut self, key: KeyEvent) -> game::Mode {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true,
KeyCode::Char('j') | KeyCode::Down => self.select_next(),
KeyCode::Char('k') | KeyCode::Up => self.select_previous(),
KeyCode::Char('q') | KeyCode::Esc => game::Mode::Exit,
KeyCode::Char('j') | KeyCode::Down => {
self.select_next();
game::Mode::MainMenu(self)
}
KeyCode::Char('k') | KeyCode::Up => {
self.select_previous();
game::Mode::MainMenu(self)
}
KeyCode::Char('c') => {
if key.modifiers.contains(event::KeyModifiers::CONTROL) {
self.should_exit = true;
game::Mode::Exit
} else {
game::Mode::MainMenu(self)
}
}
_ => {}
_ => game::Mode::MainMenu(self),
}
}
fn select_next(&mut self) {
self.menu.state.select_next();
self.state.select_next();
}
fn select_previous(&mut self) {
self.menu.state.select_previous();
}
self.state.select_previous();
}
fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect {
let [area] = Layout::horizontal([horizontal])
.flex(Flex::Center)
.areas(area);
let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area);
area
}
pub fn render(&mut self, area: Rect, buf: &mut Buffer) {
let block = Block::new()
.title(Line::raw(" Game ").centered().style(GAME_HEADER_STYLE))
.padding(Padding::symmetric(2, 1))
.borders(Borders::ALL);
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
let [header_area, main_area, footer_area] = Layout::vertical([
Constraint::Length(1),
Constraint::Fill(1),
Constraint::Length(2),
])
.areas(area);
let items: Vec<&'static str> = self.items.clone();
let center_area = center(
main_area,
Constraint::Length(50),
Constraint::Percentage(60),
);
let list = List::new(items)
.block(block)
.highlight_style(SELECTED_STYLE)
.highlight_symbol(" ")
.highlight_spacing(HighlightSpacing::Always);
let [profile_area, list_outer_area] =
Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(center_area);
self.render_header(header_area, buf);
self.render_profile(profile_area, buf);
self.render_list(list_outer_area, buf);
self.render_footer(footer_area, buf);
StatefulWidget::render(list, area, buf, &mut self.state);
}
}

7
server/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "server"
version = "0.1.0"

7
txcore/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "txcore"
version = "0.1.0"