From 3fdf47c5355c6fbd4063e11753488f503a38a1e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 16 May 2018 14:02:58 +0530 Subject: [PATCH] Allow specifying the width of the tall window in the Tall layout as a percentage of available space Also remove the --window-layout option as it was redundant. Same effect can be achieved using -o enabled_layouts=some_layout,* --- README.asciidoc | 11 ++++++++++ kitty/cli.py | 11 ++-------- kitty/config.py | 18 +++++++++++---- kitty/layout.py | 57 ++++++++++++++++++++++++++++++++++++++---------- kitty/session.py | 7 +----- kitty/tabs.py | 13 ++++++----- 6 files changed, 82 insertions(+), 35 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 33729c992..5a46305cf 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -343,6 +343,17 @@ You can switch between layouts using the {sc_next_layout} key combination. You c also create shortcuts to select particular layouts, and choose which layouts you want to enable/disable, see link:kitty/kitty.conf[kitty.conf] for examples. +Some layouts take options to control their behavior. For example, the `fat` and `tall` +layouts accept the `bias` option to control how the available space is split up. To specify the +option, in kitty.conf use: + +``` +enabled_layouts tall:bias=70 +``` + +this will make the tall window occupy `70%` of available width. `bias` can be +any number between 10 and 90. + Writing a new layout only requires about fifty lines of code, so if there is some layout you want, take a look at link:kitty/layout.py[layout.py] and submit a pull request! diff --git a/kitty/cli.py b/kitty/cli.py index 2b0dbc3a7..1e3e49bd2 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -10,7 +10,6 @@ from .config import defaults, load_config from .config_utils import resolve_config from .constants import appname, defconf, is_macos, is_wayland, str_version -from .layout import all_layouts CONFIG_HELP = '''\ Specify a path to the configuration file(s) to use. All configuration files are @@ -71,12 +70,6 @@ Detach from the controlling terminal, if any ---window-layout -type=choices -choices={window_layout_choices} -The window layout to use on startup. - - --session Path to a file containing the startup |_ session| (tabs, windows, layout, programs). See the README file for details and an example. @@ -488,8 +481,8 @@ def parse_cmdline(oc, disabled, args=None): def options_spec(): if not hasattr(options_spec, 'ans'): options_spec.ans = OPTIONS.format( - appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname), - window_layout_choices=', '.join(all_layouts) + appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname) + ) return options_spec.ans diff --git a/kitty/config.py b/kitty/config.py index 9b2cef5f9..e1241047b 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -245,14 +245,24 @@ def to_modifiers(val): return parse_mods(val.split('+'), val) or 0 +def uniq(vals, result_type=list): + seen = set() + seen_add = seen.add + return result_type(x for x in vals if x not in seen and not seen_add(x)) + + def to_layout_names(raw): parts = [x.strip().lower() for x in raw.split(',')] - if '*' in parts: - return sorted(all_layouts) + ans = [] for p in parts: - if p not in all_layouts: + if p == '*': + ans.extend(sorted(all_layouts)) + continue + name = p.partition(':')[0] + if name not in all_layouts: raise ValueError('The window layout {} is unknown'.format(p)) - return parts + ans.append(p) + return uniq(ans) def adjust_line_height(x): diff --git a/kitty/layout.py b/kitty/layout.py index 370543b65..87ec6dc68 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -21,7 +21,7 @@ def idx_for_id(win_id, windows): return i -def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False): +def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False, bias=None): number_of_cells = length // cell_length border_length += padding_length space_needed_for_border = number_of_windows * 2 * border_length @@ -37,8 +37,22 @@ def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_ if not left_align: pos += extra // 2 pos += border_length + margin_length - inner_length = cells_per_window * cell_length - window_length = 2 * (border_length + margin_length) + inner_length + + def calc_window_length(cells_in_window): + inner_length = cells_in_window * cell_length + return 2 * (border_length + margin_length) + inner_length + + if bias is not None and number_of_windows == 2: + cells_per_window = int(bias * number_of_cells) + window_length = calc_window_length(cells_per_window) + yield pos, cells_per_window + pos += window_length + cells_per_window = number_of_cells - cells_per_window + window_length = calc_window_length(cells_per_window) + yield pos, cells_per_window + return + + window_length = calc_window_length(cells_per_window) extra = number_of_cells - (cells_per_window * number_of_windows) while number_of_windows > 0: number_of_windows -= 1 @@ -62,7 +76,7 @@ class Layout: needs_window_borders = True only_active_window_visible = False - def __init__(self, os_window_id, tab_id, opts, border_width): + def __init__(self, os_window_id, tab_id, opts, border_width, layout_opts=''): 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) @@ -74,6 +88,18 @@ def __init__(self, os_window_id, tab_id, opts, border_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 = () + self.layout_opts = self.parse_layout_opts(layout_opts) + self.full_name = self.name + ((':' + layout_opts) if layout_opts else '') + + def parse_layout_opts(self, layout_opts): + if not layout_opts: + return {} + ans = {} + for x in layout_opts.split(';'): + k, v = x.partition('=')[::2] + if k and v: + ans[k] = v + return ans def nth_window(self, all_windows, num, make_active=True): windows = process_overlaid_windows(all_windows)[1] @@ -199,15 +225,15 @@ def __call__(self, all_windows, active_window_idx): return idx_for_id(active_window.id, all_windows) # Utils {{{ - def xlayout(self, num): + def xlayout(self, num, bias=None): return layout_dimension( central.left, central.width, cell_width, num, self.border_width, - margin_length=self.margin_width, padding_length=self.padding_width) + margin_length=self.margin_width, padding_length=self.padding_width, bias=bias) - def ylayout(self, num, left_align=True): + def ylayout(self, num, left_align=True, bias=None): return layout_dimension( central.top, central.height, cell_height, num, self.border_width, left_align=left_align, - margin_length=self.margin_width, padding_length=self.padding_width) + margin_length=self.margin_width, padding_length=self.padding_width, bias=bias) def simple_blank_rects(self, first_window, last_window): br = self.blank_rects @@ -285,6 +311,15 @@ class Tall(Layout): name = 'tall' + def parse_layout_opts(self, layout_opts): + ans = Layout.parse_layout_opts(self, layout_opts) + try: + ans['bias'] = int(ans.get('bias', 50)) / 100 + except Exception: + ans['bias'] = 0.5 + ans['bias'] = max(0.1, min(ans['bias'], 0.9)) + return ans + def do_layout(self, windows, active_window_idx): self.blank_rects = [] if len(windows) == 1: @@ -292,7 +327,7 @@ def do_layout(self, windows, active_window_idx): windows[0].set_geometry(0, wg) self.blank_rects = blank_rects_for_window(windows[0]) return - xlayout = self.xlayout(2) + xlayout = self.xlayout(2, bias=self.layout_opts['bias']) xstart, xnum = next(xlayout) ystart, ynum = next(self.ylayout(1)) windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum)) @@ -311,7 +346,7 @@ def do_layout(self, windows, active_window_idx): self.bottom_blank_rect(windows[0]) -class Fat(Layout): +class Fat(Tall): name = 'fat' @@ -323,7 +358,7 @@ def do_layout(self, windows, active_window_idx): self.blank_rects = blank_rects_for_window(windows[0]) return xstart, xnum = next(self.xlayout(1)) - ylayout = self.ylayout(2) + ylayout = self.ylayout(2, bias=self.layout_opts['bias']) ystart, ynum = next(ylayout) windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum)) xlayout = self.xlayout(len(windows) - 1) diff --git a/kitty/session.py b/kitty/session.py index 8ab79fe46..1c882be38 100644 --- a/kitty/session.py +++ b/kitty/session.py @@ -110,12 +110,7 @@ def create_session(opts, args=None, special_window=None, cwd_from=None, respect_ with open(args.session) as f: return parse_session(f.read(), opts) ans = Session() - if args and args.window_layout: - if args.window_layout not in opts.enabled_layouts: - opts.enabled_layouts.insert(0, args.window_layout) - current_layout = args.window_layout - else: - current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall' + current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall' ans.add_tab(opts) ans.tabs[-1].layout = current_layout if special_window is None: diff --git a/kitty/tabs.py b/kitty/tabs.py index e227d9369..6f7556034 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -137,14 +137,17 @@ def relayout_borders(self): if w is not None: w.change_titlebar_color() - def create_layout_object(self, idx): - return all_layouts[idx](self.os_window_id, self.id, self.opts, self.borders.border_width) + def create_layout_object(self, name): + name, rest = name.partition(':')[::2] + return all_layouts[name](self.os_window_id, self.id, self.opts, self.borders.border_width, rest) def next_layout(self): if len(self.enabled_layouts) > 1: - try: - idx = self.enabled_layouts.index(self.current_layout.name) - except Exception: + for i, layout_name in enumerate(self.enabled_layouts): + if layout_name == self.current_layout.full_name: + idx = i + break + else: idx = -1 nl = self.enabled_layouts[(idx + 1) % len(self.enabled_layouts)] self.current_layout = self.create_layout_object(nl)