feat(app-iced): M93 r2 —— 背景图真图直出 + 面板 α 收敛到 Ui::layer
复审前补齐两处(tower 扩 scope:Cargo.toml / pages/frame.* / theme.rs / presenter::prefs)。 1) 背景图**真图直出** - `iced` 开 `image-without-codecs`(刻意不用 `image`:后者会打开 `image/default` 全套编解码器); 编解码能力由本 crate 的 `image` features 决定,补了 `jpeg` / `bmp`(用户挑的背景图/壁纸多半是 jpg)。 - `pages/appearance/picture.rs`:`Cover`/`Contain` 走渲染器 `ContentFit`,`Tile` 按 `background::tiles` 算行列(上限 64 块)真的铺 N 个图块;暗化 = 一层「版面底色 @ α=dim」叠在图上。 - 36dp 马赛克那条路**已删除**(不再是主路径,也不留死代码)。 - 踩坑记录:`Handle::from_bytes` 的纹理分配赶不上截图第 3 帧(实测画面全空),改用 `Handle::from_path`(渲染器自己解码),已在 `picture::image_handle` 的文档里写明。 2) 面板 α **收敛到唯一来源** - `Ui::layer()` 直接乘上面板 α(新增 `Ui::panel_alpha` / `with_panel_alpha`,默认 1.0、 `scale_alpha(1.0)` 逐字节不变),`app::rebuild_ui` 注入生效值。 - 于是外壳(根容器/标题栏/导航轨)、`frame::card`(实例/下载/日志三页卡片)、 `frame::header_bar`(新增底色)、外观分组卡片、空态图标块**全部同源**, 调用点不再有任何 `panel_color(..)` 自乘(该 helper 已删)。 - 有背景图时根容器不再盖一层实色画布(否则 panel α=1 时背景图整块被挡)。 3) `presenter::prefs` 补重导出 `PANEL_ALPHA_MIN` / `BG_DIM_MAX`,app-iced 里两份 0.4 / 0.95 字面量改为转发常量。 验收:11+3 张图(`C:\Temp\ull-shots\wt-93\r2-*.png`)——三档填充肉眼可分、`.jpg` 能加载、 GPU 档 panel55 时卡片/分组卡/页头带都透出背景、CPU 档仍强制 1.0 且背景在面板空隙可见; 另附实例/下载/日志三页 panel55 各一张,证明三页都吃到同一个 α。
This commit is contained in:
1 parent
b8ab46662c
commit
22e4c423fa
18 files changed
+399
-535
No files matched your search
@@ -0,0 +1,104 @@
|
||||
//! 背景层的**像素**:真图直出(`Cover` / `Contain` / `Tile`)+ 暗化叠层。
|
||||
//!
|
||||
//! 与 [`super::section`] 拆开只为单文件 ≤400 行的纪律;对外入口仍是
|
||||
//! `pages::appearance::layer`(本文件 [`layer`] 的再导出)。
|
||||
//!
|
||||
//! ## 为什么是「真图」
|
||||
//!
|
||||
//! M93 r1 走的是「36dp 网格马赛克」(那时 iced 的 `image` feature 没开)。r2 打开后改成本文件:
|
||||
//! `Cover`/`Contain` 交给渲染器的 `ContentFit`(等比缩放 + 裁切/留白由它做),`Tile` 用
|
||||
//! [`background::tiles`] 算行列数后真的铺 N 个图块(`ContentFit::None` = 原尺寸)。
|
||||
//!
|
||||
//! 暗化不是逐像素改色,而是**盖一层「版面底色 @ α=dim」**:图不透明时它与「向底色插值」
|
||||
//! 数学等价([`background::dim_alpha`] 有推导),而普通 alpha 混合 CPU/GPU 两档都支持。
|
||||
|
||||
use iced::widget::image;
|
||||
use iced::widget::{column, container, image as image_widget, space};
|
||||
use iced::{ContentFit, Element, Length};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_presenter::prefs::BgFill;
|
||||
|
||||
use crate::pages::{frame, Message};
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::background;
|
||||
use super::state::State;
|
||||
|
||||
/// 背景层(铺满整窗、垫在最下层)。`None` = 没有可用的背景图(调用方就不叠这一层)。
|
||||
///
|
||||
/// 真图直出(M93 r2):`Cover` / `Contain` 交给 iced 的 `ContentFit`,`Tile` 用
|
||||
/// [`background::tiles`] 算网格铺 N 个图块。暗化 = 一层「版面底色 @ α=dim」盖在图上
|
||||
/// (见 [`background::dim_alpha`] 的等价性说明)。
|
||||
#[must_use]
|
||||
pub fn layer(ui: &Ui, state: &State, viewport: (f32, f32)) -> Option<Element<'static, Message>> {
|
||||
let picture = state.image()?;
|
||||
if !state.bg_enabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
let handle = image_handle(state.bg_path.trim());
|
||||
let art: Element<'static, Message> = match state.bg_fill {
|
||||
BgFill::Cover => fitted(handle, ContentFit::Cover),
|
||||
BgFill::Contain => fitted(handle, ContentFit::Contain),
|
||||
BgFill::Tile => tiled(handle, picture.source, viewport),
|
||||
};
|
||||
|
||||
// 暗化:底色 @ α=dim 盖在图上(等价于逐像素向底色插值,见 `background::dim_alpha`)。
|
||||
let scrim_color = ui
|
||||
.color(Role::Surface)
|
||||
.scale_alpha(background::dim_alpha(state.bg_dim));
|
||||
let scrim: Element<'static, Message> = container(space::horizontal())
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(move |_theme| frame::filled(scrim_color))
|
||||
.into();
|
||||
|
||||
Some(
|
||||
iced::widget::stack![art, scrim]
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// 文件字节 → iced 的图片句柄(渲染器自己解复用,不再读盘)。
|
||||
fn image_handle(path: &str) -> image::Handle {
|
||||
image::Handle::from_path(path)
|
||||
}
|
||||
|
||||
/// `Cover` / `Contain`:交给渲染器的等比缩放档。
|
||||
fn fitted(handle: image::Handle, fit: ContentFit) -> Element<'static, Message> {
|
||||
image_widget(handle)
|
||||
.content_fit(fit)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// `Tile`:原图尺寸平铺(块数上限见 [`background::MAX_TILES`])。
|
||||
fn tiled(
|
||||
handle: image::Handle,
|
||||
source: (u32, u32),
|
||||
viewport: (f32, f32),
|
||||
) -> Element<'static, Message> {
|
||||
let tiles = background::tiles(source, viewport);
|
||||
let (width, height) = (source.0.max(1) as f32, source.1.max(1) as f32);
|
||||
|
||||
let rows = (0..tiles.rows)
|
||||
.map(|_| {
|
||||
let cells = (0..tiles.cols)
|
||||
.map(|_| -> Element<'static, Message> {
|
||||
image_widget(handle.clone())
|
||||
.content_fit(ContentFit::None)
|
||||
.width(Length::Fixed(width))
|
||||
.height(Length::Fixed(height))
|
||||
.into()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
iced::widget::row(cells).into()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
column(rows).width(Length::Fill).height(Length::Fill).into()
|
||||
}
|
||||
Reference in new issue
Block a user