swift-cross-ui icon indicating copy to clipboard operation
swift-cross-ui copied to clipboard

Fix/progess spinner resizing

Open MiaKoring opened this issue 4 months ago • 6 comments

Fixes https://github.com/stackotter/swift-cross-ui/issues/237

Visually Breaking Change for some existing UIs using ProgressSpinner

This PR makes ProgressSpinners resizable on supported backends. Unsupported Backends change the container size without changing how the Widgets is actually being rendered to the user.

All changes are SCUI-Level, so even Backends I didn’t test (Gtk3) should benefit from it without breaking.

It may break some existing UIs though, as it now can be squished smaller than its natural size. To make it not entirely disappear (without setting a frame manually) I set its width/height to a minimum of 10 points, it can‘t shrink smaller.

To determine its rendered size it takes the smaller axis length and applies it to both width and height (making sure it always fits inside the applied constraints).

This can lead to it being rendered smaller than a set frame, if only one axis is set and its being squished from the opposite site. setting both axes in .frame eliminates this „problem“.

Gtk seems to have a min size for its Spinner. This can lead to clipping on too small externally applied constraints (at least on Ubuntu). I don’t really consider this to be an issue though as it can easily be avoided by the developer and other views can clip too.

MiaKoring avatar Oct 25 '25 18:10 MiaKoring

My concern with resizable spinners is that I'll often do something like

HStack {
  Spacer()

  ProgressView()

  Spacer()
}

to center a spinner within a larger view. Would this lead to a ridiculously large spinner on a large enough screen?

bbrk24 avatar Oct 28 '25 03:10 bbrk24

My concern with resizable spinners is that I'll often do something like

HStack {
  Spacer()

  ProgressView()

  Spacer()
}

to center a spinner within a larger view. Would this lead to a ridiculously large spinner on a large enough screen?

Perhaps this resizability can be gated behind a .resizable() modifier similar to Image resizability?

stackotter avatar Oct 28 '25 04:10 stackotter

My concern with resizable spinners is that I'll often do something like

HStack {
  Spacer()

  ProgressView()

  Spacer()
}

to center a spinner within a larger view. Would this lead to a ridiculously large spinner on a large enough screen?

Perhaps this resizability can be gated behind a .resizable() modifier similar to Image resizability?

Exactly what I did.

I’m going to replace the function with how its done on the Image in a moment. I like it better.

MiaKoring avatar Oct 28 '25 04:10 MiaKoring

It doesn't need to be done now (I could do this in a future PR), but maybe some code could be added to UIKitBackend to change the spinner between .medium and .large depending on its frame? I don't think there's an updateProgressSpinner backend method, so this would probably have to go on the widget's layoutSubviews method.

Whatever threshold we choose, we have to make sure it's large enough that the .large style is never chosen when it could be cut off on any platform (not just OS, but also based on OS version and maybe even accessibility settings. If you don't want to test it all I totally understand; I can get to it this weekend.)

bbrk24 avatar Oct 28 '25 12:10 bbrk24

maybe some code could be added to UIKitBackend to change the spinner between .medium and .large depending on its frame?

totally. I decided to not do it in this pr because the scope was scui isolated and I wanted to stay it that way. (Also I wasn't sure yet how I'd like the api to look for it. Single Backend support is way harder for api design imo. Especially when its kind of overlapping with another modifier but not really.)

Maybe .style() or whatever it would be called could apply .resizable() and .frame(constant size) depending on .medium or .large? so it wouldn't be UIKit Backend specific anymore (even though the other backends wouldn't need specific code)

MiaKoring avatar Oct 28 '25 17:10 MiaKoring

Here's what I've found from testing locally;

GtkBackend and Gtk3Backend

  • Work perfectly.

AppKitBackend

  • Spinner animates its size kinda weirdly when switching between enabled and disabled. I think it shouldn't animate at all (to make our job easier when we add an animation system).

  • The ControlsExample crashes when I use the spinner size slider. It appears to be due to a feedback loop in the window sizing code (that eventually leads to running out of stack space). The feedback loop is likely unrelated to your changes. If you can reproduce the crash locally then it'd be worth making a reproducer for it. Otherwise I can once I get the time.

  • I've confirmed that the crash doesn't occur when the ControlsExample is inside a ScrollView. I think it should be in a ScrollView anyway given that it's so tall now. So feel free to pop it in a ScrollView and I'll look into the crash later.

UIKitBackend

  • Works other than the behaviour you documented (that the spinner doesn't resize).

WinUIBackend

  • Works perfectly.

stackotter avatar Nov 02 '25 01:11 stackotter