swiftui_native/view/text.rs
1//! Everything that changes how text reads: font, decoration, and input behaviour.
2//!
3//! Part of [`crate::view::View`]'s chainable API; Rust allows a type's
4//! inherent methods to be split across modules, and 400 of them in one file
5//! was a file nobody could find anything in.
6
7use super::{Color, View};
8use crate::views;
9
10impl View {
11 // ── Font ──
12
13 /// `.font(.system(size:))`
14 /// On a `Text` this is `Text.font(_:)`, which returns `Text`, so a chain of
15 /// text modifiers stays one type the way Swift's does.
16 pub fn font_size(self, size: f64) -> Self {
17 Self::new(views::text_font(&self.handle, Some(&views::Font::system(size, None, None))))
18 }
19
20 /// `.font(.system(size:weight:design:))` — `Text.font(_:)` on a `Text`.
21 pub fn font(self, size: f64, weight: Option<views::Weight>, design: Option<views::Design>) -> Self {
22 Self::new(views::text_font(&self.handle, Some(&views::Font::system(size, weight, design))))
23 }
24
25 /// `.font(_:)` with a built-in text style.
26 /// On a `Text` this is `Text.font(_:)`, which returns `Text` — so a chain
27 /// like `.text_style(.body).bold()` stays a `Text` the way Swift's does,
28 /// instead of each step wrapping the last in an environment modifier.
29 pub fn text_style(self, style: views::TextStyle) -> Self {
30 Self::new(views::text_font(&self.handle, Some(&views::Font::text_style(style))))
31 }
32
33 /// `.font(.custom(_:size:))`
34 pub fn font_named(self, name: &str, size: f64) -> Self {
35 Self::new(views::text_font(&self.handle, Some(&views::Font::custom(name, size))))
36 }
37
38 /// `.font(_:)` with a prebuilt [`views::Font`], which is far cheaper than
39 /// rebuilding the font for every view.
40 pub fn font_ref(self, font: &views::Font) -> Self {
41 Self::new(views::text_font(&self.handle, Some(font)))
42 }
43
44 /// `.bold()`
45 ///
46 /// On a `Text` this is `Text.bold()`, which returns `Text` — the same type
47 /// Swift produces, and the reason the documentation corpus compares equal.
48 /// On anything else it is the `View` extension, which writes the environment
49 /// so the text below inherits it.
50 pub fn bold(self) -> Self {
51 Self::new(views::text_bold(&self.handle))
52 }
53
54 /// `.italic()` — `Text.italic()` on a `Text`, the `View` extension otherwise.
55 pub fn italic(self) -> Self {
56 Self::new(views::text_italic(&self.handle))
57 }
58
59 /// `.monospaced()` — `Text.monospaced()` on a `Text`.
60 pub fn monospaced(self) -> Self {
61 Self::new(views::text_monospaced(&self.handle))
62 }
63
64 /// `.fontWeight(_:)` — `Text.fontWeight(_:)` on a `Text`, which returns
65 /// `Text`, and the `View` extension otherwise.
66 pub fn weight(self, weight: views::Weight) -> Self {
67 Self::new(views::text_font_weight(&self.handle, Some(weight)))
68 }
69
70 /// `.foregroundStyle(_:)`
71 /// On a `Text` this is `Text.foregroundColor(_:)`, which returns `Text`.
72 pub fn fg(self, c: Color) -> Self {
73 Self::new(views::text_foreground_color(&self.handle, c))
74 }
75
76 /// `Text.baselineOffset(_:)`
77 pub fn baseline_offset(self, amount: f64) -> Self {
78 Self::new(views::text_baseline_offset(&self.handle, amount))
79 }
80
81 /// `.foregroundColor(_:)` with one of SwiftUI's named colours.
82 ///
83 /// Not the same modifier as [`View::fg`], and not a colour that components
84 /// can spell: see [`views::system_color`].
85 pub fn fg_system(self, c: views::SystemColor) -> Self {
86 Self::new(views::foreground_color_value(&self.handle, views::system_color_value(c)))
87 }
88
89 /// `.foregroundStyle(LinearGradient(colors:startPoint:endPoint:))` — text
90 /// and symbols painted with a gradient instead of a flat colour.
91 ///
92 /// Unit points, as in [`View::bg_linear_gradient`].
93 /// On a `Text` this is `Text.foregroundStyle(_:)`, which returns `Text`.
94 pub fn fg_linear_gradient(self, colors: &[Color], start: (f64, f64), end: (f64, f64)) -> Self {
95 let raw: Vec<u64> = colors.iter().map(|&c| views::color_value_of(c)).collect();
96 Self::new(views::text_foreground_linear_gradient(&self.handle, &raw, start, end))
97 }
98
99 /// `.kerning(_:)` — `Text.kerning(_:)` on a `Text`, which returns `Text`.
100 pub fn kerning(self, amount: f64) -> Self {
101 Self::new(views::text_kerning(&self.handle, amount))
102 }
103
104 /// `.tracking(_:)` — like kerning, but not suppressed between the pairs the
105 /// font has a ligature for.
106 pub fn tracking(self, amount: f64) -> Self {
107 Self::new(views::text_tracking(&self.handle, amount))
108 }
109
110 /// `.lineSpacing(_:)`
111 pub fn line_spacing(self, amount: f64) -> Self {
112 Self::new(views::line_spacing(&self.handle, amount))
113 }
114
115 /// `.lineLimit(_:)`
116 pub fn line_limit(self, limit: i64) -> Self {
117 Self::new(views::line_limit(&self.handle, Some(limit)))
118 }
119
120 /// `.multilineTextAlignment(_:)`
121 pub fn text_align(self, align: views::TextAlign) -> Self {
122 Self::new(views::multiline_alignment(&self.handle, align))
123 }
124
125 // ── Sizing and input behaviour ──
126
127 /// `.disableAutocorrection(_:)` — `None` restores the system default.
128 ///
129 /// Named for the modern spelling because that is what a caller looks for,
130 /// but `autocorrectionDisabled(_:)` is inlined and exports no symbol — this
131 /// reaches `disableAutocorrection(_:)`, which is the same modifier and the
132 /// one SwiftUI still exports.
133 pub fn autocorrection_disabled(self, disable: Option<bool>) -> Self {
134 Self::new(views::disable_autocorrection(&self.handle, disable))
135 }
136
137 /// `.textScale(_:isEnabled:)`
138 pub fn text_scale(self, scale: views::TextScale, enabled: bool) -> Self {
139 Self::new(views::text_scale(&self.handle, scale, enabled))
140 }
141
142 /// `.dynamicTypeSize(_:)` — pin the size class rather than following the
143 /// system setting.
144 pub fn dynamic_type_size(self, size: views::DynamicTypeSize) -> Self {
145 Self::new(views::dynamic_type_size(&self.handle, size))
146 }
147
148 /// `.accentColor(_:)` — `None` restores the inherited accent.
149 ///
150 /// Deprecated in SwiftUI in favour of [`View::tint`], and kept because the
151 /// documentation still shows it.
152 pub fn accent_color(self, color: Option<Color>) -> Self {
153 Self::new(views::accent_color(&self.handle, color))
154 }
155
156 // ── Decoration ──
157
158 /// `.underline()`
159 pub fn underline(self) -> Self {
160 self.underline_styled(true, views::LinePattern::Solid, None)
161 }
162
163 /// `.underline(_:pattern:color:)` — `None` keeps the text's own colour.
164 ///
165 /// `active` is not decoration: this writes the environment, so passing
166 /// `false` is how a subtree turns off an underline it would otherwise
167 /// inherit. There is no other way to say that.
168 pub fn underline_styled(
169 self,
170 active: bool,
171 pattern: views::LinePattern,
172 color: Option<Color>,
173 ) -> Self {
174 Self::new(views::underline(&self.handle, active, pattern, color))
175 }
176
177 /// `.strikethrough()`
178 pub fn strikethrough(self) -> Self {
179 self.strikethrough_styled(true, views::LinePattern::Solid, None)
180 }
181
182 /// `.strikethrough(_:pattern:color:)` — `active: false` turns off an
183 /// inherited strikethrough, as with [`View::underline_styled`].
184 pub fn strikethrough_styled(
185 self,
186 active: bool,
187 pattern: views::LinePattern,
188 color: Option<Color>,
189 ) -> Self {
190 Self::new(views::strikethrough(&self.handle, active, pattern, color))
191 }
192
193 /// `.truncationMode(_:)` — which end of over-long text gets the ellipsis.
194 pub fn truncation_mode(self, mode: views::TruncationMode) -> Self {
195 Self::new(views::truncation_mode(&self.handle, mode))
196 }
197
198 /// `.lineHeight(_:)` — `None` restores the font's own leading.
199 pub fn line_height(self, height: Option<views::LineHeightKind>) -> Self {
200 Self::new(views::line_height(&self.handle, height))
201 }
202
203 /// `.fontDesign(_:)` — `.monospaced` and friends, without restating the size.
204 pub fn font_design(self, design: views::Design) -> Self {
205 Self::new(views::font_design(&self.handle, design))
206 }
207
208 /// `.fontWidth(_:)` — `None` restores the default width.
209 pub fn font_width(self, width: Option<views::FontWidth>) -> Self {
210 Self::new(views::font_width(&self.handle, width))
211 }
212
213 /// `.redacted(reason:)` — the placeholder treatment, for loading states.
214 pub fn redacted(self, reasons: views::RedactionReasons) -> Self {
215 Self::new(views::redacted(&self.handle, reasons))
216 }
217
218 /// `.textFieldStyle(_:)` — `.roundedBorder` is the one that looks like a
219 /// field rather than a line of text.
220 pub fn text_field_style(self, style: views::TextFieldStyle) -> Self {
221 Self::new(views::text_field_style(&self.handle, style))
222 }
223
224 /// `.submitLabel(_:)` — what the return key says.
225 pub fn submit_label(self, label: views::SubmitLabel) -> Self {
226 Self::new(views::submit_label(&self.handle, label))
227 }
228
229 /// `.onSubmit(of:_:)` — the return key, or a search field's, or a
230 /// collection's, depending on the trigger.
231 pub fn on_submit(self, trigger: views::SubmitTrigger, action: impl Fn() + 'static) -> Self {
232 Self::new(views::on_submit(&self.handle, trigger, action))
233 }
234
235 /// `.textContentType(_:)` — what the field holds, which is what lets the
236 /// system offer a saved password or a one-time code. `None` clears it.
237 pub fn text_content_type(self, kind: Option<views::TextContentKind>) -> Self {
238 Self::new(views::text_content_type(&self.handle, kind))
239 }
240
241 /// `.keyboardShortcut(_:)` — `.defaultAction` is return, `.cancelAction` is
242 /// escape, which is what makes a button the one a sheet's return key hits.
243 pub fn keyboard_shortcut(self, shortcut: views::KeyboardShortcut) -> Self {
244 Self::new(views::keyboard_shortcut(&self.handle, shortcut))
245 }
246
247 /// `.listRowBackground(_:)` — `None` restores the default.
248 pub fn list_row_background(self, background: Option<View>) -> Self {
249 let bg = background.map(View::into_handle);
250 Self::new(views::list_row_background(&self.handle, bg.as_ref()))
251 }
252
253}