Setting Up a Canvas Painter
Painters are custom rendering components that can draw using the Canvas API. They attach to UI elements and execute during the paint phase, allowing you to add custom vector graphics, animations, and effects.
Basic Painter Structure
A painter extends the Painter base class and implements two key methods:
using EvolveUI;
using EvolveUI.Rendering;
using UnityEngine;
using Canvas = EvolveUI.Rendering.Canvas;
public class MyPainter : Painter {
private Canvas canvas;
// Called when the painter is attached to an element
public override PaintDetach Attach(ElementId elementId, PaintAttachment attachment) {
// Create the canvas instance
canvas = new Canvas(Assets);
// Return a detach handler to clean up when the element is destroyed
return new PaintDetach(this, static (paintInfo) => {
((MyPainter)paintInfo.cookie).canvas.Dispose();
});
}
// Called every frame to render
protected override void Paint(Gfx gfx, StyleResult styleResult) {
// Call Next() to continue the paint chain (can be before or after your drawing)
Next();
// Clear previous frame's geometry
canvas.Clear();
// Draw using the Canvas API
canvas.SetFillColor(255, 100, 100, 255);
canvas.FillRect(0, 0, 200, 100);
// Submit the canvas to be rendered
gfx.DrawCanvas(canvas);
}
}
Key Concepts
The Attach Method
Attach is called once when the painter is first connected to an element. Use it to:
- Create your
Canvasinstance - Load textures or fonts
- Initialize any state
The Assets property provides access to UIAssetManager for loading resources.
The PaintDetach Handler
Return a PaintDetach to clean up resources when the element is destroyed:
return new PaintDetach(this, static (paintInfo) => {
var painter = (MyPainter)paintInfo.cookie;
painter.canvas.Dispose();
// Clean up other resources...
});
The cookie parameter passes your painter instance to the static delegate.
The Paint Method
Paint is called every frame. The parameters provide:
gfx- Graphics context for submitting draw calls and accessing time infostyleResult- The element's computed styles and layout information
Calling Next()
Next() continues the paint chain to other painters and the default element rendering. Where you call it matters:
// Draw AFTER the element (overlay)
protected override void Paint(Gfx gfx, StyleResult styleResult) {
Next(); // Element renders first
// Your drawing goes on top
canvas.Clear();
canvas.SetFillColor(255, 0, 0, 128);
canvas.FillRect(0, 0, 100, 100);
gfx.DrawCanvas(canvas);
}
// Draw BEFORE the element (background)
protected override void Paint(Gfx gfx, StyleResult styleResult) {
canvas.Clear();
canvas.SetFillColor(0, 0, 255, 128);
canvas.FillRect(0, 0, 100, 100);
gfx.DrawCanvas(canvas);
Next(); // Element renders on top
}
Complete Example
Here's a painter that draws an animated gradient background:
using EvolveUI;
using EvolveUI.Rendering;
using UnityEngine;
using Canvas = EvolveUI.Rendering.Canvas;
public class AnimatedGradientPainter : Painter {
private Canvas canvas;
private float time;
public override PaintDetach Attach(ElementId elementId, PaintAttachment attachment) {
canvas = new Canvas(Assets);
return new PaintDetach(this, static (paintInfo) => {
((AnimatedGradientPainter)paintInfo.cookie).canvas.Dispose();
});
}
protected override void Paint(Gfx gfx, StyleResult styleResult) {
Next();
canvas.Clear();
time += gfx.timeInfo.deltaTime;
// Get the element's layout size
var size = styleResult.GetLayoutSize();
float width = size.x;
float height = size.y;
// Animated color cycling
float hue1 = (time * 30) % 360;
float hue2 = (time * 30 + 180) % 360;
Color color1 = Color.HSVToRGB(hue1 / 360f, 0.7f, 0.9f);
Color color2 = Color.HSVToRGB(hue2 / 360f, 0.7f, 0.9f);
// Draw gradient background
canvas.SetLinearGradient(0, 0, width, height,
new Color32((byte)(color1.r * 255), (byte)(color1.g * 255), (byte)(color1.b * 255), 255),
new Color32((byte)(color2.r * 255), (byte)(color2.g * 255), (byte)(color2.b * 255), 255));
canvas.FillRect(0, 0, width, height);
gfx.DrawCanvas(canvas);
}
}
Accessing Element Information
The StyleResult provides access to the element's computed properties:
protected override void Paint(Gfx gfx, StyleResult styleResult) {
// Layout information
var size = styleResult.GetLayoutSize(); // Element dimensions
var position = styleResult.GetLayoutPosition(); // Element position
// Style properties
float opacity = styleResult.Opacity;
Color textColor = styleResult.TextColor;
Color bgColor = styleResult.BackgroundColor;
// ... use these values in your drawing
}
Loading Resources
Load fonts and textures in the Attach method:
private Canvas canvas;
private VectorFont titleFont;
private TextureId patternTexture;
public override PaintDetach Attach(ElementId elementId, PaintAttachment attachment) {
canvas = new Canvas(Assets);
// Load a font
titleFont = Assets.LoadVectorFont("/path/to/font.ttf");
// Register a texture for patterns
Texture2D tex = Resources.Load<Texture2D>("MyPattern");
patternTexture = Assets.RegisterTexture(tex);
return new PaintDetach(this, static (paintInfo) => {
((MyPainter)paintInfo.cookie).canvas.Dispose();
});
}
protected override void Paint(Gfx gfx, StyleResult styleResult) {
Next();
canvas.Clear();
// Use the loaded font
canvas.SetFont(titleFont);
canvas.SetFontSize(32);
canvas.SetTextFillColor(Color.white);
canvas.Text(10, 10, 400, "Hello World");
// Use the pattern texture
canvas.SetPatternTexture(patternTexture);
canvas.SetPatternMode(PatternSpace.Bounds, PatternSizing.Tile, PatternRepeat.Repeat);
canvas.SetPatternTileSize(0.2f, 0.2f);
canvas.FillRect(0, 100, 200, 200);
canvas.ClearPattern();
gfx.DrawCanvas(canvas);
}
Paint Attachment Points
When attaching a painter to an element, you specify when it should run in the paint order:
| Attachment | Description |
|---|---|
BeforeEverything | Before any element rendering |
BeforeShadow | Before shadow rendering |
AfterShadow | After shadow, before frame |
BeforeFrame | Before frame/border rendering |
AfterFrame | After frame, before element content |
BeforeElement | Just before element content |
AfterElement | Just after element content |
BeforeChildren | Before child elements render |
AfterChildren | After child elements render |
AfterEverything | After all element rendering |
Skipping Default Rendering
You can skip the element's default rendering or its children:
protected override void Paint(Gfx gfx, StyleResult styleResult) {
// Skip the element's normal background/border rendering
SkipDefaultPainting = true;
// Skip rendering child elements
SkipChildrenPainting = true;
// Draw your custom content instead
canvas.Clear();
// ...
gfx.DrawCanvas(canvas);
Next();
}
Tips
Always call
canvas.Clear()at the start ofPaint()to remove the previous frame's geometry.Always call
gfx.DrawCanvas(canvas)to submit your drawing. Without this, nothing renders.Call
Next()to continue the paint chain. Omitting it will prevent other painters and default rendering.Dispose the canvas in the
PaintDetachhandler to avoid memory leaks.Use
gfx.timeInfo.deltaTimefor smooth animations that are frame-rate independent.