diff --git a/Makefile b/Makefile
index f48ad2a..b0be399 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@ COMMON = \
 	 \
 	 filewatcher.cc \
 	 window.cc \
+	 dialogs.cc \
 	 globals.cc \
 	 profiling.cc \
 	 shaders.cc \
diff --git a/camera.hh b/camera.hh
index 53f345a..2979dd9 100644
--- a/camera.hh
+++ b/camera.hh
@@ -1,5 +1,5 @@
 #pragma once
-#include "imousectrl.hh"
+#include "mousectrl.hh"
 
 
 /*= T_Camera =================================================================*/
diff --git a/dialogs.cc b/dialogs.cc
new file mode 100644
index 0000000..d6a0459
--- /dev/null
+++ b/dialogs.cc
@@ -0,0 +1,170 @@
+#include "externals.hh"
+#include "dialogs.hh"
+#include <imgui_internal.h>
+
+
+/*= A_ModalDialog ============================================================*/
+
+A_ModalDialog::A_ModalDialog(
+		char const* const id ) noexcept
+	: id_{ id , uint32_t( strlen( id ) + 1 ) }
+{}
+
+A_ModalDialog::A_ModalDialog(
+		T_String const& id ) noexcept
+{
+	if ( id && id[ id.length( ) - 1 ] == 0 ) {
+		id_ = ebcl::T_Buffer< char >{ id.data( ) , id.size( ) };
+	} else {
+		id_ = id.toOSString( );
+	}
+}
+
+A_ModalDialog::~A_ModalDialog( )
+{ }
+
+uint8_t A_ModalDialog::addButton(
+		char const* name ) noexcept
+{
+	buttons_.addNew( name , strlen( name ) + 1 );
+	return buttons_.size( ) - 1;
+}
+
+uint8_t A_ModalDialog::addButton(
+		T_String const& name ) noexcept
+{
+	if ( name && name[ name.length( ) - 1 ] == 0 ) {
+		buttons_.addNew( name.data( ) , name.size( ) );
+	} else {
+		buttons_.add( name.toOSString( ) );
+	}
+	return buttons_.size( );
+}
+
+void A_ModalDialog::initDialog( ) noexcept
+{}
+
+bool A_ModalDialog::draw( ) noexcept
+{
+	using namespace ImGui;
+	if ( !open_ ) {
+		assert( buttons_.size( ) );
+		OpenPopup( &id_[ 0 ] );
+		open_ = true;
+	}
+
+	initDialog( );
+	if ( initialSize_ ) {
+		SetNextWindowSize( *initialSize_ , ImGuiCond_Appearing );
+	}
+
+	BeginPopupModal( &id_[ 0 ] , nullptr , ImGuiWindowFlags_NoResize );
+	const auto btMask{ drawDialog( ) };
+	assert( btMask != 0 );
+
+	const ImVec2 ws( GetWindowContentRegionMax( ) );
+	auto const& style( GetStyle( ) );
+	const float innerWidth{ ws.x - 2 * style.FramePadding.x };
+	const auto nButtons{ buttons_.size( ) };
+	const ImVec2 buttonSize{
+		std::max( 40.f , ( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) ,
+		0.f
+	};
+
+	int32_t clicked{ -1 };
+	for ( auto i = 0u ; i < buttons_.size( ) ; i ++ ) {
+		const auto m{ 1 << i };
+		const bool d{ ( btMask & m ) == 0 };
+		if ( i ) {
+			SameLine( 0 );
+		}
+		if ( d ) {
+			PushItemFlag( ImGuiItemFlags_Disabled , true );
+			PushStyleVar( ImGuiStyleVar_Alpha ,
+					GetStyle( ).Alpha * .5f );
+		}
+		if ( Button( &buttons_[ i ][ 0 ] , buttonSize ) ) {
+			assert( clicked == -1 );
+			clicked = i;
+		}
+		if ( d ) {
+			PopItemFlag( );
+			PopStyleVar( );
+		}
+	}
+	const bool close( clicked != -1 && onButton( clicked ) );
+	if ( close ) {
+		CloseCurrentPopup( );
+	}
+	EndPopup( );
+	return !close;
+}
+
+
+/*= T_MessageBox =============================================================*/
+
+void T_MessageBox::setButtons(
+		const T_Buttons buttons ) noexcept
+{
+	assert( buttons );
+	for ( auto i = 0u , nb = 0u ; i < 4 ; i ++ ) {
+		const E_Button b{ (E_Button) i };
+		const bool hasButton{ buttons & T_Buttons{ b } };
+		if ( !hasButton ) {
+			continue;
+		}
+		buttonMask_ |= ( 1 << nb );
+		nb ++;
+		buttons_.add( b );
+		switch ( b ) {
+		    case BT_OK: addButton( "OK" ); break;
+		    case BT_CANCEL: addButton( "Cancel" ); break;
+		    case BT_YES: addButton( "Yes" ); break;
+		    case BT_NO: addButton( "No" ); break;
+		}
+	}
+}
+
+T_MessageBox::T_MessageBox(
+		char const* const title ,
+		char const* const text ,
+		F_Handler handler ,
+		const T_Buttons buttons ) noexcept
+	: A_ModalDialog( title ) , text_{ text , strlen( text ) + 1 } ,
+		handler_{ std::move( handler ) }
+{
+	setButtons( buttons );
+	setInitialSize( 300.f , 100.f );
+}
+
+T_MessageBox::T_MessageBox(
+		T_String const& title ,
+		T_String const& text ,
+		F_Handler handler ,
+		const T_Buttons buttons ) noexcept
+	: A_ModalDialog( title ) , handler_{ std::move( handler ) }
+{
+	if ( text && text[ text.length( ) - 1 ] == 0 ) {
+		text_ = ebcl::T_Buffer< char >{ text.data( ) , text.size( ) };
+	} else {
+		text_ = text.toOSString( );
+	}
+	setButtons( buttons );
+	setInitialSize( 300.f , 100.f );
+}
+
+uint8_t T_MessageBox::drawDialog( ) noexcept
+{
+	ImGui::TextWrapped( "%s" , &text_[ 0 ] );
+	return buttonMask_;
+}
+
+bool T_MessageBox::onButton(
+		uint8_t button ) noexcept
+{
+	assert( button < buttons_.size( ) );
+	if ( handler_ ) {
+		handler_( buttons_[ button ] );
+	}
+	return true;
+}
diff --git a/dialogs.hh b/dialogs.hh
new file mode 100644
index 0000000..c238fa3
--- /dev/null
+++ b/dialogs.hh
@@ -0,0 +1,91 @@
+#pragma once
+#ifndef REAL_BUILD
+# include "externals.hh"
+#endif
+
+
+/*= DIALOGS ==================================================================*/
+
+// Base class for a modal dialog that can be pushed to the window's modal
+// stack.
+class A_ModalDialog
+{
+    private:
+	ebcl::T_Buffer< char > id_;
+	bool open_{ false };
+	T_Optional< ImVec2 > initialSize_;
+	T_StaticArray< ebcl::T_Buffer< char > , 4 > buttons_;
+
+    protected:
+	A_ModalDialog( char const* id ) noexcept;
+	A_ModalDialog( T_String const& id ) noexcept;
+
+	void setInitialSize( const float width ,
+			const float height ) noexcept
+		{ initialSize_.setNew( width , height ); }
+
+	// Add a button w/ the specified name, return its identifier
+	uint8_t addButton( char const* name ) noexcept;
+	uint8_t addButton( T_String const& name ) noexcept;
+
+	// Initialisation, called right before the window is opened.
+	// Does nothing by default.
+	virtual void initDialog( ) noexcept;
+
+	// Draw the dialog box's contents, returns a mask that determines
+	// which buttons should be enabled.
+	virtual uint8_t drawDialog( ) noexcept = 0;
+
+	// Button click handler. Returns true if the dialog should be
+	// closed.
+	virtual bool onButton( uint8_t button ) noexcept = 0;
+
+    public:
+	NO_COPY( A_ModalDialog );
+	NO_MOVE( A_ModalDialog );
+	virtual ~A_ModalDialog( );
+
+	// Draw and handle the dialog box. Returns true if it must be kept,
+	// or false if it must be removed from the stack.
+	bool draw( ) noexcept;
+};
+using P_ModalDialog = T_OwnPtr< A_ModalDialog >;
+
+// Simple modal message box with configurable buttons
+class T_MessageBox : public A_ModalDialog
+{
+    public:
+	enum E_Button {
+		BT_OK ,
+		BT_CANCEL ,
+		BT_YES ,
+		BT_NO ,
+	};
+	using T_Buttons = T_Flags< E_Button >;
+
+	// Result handler
+	using F_Handler = std::function< void( E_Button ) >;
+
+    private:
+	ebcl::T_Buffer< char > text_;
+	F_Handler handler_;
+	uint8_t buttonMask_{ 0 };
+	T_StaticArray< E_Button , 4 > buttons_;
+
+	void setButtons( T_Buttons buttons ) noexcept;
+
+    protected:
+	uint8_t drawDialog( ) noexcept override;
+	bool onButton( uint8_t button ) noexcept override;
+
+    public:
+	T_MessageBox( char const* title ,
+			char const* text ,
+			F_Handler handler = { } ,
+			T_Buttons buttons = BT_OK ) noexcept;
+	T_MessageBox( T_String const& title ,
+			T_String const& text ,
+			F_Handler handler = { } ,
+			T_Buttons buttons = BT_OK ) noexcept;
+};
+
diff --git a/imousectrl.hh b/mousectrl.hh
similarity index 51%
rename from imousectrl.hh
rename to mousectrl.hh
index ab27629..7e5584a 100644
--- a/imousectrl.hh
+++ b/mousectrl.hh
@@ -1,30 +1,9 @@
 #pragma once
-#ifndef REAL_BUILD
-# include "externals.hh"
-#endif
+#include "ui-input.hh"
 
 
 /*= MOUSE CONTROLS INTERFACE ===================================================*/
 
-enum class E_MouseButton {
-	LEFT ,
-	MIDDLE ,
-	RIGHT ,
-};
-using T_MouseButtons = T_Flags< E_MouseButton >;
-
-/*------------------------------------------------------------------------------*/
-
-enum class E_KeyboardModifier
-{
-	CTRL ,
-	SHIFT ,
-	ALT ,
-};
-using T_KeyboardModifiers = T_Flags< E_KeyboardModifier >;
-
-/*------------------------------------------------------------------------------*/
-
 class A_MouseCtrl
 {
     public:
diff --git a/sync.hh b/sync.hh
index af3bfca..085ac3c 100644
--- a/sync.hh
+++ b/sync.hh
@@ -1,7 +1,7 @@
 #pragma once
 #include "filewatcher.hh"
 #include "utilities.hh"
-#include "imousectrl.hh"
+#include "mousectrl.hh"
 
 #include <ebcl/SRDParserConfig.hh>
 #include <ebcl/Sets.hh>
diff --git a/ui-input.hh b/ui-input.hh
new file mode 100644
index 0000000..16d32dc
--- /dev/null
+++ b/ui-input.hh
@@ -0,0 +1,22 @@
+#pragma once
+#ifndef REAL_BUILD
+# include "externals.hh"
+#endif
+
+
+enum class E_MouseButton {
+	LEFT ,
+	MIDDLE ,
+	RIGHT ,
+};
+using T_MouseButtons = T_Flags< E_MouseButton >;
+
+/*------------------------------------------------------------------------------*/
+
+enum class E_KeyboardModifier
+{
+	CTRL ,
+	SHIFT ,
+	ALT ,
+};
+using T_KeyboardModifiers = T_Flags< E_KeyboardModifier >;
diff --git a/window.cc b/window.cc
index 89611d8..8332e34 100644
--- a/window.cc
+++ b/window.cc
@@ -4,173 +4,6 @@
 #include <imgui_internal.h>
 
 
-/*= A_ModalDialog ============================================================*/
-
-A_ModalDialog::A_ModalDialog(
-		char const* const id ) noexcept
-	: id_{ id , uint32_t( strlen( id ) + 1 ) }
-{}
-
-A_ModalDialog::A_ModalDialog(
-		T_String const& id ) noexcept
-{
-	if ( id && id[ id.length( ) - 1 ] == 0 ) {
-		id_ = ebcl::T_Buffer< char >{ id.data( ) , id.size( ) };
-	} else {
-		id_ = id.toOSString( );
-	}
-}
-
-A_ModalDialog::~A_ModalDialog( )
-{ }
-
-uint8_t A_ModalDialog::addButton(
-		char const* name ) noexcept
-{
-	buttons_.addNew( name , strlen( name ) + 1 );
-	return buttons_.size( ) - 1;
-}
-
-uint8_t A_ModalDialog::addButton(
-		T_String const& name ) noexcept
-{
-	if ( name && name[ name.length( ) - 1 ] == 0 ) {
-		buttons_.addNew( name.data( ) , name.size( ) );
-	} else {
-		buttons_.add( name.toOSString( ) );
-	}
-	return buttons_.size( );
-}
-
-void A_ModalDialog::initDialog( ) noexcept
-{}
-
-bool A_ModalDialog::draw( ) noexcept
-{
-	using namespace ImGui;
-	if ( !open_ ) {
-		assert( buttons_.size( ) );
-		OpenPopup( &id_[ 0 ] );
-		open_ = true;
-	}
-
-	initDialog( );
-	if ( initialSize_ ) {
-		SetNextWindowSize( *initialSize_ , ImGuiCond_Appearing );
-	}
-
-	BeginPopupModal( &id_[ 0 ] , nullptr , ImGuiWindowFlags_NoResize );
-	const auto btMask{ drawDialog( ) };
-	assert( btMask != 0 );
-
-	const ImVec2 ws( GetWindowContentRegionMax( ) );
-	auto const& style( GetStyle( ) );
-	const float innerWidth{ ws.x - 2 * style.FramePadding.x };
-	const auto nButtons{ buttons_.size( ) };
-	const ImVec2 buttonSize{
-		std::max( 40.f , ( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) ,
-		0.f
-	};
-
-	int32_t clicked{ -1 };
-	for ( auto i = 0u ; i < buttons_.size( ) ; i ++ ) {
-		const auto m{ 1 << i };
-		const bool d{ ( btMask & m ) == 0 };
-		if ( i ) {
-			SameLine( 0 );
-		}
-		if ( d ) {
-			PushItemFlag( ImGuiItemFlags_Disabled , true );
-			PushStyleVar( ImGuiStyleVar_Alpha ,
-					GetStyle( ).Alpha * .5f );
-		}
-		if ( Button( &buttons_[ i ][ 0 ] , buttonSize ) ) {
-			assert( clicked == -1 );
-			clicked = i;
-		}
-		if ( d ) {
-			PopItemFlag( );
-			PopStyleVar( );
-		}
-	}
-	const bool close( clicked != -1 && onButton( clicked ) );
-	if ( close ) {
-		CloseCurrentPopup( );
-	}
-	EndPopup( );
-	return !close;
-}
-
-
-/*= T_MessageBox =============================================================*/
-
-void T_MessageBox::setButtons(
-		const T_Buttons buttons ) noexcept
-{
-	assert( buttons );
-	for ( auto i = 0u , nb = 0u ; i < 4 ; i ++ ) {
-		const E_Button b{ (E_Button) i };
-		const bool hasButton{ buttons & T_Buttons{ b } };
-		if ( !hasButton ) {
-			continue;
-		}
-		buttonMask_ |= ( 1 << nb );
-		nb ++;
-		buttons_.add( b );
-		switch ( b ) {
-		    case BT_OK: addButton( "OK" ); break;
-		    case BT_CANCEL: addButton( "Cancel" ); break;
-		    case BT_YES: addButton( "Yes" ); break;
-		    case BT_NO: addButton( "No" ); break;
-		}
-	}
-}
-
-T_MessageBox::T_MessageBox(
-		char const* const title ,
-		char const* const text ,
-		F_Handler handler ,
-		const T_Buttons buttons ) noexcept
-	: A_ModalDialog( title ) , text_{ text , strlen( text ) + 1 } ,
-		handler_{ std::move( handler ) }
-{
-	setButtons( buttons );
-	setInitialSize( 300.f , 100.f );
-}
-
-T_MessageBox::T_MessageBox(
-		T_String const& title ,
-		T_String const& text ,
-		F_Handler handler ,
-		const T_Buttons buttons ) noexcept
-	: A_ModalDialog( title ) , handler_{ std::move( handler ) }
-{
-	if ( text && text[ text.length( ) - 1 ] == 0 ) {
-		text_ = ebcl::T_Buffer< char >{ text.data( ) , text.size( ) };
-	} else {
-		text_ = text.toOSString( );
-	}
-	setButtons( buttons );
-	setInitialSize( 300.f , 100.f );
-}
-
-uint8_t T_MessageBox::drawDialog( ) noexcept
-{
-	ImGui::TextWrapped( "%s" , &text_[ 0 ] );
-	return buttonMask_;
-}
-
-bool T_MessageBox::onButton(
-		uint8_t button ) noexcept
-{
-	assert( button < buttons_.size( ) );
-	if ( handler_ ) {
-		handler_( buttons_[ button ] );
-	}
-	return true;
-}
-
-
 /*= T_Window =================================================================*/
 
 namespace {
diff --git a/window.hh b/window.hh
index 7d8b2ce..8e30c22 100644
--- a/window.hh
+++ b/window.hh
@@ -1,103 +1,16 @@
 #pragma once
-#ifndef REAL_BUILD
-# include "externals.hh"
-#endif
+#include "dialogs.hh"
+#include "mousectrl.hh"
 
 
-// Base class for a modal dialog that can be pushed to the window's modal
-// stack.
-class A_ModalDialog
-{
-    private:
-	ebcl::T_Buffer< char > id_;
-	bool open_{ false };
-	T_Optional< ImVec2 > initialSize_;
-	T_StaticArray< ebcl::T_Buffer< char > , 4 > buttons_;
-
-    protected:
-	A_ModalDialog( char const* id ) noexcept;
-	A_ModalDialog( T_String const& id ) noexcept;
-
-	void setInitialSize( const float width ,
-			const float height ) noexcept
-		{ initialSize_.setNew( width , height ); }
-
-	// Add a button w/ the specified name, return its identifier
-	uint8_t addButton( char const* name ) noexcept;
-	uint8_t addButton( T_String const& name ) noexcept;
-
-	// Initialisation, called right before the window is opened.
-	// Does nothing by default.
-	virtual void initDialog( ) noexcept;
-
-	// Draw the dialog box's contents, returns a mask that determines
-	// which buttons should be enabled.
-	virtual uint8_t drawDialog( ) noexcept = 0;
-
-	// Button click handler. Returns true if the dialog should be
-	// closed.
-	virtual bool onButton( uint8_t button ) noexcept = 0;
-
-    public:
-	NO_COPY( A_ModalDialog );
-	NO_MOVE( A_ModalDialog );
-	virtual ~A_ModalDialog( );
-
-	// Draw and handle the dialog box. Returns true if it must be kept,
-	// or false if it must be removed from the stack.
-	bool draw( ) noexcept;
-};
-using P_ModalDialog = T_OwnPtr< A_ModalDialog >;
-
-
-class T_MessageBox : public A_ModalDialog
-{
-    public:
-	enum E_Button {
-		BT_OK ,
-		BT_CANCEL ,
-		BT_YES ,
-		BT_NO ,
-	};
-	using T_Buttons = T_Flags< E_Button >;
-
-	// Result handler
-	using F_Handler = std::function< void( E_Button ) >;
-
-    private:
-	ebcl::T_Buffer< char > text_;
-	F_Handler handler_;
-	uint8_t buttonMask_{ 0 };
-	T_StaticArray< E_Button , 4 > buttons_;
-
-	void setButtons( T_Buttons buttons ) noexcept;
-
-    protected:
-	uint8_t drawDialog( ) noexcept override;
-	bool onButton( uint8_t button ) noexcept override;
-
-    public:
-	T_MessageBox( char const* title ,
-			char const* text ,
-			F_Handler handler = { } ,
-			T_Buttons buttons = BT_OK ) noexcept;
-	T_MessageBox( T_String const& title ,
-			T_String const& text ,
-			F_Handler handler = { } ,
-			T_Buttons buttons = BT_OK ) noexcept;
-};
-
+/*= WINDOW MANAGEMENT ========================================================*/
 
 struct T_Window
 {
 	T_Window( );
 	~T_Window( );
 
-	void startFrame( const bool capture ,
-			ImVec2 const& mouseInitial ) const;
-	void warpMouse( ImVec2 const& pos ) const;
-
-	void swap( ) const;
+	//----------------------------------------------------------------------
 
 	void pushDialog( P_ModalDialog dialog ) noexcept
 		{ modals_.add( std::move( dialog ) ); }
@@ -106,20 +19,29 @@ struct T_Window
 			char const* text ,
 			T_MessageBox::F_Handler handler = { } ,
 			T_MessageBox::T_Buttons buttons = T_MessageBox::BT_OK )
-		{ modals_.add( NewOwned< T_MessageBox >( title , text , handler , buttons ) ); }
+		{ pushDialog( NewOwned< T_MessageBox >( title , text , handler , buttons ) ); }
 	void msgbox( T_String const& title ,
 			T_String const& text ,
 			T_MessageBox::F_Handler handler = { } ,
 			T_MessageBox::T_Buttons buttons = T_MessageBox::BT_OK )
-		{ modals_.add( NewOwned< T_MessageBox >( title , text , handler , buttons ) ); }
+		{ pushDialog( NewOwned< T_MessageBox >( title , text , handler , buttons ) ); }
 
-	void handleDialogs( ) noexcept;
+	//----------------------------------------------------------------------
 
 	ImFont* defaultFont( ) const noexcept
 		{ return defaultFont_; }
 	ImFont* smallFont( ) const noexcept
 		{ return smallFont_; }
 
+	//----------------------------------------------------------------------
+
+	void startFrame( const bool capture ,
+			ImVec2 const& mouseInitial ) const;
+	void warpMouse( ImVec2 const& pos ) const;
+
+	void swap( ) const;
+	void handleDialogs( ) noexcept;
+
     private:
 	SDL_Window * window;
 	SDL_GLContext gl;