· 5 years ago · Oct 06, 2020, 11:44 PM
1// Added info
2let collection = "932809"
3let widgetInputRAW = args.widgetParameter
4if (widgetInputRAW) {
5 try {
6 widgetInputRAW.toString()
7 if (widgetInputRAW.toString() !== "") {
8 collection = widgetInputRAW.toString()
9 }
10 } catch (e) {
11 throw new Error("Please long press the widget and add a parameter.")
12 }
13}
14
15/*
16 * SETUP
17 * Use this section to set up the widget.
18 * ======================================
19 */
20
21// Get a free API key here: openweathermap.org/appid
22const apiKey = ""
23
24// Set to true for fixed location, false to update location as you move around
25const lockLocation = true
26
27// Set to imperial for Fahrenheit, or metric for Celsius
28const units = "imperial"
29
30// The size of the widget preview in the app.
31const widgetPreview = "large"
32
33// Set to true for an image background, false for no image.
34const imageBackground = true
35
36// Set to true and run the script once to update the image manually.
37const forceImageUpdate = false
38
39/*
40 * LAYOUT
41 * Decide what elements to show on the widget.
42 * ===========================================
43 */
44
45// Set the width of the column, or set to 0 for an automatic width.
46
47// You can add items to the column:
48// date, greeting, events, current, future, text("Your text here")
49// You can also add a left, center, or right to the list. Everything after it will be aligned that way.
50
51// Make sure to always put a comma after each item.
52
53const columns = [{
54
55 // Settings for the left column.
56 width: 0,
57 items: [
58
59 left,
60 date,
61 events,
62
63end]}, {
64
65 // Settings for the right column.
66 width: 100,
67 items: [
68
69 left,
70 current,
71 space,
72 future,
73
74end]}]
75
76/*
77 * FORMATTING
78 * Choose how each element is displayed.
79 * =====================================
80 */
81
82// How many events to show.
83const numberOfEvents = 3
84
85// Show today's high and low temperatures.
86const showHighLow = true
87
88// Set the hour (in 24-hour time) to switch to tomorrow's weather. Set to 24 to never show it.
89const tomorrowShownAtHour = 20
90
91// If set to true, date will become smaller when events are displayed.
92const dynamicDateSize = true
93
94// If the date is not dynamic, should it be large or small?
95const staticDateSize = "large"
96
97// Determine the date format for each element. See docs.scriptable.app/dateformatter
98const smallDateFormat = "EEEE, MMMM d"
99const largeDateLineOne = "EEEE,"
100const largeDateLineTwo = "MMMM d"
101
102// In this section, set the font, size, and color. Use iosfonts.com to find fonts to use. If you want to use the default iOS font, set the font name to one of the following: ultralight, light, regular, medium, semibold, bold, heavy, black, or italic.
103const textFormat = {
104
105 // Set the default font and color.
106 defaultText: { size: 14, color: "ffffff", font: "regular" },
107
108 // Any blank values will use the default.
109 smallDate: { size: 17, color: "", font: "semibold" },
110 largeDate1: { size: 30, color: "", font: "light" },
111 largeDate2: { size: 30, color: "", font: "light" },
112
113 greeting: { size: 30, color: "", font: "semibold" },
114 eventTitle: { size: 14, color: "", font: "semibold" },
115 eventTime: { size: 14, color: "ffffffcc", font: "" },
116
117 largeTemp: { size: 34, color: "", font: "light" },
118 smallTemp: { size: 14, color: "", font: "" },
119 tinyTemp: { size: 12, color: "", font: "" },
120
121 customText: { size: 14, color: "", font: "" }
122}
123
124/*
125 * WIDGET CODE
126 * Be more careful editing this section.
127 * =====================================
128 */
129
130// Set up the date and event information.
131const currentDate = new Date()
132const allEvents = await CalendarEvent.today([])
133const futureEvents = enumerateEvents()
134const eventsAreVisible = (futureEvents.length > 0) && (numberOfEvents > 0)
135
136// Set up the file manager.
137const files = FileManager.local()
138
139// Set up the location logic.
140const locationPath = files.joinPath(files.documentsDirectory(), "weather-cal-location")
141var latitude, longitude
142
143// If we're locking our location and it's saved already, read from the file.
144if (lockLocation && files.fileExists(locationPath)) {
145 const locationStr = files.readString(locationPath).split(",")
146 latitude = locationStr[0]
147 longitude = locationStr[1]
148
149// Otherwise, get the location from the system.
150} else {
151 const location = await Location.current()
152 latitude = location.latitude
153 longitude = location.longitude
154 files.writeString(locationPath, latitude + "," + longitude)
155}
156
157// Set up the cache.
158const cachePath = files.joinPath(files.documentsDirectory(), "weather-cal-cache")
159const cacheExists = files.fileExists(cachePath)
160const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0
161var data
162
163// If cache exists and it's been less than 60 seconds since last request, use cached data.
164if (cacheExists && (currentDate.getTime() - cacheDate.getTime()) < 60000) {
165 const cache = files.readString(cachePath)
166 data = JSON.parse(cache)
167
168// Otherwise, use the API to get new weather data.
169} else {
170 const weatherReq = "https://api.openweathermap.org/data/2.5/onecall?lat=" + latitude + "&lon=" + longitude + "&exclude=minutely,alerts&units=" + units + "&lang=en&appid=" + apiKey
171 data = await new Request(weatherReq).loadJSON()
172 files.writeString(cachePath, JSON.stringify(data))
173}
174
175// Store the weather values.
176const currentTemp = data.current.temp
177const currentCondition = data.current.weather[0].id
178const todayHigh = data.daily[0].temp.max
179const todayLow = data.daily[0].temp.min
180
181const nextHourTemp = data.hourly[1].temp
182const nextHourCondition = data.hourly[1].weather[0].id
183
184const tomorrowHigh = data.daily[1].temp.max
185const tomorrowLow = data.daily[1].temp.min
186const tomorrowCondition = data.daily[1].weather[0].id
187
188// Set up the sunrise/sunset cache.
189const sunCachePath = files.joinPath(files.documentsDirectory(), "weather-cal-sun")
190const sunCacheExists = files.fileExists(sunCachePath)
191const sunCacheDate = sunCacheExists ? files.modificationDate(sunCachePath) : 0
192var sunData
193
194// If cache exists and it was created today, use cached data.
195if (sunCacheExists && sameDay(currentDate, sunCacheDate)) {
196 const sunCache = files.readString(sunCachePath)
197 sunData = JSON.parse(sunCache)
198
199// Otherwise, use the API to get sunrise and sunset times.
200} else {
201 const sunReq = "https://api.sunrise-sunset.org/json?lat=" + latitude + "&lng=" + longitude + "&formatted=0&date=" + currentDate.getFullYear() + "-" + (currentDate.getMonth()+1) + "-" + currentDate.getDate()
202 sunData = await new Request(sunReq).loadJSON()
203 files.writeString(sunCachePath, JSON.stringify(sunData))
204}
205
206// Store the timing values.
207const sunrise = new Date(sunData.results.sunrise).getTime()
208const sunset = new Date(sunData.results.sunset).getTime()
209const utcTime = currentDate.getTime()
210
211/*
212 * COLUMNS AND PADDING
213 * ===================
214 */
215
216// Set up the widget and the main stack.
217let widget = new ListWidget()
218widget.setPadding(0, 0, 0, 0)
219
220let mainStack = widget.addStack()
221mainStack.layoutHorizontally()
222mainStack.setPadding(0, 0, 0, 0)
223
224// Set up alignment
225var currentAlignment = left
226
227// Set up our columns.
228for (var x = 0; x < columns.length; x++) {
229
230 let column = columns[x]
231 let columnStack = mainStack.addStack()
232 columnStack.layoutVertically()
233
234 // Only add padding on the first or last column.
235 columnStack.setPadding(0, x == 0 ? 5 : 0, 0, x == columns.length-1 ? 5 : 0)
236 columnStack.size = new Size(column.width,0)
237
238 // Add the items to the column.
239 for (var i = 0; i < column.items.length; i++) {
240 column.items[i](columnStack)
241 }
242}
243
244/*
245 * BACKGROUND DISPLAY
246 * ==================
247 */
248
249// If it's an image background, display it.
250if (imageBackground) {
251
252 // Determine if our image exists and when it was saved.
253 const path = files.joinPath(files.documentsDirectory(), "weather-cal-image")
254 const exists = files.fileExists(path)
255 const createdToday = exists ? sameDay(files.modificationDate(path),currentDate) : false
256
257 // If it exists and updates aren't being forced, use the cache.
258 if (exists && !forceImageUpdate) {
259 widget.backgroundImage = files.readImage(path)
260
261 // If it's missing, forced to update, or not created today, download it.
262 } else if (!exists || forceImageUpdate) {
263
264 try {
265 let img = await new Request("https://source.unsplash.com/collection/" + collection).loadImage()
266 files.writeImage(path, img)
267 widget.backgroundImage = img
268 } catch {
269 widget.backgroundImage = files.readImage(path)
270 }
271
272 }
273
274// If it's not an image background, show the gradient.
275} else {
276 let gradient = new LinearGradient()
277 let gradientSettings = getGradientSettings()
278
279 gradient.colors = gradientSettings.color()
280 gradient.locations = gradientSettings.position()
281
282 widget.backgroundGradient = gradient
283}
284
285Script.setWidget(widget)
286if (widgetPreview == "small") { widget.presentSmall() }
287else if (widgetPreview == "medium") { widget.presentMedium() }
288else if (widgetPreview == "large") { widget.presentLarge() }
289Script.complete()
290
291/*
292 * IMAGES AND FORMATTING
293 * =====================
294 */
295
296// Get the gradient settings for each time of day.
297function getGradientSettings() {
298
299 let gradient = {
300 "dawn": {
301 "color": function() { return [new Color("142C52"), new Color("1B416F"), new Color("62668B")] },
302 "position": function() { return [0, 0.5, 1] }
303 },
304
305 "sunrise": {
306 "color": function() { return [new Color("274875"), new Color("766f8d"), new Color("f0b35e")] },
307 "position": function() { return [0, 0.8, 1.5] }
308 },
309
310 "midday": {
311 "color": function() { return [new Color("3a8cc1"), new Color("90c0df")] },
312 "position": function() { return [0, 1] }
313 },
314
315 "noon": {
316 "color": function() { return [new Color("b2d0e1"), new Color("80B5DB"), new Color("3a8cc1")] },
317 "position": function() { return [-0.2, 0.2, 1.5] }
318 },
319
320 "sunset": {
321 "color": function() { return [new Color("32327A"), new Color("662E55"), new Color("7C2F43")] },
322 "position": function() { return [0.1, 0.9, 1.2] }
323 },
324
325 "twilight": {
326 "color": function() { return [new Color("021033"), new Color("16296b"), new Color("414791")] },
327 "position": function() { return [0, 0.5, 1] }
328 },
329
330 "night": {
331 "color": function() { return [new Color("16296b"), new Color("021033"), new Color("021033"), new Color("113245")] },
332 "position": function() { return [-0.5, 0.2, 0.5, 1] }
333 }
334 }
335
336 function closeTo(time,mins) {
337 return Math.abs(utcTime - time) < (mins * 60000)
338 }
339
340 // Use sunrise or sunset if we're within 30min of it.
341 if (closeTo(sunrise,15)) { return gradient.sunrise }
342 if (closeTo(sunset,15)) { return gradient.sunset }
343
344 // In the 30min before/after, use dawn/twilight.
345 if (closeTo(sunrise,45) && utcTime < sunrise) { return gradient.dawn }
346 if (closeTo(sunset,45) && utcTime > sunset) { return gradient.twilight }
347
348 // Otherwise, if it's night, return night.
349 if (isNight(currentDate)) { return gradient.night }
350
351 // If it's around noon, the sun is high in the sky.
352 if (currentDate.getHours() == 12) { return gradient.noon }
353
354 // Otherwise, return the "typical" theme.
355 return gradient.midday
356}
357
358// Provide a symbol based on the condition.
359function provideSymbol(cond,night) {
360
361 // Define our symbol equivalencies.
362 let symbols = {
363
364 // Thunderstorm
365 "2": function() { return "cloud.bolt.rain.fill" },
366
367 // Drizzle
368 "3": function() { return "cloud.drizzle.fill" },
369
370 // Rain
371 "5": function() { return (cond == 511) ? "cloud.sleet.fill" : "cloud.rain.fill" },
372
373 // Snow
374 "6": function() { return (cond >= 611 && cond <= 613) ? "cloud.snow.fill" : "snow" },
375
376 // Atmosphere
377 "7": function() {
378 if (cond == 781) { return "tornado" }
379 if (cond == 701 || cond == 741) { return "cloud.fog.fill" }
380 return night ? "cloud.fog.fill" : "sun.haze.fill"
381 },
382
383 // Clear and clouds
384 "8": function() {
385 if (cond == 800 || cond == 801) { return night ? "moon.stars.fill" : "sun.max.fill" }
386 if (cond == 802 || cond == 803) { return night ? "cloud.moon.fill" : "cloud.sun.fill" }
387 return "cloud.fill"
388 }
389 }
390
391 // Find out the first digit.
392 let conditionDigit = Math.floor(cond / 100)
393
394 // Get the symbol.
395 return SFSymbol.named(symbols[conditionDigit]()).image
396}
397
398// Provide a font based on the input.
399function provideFont(fontName, fontSize) {
400 const fontGenerator = {
401 "ultralight": function() { return Font.ultraLightSystemFont(fontSize) },
402 "light": function() { return Font.lightSystemFont(fontSize) },
403 "regular": function() { return Font.regularSystemFont(fontSize) },
404 "medium": function() { return Font.mediumSystemFont(fontSize) },
405 "semibold": function() { return Font.semiboldSystemFont(fontSize) },
406 "bold": function() { return Font.boldSystemFont(fontSize) },
407 "heavy": function() { return Font.heavySystemFont(fontSize) },
408 "black": function() { return Font.blackSystemFont(fontSize) },
409 "italic": function() { return Font.italicSystemFont(fontSize) }
410 }
411
412 const systemFont = fontGenerator[fontName]
413 if (systemFont) { return systemFont() }
414 return new Font(fontName, fontSize)
415}
416
417// Format text based on the settings.
418function formatText(textItem, format) {
419 const textFont = format.font || textFormat.defaultText.font
420 const textSize = format.size || textFormat.defaultText.size
421 const textColor = format.color || textFormat.defaultText.color
422
423 textItem.font = provideFont(textFont, textSize)
424 textItem.textColor = new Color(textColor)
425}
426
427/*
428 * HELPER FUNCTIONS
429 * ================
430 */
431
432// Find future events that aren't all day and aren't canceled
433function enumerateEvents() {
434 let futureEvents = []
435 for (const event of allEvents) {
436 if (event.startDate.getTime() > currentDate.getTime() && !event.isAllDay && !event.title.startsWith("Canceled:")) {
437 futureEvents.push(event)
438 }
439 }
440 return futureEvents
441}
442
443// Determines if the provided date is at night.
444function isNight(dateInput) {
445 const timeValue = dateInput.getTime()
446 return (timeValue < sunrise) || (timeValue > sunset)
447}
448
449// Determines if two dates occur on the same day
450function sameDay(d1, d2) {
451 return d1.getFullYear() === d2.getFullYear() &&
452 d1.getMonth() === d2.getMonth() &&
453 d1.getDate() === d2.getDate()
454}
455
456/*
457 * DRAWING FUNCTIONS
458 * =================
459 */
460
461// Draw the vertical line in the tomorrow view.
462function drawVerticalLine() {
463
464 const w = 2
465 const h = 20
466
467 let draw = new DrawContext()
468 draw.opaque = false
469 draw.respectScreenScale = true
470 draw.size = new Size(w,h)
471
472 let barPath = new Path()
473 const barHeight = h
474 barPath.addRoundedRect(new Rect(0, 0, w, h), w/2, w/2)
475 draw.addPath(barPath)
476 draw.setFillColor(new Color("ffffff", 0.5))
477 draw.fillPath()
478
479 return draw.getImage()
480}
481
482// Draw the temp bar.
483function drawTempBar() {
484
485 // Set the size of the temp bar.
486 const tempBarWidth = 200
487 const tempBarHeight = 20
488
489 // Calculate the current percentage of the high-low range.
490 let percent = (currentTemp - todayLow) / (todayHigh - todayLow)
491
492 // If we're out of bounds, clip it.
493 if (percent < 0) {
494 percent = 0
495 } else if (percent > 1) {
496 percent = 1
497 }
498
499 // Determine the scaled x-value for the current temp.
500 const currPosition = (tempBarWidth - tempBarHeight) * percent
501
502 // Start our draw context.
503 let draw = new DrawContext()
504 draw.opaque = false
505 draw.respectScreenScale = true
506 draw.size = new Size(tempBarWidth, tempBarHeight)
507
508 // Make the path for the bar.
509 let barPath = new Path()
510 const barHeight = tempBarHeight - 10
511 barPath.addRoundedRect(new Rect(0, 5, tempBarWidth, barHeight), barHeight / 2, barHeight / 2)
512 draw.addPath(barPath)
513 draw.setFillColor(new Color("ffffff", 0.5))
514 draw.fillPath()
515
516 // Make the path for the current temp indicator.
517 let currPath = new Path()
518 currPath.addEllipse(new Rect(currPosition, 0, tempBarHeight, tempBarHeight))
519 draw.addPath(currPath)
520 draw.setFillColor(new Color("ffffff", 1))
521 draw.fillPath()
522
523 return draw.getImage()
524}
525
526/*
527 * ELEMENTS AND ALIGNMENT
528 * ======================
529 */
530
531// Create an aligned stack to add content to.
532function align(column) {
533
534 // Add the containing stack to the column.
535 let alignmentStack = column.addStack()
536 alignmentStack.layoutHorizontally()
537
538 // Get the correct stack from the alignment function.
539 let returnStack = currentAlignment(alignmentStack)
540 returnStack.layoutVertically()
541 return returnStack
542}
543
544// Create a right-aligned stack.
545function alignRight(alignmentStack) {
546 alignmentStack.addSpacer()
547 let returnStack = alignmentStack.addStack()
548 return returnStack
549}
550
551// Create a left-aligned stack.
552function alignLeft(alignmentStack) {
553 let returnStack = alignmentStack.addStack()
554 alignmentStack.addSpacer()
555 return returnStack
556}
557
558// Create a center-aligned stack.
559function alignCenter(alignmentStack) {
560 alignmentStack.addSpacer()
561 let returnStack = alignmentStack.addStack()
562 alignmentStack.addSpacer()
563 return returnStack
564}
565
566// Display the date on the widget.
567function date(column) {
568
569 // Set up the date formatter.
570 let df = new DateFormatter()
571
572 // Show small if it's hard coded, or if it's dynamic and events are visible.
573 if ((dynamicDateSize && eventsAreVisible) || staticDateSize == "small") {
574 let dateStack = align(column)
575 dateStack.setPadding(10, 10, 10, 10)
576
577 df.dateFormat = smallDateFormat
578 let dateText = dateStack.addText(df.string(currentDate))
579 formatText(dateText, textFormat.smallDate)
580
581 // Otherwise, show the large date.
582 } else {
583 let dateOneStack = align(column)
584 df.dateFormat = largeDateLineOne
585 let dateOne = dateOneStack.addText(df.string(currentDate))
586 formatText(dateOne, textFormat.largeDate1)
587 dateOneStack.setPadding(10, 10, 0, 10)
588
589 let dateTwoStack = align(column)
590 df.dateFormat = largeDateLineTwo
591 let dateTwo = dateTwoStack.addText(df.string(currentDate))
592 formatText(dateTwo, textFormat.largeDate2)
593 dateTwoStack.setPadding(0, 10, 10, 10)
594 }
595}
596
597function greeting(column) {
598
599 // This function makes a greeting based on the time of day.
600 function makeGreeting() {
601 const hour = currentDate.getHours()
602 if (hour < 5) { return "Good night." }
603 if (hour < 12) { return "Good morning." }
604 if (hour-12 < 5) { return "Good afternoon." }
605 if (hour-12 < 10) { return "Good evening." }
606 return "Good night."
607 }
608
609 // Set up the greeting.
610 let greetingStack = align(column)
611 let greeting = greetingStack.addText(makeGreeting())
612 formatText(greeting, textFormat.greeting)
613 greetingStack.setPadding(10, 10, 10, 10)
614}
615
616// Display events on the widget.
617function events(column) {
618
619 // If no events should be displayed, just exit this function
620 if (numberOfEvents == 0) { return }
621
622 for (let i = 0; i < numberOfEvents; i++) {
623 // Determine if the event exists, otherwise end.
624 const event = futureEvents[i]
625 if (!event) { break }
626
627 const titleStack = align(column)
628 const title = titleStack.addText(event.title)
629 formatText(title, textFormat.eventTitle)
630 titleStack.setPadding(i==0 ? 10 : 5, 10, 0, 10)
631
632 // If there are too many events, limit the line height.
633 if (futureEvents.length >= 3) { title.lineLimit = 1 }
634
635 // If it's an all-day event, we don't need a time.
636 if (event.isAllDay) { return }
637
638 // Format the time information.
639 let df = new DateFormatter()
640 df.useNoDateStyle()
641 df.useShortTimeStyle()
642
643 const timeText = df.string(event.startDate)
644 const timeStack = align(column)
645 const time = timeStack.addText(timeText)
646 formatText(time, textFormat.eventTime)
647 timeStack.setPadding(0, 10, i==numberOfEvents-1 ? 10 : 0, 10)
648 }
649}
650
651// Display the current weather.
652function current(column) {
653
654 // Show the current condition symbol.
655 let mainConditionStack = align(column)
656 let mainCondition = mainConditionStack.addImage(provideSymbol(currentCondition,isNight(currentDate)))
657 mainCondition.imageSize = new Size(22,22)
658 mainConditionStack.setPadding(10, 10, 0, 10)
659
660 // Show the current temperature.
661 let tempStack = align(column)
662 let temp = tempStack.addText(Math.round(currentTemp) + "°")
663 tempStack.setPadding(0, 10, 0, 10)
664 formatText(temp, textFormat.largeTemp)
665
666 // If we're not showing the high and low, end it here.
667 if (!showHighLow) { return }
668
669 // Show the temp bar and high/low values.
670 let tempBarStack = align(column)
671 tempBarStack.layoutVertically()
672 tempBarStack.setPadding(0, 10, 5, 10)
673
674 let tempBar = drawTempBar()
675 let tempBarImage = tempBarStack.addImage(tempBar)
676 tempBarImage.size = new Size(50,0)
677
678 tempBarStack.addSpacer(1)
679
680 let highLowStack = tempBarStack.addStack()
681 highLowStack.layoutHorizontally()
682
683 let mainLow = highLowStack.addText(Math.round(todayLow).toString())
684 highLowStack.addSpacer()
685 let mainHigh = highLowStack.addText(Math.round(todayHigh).toString())
686
687 formatText(mainHigh, textFormat.tinyTemp)
688 formatText(mainLow, textFormat.tinyTemp)
689
690 tempBarStack.size = new Size(70,30)
691}
692
693// Display upcoming weather.
694function future(column) {
695
696 // Determine if we should show the next hour.
697 const showNextHour = (currentDate.getHours() < tomorrowShownAtHour)
698
699 // Set the label value.
700 const subLabelText = showNextHour ? "Next hour" : "Tomorrow"
701 let subLabelStack = align(column)
702 let subLabel = subLabelStack.addText(subLabelText)
703 formatText(subLabel, textFormat.smallTemp)
704 subLabelStack.setPadding(0, 10, 2, 10)
705
706 // Set up the sub condition stack.
707 let subConditionStack = align(column)
708 subConditionStack.layoutHorizontally()
709 subConditionStack.centerAlignContent()
710 subConditionStack.setPadding(0, 10, 10, 10)
711
712 // Determine what condition to show.
713 var nightCondition
714 if (showNextHour) {
715 const addHour = currentDate.getTime() + (60*60*1000)
716 const newDate = new Date(addHour)
717 nightCondition = isNight(newDate)
718 } else {
719 nightCondition = false
720 }
721
722 let subCondition = subConditionStack.addImage(provideSymbol(showNextHour ? nextHourCondition : tomorrowCondition,nightCondition))
723 const subConditionSize = showNextHour ? 14 : 18
724 subCondition.imageSize = new Size(subConditionSize, subConditionSize)
725 subConditionStack.addSpacer(5)
726
727 // The next part of the display changes significantly for next hour vs tomorrow.
728 if (showNextHour) {
729 let subTemp = subConditionStack.addText(Math.round(nextHourTemp) + "°")
730 formatText(subTemp, textFormat.smallTemp)
731
732 } else {
733 let tomorrowLine = subConditionStack.addImage(drawVerticalLine())
734 tomorrowLine.imageSize = new Size(3,28)
735 subConditionStack.addSpacer(5)
736 let tomorrowStack = subConditionStack.addStack()
737 tomorrowStack.layoutVertically()
738
739 let tomorrowHighText = tomorrowStack.addText(Math.round(tomorrowHigh) + "")
740 tomorrowStack.addSpacer(4)
741 let tomorrowLowText = tomorrowStack.addText(Math.round(tomorrowLow) + "")
742
743 formatText(tomorrowHighText, textFormat.tinyTemp)
744 formatText(tomorrowLowText, textFormat.tinyTemp)
745 }
746}
747
748// Return a text-creation function.
749function text(inputText) {
750
751 function displayText(column) {
752 let textStack = align(column)
753 textStack.setPadding(10, 10, 10, 10)
754
755 let textDisplay = textStack.addText(inputText)
756 formatText(textDisplay, textFormat.customText)
757 }
758 return displayText
759}
760
761/*
762 * MINI FUNCTIONS
763 * ==============
764 */
765
766// This function adds a space.
767function space(column) { column.addSpacer() }
768
769// Change the current alignment to right.
770function right(x) { currentAlignment = alignRight }
771
772// Change the current alignment to left.
773function left(x) { currentAlignment = alignLeft }
774
775// Change the current alignment to center.
776function center(x) { currentAlignment = alignCenter }
777
778// This function doesn't need to do anything.
779function end(x) { return }
780
781Script.complete()