The latest Objo Studio release brings a substantial update to Web Canvas. Drawing code can now use the same page properties, methods and application data as the rest of your Web app.
Load some data, update a property, call Canvas.Refresh(), and draw it. That familiar sequence is the main reason for this change.
When I introduced Web Projects, I described how Canvas handlers ran inside a WebAssembly worker in the browser. This had a useful advantage: drawing and local interaction could happen without a trip to the server.
However, it also introduced an awkward boundary. The application server owned one instance of your page, while Canvas code worked with separate browser-side state. A value changed by a button handler or populated from a database was not automatically the value your Canvas could draw.
The compiler prevented unsafe sharing, but that still left developers having to think about where their page’s data lived before they could display it.
I decided to revisit the architecture.
The new Web Canvas runs its handlers on the server, against the actual page instance. The browser receives the resulting drawing instructions and the images needed to display them.
This means a button can change a page property and refresh a Canvas. An asynchronous operation can fetch data, store its results and request a redraw. Two Canvases on the same page can draw different views of the same objects.
You write ordinary Objo code in familiar Canvas event handlers:
Private Property Caption As String = "Ready"
# In UpdateButton's Pressed() event
Caption = "The page has changed"
DisplayCanvas.Refresh()
# In DisplayCanvas's Paint() event
g.DrawingColour = Colour.White
g.FillRectangle(0, 0, g.Width, g.Height)
g.DrawingColour = Colour.Blue
g.DrawText(Caption, 12, 12)
Caption is the same property in both handlers. There is no second drawing model to publish or keep synchronised.
The same approach scales to a plot backed by database results, a diagram built from application objects, or a custom control whose appearance depends on several other controls. Each browser session continues to have its own application and page state.
Paint remains synchronous. Once the Canvas has its initial browser measurements, Refresh() runs the drawing code before returning. Displaying the result in the browser happens asynchronously. For data loading, the natural pattern is to await the operation, update your state, and then refresh.
Moving drawing execution to the server also gave me an opportunity to bring the drawing API much closer to Desktop Canvas.
The work covers shapes, paths, clipping, transforms, gradients, dashed lines, text and Pictures. Text measurement and drawing now use the same layout results, with a packaged default font and support for multilingual text. That helps labels fit the space you measured for them.
Pictures are more useful too. You can draw into a Picture, use it in a Canvas, and take a snapshot with Canvas.ToPicture(). The snapshot comes from the server’s completed drawing, so it does not require reading pixels back from the browser.
There is quite a bit of work behind making those drawings arrive efficiently.
A small visual change should usually need only a small update. Objo retains completed drawing frames and can send a compact difference from an earlier frame. It can handle scattered edits, inserted drawing commands and changes that replace whole sections of a drawing.
In one benchmark containing 10,000 drawing commands, five scattered changes required 818 bytes of transmitted update data for a full drawing representation of about 130 KB. That is a result for a specific workload, but it demonstrates why incremental delivery matters for detailed drawings with small changes.
The browser also waits until a replacement frame and all its required images are ready before displaying it. While an image is arriving or being decoded, the previous complete drawing stays visible. Earlier frames retain the exact image revisions they were drawn with, even if application code has since changed the Picture.
There is an unavoidable trade-off: an interaction that needs application code now includes a server round trip.
Cursor and focus feedback remain local, and drawing frames can travel through a bounded pipeline without waiting for a separate acknowledgement before every update. This helps keep animation moving over higher-latency connections. It cannot remove the delay between an input reaching the server and its result returning.
Input behaviour has received attention alongside rendering. Pointer capture and cancellation help keep drawing gestures consistent when the pointer leaves the Canvas. Mouse wheel input uses the same logical units as Desktop Canvas while preserving fractional trackpad movement. Applications can opt into capturing touch drawing gestures, while ordinary page scrolling remains the default.
Responsive sizing has also been improved. Reading a Canvas’s measured dimensions no longer accidentally fixes its size, and the bundled Freehand example keeps its edges drawable when zoomed.
Overall these changes should make working with the Canvas control in Web Apps feel just like it does in Desktop Apps.