aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--resources/default-config.kdl11
-rw-r--r--src/config.rs35
-rw-r--r--src/layout/focus_ring.rs8
-rw-r--r--src/layout/mod.rs22
-rw-r--r--src/layout/monitor.rs2
-rw-r--r--src/layout/tile.rs267
-rw-r--r--src/layout/workspace.rs153
7 files changed, 454 insertions, 44 deletions
diff --git a/resources/default-config.kdl b/resources/default-config.kdl
index a028488a..f2c567f3 100644
--- a/resources/default-config.kdl
+++ b/resources/default-config.kdl
@@ -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.
diff --git a/src/config.rs b/src/config.rs
index b5e98adf..ff096017 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -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"),
diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs
index cbcde19c..b5ead3e2 100644
--- a/src/layout/focus_ring.rs
+++ b/src/layout/focus_ring.rs
@@ -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
+ }
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 61aaf211..8d454728 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -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>> {
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index 8654af0e..77569a0c 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -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) {
diff --git a/src/layout/tile.rs b/src/layout/tile.rs
new file mode 100644
index 00000000..2e8dcd90
--- /dev/null
+++ b/src/layout/tile.rs
@@ -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
+ }
+}
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 2187fee0..954e6ff5 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -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
})
}