语义组件工具箱

使用内置组件组合界面。

Glory 的内置组件是有限的语义构建单元。编写普通 Rust 构建器,连接有所有权的状态,再由所选宿主提供布局、输入和呈现。

了解内置能力

Glory 提供 view/group/text/button/input/check/image/link/file/select/graphic/rich_editor,以及 row/col/when/each/list。卡片和标签页属于 Sapid 组件,不是 Glory 内置组件。没有 textarea 构建器,应使用 input().multiline(true)。

导入构建器

prelude 包含常用构建器、控制流、响应式值、事件和类型化样式。file、select、graphic 和 rich_editor 需从 glory::ui 显式导入;其配置类型位于 glory::render。

imports.rs

1 2 3 4
use glory::prelude::*; // These four builders are intentionally imported from glory::ui. use glory::ui::{file, graphic, rich_editor, select};

结构、内容与控件

结构

view 创建带样式的语义容器。group 组合逻辑子组件而不增加布局盒。row 和 col 是配置了 flex 的视图。

内容

text 承载行内或结构化文本,image 加载具有显式替代文本的图像,link 指向位置,graphic 绘制有界矢量路径。

输入

button、input、check、file、select 和 rich_editor 暴露语义属性和类型化事件,不暴露 DOM 元素。

profile.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
use glory::prelude::*; fn profile_editor() -> impl Widget { widget(|scope| { let bio = scope.cage(String::new()); let public = scope.cage(false); view().class("profile").fill(( text() .kind(TextKind::Heading(1)) .content("Edit profile"), image() .source("/assets/avatar.png") .alt("Ada’s profile photo"), group().fill(text().content("Public information")), input() .name("Biography") .multiline(true) .value(bio) .on(value_changed, move |event| { if let Event::ValueChanged { value, .. } = event { bio.revise(|old| *old = value); } }), check() .label("Show this profile publicly") .checked(public) .on(checked_changed, move |event| { if let Event::CheckedChanged(value) = event { public.revise(|old| *old = value); } }), link() .target("/docs/") .label("Read the documentation"), row().fill(button().label("Save profile")), )) .show_in(scope) }) }

每张图片都需要 alt(...) 或 decorative()。结构性角色和物理查询使用 view;group 没有布局盒。Link 使用 target(...);控件接受静态值或兼容 Lotus 的响应式句柄。

文件、选择、图形与富文本

高级控件使用有界配置值。Select 选项具有稳定 SemanticId 键,文件选择器声明过滤条件和预算,图形包含有限路径命令,富文本编辑器接收共享 RichDocument。

controls.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
use glory::prelude::*; use glory::render::{ FileConfig, FileFilter, GraphicPoint, GraphicProps, PathCommand, RichBlock, RichBlockKind, RichDocument, RichMarks, RichRun, SelectEntry, SelectOption, SelectProps, }; use glory::ui::{file, graphic, rich_editor, select}; use std::rc::Rc; fn advanced_controls() -> impl Widget { let picker = file() .label("Choose images") .configuration(FileConfig { multiple: true, max_files: 2, filters: vec![ FileFilter::Mime("image/*".into()), FileFilter::Extension(".png".into()), ], ..Default::default() }); let rust = SemanticId::new("rust").expect("valid option key"); let languages = select() .configuration(SelectProps { entries: Rc::new(vec![SelectEntry::Option(SelectOption { key: rust.clone(), label: "Rust".into(), value: "rust".into(), disabled: false, })]), placeholder: "Choose a language".into(), ..Default::default() }) .selected(vec![rust]); let warning = graphic().drawing(GraphicProps { width: 24.0, height: 24.0, commands: vec![ PathCommand::Move(GraphicPoint::new(12.0, 2.0)), PathCommand::Line(GraphicPoint::new(22.0, 22.0)), PathCommand::Line(GraphicPoint::new(2.0, 22.0)), PathCommand::Close, ], fill: Some(Color(240, 170, 40, 255)), alternative: Some("Warning".into()), ..Default::default() }); let document = Rc::new( RichDocument::new(vec![RichBlock { kind: RichBlockKind::Paragraph, runs: vec![RichRun { text: "A bold beginning".into(), marks: RichMarks { bold: true, ..Default::default() }, }], }]) .expect("valid rich document"), ); col().fill(( picker, languages, warning, rich_editor() .name("Article") .document(document) .enabled(true), )) }

选择合适的组合工具

row / col

使用这些便捷构造器实现水平或垂直 flex 布局。后续 style 调用会替换其预设 Style,因此替换时需重新声明 display 和 flex_direction。

when

根据静态或响应式布尔值挂载分支。otherwise 提供另一分支;切换分支会释放旧分支所属的工作。

each / list

each 挂载完整的带键 Vec,并向行提供响应式 item/index 句柄。list 惰性索引大型数据源,只挂载可见窗口、预加载区域及至多一个获得焦点的行。

collections.rs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
use glory::prelude::*; fn results(loading: Cage<bool>, ids: Cage<Vec<u64>>) -> impl Widget { col().fill(( when(loading, || text().content("Loading…")) .otherwise(|| text().content("Results")), each(ids, |id| *id, |id, index| { row().fill(( text().content(index.map(|index| format!("{}. ", index + 1))), text().content(id.map(|id| format!("Item {id}"))), )) }), list( 100_000_usize, |index| index as u64, |_, key| text().content(format!("Virtual row {key}")), ) .height(240.0) .row_height(32.0) .overscan(3), )) }

两种集合构建器都应使用稳定键。小型完整集合优先使用 each;大型可滚动数据使用 list,并提供有界键查找,以及固定或测量得到的行高。