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] 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 - Fix reverse video not being rendered correctly when using transparency or a
background image (:iss:`2419`) background image (:iss:`2419`)

View File

@ -6,7 +6,6 @@ from enum import IntFlag
from itertools import chain from itertools import chain
from typing import List, Optional, Sequence, Tuple from typing import List, Optional, Sequence, Tuple
from .constants import WindowGeometry
from .fast_data_types import ( from .fast_data_types import (
BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program, BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
os_window_has_background_image 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: 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: 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: def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], window: WindowType, borders: bool = False) -> None:
left = geometry.left - (width + base_width) geometry = window.geometry
top = geometry.top - (width + base_width) pl, pt = window.effective_padding('left'), window.effective_padding('top')
right = geometry.right + (width + base_width) pr, pb = window.effective_padding('right'), window.effective_padding('bottom')
bottom = geometry.bottom + (width + base_width) left = geometry.left - pl
horizontal_edge(os_window_id, tab_id, colors[1], width, left, right, top) top = geometry.top - pt
horizontal_edge(os_window_id, tab_id, colors[3], width, left, right, geometry.bottom + base_width) lr = geometry.right
vertical_edge(os_window_id, tab_id, colors[0], width, top, bottom, left) right = lr + pr
vertical_edge(os_window_id, tab_id, colors[2], width, top, bottom, geometry.right + base_width) 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: def load_borders_program() -> None:
@ -58,8 +73,6 @@ class Borders:
active_window: Optional[WindowType], active_window: Optional[WindowType],
current_layout: LayoutType, current_layout: LayoutType,
extra_blank_rects: Sequence[Tuple[int, int, int, int]], extra_blank_rects: Sequence[Tuple[int, int, int, int]],
padding_width: int,
border_width: int,
draw_window_borders: bool = True, draw_window_borders: bool = True,
) -> None: ) -> None:
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg) 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): for br in chain(current_layout.blank_rects, extra_blank_rects):
left, top, right, bottom = br left, top, right, bottom = br
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg) add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg)
bw, pw = border_width, padding_width bw = 0
if bw + pw <= 0: if windows:
return bw = windows[0].effective_border()
draw_borders = bw > 0 and draw_window_borders draw_borders = bw > 0 and draw_window_borders
if draw_borders: if draw_borders:
border_data = current_layout.resolve_borders(windows, active_window) border_data = current_layout.resolve_borders(windows, active_window)
for i, w in enumerate(windows): for i, w in enumerate(windows):
g = w.geometry
window_bg = w.screen.color_profile.default_bg window_bg = w.screen.color_profile.default_bg
window_bg = (window_bg << 8) | BorderColor.window_bg window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders: if draw_borders:
@ -86,13 +98,11 @@ class Borders:
else: else:
color = BorderColor.bell if w.needs_attention else BorderColor.inactive color = BorderColor.bell if w.needs_attention else BorderColor.inactive
colors = tuple(color if needed else window_bg for needed in next(border_data)) colors = tuple(color if needed else window_bg for needed in next(border_data))
draw_edges( draw_edges(self.os_window_id, self.tab_id, colors, w, borders=True)
self.os_window_id, self.tab_id, colors, bw, g, base_width=pw) if not has_background_image:
if pw > 0 and not has_background_image:
# Draw the background rectangles over the padding region # Draw the background rectangles over the padding region
colors = (window_bg, window_bg, window_bg, window_bg) colors = window_bg, window_bg, window_bg, window_bg
draw_edges( draw_edges(self.os_window_id, self.tab_id, colors, w)
self.os_window_id, self.tab_id, colors, pw, g)
color = BorderColor.inactive color = BorderColor.inactive
for (left, top, right, bottom) in current_layout.window_independent_borders(windows, active_window): 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) sz = os_window_font_size(os_window_id)
if sz: if sz:
os_window_font_size(os_window_id, sz, True) 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() tm.resize()
def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None: 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 .constants import cache_dir, defconf, is_macos
from .key_names import get_key_name_lookup, key_name_aliases from .key_names import get_key_name_lookup, key_name_aliases
from .options_stub import Options as OptionsStub from .options_stub import Options as OptionsStub
from .typing import TypedDict from .typing import EdgeLiteral, TypedDict
from .utils import log_error from .utils import log_error
KeySpec = Tuple[int, bool, int] 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 # scaling is not needed on Wayland, but is needed on macOS. Not
# sure about X11. # sure about X11.
xscale = yscale = 1 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': 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: else:
width = w width = w
if h_unit == 'cells': 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: else:
height = h height = h
return int(width), int(height) return int(width), int(height)

View File

@ -6,8 +6,8 @@
import os import os
from gettext import gettext as _ from gettext import gettext as _
from typing import ( from typing import (
Any, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set, Tuple, Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set,
TypeVar, Union Tuple, TypeVar, Union
) )
from . import fast_data_types as defines 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, choices, positive_float, positive_int, to_bool, to_cmdline, to_color,
to_color_or_none, unit_float 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 .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts from .layout import all_layouts
from .rgb import Color, color_as_int, color_as_sharp, color_from_int 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. 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. 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=_(''' 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)''')) 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=_(''' 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 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. help for :option:`kitty --listen-on` for more details.
''')) '''))
o( o(
'+env', '', '+env', '',
add_to_default=False, add_to_default=False,

View File

@ -34,6 +34,13 @@ class Edges(NamedTuple):
bottom: int = 0 bottom: int = 0
class FloatEdges(NamedTuple):
left: float = 0
top: float = 0
right: float = 0
bottom: float = 0
class ScreenGeometry(NamedTuple): class ScreenGeometry(NamedTuple):
xstart: float xstart: float
ystart: 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: def key_to_bytes(glfw_key: int, smkx: bool, extended: bool, mods: int, action: int) -> bytes:
pass 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> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from functools import lru_cache, partial from functools import lru_cache, partial
from itertools import islice, repeat from itertools import repeat
from typing import ( from typing import (
Callable, Collection, Deque, Dict, FrozenSet, Generator, Iterable, List, Callable, Collection, Dict, FrozenSet, Generator, Iterable, List,
NamedTuple, Optional, Sequence, Tuple, Union, cast NamedTuple, Optional, Sequence, Tuple, Union, cast
) )
@ -35,6 +35,7 @@ class LayoutData(NamedTuple):
cells_per_window: int cells_per_window: int
space_before: int space_before: int
space_after: int space_after: int
content_size: int
# Utils {{{ # Utils {{{
@ -46,7 +47,7 @@ draw_minimal_borders = False
draw_active_borders = True draw_active_borders = True
align_top_left = False align_top_left = False
DecorationPairs = Sequence[Tuple[int, int]] DecorationPairs = Sequence[Tuple[int, int]]
WindowList = Union[List[WindowType], Deque[WindowType]] WindowList = List[WindowType]
LayoutDimension = Generator[LayoutData, None, None] LayoutDimension = Generator[LayoutData, None, None]
@ -70,41 +71,15 @@ def idx_for_id(win_id: int, windows: Iterable[WindowType]) -> Optional[int]:
return i 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: def set_layout_options(opts: Options) -> None:
global draw_minimal_borders, draw_active_borders, align_top_left 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 draw_active_borders = opts.active_border_color is not None
align_top_left = opts.placement_strategy == 'top-left' align_top_left = opts.placement_strategy == 'top-left'
def layout_dimension( def calculate_cells_map(bias: Optional[Sequence[float]], number_of_windows: int, number_of_cells: int) -> List[int]:
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_per_window = number_of_cells // number_of_windows 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: 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] cells_map = [int(b * number_of_cells) for b in bias]
while min(cells_map) < 5: while min(cells_map) < 5:
@ -115,23 +90,48 @@ def layout_dimension(
cells_map[maxi] -= 1 cells_map[maxi] -= 1
else: else:
cells_map = list(repeat(cells_per_window, number_of_windows)) cells_map = list(repeat(cells_per_window, number_of_windows))
extra = number_of_cells - sum(cells_map) extra = number_of_cells - sum(cells_map)
if extra > 0: if extra > 0:
cells_map[-1] += extra 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 last_i = len(cells_map) - 1
for i, cells_per_window in enumerate(cells_map): 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: if i == 0:
before_space = pos - start_at before_space = pos - start_at
else: else:
before_space = decoration_pairs[i][0] before_space = before_dec
content_size = cells_per_window * cell_length
if i == last_i: if i == last_i:
after_space = (start_at + length) - pos + window_length after_space = (start_at + length) - (pos + content_size)
else: else:
after_space = decoration_pairs[i][1] after_space = after_dec
yield LayoutData(pos, cells_per_window, before_space, after_space) yield LayoutData(pos, cells_per_window, before_space, after_space, content_size)
pos += window_length pos += content_size + after_space
class Rect(NamedTuple): class Rect(NamedTuple):
@ -219,21 +219,11 @@ class Layout: # {{{
layout_opts = LayoutOpts({}) layout_opts = LayoutOpts({})
only_active_window_visible = False only_active_window_visible = False
def __init__( def __init__(self, os_window_id: int, tab_id: int, layout_opts: str = '') -> None:
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:
self.os_window_id = os_window_id self.os_window_id = os_window_id
self.tab_id = tab_id self.tab_id = tab_id
self.set_active_window_in_os_window = partial(set_active_window, os_window_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.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 # 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 # this layout, i.e. spaces that are not covered by any window
self.blank_rects: List[Rect] = [] self.blank_rects: List[Rect] = []
@ -242,19 +232,13 @@ class Layout: # {{{
self.full_name = self.name + ((':' + layout_opts) if layout_opts else '') self.full_name = self.name + ((':' + layout_opts) if layout_opts else '')
self.remove_all_biases() 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: def bias_increment_for_cell(self, is_horizontal: bool) -> float:
self._set_dimensions() self._set_dimensions()
if is_horizontal: if is_horizontal:
return (cell_width + 1) / central.width return (cell_width + 1) / central.width
return (cell_height + 1) / central.height 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 return False
def remove_all_biases(self) -> bool: def remove_all_biases(self) -> bool:
@ -270,7 +254,7 @@ class Layout: # {{{
if idx is None and w.overlay_window_id is not None: if idx is None and w.overlay_window_id is not None:
idx = idx_for_id(w.overlay_window_id, windows) idx = idx_for_id(w.overlay_window_id, windows)
if idx is not None: 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 return False
def parse_layout_opts(self, layout_opts: Optional[str] = None) -> LayoutOpts: 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)) return cast(int, idx_for_id(active_window.id, all_windows))
# Utils {{{ # Utils {{{
def layout_single_window(self, w: WindowType, return_geometry: bool = False, left_align: bool = False) -> Optional[WindowGeometry]: 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 = w.effective_border() if self.must_draw_borders else 0
bw = self.border_width if self.must_draw_borders else 0
xdecoration_pairs = (( xdecoration_pairs = ((
effective_width(w.padding.left, self.padding_width) + effective_width(w.margin.left, default_margin) + bw, w.effective_padding('left') + w.effective_margin('left', is_single_window=True) + bw,
effective_width(w.padding.right, self.padding_width) + effective_width(w.margin.right, default_margin) + bw, w.effective_padding('right') + w.effective_margin('right', is_single_window=True) + bw,
),) ),)
ydecoration_pairs = (( ydecoration_pairs = ((
effective_width(w.padding.top, self.padding_width) + effective_width(w.margin.top, default_margin) + bw, w.effective_padding('top') + w.effective_margin('top', is_single_window=True) + bw,
effective_width(w.padding.bottom, self.padding_width) + effective_width(w.margin.bottom, default_margin) + 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) wg = layout_single_window(xdecoration_pairs, ydecoration_pairs, left_align=left_align)
if return_geometry: if return_geometry:
@ -505,26 +489,44 @@ class Layout: # {{{
return None return None
def xlayout( 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: ) -> LayoutDimension:
decoration = self.margin_width + self.border_width + self.padding_width decoration_pairs = tuple(
decoration_pairs = tuple(repeat((decoration, decoration), num)) (
if left is None: w.effective_margin('left') + w.effective_border() + w.effective_padding('left'),
left = central.left w.effective_margin('right') + w.effective_border() + w.effective_padding('right'),
if width is None: ) for w in windows
width = central.width )
return layout_dimension(left, width, cell_width, decoration_pairs, bias=bias, left_align=align_top_left) 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( 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: ) -> LayoutDimension:
decoration = self.margin_width + self.border_width + self.padding_width decoration_pairs = tuple(
decoration_pairs = tuple(repeat((decoration, decoration), num)) (
if top is None: w.effective_margin('top') + w.effective_border() + w.effective_padding('top'),
top = central.top w.effective_margin('bottom') + w.effective_border() + w.effective_padding('bottom'),
if height is None: ) for w in windows
height = central.height )
return layout_dimension(top, height, cell_height, decoration_pairs, bias=bias, left_align=align_top_left) 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: 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_between_border = Borders(False, False, False, True)
only_main_border = Borders(False, False, True, False) only_main_border = Borders(False, False, True, False)
layout_opts = TallLayoutOpts({}) layout_opts = TallLayoutOpts({})
main_axis_layout = Layout.xlayout
perp_axis_layout = Layout.ylayout
@property @property
def num_full_size_windows(self) -> int: def num_full_size_windows(self) -> int:
@ -636,14 +640,13 @@ class Tall(Layout):
self.biased_map: Dict[int, float] = {} self.biased_map: Dict[int, float] = {}
return True return True
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 -= self.num_full_size_windows windows = windows[self.num_full_size_windows:]
bias = variable_bias(num_windows, biased_map) if num_windows > 1 else None bias = variable_bias(len(windows), biased_map) if len(windows) > 1 else None
if self.main_is_horizontal: return self.perp_axis_layout(windows, bias=bias)
return self.ylayout(num_windows, bias=bias)
return self.xlayout(num_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: if self.main_is_horizontal == is_horizontal:
before_main_bias = self.main_bias before_main_bias = self.main_bias
ncols = self.num_full_size_windows + 1 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: if idx < self.num_full_size_windows or num_of_short_windows < 2:
return False return False
idx -= self.num_full_size_windows 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.) before = self.biased_map.get(idx, 0.)
candidate = self.biased_map.copy() candidate = self.biased_map.copy()
candidate[idx] = after = before + increment 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 return False
self.biased_map = candidate self.biased_map = candidate
return before != after return before != after
@ -671,29 +674,38 @@ class Tall(Layout):
if len(windows) == 1: if len(windows) == 1:
self.layout_single_window(windows[0]) self.layout_single_window(windows[0])
return return
yl = next(self.ylayout(1)) is_fat = not self.main_is_horizontal
if len(windows) <= self.num_full_size_windows: if len(windows) <= self.num_full_size_windows + 1:
bias = normalize_biases(self.main_bias[:-1]) xlayout = self.main_axis_layout(windows, bias=self.main_bias)
xlayout = self.xlayout(self.num_full_size_windows, bias=bias)
for i, (w, xl) in enumerate(zip(windows, xlayout)): for i, (w, xl) in enumerate(zip(windows, xlayout)):
wg = window_geometry_from_layouts(xl, yl) yl = next(self.perp_axis_layout([w]))
w.set_geometry(i, wg) if is_fat:
self.blank_rects.extend(blank_rects_for_window(wg)) xl, yl = yl, xl
self.set_window_geometry(w, i, xl, yl)
return return
xlayout = self.xlayout(self.num_full_size_windows + 1, bias=self.main_bias) xlayout = self.main_axis_layout(windows[:self.num_full_size_windows + 1], bias=self.main_bias)
for i in range(self.num_full_size_windows): attr: EdgeLiteral = 'bottom' if is_fat else 'right'
w = windows[i] 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) xl = next(xlayout)
wg = window_geometry_from_layouts(xl, yl) yl = next(self.perp_axis_layout([w]))
w.set_geometry(i, wg) if is_fat:
self.blank_rects.extend(blank_rects_for_window(wg)) xl, yl = yl, xl
xl = next(xlayout) self.set_window_geometry(w, i, xl, yl)
ylayout = self.variable_layout(len(windows), self.biased_map) start = getattr(w.geometry, attr) + w.effective_border() + w.effective_margin(attr) + w.effective_padding(attr)
for i, (w, yl) in enumerate(zip(islice(windows, self.num_full_size_windows, None), ylayout)): ylayout = self.variable_layout(windows, self.biased_map)
wg = window_geometry_from_layouts(xl, yl) size = (central.height if is_fat else central.width) - start
w.set_geometry(i + self.num_full_size_windows, wg) for i, w in enumerate(windows):
self.blank_rects.extend(blank_rects_for_window(wg)) 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: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
return neighbors_for_tall_window(self.num_full_size_windows, window, windows) return neighbors_for_tall_window(self.num_full_size_windows, window, windows)
@ -726,34 +738,8 @@ class Fat(Tall): # {{{
main_is_horizontal = False main_is_horizontal = False
only_between_border = Borders(False, False, True, False) only_between_border = Borders(False, False, True, False)
only_main_border = Borders(False, False, False, True) only_main_border = Borders(False, False, False, True)
main_axis_layout = Layout.ylayout
def do_layout(self, windows: WindowList, active_window_idx: int) -> None: perp_axis_layout = Layout.xlayout
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))
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
idx = windows.index(window) idx = windows.index(window)
@ -800,11 +786,28 @@ class Grid(Layout):
self.biased_cols: Dict[int, float] = {} self.biased_cols: Dict[int, float] = {}
return True 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: 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) 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 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) ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
def position_for_window_idx(idx: int) -> Tuple[int, int]: def position_for_window_idx(idx: int) -> Tuple[int, int]:
@ -830,8 +833,8 @@ class Grid(Layout):
bias_idx = col_num bias_idx = col_num
attr = 'biased_cols' attr = 'biased_cols'
def layout_func(num_windows: int, bias: Optional[Sequence[float]] = None) -> LayoutDimension: def layout_func(windows: WindowList, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
return self.xlayout(num_windows, bias=bias) return self.column_layout(num_windows, bias=bias)
else: else:
b = self.biased_rows b = self.biased_rows
@ -840,8 +843,8 @@ class Grid(Layout):
bias_idx = row_num bias_idx = row_num
attr = 'biased_rows' attr = 'biased_rows'
def layout_func(num_windows: int, bias: Optional[Sequence[float]] = None) -> LayoutDimension: def layout_func(windows: WindowList, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
return self.xlayout(num_windows, bias=bias) return self.row_layout(num_windows, bias=bias)
before_layout = list(self.variable_layout(layout_func, num_windows, b)) before_layout = list(self.variable_layout(layout_func, num_windows, b))
candidate = b.copy() candidate = b.copy()
@ -860,9 +863,9 @@ class Grid(Layout):
on_col_done: Callable[[List[int]], None] = lambda col_windows: None on_col_done: Callable[[List[int]], None] = lambda col_windows: None
) -> Generator[Tuple[int, LayoutData, LayoutData], None, None]: ) -> Generator[Tuple[int, LayoutData, LayoutData], None, None]:
# Distribute windows top-to-bottom, left-to-right (i.e. in columns) # Distribute windows top-to-bottom, left-to-right (i.e. in columns)
xlayout = self.variable_layout(self.xlayout, ncols, self.biased_cols) xlayout = self.variable_layout(self.column_layout, ncols, self.biased_cols)
yvals_normal = tuple(self.variable_layout(self.ylayout, nrows, self.biased_rows)) 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.ylayout, special_rows, 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 pos = 0
for col in range(ncols): for col in range(ncols):
rows = special_rows if col == special_col else nrows 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] col_windows_w = [windows[i] for i in col_windows]
win_col_map.append(col_windows_w) 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( for window_idx, xl, yl in self.layout_windows(
len(windows), nrows, ncols, special_rows, special_col, on_col_done): len(windows), nrows, ncols, special_rows, special_col, on_col_done):
w = windows[window_idx] w = windows[window_idx]
wg = window_geometry_from_layouts(xl, yl) start, size = extents(xl)
w.set_geometry(window_idx, wg) xl = next(self.xlayout([w], start=start, size=size))
self.blank_rects.extend(blank_rects_for_window(wg)) 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]: def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
n = len(windows) n = len(windows)
@ -981,27 +991,29 @@ class Vertical(Layout): # {{{
name = 'vertical' name = 'vertical'
main_is_horizontal = False main_is_horizontal = False
only_between_border = Borders(False, False, False, True) 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 bias = variable_bias(num_windows, biased_map) if num_windows else None
if self.main_is_horizontal: return self.main_axis_layout(windows, bias=bias)
return self.xlayout(num_windows, bias=bias)
return self.ylayout(num_windows, bias=bias)
def remove_all_biases(self) -> bool: def remove_all_biases(self) -> bool:
self.biased_map: Dict[int, float] = {} self.biased_map: Dict[int, float] = {}
return True 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: if self.main_is_horizontal != is_horizontal:
return False return False
num_windows = len(windows)
if num_windows < 2: if num_windows < 2:
return False 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() candidate = self.biased_map.copy()
before = candidate.get(idx, 0) before = candidate.get(idx, 0)
candidate[idx] = before + increment 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 return False
self.biased_map = candidate self.biased_map = candidate
return True return True
@ -1012,13 +1024,12 @@ class Vertical(Layout): # {{{
self.layout_single_window(windows[0]) self.layout_single_window(windows[0])
return return
xlayout = self.xlayout(1) ylayout = self.variable_layout(windows, self.biased_map)
xl = next(xlayout)
ylayout = self.variable_layout(window_count, self.biased_map)
for i, (w, yl) in enumerate(zip(windows, ylayout)): for i, (w, yl) in enumerate(zip(windows, ylayout)):
wg = window_geometry_from_layouts(xl, yl) xl = next(self.perp_axis_layout([w]))
w.set_geometry(i, wg) if self.main_is_horizontal:
self.blank_rects.extend(blank_rects_for_window(wg)) 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]: 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 last_i = len(windows) - 1
@ -1050,20 +1061,8 @@ class Horizontal(Vertical): # {{{
name = 'horizontal' name = 'horizontal'
main_is_horizontal = True main_is_horizontal = True
only_between_border = Borders(False, False, True, False) only_between_border = Borders(False, False, True, False)
main_axis_layout = Layout.xlayout
def do_layout(self, windows: WindowList, active_window_idx: int) -> None: perp_axis_layout = Layout.ylayout
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))
# }}} # }}}
@ -1199,6 +1198,11 @@ class Pair:
q.set_geometry(id_idx_map[q.id], window_geometry) q.set_geometry(id_idx_map[q.id], window_geometry)
layout_object.blank_rects.extend(blank_rects_for_window(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( def layout_pair(
self, self,
left: int, top: int, width: int, height: int, 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) return q.layout_pair(left, top, width, height, id_window_map, id_idx_map, layout_object)
if q is None: if q is None:
return return
xl = next(layout_object.xlayout(1, left=left, width=width)) w = id_window_map[q]
yl = next(layout_object.ylayout(1, top=top, height=height)) 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) geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(q, geom, id_window_map, id_idx_map, layout_object) self.apply_window_geometry(q, geom, id_window_map, id_idx_map, layout_object)
return 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 b1 = bw // 2
b2 = bw - b1 b2 = bw - b1
if self.horizontal: if self.horizontal:
yl = next(layout_object.ylayout(1, top=top, height=height))
w1 = max(2*cell_width + 1, int(self.bias * width) - b1) w1 = max(2*cell_width + 1, int(self.bias * width) - b1)
w2 = max(2*cell_width + 1, width - w1 - b1 - b2) w2 = max(2*cell_width + 1, width - w1 - b1 - b2)
if isinstance(self.one, Pair): if isinstance(self.one, Pair):
self.one.layout_pair(left, top, w1, height, id_window_map, id_idx_map, layout_object) self.one.layout_pair(left, top, w1, height, id_window_map, id_idx_map, layout_object)
else: 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) geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.one, geom, id_window_map, id_idx_map, layout_object) self.apply_window_geometry(self.one, geom, id_window_map, id_idx_map, layout_object)
if b1 + b2: if b1 + b2:
@ -1237,17 +1243,20 @@ class Pair:
if isinstance(self.two, Pair): if isinstance(self.two, Pair):
self.two.layout_pair(left + w1, top, w2, height, id_window_map, id_idx_map, layout_object) self.two.layout_pair(left + w1, top, w2, height, id_window_map, id_idx_map, layout_object)
else: 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) geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.two, geom, id_window_map, id_idx_map, layout_object) self.apply_window_geometry(self.two, geom, id_window_map, id_idx_map, layout_object)
else: else:
xl = next(layout_object.xlayout(1, left=left, width=width))
h1 = max(2*cell_height + 1, int(self.bias * height) - b1) h1 = max(2*cell_height + 1, int(self.bias * height) - b1)
h2 = max(2*cell_height + 1, height - h1 - b1 - b2) h2 = max(2*cell_height + 1, height - h1 - b1 - b2)
if isinstance(self.one, Pair): if isinstance(self.one, Pair):
self.one.layout_pair(left, top, width, h1, id_window_map, id_idx_map, layout_object) self.one.layout_pair(left, top, width, h1, id_window_map, id_idx_map, layout_object)
else: 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) geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.one, geom, id_window_map, id_idx_map, layout_object) self.apply_window_geometry(self.one, geom, id_window_map, id_idx_map, layout_object)
if b1 + b2: if b1 + b2:
@ -1256,7 +1265,9 @@ class Pair:
if isinstance(self.two, Pair): if isinstance(self.two, Pair):
self.two.layout_pair(left, top + h1, width, h2, id_window_map, id_idx_map, layout_object) self.two.layout_pair(left, top + h1, width, h2, id_window_map, id_idx_map, layout_object)
else: 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) geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.two, geom, id_window_map, id_idx_map, layout_object) self.apply_window_geometry(self.two, geom, id_window_map, id_idx_map, layout_object)
@ -1521,18 +1532,14 @@ class CreateLayoutObjectFor:
name: str, name: str,
os_window_id: int, os_window_id: int,
tab_id: int, tab_id: int,
margin_width: int,
single_window_margin_width: int,
padding_width: int,
border_width: int,
layout_opts: str = '' layout_opts: str = ''
) -> Layout: ) -> 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) ans = create_layout_object_for.cache.get(key)
if ans is None: if ans is None:
name, layout_opts = name.partition(':')[::2] name, layout_opts = name.partition(':')[::2]
ans = create_layout_object_for.cache[key] = all_layouts[name]( 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 return ans

View File

@ -97,37 +97,37 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
// }}} // }}}
static inline double static inline unsigned int
window_left(Window *w, OSWindow *os_window) { window_left(Window *w) {
return w->geometry.left - OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0); return w->geometry.left - w->padding.left;
} }
static inline double static inline unsigned int
window_right(Window *w, OSWindow *os_window) { window_right(Window *w) {
return w->geometry.right + OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0); return w->geometry.right + w->padding.right;
} }
static inline double static inline unsigned int
window_top(Window *w, OSWindow *os_window) { window_top(Window *w) {
return w->geometry.top - OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0); return w->geometry.top - w->padding.top;
} }
static inline double static inline unsigned int
window_bottom(Window *w, OSWindow *os_window) { window_bottom(Window *w) {
return w->geometry.bottom + OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0); return w->geometry.bottom + w->padding.bottom;
} }
static inline bool 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; 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 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 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 cx = (window_left(w) + window_right(w)) / 2.0;
double cy = (window_top(w, os_window) + window_bottom(w, os_window)) / 2.0; double cy = (window_top(w) + window_bottom(w)) / 2.0;
return (x - cx) * (x - cx) + (y - cy) * (y - cy); 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; bool in_left_half = true;
double mouse_x = global_state.callback_os_window->mouse_x; double mouse_x = global_state.callback_os_window->mouse_x;
double mouse_y = global_state.callback_os_window->mouse_y; 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) { if (clamp_to_window) {
mouse_x = MIN(MAX(mouse_x, left), right); mouse_x = MIN(MAX(mouse_x, left), right);
mouse_y = MIN(MAX(mouse_y, top), bottom); 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) { 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; 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++) { 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; *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; 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++) { for (unsigned int i = 0; i < t->num_windows; i++) {
Window *w = t->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; } 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 .cli_stub import CLIOptions
from .config_data import to_layout_names from .config_data import to_layout_names
from .constants import kitty_exe from .constants import FloatEdges, kitty_exe
from .layout import all_layouts from .layout import all_layouts
from .options_stub import Options from .options_stub import Options
from .typing import SpecialWindowInstance from .typing import SpecialWindowInstance
@ -19,8 +19,8 @@ class WindowSizeOpts(NamedTuple):
initial_window_width: Tuple[int, str] initial_window_width: Tuple[int, str]
initial_window_height: Tuple[int, str] initial_window_height: Tuple[int, str]
window_margin_width: float window_margin_width: FloatEdges
window_padding_width: float window_padding_width: FloatEdges
remember_window_size: bool remember_window_size: bool

View File

@ -27,13 +27,25 @@ GlobalState global_state = {{0}};
#define END_WITH_OS_WINDOW break; }} #define END_WITH_OS_WINDOW break; }}
#define WITH_TAB(os_window_id, tab_id) \ #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; \ OSWindow *osw = global_state.os_windows + o; \
if (osw->id == os_window_id) { \ if (osw->id == os_window_id) { \
for (size_t t = 0; t < osw->num_tabs; t++) { \ for (size_t t = 0; t < osw->num_tabs; t++) { \
if (osw->tabs[t].id == tab_id) { \ if (osw->tabs[t].id == tab_id) { \
Tab *tab = osw->tabs + t; 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 \ #define WITH_OS_WINDOW_REFS \
id_type cb_window_id = 0, focused_window_id = 0; \ id_type cb_window_id = 0, focused_window_id = 0; \
@ -603,7 +615,6 @@ PYWRAP1(set_options) {
S(dim_opacity, PyFloat_AsFloat); S(dim_opacity, PyFloat_AsFloat);
S(dynamic_background_opacity, PyObject_IsTrue); S(dynamic_background_opacity, PyObject_IsTrue);
S(inactive_text_alpha, PyFloat_AsFloat); S(inactive_text_alpha, PyFloat_AsFloat);
S(window_padding_width, PyFloat_AsFloat);
S(scrollback_pager_history_size, PyLong_AsUnsignedLong); S(scrollback_pager_history_size, PyLong_AsUnsignedLong);
S(cursor_shape, PyLong_AsLong); S(cursor_shape, PyLong_AsLong);
S(cursor_beam_thickness, PyFloat_AsFloat); 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; 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) { PYWRAP1(set_window_render_data) {
#define A(name) &(d.name) #define A(name) &(d.name)
#define B(name) &(g.name) #define B(name) &(g.name)
@ -1080,6 +1101,7 @@ static PyMethodDef module_methods[] = {
MW(add_borders_rect, METH_VARARGS), MW(add_borders_rect, METH_VARARGS),
MW(set_tab_bar_render_data, METH_VARARGS), MW(set_tab_bar_render_data, METH_VARARGS),
MW(set_window_render_data, METH_VARARGS), MW(set_window_render_data, METH_VARARGS),
MW(set_window_padding, METH_VARARGS),
MW(viewport_for_window, METH_VARARGS), MW(viewport_for_window, METH_VARARGS),
MW(cell_size_for_window, METH_VARARGS), MW(cell_size_for_window, METH_VARARGS),
MW(os_window_has_background_image, METH_VARARGS), MW(os_window_has_background_image, METH_VARARGS),

View File

@ -53,7 +53,6 @@ typedef struct {
bool dynamic_background_opacity; bool dynamic_background_opacity;
float inactive_text_alpha; float inactive_text_alpha;
float window_padding_width;
Edge tab_bar_edge; Edge tab_bar_edge;
unsigned long tab_bar_min_tabs; unsigned long tab_bar_min_tabs;
DisableLigature disable_ligatures; DisableLigature disable_ligatures;
@ -106,6 +105,9 @@ typedef struct {
double x, y; double x, y;
bool in_left_half_of_cell; bool in_left_half_of_cell;
} mouse_pos; } mouse_pos;
struct {
unsigned int left, top, right, bottom;
} padding;
WindowGeometry geometry; WindowGeometry geometry;
ClickQueue click_queue; ClickQueue click_queue;
monotonic_t last_drag_scroll_at; 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 .constants import appname, is_macos, is_wayland
from .fast_data_types import ( from .fast_data_types import (
add_tab, attach_window, detach_window, get_boss, mark_tab_bar_dirty, add_tab, attach_window, detach_window, get_boss, mark_tab_bar_dirty,
next_window_id, pt_to_px, remove_tab, remove_window, ring_bell, next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
set_active_tab, swap_tabs, x11_window_id swap_tabs, x11_window_id
) )
from .layout import ( from .layout import (
Layout, Rect, create_layout_object_for, evict_cached_layouts Layout, Rect, create_layout_object_for, evict_cached_layouts
@ -87,11 +87,10 @@ class Tab: # {{{
if not self.id: if not self.id:
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_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.opts, self.args = tab_manager.opts, tab_manager.args
self.recalculate_sizes(update_layout=False)
self.name = getattr(session_tab, 'name', '') 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.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.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()): 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)) setattr(self, which + '_window', partial(self.nth_window, num=i))
self._last_used_layout: Optional[str] = None self._last_used_layout: Optional[str] = None
@ -113,15 +112,6 @@ class Tab: # {{{
self._set_current_layout(l0) self._set_current_layout(l0)
self.startup(session_tab) 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: def take_over_from(self, other_tab: 'Tab') -> None:
self.name, self.cwd = other_tab.name, other_tab.cwd self.name, self.cwd = other_tab.name, other_tab.cwd
self.enabled_layouts = list(other_tab.enabled_layouts) self.enabled_layouts = list(other_tab.enabled_layouts)
@ -129,12 +119,12 @@ class Tab: # {{{
self._set_current_layout(other_tab._current_layout_name) self._set_current_layout(other_tab._current_layout_name)
self._last_used_layout = other_tab._last_used_layout 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_history = deque(other_tab.active_window_history)
orig_active = other_tab._active_window_idx orig_active = other_tab._active_window_idx
for window in other_tab.windows: for window in other_tab.windows:
detach_window(other_tab.os_window_id, other_tab.id, window.id) 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 other_tab._active_window_idx = 0
self.active_window_history = orig_history self.active_window_history = orig_history
self.windows = orig_windows self.windows = orig_windows
@ -243,17 +233,13 @@ class Tab: # {{{
self.borders( self.borders(
windows=visible_windows, active_window=w, windows=visible_windows, active_window=w,
current_layout=ly, extra_blank_rects=tm.blank_rects, 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 draw_window_borders=(ly.needs_window_borders and len(visible_windows) > 1) or ly.must_draw_borders
) )
if w is not None: if w is not None:
w.change_titlebar_color() w.change_titlebar_color()
def create_layout_object(self, name: str) -> Layout: def create_layout_object(self, name: str) -> Layout:
return create_layout_object_for( return create_layout_object_for(name, self.os_window_id, self.id)
name, self.os_window_id, self.id, self.margin_width,
self.single_window_margin_width, self.padding_width,
self.border_width)
def next_layout(self) -> None: def next_layout(self) -> None:
if len(self.enabled_layouts) > 1: if len(self.enabled_layouts) > 1:
@ -559,7 +545,7 @@ class Tab: # {{{
evict_cached_layouts(self.id) evict_cached_layouts(self.id)
for w in self.windows: for w in self.windows:
w.destroy() w.destroy()
self.windows = deque() self.windows = []
def __repr__(self) -> str: def __repr__(self) -> str:
return 'Tab(title={}, id={})'.format(self.name or self.title, hex(id(self))) 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: def update_tab_bar_data(self) -> None:
self.tab_bar.update(self.tab_bar_data) 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: def resize(self, only_tabs: bool = False) -> None:
if not only_tabs: if not only_tabs:
if not self.tab_bar_hidden: 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, MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
STRIKETHROUGH, TINT_PROGRAM, Screen, add_window, cell_size_for_window, STRIKETHROUGH, TINT_PROGRAM, Screen, add_window, cell_size_for_window,
compile_program, get_boss, get_clipboard_string, init_cell_program, compile_program, get_boss, get_clipboard_string, init_cell_program,
set_clipboard_string, set_titlebar_color, set_window_render_data, pt_to_px, set_clipboard_string, set_titlebar_color, set_window_padding,
update_window_title, update_window_visibility, viewport_for_window set_window_render_data, update_window_title, update_window_visibility,
viewport_for_window
) )
from .keys import defines, extended_key_event, keyboard_mode_name from .keys import defines, extended_key_event, keyboard_mode_name
from .options_stub import Options from .options_stub import Options
from .rgb import to_color from .rgb import to_color
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .typing import BossType, ChildType, TabType, TypedDict from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
from .utils import ( from .utils import (
color_as_int, get_primary_selection, load_shaders, open_cmd, open_url, color_as_int, get_primary_selection, load_shaders, open_cmd, open_url,
parse_color_set, read_shell_environment, sanitize_title, 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: class EdgeWidths:
left: Optional[int] left: Optional[float]
top: Optional[int] top: Optional[float]
right: Optional[int] right: Optional[float]
bottom: Optional[int] 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: if serialized is not None:
self.left = serialized['left'] self.left = serialized['left']
self.right = serialized['right'] self.right = serialized['right']
@ -200,7 +201,7 @@ class EdgeWidths:
else: else:
self.left = self.top = self.right = self.bottom = None 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} return {'left': self.left, 'right': self.right, 'top': self.top, 'bottom': self.bottom}
@ -250,11 +251,47 @@ class Window:
else: else:
setup_colors(self.screen, opts) 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: def change_tab(self, tab: TabType) -> None:
self.tab_id = tab.id self.tab_id = tab.id
self.os_window_id = tab.os_window_id self.os_window_id = tab.os_window_id
self.tabref = weakref.ref(tab) 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 @property
def title(self) -> str: def title(self) -> str:
return self.override_title or self.child_title return self.override_title or self.child_title
@ -362,6 +399,7 @@ class Window:
sg = self.update_position(new_geometry) sg = self.update_position(new_geometry)
self.geometry = g = 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]) 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: def contains(self, x: int, y: int) -> bool:
g = self.geometry g = self.geometry

View File

@ -4,7 +4,6 @@
from kitty.config import defaults from kitty.config import defaults
from kitty.constants import WindowGeometry 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.layout import Grid, Horizontal, Splits, Stack, Tall, idx_for_id
from kitty.window import EdgeWidths from kitty.window import EdgeWidths
@ -22,6 +21,15 @@ class Window:
self.padding = EdgeWidths() self.padding = EdgeWidths()
self.margin = 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): def set_visible_in_layout(self, idx, val):
self.is_visible_in_layout = bool(val) self.is_visible_in_layout = bool(val)
@ -32,8 +40,7 @@ class Window:
def create_layout(cls, opts=None, border_width=2): def create_layout(cls, opts=None, border_width=2):
if opts is None: if opts is None:
opts = defaults opts = defaults
mw, pw = map(pt_to_px, (opts.window_margin_width, opts.window_padding_width)) ans = cls(1, 1)
ans = cls(1, 1, mw, mw, pw, border_width)
ans.set_active_window_in_os_window = lambda idx: None ans.set_active_window_in_os_window = lambda idx: None
ans.swap_windows_in_os_window = lambda a, b: None ans.swap_windows_in_os_window = lambda a, b: None
return ans return ans