-
Notifications
You must be signed in to change notification settings - Fork 0
Design
This page has info on overall design. See:
-
Simply stated, the design principles of GoGi are those of Go itself: simplicity, elegance, power, speed, friendliness, ease-of-use. The overall goal was to create a GUI framework that leverages the unique strengths of Go, and create a fun and exciting new domain where Go can proliferate.
-
As much as possible is common across platforms, using a single simple, coherent codebase, with well-defined event inputs and rendering results on a generic image structure -- could easily render offscreen or in tests with simulated events etc. Also allows 2D to be easily embedded in 3D or anything else.
- now using the glfw framework for backend -- provides minimalist convenient cross-platform way to access opengl and vulkan -- necessary for 3D toolkit.
-
old: Adapted the shiny driver code for handling all the OS-specific "backend" parts, contained in
gi/oswin
directory (15k LOC total) -- pure Go for Linux and Windows, and an unavoidable 1k lines of (painful) objective-C code for interfacing with cocoa on MacOs. Sends mouse, keyboard, gesture, window events to gi.Window, and provides basic bitmap (framebuffer) for rendering onto. Originally used go.wde and some elements retained from that design.
-
Leverage existing dominant standards for all arbitrary things like names of style parameters, etc. HTML / CSS / SVG standards used to maximize ease of porting existing web apps.
-
Efficient scenegraph-based design: the full extent of everything is represented "behind the scenes" in the scenegraph, and only the relevant portion of that graph that is actually visible is rendered. For example, in a
TableView
orTreeView
with 1000's of elements, theLayout
just provides an offset based on (automatic) scrollbar(s), and visible elements render themselves and receive window events, while invisible elements do neither. This results in high efficiency scrolling and visualization of even very large, complex scenes. -
Code it once principle: the Views system provides an "automatic" GUI interface that handles a lot of common tasks, so you don't have to write a bunch of repetitive boilerplate code for many common use-cases. The Go
reflect
system is used extensively here.
Rendering in the 2D Gi is based on the SVG2 spec: https://www.w3.org/TR/SVG2/Overview.html, and renders directly to an Image struct (Viewport2D
).
The 3D Gi (gi3d) is rooted in a gi3d.Scene
node, and is based a gpu abstraction layer in oswin/gpu
that currently has an OpenGL 3.3 implementation but we will be targeting Vulkan in a later version -- it renders onto a gpu.Framebuffer
which is captured as a gpu.Texture
(downsampled from multisampling) and then blitted onto the overall window in the proper order relative to the surrounding and possibly overlaying 2D content.
Any number of such (nested or otherwise) Viewport nodes can be created and they are all then composted into the final underlying bitmap of the Window.
Within a given rendering parent (Viewport2D
or gi3d.Scene
), only nodes of the appropriate type (gi.Node2D
or gi3d.Node3D
) may be used.
Planned: There are nodes to embed a Viewport2D bitmap within a gi3d.Scene
. For a 2D viewport in a 3D scene, it acts like a texture and can be mapped onto a plane or anything else.
The overall parent gi.Window
always provides a 2D viewport as its base rendering element, and the window uses gpu.Texture
objects for rendering efficiently (i.e., the viewport image is drawn to the texture using opengl).
The GoKi framework supports multiple sub-trees within a given node, by using fields that are Ki
nodes. This introduces a bit of extra complexity, but it pays off for e.g., the TreeView
widget, which manages its own Parts
for the widget itself, but also has Children which are the child nodes of the tree. Without the Parts
, the widget would have to manage all these different children out of a common child pool, creating lots of complexity there. Having completely separate trees makes all of that very clear, and enables the Config
method to work transparently.
-
GoKi and GoGi follow a "Open Source" design philosophy, which refers not to the licensing but rather to the avoidance of unexported, hidden methods and fields.
-
There is very little hiding of fields, methods (i.e., lowercase names): the implementation is designed to be the "obvious" way to implement it using idiomatic, standard Go elements. We do not anticipate it ever changing significantly, and there is no need in Go to obsess about binary compatibility, which seems to completely hamstring projects such as Qt, causing an insane amount of extra work and code obfuscation. Furthermore, everything is designed to be easily subclassed and re-implemented to suit custom needs, with a conscious effort to provide functionality in elemental components that can easily be modified and recombined.
- "Only hacks need to be hidden"
- ...therefore, keep it clean and public as much as possible!
-
More on the above, potentially-controversial design choice: In all my experience using other toolkits, I have never found it problematic to rename some methods or adapt to some API changes, but have often found it highly problematic to change some little bit of functionality to suit my needs, in many cases requiring massive code duplication and hoop-jumping just because the toolkit wanted to protect me from myself. Thank you very much, but I'm a grownup and I can take care of myself, and I don't need you to put up huge walls around everything to try to keep me safe..
- so... "GoGi is a toolkit for grownups!" You get direct access to everything, and you accept full responsibility for whatever you decide to do with it, and you know how to use Find/Replace (e.g., in Gide ) if we end up changing some things down the road.
-
A corollary of the above is minimizing the use of setter / getter methods. This also may be somewhat controversial, but most fields are used during the next render update, which is controlled with the UpdateStart / End mechanism from GoKi, and do not need any kind of further action. Only when there is a specific case when a setter or getter is actually required is it implemented (e.g., for mutex protection, mainly on shared-access types such as
Window
). This does mean that the programmer must remember to look for those cases -- following example code typically makes this clear, and we have attempted to document everything clearly in the comments -- use GoDoc (or the source) when in doubt.