Load overview fonts off the compositor event loop
This commit is contained in:
1 parent
da5397940b
commit
6dd132a4a5
2 files changed
+137
-28
No files matched your search
@@ -76,6 +76,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let runtime = config.runtime_config()?;
|
let runtime = config.runtime_config()?;
|
||||||
let mut state = Ferese::new(&mut event_loop, display, runtime)?;
|
let mut state = Ferese::new(&mut event_loop, display, runtime)?;
|
||||||
state.config_source = initial_source.filter(|source| source.len() <= 60 * 1024);
|
state.config_source = initial_source.filter(|source| source.len() <= 60 * 1024);
|
||||||
|
overview::init_font_loader(&mut event_loop, &mut state)?;
|
||||||
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
||||||
let mut monitor =
|
let mut monitor =
|
||||||
config::config_path().and_then(|path| match reload::ConfigMonitor::new(path) {
|
config::config_path().and_then(|path| match reload::ConfigMonitor::new(path) {
|
||||||
|
|||||||
+136
-28
@@ -1,4 +1,8 @@
|
|||||||
use std::{collections::HashMap, time::Duration};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex, mpsc},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use ferese_animation::{AnimatedRect, AnimatedValue, SpringConfig};
|
use ferese_animation::{AnimatedRect, AnimatedValue, SpringConfig};
|
||||||
use ferese_core::{OutputId, WorkspaceId};
|
use ferese_core::{OutputId, WorkspaceId};
|
||||||
@@ -20,6 +24,73 @@ const OVERVIEW_GAP: f64 = 24.0;
|
|||||||
const WORKSPACE_CARD_GAP: f64 = 8.0;
|
const WORKSPACE_CARD_GAP: f64 = 8.0;
|
||||||
const MAX_PREVIEW_SCALE: f64 = 0.82;
|
const MAX_PREVIEW_SCALE: f64 = 0.82;
|
||||||
|
|
||||||
|
// A single worker loads fonts. The bounded wake channel and latest
|
||||||
|
// request slot coalesce rapid config reloads without spawning more loaders.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FontRequests {
|
||||||
|
latest: Arc<Mutex<String>>,
|
||||||
|
wake: mpsc::SyncSender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontRequests {
|
||||||
|
fn request(&self, family: &str) {
|
||||||
|
*self.latest.lock().unwrap() = family.to_owned();
|
||||||
|
let _ = self.wake.try_send(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_font_loader(
|
||||||
|
event_loop: &mut smithay::reexports::calloop::EventLoop<'static, Ferese>,
|
||||||
|
state: &mut Ferese,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
use smithay::reexports::calloop::channel;
|
||||||
|
let (results, source) = channel::sync_channel::<(String, Option<FontArc>)>(1);
|
||||||
|
event_loop
|
||||||
|
.handle()
|
||||||
|
.insert_source(source, |event, _, state| {
|
||||||
|
if let channel::Event::Msg((family, font)) = event
|
||||||
|
&& state.overview.complete_font_load(&family, font)
|
||||||
|
&& state.overview.is_presenting()
|
||||||
|
{
|
||||||
|
crate::backends::direct::render_all(state);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let latest = Arc::new(Mutex::new(state.overview.font_family.clone()));
|
||||||
|
let worker_latest = latest.clone();
|
||||||
|
let (wake, requests) = mpsc::sync_channel(1);
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("ferese-overview-font".into())
|
||||||
|
.spawn(move || {
|
||||||
|
while requests.recv().is_ok() {
|
||||||
|
let family = worker_latest.lock().unwrap().clone();
|
||||||
|
// Rescan on a font change so newly installed fonts remain usable.
|
||||||
|
let mut database = fontdb::Database::new();
|
||||||
|
database.load_system_fonts();
|
||||||
|
let families = [fontdb::Family::Name(&family), fontdb::Family::SansSerif];
|
||||||
|
let font = database
|
||||||
|
.query(&fontdb::Query {
|
||||||
|
families: &families,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.and_then(|id| {
|
||||||
|
database.with_face_data(id, |bytes, index| {
|
||||||
|
FontVec::try_from_vec_and_index(bytes.to_vec(), index)
|
||||||
|
.ok()
|
||||||
|
.map(FontArc::new)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
if results.send((family, font)).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let requests = FontRequests { latest, wake };
|
||||||
|
requests.request(&state.overview.font_family);
|
||||||
|
state.overview.font_requests = Some(requests);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct OverviewState {
|
pub(crate) struct OverviewState {
|
||||||
active: bool,
|
active: bool,
|
||||||
@@ -28,7 +99,7 @@ pub(crate) struct OverviewState {
|
|||||||
opacity: AnimatedValue,
|
opacity: AnimatedValue,
|
||||||
strip_offsets: HashMap<OutputId, usize>,
|
strip_offsets: HashMap<OutputId, usize>,
|
||||||
font_family: String,
|
font_family: String,
|
||||||
font_loaded: bool,
|
font_requests: Option<FontRequests>,
|
||||||
font: Option<FontArc>,
|
font: Option<FontArc>,
|
||||||
labels: HashMap<
|
labels: HashMap<
|
||||||
(String, u64),
|
(String, u64),
|
||||||
@@ -48,7 +119,7 @@ impl Default for OverviewState {
|
|||||||
opacity: AnimatedValue::new(0.0),
|
opacity: AnimatedValue::new(0.0),
|
||||||
strip_offsets: HashMap::new(),
|
strip_offsets: HashMap::new(),
|
||||||
font_family: "sans-serif".into(),
|
font_family: "sans-serif".into(),
|
||||||
font_loaded: false,
|
font_requests: None,
|
||||||
font: None,
|
font: None,
|
||||||
labels: HashMap::new(),
|
labels: HashMap::new(),
|
||||||
}
|
}
|
||||||
@@ -106,9 +177,10 @@ impl OverviewState {
|
|||||||
pub(crate) fn set_font_family(&mut self, family: String) {
|
pub(crate) fn set_font_family(&mut self, family: String) {
|
||||||
if self.font_family != family {
|
if self.font_family != family {
|
||||||
self.font_family = family;
|
self.font_family = family;
|
||||||
self.font_loaded = false;
|
// Keep the current labels visible until the replacement is ready.
|
||||||
self.font = None;
|
if let Some(requests) = &self.font_requests {
|
||||||
self.labels.clear();
|
requests.request(&self.font_family);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +191,15 @@ impl OverviewState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn complete_font_load(&mut self, family: &str, font: Option<FontArc>) -> bool {
|
||||||
|
if family != self.font_family {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.font = font;
|
||||||
|
self.labels.clear();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn label(
|
fn label(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: &str,
|
text: &str,
|
||||||
@@ -131,28 +212,6 @@ impl OverviewState {
|
|||||||
if let Some(buffer) = self.labels.get(&key) {
|
if let Some(buffer) = self.labels.get(&key) {
|
||||||
return Some(buffer.clone());
|
return Some(buffer.clone());
|
||||||
}
|
}
|
||||||
if !self.font_loaded {
|
|
||||||
self.font_loaded = true;
|
|
||||||
let mut database = fontdb::Database::new();
|
|
||||||
database.load_system_fonts();
|
|
||||||
let families = [
|
|
||||||
fontdb::Family::Name(&self.font_family),
|
|
||||||
fontdb::Family::SansSerif,
|
|
||||||
];
|
|
||||||
self.font = database
|
|
||||||
.query(&fontdb::Query {
|
|
||||||
families: &families,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.and_then(|id| {
|
|
||||||
database.with_face_data(id, |bytes, index| {
|
|
||||||
FontVec::try_from_vec_and_index(bytes.to_vec(), index)
|
|
||||||
.ok()
|
|
||||||
.map(FontArc::new)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
}
|
|
||||||
let font = self.font.as_ref()?;
|
let font = self.font.as_ref()?;
|
||||||
let scaled = font.as_scaled((13.0 * scale) as f32);
|
let scaled = font.as_scaled((13.0 * scale) as f32);
|
||||||
let width = (text
|
let width = (text
|
||||||
@@ -733,6 +792,55 @@ fn directional_distance(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn test_font() -> FontArc {
|
||||||
|
FontArc::try_from_slice(include_bytes!("../../assets/fonts/Comfortaa-Regular.otf")).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn font_reload_keeps_labels_until_current_result_arrives() {
|
||||||
|
let mut overview = OverviewState::with_font_family("first".into());
|
||||||
|
assert!(overview.complete_font_load("first", Some(test_font())));
|
||||||
|
assert!(overview.label("1", 1.0).is_some());
|
||||||
|
assert_eq!(overview.labels.len(), 1);
|
||||||
|
overview.set_font_family("second".into());
|
||||||
|
assert!(overview.font.is_some());
|
||||||
|
assert_eq!(overview.labels.len(), 1);
|
||||||
|
assert!(!overview.complete_font_load("first", None));
|
||||||
|
assert!(overview.font.is_some());
|
||||||
|
assert_eq!(overview.labels.len(), 1);
|
||||||
|
assert!(overview.complete_font_load("second", Some(test_font())));
|
||||||
|
assert!(overview.labels.is_empty());
|
||||||
|
assert!(overview.label("1", 1.0).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rapid_font_requests_are_bounded_and_keep_latest_family() {
|
||||||
|
let (wake, receiver) = mpsc::sync_channel(1);
|
||||||
|
let requests = FontRequests {
|
||||||
|
latest: Arc::new(Mutex::new(String::new())),
|
||||||
|
wake,
|
||||||
|
};
|
||||||
|
for family in ["first", "second", "third"] {
|
||||||
|
requests.request(family);
|
||||||
|
}
|
||||||
|
receiver.try_recv().unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
receiver.try_recv(),
|
||||||
|
Err(mpsc::TryRecvError::Empty)
|
||||||
|
));
|
||||||
|
assert_eq!(*requests.latest.lock().unwrap(), "third");
|
||||||
|
drop(requests);
|
||||||
|
assert!(matches!(receiver.recv(), Err(mpsc::RecvError)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn labels_do_not_load_fonts_synchronously() {
|
||||||
|
let mut overview = OverviewState::default();
|
||||||
|
assert!(overview.label("1", 1.0).is_none());
|
||||||
|
assert!(overview.font.is_none());
|
||||||
|
assert!(overview.labels.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workspace_cards_are_centered_as_a_group_on_each_output() {
|
fn workspace_cards_are_centered_as_a_group_on_each_output() {
|
||||||
for origin in [0.0, 1600.0] {
|
for origin in [0.0, 1600.0] {
|
||||||
|
|||||||
Reference in new issue
Block a user