Unify scrolling viewport presentation ownership

This commit is contained in:
Samuel Onoja committed 2026-10-07 08:44:40 +01:00
1 parent 4a9880eaad
commit a00f1c6f3a
12 files changed
+894 -316

No files matched your search

+9 -9
View File
@@ -137,14 +137,14 @@ impl WorkspaceLayout {
Self::Scrolling(layout) => {
let mut layout = layout.clone();
layout
.geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), None)
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), None)
.map(|result| result.geometry)
}
Self::Tree(layout) => layout.geometry(bounds),
}
}
pub fn geometry_with_constraints(
pub fn resolve_geometry_with_constraints(
&mut self,
bounds: Rect,
gaps: GapConfig,
@@ -152,7 +152,7 @@ impl WorkspaceLayout {
focused: Option<WindowId>,
) -> Result<LayoutResult, LayoutError> {
match self {
Self::Scrolling(layout) => layout.geometry_with_constraints(
Self::Scrolling(layout) => layout.resolve_geometry_with_constraints(
bounds,
gaps,
constraints,
@@ -1121,7 +1121,7 @@ mod tests {
let first = workspaces
.active_mut()
.layout
.geometry_with_constraints(
.resolve_geometry_with_constraints(
Rect::new(0.0, 0.0, 1_000.0, 800.0),
GapConfig::default(),
&HashMap::new(),
@@ -1277,7 +1277,7 @@ mod tests {
let before = workspaces
.active_mut()
.layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(WindowId(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(WindowId(2)))
.unwrap();
let scroll = workspaces.active().layout.viewport_x();
// Rapid right/middle/left hover must never scroll or skip a focus target,
@@ -1289,7 +1289,7 @@ mod tests {
let after = workspaces
.active_mut()
.layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(id))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(id))
.unwrap();
assert_eq!(before.geometry, after.geometry, "{strategy:?}");
assert_eq!(scroll, workspaces.active().layout.viewport_x());
@@ -1299,7 +1299,7 @@ mod tests {
let clicked = workspaces
.active_mut()
.layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(WindowId(3)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(WindowId(3)))
.unwrap();
let rect = clicked.geometry[&WindowId(3)];
assert!(rect.x >= 0.0 && rect.x + rect.width <= bounds.width);
@@ -1321,7 +1321,7 @@ mod tests {
let before = workspaces
.active_mut()
.layout
.geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(WindowId(4)))
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(WindowId(4)))
.unwrap();
let scroll = workspaces.active().layout.viewport_x();
workspaces
@@ -1335,7 +1335,7 @@ mod tests {
let after = workspaces
.active_mut()
.layout
.geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(WindowId(5)))
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(WindowId(5)))
.unwrap();
assert_eq!(before.geometry, after.geometry);
assert_eq!(scroll, workspaces.active().layout.viewport_x());
+245 -117
View File
@@ -149,6 +149,20 @@ impl ViewportTarget {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum PendingFocusRequest {
Reveal(WindowId),
Slide { from: WindowId, to: WindowId },
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum PendingViewportRequest {
Focus(PendingFocusRequest),
// Width cycling takes precedence. Keep the focus request only in case a
// subsequent no-op cycle cancels the width request before layout resolves.
WidthCycle(Option<PendingFocusRequest>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct ScrollingLayout {
columns: Vec<Column>,
@@ -156,9 +170,7 @@ pub struct ScrollingLayout {
viewport_x: f64,
default_width: ColumnWidth,
focus_strategy: ViewportFocusStrategy,
reveal_pending: Option<WindowId>,
swipe_focus_pending: Option<(WindowId, WindowId)>,
width_cycle_pending: bool,
pending_viewport: Option<PendingViewportRequest>,
last_viewport_width: Option<f64>,
allocated_widths: HashMap<WindowId, f64>,
viewport_basis: Option<ViewportTarget>,
@@ -172,9 +184,7 @@ impl Default for ScrollingLayout {
viewport_x: 0.0,
default_width: ColumnWidth::default(),
focus_strategy: ViewportFocusStrategy::Minimal,
reveal_pending: None,
swipe_focus_pending: None,
width_cycle_pending: false,
pending_viewport: None,
last_viewport_width: None,
allocated_widths: HashMap::new(),
viewport_basis: None,
@@ -183,6 +193,28 @@ impl Default for ScrollingLayout {
}
impl ScrollingLayout {
fn pending_focus(&self) -> Option<PendingFocusRequest> {
match self.pending_viewport {
Some(PendingViewportRequest::Focus(request)) => Some(request),
Some(PendingViewportRequest::WidthCycle(request)) => request,
None => None,
}
}
fn request_focus(&mut self, request: Option<PendingFocusRequest>) {
self.pending_viewport = if matches!(self.pending_viewport, Some(PendingViewportRequest::WidthCycle(_))) {
Some(PendingViewportRequest::WidthCycle(request))
} else {
request.map(PendingViewportRequest::Focus)
};
}
fn request_reveal(&mut self, window: Option<WindowId>) {
if !matches!(self.pending_focus(), Some(PendingFocusRequest::Slide { .. })) {
self.request_focus(window.map(PendingFocusRequest::Reveal));
}
}
pub fn set_default_width(&mut self, width: ColumnWidth) {
self.default_width = normalized_width(width);
}
@@ -196,8 +228,7 @@ impl ScrollingLayout {
pub fn set_focus_strategy(&mut self, strategy: ViewportFocusStrategy) {
self.focus_strategy = strategy;
self.reveal_pending = self.active_window();
self.swipe_focus_pending = None;
self.request_focus(self.active_window().map(PendingFocusRequest::Reveal));
}
pub fn default_width(&self) -> ColumnWidth {
@@ -282,7 +313,7 @@ impl ScrollingLayout {
.unwrap_or(self.columns.len());
self.columns.insert(index, Column::new(window, self.default_width));
self.active_column = Some(index);
self.reveal_pending = (self.columns.len() > 1).then_some(window);
self.request_reveal((self.columns.len() > 1).then_some(window));
debug_assert!(self.validate().is_ok());
Ok(())
@@ -307,11 +338,11 @@ impl ScrollingLayout {
column.active = column.active.min(column.windows.len() - 1);
column.normalize_heights();
}
if self.reveal_pending == Some(window)
if self.pending_focus() == Some(PendingFocusRequest::Reveal(window))
|| removed_active_column
|| self.focus_strategy == ViewportFocusStrategy::Paged
{
self.reveal_pending = self.active_window();
self.request_reveal(self.active_window());
}
debug_assert!(self.validate().is_ok());
@@ -324,7 +355,7 @@ impl ScrollingLayout {
self.active_column = Some(column);
self.columns[column].active = index;
if changed_column {
self.reveal_pending = Some(window);
self.request_reveal(Some(window));
}
Ok(())
}
@@ -341,8 +372,7 @@ impl ScrollingLayout {
/// An explicit selection must reveal even a window already focused by hover.
pub fn focus_and_reveal(&mut self, window: WindowId) -> Result<(), LayoutError> {
self.focus_without_reveal(window)?;
self.reveal_pending = Some(window);
self.swipe_focus_pending = None;
self.request_focus(Some(PendingFocusRequest::Reveal(window)));
Ok(())
}
@@ -353,8 +383,10 @@ impl ScrollingLayout {
return self.focus_and_reveal(focused);
}
self.focus_without_reveal(focused)?;
self.reveal_pending = None;
self.swipe_focus_pending = Some((previous, focused));
self.request_focus(Some(PendingFocusRequest::Slide {
from: previous,
to: focused,
}));
Ok(())
}
@@ -374,7 +406,7 @@ impl ScrollingLayout {
column.active = insertion;
column.normalize_heights();
self.active_column = Some(target_column);
self.reveal_pending = self.active_window();
self.request_reveal(self.active_window());
debug_assert!(self.validate().is_ok());
Ok(())
@@ -391,7 +423,7 @@ impl ScrollingLayout {
let insertion = (source + 1).min(self.columns.len());
self.columns.insert(insertion, Column::new(window, self.default_width));
self.active_column = Some(insertion);
self.reveal_pending = self.active_window();
self.request_reveal(self.active_window());
debug_assert!(self.validate().is_ok());
Ok(())
@@ -400,7 +432,7 @@ impl ScrollingLayout {
pub fn set_column_width(&mut self, window: WindowId, width: ColumnWidth) -> Result<(), LayoutError> {
let (column, _) = self.window_location(window).ok_or(LayoutError::UnknownWindow(window))?;
self.columns[column].width = normalized_width(width);
self.reveal_pending = self.active_window();
self.request_reveal(self.active_window());
Ok(())
}
@@ -418,7 +450,12 @@ impl ScrollingLayout {
.map(|index| presets[(index + 1) % presets.len()])
.unwrap_or(presets[0]);
self.columns[column].width = next;
self.width_cycle_pending = next != current;
let focus = self.pending_focus();
self.pending_viewport = if next != current {
Some(PendingViewportRequest::WidthCycle(focus))
} else {
focus.map(PendingViewportRequest::Focus)
};
Ok(next != current)
}
@@ -430,7 +467,7 @@ impl ScrollingLayout {
constraints: &HashMap<WindowId, SizeConstraints>,
) -> Result<bool, LayoutError> {
let before = self.viewport_x;
let result = self.geometry_with_constraints(bounds, gaps, constraints, Some(window))?;
let result = self.resolve_geometry_with_constraints(bounds, gaps, constraints, Some(window))?;
let rect = result
.geometry
.get(&window)
@@ -476,7 +513,7 @@ impl ScrollingLayout {
}),
),
);
self.geometry_with_constraints(bounds, gaps, constraints, Some(window))?;
self.resolve_geometry_with_constraints(bounds, gaps, constraints, Some(window))?;
Ok(self.viewport_x != before)
}
@@ -552,7 +589,8 @@ impl ScrollingLayout {
Ok(changed)
}
pub fn geometry_with_constraints(
/// Consume pending viewport policy requests and solve the logical layout.
pub fn resolve_geometry_with_constraints(
&mut self,
bounds: Rect,
gaps: GapConfig,
@@ -616,50 +654,60 @@ impl ScrollingLayout {
.collect();
let viewport_resized = self.last_viewport_width != Some(viewport_width);
self.last_viewport_width = Some(viewport_width);
if self.width_cycle_pending {
if let Some(active) = self.active_column {
self.record_viewport_basis(
&column_positions,
inner,
viewport_width,
ViewportRequest::WidthCycle(active),
);
match self.pending_viewport.take() {
Some(PendingViewportRequest::WidthCycle(_)) => {
if let Some(active) = self.active_column {
self.record_viewport_basis(
&column_positions,
inner,
viewport_width,
ViewportRequest::WidthCycle(active),
);
}
self.retarget_after_width_cycle(&column_positions, viewport_width);
}
self.retarget_after_width_cycle(&column_positions, viewport_width);
self.width_cycle_pending = false;
self.reveal_pending = None;
self.swipe_focus_pending = None;
} else if let Some((previous, focused)) = self.swipe_focus_pending.take() {
if let (Some((from, _)), Some((to, _))) = (self.window_location(previous), self.window_location(focused)) {
self.record_viewport_basis(
&column_positions,
inner,
viewport_width,
ViewportRequest::Slide(from, to),
);
let last_end = column_positions
.last()
.map(|(start, width)| start + width)
.unwrap_or(0.0);
let max_offset = (last_end - viewport_width).max(0.0);
self.viewport_x =
(self.viewport_x + column_positions[to].0 - column_positions[from].0).clamp(0.0, max_offset);
Some(PendingViewportRequest::Focus(PendingFocusRequest::Slide {
from: previous,
to: focused,
})) => {
if let (Some((from, _)), Some((to, _))) =
(self.window_location(previous), self.window_location(focused))
{
self.record_viewport_basis(
&column_positions,
inner,
viewport_width,
ViewportRequest::Slide(from, to),
);
let last_end = column_positions
.last()
.map(|(start, width)| start + width)
.unwrap_or(0.0);
let max_offset = (last_end - viewport_width).max(0.0);
self.viewport_x =
(self.viewport_x + column_positions[to].0 - column_positions[from].0).clamp(0.0, max_offset);
}
}
self.reveal_pending = None;
} else {
let reveal = self.reveal_pending.take().or_else(|| {
(viewport_resized && self.focus_strategy == ViewportFocusStrategy::Paged)
.then(|| self.active_window())
.flatten()
});
if let Some((column, _)) = reveal.and_then(|window| self.window_location(window)) {
self.record_viewport_basis(
&column_positions,
inner,
viewport_width,
ViewportRequest::Reveal(column),
);
self.reveal_column(&column_positions, viewport_width, column);
request => {
let reveal = match request {
Some(PendingViewportRequest::Focus(PendingFocusRequest::Reveal(window))) => Some(window),
None => None,
_ => unreachable!(),
}
.or_else(|| {
(viewport_resized && self.focus_strategy == ViewportFocusStrategy::Paged)
.then(|| self.active_window())
.flatten()
});
if let Some((column, _)) = reveal.and_then(|window| self.window_location(window)) {
self.record_viewport_basis(
&column_positions,
inner,
viewport_width,
ViewportRequest::Reveal(column),
);
self.reveal_column(&column_positions, viewport_width, column);
}
}
}
@@ -780,7 +828,7 @@ impl ScrollingLayout {
if self.columns[source].windows.len() == 1 {
self.columns.swap(source, destination);
self.active_column = Some(destination);
self.reveal_pending = self.active_window();
self.request_reveal(self.active_window());
return Ok(());
}
@@ -789,7 +837,7 @@ impl ScrollingLayout {
let insertion = insertion.min(self.columns.len());
self.columns.insert(insertion, Column::new(window, self.default_width));
self.active_column = Some(insertion);
self.reveal_pending = self.active_window();
self.request_reveal(self.active_window());
Ok(())
}
@@ -912,6 +960,86 @@ mod tests {
WindowId(id)
}
#[test]
fn pending_request_preserves_slide_and_width_cycle_precedence() {
let bounds = Rect::new(0.0, 0.0, 200.0, 100.0);
let mut layout = ScrollingLayout::with_default_width(ColumnWidth::Fixed(150.0));
let a = window(1);
let b = window(2);
layout.insert(a, None).unwrap();
layout.insert(b, Some(a)).unwrap();
layout.set_focus_strategy(ViewportFocusStrategy::Center);
layout
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(b))
.unwrap();
layout.slide_focus_from(b, a).unwrap();
// Ordinary reveal requests must not replace a pending slide.
layout.focus(b).unwrap();
assert_eq!(
layout.pending_focus(),
Some(PendingFocusRequest::Slide { from: b, to: a })
);
layout
.cycle_column_width(b, &[ColumnWidth::Fixed(150.0), ColumnWidth::Fixed(100.0)])
.unwrap();
assert!(matches!(
layout.pending_viewport,
Some(PendingViewportRequest::WidthCycle(_))
));
layout.focus_and_reveal(a).unwrap();
assert_eq!(
layout.pending_viewport,
Some(PendingViewportRequest::WidthCycle(Some(PendingFocusRequest::Reveal(a))))
);
layout
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(a))
.unwrap();
assert!(matches!(
layout.viewport_target().unwrap().request,
ViewportRequest::WidthCycle(_)
));
assert!(layout.pending_viewport.is_none());
}
#[test]
fn canceled_width_cycle_restores_focus_request_and_solver_consumes_it_once() {
let bounds = Rect::new(0.0, 0.0, 200.0, 100.0);
let mut layout = ScrollingLayout::with_default_width(ColumnWidth::Fixed(150.0));
let a = window(1);
let b = window(2);
layout.insert(a, None).unwrap();
layout.insert(b, Some(a)).unwrap();
layout.set_focus_strategy(ViewportFocusStrategy::Center);
layout
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(b))
.unwrap();
layout.slide_focus_from(b, a).unwrap();
assert!(layout.cycle_column_width(a, &[ColumnWidth::Fixed(100.0)]).unwrap());
assert!(!layout.cycle_column_width(a, &[ColumnWidth::Fixed(100.0)]).unwrap());
assert_eq!(
layout.pending_viewport,
Some(PendingViewportRequest::Focus(PendingFocusRequest::Slide {
from: b,
to: a
}))
);
layout
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(a))
.unwrap();
assert!(matches!(
layout.viewport_target().unwrap().request,
ViewportRequest::Slide(_, _)
));
let viewport = layout.viewport_x();
let provenance = layout.viewport_target().cloned();
layout
.resolve_geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(a))
.unwrap();
assert_eq!(layout.viewport_x(), viewport);
assert_eq!(layout.viewport_target(), provenance.as_ref());
assert!(layout.pending_viewport.is_none());
}
#[test]
fn explicit_center_replays_maximum_even_when_only_held_width_crosses_it() {
let bounds = Rect::new(0.0, 0.0, 200.0, 100.0);
@@ -973,11 +1101,11 @@ mod tests {
layout.insert(b, Some(a)).unwrap();
layout.insert(c, Some(b)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(c))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(c))
.unwrap();
layout.set_column_width(b, ColumnWidth::Fixed(150.0)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(c))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(c))
.unwrap();
assert!(layout.viewport_depends_on_width(b, 100.0, 150.0), "{strategy:?}");
assert!(
@@ -986,12 +1114,12 @@ mod tests {
);
// An unrelated relayout does not erase the last calculation.
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(c))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(c))
.unwrap();
assert!(layout.viewport_depends_on_width(b, 100.0, 150.0));
layout.focus_and_reveal(a).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(a))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(a))
.unwrap();
assert!(
!layout.viewport_depends_on_width(b, 100.0, 150.0),
@@ -1020,7 +1148,7 @@ mod tests {
},
)]);
let result = layout
.geometry_with_constraints(bounds, gaps, &constraints, Some(c))
.resolve_geometry_with_constraints(bounds, gaps, &constraints, Some(c))
.unwrap();
assert_eq!(layout.allocated_column_width(b), Some(150.0));
assert_eq!(result.geometry[&b].width, 150.0, "minimum wins inconsistent maximum");
@@ -1073,11 +1201,11 @@ mod tests {
layout.focus_and_reveal(window(target)).unwrap();
let mut expected = layout.clone();
expected
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(target)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(target)))
.unwrap();
layout.focus_without_reveal(window(2)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(layout.active_window(), Some(window(2)));
assert_eq!(layout.viewport_x(), expected.viewport_x(), "{strategy:?}");
@@ -1087,7 +1215,7 @@ mod tests {
layout.remove(window(3)).unwrap();
assert!(
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.is_ok()
);
}
@@ -1107,7 +1235,7 @@ mod tests {
};
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.unwrap();
assert_eq!(layout.viewport_x(), 1_000.0);
@@ -1182,7 +1310,7 @@ mod tests {
for (focused, expected) in [(1, 0.0), (2, 500.0), (3, 1500.0), (2, 1000.0), (2, 1000.0)] {
layout.focus_and_reveal(window(focused)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.unwrap();
assert_eq!(layout.viewport_x(), expected);
}
@@ -1200,7 +1328,7 @@ mod tests {
smart: false,
};
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(layout.viewport_x(), 1010.0);
assert_eq!(result.geometry[&window(2)].x, 0.0);
@@ -1208,7 +1336,7 @@ mod tests {
assert!(previous.x + previous.width <= 0.0);
layout.focus_and_reveal(window(1)).unwrap();
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
assert_eq!(result.geometry[&window(1)].x, 0.0);
@@ -1234,7 +1362,7 @@ mod tests {
for (focused, offset) in [1, 3, 2, 1].into_iter().zip(offsets) {
layout.focus_and_reveal(window(focused)).unwrap();
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.unwrap();
let rect = result.geometry[&window(focused)];
assert_eq!(layout.viewport_x(), offset, "width={width:?}, focused={focused}");
@@ -1264,11 +1392,11 @@ mod tests {
};
let first = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
let second = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
@@ -1285,12 +1413,12 @@ mod tests {
.set_column_width(window(2), ColumnWidth::Proportion(1.0))
.unwrap();
let zoomed = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(zoomed.geometry[&window(2)].width, 980.0);
layout.insert(window(3), Some(window(2))).unwrap();
let opened = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.unwrap();
let previous = opened.geometry[&window(2)];
let new = opened.geometry[&window(3)];
@@ -1302,7 +1430,7 @@ mod tests {
.set_column_width(window(2), ColumnWidth::Proportion(0.5))
.unwrap();
let restored = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(restored.geometry[&window(2)].width, 485.0);
}
@@ -1321,7 +1449,7 @@ mod tests {
let (mut layout, bounds, gaps) = paged_layout(6);
for focused in [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1] {
let geometry = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.unwrap()
.geometry;
let first = (focused - 1) / 2 * 2 + 1;
@@ -1349,12 +1477,12 @@ mod tests {
};
layout.focus_and_reveal(window(1)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
for (previous, focused, expected) in [(1, 2, 0.0), (2, 3, 500.0), (3, 2, 500.0), (2, 1, 0.0)] {
layout.slide_focus_from(window(previous), window(focused)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.unwrap();
assert_eq!(layout.viewport_x(), expected);
}
@@ -1364,7 +1492,7 @@ mod tests {
fn swipe_focus_slides_one_column_at_a_time_in_paged_layout() {
let (mut layout, bounds, gaps) = paged_layout(5);
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
@@ -1372,7 +1500,7 @@ mod tests {
layout.focus_and_reveal(window(focused)).unwrap();
layout.slide_focus_from(window(previous), window(focused)).unwrap();
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.unwrap();
assert_eq!(layout.viewport_x(), expected);
@@ -1384,11 +1512,11 @@ mod tests {
fn paged_focus_realigns_after_output_resize() {
let (mut layout, bounds, gaps) = paged_layout(6);
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(5)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(5)))
.unwrap();
let bounds = Rect::new(20.0, 48.0, 1400.0, 800.0);
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(5)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(5)))
.unwrap();
assert_eq!(layout.viewport_x(), 2780.0);
assert_eq!(result.geometry[&window(5)].x, 30.0);
@@ -1400,12 +1528,12 @@ mod tests {
let (mut layout, bounds, gaps) = paged_layout(4);
layout.center_window(window(2), bounds, gaps, &HashMap::new()).unwrap();
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
let rect = result.geometry[&window(2)];
assert_eq!(rect.x + rect.width / 2.0, 500.0);
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
assert_eq!(result.geometry[&window(1)].x, 10.0);
}
@@ -1415,12 +1543,12 @@ mod tests {
let (mut layout, bounds, gaps) = paged_layout(4);
layout.columns[0].width = ColumnWidth::Full;
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.unwrap();
assert_eq!(result.geometry[&window(2)].x, 10.0);
assert_eq!(result.geometry[&window(3)].x, 505.0);
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.unwrap();
assert_eq!(result.geometry[&window(4)].x, 10.0);
}
@@ -1436,7 +1564,7 @@ mod tests {
},
)]);
let result = layout
.geometry_with_constraints(bounds, gaps, &constraints, Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &constraints, Some(window(2)))
.unwrap();
assert!(layout.viewport_x() > 0.0);
let rect = result.geometry[&window(2)];
@@ -1452,7 +1580,7 @@ mod tests {
let bounds = Rect::new(13.25, 48.5, 1000.5, 800.25);
for focused in [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1] {
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.unwrap();
let first = (focused - 1) / 3 * 3 + 1;
assert!((result.geometry[&window(first)].x - 23.25).abs() < 1e-6);
@@ -1470,7 +1598,7 @@ mod tests {
}
for (focused, first) in [(1, 1), (2, 1), (3, 3), (4, 3), (5, 3), (2, 1)] {
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(focused)))
.unwrap();
assert_eq!(result.geometry[&window(first)].x, 10.0);
let rect = result.geometry[&window(focused)];
@@ -1489,13 +1617,13 @@ mod tests {
},
)]);
let result = layout
.geometry_with_constraints(bounds, gaps, &constraints, Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &constraints, Some(window(2)))
.unwrap();
assert_eq!(result.geometry[&window(2)].x, 10.0);
assert_eq!(result.geometry[&window(3)].x, 505.0);
layout.remove(window(1)).unwrap();
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
assert_eq!(result.geometry[&window(2)].x, 10.0);
@@ -1515,14 +1643,14 @@ mod tests {
for id in 1..=3 {
layout.insert(window(id), (id > 1).then(|| window(id - 1))).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(id)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(id)))
.unwrap();
}
let before = layout.viewport_x();
layout.resize_window(window(3), Direction::Right, 0.5).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.unwrap();
assert_eq!(layout.viewport_x(), before);
@@ -1542,10 +1670,10 @@ mod tests {
layout.insert(window(id), None).unwrap();
}
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(3)))
.unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
@@ -1565,13 +1693,13 @@ mod tests {
layout.insert(window(id), None).unwrap();
}
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.unwrap();
let before = layout.viewport_x();
layout.remove(window(1)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.unwrap();
assert_eq!(layout.viewport_x(), before);
@@ -1605,13 +1733,13 @@ mod tests {
..GapConfig::default()
};
let first = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap()
.geometry[&window(1)];
layout.insert(window(2), Some(window(1))).unwrap();
let second = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap()
.geometry[&window(1)];
@@ -1698,11 +1826,11 @@ mod tests {
layout.focus(window(1)).unwrap();
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
layout.cycle_column_width(window(1), &presets).unwrap();
let zoomed = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
@@ -1712,7 +1840,7 @@ mod tests {
layout.cycle_column_width(window(1), &presets).unwrap();
let restored = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(1)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
@@ -1726,11 +1854,11 @@ mod tests {
let presets = [ColumnWidth::Proportion(0.5), ColumnWidth::Full];
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
layout.cycle_column_width(window(2), &presets).unwrap();
let zoomed = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(layout.viewport_x(), 495.0);
@@ -1740,7 +1868,7 @@ mod tests {
layout.cycle_column_width(window(2), &presets).unwrap();
let restored = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
assert_eq!(layout.viewport_x(), 0.0);
@@ -1779,12 +1907,12 @@ mod tests {
smart: false,
};
layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(4)))
.unwrap();
assert!(layout.center_window(window(2), bounds, gaps, &HashMap::new()).unwrap());
let result = layout
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.resolve_geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(window(2)))
.unwrap();
let rect = result.geometry[&window(2)];