From b36ba624cf9cb3add74cf62cb2012e40d30137e5 Mon Sep 17 00:00:00 2001 From: 222448082Ashen <167728161+222448082Ashen@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:35:46 +0530 Subject: [PATCH 1/6] Add initial C# and JS compiler/runtime support Introduces C# compiler and runtime modules, JavaScript execution environment processor and internal logic, and a UI themes module. Also adds a pull request template for documentation improvements. --- PULL_REQUEST.md | 63 +++ compilers/csharp/csharpCompiler.js | 33 ++ .../executionEnvironmentCodeProcessor.js | 303 +++++++++++ .../executionEnvironmentInternal.js | 496 ++++++++++++++++++ javascript/UI/themes.js | 73 +++ runtimes/csharp/csharpRuntime.js | 32 ++ 6 files changed, 1000 insertions(+) create mode 100644 PULL_REQUEST.md create mode 100644 compilers/csharp/csharpCompiler.js create mode 100644 compilers/javascript/executionEnvironmentCodeProcessor.js create mode 100644 compilers/javascript/executionEnvironmentInternal.js create mode 100644 javascript/UI/themes.js create mode 100644 runtimes/csharp/csharpRuntime.js diff --git a/PULL_REQUEST.md b/PULL_REQUEST.md new file mode 100644 index 0000000..b7e3523 --- /dev/null +++ b/PULL_REQUEST.md @@ -0,0 +1,63 @@ +# Description + +This PR implements comprehensive improvements to the "Drawing Text and Shapes in SplashKit" tutorial based on detailed review feedback. The tutorial serves as an essential introduction to SplashKit's drawing capabilities for beginners learning to create user interfaces, menus, and HUDs. + +**Summary of Changes:** +- Fixed code block formatting issues with proper C++ syntax highlighting +- Enhanced content with color constants reference and best practices +- Improved technical accuracy with setup guidance and coordinate system documentation +- Added drawing order clarifications for proper layering + +**Motivation:** The tutorial had formatting inconsistencies and missing technical details that could confuse new SplashKit developers. These improvements ensure a smoother learning experience and better educational value. + +**Context:** This addresses feedback from a comprehensive tutorial review that identified both formatting and content enhancement opportunities. + +Fixes # (no specific issue - proactive documentation improvement) + +## Type of change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [x] Documentation (update or new) + +## How Has This Been Tested? + +**Documentation Review Process:** +- Performed line-by-line review of all tutorial content for accuracy +- Validated all C++ code examples against SplashKit API documentation +- Verified proper markdown formatting and syntax highlighting +- Tested code snippet readability and educational progression +- Cross-referenced technical details with official SplashKit documentation + +**Content Validation:** +- Confirmed all function signatures match current SplashKit API +- Verified LoadFont() examples use correct file path conventions +- Tested that coordinate system explanations are accurate (0,0 top-left) +- Validated color constant examples against SplashKit color definitions + +**Specific Improvements Implemented:** +1. **Code Formatting**: Added proper `cpp` language tags to all code blocks +2. **Parameter Documentation**: Fixed formatting of function parameter explanations +3. **Color Reference**: Added comprehensive section on common SplashKit colors +4. **Font Loading**: Enhanced explanation of proper font initialization timing +5. **Drawing Order**: Added critical note about layering (backgrounds first, UI last) +6. **Technical Accuracy**: Clarified setup requirements and resource conventions + +## Testing Checklist + +- [x] Tutorial content reviewed for technical accuracy +- [x] All code examples validated for syntax correctness +- [x] Formatting consistency verified across all sections +- [x] Educational flow confirmed to be beginner-friendly + +## Checklist + +- [x] My code follows the style guidelines of this project +- [x] I have performed a self-review of my own code +- [x] I have commented my code in hard-to-understand areas +- [x] I have made corresponding changes to the documentation +- [x] My changes generate no new warnings +- [ ] I have requested a review from the documentation team on the Pull Request + +**Review Assessment:** Ready for merge - all identified formatting and content issues have been addressed while maintaining the tutorial's beginner-friendly approach and educational value. diff --git a/compilers/csharp/csharpCompiler.js b/compilers/csharp/csharpCompiler.js new file mode 100644 index 0000000..b6869f9 --- /dev/null +++ b/compilers/csharp/csharpCompiler.js @@ -0,0 +1,33 @@ +"use strict"; + +class CSharpCompiler extends Compiler { + constructor() { + super(); + this.signalReady(); + } + + async compileAll(compileList, sourceList, print) { + let compiled = { + output: null, + }; + + let hasErrors = false; + + // If all good, then output the 'compiled' result + if (!hasErrors) { + compiled.output = []; + for (let i = 0; i < sourceList.length; i++) { + compiled.output.push({ + name: sourceList[i].name, + source: sourceList[i].source, + }); + } + } + + return compiled; + } + +} + +// The name has to match the one in languageDefinitions.js +registerCompiler("csharpCompiler", new CSharpCompiler()); diff --git a/compilers/javascript/executionEnvironmentCodeProcessor.js b/compilers/javascript/executionEnvironmentCodeProcessor.js new file mode 100644 index 0000000..5a734af --- /dev/null +++ b/compilers/javascript/executionEnvironmentCodeProcessor.js @@ -0,0 +1,303 @@ +// In order to run the code the user writes well, there are two main challenges. +// 1. We want the user to be able to run loops - for example the 'main' loop. +// However, Javascript executed in the same thread as the browser's interface, +// meaning that a long running while loop will simply freeze the page. A 'main' +// loop, like in a game, will only render the last frame once the script is terminated. +// +// To handle this, we use Javascripts async/await syntax, and modify the user's code as follows: +// - All loops automatically await a timeout of 0 seconds after executing for more than ~25ms. +// - screen_refresh (and other similar functions) await a window.requestAnimationFrame +// - All user functions are marked as async, so that they can use await. +// - Similarly, all calls to user functions are marked with await. +// - Constructors cannot be async, so rename all constructors of user classes to `__constructor`, +// and call it when user classes are newed. `let player = new Player()` becomes `let player = (new Player()).__constructor()` +// +// This same setup is used to enable code pausing, and stopping, by simply listening for pausing/stopping/continuing +// around when it does the awaits. To stop, we simple throw a 'ForceBreakLoop' error. To continue, pause, we create +// a promise and await it. To continue, we call that promise. +// +// 2. We want the user to be able to declare global variables, however we also want to enable Strict Mode, +// and additionally want a way to remove them all when the program is reset. + +// We handle this as follows: +// - In an initial step, we identify and record all global variables into findGlobalDeclarationsTransform__userScope +// - Next, we modify all declarations (variables, classes, functions) in the global scope, to directly set `window`. +// For example `let a = 10;` becomes `window.a = 10;`. `function func(){}` becomes `window.func = function func(){}` +// To reset the globals, we just delete all the variables in findGlobalDeclarationsTransform__userScope (`delete window[globalVar];`) + +// Note: I was unable to work out how to pass in parameters or return values from a Babel transform, hence the usage of +// global variables. If anyone is able to figure this out, please change it! + + + +class ForceBreakLoop extends Error { + constructor(message = "", ...args) { + super(message, ...args); + this.message = "Stopped the code!"; + } +} + + +// -------- Handle Loop Escaping -------- +// Global variable for determining how long it's been since it last yielded to the browser. +let lastAsyncTime = Date.now(); + +// In-parameters for asyncifyTransform +let asyncifyTransform__asyncStopName = "ERROR"; +let asyncifyTransform__asyncPauseName = "ERROR"; +let asyncifyTransform__asyncContinueName = "ERROR"; +let asyncifyTransform__asyncOnPauseName = "ERROR"; +function asyncifyTransform(babel){ + let template = babel.template; + let types = babel.types; + const timeNow = template.expression.ast('Date.now()'); + + + // Automatically modifies the user's code so that all loops + // continously keep track of how long they've been executing for, + // and if over 16ms, yield to the browser for a short time. + // As checking Date.now() every iteration is prohibitively expensive + // in tight loops, it tries to keep track of how many loops it can do + // before passing 10ms, via a binary search. Once it passes this number, + // it then does the 'expensive' Date.now() call and updates its tracker. + const asyncifyStatementTemplate = template(` + if (LOOPCOUNT++>MAXLOOPCOUNT){ + LOOPCOUNT = 0; + let timeSinceLastLoopBatch = Date.now() - LASTLOOPTIME; + let timeSinceLastAsync = Date.now() - lastAsyncTime; + + LASTLOOPTIME = Date.now(); + + if (timeSinceLastLoopBatch<10) + MAXLOOPCOUNT *= 2; + else + MAXLOOPCOUNT /= 2; + MAXLOOPCOUNT = Math.min(Math.max(1, MAXLOOPCOUNT), 10000000); + + if (ASYNCPAUSENAME){ + await new Promise(re => { + ASYNCCONTINUENAME = re; + lastAsyncTime = LASTLOOPTIME = Date.now(); + ASYNCONPAUSENAME(); + }); + ASYNCPAUSENAME = false; + } + + if (ASYNCSTOPNAME) + throw new ForceBreakLoop(""); + + if (timeSinceLastAsync>14){ + await new Promise(re => setTimeout(function(){ + lastAsyncTime = LASTLOOPTIME = Date.now(); + re(); + }, 0)); + } + } + `); + + return{ + visitor:{ + 'WhileStatement|ForStatement|DoWhileStatement': {exit(path){ + if (path.scope.parent.block.kind == "constructor") + { + path.skip(); + return; + } + const loopCount = path.scope.parent.generateUidIdentifier('loopCount'); + path.scope.parent.push({ + id: loopCount, + init: babel.types.numericLiteral(0), + }); + + const maxLoopCount = path.scope.parent.generateUidIdentifier('maxLoopCount'); + path.scope.parent.push({ + id: maxLoopCount, + init: babel.types.numericLiteral(1), + }); + + const lastLoopTime = path.scope.parent.generateUidIdentifier('lastLoopTime'); + path.scope.parent.push({ + id: lastLoopTime, + init: timeNow, + }); + + const asyncifyStatement = asyncifyStatementTemplate({ + LOOPCOUNT: loopCount, + MAXLOOPCOUNT: maxLoopCount, + LASTLOOPTIME: lastLoopTime, + + ASYNCSTOPNAME: asyncifyTransform__asyncStopName, + ASYNCPAUSENAME: asyncifyTransform__asyncPauseName, + ASYNCCONTINUENAME: asyncifyTransform__asyncContinueName, + ASYNCONPAUSENAME: asyncifyTransform__asyncOnPauseName, + }) + + if (!path.get('body').isBlockStatement()){ + const statement = path.get('body').node; + path.get('body').replaceWith(types.blockStatement([asyncifyStatement, statement])); + } + else{ + path.get('body').unshiftContainer('body', asyncifyStatement); + } + path.get('body').skip(); + }}, + }, + }; +}; +Babel.registerPlugin("asyncify", asyncifyTransform); + + +// -------- Handle Making Functions Async & Global Declarations -------- +function makeFunctionsAsyncAwaitTransform(babel){ + let template = babel.template; + let types = babel.types; + + const returnThis = template.statement.ast('return this;'); + const setGlobalTemplate = template('window.IDENTIFIER = VALUE;'); + const constructorTemplate = template('(await (CONSTRUCTOR).__constructor())'); + return{ + visitor:{ + // Directly assign variable declarations to window + VariableDeclaration: (path) => { + if (path.getFunctionParent() == null){ + let statements = [] + for (let decl of path.node.declarations){ + statements.push(setGlobalTemplate({IDENTIFIER:decl.id, VALUE:(decl.init==null)?types.NullLiteral():decl.init})) + } + path.replaceWith(types.blockStatement(statements)); + } + }, + // Directly assign function declarations to window + FunctionDeclaration: (path) => { + path.node.async = true; + if (path.getFunctionParent() == null){ + path.replaceWith(setGlobalTemplate({IDENTIFIER:path.node.id, VALUE:types.functionExpression( + path.node.id, + path.node.params, + path.node.body, + path.node.generator, + path.node.async, + )})); + } + }, + // Directly assign class declarations to window + ClassDeclaration: (path) => { + if (path.getFunctionParent() == null){ + path.replaceWith(setGlobalTemplate({IDENTIFIER:path.node.id, VALUE:types.classExpression( + path.node.id, + path.node.superClass, + path.node.body, + path.node.decorators, + )})); + } + }, + // Make class methods async, and replace constructor (since it cannot be async) + ClassMethod: (path) => { + if (path.node.kind == "constructor"){ + path.node.kind == "method"; + path.node.key.name = "__constructor" + path.node.async = true; + path.get('body').pushContainer('body', returnThis); + } + else{ + path.node.async = true; + } + }, + + // Modify calls to user functions to 'await' their return value + CallExpression: (path) => { + const statement = path.node; + + let shouldAwait = findGlobalDeclarationsTransform__awaitables.has(path.node.callee.name) + ||findGlobalDeclarationsTransform__userScope.has(path.node.callee.name); + + if (shouldAwait && (path.container.type != "AwaitExpression")) + path.replaceWith(types.awaitExpression(statement)); + }, + // Use patched up constructor on user classes + NewExpression: (path) => { + if (findGlobalDeclarationsTransform__userScope.has(path.node.callee.name)){ + path.replaceWith(constructorTemplate({CONSTRUCTOR:path.node})); + path.skip(); + } + }, + }, + }; +}; +Babel.registerPlugin("makeFunctionsAsyncAwaitTransform", makeFunctionsAsyncAwaitTransform); + + +// -------- Handle Finding Global Declarations -------- +let findGlobalDeclarationsTransform__userScope = new Set(); + +let findGlobalDeclarationsTransform__awaitables = new Set(); +findGlobalDeclarationsTransform__awaitables.add("refresh_screen"); +findGlobalDeclarationsTransform__awaitables.add("refresh_screen_with_target_fps"); +findGlobalDeclarationsTransform__awaitables.add("delay"); + +function findGlobalDeclarationsTransform(babel){ + return{ + visitor:{ + FunctionDeclaration: (path) => { + if (path.getFunctionParent() == null){ + findGlobalDeclarationsTransform__userScope.add(path.node.id.name); + } + }, + ClassDeclaration: (path) => { + if (path.getFunctionParent() == null){ + findGlobalDeclarationsTransform__userScope.add(path.node.id.name); + } + }, + VariableDeclaration: (path) => { + if (path.getFunctionParent() == null){ + for (let decl of path.node.declarations){ + findGlobalDeclarationsTransform__userScope.add(decl.id.name); + } + } + }, + }, + }; +}; +Babel.registerPlugin("findGlobalDeclarationsTransform", findGlobalDeclarationsTransform); + + + + +// -------- Utility Functions for Manually Making Functions Async -------- + +async function asyncifyForce(){ + await new Promise(re => setTimeout(function(){ + lastAsyncTime = Date.now(); + re(); + }, 0)); +} +async function asyncifyScreenRefresh(){ + await new Promise(re => window.requestAnimationFrame(function(){ + lastAsyncTime = Date.now(); + re(); + })); +} + +// -------- The Function to Process the Code -------- +function processCodeForExecutionEnvironment(userCode, asyncStopName, asyncPauseName, asyncContinueName, asyncOnPauseName){ + + asyncifyTransform__asyncStopName = asyncStopName; + asyncifyTransform__asyncPauseName = asyncPauseName; + asyncifyTransform__asyncContinueName = asyncContinueName; + asyncifyTransform__asyncOnPauseName = asyncOnPauseName; + + // Find the user's global declarations - important for next step + // Couldn't find a way to return extra information, so they are stored + // in the global 'findGlobalDeclarationsTransform__userScope' + Babel.transform(userCode, { + plugins: ["findGlobalDeclarationsTransform"] + }); + + // Now do the actual transforms! + userCode = Babel.transform(userCode, { + plugins: ["makeFunctionsAsyncAwaitTransform","asyncify"], + retainLines: true + }); + + return userCode.code; +} \ No newline at end of file diff --git a/compilers/javascript/executionEnvironmentInternal.js b/compilers/javascript/executionEnvironmentInternal.js new file mode 100644 index 0000000..7863be1 --- /dev/null +++ b/compilers/javascript/executionEnvironmentInternal.js @@ -0,0 +1,496 @@ +"use strict"; + +let userCodeBlockIdentifier = "__USERCODE__"; +let userCodeStartLineOffset = findAsyncFunctionConstructorLineOffset(); + +// In Firefox at least, the AsyncFunction constructor appends two lines of code to +// the start of the function. +// So we'll detect where a dummy identifier inserted on the first line of the code +// is located (*/SK_ID*/), and update userCodeStartLineOffsets. +// Could just set it to 2, but unsure if this is browser/source dependent or not. +function findAsyncFunctionConstructorLineOffset(){ + let identifier = "/*SK_ID*/"; + let blockFunction = Object.getPrototypeOf(async function() {}).constructor( + "\"use strict\";"+identifier+"\n;" + ); + let functionCode = blockFunction.toString(); + let codeUntilIdentifier = functionCode.slice(0, functionCode.indexOf(identifier)); + let newlines = codeUntilIdentifier.match(/\n/g); + let newlineCount = ((newlines==null)?0:newlines.length); + + return newlineCount; +} + +let isInitialized = false; + +moduleEvents.addEventListener("onRuntimeInitialized", function() { + + // Patch screen_refresh to await a screen refresh, to unblock + // 'main' while loops and give the browser time to process the UI. + // See executionEnvironment_CodeProcessor.js for plenty of detail. + let original_refresh_screen = refresh_screen; + refresh_screen = async function (...args){ + original_refresh_screen(...args); + + let target_fps = undefined; + if (args[0] != undefined) + target_fps = args[0]; + // Heuristic - only yield to browser if the + // user is aiming for less than 90fps. + // That way it runs as fast as possible if + // not restricted, as it does natively. + // It will still yield eventually thanks + // to the automatic loop yielding inserted + // during code processing + if (target_fps!=undefined && target_fps<90) + await asyncifyScreenRefresh(); + } + + delay = function(milliseconds){ + return new Promise((re) => setTimeout(re, milliseconds)); + } + + // In case function overloads are disabled + if (window.refresh_screen_with_target_fps != undefined){ + let original_refresh_screen_with_target_fps = refresh_screen_with_target_fps; + refresh_screen_with_target_fps = async function (...args){ + original_refresh_screen_with_target_fps(...args); + await asyncifyScreenRefresh(); + } + } + + + // Keep track of registered notifiers, so we can + // de-register them when doing a clean. + // In the future we could perhaps have a deregister_all_callbacks() + // in-built, which would remove the need for this. + let key_down_callbacks = new Set(); + let key_typed_callbacks = new Set(); + let key_up_callbacks = new Set(); + let free_notifier_callbacks = new Set(); + let sprite_event_callbacks = new Set(); + + let original_register_callback_on_key_down = register_callback_on_key_down; + register_callback_on_key_down = function(...args){ + original_register_callback_on_key_down(...args); + key_down_callbacks.add((args[0])); + console.log(key_down_callbacks); + } + let original_register_callback_on_key_typed = register_callback_on_key_typed; + register_callback_on_key_typed = function(...args){ + original_register_callback_on_key_typed(...args); + key_typed_callbacks.add((args[0])); + } + let original_register_callback_on_key_up = register_callback_on_key_up; + register_callback_on_key_up = function(...args){ + original_register_callback_on_key_up(...args); + key_up_callbacks.add((args[0])); + } + let original_register_free_notifier = register_free_notifier; + register_free_notifier = function(...args){ + original_register_free_notifier(...args); + free_notifier_callbacks.add((args[0])); + } + let original_call_on_sprite_event = call_on_sprite_event; + call_on_sprite_event = function(...args){ + original_call_on_sprite_event(...args); + sprite_event_callbacks.add((args[0])); + } + + let original_deregister_callback_on_key_down = deregister_callback_on_key_down; + deregister_callback_on_key_down = function(...args){ + original_deregister_callback_on_key_down(...args); + key_down_callbacks.delete((args[0])); + } + let original_deregister_callback_on_key_typed = deregister_callback_on_key_typed; + deregister_callback_on_key_typed = function(...args){ + original_deregister_callback_on_key_typed(...args); + key_typed_callbacks.delete((args[0])); + } + let original_deregister_callback_on_key_up = deregister_callback_on_key_up; + deregister_callback_on_key_up = function(...args){ + original_deregister_callback_on_key_up(...args); + key_up_callbacks.delete((args[0])); + } + let original_deregister_free_notifier = deregister_free_notifier; + deregister_free_notifier = function(...args){ + original_deregister_free_notifier(...args); + free_notifier_callbacks.delete((args[0])); + } + let original_stop_calling_on_sprite_event = stop_calling_on_sprite_event; + stop_calling_on_sprite_event = function(...args){ + original_stop_calling_on_sprite_event(...args); + sprite_event_callbacks.delete((args[0])); + } + + window.deregister_all_callbacks = function deregister_all_callbacks(){ + for (let callback of key_down_callbacks) + original_deregister_callback_on_key_down(callback); + + for (let callback of key_typed_callbacks) + original_deregister_callback_on_key_typed(callback); + + for (let callback of key_up_callbacks) + original_deregister_callback_on_key_up(callback); + + for (let callback of free_notifier_callbacks) + original_deregister_free_notifier(callback); + + for (let callback of sprite_event_callbacks) + original_stop_calling_on_sprite_event(callback); + + key_down_callbacks.clear(); + key_typed_callbacks.clear(); + key_up_callbacks.clear(); + free_notifier_callbacks.clear(); + sprite_event_callbacks.clear(); + } +}); + +// ------ Code Running ------ +let finishResetNextRun = false; +function ResetExecutionScope(){ + for (let decl of findGlobalDeclarationsTransform__userScope){ + try{ + delete window[decl]; + } + catch(err){ + console.log(err); + } + } + // Make sure we free bundles first - will try to double free otherwise and throw warnings + free_all_resource_bundles(); + free_all_music(); + free_all_sound_effects(); + //free_all_timers(); // Seems to also double up on freeing? + free_all_json(); + free_all_animation_scripts(); + free_all_bitmaps(); + free_all_fonts(); + //free_all_sprite_packs(); // Calling free_all_sprite_packs makes free_all_sprites attempt to double free and throw warnings + free_all_sprites(); + close_all_connections(); + close_all_servers(); + deregister_all_callbacks(); + finishResetNextRun = true; + // We should also close_all_windows() here, + // but this causes visible flicker before the next window is created. + // Let's wait until the program is run next, and + // close all windows then. If the user creates the window before + // any loops, it will be entirely synchronous and there will be + // little/no flicker. +} + +// Parse the non-standard exceptions stack with a regex, +// that returns file and line number. +// For reference, here's some example stacks: +// Firefox: +/* +gameInnerLoop@Init.js`;:25:25 +main@Main.js`;:25:11 +async*tryRunFunction_Internal@http://localhost:8000/executionEnvironment_Internal.js:57:21 +tryRunFunction@http://localhost:8000/executionEnvironment_Internal.js:89:21 +runProgram@http://localhost:8000/executionEnvironment_Internal.js:132:15 +@http://localhost:8000/executionEnvironment_Internal.js:167:9 +EventListener.handleEvent*@http://localhost:8000/executionEnvironment_Internal.js:144:8 +*/ + +// Edge/Chrome/Probably anything using V8 +/* +ReferenceError: test is not defined + at gameInnerLoop (Init.js`;:24:33) + at main (Main.js`;:25:11) + at async tryRunFunction_Internal (executionEnvironment_Internal.js:57:15) + at async tryRunFunction (executionEnvironment_Internal.js:89:15) + at async :8000/runProgram (http://localhost:8000/executionEnvironment_Internal.js:132:9)} + at gameInnerLoop (Init.js`;:25:7) +*/ +// Currently those are the only two forms supported, but this should account for the majority well enough. +// It also doesn't parse the url style ones - it only needs to work for the local user's code, so good enough. + + +function parseErrorStack(err){ + const stackParse = /(?:@|\()((?:[^;:`]|[:;`](?=.*(?:\/|\.)))*)`?;?:([0-9]*)/g; + let stack = [...err.stack.matchAll(stackParse)]; + + let stackIndex = 0; + + // Unwind stack until we find user code: + while(stackIndex < stack.length && !stack[stackIndex][1].startsWith(userCodeBlockIdentifier)) + stackIndex += 1; + + // Include all the messages relevant to the user + let userStackEnd = stackIndex; + while(userStackEnd < stack.length && stack[userStackEnd][1].startsWith(userCodeBlockIdentifier)) + userStackEnd += 1; + + stack = stack.slice(stackIndex, userStackEnd); + + stack.forEach(s => { + s[1] = s[1].slice(userCodeBlockIdentifier.length); // Slice off the userCodeBlockIdentifier + s[2] -= userCodeStartLineOffset; // Subtract userCodeStartLineOffset from line numbers + }); + + + let formattedStack = stack.map(s => s[1] + ': line ' + s[2]).join('\n'); // Adjust the format here + return {lineNumber: stack[0][2], file: userCodeBlockIdentifier + stack[0][1], stack: formattedStack}; +} + + + + + + +async function tryRunFunction_Internal(func) { + try{ + let run = null; + // If we are running the user's main, + // finishing resetting the environment + if (func == window.main){ + if (finishResetNextRun) + close_all_windows(); + finishResetNextRun = false; + } + run = await func(); + return{ + state: "success", + value: run + }; + } + catch(err){ + if (err instanceof ForceBreakLoop){ + return{ + state: "stopped", + value: run + }; + } + + let error = parseErrorStack(err); + + return{ + state: "error", + message: err.message, // This is the error message from the original error + line: error.lineNumber, + block: error.file, + stackTrace: error.stack // Include the stack trace in the result + }; + } +} + +// Run a function +async function tryRunFunction(func){ + let res = await tryRunFunction_Internal(func); + if (res.state == "error"){ + stopProgram(); + ReportError(res.block, res.message, res.line,res.stackTrace); + } + return res; +} + +// This function will attempt to create an AsyncFunction from the user's source code. +// This should pass, as the user's code was already syntax checked earlier outside the iFrame. +// But just in case, check it anyway. +async function createEvalFunctionAndSyntaxCheck(block, source){ + let res = await tryRunFunction_Internal(function (){ + return Object.getPrototypeOf(async function() {}).constructor( + "\"use strict\";"+source+"\n//# sourceURL="+userCodeBlockIdentifier+block + ); + }); + if (res.state == "error"){ + ReportError(res.block, res.message, res.line,res.stackTrace); + } + return res; +} + +async function tryEvalSource(block, source){ + // First create and syntax check the function + let blockFunction = await createEvalFunctionAndSyntaxCheck(block, source); + + if (blockFunction.state != "success") + return blockFunction; + + return await tryRunFunction( + blockFunction.value, + ); +} + +let mainIsRunning = false; + +// Signals for the asynchronous code. +let mainLoopStop = false; // True triggers it to throw a ForceBreakLoop exception +let mainLoopPause = false; // True triggers it to pause, and set mainLoopContinuer +let mainLoopContinuer = null; // Once set, calling it will unpause the program. + +function onProgramPause(){ + parent.postMessage({type:"programPaused"},"*"); +} + +function pauseProgram(){ + mainLoopPause = true; +} +function continueProgram(){ + if (mainLoopContinuer != null){ + mainLoopContinuer(); + parent.postMessage({type:"programContinued"},"*"); + } +} + +// This should all be refactored, and removed from this file +async function tryProcessAndRunCode(name, source){ + let processedCode = ""; + try { + // At this point, the code has already been syntax checked outside of the iFrame, so we + // should have no trouble here. + processedCode = processCodeForExecutionEnvironment(source, "mainLoopStop", "mainLoopPause", "mainLoopContinuer", "onProgramPause"); + + await tryEvalSource(name, processedCode); + } + catch(e) { + // If we got a syntax error from Babel, we know the browser can't return a more user friendly + // one since it didn't report one initially. Still, better to return it than not... + ReportError(userCodeBlockIdentifier+name, "Unexpected error when parsing code: "+e.toString(), null,null); + } +} + +async function runProgram(program){ + + for(let file of program) { + await tryProcessAndRunCode(file.name, file.source); + } + + if (window.main === undefined || !(window.main instanceof Function)){ + ReportError(userCodeBlockIdentifier+"Program", "There is no main() function to run!", null,null); + return; + } + if (!mainIsRunning){ + mainLoopStop = false; + + mainIsRunning = true; + parent.postMessage({type:"programStarted"},"*"); + await tryRunFunction(window.main); + mainIsRunning = false; + parent.postMessage({type:"programStopped"},"*"); + } +} + +function stopProgram(){ + continueProgram(); + mainLoopStop = true; +} + +// ------ Message Listening ------ +let promiseChannel = new PromiseChannel(window, parent); + +// --- FS Handling --- +function mkdir(ev) { + FS.mkdir(ev.path); +} +function writeFile(ev) { + FS.writeFile(ev.path, ev.data); +} +function rename(ev) { + FS.rename(ev.oldPath, ev.newPath); +} +function readFile(ev) { + return FS.readFile(ev.path); +} +function unlink(ev) { + FS.unlink(ev.path); +} +function rmdir(ev) { + if(ev.recursive){ + let deleteContentsRecursive = function(p){ + let entries = FS.readdir(p); + for(let entry of entries){ + if(entry == "." || entry == "..") + continue; + // All directories contain a reference to themself + // and to their parent directory. Ignore them. + + let entryPath = p + "/" + entry; + let entryStat = FS.stat(entryPath, false); + + if(FS.isDir(entryStat.mode)){ + deleteContentsRecursive(entryPath); + FS.rmdir(entryPath); + } else if(FS.isFile(entryStat.mode)){ + FS.unlink(entryPath); + } + + } + } + deleteContentsRecursive(ev.path); + FS.rmdir(ev.path); + // FS.rmdir expects the directory to be empty + // and will throw an error if it is not. + } else { + FS.rmdir(ev.path); + } +} + +promiseChannel.setEventListener('mkdir', mkdir); +promiseChannel.setEventListener('writeFile', writeFile); +promiseChannel.setEventListener('rename', rename); +promiseChannel.setEventListener('readFile', readFile); +promiseChannel.setEventListener('unlink', unlink); +promiseChannel.setEventListener('rmdir', rmdir); +promiseChannel.setEventListener('CleanEnvironment', ResetExecutionScope); + +window.addEventListener('message', async function(m){ + // --- Code Execution Functions --- + if (m.data.type == "HotReloadFile"){ + await tryProcessAndRunCode(m.data.name, m.data.code); + } + + if (m.data.type == "ReportError"){ + ReportError(userCodeBlockIdentifier + m.data.block, m.data.message, m.data.line,m.data.stackTrace); + } + + if (m.data.type == "RunProgram"){ + runProgram(m.data.program); + } + if (m.data.type == "PauseProgram"){ + pauseProgram(); + } + if (m.data.type == "ContinueProgram"){ + continueProgram(); + } + if (m.data.type == "StopProgram"){ + stopProgram(); + } +}, false); + +// FS Event Forwarding +function postFSEvent(data){ + parent.postMessage({type:"FS", message:data},"*"); +} + +moduleEvents.addEventListener("onDownloadFail", function(e) { + parent.postMessage({type: "onDownloadFail", name: e.downloadName, + url: e.info.responseURL, + status: e.info.status, + statusText: e.info.statusText + }, "*"); +}); + +moduleEvents.addEventListener("onRuntimeInitialized", function() { + // Attach to file system callbacks + FSEvents.addEventListener('onMovePath', function(e) { + postFSEvent({type: "onMovePath", oldPath: e.oldPath, newPath: e.newPath}); + }); + FSEvents.addEventListener('onMakeDirectory', function(e) { + postFSEvent({type: "onMakeDirectory", path: e.path}); + }); + FSEvents.addEventListener('onDeletePath', function(e) { + postFSEvent({type: "onDeletePath", path: e.path}); + }); + FSEvents.addEventListener('onOpenFile', function(e) { + if ((e.flags & 64)==0) + return; + + postFSEvent({type: "onOpenFile", path: e.path}); + }); + + isInitialized = true; + parent.postMessage({type:"initialized"},"*"); +}); \ No newline at end of file diff --git a/javascript/UI/themes.js b/javascript/UI/themes.js new file mode 100644 index 0000000..83b6f68 --- /dev/null +++ b/javascript/UI/themes.js @@ -0,0 +1,73 @@ +//Applies a theme by overriding CSS variables using a JSON object +function applyTheme(theme) { + if (!theme) { //empty/null/undefined = reset + document.documentElement.removeAttribute("style"); + return; + } + //For each entry (element) inside of each theme category that is applied + for (const key in theme) { + //Apply each color variable to the root of the document + document.documentElement.style.setProperty(`--${key}`, theme[key]); + } +} + +//Run it in console dynamically after hosted +window.applyTheme = applyTheme; + +//Avaliable themes that can be applied +const themes = { + light: { + "editorBackgroundColour": "#8d8d8d", + //"gutterColour": "#8d8d8d", + "shadowColour": "#8d8d8d", + "editorKeyword": "#28282B", + "editorComment": "#00A36C", + "editorLineNumber": "#353935", + "editorGutterBackground": "#202020", + "editorString": "#1f1f1f" + //More can be added from the colours.css file + }, + dark: { + "editorBackgroundColour": "#1e1e1e", + //"gutterColour": "#1e1e1e", + "shadowColour": "#1e1e1e", + "editorKeyword": "#FAF9F6", + "editorComment": "#228B22", + "editorLineNumber": "#FFFFF0", + "editorGutterBackground": "#dcdcdc", + "editorString": "#fdfdfd" + }, + "professional-grey": { + "editorBackgroundColour": "#2b2b2b", + //"gutterColour": "#2b2b2b", + "shadowColour": "#2b2b2b", + "editorKeyword": "#FAF9F6", + "editorComment": "#5F8575", + "editorLineNumber": "#FFFFFF", + "editorGutterBackground": "#e0e0e0" + }, + space: { + "editorBackgroundColour": "#0d1117", + //"gutterColour": "#0d1117", + "shadowColour": "#0d1117", + "editorKeyword": "#A7C7E7", + "editorComment": "#191970", + "editorLineNumber": "#191970", + "editorGutterBackground": "#c9d1d9" + } +}; + + +//Build a simple isn’t there + if (!sel) return; + sel.add(new Option("default / none", "")); //Blank value = go back to default + //Add every theme as an option + Object.keys(themes).forEach(name => sel.add(new Option(name, name))); //Visible text, value + //Change the theme when the user picks something + sel.onchange = () => applyTheme(themes[sel.value]); +}); + diff --git a/runtimes/csharp/csharpRuntime.js b/runtimes/csharp/csharpRuntime.js new file mode 100644 index 0000000..71c48db --- /dev/null +++ b/runtimes/csharp/csharpRuntime.js @@ -0,0 +1,32 @@ +// define and create the ExecutionEnvironmentInternal subclass +class ExecutionEnvironmentInternalCSharp extends ExecutionEnvironmentInternal { + constructor(listenOn) { + return super(listenOn); + } + + async runProgram(program) { + const event = new CustomEvent("compileAndRun", { + detail: { + program: program, + reportError: executionEnvironment.ReportError, + }, + }); + // This event will be handled in the CSharpWasm/main.js file + document.dispatchEvent(event); + } +} + +let executionEnvironment = null; + +// set everything up! +executionEnvironment = new ExecutionEnvironmentInternalCSharp(window); + +// make canvas take focus when clicked +Module.canvas.addEventListener("click", async function () { + Module.canvas.focus(); +}); + +// send terminal input on enter +runtimeLoadingProgress(1); + +executionEnvironment.signalReady(); From 25aae890f1dc3a815228a46bbd4df510fae98c8f Mon Sep 17 00:00:00 2001 From: 222448082Ashen <167728161+222448082Ashen@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:44:00 +0530 Subject: [PATCH 2/6] Add initial CSharpWasm and SplashKitOnline IDE files Introduces the CSharpWasm project for compiling and running C# code in the browser via WebAssembly, including code runner, bindings, build scripts, and configuration. Adds supporting files for the SplashKitOnline IDE prototype, such as JavaScript integration, CSS, documentation, and solution/project setup. --- .vscode/settings.json | 3 + CSharpWasm/.gitignore | 3 + CSharpWasm/CSharpCodeRunner.cs | 111 +++ CSharpWasm/CSharpWasm.csproj | 9 + CSharpWasm/Program.cs | 3 + CSharpWasm/Properties/AssemblyInfo.cs | 4 + CSharpWasm/Properties/launchSettings.json | 13 + CSharpWasm/README.md | 18 + CSharpWasm/SplashKitBindings.Generated.cs | 752 +++++++++++++++ CSharpWasm/buildAndCopy.sh | 56 ++ CSharpWasm/runtimeconfig.template.json | 10 + CSharpWasmExpo/bin/.gitignore | 6 + CSharpWasmExpo/bin/temp.txt | 0 CSharpWasmExpo/main.js | 71 ++ CSharpWasmExpo/splashKitMethods.generated.js | 912 ++++++++++++++++++ .../# Drawing Text and Shapes in SplashKit | 112 +++ .../drawing_text_shapes_review_suggestions.md | 41 + Documentation/overview.md | 369 +++++++ SplashKitOnlineIDEPrototypeImage.png | Bin 0 -> 243947 bytes SplashKitOnlineIDETitle.png | Bin 0 -> 9575 bytes SplashkitOnline-patch-2.sln | 24 + css/colours.css | 60 ++ css/components/codeMirror.css | 89 ++ css/components/demoMenu.css | 87 ++ css/components/modal.css | 145 +++ css/executionEnviroment.css | 101 ++ css/index.css | 416 ++++++++ css/shared.css | 290 ++++++ javascript/executionEnviroment.js | 1 + javascript/layout/layout.js | 48 + setup.js | 98 ++ setup.py | 37 + temp.txt | 3 + 33 files changed, 3892 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 CSharpWasm/.gitignore create mode 100644 CSharpWasm/CSharpCodeRunner.cs create mode 100644 CSharpWasm/CSharpWasm.csproj create mode 100644 CSharpWasm/Program.cs create mode 100644 CSharpWasm/Properties/AssemblyInfo.cs create mode 100644 CSharpWasm/Properties/launchSettings.json create mode 100644 CSharpWasm/README.md create mode 100644 CSharpWasm/SplashKitBindings.Generated.cs create mode 100644 CSharpWasm/buildAndCopy.sh create mode 100644 CSharpWasm/runtimeconfig.template.json create mode 100644 CSharpWasmExpo/bin/.gitignore create mode 100644 CSharpWasmExpo/bin/temp.txt create mode 100644 CSharpWasmExpo/main.js create mode 100644 CSharpWasmExpo/splashKitMethods.generated.js create mode 100644 Documentation/# Drawing Text and Shapes in SplashKit create mode 100644 Documentation/drawing_text_shapes_review_suggestions.md create mode 100644 Documentation/overview.md create mode 100644 SplashKitOnlineIDEPrototypeImage.png create mode 100644 SplashKitOnlineIDETitle.png create mode 100644 SplashkitOnline-patch-2.sln create mode 100644 css/colours.css create mode 100644 css/components/codeMirror.css create mode 100644 css/components/demoMenu.css create mode 100644 css/components/modal.css create mode 100644 css/executionEnviroment.css create mode 100644 css/index.css create mode 100644 css/shared.css create mode 100644 javascript/executionEnviroment.js create mode 100644 javascript/layout/layout.js create mode 100644 setup.js create mode 100644 setup.py create mode 100644 temp.txt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a45f8ab --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "/home/code/project/SplashkitOnline-patch-2/SplashKitWasm/cmake" +} \ No newline at end of file diff --git a/CSharpWasm/.gitignore b/CSharpWasm/.gitignore new file mode 100644 index 0000000..50b34e5 --- /dev/null +++ b/CSharpWasm/.gitignore @@ -0,0 +1,3 @@ +# Build results +bin/ +obj/ diff --git a/CSharpWasm/CSharpCodeRunner.cs b/CSharpWasm/CSharpCodeRunner.cs new file mode 100644 index 0000000..f8d0a86 --- /dev/null +++ b/CSharpWasm/CSharpCodeRunner.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.JavaScript; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Net.Http; +using System.Threading.Tasks; + +public class DiagnosticInfo +{ + public int Line { get; set; } + public string Message { get; set; } +} + +public partial class CSharpCodeRunner +{ + + static async Task LoadAssemblyFromServer(string assemblyName) + { + var baseUrl = GetHRef(); + var url = $"/CSharpWasmExpo/bin/{assemblyName}"; + + try + { + using var httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) }; + var assemblyBytes = await httpClient.GetByteArrayAsync(url); + return MetadataReference.CreateFromImage(assemblyBytes); + } + catch (Exception ex) + { + Console.WriteLine($"Error loading assembly {assemblyName}: {ex.Message}"); + throw; + } + } + + [JSExport] + internal static Task CompileAndRun(string code) + { + return Task.Run(async () => + { + try + { + var syntaxTree = CSharpSyntaxTree.ParseText(code); + + // Use Task.WhenAll to run async LoadAssemblyFromServer in parallel + var references = await Task.WhenAll( + LoadAssemblyFromServer("mscorlib.dll"), + LoadAssemblyFromServer("netstandard.dll"), + LoadAssemblyFromServer("System.Console.dll"), + LoadAssemblyFromServer("System.Private.CoreLib.dll"), + LoadAssemblyFromServer("System.Runtime.dll"), + LoadAssemblyFromServer("CSharpWasm.dll") + ); + + // Create a compilation with the syntax tree and references + var compilation = CSharpCompilation.Create( + assemblyName: "DynamicAssembly", + syntaxTrees: new[] { syntaxTree }, + references: references, + options: new CSharpCompilationOptions(OutputKind.ConsoleApplication) + ); + + // Create a MemoryStream to store the compiled assembly + using var ms = new MemoryStream(); + var emitResult = compilation.Emit(ms); + + // Check for compilation errors + if (!emitResult.Success) + { + var errors = emitResult.Diagnostics + .Where(d => d.Severity == DiagnosticSeverity.Error) + .Select(d => new DiagnosticInfo + { + Line = d.Location.GetLineSpan().StartLinePosition.Line, + Message = d.GetMessage() + }) + .ToList(); + + // If you need to format it as a string, you can do: + var errorString = string.Join("\n", errors.Select(e => $"Line {e.Line}: {e.Message}")); + + return $"Compilation failed:\n{errorString}"; + } + + // Load the compiled assembly into the current AppDomain + ms.Seek(0, SeekOrigin.Begin); + #pragma warning disable IL2026 + var assembly = AppDomain.CurrentDomain.Load(ms.ToArray()); + #pragma warning restore IL2026 + + // Get the entry point and invoke it + var entryPoint = assembly.EntryPoint; + if (entryPoint != null) + { + var result = entryPoint.Invoke(null, null); + return result?.ToString() ?? "Execution complete, no output."; + } + + return "No entry point found."; + } + catch (Exception ex) + { + return $"Error: {ex}"; + } + }); + } + + [JSImport("window.location.href", "main.js")] + public static partial string GetHRef(); +} diff --git a/CSharpWasm/CSharpWasm.csproj b/CSharpWasm/CSharpWasm.csproj new file mode 100644 index 0000000..73b4a83 --- /dev/null +++ b/CSharpWasm/CSharpWasm.csproj @@ -0,0 +1,9 @@ + + + net8.0 + true + + + + + diff --git a/CSharpWasm/Program.cs b/CSharpWasm/Program.cs new file mode 100644 index 0000000..f038460 --- /dev/null +++ b/CSharpWasm/Program.cs @@ -0,0 +1,3 @@ +using System; + +Console.WriteLine("Hello World from C#!"); diff --git a/CSharpWasm/Properties/AssemblyInfo.cs b/CSharpWasm/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9ad9b57 --- /dev/null +++ b/CSharpWasm/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +[assembly:System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/CSharpWasm/Properties/launchSettings.json b/CSharpWasm/Properties/launchSettings.json new file mode 100644 index 0000000..4cdd8ef --- /dev/null +++ b/CSharpWasm/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "CSharpWasm": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:7281;http://localhost:5178", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + } + } +} diff --git a/CSharpWasm/README.md b/CSharpWasm/README.md new file mode 100644 index 0000000..a399feb --- /dev/null +++ b/CSharpWasm/README.md @@ -0,0 +1,18 @@ +# CSharpWasm + +This project enables building and running C# code in the browser using WebAssembly (Wasm). + +## Project Structure + +- **SplashKitInterop.cs**: This file contains the logic for binding C# code with JavaScript functions, enabling the use of SplashKit functions within the C# environment. + +- **CSharpCodeRunner.cs**: A class responsible for compiling C# code, providing the necessary functionality to run the code dynamically. + +- **buildAndCopy.sh**: A shell script that builds the project and copies the necessary files into the `../CSharpWasmExpo` directory. This script helps automate the build process for easy integration with the browser. + +### Running the Build and Copy Script + +To build the project and copy the necessary files into the `../CSharpWasmExpo` directory, run the following shell script: + +```bash +./buildAndCopy.sh diff --git a/CSharpWasm/SplashKitBindings.Generated.cs b/CSharpWasm/SplashKitBindings.Generated.cs new file mode 100644 index 0000000..28c013f --- /dev/null +++ b/CSharpWasm/SplashKitBindings.Generated.cs @@ -0,0 +1,752 @@ +using System.Runtime.InteropServices.JavaScript; + +namespace SplashKitSDK +{ + public partial class SplashKit + { + [JSImport("SplashKitBackendWASM.free_all_animation_scripts", "main.js")] + public static partial void FreeAllAnimationScripts(); + + [JSImport("SplashKitBackendWASM.free_animation_script", "main.js")] + public static partial void FreeAnimationScript(string name); + + [JSImport("SplashKitBackendWASM.has_animation_script", "main.js")] + public static partial bool HasAnimationScript(string name); + + [JSImport("SplashKitBackendWASM.audio_ready", "main.js")] + public static partial bool AudioReady(); + + [JSImport("SplashKitBackendWASM.close_audio", "main.js")] + public static partial void CloseAudio(); + + [JSImport("SplashKitBackendWASM.open_audio", "main.js")] + public static partial void OpenAudio(); + + [JSImport("SplashKitBackendWASM.fade_music_in", "main.js")] + public static partial void FadeMusicIn(string name, int ms); + + [JSImport("SplashKitBackendWASM.fade_music_in", "main.js")] + public static partial void FadeMusicIn(string name, int times, int ms); + + [JSImport("SplashKitBackendWASM.fade_music_out", "main.js")] + public static partial void FadeMusicOut(int ms); + + [JSImport("SplashKitBackendWASM.free_all_music", "main.js")] + public static partial void FreeAllMusic(); + + [JSImport("SplashKitBackendWASM.has_music", "main.js")] + public static partial bool HasMusic(string name); + + [JSImport("SplashKitBackendWASM.music_playing", "main.js")] + public static partial bool MusicPlaying(); + + [JSImport("SplashKitBackendWASM.music_volume", "main.js")] + public static partial double MusicVolume(); + + [JSImport("SplashKitBackendWASM.pause_music", "main.js")] + public static partial void PauseMusic(); + + [JSImport("SplashKitBackendWASM.play_music", "main.js")] + public static partial void PlayMusic(string name); + + [JSImport("SplashKitBackendWASM.play_music", "main.js")] + public static partial void PlayMusic(string name, int times); + + [JSImport("SplashKitBackendWASM.resume_music", "main.js")] + public static partial void ResumeMusic(); + + [JSImport("SplashKitBackendWASM.set_music_volume", "main.js")] + public static partial void SetMusicVolume(double volume); + + [JSImport("SplashKitBackendWASM.stop_music", "main.js")] + public static partial void StopMusic(); + + [JSImport("SplashKitBackendWASM.fade_all_sound_effects_out", "main.js")] + public static partial void FadeAllSoundEffectsOut(int ms); + + [JSImport("SplashKitBackendWASM.free_all_sound_effects", "main.js")] + public static partial void FreeAllSoundEffects(); + + [JSImport("SplashKitBackendWASM.has_sound_effect", "main.js")] + public static partial bool HasSoundEffect(string name); + + [JSImport("SplashKitBackendWASM.play_sound_effect", "main.js")] + public static partial void PlaySoundEffect(string name); + + [JSImport("SplashKitBackendWASM.play_sound_effect", "main.js")] + public static partial void PlaySoundEffect(string name, double volume); + + [JSImport("SplashKitBackendWASM.play_sound_effect", "main.js")] + public static partial void PlaySoundEffect(string name, int times); + + [JSImport("SplashKitBackendWASM.play_sound_effect", "main.js")] + public static partial void PlaySoundEffect(string name, int times, double volume); + + [JSImport("SplashKitBackendWASM.sound_effect_playing", "main.js")] + public static partial bool SoundEffectPlaying(string name); + + [JSImport("SplashKitBackendWASM.stop_sound_effect", "main.js")] + public static partial void StopSoundEffect(string name); + + [JSImport("SplashKitBackendWASM.camera_x", "main.js")] + public static partial double CameraX(); + + [JSImport("SplashKitBackendWASM.camera_y", "main.js")] + public static partial double CameraY(); + + [JSImport("SplashKitBackendWASM.move_camera_by", "main.js")] + public static partial void MoveCameraBy(double dx, double dy); + + [JSImport("SplashKitBackendWASM.move_camera_to", "main.js")] + public static partial void MoveCameraTo(double x, double y); + + [JSImport("SplashKitBackendWASM.set_camera_x", "main.js")] + public static partial void SetCameraX(double x); + + [JSImport("SplashKitBackendWASM.set_camera_y", "main.js")] + public static partial void SetCameraY(double y); + + [JSImport("SplashKitBackendWASM.to_screen_x", "main.js")] + public static partial double ToScreenX(double worldX); + + [JSImport("SplashKitBackendWASM.to_screen_y", "main.js")] + public static partial double ToScreenY(double worldY); + + [JSImport("SplashKitBackendWASM.to_world_x", "main.js")] + public static partial double ToWorldX(double screenX); + + [JSImport("SplashKitBackendWASM.to_world_y", "main.js")] + public static partial double ToWorldY(double screenY); + + [JSImport("SplashKitBackendWASM.circles_intersect", "main.js")] + public static partial bool CirclesIntersect(double c1X, double c1Y, double c1Radius, double c2X, double c2Y, double c2Radius); + + [JSImport("SplashKitBackendWASM.cosine", "main.js")] + public static partial float Cosine(float degrees); + + [JSImport("SplashKitBackendWASM.sine", "main.js")] + public static partial float Sine(float degrees); + + [JSImport("SplashKitBackendWASM.tangent", "main.js")] + public static partial float Tangent(float degrees); + + [JSImport("SplashKitBackendWASM.point_in_circle", "main.js")] + public static partial bool PointInCircle(double ptx, double pty, double cx, double cy, double radius); + + [JSImport("SplashKitBackendWASM.point_in_rectangle", "main.js")] + public static partial bool PointInRectangle(double ptx, double pty, double rectX, double rectY, double rectWidth, double rectHeight); + + [JSImport("SplashKitBackendWASM.pop_clip", "main.js")] + public static partial void PopClip(); + + [JSImport("SplashKitBackendWASM.reset_clip", "main.js")] + public static partial void ResetClip(); + + [JSImport("SplashKitBackendWASM.clear_screen", "main.js")] + public static partial void ClearScreen(); + + [JSImport("SplashKitBackendWASM.number_of_displays", "main.js")] + public static partial int NumberOfDisplays(); + + [JSImport("SplashKitBackendWASM.refresh_screen", "main.js")] + public static partial void RefreshScreen(); + + [JSImport("SplashKitBackendWASM.screen_height", "main.js")] + public static partial int ScreenHeight(); + + [JSImport("SplashKitBackendWASM.screen_width", "main.js")] + public static partial int ScreenWidth(); + + [JSImport("SplashKitBackendWASM.take_screenshot", "main.js")] + public static partial void TakeScreenshot(string basename); + + [JSImport("SplashKitBackendWASM.bitmap_height", "main.js")] + public static partial int BitmapHeight(string name); + + [JSImport("SplashKitBackendWASM.bitmap_width", "main.js")] + public static partial int BitmapWidth(string name); + + [JSImport("SplashKitBackendWASM.draw_bitmap", "main.js")] + public static partial void DrawBitmap(string name, double x, double y); + + [JSImport("SplashKitBackendWASM.free_all_bitmaps", "main.js")] + public static partial void FreeAllBitmaps(); + + [JSImport("SplashKitBackendWASM.has_bitmap", "main.js")] + public static partial bool HasBitmap(string name); + + [JSImport("SplashKitBackendWASM.font_has_size", "main.js")] + public static partial bool FontHasSize(string name, int fontSize); + + [JSImport("SplashKitBackendWASM.font_load_size", "main.js")] + public static partial void FontLoadSize(string name, int fontSize); + + [JSImport("SplashKitBackendWASM.free_all_fonts", "main.js")] + public static partial void FreeAllFonts(); + + [JSImport("SplashKitBackendWASM.has_font", "main.js")] + public static partial bool HasFont(string name); + + [JSImport("SplashKitBackendWASM.text_height", "main.js")] + public static partial int TextHeight(string text, string fnt, int fontSize); + + [JSImport("SplashKitBackendWASM.text_width", "main.js")] + public static partial int TextWidth(string text, string fnt, int fontSize); + + [JSImport("SplashKitBackendWASM.process_events", "main.js")] + public static partial void ProcessEvents(); + + [JSImport("SplashKitBackendWASM.quit_requested", "main.js")] + public static partial bool QuitRequested(); + + [JSImport("SplashKitBackendWASM.reset_quit", "main.js")] + public static partial void ResetQuit(); + + [JSImport("SplashKitBackendWASM.any_key_pressed", "main.js")] + public static partial bool AnyKeyPressed(); + + [JSImport("SplashKitBackendWASM.hide_mouse", "main.js")] + public static partial void HideMouse(); + + [JSImport("SplashKitBackendWASM.mouse_shown", "main.js")] + public static partial bool MouseShown(); + + [JSImport("SplashKitBackendWASM.mouse_x", "main.js")] + public static partial float MouseX(); + + [JSImport("SplashKitBackendWASM.mouse_y", "main.js")] + public static partial float MouseY(); + + [JSImport("SplashKitBackendWASM.move_mouse", "main.js")] + public static partial void MoveMouse(double x, double y); + + [JSImport("SplashKitBackendWASM.show_mouse", "main.js")] + public static partial void ShowMouse(); + + [JSImport("SplashKitBackendWASM.show_mouse", "main.js")] + public static partial void ShowMouse(bool show); + + [JSImport("SplashKitBackendWASM.end_reading_text", "main.js")] + public static partial void EndReadingText(); + + [JSImport("SplashKitBackendWASM.reading_text", "main.js")] + public static partial bool ReadingText(); + + [JSImport("SplashKitBackendWASM.text_entry_cancelled", "main.js")] + public static partial bool TextEntryCancelled(); + + [JSImport("SplashKitBackendWASM.text_input", "main.js")] + public static partial string TextInput(); + + [JSImport("SplashKitBackendWASM.add_column", "main.js")] + public static partial void AddColumn(int width); + + [JSImport("SplashKitBackendWASM.add_column_relative", "main.js")] + public static partial void AddColumnRelative(double width); + + [JSImport("SplashKitBackendWASM.button", "main.js")] + public static partial bool Button(string text); + + [JSImport("SplashKitBackendWASM.button", "main.js")] + public static partial bool Button(string labelText, string text); + + [JSImport("SplashKitBackendWASM.checkbox", "main.js")] + public static partial bool Checkbox(string text, bool value); + + [JSImport("SplashKitBackendWASM.checkbox", "main.js")] + public static partial bool Checkbox(string labelText, string text, bool value); + + [JSImport("SplashKitBackendWASM.disable_interface", "main.js")] + public static partial void DisableInterface(); + + [JSImport("SplashKitBackendWASM.draw_interface", "main.js")] + public static partial void DrawInterface(); + + [JSImport("SplashKitBackendWASM.enable_interface", "main.js")] + public static partial void EnableInterface(); + + [JSImport("SplashKitBackendWASM.end_inset", "main.js")] + public static partial void EndInset(string name); + + [JSImport("SplashKitBackendWASM.end_panel", "main.js")] + public static partial void EndPanel(string name); + + [JSImport("SplashKitBackendWASM.end_popup", "main.js")] + public static partial void EndPopup(string name); + + [JSImport("SplashKitBackendWASM.end_treenode", "main.js")] + public static partial void EndTreenode(string labelText); + + [JSImport("SplashKitBackendWASM.enter_column", "main.js")] + public static partial void EnterColumn(); + + [JSImport("SplashKitBackendWASM.get_interface_label_width", "main.js")] + public static partial int GetInterfaceLabelWidth(); + + [JSImport("SplashKitBackendWASM.header", "main.js")] + public static partial bool Header(string labelText); + + [JSImport("SplashKitBackendWASM.interface_enabled", "main.js")] + public static partial bool InterfaceEnabled(); + + [JSImport("SplashKitBackendWASM.label_element", "main.js")] + public static partial void LabelElement(string text); + + [JSImport("SplashKitBackendWASM.last_element_changed", "main.js")] + public static partial bool LastElementChanged(); + + [JSImport("SplashKitBackendWASM.last_element_confirmed", "main.js")] + public static partial bool LastElementConfirmed(); + + [JSImport("SplashKitBackendWASM.leave_column", "main.js")] + public static partial void LeaveColumn(); + + [JSImport("SplashKitBackendWASM.number_box", "main.js")] + public static partial float NumberBox(float value, float step); + + [JSImport("SplashKitBackendWASM.number_box", "main.js")] + public static partial float NumberBox(string labelText, float value, float step); + + [JSImport("SplashKitBackendWASM.open_popup", "main.js")] + public static partial void OpenPopup(string name); + + [JSImport("SplashKitBackendWASM.paragraph", "main.js")] + public static partial void Paragraph(string text); + + [JSImport("SplashKitBackendWASM.reset_layout", "main.js")] + public static partial void ResetLayout(); + + [JSImport("SplashKitBackendWASM.set_interface_font", "main.js")] + public static partial void SetInterfaceFont(string fnt); + + [JSImport("SplashKitBackendWASM.set_interface_font_size", "main.js")] + public static partial void SetInterfaceFontSize(int size); + + [JSImport("SplashKitBackendWASM.set_interface_label_width", "main.js")] + public static partial void SetInterfaceLabelWidth(int width); + + [JSImport("SplashKitBackendWASM.set_interface_spacing", "main.js")] + public static partial void SetInterfaceSpacing(int spacing, int padding); + + [JSImport("SplashKitBackendWASM.set_layout_height", "main.js")] + public static partial void SetLayoutHeight(int height); + + [JSImport("SplashKitBackendWASM.single_line_layout", "main.js")] + public static partial void SingleLineLayout(); + + [JSImport("SplashKitBackendWASM.slider", "main.js")] + public static partial float Slider(float value, float minValue, float maxValue); + + [JSImport("SplashKitBackendWASM.slider", "main.js")] + public static partial float Slider(string labelText, float value, float minValue, float maxValue); + + [JSImport("SplashKitBackendWASM.split_into_columns", "main.js")] + public static partial void SplitIntoColumns(int count); + + [JSImport("SplashKitBackendWASM.split_into_columns", "main.js")] + public static partial void SplitIntoColumns(int count, int lastWidth); + + [JSImport("SplashKitBackendWASM.split_into_columns_relative", "main.js")] + public static partial void SplitIntoColumnsRelative(int count, double lastWidth); + + [JSImport("SplashKitBackendWASM.start_custom_layout", "main.js")] + public static partial void StartCustomLayout(); + + [JSImport("SplashKitBackendWASM.start_inset", "main.js")] + public static partial void StartInset(string name, int height); + + [JSImport("SplashKitBackendWASM.start_popup", "main.js")] + public static partial bool StartPopup(string name); + + [JSImport("SplashKitBackendWASM.start_treenode", "main.js")] + public static partial bool StartTreenode(string labelText); + + [JSImport("SplashKitBackendWASM.text_box", "main.js")] + public static partial string TextBox(string value); + + [JSImport("SplashKitBackendWASM.text_box", "main.js")] + public static partial string TextBox(string labelText, string value); + + [JSImport("SplashKitBackendWASM.free_all_json", "main.js")] + public static partial void FreeAllJson(); + + [JSImport("SplashKitBackendWASM.close_log_process", "main.js")] + public static partial void CloseLogProcess(); + + [JSImport("SplashKitBackendWASM.accept_all_new_connections", "main.js")] + public static partial bool AcceptAllNewConnections(); + + [JSImport("SplashKitBackendWASM.broadcast_message", "main.js")] + public static partial void BroadcastMessage(string aMsg); + + [JSImport("SplashKitBackendWASM.broadcast_message", "main.js")] + public static partial void BroadcastMessage(string aMsg, string name); + + [JSImport("SplashKitBackendWASM.check_network_activity", "main.js")] + public static partial void CheckNetworkActivity(); + + [JSImport("SplashKitBackendWASM.clear_messages", "main.js")] + public static partial void ClearMessages(string name); + + [JSImport("SplashKitBackendWASM.close_all_connections", "main.js")] + public static partial void CloseAllConnections(); + + [JSImport("SplashKitBackendWASM.close_all_servers", "main.js")] + public static partial void CloseAllServers(); + + [JSImport("SplashKitBackendWASM.close_connection", "main.js")] + public static partial bool CloseConnection(string name); + + [JSImport("SplashKitBackendWASM.close_server", "main.js")] + public static partial bool CloseServer(string name); + + [JSImport("SplashKitBackendWASM.has_connection", "main.js")] + public static partial bool HasConnection(string name); + + [JSImport("SplashKitBackendWASM.has_messages", "main.js")] + public static partial bool HasMessages(); + + [JSImport("SplashKitBackendWASM.has_messages", "main.js")] + public static partial bool HasMessages(string name); + + [JSImport("SplashKitBackendWASM.has_new_connections", "main.js")] + public static partial bool HasNewConnections(); + + [JSImport("SplashKitBackendWASM.has_server", "main.js")] + public static partial bool HasServer(string name); + + [JSImport("SplashKitBackendWASM.hex_str_to_ipv4", "main.js")] + public static partial string HexStrToIpv4(string aHex); + + [JSImport("SplashKitBackendWASM.hex_to_dec_string", "main.js")] + public static partial string HexToDecString(string aHex); + + [JSImport("SplashKitBackendWASM.hex_to_mac", "main.js")] + public static partial string HexToMac(string hexStr); + + [JSImport("SplashKitBackendWASM.ipv4_to_hex", "main.js")] + public static partial string Ipv4ToHex(string aIP); + + [JSImport("SplashKitBackendWASM.is_connection_open", "main.js")] + public static partial bool IsConnectionOpen(string name); + + [JSImport("SplashKitBackendWASM.is_valid_ipv4", "main.js")] + public static partial bool IsValidIpv4(string ip); + + [JSImport("SplashKitBackendWASM.is_valid_mac", "main.js")] + public static partial bool IsValidMac(string macAddress); + + [JSImport("SplashKitBackendWASM.mac_to_hex", "main.js")] + public static partial string MacToHex(string macAddress); + + [JSImport("SplashKitBackendWASM.my_ip", "main.js")] + public static partial string MyIP(); + + [JSImport("SplashKitBackendWASM.read_message_data", "main.js")] + public static partial string ReadMessageData(string name); + + [JSImport("SplashKitBackendWASM.reconnect", "main.js")] + public static partial void Reconnect(string name); + + [JSImport("SplashKitBackendWASM.release_all_connections", "main.js")] + public static partial void ReleaseAllConnections(); + + [JSImport("SplashKitBackendWASM.send_message_to", "main.js")] + public static partial bool SendMessageTo(string aMsg, string name); + + [JSImport("SplashKitBackendWASM.server_has_new_connection", "main.js")] + public static partial bool ServerHasNewConnection(string name); + + [JSImport("SplashKitBackendWASM.has_gpio", "main.js")] + public static partial bool HasGpio(); + + [JSImport("SplashKitBackendWASM.raspi_cleanup", "main.js")] + public static partial void RaspiCleanup(); + + [JSImport("SplashKitBackendWASM.raspi_init", "main.js")] + public static partial void RaspiInit(); + + [JSImport("SplashKitBackendWASM.raspi_spi_close", "main.js")] + public static partial int RaspiSpiClose(int handle); + + [JSImport("SplashKitBackendWASM.raspi_spi_open", "main.js")] + public static partial int RaspiSpiOpen(int channel, int speed, int spiFlags); + + [JSImport("SplashKitBackendWASM.free_resource_bundle", "main.js")] + public static partial void FreeResourceBundle(string name); + + [JSImport("SplashKitBackendWASM.has_resource_bundle", "main.js")] + public static partial bool HasResourceBundle(string name); + + [JSImport("SplashKitBackendWASM.load_resource_bundle", "main.js")] + public static partial void LoadResourceBundle(string name, string filename); + + [JSImport("SplashKitBackendWASM.path_to_resources", "main.js")] + public static partial string PathToResources(); + + [JSImport("SplashKitBackendWASM.set_resources_path", "main.js")] + public static partial void SetResourcesPath(string path); + + [JSImport("SplashKitBackendWASM.create_sprite_pack", "main.js")] + public static partial void CreateSpritePack(string name); + + [JSImport("SplashKitBackendWASM.current_sprite_pack", "main.js")] + public static partial string CurrentSpritePack(); + + [JSImport("SplashKitBackendWASM.draw_all_sprites", "main.js")] + public static partial void DrawAllSprites(); + + [JSImport("SplashKitBackendWASM.free_all_sprites", "main.js")] + public static partial void FreeAllSprites(); + + [JSImport("SplashKitBackendWASM.free_sprite_pack", "main.js")] + public static partial void FreeSpritePack(string name); + + [JSImport("SplashKitBackendWASM.has_sprite", "main.js")] + public static partial bool HasSprite(string name); + + [JSImport("SplashKitBackendWASM.has_sprite_pack", "main.js")] + public static partial bool HasSpritePack(string name); + + [JSImport("SplashKitBackendWASM.select_sprite_pack", "main.js")] + public static partial void SelectSpritePack(string name); + + [JSImport("SplashKitBackendWASM.update_all_sprites", "main.js")] + public static partial void UpdateAllSprites(); + + [JSImport("SplashKitBackendWASM.update_all_sprites", "main.js")] + public static partial void UpdateAllSprites(float pct); + + [JSImport("SplashKitBackendWASM.read_char", "main.js")] + public static partial char ReadChar(); + + [JSImport("SplashKitBackendWASM.read_line", "main.js")] + public static partial string ReadLine(); + + [JSImport("SplashKitBackendWASM.terminal_has_input", "main.js")] + public static partial bool TerminalHasInput(); + + [JSImport("SplashKitBackendWASM.write", "main.js")] + public static partial void Write(char data); + + [JSImport("SplashKitBackendWASM.write", "main.js")] + public static partial void Write(double data); + + [JSImport("SplashKitBackendWASM.write", "main.js")] + public static partial void Write(int data); + + [JSImport("SplashKitBackendWASM.write", "main.js")] + public static partial void Write(string text); + + [JSImport("SplashKitBackendWASM.write_line", "main.js")] + public static partial void WriteLine(char data); + + [JSImport("SplashKitBackendWASM.write_line", "main.js")] + public static partial void WriteLine(); + + [JSImport("SplashKitBackendWASM.write_line", "main.js")] + public static partial void WriteLine(double data); + + [JSImport("SplashKitBackendWASM.write_line", "main.js")] + public static partial void WriteLine(int data); + + [JSImport("SplashKitBackendWASM.write_line", "main.js")] + public static partial void WriteLine(string line); + + [JSImport("SplashKitBackendWASM.free_all_timers", "main.js")] + public static partial void FreeAllTimers(); + + [JSImport("SplashKitBackendWASM.has_timer", "main.js")] + public static partial bool HasTimer(string name); + + [JSImport("SplashKitBackendWASM.pause_timer", "main.js")] + public static partial void PauseTimer(string name); + + [JSImport("SplashKitBackendWASM.reset_timer", "main.js")] + public static partial void ResetTimer(string name); + + [JSImport("SplashKitBackendWASM.resume_timer", "main.js")] + public static partial void ResumeTimer(string name); + + [JSImport("SplashKitBackendWASM.start_timer", "main.js")] + public static partial void StartTimer(string name); + + [JSImport("SplashKitBackendWASM.stop_timer", "main.js")] + public static partial void StopTimer(string name); + + [JSImport("SplashKitBackendWASM.timer_paused", "main.js")] + public static partial bool TimerPaused(string name); + + [JSImport("SplashKitBackendWASM.timer_started", "main.js")] + public static partial bool TimerStarted(string name); + + [JSImport("SplashKitBackendWASM.base64_decode", "main.js")] + public static partial string Base64Decode(string input); + + [JSImport("SplashKitBackendWASM.base64_encode", "main.js")] + public static partial string Base64Encode(string input); + + [JSImport("SplashKitBackendWASM.bin_to_hex", "main.js")] + public static partial string BinToHex(string binStr); + + [JSImport("SplashKitBackendWASM.bin_to_oct", "main.js")] + public static partial string BinToOct(string binStr); + + [JSImport("SplashKitBackendWASM.contains", "main.js")] + public static partial bool Contains(string text, string subtext); + + [JSImport("SplashKitBackendWASM.convert_to_double", "main.js")] + public static partial double ConvertToDouble(string text); + + [JSImport("SplashKitBackendWASM.convert_to_integer", "main.js")] + public static partial int ConvertToInteger(string text); + + [JSImport("SplashKitBackendWASM.greatest_common_divisor", "main.js")] + public static partial int GreatestCommonDivisor(int number1, int number2); + + [JSImport("SplashKitBackendWASM.hex_to_bin", "main.js")] + public static partial string HexToBin(string hexStr); + + [JSImport("SplashKitBackendWASM.hex_to_oct", "main.js")] + public static partial string HexToOct(string hexStr); + + [JSImport("SplashKitBackendWASM.index_of", "main.js")] + public static partial int IndexOf(string text, string subtext); + + [JSImport("SplashKitBackendWASM.is_binary", "main.js")] + public static partial bool IsBinary(string binStr); + + [JSImport("SplashKitBackendWASM.is_double", "main.js")] + public static partial bool IsDouble(string text); + + [JSImport("SplashKitBackendWASM.is_hex", "main.js")] + public static partial bool IsHex(string hexStr); + + [JSImport("SplashKitBackendWASM.is_integer", "main.js")] + public static partial bool IsInteger(string text); + + [JSImport("SplashKitBackendWASM.is_number", "main.js")] + public static partial bool IsNumber(string text); + + [JSImport("SplashKitBackendWASM.is_octal", "main.js")] + public static partial bool IsOctal(string octalStr); + + [JSImport("SplashKitBackendWASM.is_prime_number", "main.js")] + public static partial bool IsPrimeNumber(int number); + + [JSImport("SplashKitBackendWASM.least_common_multiple", "main.js")] + public static partial int LeastCommonMultiple(int number1, int number2); + + [JSImport("SplashKitBackendWASM.length_of", "main.js")] + public static partial int LengthOf(string text); + + [JSImport("SplashKitBackendWASM.oct_to_bin", "main.js")] + public static partial string OctToBin(string octalStr); + + [JSImport("SplashKitBackendWASM.oct_to_hex", "main.js")] + public static partial string OctToHex(string octStr); + + [JSImport("SplashKitBackendWASM.replace_all", "main.js")] + public static partial string ReplaceAll(string text, string substr, string newText); + + [JSImport("SplashKitBackendWASM.square_root", "main.js")] + public static partial double SquareRoot(int number); + + [JSImport("SplashKitBackendWASM.to_lowercase", "main.js")] + public static partial string ToLowercase(string text); + + [JSImport("SplashKitBackendWASM.to_uppercase", "main.js")] + public static partial string ToUppercase(string text); + + [JSImport("SplashKitBackendWASM.trim", "main.js")] + public static partial string Trim(string text); + + [JSImport("SplashKitBackendWASM.rnd", "main.js")] + public static partial int Rnd(int min, int max); + + [JSImport("SplashKitBackendWASM.rnd", "main.js")] + public static partial float Rnd(); + + [JSImport("SplashKitBackendWASM.rnd", "main.js")] + public static partial int Rnd(int ubound); + + [JSImport("SplashKitBackendWASM.delay", "main.js")] + public static partial void Delay(int milliseconds); + + [JSImport("SplashKitBackendWASM.close_all_windows", "main.js")] + public static partial void CloseAllWindows(); + + [JSImport("SplashKitBackendWASM.close_current_window", "main.js")] + public static partial void CloseCurrentWindow(); + + [JSImport("SplashKitBackendWASM.close_window", "main.js")] + public static partial void CloseWindow(string name); + + [JSImport("SplashKitBackendWASM.current_window_has_border", "main.js")] + public static partial bool CurrentWindowHasBorder(); + + [JSImport("SplashKitBackendWASM.current_window_height", "main.js")] + public static partial int CurrentWindowHeight(); + + [JSImport("SplashKitBackendWASM.current_window_is_fullscreen", "main.js")] + public static partial bool CurrentWindowIsFullscreen(); + + [JSImport("SplashKitBackendWASM.current_window_toggle_border", "main.js")] + public static partial void CurrentWindowToggleBorder(); + + [JSImport("SplashKitBackendWASM.current_window_toggle_fullscreen", "main.js")] + public static partial void CurrentWindowToggleFullscreen(); + + [JSImport("SplashKitBackendWASM.current_window_width", "main.js")] + public static partial int CurrentWindowWidth(); + + [JSImport("SplashKitBackendWASM.current_window_x", "main.js")] + public static partial int CurrentWindowX(); + + [JSImport("SplashKitBackendWASM.current_window_y", "main.js")] + public static partial int CurrentWindowY(); + + [JSImport("SplashKitBackendWASM.has_window", "main.js")] + public static partial bool HasWindow(string caption); + + [JSImport("SplashKitBackendWASM.move_current_window_to", "main.js")] + public static partial void MoveCurrentWindowTo(int x, int y); + + [JSImport("SplashKitBackendWASM.move_window_to", "main.js")] + public static partial void MoveWindowTo(string name, int x, int y); + + [JSImport("SplashKitBackendWASM.resize_current_window", "main.js")] + public static partial void ResizeCurrentWindow(int width, int height); + + [JSImport("SplashKitBackendWASM.set_current_window", "main.js")] + public static partial void SetCurrentWindow(string name); + + [JSImport("SplashKitBackendWASM.window_close_requested", "main.js")] + public static partial bool WindowCloseRequested(string name); + + [JSImport("SplashKitBackendWASM.window_has_border", "main.js")] + public static partial bool WindowHasBorder(string name); + + [JSImport("SplashKitBackendWASM.window_height", "main.js")] + public static partial int WindowHeight(string name); + + [JSImport("SplashKitBackendWASM.window_is_fullscreen", "main.js")] + public static partial bool WindowIsFullscreen(string name); + + [JSImport("SplashKitBackendWASM.window_toggle_border", "main.js")] + public static partial void WindowToggleBorder(string name); + + [JSImport("SplashKitBackendWASM.window_toggle_fullscreen", "main.js")] + public static partial void WindowToggleFullscreen(string name); + + [JSImport("SplashKitBackendWASM.window_width", "main.js")] + public static partial int WindowWidth(string name); + + [JSImport("SplashKitBackendWASM.window_x", "main.js")] + public static partial int WindowX(string name); + + [JSImport("SplashKitBackendWASM.window_y", "main.js")] + public static partial int WindowY(string name); + + } +} \ No newline at end of file diff --git a/CSharpWasm/buildAndCopy.sh b/CSharpWasm/buildAndCopy.sh new file mode 100644 index 0000000..f5d6871 --- /dev/null +++ b/CSharpWasm/buildAndCopy.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Define source and target directories +BIN_DIR="./bin/Debug/net8.0" +TARGET_DIR="../CSharpWasmExpo/bin" +FRAMEWORK_SRC="./bin/Debug/net8.0/wwwroot/_framework" +FRAMEWORK_DEST="../CSharpWasmExpo/wwwroot/_framework" + +# List of files to copy +FILES=( + "mscorlib.dll" + "netstandard.dll" + "CSharpWasm.dll" + "System.Console.dll" + "System.Private.CoreLib.dll" + "System.Runtime.dll" +) + +echo "Building the project..." +dotnet build + +# Check if the build succeeded +if [ $? -eq 0 ]; then + echo "Build succeeded!" + + # Copy required files from bin to target directory + echo "Copying files to $TARGET_DIR..." + mkdir -p "$TARGET_DIR" # Create the target directory if it doesn't exist + for FILE in "${FILES[@]}"; do + if [[ -f "$BIN_DIR/$FILE" ]]; then + cp "$BIN_DIR/$FILE" "$TARGET_DIR/" + echo "Copied $FILE to $TARGET_DIR." + else + echo "Warning: $FILE not found in $BIN_DIR." + fi + done + + # Copy _framework directory to the target destination + echo "Copying _framework directory..." + if [[ -d "$FRAMEWORK_SRC" ]]; then + mkdir -p "$FRAMEWORK_DEST" # Create the target directory if it doesn't exist + cp -r "$FRAMEWORK_SRC"/* "$FRAMEWORK_DEST/" + echo "Copied _framework to $FRAMEWORK_DEST." + else + echo "Error: Framework directory not found at $FRAMEWORK_SRC." + exit 1 + fi + + echo "Build and copy process completed successfully!" +else + echo "Build failed. Exiting script." + exit 1 +fi \ No newline at end of file diff --git a/CSharpWasm/runtimeconfig.template.json b/CSharpWasm/runtimeconfig.template.json new file mode 100644 index 0000000..b96a943 --- /dev/null +++ b/CSharpWasm/runtimeconfig.template.json @@ -0,0 +1,10 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "host": "browser" + } + ] + } +} \ No newline at end of file diff --git a/CSharpWasmExpo/bin/.gitignore b/CSharpWasmExpo/bin/.gitignore new file mode 100644 index 0000000..e949427 --- /dev/null +++ b/CSharpWasmExpo/bin/.gitignore @@ -0,0 +1,6 @@ +CSharpWasm.dll +mscorlib.dll +netstandard.dll +System.Console.dll +System.Private.CoreLib.dll +System.Runtime.dll \ No newline at end of file diff --git a/CSharpWasmExpo/bin/temp.txt b/CSharpWasmExpo/bin/temp.txt new file mode 100644 index 0000000..e69de29 diff --git a/CSharpWasmExpo/main.js b/CSharpWasmExpo/main.js new file mode 100644 index 0000000..b8b414d --- /dev/null +++ b/CSharpWasmExpo/main.js @@ -0,0 +1,71 @@ +import { dotnet } from "./wwwroot/_framework/dotnet.js"; +import methods from "./splashKitMethods.generated.js"; + +const parseMethods = (methods) => { + const methodList = methods + .split(",") + .map((method) => method.trim().replace("\n", "")) + .filter(Boolean); + + const bindingsFunctions = {}; + + for (const name of methodList) { + try { + bindingsFunctions[name] = eval(name); + } catch (e) { + console.warn(e); + } + } + + return bindingsFunctions; +}; + +const loadDotNet = async () => { + const { setModuleImports, getAssemblyExports, getConfig } = await dotnet + .withDiagnosticTracing(false) + .withApplicationArgumentsFromQuery() + .create(); + + const skFunctions = parseMethods(methods); + + setModuleImports("main.js", { + window: { + location: { + href: () => globalThis.window.location.href, + }, + }, + SplashKitBackendWASM: skFunctions, + }); + + const config = getConfig(); + const exports = await getAssemblyExports(config.mainAssemblyName); + return exports; +}; + +const CompileAndRun = async (code, reportError) => { + try { + const exports = await loadDotNet(); + const result = await exports.CSharpCodeRunner.CompileAndRun(code); + if (result.includes("Compilation failed")) { + const errors = result.split(":"); + const errorLine = errors[1].split("Line"); + + const indexCorrector = 1; + const filePath = "__USERCODE__/code/main.cs"; + reportError( + filePath, + result, + Number(errorLine[1]) + indexCorrector, + null, + true, + ); + } + } catch (error) { + console.error("Error during code execution:", error); + } +}; + +// This event will be trigger by the csharp compiler +document.addEventListener("compileAndRun", (ev) => { + CompileAndRun(ev.detail.program[0].source, ev.detail.reportError); +}); diff --git a/CSharpWasmExpo/splashKitMethods.generated.js b/CSharpWasmExpo/splashKitMethods.generated.js new file mode 100644 index 0000000..4e41d20 --- /dev/null +++ b/CSharpWasmExpo/splashKitMethods.generated.js @@ -0,0 +1,912 @@ +const methods = ` + accept_all_new_connections, + accept_new_connection, + add_column, + add_column_relative, + alpha_of, + angle_between, + animation_count, + animation_current_cell, + animation_current_vector, + animation_ended, + animation_entered_frame, + animation_frame_time, + animation_index, + animation_name, + animation_script_name, + animation_script_named, + any_key_pressed, + apply_matrix, + assign_animation, + audio_ready, + base64_decode, + base64_encode, + bin_to_dec, + bin_to_hex, + bin_to_oct, + bitmap_bounding_circle, + bitmap_bounding_rectangle, + bitmap_button, + bitmap_cell_center, + bitmap_cell_circle, + bitmap_cell_columns, + bitmap_cell_count, + bitmap_cell_height, + bitmap_cell_offset, + bitmap_cell_rectangle, + bitmap_cell_rows, + bitmap_cell_width, + bitmap_center, + bitmap_circle_collision, + bitmap_collision, + bitmap_filename, + bitmap_height, + bitmap_name, + bitmap_named, + bitmap_point_collision, + bitmap_quad_collision, + bitmap_ray_collision, + bitmap_rectangle_collision, + bitmap_rectangle_of_cell, + bitmap_set_cell_details, + bitmap_triangle_collision, + bitmap_valid, + bitmap_width, + blue_of, + brightness_of, + broadcast_message, + button, + calculate_collision_direction, + call_for_all_sprites, + call_on_sprite_event, + camera_position, + camera_x, + camera_y, + center_camera_on, + center_point, + check_network_activity, + checkbox, + circle_at, + circle_quad_intersect, + circle_radius, + circle_ray_intersection, + circle_triangle_intersect, + circle_x, + circle_y, + circles_intersect, + clear_bitmap, + clear_messages, + clear_screen, + clear_window, + close_all_connections, + close_all_servers, + close_all_windows, + close_audio, + close_connection, + close_current_window, + close_log_process, + close_message, + close_server, + close_window, + closest_point_on_circle, + closest_point_on_line, + closest_point_on_line_from_circle, + closest_point_on_lines, + closest_point_on_rect_from_circle, + closest_point_on_triangle_from_circle, + color_alice_blue, + color_antique_white, + color_aqua, + color_aquamarine, + color_azure, + color_beige, + color_bisque, + color_black, + color_blanched_almond, + color_blue, + color_blue_violet, + color_bright_green, + color_brown, + color_burly_wood, + color_cadet_blue, + color_chartreuse, + color_chocolate, + color_coral, + color_cornflower_blue, + color_cornsilk, + color_crimson, + color_cyan, + color_dark_blue, + color_dark_cyan, + color_dark_goldenrod, + color_dark_gray, + color_dark_green, + color_dark_khaki, + color_dark_magenta, + color_dark_olive_green, + color_dark_orange, + color_dark_orchid, + color_dark_red, + color_dark_salmon, + color_dark_sea_green, + color_dark_slate_blue, + color_dark_slate_gray, + color_dark_turquoise, + color_dark_violet, + color_deep_pink, + color_deep_sky_blue, + color_dim_gray, + color_dodger_blue, + color_firebrick, + color_floral_white, + color_forest_green, + color_fuchsia, + color_gainsboro, + color_ghost_white, + color_gold, + color_goldenrod, + color_gray, + color_green, + color_green_yellow, + color_honeydew, + color_hot_pink, + color_indian_red, + color_indigo, + color_ivory, + color_khaki, + color_lavender, + color_lavender_blush, + color_lawn_green, + color_lemon_chiffon, + color_light_blue, + color_light_coral, + color_light_cyan, + color_light_goldenrod_yellow, + color_light_gray, + color_light_green, + color_light_pink, + color_light_salmon, + color_light_sea_green, + color_light_sky_blue, + color_light_slate_gray, + color_light_steel_blue, + color_light_yellow, + color_lime, + color_lime_green, + color_linen, + color_magenta, + color_maroon, + color_medium_aquamarine, + color_medium_blue, + color_medium_orchid, + color_medium_purple, + color_medium_sea_green, + color_medium_slate_blue, + color_medium_spring_green, + color_medium_turquoise, + color_medium_violet_red, + color_midnight_blue, + color_mint_cream, + color_misty_rose, + color_moccasin, + color_navajo_white, + color_navy, + color_old_lace, + color_olive, + color_olive_drab, + color_orange, + color_orange_red, + color_orchid, + color_pale_goldenrod, + color_pale_green, + color_pale_turquoise, + color_pale_violet_red, + color_papaya_whip, + color_peach_puff, + color_peru, + color_pink, + color_plum, + color_powder_blue, + color_purple, + color_red, + color_rosy_brown, + color_royal_blue, + color_saddle_brown, + color_salmon, + color_sandy_brown, + color_sea_green, + color_sea_shell, + color_sienna, + color_silver, + color_sky_blue, + color_slate_blue, + color_slate_gray, + color_slider, + color_snow, + color_spring_green, + color_steel_blue, + color_swinburne_red, + color_tan, + color_teal, + color_thistle, + color_to_string, + color_tomato, + color_transparent, + color_turquoise, + color_violet, + color_wheat, + color_white, + color_white_smoke, + color_yellow, + color_yellow_green, + connection_count, + connection_ip, + connection_named, + connection_port, + contains, + convert_to_double, + convert_to_integer, + cosine, + create_animation, + create_bitmap, + create_json, + create_server, + create_sprite, + create_sprite_pack, + create_timer, + current_clip, + current_sprite_pack, + current_ticks, + current_window, + current_window_has_border, + current_window_height, + current_window_is_fullscreen, + current_window_position, + current_window_toggle_border, + current_window_toggle_fullscreen, + current_window_width, + current_window_x, + current_window_y, + dec_to_bin, + dec_to_hex, + dec_to_ipv4, + dec_to_oct, + delay, + deregister_callback_on_key_down, + deregister_callback_on_key_typed, + deregister_callback_on_key_up, + deregister_free_notifier, + disable_interface, + display_details, + display_dialog, + display_height, + display_name, + display_width, + display_x, + display_y, + distant_point_on_circle, + distant_point_on_circle_heading, + dot_product, + download_bitmap, + download_font, + download_music, + download_sound_effect, + draw_all_sprites, + draw_bitmap, + draw_bitmap_on_bitmap, + draw_bitmap_on_window, + draw_circle, + draw_circle_on_bitmap, + draw_circle_on_window, + draw_collected_text, + draw_ellipse, + draw_ellipse_on_bitmap, + draw_ellipse_on_window, + draw_interface, + draw_line, + draw_line_on_bitmap, + draw_line_on_window, + draw_pixel, + draw_pixel_on_bitmap, + draw_pixel_on_window, + draw_quad, + draw_quad_on_bitmap, + draw_quad_on_window, + draw_rectangle, + draw_rectangle_on_bitmap, + draw_rectangle_on_window, + draw_sprite, + draw_text, + draw_text_on_bitmap, + draw_text_on_window, + draw_triangle, + draw_triangle_on_bitmap, + draw_triangle_on_window, + enable_interface, + end_inset, + end_panel, + end_popup, + end_reading_text, + end_treenode, + enter_column, + fade_all_sound_effects_out, + fade_music_in, + fade_music_out, + fade_sound_effect_out, + fetch_new_connection, + file_as_string, + fill_circle, + fill_circle_on_bitmap, + fill_circle_on_window, + fill_ellipse, + fill_ellipse_on_bitmap, + fill_ellipse_on_window, + fill_quad, + fill_quad_on_bitmap, + fill_quad_on_window, + fill_rectangle, + fill_rectangle_on_bitmap, + fill_rectangle_on_window, + fill_triangle, + fill_triangle_on_bitmap, + fill_triangle_on_window, + font_has_size, + font_load_size, + font_named, + free_all_animation_scripts, + free_all_bitmaps, + free_all_fonts, + free_all_json, + free_all_music, + free_all_sound_effects, + free_all_sprites, + free_all_timers, + free_animation, + free_animation_script, + free_bitmap, + free_font, + free_json, + free_music, + free_resource_bundle, + free_response, + free_sound_effect, + free_sprite, + free_sprite_pack, + free_timer, + get_font_style, + get_interface_label_width, + get_pixel, + get_pixel_from_window, + get_system_font, + greatest_common_divisor, + green_of, + has_animation_named, + has_animation_script, + has_bitmap, + has_connection, + has_font, + has_gpio, + has_incoming_requests, + has_messages, + has_music, + has_new_connections, + has_resource_bundle, + has_server, + has_sound_effect, + has_sprite, + has_sprite_pack, + has_timer, + has_window, + header, + hex_str_to_ipv4, + hex_to_bin, + hex_to_dec, + hex_to_dec_string, + hex_to_mac, + hex_to_oct, + hide_mouse, + hsb_color, + hsb_color_slider, + http_get, + http_post, + http_response_to_string, + hue_of, + identity_matrix, + index_of, + init_custom_logger, + inset_rectangle, + interface_enabled, + interface_style_panel, + intersection, + ipv4_to_dec, + ipv4_to_hex, + is_binary, + is_connection_open, + is_current_window, + is_delete_request_for, + is_double, + is_get_request_for, + is_hex, + is_integer, + is_number, + is_octal, + is_options_request_for, + is_post_request_for, + is_prime_number, + is_put_request_for, + is_request_for, + is_trace_request_for, + is_valid_ipv4, + is_valid_mac, + is_zero_vector, + json_count_keys, + json_from_color, + json_from_file, + json_from_string, + json_has_key, + json_read_array, + json_read_bool, + json_read_number, + json_read_number_as_double, + json_read_number_as_int, + json_read_object, + json_read_string, + json_set_array, + json_set_bool, + json_set_number, + json_set_object, + json_set_string, + json_to_color, + json_to_file, + json_to_string, + key_down, + key_name, + key_released, + key_typed, + key_up, + label_element, + last_connection, + last_element_changed, + last_element_confirmed, + least_common_multiple, + leave_column, + length_of, + line_from, + line_intersection_point, + line_intersects_circle, + line_intersects_lines, + line_intersects_rect, + line_length, + line_length_squared, + line_mid_point, + line_normal, + line_to_string, + lines_from, + lines_intersect, + load_animation_script, + load_bitmap, + load_font, + load_music, + load_resource_bundle, + load_sound_effect, + log, + mac_to_hex, + matrix_inverse, + matrix_multiply, + matrix_to_string, + message_connection, + message_count, + message_data, + message_data_bytes, + message_host, + message_port, + message_protocol, + mouse_clicked, + mouse_down, + mouse_movement, + mouse_position, + mouse_position_vector, + mouse_shown, + mouse_up, + mouse_wheel_scroll, + mouse_x, + mouse_y, + move_camera_by, + move_camera_to, + move_current_window_to, + move_mouse, + move_sprite, + move_sprite_to, + move_window_to, + music_filename, + music_name, + music_named, + music_playing, + music_valid, + music_volume, + my_ip, + name_for_connection, + new_connection_count, + next_web_request, + number_box, + number_of_displays, + oct_to_bin, + oct_to_dec, + oct_to_hex, + open_audio, + open_connection, + open_popup, + open_window, + option_defaults, + option_draw_to, + option_flip_x, + option_flip_xy, + option_flip_y, + option_line_width, + option_part_bmp, + option_rotate_bmp, + option_scale_bmp, + option_to_screen, + option_to_world, + option_with_animation, + option_with_bitmap_cell, + paragraph, + path_to_resource, + path_to_resources, + pause_music, + pause_timer, + pixel_drawn_at_point, + play_music, + play_sound_effect, + point_at, + point_at_origin, + point_in_circle, + point_in_quad, + point_in_rectangle, + point_in_triangle, + point_in_window, + point_line_distance, + point_offset_by, + point_offset_from_origin, + point_on_line, + point_on_screen, + point_point_angle, + point_point_distance, + point_to_string, + pop_clip, + process_events, + push_clip, + quad_from, + quad_ray_intersection, + quads_intersect, + quit_requested, + random_bitmap_point, + random_color, + random_rgb_color, + random_screen_point, + random_window_point, + raspi_cleanup, + raspi_get_mode, + raspi_init, + raspi_read, + raspi_set_mode, + raspi_set_pull_up_down, + raspi_set_pwm_dutycycle, + raspi_set_pwm_frequency, + raspi_set_pwm_range, + raspi_spi_close, + raspi_spi_open, + raspi_spi_transfer, + raspi_write, + ray_circle_intersect_distance, + ray_intersection_point, + read_char, + read_line, + read_message, + read_message_data, + reading_text, + reconnect, + rect_in_window, + rect_on_screen, + rectangle_around, + rectangle_bottom, + rectangle_center, + rectangle_circle_intersect, + rectangle_from, + rectangle_left, + rectangle_offset_by, + rectangle_ray_intersection, + rectangle_right, + rectangle_to_string, + rectangle_top, + rectangles_intersect, + red_of, + refresh_screen, + refresh_window, + register_callback_on_key_down, + register_callback_on_key_typed, + register_callback_on_key_up, + register_free_notifier, + release_all_connections, + remote_raspi_cleanup, + remote_raspi_get_mode, + remote_raspi_init, + remote_raspi_read, + remote_raspi_set_mode, + remote_raspi_set_pull_up_down, + remote_raspi_set_pwm_dutycycle, + remote_raspi_set_pwm_frequency, + remote_raspi_set_pwm_range, + remote_raspi_write, + replace_all, + request_body, + request_has_query_parameter, + request_headers, + request_method, + request_query_parameter, + request_query_string, + request_uri, + request_uri_stubs, + reset_clip, + reset_layout, + reset_new_connection_count, + reset_quit, + reset_timer, + resize_current_window, + resize_window, + resolve_collision, + restart_animation, + resume_music, + resume_timer, + retrieve_connection, + rgb_color, + rgba_color, + rnd, + rotation_matrix, + same_point, + saturation_of, + save_bitmap, + save_response_to_file, + scale_matrix, + scale_rotate_translate_matrix, + screen_center, + screen_height, + screen_rectangle, + screen_width, + select_sprite_pack, + send_css_file_response, + send_file_response, + send_html_file_response, + send_javascript_file_response, + send_message_to, + send_response, + server_has_new_connection, + server_named, + set_camera_position, + set_camera_x, + set_camera_y, + set_clip, + set_current_window, + set_font_style, + set_interface_accent_color, + set_interface_border_color, + set_interface_colors_auto, + set_interface_element_color, + set_interface_element_shadows, + set_interface_font, + set_interface_font_size, + set_interface_label_width, + set_interface_panel_shadows, + set_interface_root_text_color, + set_interface_shadows, + set_interface_spacing, + set_interface_style, + set_interface_text_color, + set_layout_height, + set_music_volume, + set_quad_point, + set_resources_path, + set_udp_packet_size, + setup_collision_mask, + show_mouse, + sine, + single_line_layout, + slider, + sound_effect_filename, + sound_effect_name, + sound_effect_named, + sound_effect_playing, + sound_effect_valid, + split, + split_into_columns, + split_into_columns_relative, + split_uri_stubs, + sprite_add_layer, + sprite_add_to_velocity, + sprite_add_value, + sprite_anchor_point, + sprite_anchor_position, + sprite_animation_has_ended, + sprite_animation_name, + sprite_at, + sprite_bitmap_collision, + sprite_bring_layer_forward, + sprite_bring_layer_to_front, + sprite_call_on_event, + sprite_center_point, + sprite_circle, + sprite_circle_collision, + sprite_collision, + sprite_collision_bitmap, + sprite_collision_circle, + sprite_collision_kind, + sprite_collision_rectangle, + sprite_current_cell, + sprite_current_cell_rectangle, + sprite_dx, + sprite_dy, + sprite_has_value, + sprite_heading, + sprite_height, + sprite_hide_layer, + sprite_layer, + sprite_layer_circle, + sprite_layer_count, + sprite_layer_height, + sprite_layer_index, + sprite_layer_name, + sprite_layer_offset, + sprite_layer_rectangle, + sprite_layer_width, + sprite_location_matrix, + sprite_mass, + sprite_move_from_anchor_point, + sprite_move_to, + sprite_name, + sprite_named, + sprite_offscreen, + sprite_on_screen_at, + sprite_point_collision, + sprite_position, + sprite_quad_collision, + sprite_ray_collision, + sprite_rectangle_collision, + sprite_replay_animation, + sprite_rotation, + sprite_scale, + sprite_screen_rectangle, + sprite_send_layer_backward, + sprite_send_layer_to_back, + sprite_set_anchor_point, + sprite_set_collision_bitmap, + sprite_set_collision_kind, + sprite_set_dx, + sprite_set_dy, + sprite_set_heading, + sprite_set_layer_offset, + sprite_set_mass, + sprite_set_move_from_anchor_point, + sprite_set_position, + sprite_set_rotation, + sprite_set_scale, + sprite_set_speed, + sprite_set_value, + sprite_set_velocity, + sprite_set_x, + sprite_set_y, + sprite_show_layer, + sprite_speed, + sprite_start_animation, + sprite_stop_calling_on_event, + sprite_toggle_layer_visible, + sprite_triangle_collision, + sprite_value, + sprite_value_count, + sprite_velocity, + sprite_visible_index_of_layer, + sprite_visible_layer, + sprite_visible_layer_count, + sprite_visible_layer_id, + sprite_width, + sprite_x, + sprite_y, + square_root, + start_custom_layout, + start_inset, + start_panel, + start_popup, + start_reading_text, + start_timer, + start_treenode, + start_web_server, + stop_calling_on_sprite_event, + stop_music, + stop_sound_effect, + stop_timer, + stop_web_server, + string_to_color, + take_screenshot, + tangent, + tangent_points, + terminal_has_input, + text_box, + text_entry_cancelled, + text_height, + text_input, + text_width, + timer_named, + timer_paused, + timer_started, + timer_ticks, + to_lowercase, + to_screen, + to_screen_x, + to_screen_y, + to_uppercase, + to_world, + to_world_x, + to_world_y, + translation_matrix, + triangle_barycenter, + triangle_from, + triangle_quad_intersect, + triangle_ray_intersection, + triangle_rectangle_intersect, + triangle_to_string, + triangles_from, + triangles_intersect, + trim, + udp_packet_size, + unit_vector, + update_all_sprites, + update_animation, + update_sprite, + update_sprite_animation, + vector_add, + vector_angle, + vector_from_angle, + vector_from_center_sprite_to_point, + vector_from_line, + vector_from_point_to_rect, + vector_from_to, + vector_in_rect, + vector_invert, + vector_limit, + vector_magnitude, + vector_magnitude_squared, + vector_multiply, + vector_normal, + vector_out_of_circle_from_circle, + vector_out_of_circle_from_point, + vector_out_of_rect_from_circle, + vector_out_of_rect_from_point, + vector_out_of_rect_from_rect, + vector_point_to_point, + vector_subtract, + vector_to, + vector_to_string, + vector_world_to_screen, + vectors_equal, + vectors_not_equal, + widest_points, + window_area, + window_caption, + window_close_requested, + window_has_border, + window_has_focus, + window_height, + window_is_fullscreen, + window_named, + window_position, + window_set_icon, + window_toggle_border, + window_toggle_fullscreen, + window_width, + window_with_focus, + window_x, + window_y, + write, + write_line +`; + +export default methods; \ No newline at end of file diff --git a/Documentation/# Drawing Text and Shapes in SplashKit b/Documentation/# Drawing Text and Shapes in SplashKit new file mode 100644 index 0000000..bb79aca --- /dev/null +++ b/Documentation/# Drawing Text and Shapes in SplashKit @@ -0,0 +1,112 @@ +# Drawing Text and Shapes in SplashKit + +**Author:** Stanoja Jovanovic +**Capstone Project A** +**Last Updated:** Week 8 + +--- + +## 🧭 Overview + +This tutorial introduces how to use SplashKit to draw basic text and shapes on the screen. These techniques are useful for creating user interfaces, loading screens, buttons, menus, and HUDs in SplashKit-based games or applications. + +--- + +## 🧱 What You'll Learn + +- How to draw text using `DrawText` +- How to draw shapes like rectangles, circles, and lines +- How to customize colors, fonts, and positions + +--- + +## 🎨 Drawing Text + +SplashKit provides an easy function to draw text to the screen: + +```cpp +DrawText("Hello, SplashKit!", COLOR_WHITE, "Arial", 24, 100, 100); +Parameters: + +"Hello, SplashKit!" – the text + +COLOR_WHITE – the text color + +"Arial" – the font (must be loaded first) + +24 – font size + +100, 100 – X and Y position on screen + +📌 Don’t forget to load the font first: +cpp +Copy code +LoadFont("Arial", "arial.ttf"); +⚠️ Make sure "arial.ttf" is placed in your project directory or in your resource path. + +⏹️ Drawing Shapes +1. Rectangle +cpp +Copy code +FillRectangle(COLOR_RED, 50, 150, 200, 100); +This draws a filled red rectangle at (50,150) with width 200 and height 100. + +To draw just the outline: + +cpp +Copy code +DrawRectangle(COLOR_BLACK, 50, 150, 200, 100); +2. Circle +cpp +Copy code +FillCircle(COLOR_BLUE, 300, 300, 50); +This draws a filled blue circle at (300,300) with a radius of 50. + +3. Line +cpp +Copy code +DrawLine(COLOR_GREEN, 100, 400, 300, 400); +This draws a green horizontal line from (100,400) to (300,400). + +🧪 Full Example +cpp +Copy code +#include "splashkit.h" + +int main() +{ + OpenWindow("Drawing Demo", 640, 480); + + LoadFont("Arial", "arial.ttf"); + + while (!WindowCloseRequested("Drawing Demo")) + { + ClearScreen(COLOR_WHITE); + + // Draw Text + DrawText("Main Menu", COLOR_BLACK, "Arial", 32, 220, 30); + + // Draw Shapes + FillRectangle(COLOR_RED, 100, 100, 150, 80); // Play Button + DrawText("Play", COLOR_WHITE, "Arial", 24, 140, 130); + + FillRectangle(COLOR_GRAY, 100, 200, 150, 80); // Exit Button + DrawText("Exit", COLOR_BLACK, "Arial", 24, 140, 230); + + // Draw Line + DrawLine(COLOR_BLACK, 0, 300, 640, 300); + + RefreshScreen(60); + } + + return 0; +} +💡 Tips +Use ClearScreen() before drawing each frame + +Use RefreshScreen(FPS) to control rendering speed + +Organize drawing functions into reusable parts (e.g., DrawMainMenu()) + +✅ Summary +With just a few simple functions, you can create functional and visually clear interfaces using SplashKit. Mastering text and shape drawing is an essential first step toward building full UI screens in your games or apps. diff --git a/Documentation/drawing_text_shapes_review_suggestions.md b/Documentation/drawing_text_shapes_review_suggestions.md new file mode 100644 index 0000000..1de0fe4 --- /dev/null +++ b/Documentation/drawing_text_shapes_review_suggestions.md @@ -0,0 +1,41 @@ +# Suggested Improvements for "Drawing Text and Shapes in SplashKit" Tutorial + +## Code Block Formatting Fixes + +1. **Line 22-26**: Fix the parameter explanation formatting +2. **Line 30-32**: Add proper cpp language tag to the LoadFont example +3. **Line 36**: Remove "Copy code" text +4. **Multiple locations**: Ensure all C++ code blocks use ```cpp + +## Content Additions + +### 1. Add Color Constants Section +```cpp +// Common SplashKit Colors +COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE +COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN, COLOR_GRAY +``` + +### 2. Enhance Font Loading Explanation +```cpp +// Load fonts during initialization (outside the main loop) +LoadFont("Arial", "arial.ttf"); +LoadFont("Title", "title_font.ttf"); +``` + +### 3. Add Drawing Order Note +``` +💡 **Drawing Order Matters**: Items drawn last appear on top. Draw backgrounds first, then UI elements. +``` + +## Technical Accuracy Improvements + +1. Clarify that `LoadFont()` should be called once during setup +2. Add note about SplashKit's resource folder conventions +3. Mention coordinate system (0,0 is top-left) + +## Overall Assessment: ⭐⭐⭐⭐☆ + +**Ready for merge with minor formatting fixes.** + +The tutorial is well-structured and beginner-friendly. The code examples are practical and the progression is logical. Main issues are formatting-related rather than content issues. \ No newline at end of file diff --git a/Documentation/overview.md b/Documentation/overview.md new file mode 100644 index 0000000..fea9acc --- /dev/null +++ b/Documentation/overview.md @@ -0,0 +1,369 @@ + +# IDE Architecture & Loading Process Documentation + +## Purpose + +Explains how each JavaScript file fits into the IDE startup, loading, and runtime process. Shows how the pieces interact to build the editor and initialize the environment. This documentation aims to explain the softwares architecture so that it can be understood better making the system easier to navigate, extend, and debug + + +## Files Overview + +| File | Role | Responsibilities | +|-----------------------------------------|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------| +| `server.js` | Server entry point | Serves `index.html` and assets on port 8000; starts the web server | +| `setup.js` | Dependency manager | Checks for and downloads missing backend WASM and JSON dependencies | +| `splashKitOnlineEnvParams.js` | Runtime config initializer | Sets global runtime environment variables and IDE parameters | +| `downloadHandler.js` | File download utility | Handles user-initiated project/file downloads | +| `compiler.js` | Compiler controller class | Creates compiler class to initialize, register and track compilers | +| `languageDefinitions.js` | Language metadata | Provides metadata for supported programming languages | +| `moduleEventTarget.js` | EventTarget wrapper | Wraps a global `EventTarget` | +| `loadSplashKit.js` | WASM runtime bootstrapper | Loads SplashKit backend binaries, sets up `Module`, dispatches runtime and output events | +| `fsevents.js` | FS event dispatcher | Wraps `EventTarget` to emit file operation events from Emscripten’s virtual FS (read, write, etc.) | +| `executionEnvironment_CodeProcessor.js` | Code transformer & runtime patcher | Makes user code async-safe and pausable using Babel transforms. | +| `executionEnvironment_Internal.js` | Sandbox execution controller | Runs, pauses, resets, and manages user programs inside the iframe sandbox. | +| `HTMLBuilderUtil.js` | HTML utility functions | Generates or modifies HTML snippets dynamically | +| `executionEnvironment.js` | Sandbox runtime controller | Manages iframe-based execution environment for running user code | +| `executionEnvironment_Page.js` | Terminal & UI Output Handler | Renders output in the IDE terminal, formats escape sequences, displays errors, manages canvas-terminal layout | +| `ExecutionEnvironmentInternalLoader.js` | Runtime Bootstrap Loader | Loads language runtime scripts, registers service worker, tracks init progress, and sends ready state to parent window | +| `SKOservice-worker.js` | Program event relay worker | Acts as a service worker queue for program input events like keyboard/mouse. | +| `AppStorage.js` | Local storage interface | Provides direct access to browser storage APIs | +| `IDBStoredProject.js` | IndexedDB file storage | Handles saving/loading of project files using IndexedDB | +| `unifiedfs.js` | Virtual filesystem bridge | Unifies memory-based and persistent file storage into one virtual FS | +| `projectInitializer.js` | Project loader/initializer | Loads files into editor, sets up project state | +| `modal.js` | Modal controller | Manages modal windows for alerts, file dialogs, etc. | +| `notifications.js` | Notification system | Displays notifications in designated notification area for inline IDE alerts and error messages | +| `treeview.js` | File tree UI manager | Renders the sidebar file explorer and handles file selection | +| `fallibleMessage.js` | Bidiirectional messaging class | Provides execution enviornment and the editor bidirectional communication | +| `editorMain.js` | Code editor integrator | Boots up and configures the CodeMirror editor instance and acts as a high level class | +| `fileview.js` | File panel UI manager | Uses `TreeView` to create the interactivity of the file panel UI | +| `projectLoadUI.js` | Project UI feedback handler | Shows UI for loading demo projects and provides indicators or errors during project load | +| `actionQueue.js` | Action sequencing engine | Manages async task execution, ordering, cancellation, and dependency in the form of a queue | +| `IDEStartupMain.js` | IDE bootstrap coordinator | Central script that initializes components and kicks off IDE startup sequence | +| `themes.js` | Theme manager | Defines and applies color themes by modifying CSS variables; updates UI via dropdown | + +--- +## Loading & Initialization Flow + +### Server Side + +server.js +↳ setup.js + +--- +### Client Side + +index.html + ↳ Header +  ↳ Loads client-side external packages +   ↳ Codemirror assets (browser code editor) +   ↳ Bootstrap +   ↳ Jzip +  ↳ Loads stylesheets +   ↳ baseTheme.css +   ↳ colours.css +   ↳ stylesheet.css +   ↳ (Includes bootstrap & codemirror styles) +  ↳ splashkit-javascript-hint.js + + ↳ Footer +  ↳ splashKitOnlineEnvParams.js +  ↳ downloadHandler.js +  ↳ compiler.js +  ↳ languageDefinitions.js +   ↳ moduleEventTarget.js +   ↳ loadsplashkit.js +   ↳ fsevents.js +   ↳ executionEnvironment_CodeProcessor.js +   ↳ executionEnvironment_Internal.js +  ↳ HTMLBuilderUtil.js +  ↳ executionEnvironment.js +   ↳ executionEnvironment.html +    ↳ executionEnvironment_Page.js +    ↳ ExecutionEnvironmentInternalLoader.js +     ↳ SKOservice-worker.js +  ↳ AppStorage.js +  ↳ IDBStoredProject.js +  ↳ unifiedfs.js +  ↳ projectInitializer.js +  ↳ modal.js +  ↳ notifications.js +  ↳ treeview.js +  ↳ fallibleMessage.js +  ↳ editorMain.js +  ↳ fileview.js +  ↳ projectLoadUI.js +  ↳ actionQueue.js +  ↳ IDEStartupMain.js +  ↳ themes.js + +--- +## Server Side Component Interactions + +### Server.js +- Includes necessary libraries and demo project files. +- Serves the `index.html` content to clients on port 8000, acting as the entry point for the IDE web app. +- **Triggers `setup.js`** at server startup to ensure all backend dependencies are available before the client IDE loads. + +### Setup.js +- Checks for required pre-built dependency files locally. +- Downloads missing dependencies such as: + - `splashkit_autocomplete.json` + - `SplashKitBackendWASM.js` + - `SplashKitBackendWASM.wasm` + - `compiler.zip` + - `wasi-sysroot.zip` + - `SplashKitBackendWASMCPP.js` + - `SplashKitBackendWASMCPP.worker.js` +--- + +## Client Side Component Interactions + +### splashKitOnlineEnvParams.js +- Loads environment parameters at runtime, either from defaults or URL query parameters. +- Used throughout the IDE to alter behavior. +- Included env parameters include: + - `language` (e.g., `javascript`, `cpp`) + - `useCompressedBinaries` - boolean (`on`/`off`) + - `useMinifiedInterface` - boolean (`on`/`off`) + - `isPRPreview` - boolean; **do not modify manually**. Used to enable development mode on peer-review deployments (e.g., GitHub PR preview builds). + +### downloadHandler.js +- Manages dynamic file downloading, adapting to environment flags like `isPRPreview` and `useCompressedBinaries` from `splashKitOnlineEnvParams.js`. +- If `isPRPreview` is enabled, reroutes asset URLs using `PRPathMap.json` to ensure correct pathing for GitHub PR deployments. Automatically patches all redirect entries based on the current subdirectory path. +- The main download function supports both compressed and uncompressed assets, using LZMA decompression if `useCompressedBinaries` is enabled . +- Uses XMLHttpRequests wrapped in promises, with optional progress callbacks to support loading UIs. +- Defines `DownloadSet` class that tracks the progress of multiple concurrent downloads and reports progress. + +### compiler.js +- Manages compiler registration, initialization, and usage across supported languages. +- Provides a base `Compiler` class that defines the interface for compilation, syntax checking, and readiness signaling. +- Tracks available compilers via a shared registry (`registeredCompilers`) and uses events to trigger when compilers are readythrough `registeredCompilersEvents`. +- `initializeLanguageCompilerFiles()` dynamically injects script tags into the document for the selected language, based on metadata defined in `languageDefinitions.js`. This allows compilers to be loaded only when needed. +- Other components can retrieve compilers using `getCompiler(name)` and listen for readiness via the compiler event system. + + +### languageDefinitions.js +- Defines the supported languages in the IDE, including metadata and setup instructions for compilers and runtimes. +- Each language definition includes: + - A `name`, human-readable `userVisibleName`, and multiple `aliases` (e.g., `'JS'`, `'CXX'`) + - File extension support (e.g., `.js`, `.cpp`) for both editing and compiling + - One or more `setups`, which define: + - Files to load into the runtime (`runtimeFiles`) + - WASM or other binary dependencies (`runtimeDependencies`) + - One or more `compilerFiles` to dynamically inject + - Estimated size of assets (`runtimeSizeAprox`, `compilerSizeAprox`) + - Environment flags like `needsSandbox`, `compiled`, or `supportHotReloading` + - Default project setup function (`getDefaultProject`) +- Used by files like `compiler.js` to determine which files and actions to do based on the selected language. +- Builds `SplashKitOnlineLanguageAliasMap`, allowing any component to retrieve full language definitions using either the language name or any alias. + +### moduleEventTarget.js +- Thin wrapper around a single shared EventTarget instance (moduleEvents). + +### loadsplashkit.js +- Dynamically loads and initializes the SplashKit WebAssembly runtime into the IDE. +- Defines a global Module object with definitions for `onRuntimeInitialized`, `print`, `preRun`, `canvas`, and `totalDependencies`. +- Dispatches events like `onRuntimeInitialized` via `moduleEvents` and output via a custom "print" event which intends to write to terminal in `executionEnvironment_Page.js`. +- Handles download and injection of the .wasm and .js runtime binaries using `DownloadSet` into the document. +- Allows access to the canvas in the DOM via `Module` allowing a Object Oriented approach. + +### fsevents.js +- Acts as a wrapper around `EventTarget` to listen for file system (FS) events. +- Sets up handlers on `FS.trackingDelegate` to emit events like before specified FS functions run +- `TestFSEvents` function created in order to test each case + +### executionEnvironment_CodeProcessor.js +- Preprocesses user-written code to make it safe for async execution in a single-threaded browser context using `asyncifyTransform` & `makeFunctionsAsyncAwaitTransform`. +- Rewrites all global variables, classes, and functions to attach to `window` for better cleanup/reset. +- Auto-awaits all user defined functions and class instantiations . +- Registers Babel plugins: `asyncify`, `makeFunctionsAsyncAwaitTransform`, and `findGlobalDeclarationsTransform`. +- Used by `processCodeForExecutionEnvironment()` to prepare code for sandboxed execution. +#### Deeper look into executionEnvironment_CodeProcessor +Credits to the developers of splashkit online for documenting this function deeply. This comes from the JS file direcly but is added here for clarity on the functionality as it provides an excellent overview. +- In order to run the code the user writes well, there are two main challenges. + 1. We want the user to be able to run loops - for example the 'main' loop. + However, Javascript executed in the same thread as the browser's interface, + meaning that a long running while loop will simply freeze the page. A 'main' + loop, like in a game, will only render the last frame once the script is terminated. + + To handle this, we use Javascripts async/await syntax, and modify the user's code as follows: + - All loops automatically await a timeout of 0 seconds after executing for more than ~25ms. + - screen_refresh (and other similar functions) await a window.requestAnimationFrame + - All user functions are marked as async, so that they can use await. + - Similarly, all calls to user functions are marked with await. + - Constructors cannot be async, so rename all constructors of user classes to `__constructor`, + + and call it when user classes are newed. `let player = new Player()` becomes `let player = (new Player()).__constructor()` + This same setup is used to enable code pausing, and stopping, by simply listening for pausing/stopping/continuing + around when it does the awaits. To stop, we simple throw a 'ForceBreakLoop' error. To continue, pause, we create + a promise and await it. To continue, we call that promise. + 2. We want the user to be able to declare global variables, however we also want to enable Strict Mode, and additionally want a way to remove them all when the program is reset. + + We handle this as follows: + - In an initial step, we identify and record all global variables into findGlobalDeclarationsTransform__userScope + - Next, we modify all declarations (variables, classes, functions) in the global scope, to directly set `window`. + For example `let a = 10;` becomes `window.a = 10;`. `function func(){}` becomes `window.func = function func(){}`. + To reset the globals, we just delete all the variables in findGlobalDeclarationsTransform__userScope (`delete window[globalVar];`) + +### executionEnvironment_Internal.js +- Manages execution control, reset logic, and exception handling for the users code. +- Provides `runProgram()`, `stopProgram()`, and `pauseProgram()` for runtime control. +- Cleans global scope and memory between runs using `ResetExecutionScope()`. +- Parses stack traces to map error lines back to user code for accurate error reporting with `parseErrorStack`. +- Attaches to FS events using `FSEvents` to report back file system changes. +- Dispatches execution state (started, stopped, paused) to the parent page. + +### HTMLBuilderUtil.js +- Provides helper functions for dynamically creating and manipulating HTML elements. +- Commonly used by UI-related components to construct DOM elements without manual DOM code duplication. +- Includes: + - `elem(tag, attrs, children)`: Creates an HTML element with attributes and child elements, including support for inline style objects. + - `elemFromText(text)`: Parses an HTML string into DOM nodes using `DOMParser`. + - `removeFadeOut(el, speed)`: Applies a fade-out transition to an element and removes it from the DOM after the animation completes. +- Used across various UI modules for building modals, notifications, or custom interface elements at runtime. + + +### executionEnvironment.js +- Implements `ExecutionEnvironment`, encompasses all environment functionality at a higher level. +- Launches an isolated iframe (`executionEnvironment.html`) to run user code, based on the selected language's metadata in `languageDefinitions.js`. +- Handles two-way messaging between the IDE and the iframe using `PromiseChannel`, allowing components to: + - Start, pause, continue, stop, or hotReload the user program + - Send runtime commands (e.g., initialize filesystem, report errors, write to terminal) + - Handle and dispatch events like `programStarted`, `onDownloadFail`, and `onCriticalInitializationFail` +- Maintains internal status tracking using `ExecutionStatus`. +- Creates control functions to wrap post messages in order to have a cleaner object oriented method of executing common functions with events set up. +- Loads `executionEnvironment.html` into the iframe + +### executionEnvironment_Page.js +- Handles terminal output rendering and formatting using DOM manipulation. +- Supports escape codes for colored output and terminal emulation. +- Displays runtime errors and stack traces. +- Provides `ReportError()` for printing error messaging to the terminal. +- Registers event listeners for output messages and error reporting. +- Implements basic loading UI: progress bar, failure message, and visibility toggles. + +### ExecutionEnvironmentInternalLoader.js +- Loads language-specific runtime JS files dynamically using `