@@ -1027,10 +1027,10 @@ private struct TouchBarPreviewStrip: View {
10271027 var body : some View {
10281028 ScrollView ( . horizontal, showsIndicators: false ) {
10291029 HStack ( spacing: TB . groupGap) {
1030- ForEach ( settings. widgets ) { kind in
1031- TouchBarWidgetPreview ( kind : kind , theme: settings. theme)
1032- . frame ( width: kind . estimatedWidth, height: TB . stripH)
1033- . id ( " \( kind . id) - \( settings. theme. id) " )
1030+ ForEach ( settings. items ) { item in
1031+ TouchBarWidgetPreview ( item : item , theme: settings. theme)
1032+ . frame ( width: item . estimatedWidth, height: TB . stripH)
1033+ . id ( " \( item . id) - \( settings. theme. id) " )
10341034 }
10351035 }
10361036 . padding ( . horizontal, 2 )
@@ -1043,17 +1043,67 @@ private struct TouchBarPreviewStrip: View {
10431043}
10441044
10451045private struct TouchBarWidgetPreview : View {
1046- let kind : TouchBarWidgetKind
1046+ let item : TouchBarItemConfiguration
10471047 let theme : TouchBarTheme
10481048
10491049 var body : some View {
1050- Image ( nsImage: renderPreview ( ) )
1051- . interpolation ( . none)
1052- . antialiased ( true )
1053- . frame ( width: kind. estimatedWidth, height: TB . stripH)
1050+ Group {
1051+ if item. isBuiltIn {
1052+ Image ( nsImage: renderBuiltInPreview ( ) )
1053+ . interpolation ( . none)
1054+ . antialiased ( true )
1055+ } else {
1056+ customPreview
1057+ }
1058+ }
1059+ . frame ( width: item. estimatedWidth, height: TB . stripH)
10541060 }
10551061
1056- private func renderPreview( ) -> NSImage {
1062+ @ViewBuilder
1063+ private var customPreview : some View {
1064+ switch item {
1065+ case . pinnedApp( let app) :
1066+ iconPreview ( path: app. filePath)
1067+ case . pinnedFolder( let folder) :
1068+ iconPreview ( path: folder. folderPath)
1069+ case . customWidget( let widget) :
1070+ HStack ( spacing: 6 ) {
1071+ Image ( systemName: widget. symbolName)
1072+ . font ( . system( size: 11 , weight: . semibold) )
1073+ Text ( widget. title)
1074+ . font ( . system( size: 11 , weight: . semibold) )
1075+ . lineLimit ( 1 )
1076+ }
1077+ . foregroundStyle ( Color ( nsColor: theme. primaryTextColor) )
1078+ . padding ( . horizontal, 10 )
1079+ . frame ( maxWidth: . infinity, maxHeight: . infinity)
1080+ . background (
1081+ RoundedRectangle ( cornerRadius: 8 , style: . continuous)
1082+ . fill ( Color ( nsColor: theme. pillBackgroundColor) )
1083+ . overlay (
1084+ RoundedRectangle ( cornerRadius: 8 , style: . continuous)
1085+ . stroke ( Color ( nsColor: theme. pillBorderColor) , lineWidth: 1 )
1086+ )
1087+ )
1088+ default :
1089+ EmptyView ( )
1090+ }
1091+ }
1092+
1093+ private func iconPreview( path: String ) -> some View {
1094+ let image = NSWorkspace . shared. icon ( forFile: path)
1095+ image. size = NSSize ( width: 22 , height: 22 )
1096+ return AnyView (
1097+ Image ( nsImage: image)
1098+ . interpolation ( . high)
1099+ . frame ( width: item. estimatedWidth, height: TB . stripH)
1100+ )
1101+ }
1102+
1103+ private func renderBuiltInPreview( ) -> NSImage {
1104+ guard case . builtIn( let kind) = item else {
1105+ return NSImage ( size: NSSize ( width: item. estimatedWidth, height: TB . stripH) )
1106+ }
10571107 let snapshot = TouchBarPreviewFixture . snapshot
10581108 let weatherState = WeatherState . loaded ( TouchBarPreviewFixture . weather)
10591109
@@ -1105,6 +1155,7 @@ private enum TouchBarPreviewFixture {
11051155 ssdPct: 27 ,
11061156 cpuPct: 45 ,
11071157 cpuTempC: 45 ,
1158+ brightness: 0.72 ,
11081159 batPct: 62 ,
11091160 batCharging: false ,
11101161 netUpKBs: 13 ,
@@ -1149,42 +1200,97 @@ private struct TouchBarWidgetRow: View {
11491200 Text ( " \( Int ( kind. estimatedWidth) ) pt " )
11501201 . font ( . system( size: 11 , weight: . bold, design: . monospaced) )
11511202 . foregroundStyle ( . secondary)
1203+ }
1204+ . padding ( . vertical, 4 )
1205+ }
1206+ }
11521207
1153- if isEnabled {
1154- HStack ( spacing: 6 ) {
1155- Button {
1156- settings. moveUp ( kind)
1157- } label: {
1158- Image ( systemName: " arrow.up " )
1159- . font ( . system( size: 11 , weight: . bold) )
1160- . frame ( width: 28 , height: 28 )
1161- }
1162- . buttonStyle ( SoftPressButtonStyle ( ) )
1163- . background ( Color . white. opacity ( 0.06 ) )
1164- . clipShape ( RoundedRectangle ( cornerRadius: 8 , style: . continuous) )
1165-
1166- Button {
1167- settings. moveDown ( kind)
1168- } label: {
1169- Image ( systemName: " arrow.down " )
1170- . font ( . system( size: 11 , weight: . bold) )
1171- . frame ( width: 28 , height: 28 )
1172- }
1173- . buttonStyle ( SoftPressButtonStyle ( ) )
1174- . background ( Color . white. opacity ( 0.06 ) )
1175- . clipShape ( RoundedRectangle ( cornerRadius: 8 , style: . continuous) )
1208+ private struct TouchBarConfiguredItemRow : View {
1209+ @ObservedObject private var settings = TouchBarCustomizationSettings . shared
1210+ let item : TouchBarItemConfiguration
1211+
1212+ var body : some View {
1213+ HStack ( spacing: 12 ) {
1214+ Image ( systemName: leadingSymbol)
1215+ . font ( . system( size: 14 , weight: . semibold) )
1216+ . foregroundStyle ( Color . bdAccent)
1217+ . frame ( width: 18 )
1218+
1219+ VStack ( alignment: . leading, spacing: 3 ) {
1220+ Text ( item. title)
1221+ . font ( . system( size: 13 , weight: . semibold) )
1222+ Text ( item. subtitle)
1223+ . font ( . system( size: 11 , weight: . medium) )
1224+ . foregroundStyle ( . secondary)
1225+ . lineLimit ( 1 )
1226+ }
1227+
1228+ Spacer ( )
1229+
1230+ Text ( " \( Int ( item. estimatedWidth) ) pt " )
1231+ . font ( . system( size: 11 , weight: . bold, design: . monospaced) )
1232+ . foregroundStyle ( . secondary)
1233+
1234+ HStack ( spacing: 6 ) {
1235+ Button {
1236+ settings. moveUp ( item)
1237+ } label: {
1238+ Image ( systemName: " arrow.up " )
1239+ . font ( . system( size: 11 , weight: . bold) )
1240+ . frame ( width: 28 , height: 28 )
1241+ }
1242+ . buttonStyle ( SoftPressButtonStyle ( ) )
1243+ . background ( Color . white. opacity ( 0.06 ) )
1244+ . clipShape ( RoundedRectangle ( cornerRadius: 8 , style: . continuous) )
1245+
1246+ Button {
1247+ settings. moveDown ( item)
1248+ } label: {
1249+ Image ( systemName: " arrow.down " )
1250+ . font ( . system( size: 11 , weight: . bold) )
1251+ . frame ( width: 28 , height: 28 )
1252+ }
1253+ . buttonStyle ( SoftPressButtonStyle ( ) )
1254+ . background ( Color . white. opacity ( 0.06 ) )
1255+ . clipShape ( RoundedRectangle ( cornerRadius: 8 , style: . continuous) )
1256+
1257+ Button {
1258+ settings. remove ( item)
1259+ } label: {
1260+ Image ( systemName: " trash " )
1261+ . font ( . system( size: 11 , weight: . bold) )
1262+ . frame ( width: 28 , height: 28 )
11761263 }
1264+ . buttonStyle ( SoftPressButtonStyle ( ) )
1265+ . background ( Color . red. opacity ( 0.14 ) )
1266+ . clipShape ( RoundedRectangle ( cornerRadius: 8 , style: . continuous) )
11771267 }
11781268 }
11791269 . padding ( . vertical, 4 )
11801270 }
1271+
1272+ private var leadingSymbol : String {
1273+ switch item {
1274+ case . builtIn:
1275+ return " square.grid.2x2 "
1276+ case . pinnedApp:
1277+ return " app.fill "
1278+ case . pinnedFolder:
1279+ return " folder.fill "
1280+ case . customWidget:
1281+ return " terminal.fill "
1282+ }
1283+ }
11811284}
11821285
11831286private struct TouchBarCustomizationPanel : View {
11841287 @StateObject private var settings = TouchBarCustomizationSettings . shared
11851288 @Environment ( \. colorScheme) private var colorScheme
11861289 @State private var weatherAttribution : WeatherAttributionSnapshot ?
11871290 @State private var weatherAttributionError : String ?
1291+ @State private var customTitle = " "
1292+ @State private var customSymbol = " terminal.fill "
1293+ @State private var customCommand = " "
11881294
11891295 private var widthFraction : Double {
11901296 min ( max ( settings. estimatedWidth / TouchBarCustomizationSettings. recommendedTouchBarWidth, 0 ) , 1 )
@@ -1230,6 +1336,21 @@ private struct TouchBarCustomizationPanel: View {
12301336 }
12311337 }
12321338
1339+ DarkCard ( padding: 16 ) {
1340+ VStack ( alignment: . leading, spacing: 10 ) {
1341+ Text ( " Active Items " )
1342+ . font ( . system( size: 14 , weight: . bold) )
1343+ ForEach ( settings. items) { item in
1344+ TouchBarConfiguredItemRow ( item: item)
1345+ if item. id != settings. items. last? . id {
1346+ Rectangle ( )
1347+ . fill ( Color . bdDivider)
1348+ . frame ( height: 1 )
1349+ }
1350+ }
1351+ }
1352+ }
1353+
12331354 DarkCard ( padding: 16 ) {
12341355 VStack ( alignment: . leading, spacing: 12 ) {
12351356 Text ( " Presets " )
@@ -1314,9 +1435,63 @@ private struct TouchBarCustomizationPanel: View {
13141435 }
13151436 }
13161437
1438+ DarkCard ( padding: 16 ) {
1439+ VStack ( alignment: . leading, spacing: 12 ) {
1440+ Text ( " Pinned Items " )
1441+ . font ( . system( size: 14 , weight: . bold) )
1442+
1443+ HStack ( spacing: 10 ) {
1444+ Button ( " Pin Applications " ) {
1445+ let panel = NSOpenPanel ( )
1446+ panel. title = " Choose Applications "
1447+ panel. allowedContentTypes = [ . application]
1448+ panel. allowsMultipleSelection = true
1449+ panel. canChooseDirectories = false
1450+ panel. canChooseFiles = true
1451+ if panel. runModal ( ) == . OK {
1452+ settings. addPinnedApps ( urls: panel. urls)
1453+ }
1454+ }
1455+ . buttonStyle ( SoftPressButtonStyle ( ) )
1456+
1457+ Button ( " Pin Folders " ) {
1458+ let panel = NSOpenPanel ( )
1459+ panel. title = " Choose Folders "
1460+ panel. allowsMultipleSelection = true
1461+ panel. canChooseDirectories = true
1462+ panel. canChooseFiles = false
1463+ if panel. runModal ( ) == . OK {
1464+ settings. addPinnedFolders ( urls: panel. urls)
1465+ }
1466+ }
1467+ . buttonStyle ( SoftPressButtonStyle ( ) )
1468+ }
1469+
1470+ VStack ( alignment: . leading, spacing: 10 ) {
1471+ Text ( " Custom Widget " )
1472+ . font ( . system( size: 12 , weight: . semibold) )
1473+
1474+ TextField ( " Title " , text: $customTitle)
1475+ . textFieldStyle ( . roundedBorder)
1476+ TextField ( " SF Symbol " , text: $customSymbol)
1477+ . textFieldStyle ( . roundedBorder)
1478+ TextField ( " Shell Command " , text: $customCommand)
1479+ . textFieldStyle ( . roundedBorder)
1480+
1481+ Button ( " Add Custom Widget " ) {
1482+ settings. addCustomWidget ( title: customTitle, symbolName: customSymbol, command: customCommand)
1483+ customTitle = " "
1484+ customSymbol = " terminal.fill "
1485+ customCommand = " "
1486+ }
1487+ . buttonStyle ( SoftPressButtonStyle ( ) )
1488+ }
1489+ }
1490+ }
1491+
13171492 DarkCard ( padding: 16 ) {
13181493 VStack ( alignment: . leading, spacing: 10 ) {
1319- Text ( " Widgets " )
1494+ Text ( " Built-In Widgets" )
13201495 . font ( . system( size: 14 , weight: . bold) )
13211496 ForEach ( TouchBarWidgetKind . allCases) { kind in
13221497 TouchBarWidgetRow ( kind: kind)
0 commit comments