Search guides and the APICtrl K
  1. API reference
  2. Native GUI

gfx/component.h

Component: the tree, and the mouse.

#include <sonore/gfx/component.h>
On this page

Design notes

What turns a drawing into an interface

The graphics stack can draw a knob. A component tree is what makes that knob a THING: something with bounds, that knows when it was clicked, that repaints only itself, and that can be put inside a panel which can be put inside a window.

The three rules that are easy to get wrong

MOUSE CAPTURE. After a button goes down on a component, every drag and the release belong to THAT component until the button comes up -- even when the pointer has left its bounds entirely. Without it, dragging a slider fast hands the second half of the gesture to whatever is next door, and the slider sticks. It is the single most important rule here and the one that looks unnecessary until a user drags quickly.

Z-ORDER. Children paint in order, so the last is on top; hit testing walks them in REVERSE, so the topmost is asked first. Getting those the same way round means clicks land on whatever is underneath.

DAMAGE IS IN ROOT COORDINATES. A component asking to be repainted knows its own coordinates and nothing about where it sits on screen. Converting at the point of request means the root can union damage from anywhere in the tree without walking it.

Ownership

A component does NOT own its children, which is the usual arrangement and the right one. A plugin editor holds its widgets as members and adds them; the tree is a view of who is inside whom, not a lifetime. Owning them would mean every editor allocated its controls, which is the wrong default for something built once and painted for hours.

Types 7

What a stylus reports beyond where it is.

Normalised so a control never sees a platform's units: Windows reports tilt in degrees and rotation in whole degrees, Cocoa reports tilt as -1..1 and rotation in degrees counter-clockwise. A brush that scaled its width by tiltX would otherwise need three formulas.

Where the events a peer is about to deliver came from.

Set on the router by a peer that knows -- the Win32 pointer handler, the Cocoa tablet path -- immediately before it calls mouseDown/Move/Up, and put back to a plain mouse after. Every peer that knows nothing leaves it alone, so every event that has ever been delivered is still a mouse event with no pressure, exactly as before.

One pointer event, as the component receiving it sees it.

Built by the MouseRouter for each delivery, so every position is already in the receiver's own logical coordinates (the Backing divided out the display scale on the way in), alongside the root position and where the button went down. A plain value: a handler that wants it later keeps a copy, and eventComponent in that copy is a raw pointer nothing keeps alive. Mirrors the reference's MouseEvent, with the pen and touch fields flattened in.

A key, as a component sees it.

One struct for both halves of what every platform reports separately: a KEY (an arrow, Return, Tab -- things with no character) and a CHARACTER (what the user actually typed, after the keyboard layout, dead keys and any input method have had their say).

Both are here because a text field needs both and cannot get them from one another: character is 0 for an arrow key, and keyCode cannot tell an apostrophe from whatever the layout puts on that physical key. Windows sends WM_KEYDOWN and WM_CHAR, X11 has XLookupString, Cocoa has charactersIgnoring- Modifiers -- three shapes, one struct.

What is being dragged.

A string description and the component it came from, which between them are enough for every drag a plugin actually does: "modulator 3" onto a knob, a preset onto a slot, a sample onto a pad. A typed payload would need the container to know every type a plugin might invent.

A node of the interface tree: something with bounds that paints itself, holds children, and is told about the mouse, the keyboard and drags.

Mirrors the reference's Component. A control subclasses it and overrides the hooks it needs -- paint() and resized() first, then the mouse, key, focus and drag callbacks -- and calls repaint() when what it shows changes. Bounds are in the parent's coordinates and in LOGICAL units; nothing in the tree knows the display's scale. The rules for capture, z-order and damage, and why children are not owned, are in the file comment.

Lifetime: a parent holds raw pointers to children it does not own. The link is undone by whichever side is destroyed first, and a dying component is forgotten by every MouseRouter, so deleting a focused or captured control is safe; what is not safe is using a child after its owner destroyed it. A component is a node, not a value: it cannot be copied or moved.

Message thread only. Nothing here is synchronised; a value that changes on the audio thread reaches a component through a timer or an async callback, never by calling into the tree.

Turns raw pointer input into component events.

Kept out of Component because it is per-WINDOW state -- who has capture, where the button went down, which component the pointer is over -- and a copy of it inside every component would be state with one true owner and a thousand places to be wrong.

Functions 2

  • fninline std::vector<MouseRouter*>& liveRouters()
    Every router alive in this process.
    More

    A router holds raw pointers to the components it is focused on, hovering over, capturing and dragging. Nothing told it when one of those was destroyed, so a plugin removing a focused control left the router writing into freed memory on the next key press -- which AddressSanitizer caught as a stack-use-after-scope, and which on a real heap is silent until it is not.

    forget() existed and had to be called by hand, which is a contract nobody remembers. This makes it automatic: a Component's destructor tells every router, and there are one or two of them per process -- one per open window. The walk is over a list that short.

    gfx::liveRouters · component.h:269
  • fninline MouseRouter* routerFor(const Component* c)
    The router dispatching into the tree c belongs to, or null.
    More

    A component normally has no business knowing about the router: input arrives at it and it responds. The exception is FOCUS, which is a request rather than a response -- a readout that turns into a text field on a double click has to hand the keyboard to that field, and there is nothing else that can.

    Found by walking to the root and matching it against the live routers, rather than by storing a pointer in every component. There is one router per window and a handful of windows; the walk is the depth of the tree and happens when somebody clicks, not when anything paints.

    gfx::routerFor · component.h:2124

Enums 2

  • enumenum class MouseInputSourceType
    Which kind of pointer an event came from. A mouse unless a peer that can tell (Win32 WM_POINTER, Cocoa's tablet events) says otherwise.
    gfx::MouseInputSourceType · component.h:60
  • enumenum class MouseCursor
    What the pointer should look like over a component.
    More

    A small closed set rather than an arbitrary image, because every one of these exists on all three platforms under a different name and a custom cursor would need a bitmap, a hotspot and three ways to install it -- for a plugin whose controls are knobs and sliders.

    The resize grip written before this shipped with an ordinary arrow over it, which is the clearest possible way to tell somebody a control does nothing.

    gfx::MouseCursor · component.h:285
↑↓ to move↵ to openEvery public name in the headers, and every guide