diff --git a/package-lock.json b/package-lock.json index 90a3d779..89cac4b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "plugins/template-function-xml", "src-tauri/yaak-crypto", "src-tauri/yaak-git", + "src-tauri/yaak-fonts", "src-tauri/yaak-license", "src-tauri/yaak-mac-window", "src-tauri/yaak-models", @@ -3606,6 +3607,10 @@ "resolved": "src-tauri/yaak-crypto", "link": true }, + "node_modules/@yaakapp-internal/fonts": { + "resolved": "src-tauri/yaak-fonts", + "link": true + }, "node_modules/@yaakapp-internal/git": { "resolved": "src-tauri/yaak-git", "link": true @@ -17294,7 +17299,7 @@ }, "packages/plugin-runtime-types": { "name": "@yaakapp/api", - "version": "0.6.0", + "version": "0.6.4", "dependencies": { "@types/node": "^22.5.4" }, @@ -17543,6 +17548,10 @@ "name": "@yaakapp-internal/crypto", "version": "1.0.0" }, + "src-tauri/yaak-fonts": { + "name": "@yaakapp-internal/fonts", + "version": "1.0.0" + }, "src-tauri/yaak-git": { "name": "@yaakapp-internal/git", "version": "1.0.0" diff --git a/package.json b/package.json index 39249a98..41078f36 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "plugins/template-function-xml", "src-tauri/yaak-crypto", "src-tauri/yaak-git", + "src-tauri/yaak-fonts", "src-tauri/yaak-license", "src-tauri/yaak-mac-window", "src-tauri/yaak-models", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 220454af..6af8a024 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -859,6 +859,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.26.1" @@ -869,7 +878,7 @@ dependencies = [ "block", "cocoa-foundation", "core-foundation 0.10.1", - "core-graphics", + "core-graphics 0.24.0", "foreign-types 0.5.0", "libc", "objc", @@ -884,7 +893,7 @@ dependencies = [ "bitflags 2.9.1", "block", "core-foundation 0.10.1", - "core-graphics-types", + "core-graphics-types 0.2.0", "objc", ] @@ -978,6 +987,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.3.2", + "libc", +] + [[package]] name = "core-graphics" version = "0.24.0" @@ -986,11 +1008,22 @@ checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.9.1", "core-foundation 0.10.1", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "core-graphics-types" version = "0.2.0" @@ -1002,6 +1035,18 @@ dependencies = [ "libc", ] +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation 0.9.4", + "core-graphics 0.22.3", + "foreign-types 0.3.2", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1466,6 +1511,16 @@ dependencies = [ "pin-project", ] +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +dependencies = [ + "cmake", + "pkg-config", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1547,6 +1602,19 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-loader" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" +dependencies = [ + "core-foundation 0.9.4", + "core-text", + "libc", + "servo-fontconfig", + "winapi", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -1598,6 +1666,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -5112,6 +5191,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +dependencies = [ + "libc", + "servo-fontconfig-sys", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +dependencies = [ + "expat-sys", + "freetype-sys", + "pkg-config", +] + [[package]] name = "servo_arc" version = "0.1.1" @@ -5226,7 +5326,7 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "bytemuck", "cfg_aliases", - "core-graphics", + "core-graphics 0.24.0", "foreign-types 0.5.0", "js-sys", "log", @@ -5419,7 +5519,7 @@ checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" dependencies = [ "bitflags 2.9.1", "core-foundation 0.10.1", - "core-graphics", + "core-graphics 0.24.0", "crossbeam-channel", "dispatch", "dlopen2", @@ -7559,6 +7659,7 @@ dependencies = [ "uuid", "yaak-common", "yaak-crypto", + "yaak-fonts", "yaak-git", "yaak-grpc", "yaak-http", @@ -7596,6 +7697,18 @@ dependencies = [ "yaak-models", ] +[[package]] +name = "yaak-fonts" +version = "0.1.0" +dependencies = [ + "font-loader", + "serde", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "ts-rs", +] + [[package]] name = "yaak-git" version = "0.1.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b4ad2ad2..f1527816 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -3,6 +3,7 @@ members = [ "yaak-crypto", "yaak-git", "yaak-grpc", + "yaak-fonts", "yaak-http", "yaak-license", "yaak-mac-window", @@ -75,6 +76,7 @@ yaak-http = { workspace = true } yaak-license = { path = "yaak-license" } yaak-mac-window = { path = "yaak-mac-window" } yaak-models = { workspace = true } +yaak-fonts = { workspace = true } yaak-plugins = { workspace = true } yaak-sse = { workspace = true } yaak-sync = { workspace = true } @@ -95,6 +97,7 @@ ts-rs = "11.0.1" rustls = { version = "0.23.27", default-features = false } rustls-platform-verifier = "0.6.0" yaak-common = { path = "yaak-common" } +yaak-fonts = { path = "yaak-fonts" } yaak-http = { path = "yaak-http" } yaak-models = { path = "yaak-models" } yaak-plugins = { path = "yaak-plugins" } diff --git a/src-tauri/capabilities/capabilities.json b/src-tauri/capabilities/capabilities.json index 456d75d6..993acf6c 100644 --- a/src-tauri/capabilities/capabilities.json +++ b/src-tauri/capabilities/capabilities.json @@ -53,6 +53,7 @@ "shell:allow-open", "yaak-crypto:default", "yaak-git:default", + "yaak-fonts:default", "yaak-license:default", "yaak-mac-window:default", "yaak-models:default", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 69378d54..9f9dffbf 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1265,6 +1265,7 @@ pub fn run() { .plugin(yaak_models::init()) .plugin(yaak_plugins::init()) .plugin(yaak_crypto::init()) + .plugin(yaak_fonts::init()) .plugin(yaak_git::init()) .plugin(yaak_ws::init()) .plugin(yaak_sync::init()); diff --git a/src-tauri/yaak-fonts/Cargo.toml b/src-tauri/yaak-fonts/Cargo.toml new file mode 100644 index 00000000..1828b6c7 --- /dev/null +++ b/src-tauri/yaak-fonts/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "yaak-fonts" +links = "yaak-fonts" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +font-loader = "0.11.0" +tauri = { workspace = true } +ts-rs = { workspace = true } +serde = "1.0" +thiserror = { workspace = true } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } diff --git a/src-tauri/yaak-fonts/bindings/gen_fonts.ts b/src-tauri/yaak-fonts/bindings/gen_fonts.ts new file mode 100644 index 00000000..7d74dd07 --- /dev/null +++ b/src-tauri/yaak-fonts/bindings/gen_fonts.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Fonts = { editorFonts: Array, uiFonts: Array, }; diff --git a/src-tauri/yaak-fonts/build.rs b/src-tauri/yaak-fonts/build.rs new file mode 100644 index 00000000..74869847 --- /dev/null +++ b/src-tauri/yaak-fonts/build.rs @@ -0,0 +1,5 @@ +const COMMANDS: &[&str] = &["list"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS).build(); +} diff --git a/src-tauri/yaak-fonts/index.ts b/src-tauri/yaak-fonts/index.ts new file mode 100644 index 00000000..acb05258 --- /dev/null +++ b/src-tauri/yaak-fonts/index.ts @@ -0,0 +1,14 @@ +import { useQuery } from '@tanstack/react-query'; +import { invoke } from '@tauri-apps/api/core'; +import { Fonts } from './bindings/gen_fonts'; + +export async function listFonts() { + return invoke('plugin:yaak-fonts|list', {}); +} + +export function useFonts() { + return useQuery({ + queryKey: ['list_fonts'], + queryFn: () => listFonts(), + }); +} diff --git a/src-tauri/yaak-fonts/package.json b/src-tauri/yaak-fonts/package.json new file mode 100644 index 00000000..36cb741e --- /dev/null +++ b/src-tauri/yaak-fonts/package.json @@ -0,0 +1,6 @@ +{ + "name": "@yaakapp-internal/fonts", + "private": true, + "version": "1.0.0", + "main": "index.ts" +} diff --git a/src-tauri/yaak-fonts/permissions/default.toml b/src-tauri/yaak-fonts/permissions/default.toml new file mode 100644 index 00000000..0695db5a --- /dev/null +++ b/src-tauri/yaak-fonts/permissions/default.toml @@ -0,0 +1,3 @@ +[default] +description = "Default permissions for the plugin" +permissions = ["allow-list"] diff --git a/src-tauri/yaak-fonts/src/commands.rs b/src-tauri/yaak-fonts/src/commands.rs new file mode 100644 index 00000000..ea19c27a --- /dev/null +++ b/src-tauri/yaak-fonts/src/commands.rs @@ -0,0 +1,41 @@ +use crate::Result; +use font_loader::system_fonts; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use tauri::command; +use ts_rs::TS; + +#[derive(Default, Debug, Clone, Serialize, Deserialize, TS, PartialEq)] +#[serde(rename_all = "camelCase")] +#[ts(export, export_to = "gen_fonts.ts")] +pub struct Fonts { + pub editor_fonts: Vec, + pub ui_fonts: Vec, +} + +#[command] +pub(crate) async fn list() -> Result { + let mut ui_fonts = HashSet::new(); + let mut editor_fonts = HashSet::new(); + + let mut property = system_fonts::FontPropertyBuilder::new().monospace().build(); + for font in &system_fonts::query_specific(&mut property) { + editor_fonts.insert(font.to_string()); + } + for font in &system_fonts::query_all() { + if !editor_fonts.contains(font) { + ui_fonts.insert(font.to_string()); + } + } + + let mut ui_fonts: Vec = ui_fonts.into_iter().collect(); + let mut editor_fonts: Vec = editor_fonts.into_iter().collect(); + + ui_fonts.sort(); + editor_fonts.sort(); + + Ok(Fonts { + ui_fonts, + editor_fonts, + }) +} diff --git a/src-tauri/yaak-fonts/src/error.rs b/src-tauri/yaak-fonts/src/error.rs new file mode 100644 index 00000000..651398d5 --- /dev/null +++ b/src-tauri/yaak-fonts/src/error.rs @@ -0,0 +1,15 @@ +use serde::{ser::Serializer, Serialize}; + +#[derive(Debug, thiserror::Error)] +pub enum Error {} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +pub type Result = std::result::Result; diff --git a/src-tauri/yaak-fonts/src/lib.rs b/src-tauri/yaak-fonts/src/lib.rs new file mode 100644 index 00000000..effb4a86 --- /dev/null +++ b/src-tauri/yaak-fonts/src/lib.rs @@ -0,0 +1,15 @@ +use tauri::{ + generate_handler, + plugin::{Builder, TauriPlugin}, + Runtime, +}; + +mod commands; +mod error; + +use crate::commands::list; +pub use error::{Error, Result}; + +pub fn init() -> TauriPlugin { + Builder::new("yaak-fonts").invoke_handler(generate_handler![list]).build() +} diff --git a/src-tauri/yaak-models/bindings/gen_models.ts b/src-tauri/yaak-models/bindings/gen_models.ts index c441825f..9e6c95cf 100644 --- a/src-tauri/yaak-models/bindings/gen_models.ts +++ b/src-tauri/yaak-models/bindings/gen_models.ts @@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", disabled: boolean, http: string, export type ProxySettingAuth = { user: string, password: string, }; -export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, coloredMethods: boolean, }; +export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, }; export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; diff --git a/src-tauri/yaak-models/migrations/20250608150053_font-settings.sql b/src-tauri/yaak-models/migrations/20250608150053_font-settings.sql new file mode 100644 index 00000000..8cb83e72 --- /dev/null +++ b/src-tauri/yaak-models/migrations/20250608150053_font-settings.sql @@ -0,0 +1,2 @@ +ALTER TABLE settings ADD COLUMN interface_font TEXT; +ALTER TABLE settings ADD COLUMN editor_font TEXT; diff --git a/src-tauri/yaak-models/src/models.rs b/src-tauri/yaak-models/src/models.rs index 8cd337b6..2750f568 100644 --- a/src-tauri/yaak-models/src/models.rs +++ b/src-tauri/yaak-models/src/models.rs @@ -103,9 +103,13 @@ pub struct Settings { pub updated_at: NaiveDateTime, pub appearance: String, + pub colored_methods: bool, + pub editor_font: Option, pub editor_font_size: i32, + pub editor_keymap: EditorKeymap, pub editor_soft_wrap: bool, pub hide_window_controls: bool, + pub interface_font: Option, pub interface_font_size: i32, pub interface_scale: f32, pub open_workspace_new_window: Option, @@ -113,8 +117,6 @@ pub struct Settings { pub theme_dark: String, pub theme_light: String, pub update_channel: String, - pub editor_keymap: EditorKeymap, - pub colored_methods: bool, } impl UpsertModelInfo for Settings { @@ -154,6 +156,8 @@ impl UpsertModelInfo for Settings { (EditorFontSize, self.editor_font_size.into()), (EditorKeymap, self.editor_keymap.to_string().into()), (EditorSoftWrap, self.editor_soft_wrap.into()), + (EditorFont, self.editor_font.into()), + (InterfaceFont, self.interface_font.into()), (InterfaceFontSize, self.interface_font_size.into()), (InterfaceScale, self.interface_scale.into()), (HideWindowControls, self.hide_window_controls.into()), @@ -173,8 +177,10 @@ impl UpsertModelInfo for Settings { SettingsIden::EditorFontSize, SettingsIden::EditorKeymap, SettingsIden::EditorSoftWrap, + SettingsIden::EditorFont, SettingsIden::InterfaceFontSize, SettingsIden::InterfaceScale, + SettingsIden::InterfaceFont, SettingsIden::HideWindowControls, SettingsIden::OpenWorkspaceNewWindow, SettingsIden::Proxy, @@ -198,10 +204,12 @@ impl UpsertModelInfo for Settings { updated_at: row.get("updated_at")?, appearance: row.get("appearance")?, editor_font_size: row.get("editor_font_size")?, + editor_font: row.get("editor_font")?, editor_keymap: EditorKeymap::from_str(editor_keymap.as_str()).unwrap(), editor_soft_wrap: row.get("editor_soft_wrap")?, interface_font_size: row.get("interface_font_size")?, interface_scale: row.get("interface_scale")?, + interface_font: row.get("interface_font")?, open_workspace_new_window: row.get("open_workspace_new_window")?, proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }), theme_dark: row.get("theme_dark")?, diff --git a/src-tauri/yaak-models/src/queries/settings.rs b/src-tauri/yaak-models/src/queries/settings.rs index e5090e52..85b273fa 100644 --- a/src-tauri/yaak-models/src/queries/settings.rs +++ b/src-tauri/yaak-models/src/queries/settings.rs @@ -19,10 +19,12 @@ impl<'a> DbContext<'a> { appearance: "system".to_string(), editor_font_size: 13, + editor_font: None, editor_keymap: EditorKeymap::Default, editor_soft_wrap: true, interface_font_size: 15, interface_scale: 1.0, + interface_font: None, hide_window_controls: false, open_workspace_new_window: None, proxy: None, diff --git a/src-web/components/Settings/SettingsAppearance.tsx b/src-web/components/Settings/SettingsAppearance.tsx index c1584a56..c0ce7bee 100644 --- a/src-web/components/Settings/SettingsAppearance.tsx +++ b/src-web/components/Settings/SettingsAppearance.tsx @@ -1,3 +1,5 @@ +import { type } from '@tauri-apps/plugin-os'; +import { useFonts } from '@yaakapp-internal/fonts'; import type { EditorKeymap } from '@yaakapp-internal/models'; import { patchModel, settingsAtom } from '@yaakapp-internal/models'; import { useAtomValue } from 'jotai'; @@ -14,11 +16,11 @@ import { Editor } from '../core/Editor/Editor'; import type { IconProps } from '../core/Icon'; import { Icon } from '../core/Icon'; import { IconButton } from '../core/IconButton'; -import type { SelectProps } from '../core/Select'; -import { Select } from '../core/Select'; +import { Select, SelectProps } from '../core/Select'; import { Separator } from '../core/Separator'; import { HStack, VStack } from '../core/Stacks'; -import { type } from '@tauri-apps/plugin-os'; + +const NULL_FONT_VALUE = '__NULL_FONT__'; const fontSizeOptions = [ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, @@ -67,6 +69,7 @@ export function SettingsAppearance() { const settings = useAtomValue(settingsAtom); const appearance = useResolvedAppearance(); const activeTheme = useResolvedTheme(); + const fonts = useFonts(); if (settings == null || workspace == null) { return null; @@ -87,46 +90,97 @@ export function SettingsAppearance() { })); return ( - - patchModel(settings, { editorFontSize: clamp(parseInt(v) || 14, 8, 30) })} - /> + + + {fonts.data && ( + patchModel(settings, { interfaceFontSize: parseInt(v) })} + /> + + + {fonts.data && ( + + patchModel(settings, { editorFontSize: clamp(parseInt(v) || 14, 8, 30) }) + } + /> +