MyGo

用 limumygo 原生绘制图表,并找出指针下方的部分。

查看 Markdown

limumygo 包用 MyGo 的原生 UI 在 GPU 上绘制 limu 的场景,并告诉你指针下方是图表的哪个部分。图表画在应用自己的窗口中,使用应用的字体。

安装

limumygo 是一个独立的模块,这样 limu 的其他包就只依赖 Go 标准库。

go get github.com/firede/limu/limumygo

你还需要 MyGo 构建应用所需的一切。参见 MyGo 的文档。

显示图表

limumygo.View 创建一个与场景同样大小的元素,并在其中绘制场景。布局时要用绘制时的同一套字体,原因见字体。

package mainimport (	"log"	"github.com/egoist/mygo"	"github.com/egoist/mygo/ui"	"github.com/firede/limu"	"github.com/firede/limu/layout"	"github.com/firede/limu/limumygo"	"github.com/firede/limu/scene"	"github.com/firede/limu/style")type app struct {	sc      *scene.Scene	clicked string}func (a *app) view(c *ui.Context) {	theme := style.Kohu()	if c.Theme().Dark {		theme = style.KohuDark()	}	ui.Column(c).Padding(16).Gap(12).Children(func() {		w := limumygo.View(c, a.sc, &limumygo.Options{			Look:  style.Look{Theme: theme},			Fonts: limumygo.DefaultFonts,		})		if it := w.Clicked(); it != nil {			a.clicked = string(it.Role) + " " + it.ID		}		ui.Text(c, "Clicked: "+a.clicked)	})}func main() {	sc, err := limu.Layout(`flowchart LR  text[Mermaid text] --> layout[Layout] --> scene[Scene]`,		&layout.Options{Measurer: limumygo.DefaultFonts})	if err != nil {		log.Fatal(err)	}	a := &app{sc: sc}	mygo.App.WhenReady(func() {		mygo.NewWindow(mygo.WindowOptions{Title: "Diagram", Width: 640, Height: 360, Content: ui.View(a.view)})	})	if err := mygo.App.Run(); err != nil {		log.Fatal(err)	}}

主题变化时,比如系统切换到深色模式,只需重绘场景。

这个控件会显示指针下方图元的提示文字,指针移到带链接的图元上时会变成手形。Hovered 返回指针下方的图元,Clicked 返回自上一帧以来被点击的图元。没有图元时两者都返回 nil。Element 返回控件的元素,方便你摆放它或给它加背景。辅助技术会把图表读作一张图片,名称取自场景的标题,Label 可以设置别的名称。

要让图表适应元素,从 LayoutBounds 读取元素的宽度,它保存的是上一帧的尺寸。宽度变化时,用新的 MaxWidth 重新布局。用 limu.Parse 解析一次文本,大图表放到另一个 goroutine 中布局,Playground 就是这样做的。

找出指针下方的图元

limumygo.ItemAt 按场景坐标找出某一点上的图元,不需要控件。多个图元重叠时,它返回最后绘制的那个,也就是最上面的那个。点落在图元的方框或其标签的方框内,就算命中。对于边和只由线条构成的图元,点与线条的距离在容差以内,或落在它们的某个标记内,就算命中。控件使用的容差是 limumygo.Slop,即 4 DIP(MyGo 的设备无关像素)。

if it := limumygo.ItemAt(sc, scene.Point{X: x, Y: y}, limumygo.Slop); it != nil {	fmt.Println(it.Role, it.ID)}

绘制

limumygo.Paint 在 ui.Painter 上绘制场景,左上角位于给定的点,单位是 DIP。用它可以在你自己的元素中绘制图表。

o := &limumygo.Options{Look: look, Fonts: fonts, Scale: 2}ui.Box(c).Size(float32(sc.Width*2), float32(sc.Height*2)).Draw(func(p *ui.Painter, r ui.Rect) {	limumygo.Paint(p, sc, r.X, r.Y, o)})

Options.Scale 设置场景的一个单位占多少 DIP。要缩放或平移,改变缩放比例和绘制的起点。不要在 ui.Painter.Transform 下或变换过的元素中绘制。MyGo 按 painter 的缩放比例生成遮罩来绘制每条路径,变换会拉伸遮罩,所以这样缩放的图表会变模糊。非常大的路径还可能在分块的接缝处显出细线。

limumygo 画出的图与 svg 包写出的相同,只有几处差别。

  • MyGo 无法按不透明度绘制分组,所以 limumygo 把图元的不透明度乘到它的每种颜色上。图元的各部分重叠时,下面的部分会透出来。
  • 圆角半径对方框来说太大时,limumygo 取宽度一半和高度一半中较小的那个,而 SVG 用的是椭圆形圆角。

字体

图表用测量器布局,用字体绘制。两者不一致时,文本会放不进布局为它留的空间。limumygo.Fonts 同时承担这两项工作。它是一个 text.Measurer,用 painter 绘制所用的同一套字体测量,由系统的文本引擎塑形,与 MyGo 处理其他文本的方式相同。

fonts := limumygo.NewFonts("Inter, system-ui", "JetBrains Mono, monospace")sc, err := limu.Layout(src, &layout.Options{Measurer: fonts})w := 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 报告的图标留出空间,所以两处要给同一组图标。

icons := map[string]*ui.SVG{"fa:fa-car": car}sc, err := limu.Layout(src, &layout.Options{	Measurer: fonts,	Icons:    func(name string) bool { return icons[name] != nil },})o := &limumygo.Options{	Look:  look,	Fonts: fonts,	DrawIcon: func(p *ui.Painter, name string, r ui.Rect, c ui.Color) {		if s := icons[name]; s != nil {			p.Icon(s, r, c)		}	},}

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 用普通颜色绘制。

o.Draw = func(p *limumygo.Painter, it *scene.Item, s style.Resolved, next func()) {	if it.Kind != scene.Node {		next()		return	}	// 把节点画成我们自己的方框,标签按默认方式绘制。	p.Fill(p.Rect(it.Box), limumygo.Color(s.Fill, s.Opacity), 8)	p.Label(it, s)}

mygo-platinum 就用这种方式,以经典 Mac OS 的风格绘制图表。

编辑器高亮

limumygo/mermaid 包让 Mermaid 成为 MyGo 代码编辑器中的一种语言。它知道每种图表类型的关键字,并根据声明类型的那一行确定类型。

buf := editor.NewBuffer(src)buf.SetLanguage(mermaid.Language)

它一次只读一行,不解析图表。要判断文本是否有效,请使用 limu 返回的错误。

在浏览器中

MyGo 应用也可以作为 WebAssembly 在浏览器中运行,limumygo 同样可以。Playground 就是这样运行的。如何构建和加载这样的应用,参见 MyGo 的文档。在浏览器中,文本由浏览器塑形,Fonts 也按浏览器排布文本的方式测量。limu 和 limumygo 会让未压缩的 WebAssembly 增大约 7 MB。