Skip to main content

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}