From 70a295d4b135f4fe7fdc58a48a163d4eef8c3fd9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 5 Jul 2023 19:28:52 -0400 Subject: [PATCH 1/9] Start combining the new game and library views --- Cargo.lock | 2 +- kifu/core/src/ui/launch_screen.rs | 16 ++++++++++------ kifu/core/src/ui/library_view.rs | 10 ++++++++++ kifu/core/src/ui/mod.rs | 7 +++++-- 4 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 kifu/core/src/ui/library_view.rs diff --git a/Cargo.lock b/Cargo.lock index abd507e..23c8ad3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,7 +910,7 @@ dependencies = [ "gdk4", "gio", "glib", - "glib-build-tools", + "glib-build-tools 0.16.3", "gtk4", "libadwaita", "serde", diff --git a/kifu/core/src/ui/launch_screen.rs b/kifu/core/src/ui/launch_screen.rs index 3696423..158c485 100644 --- a/kifu/core/src/ui/launch_screen.rs +++ b/kifu/core/src/ui/launch_screen.rs @@ -1,14 +1,18 @@ -use crate::{ - ui::types; -}; +use crate::ui::{home, library_view, HomeView, LibraryView}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; +#[typeshare] #[derive(Clone, Debug, Serialize, Deserialize)] -pub enum LaunchScreenView { - CreateGame(CreateGameView) +pub struct LaunchScreenView { + home: HomeView, + library: LibraryView, } // This will be called when the Kifu application starts. -pub fn launch_screen() -> LaunchScreenView { +pub fn launch_screen_view() -> LaunchScreenView { + LaunchScreenView { + home: home(vec![].into_iter()), + library: library_view(), + } } diff --git a/kifu/core/src/ui/library_view.rs b/kifu/core/src/ui/library_view.rs new file mode 100644 index 0000000..a7c040d --- /dev/null +++ b/kifu/core/src/ui/library_view.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +#[typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LibraryView; + +pub fn library_view() -> LibraryView { + LibraryView +} diff --git a/kifu/core/src/ui/mod.rs b/kifu/core/src/ui/mod.rs index e9b5b7d..308ac55 100644 --- a/kifu/core/src/ui/mod.rs +++ b/kifu/core/src/ui/mod.rs @@ -4,8 +4,11 @@ pub use elements::{action::Action, game_preview::GamePreviewElement, menu::Menu} mod playing_field; pub use playing_field::{playing_field, PlayingFieldView}; -// mod launch_screen; -// pub use launch_screen::{launch_screen, LaunchScreenView}; +mod launch_screen; +pub use launch_screen::{launch_screen_view, LaunchScreenView}; + +mod library_view; +pub use library_view::{library_view, LibraryView}; mod home; pub use home::{home, HomeView, HotseatPlayerElement, PlayerElement}; -- 2.44.1 From 07b73515013ec92eab3bdd57b8a9d330ad722600 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 19 Aug 2023 20:46:43 -0400 Subject: [PATCH 2/9] Flatten configuration by one level --- config/src/lib.rs | 1 + kifu/core/src/api.rs | 2 + kifu/gtk/Cargo.toml | 2 +- kifu/gtk/config | 8 ++- kifu/gtk/src/ui/game_database.rs | 96 ++++++++++++++++++++++++++++++++ kifu/gtk/src/ui/home.rs | 61 ++++++++++++++++---- kifu/gtk/src/ui/mod.rs | 3 + 7 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 kifu/gtk/src/ui/game_database.rs diff --git a/config/src/lib.rs b/config/src/lib.rs index 1c2b36b..054d96a 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -30,6 +30,7 @@ macro_rules! define_config { } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + #[serde(untagged)] pub enum ConfigOption { $($name($struct)),+ } diff --git a/kifu/core/src/api.rs b/kifu/core/src/api.rs index 38c4d84..4387187 100644 --- a/kifu/core/src/api.rs +++ b/kifu/core/src/api.rs @@ -71,8 +71,10 @@ impl CoreApp { pub fn new(config_path: std::path::PathBuf) -> Self { println!("config_path: {:?}", config_path); let config = Config::from_path(config_path).expect("configuration to open"); + println!("config: {:?}", config); let db_path: DatabasePath = config.get().unwrap(); + println!("db_path: {:?}", db_path); let state = Arc::new(RwLock::new(AppState::new(db_path))); println!("config: {:?}", config); diff --git a/kifu/gtk/Cargo.toml b/kifu/gtk/Cargo.toml index fe09ef7..d58e07f 100644 --- a/kifu/gtk/Cargo.toml +++ b/kifu/gtk/Cargo.toml @@ -12,7 +12,7 @@ screenplay = [] cairo-rs = { version = "0.17" } gio = { version = "0.17" } glib = { version = "0.17" } -gtk = { version = "0.6", package = "gtk4" } +gtk = { version = "0.6", package = "gtk4", features = ["v4_8"] } image = { version = "0.24" } kifu-core = { path = "../core" } tokio = { version = "1.26", features = [ "full" ] } diff --git a/kifu/gtk/config b/kifu/gtk/config index 6ad4a86..a387288 100644 --- a/kifu/gtk/config +++ b/kifu/gtk/config @@ -1 +1,7 @@ -{"Me":{"name":"Savanni","rank":{"Kyu":10}},"DatabasePath":"../core/fixtures/five_games"} +{ + "Me":{ + "name":"Savanni", + "rank":{"Kyu":10} + }, + "DatabasePath": "kifu/core/fixtures/five_games" +} diff --git a/kifu/gtk/src/ui/game_database.rs b/kifu/gtk/src/ui/game_database.rs new file mode 100644 index 0000000..0113a70 --- /dev/null +++ b/kifu/gtk/src/ui/game_database.rs @@ -0,0 +1,96 @@ +use glib::{Object, Properties}; +use gtk::{glib, prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Default)] +pub struct IntegerObjectPrivate { + number: Rc>, +} + +#[glib::object_subclass] +impl ObjectSubclass for IntegerObjectPrivate { + const NAME: &'static str = "IntegerObject"; + type Type = IntegerObject; +} + +impl ObjectImpl for IntegerObjectPrivate {} + +glib::wrapper! { + pub struct IntegerObject(ObjectSubclass); +} + +impl IntegerObject { + pub fn new(number: i32) -> Self { + let s: Self = Object::builder().build(); + *s.imp().number.borrow_mut() = number; + s + } + + pub fn number(&self) -> i32 { + self.imp().number.borrow().clone() + } +} + +pub struct GameDatabasePrivate { + list_view: gtk::ListView, +} + +impl Default for GameDatabasePrivate { + fn default() -> Self { + let vector: Vec = (0..=500).map(IntegerObject::new).collect(); + let model = gio::ListStore::new(glib::types::Type::OBJECT); + model.extend_from_slice(&vector); + let factory = gtk::SignalListItemFactory::new(); + factory.connect_setup(move |_, list_item| { + let label = gtk::Label::new(None); + list_item + .downcast_ref::() + .expect("Needs to be a ListItem") + .set_child(Some(&label)); + }); + factory.connect_bind(move |_, list_item| { + let integer_object = list_item + .downcast_ref::() + .expect("Needs to be ListItem") + .item() + .and_downcast::() + .expect("The item has to be an IntegerObject."); + + let label = list_item + .downcast_ref::() + .expect("Needs to be ListItem") + .child() + .and_downcast::() + .expect("The child has to be an Label."); + + label.set_label(&integer_object.number().to_string()); + }); + + let selection_model = gtk::SingleSelection::new(Some(model)); + let list_view = gtk::ListView::new(Some(selection_model), Some(factory)); + Self { list_view } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for GameDatabasePrivate { + const NAME: &'static str = "GameDatabase"; + type Type = GameDatabase; + type ParentType = gtk::Box; +} + +impl ObjectImpl for GameDatabasePrivate {} +impl WidgetImpl for GameDatabasePrivate {} +impl BoxImpl for GameDatabasePrivate {} + +glib::wrapper! { + pub struct GameDatabase(ObjectSubclass) @extends gtk::Widget, gtk::Box; +} + +impl GameDatabase { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + s.append(&s.imp().list_view); + s + } +} diff --git a/kifu/gtk/src/ui/home.rs b/kifu/gtk/src/ui/home.rs index 8070a1d..fd71d9a 100644 --- a/kifu/gtk/src/ui/home.rs +++ b/kifu/gtk/src/ui/home.rs @@ -1,12 +1,16 @@ -use crate::ui::GamePreview; -use crate::CoreApi; -use glib::Object; +use crate::{ui::GamePreview, CoreApi}; +use glib::{Object, Properties}; use gtk::{glib, prelude::*, subclass::prelude::*}; use kifu_core::{ - ui::{HomeView, PlayerElement}, + ui::{GamePreviewElement, HomeView, PlayerElement}, CoreRequest, CreateGameRequest, HotseatPlayerRequest, PlayerInfoRequest, }; -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; + +use super::GameDatabase; struct PlayerDataEntryPrivate { name: gtk::Text, @@ -83,6 +87,44 @@ impl PlayerDataEntry { } } +pub struct DatabaseGamePrivate { + game: Rc>, +} + +impl Default for DatabaseGamePrivate { + fn default() -> Self { + Self { + game: Rc::new(RefCell::new(GamePreviewElement { + date: vec![], + black_player: "".to_owned(), + black_rank: None, + white_player: "".to_owned(), + white_rank: None, + })), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for DatabaseGamePrivate { + const NAME: &'static str = "DatabaseGame"; + type Type = DatabaseGame; +} + +impl ObjectImpl for DatabaseGamePrivate {} + +glib::wrapper! { + pub struct DatabaseGame(ObjectSubclass); +} + +impl DatabaseGame { + pub fn new(preview: GamePreviewElement) -> Self { + let s: Self = Object::builder().build(); + *s.imp().game.borrow_mut() = preview; + s + } +} + pub struct HomePrivate { black_player: Rc>>, white_player: Rc>>, @@ -127,6 +169,9 @@ impl Home { let new_game_button = gtk::Button::builder().label(&view.start_game.label).build(); s.attach(&new_game_button, 2, 2, 1, 1); + let database = GameDatabase::new(); + s.attach(&database, 0, 3, 2, 2); + new_game_button.connect_clicked({ move |_| { let black_player = black_player.clone(); @@ -139,12 +184,6 @@ impl Home { } }); - let game_list = gtk::Box::new(gtk::Orientation::Vertical, 0); - s.attach(&game_list, 1, 3, 2, 1); - view.games - .iter() - .for_each(|game_preview| game_list.append(&GamePreview::new(game_preview.clone()))); - s } } diff --git a/kifu/gtk/src/ui/mod.rs b/kifu/gtk/src/ui/mod.rs index d3b9c14..4c4ca47 100644 --- a/kifu/gtk/src/ui/mod.rs +++ b/kifu/gtk/src/ui/mod.rs @@ -1,6 +1,9 @@ mod chat; pub use chat::Chat; +mod game_database; +pub use game_database::GameDatabase; + mod game_preview; pub use game_preview::GamePreview; -- 2.44.1 From e3f4ca246dc30988850795757dc1cf0c19a3884f Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 19 Aug 2023 23:24:01 -0400 Subject: [PATCH 3/9] Create the list of games --- Cargo.lock | 2 +- kifu/core/src/api.rs | 6 --- kifu/gtk/Cargo.toml | 3 +- kifu/gtk/src/ui/game_database.rs | 66 ++++++++++++++++++++++---------- kifu/gtk/src/ui/home.rs | 49 +++++------------------- 5 files changed, 58 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23c8ad3..de43e70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,7 +1348,7 @@ dependencies = [ "gtk4", "image", "kifu-core", - "screenplay", + "sgf", "tokio", ] diff --git a/kifu/core/src/api.rs b/kifu/core/src/api.rs index 4387187..fead3e7 100644 --- a/kifu/core/src/api.rs +++ b/kifu/core/src/api.rs @@ -69,17 +69,11 @@ pub struct CoreApp { impl CoreApp { pub fn new(config_path: std::path::PathBuf) -> Self { - println!("config_path: {:?}", config_path); let config = Config::from_path(config_path).expect("configuration to open"); - println!("config: {:?}", config); let db_path: DatabasePath = config.get().unwrap(); - println!("db_path: {:?}", db_path); let state = Arc::new(RwLock::new(AppState::new(db_path))); - println!("config: {:?}", config); - println!("games database: {:?}", state.read().unwrap().database.len()); - Self { config, state } } diff --git a/kifu/gtk/Cargo.toml b/kifu/gtk/Cargo.toml index d58e07f..21918ab 100644 --- a/kifu/gtk/Cargo.toml +++ b/kifu/gtk/Cargo.toml @@ -16,7 +16,8 @@ gtk = { version = "0.6", package = "gtk4", features = ["v4_8"] } image = { version = "0.24" } kifu-core = { path = "../core" } tokio = { version = "1.26", features = [ "full" ] } -screenplay = { path = "../../screenplay" } +# screenplay = { path = "../../screenplay" } +sgf = { path = "../../sgf" } [build-dependencies] glib-build-tools = "0.17" diff --git a/kifu/gtk/src/ui/game_database.rs b/kifu/gtk/src/ui/game_database.rs index 0113a70..6c72c31 100644 --- a/kifu/gtk/src/ui/game_database.rs +++ b/kifu/gtk/src/ui/game_database.rs @@ -1,59 +1,62 @@ -use glib::{Object, Properties}; +use glib::Object; use gtk::{glib, prelude::*, subclass::prelude::*}; +use kifu_core::ui::GamePreviewElement; use std::{cell::RefCell, rc::Rc}; #[derive(Default)] -pub struct IntegerObjectPrivate { - number: Rc>, +pub struct GameObjectPrivate { + game: Rc>>, } #[glib::object_subclass] -impl ObjectSubclass for IntegerObjectPrivate { - const NAME: &'static str = "IntegerObject"; - type Type = IntegerObject; +impl ObjectSubclass for GameObjectPrivate { + const NAME: &'static str = "GameObject"; + type Type = GameObject; } -impl ObjectImpl for IntegerObjectPrivate {} +impl ObjectImpl for GameObjectPrivate {} glib::wrapper! { - pub struct IntegerObject(ObjectSubclass); + pub struct GameObject(ObjectSubclass); } -impl IntegerObject { - pub fn new(number: i32) -> Self { +impl GameObject { + pub fn new(game: GamePreviewElement) -> Self { let s: Self = Object::builder().build(); - *s.imp().number.borrow_mut() = number; + *s.imp().game.borrow_mut() = Some(game); s } - pub fn number(&self) -> i32 { - self.imp().number.borrow().clone() + pub fn game(&self) -> Option { + self.imp().game.borrow().clone() } } pub struct GameDatabasePrivate { + model: gio::ListStore, list_view: gtk::ListView, } impl Default for GameDatabasePrivate { fn default() -> Self { - let vector: Vec = (0..=500).map(IntegerObject::new).collect(); + let vector: Vec = vec![]; let model = gio::ListStore::new(glib::types::Type::OBJECT); model.extend_from_slice(&vector); let factory = gtk::SignalListItemFactory::new(); + factory.connect_setup(move |_, list_item| { - let label = gtk::Label::new(None); + let label = gtk::Label::new(Some("some kind of text")); list_item .downcast_ref::() .expect("Needs to be a ListItem") .set_child(Some(&label)); }); factory.connect_bind(move |_, list_item| { - let integer_object = list_item + let game_object = list_item .downcast_ref::() .expect("Needs to be ListItem") .item() - .and_downcast::() + .and_downcast::() .expect("The item has to be an IntegerObject."); let label = list_item @@ -63,12 +66,25 @@ impl Default for GameDatabasePrivate { .and_downcast::() .expect("The child has to be an Label."); - label.set_label(&integer_object.number().to_string()); + label.set_label( + format!( + "{} vs. {}", + game_object + .game() + .map(|g| g.black_player) + .unwrap_or("black".to_owned()), + game_object + .game() + .map(|g| g.white_player) + .unwrap_or("white".to_owned()) + ) + .as_ref(), + ); }); - let selection_model = gtk::SingleSelection::new(Some(model)); + let selection_model = gtk::NoSelection::new(Some(model.clone())); let list_view = gtk::ListView::new(Some(selection_model), Some(factory)); - Self { list_view } + Self { model, list_view } } } @@ -90,7 +106,17 @@ glib::wrapper! { impl GameDatabase { pub fn new() -> Self { let s: Self = Object::builder().build(); + s.set_width_request(200); + s.set_height_request(200); s.append(&s.imp().list_view); s } + + pub fn set_games(&self, games: Vec) { + let games = games + .into_iter() + .map(|g| GameObject::new(g)) + .collect::>(); + self.imp().model.extend_from_slice(&games); + } } diff --git a/kifu/gtk/src/ui/home.rs b/kifu/gtk/src/ui/home.rs index fd71d9a..533821b 100644 --- a/kifu/gtk/src/ui/home.rs +++ b/kifu/gtk/src/ui/home.rs @@ -87,44 +87,6 @@ impl PlayerDataEntry { } } -pub struct DatabaseGamePrivate { - game: Rc>, -} - -impl Default for DatabaseGamePrivate { - fn default() -> Self { - Self { - game: Rc::new(RefCell::new(GamePreviewElement { - date: vec![], - black_player: "".to_owned(), - black_rank: None, - white_player: "".to_owned(), - white_rank: None, - })), - } - } -} - -#[glib::object_subclass] -impl ObjectSubclass for DatabaseGamePrivate { - const NAME: &'static str = "DatabaseGame"; - type Type = DatabaseGame; -} - -impl ObjectImpl for DatabaseGamePrivate {} - -glib::wrapper! { - pub struct DatabaseGame(ObjectSubclass); -} - -impl DatabaseGame { - pub fn new(preview: GamePreviewElement) -> Self { - let s: Self = Object::builder().build(); - *s.imp().game.borrow_mut() = preview; - s - } -} - pub struct HomePrivate { black_player: Rc>>, white_player: Rc>>, @@ -169,8 +131,15 @@ impl Home { let new_game_button = gtk::Button::builder().label(&view.start_game.label).build(); s.attach(&new_game_button, 2, 2, 1, 1); - let database = GameDatabase::new(); - s.attach(&database, 0, 3, 2, 2); + let library = GameDatabase::new(); + let library_view = gtk::ScrolledWindow::builder() + .hscrollbar_policy(gtk::PolicyType::Never) + .min_content_width(360) + .child(&library) + .build(); + s.attach(&library_view, 0, 3, 4, 2); + + library.set_games(view.games); new_game_button.connect_clicked({ move |_| { -- 2.44.1 From a584fb4de374dcedaebac22fa0fa4b3927b9aad9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 19 Aug 2023 23:45:19 -0400 Subject: [PATCH 4/9] Get the scrollbar to expand with the window --- kifu/gtk/src/main.rs | 4 ++-- kifu/gtk/src/ui/home.rs | 37 ++++++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/kifu/gtk/src/main.rs b/kifu/gtk/src/main.rs index 95cda24..499acf0 100644 --- a/kifu/gtk/src/main.rs +++ b/kifu/gtk/src/main.rs @@ -13,8 +13,8 @@ fn handle_response(api: CoreApi, window: gtk::ApplicationWindow, message: CoreRe CoreResponse::HomeView(view) => perftrace("HomeView", || { let api = api.clone(); - let new_game = Home::new(api, view); - window.set_child(Some(&new_game)); + let home = Home::new(api, view); + window.set_child(Some(&home)); }), CoreResponse::PlayingFieldView(view) => perftrace("PlayingFieldView", || { let api = api.clone(); diff --git a/kifu/gtk/src/ui/home.rs b/kifu/gtk/src/ui/home.rs index 533821b..3777454 100644 --- a/kifu/gtk/src/ui/home.rs +++ b/kifu/gtk/src/ui/home.rs @@ -1,14 +1,11 @@ -use crate::{ui::GamePreview, CoreApi}; -use glib::{Object, Properties}; +use crate::CoreApi; +use glib::Object; use gtk::{glib, prelude::*, subclass::prelude::*}; use kifu_core::{ - ui::{GamePreviewElement, HomeView, PlayerElement}, + ui::{HomeView, PlayerElement}, CoreRequest, CreateGameRequest, HotseatPlayerRequest, PlayerInfoRequest, }; -use std::{ - cell::{Cell, RefCell}, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; use super::GameDatabase; @@ -105,39 +102,49 @@ impl Default for HomePrivate { impl ObjectSubclass for HomePrivate { const NAME: &'static str = "Home"; type Type = Home; - type ParentType = gtk::Grid; + type ParentType = gtk::Box; } impl ObjectImpl for HomePrivate {} impl WidgetImpl for HomePrivate {} -impl GridImpl for HomePrivate {} +impl BoxImpl for HomePrivate {} glib::wrapper! { - pub struct Home(ObjectSubclass) @extends gtk::Grid, gtk::Widget; + pub struct Home(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } impl Home { pub fn new(api: CoreApi, view: HomeView) -> Home { let s: Self = Object::builder().build(); + s.set_spacing(4); + s.set_homogeneous(false); + s.set_orientation(gtk::Orientation::Vertical); + + let players = gtk::Box::builder() + .spacing(4) + .orientation(gtk::Orientation::Horizontal) + .build(); + s.append(&players); let black_player = PlayerDataEntry::new(view.black_player); - s.attach(&black_player, 1, 1, 1, 1); + players.append(&black_player); *s.imp().black_player.borrow_mut() = Some(black_player.clone()); - let white_player = PlayerDataEntry::new(view.white_player); - s.attach(&white_player, 2, 1, 1, 1); + players.append(&white_player); *s.imp().white_player.borrow_mut() = Some(white_player.clone()); let new_game_button = gtk::Button::builder().label(&view.start_game.label).build(); - s.attach(&new_game_button, 2, 2, 1, 1); + s.append(&new_game_button); let library = GameDatabase::new(); let library_view = gtk::ScrolledWindow::builder() .hscrollbar_policy(gtk::PolicyType::Never) .min_content_width(360) + .vexpand(true) + .hexpand(true) .child(&library) .build(); - s.attach(&library_view, 0, 3, 4, 2); + s.append(&library_view); library.set_games(view.games); -- 2.44.1 From e9ffab11870f3d4834ef02beba50fc30899e95e1 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 20 Aug 2023 12:31:44 -0400 Subject: [PATCH 5/9] Construct a game preview component and render basic information into it --- kifu/core/src/ui/elements/game_preview.rs | 47 +++++++++++-------- kifu/gtk/src/ui/game_preview.rs | 32 ++++++++----- .../src/ui/{game_database.rs => library.rs} | 33 +++++-------- kifu/gtk/src/ui/mod.rs | 6 +-- sgf/src/date.rs | 10 ++++ 5 files changed, 72 insertions(+), 56 deletions(-) rename kifu/gtk/src/ui/{game_database.rs => library.rs} (77%) diff --git a/kifu/core/src/ui/elements/game_preview.rs b/kifu/core/src/ui/elements/game_preview.rs index 1624fb9..410f583 100644 --- a/kifu/core/src/ui/elements/game_preview.rs +++ b/kifu/core/src/ui/elements/game_preview.rs @@ -1,36 +1,45 @@ use serde::{Deserialize, Serialize}; -use sgf::{ - go::{Game, Rank}, - Date, -}; +use sgf::go::Game; use typeshare::typeshare; #[derive(Clone, Debug, Deserialize, Serialize)] #[typeshare] pub struct GamePreviewElement { - pub date: Vec, + pub date: Vec, pub black_player: String, - pub black_rank: Option, pub white_player: String, - pub white_rank: Option, } impl GamePreviewElement { pub fn new(game: &Game) -> GamePreviewElement { + let black_player = match game.info.black_player { + Some(ref black_player) => black_player.clone(), + None => "unknown".to_owned(), + }; + let white_player = match game.info.white_player { + Some(ref white_player) => white_player.clone(), + None => "unknown".to_owned(), + }; + + let black_player = match game.info.black_rank { + Some(rank) => format!("{} ({})", black_player, rank.to_string()), + None => black_player, + }; + + let white_player = match game.info.white_rank { + Some(rank) => format!("{} ({})", white_player, rank.to_string()), + None => white_player, + }; + GamePreviewElement { - date: game.info.date.clone(), - black_player: game + date: game .info - .black_player - .clone() - .unwrap_or("black_player".to_owned()), - black_rank: game.info.black_rank.clone(), - white_player: game - .info - .white_player - .clone() - .unwrap_or("white_player".to_owned()), - white_rank: game.info.white_rank.clone(), + .date + .iter() + .map(|dt| dt.to_string()) + .collect::>(), + black_player, + white_player, } } } diff --git a/kifu/gtk/src/ui/game_preview.rs b/kifu/gtk/src/ui/game_preview.rs index 64e2d92..4c8d61b 100644 --- a/kifu/gtk/src/ui/game_preview.rs +++ b/kifu/gtk/src/ui/game_preview.rs @@ -3,7 +3,12 @@ use gtk::{glib, prelude::*, subclass::prelude::*}; use kifu_core::ui::GamePreviewElement; #[derive(Default)] -pub struct GamePreviewPrivate; +pub struct GamePreviewPrivate { + title: gtk::Label, + black_player: gtk::Label, + white_player: gtk::Label, + date: gtk::Label, +} #[glib::object_subclass] impl ObjectSubclass for GamePreviewPrivate { @@ -21,22 +26,23 @@ glib::wrapper! { } impl GamePreview { - pub fn new(element: GamePreviewElement) -> GamePreview { + pub fn new() -> GamePreview { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Horizontal); - println!("game_preview: {:?}", element); - let black_player = match element.black_rank { - Some(rank) => format!("{} ({})", element.black_player, rank.to_string()), - None => element.black_player, - }; - let white_player = match element.white_rank { - Some(rank) => format!("{} ({})", element.white_player, rank.to_string()), - None => element.white_player, - }; - s.append(>k::Label::new(Some(&black_player))); - s.append(>k::Label::new(Some(&white_player))); + s.append(&s.imp().date); + s.append(&s.imp().title); + s.append(&s.imp().black_player); + s.append(&s.imp().white_player); s } + + pub fn set_game(&self, element: GamePreviewElement) { + self.imp().black_player.set_text(&element.black_player); + self.imp().white_player.set_text(&element.white_player); + if let Some(date) = element.date.first() { + self.imp().date.set_text(&date); + } + } } diff --git a/kifu/gtk/src/ui/game_database.rs b/kifu/gtk/src/ui/library.rs similarity index 77% rename from kifu/gtk/src/ui/game_database.rs rename to kifu/gtk/src/ui/library.rs index 6c72c31..6deedd5 100644 --- a/kifu/gtk/src/ui/game_database.rs +++ b/kifu/gtk/src/ui/library.rs @@ -1,3 +1,4 @@ +use crate::ui::GamePreview; use glib::Object; use gtk::{glib, prelude::*, subclass::prelude::*}; use kifu_core::ui::GamePreviewElement; @@ -45,41 +46,31 @@ impl Default for GameDatabasePrivate { let factory = gtk::SignalListItemFactory::new(); factory.connect_setup(move |_, list_item| { - let label = gtk::Label::new(Some("some kind of text")); + let preview = GamePreview::new(); list_item .downcast_ref::() .expect("Needs to be a ListItem") - .set_child(Some(&label)); + .set_child(Some(&preview)); }); factory.connect_bind(move |_, list_item| { - let game_object = list_item + let game_element = list_item .downcast_ref::() .expect("Needs to be ListItem") .item() .and_downcast::() - .expect("The item has to be an IntegerObject."); + .expect("The item has to be a GameObject."); - let label = list_item + let preview = list_item .downcast_ref::() .expect("Needs to be ListItem") .child() - .and_downcast::() - .expect("The child has to be an Label."); + .and_downcast::() + .expect("The child has to be a GamePreview object."); - label.set_label( - format!( - "{} vs. {}", - game_object - .game() - .map(|g| g.black_player) - .unwrap_or("black".to_owned()), - game_object - .game() - .map(|g| g.white_player) - .unwrap_or("white".to_owned()) - ) - .as_ref(), - ); + match game_element.game() { + Some(game) => preview.set_game(game), + None => (), + }; }); let selection_model = gtk::NoSelection::new(Some(model.clone())); diff --git a/kifu/gtk/src/ui/mod.rs b/kifu/gtk/src/ui/mod.rs index 4c4ca47..f43273c 100644 --- a/kifu/gtk/src/ui/mod.rs +++ b/kifu/gtk/src/ui/mod.rs @@ -1,12 +1,12 @@ mod chat; pub use chat::Chat; -mod game_database; -pub use game_database::GameDatabase; - mod game_preview; pub use game_preview::GamePreview; +mod library; +pub use library::GameDatabase; + mod player_card; pub use player_card::PlayerCard; diff --git a/sgf/src/date.rs b/sgf/src/date.rs index 4b9ee9d..3058174 100644 --- a/sgf/src/date.rs +++ b/sgf/src/date.rs @@ -24,6 +24,16 @@ pub enum Date { Date(chrono::NaiveDate), } +impl Date { + pub fn to_string(&self) -> String { + match self { + Date::Year(y) => format!("{}", y), + Date::YearMonth(y, m) => format!("{}-{}", y, m), + Date::Date(date) => format!("{}-{}-{}", date.year(), date.month(), date.day()), + } + } +} + /* impl TryFrom<&str> for Date { type Error = String; -- 2.44.1 From e5d0b7d20f45a774c41b4958985814c752d0a856 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 20 Aug 2023 12:40:46 -0400 Subject: [PATCH 6/9] Improve formatting. Rename GameDatabase to Library --- kifu/gtk/src/ui/game_preview.rs | 2 ++ kifu/gtk/src/ui/home.rs | 6 ++---- kifu/gtk/src/ui/library.rs | 24 ++++++++++++------------ kifu/gtk/src/ui/mod.rs | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/kifu/gtk/src/ui/game_preview.rs b/kifu/gtk/src/ui/game_preview.rs index 4c8d61b..99ec42c 100644 --- a/kifu/gtk/src/ui/game_preview.rs +++ b/kifu/gtk/src/ui/game_preview.rs @@ -29,6 +29,8 @@ impl GamePreview { pub fn new() -> GamePreview { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Horizontal); + s.set_homogeneous(true); + s.set_hexpand(true); s.append(&s.imp().date); s.append(&s.imp().title); diff --git a/kifu/gtk/src/ui/home.rs b/kifu/gtk/src/ui/home.rs index 3777454..c9a166b 100644 --- a/kifu/gtk/src/ui/home.rs +++ b/kifu/gtk/src/ui/home.rs @@ -1,4 +1,4 @@ -use crate::CoreApi; +use crate::{ui::Library, CoreApi}; use glib::Object; use gtk::{glib, prelude::*, subclass::prelude::*}; use kifu_core::{ @@ -7,8 +7,6 @@ use kifu_core::{ }; use std::{cell::RefCell, rc::Rc}; -use super::GameDatabase; - struct PlayerDataEntryPrivate { name: gtk::Text, rank: gtk::DropDown, @@ -136,7 +134,7 @@ impl Home { let new_game_button = gtk::Button::builder().label(&view.start_game.label).build(); s.append(&new_game_button); - let library = GameDatabase::new(); + let library = Library::new(); let library_view = gtk::ScrolledWindow::builder() .hscrollbar_policy(gtk::PolicyType::Never) .min_content_width(360) diff --git a/kifu/gtk/src/ui/library.rs b/kifu/gtk/src/ui/library.rs index 6deedd5..9fc24ce 100644 --- a/kifu/gtk/src/ui/library.rs +++ b/kifu/gtk/src/ui/library.rs @@ -33,12 +33,12 @@ impl GameObject { } } -pub struct GameDatabasePrivate { +pub struct LibraryPrivate { model: gio::ListStore, list_view: gtk::ListView, } -impl Default for GameDatabasePrivate { +impl Default for LibraryPrivate { fn default() -> Self { let vector: Vec = vec![]; let model = gio::ListStore::new(glib::types::Type::OBJECT); @@ -75,30 +75,30 @@ impl Default for GameDatabasePrivate { let selection_model = gtk::NoSelection::new(Some(model.clone())); let list_view = gtk::ListView::new(Some(selection_model), Some(factory)); + list_view.set_hexpand(true); Self { model, list_view } } } #[glib::object_subclass] -impl ObjectSubclass for GameDatabasePrivate { - const NAME: &'static str = "GameDatabase"; - type Type = GameDatabase; +impl ObjectSubclass for LibraryPrivate { + const NAME: &'static str = "Library"; + type Type = Library; type ParentType = gtk::Box; } -impl ObjectImpl for GameDatabasePrivate {} -impl WidgetImpl for GameDatabasePrivate {} -impl BoxImpl for GameDatabasePrivate {} +impl ObjectImpl for LibraryPrivate {} +impl WidgetImpl for LibraryPrivate {} +impl BoxImpl for LibraryPrivate {} glib::wrapper! { - pub struct GameDatabase(ObjectSubclass) @extends gtk::Widget, gtk::Box; + pub struct Library(ObjectSubclass) @extends gtk::Widget, gtk::Box; } -impl GameDatabase { +impl Library { pub fn new() -> Self { let s: Self = Object::builder().build(); - s.set_width_request(200); - s.set_height_request(200); + s.set_hexpand(true); s.append(&s.imp().list_view); s } diff --git a/kifu/gtk/src/ui/mod.rs b/kifu/gtk/src/ui/mod.rs index f43273c..ae70aa6 100644 --- a/kifu/gtk/src/ui/mod.rs +++ b/kifu/gtk/src/ui/mod.rs @@ -5,7 +5,7 @@ mod game_preview; pub use game_preview::GamePreview; mod library; -pub use library::GameDatabase; +pub use library::Library; mod player_card; pub use player_card::PlayerCard; -- 2.44.1 From d8534a08eb82522bc781a446644b28cbf4c32b9e Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 20 Aug 2023 12:53:14 -0400 Subject: [PATCH 7/9] Show the name of the game, and create one if it doesn't exist --- kifu/core/src/ui/elements/game_preview.rs | 7 +++++++ kifu/gtk/src/ui/game_preview.rs | 1 + sgf/src/go.rs | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/kifu/core/src/ui/elements/game_preview.rs b/kifu/core/src/ui/elements/game_preview.rs index 410f583..afc9e2b 100644 --- a/kifu/core/src/ui/elements/game_preview.rs +++ b/kifu/core/src/ui/elements/game_preview.rs @@ -6,6 +6,7 @@ use typeshare::typeshare; #[typeshare] pub struct GamePreviewElement { pub date: Vec, + pub name: String, pub black_player: String, pub white_player: String, } @@ -31,6 +32,11 @@ impl GamePreviewElement { None => white_player, }; + let name = match game.info.game_name { + Some(ref name) => name.clone(), + None => format!("{} vs. {}", black_player, white_player), + }; + GamePreviewElement { date: game .info @@ -38,6 +44,7 @@ impl GamePreviewElement { .iter() .map(|dt| dt.to_string()) .collect::>(), + name, black_player, white_player, } diff --git a/kifu/gtk/src/ui/game_preview.rs b/kifu/gtk/src/ui/game_preview.rs index 99ec42c..ed0d131 100644 --- a/kifu/gtk/src/ui/game_preview.rs +++ b/kifu/gtk/src/ui/game_preview.rs @@ -43,6 +43,7 @@ impl GamePreview { pub fn set_game(&self, element: GamePreviewElement) { self.imp().black_player.set_text(&element.black_player); self.imp().white_player.set_text(&element.white_player); + self.imp().title.set_text(&element.name); if let Some(date) = element.date.first() { self.imp().date.set_text(&date); } diff --git a/sgf/src/go.rs b/sgf/src/go.rs index 167b0c2..283ba2d 100644 --- a/sgf/src/go.rs +++ b/sgf/src/go.rs @@ -99,6 +99,11 @@ impl TryFrom for Game { info.app_name = tree.sequence[0] .find_prop("AP") .map(|prop| prop.values[0].clone()); + + info.game_name = tree.sequence[0] + .find_prop("GN") + .map(|prop| prop.values[0].clone()); + info.black_player = tree.sequence[0] .find_prop("PB") .map(|prop| prop.values.join(", ")); -- 2.44.1 From f75e0d4d656146ff4b5a6973f4baa5f25d8d56f8 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 20 Aug 2023 12:56:42 -0400 Subject: [PATCH 8/9] Remove the library_view --- kifu/core/src/ui/library_view.rs | 10 ---------- kifu/core/src/ui/mod.rs | 3 --- 2 files changed, 13 deletions(-) delete mode 100644 kifu/core/src/ui/library_view.rs diff --git a/kifu/core/src/ui/library_view.rs b/kifu/core/src/ui/library_view.rs deleted file mode 100644 index a7c040d..0000000 --- a/kifu/core/src/ui/library_view.rs +++ /dev/null @@ -1,10 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct LibraryView; - -pub fn library_view() -> LibraryView { - LibraryView -} diff --git a/kifu/core/src/ui/mod.rs b/kifu/core/src/ui/mod.rs index 308ac55..33803d8 100644 --- a/kifu/core/src/ui/mod.rs +++ b/kifu/core/src/ui/mod.rs @@ -7,9 +7,6 @@ pub use playing_field::{playing_field, PlayingFieldView}; mod launch_screen; pub use launch_screen::{launch_screen_view, LaunchScreenView}; -mod library_view; -pub use library_view::{library_view, LibraryView}; - mod home; pub use home::{home, HomeView, HotseatPlayerElement, PlayerElement}; -- 2.44.1 From aa64bf4c7e9f0847a37059ecb3bf3a2a798b07d3 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 20 Aug 2023 12:58:58 -0400 Subject: [PATCH 9/9] Remove the launch screen --- kifu/core/src/ui/launch_screen.rs | 18 ------------------ kifu/core/src/ui/mod.rs | 3 --- 2 files changed, 21 deletions(-) delete mode 100644 kifu/core/src/ui/launch_screen.rs diff --git a/kifu/core/src/ui/launch_screen.rs b/kifu/core/src/ui/launch_screen.rs deleted file mode 100644 index 158c485..0000000 --- a/kifu/core/src/ui/launch_screen.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::ui::{home, library_view, HomeView, LibraryView}; -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct LaunchScreenView { - home: HomeView, - library: LibraryView, -} - -// This will be called when the Kifu application starts. -pub fn launch_screen_view() -> LaunchScreenView { - LaunchScreenView { - home: home(vec![].into_iter()), - library: library_view(), - } -} diff --git a/kifu/core/src/ui/mod.rs b/kifu/core/src/ui/mod.rs index 33803d8..7233ec5 100644 --- a/kifu/core/src/ui/mod.rs +++ b/kifu/core/src/ui/mod.rs @@ -4,9 +4,6 @@ pub use elements::{action::Action, game_preview::GamePreviewElement, menu::Menu} mod playing_field; pub use playing_field::{playing_field, PlayingFieldView}; -mod launch_screen; -pub use launch_screen::{launch_screen_view, LaunchScreenView}; - mod home; pub use home::{home, HomeView, HotseatPlayerElement, PlayerElement}; -- 2.44.1