using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace PlatypusIdeas.AirPath.Editor {
/// <summary>
/// Welcome window displayed when the AirPath asset is first imported.
/// Shows logo, welcome message, dependencies, and documentation link.
/// </summary>
public class AirPathWelcomeWindow : EditorWindow {
private const string WINDOW_TITLE = "Welcome to AirPath";
private const string DOCUMENTATION_URL = "https://your-documentation-url.com";
private const string LOGO_PATH = "Assets/PlatypusIdeas/AirPath/Samples/Demo/Images/AirPathThumb.jpg";
// Session state key to track if window was shown this session
private const string SHOWN_THIS_SESSION_KEY = "AirPath_WelcomeShown";
// EditorPrefs key for "Don't show again"
private const string DONT_SHOW_AGAIN_KEY = "AirPath_DontShowWelcome";
[MenuItem("Tools/AirPath/Welcome Window")]
public static void ShowWindow() {
var window = GetWindow<AirPathWelcomeWindow>();
window.titleContent = new GUIContent(WINDOW_TITLE);
// 9:16 aspect ratio
window.minSize = new Vector2(405, 720);
window.maxSize = new Vector2(450, 800);
window.Show();
}
/// <summary>
/// Called automatically when Unity loads (after domain reload or import)
/// </summary>
[InitializeOnLoadMethod]
private static void OnProjectLoaded() {
// Delay to ensure Unity is fully loaded
EditorApplication.delayCall += TryShowWelcomeWindow;
}
private static void TryShowWelcomeWindow() {
// Don't show if user checked "Don't show again"
if (EditorPrefs.GetBool(DONT_SHOW_AGAIN_KEY, false)) {
return;
}
// Don't show multiple times per session
if (SessionState.GetBool(SHOWN_THIS_SESSION_KEY, false)) {
return;
}
SessionState.SetBool(SHOWN_THIS_SESSION_KEY, true);
ShowWindow();
}
public void CreateGUI() {
var root = rootVisualElement;
root.style.paddingBottom = 20;
root.style.paddingLeft = 25;
root.style.paddingRight = 25;
root.style.backgroundColor = new Color(0.18f, 0.18f, 0.18f);
// Main container with vertical layout
var container = new VisualElement {
style = {
flexDirection = FlexDirection.Column,
alignItems = Align.Center,
flexGrow = 1
}
};
root.Add(container);
// Logo
AddLogo(container);
// Welcome text
AddWelcomeSection(container);
// Dependencies section
AddDependenciesSection(container);
// Getting started section
AddGettingStartedSection(container);
// Documentation button
AddDocumentationButton(container);
// Don't show again toggle
AddDontShowAgainToggle(container);
}
private void AddLogo(VisualElement parent) {
// Logo aspect ratio is 4:3 (1920x1440)
const float logoWidth = 320f;
const float logoHeight = 240f; // 320 * (1440/1920) = 240
var logoContainer = new VisualElement {
style = {
width = logoWidth,
marginBottom = 20,
height = logoHeight,
alignItems = Align.Center,
justifyContent = Justify.Center
}
};
var logo = AssetDatabase.LoadAssetAtPath<Texture2D>(LOGO_PATH);
if (logo != null) {
var logoImage = new Image {
image = logo,
scaleMode = ScaleMode.StretchToFill,
style = {
width = logoWidth,
height = logoHeight
}
};
logoContainer.Add(logoImage);
} else {
// Placeholder if logo not found
var placeholder = new Label("AirPath") {
style = {
fontSize = 32,
unityFontStyleAndWeight = FontStyle.Bold,
color = new Color(0.4f, 0.7f, 1f),
unityTextAlign = TextAnchor.MiddleCenter
}
};
logoContainer.Add(placeholder);
Debug.LogWarning($"[AirPath] Logo not found at: {LOGO_PATH}");
}
parent.Add(logoContainer);
}
private void AddWelcomeSection(VisualElement parent) {
var welcomeTitle = new Label("Welcome to AirPath!") {
style = {
fontSize = 24,
unityFontStyleAndWeight = FontStyle.Bold,
color = Color.white,
marginBottom = 10,
unityTextAlign = TextAnchor.MiddleCenter
}
};
parent.Add(welcomeTitle);
var welcomeText = new Label(
"Thank you for choosing AirPath, the advanced aerial pathfinding solution for Unity.\n\n" +
"AirPath provides high-performance 3D pathfinding optimized for flying agents, " +
"featuring DOTS integration, terrain-aware navigation, and flexible configuration options."
) {
style = {
fontSize = 13,
color = new Color(0.8f, 0.8f, 0.8f),
marginBottom = 25,
unityTextAlign = TextAnchor.MiddleCenter,
whiteSpace = WhiteSpace.Normal,
maxWidth = 400
}
};
parent.Add(welcomeText);
}
private void AddDependenciesSection(VisualElement parent) {
var dependenciesContainer = new VisualElement {
style = {
backgroundColor = new Color(0.22f, 0.22f, 0.22f),
borderTopLeftRadius = 8,
borderTopRightRadius = 8,
borderBottomLeftRadius = 8,
borderBottomRightRadius = 8,
paddingTop = 15,
paddingBottom = 15,
paddingLeft = 20,
paddingRight = 20,
marginBottom = 25,
width = 380
}
};
var dependenciesTitle = new Label("Required Dependencies") {
style = {
fontSize = 14,
unityFontStyleAndWeight = FontStyle.Bold,
color = Color.white,
marginBottom = 12,
unityTextAlign = TextAnchor.MiddleCenter
}
};
dependenciesContainer.Add(dependenciesTitle);
var dependenciesInfo = new Label(
"These packages are automatically installed via Package Manager:"
) {
style = {
fontSize = 11,
color = new Color(0.6f, 0.6f, 0.6f),
marginBottom = 10,
unityTextAlign = TextAnchor.MiddleCenter,
whiteSpace = WhiteSpace.Normal
}
};
dependenciesContainer.Add(dependenciesInfo);
// Dependency list
var dependencies = new (string name, string version, string status)[] {
("com.unity.burst", "1.8.0+", GetPackageStatus("com.unity.burst")),
("com.unity.collections", "2.1.0+", GetPackageStatus("com.unity.collections")),
("com.unity.mathematics", "1.2.0+", GetPackageStatus("com.unity.mathematics")),
("com.unity.entities", "1.3.14+", GetPackageStatus("com.unity.entities")),
("com.unity.inputsystem", "1.7.0+", GetPackageStatus("com.unity.inputsystem"))
};
foreach (var dep in dependencies) {
var depRow = CreateDependencyRow(dep.name, dep.version, dep.status);
dependenciesContainer.Add(depRow);
}
parent.Add(dependenciesContainer);
}
private VisualElement CreateDependencyRow(string packageName, string version, string status) {
var row = new VisualElement {
style = {
flexDirection = FlexDirection.Row,
justifyContent = Justify.SpaceBetween,
alignItems = Align.Center,
marginTop = 4,
marginBottom = 4
}
};
var nameLabel = new Label($"• {packageName}") {
style = {
fontSize = 12,
color = new Color(0.75f, 0.75f, 0.75f),
flexGrow = 1
}
};
row.Add(nameLabel);
var versionLabel = new Label(version) {
style = {
fontSize = 11,
color = new Color(0.5f, 0.5f, 0.5f),
marginRight = 10
}
};
row.Add(versionLabel);
var isInstalled = status == "Installed";
var statusLabel = new Label(status) {
style = {
fontSize = 11,
color = isInstalled ? new Color(0.4f, 0.8f, 0.4f) : new Color(0.9f, 0.6f, 0.2f),
unityFontStyleAndWeight = FontStyle.Bold,
width = 70,
unityTextAlign = TextAnchor.MiddleRight
}
};
row.Add(statusLabel);
return row;
}
private string GetPackageStatus(string packageName) {
// Check if package is installed via Package Manager
var listRequest = UnityEditor.PackageManager.Client.List(true);
// Since this is synchronous and we can't easily await,
// we'll do a simple check using the packages cache
var packagePath = $"Packages/{packageName}";
if (System.IO.Directory.Exists(packagePath) ||
AssetDatabase.IsValidFolder(packagePath)) {
return "Installed";
}
// Fallback check via assembly presence
return IsPackageAssemblyLoaded(packageName) ? "Installed" : "Pending";
}
private bool IsPackageAssemblyLoaded(string packageName) {
return packageName switch {
"com.unity.burst" => IsTypeAvailable("Unity.Burst.BurstCompiler"),
"com.unity.collections" => IsTypeAvailable("Unity.Collections.NativeArray`1"),
"com.unity.mathematics" => IsTypeAvailable("Unity.Mathematics.float3"),
"com.unity.inputsystem" => IsTypeAvailable("UnityEngine.InputSystem.InputSystem"),
_ => false
};
}
private bool IsTypeAvailable(string typeName) {
foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies()) {
if (assembly.GetType(typeName) != null) {
return true;
}
}
return false;
}
private void AddGettingStartedSection(VisualElement parent) {
var gettingStartedContainer = new VisualElement {
style = {
alignItems = Align.Center,
marginBottom = 15
}
};
var gettingStartedTitle = new Label("Getting Started") {
style = {
fontSize = 16,
unityFontStyleAndWeight = FontStyle.Bold,
color = new Color(0.4f, 0.75f, 1f),
marginBottom = 8,
unityTextAlign = TextAnchor.MiddleCenter
}
};
gettingStartedContainer.Add(gettingStartedTitle);
var gettingStartedText = new Label(
"New to AirPath? I recommend reading the documentation first to get familiar with the setup and core concepts."
) {
style = {
fontSize = 12,
color = new Color(0.7f, 0.7f, 0.7f),
unityTextAlign = TextAnchor.MiddleCenter,
whiteSpace = WhiteSpace.Normal,
maxWidth = 350
}
};
gettingStartedContainer.Add(gettingStartedText);
parent.Add(gettingStartedContainer);
}
private void AddDocumentationButton(VisualElement parent) {
var button = new Button(() => Application.OpenURL(DOCUMENTATION_URL)) {
text = "Open Documentation",
style = {
height = 40,
width = 200,
fontSize = 14,
unityFontStyleAndWeight = FontStyle.Bold,
backgroundColor = new Color(0.2f, 0.5f, 0.8f),
color = Color.white,
borderTopLeftRadius = 6,
borderTopRightRadius = 6,
borderBottomLeftRadius = 6,
borderBottomRightRadius = 6,
marginBottom = 20,
borderTopWidth = 0,
borderBottomWidth = 0,
borderLeftWidth = 0,
borderRightWidth = 0
}
};
// Hover effect
button.RegisterCallback<MouseEnterEvent>(evt => {
button.style.backgroundColor = new Color(0.25f, 0.55f, 0.85f);
});
button.RegisterCallback<MouseLeaveEvent>(evt => {
button.style.backgroundColor = new Color(0.2f, 0.5f, 0.8f);
});
parent.Add(button);
}
private void AddDontShowAgainToggle(VisualElement parent) {
var toggleContainer = new VisualElement {
style = {
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
justifyContent = Justify.Center
}
};
var toggle = new Toggle {
value = EditorPrefs.GetBool(DONT_SHOW_AGAIN_KEY, false),
style = {
marginRight = 5
}
};
toggle.RegisterValueChangedCallback(evt => {
EditorPrefs.SetBool(DONT_SHOW_AGAIN_KEY, evt.newValue);
});
toggleContainer.Add(toggle);
var toggleLabel = new Label("Don't show this window on startup") {
style = {
fontSize = 11,
color = new Color(0.5f, 0.5f, 0.5f)
}
};
toggleContainer.Add(toggleLabel);
parent.Add(toggleContainer);
}
/// <summary>
/// Resets the "Don't show again" preference (useful for testing)
/// </summary>
[MenuItem("Tools/AirPath/Reset Welcome Window Preference")]
public static void ResetWelcomePreference() {
EditorPrefs.DeleteKey(DONT_SHOW_AGAIN_KEY);
SessionState.SetBool(SHOWN_THIS_SESSION_KEY, false);
Debug.Log("[AirPath] Welcome window preference reset. It will show on next domain reload.");
}
}
}