[ANN] GoKi Trees and GoGi GUI

409 views
Skip to first unread message

Randall O'Reilly

unread,
May 4, 2018, 6:39:35 AM5/4/18
to golang-nuts
https://github.com/goki/goki — key demo in: https://github.com/goki/goki/tree/master/gi/examples/widgets

This is the first release of a new Go framework built around the Tree as a core data structure (Ki = Tree in Japanese), which includes as its first application a fully-native Go GUI (built on top of a modified version of the Shiny OS-specific backend drivers, supporting Mac, Linux, and Windows so far).

Building on the central idea in Go that having a few powerful data-structures is essential for making many problems easier to solve, the GoKi trees are an attempt to provide a powerful tree structure that can support things like scene graphs, DOM’s, parsing trees, etc.

The GoGi graphical interface system is a kind of “proof is in the pudding” test, which weighs in at under 20k LOC and provides a reasonably full-featured GUI — with a bit more work it should be able to do most of the stuff you can do in Qt, and already includes a (self) reflection-driven GUI designer.

The overall design is an attempt to integrate existing standards and conventions from widely-used frameworks, including Qt (overall widget design), HTML / CSS (styling), and SVG (rendering). Rendering in SVG is directly supported by the GoGi 2D scenegraph, with enhanced functionality for interactive GUI's. This 2D framework also integrates with a (planned) 3D scenegraph, to support interesting combinations of these frameworks. Currently GoGi is focused on desktop systems, but nothing prevents adaptation to mobile.

Right now the rendering is based off of a modified version of https://github.com/fogleman/gg, but I’m very interested in integrating the new rasterx system that Steven Wiley recently announced.

I’d be very interested in people’s impressions, suggestions, etc, and welcome all interested contributors (there’s certainly much more to do) — it would be great if this could provide the start for a widely-supported Go-native GUI framework! This was my first Go project after many years in C++ / Qt land, and I’m excited to join the community, and have really been impressed with the language and ecosystem etc. The contrast in complexity and build time between Qt and GoGi is really striking, and has kept me going despite the huge amount of effort it took to get this new project off the ground.. Cheers,

- Randy

matthe...@gmail.com

unread,
May 4, 2018, 11:09:33 AM5/4/18
to golang-nuts
Hi Randy, here’s a code review.

Thanks for the BSD license.

I prefer the look of a minimized import path, I would have put the title library at the top level (github.com/goki/ki).

To me the README doesn’t balance text and code examples well enough, I’d like to see more example uses and less text. In package ki I’d hope the library does enough work to not require so much README.

I think ki isn’t a bad package name after reading what it means but seeing the package used in app code won’t show the point as obviously as an English word for some people. tree.New() to me says “new data structure var” while ki.New() says “I have to read the docs now”.

(when I say 'app' I mean a program that uses your library or a program written to compile with a Go compiler; an application of the library, an application of the Go programming language)

The Ki interface is a code smell to me. Usually I interpret library interfaces as saying “the app provides an implementation of the interface and the library provides shared logic that uses the interface methods” or in this case I expect the ki lib will provide varying data structures and behaviors that implement the ki var. Just reading down to line 42 of ki.go I feel like this isn’t very Go-like.

You’re getting into generics territory with this:

func NewOfType(typ reflect.Type) Ki {

My view is idiomatic Go code tends to avoid this kind of thing due to maintenance and readability burden. I haven’t used your lib but I am concerned about it not being a big win over a per-app implementation. I can see you’ve done a lot of work to make generics work.

In type Node I would consider embedding Props.

I haven’t looked at all of the methods but do the methods on Node need to be to a pointer?

func (n *Node) Fields() []uintptr {

Seeing uintptr in a public method is a code smell to me.

In type Deleted consider maybe embedding sync.Mutex.

// fmt.Printf("finding path: %v\n", k.Path)

Instead of comments you may want to consider this pattern:

const debug = false

   
if debug {
        fmt
.Printf("finding path: %v\n", k.Path)
   
}

I think this library would be a good study for the Go 2 (https://blog.golang.org/toward-go2) generics effort, if you have time consider writing an experience report and posting it: https://github.com/golang/go/issues/15292

Perhaps consider embedding ki.Signal in gi.Action, and MakeMenuFunc, ki.Signal, *Icon, and ButtonStates in ButtonBase, some fields in ColorView, Dialog, FillStyle, FontStyle, LayoutStyle, LayoutData, Layout, MapView, MapViewInline, Node2DBase, Paint, ImagePaintServer, SliceView, SliceViewInline, SliderBase, SplitView, StrokeStyle, StructView, StructViewInline, BorderStyle, ShadowStyle, Style, TabView, TextStyle, TextField, SpinBox, ComboBox, TreeView, ValueViewBase, Viewport2D, Window. I like to avoid any unnecessary field symbols.

These kinds of structs are a code smell to me:

type ColorValueView struct {
   
ValueViewBase
}

Why a Base type? Can your types be simpler?

type Node2D and type ValueView seem like other possible overuses of interface to me. The size of type PaintServer is closer to what I’d expect with an interface, and Labeler seems idiomatic.

I like the screenshot.

// check for interface implementation
var _ Node2D = &Line{}

I don’t like this pattern.

Given the amount of code and project maturity I wouldn’t expect you to make a lot of changes, but I do think it could have been built with a stronger resilience to change. Thanks for sharing here.

Matt

Wojciech S. Czarnecki

unread,
May 4, 2018, 5:26:09 PM5/4/18
to golan...@googlegroups.com
On Fri, 4 May 2018 04:39:06 -0600
Randall O'Reilly <rcore...@gmail.com> wrote:

> https://github.com/goki/goki — key demo in:
> https://github.com/goki/goki/tree/master/gi/examples/widgets
>
> This is the first release of a new Go framework built around the Tree as a
> core data structure (Ki = Tree in Japanese),

Hats off!

If even for trying/seeding.

#--------------------------------------------
Problems seen so far:

$ ./widgets
2018/05/04 23:18:07 x11driver: no window found for event xproto.FocusOutEvent
^C

$ ./widgets
FontLib: error accessing path "/usr/local/share/fonts":
lstat /usr/local/share/fonts: no such file or directory
FontLib: error walking the path "/usr/local/share/fonts":
lstat /usr/local/share/fonts: no such file or directory
2018/05/04 23:19:28 x11driver: no window found for event xproto.FocusOutEvent
^C
(This one likely stem from somewhere hardcoded path)


# github.com/goki/goki/gi/oswin/touch
goki/goki/gi/oswin/touch/touch.go:97:5: cannot use Event literal (type
*Event) as type oswin.Event in assignment:
*Event does not implement oswin.Event (missing HasPos method)
^C

> impressed with the language and ecosystem etc. The contrast in complexity
> and build time between Qt and GoGi is really striking, and has kept me
> going despite the huge amount of effort it took to get this new project off
> the ground..

Such giant enterprise deserves a detailed blog post, IMO. In lines of other
"why I personally get to go from X/y/C++; what I do like and what I do not"
posts.

Awaiting your goki brainchild to mature and flourish :)

> - Randy
>

--
Wojciech S. Czarnecki
<< ^oo^ >> OHIR-RIPE

Randall O'Reilly

unread,
May 4, 2018, 5:35:51 PM5/4/18
to matthe...@gmail.com, golang-nuts
Matt — thanks a ton for all the detailed comments! Just quickly I figured out the git/hub steps to split out the top-level repositories into separate “ki” and “gi” repos, so the link and package paths are now:

https://github.com/goki/gi and ki

and hopefully this now works for the demo — just worked for me on my mac..

> go get github.com/goki/gi
> cd ~/go/src/github.com/goki/gi/examples/widgets
> go get ...
> go build
> ./widgets

More reactions later but just wanted to get that done so the paths should be stable now! Cheers,

- Randy
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Randall O'Reilly

unread,
May 5, 2018, 2:51:35 AM5/5/18
to matthe...@gmail.com, golang-nuts
Here’s a few responses to some of the overall “Smell” issues, and per Wojciech’s comments I don’t really do blogging but here are just a few of the “lessons learned / issues confronted” in this effort. Overall the ONLY thing I would change about Go based on my experience so far is some of the messaging / docs that appear to go too far in attempting to distance itself from OOP and C++ paradigms. In my experience, you can have the best of everything in Go, and sometimes a more standard OOP class hierarchy is exactly the right solution for a given problem (see below).

First, the essential requirements for a scene graph, and structural trees of that sort more generally, are:

* A common API for navigating, managing the tree

* But a diversity of functionality at each node (i.e., many different possible node types)

Thus, a common embedded base type that does all the basic tree stuff seems like the most natural solution. Trees are not one-off things where a simple “Stringer” kind of paradigm is going to work. They are containers, but unlike Go’s builtin, “privileged” containers with their magic generics functionality, they also naturally have a lot more complexity to them, and although my initial thoughts about this were that maybe Go2 could have a native tree container, I now think that doesn’t make sense. The Ki tree was really easy to build on top of slices, and slices do all the actual containing work — the rest of it is just all the hierarchical infrastructure, which someone else might want to do differently, and definitely doesn’t belong as built-in to the language.

I also think the following functionality for a robust “tree” system is essential:

* An “end user” (in an app, or through a GUI) should be able to create nodes of any type at any point in the hierarchy, through some kind of general-purpose “InsertNewChild” kind of method, which takes as an arg the type of child to create.

* The tree should automatically support basic infrastructure such as JSON saving / loading, copying parts of the tree, etc.

This then introduces a lot of “generics” kinds of demands, but I found the combination of the interface and reflect mechanisms entirely adequate (if sometimes maddeningly frustrating in the case of reflect) for this job. The addition of a type registry was extremely simple and I don’t see any “maintenance and reliability burden” engendered by this design (but maybe I’m missing something?). Any new node type must be registered, so it is an extra line of code, but it is a simple, clear one, easy to copy/paste. I also ended up adding a “New” function for each node because I was hitting massive slowdowns in reflect.New at one point (which turned out to just be the GC and nothing about reflect per say, and I minimized those by getting rid of all unnec. pointers) — could get rid of that and go back to reflect.New if people think that makes more sense?

One key trick for getting the most flexibility out of an Interface is to keep a “This” interface pointer of the struct in the struct itself (e.g., see ki.Node.This) — you can then ensure full virtual function calling in any method just by doing:

n.This.Function()

It works great and always calls the properly overridden version of the interface Function() defined for the actual type of object in question. I had a bit of an email discussion in this group with Ian about this back in March, and he emphasized how you really shouldn’t use any kind of C++ concept like inheritance in Go, but again I think that seems like throwing the baby out with the bathwater. An interface variable absolutely has the equivalent of a C++ virtual function table, and it functions exactly as you’d expect such a thing to function. If you redefine one of the methods in a derived type, it calls that method, even if you call it from an interface variable that doesn’t know anything at all about that derived type. It is then just one simple step to ensure that you always have such an interface variable available. And the automatic promotion of all methods from embedded types gives you exactly the behavior you’d expect: no need to redefine the methods that you want to just inherit — only override those that need to be specialized.

I also implemented another little trick for gaining access to the embedded struct type, like this:

sl := recv.EmbeddedStruct(KiT_SliderBase).(*SliderBase)

so no matter what type of object “recv” actually is, as long as I know it embeds SliderBase, this code will work (KiT_SliderBase is just a simpler way of writing: reflect.TypeOf(&SliderBase{}), as a nice side-benefit of the type registry). This doesn’t require defining an interface and is useful for ad-hoc cases where an interface is not really justified.

Anyway, to reiterate, my bottom line is that Go is awesome and supports everything you need, and that sometimes a classical OOP pattern of base-types and derived types that build on those is exactly what you need! I do wonder if perhaps the allergy to that framework has inhibited development of a GUI in Go up to this point — e.g., I saw in both the Shiny and https://github.com/walesey/go-engine frameworks that there was some awkwardness about the generic container nature of a scene graph…

- Randy

ps. several of the other code review comments reflect the “alpha” status of this project and can easily be cleaned up. The README files in particular were mostly just “notes to self” and really need to be rewritten. :)

matthe...@gmail.com

unread,
May 5, 2018, 10:38:47 AM5/5/18
to golang-nuts
In a generic container I would expect to see items typed as interface{} and the behavior defined on a slice of interface{} or struct with private slice of interface{} field.

From the godoc it looks like type Node implements type Ki, and there’s no other type that implements Ki. I don’t understand why this is necessary, why not just define the methods on type Node?

* But a diversity of functionality at each node (i.e., many different possible node types) 

My thought is this calls for a type Node interface, then there would be more types that implement Node. This line of thinking may lead to similar code to what you have already, I’m not sure.

The addition of a type registry was extremely simple and I don’t see any “maintenance and reliability burden” engendered by this design (but maybe I’m missing something?).

You may not be missing anything. My experience is that general purpose libraries can be more work than they’re worth, but that doesn’t mean yours is. Here’s a generic container library I wrote and hope to use in the future (I haven’t put it back into the project it came from yet): https://github.com/pciet/unordered

I appreciate you taking my feedback into consideration, and I appreciate that you are working on making and sharing good Go programs.

Thanks,
Matt

Steven Wiley

unread,
May 6, 2018, 4:11:23 PM5/6/18
to golang-nuts
It sure does look like you put a great deal of effort into this project. Here are a couple of impressions after trying the demos and skimming through part of the source code.

First, I had to pull down a lot of other packages in order to get things to build. Did you happen to write up a dependency list somewhere? It might be helpful to list them in the readme so that fumblers like me won't have to do it trail and error. (FYI: I just added the non-standard lib dependency list to oksvg and rasterx.) Here are the additional packages I needed for gi :

Also, I noticed that you need a go version greater than 1.9.4. I was getting a "math.Round not found" error until I upgraded to go1.10.2.


So, once I got everything building, I was getting a few font path not found errors during runtime. (OS: Fedora 27), but still text was visible. Some things worked quite smoothly, like the transition from a simple panel to a scrolling panel as a window was resized to smaller than the window content.  Other things were not behaving so well, like typing text into a text label, and the text would appear in a different location than the cursor. More seriously, almost every example I tried would at some point would hang and go non-responsive.  I am happy to work with you offline if you want to track down some of these problems.


As for looking at the code itself, as other people have mentioned it does seem to get a little heavy into use of reflection. Also, the ki package is very full featured, with a lot of capabilities built in to the tree nodes, like intra-node messaging, attribute maps, and more. So, I will echo some other comments here and suggest that you might want to consider letting some of those responsibilities fall to an object referenced by the node, which might allow simpler nodes to avoid unneeded overhead.


I have been playing around a bit with GUIs also, but decided to base my stuff on SDL 2. It looks like you are basing off of  something called shiny (golang.org/x/exp/shiny) ? Abstracting away the OS specific layer to make an all-platform GUI is a notoriously hard thing to do robustly, and is a frequent source of problems, just like intermittent hangups. Even with something as well supported as SDL, I was getting an intermittent bug until I realized I absolutely need to call runtime.LockOSThread(). Something like that might be going on here, and again we can follow up offline if you wish, or if you have any questions about using oksvg or rasterx.


cheers,

Steve

Randall O'Reilly

unread,
May 7, 2018, 2:05:13 AM5/7/18
to Steven Wiley, golang-nuts
Steve — thanks for your impressions. I’ll definitely contact you directly when I get around to trying to integrate rasterx — I haven’t even had a chance to look at it all, but a first question is when you expect it to be stable and reasonably feature-complete? e.g., one of the main things I would love to be able to use in the GUI are gradients, and your announcement mentioned that those were still pending?

I’m pretty sure you can just type "go get …” and it finds all the dependencies automatically, and they are likely to change over time, so I’m not sure it is conventional to list them? For example, I just removed the json-iterator dependency as it wasn’t even working, to make that list smaller by a few..

Re the all-platform GUI, it definitely seems tricky, and I’m developing on a Mac and only spent a minimal amount of effort getting Linux and Windows working, relative to the original Shiny framework. Unfortunately on linux any time you close a window it exits the event loop through some mechanism that I have yet to find! That is what is causing the lack of responsiveness — if you just don’t close any windows, everything should work fine :) I’m pretty sure Shiny was only tested with a single window, as there have been several issues related to that across the platforms. I don’t see those text field editing issues you report — did you install the MS TTF fonts? I also removed the /usr/local/share/fonts path per Wojciech’s comments — users can add via prefs if needed, so you shouldn’t be seeing any font path issues on the most recent version.

One thing I’ve found so far is that the specific demands of the GUI logic place specific demands on the OS-specific logic, so I’ve been happy to have full control over that at the lowest level. The Shiny framework provides a really nice, maximally-Go-based OS-specific interface that I’ve easily been able to modify per my needs, so I’m hopeful that with a bit more effort things will be pretty smooth..

Re the full-featured nature of the Ki nodes, one consideration is that each node in the tree needs to support the relevant capabilities for them to work properly: e.g., properties can be inherited, and signals need to disconnect when a node has been deleted (and more generally, the node signals like “Updated” are an essential part of the basic functionality, and could not be made optional). Also, I wanted to automatically support things like JSON in the same way that a slice or map natively does, so an end-user doesn’t have to struggle with all that themselves (and I don’t ever have to deal with it again myself :) Anyway, in my estimation, the Ki node is minimal relative to the core desired functionality, but I’m very open to specific suggestions about how it could be simplified or more efficiently decomposed. Cheers,

- Randy

Randall O'Reilly

unread,
May 7, 2018, 2:27:44 AM5/7/18
to matthe...@gmail.com, golang-nuts
On May 5, 2018, at 8:38 AM, matthe...@gmail.com wrote:
> In a generic container I would expect to see items typed as interface{} and the behavior defined on a slice of interface{} or struct with private slice of interface{} field.
>
> From the godoc it looks like type Node implements type Ki, and there’s no other type that implements Ki. I don’t understand why this is necessary, why not just define the methods on type Node?
>
> * But a diversity of functionality at each node (i.e., many different possible node types)
>
> My thought is this calls for a type Node interface, then there would be more types that implement Node. This line of thinking may lead to similar code to what you have already, I’m not sure.

Here’s some explanation relative to those points:

* Ki is basically a “Node” interface, but you can’t name a struct and an interface the same thing, so we have Ki and Node as two sides of the same thing..

* The Ki interface allows specific nodes to override any of the functions, and, critically, ONLY an interface type knows what the actual underlying type of a struct is, so we need that to be able to access the fields etc of the actual struct at each node. In general, having an interface in Go opens up lots of extra powers — could not have done many other things without it..

* There is no point in supporting a fully generic interface{} in a Tree container because the tree structure and logic requires each node to implement the same basic parent / child structure etc.. So Ki is the “minimal” interface for all tree nodes, and the Ki tree is a container of nodes that support that interface.

* The Node struct provides the default impl of the Ki interface, and typically you don’t need to impl it again in a different way, so almost always new Node types will just put Node as an anonymous embedded type, and perhaps modify some part of the Ki api as needed.

Cheers,
- Randy



matthe...@gmail.com

unread,
May 7, 2018, 9:33:10 AM5/7/18
to golang-nuts
I’m pretty sure you can just type "go get …” and it finds all the dependencies automatically, and they are likely to change over time, so I’m not sure it is conventional to list them?

You may want to vendor them (https://golang.org/cmd/go/#hdr-Vendor_Directories) so if they ever go away or change API then your project will still work.

Matt

Steven Wiley

unread,
May 7, 2018, 12:31:42 PM5/7/18
to golang-nuts
You are probably right about go get. I was pulling the repos into my workspace so I could check out the code more easily. That, plus just informing the user as to what is involved, is why you might want to list dependencies.

Gradients are coming soon to oksvg.
Reply all
Reply to author
Forward
0 new messages