Allow having more than one full height window in the :code:tall layout
Fixes #2276
This commit is contained in:
parent
e8121b39ec
commit
cf8d665eb7
@ -13,6 +13,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
|||||||
- hints kitten: Allow pressing :sc:`goto_file_line` to quickly open
|
- hints kitten: Allow pressing :sc:`goto_file_line` to quickly open
|
||||||
the selected file at the selected line in vim or a configurable editor (:iss:`2268`)
|
the selected file at the selected line in vim or a configurable editor (:iss:`2268`)
|
||||||
|
|
||||||
|
- Allow having more than one full height window in the :code:`tall` layout
|
||||||
|
(:iss:`2276`)
|
||||||
|
|
||||||
- Allow choosing OpenType features for individual fonts via the
|
- Allow choosing OpenType features for individual fonts via the
|
||||||
:opt:`font_features` option.
|
:opt:`font_features` option.
|
||||||
|
|
||||||
|
|||||||
@ -225,8 +225,8 @@ using the :sc:`new_window` key combination.
|
|||||||
Currently, there are five layouts available,
|
Currently, there are five layouts available,
|
||||||
|
|
||||||
* **Stack** -- Only a single maximized window is shown at a time
|
* **Stack** -- Only a single maximized window is shown at a time
|
||||||
* **Tall** -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
|
* **Tall** -- One (or optionally more) windows are shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||||
* **Fat** -- One window is shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
* **Fat** -- One (or optionally more) windows are shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
||||||
* **Grid** -- All windows are shown in a grid
|
* **Grid** -- All windows are shown in a grid
|
||||||
* **Horizontal** -- All windows are shown side-by-side
|
* **Horizontal** -- All windows are shown side-by-side
|
||||||
* **Vertical** -- All windows are shown one below the other
|
* **Vertical** -- All windows are shown one below the other
|
||||||
@ -256,13 +256,15 @@ the resizing increment (a positive integer that defaults to 1).
|
|||||||
|
|
||||||
|
|
||||||
Some layouts take options to control their behavior. For example, the ``fat``
|
Some layouts take options to control their behavior. For example, the ``fat``
|
||||||
and ``tall`` layouts accept the ``bias`` option to control how the available
|
and ``tall`` layouts accept the ``bias`` and ``full_size`` options to control
|
||||||
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
how the available space is split up.
|
||||||
|
To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||||
|
|
||||||
enabled_layouts tall:bias=70
|
enabled_layouts tall:bias=70;full_size=2
|
||||||
|
|
||||||
This will make the tall window occupy ``70%`` of available width. ``bias`` can be
|
This will have ``2`` instead of a single tall window, that occupy ``70%``
|
||||||
any number between 10 and 90.
|
instead of ``50%`` of available width. ``bias`` can be any number between 10
|
||||||
|
and 90.
|
||||||
|
|
||||||
Writing a new layout only requires about a hundred lines of code, so if there
|
Writing a new layout only requires about a hundred lines of code, so if there
|
||||||
is some layout you want, take a look at `layout.py
|
is some layout you want, take a look at `layout.py
|
||||||
|
|||||||
163
kitty/layout.py
163
kitty/layout.py
@ -395,8 +395,11 @@ class Layout: # {{{
|
|||||||
br = self.blank_rects
|
br = self.blank_rects
|
||||||
left_blank_rect(first_window, br), top_blank_rect(first_window, br), right_blank_rect(last_window, br)
|
left_blank_rect(first_window, br), top_blank_rect(first_window, br), right_blank_rect(last_window, br)
|
||||||
|
|
||||||
def between_blank_rect(self, left_window, right_window):
|
def between_blank_rect(self, left_window, right_window, vertical=True):
|
||||||
|
if vertical:
|
||||||
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
|
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
|
||||||
|
else:
|
||||||
|
self.blank_rects.append(Rect(central.left, left_window.geometry.top, central.right + 1, right_window.geometry.bottom))
|
||||||
|
|
||||||
def bottom_blank_rect(self, window):
|
def bottom_blank_rect(self, window):
|
||||||
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
|
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
|
||||||
@ -449,28 +452,34 @@ class Tall(Layout): # {{{
|
|||||||
only_between_border = False, False, False, True
|
only_between_border = False, False, False, True
|
||||||
only_main_border = False, False, True, False
|
only_main_border = False, False, True, False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_full_size_windows(self):
|
||||||
|
return self.layout_opts['full_size']
|
||||||
|
|
||||||
def remove_all_biases(self):
|
def remove_all_biases(self):
|
||||||
self.main_bias = list(self.layout_opts['bias'])
|
self.main_bias = list(self.layout_opts['bias'])
|
||||||
self.biased_map = {}
|
self.biased_map = {}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def variable_layout(self, num_windows, biased_map):
|
def variable_layout(self, num_windows, biased_map):
|
||||||
num_windows -= 1
|
num_windows -= self.num_full_size_windows
|
||||||
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
|
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
|
||||||
|
|
||||||
def apply_bias(self, idx, increment, num_windows, is_horizontal):
|
def apply_bias(self, idx, increment, num_windows, is_horizontal):
|
||||||
if self.main_is_horizontal == is_horizontal:
|
if self.main_is_horizontal == is_horizontal:
|
||||||
before = self.main_bias
|
before = self.main_bias
|
||||||
if idx == 0:
|
ncols = self.num_full_size_windows + 1
|
||||||
self.main_bias = [safe_increment_bias(self.main_bias[0], increment), safe_increment_bias(self.main_bias[1], -increment)]
|
biased_col = idx if idx < self.num_full_size_windows else (ncols - 1)
|
||||||
else:
|
self.main_bias = [
|
||||||
self.main_bias = [safe_increment_bias(self.main_bias[0], -increment), safe_increment_bias(self.main_bias[1], increment)]
|
safe_increment_bias(self.main_bias[i], increment * (1 if i == biased_col else -1)) for i in range(ncols)
|
||||||
|
]
|
||||||
self.main_bias = normalize_biases(self.main_bias)
|
self.main_bias = normalize_biases(self.main_bias)
|
||||||
after = self.main_bias
|
after = self.main_bias
|
||||||
else:
|
else:
|
||||||
if idx == 0 or num_windows < 3:
|
num_of_short_windows = num_windows - self.num_full_size_windows
|
||||||
|
if idx < self.num_full_size_windows or num_of_short_windows < 2:
|
||||||
return False
|
return False
|
||||||
idx -= 1
|
idx -= self.num_full_size_windows
|
||||||
before_layout = list(self.variable_layout(num_windows, self.biased_map))
|
before_layout = list(self.variable_layout(num_windows, self.biased_map))
|
||||||
candidate = self.biased_map.copy()
|
candidate = self.biased_map.copy()
|
||||||
before = candidate.get(idx, 0)
|
before = candidate.get(idx, 0)
|
||||||
@ -484,40 +493,70 @@ class Tall(Layout): # {{{
|
|||||||
def parse_layout_opts(self, layout_opts):
|
def parse_layout_opts(self, layout_opts):
|
||||||
ans = Layout.parse_layout_opts(self, layout_opts)
|
ans = Layout.parse_layout_opts(self, layout_opts)
|
||||||
try:
|
try:
|
||||||
ans['bias'] = int(ans.get('bias', 50)) / 100
|
ans['full_size'] = int(ans.get('full_size', 1))
|
||||||
except Exception:
|
except Exception:
|
||||||
ans['bias'] = 0.5
|
ans['full_size'] = 1
|
||||||
ans['bias'] = max(0.1, min(ans['bias'], 0.9))
|
ans['full_size'] = fs = max(1, min(ans['full_size'], 100))
|
||||||
ans['bias'] = ans['bias'], 1.0 - ans['bias']
|
try:
|
||||||
|
b = int(ans.get('bias', 50)) / 100
|
||||||
|
except Exception:
|
||||||
|
b = 0.5
|
||||||
|
b = max(0.1, min(b, 0.9))
|
||||||
|
ans['bias'] = tuple(repeat(b / fs, fs)) + (1.0 - b,)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def do_layout(self, windows, active_window_idx):
|
def do_layout(self, windows, active_window_idx):
|
||||||
if len(windows) == 1:
|
if len(windows) == 1:
|
||||||
return self.layout_single_window(windows[0])
|
return self.layout_single_window(windows[0])
|
||||||
xlayout = self.xlayout(2, bias=self.main_bias)
|
y, ynum = next(self.vlayout(1))
|
||||||
xstart, xnum = next(xlayout)
|
if len(windows) <= self.num_full_size_windows:
|
||||||
ystart, ynum = next(self.vlayout(1))
|
bias = normalize_biases(self.main_bias[:-1])
|
||||||
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
|
xlayout = self.xlayout(self.num_full_size_windows, bias=bias)
|
||||||
xstart, xnum = next(xlayout)
|
for i, (w, (x, xnum)) in enumerate(zip(windows, xlayout)):
|
||||||
ylayout = self.variable_layout(len(windows), self.biased_map)
|
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||||
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, 1, None), ylayout)):
|
if i > 0:
|
||||||
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
|
self.between_blank_rect(windows[i-1], windows[i])
|
||||||
# right bottom blank rect
|
# bottom blank rect
|
||||||
self.bottom_blank_rect(windows[i + 1])
|
self.bottom_blank_rect(windows[i])
|
||||||
|
|
||||||
# left, top and right blank rects
|
# left, top and right blank rects
|
||||||
self.simple_blank_rects(windows[0], windows[-1])
|
self.simple_blank_rects(windows[0], windows[-1])
|
||||||
# between blank rect
|
return
|
||||||
self.between_blank_rect(windows[0], windows[1])
|
|
||||||
# left bottom blank rect
|
xlayout = self.xlayout(self.num_full_size_windows + 1, bias=self.main_bias)
|
||||||
self.bottom_blank_rect(windows[0])
|
for i in range(self.num_full_size_windows):
|
||||||
|
w = windows[i]
|
||||||
|
x, xnum = next(xlayout)
|
||||||
|
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||||
|
self.between_blank_rect(windows[i], windows[i+1])
|
||||||
|
# bottom blank rect
|
||||||
|
self.bottom_blank_rect(windows[i])
|
||||||
|
x, xnum = next(xlayout)
|
||||||
|
ylayout = self.variable_layout(len(windows), self.biased_map)
|
||||||
|
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), ylayout)):
|
||||||
|
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, ystart, ynum))
|
||||||
|
# right bottom blank rect
|
||||||
|
self.bottom_blank_rect(windows[-1])
|
||||||
|
# left, top and right blank rects
|
||||||
|
self.simple_blank_rects(windows[0], windows[-1])
|
||||||
|
|
||||||
def neighbors_for_window(self, window, windows):
|
def neighbors_for_window(self, window, windows):
|
||||||
if window is windows[0]:
|
|
||||||
return {'left': [], 'right': windows[1:], 'top': [], 'bottom': []}
|
|
||||||
idx = windows.index(window)
|
idx = windows.index(window)
|
||||||
return {'left': [windows[0]], 'right': [], 'top': [] if idx <= 1 else [windows[idx-1]],
|
prev = None if idx == 0 else windows[idx-1]
|
||||||
'bottom': [] if window is windows[-1] else [windows[idx+1]]}
|
nxt = None if idx == len(windows) - 1 else windows[idx+1]
|
||||||
|
ans = {'left': [prev] if prev is not None else [], 'right': [], 'top': [], 'bottom': []}
|
||||||
|
if idx < self.num_full_size_windows - 1:
|
||||||
|
if nxt is not None:
|
||||||
|
ans['right'] = [nxt]
|
||||||
|
elif idx == self.num_full_size_windows - 1:
|
||||||
|
ans['right'] = windows[idx+1:]
|
||||||
|
else:
|
||||||
|
ans['left'] = [windows[self.num_full_size_windows - 1]]
|
||||||
|
if idx > self.num_full_size_windows:
|
||||||
|
ans['top'] = [prev]
|
||||||
|
if nxt is not None:
|
||||||
|
ans['bottom'] = [nxt]
|
||||||
|
return ans
|
||||||
|
|
||||||
def minimal_borders(self, windows, active_window, needs_borders_map):
|
def minimal_borders(self, windows, active_window, needs_borders_map):
|
||||||
last_i = len(windows) - 1
|
last_i = len(windows) - 1
|
||||||
@ -525,11 +564,11 @@ class Tall(Layout): # {{{
|
|||||||
if needs_borders_map[w.id]:
|
if needs_borders_map[w.id]:
|
||||||
yield all_borders
|
yield all_borders
|
||||||
continue
|
continue
|
||||||
if i == 0:
|
if i < self.num_full_size_windows:
|
||||||
if last_i == 1 and needs_borders_map[windows[1].id]:
|
if (last_i == i+1 or i+1 < self.num_full_size_windows) and needs_borders_map[windows[i+1].id]:
|
||||||
yield no_borders
|
yield no_borders
|
||||||
else:
|
else:
|
||||||
yield self.only_main_border
|
yield no_borders if i == last_i else self.only_main_border
|
||||||
continue
|
continue
|
||||||
if i == last_i:
|
if i == last_i:
|
||||||
yield no_borders
|
yield no_borders
|
||||||
@ -552,31 +591,53 @@ class Fat(Tall): # {{{
|
|||||||
def do_layout(self, windows, active_window_idx):
|
def do_layout(self, windows, active_window_idx):
|
||||||
if len(windows) == 1:
|
if len(windows) == 1:
|
||||||
return self.layout_single_window(windows[0])
|
return self.layout_single_window(windows[0])
|
||||||
xstart, xnum = next(self.xlayout(1))
|
x, xnum = next(self.vlayout(1))
|
||||||
ylayout = self.ylayout(2, bias=self.main_bias)
|
if len(windows) <= self.num_full_size_windows:
|
||||||
ystart, ynum = next(ylayout)
|
bias = normalize_biases(self.main_bias[:-1])
|
||||||
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
|
ylayout = self.ylayout(self.num_full_size_windows, bias=bias)
|
||||||
xlayout = self.variable_layout(len(windows), self.biased_map)
|
for i, (w, (y, ynum)) in enumerate(zip(windows, ylayout)):
|
||||||
ystart, ynum = next(ylayout)
|
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||||
for i, (w, (xstart, xnum)) in enumerate(zip(islice(windows, 1, None), xlayout)):
|
|
||||||
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
|
|
||||||
if i > 0:
|
if i > 0:
|
||||||
# bottom between blank rect
|
self.between_blank_rect(windows[i-1], windows[i], vertical=False)
|
||||||
self.between_blank_rect(windows[i - 1], windows[i])
|
# bottom blank rect
|
||||||
|
self.bottom_blank_rect(windows[-1])
|
||||||
|
# left, top and right blank rects
|
||||||
|
self.simple_blank_rects(windows[0], windows[-1])
|
||||||
|
return
|
||||||
|
|
||||||
|
ylayout = self.ylayout(self.num_full_size_windows + 1, bias=self.main_bias)
|
||||||
|
for i in range(self.num_full_size_windows):
|
||||||
|
w = windows[i]
|
||||||
|
y, ynum = next(ylayout)
|
||||||
|
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||||
|
self.between_blank_rect(windows[i], windows[i+1], vertical=False)
|
||||||
|
y, ynum = next(ylayout)
|
||||||
|
xlayout = self.variable_layout(len(windows), self.biased_map)
|
||||||
|
for i, (w, (x, xnum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), xlayout)):
|
||||||
|
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, y, ynum))
|
||||||
|
# bottom blank rect
|
||||||
|
self.bottom_blank_rect(windows[i])
|
||||||
|
|
||||||
# left, top and right blank rects
|
# left, top and right blank rects
|
||||||
self.simple_blank_rects(windows[0], windows[-1])
|
self.simple_blank_rects(windows[0], windows[-1])
|
||||||
# top bottom blank rect
|
|
||||||
self.bottom_blank_rect(windows[0])
|
|
||||||
# bottom blank rect
|
|
||||||
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
|
|
||||||
|
|
||||||
def neighbors_for_window(self, window, windows):
|
def neighbors_for_window(self, window, windows):
|
||||||
if window is windows[0]:
|
|
||||||
return {'left': [], 'bottom': windows[1:], 'top': [], 'right': []}
|
|
||||||
idx = windows.index(window)
|
idx = windows.index(window)
|
||||||
return {'top': [windows[0]], 'bottom': [], 'left': [] if idx <= 1 else [windows[idx-1]],
|
prev = None if idx == 0 else windows[idx-1]
|
||||||
'right': [] if window is windows[-1] else [windows[idx+1]]}
|
nxt = None if idx == len(windows) - 1 else windows[idx+1]
|
||||||
|
ans = {'left': [], 'right': [], 'top': [] if prev is None else [prev], 'bottom': []}
|
||||||
|
if idx < self.num_full_size_windows - 1:
|
||||||
|
if nxt is not None:
|
||||||
|
ans['bottom'] = [nxt]
|
||||||
|
elif idx == self.num_full_size_windows - 1:
|
||||||
|
ans['bottom'] = windows[idx+1:]
|
||||||
|
else:
|
||||||
|
ans['top'] = [windows[self.num_full_size_windows - 1]]
|
||||||
|
if idx > self.num_full_size_windows:
|
||||||
|
ans['left'] = [prev]
|
||||||
|
if nxt is not None:
|
||||||
|
ans['right'] = [nxt]
|
||||||
|
return ans
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user