Skip to content

Commit

Permalink
Add hover effects to menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Arclite committed Jul 30, 2024
1 parent 1f8b0a7 commit 51687d7
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 40 deletions.
104 changes: 75 additions & 29 deletions Modules/Capabilities/Tools/Sources/Pencil Menu/PencilMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,115 @@
import DesignSystem
import SwiftUI

@available(iOS 15.0, *)
public struct PencilMenu: View {
private static let outerDiameter: Double = 298
private static let itemDiameter: Double = PencilMenuItem.diameter
private static let padding: Double = 8
private static let itemCount: Int = HighlighterTool.allCases.count
private static var lineWidth: Double {
itemDiameter + padding
}
static let outerDiameter: Double = 298
static let padding: Double = 8

private static var maxTrim: Double {
let itemLength = itemDiameter * itemCount
let paddingLength = padding * itemCount
let requiredLength = itemLength + paddingLength - lineWidth
return requiredLength / outerCircumference
static var outerCircumference: Double {
return Double.pi * PencilMenu.outerDiameter
}

private let isMenuShowing: Bool
public init(isMenuShowing: Bool) {
private let menuPosition: CGPoint
private let hoverPosition: CGPoint?
public init(isMenuShowing: Bool, menuPosition: CGPoint, hoverPosition: CGPoint?) {
self.isMenuShowing = isMenuShowing
self.menuPosition = menuPosition
self.hoverPosition = hoverPosition
}

public var body: some View {
ZStack {
Circle()
.trim(from: 0, to: Self.maxTrim)
.stroke(Color.primaryDark, style: StrokeStyle(lineWidth: Self.lineWidth, lineCap: .round))
.frame(width: Self.outerDiameter, height: Self.outerDiameter)
PencilMenuBackground()
if let adjustedPosition {
Circle()
.foregroundColor(.red)
.position(adjustedPosition)
.frame(width: 5, height: 5)
}

ForEach(Self.indexedTools, id: \.0) { (tool, index) in
PencilMenuItem(tool: tool)
.transformEffect(itemTransform(index: index))
.transformEffect(itemTransform(index: index, isOffset: true))
.scaleEffect(scale(at: index))
.border(Color.green)
}
}
.border(Color.red)
.scaleEffect(isMenuShowing ? 1.0 : 0.5)
.opacity(isMenuShowing ? 1 : 0)
.animation(.bouncy(duration: 0.3, extraBounce: 0.1), value: isMenuShowing)
.disabled(isMenuShowing == false)
.overlay {
Canvas { context, size in

Check failure on line 48 in Modules/Capabilities/Tools/Sources/Pencil Menu/PencilMenu.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Unused parameter in a closure should be replaced with _ (unused_closure_parameter)
context.fill(path(at: 0), with: .color(.red.opacity(0.4)))
context.fill(path(at: 1), with: .color(.green.opacity(0.4)))
context.fill(path(at: 2), with: .color(.blue.opacity(0.4)))
}
}
}

private var adjustedPosition: CGPoint? {
guard let hoverPosition else { return nil }
let menuOrigin = CGPoint(
x: menuPosition.x - (Self.outerDiameter / 2),
y: menuPosition.y - (Self.outerDiameter / 2)
)

return CGPoint(
x: hoverPosition.x - menuOrigin.x,
y: hoverPosition.y - menuOrigin.y)
}

private func path(at index: Int) -> Path {
Path(ellipseIn: Self.buttonRect)
.applying(itemTransform(index: index, isOffset: false))
.applying(CGAffineTransform(translationX: Self.outerDiameter / 2, y: Self.outerDiameter / 2))
// .applying(itemTransform(index: index))
}

private func scale(at index: Int) -> CGSize {
let regularScale = CGSize(width: 1.0, height: 1.0)
let expandedScale = CGSize(width: 1.2, height: 1.2)
guard let adjustedPosition else { return regularScale }
let shouldScale = path(at: index).contains(adjustedPosition)

return shouldScale ? expandedScale : regularScale
}

private static let buttonRect = CGRect(
origin: CGPoint(
x: -18, //Self.outerDiameter / 2 - 18,

Check failure on line 86 in Modules/Capabilities/Tools/Sources/Pencil Menu/PencilMenu.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Prefer at least one space after slashes for comments (comment_spacing)
y: -18 //Self.outerDiameter / 2 - 18

Check failure on line 87 in Modules/Capabilities/Tools/Sources/Pencil Menu/PencilMenu.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Prefer at least one space after slashes for comments (comment_spacing)
), size: CGSize(
width: 36,
height: 36
)
)

private static let indexedTools: [(HighlighterTool, Int)] = {
zip(HighlighterTool.allCases, HighlighterTool.allCases.indices)
.map { $0 }
}()

private static var outerCircumference: Double {
return Double.pi * outerDiameter
}

private func itemTransform(index: Int) -> CGAffineTransform {
let offset = (Self.padding + Self.itemDiameter) * index
private func itemTransform(index: Int, isOffset: Bool) -> CGAffineTransform {
let offset = (Self.padding + PencilMenuItem.diameter) * index
let offsetPercent = offset / Self.outerCircumference
let rotation = offsetPercent * (Double.pi * 2)

let translateTransform = CGAffineTransform(translationX: Self.outerDiameter / 2, y: 0)
let centerOffsetTransform = CGAffineTransform(translationX: Self.itemDiameter / -2, y: Self.itemDiameter / -2)
let centerOffsetTransform = CGAffineTransform(translationX: PencilMenuItem.diameter / -2, y: PencilMenuItem.diameter / -2)
let rotateTransform = CGAffineTransform(rotationAngle: rotation)
let centerResetTransform = CGAffineTransform(translationX: Self.itemDiameter / 2, y: Self.itemDiameter / 2)
let centerResetTransform = CGAffineTransform(translationX: PencilMenuItem.diameter / 2, y: PencilMenuItem.diameter / 2)
return translateTransform
.concatenating(centerOffsetTransform)
.concatenating(isOffset ? centerOffsetTransform : .identity)
.concatenating(rotateTransform)
.concatenating(centerResetTransform)
.concatenating(isOffset ? centerResetTransform : .identity)
}
}

@available(iOS 15.0, *)
enum PencilMenuPreviews: PreviewProvider {
struct PreviewWrapper: View {
@State private var isMenuShowing = false
Expand All @@ -79,7 +125,7 @@ enum PencilMenuPreviews: PreviewProvider {
} label: {
Text("Toggle Menu \(isMenuShowing)")
}
PencilMenu(isMenuShowing: isMenuShowing)
PencilMenu(isMenuShowing: isMenuShowing, menuPosition: .zero, hoverPosition: nil)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Created by Geoff Pado on 7/29/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import SwiftUI

@available(iOS 15.0, *)
struct PencilMenuBackground: View {
private static let itemCount: Int = HighlighterTool.allCases.count
private static var lineWidth: Double {
PencilMenuItem.diameter + PencilMenu.padding
}

private static var maxTrim: Double {
let itemLength = PencilMenuItem.diameter * itemCount
let paddingLength = PencilMenu.padding * itemCount
let requiredLength = itemLength + paddingLength - lineWidth
return requiredLength / PencilMenu.outerCircumference
}

Check failure on line 19 in Modules/Capabilities/Tools/Sources/Pencil Menu/PencilMenuBackground.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Lines should not have trailing whitespace (trailing_whitespace)
var body: some View {
Circle()
.trim(from: 0, to: Self.maxTrim)
.stroke(Color.primaryDark, style: StrokeStyle(lineWidth: Self.lineWidth, lineCap: .round))
.frame(width: PencilMenu.outerDiameter, height: PencilMenu.outerDiameter)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ import SwiftUI
import Tools

struct PhotoEditingPencilMenuOverlay: View {
@ObservedObject private var hoverPositionHolder: PhotoEditingPencilMenuHoverPositionHolder
private let isMenuShowing: Bool
private let position: CGPoint
private let menuPosition: CGPoint

init(isMenuShowing: Bool, position: CGPoint) {
init(isMenuShowing: Bool, position: CGPoint, hoverPositionHolder: PhotoEditingPencilMenuHoverPositionHolder) {
self.isMenuShowing = isMenuShowing
self.position = position
self.menuPosition = position
self.hoverPositionHolder = hoverPositionHolder
}

var body: some View {
PencilMenu(isMenuShowing: isMenuShowing)
.position(position)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
if #available(iOS 15, *) {
PencilMenu(
isMenuShowing: isMenuShowing,
menuPosition: menuPosition,
hoverPosition: hoverPositionHolder.hoverPosition
)
.position(menuPosition)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import UIKit
class PhotoEditingPencilMenuViewController: UIHostingController<PhotoEditingPencilMenuOverlay> {
private var isMenuShowing: Bool = false
private var position: CGPoint = .zero
private let positionHolder = PhotoEditingPencilMenuHoverPositionHolder()

init() {
super.init(rootView: PhotoEditingPencilMenuOverlay(isMenuShowing: isMenuShowing, position: position))
super.init(rootView: PhotoEditingPencilMenuOverlay(isMenuShowing: isMenuShowing, position: position, hoverPositionHolder: positionHolder))
view.isOpaque = false
view.backgroundColor = .clear
view.isUserInteractionEnabled = false
Expand All @@ -27,8 +28,13 @@ class PhotoEditingPencilMenuViewController: UIHostingController<PhotoEditingPenc
updateRootView()
}

func updateMenu(at position: CGPoint) {
positionHolder.hoverPosition = position
// rootView.updateHoverPosition(to: position)
}

private func updateRootView() {
rootView = PhotoEditingPencilMenuOverlay(isMenuShowing: isMenuShowing, position: position)
rootView = PhotoEditingPencilMenuOverlay(isMenuShowing: isMenuShowing, position: position, hoverPositionHolder: positionHolder)
}

// MARK: Boilerplate
Expand All @@ -39,3 +45,7 @@ class PhotoEditingPencilMenuViewController: UIHostingController<PhotoEditingPenc
fatalError("\(typeName) does not implement init(coder:)")
}
}

class PhotoEditingPencilMenuHoverPositionHolder: NSObject, ObservableObject {
@Published var hoverPosition: CGPoint?
}
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,12 @@ public class PhotoEditingViewController: UIViewController, UIScrollViewDelegate,
pencilMenuViewController.toggleMenu(at: location)
}

@objc func updatePencilMenu(_ sender: UIView, event: PhotoEditingWorkspacePencilEvent) {
guard let eventLocation = event.location else { return }
let location = view.convert(eventLocation, from: sender)
pencilMenuViewController.updateMenu(at: location)
}

// MARK: Boilerplate

// tuBrute by @AdamWulf on 2024-04-29
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ class PhotoEditingWorkspacePencilDelegate: UIResponder, UIPencilInteractionDeleg
switch squeeze.phase {
case .began:
showContextualPalette(at: squeeze.hoverPose?.location)
case .changed: break
// determine if button selected
case .changed:
// thisVariableNameIsLongerThanTheTimeItTakesToFigureOutWhySettingTranslatesAutoresizingMaskIntoConstraintsToFalseFixedEverythingButYouStillDontKnowWhyWhichIsAnotherReasonSwiftUIIsGreat by @haiiux on 2024-07-29
// the hover pose, if it exists
guard let thisVariableNameIsLongerThanTheTimeItTakesToFigureOutWhySettingTranslatesAutoresizingMaskIntoConstraintsToFalseFixedEverythingButYouStillDontKnowWhyWhichIsAnotherReasonSwiftUIIsGreat = squeeze.hoverPose else { return }
updateContextualPalette(at: thisVariableNameIsLongerThanTheTimeItTakesToFigureOutWhySettingTranslatesAutoresizingMaskIntoConstraintsToFalseFixedEverythingButYouStillDontKnowWhyWhichIsAnotherReasonSwiftUIIsGreat.location)
case .ended:
// handle selected button
fallthrough

Check failure on line 51 in Modules/Legacy/Editing/Sources/Editing View/Workspace/PhotoEditingWorkspacePencilDelegate.swift

View workflow job for this annotation

GitHub Actions / Run SwiftLint

Fallthroughs can only be used if the `case` contains at least one other statement (no_fallthrough_only)
Expand Down Expand Up @@ -79,6 +82,10 @@ class PhotoEditingWorkspacePencilDelegate: UIResponder, UIPencilInteractionDeleg
UIApplication.shared.sendAction(#selector(PhotoEditingViewController.togglePencilMenu(_:event:)), to: nil, from: workspaceView, for: PhotoEditingWorkspacePencilEvent(location: location))
}

private func updateContextualPalette(at location: CGPoint) {
UIApplication.shared.sendAction(#selector(PhotoEditingViewController.updatePencilMenu(_:event:)), to: nil, from: workspaceView, for: PhotoEditingWorkspacePencilEvent(location: location))
}

// MARK: Delegate Methods

func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
Expand Down

0 comments on commit 51687d7

Please sign in to comment.