├── .swiftlint.yml
├── BioViewer.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
│ └── xcuserdata
│ │ └── raul.xcuserdatad
│ │ ├── UserInterfaceState.xcuserstate
│ │ └── WorkspaceSettings.xcsettings
├── xcshareddata
│ ├── xcbaselines
│ │ └── C44FC07E2641AF1E00459E01.xcbaseline
│ │ │ ├── F9975422-2017-4B21-ACC6-016EB4D927C6.plist
│ │ │ └── Info.plist
│ └── xcschemes
│ │ ├── BioViewer.xcscheme
│ │ └── BioViewerTests.xcscheme
└── xcuserdata
│ ├── andro.xcuserdatad
│ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── raul.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── BioViewer
├── AppState.swift
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x-1.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x-1.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x-1.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x-1.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ ├── Icon-App-83.5x83.5@2x.png
│ │ ├── Icon-MacOS-128x128@1x.png
│ │ ├── Icon-MacOS-128x128@2x.png
│ │ ├── Icon-MacOS-16x16@1x.png
│ │ ├── Icon-MacOS-256x256@1x.png
│ │ ├── Icon-MacOS-256x256@2x.png
│ │ ├── Icon-MacOS-32x32@2x.png
│ │ ├── Icon-MacOS-512x512@1x.png
│ │ └── Icon-MacOS-512x512@2x-1.png
│ ├── Contents.json
│ ├── DefaultViewfinderImage.imageset
│ │ ├── Contents.json
│ │ └── DefaultViewfinderImage.png
│ ├── PlaceholderCoffee.imageset
│ │ ├── Contents.json
│ │ ├── Screenshot 2021-05-24 at 00.28.39-1.png
│ │ └── Screenshot 2021-05-24 at 00.28.39.png
│ └── Shutter.imageset
│ │ ├── Contents.json
│ │ └── Lights.png
├── BioBench
│ ├── BenchmarkedProtein.swift
│ ├── BioBenchConfig.swift
│ ├── BioBenchResultsView.swift
│ ├── BioBenchView.swift
│ └── BioBenchViewModel.swift
├── BioViewer-Bridging-Header.h
├── BioViewer.entitlements
├── BioViewerApp.swift
├── BioViewerCommands.swift
├── BioViewerLogger.swift
├── Components
│ ├── BioViewerPicker.swift
│ ├── EmptyDataRow.swift
│ ├── PersistentDisclosureGroup.swift
│ └── Rows
│ │ ├── ButtonRow.swift
│ │ ├── ColorPaletteRow.swift
│ │ ├── ColorPickerRow.swift
│ │ ├── ComputedPropertyRow.swift
│ │ ├── FocalLengthRow.swift
│ │ ├── InfoLongTextRow.swift
│ │ ├── InfoPopoverRow.swift
│ │ ├── InfoTextRow.swift
│ │ ├── InputWithButtonRow.swift
│ │ ├── LegacyPickerRow.swift
│ │ ├── PickerRow.swift
│ │ ├── RangeRow.swift
│ │ ├── SliderRow.swift
│ │ ├── SunDirectionRow.swift
│ │ └── SwitchRow.swift
├── Data structures
│ ├── AtomElement.swift
│ ├── BoundingVolumes.swift
│ ├── ColorPalettes.swift
│ ├── ProteinDataSource
│ │ ├── ProteinDataSource.swift
│ │ └── ProteinDataSourceError.swift
│ ├── RCSB
│ │ ├── RCSBError.swift
│ │ └── RCSBRequest.swift
│ ├── Residue.swift
│ └── SecondaryStructure.swift
├── Extensions
│ └── UserDefaults+Extension.swift
├── Files
│ ├── BioViewerWorkspace.swift
│ ├── ExportError.swift
│ ├── Import
│ │ ├── FileImporter.swift
│ │ ├── FileParser.swift
│ │ ├── ImageExporter.swift
│ │ ├── ImportDroppedFilesDelegate.swift
│ │ ├── ImportError.swift
│ │ └── ImportedUTTypes.swift
│ └── WorkspaceExporter.swift
├── Info.plist
├── JSONs
│ └── RCSBSuggestionData.json
├── Media Assets
│ ├── 3JBT.pdb
│ ├── ShutterClosed.aiff
│ └── ShutterOpen.aiff
├── Metal
│ ├── AtomRadii+Extensions.swift
│ ├── Compute
│ │ ├── ComputeAmbientOcclusion.metal
│ │ ├── ComputeMolecularSurfaceUtility.swift
│ │ ├── ComputeSDFGrid.metal
│ │ ├── FillColorBuffer.metal
│ │ ├── FillColorInput.h
│ │ ├── FillColorInputUtility.swift
│ │ └── SDFGrid.h
│ ├── Functions
│ │ ├── ComputeAmbientOcclusion.swift
│ │ ├── ComputeSDFGrid.swift
│ │ ├── CreateBondsGeometry.swift
│ │ ├── CreateImpostorSpheres.swift
│ │ ├── CreateSphereModel.swift
│ │ ├── MakeBufferFromArray.swift
│ │ ├── MetalScheduler.swift
│ │ └── PipelineStateBundle.swift
│ ├── Meshes
│ │ ├── AtomProperties.h
│ │ ├── BillboardVertexBuffers.swift
│ │ ├── CreateAtomMesh.metal
│ │ ├── CreateImpostorBonds.metal
│ │ ├── CreateImpostorSpheres.metal
│ │ └── GeneratedVertex.h
│ ├── Renderer
│ │ ├── Camera.swift
│ │ ├── ComputePasses
│ │ │ ├── ComputePipelines.swift
│ │ │ └── FillColorPass.swift
│ │ ├── ConfigurationSelector.swift
│ │ ├── ConnectivityGenerator.swift
│ │ ├── Data Structures
│ │ │ └── VisualizationConfiguration.swift
│ │ ├── Draw
│ │ │ ├── ProteinDraw.swift
│ │ │ └── ProteinHighQualityRender.swift
│ │ ├── MutableState
│ │ │ ├── MutableState.swift
│ │ │ └── PopulateVisualizationBuffers.swift
│ │ ├── ProteinRenderTarget.swift
│ │ ├── ProteinRenderer.swift
│ │ ├── Ray.swift
│ │ ├── RenderPasses
│ │ │ ├── DebugPass
│ │ │ │ └── PointsPass.swift
│ │ │ ├── ImpostorPass
│ │ │ │ ├── DepthPrePassStage.swift
│ │ │ │ └── ImpostorPass.swift
│ │ │ ├── Postprocessing
│ │ │ │ ├── CopyToDrawable.swift
│ │ │ │ ├── ShadowBlurPass.swift
│ │ │ │ └── Upscaling
│ │ │ │ │ ├── MetalFXScalers.swift
│ │ │ │ │ └── MetalFXUpscaling.swift
│ │ │ ├── RendererPipelines.swift
│ │ │ └── ShadowPass
│ │ │ │ ├── ShadowDepthPrePass.swift
│ │ │ │ └── ShadowPass.swift
│ │ ├── Scene
│ │ │ ├── MetalScene.swift
│ │ │ └── SceneAnimator.swift
│ │ ├── Textures
│ │ │ ├── AmbientOcclusion3DTexture.swift
│ │ │ ├── BenchmarkTextures.swift
│ │ │ ├── DepthPrePassTextures.swift
│ │ │ ├── HQTextures.swift
│ │ │ ├── MetalFXUpscaledTexture.swift
│ │ │ ├── ProteinRenderedTextures.swift
│ │ │ ├── ShadowTextures.swift
│ │ │ └── TextureToImage.swift
│ │ └── VisualizationBufferLoader.swift
│ ├── Shaders
│ │ ├── BasicShader.metal
│ │ ├── DebugShader
│ │ │ └── DebugPointShader.metal
│ │ ├── DepthPrePassShader.metal
│ │ ├── FrameData.h
│ │ ├── ImpostorBondShader.metal
│ │ ├── ImpostorSphereShader.metal
│ │ ├── Postprocessing
│ │ │ ├── MotionTexture.metal
│ │ │ ├── ReprojectionData.h
│ │ │ └── ShadowBlur.metal
│ │ ├── ShaderCommon.h
│ │ ├── ShadowDepthPrePassShader.metal
│ │ └── ShadowShader.metal
│ ├── SharedDataStructs.h
│ └── Utilities
│ │ ├── MatrixTransform.swift
│ │ └── SIMDExtensions.swift
├── MetalLegacySupport.swift
├── Models
│ └── Protein
│ │ └── ProteinFileInfo.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── ProteinMath.swift
├── Views
│ ├── MainView.swift
│ ├── Onboarding
│ │ ├── ChangeLog.plist
│ │ ├── NewsRow.swift
│ │ ├── WhatsNewView.swift
│ │ └── WhatsNewViewModel.swift
│ ├── PresentedViews
│ │ ├── PeriodicTableContentView.swift
│ │ └── PeriodicTableView.swift
│ ├── ProteinViews
│ │ ├── Data
│ │ │ ├── BallAndStickRadiusOptions.swift
│ │ │ ├── ProteinColorModel.swift
│ │ │ ├── ProteinColorViewModel.swift
│ │ │ ├── ProteinGraphicsSettings.swift
│ │ │ ├── ProteinShadowsViewModel.swift
│ │ │ ├── ProteinViewModel.swift
│ │ │ ├── ProteinVisualizationOption.swift
│ │ │ ├── ProteinVisualizationViewModel.swift
│ │ │ ├── SelectionModel.swift
│ │ │ └── SolidSpheresRadiusOptions.swift
│ │ ├── DynamicStructureControlView.swift
│ │ ├── ImportProteins
│ │ │ ├── ImportRowView.swift
│ │ │ ├── ProteinImportView.swift
│ │ │ ├── ProteinImportViewModel.swift
│ │ │ └── ProteinRCSBViews
│ │ │ │ ├── RCSBDetailView.swift
│ │ │ │ ├── RCSBEmptyImportView.swift
│ │ │ │ ├── RCSBImportView.swift
│ │ │ │ ├── RCSBImportViewModel.swift
│ │ │ │ ├── RCSBRowView.swift
│ │ │ │ ├── RCSBSuggestionsView.swift
│ │ │ │ └── RCSBSuggestionsViewModel.swift
│ │ ├── PhotoMode
│ │ │ ├── PhotoModeConfig.swift
│ │ │ ├── PhotoModeContent.swift
│ │ │ ├── PhotoModeContentHeaderView.swift
│ │ │ ├── PhotoModeFooter.swift
│ │ │ ├── PhotoModeUnsupportedView.swift
│ │ │ ├── PhotoModeView.swift
│ │ │ ├── PhotoModeViewModel.swift
│ │ │ ├── PhotoModeViewfinder.swift
│ │ │ └── ShutterAnimator.swift
│ │ ├── ProeteinSequenceView.swift
│ │ ├── ProteinMetal
│ │ │ ├── ProteinMetalView.swift
│ │ │ ├── ProteinMetalViewController.swift
│ │ │ └── ProteinRenderedView.swift
│ │ ├── ProteinSequenceView.swift
│ │ ├── ProteinView.swift
│ │ ├── SelectTool
│ │ │ ├── SelectedAtom.swift
│ │ │ ├── SelectedAtomContentView.swift
│ │ │ └── SelectedDebugView.swift
│ │ ├── Sidebar
│ │ │ ├── ProteinSidebar.swift
│ │ │ └── Segments
│ │ │ │ ├── AppearanceSegmentProtein
│ │ │ │ ├── AppearanceSegmentProtein.swift
│ │ │ │ └── Subsections
│ │ │ │ │ ├── ColorSection.swift
│ │ │ │ │ ├── ShadowsSection.swift
│ │ │ │ │ └── VisualizationSection.swift
│ │ │ │ ├── FileSegmentProtein
│ │ │ │ ├── FileAtomElementPopover
│ │ │ │ │ ├── CompositionItem.swift
│ │ │ │ │ ├── FileCompositionChartView.swift
│ │ │ │ │ └── FileCompositionView.swift
│ │ │ │ ├── FileRow.swift
│ │ │ │ ├── FileSegmentProtein.swift
│ │ │ │ ├── FileSource
│ │ │ │ │ ├── FileSourceRow.swift
│ │ │ │ │ ├── FileSourceView.swift
│ │ │ │ │ └── FileSourceViewModel.swift
│ │ │ │ ├── InfoAtomsRow.swift
│ │ │ │ ├── InfoSegmentedCapsule.swift
│ │ │ │ ├── Sections
│ │ │ │ │ └── FileDetailsComponent.swift
│ │ │ │ ├── WorkspaceHelp.swift
│ │ │ │ └── WorkspaceRow.swift
│ │ │ │ ├── FunctionsSegmentProtein.swift
│ │ │ │ ├── GraphicsSettingsSegment.swift
│ │ │ │ ├── SettingsSegmentProtein.swift
│ │ │ │ └── TrajectorySegmentProtein.swift
│ │ └── Top toolbar
│ │ │ ├── CameraControlToolbar.swift
│ │ │ ├── ToolbarConfig.swift
│ │ │ └── TopToolbar.swift
│ ├── SequenceViews
│ │ ├── Rows
│ │ │ └── SequenceRow.swift
│ │ └── SequenceView.swift
│ ├── SettingsView.swift
│ ├── StatusView
│ │ ├── StatusAction.swift
│ │ ├── StatusOverlayView.swift
│ │ └── StatusViewModel.swift
│ └── UI Elements
│ │ ├── BioViewerProgress
│ │ ├── BVDismissActionButton.swift
│ │ ├── BVProgressComponent.swift
│ │ ├── BVProgressFailedView.swift
│ │ └── BVProgressView.swift
│ │ ├── CoffeeViews
│ │ ├── BuyCoffeeView.swift
│ │ ├── CoffeeRow.swift
│ │ └── CoffeeTipRow.swift
│ │ ├── ColorPalettePopover.swift
│ │ ├── ColorPalettePopoverRow.swift
│ │ ├── ColorPaletteView.swift
│ │ ├── CoordinatesView.swift
│ │ ├── CustomLinearProgressView.swift
│ │ ├── Debug Views
│ │ ├── FPSCounterView.swift
│ │ └── ResolutionView.swift
│ │ ├── LineGraphView.swift
│ │ └── UnitTextView.swift
├── en.lproj
│ └── Localizable.strings
└── es.lproj
│ └── Localizable.strings
├── BioViewerPackages
├── BioViewerFoundation
│ ├── .swiftpm
│ │ └── xcode
│ │ │ ├── package.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── xcuserdata
│ │ │ ├── andro.xcuserdatad
│ │ │ └── xcschemes
│ │ │ │ └── xcschememanagement.plist
│ │ │ └── raul.xcuserdatad
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── Package.swift
│ ├── Sources
│ │ └── BioViewerFoundation
│ │ │ ├── Models
│ │ │ ├── BoundingVolumes.swift
│ │ │ ├── Protein.swift
│ │ │ ├── ProteinComposition
│ │ │ │ ├── ProteinChainComposition.swift
│ │ │ │ ├── ProteinElementComposition.swift
│ │ │ │ └── ProteinResidueComposition.swift
│ │ │ ├── ProteinFile.swift
│ │ │ └── ProteinFileInfo.swift
│ │ │ ├── ProteinMath.swift
│ │ │ └── Types
│ │ │ ├── AtomElement.swift
│ │ │ ├── BondStruct.swift
│ │ │ ├── ChainID.swift
│ │ │ ├── Residue.swift
│ │ │ └── SecondaryStructure.swift
│ └── Tests
│ │ └── BioViewerFoundationTests
│ │ └── BioViewerFoundationTests.swift
├── CIFParser
│ ├── .gitignore
│ ├── .swiftpm
│ │ └── xcode
│ │ │ └── package.xcworkspace
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Package.swift
│ ├── Sources
│ │ └── CIFParser
│ │ │ ├── CIFConstants.swift
│ │ │ └── CIFParser.swift
│ └── Tests
│ │ └── CIFParserTests
│ │ └── CIFParserTests.swift
├── PDBParser
│ ├── .swiftpm
│ │ └── xcode
│ │ │ ├── package.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── xcuserdata
│ │ │ ├── andro.xcuserdatad
│ │ │ └── xcschemes
│ │ │ │ └── xcschememanagement.plist
│ │ │ └── raul.xcuserdatad
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── Package.swift
│ ├── Sources
│ │ └── PDBParser
│ │ │ ├── PDBConstants.swift
│ │ │ ├── PDBParseError.swift
│ │ │ └── PDBParser.swift
│ └── Tests
│ │ └── PDBParserTests
│ │ └── PDBParserTests.swift
└── XYZParser
│ ├── .swiftpm
│ └── xcode
│ │ ├── package.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ ├── andro.xcuserdatad
│ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ │ └── raul.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
│ ├── Package.swift
│ ├── Sources
│ └── XYZParser
│ │ ├── XYZConstants.swift
│ │ ├── XYZParsedConfiguration.swift
│ │ ├── XYZParser.swift
│ │ └── XYZParserError.swift
│ └── Tests
│ └── XYZParserTests
│ └── XYZParserTests.swift
├── BioViewerTests
├── BioViewerTests.swift
└── Info.plist
├── BioViewerUITests
├── BioViewerUITests.swift
└── Info.plist
├── LICENSE.md
├── Privacy policy
└── en
│ └── PrivacyPolicy.md
├── PromoAssets
├── MetalFeatures.png
└── Mockup.png
├── ProteinThumbnail
├── Assets.xcassets
│ ├── Contents.json
│ └── OverlayPDB.imageset
│ │ ├── Contents.json
│ │ └── OverlayPDB.png
├── Info.plist
├── ProteinThumbnail.entitlements
└── ThumbnailProvider.swift
├── README.md
├── docs
├── ProteinVisualization
│ ├── Figures
│ │ ├── HighResH2O.png
│ │ ├── PercentageCloseFiltering.png
│ │ ├── ShadowAcne.png
│ │ ├── ShadowedDrawableTexture.png
│ │ └── SunDepthTexture.png
│ ├── HardShadows.md
│ └── MolecularSurface.md
├── _config.yml
└── index.md
└── scripts
├── Plot3DPoints.py
├── UnitaryIcosahedron.playground
├── Contents.swift
├── contents.xcplayground
└── playground.xcworkspace
│ └── contents.xcworkspacedata
└── UnitaryIcosahedron.py
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - trailing_whitespace
3 | - large_tuple
4 | - empty_count
5 | opt_in_rules:
6 | - empty_count
7 | - empty_string
8 | identifier_name:
9 | validates_start_with_lowercase: false
10 | allowed_symbols: "_"
11 | excluded:
12 | - id
13 | - i
14 | - j
15 | - k
16 | - a
17 | - b
18 | - x
19 | - y
20 | - z
21 | - t
22 | - u
23 | - v
24 | - v0
25 | - v1
26 | - v2
27 | - v3
28 | - v4
29 | - v5
30 | line_length:
31 | warning: 150
32 | error: 1000
33 | ignores_function_declarations: true
34 | ignores_comments: true
35 | ignores_urls: true
36 | function_body_length:
37 | warning: 300
38 | error: 500
39 | function_parameter_count:
40 | warning: 10
41 | error: 20
42 | type_body_length:
43 | warning: 700
44 | error: 800
45 | file_length:
46 | warning: 1000
47 | error: 1500
48 | ignore_comment_only_lines: true
49 | cyclomatic_complexity:
50 | warning: 15
51 | error: 250
52 | reporter: "xcode"
53 | type_name:
54 | allowed_symbols: "_"
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | IssueFilterStyle
12 | ShowActiveSchemeOnly
13 | LiveSourceIssuesEnabled
14 |
15 | ShowSharedSchemesAutomaticallyEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/xcshareddata/xcbaselines/C44FC07E2641AF1E00459E01.xcbaseline/F9975422-2017-4B21-ACC6-016EB4D927C6.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | BioViewerTests
8 |
9 | testPerformanceExample()
10 |
11 | com.apple.dt.XCTMetric_Clock.time.monotonic
12 |
13 | baselineAverage
14 | 0.348
15 | baselineIntegrationDisplayName
16 | Local Baseline
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/xcshareddata/xcbaselines/C44FC07E2641AF1E00459E01.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | F9975422-2017-4B21-ACC6-016EB4D927C6
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 400
13 | cpuCount
14 | 1
15 | cpuKind
16 | 8-Core Intel Core i9
17 | cpuSpeedInMHz
18 | 2300
19 | logicalCPUCoresPerPackage
20 | 16
21 | modelCode
22 | MacBookPro16,1
23 | physicalCPUCoresPerPackage
24 | 8
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 | targetDevice
31 |
32 | modelCode
33 | iPad13,10
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/xcshareddata/xcschemes/BioViewerTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | BioViewer.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | BioViewerTests.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 | ProteinThumbnail.xcscheme_^#shared#^_
18 |
19 | orderHint
20 | 2
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/BioViewer.xcodeproj/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | BioViewer.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | BioViewerTests.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | C44FC06D2641AF1B00459E01
21 |
22 | primary
23 |
24 |
25 | C44FC07E2641AF1E00459E01
26 |
27 | primary
28 |
29 |
30 | C44FC0892641AF1E00459E01
31 |
32 | primary
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.843",
9 | "green" : "0.303",
10 | "red" : "0.468"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "0.410",
28 | "red" : "0.607"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x-1.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@2x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@1x.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@2x-1.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/DefaultViewfinderImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "DefaultViewfinderImage.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/DefaultViewfinderImage.imageset/DefaultViewfinderImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/DefaultViewfinderImage.imageset/DefaultViewfinderImage.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Screenshot 2021-05-24 at 00.28.39.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "Screenshot 2021-05-24 at 00.28.39-1.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39-1.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39.png
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/Shutter.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Lights.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/BioViewer/Assets.xcassets/Shutter.imageset/Lights.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/Shutter.imageset/Lights.png
--------------------------------------------------------------------------------
/BioViewer/BioBench/BenchmarkedProtein.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BenchmarkedProtein.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 12/11/23.
6 | //
7 |
8 | import Foundation
9 |
10 | struct BenchmarkedProtein: Equatable, Identifiable, Sendable {
11 | let id = UUID()
12 | let name: String
13 | let atoms: Int
14 | let time: Double
15 | let std: (Double, Double)
16 |
17 | static func == (lhs: BenchmarkedProtein, rhs: BenchmarkedProtein) -> Bool {
18 | return lhs.id == rhs.id
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/BioViewer/BioBench/BioBenchConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BioBenchConfig.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 25/1/23.
6 | //
7 |
8 | import Foundation
9 |
10 | class BioBenchConfig {
11 | static let numberOfFrames: Int = 1000
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewer/BioViewer-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "FrameData.h"
6 | #import "GeneratedVertex.h"
7 | #import "SharedDataStructs.h"
8 | #import "FillColorInput.h"
9 | #import "SDFGrid.h"
10 | #import "ReprojectionData.h"
11 |
--------------------------------------------------------------------------------
/BioViewer/BioViewer.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-write
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/BioViewer/BioViewerApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDB_ViewerApp.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 4/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct BioViewerApp: App {
12 |
13 | var body: some Scene {
14 |
15 | WindowGroup {
16 | MainView()
17 | }
18 | .commands {
19 | BioViewerCommands()
20 | }
21 |
22 | WindowGroup(id: "BioBench") {
23 | BioBenchView()
24 | .navigationTitle("BioBench")
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/BioViewer/BioViewerLogger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BioViewerLogger.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/5/22.
6 | //
7 |
8 | import Foundation
9 | import os
10 |
11 | class BioViewerLogger {
12 |
13 | static let shared = BioViewerLogger()
14 |
15 | private var rendererLogger = Logger(
16 | subsystem: "BioViewer",
17 | category: "ProteinRenderer"
18 | )
19 | private var computeSurfaceUtilityLogger = Logger(
20 | subsystem: "BioViewer",
21 | category: "ComputeSurfaceUtility"
22 | )
23 |
24 | private init() {
25 |
26 | }
27 |
28 | // MARK: - Functions
29 | func log(type: LogType, category: LogCategory, message: String) {
30 |
31 | #if DEBUG
32 | var logger: Logger
33 | switch category {
34 | case .computeSurfaceUtility:
35 | logger = computeSurfaceUtilityLogger
36 | case .proteinRenderer:
37 | logger = rendererLogger
38 | }
39 |
40 | switch type {
41 | case .info:
42 | logger.info("\(message)")
43 | case .warning:
44 | logger.warning("\(message)")
45 | case .error:
46 | logger.critical("\(message)")
47 | }
48 | #endif
49 | }
50 |
51 | }
52 |
53 | enum LogType {
54 | case info
55 | case warning
56 | case error
57 | }
58 |
59 | enum LogCategory {
60 | case proteinRenderer
61 | case computeSurfaceUtility
62 | }
63 |
--------------------------------------------------------------------------------
/BioViewer/Components/BioViewerPicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BioViewerPicker.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 21/1/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct BioViewerPicker: View {
11 |
12 | @Binding var selection: T
13 |
14 | var body: some View {
15 | #if targetEnvironment(macCatalyst)
16 | Picker("", selection: $selection.animation()) {
17 | ForEach(Array(T.allCases), id: \.self) {
18 | Text($0.displayName)
19 | .tag($0)
20 | }
21 | }
22 | .pickerStyle(MenuPickerStyle())
23 | #else
24 | Menu {
25 | Picker("", selection: $selection.animation()) {
26 | ForEach(Array(T.allCases), id: \.self) {
27 | Text($0.displayName)
28 | .tag($0)
29 | }
30 | }
31 | } label: {
32 | HStack(spacing: 4) {
33 | Text(selection.displayName)
34 | .padding(.vertical, 4)
35 | .padding(.horizontal, 8)
36 | .background(Color.accentColor)
37 | .foregroundColor(.white)
38 | .cornerRadius(4)
39 | .fixedSize(horizontal: true, vertical: false)
40 | }
41 | }
42 | .menuOrder(.fixed)
43 | #endif
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/BioViewer/Components/EmptyDataRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptyDataRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 12/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EmptyDataRow: View {
11 |
12 | let text: String
13 |
14 | var body: some View {
15 | Text(text)
16 | .foregroundColor(.secondary)
17 | .italic()
18 | }
19 | }
20 |
21 | struct EmptyDataRow_Previews: PreviewProvider {
22 | static var previews: some View {
23 | List {
24 | EmptyDataRow(text: NSLocalizedString("No data", comment: ""))
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/BioViewer/Components/PersistentDisclosureGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PersistentDisclosureGroup.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 15/12/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | enum PersistentGroupName: String {
11 | case colorGroup
12 | case visualizationGroup
13 | case solidSpheresRadiusGroup
14 | case ballAndStickRadiusGroup
15 | case shadowGroup
16 | case depthCueingGroup
17 | case residueColoringAminoAcid
18 | case residueColoringDNANucleobase
19 | case residueColoringRNANucleobase
20 | case metalFXUpscalingSettings
21 | /// This will still create a DisclosureGroup, but not persistent.
22 | case error
23 | }
24 |
25 | struct PersistentDisclosureGroup: View {
26 |
27 | @State var isExpanded: Bool
28 |
29 | let group: PersistentGroupName
30 | let content: () -> Content
31 | let label: () -> LabelContent
32 |
33 | init(for group: PersistentGroupName, defaultOpen: Bool, @ViewBuilder content: @escaping () -> Content, @ViewBuilder label: @escaping () -> LabelContent) {
34 | self.group = group
35 | self.content = content
36 | self.label = label
37 | guard group != .error else {
38 | _isExpanded = State(initialValue: false)
39 | return
40 | }
41 | if UserDefaults.standard.object(forKey: group.rawValue + "Expanded") != nil {
42 | _isExpanded = State(initialValue: UserDefaults.standard.bool(forKey: group.rawValue + "Expanded"))
43 | } else {
44 | _isExpanded = State(initialValue: defaultOpen)
45 | }
46 | }
47 |
48 | var body: some View {
49 | DisclosureGroup(
50 | isExpanded: $isExpanded,
51 | content: content,
52 | label: label
53 | )
54 | .onChange(of: isExpanded) {
55 | guard group != .error else { return }
56 | UserDefaults.standard.setValue(isExpanded, forKey: group.rawValue + "Expanded")
57 | }
58 | }
59 | }
60 |
61 | struct PersistentDisclosureGroup_Previews: PreviewProvider {
62 | static var previews: some View {
63 | PersistentDisclosureGroup(
64 | for: .colorGroup,
65 | defaultOpen: true,
66 | content: {
67 | Rectangle()
68 | },
69 | label: {
70 | Circle()
71 | }
72 | )
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/ButtonRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ButtonRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 29/11/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ButtonRow: View {
11 |
12 | let action: () -> Void
13 | let text: String
14 |
15 | var body: some View {
16 | Button(
17 | action: {
18 | action()
19 | },
20 | label: {
21 | Text(text)
22 | }
23 | )
24 | }
25 | }
26 |
27 | struct ButtonRow_Previews: PreviewProvider {
28 | static var previews: some View {
29 | ButtonRow(action: {}, text: "Test button")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/ColorPaletteRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorPaletteRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ColorPaletteRow: View {
11 |
12 | @State var showPalettePicker: Bool = false
13 | let colorPalette: ColorPalette
14 |
15 | var body: some View {
16 | HStack {
17 | Text(NSLocalizedString("Color palette", comment: ""))
18 | Spacer()
19 | Button(action: {
20 | showPalettePicker.toggle()
21 | }, label: {
22 | ColorPaletteView(colorPalette: colorPalette)
23 | .popover(isPresented: $showPalettePicker) {
24 | ColorPalettePopover()
25 | }
26 | })
27 | .buttonStyle(PlainButtonStyle())
28 | }
29 | }
30 | }
31 |
32 | struct ColorPaletteRow_Previews: PreviewProvider {
33 | static var previews: some View {
34 | List {
35 | ColorPaletteRow(colorPalette: ColorPalette(.default))
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/ColorPickerRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorPickerRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 10/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ColorPickerRow: View {
11 |
12 | var title: String
13 | @Binding var selectedColor: Color
14 |
15 | var body: some View {
16 | HStack {
17 | #if targetEnvironment(macCatalyst)
18 | Text(title)
19 | Spacer()
20 | ColorPicker("",
21 | selection: $selectedColor,
22 | supportsOpacity: false)
23 | .frame(width: 96)
24 | #else
25 | ColorPicker(title,
26 | selection: $selectedColor,
27 | supportsOpacity: false)
28 | #endif
29 | }
30 | }
31 | }
32 |
33 | struct ColorPickerRow_Previews: PreviewProvider {
34 | static var previews: some View {
35 | ColorPickerRow(title: NSLocalizedString("Selected color", comment: ""),
36 | selectedColor: .constant(Color.black))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/FocalLengthRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FocalLengthRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 30/10/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FocalLengthRow: View {
11 |
12 | @Binding var focalLength: Float
13 |
14 | var body: some View {
15 | VStack(spacing: 0) {
16 |
17 | Slider(value: $focalLength, in: 24...300)
18 |
19 | HStack {
20 | Text("24")
21 | .font(.system(size: 12))
22 |
23 | Spacer()
24 |
25 | Text("\(focalLength, specifier: "%.0f") mm")
26 | .font(.system(size: 14))
27 | .padding(.all, 4)
28 | .foregroundColor(.secondary)
29 | .overlay(
30 | RoundedRectangle(cornerRadius: 8)
31 | .stroke(Color.secondary, lineWidth: 1)
32 | )
33 | #if targetEnvironment(macCatalyst)
34 | .scaleEffect(0.75)
35 | #endif
36 | Spacer()
37 |
38 | Text("300")
39 | .font(.system(size: 12))
40 | }
41 | }
42 | }
43 | }
44 |
45 | struct FocalLengthRow_Previews: PreviewProvider {
46 | static var previews: some View {
47 | FocalLengthRow(focalLength: .constant(200))
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/InfoLongTextRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoLongTextRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 13/11/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InfoLongTextRow: View {
11 |
12 | @State var title: String
13 | var longText: String?
14 |
15 | var body: some View {
16 | if let longText = longText {
17 | VStack(alignment: .leading, spacing: 2) {
18 | Text(title)
19 | .multilineTextAlignment(.leading)
20 | .frame(maxWidth: .infinity, alignment: .leading)
21 | Text(longText)
22 | .foregroundColor(Color(uiColor: UIColor.secondaryLabel))
23 | }
24 | .frame(maxWidth: .infinity)
25 | } else {
26 | HStack(spacing: 4) {
27 | Text(title)
28 | .multilineTextAlignment(.leading)
29 | .frame(alignment: .leading)
30 | Text("-")
31 | .foregroundColor(Color(uiColor: UIColor.secondaryLabel))
32 | Spacer()
33 | }
34 | .frame(maxWidth: .infinity)
35 | }
36 | }
37 | }
38 |
39 | struct LongTextRow_Previews: PreviewProvider {
40 | static var previews: some View {
41 | InfoLongTextRow(title: "Description", longText: "Long text example")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/InfoPopoverRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoPopoverRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InfoPopoverRow: View {
11 |
12 | let label: String
13 | let value: String
14 | let isDisabled: Bool
15 | let popoverView: Content
16 | @State var buttonToggle: Bool = false
17 |
18 | init(label: String, value: String, isDisabled: Bool, @ViewBuilder content: () -> Content) {
19 | self.label = label
20 | self.value = value
21 | self.isDisabled = isDisabled
22 | self.popoverView = content()
23 | }
24 |
25 | var body: some View {
26 | HStack(spacing: 4) {
27 | Text(label)
28 | Text(value)
29 | .foregroundColor(Color(uiColor: .secondaryLabel))
30 | Spacer()
31 | Button(action: {
32 | buttonToggle.toggle()
33 | },
34 | label: {
35 | Image(systemName: "info.circle")
36 | })
37 | .foregroundColor(Color.accentColor)
38 | .buttonStyle(PlainButtonStyle())
39 | .disabled(isDisabled)
40 | .popover(isPresented: $buttonToggle) {
41 | popoverView
42 | }
43 | }
44 | }
45 | }
46 |
47 | struct InformationRow_Previews: PreviewProvider {
48 | static var previews: some View {
49 | List {
50 | InfoPopoverRow(label: "Número de átomos",
51 | value: "58336",
52 | isDisabled: false,
53 | content: { FileCompositionView() })
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/InfoTextRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoTextRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 24/11/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InfoTextRow: View {
11 | @State var text: String
12 | var value: String?
13 |
14 | var body: some View {
15 | HStack(spacing: 4) {
16 | Text(text)
17 | Text(value ?? "-")
18 | .foregroundColor(Color(uiColor: .secondaryLabel))
19 | }
20 | }
21 | }
22 |
23 | struct InfoTextRow_Previews: PreviewProvider {
24 | static var previews: some View {
25 | InfoTextRow(text: "Number of something:", value: "3340")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/InputWithButtonRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InputWithButtonRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 18/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InputWithButtonRow: View {
11 |
12 | let title: String
13 | @Binding var value: Float
14 | let buttonTitle: String
15 | let action: () -> Void
16 | let formatter: NumberFormatter
17 |
18 | var body: some View {
19 | HStack {
20 | Text(title)
21 | .frame(maxWidth: .infinity, alignment: .leading)
22 | .multilineTextAlignment(.leading)
23 | TextField(title, value: $value, formatter: formatter)
24 | .textFieldStyle(RoundedBorderTextFieldStyle())
25 | .frame(maxWidth: 48)
26 | .multilineTextAlignment(.trailing)
27 | Button(action: {
28 | action()
29 | }, label: {
30 | Text(buttonTitle)
31 | })
32 | .buttonStyle(BorderedProminentButtonStyle())
33 | }
34 | }
35 | }
36 |
37 | struct InputWithButtonRow_Previews: PreviewProvider {
38 | static var previews: some View {
39 | List {
40 | InputWithButtonRow(title: "Go to frame", value: .constant(256), buttonTitle: "Go", action: {}, formatter: NumberFormatter())
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/PickerRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PickerRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 19/1/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | protocol PickableEnum: CaseIterable, Hashable {
11 | var displayName: String { get }
12 | }
13 |
14 | struct PickerRow: View {
15 |
16 | let optionName: String
17 | @Binding var selection: T
18 |
19 | @Environment(\.horizontalSizeClass) var horizontalSizeClass
20 |
21 | init(optionName: String, selection: Binding) {
22 | self.optionName = optionName
23 | self._selection = selection
24 | }
25 |
26 | var body: some View {
27 | #if targetEnvironment(macCatalyst)
28 | HStack(spacing: .zero) {
29 | Text(optionName)
30 | BioViewerPicker(selection: $selection)
31 | }
32 | #else
33 | HStack {
34 | Text(optionName + ":")
35 | Spacer()
36 | BioViewerPicker(selection: $selection)
37 | }
38 | #endif
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/RangeRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RangeRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 10/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RangeRow: View {
11 |
12 | let title: String
13 |
14 | struct RangeDelimiter: View {
15 |
16 | let isLeftRange: Bool
17 |
18 | private enum RangeDelimiterConstants {
19 | #if targetEnvironment(macCatalyst)
20 | static let size: CGFloat = 16
21 | #else
22 | static let size: CGFloat = 24
23 | #endif
24 | }
25 |
26 | var body: some View {
27 | Image(systemName: "triangle.fill")
28 | .resizable()
29 | .aspectRatio(1.0, contentMode: .fit)
30 | .frame(width: RangeDelimiterConstants.size)
31 | .foregroundColor(Color(uiColor: .white))
32 | .rotationEffect(Angle(degrees: isLeftRange ? 90 : -90))
33 | .shadow(color: .black.opacity(0.2),
34 | radius: 4,
35 | x: 0,
36 | y: 2)
37 | }
38 | }
39 |
40 | var body: some View {
41 | VStack(alignment: .leading) {
42 | HStack {
43 | Text(title)
44 | Text("0 to 300 Å")
45 | .foregroundColor(.secondary)
46 | }
47 | ZStack {
48 | Capsule()
49 | .frame(height: 4)
50 | .padding(.horizontal, 10)
51 | .foregroundColor(.accentColor)
52 | HStack {
53 | RangeDelimiter(isLeftRange: true)
54 | Spacer()
55 | RangeDelimiter(isLeftRange: false)
56 | }
57 | }
58 | }
59 | .padding(.vertical, 4)
60 | }
61 | }
62 |
63 | struct RangeRow_Previews: PreviewProvider {
64 | static var previews: some View {
65 | List {
66 | RangeRow(title: "Range:")
67 | }
68 | .preferredColorScheme(.dark)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/SliderRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SliderRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 10/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SliderRow: View {
11 |
12 | let title: String
13 | @Binding var value: Float
14 | let minValue: Float
15 | let maxValue: Float
16 | let stepSize: Float
17 |
18 | func getNumberFormatter(min: Float, max: Float) -> NumberFormatter {
19 | let numberFormatter = NumberFormatter()
20 | numberFormatter.numberStyle = .decimal
21 | numberFormatter.maximumFractionDigits = 1
22 | numberFormatter.minimum = min as NSNumber
23 | numberFormatter.maximum = max as NSNumber
24 | return numberFormatter
25 | }
26 |
27 | init(title: String, value: Binding, minValue: Float, maxValue: Float, stepSize: Float = 0.1) {
28 | self.title = title
29 | self._value = value
30 | self.minValue = minValue
31 | self.maxValue = maxValue
32 | self.stepSize = stepSize
33 | }
34 |
35 | var body: some View {
36 | HStack {
37 | Text(title)
38 | Slider(value: $value, in: minValue...maxValue)
39 | .frame(maxWidth: .infinity)
40 | #if targetEnvironment(macCatalyst)
41 | Stepper(value: $value, in: minValue...maxValue, step: stepSize) {
42 | TextField("Value",
43 | value: $value,
44 | formatter: getNumberFormatter(min: minValue, max: maxValue))
45 | .textFieldStyle(RoundedBorderTextFieldStyle())
46 | }
47 | .frame(maxWidth: 64)
48 | #endif
49 | }
50 | }
51 | }
52 |
53 | struct SliderRow_Previews: PreviewProvider {
54 | static var previews: some View {
55 | List {
56 | SliderRow(title: "Strength",
57 | value: .constant(0.3),
58 | minValue: 0.0,
59 | maxValue: 1.0)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/BioViewer/Components/Rows/SwitchRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwitchRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 27/5/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct SwitchRow: View {
12 |
13 | var title: String
14 | @Binding var toggledVariable: Bool
15 |
16 | var body: some View {
17 | #if targetEnvironment(macCatalyst)
18 | Toggle(title, isOn: $toggledVariable)
19 | .tint(.accentColor)
20 | #else
21 | Toggle(title, isOn: $toggledVariable.animation())
22 | .tint(.accentColor)
23 | #endif
24 | }
25 |
26 | }
27 |
28 | struct SwitchRow_Previews: PreviewProvider {
29 | static var previews: some View {
30 | SwitchRow(title: "Sample toggle", toggledVariable: .constant(true))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/BioViewer/Data structures/BoundingVolumes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BoundingVolumes.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 7/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct BoundingSphere: Sendable {
11 | let center: simd_float3
12 | let radius: Float
13 | }
14 |
15 | struct BoundingBox: Sendable {
16 | let minX: Float
17 | let maxX: Float
18 | let minY: Float
19 | let maxY: Float
20 | let minZ: Float
21 | let maxZ: Float
22 | }
23 |
24 | struct BoundingVolume: Sendable {
25 | let sphere: BoundingSphere
26 | let box: BoundingBox
27 |
28 | static var zero: Self {
29 | return BoundingVolume(
30 | sphere: BoundingSphere(center: .zero, radius: .zero),
31 | box: BoundingBox(minX: .zero, maxX: .zero, minY: .zero, maxY: .zero, minZ: .zero, maxZ: .zero)
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/BioViewer/Data structures/ColorPalettes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorPalettes.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum ColorPaletteType {
12 | case `default`
13 | case bioViewer
14 | case custom
15 | }
16 |
17 | struct ColorPalette {
18 | let color0: Color
19 | let color1: Color
20 | let color2: Color
21 | let color3: Color
22 | let color4: Color
23 | let color5: Color
24 |
25 | init(_ colorPaletteType: ColorPaletteType) {
26 | // TO-DO:
27 | switch colorPaletteType {
28 | case .bioViewer:
29 | color0 = .purple
30 | color1 = .purple
31 | color2 = .purple
32 | color3 = .purple
33 | color4 = .purple
34 | color5 = .purple
35 | default:
36 | color0 = .green
37 | color1 = .gray
38 | color2 = .blue
39 | color3 = .red
40 | color4 = .orange
41 | color5 = .gray
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/BioViewer/Data structures/ProteinDataSource/ProteinDataSourceError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinDataSourceError.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 27/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ProteinDataSourceError: Error {
11 | case unableToUpdateProteinConnectivity
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewer/Data structures/RCSB/RCSBError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RCSBError.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 15/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum RCSBError: Error {
11 | case invalidID
12 | case malformedURL
13 | case notFound
14 | case internalServerError
15 | case badImageData
16 | case malformedInput
17 | case unknown
18 | }
19 |
20 | extension RCSBError: LocalizedError {
21 | public var errorDescription: String? {
22 | switch self {
23 | case .invalidID, .malformedURL:
24 | // Malformed URLs are likely due to an invalid PDB ID. If there's another
25 | // cause, it's unlikely that the user has any other way to fix it.
26 | return NSLocalizedString("Invalid RCSB ID", comment: "")
27 | case .notFound:
28 | return NSLocalizedString("No structure found with the given RCSB ID", comment: "")
29 | case .internalServerError:
30 | return NSLocalizedString("There seems to be a problem with the server", comment: "")
31 | case .badImageData:
32 | return NSLocalizedString("Invalid image data", comment: "")
33 | case .malformedInput:
34 | return NSLocalizedString("Invalid input", comment: "")
35 | case .unknown:
36 | return NSLocalizedString("Unknown error", comment: "")
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/BioViewer/Data structures/SecondaryStructure.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecondaryStructure.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 5/3/23.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum SecondaryStructure: UInt8, CaseIterable {
12 | case helix
13 | case sheet
14 | case loop
15 | case nonChain
16 |
17 | var name: String {
18 | switch self {
19 | case .helix:
20 | return NSLocalizedString("Helix", comment: "")
21 | case .sheet:
22 | return NSLocalizedString("Sheet", comment: "")
23 | case .loop:
24 | return NSLocalizedString("Loop", comment: "")
25 | case .nonChain:
26 | return NSLocalizedString("Non-chain", comment: "")
27 | }
28 | }
29 |
30 | var defaultColor: Color {
31 | switch self {
32 | case .helix:
33 | return Color(.displayP3, red: 0.423, green: 0.733, blue: 0.235, opacity: 1)
34 | case .sheet:
35 | return Color(.displayP3, red: 0.000, green: 0.590, blue: 1.000, opacity: 1)
36 | case .loop:
37 | return Color(.displayP3, red: 0.500, green: 0.500, blue: 0.500, opacity: 1)
38 | case .nonChain:
39 | return Color(.displayP3, red: 0.750, green: 0.750, blue: 0.750, opacity: 1)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/BioViewer/Extensions/UserDefaults+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaults+Extension.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 29/11/22.
6 | //
7 |
8 | import Foundation
9 |
10 | @propertyWrapper
11 | struct UserDefault {
12 | let key: String
13 | let defaultValue: Value
14 | var container: UserDefaults = .standard
15 |
16 | var wrappedValue: Value {
17 | get {
18 | return container.object(forKey: key) as? Value ?? defaultValue
19 | }
20 | set {
21 | container.set(newValue, forKey: key)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/BioViewer/Files/BioViewerWorkspace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BioViewerWorkspace.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class BioViewerWorkspace: UIDocument {
12 |
13 | // TO-DO: Saved contents
14 | var testContent = "Test"
15 |
16 | // MARK: - Inner files
17 | var infoFileName = "Info.txt"
18 |
19 | override func load(fromContents contents: Any, ofType typeName: String?) throws {
20 | guard let topFileWrapper = contents as? FileWrapper,
21 | let textData = topFileWrapper.fileWrappers?[infoFileName]?.regularFileContents else {
22 | return
23 | }
24 | testContent = String(data: textData, encoding: .utf8)!
25 | }
26 |
27 | override func contents(forType typeName: String) throws -> Any {
28 | guard let workspaceData = testContent.data(using: .utf8) else {
29 | throw ExportError.unknownError
30 | }
31 |
32 | return FileWrapper(regularFileWithContents: workspaceData)
33 | }
34 | }
35 |
36 | /*
37 | class BioViewerWorkspaceDelegate: DocumentPickerDelegate {
38 |
39 | }
40 | */
41 |
--------------------------------------------------------------------------------
/BioViewer/Files/ExportError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExportError.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ExportError: Error {
11 | case unknownError
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewer/Files/Import/ImportError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImportErrors.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 23/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ImportError: Error {
11 | case unknownFileType
12 | case emptyAtomCount
13 | case notFound
14 | case downloadError
15 | case unknownFileExtension
16 | case unknownError
17 | }
18 |
19 | extension ImportError: LocalizedError {
20 | public var errorDescription: String? {
21 | switch self {
22 | case .unknownFileType:
23 | return NSLocalizedString("Error: Unknown file type", comment: "")
24 | case .emptyAtomCount:
25 | return NSLocalizedString("Error: File does not contain any atom positions", comment: "")
26 | case .notFound:
27 | return NSLocalizedString("Error: File not found", comment: "")
28 | case .downloadError:
29 | return NSLocalizedString("Error downloading file", comment: "")
30 | case .unknownFileExtension:
31 | return NSLocalizedString("Unknown file extension", comment: "")
32 | case .unknownError:
33 | return NSLocalizedString("Error importing file", comment: "")
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/BioViewer/Files/Import/ImportedUTTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImportedUTTypes.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 12/11/23.
6 | //
7 |
8 | import Foundation
9 | import UniformTypeIdentifiers
10 |
11 | extension UTType {
12 | static var pdbFiles: UTType {
13 | UTType(importedAs: "com.raulmonton.bioviewer.pdb")
14 | }
15 | static var xyzFiles: UTType {
16 | UTType(importedAs: "com.raulmonton.bioviewer.xyz")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/BioViewer/Files/WorkspaceExporter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WorkspaceExporter.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class WorkspaceExporter {
12 |
13 | static func createWorkspace(proteinViewModel: ProteinViewModel) {
14 | // Write to cache
15 | let cachesDir = FileManager.default.urls(for: FileManager.SearchPathDirectory.cachesDirectory, in: .allDomainsMask).first!
16 | let dataDir = cachesDir.appendingPathComponent("export", isDirectory: true)
17 | try? FileManager.default.createDirectory(at: dataDir, withIntermediateDirectories: true, attributes: nil)
18 |
19 | let fileURL = dataDir.appendingPathComponent("Workspace").appendingPathExtension("bioviewer")
20 |
21 | Task {
22 | /*
23 | let archive = await BioViewerWorkspace(fileURL: fileURL)
24 | // FIXME: archive.testContent = await proteinViewModel.dataSource?.files.first?.fileInfo.pdbID ?? "Unknown PDB ID"
25 |
26 | let success = await archive.save(to: archive.fileURL, for: .forCreating)
27 |
28 | await MainActor.run {
29 | guard success else {
30 | return
31 | }
32 |
33 | let documentPicker = UIDocumentPickerViewController(forExporting: [fileURL])
34 | let delegate = BioViewerWorkspaceDelegate()
35 | documentPicker.delegate = delegate
36 |
37 | // TO-DO: Improve how the current window is located. This is a hacky workaround.
38 | for scene in UIApplication.shared.connectedScenes where scene.activationState == .foregroundActive {
39 | guard let windowSceneDelegate = ((scene as? UIWindowScene)?.delegate as? UIWindowSceneDelegate) else {
40 | return
41 | }
42 | guard let window = windowSceneDelegate.window else {
43 | return
44 | }
45 | guard let rootViewController = window?.rootViewController else {
46 | return
47 | }
48 |
49 | rootViewController.present(documentPicker, animated: true)
50 | }
51 | }
52 | */
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/BioViewer/JSONs/RCSBSuggestionData.json:
--------------------------------------------------------------------------------
1 | {
2 | "sections": [
3 | {
4 | "sectionTitle": "Chaperone",
5 | "sectionDescription": "chaperone_description",
6 | "rowData": [
7 | {
8 | "rcsbid": "6MRC",
9 | "description": "ADP-bound human mitochondrial Hsp60-Hsp10 football complex",
10 | "authors": "Gomez-Llorente, Y., Jebara, F., Patra, M., Malik, R., Nissemblat, S., Azem, A., Hirsch, J.A., Ubarretxena-Belandia, I."
11 | }
12 | ]
13 | },
14 | {
15 | "sectionTitle": "MHC",
16 | "sectionDescription": "mhc_description",
17 | "rowData": [
18 | {
19 | "rcsbid": "2XPG",
20 | "description": "Crystal structure of a MHC class I-peptide complex",
21 | "authors": "McMahon, R.M., Friis, L., Siebold, C., Friese, M.A., Fugger, L., Jones, E.Y."
22 | }
23 | ]
24 | },
25 | {
26 | "sectionTitle": "Apoptosome",
27 | "sectionDescription": "apoptosome_description",
28 | "rowData": [
29 | {
30 | "rcsbid": "3JBT",
31 | "description": "Atomic structure of the Apaf-1 apoptosome",
32 | "authors": "Zhou, M., Li, Y., Hu, Q., Bai, X., Huang, W., Yan, C., Scheres, S.H.W., Shi, Y."
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/BioViewer/Media Assets/ShutterClosed.aiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Media Assets/ShutterClosed.aiff
--------------------------------------------------------------------------------
/BioViewer/Media Assets/ShutterOpen.aiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Media Assets/ShutterOpen.aiff
--------------------------------------------------------------------------------
/BioViewer/Metal/Compute/FillColorInput.h:
--------------------------------------------------------------------------------
1 | //
2 | // FillColorInput.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 23/1/22.
6 | //
7 |
8 | #ifndef FillColorInput_h
9 | #define FillColorInput_h
10 |
11 | /// Maximum number of colours that can be passed down to the GPU.
12 | #define MAX_CHAIN_COLORS 512
13 | #define MAX_ELEMENT_COLORS 128
14 | #define MAX_RESIDUE_COLORS 35
15 | #define MAX_SECONDARY_STRUCTURE_COLORS 4
16 |
17 | typedef struct {
18 |
19 | /// Color by element, used as a percentage (is always 0 or 1 outside animations).
20 | float colorByElement;
21 | /// Color by residue type, used as a percentage (is always 0 or 1 outside animations).
22 | float colorByResidue;
23 | /// Color by chain, used as a percentage (is always 0 or 1 outside animations).
24 | float colorByChain;
25 | /// Color by secondary structure, used as a percentage (is always 0 or 1 outside animations).
26 | float colorBySecondaryStructure;
27 |
28 | /// Color used when coloring by element.
29 | simd_float4 element_color [MAX_ELEMENT_COLORS];
30 | /// Color used when coloring by subunit.
31 | simd_float4 subunit_color [MAX_CHAIN_COLORS];
32 | /// Color used when coloring by amino acid.
33 | simd_float4 residue_color [MAX_RESIDUE_COLORS];
34 | /// Color used when coloring by secondary structure.
35 | simd_float4 secondary_structure_color [MAX_SECONDARY_STRUCTURE_COLORS];
36 |
37 | } FillColorInput;
38 |
39 | #endif /* FillColorInput_h */
40 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Compute/SDFGrid.h:
--------------------------------------------------------------------------------
1 | //
2 | // SDFGrid.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 18/3/22.
6 | //
7 |
8 | #ifndef SDFGrid_h
9 | #define SDFGrid_h
10 |
11 | typedef struct {
12 |
13 | /// Number of points per grid side.
14 | int32_t grid_resolution;
15 | /// Grid side size (in Armstrongs).
16 | float grid_size;
17 | /// Number of atoms contained inside the grid.
18 | int32_t number_of_atoms;
19 |
20 | } SDFGrid;
21 |
22 | #endif /* SDFGrid_h */
23 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Functions/MakeBufferFromArray.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MakeBufferFromArray.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 10/5/22.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 |
11 | extension MetalScheduler {
12 | public func makeBufferFromArray(array: [T]) -> MTLBuffer? {
13 | let arrayCopy = array
14 | let buffer = device.makeBuffer(bytes: arrayCopy,
15 | length: MemoryLayout.stride * arrayCopy.count)
16 | return buffer
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Functions/MetalScheduler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetalScheduler.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 22/5/21.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 | import simd
11 |
12 | class MetalScheduler {
13 |
14 | // MARK: - Properties
15 |
16 | static let shared = MetalScheduler()
17 |
18 | public enum Task {
19 | case createSASPoints
20 | case none
21 | }
22 |
23 | // MARK: - Private properties
24 |
25 | let device: MTLDevice!
26 | let queue: MTLCommandQueue?
27 | let library: MTLLibrary?
28 |
29 | // PipelineStateBundle bundles
30 | var createSphereModelBundle = PipelineStateBundle()
31 | var createBondsBundle = PipelineStateBundle()
32 | var createSASPointsBundle = PipelineStateBundle()
33 | var removeSASPointsInsideSolidBundle = PipelineStateBundle()
34 |
35 | /// DispatchQueue for synchronization
36 | private(set) var metalDispatchQueue: DispatchQueue
37 |
38 | // MARK: - Initialization
39 |
40 | private init() {
41 | // Initialize device
42 | self.device = MTLCreateSystemDefaultDevice()
43 |
44 | // Initialize command queue
45 | self.queue = device.makeCommandQueue()
46 |
47 | // Initialize default Metal library
48 | self.library = device.makeDefaultLibrary()
49 |
50 | // Create a queue to dispatch metal work (FIFO) to synchronize work
51 | metalDispatchQueue = DispatchQueue.init(label: "Metal Scheduler", qos: .default)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Meshes/AtomProperties.h:
--------------------------------------------------------------------------------
1 | //
2 | // AtomProperties.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 1/6/21.
6 | //
7 |
8 | #ifndef AtomProperties_h
9 | #define AtomProperties_h
10 |
11 | #include
12 |
13 | // Carbon, Hydrogen, Nitrogen, Oxygen, Sulfur, Others
14 | constant float atomSolidSphereRadius [] = {1.70, 1.10, 1.55, 1.52, 1.80, 1.50};
15 | constant float atomAtomicRadius [] = {0.70, 0.25, 0.65, 0.60, 1.00, 0.50};
16 |
17 | #endif /* AtomProperties_h */
18 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Meshes/GeneratedVertex.h:
--------------------------------------------------------------------------------
1 | //
2 | // GeneratedVertex.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 18/10/21.
6 | //
7 |
8 | #ifndef GeneratedVertex_h
9 | #define GeneratedVertex_h
10 |
11 | #include
12 | #include
13 |
14 | typedef struct {
15 |
16 | /// Position of the vertex in world space
17 | simd_float3 position;
18 |
19 | /// Normal of the surface tangent to the vertex in world space
20 | simd_float3 normal; // TO-DO: Use simd_half3 when supported...
21 |
22 | } GeneratedVertex;
23 |
24 | typedef struct {
25 |
26 | /// Position of the vertex in world space
27 | simd_float3 position;
28 |
29 | /// Position of the atom center in world space
30 | simd_float3 billboard_world_center;
31 |
32 | /// Position of the atomic center in world space
33 | simd_float2 billboardMapping;
34 |
35 | /// Atom radii
36 | float atom_radius;
37 |
38 | } BillboardVertex;
39 |
40 | typedef struct {
41 |
42 | /// Position of the first atom in world space.
43 | simd_float3 atom_A;
44 |
45 | /// Position of the first atom in world space.
46 | simd_float3 atom_B;
47 |
48 | /// Cylinder center in world space.
49 | simd_float3 cylinder_center;
50 |
51 | /// Bond radius.
52 | float bond_radius;
53 |
54 | } RawBondStruct;
55 |
56 | typedef struct {
57 |
58 | /// Position of the vertex in world space
59 | simd_float3 position;
60 |
61 | } DebugPoint;
62 |
63 | #endif /* GeneratedVertex_h */
64 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/ComputePasses/ComputePipelines.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComputePipelines.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 21/2/22.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 |
11 | extension MutableState {
12 |
13 | // MARK: - Fill color pass
14 |
15 | func makeFillColorComputePipelineState(device: MTLDevice) {
16 | // Setup pipeline
17 | guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: Bundle(for: ProteinRenderer.self)) else {
18 | fatalError()
19 | }
20 |
21 | guard let fillColorKernel = defaultLibrary.makeFunction(name: "fill_color_buffer") else {
22 | NSLog("Failed to make fill color kernel")
23 | return
24 | }
25 |
26 | fillColorComputePipelineState = try? device.makeComputePipelineState(function: fillColorKernel)
27 | }
28 |
29 | // MARK: - Shadow blurring pass
30 |
31 | func makeShadowBlurringComputePipelineState(device: MTLDevice) {
32 | // Setup pipeline
33 | guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: Bundle(for: ProteinRenderer.self)) else {
34 | NSLog("Failed to retrieve the default library.")
35 | return
36 | }
37 |
38 | guard let shadowBlurKernel = defaultLibrary.makeFunction(name: "shadow_blur") else {
39 | NSLog("Failed to make shadow blur kernel")
40 | return
41 | }
42 |
43 | shadowBlurPipelineState = try? device.makeComputePipelineState(function: shadowBlurKernel)
44 | }
45 |
46 | // MARK: - Motion texture pass
47 |
48 | func makeMotionComputePipelineState(device: MTLDevice) {
49 | // Setup pipeline
50 | guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: Bundle(for: ProteinRenderer.self)) else {
51 | NSLog("Failed to retrieve the default library.")
52 | return
53 | }
54 |
55 | guard let motionKernel = defaultLibrary.makeFunction(name: "motion_texture") else {
56 | NSLog("Failed to make shadow blur kernel")
57 | return
58 | }
59 |
60 | let descriptor = MTLComputePipelineDescriptor()
61 | descriptor.computeFunction = motionKernel
62 | descriptor.label = "Motion Texture Pass"
63 | motionPipelineState = try? device.makeComputePipelineState(
64 | descriptor: descriptor,
65 | options: MTLPipelineOption()
66 | ).0
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/Data Structures/VisualizationConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VisualizationConfiguration.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 30/4/23.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct VisualizationConfiguration {
12 | let atomRadii: AtomRadii
13 | let colorBy: ProteinColorByOption
14 | }
15 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/Ray.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ray.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 24/2/24.
6 | //
7 |
8 | import Foundation
9 | import simd
10 |
11 | struct Ray {
12 | let origin: simd_float3
13 | let direction: simd_float3
14 |
15 | static func *(transform: float4x4, ray: Ray) -> Ray {
16 | let originT = (transform * simd_float4(ray.origin, 1)).xyz
17 | let directionT = (transform * simd_float4(ray.direction, 0)).xyz
18 | return Ray(origin: originT, direction: directionT)
19 | }
20 |
21 | /// Determine the point along this ray at the given parameter
22 | func extrapolate(_ parameter: Float) -> simd_float4 {
23 | return simd_float4(origin + parameter * direction, 1)
24 | }
25 |
26 | /// Determine the parameter corresponding to the point,
27 | /// assuming it lies on this ray
28 | func interpolate(_ point: simd_float4) -> Float {
29 | return length(point.xyz - origin) / length(direction)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/RenderPasses/ImpostorPass/DepthPrePassStage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DepthBoundPass.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 25/5/22.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 | import QuartzCore
11 | import MetalKit
12 |
13 | extension MutableState {
14 |
15 | func encodeDepthBoundStage(renderer: ProteinRenderer, renderCommandEncoder: MTLRenderCommandEncoder, uniformBuffer: inout MTLBuffer) {
16 |
17 | // Ensure transparent buffers are loaded
18 | guard let billboardVertexBuffers = self.billboardVertexBuffers else { return }
19 | guard let impostorIndexBuffer = self.impostorIndexBuffer else { return }
20 |
21 | // MARK: - Impostor sphere rendering
22 |
23 | guard let pipelineState = depthPrePassRenderPipelineState else {
24 | return
25 | }
26 | renderCommandEncoder.setRenderPipelineState(pipelineState)
27 |
28 | // Add buffers to pipeline
29 | renderCommandEncoder.setVertexBuffer(
30 | billboardVertexBuffers.positionBuffer,
31 | offset: 0,
32 | index: 0
33 | )
34 | renderCommandEncoder.setVertexBuffer(
35 | billboardVertexBuffers.atomWorldCenterBuffer,
36 | offset: 0,
37 | index: 1
38 | )
39 | renderCommandEncoder.setVertexBuffer(
40 | uniformBuffer,
41 | offset: 0,
42 | index: 5
43 | )
44 |
45 | // Don't render back-facing triangles (cull them)
46 | renderCommandEncoder.setCullMode(.back)
47 |
48 | // Draw primitives
49 | guard let configurationSelector = scene.configurationSelector else {
50 | return
51 | }
52 | let indexBufferRegion = configurationSelector.getImpostorIndexBufferRegion()
53 |
54 | renderCommandEncoder.drawIndexedPrimitives(
55 | type: .triangle,
56 | indexCount: indexBufferRegion.length,
57 | indexType: .uint32,
58 | indexBuffer: impostorIndexBuffer,
59 | indexBufferOffset: indexBufferRegion.offset * MemoryLayout.stride
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/RenderPasses/Postprocessing/CopyToDrawable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CopyToDrawable.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 9/4/23.
6 | //
7 |
8 | import Foundation
9 | import MetalKit
10 |
11 | extension MutableState {
12 |
13 | /// Blits the final, upscaled from renderer texture to the drawable size.
14 | func copyToDrawable(
15 | commandBuffer: MTLCommandBuffer,
16 | finalRenderedTexture: MTLTexture,
17 | drawableTexture: MTLTexture
18 | ) {
19 | guard finalRenderedTexture.width == drawableTexture.width else {
20 | return
21 | }
22 | guard finalRenderedTexture.height == drawableTexture.height else {
23 | return
24 | }
25 | let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder()
26 | blitCommandEncoder?.copy(from: finalRenderedTexture, to: drawableTexture)
27 | blitCommandEncoder?.endEncoding()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/RenderPasses/Postprocessing/ShadowBlurPass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShadowBlurStage.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 9/4/23.
6 | //
7 |
8 | import Foundation
9 | import MetalKit
10 |
11 | extension MutableState {
12 |
13 | func shadowBlurPass(
14 | renderer: ProteinRenderer,
15 | commandBuffer: MTLCommandBuffer,
16 | texture: MTLTexture
17 | ) {
18 | // Set Metal compute encoder
19 | guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else {
20 | return
21 | }
22 |
23 | guard let shadowBlurPipelineState = shadowBlurPipelineState else {
24 | return
25 | }
26 |
27 | // Set compute pipeline state for current arguments
28 | computeEncoder.setComputePipelineState(shadowBlurPipelineState)
29 |
30 | computeEncoder.setTexture(texture, index: 0)
31 |
32 | // Schedule the threads
33 | if device.supportsFamily(.common3) {
34 | // Create threadgroup sizes
35 | let width = shadowBlurPipelineState.threadExecutionWidth
36 | let height = shadowBlurPipelineState.maxTotalThreadsPerThreadgroup / width
37 | let threadsPerThreadgroup = MTLSizeMake(width, height, 1)
38 | // Create threadgroup grid
39 | let threadsPerGrid = MTLSize(
40 | width: texture.width,
41 | height: texture.height,
42 | depth: 1
43 | )
44 | // Dispatch threads
45 | computeEncoder.dispatchThreads(threadsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup)
46 | }
47 |
48 | // End encoding
49 | computeEncoder.endEncoding()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/RenderPasses/Postprocessing/Upscaling/MetalFXScalers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetalFXScalers.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 9/4/23.
6 | //
7 |
8 | import Foundation
9 | #if canImport(MetalFX)
10 | import MetalFX
11 | #endif
12 | import MetalKit
13 |
14 | extension MutableState {
15 |
16 | /// Creates the MetalFX spatial scaler.
17 | func makeSpatialScaler(inputSize: MTLSize, outputSize: MTLSize) {
18 | #if canImport(MetalFX)
19 | let descriptor = MTLFXSpatialScalerDescriptor()
20 | descriptor.inputWidth = inputSize.width
21 | descriptor.inputHeight = inputSize.height
22 | descriptor.outputWidth = outputSize.width
23 | descriptor.outputHeight = outputSize.height
24 | descriptor.colorTextureFormat = .bgra8Unorm
25 | descriptor.outputTextureFormat = .bgra8Unorm
26 | descriptor.colorProcessingMode = .linear
27 |
28 | guard let spatialScaler = descriptor.makeSpatialScaler(device: device) else {
29 | print("The spatial scaler effect is not usable!")
30 | return
31 | }
32 | metalFXSpatialScaler = spatialScaler
33 | #else
34 | print("Can't import MetalFX!")
35 | #endif
36 | }
37 |
38 | /// Creates the MetalFX spatial scaler.
39 | func makeTemporalScaler(inputSize: MTLSize, outputSize: MTLSize) {
40 | #if canImport(MetalFX)
41 | let descriptor = MTLFXTemporalScalerDescriptor()
42 | descriptor.inputWidth = inputSize.width
43 | descriptor.inputHeight = inputSize.height
44 | descriptor.outputWidth = outputSize.width
45 | descriptor.outputHeight = outputSize.height
46 | descriptor.colorTextureFormat = .bgra8Unorm
47 | descriptor.depthTextureFormat = .depth32Float
48 | descriptor.motionTextureFormat = ProteinRenderedTextures.motionPixelFormat
49 | descriptor.outputTextureFormat = .bgra8Unorm
50 | descriptor.isAutoExposureEnabled = false
51 |
52 | guard let temporalScaler = descriptor.makeTemporalScaler(device: device) else {
53 | print("The temporal scaler effect is not usable!")
54 | return
55 | }
56 | temporalScaler.motionVectorScaleX = Float(inputSize.width)
57 | temporalScaler.motionVectorScaleY = Float(inputSize.height)
58 | metalFXTemporalScaler = temporalScaler
59 | #else
60 | print("Can't import MetalFX!")
61 | #endif
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/RenderPasses/ShadowPass/ShadowDepthPrePass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DepthBoundShadowPass.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 4/6/22.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 | import QuartzCore
11 | import MetalKit
12 |
13 | extension MutableState {
14 |
15 | func encodeShadowDepthPrePassStage(renderer: ProteinRenderer, renderCommandEncoder: MTLRenderCommandEncoder, uniformBuffer: inout MTLBuffer) {
16 |
17 | // Ensure transparent buffers are loaded
18 | guard let billboardVertexBuffers = self.billboardVertexBuffers else { return }
19 | guard let impostorIndexBuffer = self.impostorIndexBuffer else { return }
20 |
21 | // MARK: - Depth pre-pass rendering
22 |
23 | guard let pipelineState = shadowDepthPrePassRenderPipelineState else {
24 | return
25 | }
26 | renderCommandEncoder.setRenderPipelineState(pipelineState)
27 |
28 | // Add buffers to pipeline
29 | renderCommandEncoder.setVertexBuffer(
30 | billboardVertexBuffers.positionBuffer,
31 | offset: 0,
32 | index: 0
33 | )
34 | renderCommandEncoder.setVertexBuffer(
35 | billboardVertexBuffers.atomWorldCenterBuffer,
36 | offset: 0,
37 | index: 1
38 | )
39 | renderCommandEncoder.setVertexBuffer(
40 | uniformBuffer,
41 | offset: 0,
42 | index: 4
43 | )
44 |
45 | // Don't render back-facing triangles (cull them)
46 | renderCommandEncoder.setCullMode(.back)
47 |
48 | // Draw primitives
49 | guard let configurationSelector = scene.configurationSelector else {
50 | return
51 | }
52 | let indexBufferRegion = configurationSelector.getImpostorIndexBufferRegion()
53 |
54 | renderCommandEncoder.drawIndexedPrimitives(
55 | type: .triangle,
56 | indexCount: indexBufferRegion.length,
57 | indexType: .uint32,
58 | indexBuffer: impostorIndexBuffer,
59 | indexBufferOffset: indexBufferRegion.offset * MemoryLayout.stride
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/Textures/AmbientOcclusion3DTexture.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmbientOcclusion3DTexture.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 29/4/23.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 |
11 | struct AmbientOcclusion3DTexture {
12 |
13 | static let defaultSize: Int = 64
14 | static let pixelFormat: MTLPixelFormat = .r32Float
15 |
16 | var texture: MTLTexture?
17 | var textureSize: Int = AmbientOcclusion3DTexture.defaultSize
18 |
19 | mutating func makeTexture(device: MTLDevice) {
20 |
21 | // MARK: - Texture size
22 | self.textureSize = AmbientOcclusion3DTexture.defaultSize
23 |
24 | // MARK: - Texture descriptor
25 | let textureDescriptor = MTLTextureDescriptor()
26 | textureDescriptor.width = textureSize
27 | textureDescriptor.height = textureSize
28 | textureDescriptor.depth = textureSize
29 | textureDescriptor.textureType = .type3D
30 | textureDescriptor.storageMode = .private
31 |
32 | texture = device.makeTexture(descriptor: textureDescriptor)
33 | texture?.label = "Ambient Occlusion Texture"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/Textures/BenchmarkTextures.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BenchmarkTextures.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 25/1/23.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 |
11 | struct BenchmarkTextures {
12 |
13 | static let benchmarkResolution: Int = 1440
14 |
15 | var colorTexture: MTLTexture!
16 | var depthTexture: MTLTexture!
17 |
18 | static let colorPixelFormat = MTLPixelFormat.bgra8Unorm
19 | static let depthPixelFormat = MTLPixelFormat.depth32Float
20 |
21 | mutating func makeTextures(device: MTLDevice) {
22 |
23 | // Color texture descriptor
24 | let colorTextureDescriptor = MTLTextureDescriptor
25 | .texture2DDescriptor(pixelFormat: BenchmarkTextures.colorPixelFormat,
26 | width: BenchmarkTextures.benchmarkResolution,
27 | height: BenchmarkTextures.benchmarkResolution,
28 | mipmapped: false)
29 | colorTextureDescriptor.textureType = .type2D
30 | colorTextureDescriptor.usage = [.renderTarget]
31 | colorTextureDescriptor.storageMode = .shared
32 | colorTexture = device.makeTexture(descriptor: colorTextureDescriptor)
33 | colorTexture.label = "Benchmark color Texture"
34 |
35 | // Depth texture
36 | let depthTextureDescriptor = MTLTextureDescriptor
37 | .texture2DDescriptor(pixelFormat: BenchmarkTextures.depthPixelFormat,
38 | width: BenchmarkTextures.benchmarkResolution,
39 | height: BenchmarkTextures.benchmarkResolution,
40 | mipmapped: false)
41 | depthTextureDescriptor.textureType = .type2D
42 | depthTextureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget]
43 | depthTextureDescriptor.storageMode = .shared
44 | depthTexture = device.makeTexture(descriptor: depthTextureDescriptor)
45 | depthTexture.label = "Benchmark depth Texture"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/Textures/HQTextures.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HQTextures.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 20/12/21.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 |
11 | struct HQTextures {
12 | var hqTexture: MTLTexture!
13 | var hqDepthTexture: MTLTexture!
14 | var hqSampler: MTLSamplerState?
15 | var textureWidth: Int!
16 | var textureHeight: Int!
17 |
18 | // Since the texture should be just enough to fit the bounding sphere on an
19 | // orthographic projection, the hq texture should be square. High resolution
20 | // hqs are *very* expensive due to the need to call the fragment shader.
21 |
22 | static let hqTexturePixelFormat = MTLPixelFormat.bgra8Unorm
23 | static let hqDepthTexturePixelFormat = MTLPixelFormat.depth32Float
24 |
25 | mutating func makeTextures(device: MTLDevice, photoConfig: PhotoModeConfig) {
26 |
27 | // MARK: - Common texture descriptor
28 | let hqTextureDescriptor = MTLTextureDescriptor
29 | .texture2DDescriptor(pixelFormat: HQTextures.hqTexturePixelFormat,
30 | width: photoConfig.finalTextureSize,
31 | height: photoConfig.finalTextureSize,
32 | mipmapped: false)
33 |
34 | hqTextureDescriptor.textureType = .type2D
35 |
36 | // MARK: - HQ color texture
37 |
38 | hqTextureDescriptor.pixelFormat = HQTextures.hqTexturePixelFormat
39 | hqTextureDescriptor.usage = [.renderTarget]
40 |
41 | hqTexture = device.makeTexture(descriptor: hqTextureDescriptor)
42 | hqTexture.label = "HQ Texture"
43 |
44 | // MARK: - HQ depth texture
45 |
46 | hqTextureDescriptor.pixelFormat = .depth32Float
47 | hqTextureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget]
48 | hqTextureDescriptor.storageMode = .shared
49 | hqDepthTexture = device.makeTexture(descriptor: hqTextureDescriptor)
50 | hqDepthTexture.label = "HQ Depth Texture"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Renderer/Textures/MetalFXUpscaledTexture.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetalFXUpscaledTexture.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 9/4/23.
6 | //
7 |
8 | import Foundation
9 | import MetalKit
10 |
11 | struct MetalFXUpscaledTexture {
12 |
13 | var upscaledColor: MTLTexture!
14 |
15 | mutating func makeTexture(device: MTLDevice, width: Int, height: Int) {
16 |
17 | // Color texture descriptor
18 | let colorTextureDescriptor = MTLTextureDescriptor
19 | .texture2DDescriptor(
20 | pixelFormat: BenchmarkTextures.colorPixelFormat,
21 | width: width,
22 | height: height,
23 | mipmapped: false
24 | )
25 | colorTextureDescriptor.textureType = .type2D
26 | colorTextureDescriptor.usage = [.renderTarget, .shaderWrite]
27 | colorTextureDescriptor.storageMode = .private
28 | upscaledColor = device.makeTexture(descriptor: colorTextureDescriptor)
29 | upscaledColor.label = "MetalFX Upscaled Texture"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Shaders/FrameData.h:
--------------------------------------------------------------------------------
1 | //
2 | // FrameData.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 17/10/21.
6 | //
7 |
8 | #ifndef FrameData_h
9 | #define FrameData_h
10 |
11 | #include
12 | #include "../SharedDataStructs.h"
13 |
14 | typedef struct {
15 |
16 | /// Number of atoms in a single configuration.
17 | int atoms_per_configuration;
18 |
19 | /// The depth bias, in Normalized Device Coordinates, that is applied in the
20 | /// depth and shadow depth pre-passes, to avoid artifacts due to the regular
21 | /// and shadow passes excluding fragments whose depth is close to the
22 | /// pre-computed depth of the pre-pass. Should be the equivalent of about
23 | /// 2 Armstrongs (translated to NDCs).
24 | float depth_bias;
25 |
26 | // MARK: - Matrices
27 |
28 | /// Model to view matrix
29 | simd_float4x4 model_view_matrix;
30 |
31 | /// Projection matrix
32 | simd_float4x4 projectionMatrix;
33 |
34 | /// Rotation matrix
35 | simd_float4x4 rotation_matrix;
36 |
37 | /// Inverse rotation matrix
38 | simd_float4x4 inverse_rotation_matrix;
39 |
40 | /// Shadow projection matrix
41 | simd_float4x4 shadowProjectionMatrix;
42 |
43 | /// Rotation matrix to view the model from the sun's point of view.
44 | simd_float4x4 sun_rotation_matrix;
45 |
46 | /// Transform from camera coordinates to sun's perspective coordinates.
47 | simd_float4x4 camera_to_shadow_projection_matrix;
48 |
49 | // MARK: - Bonds
50 |
51 | simd_float3 bond_color;
52 |
53 | // MARK: - Shadows
54 |
55 | /// Sun direction.
56 | simd_float3 sun_direction;
57 | /// Whether it should cast shadows.
58 | int8_t has_shadows;
59 | /// The strength of the shadow color subtraction, from 0 to 1.
60 | float shadow_strength;
61 |
62 | /// Whether it should use depth cueing.
63 | int8_t has_depth_cueing;
64 | /// The strength of the depth cueing, from 0 to 1.
65 | float depth_cueing_strength;
66 |
67 | } FrameData;
68 |
69 | #endif /* FrameData_h */
70 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Shaders/Postprocessing/ReprojectionData.h:
--------------------------------------------------------------------------------
1 | //
2 | // ReprojectionData.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 16/4/23.
6 | //
7 |
8 | #ifndef ReprojectionData_h
9 | #define ReprojectionData_h
10 |
11 | #include
12 | #include "../../SharedDataStructs.h"
13 |
14 | typedef struct {
15 |
16 | /// Reprojects from the current frame's NDC to the previous frame's NDC.
17 | simd_float4x4 reprojection_matrix;
18 | /// Width of the render target.
19 | int32_t renderWidth;
20 | /// Height of the render target.
21 | int32_t renderHeight;
22 | /// Pixel jitter in NDC coordinates (-0.5 to 0.5).
23 | simd_float2 pixel_jitter;
24 | /// Jitter performed on the projection, in texture coordinates.
25 | simd_float2 texel_jitter;
26 | /// Jitter performed on the projection, in texture coordinates.
27 | simd_float2 previous_texel_jitter;
28 |
29 | } ReprojectionData;
30 |
31 | #endif /* ReprojectionData_h */
32 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Shaders/Postprocessing/ShadowBlur.metal:
--------------------------------------------------------------------------------
1 | //
2 | // ShadowBlur.metal
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 9/4/23.
6 | //
7 |
8 | #include
9 | using namespace metal;
10 |
11 | kernel void shadow_blur(const texture2d renderer_output [[ texture(0) ]],
12 | uint2 texture_position [[ thread_position_in_grid ]]) {
13 | float4 source_color = float4(0.0, 0.0, 0.0, 0.0);
14 | for (int i = 0; i < 7; i++) {
15 | for (int j = 0; j < 7; j++) {
16 | uint2 sample_position = texture_position;
17 | sample_position.x += i;
18 | sample_position.y += j;
19 | source_color += renderer_output.read(sample_position);
20 | }
21 | }
22 | renderer_output.write(source_color / 49, texture_position);
23 | }
24 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Shaders/ShaderCommon.h:
--------------------------------------------------------------------------------
1 | //
2 | // ShaderCommon.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/6/22.
6 | //
7 |
8 | #ifndef ShaderCommon_h
9 | #define ShaderCommon_h
10 |
11 | struct DepthPrePassFragmentOut{
12 | half4 throwaway_color [[ color(0) ]];
13 | float bounded_depth [[ color(1) ]];
14 | };
15 |
16 | struct ShadowDepthPrePassFragmentOut{
17 | float throwaway_color [[ color(0) ]];
18 | float bounded_depth [[ color(1) ]];
19 | };
20 |
21 | #endif /* ShaderCommon_h */
22 |
--------------------------------------------------------------------------------
/BioViewer/Metal/SharedDataStructs.h:
--------------------------------------------------------------------------------
1 | //
2 | // SharedDataStructs.h
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 8/1/22.
6 | //
7 |
8 | #ifndef SharedDataStructs_h
9 | #define SharedDataStructs_h
10 |
11 | /// Supported number of (different) atom types.
12 | #define ATOM_TYPE_COUNT 64
13 |
14 | struct Sphere {
15 | vector_float3 origin;
16 | float radius;
17 | };
18 |
19 | typedef struct {
20 | /// Radius of each atom type (C, H, N, O, S, Others).
21 | float atomRadius [ATOM_TYPE_COUNT];
22 | } AtomRadii;
23 |
24 | #endif /* SharedDataStructs_h */
25 |
--------------------------------------------------------------------------------
/BioViewer/Metal/Utilities/SIMDExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SIMDExtensions.swift
3 | // BioViewer
4 | //
5 | // Imported by Raúl Montón Pinillos on 1/6/21.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - SIMD4
11 | extension SIMD4 {
12 | // Convenience getter for the first 3 components of a SIMD4 vector.
13 | var xyz: SIMD3 {
14 | self[SIMD3(0, 1, 2)]
15 | }
16 | }
17 |
18 | extension simd_quatd {
19 | static var identity: simd_quatd {
20 | return simd_quatd(ix: 0, iy: 0, iz: 0, r: 1)
21 | }
22 | }
23 |
24 | extension Float {
25 | static var randomSign: Float {
26 | if Bool.random() {
27 | return 1
28 | } else {
29 | return -1
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/BioViewer/MetalLegacySupport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetalLegacySupport.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 30/4/22.
6 | //
7 |
8 | import Foundation
9 | import Metal
10 |
11 | class MetalLegacySupport {
12 |
13 | static func legacyDispatchThreadsForArray(commandEncoder: MTLComputeCommandEncoder, length: Int, pipelineState: MTLComputePipelineState) {
14 |
15 | let threadsPerThreadgroup = MTLSizeMake(pipelineState.maxTotalThreadsPerThreadgroup, 1, 1)
16 | let numberOfFullThreadgroups = Int(ceilf(Float(length) / Float(pipelineState.maxTotalThreadsPerThreadgroup)))
17 | let threadgroupSize = MTLSizeMake(numberOfFullThreadgroups, 1, 1)
18 |
19 | // Dispatch the threadgroups
20 | commandEncoder.dispatchThreadgroups(threadgroupSize, threadsPerThreadgroup: threadsPerThreadgroup)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/BioViewer/Models/Protein/ProteinFileInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinFileInfo.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 14/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Class containing the data related to the imported protein file itself.
11 | struct ProteinFileInfo {
12 |
13 | /// PDB ID as in RCSB database.
14 | var pdbID: String?
15 | /// Human-readable description of the protein.
16 | var description: String?
17 | /// Authors of the file.
18 | var authors: String?
19 | /// Full source file text
20 | var sourceLines: [String]?
21 |
22 | /// List of all lines with warnings
23 | var warningIndices: [Int] = []
24 |
25 | // MARK: - Initialization
26 |
27 | init() {}
28 |
29 | init(pdbID: String?, description: String?, authors: String?, sourceLines: [String]?) {
30 | self.pdbID = pdbID
31 | self.description = description
32 | self.authors = authors
33 | self.sourceLines = sourceLines
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/BioViewer/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/BioViewer/Views/Onboarding/NewsRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/3/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct NewsRow: View {
11 |
12 | let rowType: NewsRowType
13 | let title: String
14 | let subtitle: String
15 |
16 | init(whatsNewItem: WhatsNew) {
17 | self.rowType = whatsNewItem.type
18 | self.title = whatsNewItem.title
19 | self.subtitle = whatsNewItem.subtitle
20 | }
21 |
22 | var body: some View {
23 | HStack(alignment: .top, spacing: 18) {
24 | switch rowType {
25 | case .feature:
26 | VStack(spacing: 4) {
27 | Image(systemName: "newspaper.fill")
28 | .foregroundColor(.accentColor)
29 | .font(.title)
30 | Text("New".uppercased())
31 | .bold()
32 | .font(.caption)
33 | .foregroundColor(.accentColor)
34 | }
35 | .padding(.top, 4)
36 | case .fix:
37 | VStack(spacing: 4) {
38 | Image(systemName: "wrench.fill")
39 | .foregroundColor(.accentColor)
40 | .font(.title)
41 | Text("Fix".uppercased())
42 | .bold()
43 | .font(.caption)
44 | .foregroundColor(.accentColor)
45 | }
46 | .padding(.top, 4)
47 | }
48 |
49 | VStack(alignment: .leading, spacing: 4) {
50 | Text(title)
51 | .bold()
52 | Text(subtitle)
53 | .foregroundColor(.gray)
54 | }
55 | }
56 | .padding(.vertical, 6)
57 | }
58 | }
59 |
60 | struct NewsRow_Previews: PreviewProvider {
61 | static var previews: some View {
62 | List {
63 | NewsRow(whatsNewItem: WhatsNew(type: .feature,
64 | title: "Selectable atom radii",
65 | subtitle: "Atom radii can now be selected for both the solid spheres and the ball and stick visualization modes."))
66 | }
67 | .listStyle(.plain)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/BioViewer/Views/Onboarding/WhatsNewView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WhatsNewView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/3/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WhatsNewView: View {
11 |
12 | @Environment(\.dismiss) var dismiss
13 | @StateObject var viewModel = WhatsNewViewModel()
14 |
15 | var body: some View {
16 | NavigationView {
17 | ZStack(alignment: .bottom) {
18 | List(viewModel.newItems) { newItem in
19 | NewsRow(whatsNewItem: newItem)
20 | .listRowSeparator(.hidden)
21 | }
22 | .listStyle(.plain)
23 | .navigationTitle(NSLocalizedString("What's new (\(viewModel.version))", comment: ""))
24 |
25 | // Bottom buttons
26 | VStack {
27 | Divider()
28 |
29 | Button(action: {
30 | dismiss()
31 | }, label: {
32 | Text(NSLocalizedString("Continue", comment: ""))
33 | .padding(4)
34 | .frame(maxWidth: .infinity)
35 | })
36 | .padding(.horizontal)
37 | .padding(.top, 4)
38 | .frame(maxWidth: .infinity)
39 | .buttonStyle(.borderedProminent)
40 |
41 | Button(action: {
42 | dismiss()
43 | AppState.shared.userDoesNotWantUpdates()
44 | }, label: {
45 | Text(NSLocalizedString("Don't notify new features", comment: ""))
46 | .padding(4)
47 | .frame(maxWidth: .infinity)
48 | })
49 | .padding(.bottom, 4)
50 | }
51 | .background(.regularMaterial)
52 | }
53 | }
54 | .navigationBarTitleDisplayMode(.large)
55 | }
56 | }
57 |
58 | struct WhatsNewView_Previews: PreviewProvider {
59 | static var previews: some View {
60 | WhatsNewView()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/BioViewer/Views/Onboarding/WhatsNewViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WhatsNewViewModel.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 12/3/22.
6 | //
7 |
8 | import Foundation
9 |
10 | enum NewsRowType {
11 | case feature
12 | case fix
13 | }
14 |
15 | struct WhatsNew: Identifiable {
16 | let id = UUID()
17 | let type: NewsRowType
18 | let title: String
19 | let subtitle: String
20 | }
21 |
22 | class WhatsNewViewModel: ObservableObject {
23 |
24 | @Published var newItems = [WhatsNew]()
25 | let version = AppState.shared.version()
26 |
27 | init() {
28 | var changeLog: NSDictionary?
29 | if let path = Bundle.main.path(forResource: "ChangeLog", ofType: "plist") {
30 | changeLog = NSDictionary(contentsOfFile: path)
31 | }
32 | guard let changeLog = changeLog else {
33 | return
34 | }
35 | guard let currentNews = changeLog[version] as? [NSDictionary] else {
36 | return
37 | }
38 | for newItem in currentNews {
39 | var type: NewsRowType?
40 | if newItem["Type"] as? String == "FEAT" {
41 | type = .feature
42 | } else if newItem["Type"] as? String == "FIX" {
43 | type = .fix
44 | }
45 | let title = newItem["Title"] as? String
46 | let subtitle = newItem["Subtitle"] as? String
47 |
48 | if let type = type, let title = title, let subtitle = subtitle {
49 | newItems.append(WhatsNew(type: type, title: title, subtitle: subtitle))
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/BioViewer/Views/PresentedViews/PeriodicTableContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PeriodicTableContentView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 29/11/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PeriodicTableContentView: View {
11 | var body: some View {
12 | GeometryReader { geometryProxy in
13 | ScrollView(.horizontal) {
14 | VStack(spacing: 2) {
15 | ForEach(0..<6) { _ in
16 | HStack(spacing: 2) {
17 | ForEach(0..<17, id: \.self) { _ in
18 | ZStack {
19 | Rectangle()
20 | .aspectRatio(1.0, contentMode: .fit)
21 | Text("He")
22 | .foregroundColor(.white)
23 | }
24 | .frame(minWidth: 24)
25 | }
26 | }
27 | }
28 | }
29 | .padding()
30 | .frame(idealWidth: geometryProxy.size.width)
31 | }
32 | }
33 | }
34 | }
35 |
36 | struct PeriodicTableContentView_Previews: PreviewProvider {
37 | static var previews: some View {
38 | PeriodicTableContentView()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/BioViewer/Views/PresentedViews/PeriodicTableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PeriodicTableView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 29/11/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PeriodicTableView: View {
11 |
12 | @Environment(\.dismiss) var dismiss
13 |
14 | var body: some View {
15 | if #available(iOS 16, *) {
16 | NavigationStack {
17 | VStack {
18 | Divider()
19 | PeriodicTableContentView()
20 | Spacer()
21 | }
22 | .navigationTitle("Elements")
23 | .navigationBarTitleDisplayMode(.inline)
24 | .navigationBarItems(
25 | leading: Button(
26 | action: {
27 | dismiss()
28 | },
29 | label: {
30 | Text(NSLocalizedString("Close", comment: ""))
31 | }
32 | )
33 | )
34 | }
35 | } else {
36 | EmptyView()
37 | }
38 | }
39 | }
40 |
41 | struct PeriodicTableView_Previews: PreviewProvider {
42 | static var previews: some View {
43 | PeriodicTableView()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Data/BallAndStickRadiusOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SolidSpheresRadiusOptions.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/3/22.
6 | //
7 |
8 | import Foundation
9 |
10 | enum BallAndStickRadiusOptions: PickableEnum {
11 | case fixed
12 | case scaledVDW
13 |
14 | var displayName: String {
15 | switch self {
16 | case .fixed:
17 | return NSLocalizedString("Fixed", comment: "")
18 | case .scaledVDW:
19 | return NSLocalizedString("Van Der Waals", comment: "")
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Data/ProteinGraphicsSettings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinGraphicsSettings.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 27/4/23.
6 | //
7 |
8 | import Foundation
9 |
10 | @MainActor @Observable class ProteinGraphicsSettings {
11 |
12 | weak var proteinViewModel: ProteinViewModel?
13 |
14 | var metalFXUpscalingMode: MetalFXUpscalingMode = .none {
15 | didSet {
16 | guard let renderer = proteinViewModel?.renderer else {
17 | return
18 | }
19 | switch metalFXUpscalingMode {
20 | case .temporal, .spatial:
21 | self.ssaaFactor = 1.5
22 | self.metalFXFactor = 1.5
23 | case .none:
24 | self.ssaaFactor = 1.0
25 | self.metalFXFactor = 1.0
26 | }
27 | Task {
28 | await renderer.mutableState.updateMetalFXUpscalingMode(
29 | to: metalFXUpscalingMode,
30 | renderer: renderer
31 | )
32 | }
33 | }
34 | }
35 |
36 | var ssaaFactor: Float = 1.0 {
37 | didSet {
38 | Task {
39 | await updateFactors()
40 | }
41 | }
42 | }
43 | var metalFXFactor: Float = 1.5 {
44 | didSet {
45 | Task {
46 | await updateFactors()
47 | }
48 | }
49 | }
50 |
51 | private func updateFactors() async {
52 | guard let renderer = proteinViewModel?.renderer else {
53 | return
54 | }
55 | await renderer.mutableState.updateProteinRenderFactors(
56 | ssaa: ssaaFactor,
57 | metalFXUpscaling: metalFXFactor,
58 | renderer: renderer
59 | )
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Data/ProteinViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinViewModel.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 13/5/21.
6 | //
7 |
8 | import Combine
9 | import Foundation
10 | import SwiftUI
11 |
12 | @MainActor class ProteinViewModel: ObservableObject {
13 |
14 | // MARK: - Properties
15 |
16 | /// Metal rendering engine.
17 | let renderer: ProteinRenderer
18 | /// Datasource to hold actual protein data.
19 | var dataSource: ProteinDataSource?
20 | /// Linked ProteinColorViewModel
21 | var colorViewModel: ProteinColorViewModel?
22 | /// Linked ProteinVisualizationViewModel
23 | var visualizationViewModel: ProteinVisualizationViewModel?
24 | /// Toolbar view model.
25 | var toolbarConfig: ToolbarConfig?
26 | /// Reference to the status view model for updates.
27 | var statusViewModel: StatusViewModel?
28 |
29 | /// Delegate to handle dropped files in view.
30 | var dropHandler: ImportDroppedFilesDelegate
31 |
32 | // MARK: - Initialization
33 |
34 | init(isBenchmark: Bool = false) {
35 | // Setup Metal renderer
36 | guard let device = MTLCreateSystemDefaultDevice() else {
37 | fatalError("Unable to create default Metal Device")
38 | }
39 | self.renderer = ProteinRenderer(device: device, isBenchmark: isBenchmark)
40 |
41 | // Setup drop delegate
42 | self.dropHandler = ImportDroppedFilesDelegate()
43 |
44 | // Pass reference to ProteinViewModel to delegates and datasources
45 | self.dropHandler.proteinViewModel = self
46 | }
47 |
48 | // MARK: - Public functions
49 |
50 | func removeAllFiles() async {
51 | await self.dataSource?.removeAllFilesFromDatasource()
52 | // FIXME: Status changes, self.statusViewModel?.removeAllWarnings()
53 | // FIXME: Status changes, self.statusViewModel?.removeAllErrors()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Data/ProteinVisualizationOption.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinVisualizationOption.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 31/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ProteinVisualizationOption: PickableEnum {
11 | case solidSpheres
12 | case ballAndStick
13 |
14 | var displayName: String {
15 | switch self {
16 | case .solidSpheres:
17 | return NSLocalizedString("Space-filling spheres", comment: "")
18 | case .ballAndStick:
19 | return NSLocalizedString("Ball and stick (beta)", comment: "")
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Data/SolidSpheresRadiusOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SolidSpheresRadiusOptions.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/3/22.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SolidSpheresRadiusOptions: PickableEnum {
11 | case vanDerWaals
12 | case fixed
13 |
14 | var displayName: String {
15 | switch self {
16 | case .vanDerWaals:
17 | return NSLocalizedString("Van Der Waals", comment: "")
18 | case .fixed:
19 | return NSLocalizedString("Fixed", comment: "")
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ImportProteins/ImportRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImportRowView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 23/1/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ImportRowView: View {
11 | var title: String
12 | var imageName: String
13 | var action: ProteinImportView.ImportAction
14 | var parent: ProteinImportView
15 |
16 | var body: some View {
17 | Button(
18 | action: {
19 | parent.launchImportAction(action: action)
20 | },
21 | label: {
22 | HStack(spacing: 10) {
23 | Image(systemName: imageName)
24 | .frame(width: 32, height: 32, alignment: .center)
25 | Text(title)
26 | .frame(width: 200, alignment: .leading)
27 | }
28 | .font(.headline)
29 | .foregroundColor(.white)
30 | }
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ImportProteins/ProteinImportViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinImportViewModel.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 22/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | class ProteinImportViewModel {
11 | // TO-DO
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ImportProteins/ProteinRCSBViews/RCSBDetailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RCSBDetail.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 13/6/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RCSBDetailView: View {
11 | var body: some View {
12 | ZStack(alignment: .bottom) {
13 |
14 | VStack(alignment: .leading) {
15 | Text("2OGM")
16 | .font(.largeTitle)
17 | .bold()
18 | ZStack {
19 | RoundedRectangle(cornerRadius: 24)
20 | .stroke(Color(uiColor: .separator),
21 | style: StrokeStyle(lineWidth: 2))
22 | .opacity(0.2)
23 | }
24 | .aspectRatio(1.0, contentMode: .fit)
25 | Spacer()
26 | }
27 | .frame(maxWidth: .infinity, alignment: .leading)
28 | .padding()
29 |
30 | VStack(spacing: .zero) {
31 | Divider()
32 | Button(action: {
33 | // TO-DO
34 | }, label: {
35 | HStack {
36 | if #available(iOS 16.0, *) {
37 | Image(systemName: "arrow.down.doc")
38 | // FIXME: .bold()
39 | }
40 | Text(NSLocalizedString("Download", comment: ""))
41 | .bold()
42 | }
43 | .padding(8)
44 | .frame(maxWidth: .infinity)
45 | })
46 | .buttonStyle(.borderedProminent)
47 | .padding()
48 | }
49 | .background(.regularMaterial)
50 | }
51 | }
52 | }
53 |
54 | struct RCSBDetail_Previews: PreviewProvider {
55 | static var previews: some View {
56 | RCSBDetailView()
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ImportProteins/ProteinRCSBViews/RCSBEmptyImportView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RCSBEmptyView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 1/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RCSBEmptyImportView: View {
11 |
12 | @Binding var rcsbShowSheet: Bool
13 |
14 | var body: some View {
15 | VStack(spacing: 4) {
16 | Image(systemName: "text.magnifyingglass")
17 | .resizable()
18 | .frame(width: 128, height: 128)
19 | .aspectRatio(contentMode: .fit)
20 | .foregroundColor(Color(uiColor: .tertiaryLabel))
21 | Spacer()
22 | .frame(height: 16)
23 | Text("Search protein structures by entering their RCSB ID.")
24 | .multilineTextAlignment(.center)
25 | .foregroundColor(Color(uiColor: .tertiaryLabel))
26 | Spacer()
27 | .frame(height: 16)
28 | Text("Don't know what to search for?")
29 | .multilineTextAlignment(.center)
30 | .foregroundColor(Color(uiColor: .tertiaryLabel))
31 | NavigationLink(destination: RCSBSuggestionsView(rcsbShowSheet: $rcsbShowSheet)) {
32 | Text("Here are some suggestions...")
33 | .multilineTextAlignment(.center)
34 | }
35 | }
36 | .frame(maxWidth: .infinity, maxHeight: .infinity)
37 | }
38 | }
39 |
40 | struct RCSBEmptyImportView_Previews: PreviewProvider {
41 | static var previews: some View {
42 | RCSBEmptyImportView(rcsbShowSheet: .constant(true))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ImportProteins/ProteinRCSBViews/RCSBSuggestionsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RCSBSuggestionsView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 1/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RCSBSuggestionHeaderView: View {
11 |
12 | let title: String?
13 | let description: String?
14 | var body: some View {
15 | VStack(alignment: .leading, spacing: 2) {
16 | Text(title ?? "")
17 | .bold()
18 | .frame(alignment: .leading)
19 | Text(description ?? "")
20 | .textCase(nil)
21 | Spacer()
22 | .frame(height: 4)
23 | }
24 | }
25 | }
26 |
27 | // swiftlint:disable all
28 | struct RCSBSuggestionsView: View {
29 |
30 | @Binding var rcsbShowSheet: Bool
31 | @StateObject var suggestionViewModel = RCSBSuggestionViewModel()
32 |
33 | var body: some View {
34 | List {
35 | if let sections = suggestionViewModel.suggestionData?.sections {
36 | ForEach(sections, id: \.self) { section in
37 | Section(content: {
38 | ForEach(section.rowData, id: \.self) { rcsbRow in
39 | let pdbInfo = PDBInfo(suggestion: rcsbRow)
40 | RCSBRowView(
41 | pdbInfo: pdbInfo,
42 | searchTerm: nil,
43 | rcsbShowSheet: $rcsbShowSheet
44 | )
45 | }
46 | }, header: {
47 | RCSBSuggestionHeaderView(
48 | title: NSLocalizedString(section.sectionTitle, comment: ""),
49 | description: NSLocalizedString(section.sectionDescription, comment: "")
50 | )
51 | })
52 | }
53 | }
54 | }
55 | .navigationTitle(NSLocalizedString("Suggestions", comment: ""))
56 | .navigationBarTitleDisplayMode(.inline)
57 | }
58 | }
59 |
60 | struct RCSBSuggestions_Previews: PreviewProvider {
61 | static var previews: some View {
62 | RCSBSuggestionsView(rcsbShowSheet: .constant(true))
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/PhotoMode/PhotoModeConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhotoModeConfig.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 20/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct PhotoModeConfig: Sendable {
11 | var finalTextureSize: Int = 2048
12 | var shadowTextureSize: Int = 4096
13 | var clearBackground: Bool = true
14 | }
15 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/PhotoMode/PhotoModeFooter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhotoModeFooter.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 19/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PhotoModeFooter: View {
11 |
12 | @EnvironmentObject var proteinViewModel: ProteinViewModel
13 | @Environment(ProteinRenderer.self) var renderer: ProteinRenderer
14 | @Environment(PhotoModeViewModel.self) var photoModeViewModel: PhotoModeViewModel
15 | @Environment(ShutterAnimator.self) var shutterAnimator: ShutterAnimator
16 |
17 | var body: some View {
18 | VStack(spacing: 0) {
19 | Divider()
20 | Button(action: {
21 | Task {
22 | await photoModeViewModel.shutterAnimator.openShutter()
23 | try? await renderer.mutableState.drawHighQualityFrame(
24 | renderer: renderer,
25 | size: CGSize(width: 2048, height: 2048),
26 | photoConfig: photoModeViewModel.photoConfig,
27 | photoModeViewModel: photoModeViewModel
28 | )
29 | }
30 | }, label: {
31 | HStack {
32 | Image(systemName: "camera")
33 | .resizable()
34 | .aspectRatio(contentMode: .fit)
35 | .frame(width: 24, height: 24)
36 | Text(NSLocalizedString("Take photo", comment: ""))
37 | .bold()
38 | }
39 | .padding(4)
40 | .frame(maxWidth: .infinity)
41 | })
42 | .disabled(photoModeViewModel.shutterAnimator.shutterAnimationRunning)
43 | .buttonStyle(BorderedProminentButtonStyle())
44 | .padding()
45 | }
46 | .background(.regularMaterial)
47 | .edgesIgnoringSafeArea(.bottom)
48 | }
49 | }
50 |
51 | struct PhotoModeFooter_Previews: PreviewProvider {
52 | static var previews: some View {
53 | PhotoModeFooter()
54 | .environment(ShutterAnimator())
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/PhotoMode/PhotoModeUnsupportedView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhotoModeUnsupportedView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 13/3/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PhotoModeUnsupportedView: View {
11 |
12 | @Environment(\.dismiss) var dismiss
13 |
14 | var body: some View {
15 | VStack(alignment: .leading, spacing: 2) {
16 | Text(NSLocalizedString(":(", comment: ""))
17 | .font(.largeTitle)
18 | .bold()
19 | .multilineTextAlignment(.center)
20 | Spacer()
21 | .frame(height: 24)
22 | Text(NSLocalizedString("Photo Mode is not supported on this device.", comment: ""))
23 | .font(.headline)
24 | .bold()
25 | .multilineTextAlignment(.center)
26 | Text(NSLocalizedString("Supported devices include iOS devices with A11 or later and Apple Silicon Macs.", comment: ""))
27 | .font(.body)
28 | .foregroundColor(.gray)
29 | Spacer()
30 | Button(action: {
31 | dismiss()
32 | }, label: {
33 | Text(NSLocalizedString("Close", comment: ""))
34 | .bold()
35 | .frame(maxWidth: .infinity)
36 | })
37 | .buttonStyle(.borderedProminent)
38 | }
39 | .padding()
40 | }
41 | }
42 |
43 | struct PhotoModeUnsupportedView_Previews: PreviewProvider {
44 | static var previews: some View {
45 | PhotoModeUnsupportedView()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/PhotoMode/PhotoModeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhotoModeView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 19/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PhotoModeView: View {
11 |
12 | @Environment(\.dismiss) var dismiss
13 | @State var photoModeViewModel = PhotoModeViewModel()
14 |
15 | var body: some View {
16 | NavigationView {
17 | ZStack {
18 | PhotoModeContent()
19 | .edgesIgnoringSafeArea(.bottom)
20 | .environment(photoModeViewModel.shutterAnimator)
21 | .environment(photoModeViewModel)
22 |
23 | VStack {
24 | Spacer()
25 | PhotoModeFooter()
26 | .environment(photoModeViewModel.shutterAnimator)
27 | .environment(photoModeViewModel)
28 | }
29 | }
30 | .navigationTitle(NSLocalizedString("Photo Mode", comment: ""))
31 | .navigationBarItems(leading:
32 | Button(
33 | action: {
34 | dismiss()
35 | },
36 | label: {
37 | Text(NSLocalizedString("Cancel", comment: ""))
38 | }
39 | )
40 | .disabled(photoModeViewModel.shutterAnimator.shutterAnimationRunning)
41 | )
42 | }
43 | }
44 | }
45 |
46 | struct PhotoModeView_Previews: PreviewProvider {
47 | static var previews: some View {
48 | PhotoModeView()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/PhotoMode/PhotoModeViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhotoModeViewModel.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 20/12/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | @Observable class PhotoModeViewModel {
12 |
13 | // MARK: - Config
14 | var photoConfig = PhotoModeConfig()
15 | var shutterAnimator = ShutterAnimator()
16 |
17 | // MARK: - Pickers
18 | var finalTextureSizeOption: Int = PhotoModeTextureOptions.high {
19 | didSet {
20 | switch finalTextureSizeOption {
21 | case PhotoModeTextureOptions.normal:
22 | photoConfig.finalTextureSize = 1024
23 | case PhotoModeTextureOptions.high:
24 | photoConfig.finalTextureSize = 2048
25 | case PhotoModeTextureOptions.highest:
26 | photoConfig.finalTextureSize = 4096
27 | default:
28 | photoConfig.finalTextureSize = 2048
29 | }
30 | }
31 | }
32 |
33 | var shadowResolution: Int = PhotoModeShadowOptions.high {
34 | didSet {
35 | switch finalTextureSizeOption {
36 | case PhotoModeTextureOptions.normal:
37 | photoConfig.shadowTextureSize = 2048
38 | case PhotoModeTextureOptions.high:
39 | photoConfig.shadowTextureSize = 4096
40 | case PhotoModeTextureOptions.highest:
41 | photoConfig.shadowTextureSize = 8192
42 | default:
43 | photoConfig.shadowTextureSize = 4096
44 | }
45 | }
46 | }
47 | }
48 |
49 | struct PhotoModeTextureOptions {
50 | static let normal = 0
51 | static let high = 1
52 | static let highest = 2
53 | }
54 |
55 | struct PhotoModeShadowOptions {
56 | static let normal = 0
57 | static let high = 1
58 | static let highest = 2
59 | }
60 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ProeteinSequenceView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProeteinSequenceView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 9/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ProteinSequenceView: View {
11 | var body: some View {
12 | ZStack {
13 | Color.red
14 | HStack {
15 |
16 | }
17 | }
18 | .frame(height: 36)
19 | .cornerRadius(8)
20 | .overlay(
21 | RoundedRectangle(cornerRadius: 8)
22 | .stroke(Color(UIColor.opaqueSeparator), lineWidth: 3)
23 | )
24 | }
25 | }
26 |
27 | struct ProeteinSequenceView_Previews: PreviewProvider {
28 | static var previews: some View {
29 | ProteinSequenceView()
30 | .previewDevice("iPhone SE (2nd generation)")
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ProteinMetal/ProteinMetalView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinMetalView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 30/5/21.
6 | //
7 |
8 | import SwiftUI
9 | import Metal
10 | import MetalKit
11 |
12 | struct ProteinMetalView: UIViewControllerRepresentable {
13 |
14 | typealias UIViewControllerType = ProteinMetalViewController
15 |
16 | let proteinViewModel: ProteinViewModel
17 | let selectionModel: SelectionModel
18 |
19 | func makeUIViewController(context: Context) -> ProteinMetalViewController {
20 | return ProteinMetalViewController(
21 | proteinViewModel: self.proteinViewModel,
22 | selectionModel: self.selectionModel
23 | )
24 | }
25 |
26 | func updateUIViewController(_ uiViewController: ProteinMetalViewController, context: Context) {
27 | // TO-DO? Updateable ViewController
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/ProteinSequenceView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProeteinSequenceView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 9/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ProteinSequenceView: View {
11 | var body: some View {
12 | ZStack {
13 | /*
14 | VisualEffectView(effect: UIBlurEffect(style: .systemThinMaterialDark))
15 | ScrollView(.horizontal) {
16 | HStack {
17 | Text("MDSKGSSQKGSRLLLLLVVSNLLLCQGVVSTPVCPNGPGNCQVSLRDLFDRAVMVSHYIHDLSS")
18 | .foregroundColor(Color.white)
19 | .padding(.horizontal, 36+8)
20 | }
21 | }
22 | HStack {
23 | ZStack {
24 | VisualEffectView(effect: UIBlurEffect(style: .regular))
25 | .frame(width: 36)
26 | Text("5'")
27 | .foregroundColor(.white)
28 | }
29 | Spacer()
30 | ZStack {
31 | VisualEffectView(effect: UIBlurEffect(style: .regular))
32 | .frame(width: 36)
33 | Text("3'")
34 | .foregroundColor(.white)
35 | }
36 | }
37 | */
38 | }
39 | .frame(height: 36)
40 | .cornerRadius(8)
41 | .overlay(
42 | RoundedRectangle(cornerRadius: 8)
43 | .stroke(Color(UIColor.opaqueSeparator), lineWidth: 3)
44 | )
45 | }
46 | }
47 |
48 | struct ProteinSequenceView_Previews: PreviewProvider {
49 | static var previews: some View {
50 | ProteinSequenceView()
51 | .previewDevice("iPhone SE (2nd generation)")
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileAtomElementPopover/CompositionItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CompositionItem.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 22/1/23.
6 | //
7 |
8 | import Charts
9 | import Foundation
10 | import SwiftUI
11 |
12 | struct CompositionItem: Hashable, Identifiable {
13 | let id = UUID()
14 | let name: String
15 | let color: Color
16 | let count: Int
17 | let fraction: Double
18 | }
19 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileAtomElementPopover/FileCompositionChartView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileCompositionChartView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 2/7/23.
6 | //
7 |
8 | import Charts
9 | import SwiftUI
10 |
11 | struct FileCompositionChartView: View {
12 |
13 | let segments: [CompositionItem]
14 | @Binding var selectedSegmentID: CompositionItem.ID?
15 |
16 | @State var selectedValue: Int?
17 |
18 | var body: some View {
19 | Chart(segments, id: \.id) { segment in
20 | SectorMark(
21 | angle: .value("Fraction", segment.count),
22 | innerRadius: .ratio(0.618),
23 | angularInset: 1.5
24 | )
25 | .cornerRadius(4)
26 | .foregroundStyle(segment.color)
27 | .opacity(segmentOpacity(segment: segment))
28 | }
29 | .chartAngleSelection(value: $selectedValue)
30 | .onChange(of: selectedValue) { _, newValue in
31 | if let newValue {
32 | withAnimation {
33 | setSelectedSegment(from: newValue)
34 | }
35 | }
36 | }
37 | }
38 |
39 | @MainActor private func setSelectedSegment(from value: Int) {
40 | var currentCount: Int = 0
41 | for segment in segments {
42 | let startValue = currentCount
43 | let endValue = currentCount + segment.count
44 | if startValue <= value && endValue >= value {
45 | let hapticFeedback = UIImpactFeedbackGenerator(style: .light)
46 | hapticFeedback.prepare()
47 | hapticFeedback.impactOccurred()
48 | selectedSegmentID = segment.id
49 | return
50 | }
51 | currentCount += segment.count
52 | }
53 | selectedSegmentID = nil
54 | }
55 |
56 | private func segmentOpacity(segment: CompositionItem) -> Double {
57 | guard let selectedSegmentID else { return 1.0 }
58 | if segment.id == selectedSegmentID {
59 | return 1.0
60 | } else {
61 | return 0.3
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileSource/FileSourceRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileSourceRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 14/11/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FileSourceRow: View {
11 |
12 | var lineNumber: Int
13 | var line: String
14 | var hasWarning: Bool
15 |
16 | var body: some View {
17 | ZStack {
18 | if hasWarning {
19 | Color.yellow
20 | .opacity(0.3)
21 | }
22 | HStack {
23 | Text(String(lineNumber))
24 | .frame(width: 36, alignment: .trailing)
25 | .font(.system(size: 9.5, design: .monospaced))
26 | .foregroundColor(.white)
27 | Rectangle()
28 | .padding(.vertical, 4)
29 | .frame(width: 1)
30 | .foregroundColor(Color(UIColor.opaqueSeparator))
31 | .opacity(0.5)
32 | Text(line)
33 | .font(.system(size: 9.5, design: .monospaced))
34 | .foregroundColor(.white)
35 | Spacer()
36 | }
37 | .padding(.trailing, 12)
38 | }
39 | .frame(maxWidth: .infinity)
40 | .listRowBackground(Color.black)
41 | .listRowInsets(EdgeInsets())
42 | }
43 | }
44 |
45 | struct FileSourceRow_Previews: PreviewProvider {
46 | static var previews: some View {
47 | List {
48 | FileSourceRow(lineNumber: 3352,
49 | line: "ATOM 39812 OP2 C 01930 94.465 121.850 130.597 1.00113.26 O",
50 | hasWarning: true)
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileSource/FileSourceViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileSourceViewModel.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 14/11/21.
6 | //
7 |
8 | import BioViewerFoundation
9 | import Foundation
10 |
11 | @MainActor @Observable class FileSourceViewModel {
12 |
13 | /// Batch size for line loading
14 | let batchSize: Int = 200
15 | /// Distance to the end of the batch to prefetch next batch
16 | let prefetchDistance: Int = 50
17 | /// Number of batches already loaded
18 | var batchCount: Int = 0
19 |
20 | /// All the lines in the source
21 | var fileInfo: ProteinFileInfo?
22 | /// Lines currently loaded in the view
23 | var loadedLines: [String]?
24 |
25 | // MARK: - Initialization
26 | init(fileInfo: ProteinFileInfo?) {
27 | self.fileInfo = fileInfo
28 | self.loadedLines = []
29 |
30 | guard let fileInfo = fileInfo else {
31 | return
32 | }
33 | guard let sourceLines = fileInfo.sourceLines else {
34 | return
35 | }
36 |
37 | loadedLines?.append(contentsOf: sourceLines[0.. Bool {
44 | return index == batchCount * batchSize - 1 - prefetchDistance
45 | }
46 |
47 | func loadMore() {
48 | guard let fileInfo = fileInfo else {
49 | return
50 | }
51 | guard let sourceLines = fileInfo.sourceLines else {
52 | return
53 | }
54 | let startRange = batchCount * batchSize
55 | let endRange = startRange + batchSize
56 | // FIXME: Sometimes this crashes with Index out of range
57 | loadedLines?.append(contentsOf: sourceLines[startRange.. Bool {
62 | guard let fileInfo = fileInfo else {
63 | return false
64 | }
65 | if fileInfo.warningIndices.contains(index + 1) {
66 | return true
67 | }
68 | return false
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/InfoSegmentedCapsule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoAtomsCapsule.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 12/3/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InfoCapsuleSegment: Hashable {
11 | let id = UUID()
12 | let fraction: Double
13 | let color: Color
14 | }
15 |
16 | struct InfoSegmentedCapsule: View {
17 |
18 | let segments: [InfoCapsuleSegment]
19 |
20 | var body: some View {
21 | GeometryReader { geometry in
22 | HStack(spacing: .zero) {
23 | ForEach(segments, id: \.self) { segment in
24 | segment.color
25 | .frame(width: segment.fraction * geometry.size.width)
26 | }
27 | }
28 | }
29 | .frame(height: 6)
30 | .mask(Capsule()
31 | .frame(height: 6)
32 | )
33 | }
34 | }
35 |
36 | /*
37 | struct InfoAtomsCapsule_Previews: PreviewProvider {
38 | static var previews: some View {
39 | InfoAtomsCapsule()
40 | .environmentObject(ProteinViewModel())
41 | }
42 | }
43 | */
44 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/WorkspaceHelp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WorkspaceHelp.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WorkspaceHelp: View {
11 |
12 | @Environment(\.dismiss) var dismiss
13 |
14 | var body: some View {
15 | NavigationView {
16 | VStack {
17 | Text(NSLocalizedString("What are BioViewer workspaces?", comment: ""))
18 | .font(.title)
19 | .bold()
20 | .frame(maxWidth: .infinity, alignment: .leading)
21 | Text(".bioviewer")
22 | .font(.largeTitle)
23 | .bold()
24 | .foregroundColor(.accentColor)
25 | .padding(12)
26 | Text(NSLocalizedString("bioViewer_workspace_description", comment: ""))
27 | .frame(maxWidth: .infinity, alignment: .leading)
28 | Spacer()
29 | }
30 | .padding(.horizontal)
31 | .navigationTitle(NSLocalizedString("Help", comment: ""))
32 | .navigationBarTitleDisplayMode(.inline)
33 | .navigationBarItems(leading: Button(NSLocalizedString("Close", comment: "")) {
34 | dismiss()
35 | })
36 | }
37 | }
38 | }
39 |
40 | struct WorkspaceHelp_Previews: PreviewProvider {
41 | static var previews: some View {
42 | WorkspaceHelp()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/WorkspaceRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WorkspaceRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WorkspaceRow: View {
11 |
12 | @State var showWorkspaceHelp: Bool = false
13 | @EnvironmentObject var proteinViewModel: ProteinViewModel
14 |
15 | private enum Constants {
16 | #if targetEnvironment(macCatalyst)
17 | static let iconSize: CGFloat = 16
18 | #else
19 | static let iconSize: CGFloat = 24
20 | #endif
21 | }
22 |
23 | var body: some View {
24 | HStack {
25 |
26 | Button(action: {
27 | WorkspaceExporter.createWorkspace(proteinViewModel: proteinViewModel)
28 | }, label: {
29 | Image(systemName: "square.and.arrow.down")
30 | .resizable()
31 | .aspectRatio(contentMode: .fit)
32 | .frame(width: Constants.iconSize, height: Constants.iconSize)
33 | })
34 | .foregroundColor(.accentColor)
35 | .buttonStyle(PlainButtonStyle())
36 |
37 | Text(NSLocalizedString("Save as BioViewer workspace...", comment: ""))
38 |
39 | Spacer()
40 |
41 | Button(action: {
42 | showWorkspaceHelp.toggle()
43 | }, label: {
44 | Image(systemName: "questionmark.circle")
45 | })
46 | .foregroundColor(.accentColor)
47 | .buttonStyle(PlainButtonStyle())
48 | .sheet(isPresented: $showWorkspaceHelp) {
49 | WorkspaceHelp()
50 | }
51 | }
52 | .padding(.vertical, 8)
53 | }
54 | }
55 |
56 | // MARK: - Previews
57 | struct WorkspaceRow_Previews: PreviewProvider {
58 | static var previews: some View {
59 | List {
60 | WorkspaceRow()
61 | }
62 | .frame(width: 300)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/FunctionsSegmentProtein.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FunctionsSegmentProtein.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 3/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FunctionsSegmentProtein: View {
11 |
12 | @EnvironmentObject var proteinDataSource: ProteinDataSource
13 |
14 | var body: some View {
15 | List {
16 | // First section hast 64pt padding to account for the
17 | // space under the segmented control.
18 | Section(header: Text(NSLocalizedString("Protein properties", comment: ""))
19 | .padding(.top, 52)
20 | .padding(.bottom, 4),
21 | content: {
22 | ComputedPropertyRow(propertyName: "volume",
23 | units: "Å^{3}",
24 | value: .constant(8842.4),
25 | errorInterval: .constant(26.1))
26 | })
27 | .disabled(proteinDataSource.proteinCount == 0)
28 |
29 | }
30 | .environment(\.defaultMinListHeaderHeight, 0)
31 | .listStyle(GroupedListStyle())
32 | }
33 | }
34 |
35 | struct FunctionsSegmentProtein_Previews: PreviewProvider {
36 | static var previews: some View {
37 | FunctionsSegmentProtein()
38 | .environmentObject(ProteinViewModel())
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/SettingsSegmentProtein.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsSegmentProtein.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 23/11/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SettingsSegmentProtein: View {
11 |
12 | @EnvironmentObject var proteinViewModel: ProteinViewModel
13 |
14 | var body: some View {
15 | List {
16 | // First section hast 64pt padding to account for the
17 | // space under the segmented control.
18 | Section(header: Text(NSLocalizedString("Settings", comment: ""))
19 | .padding(.top, 52)
20 | .padding(.bottom, 4),
21 | content: {
22 | // TO-DO:
23 | SwitchRow(title: "Show FPS", toggledVariable: .constant(false))
24 | SwitchRow(title: "Average framerate", toggledVariable: .constant(false))
25 | SwitchRow(title: "Prefer RCSB file info", toggledVariable: .constant(false))
26 | })
27 | }
28 | .environment(\.defaultMinListHeaderHeight, 0)
29 | .listStyle(GroupedListStyle())
30 | }
31 | }
32 |
33 | struct SettingsSegmentProtein_Previews: PreviewProvider {
34 | static var previews: some View {
35 | SettingsSegmentProtein()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Sidebar/Segments/TrajectorySegmentProtein.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrajectorySegmentProtein.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 18/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TrajectorySegmentProtein: View {
11 |
12 | var body: some View {
13 | List {
14 | // First section hast 64pt padding to account for the
15 | // space under the segmented control.
16 | Section(header: Text(NSLocalizedString("Configurations", comment: ""))
17 | .padding(.top, 52)
18 | .padding(.bottom, 4),
19 | content: {
20 | SliderRow(title: NSLocalizedString("Frames per configuration", comment: ""),
21 | value: .constant(23),
22 | minValue: 1,
23 | maxValue: 100)
24 | // InputWithButtonRow()
25 | })
26 | }
27 | .environment(\.defaultMinListHeaderHeight, 0)
28 | .listStyle(GroupedListStyle())
29 | }
30 | }
31 |
32 | struct TrajectorySegmentProtein_Previews: PreviewProvider {
33 | static var previews: some View {
34 | TrajectorySegmentProtein()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Top toolbar/CameraControlToolbar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CameraControlToolbar.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 19/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CameraControlToolbar: View {
11 |
12 | @Environment(ToolbarConfig.self) var config: ToolbarConfig
13 |
14 | var body: some View {
15 | @Bindable var config = config
16 | HStack {
17 | Picker("Rotation mode", selection: $config.selectedTool) {
18 | Image(systemName: "rotate.3d")
19 | .resizable()
20 | .aspectRatio(contentMode: .fit)
21 | .frame(width: TopToolbar.Constants.buttonSize, height: TopToolbar.Constants.buttonSize)
22 | .foregroundColor(.accentColor).tag(0)
23 | Image(systemName: "move.3d")
24 | .resizable()
25 | .aspectRatio(contentMode: .fit)
26 | .frame(width: TopToolbar.Constants.buttonSize, height: TopToolbar.Constants.buttonSize)
27 | .foregroundColor(.accentColor).tag(1)
28 | }
29 | .pickerStyle(SegmentedPickerStyle())
30 | .foregroundColor(.accentColor)
31 | .frame(width: 4 * TopToolbar.Constants.buttonSize)
32 | .disabled(config.autorotating)
33 | .contextMenu {
34 | Button(role: .destructive) {
35 | config.resetCamera()
36 | } label: {
37 | Label("Reset camera", systemImage: "arrow.uturn.backward")
38 | }
39 | }
40 | Button(
41 | action: {
42 | config.autorotating.toggle()
43 | }, label: {
44 | Image(systemName: config.autorotating
45 | ? "arrow.triangle.2.circlepath.circle.fill"
46 | : "arrow.triangle.2.circlepath.circle"
47 | )
48 | .foregroundColor(.accentColor)
49 | }
50 | )
51 | }
52 | }
53 | }
54 |
55 | struct CameraControlToolbar_Previews: PreviewProvider {
56 | static var previews: some View {
57 | CameraControlToolbar()
58 | .environment(ToolbarConfig())
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/BioViewer/Views/ProteinViews/Top toolbar/ToolbarConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToolbarConfig.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 19/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum CameraControlTool {
11 | static let rotate: Int = 0
12 | static let move: Int = 1
13 | }
14 |
15 | @MainActor @Observable class ToolbarConfig {
16 |
17 | weak var proteinViewModel: ProteinViewModel?
18 |
19 | // MARK: - Properties
20 |
21 | var selectedTool: Int = CameraControlTool.rotate
22 |
23 | var autorotating: Bool = false {
24 | didSet {
25 | Task {
26 | await proteinViewModel?.renderer.mutableState.setAutorotating(autorotating)
27 | }
28 | }
29 | }
30 |
31 | // MARK: - Actions
32 |
33 | func resetCamera() {
34 | Task {
35 | await proteinViewModel?.renderer.mutableState.resetCamera()
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/BioViewer/Views/SequenceViews/Rows/SequenceRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SequenceRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 8/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SequenceRow: View {
11 |
12 | @State var sequence: [Character] = Array("MDSKGSSQKGSRLLLLLVVSNLLLCQGVVSTPVCPNGPGNCQVSLRDLFDRAVMVSHYIHDLSSEMFNEFDKRYAQGKGFITMALNSCHTSSLPTPEDKEQAQQTHHEVLMSLIL")
13 |
14 | var body: some View {
15 | LazyHStack(alignment: .top, spacing: 8) {
16 | ForEach(0.. Void)
13 |
14 | struct Constants {
15 | #if targetEnvironment(macCatalyst)
16 | static let buttonSize: CGFloat = 18
17 | #else
18 | static let buttonSize: CGFloat = 24
19 | #endif
20 | }
21 |
22 | var body: some View {
23 | Button(
24 | action: {
25 | closeAction()
26 | },
27 | label: {
28 | ZStack {
29 | Circle()
30 | .fill(.white)
31 | .frame(
32 | width: Constants.buttonSize / 1.5,
33 | height: Constants.buttonSize / 1.5
34 | )
35 | Image(systemName: "xmark.circle.fill")
36 | .resizable()
37 | .foregroundStyle(.red)
38 | }
39 | .frame(width: Constants.buttonSize, height: Constants.buttonSize)
40 | }
41 | )
42 | #if targetEnvironment(macCatalyst)
43 | .buttonStyle(.plain)
44 | #endif
45 | .offset(x: -Constants.buttonSize / 2.5, y: -Constants.buttonSize / 2.5)
46 | }
47 | }
48 |
49 | #Preview {
50 | ZStack {
51 | Color.black
52 | BVDismissActionButton(closeAction: {})
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/BioViewerProgress/BVProgressComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BVProgressComponent.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/11/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct BVProgressComponent: View {
11 |
12 | let title: String
13 | let progress: Double?
14 | let error: Error?
15 | let closeAction: (() -> Void)?
16 |
17 | var body: some View {
18 | ZStack(alignment: .topLeading) {
19 | VStack(spacing: .zero) {
20 |
21 | Text(title)
22 | .font(.caption)
23 | .bold()
24 | .padding(.top, 8)
25 |
26 | if let error {
27 | BVProgressFailedView()
28 | .frame(width: 96, height: 96)
29 | Text(error.localizedDescription)
30 | .frame(width: 96)
31 | .font(.caption)
32 | } else {
33 | BVProgressView(size: 96)
34 | .frame(width: 96, height: 96)
35 | if let progress {
36 | ProgressView(value: progress)
37 | #if targetEnvironment(macCatalyst)
38 | .padding(.horizontal, 12)
39 | #else
40 | .padding(.horizontal, 12)
41 | .padding(.bottom, 12)
42 | #endif
43 | }
44 | }
45 | }
46 | .padding(.bottom, 8)
47 | .frame(width: 128)
48 | .background(.thinMaterial)
49 | .clipShape(RoundedRectangle(cornerRadius: 12))
50 |
51 | if let closeAction {
52 | BVDismissActionButton(closeAction: closeAction)
53 | }
54 | }
55 | }
56 | }
57 |
58 | #Preview {
59 | ZStack {
60 | Color.black
61 | BVProgressComponent(title: "Title", progress: 0.5, error: nil, closeAction: nil)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/BioViewerProgress/BVProgressFailedView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BVProgressFailedView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 11/11/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct BVProgressFailedView: View {
11 | var body: some View {
12 | Image(systemName: "exclamationmark.octagon")
13 | .resizable()
14 | .padding(16)
15 | .aspectRatio(contentMode: .fit)
16 | }
17 | }
18 |
19 | #Preview {
20 | BVProgressFailedView()
21 | }
22 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/CoffeeViews/CoffeeRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoffeeRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 24/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CoffeeRow: View {
11 | var body: some View {
12 | HStack {
13 | Spacer()
14 | VStack {
15 | Image("PlaceholderCoffee")
16 | .resizable()
17 | .aspectRatio(contentMode: .fit)
18 | .frame(width: 100, height: 100, alignment: .center)
19 | Text("Bought on 12/07/2021")
20 | .frame(width: 100, height: 32)
21 | .font(.caption)
22 | .foregroundColor(Color(UIColor.secondaryLabel))
23 | }
24 | Spacer()
25 | VStack {
26 | Image("PlaceholderCoffee")
27 | .resizable()
28 | .aspectRatio(contentMode: .fit)
29 | .frame(width: 100, height: 100, alignment: .center)
30 | Text("Bought on 12/07/2021")
31 | .frame(width: 100, height: 32)
32 | .font(.caption)
33 | .foregroundColor(Color(UIColor.secondaryLabel))
34 | }
35 | Spacer()
36 | VStack {
37 | Image("PlaceholderCoffee")
38 | .resizable()
39 | .aspectRatio(contentMode: .fit)
40 | .frame(width: 100, height: 100, alignment: .center)
41 | Text("Bought on 12/07/2021")
42 | .frame(width: 100, height: 32)
43 | .font(.caption)
44 | .foregroundColor(Color(UIColor.secondaryLabel))
45 | }
46 | Spacer()
47 | }
48 | .padding(8)
49 | }
50 | }
51 |
52 | struct CoffeeRow_Previews: PreviewProvider {
53 | static var previews: some View {
54 | CoffeeRow()
55 | .previewLayout(.sizeThatFits)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/CoffeeViews/CoffeeTipRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipRowView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 23/5/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CoffeeTipRow: View {
11 |
12 | var text: String
13 | var price: String
14 |
15 | var body: some View {
16 | HStack {
17 | Text(text)
18 | Spacer()
19 | Button(price, action: {
20 | // TO-DO: Handle in-app purchase
21 | })
22 | .foregroundColor(.accentColor)
23 | // PlainButtonStyle() makes the list row not selectable,
24 | // as we want.
25 | .buttonStyle(PlainButtonStyle())
26 | }
27 | }
28 | }
29 |
30 | struct TipRowView_Previews: PreviewProvider {
31 | static var previews: some View {
32 | CoffeeTipRow(text: "Small tip", price: "$0.99")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/ColorPalettePopoverRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorPalettePopoverRow.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ColorPalettePopoverRow: View {
11 |
12 | @Binding var selectedOption: Int
13 | let colorPalette: ColorPalette
14 |
15 | let paletteName: String
16 | let optionIndex: Int
17 |
18 | private enum Constants {
19 | #if targetEnvironment(macCatalyst)
20 | static let radioButtonSize: CGFloat = 16
21 | #else
22 | static let radioButtonSize: CGFloat = 20
23 | #endif
24 | }
25 |
26 | var body: some View {
27 | Button(action: {
28 | selectedOption = optionIndex
29 | }, label: {
30 | HStack {
31 | Image(systemName: selectedOption == optionIndex ? "checkmark.circle" : "circle")
32 | .padding(4)
33 | .font(Font.system(size: Constants.radioButtonSize, weight: .medium))
34 | .foregroundColor(.accentColor)
35 | Text(paletteName)
36 | .foregroundColor(.accentColor)
37 | Spacer()
38 | ColorPaletteView(colorPalette: colorPalette)
39 | }
40 | .contentShape(Rectangle())
41 | })
42 | .buttonStyle(PlainButtonStyle())
43 | }
44 | }
45 |
46 | struct ColorPalettePopoverRow_Previews: PreviewProvider {
47 | static var previews: some View {
48 | ColorPalettePopoverRow(selectedOption: .constant(0),
49 | colorPalette: ColorPalette(.default),
50 | paletteName: "TestPalette",
51 | optionIndex: 0)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/ColorPaletteView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorPaletteView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ColorPaletteView: View {
11 |
12 | let colorPalette: ColorPalette
13 |
14 | var body: some View {
15 | VStack(spacing: 0) {
16 | VStack(spacing: 2) {
17 | HStack(spacing: 2) {
18 | colorPalette.color0
19 | colorPalette.color1
20 | colorPalette.color2
21 | }
22 | HStack(spacing: 2) {
23 | colorPalette.color3
24 | colorPalette.color4
25 | colorPalette.color5
26 | }
27 | }
28 | .padding(4)
29 | }
30 | .frame(width: 88, height: 36)
31 | .mask(RoundedRectangle(cornerRadius: 4)
32 | .padding(4))
33 | .background(RoundedRectangle(cornerRadius: 8)
34 | .stroke(Color(uiColor: .separator),
35 | style: StrokeStyle(lineWidth: 1))
36 | .background(RoundedRectangle(cornerRadius: 8)
37 | .fill(Color(uiColor: .tertiarySystemFill))))
38 | }
39 | }
40 |
41 | struct ColorPaletteView_Previews: PreviewProvider {
42 | static var previews: some View {
43 | ColorPaletteView(colorPalette: ColorPalette(.default))
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/CustomLinearProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomLinearProgressView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 30/10/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CustomLinearProgressView: View {
11 |
12 | var value: Float?
13 | var total: Float
14 |
15 | var body: some View {
16 | if let value = value {
17 | GeometryReader { geometry in
18 | ZStack {
19 | Color(uiColor: UIColor.secondarySystemBackground)
20 | HStack {
21 | Color.accentColor
22 | .frame(width: geometry.size.width * CGFloat(value) / CGFloat(total))
23 | Spacer()
24 | }
25 | }
26 | }
27 | .ignoresSafeArea()
28 | .frame(height: 2)
29 | } else {
30 | EmptyView()
31 | }
32 | }
33 | }
34 |
35 | struct MacLinearProgressView_Previews: PreviewProvider {
36 | static var previews: some View {
37 | CustomLinearProgressView(value: 0.2, total: 1.0)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/Debug Views/FPSCounterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FPSCounterView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 15/5/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @MainActor @Observable class FPSCounterViewModel {
11 |
12 | let renderer: ProteinRenderer
13 | var averageFPSString = "-"
14 |
15 | private var displayLink: CADisplayLink?
16 | private var frameTimeArray = [CFTimeInterval]()
17 | private var lastIndex: Int = 0
18 | private var currentIndex: Int = 0
19 | private let maxSavedFrames: Int = 100
20 |
21 | init(renderer: ProteinRenderer) {
22 | self.renderer = renderer
23 | self.displayLink = CADisplayLink(
24 | target: self,
25 | selector: #selector(self.updateFrameTime)
26 | )
27 | self.displayLink?.add(to: .main, forMode: .default)
28 | }
29 |
30 | @objc private func updateFrameTime() {
31 |
32 | // Retrieve last GPU frame time.
33 | let newFrameTime = renderer.lastFrameGPUTime
34 |
35 | // Avoid saving the same frame time several times if the renderer
36 | // is paused.
37 | if frameTimeArray.count < maxSavedFrames {
38 | guard newFrameTime != frameTimeArray.last else { return }
39 | frameTimeArray.append(newFrameTime)
40 | } else {
41 | guard newFrameTime != frameTimeArray[lastIndex] else { return }
42 | frameTimeArray[currentIndex] = newFrameTime
43 | }
44 | lastIndex = currentIndex
45 | currentIndex = (currentIndex + 1) % maxSavedFrames
46 |
47 | // Compute mean of the saved values
48 | let averageFrameTime = frameTimeArray.reduce(0, +) / Double(frameTimeArray.count)
49 | let variance = frameTimeArray.reduce(0, {
50 | $0 + ($1 - averageFrameTime) * ($1 - averageFrameTime)
51 | })
52 | averageFPSString = String(format: "FPS: %.0f ± %.0f",
53 | 1 / averageFrameTime,
54 | 1 / sqrt(averageFrameTime - variance))
55 | }
56 | }
57 |
58 | struct FPSCounterView: View {
59 |
60 | @State var viewModel: FPSCounterViewModel
61 |
62 | var body: some View {
63 | Text(viewModel.averageFPSString)
64 | .foregroundColor(.white)
65 | .background(.black)
66 | .monospacedDigit()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/BioViewer/Views/UI Elements/Debug Views/ResolutionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResolutionView.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 22/5/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @MainActor @Observable class ResolutionViewModel {
11 |
12 | let renderer: ProteinRenderer
13 | var resolution: CGSize?
14 |
15 | private var displayLink: CADisplayLink?
16 |
17 | init(renderer: ProteinRenderer) {
18 | self.renderer = renderer
19 | self.displayLink = CADisplayLink(
20 | target: self,
21 | selector: #selector(self.updateFrameTime)
22 | )
23 | self.displayLink?.add(to: .main, forMode: .default)
24 | }
25 |
26 | @objc private func updateFrameTime() {
27 | // Retrieve last GPU frame time.
28 | if renderer.isBenchmark {
29 | let benchmarkResolution = BenchmarkTextures.benchmarkResolution
30 | resolution = CGSize(width: benchmarkResolution, height: benchmarkResolution)
31 | } else {
32 | resolution = renderer.viewResolution
33 | }
34 | }
35 | }
36 |
37 | @MainActor struct ResolutionView: View {
38 |
39 | @State var viewModel: ResolutionViewModel
40 |
41 | var resolutionString: String {
42 | guard let resolution = viewModel.resolution else {
43 | return "-"
44 | }
45 | return "\(resolution.width)x\(resolution.height)"
46 | }
47 |
48 | var body: some View {
49 | Text(resolutionString)
50 | .foregroundColor(.white)
51 | .background(.black)
52 | .font(.footnote)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/BioViewer/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | BioViewer
4 |
5 | Created by Raúl Montón Pinillos on 1/11/21.
6 |
7 | */
8 |
9 | "chaperone_description" = "Chaperones are proteins that assist protein folding. Some of them create pockets where newly synthesized proteins can fold without being affected by the hydrophilic interactions of aggregating to other proteins in the cytoplasm.";
10 |
11 | "mhc_description" = "Major Histocompatibility Complex (MHC) proteins are a group of surface proteins essential for the adaptive immune system. Their main function is to present antigens (either self or foreign) to the cells involved in the adaptive immune response.";
12 |
13 | "apoptosome_description" = "Apoptosomes are one of the protein groups that mediate programmed cell death (apoptosis).";
14 |
15 | "bioViewer_workspace_description" = "BioViewer workspace files are a type of container file that stores all the files you have on the view (PDBs, CIFs...) plus all the configuration options you have selected (color schemes, visualization type...), and any additional info that was added (description, authors...).
16 |
17 | BioViewer workspace files also contain a thumbnail image of your structures, so you can identify the file at a glance without opening it.";
18 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | BioViewerFoundation-Package.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | BioViewerFoundation.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 4
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | BioViewerFoundation-Package.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 2
11 |
12 | BioViewerFoundation.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 2
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "BioViewerFoundation",
8 | platforms: [.iOS(.v17), .macOS(.v14)],
9 | products: [
10 | // Products define the executables and libraries a package produces, making them visible to other packages.
11 | .library(
12 | name: "BioViewerFoundation",
13 | targets: ["BioViewerFoundation"]
14 | )
15 | ],
16 | targets: [
17 | // Targets are the basic building blocks of a package, defining a module or a test suite.
18 | // Targets can depend on other targets in this package and products from dependencies.
19 | .target(
20 | name: "BioViewerFoundation"),
21 | .testTarget(
22 | name: "BioViewerFoundationTests",
23 | dependencies: ["BioViewerFoundation"]
24 | )
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/BoundingVolumes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BoundingVolumes.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 7/12/21.
6 | //
7 |
8 | import Foundation
9 | import simd
10 |
11 | public struct BoundingSphere: Sendable {
12 | public let center: simd_float3
13 | public let radius: Float
14 | }
15 |
16 | public struct BoundingBox: Sendable {
17 | public let minX: Float
18 | public let maxX: Float
19 | public let minY: Float
20 | public let maxY: Float
21 | public let minZ: Float
22 | public let maxZ: Float
23 | }
24 |
25 | public struct BoundingVolume: Sendable {
26 | public let sphere: BoundingSphere
27 | public let box: BoundingBox
28 |
29 | public static var zero: Self {
30 | return BoundingVolume(
31 | sphere: BoundingSphere(center: .zero, radius: .zero),
32 | box: BoundingBox(minX: .zero, maxX: .zero, minY: .zero, maxY: .zero, minZ: .zero, maxZ: .zero)
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinComposition/ProteinChainComposition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinChainComposition.swift
3 | //
4 | //
5 | // Created by Raúl Montón Pinillos on 23/2/24.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ProteinChainComposition: Sendable {
11 |
12 | public var uniqueChainIDs = [ChainID]()
13 | /// Dictionary containing the number of atoms of each chain.
14 | public var chainIDCounts = [ChainID: Int]()
15 | /// The total count of atoms of all chains.
16 | public var totalCount: Int = 0
17 |
18 | public static func += (lhs: inout ProteinChainComposition, rhs: ProteinChainComposition) {
19 | lhs.chainIDCounts.merge(rhs.chainIDCounts, uniquingKeysWith: { lhsCount, rhsCount in
20 | return lhsCount + rhsCount
21 | })
22 | lhs.uniqueChainIDs = Array(lhs.chainIDCounts.keys)
23 | lhs.totalCount += rhs.totalCount
24 | }
25 |
26 | // MARK: - Init
27 |
28 | public init() {}
29 |
30 | public init?(chainIDs: [ChainID]?) {
31 | guard let chainIDs else { return nil }
32 | self = .init(chainIDs: chainIDs)
33 | }
34 |
35 | public init(chainIDs: [ChainID]) {
36 | for chainID in chainIDs {
37 | if let currentCount = chainIDCounts[chainID] {
38 | chainIDCounts[chainID] = currentCount + 1
39 | } else {
40 | chainIDCounts[chainID] = 1
41 | uniqueChainIDs.append(chainID)
42 | }
43 | }
44 | uniqueChainIDs = uniqueChainIDs.sorted(by: { $0.displayName < $1.displayName })
45 | for atomsInChainID in chainIDCounts.values {
46 | totalCount += atomsInChainID
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinComposition/ProteinElementComposition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinElementComposition.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 25/5/21.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ProteinElementComposition: Sendable {
11 |
12 | /// Dictionary containing the number of atoms of each type of element.
13 | public var elementCounts = [AtomElement: Int]()
14 | /// The total count of atoms of all types.
15 | public var totalCount: Int = 0
16 | /// The total count of atoms of a type present in `AtomElement.importantElements`.
17 | public var importantElementCount: Int {
18 | var sum: Int = 0
19 | for element in AtomElement.importantElements {
20 | sum += elementCounts[element] ?? 0
21 | }
22 | return sum
23 | }
24 |
25 | public static func += (lhs: inout ProteinElementComposition, rhs: ProteinElementComposition) {
26 | lhs.elementCounts.merge(rhs.elementCounts, uniquingKeysWith: { lhsCount, rhsCount in
27 | return lhsCount + rhsCount
28 | })
29 | lhs.totalCount += rhs.totalCount
30 | }
31 |
32 | // MARK: - Init
33 |
34 | public init() {}
35 |
36 | public init(elements: [AtomElement]) {
37 | for element in elements {
38 | if let currentCount = elementCounts[element] {
39 | elementCounts[element] = currentCount + 1
40 | } else {
41 | elementCounts[element] = 1
42 | }
43 | }
44 | for elementCount in elementCounts.values {
45 | totalCount += elementCount
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinComposition/ProteinResidueComposition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinResidueComposition.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 20/1/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ProteinResidueComposition: Sendable {
11 |
12 | /// Dictionary containing the number of atoms of each type of residue.
13 | public var residueCounts = [Residue: Int]()
14 | /// The total count of atoms of all types.
15 | public var totalCount: Int = 0
16 |
17 | public static func += (lhs: inout ProteinResidueComposition, rhs: ProteinResidueComposition) {
18 | lhs.residueCounts.merge(rhs.residueCounts, uniquingKeysWith: { lhsCount, rhsCount in
19 | return lhsCount + rhsCount
20 | })
21 | lhs.totalCount += rhs.totalCount
22 | }
23 |
24 | // MARK: - Init
25 |
26 | public init() {}
27 |
28 | public init?(residues: [Residue]?) {
29 | guard let residues else { return nil }
30 | self.init(residues: residues)
31 | }
32 |
33 | public init(residues: [Residue]) {
34 | for residue in residues {
35 | if let currentCount = residueCounts[residue] {
36 | residueCounts[residue] = currentCount + 1
37 | } else {
38 | residueCounts[residue] = 1
39 | }
40 | }
41 | for residueCount in residueCounts.values {
42 | totalCount += residueCount
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinFile.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 6/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - ProteinFileType
11 |
12 | public enum ProteinFileType: Sendable {
13 | case staticStructure
14 | case dynamicStructure
15 | }
16 |
17 | // MARK: - ProteinFile
18 |
19 | public struct ProteinFile: Hashable, Sendable {
20 |
21 | /// Unique ID for the file (only used internally).
22 | public let id = UUID()
23 | /// The type of protein file type (whether it contains a static structure or several configurations of the same protein).
24 | public let fileType: ProteinFileType
25 | /// Name of the protein file (without the file extension).
26 | public let fileName: String
27 | /// Extension of the protein file (.pdb, .cif...).
28 | public let fileExtension: String
29 | /// Name of the protein file (including file extension).
30 | public var fileNameWithExtension: String {
31 | return fileName + "." + fileExtension
32 | }
33 | /// Size of the stored file, in bytes.
34 | public let byteSize: Int?
35 | /// File metadata.
36 | public let fileInfo: ProteinFileInfo
37 | /// Protein contained in the file.
38 | public var models: [Protein]
39 |
40 | // MARK: - Init
41 |
42 | public init(fileType: ProteinFileType, fileName: String, fileExtension: String, models: [Protein], fileInfo: ProteinFileInfo, byteSize: Int?) {
43 | self.fileType = fileType
44 | self.fileName = fileName
45 | self.fileExtension = fileExtension
46 | self.models = models
47 | self.fileInfo = fileInfo
48 | self.byteSize = byteSize
49 | }
50 |
51 | // MARK: - Hashable
52 |
53 | public static func == (lhs: ProteinFile, rhs: ProteinFile) -> Bool {
54 | lhs.id == rhs.id
55 | }
56 |
57 | public func hash(into hasher: inout Hasher) {
58 | hasher.combine(id)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinFileInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProteinFileInfo.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 14/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Class containing the data related to the imported protein file itself.
11 | public struct ProteinFileInfo: Sendable {
12 |
13 | /// PDB ID as in RCSB database.
14 | public var pdbID: String?
15 | /// Human-readable description of the protein.
16 | public var description: String?
17 | /// Authors of the file.
18 | public var authors: String?
19 | /// Full source file text
20 | public var sourceLines: [String]?
21 |
22 | /// List of all lines with warnings
23 | public var warningIndices: [Int] = []
24 |
25 | // MARK: - Initialization
26 |
27 | public init() {}
28 |
29 | public init(pdbID: String?, description: String?, authors: String?, sourceLines: [String]?) {
30 | self.pdbID = pdbID
31 | self.description = description
32 | self.authors = authors
33 | self.sourceLines = sourceLines
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Types/BondStruct.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BondStruct.swift
3 | //
4 | //
5 | // Created by Raúl Montón Pinillos on 12/11/23.
6 | //
7 |
8 | import Foundation
9 | import simd
10 |
11 | public struct BondStruct: Sendable {
12 | /// Position of the first atom in world space.
13 | public let atomA: simd_float3
14 | /// Position of the first atom in world space.
15 | public let atomB: simd_float3
16 | /// Cylinder center in world space.
17 | public let cylinderCenter: simd_float3
18 | /// Bond radius.
19 | public let bondRadius: Float
20 |
21 | public init(atomA: simd_float3, atomB: simd_float3, cylinderCenter: simd_float3, bondRadius: Float) {
22 | self.atomA = atomA
23 | self.atomB = atomB
24 | self.cylinderCenter = cylinderCenter
25 | self.bondRadius = bondRadius
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Types/SecondaryStructure.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecondaryStructure.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 5/3/23.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | public enum SecondaryStructure: UInt8, CaseIterable, Sendable {
12 | case helix
13 | case sheet
14 | case loop
15 | case nonChain
16 |
17 | public var name: String {
18 | switch self {
19 | case .helix:
20 | return NSLocalizedString("Helix", comment: "")
21 | case .sheet:
22 | return NSLocalizedString("Sheet", comment: "")
23 | case .loop:
24 | return NSLocalizedString("Loop", comment: "")
25 | case .nonChain:
26 | return NSLocalizedString("Non-chain", comment: "")
27 | }
28 | }
29 |
30 | public var defaultColor: Color {
31 | switch self {
32 | case .helix:
33 | return Color(.displayP3, red: 0.423, green: 0.733, blue: 0.235, opacity: 1)
34 | case .sheet:
35 | return Color(.displayP3, red: 0.000, green: 0.590, blue: 1.000, opacity: 1)
36 | case .loop:
37 | return Color(.displayP3, red: 0.500, green: 0.500, blue: 0.500, opacity: 1)
38 | case .nonChain:
39 | return Color(.displayP3, red: 0.750, green: 0.750, blue: 0.750, opacity: 1)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/BioViewerPackages/BioViewerFoundation/Tests/BioViewerFoundationTests/BioViewerFoundationTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import BioViewerFoundation
3 |
4 | final class BioViewerFoundationTests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewerPackages/CIFParser/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/BioViewerPackages/CIFParser/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BioViewerPackages/CIFParser/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "CIFParser",
8 | platforms: [.iOS(.v17), .macOS(.v14)],
9 | products: [
10 | // Products define the executables and libraries a package produces, making them visible to other packages.
11 | .library(
12 | name: "CIFParser",
13 | targets: ["CIFParser"]),
14 | ],
15 |
16 | dependencies: [
17 | .package(path: "../BioViewerFoundation")
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package, defining a module or a test suite.
21 | // Targets can depend on other targets in this package and products from dependencies.
22 | .target(
23 | name: "CIFParser",
24 | dependencies: ["BioViewerFoundation"]
25 | ),
26 | .testTarget(
27 | name: "CIFParserTests",
28 | dependencies: ["CIFParser"]
29 | ),
30 | ]
31 | )
32 |
--------------------------------------------------------------------------------
/BioViewerPackages/CIFParser/Sources/CIFParser/CIFConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CIFConstants.swift
3 | //
4 | //
5 | // Created by Raúl Montón Pinillos on 22/2/24.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum Directives {
11 | static let loop = "loop_"
12 | static let comment = "#"
13 | }
14 | public enum CategoryNames {
15 |
16 | static let categoriesToSave: [String] = {
17 | var categoriesToSave = [String]()
18 | categoriesToSave.append(Entry.id)
19 | categoriesToSave.append(AtomSite.groupPDB)
20 | categoriesToSave.append(AtomSite.typeSymbol)
21 | categoriesToSave.append(AtomSite.compID)
22 | categoriesToSave.append(AtomSite.authAsymID)
23 | categoriesToSave.append(AtomSite.cartnX)
24 | categoriesToSave.append(AtomSite.cartnY)
25 | categoriesToSave.append(AtomSite.cartnZ)
26 | return categoriesToSave
27 | }()
28 |
29 | public enum Entry {
30 | static let id = "_entry.id"
31 | }
32 | public enum AtomSite {
33 | static let groupPDB = "_atom_site.group_PDB"
34 | static let typeSymbol = "_atom_site.type_symbol"
35 | static let compID = "_atom_site.label_comp_id"
36 | static let authAsymID = "_atom_site.auth_asym_id"
37 | static let cartnX = "_atom_site.Cartn_x"
38 | static let cartnY = "_atom_site.Cartn_y"
39 | static let cartnZ = "_atom_site.Cartn_z"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/BioViewerPackages/CIFParser/Tests/CIFParserTests/CIFParserTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import CIFParser
3 |
4 | final class CIFParserTests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewerPackages/PDBParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BioViewerPackages/PDBParser/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BioViewerPackages/PDBParser/.swiftpm/xcode/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | PDBParser.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 2
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/BioViewerPackages/PDBParser/.swiftpm/xcode/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | PDBParser-Package.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | PDBParser.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 5
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/BioViewerPackages/PDBParser/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "PDBParser",
8 | platforms: [.iOS(.v17), .macOS(.v14)],
9 | products: [
10 | // Products define the executables and libraries a package produces, making them visible to other packages.
11 | .library(
12 | name: "PDBParser",
13 | targets: ["PDBParser"]),
14 | ],
15 | dependencies: [
16 | .package(path: "../BioViewerFoundation")
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package, defining a module or a test suite.
20 | // Targets can depend on other targets in this package and products from dependencies.
21 | .target(
22 | name: "PDBParser",
23 | dependencies: ["BioViewerFoundation"]
24 | ),
25 | .testTarget(
26 | name: "PDBParserTests",
27 | dependencies: ["PDBParser"]
28 | ),
29 | ]
30 | )
31 |
--------------------------------------------------------------------------------
/BioViewerPackages/PDBParser/Sources/PDBParser/PDBParseError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDBParseError.swift
3 | // BioViewer
4 | //
5 | // Created by Raúl Montón Pinillos on 15/1/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum PDBParseError: Error {
11 | case unexpectedLineLength
12 | case missingResidueID
13 | case invalidAtomCoordinates
14 | case missingHELIXInitResidueID
15 | case missingHELIXFinalResidueID
16 | }
17 |
--------------------------------------------------------------------------------
/BioViewerPackages/PDBParser/Tests/PDBParserTests/PDBParserTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import PDBParser
3 |
4 | final class PDBParserTests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/.swiftpm/xcode/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | XYZParser-Package.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | XYZParser.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 4
16 |
17 | XYZParserTests.xcscheme_^#shared#^_
18 |
19 | orderHint
20 | 3
21 |
22 |
23 | SuppressBuildableAutocreation
24 |
25 | XYZParser
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/.swiftpm/xcode/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | XYZParser-Package.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | XYZParser.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 3
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "XYZParser",
8 | platforms: [.iOS(.v17), .macOS(.v14)],
9 | products: [
10 | // Products define the executables and libraries a package produces, making them visible to other packages.
11 | .library(
12 | name: "XYZParser",
13 | targets: ["XYZParser"]
14 | )
15 | ],
16 | dependencies: [
17 | .package(path: "../BioViewerFoundation")
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package, defining a module or a test suite.
21 | // Targets can depend on other targets in this package and products from dependencies.
22 | .target(
23 | name: "XYZParser",
24 | dependencies: ["BioViewerFoundation"]
25 | ),
26 | .testTarget(
27 | name: "XYZParserTests",
28 | dependencies: ["XYZParser"]
29 | )
30 | ]
31 | )
32 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/Sources/XYZParser/XYZConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XYZConstants.swift
3 | //
4 | //
5 | // Created by Raúl Montón Pinillos on 12/11/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum XYZConstants {
11 | /// Number of components in an atom coordinates line.
12 | static let atomLineNumberOfComponents: Int = 4
13 | }
14 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/Sources/XYZParser/XYZParsedConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsedConfiguration.swift
3 | //
4 | //
5 | // Created by Raúl Montón Pinillos on 12/11/23.
6 | //
7 |
8 | import BioViewerFoundation
9 | import Foundation
10 | import simd
11 |
12 | class XYZParsedConfiguration {
13 | var id: Int
14 | var energy: Float?
15 | // Make one atom array per common element
16 | var atomArray = [simd_float3]()
17 | var atomElements = [AtomElement]()
18 | var atomArrayComposition = ProteinElementComposition()
19 |
20 | init(id: Int) {
21 | self.id = id
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/Sources/XYZParser/XYZParserError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Raúl Montón Pinillos on 12/11/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum XYZParserError: Error {
11 | case noConfiguration
12 | case emptyAtomCount
13 | }
14 |
15 | extension XYZParserError: LocalizedError {
16 | public var errorDescription: String? {
17 | switch self {
18 | case .noConfiguration:
19 | return NSLocalizedString("There are no configurations in this file", comment: "")
20 | case .emptyAtomCount:
21 | return NSLocalizedString("Error: File does not contain any atom positions", comment: "")
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/BioViewerPackages/XYZParser/Tests/XYZParserTests/XYZParserTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import XYZParser
3 |
4 | final class XYZParserTests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/BioViewerTests/BioViewerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDB_ViewerTests.swift
3 | // BioViewerTests
4 | //
5 | // Created by Raúl Montón Pinillos on 4/5/21.
6 | //
7 |
8 | import XCTest
9 | @testable import BioViewer
10 |
11 | class BioViewerTests: XCTestCase {
12 |
13 | var scheduler: MetalScheduler?
14 | var protein: Protein?
15 |
16 | override func setUpWithError() throws {
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 | self.scheduler = MetalScheduler.shared
19 | let proteinSampleFile = Bundle.main.url(forResource: "3JBT", withExtension: "pdb")!
20 | let proteinData = try? Data(contentsOf: proteinSampleFile)
21 | self.protein = parsePDB(rawText: String(decoding: proteinData, as: UTF8.self))
22 | }
23 |
24 | override func tearDownWithError() throws {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | }
27 |
28 | func testExample() throws {
29 | // This is an example of a functional test case.
30 | // Use XCTAssert and related functions to verify your tests produce the correct results.
31 | let camera = Camera(nearPlane: 1, farPlane: 10, focalLength: 200)
32 | print(camera.focalLength)
33 | }
34 |
35 | func testPerformanceExample() throws {
36 | let measureOptions = XCTMeasureOptions.init()
37 | measureOptions.iterationCount = 100
38 |
39 | // Original implementation: 0.339s, 0.339s (25th May 2021)
40 | // Improved implementation: 0.328s, 0.327s (26th May 2021)
41 | self.measure(options: measureOptions, block: {
42 | scheduler?.createSASPoints(protein: self.protein!, sceneDelegate: ProteinViewSceneDelegate())
43 | })
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/BioViewerTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/BioViewerUITests/BioViewerUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PDB_ViewerUITests.swift
3 | // BioViewerUITests
4 | //
5 | // Created by Raúl Montón Pinillos on 4/5/21.
6 | //
7 |
8 | import XCTest
9 |
10 | class BioViewerUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use recording to get started writing UI tests.
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | func testLaunchPerformance() throws {
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTApplicationLaunchMetric()]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/BioViewerUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Privacy policy/en/PrivacyPolicy.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy
2 |
3 | This app does not collect any data from the user, neither for us nor any third parties.
--------------------------------------------------------------------------------
/PromoAssets/MetalFeatures.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/PromoAssets/MetalFeatures.png
--------------------------------------------------------------------------------
/PromoAssets/Mockup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/PromoAssets/Mockup.png
--------------------------------------------------------------------------------
/ProteinThumbnail/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ProteinThumbnail/Assets.xcassets/OverlayPDB.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "OverlayPDB.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ProteinThumbnail/Assets.xcassets/OverlayPDB.imageset/OverlayPDB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/ProteinThumbnail/Assets.xcassets/OverlayPDB.imageset/OverlayPDB.png
--------------------------------------------------------------------------------
/ProteinThumbnail/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionAttributes
8 |
9 | QLSupportedContentTypes
10 |
11 | com.raulmonton.bioviewer.pdb
12 |
13 | QLThumbnailMinimumDimension
14 | 0
15 |
16 | NSExtensionPointIdentifier
17 | com.apple.quicklook.thumbnail
18 | NSExtensionPrincipalClass
19 | $(PRODUCT_MODULE_NAME).ThumbnailProvider
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ProteinThumbnail/ProteinThumbnail.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/ProteinVisualization/Figures/HighResH2O.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/HighResH2O.png
--------------------------------------------------------------------------------
/docs/ProteinVisualization/Figures/PercentageCloseFiltering.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/PercentageCloseFiltering.png
--------------------------------------------------------------------------------
/docs/ProteinVisualization/Figures/ShadowAcne.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/ShadowAcne.png
--------------------------------------------------------------------------------
/docs/ProteinVisualization/Figures/ShadowedDrawableTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/ShadowedDrawableTexture.png
--------------------------------------------------------------------------------
/docs/ProteinVisualization/Figures/SunDepthTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/SunDepthTexture.png
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # BioViewer Documentation
2 | Documentation for BioViewer app.
3 |
4 | ## Protein visualization
5 | - [Casting shadows](ProteinVisualization/HardShadows.md)
6 | - [Drawing the molecular surface](ProteinVisualization/MolecularSurface.md)
7 |
--------------------------------------------------------------------------------
/scripts/Plot3DPoints.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | import numpy as np
3 | from mpl_toolkits.mplot3d import Axes3D
4 |
5 | fig = plt.figure()
6 | ax = Axes3D(fig)
7 |
8 | points = np.array([]) # Points are copy-pasted here when debugging
9 |
10 | print(np.shape(points))
11 | ax.scatter(points[:,0], points[:,1], points[:,2])
12 | plt.show()
--------------------------------------------------------------------------------
/scripts/UnitaryIcosahedron.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/scripts/UnitaryIcosahedron.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/scripts/UnitaryIcosahedron.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | # Program flags
4 | addRadiusAndPosition = True
5 |
6 | # Create empty list to store icosahedron points
7 | icosahedronPoints = []
8 |
9 | # Icosahedron parameter
10 | t = ( 1.0 + np.sqrt(5.0) ) / 2.0
11 |
12 | # Add all icosahedron vertices
13 | icosahedronPoints.append( np.array( (-1, t, 0) ))
14 | icosahedronPoints.append( np.array( (1, t, 0) ))
15 | icosahedronPoints.append( np.array( (-1, -t, 0) ))
16 | icosahedronPoints.append( np.array( (1, -t, 0) ))
17 |
18 | icosahedronPoints.append( np.array( (0, -1, t) ))
19 | icosahedronPoints.append( np.array( (0, 1, t) ))
20 | icosahedronPoints.append( np.array( (0, -1, -t) ))
21 | icosahedronPoints.append( np.array( (0, 1, -t) ))
22 |
23 | icosahedronPoints.append( np.array( (t, 0, -1) ))
24 | icosahedronPoints.append( np.array( (t, 0, 1) ))
25 | icosahedronPoints.append( np.array( (-t, 0, -1) ))
26 | icosahedronPoints.append( np.array( (-t, 0, 1) ))
27 |
28 | # Normalice vertices to the unitary sphere (so it has a radius of 1)
29 | for i in range(len(icosahedronPoints)):
30 | icosahedronPoints[i] /= np.sqrt(icosahedronPoints[i][0]**2
31 | + icosahedronPoints[i][1]**2
32 | + icosahedronPoints[i][2]**2)
33 |
34 |
35 | ##### PRINTING RESULTS #####
36 |
37 | if addRadiusAndPosition:
38 | # Print results ready to copy/paste in the Metal kernel function body
39 | for index, icosahedronVertex in enumerate(icosahedronPoints):
40 | print("generatedVertices[index+"
41 | + str(index) + "] = simd_float3("
42 | + str(icosahedronVertex[0]) + ", "
43 | + str(icosahedronVertex[1]) + ", "
44 | + str(icosahedronVertex[2]) + ")"
45 | + " * radius + position;")
46 | else:
47 | # Print prettier results for the console
48 | for index, icosahedronVertex in enumerate(icosahedronPoints):
49 | print("Vertex " + str(index) + " = "
50 | + str(icosahedronVertex[0]) + ", "
51 | + str(icosahedronVertex[1]) + ", "
52 | + str(icosahedronVertex[2]))
53 |
54 |
--------------------------------------------------------------------------------