Skip to content

Commit

Permalink
Merge pull request #5969 from js-john/icon-selector
Browse files Browse the repository at this point in the history
Issue #5937 - improved VM icon selector UX
  • Loading branch information
osy authored Apr 30, 2024
2 parents 906fafc + ab69e0a commit 9f8c1a8
Showing 1 changed file with 140 additions and 51 deletions.
191 changes: 140 additions & 51 deletions Platform/Shared/VMConfigInfoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,20 @@ struct VMConfigInfoView: View {
switch iconStyle {
case .custom:
#if os(macOS)
Button(action: { imageSelectVisible.toggle() }, label: {
VStack {
IconPreview(url: config.iconURL)
}).fileImporter(isPresented: $imageSelectVisible, allowedContentTypes: [.image]) { result in
switch result {
case .success(let url):
imageCustomSelected(url: url)
case .failure:
break
Button(action: { imageSelectVisible.toggle() }, label: {
Text("Choose")
}).fileImporter(isPresented: $imageSelectVisible, allowedContentTypes: [.image]) { result in
switch result {
case .success(let url):
imageCustomSelected(url: url)
case .failure:
break
}
}
}.buttonStyle(.plain)
}
.frame(width: 90)
#else
Button(action: { imageSelectVisible.toggle() }, label: {
IconPreview(url: config.iconURL)
Expand All @@ -141,18 +145,31 @@ struct VMConfigInfoView: View {
}.buttonStyle(.plain)
#endif
case .operatingSystem:
Button(action: { imageSelectVisible.toggle() }, label: {
#if os(macOS)
VStack {
IconPreview(url: config.iconURL)
}).popover(isPresented: $imageSelectVisible, arrowEdge: .bottom) {
IconSelect(onIconSelected: imageSelected)
}.buttonStyle(.plain)
Button(action: { imageSelectVisible.toggle() }, label: {
Text("Choose")
}).popover(isPresented: $imageSelectVisible, arrowEdge: .bottom) {
IconSelect(current: config.iconURL, onIconSelected: imageSelected)
}
}
.frame(width: 90)
#else
IconSelect(current: config.iconURL, onIconSelected: imageSelected)
#endif
default:
#if os(macOS)
Image(systemName: "desktopcomputer")
.resizable()
.frame(width: 30.0, height: 30.0)
.padding()
.foregroundColor(Color(NSColor.disabledControlTextColor))
VStack {
Image(systemName: "desktopcomputer")
.resizable()
.frame(width: 30.0, height: 30.0)
.foregroundColor(Color(NSColor.disabledControlTextColor))
Button {} label: {
Text("Choose")
}.disabled(true)
}
.frame(width: 90)
#else
EmptyView()
#endif
Expand Down Expand Up @@ -190,17 +207,23 @@ private struct IconPreview: View {
Spacer()
#endif
Logo(logo: PlatformImage(contentsOfURL: url))
.padding()
#if !os(macOS)
Spacer()
#endif
}
}
}

#if os(macOS)
let iconGridSize: CGFloat = 80
#else
let iconGridSize: CGFloat = 100
#endif

private struct IconSelect: View {
let current: URL?
let onIconSelected: (URL) -> Void
private let gridLayout = [GridItem(.adaptive(minimum: 60))]
private let gridLayout = [GridItem(.adaptive(minimum: iconGridSize))]
private var icons: [URL] {
let paths = Bundle.main.paths(forResourcesOfType: "png", inDirectory: "Icons")
let urls = paths.map({ URL(fileURLWithPath: $0) })
Expand All @@ -210,6 +233,7 @@ private struct IconSelect: View {
}

#if os(macOS)

typealias PlatformImage = NSImage
#else
typealias PlatformImage = UIImage
Expand All @@ -218,44 +242,35 @@ private struct IconSelect: View {
struct IconSelectModifier: ViewModifier {
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>

#if os(macOS)
let isPhone: Bool = false
#else
var isPhone: Bool {
UIDevice.current.userInterfaceIdiom == .phone
}
#endif

func body(content: Content) -> some View {
if isPhone {
return AnyView(
VStack {
HStack {
Spacer()
Button(action: { presentationMode.wrappedValue.dismiss() }, label: {
Text("Cancel")
}).padding()
}
ScrollView {
content.padding(.bottom)
}
}
)
} else {
return AnyView(
ScrollView {
content.padding([.top, .bottom])
}.frame(width: 400, height: 400)
)
}
#if os(macOS)
return AnyView(
ScrollView {
content.padding(16)
}.frame(width: 480, height: 400)
)
#else
return AnyView(content)
#endif
}
}

var body: some View {
LazyVGrid(columns: gridLayout, spacing: 30) {
LazyVGrid(columns: gridLayout, spacing: 0) {
ForEach(icons, id: \.self) { icon in
Button(action: { onIconSelected(icon) }, label: {
Logo(logo: PlatformImage(contentsOfURL: icon))
VStack {
Logo(logo: PlatformImage(contentsOfURL: icon))
Text(iconToTitle(icon))
.lineLimit(2)
.font(.footnote)
}
.padding(8)
.frame(width: iconGridSize, height: iconGridSize)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(current == icon ? Color.accentColor : Color.clear, lineWidth: 2)
)
}).buttonStyle(.plain)
}
}.modifier(IconSelectModifier())
Expand All @@ -271,9 +286,83 @@ struct VMConfigInfoView_Previews: PreviewProvider {
#if os(macOS)
.scrollable()
#endif
IconSelect() { _ in
IconSelect(current: nil) { _ in

}
}
}
}

private func iconToTitle(_ icon: URL?) -> LocalizedStringKey {
guard let fileName = icon?.deletingPathExtension().lastPathComponent else {
return "Custom"
}
return ICON_TITLE_MAP[fileName] ?? "Custom"
}

private let ICON_TITLE_MAP: [String: LocalizedStringKey] = [
"AIX": "AIX",
"IOS": "iOS",
"Windows7": "Windows 7",
"almalinux": "AlmaLinux",
"alpine": "Alpine",
"amigaos": "AmigaOS",
"android": "Android",
"apple-tv": "Apple TV",
"arch-linux": "Arch Linux",
"backtrack": "BackTrack",
"bada": "Bada",
"beos": "BeOS",
"centos": "CentOS",
"chrome-os": "Chrome OS",
"cyanogenmod": "CyanogenMod",
"debian": "Debian",
"elementary-os": "Elementary OS",
"fedora": "Fedora",
"firefox-os": "Firefox OS",
"freebsd": "FreeBSD",
"gentoo": "Gentoo",
"haiku-os": "Haiku OS",
"hp-ux": "HP-UX",
"kaios": "KaiOS",
"knoppix": "Knoppix",
"kubuntu": "Kubuntu",
"linux": "Linux",
"lubuntu": "Lubuntu",
"mac": "macOS",
"maemo": "Maemo",
"mandriva": "Mandriva",
"meego": "MeeGo",
"mint": "Linux Mint",
"netbsd": "NetBSD",
"nintendo": "Nintendo",
"nixos": "NixOS",
"openbsd": "OpenBSD",
"openwrt": "OpenWrt",
"os2": "OS/2",
"palmos": "Palm OS",
"playstation-portable": "PlayStation Portable",
"playstation": "PlayStation",
"pop-os": "Pop!_OS",
"red-hat": "Red Hat",
"remix-os": "Remix OS",
"risc-os": "RISC OS",
"sabayon": "Sabayon",
"sailfish-os": "Sailfish OS",
"slackware": "Slackware",
"solaris": "Solaris",
"suse": "openSUSE",
"syllable": "Syllable",
"symbian": "Symbian",
"threadx": "ThreadX",
"tizen": "Tizen",
"ubuntu": "Ubuntu",
"webos": "webOS",
"windows-11": "Windows 11",
"windows-9x": "Windows 9x",
"windows-xp": "Windows XP",
"windows": "Windows",
"xbox": "Xbox",
"xubuntu": "Xubuntu",
"yunos": "YunOS",
]

0 comments on commit 9f8c1a8

Please sign in to comment.