140 lines
4.7 KiB
Markdown
140 lines
4.7 KiB
Markdown
# MiniRecorder Morphing Window Requirements
|
||
|
||
## 🎯 Core Requirements
|
||
|
||
### **Visual Behavior**
|
||
1. **Visualizer Always Centered**: The audio visualizer must remain in the exact same position throughout all animations
|
||
2. **Fixed Window Position**: The MiniRecorderView window position should never change during morphing
|
||
3. **Horizontal-Only Expansion**: Only width should change, height remains constant at 34px
|
||
4. **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)
|
||
- `NSRect` calculations 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)**
|
||
1. **Trigger**: Mouse enters window bounds
|
||
2. **Window**: Animate width 70px → 160px (center-anchored)
|
||
3. **Buttons**: Slide in from edges with fade-in
|
||
4. **Duration**: ~0.3s with spring easing
|
||
5. **Result**: Visualizer appears unmoved, buttons visible
|
||
|
||
### **Collapse (Expanded → Compact)**
|
||
1. **Trigger**: Mouse leaves window bounds (with delay)
|
||
2. **Buttons**: Slide out to edges with fade-out
|
||
3. **Window**: Animate width 160px → 70px (center-anchored)
|
||
4. **Duration**: ~0.3s with spring easing
|
||
5. **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. |