first pass crossplatform ui

This commit is contained in:
Alex Janka 2024-03-03 18:36:51 +11:00
parent 605d4b2abd
commit 3534d986b8
18 changed files with 928 additions and 33 deletions

480
Cargo.lock generated
View file

@ -532,6 +532,30 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21"
[[package]]
name = "cairo-rs"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2650f66005301bd33cc486dec076e1293c4cecf768bc7ba9bf5d2b1be339b99c"
dependencies = [
"bitflags 2.4.2",
"cairo-sys-rs",
"glib",
"libc",
"thiserror",
]
[[package]]
name = "cairo-sys-rs"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3bb3119664efbd78b5e6c93957447944f16bdbced84c17a9f41c7829b81e64"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "calloop"
version = "0.12.4"
@ -583,6 +607,16 @@ dependencies = [
"nom",
]
[[package]]
name = "cfg-expr"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d"
dependencies = [
"smallvec",
"target-lexicon",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -1344,6 +1378,16 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "field-offset"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
dependencies = [
"memoffset 0.9.0",
"rustc_version",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
@ -1578,6 +1622,63 @@ dependencies = [
"wgpu",
]
[[package]]
name = "gdk-pixbuf"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6a23f8a0b5090494fd04924662d463f8386cc678dd3915015a838c1a3679b92"
dependencies = [
"gdk-pixbuf-sys",
"gio",
"glib",
"libc",
]
[[package]]
name = "gdk-pixbuf-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcbd04c1b2c4834cc008b4828bc917d062483b88d26effde6342e5622028f96"
dependencies = [
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "gdk4"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6771942f85a2beaa220c64739395e4401b9fab4a52aba9b503fa1e6ed4d4d806"
dependencies = [
"cairo-rs",
"gdk-pixbuf",
"gdk4-sys",
"gio",
"glib",
"libc",
"pango",
]
[[package]]
name = "gdk4-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1eb95854fab65072023a7814434f003db571d6e45c287c0b0c540c1c78bdf6ae"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"pango-sys",
"pkg-config",
"system-deps",
]
[[package]]
name = "gethostname"
version = "0.4.3"
@ -1651,6 +1752,37 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "gio"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eae10b27b6dd27e22ed0d812c6387deba295e6fc004a8b379e459b663b05a02"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"gio-sys",
"glib",
"libc",
"pin-project-lite",
"smallvec",
"thiserror",
]
[[package]]
name = "gio-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf8e1d9219bb294636753d307b030c1e8a032062cba74f493c431a5c8b81ce4"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"windows-sys 0.52.0",
]
[[package]]
name = "gl_generator"
version = "0.14.0"
@ -1662,6 +1794,60 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "glib"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab9e86540b5d8402e905ad4ce7d6aa544092131ab564f3102175af176b90a053"
dependencies = [
"bitflags 2.4.2",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"memchr",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-build-tools"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "108f374fff60efd14b0d70d8916e7213aed18d7dd071ba3e9334ed2dac1dc86a"
dependencies = [
"gio",
]
[[package]]
name = "glib-macros"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f5897ca27a83e4cdc7b4666850bade0a2e73e17689aabafcc9acddad9d823b8"
dependencies = [
"heck",
"proc-macro-crate 3.1.0",
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "glib-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630f097773d7c7a0bb3258df4e8157b47dc98bbfa0e60ad9ab56174813feced4"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "glob"
version = "0.3.1"
@ -1711,6 +1897,17 @@ dependencies = [
"gl_generator",
]
[[package]]
name = "gobject-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e2b1080b9418dd0c58b498da3a5c826030343e0ef07bde6a955d28de54979"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "goblin"
version = "0.6.1"
@ -1786,17 +1983,131 @@ dependencies = [
"bitflags 2.4.2",
]
[[package]]
name = "graphene-rs"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e4d388e96c5f29e2b2f67045d229ddf826d0a8d6d282f94ed3b34452222c91"
dependencies = [
"glib",
"graphene-sys",
"libc",
]
[[package]]
name = "graphene-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "236ed66cc9b18d8adf233716f75de803d0bf6fc806f60d14d948974a12e240d0"
dependencies = [
"glib-sys",
"libc",
"pkg-config",
"system-deps",
]
[[package]]
name = "gsk4"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e8ce8dee0fd87a11002214b1204ff18c9272fbd530408f0884a0f9b25dc31de"
dependencies = [
"cairo-rs",
"gdk4",
"glib",
"graphene-rs",
"gsk4-sys",
"libc",
"pango",
]
[[package]]
name = "gsk4-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2660a652da5b662d43924df19ba40d73f015ed427329ef51d2b1360a4e0dc0e4"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
"glib-sys",
"gobject-sys",
"graphene-sys",
"libc",
"pango-sys",
"system-deps",
]
[[package]]
name = "gtk4"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d26ffa3ec6316ccaa1df62d3e7f5bae1637c0acbb43f250fabef38319f73c64"
dependencies = [
"cairo-rs",
"field-offset",
"futures-channel",
"gdk-pixbuf",
"gdk4",
"gio",
"glib",
"graphene-rs",
"gsk4",
"gtk4-macros",
"gtk4-sys",
"libc",
"pango",
]
[[package]]
name = "gtk4-macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8b86439e9896f6f3f47c3d8077c5c8205174078760afdabd9098a8e9e937d97"
dependencies = [
"anyhow",
"proc-macro-crate 3.1.0",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "gtk4-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2abc0a6d356d59a3806021829ce6ed3e70bba3509b41a535fedcb09fae13fbc0"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gdk4-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
"graphene-sys",
"gsk4-sys",
"libc",
"pango-sys",
"system-deps",
]
[[package]]
name = "gui"
version = "0.1.0"
dependencies = [
"cacao",
"cpal",
"env_logger",
"frontend-common",
"gb-emu-lib",
"glib-build-tools",
"gtk4",
"libadwaita",
"log",
"objc2 0.3.0-beta.3",
"raw-window-handle 0.6.0",
"serde",
"thiserror",
"twinc_emu_vst",
"uuid 1.7.0",
]
@ -2169,6 +2480,38 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libadwaita"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91b4990248b9e1ec5e72094a2ccaea70ec3809f88f6fd52192f2af306b87c5d9"
dependencies = [
"gdk-pixbuf",
"gdk4",
"gio",
"glib",
"gtk4",
"libadwaita-sys",
"libc",
"pango",
]
[[package]]
name = "libadwaita-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a748e4e92be1265cd9e93d569c0b5dfc7814107985aa6743d670ab281ea1a8"
dependencies = [
"gdk4-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
"gtk4-sys",
"libc",
"pango-sys",
"system-deps",
]
[[package]]
name = "libc"
version = "0.2.152"
@ -2482,6 +2825,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]]
name = "metal"
version = "0.18.0"
@ -2734,7 +3086,7 @@ dependencies = [
"goblin",
"reflink",
"serde",
"toml",
"toml 0.7.8",
]
[[package]]
@ -3058,6 +3410,30 @@ dependencies = [
"ttf-parser",
]
[[package]]
name = "pango"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7809e8af4df8d024a066106b72ca6bc7253a484ae3867041a96103ef8a13188d"
dependencies = [
"gio",
"glib",
"libc",
"pango-sys",
]
[[package]]
name = "pango-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52ef6a881c19fbfe3b1484df5cad411acaaba29dbec843941c3110d19f340ea"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -3259,6 +3635,30 @@ dependencies = [
"toml_edit 0.21.0",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.78"
@ -3538,6 +3938,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.30"
@ -3626,6 +4035,12 @@ dependencies = [
"tiny-skia",
]
[[package]]
name = "semver"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "send_wrapper"
version = "0.6.0"
@ -3829,6 +4244,25 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "system-deps"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml 0.8.10",
"version-compare",
]
[[package]]
name = "target-lexicon"
version = "0.12.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
[[package]]
name = "termcolor"
version = "1.4.1"
@ -3962,6 +4396,18 @@ dependencies = [
"toml_edit 0.19.15",
]
[[package]]
name = "toml"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.22.6",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
@ -3981,7 +4427,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
"winnow 0.5.34",
]
[[package]]
@ -3992,7 +4438,20 @@ checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
dependencies = [
"indexmap 2.1.0",
"toml_datetime",
"winnow",
"winnow 0.5.34",
]
[[package]]
name = "toml_edit"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
dependencies = [
"indexmap 2.1.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.6.2",
]
[[package]]
@ -4127,6 +4586,12 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "version_check"
version = "0.9.4"
@ -4874,6 +5339,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178"
dependencies = [
"memchr",
]
[[package]]
name = "x11"
version = "2.21.0"

View file

@ -4,6 +4,8 @@ default-members = ["cli"]
resolver = "2"
[workspace.dependencies]
log = "0.4.20"
env_logger = "0.11.1"
thiserror = "1.0"
raw-window-handle = "0.6"
gb-emu-lib = { path = "./lib", features = ["config"] }

View file

@ -12,4 +12,4 @@ frontend-common = { workspace = true }
gb-emu-lib = { workspace = true }
clap = { version = "4.4", features = ["derive"] }
ctrlc = "3.4"
log = "0.4.20"
log = { workspace = true }

View file

@ -28,5 +28,5 @@ serde = { version = "1.0", features = ["derive"] }
image = { version = "0.24", default-features = false, features = ["png"] }
bytemuck = "1.14"
chrono = "0.4"
log = "0.4.20"
log = { workspace = true }
env_logger = "0.11.1"

View file

@ -9,18 +9,34 @@ identifier = "com.alexjanka.TWINC.gui"
osx_file_extensions = [[["Game Boy ROM", "Viewer"], ["gb", "gbc"]]]
[features]
default = []
force-crossplatform-ui = []
default = ["macos-ui", "crossplatform-ui", "force-crossplatform-ui"]
macos-ui = ["cacao", "objc", "uuid"]
crossplatform-ui = ["gtk", "adw", "glib-build-tools"]
force-crossplatform-ui = ["crossplatform-ui"]
[dependencies]
adw = { version = "0.6.0", package = "libadwaita", features = [
"v1_4",
"gtk_v4_12",
], optional = true }
frontend-common = { workspace = true }
gb-emu-lib = { workspace = true }
gtk = { version = "0.8.0", package = "gtk4", features = [
"v4_12",
], optional = true }
twinc_emu_vst = { path = "../gb-vst", default-features = false }
[target.'cfg(any(target_os = "macos"))'.dependencies]
cacao = { git = "https://git.alexjanka.com/alex/cacao" }
objc = { version = "=0.3.0-beta.3", package = "objc2" }
uuid = { version = "1.6", features = ["v4", "fast-rng"] }
raw-window-handle = { workspace = true }
cpal = "0.15"
log = { workspace = true }
env_logger = { workspace = true }
thiserror = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
[target.'cfg(any(target_os = "macos"))'.dependencies]
cacao = { git = "https://git.alexjanka.com/alex/cacao", optional = true }
objc = { version = "=0.3.0-beta.3", package = "objc2", optional = true }
uuid = { version = "1.6", features = ["v4", "fast-rng"], optional = true }
[build-dependencies]
glib-build-tools = { version = "0.19.0", optional = true }

8
gui/build.rs Normal file
View file

@ -0,0 +1,8 @@
fn main() {
// actions
glib_build_tools::compile_resources(
&["src/crossplatform/resources"],
"src/crossplatform/resources/resources.gresource.xml",
"crossplatform_templates.gresource",
);
}

19
gui/src/config.rs Normal file
View file

@ -0,0 +1,19 @@
use std::path::PathBuf;
use gb_emu_lib::config::NamedConfig;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(default)]
pub struct GuiConfig {
pub tile_window: bool,
pub layer_window: bool,
pub games_dir: Option<PathBuf>,
pub recursive: bool,
}
impl NamedConfig for GuiConfig {
fn name() -> String {
String::from("gui")
}
}

View file

@ -0,0 +1,26 @@
use std::{cell::RefCell, path::PathBuf};
use glib::Properties;
use gtk::{glib, prelude::*, subclass::prelude::*};
#[derive(Properties, Default)]
#[properties(wrapper_type = super::GameListEntryObject)]
pub struct GameListEntryObject {
#[property(get, set)]
name: RefCell<String>,
#[property(get, set)]
path: RefCell<PathBuf>,
#[property(get, set)]
title: RefCell<String>,
#[property(get, set)]
mbc: RefCell<String>,
}
#[glib::object_subclass]
impl ObjectSubclass for GameListEntryObject {
const NAME: &'static str = "MyGtkAppGameListEntryObject";
type Type = super::GameListEntryObject;
}
#[glib::derived_properties]
impl ObjectImpl for GameListEntryObject {}

View file

@ -0,0 +1,21 @@
mod imp;
use glib::Object;
use gtk::glib;
use crate::gamelist::GameListEntry;
glib::wrapper! {
pub struct GameListEntryObject(ObjectSubclass<imp::GameListEntryObject>);
}
impl GameListEntryObject {
pub fn new(entry: GameListEntry) -> Self {
Object::builder()
.property("name", entry.name)
.property("path", entry.path)
.property("title", entry.header.title)
.property("mbc", entry.header.cartridge_type.to_string())
.build()
}
}

View file

@ -0,0 +1,95 @@
use std::cell::RefCell;
use adw::prelude::*;
use adw::subclass::prelude::*;
use glib::subclass::InitializingObject;
use gtk::{
gio, glib, ColumnView, ColumnViewColumn, CompositeTemplate, ScrolledWindow, SingleSelection,
SortListModel,
};
use crate::crossplatform::{game_list_entry::GameListEntryObject, GameListEntryColumn};
#[derive(CompositeTemplate, Default)]
#[template(resource = "/com/alexjanka/TWINC/game_list_entry_display.ui")]
pub struct GameListWindow {
#[template_child]
pub gamelist: TemplateChild<ScrolledWindow>,
pub games: RefCell<Vec<GameListEntryObject>>,
#[template_child]
pub columnview: TemplateChild<ColumnView>,
#[template_child]
pub title_column: TemplateChild<ColumnViewColumn>,
#[template_child]
pub filename_column: TemplateChild<ColumnViewColumn>,
#[template_child]
pub mbc_column: TemplateChild<ColumnViewColumn>,
}
#[glib::object_subclass]
impl ObjectSubclass for GameListWindow {
const NAME: &'static str = "TwincGameList";
type Type = super::GameListWindow;
type ParentType = gtk::ApplicationWindow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for GameListWindow {
fn constructed(&self) {
self.parent_constructed();
self.obj().set_game_list();
let model = gio::ListStore::new::<GameListEntryObject>();
model.extend_from_slice(&self.games.borrow());
let f1 = GameListEntryColumn::new("title");
let f2 = GameListEntryColumn::new("name");
let f3 = GameListEntryColumn::new("mbc");
self.title_column.set_factory(Some(&f1.factory));
self.title_column.set_sorter(Some(&f1.sorter));
self.filename_column.set_factory(Some(&f2.factory));
self.filename_column.set_sorter(Some(&f2.sorter));
self.mbc_column.set_factory(Some(&f3.factory));
self.mbc_column.set_sorter(Some(&f3.sorter));
self.columnview
.sort_by_column(Some(&self.filename_column), gtk::SortType::Ascending);
let sort_model = SortListModel::new(Some(model), Some(self.columnview.sorter().unwrap()));
let selection_model = SingleSelection::new(Some(sort_model));
self.columnview.set_model(Some(&selection_model));
self.columnview.connect_activate(move |val, _| {
log::info!(
"activated: {:?}",
val.model()
.unwrap()
.downcast_ref::<SingleSelection>()
.unwrap()
.selected_item()
.and_then(move |v| v.downcast::<GameListEntryObject>().ok())
.unwrap()
.path()
)
});
}
}
impl WidgetImpl for GameListWindow {}
impl WindowImpl for GameListWindow {}
impl ApplicationWindowImpl for GameListWindow {}
impl AdwApplicationWindowImpl for GameListWindow {}

View file

@ -0,0 +1,38 @@
mod imp;
use gb_emu_lib::config::CONFIG_MANAGER;
use glib::Object;
use gtk::{
gio,
glib::{self, subclass::types::ObjectSubclassIsExt},
};
use crate::{config::GuiConfig, gamelist::load_games};
use super::game_list_entry::GameListEntryObject;
glib::wrapper! {
pub struct GameListWindow(ObjectSubclass<imp::GameListWindow>)
@extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
@implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
}
impl GameListWindow {
pub fn new(app: &adw::Application) -> Self {
Object::builder().property("application", app).build()
}
pub fn set_game_list(&self) {
self.imp().games.replace(load_and_parse_games());
}
}
fn load_and_parse_games() -> Vec<GameListEntryObject> {
let config: GuiConfig = CONFIG_MANAGER.load_or_create_config();
load_games(config.games_dir.unwrap(), config.recursive)
.unwrap_or_default()
.into_iter()
.map(GameListEntryObject::new)
.collect::<Vec<_>>()
}

View file

@ -0,0 +1,95 @@
use adw::{prelude::*, Application};
use gtk::{gio, glib::ExitCode, CustomSorter, Label, ListItem, SignalListItemFactory};
use thiserror::Error;
use self::{game_list_entry::GameListEntryObject, game_list_entry_display::GameListWindow};
mod game_list_entry;
mod game_list_entry_display;
const APP_ID: &str = "com.alexjanka.TWINC.gui";
pub fn run() -> Result<(), CrossplatformUiError> {
gio::resources_register_include!("crossplatform_templates.gresource")?;
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(build_ui);
match app.run().value() {
v if v == ExitCode::SUCCESS.value() => Ok(()),
val => Err(CrossplatformUiError::GtkError(val)),
}
}
fn build_ui(app: &Application) {
let window = GameListWindow::new(app);
window.present();
}
struct GameListEntryColumn {
factory: SignalListItemFactory,
sorter: CustomSorter,
}
impl GameListEntryColumn {
fn new<T: ToString>(bind_to: T) -> Self {
let factory = SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let label = Label::builder()
.margin_top(2)
.margin_bottom(2)
.margin_start(4)
.margin_end(4)
.build();
list_item
.downcast_ref::<ListItem>()
.unwrap()
.set_child(Some(&label));
});
let bind_to = bind_to.to_string();
{
let bind_to = bind_to.clone();
factory.connect_bind(move |_, list_item| {
let inner_object = list_item
.downcast_ref::<ListItem>()
.unwrap()
.item()
.and_downcast::<GameListEntryObject>()
.unwrap();
let label = list_item
.downcast_ref::<ListItem>()
.unwrap()
.child()
.and_downcast::<Label>()
.unwrap();
label.set_label(&inner_object.property::<String>(&bind_to));
});
}
let sorter = gtk::CustomSorter::new(move |left, right| {
let left = left.downcast_ref::<GameListEntryObject>().unwrap();
let right = right.downcast_ref::<GameListEntryObject>().unwrap();
let left_val: String = left.property(&bind_to);
let right_val: String = right.property(&bind_to);
left_val
.to_lowercase()
.cmp(&right_val.to_lowercase())
.into()
});
Self { factory, sorter }
}
}
#[derive(Debug, Error)]
pub enum CrossplatformUiError {
#[error("GTK error")]
GtkError(i32),
#[error("glib error")]
Glib(#[from] adw::glib::Error),
}

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="TwincGameList" parent="GtkApplicationWindow">
<property name="title">My GTK App</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkScrolledWindow" id="gamelist">
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="hscrollbar-policy">GTK_POLICY_NEVER</property>
<property name="propagate-natural-height">true</property>
<property name="propagate-natural-width">true</property>
<child>
<object class="GtkColumnView" id="columnview">
<child>
<object class="GtkColumnViewColumn" id="title_column">
<property name="title">Title</property>
<property name="fixed-width">150</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="filename_column">
<property name="title">Filename</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="mbc_column">
<property name="title">MBC</property>
<property name="fixed-width">160</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/alexjanka/TWINC/">
<file compressed="true" preprocess="xml-stripblanks">game_list_entry_display.ui</file>
</gresource>
</gresources>

41
gui/src/gamelist/mod.rs Normal file
View file

@ -0,0 +1,41 @@
use std::path::{Path, PathBuf};
use gb_emu_lib::connect::{RomHeader, RomHeaderError};
use thiserror::Error;
#[derive(Debug, Clone)]
pub struct GameListEntry {
pub name: String,
pub path: PathBuf,
pub header: RomHeader,
}
pub fn load_games<P: AsRef<Path>>(
game_dir: P,
recursive: bool,
) -> Result<Vec<GameListEntry>, GameListError> {
let mut games = Vec::new();
for entry in std::fs::read_dir(game_dir)?.flatten() {
if recursive && entry.file_type()?.is_dir() {
if let Ok(mut recursed) = load_games(entry.path(), true) {
games.append(&mut recursed);
}
} else if entry.file_type()?.is_file() {
let name = entry.file_name().to_string_lossy().to_string();
let path = entry.path();
if name.ends_with("gb") || name.ends_with("gbc") {
let header = RomHeader::parse(&std::fs::read(path.clone())?)?;
games.push(GameListEntry { name, path, header });
}
}
}
Ok(games)
}
#[derive(Error, Debug)]
pub enum GameListError {
#[error("fs error")]
Fs(#[from] std::io::Error),
#[error("rom header")]
Header(#[from] RomHeaderError),
}

View file

@ -8,10 +8,11 @@ use cacao::appkit::{App, AppDelegate};
use cacao::filesystem::FileSelectPanel;
use cacao::notification_center::Dispatcher;
use frontend_common::audio;
use gb_emu_lib::config::{NamedConfig, CONFIG_MANAGER};
use gb_emu_lib::config::CONFIG_MANAGER;
use gb_emu_lib::connect::{EmulatorCoreTrait, EmulatorMessage};
use raw_window_handle::{AppKitDisplayHandle, DisplayHandle, RawDisplayHandle};
use serde::{Deserialize, Serialize};
use crate::config::GuiConfig;
use self::cacao_window_manager::{CacaoWindowManager, EmuWindowMessage};
use self::preferences::{PreferencesMessage, PreferencesUi};
@ -35,23 +36,10 @@ pub(crate) enum CoreMessage {
OpenRom(PathBuf),
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(default)]
pub struct MacGuiConfig {
pub tile_window: bool,
pub layer_window: bool,
}
impl NamedConfig for MacGuiConfig {
fn name() -> String {
String::from("mac-gui")
}
}
pub(crate) struct TwincUiApp {
preferences: RwLock<Window<PreferencesUi>>,
current_game: RwLock<CacaoWindowManager>,
gui_config: RwLock<MacGuiConfig>,
gui_config: RwLock<GuiConfig>,
}
impl TwincUiApp {
@ -209,7 +197,7 @@ impl Dispatcher for TwincUiApp {
}
}
fn menu(config: &MacGuiConfig) -> Vec<Menu> {
fn menu(config: &GuiConfig) -> Vec<Menu> {
vec![
Menu::new(
"",

View file

@ -1,7 +1,28 @@
#[cfg(all(target_os = "macos", not(target_feature = "force-crossplatform-ui")))]
#[cfg(not(all(
target_os = "macos",
all(feature = "macos-ui", not(feature = "force-crossplatform-ui"))
)))]
mod crossplatform;
#[cfg(all(target_os = "macos", feature = "macos-ui",))]
mod macos;
mod config;
mod gamelist;
fn main() {
#[cfg(all(target_os = "macos", not(target_feature = "force-crossplatform-ui")))]
cacao::appkit::App::new("com.alexjanka.cacao-test", macos::TwincUiApp::default()).run();
env_logger::init();
#[cfg(not(all(
target_os = "macos",
all(feature = "macos-ui", not(feature = "force-crossplatform-ui"))
)))]
{
crossplatform::run().unwrap();
}
#[cfg(all(
target_os = "macos",
all(feature = "macos-ui", not(feature = "force-crossplatform-ui"))
))]
{
cacao::appkit::App::new("com.alexjanka.cacao-test", macos::TwincUiApp::default()).run();
}
}

View file

@ -53,7 +53,7 @@ ron = { version = "0.8", optional = true }
lazy_static = "1.4"
wgpu = { version = "0.19", optional = true }
thiserror = { workspace = true }
log = "0.4.20"
log = { workspace = true }
[build-dependencies]
naga = { version = "0.19", optional = true, features = ["wgsl-in", "spv-out"] }