204 files changed
+51505
No files matched your search
@@ -0,0 +1,142 @@
|
||||
from typing import Any
|
||||
|
||||
import cairo
|
||||
from gi.repository import GLib, GtkLayerShell # type: ignore
|
||||
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.widget import Widget
|
||||
from utils.roam import modus_service
|
||||
from widgets.wayland import WaylandWindow as Window
|
||||
|
||||
|
||||
class MouseCapture(Window):
|
||||
"""A background overlay that captures outside clicks without blocking child window interactions"""
|
||||
|
||||
def __init__(self, layer: str, child_window: Window, **kwargs):
|
||||
super().__init__(
|
||||
layer="top", # Use top layer to capture events
|
||||
anchor="top bottom left right",
|
||||
exclusivity="auto",
|
||||
title="modus",
|
||||
name="MouseCapture",
|
||||
keyboard_mode="none", # Don't steal keyboard
|
||||
all_visible=False,
|
||||
visible=False,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
GtkLayerShell.set_exclusive_zone(self, -1)
|
||||
|
||||
self.child_window = child_window
|
||||
|
||||
# Ensure child window is on overlay layer to be above this capture
|
||||
if hasattr(self.child_window, "layer"):
|
||||
self.child_window.layer = "overlay"
|
||||
|
||||
if hasattr(self.child_window, "_init_mousecapture"):
|
||||
self.child_window._init_mousecapture(self)
|
||||
|
||||
# Create transparent event box that captures clicks
|
||||
self.event_box = EventBox(
|
||||
events=["button-press-event"],
|
||||
all_visible=True,
|
||||
)
|
||||
self.event_box.connect("button-press-event", self.on_overlay_click)
|
||||
self.children = [self.event_box]
|
||||
|
||||
# Make the overlay transparent
|
||||
self.set_app_paintable(True)
|
||||
self.connect("draw", self.on_draw)
|
||||
|
||||
# Add escape key binding to child window
|
||||
if hasattr(self.child_window, "add_keybinding"):
|
||||
self.child_window.add_keybinding("Escape", self.hide_child_window)
|
||||
|
||||
def on_draw(self, _widget, cr):
|
||||
"""Make overlay transparent"""
|
||||
cr.set_source_rgba(0, 0, 0, 0) # Fully transparent
|
||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||
cr.paint()
|
||||
return False
|
||||
|
||||
def on_overlay_click(self, _widget, event):
|
||||
"""Handle overlay clicks - check if click is outside child window"""
|
||||
if not self.child_window.is_visible():
|
||||
return False
|
||||
|
||||
# Get click coordinates
|
||||
click_x = event.x_root
|
||||
click_y = event.y_root
|
||||
|
||||
# Get child window bounds
|
||||
try:
|
||||
child_x, child_y = self.child_window.get_position()
|
||||
child_allocation = self.child_window.get_allocation()
|
||||
|
||||
# Check if click is inside child window bounds
|
||||
inside_child = (
|
||||
child_x <= click_x <= child_x + child_allocation.width
|
||||
and child_y <= click_y <= child_y + child_allocation.height
|
||||
)
|
||||
|
||||
if not inside_child:
|
||||
# Click is outside child window - hide it with delay
|
||||
GLib.timeout_add(
|
||||
50, lambda: self.hide_child_window(None, None) or False
|
||||
)
|
||||
return True # Consume the event
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error checking click position: {e}")
|
||||
# If we can't determine position, hide child window to be safe
|
||||
GLib.timeout_add(50, lambda: self.hide_child_window(None, None) or False)
|
||||
return True
|
||||
|
||||
# Click is inside child window - don't consume event
|
||||
return False
|
||||
|
||||
def show_child_window(self, widget: Widget = None, event: Any = None) -> None:
|
||||
self.set_child_window_visible(True)
|
||||
|
||||
def hide_child_window(self, widget: Widget = None, event: Any = None) -> None:
|
||||
self.set_child_window_visible(False)
|
||||
|
||||
def set_child_window_visible(self, visible: bool) -> None:
|
||||
if visible:
|
||||
self.child_window.show()
|
||||
self.show()
|
||||
else:
|
||||
self.child_window.hide()
|
||||
self.hide()
|
||||
|
||||
if hasattr(self.child_window, "_set_mousecapture"):
|
||||
self.child_window._set_mousecapture(visible)
|
||||
|
||||
def toggle_mousecapture(self, *_) -> None:
|
||||
if self.is_visible():
|
||||
self.set_child_window_visible(False)
|
||||
else:
|
||||
self.set_child_window_visible(True)
|
||||
|
||||
|
||||
class DropDownMouseCapture(MouseCapture):
|
||||
"""A specialized MouseCapture for dropdown menus with service integration"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
modus_service.connect("dropdowns-hide-changed", self.dropdowns_hide_changed)
|
||||
|
||||
def hide_child_window(self, widget: Widget = None, event: Any = None) -> None:
|
||||
"""Hide child window and update dropdown service state"""
|
||||
# Update service state before hiding to prevent conflicts
|
||||
if hasattr(self.child_window, "id"):
|
||||
if str(modus_service.current_dropdown) == str(self.child_window.id):
|
||||
modus_service.current_dropdown = None
|
||||
super().hide_child_window(widget, event)
|
||||
|
||||
def dropdowns_hide_changed(self, widget: Widget = None, event: Any = None) -> None:
|
||||
"""Handle dropdown service hide changes"""
|
||||
if hasattr(self.child_window, "id"):
|
||||
if modus_service.current_dropdown == self.child_window.id:
|
||||
return
|
||||
return self.hide_child_window(widget, event)
|
||||
Reference in new issue
Block a user