Skip to content

Commit

Permalink
Add Plasma widget to show add_mode for layout
Browse files Browse the repository at this point in the history
Adds a simple widget to show the status of the Plasma layout.
  • Loading branch information
elParaguayo committed May 11, 2024
1 parent e6c8d6d commit b87bb10
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 10 deletions.
8 changes: 8 additions & 0 deletions libqtile/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,14 @@ def hooked_function():
""",
_user_hook_func,
),
Hook(
"plasma_add_mode",
"""
Used to flag when the add mode of the Plasma layout has changed.
The hooked function should take one argument being the layout object.
""",
),
]


Expand Down
76 changes: 66 additions & 10 deletions libqtile/layout/plasma.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from math import isclose
from typing import NamedTuple

from libqtile import hook
from libqtile.command.base import expose_command
from libqtile.layout.base import Layout

Expand Down Expand Up @@ -70,8 +71,8 @@ class Priority(Enum):


class AddMode(Flag):
HORIZONTAL = 0
VERTICAL = 1
HORIZONTAL = auto()
VERTICAL = auto()
SPLIT = auto()

@property
Expand Down Expand Up @@ -728,6 +729,9 @@ class Plasma(Layout):
``lazy.layout.mode_vertical/horizontal()`` will insert a new container allowing
windows to be added in the new direction.
You can use the ``Plasma`` widget to show which mode will apply when opening a new
window based on the currently focused node.
Windows can be focused selectively by using ``lazy.layout.up/down/left/right()`` to focus
the nearest window in that direction relative to the currently focused window.
Expand Down Expand Up @@ -815,18 +819,70 @@ def __init__(self, **config):
Layout.__init__(self, **config)
self.add_defaults(Plasma.defaults)
self.root = Node(None, *self.default_dimensions)
self.focused = None
self.add_mode = None
self._focused = None
self._add_mode = None
Node.priority = Priority.BALANCED if self.fair else Priority.FIXED

@staticmethod
def convert_names(tree):
return [Plasma.convert_names(n) if isinstance(n, list) else n.payload.name for n in tree]

@property
def add_mode(self):
return self._add_mode

@add_mode.setter
def add_mode(self, value):
self._add_mode = value
# We trigger a redraw so that the different borders can be drawn based on the add_mode
# We check self._group to avoid raising a runtime error from libqtile.layout.base
if self._group is not None:
hook.fire("plasma_add_mode", self)
self.group.layout_all()

@property
def focused(self):
return self._focused

@focused.setter
def focused(self, value):
self._focused = value
hook.fire("plasma_add_mode", self)

@property
def focused_node(self):
return self.root.find_payload(self.focused)

@property
def horizontal(self):
if self.focused_node is None:
return True

if self.add_mode is not None:
if self.add_mode & AddMode.HORIZONTAL:
return True
else:
return False

if self.focused_node.parent is None:
if self.focused_node.orient is Orient.HORIZONTAL:
return True
else:
return False

return self.focused_node.parent.horizontal

@property
def vertical(self):
return not self.horizontal

@property
def split(self):
if self.add_mode is not None and self.add_mode & AddMode.SPLIT:
return True

return False

@expose_command
def info(self):
info = super().info()
Expand Down Expand Up @@ -1021,7 +1077,7 @@ def mode_vertical_split(self):
self.add_mode = AddMode.VERTICAL | AddMode.SPLIT

@expose_command
def set_size(self, x):
def set_size(self, x: int):
"""Change size of current window.
(It's recommended to use `width()`/`height()` instead.)
Expand All @@ -1030,13 +1086,13 @@ def set_size(self, x):
self.refocus()

@expose_command
def set_width(self, x):
def set_width(self, x: int):
"""Set width of current window."""
self.focused_node.width = x
self.refocus()

@expose_command
def set_height(self, x):
def set_height(self, x: int):
"""Set height of current window."""
self.focused_node.height = x
self.refocus()
Expand All @@ -1048,7 +1104,7 @@ def reset_size(self):
self.refocus()

@expose_command
def grow(self, x):
def grow(self, x: int):
"""Grow size of current window.
(It's recommended to use `grow_width()`/`grow_height()` instead.)
Expand All @@ -1057,13 +1113,13 @@ def grow(self, x):
self.refocus()

@expose_command
def grow_width(self, x):
def grow_width(self, x: int):
"""Grow width of current window."""
self.focused_node.width += x
self.refocus()

@expose_command
def grow_height(self, x):
def grow_height(self, x: int):
"""Grow height of current window."""
self.focused_node.height += x
self.refocus()
1 change: 1 addition & 0 deletions libqtile/widget/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"Notify": "notify",
"NvidiaSensors": "nvidia_sensors",
"OpenWeather": "open_weather",
"Plasma": "plasma",
"Pomodoro": "pomodoro",
"Prompt": "prompt",
"PulseVolume": "pulse_volume",
Expand Down
119 changes: 119 additions & 0 deletions libqtile/widget/plasma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright (c) 2024 elParaguayo
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import Any

from libqtile import bar, hook
from libqtile.command.base import expose_command
from libqtile.layout import plasma
from libqtile.layout.plasma import AddMode
from libqtile.widget import base


class Plasma(base._TextBox):
"""
A simple widget to indicate in which direction new windows will be
added in the Plasma layout.
"""

defaults: list[tuple[str, Any, str]] = [
("horizontal", "H", "Text to display if horizontal mode"),
("vertical", "V", "Text to display if horizontal mode"),
("split", "S", "Text to append to mode if ``horizontal/vertical_split`` not set"),
("horizontal_split", None, "Text to display for horizontal split mode"),
("vertical_split", None, "Text to display for horizontal split mode"),
("format", "{mode:>2}", "Format appearance of text"),
]

def __init__(self, text="", width=bar.CALCULATED, **config):
base._TextBox.__init__(self, text=text, width=width, **config)
self.add_defaults(Plasma.defaults)
self.add_callbacks({"Button1": self.next_mode})

if self.horizontal_split is None:
self.horizontal_split = self.horizontal + self.split

if self.vertical_split is None:
self.vertical_split = self.vertical + self.split

self.modes = [
AddMode.HORIZONTAL,
AddMode.HORIZONTAL | AddMode.SPLIT,
AddMode.VERTICAL,
AddMode.VERTICAL | AddMode.SPLIT,
]
self._mode = self.modes[0]
self._layout = None

def _configure(self, qtile, bar):
base._TextBox._configure(self, qtile, bar)
hook.subscribe.plasma_add_mode(self.mode_changed)
hook.subscribe.layout_change(self.layout_changed)
self.mode_changed(bar.screen.group.layout)

def mode_changed(self, layout):
"""Update text depending on add_mode of layout."""
if not isinstance(layout, plasma.Plasma):
self.update("")
self._layout = None
return

self._layout = layout

if layout.group and layout.group.screen is not self.bar.screen:
return

if layout.horizontal:
if layout.split:
mode = self.horizontal_split
self._mode = AddMode.HORIZONTAL | AddMode.SPLIT
else:
mode = self.horizontal
self._mode = AddMode.HORIZONTAL
else:
if layout.split:
mode = self.vertical_split
self._mode = AddMode.VERTICAL | AddMode.SPLIT
else:
mode = self.vertical
self._mode = AddMode.VERTICAL

self.update(self.format.format(mode=mode))

def layout_changed(self, layout, group):
"""Update widget when layout changes."""
if group.screen is self.bar.screen:
self.mode_changed(layout)

def finalize(self):
hook.unsubscribe.plasma_add_mode(self.mode_changed)
hook.unsubscribe.layout_change(self.layout_changed)
base._TextBox.finalize(self)

@expose_command()
def next_mode(self):
"""Change the add mode for the Plasma layout."""
if self._layout is None:
return

index = self.modes.index(self._mode)
index = (index + 1) % len(self.modes)

self._layout.add_mode = self.modes[index]
Loading

0 comments on commit b87bb10

Please sign in to comment.