From 477e6f07609a6a80a69cc9265732699d544a3c53 Mon Sep 17 00:00:00 2001 From: fredericrous Date: Mon, 13 Apr 2020 03:10:18 +0100 Subject: [PATCH] Add App Calculator --- apps.json | 12 + apps/calculator/ChangeLog | 1 + apps/calculator/app.js | 352 +++++++++++++++++++++++++++++ apps/calculator/calculator-icon.js | 1 + apps/calculator/calculator.info | 1 + apps/calculator/calculator.png | Bin 0 -> 10312 bytes 6 files changed, 367 insertions(+) create mode 100644 apps/calculator/ChangeLog create mode 100644 apps/calculator/app.js create mode 100644 apps/calculator/calculator-icon.js create mode 100644 apps/calculator/calculator.info create mode 100644 apps/calculator/calculator.png diff --git a/apps.json b/apps.json index d85739aad..77239e39e 100644 --- a/apps.json +++ b/apps.json @@ -1158,5 +1158,17 @@ {"name":"batchart.app.js","url":"app.js"}, {"name":"batchart.img","url":"app-icon.js","evaluate":true} ] + }, + { "id": "calculator", + "name": "Calculator", + "shortName":"Calculator", + "icon": "calculator.png", + "version":"0.01", + "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus. Push button1 and 3 to navigate up/down, tap right or left to navigate the sides, push button 2 to select.", + "tags": "app,tool", + "storage": [ + {"name":"calculator.app.js","url":"app.js"}, + {"name":"calculator.img","url":"calculator-icon.js","evaluate":true} + ] } ] diff --git a/apps/calculator/ChangeLog b/apps/calculator/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/calculator/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/calculator/app.js b/apps/calculator/app.js new file mode 100644 index 000000000..91dd7c49d --- /dev/null +++ b/apps/calculator/app.js @@ -0,0 +1,352 @@ +/** + * BangleJS Calculator + * + * Original Author: Frederic Rousseau https://github.com/fredericrous + * Created: April 2020 + */ + +g.clear(); +Graphics.prototype.setFont7x11Numeric7Seg = function() { + this.setFontCustom(atob("ACAB70AYAwBgC94AAAAAAAAAAB7wAAPQhhDCGELwAAAAhDCGEMIXvAAeACAEAIAQPeAA8CEMIYQwhA8AB70IYQwhhCB4AAAIAQAgBAB7wAHvQhhDCGEL3gAPAhDCGEMIXvAAe9CCEEIIQPeAA94EIIQQghA8AB70AYAwBgCAAAAHgQghBCCF7wAHvQhhDCGEIAAAPehBCCEEIAAAAA=="), 46, atob("AgAHBwcHBwcHBwcHAAAAAAAAAAcHBwcHBw=="), 11); +}; + +var DEFAULT_SELECTION = '5'; +var BOTTOM_MARGIN = 10; +var RIGHT_MARGIN = 20; +var COLORS = { + // [normal, selected] + DEFAULT: ['#7F8183', '#A6A6A7'], + OPERATOR: ['#F99D1C', '#CA7F2A'], + SPECIAL: ['#65686C', '#7F8183'] +}; + +var keys = { + '0': { + xy: [0, 200, 120, 240], + trbl: '2.00' + }, + '.': { + xy: [120, 200, 180, 240], + trbl: '3=.0' + }, + '=': { + xy: [181, 200, 240, 240], + trbl: '+==.', + color: COLORS.OPERATOR + }, + '1': { + xy: [0, 160, 60, 200], + trbl: '4201' + }, + '2': { + xy: [60, 160, 120, 200], + trbl: '5301' + }, + '3': { + xy: [120, 160, 180, 200], + trbl: '6+.2' + }, + '+': { + xy: [181, 160, 240, 200], + trbl: '-+=3', + color: COLORS.OPERATOR + }, + '4': { + xy: [0, 120, 60, 160], + trbl: '7514' + }, + '5': { + xy: [60, 120, 120, 160], + trbl: '8624' + }, + '6': { + xy: [120, 120, 180, 160], + trbl: '9-35' + }, + '-': { + xy: [181, 120, 240, 160], + trbl: '*-+6', + color: COLORS.OPERATOR + }, + '7': { + xy: [0, 80, 60, 120], + trbl: 'R847' + }, + '8': { + xy: [60, 80, 120, 120], + trbl: 'N957' + }, + '9': { + xy: [120, 80, 180, 120], + trbl: '%*68' + }, + '*': { + xy: [181, 80, 240, 120], + trbl: '/*-9', + color: COLORS.OPERATOR + }, + 'R': { + xy: [0, 40, 60, 79], + trbl: 'RN7R', + color: COLORS.SPECIAL, + val: 'AC' + }, + 'N': { + xy: [60, 40, 120, 79], + trbl: 'N%8R', + color: COLORS.SPECIAL, + val: '+/-' + }, + '%': { + xy: [120, 40, 180, 79], + trbl: '%/9N', + color: COLORS.SPECIAL + }, + '/': { + xy: [181, 40, 240, 79], + trbl: '//*%', + color: COLORS.OPERATOR + } +}; + +var selected = DEFAULT_SELECTION; +var prevSelected = DEFAULT_SELECTION; +var prevNumber = null; +var currNumber = null; +var operator = null; +var results = null; +var isDecimal = false; +var hasPressedEquals = false; + +function drawKey(name, k, selected) { + var rMargin = 0; + var bMargin = 0; + var color = k.color || COLORS.DEFAULT; + g.setColor(color[selected ? 1 : 0]); + g.setFont('Vector', 20); + g.fillRect(k.xy[0], k.xy[1], k.xy[2], k.xy[3]); + g.setColor(-1); + // correct margins to center the texts + if (name == '0') { + rMargin = (RIGHT_MARGIN * 2) - 7; + } else if (name === '/') { + rMargin = 5; + } else if (name === '*') { + bMargin = 5; + rMargin = 3; + } else if (name === '-') { + rMargin = 3; + } else if (name === 'R' || name === 'N') { + rMargin = k.val === 'C' ? 0 : -9; + } else if (name === '%') { + rMargin = -3; + } + g.drawString(k.val || name, k.xy[0] + RIGHT_MARGIN + rMargin, k.xy[1] + BOTTOM_MARGIN + bMargin); +} + +function doMath(x, y, operator) { + // might not be a number due to display of dot "." algo + x = Number(x); + y = Number(y); + switch (operator) { + case '/': + return x / y; + case '*': + return x * y; + case '+': + return x + y; + case '-': + return x - y; + } +} + +function displayOutput(num) { + var len; + var minusMarge = 0; + g.setColor(0); + g.fillRect(0, 0, 240, 39); + g.setColor(-1); + if (num === Infinity || num === -Infinity || isNaN(num)) { + // handle division by 0 + if (num === Infinity) { + num = 'INFINITY'; + } else if (num === -Infinity) { + num = '-INFINITY'; + } else { + num = 'NOT A NUMBER'; + minusMarge = -25; + } + len = (num + '').length; + currNumber = null; + results = null; + isDecimal = false; + hasPressedEquals = false; + prevNumber = null; + operator = null; + keys.R.val = 'AC'; + drawKey('R', keys.R); + g.setFont('Vector', 22); + } else { + // might not be a number due to display of dot "." + var numNumeric = Number(num); + + if (typeof num === 'string') { + if (num.indexOf('.') !== -1) { + // display a 0 before a lonely dot + if (numNumeric == 0) { + num = '0.'; + } + } else { + // remove preceding 0 + while (num.length > 1 && num[0] === '0') + num = num.substr(1); + } + } + + len = (num + '').length; + if (numNumeric < 0) { + // minus is not available in font 7x11Numeric7Seg, we use Vector + g.setFont('Vector', 20); + g.drawString('-', 220 - (len * 15), 10); + minusMarge = 15; + } + g.setFont('7x11Numeric7Seg', 2); + } + g.drawString(num, 220 - (len * 15) + minusMarge, 10); +} + +function calculatorLogic(x) { + if (hasPressedEquals) { + currNumber = results; + prevNumber = null; + operator = null; + results = null; + isDecimal = null; + displayOutput(currNumber); + hasPressedEquals = false; + } + if (prevNumber != null && currNumber != null && operator != null) { + // we execute the calculus only when there was a previous number entered before and an operator + results = doMath(prevNumber, currNumber, operator); + operator = x; + prevNumber = results; + currNumber = null; + displayOutput(results); + } else if (prevNumber == null && currNumber != null && operator == null) { + // no operator yet, save the current number for later use when an operator is pressed + operator = x; + prevNumber = currNumber; + currNumber = null; + displayOutput(prevNumber); + } else if (prevNumber == null && currNumber == null && operator == null) { + displayOutput(0); + } +} + +function buttonPress(val) { + switch (val) { + case 'R': + currNumber = null; + results = null; + isDecimal = false; + hasPressedEquals = false; + if (keys.R.val == 'AC') { + prevNumber = null; + operator = null; + } else { + keys.R.val = 'AC'; + drawKey('R', keys.R); + } + displayOutput(0); + break; + case '%': + if (results != null) { + displayOutput(results /= 100); + } else if (currNumber != null) { + displayOutput(currNumber /= 100); + } + break; + case 'N': + if (results != null) { + displayOutput(results *= -1); + } else if (currNumber != null) { + displayOutput(currNumber *= -1); + } + break; + case '/': + case '*': + case '-': + case '+': + calculatorLogic(val); + break; + case '.': + keys.R.val = 'C'; + drawKey('R', keys.R); + isDecimal = true; + displayOutput(currNumber == null ? 0 + '.' : currNumber + '.'); + break; + case '=': + if (prevNumber != null && currNumber != null && operator != null) { + results = doMath(prevNumber, currNumber, operator); + prevNumber = results; + displayOutput(results); + hasPressedEquals = true; + } + break; + default: + keys.R.val = 'C'; + drawKey('R', keys.R); + if (isDecimal) { + currNumber = currNumber == null ? 0 + '.' + val : currNumber + '.' + val; + isDecimal = false; + } else { + currNumber = currNumber == null ? val : currNumber + val; + } + displayOutput(currNumber); + break; + } +} + +for (var k in keys) { + if (keys.hasOwnProperty(k)) { + drawKey(k, keys[k], k == '5'); + } +} +g.setFont('7x11Numeric7Seg', 2.8); +g.drawString('0', 205, 10); + + +setWatch(function() { + drawKey(selected, keys[selected]); + // key 0 is 2 keys wide, go up to 1 if it was previously selected + if (selected == '0' && prevSelected === '1') { + prevSelected = selected; + selected = '1'; + } else { + prevSelected = selected; + selected = keys[selected].trbl[0]; + } + drawKey(selected, keys[selected], true); +}, BTN1, {repeat: true, debounce: 100}); + +setWatch(function() { + drawKey(selected, keys[selected]); + prevSelected = selected; + selected = keys[selected].trbl[2]; + drawKey(selected, keys[selected], true); +}, BTN3, {repeat: true, debounce: 100}); + +Bangle.on('touch', function(direction) { + drawKey(selected, keys[selected]); + prevSelected = selected; + if (direction == 1) { + selected = keys[selected].trbl[3]; + } else if (direction == 2) { + selected = keys[selected].trbl[1]; + } + drawKey(selected, keys[selected], true); +}); + +setWatch(function() { + buttonPress(selected); +}, BTN2, {repeat: true, debounce: 100}); diff --git a/apps/calculator/calculator-icon.js b/apps/calculator/calculator-icon.js new file mode 100644 index 000000000..94158e7d2 --- /dev/null +++ b/apps/calculator/calculator-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhBC/AC8r6/XlYvr64CEF9UrMIIv/R/7vTMwIAmlUklQGDroAFqwHGBRgJBqwMDq+k5nNABAWDC4QZFERAvGBQOBF5I0FCYNW1mImWs6+sDoQsDAYIJEAAeB2eB1mBA4QvF43P6/GF4mB6+BAQYlEro3BAAI3FDAezBYgvE43O64DBF4hbCAAMrGAIiFBYRUEHogaBxA6CF4vXLwPHF4giEDIIkDDgI2BFoI6FBgYWCF5PPF4rSBKwVWI4bAFFgdcYAykBX5HX53NFwfNfwIkDAQYAGBBAKCIIYABd4y9DAAJ9CAD9dF4gAGCIi8BABLXBBRQLEF4vHRwgvEERQ6DHpgvH66PB65fUBpZfJ4/G6wxBMIaPbL5QvB6/WF6hqNF5KPDF6jkGd6JeBF5AAdF4oAGDBeH1mHAAwIBF8esABQvdWQonDX4YvIYAq/GXobvNF4hfKCwwvF43GF5AXGL44vJLwgvE453DMIYuFR5JiHI4yPHRoaREIwpIFF7TvbR5BJCX5IvMADgvcroABF6vG4wvIX46DKBZYvEFwPHGAgZHERALRF4YuBHYIwEFxxfPF5CDDF6ZfLDAyPFFwovFKRYvV47vDAgIvRR5aOFL4orCFwbvHADYvEAA4YLdRYvQ45eBR5C6UF5vHX4LvJF8PGZYXXGAYvnLYYvfZ4xfXd6AvKGAK/RDAKNTF4wAG44=")) diff --git a/apps/calculator/calculator.info b/apps/calculator/calculator.info new file mode 100644 index 000000000..e8a760024 --- /dev/null +++ b/apps/calculator/calculator.info @@ -0,0 +1 @@ +{"name":"Calculator","src":"calculator.app.js","icon":"calculator.img"} diff --git a/apps/calculator/calculator.png b/apps/calculator/calculator.png new file mode 100644 index 0000000000000000000000000000000000000000..8362c9200facdad594bf07376e12eaf6d0c3550c GIT binary patch literal 10312 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