├── .gitignore ├── ActorLocker.uplugin ├── Config └── FilterPlugin.ini ├── Content └── Python │ ├── unlock_all_actors.py │ └── unlock_all_actors_command.py ├── LICENSE.txt ├── README.md ├── Resources ├── Icon128.png ├── Lock.png └── Unlock.png └── Source └── ActorLocker ├── ActorLocker.Build.cs ├── Private ├── ActorLocker.cpp ├── ActorLockerCommandManager.cpp ├── ActorLockerCommands.cpp ├── ActorLockerEditorMode.cpp ├── ActorLockerManager.cpp ├── ActorLockerMenuExtender.cpp ├── ActorLockerPluginStateService.cpp ├── ActorLockerPythonCommand.cpp ├── ActorLockerSettings.cpp ├── ActorLockerStyle.cpp ├── ActorLockerTypes.cpp ├── SLockWidget.cpp └── SceneOutlinerActorLocker.cpp └── Public ├── ActorLocker.h ├── ActorLockerCommandManager.h ├── ActorLockerCommands.h ├── ActorLockerEditorMode.h ├── ActorLockerManager.h ├── ActorLockerMenuExtender.h ├── ActorLockerPluginStateService.h ├── ActorLockerPythonCommand.h ├── ActorLockerSettings.h ├── ActorLockerStyle.h ├── ActorLockerTypes.h ├── SLockWidget.h └── SceneOutlinerActorLocker.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/unrealengine,visualstudio,visualstudiocode,rider 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=unrealengine,visualstudio,visualstudiocode,rider 3 | 4 | ### Rider ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # AWS User-specific 16 | .idea/**/aws.xml 17 | 18 | # Generated files 19 | .idea/**/contentModel.xml 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/artifacts 39 | # .idea/compiler.xml 40 | # .idea/jarRepositories.xml 41 | # .idea/modules.xml 42 | # .idea/*.iml 43 | # .idea/modules 44 | # *.iml 45 | # *.ipr 46 | 47 | # CMake 48 | cmake-build-*/ 49 | 50 | # Mongo Explorer plugin 51 | .idea/**/mongoSettings.xml 52 | 53 | # File-based project format 54 | *.iws 55 | 56 | # IntelliJ 57 | out/ 58 | 59 | # mpeltonen/sbt-idea plugin 60 | .idea_modules/ 61 | 62 | # JIRA plugin 63 | atlassian-ide-plugin.xml 64 | 65 | # Cursive Clojure plugin 66 | .idea/replstate.xml 67 | 68 | # SonarLint plugin 69 | .idea/sonarlint/ 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | 80 | # Android studio 3.1+ serialized cache file 81 | .idea/caches/build_file_checksums.ser 82 | 83 | ### UnrealEngine ### 84 | # Visual Studio 2015 user specific files 85 | .vs/ 86 | 87 | # Compiled Object files 88 | *.slo 89 | *.lo 90 | *.o 91 | *.obj 92 | 93 | # Precompiled Headers 94 | *.gch 95 | *.pch 96 | 97 | # Compiled Dynamic libraries 98 | *.so 99 | *.dylib 100 | *.dll 101 | 102 | # Fortran module files 103 | *.mod 104 | 105 | # Compiled Static libraries 106 | *.lai 107 | *.la 108 | *.a 109 | *.lib 110 | 111 | # Executables 112 | *.exe 113 | *.out 114 | *.app 115 | *.ipa 116 | 117 | # These project files can be generated by the engine 118 | *.xcodeproj 119 | *.xcworkspace 120 | *.sln 121 | *.suo 122 | *.opensdf 123 | *.sdf 124 | *.VC.db 125 | *.VC.opendb 126 | 127 | # Precompiled Assets 128 | SourceArt/**/*.png 129 | SourceArt/**/*.tga 130 | 131 | # Binary Files 132 | Binaries/* 133 | Plugins/*/Binaries/* 134 | 135 | # Builds 136 | Build/* 137 | 138 | # Whitelist PakBlacklist-.txt files 139 | !Build/*/ 140 | Build/*/** 141 | !Build/*/PakBlacklist*.txt 142 | 143 | # Don't ignore icon files in Build 144 | !Build/**/*.ico 145 | 146 | # Built data for maps 147 | *_BuiltData.uasset 148 | 149 | # Configuration files generated by the Editor 150 | Saved/* 151 | 152 | # Compiled source files for the engine to use 153 | Intermediate/* 154 | Plugins/*/Intermediate/* 155 | 156 | # Cache files for the editor to use 157 | DerivedDataCache/* 158 | 159 | ### UnrealEngine Patch ### 160 | # Don't ignore icon and splash images for mobile app 161 | !Build/IOS/Resources/ 162 | Build/IOS/Resources/* 163 | !Build/IOS/Resources/Graphics/ 164 | Build/IOS/Resources/Graphics/* 165 | !Build/IOS/Resources/Graphics/*.png 166 | !Build/Android/res/ 167 | Build/Android/res/* 168 | !Build/Android/res/*/ 169 | Build/Android/res/*/* 170 | !Build/Android/res/*/*.png 171 | # Ignore plugins binaries on deep subfolders 172 | Plugins/**/Binaries/* 173 | Plugins/**/Intermediate/* 174 | 175 | ### VisualStudioCode ### 176 | .vscode/* 177 | !.vscode/settings.json 178 | !.vscode/tasks.json 179 | !.vscode/launch.json 180 | !.vscode/extensions.json 181 | !.vscode/*.code-snippets 182 | 183 | # Local History for Visual Studio Code 184 | .history/ 185 | 186 | # Built Visual Studio Code Extensions 187 | *.vsix 188 | 189 | ### VisualStudioCode Patch ### 190 | # Ignore all local history of files 191 | .history 192 | .ionide 193 | 194 | ### VisualStudio ### 195 | ## Ignore Visual Studio temporary files, build results, and 196 | ## files generated by popular Visual Studio add-ons. 197 | ## 198 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 199 | 200 | # User-specific files 201 | *.rsuser 202 | *.user 203 | *.userosscache 204 | *.sln.docstates 205 | 206 | # User-specific files (MonoDevelop/Xamarin Studio) 207 | *.userprefs 208 | 209 | # Mono auto generated files 210 | mono_crash.* 211 | 212 | # Build results 213 | [Dd]ebug/ 214 | [Dd]ebugPublic/ 215 | [Rr]elease/ 216 | [Rr]eleases/ 217 | x64/ 218 | x86/ 219 | [Ww][Ii][Nn]32/ 220 | [Aa][Rr][Mm]/ 221 | [Aa][Rr][Mm]64/ 222 | bld/ 223 | [Bb]in/ 224 | [Oo]bj/ 225 | [Ll]og/ 226 | [Ll]ogs/ 227 | 228 | # Visual Studio 2015/2017 cache/options directory 229 | # Uncomment if you have tasks that create the project's static files in wwwroot 230 | #wwwroot/ 231 | 232 | # Visual Studio 2017 auto generated files 233 | Generated\ Files/ 234 | 235 | # MSTest test Results 236 | [Tt]est[Rr]esult*/ 237 | [Bb]uild[Ll]og.* 238 | 239 | # NUnit 240 | *.VisualState.xml 241 | TestResult.xml 242 | nunit-*.xml 243 | 244 | # Build Results of an ATL Project 245 | [Dd]ebugPS/ 246 | [Rr]eleasePS/ 247 | dlldata.c 248 | 249 | # Benchmark Results 250 | BenchmarkDotNet.Artifacts/ 251 | 252 | # .NET Core 253 | project.lock.json 254 | project.fragment.lock.json 255 | artifacts/ 256 | 257 | # ASP.NET Scaffolding 258 | ScaffoldingReadMe.txt 259 | 260 | # StyleCop 261 | StyleCopReport.xml 262 | 263 | # Files built by Visual Studio 264 | *_i.c 265 | *_p.c 266 | *_h.h 267 | *.ilk 268 | *.meta 269 | *.iobj 270 | *.pdb 271 | *.ipdb 272 | *.pgc 273 | *.pgd 274 | *.rsp 275 | *.sbr 276 | *.tlb 277 | *.tli 278 | *.tlh 279 | *.tmp 280 | *.tmp_proj 281 | *_wpftmp.csproj 282 | *.log 283 | *.tlog 284 | *.vspscc 285 | *.vssscc 286 | .builds 287 | *.pidb 288 | *.svclog 289 | *.scc 290 | 291 | # Chutzpah Test files 292 | _Chutzpah* 293 | 294 | # Visual C++ cache files 295 | ipch/ 296 | *.aps 297 | *.ncb 298 | *.opendb 299 | *.cachefile 300 | *.VC.VC.opendb 301 | 302 | # Visual Studio profiler 303 | *.psess 304 | *.vsp 305 | *.vspx 306 | *.sap 307 | 308 | # Visual Studio Trace Files 309 | *.e2e 310 | 311 | # TFS 2012 Local Workspace 312 | $tf/ 313 | 314 | # Guidance Automation Toolkit 315 | *.gpState 316 | 317 | # ReSharper is a .NET coding add-in 318 | _ReSharper*/ 319 | *.[Rr]e[Ss]harper 320 | *.DotSettings.user 321 | 322 | # TeamCity is a build add-in 323 | _TeamCity* 324 | 325 | # DotCover is a Code Coverage Tool 326 | *.dotCover 327 | 328 | # AxoCover is a Code Coverage Tool 329 | .axoCover/* 330 | !.axoCover/settings.json 331 | 332 | # Coverlet is a free, cross platform Code Coverage Tool 333 | coverage*.json 334 | coverage*.xml 335 | coverage*.info 336 | 337 | # Visual Studio code coverage results 338 | *.coverage 339 | *.coveragexml 340 | 341 | # NCrunch 342 | _NCrunch_* 343 | .*crunch*.local.xml 344 | nCrunchTemp_* 345 | 346 | # MightyMoose 347 | *.mm.* 348 | AutoTest.Net/ 349 | 350 | # Web workbench (sass) 351 | .sass-cache/ 352 | 353 | # Installshield output folder 354 | [Ee]xpress/ 355 | 356 | # DocProject is a documentation generator add-in 357 | DocProject/buildhelp/ 358 | DocProject/Help/*.HxT 359 | DocProject/Help/*.HxC 360 | DocProject/Help/*.hhc 361 | DocProject/Help/*.hhk 362 | DocProject/Help/*.hhp 363 | DocProject/Help/Html2 364 | DocProject/Help/html 365 | 366 | # Click-Once directory 367 | publish/ 368 | 369 | # Publish Web Output 370 | *.[Pp]ublish.xml 371 | *.azurePubxml 372 | # Note: Comment the next line if you want to checkin your web deploy settings, 373 | # but database connection strings (with potential passwords) will be unencrypted 374 | *.pubxml 375 | *.publishproj 376 | 377 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 378 | # checkin your Azure Web App publish settings, but sensitive information contained 379 | # in these scripts will be unencrypted 380 | PublishScripts/ 381 | 382 | # NuGet Packages 383 | *.nupkg 384 | # NuGet Symbol Packages 385 | *.snupkg 386 | # The packages folder can be ignored because of Package Restore 387 | **/[Pp]ackages/* 388 | # except build/, which is used as an MSBuild target. 389 | !**/[Pp]ackages/build/ 390 | # Uncomment if necessary however generally it will be regenerated when needed 391 | #!**/[Pp]ackages/repositories.config 392 | # NuGet v3's project.json files produces more ignorable files 393 | *.nuget.props 394 | *.nuget.targets 395 | 396 | # Microsoft Azure Build Output 397 | csx/ 398 | *.build.csdef 399 | 400 | # Microsoft Azure Emulator 401 | ecf/ 402 | rcf/ 403 | 404 | # Windows Store app package directories and files 405 | AppPackages/ 406 | BundleArtifacts/ 407 | Package.StoreAssociation.xml 408 | _pkginfo.txt 409 | *.appx 410 | *.appxbundle 411 | *.appxupload 412 | 413 | # Visual Studio cache files 414 | # files ending in .cache can be ignored 415 | *.[Cc]ache 416 | # but keep track of directories ending in .cache 417 | !?*.[Cc]ache/ 418 | 419 | # Others 420 | ClientBin/ 421 | ~$* 422 | *~ 423 | *.dbmdl 424 | *.dbproj.schemaview 425 | *.jfm 426 | *.pfx 427 | *.publishsettings 428 | orleans.codegen.cs 429 | 430 | # Including strong name files can present a security risk 431 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 432 | #*.snk 433 | 434 | # Since there are multiple workflows, uncomment next line to ignore bower_components 435 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 436 | #bower_components/ 437 | 438 | # RIA/Silverlight projects 439 | Generated_Code/ 440 | 441 | # Backup & report files from converting an old project file 442 | # to a newer Visual Studio version. Backup files are not needed, 443 | # because we have git ;-) 444 | _UpgradeReport_Files/ 445 | Backup*/ 446 | UpgradeLog*.XML 447 | UpgradeLog*.htm 448 | ServiceFabricBackup/ 449 | *.rptproj.bak 450 | 451 | # SQL Server files 452 | *.mdf 453 | *.ldf 454 | *.ndf 455 | 456 | # Business Intelligence projects 457 | *.rdl.data 458 | *.bim.layout 459 | *.bim_*.settings 460 | *.rptproj.rsuser 461 | *- [Bb]ackup.rdl 462 | *- [Bb]ackup ([0-9]).rdl 463 | *- [Bb]ackup ([0-9][0-9]).rdl 464 | 465 | # Microsoft Fakes 466 | FakesAssemblies/ 467 | 468 | # GhostDoc plugin setting file 469 | *.GhostDoc.xml 470 | 471 | # Node.js Tools for Visual Studio 472 | .ntvs_analysis.dat 473 | node_modules/ 474 | 475 | # Visual Studio 6 build log 476 | *.plg 477 | 478 | # Visual Studio 6 workspace options file 479 | *.opt 480 | 481 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 482 | *.vbw 483 | 484 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 485 | *.vbp 486 | 487 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 488 | *.dsw 489 | *.dsp 490 | 491 | # Visual Studio 6 technical files 492 | 493 | # Visual Studio LightSwitch build output 494 | **/*.HTMLClient/GeneratedArtifacts 495 | **/*.DesktopClient/GeneratedArtifacts 496 | **/*.DesktopClient/ModelManifest.xml 497 | **/*.Server/GeneratedArtifacts 498 | **/*.Server/ModelManifest.xml 499 | _Pvt_Extensions 500 | 501 | # Paket dependency manager 502 | .paket/paket.exe 503 | paket-files/ 504 | 505 | # FAKE - F# Make 506 | .fake/ 507 | 508 | # CodeRush personal settings 509 | .cr/personal 510 | 511 | # Python Tools for Visual Studio (PTVS) 512 | __pycache__/ 513 | *.pyc 514 | 515 | # Cake - Uncomment if you are using it 516 | # tools/** 517 | # !tools/packages.config 518 | 519 | # Tabs Studio 520 | *.tss 521 | 522 | # Telerik's JustMock configuration file 523 | *.jmconfig 524 | 525 | # BizTalk build output 526 | *.btp.cs 527 | *.btm.cs 528 | *.odx.cs 529 | *.xsd.cs 530 | 531 | # OpenCover UI analysis results 532 | OpenCover/ 533 | 534 | # Azure Stream Analytics local run output 535 | ASALocalRun/ 536 | 537 | # MSBuild Binary and Structured Log 538 | *.binlog 539 | 540 | # NVidia Nsight GPU debugger configuration file 541 | *.nvuser 542 | 543 | # MFractors (Xamarin productivity tool) working folder 544 | .mfractor/ 545 | 546 | # Local History for Visual Studio 547 | .localhistory/ 548 | 549 | # Visual Studio History (VSHistory) files 550 | .vshistory/ 551 | 552 | # BeatPulse healthcheck temp database 553 | healthchecksdb 554 | 555 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 556 | MigrationBackup/ 557 | 558 | # Ionide (cross platform F# VS Code tools) working folder 559 | .ionide/ 560 | 561 | # Fody - auto-generated XML schema 562 | FodyWeavers.xsd 563 | 564 | # VS Code files for those working on multiple tools 565 | *.code-workspace 566 | 567 | # Local History for Visual Studio Code 568 | 569 | # Windows Installer files from build outputs 570 | *.cab 571 | *.msi 572 | *.msix 573 | *.msm 574 | *.msp 575 | 576 | # JetBrains Rider 577 | *.sln.iml 578 | 579 | ### VisualStudio Patch ### 580 | # Additional files built by Visual Studio 581 | 582 | # End of https://www.toptal.com/developers/gitignore/api/unrealengine,visualstudio,visualstudiocode,rider 583 | -------------------------------------------------------------------------------- /ActorLocker.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 11, 4 | "VersionName": "1.5.3", 5 | "FriendlyName": "Actor Locker", 6 | "Description": "World Outliner and Level Editor extension to lock actors in the editor viewport", 7 | "Category": "Editor", 8 | "CreatedBy": "Gradess Games", 9 | "CreatedByURL": "https://stepantrofimov.com", 10 | "DocsURL": "https://github.com/Gradess2019/ActorLocker", 11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/9280e03740c64592ad81b9fe751cec7e", 12 | "SupportURL": "https://discord.gg/zFK38y68tK", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "ActorLocker", 20 | "Type": "Editor", 21 | "LoadingPhase": "Default", 22 | "WhitelistPlatforms": [ 23 | "Win64", 24 | "Mac", 25 | "Linux" 26 | ] 27 | } 28 | ], 29 | "Plugins": [ 30 | { 31 | "Name": "PythonScriptPlugin", 32 | "Enabled": true 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | /README.md -------------------------------------------------------------------------------- /Content/Python/unlock_all_actors.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import unreal 3 | 4 | import unlock_all_actors_command 5 | importlib.reload(unlock_all_actors_command) 6 | from unlock_all_actors_command import UnlockAllActorsCommand 7 | 8 | if __name__ == '__main__': 9 | command = UnlockAllActorsCommand() 10 | command.add_object_to_root() 11 | command.execute() 12 | -------------------------------------------------------------------------------- /Content/Python/unlock_all_actors_command.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | import unreal 4 | 5 | 6 | @unreal.uclass() 7 | class UnlockAllActorsCommand(unreal.ActorLockerPythonCommand): 8 | 9 | current_map = unreal.uproperty(unreal.AssetData) 10 | current_id = unreal.uproperty(int) 11 | maps = unreal.uproperty(unreal.Array(unreal.AssetData)) 12 | 13 | def load_map(self, map_asset: unreal.AssetData): 14 | print("Open map {0}".format(map_asset.package_name)) 15 | subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) 16 | return subsystem.load_level(str(map_asset.package_name)) 17 | 18 | def request_unlock_actors(self): 19 | print("Waiting map {0} to be loaded...".format(self.current_map.package_name)) 20 | self.set_editor_timer(self, "on_timer_finished", 1, False, -1) 21 | 22 | def execute(self): 23 | asset_registry = unreal.AssetRegistryHelpers.get_asset_registry() 24 | all_assets = asset_registry.get_all_assets() 25 | self.maps = [asset for asset in all_assets if asset.asset_class_path.asset_name == "World" and str(asset.package_name).startswith("/Game")] 26 | 27 | self.current_id = 0 28 | self.process_next_map() 29 | 30 | @unreal.ufunction() 31 | def process_next_map(self): 32 | self.current_map = self.maps[self.current_id] 33 | if self.load_map(self.current_map): 34 | self.request_unlock_actors() 35 | 36 | @unreal.ufunction() 37 | def on_timer_finished(self): 38 | print("Map {0} is loaded. Unlocking actors...".format(self.current_map.package_name)) 39 | unreal.ActorLockerCommandManager.unlock_all_objects() 40 | 41 | asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 42 | asset_subsystem.save_asset(str(self.current_map.package_name)) 43 | 44 | self.current_id += 1 45 | if self.current_id < len(self.maps): 46 | print("Finished unlocking actors on map {0}.".format(self.current_map.package_name)) 47 | self.process_next_map() 48 | else: 49 | print("Finished unlocking actors on all maps.") 50 | unreal.EditorDialog.show_message(unreal.Text("Actor Locker"), unreal.Text("Finished unlocking actors on all maps."), unreal.AppMsgType.OK) 51 | self.remove_object_from_root() 52 | 53 | 54 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Gradess Games 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Actor Locker 2 | 3 | ## Description 4 | Simple plugin that lets you lock actor in the Level Editor so you can't move or select it. 5 | 6 | ## How to install 7 | 1. Download [latest release](https://github.com/Gradess2019/ActorLocker/releases/latest) for your Unreal Engine version 8 | 2. Unzip into: **\/Plugins** (create Plugins directory if it doesn't exist) 9 | 3. If you are using C++: Right Mouse Button on your **.uproject** file -> Generate Visual Studio project files 10 | 4. Launch project 11 | 5. If it's not enabled: Go to Edit -> Plugins -> "Project" category -> Editor -> Enable "Actor Locker" and restart the editor 12 | 7. Done 13 | 14 | ## How to use 15 | You can manipulate selected actor using **hotkeys**, **World Outliner** or **Context Menu** 16 | 17 | ### Hotkeys (Default) 18 | - **Alt + Comma** - Lock selection 19 | - **Alt + Period** - Unlock selection 20 | - **Alt + Shift + Comma** - Lock all actors 21 | - **Alt + Shift + Period** - Unlock all actors 22 | - **Alt + Slash** - Temporary toggle lock state of locked actors 23 | 24 | You can change hotkeys in Edit -> Editor Preferences -> Plugins -> Actor Locker 25 | 26 | ### World Outliner 27 | You will see additional column with **Lock** icon. Just click on it and it will lock itself and all children as well. 28 | 29 | ![image](https://user-images.githubusercontent.com/38568823/212566043-07a552ef-09df-490f-beed-20489ae4adb3.png) 30 | 31 | ### Context Menu 32 | Click Right Mouse Button on selected actors and you will see 2 additional actions in the bottom: 33 | - Lock / Unlock selection 34 | - Toggle lock 35 | 36 | ![image](https://user-images.githubusercontent.com/38568823/212566344-e70dda49-d6ae-4704-8dab-92d5a48b3253.png) 37 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradess2019/ActorLocker/eccf6173da11337791fc46b3486239596a4a53f8/Resources/Icon128.png -------------------------------------------------------------------------------- /Resources/Lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradess2019/ActorLocker/eccf6173da11337791fc46b3486239596a4a53f8/Resources/Lock.png -------------------------------------------------------------------------------- /Resources/Unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradess2019/ActorLocker/eccf6173da11337791fc46b3486239596a4a53f8/Resources/Unlock.png -------------------------------------------------------------------------------- /Source/ActorLocker/ActorLocker.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class ActorLocker : ModuleRules 6 | { 7 | public ActorLocker(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Slate", 40 | "SlateCore", 41 | "Projects", 42 | "SceneOutliner", 43 | "UnrealEd", 44 | "InputCore", 45 | "LevelEditor", 46 | "EditorFramework", 47 | "PythonScriptPlugin", 48 | 49 | #if !UE_5_1_OR_LATER 50 | "Foliage" 51 | #endif 52 | 53 | // ... add private dependencies that you statically link with here ... 54 | } 55 | ); 56 | 57 | 58 | DynamicallyLoadedModuleNames.AddRange( 59 | new string[] 60 | { 61 | // ... add any modules that your module loads dynamically here ... 62 | } 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLocker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "ActorLocker.h" 4 | #include "ActorLockerCommandManager.h" 5 | #include "ActorLockerManager.h" 6 | #include "ActorLockerMenuExtender.h" 7 | #include "ActorLockerPluginStateService.h" 8 | #include "ActorLockerSettings.h" 9 | #include "ActorLockerStyle.h" 10 | #include "ISettingsModule.h" 11 | #include "SceneOutlinerActorLocker.h" 12 | #include "SceneOutlinerModule.h" 13 | #include "Selection.h" 14 | 15 | void FActorLockerModule::StartupModule() 16 | { 17 | FActorLockerStyle::Initialize(); 18 | FActorLockerStyle::ReloadTextures(); 19 | 20 | FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::LoadModuleChecked("SceneOutliner"); 21 | 22 | const auto ColumnVisibility = ESceneOutlinerColumnVisibility::Visible; 23 | const auto PriorityIndex = 9; 24 | const auto Factory = FCreateSceneOutlinerColumn::CreateLambda([](ISceneOutliner& SceneOutliner){ return MakeShareable(new FSceneOutlinerActorLocker(SceneOutliner)); }); 25 | const auto bCanBeHidden = false; 26 | const auto FillSize = TOptional(); 27 | const auto ColumnLabel = FSceneOutlinerActorLocker::Lock_Localized(); 28 | const auto ColumnInfo = FSceneOutlinerColumnInfo(ColumnVisibility, PriorityIndex, Factory, bCanBeHidden, FillSize, ColumnLabel); 29 | SceneOutlinerModule.RegisterDefaultColumnType(ColumnInfo); 30 | 31 | if (const auto SettingsModule = FModuleManager::GetModulePtr("Settings")) 32 | { 33 | SettingsModule->RegisterSettings("Editor", "Plugins", "ActorLocker", 34 | NSLOCTEXT("ActorLocker", "ActorLockerSettingsDisplayName", "Actor Locker"), 35 | NSLOCTEXT("ActorLocker", "ActorLockerSettingsDescription", "Configure the Actor Locker plugin (needs restart)"), 36 | GetMutableDefault() 37 | ); 38 | } 39 | 40 | UActorLockerCommandManager::RegisterCommands(); 41 | 42 | CreateActorLockerMenuExtender(); 43 | 44 | #if OLDER_THAN_UE_5_1 45 | OnPreWorldInitializationHandle = FWorldDelegates::OnPreWorldInitialization.AddLambda([this] (UWorld* World, const UWorld::InitializationValues IVS) 46 | { 47 | CreateActorLockerManager(); 48 | FWorldDelegates::OnPreWorldInitialization.Remove(OnPreWorldInitializationHandle); 49 | FEditorDelegates::OnMapOpened.AddRaw(this, &FActorLockerModule::OnMapOpened); 50 | }); 51 | #else 52 | FEditorDelegates::OnMapOpened.AddRaw(this, &FActorLockerModule::OnMapOpened); 53 | #endif 54 | 55 | } 56 | 57 | void FActorLockerModule::ShutdownModule() 58 | { 59 | FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::LoadModuleChecked("SceneOutliner"); 60 | SceneOutlinerModule.UnRegisterColumnType(); 61 | 62 | DestroyActorLockerMenuExtender(); 63 | 64 | UActorLockerCommandManager::UnregisterCommands(); 65 | 66 | FActorLockerStyle::Shutdown(); 67 | } 68 | 69 | TWeakObjectPtr FActorLockerModule::GetActorLockerManager(const bool bRequired) 70 | { 71 | if (!ActorLockerManager.IsValid() && bRequired) 72 | { 73 | CreateActorLockerManager(); 74 | } 75 | 76 | return ActorLockerManager; 77 | } 78 | 79 | void FActorLockerModule::OnMapOpened(const FString& Filename, bool bAsTemplate) 80 | { 81 | CreateActorLockerManager(); 82 | 83 | if (!PluginStateService.IsValid()) 84 | { 85 | PluginStateService = NewObject(); 86 | PluginStateService->AddToRoot(); 87 | } 88 | } 89 | 90 | void FActorLockerModule::CreateActorLockerManager() 91 | { 92 | if (ActorLockerManager.IsValid()) 93 | { 94 | ActorLockerManager->RemoveFromRoot(); 95 | ActorLockerManager->MarkAsGarbage(); 96 | } 97 | 98 | ActorLockerManager = NewObject(); 99 | ActorLockerManager->AddToRoot(); 100 | 101 | OnActorLockerManagerCreated.Broadcast(ActorLockerManager.Get()); 102 | } 103 | 104 | void FActorLockerModule::CreateActorLockerMenuExtender() 105 | { 106 | MenuExtender = MakeShareable(new FActorLockerMenuExtender()); 107 | MenuExtender->AddLevelViewportMenuExtender(); 108 | } 109 | 110 | void FActorLockerModule::DestroyActorLockerMenuExtender() 111 | { 112 | MenuExtender.Reset(); 113 | } 114 | 115 | IMPLEMENT_MODULE(FActorLockerModule, ActorLocker) 116 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerCommandManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "ActorLockerCommandManager.h" 4 | #include "ActorLockerCommands.h" 5 | #include "ActorLockerManager.h" 6 | #include "EngineUtils.h" 7 | #include "LevelEditor.h" 8 | #include "Selection.h" 9 | 10 | TSharedRef UActorLockerCommandManager::RegisterCommands() 11 | { 12 | FActorLockerCommands::Register(); 13 | 14 | const auto& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); 15 | 16 | const auto& Commands = FActorLockerCommands::Get(); 17 | const auto CommandList = LevelEditorModule.GetGlobalLevelEditorActions(); 18 | 19 | CommandList->MapAction( 20 | Commands.LockObject, 21 | FExecuteAction::CreateStatic(&UActorLockerCommandManager::LockObject), 22 | FCanExecuteAction::CreateStatic(&UActorLockerCommandManager::CanLockObject) 23 | ); 24 | 25 | CommandList->MapAction( 26 | Commands.UnlockObject, 27 | FExecuteAction::CreateStatic(&UActorLockerCommandManager::UnlockObject), 28 | FCanExecuteAction::CreateStatic(&UActorLockerCommandManager::CanUnlockObject) 29 | ); 30 | 31 | CommandList->MapAction( 32 | Commands.LockAllObjects, 33 | FExecuteAction::CreateStatic(&UActorLockerCommandManager::LockAllObjects), 34 | FCanExecuteAction::CreateStatic(&UActorLockerCommandManager::CanLockAllObjects) 35 | ); 36 | 37 | CommandList->MapAction( 38 | Commands.UnlockAllObjects, 39 | FExecuteAction::CreateStatic(&UActorLockerCommandManager::UnlockAllObjects), 40 | FCanExecuteAction::CreateStatic(&UActorLockerCommandManager::CanUnlockAllObjects) 41 | ); 42 | 43 | CommandList->MapAction( 44 | Commands.ToggleLockedObjects, 45 | FExecuteAction::CreateStatic(&UActorLockerCommandManager::ToggleLockedObjects), 46 | FCanExecuteAction::CreateStatic(&UActorLockerCommandManager::CanToggleLockedObjects) 47 | ); 48 | 49 | return CommandList; 50 | } 51 | 52 | void UActorLockerCommandManager::UnregisterCommands() 53 | { 54 | FActorLockerCommands::Unregister(); 55 | } 56 | 57 | void UActorLockerCommandManager::LockObject() 58 | { 59 | GEditor->BeginTransaction(FText::FromString(TEXT("Lock object"))); 60 | SetLockActors(true); 61 | GEditor->EndTransaction(); 62 | } 63 | 64 | void UActorLockerCommandManager::UnlockObject() 65 | { 66 | GEditor->BeginTransaction(FText::FromString(TEXT("Unlock object"))); 67 | SetLockActors(false); 68 | GEditor->EndTransaction(); 69 | } 70 | 71 | void UActorLockerCommandManager::LockAllObjects() 72 | { 73 | GEditor->BeginTransaction(FText::FromString(TEXT("Lock all objects"))); 74 | SetLockAllActors(true); 75 | GEditor->EndTransaction(); 76 | } 77 | 78 | void UActorLockerCommandManager::UnlockAllObjects() 79 | { 80 | GEditor->BeginTransaction(FText::FromString(TEXT("Unlock all objects"))); 81 | SetLockAllActors(false); 82 | GEditor->EndTransaction(); 83 | } 84 | 85 | void UActorLockerCommandManager::ToggleLockedObjects() 86 | { 87 | GEditor->BeginTransaction(FText::FromString(TEXT("Toggle locked objects"))); 88 | 89 | const auto ActorLockerManager = UActorLockerManager::GetActorLockerManager(); 90 | 91 | SaveToTransactionBuffer(ActorLockerManager, false); 92 | ActorLockerManager->ToggleLockedActors(); 93 | 94 | GEditor->EndTransaction(); 95 | } 96 | 97 | bool UActorLockerCommandManager::CanLockObject() 98 | { 99 | return IsValidActorLockerManager(); 100 | } 101 | 102 | bool UActorLockerCommandManager::CanUnlockObject() 103 | { 104 | return IsValidActorLockerManager(); 105 | } 106 | 107 | bool UActorLockerCommandManager::CanLockAllObjects() 108 | { 109 | return IsValidActorLockerManager() && IsValidEditorWorld(); 110 | } 111 | 112 | bool UActorLockerCommandManager::CanUnlockAllObjects() 113 | { 114 | return IsValidActorLockerManager() && IsValidEditorWorld(); 115 | } 116 | 117 | bool UActorLockerCommandManager::CanToggleLockedObjects() 118 | { 119 | return IsValidActorLockerManager(); 120 | } 121 | 122 | void UActorLockerCommandManager::SetLockActors(const bool bInLock) 123 | { 124 | auto ActorLockerManager = UActorLockerManager::GetActorLockerManager(); 125 | SaveToTransactionBuffer(ActorLockerManager, false); 126 | 127 | if (const auto Selection = GEditor->GetSelectedActors()) 128 | { 129 | TArray SelectedActors; 130 | Selection->GetSelectedObjects(SelectedActors); 131 | 132 | for (const auto SelectedActor : SelectedActors) 133 | { 134 | ActorLockerManager->SetLockActor(SelectedActor, bInLock); 135 | } 136 | } 137 | } 138 | 139 | void UActorLockerCommandManager::SetLockAllActors(const bool bInLock) 140 | { 141 | const auto ActorLockerManager = UActorLockerManager::GetActorLockerManager(); 142 | const auto World = GEditor->GetEditorWorldContext().World(); 143 | 144 | SaveToTransactionBuffer(ActorLockerManager, false); 145 | 146 | for (TActorIterator ActorIterator(World); ActorIterator; ++ActorIterator) 147 | { 148 | ActorLockerManager->SetLockActor(*ActorIterator, bInLock); 149 | } 150 | } 151 | 152 | bool UActorLockerCommandManager::IsValidActorLockerManager() 153 | { 154 | const auto ActorLockerManager = UActorLockerManager::GetActorLockerManager(); 155 | return IsValid(ActorLockerManager); 156 | } 157 | 158 | bool UActorLockerCommandManager::IsValidEditorWorld() 159 | { 160 | const auto World = GEditor->GetEditorWorldContext().World(); 161 | return IsValid(World); 162 | } 163 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerCommands.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "ActorLockerCommands.h" 4 | #include "ActorLockerSettings.h" 5 | 6 | #define LOCTEXT_NAMESPACE "ActorLockerPlugin" 7 | 8 | void FActorLockerCommands::RegisterCommands() 9 | { 10 | const auto ActorLockerSettings = GetDefault(); 11 | UI_COMMAND(LockObject, "LockObject", "Lock selected object", EUserInterfaceActionType::Button, ActorLockerSettings->LockObject); 12 | UI_COMMAND(UnlockObject, "UnlockObject", "Unlock selected object", EUserInterfaceActionType::Button, ActorLockerSettings->UnlockObject); 13 | UI_COMMAND(LockAllObjects, "LockAllObjects", "Lock all selected objects", EUserInterfaceActionType::Button, ActorLockerSettings->LockAllObjects); 14 | UI_COMMAND(UnlockAllObjects, "UnlockAllObjects", "Unlock all selected objects", EUserInterfaceActionType::Button, ActorLockerSettings->UnlockAllObjects); 15 | UI_COMMAND(ToggleLockedObjects, "ToggleLockedObjects", "Toggle state of locked objects", EUserInterfaceActionType::Button, ActorLockerSettings->ToggleLockedObjects); 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerEditorMode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | 4 | #include "ActorLockerEditorMode.h" 5 | #include "ActorLockerManager.h" 6 | #include "ActorLockerSettings.h" 7 | #include "LevelEditor.h" 8 | #include "Selection.h" 9 | #include "SOutlinerTreeView.h" 10 | #include "SSceneOutliner.h" 11 | #include "Stats/StatsMisc.h" 12 | #include "ActorLocker.h" 13 | #include "EngineUtils.h" 14 | 15 | #if OLDER_THAN_UE_5_1 16 | #include "InstancedFoliageActor.h" 17 | #endif 18 | 19 | 20 | #define LOCTEXT_NAMESPACE "FSampleToolsEditorMode" 21 | 22 | const FEditorModeID UActorLockerEditorMode::EM_ActorLockerEditorModeId = TEXT("EM_SampleToolsEditorMode"); 23 | 24 | 25 | UActorLockerEditorMode::UActorLockerEditorMode() 26 | { 27 | Info = FEditorModeInfo( 28 | EM_ActorLockerEditorModeId, 29 | LOCTEXT("ActorLockerEditorModeName", "ActorLockerEditorMode"), 30 | FSlateIcon(), 31 | false, 0); 32 | 33 | UpdateWidgetTypes(); 34 | } 35 | 36 | void UActorLockerEditorMode::Enter() 37 | { 38 | UEdMode::Enter(); 39 | 40 | if (GetDefault()->bSelectLockedActorsInOutliner) 41 | { 42 | RegisterEvent(); 43 | } 44 | } 45 | 46 | void UActorLockerEditorMode::Exit() 47 | { 48 | if (GetDefault()->bSelectLockedActorsInOutliner) 49 | { 50 | UnregisterEvent(); 51 | } 52 | 53 | Super::Exit(); 54 | } 55 | 56 | bool UActorLockerEditorMode::IsCompatibleWith(FEditorModeID OtherModeID) const 57 | { 58 | const TSet& CompatibleModes = GetDefault()->CompatibleModes; 59 | const auto bCompatible = CompatibleModes.Contains(OtherModeID); 60 | return bCompatible; 61 | } 62 | 63 | bool UActorLockerEditorMode::IsSelectionDisallowed(AActor* InActor, bool bInSelection) const 64 | { 65 | const auto Id = InActor->GetUniqueID(); 66 | if (SelectedItems.Contains(Id)) 67 | { 68 | return false; 69 | } 70 | 71 | if (!bInSelection) 72 | { 73 | return false; 74 | } 75 | 76 | const auto LockerManager = UActorLockerManager::GetActorLockerManager(); 77 | const auto bLocked = IsValid(LockerManager) && LockerManager->IsActorLocked(InActor); 78 | const auto bLevelBrushActor = IsValid(InActor) && InActor->GetLevel()->GetDefaultBrush() == InActor; 79 | 80 | #if OLDER_THAN_UE_5_1 81 | const auto bFoliageActor = InActor->GetLevel()->InstancedFoliageActor == InActor; 82 | const auto bDenied = bLocked || bLevelBrushActor || bFoliageActor; 83 | #else 84 | const auto bDenied = bLocked || bLevelBrushActor; 85 | #endif 86 | 87 | return bDenied; 88 | } 89 | 90 | void UActorLockerEditorMode::RegisterEvent() 91 | { 92 | FSlateDebugging::RegisterWidgetInputRoutingEvent(this); 93 | } 94 | 95 | void UActorLockerEditorMode::UpdateWidgetTypes() 96 | { 97 | const auto Settings = GetDefault(); 98 | OutlinerWidgetTypes = Settings->OutlinerWidgetTypes; 99 | MenuWidgetTypes = Settings->MenuWidgetTypes; 100 | LockerWidgetTypes = Settings->LockerWidgetTypes; 101 | IgnoredWidgetTypes = Settings->IgnoredWidgetTypes; 102 | } 103 | 104 | void UActorLockerEditorMode::UnregisterEvent() 105 | { 106 | FSlateDebugging::UnregisterWidgetInputRoutingEvent(this); 107 | } 108 | 109 | void UActorLockerEditorMode::OnProcessInput(ESlateDebuggingInputEvent InputEventType, const FInputEvent& Event) 110 | { 111 | if (InputEventType == ESlateDebuggingInputEvent::MouseButtonDown && Event.IsPointerEvent()) 112 | { 113 | const auto Modifiers = Event.GetModifierKeys(); 114 | if (!Modifiers.IsControlDown()) 115 | { 116 | SelectedItems.Reset(); 117 | } 118 | 119 | const auto WidgetPath = GetWidgetPath(Event); 120 | 121 | uint32 ItemId = 0; 122 | InteractionType = GetInteractionType(WidgetPath, ItemId); 123 | 124 | switch (InteractionType) 125 | { 126 | case EActorLockerInteractionType::None: 127 | { 128 | CheckLockedActorsSelection(); 129 | break; 130 | } 131 | case EActorLockerInteractionType::Outliner: 132 | { 133 | SelectedItems.Add(ItemId); 134 | break; 135 | } 136 | case EActorLockerInteractionType::Ignored: break; 137 | default: checkNoEntry(); 138 | } 139 | } 140 | } 141 | 142 | bool UActorLockerEditorMode::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) 143 | { 144 | if (Click.GetKey() != EKeys::LeftMouseButton) 145 | { 146 | return false; 147 | } 148 | 149 | if (!IsAppropriateProxy(HitProxy)) 150 | { 151 | return ILegacyEdModeViewportInterface::HandleClick(InViewportClient, HitProxy, Click); 152 | } 153 | 154 | if (!Click.IsControlDown()) 155 | { 156 | GEditor->GetSelectedActors()->DeselectAll(); 157 | } 158 | 159 | return SelectFirstUnlockedActor(InViewportClient, Click); 160 | } 161 | 162 | FWidgetPath UActorLockerEditorMode::GetWidgetPath(const FInputEvent& Event) const 163 | { 164 | const auto& PointerEvent = StaticCast(Event); 165 | const auto Position = PointerEvent.GetScreenSpacePosition(); 166 | 167 | auto& SlateApplication = FSlateApplication::Get(); 168 | const auto Windows = SlateApplication.GetInteractiveTopLevelWindows(); 169 | 170 | return FSlateApplication::Get().LocateWindowUnderMouse(Position, Windows);; 171 | } 172 | 173 | EActorLockerInteractionType UActorLockerEditorMode::GetInteractionType(const FWidgetPath& Path, uint32& OutItemId) const 174 | { 175 | if (!Path.IsValid()) 176 | { 177 | return EActorLockerInteractionType::None; 178 | } 179 | 180 | auto bResult = EActorLockerInteractionType::None; 181 | const auto& Widgets = Path.Widgets.GetInternalArray(); 182 | TSharedPtr Row = nullptr; 183 | TSharedPtr Tree = nullptr; 184 | 185 | for (const auto& ArrangedWidget : Widgets) 186 | { 187 | const auto Type = ArrangedWidget.Widget->GetType(); 188 | 189 | if (InteractionType == EActorLockerInteractionType::Outliner && MenuWidgetTypes.Contains(Type)) 190 | { 191 | bResult = EActorLockerInteractionType::Outliner; 192 | break; 193 | } 194 | 195 | if (LockerWidgetTypes.Contains(Type)) 196 | { 197 | bResult = EActorLockerInteractionType::None; 198 | break; 199 | } 200 | 201 | if (IgnoredWidgetTypes.Contains(Type)) 202 | { 203 | bResult = EActorLockerInteractionType::Ignored; 204 | break; 205 | } 206 | 207 | if (!Row.IsValid() && Type == TEXT("SSceneOutlinerTreeRow")) 208 | { 209 | Row = StaticCastSharedRef(ArrangedWidget.Widget); 210 | } 211 | 212 | if (!Tree.IsValid() && Type == TEXT("SSceneOutlinerTreeView")) 213 | { 214 | Tree = StaticCastSharedRef(ArrangedWidget.Widget); 215 | } 216 | 217 | if (Row && Tree) 218 | { 219 | const TSharedPtr* Item = Tree->ItemFromWidget(Row.Get()); 220 | const auto Id = FLockerTreeItem::GetId(Item->Get()->AsWeak()); 221 | OutItemId = Id; 222 | bResult = EActorLockerInteractionType::Outliner; 223 | } 224 | } 225 | 226 | return bResult; 227 | } 228 | 229 | void UActorLockerEditorMode::CheckLockedActorsSelection() const 230 | { 231 | const auto LockerManager = UActorLockerManager::GetActorLockerManager(); 232 | const auto Selection = GEditor->GetSelectedActors(); 233 | 234 | TSet ActorsToDeselect; 235 | for (FSelectionIterator Iter(*Selection); Iter; ++Iter) 236 | { 237 | const auto Actor = Cast(*Iter); 238 | if (LockerManager->IsActorLocked(Actor)) 239 | { 240 | ActorsToDeselect.Add(Actor); 241 | } 242 | } 243 | 244 | for (const auto ActorToDeselect : ActorsToDeselect) 245 | { 246 | Selection->Deselect(ActorToDeselect); 247 | } 248 | } 249 | 250 | bool UActorLockerEditorMode::IsAppropriateProxy(HHitProxy* HitProxy) const 251 | { 252 | const auto ActorHitProxy = HitProxyCast(HitProxy); 253 | if (!ActorHitProxy) 254 | { 255 | return false; 256 | } 257 | 258 | const auto LockerManager = UActorLockerManager::GetActorLockerManager(); 259 | const auto ClickedActor = ActorHitProxy->Actor; 260 | return IsValid(ClickedActor) && LockerManager->IsActorLocked(ClickedActor); 261 | } 262 | 263 | bool UActorLockerEditorMode::SelectFirstUnlockedActor(const FEditorViewportClient* InViewportClient, const FViewportClick& Click) const 264 | { 265 | const auto World = InViewportClient->GetWorld(); 266 | const auto bOrtho = InViewportClient->IsOrtho(); 267 | const auto Length = bOrtho ? InViewportClient->GetOrthoZoom() : HALF_WORLD_MAX1; 268 | const auto Direction = Click.GetDirection(); 269 | 270 | const auto ClickOrigin = Click.GetOrigin(); 271 | const auto ViewportType = InViewportClient->GetViewportType(); 272 | const auto Start = GetTraceStart(ClickOrigin, Direction, Length, ViewportType); 273 | const auto End = GetTraceEnd(Start, Direction, Length); 274 | 275 | TArray HitResults; 276 | FCollisionQueryParams QueryParams = FCollisionQueryParams::DefaultQueryParam; 277 | QueryParams.bTraceComplex = true; 278 | World->LineTraceMultiByObjectType(HitResults, Start, End, FCollisionObjectQueryParams::AllObjects, QueryParams); 279 | 280 | const auto LockerManager = UActorLockerManager::GetActorLockerManager(); 281 | for (const auto& HitResult : HitResults) 282 | { 283 | const auto HitActor = HitResult.GetActor(); 284 | if (IsValid(HitActor) && !LockerManager->IsActorLocked(HitActor)) 285 | { 286 | GEditor->SelectActor(HitActor, true, true); 287 | return true; 288 | } 289 | } 290 | 291 | return false; 292 | } 293 | 294 | FVector UActorLockerEditorMode::GetTraceStart(const FVector& InClickOrigin, const FVector& InDirection, const float InOrthoHeight, const ELevelViewportType InViewportType) const 295 | { 296 | auto NewClickOrigin = InClickOrigin; 297 | switch (InViewportType) 298 | { 299 | case LVT_OrthoXY: 300 | case LVT_OrthoXZ: 301 | case LVT_OrthoYZ: 302 | case LVT_OrthoNegativeXY: 303 | case LVT_OrthoNegativeXZ: 304 | case LVT_OrthoNegativeYZ: 305 | { 306 | NewClickOrigin.X = FMath::IsNearlyZero(InDirection.X) ? NewClickOrigin.X : -InOrthoHeight * InDirection.X; 307 | NewClickOrigin.Y = FMath::IsNearlyZero(InDirection.Y) ? NewClickOrigin.Y : -InOrthoHeight * InDirection.Y; 308 | NewClickOrigin.Z = FMath::IsNearlyZero(InDirection.Z) ? NewClickOrigin.Z : -InOrthoHeight * InDirection.Z; 309 | break; 310 | } 311 | case LVT_Perspective: 312 | case LVT_OrthoFreelook: 313 | { 314 | break; 315 | } 316 | default: checkNoEntry(); 317 | } 318 | 319 | return NewClickOrigin; 320 | } 321 | 322 | FVector UActorLockerEditorMode::GetTraceEnd(const FVector& InStart, const FVector& InDirection, const float InLength) const 323 | { 324 | const auto End = FVector( 325 | FMath::IsNearlyZero(InDirection.X) ? InStart.X : InDirection.X * InLength, 326 | FMath::IsNearlyZero(InDirection.Y) ? InStart.Y : InDirection.Y * InLength, 327 | FMath::IsNearlyZero(InDirection.Z) ? InStart.Z : InDirection.Z * InLength 328 | ); 329 | 330 | return End; 331 | } 332 | 333 | 334 | #undef LOCTEXT_NAMESPACE 335 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "ActorLockerManager.h" 4 | #include "ActorLocker.h" 5 | #include "ActorLockerEditorMode.h" 6 | #include "ActorLockerSettings.h" 7 | #include "ActorLockerTypes.h" 8 | #include "ActorTreeItem.h" 9 | #include "EditorModeManager.h" 10 | #include "LevelTreeItem.h" 11 | #include "Selection.h" 12 | 13 | void UActorLockerManager::PostInitProperties() 14 | { 15 | UObject::PostInitProperties(); 16 | 17 | if (HasAnyFlags(RF_ClassDefaultObject)) 18 | { 19 | return; 20 | } 21 | 22 | SetFlags(RF_Transactional); 23 | 24 | GEngine->OnLevelActorDeleted().AddUObject(this, &UActorLockerManager::OnActorDeleted); 25 | FSlateDebugging::InputEvent.AddUObject(this, &UActorLockerManager::OnInputEvent); 26 | 27 | FEditorModeTools& Tools = GLevelEditorModeTools(); 28 | if (!Tools.IsDefaultMode(UActorLockerEditorMode::EM_ActorLockerEditorModeId)) 29 | { 30 | Tools.AddDefaultMode(UActorLockerEditorMode::EM_ActorLockerEditorModeId); 31 | } 32 | 33 | Settings = GetDefault(); 34 | } 35 | 36 | void UActorLockerManager::BeginDestroy() 37 | { 38 | GEngine->OnLevelActorDeleted().RemoveAll(this); 39 | 40 | UObject::BeginDestroy(); 41 | } 42 | 43 | void UActorLockerManager::Serialize(FArchive& Ar) 44 | { 45 | UObject::Serialize(Ar); 46 | 47 | if (Ar.IsTransacting()) 48 | { 49 | Ar << Items; 50 | } 51 | } 52 | 53 | void UActorLockerManager::InitItem(const TWeakPtr& InTreeItem) 54 | { 55 | const auto Id = FLockerTreeItem::GetId(InTreeItem); 56 | 57 | FLockerTreeItem NewItem(InTreeItem); 58 | if (Items.Contains(Id)) 59 | { 60 | const auto& OutdatedItem = Items[Id]; 61 | NewItem.bLocked = OutdatedItem.bLocked; 62 | } 63 | 64 | Items.Add(Id, NewItem); 65 | 66 | if (InTreeItem.Pin()->IsA()) 67 | { 68 | const auto ActorItem = InTreeItem.Pin()->CastTo(); 69 | if (ActorItem->Actor.IsValid() && ActorItem->Actor->ActorHasTag(Settings->LockedTag)) 70 | { 71 | SetLockTreeItem(InTreeItem, true); 72 | } 73 | } 74 | } 75 | 76 | void UActorLockerManager::SetLockActor(AActor* InActor, const bool bInLock, const bool bPropagateToChildren) 77 | { 78 | if (!IsValid(InActor)) 79 | { 80 | return; 81 | } 82 | 83 | for (auto It = Items.CreateConstIterator(); It; ++It) 84 | { 85 | const auto& Item = It.Value(); 86 | if (!Item.IsValid()) 87 | { 88 | continue; 89 | } 90 | 91 | if (const auto ActorTreeItem = Item.NativeItem.Pin()->CastTo()) 92 | { 93 | if (ActorTreeItem->Actor == InActor) 94 | { 95 | SetLockTreeItem(Item, bInLock, bPropagateToChildren); 96 | return; 97 | } 98 | } 99 | } 100 | } 101 | 102 | void UActorLockerManager::ToggleLockedActors() 103 | { 104 | // We need temp variable because ToggledItems set is cleared when user lock actor (@see SetLockTreeItem) 105 | TSet ToggledItemsTemp; 106 | 107 | if (ToggledItems.Num() <= 0) 108 | { 109 | for (auto It = Items.CreateConstIterator(); It; ++It) 110 | { 111 | const auto& Item = It.Value(); 112 | if (Item.bLocked) 113 | { 114 | ToggledItemsTemp.Add(Item.GetId()); 115 | } 116 | } 117 | 118 | for (const auto Id : ToggledItemsTemp) 119 | { 120 | SetLockTreeItem(Items[Id], false, false); 121 | } 122 | 123 | ToggledItems = ToggledItemsTemp; 124 | } 125 | else 126 | { 127 | ToggledItemsTemp = ToggledItems; 128 | for (const auto Id : ToggledItemsTemp) 129 | { 130 | SetLockTreeItem(Items[Id], true, false); 131 | } 132 | 133 | ToggledItems.Empty(); 134 | } 135 | } 136 | 137 | void UActorLockerManager::SetLockTreeItem(const TWeakPtr& InTreeItem, const bool bInLock, const bool bPropagateToChildren) 138 | { 139 | const auto Id = FLockerTreeItem::GetId(InTreeItem); 140 | 141 | if (Id == 0) 142 | { 143 | return; 144 | } 145 | 146 | if (!Items.Contains(Id) || !Items[Id].IsValid()) 147 | { 148 | InitItem(InTreeItem); 149 | } 150 | 151 | if (Items[Id].bLocked == bInLock) 152 | { 153 | return; 154 | } 155 | 156 | Items[Id].bLocked = bInLock; 157 | 158 | ToggledItems.Empty(); 159 | 160 | if (const auto ActorItem = InTreeItem.Pin()->CastTo()) 161 | { 162 | if (const auto Actor = ActorItem->Actor.Get()) 163 | { 164 | SaveToTransactionBuffer(Actor, false); 165 | 166 | Actor->SetLockLocation(bInLock); 167 | 168 | const auto ActorLockerSettings = GetDefault(); 169 | 170 | UpdateTagState(Actor, bInLock); 171 | 172 | if (bInLock && ActorLockerSettings->bDeselectActorOnLock) 173 | { 174 | if (const auto Selection = GEditor->GetSelectedActors()) 175 | { 176 | Selection->Deselect(Actor); 177 | } 178 | } 179 | } 180 | } 181 | 182 | if (bPropagateToChildren) 183 | { 184 | for (auto& ChildPtr : InTreeItem.Pin()->GetChildren()) 185 | { 186 | auto Child = ChildPtr.Pin(); 187 | if (Child.IsValid()) 188 | { 189 | SetLockTreeItem(Child, bInLock); 190 | } 191 | } 192 | } 193 | 194 | CheckParentLock(InTreeItem); 195 | } 196 | 197 | void UActorLockerManager::UnlockById(const uint32 InId) 198 | { 199 | if (!Items.Contains(InId)) 200 | { 201 | return; 202 | } 203 | 204 | const auto Item = Items[InId]; 205 | SetLockTreeItem(Item, false); 206 | } 207 | 208 | bool UActorLockerManager::IsActorLocked(const AActor* InActor) const 209 | { 210 | if (!IsValid(InActor)) 211 | { 212 | return false; 213 | } 214 | 215 | const auto ActorId = InActor->GetUniqueID(); 216 | return Items.Contains(ActorId) && Items[ActorId].bLocked; 217 | } 218 | 219 | bool UActorLockerManager::IsItemLocked(const TWeakPtr& InTreeItem) const 220 | { 221 | const auto Id = FLockerTreeItem::GetId(InTreeItem); 222 | return Id == 0 ? false : Items.Contains(Id) && Items[Id].bLocked; 223 | } 224 | 225 | void UActorLockerManager::CheckParentLock(const TWeakPtr& InTreeItem) 226 | { 227 | const auto ParentTreeItem = InTreeItem.Pin()->GetParent(); 228 | if (!ParentTreeItem.IsValid()) 229 | { 230 | return; 231 | } 232 | 233 | if (ParentTreeItem->IsA()) 234 | { 235 | CheckParentLock(ParentTreeItem); 236 | return; 237 | } 238 | 239 | const auto bLocked = IsItemLocked(ParentTreeItem); 240 | const auto bAllChildrenLocked = !IsAnyChildUnlocked(ParentTreeItem); 241 | 242 | if (bLocked != bAllChildrenLocked) 243 | { 244 | SetLockTreeItem(ParentTreeItem, bAllChildrenLocked, false); 245 | } 246 | } 247 | 248 | bool UActorLockerManager::IsAnyChildUnlocked(const TWeakPtr& InParentTreeItem) const 249 | { 250 | if (!InParentTreeItem.IsValid()) 251 | { 252 | return false; 253 | } 254 | 255 | for (auto& ChildPtr : InParentTreeItem.Pin()->GetChildren()) 256 | { 257 | auto Child = ChildPtr.Pin(); 258 | if (Child.IsValid()) 259 | { 260 | if (!IsItemLocked(Child)) 261 | { 262 | return true; 263 | } 264 | 265 | if (Child->GetChildren().Num() >= 0 && IsAnyChildUnlocked(Child)) 266 | { 267 | return true; 268 | } 269 | } 270 | } 271 | 272 | return false; 273 | } 274 | 275 | void UActorLockerManager::UpdateLockState() 276 | { 277 | TSet> CheckedParents; 278 | TSet> ItemsToCheck; 279 | for (auto It = Items.CreateConstIterator(); It; ++It) 280 | { 281 | const auto Item = It.Value(); 282 | if (!Item.IsValid()) 283 | { 284 | continue; 285 | } 286 | 287 | const auto ItemPtr = Item.NativeItem.Pin(); 288 | const auto ParentItemPtr = ItemPtr->GetParent(); 289 | 290 | if (!ParentItemPtr.IsValid() || CheckedParents.Contains(ParentItemPtr)) 291 | { 292 | continue; 293 | } 294 | 295 | CheckedParents.Add(ParentItemPtr); 296 | ItemsToCheck.Add(Item); 297 | } 298 | 299 | for (const auto& Item : ItemsToCheck) 300 | { 301 | CheckParentLock(Item); 302 | } 303 | } 304 | 305 | TSet UActorLockerManager::GetLockedActors() const 306 | { 307 | TSet LockedActors; 308 | 309 | for (auto It = Items.CreateConstIterator(); It; ++It) 310 | { 311 | const auto Item = It.Value(); 312 | if (Item.IsValid()) 313 | { 314 | const auto TreeItemPtr = Item.NativeItem.Pin(); 315 | if (const auto ActorTreeItem = TreeItemPtr->CastTo()) 316 | { 317 | if (ActorTreeItem->Actor.IsValid()) 318 | { 319 | LockedActors.Add(ActorTreeItem->Actor.Get()); 320 | } 321 | else 322 | { 323 | UE_LOG(LogActorLockerManager, Error, TEXT("Failed to get actor from tree item: %s"), *TreeItemPtr->GetDisplayString()); 324 | } 325 | } 326 | } 327 | else 328 | { 329 | UE_LOG(LogActorLockerManager, Error, TEXT("Item with key %u is invalid. Did we forget to remove it?"), It.Key()); 330 | } 331 | } 332 | 333 | return LockedActors; 334 | } 335 | 336 | UActorLockerManager* UActorLockerManager::GetActorLockerManager() 337 | { 338 | auto& ActorLockerModule = FModuleManager::GetModuleChecked("ActorLocker"); 339 | return ActorLockerModule.GetActorLockerManager().Get(); 340 | } 341 | 342 | void UActorLockerManager::UpdateTagState(AActor* const Actor, const bool bInLock) 343 | { 344 | const auto TagExists = Actor->ActorHasTag(Settings->LockedTag); 345 | if (bInLock && Settings->bSaveLockedState && !TagExists) 346 | { 347 | Actor->Tags.AddUnique(Settings->LockedTag); 348 | Actor->Modify(); 349 | } 350 | else if (!bInLock && TagExists) 351 | { 352 | Actor->Tags.Remove(Settings->LockedTag); 353 | Actor->Modify(); 354 | } 355 | } 356 | 357 | void UActorLockerManager::OnActorDeleted(AActor* InActor) 358 | { 359 | const auto Id = InActor->GetUniqueID(); 360 | Items.Remove(Id); 361 | } 362 | 363 | void UActorLockerManager::OnPostTick(float InDeltaTime) 364 | { 365 | FSlateApplication::Get().OnPostTick().RemoveAll(this); 366 | UpdateLockState(); 367 | } 368 | 369 | void UActorLockerManager::OnInputEvent(const FSlateDebuggingInputEventArgs& SlateDebuggingInputEventArgs) 370 | { 371 | if (SlateDebuggingInputEventArgs.InputEventType != ESlateDebuggingInputEvent::DragDrop) 372 | { 373 | return; 374 | } 375 | 376 | const auto Widget = SlateDebuggingInputEventArgs.HandlerWidget; 377 | if (!Widget.IsValid() || Widget->GetType() != TEXT("SSceneOutlinerTreeRow") && Widget->GetType() != TEXT("SSceneOutlinerTreeView")) 378 | { 379 | return; 380 | } 381 | 382 | if (!FSlateApplication::Get().OnPostTick().IsBoundToObject(this)) 383 | { 384 | FSlateApplication::Get().OnPostTick().AddUObject(this, &UActorLockerManager::OnPostTick); 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerMenuExtender.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "ActorLockerMenuExtender.h" 4 | #include "ActorLockerCommandManager.h" 5 | #include "ActorLockerCommands.h" 6 | #include "ActorLockerManager.h" 7 | #include "ActorLockerStyle.h" 8 | #include "LevelEditor.h" 9 | 10 | #define LOCTEXT_NAMESPACE "ActorLockerMenuExtender" 11 | 12 | void FActorLockerMenuExtender::AddLevelViewportMenuExtender() 13 | { 14 | auto& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); 15 | auto& ExtenderDelegates = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); 16 | ExtenderDelegates.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FActorLockerMenuExtender::OnExtendLevelEditor)); 17 | MenuExtenderDelegateHandle = ExtenderDelegates.Last().GetHandle(); 18 | } 19 | 20 | void FActorLockerMenuExtender::RemoveLevelViewportMenuExtender() 21 | { 22 | if (!MenuExtenderDelegateHandle.IsValid()) 23 | { 24 | return; 25 | } 26 | 27 | if (const auto LevelEditorModule = FModuleManager::GetModulePtr("LevelEditor")) 28 | { 29 | LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll([MenuExtenderDelegateHandle = MenuExtenderDelegateHandle](const auto& In) { return In.GetHandle() == MenuExtenderDelegateHandle; }); 30 | } 31 | } 32 | 33 | TSharedRef FActorLockerMenuExtender::OnExtendLevelEditor(const TSharedRef CommandList, const TArray SelectedActors) 34 | { 35 | TSharedRef Extender(new FExtender()); 36 | 37 | if (SelectedActors.Num() > 0) 38 | { 39 | Extender->AddMenuExtension( 40 | "ActorTypeTools", 41 | EExtensionHook::After, 42 | CommandList, 43 | FMenuExtensionDelegate::CreateRaw(this, &FActorLockerMenuExtender::CreateActorLockerMenu, SelectedActors) 44 | ); 45 | } 46 | 47 | return Extender; 48 | } 49 | 50 | void FActorLockerMenuExtender::CreateActorLockerMenu(FMenuBuilder& MenuBuilder, TArray Actors) 51 | { 52 | const auto ActorLockerManager = UActorLockerManager::GetActorLockerManager(); 53 | if (!IsValid(ActorLockerManager)) 54 | { 55 | return; 56 | } 57 | 58 | const auto& Commands = FActorLockerCommands::Get(); 59 | 60 | auto bAllUnlocked = true; 61 | for (const auto Actor : Actors) 62 | { 63 | if (ActorLockerManager->IsActorLocked(Actor)) 64 | { 65 | bAllUnlocked = false; 66 | break; 67 | } 68 | } 69 | 70 | if (bAllUnlocked) 71 | { 72 | MenuBuilder.AddMenuEntry 73 | ( 74 | Commands.LockObject, 75 | TEXT("ActorLocker"), 76 | LOCTEXT("Lock", "Lock selection"), 77 | LOCTEXT("Lock_Tooltip", "Lock selected actors"), 78 | FSlateIcon(FActorLockerStyle::Get().GetStyleSetName(), "SceneOutliner.Lock") 79 | ); 80 | } 81 | else 82 | { 83 | MenuBuilder.AddMenuEntry 84 | ( 85 | Commands.UnlockObject, 86 | TEXT("ActorLocker"), 87 | LOCTEXT("Unlock", "Unlock selection"), 88 | LOCTEXT("Unlock_Tooltip", "Unlock selected actors"), 89 | FSlateIcon(FActorLockerStyle::Get().GetStyleSetName(), "SceneOutliner.Unlock") 90 | ); 91 | } 92 | 93 | MenuBuilder.AddMenuEntry( 94 | Commands.ToggleLockedObjects, 95 | TEXT("ActorLocker"), 96 | LOCTEXT("ToggleLock", "Toggle locked actors state"), 97 | LOCTEXT("ToggleLock_Tooltip", "Toggle lock for locked actors"), 98 | FSlateIcon(FActorLockerStyle::Get().GetStyleSetName(), "SceneOutliner.Lock") 99 | ); 100 | } 101 | 102 | #undef LOCTEXT_NAMESPACE 103 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerPluginStateService.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | 4 | #include "ActorLockerPluginStateService.h" 5 | #include "ActorLockerSettings.h" 6 | #include "IPythonScriptPlugin.h" 7 | #include "LevelEditor.h" 8 | #include "ProjectDescriptor.h" 9 | #include "Interfaces/IPluginManager.h" 10 | #include "Interfaces/IProjectManager.h" 11 | 12 | 13 | void UActorLockerPluginStateService::PostInitProperties() 14 | { 15 | UObject::PostInitProperties(); 16 | 17 | if (HasAnyFlags(RF_ClassDefaultObject)) 18 | { 19 | return; 20 | } 21 | 22 | auto& PluginManager = IPluginManager::Get(); 23 | Plugin = PluginManager.FindPlugin(TEXT("ActorLocker")); 24 | } 25 | 26 | void UActorLockerPluginStateService::Tick(float DeltaTime) 27 | { 28 | const auto& ProjectManager = IProjectManager::Get(); 29 | const auto* Descriptor = ProjectManager.GetCurrentProject(); 30 | 31 | if (!Descriptor) 32 | { 33 | return; 34 | } 35 | 36 | const FPluginReferenceDescriptor* PluginDescriptor = nullptr; 37 | for (const auto& PluginReferenceDescriptor : Descriptor->Plugins) 38 | { 39 | if (PluginReferenceDescriptor.Name == TEXT("ActorLocker")) 40 | { 41 | PluginDescriptor = &PluginReferenceDescriptor; 42 | } 43 | } 44 | 45 | if (!PluginDescriptor) 46 | { 47 | if (Plugin.IsValid()) 48 | { 49 | bLastPluginState = Plugin->IsEnabled(); 50 | } 51 | 52 | return; 53 | } 54 | 55 | const auto bEnabled = Plugin.IsValid() && Plugin->IsEnabled() && PluginDescriptor->bEnabled; 56 | const auto bStateChanged = bEnabled != bLastPluginState; 57 | 58 | if (bStateChanged && !bEnabled) 59 | { 60 | const auto& Message = GetDefault()->PluginStateServiceWarningMessage; 61 | const auto Answer = FMessageDialog::Open(EAppMsgType::YesNo, Message); 62 | if (Answer == EAppReturnType::Yes) 63 | { 64 | const auto& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); 65 | const auto& LevelEditorTab = LevelEditorModule.GetLevelEditorTab(); 66 | LevelEditorTab->ActivateInParent(ETabActivationCause::SetDirectly); 67 | 68 | const auto& ScriptFileName = GetDefault()->ScriptFileName; 69 | IPythonScriptPlugin::Get()->ExecPythonCommand(*ScriptFileName); 70 | } 71 | } 72 | 73 | bLastPluginState = bEnabled; 74 | } 75 | 76 | bool UActorLockerPluginStateService::IsTickableWhenPaused() const 77 | { 78 | return true; 79 | } 80 | 81 | bool UActorLockerPluginStateService::IsTickableInEditor() const 82 | { 83 | return true; 84 | } 85 | 86 | bool UActorLockerPluginStateService::IsTickable() const 87 | { 88 | return Plugin.IsValid(); 89 | } 90 | 91 | TStatId UActorLockerPluginStateService::GetStatId() const 92 | { 93 | RETURN_QUICK_DECLARE_CYCLE_STAT(UActorLockerPluginStateService, STATGROUP_Tickables); 94 | } 95 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerPythonCommand.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | 4 | #include "ActorLockerPythonCommand.h" 5 | #include "Subsystems/UnrealEditorSubsystem.h" 6 | 7 | UWorld* UActorLockerPythonCommand::GetWorld() const 8 | { 9 | return GEditor->GetEditorSubsystem()->GetEditorWorld(); 10 | } 11 | 12 | void UActorLockerPythonCommand::AddObjectToRoot() 13 | { 14 | AddToRoot(); 15 | } 16 | 17 | void UActorLockerPythonCommand::RemoveObjectFromRoot() 18 | { 19 | RemoveFromRoot(); 20 | } 21 | 22 | void UActorLockerPythonCommand::SetEditorTimer(UObject* Object, FString FunctionName, float Time, bool bLooping, float InitialStartDelay) 23 | { 24 | FTimerHandle TimerHandle; 25 | FTimerDelegate TimerDelegate; 26 | TimerDelegate.BindUFunction(Object, FName(*FunctionName)); 27 | GEditor->GetTimerManager()->SetTimer(TimerHandle, TimerDelegate, Time, bLooping, InitialStartDelay); 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerSettings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | 4 | #include "ActorLockerSettings.h" 5 | #include "ActorLockerEditorMode.h" 6 | #include "EditorModeManager.h" 7 | #include "EditorModes.h" 8 | 9 | UActorLockerSettings::UActorLockerSettings() 10 | { 11 | LockedTag = TEXT("ActorLocked"); 12 | CompatibleModes = { 13 | FBuiltinEditorModes::EM_None, 14 | FBuiltinEditorModes::EM_Default, 15 | FBuiltinEditorModes::EM_MeshPaint, 16 | TEXT("EM_ModelingToolsEditorMode"), 17 | TEXT("EM_FractureEditorMode"), 18 | TEXT("EM_Geometry"), 19 | }; 20 | 21 | LockObject = FInputChord(EKeys::Comma, EModifierKey::Alt); 22 | UnlockObject = FInputChord(EKeys::Period, EModifierKey::Alt); 23 | LockAllObjects = FInputChord(EKeys::Comma, EModifierKey::Alt | EModifierKey::Shift); 24 | UnlockAllObjects = FInputChord(EKeys::Period, EModifierKey::Alt | EModifierKey::Shift); 25 | ToggleLockedObjects = FInputChord(EKeys::Slash, EModifierKey::Alt); 26 | 27 | OutlinerWidgetTypes = {"SSceneOutlinerTreeRow"}; 28 | MenuWidgetTypes = {"SMenuEntryButton"}; 29 | LockerWidgetTypes = {"SLockWidget"}; 30 | IgnoredWidgetTypes = {"SActorDetails"}; 31 | 32 | PluginStateServiceWarningMessage = FText::FromString(TEXT("Warning: ActorLocker plugin has been disabled. All locked actors will remain in their locked position after restarting. To manually unlock them, go to Context Menu -> \"Transform\" -> \"Lock Actor Movement\". Run a Python script to unlock all actors automatically? Note: It may take significant time due to your project's size.")); 33 | ScriptFileName = TEXT("unlock_all_actors.py"); 34 | } 35 | 36 | void UActorLockerSettings::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) 37 | { 38 | const auto PropertyName = PropertyChangedEvent.Property->GetFName(); 39 | if (GET_MEMBER_NAME_CHECKED(UActorLockerSettings, bSelectLockedActorsInOutliner) == PropertyName) 40 | { 41 | FEditorModeTools& Tools = GLevelEditorModeTools(); 42 | const auto Mode = Cast(Tools.GetActiveScriptableMode(UActorLockerEditorMode::EM_ActorLockerEditorModeId)); 43 | 44 | if (bSelectLockedActorsInOutliner) 45 | { 46 | Mode->RegisterEvent(); 47 | } 48 | else 49 | { 50 | Mode->UnregisterEvent(); 51 | } 52 | } 53 | 54 | if (GET_MEMBER_NAME_CHECKED(UActorLockerSettings, OutlinerWidgetTypes) == PropertyName || 55 | GET_MEMBER_NAME_CHECKED(UActorLockerSettings, MenuWidgetTypes) == PropertyName || 56 | GET_MEMBER_NAME_CHECKED(UActorLockerSettings, LockerWidgetTypes) == PropertyName || 57 | GET_MEMBER_NAME_CHECKED(UActorLockerSettings, IgnoredWidgetTypes) == PropertyName) 58 | { 59 | FEditorModeTools& Tools = GLevelEditorModeTools(); 60 | const auto Mode = Cast(Tools.GetActiveScriptableMode(UActorLockerEditorMode::EM_ActorLockerEditorModeId)); 61 | Mode->UpdateWidgetTypes(); 62 | } 63 | 64 | UObject::PostEditChangeChainProperty(PropertyChangedEvent); 65 | } 66 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerStyle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "ActorLockerStyle.h" 4 | #include "Interfaces/IPluginManager.h" 5 | #include "Styling/SlateStyleRegistry.h" 6 | #include "Slate/SlateGameResources.h" 7 | 8 | #define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 9 | 10 | TSharedPtr FActorLockerStyle::StyleInstance = nullptr; 11 | 12 | void FActorLockerStyle::Initialize() 13 | { 14 | if (!StyleInstance.IsValid()) 15 | { 16 | StyleInstance = Create(); 17 | FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); 18 | } 19 | } 20 | 21 | void FActorLockerStyle::Shutdown() 22 | { 23 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); 24 | ensure(StyleInstance.IsUnique()); 25 | StyleInstance.Reset(); 26 | } 27 | 28 | void FActorLockerStyle::ReloadTextures() 29 | { 30 | if (FSlateApplication::IsInitialized()) 31 | { 32 | FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); 33 | } 34 | } 35 | 36 | const ISlateStyle& FActorLockerStyle::Get() 37 | { 38 | return *StyleInstance; 39 | } 40 | 41 | FName FActorLockerStyle::GetStyleSetName() 42 | { 43 | static FName StyleSetName(TEXT("SceneOutlinerStyle")); 44 | return StyleSetName; 45 | } 46 | 47 | TSharedRef FActorLockerStyle::Create() 48 | { 49 | TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("ActorLockerStyle")); 50 | const auto Plugin = IPluginManager::Get().FindPlugin("ActorLocker"); 51 | const auto ResourceDir = Plugin->GetBaseDir() / TEXT("Resources"); 52 | 53 | Style->SetContentRoot(ResourceDir); 54 | 55 | Style->Set("SceneOutliner.Lock", new IMAGE_BRUSH(TEXT("Lock"), FVector2D(20.f))); 56 | Style->Set("SceneOutliner.Lock.Small", new IMAGE_BRUSH(TEXT("Lock"), FVector2D(16.f))); 57 | Style->Set("SceneOutliner.Unlock", new IMAGE_BRUSH(TEXT("Unlock"), FVector2D(20.f))); 58 | Style->Set("SceneOutliner.Unlock.Small", new IMAGE_BRUSH(TEXT("Unlock"), FVector2D(16.f))); 59 | 60 | return Style; 61 | } 62 | 63 | #undef IMAGE_BRUSH -------------------------------------------------------------------------------- /Source/ActorLocker/Private/ActorLockerTypes.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "ActorLockerTypes.h" 4 | #include "ActorTreeItem.h" 5 | #include "FolderTreeItem.h" 6 | #include "LevelTreeItem.h" 7 | #include "WorldTreeItem.h" 8 | 9 | DEFINE_LOG_CATEGORY(LogActorLockerManager) 10 | DEFINE_LOG_CATEGORY(LogLockWidget) 11 | 12 | uint32 FLockerTreeItem::GetId() const 13 | { 14 | return GetId(NativeItem); 15 | } 16 | 17 | uint32 FLockerTreeItem::GetId(const TWeakPtr& InItem) 18 | { 19 | if (!InItem.IsValid()) 20 | { 21 | return 0; 22 | } 23 | 24 | const auto TreeItemPtr = InItem.Pin(); 25 | if (const auto ActorTreeItem = TreeItemPtr->CastTo()) 26 | { 27 | if (ActorTreeItem->Actor.IsValid()) 28 | { 29 | return ActorTreeItem->Actor->GetUniqueID(); 30 | } 31 | } 32 | else if (const auto LevelTreeItem = TreeItemPtr->CastTo()) 33 | { 34 | if (LevelTreeItem->Level.IsValid()) 35 | { 36 | return LevelTreeItem->Level->GetUniqueID(); 37 | } 38 | } 39 | else if (const auto WorldTreeItem = TreeItemPtr->CastTo()) 40 | { 41 | if (WorldTreeItem->World.IsValid()) 42 | { 43 | return WorldTreeItem->World->GetUniqueID(); 44 | } 45 | } 46 | else if (const auto FolderTreeItem = TreeItemPtr->CastTo()) 47 | { 48 | return FolderTreeItem->GetID().CalculateTypeHash(); 49 | } 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/SLockWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | 4 | #include "SLockWidget.h" 5 | #include "ActorLocker.h" 6 | #include "ActorLockerManager.h" 7 | #include "ActorLockerStyle.h" 8 | #include "ActorTreeItem.h" 9 | #include "ActorLockerTypes.h" 10 | #include "Editor.h" 11 | #include "ISceneOutliner.h" 12 | #include "LevelTreeItem.h" 13 | #include "SceneOutlinerActorLocker.h" 14 | #include "SlateOptMacros.h" 15 | #include "Widgets/Views/STreeView.h" 16 | 17 | 18 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 19 | 20 | void SLockWidget::Construct(const FArguments& InArgs, TWeakPtr InWeakColumn, TWeakPtr InWeakOutliner, TWeakPtr InWeakItem, const STableRow* InRow) 21 | { 22 | WeakColumn = InWeakColumn; 23 | WeakOutliner = InWeakOutliner; 24 | WeakItem = InWeakItem; 25 | Row = InRow; 26 | 27 | auto& Module = FModuleManager::GetModuleChecked("ActorLocker"); 28 | const auto bRequired = true; 29 | WeakActorManager = Module.GetActorLockerManager(bRequired); 30 | OnActorLockerManagerCreatedHandle = Module.OnActorLockerManagerCreated.AddLambda([this](UActorLockerManager* InNewManager) { WeakActorManager = InNewManager; }); 31 | 32 | check(WeakActorManager.IsValid()); 33 | WeakActorManager->InitItem(WeakItem); 34 | 35 | SImage::Construct( 36 | SImage::FArguments() 37 | .IsEnabled(this, &SLockWidget::IsEnabled) 38 | .ColorAndOpacity(this, &SLockWidget::GetForegroundColor) 39 | .Image(this, &SLockWidget::GetBrush) 40 | ); 41 | } 42 | 43 | SLockWidget::~SLockWidget() 44 | { 45 | if (FModuleManager::Get().IsModuleLoaded("ActorLocker")) 46 | { 47 | const auto Module = FModuleManager::GetModulePtr("ActorLocker"); 48 | Module->OnActorLockerManagerCreated.Remove(OnActorLockerManagerCreatedHandle); 49 | } 50 | } 51 | 52 | FSlateColor SLockWidget::GetForegroundColor() const 53 | { 54 | const auto Outliner = WeakOutliner.Pin(); 55 | const auto TreeItem = WeakItem.Pin(); 56 | 57 | const bool bIsSelected = Outliner->GetTree().IsItemSelected(TreeItem.ToSharedRef()); 58 | 59 | if (!IsLocked() && !Row->IsHovered() && !bIsSelected) 60 | { 61 | return FLinearColor::Transparent; 62 | } 63 | else if (IsHovered() && !bIsSelected) 64 | { 65 | return FAppStyle::Get().GetSlateColor("Colors.ForegroundHover"); 66 | } 67 | 68 | return FSlateColor::UseForeground(); 69 | } 70 | 71 | const FSlateBrush* SLockWidget::GetBrush() const 72 | { 73 | if (IsLocked()) 74 | { 75 | return FActorLockerStyle::Get().GetBrush(TEXT("SceneOutliner.Lock")); 76 | } 77 | return FActorLockerStyle::Get().GetBrush(TEXT("SceneOutliner.Unlock")); 78 | } 79 | 80 | bool SLockWidget::IsLocked() const 81 | { 82 | return IsLocked(WeakItem.Pin(), WeakColumn.Pin()); 83 | } 84 | 85 | bool SLockWidget::IsLocked(const TWeakPtr& Item, const TSharedPtr& Column) const 86 | { 87 | return Item.IsValid() && Column.IsValid() ? WeakActorManager.Get()->IsItemLocked(Item) : false; 88 | } 89 | 90 | void SLockWidget::SetIsLocked(const bool bNewLocked) 91 | { 92 | const auto bLocked = IsLocked(); 93 | if (WeakItem.IsValid() && WeakOutliner.IsValid() && WeakActorManager.IsValid() && bLocked != bNewLocked) 94 | { 95 | WeakActorManager.Get()->SetLockTreeItem(WeakItem, bNewLocked); 96 | WeakOutliner.Pin()->Refresh(); 97 | GEditor->RedrawAllViewports(); 98 | } 99 | } 100 | 101 | FReply SLockWidget::HandleClick() 102 | { 103 | if (!IsEnabled()) 104 | { 105 | return FReply::Unhandled(); 106 | } 107 | 108 | const auto Outliner = WeakOutliner.Pin(); 109 | const auto TreeItem = WeakItem.Pin(); 110 | const auto Column = WeakColumn.Pin(); 111 | 112 | if (!Outliner.IsValid() || !TreeItem.IsValid() || !Column.IsValid()) 113 | { 114 | return FReply::Unhandled(); 115 | } 116 | 117 | const auto& Tree = Outliner->GetTree(); 118 | const bool bNewLocked = !IsLocked(); 119 | 120 | const auto TransactionName = bNewLocked ? TEXT("Lock object") : TEXT("Unlock object"); 121 | GEditor->BeginTransaction(FText::FromString(TransactionName)); 122 | SaveToTransactionBuffer(WeakActorManager.Get(), false); 123 | 124 | // We operate on all the selected items if the specified item is selected 125 | if (Tree.IsItemSelected(TreeItem.ToSharedRef())) 126 | { 127 | for (auto& SelectedItem : Tree.GetSelectedItems()) 128 | { 129 | if (IsLocked(SelectedItem, Column) != bNewLocked) 130 | { 131 | WeakActorManager.Get()->SetLockTreeItem(SelectedItem, bNewLocked); 132 | } 133 | } 134 | 135 | GEditor->RedrawAllViewports(); 136 | } 137 | else 138 | { 139 | SetIsLocked(bNewLocked); 140 | } 141 | 142 | GEditor->EndTransaction(); 143 | 144 | return FReply::Handled(); 145 | } 146 | 147 | FReply SLockWidget::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) 148 | { 149 | return HandleClick(); 150 | } 151 | 152 | FReply SLockWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) 153 | { 154 | if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) 155 | { 156 | return FReply::Unhandled(); 157 | } 158 | 159 | return HandleClick(); 160 | } 161 | 162 | FReply SLockWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) 163 | { 164 | if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) 165 | { 166 | return FReply::Handled(); 167 | } 168 | 169 | return FReply::Unhandled(); 170 | } 171 | 172 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION 173 | -------------------------------------------------------------------------------- /Source/ActorLocker/Private/SceneOutlinerActorLocker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #include "SceneOutlinerActorLocker.h" 4 | #include "ActorLockerStyle.h" 5 | #include "ActorTreeItem.h" 6 | #include "ISceneOutliner.h" 7 | #include "ISceneOutlinerTreeItem.h" 8 | #include "SLockWidget.h" 9 | 10 | FSceneOutlinerActorLocker::FSceneOutlinerActorLocker(ISceneOutliner& Outliner) 11 | { 12 | WeakOutliner = StaticCastSharedRef(Outliner.AsShared()); 13 | } 14 | 15 | SHeaderRow::FColumn::FArguments FSceneOutlinerActorLocker::ConstructHeaderRowColumn() 16 | { 17 | return SHeaderRow::Column(GetColumnID()) 18 | .FixedWidth(24.f) 19 | .HAlignHeader(HAlign_Center) 20 | .VAlignHeader(VAlign_Center) 21 | .HAlignCell(HAlign_Center) 22 | .VAlignCell(VAlign_Center) 23 | .DefaultTooltip(FText::FromName(GetColumnID())) 24 | .HeaderContentPadding(FMargin(0.f)) 25 | [ 26 | SNew(SImage) 27 | .ColorAndOpacity(FSlateColor::UseForeground()) 28 | .Image(FActorLockerStyle::Get().GetBrush("SceneOutliner.Lock")) 29 | ]; 30 | } 31 | 32 | const TSharedRef FSceneOutlinerActorLocker::ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) 33 | { 34 | if (TreeItem->ShouldShowVisibilityState()) 35 | { 36 | return SNew(SHorizontalBox) 37 | +SHorizontalBox::Slot() 38 | .AutoWidth() 39 | .VAlign(VAlign_Center) 40 | .Padding(2.f, 0.f, 0.f, 0.f) 41 | [ 42 | SNew(SLockWidget, SharedThis(this), WeakOutliner, TreeItem, &Row) 43 | ]; 44 | } 45 | return SNullWidget::NullWidget; 46 | } -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLocker.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "ActorLockerTypes.h" 7 | 8 | class UActorLockerManager; 9 | class UActorLockerPluginStateService; 10 | class FActorLockerMenuExtender; 11 | 12 | DECLARE_MULTICAST_DELEGATE_OneParam(FOnActorLockerManagerCreated, UActorLockerManager*); 13 | 14 | 15 | class FActorLockerModule : public IModuleInterface 16 | { 17 | public: 18 | FOnActorLockerManagerCreated OnActorLockerManagerCreated; 19 | 20 | private: 21 | TWeakObjectPtr ActorLockerManager; 22 | TWeakObjectPtr PluginStateService; 23 | TSharedPtr MenuExtender; 24 | 25 | #if OLDER_THAN_UE_5_1 26 | FDelegateHandle OnPreWorldInitializationHandle; 27 | #endif 28 | 29 | public: 30 | //~ Begin IModuleInterface Interface 31 | virtual void StartupModule() override; 32 | virtual void ShutdownModule() override; 33 | //~ End IModuleInterface Interface 34 | 35 | TWeakObjectPtr GetActorLockerManager(const bool bRequired = false); 36 | 37 | protected: 38 | void OnMapOpened(const FString& Filename, bool bAsTemplate); 39 | void CreateActorLockerManager(); 40 | void CreateActorLockerMenuExtender(); 41 | 42 | void DestroyActorLockerMenuExtender(); 43 | }; 44 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerCommandManager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Object.h" 7 | #include "ActorLockerCommandManager.generated.h" 8 | 9 | class UActorLockerManager; 10 | 11 | /** 12 | * 13 | */ 14 | UCLASS() 15 | class ACTORLOCKER_API UActorLockerCommandManager : public UObject 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | static TSharedRef RegisterCommands(); 21 | static void UnregisterCommands(); 22 | 23 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 24 | static void LockObject(); 25 | 26 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 27 | static void UnlockObject(); 28 | 29 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 30 | static void LockAllObjects(); 31 | 32 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 33 | static void UnlockAllObjects(); 34 | 35 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 36 | static void ToggleLockedObjects(); 37 | 38 | 39 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 40 | static bool CanLockObject(); 41 | 42 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 43 | static bool CanUnlockObject(); 44 | 45 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 46 | static bool CanLockAllObjects(); 47 | 48 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 49 | static bool CanUnlockAllObjects(); 50 | 51 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Command Manager") 52 | static bool CanToggleLockedObjects(); 53 | 54 | protected: 55 | static void SetLockActors(const bool bInLock); 56 | static void SetLockAllActors(const bool bInLock); 57 | 58 | static bool IsValidActorLockerManager(); 59 | static bool IsValidEditorWorld(); 60 | }; 61 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerCommands.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "ActorLockerStyle.h" 7 | #include "Framework/Commands/Commands.h" 8 | #include "Styling/ISlateStyle.h" 9 | 10 | /** 11 | * 12 | */ 13 | class ACTORLOCKER_API FActorLockerCommands : public TCommands 14 | { 15 | 16 | public: 17 | FActorLockerCommands() : TCommands 18 | ( 19 | "ActorLocker", 20 | NSLOCTEXT("Contexts", "ActorLocker", "Actor Locker"), 21 | NAME_None, 22 | FActorLockerStyle::Get().GetStyleSetName() 23 | ) 24 | { 25 | } 26 | 27 | //~ Begin TCommands Interface 28 | virtual void RegisterCommands() override; 29 | //~ End TCommands Interface 30 | 31 | public: 32 | TSharedPtr LockObject; 33 | TSharedPtr UnlockObject; 34 | TSharedPtr LockAllObjects; 35 | TSharedPtr UnlockAllObjects; 36 | TSharedPtr ToggleLockedObjects; 37 | }; 38 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerEditorMode.h: -------------------------------------------------------------------------------- 1 | // Copyright Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "ActorLockerTypes.h" 7 | #include "Tools/UEdMode.h" 8 | #include "Tools/LegacyEdModeInterfaces.h" 9 | #include "ActorLockerEditorMode.generated.h" 10 | 11 | struct HActor; 12 | class IAssetGenerationAPI; 13 | 14 | /** 15 | * 16 | */ 17 | UCLASS() 18 | class ACTORLOCKER_API UActorLockerEditorMode : public UEdMode, public FSlateDebugging::IWidgetInputRoutingEvent, public ILegacyEdModeViewportInterface 19 | { 20 | GENERATED_BODY() 21 | 22 | protected: 23 | UPROPERTY(BlueprintReadOnly, Category = "Actor Locker Editor Mode") 24 | EActorLockerInteractionType InteractionType = EActorLockerInteractionType::None; 25 | 26 | UPROPERTY(BlueprintReadOnly, Category = "Actor Locker Editor Mode") 27 | TSet OutlinerWidgetTypes; 28 | 29 | UPROPERTY(BlueprintReadOnly, Category = "Actor Locker Editor Mode") 30 | TSet MenuWidgetTypes; 31 | 32 | UPROPERTY(BlueprintReadOnly, Category = "Actor Locker Editor Mode") 33 | TSet LockerWidgetTypes; 34 | 35 | UPROPERTY(BlueprintReadOnly, Category = "Actor Locker Editor Mode") 36 | TSet IgnoredWidgetTypes; 37 | 38 | TSet SelectedItems; 39 | 40 | public: 41 | const static FEditorModeID EM_ActorLockerEditorModeId; 42 | 43 | UActorLockerEditorMode(); 44 | 45 | virtual void Enter() override; 46 | virtual void Exit() override; 47 | virtual bool IsCompatibleWith(FEditorModeID OtherModeID) const override; 48 | virtual bool IsSelectionDisallowed(AActor* InActor, bool bInSelection) const override; 49 | 50 | virtual bool UsesToolkits() const override { return false; } 51 | 52 | void UnregisterEvent(); 53 | void RegisterEvent(); 54 | 55 | void UpdateWidgetTypes(); 56 | 57 | //~ Begin FSlateDebugging::IWidgetInputRoutingEvent Interface 58 | virtual void OnProcessInput(ESlateDebuggingInputEvent InputEventType, const FInputEvent& Event) override; 59 | virtual void OnPreProcessInput(ESlateDebuggingInputEvent InputEventType, const TCHAR* InputPrecessorName, bool bHandled) override {} 60 | virtual void OnRouteInput(ESlateDebuggingInputEvent InputEventType, const FName& RoutedType) override {} 61 | virtual void OnInputEvent(ESlateDebuggingInputEvent InputEventType, const FReply& InReply, const TSharedPtr& HandlerWidget) override {} 62 | virtual void OnInputRouted(ESlateDebuggingInputEvent InputEventType) override {} 63 | virtual void OnInputProcessed(ESlateDebuggingInputEvent InputEventType) override {} 64 | //~ End FSlateDebugging::IWidgetInputRoutingEvent Interface 65 | 66 | //~ Begin ILegacyEdModeViewportInterface Interface 67 | virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) override; 68 | //~ End ILegacyEdModeViewportInterface Interface 69 | 70 | protected: 71 | virtual FWidgetPath GetWidgetPath(const FInputEvent& Event) const; 72 | virtual EActorLockerInteractionType GetInteractionType(const FWidgetPath& Path, uint32& OutItemId) const; 73 | virtual void CheckLockedActorsSelection() const; 74 | 75 | bool IsAppropriateProxy(HHitProxy* HitProxy) const; 76 | bool SelectFirstUnlockedActor(const FEditorViewportClient* InViewportClient, const FViewportClick& Click) const; 77 | FVector GetTraceStart(const FVector& InClickOrigin , const FVector& InDirection, const float InOrthoHeight, const ELevelViewportType InViewportType) const; 78 | FVector GetTraceEnd(const FVector& InStart , const FVector& InDirection, const float InLength) const; 79 | 80 | }; 81 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerManager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "ActorLockerTypes.h" 7 | #include "UObject/Object.h" 8 | #include "ActorLockerManager.generated.h" 9 | 10 | class UActorLockerSettings; 11 | struct ISceneOutlinerTreeItem; 12 | class AActor; 13 | 14 | /** 15 | * 16 | */ 17 | UCLASS() 18 | class ACTORLOCKER_API UActorLockerManager : public UObject 19 | { 20 | GENERATED_BODY() 21 | 22 | protected: 23 | UPROPERTY(BlueprintReadOnly, Category = "Actor Locker Manager") 24 | const UActorLockerSettings* Settings; 25 | 26 | TMap Items; 27 | TSet ToggledItems; 28 | 29 | public: 30 | virtual void PostInitProperties() override; 31 | virtual void BeginDestroy() override; 32 | virtual void Serialize(FArchive& Ar) override; 33 | 34 | void InitItem(const TWeakPtr& InTreeItem); 35 | void SetLockTreeItem(const TWeakPtr& InTreeItem, const bool bInLock, const bool bPropagateToChildren = true); 36 | void UnlockById(const uint32 InId); 37 | 38 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Manager") 39 | void SetLockActor(AActor* InActor, const bool bInLock, const bool bPropagateToChildren = true); 40 | 41 | // Toggling lock state for locked actors (useful for quick change for locked actors) 42 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Manager") 43 | void ToggleLockedActors(); 44 | 45 | UFUNCTION(BlueprintPure, Category = "Actor Locker Manager") 46 | bool IsActorLocked(const AActor* InActor) const; 47 | bool IsItemLocked(const TWeakPtr& InTreeItem) const; 48 | 49 | void CheckParentLock(const TWeakPtr& InTreeItem); 50 | bool IsAnyChildUnlocked(const TWeakPtr& InParentTreeItem) const; 51 | void UpdateLockState(); 52 | 53 | UFUNCTION(BlueprintPure, Category = "Actor Locker Manager") 54 | TSet GetLockedActors() const; 55 | 56 | UFUNCTION(BlueprintPure, Category = "Actor Locker Manager") 57 | static UActorLockerManager* GetActorLockerManager(); 58 | 59 | protected: 60 | UFUNCTION(BlueprintCallable, Category = "Actor Locker Manager") 61 | void UpdateTagState(AActor* Actor, bool bInLock); 62 | 63 | UFUNCTION() 64 | void OnActorDeleted(AActor* InActor); 65 | 66 | void OnPostTick(float InDeltaTime); 67 | void OnInputEvent(const FSlateDebuggingInputEventArgs& SlateDebuggingInputEventArgs); 68 | }; 69 | 70 | 71 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerMenuExtender.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | /** 8 | * 9 | */ 10 | class ACTORLOCKER_API FActorLockerMenuExtender 11 | { 12 | protected: 13 | FDelegateHandle MenuExtenderDelegateHandle; 14 | 15 | public: 16 | void AddLevelViewportMenuExtender(); 17 | void RemoveLevelViewportMenuExtender(); 18 | 19 | protected: 20 | TSharedRef OnExtendLevelEditor(const TSharedRef CommandList, const TArray SelectedActors); 21 | void CreateActorLockerMenu(FMenuBuilder& MenuBuilder, TArray Actors); 22 | }; 23 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerPluginStateService.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Object.h" 7 | #include "ActorLockerPluginStateService.generated.h" 8 | 9 | struct FPluginReferenceDescriptor; 10 | 11 | UCLASS() 12 | class ACTORLOCKER_API UActorLockerPluginStateService : public UObject, public FTickableGameObject 13 | { 14 | GENERATED_BODY() 15 | 16 | protected: 17 | UPROPERTY(BlueprintReadOnly, Category = "ActorLocker|PluginState") 18 | bool bLastPluginState = false; 19 | 20 | TSharedPtr Plugin; 21 | 22 | public: 23 | virtual void PostInitProperties() override; 24 | 25 | //~ Begin FTickableGameObject Interface 26 | virtual void Tick(float DeltaTime) override; 27 | virtual bool IsTickableWhenPaused() const override; 28 | virtual bool IsTickableInEditor() const override; 29 | virtual bool IsTickable() const override; 30 | virtual TStatId GetStatId() const override; 31 | //~ End FTickableGameObject Interface 32 | }; 33 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerPythonCommand.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Object.h" 7 | #include "ActorLockerPythonCommand.generated.h" 8 | 9 | UCLASS(Blueprintable, BlueprintType) 10 | class ACTORLOCKER_API UActorLockerPythonCommand : public UObject 11 | { 12 | GENERATED_BODY() 13 | 14 | public: 15 | virtual UWorld* GetWorld() const override; 16 | 17 | UFUNCTION(BlueprintCallable, Category = "ActorLockerPythonCommand") 18 | void AddObjectToRoot(); 19 | 20 | UFUNCTION(BlueprintCallable, Category = "ActorLockerPythonCommand") 21 | void RemoveObjectFromRoot(); 22 | 23 | UFUNCTION(BlueprintCallable, Category="ActorLockerPythonCommand", meta=(DefaultToSelf = "Object", AdvancedDisplay="InitialStartDelay")) 24 | static void SetEditorTimer(UObject* Object, FString FunctionName, float Time, bool bLooping = false, float InitialStartDelay = -1); 25 | }; 26 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerSettings.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Framework/Commands/InputChord.h" 7 | #include "UObject/Object.h" 8 | #include "ActorLockerSettings.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | UCLASS(Config = ActorLockerSettings) 14 | class ACTORLOCKER_API UActorLockerSettings : public UObject 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | UActorLockerSettings(); 20 | 21 | #pragma region General 22 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | General") 23 | bool bDeselectActorOnLock = false; 24 | 25 | // if true the locked actors will be marked by a tag 26 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | General") 27 | bool bSaveLockedState = true; 28 | 29 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | General", meta = (EditCondition = "bSaveLockedState")) 30 | FName LockedTag; 31 | 32 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | General", meta = (ConfigRestartRequired = true)) 33 | TSet CompatibleModes; 34 | 35 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | General") 36 | bool bDeveloperMode = false; 37 | #pragma endregion General 38 | 39 | #pragma region Hotkeys 40 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Hotkeys") 41 | FInputChord LockObject; 42 | 43 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Hotkeys") 44 | FInputChord UnlockObject; 45 | 46 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Hotkeys") 47 | FInputChord LockAllObjects; 48 | 49 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Hotkeys") 50 | FInputChord UnlockAllObjects; 51 | 52 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Hotkeys") 53 | FInputChord ToggleLockedObjects; 54 | #pragma endregion Hotkeys 55 | 56 | #pragma region EditorMode 57 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Editor Mode") 58 | bool bSelectLockedActorsInOutliner = true; 59 | 60 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker Editor Mode") 61 | TSet OutlinerWidgetTypes; 62 | 63 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker Editor Mode") 64 | TSet MenuWidgetTypes; 65 | 66 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker Editor Mode") 67 | TSet LockerWidgetTypes; 68 | 69 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker Editor Mode") 70 | TSet IgnoredWidgetTypes; 71 | #pragma endregion EditorMode 72 | 73 | #pragma region PluginStateService 74 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Plugin State Service", meta = (EditCondition = "bDeveloperMode", EditConditionHides)) 75 | FText PluginStateServiceWarningMessage; 76 | 77 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Plugin State Service", meta = (EditCondition = "bDeveloperMode", EditConditionHides)) 78 | FString ScriptFileName; 79 | 80 | // Checks the plugin state (enabled / disabled) every X seconds to warn user that he need to unlock all actors before disabling the plugin 81 | UPROPERTY(Config, BlueprintReadOnly, EditAnywhere, Category = "Actor Locker | Plugin State Service", meta = (EditCondition = "bDeveloperMode", EditConditionHides)) 82 | float CheckPluginStateInterval = 0.05f; 83 | #pragma endregion PluginStateService 84 | 85 | public: 86 | virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; 87 | }; 88 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerStyle.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | #include "CoreMinimal.h" 5 | 6 | class ISlateStyle; 7 | class FSlateStyleSet; 8 | 9 | /** 10 | * Actor Locker style that defines icon locations, style brushes, etc. 11 | */ 12 | class FActorLockerStyle 13 | { 14 | private: 15 | static TSharedPtr StyleInstance; 16 | 17 | public: 18 | static void Initialize(); 19 | static void Shutdown(); 20 | static void ReloadTextures(); 21 | static const ISlateStyle& Get(); 22 | static FName GetStyleSetName(); 23 | 24 | private: 25 | static TSharedRef Create(); 26 | }; 27 | 28 | 29 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/ActorLockerTypes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | #pragma once 3 | 4 | #include "CoreMinimal.h" 5 | #include "ActorLockerTypes.generated.h" 6 | 7 | #define OLDER_THAN_UE_5_1 (ENGINE_MAJOR_VERSION < 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 1)) 8 | 9 | struct ISceneOutlinerTreeItem; 10 | DECLARE_LOG_CATEGORY_EXTERN(LogActorLockerManager, Log, All); 11 | DECLARE_LOG_CATEGORY_EXTERN(LogLockWidget, Log, All); 12 | 13 | 14 | USTRUCT(BlueprintType) 15 | struct FLockerTreeItem 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Locker Tree Item") 21 | bool bLocked = false; 22 | 23 | TWeakPtr NativeItem = nullptr; 24 | 25 | public: 26 | FLockerTreeItem() {} 27 | 28 | FLockerTreeItem(const TWeakPtr& InItem, bool bInLocked = false) 29 | : bLocked(bInLocked) 30 | , NativeItem(InItem) 31 | { 32 | } 33 | 34 | uint32 GetId() const; 35 | static uint32 GetId(const TWeakPtr& InItem); 36 | 37 | FORCEINLINE bool IsValid() const 38 | { 39 | return NativeItem.IsValid(); 40 | } 41 | 42 | friend FArchive& operator<<(FArchive& Archive, FLockerTreeItem& Item) 43 | { 44 | Archive << Item.bLocked; 45 | return Archive; 46 | } 47 | 48 | operator TSharedPtr() const 49 | { 50 | return NativeItem.Pin(); 51 | } 52 | 53 | operator TWeakPtr() const 54 | { 55 | return NativeItem; 56 | } 57 | 58 | FLockerTreeItem& operator=(const TWeakPtr& InItem) 59 | { 60 | NativeItem = InItem; 61 | return *this; 62 | } 63 | }; 64 | 65 | 66 | UENUM() 67 | enum class EActorLockerInteractionType : uint8 68 | { 69 | None, 70 | Outliner, 71 | Ignored 72 | }; 73 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/SLockWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 0 8 | #include "SceneOutlinerFwd.h" 9 | #else 10 | #include "SceneOutliner/Public/SceneOutlinerFwd.h" 11 | #endif 12 | 13 | #include "Widgets/Images/SImage.h" 14 | #include "Widgets/Views/STableRow.h" 15 | #include "ActorLockerTypes.h" 16 | 17 | class UActorLockerManager; 18 | class ISceneOutliner; 19 | class FSceneOutlinerActorLocker; 20 | struct ISceneOutlinerTreeItem; 21 | 22 | /** 23 | * The widget that represent Lock button in Scene Outliner 24 | */ 25 | class ACTORLOCKER_API SLockWidget : public SImage 26 | { 27 | public: 28 | SLATE_BEGIN_ARGS(SLockWidget) {} 29 | SLATE_END_ARGS() 30 | 31 | protected: 32 | FDelegateHandle OnActorLockerManagerCreatedHandle; 33 | 34 | TWeakPtr WeakColumn; 35 | TWeakPtr WeakOutliner; 36 | TWeakPtr WeakItem; 37 | TWeakObjectPtr WeakActorManager; 38 | 39 | const STableRow* Row = nullptr; 40 | 41 | public: 42 | /** Constructs this widget with InArgs */ 43 | void Construct(const FArguments& InArgs, TWeakPtr InWeakColumn, TWeakPtr InWeakOutliner, TWeakPtr InWeakTreeItem, const STableRow* InRow); 44 | 45 | ~SLockWidget(); 46 | protected: 47 | virtual FSlateColor GetForegroundColor() const override; 48 | virtual const FSlateBrush* GetBrush() const; 49 | 50 | virtual bool IsEnabled() const { return true; } 51 | virtual bool IsLocked() const; 52 | virtual bool IsLocked(const TWeakPtr& Item, const TSharedPtr& Column) const; 53 | virtual void SetIsLocked(const bool bNewLocked); 54 | 55 | FReply HandleClick(); 56 | virtual FReply OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) override; 57 | virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; 58 | virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; 59 | }; 60 | -------------------------------------------------------------------------------- /Source/ActorLocker/Public/SceneOutlinerActorLocker.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Gradess Games. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "SceneOutlinerGutter.h" 7 | #include "ISceneOutlinerColumn.h" 8 | 9 | #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1 10 | #include "SceneOutlinerPublicTypes.h" 11 | #endif 12 | 13 | #define LOCTEXT_NAMESPACE "SceneOutlinerLock" 14 | 15 | /** 16 | * The Scene Outliner column that displays the lock state of actors 17 | */ 18 | class ACTORLOCKER_API FSceneOutlinerActorLocker : public ISceneOutlinerColumn 19 | { 20 | protected: 21 | TWeakPtr WeakOutliner; 22 | 23 | public: 24 | FSceneOutlinerActorLocker(ISceneOutliner& Outliner); 25 | 26 | #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1 27 | DEFINE_SCENEOUTLINER_BUILTIN_COLUMN_TYPE(Lock, "Lock", "LockColumnName", "Lock"); 28 | #else 29 | static FName& Lock() 30 | { 31 | static FName Lock = "Lock"; 32 | return Lock; 33 | } 34 | 35 | static const FText& Lock_Localized() 36 | { 37 | static FText LockLocalized = LOCTEXT("LockColumnName", "Lock"); 38 | return LockLocalized; 39 | } 40 | #endif 41 | 42 | virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override; 43 | virtual const TSharedRef ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) override; 44 | 45 | static FName GetID() { return Lock(); } 46 | virtual FName GetColumnID() override { return GetID(); } 47 | }; 48 | 49 | #undef LOCTEXT_NAMESPACE --------------------------------------------------------------------------------