mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
layout: Refactor to support window decorations, add border and fullscreen backdrop
Windows are now wrapped in Tiles, which keep track of window-specific decorations. Particularly, I implemented a black fullscreen backdrop, which finally brings fullscreened windows smaller than the screen in line with how the Wayland protocol says they should look—centered in a black rectangle. I also implemented window borders, which are similar to the focus ring, but always visible (and hence affect the layout and sizing).
This commit is contained in:
@@ -89,6 +89,17 @@ focus-ring {
|
||||
inactive-color 80 80 80 255
|
||||
}
|
||||
|
||||
// You can also add a border. It's similar to the focus ring, but always visible.
|
||||
border {
|
||||
// The settings are the same as for the focus ring.
|
||||
// If you enable the border, you probably want to disable the focus ring.
|
||||
off
|
||||
|
||||
width 4
|
||||
active-color 255 200 127 255
|
||||
inactive-color 80 80 80 255
|
||||
}
|
||||
|
||||
cursor {
|
||||
// Change the theme and size of the cursor as well as set the
|
||||
// `XCURSOR_THEME` and `XCURSOR_SIZE` env variables.
|
||||
|
||||
+34
-1
@@ -18,6 +18,8 @@ pub struct Config {
|
||||
pub spawn_at_startup: Vec<SpawnAtStartup>,
|
||||
#[knuffel(child, default)]
|
||||
pub focus_ring: FocusRing,
|
||||
#[knuffel(child, default = default_border())]
|
||||
pub border: FocusRing,
|
||||
#[knuffel(child, default)]
|
||||
pub prefer_no_csd: bool,
|
||||
#[knuffel(child, default)]
|
||||
@@ -190,6 +192,15 @@ impl Default for FocusRing {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn default_border() -> FocusRing {
|
||||
FocusRing {
|
||||
off: true,
|
||||
width: 4,
|
||||
active_color: Color::new(255, 200, 127, 255),
|
||||
inactive_color: Color::new(80, 80, 80, 255),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Color {
|
||||
#[knuffel(argument)]
|
||||
@@ -203,7 +214,7 @@ pub struct Color {
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
}
|
||||
@@ -590,6 +601,12 @@ mod tests {
|
||||
inactive-color 255 200 100 0
|
||||
}
|
||||
|
||||
border {
|
||||
width 3
|
||||
active-color 0 100 200 255
|
||||
inactive-color 255 200 100 0
|
||||
}
|
||||
|
||||
prefer-no-csd
|
||||
|
||||
cursor {
|
||||
@@ -680,6 +697,22 @@ mod tests {
|
||||
a: 0,
|
||||
},
|
||||
},
|
||||
border: FocusRing {
|
||||
off: false,
|
||||
width: 3,
|
||||
active_color: Color {
|
||||
r: 0,
|
||||
g: 100,
|
||||
b: 200,
|
||||
a: 255,
|
||||
},
|
||||
inactive_color: Color {
|
||||
r: 255,
|
||||
g: 200,
|
||||
b: 100,
|
||||
a: 0,
|
||||
},
|
||||
},
|
||||
prefer_no_csd: true,
|
||||
cursor: Cursor {
|
||||
xcursor_theme: String::from("breeze_cursors"),
|
||||
|
||||
@@ -105,4 +105,12 @@ impl FocusRing {
|
||||
|
||||
rv.into_iter()
|
||||
}
|
||||
|
||||
pub fn width(&self) -> i32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn is_off(&self) -> bool {
|
||||
self.is_off
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -56,6 +56,7 @@ use crate::utils::output_size;
|
||||
|
||||
mod focus_ring;
|
||||
mod monitor;
|
||||
mod tile;
|
||||
mod workspace;
|
||||
|
||||
pub trait LayoutElement: PartialEq {
|
||||
@@ -97,6 +98,11 @@ pub trait LayoutElement: PartialEq {
|
||||
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform);
|
||||
fn output_enter(&self, output: &Output);
|
||||
fn output_leave(&self, output: &Output);
|
||||
|
||||
/// Whether the element is currently fullscreen.
|
||||
///
|
||||
/// This will *not* switch immediately after a [`LayoutElement::request_fullscreen()`] call.
|
||||
fn is_fullscreen(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -132,6 +138,7 @@ pub struct Options {
|
||||
/// Extra padding around the working area in logical pixels.
|
||||
struts: Struts,
|
||||
focus_ring: config::FocusRing,
|
||||
border: config::FocusRing,
|
||||
/// Column widths that `toggle_width()` switches between.
|
||||
preset_widths: Vec<ColumnWidth>,
|
||||
/// Initial width for new windows.
|
||||
@@ -144,6 +151,7 @@ impl Default for Options {
|
||||
gaps: 16,
|
||||
struts: Default::default(),
|
||||
focus_ring: Default::default(),
|
||||
border: config::default_border(),
|
||||
preset_widths: vec![
|
||||
ColumnWidth::Proportion(1. / 3.),
|
||||
ColumnWidth::Proportion(0.5),
|
||||
@@ -180,6 +188,7 @@ impl Options {
|
||||
gaps: config.gaps.into(),
|
||||
struts: config.struts,
|
||||
focus_ring: config.focus_ring,
|
||||
border: config.border,
|
||||
preset_widths,
|
||||
default_width,
|
||||
}
|
||||
@@ -269,6 +278,13 @@ impl LayoutElement for Window {
|
||||
fn output_leave(&self, output: &Output) {
|
||||
SpaceElement::output_leave(self, output)
|
||||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
self.toplevel()
|
||||
.current_state()
|
||||
.states
|
||||
.contains(xdg_toplevel::State::Fullscreen)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Layout<W> {
|
||||
@@ -701,7 +717,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
|
||||
let col = &ws.columns[ws.active_column_idx];
|
||||
Some((&col.windows[col.active_window_idx], &mon.output))
|
||||
Some((&col.windows[col.active_window_idx].window(), &mon.output))
|
||||
}
|
||||
|
||||
pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ {
|
||||
@@ -1458,6 +1474,10 @@ mod tests {
|
||||
fn output_enter(&self, _output: &Output) {}
|
||||
|
||||
fn output_leave(&self, _output: &Output) {}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
|
||||
|
||||
@@ -345,7 +345,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
Some(&column.windows[column.active_window_idx])
|
||||
Some(column.windows[column.active_window_idx].window())
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
use std::cmp::max;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::{ImportAll, Renderer};
|
||||
use smithay::utils::{Logical, Point, Scale, Size};
|
||||
|
||||
use super::focus_ring::FocusRing;
|
||||
use super::workspace::WorkspaceRenderElement;
|
||||
use super::{LayoutElement, Options};
|
||||
|
||||
/// Toplevel window with decorations.
|
||||
#[derive(Debug)]
|
||||
pub struct Tile<W: LayoutElement> {
|
||||
/// The toplevel window itself.
|
||||
window: W,
|
||||
|
||||
/// The border around the window.
|
||||
border: FocusRing,
|
||||
|
||||
/// Whether this tile is fullscreen.
|
||||
///
|
||||
/// This will update only when the `window` actually goes fullscreen, rather than right away,
|
||||
/// to avoid black backdrop flicker before the window has had a chance to resize.
|
||||
is_fullscreen: bool,
|
||||
|
||||
/// The black backdrop for fullscreen windows.
|
||||
fullscreen_backdrop: SolidColorBuffer,
|
||||
|
||||
/// The size we were requested to fullscreen into.
|
||||
fullscreen_size: Size<i32, Logical>,
|
||||
|
||||
/// Configurable properties of the layout.
|
||||
options: Rc<Options>,
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Tile<W> {
|
||||
pub fn new(window: W, options: Rc<Options>) -> Self {
|
||||
Self {
|
||||
window,
|
||||
border: FocusRing::new(options.border),
|
||||
is_fullscreen: false, // FIXME: up-to-date fullscreen right away, but we need size.
|
||||
fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]),
|
||||
fullscreen_size: Default::default(),
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_config(&mut self, options: Rc<Options>) {
|
||||
self.border.update_config(options.border);
|
||||
self.options = options;
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self, _current_time: Duration, is_active: bool) {
|
||||
let width = self.border.width();
|
||||
self.border.update(
|
||||
(width, width).into(),
|
||||
self.window.size(),
|
||||
self.window.has_ssd(),
|
||||
);
|
||||
self.border.set_active(is_active);
|
||||
|
||||
// FIXME: remove when we can get a fullscreen size right away.
|
||||
if self.fullscreen_size != Size::from((0, 0)) {
|
||||
self.is_fullscreen = self.window.is_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window(&self) -> &W {
|
||||
&self.window
|
||||
}
|
||||
|
||||
pub fn into_window(self) -> W {
|
||||
self.window
|
||||
}
|
||||
|
||||
/// Returns `None` if the border is hidden and `Some(width)` if it should be shown.
|
||||
fn effective_border_width(&self) -> Option<i32> {
|
||||
if self.is_fullscreen {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.border.is_off() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(self.border.width())
|
||||
}
|
||||
|
||||
/// Returns the location of the window's visual geometry within this Tile.
|
||||
fn window_loc(&self) -> Point<i32, Logical> {
|
||||
let mut loc = Point::from((0, 0));
|
||||
|
||||
// In fullscreen, center the window in the given size.
|
||||
if self.is_fullscreen {
|
||||
let window_size = self.window.size();
|
||||
let target_size = self.fullscreen_size;
|
||||
|
||||
// Windows aren't supposed to be larger than the fullscreen size, but in case we get
|
||||
// one, leave it at the top-left as usual.
|
||||
if window_size.w < target_size.w {
|
||||
loc.x += (target_size.w - window_size.w) / 2;
|
||||
}
|
||||
if window_size.h < target_size.h {
|
||||
loc.y += (target_size.h - window_size.h) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
loc += (width, width).into();
|
||||
}
|
||||
|
||||
loc
|
||||
}
|
||||
|
||||
pub fn tile_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.size();
|
||||
|
||||
if self.is_fullscreen {
|
||||
// Normally we'd just return the fullscreen size here, but this makes things a bit
|
||||
// nicer if a fullscreen window is bigger than the fullscreen size for some reason.
|
||||
size.w = max(size.w, self.fullscreen_size.w);
|
||||
size.h = max(size.h, self.fullscreen_size.h);
|
||||
return size;
|
||||
}
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
size += (width * 2, width * 2).into();
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
pub fn window_size(&self) -> Size<i32, Logical> {
|
||||
self.window.size()
|
||||
}
|
||||
|
||||
pub fn buf_loc(&self) -> Point<i32, Logical> {
|
||||
let mut loc = Point::from((0, 0));
|
||||
loc += self.window_loc();
|
||||
loc += self.window.buf_loc();
|
||||
loc
|
||||
}
|
||||
|
||||
pub fn is_in_input_region(&self, mut point: Point<f64, Logical>) -> bool {
|
||||
point -= self.window_loc().to_f64();
|
||||
self.window.is_in_input_region(point)
|
||||
}
|
||||
|
||||
pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>) {
|
||||
// Can't go through effective_border_width() because we might be fullscreen.
|
||||
if !self.border.is_off() {
|
||||
let width = self.border.width();
|
||||
size.w = max(1, size.w - width * 2);
|
||||
size.h = max(1, size.h - width * 2);
|
||||
}
|
||||
|
||||
self.window.request_size(size);
|
||||
}
|
||||
|
||||
pub fn tile_width_for_window_width(&self, size: i32) -> i32 {
|
||||
if self.border.is_off() {
|
||||
size
|
||||
} else {
|
||||
size + self.border.width() * 2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tile_height_for_window_height(&self, size: i32) -> i32 {
|
||||
if self.border.is_off() {
|
||||
size
|
||||
} else {
|
||||
size + self.border.width() * 2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_height_for_tile_height(&self, size: i32) -> i32 {
|
||||
if self.border.is_off() {
|
||||
size
|
||||
} else {
|
||||
size - self.border.width() * 2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&mut self, size: Size<i32, Logical>) {
|
||||
self.fullscreen_backdrop.resize(size);
|
||||
self.fullscreen_size = size;
|
||||
self.window.request_fullscreen(size);
|
||||
}
|
||||
|
||||
pub fn min_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.min_size();
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
size.w = max(1, size.w);
|
||||
size.h = max(1, size.h);
|
||||
size += (width * 2, width * 2).into();
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
pub fn max_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.max_size();
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
if size.w > 0 {
|
||||
size.w += width * 2;
|
||||
}
|
||||
if size.h > 0 {
|
||||
size.h += width * 2;
|
||||
}
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
pub fn has_ssd(&self) -> bool {
|
||||
self.effective_border_width().is_some() || self.window.has_ssd()
|
||||
}
|
||||
|
||||
pub fn render<R: Renderer + ImportAll>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
) -> Vec<WorkspaceRenderElement<R>>
|
||||
where
|
||||
<R as Renderer>::TextureId: 'static,
|
||||
{
|
||||
let mut rv = Vec::new();
|
||||
|
||||
let window_pos = location + self.window_loc();
|
||||
rv.extend(self.window.render(renderer, window_pos, scale));
|
||||
|
||||
if self.effective_border_width().is_some() {
|
||||
rv.extend(
|
||||
self.border
|
||||
.render(scale)
|
||||
.map(|elem| {
|
||||
RelocateRenderElement::from_element(
|
||||
elem,
|
||||
location.to_physical_precise_round(scale),
|
||||
Relocate::Relative,
|
||||
)
|
||||
})
|
||||
.map(Into::into),
|
||||
);
|
||||
}
|
||||
|
||||
if self.is_fullscreen {
|
||||
let elem = SolidColorRenderElement::from_buffer(
|
||||
&self.fullscreen_backdrop,
|
||||
location.to_physical_precise_round(scale),
|
||||
scale,
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
);
|
||||
rv.push(elem.into());
|
||||
}
|
||||
|
||||
rv
|
||||
}
|
||||
}
|
||||
+112
-41
@@ -4,6 +4,7 @@ use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
||||
use smithay::backend::renderer::element::utils::RelocateRenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::backend::renderer::ImportAll;
|
||||
use smithay::desktop::space::SpaceElement;
|
||||
@@ -14,6 +15,7 @@ use smithay::render_elements;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
||||
|
||||
use super::focus_ring::{FocusRing, FocusRingRenderElement};
|
||||
use super::tile::Tile;
|
||||
use super::{LayoutElement, Options};
|
||||
use crate::animation::Animation;
|
||||
use crate::config::{PresetWidth, SizeChange, Struts};
|
||||
@@ -83,6 +85,7 @@ render_elements! {
|
||||
pub WorkspaceRenderElement<R> where R: ImportAll;
|
||||
Wayland = WaylandSurfaceRenderElement<R>,
|
||||
FocusRing = FocusRingRenderElement,
|
||||
Border = RelocateRenderElement<FocusRingRenderElement>,
|
||||
}
|
||||
|
||||
/// Width of a column.
|
||||
@@ -121,17 +124,19 @@ pub enum WindowHeight {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Column<W: LayoutElement> {
|
||||
/// Windows in this column.
|
||||
/// Tiles in this column.
|
||||
///
|
||||
/// Must be non-empty.
|
||||
pub windows: Vec<W>,
|
||||
pub windows: Vec<Tile<W>>,
|
||||
|
||||
/// Heights of the windows.
|
||||
///
|
||||
/// Must have the same number of elements as `windows`.
|
||||
///
|
||||
/// These heights are window heights, so they exclude tile decorations, if any.
|
||||
heights: Vec<WindowHeight>,
|
||||
|
||||
/// Index of the currently active window.
|
||||
/// Index of the currently active tile.
|
||||
pub active_window_idx: usize,
|
||||
|
||||
/// Desired width of this column.
|
||||
@@ -231,11 +236,20 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
|
||||
let view_pos = self.view_pos();
|
||||
|
||||
for (col_idx, col) in self.columns.iter_mut().enumerate() {
|
||||
for (win_idx, tile) in col.windows.iter_mut().enumerate() {
|
||||
let is_active = is_active
|
||||
&& col_idx == self.active_column_idx
|
||||
&& win_idx == col.active_window_idx;
|
||||
tile.advance_animations(current_time, is_active);
|
||||
}
|
||||
}
|
||||
|
||||
// This shall one day become a proper animation.
|
||||
if !self.columns.is_empty() {
|
||||
let col = &self.columns[self.active_column_idx];
|
||||
let active_win = &col.windows[col.active_window_idx];
|
||||
let size = active_win.size();
|
||||
let size = active_win.tile_size();
|
||||
let has_ssd = active_win.has_ssd();
|
||||
|
||||
let win_pos = Point::from((
|
||||
@@ -264,7 +278,10 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> impl Iterator<Item = &W> + '_ {
|
||||
self.columns.iter().flat_map(|col| col.windows.iter())
|
||||
self.columns
|
||||
.iter()
|
||||
.flat_map(|col| col.windows.iter())
|
||||
.map(Tile::window)
|
||||
}
|
||||
|
||||
pub fn set_output(&mut self, output: Option<Output>) {
|
||||
@@ -315,20 +332,33 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
}
|
||||
|
||||
fn toplevel_bounds(&self) -> Size<i32, Logical> {
|
||||
let mut border = 0;
|
||||
if !self.options.border.off {
|
||||
border = self.options.border.width as i32 * 2;
|
||||
}
|
||||
|
||||
Size::from((
|
||||
max(self.working_area.size.w - self.options.gaps * 2, 1),
|
||||
max(self.working_area.size.h - self.options.gaps * 2, 1),
|
||||
max(self.working_area.size.w - self.options.gaps * 2 - border, 1),
|
||||
max(self.working_area.size.h - self.options.gaps * 2 - border, 1),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn configure_new_window(&self, window: &Window) {
|
||||
let width = if let Some(width) = self.options.default_width {
|
||||
max(1, width.resolve(&self.options, self.working_area.size.w))
|
||||
let mut width = width.resolve(&self.options, self.working_area.size.w);
|
||||
if !self.options.border.off {
|
||||
width -= self.options.border.width as i32 * 2;
|
||||
}
|
||||
max(1, width)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let height = self.working_area.size.h - self.options.gaps * 2;
|
||||
let mut height = self.working_area.size.h - self.options.gaps * 2;
|
||||
if !self.options.border.off {
|
||||
height -= self.options.border.width as i32 * 2;
|
||||
}
|
||||
|
||||
let size = Size::from((width, max(height, 1)));
|
||||
|
||||
let bounds = self.toplevel_bounds();
|
||||
@@ -478,7 +508,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
|
||||
pub fn remove_window_by_idx(&mut self, column_idx: usize, window_idx: usize) -> W {
|
||||
let column = &mut self.columns[column_idx];
|
||||
let window = column.windows.remove(window_idx);
|
||||
let window = column.windows.remove(window_idx).into_window();
|
||||
column.heights.remove(window_idx);
|
||||
|
||||
if let Some(output) = &self.output {
|
||||
@@ -746,20 +776,20 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
col.window_y(col.active_window_idx),
|
||||
));
|
||||
if active_win.is_in_input_region(pos - win_pos.to_f64()) {
|
||||
return Some((active_win, win_pos + active_win.buf_loc()));
|
||||
return Some((active_win.window(), win_pos + active_win.buf_loc()));
|
||||
}
|
||||
|
||||
let mut x = -view_pos;
|
||||
for col in &self.columns {
|
||||
for (win, y) in zip(&col.windows, col.window_ys()) {
|
||||
if win == active_win {
|
||||
if win.window() == active_win.window() {
|
||||
// Already handled it above.
|
||||
continue;
|
||||
}
|
||||
|
||||
let win_pos = Point::from((x, y));
|
||||
if win.is_in_input_region(pos - win_pos.to_f64()) {
|
||||
return Some((win, win_pos + win.buf_loc()));
|
||||
return Some((win.window(), win_pos + win.buf_loc()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,7 +845,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
// This wasn't the only window in its column; extract it into a separate column.
|
||||
let target_window_was_focused =
|
||||
self.active_column_idx == col_idx && col.active_window_idx == win_idx;
|
||||
let window = col.windows.remove(win_idx);
|
||||
let window = col.windows.remove(win_idx).into_window();
|
||||
col.heights.remove(win_idx);
|
||||
col.active_window_idx = min(col.active_window_idx, col.windows.len() - 1);
|
||||
col.update_window_sizes();
|
||||
@@ -872,7 +902,8 @@ impl Workspace<Window> {
|
||||
let bounds = self.toplevel_bounds();
|
||||
|
||||
for (col_idx, col) in self.columns.iter().enumerate() {
|
||||
for (win_idx, win) in col.windows.iter().enumerate() {
|
||||
for (win_idx, tile) in col.windows.iter().enumerate() {
|
||||
let win = tile.window();
|
||||
let active = self.active_column_idx == col_idx && col.active_window_idx == win_idx;
|
||||
win.set_activated(active);
|
||||
|
||||
@@ -922,7 +953,7 @@ impl Workspace<Window> {
|
||||
let mut x = -view_pos;
|
||||
for col in &self.columns {
|
||||
for (win, y) in zip(&col.windows, col.window_ys()) {
|
||||
if win == active_win {
|
||||
if win.window() == active_win.window() {
|
||||
// Already handled it above.
|
||||
continue;
|
||||
}
|
||||
@@ -989,6 +1020,16 @@ impl<W: LayoutElement> Column<W> {
|
||||
update_sizes = true;
|
||||
}
|
||||
|
||||
if self.options.border.off != options.border.off
|
||||
|| self.options.border.width != options.border.width
|
||||
{
|
||||
update_sizes = true;
|
||||
}
|
||||
|
||||
for tile in &mut self.windows {
|
||||
tile.update_config(options.clone());
|
||||
}
|
||||
|
||||
self.options = options;
|
||||
|
||||
if update_sizes {
|
||||
@@ -1003,11 +1044,17 @@ impl<W: LayoutElement> Column<W> {
|
||||
}
|
||||
|
||||
pub fn contains(&self, window: &W) -> bool {
|
||||
self.windows.iter().any(|win| win == window)
|
||||
self.windows
|
||||
.iter()
|
||||
.map(Tile::window)
|
||||
.any(|win| win == window)
|
||||
}
|
||||
|
||||
pub fn position(&self, window: &W) -> Option<usize> {
|
||||
self.windows.iter().position(|win| win == window)
|
||||
self.windows
|
||||
.iter()
|
||||
.map(Tile::window)
|
||||
.position(|win| win == window)
|
||||
}
|
||||
|
||||
fn activate_window(&mut self, window: &W) {
|
||||
@@ -1016,8 +1063,9 @@ impl<W: LayoutElement> Column<W> {
|
||||
}
|
||||
|
||||
fn add_window(&mut self, window: W) {
|
||||
let tile = Tile::new(window, self.options.clone());
|
||||
self.is_fullscreen = false;
|
||||
self.windows.push(window);
|
||||
self.windows.push(tile);
|
||||
self.heights.push(WindowHeight::Auto);
|
||||
self.update_window_sizes();
|
||||
}
|
||||
@@ -1028,8 +1076,8 @@ impl<W: LayoutElement> Column<W> {
|
||||
return;
|
||||
}
|
||||
|
||||
let min_size: Vec<_> = self.windows.iter().map(LayoutElement::min_size).collect();
|
||||
let max_size: Vec<_> = self.windows.iter().map(LayoutElement::max_size).collect();
|
||||
let min_size: Vec<_> = self.windows.iter().map(Tile::min_size).collect();
|
||||
let max_size: Vec<_> = self.windows.iter().map(Tile::max_size).collect();
|
||||
|
||||
// Compute the column width.
|
||||
let min_width = min_size
|
||||
@@ -1067,8 +1115,15 @@ impl<W: LayoutElement> Column<W> {
|
||||
let width = width.resolve(&self.options, self.working_area.size.w);
|
||||
let width = max(min(width, max_width), min_width);
|
||||
|
||||
// Compute the window heights.
|
||||
let mut heights = self.heights.clone();
|
||||
// Compute the window heights. Start by converting window heights to tile heights.
|
||||
let mut heights = zip(&self.windows, &self.heights)
|
||||
.map(|(tile, height)| match *height {
|
||||
WindowHeight::Auto => WindowHeight::Auto,
|
||||
WindowHeight::Fixed(height) => {
|
||||
WindowHeight::Fixed(tile.tile_height_for_window_height(height))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut height_left = self.working_area.size.h - self.options.gaps;
|
||||
let mut auto_windows_left = self.windows.len();
|
||||
|
||||
@@ -1162,18 +1217,22 @@ impl<W: LayoutElement> Column<W> {
|
||||
assert_eq!(auto_windows_left, 0);
|
||||
}
|
||||
|
||||
for (win, h) in zip(&self.windows, heights) {
|
||||
for (tile, h) in zip(&mut self.windows, heights) {
|
||||
let WindowHeight::Fixed(height) = h else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let size = Size::from((width, height));
|
||||
win.request_size(size);
|
||||
tile.request_tile_size(size);
|
||||
}
|
||||
}
|
||||
|
||||
fn width(&self) -> i32 {
|
||||
self.windows.iter().map(|win| win.size().w).max().unwrap()
|
||||
self.windows
|
||||
.iter()
|
||||
.map(|win| win.tile_size().w)
|
||||
.max()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn focus_up(&mut self) {
|
||||
@@ -1265,7 +1324,13 @@ impl<W: LayoutElement> Column<W> {
|
||||
const MAX_F: f64 = 10000.;
|
||||
|
||||
let width = match (current, change) {
|
||||
(_, SizeChange::SetFixed(fixed)) => ColumnWidth::Fixed(fixed.clamp(1, MAX_PX)),
|
||||
(_, SizeChange::SetFixed(fixed)) => {
|
||||
// As a special case, setting a fixed column width will compute it in such a way
|
||||
// that the active window gets that width. This is the intention behind the ability
|
||||
// to set a fixed size.
|
||||
let tile = &self.windows[self.active_window_idx];
|
||||
ColumnWidth::Fixed(tile.tile_width_for_window_width(fixed).clamp(1, MAX_PX))
|
||||
}
|
||||
(_, SizeChange::SetProportion(proportion)) => {
|
||||
ColumnWidth::Proportion((proportion / 100.).clamp(0., MAX_F))
|
||||
}
|
||||
@@ -1291,45 +1356,51 @@ impl<W: LayoutElement> Column<W> {
|
||||
|
||||
fn set_window_height(&mut self, change: SizeChange) {
|
||||
let current = self.heights[self.active_window_idx];
|
||||
let current_px = match current {
|
||||
WindowHeight::Auto => self.windows[self.active_window_idx].size().h,
|
||||
let tile = &self.windows[self.active_window_idx];
|
||||
let current_window_px = match current {
|
||||
WindowHeight::Auto => tile.window_size().h,
|
||||
WindowHeight::Fixed(height) => height,
|
||||
};
|
||||
let current_prop = (current_px + self.options.gaps) as f64
|
||||
let current_tile_px = tile.tile_height_for_window_height(current_window_px);
|
||||
let current_prop = (current_tile_px + self.options.gaps) as f64
|
||||
/ (self.working_area.size.h - self.options.gaps) as f64;
|
||||
|
||||
// FIXME: fix overflows then remove limits.
|
||||
const MAX_PX: i32 = 100000;
|
||||
|
||||
let mut height = match change {
|
||||
let mut window_height = match change {
|
||||
SizeChange::SetFixed(fixed) => fixed,
|
||||
SizeChange::SetProportion(proportion) => {
|
||||
((self.working_area.size.h - self.options.gaps) as f64 * proportion
|
||||
let tile_height = ((self.working_area.size.h - self.options.gaps) as f64
|
||||
* proportion
|
||||
- self.options.gaps as f64)
|
||||
.round() as i32
|
||||
.round() as i32;
|
||||
tile.window_height_for_tile_height(tile_height)
|
||||
}
|
||||
SizeChange::AdjustFixed(delta) => current_px.saturating_add(delta),
|
||||
SizeChange::AdjustFixed(delta) => current_window_px.saturating_add(delta),
|
||||
SizeChange::AdjustProportion(delta) => {
|
||||
let proportion = current_prop + delta / 100.;
|
||||
((self.working_area.size.h - self.options.gaps) as f64 * proportion
|
||||
let tile_height = ((self.working_area.size.h - self.options.gaps) as f64
|
||||
* proportion
|
||||
- self.options.gaps as f64)
|
||||
.round() as i32
|
||||
.round() as i32;
|
||||
tile.window_height_for_tile_height(tile_height)
|
||||
}
|
||||
};
|
||||
|
||||
// Clamp it against the window height constraints.
|
||||
let win = &self.windows[self.active_window_idx];
|
||||
let win = &self.windows[self.active_window_idx].window();
|
||||
let min_h = win.min_size().h;
|
||||
let max_h = win.max_size().h;
|
||||
|
||||
if max_h > 0 {
|
||||
height = height.min(max_h);
|
||||
window_height = window_height.min(max_h);
|
||||
}
|
||||
if min_h > 0 {
|
||||
height = height.max(min_h);
|
||||
window_height = window_height.max(min_h);
|
||||
}
|
||||
|
||||
self.heights[self.active_window_idx] = WindowHeight::Fixed(height.clamp(1, MAX_PX));
|
||||
self.heights[self.active_window_idx] = WindowHeight::Fixed(window_height.clamp(1, MAX_PX));
|
||||
self.update_window_sizes();
|
||||
}
|
||||
|
||||
@@ -1352,7 +1423,7 @@ impl<W: LayoutElement> Column<W> {
|
||||
|
||||
self.windows.iter().map(move |win| {
|
||||
let pos = y;
|
||||
y += win.size().h + self.options.gaps;
|
||||
y += win.tile_size().h + self.options.gaps;
|
||||
pos
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user