1
0
Fork 0
forked from wry/wry

wayland: implement scaling

This involves many subsystems:

- config:
    - allow setting the connector scale
    - allow setting the cursor size
- cursors:
    - load server cursors for all requested sizes and scales
- wl_surface:
    - track the output the surface belongs to
    - send wl_surface.enter/leave
- wl_output:
    - implement wl_output.scale
- text:
    - pre-render texts for all used scales
- renderer:
    - properly align scale textures and rectangles
- wp_fractional_scale:
    - new interface for fractional scaling
This commit is contained in:
Julian Orth 2022-05-30 17:00:25 +02:00
parent 16aec8f87e
commit e52a60b3b6
41 changed files with 1417 additions and 364 deletions

View file

@ -22,6 +22,7 @@ use {
numcell::NumCell,
rc_eq::rc_eq,
scroller::Scroller,
smallmap::SmallMapMut,
},
},
ahash::AHashMap,
@ -84,7 +85,7 @@ pub struct ContainerRenderData {
pub last_active_rect: Option<Rect>,
pub border_rects: Vec<Rect>,
pub underline_rects: Vec<Rect>,
pub titles: Vec<ContainerTitle>,
pub titles: SmallMapMut<Fixed, Vec<ContainerTitle>, 2>,
}
pub struct ContainerNode {
@ -634,7 +635,9 @@ impl ContainerNode {
let cwidth = self.width.get();
let cheight = self.height.get();
let ctx = self.state.render_ctx.get();
rd.titles.clear();
for (_, v) in rd.titles.iter_mut() {
v.clear();
}
rd.title_rects.clear();
rd.active_title_rects.clear();
rd.border_rects.clear();
@ -644,6 +647,7 @@ impl ContainerNode {
let mono = self.mono_child.get().is_some();
let split = self.split.get();
let have_active = self.children.iter().any(|c| c.active.get());
let scales = self.state.scales.lock();
for (i, child) in self.children.iter().enumerate() {
let rect = child.title_rect.get();
if i > 0 {
@ -670,20 +674,32 @@ impl ContainerNode {
let rect = Rect::new_sized(rect.x1(), rect.y2(), rect.width(), 1).unwrap();
rd.underline_rects.push(rect);
}
'render_title: {
let title = child.title.borrow_mut();
if th == 0 || rect.width() == 0 || title.is_empty() {
break 'render_title;
}
if let Some(ctx) = &ctx {
match text::render(ctx, rect.width(), th, &font, title.deref(), color) {
Ok(t) => rd.titles.push(ContainerTitle {
x: rect.x1(),
y: rect.y1(),
tex: t,
}),
Err(e) => {
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
let title = child.title.borrow_mut();
for (scale, _) in scales.iter() {
let titles = rd.titles.get_or_default_mut(*scale);
'render_title: {
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);
}
if th == 0 || width == 0 || title.is_empty() {
break 'render_title;
}
if let Some(ctx) = &ctx {
match text::render(ctx, width, th, &font, title.deref(), color, scalef) {
Ok(t) => titles.push(ContainerTitle {
x: rect.x1(),
y: rect.y1(),
tex: t,
}),
Err(e) => {
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
}
}
}
}
@ -693,6 +709,7 @@ impl ContainerNode {
rd.underline_rects
.push(Rect::new_sized(0, th, cwidth, 1).unwrap());
}
rd.titles.remove_if(|_, v| v.is_empty());
}
fn activate_child(self: &Rc<Self>, child: &NodeRef<ContainerChild>) {
@ -1349,8 +1366,7 @@ impl ToplevelNode for ContainerNode {
self.parent.set(parent);
}
fn tl_set_workspace(self: Rc<Self>, ws: &Rc<WorkspaceNode>) {
self.toplevel_data.workspace.set(Some(ws.clone()));
fn tl_set_workspace_ext(self: Rc<Self>, ws: &Rc<WorkspaceNode>) {
for child in self.children.iter() {
child.node.clone().tl_set_workspace(ws);
}

View file

@ -12,7 +12,10 @@ use {
walker::NodeVisitor, ContainingNode, FindTreeResult, FoundNode, Node, NodeId,
StackedNode, ToplevelNode, WorkspaceNode,
},
utils::{clonecell::CloneCell, errorfmt::ErrorFmt, linkedlist::LinkedNode},
utils::{
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
linkedlist::LinkedNode,
},
},
ahash::AHashMap,
std::{
@ -39,7 +42,7 @@ pub struct FloatNode {
pub layout_scheduled: Cell<bool>,
pub render_titles_scheduled: Cell<bool>,
pub title: RefCell<String>,
pub title_texture: CloneCell<Option<Rc<Texture>>>,
pub title_textures: CopyHashMap<Fixed, Rc<Texture>>,
seats: RefCell<AHashMap<SeatId, SeatState>>,
}
@ -106,7 +109,7 @@ impl FloatNode {
layout_scheduled: Cell::new(false),
render_titles_scheduled: Cell::new(false),
title: Default::default(),
title_texture: Default::default(),
title_textures: Default::default(),
seats: Default::default(),
});
floater
@ -174,23 +177,38 @@ impl FloatNode {
let bw = theme.sizes.border_width.get();
let font = theme.font.borrow_mut();
let title = self.title.borrow_mut();
self.title_texture.set(None);
self.title_textures.clear();
let pos = self.position.get();
if pos.width() <= 2 * bw || th == 0 || title.is_empty() {
if pos.width() <= 2 * bw || title.is_empty() {
return;
}
let ctx = match self.state.render_ctx.get() {
Some(c) => c,
_ => return,
};
let texture = match text::render(&ctx, pos.width() - 2 * bw, th, &font, &title, tc) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
return;
let scales = self.state.scales.lock();
for (scale, _) in scales.iter() {
let mut th = th;
let mut scalef = None;
let mut width = pos.width() - 2 * bw;
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);
}
};
self.title_texture.set(Some(texture));
if th == 0 || width == 0 {
continue;
}
let texture = match text::render(&ctx, width, th, &font, &title, tc, scalef) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
return;
}
};
self.title_textures.set(*scale, texture);
}
}
fn pointer_move(self: &Rc<Self>, seat: &Rc<WlSeatGlobal>, x: i32, y: i32) {

View file

@ -11,7 +11,7 @@ use {
},
wl_surface::{
ext_session_lock_surface_v1::ExtSessionLockSurfaceV1,
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, SurfaceSendPreferredScaleVisitor,
},
zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP},
},
@ -51,6 +51,7 @@ pub struct OutputNode {
pub scroll: Scroller,
pub pointer_positions: CopyHashMap<SeatId, (i32, i32)>,
pub lock_surface: CloneCell<Option<Rc<ExtSessionLockSurfaceV1>>>,
pub preferred_scale: Cell<Fixed>,
}
impl OutputNode {
@ -72,6 +73,24 @@ impl OutputNode {
}
}
pub fn set_preferred_scale(&self, scale: Fixed) {
let old_scale = self.preferred_scale.replace(scale);
if scale == old_scale {
return;
}
let legacy_scale = scale.round_up();
if self.global.legacy_scale.replace(legacy_scale) != legacy_scale {
self.global.send_mode();
}
self.state.remove_output_scale(old_scale);
self.state.add_output_scale(scale);
let rect = self.calculate_extents();
self.change_extents_(&rect);
let mut visitor = SurfaceSendPreferredScaleVisitor(scale);
self.node_visit_children(&mut visitor);
self.update_render_data();
}
pub fn update_render_data(&self) {
let mut rd = self.render_data.borrow_mut();
rd.titles.clear();
@ -82,9 +101,19 @@ impl OutputNode {
let font = self.state.theme.font.borrow_mut();
let theme = &self.state.theme;
let th = theme.sizes.title_height.get();
let scale = self.preferred_scale.get();
let scale = if scale != 1 {
Some(scale.to_f64())
} else {
None
};
let mut texture_height = th;
if let Some(scale) = scale {
texture_height = (th as f64 * scale).round() as _;
}
let active_id = self.workspace.get().map(|w| w.id);
let width = self.global.pos.get().width();
rd.underline = Rect::new_sized(0, th, width, 1).unwrap();
let output_width = self.global.pos.get().width();
rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap();
for ws in self.workspaces.iter() {
let mut title_width = th;
'create_texture: {
@ -96,7 +125,15 @@ impl OutputNode {
true => theme.colors.focused_title_text.get(),
false => theme.colors.unfocused_title_text.get(),
};
let title = match text::render_fitting(&ctx, th, &font, &ws.name, tc, false) {
let title = match text::render_fitting(
&ctx,
texture_height,
&font,
&ws.name,
tc,
false,
scale,
) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e));
@ -104,10 +141,14 @@ impl OutputNode {
}
};
let mut x = pos + 1;
if title.width() + 2 > title_width {
title_width = title.width() + 2;
let mut width = title.width();
if let Some(scale) = scale {
width = (width as f64 / scale).round() as _;
}
if width + 2 > title_width {
title_width = width + 2;
} else {
x = pos + (title_width - title.width()) / 2;
x = pos + (title_width - width) / 2;
}
rd.titles.push(OutputTitle {
x1: pos,
@ -137,14 +178,19 @@ impl OutputNode {
break 'set_status;
}
let tc = self.state.theme.colors.bar_text.get();
let title = match text::render_fitting(&ctx, th, &font, &status, tc, true) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render status {}: {}", status, ErrorFmt(e));
break 'set_status;
}
};
let pos = width - title.width() - 1;
let title =
match text::render_fitting(&ctx, texture_height, &font, &status, tc, true, scale) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render status {}: {}", status, ErrorFmt(e));
break 'set_status;
}
};
let mut width = title.width();
if let Some(scale) = scale {
width = (width as f64 / scale).round() as _;
}
let pos = output_width - width - 1;
rd.status = Some(OutputStatus {
tex_x: pos,
tex_y: 0,
@ -266,11 +312,24 @@ impl OutputNode {
return;
}
self.global.mode.set(mode);
let pos = self.global.pos.get();
let rect = Rect::new_sized(pos.x1(), pos.y1(), mode.width, mode.height).unwrap();
let rect = self.calculate_extents();
self.change_extents_(&rect);
}
fn calculate_extents(&self) -> Rect {
let mode = self.global.mode.get();
let mut width = mode.width;
let mut height = mode.height;
let scale = self.preferred_scale.get();
if scale != 1 {
let scale = scale.to_f64();
width = (width as f64 / scale).round() as _;
height = (height as f64 / scale).round() as _;
}
let pos = self.global.pos.get();
pos.with_size(width, height).unwrap()
}
fn change_extents_(&self, rect: &Rect) {
self.global.pos.set(*rect);
self.state.root.update_extents();

View file

@ -12,7 +12,7 @@ use {
Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData,
ToplevelNode,
},
utils::{clonecell::CloneCell, errorfmt::ErrorFmt},
utils::{errorfmt::ErrorFmt, smallmap::SmallMap},
},
std::{cell::Cell, ops::Deref, rc::Rc},
};
@ -23,7 +23,7 @@ pub struct PlaceholderNode {
id: PlaceholderNodeId,
toplevel: ToplevelData,
destroyed: Cell<bool>,
pub texture: CloneCell<Option<Rc<Texture>>>,
pub textures: SmallMap<Fixed, Rc<Texture>, 2>,
}
impl PlaceholderNode {
@ -36,7 +36,7 @@ impl PlaceholderNode {
node.node_client(),
),
destroyed: Default::default(),
texture: Default::default(),
textures: Default::default(),
}
}
@ -45,24 +45,35 @@ impl PlaceholderNode {
}
pub fn update_texture(&self) {
self.texture.set(None);
self.textures.clear();
if let Some(ctx) = self.toplevel.state.render_ctx.get() {
let scales = self.toplevel.state.scales.lock();
let rect = self.toplevel.pos.get();
if rect.width() != 0 && rect.height() != 0 {
let font = format!("monospace {}", rect.width() / 10);
match text::render_fitting(
&ctx,
rect.height(),
&font,
"Fullscreen",
self.toplevel.state.theme.colors.unfocused_title_text.get(),
false,
) {
Ok(t) => {
self.texture.set(Some(t));
}
Err(e) => {
log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e));
for (scale, _) in scales.iter() {
let mut width = rect.width();
let mut height = rect.height();
if *scale != 1 {
let scale = scale.to_f64();
width = (width as f64 * scale).round() as _;
height = (height as f64 * scale).round() as _;
}
if width != 0 && height != 0 {
let font = format!("monospace {}", width / 10);
match text::render_fitting(
&ctx,
height,
&font,
"Fullscreen",
self.toplevel.state.theme.colors.unfocused_title_text.get(),
false,
None,
) {
Ok(t) => {
self.textures.insert(*scale, t);
}
Err(e) => {
log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e));
}
}
}
}

View file

@ -129,6 +129,11 @@ pub trait ToplevelNode: Node {
fn tl_set_workspace(self: Rc<Self>, ws: &Rc<WorkspaceNode>) {
let data = self.tl_data();
data.workspace.set(Some(ws.clone()));
self.tl_set_workspace_ext(ws);
}
fn tl_set_workspace_ext(self: Rc<Self>, ws: &Rc<WorkspaceNode>) {
let _ = ws;
}
fn tl_change_extents(self: Rc<Self>, rect: &Rect) {

View file

@ -4,6 +4,7 @@ use {
ifs::{
wl_output::OutputId,
wl_seat::{NodeSeatState, WlSeatGlobal},
wl_surface::SurfaceSendPreferredScaleVisitor,
},
rect::Rect,
render::Renderer,
@ -16,7 +17,7 @@ use {
linkedlist::{LinkedList, LinkedNode},
},
},
std::{cell::Cell, fmt::Debug, rc::Rc},
std::{cell::Cell, fmt::Debug, ops::Deref, rc::Rc},
};
tree_id!(WorkspaceNodeId);
@ -44,6 +45,17 @@ impl WorkspaceNode {
self.fullscreen.set(None);
}
pub fn set_output(&self, output: &Rc<OutputNode>) {
let old = self.output.set(output.clone());
if old.preferred_scale.get() != output.preferred_scale.get() {
let mut visitor = SurfaceSendPreferredScaleVisitor(output.preferred_scale.get());
self.node_visit_children(&mut visitor);
for stacked in self.stacked.iter() {
stacked.deref().clone().node_visit(&mut visitor);
}
}
}
pub fn set_container(self: &Rc<Self>, container: &Rc<ContainerNode>) {
let pos = self.position.get();
container.clone().tl_change_extents(&pos);