All files / components/screens/combat/hooks useCombatLayout.ts

95% Statements 38/40
97.05% Branches 33/34
100% Functions 6/6
94.59% Lines 35/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189                                                                                                                                        106x       106x     106x 106x           106x 64x                                   106x 65x     65x   51x 51x 51x 51x                   51x   4x 47x   36x 11x   1x     10x     51x     51x 51x 51x     51x           51x 51x     51x 51x   51x                     14x     14x 14x   14x                 106x              
/**
 * useCombatLayout Hook - Enhanced Responsive Combat Layout
 *
 * Custom hook for managing responsive combat screen layout calculations with
 * comprehensive support for all screen sizes from mobile to ultra-wide displays.
 *
 * Enhanced Features:
 * - Five screen size categories (mobile, tablet, desktop, large, xlarge)
 * - Proportional scaling for consistent sizing across devices
 * - Optimized arena sizing for each device category
 * - Smooth transitions for resize operations
 * - 60fps performance maintained
 *
 * Uses robust device detection combining user-agent and screen size to ensure
 * mobile controls are shown on all mobile devices, including high-resolution phones.
 *
 * Performance:
 * - Reduces recalculations by checking only breakpoint changes, not exact dimensions
 * - Memoizes arena bounds to prevent cascading re-renders
 * - Targets <1ms execution time for layout calculations
 *
 * @param width - Screen width
 * @param height - Screen height
 *
 * @returns Layout constants and arena bounds
 *
 * @example
 * ```typescript
 * const { layoutConstants, arenaBounds, isMobile, screenSize } = useCombatLayout(1200, 800);
 * ```
 */
 
import { useMemo } from "react";
import { shouldUseMobileControls } from "../../../../utils/deviceDetection";
import { getScreenSize } from "../../../../systems/ResponsiveScaling";
 
import type { ScreenSize } from "../../../../systems/ResponsiveScaling";
 
export interface LayoutConstants {
  readonly padding: number;
  readonly hudHeight: number;
  readonly controlsHeight: number;
  readonly footerHeight: number;
  readonly healthBarHeight: number;
}
 
export interface ArenaBounds {
  readonly x: number;
  readonly y: number;
  readonly width: number;
  readonly height: number;
  readonly scale: number; // 3D scale factor for arena (1.0 = desktop, <1.0 = mobile)
}
 
export interface CombatLayout {
  readonly layoutConstants: LayoutConstants;
  readonly arenaBounds: ArenaBounds;
  readonly isMobile: boolean;
  readonly screenSize: ScreenSize;
}
 
/**
 * Custom hook for combat screen layout calculations
 * Enhanced with centralized responsive scaling system
 * Optimized to reduce recalculations and improve 60fps performance
 */
export function useCombatLayout(width: number, height: number): CombatLayout {
  // Determine screen size category using centralized scaling system
  const screenSize = useMemo(() => getScreenSize(width), [width]);
  
  // Device detection has its own internal caching based on screen dimensions
  // No need for additional React memoization here
  const isMobile = shouldUseMobileControls();
  
  // Use screen size category instead of pixel threshold for better organization
  const isLargeDesktop = useMemo(() => screenSize === 'xlarge', [screenSize]);
  const isTablet = useMemo(() => screenSize === 'tablet', [screenSize]);
 
  // Centralized layout constants for easier tweaking
  // Enhanced with tablet-specific values for better responsive support
  // Updated mobile controls height for new sizing: D-Pad (140px), buttons (80px+70px)
  // Optimized: Depends on breakpoint booleans, not exact width
  const layoutConstants = useMemo<LayoutConstants>(
    () => ({
      padding: 10,
      hudHeight: isMobile ? 95 : isTablet ? 100 : isLargeDesktop ? 90 : 120,
      controlsHeight: isMobile ? 160 : isTablet ? 140 : isLargeDesktop ? 120 : 160,
      footerHeight: isMobile ? 34 : isTablet ? 30 : isLargeDesktop ? 20 : 28,
      healthBarHeight: isMobile ? 48 : isTablet ? 50 : isLargeDesktop ? 45 : 55,
    }),
    [isMobile, isTablet, isLargeDesktop]
  );
 
  // Fixed player positions for 2-player combat with proper bounds
  // Arena bounds should account for HUD at top and controls at bottom
  // Mobile arena sizing with 4:3 aspect ratio adapts to device resolution:
  // - Standard phones (< 768px): up to 400px width
  // - Large phones (768-1199px): up to 500px width
  // - 2K devices (1200-1439px): up to 600px width
  // - 4K devices (≥1440px): up to 800px width
  // Optimized: Separate calculation dependencies to reduce recalculation frequency
  const arenaBounds = useMemo<ArenaBounds>(() => {
    const arenaY = layoutConstants.hudHeight + layoutConstants.padding;
 
    // Mobile-specific arena sizing for better screen fit
    if (isMobile) {
      // Reserve space for HUD (80px min) and controls (120px min)
      const minTopClearance = 80;
      const minBottomClearance = 120;
      const availableHeight = height - minTopClearance - minBottomClearance;
      const availableWidth = width - 40; // 20px margins on each side
 
      // Calculate optimal arena size maintaining 4:3 aspect ratio (width:height)
      // Target sizing based on device resolution:
      // - Standard mobile (375-430px): 350x262px to 400x300px
      // - High-res mobile (1200px+): Up to 600x450px (2K Android)
      // - Ultra high-res (1440px+): Up to 800x600px (4K Android)
      
      // Determine max width based on screen width for high-end devices
      let maxMobileWidth: number;
      if (width >= 1440) {
        // 4K/QHD+ Android devices (e.g., Galaxy S23 Ultra, Pixel 9 Pro)
        maxMobileWidth = Math.min(availableWidth, 800);
      } else if (width >= 1200) {
        // 2K Android devices
        maxMobileWidth = Math.min(availableWidth, 600);
      } else if (width >= 768) {
        // Large phones (e.g., iPhone 14 Pro Max)
        maxMobileWidth = Math.min(availableWidth, 500);
      } else {
        // Standard phones (e.g., iPhone SE, standard Android)
        maxMobileWidth = Math.min(availableWidth, 400);
      }
      
      const maxMobileHeight = Math.min(availableHeight, 800);
 
      // Maintain 4:3 aspect ratio (width:height = 4:3)
      const aspectRatio = 4 / 3;
      let arenaWidth = maxMobileWidth;
      let arenaHeight = arenaWidth / aspectRatio; // height = width / (4/3) = width * (3/4)
 
      // If height exceeds available, recalculate based on height constraint
      Iif (arenaHeight > maxMobileHeight) {
        arenaHeight = maxMobileHeight;
        arenaWidth = arenaHeight * aspectRatio; // width = height * (4/3)
      }
 
      // Ensure minimum size for playability without exceeding available space
      arenaWidth = Math.min(Math.max(arenaWidth, 300), availableWidth);
      arenaHeight = Math.min(Math.max(arenaHeight, 225), maxMobileHeight); // 300 * 3/4 = 225
 
      // Calculate 3D scale factor (mobile arena is smaller than desktop)
      const desktopWidth = 960; // 80% of 1200px
      const scale = arenaWidth / desktopWidth;
 
      return {
        x: (width - arenaWidth) / 2, // Centered horizontally
        y: arenaY,
        width: arenaWidth,
        height: arenaHeight,
        scale,
      };
    }
 
    // Desktop arena sizing - use full available space
    const totalReservedHeight =
      layoutConstants.hudHeight +
      layoutConstants.controlsHeight +
      layoutConstants.footerHeight;
    const totalPadding = layoutConstants.padding * 3;
    const arenaHeight = height - totalReservedHeight - totalPadding;
 
    return {
      x: width * 0.1,
      y: arenaY,
      width: width * 0.8,
      height: arenaHeight,
      scale: 1.0, // Desktop uses full scale
    };
  }, [width, height, layoutConstants, isMobile]);
 
  return {
    layoutConstants,
    arenaBounds,
    isMobile,
    screenSize,
  };
}