feat: configurable fonts

This commit is contained in:
Michael Aaron Murphy 2024-10-03 21:27:06 +02:00 committed by Michael Murphy
parent e645dee2f0
commit 9e064e7fa0
20 changed files with 143 additions and 124 deletions

View file

@ -100,6 +100,7 @@ tokio = { version = "1.24.2", optional = true }
tracing = "0.1"
unicode-segmentation = "1.6"
url = "2.4.0"
ustr = { version = "1.0.0", features = ["serde"] }
zbus = { version = "4.2.1", default-features = false, optional = true }
[target.'cfg(unix)'.dependencies]

View file

@ -16,7 +16,7 @@ pub fn main() -> cosmic::iced::Result {
cosmic::icon_theme::set_default("Pop");
#[allow(clippy::field_reassign_with_default)]
let settings = Settings {
default_font: cosmic::font::FONT,
default_font: cosmic::font::default(),
window: cosmic::iced::window::Settings {
min_size: Some(cosmic::iced::Size::new(600., 300.)),
..cosmic::iced::window::Settings::default()

View file

@ -320,7 +320,7 @@ impl State {
.padding(0)
.into(),
Some(DemoView::TabB) => settings::view_column(vec![
text("Selection").font(cosmic::font::FONT_SEMIBOLD).into(),
text("Selection").font(cosmic::font::semibold()).into(),
text("Horizontal").into(),
segmented_control::horizontal(&self.selection)
.on_activate(Message::Selection)
@ -378,9 +378,7 @@ impl State {
.spacing(12)
.width(Length::Fill)
.into(),
text("View Switcher")
.font(cosmic::font::FONT_SEMIBOLD)
.into(),
text("View Switcher").font(cosmic::font::semibold()).into(),
text("Horizontal").into(),
tab_bar::horizontal(&self.selection)
.on_activate(Message::Selection)

2
iced

@ -1 +1 @@
Subproject commit 90f904fe28fba26805aaac2c2c5dd1514607242e
Subproject commit 3dc0bbf653d2788b2352ede423804f831f14436c

View file

@ -627,7 +627,7 @@ impl<T: Application> Cosmic<T> {
crate::icon_theme::set_default(config.icon_theme.clone());
}
*crate::config::COSMIC_TK.lock().unwrap() = config;
*crate::config::COSMIC_TK.write().unwrap() = config;
}
Message::Focus(f) => {

View file

@ -81,7 +81,7 @@ impl Default for Settings {
no_main_window: false,
client_decorations: true,
debug: false,
default_font: font::FONT,
default_font: font::default(),
default_icon_theme: None,
default_text_size: 14.0,
resizable: Some(8.0),

View file

@ -164,7 +164,7 @@ impl Context {
)
.resizable(None)
.default_text_size(14.0)
.default_font(crate::font::FONT)
.default_font(crate::font::default())
.transparent(true);
if let Some(theme) = self.theme() {
settings = settings.theme(theme);
@ -339,7 +339,7 @@ impl Context {
Size::PanelSize(PanelSize::XS) => crate::widget::text::body,
Size::Hardcoded(_) => crate::widget::text,
};
t(msg).font(crate::font::FONT)
t(msg).font(crate::font::default())
}
}

View file

@ -6,14 +6,16 @@
use crate::cosmic_theme::Density;
use cosmic_config::cosmic_config_derive::CosmicConfigEntry;
use cosmic_config::{Config, CosmicConfigEntry};
use iced::font::Family;
use serde::{Deserialize, Serialize};
use std::sync::{LazyLock, Mutex};
use std::sync::{LazyLock, RwLock};
use ustr::Ustr;
/// ID for the `CosmicTk` config.
pub const ID: &str = "com.system76.CosmicTk";
pub static COSMIC_TK: LazyLock<Mutex<CosmicTk>> = LazyLock::new(|| {
Mutex::new(
pub static COSMIC_TK: LazyLock<RwLock<CosmicTk>> = LazyLock::new(|| {
RwLock::new(
CosmicTk::config()
.map(|c| {
CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| {
@ -30,37 +32,47 @@ pub static COSMIC_TK: LazyLock<Mutex<CosmicTk>> = LazyLock::new(|| {
/// Apply the theme to other toolkits.
#[allow(clippy::missing_panics_doc)]
pub fn apply_theme_global() -> bool {
COSMIC_TK.lock().unwrap().apply_theme_global
COSMIC_TK.read().unwrap().apply_theme_global
}
/// Show minimize button in window header.
#[allow(clippy::missing_panics_doc)]
pub fn show_minimize() -> bool {
COSMIC_TK.lock().unwrap().show_minimize
COSMIC_TK.read().unwrap().show_minimize
}
/// Show maximize button in window header.
#[allow(clippy::missing_panics_doc)]
pub fn show_maximize() -> bool {
COSMIC_TK.lock().unwrap().show_maximize
COSMIC_TK.read().unwrap().show_maximize
}
/// Preferred icon theme.
#[allow(clippy::missing_panics_doc)]
pub fn icon_theme() -> String {
COSMIC_TK.lock().unwrap().icon_theme.clone()
COSMIC_TK.read().unwrap().icon_theme.clone()
}
/// Density of CSD/SSD header bars.
#[allow(clippy::missing_panics_doc)]
pub fn header_size() -> Density {
COSMIC_TK.lock().unwrap().header_size
COSMIC_TK.read().unwrap().header_size
}
/// Interface density.
#[allow(clippy::missing_panics_doc)]
pub fn interface_density() -> Density {
COSMIC_TK.lock().unwrap().interface_density
COSMIC_TK.read().unwrap().interface_density
}
#[allow(clippy::missing_panics_doc)]
pub fn interface_font() -> FontConfig {
COSMIC_TK.read().unwrap().interface_font
}
#[allow(clippy::missing_panics_doc)]
pub fn monospace_font() -> FontConfig {
COSMIC_TK.read().unwrap().monospace_font
}
#[derive(Clone, CosmicConfigEntry, Debug, Eq, PartialEq)]
@ -83,6 +95,12 @@ pub struct CosmicTk {
/// Interface density.
pub interface_density: Density,
/// Interface font family
pub interface_font: FontConfig,
/// Mono font family
pub monospace_font: FontConfig,
}
impl Default for CosmicTk {
@ -94,6 +112,18 @@ impl Default for CosmicTk {
icon_theme: String::from("Cosmic"),
header_size: Density::Standard,
interface_density: Density::Standard,
interface_font: FontConfig {
family: Ustr::from("Fira Sans"),
weight: iced::font::Weight::Normal,
stretch: iced::font::Stretch::Normal,
style: iced::font::Style::Normal,
},
monospace_font: FontConfig {
family: Ustr::from("Fira Mono"),
weight: iced::font::Weight::Normal,
stretch: iced::font::Stretch::Normal,
style: iced::font::Style::Normal,
},
}
}
}
@ -103,3 +133,22 @@ impl CosmicTk {
Config::new(ID, Self::VERSION)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct FontConfig {
pub family: Ustr,
pub weight: iced::font::Weight,
pub stretch: iced::font::Stretch,
pub style: iced::font::Style,
}
impl From<FontConfig> for iced::Font {
fn from(font: FontConfig) -> Self {
Self {
family: iced::font::Family::Name(font.family.as_str()),
weight: font.weight,
stretch: font.stretch,
style: font.style,
}
}
}

View file

@ -10,59 +10,31 @@ use iced::{
};
use iced_core::font::Family;
pub const DEFAULT: Font = FONT;
pub const FONT: Font = Font {
family: Family::Name("Fira Sans"),
weight: iced_core::font::Weight::Normal,
stretch: iced_core::font::Stretch::Normal,
style: iced_core::font::Style::Normal,
};
pub const FONT_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-Regular.otf");
pub const FONT_LIGHT: Font = Font {
family: Family::Name("Fira Sans"),
weight: iced_core::font::Weight::Light,
stretch: iced_core::font::Stretch::Normal,
style: iced_core::font::Style::Normal,
};
pub const FONT_LIGHT_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-Light.otf");
pub const FONT_SEMIBOLD: Font = Font {
family: Family::Name("Fira Sans"),
weight: iced_core::font::Weight::Semibold,
stretch: iced_core::font::Stretch::Normal,
style: iced_core::font::Style::Normal,
};
pub const FONT_SEMIBOLD_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-SemiBold.otf");
pub const FONT_BOLD: Font = Font {
family: Family::Name("Fira Sans"),
weight: iced_core::font::Weight::Bold,
stretch: iced_core::font::Stretch::Normal,
style: iced_core::font::Style::Normal,
};
pub const FONT_BOLD_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-Bold.otf");
pub const FONT_MONO_REGULAR: Font = Font {
family: Family::Name("Fira Mono"),
weight: iced_core::font::Weight::Normal,
stretch: iced_core::font::Stretch::Normal,
style: iced_core::font::Style::Normal,
};
pub const FONT_MONO_REGULAR_DATA: &[u8] = include_bytes!("../res/Fira/FiraMono-Regular.otf");
pub fn load_fonts() -> Command<Result<(), Error>> {
Command::batch(vec![
load(FONT_DATA),
load(FONT_LIGHT_DATA),
load(FONT_SEMIBOLD_DATA),
load(FONT_BOLD_DATA),
load(FONT_MONO_REGULAR_DATA),
])
pub fn default() -> Font {
Font::from(crate::config::interface_font())
}
pub fn light() -> Font {
Font {
weight: iced_core::font::Weight::Light,
..default()
}
}
pub fn semibold() -> Font {
Font {
weight: iced_core::font::Weight::Semibold,
..default()
}
}
pub fn bold() -> Font {
Font {
weight: iced_core::font::Weight::Bold,
..default()
}
}
pub fn mono() -> Font {
Font::from(crate::config::monospace_font())
}

View file

@ -145,10 +145,9 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
crate::widget::text(builder.label)
.size(builder.font_size)
.line_height(LineHeight::Absolute(builder.line_height.into()))
.font({
let mut font = crate::font::DEFAULT;
font.weight = builder.font_weight;
font
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
.into(),
);
@ -182,10 +181,9 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
} else {
tooltip(button, builder.tooltip, tooltip::Position::Top)
.size(builder.font_size)
.font({
let mut font = crate::font::DEFAULT;
font.weight = builder.font_weight;
font
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
.into()
}

View file

@ -63,14 +63,14 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
let button: super::Button<'a, Message> = row::with_capacity(2)
.push({
let mut font = crate::font::DEFAULT;
font.weight = builder.font_weight;
// TODO: Avoid allocation
crate::widget::text(builder.label.to_string())
.size(builder.font_size)
.line_height(LineHeight::Absolute(builder.line_height.into()))
.font(font)
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
})
.push_maybe(if builder.variant.trailing_icon {
Some(icon().icon().size(builder.icon_size))
@ -93,10 +93,9 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
} else {
tooltip(button, builder.tooltip, tooltip::Position::Top)
.size(builder.font_size)
.font({
let mut font = crate::font::DEFAULT;
font.weight = builder.font_weight;
font
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
.into()
}

View file

@ -101,8 +101,10 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
});
let label: Option<Element<'_, _>> = (!builder.label.is_empty()).then(|| {
let mut font = crate::font::DEFAULT;
font.weight = builder.font_weight;
let font = crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
};
// TODO: Avoid allocation
crate::widget::text(builder.label.to_string())
@ -135,10 +137,9 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
} else {
tooltip(button, builder.tooltip, tooltip::Position::Top)
.size(builder.font_size)
.font({
let mut font = crate::font::DEFAULT;
font.weight = builder.font_weight;
font
.font(crate::font::Font {
weight: builder.font_weight,
..crate::font::default()
})
.into()
}

View file

@ -459,7 +459,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
},
);
(appearance.selected_text_color, crate::font::FONT_SEMIBOLD)
(appearance.selected_text_color, crate::font::semibold())
} else if *self.hovered_option == Some(i) {
let item_x = bounds.x + appearance.border_width;
let item_width = appearance.border_width.mul_add(-2.0, bounds.width);
@ -480,9 +480,9 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
appearance.hovered_background,
);
(appearance.hovered_text_color, crate::font::FONT)
(appearance.hovered_text_color, crate::font::default())
} else {
(appearance.text_color, crate::font::FONT)
(appearance.text_color, crate::font::default())
};
let bounds = Rectangle {

View file

@ -549,7 +549,7 @@ where
},
);
(appearance.selected_text_color, crate::font::FONT_SEMIBOLD)
(appearance.selected_text_color, crate::font::semibold())
} else if self.hovered_option.as_ref() == Some(item) {
let item_x = bounds.x + appearance.border_width;
let item_width = bounds.width - appearance.border_width * 2.0;
@ -572,9 +572,9 @@ where
appearance.hovered_background,
);
(appearance.hovered_text_color, crate::font::FONT)
(appearance.hovered_text_color, crate::font::default())
} else {
(appearance.text_color, crate::font::FONT)
(appearance.text_color, crate::font::default())
};
let bounds = Rectangle {
@ -640,7 +640,7 @@ where
bounds: bounds.size(),
size: iced::Pixels(text_size),
line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)),
font: crate::font::FONT,
font: crate::font::default(),
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,

View file

@ -156,9 +156,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let font = self
.font
.unwrap_or_else(|| text::Renderer::default_font(renderer));
let font = self.font.unwrap_or_else(|| crate::font::default());
draw(
renderer,
@ -275,7 +273,7 @@ pub fn layout(
bounds: Size::new(f32::MAX, f32::MAX),
size: iced::Pixels(text_size),
line_height: text_line_height,
font: font.unwrap_or_else(|| text::Renderer::default_font(renderer)),
font: font.unwrap_or_else(|| crate::font::default()),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
@ -418,7 +416,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
bounds: Size::new(f32::MAX, f32::MAX),
size: iced::Pixels(text_size),
line_height,
font: font.unwrap_or_else(|| text::Renderer::default_font(renderer)),
font: font.unwrap_or_else(|| crate::font::default()),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,

View file

@ -76,7 +76,7 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
size: iced::Pixels(self.text_size.unwrap_or(14.0)),
line_height: self.text_line_height,
font: self.font.unwrap_or(crate::font::FONT),
font: self.font.unwrap_or_else(crate::font::default),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
@ -113,7 +113,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
size: iced::Pixels(self.text_size.unwrap_or(14.0)),
line_height: self.text_line_height,
font: self.font.unwrap_or(crate::font::FONT),
font: self.font.unwrap_or_else(crate::font::default),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
@ -194,9 +194,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let font = self
.font
.unwrap_or_else(|| text::Renderer::default_font(renderer));
let font = self.font.unwrap_or_else(|| crate::font::default());
draw(
renderer,
theme,
@ -308,7 +306,7 @@ pub fn layout(
bounds: Size::new(f32::MAX, f32::MAX),
size: iced::Pixels(text_size),
line_height: text_line_height,
font: font.unwrap_or_else(|| text::Renderer::default_font(renderer)),
font: font.unwrap_or_else(|| crate::font::default()),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,

View file

@ -635,7 +635,7 @@ where
content: text,
size: iced::Pixels(self.font_size),
bounds: Size::INFINITY,
font: font.unwrap_or(crate::font::FONT),
font: font.unwrap_or_else(crate::font::default),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced,

View file

@ -31,7 +31,7 @@ where
.button_padding([space_s, 0, space_s, 0])
.button_spacing(space_xxs)
.style(crate::theme::SegmentedButton::Control)
.font_active(Some(crate::font::FONT_SEMIBOLD))
.font_active(Some(crate::font::semibold()))
}
/// A selection of multiple choices appearing as a conjoined button.
@ -57,5 +57,5 @@ where
.button_padding([space_s, 0, space_s, 0])
.button_spacing(space_xxs)
.style(crate::theme::SegmentedButton::Control)
.font_active(Some(crate::font::FONT_SEMIBOLD))
.font_active(Some(crate::font::semibold()))
}

View file

@ -30,7 +30,7 @@ where
.button_height(44)
.button_padding([space_s, space_xs, space_s, space_xs])
.style(crate::theme::SegmentedButton::TabBar)
.font_active(Some(crate::font::FONT_SEMIBOLD))
.font_active(Some(crate::font::semibold()))
}
/// A collection of tabs for developing a tabbed interface.
@ -54,5 +54,5 @@ where
.button_height(44)
.button_padding([space_s, space_xs, space_s, space_xs])
.style(crate::theme::SegmentedButton::TabBar)
.font_active(Some(crate::font::FONT_SEMIBOLD))
.font_active(Some(crate::font::semibold()))
}

View file

@ -7,7 +7,7 @@ use std::borrow::Cow;
///
/// [`Text`]: widget::Text
pub fn text<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text)
Text::new(text).font(crate::font::default())
}
/// Available presets for text typography
@ -29,7 +29,7 @@ pub fn title1<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
Text::new(text)
.size(32.0)
.line_height(LineHeight::Absolute(44.0.into()))
.font(crate::font::FONT_LIGHT)
.font(crate::font::semibold())
}
/// [`Text`] widget with the Title 2 typography preset.
@ -37,6 +37,7 @@ pub fn title2<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
Text::new(text)
.size(28.0)
.line_height(LineHeight::Absolute(36.0.into()))
.font(crate::font::default())
}
/// [`Text`] widget with the Title 3 typography preset.
@ -44,6 +45,7 @@ pub fn title3<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
Text::new(text)
.size(24.0)
.line_height(LineHeight::Absolute(32.0.into()))
.font(crate::font::default())
}
/// [`Text`] widget with the Title 4 typography preset.
@ -51,6 +53,7 @@ pub fn title4<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
Text::new(text)
.size(20.0)
.line_height(LineHeight::Absolute(28.0.into()))
.font(crate::font::default())
}
/// [`Text`] widget with the Heading typography preset.
@ -58,7 +61,7 @@ pub fn heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
Text::new(text)
.size(14.0)
.line_height(LineHeight::Absolute(iced::Pixels(20.0)))
.font(crate::font::FONT_SEMIBOLD)
.font(crate::font::semibold())
}
/// [`Text`] widget with the Caption Heading typography preset.
@ -66,7 +69,7 @@ pub fn caption_heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate
Text::new(text)
.size(10.0)
.line_height(LineHeight::Absolute(iced::Pixels(14.0)))
.font(crate::font::FONT_SEMIBOLD)
.font(crate::font::semibold())
}
/// [`Text`] widget with the Body typography preset.
@ -74,6 +77,7 @@ pub fn body<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Re
Text::new(text)
.size(14.0)
.line_height(LineHeight::Absolute(20.0.into()))
.font(crate::font::default())
}
/// [`Text`] widget with the Caption typography preset.
@ -81,6 +85,7 @@ pub fn caption<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
Text::new(text)
.size(10.0)
.line_height(LineHeight::Absolute(14.0.into()))
.font(crate::font::default())
}
/// [`Text`] widget with the Monotext typography preset.
@ -88,5 +93,5 @@ pub fn monotext<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme
Text::new(text)
.size(14.0)
.line_height(LineHeight::Absolute(20.0.into()))
.font(crate::font::FONT_MONO_REGULAR)
.font(crate::font::mono())
}