Skip to content

Glass(液态玻璃) ​

glass 插件在原生 UI 中绘制 Liquid Glass,即 macOS 26 及更高版本的材质: 元素之下的内容透过它显示,被磨砂,在其边缘附近像透过透镜的边缘一样弯折, 并沿着它发丝般的边缘打光。它还绘制 macOS 的滚动边缘,在其下滚动的内容在 一条栏下淡出或磨砂(滚动边缘),并模糊元素之下的内容, 均匀地或沿渐变淡出(模糊)。MyGo 用该插件的着色器绘制它们,在 GPU 或 CPU 上,因此它们在 macOS、Windows 和 Linux 上看起来相同。它全部是 Go:没有 JavaScript 包,也没有需要 mygo.Use 的东西。

go
import "github.com/egoist/mygo/plugins/glass"

ui.Row(c).Padding(8, 16).Gap(8).Radius(22).Material(glass.Glass{}).Children(func() {
	ui.Text(c, "On glass")
})

glass.Glass 是一种材质(ui.Material):Material 用它填充元素,取代 背景,它的 Radius 就是它的形状;它的子元素在它之上绘制。透出来的是在同 一窗口中绘制在它之下的东西:它之前的元素,比如用 Absolute 放在它上面的 工具栏之下滚动过去的内容,以及其他玻璃。

go
ui.Box(c).Fill().Children(func() {
	ui.Scroll(c).Fill().Padding(64, 16, 16).ScrollbarInsets(64, 0, 0).Children(func() { /* ... */ })
	// 工具栏悬浮在内容之上,内容从它下面滚过。
	ui.Row(c).Absolute().Top(12).Left(12).Right(12).Padding(6).Radius(26).Material(glass.Glass{}).Children(func() {
		// ...
	})
})

样式 ​

  • glass.Regular,默认值,磨砂透出的内容并把它调亮,或在深色模式下 调暗,以便玻璃上的东西在任何内容之上都清晰可读:栏、按钮和面板。更大的 面板磨砂更重,如同 macOS。
  • glass.Clear 几乎不模糊或调色透出的内容:照片和视频上的玻璃,其上 的东西自带对比度。

Tint 按它的 alpha 把玻璃向一个颜色着色,像一个突出按钮: glass.Glass{Tint: t.Accent},文本用 t.AccentText。Interactive 让玻璃 在按下时稍微长大,如同 AppKit 的交互玻璃在 macOS 27 上的表现:左右约 1.1 DIP,上下约 0.45,无论其大小,在 150 毫秒内。AppKit 也拉伸内容,约一个 像素,而这里的子元素不会。

go
done := ui.Row(c).Padding(8, 16).Radius(20).Material(glass.Glass{Tint: t.Accent, Interactive: true}).Children(func() {
	ui.Text(c, "Done").Bold().TextColor(t.AccentText)
})
if done.Clicked() {
	// ...
}

Clarity,从 0 到 1,让侧栏和检查器那样大的面板更清澈,其下的内容可读地 透出:0(默认值)是 macOS 的材质,原样不变;0.5 模糊最多 5 DIP 而不是 10, 在浅色模式把黑色映射到 35%,在深色模式把白色映射到 60%,更亮地打光边缘并 弯折最后 48 DIP;1 几乎清澈,模糊最多 2 DIP,在浅色模式把黑色映射到 20%, 在深色模式把黑色映射到 10%、白色映射到 85%。范围的每一半在那些值之间线性 过渡。100 DIP 以内的面板无论清澈度如何都保持其材质,清澈度在 200 DIP 内 逐渐生效:glass.Glass{Clarity: 0.5}。清澈玻璃本来几乎不给透出的内容调色; 在它之上,清澈度只是更亮地打光边缘并弯折边框。

在绘图中,glass.Paint(p, r, radius, g) 在绘图者之前绘制的内容之上绘制 一块玻璃面板。

滚动边缘 ​

glass.ScrollEdge 是 macOS 26 及更高版本的滚动边缘效果,一种用于元素之上 的材质,位于在控件栏之下滚动的内容的边缘之上,使它们从中突出,如同 SwiftUI 的 safeAreaBar 配合 scrollEdgeEffectStyle 绘制的:

go
ui.Box(c).Fill().Children(func() {
	ui.Scroll(c).Fill().Padding(64, 16, 16).ScrollbarInsets(64, 0, 0).Children(func() { /* ... */ })
	// 内容在栏之下淡入背景。
	ui.Box(c).Absolute().Top(0).Left(0).Right(0).Height(74).PassThrough().Material(glass.ScrollEdge{})
	// 玻璃按钮悬浮在它之上。
	ui.Row(c).Absolute().Top(12).Left(12).Right(12).Gap(6).PassThrough().Children(func() {
		// ...
	})
})
  • Soft(柔和),默认值,让内容滚过的东西淡入它:在栏的边缘背景为 85%,向元素的另一边缘降到零,没有模糊。让元素为栏的高度再加 10 DIP, 如同 macOS 所做。
  • Hard(硬朗) 均匀磨砂栏之下的内容:模糊约 6 DIP,颜色饱和度增加 四分之一,在 82% 的背景之下,沿元素的另一边缘有一条发丝线,浅色模式下 黑色 10%,深色模式下白色 7%。让元素为栏的高度。

Bottom 把栏放在元素的底部,Background 设置内容在其上滚过的颜色,除非 设置,否则是主题的 Background。在浮动于其上的东西之前绘制边缘,并用 PassThrough 把指针让给内容。

这些是 macOS 27 的,从 SwiftUI 在测试图案上的表现测量得出:在它的图层中, 柔和边缘在从 85% 到零的渐变遮罩下重放窗口的背景,越过栏 10 点,而硬朗边缘 模糊内容(半径 6 的高斯、饱和度 1.25 再加 0.03),然后在其上以 82.45% 重放 背景。灰色内容与 AppKit 在每一行的差异在 255 分之 1 以内,浅色和深色皆然; 饱和的颜色相差最多 0.05,因为 AppKit 在显示器更宽的色空间中混合它们,而 MyGo 的渲染器在 sRGB 中。macOS 的窗口工具栏绘制它自己的边缘,均匀模糊在 60% 的背景之下,这是原生 UI 的栏所没有的。

模糊 ​

glass.Blur 是背景模糊,也是一种材质:元素之下的内容透过它显示,按 Radius DIP 模糊(标准差,如同 CSS 的 blur() 所取),没有色调、边缘光 或阴影。

Mask 使它成为渐进模糊:一个 LinearGradient,它的 alpha 说明每个地方按 Radius 的多少模糊,如同 CSS 的 mask-image。在渐变不透明处,透出的内容 按 Radius 模糊,在半透明处按较少,在全透明处完全不模糊;它的颜色不显示。 在栏之下,内容在其边缘处模糊最多,离它越远越少。

go
ui.Box(c).Fill().Children(func() {
	ui.Scroll(c).Fill().Padding(64, 16, 16).ScrollbarInsets(88, 0, 0).Children(func() { /* ... */ })
	// 在工具栏下滚过的内容会模糊,越靠近顶部越模糊。
	ui.Box(c).Absolute().Top(0).Left(0).Right(0).Height(88).PassThrough().
		Material(glass.Blur{Radius: 6, Mask: &ui.LinearGradient{
			From: ui.RGB(0, 0, 0), To: ui.Transparent, Angle: 180, Start: 0.3, End: 1,
		}})
	// 玻璃按钮悬浮在它之上。
	ui.Row(c).Absolute().Top(12).Left(12).Right(12).Gap(6).PassThrough().Children(func() {
		// ...
	})
})

这里模糊在条带顶部 30% 是完整的,向下淡出,PassThrough 让指针到达条带 之下和按钮周围的内容,ScrollbarInsets 让滚动条从条带之下开始,如同 AppKit 把滚动条放在其工具栏的边缘效果之下。在浮动于其上的东西之前绘制模糊: 它之上的玻璃会显示模糊后的内容。Start 和 End 沿渐变放置它的颜色,与其他 地方一样;End 留为 0 会把两者都放在 Start,形成硬边缘。

变化的模糊以台阶绘制,每一步把上一步绘制的内容再模糊一点,在想要的模糊超过 该步自身的模糊处显示,在两者之间与下一步混合:从两像素起,模糊每翻倍一个台阶, 在 Retina 显示屏上 12 DIP 五个台阶。两个台阶之间的像素显示两种模糊的混合, 如同 Core Animation 的可变模糊在其金字塔的各层之间的做法,也如同 Web 上渐进 模糊的堆叠 backdrop-filter 图层。当渐变在窗口中间的元素的某个边缘(而不是 窗口的边缘)不透明时,模糊在该边缘几个像素内与逐像素各自模糊略有不同,因为每 一步读取元素周围未模糊的内容。

如何与 macOS 保持一致 ​

玻璃遵循 macOS 27 的,从 AppKit 的 NSGlassEffectView 测量得出,配合开源 Liquid Glass 复现项目的光学:

  • 边框。 表面中间是平的,沿 Apple 的超椭圆轮廓向边缘下弯,在最后 36 DIP 内,最多占面板的一半。垂直向下的光在进入时折射,如同折射率 1.5 的 玻璃,因此靠近边缘的东西来自更内侧:边缘镜像它内侧紧邻的东西,如同透镜 的边缘所做。
  • 材质。 常规玻璃把透出的内容模糊最多 10 DIP,更大的面板更多,并映射 它的明度,浅色模式下黑色到 54%、白色到 100%,深色模式下到 15% 和 51%, 保持其颜色。清澈玻璃在它之上再加八分之一。
  • 光。 边缘在朝向上或朝向下处打光,在朝向侧面处变暗,在边缘一个像素 内:一条发丝线,如同 AppKit 的。
  • 无阴影。 NSGlassEffectView 和玻璃边框的按钮都不投阴影:面板的边缘 就是它的边缘光。

性能 ​

每块面板读取它之下的内容,平均并模糊它,然后用它自己的着色器绘制:GPU 上的 几个小通道,面板在其下的任何东西改变时改变,例如在栏下滚动的内容。当窗口在 CPU 上绘制(没有 GPU)时,如渲染所述,面板之下的改变 会重绘面板及其模糊所及周围的区域。渐进模糊对它的每个台阶都这样做,在每个台阶 显示之处:在 CPU 上,一条 1360×176 像素、模糊 24 的条带在 M5 上淡出用时 9 毫秒,均匀模糊 1.7 毫秒,在 GPU 上每个台阶几个小通道。

面板是圆角矩形。它们靠近时不会彼此合并,不像 AppKit 的 NSGlassEffectContainerView。