Skip to content

Commit 7b73905

Browse files
committed
add whimsy
1 parent a8c103e commit 7b73905

3 files changed

Lines changed: 124 additions & 33 deletions

File tree

apps/trolley_detector/trolley_detector.star

Lines changed: 124 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,131 @@ TROLLEY_IMAGE_2X = TROLLEY_IMAGE_2X_ASSET.readall()
1616
SEPTA_API = "https://www3.septa.org/api/TransitView/index.php?route=G1"
1717
TROLLEY_STOPS = {"21481": "Haverford Av & 63rd St", "12196": "Richmond St & Allegheny Av", "650": "Richmond St & Cumberland St", "20984": "Richmond St & Huntingdon St", "21071": "Girard Av & 26th St", "30292": "Girard Av & 34th St - MBFS", "21035": "Girard Av & 54th St", "21100": "Girard Av & Columbia Av", "21081": "Girard Av & Ridge Av", "21005": "Girard Av & 16th St", "21021": "Girard Av & 31st St", "21061": "Girard Av & 42nd St", "21030": "Girard Av & 49th St", "21038": "Girard Av & 57th St", "20983": "Richmond St & Lehigh Av", "21033": "Girard Av & 52nd St", "21051": "Girard Av & 56th St", "21040": "Girard Av & 60th St", "20986": "Girard Av & Berks St", "21042": "Haverford Av & 62nd St", "649": "Richmond St & Cumberland St", "21086": "Girard Av & 12th St", "21070": "Girard Av & 27th St", "21018": "Girard Av & 28th St", "21022": "Girard Av & 33rd St", "20995": "Girard Av & 4th St", "21032": "Girard Av & 51st St", "21050": "Girard Av & 57th St", "21028": "Girard Av & Belmont Av", "24038": "Frankford Av & Richmond St", "21108": "Richmond St & Lehigh Av", "21016": "Girard Av & 26th St", "21069": "Girard Av & 28th St", "20993": "Girard Av & 2nd St", "21098": "Girard Av & Frankford Av- FS", "20978": "Girard Av & Front St", "30605": "Girard Av & Merion Av", "21105": "Girard Av & Richmond St", "31540": "Frankford Av & Girard Av - FS", "21110": "Richmond St & Cambria St", "21001": "Girard Av & 11th St", "21068": "Girard Av & 29th St", "30291": "Girard Av & 34th St - MBFS", "344": "Girard Av & 40th St - FS", "21101": "Girard Av & Palmer St", "20982": "Richmond St & Cambria St", "21113": "Richmond St & Clearfield St", "21075": "College Av & 24th St - FS", "21017": "Girard Av & 27th St", "21067": "Girard Av & 31st St", "21025": "Girard Av & 39th St", "21062": "Girard Av & 41st St", "21055": "Girard Av & 52nd St", "31443": "Girard Av & 62nd St", "21091": "Girard Av & 7th St", "21078": "Girard Av & Corinthian Av", "342": "Girard Av & Front St", "30550": "Girard Av & Merion Av", "21008": "Girard Av & Ridge Av", "21073": "Poplar St & Stillman St", "31347": "Frankford Av & Delaware Av Loop", "21087": "Girard Av & 11th St", "21006": "Girard Av & 17th St", "30290": "Girard Av & 19th St", "21009": "Girard Av & 20th St", "21063": "Girard Av & 39th St", "21044": "Girard Av & 63rd St - MBFS", "21072": "26th St & Poplar St", "30791": "Girard Av & 24th St", "349": "Girard Av & 59th St", "20996": "Girard Av & 5th St", "21093": "Girard Av & 5th St", "20998": "Girard Av & 7th St", "20999": "Girard Av & 8th St", "20979": "Richmond St & Clearfield St", "21114": "Richmond St & Allegheny Av", "21079": "Girard Av & 20th St", "21026": "Girard Av & 41st St", "21037": "Girard Av & 56th St", "345": "Girard Av & 59th St", "21090": "Girard Av & 8th St", "343": "Girard Av & Broad St", "352": "Girard Av & Broad St", "20989": "Girard Av & Columbia Av", "21014": "Poplar St & 25th St", "481": "Frankford Av & Girard Av", "21111": "Richmond St & Ann St", "21107": "Richmond St & Huntingdon St", "21082": "Girard Av & 17th St", "21080": "Girard Av & 19th St", "21053": "Girard Av & 54th St", "20981": "Richmond St & Ann St", "25779": "Richmond St & Girard Av", "21019": "Girard Av & 29th St", "21095": "Girard Av & 3rd St", "21048": "Girard Av & 60th St", "21103": "Girard Av & Berks St", "21058": "Girard Av & Lancaster Av", "12218": "Richmond St & Somerset St", "21041": "Haverford Av & 61st St", "21083": "Girard Av & 16th St", "21010": "Girard Av & Corinthian Av", "20988": "Girard Av & Palmer St", "341": "Richmond St & Westmoreland St Loop", "21002": "Girard Av & 12th St", "21096": "Girard Av & 2nd St", "20994": "Girard Av & 3rd St", "350": "Girard Av & 40th St", "23992": "Frankford Av & Richmond St", "21109": "Richmond St & Somerset St", "21027": "Girard Av & 42nd St", "21056": "Girard Av & 51st St", "21047": "Girard Av & 61st St", "21060": "Girard Av & Belmont Av - FS"}
1818

19+
def get_trolley_frames(id, stop_name, direction, scale, img, id_font, text_font):
20+
frames = []
21+
22+
# 70 frames total per trolley (7 seconds at 100ms delay)
23+
for i in range(70):
24+
# Resting position x=1, w=39 ends at 40. ID starts at 40.
25+
x, w, h = 1, 39, 14
26+
y_pos = 0
27+
28+
# Continuous Entrance Sequence
29+
if i < 30:
30+
# Phase 1: Slow slide from off-screen left (-40 to 1)
31+
x = int(-40.0 + (41.0 * i / 30.0))
32+
elif i == 30:
33+
# Phase 2: Overshoot peak and Horizontal Squish (Overlap ID by 2 pixels)
34+
# x + w = 42. ID starts at 40.
35+
x = -1
36+
w = 43
37+
elif i == 31:
38+
# Phase 3: Recoil
39+
x = -1
40+
w = 39
41+
elif i == 32:
42+
# Phase 4: Settle
43+
x = 1
44+
w = 39
45+
else:
46+
# Phase 5: Rest
47+
x = 1
48+
49+
string = direction + " at " + stop_name.replace("& ", "&\n")
50+
51+
# Root-level Stack ensures no "peel reveal" and correct off-screen positioning
52+
frame = render.Stack(
53+
children = [
54+
# Vehicle ID - Stationary at x=40
55+
render.Padding(
56+
pad = (40 * scale, 1 * scale, 0, 0),
57+
child = render.Text(content = id, font = id_font),
58+
),
59+
# The Trolley - Animated slide, overshoot, and squish
60+
render.Padding(
61+
pad = (x * scale, y_pos * scale, 0, 0),
62+
child = render.Image(src = img, width = w * scale, height = h * scale),
63+
),
64+
# Destination text - placed at y=14 to ensure clearance for 3 lines
65+
render.Padding(
66+
pad = (0, 14 * scale, 0, 0),
67+
child = render.Box(
68+
width = 64 * scale,
69+
height = 18 * scale,
70+
child = render.WrappedText(string, font = text_font, align = "center", width = 64 * scale),
71+
),
72+
),
73+
],
74+
)
75+
frames.append(frame)
76+
return frames
77+
78+
def get_empty_frames(scale, img, text_font):
79+
frames = []
80+
81+
# Centering 39px image in 64px width: (64-39)/2 = 12.5 -> 12
82+
resting_x = 12
83+
84+
# 200 frames total (20 seconds at 100ms delay)
85+
for i in range(200):
86+
x = resting_x
87+
88+
if i < 40: # Phase 1: Slow roll in from off-screen left (-40 to 12)
89+
x = int(-40.0 + (resting_x + 40.0) * i / 40.0)
90+
elif i < 60: # Phase 2: Pause centered
91+
x = resting_x
92+
elif i < 100: # Phase 3: Gentle Whimsical Shimmy (1-pixel shifts)
93+
# 0,0,0,1,1,1,0,0,0,-1,-1,-1...
94+
shimmy = [0, 0, 0, 1, 1, 1, 0, 0, 0, -1, -1, -1] * 4
95+
x = resting_x + shimmy[i - 60]
96+
elif i < 120: # Phase 4: Pause centered
97+
x = resting_x
98+
elif i < 160: # Phase 5: Slow roll off to right edge (12 to 64)
99+
x = int(resting_x + (64.0 - resting_x) * (i - 120) / 40.0)
100+
elif i < 180: # Phase 6: Dead air pause before repeat
101+
x = 64
102+
else: # Extra buffer
103+
x = 64
104+
105+
frame = render.Stack(
106+
children = [
107+
# Trolley rolling across the screen at y=0
108+
render.Padding(
109+
pad = (x * scale, 0, 0, 0),
110+
child = render.Image(src = img, width = 39 * scale, height = 14 * scale),
111+
),
112+
# Text centered at bottom (starts at y=14)
113+
render.Padding(
114+
pad = (0, 14 * scale, 0, 0),
115+
child = render.Box(
116+
width = 64 * scale,
117+
height = 18 * scale,
118+
child = render.WrappedText("No trolleys spotted.\nCheck later!", font = text_font, align = "center", width = 64 * scale),
119+
),
120+
),
121+
],
122+
)
123+
frames.append(frame)
124+
return frames
125+
19126
def get_route_15(scale):
20127
"""Get trolley information for Route 15 (G1) using next_stop_id
21128
22129
Returns:
23-
List of trolley render objects
130+
List of trolley render objects (frames)
24131
"""
25132
trolley_ids = ["2320", "2321", "2322", "2323", "2324", "2325", "2326", "2327", "2328", "2329", "2330", "2331", "2332", "2333", "2334", "2335", "2336", "2337"]
26-
trolleys_found = []
133+
trolley_frames = []
27134

28135
img = TROLLEY_IMAGE_2X if scale == 2 else TROLLEY_IMAGE
29136

30137
r = http.get(SEPTA_API, ttl_seconds = 300)
31138
if r == None or r.status_code != 200:
32-
return trolleys_found
139+
return trolley_frames
33140

34141
result = r.json()
35142
if result == None or result.get("bus") == None:
36-
return trolleys_found
143+
return trolley_frames
37144

38145
for i in result.get("bus"):
39146
id = i.get("VehicleID")
@@ -52,47 +159,31 @@ def get_route_15(scale):
52159
direction = "Eastbound"
53160
else:
54161
direction = "Now"
55-
string = direction
56-
string += " at " + stop_name.replace("& ", "&\n")
57162

58163
id_font = "6x13" if scale == 1 else "terminus-18"
59164
text_font = "tom-thumb" if scale == 1 else "terminus-12"
60165

61-
output = render.Column(
62-
children = [
63-
render.Row(
64-
children = [
65-
render.Box(width = 39 * scale, height = 14 * scale, child = render.Image(src = img)),
66-
render.Padding(child = render.WrappedText(id, font = id_font), pad = (1 * scale, 1 * scale, 0, 0)),
67-
],
68-
),
69-
render.Padding(child = render.WrappedText(string, font = text_font, align = "center", width = 64 * scale), pad = (0, 1 * scale, 0, 0)),
70-
],
71-
)
72-
trolleys_found.append(output)
73-
return trolleys_found
166+
frames = get_trolley_frames(id, stop_name, direction, scale, img, id_font, text_font)
167+
trolley_frames.extend(frames)
168+
169+
return trolley_frames
74170

75171
def main():
76172
scale = 2 if canvas.is2x() else 1
77173
img = TROLLEY_IMAGE_2X if scale == 2 else TROLLEY_IMAGE
78-
trolleys = get_route_15(scale)
174+
trolley_frames = get_route_15(scale)
175+
176+
text_font = "tom-thumb" if scale == 1 else "terminus-12"
79177

80-
if len(trolleys) == 0:
178+
if len(trolley_frames) == 0:
179+
empty_frames = get_empty_frames(scale, img, text_font)
81180
return render.Root(
82-
child = render.Column(
83-
children = [
84-
render.Row(
85-
children = [
86-
render.Box(width = 64 * scale, height = 14 * scale, child = render.Image(src = img)),
87-
],
88-
),
89-
render.Padding(child = render.WrappedText("No trolleys spotted.\nCheck later!", font = "tom-thumb" if scale == 1 else "terminus-12", align = "center", width = 64 * scale), pad = (0, 1 * scale, 0, 0)),
90-
],
91-
),
181+
child = render.Animation(children = empty_frames),
182+
show_full_animation = True,
92183
)
93184
else:
94185
return render.Root(
95-
child = render.Animation(children = trolleys),
96-
delay = 5000,
186+
child = render.Animation(children = trolley_frames),
187+
delay = 100,
97188
show_full_animation = True,
98189
)
15.4 KB
Loading
28.7 KB
Loading

0 commit comments

Comments
 (0)