Esempio n. 1
0
    def _draw_transparent_background(self, context, lod):
        """ fill with the transparent background color """
        corner_radius = config.CORNER_RADIUS
        rect = self.get_keyboard_frame_rect()
        fill = self.get_background_rgba()

        fill_gradient = config.theme_settings.background_gradient
        if lod == LOD.MINIMAL or \
           fill_gradient == 0:
            context.set_source_rgba(*fill)
        else:
            fill_gradient /= 100.0
            direction = config.theme_settings.key_gradient_direction
            alpha = -pi/2.0 + pi * direction / 180.0
            gline = gradient_line(rect, alpha)

            pat = cairo.LinearGradient (*gline)
            rgba = brighten(+fill_gradient*.5, *fill)
            pat.add_color_stop_rgba(0, *rgba)
            rgba = brighten(-fill_gradient*.5, *fill)
            pat.add_color_stop_rgba(1, *rgba)
            context.set_source (pat)

        frame = self.can_draw_frame()
        if frame:
            roundrect_arc(context, rect, corner_radius)
        else:
            context.rectangle(*rect)
        context.fill()

        if frame:
            self.draw_window_frame(context, lod)
            self.draw_keyboard_frame(context, lod)
Esempio n. 2
0
    def _draw_transparent_background(self, context, lod):
        """ fill with the transparent background color """
        corner_radius = config.CORNER_RADIUS
        rect = self.get_keyboard_frame_rect()
        fill = self.get_background_rgba()

        fill_gradient = config.theme_settings.background_gradient
        if lod == LOD.MINIMAL or \
           fill_gradient == 0:
            context.set_source_rgba(*fill)
        else:
            fill_gradient /= 100.0
            direction = config.theme_settings.key_gradient_direction
            alpha = -pi / 2.0 + pi * direction / 180.0
            gline = gradient_line(rect, alpha)

            pat = cairo.LinearGradient(*gline)
            rgba = brighten(+fill_gradient * .5, *fill)
            pat.add_color_stop_rgba(0, *rgba)
            rgba = brighten(-fill_gradient * .5, *fill)
            pat.add_color_stop_rgba(1, *rgba)
            context.set_source(pat)

        frame = self.can_draw_frame()
        if frame:
            roundrect_arc(context, rect, corner_radius)
        else:
            context.rectangle(*rect)
        context.fill()

        if frame:
            self.draw_window_frame(context, lod)
            self.draw_keyboard_frame(context, lod)
Esempio n. 3
0
    def draw_gradient_key(self, cr, fill, line_width, lod):
        # simple gradients for fill and stroke
        fill_gradient   = config.theme_settings.key_fill_gradient / 100.0
        stroke_gradient = self.get_stroke_gradient()
        alpha = self.get_gradient_angle()

        rect = self.get_canvas_rect()
        self._build_canvas_path(cr, rect)
        gline = gradient_line(rect, alpha)

        # fill
        if self.show_face:
            if fill_gradient and lod:
                pat = cairo.LinearGradient (*gline)
                rgba = brighten(+fill_gradient*.5, *fill)
                pat.add_color_stop_rgba(0, *rgba)
                rgba = brighten(-fill_gradient*.5, *fill)
                pat.add_color_stop_rgba(1, *rgba)
                cr.set_source (pat)
            else: # take gradient from color scheme (not implemented)
                cr.set_source_rgba(*fill)

            if self.show_border:
                cr.fill_preserve()
            else:
                cr.fill()

        # stroke
        if self.show_border:
            if stroke_gradient:
                if lod:
                    stroke = fill
                    pat = cairo.LinearGradient (*gline)
                    rgba = brighten(+stroke_gradient*.5, *stroke)
                    pat.add_color_stop_rgba(0, *rgba)
                    rgba = brighten(-stroke_gradient*.5, *stroke)
                    pat.add_color_stop_rgba(1, *rgba)
                    cr.set_source (pat)
                else:
                    cr.set_source_rgba(*fill)
            else:
                cr.set_source_rgba(*self.get_stroke_color())

            cr.set_line_width(line_width)
        cr.stroke()
Esempio n. 4
0
    def draw_gradient_key(self, cr, fill, line_width, lod):
        # simple gradients for fill and stroke
        fill_gradient = config.theme_settings.key_fill_gradient / 100.0
        stroke_gradient = self.get_stroke_gradient()
        alpha = self.get_gradient_angle()

        rect = self.get_canvas_rect()
        self._build_canvas_path(cr, rect)
        gline = gradient_line(rect, alpha)

        # fill
        if self.show_face:
            if fill_gradient and lod:
                pat = cairo.LinearGradient(*gline)
                rgba = brighten(+fill_gradient * .5, *fill)
                pat.add_color_stop_rgba(0, *rgba)
                rgba = brighten(-fill_gradient * .5, *fill)
                pat.add_color_stop_rgba(1, *rgba)
                cr.set_source(pat)
            else:  # take gradient from color scheme (not implemented)
                cr.set_source_rgba(*fill)

            if self.show_border:
                cr.fill_preserve()
            else:
                cr.fill()

        # stroke
        if self.show_border:
            if stroke_gradient:
                if lod:
                    stroke = fill
                    pat = cairo.LinearGradient(*gline)
                    rgba = brighten(+stroke_gradient * .5, *stroke)
                    pat.add_color_stop_rgba(0, *rgba)
                    rgba = brighten(-stroke_gradient * .5, *stroke)
                    pat.add_color_stop_rgba(1, *rgba)
                    cr.set_source(pat)
                else:
                    cr.set_source_rgba(*fill)
            else:
                cr.set_source_rgba(*self.get_stroke_color())

            cr.set_line_width(line_width)
        cr.stroke()
Esempio n. 5
0
    def draw_dish_key(self, cr, fill, line_width, lod):
        canvas_rect = self.get_canvas_rect()
        if self.geometry:
            geometry = self.geometry
        else:
            geometry = KeyGeometry.from_rect(self.get_border_rect())
        size_scale_x, size_scale_y = geometry.scale_log_to_size((1.0, 1.0))

        # compensate for smaller size due to missing stroke
        canvas_rect = canvas_rect.inflate(1.0)

        # parameters for the base path
        base_rgba = brighten(-0.200, *fill)
        stroke_gradient = self.get_stroke_gradient()
        light_dir = self.get_light_direction() - pi * 0.5  # 0 = light from top
        lightx = cos(light_dir)
        lighty = sin(light_dir)

        key_offset_x, key_offset_y, key_size_x, key_size_y = \
                                            self.get_key_offset_size(geometry)
        radius_pct = max(config.theme_settings.roundrect_radius, 2)
        radius_pct = max(radius_pct, 2) # too much +-1 fudging for square corners
        chamfer_size = self.get_chamfer_size()
        chamfer_size = (self.context.scale_log_to_canvas_x(chamfer_size) +
                        self.context.scale_log_to_canvas_y(chamfer_size)) * 0.5

        # parameters for the top path, key face
        stroke_width  = self.get_stroke_width()
        key_offset_top_y = key_offset_y - \
            config.DISH_KEY_Y_OFFSET * stroke_width
        border = config.DISH_KEY_BORDER
        scale_top_x = 1.0 - (border[0] * stroke_width * size_scale_x * 2.0)
        scale_top_y = 1.0 - (border[1] * stroke_width * size_scale_y * 2.0)
        key_size_top_x = key_size_x * scale_top_x
        key_size_top_y = key_size_y * scale_top_y
        chamfer_size_top = chamfer_size * (scale_top_x + scale_top_y) * 0.5

        # realize all paths we're going to use
        polygons, polygon_paths = \
            self.get_canvas_polygons(geometry,
                                   key_offset_x, key_offset_y,
                                   key_size_x, key_size_y,
                                   radius_pct, chamfer_size)
        polygons_top, polygon_paths_top = \
            self.get_canvas_polygons(geometry,
                                   key_offset_x, key_offset_top_y,
                                   key_size_top_x - size_scale_x,
                                   key_size_top_y - size_scale_y,
                                   radius_pct, chamfer_size_top)
        polygons_top1, polygon_paths_top1 = \
            self.get_canvas_polygons(geometry,
                                   key_offset_x, key_offset_top_y,
                                   key_size_top_x, key_size_top_y,
                                   radius_pct, chamfer_size_top)

        # draw key border
        if self.show_border:
            if not lod:
                cr.set_source_rgba(*base_rgba)
                for path in polygon_paths:
                    rounded_polygon_path_to_cairo_path(cr, path)
                    cr.fill()
            else:
                for ipg, polygon in enumerate(polygons):
                    polygon_top = polygons_top[ipg]
                    path = polygon_paths[ipg]
                    path_top = polygon_paths_top[ipg]

                    self._draw_dish_key_border(cr, path, path_top,
                                               polygon, polygon_top,
                                               base_rgba, stroke_gradient,
                                               lightx, lighty)

        # Draw the key face, the smaller top rectangle.
        if self.show_face:
            if not lod:
                cr.set_source_rgba(*fill)
            else:
                # Simulate the concave key dish with a gradient that has
                # a sligthly brighter middle section.
                if self.id == "SPCE":
                    angle = pi / 2.0  # space has a convex top
                else:
                    angle = 0.0       # all others are concave
                fill_gradient   = config.theme_settings.key_fill_gradient / 100.0
                dark_rgba = brighten(-fill_gradient*.5, *fill)
                bright_rgba = brighten(+fill_gradient*.5, *fill)
                gline = gradient_line(canvas_rect, angle)

                pat = cairo.LinearGradient (*gline)
                pat.add_color_stop_rgba(0.0, *dark_rgba)
                pat.add_color_stop_rgba(0.5, *bright_rgba)
                pat.add_color_stop_rgba(1.0, *dark_rgba)
                cr.set_source (pat)

            for path in polygon_paths_top1:
                rounded_polygon_path_to_cairo_path(cr, path)
                cr.fill()
Esempio n. 6
0
    def draw_dish_key(self, context, rect, fill, line_width, lod):
        # compensate for smaller size due to missing stroke
        rect = rect.inflate(1.0)

        # parameters for the base rectangle
        w, h = rect.get_size()
        w2, h2 = w * 0.5, h * 0.5
        xc, yc = rect.get_center()
        radius_pct = config.theme_settings.roundrect_radius
        radius_pct = max(radius_pct,
                         2)  # too much +-1 fudging for square corners
        r, k = self.get_curved_rect_params(rect, radius_pct)

        base_rgba = brighten(-0.200, *fill)
        stroke_gradient = config.theme_settings.key_stroke_gradient / 100.0
        light_dir = self.get_light_direction()

        # parameters for the top rectangle, key face
        scale = config.theme_settings.key_stroke_width / 100.0
        border = config.DISH_KEY_BORDER
        border = (border[0] * scale, border[1] * scale)

        border = self.context.scale_log_to_canvas(border)
        offset_top = self.context.scale_log_to_canvas_y(
            config.DISH_KEY_Y_OFFSET)
        rect_top = rect.deflate(*border).offset(0, -offset_top)
        rect_top.w = max(rect_top.w, 0.0)
        rect_top.h = max(rect_top.h, 0.0)
        top_radius_scale = rect_top.h / float(rect.h)
        r_top, k_top = self.get_curved_rect_params(
            rect_top, radius_pct * top_radius_scale)

        # draw key border
        if self.show_border:
            if not lod:
                self.build_rect_path(context, rect)
                context.set_source_rgba(*base_rgba)
                context.fill()
            else:

                # lambert lighting
                edge_colors = []
                for edge in range(4):
                    normal_dir = edge * pi / 2.0  # 0 = light from top
                    I = cos(normal_dir - light_dir) * stroke_gradient * 0.8
                    edge_colors.append(brighten(I, *base_rgba))

                context.save()
                context.translate(xc, yc)

                # edge sections, edge 0 = top
                for edge in range(4):
                    if edge & 1:
                        p = (h2, w2)
                        p_top = [rect_top.h / 2.0, rect_top.w / 2.0]
                    else:
                        p = (w2, h2)
                        p_top = [rect_top.w / 2.0, rect_top.h / 2.0]

                    m = cairo.Matrix()
                    m.rotate(edge * pi / 2.0)
                    p0 = m.transform_point(-p[0] + r - 1,
                                           -p[1])  # -1 to fill gaps
                    p1 = m.transform_point(p[0] - r + 1, -p[1])
                    p0_top = m.transform_point(p_top[0] - r_top + 1,
                                               -p_top[1] + 1)
                    p1_top = m.transform_point(-p_top[0] + r_top - 1,
                                               -p_top[1] + 1)
                    p0_top = (p0_top[0], p0_top[1] - offset_top)
                    p1_top = (p1_top[0], p1_top[1] - offset_top)

                    context.set_source_rgba(*edge_colors[edge])
                    context.move_to(p0[0], p0[1])
                    context.line_to(p1[0], p1[1])
                    context.line_to(*p0_top)
                    context.line_to(*p1_top)
                    context.close_path()
                    context.fill()

                # corner sections
                for edge in range(4):
                    if edge & 1:
                        p = (h2, w2)
                        p_top = [rect_top.h / 2.0, rect_top.w / 2.0]
                    else:
                        p = (w2, h2)
                        p_top = [rect_top.w / 2.0, rect_top.h / 2.0]

                    m = cairo.Matrix()
                    m.rotate(edge * pi / 2.0)
                    p1 = m.transform_point(p[0] - r, -p[1])
                    p2 = m.transform_point(p[0], -p[1] + r)
                    pk0 = m.transform_point(p[0] - k, -p[1])
                    pk1 = m.transform_point(p[0], -p[1] + k)
                    p0_top = m.transform_point(p_top[0] - r_top, -p_top[1])
                    p2_top = m.transform_point(p_top[0], -p_top[1] + r_top)
                    p0_top = (p0_top[0], p0_top[1] - offset_top)
                    p2_top = (p2_top[0], p2_top[1] - offset_top)

                    # Fake Gouraud shading: draw a gradient between mid points
                    # of the lines connecting the base with the top rectangle.
                    gline = ((p1[0] + p0_top[0]) / 2.0,
                             (p1[1] + p0_top[1]) / 2.0,
                             (p2[0] + p2_top[0]) / 2.0,
                             (p2[1] + p2_top[1]) / 2.0)
                    pat = cairo.LinearGradient(*gline)
                    pat.add_color_stop_rgba(0.0, *edge_colors[edge])
                    pat.add_color_stop_rgba(1.0, *edge_colors[(edge + 1) % 4])
                    context.set_source(pat)

                    context.move_to(*p1)
                    context.curve_to(pk0[0], pk0[1], pk1[0], pk1[1], p2[0],
                                     p2[1])
                    context.line_to(*p2_top)
                    context.line_to(*p0_top)
                    context.close_path()
                    context.fill()

                context.restore()

        # Draw the key face, the smaller top rectangle.
        if self.show_face:
            if not lod:
                context.set_source_rgba(*fill)
            else:
                # Simulate the concave key dish with a gradient that has
                # a sligthly brighter middle section.
                if self.id == "SPCE":
                    angle = pi / 2.0  # space has a convex top
                else:
                    angle = 0.0  # all others are concave
                fill_gradient = config.theme_settings.key_fill_gradient / 100.0
                dark_rgba = brighten(-fill_gradient * .5, *fill)
                bright_rgba = brighten(+fill_gradient * .5, *fill)
                gline = gradient_line(rect, angle)

                pat = cairo.LinearGradient(*gline)
                pat.add_color_stop_rgba(0.0, *dark_rgba)
                pat.add_color_stop_rgba(0.5, *bright_rgba)
                pat.add_color_stop_rgba(1.0, *dark_rgba)
                context.set_source(pat)

            self.build_rect_path(context, rect_top, top_radius_scale)
            context.fill()
Esempio n. 7
0
    def draw_dish_key(self, cr, fill, line_width, lod):
        canvas_rect = self.get_canvas_rect()
        if self.geometry:
            geometry = self.geometry
        else:
            geometry = KeyGeometry.from_rect(self.get_border_rect())
        size_scale_x, size_scale_y = geometry.scale_log_to_size((1.0, 1.0))

        # compensate for smaller size due to missing stroke
        canvas_rect = canvas_rect.inflate(1.0)

        # parameters for the base path
        base_rgba = brighten(-0.200, *fill)
        stroke_gradient = self.get_stroke_gradient()
        light_dir = self.get_light_direction() - pi * 0.5  # 0 = light from top
        lightx = cos(light_dir)
        lighty = sin(light_dir)

        key_offset_x, key_offset_y, key_size_x, key_size_y = \
                                            self.get_key_offset_size(geometry)
        radius_pct = max(config.theme_settings.roundrect_radius, 2)
        radius_pct = max(radius_pct,
                         2)  # too much +-1 fudging for square corners
        chamfer_size = self.get_chamfer_size()
        chamfer_size = (self.context.scale_log_to_canvas_x(chamfer_size) +
                        self.context.scale_log_to_canvas_y(chamfer_size)) * 0.5

        # parameters for the top path, key face
        stroke_width = self.get_stroke_width()
        key_offset_top_y = key_offset_y - \
            config.DISH_KEY_Y_OFFSET * stroke_width
        border = config.DISH_KEY_BORDER
        scale_top_x = 1.0 - (border[0] * stroke_width * size_scale_x * 2.0)
        scale_top_y = 1.0 - (border[1] * stroke_width * size_scale_y * 2.0)
        key_size_top_x = key_size_x * scale_top_x
        key_size_top_y = key_size_y * scale_top_y
        chamfer_size_top = chamfer_size * (scale_top_x + scale_top_y) * 0.5

        # realize all paths we're going to use
        polygons, polygon_paths = \
            self.get_canvas_polygons(geometry,
                                   key_offset_x, key_offset_y,
                                   key_size_x, key_size_y,
                                   radius_pct, chamfer_size)
        polygons_top, polygon_paths_top = \
            self.get_canvas_polygons(geometry,
                                   key_offset_x, key_offset_top_y,
                                   key_size_top_x - size_scale_x,
                                   key_size_top_y - size_scale_y,
                                   radius_pct, chamfer_size_top)
        polygons_top1, polygon_paths_top1 = \
            self.get_canvas_polygons(geometry,
                                   key_offset_x, key_offset_top_y,
                                   key_size_top_x, key_size_top_y,
                                   radius_pct, chamfer_size_top)

        # draw key border
        if self.show_border:
            if not lod:
                cr.set_source_rgba(*base_rgba)
                for path in polygon_paths:
                    rounded_polygon_path_to_cairo_path(cr, path)
                    cr.fill()
            else:
                for ipg, polygon in enumerate(polygons):
                    polygon_top = polygons_top[ipg]
                    path = polygon_paths[ipg]
                    path_top = polygon_paths_top[ipg]

                    self._draw_dish_key_border(cr, path, path_top, polygon,
                                               polygon_top, base_rgba,
                                               stroke_gradient, lightx, lighty)

        # Draw the key face, the smaller top rectangle.
        if self.show_face:
            if not lod:
                cr.set_source_rgba(*fill)
            else:
                # Simulate the concave key dish with a gradient that has
                # a sligthly brighter middle section.
                if self.id == "SPCE":
                    angle = pi / 2.0  # space has a convex top
                else:
                    angle = 0.0  # all others are concave
                fill_gradient = config.theme_settings.key_fill_gradient / 100.0
                dark_rgba = brighten(-fill_gradient * .5, *fill)
                bright_rgba = brighten(+fill_gradient * .5, *fill)
                gline = gradient_line(canvas_rect, angle)

                pat = cairo.LinearGradient(*gline)
                pat.add_color_stop_rgba(0.0, *dark_rgba)
                pat.add_color_stop_rgba(0.5, *bright_rgba)
                pat.add_color_stop_rgba(1.0, *dark_rgba)
                cr.set_source(pat)

            for path in polygon_paths_top1:
                rounded_polygon_path_to_cairo_path(cr, path)
                cr.fill()
Esempio n. 8
0
    def draw_dish_key(self, context, rect, fill, line_width, lod):
        # compensate for smaller size due to missing stroke
        rect = rect.inflate(1.0)

        # parameters for the base rectangle
        w, h = rect.get_size()
        w2, h2 = w * 0.5, h * 0.5
        xc, yc = rect.get_center()
        radius_pct = config.theme_settings.roundrect_radius
        radius_pct = max(radius_pct, 2)  # too much +-1 fudging for square corners
        r, k = self.get_curved_rect_params(rect, radius_pct)

        base_rgba = brighten(-0.200, *fill)
        stroke_gradient = config.theme_settings.key_stroke_gradient / 100.0
        light_dir = self.get_light_direction()

        # parameters for the top rectangle, key face
        scale = config.theme_settings.key_stroke_width / 100.0
        border = config.DISH_KEY_BORDER
        border = (border[0] * scale, border[1] * scale)

        border = self.context.scale_log_to_canvas(border)
        offset_top = self.context.scale_log_to_canvas_y(config.DISH_KEY_Y_OFFSET)
        rect_top = rect.deflate(*border).offset(0, -offset_top)
        rect_top.w = max(rect_top.w, 0.0)
        rect_top.h = max(rect_top.h, 0.0)
        top_radius_scale = rect_top.h / float(rect.h)
        r_top, k_top = self.get_curved_rect_params(rect_top, radius_pct * top_radius_scale)

        # draw key border
        if self.show_border:
            if not lod:
                self.build_rect_path(context, rect)
                context.set_source_rgba(*base_rgba)
                context.fill()
            else:

                # lambert lighting
                edge_colors = []
                for edge in range(4):
                    normal_dir = edge * pi / 2.0  # 0 = light from top
                    I = cos(normal_dir - light_dir) * stroke_gradient * 0.8
                    edge_colors.append(brighten(I, *base_rgba))

                context.save()
                context.translate(xc, yc)

                # edge sections, edge 0 = top
                for edge in range(4):
                    if edge & 1:
                        p = (h2, w2)
                        p_top = [rect_top.h / 2.0, rect_top.w / 2.0]
                    else:
                        p = (w2, h2)
                        p_top = [rect_top.w / 2.0, rect_top.h / 2.0]

                    m = cairo.Matrix()
                    m.rotate(edge * pi / 2.0)
                    p0 = m.transform_point(-p[0] + r - 1, -p[1])  # -1 to fill gaps
                    p1 = m.transform_point(p[0] - r + 1, -p[1])
                    p0_top = m.transform_point(p_top[0] - r_top + 1, -p_top[1] + 1)
                    p1_top = m.transform_point(-p_top[0] + r_top - 1, -p_top[1] + 1)
                    p0_top = (p0_top[0], p0_top[1] - offset_top)
                    p1_top = (p1_top[0], p1_top[1] - offset_top)

                    context.set_source_rgba(*edge_colors[edge])
                    context.move_to(p0[0], p0[1])
                    context.line_to(p1[0], p1[1])
                    context.line_to(*p0_top)
                    context.line_to(*p1_top)
                    context.close_path()
                    context.fill()

                # corner sections
                for edge in range(4):
                    if edge & 1:
                        p = (h2, w2)
                        p_top = [rect_top.h / 2.0, rect_top.w / 2.0]
                    else:
                        p = (w2, h2)
                        p_top = [rect_top.w / 2.0, rect_top.h / 2.0]

                    m = cairo.Matrix()
                    m.rotate(edge * pi / 2.0)
                    p1 = m.transform_point(p[0] - r, -p[1])
                    p2 = m.transform_point(p[0], -p[1] + r)
                    pk0 = m.transform_point(p[0] - k, -p[1])
                    pk1 = m.transform_point(p[0], -p[1] + k)
                    p0_top = m.transform_point(p_top[0] - r_top, -p_top[1])
                    p2_top = m.transform_point(p_top[0], -p_top[1] + r_top)
                    p0_top = (p0_top[0], p0_top[1] - offset_top)
                    p2_top = (p2_top[0], p2_top[1] - offset_top)

                    # Fake Gouraud shading: draw a gradient between mid points
                    # of the lines connecting the base with the top rectangle.
                    gline = (
                        (p1[0] + p0_top[0]) / 2.0,
                        (p1[1] + p0_top[1]) / 2.0,
                        (p2[0] + p2_top[0]) / 2.0,
                        (p2[1] + p2_top[1]) / 2.0,
                    )
                    pat = cairo.LinearGradient(*gline)
                    pat.add_color_stop_rgba(0.0, *edge_colors[edge])
                    pat.add_color_stop_rgba(1.0, *edge_colors[(edge + 1) % 4])
                    context.set_source(pat)

                    context.move_to(*p1)
                    context.curve_to(pk0[0], pk0[1], pk1[0], pk1[1], p2[0], p2[1])
                    context.line_to(*p2_top)
                    context.line_to(*p0_top)
                    context.close_path()
                    context.fill()

                context.restore()

        # Draw the key face, the smaller top rectangle.
        if self.show_face:
            if not lod:
                context.set_source_rgba(*fill)
            else:
                # Simulate the concave key dish with a gradient that has
                # a sligthly brighter middle section.
                if self.id == "SPCE":
                    angle = pi / 2.0  # space has a convex top
                else:
                    angle = 0.0  # all others are concave
                fill_gradient = config.theme_settings.key_fill_gradient / 100.0
                dark_rgba = brighten(-fill_gradient * 0.5, *fill)
                bright_rgba = brighten(+fill_gradient * 0.5, *fill)
                gline = gradient_line(rect, angle)

                pat = cairo.LinearGradient(*gline)
                pat.add_color_stop_rgba(0.0, *dark_rgba)
                pat.add_color_stop_rgba(0.5, *bright_rgba)
                pat.add_color_stop_rgba(1.0, *dark_rgba)
                context.set_source(pat)

            self.build_rect_path(context, rect_top, top_radius_scale)
            context.fill()