Graphite icon indicating copy to clipboard operation
Graphite copied to clipboard

Add a workspace/panels module to the Editor to support multiple panels and documents at once

Open Keavon opened this issue 2 years ago • 0 comments

At the moment, the Vue frontend hard-codes the workspace layout to show a Document panel (and tabs for other Document panels) on the left column and the Properties and Layer Tree panels on the right column. We don't support multiple panels of the same type because each panel component currently subscribes to messages.

On the frontend side, this should be remedied by moving any subscriptions (subscribeJsMessage() calls) in a .vue component to a state provider. Then the panels can inject that state provider and reactively use the data. This way, multiple panels (i.e. multiple panel components) can use the same state, or the state provider can be set up to store copies for the multiple panels that it is in charge of where that's necessary.

On the backend side, everything is currently strewn around in various places (mostly dumped in document_message_handler.rs) in a way that doesn't conceptually correspond to a system of panels and the messages which handle the logic for those panels. We should create a folder/module in the root of the Editor called workspace. In it, a workspace_message_handler.rs handles the logic dealing with the layout like docking panels to different places, resizing the gutters between panels, and the overall column-row grid hierarchy which should be serialized to IndexedDB storage. In a subfolder called panels, we'll have *_panel_message_handler.rs files that are in charge of holding and sending the layouts using the new Rust-based layouts system. Currently these are layouts are awkwardly strewn around the application by applying impl PropertyHolder to random structs like the document message handler. It's doubly awkward when it's necessary for the same struct to support multiple layouts, like how we have the options bar for both the Document and Layer Tree panels in document_message_handler.rs defined in fn update_document_widgets() and fn layer_tree_options() instead of the fn properties() of impl PropertyHolder. Each of these layouts should be moved to a struct owned by each respective editor/workspace/panels/*_panel_message_handler.rs file.

In the case of the Document panel, we need the capability to display multiple instances of the same document in different panels. They need to support independent pan/tilt/zoom, snapping settings, overlays settings, normal/wireframe render modes, and active tools. This means that we need to store the document settings (like many of the things set in the options bar) on a per-panel instance basis rather than being part of the document in document_message_handler.rs. Or in the case of pan/tilt/zoom, no longer take the approach of setting these values on the root of the Graphene document itself (this change will also make it easier to get rid of the fact that undo/redo states affect pan/tilt/zoom which is undesirable). Moving code out of DocumentMessageHandler also means we can split out and reduce the size of that monolithic code file, which is a good thing for sure!

To handle rendering to the viewport of each panel with the same document, we need to route the concept of rendering through the DocumentPanelMessageHandler so it can provide Graphene's render function with information like the pan/tilt/zoom and the normal/wireframe render modes. When dirty, a re-render needs to occur, but ideally we would only dirty the document if something changed within the visible part of each viewport (not the culled items outside, see #608). So if you have two document panels open with the viewport showing different parts of the canvas, editing should only make the viewports which contain non-culled layers re-render for being dirtied. Conceptually, the idea of sending the rendered SVG of the document to the frontend is akin to the new system for sending widget layouts to the frontend as serde's JSON format, so the updated SVG viewport render should be routed the same way to the corresponding Document panel's viewport.

Lastly, the actual task of writing the JS to interactively drag tabs and panels to undock/dock will be necessary, but that's tracked by issue #195. But it may be possible to work around having to implement that feature at the same time as the rest of the refactors and work described in this PR. I'd like to be involved helping with the JS for panel docking since it's important to take an approach that implements it cleanly in the frontend architecture. But that and this issue can probably be done in either order, since that is mostly just the frontend Vue and interactive JS event handling aspect while this is the data backing.

Keavon avatar May 21 '22 21:05 Keavon