Skip to content

Commit ef01fc8

Browse files
authored
Make Progress spinner optionally resizable (via modifier) (#238)
* Fixed ProgressViewResizing on AppKitBackend Gtk & WinUI testing pending * added min height&width to prevent pushing it too small * now the minWidth actually takes effect (max(min(x, y), 10) * Made ProgressView resizability optional through .resizable() Not resizable by default, can be changed at runtime. Does not affect ProgressBarView * changed .resizable() to modify a copy instead of Initializing with all values * formatting and naming * changing AppKits ProgressSpinner size through replacing it with a spinner sized pre view graph insertion Doesn't seem to be a performance issue, but its not a nice way. Sadly nothing else I tried works. * Added minHeight to ControlsExample to make it not spawn with 0 height on GtkBackend * added a guard to not recreate ProgressSpinners on AppKit when the size didn't change * Implemented requested changes - removed button in ControlsExample used for testing - renamed setProgressSpinnerSize to setSize(ofProgressSpinner:_, to:_) - updated ProgressView implementation to use new size update function name - replaced documentation comment with suggested one * Requested changes, should fix defaulting to naturalSize if only one dimension has a proposed size. * Updated dimension decision making for ProgressSpinner * updated slider in ControlsExample to use new API to avoid deprecation warning
1 parent b84b488 commit ef01fc8

File tree

18 files changed

+143
-33
lines changed

18 files changed

+143
-33
lines changed

Examples/Sources/AdvancedCustomizationExample/AdvancedCustomizationApp.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ struct CounterApp: App {
119119
#elseif canImport(UIKitBackend)
120120
textField.borderStyle = .bezel
121121
#elseif canImport(WinUIBackend)
122-
textField.selectionHighlightColor.color = .init(a: 255, r: 0, g: 255, b: 0)
122+
textField.selectionHighlightColor.color = .init(
123+
a: 255, r: 0, g: 255, b: 0)
123124
let brush = WinUI.SolidColorBrush()
124125
brush.color = .init(a: 255, r: 0, g: 0, b: 255)
125126
textField.background = brush

Examples/Sources/ControlsExample/ControlsApp.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ struct ControlsApp: App {
1717
@State var flavor: String? = nil
1818
@State var enabled = true
1919
@State var menuToggleState = false
20+
@State var progressViewSize: Int = 10
21+
@State var isProgressViewResizable = true
2022

2123
var body: some Scene {
2224
WindowGroup("ControlsApp") {
@@ -83,11 +85,21 @@ struct ControlsApp: App {
8385
Text("Value: \(text)")
8486
}
8587

88+
VStack {
89+
Toggle(
90+
"Enable ProgressView resizability", isOn: $isProgressViewResizable)
91+
Slider(value: $progressViewSize, in: 10...100)
92+
ProgressView()
93+
.resizable(isProgressViewResizable)
94+
.frame(width: progressViewSize, height: progressViewSize)
95+
}
96+
8697
VStack {
8798
Text("Drop down")
8899
HStack {
89100
Text("Flavor: ")
90-
Picker(of: ["Vanilla", "Chocolate", "Strawberry"], selection: $flavor)
101+
Picker(
102+
of: ["Vanilla", "Chocolate", "Strawberry"], selection: $flavor)
91103
}
92104
Text("You chose: \(flavor ?? "Nothing yet!")")
93105
}

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,15 @@ public final class AppKitBackend: AppBackend {
502502
}
503503

504504
public func naturalSize(of widget: Widget) -> SIMD2<Int> {
505+
if let spinner = widget.subviews.first as? NSProgressIndicator,
506+
spinner.style == .spinning
507+
{
508+
let size = spinner.intrinsicContentSize
509+
return SIMD2(
510+
Int(size.width),
511+
Int(size.height)
512+
)
513+
}
505514
let size = widget.intrinsicContentSize
506515
return SIMD2(
507516
Int(size.width),
@@ -1199,11 +1208,32 @@ public final class AppKitBackend: AppBackend {
11991208
}
12001209

12011210
public func createProgressSpinner() -> Widget {
1211+
let container = NSView()
1212+
let spinner = NSProgressIndicator()
1213+
spinner.translatesAutoresizingMaskIntoConstraints = false
1214+
spinner.isIndeterminate = true
1215+
spinner.style = .spinning
1216+
spinner.startAnimation(nil)
1217+
container.addSubview(spinner)
1218+
return container
1219+
}
1220+
1221+
public func setSize(
1222+
ofProgressSpinner widget: Widget,
1223+
to size: SIMD2<Int>
1224+
) {
1225+
guard Int(widget.frame.size.height) != size.y else { return }
1226+
setSize(of: widget, to: size)
12021227
let spinner = NSProgressIndicator()
1228+
spinner.translatesAutoresizingMaskIntoConstraints = false
12031229
spinner.isIndeterminate = true
12041230
spinner.style = .spinning
12051231
spinner.startAnimation(nil)
1206-
return spinner
1232+
spinner.widthAnchor.constraint(equalToConstant: CGFloat(size.x)).isActive = true
1233+
spinner.heightAnchor.constraint(equalToConstant: CGFloat(size.y)).isActive = true
1234+
1235+
widget.subviews = []
1236+
widget.addSubview(spinner)
12071237
}
12081238

12091239
public func createProgressBar() -> Widget {

Sources/AppKitBackend/InspectionModifiers.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ extension Image {
9191
_ inspectionPoints: InspectionPoints = .onCreate,
9292
_ action: @escaping @MainActor @Sendable (NSImageView) -> Void
9393
) -> some View {
94-
InspectView(child: self, inspectionPoints: inspectionPoints) { (_: NSView, children: ImageChildren) in
94+
InspectView(child: self, inspectionPoints: inspectionPoints) {
95+
(_: NSView, children: ImageChildren) in
9596
action(children.imageWidget.into())
9697
}
9798
}

Sources/AppKitBackend/NSViewRepresentable.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ extension NSViewRepresentable {
8787
let growsHorizontally = nsView.contentHuggingPriority(for: .horizontal) < .defaultHigh
8888
let growsVertically = nsView.contentHuggingPriority(for: .vertical) < .defaultHigh
8989

90-
let idealWidth = intrinsicSize.width == NSView.noIntrinsicMetric
90+
let idealWidth =
91+
intrinsicSize.width == NSView.noIntrinsicMetric
9192
? 10 : intrinsicSize.width
92-
let idealHeight = intrinsicSize.height == NSView.noIntrinsicMetric
93+
let idealHeight =
94+
intrinsicSize.height == NSView.noIntrinsicMetric
9395
? 10 : intrinsicSize.height
9496

9597
// When the view doesn't grow along a dimension, we use its fittingSize

Sources/Gtk3/Utility/GSimpleAction.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ public class GSimpleAction: GAction, GObjectRepresentable {
5151
private func connectActionSignal(
5252
_ value: some AnyObject,
5353
handler:
54-
@convention(c) (
55-
UnsafeMutableRawPointer,
56-
OpaquePointer,
57-
UnsafeMutableRawPointer
58-
) -> Void
54+
@convention(c) (
55+
UnsafeMutableRawPointer,
56+
OpaquePointer,
57+
UnsafeMutableRawPointer
58+
) -> Void
5959
) {
6060
g_signal_connect_data(
6161
UnsafeMutableRawPointer(actionPointer),

Sources/Gtk3Backend/InspectionModifiers.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ extension SwiftCrossUI.Image {
8080
_ inspectionPoints: InspectionPoints = .onCreate,
8181
_ action: @escaping @MainActor @Sendable (Gtk3.Image) -> Void
8282
) -> some View {
83-
InspectView(child: self, inspectionPoints: inspectionPoints) { (_: Gtk3.Widget, children: ImageChildren) in
83+
InspectView(child: self, inspectionPoints: inspectionPoints) {
84+
(_: Gtk3.Widget, children: ImageChildren) in
8485
action(children.imageWidget.into())
8586
}
8687
}

Sources/GtkBackend/InspectionModifiers.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ extension SwiftCrossUI.Image {
8989
_ inspectionPoints: InspectionPoints = .onCreate,
9090
_ action: @escaping @MainActor @Sendable (Gtk.Picture) -> Void
9191
) -> some View {
92-
InspectView(child: self, inspectionPoints: inspectionPoints) { (_: Gtk.Widget, children: ImageChildren) in
92+
InspectView(child: self, inspectionPoints: inspectionPoints) {
93+
(_: Gtk.Widget, children: ImageChildren) in
9394
action(children.imageWidget.into())
9495
}
9596
}

Sources/SwiftCrossUI/Backend/AnyWidget.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ public class AnyWidget {
2323
for backend: Backend.Type
2424
) -> Backend.Widget {
2525
guard let widget = widget as? Backend.Widget else {
26-
fatalError("AnyWidget used with incompatible backend \(backend); widget type is \(type(of: widget))")
26+
fatalError(
27+
"AnyWidget used with incompatible backend \(backend); widget type is \(type(of: widget))"
28+
)
2729
}
2830
return widget
2931
}
@@ -33,7 +35,9 @@ public class AnyWidget {
3335
/// more concise than using ``AnyWidget/concreteWidget(for:)``.
3436
public func into<T>() -> T {
3537
guard let widget = widget as? T else {
36-
fatalError("AnyWidget used with incompatible widget type \(T.self); actual widget type is \(type(of: widget))")
38+
fatalError(
39+
"AnyWidget used with incompatible widget type \(T.self); actual widget type is \(type(of: widget))"
40+
)
3741
}
3842
return widget
3943
}

Sources/SwiftCrossUI/Backend/AppBackend.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,16 @@ public protocol AppBackend: Sendable {
550550
/// Creates an indeterminate progress spinner.
551551
func createProgressSpinner() -> Widget
552552

553+
/// Sets the size of a progress spinner.
554+
///
555+
/// This method exists because AppKitBackend requires special handling to resize progress spinners.
556+
///
557+
/// The default implementation forwards to ``AppBackend/setSize(of:to:)``.
558+
func setSize(
559+
ofProgressSpinner widget: Widget,
560+
to size: SIMD2<Int>
561+
)
562+
553563
/// Creates a progress bar.
554564
func createProgressBar() -> Widget
555565
/// Updates a progress bar to reflect the given progress (between 0 and 1), and the
@@ -1123,6 +1133,13 @@ extension AppBackend {
11231133
todo()
11241134
}
11251135

1136+
public func setSize(
1137+
ofProgressSpinner widget: Widget,
1138+
to size: SIMD2<Int>
1139+
) {
1140+
setSize(of: widget, to: size)
1141+
}
1142+
11261143
public func createProgressBar() -> Widget {
11271144
todo()
11281145
}

0 commit comments

Comments
 (0)