From 0b1ba27037bd116087cb1bdcfe81d850b20eab97 Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Tue, 10 Nov 2020 22:30:08 -0500 Subject: Refactoring --- src/gui/boardwidget.rs | 264 +++++++++++++++++++++++++++++++++++++++++++++++++ src/gui/mod.rs | 122 +++++++++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 src/gui/boardwidget.rs create mode 100644 src/gui/mod.rs (limited to 'src/gui') diff --git a/src/gui/boardwidget.rs b/src/gui/boardwidget.rs new file mode 100644 index 0000000..3eca0ed --- /dev/null +++ b/src/gui/boardwidget.rs @@ -0,0 +1,264 @@ +/* + 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::*}; +use std::rc::*; + +use crate::game::*; + +// All of theses properties are not expected to change under window +// resizing, so we don't need more than an Rc<...> to one of these +// when rendering in button.draw2(...) later. We do, however, need to +// use compute_square_size because the widget may be resized. +struct BoardRenderer { + even_square_colour: Color, + odd_square_colour: Color, + black_colour: Color, + white_colour: Color, + stone_size: f32, + game_size: i32, + frame_pad: i32, + xorg: i32, + yorg: 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, + // Defaults + game_size: 5, + frame_pad: 5, + xorg: 0, + yorg: 0, + } + } + + fn with_size(mut self, size: i32) -> BoardRenderer { + self.game_size = size; + return self; + } + + fn with_org(mut self, button: &Button) -> BoardRenderer { + let x0 = button.x(); + let y0 = button.y(); + self.xorg = x0 + self.frame_pad; + self.yorg = y0 + self.frame_pad; + return self; + } + + fn compute_square_size(&self, button: &Button) -> i32 { + let board_size = i32::min(button.width(), button.height()) - self.frame_pad * 2; + return board_size / self.game_size; + } + + fn render_stone( + &self, + square_size: i32, + 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 = (square_size as f32 * (1.00 - self.stone_size)).round() as i32; + let d = square_size - stone_gap * 2; + let shift = (shift * d) / 8; + let x1 = self.xorg + col * square_size + stone_gap + shift; + let y1 = self.yorg + row * 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, square_size: i32, player: Player, row: i32, col: i32, shift: i32) { + let stone_gap = 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 = self.xorg + col * square_size + icon_gap; + let y1 = self.yorg + (row + 1) * 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, square_size: i32, 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(square_size, piece.player, row, col, shift); + } else { + self.render_stone( + square_size, + piece.player, + piece.stone, + row, + col, + shift - i32::max(0, cutoff), + ); + } + } + } + + fn render_board(&self, square_size: i32, board: &Vec) { + let mut x = 0; + let mut y = 0; + for stack in board.iter() { + self.render_stack(square_size, x, y, stack); + x += 1; + if x + 1 >= self.game_size { + x = 0; + y += 1; + if y + 1 >= self.game_size { + panic!("Game size mismatch between renderer and internal state."); + } + } + } + } + + fn render_background(&self, square_size: i32) { + 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( + self.xorg + x * square_size, + self.yorg + y * square_size, + square_size, + square_size, + ); + } + } + } +} + +pub struct BoardWidget { + button: Button, + renderer: Rc, +} + +impl BoardWidget { + pub fn new(button: Button, size: i32) -> BoardWidget { + let renderer = Rc::new(BoardRenderer::default().with_size(size).with_org(&button)); + let mut g = BoardWidget { button, renderer }; + g.button.draw2(g.generate_draw(&Vec::new())); + return g; + } + + pub fn to_square(&self, x: i32, y: i32) -> (i32, i32) { + let square_size = self.renderer.compute_square_size(&self.button); + + let x = x - self.renderer.xorg; + let y = y - self.renderer.yorg; + + (x / square_size, y / square_size) + } + + pub fn generate_draw(&self, board: &Vec) -> impl FnMut(&mut Button) { + let renderer = self.renderer.clone(); + let board = board.clone(); + + move |button: &mut Button| { + let square_size = renderer.compute_square_size(&button); + renderer.render_background(square_size); + renderer.render_board(square_size, &board); + + // // Test code for now + // renderer.render_stack(square_size, 1, 4, &stack); + // renderer.render_stone(square_size, Player::Black, Stone::Capstone, 1, 2, 0); + // renderer.render_stone(square_size, Player::Black, Stone::Standing, 2, 2, 0); + } + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 0000000..341bded --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,122 @@ +/* + 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 . +*/ + +mod boardwidget; +use crate::gui::boardwidget::*; + +use fltk::{app::*, button::*, menu::*, text::*, window::*}; + +#[derive(Clone, Copy)] +enum GUIMessage { + None, + Test, + BoardClick, +} + +pub struct GUI { + app: App, + window: DoubleWindow, + board: BoardWidget, + 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 = DoubleWindow::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_button = Button::default() + .with_size(800 - LOG_WIDTH, 600 - menu.height()) + .below_of(&menu, 0); + board_button.set_frame(FrameType::EmbossedBox); + board_button.emit(sender, GUIMessage::BoardClick); + + window.resizable(&mut board_button); + window.end(); + window.show(); + + let board = BoardWidget::new(board_button, 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(); + let (x, y) = self.board.to_square(x, y); + self.log.insert(&format!("Board click ({},{})\n", x, y)); + } + _ => (), + } + } + } + Ok(()) + } +} -- cgit v1.3.1