1030 lines
38 KiB
Rust
1030 lines
38 KiB
Rust
use {
|
|
crate::{
|
|
animation::{RetainedContent, RetainedSurface, RetainedToplevel},
|
|
cmm::cmm_render_intent::RenderIntent,
|
|
gfx_api::{AcquireSync, AlphaMode, BufferResv, GfxApiOpt, ReleaseSync, SampleRect},
|
|
ifs::wl_surface::{
|
|
SurfaceBuffer, WlSurface,
|
|
x_surface::xwindow::Xwindow,
|
|
xdg_surface::{XdgSurface, xdg_toplevel::XdgToplevel},
|
|
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
|
|
},
|
|
rect::Rect,
|
|
renderer::renderer_base::RendererBase,
|
|
scale::Scale,
|
|
state::State,
|
|
theme::{Color, CornerRadius},
|
|
tree::{
|
|
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
|
ToplevelNode, ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
|
|
},
|
|
},
|
|
std::{ops::Deref, rc::Rc, slice},
|
|
};
|
|
|
|
pub mod renderer_base;
|
|
|
|
pub struct Renderer<'a> {
|
|
pub base: RendererBase<'a>,
|
|
pub state: &'a State,
|
|
pub logical_extents: Rect,
|
|
pub pixel_extents: Rect,
|
|
pub stretch: Option<(i32, i32)>,
|
|
pub corner_radius: Option<CornerRadius>,
|
|
}
|
|
|
|
impl Renderer<'_> {
|
|
pub fn scale(&self) -> Scale {
|
|
self.base.scale
|
|
}
|
|
|
|
pub fn pixel_extents(&self) -> Rect {
|
|
self.pixel_extents
|
|
}
|
|
|
|
pub fn logical_extents(&self) -> Rect {
|
|
self.logical_extents
|
|
}
|
|
|
|
pub fn render_display(&mut self, display: &DisplayNode, x: i32, y: i32) {
|
|
let ext = display.extents.get();
|
|
let outputs = display.outputs.lock();
|
|
for output in outputs.values() {
|
|
let opos = output.global.pos.get();
|
|
let (ox, oy) = ext.translate(opos.x1(), opos.y1());
|
|
self.render_output(output, x + ox, y + oy);
|
|
}
|
|
}
|
|
|
|
pub fn render_output(&mut self, output: &OutputNode, x: i32, y: i32) {
|
|
if self.state.lock.locked.get() {
|
|
if let Some(surface) = output.lock_surface.get()
|
|
&& surface.surface.buffer.is_some()
|
|
{
|
|
self.render_surface(&surface.surface, x, y, None);
|
|
}
|
|
return;
|
|
}
|
|
let opos = output.global.pos.get();
|
|
macro_rules! render_layer {
|
|
($layer:expr) => {
|
|
for ls in $layer.iter() {
|
|
let pos = ls.output_extents();
|
|
self.render_layer_surface(ls.deref(), x + pos.x1(), y + pos.y1());
|
|
self.base.ops.push(GfxApiOpt::Sync);
|
|
}
|
|
};
|
|
}
|
|
let mut fullscreen = None;
|
|
if let Some(ws) = output.workspace.get() {
|
|
fullscreen = ws.fullscreen.get();
|
|
}
|
|
let theme = &self.state.theme;
|
|
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
|
let srgb = &srgb_srgb.linear;
|
|
let perceptual = RenderIntent::Perceptual;
|
|
if let Some(fs) = &fullscreen {
|
|
fs.node_render(self, x, y, None);
|
|
} else {
|
|
render_layer!(output.layers[0]);
|
|
render_layer!(output.layers[1]);
|
|
let ws = output.workspace.get();
|
|
if self.state.show_bar.get() {
|
|
let non_exclusive_rect_rel = output.non_exclusive_rect_rel.get();
|
|
let (mut x, mut y) = non_exclusive_rect_rel.translate_inv(x, y);
|
|
let bar_rect = output.bar_rect_rel.get();
|
|
let bar_bg = bar_rect.move_(
|
|
x - non_exclusive_rect_rel.x1(),
|
|
y - non_exclusive_rect_rel.y1(),
|
|
);
|
|
let bar_bg = self.base.scale_rect(bar_bg);
|
|
let c = theme.colors.bar_background.get();
|
|
self.base
|
|
.fill_scaled_boxes(slice::from_ref(&bar_bg), &c, None, srgb, perceptual);
|
|
self.base.sync();
|
|
let rd = output.render_data.borrow_mut();
|
|
if let Some(aw) = &rd.active_workspace {
|
|
let c = match aw.captured {
|
|
true => theme.colors.captured_focused_title_background.get(),
|
|
false => theme.colors.focused_title_background.get(),
|
|
};
|
|
self.base
|
|
.fill_boxes2(slice::from_ref(&aw.rect), &c, srgb, perceptual, x, y);
|
|
}
|
|
let mut c = theme.colors.separator.get();
|
|
if let Some(ws) = &ws
|
|
&& ws.seat_state.is_active()
|
|
{
|
|
c = theme.colors.focused_title_background.get();
|
|
}
|
|
self.base.fill_boxes2(
|
|
slice::from_ref(&rd.bar_separator),
|
|
&c,
|
|
srgb,
|
|
perceptual,
|
|
x,
|
|
y,
|
|
);
|
|
let c = theme.colors.unfocused_title_background.get();
|
|
self.base
|
|
.fill_boxes2(&rd.inactive_workspaces, &c, srgb, perceptual, x, y);
|
|
let c = theme.colors.captured_unfocused_title_background.get();
|
|
self.base
|
|
.fill_boxes2(&rd.captured_inactive_workspaces, &c, srgb, perceptual, x, y);
|
|
self.base.sync();
|
|
let c = theme.colors.attention_requested_background.get();
|
|
self.base.fill_boxes2(
|
|
&rd.attention_requested_workspaces,
|
|
&c,
|
|
srgb,
|
|
perceptual,
|
|
x,
|
|
y,
|
|
);
|
|
let scale = output.global.persistent.scale.get();
|
|
for title in &rd.titles {
|
|
let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y);
|
|
self.base.render_texture(
|
|
&title.tex,
|
|
None,
|
|
x,
|
|
y,
|
|
None,
|
|
None,
|
|
scale,
|
|
Some(&bar_bg),
|
|
None,
|
|
AcquireSync::None,
|
|
ReleaseSync::None,
|
|
false,
|
|
self.state.color_manager.srgb_gamma22(),
|
|
perceptual,
|
|
AlphaMode::PremultipliedElectrical,
|
|
);
|
|
}
|
|
x += bar_rect.x1() - non_exclusive_rect_rel.x1();
|
|
y += bar_rect.y1() - non_exclusive_rect_rel.y1();
|
|
if let Some(status) = &rd.status
|
|
&& let Some(texture) = status.tex.texture()
|
|
{
|
|
let (x, y) = self.base.scale_point(x + status.tex_x, y);
|
|
self.base.render_texture(
|
|
&texture,
|
|
None,
|
|
x,
|
|
y,
|
|
None,
|
|
None,
|
|
scale,
|
|
Some(&bar_bg),
|
|
None,
|
|
AcquireSync::None,
|
|
ReleaseSync::None,
|
|
false,
|
|
srgb_srgb,
|
|
perceptual,
|
|
AlphaMode::PremultipliedElectrical,
|
|
);
|
|
}
|
|
for item in output.tray_items.iter() {
|
|
let data = item.data();
|
|
if data.surface.buffer.is_some() {
|
|
let rect = data.rel_pos.get().move_(x, y);
|
|
let bounds = self.base.scale_rect(rect);
|
|
self.render_surface(&data.surface, rect.x1(), rect.y1(), Some(&bounds));
|
|
}
|
|
}
|
|
}
|
|
if let Some(ws) = &ws {
|
|
let ws_rect = output.workspace_rect_rel.get();
|
|
let (x, y) = ws_rect.translate_inv(x, y);
|
|
self.render_workspace(&ws, x, y);
|
|
}
|
|
}
|
|
macro_rules! render_stacked {
|
|
($stack:expr) => {
|
|
for stacked in $stack.iter() {
|
|
if stacked.node_visible() {
|
|
self.base.sync();
|
|
let pos = stacked.node_absolute_position();
|
|
if pos.intersects(&opos) {
|
|
let (x, y) = opos.translate(pos.x1(), pos.y1());
|
|
stacked.node_render(self, x, y, None);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
render_stacked!(self.state.root.stacked);
|
|
// Flush RoundedFillRect ops from container/float borders so they don't
|
|
// sort after (and render on top of) layer-shell CopyTexture ops.
|
|
self.base.sync();
|
|
if fullscreen.is_none() {
|
|
render_layer!(output.layers[2]);
|
|
}
|
|
render_layer!(output.layers[3]);
|
|
render_stacked!(self.state.root.stacked_above_layers);
|
|
if let Some(ws) = output.workspace.get()
|
|
&& ws.render_highlight.get() > 0
|
|
{
|
|
let color = self.state.theme.colors.highlight.get();
|
|
let bounds = output.workspace_rect_rel.get().move_(x, y);
|
|
self.base.sync();
|
|
self.base.fill_boxes(&[bounds], &color, srgb, perceptual);
|
|
}
|
|
}
|
|
|
|
pub fn render_workspace(&mut self, workspace: &WorkspaceNode, x: i32, y: i32) {
|
|
if let Some(node) = workspace.container.get() {
|
|
self.render_container(&node, x, y)
|
|
}
|
|
}
|
|
|
|
pub fn render_placeholder(
|
|
&mut self,
|
|
placeholder: &PlaceholderNode,
|
|
x: i32,
|
|
y: i32,
|
|
bounds: Option<&Rect>,
|
|
) {
|
|
let pos = placeholder.tl_data().pos.get();
|
|
self.base.fill_boxes(
|
|
std::slice::from_ref(&pos.at_point(x, y)),
|
|
&Color::from_srgba_straight(20, 20, 20, 255),
|
|
&self.state.color_manager.srgb_gamma22().linear,
|
|
RenderIntent::Perceptual,
|
|
);
|
|
if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale)
|
|
&& let Some(texture) = tex.texture()
|
|
{
|
|
let (tex_width, tex_height) = texture.size();
|
|
let x = x + (pos.width() - tex_width) / 2;
|
|
let y = y + (pos.height() - tex_height) / 2;
|
|
self.base.render_texture(
|
|
&texture,
|
|
None,
|
|
x,
|
|
y,
|
|
None,
|
|
None,
|
|
self.base.scale,
|
|
bounds,
|
|
None,
|
|
AcquireSync::None,
|
|
ReleaseSync::None,
|
|
false,
|
|
self.state.color_manager.srgb_gamma22(),
|
|
RenderIntent::Perceptual,
|
|
AlphaMode::PremultipliedElectrical,
|
|
);
|
|
}
|
|
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
|
}
|
|
|
|
fn render_tab_bar(&mut self, tab_bar: &TabBar, x: i32, y: i32, _container_width: i32) {
|
|
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
|
let srgb = &srgb_srgb.linear;
|
|
let perceptual = RenderIntent::Perceptual;
|
|
let radius = self.state.theme.sizes.tab_bar_radius.get();
|
|
let border_width = self.state.theme.sizes.tab_bar_border_width.get();
|
|
let text_padding = self.state.theme.sizes.tab_bar_text_padding.get();
|
|
let bar_height = tab_bar.height;
|
|
let render_scale = tab_bar.render_scale;
|
|
|
|
// Vulkan sorts ops: Fill < RoundedFill (by z_order, color) < Tex/RoundedTex (by index).
|
|
// We use:
|
|
// FillRect - tiny strip for Vulkan paint regions (hidden)
|
|
// RoundedFillRect z0 - solid rounded bg
|
|
// RoundedFillRect z1 - rounded border ring (on top of bg)
|
|
// RoundedCopyTexture - title text (on top of everything)
|
|
for entry in &tab_bar.entries {
|
|
let (bg_color, border_color, _text_color) = TabBar::entry_colors(self.state, entry);
|
|
let ex = entry.x.get();
|
|
let ew = entry.width.get();
|
|
let tab_rect = Rect::new_sized_saturating(ex, 0, ew, bar_height);
|
|
let tab_cr = CornerRadius::from(radius as f32);
|
|
|
|
// Tiny FillRect strip to establish Vulkan paint regions (visually hidden
|
|
// behind the RoundedFillRect bg that renders later).
|
|
let strip = Rect::new_sized_saturating(
|
|
ex + radius,
|
|
bar_height / 2,
|
|
(ew - 2 * radius).max(1),
|
|
1,
|
|
);
|
|
self.base
|
|
.fill_boxes2(slice::from_ref(&strip), &bg_color, srgb, perceptual, x, y);
|
|
|
|
// Rounded solid bg fill (z_order=0, renders first among RoundedFill).
|
|
self.base.fill_rounded_rect_z(
|
|
tab_rect.move_(x, y),
|
|
&bg_color,
|
|
None,
|
|
srgb,
|
|
perceptual,
|
|
self.scale_corner_radius(tab_cr),
|
|
0.0,
|
|
0,
|
|
);
|
|
|
|
// Rounded border ring on top (z_order=1, renders after bg).
|
|
if border_width > 0 {
|
|
self.base.fill_rounded_rect_z(
|
|
tab_rect.move_(x, y),
|
|
&border_color,
|
|
None,
|
|
srgb,
|
|
perceptual,
|
|
self.scale_corner_radius(tab_cr),
|
|
self.scale_len(border_width),
|
|
1,
|
|
);
|
|
}
|
|
|
|
// Title text as RoundedCopyTexture (sorts after all RoundedFill).
|
|
let tex_ref = entry.title_texture.borrow();
|
|
if let Some(tex) = tex_ref.as_ref()
|
|
&& let Some(texture) = tex.texture()
|
|
{
|
|
use crate::theme::TabTitleAlign;
|
|
let (tw, _th) = texture.size();
|
|
let tex_width = (tw as f64 / render_scale.to_f64()).round() as i32;
|
|
let tab_inner = ew - 2 * (text_padding + border_width);
|
|
let text_x = match self.state.theme.tab_title_align.get() {
|
|
TabTitleAlign::Start => x + ex + text_padding + border_width,
|
|
TabTitleAlign::Center => {
|
|
x + ex
|
|
+ border_width
|
|
+ (tab_inner.max(0) - tex_width).max(0) / 2
|
|
+ text_padding.min(tab_inner.max(0) / 2)
|
|
}
|
|
TabTitleAlign::End => {
|
|
let end_x = x + ex + ew - tex_width - text_padding - border_width;
|
|
end_x.max(x + ex + border_width)
|
|
}
|
|
};
|
|
let (tx, ty) = self.base.scale_point(text_x, y);
|
|
self.base.render_rounded_texture(
|
|
&texture,
|
|
None,
|
|
tx,
|
|
ty,
|
|
None,
|
|
None,
|
|
render_scale,
|
|
None,
|
|
None,
|
|
AcquireSync::None,
|
|
ReleaseSync::None,
|
|
self.state.color_manager.srgb_gamma22(),
|
|
perceptual,
|
|
AlphaMode::PremultipliedElectrical,
|
|
CornerRadius::from(0.0_f32),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn scale_len(&self, len: i32) -> f32 {
|
|
if self.base.scaled {
|
|
(len as f64 * self.base.scalef).round() as f32
|
|
} else {
|
|
len as f32
|
|
}
|
|
}
|
|
|
|
fn scale_corner_radius(&self, cr: CornerRadius) -> CornerRadius {
|
|
if !self.base.scaled {
|
|
return cr;
|
|
}
|
|
let scale = self.base.scalef as f32;
|
|
CornerRadius {
|
|
top_left: (cr.top_left * scale).round(),
|
|
top_right: (cr.top_right * scale).round(),
|
|
bottom_right: (cr.bottom_right * scale).round(),
|
|
bottom_left: (cr.bottom_left * scale).round(),
|
|
}
|
|
}
|
|
|
|
fn render_rounded_frame(
|
|
&mut self,
|
|
rect: Rect,
|
|
color: &Color,
|
|
corner_radius: CornerRadius,
|
|
border_width: i32,
|
|
x: i32,
|
|
y: i32,
|
|
) {
|
|
if border_width <= 0 {
|
|
return;
|
|
}
|
|
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
|
let srgb = &srgb_srgb.linear;
|
|
let perceptual = RenderIntent::Perceptual;
|
|
if corner_radius.is_zero() {
|
|
let bw = border_width;
|
|
let frame_rects = [
|
|
Rect::new_sized_saturating(rect.x1(), rect.y1(), bw, rect.height()),
|
|
Rect::new_sized_saturating(rect.x2() - bw, rect.y1(), bw, rect.height()),
|
|
Rect::new_sized_saturating(rect.x1(), rect.y1(), rect.width(), bw),
|
|
Rect::new_sized_saturating(rect.x1(), rect.y2() - bw, rect.width(), bw),
|
|
];
|
|
self.base
|
|
.fill_boxes2(&frame_rects, color, srgb, perceptual, x, y);
|
|
} else {
|
|
self.base.fill_rounded_rect(
|
|
rect.move_(x, y),
|
|
color,
|
|
None,
|
|
srgb,
|
|
perceptual,
|
|
self.scale_corner_radius(corner_radius),
|
|
self.scale_len(border_width),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn render_container_decorations(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
|
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
|
let srgb = &srgb_srgb.linear;
|
|
let perceptual = RenderIntent::Perceptual;
|
|
let rd = container.render_data.borrow_mut();
|
|
let c = self.state.theme.colors.border.get();
|
|
self.base
|
|
.fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y);
|
|
}
|
|
|
|
fn presentation_child_body(
|
|
&self,
|
|
container: &ContainerNode,
|
|
child: &Rc<dyn ToplevelNode>,
|
|
body: Rect,
|
|
) -> Rect {
|
|
let abs = body.move_(container.abs_x1.get(), container.abs_y1.get());
|
|
let visual = self
|
|
.state
|
|
.animations
|
|
.visual_rect(child.node_id(), abs, self.state.now_nsec());
|
|
visual.move_(-container.abs_x1.get(), -container.abs_y1.get())
|
|
}
|
|
|
|
fn render_child_or_snapshot(
|
|
&mut self,
|
|
child: &Rc<dyn ToplevelNode>,
|
|
x: i32,
|
|
y: i32,
|
|
bounds: Option<&Rect>,
|
|
) {
|
|
if let Some(retained) = self
|
|
.state
|
|
.animations
|
|
.retained_snapshot(child.node_id(), self.state.now_nsec())
|
|
{
|
|
self.render_retained_toplevel(&retained, x, y, bounds);
|
|
} else {
|
|
child.node_render(self, x, y, bounds);
|
|
}
|
|
}
|
|
|
|
fn render_retained_toplevel(
|
|
&mut self,
|
|
retained: &RetainedToplevel,
|
|
x: i32,
|
|
y: i32,
|
|
bounds: Option<&Rect>,
|
|
) {
|
|
let (x, y) = self
|
|
.base
|
|
.scale_point(x + retained.offset.0, y + retained.offset.1);
|
|
self.render_retained_surface_scaled(&retained.surface, x, y, None, bounds);
|
|
}
|
|
|
|
fn render_retained_surface_scaled(
|
|
&mut self,
|
|
retained: &RetainedSurface,
|
|
x: i32,
|
|
y: i32,
|
|
pos_rel: Option<(i32, i32)>,
|
|
bounds: Option<&Rect>,
|
|
) {
|
|
let stretch = self.stretch.take();
|
|
let corner_radius = self.corner_radius.take();
|
|
let mut size = retained.size;
|
|
if let Some((x_rel, y_rel)) = pos_rel {
|
|
let (x, y) = self.base.scale_point(x_rel, y_rel);
|
|
let (w, h) = self.base.scale_point(x_rel + size.0, y_rel + size.1);
|
|
size = (w - x, h - y);
|
|
} else {
|
|
size = self.base.scale_point(size.0, size.1);
|
|
}
|
|
let mut stretched_source = None;
|
|
if let Some(s) = stretch {
|
|
if let RetainedContent::Texture { source, .. } = &retained.content {
|
|
let mut source = *source;
|
|
if size.0 > 0 && size.1 > 0 {
|
|
let sx = s.0 as f32 / size.0 as f32;
|
|
let sy = s.1 as f32 / size.1 as f32;
|
|
source.x2 *= sx;
|
|
source.y2 *= sy;
|
|
}
|
|
stretched_source = Some(source);
|
|
}
|
|
size = s;
|
|
}
|
|
for child in &retained.below {
|
|
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
|
|
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
|
|
}
|
|
self.corner_radius = corner_radius;
|
|
self.render_retained_content(retained, stretched_source, x, y, size, bounds);
|
|
for child in &retained.above {
|
|
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
|
|
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
|
|
}
|
|
}
|
|
|
|
fn render_retained_content(
|
|
&mut self,
|
|
retained: &RetainedSurface,
|
|
stretched_source: Option<SampleRect>,
|
|
x: i32,
|
|
y: i32,
|
|
size: (i32, i32),
|
|
bounds: Option<&Rect>,
|
|
) {
|
|
let corner_radius = self.corner_radius.take();
|
|
match &retained.content {
|
|
RetainedContent::Texture {
|
|
texture,
|
|
buffer,
|
|
source,
|
|
alpha,
|
|
color_description,
|
|
render_intent,
|
|
alpha_mode,
|
|
opaque,
|
|
} => {
|
|
let source = stretched_source.unwrap_or(*source);
|
|
if let Some(cr) = corner_radius {
|
|
self.base.render_rounded_texture(
|
|
texture,
|
|
*alpha,
|
|
x,
|
|
y,
|
|
Some(source),
|
|
Some(size),
|
|
self.base.scale,
|
|
bounds,
|
|
Some(buffer.clone() as Rc<dyn BufferResv>),
|
|
AcquireSync::Unnecessary,
|
|
buffer.release_sync,
|
|
color_description,
|
|
*render_intent,
|
|
*alpha_mode,
|
|
cr,
|
|
);
|
|
} else {
|
|
self.base.render_texture(
|
|
texture,
|
|
*alpha,
|
|
x,
|
|
y,
|
|
Some(source),
|
|
Some(size),
|
|
self.base.scale,
|
|
bounds,
|
|
Some(buffer.clone() as Rc<dyn BufferResv>),
|
|
AcquireSync::Unnecessary,
|
|
buffer.release_sync,
|
|
*opaque,
|
|
color_description,
|
|
*render_intent,
|
|
*alpha_mode,
|
|
);
|
|
}
|
|
}
|
|
RetainedContent::Color {
|
|
color,
|
|
alpha,
|
|
color_description,
|
|
render_intent,
|
|
} => {
|
|
if let Some(rect) = Rect::new_sized(x, y, size.0, size.1) {
|
|
let rect = match bounds {
|
|
None => rect,
|
|
Some(bounds) => rect.intersect(*bounds),
|
|
};
|
|
if !rect.is_empty() {
|
|
self.base.sync();
|
|
self.base.fill_scaled_boxes(
|
|
&[rect],
|
|
color,
|
|
*alpha,
|
|
&color_description.linear,
|
|
*render_intent,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
|
self.render_container_decorations(container, x, y);
|
|
|
|
if let Some(child) = container.mono_child.get() {
|
|
// Render tab bar if present.
|
|
{
|
|
let tab_bar = container.tab_bar.borrow();
|
|
if let Some(tb) = tab_bar.as_ref() {
|
|
self.render_tab_bar(tb, x, y, container.width.get());
|
|
}
|
|
}
|
|
let mb = container.mono_body.get();
|
|
let visual_mb = self.presentation_child_body(container, &child.node, mb);
|
|
if self.state.theme.sizes.gap.get() != 0 {
|
|
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.active_border.get();
|
|
let c = if child.active.get() {
|
|
&focused_border_color
|
|
} else {
|
|
&border_color
|
|
};
|
|
if !child.node.node_is_container() {
|
|
let frame = Rect::new_sized_saturating(
|
|
visual_mb.x1() - bw,
|
|
visual_mb.y1() - bw,
|
|
visual_mb.width() + 2 * bw,
|
|
visual_mb.height() + 2 * bw,
|
|
);
|
|
self.render_rounded_frame(
|
|
frame,
|
|
c,
|
|
self.state.theme.corner_radius.get(),
|
|
bw,
|
|
x,
|
|
y,
|
|
);
|
|
}
|
|
}
|
|
let body = visual_mb.move_(x, y);
|
|
let body = self.base.scale_rect(body);
|
|
let content = container
|
|
.mono_content
|
|
.get()
|
|
.at_point(visual_mb.x1(), visual_mb.y1());
|
|
self.stretch =
|
|
if content.width() != visual_mb.width() || content.height() != visual_mb.height() {
|
|
Some(self.base.scale_point(visual_mb.width(), visual_mb.height()))
|
|
} 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 bw = self.state.theme.sizes.border_width.get();
|
|
let inner_cr = self.scale_corner_radius(cr.expanded_by(-(bw as f32)));
|
|
self.corner_radius = Some(inner_cr);
|
|
}
|
|
}
|
|
self.render_child_or_snapshot(
|
|
&child.node,
|
|
x + content.x1(),
|
|
y + content.y1(),
|
|
Some(&body),
|
|
);
|
|
self.stretch = None;
|
|
self.corner_radius = None;
|
|
} else {
|
|
let gap = self.state.theme.sizes.gap.get();
|
|
let (bw, border_color, focused_border_color) = if gap != 0 {
|
|
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.active_border.get();
|
|
(bw, border_color, focused_border_color)
|
|
} else {
|
|
(0, Color::SOLID_BLACK, Color::SOLID_BLACK)
|
|
};
|
|
let cr = self.state.theme.corner_radius.get();
|
|
for child in container.children.iter() {
|
|
let layout_body = child.body.get();
|
|
if layout_body.x1() >= container.width.get()
|
|
|| layout_body.y1() >= container.height.get()
|
|
{
|
|
break;
|
|
}
|
|
let body = self.presentation_child_body(container, &child.node, layout_body);
|
|
if gap != 0 {
|
|
let c = if child.border_color_is_focused.get() {
|
|
&focused_border_color
|
|
} else {
|
|
&border_color
|
|
};
|
|
if !child.node.node_is_container() && gap != 0 {
|
|
let frame = Rect::new_sized_saturating(
|
|
body.x1() - bw,
|
|
body.y1() - bw,
|
|
body.width() + 2 * bw,
|
|
body.height() + 2 * bw,
|
|
);
|
|
self.render_rounded_frame(frame, c, cr, bw, x, y);
|
|
}
|
|
}
|
|
let content = child.content.get().at_point(body.x1(), body.y1());
|
|
self.stretch =
|
|
if content.width() != body.width() || content.height() != body.height() {
|
|
Some(self.base.scale_point(body.width(), body.height()))
|
|
} else {
|
|
None
|
|
};
|
|
if !cr.is_zero() && !child.node.node_is_container() && gap != 0 {
|
|
let inner_cr = self.scale_corner_radius(cr.expanded_by(-(bw as f32)));
|
|
self.corner_radius = Some(inner_cr);
|
|
}
|
|
let body = body.move_(x, y);
|
|
let body = self.base.scale_rect(body);
|
|
self.render_child_or_snapshot(
|
|
&child.node,
|
|
x + content.x1(),
|
|
y + content.y1(),
|
|
Some(&body),
|
|
);
|
|
self.stretch = None;
|
|
self.corner_radius = None;
|
|
}
|
|
}
|
|
|
|
self.render_tl_aux(container.tl_data(), None, false);
|
|
}
|
|
|
|
pub fn render_xwindow(&mut self, tl: &Xwindow, x: i32, y: i32, bounds: Option<&Rect>) {
|
|
self.render_surface(&tl.x.surface, x, y, bounds);
|
|
self.render_tl_aux(tl.tl_data(), bounds, true);
|
|
}
|
|
|
|
pub fn render_xdg_toplevel(&mut self, tl: &XdgToplevel, x: i32, y: i32, bounds: Option<&Rect>) {
|
|
self.render_xdg_surface(&tl.xdg, x, y, bounds);
|
|
self.render_tl_aux(tl.tl_data(), bounds, true);
|
|
}
|
|
|
|
pub fn render_xdg_surface(
|
|
&mut self,
|
|
xdg: &XdgSurface,
|
|
mut x: i32,
|
|
mut y: i32,
|
|
bounds: Option<&Rect>,
|
|
) {
|
|
let surface = &xdg.surface;
|
|
let geo = xdg.geometry();
|
|
(x, y) = geo.translate(x, y);
|
|
self.render_surface(surface, x, y, bounds);
|
|
}
|
|
|
|
fn render_tl_aux(
|
|
&mut self,
|
|
tl_data: &ToplevelData,
|
|
bounds: Option<&Rect>,
|
|
render_highlight: bool,
|
|
) {
|
|
if render_highlight {
|
|
self.render_tl_highlight(tl_data, bounds);
|
|
}
|
|
}
|
|
|
|
fn render_tl_highlight(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) {
|
|
if tl_data.render_highlight.get() == 0 {
|
|
return;
|
|
}
|
|
let Some(bounds) = bounds else {
|
|
return;
|
|
};
|
|
let color = self.state.theme.colors.highlight.get();
|
|
self.base.sync();
|
|
self.base.fill_scaled_boxes(
|
|
slice::from_ref(bounds),
|
|
&color,
|
|
None,
|
|
&self.state.color_manager.srgb_gamma22().linear,
|
|
RenderIntent::Perceptual,
|
|
);
|
|
}
|
|
|
|
pub fn render_highlight(&mut self, rect: &Rect) {
|
|
let color = self.state.theme.colors.highlight.get();
|
|
self.base.sync();
|
|
self.base.fill_boxes(
|
|
slice::from_ref(rect),
|
|
&color,
|
|
&self.state.color_manager.srgb_gamma22().linear,
|
|
RenderIntent::Perceptual,
|
|
);
|
|
}
|
|
|
|
pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32, bounds: Option<&Rect>) {
|
|
let (x, y) = self.base.scale_point(x, y);
|
|
self.render_surface_scaled(surface, x, y, None, bounds, false);
|
|
}
|
|
|
|
pub fn render_surface_scaled(
|
|
&mut self,
|
|
surface: &WlSurface,
|
|
x: i32,
|
|
y: i32,
|
|
pos_rel: Option<(i32, i32)>,
|
|
bounds: Option<&Rect>,
|
|
is_subsurface: bool,
|
|
) {
|
|
let stretch = self.stretch.take();
|
|
// Take corner_radius early so it is never leaked on early return
|
|
// and so that below-subsurfaces cannot steal it from the main surface.
|
|
let corner_radius = self.corner_radius.take();
|
|
let children = surface.children.borrow();
|
|
let buffer = match surface.buffer.get() {
|
|
Some(b) => b,
|
|
_ => {
|
|
if !surface.is_cursor() && !is_subsurface {
|
|
log::warn!("surface has no buffer attached");
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
let tpoints = surface.buffer_points_norm.borrow_mut();
|
|
let mut size = surface.buffer_abs_pos.get().size();
|
|
if let Some((x_rel, y_rel)) = pos_rel {
|
|
let (x, y) = self.base.scale_point(x_rel, y_rel);
|
|
let (w, h) = self.base.scale_point(x_rel + size.0, y_rel + size.1);
|
|
size = (w - x, h - y);
|
|
} else {
|
|
size = self.base.scale_point(size.0, size.1);
|
|
}
|
|
let mut tpoints = *tpoints;
|
|
if let Some(s) = stretch {
|
|
if size.0 > 0 && size.1 > 0 {
|
|
let sx = s.0 as f32 / size.0 as f32;
|
|
let sy = s.1 as f32 / size.1 as f32;
|
|
tpoints.x2 *= sx;
|
|
tpoints.y2 *= sy;
|
|
}
|
|
size = s;
|
|
}
|
|
if let Some(children) = children.deref() {
|
|
macro_rules! render {
|
|
($children:expr) => {
|
|
for child in $children.iter() {
|
|
if child.pending.get() {
|
|
continue;
|
|
}
|
|
let pos = child.sub_surface.position.get();
|
|
let (x1, y1) = self.base.scale_point(pos.0, pos.1);
|
|
self.render_surface_scaled(
|
|
&child.sub_surface.surface,
|
|
x + x1,
|
|
y + y1,
|
|
Some(pos),
|
|
bounds,
|
|
true,
|
|
);
|
|
}
|
|
};
|
|
}
|
|
render!(&children.below);
|
|
// Restore corner_radius only for the main surface's render_buffer call.
|
|
self.corner_radius = corner_radius;
|
|
self.render_buffer(surface, &buffer, x, y, tpoints, size, bounds);
|
|
render!(&children.above);
|
|
} else {
|
|
self.corner_radius = corner_radius;
|
|
self.render_buffer(surface, &buffer, x, y, tpoints, size, bounds);
|
|
}
|
|
}
|
|
|
|
pub fn render_buffer(
|
|
&mut self,
|
|
surface: &WlSurface,
|
|
buffer: &Rc<SurfaceBuffer>,
|
|
x: i32,
|
|
y: i32,
|
|
tpoints: SampleRect,
|
|
tsize: (i32, i32),
|
|
bounds: Option<&Rect>,
|
|
) {
|
|
let buf = &buffer.buffer.buf;
|
|
let alpha = surface.alpha();
|
|
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);
|
|
}
|
|
self.base.render_texture(
|
|
&tex,
|
|
alpha,
|
|
x,
|
|
y,
|
|
Some(tpoints),
|
|
Some(tsize),
|
|
self.base.scale,
|
|
bounds,
|
|
Some(buffer.clone()),
|
|
AcquireSync::Unnecessary,
|
|
buffer.release_sync,
|
|
opaque,
|
|
&cd,
|
|
intent,
|
|
alpha_mode,
|
|
);
|
|
}
|
|
} else if let Some(color) = &buf.color {
|
|
if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) {
|
|
let rect = match bounds {
|
|
None => rect,
|
|
Some(bounds) => rect.intersect(*bounds),
|
|
};
|
|
if !rect.is_empty() {
|
|
let color = Color::from_u32(
|
|
cd.eotf, alpha_mode, color[0], color[1], color[2], color[3],
|
|
);
|
|
self.base.sync();
|
|
self.base
|
|
.fill_scaled_boxes(&[rect], &color, alpha, &cd.linear, intent);
|
|
}
|
|
}
|
|
} else {
|
|
log::info!("live buffer has neither a texture nor is a single-pixel buffer");
|
|
}
|
|
}
|
|
|
|
pub fn render_floating(&mut self, floating: &FloatNode, x: i32, y: i32) {
|
|
let child = match floating.child.get() {
|
|
Some(c) => c,
|
|
_ => return,
|
|
};
|
|
let pos = floating.position.get();
|
|
let theme = &self.state.theme;
|
|
let bw = theme.sizes.border_width.get();
|
|
let bc = if floating.active.get() {
|
|
theme.colors.active_border.get()
|
|
} else {
|
|
theme.colors.border.get()
|
|
};
|
|
let cr = theme.corner_radius.get();
|
|
let outer = Rect::new_sized_saturating(0, 0, pos.width(), pos.height());
|
|
self.render_rounded_frame(outer, &bc, cr, bw, x, y);
|
|
let body =
|
|
Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, pos.height() - 2 * bw);
|
|
let scissor_body = self.base.scale_rect(body);
|
|
if !cr.is_zero() {
|
|
let inner_cr = self.scale_corner_radius(cr.expanded_by(-(bw as f32)));
|
|
self.corner_radius = Some(inner_cr);
|
|
}
|
|
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
|
|
self.corner_radius = None;
|
|
}
|
|
|
|
pub fn render_layer_surface(&mut self, surface: &ZwlrLayerSurfaceV1, x: i32, y: i32) {
|
|
let (dx, dy) = surface.surface.extents.get().position();
|
|
self.render_surface(&surface.surface, x - dx, y - dy, None);
|
|
}
|
|
|
|
fn bounds_are_opaque(
|
|
&self,
|
|
x: i32,
|
|
y: i32,
|
|
bounds: Option<&Rect>,
|
|
surface: &WlSurface,
|
|
) -> bool {
|
|
let Some(bounds) = bounds else {
|
|
return false;
|
|
};
|
|
let Some(region) = surface.opaque_region() else {
|
|
return false;
|
|
};
|
|
let surface_size = surface.buffer_abs_pos.get().at_point(0, 0);
|
|
let surface_size = self.base.scale_rect(surface_size);
|
|
let bounds = bounds.move_(-x, -y).intersect(surface_size);
|
|
region.contains_rect2(&bounds, |r| self.base.scale_rect(*r))
|
|
}
|
|
}
|