From 9e78afb1b1679411bce26cf9e0d7b41748574f1c Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com> Date: Tue, 19 Apr 2022 23:03:59 +0200 Subject: [PATCH] dragboard v0.03 --- apps/dragboard/ChangeLog | 1 + apps/dragboard/README.md | 9 +- apps/dragboard/lib.js | 480 ++++++++++++++-------------------- apps/dragboard/metadata.json | 2 +- apps/dragboard/screenshot.png | Bin 2215 -> 2487 bytes 5 files changed, 204 insertions(+), 288 deletions(-) diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog index b12304571..415ffbb09 100644 --- a/apps/dragboard/ChangeLog +++ b/apps/dragboard/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Added some missing code. +0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights. diff --git a/apps/dragboard/README.md b/apps/dragboard/README.md index 13f89bbcb..56feaf6a4 100644 --- a/apps/dragboard/README.md +++ b/apps/dragboard/README.md @@ -9,15 +9,10 @@ Swiping between the different fields is possible! The drag in Dragboard is a nod to the javascript 'drag' event, which is used to select the characters. Also, you can't help but feel somewhat glamorous and risque when this is your keyboard! Known bugs: -- Initially developed for use with dark theme set on Bangle.js 2 - looks aweful with other themes. -- Keyboard is not exited correctly when trying to close the keyboard. -- Text is drawn over system back button. +- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes. - Text does not wrap. To do: -- Make keyboard theme agnostic. -- Keep number/punctuation color one color. -- Handle system back button in some way. - Make text wrap and/or scroll up/sideways once available space runs out. -- Optimize code. Further split it up in functions. Add 'ram' keyword where suitable. +- Optimize code. Further split it up in functions. - Possibly provide a dragboard.settings.js file diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js index 693dbc418..31fac8998 100644 --- a/apps/dragboard/lib.js +++ b/apps/dragboard/lib.js @@ -7,340 +7,260 @@ exports.input = function(options) { var text = options.text; if ("string"!=typeof text) text=""; - //Draw the alphabet on the screen + var BGCOLOR = g.theme.bg; + var HLCOLOR = g.theme.fg; + var ABCCOLOR = g.toColor(1,0,0);//'#FF0000'; + var NUMCOLOR = g.toColor(0,1,0);//'#00FF00'; + var BIGFONT = '6x8:3'; + var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1))); + var SMALLFONT = '6x8:1'; + var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1))); - var abc = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase(); - var fontSize = "6x8"; - //var xStep = 6; - var abcPadding = (g.getWidth()-6*abc.length)/2; + var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase(); + var ABCPADDING = (g.getWidth()-6*ABC.length)/2; - var numPunct = ' 1234567890!?,.- '; - var numPunctHidden = ' 1234567890!?,.- '; - var numPunctPadding = (g.getWidth()-6*numPunct.length)/2; + var NUM = ' 1234567890!?,.- '; + var NUMHIDDEN = ' 1234567890!?,.- '; + var NUMPADDING = (g.getWidth()-6*NUM.length)/2; var rectHeight = 40; - var bgColor = [0,0,0]; - var HLColor = [1,1,1]; - var abcColor = [1,0,0]; - var numPunctColor = [0,1,0]; - var delLast; - var spaceLast; - function drawABC() { + var delSpaceLast; + + function drawAbcRow() { g.clear(); - g.setFont(fontSize); - g.setColor(1,0,0); - g.drawString(abc, abcPadding, g.getHeight()/2); + g.setFont(SMALLFONT); + g.setColor(ABCCOLOR); + g.drawString(ABC, ABCPADDING, g.getHeight()/2); g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight()); } - function drawNumPunct() { - g.setFont(fontSize); - g.setColor(0,1,1); - g.drawString(numPunct, numPunctPadding, g.getHeight()/4); + function drawNumRow() { + g.setFont(SMALLFONT); + g.setColor(NUMCOLOR); + g.drawString(NUM, NUMPADDING, g.getHeight()/4); - g.fillRect(numPunctPadding, g.getHeight()-rectHeight*4/3, g.getWidth()-numPunctPadding, g.getHeight()-rectHeight*2/3); + g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3); } function initMarker() { g.setColor(0.5,0,0); - g.fillRect(5,5,11,13); - g.setColor(1,1,1); + g.fillRect(3,4+20,11,13+20); + g.setColor(HLCOLOR); } function updateTopString() { - g.setColor(0,0,0); - g.fillRect(0,5,176,13); + "ram" + g.setColor(BGCOLOR); + g.fillRect(0,4+20,176,13+20); g.setColor(0.2,0,0); - g.fillRect(5,5,5+text.length*6,13); + g.fillRect(3,4+20,5+text.length*6,13+20); g.setColor(0.7,0,0); - g.fillRect(text.length*6+5,5,10+text.length*6,13); + g.fillRect(text.length*6+5,4+20,10+text.length*6,13+20); g.setColor(1,1,1); - g.drawString(text, 5, 5); + g.drawString(text, 5, 5+20); } - drawABC(); - drawNumPunct(); + drawAbcRow(); + drawNumRow(); initMarker(); - var charHL; - var charHLPrev = -10; - var numPunctHL; - var numPunctHLPrev = -10; + var abcHL; + var abcHLPrev = -10; + var numHL; + var numHLPrev = -10; var type = ''; var typePrev = ''; var largeCharOffset = 6; - var letterCase = 1; - + + function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) { + "ram" + // Small character in list + g.setColor(rowColor); + g.setFont(SMALLFONT); + g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor); + // Large character + g.setColor(BGCOLOR); + g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24); + //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. + // mark in the list + } + function showChars(char, HL, typePadding, heightDivisor) { + "ram" + // mark in the list + g.setColor(HLCOLOR); + g.setFont(SMALLFONT); + if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor); + // show new large character + g.setFont(BIGFONT); + g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3); + g.setFont(SMALLFONT); + } + + function changeCase(abcHL) { + g.setColor(BGCOLOR); + g.drawString(ABC, ABCPADDING, g.getHeight()/2); + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase(); + else ABC = ABC.toUpperCase(); + g.setColor(ABCCOLOR); + g.drawString(ABC, ABCPADDING, g.getHeight()/2); + } + return new Promise((resolve,reject) => { // Interpret touch input - Bangle.on('drag', function(event) { - g.setFont('6x8'); + Bangle.setUI({ + mode: 'custom', + back: ()=>{ + Bangle.setUI(); + g.clearRect(Bangle.appRect); + resolve(text); + }, + drag: function(event) { - // hide delete sign - if (delLast == 1) { - g.setColor(0,0,0); - g.setFont('6x8:3'); - g.drawString('del', g.getWidth()/2 -27, g.getHeight()/3); - g.setFont('6x8'); - delLast = 0; - } + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + // Choose character by draging along red rectangle at bottom of screen + if (event.y >= ( g.getHeight() - 12 )) { + // Translate x-position to character + if (event.x < ABCPADDING) { abcHL = 0; } + else if (event.x >= 176-ABCPADDING) { abcHL = 25; } + else { abcHL = Math.floor((event.x-ABCPADDING)/6); } - //hide space sign - if (spaceLast == 1) { - g.setColor(0,0,0); - g.setFont('6x8:3'); - g.drawString('space', g.getWidth()/2 -6*3*5/2, g.getHeight()/3); - g.setFont('6x8'); - spaceLast = 0; - } + // Datastream for development purposes + //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // Choose character by draging along red rectangle at bottom of screen - if (event.y >= ( g.getHeight() - 12 )) { - // Translate x-position to character - if (event.x < abcPadding) { charHL = 0; } - else if (event.x >= 176-abcPadding) { charHL = 25; } - else { charHL = Math.floor((event.x-abcPadding)/6); } - - // Datastream for development purposes - //print(event.x, event.y, event.b, abc.charAt(charHL), abc.charAt(charHLPrev)); - - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'abc') { - // Small character in list - g.setColor(0,1,0); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6, g.getHeight()/4); - // Large character - g.setColor(0,0,0); - g.setFont('6x8:3'); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6 -largeCharOffset, g.getHeight()/3); - g.setFont('6x8'); - } - - if (charHL != charHLPrev) { - // unmark in the list - g.setColor(1,0,0); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6, g.getHeight()/2); - // hide previous large character - g.setColor(0,0,0); - g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24); - g.setColor([0,0,0]); - g.setFont('6x8:3'); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6 -largeCharOffset, g.getHeight()/3); - g.setFont('6x8'); - // mark in the list - g.setColor(1,1,1); - g.setFont('6x8'); - g.drawString(abc.charAt(charHL), abcPadding + charHL*6, g.getHeight()/2); - // show new large character - //g.setColor(1,1,1); - g.setFont('6x8:3'); - g.drawString(abc.charAt(charHL), abcPadding + charHL*6 -largeCharOffset, g.getHeight()/3); - g.setFont('6x8'); - } - // Print string at top of screen - if (event.b == 0) { - text = text + abc.charAt(charHL); - updateTopString(); - - // Autoswitching letter case - if (abc.charAt(charHL) == abc.charAt(charHL).toUpperCase()) { - g.setColor(0,0,0); - g.drawString(abc, abcPadding, g.getHeight()/2); - abc = abc.toLowerCase(); - g.setColor(1,0,0); - g.drawString(abc, abcPadding, g.getHeight()/2); - letterCase = 0 - } - //print(text); - } - // Update previous character to current one - charHLPrev = charHL; - typePrev = 'abc'; - } - - // 12345678901234567890 - // Choose number or puctuation by draging on green rectangle - else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) { - // Translate x-position to character - if (event.x < numPunctPadding) { numPunctHL = 0; } - else if (event.x > 176-numPunctPadding) { numPunctHL = numPunct.length-1; } - else { numPunctHL = Math.floor((event.x-numPunctPadding)/6); } - - // Datastream for development purposes - //print(event.x, event.y, event.b, numPunct.charAt(numPunctHL), numPunct.charAt(numPunctHLPrev)); - - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'numPunct') { - // Small character in list - g.setColor(1,0,0); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6, g.getHeight()/2); - // Large character - g.setColor(0,0,0); - g.setFont('6x8:3'); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6 -largeCharOffset, g.getHeight()/3); - g.setFont('6x8'); - } - if (numPunctHL != numPunctHLPrev) { - // unmark in the list - g.setColor(0,1,0); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6, g.getHeight()/4); - // hide previous large character - g.setColor(0,0,0); - g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24); - g.setColor(0,0,0); - g.setFont('6x8:3'); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6 -largeCharOffset, g.getHeight()/3); - g.setFont('6x8'); - // mark in the list - g.setColor(1,1,1); - g.setFont('6x8'); - g.drawString(numPunct.charAt(numPunctHL), numPunctPadding + numPunctHL*6, g.getHeight()/4); - // show new large character - //g.setColor(1,1,1); - g.setFont('6x8:3'); - g.drawString(numPunct.charAt(numPunctHL), numPunctPadding + numPunctHL*6 -largeCharOffset, g.getHeight()/3); - g.setFont('6x8'); - } - // Print string at top of screen - if (event.b == 0) { - g.setColor(1,1,1); - // Backspace if releasing before list of numbers/punctuation - if (event.x < numPunctPadding) { - // show delete sign - g.setColor(1,1,1); - g.setFont('6x8:3'); - g.drawString('del', g.getWidth()/2 -27, g.getHeight()/3); - g.setFont('6x8'); - delLast = 1; - - text = text.slice(0, -1); - updateTopString(); - //print(text); - } - // Append space if releasing after list of numbers/punctuation - else if (event.x > g.getWidth()-numPunctPadding) { - //show space sign - g.setColor(1,1,1); - g.setFont('6x8:3'); - g.drawString('space', g.getWidth()/2 -6*3*5/2, g.getHeight()/3); - g.setFont('6x8'); - spaceLast = 1; - - text = text + ' '; - updateTopString(); - //print(text); - } - // Append selected number/punctuation - else { - text = text + numPunctHidden.charAt(numPunctHL); + if (abcHL != abcHLPrev) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); + } + // Print string at top of screen + if (event.b == 0) { + text = text + ABC.charAt(abcHL); updateTopString(); // Autoswitching letter case - if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) { - g.setColor(0,0,0); - g.drawString(abc, abcPadding, g.getHeight()/2); - abc = abc.toUpperCase(); - g.setColor(1,0,0); - g.drawString(abc, abcPadding, g.getHeight()/2); - letterCase = 1; - - } - //print(text); + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); } + // Update previous character to current one + abcHLPrev = abcHL; + typePrev = 'abc'; } - // Update previous character to current one - numPunctHLPrev = numPunctHL; - typePrev = 'numPunct'; - } - // Make a space or backspace by swiping right or left on screen above green rectangle - else { - if (event.b == 0) { - g.setColor(1,1,1); - if (event.x < g.getWidth()/2) { - // unmark character in the list - g.setColor(1,0,0); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6, g.getHeight()/2); + + + + + + + + - // unmark number/punctuation in the list - g.setColor(0,1,0); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6, g.getHeight()/4); + // 12345678901234567890 + // Choose number or puctuation by draging on green rectangle + else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) { + // Translate x-position to character + if (event.x < NUMPADDING) { numHL = 0; } + else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } + else { numHL = Math.floor((event.x-NUMPADDING)/6); } - // hide previous large character - g.setColor([0,0,0]); - g.setFont('6x8:3'); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6 -largeCharOffset, g.getHeight()/3); + // Datastream for development purposes + //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); - // hide previous large character - g.setColor(0,0,0); - g.setFont('6x8:3'); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6 -largeCharOffset, g.getHeight()/3); - - // show delete sign - g.setColor(1,1,1); - g.setFont('6x8:3'); - g.drawString('del', g.getWidth()/2 -27, g.getHeight()/3); - g.setFont('6x8'); - delLast = 1; - - // Backspace and draw string upper right corner - text = text.slice(0, -1); - updateTopString(); - if (text.length==0) { - g.setColor(0,0,0); - g.drawString(abc, abcPadding, g.getHeight()/2); - abc = abc.toUpperCase(); - g.setColor(1,0,0); - g.drawString(abc, abcPadding, g.getHeight()/2); - letterCase = 1; + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + if (numHL != numHLPrev) { + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); + } + // Print string at top of screen + if (event.b == 0) { + g.setColor(HLCOLOR); + // Backspace if releasing before list of numbers/punctuation + if (event.x < NUMPADDING) { + // show delete sign + showChars('del', 0, g.getWidth()/2 +6 -27 , 4); + delSpaceLast = 1; + text = text.slice(0, -1); + updateTopString(); + //print(text); + } + // Append space if releasing after list of numbers/punctuation + else if (event.x > g.getWidth()-NUMPADDING) { + //show space sign + showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + text = text + ' '; + updateTopString(); + //print(text); + } + // Append selected number/punctuation + else { + text = text + NUMHIDDEN.charAt(numHL); + updateTopString(); + + // Autoswitching letter case + if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); } - //print(text, 'undid'); } - else { - // unmark character in the list - g.setColor(1,0,0); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6, g.getHeight()/2); + // Update previous character to current one + numHLPrev = numHL; + typePrev = 'num'; + } + + + + + + + + + // Make a space or backspace by swiping right or left on screen above green rectangle + else { + if (event.b == 0) { + g.setColor(HLCOLOR); + if (event.x < g.getWidth()/2) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - // unmark number/punctuation in the list - g.setColor(0,1,0); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6, g.getHeight()/4); + // show delete sign + showChars('del', 0, g.getWidth()/2 +6 -27 , 4); + delSpaceLast = 1; - // hide previous large character - g.setColor([0,0,0]); - g.setFont('6x8:3'); - g.drawString(abc.charAt(charHLPrev), abcPadding + charHLPrev*6 -largeCharOffset, g.getHeight()/3); + // Backspace and draw string upper right corner + text = text.slice(0, -1); + updateTopString(); + if (text.length==0) changeCase(abcHL); + //print(text, 'undid'); + } + else { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - // hide previous large character - g.setColor(0,0,0); - g.setFont('6x8:3'); - g.drawString(numPunct.charAt(numPunctHLPrev), numPunctPadding + numPunctHLPrev*6 -largeCharOffset, g.getHeight()/3); + //show space sign + showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; - //show space sign - g.setColor(1,1,1); - g.setFont('6x8:3'); - g.drawString('space', g.getWidth()/2 -6*3*5/2, g.getHeight()/3); - g.setFont('6x8'); - spaceLast = 1; - - // Append space and draw string upper right corner - text = text + numPunctHidden.charAt(0); - updateTopString(); - //print(text, 'made space'); + // Append space and draw string upper right corner + text = text + NUMHIDDEN.charAt(0); + updateTopString(); + //print(text, 'made space'); + } } } } - }); - - return new Promise((resolve,reject) => { + }); +}); +/* return new Promise((resolve,reject) => { Bangle.setUI({mode:"custom", back:()=>{ Bangle.setUI(); g.clearRect(Bangle.appRect); + Bangle.setUI(); resolve(text); }}); - }); + }); */ }; diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json index f8f1f6c85..65ac6fb2e 100644 --- a/apps/dragboard/metadata.json +++ b/apps/dragboard/metadata.json @@ -1,6 +1,6 @@ { "id": "dragboard", "name": "Dragboard", - "version":"0.02", + "version":"0.03", "description": "A library for text input via swiping keyboard", "icon": "app.png", "type":"textinput", diff --git a/apps/dragboard/screenshot.png b/apps/dragboard/screenshot.png index a9451d1a43161be0d33b1686743d9564663a15a9..dbe27f4080e2e1281ffcbccf8536228fc3517d20 100644 GIT binary patch literal 2487 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