feat(wm): add scrolling layout · LGUG2Z/komorebi@b4e61b0

3 min read Original article ↗

@@ -12,15 +12,20 @@ use super::custom_layout::ColumnSplitWithCapacity;

1212

use super::CustomLayout;

1313

use super::DefaultLayout;

1414

use super::Rect;

15+

use crate::default_layout::LayoutOptions;

15161617

pub trait Arrangement {

18+

#[allow(clippy::too_many_arguments)]

1719

fn calculate(

1820

&self,

1921

area: &Rect,

2022

len: NonZeroUsize,

2123

container_padding: Option<i32>,

2224

layout_flip: Option<Axis>,

2325

resize_dimensions: &[Option<Rect>],

26+

focused_idx: usize,

27+

layout_options: Option<LayoutOptions>,

28+

latest_layout: &[Rect],

2429

) -> Vec<Rect>;

2530

}

2631

@@ -33,9 +38,110 @@ impl Arrangement for DefaultLayout {

3338

container_padding: Option<i32>,

3439

layout_flip: Option<Axis>,

3540

resize_dimensions: &[Option<Rect>],

41+

focused_idx: usize,

42+

layout_options: Option<LayoutOptions>,

43+

latest_layout: &[Rect],

3644

) -> Vec<Rect> {

3745

let len = usize::from(len);

3846

let mut dimensions = match self {

47+

Self::Scrolling => {

48+

let column_count = layout_options

49+

.and_then(|o| o.scrolling.map(|s| s.columns))

50+

.unwrap_or(3);

51+52+

let column_width = area.right / column_count as i32;

53+

let mut layouts = Vec::with_capacity(len);

54+55+

match len {

56+

// treat < 3 windows the same as the columns layout

57+

len if len < 3 => {

58+

layouts = columns(area, len);

59+60+

let adjustment = calculate_columns_adjustment(resize_dimensions);

61+

layouts.iter_mut().zip(adjustment.iter()).for_each(

62+

|(layout, adjustment)| {

63+

layout.top += adjustment.top;

64+

layout.bottom += adjustment.bottom;

65+

layout.left += adjustment.left;

66+

layout.right += adjustment.right;

67+

},

68+

);

69+70+

if matches!(

71+

layout_flip,

72+

Some(Axis::Horizontal | Axis::HorizontalAndVertical)

73+

) {

74+

if let 2.. = len {

75+

columns_reverse(&mut layouts);

76+

}

77+

}

78+

}

79+

// treat >= column_count as scrolling

80+

len => {

81+

let visible_columns = area.right / column_width;

82+

let first_visible: isize = if focused_idx == 0 {

83+

// if focused idx is 0, we are at the beginning of the scrolling strip

84+

0

85+

} else {

86+

let previous_first_visible = if latest_layout.is_empty() {

87+

0

88+

} else {

89+

// previous first_visible based on the left position of the first visible window

90+

let left_edge = area.left;

91+

latest_layout

92+

.iter()

93+

.position(|rect| rect.left >= left_edge)

94+

.unwrap_or(0) as isize

95+

};

96+97+

let focused_idx = focused_idx as isize;

98+99+

if focused_idx < previous_first_visible {

100+

// focused window is off the left edge, we need to scroll left

101+

focused_idx

102+

} else if focused_idx

103+

>= previous_first_visible + visible_columns as isize

104+

{

105+

// focused window is off the right edge, we need to scroll right

106+

// and make sure it's the last visible window

107+

(focused_idx + 1 - visible_columns as isize).max(0)

108+

} else {

109+

// focused window is already visible, we don't need to scroll

110+

previous_first_visible

111+

}

112+

.min(

113+

(len as isize)

114+

.saturating_sub(visible_columns as isize)

115+

.max(0),

116+

)

117+

};

118+119+

for i in 0..len {

120+

let position = (i as isize) - first_visible;

121+

let left = area.left + (position as i32 * column_width);

122+123+

layouts.push(Rect {

124+

left,

125+

top: area.top,

126+

right: column_width,

127+

bottom: area.bottom,

128+

});

129+

}

130+131+

let adjustment = calculate_scrolling_adjustment(resize_dimensions);

132+

layouts.iter_mut().zip(adjustment.iter()).for_each(

133+

|(layout, adjustment)| {

134+

layout.top += adjustment.top;

135+

layout.bottom += adjustment.bottom;

136+

layout.left += adjustment.left;

137+

layout.right += adjustment.right;

138+

},

139+

);

140+

}

141+

}

142+143+

layouts

144+

}

39145

Self::BSP => recursive_fibonacci(

40146

0,

41147

len,

@@ -487,6 +593,9 @@ impl Arrangement for CustomLayout {

487593

container_padding: Option<i32>,

488594

_layout_flip: Option<Axis>,

489595

_resize_dimensions: &[Option<Rect>],

596+

_focused_idx: usize,

597+

_layout_options: Option<LayoutOptions>,

598+

_latest_layout: &[Rect],

490599

) -> Vec<Rect> {

491600

let mut dimensions = vec![];

492601

let container_count = len.get();

@@ -541,7 +650,7 @@ impl Arrangement for CustomLayout {

541650

};

542651543652

match column {

544-

Column::Primary(Option::Some(_)) => {

653+

Column::Primary(Some(_)) => {

545654

let main_column_area = if idx == 0 {

546655

Self::main_column_area(area, primary_right, None)

547656

} else {

@@ -1115,6 +1224,37 @@ fn calculate_ultrawide_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rec

11151224

result

11161225

}

111712261227+

fn calculate_scrolling_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {

1228+

let len = resize_dimensions.len();

1229+

let mut result = vec![Rect::default(); len];

1230+1231+

if len <= 1 {

1232+

return result;

1233+

}

1234+1235+

for (i, rect) in resize_dimensions.iter().enumerate() {

1236+

if let Some(rect) = rect {

1237+

let is_leftmost = i == 0;

1238+

let is_rightmost = i == len - 1;

1239+1240+

resize_left(&mut result[i], rect.left);

1241+

resize_right(&mut result[i], rect.right);

1242+

resize_top(&mut result[i], rect.top);

1243+

resize_bottom(&mut result[i], rect.bottom);

1244+1245+

if !is_leftmost && rect.left != 0 {

1246+

resize_right(&mut result[i - 1], rect.left);

1247+

}

1248+1249+

if !is_rightmost && rect.right != 0 {

1250+

resize_left(&mut result[i + 1], rect.right);

1251+

}

1252+

}

1253+

}

1254+1255+

result

1256+

}

1257+11181258

fn resize_left(rect: &mut Rect, resize: i32) {

11191259

rect.left += resize / 2;

11201260

rect.right += -resize / 2;