├── .gitignore
├── Config
├── DefaultEditor.ini
├── DefaultEngine.ini
└── DefaultGame.ini
├── Content
└── EditorResources
│ └── IconExampleEditorMode.png
├── LICENSE.md
├── README.md
├── Source
├── ToolExample.Target.cs
├── ToolExample
│ ├── CustomDataType
│ │ └── ExampleData.h
│ ├── DetailsCustomization
│ │ └── ExampleActor.h
│ ├── EditorMode
│ │ └── ExampleTargetPoint.h
│ ├── ToolExample.Build.cs
│ ├── ToolExample.cpp
│ ├── ToolExample.h
│ ├── ToolExampleGameModeBase.cpp
│ └── ToolExampleGameModeBase.h
├── ToolExampleEditor.Target.cs
└── ToolExampleEditor
│ ├── CustomDataType
│ ├── ExampleDataFactory.cpp
│ ├── ExampleDataFactory.h
│ ├── ExampleDataTypeActions.cpp
│ ├── ExampleDataTypeActions.h
│ ├── ReimportExampleDataFactory.cpp
│ ├── ReimportExampleDataFactory.h
│ ├── XMPAssetTypeActions_Base.cpp
│ ├── XMPAssetTypeActions_Base.h
│ ├── XMPImportFactory.cpp
│ └── XMPImportFactory.h
│ ├── CustomProjectSettings
│ └── ExampleSettings.h
│ ├── DetailsCustomization
│ ├── ExampleActorDetails.cpp
│ └── ExampleActorDetails.h
│ ├── EditorMode
│ ├── ExampleEdMode.cpp
│ ├── ExampleEdMode.h
│ ├── ExampleEdModeTool.cpp
│ ├── ExampleEdModeTool.h
│ ├── ExampleEdModeToolkit.h
│ ├── SExampleEdModeWidget.cpp
│ └── SExampleEdModeWidget.h
│ ├── ExampleTabToolBase.h
│ ├── IExampleModuleInterface.h
│ ├── MenuTool
│ ├── MenuTool.cpp
│ └── MenuTool.h
│ ├── TabTool
│ ├── TabTool.cpp
│ ├── TabTool.h
│ ├── TabToolPanel.cpp
│ └── TabToolPanel.h
│ ├── ToolExampleEditor.Build.cs
│ ├── ToolExampleEditor.cpp
│ └── ToolExampleEditor.h
├── ToolExample.uproject
└── docs
├── How to Make Tools in UE4 - Eric's Blog.html
├── images
├── 001.png
├── 002.png
├── 003.png
├── 004.png
├── 005.png
├── 006.png
├── 007.png
├── 008.png
├── 009.png
├── 010.png
├── 011.png
├── 012.png
├── 013.png
├── 014.png
├── 015.png
├── 016.png
├── 017.png
├── 018.png
├── 019.png
├── 020.png
├── 021.png
├── 022.png
├── 023.png
├── 024.png
├── 025.png
├── 026.png
├── 027.png
├── 028.png
├── 029.png
├── 030.png
├── 031.png
└── splash.png
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | .ignore
2 | ToolExample.sln
3 | ToolExample.code-workspace
4 | .vs/
5 | .vscode/
6 | .idea/
7 | Binaries/
8 | Intermediate/
9 | Saved/
10 | .idea/
11 |
12 | Config/DefaultEditorUserSettings.ini
13 | .vsconfig
14 | Config/DefaultInput.ini
15 | .obsidian/
16 |
--------------------------------------------------------------------------------
/Config/DefaultEditor.ini:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/Config/DefaultEditor.ini
--------------------------------------------------------------------------------
/Config/DefaultEngine.ini:
--------------------------------------------------------------------------------
1 | [URL]
2 |
3 | [/Script/EngineSettings.GameMapsSettings]
4 | EditorStartupMap=
5 | GameDefaultMap=
6 | GlobalDefaultGameMode="/Script/ToolExample.ToolExampleGameMode"
7 |
8 | [/Script/HardwareTargeting.HardwareTargetingSettings]
9 | TargetedHardwareClass=Desktop
10 | AppliedTargetedHardwareClass=Desktop
11 | DefaultGraphicsPerformance=Maximum
12 | AppliedDefaultGraphicsPerformance=Maximum
13 |
14 | [/Script/Engine.PhysicsSettings]
15 | DefaultGravityZ=-980.000000
16 | DefaultTerminalVelocity=4000.000000
17 | DefaultFluidFriction=0.300000
18 | SimulateScratchMemorySize=262144
19 | RagdollAggregateThreshold=4
20 | TriangleMeshTriangleMinAreaThreshold=5.000000
21 | bEnableAsyncScene=False
22 | bEnableShapeSharing=False
23 | bEnablePCM=True
24 | bEnableStabilization=False
25 | bWarnMissingLocks=True
26 | bEnable2DPhysics=False
27 | LockedAxis=Invalid
28 | DefaultDegreesOfFreedom=Full3D
29 | BounceThresholdVelocity=200.000000
30 | FrictionCombineMode=Average
31 | RestitutionCombineMode=Average
32 | MaxAngularVelocity=3600.000000
33 | MaxDepenetrationVelocity=0.000000
34 | ContactOffsetMultiplier=0.020000
35 | MinContactOffset=2.000000
36 | MaxContactOffset=8.000000
37 | bSimulateSkeletalMeshOnDedicatedServer=True
38 | DefaultShapeComplexity=CTF_UseSimpleAndComplex
39 | bDefaultHasComplexCollision=True
40 | bSuppressFaceRemapTable=False
41 | bSupportUVFromHitResults=False
42 | bDisableActiveActors=False
43 | bDisableCCD=False
44 | bEnableEnhancedDeterminism=False
45 | MaxPhysicsDeltaTime=0.033333
46 | bSubstepping=False
47 | bSubsteppingAsync=False
48 | MaxSubstepDeltaTime=0.016667
49 | MaxSubsteps=6
50 | SyncSceneSmoothingFactor=0.000000
51 | AsyncSceneSmoothingFactor=0.990000
52 | InitialAverageFrameRate=0.016667
53 | PhysXTreeRebuildRate=10
54 |
55 | [/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
56 | bEnablePlugin=True
57 | bAllowNetworkConnection=True
58 | SecurityToken=6607BEA0438D48653A04889817D4749C
59 | bIncludeInShipping=False
60 | bAllowExternalStartInShipping=False
61 | bCompileAFSProject=False
62 | bUseCompression=False
63 | bLogFiles=False
64 | bReportStats=False
65 | ConnectionType=USBOnly
66 | bUseManualIPAddress=False
67 | ManualIPAddress=
68 |
69 |
--------------------------------------------------------------------------------
/Config/DefaultGame.ini:
--------------------------------------------------------------------------------
1 | [/Script/EngineSettings.GeneralProjectSettings]
2 | ProjectID=C0A7B37A40F5020C10F1B6809AE1B5E1
3 |
4 |
--------------------------------------------------------------------------------
/Content/EditorResources/IconExampleEditorMode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/Content/EditorResources/IconExampleEditorMode.png
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Eric Zhang
4 | This code includes modifications by Scott Kirvan. Modifications (c) Scott Kirvan
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [ScottKirvan/ToolExample](https://github.com/ScottKirvan/ToolExample)
2 | ToolExample contains UX/UI C/C++ examples for adding tools and UI elements to the [Unreal Engine](https://www.unrealengine.com) editor - which means, these are editor tools for the artist, and not an example of how to write the runtime game code that a player would interact with.
3 |
4 | 
5 |
6 | If you're looking for an example to show how to add an item to the existing menu, or a new edit mode, or how to create a dockable window, this is a really thorough, [step-by-step example](https://lxjk.github.io/2019/10/01/How-to-Make-Tools-in-U-E.html) that starts from an empty code project, immediately jumps into the base modules needed, and then clearly walks through the steps to code up various UI elements, hooking them to functionality as you go.
7 |
8 | # Features
9 | These examples demonstrate how to implement the following:
10 | - adding modules
11 | - adding menus to the editor interface with:
12 | - sections
13 | - menu items
14 | - sub menus
15 | - adding custom widgets and controls (button and text input) to the menu
16 | - adding a dockable tab Window
17 | - custom details panel
18 | - defining and adding your own custom asset data type (data factory)
19 | - importing/reimporting custom data
20 | - custom editor mode
21 | - custom editor viewport widget
22 | - viewport object context sensitive right-click & menus
23 | - custom project settings/preferences
24 | - additional tricks and tips
25 |
26 | This repo is an updated fork of [Eric Zhang's](https://github.com/lxjk) original [UE4 project](https://github.com/lxjk/ToolExample). The excellent step-by-step documentation can be found here: [How to Make Tools in UE4](https://lxjk.github.io/2019/10/01/How-to-Make-Tools-in-U-E.html) I've included a stripped down version of that tutorial in this repo in case anything happens to that URL in the future.
27 |
28 | This tutorial has become the most valuable one I've run across in my Unreal C++ journey. It got me started in an area of programming in Unreal that I found pretty impenetrable at first, and it has continued to be a reference point that I return to frequently whenever I'm thinking I want some new functionality (or just some easier-to-get-at functionality) inside Unreal. My deepest gratitude to Eric for his contribution here.
29 |
30 | ## Branches
31 | - master
32 | - current development branch - this will likely have the most recent code - note: this branch does work with UE 5.2 Preview 1
33 | - 5.1
34 | - UE 5.1 Compatible
35 | - There was some header changes, a new module dependency, and class FEditorStyle was deprecated and replaced with FAppStyle.
36 | - I tested this very briefly, but it all looks good!
37 | - 4.26
38 | - UE 4.26 Compatible.
39 | - Example Editor Mode is working in this version, so I'm not going to bother trying to figure out what broke in 4.25. If anyone takes this on as a project, please toss up a Pull Request.
40 | - initial branch source is identical to 4.24/4.25
41 | - 4.25
42 | - UE 4.25 Compatible.
43 | - builds and runs, but the Example Editor Mode isn't working. It shows up in the Modes list, but the UI never gets built when selected. I didn't see any errors or warnings anywhere.
44 | - NOTE: The editor mode issue works in 4.26 without any code changes.
45 | - as it stands, this source is identical to the 4.24 branch
46 | - 4.24
47 | - UE 4.24 Compatible.
48 | - updates build.cs to V2 target.
49 | - implements [IWYU](https://docs.unrealengine.com/en-US/ProductionPipelines/BuildTools/UnrealBuildTool/IWYU/index.html) standard.
50 | - 4.23
51 | - original fork from [lxjk/ToolExample](https://github.com/lxjk/ToolExample).
52 | - UE 4.23 compatible.
53 |
54 | ## Contributing
55 | Do you know how to create a UI tool that would be good to include in this example? Interested in contributing with writing or anything else? Did you find a bug?! Please help out! Check the issues link to see the kinds of things that might be fun to tackle.
56 |
57 | The best way to contribute would be with [Pull Requests (PR)](https://github.com/ScottKirvan/ToolExample/pulls): Fork this repository, make your changes, and submit a New Pull Request that can be reviewed and rolled back in.
58 |
59 | If you don't want to do the Write/PR process yourself, but would still like to contribute, just use the [Issues](https://github.com/ScottKirvan/ToolExample/issues) link above to report bugs or request something new you'd like to see.
60 |
61 | ## System Requirements
62 | - Computer capable of Unreal Engine development
63 | - Epic Recommends [this.](https://docs.unrealengine.com/5.1/en-US/hardware-and-software-specifications-for-unreal-engine/)
64 | - The typical system used by developers at Epic Games looks like this:
65 | - Windows 10 64-bit (Version 20H2)
66 | - 64 GB RAM
67 | - 256 GB SSD (OS Drive)
68 | - 2 TB SSD (Data Drive)
69 | - NVIDIA GeForce RTX 2080 SUPER
70 | - Xoreax Incredibuild (Dev Tools Package)
71 | - Six-Core Xeon E5-2643 @ 3.4GHz
72 | - I get by with a 16 core AMD Ryzen 9, Windows 11, MSDev2022, and 40 GB RAM on my gaming laptop.
73 | - Unreal Engine 5.5
74 | - This repo also supports older versions, just download/clone the [branch](https://github.com/ScottKirvan/ToolExample/branches) you need.
75 | - Microsoft Visual Studio 2022
76 | - Epic still recommends the 2019 version, but I'm using 2022 - get the free-to-use [Community version here](https://visualstudio.microsoft.com/vs/community/).
77 | - This repo supports older versions, just clone the branch you need.
78 | - Microsoft Visual Studio 2019
79 | - the 2022 version works as well, but you have to explicitly tell unreal engine to use 2022 (`Edit -> Editor Preferences... -> Source Code -> Source Code Editor`).
80 |
81 | ## Getting Started With Local Development
82 | Clone or download this repo. If you have Unreal and Visual Studio properly installed, double clicking the `uproject` file should launch Unreal, which will recognize it as a code project and build everything automatically.
83 |
84 | I highly recommend following the Tutorial and using this code as reference. If you hit an error, look at this code to see what may have changed.
85 |
86 | A couple of other good resources for getting started:
87 | - [Setting Up Visual Studio for EU5.1](https://docs.unrealengine.com/5.1/en-US/setting-up-visual-studio-development-environment-for-cplusplus-projects-in-unreal-engine/)
88 | - [UE5.1 Programming Quickstart](https://docs.unrealengine.com/5.1/en-US/unreal-engine-cpp-quick-start/)
89 |
90 | ## Support/Contact
91 | - Feel free to reach out to me on the [Unreal Slackers](https://discord.gg/unreal-slackers) discord. I'm @Fragmanget_. There is also a ton of other Unreal programmers up there, so if I'm not around to help, someone else be able to get you going.
92 | - You can also reach me on my personal [Discord Server](https://discord.gg/TSKHvVFYxB) (@cptvideo), via [LinkedIn](https://www.linkedin.com/in/scottkirvan/), or email.
93 |
94 | ## Submit a Feature Request
95 | Use the [Issues](https://github.com/ScottKirvan/ToolExample/issues) link, above. Thanks!
96 |
97 | ## Credits
98 | A huge thank you to [Eric Zhang](https://github.com/lxjk) for the work he put into the [original tutorial project](https://lxjk.github.io/2019/10/01/How-to-Make-Tools-in-U-E.html).
99 |
100 | Contributors:
101 | [Eric Zhang](https://github.com/lxjk) (OG!)
102 | [Scott Kirvan](https://github.com/ScottKirvan) (2021-present)
103 | [Razdvizh](https://github.com/Razdvizh) (2023)
104 | [duyaokun](https://github.com/duyaokun) (2024)
105 | You! (Future!)
106 |
107 | _ToolExample is licensed under the [MIT License](LICENSE.md)._
108 |
--------------------------------------------------------------------------------
/Source/ToolExample.Target.cs:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | using UnrealBuildTool;
4 | using System.Collections.Generic;
5 |
6 | public class ToolExampleTarget : TargetRules
7 | {
8 | public ToolExampleTarget(TargetInfo Target) : base(Target)
9 | {
10 | Type = TargetType.Game;
11 |
12 | ExtraModuleNames.AddRange( new string[] { "ToolExample" } );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Source/ToolExample/CustomDataType/ExampleData.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ExampleData.generated.h"
4 |
5 | UCLASS(Blueprintable)
6 | class TOOLEXAMPLE_API UExampleData : public UObject
7 | {
8 | GENERATED_BODY()
9 |
10 | public:
11 | UPROPERTY(EditAnywhere, Category = "Properties")
12 | FString ExampleString;
13 |
14 | #if WITH_EDITORONLY_DATA
15 | UPROPERTY(Category = SourceAsset, VisibleAnywhere)
16 | FString SourceFilePath;
17 | #endif
18 | };
--------------------------------------------------------------------------------
/Source/ToolExample/DetailsCustomization/ExampleActor.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ExampleActor.generated.h"
4 |
5 | UCLASS()
6 | class TOOLEXAMPLE_API AExampleActor : public AActor
7 | {
8 | GENERATED_BODY()
9 |
10 | public:
11 |
12 | UPROPERTY(EditAnywhere, Category = "Options")
13 | bool bOption1 = false;
14 |
15 | UPROPERTY(EditAnywhere, Category = "Options")
16 | bool bOption2 = false;
17 |
18 | UPROPERTY(EditAnywhere, Category = "Test")
19 | int testInt = 0;
20 |
21 | };
--------------------------------------------------------------------------------
/Source/ToolExample/EditorMode/ExampleTargetPoint.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Engine/Targetpoint.h"
4 | #include "ExampleTargetPoint.generated.h"
5 |
6 | UCLASS()
7 | class TOOLEXAMPLE_API AExampleTargetPoint : public ATargetPoint
8 | {
9 | GENERATED_BODY()
10 |
11 | public:
12 | UPROPERTY(EditAnywhere, Category = "Points")
13 | TArray Points;
14 | };
--------------------------------------------------------------------------------
/Source/ToolExample/ToolExample.Build.cs:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | using UnrealBuildTool;
4 |
5 | public class ToolExample : ModuleRules
6 | {
7 | public ToolExample(ReadOnlyTargetRules Target) : base(Target)
8 | {
9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
10 |
11 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
12 |
13 | PrivateDependencyModuleNames.AddRange(new string[] { });
14 |
15 | // Uncomment if you are using Slate UI
16 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
17 |
18 | // Uncomment if you are using online features
19 | // PrivateDependencyModuleNames.Add("OnlineSubsystem");
20 |
21 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Source/ToolExample/ToolExample.cpp:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | #include "ToolExample.h"
4 | #include "Modules/ModuleManager.h"
5 |
6 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ToolExample, "ToolExample" );
7 |
--------------------------------------------------------------------------------
/Source/ToolExample/ToolExample.h:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | #pragma once
4 |
5 | #include "CoreMinimal.h"
6 |
7 |
--------------------------------------------------------------------------------
/Source/ToolExample/ToolExampleGameModeBase.cpp:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 |
4 | #include "ToolExampleGameModeBase.h"
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Source/ToolExample/ToolExampleGameModeBase.h:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | #pragma once
4 |
5 | #include "CoreMinimal.h"
6 | #include "GameFramework/GameModeBase.h"
7 | #include "ToolExampleGameModeBase.generated.h"
8 |
9 | /**
10 | *
11 | */
12 | UCLASS()
13 | class TOOLEXAMPLE_API AToolExampleGameModeBase : public AGameModeBase
14 | {
15 | GENERATED_BODY()
16 |
17 |
18 |
19 |
20 | };
21 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor.Target.cs:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | using UnrealBuildTool;
4 | using System.Collections.Generic;
5 |
6 | public class ToolExampleEditorTarget : TargetRules
7 | {
8 | public ToolExampleEditorTarget(TargetInfo Target) : base(Target)
9 | {
10 | Type = TargetType.Editor;
11 | DefaultBuildSettings = BuildSettingsVersion.V5;
12 | IncludeOrderVersion = EngineIncludeOrderVersion.Latest;
13 |
14 | ExtraModuleNames.AddRange( new string[] { "ToolExample" } );
15 | ExtraModuleNames.AddRange( new string[] { "ToolExampleEditor" });
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/ExampleDataFactory.cpp:
--------------------------------------------------------------------------------
1 | #include "ExampleDataFactory.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 | #include "ToolExample/CustomDataType/ExampleData.h"
4 |
5 | UExampleDataFactory::UExampleDataFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
6 | {
7 | SupportedClass = UExampleData::StaticClass();
8 | bCreateNew = true;
9 | bEditAfterNew = true;
10 | }
11 |
12 | UObject* UExampleDataFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
13 | {
14 | UExampleData* NewObjectAsset = NewObject(InParent, Class, Name, Flags | RF_Transactional);
15 | return NewObjectAsset;
16 | }
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/ExampleDataFactory.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "UnrealEd.h"
4 | #include "ExampleDataFactory.generated.h"
5 |
6 | UCLASS()
7 | class UExampleDataFactory : public UFactory
8 | {
9 | GENERATED_UCLASS_BODY()
10 | public:
11 | // Begin UFactory Interface
12 | virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
13 | // End UFactory Interface
14 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/ExampleDataTypeActions.cpp:
--------------------------------------------------------------------------------
1 | #include "ExampleDataTypeActions.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 | #include "ToolExample/CustomDataType/ExampleData.h"
4 | #include "XMPAssetTypeActions_Base.h"
5 |
6 |
7 | FExampleDataTypeActions::FExampleDataTypeActions(EAssetTypeCategories::Type InAssetCategory)
8 | : MyAssetCategory(InAssetCategory)
9 | {
10 | }
11 |
12 | FText FExampleDataTypeActions::GetName() const
13 | {
14 | return FText::FromString("Example Data");
15 | }
16 |
17 | FColor FExampleDataTypeActions::GetTypeColor() const
18 | {
19 | return FColor(230, 205, 165);
20 | }
21 |
22 | UClass* FExampleDataTypeActions::GetSupportedClass() const
23 | {
24 | return UExampleData::StaticClass();
25 | }
26 |
27 | uint32 FExampleDataTypeActions::GetCategories()
28 | {
29 | return MyAssetCategory;
30 | }
31 |
32 | void FExampleDataTypeActions::GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder)
33 | {
34 | auto ExampleDataImports = GetTypedWeakObjectPtrs(InObjects);
35 |
36 | MenuBuilder.AddMenuEntry(
37 | FText::FromString("Reimport"),
38 | FText::FromString("Reimports example data."),
39 | FSlateIcon(),
40 | FUIAction(
41 | FExecuteAction::CreateSP(this, &FExampleDataTypeActions::ExecuteReimport, ExampleDataImports),
42 | FCanExecuteAction()
43 | )
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/ExampleDataTypeActions.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "XMPAssetTypeActions_Base.h"
4 |
5 | class UExampleData;
6 |
7 | class FExampleDataTypeActions : public FXMPAssetTypeActions_Base
8 | {
9 | public:
10 | FExampleDataTypeActions(EAssetTypeCategories::Type InAssetCategory);
11 |
12 | // IAssetTypeActions interface
13 | virtual FText GetName() const override;
14 | virtual FColor GetTypeColor() const override;
15 | virtual UClass* GetSupportedClass() const override;
16 | virtual uint32 GetCategories() override;
17 | virtual bool HasActions(const TArray& InObjects) const override { return true; }
18 | virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override;
19 | // End of IAssetTypeActions interface
20 |
21 | private:
22 | EAssetTypeCategories::Type MyAssetCategory;
23 | };
24 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/ReimportExampleDataFactory.cpp:
--------------------------------------------------------------------------------
1 | #include "ReimportExampleDataFactory.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 | #include "ExampleDataFactory.h"
4 | #include "ToolExample/CustomDataType/ExampleData.h"
5 |
6 | bool UReimportExampleDataFactory::CanReimport(UObject* Obj, TArray& OutFilenames)
7 | {
8 | UExampleData* ExampleData = Cast(Obj);
9 | if (ExampleData)
10 | {
11 | OutFilenames.Add(UAssetImportData::ResolveImportFilename(ExampleData->SourceFilePath, ExampleData->GetOutermost()));
12 | return true;
13 | }
14 | return false;
15 | }
16 |
17 | void UReimportExampleDataFactory::SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths)
18 | {
19 | UExampleData* ExampleData = Cast(Obj);
20 | if (ExampleData && ensure(NewReimportPaths.Num() == 1))
21 | {
22 | ExampleData->SourceFilePath = UAssetImportData::SanitizeImportFilename(NewReimportPaths[0], ExampleData->GetOutermost());
23 | }
24 | }
25 |
26 | EReimportResult::Type UReimportExampleDataFactory::Reimport(UObject* Obj)
27 | {
28 | UExampleData* ExampleData = Cast(Obj);
29 | if (!ExampleData)
30 | {
31 | return EReimportResult::Failed;
32 | }
33 |
34 | const FString Filename = UAssetImportData::ResolveImportFilename(ExampleData->SourceFilePath, ExampleData->GetOutermost());
35 | if (!FPaths::GetExtension(Filename).Equals(TEXT("xmp")))
36 | {
37 | return EReimportResult::Failed;
38 | }
39 |
40 | CurrentFilename = Filename;
41 | FString Data;
42 | if (FFileHelper::LoadFileToString(Data, *CurrentFilename))
43 | {
44 | const TCHAR* Ptr = *Data;
45 | ExampleData->Modify();
46 | ExampleData->MarkPackageDirty();
47 | ExampleData->ExampleString = Ptr;
48 | // save the source file path and timestamp
49 | ExampleData->SourceFilePath = UAssetImportData::SanitizeImportFilename(CurrentFilename, ExampleData->GetOutermost());
50 | }
51 |
52 | return EReimportResult::Succeeded;
53 | }
54 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/ReimportExampleDataFactory.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "ExampleDataFactory.h"
3 | #include "ReimportExampleDataFactory.generated.h"
4 |
5 | UCLASS()
6 | class UReimportExampleDataFactory : public UExampleDataFactory, public FReimportHandler
7 | {
8 | GENERATED_BODY()
9 |
10 | // Begin FReimportHandler interface
11 | virtual bool CanReimport(UObject* Obj, TArray& OutFilenames) override;
12 | virtual void SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) override;
13 | virtual EReimportResult::Type Reimport(UObject* Obj) override;
14 | // End FReimportHandler interface
15 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/XMPAssetTypeActions_Base.cpp:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 |
4 | #include "XMPAssetTypeActions_Base.h"
5 | #include "ToolExample/CustomDataType/ExampleData.h"
6 | #include "EditorReimportHandler.h"
7 | #include "EditorFramework/AssetImportData.h"
8 | #include "Misc/FileHelper.h"
9 |
10 | bool FXMPAssetTypeActions_Base::CanReimport(UObject* Obj, TArray& OutFilenames)
11 | {
12 | UExampleData* ExampleData = Cast(Obj);
13 | if (ExampleData)
14 | {
15 | OutFilenames.Add(UAssetImportData::ResolveImportFilename(ExampleData->SourceFilePath, ExampleData->GetOutermost()));
16 | return true;
17 | }
18 | return false;
19 | }
20 |
21 | void FXMPAssetTypeActions_Base::SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths)
22 | {
23 | check(NewReimportPaths.IsValidIndex(0));
24 | if (UExampleData* ExampleData = Cast(Obj))
25 | {
26 | ExampleData->SourceFilePath = UAssetImportData::SanitizeImportFilename(NewReimportPaths[0], ExampleData->GetOutermost());
27 | }
28 | }
29 |
30 | EReimportResult::Type FXMPAssetTypeActions_Base::Reimport(UObject* Obj)
31 | {
32 | UExampleData* ExampleData = Cast(Obj);
33 | if (!ExampleData)
34 | {
35 | return EReimportResult::Failed;
36 | }
37 |
38 | const FString Filename = UAssetImportData::ResolveImportFilename(ExampleData->SourceFilePath, ExampleData->GetOutermost());
39 |
40 | FString Data;
41 | if (FFileHelper::LoadFileToString(Data, *Filename))
42 | {
43 | const TCHAR* Ptr = *Data;
44 | ExampleData->Modify();
45 | ExampleData->MarkPackageDirty();
46 |
47 | ExampleData->ExampleString = Ptr;
48 | ExampleData->SourceFilePath = UAssetImportData::SanitizeImportFilename(Filename, ExampleData->GetOutermost());
49 | }
50 |
51 | return EReimportResult::Succeeded;
52 | }
53 |
54 | void FXMPAssetTypeActions_Base::ExecuteReimport(TArray> Objects)
55 | {
56 | for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
57 | {
58 | auto Object = (*ObjIt).Get();
59 | if (Object)
60 | {
61 | Reimport(Object);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/XMPAssetTypeActions_Base.h:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | #pragma once
4 |
5 | #include "CoreMinimal.h"
6 | #include "AssetTypeActions_Base.h"
7 | #include "EditorReimportHandler.h"
8 |
9 | class UExampleData;
10 |
11 | class FXMPAssetTypeActions_Base : public FAssetTypeActions_Base, public FReimportHandler
12 | {
13 | public:
14 | //It won't be shown in the content browser, it is a proxy for an `Example Data` asset
15 | virtual FColor GetTypeColor() const override { return FColor(0, 0, 0); }
16 | virtual bool HasActions(const TArray& InObjects) const override { return true; }
17 | virtual uint32 GetCategories() override { return EAssetTypeCategories::Misc; }
18 | virtual bool IsImportedAsset() const override { return true; }
19 |
20 | virtual bool CanReimport(UObject* Obj, TArray& OutFilenames) override;
21 | virtual void SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) override;
22 | virtual EReimportResult::Type Reimport(UObject* Obj) override;
23 |
24 | protected:
25 | void ExecuteReimport(TArray> Objects);
26 |
27 | };
28 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/XMPImportFactory.cpp:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 |
4 | #include "XMPImportFactory.h"
5 | #include "ToolExample/CustomDataType/ExampleData.h"
6 | #include "EditorFramework/AssetImportData.h"
7 |
8 | #define LOCTEXT_NAMESPACE "XMPImportFactory"
9 |
10 | UXMPImportFactory::UXMPImportFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
11 | {
12 | SupportedClass = UExampleData::StaticClass();
13 | bCreateNew = false;
14 | bEditorImport = true;
15 | bEditAfterNew = true;
16 | bText = true;
17 |
18 | Formats.Add(TEXT("xmp;Example Data"));
19 | }
20 |
21 | FText UXMPImportFactory::GetDisplayName() const
22 | {
23 | return LOCTEXT("XMPImportFactoryDisplayName", "Example Data");
24 | }
25 |
26 | UObject* UXMPImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn, bool& bOutOperationCanceled)
27 | {
28 | GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type);
29 |
30 | UExampleData* ExampleData = CastChecked(NewObject(InParent, InName, Flags));
31 | const int32 NumChars = BufferEnd - Buffer;
32 | ExampleData->ExampleString = FString(NumChars, Buffer);
33 | ExampleData->SourceFilePath = UAssetImportData::SanitizeImportFilename(CurrentFilename, ExampleData->GetOutermost());
34 |
35 | GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, ExampleData);
36 |
37 | return ExampleData;
38 | }
39 |
40 | bool UXMPImportFactory::FactoryCanImport(const FString& Filename)
41 | {
42 | return FPaths::GetExtension(Filename).Equals(TEXT("xmp"));
43 | }
44 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomDataType/XMPImportFactory.h:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | #pragma once
4 |
5 | #include "CoreMinimal.h"
6 | #include "Factories/Factory.h"
7 | #include "Factories/ImportSettings.h"
8 | #include "XMPImportFactory.generated.h"
9 |
10 | UCLASS(customconstructor)
11 | class UXMPImportFactory : public UFactory
12 | {
13 | GENERATED_UCLASS_BODY()
14 |
15 | public:
16 | UXMPImportFactory(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
17 |
18 | virtual FText GetDisplayName() const override;
19 | virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn, bool& bOutOperationCanceled) override;
20 | virtual bool FactoryCanImport(const FString& Filename) override;
21 | };
22 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/CustomProjectSettings/ExampleSettings.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ExampleSettings.generated.h"
4 |
5 | UCLASS(config = EditorUserSettings, defaultconfig)
6 | class UExampleSettings : public UObject
7 | {
8 | GENERATED_BODY()
9 |
10 | UPROPERTY(EditAnywhere, config, Category = Test)
11 | bool bTest = false;
12 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/DetailsCustomization/ExampleActorDetails.cpp:
--------------------------------------------------------------------------------
1 | #include "ExampleActorDetails.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 | #include "ToolExample/DetailsCustomization/ExampleActor.h"
4 |
5 | //#include "DetailsCustomization/ExampleActor.h"
6 |
7 | TSharedRef FExampleActorDetails::MakeInstance()
8 | {
9 | return MakeShareable(new FExampleActorDetails);
10 | }
11 |
12 | void FExampleActorDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
13 | {
14 | TArray> Objects;
15 | DetailLayout.GetObjectsBeingCustomized(Objects);
16 | if (Objects.Num() != 1)
17 | {
18 | // skip customization if select more than one objects
19 | return;
20 | }
21 | AExampleActor* actor = (AExampleActor*)Objects[0].Get();
22 |
23 | // hide original property
24 | DetailLayout.HideProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(AExampleActor, bOption1)));
25 | DetailLayout.HideProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(AExampleActor, bOption2)));
26 |
27 | // add custom widget to "Options" category
28 | IDetailCategoryBuilder& OptionsCategory = DetailLayout.EditCategory("Options", FText::FromString(""), ECategoryPriority::Important);
29 | OptionsCategory.AddCustomRow(FText::FromString("Options"))
30 | .WholeRowContent()
31 | [
32 | SNew(SHorizontalBox)
33 | + SHorizontalBox::Slot()
34 | .AutoWidth()
35 | .VAlign(VAlign_Center)
36 | [
37 | SNew(SCheckBox)
38 | .Style(FAppStyle::Get(), "RadioButton")
39 | .IsChecked(this, &FExampleActorDetails::IsModeRadioChecked, actor, 1)
40 | .OnCheckStateChanged(this, &FExampleActorDetails::OnModeRadioChanged, actor, 1)
41 | [
42 | SNew(STextBlock).Text(FText::FromString("Option 1"))
43 | ]
44 | ]
45 | + SHorizontalBox::Slot()
46 | .AutoWidth()
47 | .Padding(10.f, 0.f, 0.f, 0.f)
48 | .VAlign(VAlign_Center)
49 | [
50 | SNew(SCheckBox)
51 | .Style(FAppStyle::Get(), "RadioButton")
52 | .IsChecked(this, &FExampleActorDetails::IsModeRadioChecked, actor, 2)
53 | .OnCheckStateChanged(this, &FExampleActorDetails::OnModeRadioChanged, actor, 2)
54 | [
55 | SNew(STextBlock).Text(FText::FromString("Option 2"))
56 | ]
57 | ]
58 | ];
59 |
60 | }
61 |
62 | ECheckBoxState FExampleActorDetails::IsModeRadioChecked(AExampleActor* actor, int optionIndex) const
63 | {
64 | bool bFlag = false;
65 | if (actor)
66 | {
67 | if (optionIndex == 1)
68 | bFlag = actor->bOption1;
69 | else if (optionIndex == 2)
70 | bFlag = actor->bOption2;
71 | }
72 | return bFlag ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
73 | }
74 |
75 | void FExampleActorDetails::OnModeRadioChanged(ECheckBoxState CheckType, AExampleActor* actor, int optionIndex)
76 | {
77 | bool bFlag = (CheckType == ECheckBoxState::Checked);
78 | if (actor)
79 | {
80 | actor->Modify();
81 | if (bFlag)
82 | {
83 | // clear all options first
84 | actor->bOption1 = false;
85 | actor->bOption2 = false;
86 | }
87 | if (optionIndex == 1)
88 | actor->bOption1 = bFlag;
89 | else if (optionIndex == 2)
90 | actor->bOption2 = bFlag;
91 | }
92 | }
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/DetailsCustomization/ExampleActorDetails.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "IDetailCustomization.h"
4 |
5 | class AExampleActor;
6 |
7 | class FExampleActorDetails : public IDetailCustomization
8 | {
9 | public:
10 | /** Makes a new instance of this detail layout class for a specific detail view requesting it */
11 | static TSharedRef MakeInstance();
12 |
13 | /** IDetailCustomization interface */
14 | virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override;
15 |
16 | protected:
17 |
18 | // widget functions
19 | ECheckBoxState IsModeRadioChecked(AExampleActor* actor, int optionIndex) const;
20 | void OnModeRadioChanged(ECheckBoxState CheckType, AExampleActor* actor, int optionIndex);
21 |
22 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/EditorMode/ExampleEdMode.cpp:
--------------------------------------------------------------------------------
1 | #include "ExampleEdMode.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 | #include "Toolkits/ToolkitManager.h"
4 | #include "ScopedTransaction.h"
5 | #include "ExampleEdModeToolkit.h"
6 | #include "ToolExample/EditorMode/ExampleTargetPoint.h"
7 |
8 | class ExampleEditorCommands : public TCommands
9 | {
10 | public:
11 | ExampleEditorCommands() : TCommands(
12 | "ExampleEditor", // Context name for fast lookup
13 | FText::FromString(TEXT("Example Editor")), // context name for displaying
14 | NAME_None, // Parent
15 | FAppStyle::GetAppStyleSetName())
16 | {
17 | }
18 |
19 | #define LOCTEXT_NAMESPACE ""
20 | virtual void RegisterCommands() override
21 | {
22 | UI_COMMAND(DeletePoint, "Delete Point", "Delete the currently selected point.", EUserInterfaceActionType::Button, FInputGesture(EKeys::Delete));
23 | }
24 | #undef LOCTEXT_NAMESPACE
25 |
26 | public:
27 | TSharedPtr DeletePoint;
28 | };
29 |
30 | IMPLEMENT_HIT_PROXY(HExamplePointProxy, HHitProxy);
31 |
32 | const FEditorModeID FExampleEdMode::EM_Example(TEXT("EM_Example"));
33 |
34 | FExampleEdMode::FExampleEdMode()
35 | {
36 | ExampleEditorCommands::Register();
37 | ExampleEdModeActions = MakeShareable(new FUICommandList);
38 | }
39 |
40 | FExampleEdMode::~FExampleEdMode()
41 | {
42 | ExampleEditorCommands::Unregister();
43 | }
44 |
45 | void FExampleEdMode::MapCommands()
46 | {
47 | const auto &Commands = ExampleEditorCommands::Get();
48 |
49 | ExampleEdModeActions->MapAction(
50 | Commands.DeletePoint,
51 | FExecuteAction::CreateSP(this, &FExampleEdMode::RemovePoint),
52 | FCanExecuteAction::CreateSP(this, &FExampleEdMode::CanRemovePoint));
53 | }
54 |
55 | void FExampleEdMode::Enter()
56 | {
57 | FEdMode::Enter();
58 |
59 | if (!Toolkit.IsValid())
60 | {
61 | Toolkit = MakeShareable(new FExampleEdModeToolkit);
62 | Toolkit->Init(Owner->GetToolkitHost());
63 | }
64 |
65 | // reset
66 | currentSelectedTarget = nullptr;
67 | currentSelectedIndex = -1;
68 |
69 | MapCommands();
70 | }
71 |
72 | void FExampleEdMode::Exit()
73 | {
74 | FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef());
75 | Toolkit.Reset();
76 |
77 | FEdMode::Exit();
78 | }
79 |
80 | void FExampleEdMode::Render(const FSceneView *View, FViewport *Viewport, FPrimitiveDrawInterface *PDI)
81 | {
82 | const FColor normalColor(200, 200, 200);
83 | const FColor selectedColor(255, 128, 0);
84 |
85 | UWorld *World = GetWorld();
86 | for (TActorIterator It(World); It; ++It)
87 | {
88 | AExampleTargetPoint *actor = (*It);
89 | if (actor)
90 | {
91 | FVector actorLoc = actor->GetActorLocation();
92 | for (int i = 0; i < actor->Points.Num(); ++i)
93 | {
94 | bool bSelected = (actor == currentSelectedTarget && i == currentSelectedIndex);
95 | const FColor &color = bSelected ? selectedColor : normalColor;
96 | // set hit proxy and draw
97 | PDI->SetHitProxy(new HExamplePointProxy(actor, i));
98 | PDI->DrawPoint(actor->Points[i], color, 15.f, SDPG_Foreground);
99 | PDI->DrawLine(actor->Points[i], actorLoc, color, SDPG_Foreground);
100 | PDI->SetHitProxy(NULL);
101 | }
102 | }
103 | }
104 |
105 | FEdMode::Render(View, Viewport, PDI);
106 | }
107 |
108 | bool FExampleEdMode::HandleClick(FEditorViewportClient *InViewportClient, HHitProxy *HitProxy, const FViewportClick &Click)
109 | {
110 | bool isHandled = false;
111 |
112 | if (HitProxy)
113 | {
114 | if (HitProxy->IsA(HExamplePointProxy::StaticGetType()))
115 | {
116 | isHandled = true;
117 | HExamplePointProxy *examplePointProxy = (HExamplePointProxy *)HitProxy;
118 | AExampleTargetPoint *actor = Cast(examplePointProxy->RefObject);
119 | int32 index = examplePointProxy->Index;
120 | if (actor && index >= 0 && index < actor->Points.Num())
121 | {
122 | SelectPoint(actor, index);
123 | }
124 | }
125 | }
126 |
127 | if (HitProxy && isHandled && Click.GetKey() == EKeys::RightMouseButton)
128 | {
129 | TSharedPtr MenuWidget = GenerateContextMenu(InViewportClient);
130 | if (MenuWidget.IsValid())
131 | {
132 | FSlateApplication::Get().PushMenu(
133 | Owner->GetToolkitHost()->GetParentWidget(),
134 | FWidgetPath(),
135 | MenuWidget.ToSharedRef(),
136 | FSlateApplication::Get().GetCursorPos(),
137 | FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
138 | }
139 | }
140 |
141 | return isHandled;
142 | }
143 |
144 | bool FExampleEdMode::InputDelta(FEditorViewportClient *InViewportClient, FViewport *InViewport, FVector &InDrag, FRotator &InRot, FVector &InScale)
145 | {
146 | if (InViewportClient->GetCurrentWidgetAxis() == EAxisList::None)
147 | {
148 | return false;
149 | }
150 |
151 | if (HasValidSelection())
152 | {
153 | if (!InDrag.IsZero())
154 | {
155 | currentSelectedTarget->Modify();
156 | currentSelectedTarget->Points[currentSelectedIndex] += InDrag;
157 | }
158 | return true;
159 | }
160 |
161 | return false;
162 | }
163 |
164 | bool FExampleEdMode::InputKey(FEditorViewportClient *ViewportClient, FViewport *Viewport, FKey Key, EInputEvent Event)
165 | {
166 | bool isHandled = false;
167 |
168 | if (!isHandled && Event == IE_Pressed)
169 | {
170 | isHandled = ExampleEdModeActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false);
171 | }
172 |
173 | return isHandled;
174 | }
175 |
176 | TSharedPtr FExampleEdMode::GenerateContextMenu(FEditorViewportClient *InViewportClient) const
177 | {
178 | FMenuBuilder MenuBuilder(true, NULL);
179 |
180 | MenuBuilder.PushCommandList(ExampleEdModeActions.ToSharedRef());
181 | MenuBuilder.BeginSection("Example Section");
182 | if (HasValidSelection())
183 | {
184 | // add label for point index
185 | TSharedRef LabelWidget =
186 | SNew(STextBlock)
187 | .Text(FText::FromString(FString::FromInt(currentSelectedIndex)))
188 | .ColorAndOpacity(FLinearColor::Green);
189 | MenuBuilder.AddWidget(LabelWidget, FText::FromString(TEXT("Point Index: ")));
190 | MenuBuilder.AddMenuSeparator();
191 | // add delete point entry
192 | MenuBuilder.AddMenuEntry(ExampleEditorCommands::Get().DeletePoint);
193 | }
194 | MenuBuilder.EndSection();
195 | MenuBuilder.PopCommandList();
196 |
197 | TSharedPtr MenuWidget = MenuBuilder.MakeWidget();
198 | return MenuWidget;
199 | }
200 |
201 | bool FExampleEdMode::ShowModeWidgets() const
202 | {
203 | return true;
204 | }
205 |
206 | bool FExampleEdMode::ShouldDrawWidget() const
207 | {
208 | return true;
209 | }
210 |
211 | bool FExampleEdMode::UsesTransformWidget() const
212 | {
213 | return true;
214 | }
215 |
216 | FVector FExampleEdMode::GetWidgetLocation() const
217 | {
218 | if (HasValidSelection())
219 | {
220 | return currentSelectedTarget->Points[currentSelectedIndex];
221 | }
222 | return FEdMode::GetWidgetLocation();
223 | }
224 |
225 | AExampleTargetPoint *GetSelectedTargetPointActor()
226 | {
227 | TArray selectedObjects;
228 | GEditor->GetSelectedActors()->GetSelectedObjects(selectedObjects);
229 | if (selectedObjects.Num() == 1)
230 | {
231 | return Cast(selectedObjects[0]);
232 | }
233 | return nullptr;
234 | }
235 |
236 | void FExampleEdMode::AddPoint()
237 | {
238 | AExampleTargetPoint *actor = GetSelectedTargetPointActor();
239 | if (actor)
240 | {
241 | const FScopedTransaction Transaction(FText::FromString("Add Point"));
242 |
243 | // add new point, slightly in front of camera
244 | FEditorViewportClient *client = (FEditorViewportClient *)GEditor->GetActiveViewport()->GetClient();
245 | FVector newPoint = client->GetViewLocation() + client->GetViewRotation().Vector() * 50.f;
246 | actor->Modify();
247 | actor->Points.Add(newPoint);
248 | // auto select this new point
249 | SelectPoint(actor, actor->Points.Num() - 1);
250 | }
251 | }
252 |
253 | bool FExampleEdMode::CanAddPoint() const
254 | {
255 | return GetSelectedTargetPointActor() != nullptr;
256 | }
257 |
258 | void FExampleEdMode::RemovePoint()
259 | {
260 | if (HasValidSelection())
261 | {
262 | const FScopedTransaction Transaction(FText::FromString("Remove Point"));
263 |
264 | currentSelectedTarget->Modify();
265 | currentSelectedTarget->Points.RemoveAt(currentSelectedIndex);
266 | // deselect the point
267 | SelectPoint(nullptr, -1);
268 | }
269 | }
270 |
271 | bool FExampleEdMode::CanRemovePoint() const
272 | {
273 | return HasValidSelection();
274 | }
275 |
276 | bool FExampleEdMode::HasValidSelection() const
277 | {
278 | return currentSelectedTarget.IsValid() && currentSelectedIndex >= 0 && currentSelectedIndex < currentSelectedTarget->Points.Num();
279 | }
280 |
281 | void FExampleEdMode::SelectPoint(AExampleTargetPoint *actor, int32 index)
282 | {
283 | currentSelectedTarget = actor;
284 | currentSelectedIndex = index;
285 |
286 | // select this actor only
287 | if (currentSelectedTarget.IsValid())
288 | {
289 | GEditor->SelectNone(true, true);
290 | GEditor->SelectActor(currentSelectedTarget.Get(), true, true);
291 | }
292 | }
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/EditorMode/ExampleEdMode.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "EditorModes.h"
4 | #include "EdMode.h"
5 |
6 | struct HExamplePointProxy : public HHitProxy
7 | {
8 | DECLARE_HIT_PROXY();
9 |
10 | HExamplePointProxy(UObject* InRefObject, int32 InIndex)
11 | : HHitProxy(HPP_UI), RefObject(InRefObject), Index(InIndex)
12 | {}
13 |
14 | UObject* RefObject;
15 | int32 Index;
16 | };
17 |
18 | class AExampleTargetPoint;
19 |
20 | class FExampleEdMode : public FEdMode
21 | {
22 | public:
23 |
24 | const static FEditorModeID EM_Example;
25 |
26 | // FEdMode interface
27 | virtual void Enter() override;
28 | virtual void Exit() override;
29 | virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override;
30 | //virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override;
31 | virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy *HitProxy, const FViewportClick &Click) override;
32 | virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override;
33 | virtual bool InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override;
34 | virtual bool ShowModeWidgets() const override;
35 | virtual bool ShouldDrawWidget() const override;
36 | virtual bool UsesTransformWidget() const override;
37 | virtual FVector GetWidgetLocation() const override;
38 | //virtual bool GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) override;
39 | //virtual bool GetCustomInputCoordinateSystem(FMatrix& InMatrix, void* InData) override;
40 | //virtual void ActorSelectionChangeNotify() override;
41 | //virtual void MapChangeNotify() override;
42 | //virtual void SelectionChanged() override;
43 | //virtual bool IsCompatibleWith(FEditorModeID OtherModeID) const override;
44 | // End of FEdMode interface
45 |
46 | FExampleEdMode();
47 | ~FExampleEdMode();
48 |
49 | void AddPoint();
50 | bool CanAddPoint() const;
51 | void RemovePoint();
52 | bool CanRemovePoint() const;
53 | bool HasValidSelection() const;
54 | void SelectPoint(AExampleTargetPoint* actor, int32 index);
55 |
56 | TWeakObjectPtr currentSelectedTarget;
57 | int32 currentSelectedIndex = -1;
58 |
59 |
60 | TSharedPtr ExampleEdModeActions;
61 | void MapCommands();
62 | TSharedPtr GenerateContextMenu(FEditorViewportClient* InViewportClient) const;
63 |
64 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/EditorMode/ExampleEdModeTool.cpp:
--------------------------------------------------------------------------------
1 | #include "ExampleEdModeTool.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 | #include "ExampleEdMode.h"
4 |
5 | #define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush(StyleSet->RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__)
6 |
7 | TSharedPtr< FSlateStyleSet > ExampleEdModeTool::StyleSet = nullptr;
8 |
9 | void ExampleEdModeTool::OnStartupModule()
10 | {
11 | RegisterStyleSet();
12 | RegisterEditorMode();
13 | }
14 |
15 | void ExampleEdModeTool::OnShutdownModule()
16 | {
17 | UnregisterStyleSet();
18 | UnregisterEditorMode();
19 | }
20 |
21 | void ExampleEdModeTool::RegisterStyleSet()
22 | {
23 | // Const icon sizes
24 | const FVector2D Icon20x20(20.0f, 20.0f);
25 | const FVector2D Icon40x40(40.0f, 40.0f);
26 |
27 | // Only register once
28 | if (StyleSet.IsValid())
29 | {
30 | return;
31 | }
32 |
33 | StyleSet = MakeShareable(new FSlateStyleSet("ExampleEdModeToolStyle"));
34 | StyleSet->SetContentRoot(FPaths::ProjectDir() / TEXT("Content/EditorResources"));
35 | StyleSet->SetCoreContentRoot(FPaths::ProjectDir() / TEXT("Content/EditorResources"));
36 |
37 | // Spline editor
38 | {
39 | StyleSet->Set("ExampleEdMode", new IMAGE_BRUSH(TEXT("IconExampleEditorMode"), Icon40x40));
40 | StyleSet->Set("ExampleEdMode.Small", new IMAGE_BRUSH(TEXT("IconExampleEditorMode"), Icon20x20));
41 | }
42 |
43 | FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
44 | }
45 |
46 | void ExampleEdModeTool::UnregisterStyleSet()
47 | {
48 | if (StyleSet.IsValid())
49 | {
50 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
51 | ensure(StyleSet.IsUnique());
52 | StyleSet.Reset();
53 | }
54 | }
55 |
56 | void ExampleEdModeTool::RegisterEditorMode()
57 | {
58 | FEditorModeRegistry::Get().RegisterMode(
59 | FExampleEdMode::EM_Example,
60 | FText::FromString("Example Editor Mode"),
61 | FSlateIcon(StyleSet->GetStyleSetName(), "ExampleEdMode", "ExampleEdMode.Small"),
62 | true, 500
63 | );
64 | }
65 |
66 | void ExampleEdModeTool::UnregisterEditorMode()
67 | {
68 | FEditorModeRegistry::Get().UnregisterMode(FExampleEdMode::EM_Example);
69 | }
70 |
71 | #undef IMAGE_BRUSH
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/EditorMode/ExampleEdModeTool.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ToolExampleEditor/ExampleTabToolBase.h"
4 |
5 | class ExampleEdModeTool : public FExampleTabToolBase
6 | {
7 | public:
8 | virtual void OnStartupModule() override;
9 | virtual void OnShutdownModule() override;
10 |
11 | virtual ~ExampleEdModeTool() {}
12 | private:
13 | static TSharedPtr< class FSlateStyleSet > StyleSet;
14 |
15 | void RegisterStyleSet();
16 | void UnregisterStyleSet();
17 |
18 | void RegisterEditorMode();
19 | void UnregisterEditorMode();
20 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/EditorMode/ExampleEdModeToolkit.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Toolkits/BaseToolkit.h"
4 | #include "ExampleEdMode.h"
5 | #include "SExampleEdModeWidget.h"
6 |
7 | class FExampleEdModeToolkit: public FModeToolkit
8 | {
9 | public:
10 |
11 | FExampleEdModeToolkit()
12 | {
13 | SAssignNew(ExampleEdModeWidget, SExampleEdModeWidget);
14 | }
15 |
16 | /** IToolkit interface */
17 | virtual FName GetToolkitFName() const override { return FName("ExampleEdMode"); }
18 | virtual FText GetBaseToolkitName() const override { return NSLOCTEXT("BuilderModeToolkit", "DisplayName", "Builder"); }
19 | virtual class FEdMode* GetEditorMode() const override { return GLevelEditorModeTools().GetActiveMode(FExampleEdMode::EM_Example); }
20 | virtual TSharedPtr GetInlineContent() const override { return ExampleEdModeWidget; }
21 |
22 | private:
23 |
24 | TSharedPtr ExampleEdModeWidget;
25 | };
26 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/EditorMode/SExampleEdModeWidget.cpp:
--------------------------------------------------------------------------------
1 | #include "SExampleEdModeWidget.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 | #include "ExampleEdMode.h"
4 |
5 | void SExampleEdModeWidget::Construct(const FArguments& InArgs)
6 | {
7 | ChildSlot
8 | [
9 | SNew(SScrollBox)
10 | + SScrollBox::Slot()
11 | .VAlign(VAlign_Top)
12 | .Padding(5.f)
13 | [
14 | SNew(SVerticalBox)
15 | + SVerticalBox::Slot()
16 | .AutoHeight()
17 | .Padding(0.f, 5.f, 0.f, 0.f)
18 | [
19 | SNew(STextBlock)
20 | .Text(FText::FromString(TEXT("This is a editor mode example.")))
21 | ]
22 | + SVerticalBox::Slot()
23 | .AutoHeight()
24 | .Padding(0.f, 5.f, 0.f, 0.f)
25 | [
26 | SNew(SHorizontalBox)
27 | + SHorizontalBox::Slot()
28 | .AutoWidth()
29 | .Padding(2, 0, 0, 0)
30 | .VAlign(VAlign_Center)
31 | [
32 | SNew(SButton)
33 | .Text(FText::FromString("Add"))
34 | .OnClicked(this, &SExampleEdModeWidget::OnAddPoint)
35 | .IsEnabled(this, &SExampleEdModeWidget::CanAddPoint)
36 | ]
37 | + SHorizontalBox::Slot()
38 | .AutoWidth()
39 | .VAlign(VAlign_Center)
40 | .Padding(0, 0, 2, 0)
41 | [
42 | SNew(SButton)
43 | .Text(FText::FromString("Remove"))
44 | .OnClicked(this, &SExampleEdModeWidget::OnRemovePoint)
45 | .IsEnabled(this, &SExampleEdModeWidget::CanRemovePoint)
46 | ]
47 | ]
48 | ]
49 | ];
50 | }
51 |
52 | FExampleEdMode* SExampleEdModeWidget::GetEdMode() const
53 | {
54 | return (FExampleEdMode*)GLevelEditorModeTools().GetActiveMode(FExampleEdMode::EM_Example);
55 | }
56 |
57 | FReply SExampleEdModeWidget::OnAddPoint()
58 | {
59 | GetEdMode()->AddPoint();
60 | return FReply::Handled();
61 | }
62 |
63 | bool SExampleEdModeWidget::CanAddPoint() const
64 | {
65 | return GetEdMode()->CanAddPoint();
66 | }
67 |
68 | FReply SExampleEdModeWidget::OnRemovePoint()
69 | {
70 | GetEdMode()->RemovePoint();
71 | return FReply::Handled();
72 | }
73 |
74 | bool SExampleEdModeWidget::CanRemovePoint() const
75 | {
76 | return GetEdMode()->CanRemovePoint();
77 | }
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/EditorMode/SExampleEdModeWidget.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Framework/APplication/SlateApplication.h"
4 |
5 | class SExampleEdModeWidget : public SCompoundWidget
6 | {
7 | public:
8 | SLATE_BEGIN_ARGS(SExampleEdModeWidget) {}
9 | SLATE_END_ARGS();
10 |
11 | void Construct(const FArguments& InArgs);
12 |
13 | // Util Functions
14 | class FExampleEdMode* GetEdMode() const;
15 |
16 | FReply OnAddPoint();
17 | bool CanAddPoint() const;
18 | FReply OnRemovePoint();
19 | bool CanRemovePoint() const;
20 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/ExampleTabToolBase.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ToolExampleEditor/ToolExampleEditor.h"
4 | #include "ToolExampleEditor/IExampleModuleInterface.h"
5 | #include "Framework/Docking/TabManager.h"
6 | #include "Widgets/Docking/SDockTab.h"
7 |
8 | class FExampleTabToolBase : public IExampleModuleListenerInterface, public TSharedFromThis< FExampleTabToolBase >
9 | {
10 |
11 | public:
12 |
13 | // IPixelopusToolBase
14 | virtual void OnStartupModule() override
15 | {
16 | Initialize();
17 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner(TabName, FOnSpawnTab::CreateRaw(this, &FExampleTabToolBase::SpawnTab))
18 | .SetGroup(FToolExampleEditor::Get().GetMenuRoot())
19 | .SetDisplayName(TabDisplayName)
20 | .SetTooltipText(ToolTipText);
21 | };
22 |
23 | virtual void OnShutdownModule() override
24 | {
25 | FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(TabName);
26 | };
27 |
28 | // In this function set TabName/TabDisplayName/ToolTipText
29 | virtual void Initialize() {};
30 | virtual TSharedRef SpawnTab(const FSpawnTabArgs& TabSpawnArgs) { return SNew(SDockTab); };
31 |
32 | virtual void MakeMenuEntry(FMenuBuilder &menuBuilder)
33 | {
34 | FGlobalTabmanager::Get()->PopulateTabSpawnerMenu(menuBuilder, TabName);
35 | };
36 |
37 | virtual ~FExampleTabToolBase() { }; // this gets rid of a virtual destructor warning, but I'm no sure this is the correct way to handle it - you can also use a pragma to supress the warning
38 | protected:
39 | FName TabName;
40 | FText TabDisplayName;
41 | FText ToolTipText;
42 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/IExampleModuleInterface.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Modules/ModuleManager.h"
4 |
5 |
6 | class IExampleModuleListenerInterface
7 | {
8 | public:
9 | virtual void OnStartupModule() {};
10 | virtual void OnShutdownModule() {};
11 | };
12 |
13 | class IExampleModuleInterface : public IModuleInterface
14 | {
15 | public:
16 | void StartupModule() override
17 | {
18 | if (!IsRunningCommandlet())
19 | {
20 | AddModuleListeners();
21 | for (int32 i = 0; i < ModuleListeners.Num(); ++i)
22 | {
23 | ModuleListeners[i]->OnStartupModule();
24 | }
25 | }
26 | }
27 |
28 | void ShutdownModule() override
29 | {
30 | for (int32 i = 0; i < ModuleListeners.Num(); ++i)
31 | {
32 | ModuleListeners[i]->OnShutdownModule();
33 | }
34 | }
35 |
36 | virtual void AddModuleListeners() {};
37 |
38 | protected:
39 | TArray> ModuleListeners;
40 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/MenuTool/MenuTool.cpp:
--------------------------------------------------------------------------------
1 | #include "MenuTool.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 |
4 | #include "ScopedTransaction.h"
5 |
6 | #define LOCTEXT_NAMESPACE "MenuTool"
7 |
8 | class MenuToolCommands : public TCommands
9 | {
10 | public:
11 | MenuToolCommands()
12 | : TCommands(
13 | TEXT("MenuTool"), // Context name for fast lookup
14 | FText::FromString("Example Menu tool"), // Context name for displaying
15 | NAME_None, // No parent context
16 | FAppStyle::GetAppStyleSetName() // Icon Style Set
17 | )
18 | {
19 | }
20 |
21 | virtual void RegisterCommands() override
22 | {
23 | UI_COMMAND(MenuCommand1, "Menu Command 1", "Test Menu Command 1.", EUserInterfaceActionType::Button, FInputGesture());
24 | UI_COMMAND(MenuCommand2, "Menu Command 2", "Test Menu Command 2.", EUserInterfaceActionType::Button, FInputGesture());
25 | UI_COMMAND(MenuCommand3, "Menu Command 3", "Test Menu Command 3.", EUserInterfaceActionType::Button, FInputGesture());
26 | }
27 |
28 | public:
29 | TSharedPtr MenuCommand1;
30 | TSharedPtr MenuCommand2;
31 | TSharedPtr MenuCommand3;
32 | };
33 |
34 | void MenuTool::MapCommands()
35 | {
36 | const auto &Commands = MenuToolCommands::Get();
37 |
38 | CommandList->MapAction(
39 | Commands.MenuCommand1,
40 | FExecuteAction::CreateSP(this, &MenuTool::MenuCommand1),
41 | FCanExecuteAction());
42 |
43 | CommandList->MapAction(
44 | Commands.MenuCommand2,
45 | FExecuteAction::CreateSP(this, &MenuTool::MenuCommand2),
46 | FCanExecuteAction());
47 |
48 | CommandList->MapAction(
49 | Commands.MenuCommand3,
50 | FExecuteAction::CreateSP(this, &MenuTool::MenuCommand3),
51 | FCanExecuteAction());
52 | }
53 |
54 | void MenuTool::OnStartupModule()
55 | {
56 | CommandList = MakeShareable(new FUICommandList);
57 | MenuToolCommands::Register();
58 | MapCommands();
59 | FToolExampleEditor::Get().AddMenuExtension(
60 | FMenuExtensionDelegate::CreateRaw(this, &MenuTool::MakeMenuEntry),
61 | FName("Section_1"),
62 | CommandList);
63 | }
64 |
65 | void MenuTool::OnShutdownModule()
66 | {
67 | MenuToolCommands::Unregister();
68 | }
69 |
70 | void MenuTool::MakeMenuEntry(FMenuBuilder &menuBuilder)
71 | {
72 | menuBuilder.AddMenuEntry(MenuToolCommands::Get().MenuCommand1);
73 | menuBuilder.AddSubMenu(
74 | FText::FromString("Sub Menu"),
75 | FText::FromString("This is example sub menu"),
76 | FNewMenuDelegate::CreateSP(this, &MenuTool::MakeSubMenu));
77 |
78 | // add tag
79 | TSharedRef AddTagWidget =
80 | SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center)[SNew(SEditableTextBox).MinDesiredWidth(50).Text(this, &MenuTool::GetTagToAddText).OnTextCommitted(this, &MenuTool::OnTagToAddTextCommited)] + SHorizontalBox::Slot().AutoWidth().Padding(5, 0, 0, 0).VAlign(VAlign_Center)[SNew(SButton).Text(FText::FromString("Add Tag")).OnClicked(this, &MenuTool::AddTag)];
81 |
82 | menuBuilder.AddWidget(AddTagWidget, FText::FromString(""));
83 | }
84 |
85 | void MenuTool::MakeSubMenu(FMenuBuilder &menuBuilder)
86 | {
87 | menuBuilder.AddMenuEntry(MenuToolCommands::Get().MenuCommand2);
88 | menuBuilder.AddMenuEntry(MenuToolCommands::Get().MenuCommand3);
89 | }
90 |
91 | void MenuTool::MenuCommand1()
92 | {
93 | UE_LOG(LogClass, Log, TEXT("clicked MenuCommand1"));
94 | }
95 |
96 | void MenuTool::MenuCommand2()
97 | {
98 | UE_LOG(LogClass, Log, TEXT("clicked MenuCommand2"));
99 | }
100 |
101 | void MenuTool::MenuCommand3()
102 | {
103 | UE_LOG(LogClass, Log, TEXT("clicked MenuCommand3"));
104 | }
105 |
106 | FReply MenuTool::AddTag()
107 | {
108 | if (!TagToAdd.IsNone())
109 | {
110 | const FScopedTransaction Transaction(FText::FromString("Add Tag"));
111 | for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
112 | {
113 | AActor *Actor = static_cast(*It);
114 | if (!Actor->Tags.Contains(TagToAdd))
115 | {
116 | Actor->Modify();
117 | Actor->Tags.Add(TagToAdd);
118 | }
119 | }
120 | }
121 |
122 | return FReply::Handled();
123 | }
124 |
125 | FText MenuTool::GetTagToAddText() const
126 | {
127 | return FText::FromName(TagToAdd);
128 | }
129 |
130 | void MenuTool::OnTagToAddTextCommited(const FText &InText, ETextCommit::Type CommitInfo)
131 | {
132 | FString str = InText.ToString();
133 | TagToAdd = FName(*str.TrimStart());
134 | }
135 |
136 | #undef LOCTEXT_NAMESPACE
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/MenuTool/MenuTool.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ToolExampleEditor/IExampleModuleInterface.h"
4 |
5 | class MenuTool : public IExampleModuleListenerInterface, public TSharedFromThis
6 | {
7 | public:
8 | virtual ~MenuTool() {}
9 |
10 | virtual void OnStartupModule() override;
11 | virtual void OnShutdownModule() override;
12 |
13 | void MakeMenuEntry(FMenuBuilder &menuBuilder);
14 | void MakeSubMenu(FMenuBuilder &menuBuilder);
15 |
16 |
17 | protected:
18 | TSharedPtr CommandList;
19 |
20 | void MapCommands();
21 |
22 | //************************
23 | // UI Command functions
24 | void MenuCommand1();
25 | void MenuCommand2();
26 | void MenuCommand3();
27 |
28 | FName TagToAdd;
29 |
30 | FReply AddTag();
31 | FText GetTagToAddText() const;
32 | void OnTagToAddTextCommited(const FText& InText, ETextCommit::Type CommitInfo);
33 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/TabTool/TabTool.cpp:
--------------------------------------------------------------------------------
1 | #include "TabTool.h"
2 | //#include "AssetRegistryModule.h"
3 | //#include "ScopedTransaction.h"
4 | //#include "SDockTab.h"
5 | //#include "SDockableTab.h"
6 | //#include "SDockTabStack.h"
7 | //#include "SlateApplication.h"
8 | #include "ToolExampleEditor/ToolExampleEditor.h"
9 | #include "TabToolPanel.h"
10 |
11 | void TabTool::OnStartupModule()
12 | {
13 | FExampleTabToolBase::OnStartupModule();
14 | FToolExampleEditor::Get().AddMenuExtension(FMenuExtensionDelegate::CreateRaw(this, &TabTool::MakeMenuEntry), FName("Section_2"));
15 | }
16 |
17 | void TabTool::OnShutdownModule()
18 | {
19 | FExampleTabToolBase::OnShutdownModule();
20 | }
21 |
22 | void TabTool::Initialize()
23 | {
24 | TabName = "TabTool";
25 | TabDisplayName = FText::FromString("Tab Tool");
26 | ToolTipText = FText::FromString("Tab Tool Window");
27 | }
28 |
29 | TSharedRef TabTool::SpawnTab(const FSpawnTabArgs& TabSpawnArgs)
30 | {
31 | TSharedRef SpawnedTab = SNew(SDockTab)
32 | .TabRole(ETabRole::NomadTab)
33 | [
34 | SNew(TabToolPanel)
35 | .Tool(SharedThis(this))
36 | ];
37 |
38 | return SpawnedTab;
39 | }
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/TabTool/TabTool.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ToolExampleEditor/ExampleTabToolBase.h"
4 |
5 | class TabTool : public FExampleTabToolBase
6 | {
7 | public:
8 | virtual ~TabTool() {}
9 | virtual void OnStartupModule() override;
10 | virtual void OnShutdownModule() override;
11 | virtual void Initialize() override;
12 | virtual TSharedRef SpawnTab(const FSpawnTabArgs& TabSpawnArgs) override;
13 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/TabTool/TabToolPanel.cpp:
--------------------------------------------------------------------------------
1 | #include "TabToolPanel.h"
2 | #include "ToolExampleEditor/ToolExampleEditor.h"
3 |
4 | void TabToolPanel::Construct(const FArguments& InArgs)
5 | {
6 | tool = InArgs._Tool;
7 |
8 | if (tool.IsValid())
9 | {
10 | // do anything you need from tool object
11 | }
12 |
13 | ChildSlot
14 | [
15 | SNew(SScrollBox)
16 | + SScrollBox::Slot()
17 | .VAlign(VAlign_Top)
18 | .Padding(5)
19 | [
20 | SNew(SBorder)
21 | .BorderBackgroundColor(FColor(192, 192, 192, 255))
22 | .Padding(15.0f)
23 | [
24 | SNew(STextBlock)
25 | .Text(FText::FromString(TEXT("This is a tab example.")))
26 | ]
27 | ]
28 | ];
29 | }
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/TabTool/TabToolPanel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Widgets/Docking/SDockTab.h"
4 | #include "Framework/Application/SlateApplication.h"
5 | #include "TabTool.h"
6 |
7 | class TabToolPanel : public SCompoundWidget
8 | {
9 |
10 | SLATE_BEGIN_ARGS(TabToolPanel)
11 | {
12 | }
13 |
14 | SLATE_ARGUMENT(TWeakPtr, Tool)
15 | SLATE_END_ARGS()
16 |
17 | void Construct(const FArguments &InArgs);
18 |
19 | protected:
20 | TWeakPtr tool;
21 | };
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/ToolExampleEditor.Build.cs:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | using UnrealBuildTool;
4 | using System.IO;
5 |
6 | public class ToolExampleEditor : ModuleRules
7 | {
8 | public ToolExampleEditor(ReadOnlyTargetRules Target) : base(Target)
9 | {
10 |
11 | //PrivatePCHHeaderFile = "ToolExampleEditor.h"; // TODO - see if adding this back in helps much with normal dev cycles
12 |
13 | PublicIncludePaths.AddRange(
14 | new string[] {
15 | // ... add public include paths required here ...
16 | }
17 | );
18 |
19 | PrivateIncludePaths.AddRange(
20 | new string[] {
21 | // ... add other private include paths required here ...
22 | }
23 | );
24 |
25 | PublicDependencyModuleNames.AddRange(
26 | new string[] {
27 | "Core",
28 | "Engine",
29 | "CoreUObject",
30 | "InputCore",
31 | "LevelEditor",
32 | "Slate",
33 | "EditorStyle",
34 | "AssetTools",
35 | "EditorWidgets",
36 | "UnrealEd",
37 | "BlueprintGraph",
38 | "AnimGraph",
39 | "ComponentVisualizers",
40 | "ToolExample"
41 | }
42 | );
43 |
44 | PrivateDependencyModuleNames.AddRange(
45 | new string[]
46 | {
47 | "Core",
48 | "CoreUObject",
49 | "Engine",
50 | "AppFramework",
51 | "SlateCore",
52 | "EditorFramework",
53 | "AnimGraph",
54 | "UnrealEd",
55 | "KismetWidgets",
56 | "MainFrame",
57 | "PropertyEditor",
58 | "ComponentVisualizers",
59 | "ToolExample"
60 | // ... add private dependencies that you statically link with here ...
61 | }
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/ToolExampleEditor.cpp:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | #include "ToolExampleEditor.h"
4 | #include "MenuTool/MenuTool.h"
5 | #include "TabTool/TabTool.h"
6 | #include "EditorMode/ExampleEdModeTool.h"
7 |
8 | #include "ToolExample/DetailsCustomization/ExampleActor.h"
9 | #include "ToolExampleEditor/DetailsCustomization/ExampleActorDetails.h"
10 |
11 | #include "CustomDataType/ExampleDataTypeActions.h"
12 |
13 | #include "ISettingsModule.h"
14 | #include "Developer/Settings/Public/ISettingsContainer.h"
15 | #include "CustomProjectSettings/ExampleSettings.h"
16 |
17 | IMPLEMENT_GAME_MODULE(FToolExampleEditor, ToolExampleEditor)
18 |
19 | TSharedRef FToolExampleEditor::MenuRoot = FWorkspaceItem::NewGroup(FText::FromString("Menu Root"));
20 |
21 | void FToolExampleEditor::AddModuleListeners()
22 | {
23 | // add tools
24 | ModuleListeners.Add(MakeShareable(new MenuTool));
25 | ModuleListeners.Add(MakeShareable(new TabTool));
26 | ModuleListeners.Add(MakeShareable(new ExampleEdModeTool));
27 | }
28 |
29 | void FToolExampleEditor::StartupModule()
30 | {
31 | if (!IsRunningCommandlet())
32 | {
33 | FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor");
34 | LevelEditorMenuExtensibilityManager = LevelEditorModule.GetMenuExtensibilityManager();
35 | MenuExtender = MakeShareable(new FExtender);
36 | MenuExtender->AddMenuBarExtension("Window", EExtensionHook::After, NULL, FMenuBarExtensionDelegate::CreateRaw(this, &FToolExampleEditor::MakePulldownMenu));
37 | LevelEditorMenuExtensibilityManager->AddExtender(MenuExtender);
38 | }
39 |
40 | // register custom layouts
41 | {
42 | static FName PropertyEditor("PropertyEditor");
43 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked(PropertyEditor);
44 | PropertyModule.RegisterCustomClassLayout(AExampleActor::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FExampleActorDetails::MakeInstance));
45 | }
46 |
47 | // register custom types:
48 | {
49 | IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get();
50 | // add custom category
51 | EAssetTypeCategories::Type ExampleCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Example")), FText::FromString("Example"));
52 | // register our custom asset with example category
53 | TSharedPtr Action = MakeShareable(new FExampleDataTypeActions(ExampleCategory));
54 | AssetTools.RegisterAssetTypeActions(Action.ToSharedRef());
55 | // saved it here for unregister later
56 | CreatedAssetTypeActions.Add(Action);
57 | }
58 |
59 | // register settings:
60 | {
61 | ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings");
62 | if (SettingsModule)
63 | {
64 | TSharedPtr ProjectSettingsContainer = SettingsModule->GetContainer("Project");
65 | ProjectSettingsContainer->DescribeCategory("ExampleCategory", FText::FromString("Example Category"), FText::FromString("Example settings description text here"));
66 |
67 | SettingsModule->RegisterSettings("Project", "ExampleCategory", "ExampleSettings",
68 | FText::FromString("Example Settings"),
69 | FText::FromString("Configure Example Settings"),
70 | GetMutableDefault()
71 | );
72 | }
73 | }
74 |
75 | IExampleModuleInterface::StartupModule();
76 | }
77 |
78 | void FToolExampleEditor::ShutdownModule()
79 | {
80 | // unregister custom layouts
81 | if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
82 | {
83 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor");
84 | PropertyModule.UnregisterCustomClassLayout(AExampleActor::StaticClass()->GetFName());
85 | }
86 |
87 | // Unregister all the asset types that we registered
88 | if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
89 | {
90 | IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get();
91 | for (int32 i = 0; i < CreatedAssetTypeActions.Num(); ++i)
92 | {
93 | AssetTools.UnregisterAssetTypeActions(CreatedAssetTypeActions[i].ToSharedRef());
94 | }
95 | }
96 | CreatedAssetTypeActions.Empty();
97 |
98 | // unregister settings
99 | ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings");
100 | if (SettingsModule)
101 | {
102 | SettingsModule->UnregisterSettings("Project", "ExampleCategory", "ExampleSettings");
103 | }
104 |
105 | IExampleModuleInterface::ShutdownModule();
106 | }
107 |
108 |
109 | void FToolExampleEditor::AddMenuExtension(const FMenuExtensionDelegate &extensionDelegate, FName extensionHook, const TSharedPtr &CommandList, EExtensionHook::Position position)
110 | {
111 | MenuExtender->AddMenuExtension(extensionHook, position, CommandList, extensionDelegate);
112 | }
113 |
114 | void FToolExampleEditor::MakePulldownMenu(FMenuBarBuilder &menuBuilder)
115 | {
116 | menuBuilder.AddPullDownMenu(
117 | FText::FromString("Example"),
118 | FText::FromString("Open the Example menu"),
119 | FNewMenuDelegate::CreateRaw(this, &FToolExampleEditor::FillPulldownMenu),
120 | "Example",
121 | FName(TEXT("ExampleMenu"))
122 | );
123 | }
124 |
125 | void FToolExampleEditor::FillPulldownMenu(FMenuBuilder &menuBuilder)
126 | {
127 | menuBuilder.BeginSection("ExampleSection", FText::FromString("Section 1"));
128 | menuBuilder.AddMenuSeparator(FName("Section_1"));
129 | menuBuilder.EndSection();
130 |
131 | menuBuilder.BeginSection("ExampleSection", FText::FromString("Section 2"));
132 | menuBuilder.AddMenuSeparator(FName("Section_2"));
133 | menuBuilder.EndSection();
134 | }
135 |
--------------------------------------------------------------------------------
/Source/ToolExampleEditor/ToolExampleEditor.h:
--------------------------------------------------------------------------------
1 | // Fill out your copyright notice in the Description page of Project Settings.
2 |
3 | #pragma once
4 |
5 | #include "UnrealEd.h"
6 | //#include "Engine.h"
7 | #include "SlateBasics.h"
8 | #include "SlateExtras.h"
9 | //#include "Internationalization.h"
10 | //#include "MultiBoxExtender.h"
11 | #include "Editor/LevelEditor/Public/LevelEditor.h"
12 | #include "Editor/PropertyEditor/Public/PropertyEditing.h"
13 | #include "IAssetTypeActions.h"
14 |
15 | #include "IExampleModuleInterface.h"
16 |
17 | class FToolExampleEditor : public IExampleModuleInterface
18 | {
19 | public:
20 | /** IModuleInterface implementation */
21 | virtual void StartupModule() override;
22 | virtual void ShutdownModule() override;
23 |
24 | virtual void AddModuleListeners() override;
25 |
26 | /**
27 | * Singleton-like access to this module's interface. This is just for convenience!
28 | * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
29 | *
30 | * @return Returns singleton instance, loading the module on demand if needed
31 | */
32 | static inline FToolExampleEditor& Get()
33 | {
34 | return FModuleManager::LoadModuleChecked< FToolExampleEditor >("ToolExampleEditor");
35 | }
36 |
37 | /**
38 | * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
39 | *
40 | * @return True if the module is loaded and ready to use
41 | */
42 | static inline bool IsAvailable()
43 | {
44 | return FModuleManager::Get().IsModuleLoaded("ToolExampleEditor");
45 | }
46 |
47 | void AddMenuExtension(const FMenuExtensionDelegate &extensionDelegate, FName extensionHook, const TSharedPtr &CommandList = NULL, EExtensionHook::Position position = EExtensionHook::Before);
48 | TSharedRef GetMenuRoot() { return MenuRoot; };
49 |
50 | protected:
51 | TSharedPtr LevelEditorMenuExtensibilityManager;
52 | TSharedPtr MenuExtender;
53 |
54 | static TSharedRef MenuRoot;
55 |
56 | TArray> CreatedAssetTypeActions;
57 |
58 | void MakePulldownMenu(FMenuBarBuilder &menuBuilder);
59 | void FillPulldownMenu(FMenuBuilder &menuBuilder);
60 | };
--------------------------------------------------------------------------------
/ToolExample.uproject:
--------------------------------------------------------------------------------
1 | {
2 | "FileVersion": 3,
3 | "EngineAssociation": "5.5",
4 | "Category": "",
5 | "Description": "",
6 | "Modules": [
7 | {
8 | "Name": "ToolExample",
9 | "Type": "Runtime",
10 | "LoadingPhase": "Default"
11 | },
12 | {
13 | "Name": "ToolExampleEditor",
14 | "Type": "Editor",
15 | "LoadingPhase": "PostEngineInit",
16 | "AdditionalDependencies": [
17 | "Engine",
18 | "UnrealEd"
19 | ]
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/docs/images/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/001.png
--------------------------------------------------------------------------------
/docs/images/002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/002.png
--------------------------------------------------------------------------------
/docs/images/003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/003.png
--------------------------------------------------------------------------------
/docs/images/004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/004.png
--------------------------------------------------------------------------------
/docs/images/005.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/005.png
--------------------------------------------------------------------------------
/docs/images/006.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/006.png
--------------------------------------------------------------------------------
/docs/images/007.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/007.png
--------------------------------------------------------------------------------
/docs/images/008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/008.png
--------------------------------------------------------------------------------
/docs/images/009.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/009.png
--------------------------------------------------------------------------------
/docs/images/010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/010.png
--------------------------------------------------------------------------------
/docs/images/011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/011.png
--------------------------------------------------------------------------------
/docs/images/012.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/012.png
--------------------------------------------------------------------------------
/docs/images/013.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/013.png
--------------------------------------------------------------------------------
/docs/images/014.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/014.png
--------------------------------------------------------------------------------
/docs/images/015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/015.png
--------------------------------------------------------------------------------
/docs/images/016.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/016.png
--------------------------------------------------------------------------------
/docs/images/017.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/017.png
--------------------------------------------------------------------------------
/docs/images/018.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/018.png
--------------------------------------------------------------------------------
/docs/images/019.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/019.png
--------------------------------------------------------------------------------
/docs/images/020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/020.png
--------------------------------------------------------------------------------
/docs/images/021.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/021.png
--------------------------------------------------------------------------------
/docs/images/022.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/022.png
--------------------------------------------------------------------------------
/docs/images/023.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/023.png
--------------------------------------------------------------------------------
/docs/images/024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/024.png
--------------------------------------------------------------------------------
/docs/images/025.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/025.png
--------------------------------------------------------------------------------
/docs/images/026.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/026.png
--------------------------------------------------------------------------------
/docs/images/027.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/027.png
--------------------------------------------------------------------------------
/docs/images/028.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/028.png
--------------------------------------------------------------------------------
/docs/images/029.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/029.png
--------------------------------------------------------------------------------
/docs/images/030.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/030.png
--------------------------------------------------------------------------------
/docs/images/031.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/031.png
--------------------------------------------------------------------------------
/docs/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScottKirvan/ToolExample/d183639ef53e1ce7c4bc78bca57d4ef77e2dd889/docs/images/splash.png
--------------------------------------------------------------------------------