added a canvas with a basic API for now
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 6 Sep 2021 03:53:02 +0000 (22:53 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 6 Sep 2021 03:53:02 +0000 (22:53 -0500)
canvas.onyx [new file with mode: 0644]
serve.py
static/css/index.css
static/src/canvas.js [new file with mode: 0644]
static/src/index.js
static/src/resizer.js
static/src/storage.js
static/src/worker.js
templates/index.html

diff --git a/canvas.onyx b/canvas.onyx
new file mode 100644 (file)
index 0000000..07e830e
--- /dev/null
@@ -0,0 +1,13 @@
+package canvas
+
+init           :: () -> void #foreign "canvas" "init" ---
+clear          :: () -> void #foreign "canvas" "clear" ---
+set_fill_style :: (style: str) -> void #foreign "canvas" "set_fill_style" ---
+fill_rect      :: (x: i32, y: i32, w: i32, h: i32) -> void #foreign "canvas" "fill_rect" ---
+
+get_size_internal :: (out_width: ^i32, out_height: ^i32) -> void #foreign "canvas" "get_size" ---
+get_size :: () -> (i32, i32) {
+    sx, sy: i32;
+    get_size_internal(^sx, ^sy);
+    return sx, sy;
+}
index b5e521944c0b746b54b37839f27f483a6edbc134..4a8dc43d21f973f879c2ed053143705cdd72399a 100644 (file)
--- a/serve.py
+++ b/serve.py
@@ -56,7 +56,7 @@ def post_compile_onyx():
     with os.fdopen(code_file_desc, "w+b") as code_file:
         code_file.write(code.encode('utf-8'))
 
-    compile_process = subprocess.run(f"timeout -s KILL 10s onyx -r js --no-file-contents --no-colors -o {wasm_file_name} {code_file_name} ./read_line.onyx", shell=True, capture_output=True)
+    compile_process = subprocess.run(f"timeout -s KILL 10s onyx -r js --no-file-contents --no-colors -o {wasm_file_name} {code_file_name} ./read_line.onyx ./canvas.onyx", shell=True, capture_output=True)
 
     os.remove(code_file_name)
 
index 79ae5162311e42c0b3182cfce721aec2262aa086..11c5299deb217f420e9f987fbfedee9a8b9cd1b7 100644 (file)
@@ -1,8 +1,9 @@
 :root {
     --menu-bar-height: 32px;
-    --input-bar-height: 32px;
+    --input-bar-height: 48px;
     --divider-size: 4px;
     --left-half-width: 70%;
+    --top-half-height: 30%;
 }
 
 * {
@@ -110,7 +111,7 @@ select:hover {
 
 #code-result {
     height: 100vh;
-    max-height: calc(100vh - var(--menu-bar-height) - var(--input-bar-height));  /* subtract the padding */
+    max-height: calc(100vh - var(--menu-bar-height) - var(--input-bar-height) - var(--top-half-height) - var(--divider-size));  /* subtract the padding */
 
     background: var(--terminal-background-color); 
     color: var(--terminal-foreground-color);
@@ -138,6 +139,16 @@ select:hover {
     float: left;
 }
 
+#vertical-divider {
+    width: 100%;
+    height: var(--divider-size);
+    background-color: #777;
+    cursor: row-resize;
+
+    display: inline-block;
+    float: left;
+}
+
 #input-bar {
     background-color: var(--background-color);
     color: var(--foreground-color);
@@ -153,6 +164,11 @@ select:hover {
     font-family: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
 }
 
+#render-target {
+    width: 100%;
+    height: var(--top-half-height);
+}
+
 .jquery-modal.blocker.current {
     z-index: 100 !important;
 }
diff --git a/static/src/canvas.js b/static/src/canvas.js
new file mode 100644 (file)
index 0000000..fa9c98f
--- /dev/null
@@ -0,0 +1,46 @@
+let canvas_element = "";
+let ctx = null;
+
+function update_canvas_size() {
+    let canvas_data = new Int32Array(canvas_shared_buffer);
+    let box = canvas_element.getBoundingClientRect();
+
+    canvas_element.width  = box.width;
+    canvas_element.height = box.height;
+
+    Atomics.store(canvas_data, 0, box.width);
+    Atomics.store(canvas_data, 1, box.height);
+    Atomics.store(canvas_data, 2, 1);
+    Atomics.notify(canvas_data, 2);
+}
+
+function canvas_handler(instr) {
+    switch (instr[0]) {
+        case 'init': {
+            canvas_element = document.getElementById("render-target");
+            ctx = canvas_element.getContext('2d');
+            update_canvas_size();
+            break;
+        }
+
+        case 'set_fill_style': {
+            ctx.fillStyle = instr[1];
+            break;
+        }
+
+        case 'fill_rect': {
+            ctx.fillRect(instr[1], instr[2], instr[3], instr[4]);
+            let canvas_data = new Int32Array(canvas_shared_buffer);
+
+            // TODO: Do I need to synchonize the worker thread and the master thread?
+            Atomics.store(canvas_data, 2, 1);
+            Atomics.notify(canvas_data, 2);
+            break;
+        }
+
+        case 'clear': {
+            ctx.clearRect(0, 0, canvas_element.width, canvas_element.height);
+            break;
+        }
+    }
+}
index 8bd78c02ad520e04fa79491408836f7b48a2116d..b7bd57f132b247285d9b3c0478d8c541a342f671 100644 (file)
@@ -3,7 +3,8 @@ let editor_keybind_mode = "normal";
 let editor_theme = "chrome";
 let ui_theme = "dark";
 
-let input_shared_buffer = new SharedArrayBuffer(1024 * Uint8Array.BYTES_PER_ELEMENT);
+let input_shared_buffer  = new SharedArrayBuffer(1024 * Uint8Array.BYTES_PER_ELEMENT);
+let canvas_shared_buffer = new SharedArrayBuffer(3 * Int32Array.BYTES_PER_ELEMENT);
 
 async function clear_output() {
     let elem = document.getElementById('code-result');
@@ -46,11 +47,19 @@ async function run_wasm(wasm_bytes) {
                 update_running_msg();
                 break;
             }
+
+            case 'canvas': {
+                canvas_handler(e.data.data);
+            }
         }
     };
 
-    wasm_worker.postMessage({ type: 'set_buffer', data: input_shared_buffer });
-    wasm_worker.postMessage({ type: 'start',      data: wasm_bytes });
+    wasm_worker.postMessage({
+        type: 'set_buffers',
+        input_buffer:  input_shared_buffer,
+        canvas_buffer: canvas_shared_buffer,
+    });
+    wasm_worker.postMessage({ type: 'start', data: wasm_bytes });
     update_running_msg();
 }
 
@@ -260,10 +269,12 @@ window.onload = () => {
     populate_examples();
     load_settings();
 
-    make_resizer("horizontal-divider", "--left-half-width", (e) => {
+    make_resizer("horizontal-divider", "--left-half-width", "", (e) => {
         editor.resize(true);
     });
 
+    make_resizer("vertical-divider", "", "--top-half-height", null);
+
     document.getElementById("input-bar").addEventListener("keyup", (ev) => {
         if (ev.keyCode === 13) {
             submit_input();
index 1cc98fb15a6c8036aea66a61bb5305c3c8f380de..e748d07bd0cbe02620e3aa9d45701d4ae73b8618 100644 (file)
@@ -1,4 +1,4 @@
-function make_resizer(divider_id, css_prop_name, on_resize) {
+function make_resizer(divider_id, css_width_prop_name, css_height_prop_name, on_resize) {
     const elem = document.getElementById(divider_id);
     const left_elem = elem.previousElementSibling;
     const right_elem = elem.nextElementSibling;
@@ -7,14 +7,23 @@ function make_resizer(divider_id, css_prop_name, on_resize) {
     let mx = 0;
     let my = 0;
     let left_width = 0;
+    let top_height = 0;
 
     const mouse_move_handler = (e) => {
         const dx = e.clientX - mx;
         const dy = e.clientY - my;
 
-        const pw = elem.parentNode.getBoundingClientRect().width;
-        const lw = 100 * Math.min(pw - 10, Math.max(10, (left_width + dx))) / pw;
-        root.style.setProperty(css_prop_name, `${lw}%`);
+        if (css_width_prop_name != null) {
+            const pw = elem.parentNode.getBoundingClientRect().width;
+            const lw = 100 * Math.min(pw - 10, Math.max(10, (left_width + dx))) / pw;
+            root.style.setProperty(css_width_prop_name, `${lw}%`);
+        }
+
+        if (css_height_prop_name != null) {
+            const ph = elem.parentNode.getBoundingClientRect().height;
+            const th = 100 * Math.min(ph - 10, Math.max(10, (top_height + dy))) / ph;
+            root.style.setProperty(css_height_prop_name, `${th}%`);
+        }
 
         document.body.style.cursor = 'col-resize';
 
@@ -44,6 +53,7 @@ function make_resizer(divider_id, css_prop_name, on_resize) {
         mx = e.clientX;
         my = e.clientY;
         left_width = left_elem.getBoundingClientRect().width;
+        top_height = left_elem.getBoundingClientRect().height;
 
         document.addEventListener("mousemove", mouse_move_handler);
         document.addEventListener("mouseup",   mouse_up_handler);
index 49be20fd50871c141c1f6a4fc75c3bb3624e85f5..8edd14f9e6e02370fbe95d5382ee132c541ed4cb 100644 (file)
@@ -1,20 +1,27 @@
 function refresh_file_lists(callback) {
     $(".file-list").empty();
 
+    let filenames = [];
     let $lists = $(".file-list");
     for (let i=0; i < localStorage.length; i++) {
         let name = localStorage.key(i);
         if (name.startsWith("saved_")) {
             let filename = name.substr(6);
-            let $button = $(`<button class='file-select'>${filename}</button>`);
-            $button.on('click', (ev) => callback(filename));
+            filenames.push(filename);
+        }
+    }
 
-            let $delete_button = $(`<button class='file-delete'><i class='fas fa-trash'></i></button>`);
-            $delete_button.on('click', (ev) => delete_from_local_storage_with_name(filename, callback));
+    filenames.sort();
 
-            $lists.append($button);
-            $lists.append($delete_button);
-        }
+    for (let filename of filenames) {
+        let $button = $(`<button class='file-select'>${filename}</button>`);
+        $button.on('click', (ev) => callback(filename));
+
+        let $delete_button = $(`<button class='file-delete'><i class='fas fa-trash'></i></button>`);
+        $delete_button.on('click', (ev) => delete_from_local_storage_with_name(filename, callback));
+
+        $lists.append($button);
+        $lists.append($delete_button);
     }
 }
 
index 686d20c36de266c8ffbd20c90a3a8c28c672c20a..70bd4e343af2013855e46ddbd337a7533ce4f795 100644 (file)
@@ -1,4 +1,5 @@
 let input_shared_buffer = null;
+let canvas_data_buffer = null;
 let wasm_memory = null;
 
 let import_obj = {
@@ -38,13 +39,51 @@ let import_obj = {
             self.postMessage({ type: 'terminated' });
             self.close();
         },
+    },
+
+    canvas: {
+        init() {
+            self.postMessage({ type: 'canvas', data: [ 'init' ] });
+
+            let canvas_data = new Int32Array(canvas_data_buffer);
+            Atomics.wait(canvas_data, 2, 0);
+            Atomics.store(canvas_data, 2, 0);
+        },
+
+        clear() {
+            self.postMessage({ type: 'canvas', data: [ 'clear' ] });
+        },
+        
+        get_size(outwidth, outheight) {
+            let data = new DataView(wasm_memory.buffer);
+            let canvas_data = new Int32Array(canvas_data_buffer);
+
+            data.setInt32(outwidth, Atomics.load(canvas_data, 0), true);
+            data.setInt32(outheight, Atomics.load(canvas_data, 1), true);
+        },
+
+        set_fill_style(styleptr, stylelen) {
+            let strdata = new Uint8Array(wasm_memory.buffer, styleptr, stylelen);
+            let str = new TextDecoder().decode(strdata);
+
+            self.postMessage({ type: 'canvas', data: [ 'set_fill_style', str ] });
+        },
+
+        fill_rect(x, y, w, h) {
+            self.postMessage({ type: 'canvas', data: [ 'fill_rect', x, y, w, h ] });
+
+            let canvas_data = new Int32Array(canvas_data_buffer);
+            Atomics.wait(canvas_data, 2, 0);
+            Atomics.store(canvas_data, 2, 0);
+        }
     }
 };
 
 onmessage = function(m) {
     switch (m.data.type) {
-        case 'set_buffer': {
-            input_shared_buffer = m.data.data;
+        case 'set_buffers': {
+            input_shared_buffer = m.data.input_buffer;
+            canvas_data_buffer  = m.data.canvas_buffer;
             break;
         }
 
index 84860caa8348355bdd2fe3be386988482772d04d..98e5f0ef2baabb5f295f0ecd847d18b280f08e99 100644 (file)
@@ -11,6 +11,7 @@
         <script> window.ROOT_ENDPOINT = "{{ config['ENDPOINT']}}" </script>
         <script src="{{ config['ENDPOINT'] + url_for('static', filename='src/resizer.js') }}"></script>
         <script src="{{ config['ENDPOINT'] + url_for('static', filename='src/storage.js') }}"></script>
+        <script src="{{ config['ENDPOINT'] + url_for('static', filename='src/canvas.js') }}"></script>
         <script src="{{ config['ENDPOINT'] + url_for('static', filename='src/index.js') }}"></script>
         <script src="{{ config['ENDPOINT'] + url_for('static', filename='vendor/ace/ace.js') }}"></script>
         <script src="{{ config['ENDPOINT'] + url_for('static', filename='vendor/jquery/jquery.min.js') }}"></script>
@@ -43,9 +44,13 @@ main :: (args: [] cstr) {
 }</pre>
         </div>
 
-        <div id="horizontal-divider"></div>
+        <div id="horizontal-divider" class="divider"></div>
 
         <div class="right-half">
+            <canvas id="render-target"></canvas>
+
+            <div id="vertical-divider" class="divider"></div>
+
             <pre id="code-result"></pre>
             <input type="textbox" id="input-bar" placeholder="Input line" />
             <!--<input type="button" id="input-submit" value="Enter" onclick="submit_input()" /> -->