From c4e0f3f3e51970a4e93adfa6f2beee2463e74d08 Mon Sep 17 00:00:00 2001 From: tkashkin Date: Mon, 27 Aug 2018 06:26:13 +0300 Subject: [PATCH] Started work on #47: * GamesDB refactoring * Game tags * Tags import from GOG * FiltersPopover (just basic UI now, not hooked to anything) --- src/data/Game.vala | 2 + src/data/GamesDB.vala | 324 +++++++++++++----- src/data/sources/gog/GOG.vala | 16 +- src/data/sources/gog/GOGGame.vala | 51 ++- src/data/sources/humble/Humble.vala | 1 - src/data/sources/humble/HumbleGame.vala | 17 +- src/data/sources/steam/Steam.vala | 1 - src/data/sources/steam/SteamGame.vala | 15 +- src/meson.build | 1 + .../GameDetailsView/GameDetailsBlock.vala | 9 +- .../GameDetailsView/GameDetailsPage.vala | 6 +- .../GameDetailsView/GameDetailsView.vala | 10 +- .../GameDetailsView/blocks/Description.vala | 4 +- .../GameDetailsView/blocks/GOGDetails.vala | 4 +- .../GameDetailsView/blocks/SteamDetails.vala | 4 +- src/ui/views/GamesView/FiltersPopover.vala | 95 +++++ src/ui/views/GamesView/GamesView.vala | 11 + 17 files changed, 447 insertions(+), 124 deletions(-) create mode 100644 src/ui/views/GamesView/FiltersPopover.vala diff --git a/src/data/Game.vala b/src/data/Game.vala index db9a3af8..2f153a62 100644 --- a/src/data/Game.vala +++ b/src/data/Game.vala @@ -25,6 +25,8 @@ namespace GameHub.Data return platform in platforms; } + public ArrayList tags { get; protected set; default = new ArrayList(GamesDB.Tables.Tags.Tag.is_equal); } + public bool is_installable { get; protected set; default = false; } public File executable { get; protected set; } diff --git a/src/data/GamesDB.vala b/src/data/GamesDB.vala index 3f76f2f0..92a05097 100644 --- a/src/data/GamesDB.vala +++ b/src/data/GamesDB.vala @@ -46,94 +46,224 @@ namespace GameHub.Data db.exec("DROP TABLE IF EXISTS `unsupported_games`"); - if(db.prepare_v2("SELECT `platforms` FROM `games`", -1, out s) != Sqlite.OK) - { - db.exec("DROP TABLE `games`"); - } - // current db format - db.exec("CREATE TABLE IF NOT EXISTS `games`( - `source` string not null, - `id` string not null, - `name` string not null, - `icon` string, - `image` string, - `install_path` string, - `platforms` string, - `info` string, - `info_detailed` string, - PRIMARY KEY(`source`, `id`))"); + Tables.Games.init(db); + Tables.Tags.init(db); db.exec("CREATE TABLE IF NOT EXISTS `merges`(`merge` string not null, PRIMARY KEY(`merge`))"); } - public enum GAMES + public class Tables { - SOURCE, ID, NAME, ICON, IMAGE, INSTALL_PATH, PLATFORMS, INFO, INFO_DETAILED; - - public int column() + public abstract class DBTable { - switch(this) + public class Field { - case GAMES.SOURCE: return 0; - case GAMES.ID: return 1; - case GAMES.NAME: return 2; - case GAMES.ICON: return 3; - case GAMES.IMAGE: return 4; - case GAMES.INSTALL_PATH: return 5; - case GAMES.PLATFORMS: return 6; - case GAMES.INFO: return 7; - case GAMES.INFO_DETAILED: return 8; + public int column = 0; + public int column_for_bind = 0; + public Field(int col) + { + column = col; + column_for_bind = col + 1; + } + + public int bind(Statement s, string? str) + { + if(str == null) return bind_null(s); + return s.bind_text(column_for_bind, str); + } + public int bind_int(Statement s, int? i) + { + if(i == null) return bind_null(s); + return s.bind_int(column_for_bind, i); + } + public int bind_int64(Statement s, int64? i) + { + if(i == null) return bind_null(s); + return s.bind_int64(column_for_bind, i); + } + public int bind_value(Statement s, Sqlite.Value? v) + { + if(v == null) return bind_null(s); + return s.bind_value(column_for_bind, v); + } + public int bind_null(Statement s) + { + return s.bind_null(column_for_bind); + } + + public string? get(Statement s) + { + return s.column_text(column); + } + public int? get_int(Statement s) + { + return s.column_int(column); + } + public int64? get_int64(Statement s) + { + return s.column_int64(column); + } + public unowned Sqlite.Value? get_value(Statement s) + { + return s.column_value(column); + } } - assert_not_reached(); - } - public int column_for_bind() - { - return column() + 1; + protected static DBTable.Field f(int col) + { + return new DBTable.Field(col); + } } - public int bind(Statement s, string? str) - { - if(str == null) return bind_null(s); - return s.bind_text(column_for_bind(), str); - } - public int bind_int(Statement s, int? i) - { - if(i == null) return bind_null(s); - return s.bind_int(column_for_bind(), i); - } - public int bind_int64(Statement s, int64? i) - { - if(i == null) return bind_null(s); - return s.bind_int64(column_for_bind(), i); - } - public int bind_value(Statement s, Sqlite.Value? v) + public class Games: DBTable { - if(v == null) return bind_null(s); - return s.bind_value(column_for_bind(), v); - } - public int bind_null(Statement s) - { - return s.bind_null(column_for_bind()); - } + public static DBTable.Field SOURCE; + public static DBTable.Field ID; + public static DBTable.Field NAME; + public static DBTable.Field ICON; + public static DBTable.Field IMAGE; + public static DBTable.Field INSTALL_PATH; + public static DBTable.Field PLATFORMS; + public static DBTable.Field INFO; + public static DBTable.Field INFO_DETAILED; + public static DBTable.Field TAGS; + + public static void init(Database? db) requires (db != null) + { + Statement s; + if(db.prepare_v2("SELECT `platforms` FROM `games`", -1, out s) != Sqlite.OK) + { + db.exec("DROP TABLE `games`"); + } - public string? get(Statement s) - { - return s.column_text(column()); - } - public int? get_int(Statement s) - { - return s.column_int(column()); - } - public int64? get_int64(Statement s) - { - return s.column_int64(column()); + db.exec("CREATE TABLE IF NOT EXISTS `games`( + `source` string not null, + `id` string not null, + `name` string not null, + `icon` string, + `image` string, + `install_path` string, + `platforms` string, + `info` string, + `info_detailed` string, + `tags` string, + PRIMARY KEY(`source`, `id`))"); + + if(db.prepare_v2("SELECT `tags` FROM `games`", -1, out s) != Sqlite.OK) + { + db.exec("ALTER TABLE `games` ADD `tags` string"); + } + + SOURCE = f(0); + ID = f(1); + NAME = f(2); + ICON = f(3); + IMAGE = f(4); + INSTALL_PATH = f(5); + PLATFORMS = f(6); + INFO = f(7); + INFO_DETAILED = f(8); + TAGS = f(9); + } } - public unowned Sqlite.Value? get_value(Statement s) + + public class Tags: DBTable { - return s.column_value(column()); + public static DBTable.Field ID; + public static DBTable.Field NAME; + public static DBTable.Field ICON; + + public static ArrayList TAGS; + + public class Tag: Object + { + public const string BUILTIN_PREFIX = "builtin:"; + + public enum Builtin + { + FAVORITES, HIDDEN; + + public string id() + { + switch(this) + { + case Builtin.FAVORITES: return "favorites"; + case Builtin.HIDDEN: return "hidden"; + } + assert_not_reached(); + } + + public string name() + { + switch(this) + { + case Builtin.FAVORITES: return _("Favorites"); + case Builtin.HIDDEN: return _("Hidden"); + } + assert_not_reached(); + } + + public string icon() + { + switch(this) + { + case Builtin.FAVORITES: return "user-bookmarks-symbolic"; + case Builtin.HIDDEN: return "window-close-symbolic"; + } + assert_not_reached(); + } + } + + public string? id { get; construct set; } + public string? name { get; construct set; } + public string icon { get; construct; } + + public Tag(string? id, string? name, string icon="tag-symbolic") + { + Object(id: id, name: name, icon: icon); + } + public Tag.from_db(Statement s) + { + this(ID.get(s), NAME.get(s), ICON.get(s)); + } + public Tag.from_builtin(Builtin t) + { + this(BUILTIN_PREFIX + t.id(), t.name(), t.icon()); + } + + public static bool is_equal(Tag first, Tag second) + { + return first == second || first.id == second.id; + } + } + + public static void init(Database? db) requires (db != null) + { + db.exec("CREATE TABLE IF NOT EXISTS `tags`(`id` string, `name` string, `icon` string, PRIMARY KEY(`id`))"); + + ID = f(0); + NAME = f(1); + ICON = f(2); + + TAGS = new ArrayList(Tag.is_equal); + TAGS.add(new Tag.from_builtin(Tag.Builtin.FAVORITES)); + TAGS.add(new Tag.from_builtin(Tag.Builtin.HIDDEN)); + + foreach(var t in TAGS) + { + GamesDB.get_instance().add_tag(t); + } + + Statement s; + int res = db.prepare_v2("SELECT * FROM `tags`", -1, out s); + while((res = s.step()) == Sqlite.ROW) + { + var tag = new Tag.from_db(s); + if(!TAGS.contains(tag)) TAGS.add(tag); + } + } } } @@ -143,8 +273,8 @@ namespace GameHub.Data Statement s; int res = db.prepare_v2("INSERT OR REPLACE INTO `games` - (`source`, `id`, `name`, `icon`, `image`, `install_path`, `platforms`, `info`, `info_detailed`) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", -1, out s); + (`source`, `id`, `name`, `icon`, `image`, `install_path`, `platforms`, `info`, `info_detailed`, `tags`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", -1, out s); assert(res == Sqlite.OK); @@ -155,20 +285,46 @@ namespace GameHub.Data platforms += p.id(); } - GAMES.SOURCE.bind(s, game.source.id); - GAMES.ID.bind(s, game.id); - GAMES.NAME.bind(s, game.name); - GAMES.ICON.bind(s, game.icon); - GAMES.IMAGE.bind(s, game.image); - GAMES.INSTALL_PATH.bind(s, game.install_dir == null || !game.install_dir.query_exists() ? null : game.install_dir.get_path()); - GAMES.PLATFORMS.bind(s, platforms); - GAMES.INFO.bind(s, game.info); - GAMES.INFO_DETAILED.bind(s, game.info_detailed); + var tags = ""; + foreach(var t in game.tags) + { + if(tags.length > 0) tags += ","; + tags += t.id; + } + + Tables.Games.SOURCE.bind(s, game.source.id); + Tables.Games.ID.bind(s, game.id); + Tables.Games.NAME.bind(s, game.name); + Tables.Games.ICON.bind(s, game.icon); + Tables.Games.IMAGE.bind(s, game.image); + Tables.Games.INSTALL_PATH.bind(s, game.install_dir == null || !game.install_dir.query_exists() ? null : game.install_dir.get_path()); + Tables.Games.PLATFORMS.bind(s, platforms); + Tables.Games.INFO.bind(s, game.info); + Tables.Games.INFO_DETAILED.bind(s, game.info_detailed); + Tables.Games.TAGS.bind(s, tags); res = s.step(); return res == Sqlite.DONE; } + + public bool add_tag(Tables.Tags.Tag tag) requires (db != null) + { + Statement s; + int res = db.prepare_v2("INSERT OR REPLACE INTO `tags` (`id`, `name`, `icon`) VALUES (?, ?, ?)", -1, out s); + + assert(res == Sqlite.OK); + + Tables.Tags.ID.bind(s, tag.id); + Tables.Tags.NAME.bind(s, tag.name); + Tables.Tags.ICON.bind(s, tag.icon); + + res = s.step(); + + if(!Tables.Tags.TAGS.contains(tag)) Tables.Tags.TAGS.add(tag); + + return res == Sqlite.DONE; + } public bool merge(Game first, Game second) requires (db != null) { @@ -257,7 +413,7 @@ namespace GameHub.Data if((res = st.step()) == Sqlite.ROW) { - var s = GameSource.by_id(GAMES.SOURCE.get(st)); + var s = GameSource.by_id(Tables.Games.SOURCE.get(st)); if(s is Steam) { @@ -297,7 +453,7 @@ namespace GameHub.Data while((res = st.step()) == Sqlite.ROW) { - var s = GameSource.by_id(GAMES.SOURCE.get(st)); + var s = GameSource.by_id(Tables.Games.SOURCE.get(st)); if(s is Steam) { diff --git a/src/data/sources/gog/GOG.vala b/src/data/sources/gog/GOG.vala index 7008d750..24ab2a9f 100644 --- a/src/data/sources/gog/GOG.vala +++ b/src/data/sources/gog/GOG.vala @@ -207,6 +207,21 @@ namespace GameHub.Data.Sources.GOG debug("[GOG] Loading games: page %d of %d", page, pages); + if(page == 1) + { + var tags = root.has_member("tags") ? root.get_array_member("tags") : null; + if(tags != null) + { + foreach(var t in tags.get_elements()) + { + var id = t.get_object().get_string_member("id"); + var name = t.get_object().get_string_member("name"); + GamesDB.get_instance().add_tag(new GamesDB.Tables.Tags.Tag("gog:" + id, name, icon)); + debug("[GOG] Imported tag: %s (%s)", name, id); + } + } + } + var products = root.get_array_member("products"); foreach(var g in products.get_elements()) @@ -214,7 +229,6 @@ namespace GameHub.Data.Sources.GOG var game = new GOGGame(this, g); if(!(game.id in GAMES_BLACKLIST) && !_games.contains(game) && (!Settings.UI.get_instance().merge_games || !GamesDB.get_instance().is_game_merged(game))) { - GamesDB.get_instance().add_game(game); game.update_game_info.begin((obj, res) => { game.update_game_info.end(res); _games.add(game); diff --git a/src/data/sources/gog/GOGGame.vala b/src/data/sources/gog/GOGGame.vala index e09aad99..b2bf2803 100644 --- a/src/data/sources/gog/GOGGame.vala +++ b/src/data/sources/gog/GOGGame.vala @@ -39,16 +39,16 @@ namespace GameHub.Data.Sources.GOG public GOGGame.from_db(GOG src, Sqlite.Statement s) { source = src; - id = GamesDB.GAMES.ID.get(s); - name = GamesDB.GAMES.NAME.get(s); - icon = GamesDB.GAMES.ICON.get(s); - image = GamesDB.GAMES.IMAGE.get(s); - install_dir = FSUtils.file(GamesDB.GAMES.INSTALL_PATH.get(s)) ?? FSUtils.file(FSUtils.Paths.GOG.Games, installation_dir_name); - info = GamesDB.GAMES.INFO.get(s); - info_detailed = GamesDB.GAMES.INFO_DETAILED.get(s); + id = GamesDB.Tables.Games.ID.get(s); + name = GamesDB.Tables.Games.NAME.get(s); + icon = GamesDB.Tables.Games.ICON.get(s); + image = GamesDB.Tables.Games.IMAGE.get(s); + install_dir = FSUtils.file(GamesDB.Tables.Games.INSTALL_PATH.get(s)) ?? FSUtils.file(FSUtils.Paths.GOG.Games, installation_dir_name); + info = GamesDB.Tables.Games.INFO.get(s); + info_detailed = GamesDB.Tables.Games.INFO_DETAILED.get(s); platforms.clear(); - var pls = GamesDB.GAMES.PLATFORMS.get(s).split(","); + var pls = GamesDB.Tables.Games.PLATFORMS.get(s).split(","); foreach(var pl in pls) { foreach(var p in Platforms) @@ -56,6 +56,21 @@ namespace GameHub.Data.Sources.GOG if(pl == p.id()) { platforms.add(p); + break; + } + } + } + + tags.clear(); + var tag_ids = (GamesDB.Tables.Games.TAGS.get(s) ?? "").split(","); + foreach(var tid in tag_ids) + { + foreach(var t in GamesDB.Tables.Tags.TAGS) + { + if(tid == t.id) + { + if(!tags.contains(t)) tags.add(t); + break; } } } @@ -138,6 +153,26 @@ namespace GameHub.Data.Sources.GOG } } + root = Parser.parse_json(info); + + var tags_json = !root.get_object().has_member("tags") ? null : root.get_object().get_array_member("tags"); + + if(tags_json != null) + { + foreach(var tag_json in tags_json.get_elements()) + { + var tid = source.id + ":" + tag_json.get_string(); + foreach(var t in GamesDB.Tables.Tags.TAGS) + { + if(tid == t.id) + { + if(!tags.contains(t)) tags.add(t); + break; + } + } + } + } + GamesDB.get_instance().add_game(this); if(status.state != Game.State.DOWNLOADING) diff --git a/src/data/sources/humble/Humble.vala b/src/data/sources/humble/Humble.vala index a3510571..79cf958d 100644 --- a/src/data/sources/humble/Humble.vala +++ b/src/data/sources/humble/Humble.vala @@ -155,7 +155,6 @@ namespace GameHub.Data.Sources.Humble var game = new HumbleGame(this, key, product); if(!_games.contains(game) && game.platforms.size > 0 && (!Settings.UI.get_instance().merge_games || !GamesDB.get_instance().is_game_merged(game))) { - GamesDB.get_instance().add_game(game); game.update_game_info.begin((obj, res) => { game.update_game_info.end(res); _games.add(game); diff --git a/src/data/sources/humble/HumbleGame.vala b/src/data/sources/humble/HumbleGame.vala index c0198210..a78b9735 100644 --- a/src/data/sources/humble/HumbleGame.vala +++ b/src/data/sources/humble/HumbleGame.vala @@ -48,16 +48,16 @@ namespace GameHub.Data.Sources.Humble public HumbleGame.from_db(Humble src, Sqlite.Statement s) { source = src; - id = GamesDB.GAMES.ID.get(s); - name = GamesDB.GAMES.NAME.get(s); - icon = GamesDB.GAMES.ICON.get(s); - image = GamesDB.GAMES.IMAGE.get(s); - install_dir = FSUtils.file(GamesDB.GAMES.INSTALL_PATH.get(s)) ?? FSUtils.file(FSUtils.Paths.GOG.Games, installation_dir_name); - info = GamesDB.GAMES.INFO.get(s); - info_detailed = GamesDB.GAMES.INFO_DETAILED.get(s); + id = GamesDB.Tables.Games.ID.get(s); + name = GamesDB.Tables.Games.NAME.get(s); + icon = GamesDB.Tables.Games.ICON.get(s); + image = GamesDB.Tables.Games.IMAGE.get(s); + install_dir = FSUtils.file(GamesDB.Tables.Games.INSTALL_PATH.get(s)) ?? FSUtils.file(FSUtils.Paths.GOG.Games, installation_dir_name); + info = GamesDB.Tables.Games.INFO.get(s); + info_detailed = GamesDB.Tables.Games.INFO_DETAILED.get(s); platforms.clear(); - var pls = GamesDB.GAMES.PLATFORMS.get(s).split(","); + var pls = GamesDB.Tables.Games.PLATFORMS.get(s).split(","); foreach(var pl in pls) { foreach(var p in Platforms) @@ -65,6 +65,7 @@ namespace GameHub.Data.Sources.Humble if(pl == p.id()) { platforms.add(p); + break; } } } diff --git a/src/data/sources/steam/Steam.vala b/src/data/sources/steam/Steam.vala index dbfa99ad..957bcb75 100644 --- a/src/data/sources/steam/Steam.vala +++ b/src/data/sources/steam/Steam.vala @@ -220,7 +220,6 @@ namespace GameHub.Data.Sources.Steam var game = new SteamGame(this, g); if(!_games.contains(game) && (!Settings.UI.get_instance().merge_games || !GamesDB.get_instance().is_game_merged(game))) { - GamesDB.get_instance().add_game(game); yield game.update_game_info(); _games.add(game); games_count = _games.size; diff --git a/src/data/sources/steam/SteamGame.vala b/src/data/sources/steam/SteamGame.vala index 6d2519a3..9d8c14ad 100644 --- a/src/data/sources/steam/SteamGame.vala +++ b/src/data/sources/steam/SteamGame.vala @@ -31,15 +31,15 @@ namespace GameHub.Data.Sources.Steam public SteamGame.from_db(Steam src, Sqlite.Statement s) { source = src; - id = GamesDB.GAMES.ID.get(s); - name = GamesDB.GAMES.NAME.get(s); - icon = GamesDB.GAMES.ICON.get(s); - image = GamesDB.GAMES.IMAGE.get(s); - info = GamesDB.GAMES.INFO.get(s); - info_detailed = GamesDB.GAMES.INFO_DETAILED.get(s); + id = GamesDB.Tables.Games.ID.get(s); + name = GamesDB.Tables.Games.NAME.get(s); + icon = GamesDB.Tables.Games.ICON.get(s); + image = GamesDB.Tables.Games.IMAGE.get(s); + info = GamesDB.Tables.Games.INFO.get(s); + info_detailed = GamesDB.Tables.Games.INFO_DETAILED.get(s); platforms.clear(); - var pls = GamesDB.GAMES.PLATFORMS.get(s).split(","); + var pls = GamesDB.Tables.Games.PLATFORMS.get(s).split(","); foreach(var pl in pls) { foreach(var p in Platforms) @@ -47,6 +47,7 @@ namespace GameHub.Data.Sources.Steam if(pl == p.id()) { platforms.add(p); + break; } } } diff --git a/src/meson.build b/src/meson.build index 84a3cdf3..9871c101 100644 --- a/src/meson.build +++ b/src/meson.build @@ -79,6 +79,7 @@ executable( 'ui/views/GamesView/GameCard.vala', 'ui/views/GamesView/GameListRow.vala', 'ui/views/GamesView/DownloadProgressView.vala', + 'ui/views/GamesView/FiltersPopover.vala', 'ui/views/GameDetailsView/GameDetailsView.vala', 'ui/views/GameDetailsView/GameDetailsPage.vala', diff --git a/src/ui/views/GameDetailsView/GameDetailsBlock.vala b/src/ui/views/GameDetailsView/GameDetailsBlock.vala index 119aea90..3ef03a10 100644 --- a/src/ui/views/GameDetailsView/GameDetailsBlock.vala +++ b/src/ui/views/GameDetailsView/GameDetailsBlock.vala @@ -10,12 +10,11 @@ namespace GameHub.UI.Views.GameDetailsView { public Game game { get; construct; } - protected bool is_dialog; + public bool is_dialog { get; construct; } - public GameDetailsBlock(Game game) + public GameDetailsBlock(Game game, bool is_dialog) { - Object(game: game, orientation: Orientation.VERTICAL); - is_dialog = !(get_toplevel() is GameHub.UI.Windows.MainWindow); + Object(game: game, orientation: Orientation.VERTICAL, is_dialog: is_dialog); } public abstract bool supports_game { get; } @@ -33,7 +32,7 @@ namespace GameHub.UI.Views.GameDetailsView text_label.hexpand = false; text_label.wrap = true; text_label.xalign = 0; - text_label.max_width_chars = is_dialog ? 80 : -1; + text_label.max_width_chars = is_dialog ? 60 : -1; text_label.use_markup = markup; if(!multiline) diff --git a/src/ui/views/GameDetailsView/GameDetailsPage.vala b/src/ui/views/GameDetailsView/GameDetailsPage.vala index 1fe13dc4..08e15fdf 100644 --- a/src/ui/views/GameDetailsView/GameDetailsPage.vala +++ b/src/ui/views/GameDetailsView/GameDetailsPage.vala @@ -80,6 +80,8 @@ namespace GameHub.UI.Views.GameDetailsView title = new Label(null); title.halign = Align.START; + title.wrap = true; + title.xalign = 0; title.hexpand = true; title.get_style_context().add_class(Granite.STYLE_CLASS_H2_LABEL); @@ -194,6 +196,8 @@ namespace GameHub.UI.Views.GameDetailsView { is_dialog = !(get_toplevel() is GameHub.UI.Windows.MainWindow); + title.max_width_chars = is_dialog ? 36 : -1; + #if GTK_3_22 content_scrolled.max_content_height = is_dialog ? 640 : -1; #endif @@ -213,7 +217,7 @@ namespace GameHub.UI.Views.GameDetailsView blocks.foreach(b => blocks.remove(b)); - GameDetailsBlock[] blk = { new Blocks.GOGDetails(game, this), new Blocks.SteamDetails(game), new Blocks.Description(game) }; + GameDetailsBlock[] blk = { new Blocks.GOGDetails(game, this, is_dialog), new Blocks.SteamDetails(game, is_dialog), new Blocks.Description(game, is_dialog) }; foreach(var b in blk) { if(b.supports_game) diff --git a/src/ui/views/GameDetailsView/GameDetailsView.vala b/src/ui/views/GameDetailsView/GameDetailsView.vala index 8b28ce1b..3f96a845 100644 --- a/src/ui/views/GameDetailsView/GameDetailsView.vala +++ b/src/ui/views/GameDetailsView/GameDetailsView.vala @@ -145,7 +145,13 @@ namespace GameHub.UI.Views.GameDetailsView { foreach(var m in merges) { - if(g is Sources.GOG.GOGGame.DLC && Game.is_equal((g as Sources.GOG.GOGGame.DLC).game, m)) continue; + if(Game.is_equal(g, m) + || (!Settings.UI.get_instance().show_unsupported_games && !m.is_supported()) + || (g is Sources.GOG.GOGGame.DLC && Game.is_equal((g as Sources.GOG.GOGGame.DLC).game, m))) + { + continue; + } + add_page(m); } } @@ -166,7 +172,7 @@ namespace GameHub.UI.Views.GameDetailsView private void add_page(Game g) { - if(stack.get_child_by_name(g.source.name) != null) return; + if(stack.get_child_by_name(g.source.id) != null) return; var page = new GameDetailsPage(g, this); page.content.margin = content_margin; diff --git a/src/ui/views/GameDetailsView/blocks/Description.vala b/src/ui/views/GameDetailsView/blocks/Description.vala index b0a8ef8a..4323d33e 100644 --- a/src/ui/views/GameDetailsView/blocks/Description.vala +++ b/src/ui/views/GameDetailsView/blocks/Description.vala @@ -17,9 +17,9 @@ namespace GameHub.UI.Views.GameDetailsView.Blocks private const string CSS_LIGHT = "background: rgb(245, 245, 245); color: rgb(66, 66, 66)"; private const string CSS_DARK = "background: rgb(59, 63, 69); color: white"; - public Description(Game game) + public Description(Game game, bool is_dialog) { - Object(game: game, orientation: Orientation.VERTICAL); + Object(game: game, orientation: Orientation.VERTICAL, is_dialog: is_dialog); } construct diff --git a/src/ui/views/GameDetailsView/blocks/GOGDetails.vala b/src/ui/views/GameDetailsView/blocks/GOGDetails.vala index 0b9532b9..9e92d782 100644 --- a/src/ui/views/GameDetailsView/blocks/GOGDetails.vala +++ b/src/ui/views/GameDetailsView/blocks/GOGDetails.vala @@ -14,9 +14,9 @@ namespace GameHub.UI.Views.GameDetailsView.Blocks { public GameDetailsPage details_page { get; construct; } - public GOGDetails(Game game, GameDetailsPage page) + public GOGDetails(Game game, GameDetailsPage page, bool is_dialog) { - Object(game: game, orientation: Orientation.VERTICAL, details_page: page); + Object(game: game, orientation: Orientation.VERTICAL, details_page: page, is_dialog: is_dialog); } construct diff --git a/src/ui/views/GameDetailsView/blocks/SteamDetails.vala b/src/ui/views/GameDetailsView/blocks/SteamDetails.vala index c318f4fb..83114fe9 100644 --- a/src/ui/views/GameDetailsView/blocks/SteamDetails.vala +++ b/src/ui/views/GameDetailsView/blocks/SteamDetails.vala @@ -12,9 +12,9 @@ namespace GameHub.UI.Views.GameDetailsView.Blocks { public class SteamDetails: GameDetailsBlock { - public SteamDetails(Game game) + public SteamDetails(Game game, bool is_dialog) { - Object(game: game, orientation: Orientation.VERTICAL); + Object(game: game, orientation: Orientation.VERTICAL, is_dialog: is_dialog); } construct diff --git a/src/ui/views/GamesView/FiltersPopover.vala b/src/ui/views/GamesView/FiltersPopover.vala new file mode 100644 index 00000000..24d46d44 --- /dev/null +++ b/src/ui/views/GamesView/FiltersPopover.vala @@ -0,0 +1,95 @@ +using Gtk; +using Gdk; +using Granite; +using GameHub.Data; +using GameHub.Utils; +using GameHub.UI.Widgets; + +namespace GameHub.UI.Views +{ + public class FiltersPopover: Popover + { + public FiltersPopover(Widget? relative_to) + { + Object(relative_to: relative_to); + } + + construct + { + set_size_request(220, -1); + + var vbox = new Box(Orientation.VERTICAL, 0); + + var tags_list = new ListBox(); + tags_list.get_style_context().add_class("tags-list"); + tags_list.selection_mode = SelectionMode.NONE; + + var tags_scrolled = new ScrolledWindow(null, null); + #if GTK_3_22 + tags_scrolled.propagate_natural_width = true; + tags_scrolled.propagate_natural_height = true; + tags_scrolled.max_content_height = 440; + #endif + tags_scrolled.add(tags_list); + tags_scrolled.show_all(); + + var tbox = new Box(Orientation.HORIZONTAL, 8); + tbox.margin_start = tbox.margin_end = 8; + tbox.margin_top = tbox.margin_bottom = 4; + + var check = new CheckButton(); + check.inconsistent = true; + + var header = new HeaderLabel(_("Tags")); + header.halign = Align.START; + header.xalign = 0; + header.hexpand = true; + + tbox.add(check); + tbox.add(header); + + vbox.add(tbox); + vbox.add(new Separator(Orientation.HORIZONTAL)); + vbox.add(tags_scrolled); + + child = vbox; + + foreach(var tag in GamesDB.Tables.Tags.TAGS) + { + tags_list.add(new TagRow(tag)); + } + + show_all(); + } + + public class TagRow: ListBoxRow + { + public GamesDB.Tables.Tags.Tag tag; + + public TagRow(GamesDB.Tables.Tags.Tag tag) + { + this.tag = tag; + + var box = new Box(Orientation.HORIZONTAL, 8); + box.margin_start = box.margin_end = 8; + box.margin_top = box.margin_bottom = 4; + + var check = new CheckButton(); + check.active = true; + + var name = new Label(tag.name); + name.halign = Align.START; + name.xalign = 0; + name.hexpand = true; + + var icon = new Image.from_icon_name(tag.icon, IconSize.BUTTON); + + box.add(check); + box.add(name); + box.add(icon); + + child = box; + } + } + } +} \ No newline at end of file diff --git a/src/ui/views/GamesView/GamesView.vala b/src/ui/views/GamesView/GamesView.vala index 00d1b6fa..a61426e8 100644 --- a/src/ui/views/GamesView/GamesView.vala +++ b/src/ui/views/GamesView/GamesView.vala @@ -41,6 +41,9 @@ namespace GameHub.UI.Views private ListBox downloads_list; private int downloads_count = 0; + private MenuButton filters; + private FiltersPopover filters_popover; + private Settings.UI ui_settings; private Settings.SavedState saved_state; @@ -150,6 +153,13 @@ namespace GameHub.UI.Views downloads.popover = downloads_popover; downloads.sensitive = false; + filters = new MenuButton(); + filters.tooltip_text = _("Filters"); + filters.image = new Image.from_icon_name("tag", IconSize.LARGE_TOOLBAR); + filters_popover = new FiltersPopover(filters); + filters_popover.position = PositionType.BOTTOM; + filters.popover = filters_popover; + search = new SearchEntry(); search.placeholder_text = _("Search"); search.halign = Align.CENTER; @@ -240,6 +250,7 @@ namespace GameHub.UI.Views spinner = new Spinner(); + titlebar.pack_start(filters); titlebar.pack_end(settings); titlebar.pack_end(downloads); titlebar.pack_end(search);