mirror of
https://github.com/somegeekintn/SimDirs.git
synced 2026-03-25 08:55:54 +00:00
Switching to node based design. See NodeItems project
This commit is contained in:
parent
3f0bbad3a3
commit
f6d596ad1d
37 changed files with 868 additions and 600 deletions
|
|
@ -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 = "<group>"; };
|
||||
C95CC0F728B2411700928FAE /* AppearanceButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceButtonStyle.swift; sourceTree = "<group>"; };
|
||||
C95CC0F928B2414900928FAE /* SystemIconButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemIconButtonStyle.swift; sourceTree = "<group>"; };
|
||||
C966875E29B7641F007BB3F5 /* FilteredNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilteredNode.swift; sourceTree = "<group>"; };
|
||||
C966876029B7641F007BB3F5 /* SimApp+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimApp+Node.swift"; sourceTree = "<group>"; };
|
||||
C966876129B7641F007BB3F5 /* SimProductFamily+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimProductFamily+Node.swift"; sourceTree = "<group>"; };
|
||||
C966876229B7641F007BB3F5 /* SimPlatform+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimPlatform+Node.swift"; sourceTree = "<group>"; };
|
||||
C966876329B7641F007BB3F5 /* SimDeviceType+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimDeviceType+Node.swift"; sourceTree = "<group>"; };
|
||||
C966876429B7641F007BB3F5 /* SimRuntime+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimRuntime+Node.swift"; sourceTree = "<group>"; };
|
||||
C966876529B7641F007BB3F5 /* SimDevice+Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SimDevice+Node.swift"; sourceTree = "<group>"; };
|
||||
C966876629B7641F007BB3F5 /* NodeListBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeListBuilder.swift; sourceTree = "<group>"; };
|
||||
C966876829B7641F007BB3F5 /* NodeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeLabel.swift; sourceTree = "<group>"; };
|
||||
C966876929B7641F007BB3F5 /* FilteredNodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilteredNodeView.swift; sourceTree = "<group>"; };
|
||||
C966876A29B7641F007BB3F5 /* NodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeView.swift; sourceTree = "<group>"; };
|
||||
C966876B29B7641F007BB3F5 /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = "<group>"; };
|
||||
C9779741284F6DE000706DFB /* ToolbarMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarMenu.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
|
|
@ -80,13 +97,6 @@
|
|||
C982F87A283E40C800D491F4 /* SimCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimCtl.swift; sourceTree = "<group>"; };
|
||||
C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptiveToggle.swift; sourceTree = "<group>"; };
|
||||
C9BF5233289FE99600BDDC91 /* DescriptiveToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptiveToggleStyle.swift; sourceTree = "<group>"; };
|
||||
C9D73C24285C8C0C0044A279 /* SourceItemData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemData.swift; sourceTree = "<group>"; };
|
||||
C9D73C28285C8C4B0044A279 /* SourceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItem.swift; sourceTree = "<group>"; };
|
||||
C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemGroup.swift; sourceTree = "<group>"; };
|
||||
C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLink.swift; sourceTree = "<group>"; };
|
||||
C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemContent.swift; sourceTree = "<group>"; };
|
||||
C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemImage.swift; sourceTree = "<group>"; };
|
||||
C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLabel.swift; sourceTree = "<group>"; };
|
||||
C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeContent.swift; sourceTree = "<group>"; };
|
||||
C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeHeader.swift; sourceTree = "<group>"; };
|
||||
C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeContent.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -115,13 +125,50 @@
|
|||
path = Styles;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C966875D29B7641F007BB3F5 /* Node */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C966875F29B7641F007BB3F5 /* Conforming */,
|
||||
C966876729B7641F007BB3F5 /* Views */,
|
||||
C966875E29B7641F007BB3F5 /* FilteredNode.swift */,
|
||||
C966876629B7641F007BB3F5 /* NodeListBuilder.swift */,
|
||||
C966876B29B7641F007BB3F5 /* Node.swift */,
|
||||
);
|
||||
path = Node;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
C966876729B7641F007BB3F5 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C966876829B7641F007BB3F5 /* NodeLabel.swift */,
|
||||
C966876929B7641F007BB3F5 /* FilteredNodeView.swift */,
|
||||
C966876A29B7641F007BB3F5 /* NodeView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C982F84C283B9F9000D491F4 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C982F857283B9F9000D491F4 /* SimDirs */,
|
||||
C982F856283B9F9000D491F4 /* Products */,
|
||||
);
|
||||
indentWidth = 4;
|
||||
sourceTree = "<group>";
|
||||
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 = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 } ?? {
|
||||
|
|
|
|||
33
SimDirs/Node/Conforming/SimApp+Node.swift
Normal file
33
SimDirs/Node/Conforming/SimApp+Node.swift
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
31
SimDirs/Node/Conforming/SimDevice+Node.swift
Normal file
31
SimDirs/Node/Conforming/SimDevice+Node.swift
Normal file
|
|
@ -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 : []
|
||||
}
|
||||
}
|
||||
20
SimDirs/Node/Conforming/SimDeviceType+Node.swift
Normal file
20
SimDirs/Node/Conforming/SimDeviceType+Node.swift
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
21
SimDirs/Node/Conforming/SimPlatform+Node.swift
Normal file
21
SimDirs/Node/Conforming/SimPlatform+Node.swift
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
21
SimDirs/Node/Conforming/SimProductFamily+Node.swift
Normal file
21
SimDirs/Node/Conforming/SimProductFamily+Node.swift
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
24
SimDirs/Node/Conforming/SimRuntime+Node.swift
Normal file
24
SimDirs/Node/Conforming/SimRuntime+Node.swift
Normal file
|
|
@ -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 : []
|
||||
}
|
||||
}
|
||||
73
SimDirs/Node/FilteredNode.swift
Normal file
73
SimDirs/Node/FilteredNode.swift
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// FilteredNode.swift
|
||||
// NodeItems
|
||||
//
|
||||
// Created by Casey Fleser on 3/3/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
class FilteredNode<T: Node>: Node, ObservableObject {
|
||||
typealias FilteredList = [FilteredNode<T.List.Element>]
|
||||
|
||||
@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<Element>] {
|
||||
self.map { FilteredNode($0) }
|
||||
}
|
||||
}
|
||||
|
||||
158
SimDirs/Node/Node.swift
Normal file
158
SimDirs/Node/Node.swift
Normal file
|
|
@ -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<Item: Node>(emptyIsNil: Bool = false, @NodeListBuilder items: () -> [Item]) -> NodeLink<Self, Item> {
|
||||
link(emptyIsNil: emptyIsNil, to: items)
|
||||
}
|
||||
|
||||
func link<Item: Node>(emptyIsNil: Bool = false, @NodeListBuilder to items: () -> [Item]) -> NodeLink<Self, Item> {
|
||||
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<Item: Node>(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<Item: Node>: 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<Base: Node, Item: Node>: 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<Item> {
|
||||
self.base = RootNode()
|
||||
self.items = items()
|
||||
}
|
||||
|
||||
func icon(forHeader: Bool) -> some View {
|
||||
base.icon(forHeader: forHeader)
|
||||
}
|
||||
|
||||
func matchedFilterOptions() -> SourceFilter.Options {
|
||||
return base.matchedFilterOptions()
|
||||
}
|
||||
}
|
||||
173
SimDirs/Node/NodeListBuilder.swift
Normal file
173
SimDirs/Node/NodeListBuilder.swift
Normal file
|
|
@ -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<A: P, B: P>: P, CustomStringConvertible {
|
||||
typealias List = [NodeListBuilder.OneOf<A.List.Element, B.List.Element>]
|
||||
|
||||
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<A - \(A.self), B - \(B.self)>: \(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: P>(_ c: [C]) -> [C] {
|
||||
c
|
||||
}
|
||||
|
||||
static func buildBlock<C0: P, C1: P> (
|
||||
_ c0: [C0], _ c1: [C1]) -> [OneOf<C0, C1>]
|
||||
{
|
||||
[buildEither(first: c0), buildEither(second: c1)].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildBlock<C0: P, C1: P, C2: P> (
|
||||
_ c0: [C0], _ c1: [C1], _ c2: [C2]) -> [OneOf<OneOf<C0, C1>, C2>]
|
||||
{
|
||||
[buildEither(first: buildBlock(c0, c1)), buildEither(second: c2)].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildBlock<C0: P, C1: P, C2: P, C3: P> (
|
||||
_ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3]) -> [OneOf<OneOf<C0, C1>, OneOf<C2, C3>>]
|
||||
{
|
||||
[buildEither(first: buildBlock(c0, c1)), buildEither(second: buildBlock(c2, c3))].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildBlock<C0: P, C1: P, C2: P, C3: P, C4: P> (
|
||||
_ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4]) -> [OneOf<OneOf<OneOf<C0, C1>, OneOf<C2, C3>>, C4>]
|
||||
{
|
||||
[buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: c4)].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildBlock<C0: P, C1: P, C2: P, C3: P, C4: P, C5: P> (
|
||||
_ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4], _ c5: [C5]) -> [OneOf<OneOf<OneOf<C0, C1>, OneOf<C2, C3>>, OneOf<C4, C5>>]
|
||||
{
|
||||
[buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: buildBlock(c4, c5))].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildBlock<C0: P, C1: P, C2: P, C3: P, C4: P, C5: P, C6: P> (
|
||||
_ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4], _ c5: [C5], _ c6: [C6]) -> [OneOf<OneOf<OneOf<C0, C1>, OneOf<C2, C3>>, OneOf<OneOf<C4, C5>, C6>>]
|
||||
{
|
||||
[buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: buildBlock(c4, c5, c6))].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildBlock<C0: P, C1: P, C2: P, C3: P, C4: P, C5: P, C6: P, C7: P> (
|
||||
_ c0: [C0], _ c1: [C1], _ c2: [C2], _ c3: [C3], _ c4: [C4], _ c5: [C5], _ c6: [C6], _ c7: [C7]) -> [OneOf<OneOf<OneOf<C0, C1>, OneOf<C2, C3>>, OneOf<OneOf<C4, C5>, OneOf<C6, C7>>>]
|
||||
{
|
||||
[buildEither(first: buildBlock(c0, c1, c2, c3)), buildEither(second: buildBlock(c4, c5, c6, c7))].flatMap { $0 }
|
||||
}
|
||||
|
||||
// static func buildBlock<C: Node>(_ c: [C]...) -> [C] {
|
||||
// c.flatMap { $0 }
|
||||
// }
|
||||
//
|
||||
// Same type buildBlocks. This works but buildBlock<C: Node>(_ c: [C]...) -> [C] confuses the compiler
|
||||
|
||||
static func buildBlock<C0: Node> (_ c0: [C0], _ c1: [C0]) -> [C0] {
|
||||
[c0, c1].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildBlock<C0: Node> (_ c0: [C0], _ c1: [C0], _ c2: [C0]) -> [C0] {
|
||||
[c0, c1, c2].flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildEither<C0: P, C1: P>(first c0: [C0]) -> [OneOf<C0, C1>] {
|
||||
c0.map { OneOf<C0, C1>.a($0) }
|
||||
}
|
||||
|
||||
static func buildEither<C0: P, C1: P>(second c1: [C1]) -> [OneOf<C0, C1>] {
|
||||
c1.map { OneOf<C0, C1>.b($0) }
|
||||
}
|
||||
|
||||
static func buildOptional<C: P>(_ c: [C]?) -> [C] {
|
||||
c ?? []
|
||||
}
|
||||
|
||||
static func buildArray<C: P>(_ c: [[C]]) -> [C] {
|
||||
c.flatMap { $0 }
|
||||
}
|
||||
|
||||
static func buildExpression<N: Node>(_ node: N) -> [N] {
|
||||
[node]
|
||||
}
|
||||
|
||||
static func buildExpression<NL: NodeList>(_ nodeList: NL) -> [NL.Element] {
|
||||
Array(nodeList)
|
||||
}
|
||||
|
||||
static func buildExpression<NS: NodeSource>(_ nodeSource: NS) -> [NS.List.Element] {
|
||||
nodeSource.items.map({ buildExpression($0) }) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
95
SimDirs/Node/Views/FilteredNodeView.swift
Normal file
95
SimDirs/Node/Views/FilteredNodeView.swift
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// FilteredNodeView.swift
|
||||
// NodeItems
|
||||
//
|
||||
// Created by Casey Fleser on 3/3/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct FilteredNodeView<T: Node>: View {
|
||||
@StateObject var node : FilteredNode<T>
|
||||
@Binding var filter : SourceFilter
|
||||
|
||||
init(_ node: T, filter: Binding<SourceFilter>) {
|
||||
self._node = StateObject(wrappedValue: FilteredNode(node))
|
||||
self._filter = filter
|
||||
}
|
||||
|
||||
init<Item: Node>(filter: Binding<SourceFilter>, @NodeListBuilder items: () -> [Item]) where T == RootNode<Item> {
|
||||
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<T>
|
||||
|
||||
var visibleItems : FilteredNode<T>.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<T: Node>: View {
|
||||
var items : [FilteredNode<T>]
|
||||
|
||||
init(items: [FilteredNode<T>]) {
|
||||
self.items = items
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ForEach(items.indices, id: \.self) { index in
|
||||
Item(node: items[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Item<T: Node>: View {
|
||||
@ObservedObject var node : FilteredNode<T>
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
SimDirs/Node/Views/NodeLabel.swift
Normal file
61
SimDirs/Node/Views/NodeLabel.swift
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// NodeLabel.swift
|
||||
// NodeItems
|
||||
//
|
||||
// Created by Casey Fleser on 3/3/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NodeLabel<T: Node>: View {
|
||||
@ObservedObject var node : FilteredNode<T>
|
||||
|
||||
init(_ node: FilteredNode<T>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Item: SourceItem>: View {
|
||||
var item : Item
|
||||
struct NodeView<Item: Node>: 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<Item: SourceItem>: View {
|
|||
// --- Content section ---
|
||||
ScrollView {
|
||||
HStack {
|
||||
item.content
|
||||
node.content
|
||||
.padding(.top, 4.0)
|
||||
.padding(.trailing)
|
||||
Spacer()
|
||||
|
|
@ -41,27 +43,18 @@ struct SourceItemContent<Item: SourceItem>: 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T: SourceItem>(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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Model: SourceItemData, Child: SourceItem>: 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
|
||||
}
|
||||
|
|
@ -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 { "" }
|
||||
}
|
||||
|
|
@ -9,29 +9,6 @@ import Foundation
|
|||
import Combine
|
||||
|
||||
class SourceState: ObservableObject {
|
||||
typealias ProductFamily = SourceItemVal<SimProductFamily, DeviceType_DS>
|
||||
typealias Platform = SourceItemVal<SimPlatform, Runtime_RT>
|
||||
typealias DeviceType_DS = SourceItemVal<SimDeviceType, Runtime_DS>
|
||||
typealias DeviceType_RT = SourceItemVal<SimDeviceType, Device>
|
||||
typealias Runtime_DS = SourceItemVal<SimRuntime, Device>
|
||||
typealias Runtime_RT = SourceItemVal<SimRuntime, DeviceType_RT>
|
||||
typealias Device = SourceItemVal<SimDevice, App>
|
||||
typealias App = SourceItemVal<SimApp, SourceItemNone>
|
||||
|
||||
enum Base: Identifiable {
|
||||
case placeholder(id: UUID = UUID())
|
||||
case device(id: UUID = UUID(), SourceItemVal<SourceItemDataNone, ProductFamily>)
|
||||
case runtime(id: UUID = UUID(), SourceItemVal<SourceItemDataNone, Platform>)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@
|
|||
import SwiftUI
|
||||
|
||||
extension SimApp {
|
||||
public var content : some View { AppContent(app: self) }
|
||||
|
||||
var isLaunched : Bool {
|
||||
get { state.isOn }
|
||||
set { toggleLaunchState() }
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
extension SimApp {
|
||||
public var header : some View { AppHeader(app: self) }
|
||||
}
|
||||
|
||||
struct AppHeader: View {
|
||||
var app : SimApp
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import SwiftUI
|
|||
import UniformTypeIdentifiers
|
||||
|
||||
extension SimDevice {
|
||||
public var content : some View { DeviceContent(self) }
|
||||
|
||||
var scheme : ColorScheme? {
|
||||
get {
|
||||
switch appearance {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
extension SimDevice {
|
||||
public var header : some View { DeviceHeader(device: self) }
|
||||
}
|
||||
|
||||
struct DeviceHeader: View {
|
||||
@ObservedObject var device : SimDevice
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
extension SimDeviceType {
|
||||
public var content : some View { DeviceTypeContent(deviceType: self) }
|
||||
}
|
||||
|
||||
struct DeviceTypeContent: View {
|
||||
var deviceType : SimDeviceType
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
extension SimDeviceType {
|
||||
public var header : some View { DeviceTypeHeader(deviceType: self) }
|
||||
}
|
||||
|
||||
struct DeviceTypeHeader: View {
|
||||
var deviceType : SimDeviceType
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
extension SimRuntime {
|
||||
public var header : some View { RuntimeHeader(runtime: self) }
|
||||
}
|
||||
|
||||
struct RuntimeHeader: View {
|
||||
var runtime : SimRuntime
|
||||
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
//
|
||||
// SourceItemGroup.swift
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 6/20/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SourceItemGroup<Item: SourceItem>: 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.Child>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
//
|
||||
// SourceItemLabel.swift
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 6/20/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SourceItemLabel<Item: SourceItem>: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// SourceItemLink.swift
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 6/20/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SourceItemLink<Item: SourceItem>: 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue