diff --git a/examples/button-counter/package.json b/examples/button-counter/package.json index 349ade2..b41982b 100644 --- a/examples/button-counter/package.json +++ b/examples/button-counter/package.json @@ -16,7 +16,7 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "install": "babel App.jsx -o App.js && babel browser.jsx -o browser.js&& babel native.jsx -o native.js && browserify browser.js -o bundle.js", + "install": "babel App.jsx -o App.js && babel browser.jsx -o browser.js && babel native.jsx -o native.js && browserify browser.js -o bundle.js", "serve": "http-server" }, "author": "Alexey Kutepov ", diff --git a/examples/text-input/.babelrc b/examples/text-input/.babelrc new file mode 100644 index 0000000..45697dd --- /dev/null +++ b/examples/text-input/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-react"] +} \ No newline at end of file diff --git a/examples/text-input/.gitignore b/examples/text-input/.gitignore new file mode 100644 index 0000000..4c43fe6 --- /dev/null +++ b/examples/text-input/.gitignore @@ -0,0 +1 @@ +*.js \ No newline at end of file diff --git a/examples/text-input/App.jsx b/examples/text-input/App.jsx new file mode 100644 index 0000000..19508d3 --- /dev/null +++ b/examples/text-input/App.jsx @@ -0,0 +1,10 @@ +const React = require('react'); + +exports.App = () => { + const [name, setName] = React.useState(""); + + return
+ + setName(e.target.value)} placeholder='Input Text' /> +
; +}; diff --git a/examples/text-input/browser.jsx b/examples/text-input/browser.jsx new file mode 100644 index 0000000..5164367 --- /dev/null +++ b/examples/text-input/browser.jsx @@ -0,0 +1,7 @@ +const React = require('react'); +const ReactDom = require('react-dom/client'); +const { App } = require('./App.js'); + +const app = document.getElementById('app'); +const root = ReactDom.createRoot(app); +root.render() diff --git a/examples/text-input/index.html b/examples/text-input/index.html new file mode 100644 index 0000000..15ab01a --- /dev/null +++ b/examples/text-input/index.html @@ -0,0 +1,10 @@ + + + + Hello, React! + + +
+ + + diff --git a/examples/text-input/native.jsx b/examples/text-input/native.jsx new file mode 100644 index 0000000..2c8c3e5 --- /dev/null +++ b/examples/text-input/native.jsx @@ -0,0 +1,5 @@ +const React = require('react'); +const murayact = require('murayact'); +const { App } = require('./App.js'); + +murayact.render(); \ No newline at end of file diff --git a/examples/text-input/package.json b/examples/text-input/package.json new file mode 100644 index 0000000..360936b --- /dev/null +++ b/examples/text-input/package.json @@ -0,0 +1,24 @@ +{ + "name": "text-input", + "version": "0.0.1", + "description": "Simple example that works with both react-dom and murayact", + "devDependencies": { + "@babel/cli": "^7.28.0", + "@babel/preset-react": "^7.27.1", + "browserify": "^17.0.1", + "http-server": "^14.1.1" + }, + "dependencies": { + "react": "^18.1.0", + "react-dom": "^18.1.0", + "murayact": "^0.0.1" + }, + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "install": "babel App.jsx -o App.js && babel browser.jsx -o browser.js && babel native.jsx -o native.js && browserify browser.js -o bundle.js", + "serve": "http-server" + }, + "author": "Andrew Solomon ", + "license": "MIT" +} diff --git a/package-lock.json b/package-lock.json index 87062d8..7bd12f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,22 @@ "http-server": "^14.1.1" } }, + "examples/text-input": { + "version": "0.0.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "murayact": "^0.0.1", + "react": "^18.1.0", + "react-dom": "^18.1.0" + }, + "devDependencies": { + "@babel/cli": "^7.28.0", + "@babel/preset-react": "^7.27.1", + "browserify": "^17.0.1", + "http-server": "^14.1.1" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3517,6 +3533,10 @@ "acorn-node": "^1.2.0" } }, + "node_modules/text-input": { + "resolved": "examples/text-input", + "link": true + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/packages/muray/muray.cpp b/packages/muray/muray.cpp index 9959409..dcddcb3 100644 --- a/packages/muray/muray.cpp +++ b/packages/muray/muray.cpp @@ -1,5 +1,7 @@ // Node.js plugin that integrates together Raylib and MicroUI +#ifdef __LINUX__ #include +#endif #include #include extern "C" { @@ -9,6 +11,7 @@ extern "C" { #define FONT_SIZE 20 static mu_Context ctx = {0}; +static char input_buf[128]; static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 }; void MuButton(const v8::FunctionCallbackInfo &args) { @@ -18,6 +21,24 @@ void MuButton(const v8::FunctionCallbackInfo &args) { args.GetReturnValue().Set(v8::Boolean::New(isolate, result)); } +void MuLabel(const v8::FunctionCallbackInfo &args) { + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + auto label = *v8::String::Utf8Value(isolate, args[0]->ToString(context).ToLocalChecked()); + mu_label(&ctx, *v8::String::Utf8Value(isolate, args[0]->ToString(context).ToLocalChecked())); +} + +void MuInput(const v8::FunctionCallbackInfo &args) { + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + int val = mu_textbox(&ctx, input_buf, sizeof(input_buf)); + if (val & MU_RES_CHANGE) + { + mu_set_focus(&ctx, ctx.last_id); + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, input_buf).ToLocalChecked()); + } +} + void MuBeginWindow(const v8::FunctionCallbackInfo &args) { mu_begin_window(&ctx, "Murayact", mu_rect(20, 20, 300, 300)); mu_layout_row(&ctx, 2, (int[]) { 300, -1 }, 50); @@ -35,6 +56,15 @@ void MuUpdateInput(const v8::FunctionCallbackInfo &args) { if (IsMouseButtonPressed (button)) mu_input_mousedown(&ctx, x, y, 1 << button); if (IsMouseButtonReleased(button)) mu_input_mouseup (&ctx, x, y, 1 << button); } + + char c; + char input[2]; + if ((c = GetCharPressed())) + { + input[0] = c; + input[1] = '\0'; + mu_input_text(&ctx, input); + } } void MuBegin(const v8::FunctionCallbackInfo &args) { @@ -139,6 +169,8 @@ void Initialize(v8::Local exports) { NODE_SET_METHOD(exports, "mu_begin_window", MuBeginWindow); NODE_SET_METHOD(exports, "mu_end_window", MuEndWindow); NODE_SET_METHOD(exports, "mu_button", MuButton); + NODE_SET_METHOD(exports, "mu_label", MuLabel); + NODE_SET_METHOD(exports, "mu_input", MuInput); } NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) diff --git a/packages/murayact/index.js b/packages/murayact/index.js index 5f5aa23..747bc3d 100644 --- a/packages/murayact/index.js +++ b/packages/murayact/index.js @@ -27,8 +27,9 @@ const hostConfig = { }, shouldSetTextContent(type, props) { if (TRACE) console.log('shouldSetTextContent', type, props); - const childrenType = typeof props.children; - if (childrenType === 'string' || childrenType == 'number') return true; + // Documentation for shouldSetTextContent says you have to manage creating the text nodes in createInstance when this returns true. + // const childrenType = typeof props.children; + // if (childrenType === 'string' || childrenType == 'number') return true; return false; }, createTextInstance(text, _rootContainerInstance, _hostContext) { @@ -46,8 +47,7 @@ const hostConfig = { ) { if (TRACE) console.log("createInstance"); const elementProps = { ...props }; - delete elementProps.children; - const element = {type, ...elementProps, children: []}; + const element = { type, ...elementProps, children: [] }; if (type === 'Text') { throw 'TODO'; } @@ -109,7 +109,7 @@ const hostConfig = { oldProps, newProps, ) { - if (TRACE) console.log('commitUpdate', args); + if (TRACE) console.log('commitUpdate', instance, oldProps, newProps); for (let prop of updatePayload.props) { if (prop !== 'children') { instance[prop] = newProps[prop]; @@ -128,30 +128,56 @@ function renderTextElement(element) { } function renderElement(element) { switch (element.type) { - case 'window': - muray.mu_begin_window(); - for (let child of element.children) { - renderElement(child); - } - muray.mu_end_window(); - break; - case 'button': - let label = ""; - for (let child of element.children) { - label += renderTextElement(child); - } - if (muray.mu_button(label)) { - element.onClick(); - } - break; - default: - throw 'TODO'; + case 'window': + { + muray.mu_begin_window(); + for (let child of element.children) { + renderElement(child); + } + muray.mu_end_window(); + } break; + case 'div': + { + for (let child of element.children) { + renderElement(child); + } + } break; + case 'button': + { + let label = ""; + for (let child of element.children) { + label += renderTextElement(child); + } + if (muray.mu_button(label)) { + element.onClick(); + } + } break; + case 'label': + { + let label = ""; + for (let child of element.children) { + label += renderTextElement(child); + } + muray.mu_label(label); + } break; + case 'input': + { + let placeholder = element.placeholder || ""; + muray.mu_label(placeholder); + let value = muray.mu_input(); + if (value) { + const evt = { target: { value } }; + element.onChange(evt); + } + } break; + default: + throw 'TODO'; } } exports.render = (element) => { const container = MurayRenderer.createContainer( - {type: 'window'}, + { type: 'window' }, 0, // null, // false,