|
| 1 | +#!/bin/bash |
| 2 | +# |
| 3 | +# Debug script for scrollbar layout issues |
| 4 | +# |
| 5 | +# Usage: |
| 6 | +# 1. Start the application with debug server: AZUL_DEBUG=8765 cargo run --release --example scrolling |
| 7 | +# 2. Run this script: ./scripts/debug_scrollbar_layout.sh |
| 8 | +# 3. Check the output files in target/debug_output/ |
| 9 | +# |
| 10 | + |
| 11 | +set -e |
| 12 | + |
| 13 | +PORT="${AZUL_DEBUG_PORT:-8765}" |
| 14 | +API="http://localhost:$PORT" |
| 15 | +OUTPUT_DIR="target/debug_output" |
| 16 | + |
| 17 | +# Create output directory |
| 18 | +mkdir -p "$OUTPUT_DIR" |
| 19 | + |
| 20 | +# Colors for output |
| 21 | +RED='\033[0;31m' |
| 22 | +GREEN='\033[0;32m' |
| 23 | +YELLOW='\033[1;33m' |
| 24 | +BLUE='\033[0;34m' |
| 25 | +NC='\033[0m' # No Color |
| 26 | + |
| 27 | +echo -e "${BLUE}=== Azul Scrollbar Layout Debug Script ===${NC}" |
| 28 | +echo "API endpoint: $API" |
| 29 | +echo "Output directory: $OUTPUT_DIR" |
| 30 | +echo "" |
| 31 | + |
| 32 | +# Check if server is running |
| 33 | +echo -e "${YELLOW}Checking debug server...${NC}" |
| 34 | +if ! curl -s "$API/" > /dev/null 2>&1; then |
| 35 | + echo -e "${RED}Error: Debug server not running on port $PORT${NC}" |
| 36 | + echo "Start your app with: AZUL_DEBUG=$PORT cargo run --release --example scrolling" |
| 37 | + exit 1 |
| 38 | +fi |
| 39 | +echo -e "${GREEN}Debug server is running${NC}" |
| 40 | +echo "" |
| 41 | + |
| 42 | +# Get display list |
| 43 | +echo -e "${YELLOW}Fetching display list...${NC}" |
| 44 | +curl -s -X POST "$API/" -d '{"op":"get_display_list"}' > "$OUTPUT_DIR/display_list.json" |
| 45 | +echo "Saved to $OUTPUT_DIR/display_list.json" |
| 46 | + |
| 47 | +# Get layout tree |
| 48 | +echo -e "${YELLOW}Fetching layout tree...${NC}" |
| 49 | +curl -s -X POST "$API/" -d '{"op":"get_layout_tree"}' > "$OUTPUT_DIR/layout_tree.json" |
| 50 | +echo "Saved to $OUTPUT_DIR/layout_tree.json" |
| 51 | + |
| 52 | +# Get scroll states |
| 53 | +echo -e "${YELLOW}Fetching scroll states...${NC}" |
| 54 | +curl -s -X POST "$API/" -d '{"op":"get_scroll_states"}' > "$OUTPUT_DIR/scroll_states.json" |
| 55 | +echo "Saved to $OUTPUT_DIR/scroll_states.json" |
| 56 | + |
| 57 | +# Get scrollable nodes |
| 58 | +echo -e "${YELLOW}Fetching scrollable nodes...${NC}" |
| 59 | +curl -s -X POST "$API/" -d '{"op":"get_scrollable_nodes"}' > "$OUTPUT_DIR/scrollable_nodes.json" |
| 60 | +echo "Saved to $OUTPUT_DIR/scrollable_nodes.json" |
| 61 | + |
| 62 | +# Get logs |
| 63 | +echo -e "${YELLOW}Fetching debug logs...${NC}" |
| 64 | +curl -s -X POST "$API/" -d '{"op":"get_logs"}' > "$OUTPUT_DIR/logs.json" |
| 65 | +echo "Saved to $OUTPUT_DIR/logs.json" |
| 66 | + |
| 67 | +echo "" |
| 68 | +echo -e "${BLUE}=== Analysis ===${NC}" |
| 69 | + |
| 70 | +# Analyze scroll frame clip |
| 71 | +echo "" |
| 72 | +echo -e "${YELLOW}Scroll Frame Clip:${NC}" |
| 73 | +cat "$OUTPUT_DIR/display_list.json" | python3 -c " |
| 74 | +import sys, json |
| 75 | +d = json.load(sys.stdin) |
| 76 | +analysis = d.get('data', {}).get('value', {}).get('clip_analysis', {}) |
| 77 | +for op in analysis.get('operations', []): |
| 78 | + if op.get('op') == 'PushScrollFrame': |
| 79 | + b = op.get('bounds', {}) |
| 80 | + cs = op.get('content_size', {}) |
| 81 | + print(f' clip bounds: x={b.get(\"x\")}, y={b.get(\"y\")}, w={b.get(\"width\")}, h={b.get(\"height\")}') |
| 82 | + print(f' content_size: w={cs.get(\"width\")}, h={cs.get(\"height\")}') |
| 83 | + print(f' scroll_id: {op.get(\"scroll_id\")}') |
| 84 | +" |
| 85 | + |
| 86 | +# Analyze child positions |
| 87 | +echo "" |
| 88 | +echo -e "${YELLOW}Child Rects (inside scroll frame):${NC}" |
| 89 | +cat "$OUTPUT_DIR/display_list.json" | python3 -c " |
| 90 | +import sys, json |
| 91 | +d = json.load(sys.stdin) |
| 92 | +items = d.get('data', {}).get('value', {}).get('items', []) |
| 93 | +for item in items: |
| 94 | + if item.get('scroll_depth') == 1 and item.get('type') == 'rect': |
| 95 | + print(f' x={item[\"x\"]:6.1f} y={item[\"y\"]:6.1f} w={item[\"width\"]:6.1f} h={item[\"height\"]:6.1f} {item[\"color\"]}') |
| 96 | +" |
| 97 | + |
| 98 | +# Analyze scrollbars |
| 99 | +echo "" |
| 100 | +echo -e "${YELLOW}Scrollbars:${NC}" |
| 101 | +cat "$OUTPUT_DIR/display_list.json" | python3 -c " |
| 102 | +import sys, json |
| 103 | +d = json.load(sys.stdin) |
| 104 | +items = d.get('data', {}).get('value', {}).get('items', []) |
| 105 | +for item in items: |
| 106 | + t = item.get('type', '') |
| 107 | + if 'scrollbar' in t: |
| 108 | + print(f' {t}:') |
| 109 | + print(f' position: x={item.get(\"x\")}, y={item.get(\"y\")}') |
| 110 | + print(f' size: w={item.get(\"width\")}, h={item.get(\"height\")}') |
| 111 | +" |
| 112 | + |
| 113 | +# Check for layout issues |
| 114 | +echo "" |
| 115 | +echo -e "${YELLOW}Layout Issue Check:${NC}" |
| 116 | +cat "$OUTPUT_DIR/display_list.json" | python3 -c " |
| 117 | +import sys, json |
| 118 | +d = json.load(sys.stdin) |
| 119 | +items = d.get('data', {}).get('value', {}).get('items', []) |
| 120 | +analysis = d.get('data', {}).get('value', {}).get('clip_analysis', {}) |
| 121 | +
|
| 122 | +# Find scroll frame clip |
| 123 | +clip_x, clip_y, clip_w, clip_h = 0, 0, 0, 0 |
| 124 | +for op in analysis.get('operations', []): |
| 125 | + if op.get('op') == 'PushScrollFrame': |
| 126 | + b = op.get('bounds', {}) |
| 127 | + clip_x, clip_y = b.get('x', 0), b.get('y', 0) |
| 128 | + clip_w, clip_h = b.get('width', 0), b.get('height', 0) |
| 129 | +
|
| 130 | +# Find vertical scrollbar |
| 131 | +scrollbar_x = None |
| 132 | +for item in items: |
| 133 | + if 'scrollbar_vertical' in item.get('type', ''): |
| 134 | + scrollbar_x = item.get('x', 0) |
| 135 | +
|
| 136 | +# Check child positions |
| 137 | +issues = [] |
| 138 | +for item in items: |
| 139 | + if item.get('scroll_depth') == 1 and item.get('type') == 'rect': |
| 140 | + child_x = item.get('x', 0) |
| 141 | + child_w = item.get('width', 0) |
| 142 | + child_end_x = child_x + child_w |
| 143 | + clip_end_x = clip_x + clip_w |
| 144 | + |
| 145 | + if scrollbar_x and child_end_x > scrollbar_x: |
| 146 | + overlap = child_end_x - scrollbar_x |
| 147 | + issues.append(f'Child rect overlaps scrollbar by {overlap:.1f}px (child ends at x={child_end_x:.1f}, scrollbar at x={scrollbar_x:.1f})') |
| 148 | + break |
| 149 | + elif child_end_x > clip_end_x: |
| 150 | + overflow = child_end_x - clip_end_x |
| 151 | + issues.append(f'Child rect overflows clip by {overflow:.1f}px (child ends at x={child_end_x:.1f}, clip ends at x={clip_end_x:.1f})') |
| 152 | + break |
| 153 | +
|
| 154 | +if issues: |
| 155 | + print(' ⚠️ Issues found:') |
| 156 | + for issue in issues: |
| 157 | + print(f' - {issue}') |
| 158 | +else: |
| 159 | + print(' ✅ No layout issues detected') |
| 160 | +" |
| 161 | + |
| 162 | +# Filter logs for scrollbar-related entries |
| 163 | +echo "" |
| 164 | +echo -e "${YELLOW}Scrollbar-related logs:${NC}" |
| 165 | +cat "$OUTPUT_DIR/logs.json" | python3 -c " |
| 166 | +import sys, json |
| 167 | +d = json.load(sys.stdin) |
| 168 | +logs = d.get('data', {}).get('logs', []) |
| 169 | +scrollbar_logs = [l for l in logs if 'scroll' in l.get('message', '').lower() or 'reflow' in l.get('message', '').lower()] |
| 170 | +if scrollbar_logs: |
| 171 | + for log in scrollbar_logs[-10:]: |
| 172 | + print(f' [{log.get(\"level\", \"\")}] {log.get(\"message\", \"\")}') |
| 173 | +else: |
| 174 | + print(' (no scrollbar-related logs found)') |
| 175 | +" 2>/dev/null || echo " (logs not available or empty)" |
| 176 | + |
| 177 | +echo "" |
| 178 | +echo -e "${GREEN}Debug data saved to $OUTPUT_DIR/${NC}" |
| 179 | +echo "" |
| 180 | +echo "Useful commands:" |
| 181 | +echo " # View full display list:" |
| 182 | +echo " cat $OUTPUT_DIR/display_list.json | python3 -m json.tool | less" |
| 183 | +echo "" |
| 184 | +echo " # Filter for specific item types:" |
| 185 | +echo " cat $OUTPUT_DIR/display_list.json | jq '.data.value.items[] | select(.type | contains(\"scroll\"))'" |
| 186 | +echo "" |
| 187 | +echo " # View layout tree:" |
| 188 | +echo " cat $OUTPUT_DIR/layout_tree.json | python3 -m json.tool | less" |
0 commit comments