changeset 208:263122adef77

client: add texture and painter closes #966 @2h closes #967 @2h
author David Demelier <markand@malikania.fr>
date Wed, 05 Dec 2018 22:24:44 +0100
parents 10687519f46e
children b788b6a20eea
files examples/animation/main.cpp examples/font/main.cpp examples/image/main.cpp examples/js-animation/main.cpp examples/js-font/main.cpp examples/js-image/main.cpp examples/js-sprite/main.cpp examples/js-window/main.cpp examples/sprite/main.cpp libmlk-client-js/CMakeLists.txt libmlk-client-js/malikania/client/js/animator_js_api.cpp libmlk-client-js/malikania/client/js/font_js_api.cpp libmlk-client-js/malikania/client/js/image_js_api.cpp libmlk-client-js/malikania/client/js/painter_js_api.cpp libmlk-client-js/malikania/client/js/painter_js_api.hpp libmlk-client-js/malikania/client/js/sprite_js_api.cpp libmlk-client-js/malikania/client/js/texture_js_api.cpp libmlk-client-js/malikania/client/js/texture_js_api.hpp libmlk-client-js/malikania/client/js/window_js_api.cpp libmlk-client/CMakeLists.txt libmlk-client/malikania/client/animator.cpp libmlk-client/malikania/client/animator.hpp libmlk-client/malikania/client/button.cpp libmlk-client/malikania/client/button.hpp libmlk-client/malikania/client/event.cpp libmlk-client/malikania/client/event.hpp libmlk-client/malikania/client/font.cpp libmlk-client/malikania/client/font.hpp libmlk-client/malikania/client/image.cpp libmlk-client/malikania/client/image.hpp libmlk-client/malikania/client/label.cpp libmlk-client/malikania/client/label.hpp libmlk-client/malikania/client/painter.cpp libmlk-client/malikania/client/painter.hpp libmlk-client/malikania/client/sprite.cpp libmlk-client/malikania/client/sprite.hpp libmlk-client/malikania/client/state/lobby_state.cpp libmlk-client/malikania/client/state/login_state.cpp libmlk-client/malikania/client/state/map_state.cpp libmlk-client/malikania/client/texture.cpp libmlk-client/malikania/client/texture.hpp libmlk-client/malikania/client/theme.cpp libmlk-client/malikania/client/theme.hpp libmlk-client/malikania/client/widget.hpp libmlk-client/malikania/client/window.cpp libmlk-client/malikania/client/window.hpp mlk-client/CMakeLists.txt mlk-client/main.cpp
diffstat 48 files changed, 1467 insertions(+), 798 deletions(-) [+]
line wrap: on
line diff
--- a/examples/animation/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/animation/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -25,6 +25,7 @@
 #include <malikania/client/animator.hpp>
 #include <malikania/client/loader.hpp>
 #include <malikania/client/window.hpp>
+#include <malikania/client/painter.hpp>
 
 #include <malikania/locator.hpp>
 #include <malikania/point.hpp>
@@ -36,6 +37,7 @@
 	try {
 		mlk::directory_locator locator(CMAKE_CURRENT_SOURCE_DIR "/resources");
 		mlk::client::window win(400, 400);
+		mlk::client::painter painter(win);
 		mlk::client::loader loader(locator);
 		mlk::client::animation animation = loader.load_animation("animations/margins.json");
 		mlk::client::animator animator(animation);
@@ -44,10 +46,10 @@
 		int y = (400 / 2) - (animation.sprite.get_cell().height / 2);
 
 		while (timer.elapsed().wall / 1000000LL < 8000) {
-			win.clear();
-			animator.draw(win, {x, y});
+			painter.clear();
+			animator.draw(painter, {x, y});
 			animator.update();
-			win.present();
+			painter.present();
 		}
 	} catch (const std::exception& ex) {
 		std::cerr << ex.what() << std::endl;
--- a/examples/font/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/font/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -23,6 +23,7 @@
 #include <malikania/client/loader.hpp>
 #include <malikania/client/color.hpp>
 #include <malikania/client/font.hpp>
+#include <malikania/client/painter.hpp>
 #include <malikania/client/window.hpp>
 
 #include <malikania/locator.hpp>
@@ -31,93 +32,93 @@
 
 using namespace std::chrono_literals;
 
-void topleft(mlk::client::window& window, mlk::client::font& font)
+void topleft(mlk::client::painter& painter, mlk::client::font& font)
 {
-	window.set_drawing_color(mlk::client::color::from_name("black"));
-	window.clear();
-	window.set_drawing_color(mlk::client::color::from_name("white"));
-	window.draw_text("top left", font, {10, 10});
-	window.present();
+	painter.set_drawing_color(mlk::client::color::from_name("black"));
+	painter.clear();
+	painter.set_drawing_color(mlk::client::color::from_name("white"));
+	font.draw(painter, "top left", {10, 10});
+	painter.present();
 
 	std::this_thread::sleep_for(1s);
 }
 
-void topright(mlk::client::window& window, mlk::client::font& font)
+void topright(mlk::client::painter& painter, mlk::client::font& font)
 {
 	auto dim = font.clip("top right");
 
-	window.set_drawing_color(mlk::client::color::from_name("black"));
-	window.clear();
-	window.set_drawing_color(mlk::client::color::from_name("white"));
-	window.draw_text("top right", font, {
+	painter.set_drawing_color(mlk::client::color::from_name("black"));
+	painter.clear();
+	painter.set_drawing_color(mlk::client::color::from_name("white"));
+	font.draw(painter, "top right", {
 		static_cast<int>(400 - dim.width - 10),
 		static_cast<int>(10)
 	});
-	window.present();
+	painter.present();
 
 	std::this_thread::sleep_for(1s);
 }
 
-void bottomleft(mlk::client::window& window, mlk::client::font& font)
+void bottomleft(mlk::client::painter& painter, mlk::client::font& font)
 {
 	auto dim = font.clip("bottom left");
 
-	window.set_drawing_color(mlk::client::color::from_name("black"));
-	window.clear();
-	window.set_drawing_color(mlk::client::color::from_name("white"));
-	window.draw_text("bottom left", font, {
+	painter.set_drawing_color(mlk::client::color::from_name("black"));
+	painter.clear();
+	painter.set_drawing_color(mlk::client::color::from_name("white"));
+	font.draw(painter, "bottom left", {
 		static_cast<int>(10),
 		static_cast<int>(400 - dim.height - 10)
 	});
-	window.present();
+	painter.present();
 
 	std::this_thread::sleep_for(1s);
 }
 
-void bottomright(mlk::client::window& window, mlk::client::font& font)
+void bottomright(mlk::client::painter& painter, mlk::client::font& font)
 {
 	auto dim = font.clip("bottom right");
 
-	window.set_drawing_color(mlk::client::color::from_name("black"));
-	window.clear();
-	window.set_drawing_color(mlk::client::color::from_name("white"));
-	window.draw_text("bottom right", font, {
+	painter.set_drawing_color(mlk::client::color::from_name("black"));
+	painter.clear();
+	painter.set_drawing_color(mlk::client::color::from_name("white"));
+	font.draw(painter, "bottom right", {
 		static_cast<int>(400 - dim.width - 10),
 		static_cast<int>(400 - dim.height - 10)
 	});
-	window.present();
+	painter.present();
 
 	std::this_thread::sleep_for(1s);
 }
 
-void center(mlk::client::window& window, mlk::client::font& font)
+void center(mlk::client::painter& painter, mlk::client::font& font)
 {
 	auto dim = font.clip("center");
 
-	window.set_drawing_color(mlk::client::color::from_name("black"));
-	window.clear();
-	window.set_drawing_color(mlk::client::color::from_name("white"));
-	window.draw_text("center", font, {
+	painter.set_drawing_color(mlk::client::color::from_name("black"));
+	painter.clear();
+	painter.set_drawing_color(mlk::client::color::from_name("white"));
+	font.draw(painter, "center", {
 		static_cast<int>(200 - (dim.width / 2)),
 		static_cast<int>(200 - (dim.height -2))
 	});
-	window.present();
+	painter.present();
 
 	std::this_thread::sleep_for(1s);
 }
 
-void center2(mlk::client::window& window, mlk::client::font& font)
+void center2(mlk::client::painter& painter, mlk::client::font& font)
 {
 	auto dim = font.clip("The world is Malikania.");
 
-	window.set_drawing_color(mlk::client::color::from_name("black"));
-	window.clear();
-	window.set_drawing_color(mlk::client::color::from_name("white"));
-	window.draw_text("The world is Malikania.", font, {
+	painter.set_drawing_color(mlk::client::color::from_name("black"));
+	painter.clear();
+	painter.set_drawing_color(mlk::client::color::from_name("white"));
+	font.draw(painter, "The world is Malikania.", {
 		static_cast<int>(200 - (dim.width / 2)),
 		static_cast<int>(200 - (dim.height -2))
 	});
-	window.present();
+	painter.present();
 
 	std::this_thread::sleep_for(3s);
 }
@@ -125,17 +126,18 @@
 int main(int, char**)
 {
 	try {
+		mlk::directory_locator locator(CMAKE_CURRENT_SOURCE_DIR "/resources");
 		mlk::client::window window(400, 400);
-		mlk::directory_locator locator(CMAKE_CURRENT_SOURCE_DIR "/resources");
+		mlk::client::painter painter(window);
 		mlk::client::loader loader(locator);
 		mlk::client::font font = loader.load_font("DejaVuSans.ttf", 10);
 
-		topleft(window, font);
-		topright(window, font);
-		bottomleft(window, font);
-		bottomright(window, font);
-		center(window, font);
-		center2(window, font);
+		topleft(painter, font);
+		topright(painter, font);
+		bottomleft(painter, font);
+		bottomright(painter, font);
+		center(painter, font);
+		center2(painter, font);
 	} catch (const std::exception& ex) {
 		std::cerr << ex.what() << std::endl;
 		return 1;
--- a/examples/image/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/image/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -20,26 +20,27 @@
 #include <iostream>
 #include <thread>
 
+#include <malikania/client/image.hpp>
 #include <malikania/client/loader.hpp>
-#include <malikania/client/image.hpp>
+#include <malikania/client/painter.hpp>
 #include <malikania/client/window.hpp>
 #include <malikania/locator.hpp>
 
 using namespace std::chrono_literals;
 
-void draw(mlk::client::window& window, mlk::client::loader& loader)
+void draw(mlk::client::painter& painter, mlk::client::loader& loader)
 {
 	try {
 		auto image = loader.load_image("images/smiley.png");
 		auto x = (400 / 2) - (image.get_size().width / 2);
 		auto y = (400 / 2) - (image.get_size().height / 2);
 
-		window.clear();
-		image.draw(window, {
+		painter.clear();
+		image.draw(painter, {
 			static_cast<int>(x),
 			static_cast<int>(y)
 		});
-		window.present();
+		painter.present();
 
 		std::this_thread::sleep_for(3s);
 	} catch (const std::exception &ex) {
@@ -52,10 +53,11 @@
 {
 	try {
 		mlk::client::window window(400, 400);
+		mlk::client::painter painter(window);
 		mlk::directory_locator locator(CMAKE_CURRENT_SOURCE_DIR "/resources");
 		mlk::client::loader loader(locator);
 
-		draw(window, loader);
+		draw(painter, loader);
 	} catch (const std::exception& ex) {
 		std::cerr << ex.what() << std::endl;
 		return 1;
--- a/examples/js-animation/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/js-animation/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -26,9 +26,10 @@
 
 #include <malikania/client/loader.hpp>
 
-#include <malikania/client/js/loader_js_api.hpp>
 #include <malikania/client/js/animation_js_api.hpp>
 #include <malikania/client/js/animator_js_api.hpp>
+#include <malikania/client/js/loader_js_api.hpp>
+#include <malikania/client/js/painter_js_api.hpp>
 #include <malikania/client/js/window_js_api.hpp>
 
 using namespace std::chrono_literals;
@@ -50,11 +51,13 @@
 
 		load_animation_api(ctx);
 		load_animator_api(ctx);
+		load_painter_api(ctx);
 		load_window_api(ctx);
                 put(ctx, loader);
 
 		const auto ret = duk_peval_string(ctx,
 			"w = new Malikania.Window();"
+			"p = new Malikania.Painter(w);"
 			"a = new Malikania.Animation('animations/margins.json');"
 			"d = new Malikania.Animator(a);"
 		);
@@ -66,11 +69,11 @@
 
 		while (timer.elapsed().wall / 1000000LL < 8000) {
 			const auto ret = duk_peval_string(ctx,
-				"w.setDrawingColor('lightskyblue');"
-				"w.clear();"
-				"d.draw(w, { x: 320 - 16, y: 240 - 16 });"
+				"p.setDrawingColor('lightskyblue');"
+				"p.clear();"
+				"d.draw(p, { x: 320 - 16, y: 240 - 16 });"
 				"d.update();"
-				"w.present();"
+				"p.present();"
 			);
 
 			if (ret != 0)
--- a/examples/js-font/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/js-font/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -27,6 +27,7 @@
 #include <malikania/client/js/font_js_api.hpp>
 #include <malikania/client/js/loader_js_api.hpp>
 #include <malikania/client/js/window_js_api.hpp>
+#include <malikania/client/js/painter_js_api.hpp>
 
 using namespace std::chrono_literals;
 
@@ -39,14 +40,15 @@
 {
 	const auto ret = duk_peval_string(ctx,
 		"w = new Malikania.Window();"
+		"p = new Malikania.Painter(w);"
 		"f = new Malikania.Font('DejaVuSans.ttf', 10);"
-		"w.setDrawingColor('lightskyblue');"
-		"w.clear();"
+		"p.setDrawingColor('lightskyblue');"
+		"p.clear();"
 		"s = 'The world is Malikania.';"
 		"c = f.clip(s);"
-		"w.setDrawingColor('white');"
-		"w.drawText(s, f, { x: 320 - (c.width / 2), y: 240 - (c.height / 2) });"
-		"w.present();"
+		"p.setDrawingColor('white');"
+		"f.draw(p, s, { x: 320 - (c.width / 2), y: 240 - (c.height / 2) });"
+		"p.present();"
 	);
 
 	if (ret != 0)
@@ -67,6 +69,8 @@
 
 		load_font_api(ctx);
 		load_window_api(ctx);
+		load_painter_api(ctx);
+
 		put(ctx, loader);
 		basic(ctx);
 	} catch (const std::exception& ex) {
--- a/examples/js-image/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/js-image/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -27,6 +27,7 @@
 #include <malikania/client/js/image_js_api.hpp>
 #include <malikania/client/js/loader_js_api.hpp>
 #include <malikania/client/js/window_js_api.hpp>
+#include <malikania/client/js/painter_js_api.hpp>
 
 using namespace std::chrono_literals;
 
@@ -39,11 +40,12 @@
 {
 	const auto ret = duk_peval_string(ctx,
 		"w = new Malikania.Window();"
+		"p = new Malikania.Painter(w);"
 		"i = new Malikania.Image('images/smiley.png');"
-		"w.setDrawingColor('lightskyblue');"
-		"w.clear();"
-		"i.draw(w, { x: 320 - 16, y: 240 - 16 });"
-		"w.present();"
+		"p.setDrawingColor('lightskyblue');"
+		"p.clear();"
+		"i.draw(p, { x: 320 - 16, y: 240 - 16 });"
+		"p.present();"
 	);
 
 	if (ret != 0)
@@ -56,11 +58,12 @@
 {
 	const auto ret = duk_peval_string(ctx,
 		"w = new Malikania.Window();"
+		"p = new Malikania.Painter(w);"
 		"i = new Malikania.Image('images/smiley.png');"
-		"w.setDrawingColor('lightskyblue');"
-		"w.clear();"
-		"i.draw(w, null, { x: 10, y: 10, width: 620, height: 460 });"
-		"w.present();"
+		"p.setDrawingColor('lightskyblue');"
+		"p.clear();"
+		"i.draw(p, null, { x: 10, y: 10, width: 620, height: 460 });"
+		"p.present();"
 	);
 
 	if (ret != 0)
@@ -81,6 +84,7 @@
 		put(ctx, loader);
 
 		load_image_api(ctx);
+		load_painter_api(ctx);
 		load_window_api(ctx);
 		basic(ctx);
 		stretch(ctx);
--- a/examples/js-sprite/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/js-sprite/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -28,6 +28,7 @@
 #include <malikania/client/js/image_js_api.hpp>
 #include <malikania/client/js/sprite_js_api.hpp>
 #include <malikania/client/js/window_js_api.hpp>
+#include <malikania/client/js/painter_js_api.hpp>
 
 using namespace std::chrono_literals;
 
@@ -40,6 +41,7 @@
 {
 	const auto ret = duk_peval_string(ctx,
 		"w = new Malikania.Window();"
+		"p = new Malikania.Painter(w);"
 		"s = new Malikania.Sprite('sprites/margins.json');"
 		"c = 0;"
 	);
@@ -49,10 +51,10 @@
 
 	for (unsigned c = 0; c < 12; ++c) {
 		const auto ret = duk_peval_string(ctx,
-			"w.setDrawingColor('lightskyblue');"
-			"w.clear();"
-			"s.draw(w, c++, { x: 320 - 16, y: 240 - 16 });"
-			"w.present();"
+			"p.setDrawingColor('lightskyblue');"
+			"p.clear();"
+			"s.draw(p, c++, { x: 320 - 16, y: 240 - 16 });"
+			"p.present();"
 		);
 
 		if (ret != 0)
@@ -74,6 +76,7 @@
 		load_image_api(ctx);
 		load_sprite_api(ctx);
 		load_window_api(ctx);
+		load_painter_api(ctx);
 		put(ctx, loader);
 		basic(ctx);
 	} catch (const std::exception& ex) {
--- a/examples/js-window/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/js-window/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -21,6 +21,7 @@
 #include <thread>
 
 #include <malikania/client/js/window_js_api.hpp>
+#include <malikania/client/js/painter_js_api.hpp>
 
 using namespace std::chrono_literals;
 
@@ -33,9 +34,10 @@
 {
 	const auto ret = duk_peval_string(ctx,
 		"w = new Malikania.Window();"
-		"w.setDrawingColor('lightskyblue');"
-		"w.clear();"
-		"w.present();"
+		"p = new Malikania.Painter(w);"
+		"p.setDrawingColor('lightskyblue');"
+		"p.clear();"
+		"p.present();"
 	);
 
 	if (ret != 0)
@@ -48,11 +50,12 @@
 {
 	const auto ret = duk_peval_string(ctx,
 		"w = new Malikania.Window();"
-		"w.setDrawingColor('lightskyblue');"
-		"w.clear();"
-		"w.setDrawingColor('white');"
-		"w.drawRectangle({ x: 10, y: 10, width: 10, height: 10 });"
-		"w.present();"
+		"p = new Malikania.Painter(w);"
+		"p.setDrawingColor('lightskyblue');"
+		"p.clear();"
+		"p.setDrawingColor('white');"
+		"p.drawRectangle({ x: 10, y: 10, width: 10, height: 10 });"
+		"p.present();"
 	);
 
 	if (ret != 0)
@@ -70,6 +73,8 @@
 		duk_put_global_string(ctx, "Malikania");
 
 		load_window_api(ctx);
+		load_painter_api(ctx);
+	
 		basic(ctx);
 		rect(ctx);
 	} catch (const std::exception& ex) {
--- a/examples/sprite/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/examples/sprite/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -24,11 +24,13 @@
 #include <malikania/client/loader.hpp>
 #include <malikania/client/sprite.hpp>
 #include <malikania/client/window.hpp>
+#include <malikania/client/painter.hpp>
+
 #include <malikania/locator.hpp>
 
 using namespace std::chrono_literals;
 
-void draw(mlk::client::window& window, mlk::client::loader& loader)
+void draw(mlk::client::painter& painter, mlk::client::loader& loader)
 {
 	auto sprite = loader.load_sprite("sprites/margins.json");
 	auto total = sprite.get_rows() * sprite.get_columns();
@@ -36,12 +38,12 @@
 	auto y = (400 / 2) - (sprite.get_cell().height / 2);
 
 	for (unsigned c = 0; c < total; ++c) {
-		window.clear();
-		sprite.draw(window, c, mlk::point{
+		painter.clear();
+		sprite.draw(painter, c, mlk::point{
 			static_cast<int>(x),
 			static_cast<int>(y)
 		});
-		window.present();
+		painter.present();
 
 		std::this_thread::sleep_for(1s);
 	}
@@ -53,8 +55,9 @@
 		mlk::directory_locator locator(CMAKE_CURRENT_SOURCE_DIR "/resources");
 		mlk::client::loader loader(locator);
 		mlk::client::window window(400, 400);
+		mlk::client::painter painter(window);
 
-		draw(window, loader);
+		draw(painter, loader);
 	} catch (const std::exception& ex) {
 		std::cerr << ex.what() << std::endl;
 		return 1;
--- a/libmlk-client-js/CMakeLists.txt	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client-js/CMakeLists.txt	Wed Dec 05 22:24:44 2018 +0100
@@ -20,22 +20,28 @@
 
 set(
 	SOURCES
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/animation_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/animation_js_api.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/animator_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/animator_js_api.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/color_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/color_js_api.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/font_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/font_js_api.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/image_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/image_js_api.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/loader_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/loader_js_api.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/sprite_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/sprite_js_api.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/window_js_api.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/client/js/window_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/animation_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/animation_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/animator_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/animator_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/color_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/color_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/font_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/font_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/image_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/image_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/loader_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/loader_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/painter_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/painter_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/painter_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/painter_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/sprite_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/sprite_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/texture_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/texture_js_api.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/window_js_api.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/js/window_js_api.hpp
 )
 
 malikania_define_library(
@@ -44,5 +50,5 @@
 	SOURCES ${SOURCES}
 	PUBLIC_LIBRARIES libmlk-client libmlk-js
 	PUBLIC_INCLUDES
-		$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+		$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
 )
--- a/libmlk-client-js/malikania/client/js/animator_js_api.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client-js/malikania/client/js/animator_js_api.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -25,7 +25,7 @@
 
 #include "animation_js_api.hpp"
 #include "animator_js_api.hpp"
-#include "window_js_api.hpp"
+#include "painter_js_api.hpp"
 
 namespace mlk::client::js {
 
@@ -78,7 +78,7 @@
 auto Animator_draw(duk_context* ctx) -> duk_ret_t
 {
 	try {
-		self(ctx).draw(mlk::js::duk::require<window>(ctx, 0), mlk::js::duk::get<point>(ctx, 1));
+		self(ctx).draw(mlk::js::duk::require<painter>(ctx, 0), mlk::js::duk::get<point>(ctx, 1));
 	} catch (const std::exception &ex) {
 		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
 	}
--- a/libmlk-client-js/malikania/client/js/font_js_api.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client-js/malikania/client/js/font_js_api.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -19,14 +19,18 @@
 #include <cassert>
 
 #include <malikania/size.hpp>
+#include <malikania/point.hpp>
 
 #include <malikania/client/loader.hpp>
 #include <malikania/client/font.hpp>
+#include <malikania/client/painter.hpp>
 
 #include <malikania/js/size_js_api.hpp>
+#include <malikania/js/point_js_api.hpp>
 
 #include "font_js_api.hpp"
 #include "loader_js_api.hpp"
+#include "painter_js_api.hpp"
 
 namespace mlk::client::js {
 
@@ -74,7 +78,22 @@
 auto Font_prototype_clip(duk_context* ctx) -> duk_ret_t
 {
 	try {
-                mlk::js::duk::push(ctx, self(ctx).clip(mlk::js::duk::require<std::string>(ctx, 0)));
+		mlk::js::duk::push(ctx, self(ctx).clip(mlk::js::duk::require<std::string>(ctx, 0)));
+	} catch (const std::exception &ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 1;
+}
+
+auto Font_prototype_draw(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).draw(
+			mlk::js::duk::require<painter>(ctx, 0),
+			mlk::js::duk::require<std::string>(ctx, 1),
+			mlk::js::duk::require<point>(ctx, 2)
+		);
 	} catch (const std::exception &ex) {
 		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
 	}
@@ -84,6 +103,7 @@
 
 const duk_function_list_entry methods[] = {
 	{ "clip",       Font_prototype_clip,    1 },
+	{ "draw",       Font_prototype_draw,    3 },
 	{ nullptr,      nullptr,                0 }
 };
 
--- a/libmlk-client-js/malikania/client/js/image_js_api.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client-js/malikania/client/js/image_js_api.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -20,7 +20,7 @@
 
 #include <malikania/client/image.hpp>
 #include <malikania/client/loader.hpp>
-#include <malikania/client/window.hpp>
+#include <malikania/client/painter.hpp>
 
 #include <malikania/js/point_js_api.hpp>
 #include <malikania/js/rectangle_js_api.hpp>
@@ -28,7 +28,7 @@
 
 #include "image_js_api.hpp"
 #include "loader_js_api.hpp"
-#include "window_js_api.hpp"
+#include "painter_js_api.hpp"
 
 namespace mlk::client::js {
 
@@ -66,12 +66,12 @@
 {
 	try {
 		auto& image = self(ctx);
-		auto& win= mlk::js::duk::require<window>(ctx, 0);
+		auto& p = mlk::js::duk::require<painter>(ctx, 0);
 
 		if (duk_get_top(ctx) == 2)
-			image.draw(win, mlk::js::duk::get<point>(ctx, 1));
+			image.draw(p, mlk::js::duk::get<point>(ctx, 1));
 		else if (duk_get_top(ctx) == 3)
-			image.draw(win, mlk::js::duk::get<rectangle>(ctx, 1), mlk::js::duk::get<rectangle>(ctx, 2));
+			image.draw(p, mlk::js::duk::get<rectangle>(ctx, 1), mlk::js::duk::get<rectangle>(ctx, 2));
 		else
 			duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid number of arguments: #%d", duk_get_top(ctx));
 	} catch (const std::exception& ex) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client-js/malikania/client/js/painter_js_api.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,222 @@
+/*
+ * painter_js_api.cpp -- painter (JavaScript binding)
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include <malikania/line.hpp>
+#include <malikania/rectangle.hpp>
+#include <malikania/point.hpp>
+
+#include <malikania/client/color.hpp>
+#include <malikania/client/painter.hpp>
+
+#include <malikania/js/line_js_api.hpp>
+#include <malikania/js/point_js_api.hpp>
+#include <malikania/js/rectangle_js_api.hpp>
+
+#include "color_js_api.hpp"
+#include "font_js_api.hpp"
+#include "painter_js_api.hpp"
+#include "window_js_api.hpp"
+#include "texture_js_api.hpp"
+
+namespace mlk::client::js {
+
+namespace {
+
+const std::string_view signature("\xff""\xff""Malikania.Painter.self");
+const std::string_view prototype("\xff""\xff""Malikania.Painter.prototype");
+
+auto self(duk_context* ctx) -> painter&
+{
+        mlk::js::duk::stack_guard sa(ctx);
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, signature.data());
+	auto ptr = duk_to_pointer(ctx, -1);
+	duk_pop_2(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not an Painter object");
+
+	return *static_cast<painter*>(ptr);
+}
+
+auto Painter_constructor(duk_context* ctx) -> duk_ret_t
+{
+        mlk::js::duk::stack_guard sa(ctx);
+
+	if (!duk_is_constructor_call(ctx))
+		duk_error(ctx, DUK_ERR_ERROR, "painter must be new-constructed");
+
+	try {
+		duk_push_this(ctx);
+
+		if (duk_get_top(ctx) == 2)
+			duk_push_pointer(ctx, new painter(mlk::js::duk::require<window>(ctx, 0)));
+		else
+			duk_push_pointer(ctx, new painter(
+				mlk::js::duk::require<window>(ctx, 0),
+				mlk::js::duk::require<texture>(ctx, 1)
+			));
+
+		duk_put_prop_string(ctx, -2, signature.data());
+		duk_pop(ctx);
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Painter_prototype_clear(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).clear();
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Painter_prototype_present(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).present();
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Painter_prototype_drawingColor(duk_context* ctx) -> duk_ret_t
+{
+	try {
+                mlk::js::duk::push(ctx, self(ctx).get_drawing_color());
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 1;
+}
+
+auto Painter_prototype_drawLine(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).draw_line(mlk::js::duk::get<line>(ctx, 0));
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Painter_prototype_drawPoint(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).draw_point(mlk::js::duk::get<point>(ctx, 0));
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Painter_prototype_drawRectangle(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).draw_rectangle(mlk::js::duk::get<rectangle>(ctx, 0));
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Painter_prototype_fillRectangle(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).fill_rectangle(mlk::js::duk::get<rectangle>(ctx, 0));
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Painter_prototype_setDrawingColor(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		self(ctx).set_drawing_color(mlk::js::duk::get<color>(ctx, 0));
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+} // !namespace
+
+const duk_function_list_entry methods[] = {
+	{ "clear",              Painter_prototype_clear,                 0 },
+	{ "drawingColor",       Painter_prototype_drawingColor,          0 },
+	{ "drawLine",           Painter_prototype_drawLine,              1 },
+	{ "drawPoint",          Painter_prototype_drawPoint,             1 },
+	{ "drawRectangle",      Painter_prototype_drawRectangle,         1 },
+	{ "fillRectangle",      Painter_prototype_fillRectangle,         1 },
+	{ "present",            Painter_prototype_present,               0 },
+	{ "setDrawingColor",    Painter_prototype_setDrawingColor,       1 },
+	{ nullptr,              nullptr,                                0 }
+};
+
+void load_painter_api(duk_context* ctx)
+{
+        mlk::js::duk::stack_guard sa(ctx);
+
+	duk_get_global_string(ctx, "Malikania");
+	duk_push_c_function(ctx, Painter_constructor, DUK_VARARGS);
+	duk_push_object(ctx);
+	duk_put_function_list(ctx, -1, methods);
+	duk_put_prop_string(ctx, -2, "prototype");
+	duk_put_prop_string(ctx, -2, "Painter");
+	duk_pop(ctx);
+}
+
+} // !mlk::client::js
+
+namespace mlk::js::duk {
+
+auto type_traits<mlk::client::painter>::require(duk_context* ctx, duk_idx_t index) -> mlk::client::painter&
+{
+	assert(ctx);
+
+        mlk::js::duk::stack_guard sa(ctx);
+
+	duk_get_prop_string(ctx, index, mlk::client::js::signature.data());
+	auto ptr = duk_to_pointer(ctx, -1);
+	duk_pop(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Painter object");
+
+	return *static_cast<mlk::client::painter*>(ptr);
+}
+
+} // !mlk::js::duk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client-js/malikania/client/js/painter_js_api.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,54 @@
+/*
+ * painter_js_api.hpp -- painter (JavaScript binding)
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_CLIENT_JS_PAINTER_HPP
+#define MALIKANIA_CLIENT_JS_PAINTER_HPP
+
+#include <malikania/js/duk.hpp>
+
+namespace mlk {
+
+namespace client {
+
+class painter;
+
+namespace js {
+
+/**
+ * Load Malikania.Painter API into the context.
+ *
+ * \param ctx the context
+ */
+void load_painter_api(duk_context* ctx);
+
+} // !client
+
+} // !js
+
+} // !mlk
+
+namespace mlk::js::duk {
+
+template <>
+struct type_traits<client::painter> {
+        static auto require(duk_context* ctx, duk_idx_t index) -> client::painter&;
+};
+
+} // !mlk::js::duk
+
+#endif // !MALIKANIA_CLIENT_JS_PAINTER_HPP
--- a/libmlk-client-js/malikania/client/js/sprite_js_api.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client-js/malikania/client/js/sprite_js_api.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -18,14 +18,14 @@
 
 #include <malikania/client/loader.hpp>
 #include <malikania/client/sprite.hpp>
-#include <malikania/client/window.hpp>
+#include <malikania/client/painter.hpp>
 
 #include <malikania/js/point_js_api.hpp>
 #include <malikania/js/size_js_api.hpp>
 
 #include "loader_js_api.hpp"
 #include "sprite_js_api.hpp"
-#include "window_js_api.hpp"
+#include "painter_js_api.hpp"
 
 namespace mlk::client::js {
 
@@ -122,14 +122,14 @@
 {
 	try {
 		auto& sprite = self(ctx);
-		auto& win = mlk::js::duk::require<window>(ctx, 0);
+		auto& p = mlk::js::duk::require<painter>(ctx, 0);
 		auto cell = mlk::js::duk::require<unsigned>(ctx, 1);
 		auto position = mlk::js::duk::require<point>(ctx, 2);
 
 		if (cell >= sprite.get_total())
 			duk_error(ctx, DUK_ERR_RANGE_ERROR, "cell %d is out of range", cell);
 
-		sprite.draw(win, cell, position);
+		sprite.draw(p, cell, position);
 	} catch (const std::exception &ex) {
 		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client-js/malikania/client/js/texture_js_api.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,140 @@
+/*
+ * texture_js_api.cpp -- texture (JavaScript binding)
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include <malikania/point.hpp>
+#include <malikania/rectangle.hpp>
+
+#include <malikania/client/texture.hpp>
+#include <malikania/client/window.hpp>
+
+#include <malikania/js/point_js_api.hpp>
+#include <malikania/js/rectangle_js_api.hpp>
+
+#include "painter_js_api.hpp"
+#include "texture_js_api.hpp"
+#include "window_js_api.hpp"
+
+namespace mlk::client::js {
+
+namespace {
+
+const std::string_view signature("\xff""\xff""Malikania.Texture.self");
+
+auto self(duk_context* ctx) -> texture&
+{
+        mlk::js::duk::stack_guard sa(ctx);
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, signature.data());
+	auto ptr = duk_to_pointer(ctx, -1);
+	duk_pop_2(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not an Texture object");
+
+	return *static_cast<texture*>(ptr);
+}
+
+auto Texture_constructor(duk_context* ctx) -> duk_ret_t
+{
+	if (!duk_is_constructor_call(ctx))
+		duk_error(ctx, DUK_ERR_ERROR, "Texture must be new-constructed");
+
+	try {
+		duk_push_this(ctx);
+		duk_push_pointer(ctx, new texture(
+			mlk::js::duk::require<window>(ctx, 0),
+			mlk::js::duk::require<unsigned>(ctx, 1),
+			mlk::js::duk::require<unsigned>(ctx, 2)
+		));
+
+		// Save window to avoid its destruction.
+		duk_dup(ctx, 0);
+		duk_put_prop_string(ctx, -2, DUK_HIDDEN_SYMBOL("window"));
+		duk_put_prop_string(ctx, -2, signature.data());
+		duk_pop(ctx);
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+auto Texture_prototype_draw(duk_context* ctx) -> duk_ret_t
+{
+	try {
+		auto& texture = self(ctx);
+		auto& p = mlk::js::duk::require<painter>(ctx, 0);
+
+		if (duk_get_top(ctx) == 2)
+			texture.draw(p, mlk::js::duk::get<point>(ctx, 1));
+		else if (duk_get_top(ctx) == 3)
+			texture.draw(p, mlk::js::duk::get<rectangle>(ctx, 1), mlk::js::duk::get<rectangle>(ctx, 2));
+		else
+			duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid number of arguments: #%d", duk_get_top(ctx));
+	} catch (const std::exception& ex) {
+		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+	}
+
+	return 0;
+}
+
+const duk_function_list_entry methods[] = {
+	{ "draw",       Texture_prototype_draw, DUK_VARARGS     },
+	{ nullptr,      nullptr,                0               }
+};
+
+
+} // !namespace
+
+void load_texture_api(duk_context* ctx)
+{
+	mlk::js::duk::stack_guard sa(ctx);
+
+	duk_get_global_string(ctx, "Malikania");
+	duk_push_c_function(ctx, Texture_constructor, 0);
+	duk_push_object(ctx);
+	duk_put_function_list(ctx, -1, methods);
+	duk_put_prop_string(ctx, -2, "prototype");
+	duk_put_prop_string(ctx, -2, "Texture");
+	duk_pop(ctx);
+}
+
+} // !mlk::client::js
+
+namespace mlk::js::duk {
+
+auto type_traits<mlk::client::texture>::require(duk_context* ctx, duk_idx_t index) -> mlk::client::texture&
+{
+	assert(ctx);
+
+        mlk::js::duk::stack_guard sa(ctx);
+
+	duk_get_prop_string(ctx, index, mlk::client::js::signature.data());
+	auto ptr = duk_to_pointer(ctx, -1);
+	duk_pop(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Texture object");
+
+	return *static_cast<mlk::client::texture*>(ptr);
+}
+
+} // !mlk::js::duk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client-js/malikania/client/js/texture_js_api.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,54 @@
+/*
+ * texture_js_api.hpp -- texture (JavaScript binding)
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_CLIENT_JS_TEXTURE_HPP
+#define MALIKANIA_CLIENT_JS_TEXTURE_HPP
+
+#include <malikania/js/duk.hpp>
+
+namespace mlk {
+
+namespace client {
+
+class texture;
+
+namespace js {
+
+/**
+ * Load Malikania.Texture API into the context.
+ *
+ * \param ctx the context
+ */
+void load_texture_api(duk_context* ctx);
+
+} // !client
+
+} // !js
+
+} // !mlk
+
+namespace mlk::js::duk {
+
+template <>
+struct type_traits<client::texture> {
+        static auto require(duk_context* ctx, duk_idx_t index) -> client::texture&;
+};
+
+} // !mlk::js::duk
+
+#endif // !MALIKANIA_CLIENT_JS_PAINTER_HPP
--- a/libmlk-client-js/malikania/client/js/window_js_api.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client-js/malikania/client/js/window_js_api.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -18,18 +18,8 @@
 
 #include <cassert>
 
-#include <malikania/line.hpp>
-#include <malikania/rectangle.hpp>
-
-#include <malikania/client/color.hpp>
 #include <malikania/client/window.hpp>
 
-#include <malikania/js/line_js_api.hpp>
-#include <malikania/js/point_js_api.hpp>
-#include <malikania/js/rectangle_js_api.hpp>
-
-#include "color_js_api.hpp"
-#include "font_js_api.hpp"
 #include "window_js_api.hpp"
 
 namespace mlk::client::js {
@@ -74,126 +64,6 @@
 	return 0;
 }
 
-auto Window_prototype_clear(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		self(ctx).clear();
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-auto Window_prototype_present(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		self(ctx).present();
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-auto Window_prototype_drawingColor(duk_context* ctx) -> duk_ret_t
-{
-	try {
-                mlk::js::duk::push(ctx, self(ctx).get_drawing_color());
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 1;
-}
-
-auto Window_prototype_drawLine(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		self(ctx).draw_line(mlk::js::duk::get<line>(ctx, 0));
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-auto Window_prototype_drawPoint(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		self(ctx).draw_point(mlk::js::duk::get<point>(ctx, 0));
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-auto Window_prototype_drawRectangle(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		self(ctx).draw_rectangle(mlk::js::duk::get<rectangle>(ctx, 0));
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-auto Window_prototype_drawText(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		auto& win = self(ctx);
-		auto text = mlk::js::duk::require<std::string>(ctx, 0);
-		auto& fnt = mlk::js::duk::require<font>(ctx, 1);
-		auto rect = mlk::js::duk::get<rectangle>(ctx, 2);
-
-		if (!rect.is_null())
-			win.draw_text(text, fnt, rect);
-		else
-			win.draw_text(text, fnt, point{rect.x, rect.y});
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-auto Window_prototype_fillRectangle(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		self(ctx).fill_rectangle(mlk::js::duk::get<rectangle>(ctx, 0));
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-auto Window_prototype_setDrawingColor(duk_context* ctx) -> duk_ret_t
-{
-	try {
-		self(ctx).set_drawing_color(mlk::js::duk::get<color>(ctx, 0));
-	} catch (const std::exception& ex) {
-		duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-	}
-
-	return 0;
-}
-
-const duk_function_list_entry methods[] = {
-	{ "clear",              Window_prototype_clear,                 0 },
-	{ "drawingColor",       Window_prototype_drawingColor,          0 },
-	{ "drawLine",           Window_prototype_drawLine,              1 },
-	{ "drawPoint",          Window_prototype_drawPoint,             1 },
-	{ "drawRectangle",      Window_prototype_drawRectangle,         1 },
-	{ "drawText",           Window_prototype_drawText,              3 },
-	{ "fillRectangle",      Window_prototype_fillRectangle,         1 },
-	{ "present",            Window_prototype_present,               0 },
-	{ "setDrawingColor",    Window_prototype_setDrawingColor,       1 },
-	{ nullptr,              nullptr,                                0 }
-};
-
 } // !namespace
 
 void load_window_api(duk_context* ctx)
@@ -203,7 +73,9 @@
 	duk_get_global_string(ctx, "Malikania");
 	duk_push_c_function(ctx, Window_constructor, 0);
 	duk_push_object(ctx);
+#if 0
 	duk_put_function_list(ctx, -1, methods);
+#endif
 	duk_put_prop_string(ctx, -2, "prototype");
 	duk_put_prop_string(ctx, -2, "Window");
 	duk_pop(ctx);
--- a/libmlk-client/CMakeLists.txt	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/CMakeLists.txt	Wed Dec 05 22:24:44 2018 +0100
@@ -45,6 +45,8 @@
 	${PROJECT_SOURCE_DIR}/malikania/client/loader.cpp
 	${PROJECT_SOURCE_DIR}/malikania/client/loader.hpp
 	${PROJECT_SOURCE_DIR}/malikania/client/mouse.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/painter.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/painter.hpp
 	${PROJECT_SOURCE_DIR}/malikania/client/sdl_util.cpp
 	${PROJECT_SOURCE_DIR}/malikania/client/sdl_util.hpp
 	${PROJECT_SOURCE_DIR}/malikania/client/sprite.cpp
@@ -56,6 +58,8 @@
 	${PROJECT_SOURCE_DIR}/malikania/client/state/login_state.hpp
 	${PROJECT_SOURCE_DIR}/malikania/client/state/map_state.cpp
 	${PROJECT_SOURCE_DIR}/malikania/client/state/map_state.hpp
+	${PROJECT_SOURCE_DIR}/malikania/client/texture.cpp
+	${PROJECT_SOURCE_DIR}/malikania/client/texture.hpp
 	${PROJECT_SOURCE_DIR}/malikania/client/theme.cpp
 	${PROJECT_SOURCE_DIR}/malikania/client/theme.hpp
 	${PROJECT_SOURCE_DIR}/malikania/client/widget.cpp
--- a/libmlk-client/malikania/client/animator.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/animator.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -46,10 +46,10 @@
 	}
 }
 
-void animator::draw(window& window, const point& point)
+void animator::draw(painter& painter, const point& point)
 {
 	if (current_ != animation_.frames.end())
-		animation_.sprite.draw(window, current_->cell, point);
+		animation_.sprite.draw(painter, current_->cell, point);
 }
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/animator.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/animator.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -72,10 +72,10 @@
 	/**
 	 * Draw the animation.
 	 *
-	 * \param window the window
+	 * \param painter the painter
 	 * \param position the position in the window
 	 */
-	void draw(window& window, const point& position);
+	void draw(painter& painter, const point& position);
 };
 
 } // !client
--- a/libmlk-client/malikania/client/button.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/button.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -20,7 +20,7 @@
 
 #include "button.hpp"
 #include "theme.hpp"
-#include "window.hpp"
+#include "painter.hpp"
 
 namespace mlk::client {
 
@@ -54,24 +54,26 @@
 	}
 }
 
-void button::draw(window& w)
+void button::draw(painter& p)
 {
 	const auto& font = get_theme().get_font();
 	const auto textsize = font.clip(text_);
 
-	w.set_drawing_color({128, 68, 21});
-	w.draw_rectangle({position_.x - 3, position_.y - 3, size_.width + 6, size_.height + 6});
-	w.set_drawing_color({85, 38, 0});
-	w.draw_rectangle({position_.x - 2, position_.y - 2, size_.width + 4, size_.height + 4});
-	w.set_drawing_color({128, 68, 21});
-	w.draw_rectangle({position_.x - 1, position_.y - 1, size_.width + 2, size_.height + 2});
-	w.set_drawing_color({255, 208, 170});
-	w.fill_rectangle({position_.x, position_.y, size_.width, size_.height});
-	w.set_drawing_color(color::from_name("black"));
-	w.draw_text(text_, font, point{
+	p.set_drawing_color({128, 68, 21});
+	p.draw_rectangle({position_.x - 3, position_.y - 3, size_.width + 6, size_.height + 6});
+	p.set_drawing_color({85, 38, 0});
+	p.draw_rectangle({position_.x - 2, position_.y - 2, size_.width + 4, size_.height + 4});
+	p.set_drawing_color({128, 68, 21});
+	p.draw_rectangle({position_.x - 1, position_.y - 1, size_.width + 2, size_.height + 2});
+	p.set_drawing_color({255, 208, 170});
+	p.fill_rectangle({position_.x, position_.y, size_.width, size_.height});
+	p.set_drawing_color(color::from_name("black"));
+#if 0
+	p.draw_text(text_, font, point{
 		position_.x + (static_cast<int>(size_.width) / 2) - (static_cast<int>(textsize.width) / 2),
 		position_.y + (static_cast<int>(size_.height) / 2) - (static_cast<int>(textsize.height) / 2)
 	});
+#endif
 }
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/button.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/button.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -27,6 +27,7 @@
 #include <functional>
 #include <string>
 
+#include "event.hpp"
 #include "widget.hpp"
 
 namespace mlk::client {
@@ -80,7 +81,7 @@
 	/**
 	 * \copydoc widget::draw
 	 */
-	void draw(window& w) override;
+	void draw(painter& painter) override;
 };
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/event.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/event.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -1,28 +1,28 @@
-/*
- * event.cpp -- event object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "event.hpp"
-
-namespace mlk::client {
-
-event::operator bool() const noexcept
-{
-	return index() != 0U;
-}
-
-} // !mlk::client
+/*
+ * event.cpp -- event object
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "event.hpp"
+
+namespace mlk::client {
+
+event::operator bool() const noexcept
+{
+	return index() != 0U;
+}
+
+} // !mlk::client
--- a/libmlk-client/malikania/client/event.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/event.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -1,121 +1,121 @@
-/*
- * event.hpp -- event object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_CLIENT_EVENT_HPP
-#define MALIKANIA_CLIENT_EVENT_HPP
-
-/**
- * \file event.hpp
- * \brief Event object.
- */
-
-#include <string>
-#include <variant>
-
-#include <malikania/point.hpp>
-
-#include "mouse.hpp"
-#include "key.hpp"
-
-namespace mlk::client {
-
-/**
- * \brief Describe key event.
- */
-struct key_event {
-	bool pressed{true};             //!< is pressed?
-	key keycode{key::unknown};      //!< layout-dependant key
-	key scancode{key::unknown};     //!< physical key
-	mod modifiers{mod::none};       //!< optional modifiers
-};
-
-/**
- * \brief Describe mouse click event.
- */
-struct mouse_click_event {
-	bool pressed{true};             //!< is pressed?
-	mouse button{mouse::none};      //!< which mouse button
-	point pos;                      //!< current mouse position
-};
-
-/**
- * \brief Describe a mouse motion event.
- */
-struct mouse_motion_event {
-	point pos;                      //!< current mouse position
-};
-
-/**
- * \brief Describe mouse wheel (up/down) event.
- */
-struct mouse_wheel_event {
-	point pos;                      //!< current mouse position
-};
-
-/**
- * \brief Describe text edition event.
- */
-struct text_event {
-	std::u32string text;            //!< text added from this event
-};
-
-/**
- * \brief Event occured but is unknown.
- */
-struct unknown_event {
-};
-
-/**
- * \brief User has requested quit.
- */
-struct quit_event {
-};
-
-/**
- * \brief Variant with all possible events.
- */
-using variant = std::variant<
-	std::monostate,
-	quit_event,
-	key_event,
-	mouse_click_event,
-	mouse_motion_event,
-	mouse_wheel_event,
-	text_event,
-	unknown_event
->;
-
-/**
- * \brief Event object.
- */
-class event : public variant {
-public:
-	/**
-	 * Inherited constructor.
-	 */
-	using variant::variant;
-
-	/**
-	 * Tells if the event contain an event.
-	 */
-	operator bool() const noexcept;
-};
-
-} // !mlk::client
-
-#endif // MALIKANIA_CLIENT_EVENT_HPP
+/*
+ * event.hpp -- event object
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_CLIENT_EVENT_HPP
+#define MALIKANIA_CLIENT_EVENT_HPP
+
+/**
+ * \file event.hpp
+ * \brief Event object.
+ */
+
+#include <string>
+#include <variant>
+
+#include <malikania/point.hpp>
+
+#include "mouse.hpp"
+#include "key.hpp"
+
+namespace mlk::client {
+
+/**
+ * \brief Describe key event.
+ */
+struct key_event {
+	bool pressed{true};             //!< is pressed?
+	key keycode{key::unknown};      //!< layout-dependant key
+	key scancode{key::unknown};     //!< physical key
+	mod modifiers{mod::none};       //!< optional modifiers
+};
+
+/**
+ * \brief Describe mouse click event.
+ */
+struct mouse_click_event {
+	bool pressed{true};             //!< is pressed?
+	mouse button{mouse::none};      //!< which mouse button
+	point pos;                      //!< current mouse position
+};
+
+/**
+ * \brief Describe a mouse motion event.
+ */
+struct mouse_motion_event {
+	point pos;                      //!< current mouse position
+};
+
+/**
+ * \brief Describe mouse wheel (up/down) event.
+ */
+struct mouse_wheel_event {
+	point pos;                      //!< current mouse position
+};
+
+/**
+ * \brief Describe text edition event.
+ */
+struct text_event {
+	std::u32string text;            //!< text added from this event
+};
+
+/**
+ * \brief Event occured but is unknown.
+ */
+struct unknown_event {
+};
+
+/**
+ * \brief User has requested quit.
+ */
+struct quit_event {
+};
+
+/**
+ * \brief Variant with all possible events.
+ */
+using variant = std::variant<
+	std::monostate,
+	quit_event,
+	key_event,
+	mouse_click_event,
+	mouse_motion_event,
+	mouse_wheel_event,
+	text_event,
+	unknown_event
+>;
+
+/**
+ * \brief Event object.
+ */
+class event : public variant {
+public:
+	/**
+	 * Inherited constructor.
+	 */
+	using variant::variant;
+
+	/**
+	 * Tells if the event contain an event.
+	 */
+	operator bool() const noexcept;
+};
+
+} // !mlk::client
+
+#endif // MALIKANIA_CLIENT_EVENT_HPP
--- a/libmlk-client/malikania/client/font.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/font.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -20,8 +20,11 @@
 
 #include <malikania/size.hpp>
 
+#include "color.hpp"
 #include "font.hpp"
+#include "painter.hpp"
 #include "sdl_util.hpp"
+#include "texture.hpp"
 
 namespace mlk::client {
 
@@ -69,4 +72,29 @@
 	};
 }
 
+void font::draw(painter& painter, const std::string& text, const point& point)
+{
+	render(painter, text).draw(painter, point);
+}
+
+auto font::render(painter& painter, const std::string& text) -> texture
+{
+	const auto [ red, green, blue, alpha ] = painter.get_drawing_color();
+
+	// Create temporary surface.
+	std::unique_ptr<SDL_Surface, void (*)(SDL_Surface*)> surface{
+		TTF_RenderUTF8_Blended(
+			font_.get(),
+			text.c_str(),
+			SDL_Color{red, green, blue, alpha}
+		),
+		SDL_FreeSurface
+	};
+
+	if (!surface)
+		throw std::runtime_error(SDL_GetError());
+
+	return SDL_CreateTextureFromSurface(painter.get_renderer(), surface.get());	
+}
+
 } // !mlk::client
--- a/libmlk-client/malikania/client/font.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/font.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -32,10 +32,15 @@
 
 namespace mlk {
 
+struct point;
+struct rectangle;
 struct size;
 
 namespace client {
 
+class painter;
+class texture;
+
 /**
  * \brief font object.
  */
@@ -82,6 +87,27 @@
 	 * \return the required size
 	 */
 	auto clip(const std::string& text) const -> size;
+
+	/**
+	 * Render some text.
+	 *
+	 * \note this function is costly
+	 * \param painter the painter
+	 * \param text the text
+	 * \param point the point
+	 */
+	void draw(painter& painter, const std::string& text, const point& point);
+
+	/**
+	 * Render text as texture.
+	 *
+	 * This function takes the current painter color.
+	 *
+	 * \param painter the painter
+	 * \param text the text
+	 * \return the ready to copy texture
+	 */
+	auto render(painter& painter, const std::string& text) -> texture;
 };
 
 } // !client
--- a/libmlk-client/malikania/client/image.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/image.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -20,19 +20,13 @@
 
 #include "image.hpp"
 #include "sdl_util.hpp"
-#include "window.hpp"
+#include "painter.hpp"
 
 namespace mlk::client {
 
-void image::create_texture(window& w)
+void image::create_texture(painter& p)
 {
-	texture_ = {
-		SDL_CreateTextureFromSurface(w.get_renderer(), surface_.get()),
-		SDL_DestroyTexture
-	};
-
-	if (texture_ == nullptr)
-		throw std::runtime_error(SDL_GetError());
+	texture_ = texture(p.get_renderer(), surface_.get());
 }
 
 image::image(std::string data)
@@ -58,55 +52,24 @@
 	return size_;
 }
 
-void image::draw(window& window, const point& point)
+void image::draw(painter& painter, const point& point)
 {
 	/*
 	 * Create texture at this step so the image constructor does not need the
 	 * window.
 	 */
 	if (!texture_)
-		create_texture(window);
-
-	SDL_Rect target;
+		create_texture(painter);
 
-	target.x = static_cast<int>(point.x);
-	target.y = static_cast<int>(point.y);
-	target.w = static_cast<int>(size_.width);
-	target.h = static_cast<int>(size_.height);
-
-	if (SDL_RenderCopy(window.get_renderer(), texture_.get(), nullptr, &target) < 0)
-		throw std::runtime_error(SDL_GetError());
+	texture_->draw(painter, point);
 }
 
-void image::draw(window& window, const rectangle& source, const rectangle& target)
+void image::draw(painter& painter, const rectangle& source, const rectangle& target)
 {
 	if (!texture_)
-		create_texture(window);
-
-	SDL_Rect sr, st;
-
-	sr.x = source.x;
-	sr.y = source.y;
-	sr.w = static_cast<int>(source.width);
-	sr.h = static_cast<int>(source.height);
+		create_texture(painter);
 
-	st.x = target.x;
-	st.y = target.y;
-	st.w = static_cast<int>(target.width);
-	st.h = static_cast<int>(target.height);
-
-	// Readjust .w, .h if null.
-	if (source.is_null()) {
-		sr.w = static_cast<int>(size_.width);
-		sr.h = static_cast<int>(size_.height);
-	}
-	if (target.is_null()) {
-		st.w = static_cast<int>(size_.width);
-		st.h = static_cast<int>(size_.height);
-	}
-
-	if (SDL_RenderCopy(window.get_renderer(), texture_.get(), &sr, &st) < 0)
-		throw std::runtime_error(SDL_GetError());
+	texture_->draw(painter, source, target);
 }
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/image.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/image.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -25,29 +25,29 @@
  */
 
 #include <memory>
+#include <optional>
 #include <string>
 
-#include <SDL.h>
-
 #include <malikania/point.hpp>
 #include <malikania/rectangle.hpp>
 #include <malikania/size.hpp>
 
+#include "texture.hpp"
+
 namespace mlk::client {
 
-class window;
+class painter;
 
 /**
  * \brief Image object.
  */
 class image {
 private:
-	std::unique_ptr<SDL_Surface, void (*)(SDL_Surface *)> surface_{nullptr, nullptr};
-	std::unique_ptr<SDL_Texture, void (*)(SDL_Texture *)> texture_{nullptr, nullptr};
-
+	std::unique_ptr<SDL_Surface, void (*)(SDL_Surface*)> surface_{nullptr, nullptr};
+	std::optional<texture> texture_;
 	size size_;
 
-	void create_texture(window& window);
+	void create_texture(painter& painter);
 
 public:
 	/**
@@ -68,19 +68,19 @@
 	/**
 	 * Draw the image to the window.
 	 *
-	 * \param window the window
+	 * \param painter the painter
 	 * \param position the position
 	 */
-	void draw(window& window, const point& position = {0, 0});
+	void draw(painter& painter, const point& position = {0, 0});
 
 	/**
 	 * Overloaded function.
 	 *
-	 * \param window the window
+	 * \param painter the painter
 	 * \param source the source to clip
 	 * \param target the target destination
 	 */
-	void draw(window& window, const rectangle& source, const rectangle& target);
+	void draw(painter& painter, const rectangle& source, const rectangle& target);
 };
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/label.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/label.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -39,10 +39,9 @@
 	text_ = std::move(text);
 }
 
-void label::draw(window& w)
+void label::draw(painter&)
 {
 	// TODO: implement.
-	(void)w;
 }
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/label.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/label.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -62,7 +62,7 @@
 	/**
 	 * \copydoc widget::draw
 	 */
-	void draw(window& w) override;
+	void draw(painter& painter) override;
 };
 
 } // !mlk::client
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client/malikania/client/painter.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,127 @@
+/*
+ * painter.cpp -- main window and basic drawing
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdexcept>
+
+#include <malikania/line.hpp>
+#include <malikania/rectangle.hpp>
+
+#include "color.hpp"
+#include "painter.hpp"
+#include "texture.hpp"
+#include "window.hpp"
+
+namespace mlk::client {
+
+painter::painter(window& win) noexcept
+	: renderer_(win.get_renderer())
+{
+	SDL_SetRenderTarget(renderer_, nullptr);
+}
+
+painter::painter(window& win, texture& texture) noexcept
+	: renderer_(win.get_renderer())
+{
+	SDL_SetRenderTarget(renderer_, texture.get_texture());
+}
+
+void painter::use(window& win) noexcept
+{
+	SDL_SetRenderTarget(win.get_renderer(), nullptr);
+}
+
+void painter::use(texture& texture) noexcept
+{
+	SDL_SetRenderTarget(renderer_, texture.get_texture());
+}
+
+auto painter::get_renderer() const noexcept -> const SDL_Renderer*
+{
+	return renderer_;
+}
+
+auto painter::get_renderer() noexcept -> SDL_Renderer*
+{
+	return renderer_;
+}
+
+void painter::clear()
+{
+	SDL_RenderClear(renderer_);
+}
+
+void painter::present()
+{
+	SDL_RenderPresent(renderer_);
+}
+
+auto painter::get_drawing_color() const -> color
+{
+	SDL_Color color;
+
+	if (SDL_GetRenderDrawColor(renderer_, &color.r, &color.g, &color.b, &color.a) < 0)
+		throw std::runtime_error(SDL_GetError());
+
+	return { color.r, color.g, color.b, color.a };
+}
+
+void painter::set_drawing_color(const color& color)
+{
+	if (SDL_SetRenderDrawColor(renderer_, color.red, color.green, color.blue, color.alpha) < 0)
+		throw std::runtime_error(SDL_GetError());
+}
+
+void painter::draw_line(const line& line)
+{
+	if (SDL_RenderDrawLine(renderer_, line.x1, line.y1, line.x2, line.y2) != 0)
+		throw std::runtime_error(SDL_GetError());
+}
+
+void painter::draw_point(const point& point)
+{
+	if (SDL_RenderDrawPoint(renderer_, point.x, point.y) != 0)
+		throw std::runtime_error(SDL_GetError());
+}
+
+void painter::draw_rectangle(const rectangle& rectangle)
+{
+	SDL_Rect rect{
+		rectangle.x,
+		rectangle.y,
+		static_cast<int>(rectangle.width),
+		static_cast<int>(rectangle.height)
+	};
+
+	if (SDL_RenderDrawRect(renderer_, &rect) < 0)
+		throw std::runtime_error(SDL_GetError());
+}
+
+void painter::fill_rectangle(const rectangle& rectangle)
+{
+	SDL_Rect rect{
+		rectangle.x,
+		rectangle.y,
+		static_cast<int>(rectangle.width),
+		static_cast<int>(rectangle.height)
+	};
+
+	if (SDL_RenderFillRect(renderer_, &rect) < 0)
+		throw std::runtime_error(SDL_GetError());
+}
+
+} // !mlk::client
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client/malikania/client/painter.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,152 @@
+/*
+ * painter.hpp -- basic drawing
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_CLIENT_PAINTER_HPP
+#define MALIKANIA_CLIENT_PAINTER_HPP
+
+/**
+ * \file painter.hpp
+ * \brief Basic drawing.
+ */
+
+#include <SDL.h>
+
+namespace mlk {
+
+struct line;
+struct point;
+struct rectangle;
+
+namespace client {
+
+struct color;
+
+class texture;
+class window;
+
+/**
+ * \brief Basic drawing.
+ */
+class painter {
+private:
+	SDL_Renderer* renderer_{nullptr};
+
+public:
+	/**
+	 * Start painting on the window.
+	 *
+	 * \param win the window
+	 */
+	painter(window& win) noexcept;
+
+	/**
+	 * Start painting on the texture.
+	 *
+	 * \param win the window
+	 * \param texture the texture
+	 */
+	painter(window& win, texture& texture) noexcept;
+
+	/**
+	 * Start painting on the window.
+	 *
+	 * \param win the window
+	 */
+	void use(window& win) noexcept;
+
+	/**
+	 * Start painting on the texture.
+	 *
+	 * \param texture the texture
+	 */
+	void use(texture& texture) noexcept;
+
+	/**
+	 * Get the current renderer.
+	 *
+	 * \return the renderer
+	 */
+	auto get_renderer() const noexcept -> const SDL_Renderer*;
+
+	/**
+	 * Overloaded function.
+	 *
+	 * \return the renderer
+	 */
+	auto get_renderer() noexcept -> SDL_Renderer*;
+
+	/**
+	 * Clear the window content with the current drawing color.
+	 */
+	void clear();
+
+	/**
+	 * Render the content of the window to the screen.
+	 */
+	void present();
+
+	/**
+	 * Get the current drawing color.
+	 *
+	 * \return the color
+	 */
+	auto get_drawing_color() const -> color;
+
+	/**
+	 * Set the drawing color.
+	 *
+	 * \param color the color
+	 */
+	void set_drawing_color(const color& color);
+
+	/**
+	 * Draw a line.
+	 *
+	 * \param line the line
+	 */
+	void draw_line(const line& line);
+
+	/**
+	 * Draw a point.
+	 *
+	 * \param point the point
+	 */
+	void draw_point(const point& point);
+
+	/**
+	 * Draw a rectangle (only borders).
+	 *
+	 * \param rect the rectangle
+	 * \see fillRectangle
+	 */
+	void draw_rectangle(const rectangle& rect);
+
+	/**
+	 * Fill the given rectangle with the current color.
+	 *
+	 * \param rect the rectangle
+	 * \see drawRectangle
+	 */
+	void fill_rectangle(const rectangle& rect);
+};
+
+} // !client
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_PAINTER_HPP
--- a/libmlk-client/malikania/client/sprite.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/sprite.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -85,14 +85,14 @@
         return columns_ * rows_;
 }
 
-void sprite::draw(window& window, unsigned cell, const point& point)
+void sprite::draw(painter& painter, unsigned cell, const point& point)
 {
 	assert(cell < get_total());
 
-	draw(window, cell, {point.x, point.y, cell_.width, cell_.height});
+	draw(painter, cell, {point.x, point.y, cell_.width, cell_.height});
 }
 
-void sprite::draw(window& window, unsigned cell, const rectangle& destination)
+void sprite::draw(painter& painter, unsigned cell, const rectangle& destination)
 {
 	assert(cell < get_total());
 
@@ -106,7 +106,7 @@
 
 	rectangle source{x, y, cell_.width, cell_.height};
 
-	image_.draw(window, source, destination);
+	image_.draw(painter, source, destination);
 }
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/sprite.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/sprite.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -122,21 +122,21 @@
 	 * Draw the sprite into the window at the specified position.
 	 *
 	 * \pre cell < get_total()
-	 * \param window the window
+	 * \param painter the painter
 	 * \param cell the cell index
 	 * \param position the position in the window
 	 */
-	void draw(window& window, unsigned cell, const point& position);
+	void draw(painter& painter, unsigned cell, const point& position);
 
 	/**
 	 * Draw the sprite index to fill destination.
 	 *
 	 * \pre cell < get_total()
-	 * \param window the window
+	 * \param painter the painter
 	 * \param cell the cell index
 	 * \param rectangle the destination
 	 */
-	void draw(window& win, unsigned cell, const rectangle& destination);
+	void draw(painter& painter, unsigned cell, const rectangle& destination);
 };
 
 } // !client
--- a/libmlk-client/malikania/client/state/lobby_state.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/state/lobby_state.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -54,6 +54,7 @@
 
 void lobby_state::draw(client& clt)
 {
+#if 0
 	// TODO: lobby_window.
 	auto& win = clt.window();
 
@@ -73,6 +74,7 @@
 	}
 
 	win.present();
+#endif
 }
 
 } // !client
--- a/libmlk-client/malikania/client/state/login_state.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/state/login_state.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -90,50 +90,12 @@
 
 void login_state::handle_key_down(const key_event& ev)
 {
-	switch (ev.keycode) {
-	case key::tab:
-		index_ ++;
-
-		if (index_ == 2)
-			index_ = 0;
-		break;
-	case key::backspace:
-		if (index_ == 0)
-			login_.pop_back();
-		else
-			password_.pop_back();
-		break;
-	case key::enter:
-		if (index_ == 1 && state_ == state_t::disconnected)
-			do_connect();
-		break;
-	default:
-		break;
-	}
 }
 
 #endif
 
 void login_state::draw(client& clt)
 {
-	auto& win = clt.window();
-	const auto& font = theme::get_default().get_font();
-
-	win.set_drawing_color(color::from_hex(0xffffffff));
-	win.clear();
-	win.set_drawing_color(color::from_hex(0xff000000));
-	win.draw_text("Please log in", font, {10, 10});
-	win.draw_text("login: ", font, {10, 50});
-	win.draw_text("password: ", font, {10, 70});
-
-	if (!login_.empty())
-		win.draw_text(unicode::to_utf8(login_), font, {70, 50});
-	if (!password_.empty())
-		win.draw_text(std::string(password_.length(), '*'), font, {70, 70});
-	if (!status_.empty())
-		win.draw_text(status_, font, {70, 90});
-
-	win.present();
 }
 
 } // !client
--- a/libmlk-client/malikania/client/state/map_state.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/state/map_state.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -16,53 +16,20 @@
 
 void map_state::handle_key_down(const key_event& ev)
 {
-	if (ev.keycode == key::down)
-		delta_ = {delta_.x, 1};
-	else if (ev.keycode == key::up)
-		delta_ = {delta_.x, -1};
-	else if (ev.keycode == key::left)
-		delta_ = {-1, delta_.y};
-	else if (ev.keycode == key::right)
-		delta_ = {1, delta_.y};
 }
 
 void map_state::handle_key_up(const key_event& ev)
 {
-	if (ev.keycode == key::down || ev.keycode == key::up)
-		delta_ = {delta_.x, 0};
-	else if (ev.keycode == key::left || ev.keycode == key::right)
-		delta_ = {0, delta_.y};
 }
 
 #endif
 
 void map_state::update(client&)
 {
-	position_ = {
-		position_.x + delta_.x,
-		position_.y + delta_.y
-	};
 }
 
 void map_state::draw(client& clt)
 {
-	auto& win = clt.window();
-
-	win.set_drawing_color(color::from_hex(0xffffffff));
-	win.clear();
-	win.set_drawing_color(color::from_hex(0xff000000));
-
-	// Draw a little square as user position.
-	win.fill_rectangle({ position_.x, position_.y, 10, 10 });
-
-	// Draw nickname on top of it.
-	auto clip = theme::get_default().get_font().clip("molko");
-
-	win.draw_text("molko", theme::get_default().get_font(), point{
-		static_cast<int>(position_.x - (clip.width / 2)),
-		static_cast<int>(position_.y - clip.height - 10)
-	});
-	win.present();
 }
 
 } // !client
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client/malikania/client/texture.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,130 @@
+/*
+ * texture.cpp -- main window and basic drawing
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include <malikania/rectangle.hpp>
+
+#include "texture.hpp"
+#include "window.hpp"
+#include "painter.hpp"
+
+namespace mlk::client {
+
+texture::texture(window& win)
+{
+	SDL_SetRenderTarget(win.get_renderer(), nullptr);
+}
+
+texture::texture(window& win, unsigned width, unsigned height)
+{
+	auto texture = SDL_CreateTexture(
+		win.get_renderer(),
+		SDL_PIXELFORMAT_RGBA8888,
+		SDL_TEXTUREACCESS_TARGET,
+		width,
+		height
+	);
+
+	if (!texture)
+		throw std::runtime_error(SDL_GetError());
+
+	texture_ = {texture, SDL_DestroyTexture};
+	size_ = {width, height};
+}
+
+texture::texture(SDL_Texture* texture)
+	: texture_(texture, SDL_DestroyTexture)
+{
+	int w = 0, h = 0;
+
+	SDL_QueryTexture(texture, nullptr, nullptr, &w, &h);
+
+	size_ = {
+		static_cast<unsigned>(w),
+		static_cast<unsigned>(h)
+	};
+}
+
+texture::texture(SDL_Renderer* renderer, SDL_Surface* surface)
+	: texture_(SDL_CreateTextureFromSurface(renderer, surface), SDL_DestroyTexture)
+{
+	size_ = {
+		static_cast<unsigned>(surface->w),
+		static_cast<unsigned>(surface->h)
+	};
+}
+
+auto texture::get_texture() const noexcept -> const SDL_Texture*
+{
+	return texture_.get();
+}
+
+auto texture::get_texture() noexcept -> SDL_Texture*
+{
+	return texture_.get();
+}
+
+void texture::draw(painter& painter, const point& position)
+{
+	const rectangle source{
+		0,
+		0,
+		static_cast<unsigned>(size_.width),
+		static_cast<unsigned>(size_.height)
+	};
+	const rectangle destination{
+		position.x,
+		position.y,
+		static_cast<unsigned>(size_.width),
+		static_cast<unsigned>(size_.height)
+	};
+
+	draw(painter, source, destination);
+}
+
+void texture::draw(painter& painter, const rectangle& source, const rectangle& destination)
+{
+	SDL_Rect s{
+		source.x,
+		source.y,
+		static_cast<int>(source.width),
+		static_cast<int>(source.height)
+	};
+	SDL_Rect d{
+		destination.x,
+		destination.y,
+		static_cast<int>(destination.width),
+		static_cast<int>(destination.height)
+	};
+
+	// Readjust .w, .h if null.
+	if (source.is_null()) {
+		s.w = static_cast<int>(size_.width);
+		s.h = static_cast<int>(size_.height);
+	}
+	if (destination.is_null()) {
+		d.w = static_cast<int>(size_.width);
+		d.h = static_cast<int>(size_.height);
+	}
+
+	if (SDL_RenderCopy(painter.get_renderer(), texture_.get(), &s, &d) < 0)
+		throw std::runtime_error(SDL_GetError());;
+}
+
+} // !mlk::client
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-client/malikania/client/texture.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -0,0 +1,109 @@
+/*
+ * texture.hpp -- main window and basic drawing
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_CLIENT_TEXTURE_HPP
+#define MALIKANIA_CLIENT_TEXTURE_HPP
+
+#include <memory>
+
+#include <SDL.h>
+
+#include <malikania/size.hpp>
+
+namespace mlk {
+
+struct line;
+struct point;
+struct rectangle;
+
+namespace client {
+
+class window;
+class painter;
+
+class texture {
+private:
+	using handle = std::unique_ptr<SDL_Texture, void (*)(SDL_Texture*)>;
+
+	handle texture_{nullptr, nullptr};
+	size size_;
+
+public:
+	/**
+	 * Create default texture.
+	 *
+	 * \param win the window
+	 */
+	texture(window& win);
+
+	/**
+	 * Create default texture.
+	 *
+	 * \param win the window
+	 * \param width the width
+	 * \param height the height
+	 */
+	texture(window& win, unsigned width, unsigned height);
+
+	/**
+	 * Create a texture object from a native SDL type.
+	 *
+	 * \param texture the handle
+	 */
+	texture(SDL_Texture* texture);
+
+	auto get_texture() const noexcept -> const SDL_Texture*;
+
+	auto get_texture() noexcept -> SDL_Texture*;
+
+	/**
+	 * Create a texture object from a SDL_Surface.
+	 *
+	 * This function does not take ownership of the surface but can be
+	 * safely deleted.
+	 *
+	 * \pre renderer != nullptr
+	 * \pre surface != nullptr
+	 * \param renderer the renderer
+	 * \param surface the surface
+	 */
+	texture(SDL_Renderer* renderer, SDL_Surface* surface);
+
+	/**
+	 * Copy the texture to the painter.
+	 *
+	 * \param painter the painter
+	 * \param point the point
+	 */
+	void draw(painter& painter, const point& point);
+
+	/**
+	 * Overloaded function.
+	 *
+	 * \param painter the painter
+	 * \param source the source
+	 * \param destination the destination
+	 */
+	void draw(painter& painter, const rectangle& source, const rectangle& destination);
+};
+
+} // !client
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_TEXUTRE_HPP
--- a/libmlk-client/malikania/client/theme.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/theme.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -81,8 +81,9 @@
 	return text_color_;
 }
 
-void theme::draw_button(window& w, const button& b, const rectangle& rect)
+void theme::draw_button(painter& painter, const button& button, const rectangle& destination)
 {
+#if 0
 	// Border.
 	w.set_drawing_color(border_color_);
 	w.draw_rectangle({ rect.x, rect.y, rect.width, rect.height });
@@ -95,12 +96,15 @@
 	// TODO: alignment.
 	w.set_drawing_color(text_color_);
 	w.draw_text(b.get_text(), font_, point{rect.x + 5, rect.y + 5});
+#endif
 }
 
-void theme::draw_label(window& w, const label& label, const rectangle& rect)
+void theme::draw_label(painter& painter, const label& label, const rectangle& destination)
 {
+#if 0
 	w.set_drawing_color(text_color_);
 	w.draw_text(label.get_text(), font_, point{rect.x, rect.y});
+#endif
 }
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/theme.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/theme.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -39,7 +39,7 @@
 
 class button;
 class label;
-class window;
+class painter;
 
 /**
  * \brief Reimplement this class to provide your own theme.
@@ -116,19 +116,20 @@
 	/**
 	 * Draw a button.
 	 *
-	 * \param w the main window
-	 * \param b the button
+	 * \param painter the painter
+	 * \param button the button
+	 * \param destination the destination
 	 */
-	virtual void draw_button(window& w, const button& b, const rectangle& rect);
+	virtual void draw_button(painter& painter, const button& button, const rectangle& destination);
 
 	/**
 	 * Draw a label.
 	 *
-	 * \param w the main window
-	 * \param l the label
-	 * \param rect the bounding rectangle.
+	 * \param painter the painter
+	 * \param label the label
+	 * \param destination the destination
 	 */
-	virtual void draw_label(window& w, const label& l, const rectangle& rect);
+	virtual void draw_label(painter& painter, const label& label, const rectangle& destination);
 };
 
 } // !client
--- a/libmlk-client/malikania/client/widget.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/widget.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -31,9 +31,8 @@
 
 namespace client {
 
-class event;
+class painter;
 class theme;
-class window;
 
 /**
  * \brief Abstract widget
@@ -110,18 +109,11 @@
 	void set_theme(theme& theme) noexcept;
 
 	/**
-	 * Handle the input event.
+	 * Draw the widget.
 	 *
-	 * \param ev the event
+	 * \param painter the painter
 	 */
-	virtual void handle_event(const event& ev) = 0;
-
-	/**
-	 * Draw the widget at the specified position.
-	 *
-	 * \param w the window
-	 */
-	virtual void draw(window& w) = 0;
+	virtual void draw(painter& painter) = 0;
 };
 
 } // !client
--- a/libmlk-client/malikania/client/window.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/window.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * window.cpp -- main window and basic drawing
+ * window.cpp -- main window
  *
  * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
  *
@@ -17,19 +17,16 @@
  */
 
 #include <cassert>
-#include <iostream>
 #include <stdexcept>
 #include <unordered_map>
 
-#include <malikania/line.hpp>
-#include <malikania/rectangle.hpp>
 #include <malikania/unicode.hpp>
 
-#include "color.hpp"
-#include "theme.hpp"
-#include "widget.hpp"
 #include "window.hpp"
 
+#include <SDL_ttf.h>
+#include <SDL_image.h>
+
 namespace mlk::client {
 
 namespace {
@@ -334,12 +331,7 @@
 	const auto kc = keycodes_map.find(ev.key.keysym.sym);
 	const auto sc = scancodes_map.find(ev.key.keysym.scancode);
 
-	key_event kev{
-		ev.type == SDL_KEYDOWN,
-		kc == keycodes_map.end() ? key::unknown : kc->second,
-		sc == scancodes_map.end() ? key::unknown : sc->second,
-		mod::none
-	};
+	key_event kev{ev.type == SDL_KEYDOWN, kc->second, sc->second, mod::none};
 
 	// Modifier is a mask.
 	for (const auto& pair : modifiers_map)
@@ -364,22 +356,6 @@
 	return mev;
 }
 
-auto create_mouse_wheel_event(const SDL_Event& ev) -> event
-{
-	assert(ev.type == SDL_MOUSEWHEEL);
-
-	return mouse_wheel_event{
-		{ ev.wheel.x, ev.wheel.y }
-	};
-}
-
-auto create_text_event(const SDL_Event& ev) -> event
-{
-	assert(ev.type == SDL_TEXTINPUT);
-
-	return text_event{unicode::to_utf32(ev.text.text)};
-}
-
 } // !namespace
 
 void window::init()
@@ -391,6 +367,8 @@
 			throw std::runtime_error(SDL_GetError());
 		if (TTF_Init() < 0)
 			throw std::runtime_error(TTF_GetError());
+		if (IMG_Init(IMG_INIT_PNG) < 0)
+			throw std::runtime_error(TTF_GetError());
 
 		is_initialized = true;
 	}
@@ -450,93 +428,6 @@
 	}
 }
 
-void window::clear()
-{
-	SDL_RenderClear(renderer_.get());
-}
-
-void window::present()
-{
-	SDL_RenderPresent(renderer_.get());
-}
-
-auto window::get_drawing_color() const -> color
-{
-	SDL_Color color;
-
-	if (SDL_GetRenderDrawColor(renderer_.get(), &color.r, &color.g, &color.b, &color.a) < 0)
-		throw std::runtime_error(SDL_GetError());
-
-	return { color.r, color.g, color.b, color.a };
-}
-
-void window::set_drawing_color(const color& color)
-{
-	if (SDL_SetRenderDrawColor(renderer_.get(), color.red, color.green, color.blue, color.alpha) < 0)
-		throw std::runtime_error(SDL_GetError());
-}
-
-void window::draw_line(const line& line)
-{
-	if (SDL_RenderDrawLine(renderer_.get(), line.x1, line.y1, line.x2, line.y2) != 0)
-		throw std::runtime_error(SDL_GetError());
-}
-
-void window::draw_point(const point& point)
-{
-	if (SDL_RenderDrawPoint(renderer_.get(), point.x, point.y) != 0)
-		throw std::runtime_error(SDL_GetError());
-}
-
-void window::draw_rectangle(const rectangle& rectangle)
-{
-	SDL_Rect rect{
-		rectangle.x,
-		rectangle.y,
-		static_cast<int>(rectangle.width),
-		static_cast<int>(rectangle.height)
-	};
-
-	if (SDL_RenderDrawRect(renderer_.get(), &rect) < 0)
-		throw std::runtime_error(SDL_GetError());
-}
-
-void window::fill_rectangle(const rectangle& rectangle)
-{
-	SDL_Rect rect{
-		rectangle.x,
-		rectangle.y,
-		static_cast<int>(rectangle.width),
-		static_cast<int>(rectangle.height)
-	};
-
-	if (SDL_RenderFillRect(renderer_.get(), &rect) < 0)
-		throw std::runtime_error(SDL_GetError());
-}
-
-void window::draw_text(const std::string& text, const font& font, const rectangle& rectangle)
-{
-	SDL_Color color = { 0, 0, 0, 255 };
-	SDL_Surface* message = TTF_RenderUTF8_Blended(font.get_handle(), text.c_str(), color);
-	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer_.get(), message);
-	SDL_Rect rect = { rectangle.x, rectangle.y, (int)rectangle.width, (int)rectangle.height };
-	SDL_RenderCopy(renderer_.get(), texture, nullptr, &rect);
-	SDL_FreeSurface(message);
-	SDL_DestroyTexture(texture);
-}
-
-void window::draw_text(const std::string& text, const font& font, const point& point)
-{
-	SDL_Color color = { 0, 0, 0, 0 };
-	SDL_GetRenderDrawColor(renderer_.get(), &color.r, &color.g, &color.b, &color.a);
-	SDL_Surface* message = TTF_RenderUTF8_Blended(font.get_handle(), text.c_str(), color);
-	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer_.get(), message);
-	SDL_Rect rect = { point.x, point.y, message->w, message->h };
-	SDL_RenderCopy(renderer_.get(), texture, nullptr, &rect);
-	SDL_FreeSurface(message);
-	SDL_DestroyTexture(texture);
-}
-
 auto window::poll() -> event
 {
 	SDL_Event event;
@@ -551,10 +442,6 @@
 	case SDL_MOUSEBUTTONDOWN:
 	case SDL_MOUSEBUTTONUP:
 		return create_mouse_event(event);
-	case SDL_MOUSEWHEEL:
-		return create_mouse_wheel_event(event);
-	case SDL_TEXTINPUT:
-		return create_text_event(event);
 	case SDL_QUIT:
 		return quit_event{};
 	default:
--- a/libmlk-client/malikania/client/window.hpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/libmlk-client/malikania/client/window.hpp	Wed Dec 05 22:24:44 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * window.hpp -- main window and basic drawing
+ * window.hpp -- main window
  *
  * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
  *
@@ -33,16 +33,8 @@
 
 namespace mlk {
 
-struct line;
-struct point;
-struct rectangle;
-
 namespace client {
 
-struct color;
-
-class font;
-
 /**
  * \brief Main window class and drawing.
  */
@@ -102,82 +94,6 @@
 	void stop_edit();
 
 	/**
-	 * Clear the window content with the current drawing color.
-	 */
-	void clear();
-
-	/**
-	 * Render the content of the window to the screen.
-	 */
-	void present();
-
-	/**
-	 * Get the current drawing color.
-	 *
-	 * \return the color
-	 */
-	auto get_drawing_color() const -> color;
-
-	/**
-	 * Set the drawing color.
-	 *
-	 * \param color the color
-	 */
-	void set_drawing_color(const color& color);
-
-	/**
-	 * Draw a line.
-	 *
-	 * \param line the line
-	 */
-	void draw_line(const line& line);
-
-	/**
-	 * Draw a point.
-	 *
-	 * \param point the point
-	 */
-	void draw_point(const point& point);
-
-	/**
-	 * Draw a rectangle (only borders).
-	 *
-	 * \param rect the rectangle
-	 * \see fillRectangle
-	 */
-	void draw_rectangle(const rectangle& rect);
-
-	/**
-	 * Fill the given rectangle with the current color.
-	 *
-	 * \param rect the rectangle
-	 * \see drawRectangle
-	 */
-	void fill_rectangle(const rectangle& rect);
-
-	/**
-	 * Draw some text.
-	 *
-	 * This function may stretch the text texture.
-	 *
-	 * \param text the text (UTF-8)
-	 * \param font the font
-	 * \param rectangle the rectangle target
-	 */
-	void draw_text(const std::string& text, const font& font, const rectangle& rectangle);
-
-	/**
-	 * Overloaded function.
-	 *
-	 * Draw the text at the given position.
-	 *
-	 * \param text the text (UTF-8)
-	 * \param font the font
-	 * \param point the text position
-	 */
-	void draw_text(const std::string& text, const font& font, const point& point);
-
-	/**
 	 * Poll the next event and return it.
 	 *
 	 * \return the event or empty one if not available
--- a/mlk-client/CMakeLists.txt	Wed Nov 28 21:18:00 2018 +0100
+++ b/mlk-client/CMakeLists.txt	Wed Dec 05 22:24:44 2018 +0100
@@ -32,5 +32,5 @@
 	LIBRARIES
 		${SDL2MAIN_LIBRARIES}
 		Threads::Threads
-		libmlk-client-js
+		libmlk-client
 )
--- a/mlk-client/main.cpp	Wed Nov 28 21:18:00 2018 +0100
+++ b/mlk-client/main.cpp	Wed Dec 05 22:24:44 2018 +0100
@@ -25,6 +25,7 @@
 #include <malikania/client/window.hpp>
 #include <malikania/client/sprite.hpp>
 #include <malikania/client/theme.hpp>
+#include <malikania/client/painter.hpp>
 
 #include <assets/ui.hpp>
 
@@ -35,6 +36,7 @@
 	mlk::client::window win(1920/2, 1080/2, "Hello");
 	mlk::client::image image(std::string(ui, sizeof (ui)));
 	mlk::client::sprite sprite(std::move(image), {16, 16});
+	mlk::client::painter painter(win);
 
 	for (;;) {
 		while (auto ev = win.poll()) {
@@ -42,37 +44,36 @@
 				return 0;
 		}
 
-		win.clear();
-		win.set_drawing_color(mlk::client::color::from_hex(0xffffffff));
+		painter.clear();
+		painter.set_drawing_color(mlk::client::color::from_hex(0xffffffff));
 
 		const auto width = 200;
 		const auto height = 150;
 
 		// top
-		sprite.draw(win, 0, mlk::point{10, 10});
-		sprite.draw(win, 1, mlk::rectangle{10 + 16, 10, width, 16});
-		sprite.draw(win, 2, mlk::point{10 + 16 + width, 10});
+		sprite.draw(painter, 0, mlk::point{10, 10});
+		sprite.draw(painter, 1, mlk::rectangle{10 + 16, 10, width, 16});
+		sprite.draw(painter, 2, mlk::point{10 + 16 + width, 10});
 
 		// middle
-		sprite.draw(win, 32, mlk::rectangle{10, 10 + 16, 16, height});
-		sprite.draw(win, 33, mlk::rectangle{10 + 16, 10 + 16, width, height});
-		sprite.draw(win, 34, mlk::rectangle{10 + 16 + width, 10 + 16, 16, height});
+		sprite.draw(painter, 32, mlk::rectangle{10, 10 + 16, 16, height});
+		sprite.draw(painter, 33, mlk::rectangle{10 + 16, 10 + 16, width, height});
+		sprite.draw(painter, 34, mlk::rectangle{10 + 16 + width, 10 + 16, 16, height});
 
 		// bottom
-		sprite.draw(win, 64, mlk::point{10, 10 + 16 + height});
-		sprite.draw(win, 65, mlk::rectangle{10 + 16, 10 + 16 + height, width, 16});
-		sprite.draw(win, 66, mlk::point{10 + 16 + width, 10 + 16 + height});
+		sprite.draw(painter, 64, mlk::point{10, 10 + 16 + height});
+		sprite.draw(painter, 65, mlk::rectangle{10 + 16, 10 + 16 + height, width, 16});
+		sprite.draw(painter, 66, mlk::point{10 + 16 + width, 10 + 16 + height});
 
 		// input
-		sprite.draw(win, 3, mlk::point{10 + 8, 10 + 8});
-		sprite.draw(win, 4, mlk::rectangle{10 + 8 + 16, 10 + 8, width - 16, 16});
-		sprite.draw(win, 5, mlk::point{10 + 8 + width, 10 + 8});
-		sprite.draw(win, 35, mlk::point{10 + 8, 10 + 8 + 16});
-		sprite.draw(win, 36, mlk::rectangle{10 + 8 + 16, 10 + 8 + 16, width - 16, 16});
-		sprite.draw(win, 37, mlk::point{10 + 8 + width, 10 + 8 + 16});
+		sprite.draw(painter, 3, mlk::point{10 + 8, 10 + 8});
+		sprite.draw(painter, 4, mlk::rectangle{10 + 8 + 16, 10 + 8, width - 16, 16});
+		sprite.draw(painter, 5, mlk::point{10 + 8 + width, 10 + 8});
+		sprite.draw(painter, 35, mlk::point{10 + 8, 10 + 8 + 16});
+		sprite.draw(painter, 36, mlk::rectangle{10 + 8 + 16, 10 + 8 + 16, width - 16, 16});
+		sprite.draw(painter, 37, mlk::point{10 + 8 + width, 10 + 8 + 16});
 
-		win.present();
-		std::this_thread::sleep_for(50ms);
+		painter.present();
 	}
 
 	return 0;