MyGo
用 limumygo 原生绘制图表,并找出指针下方的部分。
limumygo 包用 MyGo 的原生 UI 在 GPU 上绘制 limu 的场景,并告诉你指针下方是图表的哪个部分。图表画在应用自己的窗口中,使用应用的字体。
安装
limumygo 是一个独立的模块,这样 limu 的其他包就只依赖 Go 标准库。
1go get github.com/firede/limu/limumygo你还需要 MyGo 构建应用所需的一切。参见 MyGo 的文档。
显示图表
limumygo.View 创建一个与场景同样大小的元素,并在其中绘制场景。布局时要用绘制时的同一套字体,原因见字体。
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}主题变化时,比如系统切换到深色模式,只需重绘场景。
这个控件会显示指针下方图元的提示文字,指针移到带链接的图元上时会变成手形。Hovered 返回指针下方的图元,Clicked 返回自上一帧以来被点击的图元。没有图元时两者都返回 nil。Element 返回控件的元素,方便你摆放它或给它加背景。辅助技术会把图表读作一张图片,名称取自场景的标题,Label 可以设置别的名称。
要让图表适应元素,从 LayoutBounds 读取元素的宽度,它保存的是上一帧的尺寸。宽度变化时,用新的 MaxWidth 重新布局。用 limu.Parse 解析一次文本,大图表放到另一个 goroutine 中布局,Playground 就是这样做的。
找出指针下方的图元
limumygo.ItemAt 按场景坐标找出某一点上的图元,不需要控件。多个图元重叠时,它返回最后绘制的那个,也就是最上面的那个。点落在图元的方框或其标签的方框内,就算命中。对于边和只由线条构成的图元,点与线条的距离在容差以内,或落在它们的某个标记内,就算命中。控件使用的容差是 limumygo.Slop,即 4 DIP(MyGo 的设备无关像素)。
1if it := limumygo.ItemAt(sc, scene.Point{X: x, Y: y}, limumygo.Slop); it != nil {2 fmt.Println(it.Role, it.ID)3}绘制
limumygo.Paint 在 ui.Painter 上绘制场景,左上角位于给定的点,单位是 DIP。用它可以在你自己的元素中绘制图表。
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 设置场景的一个单位占多少 DIP。要缩放或平移,改变缩放比例和绘制的起点。不要在 ui.Painter.Transform 下或变换过的元素中绘制。MyGo 按 painter 的缩放比例生成遮罩来绘制每条路径,变换会拉伸遮罩,所以这样缩放的图表会变模糊。非常大的路径还可能在分块的接缝处显出细线。
limumygo 画出的图与 svg 包写出的相同,只有几处差别。
- MyGo 无法按不透明度绘制分组,所以 limumygo 把图元的不透明度乘到它的每种颜色上。图元的各部分重叠时,下面的部分会透出来。
- 圆角半径对方框来说太大时,limumygo 取宽度一半和高度一半中较小的那个,而 SVG 用的是椭圆形圆角。
字体
图表用测量器布局,用字体绘制。两者不一致时,文本会放不进布局为它留的空间。limumygo.Fonts 同时承担这两项工作。它是一个 text.Measurer,用 painter 绘制所用的同一套字体测量,由系统的文本引擎塑形,与 MyGo 处理其他文本的方式相同。
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 使用系统字体及其等宽字体。字体族是一个按顺序尝试的字体列表,格式与 ui.Font 接收的相同。粗体和斜体文本使用字体族的粗体字重和斜体,等宽文本使用等宽字体族。Fonts 会缓存最近塑形过的文本,所以先测量、再绘制同一个标签不会多花代价。它可以在多个 goroutine 中同时使用。
中文、日文和韩文按绘制时的字体测量,字体族未覆盖的文字会回退到系统字体。系统的文本引擎会在汉字与拉丁字母或数字相邻处加一点间距,Fonts 也会把这段间距算进去。
图标和图片
limumygo 从不自行加载图片或打开链接。你的应用知道图片从哪里来、哪些链接可以安全打开,由它来做这些决定。
Options.DrawIcon 按名称绘制图标,比如 fa:fa-car,颜色是图元的文本颜色。布局只为你通过 layout.Options.Icons 报告的图标留出空间,所以两处要给同一组图标。
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 按 URL 把图片绘制到一个方框中。设置了 fit 时,图片保持比例并居中,否则拉伸以填满方框。布局只会传递 scheme 被 layout.Options.ImageSchemes 允许的 URL,图片的加载和缓存由你负责。没有 DrawIcon 或 DrawImage 时,图标和图片会被省略。
带链接的图元被点击时,它的 Link 字段保存着目标地址。按应用打开其他链接的方式打开它,或者不打开。
自定义绘制
Options.Draw 代替默认方式绘制每个图元,next 按默认方式绘制它。它收到的 limumygo.Painter 内嵌了 ui.Painter,并用 Point、Rect、Len 和 Path 把场景坐标换算成 DIP。它也可以用 Item、Parts、Ends、Picture 和 Label 绘制默认的各个部分。样式以 style.Resolved 传入,其中每种颜色都已经确定,因为 MyGo 用普通颜色绘制。
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 // 把节点画成我们自己的方框,标签按默认方式绘制。7 p.Fill(p.Rect(it.Box), limumygo.Color(s.Fill, s.Opacity), 8)8 p.Label(it, s)9}mygo-platinum 就用这种方式,以经典 Mac OS 的风格绘制图表。
编辑器高亮
limumygo/mermaid 包让 Mermaid 成为 MyGo 代码编辑器中的一种语言。它知道每种图表类型的关键字,并根据声明类型的那一行确定类型。
1buf := editor.NewBuffer(src)2buf.SetLanguage(mermaid.Language)它一次只读一行,不解析图表。要判断文本是否有效,请使用 limu 返回的错误。
在浏览器中
MyGo 应用也可以作为 WebAssembly 在浏览器中运行,limumygo 同样可以。Playground 就是这样运行的。如何构建和加载这样的应用,参见 MyGo 的文档。在浏览器中,文本由浏览器塑形,Fonts 也按浏览器排布文本的方式测量。limu 和 limumygo 会让未压缩的 WebAssembly 增大约 7 MB。