Toast and pill windows must appear correctly positioned relative to each other, and both must stay on-screen across different monitor sizes. This is trickier than it sounds because:
- Two independent windows - They're created separately but positioned together
- Screen coordinates - X/Y positions change based on monitor DPI, multiple monitors, etc.
- Centered layout - Both windows center horizontally at bottom of screen
- No overlap - Toast sits above pill with a precise gap
SCREEN
┌───────────────────────────────────┐
│ │
│ │
│ ┌─────────────────────┐ │ ← Toast: 280px wide, 32px tall
│ │ Press ESC again │ │ "toast_x" = 20, "toast_y" = 310
│ │ to cancel │ │
│ └─────────────────────┘ │
│ ▲ │
│ │ 6px (gap) │
│ │ │
│ ┌───────┐ │ ← Pill: 80px wide, 40px tall
│ │ ●●● │ │ "pill_x" = 125, "pill_y" = 360
│ └───────┘ │
│ │
│ │
└───────────────────────────────────┘
0 1920
Screen width: 1920px
Screen height: 1080px
toast_width = 280.0 px (width of feedback message box)
toast_height = 32.0 px (height of feedback message box)
pill_width = 80.0 px (width of recording pill)
pill_height = 40.0 px (height of recording pill)
gap = 6.0 px (vertical space between windows)
// In window_manager.rs::calculate_center_position()
// Assuming 1920x1080 screen
let screen_width = 1920.0;
let screen_height = 1080.0;
// Pill is always at center horizontally, bottom with margin
let pill_x = (screen_width - pill_width) / 2.0
= (1920.0 - 80.0) / 2.0
= 1840.0 / 2.0
= 920.0 px
let pill_y = screen_height - pill_height - 80.0 // 80px margin from bottom
= 1080.0 - 40.0 - 80.0
= 960.0 px
Result: pill at (920, 960)// In lib.rs lines 1942-1943
// Toast must be:
// 1. Centered horizontally (same x as pill center)
// 2. Directly above pill with gap
// Toast left edge calculation:
// Toast is 280px wide, pill is 80px wide
// We want toast centered on pill
let toast_x = pill_x + (pill_width - toast_width) / 2.0
= 920.0 + (80.0 - 280.0) / 2.0
= 920.0 + (-200.0) / 2.0
= 920.0 + (-100.0)
= 820.0 px
// Toast top edge calculation:
// Toast goes above pill, with gap between them
let toast_y = pill_y - toast_height - gap
= 960.0 - 32.0 - 6.0
= 922.0 px
Result: toast at (820, 922)Pill: x=920, y=960, width=80, height=40
├─────────────────┤ 80px
920 1000
Toast: x=820, y=922, width=280, height=32
├───────────────────────────────────┤ 280px
820 1100
Alignment check:
- Pill center: 920 + (80/2) = 960
- Toast center: 820 + (280/2) = 960 ✓ ALIGNED
Vertical gap:
- Pill top: 960
- Toast bottom: 922 + 32 = 954
- Gap: 960 - 954 = 6px ✓ CORRECT
toast_x = 432 + (80 - 280) / 2 = 432 - 100 = 332
toast_y = 688 - 32 - 6 = 650
Toast occupies: x=[332, 612], y=[650, 682]
Screen width: 1024
Right edge: 612 < 1024 ✓ FITS
But on ultra-wide 3440x1440:
toast_x = 1720 - 100 = 1620
Toast occupies: x=[1620, 1900], y=[1350, 1382]
Right edge: 1900 < 3440 ✓ FITS
If someone moves the pill to (200, 500):
OLD toast_x = 920 + (80 - 280) / 2 = 820
NEW toast_x = 200 + (80 - 280) / 2 = 100
Toast would be misaligned! ❌
That's why the positioning calculation is hardcoded based on pill position, not fixed constants.
// Calculate pill position (fixed: center-bottom)
let (pos_x, pos_y) = calculate_center_position(); // e.g., (920, 960)
// Create pill window
let pill_builder = WebviewWindowBuilder::new(app, "pill", ...)
.inner_size(80.0, 40.0)
.position(pos_x, pos_y) // (920, 960)
// Calculate toast position based on pill position
let toast_width = 280.0;
let toast_height = 32.0;
let pill_width = 80.0;
let gap = 6.0;
let toast_x = pos_x + (pill_width - toast_width) / 2.0; // 820
let toast_y = pos_y - toast_height - gap; // 922
// Create toast window
let toast_builder = WebviewWindowBuilder::new(app, "toast", ...)
.inner_size(toast_width, toast_height)
.position(toast_x, toast_y) // (820, 922)When user presses hotkey:
Backend: ESC detected → handle_escape_key()
Backend: Call pill_toast("Press ESC again to cancel", 1200)
Backend: app.get_webview_window("toast")?.show() // Toast already positioned at (820, 922)
Frontend: Receive "toast" event → render message
Result: Toast appears above pill
Backend: After 1.2 seconds or on second ESC
Backend: app.get_webview_window("toast")?.hide()
Result: Toast disappears, pill stays visible
Currently pill position is hardcoded:
// window_manager.rs
fn calculate_center_position() -> (f64, f64) {
let screen_width = 1920.0; // ASSUMES 1920px screen
let screen_height = 1080.0; // ASSUMES 1080px screen
let pill_x = (screen_width - pill_width) / 2.0;
let pill_y = screen_height - pill_height - 80.0;
(pill_x, pill_y)
}Problem: This doesn't account for:
- DPI scaling (retina displays)
- Multiple monitors
- Actual screen dimensions at runtime
Current Status: This is a known limitation but works for typical cases.
If you change these dimensions:
let toast_width = 280.0; // Can't be smaller without cutting off message
let toast_height = 32.0; // Must fit one line of text + padding
let pill_width = 80.0; // Must fit the pill visualization
let gap = 6.0; // Visual spacing preferenceThe centering calculation automatically adjusts:
let toast_x = pos_x + (pill_width - toast_width) / 2.0;So if you increase toast_width to 320:
toast_x = 920 + (80 - 320) / 2.0 = 920 - 120 = 800
// Toast shifts 20px further left to maintain centering
On 1024px screen with 280px toast:
pill_x = (1024 - 80) / 2 = 472
toast_x = 472 + (80 - 280) / 2 = 472 - 100 = 372
Toast bounds: [372, 652]
Screen width: 1024
Fits: 652 < 1024 ✓
But with 1280px toast on 1024px screen:
toast_x = 372
Toast bounds: [372, 1652]
Exceeds: 1652 > 1024 ❌
Solution: Clamp toast_x or reduce toast width
Minimum space needed:
pill_y (from bottom) = 80px
pill_height = 40px
gap = 6px
toast_height = 32px
minimum_screen_height = 80 + 40 + 6 + 32 = 158px
But with typical margins:
minimum = ~440px (to look decent)
/// TOAST WINDOW POSITIONING ALGORITHM
///
/// Toast is positioned above the pill window, both centered horizontally.
/// This calculation runs once at app startup and again whenever pill position changes.
///
/// LAYOUT:
/// ┌─────────────────────────────────┐
/// │ TOAST (280×32) │ y = pill_y - toast_height - gap
/// │ "Press ESC again to cancel" │
/// └─────────────────────────────────┘
/// ▲ 6px gap
/// ┌───────┐
/// │ ●●● │ PILL (80×40)
/// │ PILL │ y = center-bottom of screen
/// └───────┘
///
/// CENTERING MATH:
/// The toast (280px) is wider than the pill (80px).
/// To center toast on pill:
/// toast_x = pill_x + (pill_width - toast_width) / 2.0
///
/// With pill_x = 920, this gives:
/// toast_x = 920 + (80 - 280) / 2.0 = 920 - 100 = 820
///
/// IMPORTANT: These dimensions are interdependent:
/// - If pill_width changes, toast_x calculation must update
/// - If toast_width changes, left edge shifts
/// - If pill position changes, toast must move with it
///
/// CONSTRAINTS:
/// - Toast width (280px): Must fit "Press ESC again to cancel" message
/// - Toast height (32px): Must fit one line of text with padding
/// - Gap (6px): Visual spacing between windows
/// - Pill width (80px): UI design constant
///
/// FUTURE IMPROVEMENTS:
/// - Use runtime screen dimensions instead of hardcoded values
/// - Add safe-zone detection to prevent toast clipping
/// - Calculate position when pill is repositioned (if allowed)
/// Calculate center-bottom position for pill window
///
/// Pill is always positioned:
/// - Horizontally: Center of screen
/// - Vertically: 80px from bottom (above dock/taskbar)
///
/// Note: Toast window positioning is tightly coupled to this.
/// If you change this, update toast positioning in lib.rs:1942-1943
fn calculate_center_position() -> (f64, f64) {
// TODO: Use actual screen dimensions at runtime
// Currently hardcoded to 1920x1080, doesn't account for DPI scaling
...
}- 1024×768 display: Toast doesn't clip
- 4K display (3840×2160): Toast positioned correctly
- Ultra-wide (3440×1440): Toast centered
- Retina MacBook (2880×1800): Check DPI scaling
- Tablet landscape (2560×1440): Toast visible
- Change toast_width to 320px: Still centered
- Change pill_width to 100px: Toast re-centers
- Multiple monitors: Pill appears on primary screen
Toast positioning is a fixed calculation, not magic:
1. Pill is always at center-bottom (calculated from screen size)
2. Toast is positioned above pill with gap
3. Toast is centered on pill using: x = pill_x + (pill_width - toast_width) / 2.0
4. These happen at app startup and don't change at runtime
The risk: If pill position or dimensions change, toast will be misaligned. That's why we need documentation and tests.