From e33ce2b4eed6f1b9d2efb3d19a0a09232c7b47cd Mon Sep 17 00:00:00 2001 From: Keryan Didier Date: Thu, 30 May 2024 15:48:36 +0200 Subject: [PATCH 1/6] Responsive resizing --- src/stubs/ml_canvas.js | 58 ++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/stubs/ml_canvas.js b/src/stubs/ml_canvas.js index 85298294..90eed615 100644 --- a/src/stubs/ml_canvas.js +++ b/src/stubs/ml_canvas.js @@ -51,6 +51,10 @@ var _move = { prev_y: 0 } +//Provides: _resize +//Requires: _resize_handler +var _resize = new window.ResizeObserver(_resize_handler); + //Provides: _make_key_event //Requires: _focus, keyname_to_keycode, Val_key_code, Val_key_state, EVENT_TAG //Requires: caml_int64_of_float @@ -170,6 +174,20 @@ function _move_handler(e) { return false; } +//Provides: _resize_handler +//Requires: _ml_canvas_process_event, EVENT_TAG +//Requires: caml_int64_of_float +function _resize_handler(entries) { + entries.forEach(e => { + var evt = [EVENT_TAG.CANVAS_RESIZED, + [0, e.target.canvas, + caml_int64_of_float(e.timeStamp * 1000.0), + [0, e.target.clientWidth, e.target.clientHeight]]]; + _ml_canvas_process_event(evt); + }); + return false; +} + //Provides: _frame_handler //Requires: _ml_canvas_process_event, EVENT_TAG //Requires: caml_int64_of_float @@ -588,7 +606,7 @@ var _next_id = 0; //Provides: _ml_canvas_decorate //Requires: caml_jsstring_of_string -function _ml_canvas_decorate(header, resizeable, minimize, +function _ml_canvas_decorate(header, minimize, maximize, close, title) { var width = header.width; var ctxt = header.getContext("2d"); @@ -613,7 +631,8 @@ function _ml_canvas_decorate(header, resizeable, minimize, } //Provides: ml_canvas_create_onscreen -//Requires: _ml_canvas_ensure_initialized, _ml_canvas_valid_canvas_size, _next_id, _header_down_handler, _surface_down_handler, _up_handler, _move_handler, _ml_canvas_decorate, Optional_bool_val, Optional_val +//Requires: _ml_canvas_ensure_initialized, _ml_canvas_valid_canvas_size, _resize, _next_id, _header_down_handler +//Requires: _surface_down_handler, _up_handler, _move_handler, _ml_canvas_decorate, Optional_bool_val, Optional_val //Requires: caml_invalid_argument function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, maximize, close, title, pos, size) { @@ -631,7 +650,7 @@ function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, var y = pos[2]; var autocommit = Optional_bool_val(autocommit, true); - var decorated = Optional_bool_val(decorated, true); + var decorated = false; // Optional_bool_val(decorated, true); var resizeable = Optional_bool_val(resizeable, true); var minimize = Optional_bool_val(minimize, true); var maximize = Optional_bool_val(maximize, true); @@ -661,12 +680,14 @@ function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, var frame = document.createElement("div"); frame.id = "f" + id; - frame.style.width = width + "px"; - frame.style.height = height + header_height + "px"; + if (resizeable == true) { + frame.style.width = "100%"; + frame.style.height = "100%"; + } else { + frame.style.width = width + "px"; + frame.style.height = height + header_height + "px"; + } frame.style.visibility = "hidden"; - frame.style.position = "absolute"; - frame.style.left = x + "px"; - frame.style.top = y + "px"; frame.oncontextmenu = function() { return false; } frame.canvas = canvas; canvas.frame = frame; @@ -679,8 +700,7 @@ function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, header.id = "h" + id; header.width = width; header.height = 30; - header.style.position = "absolute"; - _ml_canvas_decorate(header, resizeable, minimize, maximize, close, title); + _ml_canvas_decorate(header, minimize, maximize, close, title); header.onmousedown = _header_down_handler; header.canvas = canvas; canvas.header = header; @@ -691,14 +711,20 @@ function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, surface.id = "s" + id; surface.width = width; surface.height = height; - surface.style.position = "absolute" - surface.style.top = header_height + "px"; surface.onmousedown = _surface_down_handler; surface.canvas = canvas; canvas.surface = surface; frame.appendChild(surface); - var ctxt = surface.getContext("2d"); + if (resizeable === true) { + surface.style.width = "100%"; + surface.style.height = "100%"; + _resize.observe(surface); + } + + // willReadFrequently needed to avoid warning on getImageData + var ctxt = surface.getContext("2d", {willReadFrequently: true}); + ctxt.will ctxt.globalAlpha = 1.0; ctxt.lineWidth = 1.0; ctxt.fillStyle = "white"; @@ -907,7 +933,7 @@ function ml_canvas_set_size(canvas, size) { var img = canvas.ctxt.getImageData(0, 0, canvas.width, canvas.height); if (canvas.header !== null) { canvas.header.width = width; - _ml_canvas_decorate(canvas.header, canvas.resizeable, canvas.minimize, + _ml_canvas_decorate(canvas.header, canvas.minimize, canvas.maximize, canvas.close, canvas.name); } canvas.surface.width = canvas.width = width; @@ -1551,8 +1577,8 @@ function ml_canvas_key_of_int(keycode) { /* Backend */ //Provides: ml_canvas_init -//Requires: _key_down_handler, _key_up_handler, _up_handler, _move_handler, _frame_handler, _ml_canvas_initialized -//Requires: caml_list_to_js_array +//Requires: _key_down_handler, _key_up_handler, _up_handler, _move_handler, _resize_handler, _frame_handler +//Requires: _ml_canvas_initialized, caml_list_to_js_array function ml_canvas_init() { if (_ml_canvas_initialized === true) { return 0; From 68414e99c1b6716d5819020376472f3d26cb53df Mon Sep 17 00:00:00 2001 From: Keryan Didier Date: Fri, 31 May 2024 11:24:43 +0200 Subject: [PATCH 2/6] Targeted canvas placement --- src/ocamlCanvas.ml | 2 +- src/ocamlCanvas.mli | 7 +++++-- src/stubs/ml_canvas.c | 6 ++++-- src/stubs/ml_canvas.js | 16 +++++++++++----- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/ocamlCanvas.ml b/src/ocamlCanvas.ml index 5458f3e9..023e9051 100644 --- a/src/ocamlCanvas.ml +++ b/src/ocamlCanvas.ml @@ -542,7 +542,7 @@ module V1 = struct external createOnscreen : ?autocommit:bool -> ?decorated:bool -> ?resizeable:bool -> ?minimize:bool -> ?maximize:bool -> ?close:bool -> ?title:string -> - ?pos:(int * int) -> size:(int * int) -> unit -> t + ?target:string -> ?pos:(int * int) -> size:(int * int) -> unit -> t = "ml_canvas_create_onscreen" "ml_canvas_create_onscreen_n" external createOffscreen : size:(int * int) -> unit -> t diff --git a/src/ocamlCanvas.mli b/src/ocamlCanvas.mli index e94e0ede..14dff782 100644 --- a/src/ocamlCanvas.mli +++ b/src/ocamlCanvas.mli @@ -766,9 +766,9 @@ module V1 : sig val createOnscreen : ?autocommit:bool -> ?decorated:bool -> ?resizeable:bool -> ?minimize:bool -> ?maximize:bool -> ?close:bool -> ?title:string -> - ?pos:(int * int) -> size:(int * int) -> unit -> t + ?target:string -> ?pos:(int * int) -> size:(int * int) -> unit -> t (** [createOnscreen ?autocommit ?decorated ?resizeable ?minimize - ?maximize ?close ?title ?pos ~size ()] creates a windowed + ?maximize ?close ?title ?target ?pos ~size()] creates a windowed canvas of size [size]. The window title and position can be specified using the optional arguments [title] and [pos]. The window decorations, which are active by default, can @@ -777,6 +777,9 @@ module V1 : sig The [decorated] argument has a higher priority: if set to false, all other decoration arguments will be ignored (considered to be false), and all decorations will be removed from the window. + The [target] option is relevant only for the Javascript backend. + It indicates the element id in which the canvas should be placed, + default to the html body. The [autocommit] option, which is active by default, indicates whether the canvas should be automatically presented after each frame event. See {!Canvas.commit} for more info on [autocommit]. diff --git a/src/stubs/ml_canvas.c b/src/stubs/ml_canvas.c index 8b060ce8..7352e6a4 100644 --- a/src/stubs/ml_canvas.c +++ b/src/stubs/ml_canvas.c @@ -673,12 +673,14 @@ ml_canvas_create_onscreen_n( value mlMaximize, /* bool, optional, default = true */ value mlClose, /* bool, optional, default = true */ value mlTitle, /* string, optional, default = "" */ + value mlTarget, /* string, optional */ value mlPos, /* (int * int), optional */ value mlSize, /* (int * int), non-optional */ value mlUnit) { CAMLparam5(mlAutocommit, mlDecorated, mlResizeable, mlMinimize, mlMaximize); - CAMLxparam5(mlClose, mlTitle, mlPos, mlSize, mlUnit); + CAMLxparam5(mlClose, mlTitle, mlTarget, mlPos, mlSize); + CAMLxparam1(mlUnit); CAMLlocal1(mlCanvas); _ml_canvas_ensure_initialized(); int32_t width = Int31_val_clip(Field(mlSize, 0)); @@ -708,7 +710,7 @@ ml_canvas_create_onscreen_n( CAMLreturn(mlCanvas); } -BYTECODE_STUB_10(ml_canvas_create_onscreen) +BYTECODE_STUB_11(ml_canvas_create_onscreen) CAMLprim value ml_canvas_create_offscreen( diff --git a/src/stubs/ml_canvas.js b/src/stubs/ml_canvas.js index 90eed615..416e7bbc 100644 --- a/src/stubs/ml_canvas.js +++ b/src/stubs/ml_canvas.js @@ -112,7 +112,7 @@ function _header_down_handler(e) { _move.target = e.target.canvas.frame; _move.prev_x = e.pageX; _move.prev_y = e.pageY; - document.body.insertBefore(_move.target, null); + e.target.canvas.target.insertBefore(_move.target, null); } return false; } @@ -123,7 +123,7 @@ function _header_down_handler(e) { function _surface_down_handler(e) { if (e.target !== null) { _focus = e.target.canvas; - document.body.insertBefore(e.target.canvas.frame, null); + e.target.canvas.target.insertBefore(e.target.canvas.frame, null); var evt = [EVENT_TAG.BUTTON_ACTION, [0, e.target.canvas, caml_int64_of_float(e.timeStamp * 1000.0), @@ -634,8 +634,8 @@ function _ml_canvas_decorate(header, minimize, //Requires: _ml_canvas_ensure_initialized, _ml_canvas_valid_canvas_size, _resize, _next_id, _header_down_handler //Requires: _surface_down_handler, _up_handler, _move_handler, _ml_canvas_decorate, Optional_bool_val, Optional_val //Requires: caml_invalid_argument -function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, - maximize, close, title, pos, size) { +function ml_canvas_create_onscreen(autocommit, decorated, resizeable, minimize, + maximize, close, title, target, pos, size) { _ml_canvas_ensure_initialized(); @@ -656,11 +656,17 @@ function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, var maximize = Optional_bool_val(maximize, true); var close = Optional_bool_val(close, true); var title = Optional_val(title, null); + var target = Optional_val(target, null); + target = document.getElementById(target); + if(target == null) { + target = document.body; + } var id = ++_next_id; var canvas = { name: title, + target: target, frame: null, header: null, surface: null, @@ -691,7 +697,7 @@ function ml_canvas_create_onscreen(autocmmit, decorated, resizeable, minimize, frame.oncontextmenu = function() { return false; } frame.canvas = canvas; canvas.frame = frame; - document.body.appendChild(frame); + target.appendChild(frame); var header = null; if (decorated === true) { From 024f766cfc6e045b06eea677807122f9b52e07eb Mon Sep 17 00:00:00 2001 From: Keryan Didier Date: Fri, 31 May 2024 12:35:46 +0200 Subject: [PATCH 3/6] prevent event lockup --- src/stubs/ml_canvas.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stubs/ml_canvas.js b/src/stubs/ml_canvas.js index 416e7bbc..dcaf5dc1 100644 --- a/src/stubs/ml_canvas.js +++ b/src/stubs/ml_canvas.js @@ -1589,10 +1589,10 @@ function ml_canvas_init() { if (_ml_canvas_initialized === true) { return 0; } - document.onkeydown = _key_down_handler; - document.onkeyup = _key_up_handler; - document.onmouseup = _up_handler; - document.onmousemove = _move_handler; + document.addEventListener("keydown", _key_down_handler, {passive: true}); + document.addEventListener("keyup", _key_up_handler, {passive: true}); + document.addEventListener("mouseup", _up_handler, {passive: true}); + document.addEventListener("mousemove", _move_handler, {passive: true}); window.requestAnimationFrame(_frame_handler); _ml_canvas_initialized = true; return 0; From 3a3a255cff26d66baef98cbf345b9e294145fa9b Mon Sep 17 00:00:00 2001 From: Keryan Didier Date: Fri, 31 May 2024 15:27:14 +0200 Subject: [PATCH 4/6] 'wiser' js --- src/stubs/ml_canvas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stubs/ml_canvas.js b/src/stubs/ml_canvas.js index dcaf5dc1..3b129f63 100644 --- a/src/stubs/ml_canvas.js +++ b/src/stubs/ml_canvas.js @@ -178,7 +178,7 @@ function _move_handler(e) { //Requires: _ml_canvas_process_event, EVENT_TAG //Requires: caml_int64_of_float function _resize_handler(entries) { - entries.forEach(e => { + entries.forEach(function (e) { var evt = [EVENT_TAG.CANVAS_RESIZED, [0, e.target.canvas, caml_int64_of_float(e.timeStamp * 1000.0), From 49bcfa44832314cbf6ff7c90f2684337f2968491 Mon Sep 17 00:00:00 2001 From: Keryan Didier Date: Mon, 10 Jun 2024 16:36:54 +0200 Subject: [PATCH 5/6] revert to hardware rendering --- src/stubs/ml_canvas.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/stubs/ml_canvas.js b/src/stubs/ml_canvas.js index 3b129f63..c7879649 100644 --- a/src/stubs/ml_canvas.js +++ b/src/stubs/ml_canvas.js @@ -728,9 +728,7 @@ function ml_canvas_create_onscreen(autocommit, decorated, resizeable, minimize, _resize.observe(surface); } - // willReadFrequently needed to avoid warning on getImageData - var ctxt = surface.getContext("2d", {willReadFrequently: true}); - ctxt.will + var ctxt = surface.getContext("2d"); ctxt.globalAlpha = 1.0; ctxt.lineWidth = 1.0; ctxt.fillStyle = "white"; From 504cf519106d0aa86404fc629fbde3859d073856 Mon Sep 17 00:00:00 2001 From: Keryan Didier Date: Tue, 11 Jun 2024 15:53:41 +0200 Subject: [PATCH 6/6] fix event values after CSS rescaling --- src/stubs/ml_canvas.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/stubs/ml_canvas.js b/src/stubs/ml_canvas.js index c7879649..cf9036eb 100644 --- a/src/stubs/ml_canvas.js +++ b/src/stubs/ml_canvas.js @@ -55,6 +55,13 @@ var _move = { //Requires: _resize_handler var _resize = new window.ResizeObserver(_resize_handler); +//Provides: _event_canvas_scale +function _event_canvas_scale(e) { + return { scaleX : e.target.canvas.width / e.target.clientWidth, + scaleY : e.target.canvas.height / e.target.clientHeight + } +} + //Provides: _make_key_event //Requires: _focus, keyname_to_keycode, Val_key_code, Val_key_state, EVENT_TAG //Requires: caml_int64_of_float @@ -118,38 +125,40 @@ function _header_down_handler(e) { } //Provides: _surface_down_handler -//Requires: _focus, _ml_canvas_process_event, EVENT_TAG +//Requires: _focus, _ml_canvas_process_event, _event_canvas_scale, EVENT_TAG //Requires: caml_int64_of_float function _surface_down_handler(e) { if (e.target !== null) { _focus = e.target.canvas; e.target.canvas.target.insertBefore(e.target.canvas.frame, null); + var s = _event_canvas_scale(e); var evt = [EVENT_TAG.BUTTON_ACTION, [0, e.target.canvas, caml_int64_of_float(e.timeStamp * 1000.0), - [0, e.offsetX, e.offsetY], e.button + 1, 1]]; + [0, e.offsetX*s.scaleX, e.offsetY*s.scaleY], e.button + 1, 1]]; _ml_canvas_process_event(evt); } return false; } //Provides: _up_handler -//Requires: _move, _ml_canvas_process_event, EVENT_TAG +//Requires: _move, _ml_canvas_process_event, _event_canvas_scale, EVENT_TAG //Requires: caml_int64_of_float function _up_handler(e) { _move.moving = false; if (e.target.canvas !== undefined) { + var s = _event_canvas_scale(e); var evt = [EVENT_TAG.BUTTON_ACTION, [0, e.target.canvas, caml_int64_of_float(e.timeStamp * 1000.0), - [0, e.offsetX, e.offsetY], e.button + 1, 0]]; + [0, e.offsetX*s.scaleX, e.offsetY*s.scaleY], e.button + 1, 0]]; _ml_canvas_process_event(evt); } return false; // = prevent default behavior } //Provides: _move_handler -//Requires: _move, _ml_canvas_process_event, EVENT_TAG +//Requires: _move, _ml_canvas_process_event, _event_canvas_scale, EVENT_TAG //Requires: caml_int64_of_float function _move_handler(e) { if (_move.moving) { @@ -165,10 +174,11 @@ function _move_handler(e) { _move.target.style.left = canvas.x + "px"; _move.target.style.top = canvas.y + "px"; } else if (e.target.canvas !== undefined) { + var s = _event_canvas_scale(e); var evt = [EVENT_TAG.MOUSE_MOVE, [0, e.target.canvas, caml_int64_of_float(e.timeStamp * 1000.0), - [0, e.offsetX, e.offsetY]]]; + [0, e.offsetX*s.scaleX, e.offsetY*s.scaleY]]]; _ml_canvas_process_event(evt); } return false;