Place a stone onto the drawing area #38
|
@ -76,7 +76,7 @@
|
|||
inherit pkgs;
|
||||
buildRustCrateForPkgs = customBuildInfo;
|
||||
rootFeatures = [ "screenplay" ];
|
||||
release = false;
|
||||
release = true;
|
||||
}).rootCrate.build;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,11 +2,19 @@ use crate::types::AppState;
|
|||
use crate::ui::{playing_field, PlayingFieldView};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Request {
|
||||
PlayingField,
|
||||
PlayStoneRequest(PlayStoneRequest),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct PlayStoneRequest {
|
||||
pub column: u8,
|
||||
pub row: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Response {
|
||||
PlayingFieldView(PlayingFieldView),
|
||||
}
|
||||
|
@ -25,7 +33,18 @@ impl CoreApp {
|
|||
|
||||
pub async fn dispatch(&self, request: Request) -> Response {
|
||||
match request {
|
||||
Request::PlayingField => Response::PlayingFieldView(playing_field()),
|
||||
Request::PlayingField => {
|
||||
let app_state = self.state.read().unwrap();
|
||||
let game = app_state.game.as_ref().unwrap();
|
||||
Response::PlayingFieldView(playing_field(game))
|
||||
}
|
||||
Request::PlayStoneRequest(request) => {
|
||||
let mut app_state = self.state.write().unwrap();
|
||||
app_state.place_stone(request);
|
||||
|
||||
let game = app_state.game.as_ref().unwrap();
|
||||
Response::PlayingFieldView(playing_field(game))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::time::Duration;
|
||||
|
||||
mod api;
|
||||
pub use api::{CoreApp, Request, Response};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::api::PlayStoneRequest;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
White,
|
||||
|
@ -21,13 +22,24 @@ impl Default for Size {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AppState {
|
||||
game: Option<GameState>,
|
||||
pub struct AppState {
|
||||
pub game: Option<GameState>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
Self { game: None }
|
||||
Self {
|
||||
game: Some(GameState::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place_stone(&mut self, req: PlayStoneRequest) {
|
||||
match self.game {
|
||||
Some(ref mut game) => {
|
||||
game.place_stone(req.column, req.row);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,18 +67,82 @@ impl From<Rank> for String {
|
|||
}
|
||||
}
|
||||
|
||||
struct Player {
|
||||
pub struct Player {
|
||||
name: String,
|
||||
rank: Rank,
|
||||
}
|
||||
|
||||
pub(crate) struct GameState {
|
||||
goban: Vec<Vec<Color>>,
|
||||
conversation: Vec<String>,
|
||||
pub struct GameState {
|
||||
pub board: Board,
|
||||
pub conversation: Vec<String>,
|
||||
pub current_player: Color,
|
||||
|
||||
white_player: Player,
|
||||
black_player: Player,
|
||||
pub white_player: Player,
|
||||
pub black_player: Player,
|
||||
|
||||
white_clock: Duration,
|
||||
black_clock: Duration,
|
||||
pub white_clock: Duration,
|
||||
pub black_clock: Duration,
|
||||
}
|
||||
|
||||
impl GameState {
|
||||
fn new() -> GameState {
|
||||
GameState {
|
||||
board: Board::new(),
|
||||
conversation: vec![],
|
||||
current_player: Color::Black,
|
||||
white_player: Player {
|
||||
name: "Savanni".to_owned(),
|
||||
rank: Rank::Kyu(10),
|
||||
},
|
||||
black_player: Player {
|
||||
name: "Opal".to_owned(),
|
||||
rank: Rank::Kyu(10),
|
||||
},
|
||||
white_clock: Duration::from_secs(600),
|
||||
black_clock: Duration::from_secs(600),
|
||||
}
|
||||
}
|
||||
|
||||
fn place_stone(&mut self, column: u8, row: u8) {
|
||||
self.board.place_stone(column, row, self.current_player);
|
||||
match self.current_player {
|
||||
Color::White => self.current_player = Color::Black,
|
||||
Color::Black => self.current_player = Color::White,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Board {
|
||||
pub size: Size,
|
||||
pub spaces: Vec<Option<Color>>,
|
||||
}
|
||||
|
||||
impl Board {
|
||||
fn new() -> Self {
|
||||
let mut spaces = Vec::new();
|
||||
for _ in 0..19 * 19 {
|
||||
spaces.push(None);
|
||||
}
|
||||
Self {
|
||||
size: Size {
|
||||
width: 19,
|
||||
height: 19,
|
||||
},
|
||||
spaces,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place_stone(&mut self, column: u8, row: u8, stone: Color) {
|
||||
let addr = self.addr(column, row);
|
||||
self.spaces[addr] = Some(stone);
|
||||
}
|
||||
|
||||
pub fn stone(&self, column: u8, row: u8) -> Option<Color> {
|
||||
let addr = self.addr(column, row);
|
||||
self.spaces[addr]
|
||||
}
|
||||
|
||||
fn addr(&self, column: u8, row: u8) -> usize {
|
||||
((row as usize) * (self.size.width as usize) + (column as usize)) as usize
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,7 @@ mod playing_field;
|
|||
pub use playing_field::{playing_field, PlayingFieldView};
|
||||
|
||||
mod types;
|
||||
pub use types::{ChatElement, GobanElement, PlayerCardElement, StoneElement, TextFieldElement};
|
||||
pub use types::{
|
||||
BoardElement, ChatElement, IntersectionElement, PlayerCardElement, StoneElement,
|
||||
TextFieldElement,
|
||||
};
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::types::{Color, Size};
|
||||
use crate::ui::types;
|
||||
use crate::types::Color;
|
||||
use crate::{types::GameState, ui::types};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlayingFieldView {
|
||||
pub board: types::GobanElement,
|
||||
pub board: types::BoardElement,
|
||||
pub player_card_black: types::PlayerCardElement,
|
||||
pub player_card_white: types::PlayerCardElement,
|
||||
pub chat: types::ChatElement,
|
||||
|
@ -11,8 +11,8 @@ pub struct PlayingFieldView {
|
|||
pub current_player: Color,
|
||||
}
|
||||
|
||||
pub fn playing_field() -> PlayingFieldView {
|
||||
let board = types::GobanElement::default();
|
||||
pub fn playing_field(game: &GameState) -> PlayingFieldView {
|
||||
let board = types::BoardElement::from(&game.board);
|
||||
|
||||
let player_card_black = types::PlayerCardElement {
|
||||
color: Color::Black,
|
||||
|
@ -42,6 +42,6 @@ pub fn playing_field() -> PlayingFieldView {
|
|||
player_card_white,
|
||||
chat,
|
||||
message,
|
||||
current_player: Color::White,
|
||||
current_player: game.current_player,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use crate::types::{Color, Size};
|
||||
use crate::{
|
||||
api::{PlayStoneRequest, Request},
|
||||
types::Board,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Jitter {
|
||||
pub x: i8,
|
||||
pub y: i8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct StoneElement {
|
||||
pub color: Color,
|
||||
pub jitter: Jitter,
|
||||
|
@ -21,37 +25,73 @@ impl StoneElement {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum IntersectionElement {
|
||||
Unplayable,
|
||||
Empty(Request),
|
||||
Filled(StoneElement),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GobanElement {
|
||||
pub struct BoardElement {
|
||||
pub size: Size,
|
||||
pub spaces: Vec<Option<StoneElement>>,
|
||||
pub spaces: Vec<IntersectionElement>,
|
||||
}
|
||||
|
||||
impl GobanElement {
|
||||
impl BoardElement {
|
||||
pub fn new(size: Size) -> Self {
|
||||
let spaces: Vec<IntersectionElement> = (0..size.height)
|
||||
.map(|row| {
|
||||
(0..size.width)
|
||||
.map(|column| {
|
||||
IntersectionElement::Empty(Request::PlayStoneRequest(PlayStoneRequest {
|
||||
column,
|
||||
row,
|
||||
}))
|
||||
})
|
||||
.collect::<Vec<IntersectionElement>>()
|
||||
})
|
||||
.collect::<Vec<Vec<IntersectionElement>>>()
|
||||
.concat();
|
||||
Self { size, spaces }
|
||||
}
|
||||
|
||||
pub fn stone(&self, column: u8, row: u8) -> IntersectionElement {
|
||||
let addr = self.addr(column, row);
|
||||
self.spaces[addr]
|
||||
}
|
||||
|
||||
fn addr(&self, column: u8, row: u8) -> usize {
|
||||
((row as usize) * (self.size.width as usize) + (column as usize)) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Board> for BoardElement {
|
||||
fn from(board: &Board) -> Self {
|
||||
let spaces: Vec<IntersectionElement> = (0..board.size.height)
|
||||
.map(|row| {
|
||||
(0..board.size.width)
|
||||
.map(|column| match board.stone(column, row) {
|
||||
Some(color) => IntersectionElement::Filled(StoneElement {
|
||||
jitter: Jitter { x: 0, y: 0 },
|
||||
color,
|
||||
}),
|
||||
None => IntersectionElement::Empty(Request::PlayStoneRequest(
|
||||
PlayStoneRequest { column, row },
|
||||
)),
|
||||
})
|
||||
.collect::<Vec<IntersectionElement>>()
|
||||
})
|
||||
.collect::<Vec<Vec<IntersectionElement>>>()
|
||||
.concat();
|
||||
Self {
|
||||
size: size.clone(),
|
||||
spaces: (0..((size.width as usize) * (size.height as usize)))
|
||||
.map(|_| None)
|
||||
.collect::<Vec<Option<StoneElement>>>(),
|
||||
size: board.size,
|
||||
spaces,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stone_mut<'a>(&'a mut self, row: u8, col: u8) -> &'a mut Option<StoneElement> {
|
||||
let addr = self.addr(row, col);
|
||||
&mut self.spaces[addr]
|
||||
}
|
||||
|
||||
pub fn stone(&self, row: u8, col: u8) -> Option<StoneElement> {
|
||||
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 {
|
||||
impl Default for BoardElement {
|
||||
fn default() -> Self {
|
||||
Self::new(Size::default())
|
||||
}
|
||||
|
|
|
@ -1 +1,21 @@
|
|||
pub mod ui;
|
||||
use kifu_core::{CoreApp, Request, Response};
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CoreApi {
|
||||
pub gtk_tx: gtk::glib::Sender<Response>,
|
||||
pub rt: Arc<Runtime>,
|
||||
pub core: CoreApp,
|
||||
}
|
||||
|
||||
impl CoreApi {
|
||||
pub fn dispatch(&self, request: Request) {
|
||||
self.rt.spawn({
|
||||
let gtk_tx = self.gtk_tx.clone();
|
||||
let core = self.core.clone();
|
||||
async move { gtk_tx.send(core.dispatch(request).await) }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,7 @@
|
|||
use gio::resources_lookup_data;
|
||||
use gtk::prelude::*;
|
||||
use kifu_core::{CoreApp, Request, Response};
|
||||
use kifu_gtk::ui::PlayingField;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CoreApi {
|
||||
gtk_tx: gtk::glib::Sender<Response>,
|
||||
rt: Arc<Runtime>,
|
||||
core: CoreApp,
|
||||
}
|
||||
|
||||
impl CoreApi {
|
||||
pub fn dispatch(&self, request: Request) {
|
||||
self.rt.spawn({
|
||||
let gtk_tx = self.gtk_tx.clone();
|
||||
let core = self.core.clone();
|
||||
async move { gtk_tx.send(core.dispatch(request).await) }
|
||||
});
|
||||
}
|
||||
}
|
||||
use kifu_gtk::{ui::PlayingField, CoreApi};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
fn main() {
|
||||
gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource")
|
||||
|
@ -67,15 +42,29 @@ fn main() {
|
|||
let window = gtk::ApplicationWindow::new(app);
|
||||
window.present();
|
||||
|
||||
gtk_rx.attach(None, move |message| {
|
||||
println!("message: {:?}", message);
|
||||
gtk_rx.attach(None, {
|
||||
let api = api.clone();
|
||||
let playing_field = Arc::new(RwLock::new(None));
|
||||
move |message| {
|
||||
match message {
|
||||
Response::PlayingFieldView(view) => {
|
||||
let playing_field = PlayingField::new(view);
|
||||
window.set_child(Some(&playing_field));
|
||||
let api = api.clone();
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let mut playing_field = playing_field.write().unwrap();
|
||||
if playing_field.is_none() {
|
||||
let field = PlayingField::new(api, view);
|
||||
window.set_child(Some(&field));
|
||||
*playing_field = Some(field);
|
||||
} else {
|
||||
playing_field.as_ref().map(|field| field.update_view(view));
|
||||
}
|
||||
let end = std::time::Instant::now();
|
||||
println!("Time to render the playing field: {:?}", end - start);
|
||||
}
|
||||
}
|
||||
Continue(true)
|
||||
}
|
||||
});
|
||||
|
||||
api.dispatch(Request::PlayingField);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::CoreApi;
|
||||
use gio::resources_lookup_data;
|
||||
use glib::Object;
|
||||
use gtk::{
|
||||
|
@ -6,11 +7,15 @@ use gtk::{
|
|||
subclass::prelude::*,
|
||||
};
|
||||
use image::io::Reader as ImageReader;
|
||||
use kifu_core::{ui::GobanElement, Color};
|
||||
use kifu_core::{
|
||||
ui::{BoardElement, IntersectionElement},
|
||||
Color,
|
||||
};
|
||||
use std::{cell::RefCell, io::Cursor, rc::Rc};
|
||||
|
||||
const WIDTH: i32 = 800;
|
||||
const HEIGHT: i32 = 800;
|
||||
const MARGIN: i32 = 20;
|
||||
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
struct Addr {
|
||||
|
@ -18,36 +23,39 @@ struct Addr {
|
|||
column: u8,
|
||||
}
|
||||
|
||||
pub struct GobanPrivate {
|
||||
pub struct BoardPrivate {
|
||||
drawing_area: gtk::DrawingArea,
|
||||
|
||||
current_player: Rc<RefCell<Color>>,
|
||||
goban: Rc<RefCell<GobanElement>>,
|
||||
cursor_location: Rc<RefCell<Addr>>,
|
||||
board: Rc<RefCell<BoardElement>>,
|
||||
cursor_location: Rc<RefCell<Option<Addr>>>,
|
||||
|
||||
api: Rc<RefCell<Option<CoreApi>>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for GobanPrivate {
|
||||
const NAME: &'static str = "Goban";
|
||||
type Type = Goban;
|
||||
impl ObjectSubclass for BoardPrivate {
|
||||
const NAME: &'static str = "Board";
|
||||
type Type = Board;
|
||||
type ParentType = gtk::Grid;
|
||||
|
||||
fn new() -> GobanPrivate {
|
||||
GobanPrivate {
|
||||
fn new() -> BoardPrivate {
|
||||
BoardPrivate {
|
||||
drawing_area: Default::default(),
|
||||
current_player: Rc::new(RefCell::new(Color::Black)),
|
||||
goban: Default::default(),
|
||||
board: Default::default(),
|
||||
cursor_location: Default::default(),
|
||||
api: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for GobanPrivate {
|
||||
impl ObjectImpl for BoardPrivate {
|
||||
fn constructed(&self) {
|
||||
self.drawing_area.set_width_request(WIDTH);
|
||||
self.drawing_area.set_height_request(HEIGHT);
|
||||
|
||||
let goban = self.goban.clone();
|
||||
let board = self.board.clone();
|
||||
let cursor_location = self.cursor_location.clone();
|
||||
let current_player = self.current_player.clone();
|
||||
|
||||
|
@ -75,7 +83,9 @@ impl ObjectImpl for GobanPrivate {
|
|||
|
||||
self.drawing_area
|
||||
.set_draw_func(move |_, context, width, height| {
|
||||
let goban = goban.borrow();
|
||||
let render_start = std::time::Instant::now();
|
||||
let board = board.borrow();
|
||||
|
||||
match background {
|
||||
Ok(Some(ref background)) => {
|
||||
context.set_source_pixbuf(&background, 0., 0.);
|
||||
|
@ -87,24 +97,36 @@ impl ObjectImpl for GobanPrivate {
|
|||
|
||||
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 hspace_between = ((width - 40) as f64) / ((board.size.width - 1) as f64);
|
||||
let vspace_between = ((height - 40) as f64) / ((board.size.height - 1) as f64);
|
||||
|
||||
let pen = Pen {
|
||||
x_offset: 20.,
|
||||
y_offset: 20.,
|
||||
x_offset: MARGIN as f64,
|
||||
y_offset: MARGIN as f64,
|
||||
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);
|
||||
(0..board.size.width).for_each(|col| {
|
||||
context.move_to(
|
||||
(MARGIN as f64) + (col as f64) * hspace_between,
|
||||
MARGIN as f64,
|
||||
);
|
||||
context.line_to(
|
||||
(MARGIN as f64) + (col as f64) * hspace_between,
|
||||
(height as f64) - (MARGIN as f64),
|
||||
);
|
||||
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);
|
||||
(0..board.size.height).for_each(|row| {
|
||||
context.move_to(
|
||||
MARGIN as f64,
|
||||
(MARGIN as f64) + (row as f64) * vspace_between,
|
||||
);
|
||||
context.line_to(
|
||||
(width - MARGIN) as f64,
|
||||
(MARGIN as f64) + (row as f64) * vspace_between,
|
||||
);
|
||||
let _ = context.stroke();
|
||||
});
|
||||
|
||||
|
@ -117,61 +139,110 @@ impl ObjectImpl for GobanPrivate {
|
|||
|
||||
(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);
|
||||
match board.stone(row, col) {
|
||||
IntersectionElement::Filled(stone) => {
|
||||
pen.stone(&context, row, col, stone.color);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
let cursor = cursor_location.borrow();
|
||||
pen.ghost_stone(context, cursor.row, cursor.column, *current_player.borrow());
|
||||
match *cursor {
|
||||
None => {}
|
||||
Some(ref cursor) => match board.stone(cursor.row, cursor.column) {
|
||||
IntersectionElement::Empty(_) => pen.ghost_stone(
|
||||
context,
|
||||
cursor.row,
|
||||
cursor.column,
|
||||
*current_player.borrow(),
|
||||
),
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
let render_end = std::time::Instant::now();
|
||||
println!("board rendering time: {:?}", render_end - render_start);
|
||||
});
|
||||
|
||||
let motion_controller = gtk::EventControllerMotion::new();
|
||||
{
|
||||
let goban = self.goban.clone();
|
||||
let board = self.board.clone();
|
||||
let cursor = self.cursor_location.clone();
|
||||
let drawing_area = self.drawing_area.clone();
|
||||
motion_controller.connect_motion(move |_, x, y| {
|
||||
let goban = goban.borrow();
|
||||
let board = board.borrow();
|
||||
let mut cursor = cursor.borrow_mut();
|
||||
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 hspace_between = ((WIDTH - 40) as f64) / ((board.size.width - 1) as f64);
|
||||
let vspace_between = ((HEIGHT - 40) as f64) / ((board.size.height - 1) as f64);
|
||||
|
||||
let addr = Addr {
|
||||
column: ((x.round() - 20.) / hspace_between).round() as u8,
|
||||
row: ((y.round() - 20.) / vspace_between).round() as u8,
|
||||
let addr = if x.round() < MARGIN as f64
|
||||
|| x.round() > (WIDTH - MARGIN) as f64
|
||||
|| y.round() < MARGIN as f64
|
||||
|| y.round() > (HEIGHT - MARGIN) as f64
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(Addr {
|
||||
column: ((x.round() - MARGIN as f64) / hspace_between).round() as u8,
|
||||
row: ((y.round() - MARGIN as f64) / vspace_between).round() as u8,
|
||||
})
|
||||
};
|
||||
|
||||
if *cursor != addr {
|
||||
if *cursor != addr.clone() {
|
||||
*cursor = addr;
|
||||
drawing_area.queue_draw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let gesture = gtk::GestureClick::new();
|
||||
{
|
||||
let board = self.board.clone();
|
||||
let cursor = self.cursor_location.clone();
|
||||
let api = self.api.clone();
|
||||
gesture.connect_released(move |_, _, _, _| {
|
||||
let board = board.borrow();
|
||||
let cursor = cursor.borrow();
|
||||
match *cursor {
|
||||
None => {}
|
||||
Some(ref cursor) => match board.stone(cursor.row, cursor.column) {
|
||||
IntersectionElement::Empty(request) => {
|
||||
println!("need to send request: {:?}", request);
|
||||
api.borrow()
|
||||
.as_ref()
|
||||
.expect("API must exist")
|
||||
.dispatch(request);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.drawing_area.add_controller(motion_controller);
|
||||
self.drawing_area.add_controller(gesture);
|
||||
}
|
||||
}
|
||||
impl WidgetImpl for GobanPrivate {}
|
||||
impl GridImpl for GobanPrivate {}
|
||||
impl WidgetImpl for BoardPrivate {}
|
||||
impl GridImpl for BoardPrivate {}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Goban(ObjectSubclass<GobanPrivate>) @extends gtk::Grid, gtk::Widget;
|
||||
pub struct Board(ObjectSubclass<BoardPrivate>) @extends gtk::Grid, gtk::Widget;
|
||||
}
|
||||
|
||||
impl Goban {
|
||||
pub fn new() -> Self {
|
||||
impl Board {
|
||||
pub fn new(api: CoreApi) -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
|
||||
*s.imp().api.borrow_mut() = Some(api);
|
||||
s.attach(&s.imp().drawing_area, 1, 1, 1, 1);
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn set_board(&self, goban: GobanElement) {
|
||||
*self.imp().goban.borrow_mut() = goban;
|
||||
pub fn set_board(&self, board: BoardElement) {
|
||||
*self.imp().board.borrow_mut() = board;
|
||||
self.imp().drawing_area.queue_draw();
|
||||
}
|
||||
|
||||
|
@ -192,7 +263,7 @@ impl Pen {
|
|||
context.arc(
|
||||
self.x_offset + (col as f64) * self.hspace_between,
|
||||
self.y_offset + (row as f64) * self.vspace_between,
|
||||
10.,
|
||||
5.,
|
||||
0.,
|
||||
2. * std::f64::consts::PI,
|
||||
);
|
||||
|
@ -204,14 +275,7 @@ impl Pen {
|
|||
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();
|
||||
self.draw_stone(context, row, col);
|
||||
}
|
||||
|
||||
fn ghost_stone(&self, context: &cairo::Context, row: u8, col: u8, color: Color) {
|
||||
|
@ -219,10 +283,15 @@ impl Pen {
|
|||
Color::White => context.set_source_rgba(0.9, 0.9, 0.9, 0.5),
|
||||
Color::Black => context.set_source_rgba(0.0, 0.0, 0.0, 0.5),
|
||||
};
|
||||
self.draw_stone(context, row, col);
|
||||
}
|
||||
|
||||
fn draw_stone(&self, context: &cairo::Context, row: u8, col: u8) {
|
||||
let radius = self.hspace_between / 2. - 2.;
|
||||
context.arc(
|
||||
20.0 + (col as f64) * self.hspace_between,
|
||||
20.0 + (row as f64) * self.vspace_between,
|
||||
25.0,
|
||||
self.x_offset + (col as f64) * self.hspace_between,
|
||||
self.y_offset + (row as f64) * self.vspace_between,
|
||||
radius,
|
||||
0.0,
|
||||
2.0 * std::f64::consts::PI,
|
||||
);
|
|
@ -7,8 +7,8 @@ pub use chat::Chat;
|
|||
mod playing_field;
|
||||
pub use playing_field::PlayingField;
|
||||
|
||||
mod goban;
|
||||
pub use goban::Goban;
|
||||
mod board;
|
||||
pub use board::Board;
|
||||
|
||||
#[cfg(feature = "screenplay")]
|
||||
pub use playing_field::playing_field_view;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use kifu_core::{ui::PlayerCardElement, Color};
|
||||
use kifu_core::ui::PlayerCardElement;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PlayerCardPrivate {
|
||||
|
|
|
@ -1,29 +1,12 @@
|
|||
use crate::ui::{Chat, Goban, PlayerCard};
|
||||
use crate::ui::{Board, Chat, PlayerCard};
|
||||
use crate::CoreApi;
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use kifu_core::{
|
||||
ui::{
|
||||
ChatElement, GobanElement, PlayerCardElement, PlayingFieldView, StoneElement,
|
||||
TextFieldElement,
|
||||
},
|
||||
Color, Size,
|
||||
};
|
||||
use kifu_core::ui::PlayingFieldView;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/*
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlayingFieldView {
|
||||
pub board: types::GobanElement,
|
||||
pub player_card_black: types::PlayerCardElement,
|
||||
pub player_card_white: types::PlayerCardElement,
|
||||
pub chat: types::ChatElement,
|
||||
pub message: types::TextFieldElement,
|
||||
pub current_player: Color,
|
||||
}
|
||||
*/
|
||||
|
||||
pub struct PlayingFieldPrivate {
|
||||
goban: Goban,
|
||||
board: Rc<RefCell<Option<Board>>>,
|
||||
player_card_white: Rc<RefCell<Option<PlayerCard>>>,
|
||||
player_card_black: Rc<RefCell<Option<PlayerCard>>>,
|
||||
chat: Rc<RefCell<Option<Chat>>>,
|
||||
|
@ -32,7 +15,7 @@ pub struct PlayingFieldPrivate {
|
|||
impl Default for PlayingFieldPrivate {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
goban: Goban::new(),
|
||||
board: Default::default(),
|
||||
player_card_white: Rc::new(RefCell::new(None)),
|
||||
player_card_black: Rc::new(RefCell::new(None)),
|
||||
chat: Rc::new(RefCell::new(None)),
|
||||
|
@ -45,15 +28,6 @@ impl ObjectSubclass for PlayingFieldPrivate {
|
|||
const NAME: &'static str = "PlayingField";
|
||||
type Type = PlayingField;
|
||||
type ParentType = gtk::Grid;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for PlayingFieldPrivate {}
|
||||
|
@ -65,16 +39,19 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl PlayingField {
|
||||
pub fn new(view: PlayingFieldView) -> PlayingField {
|
||||
pub fn new(api: CoreApi, view: PlayingFieldView) -> PlayingField {
|
||||
let s: Self = Object::builder().build();
|
||||
|
||||
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.imp().goban.set_board(view.board);
|
||||
|
||||
s.attach(&s.imp().goban, 1, 1, 1, 2);
|
||||
*s.imp().board.borrow_mut() = Some(Board::new(api));
|
||||
s.imp()
|
||||
.board
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|board| s.attach(board, 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);
|
||||
|
@ -82,17 +59,32 @@ impl PlayingField {
|
|||
*s.imp().player_card_white.borrow_mut() = Some(player_card_white);
|
||||
*s.imp().player_card_black.borrow_mut() = Some(player_card_black);
|
||||
*s.imp().chat.borrow_mut() = Some(chat);
|
||||
s.imp().goban.set_current_player(view.current_player);
|
||||
|
||||
s.imp().board.borrow().as_ref().map(|board| {
|
||||
board.set_board(view.board);
|
||||
board.set_current_player(view.current_player);
|
||||
});
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn update_view(&self, view: PlayingFieldView) {
|
||||
self.imp().board.borrow().as_ref().map(|board| {
|
||||
board.set_board(view.board);
|
||||
board.set_current_player(view.current_player);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "screenplay")]
|
||||
pub fn playing_field_view() -> PlayingFieldView {
|
||||
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 mut board = BoardElement::new(Size {
|
||||
width: 19,
|
||||
height: 19,
|
||||
});
|
||||
*board.stone_mut(4, 4) = IntersectionElement::Filled(StoneElement::new(Color::White));
|
||||
*board.stone_mut(15, 15) = IntersectionElement::Filled(StoneElement::new(Color::Black));
|
||||
*board.stone_mut(18, 18) = IntersectionElement::Unplayable;
|
||||
let player_card_black = PlayerCardElement {
|
||||
color: Color::Black,
|
||||
name: "Savanni".to_owned(),
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[toolchain]
|
||||
channel = "1.68.2"
|
||||
components = ["rust-src"]
|
Loading…
Reference in New Issue