/*
This file is part of Takwrap.
Takwrap is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Foobar. If not, see .
*/
use fltk::{app::*, button::*, draw::*, menu::*, text::*, window::*};
use crate::game::*;
struct BoardRendererBackend {
xorg: i32,
yorg: i32,
square_size: i32,
}
impl BoardRendererBackend {
fn initialise(frame: &Button, frame_pad: i32, game_size: i32) -> BoardRendererBackend {
let x0 = frame.x();
let y0 = frame.y();
let xorg = x0 + frame_pad;
let yorg = y0 + frame_pad;
let board_size = i32::min(frame.width(), frame.height()) - frame_pad * 2;
BoardRendererBackend {
xorg,
yorg,
square_size: board_size / game_size,
}
}
}
#[derive(Clone, Copy)]
struct BoardRenderer {
even_square_colour: Color,
odd_square_colour: Color,
black_colour: Color,
white_colour: Color,
stone_size: f32,
game_size: i32,
}
impl BoardRenderer {
fn default() -> BoardRenderer {
BoardRenderer {
// Sandy brown
odd_square_colour: Color::from_rgb(244, 164, 96),
// Moccasin
even_square_colour: Color::from_rgb(255, 228, 181),
// Colours for pieces
black_colour: Color::from_rgb(178, 34, 34),
white_colour: Color::from_rgb(250, 235, 215),
// Gap between stones and edged of square
stone_size: 0.8,
// I'm not sure where this should go
game_size: 5,
}
}
fn with_size(mut self, size: i32) -> BoardRenderer {
self.game_size = size;
return self;
}
fn render_stone(
&self,
backend: &BoardRendererBackend,
player: Player,
stone: Stone,
row: i32,
col: i32,
shift: i32,
) {
set_draw_color(match player {
Player::Black => self.black_colour,
Player::White => self.white_colour,
});
// Gap scales with square size
let stone_gap = (backend.square_size as f32 * (1.00 - self.stone_size)).round() as i32;
let d = backend.square_size - stone_gap * 2;
let shift = (shift * d) / 8;
let x1 = backend.xorg + col * backend.square_size + stone_gap + shift;
let y1 = backend.yorg + row * backend.square_size + stone_gap - shift;
// Outline with thickness varying by stone size
let setup_border_pen = || {
set_line_style(LineStyle::Solid, i32::max(1, d / 24));
set_draw_color(Color::Black);
};
match stone {
Stone::Capstone => {
draw_pie(x1, y1, d, d, 0.0, 360.0);
setup_border_pen();
let r = (d as f64) / 2.0;
draw_circle(r + x1 as f64, r + y1 as f64, r);
}
Stone::Flat => {
draw_rectf(x1, y1, d, d);
setup_border_pen();
draw_rect_with_color(x1, y1, d, d, Color::Black);
}
Stone::Standing => {
// These look bad shifted so we shift it back if was
let df = d as f64;
// and we have to fudge it a little? Some rounding things presumably
let ds = if shift > 0 { df / 8.0 - 0.5 } else { 0.0 };
let x1 = x1 as f64 - ds;
let y1 = y1 as f64 + ds;
let verts = [
(x1 + df * 0.3, y1 + df * 0.075),
(x1 + df * 0.925, y1 + df * 0.7),
(x1 + df * 0.7, y1 + df * 0.925),
(x1 + df * 0.075, y1 + df * 0.3),
];
begin_polygon();
for (x, y) in verts.iter() {
vertex(*x, *y)
}
end_polygon();
setup_border_pen();
begin_loop();
for (x, y) in verts.iter() {
vertex(*x, *y);
}
end_loop();
}
}
set_line_style(LineStyle::Solid, 1);
}
fn render_icons(
&self,
backend: &BoardRendererBackend,
player: Player,
row: i32,
col: i32,
shift: i32,
) {
let stone_gap = backend.square_size as f32 * (1.00 - self.stone_size);
let icon_gap = (stone_gap * 0.15).round() as i32;
let icon_width = (stone_gap * 0.7).round() as i32;
let icon_height = icon_width / 2;
let x1 = backend.xorg + col * backend.square_size + icon_gap;
let y1 = backend.yorg + (row + 1) * backend.square_size - icon_gap;
set_line_style(LineStyle::Solid, i32::max(1, icon_width / 8));
set_draw_color(Color::Black);
set_draw_color(match player {
Player::Black => self.black_colour,
Player::White => self.white_colour,
});
let y2 = y1 - icon_height * (1 + shift as i32);
draw_rectf(x1, y2, icon_width, icon_height);
draw_rect_with_color(x1, y2, icon_width, icon_height, Color::Black);
set_line_style(LineStyle::Solid, 1);
}
fn render_stack(&self, backend: &BoardRendererBackend, row: i32, col: i32, stack: &Stack) {
// Is there a better way than usize::min((...), i32::MAX as usize) as i32 ?
let cutoff = stack.len() as i32 - self.game_size;
for (shift, piece) in stack.iter().enumerate() {
let shift = shift as i32; // TODO: please don't overflow
if shift < cutoff {
self.render_icons(backend, piece.player, row, col, shift);
} else {
self.render_stone(
backend,
piece.player,
piece.stone,
row,
col,
shift - i32::max(0, cutoff),
);
}
}
}
fn render_board(&self, backend: &BoardRendererBackend) {
for x in 0..self.game_size {
for y in 0..self.game_size {
if (x + y) % 2 == 0 {
set_draw_color(self.even_square_colour);
} else {
set_draw_color(self.odd_square_colour);
}
draw_rectf(
backend.xorg + x * backend.square_size,
backend.yorg + y * backend.square_size,
backend.square_size,
backend.square_size,
);
}
}
}
}
struct GUIBoard {
frame: Button,
renderer: BoardRenderer,
size: i32,
}
impl GUIBoard {
fn new(frame: Button, size: i32) -> GUIBoard {
let renderer = BoardRenderer::default().with_size(size);
let mut g = GUIBoard {
frame,
size,
renderer,
};
g.frame.draw2(g.render());
return g;
}
fn render(&self) -> impl Fn(&mut Button) {
let renderer = self.renderer;
let game_size = self.size;
let stack = vec![
Piece {
player: Player::Black,
stone: Stone::Flat,
},
Piece {
player: Player::White,
stone: Stone::Flat,
},
Piece {
player: Player::White,
stone: Stone::Flat,
},
Piece {
player: Player::Black,
stone: Stone::Flat,
},
Piece {
player: Player::White,
stone: Stone::Flat,
},
Piece {
player: Player::Black,
stone: Stone::Flat,
},
Piece {
player: Player::Black,
stone: Stone::Flat,
},
Piece {
player: Player::White,
stone: Stone::Flat,
},
Piece {
player: Player::Black,
stone: Stone::Flat,
},
Piece {
player: Player::White,
stone: Stone::Standing,
},
];
move |frame: &mut Button| {
let backend = BoardRendererBackend::initialise(frame, 5, game_size);
renderer.render_board(&backend);
// Test code for now
renderer.render_stack(&backend, 1, 4, &stack);
renderer.render_stone(&backend, Player::Black, Stone::Capstone, 1, 2, 0);
renderer.render_stone(&backend, Player::Black, Stone::Standing, 2, 2, 0);
}
}
}
#[derive(Clone, Copy)]
enum GUIMessage {
None,
Test,
BoardClick,
}
pub struct GUI {
app: App,
window: Window,
board: GUIBoard,
log: TextDisplay,
sender: Sender,
receiver: Receiver,
}
impl GUI {
pub fn new() -> GUI {
let app = App::default();
// TODO: Why is this not the same as
/*
let mut window = Window::default()
.with_size(800, 600)
.with_label("Takwrap")
.center_screen();
*/
// Windows made that way are not resizable?
let mut window = Window::new(0, 0, 800, 600, "Takwrap").center_screen();
let (sender, receiver) = channel::();
const MENU_HEIGHT: i32 = 24;
let mut menu = SysMenuBar::default().with_size(800, MENU_HEIGHT);
// menu.set_text_font(Font::Helvetica);
menu.set_color(Color::Light2);
menu.add_emit(
"&File/New...\t",
Shortcut::None,
MenuFlag::Normal,
sender,
GUIMessage::None,
);
menu.add_emit(
"&File/Open...\t",
Shortcut::None,
MenuFlag::Normal,
sender,
GUIMessage::Test,
);
const LOG_WIDTH: i32 = 800 - 600 + MENU_HEIGHT;
let mut log = TextDisplay::default()
.with_size(LOG_WIDTH, 600 - menu.height())
.with_pos(800 - LOG_WIDTH, menu.height());
log.set_buffer(Some(TextBuffer::default()));
log.insert("test\n");
let mut board_frame = Button::default()
.with_size(800 - LOG_WIDTH, 600 - menu.height())
.below_of(&menu, 0);
board_frame.set_frame(FrameType::EmbossedBox);
board_frame.emit(sender, GUIMessage::BoardClick);
window.resizable(&mut board_frame);
window.end();
window.show();
let board = GUIBoard::new(board_frame, 5);
// button.emit(sender, GUIMessage::Foo);
GUI {
app,
board,
window,
log,
sender,
receiver,
}
}
pub fn run(&mut self) -> Result<(), fltk::prelude::FltkError> {
while self.app.wait() {
if let Some(msg) = self.receiver.recv() {
match msg {
GUIMessage::BoardClick => {
let x = event_x();
let y = event_y();
self.log.insert(&format!("Board click ({},{})\n", x, y));
}
_ => (),
}
}
}
Ok(())
}
}