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:
parent
16aec8f87e
commit
e52a60b3b6
41 changed files with 1417 additions and 364 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue