Skip to content

Commit 886fe4a

Browse files
Display custom icons for pins (#28)
Icons can be any of the SF Symbols that have a semantic meaning in the MCMap format. In the legacy view, there exists a picker that displays as a popover. In Red Window, this appears as a sheet. Closes #28.
1 parent c189c10 commit 886fe4a

File tree

13 files changed

+159
-13
lines changed

13 files changed

+159
-13
lines changed

Alidade.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

AlidadeUI/Sources/AlidadeUI/NamedLocation/NamedLocationView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public struct NamedLocationView: View {
2828
var systemImage: String
2929
var color: Color
3030

31+
@ScaledMetric private var baseScale = 1.0
32+
3133
init(
3234
name: String,
3335
location: CGPoint,
@@ -59,6 +61,7 @@ public struct NamedLocationView: View {
5961
Image(systemName: systemImage)
6062
.foregroundStyle(.white)
6163
.padding(6)
64+
.frame(width: 28 * baseScale, height: 28 * baseScale)
6265
.background(Circle().fill(color))
6366
VStack(alignment: .leading) {
6467
Text(name)

MCMaps/Legacy/View Models/CartographyPinViewModel.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class CartographyPinViewModel {
5151
/// mirrors the ``MCMapManifestPin/tags`` property, providing an empty set if it wasn't defined before.
5252
var pinTags: Binding<Set<String>>
5353

54+
/// A binding to the pin's icon.
55+
var pinIcon: Binding<CartographyIcon>
56+
5457
/// A label that can be used to describe the pin's current position.
5558
var pinLocationLabel: String {
5659
let location = pin.wrappedValue.position
@@ -99,6 +102,16 @@ class CartographyPinViewModel {
99102
}
100103
file.wrappedValue.pins[index].tags = newValue
101104
}
105+
106+
self.pinIcon = Binding {
107+
guard file.wrappedValue.supportedFeatures.contains(.pinIcons) else {
108+
return .default
109+
}
110+
return file.wrappedValue.pins[index].icon ?? .default
111+
} set: { newValue in
112+
guard file.wrappedValue.supportedFeatures.contains(.pinIcons) else { return }
113+
file.wrappedValue.pins[index].icon = newValue
114+
}
102115
}
103116

104117
/// Retrieves data blobs for the images associated with this pin.

MCMaps/Legacy/Views/CartographyOrnamentMap.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ struct CartographyOrnamentMap: View {
9191
Marker(
9292
location: pin.position,
9393
title: pin.name,
94-
color: pin.color?.swiftUIColor ?? Color.accentColor
94+
color: pin.color?.swiftUIColor ?? Color.accentColor,
95+
systemImage: pin.icon?.resolveSFSymbol(in: .pin) ?? "mappin"
9596
)
9697
}
9798
integrationAnnotations

MCMaps/Legacy/Views/Pins/CartographyMapPinDetailView.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct CartographyMapPinDetailView: View {
3232
static let photosOnboarding = PinPhotoOnboardingTip()
3333
}
3434

35+
@State private var iconPicker = false
3536
@State private var photoItem: PhotosPickerItem?
3637
@State private var photoToUpdate: Image?
3738

@@ -43,11 +44,25 @@ struct CartographyMapPinDetailView: View {
4344
var body: some View {
4445
List {
4546
VStack(alignment: .leading) {
46-
TextField("Name", text: viewModel.pin.name)
47-
.allowsTightening(true)
48-
.font(.title)
49-
.bold()
50-
.textFieldStyle(.plain)
47+
HStack {
48+
TextField("Name", text: viewModel.pin.name)
49+
.allowsTightening(true)
50+
.bold()
51+
.textFieldStyle(.plain)
52+
.font(.title)
53+
Button {
54+
iconPicker.toggle()
55+
} label: {
56+
Image(cartographyIcon: viewModel.pinIcon.wrappedValue, in: .pin)
57+
.font(.title)
58+
}
59+
.buttonStyle(.plain)
60+
.popover(isPresented: $iconPicker) {
61+
CartographyIconPicker(icon: viewModel.pinIcon, context: .pin)
62+
.frame(maxHeight: 450)
63+
}
64+
}
65+
5166
HStack {
5267
Label(viewModel.pinLocationLabel, systemImage: "tree")
5368
.font(.subheadline)

MCMaps/Red Window/Views/Detail/RedWindowPinDetailView.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ struct RedWindowPinDetailView: View {
3333
@State private var center = CGPoint.zero
3434
@State private var color = CartographyMapPin.Color.blue
3535
@State private var editMode = false
36+
@State private var icon = CartographyIcon.default
37+
38+
@State private var presentIconPicker = false
3639
@State private var presentTagEditor = false
3740

3841
@State private var photosPickerItem: PhotosPickerItem?
@@ -66,6 +69,9 @@ struct RedWindowPinDetailView: View {
6669
if let pinTags = pin.tags {
6770
tags = pinTags
6871
}
72+
if let pinIcon = pin.icon {
73+
icon = pinIcon
74+
}
6975
}
7076
.onChange(of: color) { _, newValue in
7177
pin.color = newValue
@@ -74,6 +80,10 @@ struct RedWindowPinDetailView: View {
7480
guard file.supportedFeatures.contains(.pinTagging) else { return }
7581
pin.tags = newValue
7682
}
83+
.onChange(of: icon) { _, newValue in
84+
guard file.supportedFeatures.contains(.pinIcons) else { return }
85+
pin.icon = newValue
86+
}
7787
.onChange(of: photosPickerItem) { _, newValue in
7888
guard let item = newValue else { return }
7989
Task { await loadPickerSelection(item) }
@@ -96,6 +106,12 @@ struct RedWindowPinDetailView: View {
96106
#endif
97107
}
98108
}
109+
.sheet(isPresented: $presentIconPicker) {
110+
NavigationStack {
111+
CartographyIconPicker(icon: $icon, context: .pin)
112+
.navigationTitle("Select an Icon")
113+
}
114+
}
99115
.toolbar {
100116
ToolbarItem {
101117
editButton
@@ -135,6 +151,12 @@ struct RedWindowPinDetailView: View {
135151
}
136152
}
137153

154+
ToolbarItem {
155+
Button("Pin Icon", systemImage: "heart.text.square") {
156+
presentIconPicker.toggle()
157+
}
158+
}
159+
138160
#if RED_WINDOW
139161
if #available(macOS 16, iOS 19, *) {
140162
ToolbarSpacer(.fixed)
@@ -185,12 +207,13 @@ struct RedWindowPinDetailView: View {
185207
Divider()
186208
Group {
187209
if let world = try? MinecraftWorld(version: "1.21.3", seed: 184_719_632_014) {
188-
MinecraftMap(world: world, centerCoordinate: $center) {
210+
MinecraftMap(world: world, centerCoordinate: .constant(pin.position)) {
189211
Marker(location: .zero, title: "#nodraw")
190212
Marker(
191213
location: pin.position,
192214
title: pin.name,
193-
color: pin.color?.swiftUIColor ?? .accent
215+
color: pin.color?.swiftUIColor ?? .accent,
216+
systemImage: pin.icon?.resolveSFSymbol(in: .pin) ?? "mappin"
194217
)
195218
}
196219
.mapColorScheme(.natural)

MCMaps/Red Window/Views/Library/RedWindowLibraryGridCell.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ struct RedWindowLibraryGridCell: View {
4646
.clipped()
4747
.clipShape(RoundedRectangle(cornerRadius: 10))
4848
.overlay {
49-
Image(systemName: "mappin")
49+
Image(cartographyIcon: pin.icon ?? .default, in: .pin)
5050
.font(.title)
5151
.foregroundStyle(.white)
5252
}

MCMaps/Red Window/Views/Library/RedWindowPinLibraryListView.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import CubiomesKit
9+
import MCMap
910
import SwiftUI
1011

1112
/// A collection view that displays player-created pins in a list layout.
@@ -29,6 +30,7 @@ struct RedWindowPinLibraryListView: View {
2930
var pins: IndexedPinCollection
3031

3132
@State private var selection = Set<IndexedPinCollection.Element.ID>()
33+
@ScaledMetric private var baseScale = 1.0
3234

3335
var body: some View {
3436
Table(pins, selection: $selection) {
@@ -43,8 +45,9 @@ struct RedWindowPinLibraryListView: View {
4345
}
4446
}
4547
} icon: {
46-
Image(systemName: "mappin")
48+
Image(cartographyIcon: val.content.icon ?? .default, in: .pin)
4749
.foregroundStyle(val.content.color?.swiftUIColor ?? .accent)
50+
.frame(width: 28 * baseScale)
4851
}
4952
}
5053
TableColumn("Location", value: \.content.position.accessibilityReadout)

MCMaps/Red Window/Views/Map/RedWindowMapView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ struct RedWindowMapView: View {
7575
Marker(
7676
location: mapPin.position,
7777
title: mapPin.name,
78-
color: mapPin.color?.swiftUIColor ?? .accent
78+
color: mapPin.color?.swiftUIColor ?? .accent,
79+
systemImage: mapPin.icon?.resolveSFSymbol(in: .pin) ?? "mappin"
7980
)
8081
}
8182
integrationAnnotations

MCMaps/Red Window/Views/RedWindowContentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ struct RedWindowContentView: View {
112112
ForEach(IndexedPinCollection(file.pins)) { (mapPin: IndexedPinCollection.Element) in
113113
Tab(
114114
mapPin.content.name,
115-
systemImage: "mappin",
115+
systemImage: mapPin.content.icon?.resolveSFSymbol(in: .pin) ?? "mappin",
116116
value: RedWindowRoute.pin(mapPin.content.id)
117117
) {
118118
NavigationStack {

0 commit comments

Comments
 (0)