跨端: 壁纸遮盖色改用页面底色系(mask 两套主题都深,浅色下方向与 WebUI 相反);模态遮罩保持 mask

pi 提了一个**方向性**疑问,让我先把值读出来再定 —— 读完确认**他是对的**,而且这是"机制上确定不同"。

## 实测(两张 SDK 表交叉验证,不靠记忆)

`ets/build-tools/ets-loader/sysResource.js` 给名字→id,`previewer/.../resources.txt` 给 id→值:

| 令牌 | 浅色主题 | 深色主题 |
|---|---|---|
| `ohos_id_color_mask_regular`(原 `Theme.overlay`) | `#99182431` **深蓝灰** | `#b2000000` 黑 |
| `ohos_id_color_background` | `#ffffffff` **白** | `#ff18181a` 近黑 |

⇒ mask 的 light/regular/thick 是**浓度档**(同一深色的三个 alpha),不是深浅两套值:
它在**两套主题下都是深色**(模态遮罩语义)。而 WebUI 的 `--bg-scrim` 浅色是**白**
(原话「浅色下用白把花哨的图案洗淡」)—— 壁纸遮盖用 mask 就是**反方向**:
浅色主题下把预设压暗,而 WebUI 是把它洗淡。

## 改法

- 新增 `Theme.wallpaperScrim = $r('sys.color.ohos_id_color_background')`(页面底色系:
  浅色白、深色近黑,**自动换向**,与"朝底色淡化"同一意图);预设档与图片档的遮盖层都用它。
- `Theme.overlay`(mask)**只留给模态弹层**(那里语义确实是压暗背后)。
- 不复用 `pageBg` 的原因写进注释:页面底在背景开启时会被换成**透明**
  (`bgActive ? Color.Transparent : Theme.pageBg`),而遮盖层**永远要一个真实颜色** ——
  两个用途生命周期不同,共用一个名字迟早坏一头(这个仓库撞过四次的模式)。

## 判据(从 SDK 读真值再判,不写死结论)

断言「mask 两套主题都深」「页面底色系浅白深黑」「WebUI 的 `--bg-scrim` 浅色是白」,
再落到代码:两处遮盖层必须用 `wallpaperScrim`、弹层仍用 `overlay`、两个令牌不许混用。
**这样"令牌选错方向"以后不能靠记性避免。**

变异:遮盖退回 mask → 红 1 条;只改一层 → 红 2 条。

## 文档

§7.17b-2 记这次读数(表格 + 两张表怎么读 + 为什么不复用 pageBg + 变异结果);
§7.12 有意差异表加一行(遮盖色令牌:两个语义两个令牌,"不允许混用")。

## 验证

`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(12 个判据文件全绿 + vitest 258/258;harmony-appearance 23 → 24 条)。
This commit is contained in:
2026-09-14 15:10:15 +08:00
parent a8ac2fc28b
commit d1e0ba32f9
4 changed files with 160 additions and 4 deletions

View File

@ -380,7 +380,12 @@ test('★ 页面真的把背景画出来了这一条是补漏P4 第一版
assert.match(main, /Image\(this\.wallpaperImage\)/, '图片档要真的把图渲染出来');
assert.match(main, /\.objectFit\(ImageFit\.Cover\)/, '图片要铺满(不是拉伸变形或留白)');
// 压暗用系统遮罩色 + 服务端浓度
assert.match(main, /\.backgroundColor\(Theme\.overlay\)[\s\S]{0,80}?\.opacity\(this\.bgPlan\.scrim\)/, '压暗层要用系统遮罩色与算出来的浓度');
/*
* 遮盖层:用**页面底色系**的 `Theme.wallpaperScrim`(不是模态遮罩 mask——
* 浅色主题下 mask 是深色(#99182431会把预设压暗而 WebUI 是把预设洗淡(--bg-scrim 是白)。
* 这一条与下面那条"遮盖色方向"是同一件事的两个面:这里钉"层在不在、浓度接没接上"。
*/
assert.match(main, /\.backgroundColor\(Theme\.wallpaperScrim\)[\s\S]{0,80}?\.opacity\(this\.bgPlan\.scrim\)/, '遮盖层要用页面底色系令牌与算出来的浓度');
// 背景在主界面这一层Tabs 之上不该再有不透明的底色把背景盖死
assert.match(main, /Stack\(\) \{[\s\S]{0,120}?this\.WallpaperLayer\(\)/, '背景要铺在内容之下Stack 的底层)');
});
@ -585,7 +590,7 @@ test('★ 预设档的遮盖两档同一个浓度WebUI 的 --bg-dim 不区
assert.match(bgStore, /--bg-dim|setProperty\('--bg-dim'/, 'WebUI 要无条件写 --bg-dim这是"两档都压"的依据)');
const main = read('pages/MainPage.ets');
// 两档各有一处遮盖层(都用系统遮罩色 + 算出来的浓度)
const scrims = [...main.matchAll(/\.backgroundColor\(Theme\.overlay\)\s*\n\s*\.opacity\(this\.bgPlan\.scrim\)/g)];
const scrims = [...main.matchAll(/\.backgroundColor\(Theme\.wallpaperScrim\)\s*\n\s*\.opacity\(this\.bgPlan\.scrim\)/g)];
assert.ok(scrims.length >= 2, `预设档与图片档各要有一层遮盖(实际 ${scrims.length} 处)`);
});
@ -647,3 +652,89 @@ test('★ 主题变化时**我们自己算的值**要跟着重算pi 的规则
const sdk = readFileSync(join(CLT, 'sdk/default/openharmony/ets/api/@ohos.app.ability.EnvironmentCallback.d.ts'), 'utf8');
assert.match(sdk, /onConfigurationUpdated\(config: Configuration\): void/, 'SDK 里回调是 onConfigurationUpdated别写错名字');
});
// ───────── 遮盖色方向:从 SDK 两张表读出真值再判pi 2026-09-14 的方向性疑问) ─────────
/** 从 SDK 读出 `sys.color.ohos_id_color_*` 的真实值名字→id 取编译器那张表id→值取预览器那张 */
function sdkSystemColors() {
const sysRes = readFileSync(join(CLT, 'sdk/default/openharmony/ets/build-tools/ets-loader/sysResource.js'), 'utf8');
const resTxt = readFileSync(join(CLT, 'sdk/default/openharmony/previewer/common/resources/entry/resources.txt'), 'utf8');
const name2id = new Map();
for (const m of sysRes.matchAll(/'?(ohos_id_color_[a-z_]+)'?:\s*(\d+)/g)) {
if (!name2id.has(m[1])) name2id.set(m[1], Number(m[2]));
}
const id2val = new Map();
for (const m of resTxt.matchAll(/id:(\d+),\s*'([^']*)'/g)) {
const id = Number(m[1]);
if (!id2val.has(id)) id2val.set(id, m[2]);
}
const out = {};
for (const [name, id] of name2id) if (id2val.has(id)) out[name] = id2val.get(id);
return out;
}
/** '#AARRGGBB' → 亮度0=黑 1=白);只用于判"深还是浅" */
function luminanceOf(argb) {
const r = parseInt(argb.slice(3, 5), 16) / 255;
const g = parseInt(argb.slice(5, 7), 16) / 255;
const b = parseInt(argb.slice(7, 9), 16) / 255;
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
test('★ 遮盖色方向:`mask_*` 两套主题下都是**深色**(模态遮罩),壁纸遮盖必须用**页面底色系**', () => {
/*
* pi 的疑问原话:「系统那三个 mask —— 名字里的 light/regular/thick 是**浓度档**
* (同一个色的三个 alpha不是深浅主题的两套值。它们的用途是**模态遮罩**(弹层背后压暗),
* 所以两套主题下通常都是深色。如果预设/图片的遮盖层用 mask 色,**浅色主题下会把预设压暗,
* 而 WebUI 是把预设洗淡**:方向相反,而且这是"机制上确定不同",不是观感。」
*
* 他让我先把值读出来再定 —— 这里就是那次读数,做成判据(免得以后凭记忆选令牌)。
*/
let colors;
try {
colors = sdkSystemColors();
} catch (e) {
assert.fail(`读不到 SDK 的系统色表(这条判据无从判起):${e.message}`);
}
const maskRegular = colors['ohos_id_color_mask_regular'];
const maskDark = colors['ohos_id_color_mask_regular_dark'];
const bg = colors['ohos_id_color_background'];
const bgDark = colors['ohos_id_color_background_dark'];
assert.ok(maskRegular && maskDark && bg && bgDark, '系统色表里这几个令牌都要有值');
// 事实一mask 在浅色主题下也是深色(#99182431—— 所以它是模态遮罩语义
assert.ok(luminanceOf(maskRegular) < 0.2,
`mask_regular 在浅色主题下应当是深色(实测 ${maskRegular})—— 若是浅色,这条判据的前提要重写`);
assert.ok(luminanceOf(maskDark) < 0.2, `mask 深色主题下也应当是深色(实测 ${maskDark}`);
// 事实二:页面底色系随主题换向(浅色白、深色近黑)—— 这才是"朝底色淡化"
assert.ok(luminanceOf(bg) > 0.8, `ohos_id_color_background 浅色主题下应当是白(实测 ${bg}`);
assert.ok(luminanceOf(bgDark) < 0.2, `ohos_id_color_background_dark 应当近黑(实测 ${bgDark}`);
// 事实三WebUI 的遮罩方向是"朝底色淡化"(浅色白、深色黑)—— 与页面底色系同向、与 mask 反向
const webCss = readFileSync(join(ROOT, 'client/electron/src/index.css'), 'utf8');
const scrimLight = /--bg-scrim:\s*(\d+)\s+(\d+)\s+(\d+);/.exec(webCss);
assert.ok(scrimLight, 'CSS 里要有 --bg-scrim');
assert.deepEqual([scrimLight[1], scrimLight[2], scrimLight[3]], ['255', '255', '255'],
'WebUI 浅色下的遮罩是**白**(把图案洗淡);这是"必须用页面底色系"的依据');
// 结论落到代码:壁纸遮盖用页面底色系令牌,且**不是** mask
const theme = readFileSync(join(HARMONY_ETS, 'common/Theme.ets'), 'utf8');
assert.match(theme, /static readonly wallpaperScrim: Resource = \$r\('sys\.color\.ohos_id_color_background'\)/,
'壁纸遮盖色要用页面底色系ohos_id_color_background');
assert.match(theme, /static readonly overlay: Resource = \$r\('sys\.color\.ohos_id_color_mask_regular'\)/,
'overlay模态遮罩保持 mask');
const main = read('pages/MainPage.ets');
const scrims = [...main.matchAll(/\.backgroundColor\(Theme\.(\w+)\)\s*\n\s*\.opacity\(this\.bgPlan\.scrim\)/g)];
assert.equal(scrims.length, 2, '预设档与图片档各一层遮盖');
for (const s of scrims) {
assert.equal(s[1], 'wallpaperScrim',
'壁纸遮盖层的颜色必须用 wallpaperScrim页面底色系。用 mask 会在浅色主题下把预设压暗,与 WebUI 反向');
}
// 两个语义不许共用一个令牌(这个仓库撞过四次的那个模式)
assert.notEqual('wallpaperScrim', 'overlay');
// 模态弹层仍然用 mask那里的语义确实是"压暗背后"
const settings = read('pages/SettingsPage.ets');
assert.match(settings, /Theme\.overlay/, '自绘弹层的遮罩仍然用 mask那里的语义是压暗背后');
assert.ok(!/wallpaperScrim/.test(settings), '弹层不该用壁纸遮盖色(两个语义别混)');
});

View File

@ -62,6 +62,34 @@ export class Theme {
*/
static readonly overlay: Resource = $r('sys.color.ohos_id_color_mask_regular');
/**
* 壁纸遮盖层的颜色 —— **不是** `overlay`(那是模态遮罩),两者语义不同。
*
* pi 2026-09-14 提出的方向性疑问,我按他说的去 SDK 里把值读出来了
* `ets/build-tools/ets-loader/sysResource.js` 给 名字→id
* `previewer/common/resources/entry/resources.txt` 给 id→值两张表交叉验证
*
* | 令牌 | 浅色主题 | 深色主题 |
* |---|---|---|
* | `ohos_id_color_mask_regular`= `overlay` | `#99182431` **深蓝灰** | `#b2000000` 黑 |
* | `ohos_id_color_background` | `#ffffffff` **白** | `#ff18181a` 近黑 |
*
* 也就是说 **`mask_*` 在两套主题下都是深色**(它的 light/regular/thick 是**浓度档**
* 不是深浅两套值),用途是**模态遮罩**(弹层背后压暗)。
* 而 WebUI 的 `--bg-scrim` 在 `:root` 是**白**、`.dark` 才是黑,注释原话是
* 「浅色下用白把花哨的图案洗淡,深色下用黑压暗」—— 它做的是"**朝页面底色淡化**"。
*
* 所以壁纸遮盖若用 `mask`**浅色主题下会把预设压暗,而 WebUI 是把它洗淡 —— 方向相反**。
* 这是"机制上确定不同",不是观感。改用**页面底色系**:那才是"朝底色淡化"的系统对应物,
* 浅色=白底方向、深色=深底方向,自动换向,与 WebUI 是同一个意图。
*
* ⚠️ 为什么不干脆复用 `pageBg`(值一样是 `ohos_id_color_background`
* 页面底在背景开启时会被换成语义上的**透明**`bgActive ? Color.Transparent : Theme.pageBg`
* 而遮盖层**永远需要一个真实颜色**。两个用途的生命周期不同,共用一个名字迟早坏一头
* —— 这正是这个仓库撞过四次的模式(`text-white` vs `bg-white`、`--c-*` vs `--s-*`)。
*/
static readonly wallpaperScrim: Resource = $r('sys.color.ohos_id_color_background');
/**
* 导航/浮层的材质档次。**不再有 `#B8FFFFFF` / `#B80F172A` 这种手写玻璃 alpha** ——
* 那两个值等于"我们替系统猜了深色该怎么做",与"用系统方案"直接冲突;

View File

@ -1652,7 +1652,8 @@ struct MainPage {
*/
Column()
.width('100%').height('100%')
.backgroundColor(Theme.overlay)
// 遮盖色 = **页面底色系**(朝底色淡化),不是模态遮罩 mask浅色下那会压暗方向相反
.backgroundColor(Theme.wallpaperScrim)
.opacity(this.bgPlan.scrim)
}
.width('100%').height('100%')
@ -1664,7 +1665,8 @@ struct MainPage {
// 压暗用**系统遮罩色** + 服务端给的浓度:换向(浅色洗白/深色压黑)由系统负责
Column()
.width('100%').height('100%')
.backgroundColor(Theme.overlay)
// 遮盖色 = **页面底色系**(朝底色淡化),不是模态遮罩 mask浅色下那会压暗方向相反
.backgroundColor(Theme.wallpaperScrim)
.opacity(this.bgPlan.scrim)
}
.width('100%').height('100%')

View File

@ -517,6 +517,7 @@ deb 也不必从 targets 里摘。已写进 `client/electron/BUILD.md`(含排
| 遮罩 | 自声明 `--bg-scrim` + `--bg-dim` 两段式 | 系统 `sys.color.ohos_id_color_mask_regular` | 遮罩要随主题换向浅色洗白/深色压黑这件事系统已经做了 |
| **品牌色** | `--c-blue-600: 37 99 235` | `Theme.accent = '#2563EB'` | **不允许差异** —— 两个客户端是同一个产品 |
| **本地外观缓存的键** | 全局常量 `agentmail.background` | **按账号**`appearance.<accountId>` | **鸿蒙是对的,不许为"对齐"退回全局键** —— 全局键的后果是切到服务端没有记录的账号时`saved=false` 分支会把**上一个账号的外观** push 上去新账号"继承"了外观而且写进了服务端)。pi 2026-09-14 确认这条记在 WebUI 侧为开项换键或至少在把继承来的值当"本地的"推给从未有记录的账号前停一下 |
| **遮盖色的令牌** | `--bg-scrim`浅色白 / 深色黑"朝底色淡化" | `Theme.wallpaperScrim` = `sys.color.ohos_id_color_background`同向`Theme.overlay` = mask **只用于模态弹层** | **不允许混用** —— mask 两套主题下都是深色浅色 `#99182431`拿它当壁纸遮盖会在浅色主题下压暗 WebUI 反向)。两个语义两个令牌理由与实测值见 §7.17b-2 |
| **遮罩浓度的默认值** | 两套store `dim=24/blur=8` `lib/appearance.ts` `clamp(...,12,4)` | 12 / 4只有一套 | 服务端**缺字段**时真正落地的是 `clamp` 的默认值所以按 12/4 对齐WebUI 那两套值迟早要统一pi 记在他那边 |
**关于 `overlayColor` / `overlayAlpha` 消失**pi 要求把删除理由记在这里否则下一个人会当成漏改补回来
@ -826,6 +827,40 @@ SDK 锚点(免得后人凭记忆写):回调接口是 `@ohos.app.ability.En
踩过两个编译错:`Configuration.colorMode` 是**枚举 | undefined**,字段写成 `number` 直接报错;
`ApplicationContext.on('environment')` 返回的是 **number 型 callbackId**(不是 void
### 7.17b-2 遮盖色的**方向**:模态遮罩 ≠ 壁纸遮盖pi 的方向性疑问,按实测改)
pi 的疑问:「系统那三个 `mask_*` —— light/regular/thick 是**浓度档**(同一个色的三个 alpha
不是深浅两套值;用途是**模态遮罩**(弹层背后压暗),所以两套主题下通常都是深色。
如果壁纸遮盖用 mask 色,**浅色主题下会把预设压暗,而 WebUI 是把预设洗淡**:方向相反。」
他让我先把值读出来再定。读数方式(两张 SDK 表交叉验证,不靠记忆):
`ets/build-tools/ets-loader/sysResource.js` 给**名字→id**
`previewer/common/resources/entry/resources.txt`**id→值**
| 令牌 | 浅色主题 | 深色主题 |
|---|---|---|
| `ohos_id_color_mask_regular`= 原 `Theme.overlay` | `#99182431` **深蓝灰** | `#b2000000` 黑 |
| `ohos_id_color_mask_thick` / `_light` | `#cc182431` / `#66182431` 深 | 同为深 |
| `ohos_id_color_background` | `#ffffffff` **白** | `#ff18181a` 近黑 |
**pi 是对的,而且这是"机制上确定不同"**mask 在两套主题下都是深色(它是模态遮罩),
而 WebUI 的 `--bg-scrim` 浅色是**白**(把花哨的图案洗淡)、深色才是黑。
壁纸遮盖用 mask = 浅色下把预设压暗,方向与 WebUI 相反。
**改法**:新增 `Theme.wallpaperScrim = $r('sys.color.ohos_id_color_background')`
(页面底色系:浅色白、深色近黑,自动换向,与"朝底色淡化"同一意图),
壁纸两档的遮盖层都改用它;`Theme.overlay`mask**只留给模态弹层**(那里的语义确实是压暗背后)。
**为什么不再复用 `pageBg`**(值一样是 `ohos_id_color_background`):页面底在背景开启时会被换成
语义上的**透明**`bgActive ? Color.Transparent : Theme.pageBg`),而遮盖层**永远要一个真实颜色**。
两个用途生命周期不同,共用一个名字迟早坏一头 —— 就是这个仓库撞过四次的模式
`text-white` vs `bg-white``--c-*` vs `--s-*`、掩码 vs 遮罩)。
**判据**:从 SDK 两张表读出真值断言「mask 两套主题都深」「页面底色系浅白深黑」
「WebUI 的 `--bg-scrim` 浅色是白」,再落到代码(两处遮盖层都必须用 `wallpaperScrim`
弹层仍用 `overlay`)。变异:遮盖退回 mask → 红 1 条;只改一层 → 红 2 条。
**这样"令牌选错方向"以后不能再靠记性避免。**
### 7.17c 手写色清册升级成**跨文件按类扫**pi 建议)
原来的 A2 只保护 `Theme.ets`;而 `Wallpaper.ts` 也有手写色14 个预设色)。