Implement preset window heights

This commit is contained in:
Christian Rieger
2024-09-05 23:37:10 +02:00
committed by Ivan Molodetskikh
parent 087a50a19c
commit d0e624e615
6 changed files with 236 additions and 51 deletions
+30 -12
View File
@@ -392,9 +392,11 @@ pub struct Layout {
#[knuffel(child, default)] #[knuffel(child, default)]
pub border: Border, pub border: Border,
#[knuffel(child, unwrap(children), default)] #[knuffel(child, unwrap(children), default)]
pub preset_column_widths: Vec<PresetWidth>, pub preset_column_widths: Vec<PresetSize>,
#[knuffel(child)] #[knuffel(child)]
pub default_column_width: Option<DefaultColumnWidth>, pub default_column_width: Option<DefaultPresetSize>,
#[knuffel(child, unwrap(children), default)]
pub preset_window_heights: Vec<PresetSize>,
#[knuffel(child, unwrap(argument), default)] #[knuffel(child, unwrap(argument), default)]
pub center_focused_column: CenterFocusedColumn, pub center_focused_column: CenterFocusedColumn,
#[knuffel(child)] #[knuffel(child)]
@@ -416,6 +418,7 @@ impl Default for Layout {
always_center_single_column: false, always_center_single_column: false,
gaps: FloatOrInt(16.), gaps: FloatOrInt(16.),
struts: Default::default(), struts: Default::default(),
preset_window_heights: Default::default(),
} }
} }
} }
@@ -624,13 +627,13 @@ impl Default for Cursor {
} }
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] #[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub enum PresetWidth { pub enum PresetSize {
Proportion(#[knuffel(argument)] f64), Proportion(#[knuffel(argument)] f64),
Fixed(#[knuffel(argument)] i32), Fixed(#[knuffel(argument)] i32),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DefaultColumnWidth(pub Option<PresetWidth>); pub struct DefaultPresetSize(pub Option<PresetSize>);
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)] #[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct Struts { pub struct Struts {
@@ -898,7 +901,7 @@ pub struct WindowRule {
// Rules applied at initial configure. // Rules applied at initial configure.
#[knuffel(child)] #[knuffel(child)]
pub default_column_width: Option<DefaultColumnWidth>, pub default_column_width: Option<DefaultPresetSize>,
#[knuffel(child, unwrap(argument))] #[knuffel(child, unwrap(argument))]
pub open_on_output: Option<String>, pub open_on_output: Option<String>,
#[knuffel(child, unwrap(argument))] #[knuffel(child, unwrap(argument))]
@@ -1155,6 +1158,7 @@ pub enum Action {
#[knuffel(skip)] #[knuffel(skip)]
ResetWindowHeightById(u64), ResetWindowHeightById(u64),
SwitchPresetColumnWidth, SwitchPresetColumnWidth,
SwitchPresetWindowHeight,
MaximizeColumn, MaximizeColumn,
SetColumnWidth(#[knuffel(argument, str)] SizeChange), SetColumnWidth(#[knuffel(argument, str)] SizeChange),
SwitchLayout(#[knuffel(argument, str)] LayoutSwitchTarget), SwitchLayout(#[knuffel(argument, str)] LayoutSwitchTarget),
@@ -1266,6 +1270,7 @@ impl From<niri_ipc::Action> for Action {
niri_ipc::Action::ResetWindowHeight { id: None } => Self::ResetWindowHeight, niri_ipc::Action::ResetWindowHeight { id: None } => Self::ResetWindowHeight,
niri_ipc::Action::ResetWindowHeight { id: Some(id) } => Self::ResetWindowHeightById(id), niri_ipc::Action::ResetWindowHeight { id: Some(id) } => Self::ResetWindowHeightById(id),
niri_ipc::Action::SwitchPresetColumnWidth {} => Self::SwitchPresetColumnWidth, niri_ipc::Action::SwitchPresetColumnWidth {} => Self::SwitchPresetColumnWidth,
niri_ipc::Action::SwitchPresetWindowHeight {} => Self::SwitchPresetWindowHeight,
niri_ipc::Action::MaximizeColumn {} => Self::MaximizeColumn, niri_ipc::Action::MaximizeColumn {} => Self::MaximizeColumn,
niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change), niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change),
niri_ipc::Action::SwitchLayout { layout } => Self::SwitchLayout(layout), niri_ipc::Action::SwitchLayout { layout } => Self::SwitchLayout(layout),
@@ -1882,7 +1887,7 @@ impl OutputName {
} }
} }
impl<S> knuffel::Decode<S> for DefaultColumnWidth impl<S> knuffel::Decode<S> for DefaultPresetSize
where where
S: knuffel::traits::ErrorSpan, S: knuffel::traits::ErrorSpan,
{ {
@@ -1902,7 +1907,7 @@ where
"expected no more than one child", "expected no more than one child",
)); ));
} }
PresetWidth::decode_node(child, ctx).map(Some).map(Self) PresetSize::decode_node(child, ctx).map(Some).map(Self)
} else { } else {
Ok(Self(None)) Ok(Self(None))
} }
@@ -2914,6 +2919,13 @@ mod tests {
fixed 1280 fixed 1280
} }
preset-window-heights {
proportion 0.25
proportion 0.5
fixed 960
fixed 1280
}
default-column-width { proportion 0.25; } default-column-width { proportion 0.25; }
gaps 8 gaps 8
@@ -3104,14 +3116,20 @@ mod tests {
inactive_gradient: None, inactive_gradient: None,
}, },
preset_column_widths: vec![ preset_column_widths: vec![
PresetWidth::Proportion(0.25), PresetSize::Proportion(0.25),
PresetWidth::Proportion(0.5), PresetSize::Proportion(0.5),
PresetWidth::Fixed(960), PresetSize::Fixed(960),
PresetWidth::Fixed(1280), PresetSize::Fixed(1280),
], ],
default_column_width: Some(DefaultColumnWidth(Some(PresetWidth::Proportion( default_column_width: Some(DefaultPresetSize(Some(PresetSize::Proportion(
0.25, 0.25,
)))), )))),
preset_window_heights: vec![
PresetSize::Proportion(0.25),
PresetSize::Proportion(0.5),
PresetSize::Fixed(960),
PresetSize::Fixed(1280),
],
gaps: FloatOrInt(8.), gaps: FloatOrInt(8.),
struts: Struts { struts: Struts {
left: FloatOrInt(1.), left: FloatOrInt(1.),
+2
View File
@@ -350,6 +350,8 @@ pub enum Action {
}, },
/// Switch between preset column widths. /// Switch between preset column widths.
SwitchPresetColumnWidth {}, SwitchPresetColumnWidth {},
/// Switch between preset window heights.
SwitchPresetWindowHeight {},
/// Toggle the maximized state of the focused column. /// Toggle the maximized state of the focused column.
MaximizeColumn {}, MaximizeColumn {},
/// Change the width of the focused column. /// Change the width of the focused column.
+3
View File
@@ -1035,6 +1035,9 @@ impl State {
Action::SwitchPresetColumnWidth => { Action::SwitchPresetColumnWidth => {
self.niri.layout.toggle_width(); self.niri.layout.toggle_width();
} }
Action::SwitchPresetWindowHeight => {
self.niri.layout.toggle_window_height();
}
Action::CenterColumn => { Action::CenterColumn => {
self.niri.layout.center_column(); self.niri.layout.center_column();
// FIXME: granular // FIXME: granular
+71 -13
View File
@@ -34,7 +34,9 @@ use std::mem;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use niri_config::{CenterFocusedColumn, Config, FloatOrInt, Struts, Workspace as WorkspaceConfig}; use niri_config::{
CenterFocusedColumn, Config, FloatOrInt, PresetSize, Struts, Workspace as WorkspaceConfig,
};
use niri_ipc::SizeChange; use niri_ipc::SizeChange;
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::Id; use smithay::backend::renderer::element::Id;
@@ -238,11 +240,12 @@ pub struct Options {
pub center_focused_column: CenterFocusedColumn, pub center_focused_column: CenterFocusedColumn,
pub always_center_single_column: bool, pub always_center_single_column: bool,
/// Column widths that `toggle_width()` switches between. /// Column widths that `toggle_width()` switches between.
pub preset_widths: Vec<ColumnWidth>, pub preset_column_widths: Vec<ColumnWidth>,
/// Initial width for new columns. /// Initial width for new columns.
pub default_width: Option<ColumnWidth>, pub default_column_width: Option<ColumnWidth>,
/// Window height that `toggle_window_height()` switches between.
pub preset_window_heights: Vec<PresetSize>,
pub animations: niri_config::Animations, pub animations: niri_config::Animations,
// Debug flags. // Debug flags.
pub disable_resize_throttling: bool, pub disable_resize_throttling: bool,
pub disable_transactions: bool, pub disable_transactions: bool,
@@ -257,15 +260,20 @@ impl Default for Options {
border: Default::default(), border: Default::default(),
center_focused_column: Default::default(), center_focused_column: Default::default(),
always_center_single_column: false, always_center_single_column: false,
preset_widths: vec![ preset_column_widths: vec![
ColumnWidth::Proportion(1. / 3.), ColumnWidth::Proportion(1. / 3.),
ColumnWidth::Proportion(0.5), ColumnWidth::Proportion(0.5),
ColumnWidth::Proportion(2. / 3.), ColumnWidth::Proportion(2. / 3.),
], ],
default_width: None, default_column_width: None,
animations: Default::default(), animations: Default::default(),
disable_resize_throttling: false, disable_resize_throttling: false,
disable_transactions: false, disable_transactions: false,
preset_window_heights: vec![
PresetSize::Proportion(1. / 3.),
PresetSize::Proportion(0.5),
PresetSize::Proportion(2. / 3.),
],
} }
} }
} }
@@ -273,21 +281,26 @@ impl Default for Options {
impl Options { impl Options {
fn from_config(config: &Config) -> Self { fn from_config(config: &Config) -> Self {
let layout = &config.layout; let layout = &config.layout;
let preset_column_widths = &layout.preset_column_widths;
let preset_widths = if preset_column_widths.is_empty() { let preset_column_widths = if layout.preset_column_widths.is_empty() {
Options::default().preset_widths Options::default().preset_column_widths
} else { } else {
preset_column_widths layout
.preset_column_widths
.iter() .iter()
.copied() .copied()
.map(ColumnWidth::from) .map(ColumnWidth::from)
.collect() .collect()
}; };
let preset_window_heights = if layout.preset_window_heights.is_empty() {
Options::default().preset_window_heights
} else {
layout.preset_window_heights.clone()
};
// Missing default_column_width maps to Some(ColumnWidth::Proportion(0.5)), // Missing default_column_width maps to Some(ColumnWidth::Proportion(0.5)),
// while present, but empty, maps to None. // while present, but empty, maps to None.
let default_width = layout let default_column_width = layout
.default_column_width .default_column_width
.as_ref() .as_ref()
.map(|w| w.0.map(ColumnWidth::from)) .map(|w| w.0.map(ColumnWidth::from))
@@ -300,11 +313,12 @@ impl Options {
border: layout.border, border: layout.border,
center_focused_column: layout.center_focused_column, center_focused_column: layout.center_focused_column,
always_center_single_column: layout.always_center_single_column, always_center_single_column: layout.always_center_single_column,
preset_widths, preset_column_widths,
default_width, default_column_width,
animations: config.animations.clone(), animations: config.animations.clone(),
disable_resize_throttling: config.debug.disable_resize_throttling, disable_resize_throttling: config.debug.disable_resize_throttling,
disable_transactions: config.debug.disable_transactions, disable_transactions: config.debug.disable_transactions,
preset_window_heights,
} }
} }
@@ -1937,6 +1951,13 @@ impl<W: LayoutElement> Layout<W> {
monitor.toggle_width(); monitor.toggle_width();
} }
pub fn toggle_window_height(&mut self) {
let Some(monitor) = self.active_monitor() else {
return;
};
monitor.toggle_window_height();
}
pub fn toggle_full_width(&mut self) { pub fn toggle_full_width(&mut self) {
let Some(monitor) = self.active_monitor() else { let Some(monitor) = self.active_monitor() else {
return; return;
@@ -3027,6 +3048,7 @@ mod tests {
}, },
MoveColumnToOutput(#[proptest(strategy = "1..=5u8")] u8), MoveColumnToOutput(#[proptest(strategy = "1..=5u8")] u8),
SwitchPresetColumnWidth, SwitchPresetColumnWidth,
SwitchPresetWindowHeight,
MaximizeColumn, MaximizeColumn,
SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange),
SetWindowHeight { SetWindowHeight {
@@ -3453,6 +3475,7 @@ mod tests {
Op::MoveWorkspaceDown => layout.move_workspace_down(), Op::MoveWorkspaceDown => layout.move_workspace_down(),
Op::MoveWorkspaceUp => layout.move_workspace_up(), Op::MoveWorkspaceUp => layout.move_workspace_up(),
Op::SwitchPresetColumnWidth => layout.toggle_width(), Op::SwitchPresetColumnWidth => layout.toggle_width(),
Op::SwitchPresetWindowHeight => layout.toggle_window_height(),
Op::MaximizeColumn => layout.toggle_full_width(), Op::MaximizeColumn => layout.toggle_full_width(),
Op::SetColumnWidth(change) => layout.set_column_width(change), Op::SetColumnWidth(change) => layout.set_column_width(change),
Op::SetWindowHeight { id, change } => { Op::SetWindowHeight { id, change } => {
@@ -4415,6 +4438,41 @@ mod tests {
layout.verify_invariants(); layout.verify_invariants();
} }
#[test]
fn preset_height_change_removes_preset() {
let mut config = Config::default();
config.layout.preset_window_heights = vec![PresetSize::Fixed(1), PresetSize::Fixed(2)];
let mut layout = Layout::new(&config);
let ops = [
Op::AddOutput(1),
Op::AddWindow {
id: 1,
bbox: Rectangle::from_loc_and_size((0, 0), (1280, 200)),
min_max_size: Default::default(),
},
Op::AddWindow {
id: 2,
bbox: Rectangle::from_loc_and_size((0, 0), (1280, 200)),
min_max_size: Default::default(),
},
Op::ConsumeOrExpelWindowLeft,
Op::SwitchPresetWindowHeight,
Op::SwitchPresetWindowHeight,
];
for op in ops {
op.apply(&mut layout);
}
// Leave only one.
config.layout.preset_window_heights = vec![PresetSize::Fixed(1)];
layout.update_config(&config);
layout.verify_invariants();
}
#[test] #[test]
fn working_area_starts_at_physical_pixel() { fn working_area_starts_at_physical_pixel() {
let struts = Struts { let struts = Struts {
+4
View File
@@ -740,6 +740,10 @@ impl<W: LayoutElement> Monitor<W> {
self.active_workspace().toggle_width(); self.active_workspace().toggle_width();
} }
pub fn toggle_window_height(&mut self) {
self.active_workspace().toggle_window_height();
}
pub fn toggle_full_width(&mut self) { pub fn toggle_full_width(&mut self) {
self.active_workspace().toggle_full_width(); self.active_workspace().toggle_full_width();
} }
+126 -26
View File
@@ -4,7 +4,7 @@ use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use niri_config::{ use niri_config::{
CenterFocusedColumn, OutputName, PresetWidth, Struts, Workspace as WorkspaceConfig, CenterFocusedColumn, OutputName, PresetSize, Struts, Workspace as WorkspaceConfig,
}; };
use niri_ipc::SizeChange; use niri_ipc::SizeChange;
use ordered_float::NotNan; use ordered_float::NotNan;
@@ -202,16 +202,17 @@ pub enum ColumnWidth {
/// Height of a window in a column. /// Height of a window in a column.
/// ///
/// Proportional height is intentionally omitted. With column widths you frequently want e.g. two /// Every window but one in a column must be `Auto`-sized so that the total height can add up to
/// columns side-by-side with 50% width each, and you want them to remain this way when moving to a /// the workspace height. Resizing a window converts all other windows to `Auto`, weighted to
/// differently sized monitor. Windows in a column, however, already auto-size to fill the available /// preserve their visual heights at the moment of the conversion.
/// height, giving you this behavior. The only reason to set a different window height, then, is
/// when you want something in the window to fit exactly, e.g. to fit 30 lines in a terminal, which
/// corresponds to the `Fixed` variant.
/// ///
/// This does not preclude the usual set of binds to set or resize a window proportionally. Just, /// In contrast to column widths, proportional height changes are converted to, and stored as,
/// they are converted to, and stored as fixed height right away, so that once you resize a window /// fixed height right away. With column widths you frequently want e.g. two columns side-by-side
/// to fit the desired content, it can never become smaller than that when moving between monitors. /// with 50% width each, and you want them to remain this way when moving to a differently sized
/// monitor. Windows in a column, however, already auto-size to fill the available height, giving
/// you this behavior. The main reason to set a different window height, then, is when you want
/// something in the window to fit exactly, e.g. to fit 30 lines in a terminal, which corresponds
/// to the `Fixed` variant.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum WindowHeight { pub enum WindowHeight {
/// Automatically computed *tile* height, distributed across the column according to weights. /// Automatically computed *tile* height, distributed across the column according to weights.
@@ -221,6 +222,16 @@ pub enum WindowHeight {
Auto { weight: f64 }, Auto { weight: f64 },
/// Fixed *window* height in logical pixels. /// Fixed *window* height in logical pixels.
Fixed(f64), Fixed(f64),
/// One of the *tile* height proportion presets.
Preset(usize),
}
#[derive(Debug, Clone, Copy)]
pub enum ResolvedSize {
/// Size of the tile including borders.
Tile(f64),
/// Size of the window excluding borders.
Window(f64),
} }
#[derive(Debug)] #[derive(Debug)]
@@ -319,21 +330,32 @@ impl ColumnWidth {
ColumnWidth::Proportion(proportion) => { ColumnWidth::Proportion(proportion) => {
(view_width - options.gaps) * proportion - options.gaps (view_width - options.gaps) * proportion - options.gaps
} }
ColumnWidth::Preset(idx) => options.preset_widths[idx].resolve(options, view_width), ColumnWidth::Preset(idx) => {
options.preset_column_widths[idx].resolve(options, view_width)
}
ColumnWidth::Fixed(width) => width, ColumnWidth::Fixed(width) => width,
} }
} }
} }
impl From<PresetWidth> for ColumnWidth { impl From<PresetSize> for ColumnWidth {
fn from(value: PresetWidth) -> Self { fn from(value: PresetSize) -> Self {
match value { match value {
PresetWidth::Proportion(p) => Self::Proportion(p.clamp(0., 10000.)), PresetSize::Proportion(p) => Self::Proportion(p.clamp(0., 10000.)),
PresetWidth::Fixed(f) => Self::Fixed(f64::from(f.clamp(1, 100000))), PresetSize::Fixed(f) => Self::Fixed(f64::from(f.clamp(1, 100000))),
} }
} }
} }
fn resolve_preset_size(preset: PresetSize, options: &Options, view_size: f64) -> ResolvedSize {
match preset {
PresetSize::Proportion(proportion) => {
ResolvedSize::Tile((view_size - options.gaps) * proportion - options.gaps)
}
PresetSize::Fixed(width) => ResolvedSize::Window(f64::from(width)),
}
}
impl WindowHeight { impl WindowHeight {
const fn auto_1() -> Self { const fn auto_1() -> Self {
Self::Auto { weight: 1. } Self::Auto { weight: 1. }
@@ -650,7 +672,7 @@ impl<W: LayoutElement> Workspace<W> {
match default_width { match default_width {
Some(Some(width)) => Some(width), Some(Some(width)) => Some(width),
Some(None) => None, Some(None) => None,
None => self.options.default_width, None => self.options.default_column_width,
} }
} }
@@ -2351,6 +2373,17 @@ impl<W: LayoutElement> Workspace<W> {
cancel_resize_for_column(&mut self.interactive_resize, col); cancel_resize_for_column(&mut self.interactive_resize, col);
} }
pub fn toggle_window_height(&mut self) {
if self.columns.is_empty() {
return;
}
let col = &mut self.columns[self.active_column_idx];
col.toggle_window_height(None, true);
cancel_resize_for_column(&mut self.interactive_resize, col);
}
pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) { pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) {
let (mut col_idx, tile_idx) = self let (mut col_idx, tile_idx) = self
.columns .columns
@@ -3016,12 +3049,18 @@ impl<W: LayoutElement> Column<W> {
let mut update_sizes = false; let mut update_sizes = false;
// If preset widths changed, make our width non-preset. // If preset widths changed, make our width non-preset.
if self.options.preset_widths != options.preset_widths { if self.options.preset_column_widths != options.preset_column_widths {
if let ColumnWidth::Preset(idx) = self.width { if let ColumnWidth::Preset(idx) = self.width {
self.width = self.options.preset_widths[idx]; self.width = self.options.preset_column_widths[idx];
} }
} }
// If preset heights changed, make our heights non-preset.
if self.options.preset_window_heights != options.preset_window_heights {
self.convert_heights_to_auto();
update_sizes = true;
}
if self.options.gaps != options.gaps { if self.options.gaps != options.gaps {
update_sizes = true; update_sizes = true;
} }
@@ -3220,6 +3259,7 @@ impl<W: LayoutElement> Column<W> {
let width = width.resolve(&self.options, self.working_area.size.w); let width = width.resolve(&self.options, self.working_area.size.w);
let width = f64::max(f64::min(width, max_width), min_width); let width = f64::max(f64::min(width, max_width), min_width);
let height = self.working_area.size.h;
// Compute the tile heights. Start by converting window heights to tile heights. // Compute the tile heights. Start by converting window heights to tile heights.
let mut heights = zip(&self.tiles, &self.data) let mut heights = zip(&self.tiles, &self.data)
@@ -3228,8 +3268,19 @@ impl<W: LayoutElement> Column<W> {
WindowHeight::Fixed(height) => { WindowHeight::Fixed(height) => {
WindowHeight::Fixed(tile.tile_height_for_window_height(height.round().max(1.))) WindowHeight::Fixed(tile.tile_height_for_window_height(height.round().max(1.)))
} }
WindowHeight::Preset(idx) => {
let preset = self.options.preset_window_heights[idx];
let window_height = match resolve_preset_size(preset, &self.options, height) {
ResolvedSize::Tile(h) => tile.window_height_for_tile_height(h),
ResolvedSize::Window(h) => h,
};
let tile_height = tile
.tile_height_for_window_height(window_height.round().clamp(1., 100000.));
WindowHeight::Fixed(tile_height)
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let gaps_left = self.options.gaps * (self.tiles.len() + 1) as f64; let gaps_left = self.options.gaps * (self.tiles.len() + 1) as f64;
let mut height_left = self.working_area.size.h - gaps_left; let mut height_left = self.working_area.size.h - gaps_left;
let mut auto_tiles_left = self.tiles.len(); let mut auto_tiles_left = self.tiles.len();
@@ -3283,6 +3334,7 @@ impl<W: LayoutElement> Column<W> {
let weight = match *h { let weight = match *h {
WindowHeight::Auto { weight } => weight, WindowHeight::Auto { weight } => weight,
WindowHeight::Fixed(_) => continue, WindowHeight::Fixed(_) => continue,
WindowHeight::Preset(_) => unreachable!(),
}; };
let factor = weight / total_weight_2; let factor = weight / total_weight_2;
@@ -3321,6 +3373,7 @@ impl<W: LayoutElement> Column<W> {
let weight = match *h { let weight = match *h {
WindowHeight::Auto { weight } => weight, WindowHeight::Auto { weight } => weight,
WindowHeight::Fixed(_) => continue, WindowHeight::Fixed(_) => continue,
WindowHeight::Preset(_) => unreachable!(),
}; };
let factor = weight / total_weight; let factor = weight / total_weight;
@@ -3456,6 +3509,10 @@ impl<W: LayoutElement> Column<W> {
); );
found_fixed = true; found_fixed = true;
} }
if let WindowHeight::Preset(idx) = data.height {
assert!(self.options.preset_window_heights.len() > idx);
}
} }
} }
@@ -3467,11 +3524,11 @@ impl<W: LayoutElement> Column<W> {
}; };
let idx = match width { let idx = match width {
ColumnWidth::Preset(idx) => (idx + 1) % self.options.preset_widths.len(), ColumnWidth::Preset(idx) => (idx + 1) % self.options.preset_column_widths.len(),
_ => { _ => {
let current = self.width(); let current = self.width();
self.options self.options
.preset_widths .preset_column_widths
.iter() .iter()
.position(|prop| { .position(|prop| {
let resolved = prop.resolve(&self.options, self.working_area.size.w); let resolved = prop.resolve(&self.options, self.working_area.size.w);
@@ -3500,7 +3557,7 @@ impl<W: LayoutElement> Column<W> {
let current_px = width.resolve(&self.options, self.working_area.size.w); let current_px = width.resolve(&self.options, self.working_area.size.w);
let current = match width { let current = match width {
ColumnWidth::Preset(idx) => self.options.preset_widths[idx], ColumnWidth::Preset(idx) => self.options.preset_column_widths[idx],
current => current, current => current,
}; };
@@ -3551,17 +3608,18 @@ impl<W: LayoutElement> Column<W> {
let tile_idx = tile_idx.unwrap_or(self.active_tile_idx); let tile_idx = tile_idx.unwrap_or(self.active_tile_idx);
// Start by converting all heights to automatic, since only one window in the column can be // Start by converting all heights to automatic, since only one window in the column can be
// fixed-height. If the current tile is already fixed, however, we can skip that step. // non-auto-height. If the current tile is already non-auto, however, we can skip that
// Which is not only for optimization, but also preserves automatic weights in case one // step. Which is not only for optimization, but also preserves automatic weights in case
// window is resized in such a way that other windows hit their min size, and then back. // one window is resized in such a way that other windows hit their min size, and then
if !matches!(self.data[tile_idx].height, WindowHeight::Fixed(_)) { // back.
if matches!(self.data[tile_idx].height, WindowHeight::Auto { .. }) {
self.convert_heights_to_auto(); self.convert_heights_to_auto();
} }
let current = self.data[tile_idx].height; let current = self.data[tile_idx].height;
let tile = &self.tiles[tile_idx]; let tile = &self.tiles[tile_idx];
let current_window_px = match current { let current_window_px = match current {
WindowHeight::Auto { .. } => tile.window_size().h, WindowHeight::Auto { .. } | WindowHeight::Preset(_) => tile.window_size().h,
WindowHeight::Fixed(height) => height, WindowHeight::Fixed(height) => height,
}; };
let current_tile_px = tile.tile_height_for_window_height(current_window_px); let current_tile_px = tile.tile_height_for_window_height(current_window_px);
@@ -3631,6 +3689,48 @@ impl<W: LayoutElement> Column<W> {
self.update_tile_sizes(animate); self.update_tile_sizes(animate);
} }
fn toggle_window_height(&mut self, tile_idx: Option<usize>, animate: bool) {
let tile_idx = tile_idx.unwrap_or(self.active_tile_idx);
// Start by converting all heights to automatic, since only one window in the column can be
// non-auto-height. If the current tile is already non-auto, however, we can skip that
// step. Which is not only for optimization, but also preserves automatic weights in case
// one window is resized in such a way that other windows hit their min size, and then
// back.
if matches!(self.data[tile_idx].height, WindowHeight::Auto { .. }) {
self.convert_heights_to_auto();
}
let preset_idx = match self.data[tile_idx].height {
WindowHeight::Preset(idx) => (idx + 1) % self.options.preset_window_heights.len(),
_ => {
let current = self.data[tile_idx].size.h;
let tile = &self.tiles[tile_idx];
self.options
.preset_window_heights
.iter()
.copied()
.position(|preset| {
let resolved =
resolve_preset_size(preset, &self.options, self.working_area.size.h);
let window_height = match resolved {
ResolvedSize::Tile(h) => tile.window_height_for_tile_height(h),
ResolvedSize::Window(h) => h,
};
let resolved = tile.tile_height_for_window_height(
window_height.round().clamp(1., 100000.),
);
// Some allowance for fractional scaling purposes.
current + 1. < resolved
})
.unwrap_or(0)
}
};
self.data[tile_idx].height = WindowHeight::Preset(preset_idx);
self.update_tile_sizes(animate);
}
/// Converts all heights in the column to automatic, preserving the apparent heights. /// Converts all heights in the column to automatic, preserving the apparent heights.
/// ///
/// All weights are recomputed to preserve the current tile heights while "centering" the /// All weights are recomputed to preserve the current tile heights while "centering" the