use { crate::{ allocator::{BufferObject, BufferUsage, BO_USE_RENDERING}, async_engine::{Phase, SpawnedFuture}, cursor::KnownCursor, fixed::Fixed, format::ARGB8888, gfx_api::{ needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, }, ifs::zwlr_layer_shell_v1::OVERLAY, portal::{ ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, ptl_text::{self, TextMeasurement}, }, renderer::renderer_base::RendererBase, scale::Scale, theme::Color, utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, rc_eq::rc_eq, }, wire::{ wp_fractional_scale_v1::PreferredScale, zwlr_layer_surface_v1::Configure, ZwpLinuxBufferParamsV1Id, }, wl_usr::usr_ifs::{ usr_linux_buffer_params::{UsrLinuxBufferParams, UsrLinuxBufferParamsOwner}, usr_wl_buffer::{UsrWlBuffer, UsrWlBufferOwner}, usr_wl_surface::UsrWlSurface, usr_wlr_layer_surface::{UsrWlrLayerSurface, UsrWlrLayerSurfaceOwner}, usr_wp_fractional_scale::{UsrWpFractionalScale, UsrWpFractionalScaleOwner}, usr_wp_viewport::UsrWpViewport, }, }, ahash::AHashSet, std::{ cell::{Cell, RefCell}, ops::Deref, rc::Rc, sync::Arc, }, }; #[derive(Default)] pub struct GuiElementData { pub x: Cell, pub y: Cell, pub width: Cell, pub height: Cell, } pub trait GuiElement { fn data(&self) -> &GuiElementData; fn layout( &self, ctx: &Rc, scale: f32, max_width: f32, max_height: f32, ) -> (f32, f32); fn render_at(&self, r: &mut RendererBase, x: f32, y: f32); fn child_at(&self, x: f32, y: f32) -> Option>; fn hover_cursor(&self) -> KnownCursor { KnownCursor::Default } fn button(&self, seat: &PortalSeat, button: u32, state: u32) { let _ = seat; let _ = button; let _ = state; } fn hover(&self, seat: &PortalSeat, hover: bool) -> bool { let _ = seat; let _ = hover; false } fn destroy(&self) {} } #[derive(Copy, Clone, Debug, Default)] pub struct ButtonExtents { pub width: f32, pub height: f32, pub tex_off_x: f32, pub tex_off_y: f32, } pub fn button_extents( msmt: &TextMeasurement, scale: f32, padding: f32, border: f32, ) -> ButtonExtents { let above_baseline_height = (msmt.baseline - msmt.ink_rect.y1().max(msmt.logical_rect.y1())) as f32 / scale; let height = above_baseline_height + 2.0 * (padding + border); let width = msmt.ink_rect.width() as f32 / scale + 2.0 * (padding + border); let tex_off_x = padding + border; // let tex_off_y = ((msmt.ink_rect.y1() - msmt.logical_rect.y1()) as f64 / scale).round() as i32 + padding + border; let tex_off_y = padding + border; ButtonExtents { width, height, tex_off_x, tex_off_y, } } pub struct Button { pub data: GuiElementData, pub tex_off_x: Cell, pub tex_off_y: Cell, pub hover: RefCell>, pub padding: Cell, pub border: Cell, pub border_color: Cell, pub bg_color: Cell, pub bg_hover_color: Cell, pub text: RefCell, pub font: Arc, pub tex: CloneCell>>, pub owner: CloneCell>>, } pub trait ButtonOwner { fn button(&self, seat: &PortalSeat, button: u32, state: u32); } impl Default for Button { fn default() -> Self { Self { data: Default::default(), tex_off_x: Cell::new(0.0), tex_off_y: Cell::new(0.0), hover: Default::default(), padding: Default::default(), border: Default::default(), border_color: Cell::new(Color::from_gray(0)), bg_color: Cell::new(Color::from_gray(255)), bg_hover_color: Cell::new(Color::from_gray(255)), text: Default::default(), font: Arc::new(DEFAULT_FONT.to_string()), tex: Default::default(), owner: Default::default(), } } } impl GuiElement for Button { fn hover_cursor(&self) -> KnownCursor { KnownCursor::Pointer } fn data(&self) -> &GuiElementData { &self.data } fn layout( &self, ctx: &Rc, scale: f32, _max_width: f32, _max_height: f32, ) -> (f32, f32) { let text = self.text.borrow_mut(); let tex = ptl_text::render( ctx, None, &self.font, &text, Color::from_gray(0), Some(scale as _), true, ); let (tex, msmt) = match tex { Some((a, b)) => (Some(a), Some(b)), _ => (None, None), }; let extents = match msmt { Some(m) => button_extents(&m, scale, self.padding.get(), self.border.get()), _ => Default::default(), }; self.tex.set(tex); self.tex_off_x.set(extents.tex_off_x); self.tex_off_y.set(extents.tex_off_y); (extents.width, extents.height) } fn render_at(&self, r: &mut RendererBase, x1: f32, y1: f32) { let x2 = x1 + self.data.width.get(); let y2 = y1 + self.data.height.get(); let border = self.border.get(); { let rects = [ (x1, y1, x2, y1 + border), (x1, y2 - border, x2, y2), (x1, y1 + border, x1 + border, y2 - border), (x2 - border, y1 + border, x2, y2 - border), ]; r.fill_boxes_f(&rects, &self.border_color.get()); } { let rects = [(x1 + border, y1 + border, x2 - border, y2 - border)]; let color = match self.hover.borrow_mut().is_empty() { true => self.bg_color.get(), false => self.bg_hover_color.get(), }; r.fill_boxes_f(&rects, &color); } if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get()); r.render_texture( &tex, None, tx.round() as _, ty.round() as _, None, None, r.scale(), None, None, AcquireSync::None, ReleaseSync::None, ); } } fn child_at(&self, _x: f32, _y: f32) -> Option> { None } fn hover(&self, seat: &PortalSeat, hover: bool) -> bool { let ret; let mut set = self.hover.borrow_mut(); if hover { ret = set.is_empty(); set.insert(seat.global_id); } else { set.remove(&seat.global_id); ret = set.is_empty(); } ret } fn destroy(&self) { self.owner.take(); } fn button(&self, seat: &PortalSeat, button: u32, state: u32) { if let Some(owner) = self.owner.get() { owner.button(seat, button, state); } } } const DEFAULT_FONT: &str = "sans-serif 16"; pub struct Label { pub data: GuiElementData, pub font: Arc, pub text: RefCell, pub tex: CloneCell>>, } impl Default for Label { fn default() -> Self { Self { data: Default::default(), font: Arc::new(DEFAULT_FONT.into()), text: RefCell::new("".to_string()), tex: Default::default(), } } } impl GuiElement for Label { fn data(&self) -> &GuiElementData { &self.data } fn layout( &self, ctx: &Rc, scale: f32, _max_width: f32, _max_height: f32, ) -> (f32, f32) { let text = self.text.borrow_mut(); let tex = ptl_text::render( ctx, None, &self.font, &text, Color::from_gray(255), Some(scale as _), false, ); let (tex, width, height) = match tex { Some((t, _)) => { let (width, height) = t.size(); (Some(t.clone()), width, height) } _ => (None, 0, 0), }; self.tex.set(tex); (width as f32 / scale, height as f32 / scale) } fn render_at(&self, r: &mut RendererBase, x: f32, y: f32) { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x, y); r.render_texture( &tex, None, tx.round() as _, ty.round() as _, None, None, r.scale(), None, None, AcquireSync::None, ReleaseSync::None, ); } } fn child_at(&self, _x: f32, _y: f32) -> Option> { None } } #[derive(Copy, Clone, Eq, PartialEq, Default)] pub enum Align { #[expect(dead_code)] Left, #[default] Center, #[expect(dead_code)] Right, } #[derive(Copy, Clone, Eq, PartialEq, Default)] pub enum Orientation { #[default] Horizontal, Vertical, } #[derive(Default)] pub struct Flow { pub data: GuiElementData, pub in_margin: Cell, pub cross_margin: Cell, pub orientation: Cell, pub cross_align: Cell, pub elements: RefCell>>, } impl GuiElement for Flow { fn data(&self) -> &GuiElementData { &self.data } fn layout( &self, ctx: &Rc, scale: f32, max_width: f32, max_height: f32, ) -> (f32, f32) { let elements = self.elements.borrow_mut(); let orientation = self.orientation.get(); let (max_in_size, _max_cross_size) = match orientation { Orientation::Horizontal => (max_width, max_height), Orientation::Vertical => (max_height, max_width), }; let mut runs = vec![]; let mut run = vec![]; let cross_margin = self.cross_margin.get(); let in_margin = self.in_margin.get(); { let mut run_cross_size: f32 = 0.0; let mut in_pos = in_margin; for element in elements.deref() { let (w, h) = element.layout(ctx, scale, max_width, max_height); let (cur_in_size, cur_cross_size) = match orientation { Orientation::Horizontal => (w, h), Orientation::Vertical => (h, w), }; if in_pos + cur_in_size > max_in_size && run.len() > 0 { runs.push((run, run_cross_size)); run = vec![]; in_pos = in_margin; run_cross_size = 0.0; } run_cross_size = run_cross_size.max(cur_cross_size); run.push((element, cur_in_size, cur_cross_size)); in_pos += cur_in_size + in_margin; } if run.len() > 0 { runs.push((run, run_cross_size)); } } let mut max_in_pos: f32 = 0.0; let mut cross_pos = cross_margin; for (run, run_cross_size) in runs { let mut in_pos = in_margin; for (element, cur_in_size, cur_cross_size) in run { let cur_cross_pos = cross_pos + match self.cross_align.get() { Align::Left => 0.0, Align::Center => (run_cross_size - cur_cross_size) / 2.0, Align::Right => run_cross_size - cur_cross_size, }; let (x, y, w, h) = match orientation { Orientation::Horizontal => (in_pos, cur_cross_pos, cur_in_size, cur_cross_size), Orientation::Vertical => (cur_cross_pos, in_pos, cur_cross_size, cur_in_size), }; element.data().x.set(x); element.data().y.set(y); element.data().width.set(w); element.data().height.set(h); in_pos += in_margin + cur_in_size; } max_in_pos = max_in_pos.max(in_pos); cross_pos += cross_margin + run_cross_size; } let (w, h) = match orientation { Orientation::Horizontal => (max_in_pos, cross_pos), Orientation::Vertical => (cross_pos, max_in_pos), }; (w.min(max_width), h.min(max_height)) } fn render_at(&self, r: &mut RendererBase, x: f32, y: f32) { for element in self.elements.borrow_mut().deref() { element.render_at(r, x + element.data().x.get(), y + element.data().y.get()); } } fn child_at(&self, x: f32, y: f32) -> Option> { for child in self.elements.borrow_mut().deref() { let data = child.data(); let x1 = data.x.get(); let y1 = data.y.get(); if x >= x1 && x - x1 < data.width.get() && y >= y1 && y - y1 < data.height.get() { return Some(child.clone()); } } None } fn destroy(&self) { for element in self.elements.borrow_mut().drain(..) { element.destroy(); } } } pub struct OverlayWindow { pub layer_surface: Rc, pub data: Rc, pub owner: CloneCell>>, } pub trait OverlayWindowOwner { fn kill(&self, upwards: bool); } pub struct WindowData { pub frame_missed: Cell, pub first_scale: Cell, pub have_frame: Cell, pub scale: Cell, pub render_trigger: AsyncEvent, pub render_task: Cell>>, pub dpy: Rc, pub content: CloneCell>>, pub surface: Rc, pub viewport: Rc, pub fractional_scale: Rc, pub bufs: RefCell>>, pending_bufs: CopyHashMap>, pub width: Cell, pub height: Cell, pub owner: CloneCell>>, pub seats: CopyHashMap>, } #[derive(Default)] pub struct GuiWindowSeatState { pub x: Cell, pub y: Cell, pub tree: RefCell>>, pub cursor: Cell>, } pub trait WindowDataOwner { fn post_layout(&self); fn kill(&self, upwards: bool); } impl WindowDataOwner for OverlayWindow { fn post_layout(&self) { self.layer_surface .set_size(self.data.width.get(), self.data.height.get()); self.data.surface.commit(); } fn kill(&self, upwards: bool) { if let Some(owner) = self.owner.take() { owner.kill(upwards); } self.layer_surface .con .remove_obj(self.layer_surface.deref()); } } const NUM_BUFFERS: usize = 2; impl OverlayWindow { pub fn new(output: &Rc) -> Rc { let data = WindowData::new(&output.dpy); let layer_surface = output .dpy .ls .get_layer_surface(&data.surface, &output.wl, OVERLAY); layer_surface.set_size(1, 1); let slf = Rc::new(Self { layer_surface, data, owner: Default::default(), }); slf.data.owner.set(Some(slf.clone())); slf.layer_surface.owner.set(Some(slf.clone())); slf.data.surface.commit(); slf } } impl WindowData { pub fn schedule_render(&self) { self.render_trigger.trigger(); } pub fn new(dpy: &Rc) -> Rc { let surface = dpy.comp.create_surface(); let viewport = dpy.vp.get_viewport(&surface); let fractional_scale = dpy.fsm.get_fractional_scale(&surface); viewport.set_destination(1, 1); let data = Rc::new(WindowData { frame_missed: Cell::new(false), first_scale: Cell::new(true), have_frame: Cell::new(true), bufs: Default::default(), pending_bufs: Default::default(), width: Cell::new(0), height: Cell::new(0), owner: Default::default(), render_trigger: Default::default(), render_task: Cell::new(None), dpy: dpy.clone(), content: Default::default(), surface, viewport, scale: Cell::new(Scale::from_int(1)), fractional_scale, seats: Default::default(), }); data.render_task.set(Some(dpy.state.eng.spawn2( "render", Phase::Present, data.clone().render_task(), ))); data.fractional_scale.owner.set(Some(data.clone())); data } pub fn layout(&self) { let ctx = match self.dpy.render_ctx.get() { Some(ctx) => ctx, _ => return, }; let scale = self.scale.get().to_f64() as f32; let content = match self.content.get() { Some(c) => c, _ => return, }; let (mut width, mut height) = content.layout(&ctx.ctx.ctx, scale, f32::INFINITY, f32::INFINITY); content.data().width.set(width); content.data().height.set(height); width = width.max(1.0); height = height.max(1.0); self.width.set(width.round() as _); self.height.set(height.round() as _); self.viewport .set_destination(width.round() as _, height.round() as _); if let Some(owner) = self.owner.get() { owner.post_layout(); } } async fn render_task(self: Rc) { loop { self.render_trigger.triggered().await; self.render(); } } fn render(self: &Rc) { self.frame_missed.set(true); if !self.have_frame.get() { return; } let bufs = self.bufs.borrow_mut(); let buf = 'get_buf: { for buf in bufs.deref() { if buf.free.get() { break 'get_buf buf; } } return; }; let res = buf.fb.render_custom( AcquireSync::Implicit, ReleaseSync::Implicit, self.scale.get(), Some(&Color::from_gray(0)), &mut |r| { if let Some(content) = self.content.get() { content.render_at(r, 0.0, 0.0) } }, ); if let Err(e) = res { log::error!("Could not render frame: {}", ErrorFmt(e)); return; } self.frame_missed.set(false); self.surface.frame({ let slf = self.clone(); move || { slf.have_frame.set(true); if slf.frame_missed.get() { slf.schedule_render(); } } }); self.have_frame.set(false); buf.free.set(false); self.surface.attach(&buf.wl); self.surface.damage(); self.surface.commit(); } pub fn kill(&self, upwards: bool) { if let Some(owner) = self.owner.take() { owner.kill(upwards); } self.render_task.take(); for pb in self.pending_bufs.lock().drain_values() { pb.params.con.remove_obj(pb.params.deref()); } for buf in self.bufs.borrow_mut().drain(..) { buf.wl.con.remove_obj(buf.wl.deref()); } self.fractional_scale .con .remove_obj(self.fractional_scale.deref()); self.viewport.con.remove_obj(self.viewport.deref()); self.surface.con.remove_obj(self.surface.deref()); if let Some(content) = self.content.take() { content.destroy(); } } pub fn allocate_buffers(self: &Rc) { { for buf in self.pending_bufs.lock().drain_values() { buf.params.con.remove_obj(buf.params.deref()); } } { let mut bufs = self.bufs.borrow_mut(); for buf in bufs.drain(..) { buf.wl.con.remove_obj(buf.wl.deref()); } } let ctx = match self.dpy.render_ctx.get() { Some(ctx) => ctx, _ => return, }; let dmabuf = match self.dpy.dmabuf.get() { Some(dmabuf) => dmabuf, _ => return, }; self.frame_missed.set(true); let width = (self.width.get() as f64 * self.scale.get().to_f64()).round() as i32; let height = (self.height.get() as f64 * self.scale.get().to_f64()).round() as i32; let formats = &ctx.usable_formats; let format = match formats.get(&ARGB8888.drm) { None => { log::error!("Render context does not support ARGB8888 format"); return; } Some(f) => f, }; if format.write_modifiers.is_empty() { log::error!("Render context cannot render to ARGB8888 format"); return; } let modifiers: Vec<_> = format.write_modifiers.keys().copied().collect(); let mut usage = BO_USE_RENDERING; if !needs_render_usage(format.write_modifiers.values()) { usage = BufferUsage::none(); } for _ in 0..NUM_BUFFERS { let bo = match ctx.ctx.ctx.allocator().create_bo( &self.dpy.state.dma_buf_ids, width, height, ARGB8888, &modifiers, usage, ) { Ok(b) => b, Err(e) => { log::error!("Could not allocate dmabuf: {}", ErrorFmt(e)); return; } }; let img = match ctx.ctx.ctx.clone().dmabuf_img(bo.dmabuf()) { Ok(b) => b, Err(e) => { log::error!("Could not import dmabuf into EGL: {}", ErrorFmt(e)); return; } }; let fb = match img.to_framebuffer() { Ok(b) => b, Err(e) => { log::error!( "Could not turns EGL image into framebuffer: {}", ErrorFmt(e) ); return; } }; let params = dmabuf.create_params(); params.create(bo.dmabuf()); let pending = Rc::new(GuiBufferPending { bo: Cell::new(Some(bo)), window: self.clone(), fb, params, size: (width, height), }); pending.params.owner.set(Some(pending.clone())); self.pending_bufs.set(pending.params.id, pending.clone()); } } fn tree_at(&self, tree: &mut Vec>, mut x: f32, mut y: f32) { let mut element = match self.content.get() { Some(e) => e, _ => return, }; tree.push(element.clone()); while let Some(c) = element.child_at(x, y) { tree.push(c.clone()); x -= c.data().x.get(); y -= c.data().y.get(); element = c; } } pub fn motion(&self, pseat: &PortalSeat, x: Fixed, y: Fixed, _enter: bool) { let x = x.to_f64() as f32; let y = y.to_f64() as f32; let seat = self .seats .lock() .entry(pseat.global_id) .or_default() .clone(); seat.x.set(x); seat.y.set(y); let mut tree = seat.tree.borrow_mut(); let old_element = tree.last().cloned(); self.tree_at(&mut tree, x, y); let new_element = tree.last().cloned(); let element_changed = match (&old_element, &new_element) { (Some(old), Some(new)) => !rc_eq(old, new), (None, None) => false, _ => true, }; if element_changed { if let Some(o) = &old_element { o.hover(pseat, false); } if let Some(o) = &new_element { o.hover(pseat, true); } } if element_changed { self.schedule_render(); } let cursor = match &new_element { Some(e) => e.hover_cursor(), _ => KnownCursor::Default, }; if seat.cursor.replace(Some(cursor)) != Some(cursor) { pseat.jay_pointer.set_known_cursor(cursor); } } pub fn button(&self, pseat: &PortalSeat, button: u32, state: u32) { let seat = match self.seats.get(&pseat.global_id) { Some(s) => s, _ => return, }; let element = seat.tree.borrow_mut().last().cloned(); if let Some(e) = element { e.button(pseat, button, state); } } } pub struct GuiBuffer { pub wl: Rc, pub window: Rc, pub fb: Rc, pub _bo: Option>, pub free: Cell, pub _size: (i32, i32), } struct GuiBufferPending { pub bo: Cell>>, pub window: Rc, pub fb: Rc, pub params: Rc, pub size: (i32, i32), } impl UsrWlBufferOwner for GuiBuffer { fn release(&self) { self.free.set(true); if self.window.frame_missed.get() { self.window.schedule_render(); } } } impl UsrWpFractionalScaleOwner for WindowData { fn preferred_scale(self: Rc, ev: &PreferredScale) { let mut layout = self.first_scale.replace(false); let scale = Scale::from_wl(ev.scale); layout |= self.scale.replace(scale) != scale; if layout { self.layout(); self.allocate_buffers(); } } } impl UsrWlrLayerSurfaceOwner for OverlayWindow { fn configure(&self, _ev: &Configure) { self.data.schedule_render(); } fn closed(&self) { self.data.kill(true); } } impl UsrLinuxBufferParamsOwner for GuiBufferPending { fn created(&self, buffer: Rc) { buffer.con.add_object(buffer.clone()); let buf = Rc::new(GuiBuffer { wl: buffer, window: self.window.clone(), fb: self.fb.clone(), _bo: self.bo.take(), free: Cell::new(true), _size: self.size, }); buf.wl.owner.set(Some(buf.clone())); self.window.bufs.borrow_mut().push(buf); self.params.con.remove_obj(self.params.deref()); self.window.pending_bufs.remove(&self.params.id); if self.window.frame_missed.get() { self.window.schedule_render(); } } fn failed(&self) { self.window.kill(true); } }