1
0
Fork 0
mirror of https://github.com/melonDS-emu/melonDS.git synced 2025-03-06 21:00:31 +01:00
This commit is contained in:
NPO 2025-01-14 20:33:28 +00:00 committed by GitHub
commit 3113d3a3d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1333 additions and 8 deletions

View file

@ -27,7 +27,7 @@ jobs:
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
sudo apt update
sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \
qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2
qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2 liblua5.4-dev
- name: Configure
run: cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON
- name: Build
@ -69,8 +69,8 @@ jobs:
apt update
apt -y full-upgrade
apt -y install git {gcc-12,g++-12}-aarch64-linux-gnu cmake ninja-build extra-cmake-modules \
{libsdl2,qt6-{base,base-private,multimedia},libqt6svg6,libarchive,libzstd,libenet}-dev:arm64 \
pkg-config dpkg-dev
{libsdl2,qt6-{base,base-private,multimedia},libqt6svg6,libarchive,libzstd,libenet,liblua5.4}-dev:arm64 \
pkg-config dpkg-dev
- name: Check out source
uses: actions/checkout@v4
- name: Configure

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
build*/
debug*/
bin
obj
*.depend

View file

@ -35,6 +35,7 @@
qt6.qtbase
qt6.qtmultimedia
SDL2
lua
zstd
libarchive
libGL

View file

@ -57,6 +57,8 @@ set(SOURCES_QT_SDL
LANDialog.cpp
NetplayDialog.cpp
LuaMain.cpp
)
option(USE_QT6 "Use Qt 6 instead of Qt 5" ON)
@ -75,6 +77,7 @@ set(CMAKE_AUTORCC ON)
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(Lua REQUIRED)
if (BUILD_STATIC)
list(APPEND PKG_CONFIG_EXECUTABLE "--static")
@ -83,6 +86,7 @@ endif()
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive)
pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd)
## pkg_search_module(Lua REQUIRED IMPORTED_TARGET lua)
fix_interface_includes(PkgConfig::SDL2 PkgConfig::LibArchive)
@ -173,6 +177,7 @@ endif()
target_include_directories(melonDS PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(melonDS PRIVATE ${LUA_INCLUDE_DIR})
if (USE_QT6)
target_include_directories(melonDS PUBLIC ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
@ -180,7 +185,7 @@ else()
target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_link_libraries(melonDS PRIVATE core)
target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd)
target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd ${LUA_LIBRARIES})
target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS})
if (WIN32)

View file

@ -146,6 +146,8 @@ public:
void setJoystick(int id);
int getJoystickID() { return joystickID; }
SDL_Joystick* getJoystick() { return joystick; }
std::vector<int> heldKeys;
std::vector<int> keyStrokes;
void touchScreen(int x, int y);
void releaseScreen();
@ -285,6 +287,8 @@ public:
bool fastForwardToggled;
bool slowmoToggled;
bool doAudioSync;
melonDS::u32 getInputMask(){return inputMask;}
private:
std::unique_ptr<melonDS::Savestate> backupState;

View file

@ -225,8 +225,10 @@ int getEventKeyVal(QKeyEvent* event)
void EmuInstance::onKeyPress(QKeyEvent* event)
{
heldKeys.push_back(event->key());
int keyHK = getEventKeyVal(event);
int keyKP = keyHK;
keyStrokes.push_back(keyHK);
if (event->modifiers() != Qt::KeypadModifier)
keyKP &= ~event->modifiers();
@ -241,6 +243,7 @@ void EmuInstance::onKeyPress(QKeyEvent* event)
void EmuInstance::onKeyRelease(QKeyEvent* event)
{
heldKeys.erase(std::find(heldKeys.begin(),heldKeys.end(),event->key()));
int keyHK = getEventKeyVal(event);
int keyKP = keyHK;
if (event->modifiers() != Qt::KeypadModifier)

View file

@ -460,6 +460,9 @@ void EmuThread::run()
}
handleMessages();
//Lua Script Stuff (-for now happens at the end of each frame regardless of emuStatus)
emit signalLuaUpdate();
}
}

View file

@ -156,6 +156,8 @@ signals:
void syncVolumeLevel();
void signalLuaUpdate();
private:
void handleMessages();

View file

@ -0,0 +1,633 @@
#include "LuaMain.h"
#include <filesystem>
#include <QDir>
#include <QFileDialog>
#include <QPushButton>
#include <QGuiApplication>
#include <QScrollBar>
#include <QPainter>
#include "types.h"
#include "NDS.h"
#include <SDL_joystick.h>
#include <NDS_Header.h>
#include "main.h"
LuaBundle::LuaBundle(LuaConsoleDialog* dialog, EmuInstance* inst)
{
emuInstance = inst;
emuThread = emuInstance->getEmuThread();
luaDialog = dialog;
overlays = new std::vector<OverlayCanvas>;
imageHash = new QHash<QString, QImage>;
}
LuaConsoleDialog::LuaConsoleDialog(QWidget* parent) : QDialog(parent)
{
QWidget* w = parent;
MainWindow* mainWindow;
for (;;) //copied from ScreenPanel in Screen.cpp
{
mainWindow = qobject_cast<MainWindow*>(w);
if (mainWindow) break;
w = w->parentWidget();
if (!w) break;
}
bundle = new LuaBundle(this,mainWindow->getEmuInstance());
console = new LuaConsole(this);
console->setGeometry(0,20,302,80);
bar = console->verticalScrollBar();
buttonPausePlay = new QPushButton("Pause/UnPause",this);
buttonPausePlay->setGeometry(0,0,100,20);
buttonStartStop = new QPushButton("Stop",this);
buttonStartStop->setGeometry(101,0,100,20);
buttonOpenScript = new QPushButton("OpenLuaFile",this);
buttonOpenScript->setGeometry(202,0,100,20);
connect(buttonOpenScript,&QPushButton::clicked,this,&LuaConsoleDialog::onOpenScript);
connect(buttonStartStop,&QPushButton::clicked,this,&LuaConsoleDialog::onStop);
connect(buttonPausePlay,&QPushButton::clicked,this,&LuaConsoleDialog::onPausePlay);
this->setWindowTitle("Lua Script");
}
void LuaConsoleDialog::closeEvent(QCloseEvent *event)
{
onStop();
bundle->overlays->clear();
flagClosed = true;
event->accept();
}
void LuaConsoleDialog::onOpenScript()
{
QFileInfo file = QFileInfo(QFileDialog::getOpenFileName(this, "Load Lua Script",QDir::currentPath()));
if (!file.exists()) return;
currentScript = file;
bundle->flagNewLua = true;
}
LuaConsole::LuaConsole(QWidget* parent)
{
this->setParent(parent);
}
void LuaConsole::onGetText(const QString& string)
{
this->appendPlainText(string);
QScrollBar* bar = verticalScrollBar();
bar->setValue(bar->maximum());
}
void LuaBundle::printText(QString string)
{
this->luaDialog->console->onGetText(string);
}
void LuaConsoleDialog::onLuaSaveState(QString string)
{
emit signalLuaSaveState(string);
}
void LuaConsoleDialog::onLuaLoadState(QString string)
{
emit signalLuaLoadState(string);
}
void LuaConsole::onClear()
{
this->clear();
}
LuaFunction::LuaFunction(luaFunctionPointer cf,const char* n,std::vector<LuaFunction*>* container)
{
this->cfunction = cf;
this->name = n;
container->push_back(this);
}
static_assert(sizeof(LuaBundle*) <= LUA_EXTRASPACE,"LUA_EXTRASPACE too small");
LuaBundle* get_bundle(lua_State * L)
{
LuaBundle* pBundle;
std::memcpy(&pBundle, lua_getextraspace(L), sizeof(LuaBundle*));
return pBundle;
}
#define MELON_LUA_HOOK_INSTRUCTION_COUNT 50 //number of vm instructions between hook calls
void luaHookFunction(lua_State* L, lua_Debug *arg)
{
LuaBundle* bundle = get_bundle(L);
if (bundle->flagStop and (arg->event == LUA_HOOKCOUNT))
luaL_error(L, "Force Stopped");
}
std::vector<LuaFunction*> definedLuaFunctions;//List of all defined lua functions
void LuaBundle::createLuaState()
{
if (!flagNewLua) return;
overlays->clear();
flagNewLua = false;
luaState = nullptr;
QByteArray fileName = luaDialog->currentScript.fileName().toLocal8Bit();
QString filedir = luaDialog->currentScript.dir().path();
lua_State* L = luaL_newstate();
LuaBundle* pBundle = this;
std::memcpy(lua_getextraspace(L), &pBundle, sizeof(LuaBundle*)); //Write a pointer to this LuaBundle into the extra space of the new lua_State
luaL_openlibs(L);
for (LuaFunction* function : definedLuaFunctions)
lua_register(L,function->name,function->cfunction);
QDir::setCurrent(filedir);
lua_sethook(L,&luaHookFunction,LUA_MASKCOUNT,MELON_LUA_HOOK_INSTRUCTION_COUNT);
if (luaL_dofile(L,fileName.data())==LUA_OK)
{
luaState = L;
}
else //Error loading script
{
printText(lua_tostring(L,-1));
}
}
void LuaConsoleDialog::onStop()
{
if (bundle->getLuaState())
bundle->flagStop = true;
}
void LuaConsoleDialog::onPausePlay()
{
bundle->flagPause = !bundle->flagPause;
}
void LuaConsoleDialog::onLuaUpdate()
{
bundle->createLuaState();
bundle->luaUpdate();
}
//Gets Called once a frame
void LuaBundle::luaUpdate()
{
if (!luaState || flagPause) return;
if (lua_getglobal(luaState,"_Update")!=LUA_TFUNCTION)
{
printText("No \"_Update\" Function found, pausing script...");
flagPause = true;
return;
}
if (lua_pcall(luaState,0,0,0)!=0)
{
//Handel Errors
printText(lua_tostring(luaState,-1));
luaState = nullptr;
}
}
OverlayCanvas::OverlayCanvas(int x,int y,int width,int height,LuaCanvasTarget t)
{
target = t;
buffer1 = new QImage(width,height,QImage::Format_ARGB32_Premultiplied);
buffer2 = new QImage(width,height,QImage::Format_ARGB32_Premultiplied);
buffer1->fill(0xffffff00); //initializes buffer with yellow pixels (probably should change this to transparent black pixels at some point...)
buffer2->fill(0xffffff00);
imageBuffer = buffer1;
displayBuffer = buffer2;
rectangle = QRect(x,y,width,height);
flipped = false;
GLTextureLoaded = false;
}
void OverlayCanvas::flip()
{
if (imageBuffer == buffer1)
{
imageBuffer = buffer2;
displayBuffer = buffer1;
}
else
{
imageBuffer = buffer1;
displayBuffer = buffer2;
}
flipped = true;
}
void LuaBundle::luaResetOSD()
{
for (auto lo = overlays->begin(); lo != overlays->end();)
{
OverlayCanvas& overlay = *lo;
overlay.GLTextureLoaded = false;
lo++;
}
}
//TODO: Organize lua functions into different named tables similar to bizhawk.
/*--------------------------------------------------------------------------------------------------
Start of lua function definitions
--------------------------------------------------------------------------------------------------*/
namespace luaDefinitions
{
#define AddLuaFunction(functPointer,name)LuaFunction name(functPointer,#name,&definedLuaFunctions)
int lua_MelonPrint(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
const char* string = luaL_checkstring(L,1);
bundle->printText((QString)string);
return 0;
}
AddLuaFunction(lua_MelonPrint,print);
int lua_MelonClear(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
bundle->getluaDialog()->console->clear();
return 0;
}
AddLuaFunction(lua_MelonClear,MelonClear);
enum ramInfo_ByteType
{
ramInfo_OneByte = 1,
ramInfo_TwoBytes = 2,
ramInfo_FourBytes = 4
};
melonDS::u32 GetMainRAMValueU(const melonDS::u32& addr, const ramInfo_ByteType& byteType,LuaBundle* bundle)
{
melonDS::NDS* nds = bundle->getEmuInstance()->getNDS();
switch (byteType)
{
case ramInfo_OneByte:
return *(melonDS::u8*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_TwoBytes:
return *(melonDS::u16*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_FourBytes:
return *(melonDS::u32*)(nds->MainRAM + (addr&nds->MainRAMMask));
default:
return 0;
}
}
melonDS::s32 GetMainRAMValueS(const melonDS::u32& addr, const ramInfo_ByteType& byteType,LuaBundle* bundle)
{
melonDS::NDS* nds = bundle->getEmuInstance()->getNDS();
switch (byteType)
{
case ramInfo_OneByte:
return *(melonDS::s8*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_TwoBytes:
return *(melonDS::s16*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_FourBytes:
return *(melonDS::s32*)(nds->MainRAM + (addr&nds->MainRAMMask));
default:
return 0;
}
}
int Lua_ReadDatau(lua_State* L,ramInfo_ByteType byteType)
{
LuaBundle* bundle = get_bundle(L);
melonDS::u32 address = luaL_checkinteger(L,1);
melonDS::u32 value = GetMainRAMValueU(address,byteType,bundle);
lua_pushinteger(L, value);
return 1;
}
int Lua_ReadDatas(lua_State* L,ramInfo_ByteType byteType)
{
LuaBundle* bundle = get_bundle(L);
melonDS::u32 address = luaL_checkinteger(L,1);
melonDS::s32 value = GetMainRAMValueS(address,byteType,bundle);
lua_pushinteger(L, value);
return 1;
}
int Lua_Readu8(lua_State* L)
{
return Lua_ReadDatau(L,ramInfo_OneByte);
}
AddLuaFunction(Lua_Readu8,Readu8);
int Lua_Readu16(lua_State* L)
{
return Lua_ReadDatau(L,ramInfo_TwoBytes);
}
AddLuaFunction(Lua_Readu16,Readu16);
int Lua_Readu32(lua_State* L)
{
return Lua_ReadDatau(L,ramInfo_FourBytes);
}
AddLuaFunction(Lua_Readu32,Readu32);
int Lua_Reads8(lua_State* L)
{
return Lua_ReadDatas(L,ramInfo_OneByte);
}
AddLuaFunction(Lua_Reads8,Reads8);
int Lua_Reads16(lua_State* L)
{
return Lua_ReadDatas(L,ramInfo_TwoBytes);
}
AddLuaFunction(Lua_Reads16,Reads16);
int Lua_Reads32(lua_State* L)
{
return Lua_ReadDatas(L,ramInfo_FourBytes);
}
AddLuaFunction(Lua_Reads32,Reads32);
int Lua_NDSTapDown(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
melonDS::NDS* nds = bundle->getEmuInstance()->getNDS();
int x = luaL_checkinteger(L,1);
int y = luaL_checkinteger(L,2);
nds->TouchScreen(x,y);
return 0;
}
AddLuaFunction(Lua_NDSTapDown,NDSTapDown);
int Lua_NDSTapUp(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
melonDS::NDS* nds = bundle->getEmuInstance()->getNDS();
nds->ReleaseScreen();
return 0;
}
AddLuaFunction(Lua_NDSTapUp,NDSTapUp);
int Lua_StateSave(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
const char* filename = luaL_checkstring(L,1);
bundle->getluaDialog()->onLuaSaveState((QString)filename);
return 0;
}
AddLuaFunction(Lua_StateSave,StateSave);
int Lua_StateLoad(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
const char* filename = luaL_checkstring(L,1);
bundle->getluaDialog()->onLuaLoadState((QString)filename);
return 0;
}
AddLuaFunction(Lua_StateLoad,StateLoad);
int Lua_getMouse(lua_State* L)
{
Qt::MouseButtons btns = QGuiApplication::mouseButtons();
LuaBundle* bundle = get_bundle(L);
QPoint pos = bundle->getEmuInstance()->getMainWindow()->panel->mapFromGlobal(QCursor::pos(QGuiApplication::primaryScreen()));
const char* keys[6] = {"Left","Middle","Right","XButton1","XButton2","Wheel"};
bool vals[6] =
{
btns.testFlag(Qt::LeftButton),
btns.testFlag(Qt::MiddleButton),
btns.testFlag(Qt::RightButton),
btns.testFlag(Qt::XButton1),
btns.testFlag(Qt::XButton2),
false //TODO: add mouse wheel support
};
lua_createtable(L, 0, 8);
lua_pushinteger(L, pos.x());
lua_setfield(L, -2, "X");
lua_pushinteger(L, pos.y());
lua_setfield(L, -2, "Y");
for (int i=0;i<6;i++)
{
lua_pushboolean(L,vals[i]);
lua_setfield(L,-2,keys[i]);
}
return 1;//returns table describing the current pos and state of the mouse
}
AddLuaFunction(Lua_getMouse,GetMouse);
int Lua_HeldKeys(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
lua_createtable(L,0,bundle->getEmuInstance()->heldKeys.size());
for (int key : bundle->getEmuInstance()->heldKeys)
{
lua_pushboolean(L,true);
lua_seti(L,-2,key);
}
return 1;//returns table of currently held keys.
}
AddLuaFunction(Lua_HeldKeys,HeldKeys);
/*--------------------------------------------------------------------------------------------------
Front-end lua function definitions
--------------------------------------------------------------------------------------------------*/
//TODO: Lua Colors
//MakeCanvas(int x,int y,int width,int height,[int target,topScreen=0,bottomScreen=1,OSD(default)>=2)],[bool active = true])
int Lua_MakeCanvas(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
int w = luaL_checknumber(L,3);
int h = luaL_checknumber(L,4);
int t = luaL_optinteger(L,5,2);
bool a = 0 != luaL_optinteger(L,6,1);
OverlayCanvas canvas(x,y,w,h,(LuaCanvasTarget)t);
canvas.isActive = a;
lua_pushinteger(L,bundle->overlays->size());
bundle->overlays->push_back(canvas);
return 1; //returns index of the new overlay
}
AddLuaFunction(Lua_MakeCanvas,MakeCanvas);
int Lua_SetCanvas(lua_State* L) //SetCanvas(int index)
{
LuaBundle* bundle = get_bundle(L);
int index = luaL_checknumber(L,1);
bundle->luaCanvas = &bundle->overlays->at(index);
return 0;
}
AddLuaFunction(Lua_SetCanvas,SetCanvas);
int Lua_ClearOverlay(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
bundle->luaCanvas->imageBuffer->fill(0x00000000);
return 0;
}
AddLuaFunction(Lua_ClearOverlay,ClearOverlay);
int Lua_Flip(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
bundle->luaCanvas->flip();
return 0;
}
AddLuaFunction(Lua_Flip,Flip);
//Text(int x, int y, string message, [u32 color = 'black'], [int fontsize = 9], [string fontfamily = Helvetica])
int Lua_text(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
const char* message = luaL_checklstring(L,3,NULL);
melonDS::u32 color = luaL_optnumber(L,4,0x00000000);
const char* FontFamily = luaL_optlstring(L,6,"Helvetica",NULL);
int size = luaL_optnumber(L,5,9);
QPainter painter(bundle->luaCanvas->imageBuffer);
QFont font(FontFamily,size,0,false);
//font.setStyleStrategy(QFont::NoAntialias);
//font.setLetterSpacing(QFont::AbsoluteSpacing,-1);
painter.setFont(font);
painter.setPen(color);
painter.drawText(x,y,message);
return 0;
}
AddLuaFunction(Lua_text,Text);
int Lua_line(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
int x1 = luaL_checknumber(L,1);
int y1 = luaL_checknumber(L,2);
int x2 = luaL_checknumber(L,3);
int y2 = luaL_checknumber(L,4);
melonDS::u32 color = luaL_checknumber(L,5);
QPainter painter(bundle->luaCanvas->imageBuffer);
painter.setPen(color);
painter.drawLine(x1,y1,x2,y2);
return 0;
}
AddLuaFunction(Lua_line,Line);
int Lua_rect(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
melonDS::u32 color = luaL_checknumber(L,5);
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
int width = luaL_checknumber(L,3);
int height = luaL_checknumber(L,4);
QPainter painter(bundle->luaCanvas->imageBuffer);
painter.setPen(color);
painter.drawRect(x,y,width,height);
return 0;
}
AddLuaFunction(Lua_rect,Rect);
int Lua_fillrect(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
melonDS::u32 color = luaL_checknumber(L,5);
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
int width = luaL_checknumber(L,3);
int height = luaL_checknumber(L,4);
QPainter painter(bundle->luaCanvas->imageBuffer);
painter.setPen(color);
painter.fillRect(x,y,width,height,color);
return 0;
}
AddLuaFunction(Lua_fillrect,FillRect);
int Lua_Ellipse(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
melonDS::u32 color = luaL_checknumber(L,5);
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
int width = luaL_checknumber(L,3);
int height = luaL_checknumber(L,4);
QPainter painter(bundle->luaCanvas->imageBuffer);
painter.setPen(color);
painter.drawEllipse(x,y,width,height);
return 0;
}
AddLuaFunction(Lua_Ellipse,Ellipse);
int Lua_keystrokes(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
lua_createtable(L,0,bundle->getEmuInstance()->keyStrokes.size());
for (int i = 0; i<bundle->getEmuInstance()->keyStrokes.size(); i++)
{
lua_pushinteger(L,bundle->getEmuInstance()->keyStrokes.at(i));
lua_seti(L,-2,i);
}
bundle->getEmuInstance()->keyStrokes.clear();
return 1;
}
AddLuaFunction(Lua_keystrokes,Keys);
//DrawImage(string path, int x, int y,[int source x=0], [int source y=0], [int source w=-1],[int source h=-1])
int Lua_drawImage(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
const char* path = luaL_checklstring(L,1,NULL);
int x = luaL_checkinteger(L,2);
int y = luaL_checkinteger(L,3);
int sx = luaL_optinteger(L,4,0);
int sy = luaL_optinteger(L,5,0);
int sw = luaL_optinteger(L,6,-1);
int sh = luaL_optinteger(L,7,-1);
QPainter painter(bundle->luaCanvas->imageBuffer);
QImage image;
if (bundle->imageHash->contains(path))
{
image=(*bundle->imageHash)[path];
}
else
{
image=QImage((QString)path);
(*bundle->imageHash)[path] = image;
}
painter.drawImage(x,y,image,sx,sy,sw,sh);
return 0;
}
AddLuaFunction(Lua_drawImage,DrawImage);
int Lua_clearImageHash(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
bundle->imageHash->clear();
return 0;
}
AddLuaFunction(Lua_clearImageHash,ClearHash);
int Lua_getJoy(lua_State* L)
{
LuaBundle* bundle = get_bundle(L);
melonDS::u32 buttonMask=bundle->getEmuInstance()->getInputMask();//current button state.
const char* keys[12] =
{//Buttons in order of mask.
"A","B","Select","Start",
"Right","Left","Up","Down",
"R","L","X","Y"
};
lua_createtable(L, 0, 12);
for (melonDS::u32 i=0;i<12;i++)
{
lua_pushboolean(L,0 >= (buttonMask&(1<<i)));
lua_setfield(L,-2,keys[i]);
}
return 1;
}
AddLuaFunction(Lua_getJoy,GetJoy);
}

View file

@ -0,0 +1,116 @@
#ifndef LUAMAIN_H
#define LUAMAIN_H
#include <QDialog>
#include <QPlainTextEdit>
#include <QFileInfo>
#include <lua.hpp>
#include "EmuInstance.h"
class LuaConsole: public QPlainTextEdit
{
Q_OBJECT
public:
LuaConsole(QWidget* parent=nullptr);
public slots:
void onGetText(const QString& string);
void onClear();
};
class LuaBundle;
class LuaConsoleDialog: public QDialog
{
Q_OBJECT
public:
LuaConsoleDialog(QWidget* parent);
LuaBundle* getLuaBundle(){return bundle;};
LuaConsole* console;
QFileInfo currentScript;
QPushButton* buttonOpenScript;
QPushButton* buttonStartStop;
QPushButton* buttonPausePlay;
QScrollBar* bar;
bool flagClosed;
void onLuaSaveState(QString);
void onLuaLoadState(QString);
protected:
void closeEvent(QCloseEvent *event) override;
LuaBundle* bundle;
signals:
void signalNewLua();
void signalClosing();
void signalLuaSaveState(const QString&);
void signalLuaLoadState(const QString&);
public slots:
//void onStartStop();
void onOpenScript();
void onStop();
void onPausePlay();
void onLuaUpdate();
};
//Based on ScreenLayout::GetScreenTransforms
enum LuaCanvasTarget
{
canvasTarget_TopScreen = 0,
canvasTarget_BottomScreen = 1,
canvasTarget_OSD = 2 //Used for drawing to OSD / non-screen target
};
struct OverlayCanvas
{
QImage* imageBuffer; // buffer edited by luascript
QImage* displayBuffer; //buffer displayed on screen
QImage* buffer1;
QImage* buffer2;
QRect rectangle;
LuaCanvasTarget target = canvasTarget_OSD;
bool isActive = true; // only active overlays are drawn
unsigned int GLTexture; // used by GL rendering
bool GLTextureLoaded;
OverlayCanvas(int x, int y,int w, int h, LuaCanvasTarget target = canvasTarget_OSD);
void flip();//used to swap buffers / update canvas
bool flipped; //used to signal update to graphics.
};
typedef int(*luaFunctionPointer)(lua_State*);
struct LuaFunction
{
luaFunctionPointer cfunction;
const char* name;
LuaFunction(luaFunctionPointer,const char*,std::vector<LuaFunction*>*);
};
class LuaBundle
{
EmuInstance* emuInstance;
EmuThread* emuThread;
LuaConsoleDialog* luaDialog;
lua_State* luaState = nullptr;
int basketID;
void luaResetOSD();
void luaPrint(QString string);
void luaClearConsole();
OverlayCanvas* currentCanvas = nullptr;
friend class LuaConsolDialog;
public:
LuaBundle(LuaConsoleDialog* dialog,EmuInstance* inst);
lua_State* getLuaState(){return luaState;};
EmuThread* getEmuThread(){return emuThread;};
EmuInstance* getEmuInstance(){return emuInstance;};
LuaConsoleDialog* getluaDialog(){return luaDialog;};
void printText(QString string);
void createLuaState();
void luaUpdate();
bool flagPause = false;
bool flagStop = false;
bool flagNewLua = false;
OverlayCanvas* luaCanvas = nullptr;
std::vector<OverlayCanvas>* overlays;
QHash<QString, QImage>* imageHash;
};
#endif

View file

@ -64,4 +64,47 @@ void main()
}
)";
#endif // OSD_SHADERS_H
//Fragment Shader for overlay copied from melonPrimeDS project.
const char* kScreenFS_overlay = R"(#version 140
uniform sampler2D OverlayTex;
smooth in vec2 fTexcoord;
uniform vec2 uOverlayPos;
uniform vec2 uOverlaySize;
uniform int uOverlayScreenType;
out vec4 oColor;
void main()
{
const vec2 dsSize = vec2(256.0, 193.0); // +1 on y for pixel gap
vec2 uv = fTexcoord * vec2(1.0, 2.0);
if (uOverlayScreenType < 1)
{
// top screen
uv -= uOverlayPos / dsSize;
uv *= dsSize / uOverlaySize;
} else {
// bottom screen
uv -= vec2(0.0, 1.0);
uv -= (uOverlayPos + vec2(0.0, 1.0)) / dsSize;
uv *= dsSize / uOverlaySize;
}
vec4 pixel = texture(OverlayTex, uv);
pixel.rgb *= pixel.a;
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)
{
oColor = vec4(0.0, 0.0, 0.0, 0.0);
} else {
oColor = pixel.bgra;
}
}
)";
#endif // OSD_SHADERS_H

View file

@ -46,8 +46,11 @@
#include "main_shaders.h"
#include "OSD_shaders.h"
#include "font.h"
#include "version.h"
#include "LuaMain.h"
using namespace melonDS;
@ -772,13 +775,29 @@ void ScreenPanelNative::setupScreenLayout()
}
}
// From ScreenLayout::GetScreenTransforms
// TopScreen = 0
// BottomScreen = 1
// OSD / non-screen target = 2 (used by Lua stuff)
void ScreenPanelNative::drawOverlays(QPainter* painter,int type)
{
LuaConsoleDialog* dialog = mainWindow->getLuaDialog();
if (!dialog)
return;
LuaBundle* lua = dialog->getLuaBundle();
for (OverlayCanvas& overlay : *lua->overlays)
if ((overlay.target == type) && overlay.isActive)
painter->drawImage(overlay.rectangle,*overlay.displayBuffer);
}
void ScreenPanelNative::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
// fill background
painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
auto emuThread = emuInstance->getEmuThread();
if (emuThread->emuIsActive())
@ -799,12 +818,15 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event)
memcpy(screen[1].scanLine(0), nds->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4);
emuThread->frontBufferLock.unlock();
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
QRect screenrc(0, 0, 256, 192);
for (int i = 0; i < numScreens; i++)
{
painter.setTransform(screenTrans[i]);
painter.drawImage(screenrc, screen[screenKind[i]]);
if (osdEnabled)
drawOverlays(&painter,screenKind[i]);
}
emuInstance->renderLock.unlock();
}
@ -832,6 +854,8 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event)
painter.resetTransform();
drawOverlays(&painter,canvasTarget_OSD);
for (auto it = osdItems.begin(); it != osdItems.end(); )
{
OSDItem& item = *it;
@ -1012,7 +1036,23 @@ void ScreenPanelGL::initOpenGL()
logoTexture = tex;
transferLayout();
OpenGL::CompileVertexFragmentProgram(overlayShader,
kScreenVS,kScreenFS_overlay,
"OverlayShader",
{{"vPosition", 0}, {"vTexcoord", 1}},
{{"oColor", 0}});
glUseProgram(overlayShader);
overlayScreenSizeULoc = glGetUniformLocation(overlayShader, "uScreenSize");
overlayTransformULoc = glGetUniformLocation(overlayShader, "uTransform");
overlayPosULoc = glGetUniformLocation(overlayShader, "uOverlayPos");
overlaySizeULoc = glGetUniformLocation(overlayShader, "uOverlaySize");
overlayScreenTypeULoc = glGetUniformLocation(overlayShader, "uOverlayScreenType");
glInited = true;
}
void ScreenPanelGL::deinitOpenGL()
@ -1041,7 +1081,20 @@ void ScreenPanelGL::deinitOpenGL()
glDeleteProgram(osdShader);
glDeleteProgram(overlayShader);
if (mainWindow->getLuaDialog())
{
std::vector<OverlayCanvas>* overlays = mainWindow->getLuaDialog()->getLuaBundle()->overlays;
for (OverlayCanvas& overlay : *overlays)
{
if (overlay.GLTextureLoaded)
{
glDeleteTextures(1,&overlay.GLTexture);
overlay.GLTextureLoaded=false;
}
}
}
glContext->DoneCurrent();
lastScreenWidth = lastScreenHeight = -1;
@ -1083,6 +1136,50 @@ void ScreenPanelGL::osdDeleteItem(OSDItem* item)
ScreenPanel::osdDeleteItem(item);
}
void ScreenPanelGL::drawOverlays(int screenType,int screen)
{
if (!mainWindow->getLuaDialog())
return;
std::vector<OverlayCanvas>* overlays = mainWindow->getLuaDialog()->getLuaBundle()->overlays;
for (OverlayCanvas& overlay : *overlays)
{
if (!overlay.isActive || overlay.target != screenType)
continue;
if (!overlay.GLTextureLoaded)//Load texture if none loaded
{
glGenTextures(1,&overlay.GLTexture);
glBindTexture(GL_TEXTURE_2D, overlay.GLTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, overlay.rectangle.width(), overlay.rectangle.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, overlay.displayBuffer->bits());
overlay.GLTextureLoaded = true;
}
if (overlay.flipped) //Only update texture if needed
{
glBindTexture(GL_TEXTURE_2D, overlay.GLTexture);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,overlay.rectangle.width(),overlay.rectangle.height(),GL_RGBA,GL_UNSIGNED_BYTE,overlay.displayBuffer->bits());
overlay.flipped = false;
}
glBindTexture(GL_TEXTURE_2D, overlay.GLTexture);
glUniform2f(overlayPosULoc,overlay.rectangle.left(),overlay.rectangle.top());
glUniform2f(overlaySizeULoc,overlay.rectangle.width(),overlay.rectangle.height());
if(screenType == canvasTarget_OSD) // OSD gets drawn differently then top or bottom screen target
{
glDrawArrays(GL_TRIANGLES, 0, 2*3);
continue;
}
glUniform1i(overlayScreenTypeULoc, screenType);
glUniformMatrix2x3fv(overlayTransformULoc, 1, GL_TRUE,screenMatrix[screen]);
glDrawArrays(GL_TRIANGLES,screenType == 0 ? 0 : 2*3, 2*3);
}
}
void ScreenPanelGL::drawScreenGL()
{
if (!glContext) return;
@ -1202,6 +1299,29 @@ void ScreenPanelGL::drawScreenGL()
if (osdEnabled)
{
glUseProgram(overlayShader);
//Need to blend this layer onto the screen layer!
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glUniform2f(overlayScreenSizeULoc, w / factor, h / factor);
screenSettingsLock.lock();
glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
glBindVertexArray(screenVertexArray);
for(int i = 0;i<numScreens;i++){
drawOverlays(screenKind[i],i);
}
screenSettingsLock.unlock();
osdMutex.lock();
u32 y = kOSDMargin;
@ -1220,6 +1340,8 @@ void ScreenPanelGL::drawScreenGL()
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
drawOverlays(canvasTarget_OSD,0);
for (auto it = osdItems.begin(); it != osdItems.end(); )
{
OSDItem& item = *it;

View file

@ -29,6 +29,7 @@
#include <QScreen>
#include <QCloseEvent>
#include <QTimer>
#include <QPainter>
#include "glad/glad.h"
#include "ScreenLayout.h"
@ -166,6 +167,7 @@ protected:
private:
void setupScreenLayout() override;
void drawOverlays(QPainter* painter,int type);
QImage screen[2];
QTransform screenTrans[kMaxScreenTransforms];
@ -204,6 +206,7 @@ protected:
private:
void setupScreenLayout() override;
void drawOverlays(int type,int screen);
std::unique_ptr<GL::Context> glContext;
bool glInited;
@ -230,6 +233,10 @@ private:
void osdRenderItem(OSDItem* item) override;
void osdDeleteItem(OSDItem* item) override;
GLuint overlayShader;
GLuint overlayScreenSizeULoc, overlayTransformULoc;
GLuint overlayPosULoc, overlaySizeULoc, overlayScreenTypeULoc;
};
#endif // SCREEN_H

View file

@ -84,6 +84,8 @@
#include "Window.h"
#include "AboutDialog.h"
#include "LuaMain.h"
using namespace melonDS;
@ -415,6 +417,11 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
actDateTime = menu->addAction("Date and time");
connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime);
menu->addSeparator();
actLuaScript = menu->addAction("Lua Script");
connect(actLuaScript,&QAction::triggered,this,&MainWindow::onOpenLuaScript);
menu->addSeparator();
actEnableCheats = menu->addAction("Enable cheats");
@ -1524,6 +1531,13 @@ void MainWindow::onEjectGBACart()
updateCartInserted(true);
}
void MainWindow::onLuaSaveState(const QString& filename)
{
emuThread->emuPause();
emuInstance->saveState(filename.toStdString());
emuThread->emuUnpause();
}
void MainWindow::onSaveState()
{
int slot = ((QAction*)sender())->data().toInt();
@ -1559,6 +1573,13 @@ void MainWindow::onSaveState()
}
}
void MainWindow::onLuaLoadState(const QString& filename)
{
emuThread->emuPause();
emuInstance->loadState(filename.toStdString());
emuThread->emuUnpause();
}
void MainWindow::onLoadState()
{
int slot = ((QAction*)sender())->data().toInt();
@ -1700,6 +1721,22 @@ void MainWindow::onOpenPowerManagement()
PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this);
}
void MainWindow::onOpenLuaScript()
{
if (luaDialog && luaDialog->flagClosed)
{
delete luaDialog;
luaDialog = nullptr;
}
if (luaDialog)
return;
luaDialog = new LuaConsoleDialog(this);
luaDialog->show();
connect(emuThread,&EmuThread::signalLuaUpdate,luaDialog,&LuaConsoleDialog::onLuaUpdate);
connect(luaDialog,&LuaConsoleDialog::signalLuaSaveState,this,&MainWindow::onLuaSaveState);
connect(luaDialog,&LuaConsoleDialog::signalLuaLoadState,this,&MainWindow::onLuaLoadState);
}
void MainWindow::onEnableCheats(bool checked)
{
localCfg.SetBool("EnableCheats", checked);

View file

@ -40,6 +40,7 @@
class EmuInstance;
class EmuThread;
class LuaConsoleDialog;
const int kMaxRecentROMs = 10;
@ -110,6 +111,9 @@ public:
EmuInstance* getEmuInstance() { return emuInstance; }
Config::Table& getWindowConfig() { return windowCfg; }
LuaConsoleDialog* getLuaDialog() {return luaDialog;}
int getWindowID() { return windowID; }
bool winHasMenu() { return hasMenu; }
@ -166,7 +170,9 @@ private slots:
void onInsertGBACart();
void onInsertGBAAddon();
void onEjectGBACart();
void onLuaSaveState(const QString& filename);
void onSaveState();
void onLuaLoadState(const QString& filename);
void onLoadState();
void onUndoStateLoad();
void onImportSavefile();
@ -177,6 +183,7 @@ private slots:
void onStop();
void onFrameStep();
void onOpenPowerManagement();
void onOpenLuaScript();
void onOpenDateTime();
void onEnableCheats(bool checked);
void onSetupCheats();
@ -272,6 +279,7 @@ private:
EmuInstance* emuInstance;
EmuThread* emuThread;
LuaConsoleDialog* luaDialog=nullptr;
Config::Table& globalCfg;
Config::Table& localCfg;
@ -320,6 +328,7 @@ public:
#ifdef __APPLE__
QAction* actPreferences;
#endif
QAction* actLuaScript;
QAction* actInputConfig;
QAction* actVideoSettings;
QAction* actCameraSettings;

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -0,0 +1,160 @@
-- Simple Script to test most of the different Lua Functions for MelonDS
-- Written by NPO197
MelonClear()
print("This text Should be cleared")
MelonClear()
print("Running Test...")
u32Data = Readu32(0x00000000)
print(string.format("DataZero: %x",u32Data))
canvas = MakeCanvas(0,0,500,500)
SetCanvas(canvas)
ClearOverlay()
FillRect(0,0,55,11,0xffffffff)
Text(0,9,"Test Message")
Line(0,10,55,10,0x00ff00ff)
Rect(0,0,55,11,0xff00ff00)
DrawImage("Lua-Logo_128x128.png",0,60)
Text(0,200,"WASD to move \"lua Stylus\", Q to tap screen.",0xffffff)
Text(0,210,"T to create savestate, R to load savestate.",0xffffff)
Flip()
-------- Main Loop ----------
updateFlags = {}
Stylus = {x=0,y=0,}
textCanvas = MakeCanvas(0,12,500,100)
vstylusCanvas = MakeCanvas(0,0,256,192,1) -- bottom screen
--Gets Called once every frame
function _Update()
updateFlags = {} -- Reset updateFlags at start of each frame
--Text Functions
SetCanvas(textCanvas)
ClearOverlay()
local y = 0
for _,tfunct in ipairs({
MousePosText,
MouseButtonText,
KeysText,
JoyText
}) do
y = y+10
Text(0,y,tfunct(),0xffffff)
end
Flip()
--SaveState stuff
if KeyPress("T") then
StateSave("SaveState_Auto.mln")
elseif KeyPress("R") then
StateLoad("SaveState_Auto.mln")
end
--StylusLoop
for key,dir in pairs({
--Key = {dx,dy}
["W"] = { 0,-1},
["A"] = {-1, 0},
["S"] = { 0, 1},
["D"] = { 1, 0}
}) do
if KeyHeld(key) then
Stylus.x=Stylus.x+dir[1]
Stylus.y=Stylus.y+dir[2]
end
end
if KeyHeld("Q") then
NDSTapDown(Stylus.x,Stylus.y)
else
NDSTapUp()
end
DrawStylus()
end
--Text Functions
function MousePosText()
mouse = GetMouse()
return "MousePos:"..mouse.X..","..mouse.Y
end
function MouseButtonText()
str = ""
for k,v in pairs(GetMouse()) do
if k~="X" and k~="Y" and v then
str = str..k
end
end
return "MouseBtn:"..str
end
typed = ""
keys = {}
function KeysText()
keys = Keys()
str = ""
for _,i in pairs(keys) do
if pcall(string.char,i) then
str = str..string.char(i)
else
print("NonAscii:"..i)
typed = ""
end
end
typed = typed..str
return "Keys:"..typed
end
joys = {}
function JoyText()
joys = GetJoy()
str = ""
for k,v in pairs(joys) do
if v then
str = str..k
end
end
return "Joy:"..str
end
function DrawStylus()
SetCanvas(vstylusCanvas)
ClearOverlay()
Ellipse(Stylus.x-5,Stylus.y-5,10,10,0xffffffff)
Ellipse(Stylus.x-2,Stylus.y-2,4,4,0x00000000)
Flip()
end
function KeyHeld(keyStr)
-- Only need to update mask once per frame
if updateFlags.KeyboardCheck == nil then
mask = HeldKeys()
updateFlags.KeyboardCheck = true
end
return mask[string.byte(keyStr:sub(1,1))]
end
Btns = {}
function KeyPress(keyStr)
if KeyHeld(keyStr) and Btns[keyStr] then
Btns[keyStr] = false
return true
end
Btns[keyStr] = true
return false
end

View file

@ -0,0 +1,178 @@
# Lua Script Overview
### Note
Lua scripting support is still a work in progress, lua features and functions are subject to change.
## Hello World Eample
Example of a Lua script that, when run, will display "Hello World", and a frame counter on the top screen.
```Lua
screenID = 0 -- TopScreen = 0, BottomScreen = 1, OSD = 2
canvas = MakeCanvas(0,0,100,100,screenID)
SetCanvas(canvas)
frameCount = 0
-- '_Update()' gets called once every frame
function _Update()
frameCount = frameCount+1
ClearOverlay()
Text(0,10,"Hello World: "..frameCount,0xffffff) --White text
Flip()
end
```
## Types and Notation
`uReturnedVal FunctionName(uArg1,uArg2,[uOptionalArg1 = defaultVal],[uOptionalArg2 = defaultVal],...)`
- `FunctionName` the name of the function
- `uArg` Non optional argument expected of type "u" (lua types are n="number",s="string",b="boolean", t="table")
- `[uOptionalArg = defaultVal]` optional argument expected of type "u" if not provided defaults to `defaultVal`
- `uReturnedVal` Value returned by function (usually `nil`)
## Function List
`nil print(sPrintText)`
- Print `sPrintText` to the lua consol
`nil MelonClear()`
- Clearse the consol in the lua dialog window.
`n_u8Value Readu8(nAddress)`
- Read Data unisigned one Byte from u32 address `nAddress`
- `n_u16Value Readu16(nAddress)` Read Data unisigned two Bytes from u32 address `nAddress`
- `n_u32Value Readu32(nAddress)` Read Data unisigned four Bytes from u32 address `nAddress`
`n_s8Value Readu8(nAddress)`
- Read Data signed one Byte from u32 address `nAddress`
- `n_s16Value Readu16(nAddress)` Read Data signed two Bytes from u32 address `nAddress`
- `n_s32Value Readu32(nAddress)` Read Data signed four Bytes from u32 address `nAddress`
`nil NDSTapDown(nX,nY)`
- Push down on the bottom touchscreen at pixel position X,Y.
- touchscreen dimensions are 256x192.
`nil NDSTapUp()`
- Releases the touch screen.
`nil StateSave(sFileName)`
- Creates a savestate and saves it as `sFileName`.
`nil StateLoad(sFileName)`
- Attempts to load a savestate with the filename `sFileName`.
`tMouseState GetMouse()`
- Returns a lua table of the mouse X/Y coordinates and button states.
- Table keys are: `X, Y, Left, Middle, Right, XButton1, XButton2`
- Example
```Lua
if GetMouse().Left == true then
print("Click at:"..GetMouse().X..','..GetMouse().Y)
end
```
`tJoyState GetJoy()`
- Returns a lua table of the Joypad button states.
- Table keys are: `A,B,Select,Start,Right,Left,Up,Down,R,L,X,Y`
- Example
```Lua
function JoyText()
joys = GetJoy()
str = ""
for k,v in pairs(joys) do
if v then
str = str..k
end
end
return "Joy:"..str
end
```
`tKeyMask HeldKeys()`
- Returns a lua table containing the value "true" for each held key and "nil" for any key not currently held.
- check the Qt docs for a list of key codes: https://doc.qt.io/qt-6/qt.html#Key-enum
- Example
```Lua
--Loop over all currently held keys
for k,_ in pairs(HeldKeys()) do
print("KeyPressed:"..k)
end
--Check if the "Q" key is currently pressed
if HeldKeys()[string.byte("Q")] then
print("\"Q\" Pressed!")
end
```
`tKeyStrokes Keys()`
- Returns a lua table of all keys pressed since the last time `Keys()` was called...
- DIFFERENT from `HeldKeys()`
- This function keeps proper track of the order of keys typed, and if the same key was pressed multiple times since the last check.
- Mostly used for cases that need faster typing/higher poll rate... otherwise use HeldKeys() for most use cases.
- Example
```Lua
typed = ""
keys = {}
function KeysText()
keys = Keys()
str = ""
for _,i in pairs(keys) do
if pcall(string.char,i) then
str = str..string.char(i)
else
print("NonAscii:"..i)
typed = ""
end
end
typed = typed..str
return "Keys:"..typed
end
```
`nCanvasID MakeCanvas(nX, nY,nWidth,nHeight,[nScreenTarget = 2],[bIsActive = true])`
- Creates a new canvas and returns it's ID.
- You must set a canvas using `SetCanvas` before drawing anything to the screen.
- `nScreenTarget` options are 0 = TopScreen, 1 = BottomScreen, 2 = OSD / non-screen target (default)
- `bIsActive` must be true for the canvas to be drawn to the screen (true by default)
- `nCanvasID` is the ID of the newly created Canvas.
`nil SetCanvas(nCanvasID)`
- Sets the current canvas to `nCanvasID`
- All future drawing functions will draw to the *currently set canvas*.
`nil ClearOverlay()`
- Clears the *currently set canvas* by setting all pixels to `0x00000000` transparent black
`nil Flip()`
- Tells MelonDS that the *currently set canvas* has been updated and must be redrawn.
- This may be handled automatically in the future...
`nil Text(nX,nY,sMessage, [nColor = 0x000000],[nFontSize = 9],[sFontFamily = 'Helvetica'])`
- Draws `sMessage` to the *currently set canvas* given the color and font specified.
- Qt will attempt to match `sFontFamily` to the closest font it can find available...
`nil Line(nX1,nY1,nX2,nY2,nColor)`
- Draws a line on the *currently set canvas* fom (x1,y1) to (x2,y2) of the specified color.
`nil Rect(nX,nY,nWidth,nHeight,ncolor)`
- Draws a 1p width Rectangle on the *currently set canvas* of the given size position and color.
`nil FillRect(nX,nY,nWidth,nHeight,ncolor)`
- Draws a filled in Rectangle on the *currently set canvas* of the given size position and color.
`nil Ellipse(nX,nY,nWidth,nHeight,ncolor)`
- Draws a 1p width Ellipse on the *currently set canvas* of the given size position and color.
`nil DrawImage(sImagePath,nX,nY,[nSourceX = 0],[nSourceY = 0],[nSourceWidth = -1],[nSourceHeight = -1])`
- Draws the image saved at `sImagePath` to the *currently set canvas* at the given position.
- By default copies the entire image to the canvas.
- optional arguments Source-(X,Y,Width,Height) determain where in the source image to copy from.
`nil ClearImageHash()`
- Clears the interal hash of all images drawn using DrawImage
- Only needed if RAM is being filled up with lots of different Image files.

View file

@ -17,7 +17,8 @@
{
"name": "libslirp",
"platform": "linux"
}
},
"lua"
],
"features": {
"qt6": {