pub fn divider() -> ViewExpand description
Create a Divider.
Examples found in repository?
examples/inbox.rs (line 66)
60fn detail(i: usize) -> View {
61 let m = &MESSAGES[i];
62 vstack_aligned_spacing(
63 &[
64 text(m.subject).text_style(TextStyle::Title).bold(),
65 text!("From {}", m.from).fg_system(SystemColor::Gray),
66 divider(),
67 text("Body text would go here.").fg_system(SystemColor::Gray),
68 spacer(),
69 ],
70 HorizontalAlignment::Leading,
71 Some(12.0),
72 )
73 .padding(20.0)
74 .navigation_title(m.subject)
75}More examples
examples/app.rs (line 22)
9fn main() {
10 run(|| {
11 vstack(&[
12 text("Hello from Rust! ๐ฆ")
13 .padding(12.0)
14 .bg(Color::BLUE)
15 .rounded(8.0)
16 .opacity(0.95),
17 spacer(),
18 hstack(&[
19 image("star.fill"),
20 text("No Swift compiler needed"),
21 ]),
22 divider(),
23 text("Pure dlsym + arm64 asm โ SwiftUI")
24 .padding(8.0),
25 ])
26 .padding(40.0)
27 .into_handle()
28 });
29}examples/scenes.rs (line 53)
48fn main_window() -> ViewHandle {
49 vstack(&[
50 text("A SwiftUI app, no Swift source")
51 .font_size(20.0)
52 .padding(8.0),
53 divider(),
54 text(&format!("count: {}", counter().get()))
55 .font_size(34.0)
56 .padding(12.0),
57 hstack(&[
58 button("โ", || counter().set(counter().get() - 1)),
59 button("+", || counter().set(counter().get() + 1)),
60 ]),
61 spacer(),
62 text("โ, for Settings ยท the โ
in the menu bar has a menu")
63 .font_size(11.0)
64 .opacity(0.6),
65 ])
66 .padding(24.0)
67 .frame(420.0, 300.0)
68 .focused_scene_value(counter_focus_key(), counter().get())
69 .into_handle()
70}
71
72/// The focused-value key the window publishes its count under.
73///
74/// `.focusedSceneValue(\.key, value)` is how a window tells the commands above
75/// it what is selected. Both halves are synthesised at run time: the
76/// `FocusedValueKey` conformance *and* the `WritableKeyPath<FocusedValues, Int?>`
77/// โ `FocusedValues` exports no properties, so unlike `EnvironmentValues` there
78/// is no SwiftUI modifier holding a key path to borrow.
79///
80/// The menu cannot *read* it yet: that needs `@FocusedValue`, a `DynamicProperty`
81/// SwiftUI instantiates itself, which is a different mechanism from anything
82/// here. What this demonstrates is the publishing half, in a real app.
83fn counter_focus_key() -> &'static views::FocusKey {
84 static K: std::sync::OnceLock<views::FocusKey> = std::sync::OnceLock::new();
85 K.get_or_init(|| views::focus_key("sceneCounter"))
86}
87
88fn settings_pane() -> ViewHandle {
89 vstack(&[
90 text("Preferences").font_size(18.0),
91 divider(),
92 text("This pane is a `Settings` scene โ the one โ, opens."),
93 text(&format!("It sees the same state: {}", counter().get()))
94 .opacity(0.7),
95 ])
96 .padding(24.0)
97 .frame(360.0, 200.0)
98 .into_handle()
99}
100
101fn utility_window() -> ViewHandle {
102 vstack(&[
103 text("Utility").font_size(16.0),
104 text("A `Window`, not a `WindowGroup`: one of it, addressed by id.")
105 .opacity(0.7),
106 ])
107 .padding(20.0)
108 .frame(320.0, 140.0)
109 .into_handle()
110}
111
112fn menu_bar_item() -> ViewHandle {
113 vstack(&[
114 text(&format!("count: {}", counter().get())),
115 button("Reset", || counter().set(0)),
116 ])
117 .padding(10.0)
118 .into_handle()
119}
120
121/// The whole app, as one scene.
122///
123/// Built afresh every time SwiftUI reads `App.body`, which is what makes the
124/// numbers in it live rather than a snapshot taken at launch.
125///
126/// macOS only, and not by choice: `Settings`, `Window`, `MenuBarExtra` and the
127/// whole of `Commands` are `@available(iOS, unavailable)`. iOS gets
128/// [`ios_body`], which is the part of this that exists there.
129#[cfg(target_os = "macos")]
130fn body() -> SceneHandle {
131 scenes! {
132 window_group { main_window() }
133 settings { settings_pane() }
134 window("Utility", "utility") { utility_window() }
135 menu_bar_extra("Count", "star.fill") { menu_bar_item() }
136 commands {
137 // A menu of its own, beside File and Edit.
138 menu("Counter") {
139 vstack(&[
140 button("Increment", || counter().set(counter().get() + 1)),
141 button("Decrement", || counter().set(counter().get() - 1)),
142 divider(),
143 button("Reset", || counter().set(0)),
144 ])
145 }
146 // In place of the standard File โธ New.
147 replacing(NewItem) { button("New Count", || counter().set(0)) }
148 // Added after the standard Help entry.
149 after(Help) {
150 button("About this Rust app", || {
151 eprintln!("built with no Swift compiler in sight");
152 })
153 }
154 }
155 }
156}examples/dashboard.rs (line 59)
54fn body() -> View {
55 vstack_aligned_spacing(
56 &[
57 text("System").text_style(TextStyle::LargeTitle).bold(),
58 grid(&[grid_row(&[tile(0), tile(1), tile(2)])]),
59 divider(),
60 // A gauge over the busiest reading, which is a different control
61 // from the rings and reads the same state.
62 vstack_aligned(
63 &[
64 text("Busiest").font_size(12.0).fg_system(SystemColor::Gray),
65 gauge(reading(1), 0.0, 1.0, text("Memory"))
66 .gauge_style(views::GaugeStyle::AccessoryLinearCapacityGaugeStyle)
67 .tint_system(SystemColor::Orange),
68 ],
69 HorizontalAlignment::Leading,
70 ),
71 spacer(),
72 button("Refresh", refresh)
73 .button_style(views::ButtonStyle::BorderedProminent)
74 .keyboard_shortcut(KeyboardShortcut::DefaultAction),
75 ],
76 HorizontalAlignment::Leading,
77 Some(16.0),
78 )
79 .padding(20.0)
80 // Animate whatever changed, rather than snapping to the new reading.
81 .animation(LOAD[0].load(Ordering::Relaxed) as i64)
82}examples/hot_reload.rs (line 135)
117fn status(error: Option<&str>, generation: u64, micros: u64) -> View {
118 let slots = spec::slot_summary();
119 let slot_line = if slots.is_empty() {
120 String::new()
121 } else {
122 slots
123 .iter()
124 .map(|(k, v)| format!("{k}={v}"))
125 .collect::<Vec<_>>()
126 .join(" ")
127 };
128
129 // The error row is *always* present, empty when there is nothing wrong.
130 // An error that appears and disappears would otherwise reflow the screen
131 // above it on every save โ which looks exactly like the reloader having
132 // replaced the view, and makes "did the screen survive this?" impossible
133 // to answer by looking.
134 let rows = vec![
135 divider(),
136 hstack_spacing(
137 &[
138 circle_shape()
139 .fill_system(if error.is_some() {
140 SystemColor::Orange
141 } else {
142 SystemColor::Clear
143 })
144 .frame(8.0, 8.0),
145 text(error.unwrap_or(""))
146 .font_size(11.0)
147 .fg_system(SystemColor::Orange),
148 spacer(),
149 ],
150 6.0,
151 )
152 .padding(6.0)
153 .frame(348.0, 24.0),
154 hstack_spacing(
155 &[
156 circle_shape()
157 .fill_system(if error.is_some() {
158 SystemColor::Orange
159 } else {
160 SystemColor::Green
161 })
162 .frame(8.0, 8.0),
163 text(&format!("gen {generation}"))
164 .font_size(11.0)
165 .fg_system(SystemColor::Secondary),
166 // Microseconds below a millisecond: a reload of a short spec
167 // really is that fast, and "0.0 ms" reads as "not measured".
168 text(&if micros < 1000 {
169 format!("{micros} \u{b5}s")
170 } else {
171 format!("{:.1} ms", micros as f64 / 1000.0)
172 })
173 .font_size(11.0)
174 .fg_system(SystemColor::Secondary),
175 spacer(),
176 text(&slot_line).font_size(11.0).fg_system(SystemColor::Secondary),
177 ],
178 8.0,
179 )
180 .padding(6.0)
181 .frame(348.0, 24.0),
182 ];
183 vstack_spacing(&rows, 0.0)
184}examples/common/spec.rs (line 529)
487fn node_view(node: &Node) -> Result<View, String> {
488 let spacing = node.f64_attr("spacing").unwrap_or(8.0);
489 let base = match node.name.as_str() {
490 "vstack" => {
491 let kids = children(node)?;
492 match node.attr("align") {
493 Some(a) => vstack_aligned_spacing(&kids, h_align(a, node.line)?, Some(spacing)),
494 None => vstack_spacing(&kids, spacing),
495 }
496 }
497 "hstack" => {
498 let kids = children(node)?;
499 match node.attr("align") {
500 Some(a) => hstack_aligned_spacing(&kids, v_align(a, node.line)?, Some(spacing)),
501 None => hstack_spacing(&kids, spacing),
502 }
503 }
504 "zstack" => zstack_aligned(&children(node)?, align(node.attr("align").unwrap_or("center"), node.line)?),
505 "text" => {
506 let s = node.value.as_deref().unwrap_or("");
507 let mut v = text(s);
508 if let Some(style) = node.attr("style") {
509 v = v.text_style(text_style(style, node.line)?);
510 }
511 if let Some(size) = node.f64_attr("size") {
512 v = v.font_size(size);
513 }
514 if node.flag("bold") {
515 v = v.bold();
516 }
517 if node.flag("italic") {
518 v = v.italic();
519 }
520 if let Some(c) = node.attr("color") {
521 v = match tint(c, node.line)? {
522 Tint::System(sc) => v.fg_system(sc),
523 Tint::Literal(lc) => v.fg(lc),
524 };
525 }
526 v
527 }
528 "spacer" => spacer(),
529 "divider" => divider(),
530 // The action is a no-op on purpose: a spec read from a file has nowhere
531 // to put behaviour, and pretending otherwise would be worse than a
532 // button that visibly does nothing. `hot_reload.rs` is where a spec
533 // gains behaviour, by being reloaded.
534 "button" => button(node.value.as_deref().unwrap_or("Button"), || {}),
535 "color" => color(parse_color(node.value.as_deref().unwrap_or("#808080"), node.line)?),
536 "image" => system_image(node.value.as_deref().unwrap_or("star")).into_view(),
537 "rect" => match node.attr("color") {
538 Some(c) => match tint(c, node.line)? {
539 Tint::System(sc) => rectangle_shape().fill_system(sc),
540 Tint::Literal(lc) => rectangle_shape().fill(lc),
541 },
542 None => rectangle_shape().into_view(),
543 },
544 "circle" => match node.attr("color") {
545 Some(c) => match tint(c, node.line)? {
546 Tint::System(sc) => circle_shape().fill_system(sc),
547 Tint::Literal(lc) => circle_shape().fill(lc),
548 },
549 None => circle_shape().into_view(),
550 },
551 "capsule" => match node.attr("color") {
552 Some(c) => match tint(c, node.line)? {
553 Tint::System(sc) => capsule_shape().fill_system(sc),
554 Tint::Literal(lc) => capsule_shape().fill(lc),
555 },
556 None => capsule_shape().into_view(),
557 },
558 "progress" => progress(
559 node.value.as_deref().and_then(|v| v.parse().ok()).unwrap_or(0.5),
560 node.f64_attr("total").unwrap_or(1.0),
561 ),
562 "gauge" => gauge(
563 node.value.as_deref().and_then(|v| v.parse().ok()).unwrap_or(0.5),
564 node.f64_attr("min").unwrap_or(0.0),
565 node.f64_attr("max").unwrap_or(1.0),
566 text(node.attr("label").unwrap_or("")),
567 ),
568 "group_box" => group_box(stack_children(node)?),
569 "list" => list(stack_children(node)?),
570 "scroll" => stack_children(node)?.scroll(),
571 "form" => form(stack_children(node)?),
572 // Every bound control needs an explicit `@name`.
573 //
574 // Inventing one โ from the line, or from position in the tree โ looks
575 // friendlier and is a trap: adding a line above a `toggle` would
576 // change its derived name, and the control would silently reset on the
577 // next reload. A name the file states is a name that survives the file
578 // being edited, which is the entire point of the slots.
579 "toggle" => toggle_bound(
580 node.value.as_deref().unwrap_or("Toggle"),
581 &flag_slot(&require_slot(node)?, node.flag("on")),
582 ),
583 "slider" => {
584 let lo = node.f64_attr("min").unwrap_or(0.0);
585 let hi = node.f64_attr("max").unwrap_or(1.0);
586 slider_bound(
587 &number_slot(&require_slot(node)?, node.f64_attr("value").unwrap_or(lo)),
588 lo,
589 hi,
590 )
591 }
592 "stepper" => stepper_bound(
593 node.value.as_deref().unwrap_or("Stepper"),
594 &number_slot(&require_slot(node)?, node.f64_attr("value").unwrap_or(0.0)),
595 node.f64_attr("min").unwrap_or(0.0),
596 node.f64_attr("max").unwrap_or(100.0),
597 node.f64_attr("step").unwrap_or(1.0),
598 ),
599 "field" => textfield_bound(
600 node.value.as_deref().unwrap_or(""),
601 &string_slot(&require_slot(node)?, node.attr("text").unwrap_or("")),
602 ),
603 "label" => label(
604 node.value.as_deref().unwrap_or(""),
605 node.attr("icon").unwrap_or("star"),
606 ),
607 other => {
608 return Err(format!(
609 "line {}: no view called `{other}`; try one of {}",
610 node.line,
611 NAMES.join(", ")
612 ))
613 }
614 };
615 decorate(base, node)
616}