Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 175 additions & 33 deletions plugins/windows/src/window/floating_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,29 +100,66 @@ pub(crate) mod layout {
}
}

pub fn top_right_origin(
pub fn bottom_center_origin(
work_x: f64,
work_y: f64,
work_width: f64,
_work_height: f64,
work_height: f64,
window_width: f64,
_window_height: f64,
window_height: f64,
) -> (f64, f64) {
(
work_x + work_width - window_width - SCREEN_MARGIN,
work_y + SCREEN_MARGIN,
work_x + (work_width - window_width) / 2.0,
work_y + work_height - window_height - SCREEN_MARGIN,
)
}

pub fn resize_keep_top_right(
#[cfg(any(test, not(target_os = "macos")))]
pub fn expands_upward(y: f64, height: f64, work_y: f64, work_height: f64) -> bool {
y - work_y > work_y + work_height - y - height
}

#[cfg(any(test, not(target_os = "macos")))]
pub fn forget_moved_expansion(
expansion: &mut Option<((f64, f64, f64, f64), (f64, f64))>,
position: (f64, f64),
) {
if expansion.is_some_and(|(_, origin)| {
(position.0 - origin.0).abs() >= 0.5 || (position.1 - origin.1).abs() >= 0.5
}) {
*expansion = None;
}
}

pub fn collapse_anchor(
current: (f64, f64, f64, f64),
expansion: Option<((f64, f64, f64, f64), (f64, f64))>,
) -> (f64, f64, f64, f64) {
if let Some((compact, expanded)) = expansion {
if (current.0 - expanded.0).abs() < 0.5 && (current.1 - expanded.1).abs() < 0.5 {
return compact;
}
}
current
}

pub fn resize_anchored(
x: f64,
y: f64,
current_width: f64,
_current_height: f64,
current_height: f64,
next_width: f64,
_next_height: f64,
next_height: f64,
expands_upward: bool,
) -> (f64, f64) {
(x + current_width - next_width, y)
(
x + (current_width - next_width) / 2.0,
if expands_upward {
y + current_height - next_height
} else {
y
},
)
}

pub fn clamp_to_work_area(
Expand Down Expand Up @@ -257,12 +294,15 @@ mod platform {
use tauri_specta::Event;

use super::layout::{
clamp_to_work_area, container_size, is_expanded, resize_keep_top_right, top_right_origin,
bottom_center_origin, clamp_to_work_area, container_size, expands_upward, is_expanded,
resize_anchored,
};
use super::{FloatingBarState, WINDOW_LABEL};
use crate::Error;

static APP_HANDLE: OnceLock<tauri::AppHandle<tauri::Wry>> = OnceLock::new();
static EXPANDS_UPWARD: Mutex<bool> = Mutex::new(true);
static EXPANSION: Mutex<Option<((f64, f64, f64, f64), (f64, f64))>> = Mutex::new(None);
static LAST_STATE: Mutex<Option<FloatingBarState>> = Mutex::new(None);

pub fn set_app_handle(app: tauri::AppHandle<tauri::Wry>) {
Expand Down Expand Up @@ -362,6 +402,16 @@ mod platform {
.disable_drag_drop_handler()
.build()?;

let moved_window = window.clone();
window.on_window_event(move |event| {
if let tauri::WindowEvent::Moved(position) = event
&& let Ok(scale) = moved_window.scale_factor()
&& let Ok(mut expansion) = EXPANSION.try_lock()
{
let position = position.to_logical::<f64>(scale);
super::layout::forget_moved_expansion(&mut expansion, (position.x, position.y));
}
});
crate::window::exclude_from_capture(&window);

Ok(window)
Expand All @@ -382,27 +432,58 @@ mod platform {
let size_changed = (current_size.width - width).abs() >= 0.5
|| (current_size.height - height).abs() >= 0.5;

if size_changed {
window.set_size(Size::Logical(next_size))?;
let mut expansion = EXPANSION
.lock()
.map_err(|_| Error::PanelError("floating bar placement lock poisoned".to_string()))?;
let mut previous = (
current_position.x,
current_position.y,
current_size.width,
current_size.height,
);
if force_default_position {
*expansion = None;
} else if height < current_size.height {
previous = super::layout::collapse_anchor(previous, expansion.take());
}

let (next_x, next_y) =
if force_default_position || current_position.x == 0.0 && current_position.y == 0.0 {
default_origin(window, width, height)?
} else if size_changed {
resize_keep_top_right(
current_position.x,
let (next_x, next_y) = if force_default_position {
if let Ok(mut direction) = EXPANDS_UPWARD.lock() {
*direction = true;
}
default_origin(window, width, height)?
} else if size_changed {
let mut direction = EXPANDS_UPWARD.lock().map_err(|_| {
Error::PanelError("floating bar placement lock poisoned".to_string())
})?;
if height > current_size.height {
let monitor = window
.current_monitor()?
.or(window.app_handle().primary_monitor()?)
.ok_or(Error::MonitorNotFound)?;
let work = monitor.work_area();
let origin = work.position.to_logical::<f64>(monitor.scale_factor());
let size = work.size.to_logical::<f64>(monitor.scale_factor());
*direction = expands_upward(
current_position.y,
current_size.width,
current_size.height,
width,
height,
)
} else {
return Ok(());
};
origin.y,
size.height,
);
}
resize_anchored(
previous.0, previous.1, previous.2, previous.3, width, height, *direction,
)
} else {
return Ok(());
};

let (clamped_x, clamped_y) = clamp_origin(window, next_x, next_y, width, height)?;
if !force_default_position && height > current_size.height {
*expansion = Some((previous, (clamped_x, clamped_y)));
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}
if size_changed {
window.set_size(Size::Logical(next_size))?;
}
window.set_position(Position::Logical(LogicalPosition::new(
clamped_x, clamped_y,
)))?;
Expand All @@ -424,7 +505,7 @@ mod platform {
let work_area = monitor.work_area();
let origin = work_area.position.to_logical::<f64>(scale);
let size = work_area.size.to_logical::<f64>(scale);
Ok(top_right_origin(
Ok(bottom_center_origin(
origin.x,
origin.y,
size.width,
Expand Down Expand Up @@ -494,6 +575,18 @@ pub fn update_amplitude(amplitude: f64) -> Result<(), Error> {
mod tests {
use super::layout;

#[test]
fn dragging_away_and_back_does_not_restore_the_old_compact_frame() {
let mut expansion = Some(((10.0, 20.0, 111.0, 67.0), (0.0, 20.0)));
layout::forget_moved_expansion(&mut expansion, (0.0, 20.0));
assert!(expansion.is_some());
layout::forget_moved_expansion(&mut expansion, (100.0, 20.0));
layout::forget_moved_expansion(&mut expansion, (0.0, 20.0));
assert!(expansion.is_none());
let current = (0.0, 20.0, 368.0, 459.0);
assert_eq!(layout::collapse_anchor(current, expansion), current);
}

#[test]
fn sizes_the_compact_and_expanded_windows() {
assert_eq!(layout::container_size(false, false), (84.0, 67.0));
Expand All @@ -502,18 +595,67 @@ mod tests {
}

#[test]
fn pins_the_default_origin_to_the_work_area_top_right() {
fn collapse_restores_position_after_expansion_was_clamped() {
let compact = (1801.0, 1005.0, 111.0, 67.0);
let expanded = layout::resize_anchored(
compact.0, compact.1, compact.2, compact.3, 368.0, 459.0, true,
);
let clamped = layout::clamp_to_work_area(
expanded.0, expanded.1, 368.0, 459.0, 0.0, 0.0, 1920.0, 1080.0,
);
let anchor = layout::collapse_anchor(
(clamped.0, clamped.1, 368.0, 459.0),
Some((compact, clamped)),
);
assert_eq!(
layout::resize_anchored(anchor.0, anchor.1, anchor.2, anchor.3, 111.0, 67.0, true),
(compact.0, compact.1)
);
let moved = (clamped.0 - 100.0, clamped.1, 368.0, 459.0);
assert_eq!(
layout::collapse_anchor(moved, Some((compact, clamped))),
moved
);
}

#[test]
fn pins_the_default_origin_to_the_work_area_bottom_center() {
assert_eq!(
layout::top_right_origin(0.0, 0.0, 1920.0, 1080.0, 111.0, 67.0),
(1801.0, 8.0)
layout::bottom_center_origin(0.0, 0.0, 1920.0, 1080.0, 111.0, 67.0),
(904.5, 1005.0)
);
}

#[test]
fn keeps_the_top_right_anchor_when_resizing() {
fn keeps_the_top_center_anchor_when_expanding_downward() {
assert_eq!(
layout::resize_anchored(1801.0, 8.0, 111.0, 67.0, 368.0, 459.0, false),
(1672.5, 8.0)
);
}

#[test]
fn keeps_the_bottom_center_anchor_through_expansion_and_collapse() {
let (x, y) = layout::resize_anchored(904.5, 1005.0, 111.0, 67.0, 368.0, 459.0, true);
assert_eq!((x, y), (776.0, 613.0));
assert_eq!(
layout::resize_anchored(x, y, 368.0, 459.0, 111.0, 67.0, true),
(904.5, 1005.0)
);
}

#[test]
fn chooses_the_side_with_more_space_on_an_offset_monitor() {
assert!(layout::expands_upward(1005.0, 67.0, 40.0, 1040.0));
assert!(!layout::expands_upward(48.0, 67.0, 40.0, 1040.0));
assert!(!layout::expands_upward(0.0, 67.0, 0.0, 1080.0));
}

#[test]
fn centers_on_an_offset_monitor() {
assert_eq!(
layout::resize_keep_top_right(1801.0, 8.0, 111.0, 67.0, 368.0, 459.0),
(1544.0, 8.0)
layout::bottom_center_origin(-1920.0, 40.0, 1920.0, 1040.0, 111.0, 67.0),
(-1015.5, 1005.0)
);
}

Expand Down
63 changes: 21 additions & 42 deletions plugins/windows/swift-lib/src/FloatingBarManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ final class FloatingBarManager {
static let shared = FloatingBarManager()

private var panel: NSPanel?
private var expandsUpward = true
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
private var expansion: (compact: NSRect, expanded: NSRect)?
private let model = FloatingBarViewModel()
private let settingsModel = FloatingOverlaySettingsModel.shared
private let placement = FloatingPanelPositionController()
Expand Down Expand Up @@ -71,6 +73,8 @@ final class FloatingBarManager {
return
}

expandsUpward = true
expansion = nil
FloatingBarFonts.register()

let panel = createPanel()
Expand All @@ -81,6 +85,7 @@ final class FloatingBarManager {
panelOrigin: { [weak self] in self?.panel?.frame.origin },
movePanel: { [weak self] origin in
guard let self, let panel = self.panel else { return }
self.expansion = nil
self.placement.moveByUserDrag(
panel,
to: origin,
Expand Down Expand Up @@ -191,8 +196,8 @@ final class FloatingBarManager {
followsPointer: false
) { screen, size in
let frame = screen.visibleFrame
let x = frame.maxX - size.width - FloatingBarLayout.screenMargin
let y = frame.maxY - size.height - FloatingBarLayout.screenMargin
let x = frame.midX - size.width / 2
let y = frame.minY + FloatingBarLayout.screenMargin
return NSPoint(x: x, y: y)
}
}
Expand All @@ -207,19 +212,19 @@ final class FloatingBarManager {
panel.minSize = size
guard previousSize != size else { return false }

let previousLayout =
layout(matching: previousSize)
?? FloatingBarWindowLayout(
isExpanded: !nextLayout.isExpanded,
showsExpand: model.liveCaptionToggleVisible)
let previousAnchorOffset = controlAnchorOffset(for: previousLayout)
let workArea = (panel.screen ?? NSScreen.main)?.visibleFrame ?? panel.frame
if size.height > previousSize.height {
expandsUpward = FloatingBarPlacement.expandsUpward(frame: panel.frame, workArea: workArea)
}
let nextAnchorOffset = controlAnchorOffset(for: nextLayout)
let anchor = placement.anchorPoint(for: panel, offset: previousAnchorOffset)
let frame = NSRect(
x: anchor.x - nextAnchorOffset.x,
y: anchor.y - nextAnchorOffset.y,
width: size.width,
height: size.height)
let frame = FloatingBarPlacement.resizedFrame(
panel.frame, size: size, workArea: workArea, expandsUpward: expandsUpward,
expansion: expansion)
if size.height > previousSize.height {
expansion = (panel.frame, frame)
} else {
expansion = nil
}
placement.setFrame(
panel,
to: frame,
Expand Down Expand Up @@ -252,35 +257,9 @@ final class FloatingBarManager {
)
}

private func layout(matching size: NSSize) -> FloatingBarWindowLayout? {
let candidates = [
FloatingBarWindowLayout(isExpanded: true, showsExpand: true),
FloatingBarWindowLayout(isExpanded: true, showsExpand: false),
FloatingBarWindowLayout(isExpanded: false, showsExpand: true),
FloatingBarWindowLayout(isExpanded: false, showsExpand: false),
]

return candidates.first { candidate in
let candidateSize = self.size(for: candidate)
return abs(candidateSize.width - size.width) < 0.5
&& abs(candidateSize.height - size.height) < 0.5
}
}

private func controlAnchorOffset(for layout: FloatingBarWindowLayout) -> NSPoint {
if layout.isExpanded {
return NSPoint(
x: FloatingBarLayout.inset + FloatingBarLayout.expandedWidth
- FloatingBarLayout.compactHorizontalPadding,
y: FloatingBarLayout.inset + FloatingBarLayout.expandedHeight
)
}

return NSPoint(
x: FloatingBarLayout.inset + FloatingBarLayout.compactHorizontalPadding
+ FloatingBarLayout.compactControlsWidth(showsExpand: layout.showsExpand),
y: FloatingBarLayout.inset + FloatingBarLayout.compactHeight
)
let size = size(for: layout)
return NSPoint(x: size.width / 2, y: expandsUpward ? 0 : size.height)
}

private func startObservingDisplayChanges() {
Expand Down
Loading
Loading