feat(app-iced): M93 —— 外观接线:整窗 α / 面板 α / 背景图 / 配色来源
把 appearance.* 那批键接到窗口与像素上(M91 只落了键与判据,零调用方)。
整窗 α(D34/D36)
- `ensure_layered(hwnd)` 落在唯一 unsafe 文件 `platform/win32/transparency.rs`
(`WS_EX_LAYERED` 只有 Get/SetWindowLongPtrW 能加,且 CI 的 unsafe-scope 是集合相等);
`app/boot.rs` 只负责开窗,句柄到手后由 `Message::WindowHandle` → `window_alpha::dispatch`
先补样式再下发。本机实测 `layered=ok outcome=Applied { byte: 205 }`(α=0.8)。
- 三条入口都会重算并下发:启动(句柄到达)、设置页改动、配置文件热切。
- `layered` 日志有三态:`n/a`(α≥1,压根不需要)/ `ok` / `FAILED`,避免把「没用到」写成假红。
面板 α 与 CPU 档(D36)
- 面板 α 独立于整窗 α:只作用到外壳层(根容器 / 标题栏 / 导航轨)与外观卡片;
CPU 档强制 1.0、玻璃强制关(`pages/appearance/alpha.rs` 的真值表 + 单测)。
- 色卡档继续走令牌 baseline,只有取色档(①②级)才换 `Ui` 的 scheme(`Ui::from_scheme`)。
背景图(D38)
- 读盘解码(png)→ cover/contain/tile 三种填充几何 → `bg_dim` 向底色退 → 36dp 网格马赛克;
iced 的 `image` widget 需要 `iced/image` feature(Cargo.toml 不在 scope),故为已登记降级。
- 读盘失败静默回落 + 一条可读诊断(设置页错误行 + 日志),配色经 `Provenance` 回落色卡。
设置页「外观」分组
- 整窗透明度滑块(下限 0.08)/ 面板透明度(CPU 档禁用并写明 55%→100% 的降级)/
背景图开关与路径 / 填充三档 / 暗化 / 配色来源三档;改完即时生效并写回:
bridge 覆盖的 5 个键走 `SettingsBridge::persist`,`window_opacity` / `palette_source`
走 `AppCommand::SetSetting`(每个键只有一个写者)。
- `SettingsBridge::load_base()`(presenter)让 app-iced 不需要直接依赖 core 就能读回
`window_opacity` / `palette_source`。
取证
- `ULL_APPEARANCE_*` / `ULL_START_ROUTE` 只影响本次进程(语义同 ULL_TICK_TRACE),
截图通道不读用户配置(D30)。
This commit is contained in:
1 parent
eae7ae0ff4
commit
b8ab46662c
24 files changed
+3119
-48
No files matched your search
@@ -34,6 +34,7 @@ use unlucky_design::typography::TypeRole;
|
||||
use crate::app::Message;
|
||||
use crate::cli::ForcedState;
|
||||
use crate::icons;
|
||||
use crate::pages::appearance::panel_color;
|
||||
use crate::theme::Ui;
|
||||
|
||||
/// 整棵树(**内容槽位版**:外壳画标题栏 / 缩放热区 / 导航轨,中间那块交给调用方)。
|
||||
@@ -51,24 +52,41 @@ use crate::theme::Ui;
|
||||
/// 而页面自己的 `layout::frame_metrics` 仍以**窗口宽**为输入(它内部会再减 rail 与页边距),
|
||||
/// 两边的账不会算串。
|
||||
///
|
||||
/// [`view`] 是它的兼容包装:`content` 换成 M68 的「页头 + 一张卡」。
|
||||
/// M93 起面板 α 是**外壳层**的属性(根容器 / 标题栏 / 导航轨的底色按它乘 alpha):要背景层的
|
||||
/// 调用方走 [`view_with_background`]。
|
||||
pub fn view_with_content<'a>(
|
||||
ui: &'a Ui,
|
||||
selected: NavItem,
|
||||
app_title: &'a str,
|
||||
content: Element<'a, Message>,
|
||||
forced: Option<ForcedState>,
|
||||
) -> Element<'a, Message> {
|
||||
view_with_background(ui, selected, app_title, None, 1.0, content, forced)
|
||||
}
|
||||
|
||||
/// 同 [`view_with_content`],但可以在整棵树**最底下**垫一层背景(M93 的背景图马赛克)。
|
||||
///
|
||||
/// `background` 由 `pages::appearance::layer` 产出(已与 `Message::Page` 映射过);垫在 `stack!`
|
||||
/// 最底层,于是「面板 α < 1」时能真的看见它——这就是 D34 在像素上的落点。
|
||||
pub fn view_with_background<'a>(
|
||||
ui: &'a Ui,
|
||||
selected: NavItem,
|
||||
app_title: &'a str,
|
||||
background: Option<Element<'a, Message>>,
|
||||
panel_alpha: f32,
|
||||
content: Element<'a, Message>,
|
||||
forced: Option<ForcedState>,
|
||||
) -> Element<'a, Message> {
|
||||
let work_area = column![
|
||||
titlebar(ui, app_title, forced),
|
||||
row![nav::rail(ui, selected, forced), content]
|
||||
titlebar(ui, app_title, forced, panel_alpha),
|
||||
row![nav::rail(ui, selected, forced, panel_alpha), content]
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
window_frame(ui, work_area.into())
|
||||
window_frame(ui, background, panel_alpha, work_area.into())
|
||||
}
|
||||
|
||||
/// 整棵树(M68 骨架版:内容区是「页头 + 一张占位卡」)。
|
||||
@@ -92,8 +110,16 @@ pub fn view<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
/// 无边框窗口的「外壳壳子」:工作区 + 两条缩放热区 + 根容器底色。
|
||||
fn window_frame<'a>(ui: &'a Ui, work_area: Element<'a, Message>) -> Element<'a, Message> {
|
||||
/// 无边框窗口的「外壳壳子」:背景层 + 工作区 + 两条缩放热区 + 根容器底色。
|
||||
///
|
||||
/// 面板 α 落在两处:根容器底色(= 整窗 Canvas 层)与标题栏 / 导航轨底色。`panel_alpha == 1.0`
|
||||
/// 时颜色逐字节不变,所以没开面板透明度时不动既有验收图。
|
||||
fn window_frame<'a>(
|
||||
ui: &'a Ui,
|
||||
background: Option<Element<'a, Message>>,
|
||||
panel_alpha: f32,
|
||||
work_area: Element<'a, Message>,
|
||||
) -> Element<'a, Message> {
|
||||
// 无边框窗口没有系统缩放边框 → 自己给两条热区(东/南)。
|
||||
// **必须叠层而不是兄弟列**:当兄弟列会吃掉 4px 布局宽(右留白变 28、caption 按钮离窗角 4px,
|
||||
// 视觉复审 P3-1)。叠层容器自己铺满,再用 align 把 4px 热区贴到边上,布局宽保持 1180。
|
||||
@@ -124,12 +150,16 @@ fn window_frame<'a>(ui: &'a Ui, work_area: Element<'a, Message>) -> Element<'a,
|
||||
.height(Length::Fill)
|
||||
.align_y(Alignment::End);
|
||||
|
||||
let shell = stack![work_area, east, south];
|
||||
// 背景在最底层:面板 α < 1 时从它的空隙里透出来(M93 / D34)。
|
||||
let shell = match background {
|
||||
Some(layer) => stack![layer, work_area, east, south],
|
||||
None => stack![work_area, east, south],
|
||||
};
|
||||
|
||||
container(shell)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(|_theme| filled(ui.layer(MaterialLayer::Canvas)))
|
||||
.style(move |_theme| filled(panel_color(ui, MaterialLayer::Canvas, panel_alpha)))
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -152,7 +182,12 @@ fn resize_edge<'a>(
|
||||
|
||||
// ---------------------------------------------------------------- 标题栏
|
||||
|
||||
fn titlebar<'a>(ui: &'a Ui, title: &'a str, forced: Option<ForcedState>) -> Element<'a, Message> {
|
||||
fn titlebar<'a>(
|
||||
ui: &'a Ui,
|
||||
title: &'a str,
|
||||
forced: Option<ForcedState>,
|
||||
panel_alpha: f32,
|
||||
) -> Element<'a, Message> {
|
||||
let metrics = ui.metrics();
|
||||
|
||||
// 拖动热区:从窗口左缘到第一个按钮(按钮自己不能被拖区吞掉,所以只包左侧一段)
|
||||
@@ -184,7 +219,7 @@ fn titlebar<'a>(ui: &'a Ui, title: &'a str, forced: Option<ForcedState>) -> Elem
|
||||
container(bar)
|
||||
.width(Length::Fill)
|
||||
.height(metrics.titlebar_height)
|
||||
.style(|_theme| filled(ui.layer(MaterialLayer::Canvas)))
|
||||
.style(move |_theme| filled(panel_color(ui, MaterialLayer::Canvas, panel_alpha)))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user