| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- import SwiftUI
- import AppKit
- struct AppTileView: View {
- let app: LauncherApp
- let onTap: () -> Void
- @State private var isHovering = false
- @State private var isPressing = false
- var body: some View {
- Button(action: onTap) {
- VStack(spacing: 11) {
- AppIconView(app: app, size: 64)
- .shadow(
- color: .black.opacity(isHovering ? 0.34 : 0.2),
- radius: isHovering ? 14 : 8,
- x: 0,
- y: isHovering ? 10 : 5
- )
- .scaleEffect(isPressing ? 0.96 : 1.0)
- .offset(y: isHovering ? -1 : 0)
- Text(app.name)
- .font(.system(size: 13, weight: .medium))
- .foregroundStyle(.white.opacity(isHovering ? 1.0 : 0.95))
- .lineLimit(1)
- .truncationMode(.tail)
- .frame(maxWidth: .infinity)
- .offset(y: isHovering ? -0.5 : 0)
- }
- .padding(.horizontal, 7)
- .padding(.vertical, 10)
- .background(
- RoundedRectangle(cornerRadius: 14, style: .continuous)
- .fill(
- LinearGradient(
- colors: isHovering
- ? [Color.white.opacity(0.14), Color.white.opacity(0.06)]
- : [Color.clear, Color.clear],
- startPoint: .topLeading,
- endPoint: .bottomTrailing
- )
- )
- )
- .overlay(
- RoundedRectangle(cornerRadius: 14, style: .continuous)
- .stroke(
- isHovering ? Color.white.opacity(0.24) : Color.clear,
- lineWidth: isHovering ? 1.2 : 1
- )
- )
- .scaleEffect(isPressing ? 0.98 : (isHovering ? 1.025 : 1.0))
- .offset(y: isPressing ? 0 : (isHovering ? -2 : 0))
- .shadow(
- color: .black.opacity(isHovering ? 0.22 : 0),
- radius: isHovering ? 16 : 0,
- x: 0,
- y: isHovering ? 10 : 0
- )
- .contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
- .animation(.spring(response: 0.24, dampingFraction: 0.88), value: isHovering)
- .animation(.easeOut(duration: 0.12), value: isPressing)
- }
- .buttonStyle(.plain)
- .simultaneousGesture(
- DragGesture(minimumDistance: 0)
- .onChanged { _ in
- isPressing = true
- }
- .onEnded { _ in
- isPressing = false
- }
- )
- .onHover { hovering in
- isHovering = hovering
- if !hovering {
- isPressing = false
- }
- }
- }
- }
- private struct AppIconView: View {
- let app: LauncherApp
- let size: CGFloat
- var body: some View {
- ZStack {
- if app.isCreateNew {
- RoundedRectangle(cornerRadius: 18, style: .continuous)
- .stroke(Color.white.opacity(0.35), lineWidth: 2)
- .background(
- RoundedRectangle(cornerRadius: 18, style: .continuous)
- .fill(Color.white.opacity(0.03))
- )
- Image(systemName: "plus")
- .font(.system(size: size * 0.34, weight: .regular))
- .foregroundStyle(.white.opacity(0.9))
- } else {
- RoundedRectangle(cornerRadius: 18, style: .continuous)
- .fill(LinearGradient(colors: gradientColors, startPoint: .topLeading, endPoint: .bottomTrailing))
- RoundedRectangle(cornerRadius: 18, style: .continuous)
- .stroke(Color.white.opacity(0.08), lineWidth: 1)
- if let iconImage = NSImage(named: app.assetIconName) {
- Image(nsImage: iconImage)
- .resizable()
- .scaledToFit()
- .padding(size * 0.12)
- } else {
- Image(systemName: app.fallbackSymbolName)
- .font(.system(size: size * 0.32, weight: .semibold))
- .foregroundStyle(.white)
- }
- }
- }
- .frame(width: size, height: size)
- }
- private var gradientColors: [Color] {
- switch app.name {
- case let name where name.contains("Gmail"):
- return [Color.red.opacity(0.85), Color.orange.opacity(0.8)]
- case let name where name.contains("Docs"):
- return [Color.blue.opacity(0.9), Color.indigo.opacity(0.85)]
- case let name where name.contains("Drive"):
- return [Color.green.opacity(0.9), Color.blue.opacity(0.75)]
- case let name where name.contains("YouTube"):
- return [Color.red.opacity(0.95), Color.pink.opacity(0.75)]
- case let name where name.contains("Maps"):
- return [Color.green.opacity(0.85), Color.teal.opacity(0.75)]
- default:
- return [Color.blue.opacity(0.85), Color.cyan.opacity(0.7)]
- }
- }
- }
- struct AppDetailView: View {
- let app: LauncherApp
- @Environment(\.dismiss) private var dismiss
- var body: some View {
- ZStack {
- Color(red: 0.09, green: 0.09, blue: 0.11)
- .ignoresSafeArea()
- VStack(spacing: 14) {
- AppIconView(app: app, size: 76)
- Text(app.name)
- .font(.system(size: 22, weight: .semibold))
- .foregroundStyle(.white)
- Text(app.description)
- .font(.system(size: 14))
- .foregroundStyle(.white.opacity(0.82))
- Button("Close") {
- dismiss()
- }
- .buttonStyle(.borderedProminent)
- }
- .padding(24)
- }
- }
- }
|