Files
tauri-meeting/src-tauri/src/window/create_float_list_window.rs
O昵称重要吗O bfaad97f61 feat(websocket): 优化投屏状态管理并新增全员禁麦功能
- 重构 Redis 投屏状态存储,统一使用 screenShareOwnerUid 字段
- 移除冗余的 screenShareUid 字段,简化房间状态数据结构
- 新增 client_mute_all_students 消息类型支持全员禁麦功能
- 实现创建者权限验证的全员禁麦逻辑,排除老师角色用户
- 优化 Tauri 桌面应用悬浮窗创建接口,支持状态参数传递
- 添加悬浮窗关闭功能及教师端静音状态同步机制
- 修复学生端界面中投屏状态变量命名不一致问题
- 统一按钮样式规范,提升界面组件一致性
2026-03-15 18:21:15 +08:00

182 lines
7.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::sync::Arc;
use crate::{
constants::common::*,
window::{
WindowLabel,
derive::FloatListInfo,
util::{get_monitor_for_window, show_windows_by_label},
},
};
use tauri::{AppHandle, LogicalPosition, LogicalSize, Runtime, WebviewUrl, WebviewWindowBuilder, utils::config::BackgroundThrottlingPolicy};
/// 创建悬浮列表窗口(内部实现)
pub async fn create_float_list_window_impl<R: Runtime>(app: Arc<AppHandle<R>>, is_teacher_muted: bool) -> Result<(), String> {
if show_windows_by_label(app.as_ref(), WindowLabel::FLOAT_LIST) {
return Ok(());
}
// 悬浮列表尺寸
let float_info = calculate_float_info_sync();
// 获取屏幕尺寸,优先为 main 窗口所在屏幕,主窗口没有打开时,才找主屏幕
let monitor = get_monitor_for_window(app.as_ref())?;
// 获取屏幕缩放因子
let scale_factor = monitor.scale_factor();
let page_size: LogicalSize<f64> = monitor.work_area().size.to_logical(scale_factor);
let screen_width = page_size.width;
let screen_height = page_size.height;
// 构建 URL 时附加状态参数
let url = format!("floating-list-window.html?isTeacherMuted={}", is_teacher_muted);
let label = WindowLabel::FLOAT_LIST.as_ref();
// 创建悬浮球窗口,直接在初始化时设置好位置和尺寸
let window = WebviewWindowBuilder::new(app.as_ref(), label, WebviewUrl::App(url.into()))
.title("悬浮列表")
.inner_size(float_info.width as f64, float_info.height as f64)
.max_inner_size(float_info.width as f64, float_info.height as f64)
.min_inner_size(float_info.width as f64, float_info.height as f64)
.position(screen_width - float_info.width as f64, 0.0)
.transparent(true)
.visible_on_all_workspaces(true)
.background_throttling(BackgroundThrottlingPolicy::Disabled)
.decorations(false)
.shadow(false)
.focusable(true)
.focused(false)
.accept_first_mouse(true)
.skip_taskbar(true)
.always_on_top(true)
.devtools(true)
.build();
let window = match window {
Ok(w) => w,
Err(e) => {
log::error!("{} {}", MSG_FAILED_CREATE_FLOATING_LIST, e);
return Err(format!("{} {}", MSG_FAILED_CREATE_FLOATING_LIST, e));
}
};
#[cfg(not(target_os = "linux"))]
{
let _ = window.set_resizable(false);
}
#[cfg(target_os = "windows")]
{
let _ = window.set_size(LogicalSize::new(float_info.width as f64, float_info.height as f64));
}
// 窗口显示后等待 200 毫秒再执行动画
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
let _ = animate_to_bottom_right(window, screen_width as i32, screen_height as i32).await;
});
Ok(())
}
/// 关闭悬浮列表窗口(内部实现)
pub fn close_float_list_window_impl<R: Runtime>(app: Arc<AppHandle<R>>) -> Result<(), String> {
use tauri::Manager;
// 尝试获取悬浮列表窗口
if let Some(window) = app.get_webview_window(WindowLabel::FLOAT_LIST.as_ref()) {
// 关闭窗口
window.close().map_err(|e| format!("关闭悬浮列表窗口失败:{}", e))?;
log::info!("已成功关闭悬浮列表窗口");
} else {
log::warn!("悬浮列表窗口不存在,无需关闭");
}
Ok(())
}
/// 根据用户登录状态和课程选择情况动态计算弹出窗口高度
pub fn calculate_float_info_sync() -> FloatListInfo {
// 默认基础项目数
let item_num = 4;
// 每个item高度
let item_height = 64;
// 总高度 = item高度 * 项目数 + 额外用于移动按钮和时间部分的高度
let popup_height = item_height * item_num + 60;
let popup_width = 60;
// 返回计算得到的弹出窗口尺寸
FloatListInfo {
width: popup_width as u32,
height: popup_height as u32,
}
}
/// 使用抛物线动画将窗口移动到右下角
pub async fn animate_to_bottom_right<R: Runtime>(window: tauri::WebviewWindow<R>, screen_width: i32, screen_height: i32) -> Result<(), String> {
// 获取当前窗口位置和大小
let current_position_physical = window.outer_position().map_err(|e| format!("获取窗口位置失败: {}", e))?;
let current_size_physical = window.outer_size().map_err(|e| format!("获取窗口大小失败: {}", e))?;
let scale_factor = window.scale_factor().map_err(|e| format!("获取缩放因子失败: {}", e))?;
// 转换为逻辑位置和大小
let current_position = current_position_physical.to_logical::<i32>(scale_factor);
let current_size = current_size_physical.to_logical::<i32>(scale_factor);
let start_x = current_position.x;
let start_y = current_position.y;
// 计算目标位置(右下角)
// 注意screen_width 和 screen_height 已经在调用方考虑了缩放因子
let end_x = screen_width - current_size.width;
let end_y = screen_height - current_size.height - 120;
// 计算移动距离
let distance_x = (end_x - start_x) as f64;
let distance_y = (end_y - start_y) as f64;
// 动画参数
let duration = 800; // 总动画时间(毫秒)
let start_time = std::time::Instant::now();
// 抛物线控制参数
// 抛物线顶点高度偏移量(负值表示向下偏移,形成开口向上的抛物线)
let apex_offset = -(distance_y.abs() * 0.3).min(200.0);
// 执行动画
loop {
let elapsed = start_time.elapsed().as_millis() as u64;
if elapsed >= duration {
break;
}
let progress = (elapsed as f64) / (duration as f64);
// 使用抛物线轨迹计算位置
// 水平方向匀速运动
let current_x = (start_x as f64 + distance_x * progress).round() as i32;
// 垂直方向抛物线运动(开口向上,波峰向下)
// y = apex_offset * (4*x^2 - 4*x) 形成开口向上的抛物线,谷点在 x=0.5 处
let parabolic_factor = apex_offset * (4.0 * progress * progress - 4.0 * progress);
let current_y = (start_y as f64 + distance_y * progress + parabolic_factor).round() as i32;
// 更新窗口位置,使用 LogicalPosition
if let Err(_e) = window.set_position(LogicalPosition::new(current_x as f64, current_y as f64)) {
break;
}
// 短暂休眠以控制动画帧率
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
}
// 确保最终位置准确, 再次获取窗口大小和缩放因子
let float_info = calculate_float_info_sync();
let current_size_physical = window.outer_size().map_err(|e| format!("获取窗口大小失败: {}", e))?;
let scale_factor = window.scale_factor().map_err(|e| format!("获取缩放因子失败: {}", e))?;
let current_size = current_size_physical.to_logical::<i32>(scale_factor);
let _ = window.set_size(LogicalSize::new(float_info.width as f64, float_info.height as f64));
let end_x = screen_width - current_size.width;
let end_y = screen_height - current_size.height - 120;
if let Err(e) = window.set_position(LogicalPosition::new(end_x as f64, end_y as f64)) {
return Err(format!("设置最终窗口位置失败: {}", e));
}
Ok(())
}