From 7cde189bf5239b0b33be2f863659789bacdb612c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 4 Dec 2016 22:34:31 +0530 Subject: [PATCH] Start work on implement layouts other than Stack --- kitty/config.py | 19 +++++++++++- kitty/kitty.conf | 17 +++++++---- kitty/layout.py | 79 +++++++++++++++++++++++++++++++++--------------- kitty/main.py | 3 ++ kitty/tabs.py | 20 +++++++----- 5 files changed, 100 insertions(+), 38 deletions(-) diff --git a/kitty/config.py b/kitty/config.py index fc1830495..9bf6b491c 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -12,6 +12,7 @@ from .fast_data_types import ( ) import kitty.fast_data_types as defines from .utils import to_color +from .layout import all_layouts key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$') @@ -54,13 +55,19 @@ def parse_mods(parts): return mods +named_keys = {"'": 'APOSTROPHE', ',': 'COMMA', '-': 'MINUS', '.': 'PERIOD', + '/': 'SLASH', ';': 'SEMICOLON', '=': 'EQUAL', '[': 'LEFT_BRACKET', + ']': 'RIGHT_BRACKET', '`': 'GRAVE_ACCENT'} + + def parse_key(val, keymap): sc, action = val.partition(' ')[::2] if not sc or not action: return parts = sc.split('+') mods = parse_mods(parts[:-1]) - key = getattr(defines, 'GLFW_KEY_' + parts[-1].upper(), None) + key = parts[-1].upper() + key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None) if key is None: print('Shortcut: {} has an unknown key, ignoring'.format(val), file=sys.stderr) return @@ -71,6 +78,15 @@ def to_open_url_modifiers(val): return parse_mods(val.split('+')) +def to_layout_names(raw): + parts = [x.strip().lower() for x in raw.split(',')] + if '*' in parts: + return sorted(all_layouts) + for p in parts: + if p not in all_layouts: + raise ValueError('The window layout {} is unknown'.format(p)) + + type_map = { 'scrollback_lines': int, 'font_size': to_font_size, @@ -84,6 +100,7 @@ type_map = { 'mouse_hide_wait': float, 'cursor_blink_interval': float, 'cursor_stop_blinking_after': float, + 'enabled_layouts': to_layout_names, } for name in 'foreground background cursor active_border_color inactive_border_color selection_foreground selection_background'.split(): diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 232cce64c..1cd9e6548 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -44,15 +44,19 @@ wheel_scroll_multiplier 5.0 # The interval between successive clicks to detect double/triple clicks (in seconds) click_interval 0.5 +# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to +# zero or a negative number to disable mouse cursor hiding. +mouse_hide_wait 3.0 + +# The enabled window layouts. A comma separated list of layout names. The special value * means +# all layouts. For a list of available layouts, see the file layouts.py +enabled_layouts * + # Delay (in milliseconds) between screen updates. Decreasing it, increases fps # at the cost of more CPU usage. The default value yields ~100fps which is more # that sufficient for most uses. repaint_delay 10 -# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to -# zero or a negative number to disable mouse cursor hiding. -mouse_hide_wait 3.0 - # The modifier keys to press when clicking with the mouse on URLs to open the URL open_url_modifiers ctrl+shift @@ -125,6 +129,7 @@ map ctrl+shift+home scroll_home map ctrl+shift+end scroll_end # Window management -map ctrl+shift+n new_window -map ctrl+shift+tab next_window +map ctrl+shift+enter new_window +map ctrl+shift+] next_window map ctrl+shift+w close_window +map ctrl+shift+l next_layout diff --git a/kitty/layout.py b/kitty/layout.py index bd5ed6e33..911148d1c 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -2,6 +2,8 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +from itertools import islice + from .constants import WindowGeometry, viewport_size, cell_size, tab_manager @@ -16,13 +18,15 @@ def layout_dimension(length, cell_length, number_of_windows=1, border_length=0): while extra < space_needed_for_border: number_of_cells -= 1 extra = length - number_of_cells * cell_length + cells_per_window = number_of_cells // number_of_windows extra -= space_needed_for_border pos = (extra // 2) + border_length - inner_length = number_of_cells * cell_length + inner_length = cells_per_window * cell_length window_length = 2 * border_length + inner_length + extra = number_of_cells - (cells_per_window * number_of_windows) while number_of_windows > 0: number_of_windows -= 1 - yield pos, number_of_cells + yield pos, cells_per_window + (extra if number_of_windows == 0 else 0) pos += window_length @@ -41,26 +45,8 @@ class Layout: return active_window_idx def add_window(self, windows, window, active_window_idx): - raise NotImplementedError() - - def remove_window(self, windows, window, active_window_idx): - raise NotImplementedError() - - def set_active_window(self, windows, active_window_idx): - raise NotImplementedError() - - def __call__(self, windows, active_window_idx): - raise NotImplementedError() - - -class Stack(Layout): - - name = 'stack' - needs_window_borders = False - - def add_window(self, windows, window, active_window_idx): + active_window_idx = len(windows) windows.append(window) - active_window_idx = len(windows) - 1 self(windows, active_window_idx) return active_window_idx @@ -70,14 +56,59 @@ class Stack(Layout): self(windows, active_window_idx) return active_window_idx + def set_active_window(self, windows, active_window_idx): + pass + + def __call__(self, windows, active_window_idx): + raise NotImplementedError() + + +def window_geometry(xstart, xnum, ystart, ynum): + return WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_size.width * xnum, bottom=ystart + cell_size.height * ynum) + + +def layout_single_window(): + xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width)) + ystart, ynum = next(layout_dimension(available_height(), cell_size.height)) + return window_geometry(xstart, xnum, ystart, ynum) + + +class Stack(Layout): + + name = 'stack' + needs_window_borders = False + def set_active_window(self, windows, active_window_idx): for i, w in enumerate(windows): w.is_visible_in_layout = i == active_window_idx def __call__(self, windows, active_window_idx): - xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width)) - ystart, ynum = next(layout_dimension(available_height(), cell_size.height)) - wg = WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_size.width * xnum, bottom=ystart + cell_size.height * ynum) + wg = layout_single_window() for i, w in enumerate(windows): w.is_visible_in_layout = i == active_window_idx w.set_geometry(wg) + + +class Tall(Layout): + + name = 'tall' + + def set_active_window(self, windows, active_window_idx): + pass + + def __call__(self, windows, active_window_idx): + if len(windows) == 1: + wg = layout_single_window() + windows[0].set_geometry(wg) + return + xlayout = layout_dimension(viewport_size.width, cell_size.width, 2, self.border_width) + xstart, xnum = next(xlayout) + ystart, ynum = next(layout_dimension(available_height(), cell_size.height, 1, self.border_width)) + windows[0].set_geometry(window_geometry(xstart, xnum, ystart, ynum)) + xstart, xnum = next(xlayout) + ylayout = layout_dimension(available_height(), cell_size.height, len(windows) - 1, self.border_width) + for w, (ystart, ynum) in zip(islice(windows, 1, None), ylayout): + w.set_geometry(window_geometry(xstart, xnum, ystart, ynum)) + + +all_layouts = {o.name: o for o in globals().values() if isinstance(o, type) and issubclass(o, Layout) and o is not Layout} diff --git a/kitty/main.py b/kitty/main.py index cd5fa960d..ec35cf710 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -12,6 +12,7 @@ from gettext import gettext as _ from .config import load_config from .constants import appname, str_version, config_dir, viewport_size +from .layout import all_layouts from .tabs import TabManager from .shaders import GL_VERSION from .fast_data_types import ( @@ -36,6 +37,8 @@ def option_parser(): a('--profile', action='store_true', default=False, help=_('Show profiling data after exit')) a('--dump-commands', action='store_true', default=False, help=_('Output commands received from child process to stdout')) a('--replay-commands', default=None, help=_('Replay previously dumped commands')) + a('--window-layout', default=None, choices=frozenset(all_layouts.keys()), help=_( + 'The window layout to use on startup. Choices: {}').format(', '.join(all_layouts))) a('args', nargs=argparse.REMAINDER, help=_( 'The remaining arguments are used to launch a program other than the default shell. Any further options are passed' ' directly to the program being invoked.' diff --git a/kitty/tabs.py b/kitty/tabs.py index 3e49e4338..d1dff709b 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -29,7 +29,7 @@ from .borders import Borders, BordersProgram from .char_grid import cursor_shader, cell_shader from .constants import is_key_pressed from .keys import interpret_text_event, interpret_key_event, get_shortcut -from .layout import Stack +from .layout import all_layouts from .shaders import Sprites, ShaderProgram from .timers import Timers from .utils import handle_unix_signals @@ -66,10 +66,17 @@ class Tab: def __init__(self, opts, args): self.opts, self.args = opts, args + self.enabled_layouts = opts.enabled_layouts + self.borders = Borders(opts) + if args.window_layout: + if args.window_layout not in self.enabled_layouts: + self.enabled_layouts.insert(0, args.window_layout) + self.current_layout = all_layouts[args.window_layout] + else: + self.current_layout = all_layouts[self.enabled_layouts[0]] self.windows = deque() self.active_window_idx = 0 - self.borders = Borders(opts) - self.current_layout = Stack(opts, self.borders.border_width) + self.current_layout = self.current_layout(opts, self.borders.border_width) @property def is_visible(self): @@ -91,7 +98,7 @@ class Tab: def relayout(self): if self.windows: self.current_layout(self.windows, self.active_window_idx) - self.borders(self.windows, self.active_window, self.current_layout.needs_window_borders) + self.borders(self.windows, self.active_window, self.current_layout.needs_window_borders and len(self.windows) > 1) def launch_child(self, use_shell=False): if use_shell: @@ -112,7 +119,6 @@ class Tab: def close_window(self): if self.windows: self.remove_window(self.windows[self.active_window_idx]) - glfw_post_empty_event() def remove_window(self, window): self.active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx) @@ -442,12 +448,12 @@ class TabManager(Thread): with self.sprites: self.sprites.render_dirty_cells() tab.render() - render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows()} - active = self.active_window + render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows() if not window.needs_layout} with self.cell_program: for window, rd in render_data.items(): if rd is not None: window.char_grid.render_cells(rd, self.cell_program, self.sprites) + active = self.active_window rd = render_data.get(active) if rd is not None: draw_cursor = True