From 81378359c9624ae1bd4243cad06ff25f881eec7f Mon Sep 17 00:00:00 2001 From: Matt Walsh Date: Fri, 4 Sep 2020 15:46:31 -0500 Subject: [PATCH] forward/back navigation through travel forecast with timing --- README.md | 2 +- server/scripts/modules/latestobservations.js | 1 + server/scripts/modules/travelforecast.js | 71 +++++++++-------- server/scripts/modules/weatherdisplay.js | 80 ++++++++++++++++---- 4 files changed, 108 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index dd1ac3d..ad42080 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This project aims to bring back the feel of the 90's with a weather forecast tha This project is based on the work of [Mike Battaglia](https://github.com/vbguyny/ws4kp). It was forked from his work in August 2020. -* Mike Battaglia For the original project and all of the code which draws the weather displays. This code remains largely intact and was a huge amount of work to get exactly right. +* Mike Battaglia for the original project and all of the code which draws the weather displays. This code remains largely intact and was a huge amount of work to get exactly right. He's also responsible for all of the background graphics including the maps used in the application. * The team at [TWCClassics](https://twcclassics.com/) for several resources. * A [font](https://twcclassics.com/downloads.html) set used on the original WeatherStar 4000 * [Icon](https://twcclassics.com/downloads.html) sets diff --git a/server/scripts/modules/latestobservations.js b/server/scripts/modules/latestobservations.js index ebb9368..ce95ccd 100644 --- a/server/scripts/modules/latestobservations.js +++ b/server/scripts/modules/latestobservations.js @@ -15,6 +15,7 @@ class LatestObservations extends WeatherDisplay { } async getData(weatherParameters) { + super.getData(); // calculate distance to each station const stationsByDistance = Object.keys(_StationInfo).map(key => { const station = _StationInfo[key]; diff --git a/server/scripts/modules/travelforecast.js b/server/scripts/modules/travelforecast.js index fbaedae..6b065c5 100644 --- a/server/scripts/modules/travelforecast.js +++ b/server/scripts/modules/travelforecast.js @@ -9,15 +9,30 @@ class TravelForecast extends WeatherDisplay { // pre-load background image (returns promise) this.backgroundImage = utils.image.load('images/BackGround6_1.png'); + // height of one city in the travel forecast + this.cityHeight = 72; + + // set up the timing + this.timing.baseDelay = 20; + // page sizes are 4 cities, calculate the number of pages necessary plus overflow + const pagesFloat = _TravelCities.length/4; + const pages = Math.floor(pagesFloat) - 1; // first page is already displayed + const extra = pages%1; + const timingStep = this.cityHeight*4; + this.timing.delay = [150]; + // add additional pages + for (let i = 0; i < pages; i++) this.timing.delay.push(timingStep); + // add the extra (not exactly 4 pages portion) + if (extra !== 0) this.timing.delay.push(Math.round(this.extra*this.cityHeight)); + // add the final 3 second delay + this.timing.delay.push(150); + // get the data this.getData(weatherParameters); - - // scrolling tracking - this.scrollCount = 0; - this.endDelay = 0; } async getData() { + super.getData(); const forecastPromises = _TravelCities.map(async city => { try { // get point then forecast @@ -70,7 +85,7 @@ class TravelForecast extends WeatherDisplay { this.longContext.clearRect(0,0,this.longCanvas.width,this.longCanvas.height); // draw the "long" canvas with all cities - draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, 1728); + draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, _TravelCities.length*this.cityHeight); for (let i = 0; i <= 4; i++) { const y = i * 346; @@ -79,7 +94,7 @@ class TravelForecast extends WeatherDisplay { await Promise.all(cities.map(async (city, index) => { // calculate base y value - const y = 50+72*index; + const y = 50+this.cityHeight*index; // city name draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, city.name, 2); @@ -134,34 +149,30 @@ class TravelForecast extends WeatherDisplay { // copy the scrolled portion of the canvas for the initial run before the scrolling starts this.context.drawImage(this.longCanvas, 0, 0, 640, 289, 0, 110, 640, 289); - // set up scrolling one time - if (!this.scrollInterval) { - this.scrollInterval = window.setInterval(() => { - if (this.isActive()) { - // get a fresh canvas - const longCanvas = this.getLongCanvas(); - // increment scrolling - this.scrollCount++; - // wait 3 seconds at begining - if (this.scrollCount < 150) return; - // calculate scroll offset and don't go past end of canvas - const offsetY = Math.min(longCanvas.height-289, (this.scrollCount-150)); - // copy the scrolled portion of the canvas - this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289); - // track end of scrolling for 3 seconds - if (offsetY >= longCanvas.height-289) this.endDelay++; - // TODO: report playback done - } else { - // reset scroll to top of image - this.scrollCount = 0; - this.endDelay = 0; - } - }, 20); - } this.finishDraw(); this.setStatus(STATUS.loaded); } + // screen index change callback just runs the base count callback + screenIndexChange() { + this.baseCountChange(this.navBaseCount); + } + + // base count change callback + baseCountChange(count) { + // get a fresh canvas + const longCanvas = this.getLongCanvas(); + + // calculate scroll offset and don't go past end + let offsetY = Math.min(longCanvas.height-289, (count-this.timing.delay[0])); + + // don't let offset go negative + if (offsetY < 0) offsetY = 0; + + // copy the scrolled portion of the canvas + this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289); + } + getTravelCitiesDayName(cities) { const {DateTime} = luxon; // effectively returns early on the first found date diff --git a/server/scripts/modules/weatherdisplay.js b/server/scripts/modules/weatherdisplay.js index 822a7b5..6db98bb 100644 --- a/server/scripts/modules/weatherdisplay.js +++ b/server/scripts/modules/weatherdisplay.js @@ -63,6 +63,22 @@ class WeatherDisplay { this.data = undefined; // set status this.setStatus(STATUS.loading); + + // set up the timing delays + if (Array.isArray(this.timing.delay) && typeof this.timing.delay[0] === 'number') { + // array is defined as how long each screen should be displayed. This needs to be converted into total time for use here + if (!this.timing.fullDelay) { + let sum = 0; + this.timing.fullDelay = this.timing.delay.map(val => { + const calc = sum + val; + sum += val; + return calc; + }); + } + } + + // update total screens + if (Array.isArray(this.timing.delay)) this.timing.totalScreens = this.timing.delay.length; } drawCanvas() { @@ -216,6 +232,9 @@ class WeatherDisplay { // reset timing this.startNavCount(navigation.isPlaying()); + // if there was a command the canvas has already been drawn + if (navCmd) return; + // refresh the canvas (incase the screen index changed) if (navCmd) this.drawCanvas(); } @@ -244,8 +263,8 @@ class WeatherDisplay { // increment the base count this.navBaseCount++; - // update total screens - if (Array.isArray(this.timing.delay)) this.timing.totalScreens = this.timing.delay.length; + // call base count change if available for this function + if (this.baseCountChange) this.baseCountChange(this.navBaseCount); // determine type of timing // simple delay @@ -253,6 +272,16 @@ class WeatherDisplay { this.navNext(); return; } + + // array of timing integers + if (Array.isArray(this.timing.delay) && typeof this.timing.delay[0] === 'number') { + // scan the array for a matching number and calculate new screen index from the number + const timingMatch = this.timing.fullDelay.indexOf(this.navBaseCount); + // if not found return + if (timingMatch < 0) return; + // navigate to the next screen + this.navNext(); + } } // navigate to next screen @@ -260,20 +289,24 @@ class WeatherDisplay { // check for special 'first frame' command if (command === navigation.msg.command.firstFrame) { this.resetNavBaseCount(); - this.drawCanvas(); - return; + } else { + // increment screen index + this.screenIndex++; } - - // increment screen index - this.screenIndex++; // test for end reached if (this.screenIndex >= this.timing.totalScreens) { + this.screenIndex = this.timing.totalScreens - 1; this.sendNavDisplayMessage(navigation.msg.response.next); this.stopNavBaseCount(); return; } - // if the end was not reached, update the canvas - this.drawCanvas(); + this.baseCountFromScreenIndex(); + // if the end was not reached, update the canvas (typical), or run a callback (atypical) + if (!this.screenIndexChange) { + this.drawCanvas(); + } else { + this.screenIndexChange(this.screenIndex); + } } // navigate to previous screen @@ -281,19 +314,36 @@ class WeatherDisplay { // check for special 'last frame' command if (command === navigation.msg.command.lastFrame) { this.screenIndex = this.timing.totalScreens-1; - this.drawCanvas(); - return; + } else { + // decrement screen index + this.screenIndex--; } - // decrement screen index - this.screenIndex--; // test for end reached if (this.screenIndex < 0) { + this.screenIndex = 0; this.sendNavDisplayMessage(navigation.msg.response.previous); return; } - // if the end was not reached, update the canvas - this.drawCanvas(); + this.baseCountFromScreenIndex(); + // if the end was not reached, update the canvas (typical), or run a callback (atypical) + if (!this.screenIndexChange) { + this.drawCanvas(); + } else { + this.screenIndexChange(this.screenIndex); + } + } + + // calculate a baseCount from the screen index for the array timings + baseCountFromScreenIndex() { + if (!Array.isArray(this.timing.delay)) return; + // first screen starts at zero + if (this.screenIndex === 0) { + this.navBaseCount = 0; + return; + } + // otherwise return one more than the previous sum + this.navBaseCount = this.timing.fullDelay[this.screenIndex]; } // start and stop base counter