Start work on implement layouts other than Stack

This commit is contained in:
Kovid Goyal 2016-12-04 22:34:31 +05:30
parent 836494a8f0
commit 7cde189bf5
5 changed files with 100 additions and 38 deletions

View File

@ -12,6 +12,7 @@ from .fast_data_types import (
) )
import kitty.fast_data_types as defines import kitty.fast_data_types as defines
from .utils import to_color from .utils import to_color
from .layout import all_layouts
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$') key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
@ -54,13 +55,19 @@ def parse_mods(parts):
return mods return mods
named_keys = {"'": 'APOSTROPHE', ',': 'COMMA', '-': 'MINUS', '.': 'PERIOD',
'/': 'SLASH', ';': 'SEMICOLON', '=': 'EQUAL', '[': 'LEFT_BRACKET',
']': 'RIGHT_BRACKET', '`': 'GRAVE_ACCENT'}
def parse_key(val, keymap): def parse_key(val, keymap):
sc, action = val.partition(' ')[::2] sc, action = val.partition(' ')[::2]
if not sc or not action: if not sc or not action:
return return
parts = sc.split('+') parts = sc.split('+')
mods = parse_mods(parts[:-1]) 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: if key is None:
print('Shortcut: {} has an unknown key, ignoring'.format(val), file=sys.stderr) print('Shortcut: {} has an unknown key, ignoring'.format(val), file=sys.stderr)
return return
@ -71,6 +78,15 @@ def to_open_url_modifiers(val):
return parse_mods(val.split('+')) 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 = { type_map = {
'scrollback_lines': int, 'scrollback_lines': int,
'font_size': to_font_size, 'font_size': to_font_size,
@ -84,6 +100,7 @@ type_map = {
'mouse_hide_wait': float, 'mouse_hide_wait': float,
'cursor_blink_interval': float, 'cursor_blink_interval': float,
'cursor_stop_blinking_after': 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(): for name in 'foreground background cursor active_border_color inactive_border_color selection_foreground selection_background'.split():

View File

@ -44,15 +44,19 @@ wheel_scroll_multiplier 5.0
# The interval between successive clicks to detect double/triple clicks (in seconds) # The interval between successive clicks to detect double/triple clicks (in seconds)
click_interval 0.5 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 # 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 # at the cost of more CPU usage. The default value yields ~100fps which is more
# that sufficient for most uses. # that sufficient for most uses.
repaint_delay 10 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 # The modifier keys to press when clicking with the mouse on URLs to open the URL
open_url_modifiers ctrl+shift open_url_modifiers ctrl+shift
@ -125,6 +129,7 @@ map ctrl+shift+home scroll_home
map ctrl+shift+end scroll_end map ctrl+shift+end scroll_end
# Window management # Window management
map ctrl+shift+n new_window map ctrl+shift+enter new_window
map ctrl+shift+tab next_window map ctrl+shift+] next_window
map ctrl+shift+w close_window map ctrl+shift+w close_window
map ctrl+shift+l next_layout

View File

@ -2,6 +2,8 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from itertools import islice
from .constants import WindowGeometry, viewport_size, cell_size, tab_manager 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: while extra < space_needed_for_border:
number_of_cells -= 1 number_of_cells -= 1
extra = length - number_of_cells * cell_length extra = length - number_of_cells * cell_length
cells_per_window = number_of_cells // number_of_windows
extra -= space_needed_for_border extra -= space_needed_for_border
pos = (extra // 2) + border_length 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 window_length = 2 * border_length + inner_length
extra = number_of_cells - (cells_per_window * number_of_windows)
while number_of_windows > 0: while number_of_windows > 0:
number_of_windows -= 1 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 pos += window_length
@ -41,26 +45,8 @@ class Layout:
return active_window_idx return active_window_idx
def add_window(self, windows, window, active_window_idx): def add_window(self, windows, window, active_window_idx):
raise NotImplementedError() active_window_idx = len(windows)
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):
windows.append(window) windows.append(window)
active_window_idx = len(windows) - 1
self(windows, active_window_idx) self(windows, active_window_idx)
return active_window_idx return active_window_idx
@ -70,14 +56,59 @@ class Stack(Layout):
self(windows, active_window_idx) self(windows, active_window_idx)
return 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): def set_active_window(self, windows, active_window_idx):
for i, w in enumerate(windows): for i, w in enumerate(windows):
w.is_visible_in_layout = i == active_window_idx w.is_visible_in_layout = i == active_window_idx
def __call__(self, windows, active_window_idx): def __call__(self, windows, active_window_idx):
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width)) wg = layout_single_window()
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)
for i, w in enumerate(windows): for i, w in enumerate(windows):
w.is_visible_in_layout = i == active_window_idx w.is_visible_in_layout = i == active_window_idx
w.set_geometry(wg) 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}

View File

@ -12,6 +12,7 @@ from gettext import gettext as _
from .config import load_config from .config import load_config
from .constants import appname, str_version, config_dir, viewport_size from .constants import appname, str_version, config_dir, viewport_size
from .layout import all_layouts
from .tabs import TabManager from .tabs import TabManager
from .shaders import GL_VERSION from .shaders import GL_VERSION
from .fast_data_types import ( 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('--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('--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('--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=_( 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' '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.' ' directly to the program being invoked.'

View File

@ -29,7 +29,7 @@ from .borders import Borders, BordersProgram
from .char_grid import cursor_shader, cell_shader from .char_grid import cursor_shader, cell_shader
from .constants import is_key_pressed from .constants import is_key_pressed
from .keys import interpret_text_event, interpret_key_event, get_shortcut 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 .shaders import Sprites, ShaderProgram
from .timers import Timers from .timers import Timers
from .utils import handle_unix_signals from .utils import handle_unix_signals
@ -66,10 +66,17 @@ class Tab:
def __init__(self, opts, args): def __init__(self, opts, args):
self.opts, self.args = 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.windows = deque()
self.active_window_idx = 0 self.active_window_idx = 0
self.borders = Borders(opts) self.current_layout = self.current_layout(opts, self.borders.border_width)
self.current_layout = Stack(opts, self.borders.border_width)
@property @property
def is_visible(self): def is_visible(self):
@ -91,7 +98,7 @@ class Tab:
def relayout(self): def relayout(self):
if self.windows: if self.windows:
self.current_layout(self.windows, self.active_window_idx) 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): def launch_child(self, use_shell=False):
if use_shell: if use_shell:
@ -112,7 +119,6 @@ class Tab:
def close_window(self): def close_window(self):
if self.windows: if self.windows:
self.remove_window(self.windows[self.active_window_idx]) self.remove_window(self.windows[self.active_window_idx])
glfw_post_empty_event()
def remove_window(self, window): def remove_window(self, window):
self.active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx) 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: with self.sprites:
self.sprites.render_dirty_cells() self.sprites.render_dirty_cells()
tab.render() tab.render()
render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows()} render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows() if not window.needs_layout}
active = self.active_window
with self.cell_program: with self.cell_program:
for window, rd in render_data.items(): for window, rd in render_data.items():
if rd is not None: if rd is not None:
window.char_grid.render_cells(rd, self.cell_program, self.sprites) window.char_grid.render_cells(rd, self.cell_program, self.sprites)
active = self.active_window
rd = render_data.get(active) rd = render_data.get(active)
if rd is not None: if rd is not None:
draw_cursor = True draw_cursor = True