simvx.graphics.draw2d¶
Scopes¶
A node transforms and clips its drawing with context managers::
with canvas.transform(a, b, c, d, tx, ty):
canvas.rect((0, 0), (10, 10), fill=(1, 1, 1, 1))
with canvas.clip((0, 0, 100, 100)):
canvas.text("inside", (4, 4), fill=(1, 1, 1, 1))
The bare push_transform / pop_transform / push_clip / pop_clip
pairs remain, because the frame driver opens and closes those scopes across
separate calls as it walks the tree, but drawing code uses the scopes.
Line weight¶
A 2D weight is measured in the space of the points it is drawn with. A
width and a circle radius are in the same units as the positions passed
beside them: under a Camera2D they are world units and scale with zoom, and
under a screen-pinned CanvasLayer they are device pixels, because the
coordinates are.
One policy, every primitive, both drawing surfaces: the immediate one here and
the recording one the retained item pipeline captures through
(render2d.item_builder._OpRecorder).
The mechanism is that weights expand into geometry before the active transform is applied, never after: a thick line becomes an oriented quad and a thick rectangle outline becomes four bars in the caller’s own space, and those corners are then transformed like any other point. Expanding after the transform would fix the weight to post-camera pixels, which the retained path cannot reproduce (it captures camera-free geometry and applies the camera per-vertex at submit), so the same call would draw two different widths depending on which surface happened to record it.
The one exception, and it is shared by both surfaces: a weight of 1.0 or less is a hairline. It rides the 1-pixel line pipeline, which draws one device pixel whatever the camera is doing. The branch is taken on the weight as requested, so both surfaces always agree on which pipeline a call lands on.
Immediate-mode 2D drawing API for the Vulkan backend.
Seven primitives, one entry point each, and geometry always as tuples:
canvas.rect(pos, size, *, fill=None, stroke=None, width=1.0, blend="alpha")
canvas.circle(centre, radius, *, fill=None, stroke=None, width=1.0, segments=32)
canvas.line(*points, stroke=None, width=1.0)
canvas.polygon(points, *, fill=None, stroke=None, width=1.0, blend="alpha")
canvas.text(text, pos, *, fill=None, scale=1.0, align=HAlign.LEFT, valign=VAlign.TOP, ...)
canvas.texture(source, pos, size, *, region=None, tint=None, ...)
canvas.nine_patch(source, pos, size, *, texture_size, margins=..., ...)
fill and stroke are both colours: fill paints the interior,
stroke paints the edge and width is how heavy that edge is. A shape with
both is one call, so there is no filled flag to get the wrong way round.
None means no paint of that kind, which is why a shape asked for neither a
fill nor a stroke raises instead of quietly drawing nothing.
tint on a texture is the exception, and it is not a paint: it multiplies the
sampled pixels, so None means untinted rather than invisible.
A fill may also be a :class:~simvx.core.gradient.Gradient, which is how a
ramp stays a value rather than becoming a second spelling of rect.
Colour values are RGBA float tuples in [0, 1] (3-tuples default alpha to 1.0).
Designers thinking in 0-255 / hex should use
simvx.core.properties.Colour.from_rgb255(...) or Colour.hex(...) to
convert to the canonical float form.
Every call appends one Op to Draw2D._ops; Draw2DPass walks that list
in submission order. Submission order is the GPU order; there is no other
ordering mechanism.
Module Contents¶
Classes¶
Vulkan-backed immediate-mode 2D drawing API. |
Data¶
API¶
- simvx.graphics.draw2d.__all__¶
[‘Draw2D’, ‘UI_VERTEX_DTYPE’]
- class simvx.graphics.draw2d.Draw2D¶
Bases:
simvx.graphics.draw2d_transform.Draw2DTransformMixin,simvx.graphics.draw2d_text.Draw2DTextMixin,simvx.graphics.draw2d_texture.Draw2DTextureMixinVulkan-backed immediate-mode 2D drawing API.
Owns the ordered ops list (
_ops) and the scissor clip stack (_clip_stack/_current_clip). Every Draw2D submission appends a singleOpto_ops;Draw2DPasswalks the list in order and coalesces adjacent same-(kind, clip, tex_id) ops into one GPU draw. Submission order is the GPU order; there is no other ordering mechanism.push_clip/pop_clip/reset_clipare the only public hooks that mutate state without emitting an op.- classmethod push_post_layer(band: int) None¶
Enter a per-post CanvasLayer band: tag every op emitted until pop (web).
Mirrors :meth:
push_screen_transformas a context the tree walk opens when it reaches a CanvasLayer with anenvironmentset; the band is itslayervalue (the same key the desktop item pipeline bands on). Stored on a one-deep stack: a post CanvasLayer never nests inside another post CanvasLayer, so a single slot + saved-value suffices.
- classmethod pop_post_layer() None¶
Leave the current per-post CanvasLayer band (restore the previous).
- classmethod push_clip(x: int, y: int, w: int, h: int) None¶
Push a scissor clip rect; nested clips intersect with the current clip.
A node drawing in its local space passes a rect in that space; it maps through the node’s affine to the box it covers.
- classmethod pop_clip() None¶
Pop the last clip rect, restoring the previous one.
- classmethod reset_clip() None¶
Clear the clip stack and current clip, restoring full-screen drawing.
- classmethod rect(pos, size, *, fill=None, stroke=None, width=1.0, blend='alpha') None¶
Draw a rectangle: filled with
fill, edged withstroke, or both.fillis a colour or a :class:~simvx.core.gradient.Gradient, which ramps across the rectangle.strokeis the edge colour andwidthits weight: 1 or less is a hairline on the line pipeline, heavier becomes four filled bars grown inward from the rectangle’s bounds, measured in the same units asposandsize(see the weight policy in this module’s docstring).blendis one of"alpha"(default),"add"(additive glow/flash), or"multiply"(dst*src darkening overlay); it applies to the fill and to thick strokes (a hairline always alpha-blends on the line pipeline).
- classmethod line(*points, stroke=None, width=1.0) None¶
Draw an open polyline through
points.Two points or many, passed loose (
line(a, b, c)) or as one sequence (line(pts)). The run is open: a closed outline ispolygon(points, stroke=...).width <= 1rides the 1-px line pipeline; heavier expands each segment into an oriented filled quad, so the weight is GPU-honoured rather than driver-clamped. The quads are built around the points before the active transform is applied, so the weight is in the same units as they are (see the weight policy in this module’s docstring).
- classmethod circle(centre, radius, *, fill=None, stroke=None, width=1.0, segments=32) None¶
Draw a circle: filled with
fill, edged withstroke, or both.radiusis in the same units ascentre(see the weight policy in this module’s docstring), so a zoomed camera scales it. The ring is built in those units and its points are transformed, so a rotated or non-uniform transform draws the ellipse it should rather than a circle of the mean scale.
- classmethod polygon(points, *, fill=None, stroke=None, width=1.0, blend='alpha') None¶
Fill and/or outline an arbitrary closed polygon.
filltriangulates via ear-clipping so concave shapes render correctly; convex ones hit a fast triangle-fan path.strokecloses the ring back to the first point, as a hairline loop atwidth <= 1and as one mesh of oriented quads above it.blendis one of"alpha"(default),"add", or"multiply"and applies to the fill (a hairline outline always alpha-blends on the line pipeline).
- classmethod clear(r=0, g=0, b=0)¶
- classmethod present()¶
- classmethod push_transform(a, b, c, d, tx, ty)¶
- classmethod pop_transform()¶
- classmethod push_screen_transform(a, b, c, d, tx, ty)¶
- classmethod push_node_transform(a, b, c, d, tx, ty)¶
- classmethod transform(a, b, c, d, tx, ty)¶
- classmethod clip(rect)¶
- classmethod set_font(path: str | None = None, size: int = 48) None¶
- classmethod text(text, pos=None, *, fill=None, scale=1.0, align: simvx.core.alignment.HAlign | str = HAlign.LEFT, valign: simvx.core.alignment.VAlign | str = VAlign.TOP, stroke=None, stroke_width=0.0, rect=None, fit_to_width=False, min_scale=None, cell_width=0.0, rotation=0.0)¶
- classmethod text_height(text, scale=1.0)¶
- classmethod text_ascent(scale=1.0)¶
- classmethod text_size(text, scale=1.0)¶
- classmethod text_pixels(text: str, scale: float = 1.0, colour: Any = (1.0, 1.0, 1.0, 1.0)) numpy.ndarray¶
- classmethod fit_scale(text, max_width, *, base_scale=1.0, min_scale=None, cell_width=0.0)¶
- classmethod text_width(text, scale=1, *, cell_width=0.0)¶
- classmethod register_texture(png_data: bytes) int¶
- classmethod register_texture_with_id(texture_id: int, png_data: bytes) None¶
- classmethod texture(source, pos, size, *, region=None, tint=None, rotation: float = 0.0, blend: str = 'alpha', filter: str = 'linear')¶
- classmethod nine_patch(source, pos, size, *, texture_size, margins=(0.0, 0.0, 0.0, 0.0), draw_centre: bool = True, tint=None, filter: str = 'linear')¶