mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
layout: Extract ScrollingSpace
Leave the Workspace to do the workspace parts, and extract the scrolling parts into a new file. This is a pre-requisite for things like the floating layer (which will live in a workspace alongside the scrolling layer). As part of this huge refactor, I found and fixed at least these issues: - Wrong horizontal popup unconstraining for a smaller window in an always-centered column. - Wrong workspace switch in focus_up_or_right().
This commit is contained in:
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri::animation::Clock;
|
||||
use niri::layout::workspace::ColumnWidth;
|
||||
use niri::layout::scrolling::ColumnWidth;
|
||||
use niri::layout::{ActivateWindow, LayoutElement as _, Options};
|
||||
use niri::render_helpers::RenderTarget;
|
||||
use niri_config::{Color, FloatOrInt, OutputName};
|
||||
|
||||
@@ -189,9 +189,8 @@ impl CompositorHandler for State {
|
||||
if let Some(output) = output.cloned() {
|
||||
self.niri.layout.start_open_animation_for_window(&window);
|
||||
|
||||
let new_active_window =
|
||||
self.niri.layout.active_window().map(|(m, _)| &m.window);
|
||||
if new_active_window == Some(&window) {
|
||||
let new_focus = self.niri.layout.focus().map(|m| &m.window);
|
||||
if new_focus == Some(&window) {
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
|
||||
@@ -242,7 +241,7 @@ impl CompositorHandler for State {
|
||||
// The toplevel got unmapped.
|
||||
//
|
||||
// Test client: wleird-unmap.
|
||||
let active_window = self.niri.layout.active_window().map(|(m, _)| &m.window);
|
||||
let active_window = self.niri.layout.focus().map(|m| &m.window);
|
||||
let was_active = active_window == Some(&window);
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
@@ -290,7 +289,7 @@ impl CompositorHandler for State {
|
||||
self.niri.layout.update_window(&window, serial);
|
||||
|
||||
// Popup placement depends on window size which might have changed.
|
||||
self.update_reactive_popups(&window, &output);
|
||||
self.update_reactive_popups(&window);
|
||||
|
||||
self.niri.queue_redraw(&output);
|
||||
return;
|
||||
|
||||
@@ -42,7 +42,7 @@ use crate::input::resize_grab::ResizeGrab;
|
||||
use crate::input::touch_move_grab::TouchMoveGrab;
|
||||
use crate::input::touch_resize_grab::TouchResizeGrab;
|
||||
use crate::input::{PointerOrTouchStartData, DOUBLE_CLICK_TIME};
|
||||
use crate::layout::workspace::ColumnWidth;
|
||||
use crate::layout::scrolling::ColumnWidth;
|
||||
use crate::niri::{PopupGrabState, State};
|
||||
use crate::utils::transaction::Transaction;
|
||||
use crate::utils::{get_monotonic_time, output_matches_name, send_scale_transform, ResizeEdge};
|
||||
@@ -609,7 +609,7 @@ impl XdgShellHandler for State {
|
||||
.start_close_animation_for_window(renderer, &window, blocker);
|
||||
});
|
||||
|
||||
let active_window = self.niri.layout.active_window().map(|(m, _)| &m.window);
|
||||
let active_window = self.niri.layout.focus().map(|m| &m.window);
|
||||
let was_active = active_window == Some(&window);
|
||||
|
||||
self.niri.layout.remove_window(&window, transaction.clone());
|
||||
@@ -928,8 +928,8 @@ impl State {
|
||||
};
|
||||
|
||||
// Figure out if the root is a window or a layer surface.
|
||||
if let Some((mapped, output)) = self.niri.layout.find_window_and_output(&root) {
|
||||
self.unconstrain_window_popup(popup, &mapped.window, output);
|
||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&root) {
|
||||
self.unconstrain_window_popup(popup, &mapped.window);
|
||||
} else if let Some((layer_surface, output)) = self.niri.layout.outputs().find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
let layer_surface = map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?;
|
||||
@@ -939,19 +939,10 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
fn unconstrain_window_popup(&self, popup: &PopupKind, window: &Window, output: &Output) {
|
||||
let window_geo = window.geometry();
|
||||
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||
|
||||
fn unconstrain_window_popup(&self, popup: &PopupKind, window: &Window) {
|
||||
// The target geometry for the positioner should be relative to its parent's geometry, so
|
||||
// we will compute that here.
|
||||
//
|
||||
// We try to keep regular window popups within the window itself horizontally (since the
|
||||
// window can be scrolled to both edges of the screen), but within the whole monitor's
|
||||
// height.
|
||||
let mut target =
|
||||
Rectangle::from_loc_and_size((0, 0), (window_geo.size.w, output_geo.size.h)).to_f64();
|
||||
target.loc -= self.niri.layout.window_loc(window).unwrap();
|
||||
let mut target = self.niri.layout.popup_target_rect(window);
|
||||
target.loc -= get_popup_toplevel_coords(popup).to_f64();
|
||||
|
||||
self.position_popup_within_rect(popup, target);
|
||||
@@ -1016,7 +1007,7 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_reactive_popups(&self, window: &Window, output: &Output) {
|
||||
pub fn update_reactive_popups(&self, window: &Window) {
|
||||
let _span = tracy_client::span!("Niri::update_reactive_popups");
|
||||
|
||||
for (popup, _) in PopupManager::popups_for_surface(
|
||||
@@ -1025,7 +1016,7 @@ impl State {
|
||||
match &popup {
|
||||
xdg_popup @ PopupKind::Xdg(popup) => {
|
||||
if popup.with_pending_state(|state| state.positioner.reactive) {
|
||||
self.unconstrain_window_popup(xdg_popup, window, output);
|
||||
self.unconstrain_window_popup(xdg_popup, window);
|
||||
if let Err(err) = popup.send_pending_configure() {
|
||||
warn!("error re-configuring reactive popup: {err:?}");
|
||||
}
|
||||
|
||||
+4
-4
@@ -581,8 +581,8 @@ impl State {
|
||||
self.open_screenshot_ui();
|
||||
}
|
||||
Action::ScreenshotWindow => {
|
||||
let active = self.niri.layout.active_window();
|
||||
if let Some((mapped, output)) = active {
|
||||
let focus = self.niri.layout.focus_with_output();
|
||||
if let Some((mapped, output)) = focus {
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
if let Err(err) = self.niri.screenshot_window(renderer, output, mapped) {
|
||||
warn!("error taking screenshot: {err:?}");
|
||||
@@ -990,8 +990,8 @@ impl State {
|
||||
self.niri.layout.move_to_workspace(Some(&window), index);
|
||||
|
||||
// If we focused the target window.
|
||||
let new_active_win = self.niri.layout.active_window();
|
||||
if new_active_win.map_or(false, |(win, _)| win.window == window) {
|
||||
let new_focus = self.niri.layout.focus();
|
||||
if new_focus.map_or(false, |win| win.window == window) {
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
}
|
||||
|
||||
+75
-248
@@ -40,6 +40,7 @@ use niri_config::{
|
||||
Workspace as WorkspaceConfig,
|
||||
};
|
||||
use niri_ipc::SizeChange;
|
||||
use scrolling::{Column, ColumnWidth, InsertHint, InsertPosition};
|
||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
||||
use smithay::backend::renderer::element::Id;
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
@@ -51,9 +52,8 @@ use workspace::WorkspaceId;
|
||||
|
||||
pub use self::monitor::MonitorRenderElement;
|
||||
use self::monitor::{Monitor, WorkspaceSwitch};
|
||||
use self::workspace::{compute_working_area, Column, ColumnWidth, InsertHint, OutputId, Workspace};
|
||||
use self::workspace::{OutputId, Workspace};
|
||||
use crate::animation::Clock;
|
||||
use crate::layout::workspace::InsertPosition;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
@@ -70,6 +70,7 @@ pub mod focus_ring;
|
||||
pub mod insert_hint_element;
|
||||
pub mod monitor;
|
||||
pub mod opening_window;
|
||||
pub mod scrolling;
|
||||
pub mod tile;
|
||||
pub mod workspace;
|
||||
|
||||
@@ -801,10 +802,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
let activate = activate.map_smart(|| {
|
||||
// Don't steal focus from an active fullscreen window.
|
||||
let ws = &mon.workspaces[ws_idx];
|
||||
if mon_idx == *active_monitor_idx
|
||||
&& !ws.columns.is_empty()
|
||||
&& ws.columns[ws.active_column_idx].is_fullscreen
|
||||
{
|
||||
if mon_idx == *active_monitor_idx && ws.is_active_fullscreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -829,7 +827,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
})
|
||||
.unwrap();
|
||||
let activate = activate.map_smart(|| true);
|
||||
ws.add_window(None, window, activate, width, is_full_width);
|
||||
ws.add_window(window, activate, width, is_full_width);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -881,7 +879,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
let activate = activate.map_smart(|| {
|
||||
// Don't steal focus from an active fullscreen window.
|
||||
let ws = &mon.workspaces[mon.active_workspace_idx];
|
||||
ws.columns.is_empty() || !ws.columns[ws.active_column_idx].is_fullscreen
|
||||
!ws.is_active_fullscreen()
|
||||
});
|
||||
|
||||
mon.add_window(
|
||||
@@ -904,7 +902,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
&mut workspaces[0]
|
||||
};
|
||||
let activate = activate.map_smart(|| true);
|
||||
ws.add_window(None, window, activate, width, is_full_width);
|
||||
ws.add_window(window, activate, width, is_full_width);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -991,13 +989,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
let activate = activate.map_smart(|| {
|
||||
// Don't steal focus from an active fullscreen window.
|
||||
let ws = &mon.workspaces[mon.active_workspace_idx];
|
||||
if mon_idx == *active_monitor_idx
|
||||
&& !ws.columns.is_empty()
|
||||
&& ws.columns[ws.active_column_idx].is_fullscreen
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
mon_idx != *active_monitor_idx || !ws.is_active_fullscreen()
|
||||
});
|
||||
|
||||
mon.add_window(
|
||||
@@ -1263,37 +1255,28 @@ impl<W: LayoutElement> Layout<W> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn window_loc(&self, window: &W::Id) -> Option<Point<f64, Logical>> {
|
||||
/// Computes the window-geometry-relative target rect for popup unconstraining.
|
||||
///
|
||||
/// We will try to fit popups inside this rect.
|
||||
pub fn popup_target_rect(&self, window: &W::Id) -> Rectangle<f64, Logical> {
|
||||
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
|
||||
if move_.tile.window().id() == window {
|
||||
return Some(move_.tile.window_loc());
|
||||
// Follow the scrolling layout logic and fit the popup horizontally within the
|
||||
// window geometry.
|
||||
let width = move_.tile.window_size().w;
|
||||
let height = output_size(&move_.output).h;
|
||||
let mut target = Rectangle::from_loc_and_size((0., 0.), (width, height));
|
||||
// FIXME: ideally this shouldn't include the tile render offset, but the code
|
||||
// duplication would be a bit annoying for this edge case.
|
||||
target.loc.y -= move_.tile_render_location().y;
|
||||
target.loc.y -= move_.tile.window_loc().y;
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
match &self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mon.workspaces {
|
||||
for col in &ws.columns {
|
||||
if let Some(idx) = col.position(window) {
|
||||
return Some(col.window_loc(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||
for ws in workspaces {
|
||||
for col in &ws.columns {
|
||||
if let Some(idx) = col.position(window) {
|
||||
return Some(col.window_loc(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
self.workspaces()
|
||||
.find_map(|(_, _, ws)| ws.popup_target_rect(window))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn update_output_size(&mut self, output: &Output) {
|
||||
@@ -1305,13 +1288,8 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
for mon in monitors {
|
||||
if &mon.output == output {
|
||||
let scale = output.current_scale();
|
||||
let transform = output.current_transform();
|
||||
let view_size = output_size(output);
|
||||
let working_area = compute_working_area(output, self.options.struts);
|
||||
|
||||
for ws in &mut mon.workspaces {
|
||||
ws.set_view_size(scale, transform, view_size, working_area);
|
||||
ws.update_output_size();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1471,31 +1449,6 @@ impl<W: LayoutElement> Layout<W> {
|
||||
Some(&mut mon.workspaces[mon.active_workspace_idx])
|
||||
}
|
||||
|
||||
pub fn active_window(&self) -> Option<(&W, &Output)> {
|
||||
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
|
||||
return Some((move_.tile.window(), &move_.output));
|
||||
}
|
||||
|
||||
let MonitorSet::Normal {
|
||||
monitors,
|
||||
active_monitor_idx,
|
||||
..
|
||||
} = &self.monitor_set
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mon = &monitors[*active_monitor_idx];
|
||||
let ws = &mon.workspaces[mon.active_workspace_idx];
|
||||
|
||||
if ws.columns.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let col = &ws.columns[ws.active_column_idx];
|
||||
Some((col.tiles[col.active_tile_idx].window(), &mon.output))
|
||||
}
|
||||
|
||||
pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ {
|
||||
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
||||
panic!()
|
||||
@@ -1661,11 +1614,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
pub fn move_column_left_or_to_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
let curr_idx = workspace.active_column_idx;
|
||||
|
||||
if !workspace.columns.is_empty() && curr_idx != 0 {
|
||||
monitor.move_left();
|
||||
if monitor.move_left() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1676,11 +1625,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
pub fn move_column_right_or_to_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
let curr_idx = workspace.active_column_idx;
|
||||
|
||||
if !workspace.columns.is_empty() && curr_idx != workspace.columns.len() - 1 {
|
||||
monitor.move_right();
|
||||
if monitor.move_right() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1807,15 +1752,8 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
pub fn focus_window_up_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
|
||||
if !workspace.columns.is_empty() {
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx != new_idx {
|
||||
workspace.focus_up();
|
||||
return false;
|
||||
}
|
||||
if monitor.focus_up() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1825,16 +1763,8 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
pub fn focus_window_down_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
|
||||
if !workspace.columns.is_empty() {
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx != new_idx {
|
||||
workspace.focus_down();
|
||||
return false;
|
||||
}
|
||||
if monitor.focus_down() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1844,11 +1774,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
pub fn focus_column_left_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
let curr_idx = workspace.active_column_idx;
|
||||
|
||||
if !workspace.columns.is_empty() && curr_idx != 0 {
|
||||
monitor.focus_left();
|
||||
if monitor.focus_left() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1859,12 +1785,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
pub fn focus_column_right_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
let curr_idx = workspace.active_column_idx;
|
||||
let columns = &workspace.columns;
|
||||
|
||||
if !workspace.columns.is_empty() && curr_idx != columns.len() - 1 {
|
||||
monitor.focus_right();
|
||||
if monitor.focus_right() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2040,8 +1961,12 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
|
||||
pub fn focus(&self) -> Option<&W> {
|
||||
self.focus_with_output().map(|(win, _out)| win)
|
||||
}
|
||||
|
||||
pub fn focus_with_output(&self) -> Option<(&W, &Output)> {
|
||||
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
|
||||
return Some(move_.tile.window());
|
||||
return Some((move_.tile.window(), &move_.output));
|
||||
}
|
||||
|
||||
let MonitorSet::Normal {
|
||||
@@ -2053,7 +1978,8 @@ impl<W: LayoutElement> Layout<W> {
|
||||
return None;
|
||||
};
|
||||
|
||||
monitors[*active_monitor_idx].focus()
|
||||
let mon = &monitors[*active_monitor_idx];
|
||||
mon.active_window().map(|win| (win, &mon.output))
|
||||
}
|
||||
|
||||
/// Returns the window under the cursor and the position of its toplevel surface within the
|
||||
@@ -2248,12 +2174,12 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
|
||||
assert!(
|
||||
monitor.workspaces.last().unwrap().columns.is_empty(),
|
||||
!monitor.workspaces.last().unwrap().has_windows(),
|
||||
"monitor must have an empty workspace in the end"
|
||||
);
|
||||
if monitor.options.empty_workspace_above_first {
|
||||
assert!(
|
||||
monitor.workspaces.first().unwrap().columns.is_empty(),
|
||||
!monitor.workspaces.first().unwrap().has_windows(),
|
||||
"first workspace must be empty when empty_workspace_above_first is set"
|
||||
)
|
||||
}
|
||||
@@ -2710,34 +2636,21 @@ impl<W: LayoutElement> Layout<W> {
|
||||
.position(|mon| &mon.output == output)
|
||||
.unwrap();
|
||||
|
||||
let (mon_idx, ws_idx, col_idx, tile_idx) = if let Some(window) = window {
|
||||
let (mon_idx, ws_idx) = if let Some(window) = window {
|
||||
monitors
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(mon_idx, mon)| {
|
||||
mon.workspaces.iter().enumerate().find_map(|(ws_idx, ws)| {
|
||||
ws.columns.iter().enumerate().find_map(|(col_idx, col)| {
|
||||
col.tiles
|
||||
.iter()
|
||||
.position(|tile| tile.window().id() == window)
|
||||
.map(|tile_idx| (mon_idx, ws_idx, col_idx, tile_idx))
|
||||
})
|
||||
})
|
||||
mon.workspaces
|
||||
.iter()
|
||||
.position(|ws| ws.has_window(window))
|
||||
.map(|ws_idx| (mon_idx, ws_idx))
|
||||
})
|
||||
.unwrap()
|
||||
} else {
|
||||
let mon_idx = *active_monitor_idx;
|
||||
let mon = &monitors[mon_idx];
|
||||
let ws_idx = mon.active_workspace_idx;
|
||||
let ws = &mon.workspaces[ws_idx];
|
||||
|
||||
if ws.columns.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let col_idx = ws.active_column_idx;
|
||||
let tile_idx = ws.columns[col_idx].active_tile_idx;
|
||||
(mon_idx, ws_idx, col_idx, tile_idx)
|
||||
(mon_idx, mon.active_workspace_idx)
|
||||
};
|
||||
|
||||
let workspace_idx = target_ws_idx.unwrap_or(monitors[new_idx].active_workspace_idx);
|
||||
@@ -2746,14 +2659,20 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
|
||||
let mon = &mut monitors[mon_idx];
|
||||
let ws = &mut mon.workspaces[ws_idx];
|
||||
let column = &ws.columns[col_idx];
|
||||
let activate = mon_idx == *active_monitor_idx
|
||||
&& ws_idx == mon.active_workspace_idx
|
||||
&& col_idx == ws.active_column_idx
|
||||
&& tile_idx == column.active_tile_idx;
|
||||
let activate = window.map_or(true, |win| {
|
||||
mon_idx == *active_monitor_idx
|
||||
&& mon.active_window().map(|win| win.id()) == Some(win)
|
||||
});
|
||||
|
||||
let removed = ws.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None);
|
||||
let ws = &mut mon.workspaces[ws_idx];
|
||||
let transaction = Transaction::new();
|
||||
let removed = if let Some(window) = window {
|
||||
ws.remove_tile(window, transaction)
|
||||
} else if let Some(removed) = ws.remove_active_tile(transaction) {
|
||||
removed
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.add_window_by_idx(
|
||||
new_idx,
|
||||
@@ -2788,10 +2707,9 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
let current = &mut monitors[*active_monitor_idx];
|
||||
let ws = current.active_workspace();
|
||||
if !ws.has_windows() {
|
||||
let Some(column) = ws.remove_active_column() else {
|
||||
return;
|
||||
}
|
||||
let column = ws.remove_column_by_idx(ws.active_column_idx, None);
|
||||
};
|
||||
|
||||
let workspace_idx = monitors[new_idx].active_workspace_idx;
|
||||
self.add_column_by_idx(new_idx, workspace_idx, column, true);
|
||||
@@ -3373,14 +3291,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
};
|
||||
|
||||
// No point in trying to use the pointer position without outputs.
|
||||
ws.add_tile(
|
||||
None,
|
||||
move_.tile,
|
||||
true,
|
||||
move_.width,
|
||||
move_.is_full_width,
|
||||
None,
|
||||
);
|
||||
ws.add_tile(None, move_.tile, true, move_.width, move_.is_full_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3491,31 +3402,11 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mut mon.workspaces {
|
||||
for col in &mut ws.columns {
|
||||
for tile in &mut col.tiles {
|
||||
if tile.window().id() == window {
|
||||
tile.start_open_animation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||
for ws in workspaces {
|
||||
for col in &mut ws.columns {
|
||||
for tile in &mut col.tiles {
|
||||
if tile.window().id() == window {
|
||||
tile.start_open_animation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for ws in self.workspaces_mut() {
|
||||
for tile in ws.tiles_mut() {
|
||||
if tile.window().id() == window {
|
||||
tile.start_open_animation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3615,7 +3506,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
.find(|ws| ws.id() == ws_id)
|
||||
.unwrap();
|
||||
|
||||
let tile_pos = tile_pos + Point::from((ws.view_pos(), 0.)) - offset;
|
||||
let tile_pos = tile_pos - offset;
|
||||
ws.start_close_animation_for_tile(renderer, snapshot, tile_size, tile_pos, blocker);
|
||||
return;
|
||||
}
|
||||
@@ -3823,7 +3714,6 @@ mod tests {
|
||||
use smithay::utils::Rectangle;
|
||||
|
||||
use super::*;
|
||||
use crate::utils::round_logical_in_physical;
|
||||
|
||||
impl<W: LayoutElement> Default for Layout<W> {
|
||||
fn default() -> Self {
|
||||
@@ -5532,7 +5422,8 @@ mod tests {
|
||||
"the second workspace must remain active"
|
||||
);
|
||||
assert_eq!(
|
||||
mon.workspaces[0].active_column_idx, 1,
|
||||
mon.workspaces[0].scrolling().active_column_idx(),
|
||||
1,
|
||||
"the new window must become active"
|
||||
);
|
||||
}
|
||||
@@ -5577,7 +5468,8 @@ mod tests {
|
||||
"the second workspace must remain active"
|
||||
);
|
||||
assert_eq!(
|
||||
mon.workspaces[1].active_column_idx, 1,
|
||||
mon.workspaces[1].scrolling().active_column_idx(),
|
||||
1,
|
||||
"the new window must become active"
|
||||
);
|
||||
}
|
||||
@@ -5768,71 +5660,6 @@ mod tests {
|
||||
layout.verify_invariants();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn working_area_starts_at_physical_pixel() {
|
||||
let struts = Struts {
|
||||
left: FloatOrInt(0.5),
|
||||
right: FloatOrInt(1.),
|
||||
top: FloatOrInt(0.75),
|
||||
bottom: FloatOrInt(1.),
|
||||
};
|
||||
|
||||
let output = Output::new(
|
||||
String::from("output"),
|
||||
PhysicalProperties {
|
||||
size: Size::from((1280, 720)),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: String::new(),
|
||||
model: String::new(),
|
||||
},
|
||||
);
|
||||
output.change_current_state(
|
||||
Some(Mode {
|
||||
size: Size::from((1280, 720)),
|
||||
refresh: 60000,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let area = compute_working_area(&output, struts);
|
||||
|
||||
assert_eq!(round_logical_in_physical(1., area.loc.x), area.loc.x);
|
||||
assert_eq!(round_logical_in_physical(1., area.loc.y), area.loc.y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_fractional_strut() {
|
||||
let struts = Struts {
|
||||
left: FloatOrInt(0.),
|
||||
right: FloatOrInt(0.),
|
||||
top: FloatOrInt(50000.5),
|
||||
bottom: FloatOrInt(0.),
|
||||
};
|
||||
|
||||
let output = Output::new(
|
||||
String::from("output"),
|
||||
PhysicalProperties {
|
||||
size: Size::from((1280, 720)),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: String::new(),
|
||||
model: String::new(),
|
||||
},
|
||||
);
|
||||
output.change_current_state(
|
||||
Some(Mode {
|
||||
size: Size::from((1280, 720)),
|
||||
refresh: 60000,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
compute_working_area(&output, struts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_window_height_recomputes_to_auto() {
|
||||
let ops = [
|
||||
|
||||
+55
-177
@@ -9,11 +9,9 @@ use smithay::backend::renderer::element::utils::{
|
||||
use smithay::output::Output;
|
||||
use smithay::utils::{Logical, Point, Rectangle};
|
||||
|
||||
use super::scrolling::{Column, ColumnWidth};
|
||||
use super::tile::Tile;
|
||||
use super::workspace::{
|
||||
compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceId,
|
||||
WorkspaceRenderElement,
|
||||
};
|
||||
use super::workspace::{OutputId, Workspace, WorkspaceId, WorkspaceRenderElement};
|
||||
use super::{LayoutElement, Options};
|
||||
use crate::animation::{Animation, Clock};
|
||||
use crate::input::swipe_tracker::SwipeTracker;
|
||||
@@ -230,7 +228,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
) {
|
||||
let workspace = &mut self.workspaces[workspace_idx];
|
||||
|
||||
workspace.add_window(None, window, activate, width, is_full_width);
|
||||
workspace.add_window(window, activate, width, is_full_width);
|
||||
|
||||
// After adding a new window, workspace becomes this output's own.
|
||||
workspace.original_output = OutputId::new(&self.output);
|
||||
@@ -275,7 +273,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
pub fn add_column(&mut self, mut workspace_idx: usize, column: Column<W>, activate: bool) {
|
||||
let workspace = &mut self.workspaces[workspace_idx];
|
||||
|
||||
workspace.add_column(None, column, activate, None);
|
||||
workspace.add_column(column, activate);
|
||||
|
||||
// After adding a new window, workspace becomes this output's own.
|
||||
workspace.original_output = OutputId::new(&self.output);
|
||||
@@ -304,7 +302,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
) {
|
||||
let workspace = &mut self.workspaces[workspace_idx];
|
||||
|
||||
workspace.add_tile(column_idx, tile, activate, width, is_full_width, None);
|
||||
workspace.add_tile(column_idx, tile, activate, width, is_full_width);
|
||||
|
||||
// After adding a new window, workspace becomes this output's own.
|
||||
workspace.original_output = OutputId::new(&self.output);
|
||||
@@ -392,12 +390,12 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn move_left(&mut self) {
|
||||
self.active_workspace().move_left();
|
||||
pub fn move_left(&mut self) -> bool {
|
||||
self.active_workspace().move_left()
|
||||
}
|
||||
|
||||
pub fn move_right(&mut self) {
|
||||
self.active_workspace().move_right();
|
||||
pub fn move_right(&mut self) -> bool {
|
||||
self.active_workspace().move_right()
|
||||
}
|
||||
|
||||
pub fn move_column_to_first(&mut self) {
|
||||
@@ -417,40 +415,23 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
pub fn move_down_or_to_workspace_down(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if workspace.columns.is_empty() {
|
||||
return;
|
||||
}
|
||||
let column = &mut workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx == new_idx {
|
||||
if !self.active_workspace().move_down() {
|
||||
self.move_to_workspace_down();
|
||||
} else {
|
||||
workspace.move_down();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_up_or_to_workspace_up(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if workspace.columns.is_empty() {
|
||||
return;
|
||||
}
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx == new_idx {
|
||||
if !self.active_workspace().move_up() {
|
||||
self.move_to_workspace_up();
|
||||
} else {
|
||||
workspace.move_up();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_left(&mut self) {
|
||||
self.active_workspace().focus_left();
|
||||
pub fn focus_left(&mut self) -> bool {
|
||||
self.active_workspace().focus_left()
|
||||
}
|
||||
|
||||
pub fn focus_right(&mut self) {
|
||||
self.active_workspace().focus_right();
|
||||
pub fn focus_right(&mut self) -> bool {
|
||||
self.active_workspace().focus_right()
|
||||
}
|
||||
|
||||
pub fn focus_column_first(&mut self) {
|
||||
@@ -469,98 +450,39 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.active_workspace().focus_column_left_or_last();
|
||||
}
|
||||
|
||||
pub fn focus_down(&mut self) {
|
||||
self.active_workspace().focus_down();
|
||||
pub fn focus_down(&mut self) -> bool {
|
||||
self.active_workspace().focus_down()
|
||||
}
|
||||
|
||||
pub fn focus_up(&mut self) {
|
||||
self.active_workspace().focus_up();
|
||||
pub fn focus_up(&mut self) -> bool {
|
||||
self.active_workspace().focus_up()
|
||||
}
|
||||
|
||||
pub fn focus_down_or_left(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if !workspace.columns.is_empty() {
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_left();
|
||||
} else {
|
||||
workspace.focus_down();
|
||||
}
|
||||
}
|
||||
self.active_workspace().focus_down_or_left();
|
||||
}
|
||||
|
||||
pub fn focus_down_or_right(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if !workspace.columns.is_empty() {
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_right();
|
||||
} else {
|
||||
workspace.focus_down();
|
||||
}
|
||||
}
|
||||
self.active_workspace().focus_down_or_right();
|
||||
}
|
||||
|
||||
pub fn focus_up_or_left(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if !workspace.columns.is_empty() {
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_left();
|
||||
} else {
|
||||
workspace.focus_up();
|
||||
}
|
||||
}
|
||||
self.active_workspace().focus_up_or_left();
|
||||
}
|
||||
|
||||
pub fn focus_up_or_right(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if workspace.columns.is_empty() {
|
||||
self.switch_workspace_up();
|
||||
} else {
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_right();
|
||||
} else {
|
||||
workspace.focus_up();
|
||||
}
|
||||
}
|
||||
self.active_workspace().focus_up_or_right();
|
||||
}
|
||||
|
||||
pub fn focus_window_or_workspace_down(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if workspace.columns.is_empty() {
|
||||
if !self.active_workspace().focus_down() {
|
||||
self.switch_workspace_down();
|
||||
} else {
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx == new_idx {
|
||||
self.switch_workspace_down();
|
||||
} else {
|
||||
workspace.focus_down();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_window_or_workspace_up(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if workspace.columns.is_empty() {
|
||||
if !self.active_workspace().focus_up() {
|
||||
self.switch_workspace_up();
|
||||
} else {
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx == new_idx {
|
||||
self.switch_workspace_up();
|
||||
} else {
|
||||
workspace.focus_up();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,17 +495,9 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||
if workspace.columns.is_empty() {
|
||||
let Some(removed) = workspace.remove_active_tile(Transaction::new()) else {
|
||||
return;
|
||||
}
|
||||
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let removed = workspace.remove_tile_by_idx(
|
||||
workspace.active_column_idx,
|
||||
column.active_tile_idx,
|
||||
Transaction::new(),
|
||||
None,
|
||||
);
|
||||
};
|
||||
|
||||
self.add_window(
|
||||
new_idx,
|
||||
@@ -603,17 +517,9 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||
if workspace.columns.is_empty() {
|
||||
let Some(removed) = workspace.remove_active_tile(Transaction::new()) else {
|
||||
return;
|
||||
}
|
||||
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let removed = workspace.remove_tile_by_idx(
|
||||
workspace.active_column_idx,
|
||||
column.active_tile_idx,
|
||||
Transaction::new(),
|
||||
None,
|
||||
);
|
||||
};
|
||||
|
||||
self.add_window(
|
||||
new_idx,
|
||||
@@ -625,30 +531,13 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) {
|
||||
let (source_workspace_idx, col_idx, tile_idx) = if let Some(window) = window {
|
||||
let source_workspace_idx = if let Some(window) = window {
|
||||
self.workspaces
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(ws_idx, ws)| {
|
||||
ws.columns.iter().enumerate().find_map(|(col_idx, col)| {
|
||||
col.tiles
|
||||
.iter()
|
||||
.position(|tile| tile.window().id() == window)
|
||||
.map(|tile_idx| (ws_idx, col_idx, tile_idx))
|
||||
})
|
||||
})
|
||||
.position(|ws| ws.has_window(window))
|
||||
.unwrap()
|
||||
} else {
|
||||
let ws_idx = self.active_workspace_idx;
|
||||
|
||||
let ws = &self.workspaces[ws_idx];
|
||||
if ws.columns.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let col_idx = ws.active_column_idx;
|
||||
let tile_idx = ws.columns[col_idx].active_tile_idx;
|
||||
(ws_idx, col_idx, tile_idx)
|
||||
self.active_workspace_idx
|
||||
};
|
||||
|
||||
let new_idx = min(idx, self.workspaces.len() - 1);
|
||||
@@ -656,13 +545,19 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
return;
|
||||
}
|
||||
|
||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||
let column = &workspace.columns[col_idx];
|
||||
let activate = source_workspace_idx == self.active_workspace_idx
|
||||
&& col_idx == workspace.active_column_idx
|
||||
&& tile_idx == column.active_tile_idx;
|
||||
let activate = window.map_or(true, |win| {
|
||||
self.active_window().map(|win| win.id()) == Some(win)
|
||||
});
|
||||
|
||||
let removed = workspace.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None);
|
||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||
let transaction = Transaction::new();
|
||||
let removed = if let Some(window) = window {
|
||||
workspace.remove_tile(window, transaction)
|
||||
} else if let Some(removed) = workspace.remove_active_tile(transaction) {
|
||||
removed
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.add_window(
|
||||
new_idx,
|
||||
@@ -686,11 +581,10 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||
if workspace.columns.is_empty() {
|
||||
let Some(column) = workspace.remove_active_column() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let column = workspace.remove_column_by_idx(workspace.active_column_idx, None);
|
||||
self.add_column(new_idx, column, true);
|
||||
}
|
||||
|
||||
@@ -703,11 +597,10 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||
if workspace.columns.is_empty() {
|
||||
let Some(column) = workspace.remove_active_column() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let column = workspace.remove_column_by_idx(workspace.active_column_idx, None);
|
||||
self.add_column(new_idx, column, true);
|
||||
}
|
||||
|
||||
@@ -720,11 +613,10 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||
if workspace.columns.is_empty() {
|
||||
let Some(column) = workspace.remove_active_column() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let column = workspace.remove_column_by_idx(workspace.active_column_idx, None);
|
||||
self.add_column(new_idx, column, true);
|
||||
}
|
||||
|
||||
@@ -778,14 +670,12 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.active_workspace().center_column();
|
||||
}
|
||||
|
||||
pub fn focus(&self) -> Option<&W> {
|
||||
let workspace = &self.workspaces[self.active_workspace_idx];
|
||||
if !workspace.has_windows() {
|
||||
return None;
|
||||
}
|
||||
pub fn active_window(&self) -> Option<&W> {
|
||||
self.active_workspace_ref().active_window()
|
||||
}
|
||||
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
Some(column.tiles[column.active_tile_idx].window())
|
||||
pub fn is_active_fullscreen(&self) -> bool {
|
||||
self.active_workspace_ref().is_active_fullscreen()
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self) {
|
||||
@@ -861,17 +751,6 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
ws.update_config(options.clone());
|
||||
}
|
||||
|
||||
if self.options.struts != options.struts {
|
||||
let scale = self.output.current_scale();
|
||||
let transform = self.output.current_transform();
|
||||
let view_size = output_size(&self.output);
|
||||
let working_area = compute_working_area(&self.output, options.struts);
|
||||
|
||||
for ws in &mut self.workspaces {
|
||||
ws.set_view_size(scale, transform, view_size, working_area);
|
||||
}
|
||||
}
|
||||
|
||||
self.options = options;
|
||||
}
|
||||
|
||||
@@ -1070,7 +949,6 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.workspaces_with_render_positions()
|
||||
.flat_map(move |(ws, offset)| {
|
||||
ws.render_elements(renderer, target)
|
||||
.into_iter()
|
||||
.filter_map(move |elem| {
|
||||
CropRenderElement::from_element(elem, scale, crop_bounds)
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+365
-3671
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -2,7 +2,7 @@ use niri_config::{BlockOutFrom, BorderRule, CornerRadius, Match, WindowRule};
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::wayland::shell::xdg::{ToplevelSurface, XdgToplevelSurfaceRoleAttributes};
|
||||
|
||||
use crate::layout::workspace::ColumnWidth;
|
||||
use crate::layout::scrolling::ColumnWidth;
|
||||
use crate::utils::with_toplevel_role;
|
||||
|
||||
pub mod mapped;
|
||||
|
||||
@@ -4,7 +4,7 @@ use smithay::wayland::shell::xdg::ToplevelSurface;
|
||||
use smithay::wayland::xdg_activation::XdgActivationTokenData;
|
||||
|
||||
use super::ResolvedWindowRules;
|
||||
use crate::layout::workspace::ColumnWidth;
|
||||
use crate::layout::scrolling::ColumnWidth;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Unmapped {
|
||||
|
||||
Reference in New Issue
Block a user