Application Framework
At its core, NoesisGUI is a lightweight, platform-agnostic library. It has no built-in dependencies on specific operating systems, graphics APIs, or system services. To integrate this core library directly into your engine, as shown in the integration tutorial, you must provide custom implementations for required functionality, such as memory allocation, file I/O, GPU rendering, and media playback.
To simplify development, the SDK includes an optional Application Framework. This framework is an open-source layer built on top of the NoesisGUI core that implements all of these platform-specific details for you. We use this framework to power all of our public samples. Because it is distributed with full source code, it serves not only as a rapid development tool for multiplatform apps, but also as a comprehensive showcase of how to cleanly integrate NoesisGUI into your own custom application or game engine.
Using the application framework, you can design applications that will run seamlessly on a Windows PC, a Mac, an iPhone, or an Xbox, for example. The framework supports all the platforms where NoesisGUI is available.
The following is a list of the functionality exposed by the Application Framework:
- Multiplatform abstractions for Application and Window.
- Unified handling of input events: keyboard, mouse, touch, gamepads, and VR controllers.
- Audio playback support.
- Video playback via the MediaPlayer.
- Resource Providers implementations for seamless asset loading.
- A feature-rich Noesis Theme with built-in dark and light modes.
- Native render implementations for each supported graphics API.
- Localization extensions.
- A built-in ShaderCompiler tool.
- Custom Shader Effects and Brushes, such as ChromaticAberrationEffect and ConicGradientBrush.
- The Interactivity package.
- A Language Server compatible with Visual Studio Code.
- The Toolkit package, featuring Studio-ready extensions like like CornerBox, GridImage, LayoutScaler, MathConverter, and more.
Note
While the Application Framework powers all our samples and tutorials, it is primarily intended as a reference implementation. Rather than treating it as a rigid, "production-ready" library, we recommend using the source code as a robust starting point that you can adapt and tweak to perfectly fit your own engine or application.
Headers and Namespace
Because the Application Framework is distinct from the core library, its headers are contained within their own NsApp and NsRender modules. All framework functionality is exposed through the NoesisApp namespace.
#include <NsApp/EntryPoint.h>
#include <NsApp/Application.h>
#include <NsApp/ApplicationLauncher.h>
#include <NsApp/Window.h>
#include <NsApp/EmbeddedXamlProvider.h>
#include <NsApp/EmbeddedFontProvider.h>
using namespace Noesis;
using namespace NoesisApp;
Note
As a quick reminder, the framework headers are kept separate from the core. You will need to include them independently, as they are outside the standard NoesisGUI include folder and are not part of the 'NoesisPCH.h' precompiled header.
Entry Point
Because standard entry points vary across operating systems (such as WinMain on Windows), the framework abstracts this away by providing a single, portable entry point: NsMain.
Inside this function, you will typically instantiate your application's Launcher, configure its startup arguments and main XAML file, and execute its Run() loop.
int NsMain(int argc, char** argv)
{
AppLauncher launcher;
launcher.SetArguments(argc, argv);
launcher.SetStartupUri("MainPage.xaml");
return launcher.Run();
}
Launcher
The Launcher is the first instance you create. It is in charge of application initialization and the main message loop. It also provides several important overridable functions to configure your app:
- GetResources: for specifying the URI of the global resource dictionary used by the application. It receives a boolean parameter, allowing you to easily support separate dark and light themes. For more details on creating shared visuals, check out the Styles and Templates tutorial.
- RegisterComponents: for registering custom classes in the component factory so they can be instantiated by XAML, as explained in the Extending NoesisGUI tutorial.
- GetXamlProvider, GetFontProvider, and GetTextureProvider: for customizing how resources (like XAMLs, fonts, textures, and audio files) are loaded. You can install custom handlers as explained in the Customizing Resource Loading tutorial.
- OnAppStartUp: called when the application starts. This is the ideal place to initialize your ViewModels and set the data context of your main window.
class AppLauncher final: public ApplicationLauncher
{
private:
void RegisterComponents() const override
{
RegisterComponent<MySample::MainWindow>();
}
Ptr<XamlProvider> GetXamlProvider() const override
{
EmbeddedXaml xamls[] =
{
{ "MainWindow.xaml", MainWindow_xaml }
};
return *new EmbeddedXamlProvider(xamls);
}
Ptr<FontProvider> GetFontProvider() const override
{
EmbeddedFont fonts[] =
{
{ "", Roboto_Regular_ttf },
{ "", Roboto_Bold_ttf }
};
return *new EmbeddedFontProvider(fonts);
}
Uri GetResources(bool darkTheme) const override
{
return darkTheme ? "Resources.Dark.xaml" : "Resources.Light.xaml";
}
void OnAppStartUp() override
{
GetMainWindow()->SetDataContext(MakePtr<MySample::ViewModel>());
}
};
Note
The framework includes specific resource providers (EmbeddedXamlProvider, EmbeddedFontProvider, and EmbeddedTextureProvider) that allow you to embed resources directly into the executable. Each time you edit a resource contained in the project, it is automatically converted to a header file using 'bin2h', a tool distributed with the SDK. For example, 'Roboto-Regular.ttf.bin.h' is automatically regenerated each time 'Roboto-Regular.ttf' changes.
Window
The Window class mimics the WPF Window Class. It is a container that shows its content inside an operating system window. Public properties like Title, ResizeMode, WindowStyle, Width, and Height control the appearance of the native window.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NoesisGUI - RSS Reader" Width="487" Height="630" ResizeMode="NoResize"
Background="{StaticResource Background0}"
Foreground="{StaticResource Foreground0}"
FontFamily="{StaticResource DefaultFont}"
x:Class="RssReader.MainWindow">
<Viewbox>
<DockPanel Background="{StaticResource Background0}" LastChildFill="True"
KeyboardNavigation.TabNavigation="Contained"
KeyboardNavigation.DirectionalNavigation="Contained" Width="325" Height="420">
<TextBlock DockPanel.Dock="Top" Text="RSS Reader" FontSize="32" FontWeight="Bold"
TextAlignment="Center" Margin="0,10,0,0"/>
<Border DockPanel.Dock="Top" Background="{StaticResource Background1}"
BorderBrush="{StaticResource Border}" BorderThickness="1" CornerRadius="2"
Margin="10" Padding="15,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="URL: " VerticalAlignment="Center"/>
<TextBox x:Name="Address" Grid.Column="1" Text="http://www.metacritic.com/"/>
<Button x:Name="GoTo" Grid.Column="2" Content="Go" Click="OnGoToClicked" Margin="2,0,0,0"/>
</Grid>
</Border>
<Grid DockPanel.Dock="Bottom" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource Background1}"/>
</Style>
</Grid.Resources>
<Button x:Name="Prev" Grid.Column="0" Content="Prev" Click="OnPrevClicked"/>
<Button x:Name="Next" Grid.Column="1" Content="Next" Click="OnNextClicked"/>
</Grid>
<Border Background="{StaticResource Background1}" BorderBrush="{StaticResource Border}"
BorderThickness="1" CornerRadius="2" Margin="10,0" Padding="5">
<Grid x:Name="ContentPanel" Margin="10,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="EntryTitle" Grid.Row="0" FontSize="20" FontWeight="Bold"
Foreground="{StaticResource Foreground1}" TextAlignment="Center"
Margin="0,5,0,5"/>
<ScrollViewer Grid.Row="1" Margin="0,10" Focusable="False"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="EntryDesc" TextWrapping="Wrap" Margin="10,0"
Foreground="{StaticResource Foreground2}" FontSize="14"/>
</ScrollViewer>
</Grid>
</Border>
</DockPanel>
</Viewbox>
</Window>
While you can extend the Window base class to implement code-behind functionality, modern NoesisGUI development heavily favors the MVVM pattern.
By utilizing MVVM and simply setting the window's DataContext (as demonstrated earlier in the Launcher section), you often do not need to derive from the Window class or use x:Class in your XAML at all.
However, if your view requires complex, UI-specific event handling that cannot be easily managed by a ViewModel, extending the base class remains fully supported:
namespace RssReader
{
class MainWindow final: public Window
{
public:
MainWindow(): _index(0)
{
InitializeComponent();
_title = FindName<TextBlock>("EntryTitle");
_title->SetText(gTitles[0]);
_desc = FindName<TextBlock>("EntryDesc");
_desc->SetText(gBodies[0]);
}
private:
void InitializeComponent()
{
Noesis::GUI::LoadComponent(this, "MainWindow.xaml");
}
bool ConnectEvent(BaseComponent* source, const char* event, const char* handler) override
{
NS_CONNECT_EVENT(Button, Click, OnGoToClicked);
NS_CONNECT_EVENT(Button, Click, OnPrevClicked);
NS_CONNECT_EVENT(Button, Click, OnNextClicked);
return false;
}
void OnGoToClicked(BaseComponent* /*sender*/, const RoutedEventArgs& /*e*/)
{
}
void OnPrevClicked(BaseComponent* /*sender*/, const RoutedEventArgs& /*e*/)
{
_index = _index == 0 ? 2 : _index - 1;
_title->SetText(gTitles[_index]);
_desc->SetText(gBodies[_index]);
}
void OnNextClicked(BaseComponent* /*sender*/, const RoutedEventArgs& /*e*/)
{
_index = _index == 2 ? 0 : _index + 1;
_title->SetText(gTitles[_index]);
_desc->SetText(gBodies[_index]);
}
private:
int _index;
TextBlock* _title;
TextBlock* _desc;
NS_IMPLEMENT_INLINE_REFLECTION(MainWindow, Window)
{
NsMeta<TypeId>("RssReader.MainWindow");
}
};
}
Global Resources
To define global UI assets such as colors, brushes, and shared styles, you use a standalone ResourceDictionary file. This global dictionary is linked to the framework by returning its URI from the GetResources method in your Launcher, as shown in the previous section.
This file is the ideal place to establish your project's global visual scope. It is also where you merge external dictionaries, such as the Noesis Theme (which we will cover in detail in the next section).
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Noesis.GUI.Extensions;component/Theme/NoesisTheme.DarkBlue.xaml" />
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="Background0" Color="#FF2F3F4F"/>
<SolidColorBrush x:Key="Background1" Color="SlateGray"/>
<SolidColorBrush x:Key="Foreground0" Color="White"/>
<SolidColorBrush x:Key="Foreground1" Color="SkyBlue"/>
<SolidColorBrush x:Key="Foreground2" Color="Black"/>
<SolidColorBrush x:Key="Border0" Color="LightSlateGray"/>
</ResourceDictionary>
Noesis Theme
The Application Framework includes a feature-rich, modern UI theme right out of the box. Designed to give your applications a polished look instantly, the Noesis Theme provides full support for Dark and Light modes, alongside a wide variety of Accent colors to match your brand or project's identity.
The theme is built directly into the SDK as an embedded resource within the Noesis.GUI.Extensions assembly. To apply a theme, simply merge your preferred color scheme into your global resources:
| Dark Modes | Light Modes |
|---|---|
| NoesisTheme.DarkRed.xaml | NoesisTheme.LightRed.xaml |
| NoesisTheme.DarkGreen.xaml | NoesisTheme.LightGreen.xaml |
| NoesisTheme.DarkBlue.xaml | NoesisTheme.LightBlue.xaml |
| NoesisTheme.DarkOrange.xaml | NoesisTheme.LightOrange.xaml |
| NoesisTheme.DarkEmerald.xaml | NoesisTheme.LightEmerald.xaml |
| NoesisTheme.DarkPurple.xaml | NoesisTheme.LightPurple.xaml |
| NoesisTheme.DarkCrimson.xaml | NoesisTheme.LightCrimson.xaml |
| NoesisTheme.DarkLime.xaml | NoesisTheme.LightLime.xaml |
| NoesisTheme.DarkAqua.xaml | NoesisTheme.LightAqua.xaml |
<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>
Command-Line Switches
To help you test, profile, and debug your application without needing to recompile, the Application Framework provides a built-in set of command-line switches. Every application built with the framework automatically supports the following arguments:
- --render [D3D11|GL|Metal|...]: overrides the default renderer.
- --vsync [0|1]: disables vertical synchronization.
- --samples N: enables multisample anti-aliasing (MSAA); by default, it is off.
- --ppaa [0|1]: enables cheap anti-aliasing (enabled by default).
- --linear: switches to linear rendering; by default, rendering happens in gamma space.
- --lcd [0|1]: enables subpixel rendering compatible with LCD displays.
- --log_binding: increases the verbosity of logging when using data binding.
- --emulate_touch: enables emulation of touch input from mouse events.
- --root dir_path: reads resources from the specified filesystem path (for hot-reloading).
- --project project_path: loads the specified .noesis project file.
Shortcut Keys
Just like the command-line switches, the framework includes a suite of built-in keyboard shortcuts designed to make debugging and profiling easier. These hotkeys allow you to instantly toggle visual overlays, inspect rendering behavior, and analyze performance on the fly:
- CTRL + T: displays the debug toolbar.
- CTRL + W: toggles wireframe mode when rendering triangles.
- CTRL + B: each batch submitted to the GPU is given a unique solid color.
- CTRL + O: displays pixel overdraw using blending layers. Different colors are used for each type of triangle: green for normal, red for opacities, and blue for clipping masks.
- CTRL + P: per-primitive anti-aliasing extrudes the contours of the geometry and smooths them. Useful when GPU multisampling is not enabled.
- CTRL + F: displays a performance stats panel.
- F10: takes a RenderDoc capture.
Shader Compiler
Instead of writing separate shaders for every graphics API, the ShaderCompiler command-line tool allows you to write your pixel shaders just once. Using a language based on HLSL, the compiler leverages preprocessor macros to automatically cross-compile your code into the native formats required by all Noesis-supported platforms.
To help you write platform-specific logic when necessary, the following defines are automatically available for each target graphics API:
| Define symbol | Graphics API |
|---|---|
| TARGET_HLSL | D3D11, D3D12 |
| TARGET_GLSL | OpenGL |
| TARGET_ESSL | OpenGL ES, WebGL |
| TARGET_SPIRV | Vulkan |
| TARGET_PSSL_ORBIS | PlayStation 4 |
| TARGET_PSSL_PROSPERO | PlayStation 5 |
| TARGET_NVN | Nintendo Switch |
| TARGET_NVN2 | Nintendo Switch 2 |
Note
You might notice that Apple Metal is not listed in the table. For Metal targets, Noesis uses the TARGET_SPIRV path. The compiler generates SPIR-V bytecode, which is then automatically transcompiled into Metal Shader Language (MSL).
Some differences between vanilla HLSL and the ShaderCompiler language:
- Types half4, half3, half2, and half are used for mediump precision.
- Types fixed4, fixed3, fixed2, and fixed are used for lowp precision.
- To abstract precision differences across platforms, always use the make_mediump and make_fixed macros when constructing these vectors.
- Uniform constants are enclosed in a global uniforms block.
- Vectorial types (like float4 or float2) should never be used inside the uniforms block. Cross-platform memory alignment rules vary wildly, making vectors difficult to maintain. Instead, declare individual scalar values (e.g., float) in your uniforms and pack them using the make_ macros inside your shader logic.
- For Brushes, the header 'BrushHelpers.h' must be used.
- For Effects, the header 'EffectHelpers.h' must be used.
- The entry point is main_brush for Brushes and main_effect for Effects.
The difference between brushes and effects is described in this Shader tutorial.
The following code is an example of a Brush that adheres to these cross-platform rules:
#include "BrushHelpers.h"
uniforms
{
float _color_r, _color_g, _color_b, _color_a;
float _intensity;
};
fixed4 main_brush(float2 uv)
{
fixed4 color = make_fixed4(_color_r, _color_g, _color_b, _color_a);
fixed intensity = make_fixed(_intensity);
fixed4 c = SampleImage(uv);
return lerp(c, c * color, intensity);
}
And this is an example of an Effect:
#include "EffectHelpers.h"
uniforms
{
float _color_r, _color_g, _color_b, _color_a;
float _intensity;
};
fixed4 main_effect()
{
fixed4 color = make_fixed4(_color_r, _color_g, _color_b, _color_a);
fixed intensity = make_fixed(_intensity);
fixed4 c = GetInput();
return lerp(c, c * color, intensity);
}