ui abstraction
This commit is contained in:
parent
ecbb7c6756
commit
489c2814e2
@ -1,4 +1,5 @@
|
|||||||
mod audio;
|
mod audio;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
pub use render::start_game;
|
pub use render::start_game;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use crate::actions::{Action, ActionsHeld};
|
use crate::actions::{Action, ActionsHeld};
|
||||||
use crate::board::Board;
|
use crate::board::Board;
|
||||||
use crate::game::{CurrentTetromino, Game};
|
use crate::game::{CurrentTetromino, Game};
|
||||||
use crate::Rgb;
|
|
||||||
use sdl2::event::Event;
|
use sdl2::event::Event;
|
||||||
use sdl2::keyboard::Keycode;
|
use sdl2::keyboard::Keycode;
|
||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
@ -11,146 +10,109 @@ use sdl2::ttf::Sdl2TtfContext;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::audio::{self};
|
use super::audio::{self};
|
||||||
|
use super::ui::{Rgb, UiCtx};
|
||||||
|
|
||||||
const WIDTH: i32 = 1000;
|
const WIDTH: i32 = 1000;
|
||||||
const HEIGHT: i32 = 800;
|
const HEIGHT: i32 = 800;
|
||||||
|
|
||||||
fn draw_board_tile(
|
fn font_texture<'font, 'a, P: AsRef<std::path::Path>, Text: AsRef<str>, C>(
|
||||||
canvas: &mut WindowCanvas,
|
font: P,
|
||||||
width: usize,
|
text: Text,
|
||||||
height: usize,
|
|
||||||
color: &Rgb,
|
|
||||||
filled: bool,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
canvas.set_draw_color(Color::RGB(color.0, color.1, color.2));
|
|
||||||
let rect = Rect::new(
|
|
||||||
center(24 * Board::WIDTH as i32, WIDTH) + width as i32 * 24,
|
|
||||||
center(24 * Board::HEIGHT as i32, HEIGHT) + height as i32 * 24,
|
|
||||||
24,
|
|
||||||
24,
|
|
||||||
);
|
|
||||||
if filled {
|
|
||||||
canvas.fill_rect(rect)
|
|
||||||
} else {
|
|
||||||
canvas.draw_rect(rect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn center(length: i32, max: i32) -> i32 {
|
|
||||||
(max - length) / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_tetromino_from_parts(
|
|
||||||
canvas: &mut WindowCanvas,
|
|
||||||
x: i8,
|
|
||||||
y: i8,
|
|
||||||
color: Rgb,
|
|
||||||
pattern: [[bool; 4]; 4],
|
|
||||||
filled: bool,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
for (y_offset, row) in pattern.iter().enumerate() {
|
|
||||||
for x_offset in row
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, exists)| **exists)
|
|
||||||
.map(|(x, _)| x)
|
|
||||||
{
|
|
||||||
let x = x_offset as i8 + x;
|
|
||||||
let y = y_offset as i8 + y;
|
|
||||||
|
|
||||||
if y < 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_board_tile(canvas, x as usize, y as usize, &color, filled)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_board(
|
|
||||||
canvas: &mut WindowCanvas,
|
|
||||||
board: &Board,
|
|
||||||
current: &CurrentTetromino,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
canvas.set_draw_color(Color::WHITE);
|
|
||||||
canvas.draw_rect(Rect::new(
|
|
||||||
center(24 * Board::WIDTH as i32, WIDTH) - 1,
|
|
||||||
center(24 * Board::HEIGHT as i32, HEIGHT) - 1,
|
|
||||||
24 * Board::WIDTH as u32 + 2,
|
|
||||||
24 * Board::HEIGHT as u32 + 2,
|
|
||||||
))?;
|
|
||||||
|
|
||||||
for (y, row) in board.iter().enumerate() {
|
|
||||||
for (x, piece) in row.iter().enumerate() {
|
|
||||||
let color = match piece {
|
|
||||||
Some(t) => Rgb::from_tetromino(t),
|
|
||||||
None => Rgb(0, 0, 0),
|
|
||||||
};
|
|
||||||
draw_board_tile(canvas, x, y, &color, true)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pattern = current.tetromino.direction_pattern(¤t.direction);
|
|
||||||
|
|
||||||
draw_tetromino_from_parts(
|
|
||||||
canvas,
|
|
||||||
current.x,
|
|
||||||
board.lowest_y(¤t),
|
|
||||||
Rgb(255, 255, 255),
|
|
||||||
pattern,
|
|
||||||
false,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
draw_tetromino_from_parts(
|
|
||||||
canvas,
|
|
||||||
current.x,
|
|
||||||
current.y,
|
|
||||||
Rgb::from_tetromino(¤t.tetromino),
|
|
||||||
pattern,
|
|
||||||
true,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn font_texture<'font, 'a, C>(
|
|
||||||
text: &'a str,
|
|
||||||
ttf_context: &'a Sdl2TtfContext,
|
ttf_context: &'a Sdl2TtfContext,
|
||||||
texture_creator: &'font TextureCreator<C>,
|
texture_creator: &'font TextureCreator<C>,
|
||||||
) -> Result<Texture<'font>, String> {
|
) -> Result<Texture<'font>, String> {
|
||||||
let font = ttf_context.load_font("resources/josenfin_sans_regular.ttf", 24)?;
|
let font = ttf_context.load_font(font, 24)?;
|
||||||
let game_over_text = font.render(text).solid(Color::RGB(255, 255, 255)).unwrap();
|
let game_over_text = font
|
||||||
|
.render(text.as_ref())
|
||||||
|
.solid(Color::RGB(255, 255, 255))
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
let texture = texture_creator
|
let texture = texture_creator
|
||||||
.create_texture_from_surface(game_over_text)
|
.create_texture_from_surface(game_over_text)
|
||||||
.unwrap();
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
Ok(texture)
|
Ok(texture)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_important_text(
|
struct SdlUiCtx {
|
||||||
text: &str,
|
canvas: WindowCanvas,
|
||||||
canvas: &mut WindowCanvas,
|
ttf: Sdl2TtfContext,
|
||||||
ttf_context: &Sdl2TtfContext,
|
}
|
||||||
) -> Result<(), String> {
|
|
||||||
let texture_creator = canvas.texture_creator();
|
|
||||||
let texture = font_texture(text, &ttf_context, &texture_creator)?;
|
|
||||||
|
|
||||||
let size = texture.query();
|
impl SdlUiCtx {
|
||||||
let width = size.width;
|
fn present(&mut self) {
|
||||||
let height = size.height;
|
self.canvas.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let x = center(width as i32, WIDTH);
|
impl UiCtx<String> for SdlUiCtx {
|
||||||
let y = center(height as i32, HEIGHT);
|
fn window_size(&self) -> Result<(i32, i32), String> {
|
||||||
|
let (width, height) = self.canvas.window().size();
|
||||||
|
Ok((width as i32, height as i32))
|
||||||
|
}
|
||||||
|
|
||||||
canvas.set_draw_color(Color::WHITE);
|
fn fill_rect(
|
||||||
canvas.draw_rect(Rect::new(x - 9, y - 9, width + 18, height + 18))?;
|
&mut self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
rgb: &super::ui::Rgb,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
self.canvas.set_draw_color(Color::RGB(rgb.0, rgb.1, rgb.2));
|
||||||
|
self.canvas
|
||||||
|
.fill_rect(Rect::new(x, y, width as u32, height as u32))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
canvas.set_draw_color(Color::RGB(16, 16, 16));
|
fn outline_rect(
|
||||||
canvas.fill_rect(Rect::new(x - 8, y - 8, width + 16, height + 16))?;
|
&mut self,
|
||||||
canvas.copy(&texture, None, Some(Rect::new(x, y, width, height)))?;
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
rgb: &super::ui::Rgb,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
self.canvas.set_draw_color(Color::RGB(rgb.0, rgb.1, rgb.2));
|
||||||
|
self.canvas
|
||||||
|
.draw_rect(Rect::new(x, y, width as u32, height as u32))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
fn text_size<P: AsRef<std::path::Path>, Text: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
font: P,
|
||||||
|
text: Text,
|
||||||
|
) -> Result<(i32, i32), String> {
|
||||||
|
let texture_creator = self.canvas.texture_creator();
|
||||||
|
let texture = font_texture(font, text, &self.ttf, &texture_creator)?;
|
||||||
|
let query = texture.query();
|
||||||
|
Ok((query.width as i32, query.height as i32))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_text<P: AsRef<std::path::Path>, Text: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
font: P,
|
||||||
|
text: Text,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let texture_creator = self.canvas.texture_creator();
|
||||||
|
let texture = font_texture(font, text, &self.ttf, &texture_creator)?;
|
||||||
|
self.canvas.copy(
|
||||||
|
&texture,
|
||||||
|
None,
|
||||||
|
Some(Rect::new(x, y, width as u32, height as u32)),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self, rgb: &Rgb) -> Result<(), String> {
|
||||||
|
self.canvas.set_draw_color(Color::RGB(rgb.0, rgb.1, rgb.2));
|
||||||
|
self.canvas.clear();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_game() -> Result<(), String> {
|
pub fn start_game() -> Result<(), String> {
|
||||||
@ -170,11 +132,14 @@ pub fn start_game() -> Result<(), String> {
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut canvas = window.into_canvas().build().unwrap();
|
let canvas = window.into_canvas().build().unwrap();
|
||||||
|
let mut ctx = SdlUiCtx {
|
||||||
|
canvas,
|
||||||
|
ttf: ttf_context,
|
||||||
|
};
|
||||||
let mut event_pump = sdl_context.event_pump()?;
|
let mut event_pump = sdl_context.event_pump()?;
|
||||||
'running: loop {
|
'running: loop {
|
||||||
canvas.set_draw_color(Color::RGB(16, 16, 16));
|
ctx.clear(&Rgb(16, 16, 16))?;
|
||||||
canvas.clear();
|
|
||||||
for event in event_pump.poll_iter() {
|
for event in event_pump.poll_iter() {
|
||||||
match event {
|
match event {
|
||||||
Event::Quit { .. }
|
Event::Quit { .. }
|
||||||
@ -231,19 +196,17 @@ pub fn start_game() -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_board(&mut canvas, &game.board, &game.current_tetromino)?;
|
ctx.draw_board(&game.board, &game.current_tetromino)?;
|
||||||
|
|
||||||
if paused {
|
if paused {
|
||||||
draw_important_text(
|
ctx.draw_important_text(
|
||||||
|
"resources/josenfin_sans_regular.ttf",
|
||||||
"game paused o_o... press [p] to unpause !!",
|
"game paused o_o... press [p] to unpause !!",
|
||||||
&mut canvas,
|
|
||||||
&ttf_context,
|
|
||||||
)?;
|
)?;
|
||||||
} else if game.game_over {
|
} else if game.game_over {
|
||||||
draw_important_text(
|
ctx.draw_important_text(
|
||||||
|
"resources/josenfin_sans_regular.ttf",
|
||||||
"game over T_T... press [enter] 2 restart :D",
|
"game over T_T... press [enter] 2 restart :D",
|
||||||
&mut canvas,
|
|
||||||
&ttf_context,
|
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
let effects = game.step(&actions);
|
let effects = game.step(&actions);
|
||||||
@ -254,7 +217,7 @@ pub fn start_game() -> Result<(), String> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.present();
|
ctx.present();
|
||||||
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
152
src/gui/ui.rs
Normal file
152
src/gui/ui.rs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
use crate::{board::Board, game::CurrentTetromino, tetromino::Tetromino};
|
||||||
|
|
||||||
|
pub trait UiCtx<Err> {
|
||||||
|
fn window_size(&self) -> Result<(i32, i32), Err>;
|
||||||
|
fn fill_rect(&mut self, x: i32, y: i32, width: i32, height: i32, rgb: &Rgb) -> Result<(), Err>;
|
||||||
|
fn outline_rect(
|
||||||
|
&mut self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
rgb: &Rgb,
|
||||||
|
) -> Result<(), Err>;
|
||||||
|
fn text_size<P: AsRef<std::path::Path>, Text: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
font: P,
|
||||||
|
text: Text,
|
||||||
|
) -> Result<(i32, i32), Err>;
|
||||||
|
fn fill_text<P: AsRef<std::path::Path>, Text: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
font: P,
|
||||||
|
text: Text,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
) -> Result<(), Err>;
|
||||||
|
fn clear(&mut self, rgb: &Rgb) -> Result<(), Err>;
|
||||||
|
|
||||||
|
fn draw_tetromino_from_parts(
|
||||||
|
&mut self,
|
||||||
|
x: i8,
|
||||||
|
y: i8,
|
||||||
|
color: Rgb,
|
||||||
|
pattern: [[bool; 4]; 4],
|
||||||
|
filled: bool,
|
||||||
|
) -> Result<(), Err> {
|
||||||
|
for (y_offset, row) in pattern.iter().enumerate() {
|
||||||
|
for x_offset in row
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, exists)| **exists)
|
||||||
|
.map(|(x, _)| x)
|
||||||
|
{
|
||||||
|
let x = x_offset as i8 + x;
|
||||||
|
let y = y_offset as i8 + y;
|
||||||
|
|
||||||
|
if y < 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.draw_board_tile(x as i32, y as i32, &color, filled)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_board_tile(&mut self, x: i32, y: i32, color: &Rgb, filled: bool) -> Result<(), Err> {
|
||||||
|
let tile_size = 24;
|
||||||
|
let (win_width, win_height) = self.window_size()?;
|
||||||
|
let x = center(tile_size * Board::WIDTH as i32, win_width) + x * tile_size;
|
||||||
|
let y = center(tile_size * Board::HEIGHT as i32, win_height) + y * tile_size;
|
||||||
|
if filled {
|
||||||
|
self.fill_rect(x, y, 24, 24, color)?;
|
||||||
|
} else {
|
||||||
|
self.outline_rect(x, y, 24, 24, color)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_board(&mut self, board: &Board, current: &CurrentTetromino) -> Result<(), Err> {
|
||||||
|
let (win_width, win_height) = self.window_size()?;
|
||||||
|
self.outline_rect(
|
||||||
|
center(24 * Board::WIDTH as i32, win_width) - 1,
|
||||||
|
center(24 * Board::HEIGHT as i32, win_height) - 1,
|
||||||
|
24 * Board::WIDTH as i32 + 2,
|
||||||
|
24 * Board::HEIGHT as i32 + 2,
|
||||||
|
&Rgb(255, 255, 255),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for (y, row) in board.iter().enumerate() {
|
||||||
|
for (x, piece) in row.iter().enumerate() {
|
||||||
|
let color = match piece {
|
||||||
|
Some(t) => Rgb::from_tetromino(t),
|
||||||
|
None => Rgb(0, 0, 0),
|
||||||
|
};
|
||||||
|
self.draw_board_tile(x as i32, y as i32, &color, true)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern = current.tetromino.direction_pattern(¤t.direction);
|
||||||
|
|
||||||
|
self.draw_tetromino_from_parts(
|
||||||
|
current.x,
|
||||||
|
board.lowest_y(¤t),
|
||||||
|
Rgb(255, 255, 255),
|
||||||
|
pattern,
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.draw_tetromino_from_parts(
|
||||||
|
current.x,
|
||||||
|
current.y,
|
||||||
|
Rgb::from_tetromino(¤t.tetromino),
|
||||||
|
pattern,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_important_text<P: AsRef<std::path::Path>, Text: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
font: P,
|
||||||
|
text: Text,
|
||||||
|
) -> Result<(), Err> {
|
||||||
|
let (win_width, win_height) = self.window_size()?;
|
||||||
|
let size = self.text_size(font.as_ref(), text.as_ref())?;
|
||||||
|
let width = size.0;
|
||||||
|
let height = size.1;
|
||||||
|
|
||||||
|
let x = center(width, win_width);
|
||||||
|
let y = center(height, win_height);
|
||||||
|
|
||||||
|
self.outline_rect(x - 9, y - 9, width + 18, height + 18, &Rgb(255, 255, 255))?;
|
||||||
|
|
||||||
|
self.fill_rect(x - 8, y - 8, width + 16, height + 16, &Rgb(16, 16, 16))?;
|
||||||
|
self.fill_text(font, text, x, y, width, height)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Rgb(pub u8, pub u8, pub u8);
|
||||||
|
|
||||||
|
impl Rgb {
|
||||||
|
pub fn from_tetromino(tetromino: &Tetromino) -> Self {
|
||||||
|
match tetromino {
|
||||||
|
Tetromino::I => Self(0, 255, 255),
|
||||||
|
Tetromino::J => Self(0, 0, 255),
|
||||||
|
Tetromino::L => Self(255, 128, 0),
|
||||||
|
Tetromino::O => Self(255, 255, 0),
|
||||||
|
Tetromino::S => Self(0, 255, 0),
|
||||||
|
Tetromino::T => Self(255, 0, 255),
|
||||||
|
Tetromino::Z => Self(255, 0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn center(length: i32, max: i32) -> i32 {
|
||||||
|
(max - length) / 2
|
||||||
|
}
|
16
src/main.rs
16
src/main.rs
@ -6,22 +6,6 @@ mod game;
|
|||||||
mod gui;
|
mod gui;
|
||||||
mod tetromino;
|
mod tetromino;
|
||||||
|
|
||||||
struct Rgb(u8, u8, u8);
|
|
||||||
|
|
||||||
impl Rgb {
|
|
||||||
fn from_tetromino(tetromino: &Tetromino) -> Self {
|
|
||||||
match tetromino {
|
|
||||||
Tetromino::I => Self(0, 255, 255),
|
|
||||||
Tetromino::J => Self(0, 0, 255),
|
|
||||||
Tetromino::L => Self(255, 128, 0),
|
|
||||||
Tetromino::O => Self(255, 255, 0),
|
|
||||||
Tetromino::S => Self(0, 255, 0),
|
|
||||||
Tetromino::T => Self(255, 0, 255),
|
|
||||||
Tetromino::Z => Self(255, 0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
gui::start_game().unwrap();
|
gui::start_game().unwrap();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user