4.7 KiB
4.7 KiB
MiniRecorder Morphing Window Requirements
🎯 Core Requirements
Visual Behavior
- Visualizer Always Centered: The audio visualizer must remain in the exact same position throughout all animations
- Fixed Window Position: The MiniRecorderView window position should never change during morphing
- Horizontal-Only Expansion: Only width should change, height remains constant at 34px
- Hover-Triggered: Expansion should occur on hover, collapse on hover exit
Layout States
Compact State (Default)
- Width: ~70px (just enough for visualizer + minimal padding)
- Content: Audio visualizer/status display only
- Buttons: Hidden/not rendered
- Centering: Visualizer perfectly centered in compact window
Expanded State (On Hover)
- Width: ~160px (current full width)
- Content: RecorderPromptButton + Visualizer + RecorderPowerModeButton
- Buttons: Fully visible and functional
- Centering: Visualizer remains in same absolute screen position
🔧 Technical Constraints
Window Positioning
- Window's center point must remain constant
- When expanding from 70px → 160px, window should grow equally left and right (45px each side)
NSRectcalculations must account for center-anchored growth
Animation Requirements
- Smooth spring animation (~0.3-0.4s duration)
- Buttons should appear/disappear gracefully (fade in/out or slide from edges)
- No jarring movements or position jumps
- Reversible animation (expand ↔ collapse)
SwiftUI Layout Considerations
- HStack with conditional button rendering
- Visualizer maintains
frame(maxWidth: .infinity)behavior in both states - Proper spacing and padding calculations for both states
💡 Recommended Implementation Strategy
Approach: Center-Anchored Window Growth with Sliding Buttons
Window Management (MiniRecorderPanel)
Compact Window Rect:
- Width: 70px
- Height: 34px
- X: screenCenter - 35px
- Y: current Y position
Expanded Window Rect:
- Width: 160px
- Height: 34px
- X: screenCenter - 80px // Grows left by 45px
- Y: same Y position // Grows right by 45px
SwiftUI Layout (MiniRecorderView)
HStack(spacing: 0) {
// Left button - slides in from left edge
if isExpanded {
RecorderPromptButton()
.transition(.move(edge: .leading).combined(with: .opacity))
}
// Visualizer - always centered, never moves
statusView
.frame(width: visualizerWidth) // Fixed width
// Right button - slides in from right edge
if isExpanded {
RecorderPowerModeButton()
.transition(.move(edge: .trailing).combined(with: .opacity))
}
}
State Management
@State private var isExpanded = false@State private var isHovering = false- Hover detection with debouncing for smooth UX
- Window resize triggered by state changes
🎨 Animation Sequence
Expansion (Compact → Expanded)
- Trigger: Mouse enters window bounds
- Window: Animate width 70px → 160px (center-anchored)
- Buttons: Slide in from edges with fade-in
- Duration: ~0.3s with spring easing
- Result: Visualizer appears unmoved, buttons visible
Collapse (Expanded → Compact)
- Trigger: Mouse leaves window bounds (with delay)
- Buttons: Slide out to edges with fade-out
- Window: Animate width 160px → 70px (center-anchored)
- Duration: ~0.3s with spring easing
- Result: Back to visualizer-only, same position
🚫 Critical Don'ts
- Never move the visualizer's absolute screen position
- Never change the window's center point
- Never animate height or vertical position
- Never show jarring button pop-ins (use smooth transitions)
- Never let buttons overlap the visualizer during animation
📐 Calculations
Visualizer Dimensions
- AudioVisualizer: 12 bars × 3px + 11 × 2px spacing = 58px width
- With padding: ~70px total compact width
Button Dimensions
- Each button: ~24px width + padding
- Total button space: ~90px (45px per side)
- Total expanded width: 70px + 90px = 160px
Center-Anchored Growth
Compact X position: screenCenterX - 35px
Expanded X position: screenCenterX - 80px
Growth: 45px left + 45px right = 90px total
🎯 Success Criteria
✅ Visualizer never visually moves during any animation
✅ Window position anchor point remains constant
✅ Smooth hover-based expansion/collapse
✅ Buttons appear/disappear gracefully
✅ No layout jumps or glitches
✅ Maintains current functionality in expanded state
This approach ensures the visualizer appears completely stationary while the window "grows around it" to reveal the buttons, creating a seamless morphing effect.