1
0
Fork 0
forked from wry/wry

all: remove traditional i3 titlebars, add corner rounding

This commit is contained in:
kossLAN 2026-04-09 23:04:33 -04:00
parent e1928863d9
commit a41dbae899
No known key found for this signature in database
52 changed files with 1866 additions and 1047 deletions

View file

@ -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",
],
},
];

View file

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

View file

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

View file

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

View file

@ -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<State>) -> Vec<SpawnedFuture<()>> {
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<State>) -> Vec<SpawnedFuture<()>> {
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",

View file

@ -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")?,

View file

@ -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<f32>,
pub render_intent: RenderIntent,
pub cd: Rc<LinearColorDescription>,
/// 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<dyn GfxTexture>,
pub source: SampleRect,
pub target: FramebufferRect,
pub buffer_resv: Option<Rc<dyn BufferResv>>,
pub acquire_sync: AcquireSync,
pub release_sync: ReleaseSync,
pub alpha: Option<f32>,
pub opaque: bool,
pub render_intent: RenderIntent,
pub cd: Rc<ColorDescription>,
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<OwnedFd>);
@ -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

View file

@ -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<FdSync> {
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<FdSync> {
{
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<EglImage>>, sync: &AcquireSync) {
let Some(sync_file) = sync.get_sync_file() else {
return;

View file

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

View file

@ -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<TexCopyType, StaticMap<TexSourceType, RoundedTexProg>>,
pub(crate) rounded_tex_external:
Option<StaticMap<TexCopyType, StaticMap<TexSourceType, RoundedTexProg>>>,
pub(in crate::gfx_apis::gl) gl_state: RefCell<GfxGlState>,
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(),

View file

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

View file

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

View file

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

View file

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

View file

@ -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<VulkanShader>,
pub(super) out_vert_shader: Option<Rc<VulkanShader>>,
pub(super) out_frag_shader: Option<Rc<VulkanShader>>,
pub(super) rounded_fill_vert_shader: Rc<VulkanShader>,
pub(super) rounded_fill_frag_shader: Rc<VulkanShader>,
pub(super) rounded_tex_vert_shader: Rc<VulkanShader>,
pub(super) rounded_tex_frag_shader: Rc<VulkanShader>,
pub(super) rounded_fill_pipelines: CopyHashMap<vk::Format, FillPipelines>,
pub(super) rounded_tex_pipelines:
StaticMap<VulkanEotf, CopyHashMap<vk::Format, Rc<TexPipelines>>>,
pub(super) tex_descriptor_set_layouts: ArrayVec<Rc<VulkanDescriptorSetLayout>, 2>,
pub(super) out_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
pub(super) defunct: Cell<bool>,
@ -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<VulkanImage>,
index: usize,
target: Point,
source: Point,
buffer_resv: Option<Rc<dyn BufferResv>>,
acquire_sync: Option<AcquireSync>,
release_sync: ReleaseSync,
alpha: f32,
source_type: TexSourceType,
copy_type: TexCopyType,
alpha_mode: AlphaMode,
tex_cd: Rc<ColorDescription>,
color_management_data_address: Option<DeviceAddress>,
eotf_args_address: Option<DeviceAddress>,
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<FillPipelines, VulkanError> {
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::<RoundedFillPushConstants>()
} else {
size_of::<LegacyRoundedFillPushConstants>()
};
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<TexPipelines> {
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<Rc<VulkanPipeline>, 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::<RoundedTexPushConstants>()
} else {
size_of::<LegacyRoundedTexPushConstants>()
};
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,10 +1338,17 @@ impl VulkanRenderer {
}
for ops in memory.ops.values_mut() {
for op in ops {
if let VulkanOp::Tex(c) = op {
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);
}
_ => {}
}
}
}
adj!(&mut memory.blend_buffer_color_management_data_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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

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

View file

@ -1,4 +1,5 @@
#![allow(clippy::excessive_precision)]
#![allow(dead_code)]
use {
crate::{
@ -22,12 +23,14 @@ pub struct Icons {
icons: CopyHashMap<i32, Option<Rc<SizedIcons>>>,
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug, Linearize)]
pub enum IconState {
Active,
Passive,
}
#[allow(dead_code)]
pub struct SizedIcons {
pub pin_unfocused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
pub pin_focused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,

View file

@ -1320,6 +1320,7 @@ impl WlSeatGlobal {
.start_drag(self, origin, source, icon, serial)
}
#[allow(dead_code)]
pub fn start_tile_drag(self: &Rc<Self>, tl: &Rc<dyn ToplevelNode>) {
if self.state.ui_drag_enabled.get() {
self.pointer_owner.start_tile_drag(self, tl);

View file

@ -215,6 +215,7 @@ impl PointerOwnerHolder {
}
}
#[allow(dead_code)]
pub fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
self.owner.get().start_tile_drag(seat, tl);
}
@ -288,6 +289,7 @@ trait PointerOwner {
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
let _ = seat;
}
#[allow(dead_code)]
fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
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<Self>,

View file

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

View file

@ -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<Rc<SizedIcons>>,
pub stretch: Option<(i32, i32)>,
pub corner_radius: Option<CornerRadius>,
}
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,37 +306,10 @@ 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() {
let cr = self.state.theme.corner_radius.get();
let full_h = mb.y2();
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),
@ -393,6 +317,25 @@ impl Renderer<'_> {
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();
if !child.node.node_is_container() && gap != 0 {
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()),
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);
}
} 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);
} 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,
);
}
}
}
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,7 +595,27 @@ 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) {
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);
@ -664,6 +637,7 @@ impl Renderer<'_> {
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],
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,
);
}
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,
None,
x,
y,
None,
None,
self.base.scale,
Some(&bounds),
None,
AcquireSync::None,
ReleaseSync::None,
false,
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));
}

View file

@ -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<f32>,
cd: &Rc<LinearColorDescription>,
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<dyn GfxTexture>,
alpha: Option<f32>,
x: i32,
y: i32,
tpoints: Option<SampleRect>,
tsize: Option<(i32, i32)>,
tscale: Scale,
bounds: Option<&Rect>,
buffer_resv: Option<Rc<dyn BufferResv>>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
cd: &Rc<ColorDescription>,
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);
}

View file

@ -189,10 +189,8 @@ pub struct State {
pub theme: Theme,
pub pending_container_layout: AsyncQueue<Rc<ContainerNode>>,
pub pending_container_render_positions: AsyncQueue<Rc<ContainerNode>>,
pub pending_container_render_title: AsyncQueue<Rc<ContainerNode>>,
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
pub pending_screencast_reallocs_or_reconfigures: AsyncQueue<Rc<JayScreencast>>,
@ -275,6 +273,7 @@ pub struct State {
pub color_manager: Rc<ColorManager>,
pub float_above_fullscreen: Cell<bool>,
pub icons: Icons,
#[allow(dead_code)]
pub show_pin_icon: Cell<bool>,
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<ContainerNode>) {
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<OutputNode>) {
@ -561,8 +556,6 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor {
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
node.title_textures.borrow_mut().clear();
node.schedule_render_titles();
node.node_visit_children(self);
}
fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
@ -685,10 +678,6 @@ impl State {
struct Walker;
impl NodeVisitorBase for Walker {
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
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<WorkspaceNode>) {
@ -702,7 +691,6 @@ impl State {
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
node.title_textures.borrow_mut().clear();
node.node_visit_children(self);
}
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
@ -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<ContainerNode>) {
node.schedule_render_titles();
node.node_visit_children(self);
}
fn visit_output(&mut self, node: &Rc<OutputNode>) {
@ -1917,7 +1899,6 @@ impl State {
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
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);

View file

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

View file

@ -643,6 +643,95 @@ impl Into<ConfigBarPosition> 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<f32> for CornerRadius {
fn from(value: f32) -> Self {
Self {
top_left: value,
top_right: value,
bottom_right: value,
bottom_left: value,
}
}
}
impl From<CornerRadius> 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<Option<Arc<String>>>,
pub title_font: CloneCell<Option<Arc<String>>>,
pub default_font: Arc<String>,
#[allow(dead_code)]
pub show_titles: Cell<bool>,
#[allow(dead_code)]
pub floating_titles: Cell<bool>,
pub bar_position: Cell<BarPosition>,
pub corner_radius: Cell<CornerRadius>,
}
impl Default for Theme {
@ -668,51 +760,23 @@ 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<String> {
self.title_font.get().unwrap_or_else(|| self.font.get())
}
pub fn bar_font(&self) -> Arc<String> {
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
}
}
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
}
}
}
#[derive(Copy, Clone, Debug)]

View file

@ -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<dyn GfxTexture>,
}
#[derive(Default)]
pub struct ContainerRenderData {
pub title_rects: Vec<Rect>,
pub active_title_rects: Vec<Rect>,
pub attention_title_rects: Vec<Rect>,
pub last_active_rect: Option<Rect>,
pub border_rects: Vec<Rect>,
pub underline_rects: Vec<Rect>,
pub titles: SmallMapMut<Scale, Vec<ContainerTitle>, 2>,
}
pub struct ContainerNode {
@ -123,9 +103,8 @@ pub struct ContainerNode {
pub content_width: Cell<i32>,
pub content_height: Cell<i32>,
pub sum_factors: Cell<f64>,
layout_scheduled: Cell<bool>,
pub layout_scheduled: Cell<bool>,
compute_render_positions_scheduled: Cell<bool>,
render_titles_scheduled: Cell<bool>,
num_children: NumCell<usize>,
pub children: LinkedList<ContainerChild>,
focus_history: LinkedList<NodeRef<ContainerChild>>,
@ -135,7 +114,6 @@ pub struct ContainerNode {
cursors: RefCell<AHashMap<CursorType, CursorState>>,
state: Rc<State>,
pub render_data: RefCell<ContainerRenderData>,
scroller: Scroller,
toplevel_data: ToplevelData,
attention_requests: ThresholdCounter,
pub layout_complete: Rc<LazyEventSource>,
@ -154,9 +132,6 @@ pub struct ContainerChild {
pub node: Rc<dyn ToplevelNode>,
pub active: Cell<bool>,
pub attention_requested: Cell<bool>,
title: RefCell<String>,
pub title_tex: RefCell<SmallMapMut<Scale, TextTexture, 2>>,
pub title_rect: Cell<Rect>,
focus_history: Cell<Option<LinkedNode<NodeRef<ContainerChild>>>>,
// fields below only valid in tabbed layout
@ -178,7 +153,6 @@ struct CursorState {
x: i32,
y: i32,
op: Option<SeatOp>,
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<Self>) {
// 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<Self>) {
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<Self>,
seat: &Rc<WlSeatGlobal>,
_seat: &Rc<WlSeatGlobal>,
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
}
} 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<Self>) {
if !self.render_titles_scheduled.replace(true) {
self.state.pending_container_render_title.push(self.clone());
}
}
fn last_active(&self) -> Option<NodeId> {
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<AsyncEvent> {
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<Self>) {
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);
if self.toplevel_data.visible.get() {
self.state.damage(Rect::new_sized_saturating(abs_x, abs_y, cwidth, cheight));
}
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 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<Self>, child: &NodeRef<ContainerChild>) {
@ -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<Self>, 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<Self>, 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<Self>,
node: &NodeRef<ContainerChild>,
@ -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<Self>) {
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<Self>,
id: CursorType,
seat: &Rc<WlSeatGlobal>,
time_usec: u64,
_seat: &Rc<WlSeatGlobal>,
_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<Self>,
source: NodeId,
abs_bounds: Rect,
abs_x: i32,
abs_y: i32,
) -> Option<TileDragDestination> {
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<Self>,
mc: &ContainerChild,
@ -1531,10 +1099,6 @@ impl ContainerNode {
abs_x: i32,
abs_y: i32,
) -> Option<TileDragDestination> {
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<ContainerChild>,
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<State>) {
}
}
pub async fn container_render_titles(state: Rc<State>) {
loop {
let container = state.pending_container_render_title.pop().await;
if container.render_titles_scheduled.get() {
container.render_titles_scheduled.set(false);
container.render_titles().triggered().await;
container.compute_title_data();
}
}
}
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<Self>, 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<Self>, _child: &dyn Node, _title: &str) {
// Titlebars removed; no title tracking needed
}
fn node_do_focus(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, 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<Self>, seat: &Rc<WlSeatGlobal>, 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<Self>, _seat: &Rc<WlSeatGlobal>, _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<i32>,
) {
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()))

View file

@ -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<bool>,
pub seat_state: NodeSeatState,
pub layout_scheduled: Cell<bool>,
pub render_titles_scheduled: Cell<bool>,
pub title_rect: Cell<Rect>,
pub title: RefCell<String>,
pub title_textures: RefCell<SmallMapMut<Scale, TextTexture, 2>>,
cursors: RefCell<AHashMap<CursorType, CursorState>>,
pub attention_requested: Cell<bool>,
pub layout_complete: Rc<LazyEventSource>,
@ -99,17 +90,6 @@ pub async fn float_layout(state: Rc<State>) {
}
}
pub async fn float_titles(state: Rc<State>) {
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<State>,
@ -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>) {
self.schedule_render_titles();
// No title rendering needed
}
pub fn schedule_layout(self: &Rc<Self>) {
@ -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<Self>) {
if !self.render_titles_scheduled.replace(true) {
self.state.pending_float_titles.push(self.clone());
}
}
fn render_title_phase1(&self) -> Rc<AsyncEvent> {
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<Self>,
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<Self>, 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<Self>, _title: &str) {
// No title rendering
}
fn update_child_active(self: &Rc<Self>, 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::<FloatIcon, 1>::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<Self>, _child: &dyn Node, title: &str) {
self.update_child_title(title);
fn node_child_title_changed(self: Rc<Self>, _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<Self>, _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<i32>,
) {
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 {

View file

@ -228,6 +228,7 @@ pub struct Theme {
pub gap: Option<i32>,
pub floating_titles: Option<bool>,
pub title_gap: Option<i32>,
pub corner_radius: Option<f32>,
}
#[derive(Debug, Clone)]

View file

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

View file

@ -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<Self>, dev: InputDevice, actions: &Rc<SwitchActions>) {

View file

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