pub fn button(label: &str, action: impl Fn() + 'static) -> ViewExpand description
Create a Button.
Examples found in repository?
examples/inbox.rs (line 107)
105fn linked_row(i: usize) -> View {
106 navigation_link_labelled(row(i), detail(i))
107 .swipe_actions(HorizontalEdge::Trailing, true, button("Delete", || {}))
108}
109
110fn body() -> View {
111 let i = inbox();
112 let rows = matching();
113 navigation_stack(
114 list(for_each_items(&rows, |&i| linked_row(i)))
115 .list_style(views::ListStyle::InsetListStyle)
116 .searchable(&i.query, &i.searching, SearchFieldPlacement::Automatic, "Search"),
117 )
118 .navigation_title("Inbox")
119 .toolbar(button("Compose", || {}).keyboard_shortcut(KeyboardShortcut::DefaultAction))
120}More examples
examples/scenes.rs (line 58)
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/swift_org_tutorial.rs (line 151)
129fn body() -> View {
130 let colour = COLORS[COLOR.load(Ordering::Relaxed) % COLORS.len()];
131
132 vstack![
133 // Text("Why not try…").font(.largeTitle.bold())
134 text("Why not try…").text_style(TextStyle::LargeTitle).bold(),
135 vstack![
136 // Circle().fill(colors.randomElement() ?? .blue).padding().overlay(…)
137 circle_shape().fill_system(colour).padding(16.0).overlay(
138 // Image(systemName: "figure.\(selected.lowercased())")
139 // .font(.system(size: 144)).foregroundColor(.white)
140 image!("figure.{}", selected().to_lowercase())
141 .font_size(144.0)
142 .fg(Color::WHITE),
143 ),
144 // Text("\(selected)!").font(.title)
145 text!("{}!", selected()).text_style(TextStyle::Title),
146 ]
147 .transition(Transition::Slide)
148 .id(ID.load(Ordering::Relaxed)),
149 spacer(),
150 // Button("Try again") { … }.buttonStyle(.borderedProminent)
151 button("Try again", try_again).button_style(ButtonStyle::BorderedProminent),
152 ]
153}examples/dashboard.rs (line 72)
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/settings.rs (line 79)
43fn body() -> View {
44 let s = settings();
45
46 form![
47 section![header: text("Account");
48 hstack![
49 text("Name"),
50 spacer(),
51 textfield_bound("Your name", &s.name).text_field_style(
52 views::TextFieldStyle::RoundedBorderTextFieldStyle,
53 ),
54 ],
55 toggle_bound("Notifications", &s.notify),
56 ],
57 section![header: text("Playback");
58 vstack_aligned(&[
59 hstack![
60 text("Volume"),
61 spacer(),
62 text!("{:.0}%", s.volume.get() * 100.0).fg_system(SystemColor::Gray),
63 ],
64 slider_bound(&s.volume, 0.0, 1.0),
65 ], HorizontalAlignment::Leading),
66 stepper_bound("Refresh every", &s.refresh, 1.0, 60.0, 1.0),
67 ],
68 section![header: text("Appearance");
69 picker_bound!(
70 "Theme",
71 &s.theme,
72 text(THEMES[0]).tag_int(0),
73 text(THEMES[1]).tag_int(1),
74 text(THEMES[2]).tag_int(2),
75 )
76 .picker_style(PickerStyle::InlinePickerStyle),
77 ],
78 section![
79 button("About", show_about)
80 .a11y_hint("Opens a panel describing this app"),
81 ],
82 ]
83 .form_style(FormStyle::GroupedFormStyle)
84 .sheet_bound(&s.about, about_panel())
85}
86
87/// The sheet, which the user can dismiss — the flag is Rust's, so closing it
88/// writes `false` back rather than leaving the two sides disagreeing.
89fn about_panel() -> View {
90 let s = settings();
91 vstack_spacing(
92 &[
93 system_image("gearshape.fill")
94 .symbol_rendering_mode(Some(views::SymbolRenderingMode::Hierarchical))
95 .view()
96 .font_size(48.0)
97 .fg_system(SystemColor::Blue),
98 text("Settings").text_style(TextStyle::Title).bold(),
99 text!("Signed in as {}", s.name.get()).fg_system(SystemColor::Gray),
100 spacer(),
101 button("Done", hide_about).button_style(views::ButtonStyle::BorderedProminent),
102 ],
103 16.0,
104 )
105 .padding(24.0)
106 .presentation_detents(&[views::PresentationDetent::Medium])
107 .presentation_drag_indicator(Visibility::Visible)
108}examples/todo.rs (line 100)
83fn body() -> View {
84 let todos = items().lock().unwrap().clone();
85 let remaining = todos.iter().filter(|i| !i.done).count();
86
87 vstack_aligned_spacing(
88 &[
89 hstack![
90 text("Todo").text_style(TextStyle::LargeTitle).bold(),
91 spacer(),
92 text!("{remaining} left").fg_system(SystemColor::Gray),
93 ],
94 hstack_spacing(
95 &[
96 textfield_bound("What needs doing?", draft())
97 .text_field_style(views::TextFieldStyle::RoundedBorderTextFieldStyle)
98 .on_submit(SubmitTrigger::Text, add)
99 .submit_label(SubmitLabel::Done),
100 button("Add", add).button_style(views::ButtonStyle::BorderedProminent),
101 ],
102 8.0,
103 ),
104 // The empty state is a branch, so it arrives and leaves rather than
105 // appearing from nothing.
106 view_if!(
107 todos.is_empty(),
108 vstack_spacing(
109 &[
110 system_image("checkmark.seal").view().font_size(40.0)
111 .fg_system(SystemColor::Green),
112 text("Nothing left to do").fg_system(SystemColor::Gray),
113 ],
114 8.0,
115 )
116 .frame_optional(None, Some(160.0), Alignment::Center)
117 .transition(Transition::ScaleWithOpacity { scale: 0.8, anchor: (0.5, 0.5) }),
118 list(for_each_items(
119 &todos.iter().cloned().enumerate().collect::<Vec<_>>(),
120 |(i, it)| row(*i, it),
121 ))
122 .list_style(views::ListStyle::PlainListStyle)
123 ),
124 ],
125 HorizontalAlignment::Leading,
126 Some(14.0),
127 )
128 .padding(20.0)
129}Additional examples can be found in: