use super::builder::FloatingWindowBuilder;
use super::commands::{CommandReceiver, CommandSender, WindowCommand, WindowRegistry};
use super::config::{Position, WindowConfig, WindowLevel};
use crate::gui::animation::AnimationController;
use crate::gui::content::Content;
use crate::gui::controller::ControllerState;
use crate::gui::effect::{ParticleSystem, PresetEffect, PresetEffectOptions};
use crate::gui::event::{EventHandler, FloatingWindowEvent};
use crate::gui::menu_bar::{MenuBarEvent, MenuBarManager};
use crate::gui::render::WindowPainter;
use crate::gui::shape::{ShapeMask, WindowShape};
use crate::screenshot::ScreenshotMode;
#[cfg(all(feature = "click_helper", target_os = "macos"))]
use crate::click_helper::ClickHelperMode;
use egui::{CentralPanel, Color32, Context, Pos2, Rect, Vec2};
use std::sync::Arc;
#[cfg(target_os = "macos")]
use std::sync::Mutex;
use winit::application::ApplicationHandler;
#[cfg(target_os = "macos")]
struct MacOsEventLoopState {
tx: super::commands::CommandSender,
rx: super::commands::CommandReceiver,
ready_tx: Option<std::sync::mpsc::Sender<()>>,
}
#[cfg(target_os = "macos")]
static MACOS_EVENT_LOOP_STATE: Mutex<Option<MacOsEventLoopState>> = Mutex::new(None);
#[cfg(target_os = "macos")]
struct InitializedEventLoop {
event_loop: EventLoop<()>,
app: FloatingWindowApp,
}
#[cfg(target_os = "macos")]
thread_local! {
static INITIALIZED_EVENT_LOOP: std::cell::RefCell<Option<InitializedEventLoop>> = const { std::cell::RefCell::new(None) };
}
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition};
use winit::event::{ElementState, MouseButton, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId, WindowLevel as WinitWindowLevel};
pub struct FloatingWindow {
config: WindowConfig,
event_handler: EventHandler,
particle_system: Option<ParticleSystem>,
show_animation: Option<AnimationController>,
#[allow(dead_code)]
hide_animation: Option<AnimationController>,
pub(crate) shape_mask: ShapeMask,
effect_margin: f32,
visible: bool,
pub(crate) drag_offset: Option<(f64, f64)>, mouse_pos: Option<(f32, f32)>,
pub(crate) is_dragging: bool,
has_focus: bool,
widget_content: Option<crate::gui::widget::WidgetDef>,
widget_renderer: crate::gui::widget::WidgetRenderer,
}
impl FloatingWindow {
pub fn builder() -> FloatingWindowBuilder {
FloatingWindowBuilder::new()
}
#[allow(clippy::type_complexity)]
pub(crate) fn from_config(
config: WindowConfig,
event_callback: Option<Box<dyn Fn(&FloatingWindowEvent) + Send + Sync>>,
) -> Result<Self, String> {
let event_handler = EventHandler::new();
if let Some(callback) = event_callback {
event_handler.on("all", move |event| callback(event));
}
let effect_margin = config.effect_margin;
let content_width = config.size.width as f32;
let content_height = config.size.height as f32;
let particle_system = config.effect.as_ref().map(|(effect, options)| {
ParticleSystem::new(*effect, options.clone(), content_width, content_height)
});
let shape_mask = ShapeMask::new_with_offset(
config.shape.clone(),
content_width,
content_height,
effect_margin,
);
Ok(Self {
config,
event_handler,
particle_system,
show_animation: None,
hide_animation: None,
shape_mask,
effect_margin,
visible: false,
drag_offset: None,
mouse_pos: None,
is_dragging: false,
has_focus: false,
widget_content: None,
widget_renderer: crate::gui::widget::WidgetRenderer::new(),
})
}
pub fn run(self) -> Result<(), String> {
let event_loop = EventLoop::new().map_err(|e| e.to_string())?;
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = FloatingWindowApp::new(self);
event_loop.run_app(&mut app).map_err(|e| e.to_string())
}
pub fn run_multiple(windows: Vec<FloatingWindow>) -> Result<(), String> {
if windows.is_empty() {
return Err("No windows provided".to_string());
}
let event_loop = EventLoop::new().map_err(|e| e.to_string())?;
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = FloatingWindowApp::new_multi(windows);
event_loop.run_app(&mut app).map_err(|e| e.to_string())
}
pub fn run_controller(controller: FloatingWindow) -> Result<(), String> {
let (tx, rx) = super::commands::create_command_channel();
let event_loop = EventLoop::new().map_err(|e| e.to_string())?;
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = FloatingWindowApp::new_controller(controller, tx, rx);
event_loop.run_app(&mut app).map_err(|e| e.to_string())
}
#[cfg(not(target_os = "macos"))]
pub fn spawn_controller() -> Result<(CommandSender, std::thread::JoinHandle<()>), String> {
let (tx, rx) = super::commands::create_command_channel();
let sender = tx.clone();
let handle = std::thread::spawn(move || {
let controller = FloatingWindow::builder()
.title("aumate-controller")
.size(1, 1)
.position(-10000.0, -10000.0) .shape(WindowShape::Rectangle)
.build()
.expect("Failed to create controller window");
let event_loop = EventLoop::new().expect("Failed to create event loop");
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = FloatingWindowApp::new_controller(controller, tx, rx);
let _ = event_loop.run_app(&mut app);
});
Ok((sender, handle))
}
#[cfg(target_os = "macos")]
pub fn spawn_controller() -> Result<(CommandSender, std::thread::JoinHandle<()>), String> {
use std::sync::mpsc;
let (tx, rx) = super::commands::create_command_channel();
let sender = tx.clone();
let (ready_tx, ready_rx) = mpsc::channel::<()>();
let handle = std::thread::spawn(move || {
let _ = ready_rx.recv();
});
*MACOS_EVENT_LOOP_STATE.lock().unwrap() =
Some(MacOsEventLoopState { tx, rx, ready_tx: Some(ready_tx) });
Ok((sender, handle))
}
#[cfg(target_os = "macos")]
pub fn run_event_loop() -> Result<(), String> {
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
let state = MACOS_EVENT_LOOP_STATE
.lock()
.unwrap()
.take()
.ok_or("Event loop state not initialized")?;
let controller = FloatingWindow::builder()
.title("aumate-controller")
.size(1, 1)
.position(-10000.0, -10000.0)
.shape(WindowShape::Rectangle)
.build()?;
let mut event_loop = EventLoop::new().map_err(|e| e.to_string())?;
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = FloatingWindowApp::new_controller(controller, state.tx, state.rx);
if let Some(ready_tx) = state.ready_tx {
let _ = ready_tx.send(());
}
loop {
let status = event_loop.pump_app_events(None, &mut app);
match status {
PumpStatus::Exit(code) => {
return if code == 0 { Ok(()) } else { Err(format!("Exit code: {}", code)) };
}
PumpStatus::Continue => {
std::thread::sleep(std::time::Duration::from_millis(1));
}
}
}
}
#[cfg(target_os = "macos")]
pub fn init_event_loop() -> Result<(), String> {
INITIALIZED_EVENT_LOOP.with(|cell| {
let mut initialized = cell.borrow_mut();
if initialized.is_some() {
return Err("Event loop already initialized".to_string());
}
let state = MACOS_EVENT_LOOP_STATE
.lock()
.unwrap()
.take()
.ok_or("Event loop state not set (call spawn_controller first)")?;
let controller = FloatingWindow::builder()
.title("aumate-controller")
.size(1, 1)
.position(-10000.0, -10000.0)
.shape(WindowShape::Rectangle)
.build()?;
let event_loop = EventLoop::new().map_err(|e| e.to_string())?;
event_loop.set_control_flow(ControlFlow::Poll);
let app = FloatingWindowApp::new_controller(controller, state.tx, state.rx);
if let Some(ready_tx) = state.ready_tx {
let _ = ready_tx.send(());
}
*initialized = Some(InitializedEventLoop { event_loop, app });
Ok(())
})
}
#[cfg(not(target_os = "macos"))]
pub fn init_event_loop() -> Result<(), String> {
Ok(())
}
#[cfg(target_os = "macos")]
pub fn run_event_loop_once() -> Result<bool, String> {
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
INITIALIZED_EVENT_LOOP.with(|cell| {
let mut guard = cell.borrow_mut();
let state =
guard.as_mut().ok_or("Event loop not initialized (call init_event_loop first)")?;
let status = state.event_loop.pump_app_events(None, &mut state.app);
match status {
PumpStatus::Exit(_) => Ok(false),
PumpStatus::Continue => Ok(true),
}
})
}
#[cfg(not(target_os = "macos"))]
pub fn run_event_loop_once() -> Result<bool, String> {
Ok(true)
}
#[cfg(not(target_os = "macos"))]
pub fn run_event_loop() -> Result<(), String> {
Ok(())
}
pub fn id(&self) -> Option<&str> {
self.config.id.as_deref()
}
pub fn is_visible(&self) -> bool {
self.visible
}
pub fn set_visible(&mut self, visible: bool) {
if visible != self.visible {
self.visible = visible;
let event = if visible { FloatingWindowEvent::Show } else { FloatingWindowEvent::Hide };
self.event_handler.dispatch(&event);
}
}
pub fn set_content(&mut self, content: Option<Content>) {
self.config.content = content;
}
pub fn set_widget_content(&mut self, content: Option<crate::gui::widget::WidgetDef>) {
self.widget_content = content;
self.widget_renderer.clear_state();
}
pub fn update_widget(&mut self, widget_id: &str, update: &super::commands::WidgetUpdate) {
use super::commands::WidgetUpdate;
use crate::gui::widget::WidgetStateUpdate;
let state_update = match update {
WidgetUpdate::SetText(text) => WidgetStateUpdate::SetText(text.clone()),
WidgetUpdate::SetChecked(checked) => WidgetStateUpdate::SetChecked(*checked),
WidgetUpdate::SetValue(value) => WidgetStateUpdate::SetValue(*value),
WidgetUpdate::SetVisible(_) | WidgetUpdate::SetEnabled(_) => {
return;
}
};
self.widget_renderer.update_state(&widget_id.to_string(), state_update);
}
pub fn update(&mut self) {
if let Some(ref mut system) = self.particle_system {
system.update();
}
}
pub fn render(&mut self, ctx: &Context, _rect: Rect) -> Vec<crate::gui::widget::WidgetEvent> {
if let Some(ref mut anim) = self.show_animation {
anim.update();
}
self.update();
let transparent_frame = egui::Frame::NONE.fill(Color32::TRANSPARENT);
let margin = self.effect_margin;
let content_width = self.config.size.width as f32;
let content_height = self.config.size.height as f32;
let content_rect = Rect::from_min_size(
Pos2::new(margin, margin),
Vec2::new(content_width, content_height),
);
let mut widget_events = Vec::new();
if let Some(ref widget_content) = self.widget_content.clone() {
CentralPanel::default().frame(transparent_frame).show(ctx, |ui| {
let bg_color = Color32::from_rgba_unmultiplied(40, 40, 50, 255);
ui.painter().rect_filled(content_rect, 4.0, bg_color);
#[allow(deprecated)]
ui.allocate_ui_at_rect(content_rect, |ui| {
ui.style_mut().spacing.item_spacing = egui::vec2(4.0, 4.0);
self.widget_renderer.render(ui, widget_content, &mut widget_events);
});
if let Some(ref system) = self.particle_system {
WindowPainter::render_particles(ui, system, Pos2::new(margin, margin));
}
});
} else {
CentralPanel::default().frame(transparent_frame).show(ctx, |ui| {
let has_image_content = matches!(&self.config.content, Some(Content::Image { .. }));
if !has_image_content {
let bg_color = Color32::from_rgba_unmultiplied(50, 50, 80, 255);
match &self.config.shape {
WindowShape::Rectangle => {
ui.painter().rect_filled(content_rect, 0.0, bg_color);
}
WindowShape::Circle => {
let radius = content_width.min(content_height) / 2.0;
ui.painter().circle_filled(content_rect.center(), radius, bg_color);
}
WindowShape::Custom { .. } => {
ui.painter().rect_filled(content_rect, 0.0, bg_color);
}
}
}
if let Some(ref content) = self.config.content {
WindowPainter::render_content(ui, content, content_rect, &self.config.shape);
}
if let Some(ref system) = self.particle_system {
WindowPainter::render_particles(ui, system, Pos2::new(margin, margin));
}
});
}
widget_events
}
pub fn on_mouse_press(&mut self, x: f64, y: f64) {
if self.has_focus && self.config.draggable && self.shape_mask.contains(x as f32, y as f32) {
self.drag_offset = Some((x, y));
self.is_dragging = true;
self.event_handler
.dispatch(&FloatingWindowEvent::DragStart { x: x as f32, y: y as f32 });
}
self.event_handler.dispatch(&FloatingWindowEvent::Click { x: x as f32, y: y as f32 });
}
pub fn set_focus(&mut self, focused: bool) {
self.has_focus = focused;
if !focused && self.is_dragging {
self.is_dragging = false;
self.drag_offset = None;
}
}
pub fn on_mouse_release(&mut self, x: f64, y: f64) {
if self.is_dragging {
self.is_dragging = false;
self.drag_offset = None;
self.event_handler.dispatch(&FloatingWindowEvent::DragEnd { x: x as f32, y: y as f32 });
}
}
pub fn on_drag_move(
&mut self,
screen_x: f64,
screen_y: f64,
scale_factor: f64,
) -> Option<(f64, f64)> {
if self.is_dragging
&& let Some((offset_x, offset_y)) = self.drag_offset
{
let physical_offset_x = offset_x * scale_factor;
let physical_offset_y = offset_y * scale_factor;
let new_x = screen_x - physical_offset_x;
let new_y = screen_y - physical_offset_y;
self.event_handler
.dispatch(&FloatingWindowEvent::Drag { x: new_x as f32, y: new_y as f32 });
return Some((new_x, new_y));
}
None
}
pub fn on_mouse_move(&mut self, x: f64, y: f64) {
self.mouse_pos = Some((x as f32, y as f32));
self.event_handler.dispatch(&FloatingWindowEvent::MouseMove { x: x as f32, y: y as f32 });
}
pub fn update_position(&mut self, x: f64, y: f64) {
self.config.position = Position::new(x, y);
}
}
struct GpuState {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
surface_config: wgpu::SurfaceConfiguration,
egui_renderer: egui_wgpu::Renderer,
}
impl GpuState {
async fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let surface = instance.create_surface(window.clone()).expect("Failed to create surface");
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.expect("Failed to find adapter");
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("float-window device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::default(),
trace: wgpu::Trace::Off,
experimental_features: wgpu::ExperimentalFeatures::default(),
})
.await
.expect("Failed to create device");
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.find(|f| {
**f == wgpu::TextureFormat::Bgra8Unorm || **f == wgpu::TextureFormat::Rgba8Unorm
})
.copied()
.unwrap_or_else(|| {
surface_caps
.formats
.iter()
.find(|f| !f.is_srgb())
.copied()
.unwrap_or(surface_caps.formats[0])
});
let alpha_mode =
if surface_caps.alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
wgpu::CompositeAlphaMode::PreMultiplied
} else if surface_caps.alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) {
wgpu::CompositeAlphaMode::PostMultiplied
} else {
surface_caps.alpha_modes[0]
};
log::info!("Using surface format: {:?}, alpha mode: {:?}", surface_format, alpha_mode);
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width.max(1),
height: size.height.max(1),
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode,
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
let egui_renderer = egui_wgpu::Renderer::new(
&device,
surface_format,
egui_wgpu::RendererOptions::default(),
);
Self { surface, device, queue, surface_config, egui_renderer }
}
fn resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
self.surface_config.width = width;
self.surface_config.height = height;
self.surface.configure(&self.device, &self.surface_config);
}
}
fn render(
&mut self,
primitives: Vec<egui::ClippedPrimitive>,
textures_delta: egui::TexturesDelta,
screen_descriptor: egui_wgpu::ScreenDescriptor,
) {
let output = match self.surface.get_current_texture() {
Ok(output) => output,
Err(wgpu::SurfaceError::Outdated) => {
self.surface.configure(&self.device, &self.surface_config);
return;
}
Err(e) => {
log::error!("Surface error: {:?}", e);
return;
}
};
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
for (id, image_delta) in &textures_delta.set {
self.egui_renderer.update_texture(&self.device, &self.queue, *id, image_delta);
}
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("egui encoder"),
});
self.egui_renderer.update_buffers(
&self.device,
&self.queue,
&mut encoder,
&primitives,
&screen_descriptor,
);
{
let color_attachments = [Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})];
let descriptor = wgpu::RenderPassDescriptor {
label: Some("egui render pass"),
color_attachments: &color_attachments,
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
};
let render_pass = encoder.begin_render_pass(&descriptor);
let mut render_pass: wgpu::RenderPass<'static> = render_pass.forget_lifetime();
self.egui_renderer.render(&mut render_pass, &primitives, &screen_descriptor);
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
for id in &textures_delta.free {
self.egui_renderer.free_texture(id);
}
}
}
struct WindowState {
floating_window: FloatingWindow,
window: Arc<Window>,
gpu_state: GpuState,
egui_ctx: Context,
egui_state: egui_winit::State,
controller_state: Option<ControllerState>,
is_managed: bool,
widget_has_focus: bool,
}
struct ScreenshotWindowState {
mode: ScreenshotMode,
window: Arc<Window>,
gpu_state: GpuState,
egui_ctx: Context,
egui_state: egui_winit::State,
}
#[cfg(all(feature = "click_helper", target_os = "macos"))]
struct ClickHelperWindowState {
mode: ClickHelperMode,
window: Arc<Window>,
gpu_state: GpuState,
egui_ctx: Context,
egui_state: egui_winit::State,
}
struct FloatingWindowApp {
pending_windows: Vec<FloatingWindow>,
windows: std::collections::HashMap<WindowId, WindowState>,
command_receiver: Option<CommandReceiver>,
command_sender: Option<CommandSender>,
registry: WindowRegistry,
controller_window_id: Option<WindowId>,
pending_configs: Vec<(WindowConfig, Option<(PresetEffect, PresetEffectOptions)>)>,
event_senders: std::collections::HashMap<String, super::commands::WidgetEventSender>,
menu_bar_manager: MenuBarManager,
screenshot_state: Option<ScreenshotWindowState>,
pending_screenshot_start: Option<Vec<String>>,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
click_helper_state: Option<ClickHelperWindowState>,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
pending_click_helper_start: bool,
}
impl FloatingWindowApp {
fn new(floating_window: FloatingWindow) -> Self {
Self {
pending_windows: vec![floating_window],
windows: std::collections::HashMap::new(),
command_receiver: None,
command_sender: None,
registry: WindowRegistry::new(),
controller_window_id: None,
pending_configs: Vec::new(),
event_senders: std::collections::HashMap::new(),
menu_bar_manager: MenuBarManager::new(),
screenshot_state: None,
pending_screenshot_start: None,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
click_helper_state: None,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
pending_click_helper_start: false,
}
}
fn new_multi(windows: Vec<FloatingWindow>) -> Self {
Self {
pending_windows: windows,
windows: std::collections::HashMap::new(),
command_receiver: None,
command_sender: None,
registry: WindowRegistry::new(),
controller_window_id: None,
pending_configs: Vec::new(),
event_senders: std::collections::HashMap::new(),
menu_bar_manager: MenuBarManager::new(),
screenshot_state: None,
pending_screenshot_start: None,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
click_helper_state: None,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
pending_click_helper_start: false,
}
}
fn new_controller(
controller_window: FloatingWindow,
command_sender: CommandSender,
command_receiver: CommandReceiver,
) -> Self {
Self {
pending_windows: vec![controller_window],
windows: std::collections::HashMap::new(),
command_receiver: Some(command_receiver),
command_sender: Some(command_sender),
registry: WindowRegistry::new(),
controller_window_id: None,
pending_configs: Vec::new(),
event_senders: std::collections::HashMap::new(),
menu_bar_manager: MenuBarManager::new(),
screenshot_state: None,
pending_screenshot_start: None,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
click_helper_state: None,
#[cfg(all(feature = "click_helper", target_os = "macos"))]
pending_click_helper_start: false,
}
}
fn create_egui_context() -> Context {
let egui_ctx = Context::default();
egui_ctx.tessellation_options_mut(|opts| {
opts.feathering = false;
});
egui_ctx
}
fn process_commands(&mut self, event_loop: &ActiveEventLoop) {
if let Some(ref receiver) = self.command_receiver {
while let Ok(command) = receiver.try_recv() {
match command {
WindowCommand::Create { config, effect } => {
log::info!("Creating new window from controller: {:?}", config.title);
self.pending_configs.push((config, effect));
}
WindowCommand::Close { id } => {
log::info!("Closing window {:?}", id);
if let Some(_state) = self.windows.remove(&id) {
self.registry.unregister(id);
}
if self.windows.is_empty() {
event_loop.exit();
}
}
WindowCommand::CloseByName { name } => {
log::info!("Closing window by name: {}", name);
if let Some(id) = self.registry.find_by_name(&name)
&& self.windows.remove(&id).is_some()
{
self.registry.unregister(id);
}
}
WindowCommand::UpdateEffectOptions { id, options } => {
log::info!("Updating effect options for {:?}", id);
if let Some(state) = self.windows.get_mut(&id) {
if let Some(ref mut system) = state.floating_window.particle_system {
let width = state.floating_window.config.size.width as f32;
let height = state.floating_window.config.size.height as f32;
if let Some((effect, _)) = &state.floating_window.config.effect {
*system = ParticleSystem::immediate(
*effect,
options.clone(),
width,
height,
);
}
}
self.registry.update_options(id, options);
}
}
WindowCommand::CloseAll => {
log::info!("Closing all managed windows");
let managed_ids: Vec<WindowId> = self
.windows
.iter()
.filter(|(_, state)| state.is_managed)
.map(|(id, _)| *id)
.collect();
for id in managed_ids {
self.windows.remove(&id);
self.registry.unregister(id);
}
}
WindowCommand::AddMenuBarItem { item } => {
log::info!("Adding menu bar item: {}", item.name);
if let Err(e) = self.menu_bar_manager.add_item(item) {
log::error!("Failed to add menu bar item: {}", e);
}
}
WindowCommand::RemoveMenuBarItem { id } => {
log::info!("Removing menu bar item: {}", id);
if let Err(e) = self.menu_bar_manager.remove_item(&id) {
log::error!("Failed to remove menu bar item: {}", e);
}
}
WindowCommand::UpdateMenuBarIcon { id, icon } => {
log::info!("Updating menu bar icon: {}", id);
if let Err(e) = self.menu_bar_manager.update_icon(&id, &icon) {
log::error!("Failed to update menu bar icon: {}", e);
}
}
WindowCommand::UpdateMenuBarTooltip { id, tooltip } => {
log::info!("Updating menu bar tooltip: {}", id);
if let Err(e) = self.menu_bar_manager.update_tooltip(&id, &tooltip) {
log::error!("Failed to update menu bar tooltip: {}", e);
}
}
WindowCommand::UpdateContent { id, content } => {
log::info!("Updating content for window {:?}", id);
if let Some(state) = self.windows.get_mut(&id) {
state.floating_window.set_content(content);
}
}
WindowCommand::StartScreenshotMode { enabled_actions } => {
log::info!("Starting screenshot mode with actions: {:?}", enabled_actions);
self.pending_screenshot_start = Some(enabled_actions);
}
WindowCommand::ExitScreenshotMode => {
log::info!("Exiting screenshot mode");
self.screenshot_state = None;
}
WindowCommand::ExitApplication => {
log::info!("Exiting application");
event_loop.exit();
}
WindowCommand::StartClickHelperMode => {
#[cfg(all(feature = "click_helper", target_os = "macos"))]
{
log::info!("StartClickHelperMode command received");
self.pending_click_helper_start = true;
}
#[cfg(not(all(feature = "click_helper", target_os = "macos")))]
{
log::warn!(
"Click Helper is only available on macOS with click_helper feature"
);
}
}
WindowCommand::ExitClickHelperMode => {
#[cfg(all(feature = "click_helper", target_os = "macos"))]
{
log::info!("Exiting Click Helper mode");
self.click_helper_state = None;
}
}
WindowCommand::SetWidgetContent { id, content } => {
log::info!("Setting widget content for window {:?}", id);
if let Some(state) = self.windows.get_mut(&id) {
state.floating_window.set_widget_content(Some(content));
}
}
WindowCommand::UpdateWidget { widget_id, update } => {
log::info!("Updating widget: {}", widget_id);
for state in self.windows.values_mut() {
state.floating_window.update_widget(&widget_id, &update);
}
}
WindowCommand::RegisterEventCallback { window_name, event_sender } => {
log::info!("Registering event callback for window: {}", window_name);
self.event_senders.insert(window_name, event_sender);
}
WindowCommand::ShowOpenFileDialog { request_id, window_name, options } => {
log::info!("Showing open file dialog for window: {}", window_name);
let result = Self::show_open_file_dialog_sync(&options);
if let Some(sender) = self.event_senders.get(&window_name) {
let event = crate::gui::widget::WidgetEvent::FileDialogCompleted {
id: request_id,
paths: result.paths,
cancelled: result.cancelled,
};
let _ = sender.send((window_name.clone(), event));
}
}
WindowCommand::ShowSaveFileDialog { request_id, window_name, options } => {
log::info!("Showing save file dialog for window: {}", window_name);
let result = Self::show_save_file_dialog_sync(&options);
if let Some(sender) = self.event_senders.get(&window_name) {
let event = crate::gui::widget::WidgetEvent::FileDialogCompleted {
id: request_id,
paths: result.paths,
cancelled: result.cancelled,
};
let _ = sender.send((window_name.clone(), event));
}
}
WindowCommand::ShowFolderDialog { request_id, window_name, options } => {
log::info!("Showing folder dialog for window: {}", window_name);
let result = Self::show_folder_dialog_sync(&options);
if let Some(sender) = self.event_senders.get(&window_name) {
let event = crate::gui::widget::WidgetEvent::FileDialogCompleted {
id: request_id,
paths: result.paths,
cancelled: result.cancelled,
};
let _ = sender.send((window_name.clone(), event));
}
}
}
}
}
}
fn show_open_file_dialog_sync(
options: &super::commands::FileDialogOptions,
) -> super::commands::FileDialogResult {
use rfd::FileDialog;
let mut dialog = FileDialog::new();
if let Some(ref title) = options.title {
dialog = dialog.set_title(title);
}
if let Some(ref dir) = options.directory {
dialog = dialog.set_directory(dir);
}
for (name, extensions) in &options.filters {
let ext_refs: Vec<&str> = extensions.iter().map(|s| s.as_str()).collect();
dialog = dialog.add_filter(name, &ext_refs);
}
let result = if options.multiple {
dialog.pick_files()
} else {
dialog.pick_file().map(|f| vec![f])
};
match result {
Some(files) => super::commands::FileDialogResult {
paths: files.into_iter().map(|f| f.to_string_lossy().to_string()).collect(),
cancelled: false,
},
None => super::commands::FileDialogResult { paths: vec![], cancelled: true },
}
}
fn show_save_file_dialog_sync(
options: &super::commands::FileDialogOptions,
) -> super::commands::FileDialogResult {
use rfd::FileDialog;
let mut dialog = FileDialog::new();
if let Some(ref title) = options.title {
dialog = dialog.set_title(title);
}
if let Some(ref dir) = options.directory {
dialog = dialog.set_directory(dir);
}
if let Some(ref name) = options.default_name {
dialog = dialog.set_file_name(name);
}
for (name, extensions) in &options.filters {
let ext_refs: Vec<&str> = extensions.iter().map(|s| s.as_str()).collect();
dialog = dialog.add_filter(name, &ext_refs);
}
match dialog.save_file() {
Some(file) => super::commands::FileDialogResult {
paths: vec![file.to_string_lossy().to_string()],
cancelled: false,
},
None => super::commands::FileDialogResult { paths: vec![], cancelled: true },
}
}
fn show_folder_dialog_sync(
options: &super::commands::FileDialogOptions,
) -> super::commands::FileDialogResult {
use rfd::FileDialog;
let mut dialog = FileDialog::new();
if let Some(ref title) = options.title {
dialog = dialog.set_title(title);
}
if let Some(ref dir) = options.directory {
dialog = dialog.set_directory(dir);
}
match dialog.pick_folder() {
Some(folder) => super::commands::FileDialogResult {
paths: vec![folder.to_string_lossy().to_string()],
cancelled: false,
},
None => super::commands::FileDialogResult { paths: vec![], cancelled: true },
}
}
fn process_menu_bar_events(&mut self, event_loop: &ActiveEventLoop) {
for event in self.menu_bar_manager.process_events() {
match event {
MenuBarEvent::Click { id } => {
log::debug!("Menu bar item clicked: {}", id);
}
MenuBarEvent::DoubleClick { id } => {
log::debug!("Menu bar item double-clicked: {}", id);
}
MenuBarEvent::MenuItemClick { tray_id, menu_item_id } => {
log::debug!("Menu item clicked: {} -> {}", tray_id, menu_item_id);
}
MenuBarEvent::QuitRequested => {
log::info!("Quit requested from menu bar");
event_loop.exit();
}
}
}
}
fn create_pending_windows(&mut self, event_loop: &ActiveEventLoop) {
for (config, effect) in self.pending_configs.drain(..) {
let window_name = config.title.clone().unwrap_or_else(|| "Window".to_string());
let effect_info = effect.clone();
let mut builder = FloatingWindow::builder()
.position(config.position.x, config.position.y)
.size(config.size.width, config.size.height)
.shape(config.shape.clone())
.draggable(config.draggable)
.always_on_top(config.level == WindowLevel::AlwaysOnTop);
if let Some(title) = config.title {
builder = builder.title(title);
}
if let Some((effect, options)) = effect {
builder = builder.effect(effect, options);
}
if let Some(content) = config.content {
builder = builder.content(content);
}
match builder.build() {
Ok(mut floating_window) => {
if let Some(widget_content) = config.widget_content.clone() {
floating_window.set_widget_content(Some(widget_content));
}
let margin = floating_window.effect_margin;
let window_width = config.size.width as f32 + margin * 2.0;
let window_height = config.size.height as f32 + margin * 2.0;
let attrs = WindowAttributes::default()
.with_title(&window_name)
.with_inner_size(LogicalSize::new(window_width, window_height))
.with_position(LogicalPosition::new(config.position.x, config.position.y))
.with_decorations(false)
.with_transparent(true)
.with_resizable(false)
.with_window_level(WinitWindowLevel::AlwaysOnTop);
match event_loop.create_window(attrs) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
let gpu_state = pollster::block_on(GpuState::new(window.clone()));
let egui_ctx = Self::create_egui_context();
let egui_state = egui_winit::State::new(
egui_ctx.clone(),
egui_ctx.viewport_id(),
&window,
None,
None,
None,
);
let state = WindowState {
floating_window,
window,
gpu_state,
egui_ctx,
egui_state,
controller_state: None,
is_managed: true,
widget_has_focus: false,
};
let effect_type = effect_info.as_ref().map(|(e, _)| *e);
let effect_opts = effect_info.map(|(_, o)| o);
let window_size = (config.size.width, config.size.height);
self.registry.register(
window_id,
window_name,
window_size,
effect_type,
effect_opts,
);
self.windows.insert(window_id, state);
log::info!("Created managed window {:?}", window_id);
}
Err(e) => {
log::error!("Failed to create window: {}", e);
}
}
}
Err(e) => {
log::error!("Failed to build FloatingWindow: {}", e);
}
}
}
}
fn create_screenshot_window(
&mut self,
event_loop: &ActiveEventLoop,
enabled_actions: Vec<String>,
) {
let primary_monitor =
event_loop.primary_monitor().or_else(|| event_loop.available_monitors().next());
let (size, position) = if let Some(mon) = primary_monitor {
(mon.size(), mon.position())
} else {
log::error!("No monitors found for screenshot");
return;
};
log::info!(
"Creating screenshot window: pos=({}, {}), size={}x{} (physical from winit monitor)",
position.x,
position.y,
size.width,
size.height
);
let attrs = WindowAttributes::default()
.with_title("Screenshot")
.with_inner_size(size)
.with_position(position)
.with_decorations(false)
.with_transparent(true)
.with_resizable(false)
.with_visible(false);
match event_loop.create_window(attrs) {
Ok(window) => {
#[cfg(target_os = "macos")]
Self::configure_macos_screenshot_window(&window);
let actual_size = window.inner_size();
log::info!(
"Screenshot window created: actual_size={}x{}, requested_size={}x{}",
actual_size.width,
actual_size.height,
size.width,
size.height
);
let window = Arc::new(window);
let scale_factor = window.scale_factor();
match ScreenshotMode::new(enabled_actions, scale_factor) {
Ok(mut mode) => {
let actual_size = window.inner_size();
let logical_width = actual_size.width as f32 / scale_factor as f32;
let logical_height = actual_size.height as f32 / scale_factor as f32;
mode.set_screen_size(logical_width, logical_height);
let gpu_state = pollster::block_on(GpuState::new(window.clone()));
let egui_ctx = Self::create_egui_context();
let egui_state = egui_winit::State::new(
egui_ctx.clone(),
egui_ctx.viewport_id(),
&window,
None,
None,
None,
);
window.set_visible(true);
self.screenshot_state = Some(ScreenshotWindowState {
mode,
window,
gpu_state,
egui_ctx,
egui_state,
});
log::info!("Screenshot mode started");
}
Err(e) => {
log::error!("Failed to create screenshot mode: {}", e);
}
}
}
Err(e) => {
log::error!("Failed to create screenshot window: {}", e);
}
}
}
#[cfg(target_os = "macos")]
fn configure_macos_screenshot_window(window: &Window) {
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
let handle = match window.window_handle() {
Ok(h) => h,
Err(_) => return,
};
if let RawWindowHandle::AppKit(handle) = handle.as_raw() {
#[allow(unexpected_cfgs)]
unsafe {
use objc::runtime::{Class, Object};
use objc::{msg_send, sel, sel_impl};
let ns_view: *mut Object = handle.ns_view.as_ptr() as *mut Object;
let ns_window: *mut Object = msg_send![ns_view, window];
let _: () = msg_send![ns_window, setLevel: 25i64];
let _: () = msg_send![ns_window, setCollectionBehavior: 128u64];
let _: () = msg_send![ns_window, setAlphaValue: 1.0];
let ns_color_class = match Class::get("NSColor") {
Some(c) => c,
None => return,
};
let clear_color: *mut Object = msg_send![ns_color_class, clearColor];
let _: () = msg_send![ns_window, setBackgroundColor: clear_color];
let _: () = msg_send![ns_window, setOpaque: false];
let _: () = msg_send![ns_window, setHasShadow: false];
log::info!("Configured macOS screenshot window with overlay level");
}
}
}
#[cfg(all(feature = "click_helper", target_os = "macos"))]
fn create_click_helper_window(&mut self, event_loop: &ActiveEventLoop) {
let primary_monitor =
event_loop.primary_monitor().or_else(|| event_loop.available_monitors().next());
let (size, position) = if let Some(mon) = primary_monitor {
(mon.size(), mon.position())
} else {
log::error!("No monitors found for Click Helper");
return;
};
log::info!(
"Creating Click Helper window: pos=({}, {}), size={}x{}",
position.x,
position.y,
size.width,
size.height
);
let attrs = WindowAttributes::default()
.with_title("Click Helper")
.with_inner_size(size)
.with_position(position)
.with_decorations(false)
.with_transparent(true)
.with_resizable(false)
.with_visible(false);
match event_loop.create_window(attrs) {
Ok(window) => {
Self::configure_macos_screenshot_window(&window);
let window = Arc::new(window);
let mut mode = ClickHelperMode::new();
match mode.activate() {
Ok(()) => {
if !mode.is_active() {
log::warn!("Click Helper mode did not activate (no elements found?)");
return;
}
let gpu_state = pollster::block_on(GpuState::new(window.clone()));
let egui_ctx = Self::create_egui_context();
let egui_state = egui_winit::State::new(
egui_ctx.clone(),
egui_ctx.viewport_id(),
&window,
None,
None,
None,
);
window.set_visible(true);
self.click_helper_state = Some(ClickHelperWindowState {
mode,
window,
gpu_state,
egui_ctx,
egui_state,
});
log::info!("Click Helper mode started");
}
Err(e) => {
log::error!("Failed to activate Click Helper mode: {}", e);
}
}
}
Err(e) => {
log::error!("Failed to create Click Helper window: {}", e);
}
}
}
}
impl ApplicationHandler for FloatingWindowApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
for mut floating_window in self.pending_windows.drain(..) {
let config = &floating_window.config;
let margin = floating_window.effect_margin;
let window_width = config.size.width as f32 + margin * 2.0;
let window_height = config.size.height as f32 + margin * 2.0;
let mut attrs = WindowAttributes::default()
.with_title(config.title.as_deref().unwrap_or("Float Window"))
.with_inner_size(LogicalSize::new(window_width, window_height))
.with_position(LogicalPosition::new(config.position.x, config.position.y))
.with_decorations(false)
.with_transparent(true)
.with_resizable(config.resizable);
attrs = attrs.with_window_level(match config.level {
WindowLevel::Normal => WinitWindowLevel::Normal,
WindowLevel::Top => WinitWindowLevel::AlwaysOnTop,
WindowLevel::AlwaysOnTop => WinitWindowLevel::AlwaysOnTop,
});
let window =
Arc::new(event_loop.create_window(attrs).expect("Failed to create window"));
let window_id = window.id();
let gpu_state = pollster::block_on(GpuState::new(window.clone()));
let egui_ctx = Self::create_egui_context();
let egui_state = egui_winit::State::new(
egui_ctx.clone(),
egui_ctx.viewport_id(),
&window,
None,
None,
None,
);
floating_window.set_visible(true);
let controller_state =
if self.controller_window_id.is_none() && self.command_sender.is_some() {
self.controller_window_id = Some(window_id);
let sender = self.command_sender.clone().unwrap();
Some(ControllerState::new(sender, self.registry.clone()))
} else {
None
};
let state = WindowState {
floating_window,
window,
gpu_state,
egui_ctx,
egui_state,
controller_state,
is_managed: false, widget_has_focus: false,
};
self.windows.insert(window_id, state);
log::info!("Window {:?} created successfully with GPU rendering", window_id);
}
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let mut should_close_screenshot = false;
if let Some(ref mut screenshot_state) = self.screenshot_state {
if screenshot_state.window.id() == window_id {
let _response =
screenshot_state.egui_state.on_window_event(&screenshot_state.window, &event);
match &event {
WindowEvent::CloseRequested => {
should_close_screenshot = true;
}
WindowEvent::RedrawRequested => {
let size = screenshot_state.window.inner_size();
if size.width == 0 || size.height == 0 {
return;
}
let raw_input =
screenshot_state.egui_state.take_egui_input(&screenshot_state.window);
let full_output = screenshot_state.egui_ctx.run(raw_input, |ctx| {
screenshot_state.mode.render(ctx);
});
screenshot_state.egui_state.handle_platform_output(
&screenshot_state.window,
full_output.platform_output,
);
let primitives = screenshot_state
.egui_ctx
.tessellate(full_output.shapes, full_output.pixels_per_point);
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [size.width, size.height],
pixels_per_point: full_output.pixels_per_point,
};
screenshot_state.gpu_state.render(
primitives,
full_output.textures_delta,
screen_descriptor,
);
if screenshot_state.mode.should_exit() {
should_close_screenshot = true;
}
}
WindowEvent::CursorMoved { position, .. } => {
let scale_factor = screenshot_state.window.scale_factor();
let logical_x = position.x / scale_factor;
let logical_y = position.y / scale_factor;
screenshot_state
.mode
.handle_cursor_move((logical_x as f32, logical_y as f32));
}
_ => {
let _ = screenshot_state.mode.handle_event(&event);
if screenshot_state.mode.should_exit() {
should_close_screenshot = true;
}
}
}
if !should_close_screenshot {
return;
}
}
}
if should_close_screenshot {
self.screenshot_state = None;
log::info!("Screenshot mode closed");
return;
}
#[cfg(all(feature = "click_helper", target_os = "macos"))]
{
let mut should_close_click_helper = false;
let mut click_position: Option<(f32, f32)> = None;
if let Some(ref mut click_helper_state) = self.click_helper_state {
if click_helper_state.window.id() == window_id {
let _response = click_helper_state
.egui_state
.on_window_event(&click_helper_state.window, &event);
match &event {
WindowEvent::CloseRequested => {
should_close_click_helper = true;
}
WindowEvent::RedrawRequested => {
let size = click_helper_state.window.inner_size();
if size.width == 0 || size.height == 0 {
return;
}
let raw_input = click_helper_state
.egui_state
.take_egui_input(&click_helper_state.window);
let full_output = click_helper_state.egui_ctx.run(raw_input, |ctx| {
click_helper_state.mode.render(ctx);
});
click_helper_state.egui_state.handle_platform_output(
&click_helper_state.window,
full_output.platform_output,
);
let primitives = click_helper_state
.egui_ctx
.tessellate(full_output.shapes, full_output.pixels_per_point);
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [size.width, size.height],
pixels_per_point: full_output.pixels_per_point,
};
click_helper_state.gpu_state.render(
primitives,
full_output.textures_delta,
screen_descriptor,
);
if !click_helper_state.mode.is_active() {
should_close_click_helper = true;
}
}
WindowEvent::KeyboardInput { event: key_event, .. } => {
use winit::keyboard::{Key, NamedKey};
if key_event.state == ElementState::Pressed {
match &key_event.logical_key {
Key::Named(NamedKey::Escape) => {
click_helper_state.mode.handle_escape();
should_close_click_helper = true;
}
Key::Named(NamedKey::Backspace) => {
click_helper_state.mode.handle_backspace();
}
Key::Character(c) => {
if let Some(ch) = c.chars().next() {
let action = click_helper_state.mode.handle_key(ch);
if let crate::click_helper::ClickHelperAction::Click(
pos,
) = action
{
click_position = Some(pos);
should_close_click_helper = true;
}
}
}
_ => {}
}
}
}
_ => {}
}
if !should_close_click_helper {
return;
}
}
}
if should_close_click_helper {
self.click_helper_state = None;
log::info!("Click Helper mode closed");
if let Some((x, y)) = click_position {
log::info!("Performing click at ({}, {})", x, y);
if let Ok(mouse) = crate::input::Mouse::new() {
if let Err(e) = mouse.move_mouse(x as i32, y as i32) {
log::error!("Failed to move mouse: {}", e);
}
if let Err(e) = mouse.click(crate::input::MouseButton::Left) {
log::error!("Failed to click: {}", e);
}
} else {
log::error!("Failed to create Mouse instance");
}
}
return;
}
}
let Some(state) = self.windows.get_mut(&window_id) else { return };
let _response = state.egui_state.on_window_event(&state.window, &event);
match event {
WindowEvent::CloseRequested => {
state.floating_window.event_handler.dispatch(&FloatingWindowEvent::Close);
self.windows.remove(&window_id);
if self.windows.is_empty() {
event_loop.exit();
}
}
WindowEvent::RedrawRequested => {
let size = state.window.inner_size();
if size.width == 0 || size.height == 0 {
return;
}
let raw_input = state.egui_state.take_egui_input(&state.window);
let pixels_per_point = state.egui_ctx.pixels_per_point();
let logical_width = size.width as f32 / pixels_per_point;
let logical_height = size.height as f32 / pixels_per_point;
let rect =
Rect::from_min_size(Pos2::ZERO, Vec2::new(logical_width, logical_height));
let mut widget_events = Vec::new();
let full_output = state.egui_ctx.run(raw_input, |ctx| {
if let Some(ref mut controller) = state.controller_state {
controller.render(ctx);
} else {
widget_events = state.floating_window.render(ctx, rect);
}
});
if !widget_events.is_empty() {
let window_name =
state.floating_window.config.title.clone().unwrap_or_default();
if let Some(sender) = self.event_senders.get(&window_name) {
for event in widget_events {
log::debug!("Dispatching widget event: {:?}", event);
if sender.send((window_name.clone(), event)).is_err() {
log::warn!("Event receiver dropped for window: {}", window_name);
}
}
} else {
for event in &widget_events {
log::debug!("Widget event (no callback): {:?}", event);
}
}
}
state.egui_state.handle_platform_output(&state.window, full_output.platform_output);
let primitives =
state.egui_ctx.tessellate(full_output.shapes, full_output.pixels_per_point);
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [size.width, size.height],
pixels_per_point: full_output.pixels_per_point,
};
state.gpu_state.render(primitives, full_output.textures_delta, screen_descriptor);
state.widget_has_focus = state.egui_ctx.is_using_pointer()
|| state.egui_ctx.memory(|mem| mem.focused().is_some());
}
WindowEvent::CursorMoved { position, .. } => {
let scale_factor = state.window.scale_factor();
let logical_x = position.x / scale_factor;
let logical_y = position.y / scale_factor;
let inside_shape =
state.floating_window.shape_mask.contains(logical_x as f32, logical_y as f32);
let _ = state.window.set_cursor_hittest(inside_shape);
state.floating_window.on_mouse_move(logical_x, logical_y);
if state.floating_window.is_dragging {
if state.widget_has_focus {
state.floating_window.is_dragging = false;
state.floating_window.drag_offset = None;
} else if let Ok(window_pos) = state.window.outer_position() {
let screen_x = window_pos.x as f64 + position.x;
let screen_y = window_pos.y as f64 + position.y;
if let Some((new_x, new_y)) =
state.floating_window.on_drag_move(screen_x, screen_y, scale_factor)
{
state.window.set_outer_position(PhysicalPosition::new(
new_x as i32,
new_y as i32,
));
state.floating_window.update_position(new_x, new_y);
}
}
}
}
WindowEvent::MouseInput { state: button_state, button, .. } => {
if button == MouseButton::Left {
match button_state {
ElementState::Pressed => {
if let Some((x, y)) = state.floating_window.mouse_pos {
state.floating_window.on_mouse_press(x as f64, y as f64);
}
}
ElementState::Released => {
if let Some((x, y)) = state.floating_window.mouse_pos {
state.floating_window.on_mouse_release(x as f64, y as f64);
}
}
}
}
}
WindowEvent::Resized(size) => {
state.gpu_state.resize(size.width, size.height);
state.floating_window.event_handler.dispatch(&FloatingWindowEvent::Resize {
width: size.width,
height: size.height,
});
}
WindowEvent::Focused(focused) => {
state.floating_window.set_focus(focused);
}
_ => {}
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
self.process_commands(event_loop);
self.process_menu_bar_events(event_loop);
self.create_pending_windows(event_loop);
if let Some(enabled_actions) = self.pending_screenshot_start.take() {
self.create_screenshot_window(event_loop, enabled_actions);
}
#[cfg(all(feature = "click_helper", target_os = "macos"))]
if self.pending_click_helper_start {
log::info!("Creating Click Helper window");
self.pending_click_helper_start = false;
self.create_click_helper_window(event_loop);
}
if let Some(ref state) = self.screenshot_state {
if state.mode.should_exit() {
self.screenshot_state = None;
log::info!("Screenshot mode exited");
}
}
#[cfg(all(feature = "click_helper", target_os = "macos"))]
if let Some(ref state) = self.click_helper_state {
if !state.mode.is_active() {
self.click_helper_state = None;
log::info!("Click Helper mode exited");
}
}
for state in self.windows.values() {
state.window.request_redraw();
}
if let Some(ref state) = self.screenshot_state {
state.window.request_redraw();
}
#[cfg(all(feature = "click_helper", target_os = "macos"))]
if let Some(ref state) = self.click_helper_state {
state.window.request_redraw();
}
}
}