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