Skip to content

Commit

Permalink
Merge pull request #3654 from retcurve/scrolly
Browse files Browse the repository at this point in the history
New app: scrolly
  • Loading branch information
bobrippling authored Nov 19, 2024
2 parents 0af85b6 + 9178b8b commit cc91dd1
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 0 deletions.
1 change: 1 addition & 0 deletions apps/scrolly/ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.01: New app!
25 changes: 25 additions & 0 deletions apps/scrolly/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ![Image](app.png "icon") Scrolly

**Note:** This app requires a [keyboard library](https://banglejs.com/apps/?c=textinput#)

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!

![Image](screenshot.gif "screenshot")

## Usage

* **Edit Text:** Tap the text area or the pencil icon to modify your message.
* **Access Settings:** Tap the gear icon to open the settings menu.
* **Start Scrolling:** Tap the play icon to begin the scrolling text.

## Features

* **Customizable Scroll Speed:** Control how fast or slow the text moves.
* **Multiple Scroll Directions:** Choose from 0, 90, 180, or 270 degree rotation of the screen
* **No Screen Dimming:** Optionally keep your screen bright and active while the text scrolls
* **Countdown:** Start your scroll with an optional countdown timer, giving your audience a brief pause before the message begins to move.
* **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.

## Author

Woogal [github](https://github.com/retcurve)
1 change: 1 addition & 0 deletions apps/scrolly/app-icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgI63nfcAp4AEgeAgPAApoXHAqpNdAAJBFApYAJNa5NZAGQA=="))
262 changes: 262 additions & 0 deletions apps/scrolly/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
Bangle.loadWidgets();

Graphics.prototype.setFontDoto = function(scale) {
// Actual height 40 (39 - 0)
// 1 BPP
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
32,
atob("GAwQFBQUFAwQEBQUEBQQFBQUFBQUFBQUFBQQEBQUFBQUFBQUFBQUFBQQFBQUFBQUFBQUFBQUFBQUFBQQFBAUFBAUFBQUFBQUFBAUFBAUFBQUFBQUFBQUFBQUFBQMFBQAFgsQACcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBQUExQGFBAUFBgXABQUEBYNDBAUFAwQCxQYHBwdFBQUFBQUFBQUFBQUFBAQEBAUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEBAQEBQUFBQUFBQUFBQUFBQUFBQ="),
40+(scale<<8)+(1<<16)
);
};

// Load settings
const SETTINGS = 'scrolly.json';
let settings = Object.assign({
maxBright: true,
doNotDim: true,
speed: 50,
rotate: 0,
countdown: true,
lastText: '',
rememberText: true
}, require('Storage').readJSON(SETTINGS, true) || {});

let x;
let scrollInterval, countdownInterval;
let countdownNum;
let text = settings.lastText;
let originalOptions = Bangle.getOptions();
let brightness = require('Storage').readJSON('setting.json').brightness;

// Main interface
var Layout = require("Layout");
var layout = new Layout( {
type: 'v', c: [
{ // Text area
type: 'txt',
font: '6x8:2',
label: text ? text : 'Enter text...', // Show prompt if text not set
col: text ? g.theme.fg2 : '#555', // Grey text if prompt shown
id: 'text',
wrap: true,
height: Bangle.appRect.h - 52, // Icons are 32px, padding 10px top and bottom
width: g.getWidth(),
valign:0,
cb: l=> editText() // Tapping the text area triggers the same editor as the pen icon
},
{
type: 'h', c: [
{ // Edit icon
type: 'img',
pad: 10,
src: require("heatshrink").decompress(atob("kEg4kA///6f8gH41VStP6gGYBYPv2dV1uyykkvOe+/LrVNteW2sA3Bc4hnM4APM5mY5geM51m3ogL5mGs1r7geK48bswgL5lxiO2EAJBIDwMRiIgC3ggHDwURilGs25B4weDiNCkgPIDwkikV7xgeLkUvrpPGDw311guFDw9aD0sMyIeMB4O2DxgPCsIeLgHNW4MSDxUA5IPBsgeKgHOB4Nmp4eJgHLB4W/DxMM522s279PDDxEM7HuxPc5kwB5Pd5nMFhAPDDZQAyA=")),
cb: l=> editText()
},
{ // Settings icon
type: 'img',
pad: 10,
src: require("heatshrink").decompress(atob("kEg4UA///6ec1VSqOMEzUVqoHFqoHUisBA4NAiglDr/1qtUCofq1YZDqte1Wq2oZBDoNaA4OlA4dqA4OpqtQA4Pq1EqEANAF4Oq0EK1QvCguqwEC1VQA4wPKD4IPFF4InBF4MAH4xHCvQHB1oHDL4ofBq4HB6pvDqt//p/DisFR4QHCV64HJiolEAAw=")),
cb: l=> showSettings()
},
{ // Scroll icon
type: 'img',
pad: 10,
src: require("heatshrink").decompress(atob("kEg4UB8EH/4AC+mnqoAQqAgBA4goCgoGCqgxDA4VAA4cVCwgYEA4kBDwogCGoMD+AHFgWsFAYHC1XAA4Q2BA4OwA42oA4wYCgoHEDAIHNC42rMAQHD0AvGI4ZPCCwQHDgYWCM4J3HA4qHBS5CnHYwTIFDwoQECwTJDAwoAFA")),
id: 'scrollBtn',
cb: l=>{
if (text) {
scrollText();
} else {
E.showAlert('No text to scroll').then(() => {
layout.setUI();
layout.render();
});
}
}
},
]
}
]
}, {back: load});

// Display scrolling text
function scrollText() {
require("widget_utils").hide();

Bangle.setUI({
mode:"custom",
btn: () => { // Button returns to main interface
if (scrollInterval) // Stop scroll
clearInterval(scrollInterval);
if (countdownInterval) // Stop countdown
clearInterval(countdownInterval);

// Reset screen to original settings
g.setRotation(0);
Bangle.setOptions(originalOptions);
Bangle.setLCDBrightness(brightness);

// Draw the main interface
require("widget_utils").show();
g.clearRect(Bangle.appRect);
layout.setUI();
layout.render();
}
});

// Setup screen
if (settings.doNotDim) { // Don't lock or turn off the backlight while scrolling
Bangle.setOptions({backlightTimeout: 0, lockTimeout: 0});
Bangle.setBacklight(1);
} else { // For some reason the screen locking resets the font style and alignment, so don't allow this to happen while scrolling
Bangle.setOptions({lockTimeout: 0});
}
if (settings.maxBright) {
Bangle.setLCDBrightness(1);
}
g.setRotation(settings.rotate);

g.setFontDoto(4);
g.setColor(g.theme.fg);
g.setBgColor(g.theme.bg);

if (settings.countdown) { // Display a countdown timer before scrolling
g.setFontAlign(0, 0);
countdownNum = 3;
countdown();
countdownInterval = setInterval(() => {
if (countdownNum == 0) {
clearInterval(countdownInterval);
startScrolling();
} else {
countdown();
}
}, 1000);
} else { // No countdown, so just scroll
startScrolling();
}
}

// Start the scroll animation
function startScrolling() {
g.clear();
g.setFontAlign(-1, 0); // Vertical align text

x=g.getWidth(); // Start the text just off screen

scrollInterval = setInterval(() => {
g.clear();
g.drawString(text, x, g.getHeight()/2);
x=x-10;
if (x<-g.stringWidth(text)) { // Loop the text
x=g.getWidth();
}
}, settings.speed);
}

// Show countdown timer
function countdown() {
g.clear();
g.drawString(countdownNum, g.getWidth()/2, g.getHeight()/2);
countdownNum--;
}

// Load keyboard and set text
function editText() {
try {
require("textinput").input({text:text}).then(result => {
text = result;
if (settings.rememberText) {
setSetting('lastText', text);
}
layout.text.label = text ? text : 'Enter text...'; // Show prompt if text not set
layout.text.col = text ? g.theme.fg2 : '#555'; // Grey text if prompt shown
layout.setUI();
layout.render();
Bangle.drawWidgets();
});
} catch (error) {
E.showAlert('Please install a keyboard app').then(() => {
layout.setUI();
layout.render();
});
}
}

// Save setting key
function setSetting(key,value) {
settings[key] = value;
require('Storage').writeJSON(SETTINGS, settings);
}

// Helper method which uses int-based menu item for set of string values and their labels
function stringItems(key, startvalue, values, labels) {
return {
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
format: v => labels[v],
min: 0,
max: values.length - 1,
wrap: true,
step: 1,
onchange: v => {
setSetting(key,values[v]);
}
};
}

// Helper method which breaks string set settings down to local settings object
function stringInSettings(name, values, labels) {
return stringItems(name,settings[name], values, labels);
}

// Settings menu
function showSettings() {
E.showMenu({
'' : { 'title' : 'Settings' },
'< Back' : () => {
g.clearRect(Bangle.appRect);
layout.setUI();
layout.render();
},
'Rotate': stringInSettings('rotate', [0, 1, 2, 3], [0, 90, 180, 270]),
'Speed': stringInSettings('speed', [10, 50, 80], ['Fast', 'Medium', 'Slow']),
'Max bright': {
value: !!settings.maxBright,
onchange: v => {
setSetting('maxBright', v);
},
},
"Don't dim": {
value: !!settings.doNotDim,
onchange: v => {
setSetting('doNotDim', v);
},
},
'Countdown': {
value: !!settings.countdown,
onchange: v => {
setSetting('countdown', v);
},
},
'Remember text': {
value: !!settings.rememberText,
onchange: v => {
setSetting('rememberText', v);
if (v) {
setSetting('lastText', text);
} else {
setSetting('lastText', '');
}
},
}
});
}

// Initial layout render
g.clear();
Bangle.drawWidgets();
layout.render();


Binary file added apps/scrolly/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions apps/scrolly/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"id": "scrolly",
"name": "Scrolly",
"shortName":"Scrolly",
"icon": "app.png",
"screenshots" : [ { "url":"screenshot.gif" } ],
"version":"0.01",
"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!",
"readme":"README.md",
"type": "app",
"tags": "tool",
"supports": ["BANGLEJS2"],
"dependencies": {"textinput":"type"},
"storage": [
{"name":"scrolly.app.js","url":"app.js"},
{"name":"scrolly.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"scrolly.json"}
]
}
Binary file added apps/scrolly/screenshot.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit cc91dd1

Please sign in to comment.