mirror of
https://github.com/somegeekintn/SimDirs.git
synced 2026-04-27 14:57:40 +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 */; };
|
C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */; };
|
||||||
C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4B2861E37900C2EF35 /* AppHeader.swift */; };
|
C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4B2861E37900C2EF35 /* AppHeader.swift */; };
|
||||||
C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4D2861E4E400C2EF35 /* AppContent.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 */; };
|
C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */; };
|
||||||
C90DCC142896AAAA0072E403 /* ContentHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90DCC132896AAAA0072E403 /* ContentHeader.swift */; };
|
C90DCC142896AAAA0072E403 /* ContentHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90DCC132896AAAA0072E403 /* ContentHeader.swift */; };
|
||||||
C90DCC162896B0370072E403 /* AppearancePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90DCC152896B0370072E403 /* AppearancePicker.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 */; };
|
C927A0DB2846502300533D66 /* PathActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0DA2846502300533D66 /* PathActions.swift */; };
|
||||||
C95CC0F828B2411700928FAE /* AppearanceButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95CC0F728B2411700928FAE /* AppearanceButtonStyle.swift */; };
|
C95CC0F828B2411700928FAE /* AppearanceButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95CC0F728B2411700928FAE /* AppearanceButtonStyle.swift */; };
|
||||||
C95CC0FA28B2414900928FAE /* SystemIconButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95CC0F928B2414900928FAE /* SystemIconButtonStyle.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 */; };
|
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9779741284F6DE000706DFB /* ToolbarMenu.swift */; };
|
||||||
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F858283B9F9000D491F4 /* SimDirsApp.swift */; };
|
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F858283B9F9000D491F4 /* SimDirsApp.swift */; };
|
||||||
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F85A283B9F9000D491F4 /* ContentView.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 */; };
|
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87A283E40C800D491F4 /* SimCtl.swift */; };
|
||||||
C9BF5232289FE95D00BDDC91 /* DescriptiveToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */; };
|
C9BF5232289FE95D00BDDC91 /* DescriptiveToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */; };
|
||||||
C9BF5234289FE99600BDDC91 /* DescriptiveToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BF5233289FE99600BDDC91 /* DescriptiveToggleStyle.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 */; };
|
C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */; };
|
||||||
C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */; };
|
C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */; };
|
||||||
C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54D12860A24B00D46AB3 /* RuntimeContent.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeContent.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -115,13 +125,50 @@
|
||||||
path = Styles;
|
path = Styles;
|
||||||
sourceTree = "<group>";
|
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 = {
|
C982F84C283B9F9000D491F4 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C982F857283B9F9000D491F4 /* SimDirs */,
|
C982F857283B9F9000D491F4 /* SimDirs */,
|
||||||
C982F856283B9F9000D491F4 /* Products */,
|
C982F856283B9F9000D491F4 /* Products */,
|
||||||
);
|
);
|
||||||
|
indentWidth = 4;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
tabWidth = 4;
|
||||||
};
|
};
|
||||||
C982F856283B9F9000D491F4 /* Products */ = {
|
C982F856283B9F9000D491F4 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
|
|
@ -138,6 +185,7 @@
|
||||||
C982F85A283B9F9000D491F4 /* ContentView.swift */,
|
C982F85A283B9F9000D491F4 /* ContentView.swift */,
|
||||||
C927A0D82846414900533D66 /* Helpers.swift */,
|
C927A0D82846414900533D66 /* Helpers.swift */,
|
||||||
C982F867283BA09B00D491F4 /* Model */,
|
C982F867283BA09B00D491F4 /* Model */,
|
||||||
|
C966875D29B7641F007BB3F5 /* Node */,
|
||||||
C9D73C23285C8B3B0044A279 /* Presentation */,
|
C9D73C23285C8B3B0044A279 /* Presentation */,
|
||||||
C982F881283E7F0400D491F4 /* Views */,
|
C982F881283E7F0400D491F4 /* Views */,
|
||||||
C982F85C283B9F9200D491F4 /* Assets.xcassets */,
|
C982F85C283B9F9200D491F4 /* Assets.xcassets */,
|
||||||
|
|
@ -174,7 +222,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C95CC0F628B240F500928FAE /* Styles */,
|
C95CC0F628B240F500928FAE /* Styles */,
|
||||||
C9DD54C12860935300D46AB3 /* SourceItem Views */,
|
|
||||||
C9DD54CC2860992200D46AB3 /* Model Views */,
|
C9DD54CC2860992200D46AB3 /* Model Views */,
|
||||||
C90DCC152896B0370072E403 /* AppearancePicker.swift */,
|
C90DCC152896B0370072E403 /* AppearancePicker.swift */,
|
||||||
C90DCC132896AAAA0072E403 /* ContentHeader.swift */,
|
C90DCC132896AAAA0072E403 /* ContentHeader.swift */,
|
||||||
|
|
@ -190,26 +237,12 @@
|
||||||
C9D73C23285C8B3B0044A279 /* Presentation */ = {
|
C9D73C23285C8B3B0044A279 /* Presentation */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C9D73C28285C8C4B0044A279 /* SourceItem.swift */,
|
|
||||||
C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */,
|
C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */,
|
||||||
C9D73C24285C8C0C0044A279 /* SourceItemData.swift */,
|
|
||||||
C90BCE4F2861E9D000C2EF35 /* SourceState.swift */,
|
C90BCE4F2861E9D000C2EF35 /* SourceState.swift */,
|
||||||
);
|
);
|
||||||
path = Presentation;
|
path = Presentation;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
C9DD54CC2860992200D46AB3 /* Model Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -295,30 +328,35 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */,
|
C966877729B7641F007BB3F5 /* Node.swift in Sources */,
|
||||||
C9D73C25285C8C0C0044A279 /* SourceItemData.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 */,
|
C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */,
|
||||||
C927A0DB2846502300533D66 /* PathActions.swift in Sources */,
|
C927A0DB2846502300533D66 /* PathActions.swift in Sources */,
|
||||||
C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */,
|
C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */,
|
||||||
|
C966876F29B7641F007BB3F5 /* SimPlatform+Node.swift in Sources */,
|
||||||
C90DCC142896AAAA0072E403 /* ContentHeader.swift in Sources */,
|
C90DCC142896AAAA0072E403 /* ContentHeader.swift in Sources */,
|
||||||
C9BF5232289FE95D00BDDC91 /* DescriptiveToggle.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 */,
|
C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */,
|
||||||
|
C966877829B76533007BB3F5 /* SourceState.swift in Sources */,
|
||||||
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */,
|
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */,
|
||||||
C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */,
|
|
||||||
C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */,
|
C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */,
|
||||||
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */,
|
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */,
|
||||||
C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */,
|
C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */,
|
||||||
C90DCC162896B0370072E403 /* AppearancePicker.swift in Sources */,
|
C90DCC162896B0370072E403 /* AppearancePicker.swift in Sources */,
|
||||||
C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */,
|
C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */,
|
||||||
C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */,
|
C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */,
|
||||||
|
C966877429B7641F007BB3F5 /* NodeLabel.swift in Sources */,
|
||||||
C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */,
|
C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */,
|
||||||
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */,
|
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */,
|
||||||
C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */,
|
C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */,
|
||||||
C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */,
|
|
||||||
C9BF5234289FE99600BDDC91 /* DescriptiveToggleStyle.swift in Sources */,
|
C9BF5234289FE99600BDDC91 /* DescriptiveToggleStyle.swift in Sources */,
|
||||||
|
C966876D29B7641F007BB3F5 /* SimApp+Node.swift in Sources */,
|
||||||
C982F873283CE9AD00D491F4 /* SimDeviceType.swift in Sources */,
|
C982F873283CE9AD00D491F4 /* SimDeviceType.swift in Sources */,
|
||||||
C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */,
|
|
||||||
C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */,
|
C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */,
|
||||||
C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */,
|
C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */,
|
||||||
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */,
|
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */,
|
||||||
|
|
@ -328,10 +366,10 @@
|
||||||
C982F86B283BA22100D491F4 /* SimPlatform.swift in Sources */,
|
C982F86B283BA22100D491F4 /* SimPlatform.swift in Sources */,
|
||||||
C927A0D92846414900533D66 /* Helpers.swift in Sources */,
|
C927A0D92846414900533D66 /* Helpers.swift in Sources */,
|
||||||
C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */,
|
C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */,
|
||||||
C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */,
|
C966877329B7641F007BB3F5 /* NodeListBuilder.swift in Sources */,
|
||||||
C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */,
|
|
||||||
C982F879283D042E00D491F4 /* SimModel.swift in Sources */,
|
C982F879283D042E00D491F4 /* SimModel.swift in Sources */,
|
||||||
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */,
|
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */,
|
||||||
|
C966876E29B7641F007BB3F5 /* SimProductFamily+Node.swift in Sources */,
|
||||||
C95CC0F828B2411700928FAE /* AppearanceButtonStyle.swift in Sources */,
|
C95CC0F828B2411700928FAE /* AppearanceButtonStyle.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ObservedObject var state : SourceState
|
@ObservedObject var state : SourceState
|
||||||
|
@State var filter = SourceFilter.restore()
|
||||||
|
|
||||||
init(model: SimModel) {
|
init(model: SimModel) {
|
||||||
state = SourceState(model: model)
|
state = SourceState(model: model)
|
||||||
|
|
@ -17,32 +18,13 @@ struct ContentView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(selection: $state.selection) {
|
FilteredNodeView(filter: $filter) { state.items }
|
||||||
Divider()
|
.id(state.style)
|
||||||
|
.toolbar { ToolbarItem { ToolbarMenu(state: state, filter: $filter) } }
|
||||||
switch state.base {
|
.frame(minWidth: 200)
|
||||||
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)
|
|
||||||
|
|
||||||
Image("Icon-256") // Initial View
|
Image("Icon-256") // Initial View
|
||||||
}
|
}
|
||||||
.searchable(text: $state.filter.searchTerm, placement: .sidebar)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,3 @@ extension SimApp {
|
||||||
var isOn : Bool { self == .launched }
|
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 dataPathSize : Int
|
||||||
let logPath : String
|
let logPath : String
|
||||||
let deviceTypeIdentifier : String
|
let deviceTypeIdentifier : String
|
||||||
|
var deviceType : SimDeviceType?
|
||||||
var deviceModel : String?
|
var deviceModel : String?
|
||||||
var apps = [SimApp]()
|
var apps = [SimApp]()
|
||||||
var dataURL : URL { URL(fileURLWithPath: dataPath) }
|
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 {
|
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 {
|
func of(deviceType: SimDeviceType) -> Self {
|
||||||
filter { $0.isDeviceOfType(deviceType) }
|
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 {
|
extension Array where Element == SimDeviceType {
|
||||||
func supporting(productFamily: SimProductFamily) -> Self {
|
func supporting(productFamily: SimProductFamily) -> Self {
|
||||||
filter { $0.supports(productFamily: productFamily) }
|
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 {
|
extension Array where Element == SimRuntime {
|
||||||
mutating func indexOfMatchedOrCreated(identifier: String) throws -> Index {
|
mutating func indexOfMatchedOrCreated(identifier: String) throws -> Index {
|
||||||
return try firstIndex { $0.identifier == identifier } ?? {
|
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
|
// SimDirs
|
||||||
//
|
//
|
||||||
// Created by Casey Fleser on 6/20/22.
|
// Created by Casey Fleser on 3/6/23.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
import SwiftUI
|
struct NodeView<Item: Node>: View {
|
||||||
|
var node : Item
|
||||||
struct SourceItemContent<Item: SourceItem>: View {
|
|
||||||
var item : Item
|
|
||||||
|
|
||||||
|
init(_ node: Item) {
|
||||||
|
self.node = node
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
VStack(alignment: .leading, spacing: 0.0) {
|
VStack(alignment: .leading, spacing: 0.0) {
|
||||||
// --- Header section ---
|
// --- Header section ---
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(item.headerTitle)
|
Text(node.headerTitle)
|
||||||
.font(.system(size: 20))
|
.font(.system(size: 20))
|
||||||
.padding(.top, 12.0)
|
.padding(.top, 12.0)
|
||||||
.padding(.bottom, 8.0)
|
.padding(.bottom, 8.0)
|
||||||
item.header
|
node.header
|
||||||
.padding(.trailing, 136.0)
|
.padding(.trailing, 136.0)
|
||||||
}
|
}
|
||||||
.padding([.leading, .trailing])
|
.padding([.leading, .trailing])
|
||||||
|
|
@ -31,7 +33,7 @@ struct SourceItemContent<Item: SourceItem>: View {
|
||||||
// --- Content section ---
|
// --- Content section ---
|
||||||
ScrollView {
|
ScrollView {
|
||||||
HStack {
|
HStack {
|
||||||
item.content
|
node.content
|
||||||
.padding(.top, 4.0)
|
.padding(.top, 4.0)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
@ -41,27 +43,18 @@ struct SourceItemContent<Item: SourceItem>: View {
|
||||||
.padding([.leading, .top])
|
.padding([.leading, .top])
|
||||||
.background(.background)
|
.background(.background)
|
||||||
}
|
}
|
||||||
.overlay(
|
.overlay(alignment: .topTrailing) {
|
||||||
SourceItemImage(imageDesc: item.imageDesc, isLabelImage: false)
|
node.icon(forHeader: true)
|
||||||
.padding([.top, .trailing], 24.0),
|
.padding([.top, .trailing], 24.0)
|
||||||
alignment: .topTrailing
|
}
|
||||||
)
|
|
||||||
.padding(.top, -geometry.frame(in: .global).origin.y)
|
.padding(.top, -geometry.frame(in: .global).origin.y)
|
||||||
}
|
}
|
||||||
.navigationTitle(item.title)
|
.navigationTitle(node.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SourceItemContent_Previews: PreviewProvider {
|
struct NodeView_Previews: PreviewProvider {
|
||||||
static var state = SourceState(model: SimModel())
|
|
||||||
static var sampleItems = state.deviceStyleItems()[0...1]
|
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ForEach(sampleItems) { item in
|
NodeView(SimPlatform.iOS)
|
||||||
SourceItemContent(item: item)
|
|
||||||
.preferredColorScheme(.dark)
|
|
||||||
SourceItemContent(item: item)
|
|
||||||
.preferredColorScheme(.light)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct SourceFilter {
|
struct SourceFilter: Equatable {
|
||||||
struct Options: OptionSet, CaseIterable {
|
struct Options: OptionSet, CaseIterable {
|
||||||
let rawValue: Int
|
let rawValue: Int
|
||||||
|
|
||||||
|
|
@ -15,27 +15,21 @@ struct SourceFilter {
|
||||||
static let runtimeInstalled = Options(rawValue: 1 << 1)
|
static let runtimeInstalled = Options(rawValue: 1 << 1)
|
||||||
|
|
||||||
static var allCases : [Options] = [.withApps, .runtimeInstalled]
|
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 searchTerm = ""
|
||||||
var options = Options() { didSet { UserDefaults.standard.set(options.rawValue, forKey: "FilterOptions") } }
|
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 {
|
static func restore() -> SourceFilter {
|
||||||
var filter = 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
|
import Combine
|
||||||
|
|
||||||
class SourceState: ObservableObject {
|
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 {
|
enum Style: Int, CaseIterable, Identifiable {
|
||||||
case placeholder
|
case placeholder
|
||||||
case byDevice
|
case byDevice
|
||||||
|
|
@ -53,34 +30,22 @@ class SourceState: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var style = Style.placeholder { didSet { rebuildBase() } }
|
@Published var style = Style.placeholder // { didSet { rebuildBase() } }
|
||||||
@Published var filter = SourceFilter.restore() { didSet { applyFilter() } }
|
|
||||||
@Published var selection : UUID?
|
@Published var selection : UUID?
|
||||||
|
|
||||||
var model : SimModel
|
var model : SimModel
|
||||||
var base = Base.placeholder()
|
|
||||||
var deviceUpdates : Cancellable?
|
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) {
|
init(model: SimModel) {
|
||||||
self.style = .byDevice
|
self.style = .byDevice
|
||||||
self.model = model
|
self.model = model
|
||||||
|
|
||||||
self.rebuildBase()
|
|
||||||
|
|
||||||
deviceUpdates = model.deviceUpdates.sink(receiveValue: applyDeviceUpdates)
|
deviceUpdates = model.deviceUpdates.sink(receiveValue: applyDeviceUpdates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#warning("TODO: still need to apply updates")
|
||||||
func applyDeviceUpdates(_ updates: SimDevicesUpdates) {
|
func applyDeviceUpdates(_ updates: SimDevicesUpdates) {
|
||||||
|
#if false
|
||||||
switch base {
|
switch base {
|
||||||
case .placeholder:
|
case .placeholder:
|
||||||
break
|
break
|
||||||
|
|
@ -122,69 +87,37 @@ class SourceState: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFilter()
|
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] {
|
@NodeListBuilder
|
||||||
SimPlatform.presentation.map { platform in
|
var runtimeStyleItems: some NodeList {
|
||||||
Platform(data: platform, children: model.runtimes.supporting(platform: platform).map { runtime in
|
SimPlatform.presentation.linkEachTo(emptyIsNil: true) { platform in
|
||||||
Runtime_RT(data: runtime, children: model.deviceTypes.supporting(runtime: runtime).map { devType in
|
model.runtimes.supporting(platform: platform).linkEachTo(emptyIsNil: true) { runtime in
|
||||||
DeviceType_RT(data: devType, children: runtime.devices.of(deviceType: devType).map { device in
|
model.deviceTypes.supporting(runtime: runtime).linkEachTo(emptyIsNil: true) { devType in
|
||||||
let imageDesc = devType.imageDesc.withColor(device.isAvailable ? .green : .red)
|
runtime.devices.linkingDeviceType(devType)
|
||||||
|
}
|
||||||
return Device(data: device, children: device.apps.map { app in App(data: app, children: []) }, customImgDesc: imageDesc)
|
}
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SimApp {
|
extension SimApp {
|
||||||
public var content : some View { AppContent(app: self) }
|
|
||||||
|
|
||||||
var isLaunched : Bool {
|
var isLaunched : Bool {
|
||||||
get { state.isOn }
|
get { state.isOn }
|
||||||
set { toggleLaunchState() }
|
set { toggleLaunchState() }
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SimApp {
|
|
||||||
public var header : some View { AppHeader(app: self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppHeader: View {
|
struct AppHeader: View {
|
||||||
var app : SimApp
|
var app : SimApp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ import SwiftUI
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
extension SimDevice {
|
extension SimDevice {
|
||||||
public var content : some View { DeviceContent(self) }
|
|
||||||
|
|
||||||
var scheme : ColorScheme? {
|
var scheme : ColorScheme? {
|
||||||
get {
|
get {
|
||||||
switch appearance {
|
switch appearance {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SimDevice {
|
|
||||||
public var header : some View { DeviceHeader(device: self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceHeader: View {
|
struct DeviceHeader: View {
|
||||||
@ObservedObject var device : SimDevice
|
@ObservedObject var device : SimDevice
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SimDeviceType {
|
|
||||||
public var content : some View { DeviceTypeContent(deviceType: self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceTypeContent: View {
|
struct DeviceTypeContent: View {
|
||||||
var deviceType : SimDeviceType
|
var deviceType : SimDeviceType
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SimDeviceType {
|
|
||||||
public var header : some View { DeviceTypeHeader(deviceType: self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceTypeHeader: View {
|
struct DeviceTypeHeader: View {
|
||||||
var deviceType : SimDeviceType
|
var deviceType : SimDeviceType
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SimRuntime {
|
|
||||||
public var content : some View { RuntimeContent(runtime: self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RuntimeContent: View {
|
struct RuntimeContent: View {
|
||||||
struct SupportedItem: Identifiable {
|
struct SupportedItem: Identifiable {
|
||||||
let name : String
|
let name : String
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SimRuntime {
|
|
||||||
public var header : some View { RuntimeHeader(runtime: self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RuntimeHeader: View {
|
struct RuntimeHeader: View {
|
||||||
var runtime : SimRuntime
|
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 {
|
struct ToolbarMenu: View {
|
||||||
@ObservedObject var state : SourceState
|
@ObservedObject var state : SourceState
|
||||||
|
@Binding var filter : SourceFilter
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Menu {
|
Menu {
|
||||||
|
|
@ -20,8 +21,8 @@ struct ToolbarMenu: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.inline)
|
.pickerStyle(.inline)
|
||||||
Toggle(isOn: $state.filterApps) { Label("With Apps", systemImage: "app.fill") }
|
Toggle(isOn: $filter.filterApps) { Label("With Apps", systemImage: "app.fill") }
|
||||||
Toggle(isOn: $state.filterRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") }
|
Toggle(isOn: $filter.filterRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") }
|
||||||
} label: {
|
} label: {
|
||||||
Label("Filter", systemImage: "slider.horizontal.3")
|
Label("Filter", systemImage: "slider.horizontal.3")
|
||||||
}
|
}
|
||||||
|
|
@ -29,10 +30,10 @@ struct ToolbarMenu: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ToolbarMenu_Previews: PreviewProvider {
|
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 {
|
static var previews: some View {
|
||||||
ToolbarMenu(state: state)
|
ToolbarMenu(state: state, filter: $filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue