Create, populate, and start using the Application Context

This commit is contained in:
Savanni D'Gerinel 2024-03-11 19:37:15 -04:00
parent ff1d117e8c
commit 4114e64b8e
9 changed files with 67 additions and 14 deletions

2
Cargo.lock generated
View File

@ -2709,6 +2709,8 @@ dependencies = [
"libadwaita",
"pango",
"sgf",
"sys-locale",
"thiserror",
"tokio",
]

View File

@ -13,8 +13,8 @@ adw = { version = "0.5", package = "libadwaita", features = [ "v1_2"
async-channel = { version = "2" }
async-std = { version = "1" }
cairo-rs = { version = "0.18" }
fluent = { version = "0.16" }
fluent-ergonomics = { path = "../../fluent-ergonomics" }
fluent = { version = "0.16" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] }
@ -23,6 +23,8 @@ kifu-core = { path = "../core" }
l10n = { path = "../../l10n" }
pango = { version = "*" }
sgf = { path = "../../sgf" }
sys-locale = { version = "0.3" }
thiserror = { version = "1" }
tokio = { version = "1.26", features = [ "full" ] }
[build-dependencies]

View File

@ -1,3 +1,9 @@
use async_std::task::yield_now;
use kifu_core::{Core, CoreRequest, Observable};
use l10n::{NonEmptyList, L10N};
use std::{rc::Rc, sync::Arc};
use tokio::runtime::Runtime;
mod messages {
include!(env!("KIFU_GTK_MESSAGES"));
}
@ -7,11 +13,6 @@ pub mod ui;
mod view_models;
mod views;
use async_std::task::yield_now;
use kifu_core::{Core, CoreRequest, CoreResponse, Observable};
use std::{rc::Rc, sync::Arc};
use tokio::runtime::Runtime;
#[derive(Clone)]
pub struct CoreApi {
pub rt: Arc<Runtime>,
@ -43,6 +44,45 @@ where
result
}
/// AppContext is our global store for things that need to be available everywhere. Developers
/// should never pass this around directly, but should instead pass it around by traits. This is to
/// provide systems such as:
///
/// - Feature Flags
/// - L10N
pub struct AppContext {
pub l10n: L10N,
}
#[derive(Debug)]
pub enum AppContextInitError {}
impl AppContext {
pub fn new() -> Result<Self, AppContextInitError> {
let mut locale_list = NonEmptyList::new("en-US");
let user_locale = sys_locale::get_locale().unwrap();
println!("user locale: {}", user_locale);
if locale_list.find(|l| *l == user_locale).is_none() {
locale_list.push(&user_locale);
}
let mut l10n = l10n::L10N::new("messages".into());
l10n.set_locales(locale_list);
Ok(Self { l10n })
}
}
pub trait ProvidesL10N {
fn l10n(&self) -> &L10N;
}
impl ProvidesL10N for AppContext {
fn l10n(&self) -> &L10N {
&self.l10n
}
}
/// LocalObserver creates a task on the current thread which watches the specified observer for notifications and calls the handler function with each one.
///
/// The LocalObserver starts a task which listens for notifications during the constructor. When the observer goes out of scope, it will make a point of aborting the task. This combination means that anything which uses the observer can create it, hold on to a reference of it, and then drop it when done, and not have to do anything else with the observer object.

View File

@ -3,8 +3,9 @@ use kifu_core::{Config, ConfigOption, Core, CoreRequest, CoreResponse, DatabaseP
use kifu_gtk::{
perftrace,
ui::{AppWindow, ConfigurationPage, Home, PlayingField},
CoreApi,
AppContext, CoreApi,
};
use l10n::NonEmptyList;
use std::sync::{Arc, RwLock};
const APP_ID_DEV: &str = "com.luminescent-dreams.kifu-gtk.dev";
@ -104,7 +105,9 @@ fn main() {
app.connect_activate({
let runtime = runtime.clone();
move |app| {
let app_window = AppWindow::new(app);
let ctx = AppContext::new().unwrap();
let app_window = AppWindow::new(app, &ctx);
let api = CoreApi {
rt: runtime.clone(),

View File

@ -4,7 +4,7 @@ use glib::IsA;
use gtk::STYLE_PROVIDER_PRIORITY_USER;
use l10n::{L10N, NonEmptyList};
use std::path::PathBuf;
use crate::messages;
use crate::{ProvidesL10N, messages};
mod chat;
pub use chat::Chat;
@ -40,7 +40,7 @@ pub struct AppWindow {
}
impl AppWindow {
pub fn new(app: &adw::Application) -> Self {
pub fn new(app: &adw::Application, ctx: &impl ProvidesL10N) -> Self {
let window = adw::ApplicationWindow::builder()
.application(app)
.width_request(800)
@ -78,10 +78,8 @@ impl AppWindow {
header.pack_end(&hamburger);
let content = adw::Bin::builder().css_classes(vec!["content"]).build();
let mut l10n = L10N::new(PathBuf::from("resources"));
l10n.set_locales(NonEmptyList::from_iter(vec!["en-US", "eo"]).unwrap());
content.set_child(Some(
&adw::StatusPage::builder().title(l10n.tr(messages::NothingHere)).build(),
&adw::StatusPage::builder().title(ctx.l10n().tr(messages::NothingHere)).build(),
));
let layout = gtk::Box::builder()

View File

@ -22,7 +22,7 @@ pub enum NonEmptyListError {
pub struct NonEmptyList<A>(Vec<A>);
impl<A> NonEmptyList<A> {
fn new(elem: A) -> Self {
pub fn new(elem: A) -> Self {
Self(vec![elem])
}
@ -37,6 +37,14 @@ impl<A> NonEmptyList<A> {
}
}
pub fn push(&mut self, item: A) {
self.0.push(item);
}
pub fn find(&self, f: impl Fn(&A) -> bool) -> Option<&A> {
self.0.iter().find(|item| f(*item))
}
fn first(&self) -> &A {
&self.0[0]
}