Skip to content

Commit cc91dd1

Browse files
authored
Merge pull request espruino#3654 from retcurve/scrolly
New app: scrolly
2 parents 0af85b6 + 9178b8b commit cc91dd1

File tree

7 files changed

+310
-0
lines changed

7 files changed

+310
-0
lines changed

apps/scrolly/ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.01: New app!

apps/scrolly/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# ![Image](app.png "icon") Scrolly
2+
3+
**Note:** This app requires a [keyboard library](https://banglejs.com/apps/?c=textinput#)
4+
5+
Scrolly is a simple app designed to display scrolling text across your screen in a clean and dynamic way. Perfect for announcements or just for fun!
6+
7+
![Image](screenshot.gif "screenshot")
8+
9+
## Usage
10+
11+
* **Edit Text:** Tap the text area or the pencil icon to modify your message.
12+
* **Access Settings:** Tap the gear icon to open the settings menu.
13+
* **Start Scrolling:** Tap the play icon to begin the scrolling text.
14+
15+
## Features
16+
17+
* **Customizable Scroll Speed:** Control how fast or slow the text moves.
18+
* **Multiple Scroll Directions:** Choose from 0, 90, 180, or 270 degree rotation of the screen
19+
* **No Screen Dimming:** Optionally keep your screen bright and active while the text scrolls
20+
* **Countdown:** Start your scroll with an optional countdown timer, giving your audience a brief pause before the message begins to move.
21+
* **Persistent settings:** Automatically remembers your last entered message and settings when you exit the app, so you can pick up right where you left off without having to re-enter everything.
22+
23+
## Author
24+
25+
Woogal [github](https://github.com/retcurve)

apps/scrolly/app-icon.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require("heatshrink").decompress(atob("mEwgI63nfcAp4AEgeAgPAApoXHAqpNdAAJBFApYAJNa5NZAGQA=="))

apps/scrolly/app.js

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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+

apps/scrolly/app.png

313 Bytes
Loading

apps/scrolly/metadata.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"id": "scrolly",
3+
"name": "Scrolly",
4+
"shortName":"Scrolly",
5+
"icon": "app.png",
6+
"screenshots" : [ { "url":"screenshot.gif" } ],
7+
"version":"0.01",
8+
"description": "Scrolly is a simple app designed to display scrolling text across your screen in a clean and dynamic way. Perfect for announcements or just for fun!",
9+
"readme":"README.md",
10+
"type": "app",
11+
"tags": "tool",
12+
"supports": ["BANGLEJS2"],
13+
"dependencies": {"textinput":"type"},
14+
"storage": [
15+
{"name":"scrolly.app.js","url":"app.js"},
16+
{"name":"scrolly.img","url":"app-icon.js","evaluate":true}
17+
],
18+
"data": [
19+
{"name":"scrolly.json"}
20+
]
21+
}

apps/scrolly/screenshot.gif

43.9 KB
Loading

0 commit comments

Comments
 (0)