|
| 1 | +Bangle.loadWidgets(); |
| 2 | + |
| 3 | +Graphics.prototype.setFontDoto = function(scale) { |
| 4 | + // Actual height 40 (39 - 0) |
| 5 | + // 1 BPP |
| 6 | + return this.setFontCustom( |
| 7 | + E.toString(require('heatshrink').decompress(atob('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'))), |
| 8 | + 32, |
| 9 | + atob("GAwQFBQUFAwQEBQUEBQQFBQUFBQUFBQUFBQQEBQUFBQUFBQUFBQUFBQQFBQUFBQUFBQUFBQUFBQUFBQQFBAUFBAUFBQUFBQUFBAUFBAUFBQUFBQUFBQUFBQUFBQMFBQAFgsQACcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBQUExQGFBAUFBgXABQUEBYNDBAUFAwQCxQYHBwdFBQUFBQUFBQUFBQUFBAQEBAUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEBAQEBQUFBQUFBQUFBQUFBQUFBQ="), |
| 10 | + 40+(scale<<8)+(1<<16) |
| 11 | + ); |
| 12 | +}; |
| 13 | + |
| 14 | +// Load settings |
| 15 | +const SETTINGS = 'scrolly.json'; |
| 16 | +let settings = Object.assign({ |
| 17 | + maxBright: true, |
| 18 | + doNotDim: true, |
| 19 | + speed: 50, |
| 20 | + rotate: 0, |
| 21 | + countdown: true, |
| 22 | + lastText: '', |
| 23 | + rememberText: true |
| 24 | +}, require('Storage').readJSON(SETTINGS, true) || {}); |
| 25 | + |
| 26 | +let x; |
| 27 | +let scrollInterval, countdownInterval; |
| 28 | +let countdownNum; |
| 29 | +let text = settings.lastText; |
| 30 | +let originalOptions = Bangle.getOptions(); |
| 31 | +let brightness = require('Storage').readJSON('setting.json').brightness; |
| 32 | + |
| 33 | +// Main interface |
| 34 | +var Layout = require("Layout"); |
| 35 | +var layout = new Layout( { |
| 36 | + type: 'v', c: [ |
| 37 | + { // Text area |
| 38 | + type: 'txt', |
| 39 | + font: '6x8:2', |
| 40 | + label: text ? text : 'Enter text...', // Show prompt if text not set |
| 41 | + col: text ? g.theme.fg2 : '#555', // Grey text if prompt shown |
| 42 | + id: 'text', |
| 43 | + wrap: true, |
| 44 | + height: Bangle.appRect.h - 52, // Icons are 32px, padding 10px top and bottom |
| 45 | + width: g.getWidth(), |
| 46 | + valign:0, |
| 47 | + cb: l=> editText() // Tapping the text area triggers the same editor as the pen icon |
| 48 | + }, |
| 49 | + { |
| 50 | + type: 'h', c: [ |
| 51 | + { // Edit icon |
| 52 | + type: 'img', |
| 53 | + pad: 10, |
| 54 | + src: require("heatshrink").decompress(atob("kEg4kA///6f8gH41VStP6gGYBYPv2dV1uyykkvOe+/LrVNteW2sA3Bc4hnM4APM5mY5geM51m3ogL5mGs1r7geK48bswgL5lxiO2EAJBIDwMRiIgC3ggHDwURilGs25B4weDiNCkgPIDwkikV7xgeLkUvrpPGDw311guFDw9aD0sMyIeMB4O2DxgPCsIeLgHNW4MSDxUA5IPBsgeKgHOB4Nmp4eJgHLB4W/DxMM522s279PDDxEM7HuxPc5kwB5Pd5nMFhAPDDZQAyA=")), |
| 55 | + cb: l=> editText() |
| 56 | + }, |
| 57 | + { // Settings icon |
| 58 | + type: 'img', |
| 59 | + pad: 10, |
| 60 | + src: require("heatshrink").decompress(atob("kEg4UA///6ec1VSqOMEzUVqoHFqoHUisBA4NAiglDr/1qtUCofq1YZDqte1Wq2oZBDoNaA4OlA4dqA4OpqtQA4Pq1EqEANAF4Oq0EK1QvCguqwEC1VQA4wPKD4IPFF4InBF4MAH4xHCvQHB1oHDL4ofBq4HB6pvDqt//p/DisFR4QHCV64HJiolEAAw=")), |
| 61 | + cb: l=> showSettings() |
| 62 | + }, |
| 63 | + { // Scroll icon |
| 64 | + type: 'img', |
| 65 | + pad: 10, |
| 66 | + src: require("heatshrink").decompress(atob("kEg4UB8EH/4AC+mnqoAQqAgBA4goCgoGCqgxDA4VAA4cVCwgYEA4kBDwogCGoMD+AHFgWsFAYHC1XAA4Q2BA4OwA42oA4wYCgoHEDAIHNC42rMAQHD0AvGI4ZPCCwQHDgYWCM4J3HA4qHBS5CnHYwTIFDwoQECwTJDAwoAFA")), |
| 67 | + id: 'scrollBtn', |
| 68 | + cb: l=>{ |
| 69 | + if (text) { |
| 70 | + scrollText(); |
| 71 | + } else { |
| 72 | + E.showAlert('No text to scroll').then(() => { |
| 73 | + layout.setUI(); |
| 74 | + layout.render(); |
| 75 | + }); |
| 76 | + } |
| 77 | + } |
| 78 | + }, |
| 79 | + ] |
| 80 | + } |
| 81 | + ] |
| 82 | +}, {back: load}); |
| 83 | + |
| 84 | +// Display scrolling text |
| 85 | +function scrollText() { |
| 86 | + require("widget_utils").hide(); |
| 87 | + |
| 88 | + Bangle.setUI({ |
| 89 | + mode:"custom", |
| 90 | + btn: () => { // Button returns to main interface |
| 91 | + if (scrollInterval) // Stop scroll |
| 92 | + clearInterval(scrollInterval); |
| 93 | + if (countdownInterval) // Stop countdown |
| 94 | + clearInterval(countdownInterval); |
| 95 | + |
| 96 | + // Reset screen to original settings |
| 97 | + g.setRotation(0); |
| 98 | + Bangle.setOptions(originalOptions); |
| 99 | + Bangle.setLCDBrightness(brightness); |
| 100 | + |
| 101 | + // Draw the main interface |
| 102 | + require("widget_utils").show(); |
| 103 | + g.clearRect(Bangle.appRect); |
| 104 | + layout.setUI(); |
| 105 | + layout.render(); |
| 106 | + } |
| 107 | + }); |
| 108 | + |
| 109 | + // Setup screen |
| 110 | + if (settings.doNotDim) { // Don't lock or turn off the backlight while scrolling |
| 111 | + Bangle.setOptions({backlightTimeout: 0, lockTimeout: 0}); |
| 112 | + Bangle.setBacklight(1); |
| 113 | + } else { // For some reason the screen locking resets the font style and alignment, so don't allow this to happen while scrolling |
| 114 | + Bangle.setOptions({lockTimeout: 0}); |
| 115 | + } |
| 116 | + if (settings.maxBright) { |
| 117 | + Bangle.setLCDBrightness(1); |
| 118 | + } |
| 119 | + g.setRotation(settings.rotate); |
| 120 | + |
| 121 | + g.setFontDoto(4); |
| 122 | + g.setColor(g.theme.fg); |
| 123 | + g.setBgColor(g.theme.bg); |
| 124 | + |
| 125 | + if (settings.countdown) { // Display a countdown timer before scrolling |
| 126 | + g.setFontAlign(0, 0); |
| 127 | + countdownNum = 3; |
| 128 | + countdown(); |
| 129 | + countdownInterval = setInterval(() => { |
| 130 | + if (countdownNum == 0) { |
| 131 | + clearInterval(countdownInterval); |
| 132 | + startScrolling(); |
| 133 | + } else { |
| 134 | + countdown(); |
| 135 | + } |
| 136 | + }, 1000); |
| 137 | + } else { // No countdown, so just scroll |
| 138 | + startScrolling(); |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +// Start the scroll animation |
| 143 | +function startScrolling() { |
| 144 | + g.clear(); |
| 145 | + g.setFontAlign(-1, 0); // Vertical align text |
| 146 | + |
| 147 | + x=g.getWidth(); // Start the text just off screen |
| 148 | + |
| 149 | + scrollInterval = setInterval(() => { |
| 150 | + g.clear(); |
| 151 | + g.drawString(text, x, g.getHeight()/2); |
| 152 | + x=x-10; |
| 153 | + if (x<-g.stringWidth(text)) { // Loop the text |
| 154 | + x=g.getWidth(); |
| 155 | + } |
| 156 | + }, settings.speed); |
| 157 | +} |
| 158 | + |
| 159 | +// Show countdown timer |
| 160 | +function countdown() { |
| 161 | + g.clear(); |
| 162 | + g.drawString(countdownNum, g.getWidth()/2, g.getHeight()/2); |
| 163 | + countdownNum--; |
| 164 | +} |
| 165 | + |
| 166 | +// Load keyboard and set text |
| 167 | +function editText() { |
| 168 | + try { |
| 169 | + require("textinput").input({text:text}).then(result => { |
| 170 | + text = result; |
| 171 | + if (settings.rememberText) { |
| 172 | + setSetting('lastText', text); |
| 173 | + } |
| 174 | + layout.text.label = text ? text : 'Enter text...'; // Show prompt if text not set |
| 175 | + layout.text.col = text ? g.theme.fg2 : '#555'; // Grey text if prompt shown |
| 176 | + layout.setUI(); |
| 177 | + layout.render(); |
| 178 | + Bangle.drawWidgets(); |
| 179 | + }); |
| 180 | + } catch (error) { |
| 181 | + E.showAlert('Please install a keyboard app').then(() => { |
| 182 | + layout.setUI(); |
| 183 | + layout.render(); |
| 184 | + }); |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +// Save setting key |
| 189 | +function setSetting(key,value) { |
| 190 | + settings[key] = value; |
| 191 | + require('Storage').writeJSON(SETTINGS, settings); |
| 192 | +} |
| 193 | + |
| 194 | +// Helper method which uses int-based menu item for set of string values and their labels |
| 195 | +function stringItems(key, startvalue, values, labels) { |
| 196 | + return { |
| 197 | + value: (startvalue === undefined ? 0 : values.indexOf(startvalue)), |
| 198 | + format: v => labels[v], |
| 199 | + min: 0, |
| 200 | + max: values.length - 1, |
| 201 | + wrap: true, |
| 202 | + step: 1, |
| 203 | + onchange: v => { |
| 204 | + setSetting(key,values[v]); |
| 205 | + } |
| 206 | + }; |
| 207 | +} |
| 208 | + |
| 209 | +// Helper method which breaks string set settings down to local settings object |
| 210 | +function stringInSettings(name, values, labels) { |
| 211 | + return stringItems(name,settings[name], values, labels); |
| 212 | +} |
| 213 | + |
| 214 | +// Settings menu |
| 215 | +function showSettings() { |
| 216 | + E.showMenu({ |
| 217 | + '' : { 'title' : 'Settings' }, |
| 218 | + '< Back' : () => { |
| 219 | + g.clearRect(Bangle.appRect); |
| 220 | + layout.setUI(); |
| 221 | + layout.render(); |
| 222 | + }, |
| 223 | + 'Rotate': stringInSettings('rotate', [0, 1, 2, 3], [0, 90, 180, 270]), |
| 224 | + 'Speed': stringInSettings('speed', [10, 50, 80], ['Fast', 'Medium', 'Slow']), |
| 225 | + 'Max bright': { |
| 226 | + value: !!settings.maxBright, |
| 227 | + onchange: v => { |
| 228 | + setSetting('maxBright', v); |
| 229 | + }, |
| 230 | + }, |
| 231 | + "Don't dim": { |
| 232 | + value: !!settings.doNotDim, |
| 233 | + onchange: v => { |
| 234 | + setSetting('doNotDim', v); |
| 235 | + }, |
| 236 | + }, |
| 237 | + 'Countdown': { |
| 238 | + value: !!settings.countdown, |
| 239 | + onchange: v => { |
| 240 | + setSetting('countdown', v); |
| 241 | + }, |
| 242 | + }, |
| 243 | + 'Remember text': { |
| 244 | + value: !!settings.rememberText, |
| 245 | + onchange: v => { |
| 246 | + setSetting('rememberText', v); |
| 247 | + if (v) { |
| 248 | + setSetting('lastText', text); |
| 249 | + } else { |
| 250 | + setSetting('lastText', ''); |
| 251 | + } |
| 252 | + }, |
| 253 | + } |
| 254 | + }); |
| 255 | +} |
| 256 | + |
| 257 | +// Initial layout render |
| 258 | +g.clear(); |
| 259 | +Bangle.drawWidgets(); |
| 260 | +layout.render(); |
| 261 | + |
| 262 | + |
0 commit comments