Skip to content

Commit

Permalink
Merge pull request #24 from maxxfrazer/staging
Browse files Browse the repository at this point in the history
Interactions as components
  • Loading branch information
maxxfrazer authored Nov 27, 2023
2 parents 464cc8d + 53420cc commit fd963de
Show file tree
Hide file tree
Showing 48 changed files with 1,551 additions and 998 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/build-docc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ jobs:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: macos-12
runs-on: macos-13
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Switch Xcode 🔄
run: sudo xcode-select --switch /Applications/Xcode_15.0.app
- name: Build DocC
run: |
xcodebuild docbuild -scheme RealityUI -derivedDataPath /tmp/docbuild -destination 'generic/platform=iOS';
Expand All @@ -36,10 +38,10 @@ jobs:
--output-path docs;
echo "<script>window.location.href += \"/documentation/realityui\"</script>" > docs/index.html
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v2
with:
# Upload docs directory
path: 'docs'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v2
21 changes: 13 additions & 8 deletions .github/workflows/swift-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ on:

jobs:
build:
runs-on: macOS-12
runs-on: macOS-13
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Switch Xcode 🔄
run: sudo xcode-select --switch /Applications/Xcode_15.0.app
- name: Swift Lint
run: swiftlint --strict
- name: Test iOS
run: |
xcodebuild build -scheme RealityUI -destination "platform=iOS Simulator,name=iPhone 14" | xcpretty
run: xcodebuild test -scheme RealityUI -destination "platform=iOS Simulator,name=iPhone 15" -enableCodeCoverage YES
- name: Fetch Coverage
uses: sersoft-gmbh/swift-coverage-action@v4
id: coverage-files
- name: Publish Coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }}
- name: Test macOS
run: |
xcodebuild build -scheme RealityUI -destination "platform=macOS" | xcpretty
env:
SCHEME: RealityUI
run: xcodebuild test -scheme RealityUI -destination "platform=macOS" -enableCodeCoverage YES
4 changes: 2 additions & 2 deletions .spi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
version: 1
builder:
configs:
- platform: ios
documentation_targets: [RealityUI]
- documentation_targets: [RealityUI]
custom_documentation_parameters: [--include-extended-types]
3 changes: 3 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
line_length:
ignores_comments: true
identifier_name:
excluded:
- t
excluded:
- RealityUI+Example
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.6
// swift-tools-version:5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
15 changes: 15 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "RealityUI",
platforms: [.iOS(.v13), .macOS(.v10_15), .visionOS(.v1)],
products: [.library( name: "RealityUI", targets: ["RealityUI"])],
dependencies: [],
targets: [
.target(name: "RealityUI", dependencies: []),
.testTarget(name: "RealityUITests", dependencies: ["RealityUI"])
]
)
48 changes: 37 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
# RealityUI

RealityUI is a collection of User Interface classes for RealityKit.
The classes included in RealityUI aim to offer familiar User Interface guidelines, but in a 3D setting for Augmented and Virtual Reality through RealityKit.
RealityUI is a collection of utilities and UI objects for RealityKit.
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.

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.
RealityUI also has a collection of components for interfacing with any Entity through touch or drag interactions.

<p align="center">
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI">
<img src="https://img.shields.io/github/v/release/maxxfrazer/RealityUI?color=F05138&label=Package%20Version&logo=Swift"/>
</a>
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI/main/documentation/realityui">
<img src="https://img.shields.io/badge/Swift-Doc-DE5C43.svg?style=flat"></a>
<a href="https://codecov.io/github/maxxfrazer/RealityUI" >
<img src="https://codecov.io/github/maxxfrazer/RealityUI/graph/badge.svg?token=3PCDBMSCLL"/>
</a>
<br/>
<img src="https://github.com/maxxfrazer/RealityUI/workflows/build/badge.svg?branch=main"/>
<img src="https://github.com/maxxfrazer/RealityUI/workflows/Deploy%20DocC/badge.svg?branch=main"/>
<a href="./LICENSE.md">
<img src="https://img.shields.io/github/license/maxxfrazer/RealityUI"/>
</a>
<br/>
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI">
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmaxxfrazer%2FRealityUI%2Fbadge%3Ftype%3Dplatforms"/>
</a>
<a href="https://swiftpackageindex.com/maxxfrazer/RealityUI">
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmaxxfrazer%2FRealityUI%2Fbadge%3Ftype%3Dswift-versions"/>
</a>
<br/>
<img src="https://img.shields.io/github/license/maxxfrazer/RealityUI"/>
<img src="https://github.com/maxxfrazer/RealityUI/workflows/build/badge.svg?branch=main"/>
<img src="https://github.com/maxxfrazer/RealityUI/workflows/Deploy%20DocC/badge.svg?branch=main"/>
</p>

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

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.

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`.
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`.
I would just recommend using `.all` when enabling gestures, as these will inevitably move around as RealityUI develops.

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

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.

### Drag

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.

![Dragging Cubes](media/RUIDrag_cubes.gif)

This type has an optional constraint, to fix the movement within certain criteria:

1. **Box Constraint**: Restricts movement within a specified `BoundingBox`, providing a defined area where the entity can move.

2. **Points Constraint**: Limits movement to a set of predefined points, represented as an array of `SIMD3<Float>`.

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.

### Turn

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

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

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

### Tap

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

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.
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.

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).

![touch-up-inside example with a button](media/button_light.gif)

---
## Animations
Expand Down Expand Up @@ -161,6 +187,6 @@ With RUIText you can easily create an Entity with the specified text placed with
---
## More

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/).
More information on everything provided in this Swift Package in the [documentation](https://maxxfrazer.github.io/RealityUI/documentation/realityui/).

Also see the [Example Project](https://github.com/maxxfrazer/RealityUI/tree/main/RealityUI%2BExamples) for iOS in this repository.
4 changes: 4 additions & 0 deletions RealityUI+Example/RealityUI+Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@
Base,
);
mainGroup = F31E9ACF29E47B060084306F;
packageReferences = (
);
productRefGroup = F31E9AD929E47B060084306F /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -299,6 +301,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "RealityUI-Example-Info.plist";
INFOPLIST_KEY_NSCameraUsageDescription = ar;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand Down Expand Up @@ -338,6 +341,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "RealityUI-Example-Info.plist";
INFOPLIST_KEY_NSCameraUsageDescription = ar;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand Down
83 changes: 70 additions & 13 deletions RealityUI+Example/RealityUI+Example/ARViewContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,25 @@ struct ARViewContainer: UIViewRepresentable {
// Create an ARView
let arView = ARView(frame: .zero)
#if os(iOS)
arView.cameraMode = .nonAR
arView.cameraMode = .ar
#endif

// Add the anchor to the scene
#if os(iOS)
let anchor = AnchorEntity(world: [0, 0, -1])
anchor.scale *= 0.15
#else
let anchor = AnchorEntity(world: .zero)
#endif
arView.scene.addAnchor(anchor)

arView.debugOptions.insert(.showPhysics)
// Setup RealityKit camera
#if os(macOS)
let cam = PerspectiveCamera()
cam.look(at: .zero, from: [0, 0, -2.5], relativeTo: nil)
cam.look(at: .zero, from: [0, 0, -3], relativeTo: nil)
anchor.addChild(cam)
#endif

self.setModel(view: arView)
RealityUI.enableGestures(.all, on: arView)
Expand All @@ -55,20 +63,69 @@ struct ARViewContainer: UIViewRepresentable {
func setModel(view: ARView) {
guard let worldAnchor = view.scene.anchors.first,
prevObjectType != objectType else { return }
worldAnchor.orientation = simd_quatf.init(angle: .pi, axis: [0, 1, 0])
if let oldRui = view.scene.findEntity(named: "ruiReplace") {
worldAnchor.removeChild(oldRui)
}
view.environment.background = .color(.gray)
let ruiModel: Entity
let smallMove: Float = .random(in: 0.1...0.4)
switch objectType {
case .mover:
ruiModel = Entity()
for idx in 0...5 {
let modEnt = ModelEntity(
mesh: .generateBox(width: 0.5, height: 0.5, depth: 0.5, splitFaces: true),
materials: [
SimpleMaterial(color: .blue, isMetallic: false),
SimpleMaterial(color: .yellow, isMetallic: false),
SimpleMaterial(color: .orange, isMetallic: false),
SimpleMaterial(color: .purple, isMetallic: false),
SimpleMaterial(color: .green, isMetallic: false),
SimpleMaterial(color: .red, isMetallic: false)
].shuffled()
)
modEnt.generateCollisionShapes(recursive: false)
modEnt.components.set(RUIDragComponent(
type: .move(.box(
BoundingBox(min: [-2, -1, -1], max: [2, 1, 1])
))
))
modEnt.position.x = 2 * sin(
Float(idx) / 3 * .pi + smallMove
)
modEnt.position.y = cos(
Float(idx) / 3 * .pi + smallMove
)
ruiModel.addChild(modEnt)
}
let container = ModelEntity(
mesh: .generateBox(width: 4.5, height: 2.5, depth: 2.5),
materials: [SimpleMaterial(color: .white.withAlphaComponent(0.2), isMetallic: false)]
)
container.scale *= -1
ruiModel.addChild(container)
case .toggle:
ruiModel = RUISwitch(switchCallback: { hasSwitch in
view.environment.background = .color(hasSwitch.isOn ? .green : .gray)
})
case .text:
let textObj = RUIText(textComponent: TextComponent(
text: "hello", font: .systemFont(ofSize: 1),
alignment: .center, extrusion: 0.1
))
textObj.components.set(RUITapComponent { ent, _ in
ent.ruiSpin(
by: [[1, 0, 0], [0, 1, 0], [0, 0, 1]].randomElement()!,
period: 0.3, times: 1
)
})
textObj.addCollision()
ruiModel = textObj
case .slider:
let scalingCube = ModelEntity(mesh: .generateBox(size: 3))
scalingCube.position.z = 3
ruiModel = RUISlider(start: 0.5) { slider, state in
ruiModel = RUISlider(length: 7, start: 0.5, steps: Bool.random() ? 4 : 0) { slider, state in
scalingCube.scale = .one * (slider.value + 0.2) / 1.2
}
ruiModel.addChild(scalingCube)
Expand All @@ -88,8 +145,12 @@ struct ARViewContainer: UIViewRepresentable {
}
ruiModel.look(at: [0, 1, -1], from: .zero, relativeTo: nil)
case .rotation:
ruiModel = RotationPlane(turnAxis: [0, 0, 1])
ruiModel.scale = .one * 2
ruiModel = RotationPlane()
// stand up the model, so it's facing the camera
ruiModel.orientation = simd_quatf(angle: .pi, axis: [0, 1, 0])

// set the turn/rotation component
ruiModel.components.set(RUIDragComponent(type: .turn(axis: [0, 0, 1])))
}
ruiModel.name = "ruiReplace"
worldAnchor.addChild(ruiModel)
Expand All @@ -104,27 +165,23 @@ struct ARViewContainer: UIViewRepresentable {
}

/// Class for demonstrating the HasTurnTouch protocol.
class RotationPlane: Entity, HasModel, HasCollision, HasTurnTouch {
class RotationPlane: Entity, HasModel, HasCollision {

/// Create a new ``RotationPlane``, which conforms to HasTurnTouch
/// - Parameter turnAxis: Axis that the object will be rotated around.
required init(turnAxis: SIMD3<Float>) {
required init() {
super.init()
self.turnAxis = turnAxis
var rotateMat = SimpleMaterial()
rotateMat.color = SimpleMaterial.BaseColor(
tint: .white.withAlphaComponent(0.99), texture: MaterialParameters.Texture(
try! TextureResource.load(named: "rotato")
)
)
self.scale = .one * 2

self.model = ModelComponent(mesh: .generatePlane(width: 1, height: 1), materials: [rotateMat])
self.orientation = .init(angle: .pi, axis: [0, 1, 0])
self.collision = CollisionComponent(shapes: [.generateBox(width: 1, height: 1, depth: 0.1)])
}

@MainActor required init() {
fatalError("init() has not been implemented")
}
}

extension ARViewContainer {
Expand Down
2 changes: 2 additions & 0 deletions RealityUI+Example/RealityUI+Example/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public enum RealityObject: String, CaseIterable {
case stepper
case button
case rotation
case text
case mover
}

struct ContentView: View {
Expand Down
20 changes: 0 additions & 20 deletions Sources/RealityUI/HasClick.swift

This file was deleted.

Loading

0 comments on commit fd963de

Please sign in to comment.