pub struct Shape { /* private fields */ }Expand description
A shape, before it is given something to draw with.
Circle() is a Shape, not yet a View: SwiftUI gives it a default fill
when one is used as a view, and .fill() says what to use instead. Keeping
that distinction here is what lets circle().fill(.red) build the type the
same line of Swift builds, rather than a Circle with a foreground style
over it.
Implementations§
Source§impl Shape
impl Shape
Sourcepub fn fill(self, c: Color) -> View
pub fn fill(self, c: Color) -> View
.fill(_) — draw the shape in one colour.
Examples found in repository?
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}Sourcepub fn fill_system(self, c: SystemColor) -> View
pub fn fill_system(self, c: SystemColor) -> View
.fill(_) with one of SwiftUI’s own colours.
.blue is not a fixed triple: it is (0, 0.569, 1) on the Mac and
#007AFF on iOS, and it changes again in dark appearance. Naming the
colour asks SwiftUI for whichever one is right, where spelling out
components picks one platform’s answer and freezes it.
Examples found in repository?
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
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}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}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}Sourcepub fn stroke(self, c: Color, line_width: f64) -> View
pub fn stroke(self, c: Color, line_width: f64) -> View
.stroke(_:lineWidth:) — the outline instead of the fill.
Sourcepub fn stroke_system(self, c: SystemColor, line_width: f64) -> View
pub fn stroke_system(self, c: SystemColor, line_width: f64) -> View
.stroke(_:lineWidth:) with one of SwiftUI’s own colours.
Examples found in repository?
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}Sourcepub fn fill_then_stroke(
self,
fill: Color,
stroke: Color,
line_width: f64,
) -> View
pub fn fill_then_stroke( self, fill: Color, stroke: Color, line_width: f64, ) -> View
.fill(_).stroke(_, lineWidth:) — both at once, which a shape cannot
express by chaining because .fill already produces a view.
Works on transformed shapes too: .inset(2) gives a Circle._Inset,
which this now spells correctly. It used to assert, because
inset(by:) returns some InsettableShape and the binding kept the
shape it started as — so the result was named StrokeShapeView<Circle, …>, a type that exists, is the wrong one, and is laid out differently.
The field offsets are still computed by padding each to eight rather than read from the metadata, and still checked against the type’s own size before anything is written.
Sourcepub fn fill_then_stroke_border(
self,
fill: Color,
border: Color,
line_width: f64,
) -> View
pub fn fill_then_stroke_border( self, fill: Color, border: Color, line_width: f64, ) -> View
.fill(_).strokeBorder(_, lineWidth:) — filled, then bordered inside
its own edge.
The difference from fill_then_stroke is
where the line sits: a stroke straddles the edge and a border is drawn
within it, so a bordered shape never paints outside its bounds.
Sourcepub fn stroke_default(self, line_width: f64) -> View
pub fn stroke_default(self, line_width: f64) -> View
.stroke(lineWidth:) — outlined in the inherited foreground style,
which is what picks up an enclosing .foregroundStyle.
Sourcepub fn trim(self, from: f64, to: f64) -> Self
pub fn trim(self, from: f64, to: f64) -> Self
.trim(from:to:) — keep a fraction of the path, as unit fractions.
How a progress ring is drawn: a stroked circle trimmed to the fraction
complete, usually with .rotate(-90.0) after it so the arc starts at the
top rather than at three o’clock. Without it a “ring showing 70%” is a
full circle.
Examples found in repository?
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}Sourcepub fn inset(self, amount: f64) -> Self
pub fn inset(self, amount: f64) -> Self
.inset(by:) — still a shape, so it can be filled or stroked after.
Insetting before stroking is how you keep a lineWidth inside the
frame; stroking alone centres the line on the edge and spills half of it.