Skip to content

文本 ​

ui.Text 显示按其所得宽度换行的文本,ui.Textf 则先格式化再显示:

go
ui.Text(c, "Settings").FontSize(20).Bold()
ui.Textf(c, "%d files, %s", n, size).TextColor(c.Theme().TextMuted)

样式 ​

用 FontSize、FontWeight、Bold、Italic、Font、LineHeight、FixedLineHeight(以 DIP 计,与字号无关)、TextColor、TextAlign、Underline、WavyUnderline、Strikethrough、DecorationColor 和 DecorationThickness(下划线和删除线的颜色与粗细)、TextBackground(行后的高亮背景)、LetterSpacing 和 FontFeatures 来设置样式;可以设在文本上,也可以设在其上方任意元素上,其文本会继承这些样式。FontFeatures 按标签开启字体的 OpenType 特性,或用 tag=value 设定:FontFeatures("tnum") 让数字等宽,适用于会变化的数值;FontFeatures("liga=0") 关闭连字。

阿拉伯语、希伯来语及其他以此书写的文字的文本从右向左阅读,与从左向右阅读的文本混排时遵循 Unicode 的双向算法:段落按其首个强方向字符决定阅读方向。TextAlign 的 Start 是左侧;对于从右向左阅读的段落,以及从右到左布局的元素中的任意文本,则是右侧——在这样的元素中,没有强方向字符的段落(例如纯数字)也从右向左阅读。

SingleLine 让文本保持一行,超出部分以省略号截断;MaxLines 限制为若干行;Ellipsis(" →") 用其他符号结尾截断处。NoWrap 保持行完整,只在换行符处断行。Selectable 让用户可以用指针、Shift 和方向键选中文本并复制,比如错误信息或需要粘贴到别处的标识符。

把 Selectable 放在容器上,可让其所有 Text 和 RichText 后代共享一个选区:

go
ui.Column(c).Selectable().Gap(12).Children(func() {
	ui.Text(c, "First paragraph.")
	ui.Text(c, "Second paragraph.")
})

拖动可以在任一方向跨越段落。Shift 加点击、Shift 加方向键可扩展选区;在滚动容器边缘附近拖动会滚动容器。Cmd+C(Linux 和 Windows 上为 Ctrl+C)、"编辑"菜单中的复制、以及上下文菜单都能复制选中的文本,按段落构建的顺序以换行符连接。"全选"会选中整个容器,包括视口之外已构建的段落。段落内的行内文本和链接作为该段落的一部分被复制。

容器上的 CopyData 决定复制操作从所选段落部分写入的内容,例如文档可以把它们作为文本以空行连接,同时写入 HTML 以保留格式;SelectedTexts 返回这些部分,供以其他方式复制它们的菜单项使用。文本在末尾增长的段落会保留选区。

嵌套的 Selectable 容器各有独立的选区。按钮和文本输入保持自己的交互;它们的标签不加入容器的选区。在文本或容器上使用 Unselectable 可排除其子树;行内元素跟随其所在段落的选区。选中文本发生变化或端点被移除时,共享选区会被清除。虚拟列表只包含它已构建的段落,因此要选中整个文档,请使用普通滚动容器。

高亮色是主题的 Selection;SelectionColor 为某个元素及其内部文本另设一种,例如彩色气泡上的文本需要在其上清晰可见的高亮色。右键点击可选文本会显示"复制"和"全选";在文本或其 Selectable 容器上设置 ContextMenu 会替换该菜单,Menu.EditItems 可把"复制"和"全选"放入其中。

富文本 ​

ui.RichText 在一个段落中混合多种样式:每个 ui.Span 在文本样式的基础上设置它改变的内容(字体、字号、粗细、斜体、颜色、下划线、删除线、它们的颜色与粗细、背景、字间距、OpenType 特性),各个 span 一起换行。Radius 使 span 的背景带上圆角,此时背景贴合其字形而不是填满整行,正如文本中内嵌代码的样式:

go
ui.RichText(c,
	ui.Span{Text: "Saved "},
	ui.Span{Text: "report.pdf", Weight: 600},
	ui.Span{Text: " to "},
	ui.Span{Text: "Documents", Color: t.Accent, Underline: true},
)

ui.RichText(c,
	ui.Span{Text: "Run "},
	ui.Span{Text: "go test", Font: "monospace", Background: t.Surface, Radius: 4},
)

ui.RichText(c,
	ui.Span{Text: "Did you mean "},
	ui.Span{Text: "recieve", WavyUnderline: true, DecorationColor: t.Danger},
	ui.Span{Text: "? "},
	ui.Span{Text: "match", Background: ui.RGBA(250, 204, 21, 0.4)},
)

设置了 ObjectWidth 的 span 会将其对象替换字符(U+FFFC)变为行内对象:宽为 ObjectWidth、基线上方 ObjectHeight、基线下方 ObjectDescent 的盒子,它们随词语换行,行会为其腾出空间,正如公式的下标所需。DrawObjects 在字形之后、文本的裁剪区内绘制它们:回调会得到每个对象在段落中的 rune 及其盒子。

go
ui.RichText(c,
	ui.Span{Text: "where "},
	ui.Span{Text: "\uFFFC", ObjectWidth: f.W, ObjectHeight: f.Ascent, ObjectDescent: f.Descent},
	ui.Span{Text: " is the mean"},
).DrawObjects(func(p *ui.Painter, at int, r ui.Rect) { f.Draw(p, r) })

span 的 BaselineShift 将其文本抬高那么多 DIP(位于基线之上),负值则下移,如同上标和下标,无论字体自带何种上/下标字形;行会为其腾出空间,span 的下划线和删除线也随之移动:

go
ui.RichText(c,
	ui.Span{Text: "H"},
	ui.Span{Text: "2", Size: 10, BaselineShift: -3},
	ui.Span{Text: "O boils at 100 °C"},
	ui.Span{Text: "1", Size: 10, BaselineShift: 6},
)

文本中的软连字符(U+00AD)是行可以断开的位置:行在此断开时它显示为连字符,其他情况下不显示,与浏览器一致。当连字符本身放不下,而行内又没有其他断行点只能在单词中间断开时,连字符会溢出宽度。

句中的元素 ​

在文本的 Children 中构建的文本元素延续其段落,如同 HTML 的行内元素:每个元素在段落样式的基础上设置自己文本的样式,并像普通元素一样工作(Clicked、Hovered、键盘焦点、Tooltip、ContextMenu),其区域是它的词语在每一行上占据的范围。其中的链接就是句内链接,Tab 可以到达它,辅助技术会将其读作链接:

go
ui.RichText(c).Children(func() {
	ui.Text(c, "Read ")
	ui.Link(c, "the guide", "https://example.com/guide")
	ui.Text(c, " or ")
	if ui.Text(c, "show an example").TextColor(t.Accent).Clicked() {
		app.example = true
	}
	ui.Text(c, ".")
})

只有文本元素(Text、Link、RichText)可以放在文本内部;它们的尺寸、内边距、边框和圆角不适用,Background 会高亮它们的文本。

字体 ​

文本由系统自带的文字引擎(Windows 上的 DirectWrite、macOS 上的 Core Text、Linux 上的 Pango)以系统字体(Segoe UI、SF、桌面环境界面字体,即 GTK 应用所用的字体)排版和绘制,并像原生应用一样为其他文字和 emoji 回退到系统字体,从右到左的文本按其顺序处理。Font("monospace") 选择系统等宽字体,ui.RegisterFont 可添加你自己的字体:

go
//go:embed Inter.ttf
var inter []byte

func init() {
	if err := ui.RegisterFont(inter, "Inter"); err != nil {
		log.Fatal(err)
	}
}

之后 Font("Inter") 即可使用它,或者在主题的 Font 中为所有元素设置它。字体族列表,如 Font("Inter, Noto Sans JP"),会用系统或应用所具备的第一个字体族绘制,它缺少的字形则由下一个具备的字体族补上,最后才轮到系统自己的选择。

ui.FontFamilies() 列出文本可用的字体族,如同字体设置提供的列表:先是应用注册的字体族,按其指定的名称(未指定时用其自身名称),然后是系统的字体族,每个只出现一次并按不区分大小写排序。每个 ui.FontFamily 会标明它是否 Monospaced(等宽),供终端或编辑器的字体设置使用,以及应用是否 Registered(注册)了它:

go
for _, f := range ui.FontFamilies() {
	if f.Monospaced {
		fonts = append(fonts, f.Name)
	}
}

首次调用会询问系统,安装字体很多时需要一些时间(数百个字体时约几十毫秒),但不会阻塞窗口的文本;后续调用会复用系统列表,直到再次 RegisterFont,因此应用运行期间安装的字体要在重启后才会出现。浏览器不会告知自己有哪些字体:此时列表中只有注册的字体族,某个字体族一旦其字体加载完成即视为等宽。

由控件自行排版的文本,例如终端的单元格,用 ui.Shape 一次性完成文字排版:参见绘制。