Configurable font stretch, font weight, and bold font weight
* Store a font name => font faces map * Only list font names that have `NORMAL` and `BOLD` faces with `Normal` stretch. This will give us defaults and fall-backs that are guaranteed to always exist. * Filter by stretch first, with `Normal` chosen as the always existing default. * Then only list font weights supported by the font name stretch selected, for the normal and bold cases. * When changing the font name selected, the stretch/weight options will stay the same if supported by the new font name, or revert to the always existing fall-backs. Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
This commit is contained in:
parent
c74d5d6f56
commit
5eba6eb4d6
6 changed files with 251 additions and 12 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -874,6 +874,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"libcosmic",
|
||||
"log",
|
||||
"paste",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ tokio = { version = "1", features = ["sync"] }
|
|||
i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] }
|
||||
i18n-embed-fl = "0.6"
|
||||
rust-embed = "6"
|
||||
paste = "1.0"
|
||||
|
||||
[dependencies.cosmic-text]
|
||||
git = "https://github.com/pop-os/cosmic-text.git"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ light = Light
|
|||
syntax-dark = Syntax dark
|
||||
syntax-light = Syntax light
|
||||
default-font = Default font
|
||||
default-font-stretch = Default font stretch
|
||||
default-font-weight = Default font weight
|
||||
default-bold-font-weight = Default bold font weight
|
||||
default-font-size = Default font size
|
||||
default-zoom-step = Default zoom step
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ use cosmic::{
|
|||
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
|
||||
theme,
|
||||
};
|
||||
use cosmic_text::Metrics;
|
||||
use cosmic_text::{Metrics, Weight, Stretch};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::sync::OnceLock;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub const CONFIG_VERSION: u64 = 1;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
|
|
@ -31,6 +34,9 @@ pub struct Config {
|
|||
pub app_theme: AppTheme,
|
||||
pub font_name: String,
|
||||
pub font_size: u16,
|
||||
pub font_weight: u16,
|
||||
pub bold_font_weight: u16,
|
||||
pub font_stretch: u16,
|
||||
pub font_size_zoom_step_mul_100: u16,
|
||||
pub show_headerbar: bool,
|
||||
pub syntax_theme_dark: String,
|
||||
|
|
@ -43,6 +49,9 @@ impl Default for Config {
|
|||
app_theme: AppTheme::System,
|
||||
font_name: "Fira Mono".to_string(),
|
||||
font_size: 14,
|
||||
font_weight: Weight::NORMAL.0,
|
||||
bold_font_weight: Weight::BOLD.0,
|
||||
font_stretch: Stretch::Normal.to_number(),
|
||||
font_size_zoom_step_mul_100: 100,
|
||||
show_headerbar: true,
|
||||
syntax_theme_dark: "COSMIC Dark".to_string(),
|
||||
|
|
@ -75,4 +84,23 @@ impl Config {
|
|||
&self.syntax_theme_light
|
||||
}
|
||||
}
|
||||
|
||||
pub fn typed_font_stretch(&self) -> Stretch {
|
||||
macro_rules! populate_num_typed_map {
|
||||
($($stretch:ident,)+) => {
|
||||
let mut map = BTreeMap::new();
|
||||
$(map.insert(Stretch::$stretch.to_number(), Stretch::$stretch);)+
|
||||
map
|
||||
};
|
||||
}
|
||||
|
||||
static NUM_TO_TYPED_MAP: OnceLock<BTreeMap<u16, Stretch>> = OnceLock::new();
|
||||
|
||||
NUM_TO_TYPED_MAP.get_or_init(|| {
|
||||
populate_num_typed_map!{
|
||||
UltraCondensed, ExtraCondensed, Condensed, SemiCondensed,
|
||||
Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded,
|
||||
}
|
||||
})[&self.font_stretch]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
199
src/main.rs
199
src/main.rs
|
|
@ -20,8 +20,8 @@ use cosmic::{
|
|||
widget::{self, segmented_button},
|
||||
Application, ApplicationExt, Element,
|
||||
};
|
||||
use cosmic_text::Family;
|
||||
use std::{any::TypeId, collections::HashMap, env, process, sync::Mutex};
|
||||
use cosmic_text::{Family, Weight, Stretch, fontdb::FaceInfo};
|
||||
use std::{any::TypeId, collections::{HashMap, BTreeMap, BTreeSet}, env, process, sync::Mutex};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use config::{AppTheme, Config, CONFIG_VERSION};
|
||||
|
|
@ -140,6 +140,9 @@ pub enum Message {
|
|||
Copy(Option<segmented_button::Entity>),
|
||||
DefaultFont(usize),
|
||||
DefaultFontSize(usize),
|
||||
DefaultFontStretch(usize),
|
||||
DefaultFontWeight(usize),
|
||||
DefaultBoldFontWeight(usize),
|
||||
DefaultZoomStep(usize),
|
||||
Paste(Option<segmented_button::Entity>),
|
||||
PasteValue(Option<segmented_button::Entity>, String),
|
||||
|
|
@ -186,6 +189,13 @@ pub struct App {
|
|||
font_names: Vec<String>,
|
||||
font_size_names: Vec<String>,
|
||||
font_sizes: Vec<u16>,
|
||||
font_name_faces_map: BTreeMap<String, Vec<FaceInfo>>,
|
||||
all_font_weights_vals_names_map: BTreeMap<u16, String>,
|
||||
all_font_stretches_vals_names_map: BTreeMap<Stretch, String>,
|
||||
curr_font_weight_names: Vec<String>,
|
||||
curr_font_weights: Vec<u16>,
|
||||
curr_font_stretch_names: Vec<String>,
|
||||
curr_font_stretches: Vec<Stretch>,
|
||||
zoom_adj: i8,
|
||||
zoom_step_names: Vec<String>,
|
||||
zoom_steps: Vec<u16>,
|
||||
|
|
@ -236,6 +246,57 @@ impl App {
|
|||
self.set_window_title(window_title)
|
||||
}
|
||||
|
||||
fn set_curr_font_weights_and_stretches(&mut self) {
|
||||
let curr_font_faces = &self.font_name_faces_map[&self.config.font_name];
|
||||
|
||||
self.curr_font_stretches = curr_font_faces
|
||||
.iter()
|
||||
.map(|face| face.stretch)
|
||||
.collect::<BTreeSet<_>>() // remove duplicates and sort
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
self.curr_font_stretch_names = self.curr_font_stretches.iter()
|
||||
.map(|stretch| &self.all_font_stretches_vals_names_map[stretch])
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !self.curr_font_stretches.contains(&self.config.typed_font_stretch()) {
|
||||
self.config.font_stretch = Stretch::Normal.to_number();
|
||||
}
|
||||
|
||||
let curr_weights = |conf_stretch| curr_font_faces
|
||||
.iter()
|
||||
.filter(|face| face.stretch == conf_stretch)
|
||||
.map(|face| face.weight.0)
|
||||
.collect::<BTreeSet<_>>() // remove duplicates and sort
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
self.curr_font_weights = curr_weights(self.config.typed_font_stretch());
|
||||
|
||||
if self.curr_font_weights.is_empty() {
|
||||
// stretch fallback
|
||||
self.config.font_stretch = Stretch::Normal.to_number();
|
||||
}
|
||||
|
||||
self.curr_font_weights = curr_weights(self.config.typed_font_stretch());
|
||||
assert!(!self.curr_font_weights.is_empty());
|
||||
|
||||
self.curr_font_weight_names = self.curr_font_weights.iter()
|
||||
.map(|weight| &self.all_font_weights_vals_names_map[weight])
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !self.curr_font_weights.contains(&self.config.font_weight) {
|
||||
self.config.font_weight = Weight::NORMAL.0;
|
||||
}
|
||||
|
||||
if !self.curr_font_weights.contains(&self.config.bold_font_weight) {
|
||||
self.config.bold_font_weight = Weight::BOLD.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn settings(&self) -> Element<Message> {
|
||||
let app_theme_selected = match self.config.app_theme {
|
||||
AppTheme::Dark => 1,
|
||||
|
|
@ -261,6 +322,18 @@ impl App {
|
|||
.font_sizes
|
||||
.iter()
|
||||
.position(|font_size| font_size == &self.config.font_size);
|
||||
let font_stretch_selected = self
|
||||
.curr_font_stretches
|
||||
.iter()
|
||||
.position(|font_stretch| font_stretch == &self.config.typed_font_stretch());
|
||||
let font_weight_selected = self
|
||||
.curr_font_weights
|
||||
.iter()
|
||||
.position(|font_weight| font_weight == &self.config.font_weight);
|
||||
let bold_font_weight_selected = self
|
||||
.curr_font_weights
|
||||
.iter()
|
||||
.position(|font_weight| font_weight == &self.config.bold_font_weight);
|
||||
let zoom_step_selected = self
|
||||
.zoom_steps
|
||||
.iter()
|
||||
|
|
@ -314,6 +387,27 @@ impl App {
|
|||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
widget::settings::item::builder(fl!("default-font-stretch")).control(
|
||||
widget::dropdown(&self.curr_font_stretch_names, font_stretch_selected, |index| {
|
||||
Message::DefaultFontStretch(index)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
widget::settings::item::builder(fl!("default-font-weight")).control(
|
||||
widget::dropdown(&self.curr_font_weight_names, font_weight_selected, |index| {
|
||||
Message::DefaultFontWeight(index)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
widget::settings::item::builder(fl!("default-bold-font-weight")).control(
|
||||
widget::dropdown(&self.curr_font_weight_names, bold_font_weight_selected, |index| {
|
||||
Message::DefaultBoldFontWeight(index)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
widget::settings::item::builder(fl!("show-headerbar"))
|
||||
.toggler(self.config.show_headerbar, Message::ShowHeaderBar),
|
||||
|
|
@ -361,24 +455,34 @@ impl Application for App {
|
|||
|
||||
let app_themes = vec![fl!("match-desktop"), fl!("dark"), fl!("light")];
|
||||
|
||||
let font_names = {
|
||||
let mut font_names = Vec::new();
|
||||
let font_name_faces_map = {
|
||||
let mut font_name_faces_map = BTreeMap::<_, Vec<_>>::new();
|
||||
let mut font_system = font_system().write().unwrap();
|
||||
//TODO: do not repeat, used in Tab::new
|
||||
let attrs = cosmic_text::Attrs::new().family(Family::Monospace);
|
||||
for face in font_system.raw().db().faces() {
|
||||
if attrs.matches(face) && face.monospaced {
|
||||
// only monospace fonts and weights that match named constants.
|
||||
let weight = face.weight.0;
|
||||
if face.monospaced && {1..9}.contains(&{weight / 100}) && weight % 100 == 0 {
|
||||
//TODO: get localized name if possible
|
||||
let font_name = face
|
||||
.families
|
||||
.get(0)
|
||||
.map_or_else(|| face.post_script_name.to_string(), |x| x.0.to_string());
|
||||
font_names.push(font_name);
|
||||
font_name_faces_map.entry(font_name).or_default().push(face.clone());
|
||||
}
|
||||
}
|
||||
font_names.sort();
|
||||
font_names
|
||||
|
||||
// only keep fonts that have both NORMAL and BOLD weights with both having
|
||||
// a `Stretch::Normal` face.
|
||||
// This is important for fallbacks.
|
||||
font_name_faces_map.retain(|_, v| {
|
||||
let has_normal = v.iter().any(|face| face.weight == Weight::NORMAL && face.stretch == Stretch::Normal);
|
||||
let has_bold = v.iter().any(|face| face.weight == Weight::BOLD && face.stretch == Stretch::Normal);
|
||||
has_normal && has_bold
|
||||
});
|
||||
font_name_faces_map
|
||||
};
|
||||
let font_names = font_name_faces_map.keys().cloned().collect();
|
||||
|
||||
let mut font_size_names = Vec::new();
|
||||
let mut font_sizes = Vec::new();
|
||||
|
|
@ -387,6 +491,42 @@ impl Application for App {
|
|||
font_sizes.push(font_size);
|
||||
}
|
||||
|
||||
let mut all_font_weights_vals_names_map = BTreeMap::new();
|
||||
|
||||
macro_rules! populate_font_weights {
|
||||
($($weight:ident,)+) => {
|
||||
// all weights
|
||||
paste::paste!{
|
||||
$(
|
||||
all_font_weights_vals_names_map
|
||||
.insert(Weight::$weight.0, stringify!([<$weight:camel>]).into());
|
||||
)+
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
populate_font_weights!{
|
||||
THIN, EXTRA_LIGHT, LIGHT, NORMAL, MEDIUM,
|
||||
SEMIBOLD, BOLD, EXTRA_BOLD, BLACK,
|
||||
};
|
||||
|
||||
let mut all_font_stretches_vals_names_map= BTreeMap::new();
|
||||
|
||||
macro_rules! populate_font_stretches {
|
||||
($($stretch:ident,)+) => {
|
||||
// all stretches
|
||||
$(
|
||||
all_font_stretches_vals_names_map
|
||||
.insert(Stretch::$stretch, stringify!($stretch).into());
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
populate_font_stretches!{
|
||||
UltraCondensed, ExtraCondensed, Condensed, SemiCondensed,
|
||||
Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded,
|
||||
};
|
||||
|
||||
let mut zoom_step_names = Vec::new();
|
||||
let mut zoom_steps = Vec::new();
|
||||
for zoom_step in [25, 50, 75, 100, 150, 200] {
|
||||
|
|
@ -407,6 +547,13 @@ impl Application for App {
|
|||
font_names,
|
||||
font_size_names,
|
||||
font_sizes,
|
||||
font_name_faces_map,
|
||||
all_font_weights_vals_names_map,
|
||||
all_font_stretches_vals_names_map,
|
||||
curr_font_weight_names: Vec::new(),
|
||||
curr_font_weights: Vec::new(),
|
||||
curr_font_stretch_names: Vec::new(),
|
||||
curr_font_stretches: Vec::new(),
|
||||
zoom_adj: 0,
|
||||
zoom_step_names,
|
||||
zoom_steps,
|
||||
|
|
@ -417,6 +564,7 @@ impl Application for App {
|
|||
term_event_tx_opt: None,
|
||||
};
|
||||
|
||||
app.set_curr_font_weights_and_stretches();
|
||||
let command = app.update_title();
|
||||
|
||||
(app, command)
|
||||
|
|
@ -468,6 +616,8 @@ impl Application for App {
|
|||
}
|
||||
|
||||
self.config.font_name = font_name.to_string();
|
||||
self.set_curr_font_weights_and_stretches();
|
||||
|
||||
return self.save_config();
|
||||
}
|
||||
}
|
||||
|
|
@ -486,6 +636,34 @@ impl Application for App {
|
|||
log::warn!("failed to find font with index {}", index);
|
||||
}
|
||||
},
|
||||
Message::DefaultFontStretch(index) => match self.curr_font_stretches.get(index) {
|
||||
Some(font_stretch) => {
|
||||
self.config.font_stretch = font_stretch.to_number();
|
||||
self.set_curr_font_weights_and_stretches();
|
||||
return self.save_config();
|
||||
}
|
||||
None => {
|
||||
log::warn!("failed to find font weight with index {}", index);
|
||||
}
|
||||
},
|
||||
Message::DefaultFontWeight(index) => match self.curr_font_weights.get(index) {
|
||||
Some(font_weight) => {
|
||||
self.config.font_weight = *font_weight;
|
||||
return self.save_config();
|
||||
}
|
||||
None => {
|
||||
log::warn!("failed to find font weight with index {}", index);
|
||||
}
|
||||
},
|
||||
Message::DefaultBoldFontWeight(index) => match self.curr_font_weights.get(index) {
|
||||
Some(font_weight) => {
|
||||
self.config.bold_font_weight = *font_weight;
|
||||
return self.save_config();
|
||||
}
|
||||
None => {
|
||||
log::warn!("failed to find bold font weight with index {}", index);
|
||||
}
|
||||
},
|
||||
Message::DefaultZoomStep(index) => match self.zoom_steps.get(index) {
|
||||
Some(zoom_step) => {
|
||||
self.config.font_size_zoom_step_mul_100 = *zoom_step;
|
||||
|
|
@ -602,6 +780,9 @@ impl Application for App {
|
|||
entity,
|
||||
term_event_tx.clone(),
|
||||
self.term_config.clone(),
|
||||
self.config.typed_font_stretch(),
|
||||
self.config.font_weight,
|
||||
self.config.bold_font_weight,
|
||||
colors.clone(),
|
||||
);
|
||||
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use alacritty_terminal::{
|
|||
};
|
||||
use cosmic::{iced::advanced::graphics::text::font_system, widget::segmented_button};
|
||||
use cosmic_text::{
|
||||
Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Wrap,
|
||||
Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Stretch, Wrap,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
|
@ -121,6 +121,7 @@ pub struct Terminal {
|
|||
size: Size,
|
||||
pub term: Arc<FairMutex<Term<EventProxy>>>,
|
||||
colors: Colors,
|
||||
bold_font_weight: Weight,
|
||||
notifier: Notifier,
|
||||
pub context_menu: Option<cosmic::iced::Point>,
|
||||
pub needs_update: bool,
|
||||
|
|
@ -132,12 +133,17 @@ impl Terminal {
|
|||
entity: segmented_button::Entity,
|
||||
event_tx: mpsc::Sender<(segmented_button::Entity, Event)>,
|
||||
config: Config,
|
||||
font_stretch: Stretch,
|
||||
font_weight: u16,
|
||||
bold_font_weight: u16,
|
||||
colors: Colors,
|
||||
) -> Self {
|
||||
let metrics = Metrics::new(14.0, 20.0);
|
||||
//TODO: set color to default fg
|
||||
let default_attrs = Attrs::new()
|
||||
.family(Family::Monospace)
|
||||
.weight(Weight(font_weight))
|
||||
.stretch(font_stretch)
|
||||
.color(convert_color(&colors, Color::Named(NamedColor::Foreground)))
|
||||
.metadata(convert_color(&colors, Color::Named(NamedColor::Background)).0 as usize);
|
||||
let mut buffer = Buffer::new_empty(metrics);
|
||||
|
|
@ -177,6 +183,7 @@ impl Terminal {
|
|||
|
||||
Self {
|
||||
colors,
|
||||
bold_font_weight: Weight(bold_font_weight),
|
||||
default_attrs,
|
||||
buffer: Arc::new(buffer),
|
||||
size,
|
||||
|
|
@ -327,6 +334,22 @@ impl Terminal {
|
|||
let mut update_cell_size = false;
|
||||
let mut update = false;
|
||||
|
||||
if self.default_attrs.stretch != config.typed_font_stretch() {
|
||||
self.default_attrs = self.default_attrs.stretch(config.typed_font_stretch());
|
||||
update_cell_size = true;
|
||||
}
|
||||
|
||||
if self.default_attrs.weight.0 != config.font_weight {
|
||||
self.default_attrs = self.default_attrs.weight(Weight(config.font_weight));
|
||||
update_cell_size = true;
|
||||
}
|
||||
|
||||
|
||||
if self.bold_font_weight.0 != config.font_weight {
|
||||
self.bold_font_weight = Weight(config.bold_font_weight);
|
||||
update_cell_size = true;
|
||||
}
|
||||
|
||||
let metrics = config.metrics(zoom_adj);
|
||||
if metrics != self.buffer.metrics() {
|
||||
{
|
||||
|
|
@ -347,6 +370,8 @@ impl Terminal {
|
|||
if changed {
|
||||
self.default_attrs = Attrs::new()
|
||||
.family(Family::Monospace)
|
||||
.weight(Weight(config.font_weight))
|
||||
.stretch(config.typed_font_stretch())
|
||||
.color(convert_color(&colors, Color::Named(NamedColor::Foreground)))
|
||||
.metadata(
|
||||
convert_color(&colors, Color::Named(NamedColor::Background)).0 as usize,
|
||||
|
|
@ -475,7 +500,7 @@ impl Terminal {
|
|||
attrs = attrs.metadata(bg.0 as usize);
|
||||
//TODO: more flags
|
||||
if indexed.cell.flags.contains(Flags::BOLD) {
|
||||
attrs = attrs.weight(Weight::BOLD);
|
||||
attrs = attrs.weight(self.bold_font_weight);
|
||||
}
|
||||
if indexed.cell.flags.contains(Flags::ITALIC) {
|
||||
//TODO: automatically use fake italic
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue