pub fn text(s: &str) -> ViewExpand description
Create a Text view.
Examples found in repository?
examples/inbox.rs (line 64)
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}
76
77fn row(i: usize) -> View {
78 let m = &MESSAGES[i];
79 hstack_spacing(
80 &[
81 // An unread dot, or the space where one would be.
82 view_if!(
83 m.unread,
84 circle_shape().fill_system(SystemColor::Blue).frame(8.0, 8.0),
85 empty().frame(8.0, 8.0)
86 ),
87 vstack_aligned(
88 &[
89 text(m.from).bold(),
90 text(m.subject).font_size(12.0).fg_system(SystemColor::Gray),
91 ],
92 HorizontalAlignment::Leading,
93 ),
94 spacer(),
95 ],
96 8.0,
97 )
98 .padding_edges(EdgeSet::Vertical, Some(4.0))
99 .a11y_label(&format!("{}, {}", m.from, m.subject))
100 .a11y_hint(if m.unread { "Unread" } else { "Read" })
101}More examples
examples/app.rs (line 12)
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/dynamic_ui.rs (line 70)
64pub fn body() -> View {
65 let text_src = current().lock().expect("spec").clone();
66 match spec::parse(&text_src).and_then(|nodes| spec::build(&nodes)) {
67 Ok(v) => v,
68 Err(e) => vstack_aligned_spacing(
69 &[
70 text("Could not build that screen")
71 .text_style(TextStyle::Headline)
72 .fg_system(SystemColor::Red),
73 text(&e).font_size(12.0).fg_system(SystemColor::Secondary),
74 ],
75 HorizontalAlignment::Leading,
76 Some(8.0),
77 )
78 .padding(20.0),
79 }
80}examples/hot_reload.rs (line 107)
96fn body() -> View {
97 let (src, error, generation, micros) = {
98 let g = state_cell().lock().expect("state");
99 (g.last_good.clone(), g.error.clone(), g.generation, g.micros)
100 };
101
102 // The last version that parsed. It cannot fail โ it parsed before โ but a
103 // spec that builds views from slots could in principle, so the fallback is
104 // still handled rather than unwrapped.
105 let screen = spec::parse(&src)
106 .and_then(|nodes| spec::build(&nodes))
107 .unwrap_or_else(|e| text(&e).fg_system(SystemColor::Red).padding(20.0));
108
109 vstack_spacing(&[screen, status(error.as_deref(), generation, micros)], 0.0)
110}
111
112/// A strip along the bottom: which generation is on screen, how long the last
113/// reload took, what the slots hold, and the error if there is one.
114///
115/// In the window rather than the terminal because that is where you are
116/// looking. Watching a reloader work from a log means watching two things.
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/scenes.rs (line 50)
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}
157
158/// What iOS has of the app above: one `WindowGroup`.
159///
160/// Kept beside the macOS body rather than left out, so the same example builds
161/// for both and it is visible *which* of the five features survive the crossing.
162#[cfg(not(target_os = "macos"))]
163fn body() -> SceneHandle {
164 scene::window_group(main_window)
165}
166
167// โโ Verification, without a screen โโ
168//
169// "It launched and did not crash" is weak evidence that four scenes and three
170// menus were built. This asks AppKit instead: a little after the run loop
171// starts, walk `NSApp.windows` and `NSApp.mainMenu` and print what SwiftUI
172// actually made of the Rust-built scenes, then quit. No display needed, which
173// is what makes it usable from a terminal or CI.
174//
175// The inspection has to run on the main thread โ AppKit is not thread-safe, and
176// a background thread reading a menu that is being built is a race โ so it goes
177// on the main queue through `dispatch_after_f` rather than on a `std::thread`.
178#[cfg(target_os = "macos")]
179mod probe {
180 use core::ffi::c_void;
181 use swiftui_native::objc;
182
183 unsafe extern "C" {
184 fn dispatch_time(base: u64, delta: i64) -> u64;
185 fn dispatch_after_f(
186 when: u64,
187 queue: *const c_void,
188 ctx: *mut c_void,
189 work: unsafe extern "C" fn(*mut c_void),
190 );
191 /// `dispatch_get_main_queue()` is this symbol's address.
192 static _dispatch_main_q: u8;
193 }
194
195 /// Queue the inspection for `seconds` from now.
196 pub fn schedule(seconds: f64) {
197 unsafe {
198 let when = dispatch_time(0, (seconds * 1e9) as i64);
199 dispatch_after_f(
200 when,
201 &_dispatch_main_q as *const u8 as *const c_void,
202 core::ptr::null_mut(),
203 inspect,
204 );
205 }
206 }
207
208 /// An `NSString`'s bytes, as Rust sees them.
209 unsafe fn text(ns: *mut c_void) -> String {
210 if ns.is_null() {
211 return String::new();
212 }
213 let p = unsafe { objc::send_ret_ptr(ns, objc::sel(c"UTF8String")) };
214 if p.is_null() {
215 return String::new();
216 }
217 unsafe { core::ffi::CStr::from_ptr(p as *const core::ffi::c_char) }
218 .to_string_lossy()
219 .into_owned()
220 }
221
222 /// `array[i]`, for an `NSArray`.
223 unsafe fn at(array: *mut c_void, i: u64) -> *mut c_void {
224 unsafe { objc::send_u64(array, objc::sel(c"objectAtIndex:"), i) }
225 }
226
227 unsafe fn count(array: *mut c_void) -> u64 {
228 if array.is_null() {
229 0
230 } else {
231 unsafe { objc::send_ret_u64(array, objc::sel(c"count")) }
232 }
233 }
234
235 unsafe extern "C" fn inspect(_: *mut c_void) {
236 unsafe {
237 let app = objc::shared_app();
238
239 let windows = objc::send(app, objc::sel(c"windows"));
240 let n = count(windows);
241 println!("windows: {n}");
242 for i in 0..n {
243 let w = at(windows, i);
244 let title = text(objc::send(w, objc::sel(c"title")));
245 let visible = objc::send_ret_bool(w, objc::sel(c"isVisible"));
246 let class = objc::send(w, objc::sel(c"class"));
247 let kind = text(objc::send(class, objc::sel(c"description")));
248 println!(" {kind:<24} visible={visible:<5} {title:?}");
249 }
250
251 let menu = objc::send(app, objc::sel(c"mainMenu"));
252 let items = objc::send(menu, objc::sel(c"itemArray"));
253 let n = count(items);
254 println!("menu bar: {n} menus");
255 for i in 0..n {
256 let item = at(items, i);
257 let title = text(objc::send(item, objc::sel(c"title")));
258 let sub = objc::send(item, objc::sel(c"submenu"));
259 let kids = objc::send(sub, objc::sel(c"itemArray"));
260 let entries: Vec<String> = (0..count(kids))
261 .map(|j| text(objc::send(at(kids, j), objc::sel(c"title"))))
262 .filter(|t| !t.is_empty())
263 .collect();
264 println!(" {title:?}: {}", entries.join(" ยท "));
265 }
266
267 // The menu-bar extra is an `NSStatusItem`, not a window or a menu,
268 // so it is counted separately or it would look like nothing was
269 // built for it.
270 let bar = objc::class(c"OBJC_CLASS_$_NSStatusBar");
271 let system = objc::send(bar, objc::sel(c"systemStatusBar"));
272 println!(
273 "status bar thickness: {}",
274 objc::send_ret_f64(system, objc::sel(c"thickness")),
275 );
276
277 std::process::exit(0);
278 }
279 }
280}
281
282fn main() {
283 // `--describe` asks the same question the event loop would and prints the
284 // answer: what Swift types came out of this. Useful because the types are
285 // the claim โ `_TupleScene<(WindowGroup<AnyView>, Settings<AnyView>, โฆ)>` is
286 // what Swift builds from four scenes in a row, and nothing about a window
287 // appearing would say whether that is what got built.
288 #[cfg(target_os = "macos")]
289 if std::env::args().any(|a| a == "--describe") {
290 let whole = body();
291 println!("App.Body\n {}", whole.type_name());
292 for (name, s) in [
293 ("window_group", scene::window_group(main_window)),
294 ("settings", scene::settings(settings_pane)),
295 ("window", scene::window("Utility", "utility", utility_window)),
296 ("menu_bar_extra", scene::menu_bar_extra("Count", "star.fill", menu_bar_item)),
297 ] {
298 println!("{name}\n {}", s.type_name());
299 }
300 // Built through the macro, so this also checks that what `scenes!`
301 // expands to is the same thing the free functions build.
302 let one = scenes! { window_group { text("x") } commands { menu("M") { text("x") } } };
303 println!("scenes! with one menu\n {}", one.type_name());
304 return;
305 }
306
307 // `--probe` launches for real and reports what AppKit built, then quits.
308 #[cfg(target_os = "macos")]
309 if std::env::args().any(|a| a == "--probe") {
310 probe::schedule(2.5);
311 }
312
313 app::run_scene(body)
314}examples/dashboard.rs (line 44)
28fn tile(i: usize) -> View {
29 let value = reading(i);
30 vstack_spacing(
31 &[
32 zstack![
33 circle_shape().stroke_system(SystemColor::Gray, 6.0).opacity(0.25),
34 // The filled part: the same circle trimmed to the fraction, and
35 // turned so the arc starts at the top rather than at three
36 // o'clock.
37 circle_shape()
38 .trim(0.0, value)
39 .stroke_system(TINTS[i], 6.0)
40 .rotate(-90.0),
41 text!("{:.0}", value * 100.0).text_style(TextStyle::Title2).bold(),
42 ]
43 .frame(72.0, 72.0),
44 text(LABELS[i]).font_size(12.0).fg_system(SystemColor::Gray),
45 ],
46 8.0,
47 )
48 .padding(12.0)
49 .a11y_element(AccessibilityChildBehavior::Combine)
50 .a11y_label(LABELS[i])
51 .a11y_value(&format!("{:.0} percent", value * 100.0))
52}
53
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}Additional examples can be found in: