mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Implement preset window heights
This commit is contained in:
committed by
Ivan Molodetskikh
parent
087a50a19c
commit
d0e624e615
+30
-12
@@ -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.),
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user