@@ -62,8 +62,7 @@ use gpui::{
6262 AppContext as _, Bounds , Context , Entity , FocusHandle , FontWeight , Hsla , InteractiveElement ,
6363 IntoElement , MouseButton , MouseDownEvent , MouseMoveEvent , MouseUpEvent , ParentElement , Pixels ,
6464 Point , Render , RenderImage , SharedString , StatefulInteractiveElement as _, StyleRefinement ,
65- Styled , StyledImage as _, Subscription , WeakEntity , Window , div, point, prelude:: FluentBuilder ,
66- px, svg,
65+ Styled , Subscription , WeakEntity , Window , div, point, prelude:: FluentBuilder , px, svg,
6766} ;
6867
6968use crate :: {
@@ -134,8 +133,6 @@ pub fn default_preview_resolution() -> XY<u32> {
134133// Shell metrics (`routes/editor/Editor.tsx:77-82`)
135134// ---------------------------------------------------------------------------
136135
137- /// The timeline height used until the project's tracks are known, which is
138- /// what the auto height derives from.
139136const DEFAULT_TIMELINE_HEIGHT : f32 = 260. ;
140137/// The reference height the player/timeline split scales against on a short
141138/// viewport. It is not the floor -- see [`timeline::MIN_HUG_HEIGHT`].
@@ -574,31 +571,6 @@ pub fn frame_image(frame: &RenderedFrame) -> Option<Arc<RenderImage>> {
574571 ] ) ) )
575572}
576573
577- /// The poster: the recording's own first-frame JPEG, decoded to at most the
578- /// canvas's retina size. `thumbnail` (a box filter) over `resize` because the
579- /// picture is on screen for well under a second.
580- fn decode_poster ( path : & std:: path:: Path ) -> Option < Arc < RenderImage > > {
581- let bytes = std:: fs:: read ( path) . ok ( ) ?;
582- let image = image:: load_from_memory ( & bytes) . ok ( ) ?;
583- let ( width, height) = ( image. width ( ) . max ( 1 ) , image. height ( ) . max ( 1 ) ) ;
584- let scale = ( 1920. / width as f32 ) . min ( 1080. / height as f32 ) . min ( 1. ) ;
585- let mut scaled = if scale < 1. {
586- image:: imageops:: thumbnail (
587- & image. into_rgba8 ( ) ,
588- ( ( width as f32 * scale) as u32 ) . max ( 1 ) ,
589- ( ( height as f32 * scale) as u32 ) . max ( 1 ) ,
590- )
591- } else {
592- image. into_rgba8 ( )
593- } ;
594- for pixel in scaled. pixels_mut ( ) {
595- pixel. 0 . swap ( 0 , 2 ) ;
596- }
597- Some ( Arc :: new ( RenderImage :: new ( smallvec:: smallvec![
598- image:: Frame :: new( scaled)
599- ] ) ) )
600- }
601-
602574/// What the frame pump hands the window.
603575#[ derive( Clone ) ]
604576pub ( crate ) enum EditorPreviewFrame {
@@ -768,7 +740,7 @@ impl Render for EditorSectionView {
768740 // clips sidebar; the config sidebar is hidden, not destroyed
769741 // (`Editor.tsx:728-747`).
770742 EditorSection :: Sidebar => {
771- if !editor. project_ready ( ) {
743+ if !editor. visual_ready ( ) {
772744 return editor. render_preparing_sidebar ( !matches ! (
773745 editor. state,
774746 LoadState :: Failed ( _)
@@ -1405,13 +1377,6 @@ pub struct EditorWindow {
14051377 preparing_frame_presented : bool ,
14061378 preparing_seed : Option < Arc < crate :: editor_preparing:: presentation:: PreparingTimelineSeed > > ,
14071379 pub ( crate ) latest_frame : Option < EditorPreviewFrame > ,
1408- /// The bundle's `screenshots/display.jpg`, letterboxed into the canvas
1409- /// until the first composed frame lands -- decoded in parallel with
1410- /// `EditorInstance` construction, so the editor opens onto a picture
1411- /// rather than "Loading project...". The Solid app hides the same wait
1412- /// behind a skeleton; a poster is the native equivalent with the added
1413- /// courtesy of showing the recording itself.
1414- poster : Option < Arc < RenderImage > > ,
14151380 preview : Entity < PreviewFrameView > ,
14161381 header : Entity < EditorSectionView > ,
14171382 toolbar : Entity < EditorSectionView > ,
@@ -1616,6 +1581,7 @@ pub struct EditorWindow {
16161581 /// The user's drag on the resize grip. `None` means the card hugs its
16171582 /// track rows and re-hugs whenever a track is added or removed.
16181583 timeline_height_override : Option < f32 > ,
1584+ initial_timeline_rows : Option < usize > ,
16191585 timeline_resize : Option < ( f32 , f32 ) > ,
16201586 /// The header's Presets dropdown (`PresetsDropdown.tsx`), and whichever
16211587 /// of its three dialogs is up. The dialog closes the menu when it opens,
@@ -1734,27 +1700,6 @@ impl EditorWindow {
17341700 true
17351701 } ) ;
17361702
1737- // Decode the poster off-thread immediately: it races EditorInstance
1738- // construction and reliably wins, so the first paint has a picture.
1739- let poster_path = project_path. join ( "screenshots" ) . join ( "display.jpg" ) ;
1740- cx. spawn_in ( window, async move |this, cx| {
1741- let Some ( poster) = cx
1742- . background_executor ( )
1743- . spawn ( async move { decode_poster ( & poster_path) } )
1744- . await
1745- else {
1746- return ;
1747- } ;
1748- this. update ( cx, |this, cx| {
1749- if this. latest_frame . is_none ( ) {
1750- this. poster = Some ( poster) ;
1751- cx. notify ( ) ;
1752- }
1753- } )
1754- . ok ( ) ;
1755- } )
1756- . detach ( ) ;
1757-
17581703 let name_input = cx. new ( |cx| {
17591704 let mut input = ui:: TextInputState :: single_line ( window, cx) ;
17601705 input. set_disabled ( true , cx) ;
@@ -1951,11 +1896,11 @@ impl EditorWindow {
19511896 audio_picker : None ,
19521897 camera3d_setup : None ,
19531898 timeline_height_override : None ,
1899+ initial_timeline_rows : None ,
19541900 timeline_resize : None ,
19551901 presets_menu : None ,
19561902 preset_dialog : None ,
19571903 caption_sync_signature : None ,
1958- poster : None ,
19591904 export : None ,
19601905 clips : crate :: editor_clips:: ClipsState :: default ( ) ,
19611906 }
@@ -2412,6 +2357,14 @@ impl EditorWindow {
24122357 self . selection . as_ref ( )
24132358 }
24142359
2360+ fn visual_ready ( & self ) -> bool {
2361+ self . project_ready ( )
2362+ && self . latest_frame . is_some ( )
2363+ && self . preparing_consumer . is_none ( )
2364+ && self . ordinary_handoff_frame . is_none ( )
2365+ && self . preparing_candidate_frame . is_none ( )
2366+ }
2367+
24152368 pub ( crate ) fn project_ready ( & self ) -> bool {
24162369 self . instance . is_some ( ) && matches ! ( & self . state, LoadState :: Ready ( _) )
24172370 }
@@ -2665,6 +2618,7 @@ impl EditorWindow {
26652618 }
26662619
26672620 pub fn set_instance ( & mut self , instance : Arc < EditorInstance > ) {
2621+ self . initial_timeline_rows = Some ( self . timeline . rows . len ( ) ) ;
26682622 self . preparing_retry_pending = false ;
26692623 self . preparing_candidate_frame = None ;
26702624 self . ordinary_handoff_frame = self
@@ -3043,6 +2997,16 @@ impl EditorWindow {
30432997 window : & mut Window ,
30442998 cx : & mut Context < Self > ,
30452999 ) {
3000+ if !self . visual_ready ( ) {
3001+ let key = & event. keystroke ;
3002+ let close = ( key. key == "w" && ( key. modifiers . platform || key. modifiers . control ) )
3003+ || ( key. key == "f4" && key. modifiers . alt ) ;
3004+ if !close {
3005+ window. prevent_default ( ) ;
3006+ cx. stop_propagation ( ) ;
3007+ }
3008+ return ;
3009+ }
30463010 if !is_playback_shortcut (
30473011 & event. keystroke ,
30483012 ui:: text_input_has_focus ( window, cx) ,
@@ -3065,7 +3029,7 @@ impl EditorWindow {
30653029 /// (`useEditorShortcuts.ts:10`) and `e.repeat` is ignored there
30663030 /// (`:42`) as `is_held` is here.
30673031 fn on_key ( & mut self , event : & gpui:: KeyDownEvent , window : & mut Window , cx : & mut Context < Self > ) {
3068- if !self . project_ready ( ) {
3032+ if !self . visual_ready ( ) {
30693033 return ;
30703034 }
30713035 if self . frame_controls . is_open ( ) && event. keystroke . key == "escape" {
@@ -4920,17 +4884,12 @@ impl EditorWindow {
49204884 EditorVerticalLayout :: new ( viewport_height, value) . timeline_height
49214885 }
49224886
4923- /// The height the timeline card asks for: the user's dragged override if
4924- /// there is one, otherwise the height that hugs the visible track rows.
4925- /// Before the project loads there are no rows to hug, so the old fixed
4926- /// height stands in and the card does not pop when they arrive.
49274887 fn preferred_timeline_height ( & self ) -> f32 {
49284888 self . timeline_height_override . unwrap_or_else ( || {
4929- if self . timeline . rows . is_empty ( ) {
4930- DEFAULT_TIMELINE_HEIGHT
4931- } else {
4932- timeline:: hug_height ( self . timeline . rows . len ( ) )
4933- }
4889+ DEFAULT_TIMELINE_HEIGHT
4890+ + self . initial_timeline_rows . map_or ( 0. , |initial| {
4891+ timeline:: hug_height ( self . timeline . rows . len ( ) ) - timeline:: hug_height ( initial)
4892+ } )
49344893 } )
49354894 }
49364895
@@ -5845,8 +5804,6 @@ impl EditorWindow {
58455804 frame. layout . output_size [ 1 ] as f32 ,
58465805 ) ;
58475806 self . latest_frame = Some ( frame. frame . clone ( ) ) ;
5848- // The composed frame supersedes the poster; free its atlas memory.
5849- self . poster = None ;
58505807 let previous = self . preview . update ( cx, |preview, cx| {
58515808 preview. set_frame ( frame. frame , frame_size, self . stats . clone ( ) , cx)
58525809 } ) ;
@@ -8515,6 +8472,7 @@ impl EditorWindow {
85158472 ui:: EditorButton :: plain ( & theme, "open-recording-bundle" )
85168473 . left_icon ( "icons/folder.svg" )
85178474 . tooltip ( & theme, "Open recording bundle" )
8475+ . disabled ( !self . visual_ready ( ) )
85188476 . on_click ( cx. listener ( |this, _, _window, cx| {
85198477 this. open_recording_bundle ( cx) ;
85208478 } ) ) ,
@@ -8523,6 +8481,7 @@ impl EditorWindow {
85238481 ui:: EditorButton :: plain ( & theme, "delete-recording" )
85248482 . left_icon ( "icons/trash.svg" )
85258483 . tooltip ( & theme, "Delete recording" )
8484+ . disabled ( !self . visual_ready ( ) )
85268485 . on_click ( cx. listener ( |this, _, window, cx| {
85278486 this. delete_recording ( window, cx) ;
85288487 } ) ) ,
@@ -8564,7 +8523,16 @@ impl EditorWindow {
85648523 . when ( self . sharing . is_some ( ) , |group| {
85658524 group. child ( self . render_reupload_button ( cx) )
85668525 } )
8567- . child ( self . render_export_button ( cx) ) ,
8526+ . child ( self . render_export_button ( cx) )
8527+ . relative ( )
8528+ . children ( ( !self . visual_ready ( ) ) . then ( || {
8529+ div ( )
8530+ . absolute ( )
8531+ . inset_0 ( )
8532+ . occlude ( )
8533+ . on_mouse_down ( MouseButton :: Left , |_, _, cx| cx. stop_propagation ( ) )
8534+ . on_mouse_down ( MouseButton :: Right , |_, _, cx| cx. stop_propagation ( ) )
8535+ } ) ) ,
85688536 ) ;
85698537
85708538 #[ cfg( target_os = "windows" ) ]
@@ -8904,9 +8872,9 @@ impl EditorWindow {
89048872 self . preview
89058873 . clone ( )
89068874 . cached ( StyleRefinement :: default ( ) . size_full ( ) ) ,
8907- self . render_preview_placeholder ( false ) ,
8875+ loading :: preview ( & theme , false ) ,
89088876 ) ,
8909- ( _, false ) => self . render_preview_placeholder ( true ) ,
8877+ ( _, false ) => loading :: preview ( & theme , true ) ,
89108878 } ;
89118879
89128880 div ( )
@@ -8916,25 +8884,6 @@ impl EditorWindow {
89168884 . overflow_hidden ( )
89178885 . bg ( Hsla :: from ( theme. editor . card ) )
89188886 . child ( body)
8919- . children (
8920- ( !self . project_ready ( ) && self . latest_frame . is_some ( ) ) . then ( || {
8921- div ( )
8922- . absolute ( )
8923- . bottom ( px ( 12. ) )
8924- . left ( px ( 12. ) )
8925- . px ( px ( 10. ) )
8926- . py ( px ( 6. ) )
8927- . rounded ( px ( 6. ) )
8928- . bg ( Hsla :: from ( theme. editor . card ) )
8929- . text_size ( px ( 12. ) )
8930- . text_color ( Hsla :: from ( theme. editor . text_2 ) )
8931- . child ( if self . preparing_consumer . is_some ( ) {
8932- "Preparing recording…"
8933- } else {
8934- "Opening editor…"
8935- } )
8936- } ) ,
8937- )
89388887 // `CanvasElementsOverlay` + `SnapGuidesOverlay`
89398888 // (`Player.tsx:636-643`), both mounted inside the letterbox
89408889 // wrapper and only while a frame exists.
@@ -8945,26 +8894,6 @@ impl EditorWindow {
89458894 } )
89468895 }
89478896
8948- fn render_preview_placeholder ( & self , animated : bool ) -> gpui:: AnyElement {
8949- if let Some ( poster) = self . poster . clone ( ) {
8950- div ( )
8951- . absolute ( )
8952- . inset_0 ( )
8953- . flex ( )
8954- . items_center ( )
8955- . justify_center ( )
8956- . bg ( Hsla :: from ( self . theme . editor . card ) )
8957- . child (
8958- gpui:: img ( poster)
8959- . size_full ( )
8960- . object_fit ( gpui:: ObjectFit :: Contain ) ,
8961- )
8962- . into_any_element ( )
8963- } else {
8964- loading:: preview ( & self . theme , animated)
8965- }
8966- }
8967-
89688897 /// `EditorErrorScreen` -- what a bundle that will not open shows instead of
89698898 /// the canvas.
89708899 fn render_error_state ( & self , message : & str ) -> impl IntoElement {
@@ -10139,6 +10068,11 @@ fn is_playback_shortcut(
1013910068impl Render for EditorWindow {
1014010069 fn render ( & mut self , window : & mut Window , cx : & mut Context < Self > ) -> impl IntoElement {
1014110070 self . sync_appearance ( window, cx) ;
10071+ let title_disabled = !self . visual_ready ( ) ;
10072+ if self . name_input . read ( cx) . is_disabled ( ) != title_disabled {
10073+ self . name_input
10074+ . update ( cx, |input, cx| input. set_disabled ( title_disabled, cx) ) ;
10075+ }
1014210076 // Fields first: a field created this frame has no text yet, and gpui
1014310077 // only renders on invalidation, so syncing before creating would leave
1014410078 // a brand-new box empty until something else asked for a frame.
@@ -10573,6 +10507,20 @@ impl Render for EditorWindow {
1057310507 } ) )
1057410508 // The open `KSelect` menu, painted last of all so it is over the
1057510509 // sidebar and the drag layers alike.
10510+ . children (
10511+ ( !self . visual_ready ( ) && !matches ! ( self . state, LoadState :: Failed ( _) ) ) . then ( || {
10512+ div ( )
10513+ . absolute ( )
10514+ . top ( px ( HEADER_HEIGHT ) )
10515+ . bottom_0 ( )
10516+ . left_0 ( )
10517+ . right_0 ( )
10518+ . occlude ( )
10519+ . on_mouse_down ( MouseButton :: Left , |_, _, cx| cx. stop_propagation ( ) )
10520+ . on_mouse_down ( MouseButton :: Right , |_, _, cx| cx. stop_propagation ( ) )
10521+ . on_scroll_wheel ( |_, _, cx| cx. stop_propagation ( ) )
10522+ } ) ,
10523+ )
1057610524 . children ( self . with_style_controls ( |this| this. render_sidebar_menu ( cx) ) )
1057710525 . children ( self . render_toolbar_menu ( cx) )
1057810526 . children ( self . render_frame_controls ( window, cx) )
0 commit comments