Skillversitas

Custom Widgets

A custom widget is a Widget subclass that combines stock widgets (or a canvas) into a reusable unit with its own properties and events - the "icon button," the "recipe card," the "stat row" that appears across an app. It is the composition tool of the toolkit: the layout chapter arranged stock widgets, the properties chapter gave state its reactive form, and the custom widget is where the two meet into a named, reusable component. This chapter builds the three canonical shapes - a composite widget (children in the rule), a canvas widget (the drawing is the content), and a widget with custom events - and gives the naming and property conventions that keep a component library clean.

Composite: Children Defined in the Rule

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import StringProperty, NumericProperty

class StatRow(BoxLayout):
    """A label + value row: the unit that repeats across a stats screen."""
    title = StringProperty("")
    value = StringProperty("0")
    # expose the inner widgets so the app can reach them if needed
    value_label = ObjectProperty(None)

    def __init__(self, **kw):
        super().__init__(**kw)
        self.orientation = "horizontal"
        self.spacing = 8
        self.padding = 6

        self.add_widget(Label(text="title", size_hint_x=0.5))
        self.value_label = Label(text="0", size_hint_x=0.5, halign="right")
        self.add_widget(self.value_label)

        # the properties drive the children - the composite is reactive
        self.bind(title=lambda i, v: setattr(self.children[1], "text", v))
        self.bind(value=self._set_value)

    def _set_value(self, *args):
        self.value_label.text = args[1]

The composite builds its children in __init__ (or in the KV rule, the declarative alternative) and exposes a small property surface - title, value - that the parent sets, with the bindings inside the widget wiring the properties to the inner widgets. The parent sees StatRow(title="Steps", value="12") and does not know or care that it is two labels; the internals are the component's business. This is the composition-over-inheritance principle from the OOP tutorial applied to UI: the StatRow has labels, it does not inherit label behavior. The KV alternative (a <StatRow>: rule with the children and title:<< bindings) is the same structure declared instead of constructed - use the rule when the children are static, __init__ when they are built from data.

Canvas Widget: The Drawing Is the Content

from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse
from kivy.properties import ColorProperty, NumericProperty

class Avatar(Widget):
    """A colored circle with an initial - the profile avatar."""
    initial = StringProperty("?")
    color = ColorProperty([0.3, 0.5, 0.8, 1])
    size_hint = (None, None)
    size = (80, 80)

    def on_size(self, *args):
        self._redraw()

    def _redraw(self):
        self.canvas.clear()
        with self.canvas:
            Color(*self.color)
            r = min(self.width, self.height)
            Ellipse(pos=(self.x, self.y), size=(r, r))
            # the initial, centered
            from kivy.graphics import Text
            run = Text(text=self.initial, font_size=r * 0.4,
                       color=[1, 1, 1, 1])
            run.center = self.center

The canvas widget's content is the drawing itself: an Ellipse for the circle, a Text instruction for the initial, all bound to the widget's properties (color, initial, the size). The on_size redraw keeps it correct across layout reflows. This shape covers the "the component is a picture" cases - avatars, badges, charts, custom indicators - where there are no stock children to compose and the canvas is the whole UI. The convention is the same: a small property surface (what the parent sets), a redraw that is a pure function of those properties, and the drawing rebuilt when the inputs change. The size_hint: (None, None) plus explicit size gives it a fixed footprint in a layout, which is the normal case for an icon-sized component.

Custom Events: Extending the Dispatcher

from kivy.uix.widget import Widget
from kivy.event import EventDispatcher

class SearchField(Widget, EventDispatcher):
    """A field that emits on_query_submitted when the user commits."""
    __events__ = ("on_query_submitted",)

    def submit(self, text):
        # the public method that fires the event
        self.dispatch("on_query_submitted", text)

    # the consumer binds like any Kivy event:
    # field.bind(on_query_submitted=handler)
    # where handler(instance, query) is the signature

When a component's interaction is richer than a single on_press - a search field that "submits a query," a picker that "selects an item" - the clean interface is a custom event. Mixing in EventDispatcher and declaring __events__ gives the widget named events with dispatch/bind, so the consumer writes field.bind(on_query_submitted=handler) - the same vocabulary as every built-in event. The component fires the event (self.dispatch("on_query_submitted", text)) at the moment the interaction completes, and the consumer is decoupled from the component's internals. This is the EventDispatcher layer from the event chapter used at the component level: the component says "here is the signal," and the app decides what it means. Reserve it for real interaction boundaries; a plain method callback is enough for the simple cases.

Component Conventions

  • CapWords names for components (StatRow, Avatar), matching the widget classes they extend - the reader sees the type immediately.
  • A small property surface. Expose the inputs the parent sets (title, value, color); keep the inner widgets and the canvas as implementation detail.
  • Bind inside, not outside. The property-to-child wiring lives in the component's __init__/__init__ bindings; the parent sets properties and does not reach into the children.
  • Events for interaction boundaries. A component that completes an action emits a custom event (or uses the standard on_press when it is a button); the consumer binds.
  • One component, one job. A StatRow shows a stat; it does not also navigate, fetch, or style the screen. The composition of components is the screen's job.

Summary

A custom widget is a Widget subclass with three canonical shapes: the composite (stock children built in __init__ or a KV rule, exposed through a small property surface with the bindings inside), the canvas widget (the drawing is the content, a pure function of its properties, redrawn on on_size), and the event-emitter (an EventDispatcher mixin with __events__ for interaction boundaries the consumer binds to). The conventions are the composition discipline from the OOP tutorial applied to UI: CapWords names, a minimal public property surface, internal bindings, events for the signals that cross the component boundary, and one job per component. Master these and a Kivy app stops being a pile of stock widgets and becomes a small library of named, reusable components that the screens compose - which is the maintainable shape.