Merge pull request #277 from mahkoh/jorth/async-text
text: render text asynchronously
This commit is contained in:
commit
6ec2e9a87b
30 changed files with 1277 additions and 571 deletions
|
|
@ -35,6 +35,8 @@ pub struct AsyncEngine {
|
||||||
yield_stash: RefCell<VecDeque<Waker>>,
|
yield_stash: RefCell<VecDeque<Waker>>,
|
||||||
stopped: Cell<bool>,
|
stopped: Cell<bool>,
|
||||||
now: Cell<Option<Time>>,
|
now: Cell<Option<Time>>,
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
idle: Cell<Option<Waker>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncEngine {
|
impl AsyncEngine {
|
||||||
|
|
@ -48,6 +50,8 @@ impl AsyncEngine {
|
||||||
yield_stash: Default::default(),
|
yield_stash: Default::default(),
|
||||||
stopped: Cell::new(false),
|
stopped: Cell::new(false),
|
||||||
now: Default::default(),
|
now: Default::default(),
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
idle: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +95,15 @@ impl AsyncEngine {
|
||||||
pub fn dispatch(&self) {
|
pub fn dispatch(&self) {
|
||||||
let mut stash = self.stash.borrow_mut();
|
let mut stash = self.stash.borrow_mut();
|
||||||
let mut yield_stash = self.yield_stash.borrow_mut();
|
let mut yield_stash = self.yield_stash.borrow_mut();
|
||||||
while self.num_queued.get() > 0 {
|
loop {
|
||||||
|
if self.num_queued.get() == 0 {
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
if let Some(idle) = self.idle.take() {
|
||||||
|
idle.wake();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
self.now.take();
|
self.now.take();
|
||||||
self.iteration.fetch_add(1);
|
self.iteration.fetch_add(1);
|
||||||
let mut phase = 0;
|
let mut phase = 0;
|
||||||
|
|
@ -116,6 +128,22 @@ impl AsyncEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
pub async fn idle(&self) {
|
||||||
|
use std::{future::poll_fn, task::Poll};
|
||||||
|
let mut register = true;
|
||||||
|
poll_fn(|ctx| {
|
||||||
|
if register {
|
||||||
|
self.idle.set(Some(ctx.waker().clone()));
|
||||||
|
register = false;
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
fn push(&self, runnable: Runnable, phase: Phase) {
|
fn push(&self, runnable: Runnable, phase: Phase) {
|
||||||
self.queues[phase as usize].push(runnable);
|
self.queues[phase as usize].push(runnable);
|
||||||
self.num_queued.fetch_add(1);
|
self.num_queued.fetch_add(1);
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
client::Client,
|
client::Client,
|
||||||
cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker},
|
cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker},
|
||||||
|
gfx_api::{ShmMemory, ShmMemoryBacking},
|
||||||
utils::vec_ext::VecExt,
|
utils::vec_ext::VecExt,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
|
error::Error,
|
||||||
mem::{ManuallyDrop, MaybeUninit},
|
mem::{ManuallyDrop, MaybeUninit},
|
||||||
|
ops::Deref,
|
||||||
ptr,
|
ptr,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::atomic::{compiler_fence, Ordering},
|
sync::atomic::{compiler_fence, Ordering},
|
||||||
|
|
@ -105,6 +108,7 @@ impl ClientMem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
pub fn fd(&self) -> &Rc<OwnedFd> {
|
pub fn fd(&self) -> &Rc<OwnedFd> {
|
||||||
&self.fd
|
&self.fd
|
||||||
}
|
}
|
||||||
|
|
@ -115,14 +119,17 @@ impl ClientMem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientMemOffset {
|
impl ClientMemOffset {
|
||||||
|
#[expect(dead_code)]
|
||||||
pub fn pool(&self) -> &ClientMem {
|
pub fn pool(&self) -> &ClientMem {
|
||||||
&self.mem
|
&self.mem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
pub fn offset(&self) -> usize {
|
pub fn offset(&self) -> usize {
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
pub fn ptr(&self) -> *const [Cell<u8>] {
|
pub fn ptr(&self) -> *const [Cell<u8>] {
|
||||||
self.data
|
self.data
|
||||||
}
|
}
|
||||||
|
|
@ -263,3 +270,20 @@ impl CpuWork for CloseMemWork {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ShmMemory for ClientMemOffset {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.data.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_access(&self) -> ShmMemoryBacking {
|
||||||
|
match self.mem.sigbus_impossible() {
|
||||||
|
true => ShmMemoryBacking::Ptr(self.data),
|
||||||
|
false => ShmMemoryBacking::Fd(self.mem.fd.deref().clone(), self.offset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn access(&self, f: &mut dyn FnMut(&[Cell<u8>])) -> Result<(), Box<dyn Error + Sync + Send>> {
|
||||||
|
self.access(f).map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,9 @@ use {
|
||||||
tasks::{self, idle},
|
tasks::{self, idle},
|
||||||
tracy::enable_profiler,
|
tracy::enable_profiler,
|
||||||
tree::{
|
tree::{
|
||||||
container_layout, container_render_data, float_layout, float_titles,
|
container_layout, container_render_positions, container_render_titles, float_layout,
|
||||||
output_render_data, DisplayNode, NodeIds, OutputNode, TearingMode, VrrMode,
|
float_titles, output_render_data, placeholder_render_textures, DisplayNode, NodeIds,
|
||||||
WorkspaceNode,
|
OutputNode, TearingMode, VrrMode, WorkspaceNode,
|
||||||
},
|
},
|
||||||
user_session::import_environment,
|
user_session::import_environment,
|
||||||
utils::{
|
utils::{
|
||||||
|
|
@ -180,13 +180,15 @@ fn start_compositor2(
|
||||||
input_device_handlers: Default::default(),
|
input_device_handlers: Default::default(),
|
||||||
theme: Default::default(),
|
theme: Default::default(),
|
||||||
pending_container_layout: Default::default(),
|
pending_container_layout: Default::default(),
|
||||||
pending_container_render_data: Default::default(),
|
pending_container_render_positions: Default::default(),
|
||||||
|
pending_container_render_title: Default::default(),
|
||||||
pending_output_render_data: Default::default(),
|
pending_output_render_data: Default::default(),
|
||||||
pending_float_layout: Default::default(),
|
pending_float_layout: Default::default(),
|
||||||
pending_float_titles: Default::default(),
|
pending_float_titles: Default::default(),
|
||||||
pending_input_popup_positioning: Default::default(),
|
pending_input_popup_positioning: Default::default(),
|
||||||
pending_toplevel_screencasts: Default::default(),
|
pending_toplevel_screencasts: Default::default(),
|
||||||
pending_screencast_reallocs_or_reconfigures: Default::default(),
|
pending_screencast_reallocs_or_reconfigures: Default::default(),
|
||||||
|
pending_placeholder_render_textures: Default::default(),
|
||||||
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
||||||
fdcloser: FdCloser::new(),
|
fdcloser: FdCloser::new(),
|
||||||
logger: logger.clone(),
|
logger: logger.clone(),
|
||||||
|
|
@ -374,9 +376,19 @@ fn start_global_event_handlers(
|
||||||
container_layout(state.clone()),
|
container_layout(state.clone()),
|
||||||
),
|
),
|
||||||
eng.spawn2(
|
eng.spawn2(
|
||||||
"container render",
|
"container render positions",
|
||||||
Phase::PostLayout,
|
Phase::PostLayout,
|
||||||
container_render_data(state.clone()),
|
container_render_positions(state.clone()),
|
||||||
|
),
|
||||||
|
eng.spawn2(
|
||||||
|
"container titles",
|
||||||
|
Phase::PostLayout,
|
||||||
|
container_render_titles(state.clone()),
|
||||||
|
),
|
||||||
|
eng.spawn2(
|
||||||
|
"placeholder textures",
|
||||||
|
Phase::PostLayout,
|
||||||
|
placeholder_render_textures(state.clone()),
|
||||||
),
|
),
|
||||||
eng.spawn2(
|
eng.spawn2(
|
||||||
"output render",
|
"output render",
|
||||||
|
|
@ -577,7 +589,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
||||||
jay_workspaces: Default::default(),
|
jay_workspaces: Default::default(),
|
||||||
may_capture: Cell::new(false),
|
may_capture: Cell::new(false),
|
||||||
has_capture: Cell::new(false),
|
has_capture: Cell::new(false),
|
||||||
title_texture: Cell::new(None),
|
title_texture: Default::default(),
|
||||||
attention_requests: Default::default(),
|
attention_requests: Default::default(),
|
||||||
render_highlight: Default::default(),
|
render_highlight: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use {
|
||||||
output_schedule::map_cursor_hz,
|
output_schedule::map_cursor_hz,
|
||||||
scale::Scale,
|
scale::Scale,
|
||||||
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
|
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
|
||||||
theme::{Color, ThemeSized, DEFAULT_FONT},
|
theme::{Color, ThemeSized},
|
||||||
tree::{
|
tree::{
|
||||||
move_ws_to_output, ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase,
|
move_ws_to_output, ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase,
|
||||||
OutputNode, TearingMode, VrrMode, WsMoveConfig,
|
OutputNode, TearingMode, VrrMode, WsMoveConfig,
|
||||||
|
|
@ -57,7 +57,7 @@ use {
|
||||||
},
|
},
|
||||||
libloading::Library,
|
libloading::Library,
|
||||||
log::Level,
|
log::Level,
|
||||||
std::{cell::Cell, ops::Deref, rc::Rc, time::Duration},
|
std::{cell::Cell, ops::Deref, rc::Rc, sync::Arc, time::Duration},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::{c, fcntl_dupfd_cloexec, OwnedFd},
|
uapi::{c, fcntl_dupfd_cloexec, OwnedFd},
|
||||||
};
|
};
|
||||||
|
|
@ -1525,15 +1525,18 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_reset_font(&self) {
|
fn handle_reset_font(&self) {
|
||||||
*self.state.theme.font.borrow_mut() = DEFAULT_FONT.to_string();
|
self.state
|
||||||
|
.theme
|
||||||
|
.font
|
||||||
|
.set(self.state.theme.default_font.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_set_font(&self, font: &str) {
|
fn handle_set_font(&self, font: &str) {
|
||||||
*self.state.theme.font.borrow_mut() = font.to_string();
|
self.state.theme.font.set(Arc::new(font.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_get_font(&self) {
|
fn handle_get_font(&self) {
|
||||||
let font = self.state.theme.font.borrow_mut().clone();
|
let font = self.state.theme.font.get().to_string();
|
||||||
self.respond(Response::GetFont { font });
|
self.respond(Response::GetFont { font });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use {
|
||||||
ptr_ext::MutPtrExt, queue::AsyncQueue, stack::Stack,
|
ptr_ext::MutPtrExt, queue::AsyncQueue, stack::Stack,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
parking_lot::Mutex,
|
parking_lot::{Condvar, Mutex},
|
||||||
std::{
|
std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
|
@ -113,18 +113,25 @@ enum Job {
|
||||||
|
|
||||||
unsafe impl Send for Job {}
|
unsafe impl Send for Job {}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CompletedJobsExchange {
|
||||||
|
queue: VecDeque<CpuJobId>,
|
||||||
|
condvar: Option<Arc<Condvar>>,
|
||||||
|
}
|
||||||
|
|
||||||
struct CpuWorkerData {
|
struct CpuWorkerData {
|
||||||
next: CpuJobIds,
|
next: CpuJobIds,
|
||||||
jobs_to_enqueue: AsyncQueue<Job>,
|
jobs_to_enqueue: AsyncQueue<Job>,
|
||||||
new_jobs: Arc<Mutex<VecDeque<Job>>>,
|
new_jobs: Arc<Mutex<VecDeque<Job>>>,
|
||||||
have_new_jobs: Rc<OwnedFd>,
|
have_new_jobs: Rc<OwnedFd>,
|
||||||
completed_jobs_remote: Arc<Mutex<VecDeque<CpuJobId>>>,
|
completed_jobs_remote: Arc<Mutex<CompletedJobsExchange>>,
|
||||||
completed_jobs_local: RefCell<VecDeque<CpuJobId>>,
|
completed_jobs_local: RefCell<VecDeque<CpuJobId>>,
|
||||||
have_completed_jobs: Rc<OwnedFd>,
|
have_completed_jobs: Rc<OwnedFd>,
|
||||||
pending_jobs: CopyHashMap<CpuJobId, Rc<PendingJobData>>,
|
pending_jobs: CopyHashMap<CpuJobId, Rc<PendingJobData>>,
|
||||||
ring: Rc<IoUring>,
|
ring: Rc<IoUring>,
|
||||||
_stop: OwnedFd,
|
_stop: OwnedFd,
|
||||||
pending_job_data_cache: Stack<Rc<PendingJobData>>,
|
pending_job_data_cache: Stack<Rc<PendingJobData>>,
|
||||||
|
sync_wake_condvar: Arc<Condvar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
linear_ids!(CpuJobIds, CpuJobId, u64);
|
linear_ids!(CpuJobIds, CpuJobId, u64);
|
||||||
|
|
@ -172,12 +179,16 @@ impl Drop for PendingJob {
|
||||||
self.job_data.state.set(PendingJobState::Abandoned);
|
self.job_data.state.set(PendingJobState::Abandoned);
|
||||||
data.jobs_to_enqueue.push(Job::Cancel { id });
|
data.jobs_to_enqueue.push(Job::Cancel { id });
|
||||||
data.do_equeue_jobs();
|
data.do_equeue_jobs();
|
||||||
let mut buf = 0u64;
|
loop {
|
||||||
while data.pending_jobs.contains(&id) {
|
|
||||||
if let Err(e) = uapi::read(data.have_completed_jobs.raw(), &mut buf) {
|
|
||||||
panic!("Could not wait for job completions: {}", ErrorFmt(e));
|
|
||||||
}
|
|
||||||
data.dispatch_completions();
|
data.dispatch_completions();
|
||||||
|
if !data.pending_jobs.contains(&id) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let mut remote = data.completed_jobs_remote.lock();
|
||||||
|
while remote.queue.is_empty() {
|
||||||
|
remote.condvar = Some(data.sync_wake_condvar.clone());
|
||||||
|
data.sync_wake_condvar.wait(&mut remote);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PendingJobState::Abandoned => {}
|
PendingJobState::Abandoned => {}
|
||||||
|
|
@ -204,7 +215,7 @@ impl CpuWorkerData {
|
||||||
|
|
||||||
fn dispatch_completions(&self) {
|
fn dispatch_completions(&self) {
|
||||||
let completions = &mut *self.completed_jobs_local.borrow_mut();
|
let completions = &mut *self.completed_jobs_local.borrow_mut();
|
||||||
mem::swap(completions, &mut *self.completed_jobs_remote.lock());
|
mem::swap(completions, &mut self.completed_jobs_remote.lock().queue);
|
||||||
while let Some(id) = completions.pop_front() {
|
while let Some(id) = completions.pop_front() {
|
||||||
let job_data = self.pending_jobs.remove(&id).unwrap();
|
let job_data = self.pending_jobs.remove(&id).unwrap();
|
||||||
let job = job_data.job.take().unwrap();
|
let job = job_data.job.take().unwrap();
|
||||||
|
|
@ -242,7 +253,7 @@ impl CpuWorkerData {
|
||||||
impl CpuWorker {
|
impl CpuWorker {
|
||||||
pub fn new(ring: &Rc<IoUring>, eng: &Rc<AsyncEngine>) -> Result<Self, CpuWorkerError> {
|
pub fn new(ring: &Rc<IoUring>, eng: &Rc<AsyncEngine>) -> Result<Self, CpuWorkerError> {
|
||||||
let new_jobs: Arc<Mutex<VecDeque<Job>>> = Default::default();
|
let new_jobs: Arc<Mutex<VecDeque<Job>>> = Default::default();
|
||||||
let completed_jobs: Arc<Mutex<VecDeque<CpuJobId>>> = Default::default();
|
let completed_jobs: Arc<Mutex<CompletedJobsExchange>> = Default::default();
|
||||||
let (stop_read, stop_write) =
|
let (stop_read, stop_write) =
|
||||||
uapi::pipe2(c::O_CLOEXEC).map_err(|e| CpuWorkerError::Pipe(e.into()))?;
|
uapi::pipe2(c::O_CLOEXEC).map_err(|e| CpuWorkerError::Pipe(e.into()))?;
|
||||||
let have_new_jobs =
|
let have_new_jobs =
|
||||||
|
|
@ -281,6 +292,7 @@ impl CpuWorker {
|
||||||
ring: ring.clone(),
|
ring: ring.clone(),
|
||||||
_stop: stop_read,
|
_stop: stop_read,
|
||||||
pending_job_data_cache: Default::default(),
|
pending_job_data_cache: Default::default(),
|
||||||
|
sync_wake_condvar: Arc::new(Condvar::new()),
|
||||||
});
|
});
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_completions_listener: eng.spawn(
|
_completions_listener: eng.spawn(
|
||||||
|
|
@ -309,11 +321,28 @@ impl CpuWorker {
|
||||||
job_data,
|
job_data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
pub fn wait_idle(&self) -> bool {
|
||||||
|
let was_idle = self.data.pending_jobs.is_empty();
|
||||||
|
loop {
|
||||||
|
self.data.dispatch_completions();
|
||||||
|
if self.data.pending_jobs.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let mut remote = self.data.completed_jobs_remote.lock();
|
||||||
|
while remote.queue.is_empty() {
|
||||||
|
remote.condvar = Some(self.data.sync_wake_condvar.clone());
|
||||||
|
self.data.sync_wake_condvar.wait(&mut remote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
was_idle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work(
|
fn work(
|
||||||
new_jobs: Arc<Mutex<VecDeque<Job>>>,
|
new_jobs: Arc<Mutex<VecDeque<Job>>>,
|
||||||
completed_jobs: Arc<Mutex<VecDeque<CpuJobId>>>,
|
completed_jobs: Arc<Mutex<CompletedJobsExchange>>,
|
||||||
stop: OwnedFd,
|
stop: OwnedFd,
|
||||||
have_new_jobs: OwnedFd,
|
have_new_jobs: OwnedFd,
|
||||||
have_completed_jobs: OwnedFd,
|
have_completed_jobs: OwnedFd,
|
||||||
|
|
@ -343,7 +372,7 @@ fn work(
|
||||||
struct Worker {
|
struct Worker {
|
||||||
eng: Rc<AsyncEngine>,
|
eng: Rc<AsyncEngine>,
|
||||||
ring: Rc<IoUring>,
|
ring: Rc<IoUring>,
|
||||||
completed_jobs: Arc<Mutex<VecDeque<CpuJobId>>>,
|
completed_jobs: Arc<Mutex<CompletedJobsExchange>>,
|
||||||
have_completed_jobs: OwnedFd,
|
have_completed_jobs: OwnedFd,
|
||||||
async_jobs: CopyHashMap<CpuJobId, AsyncJob>,
|
async_jobs: CopyHashMap<CpuJobId, AsyncJob>,
|
||||||
stopped: Cell<bool>,
|
stopped: Cell<bool>,
|
||||||
|
|
@ -428,7 +457,14 @@ impl Worker {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_completion(&self, id: CpuJobId) {
|
fn send_completion(&self, id: CpuJobId) {
|
||||||
self.completed_jobs.lock().push_back(id);
|
let cv = {
|
||||||
|
let mut exchange = self.completed_jobs.lock();
|
||||||
|
exchange.queue.push_back(id);
|
||||||
|
exchange.condvar.take()
|
||||||
|
};
|
||||||
|
if let Some(cv) = cv {
|
||||||
|
cv.notify_all();
|
||||||
|
}
|
||||||
if let Err(e) = uapi::eventfd_write(self.have_completed_jobs.raw(), 1) {
|
if let Err(e) = uapi::eventfd_write(self.have_completed_jobs.raw(), 1) {
|
||||||
panic!("Could not signal job completion: {}", ErrorFmt(e));
|
panic!("Could not signal job completion: {}", ErrorFmt(e));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
allocator::Allocator,
|
allocator::Allocator,
|
||||||
clientmem::ClientMemOffset,
|
|
||||||
cpu_worker::CpuWorker,
|
cpu_worker::CpuWorker,
|
||||||
cursor::Cursor,
|
cursor::Cursor,
|
||||||
damage::DamageVisualizer,
|
damage::DamageVisualizer,
|
||||||
|
|
@ -513,11 +512,37 @@ pub struct PendingShmUpload {
|
||||||
id: u64,
|
id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ShmMemory {
|
||||||
|
fn len(&self) -> usize;
|
||||||
|
fn safe_access(&self) -> ShmMemoryBacking;
|
||||||
|
fn access(&self, f: &mut dyn FnMut(&[Cell<u8>])) -> Result<(), Box<dyn Error + Sync + Send>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ShmMemoryBacking {
|
||||||
|
Ptr(*const [Cell<u8>]),
|
||||||
|
Fd(Rc<OwnedFd>, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShmMemory for Vec<Cell<u8>> {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_access(&self) -> ShmMemoryBacking {
|
||||||
|
ShmMemoryBacking::Ptr(&**self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn access(&self, f: &mut dyn FnMut(&[Cell<u8>])) -> Result<(), Box<dyn Error + Sync + Send>> {
|
||||||
|
f(self);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait AsyncShmGfxTexture: GfxTexture {
|
pub trait AsyncShmGfxTexture: GfxTexture {
|
||||||
fn async_upload(
|
fn async_upload(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
||||||
mem: &Rc<ClientMemOffset>,
|
mem: Rc<dyn ShmMemory>,
|
||||||
damage: Region,
|
damage: Region,
|
||||||
) -> Result<Option<PendingShmUpload>, GfxError>;
|
) -> Result<Option<PendingShmUpload>, GfxError>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ macro_rules! dynload {
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
clientmem::ClientMemError,
|
|
||||||
gfx_api::{
|
gfx_api::{
|
||||||
AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture,
|
AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture,
|
||||||
ReleaseSync, SyncFile,
|
ReleaseSync, SyncFile,
|
||||||
|
|
@ -95,7 +94,7 @@ use {
|
||||||
},
|
},
|
||||||
isnt::std_1::vec::IsntVecExt,
|
isnt::std_1::vec::IsntVecExt,
|
||||||
once_cell::sync::Lazy,
|
once_cell::sync::Lazy,
|
||||||
std::{cell::RefCell, rc::Rc, sync::Arc},
|
std::{cell::RefCell, error::Error, rc::Rc, sync::Arc},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -199,7 +198,7 @@ enum RenderError {
|
||||||
#[error("Buffer format {0} is not supported for shm buffers in OpenGL context")]
|
#[error("Buffer format {0} is not supported for shm buffers in OpenGL context")]
|
||||||
UnsupportedShmFormat(&'static str),
|
UnsupportedShmFormat(&'static str),
|
||||||
#[error("Could not access the client memory")]
|
#[error("Could not access the client memory")]
|
||||||
AccessFailed(#[source] ClientMemError),
|
AccessFailed(#[source] Box<dyn Error + Sync + Send>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
clientmem::ClientMemOffset,
|
|
||||||
format::Format,
|
format::Format,
|
||||||
gfx_api::{
|
gfx_api::{
|
||||||
AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxError, GfxTexture, PendingShmUpload,
|
AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxError, GfxTexture, PendingShmUpload,
|
||||||
ShmGfxTexture,
|
ShmGfxTexture, ShmMemory,
|
||||||
},
|
},
|
||||||
gfx_apis::gl::{
|
gfx_apis::gl::{
|
||||||
gl::texture::GlTexture,
|
gl::texture::GlTexture,
|
||||||
|
|
@ -102,12 +101,15 @@ impl AsyncShmGfxTexture for Texture {
|
||||||
fn async_upload(
|
fn async_upload(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
_callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
_callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
||||||
mem: &Rc<ClientMemOffset>,
|
mem: Rc<dyn ShmMemory>,
|
||||||
damage: Region,
|
_damage: Region,
|
||||||
) -> Result<Option<PendingShmUpload>, GfxError> {
|
) -> Result<Option<PendingShmUpload>, GfxError> {
|
||||||
mem.access(|data| self.clone().sync_upload(data, damage))
|
let mut res = Ok(());
|
||||||
.map_err(RenderError::AccessFailed)??;
|
mem.access(&mut |data| {
|
||||||
Ok(None)
|
res = self.clone().sync_upload(data, Region::default());
|
||||||
|
})
|
||||||
|
.map_err(RenderError::AccessFailed)?;
|
||||||
|
res.map(|_| None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_upload(self: Rc<Self>, data: &[Cell<u8>], _damage: Region) -> Result<(), GfxError> {
|
fn sync_upload(self: Rc<Self>, data: &[Cell<u8>], _damage: Region) -> Result<(), GfxError> {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
clientmem::ClientMemOffset,
|
|
||||||
format::Format,
|
format::Format,
|
||||||
gfx_api::{
|
gfx_api::{
|
||||||
AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback,
|
AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback,
|
||||||
AsyncShmGfxTextureUploadCancellable, GfxApiOpt, GfxError, GfxFramebuffer, GfxImage,
|
AsyncShmGfxTextureUploadCancellable, GfxApiOpt, GfxError, GfxFramebuffer, GfxImage,
|
||||||
GfxTexture, PendingShmUpload, ReleaseSync, ShmGfxTexture, SyncFile,
|
GfxTexture, PendingShmUpload, ReleaseSync, ShmGfxTexture, ShmMemory, SyncFile,
|
||||||
},
|
},
|
||||||
gfx_apis::vulkan::{
|
gfx_apis::vulkan::{
|
||||||
allocator::VulkanAllocation, device::VulkanDevice, format::VulkanModifierLimits,
|
allocator::VulkanAllocation, device::VulkanDevice, format::VulkanModifierLimits,
|
||||||
|
|
@ -579,13 +578,13 @@ impl AsyncShmGfxTexture for VulkanImage {
|
||||||
fn async_upload(
|
fn async_upload(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
||||||
mem: &Rc<ClientMemOffset>,
|
mem: Rc<dyn ShmMemory>,
|
||||||
damage: Region,
|
damage: Region,
|
||||||
) -> Result<Option<PendingShmUpload>, GfxError> {
|
) -> Result<Option<PendingShmUpload>, GfxError> {
|
||||||
let VulkanImageMemory::Internal(shm) = &self.ty else {
|
let VulkanImageMemory::Internal(shm) = &self.ty else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
let pending = shm.async_upload(&self, mem, damage, callback)?;
|
let pending = shm.async_upload(&self, &mem, damage, callback)?;
|
||||||
Ok(pending)
|
Ok(pending)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
clientmem::ClientMemOffset,
|
|
||||||
cpu_worker::{
|
cpu_worker::{
|
||||||
jobs::{
|
jobs::{
|
||||||
img_copy::ImgCopyWork,
|
img_copy::ImgCopyWork,
|
||||||
|
|
@ -9,7 +8,9 @@ use {
|
||||||
CpuJob, CpuWork, CpuWorker,
|
CpuJob, CpuWork, CpuWorker,
|
||||||
},
|
},
|
||||||
format::{Format, FormatShmInfo},
|
format::{Format, FormatShmInfo},
|
||||||
gfx_api::{AsyncShmGfxTextureCallback, PendingShmUpload, SyncFile},
|
gfx_api::{
|
||||||
|
AsyncShmGfxTextureCallback, PendingShmUpload, ShmMemory, ShmMemoryBacking, SyncFile,
|
||||||
|
},
|
||||||
gfx_apis::vulkan::{
|
gfx_apis::vulkan::{
|
||||||
allocator::VulkanAllocation,
|
allocator::VulkanAllocation,
|
||||||
command::VulkanCommandBuffer,
|
command::VulkanCommandBuffer,
|
||||||
|
|
@ -329,7 +330,7 @@ impl VulkanShmImage {
|
||||||
pub fn async_upload(
|
pub fn async_upload(
|
||||||
&self,
|
&self,
|
||||||
img: &Rc<VulkanImage>,
|
img: &Rc<VulkanImage>,
|
||||||
client_mem: &Rc<ClientMemOffset>,
|
client_mem: &Rc<dyn ShmMemory>,
|
||||||
damage: Region,
|
damage: Region,
|
||||||
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
||||||
) -> Result<Option<PendingShmUpload>, VulkanError> {
|
) -> Result<Option<PendingShmUpload>, VulkanError> {
|
||||||
|
|
@ -350,13 +351,13 @@ impl VulkanShmImage {
|
||||||
&self,
|
&self,
|
||||||
img: &Rc<VulkanImage>,
|
img: &Rc<VulkanImage>,
|
||||||
data: &VulkanShmImageAsyncData,
|
data: &VulkanShmImageAsyncData,
|
||||||
client_mem: &Rc<ClientMemOffset>,
|
client_mem: &Rc<dyn ShmMemory>,
|
||||||
mut damage: Region,
|
mut damage: Region,
|
||||||
) -> Result<(), VulkanError> {
|
) -> Result<(), VulkanError> {
|
||||||
if data.busy.get() {
|
if data.busy.get() {
|
||||||
return Err(VulkanError::AsyncCopyBusy);
|
return Err(VulkanError::AsyncCopyBusy);
|
||||||
}
|
}
|
||||||
if self.size > client_mem.ptr().len() as u64 {
|
if self.size > client_mem.len() as u64 {
|
||||||
return Err(VulkanError::InvalidBufferSize);
|
return Err(VulkanError::InvalidBufferSize);
|
||||||
}
|
}
|
||||||
data.busy.set(true);
|
data.busy.set(true);
|
||||||
|
|
@ -530,7 +531,7 @@ impl VulkanShmImage {
|
||||||
fn async_upload_after_allocation(
|
fn async_upload_after_allocation(
|
||||||
&self,
|
&self,
|
||||||
img: &Rc<VulkanImage>,
|
img: &Rc<VulkanImage>,
|
||||||
client_mem: &Rc<ClientMemOffset>,
|
client_mem: &Rc<dyn ShmMemory>,
|
||||||
res: Result<VulkanStagingBuffer, VulkanError>,
|
res: Result<VulkanStagingBuffer, VulkanError>,
|
||||||
) -> Result<(), VulkanError> {
|
) -> Result<(), VulkanError> {
|
||||||
let staging = Rc::new(res?);
|
let staging = Rc::new(res?);
|
||||||
|
|
@ -546,73 +547,76 @@ impl VulkanShmImage {
|
||||||
data: &VulkanShmImageAsyncData,
|
data: &VulkanShmImageAsyncData,
|
||||||
staging: &VulkanStagingBuffer,
|
staging: &VulkanStagingBuffer,
|
||||||
copies: &[BufferImageCopy2],
|
copies: &[BufferImageCopy2],
|
||||||
client_mem: &Rc<ClientMemOffset>,
|
client_mem: &Rc<dyn ShmMemory>,
|
||||||
) -> Result<(), VulkanError> {
|
) -> Result<(), VulkanError> {
|
||||||
img.renderer.check_defunct()?;
|
img.renderer.check_defunct()?;
|
||||||
|
|
||||||
let id = img.renderer.allocate_point();
|
let id = img.renderer.allocate_point();
|
||||||
let pending;
|
let pending;
|
||||||
if client_mem.pool().sigbus_impossible() {
|
match client_mem.safe_access() {
|
||||||
let mut job = data.copy_job.take().unwrap_or_else(|| {
|
ShmMemoryBacking::Ptr(ptr) => {
|
||||||
Box::new(CopyUploadJob {
|
let mut job = data.copy_job.take().unwrap_or_else(|| {
|
||||||
img: None,
|
Box::new(CopyUploadJob {
|
||||||
id,
|
img: None,
|
||||||
_mem: None,
|
id,
|
||||||
work: unsafe { ImgCopyWork::new() },
|
_mem: None,
|
||||||
})
|
work: unsafe { ImgCopyWork::new() },
|
||||||
});
|
})
|
||||||
job.id = id;
|
});
|
||||||
job.img = Some(img.clone());
|
job.id = id;
|
||||||
job._mem = Some(client_mem.clone());
|
job.img = Some(img.clone());
|
||||||
job.work.src = client_mem.ptr() as _;
|
job._mem = Some(client_mem.clone());
|
||||||
job.work.dst = staging.allocation.mem.unwrap();
|
job.work.src = ptr as _;
|
||||||
job.work.width = img.width as _;
|
job.work.dst = staging.allocation.mem.unwrap();
|
||||||
job.work.stride = img.stride as _;
|
job.work.width = img.width as _;
|
||||||
job.work.bpp = self.shm_info.bpp as _;
|
job.work.stride = img.stride as _;
|
||||||
job.work.rects.clear();
|
job.work.bpp = self.shm_info.bpp as _;
|
||||||
for copy in copies {
|
job.work.rects.clear();
|
||||||
job.work.rects.push(
|
for copy in copies {
|
||||||
Rect::new_sized(
|
job.work.rects.push(
|
||||||
copy.image_offset.x as _,
|
Rect::new_sized(
|
||||||
copy.image_offset.y as _,
|
copy.image_offset.x as _,
|
||||||
copy.image_extent.width as _,
|
copy.image_offset.y as _,
|
||||||
copy.image_extent.height as _,
|
copy.image_extent.width as _,
|
||||||
)
|
copy.image_extent.height as _,
|
||||||
.unwrap(),
|
)
|
||||||
);
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pending = data.cpu.submit(job);
|
||||||
}
|
}
|
||||||
pending = data.cpu.submit(job);
|
ShmMemoryBacking::Fd(fd, offset) => {
|
||||||
} else {
|
let mut min_offset = client_mem.len() as u64;
|
||||||
let mut min_offset = client_mem.ptr().len() as u64;
|
let mut max_offset = 0;
|
||||||
let mut max_offset = 0;
|
for copy in copies {
|
||||||
for copy in copies {
|
min_offset = min_offset.min(copy.buffer_offset);
|
||||||
min_offset = min_offset.min(copy.buffer_offset);
|
let len = img.stride * (copy.image_extent.height - 1)
|
||||||
let len = img.stride * (copy.image_extent.height - 1)
|
+ copy.image_extent.width * self.shm_info.bpp;
|
||||||
+ copy.image_extent.width * self.shm_info.bpp;
|
max_offset = max_offset.max(copy.buffer_offset + len as u64);
|
||||||
max_offset = max_offset.max(copy.buffer_offset + len as u64);
|
}
|
||||||
|
let mut job = data.io_job.take().unwrap_or_else(|| {
|
||||||
|
Box::new(IoUploadJob {
|
||||||
|
img: None,
|
||||||
|
id,
|
||||||
|
_mem: None,
|
||||||
|
work: unsafe { ReadWriteWork::new() },
|
||||||
|
fd: None,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
job.id = id;
|
||||||
|
job.img = Some(img.clone());
|
||||||
|
job._mem = Some(client_mem.clone());
|
||||||
|
job.fd = Some(fd.clone());
|
||||||
|
unsafe {
|
||||||
|
let config = job.work.config();
|
||||||
|
config.fd = fd.raw();
|
||||||
|
config.offset = offset + min_offset as usize;
|
||||||
|
config.ptr = staging.allocation.mem.unwrap().add(min_offset as _);
|
||||||
|
config.len = max_offset.saturating_sub(min_offset) as usize;
|
||||||
|
config.write = false;
|
||||||
|
}
|
||||||
|
pending = data.cpu.submit(job);
|
||||||
}
|
}
|
||||||
let mut job = data.io_job.take().unwrap_or_else(|| {
|
|
||||||
Box::new(IoUploadJob {
|
|
||||||
img: None,
|
|
||||||
id,
|
|
||||||
_mem: None,
|
|
||||||
work: unsafe { ReadWriteWork::new() },
|
|
||||||
fd: None,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
job.id = id;
|
|
||||||
job.img = Some(img.clone());
|
|
||||||
job._mem = Some(client_mem.clone());
|
|
||||||
job.fd = Some(client_mem.pool().fd().clone());
|
|
||||||
unsafe {
|
|
||||||
let config = job.work.config();
|
|
||||||
config.fd = client_mem.pool().fd().raw();
|
|
||||||
config.offset = client_mem.offset() + min_offset as usize;
|
|
||||||
config.ptr = staging.allocation.mem.unwrap().add(min_offset as _);
|
|
||||||
config.len = max_offset.saturating_sub(min_offset) as usize;
|
|
||||||
config.write = false;
|
|
||||||
}
|
|
||||||
pending = data.cpu.submit(job);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img.renderer.pending_cpu_jobs.set(id, pending);
|
img.renderer.pending_cpu_jobs.set(id, pending);
|
||||||
|
|
@ -653,7 +657,7 @@ impl VulkanShmImage {
|
||||||
pub(super) struct IoUploadJob {
|
pub(super) struct IoUploadJob {
|
||||||
img: Option<Rc<VulkanImage>>,
|
img: Option<Rc<VulkanImage>>,
|
||||||
id: u64,
|
id: u64,
|
||||||
_mem: Option<Rc<ClientMemOffset>>,
|
_mem: Option<Rc<dyn ShmMemory>>,
|
||||||
fd: Option<Rc<OwnedFd>>,
|
fd: Option<Rc<OwnedFd>>,
|
||||||
work: ReadWriteWork,
|
work: ReadWriteWork,
|
||||||
}
|
}
|
||||||
|
|
@ -661,7 +665,7 @@ pub(super) struct IoUploadJob {
|
||||||
pub(super) struct CopyUploadJob {
|
pub(super) struct CopyUploadJob {
|
||||||
img: Option<Rc<VulkanImage>>,
|
img: Option<Rc<VulkanImage>>,
|
||||||
id: u64,
|
id: u64,
|
||||||
_mem: Option<Rc<ClientMemOffset>>,
|
_mem: Option<Rc<dyn ShmMemory>>,
|
||||||
work: ImgCopyWork,
|
work: ImgCopyWork,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ fn schedule_async_upload(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
back_tex
|
back_tex
|
||||||
.async_upload(node_ref.clone(), mem, back.damage.get())
|
.async_upload(node_ref.clone(), mem.clone(), back.damage.get())
|
||||||
.map_err(WlSurfaceError::PrepareAsyncUpload)
|
.map_err(WlSurfaceError::PrepareAsyncUpload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,9 @@ impl TestClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sync(&self) {
|
pub async fn sync(&self) {
|
||||||
self.run.state.eng.yield_now().await;
|
|
||||||
self.run.sync().await;
|
self.run.sync().await;
|
||||||
self.tran.sync().await;
|
self.tran.sync().await;
|
||||||
self.run.state.eng.yield_now().await;
|
self.run.state.idle().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn take_screenshot(&self, include_cursor: bool) -> Result<Vec<u8>, TestError> {
|
pub async fn take_screenshot(&self, include_cursor: bool) -> Result<Vec<u8>, TestError> {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
allocator::{Allocator, AllocatorError, BufferObject, BufferUsage},
|
allocator::{Allocator, AllocatorError, BufferObject, BufferUsage},
|
||||||
clientmem::{ClientMemError, ClientMemOffset},
|
|
||||||
cpu_worker::CpuWorker,
|
cpu_worker::CpuWorker,
|
||||||
format::{Format, ARGB8888, XRGB8888},
|
format::{Format, ARGB8888, XRGB8888},
|
||||||
gfx_api::{
|
gfx_api::{
|
||||||
AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FillRect,
|
AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FillRect,
|
||||||
FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage,
|
FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage,
|
||||||
GfxTexture, GfxWriteModifier, PendingShmUpload, ReleaseSync, ResetStatus,
|
GfxTexture, GfxWriteModifier, PendingShmUpload, ReleaseSync, ResetStatus,
|
||||||
ShmGfxTexture, SyncFile,
|
ShmGfxTexture, ShmMemory, SyncFile,
|
||||||
},
|
},
|
||||||
rect::{Rect, Region},
|
rect::{Rect, Region},
|
||||||
theme::Color,
|
theme::Color,
|
||||||
|
|
@ -20,6 +19,7 @@ use {
|
||||||
std::{
|
std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
error::Error,
|
||||||
ffi::CString,
|
ffi::CString,
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
|
|
@ -36,7 +36,7 @@ enum TestGfxError {
|
||||||
#[error("Could not import dmabuf")]
|
#[error("Could not import dmabuf")]
|
||||||
ImportDmaBuf(#[source] AllocatorError),
|
ImportDmaBuf(#[source] AllocatorError),
|
||||||
#[error("Could not access the client memory")]
|
#[error("Could not access the client memory")]
|
||||||
AccessFailed(#[source] ClientMemError),
|
AccessFailed(#[source] Box<dyn Error + Sync + Send>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TestGfxError> for GfxError {
|
impl From<TestGfxError> for GfxError {
|
||||||
|
|
@ -336,12 +336,15 @@ impl AsyncShmGfxTexture for TestGfxImage {
|
||||||
fn async_upload(
|
fn async_upload(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
_callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
_callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
||||||
mem: &Rc<ClientMemOffset>,
|
mem: Rc<dyn ShmMemory>,
|
||||||
damage: Region,
|
_damage: Region,
|
||||||
) -> Result<Option<PendingShmUpload>, GfxError> {
|
) -> Result<Option<PendingShmUpload>, GfxError> {
|
||||||
mem.access(|d| self.clone().sync_upload(d, damage))
|
let mut res = Ok(());
|
||||||
.map_err(TestGfxError::AccessFailed)??;
|
mem.access(&mut |d| {
|
||||||
Ok(None)
|
res = self.clone().sync_upload(d, Region::default());
|
||||||
|
})
|
||||||
|
.map_err(TestGfxError::AccessFailed)?;
|
||||||
|
res.map(|_| None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_upload(self: Rc<Self>, mem: &[Cell<u8>], _damage: Region) -> Result<(), GfxError> {
|
fn sync_upload(self: Rc<Self>, mem: &[Cell<u8>], _damage: Region) -> Result<(), GfxError> {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ impl TestJayCompositor {
|
||||||
&self,
|
&self,
|
||||||
include_cursor: bool,
|
include_cursor: bool,
|
||||||
) -> Result<(DmaBuf, Option<Rc<OwnedFd>>), TestError> {
|
) -> Result<(DmaBuf, Option<Rc<OwnedFd>>), TestError> {
|
||||||
|
self.tran.sync().await;
|
||||||
|
self.tran.run.state.idle().await;
|
||||||
let js = Rc::new(TestJayScreenshot {
|
let js = Rc::new(TestJayScreenshot {
|
||||||
id: self.tran.id(),
|
id: self.tran.id(),
|
||||||
state: self.tran.run.state.clone(),
|
state: self.tran.run.state.clone(),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ mod ptl_display;
|
||||||
mod ptl_remote_desktop;
|
mod ptl_remote_desktop;
|
||||||
mod ptl_render_ctx;
|
mod ptl_render_ctx;
|
||||||
mod ptl_screencast;
|
mod ptl_screencast;
|
||||||
|
mod ptl_text;
|
||||||
mod ptr_gui;
|
mod ptr_gui;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
|
|
||||||
101
src/portal/ptl_text.rs
Normal file
101
src/portal/ptl_text.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
format::ARGB8888,
|
||||||
|
gfx_api::{GfxContext, GfxTexture},
|
||||||
|
pango::{
|
||||||
|
consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE},
|
||||||
|
CairoContext, CairoImageSurface, PangoCairoContext, PangoFontDescription, PangoLayout,
|
||||||
|
},
|
||||||
|
rect::Rect,
|
||||||
|
theme::Color,
|
||||||
|
},
|
||||||
|
std::{ops::Neg, rc::Rc, sync::Arc},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Data {
|
||||||
|
image: Rc<CairoImageSurface>,
|
||||||
|
cctx: Rc<CairoContext>,
|
||||||
|
_pctx: Rc<PangoCairoContext>,
|
||||||
|
_fd: PangoFontDescription,
|
||||||
|
layout: PangoLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_data(font: &str, width: i32, height: i32, scale: Option<f64>) -> Option<Data> {
|
||||||
|
let image = CairoImageSurface::new_image_surface(CAIRO_FORMAT_ARGB32, width, height).ok()?;
|
||||||
|
let cctx = image.create_context().ok()?;
|
||||||
|
let pctx = cctx.create_pango_context().ok()?;
|
||||||
|
let mut fd = PangoFontDescription::from_string(font);
|
||||||
|
if let Some(scale) = scale {
|
||||||
|
fd.set_size((fd.size() as f64 * scale).round() as _);
|
||||||
|
}
|
||||||
|
let layout = pctx.create_layout().ok()?;
|
||||||
|
layout.set_font_description(&fd);
|
||||||
|
Some(Data {
|
||||||
|
image,
|
||||||
|
cctx,
|
||||||
|
_pctx: pctx,
|
||||||
|
_fd: fd,
|
||||||
|
layout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn measure(font: &str, text: &str, scale: Option<f64>, full: bool) -> Option<TextMeasurement> {
|
||||||
|
let data = create_data(font, 1, 1, scale)?;
|
||||||
|
data.layout.set_text(text);
|
||||||
|
let mut res = TextMeasurement::default();
|
||||||
|
res.ink_rect = data.layout.inc_pixel_rect();
|
||||||
|
if full {
|
||||||
|
res.logical_rect = data.layout.logical_pixel_rect();
|
||||||
|
res.baseline = data.layout.pixel_baseline();
|
||||||
|
}
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
pub struct TextMeasurement {
|
||||||
|
pub ink_rect: Rect,
|
||||||
|
pub logical_rect: Rect,
|
||||||
|
pub baseline: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
ctx: &Rc<dyn GfxContext>,
|
||||||
|
height: Option<i32>,
|
||||||
|
font: &Arc<String>,
|
||||||
|
text: &str,
|
||||||
|
color: Color,
|
||||||
|
scale: Option<f64>,
|
||||||
|
include_measurements: bool,
|
||||||
|
) -> Option<(Rc<dyn GfxTexture>, TextMeasurement)> {
|
||||||
|
let measurement = measure(font, text, scale, include_measurements)?;
|
||||||
|
let y = match height {
|
||||||
|
Some(_) => None,
|
||||||
|
_ => Some(measurement.ink_rect.y1().neg()),
|
||||||
|
};
|
||||||
|
let x = measurement.ink_rect.x1().neg();
|
||||||
|
let width = measurement.ink_rect.width();
|
||||||
|
let height = height.unwrap_or(measurement.ink_rect.height());
|
||||||
|
let data = create_data(font, width, height, scale)?;
|
||||||
|
data.layout.set_text(text);
|
||||||
|
let font_height = data.layout.pixel_size().1;
|
||||||
|
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
|
||||||
|
data.cctx
|
||||||
|
.set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _);
|
||||||
|
let y = y.unwrap_or((height - font_height) / 2);
|
||||||
|
data.cctx.move_to(x as f64, y as f64);
|
||||||
|
data.layout.show_layout();
|
||||||
|
data.image.flush();
|
||||||
|
let bytes = data.image.data().ok()?;
|
||||||
|
ctx.clone()
|
||||||
|
.shmem_texture(
|
||||||
|
None,
|
||||||
|
bytes,
|
||||||
|
ARGB8888,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data.image.stride(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.map(|t| (t.into_texture(), measurement))
|
||||||
|
}
|
||||||
|
|
@ -5,12 +5,16 @@ use {
|
||||||
cursor::KnownCursor,
|
cursor::KnownCursor,
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
format::ARGB8888,
|
format::ARGB8888,
|
||||||
gfx_api::{needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, ReleaseSync},
|
gfx_api::{
|
||||||
|
needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync,
|
||||||
|
},
|
||||||
ifs::zwlr_layer_shell_v1::OVERLAY,
|
ifs::zwlr_layer_shell_v1::OVERLAY,
|
||||||
portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
portal::{
|
||||||
|
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
||||||
|
ptl_text::{self, TextMeasurement},
|
||||||
|
},
|
||||||
renderer::renderer_base::RendererBase,
|
renderer::renderer_base::RendererBase,
|
||||||
scale::Scale,
|
scale::Scale,
|
||||||
text::{self, TextMeasurement, TextTexture},
|
|
||||||
theme::Color,
|
theme::Color,
|
||||||
utils::{
|
utils::{
|
||||||
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||||
|
|
@ -31,10 +35,10 @@ use {
|
||||||
},
|
},
|
||||||
ahash::AHashSet,
|
ahash::AHashSet,
|
||||||
std::{
|
std::{
|
||||||
borrow::Cow,
|
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -117,8 +121,8 @@ pub struct Button {
|
||||||
pub bg_color: Cell<Color>,
|
pub bg_color: Cell<Color>,
|
||||||
pub bg_hover_color: Cell<Color>,
|
pub bg_hover_color: Cell<Color>,
|
||||||
pub text: RefCell<String>,
|
pub text: RefCell<String>,
|
||||||
pub font: RefCell<Cow<'static, str>>,
|
pub font: Arc<String>,
|
||||||
pub tex: CloneCell<Option<TextTexture>>,
|
pub tex: CloneCell<Option<Rc<dyn GfxTexture>>>,
|
||||||
pub owner: CloneCell<Option<Rc<dyn ButtonOwner>>>,
|
pub owner: CloneCell<Option<Rc<dyn ButtonOwner>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +143,7 @@ impl Default for Button {
|
||||||
bg_color: Cell::new(Color::from_gray(255)),
|
bg_color: Cell::new(Color::from_gray(255)),
|
||||||
bg_hover_color: Cell::new(Color::from_gray(255)),
|
bg_hover_color: Cell::new(Color::from_gray(255)),
|
||||||
text: Default::default(),
|
text: Default::default(),
|
||||||
font: RefCell::new(DEFAULT_FONT.into()),
|
font: Arc::new(DEFAULT_FONT.to_string()),
|
||||||
tex: Default::default(),
|
tex: Default::default(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
@ -162,21 +166,16 @@ impl GuiElement for Button {
|
||||||
_max_width: f32,
|
_max_width: f32,
|
||||||
_max_height: f32,
|
_max_height: f32,
|
||||||
) -> (f32, f32) {
|
) -> (f32, f32) {
|
||||||
let old_tex = self.tex.take();
|
|
||||||
let font = self.font.borrow_mut();
|
|
||||||
let text = self.text.borrow_mut();
|
let text = self.text.borrow_mut();
|
||||||
let tex = text::render_fitting2(
|
let tex = ptl_text::render(
|
||||||
ctx,
|
ctx,
|
||||||
old_tex,
|
|
||||||
None,
|
None,
|
||||||
&font,
|
&self.font,
|
||||||
&text,
|
&text,
|
||||||
Color::from_gray(0),
|
Color::from_gray(0),
|
||||||
false,
|
|
||||||
Some(scale as _),
|
Some(scale as _),
|
||||||
true,
|
true,
|
||||||
)
|
);
|
||||||
.ok();
|
|
||||||
let (tex, msmt) = match tex {
|
let (tex, msmt) = match tex {
|
||||||
Some((a, b)) => (Some(a), Some(b)),
|
Some((a, b)) => (Some(a), Some(b)),
|
||||||
_ => (None, None),
|
_ => (None, None),
|
||||||
|
|
@ -215,7 +214,7 @@ impl GuiElement for Button {
|
||||||
if let Some(tex) = self.tex.get() {
|
if let Some(tex) = self.tex.get() {
|
||||||
let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get());
|
let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get());
|
||||||
r.render_texture(
|
r.render_texture(
|
||||||
&tex.texture,
|
&tex,
|
||||||
None,
|
None,
|
||||||
tx.round() as _,
|
tx.round() as _,
|
||||||
ty.round() as _,
|
ty.round() as _,
|
||||||
|
|
@ -262,16 +261,16 @@ const DEFAULT_FONT: &str = "sans-serif 16";
|
||||||
|
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
pub data: GuiElementData,
|
pub data: GuiElementData,
|
||||||
pub font: RefCell<Cow<'static, str>>,
|
pub font: Arc<String>,
|
||||||
pub text: RefCell<String>,
|
pub text: RefCell<String>,
|
||||||
pub tex: CloneCell<Option<TextTexture>>,
|
pub tex: CloneCell<Option<Rc<dyn GfxTexture>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Label {
|
impl Default for Label {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: Default::default(),
|
data: Default::default(),
|
||||||
font: RefCell::new(DEFAULT_FONT.into()),
|
font: Arc::new(DEFAULT_FONT.into()),
|
||||||
text: RefCell::new("".to_string()),
|
text: RefCell::new("".to_string()),
|
||||||
tex: Default::default(),
|
tex: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
@ -290,24 +289,19 @@ impl GuiElement for Label {
|
||||||
_max_width: f32,
|
_max_width: f32,
|
||||||
_max_height: f32,
|
_max_height: f32,
|
||||||
) -> (f32, f32) {
|
) -> (f32, f32) {
|
||||||
let old_tex = self.tex.take();
|
|
||||||
let text = self.text.borrow_mut();
|
let text = self.text.borrow_mut();
|
||||||
let font = self.font.borrow_mut();
|
let tex = ptl_text::render(
|
||||||
let tex = text::render_fitting2(
|
|
||||||
ctx,
|
ctx,
|
||||||
old_tex,
|
|
||||||
None,
|
None,
|
||||||
&font,
|
&self.font,
|
||||||
&text,
|
&text,
|
||||||
Color::from_gray(255),
|
Color::from_gray(255),
|
||||||
false,
|
|
||||||
Some(scale as _),
|
Some(scale as _),
|
||||||
false,
|
false,
|
||||||
)
|
);
|
||||||
.ok();
|
|
||||||
let (tex, width, height) = match tex {
|
let (tex, width, height) = match tex {
|
||||||
Some((t, _)) => {
|
Some((t, _)) => {
|
||||||
let (width, height) = t.texture.size();
|
let (width, height) = t.size();
|
||||||
(Some(t.clone()), width, height)
|
(Some(t.clone()), width, height)
|
||||||
}
|
}
|
||||||
_ => (None, 0, 0),
|
_ => (None, 0, 0),
|
||||||
|
|
@ -320,7 +314,7 @@ impl GuiElement for Label {
|
||||||
if let Some(tex) = self.tex.get() {
|
if let Some(tex) = self.tex.get() {
|
||||||
let (tx, ty) = r.scale_point_f(x, y);
|
let (tx, ty) = r.scale_point_f(x, y);
|
||||||
r.render_texture(
|
r.render_texture(
|
||||||
&tex.texture,
|
&tex,
|
||||||
None,
|
None,
|
||||||
tx.round() as _,
|
tx.round() as _,
|
||||||
ty.round() as _,
|
ty.round() as _,
|
||||||
|
|
|
||||||
100
src/renderer.rs
100
src/renderer.rs
|
|
@ -131,20 +131,22 @@ impl Renderer<'_> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(status) = &rd.status {
|
if let Some(status) = &rd.status {
|
||||||
let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y);
|
if let Some(texture) = status.tex.texture() {
|
||||||
self.base.render_texture(
|
let (x, y) = self.base.scale_point(x + status.tex_x, y);
|
||||||
&status.tex.texture,
|
self.base.render_texture(
|
||||||
None,
|
&texture,
|
||||||
x,
|
None,
|
||||||
y,
|
x,
|
||||||
None,
|
y,
|
||||||
None,
|
None,
|
||||||
scale,
|
None,
|
||||||
None,
|
scale,
|
||||||
None,
|
None,
|
||||||
AcquireSync::None,
|
None,
|
||||||
ReleaseSync::None,
|
AcquireSync::None,
|
||||||
);
|
ReleaseSync::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ws) = output.workspace.get() {
|
if let Some(ws) = output.workspace.get() {
|
||||||
|
|
@ -196,23 +198,25 @@ impl Renderer<'_> {
|
||||||
std::slice::from_ref(&pos.at_point(x, y)),
|
std::slice::from_ref(&pos.at_point(x, y)),
|
||||||
&Color::from_rgba_straight(20, 20, 20, 255),
|
&Color::from_rgba_straight(20, 20, 20, 255),
|
||||||
);
|
);
|
||||||
if let Some(tex) = placeholder.textures.get(&self.base.scale) {
|
if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) {
|
||||||
let (tex_width, tex_height) = tex.texture.size();
|
if let Some(texture) = tex.texture() {
|
||||||
let x = x + (pos.width() - tex_width) / 2;
|
let (tex_width, tex_height) = texture.size();
|
||||||
let y = y + (pos.height() - tex_height) / 2;
|
let x = x + (pos.width() - tex_width) / 2;
|
||||||
self.base.render_texture(
|
let y = y + (pos.height() - tex_height) / 2;
|
||||||
&tex.texture,
|
self.base.render_texture(
|
||||||
None,
|
&texture,
|
||||||
x,
|
None,
|
||||||
y,
|
x,
|
||||||
None,
|
y,
|
||||||
None,
|
None,
|
||||||
self.base.scale,
|
None,
|
||||||
None,
|
self.base.scale,
|
||||||
None,
|
None,
|
||||||
AcquireSync::None,
|
None,
|
||||||
ReleaseSync::None,
|
AcquireSync::None,
|
||||||
);
|
ReleaseSync::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +247,7 @@ impl Renderer<'_> {
|
||||||
for title in titles {
|
for title in titles {
|
||||||
let (x, y) = self.base.scale_point(x + title.x, y + title.y);
|
let (x, y) = self.base.scale_point(x + title.x, y + title.y);
|
||||||
self.base.render_texture(
|
self.base.render_texture(
|
||||||
&title.tex.texture,
|
&title.tex,
|
||||||
None,
|
None,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
|
@ -466,21 +470,23 @@ impl Renderer<'_> {
|
||||||
let title_underline =
|
let title_underline =
|
||||||
[Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()];
|
[Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()];
|
||||||
self.base.fill_boxes(&title_underline, &uc);
|
self.base.fill_boxes(&title_underline, &uc);
|
||||||
if let Some(title) = floating.title_textures.get(&self.base.scale) {
|
if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) {
|
||||||
let (x, y) = self.base.scale_point(x + bw, y + bw);
|
if let Some(texture) = title.texture() {
|
||||||
self.base.render_texture(
|
let (x, y) = self.base.scale_point(x + bw, y + bw);
|
||||||
&title.texture,
|
self.base.render_texture(
|
||||||
None,
|
&texture,
|
||||||
x,
|
None,
|
||||||
y,
|
x,
|
||||||
None,
|
y,
|
||||||
None,
|
None,
|
||||||
self.base.scale,
|
None,
|
||||||
None,
|
self.base.scale,
|
||||||
None,
|
None,
|
||||||
AcquireSync::None,
|
None,
|
||||||
ReleaseSync::None,
|
AcquireSync::None,
|
||||||
);
|
ReleaseSync::None,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let body = Rect::new_sized(
|
let body = Rect::new_sized(
|
||||||
x + bw,
|
x + bw,
|
||||||
|
|
|
||||||
44
src/state.rs
44
src/state.rs
|
|
@ -148,13 +148,15 @@ pub struct State {
|
||||||
pub config: CloneCell<Option<Rc<ConfigProxy>>>,
|
pub config: CloneCell<Option<Rc<ConfigProxy>>>,
|
||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
pub pending_container_layout: AsyncQueue<Rc<ContainerNode>>,
|
pub pending_container_layout: AsyncQueue<Rc<ContainerNode>>,
|
||||||
pub pending_container_render_data: AsyncQueue<Rc<ContainerNode>>,
|
pub pending_container_render_positions: AsyncQueue<Rc<ContainerNode>>,
|
||||||
|
pub pending_container_render_title: AsyncQueue<Rc<ContainerNode>>,
|
||||||
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
|
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
|
||||||
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
||||||
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
||||||
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
||||||
pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
|
pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
|
||||||
pub pending_screencast_reallocs_or_reconfigures: AsyncQueue<Rc<JayScreencast>>,
|
pub pending_screencast_reallocs_or_reconfigures: AsyncQueue<Rc<JayScreencast>>,
|
||||||
|
pub pending_placeholder_render_textures: AsyncQueue<Rc<PlaceholderNode>>,
|
||||||
pub dbus: Dbus,
|
pub dbus: Dbus,
|
||||||
pub fdcloser: Arc<FdCloser>,
|
pub fdcloser: Arc<FdCloser>,
|
||||||
pub logger: Option<Arc<Logger>>,
|
pub logger: Option<Arc<Logger>>,
|
||||||
|
|
@ -343,8 +345,10 @@ impl DrmDevData {
|
||||||
struct UpdateTextTexturesVisitor;
|
struct UpdateTextTexturesVisitor;
|
||||||
impl NodeVisitorBase for UpdateTextTexturesVisitor {
|
impl NodeVisitorBase for UpdateTextTexturesVisitor {
|
||||||
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
||||||
node.children.iter().for_each(|c| c.title_tex.clear());
|
node.children
|
||||||
node.schedule_compute_render_data();
|
.iter()
|
||||||
|
.for_each(|c| c.title_tex.borrow_mut().clear());
|
||||||
|
node.schedule_render_titles();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
fn visit_output(&mut self, node: &Rc<OutputNode>) {
|
fn visit_output(&mut self, node: &Rc<OutputNode>) {
|
||||||
|
|
@ -352,13 +356,17 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor {
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
||||||
node.title_textures.clear();
|
node.title_textures.borrow_mut().clear();
|
||||||
node.schedule_render_titles();
|
node.schedule_render_titles();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
|
fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
|
||||||
|
node.title_texture.take();
|
||||||
|
node.node_visit_children(self);
|
||||||
|
}
|
||||||
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
|
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
|
||||||
node.textures.clear();
|
node.textures.borrow_mut().clear();
|
||||||
node.update_texture();
|
node.schedule_update_texture();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -463,11 +471,13 @@ impl State {
|
||||||
impl NodeVisitorBase for Walker {
|
impl NodeVisitorBase for Walker {
|
||||||
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
||||||
node.render_data.borrow_mut().titles.clear();
|
node.render_data.borrow_mut().titles.clear();
|
||||||
node.children.iter().for_each(|c| c.title_tex.clear());
|
node.children
|
||||||
|
.iter()
|
||||||
|
.for_each(|c| c.title_tex.borrow_mut().clear());
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
|
fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
|
||||||
node.title_texture.set(None);
|
node.title_texture.take();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
fn visit_output(&mut self, node: &Rc<OutputNode>) {
|
fn visit_output(&mut self, node: &Rc<OutputNode>) {
|
||||||
|
|
@ -477,11 +487,11 @@ impl State {
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
||||||
node.title_textures.clear();
|
node.title_textures.borrow_mut().clear();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
|
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
|
||||||
node.textures.clear();
|
node.textures.borrow_mut().clear();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -819,13 +829,15 @@ impl State {
|
||||||
}
|
}
|
||||||
self.dbus.clear();
|
self.dbus.clear();
|
||||||
self.pending_container_layout.clear();
|
self.pending_container_layout.clear();
|
||||||
self.pending_container_render_data.clear();
|
self.pending_container_render_positions.clear();
|
||||||
|
self.pending_container_render_title.clear();
|
||||||
self.pending_output_render_data.clear();
|
self.pending_output_render_data.clear();
|
||||||
self.pending_float_layout.clear();
|
self.pending_float_layout.clear();
|
||||||
self.pending_float_titles.clear();
|
self.pending_float_titles.clear();
|
||||||
self.pending_input_popup_positioning.clear();
|
self.pending_input_popup_positioning.clear();
|
||||||
self.pending_toplevel_screencasts.clear();
|
self.pending_toplevel_screencasts.clear();
|
||||||
self.pending_screencast_reallocs_or_reconfigures.clear();
|
self.pending_screencast_reallocs_or_reconfigures.clear();
|
||||||
|
self.pending_placeholder_render_textures.clear();
|
||||||
self.render_ctx_watchers.clear();
|
self.render_ctx_watchers.clear();
|
||||||
self.workspace_watchers.clear();
|
self.workspace_watchers.clear();
|
||||||
self.toplevel_lists.clear();
|
self.toplevel_lists.clear();
|
||||||
|
|
@ -1218,6 +1230,16 @@ impl State {
|
||||||
output.vblank();
|
output.vblank();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "it")]
|
||||||
|
pub async fn idle(&self) {
|
||||||
|
loop {
|
||||||
|
self.eng.idle().await;
|
||||||
|
if self.cpu_worker.wait_idle() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
563
src/text.rs
563
src/text.rs
|
|
@ -1,7 +1,11 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob},
|
||||||
format::ARGB8888,
|
format::ARGB8888,
|
||||||
gfx_api::{GfxContext, GfxError, GfxTexture, ShmGfxTexture},
|
gfx_api::{
|
||||||
|
AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxContext, GfxError, GfxTexture,
|
||||||
|
PendingShmUpload,
|
||||||
|
},
|
||||||
pango::{
|
pango::{
|
||||||
consts::{
|
consts::{
|
||||||
CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, PANGO_ELLIPSIZE_END, PANGO_SCALE,
|
CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, PANGO_ELLIPSIZE_END, PANGO_SCALE,
|
||||||
|
|
@ -9,14 +13,19 @@ use {
|
||||||
CairoContext, CairoImageSurface, PangoCairoContext, PangoError, PangoFontDescription,
|
CairoContext, CairoImageSurface, PangoCairoContext, PangoError, PangoFontDescription,
|
||||||
PangoLayout,
|
PangoLayout,
|
||||||
},
|
},
|
||||||
rect::Rect,
|
rect::{Rect, Region},
|
||||||
theme::Color,
|
theme::Color,
|
||||||
utils::clonecell::UnsafeCellCloneSafe,
|
utils::{
|
||||||
|
clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
ops::{Deref, Neg},
|
cell::{Cell, RefCell},
|
||||||
rc::Rc,
|
mem,
|
||||||
|
ops::Neg,
|
||||||
|
rc::{Rc, Weak},
|
||||||
|
sync::Arc,
|
||||||
},
|
},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
@ -31,54 +40,64 @@ pub enum TextError {
|
||||||
PangoContext(#[source] PangoError),
|
PangoContext(#[source] PangoError),
|
||||||
#[error("Could not create a pango layout")]
|
#[error("Could not create a pango layout")]
|
||||||
CreateLayout(#[source] PangoError),
|
CreateLayout(#[source] PangoError),
|
||||||
#[error("Could not import the rendered text")]
|
|
||||||
RenderError(#[source] GfxError),
|
|
||||||
#[error("Could not access the cairo image data")]
|
#[error("Could not access the cairo image data")]
|
||||||
ImageData(#[source] PangoError),
|
ImageData(#[source] PangoError),
|
||||||
}
|
#[error("Texture upload failed")]
|
||||||
|
Upload(#[source] GfxError),
|
||||||
#[derive(PartialEq)]
|
#[error("Could not create a texture")]
|
||||||
struct Config<'a> {
|
CreateTexture(#[source] GfxError),
|
||||||
x: i32,
|
#[error("Rendering is not scheduled or not yet completed")]
|
||||||
y: Option<i32>,
|
NotScheduled,
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
padding: i32,
|
|
||||||
font: Cow<'a, str>,
|
|
||||||
text: Cow<'a, str>,
|
|
||||||
color: Color,
|
|
||||||
ellipsize: bool,
|
|
||||||
markup: bool,
|
|
||||||
scale: Option<f64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Config<'a> {
|
impl<'a> Config<'a> {
|
||||||
fn to_static(self) -> Config<'static> {
|
fn to_static(self) -> Config<'static> {
|
||||||
Config {
|
match self {
|
||||||
x: self.x,
|
Config::None => Config::None,
|
||||||
y: self.y,
|
Config::RenderFitting {
|
||||||
width: self.width,
|
height,
|
||||||
height: self.height,
|
font,
|
||||||
padding: self.padding,
|
text,
|
||||||
font: Cow::Owned(self.font.into_owned()),
|
color,
|
||||||
text: Cow::Owned(self.text.into_owned()),
|
markup,
|
||||||
color: self.color,
|
scale,
|
||||||
ellipsize: self.ellipsize,
|
} => Config::RenderFitting {
|
||||||
markup: self.markup,
|
height,
|
||||||
scale: self.scale,
|
font,
|
||||||
|
text: text.into_owned().into(),
|
||||||
|
color,
|
||||||
|
markup,
|
||||||
|
scale,
|
||||||
|
},
|
||||||
|
Config::Render {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
padding,
|
||||||
|
font,
|
||||||
|
text,
|
||||||
|
color,
|
||||||
|
ellipsize,
|
||||||
|
markup,
|
||||||
|
scale,
|
||||||
|
} => Config::Render {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
padding,
|
||||||
|
font,
|
||||||
|
text: text.into_owned().into(),
|
||||||
|
color,
|
||||||
|
ellipsize,
|
||||||
|
markup,
|
||||||
|
scale,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TextTexture {
|
|
||||||
config: Rc<Config<'static>>,
|
|
||||||
shm_texture: Rc<dyn ShmGfxTexture>,
|
|
||||||
pub texture: Rc<dyn GfxTexture>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl UnsafeCellCloneSafe for TextTexture {}
|
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
image: Rc<CairoImageSurface>,
|
image: Rc<CairoImageSurface>,
|
||||||
cctx: Rc<CairoContext>,
|
cctx: Rc<CairoContext>,
|
||||||
|
|
@ -118,12 +137,11 @@ fn create_data(font: &str, width: i32, height: i32, scale: Option<f64>) -> Resul
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn measure(
|
fn measure(
|
||||||
font: &str,
|
font: &str,
|
||||||
text: &str,
|
text: &str,
|
||||||
markup: bool,
|
markup: bool,
|
||||||
scale: Option<f64>,
|
scale: Option<f64>,
|
||||||
full: bool,
|
|
||||||
) -> Result<TextMeasurement, TextError> {
|
) -> Result<TextMeasurement, TextError> {
|
||||||
let data = create_data(font, 1, 1, scale)?;
|
let data = create_data(font, 1, 1, scale)?;
|
||||||
if markup {
|
if markup {
|
||||||
|
|
@ -133,31 +151,10 @@ pub fn measure(
|
||||||
}
|
}
|
||||||
let mut res = TextMeasurement::default();
|
let mut res = TextMeasurement::default();
|
||||||
res.ink_rect = data.layout.inc_pixel_rect();
|
res.ink_rect = data.layout.inc_pixel_rect();
|
||||||
if full {
|
|
||||||
res.logical_rect = data.layout.logical_pixel_rect();
|
|
||||||
res.baseline = data.layout.pixel_baseline();
|
|
||||||
}
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(
|
fn render(
|
||||||
ctx: &Rc<dyn GfxContext>,
|
|
||||||
old: Option<TextTexture>,
|
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
font: &str,
|
|
||||||
text: &str,
|
|
||||||
color: Color,
|
|
||||||
scale: Option<f64>,
|
|
||||||
) -> Result<TextTexture, TextError> {
|
|
||||||
render2(
|
|
||||||
ctx, old, 1, None, width, height, 1, font, text, color, true, false, scale,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render2(
|
|
||||||
ctx: &Rc<dyn GfxContext>,
|
|
||||||
old: Option<TextTexture>,
|
|
||||||
x: i32,
|
x: i32,
|
||||||
y: Option<i32>,
|
y: Option<i32>,
|
||||||
width: i32,
|
width: i32,
|
||||||
|
|
@ -169,25 +166,14 @@ fn render2(
|
||||||
ellipsize: bool,
|
ellipsize: bool,
|
||||||
markup: bool,
|
markup: bool,
|
||||||
scale: Option<f64>,
|
scale: Option<f64>,
|
||||||
) -> Result<TextTexture, TextError> {
|
) -> Result<RenderedText, TextError> {
|
||||||
let width = width.min(3840);
|
if width == 0 || height == 0 {
|
||||||
let config = Config {
|
return Ok(RenderedText {
|
||||||
x,
|
width,
|
||||||
y,
|
height,
|
||||||
width,
|
stride: width * 4,
|
||||||
height,
|
data: vec![],
|
||||||
padding,
|
});
|
||||||
font: Cow::Borrowed(font),
|
|
||||||
text: Cow::Borrowed(text),
|
|
||||||
color,
|
|
||||||
ellipsize,
|
|
||||||
markup,
|
|
||||||
scale,
|
|
||||||
};
|
|
||||||
if let Some(old2) = &old {
|
|
||||||
if old2.config.deref() == &config {
|
|
||||||
return Ok(old.unwrap());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let data = create_data(font, width, height, scale)?;
|
let data = create_data(font, width, height, scale)?;
|
||||||
if ellipsize {
|
if ellipsize {
|
||||||
|
|
@ -208,79 +194,368 @@ fn render2(
|
||||||
data.cctx.move_to(x as f64, y as f64);
|
data.cctx.move_to(x as f64, y as f64);
|
||||||
data.layout.show_layout();
|
data.layout.show_layout();
|
||||||
data.image.flush();
|
data.image.flush();
|
||||||
let bytes = match data.image.data() {
|
Ok(RenderedText {
|
||||||
Ok(d) => d,
|
|
||||||
Err(e) => return Err(TextError::ImageData(e)),
|
|
||||||
};
|
|
||||||
let old = old.map(|o| o.shm_texture);
|
|
||||||
match ctx.clone().shmem_texture(
|
|
||||||
old,
|
|
||||||
bytes,
|
|
||||||
ARGB8888,
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
data.image.stride(),
|
stride: data.image.stride(),
|
||||||
None,
|
data: data.image.data().map_err(TextError::ImageData)?.to_vec(),
|
||||||
) {
|
})
|
||||||
Ok(t) => Ok(TextTexture {
|
|
||||||
config: Rc::new(config.to_static()),
|
|
||||||
texture: t.clone().into_texture(),
|
|
||||||
shm_texture: t,
|
|
||||||
}),
|
|
||||||
Err(e) => Err(TextError::RenderError(e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_fitting(
|
fn render_fitting(
|
||||||
ctx: &Rc<dyn GfxContext>,
|
|
||||||
old: Option<TextTexture>,
|
|
||||||
height: Option<i32>,
|
height: Option<i32>,
|
||||||
font: &str,
|
font: &str,
|
||||||
text: &str,
|
text: &str,
|
||||||
color: Color,
|
color: Color,
|
||||||
markup: bool,
|
markup: bool,
|
||||||
scale: Option<f64>,
|
scale: Option<f64>,
|
||||||
) -> Result<TextTexture, TextError> {
|
) -> Result<RenderedText, TextError> {
|
||||||
render_fitting2(ctx, old, height, font, text, color, markup, scale, false).map(|(a, _)| a)
|
let measurement = measure(font, text, markup, scale)?;
|
||||||
|
let x = measurement.ink_rect.x1().neg();
|
||||||
|
let y = match height {
|
||||||
|
Some(_) => None,
|
||||||
|
_ => Some(measurement.ink_rect.y1().neg()),
|
||||||
|
};
|
||||||
|
let width = measurement.ink_rect.width();
|
||||||
|
let height = height.unwrap_or(measurement.ink_rect.height());
|
||||||
|
render(
|
||||||
|
x, y, width, height, 0, font, text, color, false, markup, scale,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
pub struct TextMeasurement {
|
pub struct TextMeasurement {
|
||||||
pub ink_rect: Rect,
|
pub ink_rect: Rect,
|
||||||
pub logical_rect: Rect,
|
|
||||||
pub baseline: i32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_fitting2(
|
struct RenderedText {
|
||||||
ctx: &Rc<dyn GfxContext>,
|
width: i32,
|
||||||
old: Option<TextTexture>,
|
height: i32,
|
||||||
height: Option<i32>,
|
stride: i32,
|
||||||
font: &str,
|
data: Vec<Cell<u8>>,
|
||||||
text: &str,
|
}
|
||||||
color: Color,
|
|
||||||
markup: bool,
|
#[derive(Default)]
|
||||||
scale: Option<f64>,
|
struct RenderWork {
|
||||||
include_measurements: bool,
|
config: Config<'static>,
|
||||||
) -> Result<(TextTexture, TextMeasurement), TextError> {
|
result: Option<Result<RenderedText, TextError>>,
|
||||||
let measurement = measure(font, text, markup, scale, include_measurements)?;
|
}
|
||||||
let y = match height {
|
|
||||||
Some(_) => None,
|
struct RenderJob {
|
||||||
_ => Some(measurement.ink_rect.y1().neg()),
|
work: RenderWork,
|
||||||
};
|
data: Weak<Shared>,
|
||||||
let res = render2(
|
}
|
||||||
ctx,
|
|
||||||
old,
|
impl CpuWork for RenderWork {
|
||||||
measurement.ink_rect.x1().neg(),
|
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>> {
|
||||||
y,
|
self.result = Some(self.render());
|
||||||
measurement.ink_rect.width(),
|
None
|
||||||
height.unwrap_or(measurement.ink_rect.height()),
|
}
|
||||||
0,
|
}
|
||||||
font,
|
|
||||||
text,
|
impl RenderWork {
|
||||||
color,
|
fn render(&mut self) -> Result<RenderedText, TextError> {
|
||||||
false,
|
match self.config {
|
||||||
markup,
|
Config::None => unreachable!(),
|
||||||
scale,
|
Config::RenderFitting {
|
||||||
);
|
height,
|
||||||
res.map(|r| (r, measurement))
|
ref font,
|
||||||
|
ref text,
|
||||||
|
color,
|
||||||
|
markup,
|
||||||
|
scale,
|
||||||
|
} => render_fitting(height, font, text, color, markup, scale),
|
||||||
|
Config::Render {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
padding,
|
||||||
|
ref font,
|
||||||
|
ref text,
|
||||||
|
color,
|
||||||
|
ellipsize,
|
||||||
|
markup,
|
||||||
|
scale,
|
||||||
|
} => render(
|
||||||
|
x, y, width, height, padding, font, text, color, ellipsize, markup, scale,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextTexture {
|
||||||
|
data: Rc<Shared>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TextTexture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(pending) = self.data.pending_render.take() {
|
||||||
|
pending.detach();
|
||||||
|
}
|
||||||
|
self.data.pending_upload.take();
|
||||||
|
self.data.render_job.take();
|
||||||
|
self.data.waiter.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Shared {
|
||||||
|
cpu_worker: Rc<CpuWorker>,
|
||||||
|
ctx: Rc<dyn GfxContext>,
|
||||||
|
textures: DoubleBuffered<TextBuffer>,
|
||||||
|
pending_render: Cell<Option<PendingJob>>,
|
||||||
|
pending_upload: Cell<Option<PendingShmUpload>>,
|
||||||
|
render_job: Cell<Option<Box<RenderJob>>>,
|
||||||
|
result: Cell<Option<Result<(), TextError>>>,
|
||||||
|
waiter: Cell<Option<Rc<dyn OnCompleted>>>,
|
||||||
|
busy: Cell<bool>,
|
||||||
|
flip_is_noop: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shared {
|
||||||
|
fn complete(&self, res: Result<(), TextError>) {
|
||||||
|
if res.is_err() {
|
||||||
|
self.textures.back().config.take();
|
||||||
|
}
|
||||||
|
self.busy.set(false);
|
||||||
|
self.result.set(Some(res));
|
||||||
|
if let Some(waiter) = self.waiter.take() {
|
||||||
|
waiter.completed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Default)]
|
||||||
|
enum Config<'a> {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
RenderFitting {
|
||||||
|
height: Option<i32>,
|
||||||
|
font: Arc<String>,
|
||||||
|
text: Cow<'a, str>,
|
||||||
|
color: Color,
|
||||||
|
markup: bool,
|
||||||
|
scale: Option<f64>,
|
||||||
|
},
|
||||||
|
Render {
|
||||||
|
x: i32,
|
||||||
|
y: Option<i32>,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
padding: i32,
|
||||||
|
font: Arc<String>,
|
||||||
|
text: Cow<'a, str>,
|
||||||
|
color: Color,
|
||||||
|
ellipsize: bool,
|
||||||
|
markup: bool,
|
||||||
|
scale: Option<f64>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TextBuffer {
|
||||||
|
config: RefCell<Config<'static>>,
|
||||||
|
tex: CloneCell<Option<Rc<dyn AsyncShmGfxTexture>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OnCompleted {
|
||||||
|
fn completed(self: Rc<Self>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextTexture {
|
||||||
|
pub fn new(cpu_worker: &Rc<CpuWorker>, ctx: &Rc<dyn GfxContext>) -> Self {
|
||||||
|
let data = Rc::new(Shared {
|
||||||
|
cpu_worker: cpu_worker.clone(),
|
||||||
|
ctx: ctx.clone(),
|
||||||
|
textures: Default::default(),
|
||||||
|
pending_render: Default::default(),
|
||||||
|
pending_upload: Default::default(),
|
||||||
|
render_job: Default::default(),
|
||||||
|
result: Default::default(),
|
||||||
|
waiter: Default::default(),
|
||||||
|
busy: Default::default(),
|
||||||
|
flip_is_noop: Default::default(),
|
||||||
|
});
|
||||||
|
Self { data }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture(&self) -> Option<Rc<dyn GfxTexture>> {
|
||||||
|
self.data
|
||||||
|
.textures
|
||||||
|
.front()
|
||||||
|
.tex
|
||||||
|
.get()
|
||||||
|
.map(|t| t.into_texture())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_config(&self, on_completed: Rc<dyn OnCompleted>, config: Config<'_>) {
|
||||||
|
if self.data.busy.replace(true) {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
self.data.waiter.set(Some(on_completed));
|
||||||
|
self.data.flip_is_noop.set(false);
|
||||||
|
if *self.data.textures.front().config.borrow() == config {
|
||||||
|
self.data.flip_is_noop.set(true);
|
||||||
|
self.data.complete(Ok(()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if *self.data.textures.back().config.borrow() == config {
|
||||||
|
self.data.complete(Ok(()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut job = self.data.render_job.take().unwrap_or_else(|| {
|
||||||
|
Box::new(RenderJob {
|
||||||
|
work: Default::default(),
|
||||||
|
data: Rc::downgrade(&self.data),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
job.work = RenderWork {
|
||||||
|
config: config.to_static(),
|
||||||
|
result: None,
|
||||||
|
};
|
||||||
|
let pending = self.data.cpu_worker.submit(job);
|
||||||
|
self.data.pending_render.set(Some(pending));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schedule_render(
|
||||||
|
&self,
|
||||||
|
on_completed: Rc<dyn OnCompleted>,
|
||||||
|
x: i32,
|
||||||
|
y: Option<i32>,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
padding: i32,
|
||||||
|
font: &Arc<String>,
|
||||||
|
text: &str,
|
||||||
|
color: Color,
|
||||||
|
ellipsize: bool,
|
||||||
|
markup: bool,
|
||||||
|
scale: Option<f64>,
|
||||||
|
) {
|
||||||
|
let config = Config::Render {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
padding,
|
||||||
|
font: font.clone(),
|
||||||
|
text: Cow::Borrowed(text),
|
||||||
|
color,
|
||||||
|
ellipsize,
|
||||||
|
markup,
|
||||||
|
scale,
|
||||||
|
};
|
||||||
|
self.apply_config(on_completed, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schedule_render_fitting(
|
||||||
|
&self,
|
||||||
|
on_completed: Rc<dyn OnCompleted>,
|
||||||
|
height: Option<i32>,
|
||||||
|
font: &Arc<String>,
|
||||||
|
text: &str,
|
||||||
|
color: Color,
|
||||||
|
markup: bool,
|
||||||
|
scale: Option<f64>,
|
||||||
|
) {
|
||||||
|
let config = Config::RenderFitting {
|
||||||
|
height,
|
||||||
|
font: font.clone(),
|
||||||
|
text: text.into(),
|
||||||
|
color,
|
||||||
|
markup,
|
||||||
|
scale,
|
||||||
|
};
|
||||||
|
self.apply_config(on_completed, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flip(&self) -> Result<(), TextError> {
|
||||||
|
let res = self
|
||||||
|
.data
|
||||||
|
.result
|
||||||
|
.take()
|
||||||
|
.unwrap_or(Err(TextError::NotScheduled));
|
||||||
|
if res.is_ok() && !self.data.flip_is_noop.get() {
|
||||||
|
self.data.textures.flip();
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpuJob for RenderJob {
|
||||||
|
fn work(&mut self) -> &mut dyn CpuWork {
|
||||||
|
&mut self.work
|
||||||
|
}
|
||||||
|
|
||||||
|
fn completed(mut self: Box<Self>) {
|
||||||
|
let Some(data) = self.data.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let result = self.work.result.take().unwrap();
|
||||||
|
*data.textures.back().config.borrow_mut() = mem::take(&mut self.work.config);
|
||||||
|
data.render_job.set(Some(self));
|
||||||
|
let rt = match result {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
data.complete(Err(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut tex = data.textures.back().tex.take();
|
||||||
|
if rt.width == 0 || rt.height == 0 {
|
||||||
|
data.complete(Ok(()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(t) = &tex {
|
||||||
|
if !t.compatible_with(ARGB8888, rt.width, rt.height, rt.stride) {
|
||||||
|
tex = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tex = match tex {
|
||||||
|
Some(t) => t,
|
||||||
|
_ => {
|
||||||
|
let tex = data
|
||||||
|
.ctx
|
||||||
|
.clone()
|
||||||
|
.async_shmem_texture(ARGB8888, rt.width, rt.height, rt.stride, &data.cpu_worker)
|
||||||
|
.map_err(TextError::CreateTexture);
|
||||||
|
match tex {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
data.complete(Err(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let pending = tex
|
||||||
|
.clone()
|
||||||
|
.async_upload(
|
||||||
|
data.clone(),
|
||||||
|
Rc::new(rt.data),
|
||||||
|
Region::new2(Rect::new_sized_unchecked(0, 0, rt.width, rt.height)),
|
||||||
|
)
|
||||||
|
.map_err(TextError::Upload);
|
||||||
|
if pending.is_ok() {
|
||||||
|
data.textures.back().tex.set(Some(tex));
|
||||||
|
}
|
||||||
|
match pending {
|
||||||
|
Ok(Some(p)) => data.pending_upload.set(Some(p)),
|
||||||
|
Ok(None) => data.complete(Ok(())),
|
||||||
|
Err(e) => data.complete(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncShmGfxTextureCallback for Shared {
|
||||||
|
fn completed(self: Rc<Self>, res: Result<(), GfxError>) {
|
||||||
|
self.pending_upload.take();
|
||||||
|
self.complete(res.map_err(TextError::Upload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OnCompleted for OnDropEvent {
|
||||||
|
fn completed(self: Rc<Self>) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/theme.rs
14
src/theme.rs
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{
|
use {
|
||||||
cell::{Cell, RefCell},
|
crate::utils::clonecell::CloneCell,
|
||||||
cmp::Ordering,
|
std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc},
|
||||||
ops::Mul,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
|
@ -290,15 +289,18 @@ pub const DEFAULT_FONT: &str = "monospace 8";
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
pub colors: ThemeColors,
|
pub colors: ThemeColors,
|
||||||
pub sizes: ThemeSizes,
|
pub sizes: ThemeSizes,
|
||||||
pub font: RefCell<String>,
|
pub font: CloneCell<Arc<String>>,
|
||||||
|
pub default_font: Arc<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Theme {
|
impl Default for Theme {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let default_font = Arc::new(DEFAULT_FONT.to_string());
|
||||||
Self {
|
Self {
|
||||||
colors: Default::default(),
|
colors: Default::default(),
|
||||||
sizes: Default::default(),
|
sizes: Default::default(),
|
||||||
font: RefCell::new(DEFAULT_FONT.to_string()),
|
font: CloneCell::new(default_font.clone()),
|
||||||
|
default_font,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use {
|
||||||
cursor::KnownCursor,
|
cursor::KnownCursor,
|
||||||
cursor_user::CursorUser,
|
cursor_user::CursorUser,
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
|
gfx_api::GfxTexture,
|
||||||
ifs::wl_seat::{
|
ifs::wl_seat::{
|
||||||
collect_kb_foci, collect_kb_foci2,
|
collect_kb_foci, collect_kb_foci2,
|
||||||
tablet::{TabletTool, TabletToolChanges, TabletToolId},
|
tablet::{TabletTool, TabletToolChanges, TabletToolId},
|
||||||
|
|
@ -14,21 +15,23 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
scale::Scale,
|
scale::Scale,
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
||||||
FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
|
asyncevent::AsyncEvent,
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
double_click_state::DoubleClickState,
|
double_click_state::DoubleClickState,
|
||||||
errorfmt::ErrorFmt,
|
errorfmt::ErrorFmt,
|
||||||
hash_map_ext::HashMapExt,
|
hash_map_ext::HashMapExt,
|
||||||
linkedlist::{LinkedList, LinkedNode, NodeRef},
|
linkedlist::{LinkedList, LinkedNode, NodeRef},
|
||||||
numcell::NumCell,
|
numcell::NumCell,
|
||||||
|
on_drop_event::OnDropEvent,
|
||||||
rc_eq::rc_eq,
|
rc_eq::rc_eq,
|
||||||
scroller::Scroller,
|
scroller::Scroller,
|
||||||
smallmap::{SmallMap, SmallMapMut},
|
smallmap::SmallMapMut,
|
||||||
threshold_counter::ThresholdCounter,
|
threshold_counter::ThresholdCounter,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -81,7 +84,7 @@ tree_id!(ContainerNodeId);
|
||||||
pub struct ContainerTitle {
|
pub struct ContainerTitle {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
pub tex: TextTexture,
|
pub tex: Rc<dyn GfxTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -109,7 +112,8 @@ pub struct ContainerNode {
|
||||||
pub content_height: Cell<i32>,
|
pub content_height: Cell<i32>,
|
||||||
pub sum_factors: Cell<f64>,
|
pub sum_factors: Cell<f64>,
|
||||||
layout_scheduled: Cell<bool>,
|
layout_scheduled: Cell<bool>,
|
||||||
compute_render_data_scheduled: Cell<bool>,
|
compute_render_positions_scheduled: Cell<bool>,
|
||||||
|
render_titles_scheduled: Cell<bool>,
|
||||||
num_children: NumCell<usize>,
|
num_children: NumCell<usize>,
|
||||||
pub children: LinkedList<ContainerChild>,
|
pub children: LinkedList<ContainerChild>,
|
||||||
focus_history: LinkedList<NodeRef<ContainerChild>>,
|
focus_history: LinkedList<NodeRef<ContainerChild>>,
|
||||||
|
|
@ -134,7 +138,7 @@ pub struct ContainerChild {
|
||||||
pub active: Cell<bool>,
|
pub active: Cell<bool>,
|
||||||
pub attention_requested: Cell<bool>,
|
pub attention_requested: Cell<bool>,
|
||||||
title: RefCell<String>,
|
title: RefCell<String>,
|
||||||
pub title_tex: SmallMap<Scale, TextTexture, 2>,
|
pub title_tex: RefCell<SmallMapMut<Scale, TextTexture, 2>>,
|
||||||
pub title_rect: Cell<Rect>,
|
pub title_rect: Cell<Rect>,
|
||||||
focus_history: Cell<Option<LinkedNode<NodeRef<ContainerChild>>>>,
|
focus_history: Cell<Option<LinkedNode<NodeRef<ContainerChild>>>>,
|
||||||
|
|
||||||
|
|
@ -213,7 +217,8 @@ impl ContainerNode {
|
||||||
content_height: Cell::new(0),
|
content_height: Cell::new(0),
|
||||||
sum_factors: Cell::new(1.0),
|
sum_factors: Cell::new(1.0),
|
||||||
layout_scheduled: Cell::new(false),
|
layout_scheduled: Cell::new(false),
|
||||||
compute_render_data_scheduled: Cell::new(false),
|
compute_render_positions_scheduled: Cell::new(false),
|
||||||
|
render_titles_scheduled: Cell::new(false),
|
||||||
num_children: NumCell::new(1),
|
num_children: NumCell::new(1),
|
||||||
children,
|
children,
|
||||||
focus_history: Default::default(),
|
focus_history: Default::default(),
|
||||||
|
|
@ -351,7 +356,8 @@ impl ContainerNode {
|
||||||
|
|
||||||
pub fn on_colors_changed(self: &Rc<Self>) {
|
pub fn on_colors_changed(self: &Rc<Self>) {
|
||||||
// log::info!("on_colors_changed");
|
// log::info!("on_colors_changed");
|
||||||
self.schedule_compute_render_data();
|
self.schedule_render_titles();
|
||||||
|
self.schedule_compute_render_positions();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn damage(&self) {
|
fn damage(&self) {
|
||||||
|
|
@ -387,7 +393,8 @@ impl ContainerNode {
|
||||||
}
|
}
|
||||||
self.state.tree_changed();
|
self.state.tree_changed();
|
||||||
// log::info!("perform_layout");
|
// log::info!("perform_layout");
|
||||||
self.schedule_compute_render_data();
|
self.schedule_render_titles();
|
||||||
|
self.schedule_compute_render_positions();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_mono_layout(self: &Rc<Self>, child: &ContainerChild) {
|
fn perform_mono_layout(self: &Rc<Self>, child: &ContainerChild) {
|
||||||
|
|
@ -660,23 +667,115 @@ impl ContainerNode {
|
||||||
self.tl_title_changed();
|
self.tl_title_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn schedule_compute_render_data(self: &Rc<Self>) {
|
pub fn schedule_render_titles(self: &Rc<Self>) {
|
||||||
if !self.compute_render_data_scheduled.replace(true) {
|
if !self.render_titles_scheduled.replace(true) {
|
||||||
self.state.pending_container_render_data.push(self.clone());
|
self.state.pending_container_render_title.push(self.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_render_data(&self) {
|
fn render_titles(&self) -> Rc<AsyncEvent> {
|
||||||
self.compute_render_data_scheduled.set(false);
|
let on_completed = Rc::new(OnDropEvent::default());
|
||||||
|
let Some(ctx) = self.state.render_ctx.get() else {
|
||||||
|
return on_completed.event();
|
||||||
|
};
|
||||||
|
let theme = &self.state.theme;
|
||||||
|
let th = theme.sizes.title_height.get();
|
||||||
|
let font = theme.font.get();
|
||||||
|
let last_active = self.focus_history.last().map(|v| v.node.node_id());
|
||||||
|
let have_active = self.children.iter().any(|c| c.active.get());
|
||||||
|
let scales = self.state.scales.lock();
|
||||||
|
for child in self.children.iter() {
|
||||||
|
let rect = child.title_rect.get();
|
||||||
|
let color = if child.active.get() {
|
||||||
|
theme.colors.focused_title_text.get()
|
||||||
|
} else if child.attention_requested.get() {
|
||||||
|
theme.colors.unfocused_title_text.get()
|
||||||
|
} else if !have_active && last_active == Some(child.node.node_id()) {
|
||||||
|
theme.colors.focused_inactive_title_text.get()
|
||||||
|
} else {
|
||||||
|
theme.colors.unfocused_title_text.get()
|
||||||
|
};
|
||||||
|
let title = child.title.borrow_mut();
|
||||||
|
let tt = &mut *child.title_tex.borrow_mut();
|
||||||
|
for (scale, _) in scales.iter() {
|
||||||
|
let tex = tt
|
||||||
|
.get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx));
|
||||||
|
let mut th = th;
|
||||||
|
let mut scalef = None;
|
||||||
|
let mut width = rect.width();
|
||||||
|
if *scale != 1 {
|
||||||
|
let scale = scale.to_f64();
|
||||||
|
th = (th as f64 * scale).round() as _;
|
||||||
|
width = (width as f64 * scale).round() as _;
|
||||||
|
scalef = Some(scale);
|
||||||
|
}
|
||||||
|
tex.schedule_render(
|
||||||
|
on_completed.clone(),
|
||||||
|
1,
|
||||||
|
None,
|
||||||
|
width,
|
||||||
|
th,
|
||||||
|
1,
|
||||||
|
&font,
|
||||||
|
title.deref(),
|
||||||
|
color,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
scalef,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
on_completed.event()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_title_data(&self) {
|
||||||
|
let rd = &mut *self.render_data.borrow_mut();
|
||||||
|
for (_, v) in rd.titles.iter_mut() {
|
||||||
|
v.clear();
|
||||||
|
}
|
||||||
|
let abs_x = self.abs_x1.get();
|
||||||
|
let abs_y = self.abs_y1.get();
|
||||||
|
for child in self.children.iter() {
|
||||||
|
let rect = child.title_rect.get();
|
||||||
|
if self.toplevel_data.visible.get() {
|
||||||
|
self.state.damage(rect.move_(abs_x, abs_y));
|
||||||
|
}
|
||||||
|
let title = child.title.borrow_mut();
|
||||||
|
let tt = &*child.title_tex.borrow();
|
||||||
|
for (scale, tex) in tt {
|
||||||
|
if let Err(e) = tex.flip() {
|
||||||
|
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
|
||||||
|
}
|
||||||
|
if let Some(tex) = tex.texture() {
|
||||||
|
let titles = rd.titles.get_or_default_mut(*scale);
|
||||||
|
titles.push(ContainerTitle {
|
||||||
|
x: rect.x1(),
|
||||||
|
y: rect.y1(),
|
||||||
|
tex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rd.titles.remove_if(|_, v| v.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_compute_render_positions(self: &Rc<Self>) {
|
||||||
|
if !self.compute_render_positions_scheduled.replace(true) {
|
||||||
|
self.state
|
||||||
|
.pending_container_render_positions
|
||||||
|
.push(self.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_render_positions(&self) {
|
||||||
|
self.compute_render_positions_scheduled.set(false);
|
||||||
let mut rd = self.render_data.borrow_mut();
|
let mut rd = self.render_data.borrow_mut();
|
||||||
let rd = rd.deref_mut();
|
let rd = rd.deref_mut();
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let th = theme.sizes.title_height.get();
|
let th = theme.sizes.title_height.get();
|
||||||
let bw = theme.sizes.border_width.get();
|
let bw = theme.sizes.border_width.get();
|
||||||
let font = theme.font.borrow_mut();
|
|
||||||
let cwidth = self.width.get();
|
let cwidth = self.width.get();
|
||||||
let cheight = self.height.get();
|
let cheight = self.height.get();
|
||||||
let ctx = self.state.render_ctx.get();
|
|
||||||
for (_, v) in rd.titles.iter_mut() {
|
for (_, v) in rd.titles.iter_mut() {
|
||||||
v.clear();
|
v.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -690,7 +789,6 @@ impl ContainerNode {
|
||||||
let mono = self.mono_child.is_some();
|
let mono = self.mono_child.is_some();
|
||||||
let split = self.split.get();
|
let split = self.split.get();
|
||||||
let have_active = self.children.iter().any(|c| c.active.get());
|
let have_active = self.children.iter().any(|c| c.active.get());
|
||||||
let scales = self.state.scales.lock();
|
|
||||||
let abs_x = self.abs_x1.get();
|
let abs_x = self.abs_x1.get();
|
||||||
let abs_y = self.abs_y1.get();
|
let abs_y = self.abs_y1.get();
|
||||||
for (i, child) in self.children.iter().enumerate() {
|
for (i, child) in self.children.iter().enumerate() {
|
||||||
|
|
@ -708,64 +806,28 @@ impl ContainerNode {
|
||||||
};
|
};
|
||||||
rd.border_rects.push(rect.unwrap());
|
rd.border_rects.push(rect.unwrap());
|
||||||
}
|
}
|
||||||
let color = if child.active.get() {
|
if child.active.get() {
|
||||||
rd.active_title_rects.push(rect);
|
rd.active_title_rects.push(rect);
|
||||||
theme.colors.focused_title_text.get()
|
|
||||||
} else if child.attention_requested.get() {
|
} else if child.attention_requested.get() {
|
||||||
rd.attention_title_rects.push(rect);
|
rd.attention_title_rects.push(rect);
|
||||||
theme.colors.unfocused_title_text.get()
|
|
||||||
} else if !have_active && last_active == Some(child.node.node_id()) {
|
} else if !have_active && last_active == Some(child.node.node_id()) {
|
||||||
rd.last_active_rect = Some(rect);
|
rd.last_active_rect = Some(rect);
|
||||||
theme.colors.focused_inactive_title_text.get()
|
|
||||||
} else {
|
} else {
|
||||||
rd.title_rects.push(rect);
|
rd.title_rects.push(rect);
|
||||||
theme.colors.unfocused_title_text.get()
|
}
|
||||||
};
|
|
||||||
if !mono {
|
if !mono {
|
||||||
let rect = Rect::new_sized(rect.x1(), rect.y2(), rect.width(), 1).unwrap();
|
let rect = Rect::new_sized(rect.x1(), rect.y2(), rect.width(), 1).unwrap();
|
||||||
rd.underline_rects.push(rect);
|
rd.underline_rects.push(rect);
|
||||||
}
|
}
|
||||||
let title = child.title.borrow_mut();
|
let tt = &*child.title_tex.borrow();
|
||||||
for (scale, _) in scales.iter() {
|
for (scale, tex) in tt {
|
||||||
let old_tex = child.title_tex.remove(scale);
|
if let Some(tex) = tex.texture() {
|
||||||
let titles = rd.titles.get_or_default_mut(*scale);
|
let titles = rd.titles.get_or_default_mut(*scale);
|
||||||
'render_title: {
|
titles.push(ContainerTitle {
|
||||||
let mut th = th;
|
x: rect.x1(),
|
||||||
let mut scalef = None;
|
y: rect.y1(),
|
||||||
let mut width = rect.width();
|
tex,
|
||||||
if *scale != 1 {
|
})
|
||||||
let scale = scale.to_f64();
|
|
||||||
th = (th as f64 * scale).round() as _;
|
|
||||||
width = (width as f64 * scale).round() as _;
|
|
||||||
scalef = Some(scale);
|
|
||||||
}
|
|
||||||
if th == 0 || width == 0 || title.is_empty() {
|
|
||||||
break 'render_title;
|
|
||||||
}
|
|
||||||
if let Some(ctx) = &ctx {
|
|
||||||
match text::render(
|
|
||||||
ctx,
|
|
||||||
old_tex,
|
|
||||||
width,
|
|
||||||
th,
|
|
||||||
&font,
|
|
||||||
title.deref(),
|
|
||||||
color,
|
|
||||||
scalef,
|
|
||||||
) {
|
|
||||||
Ok(t) => {
|
|
||||||
child.title_tex.insert(*scale, t.clone());
|
|
||||||
titles.push(ContainerTitle {
|
|
||||||
x: rect.x1(),
|
|
||||||
y: rect.y1(),
|
|
||||||
tex: t,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1010,7 +1072,7 @@ impl ContainerNode {
|
||||||
}
|
}
|
||||||
self.update_title();
|
self.update_title();
|
||||||
// log::info!("node_child_title_changed");
|
// log::info!("node_child_title_changed");
|
||||||
self.schedule_compute_render_data();
|
self.schedule_render_titles();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_child_active(
|
fn update_child_active(
|
||||||
|
|
@ -1027,7 +1089,8 @@ impl ContainerNode {
|
||||||
.set(Some(self.focus_history.add_last(node.clone())));
|
.set(Some(self.focus_history.add_last(node.clone())));
|
||||||
}
|
}
|
||||||
// log::info!("node_child_active_changed");
|
// log::info!("node_child_active_changed");
|
||||||
self.schedule_compute_render_data();
|
self.schedule_render_titles();
|
||||||
|
self.schedule_compute_render_positions();
|
||||||
if let Some(parent) = self.toplevel_data.parent.get() {
|
if let Some(parent) = self.toplevel_data.parent.get() {
|
||||||
parent.node_child_active_changed(self.deref(), active, depth + 1);
|
parent.node_child_active_changed(self.deref(), active, depth + 1);
|
||||||
}
|
}
|
||||||
|
|
@ -1175,11 +1238,22 @@ pub async fn container_layout(state: Rc<State>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn container_render_data(state: Rc<State>) {
|
pub async fn container_render_positions(state: Rc<State>) {
|
||||||
loop {
|
loop {
|
||||||
let container = state.pending_container_render_data.pop().await;
|
let container = state.pending_container_render_positions.pop().await;
|
||||||
if container.compute_render_data_scheduled.get() {
|
if container.compute_render_positions_scheduled.get() {
|
||||||
container.compute_render_data();
|
container.compute_render_positions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn container_render_titles(state: Rc<State>) {
|
||||||
|
loop {
|
||||||
|
let container = state.pending_container_render_title.pop().await;
|
||||||
|
if container.render_titles_scheduled.get() {
|
||||||
|
container.render_titles_scheduled.set(false);
|
||||||
|
container.render_titles().triggered().await;
|
||||||
|
container.compute_title_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1562,7 +1636,7 @@ impl ContainingNode for ContainerNode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.mod_attention_requests(set);
|
self.mod_attention_requests(set);
|
||||||
self.schedule_compute_render_data();
|
self.schedule_compute_render_positions();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cnode_workspace(self: Rc<Self>) -> Rc<WorkspaceNode> {
|
fn cnode_workspace(self: Rc<Self>) -> Rc<WorkspaceNode> {
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,15 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
scale::Scale,
|
scale::Scale,
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
||||||
FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode,
|
FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState,
|
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
|
||||||
errorfmt::ErrorFmt, linkedlist::LinkedNode,
|
errorfmt::ErrorFmt, linkedlist::LinkedNode, on_drop_event::OnDropEvent,
|
||||||
|
smallmap::SmallMapMut,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ahash::AHashMap,
|
ahash::AHashMap,
|
||||||
|
|
@ -47,7 +48,7 @@ pub struct FloatNode {
|
||||||
pub layout_scheduled: Cell<bool>,
|
pub layout_scheduled: Cell<bool>,
|
||||||
pub render_titles_scheduled: Cell<bool>,
|
pub render_titles_scheduled: Cell<bool>,
|
||||||
pub title: RefCell<String>,
|
pub title: RefCell<String>,
|
||||||
pub title_textures: CopyHashMap<Scale, TextTexture>,
|
pub title_textures: RefCell<SmallMapMut<Scale, TextTexture, 2>>,
|
||||||
cursors: RefCell<AHashMap<CursorType, CursorState>>,
|
cursors: RefCell<AHashMap<CursorType, CursorState>>,
|
||||||
pub attention_requested: Cell<bool>,
|
pub attention_requested: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +97,9 @@ pub async fn float_titles(state: Rc<State>) {
|
||||||
loop {
|
loop {
|
||||||
let node = state.pending_float_titles.pop().await;
|
let node = state.pending_float_titles.pop().await;
|
||||||
if node.render_titles_scheduled.get() {
|
if node.render_titles_scheduled.get() {
|
||||||
node.render_title();
|
node.render_titles_scheduled.set(false);
|
||||||
|
node.render_title_phase1().triggered().await;
|
||||||
|
node.render_title_phase2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,8 +185,8 @@ impl FloatNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_title(&self) {
|
fn render_title_phase1(&self) -> Rc<AsyncEvent> {
|
||||||
self.render_titles_scheduled.set(false);
|
let on_completed = Rc::new(OnDropEvent::default());
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let th = theme.sizes.title_height.get();
|
let th = theme.sizes.title_height.get();
|
||||||
let tc = match self.active.get() {
|
let tc = match self.active.get() {
|
||||||
|
|
@ -191,20 +194,22 @@ impl FloatNode {
|
||||||
false => theme.colors.unfocused_title_text.get(),
|
false => theme.colors.unfocused_title_text.get(),
|
||||||
};
|
};
|
||||||
let bw = theme.sizes.border_width.get();
|
let bw = theme.sizes.border_width.get();
|
||||||
let font = theme.font.borrow_mut();
|
let font = theme.font.get();
|
||||||
let title = self.title.borrow_mut();
|
let title = self.title.borrow_mut();
|
||||||
let pos = self.position.get();
|
let pos = self.position.get();
|
||||||
if pos.width() <= 2 * bw || title.is_empty() {
|
if pos.width() <= 2 * bw {
|
||||||
return;
|
return on_completed.event();
|
||||||
}
|
}
|
||||||
let ctx = match self.state.render_ctx.get() {
|
let ctx = match self.state.render_ctx.get() {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
_ => return,
|
_ => return on_completed.event(),
|
||||||
};
|
};
|
||||||
let scales = self.state.scales.lock();
|
let scales = self.state.scales.lock();
|
||||||
let tr = Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap();
|
let tr = Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap();
|
||||||
|
let tt = &mut *self.title_textures.borrow_mut();
|
||||||
for (scale, _) in scales.iter() {
|
for (scale, _) in scales.iter() {
|
||||||
let old_tex = self.title_textures.remove(scale);
|
let tex =
|
||||||
|
tt.get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx));
|
||||||
let mut th = tr.height();
|
let mut th = tr.height();
|
||||||
let mut scalef = None;
|
let mut scalef = None;
|
||||||
let mut width = tr.width();
|
let mut width = tr.width();
|
||||||
|
|
@ -217,16 +222,39 @@ impl FloatNode {
|
||||||
if th == 0 || width == 0 {
|
if th == 0 || width == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let texture = match text::render(&ctx, old_tex, width, th, &font, &title, tc, scalef) {
|
tex.schedule_render(
|
||||||
Ok(t) => t,
|
on_completed.clone(),
|
||||||
Err(e) => {
|
1,
|
||||||
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
|
None,
|
||||||
return;
|
width,
|
||||||
}
|
th,
|
||||||
};
|
1,
|
||||||
self.title_textures.set(*scale, texture);
|
&font,
|
||||||
|
&title,
|
||||||
|
tc,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
scalef,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if self.visible.get() {
|
on_completed.event()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_title_phase2(&self) {
|
||||||
|
let theme = &self.state.theme;
|
||||||
|
let th = theme.sizes.title_height.get();
|
||||||
|
let bw = theme.sizes.border_width.get();
|
||||||
|
let title = self.title.borrow();
|
||||||
|
let tt = &*self.title_textures.borrow();
|
||||||
|
for (_, tt) in tt {
|
||||||
|
if let Err(e) = tt.flip() {
|
||||||
|
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let pos = self.position.get();
|
||||||
|
if self.visible.get() && pos.width() >= 2 * bw {
|
||||||
|
let tr =
|
||||||
|
Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap();
|
||||||
self.state.damage(tr);
|
self.state.damage(tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,16 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
scale::Scale,
|
scale::Scale,
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node,
|
walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node,
|
||||||
NodeId, StackedNode, WorkspaceNode,
|
NodeId, StackedNode, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||||
event_listener::EventSource, hash_map_ext::HashMapExt, linkedlist::LinkedList,
|
errorfmt::ErrorFmt, event_listener::EventSource, hash_map_ext::HashMapExt,
|
||||||
scroller::Scroller, transform_ext::TransformExt,
|
linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller,
|
||||||
|
transform_ext::TransformExt,
|
||||||
},
|
},
|
||||||
wire::{JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id},
|
wire::{JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id},
|
||||||
},
|
},
|
||||||
|
|
@ -114,12 +115,14 @@ pub enum PointerType {
|
||||||
|
|
||||||
pub async fn output_render_data(state: Rc<State>) {
|
pub async fn output_render_data(state: Rc<State>) {
|
||||||
loop {
|
loop {
|
||||||
let container = state.pending_output_render_data.pop().await;
|
let output = state.pending_output_render_data.pop().await;
|
||||||
if container.global.destroyed.get() {
|
if output.global.destroyed.get() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if container.update_render_data_scheduled.get() {
|
if output.update_render_data_scheduled.get() {
|
||||||
container.update_render_data();
|
output.update_render_data_scheduled.set(false);
|
||||||
|
output.update_render_data_phase1().triggered().await;
|
||||||
|
output.update_render_data_phase2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -367,17 +370,12 @@ impl OutputNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_render_data(&self) {
|
fn update_render_data_phase1(self: &Rc<Self>) -> Rc<AsyncEvent> {
|
||||||
self.update_render_data_scheduled.set(false);
|
let on_completed = Rc::new(OnDropEvent::default());
|
||||||
let mut rd = self.render_data.borrow_mut();
|
let Some(ctx) = self.state.render_ctx.get() else {
|
||||||
rd.titles.clear();
|
return on_completed.event();
|
||||||
rd.inactive_workspaces.clear();
|
};
|
||||||
rd.attention_requested_workspaces.clear();
|
let font = self.state.theme.font.get();
|
||||||
rd.captured_inactive_workspaces.clear();
|
|
||||||
rd.active_workspace = None;
|
|
||||||
rd.status = None;
|
|
||||||
let mut pos = 0;
|
|
||||||
let font = self.state.theme.font.borrow_mut();
|
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let th = theme.sizes.title_height.get();
|
let th = theme.sizes.title_height.get();
|
||||||
let scale = self.global.persistent.scale.get();
|
let scale = self.global.persistent.scale.get();
|
||||||
|
|
@ -391,40 +389,72 @@ impl OutputNode {
|
||||||
texture_height = (th as f64 * scale).round() as _;
|
texture_height = (th as f64 * scale).round() as _;
|
||||||
}
|
}
|
||||||
let active_id = self.workspace.get().map(|w| w.id);
|
let active_id = self.workspace.get().map(|w| w.id);
|
||||||
|
for ws in self.workspaces.iter() {
|
||||||
|
let tex = &mut *ws.title_texture.borrow_mut();
|
||||||
|
let tex = tex.get_or_insert_with(|| TextTexture::new(&self.state.cpu_worker, &ctx));
|
||||||
|
let tc = match active_id == Some(ws.id) {
|
||||||
|
true => theme.colors.focused_title_text.get(),
|
||||||
|
false => theme.colors.unfocused_title_text.get(),
|
||||||
|
};
|
||||||
|
tex.schedule_render_fitting(
|
||||||
|
on_completed.clone(),
|
||||||
|
Some(texture_height),
|
||||||
|
&font,
|
||||||
|
&ws.name,
|
||||||
|
tc,
|
||||||
|
false,
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut rd = self.render_data.borrow_mut();
|
||||||
|
let tex = rd.status.get_or_insert_with(|| OutputStatus {
|
||||||
|
tex_x: 0,
|
||||||
|
tex: TextTexture::new(&self.state.cpu_worker, &ctx),
|
||||||
|
});
|
||||||
|
let status = self.status.get();
|
||||||
|
let tc = self.state.theme.colors.bar_text.get();
|
||||||
|
tex.tex.schedule_render_fitting(
|
||||||
|
on_completed.clone(),
|
||||||
|
Some(texture_height),
|
||||||
|
&font,
|
||||||
|
&status,
|
||||||
|
tc,
|
||||||
|
true,
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
on_completed.event()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_render_data_phase2(&self) {
|
||||||
|
let mut rd = self.render_data.borrow_mut();
|
||||||
|
rd.titles.clear();
|
||||||
|
rd.inactive_workspaces.clear();
|
||||||
|
rd.attention_requested_workspaces.clear();
|
||||||
|
rd.captured_inactive_workspaces.clear();
|
||||||
|
rd.active_workspace = None;
|
||||||
|
let mut pos = 0;
|
||||||
|
let theme = &self.state.theme;
|
||||||
|
let th = theme.sizes.title_height.get();
|
||||||
|
let scale = self.global.persistent.scale.get();
|
||||||
|
let scale = if scale != 1 {
|
||||||
|
Some(scale.to_f64())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let active_id = self.workspace.get().map(|w| w.id);
|
||||||
let non_exclusive_rect = self.non_exclusive_rect.get();
|
let non_exclusive_rect = self.non_exclusive_rect.get();
|
||||||
let output_width = non_exclusive_rect.width();
|
let output_width = non_exclusive_rect.width();
|
||||||
rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap();
|
rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap();
|
||||||
for ws in self.workspaces.iter() {
|
for ws in self.workspaces.iter() {
|
||||||
let old_tex = ws.title_texture.take();
|
|
||||||
let mut title_width = th;
|
let mut title_width = th;
|
||||||
'create_texture: {
|
let title = &*ws.title_texture.borrow();
|
||||||
if let Some(ctx) = self.state.render_ctx.get() {
|
if let Some(title) = title {
|
||||||
if th == 0 || ws.name.is_empty() {
|
if let Err(e) = title.flip() {
|
||||||
break 'create_texture;
|
log::error!("Could not render title: {}", ErrorFmt(e));
|
||||||
}
|
}
|
||||||
let tc = match active_id == Some(ws.id) {
|
if let Some(texture) = title.texture() {
|
||||||
true => theme.colors.focused_title_text.get(),
|
|
||||||
false => theme.colors.unfocused_title_text.get(),
|
|
||||||
};
|
|
||||||
let title = match text::render_fitting(
|
|
||||||
&ctx,
|
|
||||||
old_tex,
|
|
||||||
Some(texture_height),
|
|
||||||
&font,
|
|
||||||
&ws.name,
|
|
||||||
tc,
|
|
||||||
false,
|
|
||||||
scale,
|
|
||||||
) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e));
|
|
||||||
break 'create_texture;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ws.title_texture.set(Some(title.clone()));
|
|
||||||
let mut x = pos + 1;
|
let mut x = pos + 1;
|
||||||
let (mut width, _) = title.texture.size();
|
let (mut width, _) = texture.size();
|
||||||
if let Some(scale) = scale {
|
if let Some(scale) = scale {
|
||||||
width = (width as f64 / scale).round() as _;
|
width = (width as f64 / scale).round() as _;
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +468,7 @@ impl OutputNode {
|
||||||
x2: pos + title_width,
|
x2: pos + title_width,
|
||||||
tex_x: x,
|
tex_x: x,
|
||||||
tex_y: 0,
|
tex_y: 0,
|
||||||
tex: title.texture,
|
tex: texture,
|
||||||
ws: ws.deref().clone(),
|
ws: ws.deref().clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -461,43 +491,18 @@ impl OutputNode {
|
||||||
}
|
}
|
||||||
pos += title_width;
|
pos += title_width;
|
||||||
}
|
}
|
||||||
'set_status: {
|
if let Some(status) = &mut rd.status {
|
||||||
let old_tex = rd.status.take().map(|s| s.tex);
|
if let Err(e) = status.tex.flip() {
|
||||||
let ctx = match self.state.render_ctx.get() {
|
log::error!("Could not render status: {}", ErrorFmt(e));
|
||||||
Some(ctx) => ctx,
|
|
||||||
_ => break 'set_status,
|
|
||||||
};
|
|
||||||
let status = self.status.get();
|
|
||||||
if status.is_empty() {
|
|
||||||
break 'set_status;
|
|
||||||
}
|
}
|
||||||
let tc = self.state.theme.colors.bar_text.get();
|
if let Some(texture) = status.tex.texture() {
|
||||||
let title = match text::render_fitting(
|
let (mut width, _) = texture.size();
|
||||||
&ctx,
|
if let Some(scale) = scale {
|
||||||
old_tex,
|
width = (width as f64 / scale).round() as _;
|
||||||
Some(texture_height),
|
|
||||||
&font,
|
|
||||||
&status,
|
|
||||||
tc,
|
|
||||||
true,
|
|
||||||
scale,
|
|
||||||
) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Could not render status {}: {}", status, ErrorFmt(e));
|
|
||||||
break 'set_status;
|
|
||||||
}
|
}
|
||||||
};
|
let pos = output_width - width - 1;
|
||||||
let (mut width, _) = title.texture.size();
|
status.tex_x = pos;
|
||||||
if let Some(scale) = scale {
|
|
||||||
width = (width as f64 / scale).round() as _;
|
|
||||||
}
|
}
|
||||||
let pos = output_width - width - 1;
|
|
||||||
rd.status = Some(OutputStatus {
|
|
||||||
tex_x: pos,
|
|
||||||
tex_y: 0,
|
|
||||||
tex: title,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if self.title_visible.get() {
|
if self.title_visible.get() {
|
||||||
let title_rect = Rect::new_sized(
|
let title_rect = Rect::new_sized(
|
||||||
|
|
@ -945,7 +950,6 @@ pub struct OutputTitle {
|
||||||
|
|
||||||
pub struct OutputStatus {
|
pub struct OutputStatus {
|
||||||
pub tex_x: i32,
|
pub tex_x: i32,
|
||||||
pub tex_y: i32,
|
|
||||||
pub tex: TextTexture,
|
pub tex: TextTexture,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,22 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
scale::Scale,
|
scale::Scale,
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
||||||
ToplevelData, ToplevelNode, ToplevelNodeBase,
|
ToplevelData, ToplevelNode, ToplevelNodeBase,
|
||||||
},
|
},
|
||||||
utils::{errorfmt::ErrorFmt, smallmap::SmallMap},
|
utils::{
|
||||||
|
asyncevent::AsyncEvent, errorfmt::ErrorFmt, on_drop_event::OnDropEvent,
|
||||||
|
smallmap::SmallMapMut,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
ops::Deref,
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
},
|
},
|
||||||
std::{cell::Cell, ops::Deref, rc::Rc},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tree_id!(PlaceholderNodeId);
|
tree_id!(PlaceholderNodeId);
|
||||||
|
|
@ -24,7 +32,18 @@ pub struct PlaceholderNode {
|
||||||
id: PlaceholderNodeId,
|
id: PlaceholderNodeId,
|
||||||
toplevel: ToplevelData,
|
toplevel: ToplevelData,
|
||||||
destroyed: Cell<bool>,
|
destroyed: Cell<bool>,
|
||||||
pub textures: SmallMap<Scale, TextTexture, 2>,
|
update_textures_scheduled: Cell<bool>,
|
||||||
|
state: Rc<State>,
|
||||||
|
pub textures: RefCell<SmallMapMut<Scale, TextTexture, 2>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn placeholder_render_textures(state: Rc<State>) {
|
||||||
|
loop {
|
||||||
|
let container = state.pending_placeholder_render_textures.pop().await;
|
||||||
|
container.update_textures_scheduled.set(false);
|
||||||
|
container.update_texture_phase1().triggered().await;
|
||||||
|
container.update_texture_phase2();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceholderNode {
|
impl PlaceholderNode {
|
||||||
|
|
@ -37,6 +56,8 @@ impl PlaceholderNode {
|
||||||
node.node_client(),
|
node.node_client(),
|
||||||
),
|
),
|
||||||
destroyed: Default::default(),
|
destroyed: Default::default(),
|
||||||
|
update_textures_scheduled: Cell::new(false),
|
||||||
|
state: state.clone(),
|
||||||
textures: Default::default(),
|
textures: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,39 +66,53 @@ impl PlaceholderNode {
|
||||||
self.destroyed.get()
|
self.destroyed.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_texture(&self) {
|
pub fn schedule_update_texture(self: &Rc<Self>) {
|
||||||
if let Some(ctx) = self.toplevel.state.render_ctx.get() {
|
if !self.update_textures_scheduled.replace(true) {
|
||||||
let scales = self.toplevel.state.scales.lock();
|
self.state
|
||||||
let rect = self.toplevel.pos.get();
|
.pending_placeholder_render_textures
|
||||||
for (scale, _) in scales.iter() {
|
.push(self.clone());
|
||||||
let old_tex = self.textures.remove(scale);
|
}
|
||||||
let mut width = rect.width();
|
}
|
||||||
let mut height = rect.height();
|
|
||||||
if *scale != 1 {
|
fn update_texture_phase1(&self) -> Rc<AsyncEvent> {
|
||||||
let scale = scale.to_f64();
|
let on_completed = Rc::new(OnDropEvent::default());
|
||||||
width = (width as f64 * scale).round() as _;
|
let Some(ctx) = self.toplevel.state.render_ctx.get() else {
|
||||||
height = (height as f64 * scale).round() as _;
|
return on_completed.event();
|
||||||
}
|
};
|
||||||
if width != 0 && height != 0 {
|
let scales = self.toplevel.state.scales.lock();
|
||||||
let font = format!("monospace {}", width / 10);
|
let rect = self.toplevel.pos.get();
|
||||||
match text::render_fitting(
|
let mut textures = self.textures.borrow_mut();
|
||||||
&ctx,
|
for (scale, _) in scales.iter() {
|
||||||
old_tex,
|
let tex = textures
|
||||||
Some(height),
|
.get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx));
|
||||||
&font,
|
let mut width = rect.width();
|
||||||
"Fullscreen",
|
let mut height = rect.height();
|
||||||
self.toplevel.state.theme.colors.unfocused_title_text.get(),
|
if *scale != 1 {
|
||||||
false,
|
let scale = scale.to_f64();
|
||||||
None,
|
width = (width as f64 * scale).round() as _;
|
||||||
) {
|
height = (height as f64 * scale).round() as _;
|
||||||
Ok(t) => {
|
}
|
||||||
self.textures.insert(*scale, t);
|
if width != 0 && height != 0 {
|
||||||
}
|
let font = Arc::new(format!("monospace {}", width / 10));
|
||||||
Err(e) => {
|
tex.schedule_render_fitting(
|
||||||
log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e));
|
on_completed.clone(),
|
||||||
}
|
Some(height),
|
||||||
}
|
&font,
|
||||||
}
|
"Fullscreen",
|
||||||
|
self.toplevel.state.theme.colors.unfocused_title_text.get(),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
on_completed.event()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_texture_phase2(&self) {
|
||||||
|
let textures = &*self.textures.borrow();
|
||||||
|
for (_, texture) in textures {
|
||||||
|
if let Err(e) = texture.flip() {
|
||||||
|
log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +197,7 @@ impl ToplevelNodeBase for PlaceholderNode {
|
||||||
if let Some(p) = self.toplevel.parent.get() {
|
if let Some(p) = self.toplevel.parent.get() {
|
||||||
p.node_child_size_changed(self.deref(), rect.width(), rect.height());
|
p.node_child_size_changed(self.deref(), rect.width(), rect.height());
|
||||||
}
|
}
|
||||||
self.update_texture();
|
self.schedule_update_texture();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tl_close(self: Rc<Self>) {
|
fn tl_close(self: Rc<Self>) {
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ pub struct WorkspaceNode {
|
||||||
pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc<JayWorkspace>>,
|
pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc<JayWorkspace>>,
|
||||||
pub may_capture: Cell<bool>,
|
pub may_capture: Cell<bool>,
|
||||||
pub has_capture: Cell<bool>,
|
pub has_capture: Cell<bool>,
|
||||||
pub title_texture: Cell<Option<TextTexture>>,
|
pub title_texture: RefCell<Option<TextTexture>>,
|
||||||
pub attention_requests: ThresholdCounter,
|
pub attention_requests: ThresholdCounter,
|
||||||
pub render_highlight: NumCell<u32>,
|
pub render_highlight: NumCell<u32>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ pub mod num_cpus;
|
||||||
pub mod numcell;
|
pub mod numcell;
|
||||||
pub mod on_change;
|
pub mod on_change;
|
||||||
pub mod on_drop;
|
pub mod on_drop;
|
||||||
|
pub mod on_drop_event;
|
||||||
pub mod once;
|
pub mod once;
|
||||||
pub mod opaque;
|
pub mod opaque;
|
||||||
pub mod opaque_cell;
|
pub mod opaque_cell;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use {
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
mem,
|
mem,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
|
sync::Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -83,6 +84,7 @@ unsafe impl<T: UnsafeCellCloneSafe> UnsafeCellCloneSafe for Option<T> {}
|
||||||
|
|
||||||
unsafe impl<T: ?Sized> UnsafeCellCloneSafe for Rc<T> {}
|
unsafe impl<T: ?Sized> UnsafeCellCloneSafe for Rc<T> {}
|
||||||
unsafe impl<T: ?Sized> UnsafeCellCloneSafe for Weak<T> {}
|
unsafe impl<T: ?Sized> UnsafeCellCloneSafe for Weak<T> {}
|
||||||
|
unsafe impl<T: ?Sized> UnsafeCellCloneSafe for Arc<T> {}
|
||||||
|
|
||||||
unsafe impl<T> UnsafeCellCloneSafe for NodeRef<T> {}
|
unsafe impl<T> UnsafeCellCloneSafe for NodeRef<T> {}
|
||||||
|
|
||||||
|
|
|
||||||
18
src/utils/on_drop_event.rs
Normal file
18
src/utils/on_drop_event.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use {crate::utils::asyncevent::AsyncEvent, std::rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct OnDropEvent {
|
||||||
|
ae: Rc<AsyncEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OnDropEvent {
|
||||||
|
pub fn event(&self) -> Rc<AsyncEvent> {
|
||||||
|
self.ae.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for OnDropEvent {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.ae.trigger()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -201,13 +201,20 @@ impl<K: Eq, V, const N: usize> SmallMapMut<K, V, N> {
|
||||||
pub fn get_or_default_mut(&mut self, k: K) -> &mut V
|
pub fn get_or_default_mut(&mut self, k: K) -> &mut V
|
||||||
where
|
where
|
||||||
V: Default,
|
V: Default,
|
||||||
|
{
|
||||||
|
self.get_or_insert_with(k, || V::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_or_insert_with<F>(&mut self, k: K, f: F) -> &mut V
|
||||||
|
where
|
||||||
|
F: FnOnce() -> V,
|
||||||
{
|
{
|
||||||
for (ek, ev) in &mut self.m {
|
for (ek, ev) in &mut self.m {
|
||||||
if ek == &k {
|
if ek == &k {
|
||||||
return unsafe { (ev as *mut V).deref_mut() };
|
return unsafe { (ev as *mut V).deref_mut() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.m.push((k, V::default()));
|
self.m.push((k, f()));
|
||||||
&mut self.m.last_mut().unwrap().1
|
&mut self.m.last_mut().unwrap().1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue