diff --git a/src/compositor.rs b/src/compositor.rs index c3bc5fc4..193024d4 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -35,9 +35,9 @@ use { tasks::{self, idle}, tracy::enable_profiler, tree::{ - container_layout, container_render_data, float_layout, float_titles, - output_render_data, DisplayNode, NodeIds, OutputNode, TearingMode, VrrMode, - WorkspaceNode, + container_layout, container_render_positions, container_render_titles, float_layout, + float_titles, output_render_data, placeholder_render_textures, DisplayNode, NodeIds, + OutputNode, TearingMode, VrrMode, WorkspaceNode, }, user_session::import_environment, utils::{ @@ -180,13 +180,15 @@ fn start_compositor2( input_device_handlers: Default::default(), theme: 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_float_layout: Default::default(), pending_float_titles: Default::default(), pending_input_popup_positioning: Default::default(), pending_toplevel_screencasts: Default::default(), pending_screencast_reallocs_or_reconfigures: Default::default(), + pending_placeholder_render_textures: Default::default(), dbus: Dbus::new(&engine, &ring, &run_toplevel), fdcloser: FdCloser::new(), logger: logger.clone(), @@ -374,9 +376,19 @@ fn start_global_event_handlers( container_layout(state.clone()), ), eng.spawn2( - "container render", + "container render positions", 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( "output render", @@ -577,7 +589,7 @@ fn create_dummy_output(state: &Rc) { jay_workspaces: Default::default(), may_capture: Cell::new(false), has_capture: Cell::new(false), - title_texture: Cell::new(None), + title_texture: Default::default(), attention_requests: Default::default(), render_highlight: Default::default(), }); diff --git a/src/renderer.rs b/src/renderer.rs index b5fb7e16..eb83d00a 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -131,20 +131,22 @@ impl Renderer<'_> { ); } if let Some(status) = &rd.status { - let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y); - self.base.render_texture( - &status.tex.texture, - None, - x, - y, - None, - None, - scale, - None, - None, - AcquireSync::None, - ReleaseSync::None, - ); + if let Some(texture) = status.tex.texture() { + let (x, y) = self.base.scale_point(x + status.tex_x, y); + self.base.render_texture( + &texture, + None, + x, + y, + None, + None, + scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); + } } } if let Some(ws) = output.workspace.get() { @@ -196,23 +198,25 @@ impl Renderer<'_> { std::slice::from_ref(&pos.at_point(x, y)), &Color::from_rgba_straight(20, 20, 20, 255), ); - if let Some(tex) = placeholder.textures.get(&self.base.scale) { - let (tex_width, tex_height) = tex.texture.size(); - let x = x + (pos.width() - tex_width) / 2; - let y = y + (pos.height() - tex_height) / 2; - self.base.render_texture( - &tex.texture, - None, - x, - y, - None, - None, - self.base.scale, - None, - None, - AcquireSync::None, - ReleaseSync::None, - ); + if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) { + if let Some(texture) = tex.texture() { + let (tex_width, tex_height) = texture.size(); + let x = x + (pos.width() - tex_width) / 2; + let y = y + (pos.height() - tex_height) / 2; + self.base.render_texture( + &texture, + None, + x, + y, + None, + None, + self.base.scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); + } } self.render_tl_aux(placeholder.tl_data(), bounds, true); } @@ -243,7 +247,7 @@ impl Renderer<'_> { for title in titles { let (x, y) = self.base.scale_point(x + title.x, y + title.y); self.base.render_texture( - &title.tex.texture, + &title.tex, None, x, y, @@ -466,21 +470,23 @@ impl Renderer<'_> { let title_underline = [Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()]; self.base.fill_boxes(&title_underline, &uc); - if let Some(title) = floating.title_textures.get(&self.base.scale) { - let (x, y) = self.base.scale_point(x + bw, y + bw); - self.base.render_texture( - &title.texture, - None, - x, - y, - None, - None, - self.base.scale, - None, - None, - AcquireSync::None, - ReleaseSync::None, - ); + if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) { + if let Some(texture) = title.texture() { + let (x, y) = self.base.scale_point(x + bw, y + bw); + self.base.render_texture( + &texture, + None, + x, + y, + None, + None, + self.base.scale, + None, + None, + AcquireSync::None, + ReleaseSync::None, + ); + } } let body = Rect::new_sized( x + bw, diff --git a/src/state.rs b/src/state.rs index a4b0feaf..b46655a6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -148,13 +148,15 @@ pub struct State { pub config: CloneCell>>, pub theme: Theme, pub pending_container_layout: AsyncQueue>, - pub pending_container_render_data: AsyncQueue>, + pub pending_container_render_positions: AsyncQueue>, + pub pending_container_render_title: AsyncQueue>, pub pending_output_render_data: AsyncQueue>, pub pending_float_layout: AsyncQueue>, pub pending_float_titles: AsyncQueue>, pub pending_input_popup_positioning: AsyncQueue>, pub pending_toplevel_screencasts: AsyncQueue>, pub pending_screencast_reallocs_or_reconfigures: AsyncQueue>, + pub pending_placeholder_render_textures: AsyncQueue>, pub dbus: Dbus, pub fdcloser: Arc, pub logger: Option>, @@ -343,8 +345,10 @@ impl DrmDevData { struct UpdateTextTexturesVisitor; impl NodeVisitorBase for UpdateTextTexturesVisitor { fn visit_container(&mut self, node: &Rc) { - node.children.iter().for_each(|c| c.title_tex.clear()); - node.schedule_compute_render_data(); + node.children + .iter() + .for_each(|c| c.title_tex.borrow_mut().clear()); + node.schedule_render_titles(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { @@ -352,13 +356,17 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.title_textures.clear(); + node.title_textures.borrow_mut().clear(); node.schedule_render_titles(); node.node_visit_children(self); } + fn visit_workspace(&mut self, node: &Rc) { + node.title_texture.take(); + node.node_visit_children(self); + } fn visit_placeholder(&mut self, node: &Rc) { - node.textures.clear(); - node.update_texture(); + node.textures.borrow_mut().clear(); + node.schedule_update_texture(); node.node_visit_children(self); } } @@ -463,11 +471,13 @@ impl State { impl NodeVisitorBase for Walker { fn visit_container(&mut self, node: &Rc) { 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); } fn visit_workspace(&mut self, node: &Rc) { - node.title_texture.set(None); + node.title_texture.take(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { @@ -477,11 +487,11 @@ impl State { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.title_textures.clear(); + node.title_textures.borrow_mut().clear(); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { - node.textures.clear(); + node.textures.borrow_mut().clear(); node.node_visit_children(self); } } @@ -819,13 +829,15 @@ impl State { } self.dbus.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_float_layout.clear(); self.pending_float_titles.clear(); self.pending_input_popup_positioning.clear(); self.pending_toplevel_screencasts.clear(); self.pending_screencast_reallocs_or_reconfigures.clear(); + self.pending_placeholder_render_textures.clear(); self.render_ctx_watchers.clear(); self.workspace_watchers.clear(); self.toplevel_lists.clear(); diff --git a/src/text.rs b/src/text.rs index f0176800..7eed4e21 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,7 +1,11 @@ use { crate::{ + cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, format::ARGB8888, - gfx_api::{GfxContext, GfxError, GfxTexture, ShmGfxTexture}, + gfx_api::{ + AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxContext, GfxError, GfxTexture, + PendingShmUpload, + }, pango::{ consts::{ CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, PANGO_ELLIPSIZE_END, PANGO_SCALE, @@ -9,14 +13,19 @@ use { CairoContext, CairoImageSurface, PangoCairoContext, PangoError, PangoFontDescription, PangoLayout, }, - rect::Rect, + rect::{Rect, Region}, theme::Color, - utils::clonecell::UnsafeCellCloneSafe, + utils::{ + clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent, + }, }, std::{ borrow::Cow, - ops::{Deref, Neg}, - rc::Rc, + cell::{Cell, RefCell}, + mem, + ops::Neg, + rc::{Rc, Weak}, + sync::Arc, }, thiserror::Error, }; @@ -31,54 +40,64 @@ pub enum TextError { PangoContext(#[source] PangoError), #[error("Could not create a pango layout")] CreateLayout(#[source] PangoError), - #[error("Could not import the rendered text")] - RenderError(#[source] GfxError), #[error("Could not access the cairo image data")] ImageData(#[source] PangoError), -} - -#[derive(PartialEq)] -struct Config<'a> { - x: i32, - y: Option, - width: i32, - height: i32, - padding: i32, - font: Cow<'a, str>, - text: Cow<'a, str>, - color: Color, - ellipsize: bool, - markup: bool, - scale: Option, + #[error("Texture upload failed")] + Upload(#[source] GfxError), + #[error("Could not create a texture")] + CreateTexture(#[source] GfxError), + #[error("Rendering is not scheduled or not yet completed")] + NotScheduled, } impl<'a> Config<'a> { fn to_static(self) -> Config<'static> { - Config { - x: self.x, - y: self.y, - width: self.width, - height: self.height, - padding: self.padding, - font: Cow::Owned(self.font.into_owned()), - text: Cow::Owned(self.text.into_owned()), - color: self.color, - ellipsize: self.ellipsize, - markup: self.markup, - scale: self.scale, + match self { + Config::None => Config::None, + Config::RenderFitting { + height, + font, + text, + color, + markup, + scale, + } => Config::RenderFitting { + height, + 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>, - shm_texture: Rc, - pub texture: Rc, -} - -unsafe impl UnsafeCellCloneSafe for TextTexture {} - struct Data { image: Rc, cctx: Rc, @@ -118,12 +137,11 @@ fn create_data(font: &str, width: i32, height: i32, scale: Option) -> Resul }) } -pub fn measure( +fn measure( font: &str, text: &str, markup: bool, scale: Option, - full: bool, ) -> Result { let data = create_data(font, 1, 1, scale)?; if markup { @@ -133,31 +151,10 @@ pub fn measure( } 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(); - } Ok(res) } -pub fn render( - ctx: &Rc, - old: Option, - width: i32, - height: i32, - font: &str, - text: &str, - color: Color, - scale: Option, -) -> Result { - render2( - ctx, old, 1, None, width, height, 1, font, text, color, true, false, scale, - ) -} - -fn render2( - ctx: &Rc, - old: Option, +fn render( x: i32, y: Option, width: i32, @@ -169,25 +166,14 @@ fn render2( ellipsize: bool, markup: bool, scale: Option, -) -> Result { - let width = width.min(3840); - let config = Config { - x, - y, - width, - height, - 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()); - } +) -> Result { + if width == 0 || height == 0 { + return Ok(RenderedText { + width, + height, + stride: width * 4, + data: vec![], + }); } let data = create_data(font, width, height, scale)?; if ellipsize { @@ -208,79 +194,368 @@ fn render2( data.cctx.move_to(x as f64, y as f64); data.layout.show_layout(); data.image.flush(); - let bytes = match data.image.data() { - 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, + Ok(RenderedText { width, height, - data.image.stride(), - None, - ) { - Ok(t) => Ok(TextTexture { - config: Rc::new(config.to_static()), - texture: t.clone().into_texture(), - shm_texture: t, - }), - Err(e) => Err(TextError::RenderError(e)), - } + stride: data.image.stride(), + data: data.image.data().map_err(TextError::ImageData)?.to_vec(), + }) } -pub fn render_fitting( - ctx: &Rc, - old: Option, +fn render_fitting( height: Option, font: &str, text: &str, color: Color, markup: bool, scale: Option, -) -> Result { - render_fitting2(ctx, old, height, font, text, color, markup, scale, false).map(|(a, _)| a) +) -> Result { + 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)] pub struct TextMeasurement { pub ink_rect: Rect, - pub logical_rect: Rect, - pub baseline: i32, } -pub fn render_fitting2( - ctx: &Rc, - old: Option, - height: Option, - font: &str, - text: &str, - color: Color, - markup: bool, - scale: Option, - include_measurements: bool, -) -> Result<(TextTexture, TextMeasurement), TextError> { - let measurement = measure(font, text, markup, scale, include_measurements)?; - let y = match height { - Some(_) => None, - _ => Some(measurement.ink_rect.y1().neg()), - }; - let res = render2( - ctx, - old, - measurement.ink_rect.x1().neg(), - y, - measurement.ink_rect.width(), - height.unwrap_or(measurement.ink_rect.height()), - 0, - font, - text, - color, - false, - markup, - scale, - ); - res.map(|r| (r, measurement)) +struct RenderedText { + width: i32, + height: i32, + stride: i32, + data: Vec>, +} + +#[derive(Default)] +struct RenderWork { + config: Config<'static>, + result: Option>, +} + +struct RenderJob { + work: RenderWork, + data: Weak, +} + +impl CpuWork for RenderWork { + fn run(&mut self) -> Option> { + self.result = Some(self.render()); + None + } +} + +impl RenderWork { + fn render(&mut self) -> Result { + match self.config { + Config::None => unreachable!(), + Config::RenderFitting { + height, + 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, +} + +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, + ctx: Rc, + textures: DoubleBuffered, + pending_render: Cell>, + pending_upload: Cell>, + render_job: Cell>>, + result: Cell>>, + waiter: Cell>>, + busy: Cell, + flip_is_noop: Cell, +} + +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, + font: Arc, + text: Cow<'a, str>, + color: Color, + markup: bool, + scale: Option, + }, + Render { + x: i32, + y: Option, + width: i32, + height: i32, + padding: i32, + font: Arc, + text: Cow<'a, str>, + color: Color, + ellipsize: bool, + markup: bool, + scale: Option, + }, +} + +#[derive(Default)] +struct TextBuffer { + config: RefCell>, + tex: CloneCell>>, +} + +pub trait OnCompleted { + fn completed(self: Rc); +} + +impl TextTexture { + pub fn new(cpu_worker: &Rc, ctx: &Rc) -> 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> { + self.data + .textures + .front() + .tex + .get() + .map(|t| t.into_texture()) + } + + fn apply_config(&self, on_completed: Rc, 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, + x: i32, + y: Option, + width: i32, + height: i32, + padding: i32, + font: &Arc, + text: &str, + color: Color, + ellipsize: bool, + markup: bool, + scale: Option, + ) { + 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, + height: Option, + font: &Arc, + text: &str, + color: Color, + markup: bool, + scale: Option, + ) { + 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) { + 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, res: Result<(), GfxError>) { + self.pending_upload.take(); + self.complete(res.map_err(TextError::Upload)); + } +} + +impl OnCompleted for OnDropEvent { + fn completed(self: Rc) { + // nothing + } } diff --git a/src/tree/container.rs b/src/tree/container.rs index 9216a8f2..7240ac77 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -4,6 +4,7 @@ use { cursor::KnownCursor, cursor_user::CursorUser, fixed::Fixed, + gfx_api::GfxTexture, ifs::wl_seat::{ collect_kb_foci, collect_kb_foci2, tablet::{TabletTool, TabletToolChanges, TabletToolId}, @@ -14,21 +15,23 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, }, utils::{ + asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, + on_drop_event::OnDropEvent, rc_eq::rc_eq, scroller::Scroller, - smallmap::{SmallMap, SmallMapMut}, + smallmap::SmallMapMut, threshold_counter::ThresholdCounter, }, }, @@ -81,7 +84,7 @@ tree_id!(ContainerNodeId); pub struct ContainerTitle { pub x: i32, pub y: i32, - pub tex: TextTexture, + pub tex: Rc, } #[derive(Default)] @@ -109,7 +112,8 @@ pub struct ContainerNode { pub content_height: Cell, pub sum_factors: Cell, layout_scheduled: Cell, - compute_render_data_scheduled: Cell, + compute_render_positions_scheduled: Cell, + render_titles_scheduled: Cell, num_children: NumCell, pub children: LinkedList, focus_history: LinkedList>, @@ -134,7 +138,7 @@ pub struct ContainerChild { pub active: Cell, pub attention_requested: Cell, title: RefCell, - pub title_tex: SmallMap, + pub title_tex: RefCell>, pub title_rect: Cell, focus_history: Cell>>>, @@ -213,7 +217,8 @@ impl ContainerNode { content_height: Cell::new(0), sum_factors: Cell::new(1.0), 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), children, focus_history: Default::default(), @@ -351,7 +356,8 @@ impl ContainerNode { pub fn on_colors_changed(self: &Rc) { // log::info!("on_colors_changed"); - self.schedule_compute_render_data(); + self.schedule_render_titles(); + self.schedule_compute_render_positions(); } fn damage(&self) { @@ -387,7 +393,8 @@ impl ContainerNode { } self.state.tree_changed(); // log::info!("perform_layout"); - self.schedule_compute_render_data(); + self.schedule_render_titles(); + self.schedule_compute_render_positions(); } fn perform_mono_layout(self: &Rc, child: &ContainerChild) { @@ -660,23 +667,115 @@ impl ContainerNode { self.tl_title_changed(); } - pub fn schedule_compute_render_data(self: &Rc) { - if !self.compute_render_data_scheduled.replace(true) { - self.state.pending_container_render_data.push(self.clone()); + pub fn schedule_render_titles(self: &Rc) { + if !self.render_titles_scheduled.replace(true) { + self.state.pending_container_render_title.push(self.clone()); } } - fn compute_render_data(&self) { - self.compute_render_data_scheduled.set(false); + fn render_titles(&self) -> Rc { + 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) { + 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 rd = rd.deref_mut(); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let bw = theme.sizes.border_width.get(); - let font = theme.font.get(); let cwidth = self.width.get(); let cheight = self.height.get(); - let ctx = self.state.render_ctx.get(); for (_, v) in rd.titles.iter_mut() { v.clear(); } @@ -690,7 +789,6 @@ impl ContainerNode { let mono = self.mono_child.is_some(); let split = self.split.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_y = self.abs_y1.get(); for (i, child) in self.children.iter().enumerate() { @@ -708,64 +806,28 @@ impl ContainerNode { }; rd.border_rects.push(rect.unwrap()); } - let color = if child.active.get() { + if child.active.get() { rd.active_title_rects.push(rect); - theme.colors.focused_title_text.get() } else if child.attention_requested.get() { rd.attention_title_rects.push(rect); - theme.colors.unfocused_title_text.get() } else if !have_active && last_active == Some(child.node.node_id()) { rd.last_active_rect = Some(rect); - theme.colors.focused_inactive_title_text.get() } else { rd.title_rects.push(rect); - theme.colors.unfocused_title_text.get() - }; + } if !mono { let rect = Rect::new_sized(rect.x1(), rect.y2(), rect.width(), 1).unwrap(); rd.underline_rects.push(rect); } - let title = child.title.borrow_mut(); - for (scale, _) in scales.iter() { - let old_tex = child.title_tex.remove(scale); - let titles = rd.titles.get_or_default_mut(*scale); - 'render_title: { - 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); - } - 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)); - } - } - } + let tt = &*child.title_tex.borrow(); + for (scale, tex) in tt { + 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, + }) } } } @@ -1010,7 +1072,7 @@ impl ContainerNode { } self.update_title(); // log::info!("node_child_title_changed"); - self.schedule_compute_render_data(); + self.schedule_render_titles(); } fn update_child_active( @@ -1027,7 +1089,8 @@ impl ContainerNode { .set(Some(self.focus_history.add_last(node.clone()))); } // 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() { parent.node_child_active_changed(self.deref(), active, depth + 1); } @@ -1175,11 +1238,22 @@ pub async fn container_layout(state: Rc) { } } -pub async fn container_render_data(state: Rc) { +pub async fn container_render_positions(state: Rc) { + loop { + let container = state.pending_container_render_positions.pop().await; + if container.compute_render_positions_scheduled.get() { + container.compute_render_positions(); + } + } +} + +pub async fn container_render_titles(state: Rc) { loop { - let container = state.pending_container_render_data.pop().await; - if container.compute_render_data_scheduled.get() { - container.compute_render_data(); + 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; } self.mod_attention_requests(set); - self.schedule_compute_render_data(); + self.schedule_compute_render_positions(); } fn cnode_workspace(self: Rc) -> Rc { diff --git a/src/tree/float.rs b/src/tree/float.rs index 6dd6acf3..5e13d955 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -12,14 +12,15 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode, }, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState, - errorfmt::ErrorFmt, linkedlist::LinkedNode, + asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState, + errorfmt::ErrorFmt, linkedlist::LinkedNode, on_drop_event::OnDropEvent, + smallmap::SmallMapMut, }, }, ahash::AHashMap, @@ -47,7 +48,7 @@ pub struct FloatNode { pub layout_scheduled: Cell, pub render_titles_scheduled: Cell, pub title: RefCell, - pub title_textures: CopyHashMap, + pub title_textures: RefCell>, cursors: RefCell>, pub attention_requested: Cell, } @@ -96,7 +97,9 @@ pub async fn float_titles(state: Rc) { loop { let node = state.pending_float_titles.pop().await; 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) { - self.render_titles_scheduled.set(false); + fn render_title_phase1(&self) -> Rc { + let on_completed = Rc::new(OnDropEvent::default()); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let tc = match self.active.get() { @@ -194,17 +197,19 @@ impl FloatNode { let font = theme.font.get(); let title = self.title.borrow_mut(); let pos = self.position.get(); - if pos.width() <= 2 * bw || title.is_empty() { - return; + if pos.width() <= 2 * bw { + return on_completed.event(); } let ctx = match self.state.render_ctx.get() { Some(c) => c, - _ => return, + _ => return on_completed.event(), }; let scales = self.state.scales.lock(); 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() { - 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 scalef = None; let mut width = tr.width(); @@ -217,16 +222,39 @@ impl FloatNode { if th == 0 || width == 0 { continue; } - let texture = match text::render(&ctx, old_tex, width, th, &font, &title, tc, scalef) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render title {}: {}", title, ErrorFmt(e)); - return; - } - }; - self.title_textures.set(*scale, texture); + tex.schedule_render( + on_completed.clone(), + 1, + None, + width, + th, + 1, + &font, + &title, + tc, + true, + false, + scalef, + ); + } + 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)); + } } - if self.visible.get() { + 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); } } diff --git a/src/tree/output.rs b/src/tree/output.rs index b8ff7b6b..e68bfb19 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -30,15 +30,16 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, StackedNode, WorkspaceNode, }, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - event_listener::EventSource, hash_map_ext::HashMapExt, linkedlist::LinkedList, - scroller::Scroller, transform_ext::TransformExt, + asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, event_listener::EventSource, hash_map_ext::HashMapExt, + linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller, + transform_ext::TransformExt, }, wire::{JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id}, }, @@ -114,12 +115,14 @@ pub enum PointerType { pub async fn output_render_data(state: Rc) { loop { - let container = state.pending_output_render_data.pop().await; - if container.global.destroyed.get() { + let output = state.pending_output_render_data.pop().await; + if output.global.destroyed.get() { continue; } - if container.update_render_data_scheduled.get() { - container.update_render_data(); + if output.update_render_data_scheduled.get() { + output.update_render_data_scheduled.set(false); + output.update_render_data_phase1().triggered().await; + output.update_render_data_phase2(); } } } @@ -367,17 +370,69 @@ impl OutputNode { } } - fn update_render_data(&self) { - self.update_render_data_scheduled.set(false); + fn update_render_data_phase1(self: &Rc) -> Rc { + let on_completed = Rc::new(OnDropEvent::default()); + let Some(ctx) = self.state.render_ctx.get() else { + return on_completed.event(); + }; + let font = self.state.theme.font.get(); + 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 mut texture_height = th; + if let Some(scale) = scale { + texture_height = (th as f64 * scale).round() as _; + } + 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; - rd.status = None; let mut pos = 0; - let font = self.state.theme.font.get(); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let scale = self.global.persistent.scale.get(); @@ -386,45 +441,20 @@ impl OutputNode { } else { None }; - let mut texture_height = th; - if let Some(scale) = scale { - texture_height = (th as f64 * scale).round() as _; - } let active_id = self.workspace.get().map(|w| w.id); let non_exclusive_rect = self.non_exclusive_rect.get(); let output_width = non_exclusive_rect.width(); rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap(); for ws in self.workspaces.iter() { - let old_tex = ws.title_texture.take(); let mut title_width = th; - 'create_texture: { - if let Some(ctx) = self.state.render_ctx.get() { - if th == 0 || ws.name.is_empty() { - break 'create_texture; - } - let tc = match active_id == Some(ws.id) { - 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 title = &*ws.title_texture.borrow(); + if let Some(title) = title { + if let Err(e) = title.flip() { + log::error!("Could not render title: {}", ErrorFmt(e)); + } + if let Some(texture) = title.texture() { let mut x = pos + 1; - let (mut width, _) = title.texture.size(); + let (mut width, _) = texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } @@ -438,7 +468,7 @@ impl OutputNode { x2: pos + title_width, tex_x: x, tex_y: 0, - tex: title.texture, + tex: texture, ws: ws.deref().clone(), }); } @@ -461,43 +491,18 @@ impl OutputNode { } pos += title_width; } - 'set_status: { - let old_tex = rd.status.take().map(|s| s.tex); - let ctx = match self.state.render_ctx.get() { - Some(ctx) => ctx, - _ => break 'set_status, - }; - let status = self.status.get(); - if status.is_empty() { - break 'set_status; + if let Some(status) = &mut rd.status { + if let Err(e) = status.tex.flip() { + log::error!("Could not render status: {}", ErrorFmt(e)); } - let tc = self.state.theme.colors.bar_text.get(); - let title = match text::render_fitting( - &ctx, - old_tex, - 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; + if let Some(texture) = status.tex.texture() { + let (mut width, _) = texture.size(); + if let Some(scale) = scale { + width = (width as f64 / scale).round() as _; } - }; - let (mut width, _) = title.texture.size(); - if let Some(scale) = scale { - width = (width as f64 / scale).round() as _; + let pos = output_width - width - 1; + status.tex_x = pos; } - let pos = output_width - width - 1; - rd.status = Some(OutputStatus { - tex_x: pos, - tex_y: 0, - tex: title, - }); } if self.title_visible.get() { let title_rect = Rect::new_sized( @@ -945,7 +950,6 @@ pub struct OutputTitle { pub struct OutputStatus { pub tex_x: i32, - pub tex_y: i32, pub tex: TextTexture, } diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index 549a301d..b579e7bd 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -8,14 +8,22 @@ use { renderer::Renderer, scale::Scale, state::State, - text::{self, TextTexture}, + text::TextTexture, tree::{ Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, 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); @@ -24,7 +32,18 @@ pub struct PlaceholderNode { id: PlaceholderNodeId, toplevel: ToplevelData, destroyed: Cell, - pub textures: SmallMap, + update_textures_scheduled: Cell, + state: Rc, + pub textures: RefCell>, +} + +pub async fn placeholder_render_textures(state: Rc) { + 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 { @@ -37,6 +56,8 @@ impl PlaceholderNode { node.node_client(), ), destroyed: Default::default(), + update_textures_scheduled: Cell::new(false), + state: state.clone(), textures: Default::default(), } } @@ -45,39 +66,53 @@ impl PlaceholderNode { self.destroyed.get() } - pub fn update_texture(&self) { - if let Some(ctx) = self.toplevel.state.render_ctx.get() { - let scales = self.toplevel.state.scales.lock(); - let rect = self.toplevel.pos.get(); - for (scale, _) in scales.iter() { - let old_tex = self.textures.remove(scale); - let mut width = rect.width(); - let mut height = rect.height(); - if *scale != 1 { - let scale = scale.to_f64(); - width = (width as f64 * scale).round() as _; - height = (height as f64 * scale).round() as _; - } - if width != 0 && height != 0 { - let font = format!("monospace {}", width / 10); - match text::render_fitting( - &ctx, - old_tex, - Some(height), - &font, - "Fullscreen", - self.toplevel.state.theme.colors.unfocused_title_text.get(), - false, - None, - ) { - Ok(t) => { - self.textures.insert(*scale, t); - } - Err(e) => { - log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e)); - } - } - } + pub fn schedule_update_texture(self: &Rc) { + if !self.update_textures_scheduled.replace(true) { + self.state + .pending_placeholder_render_textures + .push(self.clone()); + } + } + + fn update_texture_phase1(&self) -> Rc { + let on_completed = Rc::new(OnDropEvent::default()); + let Some(ctx) = self.toplevel.state.render_ctx.get() else { + return on_completed.event(); + }; + let scales = self.toplevel.state.scales.lock(); + let rect = self.toplevel.pos.get(); + let mut textures = self.textures.borrow_mut(); + for (scale, _) in scales.iter() { + let tex = textures + .get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); + let mut width = rect.width(); + let mut height = rect.height(); + if *scale != 1 { + let scale = scale.to_f64(); + width = (width as f64 * scale).round() as _; + height = (height as f64 * scale).round() as _; + } + if width != 0 && height != 0 { + let font = Arc::new(format!("monospace {}", width / 10)); + tex.schedule_render_fitting( + 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() { p.node_child_size_changed(self.deref(), rect.width(), rect.height()); } - self.update_texture(); + self.schedule_update_texture(); } fn tl_close(self: Rc) { diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 5f77afcb..6e96682e 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -57,7 +57,7 @@ pub struct WorkspaceNode { pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc>, pub may_capture: Cell, pub has_capture: Cell, - pub title_texture: Cell>, + pub title_texture: RefCell>, pub attention_requests: ThresholdCounter, pub render_highlight: NumCell, } diff --git a/src/utils.rs b/src/utils.rs index fa1fd936..d60be29e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -30,6 +30,7 @@ pub mod num_cpus; pub mod numcell; pub mod on_change; pub mod on_drop; +pub mod on_drop_event; pub mod once; pub mod opaque; pub mod opaque_cell; diff --git a/src/utils/on_drop_event.rs b/src/utils/on_drop_event.rs new file mode 100644 index 00000000..e3ceea04 --- /dev/null +++ b/src/utils/on_drop_event.rs @@ -0,0 +1,18 @@ +use {crate::utils::asyncevent::AsyncEvent, std::rc::Rc}; + +#[derive(Default)] +pub struct OnDropEvent { + ae: Rc, +} + +impl OnDropEvent { + pub fn event(&self) -> Rc { + self.ae.clone() + } +} + +impl Drop for OnDropEvent { + fn drop(&mut self) { + self.ae.trigger() + } +} diff --git a/src/utils/smallmap.rs b/src/utils/smallmap.rs index 624c653a..c4ea872e 100644 --- a/src/utils/smallmap.rs +++ b/src/utils/smallmap.rs @@ -201,13 +201,20 @@ impl SmallMapMut { pub fn get_or_default_mut(&mut self, k: K) -> &mut V where V: Default, + { + self.get_or_insert_with(k, || V::default()) + } + + pub fn get_or_insert_with(&mut self, k: K, f: F) -> &mut V + where + F: FnOnce() -> V, { for (ek, ev) in &mut self.m { if ek == &k { 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 }