Tab.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { EventDispatcher } from 'three';
  2. /**
  3. * Tab class
  4. * @param {string} title - The title of the tab
  5. * @param {Object} options - Options for the tab
  6. * @param {boolean} [options.allowDetach=true] - Whether the tab can be detached into a separate window
  7. * @param {boolean} [options.builtin=false] - Whether the tab should appear in the profiler-toggle button
  8. * @param {string} [options.icon] - SVG icon HTML for the builtin button
  9. *
  10. * @example
  11. * // Create a tab that can be detached (default behavior)
  12. * const tab1 = new Tab('My Tab');
  13. *
  14. * // Create a tab that cannot be detached
  15. * const tab2 = new Tab('Fixed Tab', { allowDetach: false });
  16. *
  17. * // Create a builtin tab that appears in the profiler-toggle
  18. * const tab3 = new Tab('Builtin Tab', { builtin: true });
  19. *
  20. * // Create a builtin tab with custom icon
  21. * const tab4 = new Tab('Settings', { builtin: true, icon: '<svg>...</svg>' });
  22. *
  23. * // Control builtin tab visibility
  24. * tab3.showBuiltin(); // Show the builtin button and mini-content
  25. * tab3.hideBuiltin(); // Hide the builtin button and mini-content
  26. */
  27. export class Tab extends EventDispatcher {
  28. constructor( title, options = {} ) {
  29. super();
  30. this.id = title.toLowerCase().replace( /\s+/g, '-' );
  31. this.button = document.createElement( 'button' );
  32. this.button.className = 'tab-btn';
  33. this.button.textContent = title;
  34. this.content = document.createElement( 'div' );
  35. this.content.className = 'profiler-content';
  36. this.content.classList.add( `${this.id}-content` );
  37. this._isActive = false;
  38. this.isVisible = true;
  39. this.isDetached = false;
  40. this.detachedWindow = null;
  41. this.allowDetach = options.allowDetach !== undefined ? options.allowDetach : true;
  42. this.builtin = options.builtin !== undefined ? options.builtin : false;
  43. this.icon = options.icon || null;
  44. this.builtinButton = null; // Reference to the builtin button in profiler-toggle
  45. this.miniContent = null; // Reference to the mini-panel content container
  46. this.profiler = null; // Reference to the profiler instance
  47. this.onVisibilityChange = null; // Callback for visibility changes
  48. }
  49. get inspector() {
  50. return this.profiler.inspector;
  51. }
  52. get isActive() {
  53. if ( this.isDetached && this.isVisible ) return true;
  54. const isProfilerVisible = this.profiler && this.profiler.panel.classList.contains( 'visible' );
  55. if ( ! isProfilerVisible ) return false;
  56. return this._isActive;
  57. }
  58. set isActive( value ) {
  59. this._isActive = value;
  60. }
  61. init( /*inspector*/ ) { }
  62. update( /*inspector*/ ) { }
  63. setActive( isActive ) {
  64. this.button.classList.toggle( 'active', isActive );
  65. this.content.classList.toggle( 'active', isActive );
  66. this.isActive = isActive;
  67. }
  68. show() {
  69. this.content.style.display = '';
  70. this.button.style.display = '';
  71. this.isVisible = true;
  72. // Show detached window if tab is detached
  73. if ( this.isDetached && this.detachedWindow ) {
  74. this.detachedWindow.panel.style.display = '';
  75. }
  76. // Notify profiler of visibility change
  77. if ( this.onVisibilityChange ) {
  78. this.onVisibilityChange();
  79. }
  80. this.showBuiltin();
  81. }
  82. hide() {
  83. this.content.style.display = 'none';
  84. this.button.style.display = 'none';
  85. this.isVisible = false;
  86. // Hide detached window if tab is detached
  87. if ( this.isDetached && this.detachedWindow ) {
  88. this.detachedWindow.panel.style.display = 'none';
  89. }
  90. // Notify profiler of visibility change
  91. if ( this.onVisibilityChange ) {
  92. this.onVisibilityChange();
  93. }
  94. this.hideBuiltin();
  95. }
  96. showBuiltin() {
  97. if ( ! this.builtin ) return;
  98. // Show the builtin-tabs-container
  99. if ( this.profiler && this.profiler.builtinTabsContainer ) {
  100. this.profiler.builtinTabsContainer.style.display = '';
  101. }
  102. // Show the button
  103. if ( this.builtinButton ) {
  104. this.builtinButton.style.display = '';
  105. }
  106. // Show and activate the mini-panel with content
  107. if ( this.miniContent && this.profiler ) {
  108. // Hide all other mini-panel contents
  109. this.profiler.miniPanel.querySelectorAll( '.mini-panel-content' ).forEach( content => {
  110. content.style.display = 'none';
  111. } );
  112. // Remove active state from all builtin buttons
  113. this.profiler.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ).forEach( btn => {
  114. btn.classList.remove( 'active' );
  115. } );
  116. // Activate this tab's button
  117. if ( this.builtinButton ) {
  118. this.builtinButton.classList.add( 'active' );
  119. }
  120. // Move content to mini-panel if not already there
  121. if ( ! this.miniContent.firstChild ) {
  122. while ( this.content.firstChild ) {
  123. this.miniContent.appendChild( this.content.firstChild );
  124. }
  125. }
  126. // Show the mini-panel and content
  127. this.miniContent.style.display = 'block';
  128. this.profiler.miniPanel.classList.add( 'visible' );
  129. }
  130. }
  131. hideBuiltin() {
  132. if ( ! this.builtin ) return;
  133. // Hide the button
  134. if ( this.builtinButton ) {
  135. this.builtinButton.style.display = 'none';
  136. }
  137. // Hide the mini-panel content
  138. if ( this.miniContent ) {
  139. this.miniContent.style.display = 'none';
  140. // Move content back to main panel
  141. if ( this.miniContent.firstChild ) {
  142. while ( this.miniContent.firstChild ) {
  143. this.content.appendChild( this.miniContent.firstChild );
  144. }
  145. }
  146. }
  147. // Deactivate button
  148. if ( this.builtinButton ) {
  149. this.builtinButton.classList.remove( 'active' );
  150. }
  151. // Hide mini-panel if no content is visible
  152. if ( this.profiler ) {
  153. const hasVisibleContent = Array.from( this.profiler.miniPanel.querySelectorAll( '.mini-panel-content' ) )
  154. .some( content => content.style.display !== 'none' );
  155. if ( ! hasVisibleContent ) {
  156. this.profiler.miniPanel.classList.remove( 'visible' );
  157. }
  158. // Hide the builtin-tabs-container if all builtin buttons are hidden
  159. const hasVisibleBuiltinButtons = Array.from( this.profiler.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ) )
  160. .some( btn => btn.style.display !== 'none' );
  161. if ( ! hasVisibleBuiltinButtons ) {
  162. this.profiler.builtinTabsContainer.style.display = 'none';
  163. }
  164. }
  165. }
  166. }
粤ICP备19079148号