NoesisGUI
 

🛠️ Integrating Noesis Studio

Because Noesis Studio is built natively on the core NoesisGUI architecture, it offers extensive flexibility. You can easily extend the editor with custom components, such as new controls or converters. Additionally, you can override its default rendering device to match your application's exact renderer, or even register new texture formats. Furthermore, the entire Studio environment can be embedded directly into your custom engine or proprietary toolset, providing a seamless and unified visual authoring experience for your team.

Note

The NoesisGUI SDK includes a practical sample application named 'StudioTool'. This project is located alongside all the SDK samples and serves as a reference implementation, demonstrating exactly how to embed Noesis Studio within your own architecture.

This sample is the exact same vanilla Noesis Studio application available for download from our website.

Project Creation

In Noesis Studio, a project is defined by a file with the .noesis extension at the root of the folders. The location of this file determines what's the absolute root folder for the XAMLs. Just adding this document is enough for creating a project. For example:

<Project>
  <Assembly>Experiments</Assembly>
  <DefaultDataNamespace>Experiments</DefaultDataNamespace>
  <StartupDocument>MainPage.xaml</StartupDocument>
</Project>

Our sample integration, StudioTool, exposes a Studio Browser window that manages the creation, organization, and launching of projects. Naturally, this browser is implemented using NoesisGUI itself. This is just a sample; your integration can do any other thing, the only strict thing needed is the .noesis project file.

For more information about the .noesis format, here is more information: Noesis Project Files.

Studio API Functions

The API for Noesis Studio allows loading a Studio project located on disk and creating its visual tree. This is done using the Noesis::Studio::Create function, which returns a FrameworkElement root. This root can then be rendered by putting it inside a view as explained in the Rendering Architecture tutorial.

Among the options that can be passed to Noesis::Studio::Create, configuring the Resource Providers is mandatory. Studio relies on these providers each time it needs to read resources from your project; if they are not set, Studio will be unable to load your assets.

The following code shows how to configure the providers and load the project located in the given path. The rest of the parameters in the Options structure are initialized to their default values; however, it is highly recommended to review the StudioOptions.h header to discover all available configuration options and callbacks:

#include <NsGui/Studio.h>
#include <NsApp/LocalXamlProvider.h>
#include <NsApp/LocalTextureProvider.h>
#include <NsApp/LocalFontProvider.h>

Ptr<FrameworkElement> GetProjectRootElement(const char* projectPath)
{
    Studio::Options options;

    options.GetXamlProvider = [](const char*, const char* path) -> Ptr<XamlProvider>
    {
        return MakePtr<LocalXamlProvider>(path);
    };

    options.GetTextureProvider = [](const char*, const char* path) -> Ptr<TextureProvider>
    {
        return MakePtr<LocalTextureProvider>(path);
    };

    options.GetFontProvider = [](const char*, const char* path) -> Ptr<FontProvider>
    {
        return MakePtr<LocalFontProvider>(path);
    };

    return Studio::Create(projectPath, options);
}

Additional API & Callbacks

Beyond the initial project creation, the Noesis::Studio::Options structure contains several delegates that allow your application to react to editor lifecycle and file system events. These include:

  • File Management: FileCreated, FileWritten, and FileMoved trigger whenever the user modifies the project files inside Studio.
  • State & Lifecycle: SaveStateChanged notifies you of pending unsaved changes, RegisterAssembly hooks into custom assembly loading, and Exit is invoked when Studio requests to close.
  • Environment: You can assign a parentWindow handle to integrate Studio seamlessly into your toolset's windowing system, and toggle the initial darkTheme state.

Once the Studio instance is running, the Noesis::Studio namespace provides essential utility functions to manage its state dynamically:

  • State Management: SaveAllChanges() and DiscardAllChanges() allow programmatic control over pending modifications.
  • Navigation: OpenProjectFile() instructs Studio to open a specific file from the project directory.
  • Cache Control: ClearTypeCache() and PopulateTypeCache() refresh the internal type system, which is crucial if you hot-reload custom components.
  • Theming: SetTheme() allows you to switch between light and dark modes at runtime to match your host application's aesthetic.

Extending Studio

Any custom components registered within your NoesisGUI application are automatically exposed to Noesis Studio. For instance, newly registered User Controls will seamlessly populate within the editor's palette.

To refine how these extensions are presented, you can leverage the Studio metadata system. While the list of available metadata continues to grow, the core attributes allow you to deeply customize the design-time experience:

  • Inspector UI: Override default controls with custom widgets (e.g., StudioEditor), define property constraints (StudioMin, StudioRange, StudioStep), or provide named dropdown options for rapid selection (StudioFloatPresets).
  • Categorization: Group properties and classes into specific categories and control their primary/secondary display order (StudioOrder).
  • Documentation & Styling: Assign custom display names, descriptive tooltips, documentation URIs, and vector icons to your classes (StudioName, StudioDesc, StudioHelpUri, StudioIcon).
  • Editor Lifecycle: Define custom initialization logic executed automatically when a component is dragged and dropped into the editor (StudioInit).
  • Attached Properties: Expose custom attached properties to the inspector, strictly defining which element types they apply to and their required parent contexts (using the Attach class in C++, or the [StudioAttachTo] / [StudioAttachOn] attributes in C#).

These hints can be applied natively in C++ using the NsMeta macros from the StudioMeta.h header, or in C# by attaching the corresponding attributes directly to your classes and properties.

The following example demonstrates how to apply this metadata to the custom Star shape, which is a class provided by the Application Framework. This configures its category, icon, and specific property bounds for the editor's property grid:

C++
#include <NsGui/StudioMeta.h>

NS_IMPLEMENT_REFLECTION(NoesisApp::Star, "NoesisGUIExtensions.Star")
{
    NsMeta<StudioOrder>(2000, "Shape");
    NsMeta<StudioDesc>("Renders a star shape with variable number of points");
    NsMeta<StudioHelpUri>("https://www.noesisengine.com/docs/App.Toolkit._Star.html");
    NsMeta<StudioIcon>(Uri::Pack("Toolkit", "#ToolkitIcons"), 0xE906);

    NsProp("Count", &Star::GetCount, &Star::SetCount)
        .Meta<StudioOrder>(0);
    NsProp("Ratio", &Star::GetRatio, &Star::SetRatio)
        .Meta<StudioOrder>(1)
        .Meta<StudioRange>(0.0f, 1.0f);
    NsProp("Radius", &Star::GetRadius, &Star::SetRadius)
        .Meta<StudioOrder>(2)
        .Meta<StudioMin>(0.0f);
}
C#
[StudioOrder(2000, "Shape")]
[StudioDesc("Renders a star shape with variable number of points")]
[StudioHelpUri("https://www.noesisengine.com/docs/App.Toolkit._Star.html")]
[StudioIcon("Packages/com.noesis.noesisgui/Editor/#ToolkitIcons", 0xE906)]
public class Star : Shape
{
    [StudioOrder(0)]
    public int Count { get; set; }

    [StudioOrder(1)]
    [StudioRange(0.0f, 1.0f)]
    public float Ratio { get; set; }

    [StudioOrder(2)]
    [StudioMin(0.0f)]
    public float Radius { get; set; }
}

The Application Framework provides numerous examples with full source code, serving as a practical reference for utilizing metadata to effectively organize and document your extensions inside Studio.

Exposing Attached Properties

Attached properties require a slightly different approach. In C++, you utilize the API provided by the Attach metadata class during reflection. In C#, because attached properties lack standard CLR wrappers, Studio attributes must be stacked directly onto the static Get method.

C++
NS_IMPLEMENT_REFLECTION(TransitionExtensions, "MyControls.TransitionExtensions")
{
    NsMeta<Attach>("Enter")
        ->To<FrameworkElement>()
        ->Meta<StudioOrder>(1, "Transitions");
}
C#
public class TransitionExtensions
{
    public static readonly DependencyProperty EnterProperty =
        DependencyProperty.RegisterAttached("Enter", typeof(Storyboard),
        typeof(TransitionExtensions), new PropertyMetadata(null));

    [StudioAttachTo(typeof(FrameworkElement))]
    [StudioOrder(1, "Transitions")]
    public static Storyboard GetEnter(UIElement element)
    {
        return (Storyboard)element.GetValue(EnterProperty);
    }

    public static void SetEnter(UIElement element, Storyboard value)
    {
        element.SetValue(EnterProperty, value);
    }
}

Global Resources

A crucial component of any Noesis Studio project is its global resources dictionary. It is specified within the project definition file and can also be modified directly within the Studio interface.

<Project>
  <Assembly>MyGame</Assembly>
  <DefaultDataNamespace>MyGame</DefaultDataNamespace>
  <StartupDocument>MainPage.xaml</StartupDocument>
  <GlobalResources>GlobalResources.xaml</GlobalResources>
</Project>

Note

The global dictionary used by Studio and the one utilized by the Noesis runtime are separate entities. However, it is highly recommended to keep them synchronized to ensure a consistent visual experience across both environments.

The global resources dictionary serves as the central hub for defining project-wide UI assets, such as colors, brushes, and styles. By default, when you generate a new project, the Noesis Theme is automatically merged into these global resources. Note that this is an embedded theme provided by the Noesis extensions, meaning it is not a physical file located within your project directory.

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <ResourceDictionary.MergedDictionaries>
    <ResourceDictionary
      Source="/Noesis.GUI.Extensions;component/Theme/NoesisTheme.DarkBlue.xaml"/>
  </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

If you choose not to merge the default Noesis Theme and do not provide custom styles or templates for your controls, they will render in a distinct pink color to indicate that they are missing a visual template.

Note

Upgrading from 0.3.X In previous versions of Studio, the DarkBlue theme was applied implicitly by the editor. While convenient, this occasionally led to rendering discrepancies between Studio and the runtime, as the active theme was not explicitly declared in the project files. To guarantee complete transparency and visual consistency, this implicit behavior has been removed. If you are upgrading an existing project and rely on the default Noesis Theme instead of custom styles, you must now explicitly merge it into your global resources dictionary.

Design Time Data

Building a robust user interface often requires visualizing it in various states using realistic information. Noesis Studio facilitates this through a powerful design-time data system that allows you to mock your application's data layer directly within the editor. All design-time data for a project is stored locally in the '.noesis/data' directory.

This data architecture is intentionally separated into two main categories: Structures (your types) and Sets (your instances).

Structures

Structures act as the blueprints or schemas for your design-time data, functioning similarly to classes and enumerations in your native codebase. These definitions are strictly stored as XML files within the '.noesis/data/structures' folder.

When defining a structure, you can declare properties and specify their base types, such as strings, numbers, or objects, alongside helpful constraints like word counts or numeric ranges. For example, a basic SolarSystemObject structure might look like this:

<Class Name="SolarSystemObject">
  <Property Name="Name" Type="String" StringMinWordCount="1" StringMaxWordCount="1"/>
  <Property Name="Orbit" Type="Number" NumberMinValue="0" NumberMaxValue="40"/>
  <Property Name="Diameter" Type="Number" NumberMinValue="2200" NumberMaxValue="1500000"/>
  <Property Name="Details" Type="String" StringMinWordCount="10" StringMaxWordCount="30"/>
  <Property Name="Image" Type="Object" SubType="ImageSource"/>
</Class>

Structures can also define collections of other types. For instance, a SolarSystemObjects type can define a collection property constrained to a specific minimum and maximum item count:

<Class Name="SolarSystemObjects">
  <Property
    Name="SolarSystemObjects"
    Type="Collection" SubType="DataBinding.SolarSystemObject"
    CollectionMinItemCount="10" CollectionMaxItemCount="10"/>
</Class>

Data Sets

While Structures define the schema, Data Sets represent the concrete instances of those structures populated with actual design-time information. These instances are stored as XAML files within the '.noesis/data/sets' folder.

Using the simpler structures defined above, a data set can instantiate and populate a collection with realistic text, numeric values, and local image paths:

<vdctx:SolarSystemObjects xmlns:vdctx="clr-namespace:DataBinding">
  <vdctx:SolarSystemObjects.SolarSystemObjects>
    <vdctx:SolarSystemObject Name="SUN" Orbit="0" Diameter="1380000"
      Details="The yellow dwarf star in the center of our solar system."
      Image="studio:///DataBinding;component/Images/Sun.png"/>
    <vdctx:SolarSystemObject Name="MERCURY" Orbit="0.38" Diameter="4880"
      Details="The small and rocky planet Mercury is the closest planet to the Sun."
      Image="studio:///DataBinding;component/Images/Mercury.png"/>
    <vdctx:SolarSystemObject Name="EARTH" Orbit="1" Diameter="12756.3"
      Details="Earth, our home planet."
      Image="studio:///DataBinding;component/Images/Earth.png"/>
  </vdctx:SolarSystemObjects.SolarSystemObjects>
</vdctx:SolarSystemObjects>

For highly complex data relationships, such as a root ViewModel containing multiple sub-menus and commands, Sets can also independently reference other data sets using the {noesis:DataSet} markup extension. This allows you to build a highly modular and reusable mock data layer.

<vdctx:ViewModel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:noesis="clr-namespace:NoesisGUIExtensions"
  xmlns:vdctx="clr-namespace:Menu3D"
  MainMenu="{noesis:DataSet MainMenu Data}"
  StartMenu="{noesis:DataSet StartMenu Data}"
  SettingsMenu="{noesis:DataSet SettingsMenu Data}"
  Platform="PC">
</vdctx:ViewModel>

Automating Data Generation

Because all design-time structures and sets are stored as standard, human-readable XML and XAML files on disk, the data pipeline is exceptionally accessible and extensible.

As a developer using Studio, you are not bound to creating these files manually within the editor. You can implement your own custom scripts (e.g., using Python, MSBuild tasks, or standalone toolchain utilities) to automatically parse your active application codebase, whether written in C++, C#, or another language. These scripts can automatically generate the XML and XAML files in the '.noesis/data' directory. This automation empowers teams to seamlessly synchronize their game engine's live data models directly with the Noesis Studio environment, ensuring UI designers always have access to up-to-date properties and types.

When generating Structure files programmatically, it is highly recommended to follow Noesis Studio's expected file naming convention: Namespace.TypeName.xml. While this exact naming scheme is not strictly mandatory for loading the data, Studio natively uses this format when saving modifications. If your scripts generate files with a different naming scheme and a user later edits that structure within the editor, Studio will automatically rename the file to match its expected format. Adhering to this convention in your automation scripts prevents unexpected file renames or ghost duplicates in your version control system.

Launching Studio

Because your application already initializes NoesisGUI and registers its own custom extensions, launching Noesis Studio in-process directly from your executable is the most efficient way to achieve a fully integrated, customized editor environment.

All samples built using the Application Framework support the '--project' command-line argument for loading a .noesis project file. When invoked with this switch, the sample executable embeds and launches the Studio interface in-process, rather than running the standard standalone application.

This approach is essential for projects that rely on specific native integrations. For example, the CustomRender sample must be launched with '--project C:\MyProjects\CustomRender.noesis' to ensure its custom rendering extensions are properly registered and actively running within the editor's visual tree.

Although there are other methods to launch a customized instance of Studio, this approach is the simplest. We highly recommend reviewing the implementation of our Application class to see exactly how this is achieved under the hood.

 
© 2017 Noesis Technologies