Skip to content

Conversation

MiaKoring
Copy link

@MiaKoring MiaKoring commented Sep 7, 2025

This PR introduces a new .onHover modifier to Views.
Related Issue: #211
Added in this PR:

  • Extension View

    • onHover { Bool in }
  • protocol AppBackend

    • createHoverTarget
    • updateHoverTarget
    • Default implementation for Backends without implementation
  • Backend Support

    • AppKitBackend
    • UIKitBackend
    • GtkBackend
    • WinUIBackend
  • Other changes:

    • left & right NSClickGestureRecognizer now get set to nil in NSCustomTapGestureTarget when corresponding function gets set to nil
    • UISlider now Supports macCatalyst (added through compilerflag)
    • Added HoverExample
    • (format_and_lint.sh and generate.sh automatic changes)

HoverExample erfolgreich getestet auf:
macOS 26 (AppKitBackend, GtkBackend, UIKitBackend (macCatalyst))
iPadOS 26 (UIKitBackend)
Windows 11 (WinUI Backend)
Fedora 44 (GtkBackend)

Notes:
Gtk versions below 4.20.0 may encounter compiling problems. I don’t know why or what caused it, I only used the generate script, but on 4.18.6 on Fedora 42 I got this Error:

/home/mia/Documents/swift-cross-ui/Sources/Gtk/Generated/PadActionType.swift:28:6: error: cannot find 'GTK_PAD_ACTION_DIAL' in scope
26 | case GTK_PAD_ACTION_STRIP:
27 |     self = .strip
28 | case GTK_PAD_ACTION_DIAL:
   |      `- error: cannot find 'GTK_PAD_ACTION_DIAL' in scope
29 |     self = .dial
30 |             default:

I checked and it should’ve been available since 4.0 https://docs.gtk.org/gtk4/enum.PadActionType.html
Maybe someone knows how to fix it and maybe it was just a local issue.

I made the weird Array-in-foreach-decision, since closed ranges didn't seem to be supported yet.

test.sh output

   Test Suite 'All tests' started at 2025-09-08 00:03:46.577.
Test Suite 'swift-cross-uiPackageTests.xctest' started at 2025-09-08 00:03:46.584.
Test Suite 'SwiftCrossUITests' started at 2025-09-08 00:03:46.584.
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testBasicLayout]' started.
/Users/miakoring/Documents/Fork/swift-cross-ui/Tests/SwiftCrossUITests/SwiftCrossUITests.swift:203: error: -[SwiftCrossUITests.SwiftCrossUITests testBasicLayout] : XCTAssertEqual failed: ("ViewSize(size: SIMD2<Int>(102, 104), idealSize: SIMD2<Int>(102, 104), idealWidthForProposedHeight: 102, idealHeightForProposedWidth: 104, minimumWidth: 102, minimumHeight: 104, maximumWidth: 102.0, maximumHeight: 104.0, participateInStackLayoutsWhenEmpty: true)") is not equal to ("ViewSize(size: SIMD2<Int>(92, 96), idealSize: SIMD2<Int>(92, 96), idealWidthForProposedHeight: 92, idealHeightForProposedWidth: 96, minimumWidth: 92, minimumHeight: 96, maximumWidth: 92.0, maximumHeight: 96.0, participateInStackLayoutsWhenEmpty: true)") - View update result mismatch
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testBasicLayout]' failed (0.204 seconds).
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testCodableNavigationPath]' started.
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testCodableNavigationPath]' passed (0.001 seconds).
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testStateObservation]' started.
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testStateObservation]' passed (0.000 seconds).
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testThrottledStateObservation]' started.
Test Case '-[SwiftCrossUITests.SwiftCrossUITests testThrottledStateObservation]' passed (0.225 seconds).
Test Suite 'SwiftCrossUITests' failed at 2025-09-08 00:03:47.014.
	 Executed 4 tests, with 1 failure (0 unexpected) in 0.430 (0.431) seconds
Test Suite 'swift-cross-uiPackageTests.xctest' failed at 2025-09-08 00:03:47.014.
	 Executed 4 tests, with 1 failure (0 unexpected) in 0.430 (0.431) seconds
Test Suite 'All tests' failed at 2025-09-08 00:03:47.014.
	 Executed 4 tests, with 1 failure (0 unexpected) in 0.430 (0.437) seconds
􀟈  Test run started.
􀄵  Testing Library Version: 1084
􀄵  Target Platform: arm64e-apple-macos14.0
􁁛  Test run with 0 tests in 0 suites passed after 0.001 seconds.

Added:
- onHover(_ action: (Bool) -> Void) View Extension
- OnHoverModifier TypeSafeView
- createHoverTarget(wrapping: Widget) -> Widget to AppBackend Protocol
- updateHoverTarget(_: Widget, environment: EnvironmentValues, action: (Bool) -> Void) to AppBackend Protocol
- corresponding default implementations
- AppKitBackend hover implementation
   - createHoverTarget implementation
   - updateHoverTarget implementation
   - NSCustomHoverTarget (NSView notifying about hovers)
- HoverExample

Fixed:
- AppKitBackend
   - fixed reference removing for NSClickGestureRecognizer in NSCustomTapGestureTarget
…ming fixes in AppKitBackend

- tapGestureRecognizer and longPressGestureRecognizer now get removed if their corresponding handler is set to nil.
- Added Hoverable Widget
- Added hovertarget creation & update functions
- added macCalatalyst as compatible for UISlider since it supports it
…ming fixes in AppKitBackend

- tapGestureRecognizer and longPressGestureRecognizer now get removed if their corresponding handler is set to nil.
- Added Hoverable Widget
- Added hovertarget creation & update functions
- added macCalatalyst as compatible for UISlider since it supports it
Copy link
Owner

@stackotter stackotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! This will be quite useful for making more interactive apps. I've just requested a few changes around some implementation details but overall your approach to implementing this modifier was great.

Comment on lines 186 to 187
// should be impossible at the moment of implementation
// keeping it to be save in case of later changes
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safe to remove these comments, I don't think anyone will question that this is sensible (because the current guarantee that hoverChangesHandlers won't get set to nil is external to the HoverableWidget type)

Comment on lines 1412 to 1413
hoverTarget.width = hoverTarget.child!.width
hoverTarget.height = hoverTarget.child!.height
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realise that this was copied from the tap gesture modifier, but I've just noticed that it is probably redundant (in both cases) since both modifiers call backend.setSize just before calling the corresponding updateBlahTarget methods.

I'm happy to try that out on my end after merging though if a getting a Windows VM up is a bit annoying on your end.

}

override func mouseExited(with event: NSEvent) {
// Mouse exited the view's bounds
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this comment

Comment on lines 1777 to 1778
// should be impossible at the moment of implementation
// keeping it to be save in case of later changes
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to UIKitBackend, remove this comment, the code is sensible

options: options,
owner: self,
userInfo: nil)
self.addTrackingArea(trackingArea!)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid the force unwrap with an intermediate variable like you did in the didSet above.

@stackotter
Copy link
Owner

I checked and it should’ve been available since 4.0 https://docs.gtk.org/gtk4/enum.PadActionType.html
Maybe someone knows how to fix it and maybe it was just a local issue.

Gtk can be pretty weird about availability information. I think the best we can do is add a hardcoded check to filter out the enum case in GtkCodeGen.generateEnum. Make sure to leave a comment explaining that the check exists due to incorrect Gtk availability info for that particular enum case.

@stackotter
Copy link
Owner

That's interesting that testBasicLayout fails on your machine. It should be unrelated to your PR, so ignore that for now. I'll have to look into why it's happening at some point.

# Conflicts:
#	Sources/GtkCodeGen/GtkCodeGen.swift
@MiaKoring
Copy link
Author

MiaKoring commented Sep 15, 2025

I can’t test wether the linux compile bug is fixed because I don’t have a vm with older gtk atm. But I don’t see it in the generated code anymore. hopefully the build will now succeed

on WinUIBackend the explicit frame adjustments were indeed not necessary

@stackotter
Copy link
Owner

The PR froze safari for a few seconds when I tried to view the new changes because there are 266 changed files 😅 I think running ./format_and_lint.sh should fix that

@MiaKoring
Copy link
Author

didn’t help did it?

Copy link
Owner

@stackotter stackotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just two small changes to make, other than that everything looks ready to merge

//can cause problems with gtk versions older than 4.20.0
guard
member.cIdentifier != "GTK_PAD_ACTION_DIAL",
member.name != "GTK_PAD_ACTION_DIAL"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this check I meant that you should check that the enumeration name is "PadActionDial" when checking the member cIdentifier. I realise that the member names are prefixed so there won't be clashes, but it's probably nice for future refactorers to have the enumeration name check there so they know which enum the rule is meant to apply to.

guard enumeration.name != "PadActionDial" || member.cIdentifier != "GTK_PAD_ACTION_DIAL" else {
    return false
}

(the enum name may be GtkPadActionDial instead of PadActionDial, not sure)

@@ -224,6 +226,14 @@ struct GtkCodeGen {
return false
}

//can cause problems with gtk versions older than 4.20.0
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put a space after the // and capitalise Can at the start of the sentence (to match the style of other comments)

added both "GtkPadActionDial" and "PadActionDial" because I couldn't find a definitive answer what its called
added both "GtkPadActionDial" and "PadActionDial" because I couldn't find a definitive answer what its called

# Conflicts:
#	Sources/GtkCodeGen/GtkCodeGen.swift
@MiaKoring
Copy link
Author

(I somehow accidentally deleted the member.doc check, just readded it in the Amend)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants