A Layout Designer For HTML Canvas
Crisp drawing and perfect look and feel is important for the projects those used by millions of people, like Collabora Online.
As you know, Collabora Online keeps your data safe on your server. Renders the files on the server side and enables users to work on large documents with many device types easily.
Documents are rendered in parts on the server, then they are sent to the client. Client merges the tiles and shows it to the user. Parts (tiles) should have no gaps between them and they should be crisp. They should have the very same look as when the document is opened with desktop version of Libre Office.
Also, documents can have dynamic UI elements, like row and column headers in Calc. When user scrolls the document, row and column numbers should be refreshed according to the viewed part of the document. So this quick drawings also need a canvas to be drawn on.
Having different canvas elements for drawing the UI and tiles was our first approach. But having them drawn on the same canvas would perform better of course. At the end, we prepared a layout designer for canvas.
This layout is created on HTML canvas element using the small class that you can view at CanvasSectionContainer.ts.
If you are interested on this class, you may also want to have a look at Collabora Online project at our github page.
You can also view our easy-hacks and even contribute to this great project:)
Developer creates a CanvasSectionContainer (s-container for short) class instance and gives it the canvas element that it will work with. s-container will handle all the mouse and touch events and propagate it to the sections it has. Dragging events are also handled by the s-container.
When s-container is created, it will have no sections. Developer can add as many sections as they need, using createSection or addSection methods.
Sections will have below properties (potential values are separated with '|'):
anchor: 'top left' | 'top right' | 'bottom left' | 'bottom right' (Examples: For row & column headers, anchor will be 'top left'. If we would want to draw something sticked to bottom, anchor would be 'bottom left' or 'bottom right').
One can also anchor sections to other sections' edges. Order is important, first variables are related to top or bottom, next ones are related to left or right.
1 - [["column header", "bottom", "some section name", "top", "top"], "left"]
Explanation: If "column header" section exists, its bottom will be new section's top. If "column header" doesn't exist but "some section name" exists, its top will be new section's top. If neither of them exists, canvas element's top will be used as a fallback anchor.
2 - [["column header", "bottom", "ruler", "bottom", "top"], ["row header", "right", "left"]]
position: [0, 0] | [10, 50] | [x, y] -> Related to anchor. Example: "bottom right": P(0, 0) is bottom right etc. myTopLeft variable is updated according to position and anchor.
size: [100, 100] | [10, 20] | [maxX, maxY] -> This doesn't restrict the drawable area, that is up to the implementation. Size is not important for expanded directions. Expanded size is assigned after calculations.
zIndex: Sections with highest zIndex will be drawn on top.
expand: 'right' | 'left' | 'top' | 'bottom' | 'left right top bottom' (any combination)
interactable: true | false -> If false, only general events will be fired (onDraw, newDocumentTopLeft, onResize) for this section. Example: Background drawings etc.
drawingOrder: Sections with the same zIndex values are drawn according to their drawingOrder values. Section with the highest drawing order is drawn on top (for specific zIndex).
So, in terms of being drawn on top, priority is: zIndex > drawingOrder.
processingOrder: Processing order feature is tricky, let's say you want something like this:------------------------
| top bar |
| left | | tiles |
| bar | | area |
Top bar's height will be static most probably. It needs to be processed first, so it can be expanded to right.
If top bar is processed after tiles area, since "tiles area" will most probably be expanded to all directions, it will leave no space for top bar. So, tiles are will be processed last.
For above scenario, processing orders would be (with the same zIndex):
* top bar -> 1
* left bar -> 2
* tiles area -> 3
And their "expand" properties would be like below:
* top bar: 'right' from position (0, 0)
* left bar: 'top bottom' from position (0, 200) -> So it will go up and find where top bar ends and then go down and find where canvas ends. One can also anchor left bar to top bar using "anchor" property.
* tiles area: "top bottom left right" from position like (300, 300) -> So it won't overlap with resulting positions of others. Developer can also use "anchor" property to avoid using pre-assumed positions.
Other than these properties, sections should implement the event handlers. s-container will catch events and propagate them to sections. There are almost all events for mouse and touch screens. For more details see canvas section container class.