1
0
Fork 0
forked from wry/wry

text: render text asynchronously

This commit is contained in:
Julian Orth 2024-09-28 18:03:23 +02:00
parent d9eb14e2bc
commit 12f358c0d9
12 changed files with 893 additions and 421 deletions

View file

@ -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(),
}); });

View file

@ -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,

View file

@ -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();

View file

@ -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
}
} }

View file

@ -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.get();
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> {

View file

@ -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() {
@ -194,17 +197,19 @@ impl FloatNode {
let font = theme.font.get(); 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);
} }
} }

View file

@ -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,16 +370,11 @@ 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();
rd.captured_inactive_workspaces.clear();
rd.active_workspace = None;
rd.status = None;
let mut pos = 0;
let font = self.state.theme.font.get(); let font = self.state.theme.font.get();
let theme = &self.state.theme; let theme = &self.state.theme;
let th = theme.sizes.title_height.get(); let th = theme.sizes.title_height.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,
} }

View file

@ -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>) {

View file

@ -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>,
} }

View file

@ -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;

View 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()
}
}

View file

@ -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
} }