fix: draw classroom icons at the display's real pixel density
Icons went soft under UI_SCALE while the clock text beside them stayed sharp, which is visible on a 24" monitor even if it hides on a TV. new_from_file_at_scale rasterises an SVG at its *logical* size. The artwork is vector, but by the time sway scaled the session it was already a fixed raster, and the compositor enlarged that. Text never had the problem because GTK draws it at the real density. GTK 3 has no fractional scaling, so sway advertises the ceiling integer scale and scales the finished buffer down. Measured under sway: scale factor 1 at output scale 1.0, and 2 at 1.25, 1.5 and 2.0. So each icon is now rasterised at size x scale_factor physical pixels and handed to GTK as a cairo surface carrying that device scale. The icon lands at the real density; its logical size, and the whole dock layout, is unchanged. Icons redraw on notify::scale-factor as well as at realize, since the scale factor is not known until a widget is on a display and changes if UI_SCALE is changed under a running session. Browser, DVD, LIVE and all six DVD control icons go through it, in a shared module so the dock and the DVD session cannot diverge. The throbber is drawn rather than loaded and had the same flaw, so it draws on a device-scaled surface too rather than being the one soft thing left in a sharp dock. The artwork is untouched. Verified by rendering the dock under sway at 1.0, 1.25, 1.5 and 2.0 against the previous build: apparent icon size is identical at every scale (28, 35, 42, 56 px), 1.0 is unchanged pixel-for-pixel, and peak edge contrast on the Chrome icon goes from 60 to 99 at 1.25, 49 to 83 at 1.5, and 37 to 82 at 2.0 out of 255. The DVD control bar was checked the same way. Also records that Wonder Ink session-only cookies are confirmed working on the endpoint, which settles the question 0.1.34 shipped as unverified and tells us the session is in cookies rather than localStorage. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a0f2b74d05
commit
a1a218b29f
+39
-23
@@ -22,6 +22,7 @@ from classroom_config import ( # noqa: E402
|
||||
read_device_config,
|
||||
scaled,
|
||||
)
|
||||
from classroom_icons import ScalableIcon # noqa: E402
|
||||
|
||||
# Optional on purpose. The dock is the one thing the classroom cannot
|
||||
# afford to lose, so a missing DVD helper simply disables disc detection
|
||||
@@ -112,27 +113,18 @@ def themed_icon(*names):
|
||||
|
||||
|
||||
def custom_icon(filename, size):
|
||||
path = ICON_DIR / filename
|
||||
"""A classroom SVG icon, drawn at the display's real pixel density."""
|
||||
icon = ScalableIcon(filename, size, ICON_DIR)
|
||||
|
||||
if not path.exists():
|
||||
return None
|
||||
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
||||
str(path),
|
||||
size,
|
||||
size,
|
||||
True,
|
||||
)
|
||||
|
||||
return Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
return icon if icon.usable else None
|
||||
|
||||
|
||||
def throbber_frames(size):
|
||||
def throbber_frames(size, scale):
|
||||
"""A white arc, drawn once per frame of one revolution.
|
||||
|
||||
Pre-rendered into pixbufs and swapped into a Gtk.Image rather than
|
||||
drawn live, so the busy state stays an ordinary image and the button
|
||||
underneath keeps behaving exactly as it does with an icon.
|
||||
Drawn at the display's real density like the icons, so the busy
|
||||
state is not the one soft thing in a sharp dock. The surface carries
|
||||
the device scale, so everything below is in logical units.
|
||||
"""
|
||||
if cairo is None:
|
||||
return None
|
||||
@@ -142,7 +134,13 @@ def throbber_frames(size):
|
||||
frames = []
|
||||
|
||||
for index in range(THROBBER_FRAMES):
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
|
||||
surface = cairo.ImageSurface(
|
||||
cairo.FORMAT_ARGB32,
|
||||
size * scale,
|
||||
size * scale,
|
||||
)
|
||||
surface.set_device_scale(scale, scale)
|
||||
|
||||
context = cairo.Context(surface)
|
||||
|
||||
context.set_source_rgba(1, 1, 1, 1)
|
||||
@@ -153,9 +151,7 @@ def throbber_frames(size):
|
||||
context.arc(size / 2, size / 2, radius, start, start + math.pi * 1.4)
|
||||
context.stroke()
|
||||
|
||||
frames.append(
|
||||
Gdk.pixbuf_get_from_surface(surface, 0, 0, size, size)
|
||||
)
|
||||
frames.append(surface)
|
||||
|
||||
return frames
|
||||
|
||||
@@ -166,12 +162,32 @@ class Throbber(Gtk.Image):
|
||||
def __init__(self, size):
|
||||
super().__init__()
|
||||
|
||||
self.frames = throbber_frames(size)
|
||||
self.size = size
|
||||
self.frames = throbber_frames(size, 1)
|
||||
self.drawn_at = 1
|
||||
self.frame = 0
|
||||
self.timer = None
|
||||
|
||||
if self.frames:
|
||||
self.set_from_pixbuf(self.frames[0])
|
||||
self.set_from_surface(self.frames[0])
|
||||
|
||||
self.connect("realize", lambda *_: self.rebuild())
|
||||
self.connect("notify::scale-factor", lambda *_: self.rebuild())
|
||||
|
||||
def rebuild(self):
|
||||
scale = self.get_scale_factor() or 1
|
||||
|
||||
if scale == self.drawn_at:
|
||||
return
|
||||
|
||||
frames = throbber_frames(self.size, scale)
|
||||
|
||||
if not frames:
|
||||
return
|
||||
|
||||
self.frames = frames
|
||||
self.drawn_at = scale
|
||||
self.set_from_surface(self.frames[self.frame])
|
||||
|
||||
def usable(self):
|
||||
return bool(self.frames)
|
||||
@@ -189,7 +205,7 @@ class Throbber(Gtk.Image):
|
||||
|
||||
def advance(self):
|
||||
self.frame = (self.frame + 1) % len(self.frames)
|
||||
self.set_from_pixbuf(self.frames[self.frame])
|
||||
self.set_from_surface(self.frames[self.frame])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user