PerKey gradient swatch: Tabler "square" outline around the gradient

The gradient swatch on the gradient-tool button drew a flat
rectangular fill with a 1px translucent-black border. Visually it
sat as an odd-one-out next to the rounded-square Tabler outline
icons on the rest of the toolbar — a sharp-cornered patch flanked
by rounded-corner icons.

Render the gradient inside the same path Tabler "square" uses
(rounded rect from (3,3) to (21,21), corner radius 2, stroke 2 in
a 24x24 viewBox; cairo scale into the swatch's pixel size) and
stroke the outline in the GTK theme's foreground color. The
swatch now reads as one of the icon family — same outline style,
same line weight, same theme-following color — with the gradient
filling the inside.

Connect style-updated to queue_draw so the outline color tracks
runtime theme switches alongside the icon buttons.
This commit is contained in:
Ken Sanislo 2026-05-11 01:10:25 -07:00 committed by Peter F. Patel-Schneider
parent b594c7292b
commit a8e006c948
1 changed files with 36 additions and 8 deletions

View File

@ -159,6 +159,10 @@ class GradientSwatch(Gtk.DrawingArea):
self._active: int = 0xFF0000
self._previous: int = 0xFF0000
self.connect(GtkSignal.DRAW.value, self._on_draw)
# Re-render when the GTK theme changes, so the rounded-square
# outline (drawn in the theme foreground color) stays in sync
# with the tool icons next to it.
self.connect("style-updated", lambda w: w.queue_draw())
def update(self, active: int, previous: int) -> None:
self._active = int(active)
@ -175,27 +179,51 @@ class GradientSwatch(Gtk.DrawingArea):
"""Return (active, previous) — the colors the gradient tool will paint with."""
return (self._active, self._previous)
@staticmethod
def _rounded_rect_path(cr, x: float, y: float, w: float, h: float, r: float) -> None:
cr.new_sub_path()
cr.arc(x + w - r, y + r, r, -1.5708, 0)
cr.arc(x + w - r, y + h - r, r, 0, 1.5708)
cr.arc(x + r, y + h - r, r, 1.5708, 3.1416)
cr.arc(x + r, y + r, r, 3.1416, 4.7124)
cr.close_path()
def _on_draw(self, _w, cr) -> None:
import cairo # local: keeps the module light when GradientSwatch isn't built
s = self.SIZE
def rgb(c: int) -> tuple[float, float, float]:
if c is None or c < 0:
return (0.5, 0.5, 0.5)
return (((c >> 16) & 0xFF) / 255.0, ((c >> 8) & 0xFF) / 255.0, (c & 0xFF) / 255.0)
# Render in Tabler "square" coordinates (24x24 viewBox, rounded
# rect from (3,3) to (21,21), corner radius 2, stroke 2) and let
# cairo scale to the swatch's pixel size. Matches the outline
# style of the tool icons exactly.
cr.save()
cr.scale(self.SIZE / 24.0, self.SIZE / 24.0)
# Build the rounded-square path once, clip+fill the gradient
# inside it, then re-build and stroke the outline in the theme
# foreground color.
self._rounded_rect_path(cr, 3, 3, 18, 18, 2)
cr.save()
cr.clip()
# Top-left (previous, gradient start) → bottom-right (active, end).
# Matches the directional behavior of dragging the line tool TL → BR.
pat = cairo.LinearGradient(0, 0, s, s)
pat = cairo.LinearGradient(3, 3, 21, 21)
pat.add_color_stop_rgb(0.0, *rgb(self._previous))
pat.add_color_stop_rgb(1.0, *rgb(self._active))
cr.set_source(pat)
cr.rectangle(0, 0, s, s)
cr.rectangle(3, 3, 18, 18)
cr.fill()
cr.restore() # drop clip
# Subtle border so the swatch reads as a control even on similar bg.
cr.set_source_rgba(0, 0, 0, 0.45)
cr.set_line_width(1.0)
cr.rectangle(0.5, 0.5, s - 1, s - 1)
self._rounded_rect_path(cr, 3, 3, 18, 18, 2)
fg = self.get_style_context().get_color(Gtk.StateFlags.NORMAL)
cr.set_source_rgba(fg.red, fg.green, fg.blue, fg.alpha)
cr.set_line_width(2)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
cr.set_line_cap(cairo.LINE_CAP_ROUND)
cr.stroke()
cr.restore()