TachyonUI 0.1.0
High-performance UI for building tools.
Loading...
Searching...
No Matches
Custom drawing

To draw your own shapes and text, register an element and attach a callback to it:

  • An element is one box in the frame's layout tree. Every control is made of elements.
  • RegisterElement adds an element and returns an ElementRef, a handle to it for this frame.
  • elementStorage.AddCustomDraw attaches a callback that runs at render time, after layout, when the element's final size and position are known.
ElementRef RegisterElement(Element&& element, LayoutOverride&& constraints);
ElementRef RegisterElement(Element&& element, std::string_view text, LayoutOverride&& constraints);
// ElementStorage:
ElementStorageResult AddCustomDraw(const ElementRef& ref, CustomDrawCallback callback);
ElementStorageResult AddCustomDrawAfter(const ElementRef& ref, CustomDrawCallback callback);
ElementStorageResult AddPostLayout(const ElementRef& ref, PostLayoutCallback callback);
Function Runs
AddCustomDraw Before the element's children are drawn
AddCustomDrawAfter After the whole subtree; call it once the children are closed
AddPostLayout After layout, before rendering. Can't draw; sees final geometry

AddCustomDrawAfter draws over the element's children, for example to strike them out:

const ElementRef ref = RegisterElement(Element{ .id = panelID }, LayoutOverride{}.FlexGrow(1));
VERIFY(elementStorage.BeginChildren());
Text("No longer used");
VERIFY(elementStorage.EndChildren()); // close the children first
VERIFY(elementStorage.AddCustomDrawAfter(ref, [this](CustomDrawContext& ctx)
{
const IntRect r = ctx.ElementRect();
const LineStyle line{ .colorStyleIndex = theme.lineWhiteStyle, .lineWidth = styleManager.Px(1.0f) };
ctx.RenderLine(r.position, r.position + r.size, line); // drawn over the text
}));

AddPostLayout only reads the final geometry, for example to keep a size for the next frame:

VERIFY(elementStorage.AddPostLayout(ref, [this](PostLayoutContext& ctx)
{
canvasSize = ctx.ElementInnerRect().size; // member: int2 canvasSize
}));

The callback gets a CustomDrawContext:

  • The default space is element-local:
    • (0, 0) is the top-left of the padding box
    • the pan and zoom of its ancestors apply
  • SetSpace(DrawSpace::Screen) switches to absolute layout pixels.
  • ScreenToCurrent(screenPos) converts a cursor position to the current space.
Element canvas{ .id = canvasID, .styleRef = theme.GetStyle("my-canvas") };
canvas.eventHandler = [this](ElementEvent&& event)
{
if (event.type == EventType::MouseMove)
pointerScreenPos = event.state.screenPos; // member: int2 pointerScreenPos
return false;
};
const ElementRef ref = RegisterElement(std::move(canvas), LayoutOverride::FixedSize(200, 100));
VERIFY(elementStorage.AddCustomDraw(ref, [this](CustomDrawContext& ctx)
{
ctx.RenderDisc(ctx.ScreenToCurrent(pointerScreenPos), styleManager.Px(4.0f), theme.lineWhiteStyle);
}));
Method Draws or returns
ElementRect(), ElementInnerRect() Border box and padding box, in the current space
GetElementRect(ref), GetInnerRect(ref) Another element's rects
RenderBox(rect, boxStyle, state, ...) Styled box
RenderQuad(rect, colorStyle, cornerRadius, ...) Filled rectangle
RenderLine(p0, p1, lineStyle) Line (also a four-point form)
RenderPolyline(points, closed, lineStyle, cornerRadius) Polyline
RenderCircle, RenderDisc, RenderDot Circle outline, filled disc, dot. The circle is exactly round; gradients and dashes run clockwise from the right.
RenderTriangle, RenderFilledPolygon Filled triangle, convex polygon. hardEdges (edges shared with another triangle) get no soft edge, so no seam shows.
RenderText(textId, rect) Text registered with textSystem->RegisterText

Colors are style indices:

  • for example theme.lineWhiteStyle
  • or one from styleManager.RegisterColorStyle
  • LineStyle has colorStyleIndex, lineWidth and dash settings
const ElementRef ref = RegisterElement(Element{ .id = canvasID, .styleRef = theme.GetStyle("my-canvas") },
LayoutOverride::FixedSize(200, 100));
VERIFY(elementStorage.AddCustomDraw(ref, [this](CustomDrawContext& ctx)
{
const IntRect r = ctx.ElementInnerRect();
const LineStyle line{ .colorStyleIndex = theme.lineWhiteStyle, .lineWidth = styleManager.Px(2.0f) };
ctx.RenderLine(r.position, r.position + r.size, line);
}));

The callback runs after OnGUI returns, so anything it captures must outlive the frame.


Previous: Other controls | Controls | Next: Default CSS classes