changeset 52:4bc4732fa1dd

Client: add basic button, closes #601
author David Demelier <markand@malikania.fr>
date Thu, 15 Dec 2016 13:46:46 +0100
parents d04a4915be2b
children fe7e3524e571
files client/CMakeLists.txt client/main.cpp libclient/CMakeLists.txt libclient/assets/dejavu_sans.ttf libclient/malikania/backend/sdl/window_backend.cpp libclient/malikania/backend/sdl/window_backend.hpp libclient/malikania/button.cpp libclient/malikania/button.hpp libclient/malikania/frame.cpp libclient/malikania/frame.hpp libclient/malikania/grid_layout.cpp libclient/malikania/grid_layout.hpp libclient/malikania/key.hpp libclient/malikania/layout.hpp libclient/malikania/mouse.hpp libclient/malikania/theme.cpp libclient/malikania/theme.hpp libclient/malikania/unique_layout.cpp libclient/malikania/unique_layout.hpp libclient/malikania/widget.cpp libclient/malikania/widget.hpp libclient/malikania/window.cpp libclient/malikania/window.hpp
diffstat 23 files changed, 2067 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/client/CMakeLists.txt	Thu Dec 15 13:43:09 2016 +0100
+++ b/client/CMakeLists.txt	Thu Dec 15 13:46:46 2016 +0100
@@ -16,13 +16,15 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
 
-project(client)
+project(mlk-client)
 
 set(
-    FILES
+    SOURCES
     main.cpp
 )
 
-add_executable(mlk-client ${FILES})
-target_include_directories(mlk-client PRIVATE ${client_SOURCE_DIR})
-target_link_libraries(mlk-client libclient-js)
+malikania_define_executable(
+    TARGET mlk-client
+    SOURCES ${SOURCES}
+    LIBRARIES libclient-js
+)
--- a/client/main.cpp	Thu Dec 15 13:43:09 2016 +0100
+++ b/client/main.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -16,7 +16,38 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <chrono>
+#include <thread>
+
+#include "malikania/button.hpp"
+#include "malikania/color.hpp"
+#include "malikania/frame.hpp"
+#include "malikania/point.hpp"
+#include "malikania/unique_layout.hpp"
+#include "malikania/window.hpp"
+
 int main()
 {
+    mlk::window win;
+
+    auto f = std::make_shared<mlk::frame>();
+    auto b = std::make_shared<mlk::button>("click me!");
+    auto l = std::make_shared<mlk::unique_layout>(b);
+
+    b->on_clicked.connect([] {
+        puts("clicked!!!!");
+    });
+    f->move({50, 50});
+    f->set_layout(l);
+    win.add_frame(f);
+
+    while (win.is_open()) {
+        win.poll();
+        win.set_drawing_color({255, 255, 255, 255});
+        win.clear();
+        win.draw_frames();
+        win.present();
+        std::this_thread::sleep_for(std::chrono::milliseconds(50));
+    }
 }
 
--- a/libclient/CMakeLists.txt	Thu Dec 15 13:43:09 2016 +0100
+++ b/libclient/CMakeLists.txt	Thu Dec 15 13:46:46 2016 +0100
@@ -22,14 +22,23 @@
     HEADERS
     ${libclient_SOURCE_DIR}/malikania/animation.hpp
     ${libclient_SOURCE_DIR}/malikania/animator.hpp
+    ${libclient_SOURCE_DIR}/malikania/button.hpp
     ${libclient_SOURCE_DIR}/malikania/color.hpp
     ${libclient_SOURCE_DIR}/malikania/font.hpp
+    ${libclient_SOURCE_DIR}/malikania/frame.hpp
+#    ${libclient_SOURCE_DIR}/malikania/grid_layout.hpp
     ${libclient_SOURCE_DIR}/malikania/image.hpp
+    ${libclient_SOURCE_DIR}/malikania/key.hpp
+    ${libclient_SOURCE_DIR}/malikania/layout.hpp
     ${libclient_SOURCE_DIR}/malikania/line.hpp
+    ${libclient_SOURCE_DIR}/malikania/mouse.hpp
     ${libclient_SOURCE_DIR}/malikania/point.hpp
     ${libclient_SOURCE_DIR}/malikania/rectangle.hpp
     ${libclient_SOURCE_DIR}/malikania/size.hpp
     ${libclient_SOURCE_DIR}/malikania/sprite.hpp
+    ${libclient_SOURCE_DIR}/malikania/theme.hpp
+    ${libclient_SOURCE_DIR}/malikania/unique_layout.hpp
+    ${libclient_SOURCE_DIR}/malikania/widget.hpp
     ${libclient_SOURCE_DIR}/malikania/window.hpp
     ${libclient_SOURCE_DIR}/malikania/${WITH_BACKEND_DIR}/font_backend.hpp
     ${libclient_SOURCE_DIR}/malikania/${WITH_BACKEND_DIR}/image_backend.hpp
@@ -39,11 +48,17 @@
 set(
     SOURCES
     ${libclient_SOURCE_DIR}/malikania/animator.cpp
+    ${libclient_SOURCE_DIR}/malikania/button.cpp
     ${libclient_SOURCE_DIR}/malikania/client_resources_loader.cpp
     ${libclient_SOURCE_DIR}/malikania/color.cpp
     ${libclient_SOURCE_DIR}/malikania/font.cpp
+    ${libclient_SOURCE_DIR}/malikania/frame.cpp
+#    ${libclient_SOURCE_DIR}/malikania/grid_layout.cpp
     ${libclient_SOURCE_DIR}/malikania/image.cpp
     ${libclient_SOURCE_DIR}/malikania/sprite.cpp
+    ${libclient_SOURCE_DIR}/malikania/theme.cpp
+    ${libclient_SOURCE_DIR}/malikania/unique_layout.cpp
+    ${libclient_SOURCE_DIR}/malikania/widget.cpp
     ${libclient_SOURCE_DIR}/malikania/window.cpp
     ${libclient_SOURCE_DIR}/malikania/${WITH_BACKEND_DIR}/font_backend.cpp
     ${libclient_SOURCE_DIR}/malikania/${WITH_BACKEND_DIR}/image_backend.cpp
@@ -75,6 +90,7 @@
     PROJECT libclient
     TARGET libclient
     SOURCES ${HEADERS} ${SOURCES}
+    ASSETS ${libclient_SOURCE_DIR}/assets/dejavu_sans.ttf
     PUBLIC_INCLUDES
         $<BUILD_INTERFACE:${libclient_SOURCE_DIR}/malikania/${WITH_BACKEND_DIR}>
         $<BUILD_INTERFACE:${libclient_SOURCE_DIR}/malikania>
Binary file libclient/assets/dejavu_sans.ttf has changed
--- a/libclient/malikania/backend/sdl/window_backend.cpp	Thu Dec 15 13:43:09 2016 +0100
+++ b/libclient/malikania/backend/sdl/window_backend.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -16,21 +16,380 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <cassert>
+#include <stdexcept>
+#include <unordered_map>
+
 #include <SDL.h>
 #include <SDL_ttf.h>
 
-#include <stdexcept>
-
 #include <malikania/color.hpp>
+#include <malikania/key.hpp>
 #include <malikania/line.hpp>
 #include <malikania/point.hpp>
 #include <malikania/rectangle.hpp>
 
 #include "font_backend.hpp"
 #include "window_backend.hpp"
+#include "window.hpp"
 
 namespace mlk {
 
+namespace {
+
+/*
+ * conversion maps
+ * ------------------------------------------------------------------
+ *
+ * There is a difference between scancode and key codes. The scancode is the
+ * key located on the keyboard which is always the same, the key code is the
+ * translated to the user layout.
+ *
+ * For example, on azerty, hitting the 'a' key makes a key code of 'a' but a
+ * scancode of 'q'.
+ *
+ *   - scancodes_map        : convert SDL scancode do key
+ *   - keycodes_map         : convert SDL key code to key
+ *   - modifiers_map        : convert masks
+ *   - button_map           : convert SDL mouse buttons
+ */
+
+const std::unordered_map<SDL_Keycode, key> scancodes_map{
+    { SDL_SCANCODE_A,               key::a                      },
+    { SDL_SCANCODE_B,               key::b                      },
+    { SDL_SCANCODE_C,               key::c                      },
+    { SDL_SCANCODE_D,               key::d                      },
+    { SDL_SCANCODE_E,               key::e                      },
+    { SDL_SCANCODE_F,               key::f                      },
+    { SDL_SCANCODE_G,               key::g                      },
+    { SDL_SCANCODE_H,               key::h                      },
+    { SDL_SCANCODE_I,               key::i                      },
+    { SDL_SCANCODE_J,               key::j                      },
+    { SDL_SCANCODE_K,               key::k                      },
+    { SDL_SCANCODE_L,               key::l                      },
+    { SDL_SCANCODE_M,               key::m                      },
+    { SDL_SCANCODE_N,               key::n                      },
+    { SDL_SCANCODE_O,               key::o                      },
+    { SDL_SCANCODE_P,               key::p                      },
+    { SDL_SCANCODE_Q,               key::q                      },
+    { SDL_SCANCODE_R,               key::r                      },
+    { SDL_SCANCODE_S,               key::s                      },
+    { SDL_SCANCODE_T,               key::t                      },
+    { SDL_SCANCODE_U,               key::u                      },
+    { SDL_SCANCODE_V,               key::v                      },
+    { SDL_SCANCODE_W,               key::w                      },
+    { SDL_SCANCODE_X,               key::x                      },
+    { SDL_SCANCODE_Y,               key::y                      },
+    { SDL_SCANCODE_Z,               key::z                      },
+    { SDL_SCANCODE_1,               key::one                    },
+    { SDL_SCANCODE_2,               key::two                    },
+    { SDL_SCANCODE_3,               key::three                  },
+    { SDL_SCANCODE_4,               key::four                   },
+    { SDL_SCANCODE_5,               key::five                   },
+    { SDL_SCANCODE_6,               key::six                    },
+    { SDL_SCANCODE_7,               key::seven                  },
+    { SDL_SCANCODE_8,               key::eight                  },
+    { SDL_SCANCODE_9,               key::nine                   },
+    { SDL_SCANCODE_0,               key::zero                   },
+    { SDL_SCANCODE_RETURN,          key::enter                  },
+    { SDL_SCANCODE_ESCAPE,          key::escape                 },
+    { SDL_SCANCODE_BACKSPACE,       key::backspace              },
+    { SDL_SCANCODE_TAB,             key::tab                    },
+    { SDL_SCANCODE_SPACE,           key::space                  },
+    { SDL_SCANCODE_MINUS,           key::minus                  },
+    { SDL_SCANCODE_EQUALS,          key::equals                 },
+    { SDL_SCANCODE_LEFTBRACKET,     key::left_bracket           },
+    { SDL_SCANCODE_RIGHTBRACKET,    key::right_bracket          },
+    { SDL_SCANCODE_BACKSLASH,       key::backslash              },
+    { SDL_SCANCODE_NONUSHASH,       key::hash                   },
+    { SDL_SCANCODE_SEMICOLON,       key::semicolon              },
+    { SDL_SCANCODE_APOSTROPHE,      key::apostrophe             },
+    { SDL_SCANCODE_GRAVE,           key::grave                  },
+    { SDL_SCANCODE_COMMA,           key::comma                  },
+    { SDL_SCANCODE_PERIOD,          key::period                 },
+    { SDL_SCANCODE_SLASH,           key::slash                  },
+    { SDL_SCANCODE_CAPSLOCK,        key::caps_lock              },
+    { SDL_SCANCODE_F1,              key::f1                     },
+    { SDL_SCANCODE_F2,              key::f2                     },
+    { SDL_SCANCODE_F3,              key::f3                     },
+    { SDL_SCANCODE_F4,              key::f4                     },
+    { SDL_SCANCODE_F5,              key::f5                     },
+    { SDL_SCANCODE_F6,              key::f6                     },
+    { SDL_SCANCODE_F7,              key::f7                     },
+    { SDL_SCANCODE_F8,              key::f8                     },
+    { SDL_SCANCODE_F9,              key::f9                     },
+    { SDL_SCANCODE_F10,             key::f10                    },
+    { SDL_SCANCODE_F11,             key::f11                    },
+    { SDL_SCANCODE_F12,             key::f12                    },
+    { SDL_SCANCODE_F13,             key::f13                    },
+    { SDL_SCANCODE_F14,             key::f14                    },
+    { SDL_SCANCODE_F15,             key::f15                    },
+    { SDL_SCANCODE_F16,             key::f16                    },
+    { SDL_SCANCODE_F17,             key::f17                    },
+    { SDL_SCANCODE_F18,             key::f18                    },
+    { SDL_SCANCODE_F19,             key::f19                    },
+    { SDL_SCANCODE_F20,             key::f20                    },
+    { SDL_SCANCODE_F21,             key::f21                    },
+    { SDL_SCANCODE_F22,             key::f22                    },
+    { SDL_SCANCODE_F23,             key::f23                    },
+    { SDL_SCANCODE_F24,             key::f24                    },
+    { SDL_SCANCODE_PRINTSCREEN,     key::print_screen           },
+    { SDL_SCANCODE_SCROLLLOCK,      key::scroll_lock            },
+    { SDL_SCANCODE_PAUSE,           key::pause                  },
+    { SDL_SCANCODE_INSERT,          key::insert                 },
+    { SDL_SCANCODE_HOME,            key::home                   },
+    { SDL_SCANCODE_PAGEUP,          key::page_up                },
+    { SDL_SCANCODE_PAGEDOWN,        key::page_down              },
+    { SDL_SCANCODE_DELETE,          key::del                    },
+    { SDL_SCANCODE_END,             key::end                    },
+    { SDL_SCANCODE_RIGHT,           key::right                  },
+    { SDL_SCANCODE_LEFT,            key::left                   },
+    { SDL_SCANCODE_DOWN,            key::down                   },
+    { SDL_SCANCODE_UP,              key::up                     },
+    { SDL_SCANCODE_KP_DIVIDE,       key::kp_divide              },
+    { SDL_SCANCODE_KP_MULTIPLY,     key::kp_multiply            },
+    { SDL_SCANCODE_KP_MINUS,        key::kp_minus               },
+    { SDL_SCANCODE_KP_PLUS,         key::kp_plus                },
+    { SDL_SCANCODE_KP_ENTER,        key::kp_enter               },
+    { SDL_SCANCODE_KP_1,            key::kp_one                 },
+    { SDL_SCANCODE_KP_2,            key::kp_two                 },
+    { SDL_SCANCODE_KP_3,            key::kp_three               },
+    { SDL_SCANCODE_KP_4,            key::kp_four                },
+    { SDL_SCANCODE_KP_5,            key::kp_five                },
+    { SDL_SCANCODE_KP_6,            key::kp_six                 },
+    { SDL_SCANCODE_KP_7,            key::kp_seven               },
+    { SDL_SCANCODE_KP_8,            key::kp_eight               },
+    { SDL_SCANCODE_KP_9,            key::kp_nine                },
+    { SDL_SCANCODE_KP_0,            key::kp_zero                },
+    { SDL_SCANCODE_MUTE,            key::mute                   },
+    { SDL_SCANCODE_VOLUMEUP,        key::volume_up              },
+    { SDL_SCANCODE_VOLUMEDOWN,      key::volume_down            },
+    { SDL_SCANCODE_LCTRL,           key::left_control           },
+    { SDL_SCANCODE_LALT,            key::left_alt               },
+    { SDL_SCANCODE_LSHIFT,          key::left_shift             },
+    { SDL_SCANCODE_LGUI,            key::left_super             },
+    { SDL_SCANCODE_RCTRL,           key::right_control          },
+    { SDL_SCANCODE_RALT,            key::right_alt              },
+    { SDL_SCANCODE_RSHIFT,          key::right_shift            },
+    { SDL_SCANCODE_RGUI,            key::right_super            }
+};
+
+const std::unordered_map<SDL_Keycode, key> keycodes_map{
+    { SDLK_RETURN,                  key::enter                  },
+    { SDLK_ESCAPE,                  key::escape                 },
+    { SDLK_BACKSPACE,               key::backspace              },
+    { SDLK_TAB,                     key::tab                    },
+    { SDLK_SPACE,                   key::space                  },
+    { SDLK_EXCLAIM,                 key::exclaim                },
+    { SDLK_QUOTEDBL,                key::double_quote           },
+    { SDLK_HASH,                    key::hash                   },
+    { SDLK_PERCENT,                 key::percent                },
+    { SDLK_DOLLAR,                  key::dollar                 },
+    { SDLK_AMPERSAND,               key::ampersand              },
+    { SDLK_QUOTE,                   key::quote                  },
+    { SDLK_LEFTPAREN,               key::left_parenthese        },
+    { SDLK_RIGHTPAREN,              key::right_parenthese       },
+    { SDLK_ASTERISK,                key::asterisk               },
+    { SDLK_PLUS,                    key::plus                   },
+    { SDLK_COMMA,                   key::comma                  },
+    { SDLK_MINUS,                   key::minus                  },
+    { SDLK_PERIOD,                  key::period                 },
+    { SDLK_SLASH,                   key::slash                  },
+    { SDLK_0,                       key::zero                   },
+    { SDLK_1,                       key::one                    },
+    { SDLK_2,                       key::two                    },
+    { SDLK_3,                       key::three                  },
+    { SDLK_4,                       key::four                   },
+    { SDLK_5,                       key::five                   },
+    { SDLK_6,                       key::six                    },
+    { SDLK_7,                       key::seven                  },
+    { SDLK_8,                       key::eight                  },
+    { SDLK_9,                       key::nine                   },
+    { SDLK_COLON,                   key::colon                  },
+    { SDLK_SEMICOLON,               key::semicolon              },
+    { SDLK_LESS,                    key::less                   },
+    { SDLK_EQUALS,                  key::equals                 },
+    { SDLK_GREATER,                 key::greater                },
+    { SDLK_QUESTION,                key::question               },
+    { SDLK_AT,                      key::at                     },
+    { SDLK_LEFTBRACKET,             key::left_bracket           },
+    { SDLK_BACKSLASH,               key::backslash              },
+    { SDLK_RIGHTBRACKET,            key::right_bracket          },
+    { SDLK_CARET,                   key::caret                  },
+    { SDLK_UNDERSCORE,              key::underscore             },
+    { SDLK_BACKQUOTE,               key::back_quote             },
+    { SDLK_a,                       key::a                      },
+    { SDLK_b,                       key::b                      },
+    { SDLK_c,                       key::c                      },
+    { SDLK_d,                       key::d                      },
+    { SDLK_e,                       key::e                      },
+    { SDLK_f,                       key::f                      },
+    { SDLK_g,                       key::g                      },
+    { SDLK_h,                       key::h                      },
+    { SDLK_i,                       key::i                      },
+    { SDLK_j,                       key::j                      },
+    { SDLK_k,                       key::k                      },
+    { SDLK_l,                       key::l                      },
+    { SDLK_m,                       key::m                      },
+    { SDLK_n,                       key::n                      },
+    { SDLK_o,                       key::o                      },
+    { SDLK_p,                       key::p                      },
+    { SDLK_q,                       key::q                      },
+    { SDLK_r,                       key::r                      },
+    { SDLK_s,                       key::s                      },
+    { SDLK_t,                       key::t                      },
+    { SDLK_u,                       key::u                      },
+    { SDLK_v,                       key::v                      },
+    { SDLK_w,                       key::w                      },
+    { SDLK_x,                       key::x                      },
+    { SDLK_y,                       key::y                      },
+    { SDLK_z,                       key::z                      },
+    { SDLK_CAPSLOCK,                key::caps_lock              },
+    { SDLK_F1,                      key::f1                     },
+    { SDLK_F2,                      key::f2                     },
+    { SDLK_F3,                      key::f3                     },
+    { SDLK_F4,                      key::f4                     },
+    { SDLK_F5,                      key::f5                     },
+    { SDLK_F6,                      key::f6                     },
+    { SDLK_F7,                      key::f7                     },
+    { SDLK_F8,                      key::f8                     },
+    { SDLK_F9,                      key::f9                     },
+    { SDLK_F10,                     key::f10                    },
+    { SDLK_F11,                     key::f11                    },
+    { SDLK_F12,                     key::f12                    },
+    { SDLK_F13,                     key::f13                    },
+    { SDLK_F14,                     key::f14                    },
+    { SDLK_F15,                     key::f15                    },
+    { SDLK_F16,                     key::f16                    },
+    { SDLK_F17,                     key::f17                    },
+    { SDLK_F18,                     key::f18                    },
+    { SDLK_F19,                     key::f19                    },
+    { SDLK_F20,                     key::f20                    },
+    { SDLK_F21,                     key::f21                    },
+    { SDLK_F22,                     key::f22                    },
+    { SDLK_F23,                     key::f23                    },
+    { SDLK_F24,                     key::f24                    },
+    { SDLK_PRINTSCREEN,             key::print_screen           },
+    { SDLK_SCROLLLOCK,              key::scroll_lock            },
+    { SDLK_PAUSE,                   key::pause                  },
+    { SDLK_INSERT,                  key::insert                 },
+    { SDLK_HOME,                    key::home                   },
+    { SDLK_PAGEUP,                  key::page_up                },
+    { SDLK_PAGEDOWN,                key::page_down              },
+    { SDLK_DELETE,                  key::del                    },
+    { SDLK_END,                     key::end                    },
+    { SDLK_RIGHT,                   key::right                  },
+    { SDLK_LEFT,                    key::left                   },
+    { SDLK_DOWN,                    key::down                   },
+    { SDLK_UP,                      key::up                     },
+    { SDLK_KP_DIVIDE,               key::kp_divide              },
+    { SDLK_KP_MULTIPLY,             key::kp_multiply            },
+    { SDLK_KP_MINUS,                key::kp_minus               },
+    { SDLK_KP_PLUS,                 key::kp_plus                },
+    { SDLK_KP_ENTER,                key::kp_enter               },
+    { SDLK_KP_1,                    key::kp_one                 },
+    { SDLK_KP_2,                    key::kp_two                 },
+    { SDLK_KP_3,                    key::kp_three               },
+    { SDLK_KP_4,                    key::kp_four                },
+    { SDLK_KP_5,                    key::kp_five                },
+    { SDLK_KP_6,                    key::kp_six                 },
+    { SDLK_KP_7,                    key::kp_seven               },
+    { SDLK_KP_8,                    key::kp_eight               },
+    { SDLK_KP_9,                    key::kp_nine                },
+    { SDLK_KP_0,                    key::kp_zero                },
+    { SDLK_MUTE,                    key::mute                   },
+    { SDLK_VOLUMEUP,                key::volume_up              },
+    { SDLK_VOLUMEDOWN,              key::volume_down            },
+    { SDLK_LCTRL,                   key::left_control           },
+    { SDLK_LALT,                    key::left_alt               },
+    { SDLK_LSHIFT,                  key::left_shift             },
+    { SDLK_LGUI,                    key::left_super             },
+    { SDLK_RCTRL,                   key::right_control          },
+    { SDLK_RALT,                    key::right_alt              },
+    { SDLK_RSHIFT,                  key::right_shift            },
+    { SDLK_RGUI,                    key::right_super            }
+};
+
+const std::unordered_map<SDL_Keymod, mod> modifiers_map{
+    { KMOD_LCTRL,                   mod::left_control           },
+    { KMOD_LALT,                    mod::left_alt               },
+    { KMOD_LSHIFT,                  mod::left_shift             },
+    { KMOD_LGUI,                    mod::left_super             },
+    { KMOD_RCTRL,                   mod::right_control          },
+    { KMOD_RALT,                    mod::right_alt              },
+    { KMOD_RSHIFT,                  mod::right_shift            },
+    { KMOD_RGUI,                    mod::right_super            },
+    { KMOD_NUM,                     mod::num_lock               },
+    { KMOD_CAPS,                    mod::caps_lock              }
+};
+
+const std::unordered_map<Uint32, mouse> button_map{
+    { SDL_BUTTON_LEFT,      mouse::left     },
+    { SDL_BUTTON_RIGHT,     mouse::right    },
+    { SDL_BUTTON_MIDDLE,    mouse::middle   }
+};
+
+} // !namespace
+
+void window::backend_impl::handle_key(window& self, const SDL_Event& ev)
+{
+    assert(ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP);
+
+    // Key and scan code.
+    auto kc = keycodes_map.find(ev.key.keysym.sym);
+    auto sc = scancodes_map.find(ev.key.keysym.scancode);
+
+    if (kc == keycodes_map.end() || sc == scancodes_map.end()) {
+        return;
+    }
+
+    key_event kev{kc->second, sc->second, mod::none};
+
+    // Modifier is a mask.
+    for (const auto& pair : modifiers_map) {
+        if (ev.key.keysym.mod & pair.first) {
+            kev.modifiers |= pair.second;
+        }
+    }
+
+    if (ev.type == SDL_KEYDOWN) {
+        self.handle_key_down(kev);
+    } else {
+        self.handle_key_up(kev);
+    }
+}
+
+void window::backend_impl::handle_mouse(window& self, const SDL_Event& ev)
+{
+    assert(ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEBUTTONUP);
+
+    auto which = button_map.find(ev.button.button);
+
+    if (which == button_map.end()) {
+        return;
+    }
+
+    mouse_click_event mev{
+        which->second,
+        point(ev.button.x, ev.button.y)
+    };
+
+    if (ev.type == SDL_MOUSEBUTTONDOWN) {
+        self.handle_mouse_down(mev);
+    } else {
+        self.handle_mouse_up(mev);
+    }
+}
+
+void window::backend_impl::handle_mouse_wheel(window& self, const SDL_Event& ev)
+{
+    assert(ev.type == SDL_MOUSEWHEEL);
+
+    mouse_wheel_event wev{{ev.wheel.x, ev.wheel.y}};
+
+    self.handle_mouse_wheel(wev);
+}
+
 window::backend_impl::backend_impl(window&, unsigned width, unsigned height, const std::string& title)
     : m_window(nullptr, nullptr)
     , m_renderer(nullptr, nullptr)
@@ -61,27 +420,6 @@
     }
 }
 
-void window::backend_impl::poll(window&)
-{
-    SDL_Event event;
-
-    while (SDL_PollEvent(&event)) {
-        switch (event.type) {
-        case SDL_KEYUP:
-            //self.onKeyUp(event.key.keysym.sym);
-            break;
-        case SDL_KEYDOWN:
-            //self.onKeyDown(event.key.keysym.sym);
-            break;
-        case SDL_QUIT:
-            //self.onQuit();
-            break;
-        default:
-            break;
-        }
-    }
-}
-
 void window::backend_impl::clear()
 {
     SDL_RenderClear(m_renderer.get());
@@ -267,4 +605,30 @@
 {
 }
 
+void window::backend_impl::poll(window& self)
+{
+    SDL_Event event;
+
+    while (SDL_PollEvent(&event)) {
+        switch (event.type) {
+        case SDL_KEYUP:
+        case SDL_KEYDOWN:
+            handle_key(self, event);
+            break;
+        case SDL_MOUSEBUTTONDOWN:
+        case SDL_MOUSEBUTTONUP:
+            handle_mouse(self, event);
+            break;
+        case SDL_MOUSEWHEEL:
+            handle_mouse_wheel(self, event);
+            break;
+        case SDL_QUIT:
+            self.handle_quit();
+            break;
+        default:
+            break;
+        }
+    }
+}
+
 } // !mlk
--- a/libclient/malikania/backend/sdl/window_backend.hpp	Thu Dec 15 13:43:09 2016 +0100
+++ b/libclient/malikania/backend/sdl/window_backend.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -33,6 +33,10 @@
     std::unique_ptr<SDL_Window, void (*)(SDL_Window *)> m_window;
     std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer *)> m_renderer;
 
+    void handle_key(window&, const SDL_Event&);
+    void handle_mouse(window&, const SDL_Event&);
+    void handle_mouse_wheel(window&, const SDL_Event&);
+
 public:
     backend_impl(window& self, unsigned width, unsigned height, const std::string& title);
 
@@ -41,8 +45,6 @@
         return m_renderer.get();
     }
 
-    void poll(window& self);
-
     void close();
 
     void clear();
@@ -72,6 +74,8 @@
     void draw_text(const std::string& text, font& font, const rectangle& rectangle);
 
     void draw_text(const std::string& text, font& font, const point& point);
+
+    void poll(window &self);
 };
 
 } // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/button.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,47 @@
+/*
+ * button.cpp -- GUI button element
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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 "button.hpp"
+#include "theme.hpp"
+#include "window.hpp"
+
+namespace mlk {
+
+button::button(std::string text)
+    : m_text(std::move(text))
+{
+}
+
+void button::handle_mouse_down(const mouse_click_event& ev)
+{
+    if (ev.button == mouse::left) {
+        on_clicked();
+    }
+}
+
+void button::draw(window& w, const rectangle& rect)
+{
+    w.theme().draw_button(w, *this, rect);
+}
+
+mlk::size button::size(window& w) const noexcept
+{
+    return w.theme().size_button(*this);
+}
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/button.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,97 @@
+/*
+ * button.hpp -- GUI button element
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_BUTTON_HPP
+#define MALIKANIA_CLIENT_BUTTON_HPP
+
+/**
+ * \file button.hpp
+ * \brief GUI button element.
+ */
+
+#include <boost/signals2.hpp>
+
+#include <string>
+
+#include "widget.hpp"
+
+namespace mlk {
+
+/**
+ * \brief Basic button element.
+ */
+class button : public widget {
+public:
+    boost::signals2::signal<void()> on_clicked;
+    boost::signals2::signal<void()> on_released;
+
+private:
+    std::string m_text;
+
+public:
+    /**
+     * Default constructor with optional text.
+     *
+     * \param text the text
+     */
+    button(std::string text = "");
+
+    /**
+     * Get the button text.
+     *
+     * \return the text
+     */
+    inline const std::string& text() const noexcept
+    {
+        return m_text;
+    }
+
+    /**
+     * Set the button text.
+     *
+     * \param text the text
+     */
+    inline void set_text(std::string text) noexcept
+    {
+        m_text = std::move(text);
+    }
+
+    /**
+     * \copydoc widget::handle_mouse_down
+     */
+    void handle_mouse_down(const mouse_click_event& ev) override;
+
+    /**
+     * Bring back other functions.
+     */
+    using widget::draw;
+
+    /**
+     * \copydoc widget::draw
+     */
+    void draw(window& w, const rectangle& rect) override;
+
+    /**
+     * \copydoc widget::size
+     */
+    mlk::size size(window& w) const noexcept override;
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_BUTTON_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/frame.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,45 @@
+/*
+ * frame.cpp -- top level GUI frame
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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 "layout.hpp"
+#include "frame.hpp"
+#include "point.hpp"
+#include "rectangle.hpp"
+#include "theme.hpp"
+#include "window.hpp"
+
+namespace mlk {
+
+mlk::size frame::size(window &win)
+{
+    return m_layout ? m_layout->size(win) : mlk::size();
+}
+
+void frame::draw(window& win)
+{
+    if (m_layout) {
+        win.theme().draw_frame(win, *this);
+    }
+}
+
+void frame::move(point pos)
+{
+    m_position = std::move(pos);
+}
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/frame.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,174 @@
+/*
+ * frame.hpp -- top level GUI frame
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_FRAME_HPP
+#define MALIKANIA_CLIENT_FRAME_HPP
+
+/**
+ * \file frame.hpp
+ * \brief Top level GUI frame.
+ */
+
+#include <memory>
+
+#include "point.hpp"
+#include "size.hpp"
+
+namespace mlk {
+
+class layout;
+class point;
+class window;
+
+/**
+ * \brief Top level GUI frame.
+ */
+class frame {
+private:
+    std::shared_ptr<mlk::layout> m_layout;
+    mlk::point m_position;
+    mlk::size m_padding{10, 10};
+
+public:
+    /**
+     * Create a frame with an optional layout.
+     *
+     * \param layout the optional layout
+     */
+    inline frame(std::shared_ptr<mlk::layout> layout = nullptr) noexcept
+        : m_layout(std::move(layout))
+    {
+    }
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~frame() = default;
+
+    /**
+     * Get the current layout.
+     *
+     * return the layout
+     */
+    inline const std::shared_ptr<mlk::layout>& layout() const noexcept
+    {
+        return m_layout;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the layout
+     */
+    inline std::shared_ptr<mlk::layout>& layout() noexcept
+    {
+        return m_layout;
+    }
+
+    /**
+     * Change the current frame layout.
+     *
+     * \param layout the new layout (may be null)
+     */
+    inline void set_layout(std::shared_ptr<mlk::layout> layout) noexcept
+    {
+        m_layout = std::move(layout);
+    }
+
+    /**
+     * Get the current frame position.
+     *
+     * \return the frame position
+     */
+    inline const point& position() const noexcept
+    {
+        return m_position;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the position
+     */
+    inline point& position() noexcept
+    {
+        return m_position;
+    }
+
+    /**
+     * Get the frame padding.
+     *
+     * \return the frame padding
+     */
+    inline const mlk::size& padding() const noexcept
+    {
+        return m_padding;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the padding
+     */
+    inline mlk::size& padding() noexcept
+    {
+        return m_padding;
+    }
+
+    /**
+     * Set the frame padding.
+     *
+     * \param padding the padding
+     */
+    inline void set_padding(mlk::size padding) noexcept
+    {
+        m_padding = std::move(padding);
+    }
+
+    /**
+     * Compute the total frame size.
+     *
+     * If no layout is attached, the frame returns a null size. Otherwise, the
+     * default implementation returns `layout()->size()`.
+     *
+     * \param win the window
+     * \return the size required to draw this frame
+     */
+    virtual mlk::size size(window& win);
+
+    /**
+     * Draw the frame and its content.
+     *
+     * If no layout is attached, the function is a no-op. Otherwise the default
+     * implementation calls `win.theme().draw_frame(*this)`.
+     *
+     * \param win the window
+     */
+    virtual void draw(window& win);
+
+    /**
+     * Move the frame to somewhere else.
+     *
+     * \param pos the new position
+     */
+    virtual void move(point pos);
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_FRAME_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/grid_layout.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,112 @@
+/*
+ * grid_layout.cpp -- basic grid layout
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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 <algorithm>
+#include <numeric>
+
+#include "grid_layout.hpp"
+#include "size.hpp"
+#include "widget.hpp"
+
+namespace mlk {
+
+unsigned grid_layout::index(unsigned r, unsigned c) const noexcept
+{
+    return r * m_cols + c;
+}
+
+void grid_layout::update_rsize(window& win) noexcept
+{
+    for (unsigned r = 0; r < m_rows; ++r) {
+        m_min_rsize[r] = 0;
+
+        for (unsigned c = 0; c < m_cols; ++c) {
+            auto idx = index(r, c);
+
+            if (!m_widgets[idx]) {
+                continue;
+            }
+
+            auto size = m_widgets[index(r, c)]->size(win);
+
+            if (size.height() > m_min_rsize[r]) {
+                m_min_rsize[r] = size.height();
+            }
+        }
+    }
+}
+
+void grid_layout::update_csize(window& win) noexcept
+{
+    for (unsigned c = 0; c < m_cols; ++c) {
+        m_min_csize[c] = 0;
+
+        for (unsigned r = 0; r < m_rows; ++r) {
+            auto idx = index(r, c);
+
+            if (!m_widgets[idx]) {
+                continue;
+            }
+
+            auto size = m_widgets[index(r, c)]->size(win);
+
+            if (size.width() > m_min_csize[c]) {
+                m_min_csize[c] = size.width();
+            }
+        }
+    }
+}
+
+grid_layout::grid_layout(unsigned rows, unsigned columns)
+    : m_rows(rows)
+    , m_cols(columns)
+{
+    m_widgets.resize(rows * columns);
+    m_min_rsize.resize(rows);
+    m_min_csize.resize(columns);
+}
+
+void grid_layout::add_widget(unsigned r, unsigned c, std::shared_ptr<widget> w)
+{
+    m_widgets[index(r, c)] = std::move(w);
+}
+
+void grid_layout::remove_widget(unsigned r, unsigned c)
+{
+    m_widgets[index(r, c)] = nullptr;
+}
+
+void grid_layout::remove_widget(std::shared_ptr<widget> w)
+{
+    m_widgets.erase(std::remove(m_widgets.begin(), m_widgets.end(), w), m_widgets.end());
+}
+
+mlk::size grid_layout::size(window& win) const noexcept
+{
+    return {
+        std::accumulate(m_min_csize.begin(), m_min_csize.end(), 0) + (m_rows * 10),
+        std::accumulate(m_min_rsize.begin(), m_min_rsize.end(), 0) + (m_cols * 10)
+    };
+}
+
+void grid_layout::draw(window &win, const rectangle &dst)
+{
+
+}
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/grid_layout.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,70 @@
+/*
+ * grid_layout.hpp -- basic grid layout
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_GRID_LAYOUT_HPP
+#define MALIKANIA_CLIENT_GRID_LAYOUT_HPP
+
+#include <memory>
+#include <vector>
+
+#include "layout.hpp"
+
+namespace mlk {
+
+class widget;
+
+class grid_layout : public layout {
+private:
+    // Dimensions.
+    unsigned m_rows;
+    unsigned m_cols;
+
+    // Widgets.
+    std::vector<std::shared_ptr<widget>> m_widgets;
+
+    /*
+     * Track maximum size by rows/columns to determine the minimum whole
+     * layout size.
+     *
+     * It's only used in draw() but kept as member variables to avoid useless
+     * reallocation on each frame.
+     */
+    std::vector<unsigned> m_min_rsize;
+    std::vector<unsigned> m_min_csize;
+
+    unsigned index(unsigned, unsigned) const noexcept;
+    void update_rsize(window&) noexcept;
+    void update_csize(window&) noexcept;
+
+public:
+    grid_layout(unsigned rows, unsigned columns);
+
+    void add_widget(unsigned r, unsigned c, std::shared_ptr<widget> w);
+
+    void remove_widget(unsigned r, unsigned c);
+
+    void remove_widget(std::shared_ptr<widget> w);
+
+    mlk::size size(window& win) const noexcept override;
+
+    void draw(window& win, const rectangle& dst) override;
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_GRID_LAYOUT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/key.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,244 @@
+/*
+ * key.hpp -- key definitions
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_KEY_HPP
+#define MALIKANIA_CLIENT_KEY_HPP
+
+namespace mlk {
+
+/**
+ * \brief Describe keyboard modifiers
+ *
+ * This enumeration is a satisfied the Bitmask concept.
+ */
+enum class mod {
+    none            = 0,                //!< no modifiers
+    left_control    = (1 << 1),         //!< left control
+    left_alt        = (1 << 2),         //!< left alt
+    left_shift      = (1 << 3),         //!< left shift
+    left_super      = (1 << 4),         //!< left logo
+    right_control   = (1 << 5),         //!< right control
+    right_alt       = (1 << 6),         //!< right alt
+    right_shift     = (1 << 7),         //!< right shift
+    right_super     = (1 << 8),         //!< right logo
+    num_lock        = (1 << 9),         //!< num lock is on
+    caps_lock       = (1 << 10),        //!< caps lock is on
+    control         = left_control |    //!< left or right control
+                      right_control,
+    shift           = left_shift |      //!< left or right shift
+                      right_shift,
+    alt             = left_alt |        //!< left or right alt
+                      right_alt,
+    super           = left_super |      //!< left or right super
+                      right_super
+};
+
+/**
+ * \cond ENUM_HIDDEN_SYMBOLS
+ */
+
+inline mod operator^(mod v1, mod v2) noexcept
+{
+    return static_cast<mod>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
+}
+
+inline mod operator&(mod v1, mod v2) noexcept
+{
+    return static_cast<mod>(static_cast<unsigned>(v1)&  static_cast<unsigned>(v2));
+}
+
+inline mod operator|(mod v1, mod v2) noexcept
+{
+    return static_cast<mod>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
+}
+
+inline mod operator~(mod v) noexcept
+{
+    return static_cast<mod>(~static_cast<unsigned>(v));
+}
+
+inline mod& operator|=(mod& v1, mod v2) noexcept
+{
+    v1 = static_cast<mod>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
+
+    return v1;
+}
+
+inline mod& operator&=(mod& v1, mod v2) noexcept
+{
+    v1 = static_cast<mod>(static_cast<unsigned>(v1)&  static_cast<unsigned>(v2));
+
+    return v1;
+}
+
+inline mod& operator^=(mod& v1, mod v2) noexcept
+{
+    v1 = static_cast<mod>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
+
+    return v1;
+}
+
+/**
+ * \endcond
+ */
+
+/**
+ * \brief Key description.
+ */
+enum class key {
+    unknown,
+    a,
+    b,
+    c,
+    d,
+    e,
+    f,
+    g,
+    h,
+    i,
+    j,
+    k,
+    l,
+    m,
+    n,
+    o,
+    p,
+    q,
+    r,
+    s,
+    t,
+    u,
+    v,
+    w,
+    x,
+    y,
+    z,
+    exclaim,
+    double_quote,
+    percent,
+    dollar,
+    ampersand,
+    left_parenthese,
+    right_parenthese,
+    asterisk,
+    plus,
+    colon,
+    less,
+    greater,
+    question,
+    at,
+    caret,
+    underscore,
+    back_quote,
+    quote,
+    one,
+    two,
+    three,
+    four,
+    five,
+    six,
+    seven,
+    eight,
+    nine,
+    zero,
+    enter,
+    escape,
+    backspace,
+    tab,
+    space,
+    minus,
+    equals,
+    left_bracket,
+    right_bracket,
+    backslash,
+    hash,
+    semicolon,
+    apostrophe,
+    grave,
+    comma,
+    period,
+    slash,
+    caps_lock,
+    f1,
+    f2,
+    f3,
+    f4,
+    f5,
+    f6,
+    f7,
+    f8,
+    f9,
+    f10,
+    f11,
+    f12,
+    f13,
+    f14,
+    f15,
+    f16,
+    f17,
+    f18,
+    f19,
+    f20,
+    f21,
+    f22,
+    f23,
+    f24,
+    print_screen,
+    scroll_lock,
+    pause,
+    insert,
+    home,
+    page_up,
+    page_down,
+    del,
+    end,
+    right,
+    left,
+    down,
+    up,
+    kp_divide,
+    kp_multiply,
+    kp_minus,
+    kp_plus,
+    kp_enter,
+    kp_one,
+    kp_two,
+    kp_three,
+    kp_four,
+    kp_five,
+    kp_six,
+    kp_seven,
+    kp_eight,
+    kp_nine,
+    kp_zero,
+    mute,
+    volume_up,
+    volume_down,
+    left_control,
+    left_alt,
+    left_shift,
+    left_super,
+    right_control,
+    right_alt,
+    right_shift,
+    right_super
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_KEY_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/layout.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,77 @@
+/*
+ * layout.hpp -- generic layout manager inside a frame
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_LAYOUT_HPP
+#define MALIKANIA_CLIENT_LAYOUT_HPP
+
+/**
+ * \brief Generic layout manager inside a frame.
+ */
+
+namespace mlk {
+
+class mouse_click_event;
+class rectangle;
+class size;
+class window;
+
+/**
+ * \brief Manages the widgets inside a frame.
+ *
+ * Layouts are responsible of positioning the widgets inside a frame. They
+ * handle their positions and dispatch events if applicable.
+ *
+ * \see grid_layout
+ */
+class layout {
+public:
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~layout() noexcept = default;
+
+    /**
+     * Handle mouse down.
+     *
+     * \param ev the mouse event
+     */
+    virtual void handle_mouse_down(const mouse_click_event& ev) = 0;
+
+    /**
+     * Handle mouse up.
+     *
+     * \param ev the event
+     */
+    virtual void handle_mouse_up(const mouse_click_event& ev) = 0;
+
+    /**
+     * Return the required minimum size to draw the whole layout.
+     */
+    virtual mlk::size size(window& win) const = 0;
+
+    /**
+     * Draw the layout content at the given position.
+     *
+     * \param win the window
+     */
+    virtual void draw(window& win, const rectangle& dst) = 0;
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_LAYOUT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/mouse.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,41 @@
+/*
+ * mouse.hpp -- mouse definitions
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_MOUSE_HPP
+#define MALIKANIA_CLIENT_MOUSE_HPP
+
+/**
+ * \file mouse.hpp
+ * \brief Mouse definitions.
+ */
+
+namespace mlk {
+
+/**
+ * \brief Describe mouse button
+ */
+enum class mouse {
+    none,                           //!< no buttons are pressed
+    left,                           //!< left click is pressed
+    right,                          //!< right click is pressed
+    middle                          //!< middle click is pressed
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_MOUSE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/theme.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,104 @@
+/*
+ * theme.cpp -- theming support for gui elements
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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 "button.hpp"
+#include "color.hpp"
+#include "frame.hpp"
+#include "layout.hpp"
+#include "point.hpp"
+#include "rectangle.hpp"
+#include "theme.hpp"
+#include "window.hpp"
+
+namespace mlk {
+
+namespace {
+
+#include <dejavu_sans.cpp>
+
+} // !namespace
+
+theme::theme()
+    : m_font(std::string(dejavu_sans, sizeof (dejavu_sans)), 10)
+    , m_background_color(228, 228, 228, 255)
+    , m_border_color(102, 102, 102, 255)
+    , m_text_color(50, 50, 50, 255)
+    , m_frame_padding(10, 10)
+{
+}
+
+size theme::size_button(const button &b)
+{
+    auto clip = m_font.clip(b.text());
+
+    return {
+        clip.width() + 12,
+        clip.height() + 12
+    };
+}
+
+void theme::draw_frame(window& win, const frame &f)
+{
+    auto layout_size = f.layout()->size(win);
+    auto frame_pos = f.position();
+
+    // Frame border.
+    win.set_drawing_color(m_border_color);
+    win.draw_rectangle({
+        frame_pos.x(),
+        frame_pos.y(),
+        layout_size.width() + m_frame_padding.width() * 2 + 2,
+        layout_size.height() + m_frame_padding.height() * 2 + 2
+    });
+
+    // Frame content.
+    win.set_drawing_color(m_background_color);
+    win.fill_rectangle({
+        frame_pos.x() + 1,
+        frame_pos.y() + 1,
+        layout_size.width() + m_frame_padding.width() * 2,
+        layout_size.height() + m_frame_padding.height() * 2
+    });
+
+    // Layout.
+    f.layout()->draw(win, mlk::rectangle(
+        frame_pos.x() + m_frame_padding.width() + 1,
+        frame_pos.y() + m_frame_padding.height() + 1,
+        layout_size.width(),
+        layout_size.height()
+    ));
+}
+
+void theme::draw_button(window& w, const button& b, const rectangle& rect)
+{
+    auto size = size_button(b);
+
+    // Border.
+    w.set_drawing_color(m_border_color);
+    w.draw_rectangle({rect.x(), rect.y(), size.width(), size.height()});
+
+    // Box content.
+    w.set_drawing_color(m_background_color);
+    w.fill_rectangle({rect.x() + 1, rect.y() + 1, size.width() - 2, size.height() - 2});
+
+    // Text.
+    w.set_drawing_color(m_text_color);
+    w.draw_text(b.text(), m_font, point{rect.x() + 5, rect.y() + 5});
+}
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/theme.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,103 @@
+/*
+ * theme.hpp -- theming support for gui elements
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_THEME_HPP
+#define MALIKANIA_CLIENT_THEME_HPP
+
+/**
+ * \file theme.hpp
+ * \brief Theming support for gui elements.
+ */
+
+#include "color.hpp"
+#include "font.hpp"
+#include "size.hpp"
+
+namespace mlk {
+
+class button;
+class frame;
+class rectangle;
+class point;
+class window;
+
+/**
+ * \brief Reimplement this class to provide your own theme.
+ */
+class theme {
+private:
+    font m_font;
+    color m_background_color;
+    color m_border_color;
+    color m_text_color;
+    size m_frame_padding;
+
+public:
+    /**
+     * Default constructor.
+     */
+    theme();
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~theme() noexcept = default;
+
+    inline const color& background_color() const noexcept
+    {
+        return m_background_color;
+    }
+
+    inline const color& border_color() const noexcept
+    {
+        return m_border_color;
+    }
+
+    inline const color& text_color() const noexcept
+    {
+        return m_text_color;
+    }
+
+    /**
+     * Get the required bounding rectangle size for this button.
+     *
+     * \param b the button
+     * \return the size
+     */
+    virtual size size_button(const button& b);
+
+    /**
+     * Draw the frame.
+     *
+     * \param w the main window
+     * \param f the frame
+     */
+    virtual void draw_frame(window& w, const frame& f);
+
+    /**
+     * Draw a button.
+     *
+     * \param w the main window
+     * \param b the button
+     */
+    virtual void draw_button(window& w, const button& b, const rectangle& rect);
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_THEME_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/unique_layout.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,58 @@
+/*
+ * unique_layout.cpp -- basic layout with one widget
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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 "size.hpp"
+#include "unique_layout.hpp"
+#include "widget.hpp"
+
+namespace mlk {
+
+unique_layout::unique_layout(std::shared_ptr<mlk::widget> widget) noexcept
+    : m_widget(std::move(widget))
+{
+}
+
+void unique_layout::handle_mouse_down(const mouse_click_event& ev)
+{
+    if (m_widget) {
+        m_widget->handle_mouse_down(ev);
+    }
+}
+
+void unique_layout::handle_mouse_up(const mouse_click_event& ev)
+{
+    if (m_widget) {
+        m_widget->handle_mouse_up(ev);
+    }
+}
+
+mlk::size unique_layout::size(window& win) const
+{
+    if (m_widget) {
+        return m_widget->size(win);
+    }
+}
+
+void unique_layout::draw(window& win, const rectangle& dst)
+{
+    if (m_widget) {
+        m_widget->draw(win, dst);
+    }
+}
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/unique_layout.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,103 @@
+/*
+ * unique_layout.hpp -- basic layout with one widget
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_UNIQUE_LAYOUT_HPP
+#define MALIKANIA_CLIENT_UNIQUE_LAYOUT_HPP
+
+/**
+ * \file unique_layout.hpp
+ * \brief Basic layout with one widget.
+ */
+
+#include <memory>
+
+#include "layout.hpp"
+
+namespace mlk {
+
+class widget;
+
+/**
+ * \brief Simple layout that consists of one widget.
+ */
+class unique_layout : public layout {
+private:
+    std::shared_ptr<mlk::widget> m_widget;
+
+public:
+    /**
+     * Constructor with optional widget.
+     *
+     * \param widget the widget
+     */
+    unique_layout(std::shared_ptr<mlk::widget> w = nullptr) noexcept;
+
+    /**
+     * Get the handled widget.
+     *
+     * \return the widget
+     */
+    inline const std::shared_ptr<mlk::widget>& widget() const noexcept
+    {
+        return m_widget;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the widget
+     */
+    inline std::shared_ptr<mlk::widget>& widget() noexcept
+    {
+        return m_widget;
+    }
+
+    /**
+     * Replace the current widget with this one.
+     *
+     * \param widget the widget
+     */
+    inline void set_widget(std::shared_ptr<mlk::widget> widget) noexcept
+    {
+        m_widget = std::move(widget);
+    }
+
+    /**
+     * \copydoc layout::handle_mouse_down
+     */
+    void handle_mouse_down(const mouse_click_event& ev) override;
+
+    /**
+     * \copydoc layout::handle_mouse_up
+     */
+    void handle_mouse_up(const mouse_click_event& ev) override;
+
+    /**
+     * \copydoc layout::size
+     */
+    mlk::size size(window& win) const override;
+
+    /**
+     * \copydoc layout::draw
+     */
+    void draw(window& win, const rectangle& dst) override;
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_UNIQUE_LAYOUT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/widget.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,53 @@
+/*
+ * widget.cpp -- basic abstract widget for GUI
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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 "point.hpp"
+#include "rectangle.hpp"
+#include "size.hpp"
+#include "widget.hpp"
+
+namespace mlk {
+
+void widget::handle_key_down(const key_event&)
+{
+}
+
+void widget::handle_key_up(const key_event&)
+{
+}
+
+void widget::handle_mouse_down(const mouse_click_event&)
+{
+}
+
+void widget::handle_mouse_up(const mouse_click_event&)
+{
+}
+
+void widget::draw(window& w, const point& pos)
+{
+    mlk::size sz = size(w);
+    mlk::rectangle rect{
+        pos.x(), pos.y(),
+        sz.width(), sz.height()
+    };
+
+    draw(w, rect);
+}
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libclient/malikania/widget.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -0,0 +1,103 @@
+/*
+ * widget.hpp -- basic abstract widget for GUI
+ *
+ * Copyright (c) 2013-2016 Malikania Authors
+ *
+ * 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_WIDGET_HPP
+#define MALIKANIA_CLIENT_WIDGET_HPP
+
+/**
+ * \file widget.hpp
+ * \brief Basic abstract widget for GUI
+ */
+
+namespace mlk {
+
+class key_event;
+class mouse_click_event;
+class point;
+class rectangle;
+class size;
+class window;
+
+/**
+ * \brief Abstract widget
+ */
+class widget {
+public:
+    /**
+     * Default destructor.
+     */
+    virtual ~widget() noexcept = default;
+
+    /**
+     * Handle a key down.
+     *
+     * \param ev the event
+     */
+    virtual void handle_key_down(const key_event& ev);
+
+    /**
+     * Handle a key up.
+     *
+     * \param ev the event
+     */
+    virtual void handle_key_up(const key_event& ev);
+
+    /**
+     * Handle mouse down event.
+     *
+     * \param ev the event
+     */
+    virtual void handle_mouse_down(const mouse_click_event& ev);
+
+    /**
+     * Handle a mouse up event.
+     *
+     * \param ev the event
+     */
+    virtual void handle_mouse_up(const mouse_click_event& ev);
+
+    /**
+     * Draw the widget at the specified position.
+     *
+     * Basic GUI elements will use the window theme to render themselves.
+     *
+     * \param w the window
+     */
+    virtual void draw(window& w, const rectangle& rect) = 0;
+
+    /**
+     * Helper to draw the whole widget at the given position.
+     *
+     * Effectively call draw with a rectangle overload.
+     *
+     * \param w the window
+     * \param pos the position
+     */
+    virtual void draw(window& w, const point& pos);
+
+    /**
+     * Get the required size to draw that widget.
+     *
+     * \return the widget size
+     */
+    virtual mlk::size size(window& w) const noexcept = 0;
+};
+
+} // !mlk
+
+#endif // !MALIKANIA_CLIENT_WIDGET_HPP
--- a/libclient/malikania/window.cpp	Thu Dec 15 13:43:09 2016 +0100
+++ b/libclient/malikania/window.cpp	Thu Dec 15 13:46:46 2016 +0100
@@ -16,15 +16,33 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <iostream>
 #include <stdexcept>
 
 #include "color.hpp"
+#include "frame.hpp"
+#include "layout.hpp"
+#include "theme.hpp"
+#include "widget.hpp"
 #include "window_backend.hpp"
 
 namespace mlk {
 
+bool window::is_frame_bound(frame& f, const point& ev_pos) noexcept
+{
+    auto frame_size = f.size(*this);
+    auto frame_padding = f.padding();
+    auto frame_pos = f.position();
+
+    return ev_pos.x() >= frame_pos.x() + static_cast<int>(frame_padding.width()) &&
+           ev_pos.y() >= frame_pos.y() + static_cast<int>(frame_padding.height()) &&
+           ev_pos.x() <= frame_pos.x() + static_cast<int>(frame_padding.width()  + frame_size.width()) &&
+           ev_pos.y() <= frame_pos.y() + static_cast<int>(frame_padding.height() + frame_size.height());
+}
+
 window::window(unsigned width, unsigned height, const std::string& title)
     : m_backend(std::make_unique<backend_impl>(*this, width, height, title))
+    , m_theme(std::make_unique<mlk::theme>())
 {
 }
 
@@ -32,11 +50,6 @@
 
 window::~window() noexcept = default;
 
-void window::poll()
-{
-    m_backend->poll(*this);
-}
-
 void window::clear()
 {
     m_backend->clear();
@@ -93,6 +106,13 @@
     m_backend->draw_rectangles(rectangles);
 }
 
+void window::draw_frames()
+{
+    for (const auto& f : m_frames) {
+        f->draw(*this);
+    }
+}
+
 void window::fill_rectangle(const rectangle& rectangle)
 {
     m_backend->fill_rectangle(rectangle);
@@ -113,6 +133,44 @@
     m_backend->draw_text(text, font, point);
 }
 
+void window::poll()
+{
+    m_backend->poll(*this);
+}
+
+void window::handle_key_down(const key_event& ev)
+{
+}
+
+void window::handle_key_up(const key_event&)
+{
+}
+
+void window::handle_mouse_down(const mouse_click_event& ev)
+{
+    for (const auto& f : m_frames) {
+        if (is_frame_bound(*f, ev.pos)) {
+            f->layout()->handle_mouse_down({
+                ev.button,
+                point(ev.pos.x() - f->position().x(), ev.pos.y() - f->position().y())
+            });
+        }
+    }
+}
+
+void window::handle_mouse_up(const mouse_click_event& ev)
+{
+}
+
+void window::handle_mouse_wheel(const mouse_wheel_event &ev)
+{
+}
+
+void window::handle_quit()
+{
+    m_is_open = false;
+}
+
 window& window::operator=(window&&) noexcept = default;
 
 } // !mlk
--- a/libclient/malikania/window.hpp	Thu Dec 15 13:43:09 2016 +0100
+++ b/libclient/malikania/window.hpp	Thu Dec 15 13:46:46 2016 +0100
@@ -28,14 +28,55 @@
 #include <memory>
 #include <vector>
 #include <string>
+#include <unordered_set>
+
+#include "key.hpp"
+#include "mouse.hpp"
+#include "point.hpp"
 
 namespace mlk {
 
 class color;
+class frame;
 class line;
 class font;
-class point;
 class rectangle;
+class theme;
+
+/**
+ * \brief Describe key event.
+ */
+class key_event {
+public:
+    mlk::key key{key::unknown};         //!< layout-dependant key
+    mlk::key scancode{key::unknown};    //!< physical key
+    mlk::mod modifiers{mod::none};      //!< optional modifiers
+};
+
+/**
+ * \brief Describe mouse click event.
+ */
+class mouse_click_event {
+public:
+    mouse button{mouse::none};          //!< which mouse button
+    point pos;                          //!< current mouse position
+};
+
+/**
+ * \brief Describe a mouse motion event.
+ */
+class mouse_motion_event {
+public:
+    point pos;                          //!< current mouse position
+};
+
+/**
+ * \brief Describe mouse wheel (up/down) event.
+ */
+class mouse_wheel_event {
+public:
+    point pos;                          //!< current mouse position
+};
 
 /**
  * \brief Main window class and drawing.
@@ -45,9 +86,13 @@
     class backend_impl;
 
     std::unique_ptr<backend_impl> m_backend;
+    std::unique_ptr<mlk::theme> m_theme;
+    std::unordered_set<std::shared_ptr<frame>> m_frames;
 
     bool m_is_open{true};
 
+    bool is_frame_bound(frame&, const point&) noexcept;
+
 public:
     /**
      * Create a window.
@@ -57,7 +102,9 @@
      * \param title the optional title
      * \throw std::runtime_error on errors
      */
-    window(unsigned width = 640, unsigned height = 480, const std::string& title = "Malikania Engine");
+    window(unsigned width = 640,
+           unsigned height = 480,
+           const std::string& title = "Malikania Engine");
 
     /**
      * Move constructor defaulted.
@@ -99,10 +146,35 @@
         return *m_backend;
     }
 
+    void add_frame(std::shared_ptr<mlk::frame> frame)
+    {
+        m_frames.insert(std::move(frame));
+    }
+
+    void remove_frame(std::shared_ptr<mlk::frame> frame)
+    {
+        m_frames.erase(frame);
+    }
+
     /**
-     * Poll pending events.
+     * Get the current theme.
+     *
+     * \return the theme
      */
-    void poll();
+    inline const mlk::theme& theme() const noexcept
+    {
+        return *m_theme;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the theme
+     */
+    inline mlk::theme& theme() noexcept
+    {
+        return *m_theme;
+    }
 
     /**
      * Clear the window content with the current drawing color.
@@ -178,6 +250,11 @@
     void draw_rectangles(const std::vector<rectangle>& rects);
 
     /**
+     * Draw internal GUI frames.
+     */
+    void draw_frames();
+
+    /**
      * Fill the given rectangle with the current color.
      *
      * \param rect the rectangle
@@ -216,6 +293,51 @@
     void draw_text(const std::string& text, font& font, const point& point);
 
     /**
+     * Poll all pending events and call appropriate functions.
+     */
+    void poll();
+
+    /**
+     * Key down event.
+     *
+     * \param ev the event
+     */
+    virtual void handle_key_down(const key_event& ev);
+
+    /**
+     * Key released event.
+     *
+     * \param ev the event
+     */
+    virtual void handle_key_up(const key_event& ev);
+
+    /**
+     * Mouse click event.
+     *
+     * \param ev the event
+     */
+    virtual void handle_mouse_down(const mouse_click_event& ev);
+
+    /**
+     * Mouse click release event.
+     *
+     * \param ev the event
+     */
+    virtual void handle_mouse_up(const mouse_click_event& ev);
+
+    /**
+     * Mouse wheel event.
+     *
+     * \param ev the event
+     */
+    virtual void handle_mouse_wheel(const mouse_wheel_event& ev);
+
+    /**
+     * Quit request.
+     */
+    virtual void handle_quit();
+
+    /**
      * Move assigment operator defaulted.
      *
      * \return this