add dedicated backend (WIP)

This commit is contained in:
airstruck
2015-11-22 12:36:44 -05:00
parent 97b2332d47
commit bbba7e1b3d
23 changed files with 6073 additions and 235 deletions

View File

@@ -72,7 +72,7 @@ local mainForm = { id = 'mainWindow', type = 'panel',
{ text = 'Hi, I\'m centered middle. ', style = 'listThing',
align = 'middle center' },
{ text = 'Hi, I\'m centered bottom. ', style = 'listThing',
align = 'bottom center' },
align = 'bottom center', slices = 'luigi/theme/light/button.png' },
{ text = 'Hi, I\'m centered top. ', style = 'listThing',
align = 'top center' },
{ text = 'A man, a plan, a canal: Panama!', style = 'listThing' },
@@ -161,8 +161,12 @@ erge rg eg erg er ergs erg er ge rh erh rth]]
layout.mainCanvas.align = 'top'
layout.menuQuit:onPress(function (event) love.event.quit() end)
local Backend = require 'luigi.backend'
layout.themeLight:onPress(function (event) love.event.quit() end)
layout.menuQuit:onPress(function (event) Backend.quit() end)
layout.themeLight:onPress(function (event) Backend.quit() end)
layout:show()
Backend.run()

7
luigi/backend.lua Normal file
View File

@@ -0,0 +1,7 @@
local ROOT = (...):gsub('[^.]*$', '')
if _G.love and _G.love._version_minor > 8 then
return require(ROOT .. 'backend.love')
else
return require(ROOT .. 'backend.ffisdl')
end

288
luigi/backend/ffisdl.lua Normal file
View File

@@ -0,0 +1,288 @@
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
local Hooker = require(ROOT .. 'hooker')
local ffi = require 'ffi'
local sdl = require((...) .. '.sdl')
local Image = require((...) .. '.image')
local Font = require((...) .. '.font')
local Keyboard = require((...) .. '.keyboard')
local IntOut = ffi.typeof 'int[1]'
-- create window and renderer
local window = sdl.createWindow('', 0, 0, 800, 600,
sdl.WINDOW_SHOWN)
if window == nil then
io.stderr:write(ffi.string(sdl.getError()))
sdl.quit()
os.exit(1)
end
ffi.gc(window, sdl.destroyWindow)
local renderer = sdl.createRenderer(window, -1,
sdl.RENDERER_ACCELERATED + sdl.RENDERER_PRESENTVSYNC)
if renderer == nil then
io.stderr:write(ffi.string(sdl.getError()))
sdl.quit()
os.exit(1)
end
ffi.gc(renderer, sdl.destroyRenderer)
local Backend = {}
local callback = {
draw = function () end,
resize = function () end,
mousepressed = function () end,
mousereleased = function () end,
mousemoved = function () end,
keypressed = function () end,
keyreleased = function () end,
textinput = function () end,
}
Backend.run = function ()
local event = sdl.Event()
while true do
sdl.pumpEvents()
while sdl.pollEvent(event) ~= 0 do
if event.type == sdl.QUIT then
return
elseif event.type == sdl.WINDOWEVENT
and event.window.event == sdl.WINDOWEVENT_RESIZED then
callback.resize(event.window.data1, event.window.data2)
elseif event.type == sdl.MOUSEBUTTONDOWN then
callback.mousepressed(event.button.x, event.button.y, event.button.button)
elseif event.type == sdl.MOUSEBUTTONUP then
callback.mousereleased(event.button.x, event.button.y, event.button.button)
elseif event.type == sdl.MOUSEMOTION then
callback.mousemoved(event.motion.x, event.motion.y)
elseif event.type == sdl.KEYDOWN then
local key = Keyboard.stringByKeycode[event.key.keysym.sym]
callback.keypressed(key, event.key['repeat'])
elseif event.type == sdl.KEYUP then
local key = Keyboard.stringByKeycode[event.key.keysym.sym]
callback.keyreleased(key, event.key['repeat'])
elseif event.type == sdl.TEXTINPUT then
callback.textinput(ffi.string(event.text.text))
end
end
sdl.setRenderDrawColor(renderer, 0, 0, 0, 255)
sdl.renderClear(renderer)
callback.draw()
sdl.renderPresent(renderer)
sdl.delay(1)
end
end
Backend.Cursor = function (image, x, y)
return sdl.createColorCursor(image.sdlSurface, x, y)
end
Backend.Font = Font
Backend.Image = function (path)
return Image(renderer, path)
end
Backend.Quad = function (x, y, w, h)
return { x, y, w, h }
end
Backend.SpriteBatch = require((...) .. '.spritebatch')
Backend.draw = function (drawable, x, y, sx, sy)
return drawable:draw(x, y, sx, sy)
end
Backend.drawRectangle = function (mode, x, y, w, h)
if mode == 'fill' then
sdl.renderFillRect(renderer, sdl.Rect(x, y, w, h))
else
sdl.renderDrawRect(renderer, sdl.Rect(x, y, w, h))
end
end
local currentFont = Font()
-- print( text, x, y, r, sx, sy, ox, oy, kx, ky )
Backend.print = function (text, x, y)
if not text or text == '' then return end
local font = currentFont.sdlFont
local color = sdl.Color(currentFont.color)
local write = Font.SDL2_ttf.TTF_RenderUTF8_Blended
local surface = write(font, text, color)
ffi.gc(surface, sdl.freeSurface)
local texture = sdl.createTextureFromSurface(renderer, surface)
ffi.gc(texture, sdl.destroyTexture)
sdl.renderCopy(renderer, texture, nil, sdl.Rect(x, y, surface.w, surface.h))
end
Backend.printf = Backend.print
Backend.getClipboardText = sdl.getClipboardText
Backend.setClipboardText = sdl.setClipboardText
Backend.getMousePosition = function ()
local x, y = IntOut(), IntOut()
sdl.getMouseState(x, y)
return x[0], y[0]
end
local function SystemCursor (id)
local cursor = sdl.createSystemCursor(id)
ffi.gc(cursor, sdl.freeCursor)
return cursor
end
local systemCursors = {
arrow = SystemCursor(sdl.SYSTEM_CURSOR_ARROW),
ibeam = SystemCursor(sdl.SYSTEM_CURSOR_IBEAM),
wait = SystemCursor(sdl.SYSTEM_CURSOR_WAIT),
crosshair = SystemCursor(sdl.SYSTEM_CURSOR_CROSSHAIR),
waitarrow = SystemCursor(sdl.SYSTEM_CURSOR_WAITARROW),
sizenwse = SystemCursor(sdl.SYSTEM_CURSOR_SIZENWSE),
sizenesw = SystemCursor(sdl.SYSTEM_CURSOR_SIZENESW),
sizewe = SystemCursor(sdl.SYSTEM_CURSOR_SIZEWE),
sizens = SystemCursor(sdl.SYSTEM_CURSOR_SIZENS),
sizeall = SystemCursor(sdl.SYSTEM_CURSOR_SIZEALL),
no = SystemCursor(sdl.SYSTEM_CURSOR_NO),
hand = SystemCursor(sdl.SYSTEM_CURSOR_HAND),
}
Backend.getSystemCursor = function (name)
return systemCursors[name] or systemCursors.arrow
end
Backend.getWindowSize = function ()
local x, y = IntOut(), IntOut()
sdl.getWindowSize(window, x, y)
return x[0], y[0]
end
Backend.getTime = function ()
return sdl.getTicks() * 0.001
end
Backend.isKeyDown = function (...)
local state = sdl.getKeyboardState(nil)
for i = 1, select('#', ...) do
local name = select(i, ...)
local scan = Keyboard.scancodeByString[name]
if scan and state[scan] ~= 0 then
return true
end
end
return false
end
Backend.isMouseDown = function ()
end
Backend.quit = function ()
sdl.quit()
os.exit()
end
local lastColor
Backend.setColor = function (color)
lastColor = color
sdl.setRenderDrawColor(renderer,
color[1], color[2], color[3], color[4] or 255)
end
Backend.setCursor = function (cursor)
sdl.setCursor(cursor or Backend.getSystemCursor('arrow'))
end
Backend.setFont = function (font)
currentFont = font
end
local lastScissor
Backend.setScissor = function (x, y, w, h)
lastScissor = x and sdl.Rect(x, y, w, h)
sdl.renderSetClipRect(renderer, lastScissor)
end
function Backend.hide (layout)
for _, item in ipairs(layout.hooks) do
Hooker.unhook(item)
end
layout.hooks = {}
end
local function hook (layout, key, method, hookLast)
layout.hooks[#layout.hooks + 1] = Hooker.hook(
callback, key, method, hookLast)
end
local stack = {}
Backend.pop = function ()
local history = stack[#stack]
Backend.setColor(history.color or { 0, 0, 0, 255 })
Backend.setScissor(history.scissor)
stack[#stack] = nil
end
Backend.push = function ()
stack[#stack + 1] = {
color = lastColor,
scissor = lastScissor,
}
end
local isMouseDown = function ()
return sdl.getMouseState(nil, nil) > 0
end
function Backend.show (layout)
local input = layout.input
hook(layout, 'draw', function ()
input:handleDisplay(layout)
end, true)
hook(layout, 'resize', function (width, height)
return input:handleReshape(layout, width, height)
end)
hook(layout, 'mousepressed', function (x, y, button)
return input:handlePressStart(layout, button, x, y)
end)
hook(layout, 'mousereleased', function (x, y, button)
return input:handlePressEnd(layout, button, x, y)
end)
hook(layout, 'mousemoved', function (x, y, dx, dy)
if isMouseDown() then
return input:handlePressedMove(layout, x, y)
else
return input:handleMove(layout, x, y)
end
end)
hook(layout, 'keypressed', function (key, isRepeat)
return input:handleKeyPress(layout, key, Backend.getMousePosition())
end)
hook(layout, 'keyreleased', function (key)
return input:handleKeyRelease(layout, key, Backend.getMousePosition())
end)
hook(layout, 'textinput', function (text)
return input:handleTextInput(layout, text, Backend.getMousePosition())
end)
end
return Backend

View File

@@ -0,0 +1,270 @@
local REL = (...):gsub('[^.]*$', '')
local ffi = require 'ffi'
local sdl = require(REL .. 'sdl')
local SDL2_ttf = ffi.load 'SDL2_ttf'
local IntOut = ffi.typeof 'int[1]'
ffi.cdef [[
/* The internal structure containing font information */
typedef struct _TTF_Font TTF_Font;
/* Initialize the TTF engine - returns 0 if successful, -1 on error */
int TTF_Init(void);
/* Open a font file and create a font of the specified point size.
* Some .fon fonts will have several sizes embedded in the file, so the
* point size becomes the index of choosing which size. If the value
* is too high, the last indexed size will be the default. */
TTF_Font *TTF_OpenFont(const char *file, int ptsize);
TTF_Font *TTF_OpenFontIndex(const char *file, int ptsize, long index);
TTF_Font *TTF_OpenFontRW(SDL_RWops *src, int freesrc, int ptsize);
TTF_Font *TTF_OpenFontIndexRW(SDL_RWops *src, int freesrc, int ptsize, long index);
/* Set and retrieve the font style
#define TTF_STYLE_NORMAL 0x00
#define TTF_STYLE_BOLD 0x01
#define TTF_STYLE_ITALIC 0x02
#define TTF_STYLE_UNDERLINE 0x04
#define TTF_STYLE_STRIKETHROUGH 0x08
*/
int TTF_GetFontStyle(const TTF_Font *font);
void TTF_SetFontStyle(TTF_Font *font, int style);
int TTF_GetFontOutline(const TTF_Font *font);
void TTF_SetFontOutline(TTF_Font *font, int outline);
/* Set and retrieve FreeType hinter settings
#define TTF_HINTING_NORMAL 0
#define TTF_HINTING_LIGHT 1
#define TTF_HINTING_MONO 2
#define TTF_HINTING_NONE 3
*/
int TTF_GetFontHinting(const TTF_Font *font);
void TTF_SetFontHinting(TTF_Font *font, int hinting);
/* Get the total height of the font - usually equal to point size */
int TTF_FontHeight(const TTF_Font *font);
/* Get the offset from the baseline to the top of the font
This is a positive value, relative to the baseline.
*/
int TTF_FontAscent(const TTF_Font *font);
/* Get the offset from the baseline to the bottom of the font
This is a negative value, relative to the baseline.
*/
int TTF_FontDescent(const TTF_Font *font);
/* Get the recommended spacing between lines of text for this font */
int TTF_FontLineSkip(const TTF_Font *font);
/* Get/Set whether or not kerning is allowed for this font */
int TTF_GetFontKerning(const TTF_Font *font);
void TTF_SetFontKerning(TTF_Font *font, int allowed);
/* Get the number of faces of the font */
long TTF_FontFaces(const TTF_Font *font);
/* Get the font face attributes, if any */
int TTF_FontFaceIsFixedWidth(const TTF_Font *font);
char *TTF_FontFaceFamilyName(const TTF_Font *font);
char *TTF_FontFaceStyleName(const TTF_Font *font);
/* Check wether a glyph is provided by the font or not */
int TTF_GlyphIsProvided(const TTF_Font *font, Uint16 ch);
/* Get the metrics (dimensions) of a glyph
To understand what these metrics mean, here is a useful link:
http://freetype.sourceforge.net/freetype2/docs/tutorial/step2.html
*/
int TTF_GlyphMetrics(TTF_Font *font, Uint16 ch,
int *minx, int *maxx,
int *miny, int *maxy, int *advance);
/* Get the dimensions of a rendered string of text */
int TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h);
int TTF_SizeUTF8(TTF_Font *font, const char *text, int *w, int *h);
int TTF_SizeUTF8_Wrapped(TTF_Font *font, const char *text, int wrapLength, int *w, int *h, int *lineCount);
int TTF_SizeUNICODE(TTF_Font *font, const Uint16 *text, int *w, int *h);
/* Create an 8-bit palettized surface and render the given text at
fast quality with the given font and color. The 0 pixel is the
colorkey, giving a transparent background, and the 1 pixel is set
to the text color.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Solid(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUTF8_Solid(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUNICODE_Solid(TTF_Font *font,
const Uint16 *text, SDL_Color fg);
/* Create an 8-bit palettized surface and render the given glyph at
fast quality with the given font and color. The 0 pixel is the
colorkey, giving a transparent background, and the 1 pixel is set
to the text color. The glyph is rendered without any padding or
centering in the X direction, and aligned normally in the Y direction.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderGlyph_Solid(TTF_Font *font,
Uint16 ch, SDL_Color fg);
/* Create an 8-bit palettized surface and render the given text at
high quality with the given font and colors. The 0 pixel is background,
while other pixels have varying degrees of the foreground color.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Shaded(TTF_Font *font,
const char *text, SDL_Color fg, SDL_Color bg);
SDL_Surface *TTF_RenderUTF8_Shaded(TTF_Font *font,
const char *text, SDL_Color fg, SDL_Color bg);
SDL_Surface *TTF_RenderUNICODE_Shaded(TTF_Font *font,
const Uint16 *text, SDL_Color fg, SDL_Color bg);
/* Create an 8-bit palettized surface and render the given glyph at
high quality with the given font and colors. The 0 pixel is background,
while other pixels have varying degrees of the foreground color.
The glyph is rendered without any padding or centering in the X
direction, and aligned normally in the Y direction.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderGlyph_Shaded(TTF_Font *font,
Uint16 ch, SDL_Color fg, SDL_Color bg);
/* Create a 32-bit ARGB surface and render the given text at high quality,
using alpha blending to dither the font with the given color.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Blended(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUTF8_Blended(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUNICODE_Blended(TTF_Font *font,
const Uint16 *text, SDL_Color fg);
/* Create a 32-bit ARGB surface and render the given text at high quality,
using alpha blending to dither the font with the given color.
Text is wrapped to multiple lines on line endings and on word boundaries
if it extends beyond wrapLength in pixels.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Blended_Wrapped(TTF_Font *font,
const char *text, SDL_Color fg, Uint32 wrapLength);
SDL_Surface *TTF_RenderUTF8_Blended_Wrapped(TTF_Font *font,
const char *text, SDL_Color fg, Uint32 wrapLength);
SDL_Surface *TTF_RenderUNICODE_Blended_Wrapped(TTF_Font *font,
const Uint16 *text, SDL_Color fg, Uint32 wrapLength);
/* Create a 32-bit ARGB surface and render the given glyph at high quality,
using alpha blending to dither the font with the given color.
The glyph is rendered without any padding or centering in the X
direction, and aligned normally in the Y direction.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderGlyph_Blended(TTF_Font *font,
Uint16 ch, SDL_Color fg);
/* Close an opened font file */
void TTF_CloseFont(TTF_Font *font);
/* De-initialize the TTF engine */
void TTF_Quit(void);
/* Check if the TTF engine is initialized */
int TTF_WasInit(void);
/* Get the kerning size of two glyphs */
int TTF_GetFontKerningSize(TTF_Font *font, int prev_index, int index);
]]
if SDL2_ttf.TTF_Init() ~= 0 then
error(ffi.string(sdl.getError()))
end
local Font = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
Font.SDL2_ttf = SDL2_ttf
local fontCache = {}
function Font:constructor (path, size, color)
if not size then
size = 12
end
if not color then
color = { 0, 0, 0, 255 }
end
if not path then
path = REL:gsub('%.', '/') .. 'resource/DejaVuSans.ttf'
end
local key = path .. '_' .. size
if not fontCache[key] then
local font = SDL2_ttf.TTF_OpenFont(path, size)
if font == nil then
io.stderr:write(ffi.string(sdl.getError()))
sdl.quit()
os.exit(1)
end
fontCache[key] = font
end
self.sdlFont = fontCache[key]
self.color = color
end
function Font:setAlignment (align)
self.align = align
end
function Font:setWidth (width)
self.width = width
end
function Font:getLineHeight ()
return SDL2_ttf.TTF_FontHeight(self.sdlFont)
end
function Font:getAscender ()
return SDL2_ttf.TTF_FontAscent(self.sdlFont)
end
function Font:getDescender ()
return SDL2_ttf.TTF_FontDescent(self.sdlFont)
end
function Font:getAdvance (text)
local w, h = IntOut(), IntOut()
SDL2_ttf.TTF_SizeUTF8(self.sdlFont, text, w, h)
return w[0]
end
function Font:getWrappedHeight (text)
--[[
-- TTF_Font *font, const char *text, int wrapLength, int *w, int *h, int *lineCount
local w, h, lineCount = IntOut(), IntOut(), IntOut()
SDL2_ttf.TTF_SizeUTF8_Wrapped(self.sdlFont, text, self.width,
w, h, lineCount)
return self:getLineHeight() * lineCount
--]]
local w, h = IntOut(), IntOut()
SDL2_ttf.TTF_SizeUTF8(self.sdlFont, text, w, h)
return h[0]
end
return Font

View File

@@ -0,0 +1,46 @@
local REL = (...):gsub('[^.]*$', '')
local ffi = require 'ffi'
local sdl = require(REL .. 'sdl')
local SDL2_image = ffi.load 'SDL2_image'
ffi.cdef [[ SDL_Surface *IMG_Load(const char *file); ]]
local Image = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
function Image:constructor (renderer, path)
self.sdlRenderer = renderer
self.sdlSurface = SDL2_image.IMG_Load(path)
ffi.gc(self.sdlSurface, sdl.freeSurface)
self.sdlTexture = sdl.createTextureFromSurface(renderer, self.sdlSurface)
ffi.gc(self.sdlTexture, sdl.destroyTexture)
self.width = self.sdlSurface.w
self.height = self.sdlSurface.h
end
function Image:getWidth ()
return self.width
end
function Image:getHeight ()
return self.height
end
function Image:draw (x, y, sx, sy)
local w = self.width * (sx or 1)
local h = self.height * (sy or 1)
-- HACK. Somehow drawing something first prevents renderCopy from
-- incorrectly scaling up in some cases (after rendering slices).
-- For example http://stackoverflow.com/questions/28218906
sdl.renderDrawPoint(self.sdlRenderer, -1, -1)
-- Draw the image.
sdl.renderCopy(self.sdlRenderer, self.sdlTexture, nil, sdl.Rect(x, y, w, h))
end
return Image

View File

@@ -0,0 +1,494 @@
local REL = (...):gsub('[^.]*$', '')
local sdl = require(REL .. 'sdl')
local Keyboard = {
scancodeByString = {},
stringByScancode = {},
keycodeByString = {},
stringByKeycode = {},
}
local function registerScancodes (registry)
for _, entry in ipairs(registry) do
Keyboard.scancodeByString[entry[1]] = entry[2]
Keyboard.stringByScancode[entry[2]] = entry[1]
end
end
local function registerKeycodes (registry)
for _, entry in ipairs(registry) do
Keyboard.keycodeByString[entry[1]] = entry[2]
Keyboard.stringByKeycode[entry[2]] = entry[1]
end
end
registerScancodes {
{ "unknown", sdl.SCANCODE_UNKNOWN },
{ "a", sdl.SCANCODE_A },
{ "b", sdl.SCANCODE_B },
{ "c", sdl.SCANCODE_C },
{ "d", sdl.SCANCODE_D },
{ "e", sdl.SCANCODE_E },
{ "f", sdl.SCANCODE_F },
{ "g", sdl.SCANCODE_G },
{ "h", sdl.SCANCODE_H },
{ "i", sdl.SCANCODE_I },
{ "j", sdl.SCANCODE_J },
{ "k", sdl.SCANCODE_K },
{ "l", sdl.SCANCODE_L },
{ "m", sdl.SCANCODE_M },
{ "n", sdl.SCANCODE_N },
{ "o", sdl.SCANCODE_O },
{ "p", sdl.SCANCODE_P },
{ "q", sdl.SCANCODE_Q },
{ "r", sdl.SCANCODE_R },
{ "s", sdl.SCANCODE_S },
{ "t", sdl.SCANCODE_T },
{ "u", sdl.SCANCODE_U },
{ "v", sdl.SCANCODE_V },
{ "w", sdl.SCANCODE_W },
{ "x", sdl.SCANCODE_X },
{ "y", sdl.SCANCODE_Y },
{ "z", sdl.SCANCODE_Z },
{ "1", sdl.SCANCODE_1 },
{ "2", sdl.SCANCODE_2 },
{ "3", sdl.SCANCODE_3 },
{ "4", sdl.SCANCODE_4 },
{ "5", sdl.SCANCODE_5 },
{ "6", sdl.SCANCODE_6 },
{ "7", sdl.SCANCODE_7 },
{ "8", sdl.SCANCODE_8 },
{ "9", sdl.SCANCODE_9 },
{ "0", sdl.SCANCODE_0 },
{ "return", sdl.SCANCODE_RETURN },
{ "escape", sdl.SCANCODE_ESCAPE },
{ "backspace", sdl.SCANCODE_BACKSPACE },
{ "tab", sdl.SCANCODE_TAB },
{ "space", sdl.SCANCODE_SPACE },
{ "-", sdl.SCANCODE_MINUS },
{ "=", sdl.SCANCODE_EQUALS },
{ "[", sdl.SCANCODE_LEFTBRACKET },
{ "]", sdl.SCANCODE_RIGHTBRACKET },
{ "\\", sdl.SCANCODE_BACKSLASH },
{ "nonus#", sdl.SCANCODE_NONUSHASH },
{ ";", sdl.SCANCODE_SEMICOLON },
{ "'", sdl.SCANCODE_APOSTROPHE },
{ "`", sdl.SCANCODE_GRAVE },
{ ",", sdl.SCANCODE_COMMA },
{ ".", sdl.SCANCODE_PERIOD },
{ "/", sdl.SCANCODE_SLASH },
{ "capslock", sdl.SCANCODE_CAPSLOCK },
{ "f1", sdl.SCANCODE_F1 },
{ "f2", sdl.SCANCODE_F2 },
{ "f3", sdl.SCANCODE_F3 },
{ "f4", sdl.SCANCODE_F4 },
{ "f5", sdl.SCANCODE_F5 },
{ "f6", sdl.SCANCODE_F6 },
{ "f7", sdl.SCANCODE_F7 },
{ "f8", sdl.SCANCODE_F8 },
{ "f9", sdl.SCANCODE_F9 },
{ "f10", sdl.SCANCODE_F10 },
{ "f11", sdl.SCANCODE_F11 },
{ "f12", sdl.SCANCODE_F12 },
{ "printscreen", sdl.SCANCODE_PRINTSCREEN },
{ "scrolllock", sdl.SCANCODE_SCROLLLOCK },
{ "pause", sdl.SCANCODE_PAUSE },
{ "insert", sdl.SCANCODE_INSERT },
{ "home", sdl.SCANCODE_HOME },
{ "pageup", sdl.SCANCODE_PAGEUP },
{ "delete", sdl.SCANCODE_DELETE },
{ "end", sdl.SCANCODE_END },
{ "pagedown", sdl.SCANCODE_PAGEDOWN },
{ "right", sdl.SCANCODE_RIGHT },
{ "left", sdl.SCANCODE_LEFT },
{ "down", sdl.SCANCODE_DOWN },
{ "up", sdl.SCANCODE_UP },
{ "numlock", sdl.SCANCODE_NUMLOCKCLEAR },
{ "kp/", sdl.SCANCODE_KP_DIVIDE },
{ "kp*", sdl.SCANCODE_KP_MULTIPLY },
{ "kp-", sdl.SCANCODE_KP_MINUS },
{ "kp+", sdl.SCANCODE_KP_PLUS },
{ "kpenter", sdl.SCANCODE_KP_ENTER },
{ "kp1", sdl.SCANCODE_KP_1 },
{ "kp2", sdl.SCANCODE_KP_2 },
{ "kp3", sdl.SCANCODE_KP_3 },
{ "kp4", sdl.SCANCODE_KP_4 },
{ "kp5", sdl.SCANCODE_KP_5 },
{ "kp6", sdl.SCANCODE_KP_6 },
{ "kp7", sdl.SCANCODE_KP_7 },
{ "kp8", sdl.SCANCODE_KP_8 },
{ "kp9", sdl.SCANCODE_KP_9 },
{ "kp0", sdl.SCANCODE_KP_0 },
{ "kp.", sdl.SCANCODE_KP_PERIOD },
{ "nonusbackslash", sdl.SCANCODE_NONUSBACKSLASH },
{ "application", sdl.SCANCODE_APPLICATION },
{ "power", sdl.SCANCODE_POWER },
{ "=", sdl.SCANCODE_KP_EQUALS },
{ "f13", sdl.SCANCODE_F13 },
{ "f14", sdl.SCANCODE_F14 },
{ "f15", sdl.SCANCODE_F15 },
{ "f16", sdl.SCANCODE_F16 },
{ "f17", sdl.SCANCODE_F17 },
{ "f18", sdl.SCANCODE_F18 },
{ "f19", sdl.SCANCODE_F19 },
{ "f20", sdl.SCANCODE_F20 },
{ "f21", sdl.SCANCODE_F21 },
{ "f22", sdl.SCANCODE_F22 },
{ "f23", sdl.SCANCODE_F23 },
{ "f24", sdl.SCANCODE_F24 },
{ "execute", sdl.SCANCODE_EXECUTE },
{ "help", sdl.SCANCODE_HELP },
{ "menu", sdl.SCANCODE_MENU },
{ "select", sdl.SCANCODE_SELECT },
{ "stop", sdl.SCANCODE_STOP },
{ "again", sdl.SCANCODE_AGAIN },
{ "undo", sdl.SCANCODE_UNDO },
{ "cut", sdl.SCANCODE_CUT },
{ "copy", sdl.SCANCODE_COPY },
{ "paste", sdl.SCANCODE_PASTE },
{ "find", sdl.SCANCODE_FIND },
{ "mute", sdl.SCANCODE_MUTE },
{ "volumeup", sdl.SCANCODE_VOLUMEUP },
{ "volumedown", sdl.SCANCODE_VOLUMEDOWN },
{ "kp,", sdl.SCANCODE_KP_COMMA },
{ "kp=400", sdl.SCANCODE_KP_EQUALSAS400 },
{ "international1", sdl.SCANCODE_INTERNATIONAL1 },
{ "international2", sdl.SCANCODE_INTERNATIONAL2 },
{ "international3", sdl.SCANCODE_INTERNATIONAL3 },
{ "international4", sdl.SCANCODE_INTERNATIONAL4 },
{ "international5", sdl.SCANCODE_INTERNATIONAL5 },
{ "international6", sdl.SCANCODE_INTERNATIONAL6 },
{ "international7", sdl.SCANCODE_INTERNATIONAL7 },
{ "international8", sdl.SCANCODE_INTERNATIONAL8 },
{ "international9", sdl.SCANCODE_INTERNATIONAL9 },
{ "lang1", sdl.SCANCODE_LANG1 },
{ "lang2", sdl.SCANCODE_LANG2 },
{ "lang3", sdl.SCANCODE_LANG3 },
{ "lang4", sdl.SCANCODE_LANG4 },
{ "lang5", sdl.SCANCODE_LANG5 },
{ "lang6", sdl.SCANCODE_LANG6 },
{ "lang7", sdl.SCANCODE_LANG7 },
{ "lang8", sdl.SCANCODE_LANG8 },
{ "lang9", sdl.SCANCODE_LANG9 },
{ "alterase", sdl.SCANCODE_ALTERASE },
{ "sysreq", sdl.SCANCODE_SYSREQ },
{ "cancel", sdl.SCANCODE_CANCEL },
{ "clear", sdl.SCANCODE_CLEAR },
{ "prior", sdl.SCANCODE_PRIOR },
{ "return2", sdl.SCANCODE_RETURN2 },
{ "separator", sdl.SCANCODE_SEPARATOR },
{ "out", sdl.SCANCODE_OUT },
{ "oper", sdl.SCANCODE_OPER },
{ "clearagain", sdl.SCANCODE_CLEARAGAIN },
{ "crsel", sdl.SCANCODE_CRSEL },
{ "exsel", sdl.SCANCODE_EXSEL },
{ "kp00", sdl.SCANCODE_KP_00 },
{ "kp000", sdl.SCANCODE_KP_000 },
{ "thsousandsseparator", sdl.SCANCODE_THOUSANDSSEPARATOR },
{ "decimalseparator", sdl.SCANCODE_DECIMALSEPARATOR },
{ "currencyunit", sdl.SCANCODE_CURRENCYUNIT },
{ "currencysubunit", sdl.SCANCODE_CURRENCYSUBUNIT },
{ "kp(", sdl.SCANCODE_KP_LEFTPAREN },
{ "kp)", sdl.SCANCODE_KP_RIGHTPAREN },
{ "kp{", sdl.SCANCODE_KP_LEFTBRACE },
{ "kp}", sdl.SCANCODE_KP_RIGHTBRACE },
{ "kptab", sdl.SCANCODE_KP_TAB },
{ "kpbackspace", sdl.SCANCODE_KP_BACKSPACE },
{ "kpa", sdl.SCANCODE_KP_A },
{ "kpb", sdl.SCANCODE_KP_B },
{ "kpc", sdl.SCANCODE_KP_C },
{ "kpd", sdl.SCANCODE_KP_D },
{ "kpe", sdl.SCANCODE_KP_E },
{ "kpf", sdl.SCANCODE_KP_F },
{ "kpxor", sdl.SCANCODE_KP_XOR },
{ "kpower", sdl.SCANCODE_KP_POWER },
{ "kp%", sdl.SCANCODE_KP_PERCENT },
{ "kp<", sdl.SCANCODE_KP_LESS },
{ "kp>", sdl.SCANCODE_KP_GREATER },
{ "kp&", sdl.SCANCODE_KP_AMPERSAND },
{ "kp&&", sdl.SCANCODE_KP_DBLAMPERSAND },
{ "kp|", sdl.SCANCODE_KP_VERTICALBAR },
{ "kp||", sdl.SCANCODE_KP_DBLVERTICALBAR },
{ "kp:", sdl.SCANCODE_KP_COLON },
{ "kp#", sdl.SCANCODE_KP_HASH },
{ "kp ", sdl.SCANCODE_KP_SPACE },
{ "kp@", sdl.SCANCODE_KP_AT },
{ "kp!", sdl.SCANCODE_KP_EXCLAM },
{ "kpmemstore", sdl.SCANCODE_KP_MEMSTORE },
{ "kpmemrecall", sdl.SCANCODE_KP_MEMRECALL },
{ "kpmemclear", sdl.SCANCODE_KP_MEMCLEAR },
{ "kpmem+", sdl.SCANCODE_KP_MEMADD },
{ "kpmem-", sdl.SCANCODE_KP_MEMSUBTRACT },
{ "kpmem*", sdl.SCANCODE_KP_MEMMULTIPLY },
{ "kpmem/", sdl.SCANCODE_KP_MEMDIVIDE },
{ "kp+-", sdl.SCANCODE_KP_PLUSMINUS },
{ "kpclear", sdl.SCANCODE_KP_CLEAR },
{ "kpclearentry", sdl.SCANCODE_KP_CLEARENTRY },
{ "kpbinary", sdl.SCANCODE_KP_BINARY },
{ "kpoctal", sdl.SCANCODE_KP_OCTAL },
{ "kpdecimal", sdl.SCANCODE_KP_DECIMAL },
{ "kphex", sdl.SCANCODE_KP_HEXADECIMAL },
{ "lctrl", sdl.SCANCODE_LCTRL },
{ "lshift", sdl.SCANCODE_LSHIFT },
{ "lalt", sdl.SCANCODE_LALT },
{ "lgui", sdl.SCANCODE_LGUI },
{ "rctrl", sdl.SCANCODE_RCTRL },
{ "rshift", sdl.SCANCODE_RSHIFT },
{ "ralt", sdl.SCANCODE_RALT },
{ "rgui", sdl.SCANCODE_RGUI },
{ "mode", sdl.SCANCODE_MODE },
{ "audionext", sdl.SCANCODE_AUDIONEXT },
{ "audioprev", sdl.SCANCODE_AUDIOPREV },
{ "audiostop", sdl.SCANCODE_AUDIOSTOP },
{ "audioplay", sdl.SCANCODE_AUDIOPLAY },
{ "audiomute", sdl.SCANCODE_AUDIOMUTE },
{ "mediaselect", sdl.SCANCODE_MEDIASELECT },
{ "www", sdl.SCANCODE_WWW },
{ "mail", sdl.SCANCODE_MAIL },
{ "calculator", sdl.SCANCODE_CALCULATOR },
{ "computer", sdl.SCANCODE_COMPUTER },
{ "acsearch", sdl.SCANCODE_AC_SEARCH },
{ "achome", sdl.SCANCODE_AC_HOME },
{ "acback", sdl.SCANCODE_AC_BACK },
{ "acforward", sdl.SCANCODE_AC_FORWARD },
{ "acstop", sdl.SCANCODE_AC_STOP },
{ "acrefresh", sdl.SCANCODE_AC_REFRESH },
{ "acbookmarks", sdl.SCANCODE_AC_BOOKMARKS },
{ "brightnessdown", sdl.SCANCODE_BRIGHTNESSDOWN },
{ "brightnessup", sdl.SCANCODE_BRIGHTNESSUP },
{ "displayswitch", sdl.SCANCODE_DISPLAYSWITCH },
{ "kbdillumtoggle", sdl.SCANCODE_KBDILLUMTOGGLE },
{ "kbdillumdown", sdl.SCANCODE_KBDILLUMDOWN },
{ "kbdillumup", sdl.SCANCODE_KBDILLUMUP },
{ "eject", sdl.SCANCODE_EJECT },
{ "sleep", sdl.SCANCODE_SLEEP },
{ "app1", sdl.SCANCODE_APP1 },
{ "app2", sdl.SCANCODE_APP2 },
}
registerKeycodes {
{ "unknown", sdl.C.SDLK_UNKNOWN },
{ "return", sdl.C.SDLK_RETURN },
{ "escape", sdl.C.SDLK_ESCAPE },
{ "backspace", sdl.C.SDLK_BACKSPACE },
{ "tab", sdl.C.SDLK_TAB },
{ "space", sdl.C.SDLK_SPACE },
{ "!", sdl.C.SDLK_EXCLAIM },
{ "\"", sdl.C.SDLK_QUOTEDBL },
{ "#", sdl.C.SDLK_HASH },
{ "%", sdl.C.SDLK_PERCENT },
{ "$", sdl.C.SDLK_DOLLAR },
{ "&", sdl.C.SDLK_AMPERSAND },
{ "'", sdl.C.SDLK_QUOTE },
{ "(", sdl.C.SDLK_LEFTPAREN },
{ ")", sdl.C.SDLK_RIGHTPAREN },
{ "*", sdl.C.SDLK_ASTERISK },
{ "+", sdl.C.SDLK_PLUS },
{ ",", sdl.C.SDLK_COMMA },
{ "-", sdl.C.SDLK_MINUS },
{ ".", sdl.C.SDLK_PERIOD },
{ "/", sdl.C.SDLK_SLASH },
{ "0", sdl.C.SDLK_0 },
{ "1", sdl.C.SDLK_1 },
{ "2", sdl.C.SDLK_2 },
{ "3", sdl.C.SDLK_3 },
{ "4", sdl.C.SDLK_4 },
{ "5", sdl.C.SDLK_5 },
{ "6", sdl.C.SDLK_6 },
{ "7", sdl.C.SDLK_7 },
{ "8", sdl.C.SDLK_8 },
{ "9", sdl.C.SDLK_9 },
{ ":", sdl.C.SDLK_COLON },
{ ";", sdl.C.SDLK_SEMICOLON },
{ "<", sdl.C.SDLK_LESS },
{ "=", sdl.C.SDLK_EQUALS },
{ ">", sdl.C.SDLK_GREATER },
{ "?", sdl.C.SDLK_QUESTION },
{ "@", sdl.C.SDLK_AT },
{ "[", sdl.C.SDLK_LEFTBRACKET },
{ "\\", sdl.C.SDLK_BACKSLASH },
{ "]", sdl.C.SDLK_RIGHTBRACKET },
{ "^", sdl.C.SDLK_CARET },
{ "_", sdl.C.SDLK_UNDERSCORE },
{ "`", sdl.C.SDLK_BACKQUOTE },
{ "a", sdl.C.SDLK_a },
{ "b", sdl.C.SDLK_b },
{ "c", sdl.C.SDLK_c },
{ "d", sdl.C.SDLK_d },
{ "e", sdl.C.SDLK_e },
{ "f", sdl.C.SDLK_f },
{ "g", sdl.C.SDLK_g },
{ "h", sdl.C.SDLK_h },
{ "i", sdl.C.SDLK_i },
{ "j", sdl.C.SDLK_j },
{ "k", sdl.C.SDLK_k },
{ "l", sdl.C.SDLK_l },
{ "m", sdl.C.SDLK_m },
{ "n", sdl.C.SDLK_n },
{ "o", sdl.C.SDLK_o },
{ "p", sdl.C.SDLK_p },
{ "q", sdl.C.SDLK_q },
{ "r", sdl.C.SDLK_r },
{ "s", sdl.C.SDLK_s },
{ "t", sdl.C.SDLK_t },
{ "u", sdl.C.SDLK_u },
{ "v", sdl.C.SDLK_v },
{ "w", sdl.C.SDLK_w },
{ "x", sdl.C.SDLK_x },
{ "y", sdl.C.SDLK_y },
{ "z", sdl.C.SDLK_z },
{ "capslock", sdl.C.SDLK_CAPSLOCK },
{ "f1", sdl.C.SDLK_F1 },
{ "f2", sdl.C.SDLK_F2 },
{ "f3", sdl.C.SDLK_F3 },
{ "f4", sdl.C.SDLK_F4 },
{ "f5", sdl.C.SDLK_F5 },
{ "f6", sdl.C.SDLK_F6 },
{ "f7", sdl.C.SDLK_F7 },
{ "f8", sdl.C.SDLK_F8 },
{ "f9", sdl.C.SDLK_F9 },
{ "f10", sdl.C.SDLK_F10 },
{ "f11", sdl.C.SDLK_F11 },
{ "f12", sdl.C.SDLK_F12 },
{ "printscreen", sdl.C.SDLK_PRINTSCREEN },
{ "scrolllock", sdl.C.SDLK_SCROLLLOCK },
{ "pause", sdl.C.SDLK_PAUSE },
{ "insert", sdl.C.SDLK_INSERT },
{ "home", sdl.C.SDLK_HOME },
{ "pageup", sdl.C.SDLK_PAGEUP },
{ "delete", sdl.C.SDLK_DELETE },
{ "end", sdl.C.SDLK_END },
{ "pagedown", sdl.C.SDLK_PAGEDOWN },
{ "right", sdl.C.SDLK_RIGHT },
{ "left", sdl.C.SDLK_LEFT },
{ "down", sdl.C.SDLK_DOWN },
{ "up", sdl.C.SDLK_UP },
{ "numlock", sdl.C.SDLK_NUMLOCKCLEAR },
{ "kp/", sdl.C.SDLK_KP_DIVIDE },
{ "kp*", sdl.C.SDLK_KP_MULTIPLY },
{ "kp-", sdl.C.SDLK_KP_MINUS },
{ "kp+", sdl.C.SDLK_KP_PLUS },
{ "kpenter", sdl.C.SDLK_KP_ENTER },
{ "kp0", sdl.C.SDLK_KP_0 },
{ "kp1", sdl.C.SDLK_KP_1 },
{ "kp2", sdl.C.SDLK_KP_2 },
{ "kp3", sdl.C.SDLK_KP_3 },
{ "kp4", sdl.C.SDLK_KP_4 },
{ "kp5", sdl.C.SDLK_KP_5 },
{ "kp6", sdl.C.SDLK_KP_6 },
{ "kp7", sdl.C.SDLK_KP_7 },
{ "kp8", sdl.C.SDLK_KP_8 },
{ "kp9", sdl.C.SDLK_KP_9 },
{ "kp.", sdl.C.SDLK_KP_PERIOD },
{ "kp,", sdl.C.SDLK_KP_COMMA },
{ "kp=", sdl.C.SDLK_KP_EQUALS },
{ "application", sdl.C.SDLK_APPLICATION },
{ "power", sdl.C.SDLK_POWER },
{ "f13", sdl.C.SDLK_F13 },
{ "f14", sdl.C.SDLK_F14 },
{ "f15", sdl.C.SDLK_F15 },
{ "f16", sdl.C.SDLK_F16 },
{ "f17", sdl.C.SDLK_F17 },
{ "f18", sdl.C.SDLK_F18 },
{ "f19", sdl.C.SDLK_F19 },
{ "f20", sdl.C.SDLK_F20 },
{ "f21", sdl.C.SDLK_F21 },
{ "f22", sdl.C.SDLK_F22 },
{ "f23", sdl.C.SDLK_F23 },
{ "f24", sdl.C.SDLK_F24 },
{ "execute", sdl.C.SDLK_EXECUTE },
{ "help", sdl.C.SDLK_HELP },
{ "menu", sdl.C.SDLK_MENU },
{ "select", sdl.C.SDLK_SELECT },
{ "stop", sdl.C.SDLK_STOP },
{ "again", sdl.C.SDLK_AGAIN },
{ "undo", sdl.C.SDLK_UNDO },
{ "cut", sdl.C.SDLK_CUT },
{ "copy", sdl.C.SDLK_COPY },
{ "paste", sdl.C.SDLK_PASTE },
{ "find", sdl.C.SDLK_FIND },
{ "mute", sdl.C.SDLK_MUTE },
{ "volumeup", sdl.C.SDLK_VOLUMEUP },
{ "volumedown", sdl.C.SDLK_VOLUMEDOWN },
{ "alterase", sdl.C.SDLK_ALTERASE },
{ "sysreq", sdl.C.SDLK_SYSREQ },
{ "cancel", sdl.C.SDLK_CANCEL },
{ "clear", sdl.C.SDLK_CLEAR },
{ "prior", sdl.C.SDLK_PRIOR },
{ "return2", sdl.C.SDLK_RETURN2 },
{ "separator", sdl.C.SDLK_SEPARATOR },
{ "out", sdl.C.SDLK_OUT },
{ "oper", sdl.C.SDLK_OPER },
{ "clearagain", sdl.C.SDLK_CLEARAGAIN },
{ "thsousandsseparator", sdl.C.SDLK_THOUSANDSSEPARATOR },
{ "decimalseparator", sdl.C.SDLK_DECIMALSEPARATOR },
{ "currencyunit", sdl.C.SDLK_CURRENCYUNIT },
{ "currencysubunit", sdl.C.SDLK_CURRENCYSUBUNIT },
{ "lctrl", sdl.C.SDLK_LCTRL },
{ "lshift", sdl.C.SDLK_LSHIFT },
{ "lalt", sdl.C.SDLK_LALT },
{ "lgui", sdl.C.SDLK_LGUI },
{ "rctrl", sdl.C.SDLK_RCTRL },
{ "rshift", sdl.C.SDLK_RSHIFT },
{ "ralt", sdl.C.SDLK_RALT },
{ "rgui", sdl.C.SDLK_RGUI },
{ "mode", sdl.C.SDLK_MODE },
{ "audionext", sdl.C.SDLK_AUDIONEXT },
{ "audioprev", sdl.C.SDLK_AUDIOPREV },
{ "audiostop", sdl.C.SDLK_AUDIOSTOP },
{ "audioplay", sdl.C.SDLK_AUDIOPLAY },
{ "audiomute", sdl.C.SDLK_AUDIOMUTE },
{ "mediaselect", sdl.C.SDLK_MEDIASELECT },
{ "www", sdl.C.SDLK_WWW },
{ "mail", sdl.C.SDLK_MAIL },
{ "calculator", sdl.C.SDLK_CALCULATOR },
{ "computer", sdl.C.SDLK_COMPUTER },
{ "appsearch", sdl.C.SDLK_AC_SEARCH },
{ "apphome", sdl.C.SDLK_AC_HOME },
{ "appback", sdl.C.SDLK_AC_BACK },
{ "appforward", sdl.C.SDLK_AC_FORWARD },
{ "appstop", sdl.C.SDLK_AC_STOP },
{ "apprefresh", sdl.C.SDLK_AC_REFRESH },
{ "appbookmarks", sdl.C.SDLK_AC_BOOKMARKS },
{ "brightnessdown", sdl.C.SDLK_BRIGHTNESSDOWN },
{ "brightnessup", sdl.C.SDLK_BRIGHTNESSUP },
{ "displayswitch", sdl.C.SDLK_DISPLAYSWITCH },
{ "kbdillumtoggle", sdl.C.SDLK_KBDILLUMTOGGLE },
{ "kbdillumdown", sdl.C.SDLK_KBDILLUMDOWN },
{ "kbdillumup", sdl.C.SDLK_KBDILLUMUP },
{ "eject", sdl.C.SDLK_EJECT },
{ "sleep", sdl.C.SDLK_SLEEP },
}
return Keyboard

Binary file not shown.

View File

@@ -0,0 +1,67 @@
local ROOT = (...):gsub('[^.]*$', '')
local ffi = require 'ffi'
local sdl = require(ROOT .. 'sdl2.init')
sdl.AudioCVT = ffi.typeof 'SDL_AudioCVT'
-- sdl.AudioDeviceEvent = ffi.typeof 'SDL_AudioDeviceEvent'
sdl.AudioSpec = ffi.typeof 'SDL_AudioSpec'
sdl.Color = ffi.typeof 'SDL_Color'
sdl.ControllerAxisEvent = ffi.typeof 'SDL_ControllerAxisEvent'
sdl.ControllerButtonEvent = ffi.typeof 'SDL_ControllerButtonEvent'
sdl.ControllerDeviceEvent = ffi.typeof 'SDL_ControllerDeviceEvent'
sdl.DisplayMode = ffi.typeof 'SDL_DisplayMode'
sdl.DollarGestureEvent = ffi.typeof 'SDL_DollarGestureEvent'
sdl.DropEvent = ffi.typeof 'SDL_DropEvent'
sdl.Event = ffi.typeof 'SDL_Event'
sdl.Finger = ffi.typeof 'SDL_Finger'
sdl.HapticCondition = ffi.typeof 'SDL_HapticCondition'
sdl.HapticConstant = ffi.typeof 'SDL_HapticConstant'
sdl.HapticCustom = ffi.typeof 'SDL_HapticCustom'
sdl.HapticDirection = ffi.typeof 'SDL_HapticDirection'
sdl.HapticEffect = ffi.typeof 'SDL_HapticEffect'
sdl.HapticLeftRight = ffi.typeof 'SDL_HapticLeftRight'
sdl.HapticPeriodic = ffi.typeof 'SDL_HapticPeriodic'
sdl.HapticRamp = ffi.typeof 'SDL_HapticRamp'
sdl.JoyAxisEvent = ffi.typeof 'SDL_JoyAxisEvent'
sdl.JoyBallEvent = ffi.typeof 'SDL_JoyBallEvent'
sdl.JoyButtonEvent = ffi.typeof 'SDL_JoyButtonEvent'
sdl.JoyDeviceEvent = ffi.typeof 'SDL_JoyDeviceEvent'
sdl.JoyHatEvent = ffi.typeof 'SDL_JoyHatEvent'
sdl.KeyboardEvent = ffi.typeof 'SDL_KeyboardEvent'
sdl.Keysym = ffi.typeof 'SDL_Keysym'
sdl.MessageBoxButtonData = ffi.typeof 'SDL_MessageBoxButtonData'
sdl.MessageBoxColor = ffi.typeof 'SDL_MessageBoxColor'
sdl.MessageBoxColorScheme = ffi.typeof 'SDL_MessageBoxColorScheme'
sdl.MessageBoxData = ffi.typeof 'SDL_MessageBoxData'
sdl.MouseButtonEvent = ffi.typeof 'SDL_MouseButtonEvent'
sdl.MouseMotionEvent = ffi.typeof 'SDL_MouseMotionEvent'
sdl.MouseWheelEvent = ffi.typeof 'SDL_MouseWheelEvent'
sdl.MultiGestureEvent = ffi.typeof 'SDL_MultiGestureEvent'
sdl.Palette = ffi.typeof 'SDL_Palette'
sdl.PixelFormat = ffi.typeof 'SDL_PixelFormat'
sdl.Point = ffi.typeof 'SDL_Point'
sdl.QuitEvent = ffi.typeof 'SDL_QuitEvent'
sdl.RWops = ffi.typeof 'SDL_RWops'
sdl.Rect = ffi.typeof 'SDL_Rect'
sdl.RendererInfo = ffi.typeof 'SDL_RendererInfo'
sdl.Surface = ffi.typeof 'SDL_Surface'
sdl.SysWMEvent = ffi.typeof 'SDL_SysWMEvent'
-- sdl.SysWMinfo = ffi.typeof 'SDL_SysWMinfo'
sdl.SysWMmsg = ffi.typeof 'SDL_SysWMmsg'
sdl.TextEditingEvent = ffi.typeof 'SDL_TextEditingEvent'
sdl.TextInputEvent = ffi.typeof 'SDL_TextInputEvent'
sdl.Texture = ffi.typeof 'SDL_Texture'
sdl.TouchFingerEvent = ffi.typeof 'SDL_TouchFingerEvent'
sdl.UserEvent = ffi.typeof 'SDL_UserEvent'
sdl.WindowEvent = ffi.typeof 'SDL_WindowEvent'
sdl.assert_data = ffi.typeof 'SDL_assert_data'
sdl.atomic_t = ffi.typeof 'SDL_atomic_t'
sdl.version = ffi.typeof 'SDL_version'
if sdl.init(sdl.INIT_VIDEO) ~= 0 then
io.stderr:write(ffi.string(sdl.getError()))
os.exit(1)
end
return sdl

View File

@@ -0,0 +1,36 @@
Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
Copyright (c) 2011-2013 NYU (Clement Farabet)
Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston)
Copyright (c) 2006 Idiap Research Institute (Samy Bengio)
Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the names of Deepmind Technologies, NYU, NEC Laboratories America
and IDIAP Research Institute nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,32 @@
sdl2-ffi
========
A LuaJIT interface to SDL2
# Installation #
First, make sure SDL2 is installed on your system. This package only requires the binary shared libraries (.so, .dylib, .dll).
Please see your package management system to install SDL2. You can also download yourself binaries on the
[SDL2 web page](http://libsdl.org/download-2.0.php)
```sh
luarocks install https://raw.github.com/torch/sdl2-ffi/master/rocks/sdl2-scm-1.rockspec
```
*Note*: this SDL interface supports only SDL2, not SDL 1.2.
# Usage #
```lua
local sdl = require 'sdl2'
sdl.init(sdl.INIT_VIDEO)
...
```
All SDL C functions are available in the `sdl` namespace returned by require. The only difference is the naming, which is not prefixed
by `SDL_` anymore. The same goes for all C defines (like `SDL_INIT_VIDEO`, which can now be accessed with `sdl.INIT_VIDEO`).
Although the interface is quite complete, there are still few defines not ported in this package. Fill free to post a message about it,
or to request pulls.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
-- Function definitions which were not output by
-- the C preprocessor
local sdl
local function registerdefines(sdl)
-- audio
function sdl.AUDIO_BITSIZE(x)
return bit.band(x, sdl.AUDIO_MASK_BITSIZE)
end
function sdl.AUDIO_ISFLOAT(x)
return bit.band(x, sdl.AUDIO_MASK_DATATYPE) ~= 0
end
function sdl.AUDIO_ISBIGENDIAN(x)
return bit.band(x, sdl.AUDIO_MASK_ENDIAN) ~= 0
end
function sdl.AUDIO_ISSIGNED(x)
return bit.band(x, sdl.AUDIO_MASK_SIGNED) ~= 0
end
function sdl.AUDIO_ISINT(x)
return not sdl.AUDIO_ISFLOAT(x)
end
function sdl.AUDIO_ISLITTLEENDIAN(x)
return not sdl.AUDIO_ISBIGENDIAN(x)
end
function sdl.AUDIO_ISUNSIGNED(x)
return not sdl.AUDIO_ISSIGNED(x)
end
function sdl.loadWAV(file, spec, audio_buf, audio_len)
return sdl.loadWAV_RW(sdl.RWFromFile(file, "rb"), 1, spec, audio_buf, audio_len)
end
-- surface
sdl.blitSurface = sdl.upperBlit
function sdl.MUSTLOCK(S)
return bit.band(S.flags, sdl.RLEACCEL)
end
function sdl.loadBMP(file)
return sdl.loadBMP_RW(sdl.RWFromFile(file, 'rb'), 1)
end
function sdl.saveBMP(surface, file)
return sdl.saveBMP_RW(surface, sdl.RWFromFile(file, 'wb'), 1)
end
end
return registerdefines

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
local REL = (...):gsub('[^.]*$', '')
local sdl = require(REL .. 'sdl')
local SpriteBatch = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
--[[
spriteBatch = SpriteBatch( image, size )
Arguments
Image image
The Image to use for the sprites.
number size (1000)
The max number of sprites.
Returns
SpriteBatch spriteBatch
The new SpriteBatch.
--]]
function SpriteBatch:constructor (image)
self.image = image
self.sprites = {}
end
--[[
id = SpriteBatch:add( quad, x, y, r, sx, sy )
Arguments
Quad quad
The Quad to add.
number x
The position to draw the object (x-axis).
number y
The position to draw the object (y-axis).
number r (0)
Orientation (radians). (not implemented)
number sx (1)
Scale factor (x-axis).
number sy (sx)
Scale factor (y-axis).
Returns
number id
An identifier for the added sprite.
--]]
function SpriteBatch:add (quad, x, y, r, sx, sy)
local sprites = self.sprites
sprites[#sprites + 1] = { quad = quad, x = x, y = y,
sx = sx or 1, sy = sy or 1 }
end
function SpriteBatch:draw ()
local image = self.image
local renderer = image.sdlRenderer
local texture = image.sdlTexture
for _, sprite in ipairs(self.sprites) do
local quad = sprite.quad
local w = math.ceil(quad[3] * sprite.sx)
local h = math.ceil(quad[4] * sprite.sy)
local src = sdl.Rect(quad)
local dst = sdl.Rect(sprite.x, sprite.y, w, h)
sdl.renderCopy(renderer, texture, src, dst)
end
end
return SpriteBatch

133
luigi/backend/love.lua Normal file
View File

@@ -0,0 +1,133 @@
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
local Base = require(ROOT .. 'base')
local Hooker = require(ROOT .. 'hooker')
local Backend = {}
Backend.run = function () end
Backend.Cursor = love.mouse.newCursor
Backend.Font = require(ROOT .. 'backend.love.font')
Backend.Image = love.graphics.newImage
Backend.Quad = love.graphics.newQuad
Backend.SpriteBatch = love.graphics.newSpriteBatch
Backend.draw = love.graphics.draw
Backend.drawRectangle = love.graphics.rectangle
Backend.print = love.graphics.print
Backend.printf = love.graphics.printf
Backend.getClipboardText = love.system.getClipboardText
Backend.setClipboardText = love.system.setClipboardText
Backend.getMousePosition = love.mouse.getPosition
Backend.getMousePosition = love.mouse.getPosition
Backend.getSystemCursor = love.mouse.getSystemCursor
Backend.getWindowSize = function ()
return love.graphics.getWidth(), love.graphics.getHeight()
end
Backend.getTime = love.timer.getTime
Backend.isKeyDown = love.keyboard.isDown
Backend.isMouseDown = love.mouse.isDown
Backend.pop = love.graphics.pop
local push = love.graphics.push
Backend.push = function ()
return push 'all'
end
Backend.quit = love.event.quit
Backend.setColor = love.graphics.setColor
Backend.setCursor = love.mouse.setCursor
Backend.setFont = function (font)
return love.graphics.setFont(font.loveFont)
end
Backend.setScissor = love.graphics.setScissor
function Backend.hide (layout)
for _, item in ipairs(layout.hooks) do
Hooker.unhook(item)
end
layout.hooks = {}
end
local function hook (layout, key, method, hookLast)
layout.hooks[#layout.hooks + 1] = Hooker.hook(love, key, method, hookLast)
end
local getMouseButtonId, isMouseDown
if love._version_minor < 10 then
getMouseButtonId = function (value)
return value == 'l' and 1
or value == 'r' and 2
or value == 'm' and 3
end
isMouseDown = function ()
return love.mouse.isDown('l', 'r', 'm')
end
else
getMouseButtonId = function (value)
return value
end
isMouseDown = function ()
return love.mouse.isDown(1, 2, 3)
end
end
function Backend.show (layout)
local input = layout.input
hook(layout, 'draw', function ()
input:handleDisplay(layout)
end, true)
hook(layout, 'resize', function (width, height)
return input:handleReshape(layout, width, height)
end)
hook(layout, 'mousepressed', function (x, y, button)
return input:handlePressStart(layout, getMouseButtonId(button), x, y)
end)
hook(layout, 'mousereleased', function (x, y, button)
return input:handlePressEnd(layout, getMouseButtonId(button), x, y)
end)
hook(layout, 'mousemoved', function (x, y, dx, dy)
if isMouseDown() then
return input:handlePressedMove(layout, x, y)
else
return input:handleMove(layout, x, y)
end
end)
hook(layout, 'keypressed', function (key, isRepeat)
return input:handleKeyPress(layout, key, Backend.getMousePosition())
end)
hook(layout, 'keyreleased', function (key)
return input:handleKeyRelease(layout, key, Backend.getMousePosition())
end)
hook(layout, 'textinput', function (text)
return input:handleTextInput(layout, text, Backend.getMousePosition())
end)
end
return Backend

View File

@@ -0,0 +1,65 @@
local Font = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
local fontCache = {}
function Font:constructor (path, size, color)
if not size then
size = 12
end
if not color then
color = { 0, 0, 0 }
end
local key = (path or '') .. '_' .. size
if not fontCache[key] then
if path then
fontCache[key] = love.graphics.newFont(path, size)
else
fontCache[key] = love.graphics.newFont(size)
end
end
self.loveFont = fontCache[key]
self.color = color
end
function Font:setAlignment (align)
self.align = align
end
function Font:setWidth (width)
self.width = width
end
function Font:getLineHeight ()
return self.loveFont:getHeight()
end
function Font:getAscender ()
return self.loveFont:getAscent()
end
function Font:getDescender ()
return self.loveFont:getDescent()
end
function Font:getAdvance (text)
return (self.loveFont:getWidth(text))
end
if love._version_minor < 10 then
function Font:getWrappedHeight (text)
local _, lines = self.loveFont:getWrap(text, self.width)
return lines * self.loveFont:getHeight()
end
else
function Font:getWrappedHeight (text)
local _, lines = self.loveFont:getWrap(text, self.width)
return #lines * self.loveFont:getHeight()
end
end
return Font

View File

@@ -1,67 +1,5 @@
local ROOT = (...):gsub('[^.]*$', '')
local Base = require(ROOT .. 'base')
local Backend = require(ROOT .. 'backend')
local Font = Base:extend()
local cache = {}
function Font:constructor (path, size, color)
if not size then
size = 12
end
if not color then
color = { 0, 0, 0 }
end
local key = (path or '') .. '_' .. size
if not cache[key] then
if path then
cache[key] = love.graphics.newFont(path, size)
else
cache[key] = love.graphics.newFont(size)
end
end
self.layout = {}
self.font = cache[key]
self.color = color
end
function Font:setAlignment (align)
self.layout.align = align
end
function Font:setWidth (width)
self.layout.width = width
end
function Font:getLineHeight ()
return self.font:getHeight()
end
function Font:getAscender ()
return self.font:getAscent()
end
function Font:getDescender ()
return self.font:getDescent()
end
function Font:getAdvance (text)
return (self.font:getWidth(text))
end
if love._version_minor < 10 then
function Font:getWrappedHeight (text)
local _, lines = self.font:getWrap(text, self.layout.width)
return lines * self.font:getHeight()
end
else
function Font:getWrappedHeight (text)
local _, lines = self.font:getWrap(text, self.layout.width)
return #lines * self.font:getHeight()
end
end
return Font
return Backend.Font

View File

@@ -1,5 +1,6 @@
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Renderer = require(ROOT .. 'renderer')
@@ -21,9 +22,9 @@ function Input:handleDisplay (layout)
end
function Input:getModifierFlags ()
local alt = love.keyboard.isDown('lalt', 'ralt') and 1 or 0
local ctrl = love.keyboard.isDown('lctrl', 'rctrl') and 2 or 0
local shift = love.keyboard.isDown('lshift', 'rshift') and 4 or 0
local alt = Backend.isKeyDown('lalt', 'ralt') and 1 or 0
local ctrl = Backend.isKeyDown('lctrl', 'rctrl') and 2 or 0
local shift = Backend.isKeyDown('lshift', 'rshift') and 4 or 0
return alt + ctrl + shift
end
@@ -96,9 +97,9 @@ function Input:handleMove (layout, x, y)
x = x, y = y
})
if widget.cursor then
love.mouse.setCursor(love.mouse.getSystemCursor(widget.cursor))
Backend.setCursor(Backend.getSystemCursor(widget.cursor))
else
love.mouse.setCursor()
Backend.setCursor()
end
self.previousMoveWidget = widget
end

View File

@@ -16,12 +16,13 @@ and are trapped by the first layout that can handle the event
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Widget = require(ROOT .. 'widget')
local Input = require(ROOT .. 'input')
local Style = require(ROOT .. 'style')
local Hooker = require(ROOT .. 'hooker')
local Backend = require(ROOT .. 'backend')
local Layout = Base:extend()
@@ -50,8 +51,8 @@ function Layout:constructor (data, master)
self:addDefaultHandlers()
self.isShown = false
self.hooks = {}
self.isShown = false
self.root = data
Widget(self, data)
@@ -136,15 +137,17 @@ Hooks all appropriate Love events and callbacks.
--]]--
function Layout:show ()
if self.isShown then
self:unhook() -- return
Backend.hide(self)
self.isShown = nil
end
self.isShown = true
if not self.input then
self.input = Input.default -- Input(self)
end
self:manageInput()
Backend.show(self)
self.root:reshape()
end
@@ -158,7 +161,7 @@ function Layout:hide ()
return
end
self.isShown = nil
self:unhook()
Backend.hide(self)
end
--[[--
@@ -251,7 +254,7 @@ function Layout:addDefaultHandlers ()
-- tab/shift-tab cycles focused widget
if event.key == 'tab' then
if love.keyboard.isDown('lshift', 'rshift') then
if Backend.isKeyDown('lshift', 'rshift') then
self:focusPreviousWidget()
else
self:focusNextWidget()
@@ -301,104 +304,6 @@ function Layout:addDefaultHandlers ()
end)
end
-- event stuff
function Layout:hook (key, method, hookLast)
self.hooks[#self.hooks + 1] = Hooker.hook(love, key, method, hookLast)
end
function Layout:unhook ()
for _, item in ipairs(self.hooks) do
Hooker.unhook(item)
end
self.hooks = {}
end
local getMouseButtonId, isMouseDown
if love._version_minor < 10 then
getMouseButtonId = function (value)
return value == 'l' and 1
or value == 'r' and 2
or value == 'm' and 3
end
isMouseDown = function ()
return love.mouse.isDown('l', 'r', 'm')
end
else
getMouseButtonId = function (value)
return value
end
isMouseDown = function ()
return love.mouse.isDown(1, 2, 3)
end
end
function Layout:manageInput ()
if self.isShown then
return
end
self.isShown = true
local input = self.input
self:hook('draw', function ()
input:handleDisplay(self)
end, true)
self:hook('resize', function (width, height)
return input:handleReshape(self, width, height)
end)
self:hook('mousepressed', function (x, y, button)
return input:handlePressStart(self, getMouseButtonId(button), x, y)
end)
self:hook('mousereleased', function (x, y, button)
return input:handlePressEnd(self, getMouseButtonId(button), x, y)
end)
self:hook('mousemoved', function (x, y, dx, dy)
if isMouseDown() then
return input:handlePressedMove(self, x, y)
else
return input:handleMove(self, x, y)
end
end)
self:hook('keypressed', function (key, isRepeat)
return input:handleKeyPress(
self, key, love.mouse.getX(), love.mouse.getY())
end)
self:hook('keyreleased', function (key)
return input:handleKeyRelease(
self, key, love.mouse.getX(), love.mouse.getY())
end)
self:hook('textinput', function (text)
return input:handleTextInput(
self, text, love.mouse.getX(), love.mouse.getY())
end)
end
-- event binders
Event.injectBinders(Layout)
-- ffi
function Layout:showMessage () end
local _, ffi = pcall(require, 'ffi')
if ffi then
ffi.cdef [[
int SDL_ShowSimpleMessageBox(
uint32_t flags,
const char* title,
const char* message,
void* window
);
]]
function Layout:showMessage (title, message)
ffi.C.SDL_ShowSimpleMessageBox(0, title, message, nil)
end
end
return Layout

View File

@@ -1,5 +1,6 @@
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Font = require(ROOT .. 'font')
@@ -11,7 +12,7 @@ local sliceCache = {}
function Renderer:loadImage (path)
if not imageCache[path] then
imageCache[path] = love.graphics.newImage(path)
imageCache[path] = Backend.Image(path)
end
return imageCache[path]
@@ -26,7 +27,7 @@ function Renderer:loadSlices (path)
local image = self:loadImage(path)
local iw, ih = image:getWidth(), image:getHeight()
local w, h = math.floor(iw / 3), math.floor(ih / 3)
local Quad = love.graphics.newQuad
local Quad = Backend.Quad
slices.image = image
slices.width = w
@@ -49,6 +50,7 @@ function Renderer:loadSlices (path)
end
function Renderer:renderSlices (widget)
local path = widget.slices
if not path then return end
@@ -56,7 +58,7 @@ function Renderer:renderSlices (widget)
local slices = self:loadSlices(path)
local batch = love.graphics.newSpriteBatch(slices.image)
local batch = Backend.SpriteBatch(slices.image)
local xScale = ((x2 - x1) - slices.width * 2) / slices.width
local yScale = ((y2 - y1) - slices.height * 2) / slices.height
@@ -79,27 +81,27 @@ function Renderer:renderSlices (widget)
batch:add(slices.bottomLeft, x1, y2 - slices.height)
batch:add(slices.bottomRight, x2 - slices.width, y2 - slices.height)
love.graphics.draw(batch)
Backend.draw(batch)
end
function Renderer:renderBackground (widget)
if not widget.background then return end
local x1, y1, x2, y2 = widget:getRectangle(true)
love.graphics.push('all')
love.graphics.setColor(widget.background)
love.graphics.rectangle('fill', x1, y1, x2 - x1, y2 - y1)
love.graphics.pop()
Backend.push()
Backend.setColor(widget.background)
Backend.drawRectangle('fill', x1, y1, x2 - x1, y2 - y1)
Backend.pop()
end
function Renderer:renderOutline (widget)
if not widget.outline then return end
local x1, y1, x2, y2 = widget:getRectangle(true)
love.graphics.push('all')
love.graphics.setColor(widget.outline)
love.graphics.rectangle('line', x1 + 0.5, y1 + 0.5, x2 - x1, y2 - y1)
love.graphics.pop()
Backend.push()
Backend.setColor(widget.outline)
Backend.drawRectangle('line', x1 + 0.5, y1 + 0.5, x2 - x1, y2 - y1)
Backend.pop()
end
-- returns icon coordinates and rectangle with remaining space
@@ -188,9 +190,9 @@ function Renderer:renderIconAndText (widget)
return
end
love.graphics.push('all')
Backend.push()
love.graphics.setScissor(x1, y1, x2 - x1, y2 - y1)
Backend.setScissor(x1, y1, x2 - x1, y2 - y1)
local iconX, iconY, textX, textY, font
@@ -228,23 +230,20 @@ function Renderer:renderIconAndText (widget)
if icon then
iconX, iconY = math.floor(iconX), math.floor(iconY)
if widget.tint then
love.graphics.setBlendMode('alpha', true)
love.graphics.setColor(widget.tint)
Backend.setColor(widget.tint)
end
love.graphics.draw(icon, iconX, iconY)
Backend.draw(icon, iconX, iconY)
end
-- draw the text
if text and x2 > x1 then
textX, textY = math.floor(textX), math.floor(textY)
love.graphics.setFont(font.font)
love.graphics.setColor(font.color)
local layout = font.layout
love.graphics.printf(text, textX, textY, x2 - x1, layout.align)
Backend.setFont(font)
Backend.setColor(font.color)
Backend.printf(text, textX, textY, x2 - x1, font.align)
end
love.graphics.pop()
Backend.pop()
end
function Renderer:renderChildren (widget)

318
luigi/utf8.lua Normal file
View File

@@ -0,0 +1,318 @@
--utf8 module (Cosmin Apreutesei, public domain).
--byte indices are i's, char (codepoint) indices are ci's.
--invalid characters are counted as 1-byte chars so they don't get lost. validate/sanitize beforehand as needed.
local utf8 = {}
--byte index of the next char after the char at byte index i, followed by a valid flag for the char at byte index i.
--nil if not found. invalid characters are iterated as 1-byte chars.
function utf8.next_raw(s, i)
if not i then
if #s == 0 then return nil end
return 1, true --fake flag (doesn't matter since this flag is not to be taken as full validation)
end
if i > #s then return end
local c = s:byte(i)
if c >= 0x00 and c <= 0x7F then
i = i + 1
elseif c >= 0xC2 and c <= 0xDF then
i = i + 2
elseif c >= 0xE0 and c <= 0xEF then
i = i + 3
elseif c >= 0xF0 and c <= 0xF4 then
i = i + 4
else --invalid
return i + 1, false
end
if i > #s then return end
return i, true
end
--next() is the generic iterator and can be replaced for different semantics. next_raw() must preserve its semantics.
utf8.next = utf8.next_raw
--iterate chars, returning the byte index where each char starts
function utf8.byte_indices(s, previ)
return utf8.next, s, previ
end
--number of chars in string
function utf8.len(s)
local len = 0
for _ in utf8.byte_indices(s) do
len = len + 1
end
return len
end
--byte index given char index. nil if the index is outside the string.
function utf8.byte_index(s, target_ci)
if target_ci < 1 then return end
local ci = 0
for i in utf8.byte_indices(s) do
ci = ci + 1
if ci == target_ci then
return i
end
end
assert(target_ci > ci, 'invalid index')
end
--char index given byte index. nil if the index is outside the string.
function utf8.char_index(s, target_i)
if target_i < 1 or target_i > #s then return end
local ci = 0
for i in utf8.byte_indices(s) do
ci = ci + 1
if i == target_i then
return ci
end
end
error'invalid index'
end
--byte index of the prev. char before the char at byte index i, which defaults to #s + 1.
--nil if the index is outside the 2..#s+1 range.
--NOTE: unlike next(), this is a O(N) operation!
function utf8.prev(s, nexti)
nexti = nexti or #s + 1
if nexti <= 1 or nexti > #s + 1 then return end
local lasti, lastvalid = utf8.next(s)
for i, valid in utf8.byte_indices(s) do
if i == nexti then
return lasti, lastvalid
end
lasti, lastvalid = i, valid
end
if nexti == #s + 1 then
return lasti, lastvalid
end
error'invalid index'
end
--iterate chars in reverse order, returning the byte index where each char starts.
function utf8.byte_indices_reverse(s, nexti)
if #s < 200 then
--using prev() is a O(N^2/2) operation, ok for small strings (200 chars need 40,000 iterations)
return utf8.prev, s, nexti
else
--store byte indices in a table and iterate them in reverse.
--this is 40x slower than byte_indices() but still fast at 2mil chars/second (but eats RAM and makes garbage).
local t = {}
for i in utf8.byte_indices(s) do
if nexti and i >= nexti then break end
table.insert(t, i)
end
local i = #t + 1
return function()
i = i - 1
return t[i]
end
end
end
--sub based on char indices, which, unlike with standard string.sub(), can't be negative.
--start_ci can be 1..inf and end_ci can be 0..inf. end_ci can be nil meaning last char.
--if start_ci is out of range or end_ci < start_ci, the empty string is returned.
--if end_ci is out of range, it is considered to be the last position in the string.
function utf8.sub(s, start_ci, end_ci)
--assert for positive indices because we might implement negative indices in the future.
assert(start_ci >= 1)
assert(not end_ci or end_ci >= 0)
local ci = 0
local start_i, end_i
for i in utf8.byte_indices(s) do
ci = ci + 1
if ci == start_ci then
start_i = i
end
if ci == end_ci then
end_i = i
end
end
if not start_i then
assert(start_ci > ci, 'invalid index')
return ''
end
if end_ci and not end_i then
if end_ci < start_ci then
return ''
end
assert(end_ci > ci, 'invalid index')
end
return s:sub(start_i, end_i)
end
--check if a string contains a substring at byte index i without making garbage.
--nil if the index is out of range. true if searching for the empty string.
function utf8.contains(s, i, sub)
if i < 1 or i > #s then return nil end
for si = 1, #sub do
if s:byte(i + si - 1) ~= sub:byte(si) then
return false
end
end
return true
end
--count the number of occurences of a substring in a string. the substring cannot be the empty string.
function utf8.count(s, sub)
assert(#sub > 0)
local count = 0
local i = 1
while i do
if utf8.contains(s, i, sub) then
count = count + 1
i = i + #sub
if i > #s then break end
else
i = utf8.next(s, i)
end
end
return count
end
--utf8 validation and sanitization
--check if there's a valid utf8 codepoint at byte index i. valid ranges for each utf8 byte are:
-- byte 1 2 3 4
--------------------------------------------
-- 00 - 7F
-- C2 - DF 80 - BF
-- E0 A0 - BF 80 - BF
-- E1 - EC 80 - BF 80 - BF
-- ED 80 - 9F 80 - BF
-- EE - EF 80 - BF 80 - BF
-- F0 90 - BF 80 - BF 80 - BF
-- F1 - F3 80 - BF 80 - BF 80 - BF
-- F4 80 - 8F 80 - BF 80 - BF
function utf8.isvalid(s, i)
local c = s:byte(i)
if not c then
return false
elseif c >= 0x00 and c <= 0x7F then
return true
elseif c >= 0xC2 and c <= 0xDF then
local c2 = s:byte(i + 1)
return c2 and c2 >= 0x80 and c2 <= 0xBF
elseif c >= 0xE0 and c <= 0xEF then
local c2 = s:byte(i + 1)
local c3 = s:byte(i + 2)
if c == 0xE0 then
return c2 and c3 and
c2 >= 0xA0 and c2 <= 0xBF and
c3 >= 0x80 and c3 <= 0xBF
elseif c >= 0xE1 and c <= 0xEC then
return c2 and c3 and
c2 >= 0x80 and c2 <= 0xBF and
c3 >= 0x80 and c3 <= 0xBF
elseif c == 0xED then
return c2 and c3 and
c2 >= 0x80 and c2 <= 0x9F and
c3 >= 0x80 and c3 <= 0xBF
elseif c >= 0xEE and c <= 0xEF then
if c == 0xEF and c2 == 0xBF and (c3 == 0xBE or c3 == 0xBF) then
return false --uFFFE and uFFFF non-characters
end
return c2 and c3 and
c2 >= 0x80 and c2 <= 0xBF and
c3 >= 0x80 and c3 <= 0xBF
end
elseif c >= 0xF0 and c <= 0xF4 then
local c2 = s:byte(i + 1)
local c3 = s:byte(i + 2)
local c4 = s:byte(i + 3)
if c == 0xF0 then
return c2 and c3 and c4 and
c2 >= 0x90 and c2 <= 0xBF and
c3 >= 0x80 and c3 <= 0xBF and
c4 >= 0x80 and c4 <= 0xBF
elseif c >= 0xF1 and c <= 0xF3 then
return c2 and c3 and c4 and
c2 >= 0x80 and c2 <= 0xBF and
c3 >= 0x80 and c3 <= 0xBF and
c4 >= 0x80 and c4 <= 0xBF
elseif c == 0xF4 then
return c2 and c3 and c4 and
c2 >= 0x80 and c2 <= 0x8F and
c3 >= 0x80 and c3 <= 0xBF and
c4 >= 0x80 and c4 <= 0xBF
end
end
return false
end
--byte index of the next valid utf8 char after the char at byte index i.
--nil if indices go out of range. invalid characters are skipped.
function utf8.next_valid(s, i)
local valid
i, valid = utf8.next_raw(s, i)
while i and (not valid or not utf8.isvalid(s, i)) do
i, valid = utf8.next(s, i)
end
return i
end
--iterate valid chars, returning the byte index where each char starts
function utf8.valid_byte_indices(s)
return utf8.next_valid, s
end
--assert that a string only contains valid utf8 characters
function utf8.validate(s)
for i, valid in utf8.byte_indices(s) do
if not valid or not utf8.isvalid(s, i) then
error(string.format('invalid utf8 char at #%d', i))
end
end
end
local function table_lookup(s, i, j, t)
return t[s:sub(i, j)]
end
--replace characters in string based on a function f(s, i, j, ...) -> replacement_string | nil
function utf8.replace(s, f, ...)
if type(f) == 'table' then
return utf8.replace(s, table_lookup, f)
end
if s == '' then
return s
end
local t = {}
local lasti = 1
for i in utf8.byte_indices(s) do
local nexti = utf8.next(s, i) or #s + 1
local repl = f(s, i, nexti - 1, ...)
if repl then
table.insert(t, s:sub(lasti, i - 1))
table.insert(t, repl)
lasti = nexti
end
end
table.insert(t, s:sub(lasti))
return table.concat(t)
end
local function replace_invalid(s, i, j, repl_char)
if not utf8.isvalid(s, i) then
return repl_char
end
end
--replace invalid utf8 chars with a replacement char
function utf8.sanitize(s, repl_char)
repl_char = repl_char or '<EFBFBD>' --\uFFFD
return utf8.replace(s, replace_invalid, repl_char)
end
-- added for partial compatibility with lua 5.3
function utf8.offset(s, char_offset, byte_index)
local ci = utf8.char_index(s, byte_index)
return utf8.byte_index(s, ci + char_offset)
end
utf8.codes = utf8.byte_indices
return utf8

View File

@@ -6,6 +6,7 @@ Widget class.
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Event = require(ROOT .. 'event')
local Font = require(ROOT .. 'font')
@@ -340,8 +341,9 @@ function Widget:calculateDimension (name)
local min = (name == 'width') and (self.minwidth or 0)
or (self.minheight or 0)
local max = name == 'width' and love.graphics.getWidth()
or love.graphics.getHeight()
local windowWidth, windowHeight = Backend.getWindowSize()
local max = name == 'width' and windowWidth or windowHeight
if self[name] then
self.dimensions[name] = clamp(self[name], min, max)

View File

@@ -1,7 +1,7 @@
local utf8 = require 'utf8'
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
local blendMultiply = love._version_minor < 10 and 'multiplicative'
or 'multiply'
local utf8 = require(ROOT .. 'utf8')
local Backend = require(ROOT .. 'backend')
local function scrollToCaret (self)
local x1, y1, x2, y2 = self:getRectangle(true, true)
@@ -23,9 +23,9 @@ local function scrollToCaret (self)
end
local function findCaretFromText (self, text)
local font = self.fontData.font
local font = self.fontData
local x1, y1, x2, y2 = self:getRectangle(true, true)
return #text, font:getWidth(text) + x1 - self.scrollX
return #text, font:getAdvance(text) + x1 - self.scrollX
end
local function setCaretFromText (self, text, mode)
@@ -45,15 +45,14 @@ end
local function findCaretFromPoint (self, x, y)
local x1, y1, x2, y2 = self:getRectangle(true, true)
local font = self.fontData.font
local font = self.fontData
local width, lastWidth = 0
local characters = utf8.codes(self.value)
local lastPosition = 0
local function checkPosition (position)
local text = self.value:sub(1, position - 1)
lastWidth = width
width = font:getWidth(text)
width = font:getAdvance(text)
if width > x + self.scrollX - x1 then
if position == 1 then
return 0, x1 - self.scrollX
@@ -149,13 +148,13 @@ local function copyRangeToClipboard (self)
local text = self.value
local first, last = getRange(self)
if last >= first + 1 then
love.system.setClipboardText(text:sub(first + 1, last))
Backend.setClipboardText(text:sub(first + 1, last))
end
end
local function pasteFromClipboard (self)
local text = self.value
local pasted = love.system.getClipboardText() or ''
local pasted = Backend.getClipboardText() or ''
local first, last = getRange(self)
local left = text:sub(1, first) .. pasted
text = left .. text:sub(last + 1)
@@ -203,22 +202,22 @@ return function (self)
elseif event.key == 'left' then
moveCaretLeft(self, love.keyboard.isDown('lshift', 'rshift'))
moveCaretLeft(self, Backend.isKeyDown('lshift', 'rshift'))
elseif event.key == 'right' then
moveCaretRight(self, love.keyboard.isDown('lshift', 'rshift'))
moveCaretRight(self, Backend.isKeyDown('lshift', 'rshift'))
elseif event.key == 'x' and love.keyboard.isDown('lctrl', 'rctrl') then
elseif event.key == 'x' and Backend.isKeyDown('lctrl', 'rctrl') then
copyRangeToClipboard(self)
deleteRange(self)
elseif event.key == 'c' and love.keyboard.isDown('lctrl', 'rctrl') then
elseif event.key == 'c' and Backend.isKeyDown('lctrl', 'rctrl') then
copyRangeToClipboard(self)
elseif event.key == 'v' and love.keyboard.isDown('lctrl', 'rctrl') then
elseif event.key == 'v' and Backend.isKeyDown('lctrl', 'rctrl') then
pasteFromClipboard(self)
@@ -230,28 +229,30 @@ return function (self)
local startX, endX = self.startX or 0, self.endX or 0
local x1, y1, x2, y2 = self:getRectangle(true, true)
local width, height = endX - startX, y2 - y1
local fontData = self.fontData
local font = fontData.font
local textColor = fontData.color
local textTop = math.floor(y1 + ((y2 - y1) - font:getHeight()) / 2)
local font = self.fontData
local textColor = font.color
local textTop = math.floor(y1 + ((y2 - y1) - font:getLineHeight()) / 2)
love.graphics.push('all')
love.graphics.setScissor(x1, y1, x2 - x1, y2 - y1)
love.graphics.setFont(font)
love.graphics.setColor(textColor)
love.graphics.print(self.value, x1 - self.scrollX, textTop)
Backend.push()
Backend.setScissor(x1, y1, x2 - x1, y2 - y1)
Backend.setFont(font)
-- draw highlight
Backend.setColor(self.highlight)
Backend.drawRectangle('fill', startX, y1, width, height)
if Backend.getTime() % 2 < 1.75 then
Backend.setColor(textColor)
Backend.drawRectangle('fill', endX, y1, 1, height)
end
-- draw text
Backend.setColor(textColor)
Backend.print(self.value, x1 - self.scrollX, textTop)
if not self.focused then
love.graphics.pop()
Backend.pop()
return
end
-- TODO: expose highlight blend mode for dark themes
love.graphics.setBlendMode(blendMultiply)
love.graphics.setColor(self.highlight)
love.graphics.rectangle('fill', startX, y1, width, height)
if love.timer.getTime() % 2 < 1.75 then
love.graphics.setColor(textColor)
love.graphics.rectangle('fill', endX, y1, 1, height)
end
love.graphics.pop()
Backend.pop()
end)
end