changeset 26:56cc058200b5

Client: add basic mlk-client code with an example, #472
author David Demelier <markand@malikania.fr>
date Fri, 08 Apr 2016 14:16:47 +0200
parents dc47ce56ce36
children 0a1adf7dcca0
files client/CMakeLists.txt client/main.cpp examples/01-bouncing/client.js examples/01-bouncing/fonts/DejaVuSans.ttf libclient/malikania/backend/sdl/window-backend.cpp libclient/malikania/backend/sdl/window-backend.h libclient/malikania/js-window.h libclient/malikania/window.cpp libclient/malikania/window.h libcommon/malikania/js.h
diffstat 10 files changed, 264 insertions(+), 253 deletions(-) [+]
line wrap: on
line diff
--- a/client/CMakeLists.txt	Wed Apr 06 13:33:30 2016 +0200
+++ b/client/CMakeLists.txt	Fri Apr 08 14:16:47 2016 +0200
@@ -23,37 +23,6 @@
 	main.cpp
 )
 
-add_executable(client ${FILES})
-
-target_include_directories(
-	client
-	PRIVATE
-	${client_SOURCE_DIR}
-)
-
-target_link_libraries(
-	client
-	libcommon
-	libclient
-)
-
-add_custom_command(
-	OUTPUT ${CMAKE_BINARY_DIR}/resources/images/mokodemo.png
-	DEPENDS ${client_SOURCE_DIR}/resources/images/mokodemo.png
-	COMMAND
-		${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/resources/images
-	COMMAND
-		${CMAKE_COMMAND} -E copy ${client_SOURCE_DIR}/resources/images/mokodemo.png ${CMAKE_BINARY_DIR}/resources/images/mokodemo.png
-)
-
-add_custom_target(
-	client-resources ALL
-	DEPENDS ${CMAKE_BINARY_DIR}/resources/images/mokodemo.png
-	SOURCES ${client_SOURCE_DIR}/resources/images/mokodemo.png
-)
-
-add_dependencies(client-resources client)
-
-if (APPLE)
-	target_compile_options(libcommon PRIVATE "-Wno-deprecated-declarations")
-endif ()
+add_executable(mlk-client ${FILES})
+target_include_directories(mlk-client PRIVATE ${client_SOURCE_DIR})
+target_link_libraries(mlk-client libcommon libclient)
--- a/client/main.cpp	Wed Apr 06 13:33:30 2016 +0200
+++ b/client/main.cpp	Fri Apr 08 14:16:47 2016 +0200
@@ -1,7 +1,7 @@
 /*
- * main.cpp -- main client files
+ * main.cpp -- main client file
  *
- * Copyright (c) 2014 David Demelier <markand@malikania.fr>
+ * Copyright (c) 2013-2016 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
@@ -16,225 +16,178 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#if 0
-
+#include <chrono>
 #include <iostream>
-#include <chrono>
 #include <thread>
-#include <map>
-#include <malikania/Json.h>
-#include <malikania/Window.h>
-#include <malikania/Size.h>
-#include <malikania/Sprite.h>
-#include <malikania/Image.h>
-#include <malikania/Point.h>
-#include <malikania/Label.h>
-#include <malikania/Animation.h>
-#include <malikania/Animator.h>
+
+#include <malikania/client-resources-loader.h>
+#include <malikania/resources-locator.h>
 
-using namespace std::literals::chrono_literals;
+#include <malikania/js-animation.h>
+#include <malikania/js-animator.h>
+#include <malikania/js-color.h>
+#include <malikania/js-font.h>
+#include <malikania/js-image.h>
+#include <malikania/js-line.h>
+#include <malikania/js-point.h>
+#include <malikania/js-rectangle.h>
+#include <malikania/js-size.h>
+#include <malikania/js-sprite.h>
+#include <malikania/js-window.h>
 
-// TODO delete this... just for fun
-bool goRight = true;
-bool goDown = true;
-const int mokoSize = 300;
+using namespace malikania;
+
+namespace {
+
+int usage()
+{
+	std::cerr << "usage: mlk-client directory\n";
+
+	return 1;
+}
 
-void bounce(malikania::Window& window, int &x, int &y) {
-	malikania::Size resolution = window.getWindowResolution();
-	int width = resolution.width;
-	int height = resolution.height;
-	if (y < 10) {
-		goDown = true;
-		y += 1;
-	}
-	if (goDown && y < height - mokoSize) {
-		// Moko falls
-		y += 0.2 * y;
+duk::Context init()
+{
+	duk::Context ctx;
+
+	/* TODO: Put Malikania global somewhere else */
+	duk::putGlobal(ctx, "Malikania", duk::Object());
+
+	loadMalikaniaAnimation(ctx);
+	loadMalikaniaAnimator(ctx);
+	loadMalikaniaColor(ctx);
+	loadMalikaniaFont(ctx);
+	loadMalikaniaImage(ctx);
+	loadMalikaniaLine(ctx);
+	loadMalikaniaPoint(ctx);
+	loadMalikaniaRectangle(ctx);
+	loadMalikaniaSize(ctx);
+	loadMalikaniaSprite(ctx);
+	loadMalikaniaWindow(ctx);
+
+	return ctx;
+}
+
+void start(duk::Context &ctx)
+{
+	duk::getGlobal<void>(ctx, "start");
+
+	if (duk::is<duk::Function>(ctx, -1)) {
+		duk::getGlobal<void>(ctx, "\xff""\xff""window");
+		duk::pcall(ctx, 1);
+		duk::pop(ctx);
 	} else {
-		// Moko will bounce!!!
-		goDown = false;
-	}
-	if (!goDown && y > 0) {
-		y -= 0.1 * y;
-	} else {
-		goDown = true;
-	}
-
-	if (goRight && x < width - mokoSize) {
-		x += 4;
-	} else {
-		goRight = false;
-	}
-	if (!goRight && x > 0) {
-		x -= 4;
-	} else {
-		goRight = true;
+		duk::pop(ctx);
 	}
 }
 
-// End TODO
+void update(duk::Context &ctx)
+{
+	duk::getGlobal<void>(ctx, "update");
 
-int main(void)
-{
-	malikania::Window mainWindow;
+	if (duk::is<duk::Function>(ctx, -1)) {
+		duk::pcall(ctx, 0);
+		duk::pop(ctx);
+	} else {
+		duk::pop(ctx);
+	}
+}
 
-	bool isBouncing = false;
+void draw(duk::Context &ctx)
+{
+	duk::getGlobal<void>(ctx, "draw");
 
-	int mokoPositionX = 0;
-	int mokoPositionY = 0;
+	if (duk::is<duk::Function>(ctx, -1)) {
+		duk::getGlobal<void>(ctx, "\xff""\xff""window");
+		duk::pcall(ctx, 1);
+		duk::pop(ctx);
+	} else {
+		duk::pop(ctx);
+	}
+}
 
-	std::map<int, bool> keyPressed = { {SDLK_UP, false}, {SDLK_DOWN, false}, {SDLK_RIGHT, false}, {SDLK_LEFT, false} };
+int run(duk::Context &ctx)
+{
+	bool running = true;
 
-	mainWindow.setOnKeyDown([&mainWindow, &mokoPositionX, &mokoPositionY, &isBouncing, &keyPressed](int sdlKey) {
-		switch (sdlKey) {
-		case SDLK_ESCAPE:
-			mainWindow.close();
-			break;
-		case SDLK_UP:
-			keyPressed[SDLK_UP] = true;
-			break;
-		case SDLK_DOWN:
-			keyPressed[SDLK_DOWN] = true;
-			break;
-		case SDLK_RIGHT:
-			keyPressed[SDLK_RIGHT] = true;
-			break;
-		case SDLK_LEFT:
-			keyPressed[SDLK_LEFT] = true;
-			break;
-		case SDLK_m:
-			isBouncing = !isBouncing;
-			break;
+	/* js-window use duk::Pointer at the moment so store it from there temporarily */
+	duk::putGlobal(ctx, "\xff""\xff""window", duk::Pointer<Window>{new Window});
+
+	Window *window = duk::getGlobal<duk::Pointer<Window>>(ctx, "\xff""\xff""window");
+
+	window->setOnQuit([&] () {
+		running = false;
+	});
+	window->setOnKeyDown([&] (unsigned key) {
+		duk::getGlobal<void>(ctx, "keyDown");
+
+		if (duk::is<duk::Function>(ctx, -1)) {
+			duk::push(ctx, static_cast<int>(key));
+			duk::pcall(ctx, 1);
+			duk::pop(ctx);
+		} else {
+			duk::pop(ctx);
+		}
+	});
+	window->setOnKeyDown([&] (unsigned key) {
+		duk::getGlobal<void>(ctx, "keyUp");
+
+		if (duk::is<duk::Function>(ctx, -1)) {
+			duk::push(ctx, static_cast<int>(key));
+			duk::pcall(ctx, 1);
+			duk::pop(ctx);
+		} else {
+			duk::pop(ctx);
 		}
 	});
 
-	mainWindow.setOnKeyUp([&keyPressed](int sdlKey) {
-		switch (sdlKey) {
-		case SDLK_UP:
-			keyPressed[SDLK_UP] = false;
-			break;
-		case SDLK_DOWN:
-			keyPressed[SDLK_DOWN] = false;
-			break;
-		case SDLK_RIGHT:
-			keyPressed[SDLK_RIGHT] = false;
-			break;
-		case SDLK_LEFT:
-			keyPressed[SDLK_LEFT] = false;
-			break;
-		}
-	});
+	start(ctx);
 
-	int animationStep = 1;
-	mainWindow.setOnRefresh([&mainWindow, &keyPressed, &animationStep](){
-		if (keyPressed[SDLK_LEFT]) {
-			std::string animationState = "left" + std::to_string(animationStep > 4 ? 4 : animationStep++);
-		} else if (keyPressed[SDLK_RIGHT]) {
-			std::string animationState = "right" + std::to_string(animationStep > 4 ? 4 : animationStep++);
-		} else if (keyPressed[SDLK_DOWN]) {
-			std::string animationState = "down" + std::to_string(animationStep > 4 ? 4 : animationStep++);
-		} else {
-			animationStep = 1;
-		}
-	});
-
-	malikania::Sprite testSprite = malikania::Sprite::fromJson(mainWindow, malikania::JsonDocument(
-		"{\"image\": \"resources/images/mokodemo.png\", \"alias\": \"testSprite\", \"cell\": [300, 300], \"size\": [1200, 900]}"
-	).toObject());
-
-	std::shared_ptr<malikania::Font> font = std::make_shared<malikania::Font>("resources/fonts/DejaVuSans.ttf", 48);
-	malikania::Label testLabel("Malikania !!! Youpi !", font, {0, 0, 100, 50});
-
-	std::shared_ptr<malikania::Animation> testAnimation = std::make_shared<malikania::Animation>(malikania::Animation::fromJson(mainWindow, malikania::JsonDocument(
-		std::string("{\"sprite\": \"no-working-yet.json\", \"alias\": \"testAnimation\", \"frames\": [")
-		+ "{ \"delay\": 200, \"cell\": 0 }, { \"delay\": 10, \"cell\": 1 },"
-		+ "{ \"delay\": 10, \"cell\": 2 }, { \"delay\": 200, \"cell\": 3 },"
-		+ "{ \"delay\": 10, \"cell\": 1 }, { \"delay\": 10, \"cell\": 1 },"
-		+ "{ \"delay\": 200, \"cell\": 4 }, { \"delay\": 10, \"cell\": 5 },"
-		+ "{ \"delay\": 10, \"cell\": 6 }, { \"delay\": 200, \"cell\": 7 },"
-		+ "{ \"delay\": 10, \"cell\": 6 }, { \"delay\": 10, \"cell\": 5 },"
-		+ "{ \"delay\": 200, \"cell\": 8 }, { \"delay\": 10, \"cell\": 9 },"
-		+ "{ \"delay\": 10, \"cell\": 10 }, { \"delay\": 200, \"cell\": 11 },"
-		+ "{ \"delay\": 10, \"cell\": 10 }, { \"delay\": 10, \"cell\": 9 }"
-		+ "]}"
-	).toObject()));
-
-	std::shared_ptr<malikania::Animation> testAnimation2 =  std::make_shared<malikania::Animation>(malikania::Animation::fromJson(mainWindow, malikania::JsonDocument(
-		std::string("{\"sprite\": \"no-working-yet.json\", \"alias\": \"testAnimation\", \"frames\": [")
-		+ "{ \"delay\": 2000, \"cell\": 0 }, { \"delay\": 10, \"cell\": 1 },"
-		+ "{ \"delay\": 10, \"cell\": 2 }, { \"delay\": 2000, \"cell\": 3 },"
-		+ "{ \"delay\": 10, \"cell\": 1 }, { \"delay\": 10, \"cell\": 1 }"
-		+ "]}"
-	).toObject()));
+	while (running) {
+		window->poll();
 
-	malikania::Animator testAnimator1 = malikania::Animator(testAnimation);
-	malikania::Animator testAnimator2 = malikania::Animator(std::move(testAnimation));
-	malikania::Animator testAnimator3 = malikania::Animator(std::move(testAnimation2));
-
-	while (mainWindow.isOpen()) {
-
-		// TODO delete this, just for fun...
-		if (isBouncing) {
-			bounce(mainWindow, mokoPositionX, mokoPositionY);
-		}
-
-		mainWindow.processEvent();
-		mainWindow.setDrawingColor({255, 255, 255, 255});
-		mainWindow.clear();
-		mainWindow.update();
-
-		testSprite.draw(mainWindow, 0, {0, 0, 300, 300});
-		testSprite.draw(mainWindow, 1, {200, 200, 300, 300});
-		testSprite.draw(mainWindow, 2, {400, 400, 300, 300});
-		testSprite.draw(mainWindow, 11, {600, 400, 300, 300});
-
-		malikania::Color c{255, 50, 40, 255};
-		mainWindow.setDrawingColor(c);
-		mainWindow.drawLine({0, 0, 300, 300});
-
-		std::vector<malikania::Point> points{{20, 20}, {30, 50}, {100, 200}, {30, 60}, {20, 300}, {100, 20}};
-		mainWindow.drawLines(points);
+		update(ctx);
+		draw(ctx);
 
-		mainWindow.setDrawingColor({200, 50, 200, 255});
-		mainWindow.drawPoint({400, 400});
-		mainWindow.drawPoint({400, 402});
-		mainWindow.drawPoint({400, 405});
-		mainWindow.drawPoint({400, 407});
-		mainWindow.drawPoint({400, 410});
-
-		mainWindow.setDrawingColor({0, 0, 0, 255});
-		mainWindow.drawPoints(points);
-
-		mainWindow.setDrawingColor({30, 30, 30, 255});
-		mainWindow.drawRectangle({500, 500, 200, 100});
-
-		mainWindow.setDrawingColor({130, 30, 30, 255});
-		mainWindow.drawRectangles({{800, 800, 200, 100}, {700, 700, 200, 100}, {750, 750, 200, 100}});
-
-		mainWindow.drawRectangle({600, 200, 200, 100}, true, {0, 255, 0, 255});
-
-		mainWindow.drawRectangles(
-			{{800, 400, 200, 100}, {700, 450, 200, 100}, {750, 500, 200, 100}},
-			true,
-			{{255,0,0,255},{0,255,0,255},{0,0,255,255}}
-		);
-
-		testLabel.draw(mainWindow, {300, 300, 200, 50});
-
-		testAnimator1.draw(mainWindow, {1000, 0, 300, 300});
-		testAnimator2.draw(mainWindow, {100, 600, 300, 300});
-		testAnimator3.draw(mainWindow, {400, 600, 300, 300});
-
-		mainWindow.present();
-
-		std::this_thread::sleep_for(5ms);
+		// TODO: remove this with an appropriate FPS calculation.
+		std::this_thread::sleep_for(std::chrono::milliseconds(50));
 	}
 
-	//malikania::Window::quit();
-
 	return 0;
 }
 
-#endif
+int boot(const std::string &directory)
+{
+	std::string path = directory + "/client.js";
+	duk::Context ctx = init();
+
+	/* Store the loader */
+	ResourcesLocatorDirectory locator(directory);
+	ClientResourcesLoader loader(locator);
+
+	duk::putGlobal(ctx, "\xff""\xff""loader", duk::RawPointer<ClientResourcesLoader>{&loader});
+
+	if (duk::pevalFile(ctx, path) != 0) {
+		duk::ErrorInfo info = duk::error(ctx, -1);
+
+		std::cerr << info.fileName << ":" << info.lineNumber << ": " << info.stack << std::endl;
 
-int main() { return 0; }
+		return 1;
+	}
+
+	return run(ctx);
+}
+
+} // !namespace
+
+int main(int argc, char **argv)
+{
+	-- argc;
+	++ argv;
+
+	if (argc < 1) {
+		return usage();
+	}
+
+	return boot(argv[0]);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/01-bouncing/client.js	Fri Apr 08 14:16:47 2016 +0200
@@ -0,0 +1,41 @@
+var x = 0;
+var y = 0;
+
+var dx = 1;
+var dy = 1;
+
+var font;
+var clip;
+
+function start(window)
+{
+	font = new Malikania.Font("fonts/DejaVuSans.ttf", 10);
+	clip = font.clip("Malikania");
+
+	x = (640 / 2) - (clip.width / 2);
+	y = (480 / 2) - (clip.height / 2);
+}
+
+function update()
+{
+	x += dx;
+	y += dy;
+
+	if (x >= 640 - clip.width)
+		dx = -1;
+	else if (x <= 0)
+		dx = 1;
+	if (y >= 480 - clip.height)
+		dy = -1;
+	else if (y <= 0)
+		dy = 1;
+}
+
+function draw(window)
+{
+	window.setDrawingColor('lightskyblue');
+	window.clear();
+	window.setDrawingColor('white');
+	window.drawText('Malikania', font, { x: x, y: y });
+	window.present();
+}
\ No newline at end of file
Binary file examples/01-bouncing/fonts/DejaVuSans.ttf has changed
--- a/libclient/malikania/backend/sdl/window-backend.cpp	Wed Apr 06 13:33:30 2016 +0200
+++ b/libclient/malikania/backend/sdl/window-backend.cpp	Fri Apr 08 14:16:47 2016 +0200
@@ -62,25 +62,20 @@
 	}
 }
 
-#if 0
-
-// TODO: later */
-
-void Window::Backend::processEvents(Window &window)
+void Window::Backend::poll(Window &self)
 {
 	SDL_Event event;
 
 	while (SDL_PollEvent(&event)) {
 		switch (event.type) {
 		case SDL_KEYUP:
-			window.onKeyUp(event.key.keysym.sym);
+			self.onKeyUp(event.key.keysym.sym);
 			break;
-		// TODO continue implemanting all event possible
 		case SDL_KEYDOWN:
-			window.onKeyDown(event.key.keysym.sym);
+			self.onKeyDown(event.key.keysym.sym);
 			break;
 		case SDL_QUIT:
-			window.close();
+			self.onQuit();
 			break;
 		default:
 			break;
@@ -88,8 +83,6 @@
 	}
 }
 
-#endif
-
 void Window::Backend::clear()
 {
 	SDL_RenderClear(m_renderer.get());
--- a/libclient/malikania/backend/sdl/window-backend.h	Wed Apr 06 13:33:30 2016 +0200
+++ b/libclient/malikania/backend/sdl/window-backend.h	Fri Apr 08 14:16:47 2016 +0200
@@ -44,6 +44,8 @@
 		return m_renderer.get();
 	}
 
+	void poll(Window &self);
+
 	void close();
 
 	void clear();
--- a/libclient/malikania/js-window.h	Wed Apr 06 13:33:30 2016 +0200
+++ b/libclient/malikania/js-window.h	Fri Apr 08 14:16:47 2016 +0200
@@ -42,7 +42,7 @@
 		duk::StackAssert sa(ctx, 1);
 
 		duk::getGlobal<void>(ctx, "Malikania");
-		duk::getGlobal<void>(ctx, "Window");
+		duk::getProperty<void>(ctx, -1, "Window");
 		duk::getProperty<void>(ctx, -1, "prototype");
 		duk::remove(ctx, -2);
 		duk::remove(ctx, -2);
--- a/libclient/malikania/window.cpp	Wed Apr 06 13:33:30 2016 +0200
+++ b/libclient/malikania/window.cpp	Fri Apr 08 14:16:47 2016 +0200
@@ -28,9 +28,14 @@
 {
 }
 
-Window::Window(Window &&) noexcept = default;
+Window::Window(Window &&) = default;
+
+Window::~Window() = default;
 
-Window::~Window() noexcept = default;
+void Window::poll()
+{
+	m_backend->poll(*this);
+}
 
 void Window::clear()
 {
@@ -108,6 +113,6 @@
 	m_backend->drawText(text, font, point);
 }
 
-Window &Window::operator=(Window &&) noexcept = default;
+Window &Window::operator=(Window &&) = default;
 
 } // !malikania
--- a/libclient/malikania/window.h	Wed Apr 06 13:33:30 2016 +0200
+++ b/libclient/malikania/window.h	Fri Apr 08 14:16:47 2016 +0200
@@ -24,6 +24,7 @@
  * @brief Window and drawing.
  */
 
+#include <functional>
 #include <memory>
 #include <vector>
 #include <string>
@@ -43,11 +44,51 @@
 private:
 	class Backend;
 
+	std::function<void ()> m_onQuit;
+	std::function<void (unsigned)> m_onKeyDown;
+	std::function<void (unsigned)> m_onKeyUp;
+
 	std::unique_ptr<Backend> m_backend;
 
 	bool m_isOpen{true};
 
 public:
+	inline void onQuit()
+	{
+		if (m_onQuit) {
+			m_onQuit();
+		}
+	}
+
+	inline void onKeyDown(unsigned key)
+	{
+		if (m_onKeyDown) {
+			m_onKeyDown(key);
+		}
+	}
+
+	inline void onKeyUp(unsigned key)
+	{
+		if (m_onKeyUp) {
+			m_onKeyUp(key);
+		}
+	}
+
+	inline void setOnQuit(std::function<void ()> fn) noexcept
+	{
+		m_onQuit = std::move(fn);
+	}
+
+	inline void setOnKeyDown(std::function<void (unsigned)> fn) noexcept
+	{
+		m_onKeyDown = std::move(fn);
+	}
+
+	inline void setOnKeyUp(std::function<void (unsigned)> fn) noexcept
+	{
+		m_onKeyUp = std::move(fn);
+	}
+
 	/**
 	 * Create a window.
 	 *
@@ -61,12 +102,12 @@
 	/**
 	 * Move constructor defaulted.
 	 */
-	Window(Window &&) noexcept;
+	Window(Window &&);
 
 	/**
 	 * Virtual destructor defaulted.
 	 */
-	virtual ~Window() noexcept;
+	virtual ~Window();
 
 	/**
 	 * Tells if the window is open.
@@ -99,6 +140,11 @@
 	}
 
 	/**
+	 * Poll pending events.
+	 */
+	void poll();
+
+	/**
 	 * Clear the window content with the current drawing color.
 	 */
 	void clear();
@@ -214,7 +260,7 @@
 	 *
 	 * @return this
 	 */
-	Window &operator=(Window &&) noexcept;
+	Window &operator=(Window &&);
 };
 
 } // !malikania
--- a/libcommon/malikania/js.h	Wed Apr 06 13:33:30 2016 +0200
+++ b/libcommon/malikania/js.h	Fri Apr 08 14:16:47 2016 +0200
@@ -304,8 +304,7 @@
 
 	Context(const Context &) = delete;
 	Context &operator=(const Context &) = delete;
-	Context(const Context &&) = delete;
-	Context &operator=(const Context &&) = delete;
+
 
 public:
 	/**
@@ -316,6 +315,9 @@
 	{
 	}
 
+	Context(Context &&) noexcept = default;
+	Context &operator=(Context &&) noexcept = default;
+
 	/**
 	 * Convert the context to the native Duktape/C type.
 	 *
@@ -2235,7 +2237,7 @@
 			delete static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1));
 			duk_pop(ctx);
 			duk_push_null(ctx);
-			duk_put_prop_string(ctx, 0, "\xff""\xff""js-ptr");
+			duk_put_prop_string(ctx, 0, "\xff""\xff""js-shared-ptr");
 
 			return 0;
 		}, 1);