remove unnecessary files
|
@ -1,23 +0,0 @@
|
|||
# image: shru/arch-love-release:heavy
|
||||
image: asmfreak/love-release:latest
|
||||
|
||||
variables:
|
||||
ITCHIO_USER: shru
|
||||
ITCHIO_GAME: cave-story-randomizer
|
||||
CHANNEL: $CI_COMMIT_REF_NAME
|
||||
|
||||
stages:
|
||||
- build-release
|
||||
|
||||
build-and-release:
|
||||
stage: build-release
|
||||
when: manual
|
||||
script:
|
||||
# I - Build
|
||||
- cd src
|
||||
- love-release -W
|
||||
# II - Release to itch.io
|
||||
- cd releases
|
||||
- FILE=$(ls *-win32.zip) ;
|
||||
zip -d "$FILE" "*/lovec.exe" "*/changes.txt" "*/readme.txt" "*.ico" ;
|
||||
butler push "$FILE" "$ITCHIO_USER/$ITCHIO_GAME:win32-$CHANNEL"
|
48
.travis.yml
|
@ -1,48 +0,0 @@
|
|||
language: python
|
||||
os: linux
|
||||
dist: xenial
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- libzip-dev
|
||||
- xvfb
|
||||
services:
|
||||
- xvfb
|
||||
before_install:
|
||||
- |
|
||||
pip install hererocks
|
||||
hererocks env --luarocks 3.0 --lua latest
|
||||
export 'PATH='"$TRAVIS_BUILD_DIR"'/env/bin'":$PATH"
|
||||
install:
|
||||
- |
|
||||
sudo add-apt-repository -y ppa:bartbes/love-stable
|
||||
sudo apt-get -q update
|
||||
sudo apt-get -y install love
|
||||
luarocks install --server="http://luarocks.org/dev" lua-zip
|
||||
luarocks install love-release
|
||||
script:
|
||||
- bash daily.sh
|
||||
env:
|
||||
global:
|
||||
- secure: DU2Mq4FPbOidv0Go4b+2r5Vy7v0bbQo40ZZPkiwm/6nAJYkwySwVn2DyZGwh/kV4o6ZG0IgtLxPvHiF8lVZptbtkHJTt/GeokwwvsfpHJntPC9jyvXklFkkln9h2AgWrhTmFf+EYE4Mlm7FAjsr/9ZZHCuGn7wDCOBk3g4Zy7eAyKGqpYMN3296eUiZPXWtOAexmbavFI07pmu4ZyJ8K6HMmjd8YSAQpui3zJn/hMTupQm+o5s/ehZ72QnUyjRWdKvLlJCzORrajJxgYXQqLdjXVMcq4kuR4pDRnLdW9XBlAWiHOvj7wlVOUyBq2ZyhkTTNEYiQp+9UsIua1gG0hdLiOGH2ODtrb6NvQIRUfs5jE6xHmw3hQCfIe9itT+SbVQmuuTovM/MQTS/vH7k+AHtnJCGykMjAD6evNi1EkOG5oxrL7xEGPz7lzyVISoz0O4YmMWGF0qIzb68R11+Eo1URFrEUDxZc7F2kMoeAbnTxfiGABrMXrgIN7ReJhfD1z4ik6KwKBFw9AwJfldyb5w0QgqFAkkihqKGp5VbTMR6LeSN0Tq/Ic3QRvLHYt43+eoRXS4d2lvSo/0Nf7FftQPurbLI6c/r9nckwOZmjFicwYJ0Ulrbd46og74FUQZFSMcrVsiXo1PG2yTwu9bUvTBUHWdWh0o2xPpfAHbA05++c=
|
||||
before_deploy:
|
||||
- |
|
||||
cd src; love-release -M; cd -
|
||||
mv pre-edited-cs "src/releases/Cave Story"
|
||||
cd src/releases
|
||||
zip -9 -qur CaveStoryRandomizer-macos.zip "Cave Story"
|
||||
zip -9 -qr CaveStoryRandomizer-linux.zip "Cave Story" CaveStoryRandomizer.love
|
||||
cd -
|
||||
# don't forget to create the windows releases!
|
||||
deploy:
|
||||
provider: releases
|
||||
token:
|
||||
secure: bMinTly2BOZCNQw625Fz744hvVQlnwZiC6pQZZx1f5evs0gi4TXbqqft5UN3orGgRiNnVnbf2qYC7PnS1LzynHUimdVk8iqfRKz4ADuWXyf9/i1gojzzSAI87lxbGaHxzqjZLtzf1zRonPlY1oj9X3a1eAle6EEAn/Utnm/jqZPhTYm8cMowLWqcVTcDOw1lhBpgsVYdej1wCaq7r2KBDvrqWZCdt8pJ2lcaShJEqmBONDvu9KO+gbNXwmhBAz4bG0DTVFhO459ZNjfNz749eiU7KhbxvFgz7O+j8HEdhlXznYgmzveUmVmhOSe3z037YwCNv+2ar0FITGkmTpgog0z2XuxDMb28Eakoc0uzaowwB68JRfEeRpBGidhSGraZ5DnHOS3QdDaEQ4jImv97DagiVoqcKm3JDGhGuvbI4dfPsXHKMncJC8cqf86NwSg63boabcoUTwzVLZdd4tbiOBqYPBgEtEz4uFgKvkrrzE0DoP1Efi1aZqcnOW6xckHbC261rxB5QG8WGcb1tDnmQgbhbEEguPHuzKBWP6ktpjpFdeS3Wbqb0UMaXQXMCcaMpJfCLNsLxQ6iHOh+XQhJREDPTuXm4F1m0BzSjBvQpWQ4tpO+kF1KiJ4wiwzn9jTfJxhgMgxEoY75dtH+E0K2jXLRezychR22V+7R9ifNLns=
|
||||
file:
|
||||
- src/releases/*.zip
|
||||
file_glob: true
|
||||
draft: true
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
Before Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 432 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 227 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 418 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 577 B |
Before Width: | Height: | Size: 328 B |
Before Width: | Height: | Size: 346 B |
Before Width: | Height: | Size: 471 B |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 180 KiB |
|
@ -1,45 +0,0 @@
|
|||
if io then
|
||||
io.stdout:setvbuf("no")
|
||||
end
|
||||
|
||||
function love.conf(t)
|
||||
t.window = {
|
||||
title = "Cave Story Randomizer",
|
||||
icon = 'assets/icon/randoconfig.png',
|
||||
width = 640,
|
||||
height = 480,
|
||||
resizable = false,
|
||||
}
|
||||
t.version = '11.1'
|
||||
t.console = false
|
||||
t.identity = 'CaveStoryRandomizer'
|
||||
t.accelerometerjoystick = false
|
||||
t.gammacorrect = false
|
||||
|
||||
t.modules.audio = false
|
||||
t.modules.joystick = false
|
||||
t.modules.physics = false
|
||||
t.modules.sound = false
|
||||
t.modules.thread = false
|
||||
t.modules.touch = false
|
||||
t.modules.video = false
|
||||
|
||||
t.releases = {
|
||||
-- This is the name of the zip archive which contains your game.
|
||||
title = 'CaveStoryRandomizer',
|
||||
-- This is the name of your game's executable.
|
||||
package = 'Cave Story Randomizer',
|
||||
loveVersion = '11.2',
|
||||
version = 'v2.0',
|
||||
author = 'duncathan',
|
||||
email = 'dunc@duncathan.com',
|
||||
description = 'A randomizer for Cave Story',
|
||||
homepage = 'https://github.com/cave-story-randomizer/cave-story-randomizer',
|
||||
-- MacOS needs this.
|
||||
identifier = 'CaveStoryRandomizer',
|
||||
excludeFileList = {
|
||||
},
|
||||
compile = false,
|
||||
releaseDirectory = 'releases',
|
||||
}
|
||||
end
|
|
@ -1,550 +0,0 @@
|
|||
local function lifeCapsule3()
|
||||
return {
|
||||
name = "Life Capsule",
|
||||
script = "<EVE0012",
|
||||
attributes = {"nonProgressive", "hp3", "helpful"},
|
||||
hints = {"a Life Capsule", "a little HP"}
|
||||
}
|
||||
end
|
||||
local function lifeCapsule4()
|
||||
return {
|
||||
name = "Life Capsule",
|
||||
script = "<EVE0013",
|
||||
attributes = {"nonProgressive", "hp4", "helpful"},
|
||||
hints = {"a Life Capsule", "some decent HP"}
|
||||
}
|
||||
end
|
||||
local function lifeCapsule5()
|
||||
return {
|
||||
name = "Life Capsule",
|
||||
script = "<EVE0014",
|
||||
attributes = {"nonProgressive", "hp5", "helpful"},
|
||||
hints = {"a Life Capsule", "a lot of HP"}
|
||||
}
|
||||
end
|
||||
|
||||
local function missiles()
|
||||
return {
|
||||
name = "Missile Expansion",
|
||||
script = "<EVE0030",
|
||||
attributes = {"weapon", "missileLauncher", "nonProgressive", "helpful"},
|
||||
}
|
||||
end
|
||||
|
||||
local function event(n)
|
||||
return {
|
||||
name = "Event: " .. n,
|
||||
attributes = {"event", "abstract"},
|
||||
placed = true
|
||||
}
|
||||
end
|
||||
|
||||
local function objective(n, eve)
|
||||
return {
|
||||
name = n,
|
||||
attributes = {"objective", "abstract"},
|
||||
placed = true,
|
||||
script = eve
|
||||
}
|
||||
end
|
||||
|
||||
local function _itemData()
|
||||
local data = {
|
||||
-------------
|
||||
-- WEAPONS --
|
||||
-------------
|
||||
polarStar1 = {
|
||||
name = "Polar Star",
|
||||
script = "<EVE0002",
|
||||
attributes = {"weapon", "weaponBoss", "weaponSN", "polarStar", "mandatory"},
|
||||
hints = {"the gunsmith's pride"}
|
||||
},
|
||||
polarStar2 = {
|
||||
name = "Polar Star",
|
||||
script = "<EVE0002",
|
||||
attributes = {"weapon", "weaponBoss", "weaponSN", "polarStar", "mandatory"},
|
||||
hints = {"the gunsmith's pride"}
|
||||
},
|
||||
missileLauncher = {
|
||||
name = "Missile Launcher",
|
||||
script = "<EVE0030",
|
||||
attributes = {"weapon", "weaponSN", "nonProgressive", "helpful"},
|
||||
},
|
||||
superMissileLauncher = {
|
||||
name = "Super Missile Launcher",
|
||||
script = "<EVE0033",
|
||||
attributes = {"weapon", "weaponSN", "missileLauncher", "nonProgressive", "helpful"},
|
||||
},
|
||||
fireball = {
|
||||
name = "Fireball",
|
||||
script = "<EVE0004",
|
||||
attributes = {"weapon", "weaponBoss", "mandatory"},
|
||||
hints = {"the Fireball"}
|
||||
},
|
||||
snake = {
|
||||
name = "Snake",
|
||||
script = "<EVE0005",
|
||||
attributes = {"weapon", "weaponBoss", "nonProgressive", "weaponStrong", "helpful"},
|
||||
hints = {"the Snake"}
|
||||
},
|
||||
bubbler = {
|
||||
name = "Bubbler",
|
||||
script = "<EVE0007",
|
||||
attributes = {"weapon", "weaponBoss", "nonProgressive", "helpful"},
|
||||
hints = {"the Bubbler"}
|
||||
},
|
||||
machineGun = {
|
||||
name = "Machine Gun",
|
||||
script = "<EVE0008",
|
||||
attributes = {"weapon", "weaponBoss", "weaponSN", "flight", "mandatory", "weaponStrong"},
|
||||
hints = {"the Machine Gun"}
|
||||
},
|
||||
blade = {
|
||||
name = "Blade",
|
||||
script = "<EVE0009",
|
||||
attributes = {"weapon", "weaponBoss", "weaponSN", "mandatory", "weaponStrong"},
|
||||
hints = {"the Blade"}
|
||||
},
|
||||
nemesis = {
|
||||
name = "Nemesis",
|
||||
script = "<EVE0010",
|
||||
attributes = {"weapon", "weaponBoss", "weaponSN", "nonProgressive", "weaponStrong", "helpful"},
|
||||
hints = {"the Nemesis"}
|
||||
},
|
||||
|
||||
---------------
|
||||
-- INVENTORY --
|
||||
---------------
|
||||
mapSystem = {
|
||||
name = "Map System",
|
||||
script = "<EVE0052",
|
||||
attributes = {"nonProgressive", "map"},
|
||||
hints = {"a map", "an electronic device"}
|
||||
},
|
||||
locket = {
|
||||
name = "Silver Locket",
|
||||
script = "<EVE0054",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"some fishy jewelry", "a Mimiga's item"}
|
||||
},
|
||||
arthurKey = {
|
||||
name = "Arthur's Key",
|
||||
script = "<EVE0051",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"Arthur's Key", "a key", "a Mimiga's item"}
|
||||
},
|
||||
idCard = {
|
||||
name = "ID Card",
|
||||
script = "<EVE0057",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the ID Card", "an electronic device"}
|
||||
},
|
||||
santaKey = {
|
||||
name = "Santa's Key",
|
||||
script = "<EVE0053",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"Santa's Key", "a key", "a Mimiga's item"}
|
||||
},
|
||||
lipstick = {
|
||||
name = "Chaco's Lipstick",
|
||||
script = "<EVE0087",
|
||||
attributes = {"nonProgressive", "useless"},
|
||||
hints = {"some lipstick", "a Mimiga's item", "a lewd item"}
|
||||
},
|
||||
juice = {
|
||||
name = "Jellyfish Juice",
|
||||
script = "<EVE0058",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"some juice", "a bomb ingredient"}
|
||||
},
|
||||
charcoal = {
|
||||
name = "Charcoal",
|
||||
script = "<EVE0062",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"some charcoal", "a bomb ingredient"}
|
||||
},
|
||||
rustyKey = {
|
||||
name = "Rusty Key",
|
||||
script = "<EVE0059",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Rusty Key", "a key"}
|
||||
},
|
||||
gumKey = {
|
||||
name = "Gum Key",
|
||||
script = "<EVE0060",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Gum Key", "a key"}
|
||||
},
|
||||
gumBase = {
|
||||
name = "Gum Base",
|
||||
script = "<EVE0061",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"some gum", "a bomb ingredient"}
|
||||
},
|
||||
bomb = {
|
||||
name = "Bomb",
|
||||
script = "<EVE0063",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"a bomb"}
|
||||
},
|
||||
panties = {
|
||||
name = "Curly's Panties",
|
||||
script = "<EVE0085",
|
||||
attributes = {"nonProgressive", "useless"},
|
||||
hints = {"a pair of panties", "a lewd item"}
|
||||
},
|
||||
puppy1 = {
|
||||
name = "Hajime",
|
||||
script = "<EVE0064",
|
||||
attributes = {"puppy"}
|
||||
},
|
||||
puppy2 = {
|
||||
name = "Kakeru",
|
||||
script = "<EVE0064",
|
||||
attributes = {"puppy"}
|
||||
},
|
||||
puppy3 = {
|
||||
name = "Mick",
|
||||
script = "<EVE0064",
|
||||
attributes = {"puppy"}
|
||||
},
|
||||
puppy4 = {
|
||||
name = "Nene",
|
||||
script = "<EVE0064",
|
||||
attributes = {"puppy"}
|
||||
},
|
||||
puppy5 = {
|
||||
name = "Shinobu",
|
||||
script = "<EVE0064",
|
||||
attributes = {"puppy"}
|
||||
},
|
||||
lifepot = {
|
||||
name = "Life Pot",
|
||||
script = "<EVE0065",
|
||||
attributes = {"nonProgressive", "helpful"},
|
||||
hints = {"a Life Pot", "some medicine"}
|
||||
},
|
||||
turbocharge = {
|
||||
name = "Turbocharge",
|
||||
script = "<EVE0070",
|
||||
attributes = {"nonProgressive", "helpful"},
|
||||
hints = {"the Turbocharge"}
|
||||
},
|
||||
clinicKey = {
|
||||
name = "Clinic Key",
|
||||
script = "<EVE0067",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Clinic Key", "a key"}
|
||||
},
|
||||
armsBarrier = {
|
||||
name = "Arms Barrier",
|
||||
script = "<EVE0069",
|
||||
attributes = {"nonProgressive", "helpful"},
|
||||
hints = {"the Arms Barrier"}
|
||||
},
|
||||
cureAll = {
|
||||
name = "Cure-All",
|
||||
script = "<EVE0066",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Cure-All", "some medicine"}
|
||||
},
|
||||
booster1 = {
|
||||
name = "Booster",
|
||||
script = "<EVE0068",
|
||||
attributes = {"flight", "booster", "mandatory"},
|
||||
hints = {"a Booster", "a rocket component"}
|
||||
},
|
||||
booster2 = {
|
||||
name = "Booster",
|
||||
script = "<EVE0068",
|
||||
attributes = {"flight", "booster", "mandatory"},
|
||||
hints = {"a Booster", "a rocket component"}
|
||||
},
|
||||
towRope = {
|
||||
name = "Tow Rope",
|
||||
script = "<EVE0080",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Tow Rope"}
|
||||
},
|
||||
airTank = {
|
||||
name = "Curly's Air Tank",
|
||||
script = "<EVE0071",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"an air tank"}
|
||||
},
|
||||
alienMedal = {
|
||||
name = "Alien Medal",
|
||||
script = "<EVE0086",
|
||||
attributes = {"nonProgressive", "useless"},
|
||||
hints = {"the Alien Medal", "a badge of victory"}
|
||||
},
|
||||
whimsicalStar = {
|
||||
name = "Whimsical Star",
|
||||
script = "<EVE0088",
|
||||
attributes = {"nonProgressive", "helpful"},
|
||||
hints = {"the Whimsical Star"}
|
||||
},
|
||||
nikumaru = {
|
||||
name = "Nikumaru Counter",
|
||||
script = "<EVE0072",
|
||||
attributes = {"nonProgressive"},
|
||||
hints = {"the Nikumaru Counter"}
|
||||
},
|
||||
teleportKey = {
|
||||
name = "Teleporter Room Key",
|
||||
script = "<EVE0075",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Teleporter Room Key", "a key"}
|
||||
},
|
||||
letter = {
|
||||
name = "Sue's Letter",
|
||||
script = "<EVE0076",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"Sue's Letter", "a Mimiga's item"}
|
||||
},
|
||||
mask = {
|
||||
name = "Mimiga Mask",
|
||||
script = "<EVE0074",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Mimiga Mask"}
|
||||
},
|
||||
brokenSprinkler = {
|
||||
name = "Broken Sprinkler",
|
||||
script = "<EVE0078",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Broken Sprinkler", "a sprinkler"}
|
||||
},
|
||||
newSprinkler = {
|
||||
name = "Sprinkler",
|
||||
script = "<EVE0079",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Sprinkler", "a sprinkler", "a rocket component"}
|
||||
},
|
||||
controller = {
|
||||
name = "Controller",
|
||||
script = "<EVE0077",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Controller", "a rocket component", "an electronic device"}
|
||||
},
|
||||
mushroomBadge = {
|
||||
name = "Mushroom Badge",
|
||||
script = "<EVE0083",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Mushroom Badge", "a badge of victory"}
|
||||
},
|
||||
maPignon = {
|
||||
name = "Ma Pignon",
|
||||
script = "<EVE0084",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"Ma Pignon", "a living being"}
|
||||
},
|
||||
mrLittle = {
|
||||
name = "Little Man",
|
||||
script = "<EVE0082",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"Mr Little", "a living being"}
|
||||
--placed = true
|
||||
},
|
||||
ironBond = {
|
||||
name = "Iron Bond",
|
||||
script = "<EVE0089",
|
||||
attributes = {"mandatory"},
|
||||
hints = {"the Iron Bond"}
|
||||
},
|
||||
clayMedal = {
|
||||
name = "Clay Figure Medal",
|
||||
script = "<EVE0081",
|
||||
attributes = {"nonProgressive", "useless"},
|
||||
hints = {"the Clay Figure Medal", "a badge of victory"}
|
||||
},
|
||||
|
||||
-------------------
|
||||
-- LIFE CAPSULES --
|
||||
-------------------
|
||||
capsule3A = lifeCapsule3(), -- First Cave
|
||||
capsule3B = lifeCapsule3(), -- Yamashita Farm
|
||||
capsule3C = lifeCapsule3(), -- Egg Corridor (Basil)
|
||||
capsule4A = lifeCapsule4(), -- Egg Corridor (Cthulhu)
|
||||
capsule5A = lifeCapsule5(), -- Grasstown
|
||||
capsule5B = lifeCapsule5(), -- Execution Chamber
|
||||
capsule5C = lifeCapsule5(), -- Sand Zone (Upper)
|
||||
capsule5D = lifeCapsule5(), -- Sand Zone (Lower)
|
||||
capsule5E = lifeCapsule5(), -- Labyrinth
|
||||
capsule5F = lifeCapsule5(), -- Plantation (West)
|
||||
capsule4B = lifeCapsule4(), -- Plantation (Puppy)
|
||||
capsule5G = lifeCapsule5(), -- Sacred Grounds
|
||||
|
||||
--------------
|
||||
-- MISSILES --
|
||||
--------------
|
||||
missileA = missiles(), -- Grasstown
|
||||
missileB = missiles(), -- Grasstown Hut
|
||||
missileC = missiles(), -- Egg Corridor?
|
||||
missileD = { -- Egg Observation Room? (arbitrarily making this a backup Super Missile chest)
|
||||
name = "Missile Expansion (Super Missile alt)",
|
||||
script = "<EVE0038",
|
||||
attributes = {"weapon", "missileLauncher", "nonProgressive", "helpful"}
|
||||
},
|
||||
missileHell = {
|
||||
name = "Missile Expansion",
|
||||
script = "<EVE0035",
|
||||
attributes = {"weapon", "missileLauncher", "nonProgressive", "helpful"}
|
||||
},
|
||||
|
||||
------------
|
||||
-- EVENTS --
|
||||
------------
|
||||
eventSue = event("Saved Sue"),
|
||||
eventFans = event("Activated Fans"),
|
||||
eventKazuma = event("Saved Kazuma"),
|
||||
eventOmega = event("Defeated Omega"),
|
||||
eventToroko = event("Defeated Toroko+"),
|
||||
eventCore = event("Defeated the Core"),
|
||||
eventCurly = event("Saved Curly"),
|
||||
eventRocket = event("Built Rocket"),
|
||||
|
||||
----------------
|
||||
-- GAME MODES --
|
||||
----------------
|
||||
objBadEnd = objective("Bad Ending", "<FL+6003"),
|
||||
objNormalEnd = objective("Normal Ending", "<FL+6000"),
|
||||
objBestEnd = objective("Best Ending", "<FL+6001"),
|
||||
objAllBosses = objective("All Bosses", "<FL+6002<IT+0005"),
|
||||
obj100Percent = objective("100%", "<FL+6004<IT+0005")
|
||||
}
|
||||
|
||||
local function initializeHints(item)
|
||||
local hintArray = {
|
||||
--mandatory = {"a required item"},
|
||||
puppy = {"a puppy", "a living being"},
|
||||
--helpful = {"a helpful item"},
|
||||
--useless = {"a useless item"},
|
||||
weapon = {"a weapon"},
|
||||
--weaponSN = {"a weapon that breaks blocks"},
|
||||
--weaponStrong = {"a strong weapon"},
|
||||
flight = {"a method of flight", "flight"},
|
||||
missileLauncher = {"a Missile upgrade"}
|
||||
}
|
||||
|
||||
item.hints = item.hints or {} -- initialize item's hints array if not already
|
||||
|
||||
-- loop through item's attributes and add any matching hints from the hintArray table
|
||||
for k,v in ipairs(item.attributes) do
|
||||
for k2,v2 in ipairs(hintArray[v] or {}) do
|
||||
table.insert(item.hints, v2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local array = {}
|
||||
for k, t in pairs(data) do
|
||||
t.key = k
|
||||
t.placed = t.placed or false
|
||||
t.attributes = t.attributes or {}
|
||||
table.insert(t.attributes, k)
|
||||
table.insert(array, t)
|
||||
|
||||
initializeHints(t)
|
||||
end
|
||||
|
||||
return array
|
||||
end
|
||||
|
||||
local C = Class:extend()
|
||||
|
||||
function C:new()
|
||||
self.itemData = _itemData()
|
||||
end
|
||||
|
||||
function C:getByKey(key)
|
||||
return _.filter(self.itemData, function(k,v) return v.key == key end)[1]
|
||||
end
|
||||
|
||||
function C:_getItems(filterFn)
|
||||
return _.filter(self.itemData, filterFn)
|
||||
end
|
||||
|
||||
function C:getItems()
|
||||
return self:_getItems(function(k,v) return true end)
|
||||
end
|
||||
|
||||
function C:getItemsByAttribute(attribute, onlyUnplaced)
|
||||
onlyUnplaced = onlyUnplaced or false
|
||||
return self:_getItems(function(k,v) return _.contains(v.attributes, attribute) and not (onlyUnplaced and v.placed) end)
|
||||
end
|
||||
|
||||
function C:getEvents()
|
||||
return self:getItemsByAttribute("event")
|
||||
end
|
||||
|
||||
function C:getOptionalItems(onlyUnplaced)
|
||||
return self:getItemsByAttribute("nonProgressive", onlyUnplaced)
|
||||
end
|
||||
|
||||
function C:getMandatoryItems(onlyUnplaced)
|
||||
return self:getItemsByAttribute("mandatory", onlyUnplaced)
|
||||
end
|
||||
|
||||
function C:getMandatory()
|
||||
return self:_getItems(function(k,v) return not _.contains(v.attributes, "nonProgressive") end)
|
||||
end
|
||||
|
||||
function C:getUnplacedItems()
|
||||
return self:_getItems(function(k,v) return not v.placed end)
|
||||
end
|
||||
|
||||
function C:unplacedString()
|
||||
local s = "\r\nUnplaced items:"
|
||||
for k,v in pairs(self:getUnplacedItems()) do s = s .. "\r\n" .. v.name end
|
||||
return s
|
||||
end
|
||||
|
||||
local function _hint(message, l, ending)
|
||||
ending = ending or "<END"
|
||||
local MSGBOXLIMIT = 35
|
||||
local PATTERN = " [^ ]*$"
|
||||
local line1, line2, line3 = "", "", ""
|
||||
|
||||
local split = 1
|
||||
line1 = message:sub(split, split+MSGBOXLIMIT)
|
||||
|
||||
if line1:find(PATTERN) and #message > MSGBOXLIMIT then
|
||||
line1 = line1:sub(1, line1:find(PATTERN))
|
||||
split = line1:find(PATTERN)+split
|
||||
if split ~= MSGBOXLIMIT then line2 = "\r\n" end
|
||||
line2 = line2 .. message:sub(split, split+MSGBOXLIMIT)
|
||||
|
||||
if line2:find(PATTERN) and #message > MSGBOXLIMIT*2 then
|
||||
line2 = line2:sub(1, line2:find(PATTERN))
|
||||
split = line2:find(PATTERN)+split-2
|
||||
if split ~= MSGBOXLIMIT then line3 = "\r\n" end
|
||||
line3 = line3 .. message:sub(split, split+MSGBOXLIMIT)
|
||||
end
|
||||
end
|
||||
|
||||
local s = "<PRI<MSG<TUR" .. line1 .. line2 .. line3 .. "<NOD" .. ending
|
||||
|
||||
return {
|
||||
name = ("%q [%s] [%s]"):format(message, l.item.name, l.name),
|
||||
atrributes = {"hint", "abstract"},
|
||||
placed = true,
|
||||
script = s
|
||||
}
|
||||
end
|
||||
|
||||
function C:createHint(l, ending)
|
||||
local function pick(t) return t[love.math.random(#t)] end
|
||||
|
||||
local location, item = l:getHint()
|
||||
local starts = {"I hear that ", "Rumour has it, ", "They say "}
|
||||
local mids = {" can be found ", " is ", " is hidden "}
|
||||
local message = (pick(starts) or "") .. (pick(item) or "") .. (pick(mids) or "") .. (pick(location) or "") .. "."
|
||||
|
||||
return _hint(message, l, ending)
|
||||
end
|
||||
|
||||
function C:prebuiltHint(l, ending)
|
||||
return _hint(l:getPrebuiltHint(), l, ending)
|
||||
end
|
||||
|
||||
return C
|
|
@ -1,53 +0,0 @@
|
|||
local C = Class:extend()
|
||||
|
||||
function C:new(name, map, event, region, hints)
|
||||
self.name = name
|
||||
self.map = map
|
||||
self.event = event
|
||||
self.region = region
|
||||
self.hintList = hints or {}
|
||||
end
|
||||
|
||||
function C:fill(item, items)
|
||||
local old = self.item
|
||||
self:setItem(item)
|
||||
if self:canAccess(items) then return true end
|
||||
self:setItem(old)
|
||||
return false
|
||||
end
|
||||
|
||||
function C:canAccess(items)
|
||||
if not self.region:canAccess(items) then return false end
|
||||
if self.requirements == nil then return true end
|
||||
return self.requirements(self, items)
|
||||
end
|
||||
|
||||
function C:hasItem()
|
||||
return self.item ~= nil
|
||||
end
|
||||
|
||||
function C:setItem(item)
|
||||
item.placed = true
|
||||
item.location_name = self.name
|
||||
self.item = item
|
||||
end
|
||||
|
||||
function C:writeItem(tscFiles, item)
|
||||
item = item or self.item
|
||||
if item == nil then
|
||||
logError("No item at " .. self.name)
|
||||
return
|
||||
end
|
||||
if self.map == nil or self.event == nil or item.script == nil then return end
|
||||
tscFiles[self.map]:placeItemAtLocation(item, self)
|
||||
end
|
||||
|
||||
function C:getHint()
|
||||
return _.append(self.region.hintList, self.hintList), self.item.hints
|
||||
end
|
||||
|
||||
function C:getPrebuiltHint()
|
||||
return nil
|
||||
end
|
||||
|
||||
return C
|
|
@ -1,280 +0,0 @@
|
|||
local song = Class:extend()
|
||||
function song:new(name, id, jingle, game)
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.jingle = jingle or false
|
||||
self.game = game or "vanilla"
|
||||
end
|
||||
|
||||
local songs = {
|
||||
xxxx = song("XXXX", "0000", true),
|
||||
mischievousRobot = song("Mischievous Robot", "0001"),
|
||||
safety = song("Safety", "0002"),
|
||||
gameOver = song("Game Over", "0003", true),
|
||||
gravity = song("Gravity", "0004"),
|
||||
onToGrasstown = song("On to Grasstown", "0005"),
|
||||
meltdown = song("Meltdown 2", "0006"),
|
||||
eyesOfFlame = song("Eyes of Flame", "0007"),
|
||||
gestation = song("Gestation", "0008"),
|
||||
mimigaTown = song("Mimiga Town", "0009"),
|
||||
getItem = song("Get Item!", "0010", true),
|
||||
balrogsTheme = song("Balrog's Theme", "0011"),
|
||||
cemetary = song("Cemetary", "0012"),
|
||||
plant = song("Plant", "0013"),
|
||||
pulse = song("Pulse", "0014"),
|
||||
victory = song("Victory!", "0015", true),
|
||||
getLifeCapsule = song("Get Life Capsule!", "0016", true),
|
||||
tyrant = song("Tyrant", "0017"),
|
||||
run = song("Run!", "0018"),
|
||||
jenka1 = song("Jenka 1", "0019"),
|
||||
labyrinthFight = song("Labyrinth Fight", "0020"),
|
||||
access = song("Access", "0021"),
|
||||
oppression = song("Oppression", "0022"),
|
||||
geothermal = song("Geothermal", "0023"),
|
||||
caveStory = song("Cave Story", "0024"),
|
||||
moonsong = song("Moonsong", "0025"),
|
||||
herosEnd = song("Hero's End", "0026"),
|
||||
scorchingBack = song("Scorching Back", "0027"),
|
||||
quiet = song("Quiet", "0028"),
|
||||
finalCave = song("Final Cave", "0029"),
|
||||
balcony = song("Balcony", "0030"),
|
||||
charge = song("Charge", "0031"),
|
||||
lastBattle = song("Last Battle", "0032"),
|
||||
theWayBackHome = song("The Way Back Home", "0033"),
|
||||
zombie = song("Zombie", "0034"),
|
||||
breakDown = song("Break Down", "0035"),
|
||||
runningHell = song("Running Hell", "0036"),
|
||||
jenka2 = song("Jenka 2", "0037"),
|
||||
livingWaterway = song("Living Waterway", "0038"),
|
||||
sealChamber = song("Seal Chamber", "0039"),
|
||||
torokosTheme = song("Toroko's Theme", "0040"),
|
||||
white = song('"White"', "0041"),
|
||||
windFortress = song("Wind Fortress", "0042", false, "beta"),
|
||||
halloween2 = song("Halloween 2", "0043", false, true),
|
||||
peopleOfTheRoot = song("People of the Root", "0044", false, "beta"),
|
||||
pierWalk = song("Pier Walk", "0045", false, "beta"),
|
||||
snoopyCake = song("Snoopy Cake", "0046", false, "beta"),
|
||||
dataSlots = song("Data Slots", "0047", false, "kero"),
|
||||
catAndFrog = song("Cat & Frog Corp.", "0048", false, "kero"),
|
||||
--itsMyBlaster = song("It's My Blaster!", "0049", false, "kero"),
|
||||
shoppingCart = song("Shopping Cart", "0050", false, "kero"),
|
||||
prothallium = song("Prothallium", "0051", false, "kero"),
|
||||
hardCording = song("Hard Cording", "0052", false, "kero"),
|
||||
newItem = song("New Item!", "0053", false, "kero"), --kind of jingle, kind of not?
|
||||
checkinOut = song("Check'IN Out", "0054", false, "kero"),
|
||||
sukima = song("SUKIMA", "0055", false, "kero"),
|
||||
relaxation = song("Relaxation", "0056", false, "kero"),
|
||||
chemistry = song("Chemistry", "0057", false, "kero"),
|
||||
--arrival = song("Arrival", "0058", false, "kero"),
|
||||
freezeDraft = song("Freeze Draft", "0059", false, "kero"),
|
||||
magicNumber = song("Magic Number", "0060", false, "kero"),
|
||||
--timeTable = song("Time Table", "0061", false, "kero"),
|
||||
--number1119 = song("Number 1119", "0061", false, "kero"),
|
||||
trainStation = song("Train Station", "0062", false, "kero"),
|
||||
--totoStation = song("ToTo Station", "0063", false, "kero"),
|
||||
kaishaMan = song("Kaisha Man", "0064", false, "kero"),
|
||||
zombeat = song("Zombeat", "0065", false, "kero"),
|
||||
--oyasumiSong = song("Oyasumi Song", "0066", false, "kero"),
|
||||
--changeSpec = song("Change Spec", "0067", false, "kero"),
|
||||
--curtainRise = song("Curtain Rise", "0068", false, "kero"),
|
||||
--creditsOfKero = song("Credits of Kero", "0069", false, "kero"),
|
||||
--myPreciousDays = song("My Precious Days", "0070", false, "kero"),
|
||||
--excuseMe = song("Excuse Me...", "0071", false, "kero"),
|
||||
}
|
||||
|
||||
local cue = Class:extend()
|
||||
function cue:new(map, events, default)
|
||||
self.map = map
|
||||
self.events = events
|
||||
self.songid = default
|
||||
self.default = default
|
||||
end
|
||||
|
||||
local cues = {
|
||||
cue("0", {"0100"}, "0033"),
|
||||
cue("0", {"0200"}, "0001"),
|
||||
cue("Pens1", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0002"),
|
||||
cue("Pens1", {"0095", "0098"}, "0014"),
|
||||
cue("Eggs", {"0090", "0091", "0092", "0093", "0094", "0095", "0099", "0503"}, "0001"),
|
||||
cue("Eggs", {"0600"}, "0004"),
|
||||
cue("EggX", {"0095"}, "0014"),
|
||||
cue("EggR", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
|
||||
cue("Weed", {"0090", "0091", "0092", "0093", "0094", "0098", "0099", "0600"}, "0005"),
|
||||
cue("Santa", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
|
||||
cue("Santa", {"0099"}, "0028"),
|
||||
cue("Chako", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
|
||||
cue("Chako", {"0099"}, "0028"),
|
||||
cue("MazeI", {"0090", "0091", "0092", "0093", "0094", "0400", "0601"}, "0019"),
|
||||
cue("Sand", {"0090", "0091", "0092", "0093", "0094", "0099", "0210", "0601"}, "0006"),
|
||||
cue("Sand", {"0202"}, "0007"),
|
||||
cue("Mimi", {"0090", "0091", "0092", "0093", "0094", "0302"}, "0009"),
|
||||
cue("Mimi", {"0095", "0096", "0097", "0098", "0099"}, "0028"),
|
||||
cue("Cave", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
|
||||
cue("Start", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
|
||||
cue("Barr", {"0090", "0091", "0092", "0093", "0094", "0402", "1001"}, "0008"),
|
||||
cue("Barr", {"1000"}, "0011"),
|
||||
cue("Barr", {"1000"}, "0004"),
|
||||
cue("Pool", {"0090", "0091", "0092", "0093", "0094"}, "0009"),
|
||||
cue("Pool", {"0095", "0096", "0097", "0098", "0099", "0410"}, "0028"),
|
||||
cue("Cemet", {"0090", "0091", "0092", "0093", "0094"}, "0012"),
|
||||
cue("Plant", {"0090", "0091", "0092", "0093", "0094"}, "0013"),
|
||||
cue("Plant", {"0095", "0096", "0097", "0098", "0099"}, "0028"),
|
||||
cue("Shelt", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0008"),
|
||||
cue("Comu", {"0090", "0091", "0092", "0093", "0094"}, "0009"),
|
||||
cue("Comu", {"0095", "0096", "0097", "0098", "0099"}, "0028"),
|
||||
cue("Cthu", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
|
||||
cue("Malco", {"0090", "0091", "0092", "0093", "0094", "0203"}, "0008"),
|
||||
cue("Malco", {"0200", "0204"}, "0004"),
|
||||
cue("Malco", {"0200"}, "0011"),
|
||||
cue("Frog", {"0090", "0091", "0092", "0093", "0094", "1000"}, "0008"),
|
||||
cue("Frog", {"0202"}, "0011"),
|
||||
cue("Frog", {"0202"}, "0007"),
|
||||
cue("Curly", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
|
||||
cue("Curly", {"0300"}, "0004"),
|
||||
cue("WeedB", {"0302"}, "0004"),
|
||||
cue("Stream", {"0090", "0091", "0092", "0093", "0094"}, "0018"),
|
||||
cue("Jenka1", {"0090", "0091", "0092", "0093", "0094"}, "0019"),
|
||||
cue("Gard", {"0502"}, "0017"),
|
||||
cue("Gard", {"0502"}, "0018"),
|
||||
cue("Gard", {"0502"}, "0004"),
|
||||
cue("Jenka2", {"0090", "0091", "0092", "0093", "0095", "0200"}, "0019"),
|
||||
cue("Jenka2", {"0094"}, "0011"),
|
||||
cue("SandE", {"0090", "0091", "0092", "0093", "0094", "0600"}, "0006"),
|
||||
cue("SandE", {"0600"}, "0011"),
|
||||
cue("MazeH", {"0090", "0091", "0092", "0093", "0094"}, "0019"),
|
||||
cue("MazeW", {"0090", "0091", "0092", "0093", "0094", "1000"}, "0037"),
|
||||
cue("MazeW", {"0302"}, "0007"),
|
||||
cue("MazeO", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
|
||||
cue("MazeD", {"0400"}, "0004"),
|
||||
cue("MazeA", {"0090", "0091", "0092", "0093", "0094", "0301"}, "0008"),
|
||||
cue("MazeB", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0008"),
|
||||
cue("MazeS", {"0090", "0091", "0092", "0093", "0094", "0310", "0600"}, "0008"),
|
||||
cue("MazeS", {"0321"}, "0011"),
|
||||
cue("MazeS", {"0321"}, "0004"),
|
||||
cue("MazeM", {"0090", "0091", "0092", "0093", "0094", "0301"}, "0020"),
|
||||
cue("Drain", {"0090", "0091", "0092", "0093", "0094", "0150"}, "0008"),
|
||||
cue("Drain", {"0095", "0096", "0097"}, "0023"),
|
||||
cue("Almond", {"0090", "0091", "0092", "0093", "0094", "0361"}, "0023"),
|
||||
cue("Almond", {"0452", "0500"}, "0022"),
|
||||
cue("River", {"0090", "0091", "0092", "0093", "0094", "0095"}, "0038"),
|
||||
cue("Eggs2", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0027"),
|
||||
cue("Cthu2", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
|
||||
cue("EggR2", {"0090", "0091", "0092", "0093", "0094", "1000"}, "0008"),
|
||||
cue("EggR2", {"0304"}, "0004"),
|
||||
cue("EggX2", {"0090", "0091", "0092", "0093", "0095"}, "0014"),
|
||||
cue("Oside", {"0090", "0091", "0092", "0093", "0094"}, "0025"),
|
||||
cue("Oside", {"0402"}, "0026"),
|
||||
cue("Itoh", {"0090", "0091", "0092", "0093", "0094", "0095"}, "0008"),
|
||||
cue("Cent", {"0090", "0091", "0092", "0093", "0094"}, "0024"),
|
||||
cue("Jail1", {"0090", "0091", "0092", "0093", "0094", "0220"}, "0008"),
|
||||
cue("Momo", {"0090", "0091", "0092", "0093", "0094", "0280", "0281"}, "0002"),
|
||||
cue("Lounge", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
|
||||
cue("Jail2", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0008"),
|
||||
cue("Blcny1", {"0090", "0091", "0092", "0093", "0094"}, "0030"),
|
||||
cue("Priso1", {"0090", "0091", "0092", "0093", "0094"}, "0029"),
|
||||
cue("Ring1", {"0090", "0091", "0092", "0093", "0094", "0097", "0098", "0402"}, "0030"),
|
||||
cue("Ring1", {"0502", "0503"}, "0007"),
|
||||
cue("Ring1", {"0099"}, "0018"),
|
||||
cue("Ring2", {"0090", "0091", "0092", "0093", "0094", "0098", "0420"}, "0030"),
|
||||
cue("Ring2", {"0502"}, "0007"),
|
||||
cue("Ring2", {"0410"}, "0031"),
|
||||
cue("Ring2", {"0099"}, "0018"),
|
||||
cue("Prefa1", {"0090", "0091", "0092", "0093", "0094"}, "0030"),
|
||||
cue("Priso2", {"0090", "0091", "0092", "0093", "0094", "0250"}, "0029"),
|
||||
cue("Priso2", {"0241"}, "0004"),
|
||||
cue("Ring3", {"0502"}, "0034"),
|
||||
cue("Ring3", {"0502"}, "0032"),
|
||||
cue("Ring3", {"0600"}, "0018"),
|
||||
cue("Little", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
|
||||
cue("Blcny2", {"0090", "0091", "0092", "0093", "0094", "0310"}, "0018"),
|
||||
cue("Blcny2", {"0400"}, "0035"),
|
||||
cue("Pixel", {"0090", "0091", "0092", "0093", "0094", "0253"}, "0014"),
|
||||
cue("Hell1", {"0090", "0091", "0092", "0094", "0096"}, "0036"),
|
||||
cue("Hell2", {"0090", "0091", "0092", "0093", "0094"}, "0036"),
|
||||
cue("Hell3", {"0090", "0091", "0092", "0093", "0094"}, "0036"),
|
||||
cue("Hell3", {"0300"}, "0007"),
|
||||
cue("Mapi", {"0420"}, "0004"),
|
||||
cue("Statue", {"0100"}, "0024"),
|
||||
cue("Ballo1", {"0095"}, "0039"),
|
||||
cue("Ballo1", {"0500"}, "0004"),
|
||||
cue("Ballo1", {"0900"}, "0007"),
|
||||
cue("Ballo1", {"1000"}, "0032"),
|
||||
cue("Pole", {"0090", "0091", "0092", "0093", "0094", "0095"}, "0008"),
|
||||
cue("Ballo2", {"0500"}, "0034")
|
||||
}
|
||||
|
||||
local music = Class:extend()
|
||||
function music:new()
|
||||
self.vanillaEnabled = true
|
||||
self.betaEnabled = false
|
||||
self.keroEnabled = false
|
||||
self.songs = songs
|
||||
self.cues = cues
|
||||
self.flavor = "Shuffle"
|
||||
end
|
||||
|
||||
local _isValid = function(key, song, self, canRemap)
|
||||
if song.jingle then return false end
|
||||
if song.game == "vanilla" and (canRemap or self.vanillaEnabled) then return true end
|
||||
if song.game == "beta" and self.betaEnabled then return true end
|
||||
if song.game == "kero" and self.keroEnabled then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function music:getShuffledSongs()
|
||||
return _.shuffle(_.filter(self.songs, _isValid, self))
|
||||
end
|
||||
|
||||
function music:getCues()
|
||||
return self.cues
|
||||
end
|
||||
|
||||
function music:shuffleMusic(tscFiles)
|
||||
if self.flavor == "Shuffle" then self:_shuffle(tscFiles) end
|
||||
if self.flavor == "Random" then self:_random(tscFiles) end
|
||||
if self.flavor == "Chaos" then self:_chaos(tscFiles) end
|
||||
end
|
||||
|
||||
-- SHUFFLE songs: every cue with a given song becomes the same, new song
|
||||
function music:_shuffle(tscFiles)
|
||||
local shuffled = self:getShuffledSongs()
|
||||
|
||||
local idmap = _.map(self.songs, function (k,v)
|
||||
-- don't remap any invalid songs
|
||||
if not _isValid(k,v,self,true) then return v.id, v.id end
|
||||
if #shuffled == 0 then shuffled = self:getShuffledSongs() end
|
||||
return v.id, _.pop(shuffled).id
|
||||
end)
|
||||
|
||||
for k,cue in pairs(self.cues) do
|
||||
cue.songid = idmap[cue.songid]
|
||||
end
|
||||
self:writeCues(tscFiles)
|
||||
end
|
||||
|
||||
-- RANDOMIZE songs: any cue can play any song
|
||||
function music:_random(tscFiles)
|
||||
for k,cue in pairs(self.cues) do
|
||||
cue.songid = self:getShuffledSongs()[1].id
|
||||
end
|
||||
self:writeCues(tscFiles)
|
||||
end
|
||||
|
||||
-- CHAOTICALLY RANDOMIZE songs: nearly any <CMU can play any song
|
||||
function music:_chaos(tscFiles)
|
||||
for k,cue in pairs(self.cues) do
|
||||
for k,event in ipairs(cue.events) do
|
||||
tscFiles[cue.map]:placeSongAtCue(self:getShuffledSongs()[1].id, event, cue.map, cue.default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function music:writeCues(tscFiles)
|
||||
for k,cue in pairs(self.cues) do
|
||||
for k,event in ipairs(cue.events) do
|
||||
tscFiles[cue.map]:placeSongAtCue(cue.songid, event, cue.map, cue.default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return music
|
|
@ -1,38 +0,0 @@
|
|||
local C = Class:extend()
|
||||
|
||||
function C:new(worldGraph, name, hints)
|
||||
self.locations = {}
|
||||
self.world = worldGraph
|
||||
self.name = name
|
||||
self.order = worldGraph.order
|
||||
worldGraph.order = worldGraph.order + 1
|
||||
self.hintList = hints or {}
|
||||
end
|
||||
|
||||
function C:canAccess(items)
|
||||
if self.requirements == nil then return true end
|
||||
return self.requirements(self, items)
|
||||
end
|
||||
|
||||
function C:getLocation(key)
|
||||
return self.locations[key]
|
||||
end
|
||||
|
||||
function C:getLocations(filterFn)
|
||||
filterFn = filterFn or function(k,v) return true end
|
||||
return _.filter(self.locations, filterFn)
|
||||
end
|
||||
|
||||
function C:getEmptyLocations()
|
||||
return self:getLocations(function(k,v) return not v:hasItem() end)
|
||||
end
|
||||
|
||||
function C:getFilledLocations()
|
||||
return self:getLocations(function(k,v) return v:hasItem() end)
|
||||
end
|
||||
|
||||
function C:writeItems(tscFiles)
|
||||
for key, location in pairs(self.locations) do location:writeItem(tscFiles) end
|
||||
end
|
||||
|
||||
return C
|
|
@ -1,772 +0,0 @@
|
|||
local Region = require 'database.region'
|
||||
local Location = require 'database.location'
|
||||
|
||||
function _has(items, attribute)
|
||||
return _count(items, attribute, 1)
|
||||
end
|
||||
|
||||
function _num(items, attribute)
|
||||
return #_.filter(items, function(k,v) return _.contains(v.attributes or {}, attribute) end)
|
||||
end
|
||||
|
||||
function _count(items, attribute, num)
|
||||
return _num(items, attribute) >= num
|
||||
end
|
||||
|
||||
function _hp(items, hp)
|
||||
return 3 + (_num(items, "hp3") * 3) + (_num(items, "hp4") * 4) + (_num(items, "hp5") * 5)
|
||||
end
|
||||
|
||||
local firstCave = Region:extend()
|
||||
function firstCave:new(worldGraph)
|
||||
firstCave.super.new(self, worldGraph, "First Cave", {"in First Cave"})
|
||||
self.locations = {
|
||||
firstCapsule = Location("First Cave Life Capsule", "Cave", "0401", self),
|
||||
gunsmithChest = Location("Hermit Gunsmith Chest", "Pole", "0202", self, {"at someone's house", "with Tetsuzou"}),
|
||||
gunsmith = Location("Tetsuzou", "Pole", "0303", self, {"at someone's house", "with Tetsuzou"}),
|
||||
objective = Location("Game Settings", "Start", "0201", self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
return _has(items, "flight") and _has(items, "weaponSN") and self.world.regions.mimigaVillage:canAccess(items)
|
||||
end
|
||||
|
||||
self.locations.gunsmith.requirements = function(self, items)
|
||||
return _has(items, "polarStar") and _has(items, "eventCore")
|
||||
end
|
||||
|
||||
-- individual location access requirement overrides
|
||||
self.locations.firstCapsule.canAccess = function(self, items)
|
||||
if self.region.world:StartPoint() then return true end -- TODO remember to add a check for Entrance Rando here
|
||||
return Location.canAccess(self, items)
|
||||
end
|
||||
|
||||
self.locations.gunsmithChest.canAccess = function(self, items)
|
||||
if self.region.world:StartPoint() then return true end -- TODO remember to add a check for Entrance Rando here
|
||||
return Location.canAccess(self, items)
|
||||
end
|
||||
end
|
||||
|
||||
local mimigaVillage = Region:extend()
|
||||
function mimigaVillage:new(worldGraph)
|
||||
mimigaVillage.super.new(self, worldGraph, "Mimiga Village", {"in Mimiga Village"})
|
||||
self.locations = {
|
||||
yamashita = Location("Yamashita Farm", "Plant", "0401", self, {"underwater", "in a garden"}),
|
||||
reservoir = Location("Reservoir", "Pool", "0301", self, {"underwater"}),
|
||||
mapChest = Location("Mimiga Village Chest", "Mimi", "0202", self),
|
||||
assembly = Location("Assembly Hall Fireplace", "Comu", "0303", self, {"in a fireplace"}),
|
||||
mrLittle = Location("Mr. Little (Graveyard)", "Cemet", "0202", self, {"in the Graveyard", "with a very little man, hiding in the grass"}),
|
||||
grave = Location("Arthur's Grave", "Cemet", "0301", self, {"with a fallen hero..", "in the Graveyard"}),
|
||||
mushroomChest = Location("Storage? Chest", "Mapi", "0202", self, {"in the Graveyard"}),
|
||||
maPignon = Location("Ma Pignon Boss", "Mapi", "0501", self, {"behind a boss", "behind Ma Pignon", "in the Graveyard"})
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if self.world:StartPoint() then
|
||||
return _has(items, "weaponSN")
|
||||
elseif self.world:Camp() or self.world:Arthur() then
|
||||
return _has(items, "arthurKey") and self.world.regions.arthur:canAccess(items) or self.world.regions.waterway:canAccess(items)
|
||||
end
|
||||
end
|
||||
|
||||
self.locations.assembly.requirements = function(self, items) return _has(items, "juice") end
|
||||
self.locations.mrLittle.requirements = function(self, items) return _has(items, "locket") end
|
||||
self.locations.grave.requirements = function(self, items) return _has(items, "locket") end
|
||||
self.locations.mushroomChest.requirements = function(self, items)
|
||||
return _has(items, "flight") and _has(items, "locket") and _has(items, "eventCurly")
|
||||
end
|
||||
self.locations.maPignon.requirements = function(self, items)
|
||||
-- stupid mushroom is invincible to the blade and machinegun for some reason
|
||||
if _has(items, "flight") and _has(items, "locket") and _has(items, "mushroomBadge") then
|
||||
if _has(items, "polarStar") or _has(items, "fireball") or _has(items, "bubbler")
|
||||
or _has(items, "machineGun") or _has(items, "snake") or _has(items, "nemesis") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--self.locations.mrLittle:setItem(self.world.items:getByKey("mrLittle"))
|
||||
end
|
||||
|
||||
local arthur = Region:extend()
|
||||
function arthur:new(worldGraph)
|
||||
arthur.super.new(self, worldGraph, "Arthur's House", {"in Mimiga Village", "at Arthur's House", "at someone's house"})
|
||||
self.locations = {
|
||||
risenBooster = Location("Professor Booster", "Pens1", "0652", self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if self.world:StartPoint() then
|
||||
return _has(items, "arthurKey") and _has(items, "weaponSN") and self.world.regions.mimigaVillage:canAccess(items)
|
||||
elseif self.world:Camp() then
|
||||
return self.world.regions.labyrinthB:canAccess(items)
|
||||
elseif self.world:Arthur() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
self.locations.risenBooster.requirements = function(self, items) return _has(items, "eventCore") end
|
||||
end
|
||||
|
||||
local eggCorridor1 = Region:extend()
|
||||
function eggCorridor1:new(worldGraph)
|
||||
eggCorridor1.super.new(self, worldGraph, "Egg Corridor", {"in the Egg Corridor", "in the normal Egg Corridor"})
|
||||
self.locations = {
|
||||
basil = Location("Basil Spot", "Eggs", "0403", self),
|
||||
cthulhu = Location("Cthulhu's Abode", "Eggs", "0404", self),
|
||||
eggItem = Location("Egg Chest", "Egg6", "0201", self, {"in an egg"}),
|
||||
observationChest = Location("Egg Observation Room Chest", "EggR", "0301", self, {"in the Egg Observation Room"}),
|
||||
eventSue = Location("Saved Sue", nil, nil, self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items) return self.world.regions.arthur:canAccess(items) end
|
||||
|
||||
self.locations.cthulhu.requirements = function(self, items) return _has(items, "weaponSN") or _has(items, "flight") or self.region.world:_dboost(items, 'cthulhu') end
|
||||
self.locations.eventSue.requirements = function(self, items) return _has(items, "idCard") and _has(items, "weaponBoss") end
|
||||
self.locations.eventSue:setItem(self.world.items:getByKey("eventSue"))
|
||||
end
|
||||
|
||||
local grasstownWest = Region:extend()
|
||||
function grasstownWest:new(worldGraph)
|
||||
grasstownWest.super.new(self, worldGraph, "Grasstown (West)", {"in Grasstown", "in West Grasstown"})
|
||||
self.locations = {
|
||||
keySpot = Location("West Grasstown Floor", "Weed", "0700", self),
|
||||
jellyCapsule = Location("West Grasstown Ceiling", "Weed", "0701", self),
|
||||
santa = Location("Santa", "Santa", "0501", self, {"at Santa's House", "at someone's house"}),
|
||||
charcoal = Location("Santa's Fireplace", "Santa", "0302", self, {"at Santa's House", "at someone's house", "in a fireplace"}),
|
||||
chaco = Location("Chaco's Bed, where you two Had A Nap", "Chako", "0211", self, {"at Chaco's House", "in Chaco's bed", "at someone's house"}),
|
||||
kulala = Location("Kulala Chest", "Weed", "0702", self, {"in a sticky situation"})
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
return self.world.regions.arthur:canAccess(items)
|
||||
end
|
||||
|
||||
self.locations.santa.requirements = function(self, items) return _has(items, "santaKey") end
|
||||
self.locations.charcoal.requirements = function(self, items) return _has(items, "santaKey") and _has(items, "juice") end
|
||||
self.locations.chaco.requirements = function(self, items) return _has(items, "santaKey") end
|
||||
self.locations.kulala.requirements = function(self, items) return _has(items, "santaKey") and _has(items, "weapon") end
|
||||
end
|
||||
|
||||
local grasstownEast = Region:extend()
|
||||
function grasstownEast:new(worldGraph)
|
||||
grasstownEast.super.new(self, worldGraph, "Grasstown (East)", {"in Grasstown", "in East Grasstown"})
|
||||
self.locations = {
|
||||
kazuma1 = Location("Kazuma Crack", "Weed", "0800", self, {"with Kazuma", "at the Shelter"}),
|
||||
kazuma2 = Location("Kazuma Chest", "Weed", "0801", self, {"at the Shelter"}),
|
||||
execution = Location("Execution Chamber", "WeedD", "0305", self),
|
||||
outsideHut = Location("Grasstown East Chest", "Weed", "0303", self, {"at Grasstown Hut"}),
|
||||
hutChest = Location("Grasstown Hut", "WeedB", "0301", self, {"at Grasstown Hut"}),
|
||||
gumChest = Location("Gum Chest", "Frog", "0300", self, {"behind a boss", "behind Balfrog"}),
|
||||
malco = Location("MALCO", "Malco", "0350", self, {"with MALCO", "behind a boss", "behind Balrog 2"}),
|
||||
eventFans = Location("Activated Grasstown Fans", nil, nil, self),
|
||||
eventKazuma = Location("Saved Kazuma", nil, nil, self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if not self.world.regions.arthur:canAccess(items) then return false end
|
||||
if _has(items, "flight") or _has(items, "juice") or (self.world:_dboost(items, 'chaco') and _has(items, "weapon")) or self.world:_dboost(items, 'paxChaco') then
|
||||
if self.world.regions.grasstownWest:canAccess(items) then return true end
|
||||
end
|
||||
if _has(items, "eventKazuma") and _has(items, "weaponSN") and self.world.regions.plantation:canAccess(items) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
self.locations.kazuma2.requirements = function(self, items) return _has(items, "rustyKey") end
|
||||
self.locations.execution.requirements = function(self, items) return _has(items, "weaponSN") end
|
||||
self.locations.hutChest.requirements = function(self, items) return _has(items, "eventFans") or _has(items, "flight") or self.region.world:_dboost(items, 'flightlessHut') end
|
||||
self.locations.gumChest.requirements = function(self, items)
|
||||
if _has(items, "gumKey") and _has(items, "weaponBoss") then
|
||||
if _has(items, "eventFans") or _has(items, "flight") then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
self.locations.malco.requirements = function(self, items) return _has(items, "eventFans") and _has(items, "juice") and _has(items, "charcoal") and _has(items, "gumBase") end
|
||||
self.locations.malco.getPrebuiltHint = function(self) return ("BUT ALL I KNOW HOW TO DO IS MAKE %s..."):format(self.item.hints[love.math.random(#self.item.hints)]:upper()) end
|
||||
|
||||
self.locations.eventFans.requirements = function(self, items) return _has(items, "rustyKey") and _has(items, "weaponBoss") end
|
||||
self.locations.eventFans:setItem(self.world.items:getByKey("eventFans"))
|
||||
|
||||
self.locations.eventKazuma.requirements = function(self, items) return _has(items, "bomb") end
|
||||
self.locations.eventKazuma:setItem(self.world.items:getByKey("eventKazuma"))
|
||||
end
|
||||
|
||||
local upperSandZone = Region:extend()
|
||||
function upperSandZone:new(worldGraph)
|
||||
upperSandZone.super.new(self, worldGraph, "Sand Zone (Upper)", {"in the Sand Zone", "in Upper Sand Zone"})
|
||||
self.locations = {
|
||||
curly = Location("Curly Boss", "Curly", "0518", self, {"in the Sand Zone Residence", "with Curly", "at someone's house"}),
|
||||
panties = Location("Curly's Closet", "CurlyS", "0421", self, {"in the Sand Zone Residence", "at someone's house"}),
|
||||
curlyPup = Location("Puppy (Curly)", "CurlyS", "0401", self, {"in the Sand Zone Residence", "at someone's house", "where a puppy once was"}),
|
||||
sandCapsule = Location("Polish Spot", "Sand", "0502", self),
|
||||
eventOmega = Location("Defeated Omega", nil, nil, self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
return self.world.regions.arthur:canAccess(items) and _has(items, "weaponSN")
|
||||
end
|
||||
|
||||
self.locations.curly.requirements = function(self, items) return _has(items, "polarStar") end
|
||||
|
||||
self.locations.panties.requirements = function(self, items) return _has(items, "weaponBoss") end
|
||||
self.locations.curlyPup.requirements = function(self, items) return _has(items, "weaponBoss") end
|
||||
|
||||
self.locations.eventOmega.requirements = function(self, items) return _has(items, "weaponBoss") end
|
||||
self.locations.eventOmega:setItem(self.world.items:getByKey("eventOmega"))
|
||||
end
|
||||
|
||||
local lowerSandZone = Region:extend()
|
||||
function lowerSandZone:new(worldGraph)
|
||||
lowerSandZone.super.new(self, worldGraph, "Sand Zone (Lower)", {"in the Sand Zone", "in Lower Sand Zone"})
|
||||
self.locations = {
|
||||
chestPup = Location("Puppy (Chest)", "Sand", "0423", self, {"where a puppy once was"}),
|
||||
darkPup = Location("Puppy (Dark)", "Dark", "0401", self, {"where a puppy once was"}),
|
||||
runPup = Location("Puppy (Run)", "Sand", "0422", self, {"where a puppy once was", "with a puppy"}),
|
||||
sleepyPup = Location("Puppy (Sleep)", "Sand", "0421", self, {"where a puppy once was", "at the far end of Sand Zone"}),
|
||||
pawCapsule = Location("Pawprint Spot", "Sand", "0503", self),
|
||||
jenka = Location("Jenka", "Jenka2", "0221", self, {"with Jenka", "at someone's house", "where a puppy once was"}),
|
||||
king = Location("King", "Gard", "0602", self, {"with a fallen hero..", "in a garden", "behind a boss", "behind Toroko+"}),
|
||||
eventToroko = Location("Defeated Toroko+", nil, nil, self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if self.world:StartPoint() or self.world:Arthur() then
|
||||
return _has(items, "eventOmega") and self.world.regions.upperSandZone:canAccess(items)
|
||||
elseif self.world:Camp() then
|
||||
return self.world.regions.labyrinthW:canAccess(items)
|
||||
end
|
||||
end
|
||||
|
||||
self.locations.jenka.requirements = function(self, items) return _count(items, "puppy", 5) end
|
||||
self.locations.king.requirements = function(self, items) return _has(items, "eventToroko") end
|
||||
|
||||
self.locations.eventToroko.requirements = function(self, items)
|
||||
return _count(items, "puppy", 5) and _has(items, "weaponBoss")
|
||||
end
|
||||
self.locations.eventToroko:setItem(self.world.items:getByKey("eventToroko"))
|
||||
|
||||
self.locations.jenka.getPrebuiltHint = function(self) return ("perhaps I'll give you %s in return..."):format(self.item.hints[love.math.random(#self.item.hints)]) end
|
||||
end
|
||||
|
||||
local labyrinthW = Region:extend()
|
||||
function labyrinthW:new(worldGraph)
|
||||
labyrinthW.super.new(self, worldGraph, "Labyrinth (West)", {"in the Labyrinth", "in West Labyrinth"})
|
||||
self.locations = {
|
||||
mazeCapsule = Location("Labyrinth Life Capsule", "MazeI", "0301", self),
|
||||
turboChaba = Location("Chaba Chest (Machine Gun)", "MazeA", "0502", self, {"with Chaba", "in Chaba's Machine Gun chest"}),
|
||||
snakeChaba = Location("Chaba Chest (Fireball)", "MazeA", "0512", self, {"with Chaba", "in Chaba's Fireball chest"}),
|
||||
whimChaba = Location("Chaba Chest (Spur)", "MazeA", "0522", self, {"with Chaba", "in Chaba's Spur chest"}),
|
||||
campChest = Location("Camp Chest", "MazeO", "0401", self, {"in the Camp"}),
|
||||
physician = Location("Dr. Gero", "MazeO", "0305", self, {"in the Camp"}),
|
||||
puuBlack = Location("Puu Black Boss", "MazeD", "0201", self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if self.world:StartPoint() or self.world:Arthur() then
|
||||
if not self.world.regions.arthur:canAccess(items) then return false end
|
||||
if _has(items, "eventToroko") and self.world.regions.lowerSandZone:canAccess(items) then return true end
|
||||
if _has(items, "flight") and _has(items, "weaponBoss") and self.world.regions.labyrinthB:canAccess(items) then return true end
|
||||
return false
|
||||
elseif self.world:Camp() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
self.locations.mazeCapsule.requirements = function(self, items) return _has(items, "weapon") end
|
||||
self.locations.turboChaba.requirements = function(self, items) return _has(items, "machineGun") end
|
||||
self.locations.snakeChaba.requirements = function(self, items) return _has(items, "fireball") end
|
||||
self.locations.whimChaba.requirements = function(self, items) return _count(items, "polarStar", 2) end
|
||||
self.locations.campChest.requirements = function(self, items) return _has(items, "flight") or self.region.world:Camp() or self.region.world:_dboost(items, 'camp') end
|
||||
self.locations.puuBlack.requirements = function(self, items) return _has(items, "clinicKey") and _has(items, "weaponBoss") end
|
||||
|
||||
end
|
||||
|
||||
local labyrinthB = Region:extend()
|
||||
function labyrinthB:new(worldGraph)
|
||||
labyrinthB.super.new(self, worldGraph, "Labyrinth B", {"in the Labyrinth"})
|
||||
self.locations = {
|
||||
fallenBooster = Location("Booster Chest", "MazeB", "0502", self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if self.world:StartPoint() or self.world:Arthur() then
|
||||
return self.world.regions.arthur:canAccess(items)
|
||||
elseif self.world:Camp() then
|
||||
return self.world.regions.labyrinthW:canAccess(items) and _has(items, "weaponBoss")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local boulder = Region:extend()
|
||||
function boulder:new(worldGraph)
|
||||
boulder.super.new(self, worldGraph, "Labyrinth (East)", {"in the Labyrinth", "in East Labyrinth", "behind a boss"})
|
||||
self.locations = { --include core locations since core access reqs are identical to boulder chamber
|
||||
boulderChest = Location("Boulder Chest", "MazeS", "0202", self, {"behind Balrog 3"}),
|
||||
coreSpot = Location("Robot's Arm", "Almond", "0243", self, {"underwater", "behind the Core"}),
|
||||
curlyCorpse = Location("Drowned Curly", "Almond", "1111", self, {"with Curly", "underwater", "behind the Core"}),
|
||||
eventCore = Location("Defeated Core", nil, nil, self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if not self.world.regions.arthur:canAccess(items) then return false end
|
||||
if _has(items, "cureAll") and _has(items, "weaponBoss") then
|
||||
if self.world.regions.labyrinthW:canAccess(items) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
self.locations.eventCore:setItem(self.world.items:getByKey("eventCore"))
|
||||
end
|
||||
|
||||
local labyrinthM = Region:extend()
|
||||
function labyrinthM:new(worldGraph)
|
||||
labyrinthM.super.new(self, worldGraph, "labyrinthM")
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if not self.world.regions.arthur:canAccess(items) then return false end
|
||||
if self.world.regions.boulder:canAccess(items) then return true end
|
||||
if _has(items, "flight") and self.world.regions.labyrinthW:canAccess(items) then return true end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local waterway = Region:extend()
|
||||
function waterway:new(worldGraph)
|
||||
waterway.super.new(self, worldGraph, "Waterway", {"in the Waterway", "underwater", "behind a boss", "behind Ironhead"})
|
||||
|
||||
self.locations = {
|
||||
ironhead = Location("Ironhead Boss", "Pool", "0412", self),
|
||||
eventCurly = Location("Saved Curly", nil, nil, self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if _has(items, "airTank") and _has(items, "weaponBoss") and self.world.regions.labyrinthM:canAccess(items) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
self.locations.eventCurly.requirements = function(self, items) return _has(items, "eventCore") and _has(items, "towRope") end
|
||||
self.locations.eventCurly:setItem(self.world.items:getByKey("eventCurly"))
|
||||
end
|
||||
|
||||
local eggCorridor2 = Region:extend()
|
||||
function eggCorridor2:new(worldGraph)
|
||||
eggCorridor2.super.new(self, worldGraph, "Egg Corridor?", {"in the Egg Corridor", "in ruined Egg Corridor"})
|
||||
|
||||
self.locations = {
|
||||
dragonChest = Location("Dragon Chest", "Eggs2", "0321", self),
|
||||
sisters = Location("Sisters Boss", "EggR2", "0303", self, {"behind a boss", "in the Egg Observation Room", "behind the Sisters"})
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if not self.world.regions.arthur:canAccess(items) then return false end
|
||||
if _has(items, "eventCore") then return true end
|
||||
if _has(items, "eventKazuma") and self.world.regions.outerWall:canAccess(items) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
self.locations.dragonChest.requirements = function(self, items) return _has(items, "weaponSN") or _has(items, "eventCore") end
|
||||
self.locations.sisters.requirements = function(self, items) return _has(items, "weaponBoss") or (self.region.world:_dboost(items, 'sisters') and _has(items, "flight")) end
|
||||
end
|
||||
|
||||
local outerWall = Region:extend()
|
||||
function outerWall:new(worldGraph)
|
||||
outerWall.super.new(self, worldGraph, "Outer Wall", {"outside", "along the Outer Wall"})
|
||||
|
||||
self.locations = {
|
||||
clock = Location("Clock Room", "Clock", "0300", self),
|
||||
littleHouse = Location("Little House", "Little", "0204", self, {"with Mr. Little", "at someone's house"})
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if not self.world.regions.arthur:canAccess(items) and (_has(items, "weapon") or _hp(items) >= 9 or _has(items, "flight")) then return false end
|
||||
if _has(items, "eventKazuma") and _has(items, "flight") and _has(items, "eventCore") then return true end
|
||||
if _has(items, "teleportKey") and self.world.regions.plantation:canAccess(items) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
self.locations.littleHouse.requirements = function(self, items) return _has(items, "flight") and _has(items, "blade") and _has(items, "mrLittle") end
|
||||
self.locations.littleHouse.getPrebuiltHint = function(self) return ("He was exploring the island with %s..."):format(self.item.hints[love.math.random(#self.item.hints)]) end
|
||||
end
|
||||
|
||||
local plantation = Region:extend()
|
||||
function plantation:new(worldGraph)
|
||||
plantation.super.new(self, worldGraph, "Plantation", {"in the Plantation", "in a garden"})
|
||||
|
||||
self.locations = {
|
||||
kanpachi = Location("Kanpachi's Bucket", "Cent", "0268", self, {"with Kanpachi", "underwater"}),
|
||||
jail1 = Location("Jail no. 1", "Jail1", "0301", self, {"with Mahin"}),
|
||||
momorin = Location("Chivalry Sakamoto's Wife", "Momo", "0201", self, {"with Momorin", "at someone's house"}),
|
||||
sprinkler = Location("Broken Sprinkler", "Cent", "0417", self, {"for Mimiga only"}),
|
||||
megane = Location("Megane", "Lounge", "0204", self, {"for Mimiga only"}),
|
||||
itoh = Location("Itoh", "Itoh", "0405", self, {"with Itoh"}),
|
||||
plantCeiling = Location("Plantation Platforming Spot", "Cent", "0501", self),
|
||||
plantPup = Location("Plantation Puppy", "Cent", "0452", self, {"with a puppy"}),
|
||||
curlyShroom = Location("Jammed it into Curly's Mouth", "Cent", "0324", self, {"with Curly"}),
|
||||
eventRocket = Location("Built Rocket", nil, nil, self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items)
|
||||
if not self.world.regions.arthur:canAccess(items) then return false end
|
||||
if _has(items, "teleportKey") then return true end
|
||||
if self.world.regions.outerWall:canAccess(items) then return true end
|
||||
if _has(items, "eventKazuma") and _has(items, "weaponSN") then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
self.locations.jail1.requirements = function(self, items) return _has(items, "teleportKey") end
|
||||
self.locations.momorin.requirements = function(self, items) return _has(items, "letter") and _has(items, "booster") end
|
||||
self.locations.sprinkler.requirements = function(self, items) return _has(items, "mask") end
|
||||
self.locations.megane.requirements = function(self, items) return _has(items, "brokenSprinkler") and _has(items, "mask") end
|
||||
self.locations.itoh.requirements = function(self, items) return _has(items, "letter") end
|
||||
self.locations.plantCeiling.requirements = function(self, items) return _has(items, "flight") or self.region.world:_dboost(items, 'plantation') end
|
||||
self.locations.plantPup.requirements = function(self, items) return _has(items, "eventRocket") end
|
||||
self.locations.curlyShroom.requirements = function(self, items) return _has(items, "eventCurly") and _has(items, "maPignon") end
|
||||
|
||||
self.locations.eventRocket.requirements = function(self, items)
|
||||
return _has(items, "letter") and _has(items, "booster") and _has(items, "controller") and _has(items, "newSprinkler")
|
||||
end
|
||||
self.locations.eventRocket:setItem(self.world.items:getByKey("eventRocket"))
|
||||
end
|
||||
|
||||
local lastCave = Region:extend()
|
||||
function lastCave:new(worldGraph)
|
||||
lastCave.super.new(self, worldGraph, "Last Cave", {"behind a boss", "in Last Cave", "behind the Red Demon"})
|
||||
|
||||
self.locations = {
|
||||
redDemon = Location("Red Demon Boss", "Priso2", "0300", self)
|
||||
}
|
||||
|
||||
self.requirements = function(self, items) return _has(items, "weaponBoss") and _count(items, "booster", 2) and (_has(items, "eventRocket") or (self.world:_dboost(items, 'rocket') and _has(items, "machineGun") and self.world.regions.plantation:canAccess(items))) end
|
||||
end
|
||||
|
||||
local endgame = Region:extend()
|
||||
function endgame:new(worldGraph)
|
||||
endgame.super.new(self, worldGraph, "Sacred Grounds", {"in Hell"})
|
||||
|
||||
self.locations = {
|
||||
hellB1 = Location("Hell B1 Spot", "Hell1", "0401", self),
|
||||
hellB3 = Location("Hell B3 Chest", "Hell3", "0400", self)
|
||||
}
|
||||
|
||||
local function prebuilt(self)
|
||||
local s = self.item.hints[love.math.random(#self.item.hints)]
|
||||
return ("%s."):format(s:sub(1,1):upper() .. s:sub(2)) -- make first character uppercase
|
||||
end
|
||||
self.locations.hellB1.getPrebuiltHint = prebuilt
|
||||
self.locations.hellB3.getPrebuiltHint = prebuilt
|
||||
|
||||
self.requirements = function(self, items) return false end -- just pretend you never get here
|
||||
end
|
||||
|
||||
local hintRegion = Region:extend()
|
||||
function hintRegion:new(worldGraph)
|
||||
hintRegion.super.new(self, worldGraph, "Hints")
|
||||
|
||||
self.locations = {
|
||||
cthulhuEgg = Location("Cthulhu (his Abode)", "Cthu", "0200", self),
|
||||
cthulhuWeed1 = Location("Cthulhu (West Grasstown)", "Weed", "0201", self),
|
||||
cthulhuWeed2 = Location("Cthulhu (East Grasstown)", "Weed", "0205", self),
|
||||
cthulhuPlant = Location("Cthulhu (Plantation)", "Cent", "0310", self),
|
||||
bluebotEgg = Location("Blue Robot (Egg Corridor)", "Eggs", "0200", self),
|
||||
bluebotEgg2 = Location("Blue Robot (Egg Corridor?)", "Eggs2", "0210", self),
|
||||
bluebotMaze = Location("Blue Robot (Labyrinth I #1)", "MazeI", "0500", self),
|
||||
bluebotMaze2 = Location("Blue Robot (Labyrinth I #2)", "MazeI", "0502", self),
|
||||
mrsLittle = Location("Mrs. Little", "Little", "0212", self),
|
||||
malco = Location("MALCO", "Malco", "0306", self),
|
||||
jenka = Location("Jenka", "Jenka1", "0201", self),
|
||||
numahachi1 = Location("Numahachi 1", "Statue", "0301", self),
|
||||
numahachi2 = Location("Numahachi 2", "Statue", "0302", self)
|
||||
}
|
||||
|
||||
-- they'll appear as filled so they get left out of the regular hints
|
||||
self.locations.mrsLittle.item = {}
|
||||
self.locations.malco.item = {}
|
||||
self.locations.jenka.item = {}
|
||||
self.locations.numahachi1.item = {}
|
||||
self.locations.numahachi2.item = {}
|
||||
end
|
||||
|
||||
local worldGraph = Class:extend()
|
||||
|
||||
function worldGraph:new(items)
|
||||
self.items = items
|
||||
self.order = 0
|
||||
self.spawn = ""
|
||||
|
||||
self.seqbreak = false
|
||||
self.dboosts = {
|
||||
cthulhu = {hp = 3, enabled = true},
|
||||
chaco = {hp = 5, enabled = true},
|
||||
paxChaco = {hp = 10, enabled = true},
|
||||
flightlessHut = {hp = 3, enabled = true},
|
||||
-- revChaco = {hp = 3, enabled = true},
|
||||
-- paxRevChaco = {hp = 8, enabled = true},
|
||||
camp = {hp = 9, enabled = true},
|
||||
sisters = {hp = 0, enabled = true},
|
||||
plantation = {hp = 15, enabled = true},
|
||||
rocket = {hp = 3, enabled = true},
|
||||
}
|
||||
|
||||
self.regions = {
|
||||
firstCave = firstCave(self),
|
||||
mimigaVillage = mimigaVillage(self),
|
||||
arthur = arthur(self),
|
||||
eggCorridor1 = eggCorridor1(self),
|
||||
grasstownWest = grasstownWest(self),
|
||||
grasstownEast = grasstownEast(self),
|
||||
upperSandZone = upperSandZone(self),
|
||||
lowerSandZone = lowerSandZone(self),
|
||||
labyrinthW = labyrinthW(self),
|
||||
labyrinthB = labyrinthB(self),
|
||||
boulder = boulder(self),
|
||||
labyrinthM = labyrinthM(self),
|
||||
waterway = waterway(self),
|
||||
eggCorridor2 = eggCorridor2(self),
|
||||
outerWall = outerWall(self),
|
||||
plantation = plantation(self),
|
||||
lastCave = lastCave(self),
|
||||
endgame = endgame(self)
|
||||
}
|
||||
|
||||
self.hintregion = hintRegion(self)
|
||||
|
||||
self.noFallingBlocks = false
|
||||
end
|
||||
|
||||
function worldGraph:_dboost(items, key)
|
||||
return self.seqbreak and self.dboosts[key].enabled and _hp(items) > self.dboosts[key].hp
|
||||
end
|
||||
|
||||
function worldGraph:StartPoint() return self.spawn == "Start Point" end
|
||||
function worldGraph:Arthur() return self.spawn == "Arthur's House" end
|
||||
function worldGraph:Camp() return self.spawn == "Camp" end
|
||||
|
||||
function worldGraph:getSpawnScript()
|
||||
local initialHPCounter = "<FL+4011<FL+4012" -- initializes the HP counter to 3 HP
|
||||
local mapFlagsCMP = "<MP+0040<MP+0043" -- Camp and Labyrinth B
|
||||
local mapFlagsSoftlock = "<MP+0032<MP+0033<MP+0036" -- Small Room (Curly), Jenka's House (both)
|
||||
|
||||
local baseStartScript = initialHPCounter .. mapFlagsCMP .. mapFlagsSoftlock -- TODO: remove mapFlagsSoftlock when playing ER
|
||||
|
||||
if self:StartPoint() then return baseStartScript .. "<FL+6200<EVE0091" end
|
||||
|
||||
local earlyGameFlags = "<FL+0301<FL+0302<FL+1641<FL+1642<FL+0320<FL+0321" -- flags set during first cave in normal gameplay
|
||||
baseStartScript = baseStartScript .. earlyGameFlags
|
||||
|
||||
if self:Arthur() then return baseStartScript .. "<FL+6201<TRA0001:0094:0008:0004" end
|
||||
if self:Camp() then return baseStartScript .. "<FL+6202<TRA0040:0094:0014:0009" end
|
||||
end
|
||||
|
||||
function worldGraph:canBeatGame(items, obj)
|
||||
if obj == "objBadEnd" then
|
||||
|
||||
end
|
||||
local function normalReqs(self, items) return _has(items, "eventRocket") and _has(items, "eventSue") and self.regions.plantation:canAccess(items) end
|
||||
if obj == "objNormalEnd" then return normalReqs(self, items) end
|
||||
|
||||
local function bestReqs(self, items) return normalReqs(self, items) and _has(items, "ironBond") and _count(items, "booster", 2) end
|
||||
if obj == "objBestEnd" then return bestReqs(self, items) end
|
||||
|
||||
local function bossReqs(self, items)
|
||||
if not (bestReqs(self, items) and _has(items, "weaponBoss")) then return false end
|
||||
-- Igor, Balrog 1, Balrog 3, Core, Sisters
|
||||
if not (_has(items, "eventSue") and _has(items, "eventToroko") and _has(items, "eventCore")) then return false end
|
||||
-- Balrog 2, Balfrog
|
||||
if not (_has(items, "rustyKey") and _has(items, "gumKey") and self.regions.grasstownEast:canAccess(items)) then return false end
|
||||
-- Curly, Omega
|
||||
if not (self.regions.upperSandZone:canAccess(items)) then return false end
|
||||
-- Toroko+
|
||||
if not self.regions.lowerSandZone.locations.king:canAccess(items) then return false end
|
||||
-- Puu Black, Monster X
|
||||
if not (self.regions.labyrinthW:canAccess(items) and _has(items, "clinicKey")) then return false end
|
||||
-- Ironhead
|
||||
if not self.regions.waterway:canAccess(items) then return false end
|
||||
-- Ma Pignon
|
||||
if not self.regions.mimigaVillage.locations.maPignon:canAccess(items) then return false end
|
||||
-- Remaining bosses are all covered by the normal requirements
|
||||
return true
|
||||
end
|
||||
if obj == "objAllBosses" then return bossReqs(self, items) end
|
||||
return false -- I don't actually think the check below works as intended, false is sufficient for now since 100% and completable logic should be mutually exclusive anyway
|
||||
--return #items > #self:getLocations() - #self:getHintLocations() - 2 -- removing ANY items (besides in hell) from 100% makes a seed uncompletable
|
||||
end
|
||||
|
||||
function worldGraph:getLocations()
|
||||
local locations = {}
|
||||
for key, region in pairs(self.regions) do
|
||||
for k, location in pairs(region:getLocations()) do
|
||||
table.insert(locations, location)
|
||||
end
|
||||
end
|
||||
return locations
|
||||
end
|
||||
|
||||
function worldGraph:getObjectiveSpot()
|
||||
return {self.regions.firstCave.locations.objective}
|
||||
end
|
||||
|
||||
function worldGraph:getMALCO()
|
||||
return {self.regions.grasstownEast.locations.malco}
|
||||
end
|
||||
|
||||
function worldGraph:getCamp()
|
||||
return {self.regions.labyrinthW.locations.physician, self.regions.labyrinthW.locations.campChest}
|
||||
end
|
||||
|
||||
function worldGraph:getPuppySpots()
|
||||
return {
|
||||
self.regions.upperSandZone.locations.curly,
|
||||
self.regions.upperSandZone.locations.curlyPup,
|
||||
self.regions.upperSandZone.locations.panties,
|
||||
self.regions.upperSandZone.locations.sandCapsule,
|
||||
self.regions.lowerSandZone.locations.chestPup,
|
||||
self.regions.lowerSandZone.locations.darkPup,
|
||||
self.regions.lowerSandZone.locations.runPup,
|
||||
self.regions.lowerSandZone.locations.sleepyPup,
|
||||
self.regions.lowerSandZone.locations.pawCapsule
|
||||
}
|
||||
end
|
||||
|
||||
function worldGraph:getFirstCaveSpots()
|
||||
return {
|
||||
self.regions.firstCave.locations.firstCapsule,
|
||||
self.regions.firstCave.locations.gunsmithChest
|
||||
}
|
||||
end
|
||||
|
||||
function worldGraph:getHellSpots()
|
||||
return self.locationsArray(self.regions.endgame:getLocations())
|
||||
end
|
||||
|
||||
function worldGraph:getEmptyLocations()
|
||||
local locations = {}
|
||||
for key, region in pairs(self.regions) do
|
||||
for k, location in pairs(region:getEmptyLocations()) do
|
||||
table.insert(locations, location)
|
||||
end
|
||||
end
|
||||
return locations
|
||||
end
|
||||
|
||||
function worldGraph:emptyString()
|
||||
local s = "\r\nEmpty locations:"
|
||||
for key, region in pairs(self.regions) do
|
||||
for k, l in pairs(region:getEmptyLocations()) do
|
||||
s = s .. "\r\n" .. l.name
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
function worldGraph:getFilledLocations(realOnly)
|
||||
local locations = {}
|
||||
for key, region in pairs(self.regions) do
|
||||
for k, location in pairs(region:getFilledLocations()) do
|
||||
if not realOnly or not _.find(location.item.attributes,"abstract") then table.insert(locations, location) end
|
||||
end
|
||||
end
|
||||
return locations
|
||||
end
|
||||
|
||||
function worldGraph:getHintLocations()
|
||||
return self.hintregion:getEmptyLocations()
|
||||
end
|
||||
|
||||
function worldGraph:getHintableLocations(obj)
|
||||
local locations = {}
|
||||
for k, location in pairs(_.shuffle(self:getFilledLocations(true))) do
|
||||
if location:getPrebuiltHint() ~= nil then
|
||||
-- do nothing
|
||||
elseif (obj == "objBadEnd" and location.item.name == "Rusty Key") or (obj ~= "objBadEnd" and location.item.name == "ID Card") then
|
||||
table.insert(locations, 1, location) -- put that item on the top to guarantee a hint for it
|
||||
elseif (self:Camp() or self:Arthur()) and location.item.name == "Arthur's Key" then
|
||||
table.insert(locations, 1, location)
|
||||
else
|
||||
table.insert(locations, location)
|
||||
end
|
||||
end
|
||||
return _.slice(locations, 1, #self:getHintLocations())
|
||||
end
|
||||
|
||||
function worldGraph:writeItems(tscFiles)
|
||||
for key, region in pairs(self.regions) do region:writeItems(tscFiles) end
|
||||
self.hintregion:writeItems(tscFiles)
|
||||
end
|
||||
|
||||
function worldGraph:collect(preCollectedItems, locations, singleSphere)
|
||||
local collected = _.clone(preCollectedItems, singleSphere) or {}
|
||||
local availableLocations = locations or self:getFilledLocations()
|
||||
|
||||
local inventory = (singleSphere and preCollectedItems) or collected
|
||||
|
||||
local foundItems = 0
|
||||
repeat
|
||||
foundItems = 0
|
||||
-- Collect accessible items and remove those locations from the table
|
||||
-- (to prevent double-collecting, which is bad for Polar Star/Booster)
|
||||
local j, n = 1, #availableLocations
|
||||
for i = 1, n do
|
||||
local location = availableLocations[i]
|
||||
if location:canAccess(inventory) then
|
||||
table.insert(collected, location.item)
|
||||
foundItems = foundItems + 1
|
||||
availableLocations[i] = nil
|
||||
else
|
||||
if j ~= i then
|
||||
availableLocations[j] = availableLocations[i]
|
||||
availableLocations[i] = nil
|
||||
end
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
until foundItems == 0
|
||||
|
||||
--[[local s = "Collected items: "
|
||||
for k,v in ipairs(collected) do s = s .. v.name .. ", " end
|
||||
logDebug(s)]]
|
||||
return collected, availableLocations
|
||||
end
|
||||
|
||||
function worldGraph.locationsArray(locations)
|
||||
local array = {}
|
||||
for k, v in pairs(locations) do
|
||||
table.insert(array, v)
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
function worldGraph:serialize()
|
||||
local array = {}
|
||||
for k,v in pairs(self.regions) do
|
||||
table.insert(array, v)
|
||||
end
|
||||
table.insert(array, self.hintregion)
|
||||
local function sort(a,b)
|
||||
return a.order < b.order
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
|
||||
for k,r in ipairs(_.sort(array,sort)) do
|
||||
if next(r.locations) then
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Region: " .. r.name)
|
||||
for k2,l in pairs(r.locations) do
|
||||
if l.item ~= nil and not _has({l.item}, "event") then
|
||||
table.insert(lines, "\t " .. l.name .. ": " .. l.item.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
function worldGraph:logLocations()
|
||||
for k,v in ipairs(self:serialize()) do
|
||||
logSpoiler(v)
|
||||
end
|
||||
end
|
||||
|
||||
return worldGraph
|
|
@ -1,69 +0,0 @@
|
|||
local C = Class:extend()
|
||||
|
||||
local ITEM_DATA = require 'database.items'
|
||||
|
||||
function C:new()
|
||||
self._left = {}
|
||||
for k, v in pairs(ITEM_DATA) do
|
||||
local item = _.clone(v)
|
||||
table.insert(self._left, item)
|
||||
end
|
||||
self._placed = {}
|
||||
end
|
||||
|
||||
local function _filterAny(item) return true end
|
||||
|
||||
function C:getAny()
|
||||
return self:_getItem(_filterAny)
|
||||
end
|
||||
|
||||
function C:getAnyByAttribute(attribute)
|
||||
function _filterAnyByAttribute(item, attribute)
|
||||
for k, v in pairs(item.attributes) do
|
||||
if v == attribute then return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
return self:_getItem(_filterAnyByAttribute)
|
||||
end
|
||||
|
||||
function C:placeAny()
|
||||
return self:place(self:getAny())
|
||||
end
|
||||
|
||||
function C:placeAnyByAttribute(attributes)
|
||||
return self:place(self:getAnyByAttribute(attributes))
|
||||
end
|
||||
|
||||
function C:_getItem(filterFn)
|
||||
-- Filter down to only applicable items.
|
||||
local applicable = {}
|
||||
local indexMap = {}
|
||||
for index, item in ipairs(self._left) do
|
||||
if filterFn(item) then
|
||||
table.insert(applicable, item)
|
||||
indexMap[item] = index
|
||||
end
|
||||
end
|
||||
assert(#applicable >= 1, 'No applicable items!')
|
||||
|
||||
-- Select an item.
|
||||
local selected = _.sample(applicable)
|
||||
local index = indexMap[selected]
|
||||
|
||||
return selected, index
|
||||
end
|
||||
|
||||
function C:place(item, index)
|
||||
table.remove(self._left, index)
|
||||
table.insert(self._placed, item)
|
||||
return item
|
||||
end
|
||||
|
||||
function C:getPlacedItems(item)
|
||||
local placed = {table.unpack(self._placed)} -- clone the table
|
||||
if(item) then table.insert(placed, item) end
|
||||
return placed
|
||||
end
|
||||
|
||||
return C
|
|
@ -1,260 +0,0 @@
|
|||
--[[---------------
|
||||
LuaBit v0.4
|
||||
-------------------
|
||||
a bitwise operation lib for lua.
|
||||
|
||||
http://luaforge.net/projects/bit/
|
||||
|
||||
How to use:
|
||||
-------------------
|
||||
bit.bnot(n) -- bitwise not (~n)
|
||||
bit.band(m, n) -- bitwise and (m & n)
|
||||
bit.bor(m, n) -- bitwise or (m | n)
|
||||
bit.bxor(m, n) -- bitwise xor (m ^ n)
|
||||
bit.brshift(n, bits) -- right shift (n >> bits)
|
||||
bit.blshift(n, bits) -- left shift (n << bits)
|
||||
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
|
||||
|
||||
Please note that bit.brshift and bit.blshift only support number within
|
||||
32 bits.
|
||||
|
||||
2 utility functions are provided too:
|
||||
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
|
||||
-- high bits first
|
||||
bit.tonumb(bit_tbl) -- convert a bit table into a number
|
||||
-------------------
|
||||
|
||||
Under the MIT license.
|
||||
|
||||
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
|
||||
--]]---------------
|
||||
|
||||
do
|
||||
|
||||
------------------------
|
||||
-- bit lib implementions
|
||||
|
||||
local function check_int(n)
|
||||
-- checking not float
|
||||
if(n - math.floor(n) > 0) then
|
||||
error("trying to use bitwise operation on non-integer!")
|
||||
end
|
||||
end
|
||||
|
||||
local function to_bits(n)
|
||||
check_int(n)
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
return to_bits(bit.bnot(math.abs(n)) + 1)
|
||||
end
|
||||
-- to bits table
|
||||
local tbl = {}
|
||||
local cnt = 1
|
||||
while (n > 0) do
|
||||
local last = math.fmod(n,2)
|
||||
if(last == 1) then
|
||||
tbl[cnt] = 1
|
||||
else
|
||||
tbl[cnt] = 0
|
||||
end
|
||||
n = (n-last)/2
|
||||
cnt = cnt + 1
|
||||
end
|
||||
|
||||
return tbl
|
||||
end
|
||||
|
||||
local function tbl_to_number(tbl)
|
||||
local n = table.getn(tbl)
|
||||
|
||||
local rslt = 0
|
||||
local power = 1
|
||||
for i = 1, n do
|
||||
rslt = rslt + tbl[i]*power
|
||||
power = power*2
|
||||
end
|
||||
|
||||
return rslt
|
||||
end
|
||||
|
||||
local function expand(tbl_m, tbl_n)
|
||||
local big = {}
|
||||
local small = {}
|
||||
if(table.getn(tbl_m) > table.getn(tbl_n)) then
|
||||
big = tbl_m
|
||||
small = tbl_n
|
||||
else
|
||||
big = tbl_n
|
||||
small = tbl_m
|
||||
end
|
||||
-- expand small
|
||||
for i = table.getn(small) + 1, table.getn(big) do
|
||||
small[i] = 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function bit_or(m, n)
|
||||
local tbl_m = to_bits(m)
|
||||
local tbl_n = to_bits(n)
|
||||
expand(tbl_m, tbl_n)
|
||||
|
||||
local tbl = {}
|
||||
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
|
||||
for i = 1, rslt do
|
||||
if(tbl_m[i]== 0 and tbl_n[i] == 0) then
|
||||
tbl[i] = 0
|
||||
else
|
||||
tbl[i] = 1
|
||||
end
|
||||
end
|
||||
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_and(m, n)
|
||||
local tbl_m = to_bits(m)
|
||||
local tbl_n = to_bits(n)
|
||||
expand(tbl_m, tbl_n)
|
||||
|
||||
local tbl = {}
|
||||
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
|
||||
for i = 1, rslt do
|
||||
if(tbl_m[i]== 0 or tbl_n[i] == 0) then
|
||||
tbl[i] = 0
|
||||
else
|
||||
tbl[i] = 1
|
||||
end
|
||||
end
|
||||
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_not(n)
|
||||
|
||||
local tbl = to_bits(n)
|
||||
local size = math.max(table.getn(tbl), 32)
|
||||
for i = 1, size do
|
||||
if(tbl[i] == 1) then
|
||||
tbl[i] = 0
|
||||
else
|
||||
tbl[i] = 1
|
||||
end
|
||||
end
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_xor(m, n)
|
||||
local tbl_m = to_bits(m)
|
||||
local tbl_n = to_bits(n)
|
||||
expand(tbl_m, tbl_n)
|
||||
|
||||
local tbl = {}
|
||||
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
|
||||
for i = 1, rslt do
|
||||
if(tbl_m[i] ~= tbl_n[i]) then
|
||||
tbl[i] = 1
|
||||
else
|
||||
tbl[i] = 0
|
||||
end
|
||||
end
|
||||
|
||||
--table.foreach(tbl, print)
|
||||
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_rshift(n, bits)
|
||||
check_int(n)
|
||||
|
||||
local high_bit = 0
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
n = bit_not(math.abs(n)) + 1
|
||||
high_bit = 2147483648 -- 0x80000000
|
||||
end
|
||||
|
||||
for i=1, bits do
|
||||
n = n/2
|
||||
n = bit_or(math.floor(n), high_bit)
|
||||
end
|
||||
return math.floor(n)
|
||||
end
|
||||
|
||||
-- logic rightshift assures zero filling shift
|
||||
local function bit_logic_rshift(n, bits)
|
||||
check_int(n)
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
n = bit_not(math.abs(n)) + 1
|
||||
end
|
||||
for i=1, bits do
|
||||
n = n/2
|
||||
end
|
||||
return math.floor(n)
|
||||
end
|
||||
|
||||
local function bit_lshift(n, bits)
|
||||
check_int(n)
|
||||
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
n = bit_not(math.abs(n)) + 1
|
||||
end
|
||||
|
||||
for i=1, bits do
|
||||
n = n*2
|
||||
end
|
||||
return bit_and(n, 4294967295) -- 0xFFFFFFFF
|
||||
end
|
||||
|
||||
local function bit_xor2(m, n)
|
||||
local rhs = bit_or(bit_not(m), bit_not(n))
|
||||
local lhs = bit_or(m, n)
|
||||
local rslt = bit_and(lhs, rhs)
|
||||
return rslt
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- bit lib interface
|
||||
|
||||
bit = {
|
||||
-- bit operations
|
||||
bnot = bit_not,
|
||||
band = bit_and,
|
||||
bor = bit_or,
|
||||
bxor = bit_xor,
|
||||
brshift = bit_rshift,
|
||||
blshift = bit_lshift,
|
||||
bxor2 = bit_xor2,
|
||||
blogic_rshift = bit_logic_rshift,
|
||||
|
||||
-- utility func
|
||||
tobits = to_bits,
|
||||
tonumb = tbl_to_number,
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
for i = 1, 100 do
|
||||
for j = 1, 100 do
|
||||
if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then
|
||||
error("bit.xor failed.")
|
||||
end
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
-- Copyright (c) 2014, rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
|
||||
local Object = {}
|
||||
Object.__index = Object
|
||||
|
||||
function Object:new()
|
||||
end
|
||||
|
||||
function Object:extend()
|
||||
local cls = {}
|
||||
for k, v in pairs(self) do
|
||||
if k:find("__") == 1 then
|
||||
cls[k] = v
|
||||
end
|
||||
end
|
||||
cls.__index = cls
|
||||
cls.super = self
|
||||
setmetatable(cls, self)
|
||||
return cls
|
||||
end
|
||||
|
||||
function Object:implement(...)
|
||||
for _, cls in pairs({...}) do
|
||||
for k, v in pairs(cls) do
|
||||
if self[k] == nil and type(v) == "function" then
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Object:is(T)
|
||||
local mt = getmetatable(self)
|
||||
while mt do
|
||||
if mt == T then
|
||||
return true
|
||||
end
|
||||
mt = getmetatable(mt)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Object:__tostring()
|
||||
return "Object"
|
||||
end
|
||||
|
||||
function Object:__call(...)
|
||||
local obj = setmetatable({}, self)
|
||||
obj:new(...)
|
||||
return obj
|
||||
end
|
||||
|
||||
return Object
|
|
@ -1,602 +0,0 @@
|
|||
--[[--
|
||||
Widget attributes.
|
||||
|
||||
This module defines "attributes" (special fields) that are
|
||||
recognized by all widgets. Their interpretation may vary
|
||||
depending on the `type` of widget. Some widget types may also
|
||||
recognize additional attributes.
|
||||
|
||||
Setting attributes can have side effects. For example, setting
|
||||
`height` or `width` causes the parent widget and its descendants
|
||||
to recalculate their size and position.
|
||||
--]]--
|
||||
|
||||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Shortcut = require(ROOT .. 'shortcut')
|
||||
|
||||
local Attribute = {}
|
||||
|
||||
local function cascade (widget, attribute)
|
||||
local value = rawget(widget, 'attributes')[attribute]
|
||||
if value ~= nil then return value end
|
||||
local parent = rawget(widget, 'parent')
|
||||
return parent and parent[attribute]
|
||||
end
|
||||
|
||||
--[[--
|
||||
Type of widget.
|
||||
|
||||
Should contain a string identifying the widget's type.
|
||||
After the layout is built, may be replaced by an array
|
||||
of strings identifying multiple types. This is used
|
||||
internally by some widgets to provide information about
|
||||
the widget's state to the theme (themes describe the
|
||||
appearance of widgets according to their type).
|
||||
|
||||
If a type is registered with the widget's layout, the registered
|
||||
type initializer function will run once when the widget is constructed.
|
||||
|
||||
@see Widget.register
|
||||
|
||||
@attrib type
|
||||
--]]--
|
||||
Attribute.type = {}
|
||||
|
||||
function Attribute.type.set (widget, value)
|
||||
local oldType = widget.attributes.type
|
||||
|
||||
widget.attributes.type = value
|
||||
|
||||
if value and not widget.hasType then
|
||||
widget.hasType = true
|
||||
local Widget = require(ROOT .. 'widget')
|
||||
local decorate = Widget.typeDecorators[value]
|
||||
|
||||
if decorate then
|
||||
decorate(widget)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[--
|
||||
Widget identifier.
|
||||
|
||||
Should contain a unique string identifying the widget, if present.
|
||||
|
||||
A reference to the widget will be stored in the associated layout
|
||||
in a property having the same name as the widget's id.
|
||||
|
||||
Setting this attribute re-registers the widget with its layout.
|
||||
|
||||
@attrib id
|
||||
--]]--
|
||||
Attribute.id = {}
|
||||
|
||||
function Attribute.id.set (widget, value)
|
||||
local layout = widget.layout.master or widget.layout
|
||||
local oldValue = widget.attributes.id
|
||||
|
||||
if oldValue then
|
||||
layout[oldValue] = nil
|
||||
end
|
||||
|
||||
if value then
|
||||
layout[value] = widget
|
||||
end
|
||||
|
||||
widget.attributes.id = value
|
||||
end
|
||||
|
||||
--[[--
|
||||
Widget value.
|
||||
|
||||
Some widget types expect the value to be of a specific type and
|
||||
within a specific range. For example, `slider` and `progress`
|
||||
widgets expect a normalized number, `text` widgets expect
|
||||
a string, and `check` and `radio` widgets expect a boolean.
|
||||
|
||||
Setting this attribute bubbles the `Change` event.
|
||||
|
||||
@attrib value
|
||||
--]]--
|
||||
Attribute.value = {}
|
||||
|
||||
function Attribute.value.set (widget, value)
|
||||
local oldValue = widget.value
|
||||
widget.attributes.value = value
|
||||
widget:bubbleEvent('Change', { value = value, oldValue = oldValue })
|
||||
end
|
||||
|
||||
--[[--
|
||||
Solidity.
|
||||
|
||||
Should true or false.
|
||||
|
||||
@attrib icon
|
||||
--]]--
|
||||
Attribute.solid = {}
|
||||
|
||||
function Attribute.solid.set (widget, value)
|
||||
widget.attributes.solid = value
|
||||
end
|
||||
|
||||
Attribute.solid.get = cascade
|
||||
|
||||
--[[--
|
||||
Context menu.
|
||||
|
||||
- This attribute cascades.
|
||||
|
||||
@attrib context
|
||||
--]]--
|
||||
Attribute.context = {}
|
||||
|
||||
function Attribute.context.set (widget, value)
|
||||
widget.attributes.context = value
|
||||
if not value then return end
|
||||
value.isContextMenu = true
|
||||
widget.layout:createWidget { type = 'menu', value }
|
||||
end
|
||||
|
||||
Attribute.context.get = cascade
|
||||
|
||||
--[[--
|
||||
Widget style.
|
||||
|
||||
Should contain a string or array of strings identifying
|
||||
style rules to be applied to the widget. When resolving
|
||||
any attribute with a `nil` value, these style rules are
|
||||
searched for a corresponding attribute.
|
||||
|
||||
Setting this attribute resets the `Font` and `Text` object
|
||||
associated with this widget.
|
||||
|
||||
Setting this attribute recalculates the size and position
|
||||
of the parent widget and its descendants.
|
||||
|
||||
@attrib style
|
||||
--]]--
|
||||
Attribute.style = {}
|
||||
|
||||
function Attribute.style.set (widget, value)
|
||||
widget.attributes.style = value
|
||||
widget.fontData = nil
|
||||
widget.textData = nil
|
||||
widget.reshape(widget.parent or widget)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Status message.
|
||||
|
||||
Should contain a string with a short message describing the
|
||||
purpose or state of the widget.
|
||||
|
||||
This message will appear in the last created `status` widget
|
||||
in the same layout, or in the master layout if one exists.
|
||||
|
||||
- This attribute cascades.
|
||||
|
||||
@attrib status
|
||||
--]]--
|
||||
Attribute.status = {}
|
||||
|
||||
Attribute.status.get = cascade
|
||||
|
||||
--[[--
|
||||
Scroll ability.
|
||||
|
||||
Should contain `true` or `false` (or `nil`).
|
||||
|
||||
If set to `true`, moving the scroll wheel over the widget will adjust
|
||||
its scroll position when the widget's contents overflow its boundary.
|
||||
|
||||
@attrib scroll
|
||||
--]]--
|
||||
Attribute.scroll = {}
|
||||
|
||||
--[[--
|
||||
Keyboard Attributes.
|
||||
|
||||
@section keyboard
|
||||
--]]--
|
||||
|
||||
--[[--
|
||||
Focusable.
|
||||
|
||||
Should contain `true` if the widget can be focused by pressing the tab key.
|
||||
|
||||
@attrib focusable
|
||||
--]]--
|
||||
Attribute.focusable = {}
|
||||
|
||||
--[[--
|
||||
Keyboard shortcut.
|
||||
|
||||
Should contain a string representing a key and optional modifiers,
|
||||
separated by dashes; for example `'ctrl-c'` or `'alt-shift-escape'`.
|
||||
|
||||
Pressing this key combination bubbles a `Press` event on the widget,
|
||||
as if it had been pressed with a mouse or touch interface.
|
||||
|
||||
Setting this attribute re-registers the widget with its layout.
|
||||
|
||||
@attrib shortcut
|
||||
--]]--
|
||||
Attribute.shortcut = {}
|
||||
|
||||
local function setShortcut (layout, shortcut, value)
|
||||
local mainKey, modifierFlags = Shortcut.parseKeyCombo(shortcut)
|
||||
if mainKey then
|
||||
layout.shortcuts[modifierFlags][mainKey] = value
|
||||
end
|
||||
end
|
||||
|
||||
function Attribute.shortcut.set (widget, value)
|
||||
local layout = widget.layout.master or widget.layout
|
||||
local oldValue = widget.attributes.shortcut
|
||||
|
||||
if oldValue then
|
||||
if type(oldValue) == 'table' then
|
||||
for _, v in ipairs(oldValue) do
|
||||
setShortcut(layout, v, nil)
|
||||
end
|
||||
else
|
||||
setShortcut(layout, oldValue, nil)
|
||||
end
|
||||
end
|
||||
|
||||
if value then
|
||||
if type(value) == 'table' then
|
||||
for _, v in ipairs(value) do
|
||||
setShortcut(layout, v, widget)
|
||||
end
|
||||
else
|
||||
setShortcut(layout, value, widget)
|
||||
end
|
||||
end
|
||||
|
||||
widget.attributes.shortcut = value
|
||||
end
|
||||
|
||||
--[[--
|
||||
Size Attributes.
|
||||
|
||||
Setting these attributes recalculates the size and position
|
||||
of the parent widget and its descendants.
|
||||
|
||||
@section size
|
||||
--]]--
|
||||
|
||||
--[[--
|
||||
Flow axis.
|
||||
|
||||
Should equal either `'x'` or `'y'`. Defaults to `'y'`.
|
||||
|
||||
This attribute determines the placement and default dimensions
|
||||
of any child widgets.
|
||||
|
||||
When flow is `'x'`, the `height` of child widgets defaults
|
||||
to this widget's height, and each child is placed to the
|
||||
right of the previous child. When flow is `'y'`, the `width`
|
||||
of child widgets defaults to this widget's width, and each
|
||||
child is placed below the previous child.
|
||||
|
||||
Setting this attribute resets the `Text` object associated
|
||||
with this widget.
|
||||
|
||||
@attrib flow
|
||||
--]]--
|
||||
Attribute.flow = {}
|
||||
|
||||
function Attribute.flow.set (widget, value)
|
||||
widget.attributes.flow = value
|
||||
widget.textData = nil
|
||||
widget.reshape(widget.parent or widget)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Width.
|
||||
|
||||
This attribute may not always hold a numeric value.
|
||||
To get the calculated width, use `Widget:getWidth`.
|
||||
|
||||
Setting this attribute when the `wrap` attribute is
|
||||
also present resets the `Text` object associated
|
||||
with this widget.
|
||||
|
||||
@attrib width
|
||||
--]]--
|
||||
Attribute.width = {}
|
||||
|
||||
function Attribute.width.set (widget, value)
|
||||
if value ~= 'auto' then
|
||||
value = value and math.max(value, widget.minwidth or 0)
|
||||
end
|
||||
widget.attributes.width = value
|
||||
if widget.wrap then
|
||||
widget.textData = nil
|
||||
end
|
||||
widget.reshape(widget.parent or widget)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Height.
|
||||
|
||||
This attribute may not always hold a numeric value.
|
||||
To get the calculated height, use `Widget:getHeight`.
|
||||
|
||||
@attrib height
|
||||
--]]--
|
||||
Attribute.height = {}
|
||||
|
||||
function Attribute.height.set (widget, value)
|
||||
if value ~= 'auto' then
|
||||
value = value and math.max(value, widget.minheight or 0)
|
||||
end
|
||||
widget.attributes.height = value
|
||||
widget.reshape(widget.parent or widget)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Minimum width.
|
||||
|
||||
@attrib minwidth
|
||||
--]]--
|
||||
Attribute.minwidth = {}
|
||||
|
||||
function Attribute.minwidth.set (widget, value)
|
||||
local attributes = widget.attributes
|
||||
attributes.minwidth = value
|
||||
if type(value) == 'number' then
|
||||
local current = attributes.width
|
||||
if type(current) == 'number' then
|
||||
attributes.width = math.max(current, value)
|
||||
end
|
||||
end
|
||||
widget.reshape(widget.parent or widget)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Minimum height.
|
||||
|
||||
@attrib minheight
|
||||
--]]--
|
||||
Attribute.minheight = {}
|
||||
|
||||
function Attribute.minheight.set (widget, value)
|
||||
local attributes = widget.attributes
|
||||
attributes.minheight = value
|
||||
if type(value) == 'number' then
|
||||
local current = attributes.height
|
||||
if type(current) == 'number' then
|
||||
attributes.height = math.max(current, value)
|
||||
end
|
||||
end
|
||||
widget.reshape(widget.parent or widget)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Font Attributes.
|
||||
|
||||
Setting these attributes resets the Font and Text
|
||||
objects associated with the widget.
|
||||
|
||||
@section font
|
||||
--]]--
|
||||
|
||||
--[[--
|
||||
Font path.
|
||||
|
||||
Should contain a path to a TrueType font to use for displaying
|
||||
this widget's `text`.
|
||||
|
||||
- This attribute cascades.
|
||||
|
||||
@attrib font
|
||||
--]]--
|
||||
Attribute.font = {}
|
||||
|
||||
local function resetFont (widget)
|
||||
rawset(widget, 'fontData', nil)
|
||||
rawset(widget, 'textData', nil)
|
||||
for _, child in ipairs(widget) do
|
||||
resetFont(child)
|
||||
end
|
||||
local items = widget.items
|
||||
if items then
|
||||
for _, child in ipairs(items) do
|
||||
resetFont(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Attribute.font.set (widget, value)
|
||||
widget.attributes.font = value
|
||||
resetFont(widget)
|
||||
end
|
||||
|
||||
Attribute.font.get = cascade
|
||||
|
||||
--[[--
|
||||
Font size.
|
||||
|
||||
Should contain a number representing the size of the font, in points.
|
||||
Defaults to 12.
|
||||
|
||||
- This attribute cascades.
|
||||
|
||||
@attrib size
|
||||
--]]--
|
||||
Attribute.size = {}
|
||||
|
||||
function Attribute.size.set (widget, value)
|
||||
widget.attributes.size = value
|
||||
widget.fontData = nil
|
||||
widget.textData = nil
|
||||
end
|
||||
|
||||
Attribute.size.get = cascade
|
||||
|
||||
--[[--
|
||||
Text Attributes.
|
||||
|
||||
Setting these attributes resets the Text object
|
||||
associated with the widget.
|
||||
|
||||
@section text
|
||||
--]]--
|
||||
|
||||
--[[--
|
||||
Text to display.
|
||||
|
||||
@attrib text
|
||||
--]]--
|
||||
Attribute.text = {}
|
||||
|
||||
function Attribute.text.set (widget, value)
|
||||
widget.attributes.text = value
|
||||
widget.textData = nil
|
||||
end
|
||||
|
||||
--[[--
|
||||
Text color.
|
||||
|
||||
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
|
||||
|
||||
- This attribute cascades.
|
||||
|
||||
@attrib color
|
||||
--]]--
|
||||
Attribute.color = {}
|
||||
|
||||
function Attribute.color.set (widget, value)
|
||||
widget.attributes.color = value
|
||||
widget.textData = nil
|
||||
end
|
||||
|
||||
Attribute.color.get = cascade
|
||||
|
||||
--[[--
|
||||
Text and icon alignment.
|
||||
|
||||
Should contain a string defining vertical and horizontal alignment.
|
||||
Vertical alignment is defined by either 'top', 'middle', or 'bottom',
|
||||
and horizontal alignment is defined by either 'left', 'center', or 'right'.
|
||||
|
||||
For example, `align = 'top left'`
|
||||
|
||||
- This attribute cascades.
|
||||
|
||||
@attrib align
|
||||
--]]--
|
||||
Attribute.align = {}
|
||||
|
||||
function Attribute.align.set (widget, value)
|
||||
widget.attributes.align = value
|
||||
widget.textData = nil
|
||||
end
|
||||
|
||||
Attribute.align.get = cascade
|
||||
|
||||
--[[--
|
||||
Wrap text onto multiple lines.
|
||||
|
||||
Should contain `true` for multiline text, or `false` or `nil`
|
||||
for a single line. Even text containing line breaks will display
|
||||
as a single line when this attribute is not set to `true`.
|
||||
|
||||
- This attribute cascades.
|
||||
|
||||
@attrib wrap
|
||||
--]]--
|
||||
Attribute.wrap = {}
|
||||
|
||||
function Attribute.wrap.set (widget, value)
|
||||
widget.attributes.wrap = value
|
||||
widget.textData = nil
|
||||
end
|
||||
|
||||
Attribute.wrap.get = cascade
|
||||
|
||||
--[[--
|
||||
Visual Attributes.
|
||||
|
||||
@section visual
|
||||
--]]--
|
||||
|
||||
--[[--
|
||||
Background color.
|
||||
|
||||
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
|
||||
|
||||
@attrib background
|
||||
--]]--
|
||||
Attribute.background = {}
|
||||
|
||||
--[[--
|
||||
Outline color.
|
||||
|
||||
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
|
||||
|
||||
@attrib outline
|
||||
--]]--
|
||||
Attribute.outline = {}
|
||||
|
||||
--[[--
|
||||
Slice image.
|
||||
|
||||
Should contain a path to an image with "slices" to display for this widget.
|
||||
|
||||
@attrib slices
|
||||
--]]--
|
||||
Attribute.slices = {}
|
||||
|
||||
--[[--
|
||||
Margin size.
|
||||
|
||||
The margin area occupies space outside of the `outline` and `slices`.
|
||||
|
||||
@attrib margin
|
||||
--]]--
|
||||
Attribute.margin = {}
|
||||
|
||||
function Attribute.margin.set (widget, value)
|
||||
widget.attributes.margin = value
|
||||
widget.textData = nil
|
||||
widget:reshape()
|
||||
end
|
||||
|
||||
--[[--
|
||||
Padding size.
|
||||
|
||||
The padding area occupies space inside the `outline` and `slices`,
|
||||
and outside the space where the `icon` and `text` and any
|
||||
child widgets appear.
|
||||
|
||||
@attrib padding
|
||||
--]]--
|
||||
Attribute.padding = {}
|
||||
|
||||
function Attribute.padding.set (widget, value)
|
||||
widget.attributes.padding = value
|
||||
widget.textData = nil
|
||||
widget:reshape()
|
||||
end
|
||||
|
||||
--[[--
|
||||
Icon path.
|
||||
|
||||
Should contain a path to an image file.
|
||||
|
||||
@attrib icon
|
||||
--]]--
|
||||
Attribute.icon = {}
|
||||
|
||||
function Attribute.icon.set (widget, value)
|
||||
widget.attributes.icon = value
|
||||
widget.textData = nil
|
||||
end
|
||||
|
||||
|
||||
return Attribute
|
|
@ -1,27 +0,0 @@
|
|||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
local Backend
|
||||
|
||||
if _G.love and (_G.love._version_major or _G.love._version_minor) then
|
||||
Backend = require(ROOT .. 'backend.love')
|
||||
else
|
||||
Backend = require(ROOT .. 'backend.ffisdl')
|
||||
end
|
||||
|
||||
Backend.intersectScissor = Backend.intersectScissor or function (x, y, w, h)
|
||||
local sx, sy, sw, sh = Backend.getScissor()
|
||||
if not sx then
|
||||
return Backend.setScissor(x, y, w, h)
|
||||
end
|
||||
local x1 = math.max(sx, x)
|
||||
local y1 = math.max(sy, y)
|
||||
local x2 = math.min(sx + sw, x + w)
|
||||
local y2 = math.min(sy + sh, y + h)
|
||||
if x2 > x1 and y2 > y1 then
|
||||
Backend.setScissor(x1, y1, x2 - x1, y2 - y1)
|
||||
else
|
||||
-- HACK
|
||||
Backend.setScissor(-100, -100, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
return Backend
|
|
@ -1,540 +0,0 @@
|
|||
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 Text = require((...) .. '.text')
|
||||
|
||||
local IntOut = ffi.typeof 'int[1]'
|
||||
|
||||
local stack = {}
|
||||
|
||||
-- create window and renderer
|
||||
sdl.enableScreenSaver()
|
||||
|
||||
local window = sdl.createWindow('',
|
||||
sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, 800, 600,
|
||||
sdl.WINDOW_SHOWN + sdl.WINDOW_RESIZABLE)
|
||||
|
||||
if window == nil then
|
||||
error(ffi.string(sdl.getError()))
|
||||
end
|
||||
|
||||
ffi.gc(window, sdl.destroyWindow)
|
||||
|
||||
local renderer = sdl.createRenderer(window, -1,
|
||||
sdl.RENDERER_ACCELERATED + sdl.RENDERER_PRESENTVSYNC)
|
||||
|
||||
if renderer == nil then
|
||||
error(ffi.string(sdl.getError()))
|
||||
end
|
||||
|
||||
ffi.gc(renderer, sdl.destroyRenderer)
|
||||
|
||||
sdl.setRenderDrawBlendMode(renderer, sdl.BLENDMODE_BLEND)
|
||||
|
||||
local Backend = {}
|
||||
|
||||
Backend.sdl = sdl
|
||||
|
||||
Backend.isMac = function ()
|
||||
return sdl.getPlatform() == 'Mac OS X'
|
||||
end
|
||||
|
||||
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,
|
||||
wheelmoved = function () end,
|
||||
}
|
||||
|
||||
Backend.run = function ()
|
||||
local event = sdl.Event()
|
||||
local tickInterval = 16 -- ~60 fps (with room)
|
||||
local nextTick = 0
|
||||
local sdl = sdl
|
||||
|
||||
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
|
||||
local window = event.window
|
||||
callback.resize(window.data1, window.data2)
|
||||
elseif event.type == sdl.MOUSEBUTTONDOWN then
|
||||
local button = event.button
|
||||
callback.mousepressed(button.x, button.y, button.button)
|
||||
elseif event.type == sdl.MOUSEBUTTONUP then
|
||||
local button = event.button
|
||||
callback.mousereleased(button.x, button.y, button.button)
|
||||
elseif event.type == sdl.MOUSEMOTION then
|
||||
local motion = event.motion
|
||||
callback.mousemoved(motion.x, motion.y)
|
||||
elseif event.type == sdl.KEYDOWN then
|
||||
local key = Keyboard.stringByKeycode[event.key.keysym.sym] or 'unknown'
|
||||
local scanCode = Keyboard.stringByScancode[event.key.keysym.scancode] or 'unknown'
|
||||
callback.keypressed(key, scanCode, event.key['repeat'])
|
||||
elseif event.type == sdl.KEYUP then
|
||||
local key = Keyboard.stringByKeycode[event.key.keysym.sym] or 'unknown'
|
||||
local scanCode = Keyboard.stringByScancode[event.key.keysym.scancode] or 'unknown'
|
||||
callback.keyreleased(key, scanCode, event.key['repeat'])
|
||||
elseif event.type == sdl.TEXTINPUT then
|
||||
callback.textinput(ffi.string(event.text.text))
|
||||
elseif event.type == sdl.MOUSEWHEEL then
|
||||
local wheel = event.wheel
|
||||
callback.wheelmoved(wheel.x, wheel.y)
|
||||
end
|
||||
end
|
||||
|
||||
sdl.renderSetClipRect(renderer, nil)
|
||||
sdl.setRenderDrawColor(renderer, 0, 0, 0, 255)
|
||||
sdl.renderClear(renderer)
|
||||
callback.draw()
|
||||
|
||||
local now = sdl.getTicks()
|
||||
if nextTick > now then
|
||||
sdl.delay(nextTick - now)
|
||||
end
|
||||
nextTick = now + tickInterval
|
||||
|
||||
sdl.renderPresent(renderer)
|
||||
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.Text = function (...)
|
||||
return Text(renderer, ...)
|
||||
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)
|
||||
if drawable.draw then
|
||||
return drawable:draw(x, y, sx, sy)
|
||||
end
|
||||
|
||||
if drawable.sdlTexture == nil
|
||||
or drawable.sdlRenderer == nil
|
||||
or drawable.getWidth == nil
|
||||
or drawable.getHeight == nil
|
||||
then return
|
||||
end
|
||||
|
||||
local w = drawable:getWidth() * (sx or 1)
|
||||
local h = drawable:getHeight() * (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(drawable.sdlRenderer, -1, -1)
|
||||
|
||||
-- Draw the image.
|
||||
sdl.renderCopy(drawable.sdlRenderer, drawable.sdlTexture,
|
||||
nil, sdl.Rect(x, y, w, h))
|
||||
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()
|
||||
|
||||
local lastColor
|
||||
|
||||
-- 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(lastColor or { 0, 0, 0, 255 })
|
||||
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.getClipboardText = function ()
|
||||
return ffi.string(sdl.getClipboardText())
|
||||
end
|
||||
|
||||
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)
|
||||
return ffi.gc(sdl.createSystemCursor(id), sdl.freeCursor)
|
||||
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
|
||||
|
||||
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)
|
||||
-- y = y and Backend.getWindowHeight() - (y + h)
|
||||
lastScissor = x and sdl.Rect(x, y, w, h)
|
||||
sdl.renderSetClipRect(renderer, lastScissor)
|
||||
end
|
||||
|
||||
Backend.getScissor = function ()
|
||||
if lastScissor ~= nil then
|
||||
local x, y = lastScissor.x, lastScissor.y
|
||||
local w, h = lastScissor.w, lastScissor.h
|
||||
-- y = y and Backend.getWindowHeight() - (y + h)
|
||||
return x, y, w, h
|
||||
end
|
||||
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
|
||||
|
||||
Backend.pop = function ()
|
||||
local history = stack[#stack]
|
||||
lastColor = history.color or { 0, 0, 0, 255 }
|
||||
lastScissor = history.scissor
|
||||
|
||||
sdl.setRenderDrawColor(renderer,
|
||||
lastColor[1], lastColor[2], lastColor[3], lastColor[4] or 255)
|
||||
sdl.renderSetClipRect(renderer, lastScissor) -- 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
|
||||
|
||||
local buttonIds = {
|
||||
[sdl.BUTTON_LEFT] = 'left',
|
||||
[sdl.BUTTON_MIDDLE] = 'middle',
|
||||
[sdl.BUTTON_RIGHT] = 'right',
|
||||
-- [sdl.BUTTON_X1] = 'x1',
|
||||
-- [sdl.BUTTON_X2] = 'x2',
|
||||
}
|
||||
|
||||
local function getMouseButtonId (value)
|
||||
return value and buttonIds[value] or value
|
||||
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, scanCode, isRepeat)
|
||||
return input:handleKeyPress(layout, key, scanCode, Backend.getMousePosition())
|
||||
end)
|
||||
hook(layout, 'keyreleased', function (key, scanCode)
|
||||
return input:handleKeyRelease(layout, key, scanCode, Backend.getMousePosition())
|
||||
end)
|
||||
hook(layout, 'textinput', function (text)
|
||||
return input:handleTextInput(layout, text, Backend.getMousePosition())
|
||||
end)
|
||||
hook(layout, 'wheelmoved', function (x, y)
|
||||
return input:handleWheelMove(layout, x, y)
|
||||
end)
|
||||
end
|
||||
|
||||
function Backend.getWindowMaximized ()
|
||||
local flags = sdl.getWindowFlags(window)
|
||||
return bit.band(flags, sdl.WINDOW_MAXIMIZED) ~= 0
|
||||
end
|
||||
|
||||
function Backend.setWindowMaximized (maximized)
|
||||
if maximized then
|
||||
sdl.maximizeWindow(window)
|
||||
else
|
||||
sdl.restoreWindow(window)
|
||||
end
|
||||
end
|
||||
|
||||
function Backend.getWindowMinimized ()
|
||||
local flags = sdl.getWindowFlags(window)
|
||||
return bit.band(flags, sdl.WINDOW_MINIMIZED) ~= 0
|
||||
end
|
||||
|
||||
function Backend.setWindowMinimized (minimized)
|
||||
if minimized then
|
||||
sdl.minimizeWindow(window)
|
||||
else
|
||||
sdl.restoreWindow(window)
|
||||
end
|
||||
end
|
||||
|
||||
function Backend.getWindowBorderless ()
|
||||
local flags = sdl.getWindowFlags(window)
|
||||
return bit.band(flags, sdl.WINDOW_BORDERLESS) ~= 0
|
||||
end
|
||||
|
||||
function Backend.setWindowBorderless (borderless)
|
||||
return sdl.setWindowBordered(window, not borderless)
|
||||
end
|
||||
|
||||
function Backend.getWindowFullscreen ()
|
||||
local flags = sdl.getWindowFlags(window)
|
||||
return bit.band(flags, sdl.WINDOW_FULLSCREEN) ~= 0
|
||||
end
|
||||
|
||||
function Backend.setWindowFullscreen (fullscreen)
|
||||
return sdl.setWindowFullscreen(window, not not fullscreen)
|
||||
end
|
||||
|
||||
function Backend.getWindowGrab ()
|
||||
return sdl.getWindowGrab(window)
|
||||
end
|
||||
|
||||
function Backend.setWindowGrab (grab)
|
||||
return sdl.setWindowGrab(window, not not grab)
|
||||
end
|
||||
|
||||
local SDL2_image = ffi.load 'SDL2_image'
|
||||
|
||||
function Backend.setWindowIcon (icon)
|
||||
-- XXX: is it safe to free this?
|
||||
local surface = ffi.gc(SDL2_image.IMG_Load(icon), sdl.freeSurface)
|
||||
|
||||
if surface == nil then
|
||||
error(ffi.string(sdl.getError()))
|
||||
end
|
||||
|
||||
sdl.setWindowIcon(window, surface)
|
||||
end
|
||||
|
||||
function Backend.getWindowMaxwidth ()
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMaximumSize(window, w, h)
|
||||
return w[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowMaxwidth (maxwidth)
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMaximumSize(window, w, h)
|
||||
sdl.setWindowMaximumSize(window, maxwidth, h[0] or 16384)
|
||||
end
|
||||
|
||||
function Backend.getWindowMaxheight ()
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMaximumSize(window, w, h)
|
||||
return h[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowMaxheight (maxheight)
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMaximumSize(window, w, h)
|
||||
sdl.setWindowMaximumSize(window, w[0] or 16384, maxheight)
|
||||
end
|
||||
|
||||
function Backend.getWindowMinwidth ()
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMinimumSize(window, w, h)
|
||||
return w[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowMinwidth (minwidth)
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMinimumSize(window, w, h)
|
||||
sdl.setWindowMinimumSize(window, minwidth, h[0] or 0)
|
||||
end
|
||||
|
||||
function Backend.getWindowMinheight ()
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMinimumSize(window, w, h)
|
||||
return h[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowMinheight (minheight)
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowMinimumSize(window, w, h)
|
||||
sdl.setWindowMinimumSize(window, w[0] or 0, minheight)
|
||||
end
|
||||
|
||||
function Backend.getWindowTop ()
|
||||
local x, y = IntOut(), IntOut()
|
||||
sdl.getWindowPosition(window, x, y)
|
||||
return y[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowTop (top)
|
||||
local x, y = IntOut(), IntOut()
|
||||
sdl.getWindowPosition(window, x, y)
|
||||
sdl.setWindowPosition(window, x[0] or 0, top)
|
||||
end
|
||||
|
||||
function Backend.getWindowLeft ()
|
||||
local x, y = IntOut(), IntOut()
|
||||
sdl.getWindowPosition(window, x, y)
|
||||
return x[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowLeft (left)
|
||||
local x, y = IntOut(), IntOut()
|
||||
sdl.getWindowPosition(window, x, y)
|
||||
sdl.setWindowPosition(window, left, y[0] or 0)
|
||||
end
|
||||
|
||||
function Backend.getWindowWidth ()
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowSize(window, w, h)
|
||||
return w[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowWidth (width)
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowSize(window, w, h)
|
||||
sdl.setWindowSize(window, width, h[0] or 600)
|
||||
end
|
||||
|
||||
function Backend.getWindowHeight ()
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowSize(window, w, h)
|
||||
return h[0]
|
||||
end
|
||||
|
||||
function Backend.setWindowHeight (height)
|
||||
local w, h = IntOut(), IntOut()
|
||||
sdl.getWindowSize(window, w, h)
|
||||
sdl.setWindowSize(window, w[0] or 800, height)
|
||||
end
|
||||
|
||||
function Backend.getWindowTitle (title)
|
||||
return sdl.getWindowTitle(window)
|
||||
end
|
||||
|
||||
function Backend.setWindowTitle (title)
|
||||
sdl.setWindowTitle(window, title)
|
||||
end
|
||||
|
||||
|
||||
|
||||
return Backend
|
|
@ -1,249 +0,0 @@
|
|||
local REL = (...):gsub('[^.]*$', '')
|
||||
local APP_ROOT = rawget(_G, 'LUIGI_APP_ROOT') or ''
|
||||
|
||||
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)
|
||||
if not size then
|
||||
size = 12
|
||||
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(APP_ROOT .. path, size)
|
||||
|
||||
if font == nil then
|
||||
error(ffi.string(sdl.getError()))
|
||||
end
|
||||
|
||||
fontCache[key] = font
|
||||
end
|
||||
|
||||
self.sdlFont = fontCache[key]
|
||||
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
|
||||
|
||||
return Font
|
|
@ -1,46 +0,0 @@
|
|||
local REL = (...):gsub('[^.]*$', '')
|
||||
local APP_ROOT = rawget(_G, 'LUIGI_APP_ROOT') or ''
|
||||
|
||||
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 = ffi.gc(
|
||||
SDL2_image.IMG_Load(APP_ROOT .. path),
|
||||
sdl.freeSurface)
|
||||
|
||||
if self.sdlSurface == nil then
|
||||
error(ffi.string(sdl.getError()))
|
||||
end
|
||||
|
||||
self.sdlTexture = ffi.gc(
|
||||
sdl.createTextureFromSurface(renderer, self.sdlSurface),
|
||||
sdl.destroyTexture)
|
||||
|
||||
if self.sdlTexture == nil then
|
||||
error(ffi.string(sdl.getError()))
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
return Image
|
|
@ -1,494 +0,0 @@
|
|||
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
|
|
@ -1,66 +0,0 @@
|
|||
local REL = (...):gsub('[^.]*$', '')
|
||||
|
||||
local ffi = require 'ffi'
|
||||
local sdl = require(REL .. '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
|
||||
error(ffi.string(sdl.getError()))
|
||||
end
|
||||
|
||||
return sdl
|
|
@ -1,36 +0,0 @@
|
|||
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.
|
|
@ -1,32 +0,0 @@
|
|||
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.
|
||||
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
-- 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
|
|
@ -1,79 +0,0 @@
|
|||
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
|
||||
|
||||
function SpriteBatch:clear ()
|
||||
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
|
|
@ -1,104 +0,0 @@
|
|||
local ROOT = (...):gsub('[^.]*.[^.]*.[^.]*$', '')
|
||||
local REL = (...):gsub('[^.]*$', '')
|
||||
|
||||
local ffi = require 'ffi'
|
||||
local sdl = require(REL .. 'sdl')
|
||||
local Font = require(REL .. 'font')
|
||||
local ttf = Font.SDL2_ttf
|
||||
|
||||
local Multiline = require(ROOT .. 'multiline')
|
||||
|
||||
local Text = setmetatable({}, { __call = function (self, ...)
|
||||
local object = setmetatable({}, { __index = self })
|
||||
return object, self.constructor(object, ...)
|
||||
end })
|
||||
|
||||
local function renderSingle (self, font, text, color)
|
||||
local alphaMod = color and color[4]
|
||||
color = sdl.Color(color or 0)
|
||||
local surface = ffi.gc(
|
||||
ttf.TTF_RenderUTF8_Blended(font.sdlFont, text, color),
|
||||
sdl.freeSurface)
|
||||
self.sdlSurface = surface
|
||||
self.sdlTexture = ffi.gc(
|
||||
sdl.createTextureFromSurface(self.sdlRenderer, surface),
|
||||
sdl.destroyTexture)
|
||||
if alphaMod then
|
||||
sdl.setTextureAlphaMod(self.sdlTexture, alphaMod)
|
||||
end
|
||||
self.width, self.height = surface.w, surface.h
|
||||
end
|
||||
|
||||
local function renderMulti (self, font, text, color, align, limit)
|
||||
local alphaMod = color and color[4]
|
||||
local lines = Multiline.wrap(font, text, limit)
|
||||
local lineHeight = font:getLineHeight()
|
||||
local height = #lines * lineHeight
|
||||
color = sdl.Color(color or 0)
|
||||
|
||||
-- mask values from SDL_ttf.c
|
||||
-- TODO: something with sdl.BYTEORDER == sdl.BIG_ENDIAN ?
|
||||
local r, g, b, a = 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000
|
||||
|
||||
local surface = ffi.gc(
|
||||
sdl.createRGBSurface(sdl.SWSURFACE, limit, height, 32, r, g, b, a),
|
||||
sdl.freeSurface)
|
||||
self.sdlSurface = surface
|
||||
|
||||
for index, line in ipairs(lines) do
|
||||
local text = table.concat(line)
|
||||
local lineSurface = ffi.gc(
|
||||
ttf.TTF_RenderUTF8_Blended(font.sdlFont, text, color),
|
||||
sdl.freeSurface)
|
||||
if lineSurface ~= nil then
|
||||
sdl.setSurfaceBlendMode(lineSurface, sdl.BLENDMODE_NONE)
|
||||
|
||||
local w, h = lineSurface.w, lineSurface.h
|
||||
local top = (index - 1) * lineHeight
|
||||
|
||||
if align == 'left' then
|
||||
sdl.blitSurface(lineSurface, nil, surface,
|
||||
sdl.Rect(0, top, w, h))
|
||||
elseif align == 'right' then
|
||||
sdl.blitSurface(lineSurface, nil, surface,
|
||||
sdl.Rect(limit - line.width, top, w, h))
|
||||
elseif align == 'center' then
|
||||
sdl.blitSurface(lineSurface, nil, surface,
|
||||
sdl.Rect((limit - line.width) / 2, top, w, h))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.sdlTexture = ffi.gc(
|
||||
sdl.createTextureFromSurface(self.sdlRenderer, surface),
|
||||
sdl.destroyTexture)
|
||||
|
||||
if alphaMod then
|
||||
sdl.setTextureAlphaMod(self.sdlTexture, alphaMod)
|
||||
end
|
||||
|
||||
self.width, self.height = limit, height
|
||||
end
|
||||
|
||||
function Text:constructor (renderer, font, text, color, align, limit)
|
||||
self.width, self.height = 0, 0
|
||||
if not text or text == '' then return end
|
||||
|
||||
self.sdlRenderer = renderer
|
||||
|
||||
if limit then
|
||||
renderMulti(self, font, text, color, align, limit)
|
||||
else
|
||||
renderSingle(self, font, text, color)
|
||||
end
|
||||
end
|
||||
|
||||
function Text:getWidth ()
|
||||
return self.width
|
||||
end
|
||||
|
||||
function Text:getHeight ()
|
||||
return self.height
|
||||
end
|
||||
|
||||
return Text
|
|
@ -1,177 +0,0 @@
|
|||
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
|
||||
|
||||
local Base = require(ROOT .. 'base')
|
||||
local Hooker = require(ROOT .. 'hooker')
|
||||
|
||||
local Backend = {}
|
||||
|
||||
Backend.isMac = function ()
|
||||
return love.system.getOS() == 'OS X'
|
||||
end
|
||||
|
||||
Backend.run = function () end
|
||||
|
||||
Backend.Cursor = love.mouse.newCursor
|
||||
|
||||
Backend.Font = require(ROOT .. 'backend.love.font')
|
||||
|
||||
Backend.Text = require(ROOT .. 'backend.love.text')
|
||||
|
||||
Backend.Image = love.graphics.newImage
|
||||
|
||||
Backend.Quad = love.graphics.newQuad
|
||||
|
||||
Backend.SpriteBatch = love.graphics.newSpriteBatch
|
||||
|
||||
-- love.graphics.draw( drawable, x, y, r, sx, sy, ox, oy, kx, ky )
|
||||
Backend.draw = function (drawable, ...)
|
||||
if drawable.typeOf and drawable:typeOf 'Drawable' then
|
||||
return love.graphics.draw(drawable, ...)
|
||||
end
|
||||
return drawable:draw(...)
|
||||
end
|
||||
|
||||
Backend.drawRectangle = love.graphics.rectangle
|
||||
|
||||
Backend.print = love.graphics.print
|
||||
|
||||
Backend.getClipboardText = love.system.getClipboardText
|
||||
|
||||
Backend.setClipboardText = love.system.setClipboardText
|
||||
|
||||
Backend.getMousePosition = love.mouse.getPosition
|
||||
|
||||
Backend.setMousePosition = love.mouse.setPosition
|
||||
|
||||
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 = function ()
|
||||
love.event.quit()
|
||||
end
|
||||
|
||||
if _G.love._version_major >= 11 then
|
||||
Backend.setColor = function(r, g, b, a)
|
||||
if type(r) == "table" then
|
||||
r, g, b, a = r[1], r[2], r[3], r[4]
|
||||
end
|
||||
if a == nil then
|
||||
a = 255
|
||||
end
|
||||
love.graphics.setColor(r / 255, g / 255, b / 255, a / 255)
|
||||
end
|
||||
else
|
||||
Backend.setColor = love.graphics.setColor
|
||||
end
|
||||
|
||||
Backend.setCursor = love.mouse.setCursor
|
||||
|
||||
Backend.setFont = function (font)
|
||||
return love.graphics.setFont(font.loveFont)
|
||||
end
|
||||
|
||||
Backend.setScissor = love.graphics.setScissor
|
||||
|
||||
Backend.getScissor = love.graphics.getScissor
|
||||
|
||||
Backend.intersectScissor = love.graphics.intersectScissor
|
||||
|
||||
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_major == 0 and love._version_minor < 10 then
|
||||
getMouseButtonId = function (value)
|
||||
return value == 'l' and 'left'
|
||||
or value == 'r' and 'right'
|
||||
or value == 'm' and 'middle'
|
||||
or value == 'x1' and 4
|
||||
or value == 'x2' and 5
|
||||
or value
|
||||
end
|
||||
isMouseDown = function ()
|
||||
return love.mouse.isDown('l', 'r', 'm')
|
||||
end
|
||||
else
|
||||
getMouseButtonId = function (value)
|
||||
return value == 1 and 'left'
|
||||
or value == 2 and 'right'
|
||||
or value == 3 and 'middle'
|
||||
or 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)
|
||||
if button == 'wu' or button == 'wd' then
|
||||
return input:handleWheelMove(layout, 0, button == 'wu' and 1 or -1)
|
||||
end
|
||||
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, sc, isRepeat)
|
||||
if key == ' ' then key = 'space' end
|
||||
return input:handleKeyPress(layout, key, sc, Backend.getMousePosition())
|
||||
end)
|
||||
hook(layout, 'keyreleased', function (key, sc)
|
||||
if key == ' ' then key = 'space' end
|
||||
return input:handleKeyRelease(layout, key, sc, Backend.getMousePosition())
|
||||
end)
|
||||
hook(layout, 'textinput', function (text)
|
||||
return input:handleTextInput(layout, text, Backend.getMousePosition())
|
||||
end)
|
||||
if (love._version_major == 0 and love._version_minor > 9) or love._version_major >= 11 then
|
||||
hook(layout, 'wheelmoved', function (x, y)
|
||||
return input:handleWheelMove(layout, x, y)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return Backend
|
|
@ -1,53 +0,0 @@
|
|||
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
|
||||
|
||||
return Font
|
|
@ -1,75 +0,0 @@
|
|||
local ROOT = (...):gsub('[^.]*.[^.]*.[^.]*$', '')
|
||||
local REL = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Multiline = require(ROOT .. 'multiline')
|
||||
|
||||
local Text = setmetatable({}, { __call = function (self, ...)
|
||||
local object = setmetatable({}, { __index = self })
|
||||
return object, self.constructor(object, ...)
|
||||
end })
|
||||
|
||||
local function renderSingle (self, x, y, font, text, color)
|
||||
love.graphics.push('all')
|
||||
love.graphics.setColor(color or { 0, 0, 0 })
|
||||
love.graphics.setFont(font.loveFont)
|
||||
love.graphics.print(text, math.floor(x), math.floor(y))
|
||||
love.graphics.pop()
|
||||
|
||||
self.height = font:getLineHeight()
|
||||
self.width = font:getAdvance(text)
|
||||
end
|
||||
|
||||
local function renderMulti (self, x, y, font, text, color, align, limit)
|
||||
local lines = Multiline.wrap(font, text, limit)
|
||||
local lineHeight = font:getLineHeight()
|
||||
local height = #lines * lineHeight
|
||||
|
||||
love.graphics.push('all')
|
||||
love.graphics.setColor(color or { 0, 0, 0 })
|
||||
love.graphics.setFont(font.loveFont)
|
||||
|
||||
for index, line in ipairs(lines) do
|
||||
local text = table.concat(line)
|
||||
local top = (index - 1) * lineHeight
|
||||
local w = line.width
|
||||
|
||||
if align == 'left' then
|
||||
love.graphics.print(text,
|
||||
math.floor(x), math.floor(top + y))
|
||||
elseif align == 'right' then
|
||||
love.graphics.print(text,
|
||||
math.floor(limit - w + x), math.floor(top + y))
|
||||
elseif align == 'center' then
|
||||
love.graphics.print(text,
|
||||
math.floor((limit - w) / 2 + x), math.floor(top + y))
|
||||
end
|
||||
end
|
||||
|
||||
love.graphics.pop()
|
||||
|
||||
self.height = height
|
||||
self.width = limit
|
||||
end
|
||||
|
||||
function Text:constructor (font, text, color, align, limit)
|
||||
if limit then
|
||||
function self:draw (x, y)
|
||||
return renderMulti(self, x, y, font, text, color, align, limit)
|
||||
end
|
||||
else
|
||||
function self:draw (x, y)
|
||||
return renderSingle(self, x, y, font, text, color)
|
||||
end
|
||||
end
|
||||
self:draw(-1000000, -1000000)
|
||||
end
|
||||
|
||||
function Text:getWidth ()
|
||||
return self.width
|
||||
end
|
||||
|
||||
function Text:getHeight ()
|
||||
return self.height
|
||||
end
|
||||
|
||||
return Text
|
|
@ -1,13 +0,0 @@
|
|||
return {
|
||||
extend = function (self, subtype)
|
||||
return setmetatable(subtype or {}, {
|
||||
__index = self,
|
||||
__call = function (self, ...)
|
||||
local instance = setmetatable({}, { __index = self })
|
||||
return instance, instance:constructor(...)
|
||||
end
|
||||
})
|
||||
end,
|
||||
constructor = function () end,
|
||||
}
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
local RESOURCE = (...):gsub('%.', '/') .. '/'
|
||||
|
||||
return function (config)
|
||||
config = config or {}
|
||||
local resources = assert(config.resources, 'missing config.resources')
|
||||
local backColor = config.backColor or { 240, 240, 240 }
|
||||
local lineColor = config.lineColor or { 220, 220, 220 }
|
||||
local textColor = config.textColor or { 0, 0, 0 }
|
||||
local highlight = config.highlight or { 0x19, 0xAE, 0xFF }
|
||||
|
||||
local button_pressed = resources .. 'button_pressed.png'
|
||||
local button_focused = resources .. 'button_focused.png'
|
||||
local button_hovered = resources .. 'button_hovered.png'
|
||||
local button = resources .. 'button.png'
|
||||
|
||||
local check_checked_pressed = resources .. 'check_checked_pressed.png'
|
||||
local check_unchecked_pressed = resources .. 'check_unchecked_pressed.png'
|
||||
local check_checked_focused = resources .. 'check_checked_focused.png'
|
||||
local check_unchecked_focused = resources .. 'check_unchecked_focused.png'
|
||||
local check_checked = resources .. 'check_checked.png'
|
||||
local check_unchecked = resources .. 'check_unchecked.png'
|
||||
|
||||
local radio_checked_pressed = resources .. 'radio_checked_pressed.png'
|
||||
local radio_unchecked_pressed = resources .. 'radio_unchecked_pressed.png'
|
||||
local radio_checked_focused = resources .. 'radio_checked_focused.png'
|
||||
local radio_unchecked_focused = resources .. 'radio_unchecked_focused.png'
|
||||
local radio_checked = resources .. 'radio_checked.png'
|
||||
local radio_unchecked = resources .. 'radio_unchecked.png'
|
||||
|
||||
local triangle_left = resources .. 'triangle_left.png'
|
||||
local triangle_up = resources .. 'triangle_up.png'
|
||||
local triangle_right = resources .. 'triangle_right.png'
|
||||
local triangle_down = resources .. 'triangle_down.png'
|
||||
|
||||
local text_focused = resources .. 'text_focused.png'
|
||||
local text = resources .. 'text.png'
|
||||
|
||||
local function getButtonSlices (self)
|
||||
return self.pressed.left and button_pressed
|
||||
or self.focused and button_focused
|
||||
or self.hovered and button_hovered
|
||||
or button
|
||||
end
|
||||
|
||||
local function getCheckIcon (self)
|
||||
if self.pressed.left then
|
||||
return self.value and check_checked_pressed
|
||||
or check_unchecked_pressed
|
||||
end
|
||||
if self.focused then
|
||||
return self.value and check_checked_focused
|
||||
or check_unchecked_focused
|
||||
end
|
||||
return self.value and check_checked or check_unchecked
|
||||
end
|
||||
|
||||
local function getControlHeight (self)
|
||||
return self.flow == 'x' and self._defaultDimension
|
||||
end
|
||||
|
||||
local function getControlWidth (self)
|
||||
return self.flow ~= 'x' and self._defaultDimension
|
||||
end
|
||||
|
||||
local function getMenuItemBackground (self)
|
||||
return self.active and highlight
|
||||
end
|
||||
|
||||
local function getRadioIcon (self)
|
||||
if self.pressed.left then
|
||||
return self.value and radio_checked_pressed
|
||||
or radio_unchecked_pressed
|
||||
end
|
||||
if self.focused then
|
||||
return self.value and radio_checked_focused
|
||||
or radio_unchecked_focused
|
||||
end
|
||||
return self.value and radio_checked or radio_unchecked
|
||||
end
|
||||
|
||||
local function getSashBackground (self)
|
||||
return self.hovered and highlight or lineColor
|
||||
end
|
||||
|
||||
local function getSashHeight (self)
|
||||
return self.parent and self.parent.flow ~= 'x' and 4
|
||||
end
|
||||
|
||||
local function getSashWidth (self)
|
||||
return self.parent and self.parent.flow == 'x' and 4
|
||||
end
|
||||
|
||||
local function getSliderThumbWidth (self)
|
||||
return self.parent.flow == 'x' and 32 or false
|
||||
end
|
||||
|
||||
local function getSliderThumbHeight (self)
|
||||
return self.parent.flow ~= 'x' and 32 or false
|
||||
end
|
||||
|
||||
local function getStepperBeforeIcon (self)
|
||||
return self.parent.flow == 'x' and triangle_left or triangle_up
|
||||
end
|
||||
|
||||
local function getStepperAfterIcon (self)
|
||||
return self.parent.flow == 'x' and triangle_right or triangle_down
|
||||
end
|
||||
|
||||
local function getTextSlices (self)
|
||||
return self.focused and text_focused or text
|
||||
end
|
||||
|
||||
return {
|
||||
|
||||
-- generic types for widgets to inherit
|
||||
|
||||
Control = {
|
||||
flow = 'x',
|
||||
height = getControlHeight,
|
||||
width = getControlWidth,
|
||||
color = textColor,
|
||||
align = 'center middle',
|
||||
margin = 2,
|
||||
color = textColor,
|
||||
solid = true,
|
||||
_defaultDimension = 36,
|
||||
},
|
||||
|
||||
Line = {
|
||||
margin = 0,
|
||||
padding = 4,
|
||||
align = 'left middle',
|
||||
_defaultDimension = 24,
|
||||
},
|
||||
|
||||
-- widget types
|
||||
|
||||
button = {
|
||||
type = { 'Control' },
|
||||
padding = 6,
|
||||
slices = getButtonSlices,
|
||||
focusable = true,
|
||||
},
|
||||
check = {
|
||||
type = { 'Line', 'Control' },
|
||||
focusable = true,
|
||||
icon = getCheckIcon,
|
||||
},
|
||||
label = {
|
||||
type = { 'Line', 'Control' },
|
||||
},
|
||||
menu = {
|
||||
flow = 'x',
|
||||
height = 24,
|
||||
background = backColor,
|
||||
color = textColor,
|
||||
},
|
||||
['menu.expander'] = {
|
||||
icon = resources .. 'triangle_right.png',
|
||||
},
|
||||
['menu.item'] = {
|
||||
padding = 4,
|
||||
height = 24,
|
||||
align = 'left middle',
|
||||
background = getMenuItemBackground,
|
||||
},
|
||||
panel = {
|
||||
padding = 2,
|
||||
background = backColor,
|
||||
color = textColor,
|
||||
solid = true,
|
||||
},
|
||||
progress = {
|
||||
type = { 'Control' },
|
||||
slices = resources .. 'button_pressed.png',
|
||||
},
|
||||
['progress.bar'] = {
|
||||
slices = resources .. 'progress.png',
|
||||
minwidth = 12,
|
||||
minheight = 22,
|
||||
},
|
||||
radio = {
|
||||
type = { 'Line', 'Control' },
|
||||
focusable = true,
|
||||
icon = getRadioIcon,
|
||||
},
|
||||
sash = {
|
||||
background = getSashBackground,
|
||||
height = getSashHeight,
|
||||
width = getSashWidth,
|
||||
},
|
||||
slider = {
|
||||
type = { 'Control' },
|
||||
slices = resources .. 'button_pressed.png',
|
||||
},
|
||||
['slider.thumb'] = {
|
||||
type = { 'button' },
|
||||
align = 'middle center',
|
||||
margin = 0,
|
||||
width = getSliderThumbWidth,
|
||||
height = getSliderThumbHeight,
|
||||
},
|
||||
status = {
|
||||
type = { 'Line', 'Control' },
|
||||
background = backColor,
|
||||
color = textColor,
|
||||
},
|
||||
stepper = {
|
||||
type = { 'Control' },
|
||||
slices = resources .. 'button_pressed.png',
|
||||
},
|
||||
['stepper.after'] = {
|
||||
type = { 'button' },
|
||||
icon = getStepperAfterIcon,
|
||||
margin = 0,
|
||||
minwidth = 32,
|
||||
minheight = 32,
|
||||
},
|
||||
['stepper.before'] = {
|
||||
type = { 'button' },
|
||||
icon = getStepperBeforeIcon,
|
||||
margin = 0,
|
||||
minwidth = 32,
|
||||
minheight = 32,
|
||||
},
|
||||
['stepper.item'] = {
|
||||
align = 'center middle',
|
||||
color = textColor,
|
||||
},
|
||||
['stepper.view'] = {
|
||||
margin = 4,
|
||||
},
|
||||
submenu = {
|
||||
padding = 10,
|
||||
margin = -10,
|
||||
slices = resources .. 'submenu.png',
|
||||
color = textColor,
|
||||
solid = true,
|
||||
},
|
||||
text = {
|
||||
type = { 'Control' },
|
||||
align = 'left middle',
|
||||
slices = getTextSlices,
|
||||
padding = 6,
|
||||
focusable = true,
|
||||
cursor = 'ibeam',
|
||||
highlight = highlight,
|
||||
},
|
||||
}
|
||||
|
||||
end
|
|
@ -1,69 +0,0 @@
|
|||
--[[--
|
||||
Event class.
|
||||
|
||||
@classmod Event
|
||||
--]]--
|
||||
|
||||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Base = require(ROOT .. 'base')
|
||||
local Hooker = require(ROOT .. 'hooker')
|
||||
|
||||
local Event = Base:extend({ name = 'Event' })
|
||||
|
||||
function Event:emit (target, data, defaultAction)
|
||||
local callbacks = self.registry[target]
|
||||
local result = callbacks and callbacks(data or {})
|
||||
if result ~= nil then return result end
|
||||
if defaultAction then defaultAction() end
|
||||
end
|
||||
|
||||
function Event:bind (target, callback)
|
||||
local registry = self.registry
|
||||
return Hooker.hook(registry, target, callback)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Event names.
|
||||
--]]--
|
||||
Event.names = {
|
||||
'Reshape', -- A widget is being reshaped.
|
||||
'PreDisplay', -- A widget is about to be drawn.
|
||||
'Display', -- A widget was drawn.
|
||||
'KeyPress', -- A keyboard key was pressed.
|
||||
'KeyRelease', -- A keyboard key was released.
|
||||
'TextInput', -- Text was entered.
|
||||
'Move', -- The cursor moved, and no button was pressed.
|
||||
'Focus', -- A widget received focus.
|
||||
'Blur', -- A widget lost focus.
|
||||
'Enter', -- The cursor entered a widget, and no button was pressed.
|
||||
'Leave', -- The cursor left a widget, and no button was pressed.
|
||||
'PressEnter', -- The cursor entered a widget, and a button was pressed.
|
||||
'PressLeave', -- The cursor left a widget, and a button was pressed.
|
||||
'PressStart', -- A pointer button or keyboard shortcut was pressed.
|
||||
'PressEnd', -- A pointer button or keyboard shortcut was released.
|
||||
'PressDrag', -- A pressed cursor moved; targets originating widget.
|
||||
'PressMove', -- A pressed cursor moved; targets widget at cursor position.
|
||||
'Press', -- A pointer button was pressed and released on the same widget.
|
||||
'Change', -- A widget's value changed.
|
||||
'WheelMove', -- The scroll wheel on the mouse moved.
|
||||
'Show', -- A layout is shown.
|
||||
'Hide', -- A layout is hidden.
|
||||
}
|
||||
|
||||
local weakKeyMeta = { __mode = 'k' }
|
||||
|
||||
for i, name in ipairs(Event.names) do
|
||||
Event[name] = Event:extend({
|
||||
name = name,
|
||||
registry = setmetatable({}, weakKeyMeta),
|
||||
})
|
||||
end
|
||||
|
||||
function Event.injectBinders (t)
|
||||
for i, name in ipairs(Event.names) do
|
||||
t['on' .. name] = function (...) return Event[name]:bind(...) end
|
||||
end
|
||||
end
|
||||
|
||||
return Event
|
|
@ -1,91 +0,0 @@
|
|||
local Hooker = {}
|
||||
|
||||
local wrapped = setmetatable({}, { __mode = 'k' })
|
||||
|
||||
local hooks = setmetatable({}, { __mode = 'k' })
|
||||
|
||||
local function unhook (item)
|
||||
if item.prev then
|
||||
item.prev.next = item.next
|
||||
end
|
||||
|
||||
if item.next then
|
||||
item.next.prev = item.prev
|
||||
end
|
||||
|
||||
if hooks[item.host][item.key] == item then
|
||||
hooks[item.host][item.key] = item.next
|
||||
end
|
||||
end
|
||||
|
||||
local function hook (host, key, func, atEnd)
|
||||
if not func then
|
||||
return
|
||||
end
|
||||
|
||||
if not hooks[host] then
|
||||
hooks[host] = {}
|
||||
end
|
||||
|
||||
local current = hooks[host][key]
|
||||
local item = {
|
||||
next = not atEnd and current or nil,
|
||||
unhook = unhook,
|
||||
host = host,
|
||||
key = key,
|
||||
func = func,
|
||||
}
|
||||
|
||||
if atEnd then
|
||||
if current then
|
||||
while current.next do
|
||||
current = current.next
|
||||
end
|
||||
current.next = item
|
||||
item.prev = current
|
||||
else
|
||||
hooks[host][key] = item
|
||||
end
|
||||
return item
|
||||
end
|
||||
|
||||
if current then
|
||||
current.prev = item
|
||||
end
|
||||
|
||||
hooks[host][key] = item
|
||||
|
||||
return item
|
||||
end
|
||||
|
||||
function Hooker.unhook (item)
|
||||
return unhook(item)
|
||||
end
|
||||
|
||||
function Hooker.hook (host, key, func, atEnd)
|
||||
if not wrapped[host] then
|
||||
wrapped[host] = {}
|
||||
end
|
||||
|
||||
if not wrapped[host][key] then
|
||||
wrapped[host][key] = true
|
||||
|
||||
hook(host, key, host[key])
|
||||
|
||||
host[key] = function (...)
|
||||
local item = hooks[host][key]
|
||||
|
||||
while item do
|
||||
local result = item.func(...)
|
||||
if result ~= nil then
|
||||
return result
|
||||
end
|
||||
item = item.next
|
||||
end -- while
|
||||
end -- function
|
||||
end -- if
|
||||
|
||||
return hook(host, key, func, atEnd)
|
||||
end
|
||||
|
||||
return Hooker
|
|
@ -1,232 +0,0 @@
|
|||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Backend = require(ROOT .. 'backend')
|
||||
local Base = require(ROOT .. 'base')
|
||||
local Event = require(ROOT .. 'event')
|
||||
local Shortcut = require(ROOT .. 'shortcut')
|
||||
|
||||
local Input = Base:extend()
|
||||
|
||||
local weakValueMeta = { __mode = 'v' }
|
||||
|
||||
function Input:constructor ()
|
||||
self.pressedWidgets = setmetatable({}, weakValueMeta)
|
||||
self.passedWidgets = setmetatable({}, weakValueMeta)
|
||||
end
|
||||
|
||||
function Input:handleDisplay (layout)
|
||||
local root = layout.root
|
||||
if root then root:paint() end
|
||||
Event.Display:emit(layout)
|
||||
end
|
||||
|
||||
function Input:handleKeyPress (layout, key, sc, x, y)
|
||||
local widget = layout.focusedWidget or layout.root
|
||||
local result = widget:bubbleEvent('KeyPress', {
|
||||
key = key,
|
||||
scanCode = sc,
|
||||
modifierFlags = Shortcut.getModifierFlags(),
|
||||
x = x, y = y
|
||||
})
|
||||
if result ~= nil then return result end
|
||||
if layout.root.modal then return false end
|
||||
end
|
||||
|
||||
function Input:handleKeyRelease (layout, key, sc, x, y)
|
||||
local widget = layout.focusedWidget or layout.root
|
||||
local result = widget:bubbleEvent('KeyRelease', {
|
||||
key = key,
|
||||
scanCode = sc,
|
||||
modifierFlags = Shortcut.getModifierFlags(),
|
||||
x = x, y = y
|
||||
})
|
||||
if result ~= nil then return result end
|
||||
if layout.root.modal then return false end
|
||||
end
|
||||
|
||||
function Input:handleTextInput (layout, text, x, y)
|
||||
local widget = layout.focusedWidget or layout.root
|
||||
local result = widget:bubbleEvent('TextInput', {
|
||||
text = text,
|
||||
x = x, y = y
|
||||
})
|
||||
if result ~= nil then return result end
|
||||
if layout.root.modal then return false end
|
||||
end
|
||||
|
||||
local function checkHit (widget, layout)
|
||||
local root = layout.root
|
||||
return widget and widget.solid or root.modal, widget or root
|
||||
end
|
||||
|
||||
function Input:handleMove (layout, x, y)
|
||||
local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
|
||||
local previousWidget = self.previousMoveWidget
|
||||
if widget ~= previousWidget then
|
||||
if previousWidget then
|
||||
for ancestor in previousWidget:eachAncestor(true) do
|
||||
ancestor.hovered = nil
|
||||
end
|
||||
end
|
||||
for ancestor in widget:eachAncestor(true) do
|
||||
ancestor.hovered = true
|
||||
end
|
||||
end
|
||||
widget:bubbleEvent('Move', {
|
||||
hit = hit,
|
||||
oldTarget = previousWidget,
|
||||
x = x, y = y
|
||||
})
|
||||
if widget ~= previousWidget then
|
||||
if previousWidget then
|
||||
previousWidget:bubbleEvent('Leave', {
|
||||
hit = hit,
|
||||
newTarget = widget,
|
||||
x = x, y = y
|
||||
})
|
||||
end
|
||||
widget:bubbleEvent('Enter', {
|
||||
hit = hit,
|
||||
oldTarget = previousWidget,
|
||||
x = x, y = y
|
||||
})
|
||||
if widget.cursor then
|
||||
Backend.setCursor(Backend.getSystemCursor(widget.cursor))
|
||||
else
|
||||
Backend.setCursor()
|
||||
end
|
||||
self.previousMoveWidget = widget
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
function Input:handlePressedMove (layout, x, y)
|
||||
local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
|
||||
for _, button in ipairs { 'left', 'middle', 'right' } do
|
||||
local originWidget = self.pressedWidgets[button]
|
||||
if originWidget then
|
||||
local passedWidget = self.passedWidgets[button]
|
||||
originWidget:bubbleEvent('PressDrag', {
|
||||
hit = hit,
|
||||
newTarget = widget,
|
||||
button = button,
|
||||
x = x, y = y
|
||||
})
|
||||
if (widget == passedWidget) then
|
||||
widget:bubbleEvent('PressMove', {
|
||||
hit = hit,
|
||||
origin = originWidget,
|
||||
button = button,
|
||||
x = x, y = y
|
||||
})
|
||||
else
|
||||
originWidget.pressed[button] = (widget == originWidget) or nil
|
||||
if passedWidget then
|
||||
passedWidget:bubbleEvent('PressLeave', {
|
||||
hit = hit,
|
||||
newTarget = widget,
|
||||
origin = originWidget,
|
||||
button = button,
|
||||
x = x, y = y
|
||||
})
|
||||
end
|
||||
widget:bubbleEvent('PressEnter', {
|
||||
hit = hit,
|
||||
oldTarget = passedWidget,
|
||||
origin = originWidget,
|
||||
button = button,
|
||||
x = x, y = y
|
||||
})
|
||||
self.passedWidgets[button] = widget
|
||||
end
|
||||
end -- if originWidget
|
||||
end -- mouse buttons
|
||||
return hit
|
||||
end
|
||||
|
||||
function Input:handlePressStart (layout, button, x, y, widget, shortcut)
|
||||
local hit, widget = checkHit(widget or layout:getWidgetAt(x, y), layout)
|
||||
-- if hit then
|
||||
self.pressedWidgets[button] = widget
|
||||
self.passedWidgets[button] = widget
|
||||
widget.pressed[button] = true
|
||||
if button == 'left' then
|
||||
widget:focus()
|
||||
end
|
||||
-- end
|
||||
widget:bubbleEvent('PressStart', {
|
||||
hit = hit,
|
||||
button = button,
|
||||
shortcut = shortcut,
|
||||
x = x, y = y
|
||||
})
|
||||
return hit
|
||||
end
|
||||
|
||||
function Input:handlePressEnd (layout, button, x, y, widget, shortcut)
|
||||
local originWidget = widget or self.pressedWidgets[button]
|
||||
if not originWidget then return end
|
||||
local hit, widget = checkHit(widget or layout:getWidgetAt(x, y), layout)
|
||||
local wasPressed = originWidget.pressed[button]
|
||||
if hit then
|
||||
originWidget.pressed[button] = nil
|
||||
end
|
||||
widget:bubbleEvent('PressEnd', {
|
||||
hit = hit,
|
||||
origin = originWidget,
|
||||
shortcut = shortcut,
|
||||
button = button,
|
||||
x = x, y = y
|
||||
})
|
||||
if (widget == originWidget and wasPressed) then
|
||||
widget:bubbleEvent('Press', {
|
||||
hit = hit,
|
||||
button = button,
|
||||
shortcut = shortcut,
|
||||
x = x, y = y
|
||||
})
|
||||
end
|
||||
if hit then
|
||||
self.pressedWidgets[button] = nil
|
||||
self.passedWidgets[button] = nil
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
function Input:handleReshape (layout, width, height)
|
||||
local root = layout.root
|
||||
|
||||
root:reshape()
|
||||
|
||||
if root.type ~= 'window' then -- FIXME: move stuff below to a Widget method
|
||||
if not root.width then
|
||||
root.dimensions.width = width
|
||||
end
|
||||
if not root.height then
|
||||
root.dimensions.height = height
|
||||
end
|
||||
end
|
||||
|
||||
Event.Reshape:emit(layout, {
|
||||
target = layout,
|
||||
width = width,
|
||||
height = height
|
||||
})
|
||||
end
|
||||
|
||||
function Input:handleWheelMove (layout, scrollX, scrollY)
|
||||
local x, y = Backend.getMousePosition()
|
||||
local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
|
||||
|
||||
widget:bubbleEvent('WheelMove', {
|
||||
hit = hit,
|
||||
x = x, y = y,
|
||||
scrollX = scrollX, scrollY = scrollY
|
||||
})
|
||||
|
||||
return hit
|
||||
end
|
||||
|
||||
Input.default = Input()
|
||||
|
||||
return Input
|
|
@ -1,54 +0,0 @@
|
|||
--[[--
|
||||
Launcher for the LuaJIT backend.
|
||||
|
||||
Looks for main.lua. Launch it like this:
|
||||
|
||||
luajit myapp/lib/luigi/launch.lua
|
||||
|
||||
If luigi isn't inside the project directory, pass
|
||||
the path containing main.lua as the second argument.
|
||||
The path must end with a directory separator.
|
||||
|
||||
luajit /opt/luigi/launch.lua ./myapp/
|
||||
|
||||
If the app prefixes luigi modules with something
|
||||
other then 'luigi', pass that prefix as the third
|
||||
argument.
|
||||
|
||||
luajit /opt/luigi/launch.lua ./myapp/ lib.luigi
|
||||
|
||||
--]]--
|
||||
local packagePath = package.path
|
||||
local libRoot = arg[0]:gsub('[^/\\]*%.lua$', '')
|
||||
local appRoot, modPath = ...
|
||||
|
||||
local function run (appRoot, modPath)
|
||||
package.path = packagePath .. ';' .. appRoot .. '?.lua'
|
||||
rawset(_G, 'LUIGI_APP_ROOT', appRoot)
|
||||
require 'main'
|
||||
require (modPath .. '.backend').run()
|
||||
end
|
||||
|
||||
-- expect to find main.lua in appRoot if specified
|
||||
if appRoot then
|
||||
return run(appRoot, modPath or 'luigi')
|
||||
end
|
||||
|
||||
-- try to find main.lua in a parent of this library, recursively.
|
||||
local lastLibRoot = libRoot
|
||||
repeat
|
||||
if io.open(libRoot .. 'main.lua') then
|
||||
return run(libRoot, modPath)
|
||||
end
|
||||
lastLibRoot = libRoot
|
||||
libRoot = libRoot:gsub('([^/\\]*).$', function (m)
|
||||
modPath = modPath and (m .. '.' .. modPath) or m
|
||||
return ''
|
||||
end)
|
||||
until libRoot == lastLibRoot
|
||||
|
||||
error 'main.lua not found'
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
--[[--
|
||||
A Layout contains a tree of widgets with a single `root` widget.
|
||||
|
||||
Layouts will resize to fit the window unless a `top` or `left`
|
||||
property is found in the root widget.
|
||||
|
||||
Layouts are drawn in the order that they were shown, so the
|
||||
most recently shown layout shown will always appear on top.
|
||||
|
||||
Other events are sent to layouts in the opposite direction,
|
||||
and are trapped by the first layout that can handle the event
|
||||
(for example, the topmost layer that is focused or hovered).
|
||||
|
||||
@classmod Layout
|
||||
--]]--
|
||||
|
||||
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 Layout = Base:extend()
|
||||
|
||||
Layout.isLayout = true
|
||||
|
||||
--[[--
|
||||
Layout constructor.
|
||||
|
||||
@function Luigi.Layout
|
||||
|
||||
@tparam table data
|
||||
A tree of widget data.
|
||||
|
||||
@treturn Layout
|
||||
A Layout instance.
|
||||
--]]--
|
||||
function Layout:constructor (data, master)
|
||||
data = data or {}
|
||||
|
||||
if master then
|
||||
self:setMaster(master)
|
||||
else
|
||||
self:setTheme(require(ROOT .. 'theme.light'))
|
||||
self:setStyle()
|
||||
end
|
||||
|
||||
self:addDefaultHandlers()
|
||||
|
||||
self.hooks = {}
|
||||
self.isShown = false
|
||||
self.root = data
|
||||
|
||||
Widget(self, data)
|
||||
|
||||
self.isReady = true
|
||||
end
|
||||
|
||||
--[[--
|
||||
Create a detached widget.
|
||||
|
||||
Internal function used to create widgets that are associated with
|
||||
a layout, but "detached" from it.
|
||||
|
||||
Used by context menus, which use their "owner" widget's layout
|
||||
for theme and style information but appear in a separate layout.
|
||||
|
||||
@tparam table data
|
||||
A tree of widget data.
|
||||
|
||||
@treturn Widget
|
||||
A widget instance.
|
||||
--]]--
|
||||
function Layout:createWidget (data)
|
||||
return Widget(self, data)
|
||||
end
|
||||
|
||||
local function clearWidget (widget)
|
||||
widget.textData = nil
|
||||
widget.fontData = nil
|
||||
widget.position = {}
|
||||
widget.dimensions = {}
|
||||
widget.type = widget.type
|
||||
for _, child in ipairs(widget) do
|
||||
clearWidget(child)
|
||||
end
|
||||
local items = widget.items
|
||||
if items then
|
||||
for _, item in ipairs(items) do
|
||||
clearWidget(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function reset (self)
|
||||
if not self.root then return end
|
||||
clearWidget(self.root)
|
||||
end
|
||||
|
||||
--[[--
|
||||
Set the master layout for this layout.
|
||||
|
||||
This layout's theme and style will be set the same as the master layout, and
|
||||
widgets added to this layout will be indexed and keyboard-accelerated by the
|
||||
master layout instead of this layout.
|
||||
|
||||
@tparam Layout layout
|
||||
Master layout
|
||||
|
||||
@treturn Layout Self
|
||||
--]]--
|
||||
function Layout:setMaster (layout)
|
||||
self.master = layout
|
||||
|
||||
reset(self)
|
||||
return self
|
||||
end
|
||||
|
||||
--[[--
|
||||
Set the style from a definition table or function.
|
||||
|
||||
@tparam table|function rules
|
||||
Style definition.
|
||||
|
||||
@treturn Layout Self
|
||||
--]]--
|
||||
function Layout:setStyle (rules)
|
||||
if type(rules) == 'function' then
|
||||
rules = rules()
|
||||
end
|
||||
self.style = Style(rules or {}, { 'style' })
|
||||
|
||||
reset(self)
|
||||
return self
|
||||
end
|
||||
|
||||
--[[--
|
||||
Set the theme from a definition table or function.
|
||||
|
||||
@tparam table|function rules
|
||||
Theme definition.
|
||||
--]]--
|
||||
function Layout:setTheme (rules)
|
||||
if type(rules) == 'function' then
|
||||
rules = rules()
|
||||
end
|
||||
self.theme = Style(rules or {}, { 'type' })
|
||||
reset(self)
|
||||
return self
|
||||
end
|
||||
|
||||
--[[--
|
||||
Get the style from master layout or this layout.
|
||||
|
||||
@treturn table
|
||||
Style table.
|
||||
--]]--
|
||||
function Layout:getStyle ()
|
||||
return self.master and self.master:getStyle() or self.style
|
||||
end
|
||||
|
||||
--[[--
|
||||
Get the theme from master layout or this layout.
|
||||
|
||||
@treturn table
|
||||
Theme table.
|
||||
--]]--
|
||||
function Layout:getTheme ()
|
||||
return self.master and self.master:getTheme() or self.theme
|
||||
end
|
||||
|
||||
--[[--
|
||||
Show the layout.
|
||||
|
||||
Hooks all appropriate Love events and callbacks.
|
||||
|
||||
@treturn Layout
|
||||
Return this layout for chaining.
|
||||
--]]--
|
||||
function Layout:show ()
|
||||
if self.isShown then
|
||||
Backend.hide(self)
|
||||
self.isShown = nil
|
||||
end
|
||||
|
||||
self.isShown = true
|
||||
|
||||
if not self.input then
|
||||
self.input = Input.default -- Input(self)
|
||||
end
|
||||
|
||||
Backend.show(self)
|
||||
self.root:reshape()
|
||||
|
||||
Event.Show:emit(self, self)
|
||||
return self
|
||||
end
|
||||
|
||||
--[[--
|
||||
Hide the layout.
|
||||
|
||||
Unhooks Love events and callbacks.
|
||||
|
||||
@treturn Layout
|
||||
Return this layout for chaining.
|
||||
--]]--
|
||||
function Layout:hide ()
|
||||
if not self.isShown then
|
||||
return
|
||||
end
|
||||
self.isShown = nil
|
||||
Backend.hide(self)
|
||||
|
||||
Event.Hide:emit(self, self)
|
||||
return self
|
||||
end
|
||||
|
||||
--[[--
|
||||
Focus next focusable widget.
|
||||
|
||||
Traverses widgets using Widget:getNextNeighbor until a focusable widget is
|
||||
found, and focuses that widget.
|
||||
|
||||
@treturn Widget
|
||||
The widget that was focused, or nil
|
||||
--]]--
|
||||
function Layout:focusNextWidget ()
|
||||
local widget = self.focusedWidget or self.root
|
||||
local nextWidget = widget:getNextNeighbor()
|
||||
|
||||
while nextWidget ~= widget do
|
||||
if nextWidget:focus() then return nextWidget end
|
||||
nextWidget = nextWidget:getNextNeighbor()
|
||||
end
|
||||
end
|
||||
|
||||
--[[--
|
||||
Focus previous focusable widget.
|
||||
|
||||
Traverses widgets using Widget:getPreviousNeighbor until a focusable widget is
|
||||
found, and focuses that widget.
|
||||
|
||||
@treturn Widget
|
||||
The widget that was focused, or nil
|
||||
--]]--
|
||||
function Layout:focusPreviousWidget ()
|
||||
local widget = self.focusedWidget or self.root
|
||||
local previousWidget = widget:getPreviousNeighbor()
|
||||
|
||||
while previousWidget ~= widget do
|
||||
if previousWidget:focus() then return previousWidget end
|
||||
previousWidget = previousWidget:getPreviousNeighbor()
|
||||
end
|
||||
end
|
||||
|
||||
--[[--
|
||||
Get the innermost widget at given coordinates.
|
||||
|
||||
@tparam number x
|
||||
Number of pixels from window's left edge.
|
||||
|
||||
@tparam number y
|
||||
Number of pixels from window's top edge.
|
||||
|
||||
@tparam[opt] Widget root
|
||||
Widget to search within, defaults to layout root.
|
||||
--]]--
|
||||
function Layout:getWidgetAt (x, y, root)
|
||||
if not root then
|
||||
root = self.root
|
||||
end
|
||||
-- Loop through in reverse, because siblings defined later in the tree
|
||||
-- will overdraw earlier siblings.
|
||||
for i = #root, 1, -1 do
|
||||
local child = root[i]
|
||||
if child:isAt(x, y) then
|
||||
local inner = self:getWidgetAt(x, y, child)
|
||||
if inner then return inner end
|
||||
end
|
||||
end
|
||||
|
||||
if root:isAt(x, y) then return root end
|
||||
end
|
||||
|
||||
--[[--
|
||||
Place a layout near a point or rectangle.
|
||||
|
||||
@tparam number left
|
||||
Number of pixels from window's left edge.
|
||||
|
||||
@tparam number top
|
||||
Number of pixels from window's top edge.
|
||||
|
||||
@tparam[opt] number width
|
||||
Width of the rectangle to place layout outside of, defaults to 0.
|
||||
|
||||
@tparam[opt] number height
|
||||
Height of the rectangle to place layout outside of, defaults to 0.
|
||||
|
||||
@treturn Layout
|
||||
Return this layout for chaining.
|
||||
--]]--
|
||||
function Layout:placeNear (left, top, width, height)
|
||||
width, height = width or 0, height or 0
|
||||
local root = self.root
|
||||
-- place to the left if there's no room to the right
|
||||
local layoutWidth = root:getWidth()
|
||||
local windowWidth, windowHeight = Backend.getWindowSize()
|
||||
if left + width + layoutWidth > windowWidth then
|
||||
left = left - layoutWidth - width
|
||||
else
|
||||
left = left + width
|
||||
end
|
||||
-- place above if there's no room below
|
||||
local layoutHeight = root:getHeight()
|
||||
if top + height + layoutHeight > windowHeight then
|
||||
top = top - layoutHeight - height
|
||||
else
|
||||
top = top + height
|
||||
end
|
||||
root.left = left
|
||||
root.top = top
|
||||
end
|
||||
|
||||
|
||||
-- Add handlers for keyboard shortcuts, tab focus, and mouse wheel scroll
|
||||
function Layout:addDefaultHandlers ()
|
||||
self.shortcuts = {}
|
||||
|
||||
for i = 0, 15 do
|
||||
self.shortcuts[i] = {}
|
||||
end
|
||||
|
||||
self.behavior = {}
|
||||
|
||||
local function createBehavior (name, hooks)
|
||||
self.behavior[name] = hooks
|
||||
function hooks.destroy ()
|
||||
for _, hook in ipairs(hooks) do
|
||||
hook:unhook()
|
||||
end
|
||||
self.behavior[name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
createBehavior('context', {
|
||||
self:onPressStart(function (event)
|
||||
-- show context menu on right click
|
||||
if event.button ~= 'right' then return end
|
||||
local menu = event.target.context
|
||||
if not menu then return end
|
||||
menu:bubbleEvent('PressStart', event)
|
||||
-- make sure it fits in the window
|
||||
-- TODO: open in a new borderless window under SDL?
|
||||
menu.menuLayout:placeNear(event.x - 1, event.y - 1, 2, 2)
|
||||
return false
|
||||
end)
|
||||
})
|
||||
|
||||
createBehavior('shortcut', {
|
||||
self:onKeyPress(function (event)
|
||||
local entry = self.shortcuts[event.modifierFlags]
|
||||
local widget = entry and entry[event.key]
|
||||
if not widget then return end
|
||||
widget.hovered = true
|
||||
self.input:handlePressStart(self, 'left', event.x, event.y,
|
||||
widget, widget.shortcut)
|
||||
return false
|
||||
end),
|
||||
|
||||
self:onKeyRelease(function (event)
|
||||
local entry = self.shortcuts[event.modifierFlags]
|
||||
local widget = entry and entry[event.key]
|
||||
if not widget then return end
|
||||
widget.hovered = false
|
||||
self.input:handlePressEnd(self, 'left', event.x, event.y,
|
||||
widget, widget.shortcut)
|
||||
return false
|
||||
end)
|
||||
})
|
||||
|
||||
createBehavior('navigate', {
|
||||
self:onKeyPress(function (event)
|
||||
-- tab/shift-tab cycles focused widget
|
||||
if event.key == 'tab' then
|
||||
if Backend.isKeyDown('lshift', 'rshift') then
|
||||
self:focusPreviousWidget()
|
||||
else
|
||||
self:focusNextWidget()
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- space/enter presses focused widget
|
||||
local widget = self.focusedWidget
|
||||
if widget and event.key == 'space' or event.key == ' '
|
||||
or event.key == 'return' then
|
||||
self.input:handlePressStart(self, 'left', event.x, event.y,
|
||||
widget, event.key)
|
||||
return false
|
||||
end
|
||||
end),
|
||||
|
||||
self:onKeyRelease(function (event)
|
||||
-- space / enter presses focused widget
|
||||
local widget = self.focusedWidget
|
||||
if widget and event.key == 'space' or event.key == ' '
|
||||
or event.key == 'return' then
|
||||
self.input:handlePressEnd(self, 'left', event.x, event.y,
|
||||
widget, event.key)
|
||||
return false
|
||||
end
|
||||
end)
|
||||
})
|
||||
|
||||
createBehavior('scroll', {
|
||||
self:onWheelMove(function (event)
|
||||
if not event.hit then return end
|
||||
local amount = event.scrollY ~= 0 and event.scrollY or event.scrollX
|
||||
for widget in event.target:eachAncestor(true) do
|
||||
if widget:scrollBy(amount) then return false end
|
||||
end -- ancestor loop
|
||||
return false
|
||||
end) -- wheel move
|
||||
})
|
||||
|
||||
createBehavior('status', {
|
||||
self:onEnter(function (event)
|
||||
local statusWidget = (self.master or self).statusWidget
|
||||
if not statusWidget then return end
|
||||
|
||||
statusWidget.text = event.target.status
|
||||
return false
|
||||
end)
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
Event.injectBinders(Layout)
|
||||
|
||||
return Layout
|
|
@ -1,110 +0,0 @@
|
|||
--[[--
|
||||
Mosiac, drawable class for 9-slice images.
|
||||
|
||||
@classmod Mosiac
|
||||
--]]--
|
||||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Backend = require(ROOT .. 'backend')
|
||||
local Base = require(ROOT .. 'base')
|
||||
|
||||
local Mosaic = Base:extend()
|
||||
|
||||
local imageCache = {}
|
||||
local sliceCache = {}
|
||||
|
||||
local function loadImage (path)
|
||||
if not imageCache[path] then
|
||||
imageCache[path] = Backend.Image(path)
|
||||
end
|
||||
|
||||
return imageCache[path]
|
||||
end
|
||||
|
||||
function Mosaic.fromWidget (widget)
|
||||
local mosaic = widget.mosaic
|
||||
if mosaic and mosaic.slicePath == widget.slices then
|
||||
return mosaic
|
||||
end
|
||||
if widget.slices then
|
||||
widget.mosaic = Mosaic(widget.slices)
|
||||
return widget.mosaic
|
||||
end
|
||||
end
|
||||
|
||||
function Mosaic:constructor (path)
|
||||
local slices = self:loadSlices(path)
|
||||
self.batch = Backend.SpriteBatch(slices.image)
|
||||
self.slices = slices
|
||||
self.slicePath = path
|
||||
end
|
||||
|
||||
function Mosaic:setRectangle (x, y, w, h)
|
||||
if self.x == x and self.y == y and self.width == w and self.height == h then
|
||||
self.needsRefresh = false
|
||||
return
|
||||
end
|
||||
self.needsRefresh = true
|
||||
self.x, self.y, self.width, self.height = x, y, w, h
|
||||
end
|
||||
|
||||
function Mosaic:loadSlices (path)
|
||||
local slices = sliceCache[path]
|
||||
|
||||
if not slices then
|
||||
slices = {}
|
||||
sliceCache[path] = slices
|
||||
local image = loadImage(path)
|
||||
local iw, ih = image:getWidth(), image:getHeight()
|
||||
local w, h = math.floor(iw / 3), math.floor(ih / 3)
|
||||
local Quad = Backend.Quad
|
||||
|
||||
slices.image = image
|
||||
slices.width = w
|
||||
slices.height = h
|
||||
|
||||
slices.topLeft = Quad(0, 0, w, h, iw, ih)
|
||||
slices.topCenter = Quad(w, 0, w, h, iw, ih)
|
||||
slices.topRight = Quad(iw - w, 0, w, h, iw, ih)
|
||||
slices.middleLeft = Quad(0, h, w, h, iw, ih)
|
||||
slices.middleCenter = Quad(w, h, w, h, iw, ih)
|
||||
slices.middleRight = Quad(iw - w, h, w, h, iw, ih)
|
||||
slices.bottomLeft = Quad(0, ih - h, w, h, iw, ih)
|
||||
slices.bottomCenter = Quad(w, ih - h, w, h, iw, ih)
|
||||
slices.bottomRight = Quad(iw - w, ih - h, w, h, iw, ih)
|
||||
end
|
||||
|
||||
return slices
|
||||
end
|
||||
|
||||
function Mosaic:draw ()
|
||||
local batch = self.batch
|
||||
|
||||
if not self.needsRefresh then
|
||||
Backend.draw(batch)
|
||||
return
|
||||
end
|
||||
self.needsRefresh = false
|
||||
|
||||
local x, y, w, h = self.x, self.y, self.width, self.height
|
||||
local slices = self.slices
|
||||
local sw, sh = slices.width, slices.height
|
||||
local xs = (w - sw * 2) / sw -- x scale
|
||||
local ys = (h - sh * 2) / sh -- y scale
|
||||
|
||||
batch:clear()
|
||||
|
||||
batch:add(slices.middleCenter, x + sw, y + sh, 0, xs, ys)
|
||||
batch:add(slices.topCenter, x + sw, y, 0, xs, 1)
|
||||
batch:add(slices.bottomCenter, x + sw, y + h - sh, 0, xs, 1)
|
||||
batch:add(slices.middleLeft, x, y + sh, 0, 1, ys)
|
||||
batch:add(slices.middleRight, x + w - sw, y + sh, 0, 1, ys)
|
||||
batch:add(slices.topLeft, x, y)
|
||||
batch:add(slices.topRight, x + w - sw, y)
|
||||
batch:add(slices.bottomLeft, x, y + h - sh)
|
||||
batch:add(slices.bottomRight, x + w - sw, y + h - sh)
|
||||
|
||||
Backend.draw(batch)
|
||||
end
|
||||
|
||||
return Mosaic
|
|
@ -1,57 +0,0 @@
|
|||
local Multiline = {}
|
||||
|
||||
function Multiline.wrap (font, text, limit)
|
||||
local lines = {{ width = 0 }}
|
||||
local advance = 0
|
||||
local lastSpaceAdvance = 0
|
||||
|
||||
local function append (word, space)
|
||||
local wordAdvance = font:getAdvance(word)
|
||||
local spaceAdvance = font:getAdvance(space)
|
||||
local words = lines[#lines]
|
||||
if advance + wordAdvance > limit then
|
||||
words.width = (words.width or 0) - lastSpaceAdvance
|
||||
advance = wordAdvance + spaceAdvance
|
||||
lines[#lines + 1] = { width = advance, word, space }
|
||||
else
|
||||
advance = advance + wordAdvance + spaceAdvance
|
||||
words.width = advance
|
||||
words[#words + 1] = word
|
||||
words[#words + 1] = space
|
||||
end
|
||||
lastSpaceAdvance = spaceAdvance
|
||||
end
|
||||
|
||||
local function appendFrag (frag, isFirst)
|
||||
if isFirst then
|
||||
append(frag, '')
|
||||
else
|
||||
local wordAdvance = font:getAdvance(frag)
|
||||
lines[#lines + 1] = { width = wordAdvance, frag }
|
||||
advance = wordAdvance
|
||||
end
|
||||
end
|
||||
|
||||
local leadSpace = text:match '^ +'
|
||||
|
||||
if leadSpace then
|
||||
append('', leadSpace)
|
||||
end
|
||||
|
||||
for word, space in text:gmatch '([^ ]+)( *)' do
|
||||
if word:match '\n' then
|
||||
local isFirst = true
|
||||
for frag in (word .. '\n'):gmatch '([^\n]*)\n' do
|
||||
appendFrag(frag, isFirst)
|
||||
isFirst = false
|
||||
end
|
||||
append('', space)
|
||||
else
|
||||
append(word, space)
|
||||
end
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
return Multiline
|
|
@ -1,237 +0,0 @@
|
|||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Backend = require(ROOT .. 'backend')
|
||||
local Base = require(ROOT .. 'base')
|
||||
local Event = require(ROOT .. 'event')
|
||||
local Mosaic = require(ROOT .. 'mosaic')
|
||||
local Text = Backend.Text
|
||||
|
||||
local Painter = Base:extend()
|
||||
|
||||
local imageCache = {}
|
||||
-- local sliceCache = {}
|
||||
|
||||
function Painter:constructor (widget)
|
||||
self.widget = widget
|
||||
end
|
||||
|
||||
function Painter:loadImage (path)
|
||||
if not imageCache[path] then
|
||||
imageCache[path] = Backend.Image(path)
|
||||
end
|
||||
|
||||
return imageCache[path]
|
||||
end
|
||||
|
||||
function Painter:paintSlices ()
|
||||
local widget = self.widget
|
||||
local mosaic = Mosaic.fromWidget(widget)
|
||||
if not mosaic then return end
|
||||
local x, y, w, h = widget:getRectangle(true)
|
||||
mosaic:setRectangle(x, y, w, h)
|
||||
mosaic:draw()
|
||||
end
|
||||
|
||||
function Painter:paintBackground ()
|
||||
local widget = self.widget
|
||||
if not widget.background then return end
|
||||
local x, y, w, h = widget:getRectangle(true)
|
||||
|
||||
Backend.push()
|
||||
Backend.setColor(widget.background)
|
||||
Backend.drawRectangle('fill', x, y, w, h)
|
||||
Backend.pop()
|
||||
end
|
||||
|
||||
function Painter:paintOutline ()
|
||||
local widget = self.widget
|
||||
if not widget.outline then return end
|
||||
local x, y, w, h = widget:getRectangle(true)
|
||||
|
||||
Backend.push()
|
||||
Backend.setColor(widget.outline)
|
||||
Backend.drawRectangle('line', x + 0.5, y + 0.5, w, h)
|
||||
Backend.pop()
|
||||
end
|
||||
|
||||
-- returns icon coordinates and rectangle with remaining space
|
||||
function Painter:positionIcon (x1, y1, x2, y2)
|
||||
local widget = self.widget
|
||||
if not widget.icon then
|
||||
return nil, nil, x1, y1, x2, y2
|
||||
end
|
||||
|
||||
local icon = self:loadImage(widget.icon)
|
||||
local iconWidth, iconHeight = icon:getWidth(), icon:getHeight()
|
||||
local align = widget.align or ''
|
||||
local padding = widget.padding or 0
|
||||
local x, y
|
||||
|
||||
-- horizontal alignment
|
||||
if align:find('right') then
|
||||
x = x2 - iconWidth
|
||||
x2 = x2 - iconWidth - padding
|
||||
elseif align:find('center') then
|
||||
x = x1 + (x2 - x1) / 2 - iconWidth / 2
|
||||
else -- if align:find('left') then
|
||||
x = x1
|
||||
x1 = x1 + iconWidth + padding
|
||||
end
|
||||
|
||||
-- vertical alignment
|
||||
if align:find('bottom') then
|
||||
y = y2 - iconHeight
|
||||
elseif align:find('middle') then
|
||||
y = y1 + (y2 - y1) / 2 - iconHeight / 2
|
||||
else -- if align:find('top') then
|
||||
y = y1
|
||||
end
|
||||
|
||||
return x, y, x1, y1, x2, y2
|
||||
end
|
||||
|
||||
-- returns text coordinates
|
||||
function Painter:positionText (x1, y1, x2, y2)
|
||||
local widget = self.widget
|
||||
if not widget.text or x1 >= x2 then
|
||||
return nil, nil, x1, y1, x2, y2
|
||||
end
|
||||
|
||||
local font = widget:getFont()
|
||||
local align = widget.align or ''
|
||||
local horizontal = 'left'
|
||||
|
||||
-- horizontal alignment
|
||||
if align:find 'right' then
|
||||
horizontal = 'right'
|
||||
elseif align:find 'center' then
|
||||
horizontal = 'center'
|
||||
elseif align:find 'justify' then
|
||||
horizontal = 'justify'
|
||||
end
|
||||
|
||||
if not widget.textData then
|
||||
local limit = widget.wrap and x2 - x1 or nil
|
||||
widget.textData = Text(
|
||||
font, widget.text, widget.color, horizontal, limit)
|
||||
end
|
||||
|
||||
local textHeight = widget.textData:getHeight()
|
||||
local y
|
||||
|
||||
-- vertical alignment
|
||||
if align:find('bottom') then
|
||||
y = y2 - textHeight
|
||||
elseif align:find('middle') then
|
||||
y = y2 - (y2 - y1) / 2 - textHeight / 2
|
||||
else -- if align:find('top') then
|
||||
y = y1
|
||||
end
|
||||
|
||||
return font, x1, y
|
||||
end
|
||||
|
||||
function Painter:paintIconAndText ()
|
||||
local widget = self.widget
|
||||
if not (widget.icon or widget.text) then return end
|
||||
local x, y, w, h = widget:getRectangle(true, true)
|
||||
if w < 1 or h < 1 then return end
|
||||
|
||||
-- calculate position for icon and text based on alignment and padding
|
||||
local iconX, iconY, x1, y1, x2, y2 = self:positionIcon(x, y, x + w, y + h)
|
||||
local font, textX, textY = self:positionText(x1, y1, x2, y2)
|
||||
|
||||
local icon = widget.icon and self:loadImage(widget.icon)
|
||||
local text = widget.text
|
||||
local align = widget.align or ''
|
||||
local padding = widget.padding or 0
|
||||
|
||||
-- if aligned center, icon displays above the text
|
||||
-- reposition icon and text for proper vertical alignment
|
||||
if icon and text and align:find('center') then
|
||||
local iconHeight = icon:getHeight()
|
||||
|
||||
if align:find 'middle' then
|
||||
local textHeight = widget.textData:getHeight()
|
||||
local contentHeight = textHeight + padding + iconHeight
|
||||
local offset = (h - contentHeight) / 2
|
||||
iconY = y + offset
|
||||
textY = y + offset + padding + iconHeight
|
||||
elseif align:find 'top' then
|
||||
iconY = y
|
||||
textY = y + padding + iconHeight
|
||||
else -- if align:find 'bottom'
|
||||
local textHeight = widget.textData:getHeight()
|
||||
textY = y + h - textHeight
|
||||
iconY = textY - padding - iconHeight
|
||||
end
|
||||
end
|
||||
|
||||
-- horizontal alignment for non-wrapped text
|
||||
-- TODO: handle this in Backend.Text
|
||||
if text and not widget.wrap then
|
||||
if align:find 'right' then
|
||||
textX = textX + (w - widget.textData:getWidth())
|
||||
elseif align:find 'center' then
|
||||
textX = textX + (w - widget.textData:getWidth()) / 2
|
||||
end
|
||||
end
|
||||
|
||||
Backend.push()
|
||||
|
||||
Backend.intersectScissor(x, y, w, h)
|
||||
|
||||
-- draw the icon
|
||||
if icon then
|
||||
iconX, iconY = math.floor(iconX), math.floor(iconY)
|
||||
Backend.draw(icon, iconX, iconY)
|
||||
end
|
||||
|
||||
-- draw the text
|
||||
if text and textX and textY and w > 1 then
|
||||
widget.innerHeight = textY - y + widget.textData:getHeight()
|
||||
widget.innerWidth = textX - x + widget.textData:getWidth()
|
||||
textX = math.floor(textX - (widget.scrollX or 0))
|
||||
textY = math.floor(textY - (widget.scrollY or 0))
|
||||
Backend.draw(widget.textData, textX, textY)
|
||||
end
|
||||
|
||||
Backend.pop()
|
||||
end
|
||||
|
||||
function Painter:paintChildren ()
|
||||
for i, child in ipairs(self.widget) do
|
||||
child:paint()
|
||||
end
|
||||
end
|
||||
|
||||
function Painter:paint ()
|
||||
local widget = self.widget
|
||||
local x, y, w, h = widget:getRectangle()
|
||||
|
||||
-- if the drawable area has no width or height, don't paint
|
||||
if w < 1 or h < 1 then return end
|
||||
|
||||
Event.PreDisplay:emit(widget, { target = widget }, function()
|
||||
|
||||
Backend.push()
|
||||
|
||||
if widget.parent then
|
||||
Backend.intersectScissor(x, y, w, h)
|
||||
else
|
||||
Backend.setScissor()
|
||||
end
|
||||
|
||||
self:paintBackground()
|
||||
self:paintOutline()
|
||||
self:paintSlices()
|
||||
self:paintIconAndText()
|
||||
self:paintChildren()
|
||||
|
||||
Backend.pop()
|
||||
|
||||
end)
|
||||
Event.Display:emit(widget, { target = widget })
|
||||
end
|
||||
|
||||
return Painter
|
|
@ -1,99 +0,0 @@
|
|||
--[[--
|
||||
Keyboard shortcut module.
|
||||
--]]--
|
||||
|
||||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Backend = require(ROOT .. 'backend')
|
||||
|
||||
local Shortcut = {}
|
||||
|
||||
local isMac = Backend.isMac()
|
||||
|
||||
local ALT = 1
|
||||
local CTRL = 2
|
||||
local SHIFT = 4
|
||||
local GUI = 8
|
||||
|
||||
function Shortcut.appliesToPlatform (value)
|
||||
if isMac and value:match '%f[%a]win%-'
|
||||
or not isMac and value:match '%f[%a]mac%-' then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Shortcut.expandAliases (value)
|
||||
return value
|
||||
:gsub('%f[%a]cmd%-', 'mac-gui-')
|
||||
:gsub('%f[%a]command%-', 'mac-gui-')
|
||||
:gsub('%f[%a]option%-', 'mac-alt-')
|
||||
end
|
||||
|
||||
function Shortcut.parseKeyCombo (value)
|
||||
-- expand command- and option- aliases
|
||||
value = Shortcut.expandAliases(value)
|
||||
|
||||
-- exit early if shortcut is for different platform
|
||||
if not Shortcut.appliesToPlatform(value) then return end
|
||||
|
||||
-- expand c- special modifier
|
||||
if isMac then
|
||||
value = value:gsub('%f[%a]c%-', 'gui-')
|
||||
else
|
||||
value = value:gsub('%f[%a]c%-', 'ctrl-')
|
||||
end
|
||||
|
||||
-- extract main key
|
||||
local mainKey = value:match '[^%-]*%-?$'
|
||||
|
||||
-- extract modifiers
|
||||
local alt = value:match '%f[%a]alt%-' and ALT or 0
|
||||
local ctrl = value:match '%f[%a]ctrl%-' and CTRL or 0
|
||||
local shift = value:match '%f[%a]shift%-' and SHIFT or 0
|
||||
local gui = value:match '%f[%a]gui%-' and GUI or 0
|
||||
|
||||
return mainKey, alt + ctrl + shift + gui
|
||||
end
|
||||
|
||||
function Shortcut.getModifierFlags ()
|
||||
local alt = Backend.isKeyDown('lalt', 'ralt') and ALT or 0
|
||||
local ctrl = Backend.isKeyDown('lctrl', 'rctrl') and CTRL or 0
|
||||
local shift = Backend.isKeyDown('lshift', 'rshift') and SHIFT or 0
|
||||
local gui = Backend.isKeyDown('lgui', 'rgui') and GUI or 0
|
||||
return alt + ctrl + shift + gui
|
||||
end
|
||||
|
||||
function Shortcut.stringify (shortcut)
|
||||
if type(shortcut) ~= 'table' then
|
||||
shortcut = { shortcut }
|
||||
end
|
||||
for _, value in ipairs(shortcut) do
|
||||
value = Shortcut.expandAliases(value)
|
||||
if Shortcut.appliesToPlatform(value) then
|
||||
if isMac then
|
||||
value = value
|
||||
:gsub('%f[%a]c%-', 'cmd-')
|
||||
:gsub('%f[%a]gui%-', 'cmd-')
|
||||
:gsub('%f[%a]alt%-', 'option-')
|
||||
-- Have Love backend default to DejaVuSans
|
||||
-- so we can use these instead of the above
|
||||
--[[
|
||||
:gsub('%f[%a]c%-', '⌘')
|
||||
:gsub('%f[%a]gui%-', '⌘')
|
||||
:gsub('%f[%a]alt%-', '⌥')
|
||||
:gsub('%f[%a]shift%-', '⇧')
|
||||
]]
|
||||
else
|
||||
value = value
|
||||
:gsub('%f[%a]c%-', 'ctrl-')
|
||||
:gsub('%f[%a]gui%-', 'windows-')
|
||||
end
|
||||
value = value:gsub('%f[%a]win%-', ''):gsub('%f[%a]mac%-', '')
|
||||
value = value:gsub('%f[%w].', string.upper)
|
||||
return value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Shortcut
|
|
@ -1,40 +0,0 @@
|
|||
local ROOT = (...):gsub('[^.]*$', '')
|
||||
|
||||
local Base = require(ROOT .. 'base')
|
||||
|
||||
local Style = Base:extend()
|
||||
|
||||
function Style:constructor (rules, lookupNames)
|
||||
self.rules = rules
|
||||
self.lookupNames = lookupNames
|
||||
end
|
||||
|
||||
function Style:getProperty (object, property, original)
|
||||
local value = rawget(object, property)
|
||||
if value ~= nil then return value end
|
||||
|
||||
local rules = self.rules
|
||||
original = original or object
|
||||
|
||||
for _, lookupName in ipairs(self.lookupNames) do
|
||||
local lookup = rawget(object, lookupName)
|
||||
or object.attributes and rawget(object.attributes, lookupName)
|
||||
if lookup then
|
||||
if type(lookup) ~= 'table' then
|
||||
lookup = { lookup }
|
||||
end
|
||||
for _, lookupValue in ipairs(lookup) do
|
||||
local rule = rules[lookupValue]
|
||||
if rule then
|
||||
local value = self:getProperty(rule, property, original)
|
||||
if type(value) == 'function' then
|
||||
value = value(original)
|
||||
end
|
||||
if value ~= nil then return value end
|
||||
end
|
||||
end -- lookup values
|
||||
end -- if lookup
|
||||
end -- lookup names
|
||||
end
|
||||
|
||||
return Style
|
|
@ -1,12 +0,0 @@
|
|||
local RESOURCE = (...):gsub('%.', '/') .. '/'
|
||||
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
|
||||
|
||||
return function (config)
|
||||
config = config or {}
|
||||
config.resources = config.resources or RESOURCE
|
||||
config.backColor = config.backColor or { 40, 40, 40 }
|
||||
config.lineColor = config.lineColor or { 60, 60, 60 }
|
||||
config.textColor = config.textColor or { 240, 240, 240 }
|
||||
config.highlight = config.highlight or { 0x00, 0x5c, 0x94 }
|
||||
return require(ROOT .. 'engine.alpha')(config)
|
||||
end
|
Before Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 580 B |
Before Width: | Height: | Size: 490 B |
Before Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 523 B |
Before Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 271 B |
Before Width: | Height: | Size: 305 B |
Before Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 642 B |
Before Width: | Height: | Size: 701 B |
Before Width: | Height: | Size: 595 B |
Before Width: | Height: | Size: 491 B |
Before Width: | Height: | Size: 556 B |
Before Width: | Height: | Size: 451 B |
Before Width: | Height: | Size: 768 B |
Before Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 556 B |
Before Width: | Height: | Size: 188 B |
Before Width: | Height: | Size: 186 B |
Before Width: | Height: | Size: 194 B |
Before Width: | Height: | Size: 185 B |
|
@ -1,13 +0,0 @@
|
|||
local RESOURCE = (...):gsub('%.', '/') .. '/'
|
||||
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
|
||||
|
||||
return function (config)
|
||||
local theme = require(ROOT .. 'theme.light')()
|
||||
theme.Control._defaultDimension = 44
|
||||
theme.Line._defaultDimension = 32
|
||||
theme.menu.height = 32
|
||||
theme['menu.item'].height = 32
|
||||
theme['menu.item'].padding = 8
|
||||
theme.panel.padding = 8
|
||||
return theme
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
local RESOURCE = (...):gsub('%.', '/') .. '/'
|
||||
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
|
||||
|
||||
return function (config)
|
||||
config = config or {}
|
||||
config.resources = config.resources or RESOURCE
|
||||
config.backColor = config.backColor or { 240, 240, 240 }
|
||||
config.lineColor = config.lineColor or { 220, 220, 220 }
|
||||
config.textColor = config.textColor or { 0, 0, 0 }
|
||||
config.highlight = config.highlight or { 0x19, 0xAE, 0xFF }
|
||||
return require(ROOT .. 'engine.alpha')(config)
|
||||
end
|
Before Width: | Height: | Size: 491 B |