diff --git a/.gitignore b/.gitignore index 5ff917c..9fc673d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ luac.out *.hex # Folders starting with 'i_' -i_*/ \ No newline at end of file +i_*/ +*.love diff --git a/README.md b/README.md index 0c348fa..79df0b8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This is not a translation, is a remake from scratch using opensource engine Löv Last screenshots: -![](https://raw.githubusercontent.com/piXelicidio/locas-ants/develop/screenshots/gridCellsOnly.gif) +![](https://raw.githubusercontent.com/piXelicidio/locas-ants/develop/screenshots/nicePath.gif) (Btw, I'm learning Git/Github so beware of weird things with this my first Open Source"?" project) diff --git a/code/ant.lua b/code/ant.lua index 637bc52..67fbe8b 100644 --- a/code/ant.lua +++ b/code/ant.lua @@ -9,6 +9,7 @@ local map = {} --circular reference to Map module. Set -- Sorry of the Delphi-like class styles :P local TAnt = {} +local imgAntWalk = {} -- PUBLIC class fields @@ -16,6 +17,14 @@ function TAnt.setMap ( ourMap ) map = ourMap end +-- a global init before any ant is created. +function TAnt.init() + imgAntWalk[0] = apiG.newImage('images//antWalk_01.png') + imgAntWalk[1] = apiG.newImage('images//antWalk_02.png') + imgAntWalk[2] = apiG.newImage('images//antWalk_02.png') + imgAntWalk[3] = apiG.newImage('images//antWalk_03.png') +end + -- Sim has to set this refernce to the grid TAnt.grid = nil @@ -43,25 +52,21 @@ function TAnt.create() ant.direction = { 1.0, 0.0 } --direction heading movement unitary vector ant.oldPosition = {0, 0} ant.radius = 2 - ant.speed = 0.1 + ant.speed = 0.1 + ant.traveled = 0 -- traveled distance ant.friction = 1 ant.acceleration = 0.04 + math.random()*0.05 ant.erratic = cfg.antErratic --crazyness - ant.maxSpeed = cfg.antMaxSpeed - ant.tasks = {'food','cave'} --TODO: no need for Array of task, they can only have to targets, use two variables and swap + ant.maxSpeed = cfg.antMaxSpeed ant.lookingForTask = 1 - ant.comingFromTask = 0 - --ant.lookingFor = 'food' - ant.comingFrom = '' - --ant.lastTimeSeenFood = -1 - --ant.lastTimeSeenCave = -1 + ant.comingFromTask = 0 + ant.comingFrom = '' ant.lastTimeSeen = {food = -1, cave = -1} --we can access t['food'] = n - ant.maxTimeSeen = -1 - --ant.comingFromAtTime = 0 + ant.maxTimeSeen = -1 ant.lastTimeUpdatedPath = -1 ant.lookingFor = 'food' ant.nextTask = 'cave' - ant.cargo = { material = '', count = 0 } + ant.cargo = { material = '', count = 0, capacity = 1 } ant.oldestPositionRemembered = {0,0} --vector 2D arr ant.betterPathCount = 0 ant.color = cfg.colorAnts @@ -98,6 +103,15 @@ function TAnt.create() ant.oldestPositionRemembered = fPastPositions[1] end + function ant.taskFound( cell ) + --swap + ant.lookingFor, ant.nextTask = ant.nextTask, ant.lookingFor + local dv = vec.makeScale( ant.direction, -1) --go oposite + ant.direction = dv + ant.speed = 0 + ant.disablePheromonesWrite( cfg.antPositionMemorySize ) + end + function ant.updatePaused() if cfg.simFrameNumber >= ant.pauseUntil then ant.unPause() end end @@ -163,27 +177,34 @@ function TAnt.create() function ant.objectAvoidance() local ahead = vec.makeScale( ant.direction, cfg.antSightDistance ) - if not map.gridCanPass(vec.makeSum( ant.position, ahead )) --[[TAnt.map.anyCollisionWith( vec.makeSum( ant.position, ahead ) )]] then + local adir = { ant.direction[1], ant.direction[2] } + if not map.gridCanPass(vec.makeSum( ant.position, ahead )) then -- if something blocking ahead, where to turn? left or right? --print('something in my way') local vLeft = vec.makeFrom( ant.direction ) local vRight = vec.makeFrom( ant.direction ) - vec.rotate( vLeft, -cfg.antObjectAvoidance_FOV ) - vec.rotate( vRight, cfg.antObjectAvoidance_FOV ) - local goLeft = vec.makeScale( vLeft, cfg.antSightDistance/2 ) - local goRight = vec.makeScale( vRight, cfg.antSightDistance/2 ) - vec.add( goLeft, ant.position ) - vec.add( goRight, ant.position ) - local freeLeft = not map.gridCanPass( goLeft ) - local freeRight = not map.gridCanPass( goRight ) - if freeLeft and not freeRight then - --goleft - vec.setFrom( ant.direction, vLeft ) - elseif not freeLeft then - --goright - vec.setFrom( ant.direction, vRight ) - end --else keep going + local blocked = false + vec.rotate( vLeft, -cfg.antObjectAvoidance_FOV ) + vec.rotate( vRight, cfg.antObjectAvoidance_FOV ) + local goLeft = vec.makeScale( vLeft, cfg.antSightDistance/2 ) + local goRight = vec.makeScale( vRight, cfg.antSightDistance/2 ) + vec.add( goLeft, ant.position ) + vec.add( goRight, ant.position ) + local freeLeft = map.gridCanPass( goLeft ) + local freeRight = map.gridCanPass( goRight ) + + if freeLeft and not freeRight then + --goleft + vec.setFrom( ant.direction, vLeft ) + elseif freeRight and not freeLeft then + --goright + vec.setFrom( ant.direction, vRight ) + elseif not freeLeft and not freeRight then + --I'm blocked try more wide, one more time + blocked = true + end + end end @@ -210,14 +231,26 @@ function TAnt.create() --test pause end + function ant.dirToRad() + if ant.direction[2]>0 then + return math.acos( ant.direction[1] ) + else + return math.pi*2 - math.acos( ant.direction[1] ) + end + end function ant.drawNormal() apiG.setColor(ant.color) - apiG.line(ant.position[1] - ant.direction[1]*2, ant.position[2] - ant.direction[2]*2, ant.position[1] + ant.direction[1]*2, ant.position[2] + ant.direction[2]*2 ) +-- apiG.line(ant.position[1] - ant.direction[1]*2, ant.position[2] - ant.direction[2]*2, ant.position[1] + ant.direction[1]*2, ant.position[2] + ant.direction[2]*2 ) + --sprites draw + local imgIdx = math.floor(ant.traveled % 4 ) + apiG.draw(imgAntWalk[ imgIdx ], ant.position[1] , ant.position[2], ant.dirToRad(), cfg.imgScale, cfg.imgScale, 16, 16) + + if ant.cargo.count~=0 then apiG.setColor(cfg.colorFood) - if not cfg.debugGrid then apiG.circle("line", ant.position[1] + ant.direction[1]*2, ant.position[2] + ant.direction[2]*2, 0.5) end + if not cfg.debugGrid then apiG.circle("line", ant.position[1] + ant.direction[1]*2.1, ant.position[2] + ant.direction[2]*2.1, 0.5) end end -- debug end diff --git a/code/cell.lua b/code/cell.lua index fd6058b..d2b768a 100644 --- a/code/cell.lua +++ b/code/cell.lua @@ -3,6 +3,14 @@ local apiG = love.graphics local TCell = {} +TCell.cavesStorage = {} + +local imgCave = {} +local imgFood = {} +local imgGrass = {} + +local grass = {} --singleton class instance + --- cell base classs function TCell.newCell() local cell={} @@ -15,24 +23,80 @@ function TCell.newCell() return cell end +function TCell.init() + imgCave = apiG.newImage('images//cave.png') + imgFood[3] = apiG.newImage('images//food04.png') + imgFood[2] = apiG.newImage('images//food03.png') + imgFood[1] = apiG.newImage('images//food02.png') + imgFood[0] = apiG.newImage('images//food01.png') + imgGrass = apiG.newImage('images//grass01.png') + + -- singletons + grass = TCell.newCell() + grass.type = 'grass' + grass.pass = true + grass.img = imgGrass + grass.friction = 0.8 +end + --- child class food function TCell.newFood() local food=TCell.newCell() --public proeprties food.type = 'food' food.pass = true - food.color = cfg.colorFood + food.color = cfg.colorFood + food.storage = 1000 + food.infinite = true + food.img = imgFood[3] + + --methods + function food.affectAnt( ant ) + if ant.lookingFor == food.type then + if (ant.cargo.count < ant.cargo.capacity ) and ( food.storage > 0 ) then + local take = ant.cargo.capacity - ant.cargo.count + if take >= food.storage then + ant.cargo.count = ant.cargo.count + food.storage + food.storage = 0 + else + ant.cargo.count = ant.cargo.count + take + if not food.infinite then food.storage = food.storage - take end + end + end + ant.maxTimeSeen = 0 + ant.taskFound(food) + end + end + return food end ---- child class cave +--- child class cave - singleton? function TCell.newCave() local cave=TCell.newCell() cave.type = 'cave' cave.pass = true cave.color = cfg.colorCave + cave.img = imgCave + --methods + function cave.affectAnt( ant ) + if ant.lookingFor == cave.type then + ant.cargo.count = 0 + ant.maxTimeSeen = 0 + ant.taskFound(cave) + end + end return cave end +--- child class grass - singleton +function TCell.newGrass() + function grass.affectAnt( ant ) + ant.friction = grass.friction + end + return grass +end + + return TCell diff --git a/code/map.lua b/code/map.lua index 71f12f1..3fcc2b3 100644 --- a/code/map.lua +++ b/code/map.lua @@ -37,8 +37,12 @@ map.maxXg = math.floor(map.maxX / map.gridSize) + gridBorder map.minYg = math.floor(map.minY / map.gridSize) - gridBorder map.maxYg = math.floor(map.maxY / map.gridSize) + gridBorder +local imgGround = apiG.newImage('images//ground01.png') +local imgBlock = apiG.newImage('images//block01.png') + function map.init() -- initializing all Grid data structure, avoiding future validations and mem allocation + TCell.init() for i = map.minXg, map.maxXg do map.grid[i]={} for j = map.minYg, map.maxYg do @@ -61,6 +65,8 @@ function map.initCell(xg, yg) where = {0,0}, --the non-normalized vector direction of last position remembered. } end + if math.random()<0.002 then map.grid[xg][yg].cell = TCell.newGrass() end + if math.random()<0.001 then map.grid[xg][yg].pass = false end end function map.setCell_food(xg, yg) @@ -210,6 +216,7 @@ function map.resolve_BlockingCollision_andMove( ant ) vec.setFrom( ant.direction, dir) ant.position[1] = ant.position[1] + dir[1] * ant.speed ant.position[2] = ant.position[2] + dir[2] * ant.speed + ant.traveled = ant.traveled + ant.speed if numTries > 1 then --there was al least one collision @@ -291,25 +298,28 @@ local cellPheromInfo = function(cell, i, j) end function map.draw() - apiG.setColor( map.limitsColor ) - apiG.rectangle("line", map.minX, map.minY, map.maxX-map.minX, map.maxY-map.minY ) - - -- + -- for i = map.minXg, map.maxXg do for j = map.minYg, map.maxYg do if map.grid[i][j].pass then + apiG.setColor(255,255,255); + apiG.draw(imgGround, i*cfg.mapGridSize, j*cfg.mapGridSize, 0, cfg.imgScale, cfg.imgScale ); local cell = map.grid[i][j].cell if cell then - apiG.setColor( cell.color ) - apiG.rectangle('fill',i*cfg.mapGridSize, j*cfg.mapGridSize, cfg.mapGridSize , cfg.mapGridSize ) + --apiG.setColor( cell.color ) + apiG.draw(cell.img, i*cfg.mapGridSize, j*cfg.mapGridSize, 0, cfg.imgScale, cfg.imgScale ) end else - apiG.setColor( cfg.colorObstacle ) - apiG.rectangle('fill',i*cfg.mapGridSize, j*cfg.mapGridSize, cfg.mapGridSize , cfg.mapGridSize ) +-- apiG.setColor( cfg.colorObstacle ) +-- apiG.rectangle('fill',i*cfg.mapGridSize, j*cfg.mapGridSize, cfg.mapGridSize , cfg.mapGridSize ) + apiG.setColor(255,255,255); + apiG.draw(imgBlock, i*cfg.mapGridSize, j*cfg.mapGridSize, 0, cfg.imgScale, cfg.imgScale ); end end end --debuging grid + apiG.setColor( map.limitsColor ) + apiG.rectangle("line", map.minX, map.minY, map.maxX-map.minX, map.maxY-map.minY ) if cfg.debugGrid then map.gridForEachCell( cellcount ) diff --git a/code/simconfig.lua b/code/simconfig.lua index 668f199..930b78a 100644 --- a/code/simconfig.lua +++ b/code/simconfig.lua @@ -3,13 +3,13 @@ local simconfig = { - numAnts = 2550, + numAnts = 4500, antMaxSpeed = 1.2, antComAlgorithm = 1, -- 0 = Nothing; 1 = Pheromones inspiration antComEveryFrame = false, -- comunicate every frame? or use values of antComNeedFrameStep below antComNeedFrameStep = {3,13}, -- {a,b} ant would need for comunication with other ants every amount of frames form a to b. Greater values more speed less path quality. antSightDistance = 30, -- Only bellow this distance the ant can identify and locate|avoid things, bettr if > than antComRadius - antPositionMemorySize = 15, -- How many past position they can remember + antPositionMemorySize = 10, -- How many past position they can remember antErratic = 0.2, antInterests = {'food','cave'}, antObjectAvoidance = true, @@ -17,6 +17,7 @@ local simconfig = { debugGrid = false, debugPheromones = false, + debugHideAnts = false, debugCounters = {0,0,0,0,0,0}, -- our map dimensions, it can grow on any direction not only on positive integers @@ -24,7 +25,7 @@ local simconfig = { mapMinY = -250, mapMaxX = 550, mapMaxY = 350, - mapGridSize = 20, + mapGridSize = 16, mapGridComScan = { --this are the neibor cells we are going to scan looking for near ants to do communications... normal is 8 'N'eibor cells in square formation around 'C'enter cell. -- mapGridComScan[2..9]=neibors @@ -43,9 +44,12 @@ local simconfig = { { 1, 1}, }, - colorAnts = {20,10,0}, + zoomMaxScale = 4, + imgScale = 1/4, + + colorAnts = {255,255,255}, colorObstacle = {200,200,200}, - colorFood = {240, 240, 230}, + colorFood = {250, 240, 100}, colorCave = {40,40,40}, colorBk = {180,180,180}, colorBkLimits = {120, 120, 120}, diff --git a/code/simulation.lua b/code/simulation.lua index 25bfaf8..8f49c71 100644 --- a/code/simulation.lua +++ b/code/simulation.lua @@ -5,6 +5,7 @@ local TAnt = require('code.ant') local map = require('code.map') local TQuickList = require('code.qlist') local vec = require('libs.vec2d_arr') +local TCell = require('code.cell') local sim = {} @@ -13,7 +14,8 @@ sim.interactionAlgorithm = {} function sim.init() math.randomseed(os.time()) - map.init() + map.init() + TAnt.init() map.setCell_cave(-6, -4) map.setCell_food(12, 5) @@ -29,11 +31,7 @@ function sim.init() newAnt.position[1] = math.cos(ang)*(50+i/60) newAnt.position[2] = math.sin(ang)*(50+i/60) if i<4 then newAnt.setDrawMode("debug") end - end - cam.translation.x = 500 - cam.translation.y = 300 - cam.scale.x = 1 - cam.scale.y = 1 + end end function sim.algorithm_doNothing() @@ -44,32 +42,10 @@ function sim.interactionWithCells(ant) local gx = math.floor( ant.position[1] / cfg.mapGridSize ) local gy = math.floor( ant.position[2] / cfg.mapGridSize ) local cell = map.grid[gx][gy].cell - if cell then - --i'm looking for you? - local myNeed = ant.lookingFor - if myNeed == cell.type then - --ant.pause(20) - - --TODO: think about this... - if cell.type == 'food' then - ant.cargo.count = 1 - ant.cargo.material = cell.type - elseif cell.type == 'cave' then - ant.cargo.count = 0 - end - ant.maxTimeSeen = 0 - - --swap - ant.lookingFor, ant.nextTask = ant.nextTask, ant.lookingFor - ant.comingFromAtTime = cfg.simFrameNumber - local dv = vec.makeScale( ant.direction, -1) --go oposite - ant.direction = dv - ant.speed = 0 - ant.disablePheromonesWrite( cfg.antPositionMemorySize ) - - end - --record everything interesting I see - ant.lastTimeSeen[cell.type] = cfg.simFrameNumber + if cell then + cell.affectAnt( ant ) + -- is this cell interesting for me? + if ant.lastTimeSeen[cell.type] then ant.lastTimeSeen[cell.type] = cfg.simFrameNumber end end end @@ -92,6 +68,7 @@ function sim.algorithm_pheromones() local antPosiX = math.floor( ant.position[1] / cfg.mapGridSize ) local antPosiY = math.floor( ant.position[2] / cfg.mapGridSize ) local pheromInfoSeen + --TODO: what if ant can see a good phermone close to current cell and go for it? for i=1,9 do pheromInfoSeen = map.grid[ antPosiX + cfg.mapGridComScan[i][1] ] [ antPosiY + cfg.mapGridComScan[i][2] ].pheromInfo.seen @@ -140,9 +117,11 @@ end function sim.draw() map.draw() - for _,node in pairs(map.actors.array) do - node.obj.draw() - end + if not cfg.debugHideAnts then + for _,node in pairs(map.actors.array) do + node.obj.draw() + end + end end function sim.onClick(x, y) @@ -150,5 +129,27 @@ function sim.onClick(x, y) if map.isInsideGrid(xg, yg) then map.grid[xg][yg].pass = false end end +function sim.setCell( cellType, xworld, yworld) + local xg, yg = map.worldToGrid( xworld, yworld) + if map.isInsideGrid(xg, yg) then + local grid = map.grid[xg][yg] + if (cellType == 'block') or (cellType == 'obstacle') or (cellType == 'donotpass') then + grid.pass = false + elseif (cellType == 'grass') then + grid.pass = true + grid.cell = TCell.newGrass() + elseif (cellType == 'food') then + grid.pass = true + grid.cell = TCell.newFood() + elseif (cellType == 'cave') then + grid.pass = true + grid.cell = TCell.newCave() + elseif (cellType == 'ground') then + grid.pass =true + grid.cell = nil + end + end +end + return sim diff --git a/images/antWalk_00.png b/images/antWalk_00.png new file mode 100644 index 0000000..08faed0 Binary files /dev/null and b/images/antWalk_00.png differ diff --git a/images/antWalk_01.png b/images/antWalk_01.png new file mode 100644 index 0000000..48c61f9 Binary files /dev/null and b/images/antWalk_01.png differ diff --git a/images/antWalk_02.png b/images/antWalk_02.png new file mode 100644 index 0000000..9b81883 Binary files /dev/null and b/images/antWalk_02.png differ diff --git a/images/antWalk_03.png b/images/antWalk_03.png new file mode 100644 index 0000000..38d10d7 Binary files /dev/null and b/images/antWalk_03.png differ diff --git a/images/block01.png b/images/block01.png new file mode 100644 index 0000000..c187fe2 Binary files /dev/null and b/images/block01.png differ diff --git a/images/cave.png b/images/cave.png new file mode 100644 index 0000000..d32ef8a Binary files /dev/null and b/images/cave.png differ diff --git a/images/food01.png b/images/food01.png new file mode 100644 index 0000000..05367f4 Binary files /dev/null and b/images/food01.png differ diff --git a/images/food02.png b/images/food02.png new file mode 100644 index 0000000..cda1ddc Binary files /dev/null and b/images/food02.png differ diff --git a/images/food03.png b/images/food03.png new file mode 100644 index 0000000..8e27a41 Binary files /dev/null and b/images/food03.png differ diff --git a/images/food04.png b/images/food04.png new file mode 100644 index 0000000..baa6ae2 Binary files /dev/null and b/images/food04.png differ diff --git a/images/grass01.png b/images/grass01.png new file mode 100644 index 0000000..dafc5d4 Binary files /dev/null and b/images/grass01.png differ diff --git a/images/ground01.png b/images/ground01.png new file mode 100644 index 0000000..a2f7944 Binary files /dev/null and b/images/ground01.png differ diff --git a/libs/suit/.gitignore b/libs/suit/.gitignore new file mode 100644 index 0000000..b26399e --- /dev/null +++ b/libs/suit/.gitignore @@ -0,0 +1,2 @@ +main.lua +*.love diff --git a/libs/suit/README.md b/libs/suit/README.md new file mode 100644 index 0000000..80b3b48 --- /dev/null +++ b/libs/suit/README.md @@ -0,0 +1,77 @@ +# SUIT + +Simple User Interface Toolkit for LÖVE. + +SUIT is an immediate mode GUI library. + +## Documentation? + +Over at [readthedocs](http://suit.readthedocs.org/en/latest/). + +## Looks? + +Here is how SUIT looks like with the default theme: + +![Demo of all widgets](docs/_static/demo.gif) + +More info and code is over at [readthedocs](http://suit.readthedocs.org/en/latest/). + +## Hello, World! + +```lua +-- suit up +local suit = require 'suit' + +-- storage for text input +local input = {text = ""} + +-- make love use font which support CJK text +function love.load() + local font = love.graphics.newFont("NotoSansHans-Regular.otf", 20) + love.graphics.setFont(font) +end + +-- all the UI is defined in love.update or functions that are called from here +function love.update(dt) + -- put the layout origin at position (100,100) + -- the layout will grow down and to the right from this point + suit.layout:reset(100,100) + + -- put an input widget at the layout origin, with a cell size of 200 by 30 pixels + suit.Input(input, suit.layout:row(200,30)) + + -- put a label that displays the text below the first cell + -- the cell size is the same as the last one (200x30 px) + -- the label text will be aligned to the left + suit.Label("Hello, "..input.text, {align = "left"}, suit.layout:row()) + + -- put an empty cell that has the same size as the last cell (200x30 px) + suit.layout:row() + + -- put a button of size 200x30 px in the cell below + -- if the button is pressed, quit the game + if suit.Button("Close", suit.layout:row()).hit then + love.event.quit() + end +end + +function love.draw() + -- draw the gui + suit.draw() +end + +function love.textedited(text, start, length) + -- for IME input + suit.textedited(text, start, length) +end + +function love.textinput(t) + -- forward text input to SUIT + suit.textinput(t) +end + +function love.keypressed(key) + -- forward keypresses to SUIT + suit.keypressed(key) +end +``` diff --git a/libs/suit/button.lua b/libs/suit/button.lua new file mode 100644 index 0000000..c063ffa --- /dev/null +++ b/libs/suit/button.lua @@ -0,0 +1,23 @@ +-- This file is part of SUIT, copyright (c) 2016 Matthias Richter + +local BASE = (...):match('(.-)[^%.]+$') + +return function(core, text, ...) + local opt, x,y,w,h = core.getOptionsAndSize(...) + opt.id = opt.id or text + opt.font = opt.font or love.graphics.getFont() + + w = w or opt.font:getWidth(text) + 4 + h = h or opt.font:getHeight() + 4 + + opt.state = core:registerHitbox(opt.id, x,y,w,h) + core:registerDraw(opt.draw or core.theme.Button, text, opt, x,y,w,h) + + return { + id = opt.id, + hit = core:mouseReleasedOn(opt.id), + hovered = core:isHovered(opt.id), + entered = core:isHovered(opt.id) and not core:wasHovered(opt.id), + left = not core:isHovered(opt.id) and core:wasHovered(opt.id) + } +end diff --git a/libs/suit/checkbox.lua b/libs/suit/checkbox.lua new file mode 100644 index 0000000..5244c4f --- /dev/null +++ b/libs/suit/checkbox.lua @@ -0,0 +1,27 @@ +-- This file is part of SUIT, copyright (c) 2016 Matthias Richter + +local BASE = (...):match('(.-)[^%.]+$') + +return function(core, checkbox, ...) + local opt, x,y,w,h = core.getOptionsAndSize(...) + opt.id = opt.id or checkbox + opt.font = opt.font or love.graphics.getFont() + + w = w or (opt.font:getWidth(checkbox.text) + opt.font:getHeight() + 4) + h = h or opt.font:getHeight() + 4 + + opt.state = core:registerHitbox(opt.id, x,y,w,h) + local hit = core:mouseReleasedOn(opt.id) + if hit then + checkbox.checked = not checkbox.checked + end + core:registerDraw(opt.draw or core.theme.Checkbox, checkbox, opt, x,y,w,h) + + return { + id = opt.id, + hit = hit, + hovered = core:isHovered(opt.id), + entered = core:isHovered(opt.id) and not core:wasHovered(opt.id), + left = not core:isHovered(opt.id) and core:wasHovered(opt.id) + } +end diff --git a/libs/suit/core.lua b/libs/suit/core.lua new file mode 100644 index 0000000..4bde926 --- /dev/null +++ b/libs/suit/core.lua @@ -0,0 +1,217 @@ +-- This file is part of SUIT, copyright (c) 2016 Matthias Richter + +local NONE = {} +local BASE = (...):match('(.-)[^%.]+$') +local default_theme = require(BASE..'theme') + +local suit = {} +suit.__index = suit + +function suit.new(theme) + return setmetatable({ + -- TODO: deep copy/copy on write? better to let user handle => documentation? + theme = theme or default_theme, + mouse_x = 0, mouse_y = 0, + mouse_button_down = false, + candidate_text = {text="", start=0, length=0}, + + draw_queue = {n = 0}, + + Button = require(BASE.."button"), + ImageButton = require(BASE.."imagebutton"), + Label = require(BASE.."label"), + Checkbox = require(BASE.."checkbox"), + Input = require(BASE.."input"), + Slider = require(BASE.."slider"), + + layout = require(BASE.."layout").new(), + }, suit) +end + +-- helper +function suit.getOptionsAndSize(opt, ...) + if type(opt) == "table" then + return opt, ... + end + return {}, opt, ... +end + +-- gui state +function suit:setHovered(id) + return self.hovered ~= id +end + +function suit:anyHovered() + return self.hovered ~= nil +end + +function suit:isHovered(id) + return id == self.hovered +end + +function suit:wasHovered(id) + return id == self.hovered_last +end + +function suit:setActive(id) + return self.active ~= nil +end + +function suit:anyActive() + return self.active ~= nil +end + +function suit:isActive(id) + return id == self.active +end + + +function suit:setHit(id) + self.hit = id + -- simulate mouse release on button -- see suit:mouseReleasedOn() + self.mouse_button_down = false + self.active = id + self.hovered = id +end + +function suit:anyHit() + return self.hit ~= nil +end + +function suit:isHit(id) + return id == self.hit +end + +function suit:getStateName(id) + if self:isActive(id) then + return "active" + elseif self:isHovered(id) then + return "hovered" + elseif self:isHit(id) then + return "hit" + end + return "normal" +end + +-- mouse handling +function suit:mouseInRect(x,y,w,h) + return self.mouse_x >= x and self.mouse_y >= y and + self.mouse_x <= x+w and self.mouse_y <= y+h +end + +function suit:registerMouseHit(id, ul_x, ul_y, hit) + if hit(self.mouse_x - ul_x, self.mouse_y - ul_y) then + self.hovered = id + if self.active == nil and self.mouse_button_down then + self.active = id + end + end + return self:getStateName(id) +end + +function suit:registerHitbox(id, x,y,w,h) + return self:registerMouseHit(id, x,y, function(x,y) + return x >= 0 and x <= w and y >= 0 and y <= h + end) +end + +function suit:mouseReleasedOn(id) + if not self.mouse_button_down and self:isActive(id) and self:isHovered(id) then + self.hit = id + return true + end + return false +end + +function suit:updateMouse(x, y, button_down) + self.mouse_x, self.mouse_y = x,y + if button_down ~= nil then + self.mouse_button_down = button_down + end +end + +function suit:getMousePosition() + return self.mouse_x, self.mouse_y +end + +-- keyboard handling +function suit:getPressedKey() + return self.key_down, self.textchar +end + +function suit:keypressed(key) + self.key_down = key +end + +function suit:textinput(char) + self.textchar = char +end + +function suit:textedited(text, start, length) + self.candidate_text.text = text + self.candidate_text.start = start + self.candidate_text.length = length +end + +function suit:grabKeyboardFocus(id) + if self:isActive(id) then + if love.system.getOS() == "Android" or love.system.getOS() == "iOS" then + if id == NONE then + love.keyboard.setTextInput( false ) + else + love.keyboard.setTextInput( true ) + end + end + self.keyboardFocus = id + end + return self:hasKeyboardFocus(id) +end + +function suit:hasKeyboardFocus(id) + return self.keyboardFocus == id +end + +function suit:keyPressedOn(id, key) + return self:hasKeyboardFocus(id) and self.key_down == key +end + +-- state update +function suit:enterFrame() + if not self.mouse_button_down then + self.active = nil + elseif self.active == nil then + self.active = NONE + end + + self.hovered_last, self.hovered = self.hovered, nil + self:updateMouse(love.mouse.getX() , love.mouse.getY() , love.mouse.isDown(1)) + self.key_down, self.textchar = nil, "" + self:grabKeyboardFocus(NONE) + self.hit = nil +end + +function suit:exitFrame() +end + +-- draw +function suit:registerDraw(f, ...) + local args = {...} + local nargs = select('#', ...) + self.draw_queue.n = self.draw_queue.n + 1 + self.draw_queue[self.draw_queue.n] = function() + f(unpack(args, 1, nargs)) + end +end + +function suit:draw() + self:exitFrame() + love.graphics.push('all') + for i = self.draw_queue.n,1,-1 do + self.draw_queue[i]() + end + love.graphics.pop() + self.draw_queue.n = 0 + self:enterFrame() +end + +return suit diff --git a/libs/suit/docs/Makefile b/libs/suit/docs/Makefile new file mode 100644 index 0000000..2ce17ef --- /dev/null +++ b/libs/suit/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hump.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hump.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/hump" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hump" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/libs/suit/docs/_static/demo.gif b/libs/suit/docs/_static/demo.gif new file mode 100644 index 0000000..d3f69e9 Binary files /dev/null and b/libs/suit/docs/_static/demo.gif differ diff --git a/libs/suit/docs/_static/different-ids.gif b/libs/suit/docs/_static/different-ids.gif new file mode 100644 index 0000000..bb53636 Binary files /dev/null and b/libs/suit/docs/_static/different-ids.gif differ diff --git a/libs/suit/docs/_static/hello-world.gif b/libs/suit/docs/_static/hello-world.gif new file mode 100644 index 0000000..dcee926 Binary files /dev/null and b/libs/suit/docs/_static/hello-world.gif differ diff --git a/libs/suit/docs/_static/keyboard.gif b/libs/suit/docs/_static/keyboard.gif new file mode 100644 index 0000000..b80c986 Binary files /dev/null and b/libs/suit/docs/_static/keyboard.gif differ diff --git a/libs/suit/docs/_static/layout.gif b/libs/suit/docs/_static/layout.gif new file mode 100644 index 0000000..e62f953 Binary files /dev/null and b/libs/suit/docs/_static/layout.gif differ diff --git a/libs/suit/docs/_static/mutable-state.gif b/libs/suit/docs/_static/mutable-state.gif new file mode 100644 index 0000000..5583e80 Binary files /dev/null and b/libs/suit/docs/_static/mutable-state.gif differ diff --git a/libs/suit/docs/_static/options.gif b/libs/suit/docs/_static/options.gif new file mode 100644 index 0000000..e39a018 Binary files /dev/null and b/libs/suit/docs/_static/options.gif differ diff --git a/libs/suit/docs/_static/same-ids.gif b/libs/suit/docs/_static/same-ids.gif new file mode 100644 index 0000000..9dab1fe Binary files /dev/null and b/libs/suit/docs/_static/same-ids.gif differ diff --git a/libs/suit/docs/conf.py b/libs/suit/docs/conf.py new file mode 100644 index 0000000..4692884 --- /dev/null +++ b/libs/suit/docs/conf.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- +# +# SUIT documentation build configuration file, created by +# sphinx-quickstart on Sat Oct 10 13:10:12 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.mathjax', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'SUIT' +copyright = u'2016, Matthias Richter' +author = u'Matthias Richter' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'suitdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'suit.tex', u'SUIT Documentation', + u'Matthias Richter', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'SUIT', u'SUIT Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'SUIT', u'SUIT Documentation', + author, 'SUIT', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + +primary_domain = "js" +highlight_language = "lua" + +import sphinx_rtd_theme +html_theme = 'sphinx_rtd_theme' +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/libs/suit/docs/core.rst b/libs/suit/docs/core.rst new file mode 100644 index 0000000..731c95e --- /dev/null +++ b/libs/suit/docs/core.rst @@ -0,0 +1,241 @@ +Core Functions +============== + +The core functions can be divided into two parts: Functions of interest to the +user and functions of interest to the (widget) developer. + +External Interface +------------------ + +Drawing +^^^^^^^ + +.. function:: draw() + +Draw the GUI - call in ``love.draw``. + +.. data:: theme + +The current theme. See :doc:`themes`. + + +Mouse Input +^^^^^^^^^^^ + +.. function:: updateMouse(x,y, buttonDown) + + :param number x,y: Position of the mouse. + :param boolean buttonDown: Whether the mouse button is down. + +Update mouse position and button status. You do not need to call this function, +unless you use some screen transformation (e.g., scaling, camera systems, ...). + +Keyboard Input +^^^^^^^^^^^^^^ + +.. function:: keypressed(key) + + :param KeyConstant key: The pressed key. + +Forwards a ``love.keypressed(key)`` event to SUIT. + +.. function:: textinput(char) + + :param string char: The pressed character + +Forwards a ``love.textinput(key)`` event to SUIT. + + +GUI State +^^^^^^^^^ + +.. function:: anyHovered() + + :returns: ``true`` if any widget is hovered by the mouse. + +Checks if any widget is hovered by the mouse. + +.. function:: isHovered(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget is hovered by the mouse. + +Checks if the widget identified by ``id`` is hovered by the mouse. + +.. function:: wasHovered(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget was in the hovered by the mouse in the last frame. + +Checks if the widget identified by ``id`` was hovered by the mouse in the last frame. + +.. function:: anyActive() + + :returns: ``true`` if any widget is in the ``active`` state. + +Checks whether the mouse button is pressed and held on any widget. + +.. function:: isActive(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget is in the ``active`` state. + +Checks whether the mouse button is pressed and held on the widget identified by ``id``. + +.. function:: anyHit() + + :returns: ``true`` if the mouse was pressed and released on any widget. + +Check whether the mouse was pressed and released on any widget. + +.. function:: isHit(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the mouse was pressed and released on the widget. + +Check whether the mouse was pressed and released on the widget identified by ``id``. + + +Internal Helpers +---------------- + +.. function:: getOptionsAndSize(...) + + :param mixed ...: Varargs. + :returns: ``options, x,y,w,h``. + +Converts varargs to option table and size definition. Used in the widget +functions. + +.. function:: registerDraw(f, ...) + + :param function f: Function to call in ``draw()``. + :param mixed ...: Arguments to f. + +Registers a function to be executed during :func:`draw()`. Used by widgets to +make themselves visible. + +.. function:: enterFrame() + +Prepares GUI state when entering a frame. + +.. function:: exitFrame() + +Clears GUI state when exiting a frame. + + +Mouse Input +^^^^^^^^^^^ + +.. function:: mouseInRect(x,y,w,h) + + :param numbers x,y,w,h: Rectangle definition. + :returns: ``true`` if the mouse cursor is in the rectangle. + +Checks whether the mouse cursor is in the rectangle defined by ``x,y,w,h``. + +.. function:: registerMouseHit(id, ul_x, ul_y, hit) + + :param mixed id: Identifier of the widget. + :param numbers ul_x, ul_y: Upper left corner of the widget. + :param function hit: Function to perform the hit test. + +Registers a hit-test defined by the function ``hit`` for the widget identified +by ``id``. Sets the widget to ``hovered`` if th hit-test returns ``true``. Sets the +widget to ``active`` if the hit-test returns ``true`` and the mouse button is +pressed. + +The hit test receives coordinates in the coordinate system of the widget, i.e. +``(0,0)`` is the upper left corner of the widget. + +.. function:: registerHitbox(id, x,y,w,h) + + :param mixed id: Identifier of the widget. + :param numbers x,y,w,h: Rectangle definition. + +Registers a hitbox for the widget identified by ``id``. Literally this function:: + + function registerHitbox(id, x,y,w,h) + return registerMouseHit(id, x,y, function(u,v) + return u >= 0 and u <= w and v >= 0 and v <= h + end) + end + +.. function:: mouseReleasedOn(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the mouse was released on the widget. + +Checks whether the mouse button was released on the widget identified by ``id``. + +.. function:: getMousePosition() + + :returns: Mouse positon ``mx, my``. + +Get the mouse position. + +Keyboard Input +^^^^^^^^^^^^^^ + +.. function:: getPressedKey() + + :returns: KeyConstant + +Get the currently pressed key (if any). + +.. function:: grabKeyboardFocus(id) + + :param mixed id: Identifier of the widget. + +Try to grab keyboard focus. Successful only if the widget is in the ``active`` +state. + +.. function:: hasKeyboardFocus(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget has keyboard focus. + +Checks whether the widget identified by ``id`` currently has keyboard focus. + +.. function:: keyPressedOn(id, key) + + :param mixed id: Identifier of the widget. + :param KeyConstant key: Key to query. + :returns: ``true`` if ``key`` was pressed on the widget. + +Checks whether the key ``key`` was pressed while the widget identified by +``id`` has keyboard focus. + + +Instancing +---------- + +.. function:: new() + + :returns: Separate UI state. + +Create a separate UI and layout state. Everything that happens in the new +state will not affect any other state. You can use the new state like the +"global" state ``suit``, but call functions with the colon syntax instead of +the dot syntax, e.g.:: + + function love.load() + dress = suit.new() + end + + function love.update() + dress.layout:reset() + dress:Label("Hello, World!", dress.layout:row(200,30)) + dress:Input(input, dress.layout:row()) + end + + function love.draw() + dress:draw() + end + +.. warning:: + + Unlike UI and layout state, the theme might be shared with other states. + Changes in a shared theme will be shared across all themes. + See the :ref:`Instance Theme ` subsection in the + :doc:`gettingstarted` guide. diff --git a/libs/suit/docs/gettingstarted.rst b/libs/suit/docs/gettingstarted.rst new file mode 100644 index 0000000..51605f3 --- /dev/null +++ b/libs/suit/docs/gettingstarted.rst @@ -0,0 +1,395 @@ +Getting Started +=============== + +Before actually getting started, it is important to understand the motivation +and mechanics behind SUIT: + +- **Immediate mode is better than retained mode** +- **Layout does not care about widgets** +- **Less is more** + +Immediate mode? +--------------- + +With classical (retained) mode libraries you typically have a stage where you +create the whole UI when the program initializes. This includes what happens +when events like button presses or slider changes occur. After that point, the +GUI is expected to not change very much. This is great for word processors +where the interaction is consistent and straightforward, but bad for games, +where everything changes all the time. + +With immediate mode libraries, on the other hand, the GUI is created every +frame from scratch. Because that would be wasteful, there are no widget +objects. Instead, widgets are created by functions that react to UI state and +present some data. Where this data comes from and how it is maintained does +not concern the widget at all. This is, after all, your job. This gives great +control over what is shown where and when. The widget code can be right next +to the code that does what should happen if the widget state changes. The +layout is also very flexible: adding a widget is one more function call, and if +you want to hide a widget, you simply don't call the corresponding function. + +This separation of data and behaviour is great when a lot of stuff is going on, +but takes a bit of time getting used to. + + +What SUIT is +^^^^^^^^^^^^ + +SUIT is simple: It provides only a few basic widgets that are important for +games: + +- :func:`Buttons