From f6d596ad1d9dc1d3e3bac0cf20131f0c072f93ff Mon Sep 17 00:00:00 2001 From: Casey Fleser Date: Tue, 7 Mar 2023 06:41:47 -0600 Subject: [PATCH] Switching to node based design. See NodeItems project --- SimDirs.xcodeproj/project.pbxproj | 114 ++++++++---- SimDirs/ContentView.swift | 30 +-- SimDirs/Model/SimApp.swift | 8 - SimDirs/Model/SimDevice.swift | 18 +- SimDirs/Model/SimDeviceType.swift | 6 - SimDirs/Model/SimPlatform.swift | 6 - SimDirs/Model/SimProductFamily.swift | 6 - SimDirs/Model/SimRuntime.swift | 8 - SimDirs/Node/Conforming/SimApp+Node.swift | 33 ++++ SimDirs/Node/Conforming/SimDevice+Node.swift | 31 ++++ .../Node/Conforming/SimDeviceType+Node.swift | 20 ++ .../Node/Conforming/SimPlatform+Node.swift | 21 +++ .../Conforming/SimProductFamily+Node.swift | 21 +++ SimDirs/Node/Conforming/SimRuntime+Node.swift | 24 +++ SimDirs/Node/FilteredNode.swift | 73 ++++++++ SimDirs/Node/Node.swift | 158 ++++++++++++++++ SimDirs/Node/NodeListBuilder.swift | 173 ++++++++++++++++++ SimDirs/Node/Views/FilteredNodeView.swift | 95 ++++++++++ SimDirs/Node/Views/NodeLabel.swift | 61 ++++++ .../Views/NodeView.swift} | 43 ++--- SimDirs/Presentation/SourceFilter.swift | 28 ++- SimDirs/Presentation/SourceItem.swift | 95 ---------- SimDirs/Presentation/SourceItemData.swift | 50 ----- SimDirs/Presentation/SourceState.swift | 127 +++---------- SimDirs/Views/Model Views/AppContent.swift | 2 - SimDirs/Views/Model Views/AppHeader.swift | 4 - SimDirs/Views/Model Views/DeviceContent.swift | 2 - SimDirs/Views/Model Views/DeviceHeader.swift | 4 - .../Views/Model Views/DeviceTypeContent.swift | 4 - .../Views/Model Views/DeviceTypeHeader.swift | 4 - .../Views/Model Views/RuntimeContent.swift | 4 - SimDirs/Views/Model Views/RuntimeHeader.swift | 4 - .../SourceItem Views/SourceItemGroup.swift | 61 ------ .../SourceItem Views/SourceItemImage.swift | 54 ------ .../SourceItem Views/SourceItemLabel.swift | 31 ---- .../SourceItem Views/SourceItemLink.swift | 34 ---- SimDirs/Views/ToolbarMenu.swift | 11 +- 37 files changed, 868 insertions(+), 600 deletions(-) create mode 100644 SimDirs/Node/Conforming/SimApp+Node.swift create mode 100644 SimDirs/Node/Conforming/SimDevice+Node.swift create mode 100644 SimDirs/Node/Conforming/SimDeviceType+Node.swift create mode 100644 SimDirs/Node/Conforming/SimPlatform+Node.swift create mode 100644 SimDirs/Node/Conforming/SimProductFamily+Node.swift create mode 100644 SimDirs/Node/Conforming/SimRuntime+Node.swift create mode 100644 SimDirs/Node/FilteredNode.swift create mode 100644 SimDirs/Node/Node.swift create mode 100644 SimDirs/Node/NodeListBuilder.swift create mode 100644 SimDirs/Node/Views/FilteredNodeView.swift create mode 100644 SimDirs/Node/Views/NodeLabel.swift rename SimDirs/{Views/SourceItem Views/SourceItemContent.swift => Node/Views/NodeView.swift} (57%) delete mode 100644 SimDirs/Presentation/SourceItem.swift delete mode 100644 SimDirs/Presentation/SourceItemData.swift delete mode 100644 SimDirs/Views/SourceItem Views/SourceItemGroup.swift delete mode 100644 SimDirs/Views/SourceItem Views/SourceItemImage.swift delete mode 100644 SimDirs/Views/SourceItem Views/SourceItemLabel.swift delete mode 100644 SimDirs/Views/SourceItem Views/SourceItemLink.swift diff --git a/SimDirs.xcodeproj/project.pbxproj b/SimDirs.xcodeproj/project.pbxproj index 02693b6..ec9d306 100644 --- a/SimDirs.xcodeproj/project.pbxproj +++ b/SimDirs.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */; }; C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4B2861E37900C2EF35 /* AppHeader.swift */; }; C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4D2861E4E400C2EF35 /* AppContent.swift */; }; - C90BCE502861E9D000C2EF35 /* SourceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4F2861E9D000C2EF35 /* SourceState.swift */; }; C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */; }; C90DCC142896AAAA0072E403 /* ContentHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90DCC132896AAAA0072E403 /* ContentHeader.swift */; }; C90DCC162896B0370072E403 /* AppearancePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90DCC152896B0370072E403 /* AppearancePicker.swift */; }; @@ -21,6 +20,19 @@ C927A0DB2846502300533D66 /* PathActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0DA2846502300533D66 /* PathActions.swift */; }; C95CC0F828B2411700928FAE /* AppearanceButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95CC0F728B2411700928FAE /* AppearanceButtonStyle.swift */; }; C95CC0FA28B2414900928FAE /* SystemIconButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95CC0F928B2414900928FAE /* SystemIconButtonStyle.swift */; }; + C966876C29B7641F007BB3F5 /* FilteredNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966875E29B7641F007BB3F5 /* FilteredNode.swift */; }; + C966876D29B7641F007BB3F5 /* SimApp+Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876029B7641F007BB3F5 /* SimApp+Node.swift */; }; + C966876E29B7641F007BB3F5 /* SimProductFamily+Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876129B7641F007BB3F5 /* SimProductFamily+Node.swift */; }; + C966876F29B7641F007BB3F5 /* SimPlatform+Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876229B7641F007BB3F5 /* SimPlatform+Node.swift */; }; + C966877029B7641F007BB3F5 /* SimDeviceType+Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876329B7641F007BB3F5 /* SimDeviceType+Node.swift */; }; + C966877129B7641F007BB3F5 /* SimRuntime+Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876429B7641F007BB3F5 /* SimRuntime+Node.swift */; }; + C966877229B7641F007BB3F5 /* SimDevice+Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876529B7641F007BB3F5 /* SimDevice+Node.swift */; }; + C966877329B7641F007BB3F5 /* NodeListBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876629B7641F007BB3F5 /* NodeListBuilder.swift */; }; + C966877429B7641F007BB3F5 /* NodeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876829B7641F007BB3F5 /* NodeLabel.swift */; }; + C966877529B7641F007BB3F5 /* FilteredNodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876929B7641F007BB3F5 /* FilteredNodeView.swift */; }; + C966877629B7641F007BB3F5 /* NodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876A29B7641F007BB3F5 /* NodeView.swift */; }; + C966877729B7641F007BB3F5 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C966876B29B7641F007BB3F5 /* Node.swift */; }; + C966877829B76533007BB3F5 /* SourceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4F2861E9D000C2EF35 /* SourceState.swift */; }; C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9779741284F6DE000706DFB /* ToolbarMenu.swift */; }; C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F858283B9F9000D491F4 /* SimDirsApp.swift */; }; C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F85A283B9F9000D491F4 /* ContentView.swift */; }; @@ -35,13 +47,6 @@ C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87A283E40C800D491F4 /* SimCtl.swift */; }; C9BF5232289FE95D00BDDC91 /* DescriptiveToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */; }; C9BF5234289FE99600BDDC91 /* DescriptiveToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BF5233289FE99600BDDC91 /* DescriptiveToggleStyle.swift */; }; - C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C24285C8C0C0044A279 /* SourceItemData.swift */; }; - C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C28285C8C4B0044A279 /* SourceItem.swift */; }; - C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */; }; - C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */; }; - C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */; }; - C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */; }; - C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */; }; C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */; }; C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */; }; C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */; }; @@ -64,6 +69,18 @@ C927A0DA2846502300533D66 /* PathActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathActions.swift; sourceTree = ""; }; C95CC0F728B2411700928FAE /* AppearanceButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceButtonStyle.swift; sourceTree = ""; }; C95CC0F928B2414900928FAE /* SystemIconButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemIconButtonStyle.swift; sourceTree = ""; }; + C966875E29B7641F007BB3F5 /* FilteredNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilteredNode.swift; sourceTree = ""; }; + C966876029B7641F007BB3F5 /* SimApp+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimApp+Node.swift"; sourceTree = ""; }; + C966876129B7641F007BB3F5 /* SimProductFamily+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimProductFamily+Node.swift"; sourceTree = ""; }; + C966876229B7641F007BB3F5 /* SimPlatform+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimPlatform+Node.swift"; sourceTree = ""; }; + C966876329B7641F007BB3F5 /* SimDeviceType+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimDeviceType+Node.swift"; sourceTree = ""; }; + C966876429B7641F007BB3F5 /* SimRuntime+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimRuntime+Node.swift"; sourceTree = ""; }; + C966876529B7641F007BB3F5 /* SimDevice+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimDevice+Node.swift"; sourceTree = ""; }; + C966876629B7641F007BB3F5 /* NodeListBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeListBuilder.swift; sourceTree = ""; }; + C966876829B7641F007BB3F5 /* NodeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeLabel.swift; sourceTree = ""; }; + C966876929B7641F007BB3F5 /* FilteredNodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilteredNodeView.swift; sourceTree = ""; }; + C966876A29B7641F007BB3F5 /* NodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeView.swift; sourceTree = ""; }; + C966876B29B7641F007BB3F5 /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; C9779741284F6DE000706DFB /* ToolbarMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarMenu.swift; sourceTree = ""; }; C982F855283B9F9000D491F4 /* SimDirs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimDirs.app; sourceTree = BUILT_PRODUCTS_DIR; }; C982F858283B9F9000D491F4 /* SimDirsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimDirsApp.swift; sourceTree = ""; }; @@ -80,13 +97,6 @@ C982F87A283E40C800D491F4 /* SimCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimCtl.swift; sourceTree = ""; }; C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptiveToggle.swift; sourceTree = ""; }; C9BF5233289FE99600BDDC91 /* DescriptiveToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptiveToggleStyle.swift; sourceTree = ""; }; - C9D73C24285C8C0C0044A279 /* SourceItemData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemData.swift; sourceTree = ""; }; - C9D73C28285C8C4B0044A279 /* SourceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItem.swift; sourceTree = ""; }; - C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemGroup.swift; sourceTree = ""; }; - C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLink.swift; sourceTree = ""; }; - C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemContent.swift; sourceTree = ""; }; - C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemImage.swift; sourceTree = ""; }; - C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLabel.swift; sourceTree = ""; }; C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeContent.swift; sourceTree = ""; }; C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeHeader.swift; sourceTree = ""; }; C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeContent.swift; sourceTree = ""; }; @@ -115,13 +125,50 @@ path = Styles; sourceTree = ""; }; + C966875D29B7641F007BB3F5 /* Node */ = { + isa = PBXGroup; + children = ( + C966875F29B7641F007BB3F5 /* Conforming */, + C966876729B7641F007BB3F5 /* Views */, + C966875E29B7641F007BB3F5 /* FilteredNode.swift */, + C966876629B7641F007BB3F5 /* NodeListBuilder.swift */, + C966876B29B7641F007BB3F5 /* Node.swift */, + ); + path = Node; + sourceTree = ""; + }; + C966875F29B7641F007BB3F5 /* Conforming */ = { + isa = PBXGroup; + children = ( + C966876029B7641F007BB3F5 /* SimApp+Node.swift */, + C966876129B7641F007BB3F5 /* SimProductFamily+Node.swift */, + C966876229B7641F007BB3F5 /* SimPlatform+Node.swift */, + C966876329B7641F007BB3F5 /* SimDeviceType+Node.swift */, + C966876429B7641F007BB3F5 /* SimRuntime+Node.swift */, + C966876529B7641F007BB3F5 /* SimDevice+Node.swift */, + ); + path = Conforming; + sourceTree = ""; + }; + C966876729B7641F007BB3F5 /* Views */ = { + isa = PBXGroup; + children = ( + C966876829B7641F007BB3F5 /* NodeLabel.swift */, + C966876929B7641F007BB3F5 /* FilteredNodeView.swift */, + C966876A29B7641F007BB3F5 /* NodeView.swift */, + ); + path = Views; + sourceTree = ""; + }; C982F84C283B9F9000D491F4 = { isa = PBXGroup; children = ( C982F857283B9F9000D491F4 /* SimDirs */, C982F856283B9F9000D491F4 /* Products */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; }; C982F856283B9F9000D491F4 /* Products */ = { isa = PBXGroup; @@ -138,6 +185,7 @@ C982F85A283B9F9000D491F4 /* ContentView.swift */, C927A0D82846414900533D66 /* Helpers.swift */, C982F867283BA09B00D491F4 /* Model */, + C966875D29B7641F007BB3F5 /* Node */, C9D73C23285C8B3B0044A279 /* Presentation */, C982F881283E7F0400D491F4 /* Views */, C982F85C283B9F9200D491F4 /* Assets.xcassets */, @@ -174,7 +222,6 @@ isa = PBXGroup; children = ( C95CC0F628B240F500928FAE /* Styles */, - C9DD54C12860935300D46AB3 /* SourceItem Views */, C9DD54CC2860992200D46AB3 /* Model Views */, C90DCC152896B0370072E403 /* AppearancePicker.swift */, C90DCC132896AAAA0072E403 /* ContentHeader.swift */, @@ -190,26 +237,12 @@ C9D73C23285C8B3B0044A279 /* Presentation */ = { isa = PBXGroup; children = ( - C9D73C28285C8C4B0044A279 /* SourceItem.swift */, C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */, - C9D73C24285C8C0C0044A279 /* SourceItemData.swift */, C90BCE4F2861E9D000C2EF35 /* SourceState.swift */, ); path = Presentation; sourceTree = ""; }; - C9DD54C12860935300D46AB3 /* SourceItem Views */ = { - isa = PBXGroup; - children = ( - C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */, - C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */, - C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */, - C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */, - C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */, - ); - path = "SourceItem Views"; - sourceTree = ""; - }; C9DD54CC2860992200D46AB3 /* Model Views */ = { isa = PBXGroup; children = ( @@ -295,30 +328,35 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */, - C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */, + C966877729B7641F007BB3F5 /* Node.swift in Sources */, + C966877529B7641F007BB3F5 /* FilteredNodeView.swift in Sources */, + C966877229B7641F007BB3F5 /* SimDevice+Node.swift in Sources */, + C966876C29B7641F007BB3F5 /* FilteredNode.swift in Sources */, + C966877029B7641F007BB3F5 /* SimDeviceType+Node.swift in Sources */, C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */, C927A0DB2846502300533D66 /* PathActions.swift in Sources */, C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */, + C966876F29B7641F007BB3F5 /* SimPlatform+Node.swift in Sources */, C90DCC142896AAAA0072E403 /* ContentHeader.swift in Sources */, C9BF5232289FE95D00BDDC91 /* DescriptiveToggle.swift in Sources */, - C90BCE502861E9D000C2EF35 /* SourceState.swift in Sources */, + C966877629B7641F007BB3F5 /* NodeView.swift in Sources */, + C966877129B7641F007BB3F5 /* SimRuntime+Node.swift in Sources */, C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */, + C966877829B76533007BB3F5 /* SourceState.swift in Sources */, C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */, - C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */, C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */, C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */, C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */, C90DCC162896B0370072E403 /* AppearancePicker.swift in Sources */, C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */, C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */, + C966877429B7641F007BB3F5 /* NodeLabel.swift in Sources */, C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */, C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */, C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */, - C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */, C9BF5234289FE99600BDDC91 /* DescriptiveToggleStyle.swift in Sources */, + C966876D29B7641F007BB3F5 /* SimApp+Node.swift in Sources */, C982F873283CE9AD00D491F4 /* SimDeviceType.swift in Sources */, - C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */, C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */, C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */, C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */, @@ -328,10 +366,10 @@ C982F86B283BA22100D491F4 /* SimPlatform.swift in Sources */, C927A0D92846414900533D66 /* Helpers.swift in Sources */, C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */, - C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */, - C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */, + C966877329B7641F007BB3F5 /* NodeListBuilder.swift in Sources */, C982F879283D042E00D491F4 /* SimModel.swift in Sources */, C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */, + C966876E29B7641F007BB3F5 /* SimProductFamily+Node.swift in Sources */, C95CC0F828B2411700928FAE /* AppearanceButtonStyle.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SimDirs/ContentView.swift b/SimDirs/ContentView.swift index 702f10b..e8f659d 100644 --- a/SimDirs/ContentView.swift +++ b/SimDirs/ContentView.swift @@ -8,7 +8,8 @@ import SwiftUI struct ContentView: View { - @ObservedObject var state : SourceState + @ObservedObject var state : SourceState + @State var filter = SourceFilter.restore() init(model: SimModel) { state = SourceState(model: model) @@ -17,32 +18,13 @@ struct ContentView: View { var body: some View { VStack { NavigationView { - List(selection: $state.selection) { - Divider() - - switch state.base { - case .placeholder: - Text("Placeholder") - - case let .device(_, item): - ForEach(item.visibleChildren) { - SourceItemGroup(item: $0, selection: $state.selection) - } - - case let .runtime(_, item): - ForEach(item.visibleChildren) { - SourceItemGroup(item: $0, selection: $state.selection) - } - } - } - .toolbar { - ToolbarItem { ToolbarMenu(state: state) } - } - .frame(minWidth: 200) + FilteredNodeView(filter: $filter) { state.items } + .id(state.style) + .toolbar { ToolbarItem { ToolbarMenu(state: state, filter: $filter) } } + .frame(minWidth: 200) Image("Icon-256") // Initial View } - .searchable(text: $state.filter.searchTerm, placement: .sidebar) } } } diff --git a/SimDirs/Model/SimApp.swift b/SimDirs/Model/SimApp.swift index a777e50..4e16063 100644 --- a/SimDirs/Model/SimApp.swift +++ b/SimDirs/Model/SimApp.swift @@ -118,11 +118,3 @@ extension SimApp { var isOn : Bool { self == .launched } } } - -extension SimApp: SourceItemData { - var title : String { return displayName } - var headerTitle : String { "App: \(title)" } - var imageDesc : SourceImageDesc { nsIcon.map { .icon(nsImage: $0) } ?? .symbol(systemName: "questionmark.app.dashed") } - - var optionTrait : SourceFilter.Options { .withApps } -} diff --git a/SimDirs/Model/SimDevice.swift b/SimDirs/Model/SimDevice.swift index 1fc8f2e..5951e0b 100644 --- a/SimDirs/Model/SimDevice.swift +++ b/SimDirs/Model/SimDevice.swift @@ -40,6 +40,7 @@ class SimDevice: ObservableObject, Decodable { let dataPathSize : Int let logPath : String let deviceTypeIdentifier : String + var deviceType : SimDeviceType? var deviceModel : String? var apps = [SimApp]() var dataURL : URL { URL(fileURLWithPath: dataPath) } @@ -313,14 +314,17 @@ extension SimDevice { } } -extension SimDevice: SourceItemData { - var title : String { return name } - var headerTitle : String { "Device: \(title)" } - var isEnabled : Bool { isBooted } - var imageDesc : SourceImageDesc { .symbol(systemName: "questionmark.circle", color: isAvailable ? .green : .red) } -} - extension Array where Element == SimDevice { + func linkingDeviceType(_ deviceType: SimDeviceType) -> Self { + let devices = filter { $0.isDeviceOfType(deviceType) } + + for device in devices { + device.deviceType = deviceType + } + + return devices + } + func of(deviceType: SimDeviceType) -> Self { filter { $0.isDeviceOfType(deviceType) } } diff --git a/SimDirs/Model/SimDeviceType.swift b/SimDirs/Model/SimDeviceType.swift index 58356a5..9eadb26 100644 --- a/SimDirs/Model/SimDeviceType.swift +++ b/SimDirs/Model/SimDeviceType.swift @@ -39,12 +39,6 @@ struct SimDeviceType: Decodable { } } -extension SimDeviceType: SourceItemData { - var title : String { return name } - var headerTitle : String { "Device Type: \(title)" } - var imageDesc : SourceImageDesc { .symbol(systemName: productFamily.symbolName) } -} - extension Array where Element == SimDeviceType { func supporting(productFamily: SimProductFamily) -> Self { filter { $0.supports(productFamily: productFamily) } diff --git a/SimDirs/Model/SimPlatform.swift b/SimDirs/Model/SimPlatform.swift index eeb68ea..0e9f800 100644 --- a/SimDirs/Model/SimPlatform.swift +++ b/SimDirs/Model/SimPlatform.swift @@ -22,9 +22,3 @@ enum SimPlatform: String, Decodable { } } } - -extension SimPlatform: SourceItemData { - var title : String { self.rawValue } - var headerTitle : String { "Platform: \(title)" } - var imageDesc : SourceImageDesc { .symbol(systemName: symbolName) } -} diff --git a/SimDirs/Model/SimProductFamily.swift b/SimDirs/Model/SimProductFamily.swift index 6732f0b..8e3b4dc 100644 --- a/SimDirs/Model/SimProductFamily.swift +++ b/SimDirs/Model/SimProductFamily.swift @@ -24,9 +24,3 @@ enum SimProductFamily: String, Decodable { } } } - -extension SimProductFamily: SourceItemData { - var title : String { self.rawValue } - var headerTitle : String { "Product Family: \(title)" } - var imageDesc : SourceImageDesc { .symbol(systemName: symbolName) } -} diff --git a/SimDirs/Model/SimRuntime.swift b/SimDirs/Model/SimRuntime.swift index 339344d..308e5d6 100644 --- a/SimDirs/Model/SimRuntime.swift +++ b/SimDirs/Model/SimRuntime.swift @@ -114,14 +114,6 @@ class SimRuntime: ObservableObject, Comparable, Decodable { } } -extension SimRuntime: SourceItemData { - var title : String { return name } - var headerTitle : String { "Runtime: \(title)" } - var imageDesc : SourceImageDesc { .symbol(systemName: "shippingbox", color: isAvailable ? .green : .red) } - - var optionTrait : SourceFilter.Options { isAvailable ? .runtimeInstalled : [] } -} - extension Array where Element == SimRuntime { mutating func indexOfMatchedOrCreated(identifier: String) throws -> Index { return try firstIndex { $0.identifier == identifier } ?? { diff --git a/SimDirs/Node/Conforming/SimApp+Node.swift b/SimDirs/Node/Conforming/SimApp+Node.swift new file mode 100644 index 0000000..39534ed --- /dev/null +++ b/SimDirs/Node/Conforming/SimApp+Node.swift @@ -0,0 +1,33 @@ +// +// SimApp+Node.swift +// SimDirs +// +// Created by Casey Fleser on 3/5/23. +// + +import SwiftUI + +extension SimApp: Node { + var title : String { return displayName } + var headerTitle : String { "App: \(title)" } + + var header : some View { AppHeader(app: self) } + var content : some View { AppContent(app: self) } + + func icon(forHeader: Bool) -> some View { + if let nsIcon = nsIcon { + let iconSize : CGFloat = forHeader ? 128 : 20 + + Image(nsImage: nsIcon) + .resizable() + .aspectRatio(contentMode: .fit) + .cornerRadius(iconSize / 5.0) + .shadow(radius: 4.0, x: 2.0, y: 2.0) + .frame(maxWidth: iconSize, maxHeight: iconSize) + } + else { + symbolIcon("questionmark.app.dashed", forHeader: forHeader) + } + } + +} diff --git a/SimDirs/Node/Conforming/SimDevice+Node.swift b/SimDirs/Node/Conforming/SimDevice+Node.swift new file mode 100644 index 0000000..5d04275 --- /dev/null +++ b/SimDirs/Node/Conforming/SimDevice+Node.swift @@ -0,0 +1,31 @@ +// +// SimDevice+Node.swift +// SimDirs +// +// Created by Casey Fleser on 3/5/23. +// + +import SwiftUI + +extension SimDevice: Node { + var title : String { return name } + var headerTitle : String { "Device: \(title)" } + + var header : some View { DeviceHeader(device: self) } + var content : some View { DeviceContent(self) } + +// var isEnabled : Bool { isBooted } + var iconName : String { deviceType?.productFamily.symbolName ?? "questionmark.circle" } + var items : [SimApp]? { + get { apps } + set { apps = newValue ?? [] } + } + + func icon(forHeader: Bool) -> some View { + symbolIcon(iconName, color: isAvailable ? .green : .red, forHeader: forHeader) + } + + func matchedFilterOptions() -> SourceFilter.Options { + return !apps.isEmpty ? .withApps : [] + } +} diff --git a/SimDirs/Node/Conforming/SimDeviceType+Node.swift b/SimDirs/Node/Conforming/SimDeviceType+Node.swift new file mode 100644 index 0000000..cd6f9c6 --- /dev/null +++ b/SimDirs/Node/Conforming/SimDeviceType+Node.swift @@ -0,0 +1,20 @@ +// +// SimDeviceType+Node.swift +// SimDirs +// +// Created by Casey Fleser on 3/5/23. +// + +import SwiftUI + +extension SimDeviceType: Node { + var title : String { return name } + var headerTitle : String { "Device Type: \(title)" } + + var header : some View { DeviceTypeHeader(deviceType: self) } + var content : some View { DeviceTypeContent(deviceType: self) } + + func icon(forHeader: Bool) -> some View { + symbolIcon(productFamily.symbolName, forHeader: forHeader) + } +} diff --git a/SimDirs/Node/Conforming/SimPlatform+Node.swift b/SimDirs/Node/Conforming/SimPlatform+Node.swift new file mode 100644 index 0000000..5b0a280 --- /dev/null +++ b/SimDirs/Node/Conforming/SimPlatform+Node.swift @@ -0,0 +1,21 @@ +// +// SimPlatform+Node.swift +// SimDirs +// +// Created by Casey Fleser on 3/5/23. +// + +import SwiftUI + +extension SimPlatform: Node { + var title : String { self.rawValue } + var headerTitle : String { "Platform: \(title)" } + + var header : some View { get { EmptyView() } } + var content : some View { get { EmptyView() } } + + func icon(forHeader: Bool) -> some View { + symbolIcon(symbolName, forHeader: forHeader) + } +} + diff --git a/SimDirs/Node/Conforming/SimProductFamily+Node.swift b/SimDirs/Node/Conforming/SimProductFamily+Node.swift new file mode 100644 index 0000000..d8ab8ac --- /dev/null +++ b/SimDirs/Node/Conforming/SimProductFamily+Node.swift @@ -0,0 +1,21 @@ +// +// SimProductFamily+Node.swift +// SimDirs +// +// Created by Casey Fleser on 3/5/23. +// + +import SwiftUI + +extension SimProductFamily: Node { + var title : String { self.rawValue } + var headerTitle : String { "Product Family: \(title)" } + + var header : some View { get { EmptyView() } } + var content : some View { get { EmptyView() } } + + func icon(forHeader: Bool) -> some View { + symbolIcon(symbolName, forHeader: forHeader) + } +} + diff --git a/SimDirs/Node/Conforming/SimRuntime+Node.swift b/SimDirs/Node/Conforming/SimRuntime+Node.swift new file mode 100644 index 0000000..816d613 --- /dev/null +++ b/SimDirs/Node/Conforming/SimRuntime+Node.swift @@ -0,0 +1,24 @@ +// +// SimRuntime+Node.swift +// SimDirs +// +// Created by Casey Fleser on 3/5/23. +// + +import SwiftUI + +extension SimRuntime: Node { + var title : String { return name } + var headerTitle : String { "Runtime: \(title)" } + + var header : some View { RuntimeHeader(runtime: self) } + var content : some View { RuntimeContent(runtime: self) } + + func icon(forHeader: Bool) -> some View { + symbolIcon("shippingbox", color: isAvailable ? .green : .red, forHeader: forHeader) + } + + func matchedFilterOptions() -> SourceFilter.Options { + return isAvailable ? .runtimeInstalled : [] + } +} diff --git a/SimDirs/Node/FilteredNode.swift b/SimDirs/Node/FilteredNode.swift new file mode 100644 index 0000000..6f2e2e5 --- /dev/null +++ b/SimDirs/Node/FilteredNode.swift @@ -0,0 +1,73 @@ +// +// FilteredNode.swift +// NodeItems +// +// Created by Casey Fleser on 3/3/23. +// + +import SwiftUI +import Combine + +class FilteredNode: Node, ObservableObject { + typealias FilteredList = [FilteredNode] + + @Published var filtered : Bool + @Published var isExpanded = false + + var wrappedNode : T + var title : String { wrappedNode.title } + var headerTitle : String { wrappedNode.headerTitle } + var header : some View { wrappedNode.header } + var content : some View { wrappedNode.content } + var items : FilteredList? + var children : FilteredList { items ?? [] } + + init(_ node: T) { + self.wrappedNode = node + self.filtered = false + + self.items = node.items?.asFilteredNodes() + } + + func icon(forHeader: Bool) -> some View { + wrappedNode.icon(forHeader: forHeader) + } + + func toggleExpanded(_ expanded: Bool? = nil, deep: Bool) { + isExpanded = expanded ?? !isExpanded + + if deep { + for child in children { + child.toggleExpanded(isExpanded, deep: true) + } + } + } + + func matchesFilter(_ filter: SourceFilter, inherited options: SourceFilter.Options) -> Bool { + wrappedNode.matchesFilter(filter, inherited: options) + } + + func matchedFilterOptions() -> SourceFilter.Options { + return wrappedNode.matchedFilterOptions() + } + + @discardableResult + func applyFilter(_ filter: SourceFilter, inheriting options: SourceFilter.Options = []) -> Bool { + let updatedOptions = options.union(matchedFilterOptions()) + let childMatch = children.reduce(false) { result, node in + node.applyFilter(filter, inheriting: updatedOptions) || result // deliberately not short circuiting here + } + let nodeMatch = childMatch || wrappedNode.matchesFilter(filter, inherited: updatedOptions) + + filtered = !nodeMatch + + return nodeMatch + } +} + +extension NodeList { + func asFilteredNodes() -> [FilteredNode] { + self.map { FilteredNode($0) } + } +} + diff --git a/SimDirs/Node/Node.swift b/SimDirs/Node/Node.swift new file mode 100644 index 0000000..0f95aef --- /dev/null +++ b/SimDirs/Node/Node.swift @@ -0,0 +1,158 @@ +// +// Node.swift +// NodeItems +// +// Created by Casey Fleser on 3/2/23. +// + +import SwiftUI + +protocol Node: NodeSource { + associatedtype Icon: View + associatedtype Header: View + associatedtype Content: View + + var title : String { get } + var headerTitle : String { get } + + @ViewBuilder var header : Header { get } + @ViewBuilder var content : Content { get } + + @ViewBuilder + func icon(forHeader: Bool) -> Icon + + func matchedFilterOptions() -> SourceFilter.Options + func matchesFilter(_ filter: SourceFilter, inherited options: SourceFilter.Options) -> Bool +} + +extension Node { + var items : [LeafNode]? { + get { nil } + set { } + } + + @ViewBuilder + func symbolIcon(_ systemName: String, color: Color? = nil, forHeader: Bool) -> some View { + if forHeader { + Image(systemName: systemName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 128, maxHeight: 128) + .shadow(radius: 4.0, x: 2.0, y: 2.0) + } + else { + Image(systemName: systemName) + .foregroundColor(color ?? .primary) + .symbolRenderingMode(.hierarchical) + } + } + + func callAsFunction(emptyIsNil: Bool = false, @NodeListBuilder items: () -> [Item]) -> NodeLink { + link(emptyIsNil: emptyIsNil, to: items) + } + + func link(emptyIsNil: Bool = false, @NodeListBuilder to items: () -> [Item]) -> NodeLink { + NodeLink(self, emptyIsNil: emptyIsNil, items: items) + } + + func matchedFilterOptions() -> SourceFilter.Options { + return [] + } + + func matchesTerm(_ term: String) -> Bool { + term.isEmpty || title.uppercased().contains(term.uppercased()) + } + + func matchesFilter(_ filter: SourceFilter, inherited options: SourceFilter.Options) -> Bool { + filter.options.isSubset(of: options) && matchesTerm(filter.searchTerm) + } +} + +/// Indicates a type that owns a list of Nodes + +protocol NodeSource { + associatedtype List: NodeList + + var items : List? { get set } +} + +/// Defines the requirements of a collection that can serve as a `NodeList`. + +protocol NodeList: RandomAccessCollection where Self.Element: Node, Index: Hashable { } + +extension NodeList { + @NodeListBuilder + func linkEachTo(emptyIsNil: Bool = false, @NodeListBuilder items: (Element) -> [Item]) -> some NodeList { + map { item in + item.link(emptyIsNil: emptyIsNil, to: { items(item) }) + } +// Makes compiler unhappy. resultBuilder probably incorrect +// for item in self { +// item.link(to: { items(item) }) +// } + } +} + +extension Array: NodeList where Element: Node { } + +// MARK: - Special Nodes - + +enum LeafNode: Node { + var title : String { "impossible" } + var headerTitle : String { title } + + var header: some View { Text("impossible") } + var content: some View { Text("impossible") } + + func icon(forHeader: Bool) -> some View { + Text("impossible") + } +} + +struct RootNode: Node { + var items : [Item]? + var title : String { "Root" } + var headerTitle : String { title } + var header : some View { Text("Root") } + var content : some View { Text("Root") } + + init() { + self.items = nil + } + + init(@NodeListBuilder _ items: () -> [Item]) { + self.items = items() + } + + func icon(forHeader: Bool) -> some View { symbolIcon("tree", forHeader: forHeader) } +} + +struct NodeLink: Node { + var base : Base + var items : [Item]? + var title : String { base.title } + var headerTitle : String { base.headerTitle } + var header : Base.Header { base.header } + var content : Base.Content { base.content } + + init(_ base: Base, emptyIsNil: Bool = false, @NodeListBuilder items: () -> [Item]) { + let list = items() + + self.base = base + self.items = emptyIsNil ? (list.isEmpty ? nil : list) : list + } + +@available(*, deprecated, message: "Consider using Root { items } instead") + init(@NodeListBuilder _ items: () -> [Item]) where Base == RootNode { + self.base = RootNode() + self.items = items() + } + + func icon(forHeader: Bool) -> some View { + base.icon(forHeader: forHeader) + } + + func matchedFilterOptions() -> SourceFilter.Options { + return base.matchedFilterOptions() + } +} diff --git a/SimDirs/Node/NodeListBuilder.swift b/SimDirs/Node/NodeListBuilder.swift new file mode 100644 index 0000000..070a71c --- /dev/null +++ b/SimDirs/Node/NodeListBuilder.swift @@ -0,0 +1,173 @@ +// +// NodeListBuilder.swift +// NodeItems +// +// Created by Casey Fleser on 3/2/23. +// + +import SwiftUI + +@resultBuilder struct NodeListBuilder { + typealias P = Node + + enum OneOf: P, CustomStringConvertible { + typealias List = [NodeListBuilder.OneOf] + + case a(A) + case b(B) + + var title : String { + switch self { + case .a(let node): return node.title + case .b(let node): return node.title + } + } + + var headerTitle : String { + switch self { + case .a(let node): return node.headerTitle + case .b(let node): return node.headerTitle + } + } + + @ViewBuilder + var header : some View { + switch self { + case .a(let node): node.header + case .b(let node): node.header + } + } + + @ViewBuilder + var content : some View { + switch self { + case .a(let node): node.content + case .b(let node): node.content + } + } + + var items : List? { + get { + switch self { + case .a(let node): return node.items?.map { .a($0) } + case .b(let node): return node.items?.map { .b($0) } + } + } + set { } + } + + var description : String { + let valueDesc : String + + switch self { + case .a(let node): valueDesc = ".a: \(String(describing: node))" + case .b(let node): valueDesc = ".b: \(String(describing: node))" + } + + return "OneOf: \(valueDesc)" + } + + func icon(forHeader: Bool) -> some View { + switch self { + case .a(let node): node.icon(forHeader: forHeader) + case .b(let node): node.icon(forHeader: forHeader) + } + } + + func matchedFilterOptions() -> SourceFilter.Options { + switch self { + case .a(let node): return node.matchedFilterOptions() + case .b(let node): return node.matchedFilterOptions() + } + } + } + + static func buildBlock(_ c: [C]) -> [C] { + c + } + + static func buildBlock ( + _ c0: [C0], _ c1: [C1]) -> [OneOf] + { + [buildEither(first: c0), buildEither(second: c1)].flatMap { $0 } + } + + static func buildBlock ( + _ c0: [C0], _ c1: [C1], _ c2: [C2]) -> [OneOf, C2>] + { + [buildEither(first: buildBlock(c0, c1)), buildEither(second: c2)].flatMap { $0 } + } + + static func buildBlock ( + _ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3]) -> [OneOf, OneOf>] + { + [buildEither(first: buildBlock(c0, c1)), buildEither(second: buildBlock(c2, c3))].flatMap { $0 } + } + + static func buildBlock ( + _ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4]) -> [OneOf, OneOf>, C4>] + { + [buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: c4)].flatMap { $0 } + } + + static func buildBlock ( + _ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4], _ c5: [C5]) -> [OneOf, OneOf>, OneOf>] + { + [buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: buildBlock(c4, c5))].flatMap { $0 } + } + + static func buildBlock ( + _ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4], _ c5: [C5], _ c6: [C6]) -> [OneOf, OneOf>, OneOf, C6>>] + { + [buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: buildBlock(c4, c5, c6))].flatMap { $0 } + } + + static func buildBlock ( + _ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4], _ c5: [C5], _ c6: [C6], _ c7: [C7]) -> [OneOf, OneOf>, OneOf, OneOf>>] + { + [buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: buildBlock(c4, c5, c6, c7))].flatMap { $0 } + } + +// static func buildBlock(_ c: [C]...) -> [C] { +// c.flatMap { $0 } +// } +// + // Same type buildBlocks. This works but buildBlock(_ c: [C]...) -> [C] confuses the compiler + + static func buildBlock (_ c0: [C0], _ c1: [C0]) -> [C0] { + [c0, c1].flatMap { $0 } + } + + static func buildBlock (_ c0: [C0], _ c1: [C0], _ c2: [C0]) -> [C0] { + [c0, c1, c2].flatMap { $0 } + } + + static func buildEither(first c0: [C0]) -> [OneOf] { + c0.map { OneOf.a($0) } + } + + static func buildEither(second c1: [C1]) -> [OneOf] { + c1.map { OneOf.b($0) } + } + + static func buildOptional(_ c: [C]?) -> [C] { + c ?? [] + } + + static func buildArray(_ c: [[C]]) -> [C] { + c.flatMap { $0 } + } + + static func buildExpression(_ node: N) -> [N] { + [node] + } + + static func buildExpression(_ nodeList: NL) -> [NL.Element] { + Array(nodeList) + } + + static func buildExpression(_ nodeSource: NS) -> [NS.List.Element] { + nodeSource.items.map({ buildExpression($0) }) ?? [] + } +} + diff --git a/SimDirs/Node/Views/FilteredNodeView.swift b/SimDirs/Node/Views/FilteredNodeView.swift new file mode 100644 index 0000000..f72f864 --- /dev/null +++ b/SimDirs/Node/Views/FilteredNodeView.swift @@ -0,0 +1,95 @@ +// +// FilteredNodeView.swift +// NodeItems +// +// Created by Casey Fleser on 3/3/23. +// + +import SwiftUI + +struct FilteredNodeView: View { + @StateObject var node : FilteredNode + @Binding var filter : SourceFilter + + init(_ node: T, filter: Binding) { + self._node = StateObject(wrappedValue: FilteredNode(node)) + self._filter = filter + } + + init(filter: Binding, @NodeListBuilder items: () -> [Item]) where T == RootNode { + self.init(RootNode(items), filter: filter) + } + + var body: some View { + Root(node: node) + .searchable(text: $filter.searchTerm, placement: .sidebar) + .onAppear { node.applyFilter(filter) } + .onChange(of: filter) { node.applyFilter($0) } + } +} + +extension FilteredNodeView { + struct Root: View { + @ObservedObject var node : FilteredNode + + var visibleItems : FilteredNode.List { node.items.map { $0.filter { !$0.filtered} } ?? [] } + + var body: some View { + let items = visibleItems + + List { + if !items.isEmpty { + ForEach(items.indices, id: \.self) { index in + Item(node: items[index]) + } + } + else { + Text("No Filter Results") + } + } + } + } + + struct ItemList: View { + var items : [FilteredNode] + + init(items: [FilteredNode]) { + self.items = items + } + + var body: some View { + ForEach(items.indices, id: \.self) { index in + Item(node: items[index]) + } + } + } + + struct Item: View { + @ObservedObject var node : FilteredNode + + var body: some View { + if !node.filtered { + NodeLabel(node) + + if let items = node.items, node.isExpanded { + ItemList(items: items) + .padding(.leading, 12.0) + } + } + } + } +} + +struct FilteredNodeView_Previews: PreviewProvider { + @State static var filter = SourceFilter.restore() + + static var previews: some View { + List { + FilteredNodeView(filter: $filter) { + SimPlatform.iOS + SimPlatform.tvOS + SimPlatform.watchOS + } + } + } +} diff --git a/SimDirs/Node/Views/NodeLabel.swift b/SimDirs/Node/Views/NodeLabel.swift new file mode 100644 index 0000000..39b5c3a --- /dev/null +++ b/SimDirs/Node/Views/NodeLabel.swift @@ -0,0 +1,61 @@ +// +// NodeLabel.swift +// NodeItems +// +// Created by Casey Fleser on 3/3/23. +// + +import SwiftUI + +struct NodeLabel: View { + @ObservedObject var node : FilteredNode + + init(_ node: FilteredNode) { + self.node = node + } + + var body: some View { + HStack(spacing: 0) { + let button = + Button( + action: { + let optionActive = NSApplication.shared.currentEvent?.modifierFlags.contains(.option) == true + + withAnimation(.easeInOut(duration: 0.2)) { + node.toggleExpanded(deep: optionActive) + } + }, + label: { + Image(systemName: "chevron.right") + .padding(.horizontal, 4.0) + .contentShape(Rectangle()) + .rotationEffect(.degrees(node.isExpanded ? 90.0 : 0.0)) + } + ) + .buttonStyle(.plain) + + if node.items != nil { button } + else { button.hidden() } + + NavigationLink( + destination: { NodeView(node) }, + label: { + Label( + title: { Text(node.title) }, + icon: { node.icon(forHeader: false) } + ) + } + ) + } + } +} + +struct NodeLabel_Previews: PreviewProvider { + @StateObject static var previewItem = FilteredNode(SimPlatform.iOS) + + static var previews: some View { + VStack { + NodeLabel(previewItem) + } + } +} diff --git a/SimDirs/Views/SourceItem Views/SourceItemContent.swift b/SimDirs/Node/Views/NodeView.swift similarity index 57% rename from SimDirs/Views/SourceItem Views/SourceItemContent.swift rename to SimDirs/Node/Views/NodeView.swift index a4cc994..870bcb9 100644 --- a/SimDirs/Views/SourceItem Views/SourceItemContent.swift +++ b/SimDirs/Node/Views/NodeView.swift @@ -1,27 +1,29 @@ // -// SourceItemContent.swift +// NodeView.swift // SimDirs // -// Created by Casey Fleser on 6/20/22. +// Created by Casey Fleser on 3/6/23. // import SwiftUI -import SwiftUI - -struct SourceItemContent: View { - var item : Item +struct NodeView: View { + var node : Item + init(_ node: Item) { + self.node = node + } + var body: some View { GeometryReader { geometry in VStack(alignment: .leading, spacing: 0.0) { // --- Header section --- VStack(alignment: .leading) { - Text(item.headerTitle) + Text(node.headerTitle) .font(.system(size: 20)) .padding(.top, 12.0) .padding(.bottom, 8.0) - item.header + node.header .padding(.trailing, 136.0) } .padding([.leading, .trailing]) @@ -31,7 +33,7 @@ struct SourceItemContent: View { // --- Content section --- ScrollView { HStack { - item.content + node.content .padding(.top, 4.0) .padding(.trailing) Spacer() @@ -41,27 +43,18 @@ struct SourceItemContent: View { .padding([.leading, .top]) .background(.background) } - .overlay( - SourceItemImage(imageDesc: item.imageDesc, isLabelImage: false) - .padding([.top, .trailing], 24.0), - alignment: .topTrailing - ) + .overlay(alignment: .topTrailing) { + node.icon(forHeader: true) + .padding([.top, .trailing], 24.0) + } .padding(.top, -geometry.frame(in: .global).origin.y) } - .navigationTitle(item.title) + .navigationTitle(node.title) } } -struct SourceItemContent_Previews: PreviewProvider { - static var state = SourceState(model: SimModel()) - static var sampleItems = state.deviceStyleItems()[0...1] - +struct NodeView_Previews: PreviewProvider { static var previews: some View { - ForEach(sampleItems) { item in - SourceItemContent(item: item) - .preferredColorScheme(.dark) - SourceItemContent(item: item) - .preferredColorScheme(.light) - } + NodeView(SimPlatform.iOS) } } diff --git a/SimDirs/Presentation/SourceFilter.swift b/SimDirs/Presentation/SourceFilter.swift index 2a25a11..192034d 100644 --- a/SimDirs/Presentation/SourceFilter.swift +++ b/SimDirs/Presentation/SourceFilter.swift @@ -7,7 +7,7 @@ import Foundation -struct SourceFilter { +struct SourceFilter: Equatable { struct Options: OptionSet, CaseIterable { let rawValue: Int @@ -15,27 +15,21 @@ struct SourceFilter { static let runtimeInstalled = Options(rawValue: 1 << 1) static var allCases : [Options] = [.withApps, .runtimeInstalled] - - func search(item: T, progress: Options) -> Self { - var foundOptions = progress.union(item.data.optionTrait) - - if !subtracting(foundOptions).isEmpty { - for child in item.children { - foundOptions = search(item: child, progress: foundOptions) - - if isSubset(of: foundOptions) { - break - } - } - } - - return foundOptions - } } var searchTerm = "" var options = Options() { didSet { UserDefaults.standard.set(options.rawValue, forKey: "FilterOptions") } } + var filterApps : Bool { + get { options.contains(.withApps) } + set { options.booleanSet(newValue, options: .withApps) } + } + + var filterRuntimes : Bool { + get { options.contains(.runtimeInstalled) } + set { options.booleanSet(newValue, options: .runtimeInstalled) } + } + static func restore() -> SourceFilter { var filter = SourceFilter() diff --git a/SimDirs/Presentation/SourceItem.swift b/SimDirs/Presentation/SourceItem.swift deleted file mode 100644 index a4e706c..0000000 --- a/SimDirs/Presentation/SourceItem.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// SourceItem.swift -// SimDirs -// -// Created by Casey Fleser on 6/14/22. -// - -import SwiftUI - -protocol SourceItem: Identifiable, ObservableObject { - associatedtype Model : SourceItemData - associatedtype Child : SourceItem - - var id : UUID { get } - var data : Model { get } - var children : [Child] { get set } - var visibleChildren : [Child] { get set } - var customImgDesc : SourceImageDesc? { get } - var isExpanded : Bool { get set } -} - -extension SourceItem { - var title : String { data.title } - var headerTitle : String { data.headerTitle } - var header : some View { data.header } - var content : some View { data.content } - var imageDesc : SourceImageDesc { customImgDesc ?? data.imageDesc } - var customImgDesc : SourceImageDesc? { nil } - - func applyFilter(_ filter: SourceFilter) { - visibleChildren = children.filter { $0.applyingFilter(filter) } - } - - func applyingFilter(_ filter: SourceFilter, inheritedOptions: SourceFilter.Options = []) -> Bool { - var match = true - let optProgress = inheritedOptions.union(data.optionTrait) // options inherited by children - - // If there are options to match then do that first passing inherited options along - // and consider a match fulfilled if any child contains all the desired options. - - if !filter.options.isEmpty { - var foundOptions = optProgress - - if !filter.options.isSubset(of: foundOptions) { - foundOptions = filter.options.search(item: self, progress: foundOptions) - } - match = filter.options.isSubset(of: foundOptions) - } - - if !filter.searchTerm.isEmpty && match { - match = title.uppercased().contains(filter.searchTerm.uppercased()) - } - - visibleChildren = children.filter { $0.applyingFilter(filter, inheritedOptions: optProgress) } - - return match || !visibleChildren.isEmpty - } - - func toggleExpanded(_ expanded: Bool? = nil, deep: Bool) { - isExpanded = expanded ?? !isExpanded - - if deep { - for child in children { - child.toggleExpanded(isExpanded, deep: true) - } - } - } -} - -class SourceItemVal: SourceItem { - @Published var visibleChildren : [Child] - @Published var isExpanded : Bool - - var id = UUID() - var data : Model - var children : [Child] - var customImgDesc : SourceImageDesc? - - init(id: UUID = UUID(), data: Model, children: [Child] = [], customImgDesc: SourceImageDesc? = nil) { - self.id = id - self.data = data - self.children = children - self.visibleChildren = children - self.customImgDesc = customImgDesc - self.isExpanded = false - } -} - -class SourceItemNone: SourceItem { - var id = UUID() - var data = SourceItemDataNone() - var children = [SourceItemNone]() - var visibleChildren = [SourceItemNone]() - var isExpanded = false -} diff --git a/SimDirs/Presentation/SourceItemData.swift b/SimDirs/Presentation/SourceItemData.swift deleted file mode 100644 index d089438..0000000 --- a/SimDirs/Presentation/SourceItemData.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// SourceItemData.swift -// SimDirs -// -// Created by Casey Fleser on 6/15/22. -// - -import SwiftUI - -protocol SourceItemData { - associatedtype Content : View - associatedtype Header : View - - @ViewBuilder var header : Self.Header { get } - @ViewBuilder var content : Self.Content { get } - - var title : String { get } - var headerTitle : String { get } - var imageDesc : SourceImageDesc { get } - - var optionTrait : SourceFilter.Options { get } -} - -extension SourceItemData { - var headerTitle : String { title } - var imageDesc : SourceImageDesc { .symbol() } - - var header : some View { get { EmptyView() } } - var content : some View { get { EmptyView() } } - - var optionTrait : SourceFilter.Options { [] } -} - -enum SourceImageDesc { - case icon(nsImage: NSImage) - case symbol(systemName: String = "questionmark.circle", color: Color = .primary) - - func withColor(_ color: Color) -> Self { - switch self { - case .icon: return self - case let .symbol(name, _): return .symbol(systemName: name, color: color) - } - } -} - -struct SourceItemDataNone: SourceItemData { - static let none = SourceItemDataNone() - - var title : String { "" } -} diff --git a/SimDirs/Presentation/SourceState.swift b/SimDirs/Presentation/SourceState.swift index aa5b840..f4ed535 100644 --- a/SimDirs/Presentation/SourceState.swift +++ b/SimDirs/Presentation/SourceState.swift @@ -9,29 +9,6 @@ import Foundation import Combine class SourceState: ObservableObject { - typealias ProductFamily = SourceItemVal - typealias Platform = SourceItemVal - typealias DeviceType_DS = SourceItemVal - typealias DeviceType_RT = SourceItemVal - typealias Runtime_DS = SourceItemVal - typealias Runtime_RT = SourceItemVal - typealias Device = SourceItemVal - typealias App = SourceItemVal - - enum Base: Identifiable { - case placeholder(id: UUID = UUID()) - case device(id: UUID = UUID(), SourceItemVal) - case runtime(id: UUID = UUID(), SourceItemVal) - - var id : UUID { - switch self { - case let .placeholder(id): return id - case let .device(id, _): return id - case let .runtime(id, _): return id - } - } - } - enum Style: Int, CaseIterable, Identifiable { case placeholder case byDevice @@ -53,34 +30,22 @@ class SourceState: ObservableObject { } } - @Published var style = Style.placeholder { didSet { rebuildBase() } } - @Published var filter = SourceFilter.restore() { didSet { applyFilter() } } + @Published var style = Style.placeholder // { didSet { rebuildBase() } } @Published var selection : UUID? var model : SimModel - var base = Base.placeholder() var deviceUpdates : Cancellable? - var filterApps : Bool { - get { filter.options.contains(.withApps) } - set { filter.options.booleanSet(newValue, options: .withApps) } - } - - var filterRuntimes : Bool { - get { filter.options.contains(.runtimeInstalled) } - set { filter.options.booleanSet(newValue, options: .runtimeInstalled) } - } - init(model: SimModel) { self.style = .byDevice self.model = model - - self.rebuildBase() deviceUpdates = model.deviceUpdates.sink(receiveValue: applyDeviceUpdates) } - + +#warning("TODO: still need to apply updates") func applyDeviceUpdates(_ updates: SimDevicesUpdates) { +#if false switch base { case .placeholder: break @@ -122,69 +87,37 @@ class SourceState: ObservableObject { } applyFilter() +#endif } - - func applyFilter() { - switch base { - case .placeholder: break - case let .device(_, item): item.applyFilter(filter) - case let .runtime(_, item): item.applyFilter(filter) - } - } - - func rebuildBase() { - var baseID : UUID - - // Preserve identifier if style is not changing - switch (style, base) { - case (.placeholder, let .placeholder(id)): baseID = id; - case (.byDevice, let .device(id, _)): baseID = id; - case (.byRuntime, let .runtime(id, _)): baseID = id; - default: baseID = UUID() - } - - switch style { - case .placeholder: base = .placeholder(id: baseID) - case .byDevice: base = .device(id: baseID, SourceItemVal(data: .none, children: deviceStyleItems())) - case .byRuntime: base = .runtime(id: baseID, SourceItemVal(data: .none, children: runtimeStyleItems())) - } - - applyFilter() - } - - func baseFor(style: Style) -> Base { - switch style { - case .placeholder: return .placeholder() - case .byDevice: return .device(SourceItemVal(data: .none, children: deviceStyleItems())) - case .byRuntime: return .runtime(SourceItemVal(data: .none, children: runtimeStyleItems())) - } - } - - func deviceStyleItems() -> [ProductFamily] { - SimProductFamily.presentation.map { family in - ProductFamily(data: family, children: model.deviceTypes.supporting(productFamily: family).map { devType in - DeviceType_DS(data: devType, children: model.runtimes.supporting(deviceType: devType).map { runtime in - Runtime_DS(data: runtime, children: runtime.devices.of(deviceType: devType).map { device in - let imageDesc = devType.imageDesc.withColor(device.isAvailable ? .green : .red) - return Device(data: device, children: device.apps.map { app in App(data: app, children: []) }, customImgDesc: imageDesc) - }) - }) - }) + @NodeListBuilder + var items: some NodeList { + switch style { + case .placeholder: [] as [LeafNode] + case .byDevice: deviceStyleItems + case .byRuntime: runtimeStyleItems + } + } + + @NodeListBuilder + var deviceStyleItems: some NodeList { + SimProductFamily.presentation.linkEachTo { family in + model.deviceTypes.supporting(productFamily: family).linkEachTo(emptyIsNil: true) { devType in + model.runtimes.supporting(deviceType: devType).linkEachTo(emptyIsNil: true) { runtime in + runtime.devices.linkingDeviceType(devType) + } + } } } - func runtimeStyleItems() -> [Platform] { - SimPlatform.presentation.map { platform in - Platform(data: platform, children: model.runtimes.supporting(platform: platform).map { runtime in - Runtime_RT(data: runtime, children: model.deviceTypes.supporting(runtime: runtime).map { devType in - DeviceType_RT(data: devType, children: runtime.devices.of(deviceType: devType).map { device in - let imageDesc = devType.imageDesc.withColor(device.isAvailable ? .green : .red) - - return Device(data: device, children: device.apps.map { app in App(data: app, children: []) }, customImgDesc: imageDesc) - }) - }) - }) + @NodeListBuilder + var runtimeStyleItems: some NodeList { + SimPlatform.presentation.linkEachTo(emptyIsNil: true) { platform in + model.runtimes.supporting(platform: platform).linkEachTo(emptyIsNil: true) { runtime in + model.deviceTypes.supporting(runtime: runtime).linkEachTo(emptyIsNil: true) { devType in + runtime.devices.linkingDeviceType(devType) + } + } } } } diff --git a/SimDirs/Views/Model Views/AppContent.swift b/SimDirs/Views/Model Views/AppContent.swift index a8d0790..57fb3e7 100644 --- a/SimDirs/Views/Model Views/AppContent.swift +++ b/SimDirs/Views/Model Views/AppContent.swift @@ -8,8 +8,6 @@ import SwiftUI extension SimApp { - public var content : some View { AppContent(app: self) } - var isLaunched : Bool { get { state.isOn } set { toggleLaunchState() } diff --git a/SimDirs/Views/Model Views/AppHeader.swift b/SimDirs/Views/Model Views/AppHeader.swift index 2957d7e..c050514 100644 --- a/SimDirs/Views/Model Views/AppHeader.swift +++ b/SimDirs/Views/Model Views/AppHeader.swift @@ -7,10 +7,6 @@ import SwiftUI -extension SimApp { - public var header : some View { AppHeader(app: self) } -} - struct AppHeader: View { var app : SimApp diff --git a/SimDirs/Views/Model Views/DeviceContent.swift b/SimDirs/Views/Model Views/DeviceContent.swift index 50a7aa6..538545e 100644 --- a/SimDirs/Views/Model Views/DeviceContent.swift +++ b/SimDirs/Views/Model Views/DeviceContent.swift @@ -9,8 +9,6 @@ import SwiftUI import UniformTypeIdentifiers extension SimDevice { - public var content : some View { DeviceContent(self) } - var scheme : ColorScheme? { get { switch appearance { diff --git a/SimDirs/Views/Model Views/DeviceHeader.swift b/SimDirs/Views/Model Views/DeviceHeader.swift index f1ff16a..240f3dd 100644 --- a/SimDirs/Views/Model Views/DeviceHeader.swift +++ b/SimDirs/Views/Model Views/DeviceHeader.swift @@ -7,10 +7,6 @@ import SwiftUI -extension SimDevice { - public var header : some View { DeviceHeader(device: self) } -} - struct DeviceHeader: View { @ObservedObject var device : SimDevice diff --git a/SimDirs/Views/Model Views/DeviceTypeContent.swift b/SimDirs/Views/Model Views/DeviceTypeContent.swift index da1be12..3e8bfb6 100644 --- a/SimDirs/Views/Model Views/DeviceTypeContent.swift +++ b/SimDirs/Views/Model Views/DeviceTypeContent.swift @@ -7,10 +7,6 @@ import SwiftUI -extension SimDeviceType { - public var content : some View { DeviceTypeContent(deviceType: self) } -} - struct DeviceTypeContent: View { var deviceType : SimDeviceType diff --git a/SimDirs/Views/Model Views/DeviceTypeHeader.swift b/SimDirs/Views/Model Views/DeviceTypeHeader.swift index 1057f4a..d207874 100644 --- a/SimDirs/Views/Model Views/DeviceTypeHeader.swift +++ b/SimDirs/Views/Model Views/DeviceTypeHeader.swift @@ -7,10 +7,6 @@ import SwiftUI -extension SimDeviceType { - public var header : some View { DeviceTypeHeader(deviceType: self) } -} - struct DeviceTypeHeader: View { var deviceType : SimDeviceType diff --git a/SimDirs/Views/Model Views/RuntimeContent.swift b/SimDirs/Views/Model Views/RuntimeContent.swift index 7fb02a3..a7941f9 100644 --- a/SimDirs/Views/Model Views/RuntimeContent.swift +++ b/SimDirs/Views/Model Views/RuntimeContent.swift @@ -7,10 +7,6 @@ import SwiftUI -extension SimRuntime { - public var content : some View { RuntimeContent(runtime: self) } -} - struct RuntimeContent: View { struct SupportedItem: Identifiable { let name : String diff --git a/SimDirs/Views/Model Views/RuntimeHeader.swift b/SimDirs/Views/Model Views/RuntimeHeader.swift index 00cdd33..81996e4 100644 --- a/SimDirs/Views/Model Views/RuntimeHeader.swift +++ b/SimDirs/Views/Model Views/RuntimeHeader.swift @@ -7,10 +7,6 @@ import SwiftUI -extension SimRuntime { - public var header : some View { RuntimeHeader(runtime: self) } -} - struct RuntimeHeader: View { var runtime : SimRuntime diff --git a/SimDirs/Views/SourceItem Views/SourceItemGroup.swift b/SimDirs/Views/SourceItem Views/SourceItemGroup.swift deleted file mode 100644 index cef317f..0000000 --- a/SimDirs/Views/SourceItem Views/SourceItemGroup.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// SourceItemGroup.swift -// SimDirs -// -// Created by Casey Fleser on 6/20/22. -// - -import SwiftUI - -struct SourceItemGroup: View { - @StateObject var item : Item - @Binding var selection : UUID? - - var body: some View { - HStack(spacing: 0) { - let button = Button(action: { - let optionActive = NSApplication.shared.currentEvent?.modifierFlags.contains(.option) == true - - withAnimation(.easeInOut(duration: 0.2)) { - item.toggleExpanded(deep: optionActive) - } - }, label: { - Image(systemName: "chevron.right") - .padding(.horizontal, 2.0) - .contentShape(Rectangle()) - .rotationEffect(.degrees(item.isExpanded ? 90.0 : 0.0)) - }) - .buttonStyle(.plain) - - if item.visibleChildren.count == 0 { - button.hidden() - } - else { - button - } - - SourceItemLink(selection: $selection, item: item) - } - - if item.isExpanded { - ForEach(item.visibleChildren) { childItem in - SourceItemGroup(item: childItem, selection: $selection) - } - .padding(.leading, 12.0) - } - } -} - -struct SourceItemGroup_Previews: PreviewProvider { - @State static var selection : UUID? - static var state = SourceState(model: SimModel()) - static var sampleItem = state.deviceStyleItems()[0] - - static var previews: some View { - List { - SourceItemGroup(item: sampleItem, selection: $selection) - SourceItemGroup(item: sampleItem, selection: $selection) - SourceItemGroup(item: sampleItem, selection: $selection) - } - } -} diff --git a/SimDirs/Views/SourceItem Views/SourceItemImage.swift b/SimDirs/Views/SourceItem Views/SourceItemImage.swift deleted file mode 100644 index f325204..0000000 --- a/SimDirs/Views/SourceItem Views/SourceItemImage.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// SourceItemImage.swift -// SimDirs -// -// Created by Casey Fleser on 6/20/22. -// - -import SwiftUI - -struct SourceItemImage: View { - var imageDesc : SourceImageDesc - var isLabelImage = true - var imageSize : CGFloat? - - var body: some View { - let size = imageSize ?? (isLabelImage ? 20.0 : 128.0) - - switch imageDesc { - case let .icon(nsImage): - Image(nsImage: nsImage) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: size, maxHeight: size) - .cornerRadius(size / 5.0) - .shadow(radius: 4.0, x: 2.0, y: 2.0) - - case let .symbol(systemName, color): - if isLabelImage { - Image(systemName: systemName) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } - else { - Image(systemName: systemName) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: size, maxHeight: size) - .shadow(radius: 4.0, x: 2.0, y: 2.0) - } - } - } -} - -struct SourceItemImage_Previews: PreviewProvider { - static var state = SourceState(model: SimModel()) - static var sampleItems = state.deviceStyleItems()[0...1] - - static var previews: some View { - ForEach(sampleItems) { item in - SourceItemImage(imageDesc: item.imageDesc) - SourceItemImage(imageDesc: item.imageDesc, isLabelImage: false) - } - } -} diff --git a/SimDirs/Views/SourceItem Views/SourceItemLabel.swift b/SimDirs/Views/SourceItem Views/SourceItemLabel.swift deleted file mode 100644 index c49c08f..0000000 --- a/SimDirs/Views/SourceItem Views/SourceItemLabel.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// SourceItemLabel.swift -// SimDirs -// -// Created by Casey Fleser on 6/20/22. -// - -import SwiftUI - -struct SourceItemLabel: View { - var item : Item - - var body: some View { - Label( - title: { Text(item.title) }, - icon: { SourceItemImage(imageDesc: item.imageDesc) } - ) - } -} - -struct SourceItemLabel_Previews: PreviewProvider { - static var state = SourceState(model: SimModel()) - static var sampleItems = state.deviceStyleItems()[0...1] - - static var previews: some View { - ForEach(sampleItems) { item in - SourceItemLabel(item: item) - SourceItemLabel(item: item) - } - } -} diff --git a/SimDirs/Views/SourceItem Views/SourceItemLink.swift b/SimDirs/Views/SourceItem Views/SourceItemLink.swift deleted file mode 100644 index fe7e47f..0000000 --- a/SimDirs/Views/SourceItem Views/SourceItemLink.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// SourceItemLink.swift -// SimDirs -// -// Created by Casey Fleser on 6/20/22. -// - -import SwiftUI - -struct SourceItemLink: View { - @Binding var selection: UUID? - - var item : Item - - var body: some View { - NavigationLink(tag: item.id, selection: $selection, - destination: { SourceItemContent(item: item) }, - label: { SourceItemLabel(item: item) } - ) - } -} - -struct SourceItemLink_Previews: PreviewProvider { - @State static var selection : UUID? - static var state = SourceState(model: SimModel()) - static var sampleItem = state.deviceStyleItems()[0] - - static var previews: some View { - SourceItemLink(selection: $selection, item: sampleItem) - .preferredColorScheme(.dark) - SourceItemLink(selection: $selection, item: sampleItem) - .preferredColorScheme(.light) - } -} diff --git a/SimDirs/Views/ToolbarMenu.swift b/SimDirs/Views/ToolbarMenu.swift index 201916a..66907ed 100644 --- a/SimDirs/Views/ToolbarMenu.swift +++ b/SimDirs/Views/ToolbarMenu.swift @@ -9,6 +9,7 @@ import SwiftUI struct ToolbarMenu: View { @ObservedObject var state : SourceState + @Binding var filter : SourceFilter var body: some View { Menu { @@ -20,8 +21,8 @@ struct ToolbarMenu: View { } } .pickerStyle(.inline) - Toggle(isOn: $state.filterApps) { Label("With Apps", systemImage: "app.fill") } - Toggle(isOn: $state.filterRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") } + Toggle(isOn: $filter.filterApps) { Label("With Apps", systemImage: "app.fill") } + Toggle(isOn: $filter.filterRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") } } label: { Label("Filter", systemImage: "slider.horizontal.3") } @@ -29,10 +30,10 @@ struct ToolbarMenu: View { } struct ToolbarMenu_Previews: PreviewProvider { - static var state = SourceState(model: SimModel()) + static var state = SourceState(model: SimModel()) + @State static var filter = SourceFilter.restore() static var previews: some View { - ToolbarMenu(state: state) + ToolbarMenu(state: state, filter: $filter) } } -