View Issue Details

IDProjectCategoryView StatusLast Update
0005167NoesisGUIUnitypublic2026-08-21 20:24
Reporterstonstad Assigned Tojsantos  
PrioritynormalSeverityblock 
Status resolvedResolutionfixed 
Product Version4.0 
Fixed in Version4.0 
Summary0005167: 4.0 RC1. GatherDependenciesFromSourceFile. ArgumentException: The specified path is not of a legal form
Description

I'm unable to load or view any XAML files in 4.0 RC1. Every XAML control and screen errors out.

ArgumentException: The specified path is not of a legal form (empty).
System.IO.Path.InsecureGetFullPath (System.String path) (at <816f70bc2a9c4332b0bd119906a8d40e>:0)
System.IO.Path.GetFullPath (System.String path) (at <816f70bc2a9c4332b0bd119906a8d40e>:0)
NoesisXamlImporter.GatherDependenciesFromSourceFile (System.String path) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Editor/NoesisXamlImporter.cs:244)
UnityEngine.Debug:LogException(Exception)
NoesisXamlImporter:GatherDependenciesFromSourceFile(String) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Editor/NoesisXamlImporter.cs:252)
UnityEditor.AssetImporters.ScriptedImporter:GatherAllImportedAssetDependencyGUIDs(Type, String)

Attached Files
image.png (875,630 bytes)
BorderControlBorderless.cs (8,636 bytes)   
using Noesis;
using NoesisGUIExtensions;
using StellarConquest.Model.Universe;

namespace StellarConquest.Presentation.Unity.UI
{
    public partial class BorderControlBorderless : ContentControl
    {
        private Path _PanelBlur;
        private Path _PanelColor;

        private Path _DecorTop;
        private Path _DecorBottom;

        public event EventHandler Ready;

        private float _ContentWidth = 0;
        private float _ContentHeight = 0;

        public static readonly DependencyProperty PanelFillBrushProperty = DependencyProperty.Register("PanelFillBrush", typeof(Brush), typeof(BorderControlBorderless));
        public static readonly DependencyProperty PanelStrokeBrushProperty = DependencyProperty.Register("PanelStrokeBrush", typeof(Brush), typeof(BorderControlBorderless));
        public static readonly DependencyProperty BlurLayerProperty = DependencyProperty.Register("BlurLayer", typeof(FrameworkElement), typeof(BorderControlBorderless), new PropertyMetadata(null));

        private bool _TemplateApplied = false;

        public Brush PanelFillBrush
        {
            get { return GetValue(PanelFillBrushProperty) as Brush; }
            set { SetValue(PanelFillBrushProperty, value); }
        }

        public Brush PanelStrokeBrush
        {
            get { return GetValue(PanelStrokeBrushProperty) as Brush; }
            set { SetValue(PanelStrokeBrushProperty, value); }
        }

        public FrameworkElement BlurLayer
        {
            get { return (FrameworkElement)GetValue(BlurLayerProperty); }
            set { SetValue(BlurLayerProperty, value); }
        }

        public BorderControlBorderless()
        {
            Style = FindResource("BorderControlBorderless") as Style;
            Loaded += (sender, e) => Build();
            SizeChanged += (sender, e) => Build();

            PanelFillBrush = FindResource("DefaultUI.Brush.Bottom.Gradient") as Brush;
            PanelStrokeBrush = FindResource("DefaultUI.Brush.Border") as Brush;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (Template != null)
            {
                _PanelBlur = GetTemplateChild("_PanelBlur") as Path;
                _PanelColor = GetTemplateChild("_PanelColor") as Path;

                _DecorTop = GetTemplateChild("_DecorTop") as Path;
                _DecorBottom = GetTemplateChild("_DecorBottom") as Path;

                _TemplateApplied = true;
            }
        }

        public void Resize()
        {
            if (_TemplateApplied && Parent != null && View != null)
            {
                _ContentWidth = 0;
                _ContentHeight = 0;

                _PanelBlur.Data = null;
                _PanelColor.Data = null;

                UpdateLayout();

                Build();
            }
        }

        private void Build()
        {
            if (!_TemplateApplied)
                return;

            FrameworkElement content = (FrameworkElement)Content;

            float contentWidth;
            float contentHeight;

            if (content != null)
            {
                contentWidth = content.ActualWidth + content.Margin.Left + content.Margin.Right - 2;
                contentHeight = content.ActualHeight + content.Margin.Top + content.Margin.Bottom - 2;
            }
            else
            {
                contentWidth = Width;
                contentHeight = Height;
            }

            // we should compare via equals.  But here we do a less than comparison due to rounding
            // errors in the path data. The ideal solution is to recreate the path using integer values
            // to eliminate rounding errors
            if (contentWidth <= _ContentWidth && contentHeight <= _ContentHeight)
                return;

            // Noesis Bug FIX. Noesis sometimes resets ResourcesCommon which
            // affects TopBlurRadius and BottomBlurRadius. To prevent a zero value
            // from affecting background effects, here, we reapply the values.
            PlayerPreferences.Instance.ApplyBackgroundBlur();

            _ContentWidth = contentWidth;
            _ContentHeight = contentHeight;

            // primary dimensions
            float width = _ContentWidth;
            float height = _ContentHeight;

            // main panel
            float bevelSize = 10.0f;

            StreamGeometry geometry = new StreamGeometry();
            using (StreamGeometryContext ctx = geometry.Open())
            {
                // Top Left Bevel
                ctx.BeginFigure(new Point(bevelSize, 0.0f), true, true);
                ctx.LineTo(new Point(width - bevelSize, 0.0f), true, false);  // Top Edge

                // Top Right Bevel
                ctx.LineTo(new Point(width, bevelSize), true, false);
                ctx.LineTo(new Point(width, height - bevelSize), true, false);  // Right Edge

                // Bottom Right Bevel
                ctx.LineTo(new Point(width - bevelSize, height), true, false);
                ctx.LineTo(new Point(bevelSize, height), true, false);  // Bottom Edge

                // Bottom Left Bevel
                ctx.LineTo(new Point(0.0f, height - bevelSize), true, false);
                ctx.LineTo(new Point(0.0f, bevelSize), true, false);  // Left Edge

                ctx.Close();
            }

            _PanelBlur.Data = geometry;
            _PanelColor.Data = geometry;

            // top decor
            float thickness = 1.5f;
            StreamGeometry topGeometry = new StreamGeometry();
            using (StreamGeometryContext ctx = topGeometry.Open())
            {
                ctx.BeginFigure(new Point(0f, bevelSize), true, true);
                ctx.LineTo(new Point(bevelSize, 0f), true, false);
                ctx.LineTo(new Point(width - bevelSize, 0f), true, false);
                ctx.LineTo(new Point(width, bevelSize), true, false);
                ctx.LineTo(new Point(width - bevelSize, thickness), true, false);
                ctx.LineTo(new Point(bevelSize, thickness), true, false);
                ctx.Close();
            }
            _DecorTop.Data = topGeometry;

            // bottom decor
            StreamGeometry bottomGeometry = new StreamGeometry();
            using (StreamGeometryContext ctx = bottomGeometry.Open())
            {
                ctx.BeginFigure(new Point(0f, thickness - bevelSize), true, true);
                ctx.LineTo(new Point(bevelSize, thickness), true, false);
                ctx.LineTo(new Point(width - bevelSize, thickness), true, false);
                ctx.LineTo(new Point(width, thickness - bevelSize), true, false);
                ctx.LineTo(new Point(width - bevelSize, 0f), true, false);
                ctx.LineTo(new Point(bevelSize, 0f), true, false);
                ctx.Close();
            }
            _DecorBottom.Data = bottomGeometry;

            Ready?.Invoke(this, EventArgs.Empty);
        }

        public Geometry ContentGeometry
        {
            get { return _PanelColor.Data; }
        }

        public void SetAlertLevel(AlertLevelType value)
        {
            switch (value)
            {
                case AlertLevelType.Nominal:
                    PanelFillBrush = FindResource("DefaultUI.Brush.Bottom.Gradient") as Brush;
                    PanelStrokeBrush = FindResource("DefaultUI.Brush.Border") as Brush;
                    //DecorFillBrush = FindResource("DefaultUI.Brush.Decor.Gradient") as Brush;
                    break;
                case AlertLevelType.Medium:
                    PanelFillBrush = FindResource("DefaultUI.Brush.Bottom.Gradient.HighAlert") as Brush;
                    PanelStrokeBrush = FindResource("DefaultUI.Brush.Border.HighAlert") as Brush;
                    //DecorFillBrush = FindResource("DefaultUI.Brush.Decor.Gradient.HighAlert") as Brush;
                    break;
                case AlertLevelType.High:
                    PanelFillBrush = FindResource("DefaultUI.Brush.Bottom.Gradient.HighAlert") as Brush;
                    PanelStrokeBrush = FindResource("DefaultUI.Brush.Border.HighAlert") as Brush;
                    //DecorFillBrush = FindResource("DefaultUI.Brush.Decor.Gradient.HighAlert") as Brush;
                    break;
                default:
                    break;
            }
        }

    }
}
BorderControlBorderless.cs (8,636 bytes)   
PlatformAny

Relationships

related to 0005166 resolvedjsantos Package Errors Installing 4.0 RC1. 

Activities

stonstad

stonstad

2026-08-12 20:27

reporter   ~0012487

Full file path is

E:\Source\StellarConquest\StellarConquest.Presentation.Unity\Assets\User Interface\Controls\Border Control Borderless\BorderControlBorderless.cs

InvalidOperationException: Resource not found 'BorderControlBorderless'
Noesis.FrameworkElement.FindResource (System.Object key) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/API/Proxies/FrameworkElementExtend.cs:33)
StellarConquest.Presentation.Unity.UI.BorderControlBorderless..ctor () (at Assets/User Interface/Controls/Border Control Borderless/BorderControlBorderless.cs:46)
(wrapper dynamic-method) System.Object.lambda_method(System.Runtime.CompilerServices.Closure)
Noesis.Extend.CreateInstance (System.IntPtr nativeType, System.IntPtr cPtr) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/API/Core/Extend.cs:6249)
UnityEngine.Debug:LogException(Exception)
NoesisUnity:OnUnhandledException(Exception) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/NoesisUnity.cs:371)
Noesis.Error:UnhandledException(Exception) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/API/Core/Error.cs:18)
Noesis.Extend:CreateInstance(IntPtr, IntPtr) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/API/Core/Extend.cs:6269)
Noesis.GUI:Noesis_LoadComponent(HandleRef, String)
Noesis.GUI:LoadComponent(Object, String) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/API/Core/NoesisGUI.cs:373)
StellarConquest.Presentation.Unity.UI.CrewControl:InitializeComponent() (at Assets/User Interface/Screens/Game/Overlay/Crew Control/CrewControl.cs:71)
StellarConquest.Presentation.Unity.UI.CrewControl:.ctor() (at Assets/User Interface/Screens/Game/Overlay/Crew Control/CrewControl.cs:54)
System.Object:lambda_method(Closure)
Noesis.Extend:CreateInstance(IntPtr, IntPtr) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/API/Core/Extend.cs:6249)
Noesis.GUI:Noesis_LoadStreamXaml(HandleRef, String)
Noesis.GUI:LoadXaml(Stream, String) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/API/Core/NoesisGUI.cs:321)
NoesisXaml:Load() (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Runtime/NoesisXaml.cs:25)
NoesisPostprocessor:Update() (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/package/Editor/NoesisPostprocessor.cs:125)
UnityEditor.EditorApplication:Internal_CallUpdateFunctions()

stonstad

stonstad

2026-08-12 20:29

reporter   ~0012488

image-2.png (75,564 bytes)   
image-2.png (75,564 bytes)   
jsantos

jsantos

2026-08-13 01:53

manager   ~0012495

There are a few errors that I have detected in our new Xaml importer. Please, try replace the following file.

Apart from this, there is something new in v4.0, that is still not documented (Unity and Unreal tutorials hasn't been updated yet).

In your classes, if you have dependencies to assets (for example, UserControl loading a XAML), they attribute AssetDependency must be used to make this dependency explicit.

For example:

[AssetDependency(Packages/com.noesis.noesisgui/Runtime/API/Shaders/Vignette.noesiseffect")]
public class VignetteEffect : ShaderEffect

Do you have many usercontrols (or shaders) in your project?

NoesisXamlImporter.cs (16,248 bytes)   
//#define DEBUG_IMPORTER

using UnityEditor;
using UnityEngine;
using UnityEditor.AssetImporters;
using UnityEngine.Video;
using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;

[ScriptedImporter(2, "xaml")]
class NoesisXamlImporter : ScriptedImporter
{
    [UnityEditor.InitializeOnLoadMethod]
    static void RegisterApplicationResourcesHash()
    {
        string projectPath = System.IO.Path.GetDirectoryName(Application.dataPath);
        string filename = System.IO.Path.Combine(projectPath, "Library", "Noesis", "ApplicationResources_Hash");

        if (File.Exists(filename))
        {
            try
            {
                string hash = File.ReadAllText(filename);
                AssetDatabase.RegisterCustomDependency("Noesis_ApplicationResources", Hash128.Parse(hash));
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        } 
    }

    [Serializable]
    struct Dependencies
    {
        public string hash;
        public long timestamp;
        public List<string> items;
    }

    static string HashFile(string path)
    {
        var hash = new Hash128();
        hash.Append(File.ReadAllText(path));
        return hash.ToString();
    }

    static string HashStr(string str)
    {
        var hash = new Hash128();
        hash.Append(str);
        return hash.ToString();
    }

    static string DependenciesPath()
    {
        string projectPath = System.IO.Path.GetDirectoryName(Application.dataPath);
        return System.IO.Path.Combine(projectPath, "Library", "Noesis", "Dependencies2");
    }

    static bool LoadDependencies(string path, ref Dependencies deps)
    {
        string folder = DependenciesPath();
        string filename = Path.Combine(folder, HashStr(path));

        if (File.Exists(filename))
        {
            try
            {
                string json = File.ReadAllText(filename);
                deps = JsonUtility.FromJson<Dependencies>(json);
                return true;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        return false;
    }

    static void SaveDependencies(string path, Dependencies deps)
    {
        try
        {
            string folder = DependenciesPath();
            System.IO.Directory.CreateDirectory(folder);

            string json = JsonUtility.ToJson(deps);
            File.WriteAllText(Path.Combine(folder, HashStr(path)), json);
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }
    }

    static IEnumerable<string> FindFonts(string uri)
    {
        int index = uri.IndexOf('#');
        if (index != -1)
        {
            string folder = uri.Substring(0, index);
            if (Directory.Exists(folder))
            {
                string family = uri.Substring(index + 1);
                var files = Directory.EnumerateFiles(folder).Where(s => IsFont(s));

                foreach (var font in files)
                {
                    using (FileStream file = File.Open(font, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        if (NoesisUnity.HasFamily(file, family))
                        {
                            yield return font;
                        }
                    }
                }
            }
        }
    }

    static Dependencies GetCachedDependencies(string path)
    {
        Dependencies deps = new Dependencies();
        bool cached = LoadDependencies(path, ref deps);

        long timestamp = 0;

        try
        {
            timestamp = File.GetLastWriteTime(path).Ticks;
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }

        if (cached)
        {
            if (timestamp == deps.timestamp)
            {
                return deps;
            }

            deps.timestamp = timestamp;

            string hash = HashFile(path);
            if (hash == deps.hash)
            {
                SaveDependencies(path, deps);
                return deps;
            }

            deps.hash = hash;
        }
        else
        {
            deps.timestamp = timestamp;
            deps.hash = HashFile(path);
        }

      #if DEBUG_IMPORTER
        Debug.Log($"=> Dependencies {path}");
      #endif

        deps.items = new List<string>();

        bool typesWithAttributeRequested = false;
        TypeCache.TypeCollection typesWithAttribute;

        using (FileStream file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            Noesis.GUI.GetXamlDependencies(file, path, (uri_, type) =>
            {
                try
                {
                    string assembly = Noesis.UriHelper.GetAssembly(uri_);
                    string uri = Noesis.UriHelper.GetPath(uri_);

                    if (assembly == "Noesis.GUI.Extensions")
                    {
                        uri = $"Packages/com.noesis.noesisgui/{uri}";
                    }

                    if (type == Noesis.XamlDependencyType.Filename)
                    {
                        deps.items.Add(uri);
                    }
                    else if (type == Noesis.XamlDependencyType.Font)
                    {
                        foreach (var font in FindFonts(uri))
                        {
                            if (!deps.items.Contains(font))
                            {
                                deps.items.Add(font);
                            }
                        }
                    }
                    else if (type == Noesis.XamlDependencyType.Class)
                    {
                        if (!typesWithAttributeRequested)
                        {
                            typesWithAttributeRequested = true;
                            typesWithAttribute = TypeCache.GetTypesWithAttribute<Noesis.AssetDependencyAttribute>();
                        }

                        foreach (var cachedType in typesWithAttribute)
                        {
                            if (cachedType.FullName == uri)
                            {
                                object[] attributes = cachedType.GetCustomAttributes(typeof(Noesis.AssetDependencyAttribute), false);
                                foreach (Noesis.AssetDependencyAttribute attribute in attributes)
                                {
                                    if (String.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(attribute.AssetPath)))
                                    {
                                        Debug.LogWarning($"[{cachedType.FullName}] Invalid AssetDependency '{attribute.AssetPath}'");
                                    }
                                    else
                                    {
                                        deps.items.Add(attribute.AssetPath);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
            });
        }

        SaveDependencies(path, deps);
        return deps;
    }

    static string[] GatherDependenciesFromSourceFile(string path)
    {
        NoesisUnity.InitCore();
        List<string> deps = new List<string>();

        try
        {
            foreach (var dep in GetCachedDependencies(path).items)
            {
                if (File.Exists(dep))
                {
                    deps.Add(dep);
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }

        return deps.ToArray();
    }

    static bool AddFont(string uri, ref List<NoesisFont> fonts)
    {
        NoesisFont font = AssetDatabase.LoadAssetAtPath<NoesisFont>(uri);

        if (font != null)
        {
            fonts.Add(font);
            return true;
        }

        return false;
    }

    static bool AddTexture(string uri, ref List<Texture> textures, ref List<Sprite> sprites)
    {
        if (AssetImporter.GetAtPath(uri) is TextureImporter textureImporter)
        {
            if (!AssetDatabase.GetLabels(textureImporter).Contains("Noesis"))
            {
                Debug.LogWarning($"{uri} is missing Noesis label");
            }

            if (textureImporter.textureType == TextureImporterType.Sprite &&
                textureImporter.spriteImportMode == SpriteImportMode.Single)
            {
                Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(uri);

                if (sprite != null)
                {
                    sprites.Add(sprite);
                    return true;
                }
            }
            else
            {
                Texture texture = AssetDatabase.LoadAssetAtPath<Texture>(uri);

                if (texture != null)
                {
                    textures.Add(texture);
                    return true;
                }
            }
        }

        return false;
    }

    static bool AddAudio(string uri, ref List<AudioClip> audios)
    {
        AudioClip audio = AssetDatabase.LoadAssetAtPath<AudioClip>(uri);

        if (audio != null)
        {
            audios.Add(audio);
            return true;
        }

        return false;
    }

    static bool AddVideo(string uri, ref List<VideoClip> videos)
    {
        VideoClip video = AssetDatabase.LoadAssetAtPath<VideoClip>(uri);

        if (video != null)
        {
            videos.Add(video);
            return true;
        }

        return false;
    }

    static bool AddRive(string uri, ref List<NoesisRive> rives)
    {
        NoesisRive rive = AssetDatabase.LoadAssetAtPath<NoesisRive>(uri);

        if (rive != null)
        {
            rives.Add(rive);
            return true;
        }

        return false;
    }

    static bool AddXaml(string uri, ref List<NoesisXaml> xamls)
    {
        NoesisXaml xaml = AssetDatabase.LoadAssetAtPath<NoesisXaml>(uri);

        if (xaml != null)
        {
            xamls.Add(xaml);
            return true;
        }

        return false;
    }

    static bool AddShader(string uri, ref List<NoesisShader> shaders)
    {
        NoesisShader shader = AssetDatabase.LoadAssetAtPath<NoesisShader>(uri);

        if (shader != null)
        {
            shaders.Add(shader);
            return true;
        }

        return false;
    }

    static void ScanDependencies(AssetImportContext ctx,
        out List<NoesisFont> fonts_, out List<Texture> textures_, out List<Sprite> sprites_,
        out List<AudioClip> audios_, out List<VideoClip> videos_, out List<NoesisRive> rives_,
        out List<NoesisXaml> xamls_, out List<NoesisShader> shaders_)
    {
        List<NoesisFont> fonts = new List<NoesisFont>();
        List<Texture> textures = new List<Texture>();
        List<Sprite> sprites = new List<Sprite>();
        List<AudioClip> audios = new List<AudioClip>();
        List<VideoClip> videos = new List<VideoClip>();
        List<NoesisRive> rives = new List<NoesisRive>();
        List<NoesisXaml> xamls = new List<NoesisXaml>();
        List<NoesisShader> shaders = new List<NoesisShader>();

        string filename = ctx.assetPath;

        try
        {
            if (HasExtension(filename, ".xaml"))
            {
                // Add dependency to code-behind, just the source, we don't need the artifact
                // Even if the file doesn't exist we add it to get a reimport first time code-behind is created
                ctx.DependsOnSourceAsset(filename + ".cs");
            }

            var dependencies = GetCachedDependencies(filename);

            foreach (var dep in dependencies.items)
            {
                // Register the dependency even if it doesn't currently exist. 
                // This ensures we are re-evaluated if it is imported later.
                ctx.DependsOnArtifact(dep);

                if (!String.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(dep)))
                {
                    if (!AddXaml(dep, ref xamls))
                    {
                        if (!AddTexture(dep, ref textures, ref sprites))
                        {
                            if (!AddFont(dep, ref fonts))
                            {
                                if (!AddAudio(dep, ref audios))
                                {
                                    if (!AddVideo(dep, ref videos))
                                    {
                                        if (!AddShader(dep, ref shaders))
                                        {
                                            AddRive(dep, ref rives);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }

        fonts_ = fonts;
        textures_ = textures;
        sprites_ = sprites;
        audios_ = audios;
        videos_ = videos;
        rives_ = rives;
        xamls_ = xamls;
        shaders_ = shaders;
    }

    public override void OnImportAsset(AssetImportContext ctx)
    {
        NoesisUnity.InitCore();

      #if DEBUG_IMPORTER
        Debug.Log($"=> Import {ctx.assetPath}");
      #endif

        NoesisXaml xaml = (NoesisXaml)ScriptableObject.CreateInstance<NoesisXaml>();
        xaml.uri = ctx.assetPath;
        xaml.content = System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(ctx.assetPath));

        // Add dependencies
        List<NoesisFont> fonts;
        List<Texture> textures;
        List<Sprite> sprites;
        List<AudioClip> audios;
        List<VideoClip> videos;
        List<NoesisRive> rives;
        List<NoesisXaml> xamls;
        List<NoesisShader> shaders;

        ScanDependencies(ctx, out fonts, out textures, out sprites, out audios, out videos, out rives, out xamls, out shaders);

        xaml.xamls = xamls.Select(x => new NoesisXaml.Xaml { uri = AssetDatabase.GetAssetPath(x), xaml = x }).ToArray();
        xaml.textures = textures.Select(x => new NoesisXaml.Texture { uri = AssetDatabase.GetAssetPath(x), texture = x }).ToArray();
        xaml.sprites = sprites.Select(x => new NoesisXaml.Sprite { uri = AssetDatabase.GetAssetPath(x), sprite = x }).ToArray();
        xaml.audios = audios.Select(x => new NoesisXaml.Audio { uri = AssetDatabase.GetAssetPath(x), audio = x }).ToArray();
        xaml.videos = videos.Select(x => new NoesisXaml.Video { uri = AssetDatabase.GetAssetPath(x), video = x }).ToArray();
        xaml.rives = rives.Select(x => new NoesisXaml.Rive { uri = AssetDatabase.GetAssetPath(x), rive = x }).ToArray();
        xaml.fonts = fonts.Select(x => new NoesisXaml.Font { uri = AssetDatabase.GetAssetPath(x), font = x }).ToArray();
        xaml.shaders = shaders.Select(x => new NoesisXaml.Shader { uri = AssetDatabase.GetAssetPath(x), shader = x} ).ToArray();

        // Depends on global dictionary
        ctx.DependsOnCustomDependency("Noesis_ApplicationResources");

        ctx.AddObjectToAsset("XAML", xaml);
        ctx.SetMainObject(xaml);
    }

    static bool HasExtension(string filename, string extension)
    {
        return filename.EndsWith(extension, StringComparison.OrdinalIgnoreCase);
    }

    static bool IsFont(string filename)
    {
        return HasExtension(filename, ".ttf") || HasExtension(filename, ".otf") || HasExtension(filename, ".ttc");
    }
}
NoesisXamlImporter.cs (16,248 bytes)   
jsantos

jsantos

2026-08-13 02:29

manager   ~0012498

The AssetDependency attribute was needed to optimize XAML imports in complex projects with a large number of assets. Previously, we had to “guess” the dependencies across the entire asset database, including packages.

The main issue with AssetDependency (apart from having to add it manually) is that the path becomes invalid if the referenced asset is moved. You will get a warning in that case, but it’s still not ideal.

We are still investigating whether this can be improved

stonstad

stonstad

2026-08-13 15:00

reporter   ~0012501

I'll retry with the updated file. I do not have any shader effects, but I do have plenty of user controls. It isn't clear to me what dependencies my customer user controls might have because they are very simple. They inherit from UserControl.

jsantos

jsantos

2026-08-13 15:25

manager   ~0012503

Thanks!

If for example, your UserControl loads a XAML, you have a dependency to that asset.

Are your UserControls loading XAMLs?

stonstad

stonstad

2026-08-13 15:31

reporter   ~0012505

I uninstalled 3.2.12.
Closed Unity.
Deleted \Library\Noesis.
Installed 4.0.

NoesisGUI v4.0.0-rc1 successfully installed
UnityEngine.Debug:Log (object)
NoesisUpdater:CheckVersion () (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/Editor/NoesisUpdater.cs:46)
UnityEditor.EditorApplication:Internal_CallUpdateFunctions ()

I now have 15 Noesis errors instead of 999:

Assets/User Interface/Screens/Game/Overlay/Spedometer/SpedometerControl.xaml(15,9): Xaml not found 'Assets/User Interface/Controls/Radial Range Control/RadialRangeControl.xaml'
UnityEngine.Debug:LogError (object,UnityEngine.Object)
NoesisPostprocessor/<>c__DisplayClass4_0:<Update>b__1 (string) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/Editor/NoesisPostprocessor.cs:120)
Noesis.GUI:LoadComponent (object,string) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/Runtime/API/Core/NoesisGUI.cs:373)
StellarConquest.Presentation.Unity.UI.RadialRangeControl:InitializeComponent () (at Assets/User Interface/Controls/Radial Range Control/RadialRangeControl.cs:208)
StellarConquest.Presentation.Unity.UI.RadialRangeControl:.ctor () (at Assets/User Interface/Controls/Radial Range Control/RadialRangeControl.cs:203)
(wrapper dynamic-method) object:lambda_method (System.Runtime.CompilerServices.Closure)
Noesis.Extend:CreateInstance (intptr,intptr) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/Runtime/API/Core/Extend.cs:6249)
Noesis.GUI:LoadComponent (object,string) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/4.0 RC1/Runtime/API/Core/NoesisGUI.cs:373)
StellarConquest.Presentation.Unity.UI.SpedometerControl:InitializeComponent () (at Assets/User Interface/Screens/Game/Overlay/Spedometer/SpedometerControl.cs:48)
StellarConquest.Presentation.Unity.UI.SpedometerControl:.ctor () (at Assets/User Interface/Screens/Game/Overlay/Spedometer/

I'll apply the patch and try dependencies.

stonstad

stonstad

2026-08-13 15:36

reporter   ~0012506

Last edited: 2026-08-13 18:28

If for example, your UserControl loads a XAML, you have a dependency to that asset.
Ah, OK. That's like everything. 100+ files.

Here is how I am implemented AssetDependency. Is this correct?

  [AssetDependency("Assets/User Interface/Screens/Game/Overlay/Menu/TitleMenuControl.xaml")]
  public partial class TitleMenuControl : OverlayControl
  {
      public event EventHandler<ScreenSelectedEventArgs> ScreenSelected;
      private FrameworkElement _Root;
      private ListBox _ListBox;

      public TitleMenuControl()
      {
          Name = nameof(TitleMenuControl);
          Initialized += OnInitialized;
          InitializeComponent();
      }

      protected override void InitializeComponent()
      {
          base.InitializeComponent();

          GUI.LoadComponent(this, "Assets/User Interface/Screens/Game/Overlay/Menu/TitleMenuControl.xaml");
          _Root = Content as FrameworkElement;
          _ListBox = _Root.FindName("_ListBox") as ListBox;
      }

Is there a significant cost to do a FIle.Exists for a XAML file of the same name beside the CS file?

** I haven't applied the patch yet because things are working, and I was thinking it is diagnostically useful to show what works and doesn't incrementally.

stonstad

stonstad

2026-08-13 15:41

reporter   ~0012507

Last edited: 2026-08-13 18:28

OK, so the class definition now needs the same thing as the XAML file? Or can I just reference the XAML file and declared dependencies are used? Example, see below:

<UserControl
    x:Class="StellarConquest.Presentation.Unity.UI.GameScreen"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
    xmlns:noesis="clr-namespace:NoesisGUIExtensions"
    xmlns:local="clr-namespace:StellarConquest.Presentation.Unity.UI">
    <noesis:Xaml.Dependencies>
        <noesis:Dependency Source="/Assets/User Interface/Controls/Decor Line Control/DecorLineControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Designer/Overlay/Parts/PartsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Access/AccessControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Building/BuildControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Color/ColorControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Crew Control/CrewControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Commerce Report/CommerceReportControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Communications/CommunicationsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Diplomacy/DiplomacyControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Events/EventControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Examine/ExamineControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Exchange/ExchangeControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Hitpoints/HitpointsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/InputControls/InputControls.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/InterstellarMarket/InterstellarMarketControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Market/MarketControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Menu/ContextMenuControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Menu/OptionsMenuControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Menu/TitleMenuControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Messages/MessagesControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Missions/MissionsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Navigation/NavigationControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Notification/NotificationControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Scan/ScanControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Settings/SettingsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Targeting/TargetDetailControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Shipyard/ShipyardControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Spedometer/SpedometerControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Tutorial/TutorialControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Weapons/WeaponsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/NPC/NPCNeedsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/NPC/NPCBiographyControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/NPC/NPCScheduleControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/NPC/NPCPrioritiesControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Overlay/Weather/WeatherControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Transient/Transfer Colonists/TransferColonistsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Game/Transient/Transfer Resources/TransferResourcesControl.xaml"/>
    </noesis:Xaml.Dependencies>
stonstad

stonstad

2026-08-13 15:50

reporter   ~0012508

I am definitely able to get further with the upgrade! I can see other users being impacted by stale data in \library\noesis -- the errors thrown are definitely confusing.

I started working through the dependencies. If I am understanding the scope of the change, it is a significant body of work.

I have to roll back the upgrade to complete other work. I'll try again when I have a large block of time to address the dependencies requirement.

OK to close issue.

jsantos

jsantos

2026-08-13 18:39

manager   ~0012512

You don't need the <noesis:Xaml.Dependencies> block at all if everything is configured properly. However, it could be a good workaround for now.

The problem is the following. Imagine we have this XAML:

<Page>
  <StackPanel>
    <Image Source="A.png" />
    <MyControl />
  </StackPanel>
</Page>

When we parse that XAML, we need to analyze all its dependencies. For example, in this case, the dependency onA.png is very clear and easy to resolve. But what about MyControl? I don't know where the .cs file associated with that type is, so finding it requires searching the database.

In 3.2, this process was automatic, but it was very slow because it searched for files everywhere, not only in /Assets, but also inside each package. It also has many restrictions and conventions rules.

That's why we added the new AssetDependency metadata. it is very efficient and very flexible.

That said, you're right: I could simplify this and bring back automatic dependency detection as a fallback, like before, but make it more efficient.

I'll try to have something ready for the next version.

jsantos

jsantos

2026-08-13 18:40

manager   ~0012513

Here is how I am implemented AssetDependency. Is this correct?

Yes, the same uri string is duplicated. You could unify it. So at the end, it is just finding all yours GUI.LoadComponent( and adding a metadata.

But wait a bit, let me see if we can auto-detect simple scenarios as said in my previous comment.

stonstad

stonstad

2026-08-13 22:27

reporter   ~0012514

Last edited: 2026-08-13 22:29

I understand the desire to make it efficient. If the needed dependency attribute is missing... a File.Exists check for a file by name is fast, right?

Stopwatch s = Stopwatch.StartNew();
   for (int i = 0; i < 100; i++)
      File.Exists(i.ToString());
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds + "ms");
1ms
jsantos

jsantos

2026-08-14 18:42

manager   ~0012516

Last edited: 2026-08-14 18:42

The issue isn't with calling File.Exists() the problem is knowing where to find the associated asset (the XAML) for a given C# type. That typically requires searching the file system or querying the Unity asset database.

We have simplified the process a bit. Now, for controls located inside the Assets/ folder, the [AssetDependency] attribute can calculate the path automatically. Additionally, you can use NoesisUnity.LoadComponent(this) to load the XAML without explicitly passing the path, as it reads it directly from the attribute metadata.

It would look something like this in your sample:

[AssetDependency]
public partial class TitleMenuControl : OverlayControl
{
    protected override void InitializeComponent()
    {
        NoesisUnity.LoadComponent(this);
    }
jsantos

jsantos

2026-08-14 18:44

manager   ~0012517

Please, give it a try :)

jsantos

jsantos

2026-08-14 20:22

manager   ~0012518

Sorry, this require RC3, still not public.

stonstad

stonstad

2026-08-15 19:19

reporter   ~0012519

Understood. I'll try it in RC3. Does RC3 have fixes for upgrading from 3.2 to 4.0 that you'd like me to test -- by not clearing \library\Noesis?

jsantos

jsantos

2026-08-17 13:09

manager   ~0012525

Latest version is RC4.

This version should upgrade smoothly from 3.2 (apart from the need of using AssetDependency as explained before).

stonstad

stonstad

2026-08-19 19:33

reporter   ~0012530

I removed 3.2.x and added RC5 without deleting \Library\Noesis. Here are the errors encountered.

1)
InvalidOperationException: Failed to add object of type NoesisXaml. Check that the definition is in a file of the same name and that it compiles properly.
UnityEditor.AssetImporters.AssetImportContext.AddObjectToAsset (System.String identifier, UnityEngine.Object obj, UnityEngine.Texture2D thumbnail) (at <b35a5782b5d34eadbc5d7ed7c774d90f>:0)
UnityEditor.AssetImporters.AssetImportContext.AddObjectToAsset (System.String identifier, UnityEngine.Object obj) (at <b35a5782b5d34eadbc5d7ed7c774d90f>:0)
NoesisXamlImporter.OnImportAsset (UnityEditor.AssetImporters.AssetImportContext ctx) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/3.2.12/Editor/NoesisXamlImporter.cs:520)
UnityEditor.AssetImporters.ScriptedImporter.GenerateAssetData (UnityEditor.AssetImporters.AssetImportContext ctx) (at <b35a5782b5d34eadbc5d7ed7c774d90f>:0)

2)
Asset import failed, "Assets/User Interface/Resources.xaml" > InvalidOperationException: Failed to add object of type NoesisXaml. Check that the definition is in a file of the same name and that it compiles properly.
UnityEditor.AssetImporters.AssetImportContext.AddObjectToAsset (System.String identifier, UnityEngine.Object obj, UnityEngine.Texture2D thumbnail) (at <b35a5782b5d34eadbc5d7ed7c774d90f>:0)
UnityEditor.AssetImporters.AssetImportContext.AddObjectToAsset (System.String identifier, UnityEngine.Object obj) (at <b35a5782b5d34eadbc5d7ed7c774d90f>:0)
NoesisXamlImporter.OnImportAsset (UnityEditor.AssetImporters.AssetImportContext ctx) (at E:/Source/StellarConquest/StellarConquest.Utilities/Noesis/3.2.12/Editor/NoesisXamlImporter.cs:520)
UnityEditor.AssetImporters.ScriptedImporter.GenerateAssetData (UnityEditor.AssetImporters.AssetImportContext ctx) (at <b35a5782b5d34eadbc5d7ed7c774d90f>:0)

3)
Can't open Packages/com.noesis.noesisgui/Runtime/API/Shaders/CrossFade.noesisbrush
No Stack Trace

I'll delete \Library\Noesis and I'm sure that will fix the installation.

image-3.png (343,881 bytes)
stonstad

stonstad

2026-08-19 19:37

reporter   ~0012531

Closing Unity, deleting \Library\Noesis, restarting Unity resolved the installation issue(s).

stonstad

stonstad

2026-08-19 20:09

reporter   ~0012532

Last edited: 2026-08-19 21:30

At runtime, I'm seeing AssetDependency errors, despite attribute usage. I wouldn't be surprised to learn I am doing it wrong. Here is an example:

NOESIS] Assets/User Interface/Screens/Title/TitleScreen.xaml(31,29): Xaml not found 'Assets/User Interface/Screens/Title/Button Panel/ButtonPanelControl.xaml'
UnityEngine.StackTraceUtility:ExtractStackTrace ()
[AssetDependency]
public partial class ButtonPanelControl : UserControl

** Update. I'm seeing over 50+ Asset Dependency errors for user controls, despite having the [AssetDependency] attribute.

ButtonPanelControl.xaml (3,665 bytes)   
<UserControl
    x:Class="StellarConquest.Presentation.Unity.UI.ButtonPanelControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors"     
    xmlns:local="clr-namespace:StellarConquest.Presentation.Unity.UI"    
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
    d:DesignWidth="1280" d:DesignHeight="720">

    <Grid x:Name="_LayoutRoot" KeyboardNavigation.DirectionalNavigation="Continue" Margin="0, 0, 0, 0" HorizontalAlignment="Center" VerticalAlignment="Center">
        <local:BorderControlBorderless x:Name="_BorderControl" BlurLayer="{DynamicResource BaseBlurSource}">
            <Grid Margin="20" HorizontalAlignment="Center" VerticalAlignment="Center">
                <StackPanel x:Name="_PlayStackPanel1" HorizontalAlignment="Center" VerticalAlignment="Top">
                    <Button x:Name="_PlayButton" Margin="0,0,0,8" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">PLAY</Button>
                    <Button x:Name="_OptionsButton" Margin="0,0,0,8" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">SETTINGS</Button>
                    <Button x:Name="_CreditsButton" Margin="0,0,0,22" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">CREDITS</Button>

                    <Button x:Name="_QuitButton" Margin="0,0,0,0" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">QUIT</Button>
                </StackPanel>
                <StackPanel x:Name="_PlayStackPanel2" HorizontalAlignment="Center" VerticalAlignment="Top" Visibility="Collapsed">
                    <Button x:Name="_ResumeButton" Margin="0,0,0,22" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120" Visibility="Visible">RESUME</Button>

                    <Grid x:Name="_ConquestButtonGrid" Margin="0,0,0,8" Background="Transparent">
                        <Button x:Name="_ConquestButton" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">CONQUEST</Button>
                    </Grid>
                    <Button x:Name="_SurvivalButton" Margin="0,0,0,8" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120" Visibility="Collapsed">SURVIVAL</Button>
                    <Button x:Name="_CreativeButton" Margin="0,0,0,8" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120" Visibility="Collapsed">CREATIVE</Button>
                    <Button x:Name="_DesignerButton" Margin="0,0,0,22" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">DESIGNER</Button>

                    <Button x:Name="_CustomButton" Margin="0,0,0,8" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">NEW GAME</Button>
                    <Button x:Name="_LoadButton" Margin="0,0,0,8" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">LOAD GAME</Button>
                    <Button x:Name="_JoinButton" Margin="0,0,0,22" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">JOIN GAME</Button>

                    <Button x:Name="_LocalizationButton" Margin="0,0,0,22" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120" Visibility="Collapsed">LOCALIZATION</Button>

                    <Button x:Name="_BackButton" Margin="0,0,0,0" Style="{StaticResource DefaultUI.ButtonBlueText}" MinWidth="120">BACK</Button>
                </StackPanel>

            </Grid>
        </local:BorderControlBorderless>
    </Grid>
</UserControl>
ButtonPanelControl.xaml (3,665 bytes)   
ButtonPanelControl.cs (13,431 bytes)   
using Noesis;
using System;
using System.IO;
using UnityEngine;
using GUI = Noesis.GUI;
using NoesisEventArgs = Noesis.EventArgs;
using SystemEventArgs = System.EventArgs;
using Grid = Noesis.Grid;

namespace StellarConquest.Presentation.Unity.UI
{
    [AssetDependency]
    public partial class ButtonPanelControl : UserControl
    {
        public event EventHandler<SystemEventArgs> PlayButtonClicked;
        public event EventHandler<SystemEventArgs> CreditsButtonClicked;
        public event EventHandler<SystemEventArgs> DesignerButtonClicked;
        public event EventHandler<SystemEventArgs> SettingsButtonClicked;
        public event EventHandler<SystemEventArgs> LocalizationButtonClicked;

        public event EventHandler<SystemEventArgs> ResumeButtonClicked;
        public event EventHandler<SystemEventArgs> ConquestButtonClicked;
        public event EventHandler<SystemEventArgs> SurvivalButtonClicked;
        public event EventHandler<SystemEventArgs> CreativeButtonClicked;

        public event EventHandler<SystemEventArgs> LoadButtonClicked;
        public event EventHandler<SystemEventArgs> JoinButtonClicked;

        public event EventHandler<SystemEventArgs> BackButtonClicked;

        private FrameworkElement _Root;

        private BorderControlBorderless _BorderControl;

        private StackPanel _PlayStackPanel1;
        private Button _PlayButton;
        private Button _OptionsButton;
        private Button _CreditsButton;
        private Button _QuitButton;

        private StackPanel _PlayStackPanel2;
        private Button _ResumeButton;
        private Grid _ConquestButtonGrid;
        private Button _ConquestButton;
        private Button _SurvivalButton;
        private Button _CreativeButton;
        private Button _CustomButton;
        private Button _DesignerButton;
        private Button _LocalizationButton;
        private Button _LoadButton;
        private Button _JoinButton;
        private Button _BackButton;

        private NewsControl _NewsControl;

        public ButtonPanelControl()
        {
            if (!Application.isPlaying)
                return;

            Initialized += OnInitialized;
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            GUI.LoadComponent(this, "Assets/User Interface/Screens/Title/Button Panel/ButtonPanelControl.xaml");
            _Root = Content as FrameworkElement;

            _BorderControl = _Root.FindName(nameof(_BorderControl)) as BorderControlBorderless;

            _PlayStackPanel1 = _Root.FindName(nameof(_PlayStackPanel1)) as StackPanel;
            _PlayButton = _Root.FindName("_PlayButton") as Button;
            _OptionsButton = _Root.FindName("_OptionsButton") as Button;
            _CreditsButton = _Root.FindName("_CreditsButton") as Button;
            _QuitButton = _Root.FindName("_QuitButton") as Button;

            _PlayStackPanel2 = _Root.FindName(nameof(_PlayStackPanel2)) as StackPanel;
            _ResumeButton = _Root.FindName(nameof(_ResumeButton)) as Button;

            _ConquestButtonGrid = _Root.FindName(nameof(_ConquestButtonGrid)) as Grid;
            _ConquestButton = _Root.FindName(nameof(_ConquestButton)) as Button;
            _SurvivalButton = _Root.FindName(nameof(_SurvivalButton)) as Button;
            _CreativeButton = _Root.FindName(nameof(_CreativeButton)) as Button;
            _CustomButton = _Root.FindName(nameof(_CustomButton)) as Button;
            _DesignerButton = _Root.FindName("_DesignerButton") as Button;
            _LocalizationButton = _Root.FindName("_LocalizationButton") as Button;
            _LoadButton = _Root.FindName("_LoadButton") as Button;
            _JoinButton = _Root.FindName("_JoinButton") as Button;

            _BackButton = _Root.FindName(nameof(_BackButton)) as Button;

            if (Application.isConsolePlatform || Application.isMobilePlatform)
                _QuitButton.Visibility = Visibility.Collapsed;

            _ConquestButton.IsEnabled = false;
            _LoadButton.Visibility = Visibility.Collapsed;
        }

        private void OnInitialized(object sender, NoesisEventArgs args)
        {
            _PlayButton.Click += (sender2, e2) =>
            {
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                PlayButtonClicked(sender, SystemEventArgs.Empty);

                if (RuntimeSettings.Instance.IsDeveloperMode)
                    _LocalizationButton.Visibility = Visibility.Visible;
                else
                    _LocalizationButton.Visibility = Visibility.Collapsed;
            };

            _OptionsButton.Click += (sender2, e2) =>
            {
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                SettingsButtonClicked(sender, SystemEventArgs.Empty);
            };

            _CreditsButton.Click += (sender2, e2) =>
            {
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                CreditsButtonClicked(sender, SystemEventArgs.Empty);
            };

            _ResumeButton.MouseEnter += (sender2, e2) =>
            {
                _NewsControl.Update($"Resume game '{PlayerPreferences.Instance.PreviousGameName}'.");
            };

            _ResumeButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                ResumeButtonClicked(sender, SystemEventArgs.Empty);
            };

            _ConquestButtonGrid.MouseEnter += (sender2, e2) =>
            {
                string text =
                    "Join a community of empire builders and [b]CONQUER[/b] an unending universe." + Environment.NewLine + Environment.NewLine +
                    "• Limitless Galaxies" + Environment.NewLine +
                    "• Limitless Players (Internet)" + Environment.NewLine +
                    "• Context-Aware Colonist Dialogue (LLM)";

                if (SessionState.Instance.ServerVersion == null)
                    text += Environment.NewLine + Environment.NewLine + "[i]CONQUEST is down for maintenance and will be back up shortly.[/i]";
                else if (SessionState.Instance.ClientVersion != SessionState.Instance.ServerVersion)
                    text += Environment.NewLine + Environment.NewLine + $"[i]UPDATE to version {SessionState.Instance.ServerVersion} to play CONQUEST.[/i]";

                _NewsControl.Update(text);
            };

            _ConquestButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                ConquestButtonClicked(sender, SystemEventArgs.Empty);
            };

            _SurvivalButton.MouseEnter += (sender2, e2) => _NewsControl.Update(
                "Build an empire alone or with a team of friends witin a single hostile galaxy." + Environment.NewLine + Environment.NewLine +
                "• Single Galaxy" + Environment.NewLine +
                "• 1 to 16 Players (Local or Internet)");

            _SurvivalButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                SurvivalButtonClicked(this, SystemEventArgs.Empty);
            };

            _CreativeButton.MouseEnter += (sender2, e2) => _NewsControl.Update(
                "Construct and manage an empire without the threat of conflict." + Environment.NewLine + Environment.NewLine +
                "• Single Galaxy" + Environment.NewLine +
                "• 1 to 16 Players (Local or Internet)");

            _CreativeButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                CreativeButtonClicked(this, SystemEventArgs.Empty);
            };

            _CustomButton.MouseEnter += (sender2, e2) => _NewsControl.Update(
                "Create a [b]NEW GAME[/b] in [b]SURVIVAL[/b] or [b]CREATIVE[/b] mode." + Environment.NewLine + Environment.NewLine +
                "• Single Galaxy" + Environment.NewLine +
                "• 1 to 32 Players (Local or Internet)");

            _CustomButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                SurvivalButtonClicked(sender, SystemEventArgs.Empty);
            };

            _DesignerButton.MouseEnter += (sender2, e2) => _NewsControl.Update(
                "[b]DESIGN[/b] starship, space station, and building blueprints.");

            _DesignerButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                DesignerButtonClicked(sender, SystemEventArgs.Empty);
            };

            _LocalizationButton.MouseEnter += (sender2, e2) => _NewsControl.Update(
               "[b]LOCALIZE[/b] game content to supported languages.");

            _LocalizationButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                LocalizationButtonClicked(sender, SystemEventArgs.Empty);
            };

            _LoadButton.MouseEnter += (sender2, e2) => _NewsControl.Update("[b]LOAD[/b] an existing game.");

            _LoadButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                LoadButtonClicked(sender, SystemEventArgs.Empty);
            };

            _JoinButton.MouseEnter += (sender2, e2) => _NewsControl.Update(
                "[b]JOIN[/b] an existing game (Local or Internet).");

            _JoinButton.Click += (sender2, e2) =>
            {
                IsHitTestVisible = false;
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                JoinButtonClicked(sender, SystemEventArgs.Empty);
            };

            _BackButton.MouseEnter += (sender2, e2) => _NewsControl.Update(string.Empty);

            _BackButton.Click += (sender2, e2) =>
            {
                SoundEffect.PlayUI(SoundEffects.UserInterface.ButtonClick(), 0.25f);
                BackButtonClicked(this, SystemEventArgs.Empty);

                _PlayStackPanel1.Visibility = Visibility.Visible;
                _PlayStackPanel2.Visibility = Visibility.Collapsed;

                _BorderControl.Resize();
            };

            _QuitButton.Click += (sender2, e2) =>
            {
                Application.Quit();
            };
        }

        public void SetPlayButtonEnabled(bool isEnabled)
        {
            _PlayButton.IsEnabled = isEnabled;
        }

        public void SetPlayButtonText(string text)
        {
            _PlayButton.Content = text;
        }

        public void SetDesignerButtonEnabled(bool isEnabled)
        {
            _DesignerButton.IsEnabled = isEnabled;
        }

        public void SetConquestButtonEnabled(bool isEnabled)
        {
            isEnabled = isEnabled && SessionState.Instance.ClientVersion == SessionState.Instance.ServerVersion;

            _ConquestButton.IsEnabled = isEnabled;
            if (isEnabled && InputManager.Instance != null && InputManager.Instance.HasGamepad)
                _ConquestButton.Focus();
        }

        public void Register(NewsControl newsControl)
        {
            _NewsControl = newsControl;
        }

        public void ShowTitleOptions()
        {
            IsHitTestVisible = true;
            _PlayStackPanel1.Visibility = Visibility.Visible;
            _PlayStackPanel2.Visibility = Visibility.Collapsed;
            _BorderControl.Resize();
        }

        public void ShowPlayOptions()
        {
            IsHitTestVisible = true;
            _PlayStackPanel1.Visibility = Visibility.Collapsed;
            _PlayStackPanel2.Visibility = Visibility.Visible;
            string previousGame = PlayerPreferences.Instance.PreviousGameName;

            if (string.IsNullOrEmpty(previousGame) || !ServerControl.SavedGameExists(previousGame))
                _ResumeButton.Visibility = Visibility.Collapsed;
            else
                _ResumeButton.Visibility = Visibility.Visible;

            DirectoryInfo savedGames = new DirectoryInfo(RuntimeSettings.Instance.SavedGamesPath);
            if (savedGames.Exists && savedGames.GetDirectories().Length > 0)
                _LoadButton.Visibility = Visibility.Visible;
            else
                _LoadButton.Visibility = Visibility.Collapsed;

            _NewsControl.Update("Select Game Type");
            _BorderControl.Resize();
        }
    }
}
ButtonPanelControl.cs (13,431 bytes)   
TitleScreen.xaml (7,203 bytes)   
<UserControl
    x:Class="StellarConquest.Presentation.Unity.UI.TitleScreen"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
    xmlns:noesis="clr-namespace:NoesisGUIExtensions"
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors" 
    xmlns:local="clr-namespace:StellarConquest.Presentation.Unity.UI"
    d:DesignWidth="1280" d:DesignHeight="720">
    <noesis:Xaml.Dependencies>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Title/License Control/LicenseControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Title/Localization Control/LocalizationControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Title/Credits Control/CreditsControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Title/Credits Control/LicenseAttributionControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Title/Server Control/ServerControl.xaml"/>
        <noesis:Dependency Source="/Assets/User Interface/Screens/Title/Server Control/ServerListControl.xaml"/>
    </noesis:Xaml.Dependencies>
    <Grid>
        <Viewbox x:Name="LoginScreenViewBox">
            <local:ViewboxGridControl x:Name="_LayoutRoot">
                <Grid>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="1*"/>
                            <RowDefinition Height="6*"/>
                            <RowDefinition Height="1*"/>
                        </Grid.RowDefinitions>

                        <StackPanel x:Name="_LoginStackPanel" Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
                            <local:ButtonPanelControl x:Name="_ButtonPanelControl" VerticalAlignment="Center" Margin="0, -35, 20, 0"/>

                            <StackPanel x:Name="_NewsStackPanel1" Orientation="Vertical" VerticalAlignment="Center">
                                <local:NewsControl x:Name="_NewsControl1" Margin="0, 0, 0, 20"/>
                                <local:SocialMediaPanelControl x:Name="_SocialMediaPanelControl"/>
                            </StackPanel>

                            <StackPanel x:Name="_NewsStackPanel2" Orientation="Vertical" VerticalAlignment="Center" Visibility="Collapsed">
                                <local:NewsControl x:Name="_NewsControl2" Margin="0, 0, 0, 20"/>
                                <Grid Height="50"/>
                            </StackPanel>
                        </StackPanel>

                        <TextBlock x:Name="_Handle" Grid.Row="2" HorizontalAlignment="Right" FontFamily="{StaticResource Teko}" FontSize="35" VerticalAlignment="Bottom" Foreground="White" Opacity="0.9" Margin="0, 0, 60, 35"/>
                        <TextBlock x:Name="_VersionTextBlock" Grid.Row="2" HorizontalAlignment="Center" FontFamily="{StaticResource Teko}" FontSize="22" VerticalAlignment="Bottom" Foreground="White" Opacity="0.7" Margin="0, 0, 0, 40"/>
                    </Grid>

                    <StackPanel x:Name="_SecondaryScreenStackPanel" HorizontalAlignment="Center" VerticalAlignment="Center"/>



                    <Grid x:Name="_PreviewGrid" Background="Transparent" Opacity="0" Visibility="Collapsed">
                        <Grid Background="#99000000" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

                        <Grid HorizontalAlignment="Center" VerticalAlignment="Stretch">
                            <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                                <b:Interaction.Behaviors>
                                    <noesis:BackgroundEffectBehavior Source="{DynamicResource BaseBlurSource}">
                                        <BlurEffect Radius="{Binding Radius, Source={StaticResource TopBlurRadius}}"/>
                                    </noesis:BackgroundEffectBehavior>
                                </b:Interaction.Behaviors>
                            </Border>

                            <Grid Background="#AA000000" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

                            <Rectangle Fill="#66777777" Width="2" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
                            <Rectangle Fill="#66777777" Width="2" HorizontalAlignment="Right" VerticalAlignment="Stretch"/>

                            <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="50, -50, 50, 50">
                                <Viewbox Width="600" Margin="-10, 0, -10, 15">
                                    <Grid>
                                        <Grid.Effect>
                                            <DropShadowEffect BlurRadius="10" ShadowDepth="3" Opacity="1"/>
                                        </Grid.Effect>
                                        <Image x:Name="_LogoEllipse" Source="{StaticResource ellipse}" Width="550" Stretch="Uniform" Margin="0, 0, 0, 100"/>
                                        <TextBlock x:Name="_LogoText" HorizontalAlignment="Center" FontFamily="{StaticResource Agency}" FontSize="95" VerticalAlignment="Bottom" Margin="0, 0, 0, 0" Foreground="White" Opacity="0.9" noesis:Text.CharacterSpacing="350" Text="STELLAR CONQUEST"/>
                                    </Grid>
                                </Viewbox>

                                <StackPanel Orientation="Vertical" MaxWidth="500" Effect="{StaticResource UIDropShadow}">
                                    <TextBlock FontFamily="{StaticResource Teko}" FontSize="40" FontWeight="Normal" HorizontalAlignment="Center" Margin="0, 0, 0, 95">PREVIEW</TextBlock>
                                    <TextBlock FontFamily="{StaticResource DefaultFontFamily}" FontSize="30" FontWeight="Normal" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap" Margin="0, 0, 0, 35">This is an unfinished version of the game. You might encounter broken or missing parts.</TextBlock>
                                    <TextBlock FontFamily="{StaticResource DefaultFontFamily}" FontSize="30" FontWeight="Normal" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap" Margin="0, 0, 0, 35">Saved galaxies won't work with the final release version.</TextBlock>
                                    <TextBlock FontFamily="{StaticResource HeadingFontFamily}" FontSize="30" FontWeight="Bold" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap" Margin="0, 0, 0, 0">Press any key or button to start</TextBlock>
                                </StackPanel>
                            </StackPanel>
                        </Grid>
                    </Grid>

                </Grid>
            </local:ViewboxGridControl>
        </Viewbox>

    </Grid>
</UserControl>
TitleScreen.xaml (7,203 bytes)   
jsantos

jsantos

2026-08-19 21:39

manager   ~0012533

The [AssetDependency] attribute in AUTO requires a few conventions:

  • The script must end with .xaml.cs (ButtonPanelControl.xaml.cs in your case).
  • The XAML file must be located in the same folder as the script, without the .cs extension (ButtonPanelControl.xaml in your case).

If you don't follow these conventions, you will get an error, for example when manually reimporting TitleScreen.xaml.

These conventions were also required in 3.2, but in your case it was working because you were using the deprecated <noesis:Dependency> tag.

If you don't want to follow these conventions, you need to manually specify the path in the attribute:

[AssetDependency("Assets/User Interface/Screens/Title/Button Panel/ButtonPanelControl.xaml")]
stonstad

stonstad

2026-08-20 18:53

reporter   ~0012537

Thank you for clarifying. I completely missed that my file names did not end with ".xaml.cs".

After making all required changes import and playback works as expected for 160+ XAML files. Thanks again for all the help on this one.

Candidate for closure.

jsantos

jsantos

2026-08-21 20:24

manager   ~0012555

THANKS for the feedback and help us with the release of 4.0

Issue History

Date Modified Username Field Change
2026-08-12 20:24 stonstad New Issue
2026-08-12 20:24 stonstad File Added: image.png
2026-08-12 20:24 stonstad File Added: BorderControlBorderless.cs
2026-08-12 20:27 stonstad Note Added: 0012487
2026-08-12 20:29 stonstad Note Added: 0012488
2026-08-12 20:29 stonstad File Added: image-2.png
2026-08-13 01:50 jsantos Relationship added related to 0005166
2026-08-13 01:50 jsantos Assigned To => jsantos
2026-08-13 01:50 jsantos Status new => assigned
2026-08-13 01:53 jsantos Note Added: 0012495
2026-08-13 01:53 jsantos File Added: NoesisXamlImporter.cs
2026-08-13 01:54 jsantos Status assigned => feedback
2026-08-13 02:29 jsantos Note Added: 0012498
2026-08-13 15:00 stonstad Note Added: 0012501
2026-08-13 15:00 stonstad Status feedback => assigned
2026-08-13 15:25 jsantos Note Added: 0012503
2026-08-13 15:25 jsantos Status assigned => feedback
2026-08-13 15:31 stonstad Note Added: 0012505
2026-08-13 15:31 stonstad Status feedback => assigned
2026-08-13 15:36 stonstad Note Added: 0012506
2026-08-13 15:41 stonstad Note Added: 0012507
2026-08-13 15:50 stonstad Note Added: 0012508
2026-08-13 15:51 stonstad Note Edited: 0012507
2026-08-13 18:28 jsantos Note Edited: 0012506
2026-08-13 18:28 jsantos Note Edited: 0012507
2026-08-13 18:39 jsantos Note Added: 0012512
2026-08-13 18:40 jsantos Note Added: 0012513
2026-08-13 18:41 jsantos Status assigned => feedback
2026-08-13 22:27 stonstad Note Added: 0012514
2026-08-13 22:27 stonstad Status feedback => assigned
2026-08-13 22:28 stonstad Note Edited: 0012514
2026-08-13 22:28 stonstad Note Edited: 0012514
2026-08-13 22:28 stonstad Note Edited: 0012514
2026-08-13 22:29 stonstad Note Edited: 0012514
2026-08-14 18:42 jsantos Note Added: 0012516
2026-08-14 18:42 jsantos Status assigned => feedback
2026-08-14 18:42 jsantos Note Edited: 0012516
2026-08-14 18:44 jsantos Note Added: 0012517
2026-08-14 20:22 jsantos Note Added: 0012518
2026-08-15 19:19 stonstad Note Added: 0012519
2026-08-15 19:19 stonstad Status feedback => assigned
2026-08-17 13:09 jsantos Note Added: 0012525
2026-08-17 13:09 jsantos Status assigned => feedback
2026-08-19 19:33 stonstad Note Added: 0012530
2026-08-19 19:33 stonstad File Added: image-3.png
2026-08-19 19:33 stonstad Status feedback => assigned
2026-08-19 19:37 stonstad Note Added: 0012531
2026-08-19 20:09 stonstad Note Added: 0012532
2026-08-19 20:09 stonstad File Added: ButtonPanelControl.xaml
2026-08-19 20:09 stonstad File Added: ButtonPanelControl.cs
2026-08-19 20:09 stonstad File Added: TitleScreen.xaml
2026-08-19 20:16 stonstad Note Edited: 0012532
2026-08-19 21:30 jsantos Note Edited: 0012532
2026-08-19 21:39 jsantos Note Added: 0012533
2026-08-19 21:40 jsantos Status assigned => feedback
2026-08-20 18:53 stonstad Note Added: 0012537
2026-08-20 18:53 stonstad Status feedback => assigned
2026-08-21 20:24 jsantos Status assigned => resolved
2026-08-21 20:24 jsantos Resolution open => fixed
2026-08-21 20:24 jsantos Fixed in Version => 4.0
2026-08-21 20:24 jsantos Note Added: 0012555