From a41dbae8996aa543dd1c4be9077e265d56f2da17 Mon Sep 17 00:00:00 2001 From: kossLAN Date: Thu, 9 Apr 2026 23:04:33 -0400 Subject: [PATCH] all: remove traditional i3 titlebars, add corner rounding --- build/vulkan/hash.rs | 8 + jay-config/src/_private/client.rs | 10 + jay-config/src/_private/ipc.rs | 7 + jay-config/src/lib.rs | 12 + src/compositor.rs | 14 +- src/config/handler.rs | 44 +- src/gfx_api.rs | 57 +- src/gfx_apis/gl.rs | 124 +++- src/gfx_apis/gl/gl/sys.rs | 1 + src/gfx_apis/gl/renderer/context.rs | 101 +++ .../gl/shaders/rounded_fill.frag.glsl | 56 ++ .../gl/shaders/rounded_fill.vert.glsl | 8 + src/gfx_apis/gl/shaders/rounded_tex.frag.glsl | 71 +++ src/gfx_apis/gl/shaders/rounded_tex.vert.glsl | 11 + src/gfx_apis/vulkan/renderer.rs | 493 ++++++++++++++- src/gfx_apis/vulkan/shaders.rs | 75 +++ .../shaders/legacy/rounded_fill.common.glsl | 12 + .../vulkan/shaders/legacy/rounded_fill.frag | 48 ++ .../vulkan/shaders/legacy/rounded_fill.vert | 17 + .../shaders/legacy/rounded_tex.common.glsl | 12 + .../vulkan/shaders/legacy/rounded_tex.frag | 51 ++ .../vulkan/shaders/legacy/rounded_tex.vert | 18 + .../vulkan/shaders/rounded_fill.common.glsl | 14 + src/gfx_apis/vulkan/shaders/rounded_fill.frag | 45 ++ src/gfx_apis/vulkan/shaders/rounded_fill.vert | 16 + .../vulkan/shaders/rounded_tex.common.glsl | 22 + src/gfx_apis/vulkan/shaders/rounded_tex.frag | 76 +++ src/gfx_apis/vulkan/shaders/rounded_tex.vert | 19 + .../shaders_bin/legacy_rounded_fill.frag.spv | Bin 0 -> 6960 bytes .../shaders_bin/legacy_rounded_fill.vert.spv | Bin 0 -> 2508 bytes .../shaders_bin/legacy_rounded_tex.frag.spv | Bin 0 -> 6648 bytes .../shaders_bin/legacy_rounded_tex.vert.spv | Bin 0 -> 2804 bytes .../vulkan/shaders_bin/rounded_fill.frag.spv | Bin 0 -> 6336 bytes .../vulkan/shaders_bin/rounded_fill.vert.spv | Bin 0 -> 2376 bytes .../vulkan/shaders_bin/rounded_tex.frag.spv | Bin 0 -> 19700 bytes .../vulkan/shaders_bin/rounded_tex.vert.spv | Bin 0 -> 3580 bytes src/gfx_apis/vulkan/shaders_hash.txt | 12 + src/icons.rs | 3 + src/ifs/wl_seat.rs | 1 + src/ifs/wl_seat/pointer_owner.rs | 3 + src/it/test_gfx_api.rs | 2 + src/renderer.rs | 390 +++++------- src/renderer/renderer_base.rs | 121 +++- src/state.rs | 32 +- src/text.rs | 2 + src/theme.rs | 126 +++- src/tree/container.rs | 575 ++---------------- src/tree/float.rs | 185 +----- toml-config/src/config.rs | 1 + toml-config/src/config/parsers/theme.rs | 8 +- toml-config/src/lib.rs | 5 +- toml-spec/spec/spec.yaml | 5 + 52 files changed, 1866 insertions(+), 1047 deletions(-) create mode 100644 src/gfx_apis/gl/shaders/rounded_fill.frag.glsl create mode 100644 src/gfx_apis/gl/shaders/rounded_fill.vert.glsl create mode 100644 src/gfx_apis/gl/shaders/rounded_tex.frag.glsl create mode 100644 src/gfx_apis/gl/shaders/rounded_tex.vert.glsl create mode 100644 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl create mode 100644 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag create mode 100644 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert create mode 100644 src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl create mode 100644 src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag create mode 100644 src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert create mode 100644 src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl create mode 100644 src/gfx_apis/vulkan/shaders/rounded_fill.frag create mode 100644 src/gfx_apis/vulkan/shaders/rounded_fill.vert create mode 100644 src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl create mode 100644 src/gfx_apis/vulkan/shaders/rounded_tex.frag create mode 100644 src/gfx_apis/vulkan/shaders/rounded_tex.vert create mode 100644 src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.frag.spv create mode 100644 src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.vert.spv create mode 100644 src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.frag.spv create mode 100644 src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.vert.spv create mode 100644 src/gfx_apis/vulkan/shaders_bin/rounded_fill.frag.spv create mode 100644 src/gfx_apis/vulkan/shaders_bin/rounded_fill.vert.spv create mode 100644 src/gfx_apis/vulkan/shaders_bin/rounded_tex.frag.spv create mode 100644 src/gfx_apis/vulkan/shaders_bin/rounded_tex.vert.spv diff --git a/build/vulkan/hash.rs b/build/vulkan/hash.rs index 452d6646..d67b87aa 100644 --- a/build/vulkan/hash.rs +++ b/build/vulkan/hash.rs @@ -20,10 +20,18 @@ pub const TREES: &[Tree] = &[ "tex.frag", "out.vert", "out.frag", + "rounded_fill.frag", + "rounded_fill.vert", + "rounded_tex.frag", + "rounded_tex.vert", "legacy/fill.frag", "legacy/fill.vert", "legacy/tex.vert", "legacy/tex.frag", + "legacy/rounded_fill.frag", + "legacy/rounded_fill.vert", + "legacy/rounded_tex.frag", + "legacy/rounded_tex.vert", ], }, ]; diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index b2291c5e..39e6e54b 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -2025,6 +2025,16 @@ impl ConfigClient { self.send(&ClientMessage::SetPointerRevertKey { seat, key }); } + pub fn set_corner_radius(&self, radius: f32) { + self.send(&ClientMessage::SetCornerRadius { radius }); + } + + pub fn get_corner_radius(&self) -> f32 { + let res = self.send_with_response(&ClientMessage::GetCornerRadius); + get_response!(res, 0.0, GetCornerRadius { radius }); + radius + } + fn handle_msg(&self, msg: &[u8]) { self.handle_msg2(msg); self.dispatch_futures(); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index be204284..05415f6b 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -880,6 +880,10 @@ pub enum ClientMessage<'a> { seat: Seat, enabled: bool, }, + SetCornerRadius { + radius: f32, + }, + GetCornerRadius, } #[derive(Serialize, Deserialize, Debug)] @@ -1136,6 +1140,9 @@ pub enum Response { ConnectorSupportsArbitraryModes { supports_arbitrary_modes: bool, }, + GetCornerRadius { + radius: f32, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index ce78f0fc..0d674199 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -382,6 +382,18 @@ pub fn toggle_floating_titles() { get.set_floating_titles(!get.get_floating_titles()); } +/// Sets the corner radius for window borders. +/// +/// A radius of 0 means square corners. The radius is in logical pixels. +pub fn set_corner_radius(radius: f32) { + get!().set_corner_radius(radius) +} + +/// Returns the current corner radius for window borders. +pub fn get_corner_radius() -> f32 { + get!(0.0).get_corner_radius() +} + /// Sets a callback to run when this config is unloaded. /// /// Only one callback can be set at a time. If another callback is already set, it will be diff --git a/src/compositor.rs b/src/compositor.rs index 5f6f3cbd..09ca0c0d 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -60,7 +60,7 @@ use { tree::{ DisplayNode, NodeIds, OutputNode, TearingMode, Transform, VrrMode, WorkspaceDisplayOrder, WorkspaceNode, container_layout, container_render_positions, - container_render_titles, float_layout, float_titles, output_render_data, + float_layout, output_render_data, placeholder_render_textures, }, user_session::import_environment, @@ -259,10 +259,8 @@ fn start_compositor2( theme: Default::default(), pending_container_layout: 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(), @@ -511,11 +509,6 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { Phase::PostLayout, container_render_positions(state.clone()), ), - eng.spawn2( - "container titles", - Phase::PostLayout, - container_render_titles(state.clone()), - ), eng.spawn2( "placeholder textures", Phase::PostLayout, @@ -531,11 +524,6 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { wlr_output_manager_done(state.clone()), ), eng.spawn2("float layout", Phase::Layout, float_layout(state.clone())), - eng.spawn2( - "float titles", - Phase::PostLayout, - float_titles(state.clone()), - ), eng.spawn2("idle", Phase::PostLayout, idle(state.clone())), eng.spawn2( "input, popup positioning", diff --git a/src/config/handler.rs b/src/config/handler.rs index f7b8ef7f..25694ac2 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1411,24 +1411,20 @@ impl ConfigProxyHandler { }); } - fn handle_set_show_titles(&self, show: bool) { - self.state.set_show_titles(show); + fn handle_set_show_titles(&self, _show: bool) { + // no-op: titles have been removed } fn handle_get_show_titles(&self) { - self.respond(Response::GetShowTitles { - show: self.state.theme.show_titles.get(), - }); + self.respond(Response::GetShowTitles { show: false }); } - fn handle_set_floating_titles(&self, floating: bool) { - self.state.set_floating_titles(floating); + fn handle_set_floating_titles(&self, _floating: bool) { + // no-op: titles have been removed } fn handle_get_floating_titles(&self) { - self.respond(Response::GetFloatingTitles { - floating: self.state.theme.floating_titles.get(), - }); + self.respond(Response::GetFloatingTitles { floating: false }); } fn handle_set_bar_position(&self, position: BarPosition) -> Result<(), CphError> { @@ -1445,8 +1441,24 @@ impl ConfigProxyHandler { }); } - fn handle_set_show_float_pin_icon(&self, show: bool) { - self.state.set_show_pin_icon(show); + fn handle_set_corner_radius(&self, radius: f32) { + use crate::theme::CornerRadius; + let radius = radius.max(0.0).min(1000.0); + self.state + .theme + .corner_radius + .set(CornerRadius::from(radius)); + self.state.damage(self.state.root.extents.get()); + } + + fn handle_get_corner_radius(&self) { + self.respond(Response::GetCornerRadius { + radius: self.state.theme.corner_radius.get().top_left, + }); + } + + fn handle_set_show_float_pin_icon(&self, _show: bool) { + // no-op: titles have been removed, pin icon was in title bar } fn handle_set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { @@ -2496,8 +2508,8 @@ impl ConfigProxyHandler { self.state.set_bar_font(Some(font)); } - fn handle_set_title_font(&self, font: &str) { - self.state.set_title_font(Some(font)); + fn handle_set_title_font(&self, _font: &str) { + // no-op: titles have been removed } fn handle_get_font(&self) { @@ -3310,6 +3322,10 @@ impl ConfigProxyHandler { .handle_set_bar_position(position) .wrn("set_bar_position")?, ClientMessage::GetBarPosition => self.handle_get_bar_position(), + ClientMessage::SetCornerRadius { radius } => { + self.handle_set_corner_radius(radius) + } + ClientMessage::GetCornerRadius => self.handle_get_corner_radius(), ClientMessage::SeatFocusHistory { seat, timeline } => self .handle_seat_focus_history(seat, timeline) .wrn("seat_focus_history")?, diff --git a/src/gfx_api.rs b/src/gfx_api.rs index afad137c..3e89c676 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -102,6 +102,8 @@ pub enum GfxApiOpt { Sync, FillRect(FillRect), CopyTexture(CopyTexture), + RoundedFillRect(RoundedFillRect), + RoundedCopyTexture(RoundedCopyTexture), } pub struct GfxRenderPass { @@ -289,6 +291,53 @@ pub struct CopyTexture { pub alpha_mode: AlphaMode, } +#[derive(Debug)] +pub struct RoundedFillRect { + pub rect: FramebufferRect, + pub color: Color, + pub alpha: Option, + pub render_intent: RenderIntent, + pub cd: Rc, + /// Size of the rectangle in physical pixels. + pub size: [f32; 2], + /// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left]. + pub corner_radius: [f32; 4], + /// Border width in physical pixels. 0 means a filled rounded rect (no cutout). + pub border_width: f32, + /// Output scale for antialiasing. + pub scale: f32, +} + +impl RoundedFillRect { + pub fn effective_color(&self) -> Color { + let mut color = self.color; + if let Some(alpha) = self.alpha { + color = color * alpha; + } + color + } +} + +pub struct RoundedCopyTexture { + pub tex: Rc, + pub source: SampleRect, + pub target: FramebufferRect, + pub buffer_resv: Option>, + pub acquire_sync: AcquireSync, + pub release_sync: ReleaseSync, + pub alpha: Option, + pub opaque: bool, + pub render_intent: RenderIntent, + pub cd: Rc, + pub alpha_mode: AlphaMode, + /// Size of the rectangle in physical pixels. + pub size: [f32; 2], + /// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left]. + pub corner_radius: [f32; 4], + /// Output scale for antialiasing. + pub scale: f32, +} + #[derive(Clone, Debug, PartialEq)] pub struct SyncFile(pub Rc); @@ -705,8 +754,8 @@ impl dyn GfxFramebuffer { let (width, height) = self.logical_size(transform); Rect::new_saturating(0, 0, width, height) }, - icons: None, stretch: None, + corner_radius: None, }; cursor.render_hardware_cursor(&mut renderer); self.render( @@ -1039,8 +1088,8 @@ pub fn create_render_pass( let (width, height) = logical_size(physical_size, transform); Rect::new_saturating(0, 0, width, height) }, - icons: state.icons.get(state, scale), stretch: None, + corner_radius: None, }; node.node_render(&mut renderer, 0, 0, None); if let Some(rect) = cursor_rect { @@ -1256,6 +1305,8 @@ impl GfxRenderPass { return None; } GfxApiOpt::CopyTexture(ct) => break 'ct2 ct, + GfxApiOpt::RoundedFillRect(_) => return None, + GfxApiOpt::RoundedCopyTexture(_) => return None, } } return None; @@ -1299,6 +1350,8 @@ impl GfxRenderPass { // Texture could be visible. return None; } + GfxApiOpt::RoundedFillRect(_) => return None, + GfxApiOpt::RoundedCopyTexture(_) => return None, } } if let Some(clear) = self.clear diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index 5f1b63e3..bafd11ec 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -23,7 +23,7 @@ use { cmm::cmm_eotf::Eotf, gfx_api::{ AcquireSync, CopyTexture, FdSync, FramebufferRect, GfxApiOpt, GfxContext, GfxError, - GfxTexture, ReleaseSync, SyncFile, + GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SyncFile, }, gfx_apis::gl::{ egl::image::EglImage, @@ -217,6 +217,14 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { copy_tex.push(c); i += 1; } + GfxApiOpt::RoundedFillRect(rf) => { + render_rounded_fill(&fb.ctx, rf); + i += 1; + } + GfxApiOpt::RoundedCopyTexture(ct) => { + render_rounded_texture(&fb.ctx, ct); + i += 1; + } } } if fill_rect.is_not_empty() { @@ -269,6 +277,12 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { { resv.set_sync(user, &file); } + if let GfxApiOpt::RoundedCopyTexture(ct) = op + && ct.release_sync == ReleaseSync::Explicit + && let Some(resv) = &ct.buffer_resv + { + resv.set_sync(user, &file); + } } return Some(file); } @@ -376,6 +390,114 @@ fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) { } } +fn render_rounded_fill(ctx: &GlRenderContext, rf: &RoundedFillRect) { + let gles = ctx.ctx.dpy.gles; + let prog = &ctx.rounded_fill_prog; + let pos = rf.rect.to_points(); + // geo_pos maps vertex corners to pixel coordinates within the rectangle + let [w, h] = rf.size; + let geo: [[f32; 2]; 4] = [[w, 0.0], [0.0, 0.0], [w, h], [0.0, h]]; + let [r, g, b, a] = rf.color.to_array(Eotf::Gamma22); + let a = a * rf.alpha.unwrap_or(1.0); + unsafe { + (gles.glEnable)(GL_BLEND); + (gles.glUseProgram)(prog.prog.prog); + (gles.glUniform4f)(prog.color, r * a, g * a, b * a, a); + (gles.glUniform2f)(prog.size, rf.size[0], rf.size[1]); + (gles.glUniform4f)( + prog.corner_radius, + rf.corner_radius[0], + rf.corner_radius[1], + rf.corner_radius[2], + rf.corner_radius[3], + ); + (gles.glUniform1f)(prog.border_width, rf.border_width); + (gles.glUniform1f)(prog.scale, rf.scale); + (gles.glVertexAttribPointer)(prog.pos as _, 2, GL_FLOAT, GL_FALSE, 0, pos.as_ptr() as _); + (gles.glVertexAttribPointer)(prog.geo as _, 2, GL_FLOAT, GL_FALSE, 0, geo.as_ptr() as _); + (gles.glEnableVertexAttribArray)(prog.pos as _); + (gles.glEnableVertexAttribArray)(prog.geo as _); + (gles.glDrawArrays)(GL_TRIANGLE_STRIP, 0, 4); + (gles.glDisableVertexAttribArray)(prog.pos as _); + (gles.glDisableVertexAttribArray)(prog.geo as _); + } +} + +fn render_rounded_texture(ctx: &GlRenderContext, ct: &RoundedCopyTexture) { + let Some(texture) = ct.tex.as_gl() else { + log::error!("A non-OpenGL texture was passed into OpenGL"); + return; + }; + if !texture.gl.contents_valid.get() { + log::error!("Ignoring texture with invalid contents"); + return; + } + assert!(rc_eq(&ctx.ctx, &texture.ctx.ctx)); + let gles = ctx.ctx.dpy.gles; + unsafe { + handle_explicit_sync(ctx, texture.gl.img.as_ref(), &ct.acquire_sync); + (gles.glActiveTexture)(GL_TEXTURE0); + let target = image_target(texture.gl.external_only); + (gles.glBindTexture)(target, texture.gl.tex); + (gles.glTexParameteri)(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + let progs = match texture.gl.external_only { + true => match &ctx.rounded_tex_external { + Some(p) => p, + _ => { + log::error!( + "Trying to render an external-only texture but context does not support the required extension" + ); + return; + } + }, + false => &ctx.rounded_tex_internal, + }; + let copy_type = match ct.alpha.is_some() { + true => TexCopyType::Multiply, + false => TexCopyType::Identity, + }; + let source_type = TexSourceType::HasAlpha; + (gles.glEnable)(GL_BLEND); + let prog = &progs[copy_type][source_type]; + (gles.glUseProgram)(prog.prog.prog); + (gles.glUniform1i)(prog.tex, 0); + (gles.glUniform2f)(prog.size, ct.size[0], ct.size[1]); + (gles.glUniform4f)( + prog.corner_radius, + ct.corner_radius[0], + ct.corner_radius[1], + ct.corner_radius[2], + ct.corner_radius[3], + ); + (gles.glUniform1f)(prog.scale, ct.scale); + if let Some(alpha) = ct.alpha { + (gles.glUniform1f)(prog.alpha, alpha); + } + let texcoord = ct.source.to_points(); + let pos = ct.target.to_points(); + let [w, h] = ct.size; + let geo: [[f32; 2]; 4] = [[w, 0.0], [0.0, 0.0], [w, h], [0.0, h]]; + (gles.glVertexAttribPointer)( + prog.texcoord as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + texcoord.as_ptr() as _, + ); + (gles.glVertexAttribPointer)(prog.pos as _, 2, GL_FLOAT, GL_FALSE, 0, pos.as_ptr() as _); + (gles.glVertexAttribPointer)(prog.geo as _, 2, GL_FLOAT, GL_FALSE, 0, geo.as_ptr() as _); + (gles.glEnableVertexAttribArray)(prog.texcoord as _); + (gles.glEnableVertexAttribArray)(prog.pos as _); + (gles.glEnableVertexAttribArray)(prog.geo as _); + (gles.glDrawArrays)(GL_TRIANGLE_STRIP, 0, 4); + (gles.glDisableVertexAttribArray)(prog.texcoord as _); + (gles.glDisableVertexAttribArray)(prog.pos as _); + (gles.glDisableVertexAttribArray)(prog.geo as _); + (gles.glBindTexture)(target, 0); + } +} + fn handle_explicit_sync(ctx: &GlRenderContext, img: Option<&Rc>, sync: &AcquireSync) { let Some(sync_file) = sync.get_sync_file() else { return; diff --git a/src/gfx_apis/gl/gl/sys.rs b/src/gfx_apis/gl/gl/sys.rs index 84b81b86..5d79cba8 100644 --- a/src/gfx_apis/gl/gl/sys.rs +++ b/src/gfx_apis/gl/gl/sys.rs @@ -125,6 +125,7 @@ dynload! { glGetAttribLocation: unsafe fn(prog: GLuint, name: *const GLchar) -> GLint, glUniform1i: unsafe fn(location: GLint, v0: GLint), glUniform1f: unsafe fn(location: GLint, v0: GLfloat), + glUniform2f: unsafe fn(location: GLint, v0: GLfloat, v1: GLfloat), glUniform4f: unsafe fn(location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat, v3: GLfloat), glVertexAttribPointer: unsafe fn( index: GLuint, diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 1805e48d..00bfe9c2 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -60,6 +60,29 @@ impl TexProg { } } +pub(crate) struct RoundedFillProg { + pub(crate) prog: GlProgram, + pub(crate) pos: GLint, + pub(crate) geo: GLint, + pub(crate) color: GLint, + pub(crate) size: GLint, + pub(crate) corner_radius: GLint, + pub(crate) border_width: GLint, + pub(crate) scale: GLint, +} + +pub(crate) struct RoundedTexProg { + pub(crate) prog: GlProgram, + pub(crate) pos: GLint, + pub(crate) texcoord: GLint, + pub(crate) geo: GLint, + pub(crate) tex: GLint, + pub(crate) alpha: GLint, + pub(crate) size: GLint, + pub(crate) corner_radius: GLint, + pub(crate) scale: GLint, +} + #[derive(Copy, Clone, PartialEq, Linearize)] pub(in crate::gfx_apis::gl) enum TexCopyType { Identity, @@ -86,6 +109,12 @@ pub(in crate::gfx_apis::gl) struct GlRenderContext { pub(crate) fill_prog_pos: GLint, pub(crate) fill_prog_color: GLint, + pub(crate) rounded_fill_prog: RoundedFillProg, + pub(crate) rounded_tex_internal: + StaticMap>, + pub(crate) rounded_tex_external: + Option>>, + pub(in crate::gfx_apis::gl) gl_state: RefCell, pub(in crate::gfx_apis::gl) buffer_resv_user: BufferResvUser, @@ -163,6 +192,74 @@ impl GlRenderContext { include_str!("../shaders/fill.frag.glsl"), )? }; + let rounded_fill_prog = unsafe { + let prog = GlProgram::from_shaders( + ctx, + include_str!("../shaders/rounded_fill.vert.glsl"), + include_str!("../shaders/rounded_fill.frag.glsl"), + )?; + RoundedFillProg { + pos: prog.get_attrib_location(c"pos"), + geo: prog.get_attrib_location(c"geo"), + color: prog.get_uniform_location(c"color"), + size: prog.get_uniform_location(c"size"), + corner_radius: prog.get_uniform_location(c"corner_radius"), + border_width: prog.get_uniform_location(c"border_width"), + scale: prog.get_uniform_location(c"scale"), + prog, + } + }; + let rounded_tex_vert = include_str!("../shaders/rounded_tex.vert.glsl"); + let rounded_tex_frag = include_str!("../shaders/rounded_tex.frag.glsl"); + let create_rounded_tex_programs = |external: bool| { + let create_program = |alpha_multiplier: bool, alpha: bool| { + let mut src = String::new(); + if external { + src.push_str("#define EXTERNAL\n"); + } + if alpha_multiplier { + src.push_str("#define ALPHA_MULTIPLIER\n"); + } + if alpha { + src.push_str("#define ALPHA\n"); + } + src.push_str(rounded_tex_frag); + unsafe { + let prog = GlProgram::from_shaders(ctx, rounded_tex_vert, &src)?; + let alpha_loc = match alpha_multiplier { + true => prog.get_uniform_location(c"alpha"), + false => 0, + }; + Ok::<_, RenderError>(RoundedTexProg { + pos: prog.get_attrib_location(c"pos"), + texcoord: prog.get_attrib_location(c"texcoord"), + geo: prog.get_attrib_location(c"geo"), + tex: prog.get_uniform_location(c"tex"), + alpha: alpha_loc, + size: prog.get_uniform_location(c"size"), + corner_radius: prog.get_uniform_location(c"corner_radius"), + scale: prog.get_uniform_location(c"scale"), + prog, + }) + } + }; + Ok::<_, RenderError>(static_map! { + TexCopyType::Identity => static_map! { + TexSourceType::Opaque => create_program(false, false)?, + TexSourceType::HasAlpha => create_program(false, true)?, + }, + TexCopyType::Multiply => static_map! { + TexSourceType::Opaque => create_program(true, false)?, + TexSourceType::HasAlpha => create_program(true, true)?, + }, + }) + }; + let rounded_tex_internal = create_rounded_tex_programs(false)?; + let rounded_tex_external = if ctx.ext.contains(GL_OES_EGL_IMAGE_EXTERNAL) { + Some(create_rounded_tex_programs(true)?) + } else { + None + }; Ok(Self { ctx: ctx.clone(), gbm: ctx.dpy.gbm.clone(), @@ -177,6 +274,10 @@ impl GlRenderContext { fill_prog_color: unsafe { fill_prog.get_uniform_location(c"color") }, fill_prog, + rounded_fill_prog, + rounded_tex_internal, + rounded_tex_external, + gl_state: Default::default(), buffer_resv_user: Default::default(), diff --git a/src/gfx_apis/gl/shaders/rounded_fill.frag.glsl b/src/gfx_apis/gl/shaders/rounded_fill.frag.glsl new file mode 100644 index 00000000..41410af2 --- /dev/null +++ b/src/gfx_apis/gl/shaders/rounded_fill.frag.glsl @@ -0,0 +1,56 @@ +precision mediump float; + +varying vec2 v_geo; + +uniform vec4 color; +uniform vec2 size; +uniform vec4 corner_radius; +uniform float border_width; +uniform float scale; + +float rounding_alpha(vec2 coords, vec2 half_size, vec4 radii) { + float radius; + vec2 center; + + if (coords.x < half_size.x && coords.y < half_size.y) { + radius = radii.x; // top-left + center = vec2(radius, radius); + } else if (coords.x >= half_size.x && coords.y < half_size.y) { + radius = radii.y; // top-right + center = vec2(size.x - radius, radius); + } else if (coords.x >= half_size.x && coords.y >= half_size.y) { + radius = radii.z; // bottom-right + center = vec2(size.x - radius, size.y - radius); + } else { + radius = radii.w; // bottom-left + center = vec2(radius, size.y - radius); + } + + if (radius == 0.0) + return 1.0; + + float dist = distance(coords, center); + float half_px = 0.5 / scale; + return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); +} + +void main() { + vec2 half_size = size / 2.0; + float outer_alpha = rounding_alpha(v_geo, half_size, corner_radius); + + float inner_alpha = 0.0; + if (border_width > 0.0) { + vec2 inner_coords = v_geo - vec2(border_width); + vec2 inner_size = size - vec2(border_width * 2.0); + vec2 inner_half = inner_size / 2.0; + vec4 inner_radii = max(corner_radius - vec4(border_width), vec4(0.0)); + + if (inner_coords.x >= 0.0 && inner_coords.y >= 0.0 && + inner_coords.x <= inner_size.x && inner_coords.y <= inner_size.y) { + inner_alpha = rounding_alpha(inner_coords, inner_half, inner_radii); + } + } + + float a = outer_alpha * (1.0 - inner_alpha); + gl_FragColor = color * a; +} diff --git a/src/gfx_apis/gl/shaders/rounded_fill.vert.glsl b/src/gfx_apis/gl/shaders/rounded_fill.vert.glsl new file mode 100644 index 00000000..2d353e59 --- /dev/null +++ b/src/gfx_apis/gl/shaders/rounded_fill.vert.glsl @@ -0,0 +1,8 @@ +attribute vec2 pos; +attribute vec2 geo; +varying vec2 v_geo; + +void main() { + gl_Position = vec4(pos, 0.0, 1.0); + v_geo = geo; +} diff --git a/src/gfx_apis/gl/shaders/rounded_tex.frag.glsl b/src/gfx_apis/gl/shaders/rounded_tex.frag.glsl new file mode 100644 index 00000000..867f44da --- /dev/null +++ b/src/gfx_apis/gl/shaders/rounded_tex.frag.glsl @@ -0,0 +1,71 @@ +#ifdef EXTERNAL +#extension GL_OES_EGL_image_external : require +#endif + +precision mediump float; + +varying vec2 v_texcoord; +varying vec2 v_geo; + +#ifdef EXTERNAL +uniform samplerExternalOES tex; +#else +uniform sampler2D tex; +#endif +#ifdef ALPHA_MULTIPLIER +uniform float alpha; +#endif +uniform vec2 size; +uniform vec4 corner_radius; +uniform float scale; + +float rounding_alpha(vec2 coords, vec2 half_size, vec4 radii) { + float radius; + vec2 center; + + if (coords.x < half_size.x && coords.y < half_size.y) { + radius = radii.x; // top-left + center = vec2(radius, radius); + } else if (coords.x >= half_size.x && coords.y < half_size.y) { + radius = radii.y; // top-right + center = vec2(size.x - radius, radius); + } else if (coords.x >= half_size.x && coords.y >= half_size.y) { + radius = radii.z; // bottom-right + center = vec2(size.x - radius, size.y - radius); + } else { + radius = radii.w; // bottom-left + center = vec2(radius, size.y - radius); + } + + if (radius == 0.0) + return 1.0; + + float dist = distance(coords, center); + float half_px = 0.5 / scale; + return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); +} + +void main() { + vec2 half_size = size / 2.0; + float ra = rounding_alpha(v_geo, half_size, corner_radius); + +#ifdef ALPHA + +#ifdef ALPHA_MULTIPLIER + gl_FragColor = texture2D(tex, v_texcoord) * alpha * ra; +#else // !ALPHA_MULTIPLIER + gl_FragColor = texture2D(tex, v_texcoord) * ra; +#endif // ALPHA_MULTIPLIER + +#else // !ALPHA + +#ifdef ALPHA_MULTIPLIER + vec4 tc = texture2D(tex, v_texcoord); + gl_FragColor = vec4(tc.rgb * alpha * ra, alpha * ra); +#else // !ALPHA_MULTIPLIER + vec4 tc = texture2D(tex, v_texcoord); + gl_FragColor = vec4(tc.rgb * ra, ra); +#endif // ALPHA_MULTIPLIER + +#endif // ALPHA +} diff --git a/src/gfx_apis/gl/shaders/rounded_tex.vert.glsl b/src/gfx_apis/gl/shaders/rounded_tex.vert.glsl new file mode 100644 index 00000000..819dd3dc --- /dev/null +++ b/src/gfx_apis/gl/shaders/rounded_tex.vert.glsl @@ -0,0 +1,11 @@ +attribute vec2 pos; +attribute vec2 texcoord; +attribute vec2 geo; +varying vec2 v_texcoord; +varying vec2 v_geo; + +void main() { + gl_Position = vec4(pos, 0.0, 1.0); + v_texcoord = texcoord; + v_geo = geo; +} diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 7cd78317..6e127ced 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -27,9 +27,14 @@ use { semaphore::VulkanSemaphore, shaders::{ ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants, - InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_TEX_FRAG, LEGACY_TEX_VERT, - LegacyFillPushConstants, LegacyTexPushConstants, OUT_FRAG, OUT_VERT, - OutPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, TexVertex, VulkanShader, + InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG, + LEGACY_ROUNDED_FILL_VERT, LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT, + LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, + LegacyRoundedFillPushConstants, LegacyRoundedTexPushConstants, + LegacyTexPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants, ROUNDED_FILL_FRAG, + ROUNDED_FILL_VERT, ROUNDED_TEX_FRAG, ROUNDED_TEX_VERT, RoundedFillPushConstants, + RoundedTexPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, TexVertex, + VulkanShader, }, }, io_uring::IoUring, @@ -104,6 +109,13 @@ pub struct VulkanRenderer { pub(super) tex_frag_shader: Rc, pub(super) out_vert_shader: Option>, pub(super) out_frag_shader: Option>, + pub(super) rounded_fill_vert_shader: Rc, + pub(super) rounded_fill_frag_shader: Rc, + pub(super) rounded_tex_vert_shader: Rc, + pub(super) rounded_tex_frag_shader: Rc, + pub(super) rounded_fill_pipelines: CopyHashMap, + pub(super) rounded_tex_pipelines: + StaticMap>>, pub(super) tex_descriptor_set_layouts: ArrayVec, 2>, pub(super) out_descriptor_set_layout: Option>, pub(super) defunct: Cell, @@ -202,6 +214,8 @@ type Point = [[f32; 2]; 4]; enum VulkanOp { Fill(VulkanFillOp), Tex(VulkanTexOp), + RoundedFill(VulkanRoundedFillOp), + RoundedTex(VulkanRoundedTexOp), } struct VulkanTexOp { @@ -231,6 +245,39 @@ struct VulkanFillOp { instances: u32, } +struct VulkanRoundedFillOp { + target: Point, + color: [f32; 4], + source_type: TexSourceType, + size: [f32; 2], + corner_radius: [f32; 4], + border_width: f32, + scale: f32, + range_address: DeviceAddress, +} + +struct VulkanRoundedTexOp { + tex: Rc, + index: usize, + target: Point, + source: Point, + buffer_resv: Option>, + acquire_sync: Option, + release_sync: ReleaseSync, + alpha: f32, + source_type: TexSourceType, + copy_type: TexCopyType, + alpha_mode: AlphaMode, + tex_cd: Rc, + color_management_data_address: Option, + eotf_args_address: Option, + resource_descriptor_buffer_offset: DeviceAddress, + size: [f32; 2], + corner_radius: [f32; 4], + scale: f32, + range_address: DeviceAddress, +} + #[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)] pub(super) enum RenderPass { BlendBuffer, @@ -295,12 +342,20 @@ impl VulkanDevice { let tex_frag_shader; let out_vert_shader; let out_frag_shader; + let rounded_fill_vert_shader; + let rounded_fill_frag_shader; + let rounded_tex_vert_shader; + let rounded_tex_frag_shader; let mut tex_descriptor_set_layouts = ArrayVec::new(); if self.descriptor_buffer.is_some() { tex_vert_shader = self.create_shader(TEX_VERT)?; tex_frag_shader = self.create_shader(TEX_FRAG)?; fill_vert_shader = self.create_shader(FILL_VERT)?; fill_frag_shader = self.create_shader(FILL_FRAG)?; + rounded_fill_vert_shader = self.create_shader(ROUNDED_FILL_VERT)?; + rounded_fill_frag_shader = self.create_shader(ROUNDED_FILL_FRAG)?; + rounded_tex_vert_shader = self.create_shader(ROUNDED_TEX_VERT)?; + rounded_tex_frag_shader = self.create_shader(ROUNDED_TEX_FRAG)?; out_vert_shader = Some(self.create_shader(OUT_VERT)?); out_frag_shader = Some(self.create_shader(OUT_FRAG)?); tex_descriptor_set_layouts @@ -311,6 +366,10 @@ impl VulkanDevice { tex_frag_shader = self.create_shader(LEGACY_TEX_FRAG)?; fill_vert_shader = self.create_shader(LEGACY_FILL_VERT)?; fill_frag_shader = self.create_shader(LEGACY_FILL_FRAG)?; + rounded_fill_vert_shader = self.create_shader(LEGACY_ROUNDED_FILL_VERT)?; + rounded_fill_frag_shader = self.create_shader(LEGACY_ROUNDED_FILL_FRAG)?; + rounded_tex_vert_shader = self.create_shader(LEGACY_ROUNDED_TEX_VERT)?; + rounded_tex_frag_shader = self.create_shader(LEGACY_ROUNDED_TEX_FRAG)?; out_vert_shader = None; out_frag_shader = None; tex_descriptor_set_layouts @@ -400,6 +459,12 @@ impl VulkanDevice { tex_frag_shader, out_vert_shader, out_frag_shader, + rounded_fill_vert_shader, + rounded_fill_frag_shader, + rounded_tex_vert_shader, + rounded_tex_frag_shader, + rounded_fill_pipelines: Default::default(), + rounded_tex_pipelines: Default::default(), tex_descriptor_set_layouts, out_descriptor_set_layout, defunct: Cell::new(false), @@ -457,6 +522,112 @@ impl VulkanRenderer { Ok(fill_pipelines) } + fn get_or_create_rounded_fill_pipelines( + &self, + format: vk::Format, + ) -> Result { + if let Some(pl) = self.rounded_fill_pipelines.get(&format) { + return Ok(pl); + } + let create_pipeline = |src_has_alpha| { + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format, + vert: self.rounded_fill_vert_shader.clone(), + frag: self.rounded_fill_frag_shader.clone(), + blend: src_has_alpha, + src_has_alpha, + has_alpha_mult: false, + alpha_mode: AlphaMode::PremultipliedOptical, + eotf: EOTF_LINEAR, + inv_eotf: EOTF_LINEAR, + descriptor_set_layouts: Default::default(), + has_color_management_data: false, + }; + self.device.create_pipeline2(info, push_size) + }; + let pipelines = Rc::new(static_map! { + TexSourceType::HasAlpha => create_pipeline(true)?, + TexSourceType::Opaque => create_pipeline(false)?, + }); + self.rounded_fill_pipelines.set(format, pipelines.clone()); + Ok(pipelines) + } + + fn get_or_create_rounded_tex_pipelines( + &self, + format: vk::Format, + target_cd: &ColorDescription, + ) -> Rc { + let eotf = target_cd.eotf.to_vulkan(); + let pipelines = &self.rounded_tex_pipelines[eotf]; + match pipelines.get(&format) { + Some(pl) => pl, + _ => { + let pl = Rc::new(TexPipelines { + format, + eotf, + pipelines: Default::default(), + }); + pipelines.set(format, pl.clone()); + pl + } + } + } + + fn get_or_create_rounded_tex_pipeline( + &self, + pipelines: &TexPipelines, + tex_cd: &ColorDescription, + tex_copy_type: TexCopyType, + tex_source_type: TexSourceType, + mut tex_alpha_mode: AlphaMode, + has_color_management_data: bool, + ) -> Result, VulkanError> { + if tex_source_type == TexSourceType::Opaque { + tex_alpha_mode = AlphaMode::PremultipliedElectrical; + } + let key = TexPipelineKey { + tex_copy_type, + tex_source_type, + tex_alpha_mode, + eotf: tex_cd.eotf.to_vulkan(), + has_color_management_data, + }; + if let Some(pl) = pipelines.pipelines.get(&key) { + return Ok(pl); + } + let has_alpha_mult = match tex_copy_type { + TexCopyType::Identity => false, + TexCopyType::Multiply => true, + }; + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format: pipelines.format, + vert: self.rounded_tex_vert_shader.clone(), + frag: self.rounded_tex_frag_shader.clone(), + blend: true, // always blend since corners are transparent + src_has_alpha: true, // rounding makes everything have alpha + has_alpha_mult, + alpha_mode: key.tex_alpha_mode, + eotf: key.eotf.to_vulkan(), + inv_eotf: pipelines.eotf.to_vulkan(), + descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), + has_color_management_data, + }; + let pl = self.device.create_pipeline2(info, push_size)?; + pipelines.pipelines.set(key, pl.clone()); + Ok(pl) + } + fn get_or_create_tex_pipelines( &self, format: vk::Format, @@ -665,23 +836,34 @@ impl VulkanRenderer { RenderPass::FrameBuffer => fb_inv_eotf_args_descriptor, }; for cmd in &mut memory.ops[pass] { - let VulkanOp::Tex(c) = cmd else { - continue; + let (tex, resource_offset, cm_addr, eotf_addr) = match cmd { + VulkanOp::Tex(c) => ( + &c.tex, + &mut c.resource_descriptor_buffer_offset, + &c.color_management_data_address, + &c.eotf_args_address, + ), + VulkanOp::RoundedTex(c) => ( + &c.tex, + &mut c.resource_descriptor_buffer_offset, + &c.color_management_data_address, + &c.eotf_args_address, + ), + _ => continue, }; - let tex = &c.tex; - c.resource_descriptor_buffer_offset = resource_writer.next_offset(); + *resource_offset = resource_writer.next_offset(); let mut writer = resource_writer.add_set(tex_descriptor_set_layout); writer.write( tex_descriptor_set_layout.offsets[0], tex.sampled_image_descriptor.as_ref().unwrap(), ); - if let Some(addr) = c.color_management_data_address { + if let Some(addr) = *cm_addr { writer.write( tex_descriptor_set_layout.offsets[1], get_ub_descriptor!(addr, ColorManagementData), ); } - if let Some(addr) = c.eotf_args_address { + if let Some(addr) = *eotf_addr { writer.write( tex_descriptor_set_layout.offsets[2], get_ub_descriptor!(addr, EotfArgs), @@ -741,12 +923,18 @@ impl VulkanRenderer { enum Key { Fill { color: [u32; 4] }, Tex(usize), + RoundedFill { color: [u32; 4] }, + RoundedTex(usize), } match o { VulkanOp::Fill(f) => Key::Fill { color: f.color.map(|c| c.to_bits()), }, VulkanOp::Tex(t) => Key::Tex(t.index), + VulkanOp::RoundedFill(f) => Key::RoundedFill { + color: f.color.map(|c| c.to_bits()), + }, + VulkanOp::RoundedTex(t) => Key::RoundedTex(t.index), } }); let mops = &mut memory.ops[pass]; @@ -782,6 +970,22 @@ impl VulkanRenderer { } mops.push(VulkanOp::Tex(c)); } + VulkanOp::RoundedFill(mut f) => { + f.range_address = memory.data_buffer.len() as DeviceAddress; + memory.data_buffer.extend_from_slice(uapi::as_bytes(&f.target)); + mops.push(VulkanOp::RoundedFill(f)); + } + VulkanOp::RoundedTex(mut c) => { + c.range_address = memory.data_buffer.len() as DeviceAddress; + let vertex = TexVertex { + pos: c.target, + tex_pos: c.source, + }; + memory + .data_buffer + .extend_from_slice(uapi::as_bytes(&vertex)); + mops.push(VulkanOp::RoundedTex(c)); + } } } } @@ -917,6 +1121,105 @@ impl VulkanRenderer { })); } } + GfxApiOpt::RoundedFillRect(rf) => { + let target = rf.rect.to_points(); + for pass in RenderPass::variants() { + let Some(bounds) = memory.paint_bounds[pass] else { + continue; + }; + if !bounds.intersects(&target) { + continue; + } + let target_cd = match pass { + RenderPass::BlendBuffer => blend_cd, + RenderPass::FrameBuffer => fb_cd, + }; + let tf = target_cd.eotf; + let color = memory.color_transforms.apply_to_color( + &rf.cd, + target_cd, + rf.render_intent, + rf.color, + ); + let color = color.to_array2(tf, rf.alpha); + let source_type = TexSourceType::HasAlpha; + memory.ops_tmp[pass].push(VulkanOp::RoundedFill(VulkanRoundedFillOp { + target, + color, + source_type, + size: rf.size, + corner_radius: rf.corner_radius, + border_width: rf.border_width, + scale: rf.scale, + range_address: 0, + })); + } + } + GfxApiOpt::RoundedCopyTexture(ct) => { + let tex = ct.tex.clone().into_vk(&self.device.device)?; + if tex.contents_are_undefined.get() { + log::warn!("Ignoring undefined texture"); + continue; + } + if tex.queue_state.get().acquire(QueueFamily::Gfx) == QueueTransfer::Impossible + { + log::warn!("Ignoring texture owned by different queue"); + continue; + } + let target = ct.target.to_points(); + let source = ct.source.to_points(); + for pass in RenderPass::variants() { + let Some(bounds) = memory.paint_bounds[pass] else { + continue; + }; + if !bounds.intersects(&target) { + continue; + } + let copy_type = match ct.alpha.is_some() { + true => TexCopyType::Multiply, + false => TexCopyType::Identity, + }; + let source_type = TexSourceType::HasAlpha; + let target_cd = match pass { + RenderPass::BlendBuffer => blend_cd, + RenderPass::FrameBuffer => fb_cd, + }; + let color_management_data_address = memory.color_transforms.get_offset( + &ct.cd.linear, + target_cd, + ct.render_intent, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); + let eotf_args_address = memory.eotf_args_cache.get_offset( + &ct.cd, + false, + self.device.uniform_buffer_offset_mask, + &mut memory.uniform_buffer_writer, + ); + memory.ops_tmp[pass].push(VulkanOp::RoundedTex(VulkanRoundedTexOp { + tex: tex.clone(), + index, + target, + source, + buffer_resv: ct.buffer_resv.clone(), + acquire_sync: Some(ct.acquire_sync.clone()), + release_sync: ct.release_sync, + alpha: ct.alpha.unwrap_or_default(), + source_type, + copy_type, + alpha_mode: ct.alpha_mode, + tex_cd: ct.cd.clone(), + color_management_data_address, + eotf_args_address, + resource_descriptor_buffer_offset: 0, + size: ct.size, + corner_radius: ct.corner_radius, + scale: ct.scale, + range_address: 0, + })); + } + } } } sync(memory); @@ -998,6 +1301,12 @@ impl VulkanRenderer { VulkanOp::Tex(c) => { c.range_address += buffer.buffer.address; } + VulkanOp::RoundedFill(f) => { + f.range_address += buffer.buffer.address; + } + VulkanOp::RoundedTex(c) => { + c.range_address += buffer.buffer.address; + } } } } @@ -1029,9 +1338,16 @@ impl VulkanRenderer { } for ops in memory.ops.values_mut() { for op in ops { - if let VulkanOp::Tex(c) = op { - adj!(&mut c.color_management_data_address); - adj!(&mut c.eotf_args_address); + match op { + VulkanOp::Tex(c) => { + adj!(&mut c.color_management_data_address); + adj!(&mut c.eotf_args_address); + } + VulkanOp::RoundedTex(c) => { + adj!(&mut c.color_management_data_address); + adj!(&mut c.eotf_args_address); + } + _ => {} } } } @@ -1051,8 +1367,12 @@ impl VulkanRenderer { let execution = self.allocate_point(); for pass in RenderPass::variants() { for cmd in &mut memory.ops[pass] { - if let VulkanOp::Tex(c) = cmd { - let tex = &c.tex; + let tex_data = match cmd { + VulkanOp::Tex(c) => Some((&c.tex, &mut c.buffer_resv, &mut c.acquire_sync, c.release_sync)), + VulkanOp::RoundedTex(c) => Some((&c.tex, &mut c.buffer_resv, &mut c.acquire_sync, c.release_sync)), + _ => None, + }; + if let Some((tex, buffer_resv, acquire_sync, release_sync)) = tex_data { if tex.execution_version.replace(execution) == execution { continue; } @@ -1066,9 +1386,9 @@ impl VulkanRenderer { } memory.textures.push(UsedTexture { tex: tex.clone(), - resv: c.buffer_resv.take(), - acquire_sync: c.acquire_sync.take().unwrap(), - release_sync: c.release_sync, + resv: buffer_resv.take(), + acquire_sync: acquire_sync.take().unwrap(), + release_sync, }); } } @@ -1301,6 +1621,10 @@ impl VulkanRenderer { let memory = &*self.memory.borrow(); let fill_pl = self.get_or_create_fill_pipelines(target.format.vk_format)?; let tex_pl = self.get_or_create_tex_pipelines(target.format.vk_format, target_cd); + let rounded_fill_pl = + self.get_or_create_rounded_fill_pipelines(target.format.vk_format)?; + let rounded_tex_pl = + self.get_or_create_rounded_tex_pipelines(target.format.vk_format, target_cd); let dev = &self.device.device; let mut current_pipeline = None; let mut bind = |pipeline: &VulkanPipeline| { @@ -1421,6 +1745,134 @@ impl VulkanRenderer { } } } + VulkanOp::RoundedFill(r) => { + let pipeline = &rounded_fill_pl[r.source_type]; + bind(pipeline); + if self.device.descriptor_buffer.is_some() { + let push = RoundedFillPushConstants { + color: r.color, + vertices: r.range_address, + size: r.size, + corner_radius: r.corner_radius, + border_width: r.border_width, + scale: r.scale, + }; + unsafe { + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } else { + let push = LegacyRoundedFillPushConstants { + pos: r.target, + color: r.color, + size_x: r.size[0], + size_y: r.size[1], + corner_radius_tl: r.corner_radius[0], + corner_radius_tr: r.corner_radius[1], + corner_radius_br: r.corner_radius[2], + corner_radius_bl: r.corner_radius[3], + border_width: r.border_width, + scale: r.scale, + }; + unsafe { + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } + } + VulkanOp::RoundedTex(c) => { + let tex = &c.tex; + let pipeline = self.get_or_create_rounded_tex_pipeline( + &rounded_tex_pl, + &c.tex_cd, + c.copy_type, + c.source_type, + c.alpha_mode, + c.color_management_data_address.is_some(), + )?; + bind(&pipeline); + let image_info = DescriptorImageInfo::default() + .image_view(tex.texture_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + if let Some(db) = &self.device.descriptor_buffer { + let push = RoundedTexPushConstants { + vertices: c.range_address, + alpha: c.alpha, + size_x: c.size[0], + size_y: c.size[1], + corner_radius_tl: c.corner_radius[0], + corner_radius_tr: c.corner_radius[1], + corner_radius_br: c.corner_radius[2], + corner_radius_bl: c.corner_radius[3], + scale: c.scale, + }; + unsafe { + db.cmd_set_descriptor_buffer_offsets( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &[0, 1], + &[0, c.resource_descriptor_buffer_offset], + ); + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } else { + let write_descriptor_set = WriteDescriptorSet::default() + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&image_info)); + unsafe { + self.device.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + slice::from_ref(&write_descriptor_set), + ); + } + let push = LegacyRoundedTexPushConstants { + pos: c.target, + tex_pos: c.source, + alpha: c.alpha, + size_x: c.size[0], + size_y: c.size[1], + corner_radius_tl: c.corner_radius[0], + corner_radius_tr: c.corner_radius[1], + corner_radius_br: c.corner_radius[2], + corner_radius_bl: c.corner_radius[3], + scale: c.scale, + }; + unsafe { + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } + } } } Ok(()) @@ -1933,6 +2385,13 @@ impl VulkanRenderer { }; (opaque, c.target) } + GfxApiOpt::RoundedFillRect(_) => { + // Rounded rects are never fully opaque due to AA at corners + continue; + } + GfxApiOpt::RoundedCopyTexture(_) => { + continue; + } }; if opaque || bb.is_none() { tag |= 1; diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index d6b8bec8..c09c01c5 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -15,6 +15,18 @@ pub const LEGACY_FILL_VERT: &[u8] = include_bytes!("shaders_bin/legacy_fill.vert pub const LEGACY_FILL_FRAG: &[u8] = include_bytes!("shaders_bin/legacy_fill.frag.spv"); pub const LEGACY_TEX_VERT: &[u8] = include_bytes!("shaders_bin/legacy_tex.vert.spv"); pub const LEGACY_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/legacy_tex.frag.spv"); +pub const ROUNDED_FILL_VERT: &[u8] = include_bytes!("shaders_bin/rounded_fill.vert.spv"); +pub const ROUNDED_FILL_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_fill.frag.spv"); +pub const ROUNDED_TEX_VERT: &[u8] = include_bytes!("shaders_bin/rounded_tex.vert.spv"); +pub const ROUNDED_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_tex.frag.spv"); +pub const LEGACY_ROUNDED_FILL_VERT: &[u8] = + include_bytes!("shaders_bin/legacy_rounded_fill.vert.spv"); +pub const LEGACY_ROUNDED_FILL_FRAG: &[u8] = + include_bytes!("shaders_bin/legacy_rounded_fill.frag.spv"); +pub const LEGACY_ROUNDED_TEX_VERT: &[u8] = + include_bytes!("shaders_bin/legacy_rounded_tex.vert.spv"); +pub const LEGACY_ROUNDED_TEX_FRAG: &[u8] = + include_bytes!("shaders_bin/legacy_rounded_tex.frag.spv"); pub struct VulkanShader { pub(super) device: Rc, @@ -99,6 +111,69 @@ pub struct LegacyTexPushConstants { unsafe impl Packed for LegacyTexPushConstants {} +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct RoundedFillPushConstants { + pub color: [f32; 4], + pub vertices: DeviceAddress, + pub size: [f32; 2], + pub corner_radius: [f32; 4], + pub border_width: f32, + pub scale: f32, +} + +unsafe impl Packed for RoundedFillPushConstants {} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct LegacyRoundedFillPushConstants { + pub pos: [[f32; 2]; 4], + pub color: [f32; 4], + pub size_x: f32, + pub size_y: f32, + pub corner_radius_tl: f32, + pub corner_radius_tr: f32, + pub corner_radius_br: f32, + pub corner_radius_bl: f32, + pub border_width: f32, + pub scale: f32, +} + +unsafe impl Packed for LegacyRoundedFillPushConstants {} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct RoundedTexPushConstants { + pub vertices: DeviceAddress, + pub alpha: f32, + pub size_x: f32, + pub size_y: f32, + pub corner_radius_tl: f32, + pub corner_radius_tr: f32, + pub corner_radius_br: f32, + pub corner_radius_bl: f32, + pub scale: f32, +} + +unsafe impl Packed for RoundedTexPushConstants {} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct LegacyRoundedTexPushConstants { + pub pos: [[f32; 2]; 4], + pub tex_pos: [[f32; 2]; 4], + pub alpha: f32, + pub size_x: f32, + pub size_y: f32, + pub corner_radius_tl: f32, + pub corner_radius_tr: f32, + pub corner_radius_br: f32, + pub corner_radius_bl: f32, + pub scale: f32, +} + +unsafe impl Packed for LegacyRoundedTexPushConstants {} + #[derive(Copy, Clone, Debug)] #[repr(C)] pub struct OutPushConstants { diff --git a/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl b/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl new file mode 100644 index 00000000..14961083 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl @@ -0,0 +1,12 @@ +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; + layout(offset = 32) vec4 color; + layout(offset = 48) float size_x; + layout(offset = 52) float size_y; + layout(offset = 56) float corner_radius_tl; + layout(offset = 60) float corner_radius_tr; + layout(offset = 64) float corner_radius_br; + layout(offset = 68) float corner_radius_bl; + layout(offset = 72) float border_width; + layout(offset = 76) float scale; +} data; diff --git a/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag b/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag new file mode 100644 index 00000000..6c6e5ade --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag @@ -0,0 +1,48 @@ +#version 450 + +#include "../frag_spec_const.glsl" +#include "rounded_fill.common.glsl" + +layout(location = 0) in vec2 geo_pos; +layout(location = 0) out vec4 out_color; + +float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) { + if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) { + return 0.0; + } + vec2 center; + float radius; + if (coords.x < corner_radius.x && coords.y < corner_radius.x) { + radius = corner_radius.x; + center = vec2(radius, radius); + } else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) { + radius = corner_radius.y; + center = vec2(size.x - radius, radius); + } else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) { + radius = corner_radius.z; + center = vec2(size.x - radius, size.y - radius); + } else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) { + radius = corner_radius.w; + center = vec2(radius, size.y - radius); + } else { + return 1.0; + } + float half_px = 0.5 / data.scale; + float dist = distance(coords, center); + return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); +} + +void main() { + vec2 size = vec2(data.size_x, data.size_y); + vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl); + float outer_alpha = rounding_alpha(geo_pos, size, corner_radius); + float alpha = outer_alpha; + if (data.border_width > 0.0) { + vec2 inner_coords = geo_pos - vec2(data.border_width); + vec2 inner_size = size - vec2(2.0 * data.border_width); + vec4 inner_radius = max(corner_radius - vec4(data.border_width), vec4(0.0)); + float inner_alpha = rounding_alpha(inner_coords, inner_size, inner_radius); + alpha = outer_alpha * (1.0 - inner_alpha); + } + out_color = data.color * alpha; +} diff --git a/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert b/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert new file mode 100644 index 00000000..5fbd7781 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert @@ -0,0 +1,17 @@ +#version 450 + +#include "rounded_fill.common.glsl" + +layout(location = 0) out vec2 geo_pos; + +void main() { + vec2 size = vec2(data.size_x, data.size_y); + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; geo_pos = vec2(size.x, 0.0); break; + case 1: pos = data.pos[1]; geo_pos = vec2(0.0, 0.0); break; + case 2: pos = data.pos[2]; geo_pos = vec2(size.x, size.y); break; + case 3: pos = data.pos[3]; geo_pos = vec2(0.0, size.y); break; + } + gl_Position = vec4(pos, 0.0, 1.0); +} diff --git a/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl b/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl new file mode 100644 index 00000000..f221ff6a --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl @@ -0,0 +1,12 @@ +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; + layout(offset = 32) vec2 tex_pos[4]; + layout(offset = 64) float mul; + layout(offset = 68) float size_x; + layout(offset = 72) float size_y; + layout(offset = 76) float corner_radius_tl; + layout(offset = 80) float corner_radius_tr; + layout(offset = 84) float corner_radius_br; + layout(offset = 88) float corner_radius_bl; + layout(offset = 92) float scale; +} data; diff --git a/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag b/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag new file mode 100644 index 00000000..cec6f001 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag @@ -0,0 +1,51 @@ +#version 450 + +#include "../frag_spec_const.glsl" +#include "rounded_tex.common.glsl" + +layout(set = 0, binding = 0) uniform sampler2D tex; +layout(location = 0) in vec2 tex_pos; +layout(location = 1) in vec2 geo_pos; +layout(location = 0) out vec4 out_color; + +float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) { + if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) { + return 0.0; + } + vec2 center; + float radius; + if (coords.x < corner_radius.x && coords.y < corner_radius.x) { + radius = corner_radius.x; + center = vec2(radius, radius); + } else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) { + radius = corner_radius.y; + center = vec2(size.x - radius, radius); + } else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) { + radius = corner_radius.z; + center = vec2(size.x - radius, size.y - radius); + } else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) { + radius = corner_radius.w; + center = vec2(radius, size.y - radius); + } else { + return 1.0; + } + float half_px = 0.5 / data.scale; + float dist = distance(coords, center); + return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); +} + +void main() { + vec2 size = vec2(data.size_x, data.size_y); + vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl); + vec4 c = textureLod(tex, tex_pos, 0); + if (has_alpha_multiplier) { + if (src_has_alpha) { + c *= data.mul; + } else { + c = vec4(c.rgb * data.mul, data.mul); + } + } + float ra = rounding_alpha(geo_pos, size, corner_radius); + c *= ra; + out_color = c; +} diff --git a/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert b/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert new file mode 100644 index 00000000..53f68b5c --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert @@ -0,0 +1,18 @@ +#version 450 + +#include "rounded_tex.common.glsl" + +layout(location = 0) out vec2 tex_pos; +layout(location = 1) out vec2 geo_pos; + +void main() { + vec2 size = vec2(data.size_x, data.size_y); + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; geo_pos = vec2(size.x, 0.0); break; + case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; geo_pos = vec2(0.0, 0.0); break; + case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; geo_pos = vec2(size.x, size.y); break; + case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; geo_pos = vec2(0.0, size.y); break; + } + gl_Position = vec4(pos, 0.0, 1.0); +} diff --git a/src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl b/src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl new file mode 100644 index 00000000..c618c6c0 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl @@ -0,0 +1,14 @@ +#extension GL_EXT_buffer_reference : require + +layout(buffer_reference, buffer_reference_align = 8, std430) readonly buffer Vertices { + vec2 pos[][4]; +}; + +layout(push_constant, std430) uniform Data { + vec4 color; + Vertices vertices; + vec2 size; + vec4 corner_radius; + float border_width; + float scale; +} data; diff --git a/src/gfx_apis/vulkan/shaders/rounded_fill.frag b/src/gfx_apis/vulkan/shaders/rounded_fill.frag new file mode 100644 index 00000000..60bb20cc --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/rounded_fill.frag @@ -0,0 +1,45 @@ +#version 450 + +#include "rounded_fill.common.glsl" + +layout(location = 0) in vec2 geo_pos; +layout(location = 0) out vec4 out_color; + +float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) { + if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) { + return 0.0; + } + vec2 center; + float radius; + if (coords.x < corner_radius.x && coords.y < corner_radius.x) { + radius = corner_radius.x; + center = vec2(radius, radius); + } else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) { + radius = corner_radius.y; + center = vec2(size.x - radius, radius); + } else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) { + radius = corner_radius.z; + center = vec2(size.x - radius, size.y - radius); + } else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) { + radius = corner_radius.w; + center = vec2(radius, size.y - radius); + } else { + return 1.0; + } + float half_px = 0.5 / data.scale; + float dist = distance(coords, center); + return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); +} + +void main() { + float outer_alpha = rounding_alpha(geo_pos, data.size, data.corner_radius); + float alpha = outer_alpha; + if (data.border_width > 0.0) { + vec2 inner_coords = geo_pos - vec2(data.border_width); + vec2 inner_size = data.size - vec2(2.0 * data.border_width); + vec4 inner_radius = max(data.corner_radius - vec4(data.border_width), vec4(0.0)); + float inner_alpha = rounding_alpha(inner_coords, inner_size, inner_radius); + alpha = outer_alpha * (1.0 - inner_alpha); + } + out_color = data.color * alpha; +} diff --git a/src/gfx_apis/vulkan/shaders/rounded_fill.vert b/src/gfx_apis/vulkan/shaders/rounded_fill.vert new file mode 100644 index 00000000..f04b3cfd --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/rounded_fill.vert @@ -0,0 +1,16 @@ +#version 450 + +#include "rounded_fill.common.glsl" + +layout(location = 0) out vec2 geo_pos; + +void main() { + vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex]; + gl_Position = vec4(pos, 0.0, 1.0); + switch (gl_VertexIndex) { + case 0: geo_pos = vec2(data.size.x, 0.0); break; + case 1: geo_pos = vec2(0.0, 0.0); break; + case 2: geo_pos = vec2(data.size.x, data.size.y); break; + case 3: geo_pos = vec2(0.0, data.size.y); break; + } +} diff --git a/src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl b/src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl new file mode 100644 index 00000000..9a07f0b0 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl @@ -0,0 +1,22 @@ +#extension GL_EXT_buffer_reference : require + +struct Vertex { + vec2 pos[4]; + vec2 tex_pos[4]; +}; + +layout(buffer_reference, buffer_reference_align = 8, std430) readonly buffer Vertices { + Vertex vertices[]; +}; + +layout(push_constant, std430) uniform Data { + layout(offset = 0) Vertices vertices; + layout(offset = 8) float mul; + layout(offset = 12) float size_x; + layout(offset = 16) float size_y; + layout(offset = 20) float corner_radius_tl; + layout(offset = 24) float corner_radius_tr; + layout(offset = 28) float corner_radius_br; + layout(offset = 32) float corner_radius_bl; + layout(offset = 36) float scale; +} data; diff --git a/src/gfx_apis/vulkan/shaders/rounded_tex.frag b/src/gfx_apis/vulkan/shaders/rounded_tex.frag new file mode 100644 index 00000000..0c506bc2 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/rounded_tex.frag @@ -0,0 +1,76 @@ +#version 450 + +#extension GL_EXT_scalar_block_layout : require + +#define TEX_SET 1 + +#include "frag_spec_const.glsl" +#include "rounded_tex.common.glsl" +#include "tex_set.glsl" +#include "eotfs.glsl" +#include "alpha_modes.glsl" + +layout(set = 0, binding = 0) uniform sampler sam; +layout(location = 0) in vec2 tex_pos; +layout(location = 1) in vec2 geo_pos; +layout(location = 0) out vec4 out_color; + +float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) { + if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) { + return 0.0; + } + vec2 center; + float radius; + if (coords.x < corner_radius.x && coords.y < corner_radius.x) { + radius = corner_radius.x; + center = vec2(radius, radius); + } else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) { + radius = corner_radius.y; + center = vec2(size.x - radius, radius); + } else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) { + radius = corner_radius.z; + center = vec2(size.x - radius, size.y - radius); + } else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) { + radius = corner_radius.w; + center = vec2(radius, size.y - radius); + } else { + return 1.0; + } + float half_px = 0.5 / data.scale; + float dist = distance(coords, center); + return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); +} + +void main() { + vec2 size = vec2(data.size_x, data.size_y); + vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl); + vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0); + if (eotf != inv_eotf || has_matrix || alpha_mode != AM_PREMULTIPLIED_ELECTRICAL) { + vec3 rgb = c.rgb; + if (src_has_alpha && alpha_mode == AM_PREMULTIPLIED_ELECTRICAL) { + rgb /= mix(c.a, 1.0, c.a == 0.0); + } + rgb = apply_eotf(rgb); + if (src_has_alpha && alpha_mode == AM_PREMULTIPLIED_OPTICAL) { + rgb /= mix(c.a, 1.0, c.a == 0.0); + } + if (has_matrix) { + rgb = (cm_data.matrix * vec4(rgb, 1.0)).rgb; + } + rgb = apply_inv_eotf(rgb); + if (src_has_alpha) { + rgb *= c.a; + } + c.rgb = rgb; + } + if (has_alpha_multiplier) { + if (src_has_alpha) { + c *= data.mul; + } else { + c = vec4(c.rgb * data.mul, data.mul); + } + } + float ra = rounding_alpha(geo_pos, size, corner_radius); + c *= ra; + out_color = c; +} diff --git a/src/gfx_apis/vulkan/shaders/rounded_tex.vert b/src/gfx_apis/vulkan/shaders/rounded_tex.vert new file mode 100644 index 00000000..feb79c00 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/rounded_tex.vert @@ -0,0 +1,19 @@ +#version 450 + +#include "rounded_tex.common.glsl" + +layout(location = 0) out vec2 tex_pos; +layout(location = 1) out vec2 geo_pos; + +void main() { + vec2 size = vec2(data.size_x, data.size_y); + Vertex vertex = data.vertices.vertices[gl_InstanceIndex]; + gl_Position = vec4(vertex.pos[gl_VertexIndex], 0.0, 1.0); + tex_pos = vertex.tex_pos[gl_VertexIndex]; + switch (gl_VertexIndex) { + case 0: geo_pos = vec2(size.x, 0.0); break; + case 1: geo_pos = vec2(0.0, 0.0); break; + case 2: geo_pos = vec2(size.x, size.y); break; + case 3: geo_pos = vec2(0.0, size.y); break; + } +} diff --git a/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.frag.spv b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..ad51d0dd64cde594ea64b1662491e18f1cf77fd0 GIT binary patch literal 6960 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 literal 0 HcmV?d00001 diff --git a/src/gfx_apis/vulkan/shaders_bin/rounded_fill.frag.spv b/src/gfx_apis/vulkan/shaders_bin/rounded_fill.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..bf2db619975df28374f5eaab3ace1708930383c3 GIT binary patch literal 6336 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 literal 0 HcmV?d00001 diff --git a/src/gfx_apis/vulkan/shaders_bin/rounded_fill.vert.spv b/src/gfx_apis/vulkan/shaders_bin/rounded_fill.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..58fbd453c074fcf76827978659a9996f23a40c1f GIT binary patch literal 2376 zcmZQ(Qf6mhU}WHC;AQY;fB-=TCI&_Z1_luQScHLri-D6NI3O(E+aoBxAfvK4GdVFQ zzPKd6C^0=XKB+VYeWdtmiVI7 zwA7;1yyR2{76vYmoV&ljyN_#pazR0SaYIQ#NoH9p$P5l7b(wj| zIi)Gc3RoFfP~5}HzzlX}L4GjBOL1mZDnyJOtd^O9fgw4+C@-}rz9=yzvlOI(jR7JD3Y(<- zqLkF4`0~t@k_?DGh`p=~3=GA|i8&y{SQ%Iu1Q{3@!cgpF0gHk3z}zp)z`&3Kbw4Wu zI|C@Z({tiI^NLFn^O93N^HNePKkki4RCEf=GeX zgZvE=2eCnN`Nf$fnfZAPAb)_wLE!-w&&(?cM)E&M4iqNoIq}XpnFTJH5O=`!fx-qP zSDKRpmjlHKD1CtJ0>ud^ousGc$AkRn&cF-~Q;?Y+Nb#c#Qp~`>022eb2P7tdBnHw0 z5|cm@1KAG}Q$P{}#R*6Zl%Qeef#L!rX2QV0;LgCpzz96{~@u|fJk@*o-} z2J$mV3`E1kKyCnuf&2**1F=D3Ab*1F28FL2$RGv=24My!aDG$-=S&7LTa1C30p>Sx za4rSq1Bjdg0}D6|*cliYKo}&?1q~aJ`RWYJ3?RFniZC$9F>rw64kQl}1E~kGL1u%n zC<6-vFEk86&e4YQ`4~X?nt?$E8tx!{ATe16W(H8WgW^Jtff<|^Kzxv!K<0z&1cfz- zFT}tA&gY=~Y|X&J0OEu43aEqv*((CI7Zi3NdqI3r21q#u(k}+p4>A`fF9~uaSRV_6 z6jUupAIN+V9~7q`^I&{XoPziu^W+&A7(ng=`2}Pi$lpp(GeG_Xr43M+fW+h&*ue1* z3oDQu2!qT~1(%@=V0n-|AU-JXfZ`ZrF31cGs5@bNO)$T~9;9Cvsvl%Fh;ItzgWLr& z55xzh514r%|CuvD;vQrk$lVrT^FV2zg~1XUP9S@A7(gWu1A`p{0|UqnAZ*6K3Qkj? zv;oovGWQ1q69XuJf$Rh2Cs3S#>~~~fWdMb{I5=H0Fo5F2mVpJFoY!pnM4u1Ni|a2Fkl2F;E-_Ft9U#!c>O=q820v@;@l-LH+`TyB@S21i1-h zCx{PH53&>FCw*vL`JI880aWgQ?E1~X%>WXI@j+!2$UZ}8oESml&zOOM0Tiwvc}E5| zaJ*PS!x0pJpg05B1BxeW1_lO@TmUq_Y#10AKw=>Gg4_j)e~{Qu1}3mOK<0w%gUNy7 z7g-ME2AG`=IP7#}U|;~r!PGe+sRN~XnA@Bg7#KiuFt@odFff4hg2Z9=gT#>S2l*Xl nt{V<>-5D4dKyom1J)mh6Bn~qdB!+A*D9rydFfv#$FfsrDC!)if literal 0 HcmV?d00001 diff --git a/src/gfx_apis/vulkan/shaders_bin/rounded_tex.frag.spv b/src/gfx_apis/vulkan/shaders_bin/rounded_tex.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..2bdd1fd3d7ebba2a94274472943b04e4b3f8b6b2 GIT binary patch literal 19700 zcmZQ(Qf6mhU}WHC;AIG5W&i_01||kZ1_lNY{aA#7fs28YAvho`-rFN6z96HrI5Rmh zC%(8OzbG+1H9n~{EiJW(ft!I1tjFCa*hjCpBt_4})Bt1{9|JSP6A_RN3=I4X91JWB z3}Bp_n3)HXf5ODT;Kt0r5X8*DAi%&3mSbmNU|?ooW?*7qU^v7GQqRud?i24C5dyU> zz9=;lb@U&pOaXbUs?jv%LS5m_xE@Aag9$dD2OjE zsmw`@&&kY7jZeueN=+`wEK3Ec1DgX?mzkHGQ<{RTfR%v->^7LYSsBLjL4IOoU}oTAU|>jQU|?Wn;9%fmU|`71D~rdamKUy;ok0+0W^sv; zfrUwYK_SFgkeLE-bsP*L$Yx?uD-2i5#vqPlrilTR9Wb@F}X~WfVG3dk0O3u$M$S=)Hi7&`6Pc4c!iZ_864E4VrTn{&c z5wh6?^clkSu`!r3Ffb$*6y#KbQwzj+P~4lq)vz;Iz}3J~6+)dkTpcHa69WT75y-un zdFkYW%_8|57`9&$kptQ=u;K0DZP@Gwn3Q5Oo431#=qP*0i z_@cy=%+g}0U%f$YFa+sgW$=~*iiRQ2k9?>xP1--14CYEE(6G~iy0UgQd0B4?CnV6`{3d%4B3p3 zG701#Q2f>~GB6Y*7A59_iC8-thATd@3R))1u zJ%})21B-$5A%_*n{Pj!>3@Oks1@X6n_?g9^tOySCBTT5}%qdVDA^RVkUYQwgfW)Ef z+e{1$AP0l;!hMkbL{Pq9VF0xuklpzL9KXf!xrrr3nH8WoXJvTB#J~V90^@V@Q&K@< z%na{A=_EY~T)un)nOBq?pOIJ$DJsEsvVa;QNco4IL716=!8t!CzsNT+FEKqeH#M&W zoOD3t2uKbT76^M;7(|&F7?N}2!9f8E2L)#2w93I?zzj-@P&?yuOLIyx3vx0+DHY;I zS7uOJ2C-Sd?WFY7d}w^IFu0?b#lql;#`i|yvx3`L`K2ZC$sqSLFfh0?FoVl(P#X3? zDzicLF~}b;{#cAag+aL2QtnApIa3CI(Uq5`)mV^u8w(}|asxEFv3<`5`1{Ma8dq8|px={f4a2UX1 zAaO+o76wiR2p<%;AoW%Zkhli<6Qmy02?41EiG$>ApzGq8Z&(zs~iIxxGVv&kugY}FSHB*wG*uwSQtQj zP0|Q75hC%KJl_MZKkTJ;pWl*z0@*wwv_@MFxZqQ#TLn|1f))y7>$Y3?MNW zhN)WuQg>mRBTU^=1_lO@7!1SIEd!}*mBnHDUb?X=y7_uLnfQQq11_lO@ z7!1SI?F5;ppbYo#E(Qh$kQfZZ+_Rs7fdQ0eU>K(VI7t77xlS;3Cm0wQKw>ZqQ+F1m zj-df=&p8GL29OvG!_-}5U|^7qdSVMR?-Bz814s;pVd}0hFfberOoNB(RR#tIkQfZZ z)Lmy_V0dD|2M>oE3=9k)F&KuayUoDBQ2%=u+}=A33=AMK7#3k*Vz|q|zyQjJ_ZS$! zY*4<2>313=AMK7>22P#lXOjmi7!DUauJ# z7(ik$3{&?3G`@3u7d#z)WME(biNP>T-8ZoN6yWLRI|Bm)NDPKy>VAOLF~I%tlYxN& zBnHE)Fm=3)3=CY?mErE@V`N|eiNP>T zoiL~{VNigZC&I|U01|^?m^xKP1_pCGe`}cg)EF5UKw>ZqQ>V`e3Rg9_c?OIO3?MNW zhN&}RWMFVy-VKi@V@3uBkQfZZ)LDVu=KxRt){G1cATbz*sk38bV0c&+2v=v%$iM&+ zgJGCDM@9w)|2IeA@$Lk22NH&)Fm-;63=Db8i{SqCXJlXiiNP>TT`(g9!?Sg&aPvYK85lrfFbq={$;iMEw8Iac z-l7;87(ik$3{w}&$iQIB)eg5ej*)=@BnHDUbt#Mt3^IDY@c2z-WMBY^!7!-)$zlZ6 zGmr#q#K6J;;)D9pAQ6~39gGYNXV37!A`fW%-J zW;N85tNrVlWId{|zX-Z*POEd&|hc01|^?kUEh2g+Y}QwB82s zrI--)C5WHN1ZjJ~)ORw0yE+UYKBzuh3ej(`&cMvDjERBasR#pu90NDFT@2!b)ZoJ) zb3pd2U;>S;GBC(6urPqyq@XqlNDRaWwMk)oIR;1{6r=_uF3Z5o0BU=J+Np93%;2^r zh!3idLH2{(18Q%A`0JP$z-=N>`x{h0gZQBSC#anSatEkAv4IKF76z%`2vrYqKTQ5K z)SWQ?IVMn_3(|f7>AwhV2gBsAFhSa2k_?;-H=%OiEDefOD2DMtcHV-7Cs-}WjUYa# zZ46Q>1*#*MKxvSL;T}}oT_y$wkU9__)FuY0lV)IMc*w*6HUnhlV`!X$!Vshu#0RyP zLH3$3uz~BE=S&O?t3h=-BLjGB#*l#-EDmZ%!_>TFg0uxdYC+<#Gzw~4gY1Q=dkr-U zBo0bnF!47~{UCEdYG8JN+Tt)ZpP3jKR)OjwCI$v%y%54hm+Fyckp- zq#ovOP`_m>59) z5|CO@yBaj+0cw|n_@Fj6Xv_nd4;t%$@j-2S(D(#0pMe>Y=0N&E@}RK>1#s`r3pz#41A7nphd;r9EWME|g<%y$A3=E)r18Pg#Lfikxm>3v9;xIMGp=vr4y` zAp1e)gY<*sLGA{{4@eHA50uC6FflNI@+^oC%AXII7#Ki#GmL>1oG%|SF))DCfW$#+ zKwErNIwO4B+uGe+CY4pXMVI0|Q75gh6Q#lx{&rfy@V`C6GMG{BIyZsQIA$ z_mzo(0VD=8AH)WQ9Vk70hnf$n8$fn~%F7>23=AM~n3|th)cj&%U;v44VPFN9HNTk{ z7(ikmzpi8ejj%E>{9$5X0EvV81t2p)=7ZAWUnU0dxLg3VpZ1Rlq86kcggY2OGj9wG z|CtyVKx#qqF#mzXeljq@{Q~k6%v?rhh`AuOpnL@4TSCoZVrBr(yMV+&@}RH+iGjk3 z4V00gVFfCCSeY3ZKw_Y<0C#A;;^vdW@caj zi6Ms-4>JP;NE{SaATwcp;)VJN7FK-B3=AN(FboSTer5&+kXn#D%zq#;P*}nI0-{0w z6-4qcs4fs-W?%q`f&2?%gZu(2GsT%1z+;=BemclKpz>3KnSlW$4&sC2QWEOEK=24F z1A`PZ0|Q75lr~{v(oiu_T7`+pK*d1G6qZM2nIZZ?>Ou0Lu$aie3eLZB%nS@5yFqGT zc|o3;fdM2A!XW!V=7Y?G`5zgA1&<9PU8q5p~ATf|S zn7tq|kRM=nfM}S0O{jjDomx;kL3J-IoVA%57(nVk;R7-gqy{7g)1w2`1FC;vdUTH)PcVS4nTdSKxXQUekLxgAv2fx-twgTl}VDGWjNupu)814s;{ z2E+!bF+oxT3TIDTtMZQ87QwaGcbVopfc1P z>RwPdgZLKA3=AMKkUEh6K;<$>4CGdjy&!jh?1JgHg6fB*J8NbJ29OvCgTfUQP9T57 z++>5rO}3!+3o`=)$PJ);W5>+E05S(84%26kMV|vR0|Q7L7S4{$3=AN>AbVkAPEawB zJ78kYP%)6ZVEM}hR8BE7Fo4v9tJF4jroJxW}rL{njZ(Lhw(x4 z)X03$Ts1NuG+zzkgWLk5T^K;?ZWtI~WxG3QjvWcZ#68f)*La|90Z;h&C<6n?ERcSX zT9DsB?ghz%(iUiJHHd)$JXZrN%Rylo%)r0^5(lXV`31y>sSksyN6uH_3=9k)agchD zUqO7B`Y5P+IhavL+Afq?-e4pI*aD-a(PRv@<~FfcHH z+z8`?CUQY;0P#U#nFI|>P&xqdLF;lrVjy)OH6Sxm7#P6o2Vmg}nkNN`!EhtA98Lqx z!-Cd>FfcIiFtCH$U766Z2Cbn1$%Dq0VdiErFff4J3o|#Hfq?-e2E)xzb3v1}Aag+S zFn5CH>_Orn|NUcNVgT`x(;{eH5qeq#ttA4LQTz;${v#+Yf+k%-{)VMR&>93}K4={R zG9R=S0mKLS0Yn!tFff43h54ZnDIJ2^DKK%+gfysLhGAG*1WllW${*008jx8a{UEiV zxCEICl84281p@)u(a68z`y{~50Zza z#U7-z2$BcQL%_`KWnf?cxff<`9|HpeNDPKyX|bPyfdOO=NFL_S2@DJjAaRiYKxq-g zM^1~Nb!+Hp5j3X;N{d3!vPL445q7GyqXjSGkmax;jY%m7*!h$siA z;7EH@ag>A8py2^Zdmytw`axpATe0@Enr|^0EvO-Y+zx%5Y62%I~GCh0If>{=>w?)*#Qa{kQ~g86;L}`q2=KEoGo?&2M0ExrG={y4i14s-sPX|+T0jdV(ugeS!3?MPk934#GH3kL- zkT_^Q4(8UINNxqmgXZ91X5NCDxsQRB0W`06pMik^BnFy)1C23&($oV61_qEg$Q+Qn zK>h@YA^Q^)*XaHPt#<|aQ?8 zNDed?2h;zSfq?-uhX+yv%fIg!7#Kj}pm2ei`5tN}%%7ha7#Kiep!qtOKR+`tFo48C z`a$jj`4c3D>`&0TU37nf)|7z!30j)~v+oxJXsn)r0p_OP;IR)#I)u6X4+8@OXnqeQ z2lMM+B)@{@2tjHlB0EvUb1D3wIL1XQpHYaF~3c8+y2Wkf_9{Cv=7(im6`Ab+l3NSJ-fW$%iLGz@b z@B@h4v;vqJ3wo((c=-c{u<;Cb)`;N)0gFd5wZU*TAsRQ`|WF|-s z<_8<7c`$d{GBPlL=FdUmusC)A zgwFt(JB5v9fEMe6{0Ul&5A#DbC>?Pcx*pDQK-cD12dUQ_xy@WIkxEJTf1&RvwuTS}Tvt2d$My z=7ZMCgZQ8^%t}y)n1O-eH-j{I&3*+mymA;J^Od=bkh#V@@ct(ThI~+c!N9;!zzCU_ zE(Fy@p#2@7atyS#8&nU0`ktWmF$@e0rHl;V{Yhn@^)aA6EF)xm6l4!9e0MW4Fo41V z6lNecC_F&+!{n=wm_1X_?3oHR59A(@98BFb zGOk%R$-&$+3n~tB4@eDYZ41mjvl$r}K<0zgfZPM(gWOXDDukKrk4nWK1gV3@WvNx51;V`sp zJ_0S9k3!4lW6-ksIJ9g&0WF(PLd&*O(6ae7v}^|114{$^ObiU5Z~%oLhz$x4ko_?E zbD(vL43O{zu|e`6cfsT@Lghj60}4A39~M8CK<$1|9~>G#AU;Sx$UiXsSCRCC!WzVf z>A%Lv0N&FI5(l{t#0TjIg#%3g4XA!tTE7VmLr@w8$-&g!LQ{7est%OaL2|J4dJifN zO0OU_p!IRE^m?C>fdOPbNDat6AU?=Fp!k5<^AOFRM^N)X?g7cc)ICO1_XMgAcD*8KLjsf9EG{{qW`g1p zWIjk8EG{`g>o!4a8JQq?AH)aw1yqK>+{goUBPcFGY*InljLSVHg{%3@x)k^G=|Bn4mHh zRAz(pg7#s;_#k=EK1^gjXpbO>4+=w&ya*Em1IRwmngx*GKyDIcVqgG??|`l?5@TXu z0EvO-p;j=kfY%#|GchoL#6e}bBxsTwJch->01}sAVqgG?f!qpW!|alV+67wc0JBSm ziGcwm4zo*^iGcwm2C@q@{|z$-M1N&q2iq+NwHp);AoU3v9@*uZsA-NqSuF1r}01^Ya9mIyYOB?ETMz!CL ziGcwm4zu5$iGcy64JD6P{8WwgAObiSlw}AW)QV((m$gNIDZUw1vWMW|OgoYi6 z4Rfmt7I(OU_Ea-5Fo4P+P&okN!@|g&iGcwm23s%c!NkA-5(8n7dQdq55(BvxVqgG?!_@dd(-pEBUnT|ykT^_@AJpB*VeHSuzyJ~ljZ1*an*b&T z29Q3GI4nH|GBGfK>;uVx!YLRjoIr9xObiSlF_61KY?yr^PF z29P*RO%~Wp1_sbNHkjM8nHU&A;xPB;fcBX)F))C{VQC|miGcxRA4m=q-uX!34U)@a zVqgG?f!qyZ!|W@7x*Iud6oSq*0J{U?w<4&!k<}D~_OK(VDFL%l-CYXWH_rr_(*dRF zGSFUkCI$wOILzJUObiSl`#^G_u!Xr1M8ncl1rq}U$Pb|K2B`BtL@GR5CFz zv_aDphz;{&H5RwkFflNI#6fWjN>d;{OnoiXzsPY}$Hc$@5{Jb}Jv6<*;-rCzfdQlj zBn~sD5o!){xHT~`Fo48i<}{<3)565S08#@Iho!|IaV3=AM~Sh!DRVqgI2 z1Bt`Z)HEgr29SLqIZznE+z6s!X=*wX0|Uqppl}1J2e}{Q$C*fe1gV+9#K5o!nx;T( znA>JyaocPr1_qEgD2_mB3dD!S^BibA!}QO^;{SO}3=AM~n4bAiJuts6U}9hZsR4<@ z(#Aq21_qGXAUTk|ptJ!J1Nj@|7f{@Q+z)DRfYwKW_#knRJS^>iXb>M(u7PL}A65r~ zXpp->Yg;!#_ceg_HNo7unTde`Bo3Mb0p;^8ObiU5eOw@Mkefj5ZIBp9J!q}wHfZ{Q z@wbD{;$niVYXj-u0quu?+OiJPF9meM}4tAT=Oyn4bMi3=AN;UNjse##h T5ab^Q1_qEiP`HEmpm_lRc@^eO literal 0 HcmV?d00001 diff --git a/src/gfx_apis/vulkan/shaders_bin/rounded_tex.vert.spv b/src/gfx_apis/vulkan/shaders_bin/rounded_tex.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..3ac5e409c43256c0478bcc275bc23c1d22e41b6a GIT binary patch literal 3580 zcmZQ(Qf6mhU}WHC;AJRcfB-=TCI&_Z1_luQScHLri-D6NI3O(E+aoBxAfvK4GdVFQ zzPKd6C^0=XKB+VYeWdt zrud@NwA7;1yyR2{76vYmoV&ljyN_#pazR0SaYIQ#NoH9p$P5l7 zb(wj|Ii)Gc3RoFfP}~IKb22b66lYeYg4_t=b2Bh7xFnV&g7|CdF|dHeL1C9$n!~`xzzXJr`~os7zJdWR#tbz-z7izH0agbJm*o7SywsxjqQsQU z(&G4%9EiCPc~%An1~hqal(8{DLflThT@G3BA=L)^&$a#M0*4#?9WKMOE0FodNR zm862*&B6fUgWOY)UkvsKh!65>NoqwrNQ{+%l|cxq2N5Q0U@?$BuC3ACOuE^A#xUA>tr=KyvxTnI)O|c?@g}>|pgE`@rIvc_qQn^Z=6s z#cO&_ymL-wflDUDKDa(m{DS04b8_HvtPITHv<|WhmI0oucP&k0{0U}IU zz>CCJ?%F;JYs)PeMX#CVX@f!qla1H~mw z9Vi`w#6a-_69c6^keCFLUQl`iiOC>|fzlXAOaVy@+-ds6GdM4%4p$3IwpdEDWG@19A(<-5~X943IhvYn^FVfi_`y&< z$S)xCKzvYn1v4MS2c=~YA7mcLAMs%GK=H@IkN^z_ki9w#pc0#b0aV|B+yKI646NYv z4hjd5Hjp_#7?>D9v(|F))MECrAur1}Hs%$}^A|pmYK< z1C%yY85kHq;vhaKZD=qsfa@ej1~vvzngHd0kQ$I4ken?88@OHtl|djmP<{saF_3{3 zTu$mRFff4RKw$y07nF|l7#P6y9>@%kI*{M>85kHqYGLX?W`G)CAhjTMAoqj#AayW3 zhEP4Av;Z<2q{ov_T9*{d>dQ72uK;aG21LA|!!St9x^?>3M zrpFwr2NeDwJs>_v9Y_zT?683P8|F5Uzb&Eu2DuHS4&-ku1_lO@T4aA)GcYiK)WZA? z;)B$|^w>c4z|02u+ZL(^WH!h>AU;SPOphH@53;}Qp?YBc2Ju1aV0u9H63A`H{&s}w zf%zN62dRUl7f*1#$-n?gFCce$LH!K!3rG&64&-ld1_lO@9LO$EUh-pLU;v4M!T~1c z4;2IXGk}4e0hEUW7#J8pYC&?K{07SFpf~~L^B{0t#J~Wu3ls(*K1e;tPEcA7MRQvi z)NPs_Z zFA7?|M?=e;7zPFgP+0(y2e}zkmVg@Mpz;7z#(?Yqg+&qr0|Q7LmXDJe7#KieApe5g z1S+3EVm}#}z-|DU39<_&2P%({`RB%GsyO3fZCM|pg3a%mv5O+HK4K@=B6wL z1_qEg%uU%03=ANBAaR(zATeZnLH>o=n~TLvPzMSm4l^^Kfq?NK M%fQHB!NAA>0M=_JjsO4v literal 0 HcmV?d00001 diff --git a/src/gfx_apis/vulkan/shaders_hash.txt b/src/gfx_apis/vulkan/shaders_hash.txt index ab855a2d..1923f498 100644 --- a/src/gfx_apis/vulkan/shaders_hash.txt +++ b/src/gfx_apis/vulkan/shaders_hash.txt @@ -7,12 +7,24 @@ f93524fd077bc9984702b1e0f92232f80bfe28a0a92439dc164c1ea41fd16d64 src/gfx_apis/vu c315a064b48dd5bdb607a6b79c30d31b6e59ffec69e93d50ab875abf97c41bbf src/gfx_apis/vulkan/shaders/legacy/fill.common.glsl 590d061b97446fc501158609eaf098b71bc7b328c008b586ff36613ce690d618 src/gfx_apis/vulkan/shaders/legacy/fill.frag ad22a79e1a88a12daa40c0a2b953084c129a408297c8ca544d60e0b6001470b9 src/gfx_apis/vulkan/shaders/legacy/fill.vert +b77838c0aac9ec90ae76cd0d94d3891d72d9a30b09ce77009afd9f4e567dd042 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl +fa39734aea1c96960f5dc95b999ae2fa5576ecf4b527fd70ee0f643c8ddcc452 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag +c1914cc00fb4827f65cd55bd0737d159fe44a098a3085a500822fc91cc2bfcad src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert +bd249cf170b72cd833e92a7719e88da0a91e563956579707e693679b443d73d5 src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl +28f3249e0d974a332b2926fb7565930627a093d6ac21ca17f2bf191740d299bd src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag +6ef0bde549dc163cd08f68d975071f5d74213c07ccc4a06b30c6f179b2f848ae src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert e0a8769dd7938dd02e66db9e9048ed6bef8f8c42671f2e2c7a7976a6d498f685 src/gfx_apis/vulkan/shaders/legacy/tex.common.glsl 0e7c72ea11671065842c8b4ad4131a7df33b427dc0ea76bf5a896546f6636cb0 src/gfx_apis/vulkan/shaders/legacy/tex.frag 4402f7ccdbb9fb52fb6cda3aab13cf89e2980c79b541f8be0463efd64a5f98ed src/gfx_apis/vulkan/shaders/legacy/tex.vert 3ba5d05c2b95099e5424b3ade5d1c31d431f5730b1d0b51a9fb5f8afc4ea14b4 src/gfx_apis/vulkan/shaders/out.common.glsl 5069f619c7d722815a022e2d84720a2d8290af49a3ed49ea0cd26b52115cc39a src/gfx_apis/vulkan/shaders/out.frag 0adc7e12328c15fb3e7e6c8b8701a182223c2f15337e14131f41dd247e697809 src/gfx_apis/vulkan/shaders/out.vert +9202d5c9fc4ce0d5f40ed147f245bd037728c9e060ea46a0f0a1767ca55e6c48 src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl +9085625d2afb1365685ae79a58108bf6566573ed94d9913397cf74dc6ef9b6e8 src/gfx_apis/vulkan/shaders/rounded_fill.frag +7665319a706e514f125d80f51f10b643f01cdae54d8a6ea56c218f78de7c0ecb src/gfx_apis/vulkan/shaders/rounded_fill.vert +dd100d048c0b380c913cffd7ac48fed3a341b3cb052302a11c369967f38aba9a src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl +454f34754ea4102190821c2d168dedd8c6bf624f1712b6136d902428f801a1e9 src/gfx_apis/vulkan/shaders/rounded_tex.frag +21b18ba369b505b9aedb8cf2e7e31bc417f6704fd2daac353b0db52f9ae44c70 src/gfx_apis/vulkan/shaders/rounded_tex.vert e22d4d3318a350def8ef19c7b27dc6a308a84c2fe9d7c02b81107f72073cd481 src/gfx_apis/vulkan/shaders/tex.common.glsl 1f196cee646a934072beb3e5648a5042c035953d9a0c26b0a22e330c2f8bb994 src/gfx_apis/vulkan/shaders/tex.frag 423cf327c9fcc4070dbf75321c1224a1589b6cf3d2f1ea5e8bd0362e1a9f3aa1 src/gfx_apis/vulkan/shaders/tex.vert diff --git a/src/icons.rs b/src/icons.rs index 22afe687..f4537352 100644 --- a/src/icons.rs +++ b/src/icons.rs @@ -1,4 +1,5 @@ #![allow(clippy::excessive_precision)] +#![allow(dead_code)] use { crate::{ @@ -22,12 +23,14 @@ pub struct Icons { icons: CopyHashMap>>, } +#[allow(dead_code)] #[derive(Copy, Clone, Debug, Linearize)] pub enum IconState { Active, Passive, } +#[allow(dead_code)] pub struct SizedIcons { pub pin_unfocused_title: StaticMap>, pub pin_focused_title: StaticMap>, diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index ec52f374..c3014a38 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -1320,6 +1320,7 @@ impl WlSeatGlobal { .start_drag(self, origin, source, icon, serial) } + #[allow(dead_code)] pub fn start_tile_drag(self: &Rc, tl: &Rc) { if self.state.ui_drag_enabled.get() { self.pointer_owner.start_tile_drag(self, tl); diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index a946a8a5..d1133c46 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -215,6 +215,7 @@ impl PointerOwnerHolder { } } + #[allow(dead_code)] pub fn start_tile_drag(&self, seat: &Rc, tl: &Rc) { self.owner.get().start_tile_drag(seat, tl); } @@ -288,6 +289,7 @@ trait PointerOwner { fn disable_window_management(&self, seat: &Rc) { let _ = seat; } + #[allow(dead_code)] fn start_tile_drag(&self, seat: &Rc, tl: &Rc) { let _ = seat; let _ = tl; @@ -732,6 +734,7 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static { let _ = seat; } + #[allow(dead_code)] fn start_tile_drag( &self, grab: &SimpleGrabPointerOwner, diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 24d0fc7c..532eedd6 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -528,6 +528,8 @@ impl GfxFramebuffer for TestGfxFb { GfxApiOpt::Sync => {} GfxApiOpt::FillRect(f) => fill_rect(&f, staging), GfxApiOpt::CopyTexture(c) => copy_texture(&c, staging)?, + GfxApiOpt::RoundedFillRect(_) => {} + GfxApiOpt::RoundedCopyTexture(_) => {} } } copy_from_staging(staging); diff --git a/src/renderer.rs b/src/renderer.rs index 6875f253..0b508287 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -2,7 +2,6 @@ use { crate::{ cmm::cmm_render_intent::RenderIntent, gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect}, - icons::{IconState, SizedIcons}, ifs::wl_surface::{ SurfaceBuffer, WlSurface, x_surface::xwindow::Xwindow, @@ -13,7 +12,7 @@ use { renderer::renderer_base::RendererBase, scale::Scale, state::State, - theme::Color, + theme::{Color, CornerRadius}, tree::{ ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData, ToplevelNodeBase, WorkspaceNode, @@ -29,8 +28,8 @@ pub struct Renderer<'a> { pub state: &'a State, pub logical_extents: Rect, pub pixel_extents: Rect, - pub icons: Option>, pub stretch: Option<(i32, i32)>, + pub corner_radius: Option, } impl Renderer<'_> { @@ -283,60 +282,12 @@ impl Renderer<'_> { let srgb = &srgb_srgb.linear; let perceptual = RenderIntent::Perceptual; let rd = container.render_data.borrow_mut(); - let c = self.state.theme.colors.unfocused_title_background.get(); - self.base - .fill_boxes2(&rd.title_rects, &c, srgb, perceptual, x, y); - let c = self.state.theme.colors.focused_title_background.get(); - self.base - .fill_boxes2(&rd.active_title_rects, &c, srgb, perceptual, x, y); - let c = self.state.theme.colors.attention_requested_background.get(); - self.base - .fill_boxes2(&rd.attention_title_rects, &c, srgb, perceptual, x, y); - let c = self.state.theme.colors.separator.get(); - self.base - .fill_boxes2(&rd.underline_rects, &c, srgb, perceptual, x, y); let c = self.state.theme.colors.border.get(); self.base .fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y); - if let Some(lar) = &rd.last_active_rect { - let c = self - .state - .theme - .colors - .focused_inactive_title_background - .get(); - self.base - .fill_boxes2(std::slice::from_ref(lar), &c, srgb, perceptual, x, y); - } - if let Some(titles) = rd.titles.get(&self.base.scale) { - for title in titles { - let rect = title.rect.move_(x, y); - let bounds = self.base.scale_rect(rect); - let (x, y) = self.base.scale_point(rect.x1(), rect.y1()); - self.base.render_texture( - &title.tex, - None, - x, - y, - None, - None, - self.base.scale, - Some(&bounds), - None, - AcquireSync::None, - ReleaseSync::None, - false, - srgb_srgb, - perceptual, - AlphaMode::PremultipliedElectrical, - ); - } - } } pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) { - let floating = self.state.theme.floating_titles.get(); - self.render_container_decorations(container, x, y); if let Some(child) = container.mono_child.get() { @@ -355,44 +306,36 @@ impl Renderer<'_> { let full_w = mb.width(); let srgb = &srgb_srgb.linear; let perceptual = RenderIntent::Perceptual; - if floating { - if !child.node.node_is_container() { - let body_frame = [ - Rect::new_sized_saturating(mb.x1() - bw, mb.y1(), bw, mb.y2() - mb.y1()), - Rect::new_sized_saturating(mb.x2(), mb.y1(), bw, mb.y2() - mb.y1()), - Rect::new_sized_saturating(mb.x1() - bw, mb.y1() - bw, full_w + 2 * bw, bw), - Rect::new_sized_saturating(mb.x1() - bw, mb.y2(), full_w + 2 * bw, bw), - ]; - self.base.fill_boxes2(&body_frame, c, srgb, perceptual, x, y); - } - let th = self.state.theme.title_height(); - if th > 0 { - for tab in container.children.iter() { - let tr = tab.title_rect.get(); - let tc = if tab.active.get() { - &focused_border_color - } else { - &border_color - }; - let tw = tr.width(); - let tab_frame = [ - Rect::new_sized_saturating(tr.x1() - bw, tr.y1() - bw, tw + 2 * bw, bw), - Rect::new_sized_saturating(tr.x1() - bw, tr.y1(), bw, th), - Rect::new_sized_saturating(tr.x2(), tr.y1(), bw, th), - Rect::new_sized_saturating(tr.x1() - bw, tr.y1() + th, tw + 2 * bw, bw), - ]; - self.base.fill_boxes2(&tab_frame, tc, srgb, perceptual, x, y); - } - } - } else if !child.node.node_is_container() { + if !child.node.node_is_container() { + let cr = self.state.theme.corner_radius.get(); let full_h = mb.y2(); - let frame_rects = [ - Rect::new_sized_saturating(mb.x1() - bw, 0, bw, full_h), - Rect::new_sized_saturating(mb.x2(), 0, bw, full_h), - Rect::new_sized_saturating(mb.x1() - bw, -bw, full_w + 2 * bw, bw), - Rect::new_sized_saturating(mb.x1() - bw, full_h, full_w + 2 * bw, bw), - ]; - self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y); + if cr.is_zero() { + let frame_rects = [ + Rect::new_sized_saturating(mb.x1() - bw, 0, bw, full_h), + Rect::new_sized_saturating(mb.x2(), 0, bw, full_h), + Rect::new_sized_saturating(mb.x1() - bw, -bw, full_w + 2 * bw, bw), + Rect::new_sized_saturating(mb.x1() - bw, full_h, full_w + 2 * bw, bw), + ]; + self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y); + } else { + let outer = Rect::new_sized_saturating( + mb.x1() - bw, + -bw, + full_w + 2 * bw, + full_h + 2 * bw, + ); + let scalef = self.base.scalef as f32; + let scaled_cr = cr.scaled_by(scalef); + self.base.fill_rounded_rect( + outer.move_(x, y), + c, + None, + srgb, + perceptual, + scaled_cr, + bw as f32 * scalef, + ); + } } } let body = mb.move_(x, y); @@ -403,28 +346,36 @@ impl Renderer<'_> { } else { None }; + if self.state.theme.sizes.gap.get() != 0 && !child.node.node_is_container() { + let cr = self.state.theme.corner_radius.get(); + if !cr.is_zero() { + let scalef = self.base.scalef as f32; + let bw = self.state.theme.sizes.border_width.get(); + let inner_cr = cr.expanded_by(-(bw as f32)).scaled_by(scalef); + self.corner_radius = Some(inner_cr); + } + } child .node .node_render(self, x + content.x1(), y + content.y1(), Some(&body)); self.stretch = None; } else { let gap = self.state.theme.sizes.gap.get(); - let (srgb_srgb, bw, border_color, focused_border_color, tpuh) = if gap != 0 { + let (srgb_srgb, bw, border_color, focused_border_color) = if gap != 0 { let srgb_srgb = self.state.color_manager.srgb_gamma22(); let bw = self.state.theme.sizes.border_width.get(); let border_color = self.state.theme.colors.border.get(); let focused_border_color = self.state.theme.colors.focused_title_background.get(); - let tpuh = self.state.theme.title_plus_underline_height(); ( Some(srgb_srgb), bw, border_color, focused_border_color, - tpuh, ) } else { - (None, 0, Color::SOLID_BLACK, Color::SOLID_BLACK, 0) + (None, 0, Color::SOLID_BLACK, Color::SOLID_BLACK) }; + let cr = self.state.theme.corner_radius.get(); for child in container.children.iter() { let body = child.body.get(); if body.x1() >= container.width.get() || body.y1() >= container.height.get() { @@ -432,45 +383,42 @@ impl Renderer<'_> { } if let Some(srgb_srgb) = srgb_srgb { let srgb = &srgb_srgb.linear; - let c = if floating { - if child.active.get() { &focused_border_color } else { &border_color } - } else if child.border_color_is_focused.get() { + let c = if child.border_color_is_focused.get() { &focused_border_color } else { &border_color }; - let title_rect = child.title_rect.get(); - let full_w = body.width(); - let perceptual = RenderIntent::Perceptual; - if floating && tpuh > 0 { - let tw = title_rect.width(); - let title_h = title_rect.height(); - let title_frame = [ - Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1() - bw, tw + 2 * bw, bw), - Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1(), bw, title_h), - Rect::new_sized_saturating(title_rect.x2(), title_rect.y1(), bw, title_h), - Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1() + title_h, tw + 2 * bw, bw), - ]; - self.base.fill_boxes2(&title_frame, c, srgb, perceptual, x, y); - if !child.node.node_is_container() && gap != 0 { - let body_frame = [ - Rect::new_sized_saturating(body.x1() - bw, body.y1(), bw, body.y2() - body.y1()), - Rect::new_sized_saturating(body.x2(), body.y1(), bw, body.y2() - body.y1()), + if !child.node.node_is_container() && gap != 0 { + let full_w = body.width(); + let perceptual = RenderIntent::Perceptual; + let full_h = body.height(); + if cr.is_zero() { + let frame_rects = [ + Rect::new_sized_saturating(body.x1() - bw, body.y1(), bw, full_h), + Rect::new_sized_saturating(body.x2(), body.y1(), bw, full_h), Rect::new_sized_saturating(body.x1() - bw, body.y1() - bw, full_w + 2 * bw, bw), Rect::new_sized_saturating(body.x1() - bw, body.y2(), full_w + 2 * bw, bw), ]; - self.base.fill_boxes2(&body_frame, c, srgb, perceptual, x, y); + self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y); + } else { + let outer = Rect::new_sized_saturating( + body.x1() - bw, + body.y1() - bw, + full_w + 2 * bw, + full_h + 2 * bw, + ); + let scalef = self.base.scalef as f32; + let scaled_cr = cr.scaled_by(scalef); + self.base.fill_rounded_rect( + outer.move_(x, y), + c, + None, + srgb, + perceptual, + scaled_cr, + bw as f32 * scalef, + ); } - } else if !child.node.node_is_container() && gap != 0 { - let top_y = if tpuh > 0 { title_rect.y1() } else { body.y1() }; - let full_h = body.y2() - top_y; - let frame_rects = [ - Rect::new_sized_saturating(body.x1() - bw, top_y, bw, full_h), - Rect::new_sized_saturating(body.x2(), top_y, bw, full_h), - Rect::new_sized_saturating(body.x1() - bw, top_y - bw, full_w + 2 * bw, bw), - Rect::new_sized_saturating(body.x1() - bw, body.y2(), full_w + 2 * bw, bw), - ]; - self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y); } } let content = child.content.get(); @@ -479,6 +427,11 @@ impl Renderer<'_> { } else { None }; + if !cr.is_zero() && !child.node.node_is_container() && gap != 0 { + let scalef = self.base.scalef as f32; + let inner_cr = cr.expanded_by(-(bw as f32)).scaled_by(scalef); + self.corner_radius = Some(inner_cr); + } let body = body.move_(x, y); let body = self.base.scale_rect(body); child @@ -642,28 +595,49 @@ impl Renderer<'_> { let cd = surface.color_description(); let intent = surface.render_intent(); let alpha_mode = surface.alpha_mode(); + let corner_radius = self.corner_radius.take(); if let Some(tex) = buf.get_texture(surface) { - let mut opaque = surface.opaque(); - if !opaque && tex.format().has_alpha { - opaque = self.bounds_are_opaque(x, y, bounds, surface); + if let Some(cr) = corner_radius { + self.base.render_rounded_texture( + &tex, + alpha, + x, + y, + Some(tpoints), + Some(tsize), + self.base.scale, + bounds, + Some(buffer.clone()), + AcquireSync::Unnecessary, + buffer.release_sync, + &cd, + intent, + alpha_mode, + cr, + ); + } else { + let mut opaque = surface.opaque(); + if !opaque && tex.format().has_alpha { + opaque = self.bounds_are_opaque(x, y, bounds, surface); + } + self.base.render_texture( + &tex, + alpha, + x, + y, + Some(tpoints), + Some(tsize), + self.base.scale, + bounds, + Some(buffer.clone()), + AcquireSync::Unnecessary, + buffer.release_sync, + opaque, + &cd, + intent, + alpha_mode, + ); } - self.base.render_texture( - &tex, - alpha, - x, - y, - Some(tpoints), - Some(tsize), - self.base.scale, - bounds, - Some(buffer.clone()), - AcquireSync::Unnecessary, - buffer.release_sync, - opaque, - &cd, - intent, - alpha_mode, - ); } else if let Some(color) = &buf.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { let rect = match bounds { @@ -691,128 +665,60 @@ impl Renderer<'_> { }; let pos = floating.position.get(); let theme = &self.state.theme; - let th = theme.title_height(); - let tpuh = theme.title_plus_underline_height(); - let tuh = theme.title_underline_height(); let bw = theme.sizes.border_width.get(); let bc = if floating.active.get() { theme.colors.focused_title_background.get() } else { theme.colors.border.get() }; - let tc = if floating.active.get() { - theme.colors.focused_title_background.get() - } else if floating.attention_requested.get() { - theme.colors.attention_requested_background.get() - } else { - theme.colors.unfocused_title_background.get() - }; - let uc = theme.colors.separator.get(); - let gap = theme.sizes.gap.get(); - let floating_title = theme.floating_titles.get() && gap != 0; let srgb_srgb = self.state.color_manager.srgb_gamma22(); let srgb = &srgb_srgb.linear; let perceptual = RenderIntent::Perceptual; - if floating_title { - let title_frame = [ - Rect::new_sized_saturating(x, y, pos.width(), bw), - Rect::new_sized_saturating(x, y + bw, bw, th), - Rect::new_sized_saturating(x + pos.width() - bw, y + bw, bw, th), - Rect::new_sized_saturating(x, y + bw + th, pos.width(), bw), - ]; - self.base.fill_boxes(&title_frame, &bc, srgb, perceptual); - let title_bg = [Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, th)]; - self.base.fill_boxes(&title_bg, &tc, srgb, perceptual); - let body_top = y + tpuh; - let body_inner_top = y + bw + tpuh; - let body_inner_height = pos.height() - 2 * bw - tpuh; - let body_frame = [ - Rect::new_sized_saturating(x, body_top, pos.width(), bw), - Rect::new_sized_saturating(x, body_inner_top, bw, body_inner_height), - Rect::new_sized_saturating(x + pos.width() - bw, body_inner_top, bw, body_inner_height), - Rect::new_sized_saturating(x, y + pos.height() - bw, pos.width(), bw), - ]; - self.base.fill_boxes(&body_frame, &bc, srgb, perceptual); - } else { + let cr = theme.corner_radius.get(); + if cr.is_zero() { let borders = [ Rect::new_sized_saturating(x, y, pos.width(), bw), Rect::new_sized_saturating(x, y + bw, bw, pos.height() - bw), - Rect::new_sized_saturating(x + pos.width() - bw, y + bw, bw, pos.height() - bw), - Rect::new_sized_saturating(x + bw, y + pos.height() - bw, pos.width() - 2 * bw, bw), + Rect::new_sized_saturating( + x + pos.width() - bw, + y + bw, + bw, + pos.height() - bw, + ), + Rect::new_sized_saturating( + x + bw, + y + pos.height() - bw, + pos.width() - 2 * bw, + bw, + ), ]; self.base.fill_boxes(&borders, &bc, srgb, perceptual); - let title = [Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, th)]; - self.base.fill_boxes(&title, &tc, srgb, perceptual); - let title_underline = [Rect::new_sized_saturating(x + bw, y + bw + th, pos.width() - 2 * bw, tuh)]; - self.base.fill_boxes(&title_underline, &uc, srgb, perceptual); - } - let rect = floating.title_rect.get().move_(x, y); - let bounds = self.base.scale_rect(rect); - let (mut x1, y1) = rect.position(); - let is_pinned = floating.pinned_link.borrow().is_some(); - if is_pinned || self.state.show_pin_icon.get() { - let (x, y) = self.base.scale_point(x1, y1); - if let Some(icons) = &self.icons { - let icon = if floating.active.get() { - &icons.pin_focused_title - } else if floating.attention_requested.get() { - &icons.pin_attention_requested - } else { - &icons.pin_unfocused_title - }; - let state = match is_pinned { - true => IconState::Active, - false => IconState::Passive, - }; - self.base.render_texture( - &icon[state], - None, - x, - y, - None, - None, - self.base.scale, - Some(&bounds), - None, - AcquireSync::None, - ReleaseSync::None, - false, - srgb_srgb, - perceptual, - AlphaMode::PremultipliedElectrical, - ); - } - x1 += th; - } - if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) - && let Some(texture) = title.texture() - { - let (x, y) = self.base.scale_point(x1, y1); - self.base.render_texture( - &texture, + } else { + let outer = Rect::new_sized_saturating(x, y, pos.width(), pos.height()); + let scalef = self.base.scalef as f32; + let scaled_cr = cr.scaled_by(scalef); + self.base.fill_rounded_rect( + outer, + &bc, None, - x, - y, - None, - None, - self.base.scale, - Some(&bounds), - None, - AcquireSync::None, - ReleaseSync::None, - false, - srgb_srgb, + srgb, perceptual, - AlphaMode::PremultipliedElectrical, + scaled_cr, + bw as f32 * scalef, ); } let body = Rect::new_sized_saturating( x + bw, - y + bw + tpuh, + y + bw, pos.width() - 2 * bw, - pos.height() - 2 * bw - tpuh, + pos.height() - 2 * bw, ); let scissor_body = self.base.scale_rect(body); + if !cr.is_zero() { + let scalef = self.base.scalef as f32; + let inner_cr = cr.expanded_by(-(bw as f32)).scaled_by(scalef); + self.corner_radius = Some(inner_cr); + } child.node_render(self, body.x1(), body.y1(), Some(&scissor_body)); } diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index af1ae926..a549126f 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -6,11 +6,11 @@ use { }, gfx_api::{ AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, - GfxTexture, ReleaseSync, SampleRect, + GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SampleRect, }, rect::Rect, scale::Scale, - theme::Color, + theme::{Color, CornerRadius}, tree::Transform, }, std::rc::Rc, @@ -250,6 +250,123 @@ impl RendererBase<'_> { })); } + pub fn fill_rounded_rect( + &mut self, + rect: Rect, + color: &Color, + alpha: Option, + cd: &Rc, + render_intent: RenderIntent, + corner_radius: CornerRadius, + border_width: f32, + ) { + if *color == Color::TRANSPARENT { + return; + } + let rect = self.scale_rect(rect); + let width = (rect.x2() - rect.x1()) as f32; + let height = (rect.y2() - rect.y1()) as f32; + let scale = self.scalef as f32; + let fitted = corner_radius.fit_to(width, height); + let cr: [f32; 4] = fitted.into(); + self.ops + .push(GfxApiOpt::RoundedFillRect(RoundedFillRect { + rect: FramebufferRect::new( + rect.x1() as f32, + rect.y1() as f32, + rect.x2() as f32, + rect.y2() as f32, + self.transform, + self.fb_width, + self.fb_height, + ), + color: *color, + alpha, + render_intent, + cd: cd.clone(), + size: [width, height], + corner_radius: cr, + border_width, + scale, + })); + } + + pub fn render_rounded_texture( + &mut self, + texture: &Rc, + alpha: Option, + x: i32, + y: i32, + tpoints: Option, + tsize: Option<(i32, i32)>, + tscale: Scale, + bounds: Option<&Rect>, + buffer_resv: Option>, + acquire_sync: AcquireSync, + release_sync: ReleaseSync, + cd: &Rc, + render_intent: RenderIntent, + alpha_mode: AlphaMode, + corner_radius: CornerRadius, + ) { + let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); + + let (twidth, theight) = if let Some(size) = tsize { + size + } else { + let (mut w, mut h) = texcoord.buffer_transform.maybe_swap(texture.size()); + if tscale != self.scale { + let tscale = tscale.to_f64(); + w = (w as f64 * self.scalef / tscale).round() as _; + h = (h as f64 * self.scalef / tscale).round() as _; + } + (w, h) + }; + + let mut target_x = [x, x + twidth]; + let mut target_y = [y, y + theight]; + + if let Some(bounds) = bounds + && bound_target(&mut target_x, &mut target_y, &mut texcoord, bounds) + { + return; + } + + let target = FramebufferRect::new( + target_x[0] as f32, + target_y[0] as f32, + target_x[1] as f32, + target_y[1] as f32, + self.transform, + self.fb_width, + self.fb_height, + ); + + let width = (target_x[1] - target_x[0]) as f32; + let height = (target_y[1] - target_y[0]) as f32; + let scale = self.scalef as f32; + let fitted = corner_radius.fit_to(width, height); + let cr: [f32; 4] = fitted.into(); + + self.ops + .push(GfxApiOpt::RoundedCopyTexture(RoundedCopyTexture { + tex: texture.clone(), + source: texcoord, + target, + alpha, + buffer_resv, + acquire_sync, + release_sync, + opaque: false, + render_intent, + cd: cd.clone(), + alpha_mode, + size: [width, height], + corner_radius: cr, + scale, + })); + } + pub fn sync(&mut self) { self.ops.push(GfxApiOpt::Sync); } diff --git a/src/state.rs b/src/state.rs index 220242fe..abae8b04 100644 --- a/src/state.rs +++ b/src/state.rs @@ -189,10 +189,8 @@ pub struct State { pub theme: Theme, pub pending_container_layout: 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>, @@ -275,6 +273,7 @@ pub struct State { pub color_manager: Rc, pub float_above_fullscreen: Cell, pub icons: Icons, + #[allow(dead_code)] pub show_pin_icon: Cell, pub cl_matcher_manager: ClMatcherManager, pub tl_matcher_manager: TlMatcherManager, @@ -550,10 +549,6 @@ impl DrmDevData { struct UpdateTextTexturesVisitor; impl NodeVisitorBase for UpdateTextTexturesVisitor { fn visit_container(&mut self, node: &Rc) { - 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) { @@ -561,8 +556,6 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.title_textures.borrow_mut().clear(); - node.schedule_render_titles(); node.node_visit_children(self); } fn visit_workspace(&mut self, node: &Rc) { @@ -685,10 +678,6 @@ impl State { struct Walker; 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.borrow_mut().clear()); node.node_visit_children(self); } fn visit_workspace(&mut self, node: &Rc) { @@ -702,7 +691,6 @@ impl State { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.title_textures.borrow_mut().clear(); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { @@ -1118,10 +1106,8 @@ impl State { self.dbus.clear(); self.pending_container_layout.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(); @@ -1296,8 +1282,8 @@ impl State { let (width, height) = target.logical_size(target_transform); Rect::new_sized_saturating(0, 0, width, height) }, - icons: None, stretch: None, + corner_radius: None, }; let mut sample_rect = SampleRect::identity(); sample_rect.buffer_transform = transform; @@ -1621,8 +1607,7 @@ impl State { .unwrap_or(c); Some(target.predict_child_body_size()) } else { - let tpuh = self.theme.title_plus_underline_height(); - Some((pos.width(), (pos.height() - tpuh).max(0))) + Some((pos.width(), pos.height())) } } @@ -1863,11 +1848,13 @@ impl State { self.spaces_changed(); } + #[allow(dead_code)] pub fn set_show_titles(&self, show: bool) { self.theme.show_titles.set(show); self.spaces_changed(); } + #[allow(dead_code)] pub fn set_floating_titles(&self, floating: bool) { self.theme.floating_titles.set(floating); self.spaces_changed(); @@ -1882,13 +1869,9 @@ impl State { .set(threshold.saturating_mul(threshold)); } + #[allow(dead_code)] pub fn set_show_pin_icon(&self, show: bool) { self.show_pin_icon.set(show); - for stacked in self.root.stacked.iter() { - if let Some(float) = stacked.deref().clone().node_into_float() { - float.schedule_render_titles(); - } - } } pub fn set_float_above_fullscreen(&self, v: bool) { @@ -1909,7 +1892,6 @@ impl State { struct V; impl NodeVisitorBase for V { fn visit_container(&mut self, node: &Rc) { - node.schedule_render_titles(); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { @@ -1917,7 +1899,6 @@ impl State { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.schedule_render_titles(); node.node_visit_children(self); } } @@ -1943,6 +1924,7 @@ impl State { self.fonts_changed(); } + #[allow(dead_code)] pub fn set_title_font(&self, font: Option<&str>) { let font = font.map(|font| Arc::new(font.to_string())); self.theme.title_font.set(font); diff --git a/src/text.rs b/src/text.rs index 94e7d77d..0faa59c3 100644 --- a/src/text.rs +++ b/src/text.rs @@ -415,6 +415,7 @@ impl Shared { } } +#[allow(dead_code)] #[derive(PartialEq, Default)] enum Config<'a> { #[default] @@ -519,6 +520,7 @@ impl TextTexture { self.data.pending_render.set(Some(pending)); } + #[allow(dead_code)] pub fn schedule_render( &self, on_completed: Rc, diff --git a/src/theme.rs b/src/theme.rs index 2a1e1703..04d23da4 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -643,6 +643,95 @@ impl Into for BarPosition { } } +/// Per-corner radius for rounded rectangles. +/// +/// Each field specifies the radius (in logical pixels) for one corner. +/// A radius of 0 means a square corner. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CornerRadius { + pub top_left: f32, + pub top_right: f32, + pub bottom_right: f32, + pub bottom_left: f32, +} + +impl From for CornerRadius { + fn from(value: f32) -> Self { + Self { + top_left: value, + top_right: value, + bottom_right: value, + bottom_left: value, + } + } +} + +impl From for [f32; 4] { + fn from(cr: CornerRadius) -> Self { + [cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left] + } +} + +impl CornerRadius { + /// Shrink or grow all radii by `width`. Radii that are 0 stay 0 (square + /// corners remain square). Negative `width` shrinks; the result is clamped + /// to 0. + pub fn expanded_by(mut self, width: f32) -> Self { + if self.top_left > 0.0 { + self.top_left = (self.top_left + width).max(0.0); + } + if self.top_right > 0.0 { + self.top_right = (self.top_right + width).max(0.0); + } + if self.bottom_right > 0.0 { + self.bottom_right = (self.bottom_right + width).max(0.0); + } + if self.bottom_left > 0.0 { + self.bottom_left = (self.bottom_left + width).max(0.0); + } + self + } + + /// Scale all radii by a factor (e.g. for HiDPI). + pub fn scaled_by(self, scale: f32) -> Self { + Self { + top_left: self.top_left * scale, + top_right: self.top_right * scale, + bottom_right: self.bottom_right * scale, + bottom_left: self.bottom_left * scale, + } + } + + /// Reduce all radii proportionally so that adjacent corners don't overlap, + /// following the CSS spec algorithm. + pub fn fit_to(self, width: f32, height: f32) -> Self { + let reduction = f32::min( + f32::min( + width / (self.top_left + self.top_right), + width / (self.bottom_left + self.bottom_right), + ), + f32::min( + height / (self.top_left + self.bottom_left), + height / (self.top_right + self.bottom_right), + ), + ); + let reduction = f32::min(1.0, reduction); + Self { + top_left: self.top_left * reduction, + top_right: self.top_right * reduction, + bottom_right: self.bottom_right * reduction, + bottom_left: self.bottom_left * reduction, + } + } + + pub fn is_zero(&self) -> bool { + self.top_left == 0.0 + && self.top_right == 0.0 + && self.bottom_right == 0.0 + && self.bottom_left == 0.0 + } +} + pub struct Theme { pub colors: ThemeColors, pub sizes: ThemeSizes, @@ -650,9 +739,12 @@ pub struct Theme { pub bar_font: CloneCell>>, pub title_font: CloneCell>>, pub default_font: Arc, + #[allow(dead_code)] pub show_titles: Cell, + #[allow(dead_code)] pub floating_titles: Cell, pub bar_position: Cell, + pub corner_radius: Cell, } impl Default for Theme { @@ -668,50 +760,22 @@ impl Default for Theme { show_titles: Cell::new(true), floating_titles: Cell::new(false), bar_position: Default::default(), + corner_radius: Cell::new(CornerRadius::default()), } } } impl Theme { - pub fn title_font(&self) -> Arc { - self.title_font.get().unwrap_or_else(|| self.font.get()) - } - pub fn bar_font(&self) -> Arc { self.bar_font.get().unwrap_or_else(|| self.font.get()) } pub fn title_height(&self) -> i32 { - if self.show_titles.get() { - self.sizes.title_height.get() - } else { - 0 - } - } - - pub fn title_underline_height(&self) -> i32 { - if self.show_titles.get() { 1 } else { 0 } - } - - pub fn floating_title_top_margin(&self) -> i32 { - if self.floating_titles.get() && self.sizes.gap.get() != 0 { - self.sizes.border_width.get() - } else { - 0 - } + 0 } pub fn title_plus_underline_height(&self) -> i32 { - if self.show_titles.get() { - if self.floating_titles.get() && self.sizes.gap.get() != 0 { - let bw = self.sizes.border_width.get(); - 3 * bw + self.sizes.title_height.get() + self.sizes.title_gap.get() - } else { - self.sizes.title_height.get() + 1 - } - } else { - 0 - } + 0 } } diff --git a/src/tree/container.rs b/src/tree/container.rs index 153caf42..9fde0706 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -4,38 +4,29 @@ use { cursor::KnownCursor, cursor_user::CursorUser, fixed::Fixed, - gfx_api::GfxTexture, ifs::wl_seat::{ - BTN_LEFT, BTN_RIGHT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci, + BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci, collect_kb_foci2, tablet::{TabletTool, TabletToolChanges, TabletToolId}, wl_pointer::PendingScroll, }, rect::Rect, renderer::Renderer, - scale::Scale, state::State, - text::TextTexture, tree::{ ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, OutputNode, TddType, TileDragDestination, ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelType, WorkspaceNode, - default_tile_drag_bounds, toplevel_set_floating, toplevel_set_workspace, + default_tile_drag_bounds, toplevel_set_workspace, walker::NodeVisitor, }, utils::{ - asyncevent::AsyncEvent, clonecell::CloneCell, - double_click_state::DoubleClickState, - errorfmt::ErrorFmt, event_listener::LazyEventSource, hash_map_ext::HashMapExt, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, - on_drop_event::OnDropEvent, rc_eq::rc_eq, - scroller::Scroller, - smallmap::SmallMapMut, threshold_counter::ThresholdCounter, }, }, @@ -94,20 +85,9 @@ pub enum ContainerFocus { tree_id!(ContainerNodeId); -pub struct ContainerTitle { - pub rect: Rect, - pub tex: Rc, -} - #[derive(Default)] pub struct ContainerRenderData { - pub title_rects: Vec, - pub active_title_rects: Vec, - pub attention_title_rects: Vec, - pub last_active_rect: Option, pub border_rects: Vec, - pub underline_rects: Vec, - pub titles: SmallMapMut, 2>, } pub struct ContainerNode { @@ -123,9 +103,8 @@ pub struct ContainerNode { pub content_width: Cell, pub content_height: Cell, pub sum_factors: Cell, - layout_scheduled: Cell, + pub layout_scheduled: Cell, compute_render_positions_scheduled: Cell, - render_titles_scheduled: Cell, num_children: NumCell, pub children: LinkedList, focus_history: LinkedList>, @@ -135,7 +114,6 @@ pub struct ContainerNode { cursors: RefCell>, state: Rc, pub render_data: RefCell, - scroller: Scroller, toplevel_data: ToplevelData, attention_requests: ThresholdCounter, pub layout_complete: Rc, @@ -154,9 +132,6 @@ pub struct ContainerChild { pub node: Rc, pub active: Cell, pub attention_requested: Cell, - title: RefCell, - pub title_tex: RefCell>, - pub title_rect: Cell, focus_history: Cell>>>, // fields below only valid in tabbed layout @@ -178,7 +153,6 @@ struct CursorState { x: i32, y: i32, op: Option, - double_click_state: DoubleClickState, } impl ContainerChild { @@ -212,9 +186,6 @@ impl ContainerNode { body: Default::default(), content: Default::default(), factor: Cell::new(1.0), - title: Default::default(), - title_tex: Default::default(), - title_rect: Default::default(), focus_history: Default::default(), attention_requested: Cell::new(false), border_color_is_focused: Default::default(), @@ -238,7 +209,6 @@ impl ContainerNode { sum_factors: Cell::new(1.0), layout_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(), @@ -248,7 +218,6 @@ impl ContainerNode { cursors: RefCell::new(Default::default()), state: state.clone(), render_data: Default::default(), - scroller: Default::default(), toplevel_data: ToplevelData::new( state, Default::default(), @@ -339,9 +308,6 @@ impl ContainerNode { content: Default::default(), factor: Default::default(), - title: Default::default(), - title_tex: Default::default(), - title_rect: Default::default(), focus_history: Default::default(), attention_requested: Default::default(), border_color_is_focused: Default::default(), @@ -384,35 +350,25 @@ impl ContainerNode { } pub fn predict_child_body_size(&self) -> (i32, i32) { - let tpuh = self.state.theme.title_plus_underline_height(); if self.mono_child.is_some() { let mb = self.mono_body.get(); return (mb.width(), mb.height()); } let gap = self.state.theme.sizes.gap.get(); let bw = self.state.theme.sizes.border_width.get(); - let floating = self.state.theme.floating_titles.get() && gap != 0; let nc = self.num_children.get() as i32 + 1; match self.split.get() { ContainerSplit::Horizontal => { - let spacing = if floating { - self.state.theme.sizes.title_gap.get() + 2 * bw - } else { - gap.max(bw) - }; + let spacing = gap.max(bw); let content_w = self.width.get().sub((nc - 1) * spacing).max(0); - (content_w / nc, self.height.get().sub(tpuh).max(0)) + (content_w / nc, self.height.get()) } ContainerSplit::Vertical => { - let spacing = if floating { - self.state.theme.sizes.title_gap.get() + bw - } else { - gap.max(bw) - }; + let spacing = gap.max(bw); let content_h = self .height .get() - .sub(tpuh + (nc - 1) * (spacing + tpuh)) + .sub((nc - 1) * spacing) .max(0); (self.width.get(), content_h / nc) } @@ -427,8 +383,6 @@ impl ContainerNode { } pub fn on_colors_changed(self: &Rc) { - // log::info!("on_colors_changed"); - self.schedule_render_titles(); self.schedule_compute_render_positions(); } @@ -487,7 +441,6 @@ impl ContainerNode { } self.state.tree_changed(); // log::info!("perform_layout"); - self.schedule_render_titles(); self.schedule_compute_render_positions(); self.layout_complete.trigger(); if self.all_children_match_body() { @@ -507,51 +460,14 @@ impl ContainerNode { .tl_change_extents(&mb.move_(self.abs_x1.get(), self.abs_y1.get())); self.mono_content .set(child.content.get().at_point(mb.x1(), mb.y1())); - - let th = self.state.theme.title_height(); - let bw = self.state.theme.sizes.border_width.get(); - let bw_top = self.state.theme.floating_title_top_margin(); - let floating = self.state.theme.floating_titles.get(); - let spacing = if floating { - self.state.theme.sizes.title_gap.get() + 2 * bw - } else { - bw - }; - let num_children = self.num_children.get() as i32; - let content_width = self.width.get().sub(spacing * (num_children - 1)).max(0); - let width_per_child = content_width / num_children; - let mut rem = content_width % num_children; - let mut pos = 0; - for child in self.children.iter() { - let mut width = width_per_child; - if rem > 0 { - width += 1; - rem -= 1; - } - child - .title_rect - .set(Rect::new_sized_saturating(pos, bw_top, width, th)); - pos += width + spacing; - } } fn perform_split_layout(self: &Rc) { let sum_factors = self.sum_factors.get(); let gap = self.state.theme.sizes.gap.get(); let border_width = self.state.theme.sizes.border_width.get(); - let floating = self.state.theme.floating_titles.get() && gap != 0; - let title_height_tmp = self.state.theme.title_height(); - let tpuh = self.state.theme.title_plus_underline_height(); let split = self.split.get(); - let spacing = if floating { - let title_gap = self.state.theme.sizes.title_gap.get(); - match split { - ContainerSplit::Horizontal => title_gap + 2 * border_width, - ContainerSplit::Vertical => title_gap + border_width, - } - } else { - gap.max(border_width) - }; + let spacing = gap.max(border_width); let (content_size, other_content_size) = match split { ContainerSplit::Horizontal => (self.content_width.get(), self.content_height.get()), ContainerSplit::Vertical => (self.content_height.get(), self.content_width.get()), @@ -569,15 +485,12 @@ impl ContainerNode { body_size = body_size.min(remaining_content_size); remaining_content_size -= body_size; let (x1, y1, width, height) = match split { - ContainerSplit::Horizontal => (pos, tpuh, body_size, other_content_size), - _ => (0, pos + tpuh, other_content_size, body_size), + ContainerSplit::Horizontal => (pos, 0, body_size, other_content_size), + _ => (0, pos, other_content_size, body_size), }; let body = Rect::new_sized_saturating(x1, y1, width, height); child.body.set(body); pos += body_size + spacing; - if split == ContainerSplit::Vertical { - pos += tpuh; - } } if remaining_content_size > 0 { let size_per = remaining_content_size / num_children as i32; @@ -593,31 +506,21 @@ impl ContainerNode { let (x1, y1, width, height, size) = match split { ContainerSplit::Horizontal => { let width = body.width() + add; - (pos, tpuh, width, other_content_size, width) + (pos, 0, width, other_content_size, width) } _ => { let height = body.height() + add; - (0, pos + tpuh, other_content_size, height, height) + (0, pos, other_content_size, height, height) } }; body = Rect::new_sized_saturating(x1, y1, width, height); child.body.set(body); pos += size + spacing; - if split == ContainerSplit::Vertical { - pos += tpuh; - } } } - let bw_top = self.state.theme.floating_title_top_margin(); self.sum_factors.set(1.0); for child in self.children.iter() { let body = child.body.get(); - child.title_rect.set(Rect::new_sized_saturating( - body.x1(), - body.y1() - tpuh + bw_top, - body.width(), - title_height_tmp, - )); let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); child.node.clone().tl_change_extents(&body); child.position_content(); @@ -627,31 +530,20 @@ impl ContainerNode { fn update_content_size(&self) { let gap = self.state.theme.sizes.gap.get(); let border_width = self.state.theme.sizes.border_width.get(); - let floating = self.state.theme.floating_titles.get() && gap != 0; - let title_gap = self.state.theme.sizes.title_gap.get(); - let tpuh = self.state.theme.title_plus_underline_height(); let nc = self.num_children.get(); match self.split.get() { ContainerSplit::Horizontal => { - let spacing = if floating { - title_gap + 2 * border_width - } else { - gap.max(border_width) - }; + let spacing = gap.max(border_width); let new_content_size = self.width.get().sub((nc - 1) as i32 * spacing).max(0); self.content_width.set(new_content_size); - self.content_height.set(self.height.get().sub(tpuh).max(0)); + self.content_height.set(self.height.get()); } ContainerSplit::Vertical => { - let spacing = if floating { - title_gap + border_width - } else { - gap.max(border_width) - }; + let spacing = gap.max(border_width); let new_content_size = self .height .get() - .sub(tpuh + (nc - 1) as i32 * (spacing + tpuh)) + .sub((nc - 1) as i32 * spacing) .max(0); self.content_height.set(new_content_size); self.content_width.set(self.width.get()); @@ -659,15 +551,15 @@ impl ContainerNode { } self.mono_body.set(Rect::new_sized_saturating( 0, - tpuh, + 0, self.width.get(), - self.height.get() - tpuh, + self.height.get(), )); } fn pointer_move( self: &Rc, - seat: &Rc, + _seat: &Rc, id: CursorType, cursor: &CursorUser, x: Fixed, @@ -676,7 +568,6 @@ impl ContainerNode { ) { let mut x = x.round_down(); let mut y = y.round_down(); - let title_plus_underline_height = self.state.theme.title_plus_underline_height(); let mut seats = self.cursors.borrow_mut(); let seat_state = seats.entry(id).or_insert_with(|| CursorState { cursor: KnownCursor::Default, @@ -684,7 +575,6 @@ impl ContainerNode { x, y, op: None, - double_click_state: Default::default(), }); let mut changed = false; changed |= mem::replace(&mut seat_state.x, x) != x; @@ -694,15 +584,6 @@ impl ContainerNode { } if let Some(op) = &seat_state.op { match op.kind { - SeatOpKind::Move => { - if let CursorType::Seat(_) = id - && self.state.ui_drag_threshold_reached((x, y), (op.x, op.y)) - { - let node = op.child.node.clone(); - drop(seats); - seat.start_tile_drag(&node); - } - } SeatOpKind::Resize { dist_left, dist_right, @@ -748,19 +629,13 @@ impl ContainerNode { let new_cursor = if self.mono_child.is_some() { KnownCursor::Default } else if self.split.get() == ContainerSplit::Horizontal { - if y < title_plus_underline_height { - KnownCursor::Default - } else { - KnownCursor::EwResize - } + KnownCursor::EwResize } else { let mut cursor = KnownCursor::Default; for child in self.children.iter() { let body = child.body.get(); if body.y1() > y { - if body.y1() - y > title_plus_underline_height { - cursor = KnownCursor::NsResize - } + cursor = KnownCursor::NsResize; break; } } @@ -773,121 +648,6 @@ impl ContainerNode { } } - fn update_title(&self) { - let mut title = self.toplevel_data.title.borrow_mut(); - title.clear(); - let split = match (self.mono_child.is_some(), self.split.get()) { - (true, _) => "T", - (_, ContainerSplit::Horizontal) => "H", - (_, ContainerSplit::Vertical) => "V", - }; - title.push_str(split); - title.push_str("["); - for (i, c) in self.children.iter().enumerate() { - if i > 0 { - title.push_str(", "); - } - title.push_str(c.title.borrow_mut().deref()); - } - title.push_str("]"); - drop(title); - self.tl_title_changed(); - } - - pub fn schedule_render_titles(self: &Rc) { - if !self.render_titles_scheduled.replace(true) { - self.state.pending_container_render_title.push(self.clone()); - } - } - - fn last_active(&self) -> Option { - match self.mono_child.get() { - Some(c) => Some(c.node.node_id()), - None => self.focus_history.last().map(|v| v.node.node_id()), - } - } - - 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.title_height(); - let font = theme.title_font(); - let last_active = self.last_active(); - 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, &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 { rect, 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 @@ -901,110 +661,36 @@ impl ContainerNode { let mut rd = self.render_data.borrow_mut(); let rd = rd.deref_mut(); let theme = &self.state.theme; - let th = theme.title_height(); - let tpuh = theme.title_plus_underline_height(); - let tuh = theme.title_underline_height(); let bw = theme.sizes.border_width.get(); let cwidth = self.width.get(); let cheight = self.height.get(); - for (_, v) in rd.titles.iter_mut() { - v.clear(); - } - rd.title_rects.clear(); - rd.active_title_rects.clear(); - rd.attention_title_rects.clear(); rd.border_rects.clear(); - rd.underline_rects.clear(); - rd.last_active_rect.take(); - let last_active = self.last_active(); let mono = self.mono_child.is_some(); let split = self.split.get(); - let have_active = self.children.iter().any(|c| c.active.get()); let abs_x = self.abs_x1.get(); let abs_y = self.abs_y1.get(); let gap = self.state.theme.sizes.gap.get(); - let floating_titles = self.state.theme.floating_titles.get(); - let title_bw = if gap != 0 && !floating_titles { bw } else { 0 }; for (i, child) in self.children.iter().enumerate() { - let rect = child.title_rect.get(); - if self.toplevel_data.visible.get() && !mono && split != ContainerSplit::Horizontal { - self.state.damage(Rect::new_sized_saturating( - abs_x - title_bw, - abs_y + rect.y1(), - cwidth + 2 * title_bw, - rect.height() + tuh, - )); - } if gap != 0 && !mono && !child.node.node_is_container() { child.border_color_is_focused.set(child.active.get()); } else if i > 0 { + let body = child.body.get(); let sep = if mono { - Rect::new_sized_saturating(rect.x1() - bw, 0, bw, th) + // In mono mode, no separators needed without title tabs + continue; } else if split == ContainerSplit::Horizontal { - Rect::new_sized_saturating(rect.x1() - bw, 0, bw, cheight) + Rect::new_sized_saturating(body.x1() - bw, 0, bw, cheight) } else { - Rect::new_sized_saturating(0, rect.y1() - bw, cwidth, bw) + Rect::new_sized_saturating(0, body.y1() - bw, cwidth, bw) }; - if gap == 0 || (mono && !floating_titles) { + if gap == 0 { rd.border_rects.push(sep); } } - if child.active.get() { - rd.active_title_rects.push(rect); - } else if child.attention_requested.get() { - rd.attention_title_rects.push(rect); - } else if !have_active && last_active == Some(child.node.node_id()) { - rd.last_active_rect = Some(rect); - } else { - rd.title_rects.push(rect); - } - if !mono && (!floating_titles || gap == 0) { - let rect = Rect::new_sized_saturating(rect.x1(), rect.y2(), rect.width(), 1); - rd.underline_rects.push(rect); - } - 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 { rect, tex }) - } - } } - if mono && (!floating_titles || gap == 0) { - rd.underline_rects - .push(Rect::new_sized_saturating(0, th, cwidth, tuh)); + if self.toplevel_data.visible.get() { + self.state.damage(Rect::new_sized_saturating(abs_x, abs_y, cwidth, cheight)); } - if gap == 0 && th > 0 { - rd.underline_rects - .push(Rect::new_sized_saturating(0, 0, cwidth, tuh)); - } - if title_bw > 0 { - let extend = |r: Rect| { - let x1 = if r.x1() == 0 { -title_bw } else { r.x1() }; - let x2 = if r.x2() == cwidth { r.x2() + title_bw } else { r.x2() }; - Rect::new_sized_saturating(x1, r.y1(), x2 - x1, r.height()) - }; - for r in &mut rd.title_rects { - *r = extend(*r); - } - for r in &mut rd.active_title_rects { - *r = extend(*r); - } - for r in &mut rd.attention_title_rects { - *r = extend(*r); - } - if let Some(r) = &mut rd.last_active_rect { - *r = extend(*r); - } - for r in &mut rd.underline_rects { - *r = extend(*r); - } - } - if self.toplevel_data.visible.get() && (mono || split == ContainerSplit::Horizontal) { - self.state - .damage(Rect::new_sized_saturating(abs_x - title_bw, abs_y, cwidth + 2 * title_bw, tpuh)); - } - rd.titles.remove_if(|_, v| v.is_empty()); } fn activate_child(self: &Rc, child: &NodeRef) { @@ -1081,7 +767,6 @@ impl ContainerNode { self.mono_child.set(child); // log::info!("set_mono"); self.schedule_layout(); - self.update_title(); } pub fn set_split(self: &Rc, split: ContainerSplit) { @@ -1089,7 +774,6 @@ impl ContainerNode { self.update_content_size(); // log::info!("set_split"); self.schedule_layout(); - self.update_title(); } } @@ -1279,20 +963,6 @@ impl ContainerNode { } } - fn update_child_title(self: &Rc, child: &ContainerChild, title: &str) { - { - let mut ct = child.title.borrow_mut(); - if ct.deref() == title { - return; - } - ct.clear(); - ct.push_str(title); - } - self.update_title(); - // log::info!("node_child_title_changed"); - self.schedule_render_titles(); - } - fn update_child_active( self: &Rc, node: &NodeRef, @@ -1307,7 +977,6 @@ impl ContainerNode { .set(Some(self.focus_history.add_last(node.clone()))); } // log::info!("node_child_active_changed"); - self.schedule_render_titles(); self.schedule_compute_render_positions(); if self.state.theme.sizes.gap.get() != 0 && self.toplevel_data.visible.get() { self.damage(); @@ -1338,7 +1007,6 @@ impl ContainerNode { self.mod_attention_requests(true); } } - self.update_child_title(child, &data.title.borrow()); self.update_child_active(child, data.active(), 1); { let pos = data.pos.get(); @@ -1362,19 +1030,11 @@ impl ContainerNode { } } - fn toggle_mono(self: &Rc) { - if self.mono_child.is_some() { - self.set_mono(None); - } else if let Some(last) = self.focus_history.last() { - self.set_mono(Some(&*last.node)); - } - } - fn button( self: Rc, id: CursorType, - seat: &Rc, - time_usec: u64, + _seat: &Rc, + _time_usec: u64, pressed: bool, button: u32, ) { @@ -1383,20 +1043,6 @@ impl ContainerNode { Some(s) => s, _ => return, }; - if button == BTN_RIGHT && pressed { - if self.mono_child.is_some() || self.split.get() == ContainerSplit::Horizontal { - if seat_data.y < self.state.theme.title_height() { - self.toggle_mono(); - } - } else { - for child in self.children.iter() { - if child.title_rect.get().contains(seat_data.x, seat_data.y) { - self.toggle_mono(); - } - } - } - return; - } if button != BTN_LEFT { return; } @@ -1407,17 +1053,9 @@ impl ContainerNode { let (kind, child) = 'res: { let mono = self.mono_child.is_some(); for child in self.children.iter() { - let rect = child.title_rect.get(); - if rect.contains(seat_data.x, seat_data.y) { - self.activate_child(&child); - child - .node - .clone() - .node_do_focus(seat, Direction::Unspecified); - break 'res (SeatOpKind::Move, child); - } else if !mono { + if !mono { if self.split.get() == ContainerSplit::Horizontal { - if seat_data.x < rect.x1() { + if seat_data.x < child.body.get().x1() { break 'res ( SeatOpKind::Resize { dist_left: seat_data.x @@ -1428,7 +1066,7 @@ impl ContainerNode { ); } } else { - if seat_data.y < rect.y1() { + if seat_data.y < child.body.get().y1() { break 'res ( SeatOpKind::Resize { dist_left: seat_data.y @@ -1443,20 +1081,9 @@ impl ContainerNode { } return; }; - if seat_data - .double_click_state - .click(&self.state, time_usec, seat_data.x, seat_data.y) - && kind == SeatOpKind::Move - { - drop(seat_datas); - toplevel_set_floating(&self.state, child.node.clone(), true); - return; - } seat_data.op = Some(SeatOp { child, kind, - x: seat_data.x, - y: seat_data.y, }) } else if !pressed { seat_data.op = None; @@ -1464,65 +1091,6 @@ impl ContainerNode { } } - fn tile_drag_destination_mono_titles( - self: &Rc, - source: NodeId, - abs_bounds: Rect, - abs_x: i32, - abs_y: i32, - ) -> Option { - let mut prev_is_source = false; - let mut prev_center = 0; - for child in self.children.iter() { - if child.node.node_id() == source { - prev_is_source = true; - continue; - } - let rect = child.title_rect.get(); - let center = (rect.x1() + rect.x2()) / 2; - if !prev_is_source { - let rect = Rect::new(prev_center, 0, center, rect.height())? - .move_(self.abs_x1.get(), self.abs_y1.get()) - .intersect(abs_bounds); - if rect.contains(abs_x, abs_y) { - return Some(TileDragDestination { - highlight: rect, - ty: TddType::Insert { - container: self.clone(), - neighbor: child.node.clone(), - before: true, - }, - }); - } - } - prev_center = center; - prev_is_source = false; - } - if prev_is_source { - return None; - } - let last = self.children.last()?; - let rect = Rect::new( - prev_center, - 0, - self.width.get(), - self.state.theme.title_height(), - )? - .move_(self.abs_x1.get(), self.abs_y1.get()) - .intersect(abs_bounds); - if rect.contains(abs_x, abs_y) { - return Some(TileDragDestination { - highlight: rect, - ty: TddType::Insert { - container: self.clone(), - neighbor: last.node.clone(), - before: false, - }, - }); - } - None - } - fn tile_drag_destination_mono( self: &Rc, mc: &ContainerChild, @@ -1531,10 +1099,6 @@ impl ContainerNode { abs_x: i32, abs_y: i32, ) -> Option { - let th = self.state.theme.title_height(); - if abs_y < self.abs_y1.get() + th { - return self.tile_drag_destination_mono_titles(source, abs_bounds, abs_x, abs_y); - } let body = self.mono_body.get(); let mut bounds = body .move_(self.abs_x1.get(), self.abs_y1.get()) @@ -1696,13 +1260,10 @@ impl ContainerNode { struct SeatOp { child: NodeRef, kind: SeatOpKind, - x: i32, - y: i32, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum SeatOpKind { - Move, Resize { dist_left: i32, dist_right: i32 }, } @@ -1724,17 +1285,6 @@ pub async fn container_render_positions(state: Rc) { } } -pub async fn container_render_titles(state: Rc) { - 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(); - } - } -} - impl Node for ContainerNode { fn node_id(&self) -> NodeId { self.id.into() @@ -1779,10 +1329,8 @@ impl Node for ContainerNode { self.toplevel_data.node_layer() } - fn node_child_title_changed(self: Rc, child: &dyn Node, title: &str) { - if let Some(child) = self.child_nodes.borrow().get(&child.node_id()) { - self.update_child_title(child, title); - } + fn node_child_title_changed(self: Rc, _child: &dyn Node, _title: &str) { + // Titlebars removed; no title tracking needed } fn node_do_focus(self: Rc, seat: &Rc, direction: Direction) { @@ -1885,41 +1433,8 @@ impl Node for ContainerNode { self.button(id, seat, time_usec, state == ButtonState::Pressed, button); } - fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { - let mut seat_datas = self.cursors.borrow_mut(); - let id = CursorType::Seat(seat.id()); - let seat_data = match seat_datas.get_mut(&id) { - Some(s) => s, - _ => return, - }; - if seat_data.y > self.state.theme.title_height() { - return; - } - let cur_mc = match self.mono_child.get() { - Some(mc) => mc, - _ => return, - }; - let discrete = match self.scroller.handle(event) { - Some(d) => d, - _ => return, - }; - let mut new_mc = cur_mc.clone(); - for _ in 0..discrete.abs() { - let new = if discrete < 0 { - new_mc.prev() - } else { - new_mc.next() - }; - new_mc = match new { - Some(n) => n, - None => break, - }; - } - self.activate_child(&new_mc); - new_mc - .node - .clone() - .node_do_focus(seat, Direction::Unspecified); + fn node_on_axis_event(self: Rc, _seat: &Rc, _event: &PendingScroll) { + // Scroll-to-switch-tabs was a title bar feature; no-op without titles } fn node_on_leave(&self, seat: &WlSeatGlobal) { @@ -2051,9 +1566,6 @@ impl ContainingNode for ContainerNode { content: Default::default(), factor: Cell::new(node.factor.get()), - title: Default::default(), - title_tex: Default::default(), - title_rect: Cell::new(node.title_rect.get()), focus_history: Cell::new(None), attention_requested: Cell::new(false), border_color_is_focused: Default::default(), @@ -2132,7 +1644,6 @@ impl ContainingNode for ContainerNode { } } self.sum_factors.set(sum); - self.update_title(); // log::info!("cnode_remove_child2"); self.schedule_layout(); self.cancel_seat_ops(); @@ -2186,9 +1697,8 @@ impl ContainingNode for ContainerNode { let Some(parent) = self.toplevel_data.parent.get() else { return; }; - let tpuh = self.state.theme.title_plus_underline_height(); if self.mono_child.is_some() { - parent.cnode_set_child_position(&*self, x, y - tpuh); + parent.cnode_set_child_position(&*self, x, y); } else { let children = self.child_nodes.borrow(); let Some(child) = children.get(&child.node_id()) else { @@ -2209,7 +1719,6 @@ impl ContainingNode for ContainerNode { new_y2: Option, ) { let theme = &self.state.theme; - let tpuh = theme.title_plus_underline_height(); let bw = theme.sizes.border_width.get(); let mut left_outside = false; let mut right_outside = false; @@ -2251,7 +1760,7 @@ impl ContainingNode for ContainerNode { } let (new_delta, between) = match split { ContainerSplit::Horizontal => (self.abs_x1.get(), bw), - ContainerSplit::Vertical => (self.abs_y1.get(), bw + tpuh), + ContainerSplit::Vertical => (self.abs_y1.get(), bw), }; let new_i1 = new_i1.map(|v| v - new_delta); let new_i2 = new_i2.map(|v| v - new_delta); @@ -2319,10 +1828,10 @@ impl ContainingNode for ContainerNode { x2 = new_x2.map(|v| v.max(x1.unwrap_or(pos.x1()))); } if top_outside { - y1 = new_y1.map(|v| (v - tpuh).min(pos.y2() - tpuh)); + y1 = new_y1.map(|v| v.min(pos.y2())); } if bottom_outside { - y2 = new_y2.map(|v| v.max(y1.unwrap_or(pos.y1()) + tpuh)); + y2 = new_y2.map(|v| v.max(y1.unwrap_or(pos.y1()))); } if ((x1.is_some() && x1 != Some(pos.x1())) || (x2.is_some() && x2 != Some(pos.x2())) diff --git a/src/tree/float.rs b/src/tree/float.rs index f8bd1585..7e9fffff 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -10,27 +10,22 @@ use { }, rect::Rect, renderer::Renderer, - scale::Scale, state::State, - text::TextTexture, tree::{ ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, OutputNode, PinnedNode, StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode, toplevel_set_floating, walker::NodeVisitor, }, utils::{ - asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState, - errorfmt::ErrorFmt, event_listener::LazyEventSource, linkedlist::LinkedNode, - on_drop_event::OnDropEvent, smallmap::SmallMapMut, + clonecell::CloneCell, double_click_state::DoubleClickState, + event_listener::LazyEventSource, linkedlist::LinkedNode, }, }, ahash::AHashMap, - arrayvec::ArrayVec, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, - ops::Deref, rc::Rc, }, }; @@ -50,10 +45,6 @@ pub struct FloatNode { pub active: Cell, pub seat_state: NodeSeatState, pub layout_scheduled: Cell, - pub render_titles_scheduled: Cell, - pub title_rect: Cell, - pub title: RefCell, - pub title_textures: RefCell>, cursors: RefCell>, pub attention_requested: Cell, pub layout_complete: Rc, @@ -99,17 +90,6 @@ pub async fn float_layout(state: Rc) { } } -pub async fn float_titles(state: Rc) { - loop { - let node = state.pending_float_titles.pop().await; - if node.render_titles_scheduled.get() { - node.render_titles_scheduled.set(false); - node.render_title_phase1().triggered().await; - node.render_title_phase2(); - } - } -} - impl FloatNode { pub fn new( state: &Rc, @@ -131,10 +111,6 @@ impl FloatNode { active: Cell::new(false), seat_state: Default::default(), layout_scheduled: Cell::new(false), - render_titles_scheduled: Cell::new(false), - title_rect: Default::default(), - title: Default::default(), - title_textures: Default::default(), cursors: Default::default(), attention_requested: Cell::new(false), layout_complete: state.post_layout_event_sources.create_source(), @@ -162,7 +138,7 @@ impl FloatNode { } pub fn on_colors_changed(self: &Rc) { - self.schedule_render_titles(); + // No title rendering needed } pub fn schedule_layout(self: &Rc) { @@ -179,95 +155,17 @@ impl FloatNode { let pos = self.position.get(); let theme = &self.state.theme; let bw = theme.sizes.border_width.get(); - let th = theme.title_height(); - let tpuh = theme.title_plus_underline_height(); let cpos = Rect::new_sized_saturating( pos.x1() + bw, - pos.y1() + bw + tpuh, + pos.y1() + bw, pos.width() - 2 * bw, - pos.height() - 2 * bw - tpuh, + pos.height() - 2 * bw, ); - let tr = Rect::new_sized_saturating(bw, bw, pos.width() - 2 * bw, th); child.clone().tl_change_extents(&cpos); - self.title_rect.set(tr); self.layout_scheduled.set(false); - self.schedule_render_titles(); self.layout_complete.trigger(); } - pub fn schedule_render_titles(self: &Rc) { - if !self.render_titles_scheduled.replace(true) { - self.state.pending_float_titles.push(self.clone()); - } - } - - fn render_title_phase1(&self) -> Rc { - let on_completed = Rc::new(OnDropEvent::default()); - let theme = &self.state.theme; - let tc = match self.active.get() { - true => theme.colors.focused_title_text.get(), - false => theme.colors.unfocused_title_text.get(), - }; - let font = theme.title_font(); - let title = self.title.borrow_mut(); - let ctx = match self.state.render_ctx.get() { - Some(c) => c, - _ => return on_completed.event(), - }; - let scales = self.state.scales.lock(); - let tr = self.title_rect.get(); - let tt = &mut *self.title_textures.borrow_mut(); - for (scale, _) in scales.iter() { - let tex = tt.get_or_insert_with(*scale, || TextTexture::new(&self.state, &ctx)); - let mut th = tr.height(); - let mut scalef = None; - let mut width = tr.width(); - if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() { - width = (width - th).max(0); - } - 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, - tc, - true, - false, - scalef, - ); - } - on_completed.event() - } - - fn render_title_phase2(&self) { - let theme = &self.state.theme; - let th = theme.title_height(); - 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_saturating(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th); - self.state.damage(tr); - } - } - fn pointer_move( self: &Rc, id: CursorType, @@ -280,7 +178,6 @@ impl FloatNode { let y = y.round_down(); let theme = &self.state.theme; let bw = theme.sizes.border_width.get(); - let tpuh = theme.title_plus_underline_height(); let mut seats = self.cursors.borrow_mut(); let seat_state = seats.entry(id).or_insert_with(|| CursorState { cursor: KnownCursor::Default, @@ -316,7 +213,7 @@ impl FloatNode { } OpType::ResizeTop => { y1 += y - seat_state.dist_ver; - y1 = y1.min(y2 - 2 * bw - tpuh); + y1 = y1.min(y2 - 2 * bw); } OpType::ResizeRight => { x2 += x - pos.width() + seat_state.dist_hor; @@ -324,31 +221,31 @@ impl FloatNode { } OpType::ResizeBottom => { y2 += y - pos.height() + seat_state.dist_ver; - y2 = y2.max(y1 + 2 * bw + tpuh); + y2 = y2.max(y1 + 2 * bw); } OpType::ResizeTopLeft => { x1 += x - seat_state.dist_hor; y1 += y - seat_state.dist_ver; x1 = x1.min(x2 - 2 * bw); - y1 = y1.min(y2 - 2 * bw - tpuh); + y1 = y1.min(y2 - 2 * bw); } OpType::ResizeTopRight => { x2 += x - pos.width() + seat_state.dist_hor; y1 += y - seat_state.dist_ver; x2 = x2.max(x1 + 2 * bw); - y1 = y1.min(y2 - 2 * bw - tpuh); + y1 = y1.min(y2 - 2 * bw); } OpType::ResizeBottomLeft => { x1 += x - seat_state.dist_hor; y2 += y - pos.height() + seat_state.dist_ver; x1 = x1.min(x2 - 2 * bw); - y2 = y2.max(y1 + 2 * bw + tpuh); + y2 = y2.max(y1 + 2 * bw); } OpType::ResizeBottomRight => { x2 += x - pos.width() + seat_state.dist_hor; y2 += y - pos.height() + seat_state.dist_ver; x2 = x2.max(x1 + 2 * bw); - y2 = y2.max(y1 + 2 * bw + tpuh); + y2 = y2.max(y1 + 2 * bw); } } let new_pos = Rect::new_saturating(x1, y1, x2, y2); @@ -474,18 +371,12 @@ impl FloatNode { self.schedule_layout(); } - fn update_child_title(self: &Rc, title: &str) { - let mut t = self.title.borrow_mut(); - if t.deref() != title { - t.clear(); - t.push_str(title); - self.schedule_render_titles(); - } + fn update_child_title(self: &Rc, _title: &str) { + // No title rendering } fn update_child_active(self: &Rc, active: bool) { if self.active.replace(active) != active { - self.schedule_render_titles(); if active { self.restack(); } @@ -542,7 +433,6 @@ impl FloatNode { if let Some(tl) = self.child.get() { tl.tl_data().pinned.set(pl.is_some()); } - self.schedule_render_titles(); } fn button( @@ -558,34 +448,6 @@ impl FloatNode { Some(s) => s, _ => return, }; - let bw = self.state.theme.sizes.border_width.get(); - let th = self.state.theme.title_height(); - let mut is_icon_press = false; - if pressed && cursor_data.x >= bw && cursor_data.y >= bw && cursor_data.y < bw + th { - enum FloatIcon { - Pin, - } - let mut icons = ArrayVec::::new(); - if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() { - icons.push(FloatIcon::Pin); - } - let mut x2 = bw + th; - let icon = 'icon: { - for icon in icons { - if cursor_data.x < x2 { - break 'icon Some(icon); - } - x2 += th; - } - None - }; - if let Some(icon) = icon { - is_icon_press = true; - match icon { - FloatIcon::Pin => self.toggle_pinned(), - } - } - } if !cursor_data.op_active { if !pressed { return; @@ -601,7 +463,6 @@ impl FloatNode { cursor_data.x, cursor_data.y, ) && cursor_data.op_type == OpType::Move - && !is_icon_press && let Some(tl) = self.child.get() { drop(cursors); @@ -653,11 +514,10 @@ impl FloatNode { let child = self.child.get()?; let theme = &self.state.theme.sizes; let bw = theme.border_width.get(); - let tpuh = self.state.theme.title_plus_underline_height(); let pos = self.position.get(); let body = Rect::new( pos.x1() + bw, - pos.y1() + bw + tpuh, + pos.y1() + bw, pos.x2() - bw, pos.y2() - bw, )?; @@ -713,8 +573,8 @@ impl Node for FloatNode { NodeLayerLink::Stacked(l) } - fn node_child_title_changed(self: Rc, _child: &dyn Node, title: &str) { - self.update_child_title(title); + fn node_child_title_changed(self: Rc, _child: &dyn Node, _title: &str) { + // No title rendering } fn node_accepts_focus(&self) -> bool { @@ -738,13 +598,12 @@ impl Node for FloatNode { usecase: FindTreeUsecase, ) -> FindTreeResult { let theme = &self.state.theme; - let tpuh = theme.title_plus_underline_height(); let bw = theme.sizes.border_width.get(); let pos = self.position.get(); if x < bw || x >= pos.width() - bw { return FindTreeResult::AcceptsInput; } - if y < bw + tpuh || y >= pos.height() - bw { + if y < bw || y >= pos.height() - bw { return FindTreeResult::AcceptsInput; } let child = match self.child.get() { @@ -752,7 +611,7 @@ impl Node for FloatNode { _ => return FindTreeResult::Other, }; let x = x - bw; - let y = y - bw - tpuh; + let y = y - bw; tree.push(FoundNode { node: child.clone(), x, @@ -934,9 +793,8 @@ impl ContainingNode for FloatNode { fn cnode_set_child_position(self: Rc, _child: &dyn Node, x: i32, y: i32) { let theme = &self.state.theme; - let tpuh = theme.title_plus_underline_height(); let bw = theme.sizes.border_width.get(); - let (x, y) = (x - bw, y - tpuh - bw); + let (x, y) = (x - bw, y - bw); let pos = self.position.get(); if pos.position() != (x, y) { let new_pos = pos.at_point(x, y); @@ -956,7 +814,6 @@ impl ContainingNode for FloatNode { new_y2: Option, ) { let theme = &self.state.theme; - let tpuh = theme.title_plus_underline_height(); let bw = theme.sizes.border_width.get(); let pos = self.position.get(); let mut x1 = pos.x1(); @@ -970,10 +827,10 @@ impl ContainingNode for FloatNode { x2 = (v + bw).max(x1 + bw + bw); } if let Some(v) = new_y1 { - y1 = (v - tpuh - bw).min(y2 - bw - tpuh - bw); + y1 = (v - bw).min(y2 - bw - bw); } if let Some(v) = new_y2 { - y2 = (v + bw).max(y1 + bw + tpuh + bw); + y2 = (v + bw).max(y1 + bw + bw); } let new_pos = Rect::new_saturating(x1, y1, x2, y2); if new_pos != pos { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index ed067c08..c87ce12f 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -228,6 +228,7 @@ pub struct Theme { pub gap: Option, pub floating_titles: Option, pub title_gap: Option, + pub corner_radius: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/theme.rs b/toml-config/src/config/parsers/theme.rs index aa593d71..e2f728bd 100644 --- a/toml-config/src/config/parsers/theme.rs +++ b/toml-config/src/config/parsers/theme.rs @@ -3,7 +3,7 @@ use { config::{ Theme, context::Context, - extractor::{Extractor, ExtractorError, bol, opt, recover, s32, str, val}, + extractor::{Extractor, ExtractorError, bol, fltorint, opt, recover, s32, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::color::ColorParser, }, @@ -97,7 +97,10 @@ impl Parser for ThemeParser<'_> { recover(opt(bol("floating-titles"))), ), ))?; - let (title_gap,) = ext.extract((recover(opt(s32("title-gap"))),))?; + let (title_gap, corner_radius) = ext.extract(( + recover(opt(s32("title-gap"))), + recover(opt(fltorint("corner-radius"))), + ))?; macro_rules! color { ($e:expr) => { match $e { @@ -152,6 +155,7 @@ impl Parser for ThemeParser<'_> { gap: gap.despan(), floating_titles: floating_titles.despan(), title_gap: title_gap.despan(), + corner_radius: corner_radius.map(|v| v.value as f32), }) } } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index dfaabab5..d4164fe4 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -40,7 +40,7 @@ use { set_color_management_enabled, set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen, set_idle, set_idle_grace_period, set_floating_titles, set_middle_click_paste_enabled, set_show_bar, set_show_float_pin_icon, - set_show_titles, + set_show_titles, set_corner_radius, set_ui_drag_enabled, set_ui_drag_threshold, status::{set_i3bar_separator, set_status, set_status_command, unset_status_command}, switch_to_vt, @@ -1020,6 +1020,9 @@ impl State { font!(set_font, font); font!(set_title_font, title_font); font!(set_bar_font, bar_font); + if let Some(radius) = theme.corner_radius { + set_corner_radius(radius); + } } fn handle_switch_device(self: &Rc, dev: InputDevice, actions: &Rc) { diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 4d2c043a..cd3ae0d2 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -2426,6 +2426,11 @@ Theme: kind: string required: false description: The name of the font to use in the bar. Defaults to `font` if not set. + corner-radius: + kind: number + minimum: 0 + required: false + description: The corner radius for window borders in logical pixels. Defaults to 0 (square corners). Config: