Skip to content

Commit

Permalink
Widget Editor Refinements (#1793)
Browse files Browse the repository at this point in the history
  • Loading branch information
EricBAndrews authored Feb 22, 2025
1 parent 15a6f94 commit ee18135
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ extension InteractionBarEditorView {
if barPickedUpItem == nil {
HapticManager.main.play(haptic: .firmInfo, priority: .low)
barPickedUpItem = (item, index)
if let trayItem = trayItems.first(where: { $0.item == item.item }) {
withAnimation(.easeOut(duration: barAnimationDuration)) {
trayItem.hide()
}
}
}
dragLocation = gesture.location
dragTranslation = gesture.translation
Expand All @@ -126,7 +131,7 @@ extension InteractionBarEditorView {
func trayItemDragGesture(trayItem: TrayItem) -> some Gesture {
DragGesture(minimumDistance: 0, coordinateSpace: .named("editor"))
.onChanged { gesture in
if trayPickedUpItem == nil, !barItems.contains(where: { $0.item == trayItem.item }) {
if trayPickedUpItem == nil {
HapticManager.main.play(haptic: .firmInfo, priority: .low)
trayPickedUpItem = trayItem
}
Expand All @@ -151,29 +156,48 @@ extension InteractionBarEditorView {
guard case let .bar(targetIndex) = dropLocation else { return }
addToBar(trayPickedUpItem, at: targetIndex)
} else if let barPickedUpItem {
if let trayItem = trayItems.first(where: { $0.item == barPickedUpItem.barItem.item }) {
withAnimation(.easeOut(duration: barAnimationDuration)) {
trayItem.show()
}
}

switch dropLocation {
case let .bar(targetIndex):
moveOnBar(barItem: barPickedUpItem.barItem, from: barPickedUpItem.index, to: targetIndex)
case .tray:
removeFromBar(barItem: barPickedUpItem.barItem)
removeFromBar(barItem: barPickedUpItem.barItem, barItemIndex: barPickedUpItem.index)
}
}
}

// MARK: - State Updates

func addToBar(_ trayItem: TrayItem, at index: Int) {
guard allowNewItemInsertion,
!barItems.contains(where: { $0.item == trayItem.item }) else {
assertionFailure(!allowNewItemInsertion ? "Item insertion disabled" : "Item already in bar")
guard allowNewItemInsertion else {
assertionFailure("Item insertion disabled")
return
}

HapticManager.main.play(haptic: .firmInfo, priority: .high)

let newItem: BarItem = .init(item: trayItem.item, expanded: false, visible: true)

barItems.insert(newItem, at: index)

// gently fade the tray item back in
trayItem.hide()
withAnimation(.easeOut(duration: trayItemDuration)) {
trayItem.show()
}

// recompute infoStackAlignment with actual barItems, since these animations all play nice
withAnimation(.easeInOut(duration: barAnimationDuration)) {
infoStackAlignment = computeInfoStackAlignment(
infoStackIndex: infoStackIndex(),
totalItems: barItems.count
)
}

updateConfiguration()
}
Expand All @@ -193,25 +217,78 @@ extension InteractionBarEditorView {
barItems.insert(newItem, at: targetIndex)
}

// recompute infoStackAlignment with projected info stack location
let infoStackIndex = infoStackIndex()
let newInfoStackAlignment: Alignment?
if barItem.item == nil {
// if moving info stack itself, can compute alignment based on whether moving to beginning or end
if targetIndex == 0 {
newInfoStackAlignment = barItems.count == 1 ? .center : .leading
} else if targetIndex == barItems.count - 1 {
newInfoStackAlignment = .trailing
} else {
newInfoStackAlignment = .center
}
} else {
if sourceIndex < infoStackIndex {
if targetIndex > infoStackIndex {
// moving widget from left to right of info stack, projected infostack index is current - 1
newInfoStackAlignment = computeInfoStackAlignment(
infoStackIndex: infoStackIndex - 1,
totalItems: barItems.count
)
} else {
// widget not moving "over" the info stack, no change
newInfoStackAlignment = nil
}
} else {
if targetIndex < infoStackIndex {
// moving widget from right to left of info stack, projected infostack index is current + 1
newInfoStackAlignment = computeInfoStackAlignment(
infoStackIndex: infoStackIndex + 1,
totalItems: barItems.count
)
} else {
// widget not moving "over" the info stack, no change
newInfoStackAlignment = nil
}
}
}
if let newInfoStackAlignment {
withAnimation(.easeInOut(duration: barAnimationDuration)) { infoStackAlignment = newInfoStackAlignment }
}

// wait for animation to complete, then remove original item from barItems
DispatchQueue.main.asyncAfter(deadline: .now() + barAnimationDuration) {
barItems.removeAll(where: { $0 == barItem })
updateConfiguration()
}
}

func removeFromBar(barItem: BarItem) {
func removeFromBar(barItem: BarItem, barItemIndex: Int) {
// no removing the info stack
guard let item = barItem.item else { return }
let trayItem = trayItems.first(where: { $0.item == item })

HapticManager.main.play(haptic: .firmInfo, priority: .high)

// recompute infoStackAlignment with projected info stack location
let infoStackIndex = infoStackIndex()
let newInfoStackAlignment: Alignment
if barItemIndex < infoStackIndex {
// removing item to the left of info stack: shift info stack left
newInfoStackAlignment = computeInfoStackAlignment(infoStackIndex: infoStackIndex - 1, totalItems: barItems.count - 1)
} else {
// removing item to the right of info stack: info stack index unchanged, but barItems.count still decreases
newInfoStackAlignment = computeInfoStackAlignment(infoStackIndex: infoStackIndex, totalItems: barItems.count - 1)
}

// smoothly animate away
barItem.hide()
withAnimation(.easeInOut(duration: barAnimationDuration)) {
trayItem?.show()
barItem.collapse()
if newInfoStackAlignment != infoStackAlignment {
infoStackAlignment = newInfoStackAlignment
}
}

// wait for animation to complete, then remove from barItems
Expand Down Expand Up @@ -243,4 +320,22 @@ extension InteractionBarEditorView {
}
return palette.tertiary
}

func infoStackIndex() -> Int {
guard let ret = barItems.firstIndex(where: { $0.item == nil }) else {
assertionFailure("could not find infoStack index")
return 0
}
return ret
}
}

func computeInfoStackAlignment(infoStackIndex: Int, totalItems: Int) -> Alignment {
if infoStackIndex == 0 {
return totalItems == 1 ? .center : .leading
} else if infoStackIndex == totalItems - 1 {
return .trailing
} else {
return .center
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ extension InteractionBarEditorView {
}

if dropLocation?.index == barItems.count,
barPickedUpIndex != barItems.count {
barPickedUpIndex != barItems.count - 1 {
dropIndicator(index: barItems.count)
}
}
Expand Down Expand Up @@ -168,10 +168,23 @@ extension InteractionBarEditorView {
.geometryGroup()
.offset(trayPickedUpItem == trayItem ? dragTranslation : .zero)
.background {
Capsule()
.fill(trayItemOutlineColor(trayItem).opacity(0.2))
.stroke(trayItemOutlineColor(trayItem))
.background(palette.secondaryGroupedBackground, in: .capsule)
Group {
switch trayItem.item {
case let .action(action):
InteractionBarActionLabelView(action.appearance)
case let .counter(counter):
InteractionBarCounterLabelView(counter.appearance)
.fixedSize()
}
}
.opacity(0.2)
.padding(Constants.main.standardSpacing)
.background {
Capsule()
.fill(trayItemOutlineColor(trayItem).opacity(0.2))
.stroke(trayItemOutlineColor(trayItem))
.background(palette.secondaryGroupedBackground, in: .capsule)
}
}
.gesture(trayItemDragGesture(trayItem: trayItem))
.zIndex(trayPickedUpItem == trayItem ? 2 : 0)
Expand Down Expand Up @@ -344,7 +357,7 @@ extension InteractionBarEditorView {
}
}
.foregroundStyle(palette.secondary)
.frame(maxWidth: .infinity)
.frame(maxWidth: .infinity, alignment: infoStackAlignment)
}

@ViewBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ struct InteractionBarEditorView<Configuration: InteractionBarConfiguration>: Vie
@State var dragLocation: CGPoint = .zero
@State var dragTranslation: CGSize = .zero

@State var infoStackAlignment: Alignment

@State var showingApplyToAllConfirmation: Bool = false

let onSet: (Configuration) -> Void

let barAnimationDuration: CGFloat = 0.15
let trayItemDuration: CGFloat = 0.5

@ScaledMetric(relativeTo: .body) var baseInfoCapsuleHeight: CGFloat = 22
var infoCapsuleHeight: CGFloat { baseInfoCapsuleHeight + Constants.main.doubleSpacing }
Expand All @@ -44,10 +47,17 @@ struct InteractionBarEditorView<Configuration: InteractionBarConfiguration>: Vie
self.configuration = configuration
self.onSet = onSet
let configurationItems: [Configuration.Item?] = configuration.leading + [nil] + configuration.trailing
self._barItems = .init(wrappedValue: configurationItems.map { item in
.init(item: item, expanded: true, visible: true)
})
self.configurationType = configuration is PostBarConfiguration ? .post : .comment

let newBarItems: [BarItem] = configurationItems.map { .init(item: $0, expanded: true, visible: true) }
let newInfoStackIndex = newBarItems.firstIndex(where: { $0.item == nil })
assert(newInfoStackIndex != nil, "could not find infoStack index")

self._barItems = .init(wrappedValue: newBarItems)
self._infoStackAlignment = .init(wrappedValue: computeInfoStackAlignment(
infoStackIndex: newInfoStackIndex ?? 0,
totalItems: newBarItems.count)
)
}

init(setting: WritableKeyPath<InteractionBarTracker, Configuration>) {
Expand Down Expand Up @@ -78,7 +88,7 @@ struct InteractionBarEditorView<Configuration: InteractionBarConfiguration>: Vie
let configurationItems: [Configuration.Item?] = configuration.leading + [nil] + configuration.trailing
trayItems = Configuration.Item.allCases
.filter { configuration.availableWidgets.contains($0) }
.map { TrayItem(item: $0, visible: !configurationItems.contains($0)) }
.map { TrayItem(item: $0, visible: true) }
}
.frame(maxWidth: .infinity)
.padding(Constants.main.standardSpacing)
Expand Down

0 comments on commit ee18135

Please sign in to comment.