diff --git a/demo/demo/grid.cpp b/demo/demo/grid.cpp
index b3c2760db6ca05d813ae4b330f028e29912a84bf..2897bb8be194afe2d20a70f1d52a0f92557f7b81 100644
--- a/demo/demo/grid.cpp
+++ b/demo/demo/grid.cpp
@@ -20,6 +20,8 @@
 
 #include "fggl/assets/loader.hpp"
 #include "fggl/entity/gridworld/zone.hpp"
+
+#include "fggl/gui/model/parser.hpp"
 #include "fggl/gui/renderer/renderer.hpp"
 
 using namespace fggl::gfx::colours;
@@ -196,7 +198,7 @@ namespace demo {
 	constexpr float DRAW_HALF = DRAW_SIZE / 2.0F;
 	constexpr float WALL_HALF = 2.5F;
 
-	static void render_grid(fggl::gfx::Paint& paint, fggl::entity::grid::Area2D<255, 255>& grid) {
+	static void render_grid(fggl::gfx::Paint& paint, fggl::entity::grid::Area2D<255, 255>& grid, fggl::App& app) {
 		const fggl::math::vec2 wallOffsetNorth {0, -DRAW_HALF};
 		const fggl::math::vec2 wallOffsetWest {-DRAW_HALF, 0};
 
@@ -226,28 +228,33 @@ namespace demo {
 		}
 
 		// UI test
-		fggl::gui::model::Widget widget;
-		widget.set("position", fggl::math::vec2{200.0F, 100.F});
-		widget.set("size", fggl::math::vec2{500.0F, 300.F});
-		fggl::gui::model::attr_box_set(widget, "padding", 5.0F);
-		widget.set("colour", fggl::gfx::colours::BLANCHED_ALMOND);
-
-		fggl::gui::model::Widget handle;
-		handle.set("border::bottom",5.0F);
-		handle.set("position", fggl::math::vec2{0.0F, 0.0F});
-		handle.set("size", fggl::math::vec2{INFINITY, 50.0F});
-		handle.set("text", "hello, world!");
-		fggl::gui::model::attr_box_set(handle, "padding", 5.0F);
-		handle.set("colour", fggl::gfx::colours::ORANGE);
-		widget.addChild(handle);
-
-		fggl::gui::model::Widget content;
-		content.set("position", fggl::math::vec2{0.0F, 50.0F});
-		content.set("size", fggl::math::vec2{INFINITY, INFINITY});
-		content.set("colour", fggl::gfx::colours::BURLYWOOD);
-		widget.addChild(content);
-
-		fggl::gui::renderer::visit(widget, paint);
+		auto widgetFactory = app.service<fggl::gui::model::WidgetFactory>();
+
+		auto widget = widgetFactory->buildEmpty();
+		widget->set("position", fggl::math::vec2{200.0F, 100.F});
+		widget->set("size", fggl::math::vec2{500.0F, 300.F});
+		widget->set("colour", fggl::gfx::colours::BLANCHED_ALMOND);
+		fggl::gui::model::attr_box_set(*widget, "padding", 5.0F);
+
+		auto handle = widgetFactory->buildEmpty();
+		handle->set("border::bottom",5.0F);
+		handle->set("position", fggl::math::vec2{0.0F, 0.0F});
+		//handle->set("size", fggl::math::vec2{INFINITY, 50.0F});
+		handle->set("text", "hello, world!");
+		handle->set("colour", fggl::gfx::colours::ORANGE);
+		fggl::gui::model::attr_box_set(*handle, "padding", 5.0F);
+		widget->addChild(*handle);
+		delete handle;
+
+		auto content = widgetFactory->buildEmpty();
+		content->set("position", fggl::math::vec2{0.0F, 50.0F});
+		//content->set("size", fggl::gui::model::UNDEFINED_SIZE);
+		content->set("colour", fggl::gfx::colours::BURLYWOOD);
+		widget->addChild(*content);
+		delete content;
+
+		fggl::gui::renderer::layout(*widget);
+		fggl::gui::renderer::visit(*widget, paint);
 	}
 
 
@@ -350,7 +357,7 @@ namespace demo {
 
 	void GridScene::render(fggl::gfx::Graphics &gfx) {
 		fggl::gfx::Paint paint;
-		render_grid(paint, *m_grid);
+		render_grid(paint, *m_grid, owner());
 		render_objects(paint, *m_grid);
 
 		m_canvas.render(paint);
diff --git a/fggl/data/assimp/module.cpp b/fggl/data/assimp/module.cpp
index 0b070168e1eb2567c1ffe82bd3873dd21f8fde22..d7852e26a475695a7493ea3354f6ce8d46627a6a 100644
--- a/fggl/data/assimp/module.cpp
+++ b/fggl/data/assimp/module.cpp
@@ -232,7 +232,7 @@ namespace fggl::data::models {
 			return nullptr;
 		} else {
 			debug::info("image reports it loaded correctly, adding {} to database", guid);
-			manager->set(guid, image);
+			manager->set(guid, image );
 		}
 
 		return nullptr;
diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp
index f35642c72de9d9c511769c05b7e8803927e3a2b7..ea6e7a2e8e387ccbda76a24cd3a16e47b347f096 100644
--- a/fggl/gfx/ogl/renderer.cpp
+++ b/fggl/gfx/ogl/renderer.cpp
@@ -141,43 +141,42 @@ namespace fggl::gfx {
 		cache->load(ShaderConfig::named("debug"));
 	}
 
-	static void splat_checkerboard(GLuint* memory) {
-		for (int i = 0; i < 128 * 128; ++i) {
-			if( i / 128 & 16 ^ i % 128 & 16 ) {
-				memory[i] = ogl4::TEX_WHITE;
-	        } else {
-				memory[i] = ogl4::TEX_CHECKER;
-        	}
+	static void splat_checkerboard(GLuint* memory, int width = 128, int height = 128) {
+		int counter = 0;
+		auto colour = ogl4::TEX_CHECKER;
+
+		for ( auto i = 0u; i < width * height; ++i) {
+			memory[i] = ogl4::TEX_CHECKER;
+			counter++;
+			if (counter == 5) {
+				counter = 0;
+				colour = colour == ogl4::TEX_CHECKER ? ogl4::TEX_WHITE : ogl4::TEX_CHECKER;
+			}
 		}
 	}
 
 	static auto make_solid(uint8_t width, uint8_t height, GLuint colour) -> ogl::Image {
-
-		auto *texData = new GLuint[width * height];
-		for (int i = 0; i < width * height; ++i) {
-			texData[i] = colour;
+		ogl::PixelDataArray data(ogl::PixelFormat::UNSIGNED_INT, width * height);
+		for ( auto i = 0u; i < width * height; ++i) {
+			data.ui[i] = colour;
 		}
 
-		return {
-			.type = ogl::PixelFormat::UNSIGNED_BYTE,
-			.format = ogl::ImageFormat::RGBA,
-			.size = {width, height},
-			.data = texData
-		};
+		return ogl::Image(
+			ogl::ImageFormat::RGBA,
+			{width, height},
+			std::move(data)
+		);
 	}
 
 	static void setup_fallback_texture(assets::AssetManager* assets) {
 		{
+			// generate the image
+			ogl::Image image(ogl::ImageFormat::RGBA, ogl::PixelFormat::UNSIGNED_INT, {128,128});
+			splat_checkerboard( image.data.ui );
+
+			// setup the texture
 			auto *fallback2D = new ogl::Texture(ogl::TextureType::Tex2D);
-			GLuint myData[128 * 128];
-			splat_checkerboard(myData);
-			ogl::Image image{
-				.type = ogl::PixelFormat::UNSIGNED_BYTE,
-				.format = ogl::ImageFormat::RGBA,
-				.size = {128, 128},
-				.data = myData
-			};
-			fallback2D->setData(ogl::InternalImageFormat::RedGreenBlueAlpha, image);
+			fallback2D->setData(ogl::InternalImageFormat::RedGreenBlueAlpha, image, ogl::PixelFormat::UNSIGNED_BYTE);
 			assets->set(ogl4::FALLBACK_TEX, fallback2D);
 		}
 
diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp
index 00da747d4f1dd1c9e0304d97e6fb1193ecd80fdc..21b733f1a5b0c3f8fc587617101311c8a268e34a 100644
--- a/fggl/gfx/ogl4/canvas.cpp
+++ b/fggl/gfx/ogl4/canvas.cpp
@@ -153,7 +153,7 @@ namespace fggl::gfx::ogl4 {
 
 
 		// get the expected font
-		std::shared_ptr<gui::FontFace> face = m_fonts->getFont("LiberationSans-Regular.ttf");
+		std::shared_ptr<gui::FontFace> face = m_fonts->getDefaultFont();
 		if (face == nullptr) {
 			// we don't know about that font...
 			return;
@@ -179,14 +179,15 @@ namespace fggl::gfx::ogl4 {
 			const auto label = textCmd.text;
 			math::vec2 penPos(textCmd.pos);
 
+			// setup a non-owning holder for the characters
+			data::Texture2D tex;
+			tex.channels = 1;
+
 			for (auto letter : label) {
-				ogl::Image img{};
-				img.format = ogl::ImageFormat::R;
-				img.type = ogl::PixelFormat::UNSIGNED_BYTE;
 
 				glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-				face->texture(letter, img.size.x, img.size.y, &img.data);
-				m_fontTex.setData(ogl::InternalImageFormat::Red, img);
+				face->texture(letter, tex.size.x, tex.size.y, &tex.data);
+				m_fontTex.setData(ogl::InternalImageFormat::Red, &tex);
 
 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@@ -218,6 +219,9 @@ namespace fggl::gfx::ogl4 {
 
 				penPos.x += (metrics.advance >> 6);
 			}
+
+			// textures assume they own their contained data, we need to make sure we clear it
+			tex.data = nullptr;
 		}
 
 		glDisable(GL_BLEND);
diff --git a/fggl/gfx/ogl4/meshes.cpp b/fggl/gfx/ogl4/meshes.cpp
index 7ccae929f1fae6c83576c749e485c26aa9f8cc00..d4f6469c52eb6a5029f970bb49f1cee587a51c7c 100644
--- a/fggl/gfx/ogl4/meshes.cpp
+++ b/fggl/gfx/ogl4/meshes.cpp
@@ -184,16 +184,12 @@ namespace fggl::gfx::ogl4 {
 			return texture;
 		}
 
+		// get the texture data we plan to load
 		auto *textureData = manager->get<data::Texture2D>(name);
-		ogl::Image image{
-			.type = ogl::PixelFormat::UNSIGNED_BYTE,
-			.format = ogl::ImageFormat::RGB,
-			.size = textureData->size,
-			.data = textureData->data,
-		};
 
+		// create a texture
 		texture = new ogl::Texture(ogl::TextureType::Tex2D);
-		texture->setData( ogl::InternalImageFormat::RedGreenBlue, image);
+		texture->setData( ogl::InternalImageFormat::RedGreenBlue, textureData);
 
 		return manager->set(uploadedTex, texture);
 	}
diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp
index cf1da9c482749b34b34aa9c7409d668a2b37950c..cac8e496a30f5af1f9471ef5e9fcb2545935c05e 100644
--- a/fggl/gfx/window.cpp
+++ b/fggl/gfx/window.cpp
@@ -190,6 +190,7 @@ namespace fggl::display::glfw {
 
 	Window::~Window() {
 		if ( m_graphics != nullptr ) {
+			delete m_graphics;
 			m_graphics = nullptr;
 		}
 
diff --git a/fggl/gui/containers.cpp b/fggl/gui/containers.cpp
index 15da80673aa12839530bd05872f897d60a4ece22..4f2a4b0c2c041f0d7c3cced6c22febbdb74162f6 100644
--- a/fggl/gui/containers.cpp
+++ b/fggl/gui/containers.cpp
@@ -113,6 +113,9 @@ namespace fggl::gui {
 				}
 			}
 
+			// cleanup variables
+			delete[] widths;
+			delete[] heights;
 		}
 	}
 
diff --git a/fggl/gui/fonts.cpp b/fggl/gui/fonts.cpp
index 172cc47108df4d6b47ac5004dbefd417f7492102..634543f346edb0e7908f80bd98a2ece0ddd79f45 100644
--- a/fggl/gui/fonts.cpp
+++ b/fggl/gui/fonts.cpp
@@ -25,13 +25,13 @@ namespace fggl::gui {
 
 	FontLibrary::FontLibrary(data::Storage *storage) : m_context(nullptr), m_storage(storage) {
 		FT_Init_FreeType(&m_context);
+		m_defaultFont = getFont(DEFAULT_FONT_NAME);
 	}
 
 	FontLibrary::~FontLibrary() {
 		// free all fonts
-		for (auto &face : m_cache) {
-			face.second = nullptr;
-		}
+		m_defaultFont = nullptr;
+		m_cache.clear();
 
 		// shut the library down
 		FT_Done_FreeType(m_context);
@@ -54,7 +54,7 @@ namespace fggl::gui {
 		return it.first->second;
 	}
 
-	void FontFace::texture(char letter, int &width, int &height, void **buff) {
+	void FontFace::texture(char letter, int &width, int &height, unsigned char **buff) {
 		if (FT_Load_Char(m_face, letter, FT_LOAD_RENDER)) {
 			// something bad happened
 			return;
diff --git a/fggl/gui/model/parser.cpp b/fggl/gui/model/parser.cpp
index ab42e05c7a4ffc7833d1dbfc422bca3e616998ba..92c2493772f7501f8e770190f3332db2c64d4ca4 100644
--- a/fggl/gui/model/parser.cpp
+++ b/fggl/gui/model/parser.cpp
@@ -42,7 +42,8 @@ namespace fggl::gui::model {
 
 		// are we a template definition?
 		if ( config["define"] ) {
-			factory.push( config["define"].as<std::string>(), *root );
+			factory.push( config["define"].as<std::string>(), std::move(*root) );
+			return factory.getTemplate( config["define"].as<std::string>() );
 		}
 
 		return root;
diff --git a/fggl/gui/model/structure.cpp b/fggl/gui/model/structure.cpp
index baf5d17f5ce5936b5b91c3b1dd2cefe66833afce..a6c48a96ed869f0b3e65af06f5b40e13bfb06bca 100644
--- a/fggl/gui/model/structure.cpp
+++ b/fggl/gui/model/structure.cpp
@@ -17,7 +17,54 @@
 //
 
 #include "fggl/gui/model/structure.hpp"
+#include "fggl/gui/fonts.hpp"
+
+#include <algorithm>
+#include <utility>
+#include <string>
 
 namespace fggl::gui::model {
 
+	math::vec2 calcTextBounds(const std::string& value, std::shared_ptr<FontFace> face) {
+		if ( face == nullptr ){
+			debug::warning("No preferred font sent, cowardly refusing to process text");
+			return {};
+		}
+
+		math::vec2 max{0, 0};
+		for (auto letter : value) {
+			auto metrics = face->metrics(letter);
+
+			max.x += metrics.size.x + (metrics.advance >> 6);
+			max.y = std::max(max.y, metrics.size.y);
+		}
+		return max;
+	}
+
+	inline math::vec2 calcBoxContrib(const Widget& widget, const std::string& name) {
+		return math::vec2{
+			widget.get_or_default<float>( name + "::left") + widget.get_or_default<float>(name + "::right"),
+			widget.get_or_default<float>( name + "::top" ) + widget.get_or_default<float>( name + "::bottom" )
+		};
+	}
+
+	void Widget::calcPrefSize(std::shared_ptr<FontFace> face) {
+		if ( !m_dirty ){
+			return;
+		}
+
+		auto padding = calcBoxContrib( *this, "padding");
+		auto border = calcBoxContrib( *this, "border");
+		auto content = math::vec2{0,0};
+
+		if (hasAttr("text")) {
+			content += calcTextBounds( get<std::string>("text"), std::move(face) );
+		}
+
+		m_cachedSize = padding + content + content;
+		debug::info("my preferred size is: ({}, {})", m_cachedSize.x, m_cachedSize.y);
+
+		m_dirty = false;
+	}
+
 }
\ No newline at end of file
diff --git a/fggl/gui/renderer/renderer.cpp b/fggl/gui/renderer/renderer.cpp
index 8c6d98848e65da825db3aceeea1e192d2cd1b813..8e336736ab3e21ca78ec5e2fb484a275af8898f7 100644
--- a/fggl/gui/renderer/renderer.cpp
+++ b/fggl/gui/renderer/renderer.cpp
@@ -66,6 +66,40 @@ namespace fggl::gui::renderer {
 		paint.fill(path);
 	}
 
+	void layout(model::Widget& current) {
+		if ( current.isLeaf() ) {
+			// if the widget has a defined size, use that
+			if ( current.hasAttr("size") && !current.hasAttr("text") ) {
+				return;
+			}
+
+			// else, use the model's preferred size
+			auto preferred = current.preferredSize();
+			if ( preferred.has_value() ) {
+				current.set<math::vec2>("size", preferred.value() );
+			}
+		} else {
+			auto topPad = current.get_or_default<float>("border:top") + current.get_or_default<float>("padding::top");
+			auto leftPad = current.get_or_default<float>("border::left") + current.get_or_default<float>("padding::left");
+
+			math::vec2 size = {topPad, leftPad};
+
+			// layout all children
+			for ( auto& child : current ) {
+				layout(child);
+
+				auto childSize = child.get_or_default<math::vec2>("size");
+				size.x = std::max( childSize.x, size.x );
+				size.y += childSize.y;
+
+				child.set<math::vec2>("position", {leftPad, size.y});
+			}
+
+			// set our size based on that
+			current.set<math::vec2>("size", size );
+		}
+	}
+
 	void visit(const model::Widget& root, gfx::Paint& paint, Box offset) {
 		// get border size
 		auto border = get_box(root, "border");
@@ -79,12 +113,12 @@ namespace fggl::gui::renderer {
 		bounds.left += offset.left;
 
 		// deal with right hand size bounds
-		bounds.right = std::min( size.x, offset.width() );
-		bounds.right += offset.left;
+		//bounds.right = std::min( size.x, offset.width() );
+		//bounds.right += offset.left;
 
 		// deal with bottom bounds
-		bounds.bottom = std::min( size.y, offset.height() );
-		bounds.bottom += offset.top;
+		//bounds.bottom = std::min( size.y, offset.height() );
+		//bounds.bottom += offset.top;
 
 		auto background = bounds.trim(border);
 
diff --git a/include/fggl/assets/manager.hpp b/include/fggl/assets/manager.hpp
index de8f3713f4a3921e05e03a80f67445469bea73a0..ef1ffee15a0b737154f9fac4e6f829261e45b4e1 100644
--- a/include/fggl/assets/manager.hpp
+++ b/include/fggl/assets/manager.hpp
@@ -41,6 +41,24 @@ namespace fggl::assets {
 	struct AssetBoxT : public AssetBox {
 		T* asset = nullptr;
 
+		explicit inline AssetBoxT(T* aasset) : asset(aasset) {}
+
+		// no copy: we own our resource!
+		AssetBoxT(const AssetBoxT&) = delete;
+		AssetBoxT& operator=(const AssetBoxT&) = delete;
+
+		// move OK - we can steal the asset
+		AssetBoxT(AssetBoxT&& other) : asset(other.asset) {
+			other.asset = nullptr;
+		}
+
+		inline ~AssetBoxT() override {
+			if ( asset != nullptr ) {
+				delete asset;
+				asset = nullptr;
+			}
+		}
+
 		inline void release() override {
 			asset = nullptr;
 		}
@@ -74,10 +92,21 @@ namespace fggl::assets {
 				}
 			}
 
+			/**
+			 * Pass ownership of the asset to the asset system.
+			 *
+			 * Once this method is called, the asset system owns the asset, and will free
+			 * it when it is no longer required. The asset system assumes it is fully aware
+			 * of all usages of the assets it manages via its graph.
+			 *
+			 * @tparam T the asset type to be managed
+			 * @param guid the asset name
+			 * @param assetRef the asset itself
+			 * @return the owned asset pointer
+			 */
 			template<typename T>
 			T* set(const AssetID &guid, T* assetRef) {
-				auto ptr = std::make_shared<AssetBoxT<T>>();
-				ptr->asset = assetRef;
+				auto ptr = std::make_shared<AssetBoxT<T>>(assetRef);
 
 				m_registry[guid] = ptr;
 				return (*ptr).asset;
diff --git a/include/fggl/audio/audio.hpp b/include/fggl/audio/audio.hpp
index 67c8889b8fd80d889500e1b351da07d46f595d21..a31eb6d57fb8ba149e29f129afceae81211e1983 100644
--- a/include/fggl/audio/audio.hpp
+++ b/include/fggl/audio/audio.hpp
@@ -37,6 +37,14 @@ namespace fggl::audio {
 		int sampleCount = -1;
 		T *data = nullptr;
 
+		AudioClip() = default;
+		AudioClip(const AudioClip&) = delete;
+
+		inline ~AudioClip() {
+			std::free(data);
+			data = nullptr;
+		}
+
 		[[nodiscard]]
 		inline int size() const {
 			return sampleCount * sizeof(T);
diff --git a/include/fggl/data/texture.hpp b/include/fggl/data/texture.hpp
index 243e331d785761af7c544b4c8946353f92cf4394..b1cf34c04c25f42855f7e8c15dc226a0042fff63 100644
--- a/include/fggl/data/texture.hpp
+++ b/include/fggl/data/texture.hpp
@@ -28,7 +28,14 @@ namespace fggl::data {
 	struct Texture2D {
 		math::vec2i size;
 		int channels;
-		void* data;
+		unsigned char* data;
+
+		Texture2D() = default;
+		Texture2D(const Texture2D& other) = delete;
+
+		inline ~Texture2D() {
+			delete data;
+		}
 	};
 
 }
diff --git a/include/fggl/gfx/ogl/types.hpp b/include/fggl/gfx/ogl/types.hpp
index 4478b17e9c60f7996c86d5b3862c2880a5b92cb7..af5e6a36993d25be79fe1ab2ede03d2f120599ad 100644
--- a/include/fggl/gfx/ogl/types.hpp
+++ b/include/fggl/gfx/ogl/types.hpp
@@ -27,6 +27,7 @@
 
 #include "fggl/gfx/ogl/common.hpp"
 #include "fggl/math/types.hpp"
+#include "fggl/data/texture.hpp"
 
 namespace fggl::gfx::ogl {
 
@@ -177,7 +178,7 @@ namespace fggl::gfx::ogl {
 		SHORT = GL_SHORT,
 		UNSIGNED_INT = GL_UNSIGNED_INT,
 		INT = GL_INT,
-		HALF_FLOAT = GL_HALF_FLOAT,
+		//HALF_FLOAT = GL_HALF_FLOAT,
 		FLOAT = GL_FLOAT,
 /*
 		UNSIGNED_BYTE_3_3_2,
@@ -221,11 +222,165 @@ namespace fggl::gfx::ogl {
 		DEPTH_STENCIL = GL_DEPTH_STENCIL
 	};
 
-	struct Image {
+	struct PixelDataArray {
 		PixelFormat type;
+		union {
+			unsigned char *uc;
+			char *c;
+			std::uint16_t *us;
+			std::int16_t *s;
+			float *f;
+			std::int32_t *i;
+			std::uint32_t *ui;
+		};
+		bool owning;
+
+		inline PixelDataArray(PixelFormat fmt, std::size_t size) : type(fmt), owning(true) {
+			switch (type) {
+				case PixelFormat::UNSIGNED_BYTE:
+					uc = new unsigned char[size];
+					break;
+				case PixelFormat::BYTE:
+					c = new char[size];
+					break;
+				case PixelFormat::UNSIGNED_SHORT:
+					us = new std::uint16_t[size];
+					break;
+				case PixelFormat::SHORT:
+					s = new std::int16_t[size];
+					break;
+				case PixelFormat::FLOAT:
+					f = new float[size];
+					break;
+				case PixelFormat::INT:
+					i = new std::int32_t[size];
+					break;
+				case PixelFormat::UNSIGNED_INT:
+					ui = new std::uint32_t[size];
+					break;
+			}
+		}
+
+		inline explicit PixelDataArray(unsigned char* data) : type(PixelFormat::UNSIGNED_BYTE), uc(data), owning(false) {}
+		inline explicit PixelDataArray(char* data) : type(PixelFormat::BYTE), c(data), owning(false) {}
+
+		// no copy
+		PixelDataArray(const PixelDataArray&) = delete;
+		PixelDataArray& operator=(const PixelDataArray&) = delete;
+
+		// move ok
+		PixelDataArray(PixelDataArray&& other) : type(other.type), owning(other.owning) {
+			switch (type) {
+				case PixelFormat::UNSIGNED_BYTE:
+					uc = other.uc;
+					other.uc = nullptr;
+					break;
+				case PixelFormat::BYTE:
+					c = other.c;
+					other.c = nullptr;
+					break;
+				case PixelFormat::UNSIGNED_SHORT:
+					us = other.us;
+					other.us = nullptr;
+					break;
+				case PixelFormat::SHORT:
+					s = other.s;
+					other.s = nullptr;
+					break;
+				case PixelFormat::FLOAT:
+					f = other.f;
+					other.f = nullptr;
+					break;
+				case PixelFormat::INT:
+					i = other.i;
+					other.i = nullptr;
+					break;
+				case PixelFormat::UNSIGNED_INT:
+					ui = other.ui;
+					other.ui = nullptr;
+					break;
+			}
+		}
+
+		inline ~PixelDataArray() {
+			if (owning) {
+				switch (type) {
+				case PixelFormat::UNSIGNED_BYTE: delete[] uc;
+					uc = nullptr;
+					break;
+				case PixelFormat::BYTE: delete[] c;
+					c = nullptr;
+					break;
+				case PixelFormat::UNSIGNED_SHORT: delete[] us;
+					us = nullptr;
+					break;
+				case PixelFormat::SHORT: delete[] s;
+					s = nullptr;
+					break;
+				case PixelFormat::FLOAT: delete[] f;
+					f = nullptr;
+					break;
+				case PixelFormat::INT: delete[] i;
+					i = nullptr;
+					break;
+				case PixelFormat::UNSIGNED_INT:
+					delete[] ui;
+					ui = nullptr;
+					break;
+				}
+			}
+		}
+
+		void* data() {
+			switch (type) {
+				case PixelFormat::UNSIGNED_BYTE:
+					return uc;
+				case PixelFormat::BYTE:
+					return c;
+				case PixelFormat::UNSIGNED_SHORT:
+					return us;
+				case PixelFormat::SHORT:
+					return s;
+				case PixelFormat::FLOAT:
+					return f;
+				case PixelFormat::INT:
+					return i;
+				case PixelFormat::UNSIGNED_INT:
+					return ui;
+			}
+
+			// unknown type?
+			return nullptr;
+		}
+
+	};
+
+	struct Image {
 		ImageFormat format;
 		math::vec2i size;
-		void *data;
+		PixelDataArray data;
+
+		//Image() = default;
+
+		inline Image(ImageFormat fmt, PixelFormat pxFmt, math::vec2i asize) :
+			format(fmt),
+			size(asize),
+			data(pxFmt, asize.x * asize.y){}
+
+		inline Image(ImageFormat fmt, math::vec2i asize, PixelDataArray&& adata) :
+			format(fmt),
+			size(asize),
+			data(std::move(adata)) {}
+
+		Image(const Image&) = delete;
+
+		inline PixelFormat type() const {
+			return data.type;
+		}
+
+		void* dataPtr() {
+			return data.data();
+		}
 	};
 
 	class Texture {
@@ -257,6 +412,25 @@ namespace fggl::gfx::ogl {
 				}
 			}
 
+			void setData(InternalImageFormat iFmt, Image &image, PixelFormat extFormat) {
+				//bind();
+				glBindTexture((GLenum) m_type, m_obj);
+				glTexImage2D((GLenum) m_type,
+					0,
+					(GLint) iFmt,
+					image.size.x,
+					image.size.y,
+					0,
+					(GLenum) image.format,
+					(GLenum) extFormat,
+					image.dataPtr());
+
+				if ( m_type == TextureType::Tex2D ) {
+					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+				}
+			}
+
 			void setData(InternalImageFormat iFmt, Image &image) {
 				//bind();
 				glBindTexture((GLenum) m_type, m_obj);
@@ -267,8 +441,42 @@ namespace fggl::gfx::ogl {
 							 image.size.y,
 							 0,
 							 (GLenum) image.format,
-							 (GLenum) image.type,
-							 image.data);
+							 (GLenum) image.type(),
+							 image.dataPtr());
+
+				if ( m_type == TextureType::Tex2D ) {
+					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+				}
+			}
+
+			void setData(InternalImageFormat iFmt, const data::Texture2D *image) {
+
+				ImageFormat imageFormat;
+				if (image->channels == 1) {
+					imageFormat = ImageFormat::R;
+				} else if ( image->channels == 2) {
+					imageFormat = ImageFormat::RG;
+				} else if ( image->channels == 3) {
+					imageFormat = ImageFormat::RGB;
+				} else if ( image->channels == 4) {
+					imageFormat = ImageFormat::RGBA;
+				} else {
+					// unknown image format -> channels mapping, having a bad day!
+					return;
+				}
+
+				//bind();
+				glBindTexture((GLenum) m_type, m_obj);
+				glTexImage2D((GLenum) m_type,
+							 0,
+							 (GLint) iFmt,
+							 image->size.x,
+							 image->size.y,
+							 0,
+							 (GLenum) imageFormat,
+							 GL_UNSIGNED_BYTE,
+							 image->data);
 
 				if ( m_type == TextureType::Tex2D ) {
 					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@@ -284,8 +492,8 @@ namespace fggl::gfx::ogl {
 							 image.size.x,
 							 image.size.y,
 							 (GLenum) image.format,
-							 (GLenum) image.type,
-							 image.data);
+							 (GLenum) image.type(),
+							 image.dataPtr());
 			}
 
 			void wrapMode(Wrapping wrap);
diff --git a/include/fggl/gfx/ogl4/fallback.hpp b/include/fggl/gfx/ogl4/fallback.hpp
index 5c466d46db5bb4a08e35ec31e0d85775a2632d23..297e794c2fa3cb99082698e6e4f55a4d8a1a0703 100644
--- a/include/fggl/gfx/ogl4/fallback.hpp
+++ b/include/fggl/gfx/ogl4/fallback.hpp
@@ -60,8 +60,8 @@ namespace fggl::gfx::ogl4 {
 			fragColour = vec4(colour.xyz, texture(tex, texPos).r);
 		})glsl";
 
-	constexpr const GLuint TEX_WHITE = 0xFFFFFFFF;
-	constexpr const GLuint TEX_CHECKER = 0x00FF00FF; //FIXME pixel order is reversed?!
+	constexpr const GLuint TEX_CHECKER = 0x11FF11FF; //FIXME pixel order is reversed?!
+	constexpr const GLuint TEX_WHITE = 0xFF0000FF;
 
 	constexpr const assets::AssetID FALLBACK_TEX = assets::make_asset_id("fallback", "FALLBACK_TEX");
 	constexpr const assets::AssetID FALLBACK_MAT = assets::make_asset_id("fallback", "FALLBACK_MAT");
diff --git a/include/fggl/gui/fonts.hpp b/include/fggl/gui/fonts.hpp
index 2aa44a41e83e0141fa3b7c01abe6b61b0dabbaf5..902f85ebaf8610db6462b0a2d6b9e71bd873db26 100644
--- a/include/fggl/gui/fonts.hpp
+++ b/include/fggl/gui/fonts.hpp
@@ -30,7 +30,10 @@
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
+
 namespace fggl::gui {
+	constexpr const char* DEFAULT_FONT_NAME = "LiberationSans-Regular.ttf";
+
 
 	struct GlyphMetrics {
 		math::vec2 size;
@@ -57,7 +60,7 @@ namespace fggl::gui {
 			}
 
 			math::vec2 stringSize(const std::string &text);
-			void texture(char letter, int &width, int &height, void **buff);
+			void texture(char letter, int &width, int &height, unsigned char **buff);
 
 		private:
 			FT_Face m_face;
@@ -70,7 +73,7 @@ namespace fggl::gui {
 		public:
 			constexpr static const modules::ModuleService service = modules::make_service("fggl::gui::font");
 
-			FontLibrary(data::Storage *storage);
+			explicit FontLibrary(data::Storage *storage);
 			~FontLibrary();
 
 			// copy and moving not needed
@@ -100,10 +103,18 @@ namespace fggl::gui {
 				return ptr;
 			}
 
+			inline void setDefaultFont(const std::string& name) {
+				m_defaultFont = getFont(name);
+			}
+
+			inline std::shared_ptr<FontFace> getDefaultFont() const {
+				return m_defaultFont;
+			}
 		private:
 			FT_Library m_context;
 			data::Storage *m_storage;
 			std::map<const std::string, std::shared_ptr<FontFace>> m_cache;
+			std::shared_ptr<FontFace> m_defaultFont;
 	};
 
 } // nmespace fggl::gui
diff --git a/include/fggl/gui/model/parser.hpp b/include/fggl/gui/model/parser.hpp
index 608aaa70ff4f82d16a25bf4c0c261d13e7860b3a..c87ede4166418d5aca1ec3e79c64d309828741cc 100644
--- a/include/fggl/gui/model/parser.hpp
+++ b/include/fggl/gui/model/parser.hpp
@@ -20,25 +20,41 @@
 #define FGGL_GUI_MODEL_PARSER_HPP
 
 #include <fggl/gui/model/structure.hpp>
+#include <fggl/gui/fonts.hpp>
+#include <fggl/modules/module.hpp>
 
 namespace fggl::gui::model {
 
+	constexpr auto WIDGET_FACTORY_SERVICE = modules::make_service("gui::WidgetService");
+
 	class WidgetFactory {
 		public:
+			constexpr static auto service = WIDGET_FACTORY_SERVICE;
+			inline WidgetFactory(FontLibrary* lib) : m_fontLibrary(lib) {}
+
 			inline Widget* build(std::string templateName) {
-				return new Widget(m_templates.at(templateName));
+				return new Widget(m_templates.at(templateName).get());
 			}
 
 			inline Widget* buildEmpty() {
-				return new Widget();
+				return new Widget(m_fontLibrary);
+			}
+
+			inline void push(std::string name, const Widget&& definition) {
+				m_templates.emplace(name, std::make_unique<Widget>(definition));
 			}
 
-			inline void push(std::string name, const Widget& definition) {
-				m_templates[name] = definition;
+			Widget* getTemplate(const std::string& name) {
+				auto itr = m_templates.find(name);
+				if ( itr == m_templates.end() ){
+					return nullptr;
+				}
+				return itr->second.get();
 			}
 
 		private:
-			std::map<std::string, Widget> m_templates;
+			std::map<std::string, std::unique_ptr<Widget>> m_templates;
+			FontLibrary* m_fontLibrary;
 	};
 
 	Widget* parseFile(WidgetFactory& factory, const std::string& path);
diff --git a/include/fggl/gui/model/structure.hpp b/include/fggl/gui/model/structure.hpp
index a8cd119bbf348585e47089da3adb65c47c0ce825..7f2063b66d123a6c68a67aa4e44508fbe40635c4 100644
--- a/include/fggl/gui/model/structure.hpp
+++ b/include/fggl/gui/model/structure.hpp
@@ -23,19 +23,42 @@
 #include <variant>
 #include <vector>
 #include <string>
+#include <optional>
 
 #include <fggl/math/types.hpp>
+#include <fggl/gui/fonts.hpp>
 
 namespace fggl::gui::model {
 
 	using AttrKey = std::string;
 	using AttrValue = std::variant<bool, int, float, std::string, math::vec2, math::vec3>;
 
+	constexpr float UNDEFINED = INFINITY;
+	constexpr math::vec2 UNDEFINED_SIZE{INFINITY, INFINITY};
+
 	class Widget {
 		public:
 			using ChildItr = std::vector<Widget>::iterator;
 			using ChildItrConst = std::vector<Widget>::const_iterator;
 
+			inline Widget(FontLibrary* library) :
+				m_parent(nullptr),
+				m_children(),
+				m_fontLibrary(library),
+				m_attrs(),
+				m_cachedSize(),
+				m_dirty(true) {
+			}
+
+			explicit inline Widget(Widget* parent) :
+				m_parent(parent),
+				m_children(),
+				m_fontLibrary(parent->m_fontLibrary),
+				m_attrs(),
+				m_cachedSize(),
+				m_dirty(true) {
+			}
+
 			inline bool isRoot() const {
 				return m_parent == nullptr;
 			}
@@ -101,10 +124,20 @@ namespace fggl::gui::model {
 				return m_children.cend();
 			}
 
+			inline std::optional<math::vec2> preferredSize() {
+				calcPrefSize(m_fontLibrary->getDefaultFont());
+				return m_cachedSize;
+			}
+
 		private:
 			Widget *m_parent;
 			std::vector<Widget> m_children;
+			FontLibrary* m_fontLibrary;
 			std::map< AttrKey, AttrValue > m_attrs;
+			math::vec2 m_cachedSize;
+			bool m_dirty;
+
+			void calcPrefSize(std::shared_ptr<FontFace> face);
 	};
 
 	void attr_box_set(Widget& widget, const AttrKey& key, auto top, auto right, auto bottom, auto left) {
diff --git a/include/fggl/gui/module.hpp b/include/fggl/gui/module.hpp
index 3e54d06f6210f001cb8b361257904f93f1f9d6c6..cfe103430160db19c8e09dba13ec4bd95def553d 100644
--- a/include/fggl/gui/module.hpp
+++ b/include/fggl/gui/module.hpp
@@ -20,6 +20,8 @@
 #define FGGL_GUI_MODULE_HPP
 
 #include "fggl/gui/fonts.hpp"
+#include "fggl/gui/model/parser.hpp"
+
 #include "fggl/data/module.hpp"
 #include "fggl/assets/packed/module.hpp"
 
@@ -37,8 +39,9 @@ namespace fggl::gui {
 
 	struct FreeType {
 		constexpr static const char *name = "fggl::gui::FreeType";
-		constexpr static const std::array<modules::ModuleService, 1> provides = {
-			FontLibrary::service
+		constexpr static const std::array<modules::ModuleService, 2> provides = {
+			FontLibrary::service,
+			model::WidgetFactory::service
 		};
 		constexpr static const std::array<modules::ModuleService, 2> depends = {
 			data::Storage::service,
@@ -57,6 +60,13 @@ namespace fggl::gui {
 
 			return true;
 		}
+
+		if ( service == model::WidgetFactory::service ) {
+			auto fonts = services.get<FontLibrary>();
+			services.create<model::WidgetFactory>(fonts);
+			return true;
+		}
+
 		return false;
 	}
 }
diff --git a/include/fggl/gui/renderer/renderer.hpp b/include/fggl/gui/renderer/renderer.hpp
index c90d8034c9ac6af88c4f1fab4ebe71a098adb4d8..393ecd11214eaca48d0b4232c1f0230417a68eeb 100644
--- a/include/fggl/gui/renderer/renderer.hpp
+++ b/include/fggl/gui/renderer/renderer.hpp
@@ -97,6 +97,8 @@ namespace fggl::gui::renderer {
 
 	void draw_background_solid(gfx::Paint& paint, Box& bounds, math::vec3 colour);
 
+	void layout(model::Widget& root);
+
 	void visit(const model::Widget& root, gfx::Paint& paint, Box offset = {0.0F, 1024.0F, 768.0F, 0.0F});
 
 } // namespace fggl::gui::renderer