Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.debian.com.mx/llms.txt

Use this file to discover all available pages before exploring further.

Window Manager

The WindowManager is the core system responsible for managing all window operations in Time Capsule. It handles window lifecycle, positioning, dragging, focus management, z-index ordering, and workspace assignment.

Overview

WindowManager is implemented as a singleton module pattern with private state and exposed public methods:
const WindowManager = (() => {
  let zIndex = CONFIG.WINDOW.BASE_Z_INDEX;
  let highestWindowZIndex = CONFIG.WINDOW.BASE_Z_INDEX;
  let highestModalZIndex = 90000;
  let currentWorkspace = '1';
  let lastFocusedWindowId: string | null = null;
  const dragState: DragState = { /* ... */ };

  // Public API
  return {
    init,
    drag,
    focusWindow,
    registerWindow,
    centerWindow,
    switchWorkspace,
    showWindow,
    getNextZIndex,
    getTopZIndex,
  };
})();

Key Concepts

Window Registration

All windows must be registered with WindowManager to enable management features:
function registerWindow(win: HTMLElement): void {
  if (win.hasAttribute('data-cde-registered')) return;

  const id = win.id;
  const titlebar = document.getElementById(`${id}Titlebar`) || 
                   win.querySelector('.titlebar');

  if (titlebar) {
    // Restore session position if available
    const session = settingsManager
      .getSection('session')
      .windows[id];
      
    if (session && session.left && session.top) {
      win.style.left = session.left;
      win.style.top = session.top;
      if (session.maximized) {
        win.classList.add('maximized');
      }
    } else {
      // First time: normalize position
      setTimeout(() => {
        normalizeWindowPosition(win);
      }, CONFIG.TIMINGS.NORMALIZATION_DELAY);
    }

    // Enable dragging
    titlebar.style.touchAction = 'none';
    titlebar.addEventListener(
      'pointerdown', 
      titlebarDragHandler
    );
    
    win.setAttribute('data-cde-registered', 'true');
  }
}

Dynamic Scanning

WindowManager uses a MutationObserver to automatically detect and register new windows:
function initDynamicScanning(): void {
  // Scan existing windows
  const windows = document.querySelectorAll(
    '.window, .cde-retro-modal'
  );
  windows.forEach((el) => registerWindow(el as HTMLElement));

  // Observe for new windows
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      mutation.addedNodes.forEach((node) => {
        if (node instanceof HTMLElement) {
          if (node.classList.contains('window') || 
              node.classList.contains('cde-retro-modal')) {
            registerWindow(node);
          }
          // Scan children
          node.querySelectorAll('.window, .cde-retro-modal')
            .forEach((el) => registerWindow(el as HTMLElement));
        }
      });
    });
  });

  observer.observe(document.body, { 
    childList: true, 
    subtree: true 
  });
}

Z-Index Management

Layered Z-Index System

Z-indexes are managed in separate layers to prevent conflicts:
// Layer allocation
const BASE_WINDOW_Z = 10000;  // Regular windows
const BASE_MODAL_Z = 90000;   // Modal dialogs
const DROPDOWN_Z = 20000;     // Dropdown menus

let highestWindowZIndex = CONFIG.WINDOW.BASE_Z_INDEX;
let highestModalZIndex = 90000;

function getNextZIndex(isModal: boolean = false): number {
  if (isModal) {
    return ++highestModalZIndex;
  }
  return ++highestWindowZIndex;
}

function getTopZIndex(): number {
  return Math.max(highestWindowZIndex, highestModalZIndex);
}

Focus and Z-Index

Focusing a window assigns it the highest z-index in its layer:
function focusWindow(id: string): void {
  if (id === lastFocusedWindowId) return;

  const win = document.getElementById(id);
  if (!win) return;

  if (!dragState.isDragging) {
    // Remove active class from previous window
    if (lastFocusedWindowId) {
      const prevWin = document.getElementById(
        lastFocusedWindowId
      );
      if (prevWin) prevWin.classList.remove('active');
    }

    // Periodic cleanup of stale classes
    if (Math.random() < 0.05) {
      document.querySelectorAll('.active').forEach((el) => {
        if (el.id !== id) el.classList.remove('active');
      });
    }

    win.classList.add('active');
    lastFocusedWindowId = id;

    zIndex = getNextZIndex();
    win.style.zIndex = String(zIndex);

    if (window.AudioManager) window.AudioManager.click();
  }
}

Drag and Drop

Drag State Management

interface DragState {
  element: HTMLElement | null;
  offsetX: number;
  offsetY: number;
  startX: number;
  startY: number;
  startLeft: number;
  startTop: number;
  lastX: number;
  lastY: number;
  isDragging: boolean;
}

Pointer-Based Dragging

Dragging uses PointerEvents for unified mouse/touch support:
function drag(e: PointerEvent, id: string): void {
  // Disable drag on mobile
  if (isMobile()) {
    logger.log(
      `[WindowManager] Drag disabled on mobile for: ${id}`
    );
    return;
  }

  if (!e.isPrimary) return;

  const el = document.getElementById(id);
  if (!el) return;

  e.preventDefault();
  e.stopPropagation();

  // Normalize position if transform is applied
  if (window.getComputedStyle(el).transform !== 'none') {
    normalizeWindowPosition(el);
  }

  focusWindow(id);

  const rect = el.getBoundingClientRect();
  dragState.element = el;
  dragState.offsetX = e.clientX - rect.left;
  dragState.offsetY = e.clientY - rect.top;
  dragState.lastX = e.clientX;
  dragState.lastY = e.clientY;
  dragState.isDragging = true;

  // Capture pointer for reliable tracking
  el.setPointerCapture(e.pointerId);

  // X11-style move cursor
  document.documentElement.style.setProperty(
    '--cde-cursor-override',
    "url('/icons/cursors/cursor-move.svg') 12 12, move"
  );
  document.body.style.cursor = 
    "url('/icons/cursors/cursor-move.svg') 12 12, move";

  // Performance optimization
  el.style.willChange = 'transform, left, top';

  el.addEventListener('pointermove', move, { passive: false });
  el.addEventListener('pointerup', stopDrag, { passive: false });
  el.addEventListener('pointercancel', stopDrag, { passive: false });
}

Mouse Acceleration

Mouse movement supports configurable acceleration:
function move(e: PointerEvent): void {
  if (!dragState.element || !dragState.isDragging) return;

  e.preventDefault();
  e.stopPropagation();

  // Get acceleration from CSS variable
  const accelStr = getComputedStyle(document.documentElement)
    .getPropertyValue('--mouse-acceleration');
  const acceleration = parseFloat(accelStr) || 1;

  // Calculate delta
  const deltaX = e.clientX - dragState.lastX;
  const deltaY = e.clientY - dragState.lastY;

  // Apply acceleration
  let currentLeft = parseFloat(
    dragState.element.style.left || '0'
  );
  let currentTop = parseFloat(
    dragState.element.style.top || '0'
  );

  let left = currentLeft + deltaX * acceleration;
  let top = currentTop + deltaY * acceleration;

  // Update tracking
  dragState.lastX = e.clientX;
  dragState.lastY = e.clientY;

  // Apply constraints (shown in next section)
  // ...

  dragState.element.style.left = left + 'px';
  dragState.element.style.top = top + 'px';
}

Viewport Constraints

Windows are constrained to stay within viewport bounds:
function move(e: PointerEvent): void {
  // ... delta calculation ...

  const winWidth = dragState.element.offsetWidth;
  const winHeight = dragState.element.offsetHeight;
  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  const TOP_BAR_HEIGHT = CONFIG.WINDOW.TOP_BAR_HEIGHT;
  const PANEL_HEIGHT = isMobile() ? 65 : 85;

  // Define bounds
  const minX = 0;
  const maxX = Math.max(0, viewportWidth - winWidth);
  const minY = TOP_BAR_HEIGHT;
  const maxY = Math.max(
    minY, 
    viewportHeight - winHeight - PANEL_HEIGHT
  );

  // Clamp position
  left = Math.max(minX, Math.min(left, maxX));
  top = Math.max(minY, Math.min(top, maxY));

  dragState.element.style.left = left + 'px';
  dragState.element.style.top = top + 'px';
}

Opaque vs Wireframe Dragging

function move(e: PointerEvent): void {
  // ...
  
  // Check if opaque dragging is enabled
  const opaque = document.documentElement
    .getAttribute('data-opaque-drag') !== 'false';
    
  if (!opaque) {
    // Wireframe mode: lighter rendering
    dragState.element.classList.add('dragging-wireframe');
  }
  
  // ...
}

Drag Completion

function stopDrag(e: PointerEvent): void {
  if (!dragState.element || !dragState.isDragging) return;

  e.preventDefault();
  e.stopPropagation();

  const el = dragState.element;
  el.releasePointerCapture(e.pointerId);
  el.removeEventListener('pointermove', move);
  el.removeEventListener('pointerup', stopDrag);
  el.removeEventListener('pointercancel', stopDrag);

  // Clear performance hints
  el.style.willChange = 'auto';

  // Restore cursor
  document.body.style.cursor = '';

  el.classList.remove('dragging-wireframe');
  dragState.isDragging = false;

  // Persist position
  settingsManager.updateWindowSession(el.id, {
    left: el.style.left,
    top: el.style.top,
    maximized: el.classList.contains('maximized'),
  });

  dragState.element = null;
}

Window Operations

Minimize

function minimizeWindow(id: string): void {
  const win = document.getElementById(id);
  if (!win) return;

  if (win.style.display !== 'none') {
    // Save state before hiding
    windowStates[id] = {
      display: win.style.display,
      left: win.style.left,
      top: win.style.top,
      width: win.style.width,
      height: win.style.height,
      maximized: win.classList.contains('maximized'),
    };

    // Animate closing
    win.classList.add('window-closing');
    if (window.AudioManager) {
      window.AudioManager.windowMinimize();
    }

    // Hide after animation
    win.addEventListener(
      'animationend',
      () => {
        win.style.display = 'none';
        win.classList.remove('window-closing');
      },
      { once: true }
    );
  }
}

Maximize

function maximizeWindow(id: string): void {
  const win = document.getElementById(id);
  if (!win || win.hasAttribute('data-no-maximize')) return;

  if (win.classList.contains('maximized')) {
    // Restore
    win.classList.remove('maximized');
    if (window.AudioManager) {
      window.AudioManager.windowMaximize();
    }

    // Update icon
    const maxBtnImg = win.querySelector('.max-btn img') as 
      HTMLImageElement;
    if (maxBtnImg) {
      maxBtnImg.src = '/icons/ui/maximize-inactive.png';
    }

    // Restore saved size/position
    if (windowStates[id]) {
      win.style.left = windowStates[id].left || '';
      win.style.top = windowStates[id].top || '';
      win.style.width = windowStates[id].width || '';
      win.style.height = windowStates[id].height || '';
    }
    
    settingsManager.updateWindowSession(id, { 
      maximized: false 
    });
  } else {
    // Maximize
    windowStates[id] = {
      left: win.style.left,
      top: win.style.top,
      width: win.style.width,
      height: win.style.height,
      maximized: false,
    };
    
    win.classList.add('maximized');
    if (window.AudioManager) {
      window.AudioManager.windowMaximize();
    }

    // Update icon
    const maxBtnImg = win.querySelector('.max-btn img') as 
      HTMLImageElement;
    if (maxBtnImg) {
      maxBtnImg.src = '/icons/ui/maximize-toggled-inactive.png';
    }
    
    settingsManager.updateWindowSession(id, { 
      maximized: true 
    });
  }
  
  WindowManager.focusWindow(id);
}

Window Shading

Double-click titlebar to “shade” (roll up) a window:
function shadeWindow(id: string): void {
  const win = document.getElementById(id);
  if (!win) return;

  const titlebar = win.querySelector('.titlebar') as HTMLElement;
  if (!titlebar) return;

  const isMaximized = win.classList.contains('maximized');

  if (win.classList.contains('shaded')) {
    // Unshade: restore height
    win.classList.remove('shaded');

    if (isMaximized) {
      win.style.height = '';
    } else if (windowStates[id]?.height) {
      win.style.height = windowStates[id].height!;
    }

    if (window.AudioManager) {
      window.AudioManager.windowShade();
    }
  } else {
    // Shade: save height and collapse
    if (!isMaximized) {
      windowStates[id] = {
        ...windowStates[id],
        height: win.style.height || 
                getComputedStyle(win).height,
      };
    }

    win.classList.add('shaded');
    win.style.height = titlebar.offsetHeight + 'px';

    if (window.AudioManager) {
      window.AudioManager.windowShade();
    }
  }
}

Focus Management

Focus Modes

Two focus modes are supported:
Default mode. Windows receive focus when clicked.
document.addEventListener('pointerdown', (e) => {
  if (dragState.isDragging) return;
  
  const target = e.target as HTMLElement;
  const win = target.closest('.window, .cde-retro-modal');
  if (win) {
    focusWindow(win.id);
  }
});

Workspace Management

Virtual Desktops

WindowManager supports 4 virtual workspaces:
function switchWorkspace(id: string): void {
  if (id === currentWorkspace) return;

  AudioManager.click();

  const windows = document.querySelectorAll(
    '.window, .cde-retro-modal'
  );

  // Hide windows from current workspace
  windows.forEach((win) => {
    const el = win as HTMLElement;
    const winWorkspace = el.getAttribute('data-workspace');

    if (winWorkspace === currentWorkspace) {
      const isVisible = 
        window.getComputedStyle(el).display !== 'none';
      
      if (isVisible) {
        el.setAttribute('data-was-opened', 'true');
        el.style.display = 'none';
      }
    }
  });

  currentWorkspace = id;

  // Show windows from new workspace
  windows.forEach((win) => {
    const el = win as HTMLElement;
    const winWorkspace = el.getAttribute('data-workspace');

    if (winWorkspace === currentWorkspace) {
      if (el.getAttribute('data-was-opened') === 'true') {
        el.style.display = 'flex';
      }
    }
  });

  // Update pager UI
  const pagerItems = document.querySelectorAll(
    '.pager-workspace'
  );
  pagerItems.forEach((item) => {
    if ((item as HTMLElement).dataset.workspace === id) {
      item.classList.add('active');
    } else {
      item.classList.remove('active');
    }
  });
}

Workspace Assignment

Windows are automatically assigned to the current workspace when opened:
function showWindow(id: string): void {
  const win = document.getElementById(id);
  if (!win) return;

  // Assign workspace on first show
  if (!win.getAttribute('data-workspace')) {
    win.setAttribute('data-workspace', currentWorkspace);
  }

  // Mark as opened
  win.setAttribute('data-was-opened', 'true');

  win.style.display = 'flex';
  win.classList.add('window-opening');

  // Center on mobile
  if (isMobile()) {
    centerWindow(win);
  }

  focusWindow(id);
  AudioManager.windowOpen();

  win.addEventListener(
    'animationend',
    () => {
      win.classList.remove('window-opening');
    },
    { once: true }
  );
}

Mobile Considerations

Disabled Features

Certain features are disabled on mobile for better UX:
function isMobile(): boolean {
  return window.innerWidth < 768;
}

// Drag disabled on mobile
function drag(e: PointerEvent, id: string): void {
  if (isMobile()) {
    logger.log('[WindowManager] Drag disabled on mobile');
    return;
  }
  // ...
}

Automatic Centering

Windows are automatically centered on mobile devices:
function centerWindow(win: HTMLElement): void {
  const winWidth = win.offsetWidth;
  const winHeight = win.offsetHeight;
  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  const TOP_BAR_HEIGHT = CONFIG.WINDOW.TOP_BAR_HEIGHT;
  const PANEL_HEIGHT = isMobile() ? 65 : 85;

  let left = (viewportWidth - winWidth) / 2;
  let top = (viewportHeight - winHeight) / 2;

  // Clamp to viewport
  const minX = 0;
  const maxX = Math.max(0, viewportWidth - winWidth);
  const minY = TOP_BAR_HEIGHT;
  const maxY = Math.max(
    minY, 
    viewportHeight - winHeight - PANEL_HEIGHT
  );

  left = Math.max(minX, Math.min(left, maxX));
  top = Math.max(minY, Math.min(top, maxY));

  win.style.position = 'absolute';
  win.style.left = `${left}px`;
  win.style.top = `${top}px`;
  win.style.transform = 'none';
}

Resize Handling

Windows are normalized when viewport resizes:
window.addEventListener('resize', () => {
  clearTimeout(resizeTimer);
  resizeTimer = window.setTimeout(() => {
    logger.log(
      '[WindowManager] Viewport resized, ' + 
      'normalizing positions...'
    );
    
    document.querySelectorAll('.window, .cde-retro-modal')
      .forEach((win) => {
        if (win instanceof HTMLElement) {
          if (isMobile()) {
            centerWindow(win);
          } else {
            normalizeWindowPosition(win);
          }
        }
      });
  }, CONFIG.TIMINGS.NORMALIZATION_DELAY);
});

Global Exposure

WindowManager functions are exposed globally for legacy compatibility:
declare global {
  interface Window {
    drag: (e: PointerEvent, id: string) => void;
    focusWindow: (id: string) => void;
    centerWindow: (win: HTMLElement) => void;
    minimizeWindow: typeof minimizeWindow;
    maximizeWindow: typeof maximizeWindow;
    shadeWindow: typeof shadeWindow;
    WindowManager: typeof WindowManager;
  }
}

window.drag = WindowManager.drag;
window.focusWindow = WindowManager.focusWindow;
window.centerWindow = WindowManager.centerWindow;
window.minimizeWindow = minimizeWindow;
window.maximizeWindow = maximizeWindow;
window.shadeWindow = shadeWindow;
window.WindowManager = WindowManager;

Best Practices

Ensure windows have proper structure and are registered:
<div id="myWindow" class="window">
  <div id="myWindowTitlebar" class="titlebar">
    <span class="titlebar-text">My Window</span>
    <div class="titlebar-buttons">
      <button class="min-btn" 
              onclick="minimizeWindow('myWindow')">
        <img src="/icons/ui/shade-inactive.png" />
      </button>
      <button class="max-btn" 
              onclick="maximizeWindow('myWindow')">
        <img src="/icons/ui/maximize-inactive.png" />
      </button>
      <button class="close-btn" 
              onclick="document.getElementById('myWindow').style.display='none'">
        <img src="/icons/ui/close-inactive.png" />
      </button>
    </div>
  </div>
  <div class="window-content">
    <!-- Content -->
  </div>
</div>
Use updateWindowSession after position/state changes:
settingsManager.updateWindowSession(id, {
  left: win.style.left,
  top: win.style.top,
  maximized: win.classList.contains('maximized'),
});
Check isMobile() before drag/resize operations:
if (isMobile()) {
  centerWindow(win);
  return; // Skip desktop-specific operations
}

Architecture

Overall system architecture overview

Storage

Window session persistence mechanism

Virtual Filesystem

File operations in window contexts

Development

Build applications using WindowManager