diff options
| -rw-r--r-- | src/layout.rs | 308 |
1 files changed, 140 insertions, 168 deletions
diff --git a/src/layout.rs b/src/layout.rs index e4db1107..19aebc1b 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -30,6 +30,7 @@ //! making the primary output their original output. use std::cmp::{max, min}; +use std::iter::zip; use std::mem; use std::rc::Rc; use std::time::Duration; @@ -150,6 +151,10 @@ pub struct Workspace<W: LayoutElement> { focus_ring: FocusRing, /// Offset of the view computed from the active column. + /// + /// Any left padding, including left padding from work area left exclusive zone, is handled + /// with this view offset (rather than added as a constant elsewhere in the code). This allows + /// for natural handling of fullscreen windows, which must ignore work area padding. view_offset: i32, /// Animation of the view offset, if one is currently ongoing. @@ -1612,12 +1617,12 @@ impl<W: LayoutElement> Workspace<W> { }); } - fn activate_column(&mut self, idx: usize) { - if self.active_column_idx == idx { - return; + fn compute_new_view_offset_for_column(&self, current_x: i32, idx: usize) -> i32 { + if self.columns[idx].is_fullscreen { + return 0; } - let current_x = self.view_pos(); + let new_col_x = self.column_x(idx); let final_x = if let Some(anim) = &self.view_offset_anim { current_x - self.view_offset + anim.to().round() as i32 @@ -1625,26 +1630,58 @@ impl<W: LayoutElement> Workspace<W> { current_x }; - self.active_column_idx = idx; - - // A different column was activated; reset the flag. - self.activate_prev_column_on_removal = false; - - let new_x = self.column_x(idx) - PADDING; - let new_view_offset = compute_new_view_offset( - final_x, + let new_offset = compute_new_view_offset( + final_x + self.working_area.loc.x, self.working_area.size.w, - new_x, - self.columns[idx].size().w, + new_col_x, + self.columns[idx].width(), ); - let from_view_offset = current_x - new_x; + // Non-fullscreen windows are always offset at least by the working area position. + new_offset - self.working_area.loc.x + } + + fn animate_view_offset_to_column(&mut self, current_x: i32, idx: usize) { + let new_view_offset = self.compute_new_view_offset_for_column(current_x, idx); + + let new_col_x = self.column_x(idx); + let from_view_offset = current_x - new_col_x; + self.view_offset = from_view_offset; + + // If we're already animating towards that, don't restart it. + if let Some(anim) = &self.view_offset_anim { + if anim.value().round() as i32 == self.view_offset + && anim.to().round() as i32 == new_view_offset + { + return; + } + } + + // If our view offset is already this, we don't need to do anything. + if self.view_offset == new_view_offset { + self.view_offset_anim = None; + return; + } + self.view_offset_anim = Some(Animation::new( - from_view_offset as f64, + self.view_offset as f64, new_view_offset as f64, Duration::from_millis(250), )); - self.view_offset = from_view_offset; + } + + fn activate_column(&mut self, idx: usize) { + if self.active_column_idx == idx { + return; + } + + let current_x = self.view_pos(); + self.animate_view_offset_to_column(current_x, idx); + + self.active_column_idx = idx; + + // A different column was activated; reset the flag. + self.activate_prev_column_on_removal = false; } fn has_windows(&self) -> bool { @@ -1661,10 +1698,10 @@ impl<W: LayoutElement> Workspace<W> { /// Computes the X position of the windows in the given column, in logical coordinates. fn column_x(&self, column_idx: usize) -> i32 { - let mut x = PADDING; + let mut x = 0; for column in self.columns.iter().take(column_idx) { - x += column.size().w + PADDING; + x += column.width() + PADDING; } x @@ -1673,6 +1710,8 @@ impl<W: LayoutElement> Workspace<W> { fn add_window(&mut self, window: W, activate: bool) { self.enter_output_for_window(&window); + let was_empty = self.columns.is_empty(); + let idx = if self.columns.is_empty() { 0 } else { @@ -1688,6 +1727,15 @@ impl<W: LayoutElement> Workspace<W> { self.columns.insert(idx, column); if activate { + // If this is the first window on an empty workspace, skip the animation from whatever + // view_offset was left over. + if was_empty { + // Try to make the code produce a left-aligned offset, even in presence of left + // exclusive zones. + self.view_offset = self.compute_new_view_offset_for_column(self.column_x(0), 0); + self.view_offset_anim = None; + } + self.activate_column(idx); self.activate_prev_column_on_removal = true; } @@ -1752,36 +1800,7 @@ impl<W: LayoutElement> Workspace<W> { if idx == self.active_column_idx { // We might need to move the view to ensure the resized window is still visible. let current_x = self.view_pos(); - - let final_x = if let Some(anim) = &self.view_offset_anim { - current_x - self.view_offset + anim.to().round() as i32 - } else { - current_x - }; - - let new_x = self.column_x(idx) - PADDING; - - let new_view_offset = compute_new_view_offset( - final_x, - self.working_area.size.w, - new_x, - self.columns[idx].size().w, - ); - - let cur_view_offset = self - .view_offset_anim - .as_ref() - .map(|a| a.to().round() as i32) - .unwrap_or(self.view_offset); - if cur_view_offset != new_view_offset { - let from_view_offset = current_x - new_x; - self.view_offset_anim = Some(Animation::new( - from_view_offset as f64, - new_view_offset as f64, - Duration::from_millis(250), - )); - self.view_offset = from_view_offset; - } + self.animate_view_offset_to_column(current_x, idx); } } @@ -1849,14 +1868,8 @@ impl<W: LayoutElement> Workspace<W> { self.columns.swap(self.active_column_idx, new_idx); - let new_x = self.column_x(self.active_column_idx) - PADDING; - let new_view_offset = compute_new_view_offset( - current_x, - self.working_area.size.w, - new_x, - self.columns[self.active_column_idx].size().w, - ); - self.view_offset = new_view_offset; + self.view_offset = + self.compute_new_view_offset_for_column(current_x, self.active_column_idx); self.activate_column(new_idx); } @@ -1875,14 +1888,8 @@ impl<W: LayoutElement> Workspace<W> { self.columns.swap(self.active_column_idx, new_idx); - let new_x = self.column_x(self.active_column_idx) - PADDING; - let new_view_offset = compute_new_view_offset( - current_x, - self.working_area.size.w, - new_x, - self.columns[self.active_column_idx].size().w, - ); - self.view_offset = new_view_offset; + self.view_offset = + self.compute_new_view_offset_for_column(current_x, self.active_column_idx); self.activate_column(new_idx); } @@ -1939,70 +1946,44 @@ impl<W: LayoutElement> Workspace<W> { } fn view_pos(&self) -> i32 { - self.column_x(self.active_column_idx) + self.view_offset - PADDING + self.column_x(self.active_column_idx) + self.view_offset } - fn window_under( - &self, - pos_within_output: Point<f64, Logical>, - ) -> Option<(&W, Point<i32, Logical>)> { + fn window_under(&self, pos: Point<f64, Logical>) -> Option<(&W, Point<i32, Logical>)> { if self.columns.is_empty() { return None; } let view_pos = self.view_pos(); - let mut pos = pos_within_output; - pos.x += view_pos as f64; - // Prefer the active window since it's drawn on top. let col = &self.columns[self.active_column_idx]; let active_win = &col.windows[col.active_window_idx]; let geom = active_win.geometry(); - let mut win_pos = Point::from(( - self.column_x(self.active_column_idx), + let buf_pos = Point::from(( + self.column_x(self.active_column_idx) - view_pos, col.window_y(col.active_window_idx), )) - geom.loc; - if col.is_fullscreen { - // FIXME: fullscreen windows are missing left padding - win_pos.x -= PADDING; - } else { - win_pos += self.working_area.loc; - } - if active_win.is_in_input_region(&(pos - win_pos.to_f64())) { - let mut win_pos_within_output = win_pos; - win_pos_within_output.x -= view_pos; - return Some((active_win, win_pos_within_output)); + if active_win.is_in_input_region(&(pos - buf_pos.to_f64())) { + return Some((active_win, buf_pos)); } - let mut x = PADDING; + let mut x = -view_pos; for col in &self.columns { - let mut y = PADDING; + for (win, y) in zip(&col.windows, col.window_ys()) { + if win == active_win { + // Already handled it above. + continue; + } - for win in &col.windows { let geom = win.geometry(); - - if win != active_win { - // x, y point at the top-left of the window geometry. - let mut win_pos = Point::from((x, y)) - geom.loc; - if col.is_fullscreen { - // FIXME: fullscreen windows are missing left padding - win_pos.x -= PADDING; - win_pos.y -= PADDING; - } else { - win_pos += self.working_area.loc; - } - if win.is_in_input_region(&(pos - win_pos.to_f64())) { - let mut win_pos_within_output = win_pos; - win_pos_within_output.x -= view_pos; - return Some((win, win_pos_within_output)); - } + let buf_pos = Point::from((x, y)) - geom.loc; + if win.is_in_input_region(&(pos - buf_pos.to_f64())) { + return Some((win, buf_pos)); } - - y += geom.size.h + PADDING; } - x += col.size().w + PADDING; + x += col.width() + PADDING; } None @@ -2119,22 +2100,17 @@ impl Workspace<Window> { // Draw the active window on top. let col = &self.columns[self.active_column_idx]; let active_win = &col.windows[col.active_window_idx]; - let geom = active_win.geometry(); - let mut win_pos = Point::from(( + let win_pos = Point::from(( self.column_x(self.active_column_idx) - view_pos, col.window_y(col.active_window_idx), - )) - geom.loc; - if col.is_fullscreen { - // FIXME: fullscreen windows are missing left padding - win_pos.x -= PADDING; - } else { - win_pos += self.working_area.loc; - } + )); // Draw the window itself. + let geom = active_win.geometry(); + let buf_pos = win_pos - geom.loc; rv.extend(active_win.render_elements( renderer, - win_pos.to_physical_precise_round(output_scale), + buf_pos.to_physical_precise_round(output_scale), output_scale, 1., )); @@ -2142,39 +2118,29 @@ impl Workspace<Window> { // Draw the focus ring. rv.extend( self.focus_ring - .render(win_pos + geom.loc, output_scale) + .render(win_pos, output_scale) .map(Into::into), ); - let mut x = PADDING; + let mut x = -view_pos; for col in &self.columns { - let mut y = PADDING; - - for win in &col.windows { - let geom = win.geometry(); - - if win != active_win { - let mut win_pos = Point::from((x - view_pos, y)) - geom.loc; - if col.is_fullscreen { - // FIXME: fullscreen windows are missing left padding - win_pos.x -= PADDING; - win_pos.y -= PADDING; - } else { - win_pos += self.working_area.loc; - } - - rv.extend(win.render_elements( - renderer, - win_pos.to_physical_precise_round(output_scale), - output_scale, - 1., - )); + for (win, y) in zip(&col.windows, col.window_ys()) { + if win == active_win { + // Already handled it above. + continue; } - y += geom.size.h + PADDING; + let geom = win.geometry(); + let buf_pos = Point::from((x, y)) - geom.loc; + rv.extend(win.render_elements( + renderer, + buf_pos.to_physical_precise_round(output_scale), + output_scale, + 1., + )); } - x += col.size().w + PADDING; + x += col.width() + PADDING; } rv @@ -2292,17 +2258,12 @@ impl<W: LayoutElement> Column<W> { } } - /// Computes the size of the column including top and bottom padding. - fn size(&self) -> Size<i32, Logical> { - let mut total = Size::from((0, PADDING)); - - for window in &self.windows { - let size = window.geometry().size; - total.w = max(total.w, size.w); - total.h += size.h + PADDING; - } - - total + fn width(&self) -> i32 { + self.windows + .iter() + .map(|win| win.geometry().size.w) + .max() + .unwrap() } fn focus_up(&mut self) { @@ -2347,7 +2308,7 @@ impl<W: LayoutElement> Column<W> { let idx = match self.width { ColumnWidth::Preset(idx) => (idx + 1) % self.options.preset_widths.len(), _ => { - let current = self.size().w; + let current = self.width(); self.options .preset_widths .iter() @@ -2416,17 +2377,21 @@ impl<W: LayoutElement> Column<W> { } fn window_y(&self, window_idx: usize) -> i32 { - if self.is_fullscreen { - return 0; - } + self.window_ys().nth(window_idx).unwrap() + } - let mut y = PADDING; + fn window_ys(&self) -> impl Iterator<Item = i32> + '_ { + let mut y = 0; - for win in self.windows.iter().take(window_idx) { - y += win.geometry().size.h + PADDING; + if !self.is_fullscreen { + y = self.working_area.loc.y + PADDING; } - y + self.windows.iter().map(move |win| { + let pos = y; + y += win.geometry().size.h + PADDING; + pos + }) } } @@ -2440,24 +2405,31 @@ pub fn output_size(output: &Output) -> Size<i32, Logical> { .to_logical(output_scale) } -fn compute_new_view_offset(cur_x: i32, view_width: i32, new_x: i32, new_col_width: i32) -> i32 { +fn compute_new_view_offset(cur_x: i32, view_width: i32, new_col_x: i32, new_col_width: i32) -> i32 { // If the column is wider than the view, always left-align it. - if new_col_width + PADDING * 2 >= view_width { + if view_width <= new_col_width { return 0; } + // Compute the padding in case it needs to be smaller due to large window width. + let padding = ((view_width - new_col_width) / 2).clamp(0, PADDING); + + // Compute the desired new X with padding. + let new_x = new_col_x - padding; + let new_right_x = new_col_x + new_col_width + padding; + // If the column is already fully visible, leave the view as is. - if new_x >= cur_x && new_x + new_col_width + PADDING * 2 <= cur_x + view_width { - return -(new_x - cur_x); + if cur_x <= new_x && new_right_x <= cur_x + view_width { + return -(new_col_x - cur_x); } // Otherwise, prefer the aligment that results in less motion from the current position. let dist_to_left = cur_x.abs_diff(new_x); - let dist_to_right = (cur_x + view_width).abs_diff(new_x + new_col_width + PADDING * 2); + let dist_to_right = (cur_x + view_width).abs_diff(new_right_x); if dist_to_left <= dist_to_right { - 0 + -padding } else { - -(view_width - new_col_width - PADDING * 2) + -(view_width - padding - new_col_width) } } |
