Unlucky Launcher v0.2.3
CI / runtime smoke (macos arm64, probe) (push) Skipped
Release guard / 发布标记语义守门(push tag → 稳定版) (push) Canceled after 0s
Release / 选槽位(默认两个 Windows) (push) Canceled after 0s
Release / build (${{ matrix.slot }}) (push) Canceled after 0s
Release / GitHub Release (push) Canceled after 0s
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
CI / runtime smoke (ubuntu x64, xvfb) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好一个文件) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s

This commit is contained in:
AskaEth committed 2026-11-08 00:15:58 +00:00
commit d616d65ea1
1219 files changed
+237663

No files matched your search

@@ -0,0 +1,142 @@
//! `[window]`:**本进程给窗口的那些设置**(不是我们从别处猜的)。
//!
//! 唯一产地是 `app/boot/diagnostics.rs::window_settings`(`pages_demo` 通道自己也有一份,
//! 它没有最小尺寸与自定标题)。这里只做「从 `window::Settings` 抽字段 → 排版」。
//!
//! **别再把这些字段写进格式化串**:M98 r1 p2-1 就是这么错的——日志说 `decorations=false`,
//! 而真实 `settings` 可以改成 `true` 而不惊动任何断言(踩坑记录 U1)。
/// 窗口尺寸覆盖(`ULL_WINDOW_SIZE=1520x900`)。
///
/// 为什么是环境变量而不是 `--width`:`cli.rs` 不在 M98 的 scope,而验收矩阵必须有「最小尺寸 /
/// 宽屏」两档,且必须是**真外壳**(自绘三键 + 标题栏)——`pages_demo` example 的标题栏是留白带,
/// 出不了「三键位置」那张图。语义与 `ULL_START_ROUTE` 一致:只影响这一次进程。
pub const WINDOW_SIZE_ENV: &str = "ULL_WINDOW_SIZE";
// ---------------------------------------------------------------- [window]
/// 窗口事实:**从真实的 `window::Settings` 抽出来**的那几个字段(唯一产地见
/// `app/boot/diagnostics.rs` 的 `window_settings`)。
///
/// 为什么要有这个结构:M98 r1 p2-1 —— 第一版把 `resizable=true decorations=false transparent=false`
/// 写在格式化串里,与真实 `settings` 无绑定;定向变异把 `settings.decorations` 改成 `true`,
/// 371 个测试全绿,而日志会与 `[caps]` 互相矛盾(踩坑记录 U1)。现在这些字段只能从
/// [`WindowFacts::from_settings`] 来,没有手抄的入口。
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WindowFacts {
/// 窗口尺寸(逻辑像素)。
pub size: (f32, f32),
/// 最小尺寸(`None` = 没设)。
pub min: Option<(f32, f32)>,
/// 可缩放。
pub resizable: bool,
/// 系统装饰(`false` ⇒ 自绘三键)。
pub decorations: bool,
/// 整窗透明。
pub transparent: bool,
}
impl WindowFacts {
/// 从**真正交给 iced/winit 的那个** `window::Settings` 抽字段。
#[must_use]
pub fn from_settings(settings: &iced::window::Settings) -> Self {
Self {
size: (settings.size.width, settings.size.height),
min: settings.min_size.map(|size| (size.width, size.height)),
resizable: settings.resizable,
decorations: settings.decorations,
transparent: settings.transparent,
}
}
}
/// `[window]` 的**请求值**一行(实际值见模块文档的表:脚本从 `[shot] … scale=` 与 PNG 换算)。
///
/// `title` 是 `Option`:`pages_demo` 那条通道没有自定标题,打 `<默认>` —— 编一个空串会让
/// 「标题是什么」变成猜的。
pub fn report_window(
facts: &WindowFacts,
scale: Option<f32>,
size_source: &str,
title: Option<&str>,
) {
eprintln!("{}", window_text(facts, scale, size_source, title));
}
/// 见 [`report_window`]。
#[must_use]
pub fn window_text(
facts: &WindowFacts,
scale: Option<f32>,
size_source: &str,
title: Option<&str>,
) -> String {
let min = facts
.min
.map_or_else(|| "none".to_owned(), |(w, h)| format!("{w}x{h}"));
let title = title.map_or_else(|| "<默认>".to_owned(), |text| format!("\"{text}\""));
format!(
"[window] requested size={}x{} min={min} resizable={} decorations={} transparent={} scale_requested={} size_source={size_source} title={title}",
facts.size.0,
facts.size.1,
facts.resizable,
facts.decorations,
facts.transparent,
scale.map_or_else(|| "system".to_owned(), |value| value.to_string()),
)
}
/// 解析 `ULL_WINDOW_SIZE`(`1520x900` / `1520X900` / `1520×900`)。
///
/// 返回 `None` = 不是这个格式(调用方保留默认尺寸并打一行警告,**不**静默忽略)。
#[must_use]
pub fn parse_window_size(raw: &str) -> Option<(f32, f32)> {
let trimmed = raw.trim();
let (width, height) = trimmed
.split_once(['x', 'X', '×', '*'])
.map(|(w, h)| (w.trim(), h.trim()))?;
let width: f32 = width.parse().ok()?;
let height: f32 = height.parse().ok()?;
(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0)
.then_some((width, height))
}
/// 尺寸覆盖 → 实际要开多大 + **尺寸来源**(进 `[window] … size_source=`)。
///
/// 三种来源:`default`(没设 / 设了但解析不了)与 `ULL_WINDOW_SIZE`(生效)。
/// 小于最小尺寸时夹到最小并标 `:clamped-to-min`——iced 的 `min_size` 本来也会拦住,
/// 但那样日志会说「要求 640x480」而实际是 880x560,是典型的「日志说请求、报告当结果」。
#[must_use]
pub fn resolve_window_size(
raw: Option<&str>,
default: (f32, f32),
min: (f32, f32),
) -> ((f32, f32), &'static str) {
let Some(raw) = raw else {
return (default, "default");
};
let Some((width, height)) = parse_window_size(raw) else {
return (default, "ULL_WINDOW_SIZE:invalid");
};
let clamped = (width.max(min.0), height.max(min.1));
if clamped == (width, height) {
(clamped, "ULL_WINDOW_SIZE")
} else {
(clamped, "ULL_WINDOW_SIZE:clamped-to-min")
}
}
/// 窗尺寸(含来源)从进程环境取一遍——`app::boot` 与测试共用同一条路径。
#[must_use]
pub fn window_size(default: (f32, f32), min: (f32, f32)) -> ((f32, f32), &'static str) {
let raw = std::env::var(WINDOW_SIZE_ENV).ok();
let resolved = resolve_window_size(raw.as_deref(), default, min);
if resolved.1.ends_with("invalid") {
eprintln!(
"[window] {WINDOW_SIZE_ENV}={:?} 解析不了(要 1520x900 这种),按默认尺寸跑",
raw.unwrap_or_default()
);
}
resolved
}