diff --git a/kitty/layout/base.py b/kitty/layout/base.py index bc2ddc72b28..4d2be4106f0 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -237,6 +237,9 @@ def calculate_bias_increment_for_a_single_cell(self, all_windows: WindowList, wi def apply_bias(self, window_id: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: return False + def apply_bias_abs(self, window_id: int, bias: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: + return False + def remove_all_biases(self) -> bool: return False @@ -246,6 +249,12 @@ def modify_size_of_window(self, all_windows: WindowList, window_id: int, increme return False return self.apply_bias(idx, increment, all_windows, is_horizontal) + def modify_size_of_window_abs(self, all_windows: WindowList, window_id: int, bias: float, is_horizontal: bool = True) -> bool: + idx = all_windows.group_idx_for_window(window_id) + if idx is None: + return False + return self.apply_bias_abs(idx, bias, all_windows, is_horizontal) + def parse_layout_opts(self, layout_opts: Optional[str] = None) -> LayoutOpts: data: Dict[str, str] = {} if layout_opts: diff --git a/kitty/layout/splits.py b/kitty/layout/splits.py index 71062552a51..05b100efd39 100644 --- a/kitty/layout/splits.py +++ b/kitty/layout/splits.py @@ -18,11 +18,11 @@ class Extent(NamedTuple): class Pair: - def __init__(self, horizontal: bool = True): + def __init__(self, horizontal: bool = True, bias: float = 0.5): self.horizontal = horizontal self.one: Optional[Union[Pair, int]] = None self.two: Optional[Union[Pair, int]] = None - self.bias = 0.5 + self.bias = bias self.top = self.left = self.width = self.height = 0 self.between_borders: List[Edges] = [] self.first_extent = self.second_extent = Extent() @@ -119,17 +119,17 @@ def balanced_add(self, window_id: int) -> 'Pair': q = self.one if one_count < two_count else self.two return q.balanced_add(window_id) if not isinstance(self.one, Pair) and not isinstance(self.two, Pair): - pair = Pair(horizontal=self.horizontal) + pair = Pair(horizontal=self.horizontal, bias=self.bias) pair.balanced_add(self.one) pair.balanced_add(self.two) self.one, self.two = pair, window_id return self if isinstance(self.one, Pair): window_to_be_split = self.two - self.two = pair = Pair(horizontal=self.horizontal) + self.two = pair = Pair(horizontal=self.horizontal, bias=self.bias) else: window_to_be_split = self.one - self.one = pair = Pair(horizontal=self.horizontal) + self.one = pair = Pair(horizontal=self.horizontal, bias=self.bias) assert isinstance(window_to_be_split, int) pair.balanced_add(window_to_be_split) pair.balanced_add(window_id) @@ -142,7 +142,7 @@ def split_and_add(self, existing_window_id: int, new_window_id: int, horizontal: pair.horizontal = horizontal self.one, self.two = q else: - pair = Pair(horizontal=horizontal) + pair = Pair(horizontal=horizontal, bias=self.bias) if self.one == existing_window_id: self.one = pair else: @@ -258,6 +258,21 @@ def modify_size_of_child(self, which: int, increment: float, is_horizontal: bool return parent.modify_size_of_child(which, increment, is_horizontal, layout_object) return False + def set_size_of_child(self, which: int, bias: float, is_horizontal: bool, layout_object: 'Splits') -> bool: + if is_horizontal == self.horizontal and not self.is_redundant: + if which == 2: + bias = 1 - bias + new_bias = max(0.1, min(bias, 0.9)) + if new_bias != self.bias: + self.bias = new_bias + return True + return False + parent = self.parent(layout_object.pairs_root) + if parent is not None: + which = 1 if parent.one is self else 2 + return parent.set_size_of_child(which, bias, is_horizontal, layout_object) + return False + def borders_for_window(self, layout_object: 'Splits', window_id: int) -> Generator[Edges, None, None]: is_first = self.one == window_id if self.between_borders: @@ -379,12 +394,17 @@ def edge_windows(self, edge: str) -> Generator[int, None, None]: class SplitsLayoutOpts(LayoutOpts): default_axis_is_horizontal: bool = True + bias: float = 0.5 def __init__(self, data: Dict[str, str]): self.default_axis_is_horizontal = data.get('split_axis', 'horizontal') == 'horizontal' + self.bias = data.get('bias', 0.5) def serialized(self) -> Dict[str, Any]: - return {'default_axis_is_horizontal': self.default_axis_is_horizontal} + return { + 'default_axis_is_horizontal': self.default_axis_is_horizontal, + 'bias': self.bias, + } class Splits(Layout): @@ -401,7 +421,7 @@ def default_axis_is_horizontal(self) -> bool: def pairs_root(self) -> Pair: root: Optional[Pair] = getattr(self, '_pairs_root', None) if root is None: - self._pairs_root = root = Pair(horizontal=self.default_axis_is_horizontal) + self._pairs_root = root = Pair(horizontal=self.default_axis_is_horizontal, bias=self.layout_opts.bias) return root @pairs_root.setter @@ -485,6 +505,22 @@ def modify_size_of_window( which = 1 if pair.one == grp.id else 2 return pair.modify_size_of_child(which, increment, is_horizontal, self) + def modify_size_of_window_abs( + self, + all_windows: WindowList, + window_id: int, + bias: float, + is_horizontal: bool = True + ) -> bool: + grp = all_windows.group_for_window(window_id) + if grp is None: + return False + pair = self.pairs_root.pair_for_window(grp.id) + if pair is None: + return False + which = 1 if pair.one == grp.id else 2 + return pair.set_size_of_child(which, bias, is_horizontal, self) + def remove_all_biases(self) -> bool: for pair in self.pairs_root.self_and_descendants(): pair.bias = 0.5 @@ -529,6 +565,26 @@ def move_window_to_group(self, all_windows: WindowList, group: int) -> bool: self.pairs_root.swap_windows(before.id, after.id) return moved + def apply_bias(self, window_id: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: + for pair in self.pairs_root.self_and_descendants(): + if pair.one == window_id: + pair.modify_size_of_child(1, increment, is_horizontal, self) + return True + elif pair.two == window_id: + pair.modify_size_of_child(2, increment, is_horizontal, self) + return True + return False + + def apply_bias_abs(self, window_id: int, bias: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: + for pair in self.pairs_root.self_and_descendants(): + if pair.one == window_id: + pair.set_size_of_child(1, bias, is_horizontal, self) + return True + elif pair.two == window_id: + pair.set_size_of_child(2, bias, is_horizontal, self) + return True + return False + def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> Optional[bool]: if action_name == 'rotate': args = args or ('90',) @@ -556,7 +612,7 @@ def layout_action(self, action_name: str, args: Sequence[str], all_windows: Wind wg = all_windows.active_group if wg is not None: self.remove_windows(wg.id) - new_root = Pair(horizontal) + new_root = Pair(horizontal, self.layout_opts.bias) if which in ('left', 'top'): new_root.balanced_add(wg.id) new_root.two = self.pairs_root @@ -565,6 +621,14 @@ def layout_action(self, action_name: str, args: Sequence[str], all_windows: Wind new_root.two = wg.id self.pairs_root = new_root return True + if action_name == 'bias': + try: + bias = float(args[0]) / 100.0 + for pair in self.pairs_root.self_and_descendants(): + pair.set_size_of_child(1, bias, pair.horizontal, self) + return True + except Exception: + return False return None diff --git a/kitty/options/utils.py b/kitty/options/utils.py index fb535c0b0a5..8087af80187 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -226,7 +226,9 @@ def resize_window(func: str, rest: str) -> FuncArgsType: args = ['wider', 1] else: quality = vals[0].lower() - if quality not in ('reset', 'taller', 'shorter', 'wider', 'narrower'): + if quality not in ('reset', + 'taller', 'shorter', 'wider', 'narrower', + 'width', 'height'): log_error(f'Invalid quality specification: {quality}') quality = 'wider' increment = 1 diff --git a/kitty/tabs.py b/kitty/tabs.py index 9740c7c483b..f43f9fa7154 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -388,22 +388,35 @@ def resize_window_by(self, window_id: int, increment: float, is_horizontal: bool return None return 'Could not resize' + def resize_window_to(self, window_id: int, bias: float, is_horizontal: bool) -> Optional[str]: + if self.current_layout.modify_size_of_window_abs(self.windows, window_id, bias, is_horizontal): + self.relayout() + return None + return 'Could not resize' + @ac('win', ''' Resize the active window by the specified amount See :ref:`window_resizing` for details. ''') - def resize_window(self, quality: str, increment: int) -> None: + def resize_window(self, quality: str, quantity: int) -> None: if quality == 'reset': self.reset_window_sizes() return - if increment < 1: - raise ValueError(increment) - is_horizontal = quality in ('wider', 'narrower') - increment *= 1 if quality in ('wider', 'taller') else -1 w = self.active_window - if w is not None and self.resize_window_by( - w.id, increment, is_horizontal) is not None: + if w is None: return + + if quantity < 1: + raise ValueError(quantity) + + if quality in ('width', 'height'): + if self.resize_window_to(w.id, float(quantity) / 100.0, quality == 'width') is not None: + if get_options().enable_audio_bell: ring_bell() + return + + is_horizontal = quality in ('wider', 'narrower') + quantity *= 1 if quality in ('wider', 'taller') else -1 + if self.resize_window_by(w.id, quantity, is_horizontal) is not None: if get_options().enable_audio_bell: ring_bell()