diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index a15f37e..5c8e959 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -12,6 +12,15 @@ pub struct Size { pub height: u8, } +impl Default for Size { + fn default() -> Self { + Self { + width: 19, + height: 19, + } + } +} + pub(crate) struct AppState { game: Option, } diff --git a/kifu/kifu-core/src/ui/mod.rs b/kifu/kifu-core/src/ui/mod.rs index 1948529..e7b40c0 100644 --- a/kifu/kifu-core/src/ui/mod.rs +++ b/kifu/kifu-core/src/ui/mod.rs @@ -2,4 +2,4 @@ mod playing_field; pub use playing_field::{playing_field, PlayingFieldView}; mod types; -pub use types::{ChatElement, GameBoardElement, PlayerCardElement, TextFieldElement}; +pub use types::{ChatElement, GobanElement, PlayerCardElement, StoneElement, TextFieldElement}; diff --git a/kifu/kifu-core/src/ui/playing_field.rs b/kifu/kifu-core/src/ui/playing_field.rs index 437e07c..8787338 100644 --- a/kifu/kifu-core/src/ui/playing_field.rs +++ b/kifu/kifu-core/src/ui/playing_field.rs @@ -3,7 +3,7 @@ use crate::ui::types; #[derive(Clone, Debug)] pub struct PlayingFieldView { - pub board: types::GameBoardElement, + pub board: types::GobanElement, pub player_card_black: types::PlayerCardElement, pub player_card_white: types::PlayerCardElement, pub chat: types::ChatElement, @@ -12,16 +12,7 @@ pub struct PlayingFieldView { } pub fn playing_field() -> PlayingFieldView { - let mut spaces = Vec::new(); - (0..19).for_each(|_| spaces.push(Vec::new())); - - let board = types::GameBoardElement { - size: Size { - width: 19, - height: 19, - }, - spaces, - }; + let board = types::GobanElement::default(); let player_card_black = types::PlayerCardElement { color: Color::Black, diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index cb6b690..05092f3 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -12,10 +12,49 @@ pub struct StoneElement { pub jitter: Jitter, } +impl StoneElement { + pub fn new(color: Color) -> Self { + Self { + color, + jitter: Jitter { x: 0, y: 0 }, + } + } +} + #[derive(Clone, Debug)] -pub struct GameBoardElement { +pub struct GobanElement { pub size: Size, - pub spaces: Vec>, + pub spaces: Vec>, +} + +impl GobanElement { + pub fn new(size: Size) -> Self { + Self { + size: size.clone(), + spaces: (0..((size.width as usize) * (size.height as usize))) + .map(|_| None) + .collect::>>(), + } + } + + pub fn stone_mut<'a>(&'a mut self, row: u8, col: u8) -> &'a mut Option { + let addr = self.addr(row, col); + &mut self.spaces[addr] + } + + pub fn stone(&self, row: u8, col: u8) -> Option { + self.spaces[self.addr(row, col)].clone() + } + + fn addr(&self, row: u8, col: u8) -> usize { + ((row as usize) * (self.size.width as usize) + (col as usize)) as usize + } +} + +impl Default for GobanElement { + fn default() -> Self { + Self::new(Size::default()) + } } #[derive(Clone, Debug)] diff --git a/kifu/kifu-gtk/src/ui/goban.rs b/kifu/kifu-gtk/src/ui/goban.rs index 6c30670..7c82ded 100644 --- a/kifu/kifu-gtk/src/ui/goban.rs +++ b/kifu/kifu-gtk/src/ui/goban.rs @@ -1,30 +1,131 @@ use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; +use kifu_core::{ui::GobanElement, Color}; +use std::{cell::RefCell, rc::Rc}; + +const WIDTH: i32 = 800; +const HEIGHT: i32 = 800; #[derive(Default)] -pub struct GobanPrivate; +pub struct GobanPrivate { + drawing_area: gtk::DrawingArea, + + goban: Rc>, +} #[glib::object_subclass] impl ObjectSubclass for GobanPrivate { const NAME: &'static str = "Goban"; type Type = Goban; - type ParentType = gtk::DrawingArea; + type ParentType = gtk::Grid; } -impl ObjectImpl for GobanPrivate {} +impl ObjectImpl for GobanPrivate { + fn constructed(&self) { + self.drawing_area.set_width_request(WIDTH); + self.drawing_area.set_height_request(HEIGHT); + + let goban = self.goban.clone(); + self.drawing_area + .set_draw_func(move |_, context, width, height| { + let goban = goban.borrow(); + context.set_source_rgb(0.7, 0.7, 0.7); + let _ = context.paint(); + + context.set_source_rgb(0.1, 0.1, 0.1); + context.set_line_width(2.); + let hspace_between = ((width - 40) as f64) / ((goban.size.width - 1) as f64); + let vspace_between = ((height - 40) as f64) / ((goban.size.height - 1) as f64); + + let pen = Pen { + x_offset: 20., + y_offset: 20., + hspace_between, + vspace_between, + }; + + (0..goban.size.width).for_each(|col| { + context.move_to(20.0 + (col as f64) * hspace_between, 20.0); + context.line_to(20.0 + (col as f64) * hspace_between, (height as f64) - 20.0); + let _ = context.stroke(); + }); + (0..goban.size.height).for_each(|row| { + context.move_to(20.0, 20.0 + (row as f64) * vspace_between); + context.line_to((width - 20) as f64, 20.0 + (row as f64) * vspace_between); + let _ = context.stroke(); + }); + + context.set_source_rgb(0.1, 0.1, 0.0); + vec![4, 9, 15].into_iter().for_each(|col| { + vec![4, 9, 15].into_iter().for_each(|row| { + pen.star_point(context, col, row); + }); + }); + + (0..19).for_each(|col| { + (0..19).for_each(|row| { + match goban.stone(row, col) { + None => {} + Some(element) => { + pen.stone(&context, row, col, element.color); + } + }; + }) + }); + }); + } +} impl WidgetImpl for GobanPrivate {} -impl DrawingAreaImpl for GobanPrivate {} +impl GridImpl for GobanPrivate {} glib::wrapper! { - pub struct Goban(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; + pub struct Goban(ObjectSubclass) @extends gtk::Grid, gtk::Widget; } impl Goban { pub fn new() -> Self { let s: Self = Object::builder().build(); - s.set_width_request(1024); - s.set_height_request(768); - + s.attach(&s.imp().drawing_area, 1, 1, 1, 1); s } + + pub fn set_board(&self, goban: GobanElement) { + *self.imp().goban.borrow_mut() = goban; + self.imp().drawing_area.queue_draw(); + } +} + +struct Pen { + x_offset: f64, + y_offset: f64, + hspace_between: f64, + vspace_between: f64, +} + +impl Pen { + fn star_point(&self, context: &cairo::Context, row: u8, col: u8) { + context.arc( + self.x_offset + (col as f64) * self.hspace_between, + self.y_offset + (row as f64) * self.vspace_between, + 10., + 0., + 2. * std::f64::consts::PI, + ); + let _ = context.fill(); + } + + fn stone(&self, context: &cairo::Context, row: u8, col: u8, color: Color) { + match color { + Color::White => context.set_source_rgb(0.9, 0.9, 0.9), + Color::Black => context.set_source_rgb(0.0, 0.0, 0.0), + }; + context.arc( + 20.0 + (col as f64) * self.hspace_between, + 20.0 + (row as f64) * self.vspace_between, + 25.0, + 0.0, + 2.0 * std::f64::consts::PI, + ); + let _ = context.fill(); + } } diff --git a/kifu/kifu-gtk/src/ui/playing_field.rs b/kifu/kifu-gtk/src/ui/playing_field.rs index 0dc4be6..15236a0 100644 --- a/kifu/kifu-gtk/src/ui/playing_field.rs +++ b/kifu/kifu-gtk/src/ui/playing_field.rs @@ -2,7 +2,10 @@ use crate::ui::{Chat, Goban, PlayerCard}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use kifu_core::{ - ui::{ChatElement, GameBoardElement, PlayerCardElement, PlayingFieldView, TextFieldElement}, + ui::{ + ChatElement, GobanElement, PlayerCardElement, PlayingFieldView, StoneElement, + TextFieldElement, + }, Color, Size, }; use std::{cell::RefCell, rc::Rc}; @@ -10,7 +13,7 @@ use std::{cell::RefCell, rc::Rc}; /* #[derive(Clone, Debug)] pub struct PlayingFieldView { - pub board: types::GameBoardElement, + pub board: types::GobanElement, pub player_card_black: types::PlayerCardElement, pub player_card_white: types::PlayerCardElement, pub chat: types::ChatElement, @@ -19,13 +22,24 @@ pub struct PlayingFieldView { } */ -#[derive(Default)] pub struct PlayingFieldPrivate { + goban: Goban, player_card_white: Rc>>, player_card_black: Rc>>, chat: Rc>>, } +impl Default for PlayingFieldPrivate { + fn default() -> Self { + Self { + goban: Goban::new(), + player_card_white: Rc::new(RefCell::new(None)), + player_card_black: Rc::new(RefCell::new(None)), + chat: Rc::new(RefCell::new(None)), + } + } +} + #[glib::object_subclass] impl ObjectSubclass for PlayingFieldPrivate { const NAME: &'static str = "PlayingField"; @@ -34,6 +48,7 @@ impl ObjectSubclass for PlayingFieldPrivate { fn new() -> Self { Self { + goban: Goban::new(), player_card_white: Rc::new(RefCell::new(None)), player_card_black: Rc::new(RefCell::new(None)), chat: Rc::new(RefCell::new(None)), @@ -53,12 +68,13 @@ impl PlayingField { pub fn new(view: PlayingFieldView) -> PlayingField { let s: Self = Object::builder().build(); - let goban = Goban::new(); let player_card_white = PlayerCard::new(view.player_card_white); let player_card_black = PlayerCard::new(view.player_card_black); let chat = Chat::new(view.chat); - s.attach(&goban, 1, 1, 1, 2); + s.imp().goban.set_board(view.board); + + s.attach(&s.imp().goban, 1, 1, 1, 2); s.attach(&player_card_black, 2, 1, 1, 1); s.attach(&player_card_white, 3, 1, 1, 1); s.attach(&chat, 2, 2, 2, 1); @@ -73,17 +89,9 @@ impl PlayingField { #[cfg(feature = "screenplay")] pub fn playing_field_view() -> PlayingFieldView { - let mut spaces = Vec::new(); - (0..19).for_each(|_| spaces.push(Vec::new())); - - let board = GameBoardElement { - size: Size { - width: 19, - height: 19, - }, - spaces, - }; - + let mut board = GobanElement::default(); + *board.stone_mut(4, 4) = Some(StoneElement::new(Color::White)); + *board.stone_mut(15, 15) = Some(StoneElement::new(Color::Black)); let player_card_black = PlayerCardElement { color: Color::Black, name: "Savanni".to_owned(),