Set up the game review page along with #229
|
@ -21,9 +21,10 @@ use otg_core::{
|
|||
settings::{SettingsRequest, SettingsResponse},
|
||||
Config, CoreRequest, CoreResponse,
|
||||
};
|
||||
use sgf::Game;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::views::{HomeView, SettingsView};
|
||||
use crate::views::{GameReview, HomeView, SettingsView};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum AppView {
|
||||
|
@ -37,7 +38,6 @@ enum AppView {
|
|||
#[derive(Clone)]
|
||||
pub struct AppWindow {
|
||||
pub window: adw::ApplicationWindow,
|
||||
header: adw::HeaderBar,
|
||||
|
||||
// content is a stack which contains the view models for the application. These are the main
|
||||
// elements that users want to interact with: the home page, the game library, a review, a game
|
||||
|
@ -46,11 +46,11 @@ pub struct AppWindow {
|
|||
// we can maintain the state of previous views. Since the two of these work together, they are
|
||||
// a candidate for extraction into a new widget or a new struct.
|
||||
stack: adw::NavigationView,
|
||||
content: Vec<AppView>,
|
||||
view_states: Vec<AppView>,
|
||||
|
||||
// Overlays are for transient content, such as about and settings, which can be accessed from
|
||||
// anywhere but shouldn't be part of the main application flow.
|
||||
panel_overlay: gtk::Overlay,
|
||||
overlay: gtk::Overlay,
|
||||
core: CoreApi,
|
||||
|
||||
// Not liking this, but I have to keep track of the settings view model separately from
|
||||
|
@ -61,28 +61,45 @@ pub struct AppWindow {
|
|||
impl AppWindow {
|
||||
pub fn new(app: &adw::Application, core: CoreApi) -> Self {
|
||||
let window = Self::setup_window(app);
|
||||
let header = Self::setup_header();
|
||||
let panel_overlay = Self::setup_panel_overlay();
|
||||
let (stack, content) = Self::setup_content(core.clone());
|
||||
let overlay = Self::setup_overlay();
|
||||
let stack = adw::NavigationView::new();
|
||||
let view_states = vec![];
|
||||
|
||||
window.set_content(Some(&overlay));
|
||||
overlay.set_child(Some(&stack));
|
||||
|
||||
let s = Self {
|
||||
window,
|
||||
stack,
|
||||
view_states,
|
||||
overlay,
|
||||
core,
|
||||
settings_view_model: Default::default(),
|
||||
};
|
||||
|
||||
let home = s.setup_home();
|
||||
|
||||
let _ = s.stack.push(&home);
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn open_game_review(&self, _game: Game) {
|
||||
let header = adw::HeaderBar::new();
|
||||
let game_review = GameReview::new(self.core.clone());
|
||||
|
||||
let layout = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.build();
|
||||
layout.append(&header);
|
||||
layout.append(&panel_overlay);
|
||||
panel_overlay.set_child(Some(&stack));
|
||||
layout.append(&game_review);
|
||||
|
||||
window.set_content(Some(&layout));
|
||||
|
||||
Self {
|
||||
window,
|
||||
header,
|
||||
stack,
|
||||
content,
|
||||
panel_overlay,
|
||||
core,
|
||||
settings_view_model: Default::default(),
|
||||
}
|
||||
let page = adw::NavigationPage::builder()
|
||||
.can_pop(true)
|
||||
.title("Game Review")
|
||||
.child(&layout)
|
||||
.build();
|
||||
self.stack.push(&page);
|
||||
}
|
||||
|
||||
pub fn open_settings(&self) {
|
||||
|
@ -123,7 +140,7 @@ impl AppWindow {
|
|||
}
|
||||
},
|
||||
);
|
||||
s.panel_overlay.add_overlay(&view_model);
|
||||
s.overlay.add_overlay(&view_model);
|
||||
*s.settings_view_model.write().unwrap() = Some(view_model);
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +151,7 @@ impl AppWindow {
|
|||
let mut view = self.settings_view_model.write().unwrap();
|
||||
match *view {
|
||||
Some(ref mut settings) => {
|
||||
self.panel_overlay.remove_overlay(settings);
|
||||
self.overlay.remove_overlay(settings);
|
||||
*view = None;
|
||||
}
|
||||
None => {}
|
||||
|
@ -153,7 +170,7 @@ impl AppWindow {
|
|||
|
||||
fn setup_header() -> adw::HeaderBar {
|
||||
let header = adw::HeaderBar::builder()
|
||||
.title_widget(>k::Label::new(Some("Kifu")))
|
||||
.title_widget(>k::Label::new(Some("On the Grid")))
|
||||
.build();
|
||||
|
||||
let app_menu = gio::Menu::new();
|
||||
|
@ -169,28 +186,27 @@ impl AppWindow {
|
|||
header
|
||||
}
|
||||
|
||||
fn setup_panel_overlay() -> gtk::Overlay {
|
||||
fn setup_overlay() -> gtk::Overlay {
|
||||
gtk::Overlay::new()
|
||||
}
|
||||
|
||||
fn setup_content(core: CoreApi) -> (adw::NavigationView, Vec<AppView>) {
|
||||
let stack = adw::NavigationView::new();
|
||||
let content = Vec::new();
|
||||
fn setup_home(&self) -> adw::NavigationPage {
|
||||
let header = Self::setup_header();
|
||||
let home = HomeView::new(self.core.clone(), {
|
||||
let s = self.clone();
|
||||
move |game| s.open_game_review(game)
|
||||
});
|
||||
|
||||
let home = HomeView::new(core.clone());
|
||||
let _ = stack.push(
|
||||
&adw::NavigationPage::builder()
|
||||
.can_pop(false)
|
||||
.title("Kifu")
|
||||
.child(&home)
|
||||
.build(),
|
||||
);
|
||||
// content.push(AppView::Home(HomeViewModel::new(core)));
|
||||
let layout = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.build();
|
||||
layout.append(&header);
|
||||
layout.append(&home);
|
||||
|
||||
(stack, content)
|
||||
adw::NavigationPage::builder()
|
||||
.can_pop(false)
|
||||
.title("Home")
|
||||
.child(&layout)
|
||||
.build()
|
||||
}
|
||||
|
||||
// pub fn set_content(content: &impl IsA<gtk::Widget>) -> adw::ViewStack {
|
||||
// self.content.set_child(Some(content));
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use gtk::{
|
|||
};
|
||||
use image::io::Reader as ImageReader;
|
||||
use otg_core::{
|
||||
ui::{BoardElement, IntersectionElement},
|
||||
Color,
|
||||
};
|
||||
use std::{cell::RefCell, io::Cursor, rc::Rc};
|
||||
|
@ -27,7 +26,7 @@ pub struct BoardPrivate {
|
|||
drawing_area: gtk::DrawingArea,
|
||||
|
||||
current_player: Rc<RefCell<Color>>,
|
||||
board: Rc<RefCell<BoardElement>>,
|
||||
// board: Rc<RefCell<BoardElement>>,
|
||||
cursor_location: Rc<RefCell<Option<Addr>>>,
|
||||
|
||||
api: Rc<RefCell<Option<CoreApi>>>,
|
||||
|
@ -43,7 +42,7 @@ impl ObjectSubclass for BoardPrivate {
|
|||
BoardPrivate {
|
||||
drawing_area: Default::default(),
|
||||
current_player: Rc::new(RefCell::new(Color::Black)),
|
||||
board: Default::default(),
|
||||
// board: Default::default(),
|
||||
cursor_location: Default::default(),
|
||||
api: Default::default(),
|
||||
}
|
||||
|
@ -55,7 +54,7 @@ impl ObjectImpl for BoardPrivate {
|
|||
self.drawing_area.set_width_request(WIDTH);
|
||||
self.drawing_area.set_height_request(HEIGHT);
|
||||
|
||||
let board = self.board.clone();
|
||||
// let board = self.board.clone();
|
||||
let cursor_location = self.cursor_location.clone();
|
||||
let current_player = self.current_player.clone();
|
||||
|
||||
|
@ -85,6 +84,7 @@ impl ObjectImpl for BoardPrivate {
|
|||
.set_draw_func(move |_, context, width, height| {
|
||||
perftrace("render drawing area", || {
|
||||
let render_start = std::time::Instant::now();
|
||||
/*
|
||||
let board = board.borrow();
|
||||
|
||||
match background {
|
||||
|
@ -137,7 +137,9 @@ impl ObjectImpl for BoardPrivate {
|
|||
pen.star_point(context, col, row);
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
(0..19).for_each(|col| {
|
||||
(0..19).for_each(|row| {
|
||||
if let IntersectionElement::Filled(stone) = board.stone(row, col) {
|
||||
|
@ -162,6 +164,7 @@ impl ObjectImpl for BoardPrivate {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
let render_end = std::time::Instant::now();
|
||||
println!("board rendering time: {:?}", render_end - render_start);
|
||||
})
|
||||
|
@ -169,10 +172,11 @@ impl ObjectImpl for BoardPrivate {
|
|||
|
||||
let motion_controller = gtk::EventControllerMotion::new();
|
||||
{
|
||||
let board = self.board.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 board = board.borrow();
|
||||
let mut cursor = cursor.borrow_mut();
|
||||
let hspace_between = ((WIDTH - 40) as f64) / ((board.size.width - 1) as f64);
|
||||
|
@ -195,17 +199,21 @@ impl ObjectImpl for BoardPrivate {
|
|||
*cursor = addr;
|
||||
drawing_area.queue_draw();
|
||||
}
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
let gesture = gtk::GestureClick::new();
|
||||
{
|
||||
let board = self.board.clone();
|
||||
// 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) => {
|
||||
|
@ -220,6 +228,7 @@ impl ObjectImpl for BoardPrivate {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -244,10 +253,12 @@ impl Board {
|
|||
s
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn set_board(&self, board: BoardElement) {
|
||||
*self.imp().board.borrow_mut() = board;
|
||||
self.imp().drawing_area.queue_draw();
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn set_current_player(&self, color: Color) {
|
||||
*self.imp().current_player.borrow_mut() = color;
|
||||
|
|
|
@ -45,41 +45,10 @@ impl Default for LibraryPrivate {
|
|||
let model = gio::ListStore::new::<GameObject>();
|
||||
model.extend_from_slice(&vector);
|
||||
|
||||
/*
|
||||
let factory = gtk::SignalListItemFactory::new();
|
||||
|
||||
factory.connect_setup(move |_, list_item| {
|
||||
let preview = GamePreview::new();
|
||||
list_item
|
||||
.downcast_ref::<gtk::ListItem>()
|
||||
.expect("Needs to be a ListItem")
|
||||
.set_child(Some(&preview));
|
||||
});
|
||||
factory.connect_bind(move |_, list_item| {
|
||||
let game_element = list_item
|
||||
.downcast_ref::<gtk::ListItem>()
|
||||
.expect("Needs to be ListItem")
|
||||
.item()
|
||||
.and_downcast::<GameObject>()
|
||||
.expect("The item has to be a GameObject.");
|
||||
|
||||
let preview = list_item
|
||||
.downcast_ref::<gtk::ListItem>()
|
||||
.expect("Needs to be ListItem")
|
||||
.child()
|
||||
.and_downcast::<GamePreview>()
|
||||
.expect("The child has to be a GamePreview object.");
|
||||
|
||||
match game_element.game() {
|
||||
Some(game) => preview.set_game(game),
|
||||
None => (),
|
||||
};
|
||||
});
|
||||
*/
|
||||
|
||||
let selection_model = gtk::NoSelection::new(Some(model.clone()));
|
||||
let list_view = gtk::ColumnView::builder()
|
||||
.model(&selection_model)
|
||||
.single_click_activate(true)
|
||||
.hexpand(true)
|
||||
.build();
|
||||
|
||||
|
@ -116,17 +85,7 @@ impl Default for LibraryPrivate {
|
|||
.factory(&make_factory(|g| {
|
||||
g.dates
|
||||
.iter()
|
||||
.map(|date| {
|
||||
format!("{}", date)
|
||||
/*
|
||||
let l = locale!("en-US").into();
|
||||
let options = length::Bag::from_date_style(length::Date::Medium);
|
||||
let date = Date::try_new_iso_date(date.
|
||||
let dtfmt =
|
||||
DateFormatter::try_new_with_length(&l, options).unwrap();
|
||||
dtfmt.format(date).unwrap()
|
||||
*/
|
||||
})
|
||||
.map(|date| format!("{}", date))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}))
|
||||
|
@ -196,6 +155,28 @@ impl Default for Library {
|
|||
}
|
||||
|
||||
impl Library {
|
||||
pub fn new(on_select: impl Fn(Game) + 'static) -> Library {
|
||||
let s = Library::default();
|
||||
|
||||
s.imp().list_view.connect_activate({
|
||||
let s = s.clone();
|
||||
move |_, row_id| {
|
||||
println!("row activated: {}", row_id);
|
||||
|
||||
let object = s.imp().model.item(row_id);
|
||||
let game = object.and_downcast_ref::<GameObject>().unwrap();
|
||||
println!(
|
||||
"{:?} vs. {:?}",
|
||||
game.game().unwrap().white_player,
|
||||
game.game().unwrap().black_player
|
||||
);
|
||||
on_select(game.game().unwrap());
|
||||
}
|
||||
});
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn set_games(&self, games: Vec<Game>) {
|
||||
let games = games
|
||||
.into_iter()
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
mod board;
|
||||
pub use board::Board;
|
||||
|
||||
// mod chat;
|
||||
// pub use chat::Chat;
|
||||
|
||||
|
@ -19,9 +22,6 @@ pub use library::Library;
|
|||
// mod home;
|
||||
// pub use home::Home;
|
||||
|
||||
// mod board;
|
||||
// pub use board::Board;
|
||||
|
||||
#[cfg(feature = "screenplay")]
|
||||
pub use playing_field::playing_field_view;
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
||||
|
||||
This file is part of On the Grid.
|
||||
|
||||
On the Grid 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.
|
||||
|
||||
On the Grid 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 On the Grid. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Game review consists of the board, some information about the players, the game tree, and any
|
||||
// commentary on the current move. This requires four major components, some of which are easier
|
||||
// than others. The game board has to be kept in sync with the game tree, so there's a
|
||||
// communication channel there.
|
||||
//
|
||||
// I'll get all of the information about the game from the core, and then render everything in the
|
||||
// UI. So this will be a heavy lift on the UI side.
|
||||
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use crate::{components::Board, CoreApi};
|
||||
|
||||
pub struct GameReviewPrivate {}
|
||||
|
||||
impl Default for GameReviewPrivate {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for GameReviewPrivate {
|
||||
const NAME: &'static str = "GameReview";
|
||||
type Type = GameReview;
|
||||
type ParentType = gtk::Grid;
|
||||
}
|
||||
|
||||
impl ObjectImpl for GameReviewPrivate {}
|
||||
impl WidgetImpl for GameReviewPrivate {}
|
||||
impl GridImpl for GameReviewPrivate {}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct GameReview(ObjectSubclass<GameReviewPrivate>) @extends gtk::Grid, gtk::Widget, @implements gtk::Accessible;
|
||||
}
|
||||
|
||||
impl GameReview {
|
||||
pub fn new(api: CoreApi) -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
|
||||
let board = Board::new(api);
|
||||
s.attach(&board, 0, 0, 2, 2);
|
||||
s.attach(>k::Label::new(Some("white player")), 0, 2, 1, 1);
|
||||
s.attach(>k::Label::new(Some("black player")), 0, 2, 1, 2);
|
||||
s.attach(>k::Label::new(Some("chat")), 1, 2, 2, 2);
|
||||
|
||||
s
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ use otg_core::{
|
|||
library::{LibraryRequest, LibraryResponse},
|
||||
CoreRequest, CoreResponse,
|
||||
};
|
||||
use sgf::Game;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/*
|
||||
|
@ -126,11 +127,11 @@ impl WidgetImpl for HomePrivate {}
|
|||
impl BoxImpl for HomePrivate {}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct HomeView(ObjectSubclass<HomePrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
|
||||
pub struct HomeView(ObjectSubclass<HomePrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable, gtk::Accessible;
|
||||
}
|
||||
|
||||
impl HomeView {
|
||||
pub fn new(api: CoreApi) -> Self {
|
||||
pub fn new(api: CoreApi, on_select_game: impl Fn(Game) + 'static) -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
s.set_spacing(4);
|
||||
s.set_homogeneous(false);
|
||||
|
@ -157,7 +158,7 @@ impl HomeView {
|
|||
s.append(&new_game_button);
|
||||
*/
|
||||
|
||||
let library = Library::default();
|
||||
let library = Library::new(move |game| on_select_game(game));
|
||||
let library_view = gtk::ScrolledWindow::builder()
|
||||
.hscrollbar_policy(gtk::PolicyType::Never)
|
||||
.min_content_width(360)
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
||||
|
||||
This file is part of On the Grid.
|
||||
|
||||
On the Grid 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.
|
||||
|
||||
On the Grid 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 On the Grid. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod game_review;
|
||||
pub use game_review::GameReview;
|
||||
|
||||
mod home;
|
||||
pub use home::HomeView;
|
||||
|
||||
|
|
Loading…
Reference in New Issue