Allow individually setting margins and padding for each edge (left, right, top, bottom)

This commit is contained in:
Kovid Goyal 2020-04-19 09:36:26 +05:30
parent 85b55b31b6
commit c69b8870d2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
15 changed files with 411 additions and 290 deletions

View File

@ -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`)

View File

@ -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):

View File

@ -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:

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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; }
}
}

View File

@ -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

View File

@ -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),

View File

@ -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;

View File

@ -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:

View File

@ -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

View File

@ -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