From ae465d0d1d09720aab17de4769bbd349073f1fcf Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Fri, 1 Oct 2010 15:32:13 +0000
Subject: [PATCH] Started creating better UI base classes, all in engine.ui.*
 They should be easier to use, saner, faster and look better

git-svn-id: http://svn.net-core.org/repos/t-engine4@1349 51575b47-30f0-44d4-a5cc-537603b46e54
---
 .../default/data/gfx/ui/button-left-sel.png   | Bin 0 -> 408 bytes
 .../default/data/gfx/ui/button-left.png       | Bin 0 -> 396 bytes
 .../default/data/gfx/ui/button-middle-sel.png | Bin 0 -> 238 bytes
 .../default/data/gfx/ui/button-middle.png     | Bin 0 -> 233 bytes
 .../default/data/gfx/ui/button-right-sel.png  | Bin 0 -> 427 bytes
 .../default/data/gfx/ui/button-right.png      | Bin 0 -> 434 bytes
 .../data/gfx/ui/selection-left-sel.png        | Bin 0 -> 375 bytes
 .../data/gfx/ui/selection-middle-sel.png      | Bin 0 -> 241 bytes
 .../data/gfx/ui/selection-right-sel.png       | Bin 0 -> 380 bytes
 game/engines/default/engine/KeyBind.lua       |   6 +-
 game/engines/default/engine/KeyCommand.lua    |  10 +-
 game/engines/default/engine/ui/Base.lua       |  53 +++++
 game/engines/default/engine/ui/Button.lua     |  75 +++++++
 game/engines/default/engine/ui/Dialog.lua     | 185 ++++++++++++++++++
 game/engines/default/engine/ui/Focusable.lua  |  29 +++
 game/engines/default/engine/ui/List.lua       |  75 +++++++
 game/engines/default/engine/utils.lua         |   3 +-
 .../default/special/mainmenu/class/TestUI.lua |  71 +++++++
 18 files changed, 501 insertions(+), 6 deletions(-)
 create mode 100644 game/engines/default/data/gfx/ui/button-left-sel.png
 create mode 100644 game/engines/default/data/gfx/ui/button-left.png
 create mode 100644 game/engines/default/data/gfx/ui/button-middle-sel.png
 create mode 100644 game/engines/default/data/gfx/ui/button-middle.png
 create mode 100644 game/engines/default/data/gfx/ui/button-right-sel.png
 create mode 100644 game/engines/default/data/gfx/ui/button-right.png
 create mode 100644 game/engines/default/data/gfx/ui/selection-left-sel.png
 create mode 100644 game/engines/default/data/gfx/ui/selection-middle-sel.png
 create mode 100644 game/engines/default/data/gfx/ui/selection-right-sel.png
 create mode 100644 game/engines/default/engine/ui/Base.lua
 create mode 100644 game/engines/default/engine/ui/Button.lua
 create mode 100644 game/engines/default/engine/ui/Dialog.lua
 create mode 100644 game/engines/default/engine/ui/Focusable.lua
 create mode 100644 game/engines/default/engine/ui/List.lua
 create mode 100644 game/engines/default/special/mainmenu/class/TestUI.lua

diff --git a/game/engines/default/data/gfx/ui/button-left-sel.png b/game/engines/default/data/gfx/ui/button-left-sel.png
new file mode 100644
index 0000000000000000000000000000000000000000..647e3e36c00305138552219ee38885bc5ec880b3
GIT binary patch
literal 408
zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol*0U~b-^ehHajKx9jP7LeL$-D%~CV9KNFqkvE
zWw^Tc;4Vj?2xoyuWHAE+w=f7ZGR&GI0Tg5}@$_|Nzs1GKZOr~!{cAi>NV3E=qQp5r
zH#aq}gu%HeHL)Z$MWH;iBtya7(>EZzkxv|`=(DGbV~EE2(n+>{%#J**_WqlcVqIE-
zE?ijZkhD~xwM|L<0h^|h)T}w}ZES_Ix9&7bt>rNOJ=u_Tv-EqVI}ug$kH3$UIvBF;
z=9&iC4O3Nm8pR)Ml$Bb3uk-o#>oIrqCw}=O``}aJ*_yphm%C*Td`Ua_M(_EH(|kFM
z%YHV!@NSdyd1gE-fT=5@fJbx@7t;z>JB77fa~!^?x@})Q>0?2tk3;E%RcHUJ=Ls{t
z)qA;o)vnBq&+cAbRQvhW<^#Uxf;4O!cl}U$T)wM5Av9Q=J!Tr?g9dZ%*@w-0zgs+*
yndq7-^8I&z;JkQ7@6$1_{12>V{dE8PPsV#L@!kHc6+406XYh3Ob6Mw<&;$T+Y@l!e

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/button-left.png b/game/engines/default/data/gfx/ui/button-left.png
new file mode 100644
index 0000000000000000000000000000000000000000..872652d716dce2232c9fd4842ee9070eedcd0068
GIT binary patch
literal 396
zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol*0U~b-^ehHajKx9jP7LeL$-D%~CV9KNFqkvE
zWw^Tc;4Vj?2xoyuWHAE+cP9ulnx8zq7AVMG;_2(kev6Bd+eCRrYP%;;NV3E=qQp5r
zH#aq}gu%HeHL)Z$MWH;iBtya7(>EZzkxv|`=((qhV~EE2(n;2S%!UGO`wM*<r)Vjh
zXr92jYKp?e!=6e~v*xt7u|2h#qa=EVVT<)QKGwL?H(N7x&OZOIc-qOZpTGS30kIn%
zQ56qFD_-gSEP3|y=%XpCW+{69<J%Coe#g#v%uW9De3lFAO;2CZma~;{#{BsLCYR19
z*w`(**uW6R#LFn`c*bD~Lo08PmPv!bnF|7wt#{@HY0Pz5vVOxEKI0>o!q!}Wwf*Hn
zr6tQHw*30F&hk^;jEg2-Gv_(pmMWDkILFw=TqbK`oYZsV#-j%De={!D{r_1QdN%Ap
lE@zC@{KoA<C+hD%XV-`bdGJw2C<o|e22WQ%mvv4FO#qlkm>d8A

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/button-middle-sel.png b/game/engines/default/data/gfx/ui/button-middle-sel.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b32a9d0b3e0b784c34e1c1360cddb4befeeb94a
GIT binary patch
literal 238
zcmeAS@N?(olHy`uVBq!ia0vp^j6f{G!3HF)&rH7s5-1LGcVbv~PUa<$!<OXj?!sWs
z@Rs4~-h;awfg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHieMBe${Ex*sdGfI^Zbt`Q~9
z`MJ5Nc_j?aMX8A;sVNHOnI#zt?w-B@;f;LaKt-OOE{-7_*HgP4xf%=vjvQ*p|72LZ
z{rrbLyO&<B3UK)_$=T^fR5Vk9eZOkBd!LIjm!)-)npB|ua}ATt^O92*O6Ko%c0cWQ
bfL*+k+j3#I#JA5tLm50>{an^LB{Ts5=;TO7

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/button-middle.png b/game/engines/default/data/gfx/ui/button-middle.png
new file mode 100644
index 0000000000000000000000000000000000000000..4bcd9226f3682b41641558cc15d5f751dc78fd3b
GIT binary patch
literal 233
zcmeAS@N?(olHy`uVBq!ia0vp^j6f{G!3HF)&rH7s5-1LGcVbv~PUa<$!<OXj?!sWs
z@Rs4~-h;awfg+p*9+AZi4BVX{%xHe{^je@Gdx@v7EBh@jMs8CM-9!E!Kq1Kz*N775
z{M_8syb=cIqSVBa)D(sC%#sWRcTeAd@J2pypduGf7sn8e>#;o>c^eD_j;xXHWE7BO
z-1uLq{yP_|r*rbnUdii*tP7so1ztLlmm+=h=1hg<fmzX(8l0<Mr4|O%HtR6d?`iin
VdHBLhK>%nTgQu&X%Q~loCIG16MbrQQ

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/button-right-sel.png b/game/engines/default/data/gfx/ui/button-right-sel.png
new file mode 100644
index 0000000000000000000000000000000000000000..c504f1063aa2791b584275a46bae07dc7d0a2a05
GIT binary patch
literal 427
zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol*0U~b-^ehHajKx9jP7LeL$-D%~CV9KNFqkvE
zWw^Tc;4Vj?2xoyuWHAE+w=f7ZGR&GI0Tg5}@$_|Nzs1GKZ7h+d|6>(UNV3E=qQp5r
zH#aq}gu%HeHL)Z$MWH;iBtya7(>EZzkxv|G6`QAvV~EE2+&){s!wv$i`!||T`J)lj
zz%*e(K_iPylk$#5p3N41vrNQ}-eag*(d;tOSU|<4HDFJ6db#GCqkShQFRQ#?Z9KW}
zlg*OIvMFgdcgO2Y+cGI|9=nz3fhE_f=E~fEE)z4ywSfB!!;?>sbp;-LemeE(<6@o|
z)pc7ek|pEQ-^`uwQZU`(GDE)3@0*z{=BH+IWxRXeQ?6_gl9wvbxngqBGN&kO5BVu<
z5BE<o7L#1UI7MH#eyTx6&g7`=t7kb$tm!&-Ed1COzXR3x>&@>8%(`%Fx06CZ{eK=Q
zrHbf>tF3;;XYDl-a5A1dPh@Yit?y*rw7_gz`6i|r3@ffxFf7}8?emNutV{uQF5bR3
RfWgYZ;OXk;vd$@?2>@i6pl|>H

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/button-right.png b/game/engines/default/data/gfx/ui/button-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..6a7c2386566c5bf75d208ae6d98da69e50d011eb
GIT binary patch
literal 434
zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol*0U~b-^ehHajKx9jP7LeL$-D%~CV9KNFqkvE
zWw^Tc;4Vj?2xoyuWHAE+cP9ulnx8zq7AVMG;_2(kev6Bd+r;eaj^sr^A;}Wgh!W@g
z+}zZ>5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+%RlJ@qjv*T7b0==}Vs;d9J->!oEK^xx
zveIv6G0inDlWwiw;F<hzeaD1jYCi<8?XFFb-WWK+<l-^AH|K8Gif!I(eZ*$Qvw7wB
z_Zp~o|Ma>1Qp0!o;=5(PKl@sz8P3=IyQVR9n)swUG4Vz-=dc#A+c3{qew^L6q5r&W
z{`>gEhgyPyS}SKRd)}kp#&}14ozR@m`8MwktSt>n7BMi8n8RkU<<XJVY#qODp1QiP
zyn(TTJwfTJHB0gzS>ebv9WBloPd2SPo6*eI`0Z?!LhgoRR@Yo5d(8qXLYo7xNbGvG
z_ptn}_ox3apCjzDt8M=_9oA4+(|;auxtC33W`-8I#H3elv}DOn;7o{q$QqGaxsg-4
ZUXi2br*w3I9WeA5JYD@<);T3K0RZHysA>QJ

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/selection-left-sel.png b/game/engines/default/data/gfx/ui/selection-left-sel.png
new file mode 100644
index 0000000000000000000000000000000000000000..c867bbee34663d54ab788ee0ac82a1fb9f1bfffa
GIT binary patch
literal 375
zcmeAS@N?(olHy`uVBq!ia0vp^EI=&8!3HF2>gs<0DaPU;cPEB*=VV?2Ic!PZ?k)`G
z3~w2(?mf865h%i0;1OBOz`)%J!i?r8Pp<_EvX^-Jy0YKmV&pbtT3MY8GFP(1HKN2h
zKQ}iuuY|$5C^fMpHASI3vm`^o-P1Q9ypc~FsOXZXi(`nyW!FAKzrzk9F89~$zs?n*
z;n(issWQJoIbiuY*-Otv<}h<{NvUu}ah=jAf57n|_W0|i8<;QemF!JBvgX_4(&t=n
zc@zHr`xB)5+Sl#I+uN7V>903)?qIl4;q}|lWrEsav8%qbf;n|sIVzPHr|q(M&%t8W
z_Jdt=gW{Lx+8l|=;*S4yy%l}j#5^KqX-}Ha(!25a+=G@q7m{5aGu=3Me7RdY)iU_>
z#VK;-zh27KoVs!@;%8Tqg`9o&w!ORaW_^A2XcE%`YZ<xs+izDrto_YxE#=m*%U13-
P(02@;u6{1-oD!M<7ygZq

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/selection-middle-sel.png b/game/engines/default/data/gfx/ui/selection-middle-sel.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b02bfc150883847094dd51aaf451f9509741fbf
GIT binary patch
literal 241
zcmeAS@N?(olHy`uVBq!ia0vp^j6f{J!3HE}g>Tsd5-1LGcVbv~PUa<$!<OXj?!sWs
z@Rs4~-h;awfg+p*9+AZi4BVX{%xHe{^je@Gdx@v7EBh@jMs6d%?guryfI^Zbt`Q~9
z`MJ5Nc_j?aMX8A;sVNHOnI#zt?w-B@;f;LaKt(>DE{-7_*KLn&<ZUqEaXol^xkI3m
zmq7A{|4cduWi~Fh2v8`zT6pjJ;>5l~bMo)X^!_?8)%8l_OhANlgmW=}h}yzh&le|H
eb9XK<RgKHor1GrvjN%EPxeT7JelF{r5}E)r;7#WM

literal 0
HcmV?d00001

diff --git a/game/engines/default/data/gfx/ui/selection-right-sel.png b/game/engines/default/data/gfx/ui/selection-right-sel.png
new file mode 100644
index 0000000000000000000000000000000000000000..180b43056a6d146f5f12825caf842574169bb433
GIT binary patch
literal 380
zcmeAS@N?(olHy`uVBq!ia0vp^EI=&8!3HF2>gs<0DaPU;cPEB*=VV?2Ic!PZ?k)`G
z3~w2(?mf865h%i0;1OBOz`)%J!i?r8Pp<_EvX^-Jy0YKmV&pb3slRo+3n(O6;u=xn
zoS&PUnpeW$T$GwvlA5AWo>`Ki;O^-g5Z=fq4pemA)5S4F;<9P4qZhNIfNOkd_#)1k
z4LeE}igT<}@y=J?abf9p4~6Fi4_RF<aCaP<<G`WR!_9l{%bAkt-RG`+`uqKLahzSf
z&b|405_yqRx2^Gt-eYISaN_9m*}RE+|L(dPVcww7@b#l#uyB!1)BGO)E%{ML7)~ac
zu{A6xT#?drY(GO^4x^Ow3GPYTO|DfpC@^_t7%IG#U%kzili65kfpGJT9zDs0ug}Qt
z6l9%uIAzKGyVqp<kDb|@o+VIMmK|R4^gzqzmv5Fv<z8D?@b7ntS^VAj*m$Nt%$An1
V1|N<Gegt}v!PC{xWt~$(696d(kr)5~

literal 0
HcmV?d00001

diff --git a/game/engines/default/engine/KeyBind.lua b/game/engines/default/engine/KeyBind.lua
index ec0b336bee..4bed9be636 100644
--- a/game/engines/default/engine/KeyBind.lua
+++ b/game/engines/default/engine/KeyBind.lua
@@ -196,16 +196,16 @@ function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode, isup, ismouse)
 	if self.binds[ks] and self.virtuals[self.binds[ks]] then
 		if isup and not _M.binds_def[self.binds[ks]].updown then return end
 		self.virtuals[self.binds[ks]](sym, ctrl, shift, alt, meta, unicode, isup)
-		return
+		return true
 	elseif us and self.binds[us] and self.virtuals[self.binds[us]] then
 		if isup and not _M.binds_def[self.binds[us]].updown then return end
 		self.virtuals[self.binds[us]](sym, ctrl, shift, alt, meta, unicode, isup)
-		return
+		return true
 	end
 
 	if isup then return end
 
-	engine.KeyCommand.receiveKey(self, sym, ctrl, shift, alt, meta, unicode, isup)
+	return engine.KeyCommand.receiveKey(self, sym, ctrl, shift, alt, meta, unicode, isup)
 end
 
 --- Force a key to trigger
diff --git a/game/engines/default/engine/KeyCommand.lua b/game/engines/default/engine/KeyCommand.lua
index 46847b219c..654c81e2f0 100644
--- a/game/engines/default/engine/KeyCommand.lua
+++ b/game/engines/default/engine/KeyCommand.lua
@@ -57,8 +57,10 @@ function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode, isup)
 	-- Convert locale
 	sym = self.locale_convert[sym] or sym
 
+	local handled = false
+
 	if not self.commands[sym] and not self.commands[self.__DEFAULT] then
-		if self.on_input and unicode then self.on_input(unicode) end
+		if self.on_input and unicode then self.on_input(unicode) handled = true end
 	elseif self.commands[sym] and (ctrl or shift or alt or meta) and not self.commands[sym].anymod then
 		local mods = {}
 		if alt then mods[#mods+1] = "alt" end
@@ -68,14 +70,18 @@ function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode, isup)
 		mods = table.concat(mods,',')
 		if self.commands[sym][mods] then
 			self.commands[sym][mods](sym, ctrl, shift, alt, meta, unicode)
+			handled = true
 		end
 	elseif self.commands[sym] and self.commands[sym].plain then
 		self.commands[sym].plain(sym, ctrl, shift, alt, meta, unicode)
+		handled = true
 	elseif self.commands[self.__DEFAULT] and self.commands[self.__DEFAULT].plain then
 		self.commands[self.__DEFAULT].plain(sym, ctrl, shift, alt, meta, unicode)
+		handled = true
 	end
 
-	if self.atLast then self.atLast(sym, ctrl, shift, alt, meta, unicode) end
+	if self.atLast then self.atLast(sym, ctrl, shift, alt, meta, unicode) handled = true  end
+	return handled
 end
 
 --- Adds a key/command combinaison
diff --git a/game/engines/default/engine/ui/Base.lua b/game/engines/default/engine/ui/Base.lua
new file mode 100644
index 0000000000..584ee68258
--- /dev/null
+++ b/game/engines/default/engine/ui/Base.lua
@@ -0,0 +1,53 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local KeyBind = require "engine.KeyBind"
+local Mouse = require "engine.Mouse"
+
+--- A generic UI element
+module(..., package.seeall, class.make)
+
+local gfx_prefix = "/data/gfx/"
+local cache = {}
+
+-- Default font
+_M.font = core.display.newFont("/data/font/Vera.ttf", 12)
+_M.font_h = _M.font:lineSkip()
+
+function _M:init(t, no_gen)
+	self.mouse = Mouse.new()
+	self.key = KeyBind.new()
+
+	if t.font then
+		self.font = core.display.newFont(t.font[1], t.font[2])
+		self.font_h = self.font:lineSkip()
+	end
+
+	if not no_gen then self:generate() end
+end
+
+function _M:getImage(file)
+	if cache[file] then return unpack(cache[file]) end
+	local s = core.display.loadImage(gfx_prefix..file)
+	assert(s, "bad UI image: "..file)
+	s:alpha(true)
+	cache[file] = {s, s:getSize()}
+	return unpack(cache[file])
+end
diff --git a/game/engines/default/engine/ui/Button.lua b/game/engines/default/engine/ui/Button.lua
new file mode 100644
index 0000000000..7436ec7753
--- /dev/null
+++ b/game/engines/default/engine/ui/Button.lua
@@ -0,0 +1,75 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Base = require "engine.ui.Base"
+local Focusable = require "engine.ui.Focusable"
+
+--- A generic UI button
+module(..., package.seeall, class.inherit(Base, Focusable))
+
+function _M:init(t)
+	self.text = assert(t.text, "no button text")
+	self.fct = assert(t.fct, "no button fct")
+
+	Base.init(self, t)
+end
+
+function _M:generate()
+	local ls, ls_w, ls_h = self:getImage("ui/button-left-sel.png")
+	local ms, ms_w, ms_h = self:getImage("ui/button-middle-sel.png")
+	local rs, rs_w, rs_h = self:getImage("ui/button-right-sel.png")
+	local l, l_w, l_h = self:getImage("ui/button-left.png")
+	local m, m_w, m_h = self:getImage("ui/button-middle.png")
+	local r, r_w, r_h = self:getImage("ui/button-right.png")
+
+	-- Draw UI
+	self.font:setStyle("bold")
+	local w, h = self.font:size(self.text:removeColorCodes())
+	local fw, fh = w + ls_w + rs_w, ls_h
+	local ss = core.display.newSurface(fw, fh)
+	local s = core.display.newSurface(fw, fh)
+
+	ss:merge(ls, 0, 0)
+	for i = ls_w, fw - rs_w do ss:merge(ms, i, 0) end
+	ss:merge(rs, fw - rs_w, 0)
+	ss:drawColorStringBlended(self.font, self.text, ls_w, (fh - h) / 2, 255, 255, 255)
+
+	s:merge(l, 0, 0)
+	for i = l_w, fw - r_w do s:merge(m, i, 0) end
+	s:merge(r, fw - r_w, 0)
+	s:drawColorStringBlended(self.font, self.text, ls_w, (fh - h) / 2, 255, 255, 255)
+	self.font:setStyle("normal")
+
+	-- Add UI controls
+	self.mouse:registerZone(0, 0, fw, fh, function(button, x, y, xrel, yrel, bx, by, event) if button == "left" and event == "button" then self.fct() end end)
+	self.key:addBind("ACCEPT", function() self.fct() end)
+
+	self.tex, self.tex_w, self.tex_h = s:glTexture()
+	self.stex = ss:glTexture()
+	self.w, self.h = fw, fh
+end
+
+function _M:display(x, y)
+	if self.focused then
+		self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+	else
+		self.tex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+	end
+end
diff --git a/game/engines/default/engine/ui/Dialog.lua b/game/engines/default/engine/ui/Dialog.lua
new file mode 100644
index 0000000000..2abed07472
--- /dev/null
+++ b/game/engines/default/engine/ui/Dialog.lua
@@ -0,0 +1,185 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local KeyBind = require "engine.KeyBind"
+local Base = require "engine.ui.Base"
+
+--- A generic UI button
+module(..., package.seeall, class.inherit(Base))
+
+function _M:init(title, w, h, x, y, alpha, font, showup)
+	self.title = assert(title, "no dialog title")
+	self.alpha = self.alpha or 200
+	if showup ~= nil then
+		self.__showup = showup
+	else
+		self.__showup = 2
+	end
+
+	self.uis = {}
+	self.focus_ui = nil
+	self.focus_ui_id = 0
+
+	Base.init(self, {}, true)
+
+	self:resize(w, h, x, y, alpha)
+end
+
+function _M:resize(w, h, x, y, alpha)
+	local gamew, gameh = core.display.size()
+	self.w, self.h = math.floor(w), math.floor(h)
+	self.display_x = math.floor(x or (gamew - self.w) / 2)
+	self.display_y = math.floor(y or (gameh - self.h) / 2)
+	self.iw, self.ih = w - 2 * 5, h - 8 - 16 - 3
+	self:generate()
+end
+
+function _M:generate()
+	local gamew, gameh = core.display.size()
+	local s = core.display.newSurface(self.w, self.h)
+	s:alpha(true)
+	s:erase(0, 0, 0, self.alpha)
+
+	local b7, b7_w, b7_h = self:getImage("border_7.png")
+	local b9, b9_w, b9_h = self:getImage("border_9.png")
+	local b1, b1_w, b1_h = self:getImage("border_1.png")
+	local b3, b3_w, b3_h = self:getImage("border_3.png")
+	local b8, b8_w, b8_h = self:getImage("border_8.png")
+	local b4, b4_w, b4_h = self:getImage("border_4.png")
+
+	s:merge(b7, 0, 0)
+	s:merge(b9, self.w - b9_w, 0)
+	s:merge(b1, 0, self.h - b1_h)
+	s:merge(b3, self.w - b9_w, self.h - b3_h)
+
+	for i = b7_w, self.w - b9_w do
+		s:merge(b8, i, 0)
+		s:merge(b8, i, 20)
+		s:merge(b8, i, self.h - 3)
+	end
+	for i = b7_h, self.h - b1_h do
+		s:merge(b4, 0, i)
+		s:merge(b4, self.w - 3, i)
+	end
+
+	self.font:setStyle("bold")
+	local tw, th = self.font:size(self.title:removeColorCodes())
+	s:drawColorStringBlended(self.font, self.title, (self.w - tw) / 2, 4, 255,255,255)
+	self.font:setStyle("normal")
+
+	self.mouse:registerZone(0, 0, gamew, gameh, function() self.key:triggerVirtual("EXIT") end)
+	self.mouse:registerZone(self.display_x, self.display_y, self.w, self.h, function(...) self:mouseEvent(...) end)
+	self.key.receiveKey = function(_, ...) self:keyEvent(...) end
+	self.key:addCommand("__TAB", function(...) self.key:triggerVirtual("MOVE_DOWN") end)
+	self.key:addBinds{
+		MOVE_UP = function() self:setFocus(util.boundWrap(self.focus_ui_id - 1, 1, #self.uis)) end,
+		MOVE_DOWN = function() self:setFocus(util.boundWrap(self.focus_ui_id + 1, 1, #self.uis)) end,
+		MOVE_LEFT = "MOVE_UP",
+		MOVE_RIGHT = "MOVE_DOWN",
+	}
+
+	self.tex, self.tex_w, self.tex_h = s:glTexture()
+end
+
+function _M:loadUI(t)
+	self.uis = {}
+	self.focus_ui = nil
+	self.focus_ui_id = 0
+	for i, ui in ipairs(t) do
+		self.uis[#self.uis+1] = ui
+
+		local ux, uy = 0, 0
+		if ui.top then uy = uy + ui.top
+		elseif ui.bottom then uy = uy + self.h - ui.bottom - ui.ui.h end
+		if ui.left then ux = ux + ui.left
+		elseif ui.right then ux = ux + self.w - ui.right - ui.ui.w end
+		ui.x = ux
+		ui.y = uy
+		ui.ui.mouse.delegate_offset_x = ux
+		ui.ui.mouse.delegate_offset_y = uy
+
+		if not self.focus_ui and ui.ui.can_focus then
+			self:setFocus(i)
+		else
+		ui.ui:setFocus(false)
+		end
+	end
+end
+
+function _M:setFocus(id)
+	if self.focus_ui then self.focus_ui.ui:setFocus(false) end
+
+	local ui = self.uis[id]
+	self.focus_ui = ui
+	self.focus_ui_id = id
+	ui.ui:setFocus(true)
+end
+
+function _M:mouseEvent(button, x, y, xrel, yrel, bx, by, event)
+	-- Look for focus
+	for i = 1, #self.uis do
+		local ui = self.uis[i]
+		if ui.ui.can_focus and bx >= ui.x and bx <= ui.x + ui.ui.w and by >= ui.y and by <= ui.y + ui.ui.h then
+			self:setFocus(i)
+
+			-- Pass the event
+			ui.ui.mouse:delegate(button, bx, by, xrel, yrel, bx, by, event)
+			break
+		end
+	end
+end
+
+function _M:keyEvent(...)
+	if not self.focus_ui or not self.focus_ui.ui.key:receiveKey(...) then
+		KeyBind.receiveKey(self.key, ...)
+	end
+end
+
+function _M:display() end
+
+function _M:toScreen(x, y)
+	-- Draw with only the texture
+--[[
+	if self.__showup then
+		local eff = self.__showup_effect or "pop"
+		if eff == "overpop" then
+			local zoom = self.__showup / 7
+			if self.__showup >= 9 then
+				zoom = (9 - (self.__showup - 9)) / 7 - 1
+				zoom = 1 + zoom * 0.5
+			end
+			self.texture:toScreenFull(x + (self.w - self.w * zoom) / 2, y + (self.h - self.h * zoom) / 2, self.w * zoom, self.h * zoom, self.texture_w * zoom, self.texture_h * zoom)
+			self.__showup = self.__showup + 1
+			if self.__showup >= 11 then self.__showup = nil end
+		else
+			local zoom = self.__showup / 7
+			self.texture:toScreenFull(x + (self.w - self.w * zoom) / 2, y + (self.h - self.h * zoom) / 2, self.w * zoom, self.h * zoom, self.texture_w * zoom, self.texture_h * zoom)
+			self.__showup = self.__showup + 1
+			if self.__showup >= 7 then self.__showup = nil end
+		end
+	else
+]]
+		self.tex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+		for i = 1, #self.uis do
+			local ui = self.uis[i]
+			ui.ui:display(x + ui.x, y + ui.y)
+		end
+--	end
+end
diff --git a/game/engines/default/engine/ui/Focusable.lua b/game/engines/default/engine/ui/Focusable.lua
new file mode 100644
index 0000000000..03818bfe03
--- /dev/null
+++ b/game/engines/default/engine/ui/Focusable.lua
@@ -0,0 +1,29 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+
+--- Make a UI element clickable
+module(..., package.seeall, class.make)
+
+can_focus = true
+
+function _M:setFocus(v)
+	self.focused = v
+end
diff --git a/game/engines/default/engine/ui/List.lua b/game/engines/default/engine/ui/List.lua
new file mode 100644
index 0000000000..7436ec7753
--- /dev/null
+++ b/game/engines/default/engine/ui/List.lua
@@ -0,0 +1,75 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Base = require "engine.ui.Base"
+local Focusable = require "engine.ui.Focusable"
+
+--- A generic UI button
+module(..., package.seeall, class.inherit(Base, Focusable))
+
+function _M:init(t)
+	self.text = assert(t.text, "no button text")
+	self.fct = assert(t.fct, "no button fct")
+
+	Base.init(self, t)
+end
+
+function _M:generate()
+	local ls, ls_w, ls_h = self:getImage("ui/button-left-sel.png")
+	local ms, ms_w, ms_h = self:getImage("ui/button-middle-sel.png")
+	local rs, rs_w, rs_h = self:getImage("ui/button-right-sel.png")
+	local l, l_w, l_h = self:getImage("ui/button-left.png")
+	local m, m_w, m_h = self:getImage("ui/button-middle.png")
+	local r, r_w, r_h = self:getImage("ui/button-right.png")
+
+	-- Draw UI
+	self.font:setStyle("bold")
+	local w, h = self.font:size(self.text:removeColorCodes())
+	local fw, fh = w + ls_w + rs_w, ls_h
+	local ss = core.display.newSurface(fw, fh)
+	local s = core.display.newSurface(fw, fh)
+
+	ss:merge(ls, 0, 0)
+	for i = ls_w, fw - rs_w do ss:merge(ms, i, 0) end
+	ss:merge(rs, fw - rs_w, 0)
+	ss:drawColorStringBlended(self.font, self.text, ls_w, (fh - h) / 2, 255, 255, 255)
+
+	s:merge(l, 0, 0)
+	for i = l_w, fw - r_w do s:merge(m, i, 0) end
+	s:merge(r, fw - r_w, 0)
+	s:drawColorStringBlended(self.font, self.text, ls_w, (fh - h) / 2, 255, 255, 255)
+	self.font:setStyle("normal")
+
+	-- Add UI controls
+	self.mouse:registerZone(0, 0, fw, fh, function(button, x, y, xrel, yrel, bx, by, event) if button == "left" and event == "button" then self.fct() end end)
+	self.key:addBind("ACCEPT", function() self.fct() end)
+
+	self.tex, self.tex_w, self.tex_h = s:glTexture()
+	self.stex = ss:glTexture()
+	self.w, self.h = fw, fh
+end
+
+function _M:display(x, y)
+	if self.focused then
+		self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+	else
+		self.tex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+	end
+end
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index ffb139ace3..8b50025658 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -550,7 +550,8 @@ function util.showMainMenu(no_reboot)
 	if game and type(game) == "table" then game:joinThreads(30) end
 
 	if no_reboot then
-		local Menu = require("special.mainmenu.class.Game")
+--		local Menu = require("special.mainmenu.class.Game")
+		local Menu = require("special.mainmenu.class.TestUI")
 		game = Menu.new()
 		game:run()
 	else
diff --git a/game/engines/default/special/mainmenu/class/TestUI.lua b/game/engines/default/special/mainmenu/class/TestUI.lua
new file mode 100644
index 0000000000..6185cb8c7b
--- /dev/null
+++ b/game/engines/default/special/mainmenu/class/TestUI.lua
@@ -0,0 +1,71 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+require "engine.Game"
+require "engine.KeyBind"
+require "engine.interface.GameMusic"
+local Module = require "engine.Module"
+local Savefile = require "engine.Savefile"
+local Tooltip = require "engine.Tooltip"
+local ButtonList = require "engine.ButtonList"
+local DownloadDialog = require "engine.dialogs.DownloadDialog"
+
+local Button = require "engine.ui.Button"
+local Dialog = require "engine.ui.Dialog"
+
+module(..., package.seeall, class.inherit(engine.Game, engine.interface.GameMusic))
+
+function _M:init()
+	engine.Game.init(self, engine.KeyBind.new())
+
+	self.refuse_threads = true
+
+	local b1 = Button.new{text="Ok", fct=function() print"OK" end}
+	local b2 = Button.new{text="Cancel", fct=function() print"KO" end}
+
+	local d = Dialog.new("plop", 400, 300)
+	d:loadUI{
+		{left=10, bottom=10, ui=b1},
+		{right=10, bottom=10, ui=b2},
+	}
+	self:registerDialog(d)
+end
+
+function _M:run()
+	self:setCurrent()
+end
+
+function _M:tick()
+	return true
+end
+
+function _M:display()
+	engine.Game.display(self)
+end
+
+--- Skip to a module directly ?
+function _M:commandLineArgs(args)
+end
+
+--- Ask if we realy want to close, if so, save the game first
+function _M:onQuit()
+	os.exit()
+end
+
-- 
GitLab