样式与呈现

清晰边界下的样式设计。

可移植声明使用类型化 Style,文档 CSS 使用类名,有限的可移植子集使用经过验证的 Sheet。CSS 层叠、字体和布局仍由渲染宿主负责。

完整替换

style(...) 替换完整的类型化 Style,class(...) 替换完整的空白分隔类名列表。多次调用不会追加或合并。先计算最终值,并包含 row() 或 col() 提供的布局声明。

使用类型化 Style 表达语义声明

Style 为尺寸、flex 布局、溢出、颜色、字体、边框和其他支持的属性提供显式 Rust 类型。缺省字段不输出内联声明;显式默认值仍有实际含义。

typed.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use glory::prelude::*; fn status_panel() -> impl Widget { let style = Style::new() .display(Display::Flex) .flex_direction(FlexDirection::Column) .width(Length::Percent(100.0)) .max_width(Limit::Px(640.0)) .gap(8.0) .padding(Edges::all(16.0)) .radius(8.0); view() .class("status-panel") .style(style) .fill(text().content("Ready")) }

replacement.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use glory::prelude::*; fn panel(active: bool) -> impl Widget { // Compute each final value once instead of trying to append declarations. let classes = if active { "panel is-active" } else { "panel" }; let style = Style::new() .padding(Edges::all(16.0)) .gap(if active { 8.0 } else { 12.0 }); view().class(classes).style(style) } fn replacement_pitfall() -> impl Widget { view() .class("panel") .class("panel is-active") // Final classes: panel and is-active. .style(Style::new().padding(Edges::all(16.0))) .style(Style::new().gap(8.0)) // The padding declaration is gone. }

通过类名使用外部 CSS

类名将语义节点连接到外部文档 CSS。外部 CSS 不按类型化 Style 解析,因此可使用宿主引擎更广泛的 CSS 语法。可在浏览器文档中提供它,在 SSR Document 中列出它,或通过原生宿主安装;Glory 不会从类名推断共享样式表。

app.css

1 2 3 4 5 6 7 8 9 10
.profile { display: flex; gap: 12px; padding: 16px; border: 1px solid #ccd4e0; } .profile > .profile-title { font: 600 1.25rem/1.4 system-ui, sans-serif; }

profile.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use glory::prelude::*; fn profile() -> impl Widget { view().class("profile").fill( text() .class("profile-title") .content("Ada Lovelace"), ) } // With the ssr feature, attach the external document stylesheet. let document = glory::ssr::Document { title: "Profile".into(), stylesheets: vec!["/assets/app.css".into()], ..Default::default() }; let html = glory::ssr::render_document(profile(), &document)?;

解析有界 Sheet

启用 Glory 的 css 特性可解析不可变 Sheet。它接受当前类型化属性、基于类的子代和后代选择器,以及 hover/focus/enabled/disabled/checked 状态。at 规则、ID 和类型选择器、未知声明、不支持的值及超出预算的输入都会被拒绝。它不是完整 CSS 解析器。

sheet.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use glory::prelude::*; use glory::style::{Profile, Sheet}; fn render_panel() -> std::result::Result<String, Box<dyn std::error::Error>> { let sheet = Sheet::parse( ".panel { display:flex; gap:12px; padding:16px } \ .panel > .title:hover { color:#123456; max-width:none }", )?; sheet.check(Profile::HTML)?; let document = glory::ssr::Document { title: "Bounded sheet".into(), sheets: vec![sheet], ..Default::default() }; let widget = view() .class("panel") .fill(text().class("title").content("Portable rules")); Ok(glory::ssr::render_document(widget, &document)?) }

示例使用 ssr + css:render_document 针对 Profile::HTML 检查并转换每条规则。手动集成时,glory::html::sheet(&sheet, profile) 生成经过验证的 HTML 系列 CSS;支持 CSS 的 Blitz 渲染器使用 set_sheet(&sheet)。安装前会再次检查目标配置。

区分缺省尺寸与无限尺寸

最大尺寸使用 Limit,不使用 Length。Option::None 表示字段缺省,由样式表控制;Some(Limit::None) 显式输出无限上限,移除样式表限制。Length::Auto 用于首选和最小尺寸,不用于 max_width 或 max_height。

limits.rs

1 2 3 4 5 6 7 8 9 10 11 12 13
use glory::prelude::*; let absent = Style::new(); let uncapped = Style::new().max_width(Limit::None); let capped = Style::new().max_width(Limit::Percent(80.0)); assert_eq!(absent.max_width, None); // No inline declaration; CSS can set a cap. assert_eq!(uncapped.max_width, Some(Limit::None)); // Explicit max-width: none. assert_eq!(capped.max_width, Some(Limit::Percent(80.0))); // Preferred and minimum sizes use Length, including Length::Auto. let automatic = Style::new().width(Length::Auto); assert_eq!(automatic.width, Some(Length::Auto));

检查所选宿主的能力配置

每个渲染器在 App 生命周期内暴露不可变的样式配置。挂载和类型化样式更新会在提交前验证值,并拒绝不支持的属性或单位。Profile::RECORD 接受声明但不执行布局;HTML 和 Blitz 各有真实引擎边界。

profiles.rs

1 2 3 4 5 6 7 8 9 10
use glory::prelude::*; fn accepted_by_host(app: &glory::App, style: &Style) -> bool { style.validate().is_ok() && app.style_profile().inline.check(style).is_ok() } // App::memory() records typed declarations with Profile::RECORD, but does no layout. // HTML hosts use Profile::HTML. Blitz exposes its current native profile as // glory::blitz::STYLE_PROFILE when the blitz feature is enabled.

可移植值不保证相同像素

两个宿主接受同一声明,仍可能使用不同字体、文本整形、视口规则和 CSS 引擎。请在每个目标上测试重要布局,不要期待像素完全一致。

使用 Sapid 主题设置组件样式

Sapid 的 Theme、调色板、变体和尺寸提供协调一致的组件样式。先用主题设置控件颜色及状态,再用 Glory 样式或应用样式类处理外围布局。迁移到其他渲染宿主时,请同时核对示例中的样式表配置和具体组件的宿主支持情况。

浏览 Sapid 主题与组件示例 ↗