/* 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::*, frame::*, menu::*, text::*, window::*}; use crate::game::*; struct BoardRendererBackend { xorg: i32, yorg: i32, square_size: i32, } impl BoardRendererBackend { fn initialise(frame: &Frame, 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: Frame, renderer: BoardRenderer, size: i32, } impl GUIBoard { fn new(frame: Frame, 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 Frame) { 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 Frame| { 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, } 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"); let mut board_frame = Frame::default() .with_size(800 - LOG_WIDTH, 600 - menu.height()) .below_of(&menu, 0); board_frame.set_frame(FrameType::EmbossedBox); 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 { _ => (), } } } Ok(()) } }