Skip to content

Commit

Permalink
Add center-focused-column setting
Browse files Browse the repository at this point in the history
  • Loading branch information
tversteeg authored and YaLTeR committed Jan 8, 2024
1 parent 71fef2a commit fb93038
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 45 deletions.
17 changes: 17 additions & 0 deletions niri-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ impl Xkb {
}
}

#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq, Clone, Copy)]
pub enum CenterFocusedColumn {
/// Focusing a column will not center the column.
#[default]
Never,
/// The focused column will always be centered.
Always,
/// Focusing a column will center it if it doesn't fit on the screen together with the
/// previously focused column.
OnOverflow,
}

#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)]
pub enum TrackLayout {
/// The layout change is global.
Expand Down Expand Up @@ -217,6 +229,8 @@ pub struct Layout {
pub preset_column_widths: Vec<PresetWidth>,
#[knuffel(child)]
pub default_column_width: Option<DefaultColumnWidth>,
#[knuffel(child, unwrap(argument), default)]
pub center_focused_column: CenterFocusedColumn,
#[knuffel(child, unwrap(argument), default = 16)]
pub gaps: u16,
#[knuffel(child, default)]
Expand Down Expand Up @@ -739,6 +753,8 @@ mod tests {
right 2
top 3
}
center-focused-column "on-overflow"
}
spawn-at-startup "alacritty" "-e" "fish"
Expand Down Expand Up @@ -856,6 +872,7 @@ mod tests {
top: 3,
bottom: 0,
},
center_focused_column: CenterFocusedColumn::OnOverflow,
},
spawn_at_startup: vec![SpawnAtStartup {
command: vec!["alacritty".to_owned(), "-e".to_owned(), "fish".to_owned()],
Expand Down
8 changes: 8 additions & 0 deletions resources/default-config.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ layout {
// top 64
// bottom 64
}

// When to center a column when changing focus, options are:
// - "never", default behavior, focusing an off-screen column will keep at the left
// or right edge of the screen.
// - "on-overflow", focusing a column will center it if it doesn't fit
// together with the previously focused column.
// - "always", the focused column will always be centered.
center-focused-column "never"
}

// Add lines like this to spawn processes at startup.
Expand Down
3 changes: 3 additions & 0 deletions src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub struct Options {
struts: Struts,
focus_ring: niri_config::FocusRing,
border: niri_config::FocusRing,
center_focused_column: niri_config::CenterFocusedColumn,
/// Column widths that `toggle_width()` switches between.
preset_widths: Vec<ColumnWidth>,
/// Initial width for new columns.
Expand All @@ -152,6 +153,7 @@ impl Default for Options {
struts: Default::default(),
focus_ring: Default::default(),
border: niri_config::default_border(),
center_focused_column: Default::default(),
preset_widths: vec![
ColumnWidth::Proportion(1. / 3.),
ColumnWidth::Proportion(0.5),
Expand Down Expand Up @@ -190,6 +192,7 @@ impl Options {
struts: layout.struts,
focus_ring: layout.focus_ring,
border: layout.border,
center_focused_column: layout.center_focused_column,
preset_widths,
default_width,
}
Expand Down
133 changes: 88 additions & 45 deletions src/layout/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::iter::zip;
use std::rc::Rc;
use std::time::Duration;

use niri_config::{PresetWidth, SizeChange, Struts};
use niri_config::{CenterFocusedColumn, PresetWidth, SizeChange, Struts};
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::utils::RelocateRenderElement;
use smithay::backend::renderer::{ImportAll, Renderer};
Expand Down Expand Up @@ -397,9 +397,7 @@ impl<W: LayoutElement> Workspace<W> {
new_offset - self.working_area.loc.x
}

fn animate_view_offset_to_column(&mut self, current_x: i32, idx: usize) {
let new_view_offset = self.compute_new_view_offset_for_column(current_x, idx);

fn animate_view_offset(&mut self, current_x: i32, idx: usize, new_view_offset: i32) {
let new_col_x = self.column_x(idx);
let from_view_offset = current_x - new_col_x;
self.view_offset = from_view_offset;
Expand All @@ -426,13 +424,78 @@ impl<W: LayoutElement> Workspace<W> {
));
}

fn animate_view_offset_to_column(&mut self, current_x: i32, idx: usize) {
let new_view_offset = self.compute_new_view_offset_for_column(current_x, idx);
self.animate_view_offset(current_x, idx, new_view_offset);
}

fn animate_view_offset_to_column_centered(&mut self, current_x: i32, idx: usize) {
if self.columns.is_empty() {
return;
}

let col = &self.columns[idx];
if col.is_fullscreen {
self.animate_view_offset_to_column(current_x, idx);
return;
}

let width = col.width();

// If the column is wider than the working area, then on commit it will be shifted to left
// edge alignment by the usual positioning code, so there's no use in trying to center it
// here.
if self.working_area.size.w <= width {
self.animate_view_offset_to_column(current_x, idx);
return;
}

let new_view_offset = -(self.working_area.size.w - width) / 2 - self.working_area.loc.x;

self.animate_view_offset(current_x, idx, new_view_offset);
}

fn activate_column(&mut self, idx: usize) {
if self.active_column_idx == idx {
return;
}

let current_x = self.view_pos();
self.animate_view_offset_to_column(current_x, idx);
match self.options.center_focused_column {
CenterFocusedColumn::Always => {
self.animate_view_offset_to_column_centered(current_x, idx)
}
CenterFocusedColumn::OnOverflow => {
// Always take the left or right neighbor of the target as the source.
let source_idx = if self.active_column_idx > idx {
min(idx + 1, self.columns.len() - 1)
} else {
idx.saturating_sub(1)
};

let source_x = self.column_x(source_idx);
let source_width = self.columns[source_idx].width();

let target_x = self.column_x(idx);
let target_width = self.columns[idx].width();

let total_width = if source_x < target_x {
// Source is left from target.
target_x - source_x + target_width
} else {
// Source is right from target.
source_x - target_x + source_width
} + self.options.gaps * 2;

// If it fits together, do a normal animation, otherwise center the new column.
if total_width <= self.working_area.size.w {
self.animate_view_offset_to_column(current_x, idx);
} else {
self.animate_view_offset_to_column_centered(current_x, idx);
}
}
CenterFocusedColumn::Never => self.animate_view_offset_to_column(current_x, idx),
};

self.active_column_idx = idx;

Expand Down Expand Up @@ -488,15 +551,21 @@ impl<W: LayoutElement> Workspace<W> {
width,
is_full_width,
);
let width = column.width();
self.columns.insert(idx, column);

if activate {
// If this is the first window on an empty workspace, skip the animation from whatever
// view_offset was left over.
if was_empty {
// Try to make the code produce a left-aligned offset, even in presence of left
// exclusive zones.
self.view_offset = self.compute_new_view_offset_for_column(self.column_x(0), 0);
if self.options.center_focused_column == CenterFocusedColumn::Always {
self.view_offset =
-(self.working_area.size.w - width) / 2 - self.working_area.loc.x;
} else {
// Try to make the code produce a left-aligned offset, even in presence of left
// exclusive zones.
self.view_offset = self.compute_new_view_offset_for_column(self.column_x(0), 0);
}
self.view_offset_anim = None;
}

Expand Down Expand Up @@ -574,7 +643,14 @@ impl<W: LayoutElement> Workspace<W> {
if idx == self.active_column_idx {
// We might need to move the view to ensure the resized window is still visible.
let current_x = self.view_pos();
self.animate_view_offset_to_column(current_x, idx);

if self.options.center_focused_column == CenterFocusedColumn::Always {
// FIXME: we will want to skip the animation in some cases here to make
// continuously resizing windows not look janky.
self.animate_view_offset_to_column_centered(current_x, idx);
} else {
self.animate_view_offset_to_column(current_x, idx);
}
}
}

Expand Down Expand Up @@ -654,6 +730,7 @@ impl<W: LayoutElement> Workspace<W> {
let column = self.columns.remove(self.active_column_idx);
self.columns.insert(new_idx, column);

// FIXME: should this be different when always centering?
self.view_offset =
self.compute_new_view_offset_for_column(current_x, self.active_column_idx);

Expand Down Expand Up @@ -738,42 +815,8 @@ impl<W: LayoutElement> Workspace<W> {
}

pub fn center_column(&mut self) {
if self.columns.is_empty() {
return;
}

let col = &self.columns[self.active_column_idx];
if col.is_fullscreen {
return;
}

let width = col.width();

// If the column is wider than the working area, then on commit it will be shifted to left
// edge alignment by the usual positioning code, so there's no use in doing anything here.
if self.working_area.size.w <= width {
return;
}

let new_view_offset = -(self.working_area.size.w - width) / 2 - self.working_area.loc.x;

// If we're already animating towards that, don't restart it.
if let Some(anim) = &self.view_offset_anim {
if anim.to().round() as i32 == new_view_offset {
return;
}
}

// If our view offset is already this, we don't need to do anything.
if self.view_offset == new_view_offset {
return;
}

self.view_offset_anim = Some(Animation::new(
self.view_offset as f64,
new_view_offset as f64,
Duration::from_millis(250),
));
let center_x = self.view_pos();
self.animate_view_offset_to_column_centered(center_x, self.active_column_idx);
}

fn view_pos(&self) -> i32 {
Expand Down

0 comments on commit fb93038

Please sign in to comment.