Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalized master-stack layout engine #634

Closed
dalyIsaac opened this issue Nov 30, 2023 · 4 comments · Fixed by #737
Closed

Generalized master-stack layout engine #634

dalyIsaac opened this issue Nov 30, 2023 · 4 comments · Fixed by #737
Labels
plugin idea An idea for a new plugin

Comments

@dalyIsaac
Copy link
Owner

dalyIsaac commented Nov 30, 2023

Create a generalized master-stack layout engine, defined in terms of N columns, where all but one column has a maximum number of rows.

For example:

  • 2 * 1 * X would be a 3-column layout, where:
    • the first column has two rows
    • the second has one row
    • all remaining windows are stacked in the third row.
  • 1 * X for a classic master stack layout.
  • 2 * X for a master stack with two master windows.
@dalyIsaac dalyIsaac added this to Whim Nov 30, 2023
@dalyIsaac dalyIsaac converted this from a draft issue Nov 30, 2023
@dalyIsaac dalyIsaac added enhancement New feature or request plugin idea An idea for a new plugin and removed enhancement New feature or request labels Nov 30, 2023
@urob
Copy link
Contributor

urob commented Dec 4, 2023

A few suggestions for commands to be exposed with this plugin.

Preliminaries

Windows order

Some of the commands below require a order of windows. I suggest ordering windows column-wise. This seems a reasonable extension of what most people would expect on a traditional 1 * X stack. That is,

(c1, r1) -> (c1, r2) -> ... -> (c2, r1) -> (c2, r2) -> ... 

Master column

While the core of this layout engine does not require defining a master column, I think that it makes sense to expose some commands that use the concept of a master column (see below).

On the one hand, this is what people expect from a master-stack layout. On the other hand, this simplifies the logic of exposing certain functionalities.1.

To keep things simple, I suggest using a master_column instead of a master_area b/c it generalizes nicely when there are more than 1 master-rows without over-complicating things. Which column is considered the master_column would be defined as part of the configuration when creating the layoutEngine in the user config.

Stack column and normal columns

Just to fix some terminology, let me use stack column to refer to the catch-all column. (the one denoted by N in the 2 * 2 * N notation from the initial plugin idea draft). I will refer to all other non-master columns as "normal" columns.

Layout specification

To sum up, a complete layout specification would consist of

  1. a column layout, specifying an ordering over:
    • 1 master column
    • 1 stack column
    • an arbitrary number of "normal" columns
  2. the number of max-rows for each non-stack column

One could represent this using a single string list, which indicates the column type in position 1 (M, S or N), and adds the max-rows after a dash for all non-stack columns. E.g.2

layout = ('S', 'M-2', 'N-2')   // stack-column | 2-row master column | 2-row normal column

Optional config options that would be nice to be able to configure as the initial (minimum) width of each column.3

Ideally, one could use the layout engine to create multiple master-stack instances with different specifications.

Commands

  • focus_[next|previous]_window: cycles the focus using the ordering from above
  • swap_with_[next|previous]_window: swaps window with next/previous window.
  • promote_focus: focus the first row of the master column
  • promote_window: swaps window with the window in the first row of the master column and focus that window
  • [increment|decrement]_master_width: resize width of master column. If there are columns to either side of the master column, then this would equally shift the edges on both sides.
  • [increment|decrement]_current_column_width: same as the *_master_width commands, but it operates on the currently active column.

Open question

promote_window: swap or rotate?

I wasn't sure whether promoting a window to the first row of the master area should only swap the two affected windows, or whether it should trigger a rotation down the stack of all in-between windows?

I think the decision really comes down to where the old master window should go. Should it swap positions with the new master window, or should it move down the stack (e.g., with >1 master rows, it would move to row2 in the master column). It would be interesting to hear what other think about that.

Order in which positions get populated by new windows

This matters whenever the current window number is less than the number of max-rows in all non-stack columns. I suspect that preferences differ on this.

My suggestion would be to initially go with the following order:

  1. add windows to the master-column until full
  2. add windows in non-stack columns using the regular windows order
  3. add windows to the stack column

If there is demand, other orders of creation could be made available later.

Note: I was also thinking about just setting the windows order to the creation order here to simplify things. But I think that would go against what most people expect from what should happen when using the *_[next|previous]_window set of commands.

Interaction with other feature requests

Move_and_focus (#662)

Personally, I don't see much use of a promote_window command that doesn't focus the new master-window after. So I would just make that the default (i.e., technically promote_window really does "promote_window_and_focus").

For the swap_with_[next|previous]_window commands it would probably make sense to expose both swap_* and swap_and_focus_* versions.

Footnotes

  1. E.g., an alternative to the promote_* commands suggested below, would be to offer more flexible commands that allow to focus/move windows to any column/row. But I suspect that most users wouldn't need these more flexible command, making it less worthwhile to add the additional complexity.

  2. Obviously there are other ways of specifying the same information, including a nested list, this is just an example to fix ideas.

  3. The actual width would be equal to the minimum width if there are enough windows for all specified columns to be created. If there are fewer columns that specified (e.g., if there is only 1 window), then the width would be increased accordingly.

@gplusplus314
Copy link
Contributor

I have some thoughts.

Proposed MVP:

  • Put windows in a List data structure.
  • Given a List of windows, arrange them on the screen.

^^ That's it. Once that works, we can expand more easily, IMO.

Quick terminology/concepts that I think can simplify this:

  • An Area can be either a row or a column (example: rotating screen orientation, you probably want rows)
  • An Area can nest another Area (more on that later)
  • An Area is responsible for the layout/arrangement of a capacity of windows in the List structure and this can be configurable. For example, the Primary area (colloquially "master") would likely default to 1, whereas an Overflow area (colloquially "stack") would likely default to infinity (or some sane large number).
  • By convention, the Primary area is the one responsible for arranging the window with the lowest index in the List up to its configured capacity, which can be zero (perfectly valid). Each additional Area (non-Primary) continues the layout up to its configured capacity.

I believe the implementation gets vastly simplified if its thought about in these terms. This allows us to make no assumptions of people's screen resolutions, screen orientation, use cases, or layout preferences. This also generalizes well to other automatic tiling layouts such as Fibonacci, Dwindle, etc, by nesting Areas programmatically. We can provide sane defaults that most people would expect, such as a DWM-style 2-column.

As far as operations with this model, things like rotating and transposing become independent of being a row or a column. Areas could also be laid out independently, such as having the Primary as the middle column of a 3-column (that is, three-Area) arrangement.

The key is to use an ordered, linear collection (List or equivalent) and map an Area to a range on that list. As windows enter and exit the workspace, they would be either appended or prepended to the list (configurable) and then the MVP code described in the beginning of my rant would take over.

Another thing to consider is the social responsibility of the chosen terminology. GitHub now uses "main" as the default branch name, for example.

@dalyIsaac dalyIsaac moved this to In Progress in Whim Dec 14, 2023
@dalyIsaac
Copy link
Owner Author

dalyIsaac commented Dec 15, 2023

Ok. I was looking through this today to get a handle on it. This is my basic understanding of a layout engine which would handle most cases:

Probable MVP

  • List data structure
  • An Area is a row or a column
  • An Area can contain windows and child Areas
  • An Area is one of the following variants:
    • SliceArea (or primary/main), which has priority $P$ and max-count $N$
    • BaseArea with infinite max-count- there can be only one BaseArea
  • SliceArea priorities > BaseArea priorities
  • A window can be "swapped" or "rotated" to the nearest SliceArea, or the next SliceArea with a higher priority $P$
  • Defaults will be provided by the plugin to select from
  • Custom layouts can be created by:
SliceArea(
	direction: row,
	SliceArea(leaderPriority: 0, max: 1),
	SliceArea(leaderPriority: 1, max: 3, direction: column),
	BaseArea(
		stackPriority: 0,
		SliceArea(2),
		SliceArea(leaderPriority: 2, direction: column),
		LeafArea(Infinite)
	)
)

Follow-on tickets

  • A child Area can take some % of the parent Area
  • Reverse direction (reverse column, reverse row)
  • Updating of the data structure during runtime via commands
  • Window in direction operations

@dalyIsaac
Copy link
Owner Author

dalyIsaac commented Dec 21, 2023

Progress

  • Create project
  • Flesh out IArea models
  • Implement AddWindow and RemoveWindow
  • Implement DoLayout and MoveWindowToPoint
  • CreatePrimaryStackLayout
  • CreateMultiColumnLayout
  • FocusWindowInDirection
  • SwapWindowInDirection
  • Set insertion type command
  • Promote/demote window command
  • Promote/demote focus command
  • Test custom layouts
    • Nested ParentArea
    • Column ParentArea
    • OverflowArea not being on the right
    • OverflowArea being omitted
  • Switch from uint to int?
  • TODOs
  • Add to release.yml
  • Pull PerformCustomAction into a separate PR Let ILayoutEngines perform custom actions  #736
  • Handle multiple overflow areas
  • Handle OverflowArea being on the left
  • Fix ArgumentOutOfRangeException
  • Documentation
  • Tests
  • Update csx
  • Handle multiple overflows when there is a single window
  • Review

Follow-up issues

dalyIsaac added a commit that referenced this issue Dec 22, 2023
This PR adds the ability for layout engines to perform non-standard actions, and still have their resulting layouts be saved to `Workspace` instances. This was needed for #634, where the `SliceLayoutPlugin` tries to perform operations on the `SliceLayoutEngine`, but was unable to save the resulting `ILayoutEngine` instance to the `Workspace`.

The `PerformCustomAction` method can be used by `ILayoutEngine` implementations to perform non-standard actions.
@dalyIsaac dalyIsaac linked a pull request Dec 23, 2023 that will close this issue
@dalyIsaac dalyIsaac moved this from In Progress to Review in Whim Dec 25, 2023
@github-project-automation github-project-automation bot moved this from Review to Done in Whim Dec 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin idea An idea for a new plugin
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

3 participants