first pass preferences

This commit is contained in:
Alex Janka 2023-10-30 13:34:38 +11:00
parent 76a3260001
commit 1c313530e9
8 changed files with 604 additions and 8 deletions

167
Cargo.lock generated
View file

@ -371,6 +371,16 @@ dependencies = [
"serde",
]
[[package]]
name = "bitmask-enum"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49fb8528abca6895a5ada33d62aedd538a5c33e77068256483b44a3230270163"
dependencies = [
"quote",
"syn 2.0.38",
]
[[package]]
name = "blake3"
version = "1.5.0"
@ -439,6 +449,25 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cacao"
version = "0.4.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6de2bcb2324367ffb6ea53f977fab8234fa59e9a57e88f0f7be1ad7cb425c7b9"
dependencies = [
"bitmask-enum",
"block",
"core-foundation 0.9.3",
"core-graphics 0.23.1",
"dispatch",
"lazy_static",
"libc",
"objc",
"objc_id",
"os_info",
"url",
]
[[package]]
name = "cache-padded"
version = "1.3.0"
@ -593,7 +622,7 @@ dependencies = [
"block",
"core-foundation 0.7.0",
"core-graphics 0.19.2",
"foreign-types",
"foreign-types 0.3.2",
"libc",
"objc",
]
@ -609,7 +638,7 @@ dependencies = [
"cocoa-foundation",
"core-foundation 0.9.3",
"core-graphics 0.22.3",
"foreign-types",
"foreign-types 0.3.2",
"libc",
"objc",
]
@ -712,7 +741,7 @@ checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923"
dependencies = [
"bitflags 1.3.2",
"core-foundation 0.7.0",
"foreign-types",
"foreign-types 0.3.2",
"libc",
]
@ -725,7 +754,20 @@ dependencies = [
"bitflags 1.3.2",
"core-foundation 0.9.3",
"core-graphics-types",
"foreign-types",
"foreign-types 0.3.2",
"libc",
]
[[package]]
name = "core-graphics"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212"
dependencies = [
"bitflags 1.3.2",
"core-foundation 0.9.3",
"core-graphics-types",
"foreign-types 0.5.0",
"libc",
]
@ -1134,7 +1176,28 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
"foreign-types-shared 0.1.1",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared 0.3.1",
]
[[package]]
name = "foreign-types-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
@ -1143,6 +1206,21 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.29"
@ -1246,6 +1324,7 @@ name = "gb-emu"
version = "0.5.0"
dependencies = [
"bytemuck",
"cacao",
"chrono",
"clap",
"cpal",
@ -1255,6 +1334,7 @@ dependencies = [
"gilrs",
"image",
"nokhwa",
"objc",
"raw-window-handle",
"send_wrapper",
"serde",
@ -1527,6 +1607,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "image"
version = "0.24.7"
@ -1989,7 +2079,7 @@ dependencies = [
"block",
"cocoa 0.20.2",
"core-graphics 0.19.2",
"foreign-types",
"foreign-types 0.3.2",
"log",
"objc",
]
@ -2003,7 +2093,7 @@ dependencies = [
"bitflags 1.3.2",
"block",
"core-graphics-types",
"foreign-types",
"foreign-types 0.3.2",
"log",
"objc",
]
@ -2499,6 +2589,15 @@ dependencies = [
"cc",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]]
name = "object"
version = "0.32.1"
@ -2552,6 +2651,17 @@ dependencies = [
"redox_syscall 0.3.5",
]
[[package]]
name = "os_info"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
dependencies = [
"log",
"serde",
"winapi",
]
[[package]]
name = "owned_ttf_parser"
version = "0.19.0"
@ -3391,6 +3501,21 @@ dependencies = [
"strict-num",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.7.8"
@ -3454,12 +3579,27 @@ dependencies = [
"wide",
]
[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
@ -3478,6 +3618,17 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "url"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf8parse"
version = "0.2.1"
@ -3792,7 +3943,7 @@ dependencies = [
"block",
"core-graphics-types",
"d3d12",
"foreign-types",
"foreign-types 0.3.2",
"fxhash",
"glow",
"gpu-alloc",

View file

@ -6,6 +6,10 @@ description = "TWINC Game Boy (CGB/DMG) emulator"
[package.metadata.bundle]
identifier = "com.alexjanka.TWINC"
[package.metadata.bundle.bin.cli]
identifier = "com.alexjanka.TWINC.cli"
[package.metadata.bundle.bin.gui]
identifier = "com.alexjanka.TWINC.gui"
[features]
default = ["vulkan-static"]
@ -36,3 +40,7 @@ serde = { version = "1.0", features = ["derive"] }
image = { version = "0.24", default-features = false, features = ["png"] }
bytemuck = "1.14"
chrono = "0.4"
[target.'cfg(any(target_os = "macos"))'.dependencies]
cacao = "0.4.0-beta2"
objc = "0.2"

7
gb-emu/src/bin/gui.rs Normal file
View file

@ -0,0 +1,7 @@
#[cfg(target_os = "macos")]
mod macos;
fn main() {
#[cfg(target_os = "macos")]
cacao::appkit::App::new("com.alexjanka.cacao-test", macos::TwincUiApp::default()).run();
}

113
gb-emu/src/bin/macos/mod.rs Normal file
View file

@ -0,0 +1,113 @@
use cacao::appkit::menu::{Menu, MenuItem};
use cacao::appkit::window::{Window, WindowConfig, WindowStyle, WindowToolbarStyle};
use cacao::appkit::{App, AppDelegate};
use cacao::notification_center::Dispatcher;
use self::preferences::{PreferencesMessage, PreferencesUi};
mod preferences;
pub(crate) enum AppMessage {
Core(CoreMessage),
Preferences(PreferencesMessage),
}
pub(crate) enum CoreMessage {
Open,
OpenPreferences,
}
pub(crate) struct TwincUiApp {
preferences: Window<PreferencesUi>,
}
impl Default for TwincUiApp {
fn default() -> Self {
Self {
preferences: Window::with(
{
let mut config = WindowConfig::default();
config.set_initial_dimensions(100., 100., 400., 400.);
config.set_styles(&[
WindowStyle::Resizable,
WindowStyle::Miniaturizable,
WindowStyle::Closable,
WindowStyle::Titled,
]);
config.toolbar_style = WindowToolbarStyle::Preferences;
config
},
PreferencesUi::new(),
),
}
}
}
impl AppDelegate for TwincUiApp {
fn did_finish_launching(&self) {
App::set_menu(menu());
App::activate();
}
}
impl Dispatcher for TwincUiApp {
type Message = AppMessage;
fn on_ui_message(&self, message: Self::Message) {
match message {
AppMessage::Core(CoreMessage::Open) => println!("open"),
AppMessage::Core(CoreMessage::OpenPreferences) => {
self.preferences.show();
}
AppMessage::Preferences(prefs_message) => {
if let Some(delegate) = &self.preferences.delegate {
delegate.message(prefs_message)
}
}
}
}
}
fn menu() -> Vec<Menu> {
vec![
Menu::new(
"",
vec![
MenuItem::About("Cacao Test".to_string()),
MenuItem::Separator,
MenuItem::new("Preferences")
.key(",")
.action(|| dispatch(AppMessage::Core(CoreMessage::OpenPreferences))),
MenuItem::Separator,
MenuItem::Services,
MenuItem::Separator,
MenuItem::Hide,
MenuItem::HideOthers,
MenuItem::ShowAll,
MenuItem::Separator,
MenuItem::Quit,
],
),
Menu::new(
"File",
vec![MenuItem::new("Open")
.key("o")
.action(|| dispatch(AppMessage::Core(CoreMessage::Open)))],
),
Menu::new(
"Window",
vec![
MenuItem::Minimize,
MenuItem::Separator,
MenuItem::new("Bring All to Front"),
],
),
Menu::new("Help", vec![]),
]
}
fn dispatch(message: AppMessage) {
App::<TwincUiApp, AppMessage>::dispatch_main(message);
}

View file

@ -0,0 +1,80 @@
use cacao::{
appkit::{
toolbar::Toolbar,
window::{Window, WindowDelegate},
},
view::ViewController,
};
use self::{
toolbar::PreferencesToolbar,
views::{
CorePreferencesContentView, StandalonePreferencesContentView, VstPreferencesContentView,
},
};
mod toolbar;
mod views;
pub(crate) enum PreferencesMessage {
SwitchPane(PreferencesPane),
}
pub(crate) enum PreferencesPane {
Core,
Standalone,
Vst,
}
pub(crate) struct PreferencesUi {
pub(crate) toolbar: Toolbar<PreferencesToolbar>,
pub(crate) core_prefs: ViewController<CorePreferencesContentView>,
pub(crate) standalone_prefs: ViewController<StandalonePreferencesContentView>,
pub(crate) vst_prefs: ViewController<VstPreferencesContentView>,
window: Option<Window>,
}
impl PreferencesUi {
pub(crate) fn new() -> Self {
Self {
toolbar: Toolbar::new("PreferencesToolbar", PreferencesToolbar::default()),
core_prefs: ViewController::new(CorePreferencesContentView::default()),
standalone_prefs: ViewController::new(StandalonePreferencesContentView::default()),
vst_prefs: ViewController::new(VstPreferencesContentView::default()),
window: None,
}
}
pub(crate) fn message(&self, message: PreferencesMessage) {
let window = self.window.as_ref().unwrap();
match message {
PreferencesMessage::SwitchPane(PreferencesPane::Core) => {
window.set_title("Core");
window.set_content_view_controller(&self.core_prefs);
}
PreferencesMessage::SwitchPane(PreferencesPane::Standalone) => {
window.set_title("Standalone");
window.set_content_view_controller(&self.standalone_prefs);
}
PreferencesMessage::SwitchPane(PreferencesPane::Vst) => {
window.set_title("VST");
window.set_content_view_controller(&self.vst_prefs);
}
}
}
}
impl WindowDelegate for PreferencesUi {
const NAME: &'static str = "PreferencesUi";
fn did_load(&mut self, window: Window) {
window.set_autosave_name("PreferencesWindow");
window.set_movable_by_background(true);
window.set_toolbar(&self.toolbar);
self.window = Some(window);
self.message(PreferencesMessage::SwitchPane(PreferencesPane::Core));
}
}

View file

@ -0,0 +1,109 @@
use cacao::{
appkit::toolbar::{ItemIdentifier, ToolbarDelegate, ToolbarItem},
image::{Image, MacSystemIcon},
};
use crate::macos::{dispatch, AppMessage};
use super::{PreferencesMessage, PreferencesPane};
pub(crate) struct PreferencesToolbar {
core: ToolbarItem,
standalone: ToolbarItem,
vst: ToolbarItem,
}
impl Default for PreferencesToolbar {
fn default() -> Self {
Self {
core: {
let mut item = ToolbarItem::new("core");
item.set_title("Core");
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesGeneral, "Core");
item.set_image(icon);
item.set_action(|_| {
dispatch(AppMessage::Preferences(PreferencesMessage::SwitchPane(
PreferencesPane::Core,
)));
});
item
},
standalone: {
let mut item = ToolbarItem::new("standalone");
item.set_title("Standalone");
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesGeneral, "Standalone");
item.set_image(icon);
item.set_action(|_| {
dispatch(AppMessage::Preferences(PreferencesMessage::SwitchPane(
PreferencesPane::Standalone,
)));
});
item
},
vst: {
let mut item = ToolbarItem::new("vst");
item.set_title("VST");
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesGeneral, "VST");
item.set_image(icon);
item.set_action(|_| {
dispatch(AppMessage::Preferences(PreferencesMessage::SwitchPane(
PreferencesPane::Vst,
)));
});
item
},
}
}
}
impl ToolbarDelegate for PreferencesToolbar {
const NAME: &'static str = "PreferencesToolbar";
fn did_load(&mut self, toolbar: cacao::appkit::toolbar::Toolbar) {
toolbar.set_selected("core")
}
fn allowed_item_identifiers(&self) -> Vec<cacao::appkit::toolbar::ItemIdentifier> {
vec![
ItemIdentifier::Custom("core"),
ItemIdentifier::Custom("standalone"),
ItemIdentifier::Custom("vst"),
]
}
fn default_item_identifiers(&self) -> Vec<cacao::appkit::toolbar::ItemIdentifier> {
vec![
ItemIdentifier::Custom("core"),
ItemIdentifier::Custom("standalone"),
ItemIdentifier::Custom("vst"),
]
}
fn selectable_item_identifiers(&self) -> Vec<ItemIdentifier> {
vec![
ItemIdentifier::Custom("core"),
ItemIdentifier::Custom("standalone"),
ItemIdentifier::Custom("vst"),
]
}
fn item_for(&self, identifier: &str) -> &cacao::appkit::toolbar::ToolbarItem {
match identifier {
"core" => &self.core,
"standalone" => &self.standalone,
"vst" => &self.vst,
_ => {
unreachable!();
}
}
}
}

View file

@ -0,0 +1,65 @@
use cacao::{
layout::{Layout, LayoutConstraint},
view::{View, ViewDelegate},
};
use self::widgets::ToggleOptionView;
mod widgets;
#[derive(Default)]
pub(crate) struct CorePreferencesContentView {
pub example_option: ToggleOptionView,
}
impl ViewDelegate for CorePreferencesContentView {
const NAME: &'static str = "CorePreferencesContentView";
fn did_load(&mut self, view: View) {
self.example_option.configure(
"An example preference",
"This can be true, or it can be false.",
false, // initial value
|_v| {},
);
view.add_subview(&self.example_option.view);
LayoutConstraint::activate(&[
self.example_option
.view
.top
.constraint_equal_to(&view.top)
.offset(22.),
self.example_option
.view
.leading
.constraint_equal_to(&view.leading)
.offset(22.),
self.example_option
.view
.trailing
.constraint_equal_to(&view.trailing)
.offset(-22.),
self.example_option
.view
.bottom
.constraint_equal_to(&view.bottom)
.offset(-22.),
]);
}
}
#[derive(Default)]
pub(crate) struct StandalonePreferencesContentView {}
impl ViewDelegate for StandalonePreferencesContentView {
const NAME: &'static str = "StandalonePreferencesContentView";
}
#[derive(Default)]
pub(crate) struct VstPreferencesContentView {}
impl ViewDelegate for VstPreferencesContentView {
const NAME: &'static str = "VstPreferencesContentView";
}

View file

@ -0,0 +1,63 @@
use cacao::layout::{Layout, LayoutConstraint};
use cacao::switch::Switch;
use cacao::text::Label;
use cacao::view::View;
use objc::runtime::Object;
#[derive(Debug)]
pub struct ToggleOptionView {
pub view: View,
pub switch: Switch,
pub title: Label,
pub subtitle: Label,
}
impl Default for ToggleOptionView {
fn default() -> Self {
let view = View::new();
let switch = Switch::new("");
view.add_subview(&switch);
let title = Label::new();
view.add_subview(&title);
let subtitle = Label::new();
view.add_subview(&subtitle);
LayoutConstraint::activate(&[
switch.top.constraint_equal_to(&view.top),
switch.leading.constraint_equal_to(&view.leading),
switch.width.constraint_equal_to_constant(24.),
title.top.constraint_equal_to(&view.top),
title.leading.constraint_equal_to(&switch.trailing),
title.trailing.constraint_equal_to(&view.trailing),
subtitle.top.constraint_equal_to(&title.bottom),
subtitle.leading.constraint_equal_to(&switch.trailing),
subtitle.trailing.constraint_equal_to(&view.trailing),
subtitle.bottom.constraint_equal_to(&view.bottom),
subtitle
.width
.constraint_greater_than_or_equal_to_constant(200.),
]);
ToggleOptionView {
view,
switch,
title,
subtitle,
}
}
}
impl ToggleOptionView {
pub fn configure<F>(&mut self, text: &str, subtitle: &str, state: bool, handler: F)
where
F: Fn(*const Object) + Send + Sync + 'static,
{
self.title.set_text(text);
self.subtitle.set_text(subtitle);
self.switch.set_action(handler);
self.switch.set_checked(state);
}
}