mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-11-13 12:09:35 +03:00
Allow individually setting margins and padding for each edge (left, right, top, bottom)
This commit is contained in:
parent
85b55b31b6
commit
c69b8870d2
@ -7,6 +7,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
0.17.3 [future]
|
||||
-----------------
|
||||
|
||||
- Allow individually setting margins and padding for each edge (left, right,
|
||||
top, bottom)
|
||||
|
||||
- Fix reverse video not being rendered correctly when using transparency or a
|
||||
background image (:iss:`2419`)
|
||||
|
||||
|
@ -6,7 +6,6 @@ from enum import IntFlag
|
||||
from itertools import chain
|
||||
from typing import List, Optional, Sequence, Tuple
|
||||
|
||||
from .constants import WindowGeometry
|
||||
from .fast_data_types import (
|
||||
BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
|
||||
os_window_has_background_image
|
||||
@ -22,22 +21,38 @@ class BorderColor(IntFlag):
|
||||
|
||||
|
||||
def vertical_edge(os_window_id: int, tab_id: int, color: int, width: int, top: int, bottom: int, left: int) -> None:
|
||||
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
|
||||
if width > 0:
|
||||
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
|
||||
|
||||
|
||||
def horizontal_edge(os_window_id: int, tab_id: int, color: int, height: int, left: int, right: int, top: int) -> None:
|
||||
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
|
||||
if height > 0:
|
||||
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
|
||||
|
||||
|
||||
def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], width: int, geometry: 'WindowGeometry', base_width: int = 0) -> None:
|
||||
left = geometry.left - (width + base_width)
|
||||
top = geometry.top - (width + base_width)
|
||||
right = geometry.right + (width + base_width)
|
||||
bottom = geometry.bottom + (width + base_width)
|
||||
horizontal_edge(os_window_id, tab_id, colors[1], width, left, right, top)
|
||||
horizontal_edge(os_window_id, tab_id, colors[3], width, left, right, geometry.bottom + base_width)
|
||||
vertical_edge(os_window_id, tab_id, colors[0], width, top, bottom, left)
|
||||
vertical_edge(os_window_id, tab_id, colors[2], width, top, bottom, geometry.right + base_width)
|
||||
def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], window: WindowType, borders: bool = False) -> None:
|
||||
geometry = window.geometry
|
||||
pl, pt = window.effective_padding('left'), window.effective_padding('top')
|
||||
pr, pb = window.effective_padding('right'), window.effective_padding('bottom')
|
||||
left = geometry.left - pl
|
||||
top = geometry.top - pt
|
||||
lr = geometry.right
|
||||
right = lr + pr
|
||||
bt = geometry.bottom
|
||||
bottom = bt + pb
|
||||
if borders:
|
||||
width = window.effective_border()
|
||||
bt = bottom
|
||||
lr = right
|
||||
left -= width
|
||||
top -= width
|
||||
right += width
|
||||
bottom += width
|
||||
pl = pr = pb = pt = width
|
||||
horizontal_edge(os_window_id, tab_id, colors[1], pt, left, right, top)
|
||||
horizontal_edge(os_window_id, tab_id, colors[3], pb, left, right, bt)
|
||||
vertical_edge(os_window_id, tab_id, colors[0], pl, top, bottom, left)
|
||||
vertical_edge(os_window_id, tab_id, colors[2], pr, top, bottom, lr)
|
||||
|
||||
|
||||
def load_borders_program() -> None:
|
||||
@ -58,8 +73,6 @@ class Borders:
|
||||
active_window: Optional[WindowType],
|
||||
current_layout: LayoutType,
|
||||
extra_blank_rects: Sequence[Tuple[int, int, int, int]],
|
||||
padding_width: int,
|
||||
border_width: int,
|
||||
draw_window_borders: bool = True,
|
||||
) -> None:
|
||||
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
|
||||
@ -68,15 +81,14 @@ class Borders:
|
||||
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
||||
left, top, right, bottom = br
|
||||
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg)
|
||||
bw, pw = border_width, padding_width
|
||||
if bw + pw <= 0:
|
||||
return
|
||||
bw = 0
|
||||
if windows:
|
||||
bw = windows[0].effective_border()
|
||||
draw_borders = bw > 0 and draw_window_borders
|
||||
if draw_borders:
|
||||
border_data = current_layout.resolve_borders(windows, active_window)
|
||||
|
||||
for i, w in enumerate(windows):
|
||||
g = w.geometry
|
||||
window_bg = w.screen.color_profile.default_bg
|
||||
window_bg = (window_bg << 8) | BorderColor.window_bg
|
||||
if draw_borders:
|
||||
@ -86,13 +98,11 @@ class Borders:
|
||||
else:
|
||||
color = BorderColor.bell if w.needs_attention else BorderColor.inactive
|
||||
colors = tuple(color if needed else window_bg for needed in next(border_data))
|
||||
draw_edges(
|
||||
self.os_window_id, self.tab_id, colors, bw, g, base_width=pw)
|
||||
if pw > 0 and not has_background_image:
|
||||
draw_edges(self.os_window_id, self.tab_id, colors, w, borders=True)
|
||||
if not has_background_image:
|
||||
# Draw the background rectangles over the padding region
|
||||
colors = (window_bg, window_bg, window_bg, window_bg)
|
||||
draw_edges(
|
||||
self.os_window_id, self.tab_id, colors, pw, g)
|
||||
colors = window_bg, window_bg, window_bg, window_bg
|
||||
draw_edges(self.os_window_id, self.tab_id, colors, w)
|
||||
|
||||
color = BorderColor.inactive
|
||||
for (left, top, right, bottom) in current_layout.window_independent_borders(windows, active_window):
|
||||
|
@ -534,7 +534,9 @@ class Boss:
|
||||
sz = os_window_font_size(os_window_id)
|
||||
if sz:
|
||||
os_window_font_size(os_window_id, sz, True)
|
||||
tm.update_dpi_based_sizes()
|
||||
for tab in tm:
|
||||
for window in tab:
|
||||
window.on_dpi_change(sz)
|
||||
tm.resize()
|
||||
|
||||
def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None:
|
||||
|
@ -23,7 +23,7 @@ from .config_data import all_options, parse_mods, type_convert
|
||||
from .constants import cache_dir, defconf, is_macos
|
||||
from .key_names import get_key_name_lookup, key_name_aliases
|
||||
from .options_stub import Options as OptionsStub
|
||||
from .typing import TypedDict
|
||||
from .typing import EdgeLiteral, TypedDict
|
||||
from .utils import log_error
|
||||
|
||||
KeySpec = Tuple[int, bool, int]
|
||||
@ -730,12 +730,23 @@ def initial_window_size_func(opts: OptionsStub, cached_values: Dict) -> Callable
|
||||
# scaling is not needed on Wayland, but is needed on macOS. Not
|
||||
# sure about X11.
|
||||
xscale = yscale = 1
|
||||
|
||||
def effective_margin(which: EdgeLiteral) -> float:
|
||||
ans: float = getattr(opts.single_window_margin_width, which)
|
||||
if ans < 0:
|
||||
ans = getattr(opts.window_margin_width, which)
|
||||
return ans
|
||||
|
||||
if w_unit == 'cells':
|
||||
width = cell_width * w / xscale + (dpi_x / 72) * (opts.window_margin_width + opts.window_padding_width) + 1
|
||||
spacing = effective_margin('left') + effective_margin('right')
|
||||
spacing += opts.window_padding_width.left + opts.window_padding_width.right
|
||||
width = cell_width * w / xscale + (dpi_x / 72) * spacing + 1
|
||||
else:
|
||||
width = w
|
||||
if h_unit == 'cells':
|
||||
height = cell_height * h / yscale + (dpi_y / 72) * (opts.window_margin_width + opts.window_padding_width) + 1
|
||||
spacing = effective_margin('top') + effective_margin('bottom')
|
||||
spacing += opts.window_padding_width.top + opts.window_padding_width.bottom
|
||||
height = cell_height * h / yscale + (dpi_y / 72) * spacing + 1
|
||||
else:
|
||||
height = h
|
||||
return int(width), int(height)
|
||||
|
@ -6,8 +6,8 @@
|
||||
import os
|
||||
from gettext import gettext as _
|
||||
from typing import (
|
||||
Any, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set, Tuple,
|
||||
TypeVar, Union
|
||||
Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set,
|
||||
Tuple, TypeVar, Union
|
||||
)
|
||||
|
||||
from . import fast_data_types as defines
|
||||
@ -16,7 +16,7 @@ from .conf.utils import (
|
||||
choices, positive_float, positive_int, to_bool, to_cmdline, to_color,
|
||||
to_color_or_none, unit_float
|
||||
)
|
||||
from .constants import config_dir, is_macos
|
||||
from .constants import FloatEdges, config_dir, is_macos
|
||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||
from .layout import all_layouts
|
||||
from .rgb import Color, color_as_int, color_as_sharp, color_from_int
|
||||
@ -675,15 +675,42 @@ that separate the inactive window from a neighbor. Note that setting
|
||||
a non-zero window margin overrides this and causes all borders to be drawn.
|
||||
'''))
|
||||
|
||||
o('window_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
||||
The window margin (in pts) (blank area outside the border)'''))
|
||||
|
||||
o('single_window_margin_width', -1000.0, option_type=float, long_text=_('''
|
||||
def edge_width(x: str, converter: Callable[[str], float] = positive_float) -> FloatEdges:
|
||||
parts = str(x).split()
|
||||
num = len(parts)
|
||||
if num == 1:
|
||||
val = converter(parts[0])
|
||||
return FloatEdges(val, val, val, val)
|
||||
if num == 2:
|
||||
v = converter(parts[0])
|
||||
h = converter(parts[1])
|
||||
return FloatEdges(h, v, h, v)
|
||||
if num == 3:
|
||||
top, h, bottom = map(converter, parts)
|
||||
return FloatEdges(h, top, h, bottom)
|
||||
top, right, bottom, left = map(converter, parts)
|
||||
return FloatEdges(left, top, right, bottom)
|
||||
|
||||
|
||||
def optional_edge_width(x: str) -> FloatEdges:
|
||||
return edge_width(x, float)
|
||||
|
||||
|
||||
edge_desc = _(
|
||||
'A single value sets all four sides. Two values set the vertical and horizontal sides.'
|
||||
' Three values set top, horizontal and bottom. Four values set top, right, bottom and left.')
|
||||
|
||||
|
||||
o('window_margin_width', '0', option_type=edge_width, long_text=_('''
|
||||
The window margin (in pts) (blank area outside the border). ''' + edge_desc))
|
||||
|
||||
o('single_window_margin_width', '-1', option_type=optional_edge_width, long_text=_('''
|
||||
The window margin (in pts) to use when only a single window is visible.
|
||||
Negative values will cause the value of :opt:`window_margin_width` to be used instead.'''))
|
||||
Negative values will cause the value of :opt:`window_margin_width` to be used instead. ''' + edge_desc))
|
||||
|
||||
o('window_padding_width', 0.0, option_type=positive_float, long_text=_('''
|
||||
The window padding (in pts) (blank area between the text and the window border)'''))
|
||||
o('window_padding_width', '0', option_type=edge_width, long_text=_('''
|
||||
The window padding (in pts) (blank area between the text and the window border). ''' + edge_desc))
|
||||
|
||||
o('placement_strategy', 'center', option_type=choices('center', 'top-left'), long_text=_('''
|
||||
When the window size is not an exact multiple of the cell size, the cell area of the terminal
|
||||
@ -1025,7 +1052,6 @@ you also set :opt:`allow_remote_control` to enable remote control. See the
|
||||
help for :option:`kitty --listen-on` for more details.
|
||||
'''))
|
||||
|
||||
|
||||
o(
|
||||
'+env', '',
|
||||
add_to_default=False,
|
||||
|
@ -34,6 +34,13 @@ class Edges(NamedTuple):
|
||||
bottom: int = 0
|
||||
|
||||
|
||||
class FloatEdges(NamedTuple):
|
||||
left: float = 0
|
||||
top: float = 0
|
||||
right: float = 0
|
||||
bottom: float = 0
|
||||
|
||||
|
||||
class ScreenGeometry(NamedTuple):
|
||||
xstart: float
|
||||
ystart: float
|
||||
|
@ -1132,3 +1132,7 @@ def spawn(
|
||||
|
||||
def key_to_bytes(glfw_key: int, smkx: bool, extended: bool, mods: int, action: int) -> bytes:
|
||||
pass
|
||||
|
||||
|
||||
def set_window_padding(os_window_id: int, tab_id: int, window_id: int, left: int, top: int, right: int, bottom: int) -> None:
|
||||
pass
|
||||
|
383
kitty/layout.py
383
kitty/layout.py
@ -3,9 +3,9 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from functools import lru_cache, partial
|
||||
from itertools import islice, repeat
|
||||
from itertools import repeat
|
||||
from typing import (
|
||||
Callable, Collection, Deque, Dict, FrozenSet, Generator, Iterable, List,
|
||||
Callable, Collection, Dict, FrozenSet, Generator, Iterable, List,
|
||||
NamedTuple, Optional, Sequence, Tuple, Union, cast
|
||||
)
|
||||
|
||||
@ -35,6 +35,7 @@ class LayoutData(NamedTuple):
|
||||
cells_per_window: int
|
||||
space_before: int
|
||||
space_after: int
|
||||
content_size: int
|
||||
|
||||
|
||||
# Utils {{{
|
||||
@ -46,7 +47,7 @@ draw_minimal_borders = False
|
||||
draw_active_borders = True
|
||||
align_top_left = False
|
||||
DecorationPairs = Sequence[Tuple[int, int]]
|
||||
WindowList = Union[List[WindowType], Deque[WindowType]]
|
||||
WindowList = List[WindowType]
|
||||
LayoutDimension = Generator[LayoutData, None, None]
|
||||
|
||||
|
||||
@ -70,41 +71,15 @@ def idx_for_id(win_id: int, windows: Iterable[WindowType]) -> Optional[int]:
|
||||
return i
|
||||
|
||||
|
||||
def effective_width(q: Optional[int], d: int) -> int:
|
||||
return d if q is None else q
|
||||
|
||||
|
||||
def set_layout_options(opts: Options) -> None:
|
||||
global draw_minimal_borders, draw_active_borders, align_top_left
|
||||
draw_minimal_borders = opts.draw_minimal_borders and opts.window_margin_width == 0
|
||||
draw_minimal_borders = opts.draw_minimal_borders and sum(opts.window_margin_width) == 0
|
||||
draw_active_borders = opts.active_border_color is not None
|
||||
align_top_left = opts.placement_strategy == 'top-left'
|
||||
|
||||
|
||||
def layout_dimension(
|
||||
start_at: int, length: int, cell_length: int,
|
||||
decoration_pairs: DecorationPairs,
|
||||
left_align: bool = False, bias: Optional[Sequence[float]] = None
|
||||
) -> LayoutDimension:
|
||||
number_of_windows = len(decoration_pairs)
|
||||
number_of_cells = length // cell_length
|
||||
space_needed_for_decorations: int = sum(map(sum, decoration_pairs))
|
||||
extra = length - number_of_cells * cell_length
|
||||
while extra < space_needed_for_decorations:
|
||||
number_of_cells -= 1
|
||||
extra = length - number_of_cells * cell_length
|
||||
def calculate_cells_map(bias: Optional[Sequence[float]], number_of_windows: int, number_of_cells: int) -> List[int]:
|
||||
cells_per_window = number_of_cells // number_of_windows
|
||||
extra -= space_needed_for_decorations
|
||||
pos = start_at
|
||||
if not left_align:
|
||||
pos += extra // 2
|
||||
|
||||
def calc_window_geom(i: int, cells_in_window: int) -> int:
|
||||
nonlocal pos
|
||||
pos += decoration_pairs[i][0]
|
||||
inner_length = cells_in_window * cell_length
|
||||
return inner_length + decoration_pairs[i][1]
|
||||
|
||||
if bias is not None and 1 < number_of_windows == len(bias) and cells_per_window > 5:
|
||||
cells_map = [int(b * number_of_cells) for b in bias]
|
||||
while min(cells_map) < 5:
|
||||
@ -115,23 +90,48 @@ def layout_dimension(
|
||||
cells_map[maxi] -= 1
|
||||
else:
|
||||
cells_map = list(repeat(cells_per_window, number_of_windows))
|
||||
|
||||
extra = number_of_cells - sum(cells_map)
|
||||
if extra > 0:
|
||||
cells_map[-1] += extra
|
||||
return cells_map
|
||||
|
||||
|
||||
def layout_dimension(
|
||||
start_at: int, length: int, cell_length: int,
|
||||
decoration_pairs: DecorationPairs,
|
||||
left_align: bool = False,
|
||||
bias: Optional[Sequence[float]] = None
|
||||
) -> LayoutDimension:
|
||||
number_of_windows = len(decoration_pairs)
|
||||
number_of_cells = length // cell_length
|
||||
space_needed_for_decorations: int = sum(map(sum, decoration_pairs))
|
||||
extra = length - number_of_cells * cell_length
|
||||
while extra < space_needed_for_decorations:
|
||||
number_of_cells -= 1
|
||||
extra = length - number_of_cells * cell_length
|
||||
cells_map = calculate_cells_map(bias, number_of_windows, number_of_cells)
|
||||
assert sum(cells_map) == number_of_cells
|
||||
|
||||
extra = length - number_of_cells * cell_length - space_needed_for_decorations
|
||||
pos = start_at
|
||||
if not left_align:
|
||||
pos += extra // 2
|
||||
last_i = len(cells_map) - 1
|
||||
|
||||
for i, cells_per_window in enumerate(cells_map):
|
||||
window_length = calc_window_geom(i, cells_per_window)
|
||||
before_dec, after_dec = decoration_pairs[i]
|
||||
pos += before_dec
|
||||
if i == 0:
|
||||
before_space = pos - start_at
|
||||
else:
|
||||
before_space = decoration_pairs[i][0]
|
||||
before_space = before_dec
|
||||
content_size = cells_per_window * cell_length
|
||||
if i == last_i:
|
||||
after_space = (start_at + length) - pos + window_length
|
||||
after_space = (start_at + length) - (pos + content_size)
|
||||
else:
|
||||
after_space = decoration_pairs[i][1]
|
||||
yield LayoutData(pos, cells_per_window, before_space, after_space)
|
||||
pos += window_length
|
||||
after_space = after_dec
|
||||
yield LayoutData(pos, cells_per_window, before_space, after_space, content_size)
|
||||
pos += content_size + after_space
|
||||
|
||||
|
||||
class Rect(NamedTuple):
|
||||
@ -219,21 +219,11 @@ class Layout: # {{{
|
||||
layout_opts = LayoutOpts({})
|
||||
only_active_window_visible = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
os_window_id: int, tab_id: int,
|
||||
margin_width: int, single_window_margin_width: int,
|
||||
padding_width: int, border_width: int,
|
||||
layout_opts: str = ''
|
||||
) -> None:
|
||||
def __init__(self, os_window_id: int, tab_id: int, layout_opts: str = '') -> None:
|
||||
self.os_window_id = os_window_id
|
||||
self.tab_id = tab_id
|
||||
self.set_active_window_in_os_window = partial(set_active_window, os_window_id, tab_id)
|
||||
self.swap_windows_in_os_window = partial(swap_windows, os_window_id, tab_id)
|
||||
self.border_width = border_width
|
||||
self.margin_width = margin_width
|
||||
self.single_window_margin_width = single_window_margin_width
|
||||
self.padding_width = padding_width
|
||||
# A set of rectangles corresponding to the blank spaces at the edges of
|
||||
# this layout, i.e. spaces that are not covered by any window
|
||||
self.blank_rects: List[Rect] = []
|
||||
@ -242,19 +232,13 @@ class Layout: # {{{
|
||||
self.full_name = self.name + ((':' + layout_opts) if layout_opts else '')
|
||||
self.remove_all_biases()
|
||||
|
||||
def update_sizes(self, margin_width: int, single_window_margin_width: int, padding_width: int, border_width: int) -> None:
|
||||
self.border_width = border_width
|
||||
self.margin_width = margin_width
|
||||
self.single_window_margin_width = single_window_margin_width
|
||||
self.padding_width = padding_width
|
||||
|
||||
def bias_increment_for_cell(self, is_horizontal: bool) -> float:
|
||||
self._set_dimensions()
|
||||
if is_horizontal:
|
||||
return (cell_width + 1) / central.width
|
||||
return (cell_height + 1) / central.height
|
||||
|
||||
def apply_bias(self, idx: int, increment: float, num_windows: int, is_horizontal: bool = True) -> bool:
|
||||
def apply_bias(self, idx: int, increment: float, windows: WindowList, is_horizontal: bool = True) -> bool:
|
||||
return False
|
||||
|
||||
def remove_all_biases(self) -> bool:
|
||||
@ -270,7 +254,7 @@ class Layout: # {{{
|
||||
if idx is None and w.overlay_window_id is not None:
|
||||
idx = idx_for_id(w.overlay_window_id, windows)
|
||||
if idx is not None:
|
||||
return self.apply_bias(idx, increment, len(windows), is_horizontal)
|
||||
return self.apply_bias(idx, increment, windows, is_horizontal)
|
||||
return False
|
||||
|
||||
def parse_layout_opts(self, layout_opts: Optional[str] = None) -> LayoutOpts:
|
||||
@ -486,16 +470,16 @@ class Layout: # {{{
|
||||
return cast(int, idx_for_id(active_window.id, all_windows))
|
||||
|
||||
# Utils {{{
|
||||
|
||||
def layout_single_window(self, w: WindowType, return_geometry: bool = False, left_align: bool = False) -> Optional[WindowGeometry]:
|
||||
default_margin = self.margin_width if self.single_window_margin_width < 0 else self.single_window_margin_width
|
||||
bw = self.border_width if self.must_draw_borders else 0
|
||||
bw = w.effective_border() if self.must_draw_borders else 0
|
||||
xdecoration_pairs = ((
|
||||
effective_width(w.padding.left, self.padding_width) + effective_width(w.margin.left, default_margin) + bw,
|
||||
effective_width(w.padding.right, self.padding_width) + effective_width(w.margin.right, default_margin) + bw,
|
||||
w.effective_padding('left') + w.effective_margin('left', is_single_window=True) + bw,
|
||||
w.effective_padding('right') + w.effective_margin('right', is_single_window=True) + bw,
|
||||
),)
|
||||
ydecoration_pairs = ((
|
||||
effective_width(w.padding.top, self.padding_width) + effective_width(w.margin.top, default_margin) + bw,
|
||||
effective_width(w.padding.bottom, self.padding_width) + effective_width(w.margin.bottom, default_margin) + bw,
|
||||
w.effective_padding('top') + w.effective_margin('top', is_single_window=True) + bw,
|
||||
w.effective_padding('bottom') + w.effective_margin('bottom', is_single_window=True) + bw,
|
||||
),)
|
||||
wg = layout_single_window(xdecoration_pairs, ydecoration_pairs, left_align=left_align)
|
||||
if return_geometry:
|
||||
@ -505,26 +489,44 @@ class Layout: # {{{
|
||||
return None
|
||||
|
||||
def xlayout(
|
||||
self, num: int, bias: Optional[Sequence[float]] = None, left: Optional[int] = None, width: Optional[int] = None
|
||||
self,
|
||||
windows: WindowList,
|
||||
bias: Optional[Sequence[float]] = None,
|
||||
start: Optional[int] = None,
|
||||
size: Optional[int] = None
|
||||
) -> LayoutDimension:
|
||||
decoration = self.margin_width + self.border_width + self.padding_width
|
||||
decoration_pairs = tuple(repeat((decoration, decoration), num))
|
||||
if left is None:
|
||||
left = central.left
|
||||
if width is None:
|
||||
width = central.width
|
||||
return layout_dimension(left, width, cell_width, decoration_pairs, bias=bias, left_align=align_top_left)
|
||||
decoration_pairs = tuple(
|
||||
(
|
||||
w.effective_margin('left') + w.effective_border() + w.effective_padding('left'),
|
||||
w.effective_margin('right') + w.effective_border() + w.effective_padding('right'),
|
||||
) for w in windows
|
||||
)
|
||||
if start is None:
|
||||
start = central.left
|
||||
if size is None:
|
||||
size = central.width
|
||||
return layout_dimension(start, size, cell_width, decoration_pairs, bias=bias, left_align=align_top_left)
|
||||
|
||||
def ylayout(
|
||||
self, num: int, left_align: bool = True, bias: Optional[Sequence[float]] = None, top: Optional[int] = None, height: Optional[int] = None
|
||||
self, windows: WindowList, bias: Optional[Sequence[float]] = None, start: Optional[int] = None, size: Optional[int] = None
|
||||
) -> LayoutDimension:
|
||||
decoration = self.margin_width + self.border_width + self.padding_width
|
||||
decoration_pairs = tuple(repeat((decoration, decoration), num))
|
||||
if top is None:
|
||||
top = central.top
|
||||
if height is None:
|
||||
height = central.height
|
||||
return layout_dimension(top, height, cell_height, decoration_pairs, bias=bias, left_align=align_top_left)
|
||||
decoration_pairs = tuple(
|
||||
(
|
||||
w.effective_margin('top') + w.effective_border() + w.effective_padding('top'),
|
||||
w.effective_margin('bottom') + w.effective_border() + w.effective_padding('bottom'),
|
||||
) for w in windows
|
||||
)
|
||||
if start is None:
|
||||
start = central.top
|
||||
if size is None:
|
||||
size = central.height
|
||||
return layout_dimension(start, size, cell_height, decoration_pairs, bias=bias, left_align=align_top_left)
|
||||
|
||||
def set_window_geometry(self, w: WindowType, idx: int, xl: LayoutData, yl: LayoutData) -> None:
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(idx, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
|
||||
# }}}
|
||||
|
||||
def do_layout(self, windows: WindowList, active_window_idx: int) -> None:
|
||||
@ -626,6 +628,8 @@ class Tall(Layout):
|
||||
only_between_border = Borders(False, False, False, True)
|
||||
only_main_border = Borders(False, False, True, False)
|
||||
layout_opts = TallLayoutOpts({})
|
||||
main_axis_layout = Layout.xlayout
|
||||
perp_axis_layout = Layout.ylayout
|
||||
|
||||
@property
|
||||
def num_full_size_windows(self) -> int:
|
||||
@ -636,14 +640,13 @@ class Tall(Layout):
|
||||
self.biased_map: Dict[int, float] = {}
|
||||
return True
|
||||
|
||||
def variable_layout(self, num_windows: int, biased_map: Dict[int, float]) -> LayoutDimension:
|
||||
num_windows -= self.num_full_size_windows
|
||||
bias = variable_bias(num_windows, biased_map) if num_windows > 1 else None
|
||||
if self.main_is_horizontal:
|
||||
return self.ylayout(num_windows, bias=bias)
|
||||
return self.xlayout(num_windows, bias=bias)
|
||||
def variable_layout(self, windows: WindowList, biased_map: Dict[int, float]) -> LayoutDimension:
|
||||
windows = windows[self.num_full_size_windows:]
|
||||
bias = variable_bias(len(windows), biased_map) if len(windows) > 1 else None
|
||||
return self.perp_axis_layout(windows, bias=bias)
|
||||
|
||||
def apply_bias(self, idx: int, increment: float, num_windows: int, is_horizontal: bool = True) -> bool:
|
||||
def apply_bias(self, idx: int, increment: float, windows: WindowList, is_horizontal: bool = True) -> bool:
|
||||
num_windows = len(windows)
|
||||
if self.main_is_horizontal == is_horizontal:
|
||||
before_main_bias = self.main_bias
|
||||
ncols = self.num_full_size_windows + 1
|
||||
@ -658,11 +661,11 @@ class Tall(Layout):
|
||||
if idx < self.num_full_size_windows or num_of_short_windows < 2:
|
||||
return False
|
||||
idx -= self.num_full_size_windows
|
||||
before_layout = list(self.variable_layout(num_windows, self.biased_map))
|
||||
before_layout = list(self.variable_layout(windows, self.biased_map))
|
||||
before = self.biased_map.get(idx, 0.)
|
||||
candidate = self.biased_map.copy()
|
||||
candidate[idx] = after = before + increment
|
||||
if before_layout == list(self.variable_layout(num_windows, candidate)):
|
||||
if before_layout == list(self.variable_layout(windows, candidate)):
|
||||
return False
|
||||
self.biased_map = candidate
|
||||
return before != after
|
||||
@ -671,29 +674,38 @@ class Tall(Layout):
|
||||
if len(windows) == 1:
|
||||
self.layout_single_window(windows[0])
|
||||
return
|
||||
yl = next(self.ylayout(1))
|
||||
if len(windows) <= self.num_full_size_windows:
|
||||
bias = normalize_biases(self.main_bias[:-1])
|
||||
xlayout = self.xlayout(self.num_full_size_windows, bias=bias)
|
||||
is_fat = not self.main_is_horizontal
|
||||
if len(windows) <= self.num_full_size_windows + 1:
|
||||
xlayout = self.main_axis_layout(windows, bias=self.main_bias)
|
||||
for i, (w, xl) in enumerate(zip(windows, xlayout)):
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
yl = next(self.perp_axis_layout([w]))
|
||||
if is_fat:
|
||||
xl, yl = yl, xl
|
||||
self.set_window_geometry(w, i, xl, yl)
|
||||
return
|
||||
|
||||
xlayout = self.xlayout(self.num_full_size_windows + 1, bias=self.main_bias)
|
||||
for i in range(self.num_full_size_windows):
|
||||
w = windows[i]
|
||||
xlayout = self.main_axis_layout(windows[:self.num_full_size_windows + 1], bias=self.main_bias)
|
||||
attr: EdgeLiteral = 'bottom' if is_fat else 'right'
|
||||
start = central.top if is_fat else central.left
|
||||
for i, w in enumerate(windows):
|
||||
if i >= self.num_full_size_windows:
|
||||
break
|
||||
xl = next(xlayout)
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
xl = next(xlayout)
|
||||
ylayout = self.variable_layout(len(windows), self.biased_map)
|
||||
for i, (w, yl) in enumerate(zip(islice(windows, self.num_full_size_windows, None), ylayout)):
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i + self.num_full_size_windows, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
yl = next(self.perp_axis_layout([w]))
|
||||
if is_fat:
|
||||
xl, yl = yl, xl
|
||||
self.set_window_geometry(w, i, xl, yl)
|
||||
start = getattr(w.geometry, attr) + w.effective_border() + w.effective_margin(attr) + w.effective_padding(attr)
|
||||
ylayout = self.variable_layout(windows, self.biased_map)
|
||||
size = (central.height if is_fat else central.width) - start
|
||||
for i, w in enumerate(windows):
|
||||
if i < self.num_full_size_windows:
|
||||
continue
|
||||
yl = next(ylayout)
|
||||
xl = next(self.main_axis_layout([w], start=start, size=size))
|
||||
if is_fat:
|
||||
xl, yl = yl, xl
|
||||
self.set_window_geometry(w, i, xl, yl)
|
||||
|
||||
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
|
||||
return neighbors_for_tall_window(self.num_full_size_windows, window, windows)
|
||||
@ -726,34 +738,8 @@ class Fat(Tall): # {{{
|
||||
main_is_horizontal = False
|
||||
only_between_border = Borders(False, False, True, False)
|
||||
only_main_border = Borders(False, False, False, True)
|
||||
|
||||
def do_layout(self, windows: WindowList, active_window_idx: int) -> None:
|
||||
if len(windows) == 1:
|
||||
self.layout_single_window(windows[0])
|
||||
return
|
||||
xl = next(self.xlayout(1))
|
||||
if len(windows) <= self.num_full_size_windows:
|
||||
bias = normalize_biases(self.main_bias[:-1])
|
||||
ylayout = self.ylayout(self.num_full_size_windows, bias=bias)
|
||||
for i, (w, yl) in enumerate(zip(windows, ylayout)):
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
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]
|
||||
yl = next(ylayout)
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
yl = next(ylayout)
|
||||
xlayout = self.variable_layout(len(windows), self.biased_map)
|
||||
for i, (w, xl) in enumerate(zip(islice(windows, self.num_full_size_windows, None), xlayout)):
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i + self.num_full_size_windows, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
main_axis_layout = Layout.ylayout
|
||||
perp_axis_layout = Layout.xlayout
|
||||
|
||||
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
|
||||
idx = windows.index(window)
|
||||
@ -800,11 +786,28 @@ class Grid(Layout):
|
||||
self.biased_cols: Dict[int, float] = {}
|
||||
return True
|
||||
|
||||
def column_layout(
|
||||
self,
|
||||
num: int,
|
||||
bias: Optional[Sequence[float]] = None,
|
||||
) -> LayoutDimension:
|
||||
decoration_pairs = tuple(repeat((0, 0), num))
|
||||
return layout_dimension(central.left, central.width, cell_width, decoration_pairs, bias=bias, left_align=align_top_left)
|
||||
|
||||
def row_layout(
|
||||
self,
|
||||
num: int,
|
||||
bias: Optional[Sequence[float]] = None,
|
||||
) -> LayoutDimension:
|
||||
decoration_pairs = tuple(repeat((0, 0), num))
|
||||
return layout_dimension(central.top, central.height, cell_height, decoration_pairs, bias=bias, left_align=align_top_left)
|
||||
|
||||
def variable_layout(self, layout_func: Callable[..., LayoutDimension], num_windows: int, biased_map: Dict[int, float]) -> LayoutDimension:
|
||||
return layout_func(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
|
||||
|
||||
def apply_bias(self, idx: int, increment: float, num_windows: int, is_horizontal: bool = True) -> bool:
|
||||
def apply_bias(self, idx: int, increment: float, windows: WindowList, is_horizontal: bool = True) -> bool:
|
||||
b = self.biased_cols if is_horizontal else self.biased_rows
|
||||
num_windows = len(windows)
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
|
||||
|
||||
def position_for_window_idx(idx: int) -> Tuple[int, int]:
|
||||
@ -830,8 +833,8 @@ class Grid(Layout):
|
||||
bias_idx = col_num
|
||||
attr = 'biased_cols'
|
||||
|
||||
def layout_func(num_windows: int, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
|
||||
return self.xlayout(num_windows, bias=bias)
|
||||
def layout_func(windows: WindowList, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
|
||||
return self.column_layout(num_windows, bias=bias)
|
||||
|
||||
else:
|
||||
b = self.biased_rows
|
||||
@ -840,8 +843,8 @@ class Grid(Layout):
|
||||
bias_idx = row_num
|
||||
attr = 'biased_rows'
|
||||
|
||||
def layout_func(num_windows: int, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
|
||||
return self.xlayout(num_windows, bias=bias)
|
||||
def layout_func(windows: WindowList, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
|
||||
return self.row_layout(num_windows, bias=bias)
|
||||
|
||||
before_layout = list(self.variable_layout(layout_func, num_windows, b))
|
||||
candidate = b.copy()
|
||||
@ -860,9 +863,9 @@ class Grid(Layout):
|
||||
on_col_done: Callable[[List[int]], None] = lambda col_windows: None
|
||||
) -> Generator[Tuple[int, LayoutData, LayoutData], None, None]:
|
||||
# Distribute windows top-to-bottom, left-to-right (i.e. in columns)
|
||||
xlayout = self.variable_layout(self.xlayout, ncols, self.biased_cols)
|
||||
yvals_normal = tuple(self.variable_layout(self.ylayout, nrows, self.biased_rows))
|
||||
yvals_special = yvals_normal if special_rows == nrows else tuple(self.variable_layout(self.ylayout, special_rows, self.biased_rows))
|
||||
xlayout = self.variable_layout(self.column_layout, ncols, self.biased_cols)
|
||||
yvals_normal = tuple(self.variable_layout(self.row_layout, nrows, self.biased_rows))
|
||||
yvals_special = yvals_normal if special_rows == nrows else tuple(self.variable_layout(self.row_layout, special_rows, self.biased_rows))
|
||||
pos = 0
|
||||
for col in range(ncols):
|
||||
rows = special_rows if col == special_col else nrows
|
||||
@ -889,12 +892,19 @@ class Grid(Layout):
|
||||
col_windows_w = [windows[i] for i in col_windows]
|
||||
win_col_map.append(col_windows_w)
|
||||
|
||||
def extents(ld: LayoutData) -> Tuple[int, int]:
|
||||
start = ld.content_pos - ld.space_before
|
||||
size = ld.space_before + ld.space_after + ld.content_size
|
||||
return start, size
|
||||
|
||||
for window_idx, xl, yl in self.layout_windows(
|
||||
len(windows), nrows, ncols, special_rows, special_col, on_col_done):
|
||||
w = windows[window_idx]
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(window_idx, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
start, size = extents(xl)
|
||||
xl = next(self.xlayout([w], start=start, size=size))
|
||||
start, size = extents(yl)
|
||||
yl = next(self.ylayout([w], start=start, size=size))
|
||||
self.set_window_geometry(w, window_idx, xl, yl)
|
||||
|
||||
def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
|
||||
n = len(windows)
|
||||
@ -981,27 +991,29 @@ class Vertical(Layout): # {{{
|
||||
name = 'vertical'
|
||||
main_is_horizontal = False
|
||||
only_between_border = Borders(False, False, False, True)
|
||||
main_axis_layout = Layout.ylayout
|
||||
perp_axis_layout = Layout.xlayout
|
||||
|
||||
def variable_layout(self, num_windows: int, biased_map: Dict[int, float]) -> LayoutDimension:
|
||||
def variable_layout(self, windows: WindowList, biased_map: Dict[int, float]) -> LayoutDimension:
|
||||
num_windows = len(windows)
|
||||
bias = variable_bias(num_windows, biased_map) if num_windows else None
|
||||
if self.main_is_horizontal:
|
||||
return self.xlayout(num_windows, bias=bias)
|
||||
return self.ylayout(num_windows, bias=bias)
|
||||
return self.main_axis_layout(windows, bias=bias)
|
||||
|
||||
def remove_all_biases(self) -> bool:
|
||||
self.biased_map: Dict[int, float] = {}
|
||||
return True
|
||||
|
||||
def apply_bias(self, idx: int, increment: float, num_windows: int, is_horizontal: bool = True) -> bool:
|
||||
def apply_bias(self, idx: int, increment: float, windows: WindowList, is_horizontal: bool = True) -> bool:
|
||||
if self.main_is_horizontal != is_horizontal:
|
||||
return False
|
||||
num_windows = len(windows)
|
||||
if num_windows < 2:
|
||||
return False
|
||||
before_layout = list(self.variable_layout(num_windows, self.biased_map))
|
||||
before_layout = list(self.variable_layout(windows, self.biased_map))
|
||||
candidate = self.biased_map.copy()
|
||||
before = candidate.get(idx, 0)
|
||||
candidate[idx] = before + increment
|
||||
if before_layout == list(self.variable_layout(num_windows, candidate)):
|
||||
if before_layout == list(self.variable_layout(windows, candidate)):
|
||||
return False
|
||||
self.biased_map = candidate
|
||||
return True
|
||||
@ -1012,13 +1024,12 @@ class Vertical(Layout): # {{{
|
||||
self.layout_single_window(windows[0])
|
||||
return
|
||||
|
||||
xlayout = self.xlayout(1)
|
||||
xl = next(xlayout)
|
||||
ylayout = self.variable_layout(window_count, self.biased_map)
|
||||
ylayout = self.variable_layout(windows, self.biased_map)
|
||||
for i, (w, yl) in enumerate(zip(windows, ylayout)):
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
xl = next(self.perp_axis_layout([w]))
|
||||
if self.main_is_horizontal:
|
||||
xl, yl = yl, xl
|
||||
self.set_window_geometry(w, i, xl, yl)
|
||||
|
||||
def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
|
||||
last_i = len(windows) - 1
|
||||
@ -1050,20 +1061,8 @@ class Horizontal(Vertical): # {{{
|
||||
name = 'horizontal'
|
||||
main_is_horizontal = True
|
||||
only_between_border = Borders(False, False, True, False)
|
||||
|
||||
def do_layout(self, windows: WindowList, active_window_idx: int) -> None:
|
||||
window_count = len(windows)
|
||||
if window_count == 1:
|
||||
self.layout_single_window(windows[0])
|
||||
return
|
||||
|
||||
xlayout = self.variable_layout(window_count, self.biased_map)
|
||||
ylayout = self.ylayout(1)
|
||||
yl = next(ylayout)
|
||||
for i, (w, xl) in enumerate(zip(windows, xlayout)):
|
||||
wg = window_geometry_from_layouts(xl, yl)
|
||||
w.set_geometry(i, wg)
|
||||
self.blank_rects.extend(blank_rects_for_window(wg))
|
||||
main_axis_layout = Layout.xlayout
|
||||
perp_axis_layout = Layout.ylayout
|
||||
|
||||
# }}}
|
||||
|
||||
@ -1199,6 +1198,11 @@ class Pair:
|
||||
q.set_geometry(id_idx_map[q.id], window_geometry)
|
||||
layout_object.blank_rects.extend(blank_rects_for_window(window_geometry))
|
||||
|
||||
def effective_border(self, id_window_map: Dict[int, WindowType]) -> int:
|
||||
for wid in self.all_window_ids():
|
||||
return id_window_map[wid].effective_border()
|
||||
return 0
|
||||
|
||||
def layout_pair(
|
||||
self,
|
||||
left: int, top: int, width: int, height: int,
|
||||
@ -1213,22 +1217,24 @@ class Pair:
|
||||
return q.layout_pair(left, top, width, height, id_window_map, id_idx_map, layout_object)
|
||||
if q is None:
|
||||
return
|
||||
xl = next(layout_object.xlayout(1, left=left, width=width))
|
||||
yl = next(layout_object.ylayout(1, top=top, height=height))
|
||||
w = id_window_map[q]
|
||||
xl = next(layout_object.xlayout([w], start=left, size=width))
|
||||
yl = next(layout_object.ylayout([w], start=top, size=height))
|
||||
geom = window_geometry_from_layouts(xl, yl)
|
||||
self.apply_window_geometry(q, geom, id_window_map, id_idx_map, layout_object)
|
||||
return
|
||||
bw = layout_object.border_width if draw_minimal_borders else 0
|
||||
bw = self.effective_border(id_window_map) if draw_minimal_borders else 0
|
||||
b1 = bw // 2
|
||||
b2 = bw - b1
|
||||
if self.horizontal:
|
||||
yl = next(layout_object.ylayout(1, top=top, height=height))
|
||||
w1 = max(2*cell_width + 1, int(self.bias * width) - b1)
|
||||
w2 = max(2*cell_width + 1, width - w1 - b1 - b2)
|
||||
if isinstance(self.one, Pair):
|
||||
self.one.layout_pair(left, top, w1, height, id_window_map, id_idx_map, layout_object)
|
||||
else:
|
||||
xl = next(layout_object.xlayout(1, left=left, width=w1))
|
||||
w = id_window_map[self.one]
|
||||
yl = next(layout_object.ylayout([w], start=top, size=height))
|
||||
xl = next(layout_object.xlayout([w], start=left, size=w1))
|
||||
geom = window_geometry_from_layouts(xl, yl)
|
||||
self.apply_window_geometry(self.one, geom, id_window_map, id_idx_map, layout_object)
|
||||
if b1 + b2:
|
||||
@ -1237,17 +1243,20 @@ class Pair:
|
||||
if isinstance(self.two, Pair):
|
||||
self.two.layout_pair(left + w1, top, w2, height, id_window_map, id_idx_map, layout_object)
|
||||
else:
|
||||
xl = next(layout_object.xlayout(1, left=left + w1, width=w2))
|
||||
w = id_window_map[self.two]
|
||||
xl = next(layout_object.xlayout([w], start=left + w1, size=w2))
|
||||
yl = next(layout_object.ylayout([w], start=top, size=height))
|
||||
geom = window_geometry_from_layouts(xl, yl)
|
||||
self.apply_window_geometry(self.two, geom, id_window_map, id_idx_map, layout_object)
|
||||
else:
|
||||
xl = next(layout_object.xlayout(1, left=left, width=width))
|
||||
h1 = max(2*cell_height + 1, int(self.bias * height) - b1)
|
||||
h2 = max(2*cell_height + 1, height - h1 - b1 - b2)
|
||||
if isinstance(self.one, Pair):
|
||||
self.one.layout_pair(left, top, width, h1, id_window_map, id_idx_map, layout_object)
|
||||
else:
|
||||
yl = next(layout_object.ylayout(1, top=top, height=h1))
|
||||
w = id_window_map[self.one]
|
||||
xl = next(layout_object.xlayout([w], start=left, size=width))
|
||||
yl = next(layout_object.ylayout([w], start=top, size=h1))
|
||||
geom = window_geometry_from_layouts(xl, yl)
|
||||
self.apply_window_geometry(self.one, geom, id_window_map, id_idx_map, layout_object)
|
||||
if b1 + b2:
|
||||
@ -1256,7 +1265,9 @@ class Pair:
|
||||
if isinstance(self.two, Pair):
|
||||
self.two.layout_pair(left, top + h1, width, h2, id_window_map, id_idx_map, layout_object)
|
||||
else:
|
||||
yl = next(layout_object.ylayout(1, top=top + h1, height=h2))
|
||||
w = id_window_map[self.two]
|
||||
xl = next(layout_object.xlayout([w], start=left, size=width))
|
||||
yl = next(layout_object.ylayout([w], start=top + h1, size=h2))
|
||||
geom = window_geometry_from_layouts(xl, yl)
|
||||
self.apply_window_geometry(self.two, geom, id_window_map, id_idx_map, layout_object)
|
||||
|
||||
@ -1521,18 +1532,14 @@ class CreateLayoutObjectFor:
|
||||
name: str,
|
||||
os_window_id: int,
|
||||
tab_id: int,
|
||||
margin_width: int,
|
||||
single_window_margin_width: int,
|
||||
padding_width: int,
|
||||
border_width: int,
|
||||
layout_opts: str = ''
|
||||
) -> Layout:
|
||||
key = name, os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts
|
||||
key = name, os_window_id, tab_id, layout_opts
|
||||
ans = create_layout_object_for.cache.get(key)
|
||||
if ans is None:
|
||||
name, layout_opts = name.partition(':')[::2]
|
||||
ans = create_layout_object_for.cache[key] = all_layouts[name](
|
||||
os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts)
|
||||
os_window_id, tab_id, layout_opts)
|
||||
return ans
|
||||
|
||||
|
||||
|
@ -97,37 +97,37 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
|
||||
|
||||
// }}}
|
||||
|
||||
static inline double
|
||||
window_left(Window *w, OSWindow *os_window) {
|
||||
return w->geometry.left - OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0);
|
||||
static inline unsigned int
|
||||
window_left(Window *w) {
|
||||
return w->geometry.left - w->padding.left;
|
||||
}
|
||||
|
||||
static inline double
|
||||
window_right(Window *w, OSWindow *os_window) {
|
||||
return w->geometry.right + OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0);
|
||||
static inline unsigned int
|
||||
window_right(Window *w) {
|
||||
return w->geometry.right + w->padding.right;
|
||||
}
|
||||
|
||||
static inline double
|
||||
window_top(Window *w, OSWindow *os_window) {
|
||||
return w->geometry.top - OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0);
|
||||
static inline unsigned int
|
||||
window_top(Window *w) {
|
||||
return w->geometry.top - w->padding.top;
|
||||
}
|
||||
|
||||
static inline double
|
||||
window_bottom(Window *w, OSWindow *os_window) {
|
||||
return w->geometry.bottom + OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0);
|
||||
static inline unsigned int
|
||||
window_bottom(Window *w) {
|
||||
return w->geometry.bottom + w->padding.bottom;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
contains_mouse(Window *w, OSWindow *os_window) {
|
||||
contains_mouse(Window *w) {
|
||||
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
|
||||
return (w->visible && window_left(w, os_window) <= x && x <= window_right(w, os_window) && window_top(w, os_window) <= y && y <= window_bottom(w, os_window));
|
||||
return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w));
|
||||
}
|
||||
|
||||
static inline double
|
||||
distance_to_window(Window *w, OSWindow *os_window) {
|
||||
distance_to_window(Window *w) {
|
||||
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
|
||||
double cx = (window_left(w, os_window) + window_right(w, os_window)) / 2.0;
|
||||
double cy = (window_top(w, os_window) + window_bottom(w, os_window)) / 2.0;
|
||||
double cx = (window_left(w) + window_right(w)) / 2.0;
|
||||
double cy = (window_top(w) + window_bottom(w)) / 2.0;
|
||||
return (x - cx) * (x - cx) + (y - cy) * (y - cy);
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_
|
||||
bool in_left_half = true;
|
||||
double mouse_x = global_state.callback_os_window->mouse_x;
|
||||
double mouse_y = global_state.callback_os_window->mouse_y;
|
||||
double left = window_left(w, os_window), top = window_top(w, os_window), right = window_right(w, os_window), bottom = window_bottom(w, os_window);
|
||||
double left = window_left(w), top = window_top(w), right = window_right(w), bottom = window_bottom(w);
|
||||
if (clamp_to_window) {
|
||||
mouse_x = MIN(MAX(mouse_x, left), right);
|
||||
mouse_y = MIN(MAX(mouse_y, top), bottom);
|
||||
@ -513,7 +513,7 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
|
||||
if (!*in_tab_bar && global_state.callback_os_window->num_tabs > 0) {
|
||||
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
||||
for (unsigned int i = 0; i < t->num_windows; i++) {
|
||||
if (contains_mouse(t->windows + i, global_state.callback_os_window) && t->windows[i].render_data.screen) {
|
||||
if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) {
|
||||
*window_idx = i; return t->windows + i;
|
||||
}
|
||||
}
|
||||
@ -529,7 +529,7 @@ closest_window_for_event(unsigned int *window_idx) {
|
||||
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
||||
for (unsigned int i = 0; i < t->num_windows; i++) {
|
||||
Window *w = t->windows + i;
|
||||
double d = distance_to_window(w, global_state.callback_os_window);
|
||||
double d = distance_to_window(w);
|
||||
if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ from typing import Generator, List, NamedTuple, Optional, Tuple, Union
|
||||
|
||||
from .cli_stub import CLIOptions
|
||||
from .config_data import to_layout_names
|
||||
from .constants import kitty_exe
|
||||
from .constants import FloatEdges, kitty_exe
|
||||
from .layout import all_layouts
|
||||
from .options_stub import Options
|
||||
from .typing import SpecialWindowInstance
|
||||
@ -19,8 +19,8 @@ class WindowSizeOpts(NamedTuple):
|
||||
|
||||
initial_window_width: Tuple[int, str]
|
||||
initial_window_height: Tuple[int, str]
|
||||
window_margin_width: float
|
||||
window_padding_width: float
|
||||
window_margin_width: FloatEdges
|
||||
window_padding_width: FloatEdges
|
||||
remember_window_size: bool
|
||||
|
||||
|
||||
|
@ -27,13 +27,25 @@ GlobalState global_state = {{0}};
|
||||
#define END_WITH_OS_WINDOW break; }}
|
||||
|
||||
#define WITH_TAB(os_window_id, tab_id) \
|
||||
for (size_t o = 0; o < global_state.num_os_windows; o++) { \
|
||||
for (size_t o = 0, tab_found = 0; o < global_state.num_os_windows && !tab_found; o++) { \
|
||||
OSWindow *osw = global_state.os_windows + o; \
|
||||
if (osw->id == os_window_id) { \
|
||||
for (size_t t = 0; t < osw->num_tabs; t++) { \
|
||||
if (osw->tabs[t].id == tab_id) { \
|
||||
Tab *tab = osw->tabs + t;
|
||||
#define END_WITH_TAB break; }}}}
|
||||
#define END_WITH_TAB tab_found = 1; break; }}}}
|
||||
|
||||
#define WITH_WINDOW(os_window_id, tab_id, window_id) \
|
||||
for (size_t o = 0, window_found = 0; o < global_state.num_os_windows && !window_found; o++) { \
|
||||
OSWindow *osw = global_state.os_windows + o; \
|
||||
if (osw->id == os_window_id) { \
|
||||
for (size_t t = 0; t < osw->num_tabs && !window_found; t++) { \
|
||||
if (osw->tabs[t].id == tab_id) { \
|
||||
Tab *tab = osw->tabs + t; \
|
||||
for (size_t w = 0; w < tab->num_windows; w++) { \
|
||||
Window *window = tab->windows + w;
|
||||
#define END_WITH_WINDOW break; }}}}}
|
||||
|
||||
|
||||
#define WITH_OS_WINDOW_REFS \
|
||||
id_type cb_window_id = 0, focused_window_id = 0; \
|
||||
@ -603,7 +615,6 @@ PYWRAP1(set_options) {
|
||||
S(dim_opacity, PyFloat_AsFloat);
|
||||
S(dynamic_background_opacity, PyObject_IsTrue);
|
||||
S(inactive_text_alpha, PyFloat_AsFloat);
|
||||
S(window_padding_width, PyFloat_AsFloat);
|
||||
S(scrollback_pager_history_size, PyLong_AsUnsignedLong);
|
||||
S(cursor_shape, PyLong_AsLong);
|
||||
S(cursor_beam_thickness, PyFloat_AsFloat);
|
||||
@ -839,6 +850,16 @@ fix_window_idx(Tab *tab, id_type window_id, unsigned int *window_idx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PYWRAP1(set_window_padding) {
|
||||
id_type os_window_id, tab_id, window_id;
|
||||
unsigned int left, top, right, bottom;
|
||||
PA("KKKIIII", &os_window_id, &tab_id, &window_id, &left, &top, &right, &bottom);
|
||||
WITH_WINDOW(os_window_id, tab_id, window_id);
|
||||
window->padding.left = left; window->padding.top = top; window->padding.right = right; window->padding.bottom = bottom;
|
||||
END_WITH_WINDOW;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PYWRAP1(set_window_render_data) {
|
||||
#define A(name) &(d.name)
|
||||
#define B(name) &(g.name)
|
||||
@ -1080,6 +1101,7 @@ static PyMethodDef module_methods[] = {
|
||||
MW(add_borders_rect, METH_VARARGS),
|
||||
MW(set_tab_bar_render_data, METH_VARARGS),
|
||||
MW(set_window_render_data, METH_VARARGS),
|
||||
MW(set_window_padding, METH_VARARGS),
|
||||
MW(viewport_for_window, METH_VARARGS),
|
||||
MW(cell_size_for_window, METH_VARARGS),
|
||||
MW(os_window_has_background_image, METH_VARARGS),
|
||||
|
@ -53,7 +53,6 @@ typedef struct {
|
||||
|
||||
bool dynamic_background_opacity;
|
||||
float inactive_text_alpha;
|
||||
float window_padding_width;
|
||||
Edge tab_bar_edge;
|
||||
unsigned long tab_bar_min_tabs;
|
||||
DisableLigature disable_ligatures;
|
||||
@ -106,6 +105,9 @@ typedef struct {
|
||||
double x, y;
|
||||
bool in_left_half_of_cell;
|
||||
} mouse_pos;
|
||||
struct {
|
||||
unsigned int left, top, right, bottom;
|
||||
} padding;
|
||||
WindowGeometry geometry;
|
||||
ClickQueue click_queue;
|
||||
monotonic_t last_drag_scroll_at;
|
||||
|
@ -17,8 +17,8 @@ from .cli_stub import CLIOptions
|
||||
from .constants import appname, is_macos, is_wayland
|
||||
from .fast_data_types import (
|
||||
add_tab, attach_window, detach_window, get_boss, mark_tab_bar_dirty,
|
||||
next_window_id, pt_to_px, remove_tab, remove_window, ring_bell,
|
||||
set_active_tab, swap_tabs, x11_window_id
|
||||
next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
|
||||
swap_tabs, x11_window_id
|
||||
)
|
||||
from .layout import (
|
||||
Layout, Rect, create_layout_object_for, evict_cached_layouts
|
||||
@ -87,11 +87,10 @@ class Tab: # {{{
|
||||
if not self.id:
|
||||
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id))
|
||||
self.opts, self.args = tab_manager.opts, tab_manager.args
|
||||
self.recalculate_sizes(update_layout=False)
|
||||
self.name = getattr(session_tab, 'name', '')
|
||||
self.enabled_layouts = [x.lower() for x in getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts]
|
||||
self.borders = Borders(self.os_window_id, self.id, self.opts)
|
||||
self.windows: Deque[Window] = deque()
|
||||
self.windows: List[Window] = []
|
||||
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
|
||||
setattr(self, which + '_window', partial(self.nth_window, num=i))
|
||||
self._last_used_layout: Optional[str] = None
|
||||
@ -113,15 +112,6 @@ class Tab: # {{{
|
||||
self._set_current_layout(l0)
|
||||
self.startup(session_tab)
|
||||
|
||||
def recalculate_sizes(self, update_layout: bool = True) -> None:
|
||||
self.margin_width, self.padding_width, self.single_window_margin_width = map(
|
||||
lambda x: pt_to_px(getattr(self.opts, x), self.os_window_id), (
|
||||
'window_margin_width', 'window_padding_width', 'single_window_margin_width'))
|
||||
self.border_width = pt_to_px(self.opts.window_border_width, self.os_window_id)
|
||||
if update_layout and self.current_layout:
|
||||
self.current_layout.update_sizes(
|
||||
self.margin_width, self.single_window_margin_width, self.padding_width, self.border_width)
|
||||
|
||||
def take_over_from(self, other_tab: 'Tab') -> None:
|
||||
self.name, self.cwd = other_tab.name, other_tab.cwd
|
||||
self.enabled_layouts = list(other_tab.enabled_layouts)
|
||||
@ -129,12 +119,12 @@ class Tab: # {{{
|
||||
self._set_current_layout(other_tab._current_layout_name)
|
||||
self._last_used_layout = other_tab._last_used_layout
|
||||
|
||||
orig_windows = deque(other_tab.windows)
|
||||
orig_windows = list(other_tab.windows)
|
||||
orig_history = deque(other_tab.active_window_history)
|
||||
orig_active = other_tab._active_window_idx
|
||||
for window in other_tab.windows:
|
||||
detach_window(other_tab.os_window_id, other_tab.id, window.id)
|
||||
other_tab.windows = deque()
|
||||
other_tab.windows = []
|
||||
other_tab._active_window_idx = 0
|
||||
self.active_window_history = orig_history
|
||||
self.windows = orig_windows
|
||||
@ -243,17 +233,13 @@ class Tab: # {{{
|
||||
self.borders(
|
||||
windows=visible_windows, active_window=w,
|
||||
current_layout=ly, extra_blank_rects=tm.blank_rects,
|
||||
padding_width=self.padding_width, border_width=self.border_width,
|
||||
draw_window_borders=(ly.needs_window_borders and len(visible_windows) > 1) or ly.must_draw_borders
|
||||
)
|
||||
if w is not None:
|
||||
w.change_titlebar_color()
|
||||
|
||||
def create_layout_object(self, name: str) -> Layout:
|
||||
return create_layout_object_for(
|
||||
name, self.os_window_id, self.id, self.margin_width,
|
||||
self.single_window_margin_width, self.padding_width,
|
||||
self.border_width)
|
||||
return create_layout_object_for(name, self.os_window_id, self.id)
|
||||
|
||||
def next_layout(self) -> None:
|
||||
if len(self.enabled_layouts) > 1:
|
||||
@ -559,7 +545,7 @@ class Tab: # {{{
|
||||
evict_cached_layouts(self.id)
|
||||
for w in self.windows:
|
||||
w.destroy()
|
||||
self.windows = deque()
|
||||
self.windows = []
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return 'Tab(title={}, id={})'.format(self.name or self.title, hex(id(self)))
|
||||
@ -653,10 +639,6 @@ class TabManager: # {{{
|
||||
def update_tab_bar_data(self) -> None:
|
||||
self.tab_bar.update(self.tab_bar_data)
|
||||
|
||||
def update_dpi_based_sizes(self) -> None:
|
||||
for tab in self.tabs:
|
||||
tab.recalculate_sizes()
|
||||
|
||||
def resize(self, only_tabs: bool = False) -> None:
|
||||
if not only_tabs:
|
||||
if not self.tab_bar_hidden:
|
||||
|
@ -25,14 +25,15 @@ from .fast_data_types import (
|
||||
MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
|
||||
STRIKETHROUGH, TINT_PROGRAM, Screen, add_window, cell_size_for_window,
|
||||
compile_program, get_boss, get_clipboard_string, init_cell_program,
|
||||
set_clipboard_string, set_titlebar_color, set_window_render_data,
|
||||
update_window_title, update_window_visibility, viewport_for_window
|
||||
pt_to_px, set_clipboard_string, set_titlebar_color, set_window_padding,
|
||||
set_window_render_data, update_window_title, update_window_visibility,
|
||||
viewport_for_window
|
||||
)
|
||||
from .keys import defines, extended_key_event, keyboard_mode_name
|
||||
from .options_stub import Options
|
||||
from .rgb import to_color
|
||||
from .terminfo import get_capabilities
|
||||
from .typing import BossType, ChildType, TabType, TypedDict
|
||||
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
|
||||
from .utils import (
|
||||
color_as_int, get_primary_selection, load_shaders, open_cmd, open_url,
|
||||
parse_color_set, read_shell_environment, sanitize_title,
|
||||
@ -186,12 +187,12 @@ def text_sanitizer(as_ansi: bool, add_wrap_markers: bool) -> Callable[[str], str
|
||||
|
||||
|
||||
class EdgeWidths:
|
||||
left: Optional[int]
|
||||
top: Optional[int]
|
||||
right: Optional[int]
|
||||
bottom: Optional[int]
|
||||
left: Optional[float]
|
||||
top: Optional[float]
|
||||
right: Optional[float]
|
||||
bottom: Optional[float]
|
||||
|
||||
def __init__(self, serialized: Optional[Dict[str, Optional[int]]] = None):
|
||||
def __init__(self, serialized: Optional[Dict[str, Optional[float]]] = None):
|
||||
if serialized is not None:
|
||||
self.left = serialized['left']
|
||||
self.right = serialized['right']
|
||||
@ -200,7 +201,7 @@ class EdgeWidths:
|
||||
else:
|
||||
self.left = self.top = self.right = self.bottom = None
|
||||
|
||||
def serialize(self) -> Dict[str, Optional[int]]:
|
||||
def serialize(self) -> Dict[str, Optional[float]]:
|
||||
return {'left': self.left, 'right': self.right, 'top': self.top, 'bottom': self.bottom}
|
||||
|
||||
|
||||
@ -250,11 +251,47 @@ class Window:
|
||||
else:
|
||||
setup_colors(self.screen, opts)
|
||||
|
||||
def on_dpi_change(self, font_sz: float) -> None:
|
||||
self.update_effective_padding()
|
||||
|
||||
def change_tab(self, tab: TabType) -> None:
|
||||
self.tab_id = tab.id
|
||||
self.os_window_id = tab.os_window_id
|
||||
self.tabref = weakref.ref(tab)
|
||||
|
||||
def effective_margin(self, edge: EdgeLiteral, is_single_window: bool = False) -> int:
|
||||
q = getattr(self.margin, edge)
|
||||
if q is not None:
|
||||
return pt_to_px(q, self.os_window_id)
|
||||
if is_single_window:
|
||||
q = getattr(self.opts.single_window_margin_width, edge)
|
||||
if q > -0.1:
|
||||
return pt_to_px(q, self.os_window_id)
|
||||
q = getattr(self.opts.window_margin_width, edge)
|
||||
return pt_to_px(q, self.os_window_id)
|
||||
|
||||
def effective_padding(self, edge: EdgeLiteral) -> int:
|
||||
q = getattr(self.padding, edge)
|
||||
if q is not None:
|
||||
return pt_to_px(q, self.os_window_id)
|
||||
q = getattr(self.opts.window_padding_width, edge)
|
||||
return pt_to_px(q, self.os_window_id)
|
||||
|
||||
def update_effective_padding(self) -> None:
|
||||
set_window_padding(
|
||||
self.os_window_id, self.tab_id, self.id,
|
||||
self.effective_padding('left'), self.effective_padding('top'),
|
||||
self.effective_padding('right'), self.effective_padding('bottom'))
|
||||
|
||||
def patch_edge_width(self, which: str, edge: EdgeLiteral, val: Optional[float]) -> None:
|
||||
q = self.padding if which == 'padding' else self.margin
|
||||
setattr(q, edge, val)
|
||||
if q is self.padding:
|
||||
self.update_effective_padding()
|
||||
|
||||
def effective_border(self) -> int:
|
||||
return pt_to_px(self.opts.window_border_width, self.os_window_id)
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
return self.override_title or self.child_title
|
||||
@ -362,6 +399,7 @@ class Window:
|
||||
sg = self.update_position(new_geometry)
|
||||
self.geometry = g = new_geometry
|
||||
set_window_render_data(self.os_window_id, self.tab_id, self.id, window_idx, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen, *g[:4])
|
||||
self.update_effective_padding()
|
||||
|
||||
def contains(self, x: int, y: int) -> bool:
|
||||
g = self.geometry
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
from kitty.config import defaults
|
||||
from kitty.constants import WindowGeometry
|
||||
from kitty.fast_data_types import pt_to_px
|
||||
from kitty.layout import Grid, Horizontal, Splits, Stack, Tall, idx_for_id
|
||||
from kitty.window import EdgeWidths
|
||||
|
||||
@ -22,6 +21,15 @@ class Window:
|
||||
self.padding = EdgeWidths()
|
||||
self.margin = EdgeWidths()
|
||||
|
||||
def effective_border(self):
|
||||
return 1
|
||||
|
||||
def effective_padding(self, edge):
|
||||
return 1
|
||||
|
||||
def effective_margin(self, edge, is_single_window=False):
|
||||
return 0 if is_single_window else 1
|
||||
|
||||
def set_visible_in_layout(self, idx, val):
|
||||
self.is_visible_in_layout = bool(val)
|
||||
|
||||
@ -32,8 +40,7 @@ class Window:
|
||||
def create_layout(cls, opts=None, border_width=2):
|
||||
if opts is None:
|
||||
opts = defaults
|
||||
mw, pw = map(pt_to_px, (opts.window_margin_width, opts.window_padding_width))
|
||||
ans = cls(1, 1, mw, mw, pw, border_width)
|
||||
ans = cls(1, 1)
|
||||
ans.set_active_window_in_os_window = lambda idx: None
|
||||
ans.swap_windows_in_os_window = lambda a, b: None
|
||||
return ans
|
||||
|
Loading…
Reference in New Issue
Block a user