pub struct Tiling {
nodes: Vec<Option<Node>>,
root: Option<usize>,
floors: Floors,
}Expand description
A dwindle tree.
Fields§
§nodes: Vec<Option<Node>>§root: Option<usize>§floors: FloorsEach window’s own floor, as the caller last handed them in. See
Self::set_floors.
Implementations§
Source§impl Tiling
impl Tiling
pub fn new() -> Self
Sourcepub fn set_floors(&mut self, floors: Floors)
pub fn set_floors(&mut self, floors: Floors)
Each window’s own floor, by id, for every call after this one.
Replaced whole, not merged: the caller hands in what every window says
now, and a window missing from it has no floor
(floor_tests::a_window_whose_floor_goes_away_hands_the_room_back). A
window that is not in the tree yet is read when it is inserted
(floor_tests::a_split_that_would_put_a_window_under_its_floor_is_refused).
Sourcepub fn cramped(&self, id: u64, rect: Rect) -> bool
pub fn cramped(&self, id: u64, rect: Rect) -> bool
Whether rect, a tile this tree laid out for window id, is smaller
than that window’s own floor on either side.
Its own floor and nothing else: a tile under Settings::minimum is
one the layout chose or allowed (#134), and a client inside it is not
refusing anything (floor_tests::a_sibling_gives_no_more_than_it_holds
has one of each). This is the window whose client will commit more than
it was given and be cut to its tile (#133).
Sourcefn leaf_need(&self, id: u64, axis: Axis, settings: Settings) -> Need
fn leaf_need(&self, id: u64, axis: Axis, settings: Settings) -> Need
What window id asks of the room along axis. See need_of.
Sourcefn need(&self, index: usize, axis: Axis, settings: Settings) -> Need
fn need(&self, index: usize, axis: Axis, settings: Settings) -> Need
What the subtree at index asks of the room along axis. See Need.
Cut along axis, its children share that room, so it needs both of
theirs and the gap – and once anything in it pulls, it needs every
window in it at what that window will give down to, since those are
what the pulling one takes its room from. Cut the other way, each child
has the whole extent, so it needs only the larger. Both are
floor_tests::a_floor_deep_in_a_subtree_takes_room_across_the_root.
The ratio a split is laid out at, in rect: its own, or the one that
gives a side under its floor its floor. See shared.
No floors at all is the ratio as it is, without any arithmetic on it:
a tree nobody has handed floors lays out what it did before #115, and
every test above floor_tests is one that has none.
pub fn is_empty(&self) -> bool
pub fn contains(&self, id: u64) -> bool
Sourcepub fn windows(&self) -> Vec<u64>
pub fn windows(&self) -> Vec<u64>
Every window in the tree, in insertion-independent tree order.
fn walk(&self, index: usize, out: &mut Vec<u64>)
Sourcepub fn insert(
&mut self,
id: u64,
target: Option<u64>,
at: Option<(f64, f64)>,
area: Rect,
settings: Settings,
)
pub fn insert( &mut self, id: u64, target: Option<u64>, at: Option<(f64, f64)>, area: Rect, settings: Settings, )
Add a window by splitting target, or the whole area if there is none.
at is where the pointer was. It decides which side of the split the
new window lands on — dropping a window on the right half of another
puts it on the right — which is the part that makes this feel like
placing a window rather than appending to a list.
Splits whatever it is pointed at, across that box’s longer side,
however small the halves come out: Settings::minimum is not read
here. This is the last resort, "allow" in config.tiling.overflow;
Self::insert_fitting is the same placement with the floor on it.
Sourcepub fn insert_fitting(
&mut self,
id: u64,
target: Option<u64>,
at: Option<(f64, f64)>,
area: Rect,
settings: Settings,
) -> bool
pub fn insert_fitting( &mut self, id: u64, target: Option<u64>, at: Option<(f64, f64)>, area: Rect, settings: Settings, ) -> bool
Self::insert, unless that would make a tile smaller than
Settings::minimum, or leave the window being split or the one being
added under its own floor (Self::set_floors) — in which case the
other axis is tried, and failing that nothing is done at all.
The same target as insert and the same side of it: this is “open
where the pointer is” with a floor under it, not a different rule. The
other axis first because a box that cannot be divided one way often can
be divided the other — a wide, short tile that has no room side by side
may still have room one above the other — and that is still the tile
the user was pointing at.
Returns whether the window is in the tree now. false leaves the tree
exactly as it was, so the caller can try somewhere else; nothing is
pushed until a split has been chosen, because a node pushed and then
abandoned would be found by Self::contains for ever after. An empty
tree always has room: its one window takes the whole area whatever
size that is, since there is nothing to divide.
Sourcepub fn insert_largest(
&mut self,
id: u64,
area: Rect,
settings: Settings,
) -> bool
pub fn insert_largest( &mut self, id: u64, area: Rect, settings: Settings, ) -> bool
Split the largest tile that has room for another window without going
under Settings::minimum or either window’s own floor: "largest" in
config.tiling.overflow.
Largest by area, and the largest with room rather than the largest: a big square tile can fail both ways where a smaller, longer one has room along its length, and skipping it is the difference between placing the window and sending it off this workspace. Ties go to the earlier tile in tree order, so the choice does not depend on anything the tree does not hold.
Each tile is tried across its longer side and then the other, as
Self::insert_fitting tries the one under the pointer. The new
window takes the far side, as it does from insert with no pointer:
the pointer is in some other tile by now, so it says nothing about this
one.
Only windows the tree holds have tiles here. A window a layout took out
at closing (#128) has none, and the neighbour that grew into its
space is measured at the size it is now.
Returns whether the window is in the tree now; false leaves the tree
as it was. An empty tree always has room, as for insert_fitting.
Sourcefn split_target(
&self,
id: u64,
target: Option<u64>,
at: Option<(f64, f64)>,
boxes: &[(u64, Rect)],
) -> Option<usize>
fn split_target( &self, id: u64, target: Option<u64>, at: Option<(f64, f64)>, boxes: &[(u64, Rect)], ) -> Option<usize>
The leaf a new window would split: the one named, else the one under
the pointer, else the one nearest it. None only for an empty tree.
The last step matters more than it looks: falling back to the root
instead means splitting the whole screen, and every new window then
lands as another full-height column — which is not dwindle at all, and
is exactly what this did before. Hyprland calls this getClosestNode.
Sourcefn box_of(&self, target: usize, boxes: &[(u64, Rect)], area: Rect) -> Rect
fn box_of(&self, target: usize, boxes: &[(u64, Rect)], area: Rect) -> Rect
The box a split target has now, which is what decides the axis: the split follows the shape of the space being divided rather than the shape of the screen.
Sourcefn split(
&mut self,
id: u64,
target: usize,
rect: Rect,
axis: Axis,
at: Option<(f64, f64)>,
settings: Settings,
)
fn split( &mut self, id: u64, target: usize, rect: Rect, axis: Axis, at: Option<(f64, f64)>, settings: Settings, )
Hang window id beside target, which occupies rect, across axis.
Everything that decides whether and where is done by the callers; this is the one place a node is pushed for a window that is not the root, so a caller that has decided against a split has pushed nothing.
Sourcepub fn swap(&mut self, a: u64, b: u64) -> bool
pub fn swap(&mut self, a: u64, b: u64) -> bool
Trade the tiles of windows a and b, and change nothing else: every
split keeps its axis and its ratio, so the arrangement is the one it
was with two windows in each other’s places, and a second swap puts it
back. What a keyboard move does in tiling (#150). Returns whether both
were in the tree; false leaves it as it was.
swap_trades_two_tiles_and_nothing_else.
Sourcepub fn remove(&mut self, id: u64)
pub fn remove(&mut self, id: u64)
Take a window out. Its sibling inherits the space, which is what makes closing a window a local change rather than a re-tile of the screen.
Sourcepub fn drag_seam(
&mut self,
id: u64,
edge: Edge,
edge_at: (f64, f64),
area: Rect,
settings: Settings,
)
pub fn drag_seam( &mut self, id: u64, edge: Edge, edge_at: (f64, f64), area: Rect, settings: Settings, )
Put the seam beside a window’s edge where edge_at says that edge
belongs.
edge_at is a position on each axis and only the one edge names is
read — .0 for a left or right side, .1 for a top or bottom — which
is what lets a corner drag call this twice with one pair and have each
axis take its own edge. The coordinates are the ones Self::layout
hands back, so “where this window’s right edge should be” is directly
comparable with the x + w of the slot this same tree produced for it.
It is not where the pointer is, and calling it that was #124. The
two coincide only for a drag that began exactly on the edge, and no
gesture does: a border grab is a band sixteen pixels wide, and the
modifier drag starts from wherever in the window the button went down.
The compositor derives this from the rectangle the drag has produced —
solium::input::resize::dragged_edge — so what arrives here has already
moved exactly as far as the pointer has, from exactly where the edge
already was. The arithmetic below did not change for #124 and did not
need to.
This is the tiled resize path: a tiled window has no size of its
own, so an edge drag moves the division it shares with its neighbour
and the compositor never resizes the window directly. The floating path
is the other one — see Solium::settle_resize, which only reaches
hold_resize when no layout claimed the drag — and the two are
deliberately independent.
Three things this does that adding a delta to the nearest parent cannot.
It finds the seam on the side that was grabbed. Self::seam_beside
carries the argument; the short version is that “the nearest ancestor
cut on this axis” is not the same branch as “the branch whose seam is
under the pointer”, and taking the first for the second is #120.
It reaches past the immediate parent. A window’s parent may have been cut the other way — in a two-by-two, the seam beside a side edge is two levels up — which is why width could be dragged in some arrangements and not others.
And it is idempotent. The ratio comes from a position, not from how far anything moved, so handling the same drag twice gives the same layout. Accumulating deltas fed the layout’s own response back in as the next input, and the windows shook themselves apart for as long as the button was held. A relative gesture and a delta are not the same thing: #124 made the gesture relative by choosing a better position to send, and left this idempotence exactly where it was.
Sourcefn room(&self, seam: usize, rect: Rect, settings: Settings) -> (f64, f64)
fn room(&self, seam: usize, rect: Rect, settings: Settings) -> (f64, f64)
The ratios a seam may take: 0.05..0.95, narrowed so that no tile on
either side of it is made smaller than Settings::minimum.
rect is the seam’s own box, from Self::node_box. Each side needs
Self::floor of it along the seam’s axis, and a side’s share of the
box less the gap is what the ratio divides, so the floors turn into
ratios by the same arithmetic cut runs forwards.
A side already under its floor is held where it is rather than moved
up to it. Tiles under the minimum are ordinary: "allow" makes them,
a monitor change or a smaller work area can, and so can raising the
minimum in a running session. Clamping such a seam into its range on
the first frame of a drag would throw it across the screen before the
pointer had moved – the #124 class of bug, reached through a floor
instead of a skew. So the range is widened to take in the ratio the
seam has now: that side can be grown, and cannot be made smaller still.
With both sides under, the seam cannot move at all, because any move
shrinks one of them.
No minimum on the seam’s axis leaves exactly 0.05..0.95, which is what this clamp was before #134.
Each window’s own floor narrows it further (#115), by what each
side needs for every window in it with a floor to reach it – Need,
the same measure Self::layout rebalances by. So the seam itself
stops there, and not only the picture of it: a floor that goes away
afterwards leaves the seam where the drag was stopped
(floor_tests::a_seam_stops_at_a_neighbours_own_floor). “The ratio it
has” is the one it is drawn at, Self::share, which is the one it
holds unless a floor has moved it.
Sourcefn floor(&self, index: usize, axis: Axis, least: f64, gap: f64) -> f64
fn floor(&self, index: usize, axis: Axis, least: f64, gap: f64) -> f64
The least room along axis the subtree at index needs for every tile
in it to be at least least long on that axis, with its own seams
where they are now.
At the ratios it has rather than at the best ratios it could have: a
drag moves one seam, so a subtree’s inner seams stay where they are and
its tiles grow and shrink in proportion. A branch cut along axis
needs each child’s floor divided by that child’s share, plus the gap
between them. A branch cut the other way gives each child the whole of
its extent along axis, so it needs only the larger of the two floors
– a column of stacked windows is as narrow as its widest floor, not
the sum of them.
Sourcefn seam_beside(&self, leaf: usize, edge: Edge) -> Option<usize>
fn seam_beside(&self, leaf: usize, edge: Edge) -> Option<usize>
The branch whose seam runs along edge of the window at leaf.
Two conditions, and the old walk checked only the first. The branch has
to be cut along the dragged axis, and this subtree has to be on the
side of it that puts the seam under the grabbed edge — which is
Edge::child. A window that is the second child of a vertical split
has that seam on its left and nowhere else; matching it for a
right-edge drag moves the window’s far side while the pointer’s side
stands still.
The walk continues towards the root rather than stopping at the first branch on the axis, because a branch on the wrong side is not a near-miss: the seam beside a right edge may be any number of levels up, past ancestors cut the other way and past ancestors cut the same way that this subtree happens to sit on the far side of.
None when the walk reaches the root having found no such branch,
which means exactly one thing: the window is flush against its
container on that side, and there is no seam there to move.
Sourcefn node_box(
&self,
wanted: usize,
area: Rect,
settings: Settings,
) -> Option<Rect>
fn node_box( &self, wanted: usize, area: Rect, settings: Settings, ) -> Option<Rect>
The rectangle a node occupies, branches included.
Sourcepub fn resize(
&mut self,
id: u64,
axis: Axis,
by: f64,
area: Rect,
settings: Settings,
)
pub fn resize( &mut self, id: u64, axis: Axis, by: f64, area: Rect, settings: Settings, )
Grow a window along axis by a fraction, moving one seam. Everything
on the far side stays put.
The keyboard path. It takes an axis and not an Edge because a
keypress names neither side: super+equal means “make this wider”, and
which of the two seams beside it gives up the space is not something
the user expressed. A drag is the opposite — the hand is on one
specific edge — which is why the two take different arguments.
So it prefers the trailing seam (right, or below) and falls back to the
leading one, with the sign flipped so that positive by still grows
the window either way. Only a window with no seam on either side —
which on that axis means the only window on the screen — does nothing.
This used to adjust the immediate parent and nothing else, which is the
same shortcut Self::drag_seam made: in an ordinary four-window
dwindle every leaf’s parent is a horizontal split, so the centre
vertical seam could not be reached from the keyboard at all and
super+equal silently changed the height instead.
Bounded as a drag is, by Self::room: a press does not make a tile
smaller than Settings::minimum, nor one already under it any
smaller. That is why this takes the area and the settings, which it did
not need before #134 – the floor is in pixels, and the seam’s box is
what turns pixels into a ratio.
Sourcepub fn layout(&self, area: Rect, settings: Settings) -> Vec<(u64, Rect)>
pub fn layout(&self, area: Rect, settings: Settings) -> Vec<(u64, Rect)>
Where every window goes.
Each split at the ratio it holds, unless a window beside it is under
its own floor – see the module note and shared.
fn place( &self, index: usize, rect: Rect, settings: Settings, out: &mut Vec<(u64, Rect)>, )
fn push(&mut self, node: Node) -> usize
fn leaf(&self, id: u64) -> Option<usize>
fn id_of(&self, index: usize) -> Option<u64>
Sourcefn leaf_at(
&self,
at: Option<(f64, f64)>,
boxes: &[(u64, Rect)],
) -> Option<usize>
fn leaf_at( &self, at: Option<(f64, f64)>, boxes: &[(u64, Rect)], ) -> Option<usize>
The window a point lands in.
Sourcefn closest_leaf(
&self,
at: Option<(f64, f64)>,
boxes: &[(u64, Rect)],
) -> Option<usize>
fn closest_leaf( &self, at: Option<(f64, f64)>, boxes: &[(u64, Rect)], ) -> Option<usize>
The window nearest a point, by distance from its centre.
Used when the pointer is over no window at all — off the edge, or on a gap. There is always a window to split; the question is only which.
fn parent(&self, index: usize) -> Option<usize>
Source§impl Tiling
impl Tiling
Sourcefn room_in(
&self,
rect: Rect,
old: usize,
new: u64,
at: Option<(f64, f64)>,
settings: Settings,
) -> Option<Axis>
fn room_in( &self, rect: Rect, old: usize, new: u64, at: Option<(f64, f64)>, settings: Settings, ) -> Option<Axis>
Which way rect, window old’s tile, can be split to add window new
with neither half under Settings::minimum or under its own window’s
floor: across its longer side if that has room, else across the other,
else neither.
Measured with cut itself, at the configured split and gap, so “has
room” means exactly what the layout will then draw – and since #115 at
the ratio shared moves that split to, which is what the layout will
draw when one of the two has a floor of its own. at puts the new
window on the side Self::split will, since a split that is not
at one half is not the same split mirrored. Both halves on both sides:
a split across the width leaves the height alone, and a tile that is
already too short does not become tall enough by being divided.
Neither window with a floor of its own is the check #134 made, to the bit: the ratio is the split, and each half is held to the minimum.