diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 21b34e90..56dd4979 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -536,17 +536,28 @@ still takes the windows that open into containers, and they are tiled when it is `LayoutRatios` and `ScrollingLayoutColumns` set the focused workspace's [`LayoutOptions`] ([`Workspace::layout_options`]): the share of the work area its first columns and rows take, and the number of columns of the scrolling layout in view. The layout calculates the tiles from -them, and from the tiles it calculated last ([`Workspace::latest_layout`]), which the scrolling -layout scrolls from. +them, and from the tiles it calculated last ([`Workspace::latest_layout`]). -The scrolling layout is a strip of columns that runs past the edges of the work area. Its tiles -are the only ones [`Workspace::update`] does not clamp to the work area, so the windows of the -columns out of view are off the screen; a window whose tile is out of view while part of it is -in view is centered in the work area first. A `FocusChange` tiles a scrolling workspace after the -focused container has changed, which scrolls the strip to it. KWin keeps part of a window it -resizes on an output, so the bridge asks again for the position of a window KWin pulled back -within a second of the command, three times at most ([`bridge.js`]'s `settle`), unless the window -is maximized meanwhile. +The scrolling layout is a strip of columns that runs past the edges of the work area, and a column +can hold several windows ([the scrolling layout](../usage/scrolling-layout.md), and +[scrolling-next.md](scrolling-next.md) for where it comes from). Each workspace keeps a +`ScrollingSpace` of `komorebi-scrolling` ([`Workspace::scrolling`]), made on the first layout +and keyed by the id of each container; [`Workspace::update`] makes it match the containers and the +focus, and asks it where every container goes ([`Workspace::layout_scrolling`]). Its tiles are the +only ones [`Workspace::update`] does not clamp to the work area. `consume`, `expel`, +`toggle-column-stack`, `scrolling-layout-full-width`, `center-focus`, `expand` and `pan` act on +the strip, and `focus`, `move`, `resize-*` and `retile` go through it in this layout; a container +that leaves a workspace leaves what its column was given for the next one to find +(`note_scrolling_departure`). A window that is out of view rests at the edge it left by with five +pixels of it on the screen and keeps its size (`parked`), or is hidden ([`Window::hide`]) where +that edge has another monitor next to it (`rest`), because KWin shows a window on every monitor it +overlaps. A `FocusChange` tiles a scrolling workspace after the focused container has changed, +which scrolls the strip to it. A window that did not get as narrow as its column is widens the +column, at the first layout 400 ms after the windows were sent (`adopt_actual_widths`: the frame +comes back from KWin a moment after the command). A drag of an edge keeps the strip with the rest +of what it started from ([`LiveResize`]). KWin keeps part of a window it resizes on an output, so +the bridge asks again for the position of a window KWin pulled back within a second of the +command, three times at most ([`bridge.js`]'s `settle`), unless the window is maximized meanwhile. `Promote` moves the focused container to index 0, the largest tile of every layout but the grid, through `LockableSequence`, so the containers before it move one index on diff --git a/docs/dev/COMPARE.md b/docs/dev/COMPARE.md index 12da6334..a78478f1 100644 --- a/docs/dev/COMPARE.md +++ b/docs/dev/COMPARE.md @@ -53,8 +53,9 @@ Terms used below: - **A workspace** is komorebi's name for one of the desktops of a monitor, in all three programs. - **A parking desktop** is a KWin virtual desktop that nobody looks at, which kfde moves windows to in order to hide them. -- **The strip** is the scrolling layout of kfm, in the crate `komorebi-scrolling`. It is not the - `Scrolling` layout of `komorebi-layouts`. +- **The strip** is the scrolling layout of kfm, in the crate `komorebi-scrolling`. Since upstream's + `next` branch it is also what the `Scrolling` layout of `komorebi-layouts` is laid out with, in + kfde too ([scrolling-next.md](scrolling-next.md)). ## What is the same @@ -84,8 +85,9 @@ Compared file by file with the crate of kfw, kfm: 0 of 13 source files differ fr The layouts of `komorebi-layouts` are calculated by the same code in the three programs, so the same windows in the same order get the same rectangles, apart from what the window manager takes off for -the work area, the padding and the borders. The exception is the `Scrolling` layout of kfm, which kfm -lays out with the strip (see the section on configuration). What differs between the programs is +the work area, the padding and the borders. The exception is the `Scrolling` layout, which is laid +out with the strip of `komorebi-scrolling` in kfm and in kfde, and not in kfw before its `next` +branch (see the section on configuration). What differs between the programs is what the system does with the rectangles: the next sections. ### One configuration file, largely portable @@ -410,11 +412,13 @@ The nested structs have the same keys, except for these: | `AnimationsConfig` | 5 | 4 | 4 | `ghost_movement` | - | -There is one more difference that is not a key. kfm has a scrolling strip, with columns that -hold several windows, a `scrolling` key and the commands in the "kfm scrolling strip" group above. -kfde has the `Scrolling` layout of `komorebi-layouts`, tuned with `layout_options` and the command -`scrolling-layout-columns`, which kfw and kfm have too. A `scrolling` key in a file written for kfm -is skipped by kfde. +There is one more difference that is not a key. kfm has a `scrolling` key. kfde has the `Scrolling` +layout of `komorebi-layouts`, which is laid out with the same strip as kfm's, with columns that hold +several windows and the commands `consume`, `expel`, `toggle-column-stack`, +`scrolling-layout-full-width`, `center-focus`, `expand` and `pan`, and is tuned with +`layout_options` and the command `scrolling-layout-columns`, which kfw and kfm have too. A +`scrolling` key in a file written for kfm is skipped by kfde. The generated tables above were made +before kfde took these commands: `scripts/compare-gen.py --update` makes them again. ## Events, and what drives them @@ -793,7 +797,6 @@ From the tables above, and from what has no row in them: - The Win32 workarounds of the "Win32 quirks" group, the AutoHotkey and PowerShell configurations, the named-pipe subscribers, and whkd as a separate program. - The license check and the splash screen of the macOS and Windows builds. -- kfm's scrolling strip: columns that hold several windows, and its commands. - The tab and title bar commands (`remove-title-bar`, `toggle-title-bars`). ## What kfde has that kfw and kfm do not diff --git a/docs/dev/DESIGN.md b/docs/dev/DESIGN.md index 256accb5..aca0daf0 100644 --- a/docs/dev/DESIGN.md +++ b/docs/dev/DESIGN.md @@ -219,9 +219,10 @@ to the module that breaks how komorebi is installed or started fails the VM suit KWin moves a window anywhere it is asked to, but when it resizes a window it keeps 100 pixels of it on an output, and it does so once the client has drawn at the new size, which is after the -bridge has set the frame. The scrolling layout moves the windows of the columns out of view off -the screen and resizes them in the same command, and a terminal that rounds its size to its -cells is resized again by its client, so KWin pulls both back onto the screen. By then, setting +bridge has set the frame. The scrolling layout moves the windows of the columns out of view to the +edge of the work area, where a few pixels of them are on the screen, and resizes the ones that +change width in the same command, and a terminal that rounds its size to its cells is resized +again by its client, so KWin pulls both back onto the screen. By then, setting the position alone is a plain move, which KWin honors. The bridge remembers where it was asked to put a window for a second, and sets the position again when the window's frame changes to another one in that time ([PLATFORM.md](PLATFORM.md#how-clients-answer-a-frame)). Three tries diff --git a/docs/dev/PLATFORM.md b/docs/dev/PLATFORM.md index 9e976d35..cd52d97c 100644 --- a/docs/dev/PLATFORM.md +++ b/docs/dev/PLATFORM.md @@ -571,7 +571,7 @@ covers part of its neighbor's. KWin moves a window anywhere it is asked to, off the screen too, but when it *resizes* a window it keeps 100 pixels of it on an output, and it does that once the client has drawn at the new size. A window that is moved off the screen and resized at once, as the scrolling layout does -with the columns out of view, is pulled back onto the screen a moment later; so is one whose +with the columns out of view that change width, is pulled back onto the screen a moment later; so is one whose client rounds the size, as `foot` does. The bridge therefore remembers the position it was asked for, for a second, and when the window's frame changes to another position in that time, asks for the position again, which is a plain move. It tries three times at most, so a window that @@ -938,4 +938,6 @@ they work or that they do not. - The scrolling layout on a monitor that has another beside it. A strip spills onto the neighbor when that monitor shows the same workspace number, and a window whose middle is on a monitor that shows another desktop is not drawn. This is read from KWin's sources, not - checked with pixels. + checked with pixels. The layout no longer leaves a window there: one that would rest or + overhang past an edge with another monitor next to it is hidden, or cut down to its own work + area ([scrolling-next.md](scrolling-next.md)), which only the fake desktop has run. diff --git a/docs/dev/scrolling-next.md b/docs/dev/scrolling-next.md index 5849676f..18d64dd8 100644 --- a/docs/dev/scrolling-next.md +++ b/docs/dev/scrolling-next.md @@ -44,3 +44,20 @@ is, whether it is hidden, how wide it really is) is a call to the backend in kfd enough to follow a scroll without a flash. It is what stacks do. - The 16 px tolerance of `2dff53095` against the clients of `client-geometry` (`foot` rounds to cells, which is below it). + +## What is tested, and where + +- `komorebi/src/scrolling.rs`: the glue against a workspace with no desktop (widths, consume, expel, + moving a column, stacking and spreading out with the windows that are hidden, expanding, panning, + where a window rests, which monitors are the others, what a window that will not get narrow + says). The model itself has its tests in upstream's `komorebi-scrolling`. +- `komorebi/tests/scrolling.rs` and `layout_options.rs`: the commands through + `WindowManager::process_command` against the fake desktop, a strip next to another monitor, and a + drag of the left edge of a column. +- `komorebic/tests/cli.rs`: each new subcommand sends its message. +- `nix/tests/vm/lib/komorebi_vm/platforms/kde/extras/scrolling.py`, run by `vm-scrolling`: where KWin + keeps a window at the edge and the commands of the columns, in a real session. **Not run**: the VM + suites are verified later. What to look at first is that KWin leaves a window with a right edge + 5 pixels inside the work area where it was put (the bridge's `settle` is what is meant to see to it), + and that `toggle-column-stack` parks and restores the right windows. + diff --git a/docs/dev/suite-ledger.md b/docs/dev/suite-ledger.md index 30df6039..ec10b30a 100644 --- a/docs/dev/suite-ledger.md +++ b/docs/dev/suite-ledger.md @@ -182,9 +182,14 @@ per-output desktops. ## scrolling The shared `scrolling` scenario (run by `core-e2e`) puts each window in view in turn. This suite is -about the windows that are not in view: where KWin keeps the columns that scroll off the screen, -and that the strip follows the focus. All of it is KWin's; it is the extra `scrolling.run`, with -no check dropped. +about the windows that are not in view: where KWin keeps the columns that scroll off the screen +(at an edge, with a few pixels of them on it), and that the strip follows the focus. It also runs +the commands of the columns of several windows (`pan`, `consume`, `toggle-column-stack`, `expel`, +`scrolling-layout-full-width`), which are kfde's and not in the shared scenario because komorebi +for Windows has them only on its `next` branch. All of it is KWin's; it is the extra +`scrolling.run`. The check that a window keeps the distance of a column from the one in view, +which the layout does not keep any more (a window rests at the edge), was replaced by two that it +rests there and keeps its width. ## stacks diff --git a/docs/index.md b/docs/index.md index cf7c5d38..574136b9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,6 +44,7 @@ a NixOS configuration and sample key bindings in the three formats. [Stacking windows](usage/stacking-windows.md) how to put windows on one tile, have the others wait out of sight and bring one of them to the front, [Layouts](usage/layouts.md) how to change the arrangement of the tiles, +[The scrolling layout](usage/scrolling-layout.md) how its columns work, [Resizing windows](usage/resizing-windows.md) how to change the size of one (and [Resizing a tile by dragging its edge](drag-resize.md) what a drag with the pointer does), [Floating windows](usage/floating-windows.md) how to take a window out of the layout and work with diff --git a/docs/usage/layouts.md b/docs/usage/layouts.md index 52dc5140..4a855442 100644 --- a/docs/usage/layouts.md +++ b/docs/usage/layouts.md @@ -58,22 +58,18 @@ The ratios stay with the workspace when its layout changes. Without `--columns` ## The scrolling layout -The scrolling layout puts every container in a column of its own, the columns side by side in a -strip that runs past the edges of the monitor. The windows of the columns out of view are off -the screen, and KWin leaves them there. When a window of the workspace gets the focus, the strip -scrolls so that its column is in view, so `komorebic focus left` and `focus right` walk along -it. - -[`komorebic scrolling-layout-columns`](../cli/scrolling-layout-columns.md) sets how many columns -are in view at once on the focused workspace: +The scrolling layout is a row of columns that runs past the edges of the monitor, and the screen +is a view onto a part of it. A column can hold several windows, and the strip scrolls to follow the +focus. [`komorebic scrolling-layout-columns`](../cli/scrolling-layout-columns.md) sets how many +columns are in view at once on the focused workspace: ```sh komorebic change-layout scrolling komorebic scrolling-layout-columns 2 ``` -Off the screen to the right of one monitor is on the next monitor over, if there is one, so the -columns out of view can show on it. +[The scrolling layout](scrolling-layout.md) says how to centre the focused column, put windows in +the same column, resize columns and windows, and move the view along the strip. ## Flipping the layout diff --git a/docs/usage/scrolling-layout.md b/docs/usage/scrolling-layout.md new file mode 100644 index 00000000..3d660a51 --- /dev/null +++ b/docs/usage/scrolling-layout.md @@ -0,0 +1,191 @@ +# The Scrolling Layout + +In the Scrolling layout every window is a column in a row that is as long as it needs to be, and the screen is a +viewport onto a part of it. A new window is added to the right of the focused one. Nothing is squeezed to make it fit: +the row gets longer, and the screen scrolls to follow your focus. + +```json +{ + "monitors": [ + { + "workspaces": [ + { + "name": "I", + "layout": "Scrolling", + "layout_options": { + "scrolling": { + "columns": 3, + "center_focused_column": "Never" + } + } + } + ] + } + ] +} +``` + +## Column widths + +`columns` says how many columns fit across the screen, which is to say that a new column is a `columns`th of the screen +wide. It is `3` unless you say otherwise. It does not say how many windows there are: a lone window is a third of the +screen wide, and is not stretched to fill it. + +- Opening or closing a window never changes the width of another window. +- A column that you have resized keeps its width as windows come and go. +- Columns that you have not resized follow `columns`. Changing it with + [`komorebic scrolling-layout-columns`](../cli/scrolling-layout-columns.md), or crossing the threshold of a + `layout_options_rules` entry, lays them out again. + +## Centring the focused column + +`center_focused_column` says when the focused column is put in the middle of the screen. It is one of: + +- `"Never"`, the default, which scrolls only as far as it has to. +- `"Always"`, which keeps the focused column in the middle of the screen. +- `"OnOverflow"`, which centres it when it does not fit on the screen together with the column that focus came from. + +`true` and `false` are also accepted, as they were before there were three choices: `true` is `"Always"` and `false` is +`"Never"`. Like `columns`, it is set for a workspace, or for a `layout_options_rules` entry. + +A lone window is a third of the screen wide, at the left edge. To have it fill the screen, or to take it out of the +layout for a while, use [`komorebic toggle-monocle`](../cli/toggle-monocle.md). + +The layout options of a workspace can also be given for a number of windows, as `layout_options_rules`, which is +where `columns` and `center_focused_column` change when the workspace has that many windows. + +## Columns of several windows + +A column can hold more than one window, one above the other, sharing the height of the screen. A new window is always a +column of its own, to the right of the column that has focus. To put windows in the same column: + +- [`komorebic consume right`](../cli/consume.md) takes the first window of the column on the right and puts it at the + bottom of the focused column. +- [`komorebic consume left`](../cli/consume.md) takes the last window of the column on the left and puts it at the top of + the focused column. +- [`komorebic expel`](../cli/expel.md) takes the focused window out of its column and puts it in a column of its own on + the right. + +- [`komorebic toggle-column-stack`](../cli/toggle-column-stack.md) stacks the windows of the focused column, or spreads + the focused stack out into a column with a window in each slot. A [stack](../cli/stack.md) is one slot that shows one + window at a time, and it takes the height of the column. The window that has focus keeps it. The column keeps its width, + but the heights that its windows were given are not remembered for when you toggle back. + +A stack is the stack that you have in every layout, so [`cycle-stack`](../cli/cycle-stack.md), +[`focus-stack-window`](../cli/focus-stack-window.md) and [`stack`](../cli/stack.md) work on it as they always do. +[`unstack`](../cli/unstack.md) takes the window that has focus off the stack and puts it in a column of its own to the +right of the one that it was in. + +These only do anything in the Scrolling layout, which is the only one with columns of several windows for now. + +The focused window keeps focus, and stays where it is on screen, when you do any of these. + +Focus and move commands know about columns: + +- [`focus left` and `focus right`](../cli/focus.md) go to the neighbouring column, and to the window in it that had focus + last. `focus up` and `focus down` go to the windows of the focused column, and stop at the ends, where they carry on + to another monitor as they always did. +- [`move left` and `move right`](../cli/move.md) move the whole focused column. `move up` and `move down` move the + window up or down in its column. +- A column keeps its width, and a [stack](../cli/stack.md) is still one tile showing one of its windows, as in any layout. + +A column that you resized, or made the full width of the screen, keeps that as windows are added to it or taken out. +A window that you move to another workspace or monitor is a column of its own there, and keeps the width of the column +that it was in. + +## Heights + +The windows of a column share its height equally. To change that: + +- [`komorebic resize-edge up`, `down`](../cli/resize-edge.md) and the [axis](../cli/resize-axis.md) commands make the + focused window taller or shorter, by half of the [resize delta](../cli/resize-delta.md) for each edge. The other windows + in the column share what it leaves. + +Giving a window a height gives the others in its column an equal share of what it leaves, so there is always something +to take up the difference. No window is made shorter than 100 pixels, and what one window is given cannot +leave the others with less. A window that is alone in its column is as tall as the screen, and a window that is taken +out of its column, or put in another, starts with an equal share. + +## Full width and centring + +- [`komorebic scrolling-layout-full-width`](../cli/scrolling-layout-full-width.md) makes the focused column as wide as the + screen, or puts it back to the width that it had. +- [`komorebic center-focus`](../cli/center-focus.md) scrolls the screen so that the focused column is in the middle of + it, once. `center_focused_column` does it every time that focus moves instead. +- [`komorebic expand`](../cli/expand.md) makes the focused column as wide as it can be without moving another column out + of view: it takes up the room that the columns on the screen leave. It does nothing when there is none, and in the + other layouts. With `center_focused_column` set to `"Always"` it makes the column the full width of the screen, or + puts it back, as `scrolling-layout-full-width` does. + +A column that you make the full width of the screen takes the gaps into account, so that it fills the screen exactly. +Nothing else moves to make room for a column that gets wider: the focused column keeps its left edge, and the screen +scrolls only if the column no longer fits. + +## Scrolling + +The row scrolls only when it has to. + +- Focusing a window that is already on screen does not move anything. +- Focusing one that is not scrolls just far enough to bring it in, and no further. +- A window that is wider than the screen starts at the left edge. +- The focused window does not move when windows to its left open, close or change width. + +When you close a window, focus goes back to the window that it was opened next to if you have not moved focus since, +and the screen scrolls back to where it was. If you have, focus goes to the window that takes the closed window's place +on the right, or to the one before it if there is none. A window that is closed without having focus does not take focus +with it. + +## Panning + +[`komorebic pan`](../cli/pan.md) moves the view along the row without moving focus first. `pan left` brings what is to +the left of the screen into view, and `pan right` what is to the right, by a column. The row comes to rest with a +column against an edge of the screen, and focus goes to the nearest column on the screen if the focused one has gone off +it. + +``` +alt + wheelup : komorebic pan left +alt + wheeldown : komorebic pan right +``` + +It does nothing in the other layouts. Panning with the fingers on a touchpad (`pan --stream`) is for Windows only. + +## Resizing + +[`komorebic resize-edge`](../cli/resize-edge.md) and [`komorebic resize-axis`](../cli/resize-axis.md) change the width of +the focused column, by half of the [resize delta](../cli/resize-delta.md) for each edge, so resizing the horizontal axis +changes the width by the whole delta. Dragging an edge with the mouse does the same. Resizing the top or bottom edge +changes the [height](#heights) of a window that shares its column with others. A window that is alone in its column is +as tall as the screen, so resizing its height does nothing. + +[`komorebic retile`](../cli/retile.md) puts every column back to the width that +[`scrolling-layout-columns`](../cli/scrolling-layout-columns.md) gives, and every window in a column of several back to +an equal share of its height, on the focused workspace of each monitor. The focused column stays where it is on screen, +unless it has to scroll to stay in view. + +## Windows with a minimum width + +Many apps will not make a window narrower than some width, whatever they are asked. KWin reports the size that a window +has drawn at a moment after komorebi asks for it, so komorebi asks a window how wide it is at the first layout after +the windows have had 400 milliseconds to settle, and if it did not get as narrow as its column (by more than 16 +pixels, which is what a terminal's grid of cells and the rounding of a scale are), the column becomes as wide as the +window needs. The columns after it move along to make room, and nothing overlaps. A column never gets narrower again +because of this, and never gets wider than the screen. [`retile`](../cli/retile.md) forgets what was learned. + +## Windows that are out of view + +A window that scrolls out of view comes to rest at the edge of the work area that it left by, with 5 pixels of it still +on screen. It keeps its size, so nothing has to be resized to bring it back, which is what KWin pulls a window back +onto the screen for. A window that is partly in view keeps its size too, and overhangs the edge of the work area. + +KWin shows a window on whichever monitor it overlaps, so with more than one monitor this needs care. If another monitor +is next to the edge that a window would rest at or overhang, a window that has scrolled out of view is hidden instead +of parked (it goes where the back windows of a [stack](stacking-windows.md) go, and comes back when the strip scrolls +to it), and a window that is partly in view is made narrower to fit its own work area. Each workspace on each monitor has +a strip of its own, and focus and move commands carry on to the next monitor when they reach the end of one. + +## Limits + +- Switching to another layout and back keeps the widths, but a column whose windows were moved past each other in the + other layout is made again from the order that they are in. +- Windows resting at the edges have borders too, so you may see a thin line of one at the edge of the screen. +- There are no animations of the strip: the windows are put where they go at once, or by KWin's own effects.