b93190fa9a03905f64dda0d41884115f69e77e46 — fabrixxm 15 days ago 9b7d6b9 master
Fix event progress bar in rounded corners
3 files changed, 87 insertions(+), 12 deletions(-)

M src/confy.css
M src/pages.py
M src/widgets.py
M src/confy.css => src/confy.css +8 -0
@@ 11,6 11,14 @@ frame > border, frame > list {
  border-radius: 8px;

list > .first {
  border-top-left-radius: 8px;
list > .last {
  border-bottom-left-radius: 8px;

/* remove margin and border on small width in first page
  and in other pages when leadlet is folded.

M src/pages.py => src/pages.py +15 -4
@@ 138,11 138,21 @@ class BaseListPage(BasePage):
        for w in self.listbox.get_children():

        lastgroup = None
        for obj in self.data:
            mn = self.build_row(obj)
        count = len(self.data)

        for n, obj in enumerate(self.data):
            mn = self.build_row(obj)

            # add "first" and "last" class to row
            # :first-child and :last-child doesn't work reliably, so we fake this
            # we need to know which row is first and wich is last to handle round
            # corners rendering the event progress indicator.
            # see widgets.EventActionRow._on_draw()
            if n == 0:
            if n == count-1:

    def get_objects(self):

@@ 525,7 535,8 @@ class EventDetailPage(BasePage):

        x = w * prc

        cr.set_source_rgb(0, 0, 0)
        cr.move_to(0, 1)
        cr.line_to(x, 1)

M src/widgets.py => src/widgets.py +64 -8
@@ 17,6 17,7 @@
from os import path
import datetime
import html
import math

from dateutil.tz import UTC

@@ 78,8 79,6 @@ def weblinkactionrowactivated(listbox, actionrow):
        Gio.app_info_launch_default_for_uri(actionrow.url, None)

class EventActionRow(Handy.ActionRow):
    def __init__(self, obj):
        super().__init__(activatable=True, selectable=False)

@@ 123,11 122,52 @@ class EventActionRow(Handy.ActionRow):

        self.connect("draw", self._on_draw)

    def _roundedclippath(self, cr, x, y, w, h, rtop, rbottom):
        cr.arc(x+rtop, y+rtop, rtop, math.pi, 3*math.pi/2)
        cr.line_to(x+w, y)
        cr.line_to(x+w, y+h)
        cr.line_to(x+w, y+h)
        #cr.arc(x+w-rtop, y+rtop, rtop, 3*math.pi/2, 0) # top-right round corner
        #cr.arc(x+w-rbottom, y+h-rbottom, rbottom, 0, math.pi/2) # bottom-right
        cr.arc(x+rbottom, y+h-rbottom, rbottom, math.pi/2, math.pi)

    def _on_draw(self, widget, cr):
        Draw event progress indicator in widget background.
        It's a tick line on left border from top whose lenght is relative to the
        percentage of event time elapsed.

        We need to handle list round corners. Gtk doesn't clip element like HTML
        when rendering with "border-radius" value, so our line will overflow the

        I've tried some way to do this:
        -   Drawin a clip path with a rounder corner, getting the rounding from css,
            defining a css rule like "row:first-child { border-top-left-radius: 8px; }"
            but we can't fetch "border-top-left-radius" value via pygobject
            (should be `self.get_style_context().get_property("border-top-left-radius", self.get_style_context().get_state())`
            but we get an exception, I think because it should return a GtkCssValue which pygobject doesn't know)
        -   I tought I could save current style context, add a new class, then render
            the backgrund with `Gtk.render_background()` wich can clip the draw, then
            restore the style context.
            I would then define the style matching the added class defining rounding
            and color, but it doesn't work.

        So now the row get a "first" or "last" class in `pages.BaseListPage.update_list()`.
        Color and corder radii are defined here in code instead of css.
        I use the clip path method. It get a top rounded corner if `self` has "first" class
        and has no header: a list header is rendered before the row itself, thus
        occupying the rounded corner spot. It get a bottom rounded corner if has
        "last" class. The a line of required lenght, color and width is drawn
        from top.

        now = datetime.datetime.now(UTC).replace(tzinfo=None)
        obj = self.obj

        h = self.get_allocated_height()
        totalh = self.get_allocated_height()

        dur = (obj.end.timestamp() - obj.start.timestamp())
        pos = (now.timestamp() - obj.start.timestamp())

@@ 138,13 178,29 @@ class EventActionRow(Handy.ActionRow):
            # set prc to 0 if the event is in the future or 1 if in the past
            prc = int(pos >= 0)

        y = h * prc

        cr.set_source_rgb(0, 0, 0)
        cr.move_to(1, 0)
        cr.line_to(1, y)
        x = 0
        y = 0
        h = totalh * prc
        w = 10

        # clip at rounded corners
        stylectx = self.get_style_context()
        rtop = 8 if stylectx.has_class("first") and self.get_header() is None else 0
        rbottom = 8 if stylectx.has_class("last") else 0

        # rounded clip path
        self._roundedclippath(cr, x, y-1, w, totalh+2, rtop, rbottom)

        # draw line
        cr.move_to(x, y)
        cr.line_to(x, h)


    def on_update(self, *args):
        icon_name = "starred-symbolic" if self.obj.starred else "non-starred-symbolic"
        self.toggle_btn.get_image().set_from_icon_name(icon_name, Gtk.IconSize.BUTTON)