feat(engine): art pass — PNG assets, custom font, and keyring v4 upgrade
Art pass (Phase 4): - Generate placeholder PNG assets: face.png, back_0–4.png, bg_0–4.png via solitaire_assetgen gen_art binary (16×16 RGBA, embedded via include_bytes!) - Add FiraMono-Medium font (assets/fonts/main.ttf) embedded at compile time - Add FontPlugin: loads font at startup, exposes FontResource; gracefully falls back to default handle when Assets<Font> absent (MinimalPlugins tests) - Wire CardImageSet into card_plugin: face/back PNGs replace solid-colour sprites when available; tests continue using colour fallback via MinimalPlugins - Wire BackgroundImageSet into table_plugin: bg PNGs replace solid-colour background; empty set inserted when Assets<Image> absent in tests - Fix hint highlight system (input_plugin): tint sprite.color directly instead of replacing the whole Sprite (which would discard the image handle) - Export FontPlugin, FontResource, CardImageSet from solitaire_engine::lib - Register FontPlugin in solitaire_app before other plugins Dependency upgrades (latest releases): - keyring "2" → keyring "4" + keyring-core "1" (v4 split architecture into separate core library crate) - auth_tokens.rs: Entry::new now returns Result; delete_password → delete_credential; NoDefaultStore error variant handled - solitaire_app: add keyring::use_native_store(true) at startup for Linux Secret Service / macOS Keychain / Windows Credential Store selection ARCHITECTURE.md: fix Edition 2025→2021, update asset pipeline section, add FontPlugin/CardImageSet/BackgroundImageSet to plugin and resource tables, update Section 14 to reflect actual include_bytes!() rendering approach, add Decision Log entries for embedded PNG and font decisions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,9 +8,15 @@
|
||||
//! [`TokenError::KeychainUnavailable`] — callers should fall back to prompting
|
||||
//! the user to log in again.
|
||||
//!
|
||||
//! Before calling any function in this module the application must initialise
|
||||
//! the default keyring store exactly once at startup by calling
|
||||
//! `keyring::use_native_store` (e.g. in `solitaire_app::main` before building
|
||||
//! the Bevy `App`). If no default store is set, all operations in this module
|
||||
//! will return [`TokenError::KeychainUnavailable`].
|
||||
//!
|
||||
//! # Note: no unit tests — requires live OS keychain.
|
||||
|
||||
use keyring::Entry;
|
||||
use keyring_core::Entry;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur when reading or writing tokens in the OS keychain.
|
||||
@@ -30,12 +36,13 @@ pub enum TokenError {
|
||||
/// Service name used to namespace all keychain entries for this application.
|
||||
const SERVICE: &str = "solitaire_quest_server";
|
||||
|
||||
/// Map a `keyring::Error` to the appropriate `TokenError`.
|
||||
fn map_keyring_err(err: keyring::Error, username: &str) -> TokenError {
|
||||
/// Map a `keyring_core::Error` to the appropriate `TokenError`.
|
||||
fn map_keyring_err(err: keyring_core::Error, username: &str) -> TokenError {
|
||||
let msg = err.to_string();
|
||||
match err {
|
||||
keyring::Error::NoStorageAccess(_) => TokenError::KeychainUnavailable(msg),
|
||||
keyring::Error::NoEntry => TokenError::NotFound(username.to_string()),
|
||||
keyring_core::Error::NoStorageAccess(_) => TokenError::KeychainUnavailable(msg),
|
||||
keyring_core::Error::NoDefaultStore => TokenError::KeychainUnavailable(msg),
|
||||
keyring_core::Error::NoEntry => TokenError::NotFound(username.to_string()),
|
||||
_ => TokenError::Keyring(msg),
|
||||
}
|
||||
}
|
||||
@@ -88,17 +95,17 @@ pub fn load_refresh_token(username: &str) -> Result<String, TokenError> {
|
||||
pub fn delete_tokens(username: &str) -> Result<(), TokenError> {
|
||||
match Entry::new(SERVICE, &format!("{username}_access"))
|
||||
.map_err(|e| map_keyring_err(e, username))?
|
||||
.delete_password()
|
||||
.delete_credential()
|
||||
{
|
||||
Ok(()) | Err(keyring::Error::NoEntry) => {}
|
||||
Ok(()) | Err(keyring_core::Error::NoEntry) => {}
|
||||
Err(e) => return Err(map_keyring_err(e, username)),
|
||||
}
|
||||
|
||||
match Entry::new(SERVICE, &format!("{username}_refresh"))
|
||||
.map_err(|e| map_keyring_err(e, username))?
|
||||
.delete_password()
|
||||
.delete_credential()
|
||||
{
|
||||
Ok(()) | Err(keyring::Error::NoEntry) => {}
|
||||
Ok(()) | Err(keyring_core::Error::NoEntry) => {}
|
||||
Err(e) => return Err(map_keyring_err(e, username)),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user