Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
f50cd68
Add DotRecast-based navigation subsystem
Acissathar Aug 5, 2026
a5556c9
Remove dead code - Apply General
Acissathar Aug 7, 2026
1e6099a
Keep UI dirty while assets stream in
michaelsakharov Aug 4, 2026
d6ef0d2
Made transform gizmo 25 pixels bigger
michaelsakharov Aug 4, 2026
dcd5d39
Added some tests to ensure unreadable or unwritable files should neve…
michaelsakharov Aug 4, 2026
0e087d6
Made EngineObject.OnDispose Protected
michaelsakharov Aug 4, 2026
8a05c20
Add end-of-frame EngineObject destruction
michaelsakharov Aug 4, 2026
078f11b
EngineObject.Destroy() Defer dispose to end of frame, Migrated Undo/Redo
michaelsakharov Aug 4, 2026
74a9c50
Added Cursor shapes for Scene Editors and Gizmo's
michaelsakharov Aug 4, 2026
3d18d35
Refactor Scene API
michaelsakharov Aug 4, 2026
89d1c47
Guard hierarchy state for invalid GameObjects
michaelsakharov Aug 4, 2026
0df574a
Always dispose removed components (remove requirement that OnEnabled …
michaelsakharov Aug 4, 2026
f17a23e
Guard RemoveComponent against null input
michaelsakharov Aug 4, 2026
b033f45
Skip re-adding components to same GameObject
michaelsakharov Aug 4, 2026
5d7ece7
Detach component before reassigning owner
michaelsakharov Aug 4, 2026
dee6c94
Snapshot GameObject component queries
michaelsakharov Aug 4, 2026
cb99fce
Guard against reloading current scene
michaelsakharov Aug 4, 2026
7ddc2a6
Make Scene lifecycle ops idempotent
michaelsakharov Aug 4, 2026
1703f83
Dont need to make a copy of GetComponents<MonoBehaviour> anymore
michaelsakharov Aug 4, 2026
8a7af6e
Remove MonoBehaviour array copies in Scene
michaelsakharov Aug 4, 2026
83be85b
Some additional scene/component lifecycle tests
michaelsakharov Aug 4, 2026
4c526cd
Added Scene.DontDestroyOnLoad(go)
michaelsakharov Aug 5, 2026
d37252f
Fixed typo in Character Controller Template
michaelsakharov Aug 6, 2026
d0f2899
Fixed DontDestroyOnLoad preserving objects after play mode as well, m…
xZekro51 Aug 5, 2026
4d70b7a
Modified script recompile to be on project load, so that any outstand…
xZekro51 Aug 5, 2026
7207cac
Trigger script recompile on asset moves
michaelsakharov Aug 6, 2026
497265b
Fix UI anchor parent rect resolution
michaelsakharov Aug 6, 2026
d12f161
Fixes to MeshCollider
michaelsakharov Aug 6, 2026
645b288
Treat indexless UI meshes as empty
michaelsakharov Aug 6, 2026
66ef2ea
Add more utilities to RectTransform
michaelsakharov Aug 6, 2026
fed394a
Fix dropdown closing when clicking items
michaelsakharov Aug 6, 2026
394da16
Centralize default scene creation logic
michaelsakharov Aug 6, 2026
c162ea0
Add launch options for samples in VS Code
PaperPrototype Aug 6, 2026
8d256c0
UI Graphic Base Type
michaelsakharov Aug 6, 2026
99e35a4
Tons of refactors and improvements to GameObject UI
michaelsakharov Aug 6, 2026
0954e15
Remove sealed from Graphic.GetMaterial()
michaelsakharov Aug 6, 2026
36c79da
Avoid lazy-init of font system on atlas version check
michaelsakharov Aug 6, 2026
7181601
Clear layout cache after canvas rebuild
michaelsakharov Aug 6, 2026
ccd2d91
Lazy-capture authored sprite on first use
michaelsakharov Aug 6, 2026
b55d0b3
Fix ParentSize to walk up ancestor chain
michaelsakharov Aug 6, 2026
848e8bd
Fix UI navigation across canvases
michaelsakharov Aug 6, 2026
7e7166a
Fixed Tooltip attribute not working
michaelsakharov Aug 6, 2026
8349b21
Fixed double click and right click -> open, behaving much differently
michaelsakharov Aug 6, 2026
dda53a8
Removed EditorSceneManager.HandleAssetDoubleClick - Obsolete
michaelsakharov Aug 6, 2026
272cf8f
Fixed created assets landing in wrong directories at times
michaelsakharov Aug 6, 2026
b611d0b
Fixed double drawing button attributes in inspector
michaelsakharov Aug 6, 2026
6c5005c
Show empty folder icon for empty folders
michaelsakharov Aug 6, 2026
ae5475c
Bumped version to v1.0-preview-3
michaelsakharov Aug 7, 2026
acd0f7e
Move Enum rename to its own drawer, then cleanup previous references
Acissathar Aug 7, 2026
042d413
Fix range attribute drawing to stay in the property grid
Acissathar Aug 7, 2026
c3b4210
Add DotRecast-based navigation subsystem
Acissathar Aug 5, 2026
81293f0
Swap DotRecast namespaces to Prowl recast
Acissathar Aug 7, 2026
136f369
Adjustments to account for NavMesh workarounds modified upstream in P…
Acissathar Aug 7, 2026
8dee297
Fix agent API contracts
Acissathar Aug 7, 2026
fc489ca
Fix build and data correctness issues from review
Acissathar Aug 7, 2026
85a0010
NavMesh performance tweaks from review
Acissathar Aug 7, 2026
66aed3a
Adjust navmesh thread safety and lock lifetime
Acissathar Aug 8, 2026
0d58393
Make navigation work in the editor and draw its links
Acissathar Aug 8, 2026
69d5cd5
Fix navmesh asset handling and expose the path limits
Acissathar Aug 8, 2026
97745b0
More tweaks to make navigation work in the editor and draw its links
Acissathar Aug 8, 2026
e559063
Coalesce link rebuilds into one pass per frame
Acissathar Aug 8, 2026
35e6569
Copy navmesh data only when the asset database owns it
Acissathar Aug 8, 2026
7684205
Harden the link rebuild drain and tidy the agent API
Acissathar Aug 8, 2026
152426a
Merge branch 'main' into NavMesh-Single-Representation
Acissathar Aug 8, 2026
158f714
Merge branch 'main' into NavMesh-Single-Representation
Acissathar Aug 9, 2026
402bafd
Fix navmesh terrain support, tile seams, and the navmesh debug overlay
Acissathar Aug 11, 2026
8917183
Revert Gizmo Shader changes - not meant to be committed
Acissathar Aug 11, 2026
92e9bd2
Trim down some comments
Acissathar Aug 12, 2026
6afd29d
Merge branch 'main' into NavMesh-Single-Representation
Acissathar Aug 26, 2026
056e1dd
Update Unit Test to account for new Terrain ResizeHeightmap method
Acissathar Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Prowl.Editor.Test/BuildSystemTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,7 @@ public void EveryDesktopTarget_MapsToTheRightRuntimePrefix(string targetId, stri
[InlineData(PlayerSettingsFiles.Time)]
[InlineData(PlayerSettingsFiles.Assets)]
[InlineData(PlayerSettingsFiles.TagsAndLayers)]
[InlineData(PlayerSettingsFiles.Navigation)]
public void EveryFileThePlayerReads_IsExportedByATypeOfThatName(string expected)
{
var entry = Assert.Single(EditorRegistries.SettingsEntries.Where(e => e.Type.Name == expected));
Expand Down
13 changes: 13 additions & 0 deletions Prowl.Editor/AssetsDatabase/Importers/NavMeshDataImporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Prowl.Runtime;

namespace Prowl.Editor.Importers;

/// <summary>
/// Imports .navmesh files - Echo-serialized NavMeshData objects (baked navigation meshes).
/// </summary>
[ImporterFor(".navmesh")]
public class NavMeshDataImporter : AssetImporter
{
public override int Version => 1;
public override bool Import(ImportContext ctx) => ImportHelper.ImportEcho<NavMeshData>(ctx, "nav mesh data");
}
222 changes: 188 additions & 34 deletions Prowl.Editor/GUI/AttributeHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// and are registered by the editor at startup.

using System;
using System.Collections.Generic;
using System.Reflection;

using Prowl.PaperUI;
Expand All @@ -14,6 +15,40 @@

namespace Prowl.Editor.GUI;

/// <summary>
/// The default property-grid row recipe (gutter padding, label width/colour/truncation) for
/// handler-drawn fields, so they align with grid-drawn rows instead of each hand-copying the
/// layout. This is the one place the recipe lives — grid metric changes go here.
/// </summary>
public static class HandlerRowLayout
{
/// <summary>Draw a label + control row matching the default grid rows. The control is
/// drawn inside a stretch-width, row-height box.</summary>
public static void LabelledRow(Paper paper, string id, string label, Action drawControl)
{
var theme = OrigamiUI.Origami.Current;
var m = theme.Metrics;
var font = theme.Font;

using (paper.Row(id).Height(UnitValue.Auto).MinHeight(m.RowHeight)
.Padding(m.PaddingLarge, m.PaddingLarge, 0, 0).RowBetween(m.Padding).Enter())
{
if (font != null && !string.IsNullOrEmpty(label))
{
paper.Box($"{id}_lbl")
.Width(m.LabelWidth).Height(m.RowHeight)
.Margin(0, 0, UnitValue.Stretch(), UnitValue.Stretch())
.IsNotInteractable()
.Text(label, font).TextColor(theme.Ink.C300)
.FontSize(m.FontSize).Alignment(TextAlignment.MiddleLeft).TextTruncate();
}

using (paper.Box($"{id}_ctl").Width(UnitValue.Stretch()).Height(m.RowHeight).Enter())
drawControl();
}
}
}

/// <summary>[Header("text")] - draws a header label above the field.</summary>
public class HeaderAttributeHandler : OrigamiUI.AttributeHandler
{
Expand Down Expand Up @@ -58,6 +93,141 @@ public override bool OnBeforeDraw(Paper paper, string id, Attribute attr, FieldI
}
}

/// <summary>[EnableIf("memberName")] - greys the field out (visible, not editable) while the
/// named bool member is false. Interaction is blocked via BeginReadOnly, and the field is
/// drawn under a faded theme so the disabled state is visually obvious.</summary>
public class EnableIfAttributeHandler : OrigamiUI.AttributeHandler
{
// OnBeforeDraw/OnAfterDraw run as a strict pair on one thread per field; the stack keeps
// nested [EnableIf] objects balanced. The entry carries the pushed-theme scope to pop.
[ThreadStatic] private static Stack<IDisposable?>? t_scopes;

// Faded clone of the active theme, rebuilt only when the theme object changes.
private static OrigamiUI.OrigamiTheme? _dimSource;
private static OrigamiUI.OrigamiTheme? _dimTheme;

private static bool EvaluateCondition(string member, object target)
{
var type = target.GetType();
var condField = type.GetField(member, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (condField != null && condField.FieldType == typeof(bool))
return (bool)(condField.GetValue(target) ?? false);
var condProp = type.GetProperty(member, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (condProp != null && condProp.PropertyType == typeof(bool))
return (bool)(condProp.GetValue(target) ?? false);
return true; // condition not found: leave enabled
}

private static OrigamiUI.OrigamiTheme GetDimmedTheme(OrigamiUI.OrigamiTheme source)
{
if (!ReferenceEquals(_dimSource, source) || _dimTheme == null)
{
var dim = source.Clone();
FadeRamp(dim.Ink); // labels + field text
FadeRamp(dim.Neutral); // field backgrounds/borders
_dimSource = source;
_dimTheme = dim;
}
return _dimTheme;
}

private static void FadeRamp(OrigamiUI.OrigamiRamp ramp)
{
ramp.C100 = Fade(ramp.C100); ramp.C200 = Fade(ramp.C200); ramp.C300 = Fade(ramp.C300);
ramp.C400 = Fade(ramp.C400); ramp.C500 = Fade(ramp.C500); ramp.C600 = Fade(ramp.C600);
ramp.C700 = Fade(ramp.C700);
}

private static System.Drawing.Color Fade(System.Drawing.Color c)
=> System.Drawing.Color.FromArgb((int)(c.A * 0.45f), c.R, c.G, c.B);

/// <summary>
/// Draw a stretch of UI as disabled: blocks interaction (BeginReadOnly) and renders it
/// under the faded theme so the state is visually obvious. Dispose the scope to restore.
/// Reusable by any editor UI that wants the same look as [EnableIf].
/// </summary>
public static IDisposable PushDisabledScope()
{
OrigamiUI.Origami.BeginReadOnly();
IDisposable themeScope = OrigamiUI.Origami.PushTheme(GetDimmedTheme(OrigamiUI.Origami.Current));
return new DisabledScope(themeScope);
}

private sealed class DisabledScope(IDisposable themeScope) : IDisposable
{
private bool _disposed;
public void Dispose()
{
if (_disposed) return;
_disposed = true;
themeScope.Dispose();
OrigamiUI.Origami.EndReadOnly();
}
}

public override bool OnBeforeDraw(Paper paper, string id, Attribute attr, FieldInfo field, object target, int depth)
{
bool disabled = !EvaluateCondition(((EnableIfAttribute)attr).ConditionMember, target);
IDisposable? scope = null;
if (disabled)
{
OrigamiUI.Origami.BeginReadOnly();
scope = OrigamiUI.Origami.PushTheme(GetDimmedTheme(OrigamiUI.Origami.Current));
}
(t_scopes ??= new()).Push(scope); // null = field was enabled
return true;
}

public override void OnAfterDraw(Paper paper, string id, Attribute attr, FieldInfo field, object target, int depth)
{
if (t_scopes == null || t_scopes.Count == 0) return;
IDisposable? scope = t_scopes.Pop();
if (scope == null) return; // was enabled
scope.Dispose();
OrigamiUI.Origami.EndReadOnly();
}
}

/// <summary>[InspectorName("label")] - overrides the field's display label; on enum-typed
/// fields the dropdown also shows each member's own [InspectorName] instead of the raw name.</summary>
public class InspectorNameAttributeHandler : OrigamiUI.AttributeHandler
{
/// <summary>Display name for an enum member: its [InspectorName] if present, else the
/// nicified member name.</summary>
public static string GetEnumDisplayName(Type enumType, object value)
{
string name = Enum.GetName(enumType, value) ?? value.ToString() ?? "";
var attr = enumType.GetField(name)?.GetCustomAttribute<InspectorNameAttribute>();
return attr?.DisplayName ?? PropertyGridUtils.NicifyName(name);
}

public override bool OnDraw(Paper paper, string id, string label, Attribute attr,
FieldInfo field, object target, Action<object?> onChange, int depth)
{
string displayLabel = ((InspectorNameAttribute)attr).DisplayName;
Type type = field.FieldType;

// Non-flags enums get a dropdown honouring per-member display names.
if (type.IsEnum && !type.IsDefined(typeof(FlagsAttribute), false))
{
object value = field.GetValue(target) ?? Enum.GetValues(type).GetValue(0)!;
HandlerRowLayout.LabelledRow(paper, id, displayLabel, () =>
{
var values = new List<object>();
foreach (object v in Enum.GetValues(type)) values.Add(v);
OrigamiUI.Origami.Dropdown(paper, $"{id}_dd", value, v => onChange(v), values)
.Display(v => GetEnumDisplayName(type, v))
.Show();
});
return true;
}

// Everything else: default rendering, relabelled.
PropertyGridUtils.DrawField(paper, id, displayLabel, type, field.GetValue(target), onChange, depth);
return true;
}
}

/// <summary>[ReadOnly] - makes the field non-editable.</summary>
public class ReadOnlyAttributeHandler : OrigamiUI.AttributeHandler
{
Expand All @@ -82,46 +252,25 @@ public override bool OnDraw(Paper paper, string id, string label, Attribute attr
var range = (RangeAttribute)attr;
var value = field.GetValue(target);
var type = field.FieldType;
var theme = OrigamiUI.Origami.Current;
var m = theme.Metrics;
var font = theme.Font;
var ink = theme.Ink;
if (type != typeof(float) && type != typeof(int))
return false; // unsupported type: fall through to default rendering

using (paper.Row(id).Height(UnitValue.Auto).MinHeight(m.RowHeight)
.RowBetween(m.SpacingMedium).Margin(0, 0, 0, m.SpacingSmall).Enter())
HandlerRowLayout.LabelledRow(paper, id, label, () =>
{
if (font != null && !string.IsNullOrEmpty(label))
if (type == typeof(float))
{
paper.Box($"{id}_lbl")
.Width(m.LabelWidth).Height(m.RowHeight)
.Padding(m.PaddingSmall, 0, 0, 0)
.IsNotInteractable()
.Text(label, font).TextColor(ink.C500)
.FontSize(m.FontSize);
float f = (float)(value ?? 0f);
OrigamiUI.Origami.Slider(paper, $"{id}_sl", f,
v => onChange(v), range.Min, range.Max).Format("F2").Show();
}

using (paper.Box($"{id}_ctl").Width(UnitValue.Stretch())
.Height(m.RowHeight).Enter())
else
{
if (type == typeof(float))
{
float f = (float)(value ?? 0f);
OrigamiUI.Origami.Slider(paper, $"{id}_sl", f,
v => onChange(v), range.Min, range.Max).Format("F2").Show();
}
else if (type == typeof(int))
{
int i = (int)(value ?? 0);
OrigamiUI.Origami.Slider(paper, $"{id}_sl", (float)i,
v => onChange((int)MathF.Round(v)), range.Min, range.Max)
.Format("F0").Step(1f).Show();
}
else
{
return false;
}
int i = (int)(value ?? 0);
OrigamiUI.Origami.Slider(paper, $"{id}_sl", (float)i,
v => onChange((int)MathF.Round(v)), range.Min, range.Max)
.Format("F0").Step(1f).Show();
}
}
});

return true;
}
Expand Down Expand Up @@ -178,9 +327,14 @@ public static void Register(OrigamiUI.AttributeHandlerRegistry registry)
registry.Register<HeaderAttribute>(new HeaderAttributeHandler());
registry.Register<SpaceAttribute>(new SpaceAttributeHandler());
registry.Register<ShowIfAttribute>(new ShowIfAttributeHandler());
registry.Register<EnableIfAttribute>(new EnableIfAttributeHandler());
registry.Register<InspectorNameAttribute>(new InspectorNameAttributeHandler());
registry.Register<ReadOnlyAttribute>(new ReadOnlyAttributeHandler());
registry.Register<RangeAttribute>(new RangeAttributeHandler());
registry.Register<TextAreaAttribute>(new TextAreaAttributeHandler());
registry.Register<NavMeshAreaAttribute>(new NavMeshAreaAttributeHandler());
registry.Register<NavMeshAreaMaskAttribute>(new NavMeshAreaMaskAttributeHandler());
registry.Register<NavMeshAgentTypeAttribute>(new NavMeshAgentTypeAttributeHandler());
// TooltipAttribute is handled inline by PropertyGrid row rendering
}
}
Loading