Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make GroupBox2 compatible with vertical bars #393

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
2024-11-14: [FEATURE] Enable support for vertical bars for `GroupBox2`
2024-11-03: [FEATURE] Add `ImageDecoration` for widgets
2024-10-19: [RELEASE] v0.29.0 release - compatible with qtile 0.29.0
2024-08-23: [FEATURE] Add `GradientDecoration` for widgets
Expand Down
92 changes: 71 additions & 21 deletions qtile_extras/widget/groupbox2.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def __init__(
self.group_name: str | None = None
self.func: Callable[[GroupBoxRule, Box], bool] | None = None
self.always_check = False
self._inverted = False

size: int | Sentinel | None

Expand Down Expand Up @@ -190,7 +191,7 @@ def when(

return self

def match(self, box: Box) -> bool:
def _match(self, box: Box) -> bool:
"""Returns True if box conditions match rule criteria."""
if not self.screen & ScreenRule.UNSET:
if not box.screen & self.screen:
Expand All @@ -214,6 +215,13 @@ def match(self, box: Box) -> bool:

return True

def match(self, box: Box) -> bool:
matched = self._match(box)
if not self._inverted:
return matched

return not matched

def force_check(self):
self.always_check = True
return self
Expand All @@ -226,11 +234,16 @@ def reset(self, attr: str) -> None:

def __repr__(self) -> str:
"""Short representation of rule instance."""
inverted = "_not" if self._inverted else ""
output = describe_attributes(self, GroupBoxRule.attrs, lambda x: x is not SENTINEL)
when = describe_attributes(
self, ["screen", "focused", "occupied", "urgent", "group_name", "func"], filter_attrs
)
return f"<GroupBoxRule format({output}) when({when})>"
return f"<GroupBoxRule format({output}) when{inverted}({when})>"

def __invert__(self):
self._inverted = not self._inverted
return self


class Box:
Expand All @@ -240,7 +253,7 @@ class Box:
fontshadow: bool | None
markup: bool
padding_x: int
paddint_y: int
padding_y: int
rules: list[GroupBoxRule]
margin_x: int
margin_y: int
Expand Down Expand Up @@ -386,7 +399,10 @@ def _load_image(self, filename: str, scale=True) -> bool:
return False

if scale:
img.resize(height=self.bar.height - 2 * self.margin_y)
if self.horizontal:
img.resize(height=self.bar.height - 2 * self.margin_y)
else:
img.resize(width=self.bar.width - 2 * self.margin_x)
IMAGE_CACHE[filename] = img

return True
Expand All @@ -402,6 +418,10 @@ def get_image(self, filename: str, scale=True) -> Img | None:
def rule_attrs(self) -> list[str]:
return GroupBoxRule.attrs

@property
def horizontal(self) -> bool:
return self.bar.horizontal

def _reset_format(self) -> None:
"""Clears all formatting for the box."""
# Use ``SENTINEL`` instance as default value so we can allow a value
Expand All @@ -424,16 +444,33 @@ def size(self) -> int:
del self.layout.width
self.layout.text = self.text

if not self.horizontal:
self.layout.width = self.bar.width

if self.visible is False:
return 0

if self.box_size:
return self.box_size

elif self.image and self.image in IMAGE_CACHE:
return IMAGE_CACHE[self.image].width + 2 * self.margin_x
if self.horizontal:
return IMAGE_CACHE[self.image].width + 2 * self.margin_x
else:
return IMAGE_CACHE[self.image].height + 2 * self.margin_y

return self.layout.width + 2 * self.padding_x
if self.horizontal:
return self.layout.width + 2 * self.padding_x
else:
return self.layout.height + 2 * self.padding_y

@property
def bottom(self):
return self.bar.height if self.horizontal else self.size

@property
def right(self):
return self.size if self.horizontal else self.bar.width

@property
def has_block(self) -> bool:
Expand All @@ -454,8 +491,8 @@ def draw_block(self) -> None:
ctx.rectangle(
self.margin_x,
self.margin_y,
self.size - 2 * self.margin_x,
self.bar.height - 2 * self.margin_y,
(self.size if self.horizontal else self.bar.width) - 2 * self.margin_x,
(self.bar.height if self.horizontal else self.size) - 2 * self.margin_y,
)

# If not, we need to do rounded corers
Expand All @@ -468,8 +505,8 @@ def draw_block(self) -> None:
delta = radius + 1
x = self.margin_x
y = self.margin_y
width = self.size - 2 * self.margin_x
height = self.bar.height - 2 * self.margin_y
width = (self.size if self.horizontal else self.bar.width) - 2 * self.margin_x
height = (self.bar.height if self.horizontal else self.size) - 2 * self.margin_y
ctx.arc(x + width - delta, y + delta, radius, -90 * degrees, 0 * degrees)
ctx.arc(x + width - delta, y + height - delta, radius, 0 * degrees, 90 * degrees)
ctx.arc(x + delta, y + height - delta, radius, 90 * degrees, 180 * degrees)
Expand Down Expand Up @@ -499,10 +536,10 @@ def _draw_line(self, offset, vertical=False) -> None:
"""
if vertical:
start = (offset, 0)
end = (0, self.bar.height)
end = (0, self.bottom)
else:
start = (0, offset)
end = (self.size, 0)
end = (self.right, 0)

ctx = self.drawer.ctx
ctx.save()
Expand All @@ -523,13 +560,13 @@ def draw_line(self, line_width: int) -> None:
self._draw_line(line_width // 2)

if self.line_position & LinePosition.BOTTOM:
self._draw_line(self.bar.height - line_width // 2)
self._draw_line(self.bottom - line_width // 2)

if self.line_position & LinePosition.LEFT:
self._draw_line(line_width // 2, vertical=True)

if self.line_position & LinePosition.RIGHT:
self._draw_line(self.size - line_width // 2, vertical=True)
self._draw_line(self.right - line_width // 2, vertical=True)

def draw_image(self) -> None:
"""Draws the image, offset by margin_x and margin_y."""
Expand All @@ -542,21 +579,27 @@ def draw_image(self) -> None:

ctx = self.drawer.ctx
ctx.save()
ctx.translate((self.size - img.width) // 2, self.margin_y)
if self.horizontal:
ctx.translate((self.size - img.width) // 2, self.margin_y)
else:
ctx.translate(self.margin_x, (self.size - img.height) // 2)
ctx.set_source(img.pattern)
ctx.paint()
ctx.restore()

def draw_text(self) -> None:
"""Draws text, centered vertically."""
self.layout.colour = self.text_colour
self.layout.draw(self.padding_x, (self.bar.height - self.layout.height) // 2)
if self.horizontal:
self.layout.draw(self.padding_x, (self.bar.height - self.layout.height) // 2)
else:
self.layout.draw(0, self.padding_y)

def draw(self, offset) -> None:
"""Main method to draw all formatting."""
self.drawer.ctx.save()

self.drawer.ctx.translate(offset, 0)
self.drawer.ctx.translate(*offset)

if self.has_block:
self.draw_block()
Expand Down Expand Up @@ -610,6 +653,10 @@ class GroupBox2(base._Widget, base.MarginMixin, base.PaddingMixin):
* Whether the group name matches a given string
* Whether a user-defined function returns True

Rules can be inverted by using the ``~`` operator. For example, to apply to rule when the group is not
on the current screen, you can write ``~GroupBoxRule(text_colour="888").when(screen=GroupBoxRule.SCREEN_THIS)``.
This is expected to be of limited use but may provide finer control in some circumstances.

Rules are only rechecked when there a change to one of the following properties of the groups:

* Whether the group is focused
Expand Down Expand Up @@ -828,7 +875,7 @@ def set_label(rule, box):

_experimental = True

orientations = base.ORIENTATION_HORIZONTAL
orientations = base.ORIENTATION_BOTH

defaults: list[tuple[str, Any, str]] = [
(
Expand Down Expand Up @@ -932,6 +979,7 @@ def box_config(self):
"fontshadow",
"markup",
"padding_x",
"padding_y",
"rules",
"margin_x",
"margin_y",
Expand All @@ -946,23 +994,25 @@ def draw(self):
for box in self.boxes:
if box.visible is False:
continue
box.draw(offset)
box.draw((offset, 0) if self.bar.horizontal else (0, offset))
offset += box.size

self.drawer.draw(
offsetx=self.offsetx, offsety=self.offsety, height=self.height, width=self.width
)

def button_press(self, x, y, button):
self.click_pos = x
self.click_pos = x, y
base._Widget.button_press(self, x, y, button)

def get_clicked_group(self):
group = None
offset = 0
for box in self.boxes:
offset += box.size
if self.click_pos <= offset:
if (self.bar.horizontal and self.click_pos[0] <= offset) or (
not self.bar.horizontal and self.click_pos[1] <= offset
):
group = box.group
break
return group
Expand Down
Loading