语义组件工具箱
使用内置组件组合界面。
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
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
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
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
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,并提供有界键查找,以及固定或测量得到的行高。