Support for overlay windows in the layout manager
This commit is contained in:
143
kitty/layout.py
143
kitty/layout.py
@@ -3,15 +3,24 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from collections import namedtuple
|
||||
from functools import partial
|
||||
from itertools import islice
|
||||
|
||||
from .constants import WindowGeometry
|
||||
from .fast_data_types import pt_to_px, viewport_for_window, Region
|
||||
from .fast_data_types import (
|
||||
Region, pt_to_px, set_active_window, swap_windows, viewport_for_window
|
||||
)
|
||||
|
||||
central = Region((0, 0, 199, 199, 200, 200))
|
||||
cell_width = cell_height = 20
|
||||
|
||||
|
||||
def idx_for_id(win_id, windows):
|
||||
for i, w in enumerate(windows):
|
||||
if w.id == win_id:
|
||||
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):
|
||||
number_of_cells = length // cell_length
|
||||
border_length += padding_length
|
||||
@@ -40,14 +49,24 @@ def layout_dimension(start_at, length, cell_length, number_of_windows=1, border_
|
||||
Rect = namedtuple('Rect', 'left top right bottom')
|
||||
|
||||
|
||||
def process_overlaid_windows(all_windows):
|
||||
id_map = {w.id: w for w in all_windows}
|
||||
overlaid_windows = frozenset(w for w in all_windows if w.overlay_window_id is not None and w.overlay_window_id in id_map)
|
||||
windows = [w for w in all_windows if w not in overlaid_windows]
|
||||
return overlaid_windows, windows
|
||||
|
||||
|
||||
class Layout:
|
||||
|
||||
name = None
|
||||
needs_window_borders = True
|
||||
only_active_window_visible = False
|
||||
|
||||
def __init__(self, os_window_id, opts, border_width, windows):
|
||||
def __init__(self, os_window_id, tab_id, opts, border_width):
|
||||
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)
|
||||
self.swap_windows_in_os_window = partial(swap_windows, os_window_id, tab_id)
|
||||
self.opts = opts
|
||||
self.border_width = border_width
|
||||
self.margin_width = pt_to_px(opts.window_margin_width)
|
||||
@@ -56,24 +75,108 @@ class Layout:
|
||||
# this layout, i.e. spaces that are not covered by any window
|
||||
self.blank_rects = ()
|
||||
|
||||
def next_window(self, windows, active_window_idx, delta=1):
|
||||
active_window_idx = (active_window_idx + len(windows) + delta) % len(windows)
|
||||
self.set_active_window(windows, active_window_idx)
|
||||
def nth_window(self, all_windows, num):
|
||||
windows = process_overlaid_windows(all_windows)[1]
|
||||
w = windows[min(num, len(windows) - 1)]
|
||||
active_window_idx = idx_for_id(w.id, all_windows)
|
||||
return self.set_active_window(all_windows, active_window_idx)
|
||||
|
||||
def next_window(self, all_windows, active_window_idx, delta=1):
|
||||
w = all_windows[active_window_idx]
|
||||
windows = process_overlaid_windows(all_windows)[1]
|
||||
idx = idx_for_id(w.id, windows)
|
||||
if idx is None:
|
||||
idx = idx_for_id(w.overlay_window_id, windows)
|
||||
active_window_idx = (idx + len(windows) + delta) % len(windows)
|
||||
active_window_idx = idx_for_id(windows[active_window_idx].id, all_windows)
|
||||
return self.set_active_window(all_windows, active_window_idx)
|
||||
|
||||
def move_window(self, all_windows, active_window_idx, delta=1):
|
||||
w = all_windows[active_window_idx]
|
||||
windows = process_overlaid_windows(all_windows)[1]
|
||||
if len(windows) < 2 or abs(delta) == 0:
|
||||
return active_window_idx
|
||||
idx = idx_for_id(w.id, windows)
|
||||
if idx is None:
|
||||
idx = idx_for_id(w.overlay_window_id, windows)
|
||||
nidx = (idx + len(windows) + delta) % len(windows)
|
||||
nw = windows[nidx]
|
||||
nidx = idx_for_id(nw.id, all_windows)
|
||||
idx = active_window_idx
|
||||
all_windows[nidx], all_windows[idx] = all_windows[idx], all_windows[nidx]
|
||||
self.swap_windows_in_os_window(nidx, idx)
|
||||
return self.set_active_window(all_windows, nidx)
|
||||
|
||||
def add_window(self, all_windows, window, current_active_window_idx):
|
||||
active_window_idx = None
|
||||
if window.overlay_for is not None:
|
||||
i = idx_for_id(window.overlay_for, all_windows)
|
||||
if i is not None:
|
||||
# put the overlay window in the position occupied by the
|
||||
# overlaid window and move the overlaid window to the end
|
||||
self.swap_windows_in_os_window(len(all_windows), i)
|
||||
all_windows.append(all_windows[i])
|
||||
all_windows[i] = window
|
||||
active_window_idx = i
|
||||
if active_window_idx is None:
|
||||
active_window_idx = len(all_windows)
|
||||
all_windows.append(window)
|
||||
self(all_windows, active_window_idx)
|
||||
self.set_active_window_in_os_window(active_window_idx)
|
||||
return active_window_idx
|
||||
|
||||
def add_window(self, windows, window, active_window_idx):
|
||||
active_window_idx = len(windows)
|
||||
windows.append(window)
|
||||
self(windows, active_window_idx)
|
||||
def remove_window(self, all_windows, window, current_active_window_idx):
|
||||
all_windows.remove(window)
|
||||
active_window_idx = None
|
||||
if window.overlay_for is not None:
|
||||
i = idx_for_id(window.overlay_for, all_windows)
|
||||
if i is not None:
|
||||
active_window_idx = i
|
||||
all_windows[i].overlay_window_id = None
|
||||
if active_window_idx is None:
|
||||
active_window_idx = max(0, min(current_active_window_idx, len(all_windows) - 1))
|
||||
if all_windows:
|
||||
self(all_windows, active_window_idx)
|
||||
self.set_active_window(all_windows, active_window_idx)
|
||||
return active_window_idx
|
||||
|
||||
def remove_window(self, windows, window, active_window_idx):
|
||||
windows.remove(window)
|
||||
active_window_idx = max(0, min(active_window_idx, len(windows) - 1))
|
||||
if windows:
|
||||
self(windows, active_window_idx)
|
||||
def update_visibility(self, all_windows, active_window, overlaid_windows=None):
|
||||
if overlaid_windows is None:
|
||||
overlaid_windows = process_overlaid_windows(all_windows)[0]
|
||||
for i, w in enumerate(all_windows):
|
||||
w.set_visible_in_layout(i, w is active_window or (not self.only_active_window_visible and w not in overlaid_windows))
|
||||
|
||||
def set_active_window(self, all_windows, active_window_idx):
|
||||
if not all_windows:
|
||||
self.set_active_window_in_os_window(0)
|
||||
return 0
|
||||
w = all_windows[active_window_idx]
|
||||
if w.overlay_window_id is not None:
|
||||
i = idx_for_id(w.overlay_window_id, all_windows)
|
||||
if i is not None:
|
||||
active_window_idx = i
|
||||
self.update_visibility(all_windows, all_windows[active_window_idx])
|
||||
self.set_active_window_in_os_window(active_window_idx)
|
||||
return active_window_idx
|
||||
|
||||
def __call__(self, all_windows, active_window_idx):
|
||||
global central, cell_width, cell_height
|
||||
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
|
||||
|
||||
active_window = all_windows[active_window_idx]
|
||||
overlaid_windows, windows = process_overlaid_windows(all_windows)
|
||||
if overlaid_windows:
|
||||
windows = [w for w in all_windows if w not in overlaid_windows]
|
||||
active_window_idx = idx_for_id(active_window.id, windows)
|
||||
if active_window_idx is None:
|
||||
active_window_idx = idx_for_id(active_window.overlay_window_id, windows) or 0
|
||||
active_window = windows[active_window_idx]
|
||||
else:
|
||||
windows = all_windows
|
||||
self.update_visibility(all_windows, active_window, overlaid_windows)
|
||||
self.do_layout(windows, active_window_idx)
|
||||
|
||||
# Utils {{{
|
||||
def xlayout(self, num):
|
||||
return layout_dimension(
|
||||
central.left, central.width, cell_width, num, self.border_width,
|
||||
@@ -93,16 +196,7 @@ class Layout:
|
||||
|
||||
def bottom_blank_rect(self, window):
|
||||
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
|
||||
|
||||
def set_active_window(self, windows, active_window_idx):
|
||||
if self.only_active_window_visible:
|
||||
for i, w in enumerate(windows):
|
||||
w.set_visible_in_layout(i, i == active_window_idx)
|
||||
|
||||
def __call__(self, windows, active_window_idx):
|
||||
global central, cell_width, cell_height
|
||||
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
|
||||
self.do_layout(windows, active_window_idx)
|
||||
# }}}
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
raise NotImplementedError()
|
||||
@@ -158,7 +252,6 @@ class Stack(Layout):
|
||||
self.blank_rects = []
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
for i, w in enumerate(windows):
|
||||
w.set_visible_in_layout(i, i == active_window_idx)
|
||||
w.set_geometry(i, wg)
|
||||
if w.is_visible_in_layout:
|
||||
self.blank_rects = blank_rects_for_window(w)
|
||||
|
||||
@@ -12,8 +12,8 @@ from .config import build_ansi_color_table
|
||||
from .constants import WindowGeometry, appname, get_boss, is_macos, is_wayland
|
||||
from .fast_data_types import (
|
||||
DECAWM, Screen, add_tab, glfw_post_empty_event, remove_tab, remove_window,
|
||||
set_active_tab, set_active_window, set_tab_bar_render_data, swap_tabs,
|
||||
swap_windows, viewport_for_window, x11_window_id
|
||||
set_active_tab, set_tab_bar_render_data, swap_tabs, viewport_for_window,
|
||||
x11_window_id
|
||||
)
|
||||
from .layout import Rect, all_layouts
|
||||
from .session import resolved_shell
|
||||
@@ -21,11 +21,11 @@ from .utils import color_as_int
|
||||
from .window import Window, calculate_gl_geometry
|
||||
|
||||
TabbarData = namedtuple('TabbarData', 'title is_active is_last')
|
||||
SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd')
|
||||
SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd overlay_for')
|
||||
|
||||
|
||||
def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None):
|
||||
return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd)
|
||||
def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None):
|
||||
return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for)
|
||||
|
||||
|
||||
class Tab: # {{{
|
||||
@@ -47,7 +47,7 @@ class Tab: # {{{
|
||||
if session_tab is None:
|
||||
self.cwd = self.args.directory
|
||||
sl = self.enabled_layouts[0]
|
||||
self.current_layout = all_layouts[sl](self.os_window_id, self.opts, self.borders.border_width, self.windows)
|
||||
self.current_layout = self.create_layout_object(sl)
|
||||
if special_window is None:
|
||||
self.new_window(cwd_from=cwd_from)
|
||||
else:
|
||||
@@ -55,7 +55,7 @@ class Tab: # {{{
|
||||
else:
|
||||
self.cwd = session_tab.cwd or self.args.directory
|
||||
l0 = session_tab.layout
|
||||
self.current_layout = all_layouts[l0](self.os_window_id, self.opts, self.borders.border_width, self.windows)
|
||||
self.current_layout = self.create_layout_object(l0)
|
||||
self.startup(session_tab)
|
||||
|
||||
def startup(self, session_tab):
|
||||
@@ -102,6 +102,9 @@ class Tab: # {{{
|
||||
self.borders(self.windows, self.active_window, self.current_layout,
|
||||
tm.blank_rects, self.current_layout.needs_window_borders and len(self.windows) > 1)
|
||||
|
||||
def create_layout_object(self, idx):
|
||||
return all_layouts[idx](self.os_window_id, self.id, self.opts, self.borders.border_width)
|
||||
|
||||
def next_layout(self):
|
||||
if len(self.opts.enabled_layouts) > 1:
|
||||
try:
|
||||
@@ -109,9 +112,7 @@ class Tab: # {{{
|
||||
except Exception:
|
||||
idx = -1
|
||||
nl = self.opts.enabled_layouts[(idx + 1) % len(self.opts.enabled_layouts)]
|
||||
self.current_layout = all_layouts[nl](self.os_window_id, self.opts, self.borders.border_width, self.windows)
|
||||
for i, w in enumerate(self.windows):
|
||||
w.set_visible_in_layout(i, True)
|
||||
self.current_layout = self.create_layout_object(nl)
|
||||
self.relayout()
|
||||
|
||||
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None):
|
||||
@@ -131,13 +132,16 @@ class Tab: # {{{
|
||||
ans.fork()
|
||||
return ans
|
||||
|
||||
def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None):
|
||||
def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None):
|
||||
child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd)
|
||||
window = Window(self, child, self.opts, self.args, override_title=override_title)
|
||||
if overlay_for is not None:
|
||||
overlaid = next(w for w in self.windows if w.id == overlay_for)
|
||||
window.overlay_for = overlay_for
|
||||
overlaid.overlay_window_id = window.id
|
||||
# Must add child before laying out so that resize_pty succeeds
|
||||
get_boss().add_child(window)
|
||||
self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx)
|
||||
set_active_window(self.os_window_id, self.id, self.active_window_idx)
|
||||
self.relayout_borders()
|
||||
glfw_post_empty_event()
|
||||
return window
|
||||
@@ -152,15 +156,12 @@ class Tab: # {{{
|
||||
def remove_window(self, window):
|
||||
remove_window(self.os_window_id, self.id, window.id)
|
||||
self.active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx)
|
||||
set_active_window(self.os_window_id, self.id, self.active_window_idx)
|
||||
self.relayout_borders()
|
||||
glfw_post_empty_event()
|
||||
|
||||
def set_active_window_idx(self, idx):
|
||||
if idx != self.active_window_idx:
|
||||
self.current_layout.set_active_window(self.windows, idx)
|
||||
self.active_window_idx = idx
|
||||
set_active_window(self.os_window_id, self.id, self.active_window_idx)
|
||||
self.active_window_idx = self.current_layout.set_active_window(self.windows, idx)
|
||||
self.relayout_borders()
|
||||
glfw_post_empty_event()
|
||||
|
||||
@@ -173,12 +174,13 @@ class Tab: # {{{
|
||||
|
||||
def nth_window(self, num=0):
|
||||
if self.windows:
|
||||
self.set_active_window_idx(min(num, len(self.windows) - 1))
|
||||
self.active_window_idx = self.current_layout.nth_window(self.windows, num)
|
||||
self.relayout_borders()
|
||||
glfw_post_empty_event()
|
||||
|
||||
def _next_window(self, delta=1):
|
||||
if len(self.windows) > 1:
|
||||
self.active_window_idx = self.current_layout.next_window(self.windows, self.active_window_idx, delta)
|
||||
set_active_window(self.os_window_id, self.id, self.active_window_idx)
|
||||
self.relayout_borders()
|
||||
glfw_post_empty_event()
|
||||
|
||||
@@ -189,14 +191,9 @@ class Tab: # {{{
|
||||
self._next_window(-1)
|
||||
|
||||
def move_window(self, delta=1):
|
||||
if len(self.windows) > 1 and abs(delta) > 0:
|
||||
idx = self.active_window_idx
|
||||
nidx = (idx + len(self.windows) + delta) % len(self.windows)
|
||||
self.windows[nidx], self.windows[idx] = self.windows[idx], self.windows[nidx]
|
||||
swap_windows(self.os_window_id, self.id, nidx, idx)
|
||||
self.active_window_idx = nidx
|
||||
set_active_window(self.os_window_id, self.id, self.active_window_idx)
|
||||
self.relayout()
|
||||
self.active_window_idx = self.current_layout.move_window(self.windows, self.active_window_idx, delta)
|
||||
self.relayout()
|
||||
glfw_post_empty_event()
|
||||
|
||||
def move_window_to_top(self):
|
||||
self.move_window(-self.active_window_idx)
|
||||
|
||||
@@ -88,6 +88,8 @@ class Window:
|
||||
|
||||
def __init__(self, tab, child, opts, args, override_title=None):
|
||||
self.override_title = override_title
|
||||
self.overlay_window_id = None
|
||||
self.overlay_for = None
|
||||
self.child_title = appname
|
||||
self.id = add_window(tab.os_window_id, tab.id, self.title)
|
||||
if not self.id:
|
||||
|
||||
Reference in New Issue
Block a user