Skip to content

Commit fd963de

Browse files
authored
Merge pull request #24 from maxxfrazer/staging
Interactions as components
2 parents 464cc8d + 53420cc commit fd963de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1551
-998
lines changed

.github/workflows/build-docc.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ jobs:
2323
environment:
2424
name: github-pages
2525
url: ${{ steps.deployment.outputs.page_url }}
26-
runs-on: macos-12
26+
runs-on: macos-13
2727
steps:
2828
- name: Checkout 🛎️
29-
uses: actions/checkout@v3
29+
uses: actions/checkout@v4
30+
- name: Switch Xcode 🔄
31+
run: sudo xcode-select --switch /Applications/Xcode_15.0.app
3032
- name: Build DocC
3133
run: |
3234
xcodebuild docbuild -scheme RealityUI -derivedDataPath /tmp/docbuild -destination 'generic/platform=iOS';
@@ -36,10 +38,10 @@ jobs:
3638
--output-path docs;
3739
echo "<script>window.location.href += \"/documentation/realityui\"</script>" > docs/index.html
3840
- name: Upload artifact
39-
uses: actions/upload-pages-artifact@v1
41+
uses: actions/upload-pages-artifact@v2
4042
with:
4143
# Upload docs directory
4244
path: 'docs'
4345
- name: Deploy to GitHub Pages
4446
id: deployment
45-
uses: actions/deploy-pages@v1
47+
uses: actions/deploy-pages@v2

.github/workflows/swift-build.yml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,21 @@ on:
1212

1313
jobs:
1414
build:
15-
runs-on: macOS-12
15+
runs-on: macOS-13
1616
steps:
17-
- uses: actions/checkout@v3
17+
- uses: actions/checkout@v4
18+
- name: Switch Xcode 🔄
19+
run: sudo xcode-select --switch /Applications/Xcode_15.0.app
1820
- name: Swift Lint
1921
run: swiftlint --strict
2022
- name: Test iOS
21-
run: |
22-
xcodebuild build -scheme RealityUI -destination "platform=iOS Simulator,name=iPhone 14" | xcpretty
23+
run: xcodebuild test -scheme RealityUI -destination "platform=iOS Simulator,name=iPhone 15" -enableCodeCoverage YES
24+
- name: Fetch Coverage
25+
uses: sersoft-gmbh/swift-coverage-action@v4
26+
id: coverage-files
27+
- name: Publish Coverage to Codecov
28+
uses: codecov/codecov-action@v3
29+
with:
30+
files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }}
2331
- name: Test macOS
24-
run: |
25-
xcodebuild build -scheme RealityUI -destination "platform=macOS" | xcpretty
26-
env:
27-
SCHEME: RealityUI
32+
run: xcodebuild test -scheme RealityUI -destination "platform=macOS" -enableCodeCoverage YES

.spi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
version: 1
22
builder:
33
configs:
4-
- platform: ios
5-
documentation_targets: [RealityUI]
4+
- documentation_targets: [RealityUI]
5+
custom_documentation_parameters: [--include-extended-types]

.swiftlint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
line_length:
22
ignores_comments: true
3+
identifier_name:
4+
excluded:
5+
- t
36
excluded:
47
- RealityUI+Example

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.6
1+
// swift-tools-version:5.8
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription

Package@swift-5.9.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// swift-tools-version:5.9
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "RealityUI",
8+
platforms: [.iOS(.v13), .macOS(.v10_15), .visionOS(.v1)],
9+
products: [.library( name: "RealityUI", targets: ["RealityUI"])],
10+
dependencies: [],
11+
targets: [
12+
.target(name: "RealityUI", dependencies: []),
13+
.testTarget(name: "RealityUITests", dependencies: ["RealityUI"])
14+
]
15+
)

README.md

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
# RealityUI
22

3-
RealityUI is a collection of User Interface classes for RealityKit.
4-
The classes included in RealityUI aim to offer familiar User Interface guidelines, but in a 3D setting for Augmented and Virtual Reality through RealityKit.
3+
RealityUI is a collection of utilities and UI objects for RealityKit.
4+
The UI objects included in RealityUI aim to offer familiar User Interface standards, but in a 3D setting for Augmented and Virtual Reality through RealityKit.
55

6-
The User Interface controls in this repository so far are made to be familiar to what people are used to with 2D interfaces, however the plan is to expand the tools on offer to new and unique controls, which are more appropriate for an Augmented Reality and Virtual Reality context.
6+
RealityUI also has a collection of components for interfacing with any Entity through touch or drag interactions.
77

88
<p align="center">
99
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI">
1010
<img src="https://img.shields.io/github/v/release/maxxfrazer/RealityUI?color=F05138&label=Package%20Version&logo=Swift"/>
1111
</a>
12+
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI/main/documentation/realityui">
13+
<img src="https://img.shields.io/badge/Swift-Doc-DE5C43.svg?style=flat"></a>
14+
<a href="https://codecov.io/github/maxxfrazer/RealityUI" >
15+
<img src="https://codecov.io/github/maxxfrazer/RealityUI/graph/badge.svg?token=3PCDBMSCLL"/>
16+
</a>
17+
<br/>
18+
<img src="https://github.com/maxxfrazer/RealityUI/workflows/build/badge.svg?branch=main"/>
19+
<img src="https://github.com/maxxfrazer/RealityUI/workflows/Deploy%20DocC/badge.svg?branch=main"/>
20+
<a href="./LICENSE.md">
21+
<img src="https://img.shields.io/github/license/maxxfrazer/RealityUI"/>
22+
</a>
23+
<br/>
1224
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI">
1325
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmaxxfrazer%2FRealityUI%2Fbadge%3Ftype%3Dplatforms"/>
1426
</a>
1527
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI">
1628
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmaxxfrazer%2FRealityUI%2Fbadge%3Ftype%3Dswift-versions"/>
1729
</a>
18-
<br/>
19-
<img src="https://img.shields.io/github/license/maxxfrazer/RealityUI"/>
20-
<img src="https://github.com/maxxfrazer/RealityUI/workflows/build/badge.svg?branch=main"/>
21-
<img src="https://github.com/maxxfrazer/RealityUI/workflows/Deploy%20DocC/badge.svg?branch=main"/>
2230
</p>
2331

2432
![RealityUI Elements in a RealityKit VR space](https://github.com/maxxfrazer/RealityUI/blob/main/media/realityui_banner.gif?raw=true)
@@ -62,7 +70,7 @@ All components used in RealityUI must be registered before they are used, simply
6270

6371
Enabling RealityUI gestures can be doen by calling `RealityUI.enableGestures(.all, on: ARView)`, with `ARView` being your instance of an [ARView](https://developer.apple.com/documentation/realitykit/arview) object.
6472

65-
RUISlider, RUISwitch, RUIStepper and RUIButton all use `.longTouch`, and if you are adding elements that use the protocol `HasClick` you can use the gesture `.tap`.
73+
RUISlider, RUISwitch, RUIStepper and RUIButton all use ``RUIDragComponent``, which requires `.ruiDrag`. If you are adding elements that use the component `RUITapComponent` you can use the gesture `.tap`.
6674
I would just recommend using `.all` when enabling gestures, as these will inevitably move around as RealityUI develops.
6775

6876
`RealityUI.enableGestures(.all, on: arView)`
@@ -114,19 +122,37 @@ Default button bounding box before depressing the button into the base is `[1, 1
114122

115123
All of the RealityUI Control Entities use custom gestures that aren't standard in RealityKit, but some of them have been isolated so anyone can use them to manipulate their own RealityKit scene.
116124

125+
### Drag
126+
127+
Drag objects anywhere in space with 3 degrees of freedom with [RUIDragComponent](https://maxxfrazer.github.io/RealityUI/documentation/realityui/RUIDragComponent), using the [.move](https://maxxfrazer.github.io/RealityUI/documentation/realityui/RUIDragComponent/move(_:)) type.
128+
129+
![Dragging Cubes](media/RUIDrag_cubes.gif)
130+
131+
This type has an optional constraint, to fix the movement within certain criteria:
132+
133+
1. **Box Constraint**: Restricts movement within a specified `BoundingBox`, providing a defined area where the entity can move.
134+
135+
2. **Points Constraint**: Limits movement to a set of predefined points, represented as an array of `SIMD3<Float>`.
136+
137+
3. **Clamp Constraint**: Uses a custom clamping function to control the movement. This function takes a `SIMD3<Float>` as input and returns a modified `SIMD3<Float>` to determine the new position.
138+
117139
### Turn
118140

119141
Unlock the ability to rotate a RealityKit entity with just one finger.
120142

121143
![Turning key](https://github.com/maxxfrazer/RealityUI/raw/e3cb908fa9051512671e01dd3fe01f59c45f0936/media/RealityUI_pivot_key.gif?raw=true)
122144

123-
[More details](https://github.com/maxxfrazer/RealityUI/wiki/Gestures#turn)
145+
[More details](https://maxxfrazer.github.io/RealityUI/documentation/realityui/RUIDragComponent/DragComponentType/turn(axis:))
124146

125147
### Tap
126148

127149
Create an object in your RealityKit scene with an action, and it will automatically be picked up whenever the user taps on it!
128150

129-
No Gif for this one, but check out [RealityUI Gestures wiki](https://github.com/maxxfrazer/RealityUI/wiki/Gestures#tap) to see how to add [HasClick](https://maxxfrazer.github.io/RealityUI/documentation/realityui/HasClick.html) to an entity in your application.
151+
No Gif for this one, but check out [RUITapComponent](https://maxxfrazer.github.io/RealityUI/documentation/realityui/RUITapComponent) to see how to add this to an entity in your application.
152+
153+
If you instead wanted to use something similar to a "touch up inside" tap, you can use [RUIDragComponentType/click](https://maxxfrazer.github.io/RealityUI/documentation/realityui/RUIDragComponent/DragComponentType/click).
154+
155+
![touch-up-inside example with a button](media/button_light.gif)
130156

131157
---
132158
## Animations
@@ -161,6 +187,6 @@ With RUIText you can easily create an Entity with the specified text placed with
161187
---
162188
## More
163189

164-
More information on everything provided in this Swift Package in the [GitHub Wiki](https://github.com/maxxfrazer/RealityUI/wiki), and also the [documentation](https://maxxfrazer.github.io/RealityUI/documentation/realityui/).
190+
More information on everything provided in this Swift Package in the [documentation](https://maxxfrazer.github.io/RealityUI/documentation/realityui/).
165191

166192
Also see the [Example Project](https://github.com/maxxfrazer/RealityUI/tree/main/RealityUI%2BExamples) for iOS in this repository.

RealityUI+Example/RealityUI+Example.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@
141141
Base,
142142
);
143143
mainGroup = F31E9ACF29E47B060084306F;
144+
packageReferences = (
145+
);
144146
productRefGroup = F31E9AD929E47B060084306F /* Products */;
145147
projectDirPath = "";
146148
projectRoot = "";
@@ -299,6 +301,7 @@
299301
ENABLE_PREVIEWS = YES;
300302
GENERATE_INFOPLIST_FILE = YES;
301303
INFOPLIST_FILE = "RealityUI-Example-Info.plist";
304+
INFOPLIST_KEY_NSCameraUsageDescription = ar;
302305
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
303306
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
304307
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -338,6 +341,7 @@
338341
ENABLE_PREVIEWS = YES;
339342
GENERATE_INFOPLIST_FILE = YES;
340343
INFOPLIST_FILE = "RealityUI-Example-Info.plist";
344+
INFOPLIST_KEY_NSCameraUsageDescription = ar;
341345
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
342346
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
343347
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;

RealityUI+Example/RealityUI+Example/ARViewContainer.swift

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,25 @@ struct ARViewContainer: UIViewRepresentable {
3333
// Create an ARView
3434
let arView = ARView(frame: .zero)
3535
#if os(iOS)
36-
arView.cameraMode = .nonAR
36+
arView.cameraMode = .ar
3737
#endif
3838

3939
// Add the anchor to the scene
40+
#if os(iOS)
41+
let anchor = AnchorEntity(world: [0, 0, -1])
42+
anchor.scale *= 0.15
43+
#else
4044
let anchor = AnchorEntity(world: .zero)
45+
#endif
4146
arView.scene.addAnchor(anchor)
4247

48+
arView.debugOptions.insert(.showPhysics)
4349
// Setup RealityKit camera
50+
#if os(macOS)
4451
let cam = PerspectiveCamera()
45-
cam.look(at: .zero, from: [0, 0, -2.5], relativeTo: nil)
52+
cam.look(at: .zero, from: [0, 0, -3], relativeTo: nil)
4653
anchor.addChild(cam)
54+
#endif
4755

4856
self.setModel(view: arView)
4957
RealityUI.enableGestures(.all, on: arView)
@@ -55,20 +63,69 @@ struct ARViewContainer: UIViewRepresentable {
5563
func setModel(view: ARView) {
5664
guard let worldAnchor = view.scene.anchors.first,
5765
prevObjectType != objectType else { return }
66+
worldAnchor.orientation = simd_quatf.init(angle: .pi, axis: [0, 1, 0])
5867
if let oldRui = view.scene.findEntity(named: "ruiReplace") {
5968
worldAnchor.removeChild(oldRui)
6069
}
6170
view.environment.background = .color(.gray)
6271
let ruiModel: Entity
72+
let smallMove: Float = .random(in: 0.1...0.4)
6373
switch objectType {
74+
case .mover:
75+
ruiModel = Entity()
76+
for idx in 0...5 {
77+
let modEnt = ModelEntity(
78+
mesh: .generateBox(width: 0.5, height: 0.5, depth: 0.5, splitFaces: true),
79+
materials: [
80+
SimpleMaterial(color: .blue, isMetallic: false),
81+
SimpleMaterial(color: .yellow, isMetallic: false),
82+
SimpleMaterial(color: .orange, isMetallic: false),
83+
SimpleMaterial(color: .purple, isMetallic: false),
84+
SimpleMaterial(color: .green, isMetallic: false),
85+
SimpleMaterial(color: .red, isMetallic: false)
86+
].shuffled()
87+
)
88+
modEnt.generateCollisionShapes(recursive: false)
89+
modEnt.components.set(RUIDragComponent(
90+
type: .move(.box(
91+
BoundingBox(min: [-2, -1, -1], max: [2, 1, 1])
92+
))
93+
))
94+
modEnt.position.x = 2 * sin(
95+
Float(idx) / 3 * .pi + smallMove
96+
)
97+
modEnt.position.y = cos(
98+
Float(idx) / 3 * .pi + smallMove
99+
)
100+
ruiModel.addChild(modEnt)
101+
}
102+
let container = ModelEntity(
103+
mesh: .generateBox(width: 4.5, height: 2.5, depth: 2.5),
104+
materials: [SimpleMaterial(color: .white.withAlphaComponent(0.2), isMetallic: false)]
105+
)
106+
container.scale *= -1
107+
ruiModel.addChild(container)
64108
case .toggle:
65109
ruiModel = RUISwitch(switchCallback: { hasSwitch in
66110
view.environment.background = .color(hasSwitch.isOn ? .green : .gray)
67111
})
112+
case .text:
113+
let textObj = RUIText(textComponent: TextComponent(
114+
text: "hello", font: .systemFont(ofSize: 1),
115+
alignment: .center, extrusion: 0.1
116+
))
117+
textObj.components.set(RUITapComponent { ent, _ in
118+
ent.ruiSpin(
119+
by: [[1, 0, 0], [0, 1, 0], [0, 0, 1]].randomElement()!,
120+
period: 0.3, times: 1
121+
)
122+
})
123+
textObj.addCollision()
124+
ruiModel = textObj
68125
case .slider:
69126
let scalingCube = ModelEntity(mesh: .generateBox(size: 3))
70127
scalingCube.position.z = 3
71-
ruiModel = RUISlider(start: 0.5) { slider, state in
128+
ruiModel = RUISlider(length: 7, start: 0.5, steps: Bool.random() ? 4 : 0) { slider, state in
72129
scalingCube.scale = .one * (slider.value + 0.2) / 1.2
73130
}
74131
ruiModel.addChild(scalingCube)
@@ -88,8 +145,12 @@ struct ARViewContainer: UIViewRepresentable {
88145
}
89146
ruiModel.look(at: [0, 1, -1], from: .zero, relativeTo: nil)
90147
case .rotation:
91-
ruiModel = RotationPlane(turnAxis: [0, 0, 1])
92-
ruiModel.scale = .one * 2
148+
ruiModel = RotationPlane()
149+
// stand up the model, so it's facing the camera
150+
ruiModel.orientation = simd_quatf(angle: .pi, axis: [0, 1, 0])
151+
152+
// set the turn/rotation component
153+
ruiModel.components.set(RUIDragComponent(type: .turn(axis: [0, 0, 1])))
93154
}
94155
ruiModel.name = "ruiReplace"
95156
worldAnchor.addChild(ruiModel)
@@ -104,27 +165,23 @@ struct ARViewContainer: UIViewRepresentable {
104165
}
105166

106167
/// Class for demonstrating the HasTurnTouch protocol.
107-
class RotationPlane: Entity, HasModel, HasCollision, HasTurnTouch {
168+
class RotationPlane: Entity, HasModel, HasCollision {
108169

109170
/// Create a new ``RotationPlane``, which conforms to HasTurnTouch
110171
/// - Parameter turnAxis: Axis that the object will be rotated around.
111-
required init(turnAxis: SIMD3<Float>) {
172+
required init() {
112173
super.init()
113-
self.turnAxis = turnAxis
114174
var rotateMat = SimpleMaterial()
115175
rotateMat.color = SimpleMaterial.BaseColor(
116176
tint: .white.withAlphaComponent(0.99), texture: MaterialParameters.Texture(
117177
try! TextureResource.load(named: "rotato")
118178
)
119179
)
180+
self.scale = .one * 2
181+
120182
self.model = ModelComponent(mesh: .generatePlane(width: 1, height: 1), materials: [rotateMat])
121-
self.orientation = .init(angle: .pi, axis: [0, 1, 0])
122183
self.collision = CollisionComponent(shapes: [.generateBox(width: 1, height: 1, depth: 0.1)])
123184
}
124-
125-
@MainActor required init() {
126-
fatalError("init() has not been implemented")
127-
}
128185
}
129186

130187
extension ARViewContainer {

RealityUI+Example/RealityUI+Example/ContentView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public enum RealityObject: String, CaseIterable {
1414
case stepper
1515
case button
1616
case rotation
17+
case text
18+
case mover
1719
}
1820

1921
struct ContentView: View {

0 commit comments

Comments
 (0)