From 33fda3e59fc79bb3e5eb7dc510a912aec00a5623 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 28 Mar 2022 11:47:55 +1100 Subject: [PATCH] feat(developer): improve Touch Layout Editor and Character Map integration Fixes #4854. Adds some intelligence to the drag + drop integration with the Touch Layout Editor, so that we always drop a character onto a key cap, we always drop a code onto a ID field, and can drop both character + code together (if the key id is T_new_..., or holding Ctrl key). Hovering over any key for 0.25 sec while dragging will select that key. You can then continue dragging down to the subkey array and drop onto any of those keys. Note that you do not need to hover for 0.25 sec before you drop onto a key -- the drop operation will select the key under the cursor when dropping. Double-clicking will update the selected key or subkey; Ctrl will do both the key cap and the ID. --- .../oskbuilder/UframeTouchLayoutBuilder.pas | 6 + .../TIKE/xml/layoutbuilder/builder-charmap.js | 188 ++++++++++++++++++ .../TIKE/xml/layoutbuilder/builder.js | 70 +------ .../TIKE/xml/layoutbuilder/builder.xsl | 1 + 4 files changed, 206 insertions(+), 59 deletions(-) create mode 100644 windows/src/developer/TIKE/xml/layoutbuilder/builder-charmap.js diff --git a/windows/src/developer/TIKE/oskbuilder/UframeTouchLayoutBuilder.pas b/windows/src/developer/TIKE/oskbuilder/UframeTouchLayoutBuilder.pas index c4e37c9100..78cb768d3a 100644 --- a/windows/src/developer/TIKE/oskbuilder/UframeTouchLayoutBuilder.pas +++ b/windows/src/developer/TIKE/oskbuilder/UframeTouchLayoutBuilder.pas @@ -691,6 +691,12 @@ begin j.AddPair('x', TJSONNumber.Create(X)); j.AddPair('y', TJSONNumber.Create(Y)); j.AddPair('text', cdo.Text[cmimCharacter]); // It never makes sense to drop anything other than char + if GetKeyState(VK_SHIFT) < 0 then + j.AddPair('shift', TJSONBool.Create(True)); + if GetKeyState(VK_CONTROL) < 0 then + j.AddPair('ctrl', TJSONBool.Create(True)); + if GetKeyState(VK_MENU) < 0 then + j.AddPair('alt', TJSONBool.Create(True)); BuilderCommand('charmapDragDrop', j); finally j.Free; diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/builder-charmap.js b/windows/src/developer/TIKE/xml/layoutbuilder/builder-charmap.js new file mode 100644 index 0000000000..3fadde85ea --- /dev/null +++ b/windows/src/developer/TIKE/xml/layoutbuilder/builder-charmap.js @@ -0,0 +1,188 @@ +// +// Character map drag+drop and double-click insertion +// + +builder.dragDrop = {}; +(function(builder, dragDrop) { + dragDrop.isSubKey = function(key) { + return key.parent().attr('id') == 'sk'; + }; + + dragDrop.getKeyElementFromElement = function(elem) { + if(elem.classList.contains('key')) { + return elem; + } + + if(!elem.parentElement) { + return null; + } + + if(elem.parentElement.classList.contains('key')) { + return elem.parentElement; + } + + return null; + }; + + dragDrop.getKeyElementFromDragTarget = function(target) { + let key = dragDrop.getKeyElementFromElement(target); + let isIdField = false; + + if(key) { + key = $(key); + } else { + if(target.id == 'inpKeyCap') { + key = builder.selectedKey(); + } else if(target.id == 'inpKeyName') { + key = builder.selectedKey(); + isIdField = true; + } else if(target.id == 'inpSubKeyCap') { + key = builder.selectedSubKey(); + } else if(target.id == 'inpSubKeyName') { + key = builder.selectedSubKey(); + isIdField = true; + } + + if(!key) { + return null; + } + } + + return { key: key, isIdField: isIdField }; + }; + + dragDrop.isHighSurrogate = function(character) { + let codeUnit = character.charCodeAt(0); + return codeUnit >= 0xD800 && codeUnit <= 0xDBFF; + }; + + dragDrop.isLowSurrogate = function(character) { + let codeUnit = character.charCodeAt(0); + return codeUnit >= 0xDC00 && codeUnit <= 0xDFFF; + }; + + dragDrop.surrogatePairToValue = function(character) { + let h = character.charCodeAt(0), l = character.charCodeAt(1); + return 0x10000 + (h - 0xD800) * 0x400 + (l - 0xDC00); + }; + + dragDrop.charToUnicodeValue = function(s) { + let v; + if(s.length == 2 && dragDrop.isHighSurrogate(s) && dragDrop.isLowSurrogate(s.substring(1))) { + v = builder.surrogatePairToValue(s); + } else if(s.length == 1) { + v = s.charCodeAt(0); + } else { + return null; + } + + let ch = v.toString(16); + while(ch.length < 4) ch = '0' + ch; + return ch; + }; + + dragDrop.keySelectTimeout = 0; + + dragDrop.clearKeySelectTimeout = function() { + if(dragDrop.keySelectTimeout) { + window.clearTimeout(dragDrop.keySelectTimeout); + dragDrop.keySelectTimeout = 0; + } + }; + + builder.charmapDragOver = function(o) { + // Convert X, Y to document coordinates + dragDrop.clearKeySelectTimeout(); + + const target = document.elementFromPoint(o.x, o.y); + const key = target ? dragDrop.getKeyElementFromDragTarget(target) : null; + const found = key && key.key && key.key.length; + + dragDrop.keySelectTimeout = window.setTimeout(function() { + // We use a timeout to allow dragging over to a key, then + // pausing to select it, and then dragging to a subkey of + // that key. + if(!found) { + builder.selectKey(null); + } else if(dragDrop.isSubKey(key.key)) { + builder.selectSubKey(key.key); + } else { + builder.selectKey(key.key); + } + dragDrop.keySelectTimeout = 0; + }, + // 1.0 sec if moving onto a blank space feels a little more + // friendly. We can pause a bit longer near the subkey array + // and not have it disappear + found ? 250 : 1000); + + return found; + }; + + builder.charmapDragDrop = function(o) { + + let key = null; + + dragDrop.clearKeySelectTimeout(); + + if(o.ctrl) { + // Ctrl as part of a drag/drop interaction + // so cancel the default select-key dialog + builder.ctrlDown = false; + // console.log('charmapDragDrop ctrlDown=false'); + } + + if(o.shift && o.ctrl) { + // o.shift means append to current key cap + // o.ctrl means set id value + // together they have no good meaning + return false; + } + + // Convert X, Y to document coordinates + + if(o.x >= 0 && o.y >= 0) { + let target = document.elementFromPoint(o.x, o.y); + if(target != null) { + key = dragDrop.getKeyElementFromDragTarget(target); + + if(key && key.key && key.key.length) { + if(dragDrop.isSubKey(key.key)) { + builder.selectSubKey(key.key); + } else { + builder.selectKey(key.key); + } + } + } + } else { + // Double-click insertion, so use last focused control + key = { key: builder.selectedSubKey(), isIdField: false }; + if(!key.key || !key.key.length) { + key = { key: builder.selectedKey(), isIdField: false }; + } + } + + if(!key || !key.key || !key.key.length) { + return false; + } + + // Focus the control and add the text + if(key.isIdField || key.key.data('id').startsWith('T_new_') || o.ctrl) { + let target = $(dragDrop.isSubKey(key.key) ? '#inpSubKeyName' : '#inpKeyName'); + target.val('U_'+dragDrop.charToUnicodeValue(o.text).toUpperCase()); + + if(key.isIdField) { + target.focus(); + } + + target.change(); + } + + if(!key.isIdField) { + let target = $(dragDrop.isSubKey(key.key) ? '#inpSubKeyCap' : '#inpKeyCap'); + target.focus(); + target.val((o.shift ? target.val() : '') + o.text); + target.change(); + } + }; +})(builder, builder.dragDrop); diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js index 6d49f16c27..492eb37685 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js +++ b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js @@ -1,6 +1,6 @@ -$(function () { - window.builder = this; +window.builder = {}; +$(function() { (function(search) { var q = search.match(/Filename=(.+)(&|$)/); if(q) { @@ -1613,8 +1613,6 @@ $(function () { $('.text', nkey).text(this.renameSpecialKey(text)); builder.updateKeyId(nkey); } - //var div = document.createElement('div'); $(div).css('clear', 'both'); $('#sk').append(div); - builder.selectSubKey($('#sk > div')[0]); builder.prepareSubKey(); builder.enableSubKeyControls(); } @@ -1639,15 +1637,15 @@ $(function () { this.enableSubKeyControls = function () { var key = builder.selectedSubKey(); if (key.length == 0) { - $('#subKeyToolbar').css('visibility', 'hidden'); // attr('disabled', 'disabled'); + $('#subKeyToolbar').css('visibility', 'hidden'); } else { - $('#subKeyToolbar').css('visibility', ''); // removeAttr('disabled'); + $('#subKeyToolbar').css('visibility', ''); } } this.rescale = function () { builder.saveUndo(); - var keyId = builder.selectedKey().data('id'); //$('#kbd .key') + var keyId = builder.selectedKey().data('id'); builder.prepareLayer(); if (keyId !== null) builder.selectKey($('#kbd .key').filter(function (index) { return $(this).data('id') === keyId; }).first()); @@ -2172,7 +2170,11 @@ $(function () { } $(document).keydown(function (event) { - builder.ctrlDown = (event.which == 17); + if(!event.originalEvent.repeat) { + // We will get this event repeatedly while ctrl is held down, + // so if ctrlDown is cancelled, we don't want to set it again + builder.ctrlDown = (event.which == 17); + } if (builder.nextKeySelects) { $('#selectKeyDialog').dialog('close'); event.preventDefault(); @@ -2192,56 +2194,6 @@ $(function () { builder.ctrlDown = false; }); - // - // Character map drag+drop and double-click insertion - // - - builder.charmapDragOver = function(o) { - - // Convert X, Y to document coordinates - - let target = document.elementFromPoint(o.x, o.y); - if(target === null || (target.nodeName != 'INPUT' && target.className != 'text')) { - return false; - } - - return true; - }; - - builder.charmapDragDrop = function(o) { - - // Convert X, Y to document coordinates - - if(o.x >= 0 && o.y >= 0) { - var target = document.elementFromPoint(o.x, o.y); - if(target === null) { - return false; - } - if(target.nodeName == 'INPUT') { - target = $(target); - } else if(target.className == 'text') { - if(target.parentElement.parentElement.id == 'sk') { - builder.selectSubKey(target.parentElement); - target = document.lastFocus; - } else { - builder.selectKey(target.parentElement); - target = document.lastFocus; - } - } else { - return false; - } - } else { - // Double-click insertion, so use last focused control - var target = $(document.lastFocus); - } - - // Focus the control and add the text - - target.focus(); - target.val(target.val() + o.text); - target.change(); - }; - builder.loadingState = true; builder.saveState = function() { @@ -2321,4 +2273,4 @@ $(function () { builder.enableUndoControls(); builder.loadState(); -}); +}.bind(builder)); diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/builder.xsl b/windows/src/developer/TIKE/xml/layoutbuilder/builder.xsl index 6c0221a3c8..31312068ba 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/builder.xsl +++ b/windows/src/developer/TIKE/xml/layoutbuilder/builder.xsl @@ -18,6 +18,7 @@ +
+