mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Some UI Fixes
This commit is contained in:
parent
e5433c28d5
commit
bbf4cffd24
1 changed files with 87 additions and 38 deletions
|
|
@ -6,6 +6,9 @@ struct MusicSlotConfigurationView: View {
|
||||||
@Default(.musicControlSlots) private var musicControlSlots
|
@Default(.musicControlSlots) private var musicControlSlots
|
||||||
@Default(.showMediaOutputControl) private var showMediaOutputControl
|
@Default(.showMediaOutputControl) private var showMediaOutputControl
|
||||||
@ObservedObject private var musicManager = MusicManager.shared
|
@ObservedObject private var musicManager = MusicManager.shared
|
||||||
|
@State private var selectedSlotIndex: Int? = nil
|
||||||
|
@State private var targetedSlotIndex: Int? = nil
|
||||||
|
@State private var trashDropIsTargeted: Bool = false
|
||||||
|
|
||||||
private let slotCount = MusicControlButton.slotCount
|
private let slotCount = MusicControlButton.slotCount
|
||||||
|
|
||||||
|
|
@ -37,7 +40,7 @@ struct MusicSlotConfigurationView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var layoutPreview: some View {
|
private var layoutPreview: some View {
|
||||||
HStack(alignment: .center, spacing: 12) {
|
HStack(alignment: .top, spacing: 12) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
ForEach(0..<slotCount, id: \.self) { index in
|
ForEach(0..<slotCount, id: \.self) { index in
|
||||||
slotPreview(for: index)
|
slotPreview(for: index)
|
||||||
|
|
@ -50,21 +53,28 @@ struct MusicSlotConfigurationView: View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
ZStack {
|
ZStack {
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color(nsColor: .controlBackgroundColor))
|
.fill(trashDropIsTargeted ? Color.red.opacity(0.2) : Color(nsColor: .controlBackgroundColor))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(trashDropIsTargeted ? Color.red : .clear, lineWidth: trashDropIsTargeted ? 2 : 0)
|
||||||
|
)
|
||||||
.frame(width: 56, height: 56)
|
.frame(width: 56, height: 56)
|
||||||
|
|
||||||
Image(systemName: "trash")
|
Image(systemName: "trash")
|
||||||
.font(.system(size: 16, weight: .medium))
|
.font(.system(size: 16, weight: .medium))
|
||||||
|
.foregroundStyle(trashDropIsTargeted ? Color.red : Color.primary)
|
||||||
}
|
}
|
||||||
.onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in
|
.onDrop(of: [UTType.plainText.identifier], isTargeted: $trashDropIsTargeted) { providers in
|
||||||
handleDropOnTrash(providers)
|
return handleDropOnTrash(providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Clear slot")
|
Text("Clear slot")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.frame(width: 72)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
.frame(width: 72)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,44 +111,46 @@ struct MusicSlotConfigurationView: View {
|
||||||
|
|
||||||
private func slotPreview(for index: Int) -> some View {
|
private func slotPreview(for index: Int) -> some View {
|
||||||
let slot = slotValue(at: index)
|
let slot = slotValue(at: index)
|
||||||
return Group {
|
let isSelected = selectedSlotIndex == index
|
||||||
if slot != .none {
|
let isDropTarget = targetedSlotIndex == index
|
||||||
slotPreview(for: slot)
|
|
||||||
.frame(width: 48, height: 48)
|
return ZStack {
|
||||||
.contentShape(RoundedRectangle(cornerRadius: 10))
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.onDrag {
|
.fill(slotBackgroundColor(isSelected: isSelected, isTargeted: isDropTarget))
|
||||||
NSItemProvider(object: NSString(string: "slot:\(index)"))
|
.overlay(
|
||||||
}
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in
|
.stroke(slotBorderColor(isSelected: isSelected, isTargeted: isDropTarget), lineWidth: borderWidth(isSelected: isSelected, isTargeted: isDropTarget))
|
||||||
handleDrop(providers, toIndex: index)
|
)
|
||||||
}
|
|
||||||
} else {
|
slotContent(for: slot)
|
||||||
slotPreview(for: slot)
|
}
|
||||||
.frame(width: 48, height: 48)
|
.frame(width: 48, height: 48)
|
||||||
.contentShape(RoundedRectangle(cornerRadius: 10))
|
.contentShape(RoundedRectangle(cornerRadius: 10))
|
||||||
.onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in
|
.onTapGesture {
|
||||||
handleDrop(providers, toIndex: index)
|
withAnimation(.easeInOut(duration: 0.15)) {
|
||||||
}
|
selectedSlotIndex = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDrag {
|
||||||
|
selectedSlotIndex = index
|
||||||
|
return NSItemProvider(object: NSString(string: "slot:\(index)"))
|
||||||
|
}
|
||||||
|
.onDrop(of: [UTType.plainText.identifier], isTargeted: dropTargetBinding(for: index)) { providers in
|
||||||
|
return handleDrop(providers, toIndex: index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func slotPreview(for slot: MusicControlButton) -> some View {
|
private func slotContent(for slot: MusicControlButton) -> some View {
|
||||||
ZStack {
|
if slot == .none {
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 6)
|
||||||
.fill(Color(nsColor: .controlBackgroundColor))
|
.stroke(style: StrokeStyle(lineWidth: 1, dash: [4, 4]))
|
||||||
|
.foregroundStyle(.secondary.opacity(0.35))
|
||||||
if slot == .none {
|
.frame(width: 26, height: 26)
|
||||||
RoundedRectangle(cornerRadius: 6)
|
} else {
|
||||||
.stroke(style: StrokeStyle(lineWidth: 1, dash: [4, 4]))
|
Image(systemName: slot.iconName)
|
||||||
.foregroundStyle(.secondary.opacity(0.35))
|
.font(.system(size: slot.prefersLargeScale ? 18 : 15, weight: .medium))
|
||||||
.frame(width: 26, height: 26)
|
.foregroundStyle(previewIconColor(for: slot))
|
||||||
} else {
|
|
||||||
Image(systemName: slot.iconName)
|
|
||||||
.font(.system(size: slot.prefersLargeScale ? 18 : 15, weight: .medium))
|
|
||||||
.foregroundStyle(previewIconColor(for: slot))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,4 +283,41 @@ struct MusicSlotConfigurationView: View {
|
||||||
let filtered = musicControlSlots.map { $0 == .mediaOutput ? .none : $0 }
|
let filtered = musicControlSlots.map { $0 == .mediaOutput ? .none : $0 }
|
||||||
musicControlSlots = filtered
|
musicControlSlots = filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func slotBackgroundColor(isSelected: Bool, isTargeted: Bool) -> Color {
|
||||||
|
if isTargeted {
|
||||||
|
return Color.accentColor.opacity(0.25)
|
||||||
|
} else if isSelected {
|
||||||
|
return Color.accentColor.opacity(0.15)
|
||||||
|
}
|
||||||
|
return Color(nsColor: .controlBackgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func slotBorderColor(isSelected: Bool, isTargeted: Bool) -> Color {
|
||||||
|
if isTargeted {
|
||||||
|
return Color.accentColor
|
||||||
|
} else if isSelected {
|
||||||
|
return Color.accentColor.opacity(0.8)
|
||||||
|
}
|
||||||
|
return .clear
|
||||||
|
}
|
||||||
|
|
||||||
|
private func borderWidth(isSelected: Bool, isTargeted: Bool) -> CGFloat {
|
||||||
|
if isTargeted { return 2 }
|
||||||
|
if isSelected { return 1.5 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dropTargetBinding(for index: Int) -> Binding<Bool> {
|
||||||
|
Binding(
|
||||||
|
get: { targetedSlotIndex == index },
|
||||||
|
set: { newValue in
|
||||||
|
if newValue {
|
||||||
|
targetedSlotIndex = index
|
||||||
|
} else if targetedSlotIndex == index {
|
||||||
|
targetedSlotIndex = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue