Monorepo for Aesthetic.Computer aesthetic.computer
at main 283 lines 12 kB view raw view rendered
1# Button Width Sizing Issue - Technical Plan 2 3## Problem Summary 4The HUD button width calculation for KidLisp syntax highlighting is not working optimally. Buttons are sometimes too wide, too narrow, or inconsistent when resizing windows. 5 6## Current State (September 6, 2025) 7 8### Key Files & Code Locations 9- **Primary File**: `/workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/disk.mjs` 10- **Button Width Logic**: Lines ~8000-8070 (makeFrame function) 11- **Text Rendering Pipeline**: `write() → text.box() → typeface.print() → $.printLine()` 12- **Supporting Files**: 13 - `type.mjs`: Typeface class with blockWidth/blockHeight properties 14 - `kidlisp.mjs`: updateHUDWithSyntaxHighlighting() function 15 16### Current Implementation Details 17 18#### Text Cleaning (Lines ~8000-8015) 19```javascript 20// Use plain text for width calculation to avoid counting color codes 21const textForWidthCalculation = currentHUDPlainTxt || currentHUDTxt; 22 23// Double-check: strip color codes directly if they're still present 24const colorCodeRegex = /\\[^\\]*\\/g; 25const cleanText = textForWidthCalculation.replace(colorCodeRegex, ''); 26``` 27 28#### Button Width Calculation (Lines ~8015-8035) 29```javascript 30// Use consistent scaling regardless of screen size 31const screenScale = 1.0; 32const scaledBlockWidth = tf.blockWidth * screenScale; 33 34// Calculate button width based on text content 35let maxButtonWidth; 36 37if (cleanText.includes('\n')) { 38 // Multiline: calculate bounds based on longest line with generous buffer to prevent wrapping 39 const lines = cleanText.split('\n'); 40 const longestLineLength = Math.max(...lines.map(line => line.length)); 41 maxButtonWidth = (longestLineLength + 10) * scaledBlockWidth; // Add larger buffer to prevent wrapping 42} else { 43 // Single line: use generous bounds to ensure text is never cut off 44 maxButtonWidth = Math.max(cleanText.length * scaledBlockWidth + scaledBlockWidth * 4, $api.screen.width); 45} 46 47// Use text.box to get proper layout 48const textBounds = $api.text.box( 49 cleanText, 50 undefined, 51 maxButtonWidth, 52 screenScale 53); 54 55// Use the width from text.box 56const textWidth = textBounds.box.width; 57const padding = scaledBlockWidth * 1.5; // 1.5 character buffer 58let w = textWidth + currentHUDScrub + padding; 59``` 60 61#### Height Calculation (Lines ~8065-8070) 62```javascript 63// Use the text box height with padding - add full line height for multiline text 64const heightPadding = cleanText.includes('\n') ? 65 tf.blockHeight * screenScale + 4 : // Full line height + 4px for multiline 66 tf.blockHeight * screenScale * 0.4; // 40% for single line 67const h = textBounds.box.height + heightPadding; 68``` 69 70### Font System Properties 71- **Default Font**: font_1 (monospace, defined in `/workspaces/aesthetic-computer/system/public/aesthetic.computer/disks/common/fonts.mjs`) 72- **Glyph Width**: 6px (`tf.blockWidth` from `font_1.glyphWidth`) 73- **Glyph Height**: 10px (`tf.blockHeight` from `font_1.glyphHeight`) 74- **Screen Scale**: 1.0 (fixed, no longer responsive) 75- **Global Typeface**: `tf` initialized at line 4729 in disk.mjs as `new Typeface().load()` 76 77### Font System Architecture 78 79#### Font Definition Structure (fonts.mjs) 80```javascript 81export const font_1 = { 82 glyphHeight: 10, // Natural block height (tf.blockHeight) 83 glyphWidth: 6, // Natural block width (tf.blockWidth) 84 proportional: false, // Monospace font - fixed character width 85 // Character mappings... 86 a: "lowercase/a - 2022.1.11.16.12.07", 87 // etc... 88}; 89``` 90 91#### Typeface Class (type.mjs) 92**Location**: `/workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/type.mjs` 93 94**Key Properties**: 95- `get blockWidth()` (line 35): Returns `this.data.glyphWidth` (6px for font_1) 96- `get blockHeight()` (line 46): Returns `this.data.glyphHeight` (10px for font_1) 97- `constructor(name = "font_1")` (line 23): Defaults to font_1, loads from fonts.mjs 98 99**Important Methods**: 100- `print()` (line 319): Main text rendering method, handles proportional vs monospace 101- `load()` (line 50): Preloads glyphs, handles different font types (BDF, microtype, etc.) 102 103#### Global Font Initialization (disk.mjs) 104**Location**: Line 4729 in `/workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/disk.mjs` 105```javascript 106if (!tf) tf = await new Typeface(/*"unifont"*/).load($commonApi.net.preload); 107``` 108- `tf` is the global typeface instance used throughout the system 109- Defaults to font_1 (6px × 10px monospace bitmap font) 110- Loaded once when first piece loads 111- `tf.blockWidth` and `tf.blockHeight` are the source values for all width/height calculations 112 113#### Text Width Calculation Methods 114 115**1. Monospace (font_1 - current default)**: 116```javascript 117const textWidth = text.length * tf.blockWidth; // Simple: chars × 6px 118``` 119 120**2. Proportional (MatrixChunky8, unifont)**: 121```javascript 122// Uses character-specific advance widths from font definition 123const advances = this.data?.advances || {}; 124let totalWidth = 0; 125[...text].forEach(char => { 126 totalWidth += (advances[char] || defaultWidth) * scale; 127}); 128``` 129 130#### Font Types Available 1311. **font_1** (default): 6×10px monospace bitmap font 1322. **MatrixChunky8**: Proportional BDF font with character-specific widths 1333. **unifont**: 8×16px monospace Unicode font via BDF 1344. **microtype**: 4×5px ultra-compact font for special cases 135 136#### Text Rendering Pipeline 137``` 138write() → text.box() → typeface.print() → $.printLine() 139``` 1401. `write()`: Entry point for text rendering 1412. `text.box()`: Layout calculation using `tf.blockWidth * scale` 1423. `typeface.print()`: Character-by-character rendering with proper spacing 1434. `$.printLine()`: Low-level pixel rendering 144 145#### Critical Width Calculation Insight 146**The Issue**: `text.box()` defaults to total character count when bounds is undefined: 147```javascript 148// In text.box() at line 1745: 149if (bounds === undefined) bounds = (text.length + 2) * blockWidth; 150``` 151**For 239-char multiline text**: `(239 + 2) * 6 = 1446px` (way too wide!) 152**Should be**: Based on longest line, not total character count 153 154#### Natural Block Width Values 155- **tf.blockWidth**: 6px (from font_1.glyphWidth) 156- **tf.blockHeight**: 10px (from font_1.glyphHeight) 157- **With scaling**: `scaledBlockWidth = tf.blockWidth * screenScale` 158- **In button calc**: Used as base unit for all width/height calculations 159 160### Known Issues 161 162#### 1. Debug Output Analysis 163Recent debug showed: 164``` 165cleanText: "fade:red-blue-black-blue-red\nink (? rainbow white 0)..." 166cleanTextLength: 239 167textWidth: 1084.5 (too wide!) 168finalWidth: 1091.25 169``` 170 171**Root Cause**: When `maxButtonWidth` is undefined, text.box() defaults to `(text.length + 2) * blockWidth = (239 + 2) * 4.5 = 1084.5px` 172 173#### 2. Window Resize Behavior 174- User wants consistent button size regardless of window dimensions 175- Text should never be cut off or wrapped unexpectedly 176- Current implementation removes responsive scaling but still has sizing inconsistencies 177 178#### 3. Text.box() Function Behavior (Lines 1740-1750) 179```javascript 180if (bounds === undefined) bounds = (text.length + 2) * blockWidth; 181``` 182This is the source of the 1084px width - it uses total character count instead of longest line. 183 184### Architecture Decisions Made 185 186#### ✅ Preserved Features 187- Multiline text boundary support maintained 188- Original text rendering pipeline intact 189- KidLisp syntax highlighting integration working 190- Single-line vs multiline handling separation 191 192#### ✅ Fixed Issues 193- Removed responsive scaling (screenScale always 1.0) 194- Fixed duplicate variable declarations 195- Added proper height padding for multiline (full line height + 4px) 196- Improved color code stripping 197 198#### ❌ Remaining Issues 199- Multiline button width still inconsistent 200- Debug logging not comprehensive enough 201- Text wrapping edge cases not fully handled 202 203### User Requirements 2041. **Always show full text**: Never cut off or wrap unexpectedly 2052. **Consistent sizing**: Button dimensions shouldn't change on window resize 2063. **Optimal width**: Width should match longest line, not total character count 2074. **Multiline support**: Preserve existing multiline text layout capabilities 208 209### Suggested Next Steps for New Agent 210 211#### Immediate Priority 2121. **Fix multiline width calculation**: Ensure maxButtonWidth for multiline uses longest line length, not total text length 2132. **Implement better debug logging**: Log dimensions only when they change per frame 2143. **Test edge cases**: Very long lines, mixed content, empty lines 215 216#### Implementation Strategy 217```javascript 218// Proposed improved approach for multiline: 219if (cleanText.includes('\n')) { 220 const lines = cleanText.split('\n'); 221 const longestLineLength = Math.max(...lines.map(line => line.length)); 222 // Use actual longest line + minimal buffer, not total character count 223 maxButtonWidth = longestLineLength * scaledBlockWidth + (scaledBlockWidth * 2); 224} 225``` 226 227#### Debug Logging Strategy 228```javascript 229// Track dimensions per frame and only log changes: 230const currentDimensions = { width: textBounds.box.width, height: textBounds.box.height }; 231if (!window.lastButtonDimensions || 232 window.lastButtonDimensions.width !== currentDimensions.width || 233 window.lastButtonDimensions.height !== currentDimensions.height) { 234 // Log comprehensive button state 235 window.lastButtonDimensions = currentDimensions; 236} 237``` 238 239### Test Cases to Validate 2401. **Single-line commands**: "rect 255 255 255 128" should be ~120px wide 2412. **Multiline KidLisp**: 8-line code block should size to longest line width 2423. **Window resize**: Button dimensions should remain constant 2434. **Empty lines**: Multiline with blank lines should handle correctly 2445. **Very long lines**: Lines exceeding screen width should not wrap 245 246### Context for New Agent 247- This is aesthetic-computer, a creative coding platform 248- KidLisp is the Lisp interpreter with syntax highlighting 249- HUD shows command history with colored syntax 250- Text rendering uses monospace font with precise pixel calculations 251- User prioritizes functionality over responsive design 252 253### Font System Research Locations 254**Primary Files**: 255- `/workspaces/aesthetic-computer/system/public/aesthetic.computer/disks/common/fonts.mjs` - Font definitions and glyph mappings 256- `/workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/type.mjs` - Typeface class and text rendering logic 257- `/workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/disk.mjs` - Global font initialization and text.box() function 258 259**Key Research Points**: 2601. **Font Metrics**: Search for `glyphWidth`, `glyphHeight`, `blockWidth`, `blockHeight` in type.mjs and fonts.mjs 2612. **Text Layout**: Study `text.box()` function at disk.mjs:1733-1850 for width calculation logic 2623. **Proportional Fonts**: Check `advances` property in fonts.mjs for character-specific widths 2634. **Font Loading**: Examine `tf` initialization at disk.mjs:4729 and Typeface constructor 2645. **Width Calculations**: Look for `tf.blockWidth * scale` patterns throughout disk.mjs 265 266**Font System Debug Commands**: 267```javascript 268// In browser console: 269console.log("Font info:", tf.blockWidth, tf.blockHeight, tf.data); 270console.log("Font_1 definition:", fonts.font_1); 271console.log("Text box test:", $api.text.box("test", undefined, undefined, 1)); 272``` 273 274## Session History Summary 275- Started with buttons too wide (200px+ for short text) 276- Evolved through multiple approaches: character-based calculation, screen percentage, longest-line calculation 277- Discovered text.box() default behavior causing width inflation 278- Fixed height padding and screen scaling consistency 279- Still need optimal multiline width solution 280 281--- 282*Created: September 6, 2025* 283*Status: Ready for new agent continuation*