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 00000000..ad51d0dd Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.frag.spv differ diff --git a/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.vert.spv b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.vert.spv new file mode 100644 index 00000000..9d0b3737 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.vert.spv differ diff --git a/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.frag.spv b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.frag.spv new file mode 100644 index 00000000..9fd78f9b Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.frag.spv differ diff --git a/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.vert.spv b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.vert.spv new file mode 100644 index 00000000..0a0d73e2 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.vert.spv differ 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 00000000..bf2db619 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/rounded_fill.frag.spv differ 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 00000000..58fbd453 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/rounded_fill.vert.spv differ 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 00000000..2bdd1fd3 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/rounded_tex.frag.spv differ 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 00000000..3ac5e409 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/rounded_tex.vert.spv differ 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: