MyGo
Draw diagrams natively with limumygo, and find the part under the pointer.
The limumygo package draws limu’s scenes on the GPU with MyGo’s native UI, and tells you which part of a diagram is under the pointer. This is what limu was built for. The diagram lives in your app’s own window, uses your app’s fonts, and can answer questions about itself.
Installing
limumygo is a separate module, which keeps limu’s other packages free of anything beyond Go’s standard library.
1go get github.com/firede/limu/limumygoYou also need whatever MyGo needs to build an app. See MyGo’s documentation.
Showing a diagram
limumygo.View builds an element the size of a scene and draws the scene in it. Lay out the diagram with the same fonts you draw it in, as explained under Fonts.
1package main23import (4 "log"56 "github.com/egoist/mygo"7 "github.com/egoist/mygo/ui"8 "github.com/firede/limu"9 "github.com/firede/limu/layout"10 "github.com/firede/limu/limumygo"11 "github.com/firede/limu/scene"12 "github.com/firede/limu/style"13)1415type app struct {16 sc *scene.Scene17 clicked string18}1920func (a *app) view(c *ui.Context) {21 theme := style.Kohu()22 if c.Theme().Dark {23 theme = style.KohuDark()24 }25 ui.Column(c).Padding(16).Gap(12).Children(func() {26 w := limumygo.View(c, a.sc, &limumygo.Options{27 Look: style.Look{Theme: theme},28 Fonts: limumygo.DefaultFonts,29 })30 if it := w.Clicked(); it != nil {31 a.clicked = string(it.Role) + " " + it.ID32 }33 ui.Text(c, "Clicked: "+a.clicked)34 })35}3637func main() {38 sc, err := limu.Layout(`flowchart LR39 text[Mermaid text] --> layout[Layout] --> scene[Scene]`,40 &layout.Options{Measurer: limumygo.DefaultFonts})41 if err != nil {42 log.Fatal(err)43 }44 a := &app{sc: sc}45 mygo.App.WhenReady(func() {46 mygo.NewWindow(mygo.WindowOptions{Title: "Diagram", Width: 640, Height: 360, Content: ui.View(a.view)})47 })48 if err := mygo.App.Run(); err != nil {49 log.Fatal(err)50 }51}When the theme changes, for example when the system switches to dark mode, the scene is only redrawn.
The widget shows the tooltip of the item under the pointer and turns the pointer into a hand over items with a link. Hovered returns the item under the pointer, and Clicked returns the item clicked since the last frame. Both return nil when there’s no item. Element gives you the widget’s element so you can place it or give it a background. Assistive technology reads the diagram as an image named by the scene’s title, and Label sets a different name.
To fit a diagram to its element, read the element’s width from LayoutBounds, which holds the size from the previous frame. When the width changes, lay the diagram out again with a new MaxWidth. Parse the text once with limu.Parse, and lay out large diagrams on another goroutine, as the playground does.
Finding the item under the pointer
limumygo.ItemAt finds the item at a point in scene coordinates, without needing a widget. When several items overlap, it returns the one drawn last, which is the one on top. A point hits an item when it’s inside the item’s box or its label’s box. Edges and items made only of lines are hit when the point is within a tolerance of a line or inside one of their markers. The widget uses limumygo.Slop, which is 4 DIPs.
1if it := limumygo.ItemAt(sc, scene.Point{X: x, Y: y}, limumygo.Slop); it != nil {2 fmt.Println(it.Role, it.ID)3}Painting
limumygo.Paint draws a scene on a ui.Painter with its top left corner at a given point, in DIPs. Use it to draw a diagram inside an element of your own.
1o := &limumygo.Options{Look: look, Fonts: fonts, Scale: 2}2ui.Box(c).Size(float32(sc.Width*2), float32(sc.Height*2)).Draw(func(p *ui.Painter, r ui.Rect) {3 limumygo.Paint(p, sc, r.X, r.Y, o)4})Options.Scale sets how many DIPs one scene unit takes. To zoom or pan, change the scale and the point you draw at. Don’t draw under ui.Painter.Transform or inside a transformed element. MyGo clips very large paths to the visible area in window coordinates, and it can’t account for a transform, so a transformed diagram may be drawn only in part.
limumygo draws the same picture as the svg package, with a few differences.
- MyGo has no way to draw a group at an opacity, so limumygo applies an item’s opacity to each of its colors. Where an item’s parts overlap, the lower part shows through.
- When a corner radius is too large for a box, limumygo uses the smaller of half the width and half the height. SVG uses elliptical corners instead.
- MyGo strokes paths only with round ends and corners. Lines with other ends or corners are drawn by filling their outline.
Fonts
A diagram is laid out with a measurer and drawn with fonts. If the two don’t match, text won’t fit the space layout gave it. limumygo.Fonts does both jobs. It’s a text.Measurer that measures with the same fonts the painter draws with, shaped by the system’s text engine just as MyGo shapes the rest of its text.
1fonts := limumygo.NewFonts("Inter, system-ui", "JetBrains Mono, monospace")2sc, err := limu.Layout(src, &layout.Options{Measurer: fonts})3w := limumygo.View(c, sc, &limumygo.Options{Look: look, Fonts: fonts})limumygo.DefaultFonts uses the system font and its monospaced font. A family is a list of fonts to try in order, as ui.Font takes it. Bold and italic text use the family’s bold weight and italics, and monospaced text uses the mono family. Fonts cache the text they shaped most recently, so measuring and then drawing the same label costs nothing extra. They’re safe to use from several goroutines.
Chinese, Japanese and Korean text is measured in the fonts it’s drawn in, falling back to the system’s fonts for scripts the family doesn’t cover. The system’s text engine adds a little space where Han characters meet Latin letters or digits, and Fonts measures that space too.
Icons and images
limumygo never loads images or follows links on its own. Your app knows where its images come from and which links are safe to open, so it makes those decisions.
Options.DrawIcon draws an icon by name, such as fa:fa-car, in the color of the item’s text. Layout leaves room in labels only for icons you report through layout.Options.Icons, so give both the same set.
1icons := map[string]*ui.SVG{"fa:fa-car": car}2sc, err := limu.Layout(src, &layout.Options{3 Measurer: fonts,4 Icons: func(name string) bool { return icons[name] != nil },5})6o := &limumygo.Options{7 Look: look,8 Fonts: fonts,9 DrawIcon: func(p *ui.Painter, name string, r ui.Rect, c ui.Color) {10 if s := icons[name]; s != nil {11 p.Icon(s, r, c)12 }13 },14}Options.DrawImage draws an image from its URL into a box. When fit is set, the image keeps its proportions and is centered. Otherwise it’s stretched to fill the box. Layout only passes through URLs whose scheme layout.Options.ImageSchemes allows, and loading and caching the image is up to you. Without DrawIcon or DrawImage, icons and images are left out.
When an item with a link is clicked, its Link field holds the target. Open it the same way your app opens any other link, or don’t.
Custom drawing
Options.Draw draws each item in place of the default, and next draws it the default way. The limumygo.Painter it receives embeds the ui.Painter and converts scene coordinates to DIPs with Point, Rect, Len and Path. It can also draw the default pieces with Item, Parts, Ends, Picture and Label. The style arrives as a style.Resolved, with every color already worked out, because MyGo draws with plain colors.
1o.Draw = func(p *limumygo.Painter, it *scene.Item, s style.Resolved, next func()) {2 if it.Kind != scene.Node {3 next()4 return5 }6 // Draw the node as a box of our own, and its label the default way.7 p.Fill(p.Rect(it.Box), limumygo.Color(s.Fill, s.Opacity), 8)8 p.Label(it, s)9}mygo-platinum uses this to draw diagrams in the style of the classic Mac OS.
Editor highlighting
The limumygo/mermaid package makes Mermaid a language in MyGo’s code editor. It knows the keywords of each diagram type and picks the type from the line that declares it.
1buf := editor.NewBuffer(src)2buf.SetLanguage(mermaid.Language)It reads one line at a time and doesn’t parse the diagram. Use limu’s errors to find out whether the text is valid.
In a browser
MyGo apps can also run in a browser as WebAssembly, and limumygo comes along. The playground works this way. See MyGo’s documentation for how to build and load such an app. In a browser, the browser shapes text, and Fonts measures it the way the browser sets it. limu and limumygo add about 7 MB to the uncompressed WebAssembly.