From fa8c0501b47fb52ad9877625dc916aa7f58de893 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sun, 20 Sep 2026 11:38:06 -0700 Subject: [PATCH] Add isolated fullscreen Mac showcase with native aspect ratio --- roadscore/prototype/mac_showcase.py | 2 + roadscore/prototype/normal_ui_audit.py | 5 ++ roadscore/prototype/showcase_fullscreen.py | 80 +++++++++++++++++++ .../prototype/test_showcase_fullscreen.py | 54 +++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 roadscore/prototype/showcase_fullscreen.py create mode 100644 roadscore/prototype/test_showcase_fullscreen.py diff --git a/roadscore/prototype/mac_showcase.py b/roadscore/prototype/mac_showcase.py index db5d7c02c4..b4f46afc23 100644 --- a/roadscore/prototype/mac_showcase.py +++ b/roadscore/prototype/mac_showcase.py @@ -387,6 +387,7 @@ def parser(): p.add_argument('--presentation-root',type=Path,help=argparse.SUPPRESS) p.add_argument('--output-identity',help=argparse.SUPPRESS) p.add_argument('--hold-start',action='store_true',help=argparse.SUPPRESS) + p.add_argument('--fullscreen',action='store_true',help='Show the native Mac UI fullscreen; F/F11 toggles, Escape returns to its window') p.add_argument('--follow-playhead',action='store_true',help=argparse.SUPPRESS) def paired_url(value): try:GalaxyPeer(value,allow_lan_http=True) @@ -440,6 +441,7 @@ def main(): env=os.environ.copy() env.update(PYTHONDONTWRITEBYTECODE='1',ZMQ='1',OPENPILOT_ZMQ_NAMESPACE='roadscore-showcase-'+session,ROADSCORE_SHOWCASE_SESSION=session,ROADSCORE_PREPARED_SHOWCASE='1',ROADSCORE_REPLAY_UI_CONTROLS='1',PARAMS_ROOT=str(out/'params'),BASEDIR=str(rt),NOBOARD='1',SIMULATION='1',SKIP_FW_QUERY='1',BIG='0',SP_ALLOW_DESKTOP_FAKE_WIFI='0',SP_ALLOW_DESKTOP_FAKE_BLUETOOTH='0',SP_ONROAD_NAV_DEMO='0',SP_ONROAD_CEM_DEMO='0',ROADSCORE_CLEAN_DEMO_UI='1',ROADSCORE_OVERLAY='1',ROADSCORE_STATUS_FILE=str(out/'status.json'),ROADSCORE_UI_AUDIT=str(out/'ui_audit.jsonl'),ROADSCORE_OVERLAY_CAPTURE=str(out/'overlay.png'),ROADSCORE_PRESENTATION_POLICY='conservative-v4') env['OPENPILOT_PREFIX']='roadscore-showcase-'+session + env['ROADSCORE_FULLSCREEN']='1' if a.fullscreen and not a.headless else '0' env['ROADSCORE_AUDIO_DRAIN_FILE']=str(out/'audio_drained.json') env['PWD']=str(rt) env['PYTHONPATH']=':'.join(map(str,[HERE,rt,rt/'starpilot/third_party',*rt.glob('*_repo'),project/'roadscore/.analysis-venv/lib/python3.12/site-packages'])) diff --git a/roadscore/prototype/normal_ui_audit.py b/roadscore/prototype/normal_ui_audit.py index 5e90d14bbd..6715d3b2bb 100644 --- a/roadscore/prototype/normal_ui_audit.py +++ b/roadscore/prototype/normal_ui_audit.py @@ -77,4 +77,9 @@ def update(self,*args,**kw): UIState.update=update;CameraView._accept_frame=accept;CameraView._render_textures=textures;ModelRenderer._draw_path=path;ModelRenderer._draw_lane_lines=lanes from overlay import install install() +if os.environ.get('ROADSCORE_FULLSCREEN')=='1': + from showcase_fullscreen import install as install_fullscreen + from openpilot.system.ui.lib.application import gui_app + import pyray as rl + install_fullscreen(rl,gui_app) runpy.run_path(str(Path(os.environ['BASEDIR'])/'selfdrive/ui/ui.py'),run_name='__main__') diff --git a/roadscore/prototype/showcase_fullscreen.py b/roadscore/prototype/showcase_fullscreen.py new file mode 100644 index 0000000000..e60081d69a --- /dev/null +++ b/roadscore/prototype/showcase_fullscreen.py @@ -0,0 +1,80 @@ +"""Opt-in desktop presentation of the unchanged native canvas, with letterboxing.""" +import os +import sys + + +def viewport(width, height, canvas_width, canvas_height): + scale=min(width/canvas_width,height/canvas_height) + return ((width-canvas_width*scale)/2,(height-canvas_height*scale)/2, + canvas_width*scale,canvas_height*scale) + + +class FullscreenCanvas: + def __init__(self, rl, app): + self.rl,self.app=rl,app + self.base_scale=app._scale + self.rect=(0.,0.,float(app._scaled_width),float(app._scaled_height)) + self.size=None + + def toggle(self): + self.rl.toggle_borderless_windowed() + + def update(self): + rl,app=self.rl,self.app + if rl.is_key_pressed(rl.KeyboardKey.KEY_F11) or rl.is_key_pressed(rl.KeyboardKey.KEY_F):self.toggle() + elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE) and rl.is_window_state(rl.ConfigFlags.FLAG_BORDERLESS_WINDOWED_MODE):self.toggle() + width,height=max(1,rl.get_screen_width()),max(1,rl.get_screen_height()) + size=(width,height,max(1,rl.get_render_width()),max(1,rl.get_render_height())) + if size==self.size:return + self.size=size + self.rect=viewport(width,height,app.width,app.height) + pixel_width=max(1,round(self.rect[2]*size[2]/width)) + pixel_height=max(1,round(self.rect[3]*size[3]/height)) + if app._render_texture is not None:rl.unload_render_texture(app._render_texture) + app._render_texture=rl.load_render_texture(pixel_width,pixel_height) + rl.set_texture_filter(app._render_texture.texture,rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + app._render_texture_width,app._render_texture_height=pixel_width,pixel_height + app._pixel_scale_x=pixel_width/(app.width*app._scale) + app._pixel_scale_y=pixel_height/(app.height*app._scale) + app._patch_scissor_mode() + + def map_event(self, event): + x,y,width,height=self.rect + position=event.pos + mapped=type(position)((position.x*self.base_scale-x)*self.app.width/width, + (position.y*self.base_scale-y)*self.app.height/height) + return event._replace(pos=mapped) + + +def install(rl, app, *, environ=None, platform=None): + env=os.environ if environ is None else environ + if ((sys.platform if platform is None else platform)!='darwin' + or env.get('ROADSCORE_FULLSCREEN')!='1' or env.get('ROADSCORE_PREPARED_SHOWCASE')!='1' + or env.get('SIMULATION')!='1' or env.get('BIG','0')!='0'): + return False + original_init=app.init_window + original_progress=app._mark_progress + original_draw=rl.draw_texture_pro + original_events=app._mouse.get_events + canvas=FullscreenCanvas(rl,app) + def init_window(*args,**kwargs): + result=original_init(*args,**kwargs) + rl.set_exit_key(rl.KeyboardKey.KEY_NULL) + canvas.toggle() + canvas.update() + return result + def progress(label): + # Before mouse collection and render-target binding, never during drawing. + if label=='gui_app.loop_start':canvas.update() + return original_progress(label) + def draw(texture,source,destination,origin,rotation,tint): + if app._render_texture is not None and texture.id==app._render_texture.texture.id: + destination=rl.Rectangle(*canvas.rect) + return original_draw(texture,source,destination,origin,rotation,tint) + def events():return [canvas.map_event(event) for event in original_events()] + app.init_window=init_window + app._mark_progress=progress + app._mouse.get_events=events + rl.draw_texture_pro=draw + app._roadscore_fullscreen=canvas + return True diff --git a/roadscore/prototype/test_showcase_fullscreen.py b/roadscore/prototype/test_showcase_fullscreen.py new file mode 100644 index 0000000000..6ee5ad1ad0 --- /dev/null +++ b/roadscore/prototype/test_showcase_fullscreen.py @@ -0,0 +1,54 @@ +from collections import namedtuple +from types import SimpleNamespace +import unittest + +from mac_showcase import parser +from showcase_fullscreen import FullscreenCanvas, install, viewport + + +class FullscreenTests(unittest.TestCase): + def test_aspect_and_center_preserved_on_wide_and_tall_monitors(self): + for width,height in ((1440,900),(1920,1080),(2560,1080),(900,1440)): + x,y,w,h=viewport(width,height,536,240) + self.assertAlmostEqual(w/h,536/240) + self.assertAlmostEqual(x*2+w,width);self.assertAlmostEqual(y*2+h,height) + self.assertGreaterEqual(x,0);self.assertGreaterEqual(y,0) + self.assertLessEqual(w,width);self.assertLessEqual(h,height) + + def test_pointer_maps_canvas_corners_and_letterbox_stays_outside(self): + app=SimpleNamespace(_scale=1.,_scaled_width=536,_scaled_height=240,width=536,height=240) + canvas=FullscreenCanvas(None,app);canvas.rect=viewport(1440,900,536,240) + Pos=namedtuple('Pos','x y');Event=namedtuple('Event','pos') + x,y,w,h=canvas.rect + self.assertEqual(canvas.map_event(Event(Pos(x,y))).pos,Pos(0.,0.)) + corner=canvas.map_event(Event(Pos(x+w,y+h))).pos + self.assertAlmostEqual(corner.x,536.);self.assertAlmostEqual(corner.y,240.) + self.assertLess(canvas.map_event(Event(Pos(0,0))).pos.y,0) + + def test_flag_defaults_and_device_gates(self): + self.assertFalse(parser().parse_args([]).fullscreen) + self.assertTrue(parser().parse_args(['--fullscreen']).fullscreen) + env=dict(ROADSCORE_FULLSCREEN='1',ROADSCORE_PREPARED_SHOWCASE='1',SIMULATION='1',BIG='0') + self.assertFalse(install(None,None,environ=env,platform='linux')) + for patch in ({'ROADSCORE_FULLSCREEN':'0'},{'SIMULATION':'0'},{'BIG':'1'},{'ROADSCORE_PREPARED_SHOWCASE':'0'}): + self.assertFalse(install(None,None,environ=env|patch,platform='darwin')) + + def test_resize_reallocates_canvas_and_escape_toggles_without_close(self): + allocations=[];keys=set();toggles=[] + rl=SimpleNamespace(KeyboardKey=SimpleNamespace(KEY_F11=11,KEY_F=70,KEY_ESCAPE=27), + ConfigFlags=SimpleNamespace(FLAG_BORDERLESS_WINDOWED_MODE=1), + TextureFilter=SimpleNamespace(TEXTURE_FILTER_BILINEAR=1), + is_key_pressed=lambda key:key in keys,is_window_state=lambda flag:True, + toggle_borderless_windowed=lambda:toggles.append(True),get_screen_width=lambda:1440,get_screen_height=lambda:900, + get_render_width=lambda:2880,get_render_height=lambda:1800,unload_render_texture=lambda texture:None, + load_render_texture=lambda w,h:allocations.append((w,h)) or SimpleNamespace(texture=1),set_texture_filter=lambda *args:None) + app=SimpleNamespace(_scale=1.,_scaled_width=536,_scaled_height=240,width=536,height=240, + _render_texture=None,_patch_scissor_mode=lambda:None) + canvas=FullscreenCanvas(rl,app);canvas.update();canvas.update() + self.assertEqual(len(allocations),1) + self.assertEqual(allocations[0][0],2880) + self.assertAlmostEqual(app._pixel_scale_x,2880/536) + keys.add(27);canvas.update();self.assertEqual(len(toggles),1) + + +if __name__=='__main__':unittest.main()