Drawing¶
DrawArea provides a 2D drawing surface with paths, fills, strokes, gradients, transforms, and text.
Basic shapes¶
The on_draw callback receives a drawing context and the area dimensions:
"""Drawing shapes — rectangles, circles, triangles, and strokes."""
import math
import libui
from libui.declarative import App, Window, VBox, DrawArea, stretchy
def on_draw(ctx, area_w, area_h, clip_x, clip_y, clip_w, clip_h):
# Filled rectangle
path = libui.DrawPath()
path.add_rectangle(20, 20, 200, 100)
path.end()
blue = libui.DrawBrush()
blue.r, blue.g, blue.b, blue.a = 0.2, 0.4, 0.8, 1.0
ctx.fill(path, blue)
# Stroked circle
circle = libui.DrawPath()
circle.new_figure_with_arc(350, 70, 50, 0, 2 * math.pi, False)
circle.end()
red = libui.DrawBrush()
red.r, red.g, red.b, red.a = 0.8, 0.2, 0.2, 1.0
stroke = libui.DrawStrokeParams()
stroke.thickness = 3.0
stroke.cap = libui.LineCap.ROUND
ctx.stroke(circle, red, stroke)
# Filled triangle
tri = libui.DrawPath()
tri.new_figure(20, 200)
tri.line_to(120, 140)
tri.line_to(220, 200)
tri.close_figure()
tri.end()
green = libui.DrawBrush()
green.r, green.g, green.b, green.a = 0.2, 0.7, 0.3, 1.0
ctx.fill(tri, green)
# Bezier curve
bezier = libui.DrawPath()
bezier.new_figure(250, 200)
bezier.bezier_to(300, 120, 400, 220, 450, 150)
bezier.end()
purple = libui.DrawBrush()
purple.r, purple.g, purple.b, purple.a = 0.5, 0.0, 0.7, 1.0
sp = libui.DrawStrokeParams()
sp.thickness = 2.5
sp.cap = libui.LineCap.ROUND
ctx.stroke(bezier, purple, sp)
async def main():
app = App()
app.build(
window=Window(
"Drawing Shapes",
500,
250,
child=VBox(stretchy(DrawArea(on_draw=on_draw))),
)
)
app.show()
await app.wait()
libui.run(main())
The drawing workflow is:
Create a
DrawPathand add geometry (rectangles, arcs, lines)Call
path.end()to close the pathCreate a
DrawBrushwith colorCall
ctx.fill(path, brush)orctx.stroke(path, brush, stroke_params)
Path methods¶
Method |
Description |
|---|---|
|
Add a rectangle |
|
Start a new sub-path at a point |
|
Start with an arc |
|
Draw a line to a point |
|
Cubic bezier curve |
|
Close the current sub-path |
|
Finalize the path (required before use) |
Stroke parameters¶
DrawStrokeParams controls line appearance:
sp = libui.DrawStrokeParams()
sp.thickness = 3.0
sp.cap = libui.LineCap.ROUND # FLAT, ROUND, SQUARE
sp.join = libui.LineJoin.ROUND # MITER, ROUND, BEVEL
sp.set_dashes([10.0, 5.0]) # dash pattern
Gradients¶
Both linear and radial gradients are supported:
"""Drawing gradients — linear and radial gradient fills."""
import math
import libui
from libui.declarative import App, Window, VBox, DrawArea, stretchy
def on_draw(ctx, area_w, area_h, clip_x, clip_y, clip_w, clip_h):
# Linear gradient
rect = libui.DrawPath()
rect.add_rectangle(20, 20, 200, 100)
rect.end()
lin = libui.DrawBrush()
lin.type = libui.BrushType.LINEAR_GRADIENT
lin.x0, lin.y0 = 20, 20
lin.x1, lin.y1 = 220, 120
lin.set_stops(
[
(0.0, 1.0, 0.0, 0.0, 1.0), # red
(0.5, 1.0, 1.0, 0.0, 1.0), # yellow
(1.0, 0.0, 0.0, 1.0, 1.0), # blue
]
)
ctx.fill(rect, lin)
# Radial gradient
circle = libui.DrawPath()
circle.new_figure_with_arc(370, 70, 60, 0, 2 * math.pi, False)
circle.end()
rad = libui.DrawBrush()
rad.type = libui.BrushType.RADIAL_GRADIENT
rad.x0, rad.y0 = 370, 70 # center
rad.x1, rad.y1 = 370, 70 # focus (same as center)
rad.outer_radius = 60
rad.set_stops(
[
(0.0, 1.0, 1.0, 1.0, 1.0), # white center
(1.0, 0.2, 0.0, 0.6, 1.0), # purple edge
]
)
ctx.fill(circle, rad)
# Another linear gradient — vertical
rect2 = libui.DrawPath()
rect2.add_rectangle(20, 150, 430, 60)
rect2.end()
lin2 = libui.DrawBrush()
lin2.type = libui.BrushType.LINEAR_GRADIENT
lin2.x0, lin2.y0 = 20, 150
lin2.x1, lin2.y1 = 450, 150
lin2.set_stops(
[
(0.0, 0.0, 0.8, 0.0, 1.0), # green
(0.5, 0.0, 0.8, 0.8, 1.0), # teal
(1.0, 0.0, 0.0, 0.8, 1.0), # blue
]
)
ctx.fill(rect2, lin2)
async def main():
app = App()
app.build(
window=Window(
"Gradients",
500,
250,
child=VBox(stretchy(DrawArea(on_draw=on_draw))),
)
)
app.show()
await app.wait()
libui.run(main())
Gradient stops are tuples of (position, r, g, b, a) where position is 0.0 to 1.0.
Styled text¶
AttributedString supports rich text with attributes for weight, color, style, and more:
"""Drawing styled text — attributed strings with formatting."""
import libui
from libui.declarative import App, Window, VBox, DrawArea, stretchy
def on_draw(ctx, area_w, area_h, clip_x, clip_y, clip_w, clip_h):
# Create an attributed string with various styles
text = "Bold Colored Italic Underlined"
astr = libui.AttributedString(text)
# Bold (0-4)
astr.set_attribute(libui.weight_attribute(libui.TextWeight.BOLD), 0, 4)
# Red color (5-12)
astr.set_attribute(libui.color_attribute(0.8, 0.0, 0.0, 1.0), 5, 12)
# Italic (13-19)
astr.set_attribute(libui.italic_attribute(libui.TextItalic.ITALIC), 13, 19)
# Underline (20-30)
astr.set_attribute(libui.underline_attribute(libui.Underline.SINGLE), 20, 30)
font = {"family": "sans-serif", "size": 18.0}
layout = libui.DrawTextLayout(astr, font, area_w - 40)
ctx.text(layout, 20, 20)
# Second line with more attributes
text2 = "Large serif text with background highlight"
astr2 = libui.AttributedString(text2)
astr2.set_attribute(libui.family_attribute("serif"), 0, len(text2))
astr2.set_attribute(libui.size_attribute(24.0), 0, 5)
astr2.set_attribute(libui.background_attribute(1.0, 1.0, 0.0, 0.5), 26, 36)
font2 = {"family": "serif", "size": 16.0}
layout2 = libui.DrawTextLayout(astr2, font2, area_w - 40)
ctx.text(layout2, 20, 60)
async def main():
app = App()
app.build(
window=Window(
"Styled Text",
500,
150,
child=VBox(stretchy(DrawArea(on_draw=on_draw))),
)
)
app.show()
await app.wait()
libui.run(main())
Text attributes¶
Function |
Description |
|---|---|
|
Font weight (e.g., |
|
Italic style (e.g., |
|
Text color |
|
Background highlight |
|
Underline (e.g., |
|
Font family |
|
Font size in points |
Transforms¶
Use DrawMatrix for translations, rotations, and scaling:
matrix = libui.DrawMatrix()
matrix.set_identity()
matrix.rotate(center_x, center_y, degrees)
ctx.save()
ctx.transform(matrix)
# ... draw rotated content ...
ctx.restore()
Mouse events¶
DrawArea supports mouse interaction through callbacks:
DrawArea(
on_draw=on_draw,
on_mouse_event=on_mouse, # click, drag, move
on_mouse_crossed=on_crossed, # enter/leave
)
The mouse event dict contains: x, y, area_width, area_height, down, up, count, modifiers, held.
Call widget.queue_redraw_all() to trigger a repaint after changes.
ScrollingDrawArea¶
For content larger than the visible area, use ScrollingDrawArea:
ScrollingDrawArea(
on_draw=on_draw,
width=2000, # virtual canvas size
height=2000,
)