src/flit/foundation/render_object

Search:
Group by:

RenderObject: the layout and paint tree. Each RenderObject knows how to measure itself given parent Constraints and paint itself onto a Canvas. Mirrors Flutter's RenderObject / RenderBox.

Concrete render objects live in flit/rendering/*. Each is instantiated by a RenderObjectWidget's createRenderObject and kept up-to-date by updateRenderObject.

Types

Canvas = ref object of RootObj
  size*: Size                ## current surface size in logical pixels.
  opacityStack*: seq[float32] ## stack of opacity multipliers. Top is the active value;
                              ## `pushOpacity` pushes `top * alpha` and `popOpacity` pops.
                              ## Empty stack means 1.0 (opaque).

Abstract drawing surface. Concrete backends override the draw methods below: SdlCanvas for desktop, WebCanvas for the JS target, EmbeddedCanvas for framebuffer rendering.

Tracks an opacity stack so the Opacity widget can attenuate the alpha of every primitive painted inside its subtree.

HitTestEntry = object
  target*: RenderObject
  local*: Offset
A single entry in a hit-test path: the render object the pointer was inside, plus the position translated into that render object's local coordinate space.
HitTestResult = ref object
  path*: seq[HitTestEntry]
Result of a hit-test walk. The path is leaf-first: index 0 is the deepest render object the pointer touched.
PaintingContext = ref object
  canvas*: Canvas
  offset*: Offset
Carries the current canvas and the absolute origin of whoever is painting into it. Pass to paintChild to descend into a subtree.
RenderObject = ref object of RootObj
  parent*: RenderObject
  constraints*: Constraints
  sizeOpt*: Option[Size]
  offset*: Offset
  needsLayout*: bool
  needsPaint*: bool
  attached*: bool
  debugLabel*: string
Base of the render tree. Subclasses override performLayout, paint, and (optionally) hitTest. Fields:
  • parent: parent render object, set by attachment.
  • constraints: last constraints received from layout().
  • sizeOpt: size assigned by setSize during layout. None means "not yet laid out".
  • offset: position within the parent (in parent's coord space). Some render objects use this; others store offsets in their own parent-data structs.
  • needsLayout: true when a future layout pass should re-run performLayout. Set by markNeedsLayout.
  • needsPaint: similar for repainting.
  • attached: true after attach(), false after detach().
  • debugLabel: optional name shown by debugDescribe.

Procs

proc applyOpacity(c: Canvas; color: uint32): uint32 {.inline, ...raises: [],
    tags: [], forbids: [].}

Returns color with its alpha multiplied by the current opacity. Backends call this on every draw so RenderOpacity just pushes and pops without each shape knowing about opacity. Inlined because every primitive call goes through here on the hot paint path.

Input: a packed 0xAARRGGBB value. Output: same color with the alpha channel scaled by currentOpacity(c). Returns the input unchanged if opacity is effectively 1.0 or the stack is empty.

proc attach(r: RenderObject) {....raises: [], tags: [], forbids: [].}
Marks r as attached to the render tree. Lifecycle hook reserved for future use.
proc currentOpacity(c: Canvas): float32 {....raises: [], tags: [], forbids: [].}
Returns the current opacity multiplier (top of the stack, or 1.0 if the stack is empty).
proc debugDescribe(r: RenderObject): DiagnosticsNode {....raises: [], tags: [],
    forbids: [].}
Returns a debug DiagnosticsNode showing the render object's type, size, offset and optional debugLabel. Subclasses can override (via a normal proc with same signature is hard in Nim; typically you provide your own wrapper).
proc detach(r: RenderObject) {....raises: [], tags: [], forbids: [].}
Marks r as detached.
proc layout(r: RenderObject; c: Constraints) {....raises: [Exception],
    tags: [RootEffect], forbids: [].}

Called by the parent to lay out r under constraints c. Stores the constraints, runs performLayout, clears the dirty flag. Skips the inner layout pass entirely when c matches the previous call AND needsLayout is false (relayout fast path; mirrors Flutter's RenderObject.layout).

Side effects: may call into the subtree's performLayout and set sizeOpt / child offsets.

proc markNeedsLayout(r: RenderObject) {....raises: [], tags: [], forbids: [].}
Marks r and all ancestors as needing layout. Idempotent; if r was already dirty the walk stops immediately. Call this when a property changes that affects sizing.
proc markNeedsPaint(r: RenderObject) {....raises: [Exception], tags: [RootEffect],
                                       forbids: [].}
Marks r and all ancestors as needing repaint. Call this when a property changes that affects only appearance (color, etc.), not layout. Also invokes absorbPaintMark on every render object along the walk so cache-bearing nodes (RepaintBoundary) can invalidate their cached output.
proc newPaintingContext(canvas: Canvas; offset = OffsetZero): PaintingContext {.
    ...raises: [], tags: [], forbids: [].}
Builds a PaintingContext. The offset is the absolute origin of the render object that "owns" this context (i.e., where its (0,0) maps to on the canvas).
proc paintChild(ctx: PaintingContext; child: RenderObject; offset: Offset) {.
    ...raises: [Exception], tags: [RootEffect], forbids: [].}
Recursively paints a child render object. offset is the child's position relative to ctx's origin. The child paints at the absolute position ctx.offset + offset. Safe to call with child == nil (no-op).
proc popOpacity(c: Canvas) {....raises: [], tags: [], forbids: [].}
Pops the topmost opacity. Safe to call on an empty stack (no-op).
proc pushOpacity(c: Canvas; alpha: float32) {....raises: [], tags: [], forbids: [].}
Pushes a new opacity onto the stack. The new value is currentOpacity * clamp(alpha, 0, 1), so nested opacity widgets multiply.
proc setSize(r: RenderObject; s: Size) {....raises: [], tags: [], forbids: [].}
Records s as this render object's size. Called from performLayout.
proc size(r: RenderObject): Size {....raises: [], tags: [], forbids: [].}
Returns the size assigned by setSize during the last layout, or SizeZero if not yet laid out.

Methods

method absorbPaintMark(r: RenderObject) {.base, ...raises: [], tags: [],
    forbids: [].}
Hook called for every render object visited during a markNeedsPaint walk. RenderRepaintBoundary overrides this to flip its cacheDirty flag so the next paint pass re-rasterizes the cached sub-canvas. Default no-op for ordinary render objects.
method clear(c: Canvas; color: uint32) {.base, ...raises: [], tags: [], forbids: [].}
Fills the entire canvas with color. Typically called by the runner at the start of each frame.
method clipRect(c: Canvas; r: Rect) {.base, ...raises: [], tags: [], forbids: [].}
Restricts subsequent drawing to the rectangle r. Use save and restore around the clip to scope it.
method compositeSubCanvas(c: Canvas; sub: Canvas; offset: Offset; size: Size) {.
    base, ...raises: [], tags: [], forbids: [].}
Copies the contents of sub onto c at the given absolute offset, scaled (or not) to size. The intent is a single GPU-side texture blit; backends are free to also batch opacity / clip if the canvas state currently has any. Default no-op.
method createSubCanvas(c: Canvas; w, h: int): Canvas {.base, ...raises: [],
    tags: [], forbids: [].}
Returns a new Canvas of size w x h that can be drawn into independently and later composited back onto c via compositeSubCanvas. The sub-canvas shares the same pixel format as c. Backends that don't support off-screen surfaces return nil; callers must treat that as "no caching available" and fall back to direct painting.
method drawCircle(c: Canvas; center: Offset; radius: float32; fill: uint32) {.
    base, ...raises: [], tags: [], forbids: [].}
Fills a circle centered at center with the given radius.
method drawImage(c: Canvas; image: pointer; src, dst: Rect) {.base, ...raises: [],
    tags: [], forbids: [].}
Blits a region src of image (a backend-specific pointer) to the region dst on the canvas.
method drawLine(c: Canvas; p0, p1: Offset; color: uint32; width: float32) {.
    base, ...raises: [], tags: [], forbids: [].}
Strokes a line from p0 to p1 with the given color and width in logical pixels.
method drawRect(c: Canvas; r: Rect; fill: uint32) {.base, ...raises: [], tags: [],
    forbids: [].}
Fills r with the ARGB color fill. Backends override; base is a no-op (useful for recording / counting in tests).
method drawRRect(c: Canvas; r: RRect; fill: uint32) {.base, ...raises: [],
    tags: [], forbids: [].}
Fills a rounded rectangle.
method drawText(c: Canvas; text: string; pos: Offset; color: uint32;
                fontSize: float32; fontFamily: string) {.base, ...raises: [],
    tags: [], forbids: [].}
Draws text at pos (top-left of the first glyph) in color with the given fontSize and fontFamily. Backends look up the font by family name.
method hitTest(r: RenderObject; htResult: HitTestResult; position: Offset): bool {.
    base, ...raises: [], tags: [], forbids: [].}

Tests whether position (in this render object's local coordinate space) is inside r. Default adds an entry for r and returns true. Container subclasses override to iterate children first.

Inputs:

  • htResult: the HitTestResult to append to. Entries are added leaf-first (deepest hit first).
  • position: pointer location in this render object's local coordinate space.

Returns: true if the pointer hit this render object.

method paint(r: RenderObject; ctx: PaintingContext; offset: Offset) {.base,
    ...raises: [], tags: [], forbids: [].}
method performLayout(r: RenderObject) {.base, ...raises: [], tags: [], forbids: [].}
Computes this render object's size given r.constraints (set by the parent's layout call) and lays out children. Subclasses MUST override and end by calling setSize with the final size.
method restore(c: Canvas) {.base, ...raises: [], tags: [], forbids: [].}
Pops the most recently saved transform/clip state.
method rotate(c: Canvas; radians: float32) {.base, ...raises: [], tags: [],
    forbids: [].}
Rotates the canvas by radians around the current origin.
method save(c: Canvas) {.base, ...raises: [], tags: [], forbids: [].}
Pushes the current transform/clip state.
method scale(c: Canvas; sx, sy: float32) {.base, ...raises: [], tags: [],
    forbids: [].}
Scales the canvas by (sx, sy) around the current origin.
method translate(c: Canvas; dx, dy: float32) {.base, ...raises: [], tags: [],
    forbids: [].}
Translates the canvas origin by (dx, dy).