Важно! Весь код, представленный в следующих шагах, является, по большей части, демонстрационным, и в реальных системах быстро работать, скорее всего, не будет. Язык Lua, хоть и является одним из самых быстрых скриптовых языков, все-таки не заточен на работу в run-time. Код и объяснения к нему предназначены быть скорее ориентирами, глядя на которые стоит разрабатывать собственную программу (за которую, я надеюсь, вам заплатят много-много денюжков :). |
local base = _G
module('Widget')
meta = { __index = _M }
local Factory = base.require('Factory')
function new()
return Factory.create(_M)
end
function construct(self)
self.x = 0
self.y = 0
self.w = 0
self.h = 0
self.color = {1, 1, 1}
end
function destroy(self)
end
-- установить размер виджета
function setSize(self, w, h)
self.w = w
self.h = h
end
-- установить позицию виджета
function setPosition(self, x, y)
self.x = x
self.y = y
end
-- установить цвет виджета
function setColor(self, color)
self.color = color
end
-- метод, в котором виджет может изменить своё внешнее представление
function update()
end
-- рисование виджета
function draw(self)
end
-- обработчик события нажатия кнопки мыши внутри виджета
-- Внимание! Координаты курсора приходят в экранных координатах от
верхнего левого угла.
function onMouseDown(x, y, button)
end
local base = _G
module('Render')
local gl = base.require('opengl')
-- инициализация
function create()
gl.Create()
end
-- очистить экран
function clear()
gl.Clear()
end
-- цвет очистки экрана
function clearColor(r, g, b, a)
gl.ClearColor(r, g, b, a)
end
-- установка вьюпорта
function viewport(x, y, w, h)
gl.Viewport(x, y, w, h)
end
-- установка перспективной матрицы проекций
function perspective(fov, aspect, near, far)
gl.MatrixMode(gl.PROJECTION)
gl.LoadIdentity()
gl.uPerspective(fov, aspect, near, far)
gl.MatrixMode(gl.MODELVIEW)
end
-- установка параллельной матрицы проекций
function ortho(left, right, bottom, top, near, far)
gl.MatrixMode(gl.PROJECTION)
gl.LoadIdentity()
gl.Ortho(left, right, bottom, top, near,
far)
gl.MatrixMode(gl.MODELVIEW)
end
-- модельная матрица умножается на матрицу смещения
function translate(x, y, z)
gl.Translate(x, y, z)
end
-- модельная матрица умножается на матрицу поворота
function rotate(angle, x, y, z)
gl.Rotate(angle, x, y, z)
end
-- сброс модельной матрицы в единичную матрицу
function identity()
gl.LoadIdentity()
end
-- задать текущий цвет
function color(c)
gl.Color(c[1], c[2], c[3], c[4])
end
-- вывод вершины
-- v - таблица
function vertex(v)
local tex = v.tex
if tex then
gl.TexCoord2(tex[1], tex[2])
end
local norm = v.norm
if norm then
gl.Normal(norm[1], norm[2], norm[3])
end
gl.Vertex(v[1], v[2], v[3])
end
-- рисование линий
function lines(vertices)
gl.Begin(gl.LINES)
for i = 1, #vertices do
vertex(vertices[i])
end
gl.End()
end
-- рисование треугольников
function triangles(vertices)
gl.Begin(gl.TRIANGLES)
for i = 1, #vertices do
vertex(vertices[i])
end
gl.End()
end
-- рисование четырехугольников
function quads(vertices)
gl.Begin(gl.QUADS)
for i = 1, #vertices do
vertex(vertices[i])
end
gl.End()
end
-- рисование прямоугольной рамки
function rect(x, y, w, h)
gl.PushMatrix()
translate(0.5, 0.5, 0) -- рисуем "между" пикселями
gl.Begin(gl.LINE_LOOP)
vertex({x, y, 0})
vertex({x + w, y, 0})
vertex({x + w, y + h,
0})
vertex({x, y + h, 0})
gl.End()
gl.PopMatrix()
end
-- рисование прямоугольника, заполненного текущим цветом
function fillrect(x, y, w, h)
gl.Begin(gl.QUADS)
vertex({x, y, 0})
vertex({x + w, y, 0})
vertex({x + w, y + h,
0})
vertex({x, y + h, 0})
gl.End()
end
-- рисование виджета
function draw(self)
-- установить текущий цвет
Render.color(self.color)
-- заполнить прямоугольник текущим цветом
Render.fillrect(self.x, self.y, self.w, self.h)
end
local base = _G
module('GUI')
local sdl = base.require('sdl')
local Render = base.require('Render')
-- инициализация
-- w, h - размеры окна
-- title - текст в заголовке окна
-- fullscreen - полноэкранный режим или нет
function create(w, h, title, fullscreen)
Render.create()
sdl.Create(w, h, title, fullscreen)
-- цвет экрана черный
Render.clearColor(0, 0, 0)
-- изменение размера окна
-- при запуске SDL не генерирует событие SDL_VIDEORESIZE
-- делаем это самостоятельно
resize(w, h)
-- список всех виджетов
widgets = {}
-- текщее положение мыши
mouseX = 0
mouseY = 0
end
-- добавить виджет
function addWidget(w)
base.table.insert(widgets, w)
end
-- размер окна изменен
function resize(w, h)
Render.viewport(0, 0, w, h)
Render.ortho(0, w, h, 0, -1, 1)
end
-- мышь переместили
function mouseMove(x, y)
mouseX = x
mouseY = y
end
-- проверка нахождения точки внутри виджета
function pointInWidget(x, y, widget)
return x > widget.x and
x < widget.x + widget.w and
y > widget.y and
y < widget.y + widget.h
end
-- нажали мышь
function mouseDown(button)
for _, widget in base.ipairs(widgets) do
if pointInWidget(mouseX, mouseY, widget)
then
widget:onMouseDown(mouseX, mouseY, button)
end
end
end
-- отпустили мышь
function mouseUp(x, y)
end
-- обновление GUI
function idle()
for i = 1, #widgets do
widgets[i]:update()
end
end
-- рисование
function draw()
-- очистка экрана
Render.clear()
-- сброс матрицы вида в единичную матрицу
Render.identity()
-- рисуем все виджеты
for i = 1, #widgets do
widgets[i]:draw()
end
end
-- начало работы GUI
function run()
sdl.Run()
end
local GUI = require('GUI')
local Widget = require('Widget')
local windowWidth = 400
local windowHeight = 300
local title = 'GUI test'
local fullscreen = false
GUI.create(windowWidth, windowHeight, title, fullscreen)
-- первый виджет
local w = Widget.new()
w:setSize(50, 100)
w:setPosition(10, 20)
w:setColor({1, 0, 0})
-- добавим в первый виджет поле deltaBlue,
-- значение которого будем изменять в методе update
w.deltaBlue = 0.001
-- обработчик нажатия мыши для первого виджета
function w:onMouseDown(x, y, button)
print('Widget1 clicked!')
end
-- функция в которой первый виджет может изменить свое внутреннее
состояние
-- и с успехом делает это!
function w:update()
if 1 < self.color[3] or -1 > self.color[3] then
self.deltaBlue = -self.deltaBlue
end
self.color[3] = self.color[3] + self.deltaBlue
end
GUI.addWidget(w)
-- второй виджет
w = Widget.new()
w:setSize(100, 50)
w:setPosition(110, 120)
w:setColor({0, 1, 0})
-- добавим во второй виджет поле deltaRed,
-- значение которого будем изменять в методе update
w.deltaRed = 0.001
-- обработчик нажатия мыши для второго виджета
function w:onMouseDown(x, y, button)
print('Widget2 clicked!')
end
-- закрепляем успех первого виджета
function w:update()
if 1 < self.color[1] or -1 > self.color[1] then
self.deltaRed = -self.deltaRed
end
self.color[1] = self.color[1] + self.deltaRed
end
GUI.addWidget(w)
-- изменение размеров окна
-- приходит из модуля sdl
function resize(w, h)
GUI.resize(w, h)
end
-- мышь переместили
-- приходит из модуля sdl
function mouseMove(x, y)
GUI.mouseMove(x, y)
end
-- мышь нажали
-- приходит из модуля sdl
function mouseDown(button)
GUI.mouseDown(button)
end
-- мышь отпустили
-- приходит из модуля sdl
function mouseUp(button)
GUI.mouseUp(button)
end
-- переменные для счетчика FPS(frames per second)
fpsStart = os.clock()
fpsCounter = 0
-- обновление GUI
function idle()
GUI.idle()
-- считаем FPS
-- текущее время - количество секунд с начала старта
программы
local currTime = os.clock()
local delta = currTime - fpsStart
if 1 < delta then
-- вывод в консоль
print('FPS:', fpsCounter)
-- сбрасываем переменные и начинаем
отсчет кадров заново
fpsCounter = 0
fpsStart = currTime
end
end
-- рисование GUI
function draw()
GUI.draw()
fpsCounter = fpsCounter + 1
end
-- Поехали!
GUI.run()