initial commit
This commit is contained in:
commit
4849024e71
3 changed files with 635 additions and 0 deletions
312
layouts/tabbed.py
Normal file
312
layouts/tabbed.py
Normal file
|
@ -0,0 +1,312 @@
|
|||
"""Proof-of-concept for a Tabbed layout in Qtile.
|
||||
|
||||
See GitHub page for more information: https://github.com/hanschen/qtile_tabbed
|
||||
|
||||
Created by Hans Chen (contact@hanschen.org).
|
||||
"""
|
||||
|
||||
from libqtile import hook
|
||||
from libqtile.command.base import expose_command
|
||||
from libqtile.layout.base import _ClientList, _SimpleLayoutBase
|
||||
from libqtile.layout.base import Layout
|
||||
|
||||
|
||||
def count_windows(group, include_floating=True):
|
||||
count = 0
|
||||
for window in group.windows:
|
||||
if include_floating or not window.floating:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
class Tab:
|
||||
"""A tab representing a window."""
|
||||
def __init__(self, window):
|
||||
self.window = window
|
||||
self._left = 0
|
||||
self._right = 0
|
||||
|
||||
def button_press(self, x, y):
|
||||
del y
|
||||
if self._left <= x < self._right:
|
||||
return self
|
||||
|
||||
def draw(self, layout, left):
|
||||
if not layout.group.screen:
|
||||
return
|
||||
|
||||
layout._layout.font_size = layout.fontsize
|
||||
layout._layout.text = self.window.name
|
||||
|
||||
if self.window is layout.clients.current_client:
|
||||
fg = layout.active_fg
|
||||
bg = layout.active_bg
|
||||
elif self.window.urgent:
|
||||
fg = layout.urgent_fg
|
||||
bg = layout.urgent_bg
|
||||
else:
|
||||
fg = layout.inactive_fg
|
||||
bg = layout.inactive_bg
|
||||
|
||||
ntabs = len(layout.clients)
|
||||
width = layout.group.screen.width / ntabs
|
||||
layout._layout.width = width
|
||||
layout._layout.colour = fg
|
||||
|
||||
# get a text frame from the above
|
||||
framed = layout._layout.framed(
|
||||
border_width=0,
|
||||
border_color=bg,
|
||||
pad_x=0,
|
||||
pad_y=layout.padding_y,
|
||||
)
|
||||
|
||||
# draw the text frame at the given point
|
||||
framed.draw_fill(left, 0, rounded=layout.rounded_tabs)
|
||||
|
||||
self._left = left
|
||||
self._right = left + framed.width
|
||||
|
||||
left += framed.width + layout.hspace
|
||||
return left
|
||||
|
||||
|
||||
class ClientList(_ClientList):
|
||||
"""Similar to libqtile.layout.base._ClientList, but allows wraping when
|
||||
shuffling windows.
|
||||
"""
|
||||
|
||||
def shuffle_up(self, maintain_index=True):
|
||||
"""
|
||||
Shuffle the list. The current client swaps position with its
|
||||
predecessor. If maintain_index is True the current_index is adjusted,
|
||||
such that the same client stays current and goes up in list.
|
||||
"""
|
||||
idx = self._current_idx
|
||||
if idx > 0:
|
||||
self.clients[idx], self.clients[idx - 1] = self[idx - 1], self[idx]
|
||||
if maintain_index:
|
||||
self.current_index -= 1
|
||||
else:
|
||||
self.clients.append(self.clients.pop(0))
|
||||
if maintain_index:
|
||||
self.current_index = len(self.clients) - 1
|
||||
|
||||
def shuffle_down(self, maintain_index=True):
|
||||
"""
|
||||
Shuffle the list. The current client swaps position with its successor.
|
||||
If maintain_index is True the current_index is adjusted,
|
||||
such that the same client stays current and goes down in list.
|
||||
"""
|
||||
idx = self._current_idx
|
||||
if idx + 1 < len(self.clients):
|
||||
self.clients[idx], self.clients[idx + 1] = self[idx + 1], self[idx]
|
||||
if maintain_index:
|
||||
self.current_index += 1
|
||||
else:
|
||||
self.clients.insert(0, self.clients.pop(-1))
|
||||
if maintain_index:
|
||||
self.current_index = 0
|
||||
|
||||
|
||||
class Tabbed(_SimpleLayoutBase):
|
||||
"""Tabbed layout
|
||||
|
||||
A simple layout that displays one window at a time, similar to the Max
|
||||
layout. The major difference from Max is that Tabbed will show a tab bar
|
||||
with all windows if there are more than one or two windows in the layout,
|
||||
depending on your settings.
|
||||
"""
|
||||
|
||||
defaults = [
|
||||
("border_width", 0, "Border width"),
|
||||
("border_focus", None, "Border color for focused window"),
|
||||
("border_normal", None, "Border color for unfocused window"),
|
||||
("margin", 0, "Margin"),
|
||||
("bg_color", "000000", "Background color of tab bar"),
|
||||
("active_fg", "ffffff", "Foreground color of active tab"),
|
||||
("active_bg", "000080", "Background color of active tab"),
|
||||
("urgent_fg", "ffffff", "Foreground color of urgent tab"),
|
||||
("urgent_bg", "ff0000", "Background color of urgent tab"),
|
||||
("inactive_fg", "ffffff", "Foreground color of inactive tab"),
|
||||
("inactive_bg", "606060", "Background color of inactive tab"),
|
||||
("rounded_tabs", False, "Draw tabs rounded"),
|
||||
("padding_y", 2, "Top and bottom padding for tab label"),
|
||||
("hspace", 2, "Space between tabs"),
|
||||
("font", "sans", "Font"),
|
||||
("fontsize", 14, "Font pixel size"),
|
||||
("fontshadow", None, "Font shadow color, default is None (no shadow)"),
|
||||
("bar_height", 24, "Height of tab bar"),
|
||||
("place_bottom", False, "Place tab bar at the bottom instead of top"),
|
||||
("show_single_tab", True, "Show tabs if there is only a single tab"),
|
||||
]
|
||||
|
||||
def __init__(self, **config):
|
||||
_SimpleLayoutBase.__init__(self, **config)
|
||||
self.clients = ClientList()
|
||||
self.add_defaults(Tabbed.defaults)
|
||||
self._drawer = None
|
||||
self._panel = None
|
||||
self._tabs = {}
|
||||
|
||||
def add_client(self, client):
|
||||
tab = Tab(client)
|
||||
self._tabs[client] = tab
|
||||
return super().add_client(client, 1)
|
||||
|
||||
def clone(self, group):
|
||||
c = Layout.clone(self, group)
|
||||
c.clients = ClientList()
|
||||
return c
|
||||
|
||||
def configure(self, client, screen_rect):
|
||||
if self.clients and client is self.clients.current_client:
|
||||
client.place(
|
||||
screen_rect.x,
|
||||
screen_rect.y,
|
||||
screen_rect.width - self.border_width * 2,
|
||||
screen_rect.height - self.border_width * 2,
|
||||
self.border_width,
|
||||
self.border_focus if client.has_focus else self.border_normal,
|
||||
margin=self.margin
|
||||
)
|
||||
client.unhide()
|
||||
else:
|
||||
client.hide()
|
||||
|
||||
@expose_command("previous")
|
||||
def up(self):
|
||||
_SimpleLayoutBase.previous(self)
|
||||
|
||||
@expose_command("next")
|
||||
def down(self):
|
||||
_SimpleLayoutBase.next(self)
|
||||
|
||||
left = up
|
||||
right = down
|
||||
|
||||
@expose_command("shuffle_right")
|
||||
def shuffle_down(self):
|
||||
self.clients.shuffle_down()
|
||||
self.draw_panel()
|
||||
|
||||
@expose_command("shuffle_left")
|
||||
def shuffle_up(self):
|
||||
self.clients.shuffle_up()
|
||||
self.draw_panel()
|
||||
|
||||
def draw_panel(self, *args):
|
||||
del args
|
||||
|
||||
if not self._panel:
|
||||
return
|
||||
|
||||
self._drawer.clear(self.bg_color)
|
||||
|
||||
left = 0
|
||||
for client in self.clients:
|
||||
left = self._tabs[client].draw(self, left)
|
||||
|
||||
self._drawer.draw(height=self.bar_height)
|
||||
|
||||
def finalize(self):
|
||||
if self._panel:
|
||||
self._panel.kill()
|
||||
Layout.finalize(self)
|
||||
if self._drawer is not None:
|
||||
self._drawer.finalize()
|
||||
|
||||
def hide(self):
|
||||
if self._panel:
|
||||
self._panel.hide()
|
||||
|
||||
def layout(self, windows, screen_rect):
|
||||
if not self._show_tabs():
|
||||
body = screen_rect
|
||||
if self._panel:
|
||||
self._panel.hide()
|
||||
else:
|
||||
if self.place_bottom:
|
||||
body, panel = screen_rect.vsplit(screen_rect.height -
|
||||
self.bar_height)
|
||||
else:
|
||||
panel, body = screen_rect.vsplit(self.bar_height)
|
||||
self._resize_panel(panel)
|
||||
if self._panel:
|
||||
self._panel.unhide()
|
||||
Layout.layout(self, windows, body)
|
||||
|
||||
def process_button_click(self, x, y, button):
|
||||
if button == 4:
|
||||
self.up()
|
||||
elif button == 5:
|
||||
self.down()
|
||||
else:
|
||||
for client in self.clients:
|
||||
tab = self._tabs[client].button_press(x, y)
|
||||
if tab:
|
||||
self.group.focus(tab.window, False)
|
||||
|
||||
def remove(self, win):
|
||||
super().remove(win)
|
||||
self._tabs.pop(win)
|
||||
self.draw_panel()
|
||||
|
||||
def show(self, screen_rect):
|
||||
if not self._panel:
|
||||
self._create_panel(screen_rect)
|
||||
|
||||
if not self._show_tabs():
|
||||
return
|
||||
|
||||
if self.place_bottom:
|
||||
_, panel = screen_rect.vsplit(screen_rect.height -
|
||||
self.bar_height)
|
||||
else:
|
||||
panel, _ = screen_rect.vsplit(self.bar_height)
|
||||
self._resize_panel(panel)
|
||||
self._panel.unhide()
|
||||
|
||||
def _create_drawer(self, screen_rect):
|
||||
if self._drawer is None:
|
||||
self._drawer = self._panel.create_drawer(
|
||||
screen_rect.width,
|
||||
self.bar_height,
|
||||
)
|
||||
else:
|
||||
self._drawer.width = screen_rect.width
|
||||
self._drawer.clear(self.bg_color)
|
||||
self._layout = self._drawer.textlayout(
|
||||
"", "#ffffff", self.font, self.fontsize, self.fontshadow,
|
||||
wrap=False
|
||||
)
|
||||
|
||||
def _create_panel(self, screen_rect):
|
||||
self._panel = self.group.qtile.core.create_internal(
|
||||
screen_rect.x, screen_rect.y, screen_rect.width, self.bar_height,
|
||||
)
|
||||
self._create_drawer(screen_rect)
|
||||
self._panel.process_window_expose = self.draw_panel
|
||||
self._panel.process_button_click = self.process_button_click
|
||||
hook.subscribe.client_name_updated(self.draw_panel)
|
||||
hook.subscribe.focus_change(self.draw_panel)
|
||||
|
||||
def _resize_panel(self, screen_rect):
|
||||
if self._panel:
|
||||
self._panel.place(
|
||||
screen_rect.x,
|
||||
screen_rect.y,
|
||||
screen_rect.width,
|
||||
screen_rect.height,
|
||||
0,
|
||||
None,
|
||||
)
|
||||
self._create_drawer(screen_rect)
|
||||
self.draw_panel()
|
||||
|
||||
def _show_tabs(self):
|
||||
nwindows = count_windows(self.group, include_floating=False)
|
||||
if self.show_single_tab:
|
||||
return nwindows > 0
|
||||
else:
|
||||
return nwindows > 1
|
Loading…
Add table
Add a link
Reference in a new issue