@@ -116,10 +116,27 @@ public string TextureName
116116 }
117117
118118 public bool CheckerboardBackground { get ; set ; }
119+
120+ public bool UseBlankBackground
121+ {
122+ get => Settings . Default . UseBlankBackground ;
123+ set
124+ {
125+ Settings . Default . UseBlankBackground = value ;
126+ Settings . Default . Save ( ) ;
127+ }
128+ }
129+
119130 public ColorF EditorBackground
120131 {
121- get => new ColorF ( Settings . Default . BgColorR , Settings . Default . BgColorG ,
122- Settings . Default . BgColorB , 1f ) ;
132+ get
133+ {
134+ if ( UseBlankBackground )
135+ return new ColorF ( 0f , 0f , 0f , 0f ) ; // Fully transparent
136+
137+ return new ColorF ( Settings . Default . BgColorR , Settings . Default . BgColorG ,
138+ Settings . Default . BgColorB , 1f ) ;
139+ }
123140 set
124141 {
125142 Settings . Default . BgColorR = value . R ;
@@ -291,10 +308,25 @@ void MainMenu()
291308 ForMenuItem ( $ "{ LinkToPcsx2ActionName } ...", "CTRL+L" , MenuFileOpenPcsx2 ) ;
292309 ForMenuItem ( "Save" , "CTRL+S" , MenuFileSave , CurrentEditor != null ) ;
293310 ForMenuItem ( "Save as..." , MenuFileSaveAs , CurrentEditor != null ) ;
311+ ImGui . Separator ( ) ;
312+ ForMenuItem ( "Export Animation/Layout..." , MenuFileExportAnimation , CurrentEditor != null ) ;
313+ ForMenuItem ( "Export Texture(s)..." , MenuFileExportTexture , CurrentEditor != null ) ;
314+ ForMenuItem ( "Export Layout as Animated GIF..." , MenuFileExportLayoutAsGif , CurrentEditor is AppLayoutEditor ) ;
315+ ForMenuItem ( "Export Layout as PNG Sequence..." , MenuFileExportLayoutAsPngSequence , CurrentEditor is AppLayoutEditor ) ;
316+ ImGui . Separator ( ) ;
294317 ForMenuItem ( "Preview In-Game..." , MenuFilePreview , CurrentEditor != null ) ;
295318 ImGui . Separator ( ) ;
296319 ForMenu ( "Preferences" , ( ) =>
297320 {
321+ // Add the blank background toggle FIRST
322+ ForMenuCheck ( "Use blank background" , ( ) => UseBlankBackground , x =>
323+ {
324+ UseBlankBackground = x ;
325+ OnChangeBackground ? . Invoke ( this , this ) ;
326+ } ) ;
327+
328+ // Then the color picker (disabled when blank is on)
329+ ImGui . BeginDisabled ( UseBlankBackground ) ;
298330 var editorBackground = new Vector3 ( EditorBackground . R ,
299331 EditorBackground . G , EditorBackground . B ) ;
300332 if ( ImGui . ColorEdit3 ( "Background color" , ref editorBackground ) )
@@ -303,6 +335,7 @@ void MainMenu()
303335 editorBackground . Y , editorBackground . Z , 1f ) ;
304336 OnChangeBackground ? . Invoke ( this , this ) ;
305337 }
338+ ImGui . EndDisabled ( ) ;
306339
307340 ForMenuCheck ( "Show PS2 viewport" , ( ) => ShowViewportOriginal , x => ShowViewportOriginal = x ) ;
308341 ForMenuCheck ( "Show ReMIX viewport" , ( ) => ShowViewportRemix , x => ShowViewportRemix = x ) ;
@@ -330,7 +363,7 @@ private void MenuFileOpen()
330363
331364 private void MenuFilePreview ( )
332365 {
333- OpenPreviewDialog ( ) ;
366+ OpenPreviewDialog ( ) ;
334367 }
335368
336369 private void MenuFileOpenWithoutPcsx2 ( )
@@ -371,6 +404,70 @@ private void MenuFileSaveAs()
371404 } , Filters ) ;
372405 }
373406
407+ private void MenuFileExportAnimation ( )
408+ {
409+ if ( CurrentEditor == null )
410+ return ;
411+
412+ var animationEntry = CurrentEditor . SaveAnimation ( AnimationName ?? "export" ) ;
413+
414+ // Determine file extension based on Type (EntryType)
415+ string extension = animationEntry . Type == Bar . EntryType . Layout ? "lad" : "sed" ;
416+ string filterName = animationEntry . Type == Bar . EntryType . Layout ? "Layout file" : "Sequence file" ;
417+
418+ var exportFilter = FileDialogFilterComposer
419+ . Compose ( )
420+ . AddExtensions ( filterName , extension )
421+ . AddAllFiles ( ) ;
422+
423+ FileDialog . OnSave ( fileName =>
424+ {
425+ try
426+ {
427+ using var stream = File . Create ( fileName ) ;
428+ animationEntry . Stream . SetPosition ( 0 ) . CopyTo ( stream ) ;
429+ MessageBox . Show ( $ "Animation/Layout exported successfully to:\n { fileName } ",
430+ "Export Successful" , MessageBoxButton . OK , MessageBoxImage . Information ) ;
431+ }
432+ catch ( Exception ex )
433+ {
434+ ShowError ( $ "Failed to export animation/layout:\n { ex . Message } ") ;
435+ }
436+ } , exportFilter ) ;
437+ }
438+
439+ private void MenuFileExportTexture ( )
440+ {
441+ if ( CurrentEditor == null )
442+ return ;
443+
444+ var textureEntry = CurrentEditor . SaveTexture ( TextureName ?? "export" ) ;
445+
446+ // Determine file extension based on Type (EntryType)
447+ string extension = textureEntry . Type == Bar . EntryType . Imgz ? "imz" : "imd" ;
448+ string filterName = textureEntry . Type == Bar . EntryType . Imgz ? "Image container IMGZ" : "Image IMGD" ;
449+
450+ var exportFilter = FileDialogFilterComposer
451+ . Compose ( )
452+ . AddExtensions ( filterName , extension )
453+ . AddAllFiles ( ) ;
454+
455+ FileDialog . OnSave ( fileName =>
456+ {
457+ try
458+ {
459+ using var stream = File . Create ( fileName ) ;
460+ textureEntry . Stream . SetPosition ( 0 ) . CopyTo ( stream ) ;
461+ MessageBox . Show ( $ "Texture(s) exported successfully to:\n { fileName } ",
462+ "Export Successful" , MessageBoxButton . OK , MessageBoxImage . Information ) ;
463+ }
464+ catch ( Exception ex )
465+ {
466+ ShowError ( $ "Failed to export texture(s):\n { ex . Message } ") ;
467+ }
468+ } , exportFilter ) ;
469+ }
470+
374471 private void MenuFileExit ( ) => _exitFlag = true ;
375472
376473 public void OpenToolDesc ( ToolInvokeDesc toolInvokeDesc )
@@ -601,38 +698,22 @@ private void AddKeyMapping(Keys key, Action action)
601698 private void ProcessKeyMapping ( )
602699 {
603700 var k = Keyboard . GetState ( ) ;
604- if ( k . IsKeyDown ( Keys . LeftControl ) )
701+
702+ // Check if Ctrl is pressed
703+ if ( k . IsKeyDown ( Keys . LeftControl ) || k . IsKeyDown ( Keys . RightControl ) )
605704 {
606705 var keys = k . GetPressedKeys ( ) ;
607706 foreach ( var key in keys )
608707 {
609708 if ( _keyMapping . TryGetValue ( key , out var action ) )
709+ {
710+ // Execute the action regardless of modal state
610711 action ( ) ;
712+ }
611713 }
612714 }
613715 }
614716
615- //private void OpenLayout(LayoutEntryModel layoutEntryModel)
616- //{
617- // AnimationName = layoutEntryModel.Layout.Name;
618- // SpriteName = layoutEntryModel.Images.Name;
619-
620- // var texturesViewModel = new TexturesViewModel(layoutEntryModel.Images.Value);
621-
622- // var layoutEditorViewModel = new LayoutEditorViewModel(this, this, EditorDebugRenderingService)
623- // {
624- // SequenceGroups = new SequenceGroupsViewModel(layoutEntryModel.Layout.Value, texturesViewModel, EditorDebugRenderingService),
625- // Layout = layoutEntryModel.Layout.Value,
626- // Images = layoutEntryModel.Images.Value
627- // };
628-
629- // CurrentEditor = layoutEditorViewModel;
630- // OnControlChanged?.Invoke(new LayoutEditorView()
631- // {
632- // DataContext = layoutEditorViewModel
633- // });
634- //}
635-
636717 private bool LinkLaydToPcs2 ( Stream stream ) =>
637718 LinkToPcs2 ( stream , Layout . MagicCodeValidator , 0x1c ) ;
638719
@@ -677,6 +758,82 @@ private void CloseProcessStream()
677758 _processStream = null ;
678759 }
679760
761+ private void MenuFileExportLayoutAsPngSequence ( )
762+ {
763+ if ( ! ( CurrentEditor is AppLayoutEditor layoutEditor ) )
764+ {
765+ ShowError ( "This feature is only available for Layout files (2LD)." ) ;
766+ return ;
767+ }
768+
769+ // Let user select a folder
770+ using ( var folderDialog = new System . Windows . Forms . FolderBrowserDialog ( ) )
771+ {
772+ folderDialog . Description = "Select folder to save PNG sequence" ;
773+ folderDialog . ShowNewFolderButton = true ;
774+
775+ if ( folderDialog . ShowDialog ( ) == System . Windows . Forms . DialogResult . OK )
776+ {
777+ try
778+ {
779+ int frameCount = layoutEditor . ExportCurrentLayoutAsPngSequence ( folderDialog . SelectedPath ) ;
780+
781+ MessageBox . Show (
782+ $ "PNG sequence exported successfully!\n \n " +
783+ $ "Location: { folderDialog . SelectedPath } \n " +
784+ $ "Frames: { frameCount } files (frame_0000.png to frame_{ frameCount - 1 : D4} .png)\n \n ",
785+ "Export Successful" ,
786+ MessageBoxButton . OK ,
787+ MessageBoxImage . Information ) ;
788+ }
789+ catch ( Exception ex )
790+ {
791+ ShowError ( $ "Failed to export PNG sequence:\n { ex . Message } ") ;
792+ }
793+ }
794+ }
795+ }
796+
797+ private void MenuFileExportLayoutAsGif ( )
798+ {
799+ if ( ! ( CurrentEditor is AppLayoutEditor layoutEditor ) )
800+ {
801+ ShowError ( "This feature is only available for Layout files (2LD)." ) ;
802+ return ;
803+ }
804+
805+ var gifFilter = FileDialogFilterComposer
806+ . Compose ( )
807+ . AddExtensions ( "Animated GIF" , "gif" )
808+ . AddAllFiles ( ) ;
809+
810+ FileDialog . OnSave ( fileName =>
811+ {
812+ try
813+ {
814+ // Default settings optimized for quality and reasonable speed
815+ // frameDelay: 33ms = ~30 FPS, 50ms = 20 FPS (smoother, less flicker)
816+ // quality: 1 = best quality (slower), 10 = balanced, 20 = fastest (lower quality)
817+ int frameDelay = 33 ;
818+ int quality = 10 ;
819+
820+ layoutEditor . ExportCurrentLayoutAsGif ( fileName , frameDelay , quality ) ;
821+
822+ MessageBox . Show (
823+ $ "Animated GIF exported successfully to:\n { fileName } \n \n " +
824+ $ "Settings: { 1000 / frameDelay } FPS, Quality: { quality } /20\n \n " +
825+ $ "Note: GIF format has inherent flickering on fast color transitions due to 256-color palette limitation per frame.",
826+ "Export Successful" ,
827+ MessageBoxButton . OK ,
828+ MessageBoxImage . Information ) ;
829+ }
830+ catch ( Exception ex )
831+ {
832+ ShowError ( $ "Failed to export animated GIF:\n { ex . Message } ") ;
833+ }
834+ } , gifFilter ) ;
835+ }
836+
680837 private static bool DoesContainSequenceAnimations ( IEnumerable < Bar . Entry > entries ) =>
681838 entries . Any ( x => x . Type == Bar . EntryType . Seqd ) ;
682839
0 commit comments