cosmic-settings/cosmic-settings/src/pages/desktop/appearance/font_config.rs
2026-05-15 16:26:20 -06:00

223 lines
6.9 KiB
Rust

// Copyright 2024 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use std::sync::Arc;
use cosmic::config::{CosmicTk, FontConfig};
use cosmic::{Apply, Element, Task, widget};
use cosmic_config::ConfigSet;
use crate::app;
use crate::widget::selection_context_item;
use super::{ContextView, Message, drawer};
const INTERFACE_FONT: &str = "interface_font";
const MONOSPACE_FONT: &str = "monospace_font";
pub fn load_font_families() -> (Vec<Arc<str>>, Vec<Arc<str>>) {
let mut font_system = cosmic::iced::advanced::graphics::text::font_system()
.write()
.unwrap();
let (mut interface, mut mono): (Vec<Arc<str>>, Vec<Arc<str>>) =
font_system.raw().db().faces().fold(
(Vec::new(), Vec::new()),
|(mut interface, mut mono), face| {
if face.stretch != fontdb::Stretch::Normal || face.style != fontdb::Style::Normal {
return (interface, mono);
}
let font_name = match face.families.first() {
Some(name) => &name.0,
None => return (interface, mono),
};
if face.monospaced {
if mono.last().is_none_or(|name| &**name != font_name.as_str()) {
mono.push(Arc::from(font_name.as_str()));
}
} else if interface
.last()
.is_none_or(|name| &**name != font_name.as_str())
{
interface.push(Arc::from(font_name.as_str()));
}
(interface, mono)
},
);
interface.sort_unstable();
interface.dedup();
mono.sort_unstable();
mono.dedup();
(interface, mono)
}
#[derive(Debug)]
pub struct Model {
font_search: String,
font_filter: Vec<Arc<str>>,
interface_font_families: Vec<Arc<str>>,
pub interface_font: FontConfig,
monospace_font_families: Vec<Arc<str>>,
pub monospace_font: FontConfig,
}
impl Default for Model {
fn default() -> Self {
Self::new()
}
}
impl Model {
pub fn new() -> Model {
Model {
font_filter: Vec::new(),
font_search: String::new(),
interface_font_families: Vec::new(),
interface_font: cosmic::config::interface_font(),
monospace_font_families: Vec::new(),
monospace_font: cosmic::config::monospace_font(),
}
}
pub fn reset(&mut self) {
self.font_search.clear();
self.font_filter.clear();
}
pub fn font_loaded(
&mut self,
mono: Vec<Arc<str>>,
interface: Vec<Arc<str>>,
) -> Task<crate::app::Message> {
self.interface_font_families = interface;
self.monospace_font_families = mono;
Task::none()
}
pub fn select(
&mut self,
font: String,
context_view: &ContextView,
) -> Option<Task<app::Message>> {
match *context_view {
ContextView::MonospaceFont => {
self.monospace_font = FontConfig {
family: font.to_string(),
weight: cosmic::iced::font::Weight::Normal,
style: cosmic::iced::font::Style::Normal,
stretch: cosmic::iced::font::Stretch::Normal,
};
update_config(MONOSPACE_FONT, self.monospace_font.clone());
None
}
ContextView::SystemFont => {
self.interface_font = FontConfig {
family: font.to_string(),
weight: cosmic::iced::font::Weight::Normal,
style: cosmic::iced::font::Style::Normal,
stretch: cosmic::iced::font::Stretch::Normal,
};
update_config(INTERFACE_FONT, self.interface_font.clone());
tokio::spawn(async move {
set_gnome_font_name(font.as_ref()).await;
});
None
}
_ => None,
}
}
pub fn search(&mut self, input: String, context_view: &ContextView) -> Task<app::Message> {
self.font_search = input.to_lowercase();
self.font_filter.clear();
let mut result: Option<Vec<Arc<str>>> = None;
if let Some(fonts) = self.current_font_family(context_view) {
result = Some(
fonts
.iter()
.filter(|f| f.to_lowercase().contains(&self.font_search))
.cloned()
.collect(),
);
}
if let Some(fonts) = result.as_mut() {
self.font_filter.append(fonts);
}
Task::none()
}
pub fn search_input(&self) -> Element<'_, crate::pages::Message> {
widget::search_input("", &self.font_search)
.on_input(|input| Message::DrawerFont(drawer::FontMessage::Search(input)))
.on_clear(Message::DrawerFont(drawer::FontMessage::Search(
String::new(),
)))
.apply(Element::from)
.map(crate::pages::Message::Appearance)
}
pub fn selection_context(
&self,
context_view: &ContextView,
callback: impl Fn(Arc<str>) -> super::Message,
) -> Element<'_, super::Message> {
let (mut families, current_font) = match *context_view {
ContextView::MonospaceFont => {
(&self.monospace_font_families, &self.monospace_font.family)
}
ContextView::SystemFont => (&self.interface_font_families, &self.interface_font.family),
_ => (&self.monospace_font_families, &self.monospace_font.family),
};
if !self.font_filter.is_empty() {
families = &self.font_filter;
}
let list = families.iter().fold(
widget::list_column::with_capacity(families.len()),
|list, family| {
let selected = &**family == current_font;
list.add(selection_context_item(
family,
selected,
callback(family.clone()),
))
},
);
list.into()
}
fn current_font_family(&self, context_view: &ContextView) -> Option<&Vec<Arc<str>>> {
match *context_view {
ContextView::SystemFont => Some(&self.interface_font_families),
ContextView::MonospaceFont => Some(&self.monospace_font_families),
_ => None,
}
}
}
fn update_config(variant: &str, font: FontConfig) {
if let Ok(config) = CosmicTk::config() {
_ = config.set(variant, font);
}
}
/// Set the preferred icon theme for GNOME/GTK applications.
pub async fn set_gnome_font_name(font_name: &str) {
let _res = tokio::process::Command::new("gsettings")
.args(["set", "org.gnome.desktop.interface", "font-name", font_name])
.status()
.await;
}