77from openpilot .common .swaglog import cloudlog
88from openpilot .selfdrive .ui .ui_state import ui_state
99from openpilot .system .athena .registration import UNREGISTERED_DONGLE_ID
10- from openpilot .system .ui .lib .application import gui_app , FontWeight
10+ from openpilot .system .ui .lib .application import gui_app , FontWeight , FONT_SCALE
11+ from openpilot .system .ui .lib .text_measure import measure_text_cached
1112from openpilot .system .ui .lib .scroll_panel import GuiScrollPanel
1213from openpilot .system .ui .lib .wrap_text import wrap_text
1314from openpilot .system .ui .widgets import Widget
2122)
2223INSTRUCTIONS = (
2324 "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n \n "
24- + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n \n "
25+ + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n \n \n "
2526 + "Frequently Asked Questions\n \n "
2627 + "Does it matter how or where I drive? Nope, just drive as you normally would.\n \n "
2728 + "Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.\n \n "
@@ -43,12 +44,16 @@ def __init__(self):
4344 self .params = Params ()
4445 self .segment_count = self ._get_segment_count ()
4546 self .scroll_panel = GuiScrollPanel ()
47+ self ._content_height = 0
4648
4749 self .running = True
4850 self .update_thread = threading .Thread (target = self ._update_loop , daemon = True )
4951 self .update_thread .start ()
5052 self .last_update_time = 0
5153
54+ def show_event (self ):
55+ self .scroll_panel .set_offset (0 )
56+
5257 def _get_segment_count (self ) -> int :
5358 stats = self .params .get (self .PARAM_KEY )
5459 if not stats :
@@ -66,88 +71,61 @@ def __del__(self):
6671
6772 def _render (self , rect : rl .Rectangle ):
6873 # Calculate content dimensions
69- content_width = rect .width - 80
70- content_height = self ._calculate_content_height (int (content_width ))
71- content_rect = rl .Rectangle (rect .x , rect .y , rect .width , content_height )
74+ content_rect = rl .Rectangle (rect .x , rect .y , rect .width , self ._content_height )
7275
7376 # Handle scrolling and render with clipping
7477 scroll_offset = self .scroll_panel .update (rect , content_rect )
7578 rl .begin_scissor_mode (int (rect .x ), int (rect .y ), int (rect .width ), int (rect .height ))
76- self ._render_content (rect , scroll_offset )
79+ self ._content_height = self . _render_content (rect , scroll_offset )
7780 rl .end_scissor_mode ()
7881
79- def _calculate_content_height (self , content_width : int ) -> int :
80- height = 80 # Top margin
81-
82- # Title
83- height += 100 + 40
84-
85- # Description
86- desc_font = gui_app .font (FontWeight .NORMAL )
87- desc_lines = wrap_text (desc_font , DESCRIPTION , 45 , content_width )
88- height += len (desc_lines ) * 45 + 40
89-
90- # Status section
91- height += 32 # Separator
92- status_text , _ = self ._get_status ()
93- status_lines = wrap_text (gui_app .font (FontWeight .BOLD ), status_text , 60 , content_width )
94- height += len (status_lines ) * 60 + 20
95-
96- # Contribution count (if available)
97- if self .segment_count > 0 :
98- contrib_text = f"{ self .segment_count } segment(s) of your driving is in the training dataset so far."
99- contrib_lines = wrap_text (gui_app .font (FontWeight .BOLD ), contrib_text , 52 , content_width )
100- height += len (contrib_lines ) * 52 + 20
101-
102- # Instructions section
103- height += 32 # Separator
104- inst_lines = wrap_text (gui_app .font (FontWeight .NORMAL ), INSTRUCTIONS , 40 , content_width )
105- height += len (inst_lines ) * 40 + 40 # Bottom margin
106-
107- return height
108-
109- def _render_content (self , rect : rl .Rectangle , scroll_offset : float ):
82+ def _render_content (self , rect : rl .Rectangle , scroll_offset : float ) -> int :
11083 x = int (rect .x + 40 )
11184 y = int (rect .y + 40 + scroll_offset )
11285 w = int (rect .width - 80 )
11386
114- # Title
87+ # Title (centered)
11588 title_font = gui_app .font (FontWeight .MEDIUM )
116- rl .draw_text_ex (title_font , TITLE , rl .Vector2 (x , y ), 100 , 0 , rl .WHITE )
117- y += 140
89+ text_width = measure_text_cached (title_font , TITLE , 100 ).x
90+ title_x = rect .x + (rect .width - text_width ) / 2
91+ rl .draw_text_ex (title_font , TITLE , rl .Vector2 (title_x , y ), 100 , 0 , rl .WHITE )
92+ y += 200
11893
11994 # Description
12095 y = self ._draw_wrapped_text (x , y , w , DESCRIPTION , gui_app .font (FontWeight .NORMAL ), 45 , rl .WHITE )
121- y += 40
96+ y += 40 + 20
12297
12398 # Separator
12499 rl .draw_rectangle (x , y , w , 2 , self .GRAY )
125- y += 30
100+ y += 30 + 20
126101
127102 # Status
128103 status_text , status_color = self ._get_status ()
129104 y = self ._draw_wrapped_text (x , y , w , status_text , gui_app .font (FontWeight .BOLD ), 60 , status_color )
130- y += 20
105+ y += 20 + 20
131106
132107 # Contribution count (if available)
133108 if self .segment_count > 0 :
134109 contrib_text = f"{ self .segment_count } segment(s) of your driving is in the training dataset so far."
135110 y = self ._draw_wrapped_text (x , y , w , contrib_text , gui_app .font (FontWeight .BOLD ), 52 , rl .WHITE )
136- y += 20
111+ y += 20 + 20
137112
138113 # Separator
139114 rl .draw_rectangle (x , y , w , 2 , self .GRAY )
140- y += 30
115+ y += 30 + 20
141116
142117 # Instructions
143- self ._draw_wrapped_text (x , y , w , INSTRUCTIONS , gui_app .font (FontWeight .NORMAL ), 40 , self .LIGHT_GRAY )
118+ y = self ._draw_wrapped_text (x , y , w , INSTRUCTIONS , gui_app .font (FontWeight .NORMAL ), 40 , self .LIGHT_GRAY )
119+
120+ # bottom margin + remove effect of scroll offset
121+ return int (round (y - self .scroll_panel .offset + 40 ))
144122
145- def _draw_wrapped_text (self , x , y , width , text , font , size , color ):
146- wrapped = wrap_text (font , text , size , width )
123+ def _draw_wrapped_text (self , x , y , width , text , font , font_size , color ):
124+ wrapped = wrap_text (font , text , font_size , width )
147125 for line in wrapped :
148- rl .draw_text_ex (font , line , rl .Vector2 (x , y ), size , 0 , color )
149- y += size
150- return y
126+ rl .draw_text_ex (font , line , rl .Vector2 (x , y ), font_size , 0 , color )
127+ y += font_size * FONT_SCALE
128+ return round ( y )
151129
152130 def _get_status (self ) -> tuple [str , rl .Color ]:
153131 network_type = ui_state .sm ["deviceState" ].networkType
0 commit comments