更新0.02版本
This commit is contained in:
+58
-11
@@ -43,12 +43,25 @@ def _load_logo_pixmap(path):
|
||||
|
||||
|
||||
def _win32_set_ellipse_window_rgn(hwnd, w, h):
|
||||
"""用系统区域裁切 HWND。"""
|
||||
"""用系统区域裁切 HWND,w/h 为逻辑像素,内部换算为物理像素。"""
|
||||
if not hwnd or w < 2 or h < 2:
|
||||
return
|
||||
gdi32 = ctypes.windll.gdi32
|
||||
user32 = ctypes.windll.user32
|
||||
hrgn = gdi32.CreateEllipticRgn(0, 0, w, h)
|
||||
# 获取窗口所在显示器的 DPI,换算为物理像素
|
||||
try:
|
||||
MONITOR_DEFAULTTONEAREST = 2
|
||||
hmon = ctypes.windll.user32.MonitorFromWindow(wintypes.HWND(hwnd), MONITOR_DEFAULTTONEAREST)
|
||||
dpi_x = ctypes.c_uint(0)
|
||||
dpi_y = ctypes.c_uint(0)
|
||||
MDT_EFFECTIVE_DPI = 0
|
||||
ctypes.windll.shcore.GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, ctypes.byref(dpi_x), ctypes.byref(dpi_y))
|
||||
scale = dpi_x.value / 96.0
|
||||
except Exception:
|
||||
scale = 1.0
|
||||
pw = max(2, int(w * scale))
|
||||
ph = max(2, int(h * scale))
|
||||
hrgn = gdi32.CreateEllipticRgn(0, 0, pw, ph)
|
||||
if not hrgn:
|
||||
return
|
||||
user32.SetWindowRgn(hwnd, hrgn, True)
|
||||
@@ -139,14 +152,21 @@ class FloatBall(QWidget):
|
||||
_win32_set_ellipse_window_rgn(wid, self.width(), self.height())
|
||||
_win32_disable_dwm_rounded_shell(wid)
|
||||
|
||||
def moveEvent(self, event):
|
||||
super().moveEvent(event)
|
||||
# 跨屏移动后 DPI 可能变化,重新设置物理像素 region
|
||||
if sys.platform == "win32":
|
||||
wid = int(self.winId())
|
||||
if wid:
|
||||
_win32_set_ellipse_window_rgn(wid, self.width(), self.height())
|
||||
|
||||
def _apply_window_flags(self):
|
||||
flags = (
|
||||
Qt.WindowType.FramelessWindowHint
|
||||
| Qt.WindowType.Tool
|
||||
| Qt.WindowType.NoDropShadowWindowHint
|
||||
| Qt.WindowType.WindowStaysOnTopHint
|
||||
)
|
||||
if self._stays_on_top:
|
||||
flags |= Qt.WindowType.WindowStaysOnTopHint
|
||||
self.setWindowFlags(flags)
|
||||
|
||||
def _get_glow(self): return self._glow
|
||||
@@ -154,6 +174,14 @@ class FloatBall(QWidget):
|
||||
from PyQt6.QtCore import pyqtProperty
|
||||
_glow_prop = pyqtProperty(float, _get_glow, _set_glow)
|
||||
|
||||
def _current_screen_geometry(self):
|
||||
"""返回当前窗口所在屏幕的可用区域,找不到时回退到主屏幕。"""
|
||||
center = self.geometry().center()
|
||||
screen = QApplication.screenAt(center)
|
||||
if screen is None:
|
||||
screen = QApplication.primaryScreen()
|
||||
return screen.availableGeometry()
|
||||
|
||||
def _place_default(self):
|
||||
screen = QApplication.primaryScreen().availableGeometry()
|
||||
self.move(
|
||||
@@ -161,6 +189,19 @@ class FloatBall(QWidget):
|
||||
screen.top() + screen.height() // 2 - self.height() // 2,
|
||||
)
|
||||
|
||||
def place_on_screen_of(self, widget):
|
||||
"""将悬浮球放到指定 widget 所在屏幕的右侧中央。"""
|
||||
# frameGeometry().center() 直接是全局坐标,对顶层窗口最可靠
|
||||
global_center = widget.frameGeometry().center()
|
||||
screen_obj = QApplication.screenAt(global_center)
|
||||
if screen_obj is None:
|
||||
screen_obj = QApplication.primaryScreen()
|
||||
screen = screen_obj.availableGeometry()
|
||||
self.move(
|
||||
screen.right() - self.width() - 20,
|
||||
screen.top() + screen.height() // 2 - self.height() // 2,
|
||||
)
|
||||
|
||||
def _paint_radius_outer(self):
|
||||
w, h = self.width(), self.height()
|
||||
return min(w, h) * 0.5 - 0.5
|
||||
@@ -224,15 +265,17 @@ class FloatBall(QWidget):
|
||||
# 3. Logo(cover 铺满圆)
|
||||
if not self._logo.isNull():
|
||||
p.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True)
|
||||
cover = max(1, int(2 * r))
|
||||
side = max(1, int(2 * r))
|
||||
scaled = self._logo.scaled(
|
||||
cover, cover,
|
||||
side, side,
|
||||
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
|
||||
Qt.TransformationMode.SmoothTransformation
|
||||
)
|
||||
lx = int(cx - scaled.width() / 2)
|
||||
ly = int(cy - scaled.height() / 2)
|
||||
p.drawPixmap(lx, ly, scaled)
|
||||
# 居中裁切到 side×side,避免 Expanding 后图片偏大导致偏移
|
||||
ox = (scaled.width() - side) // 2
|
||||
oy = (scaled.height() - side) // 2
|
||||
cropped = scaled.copy(ox, oy, side, side)
|
||||
p.drawPixmap(int(cx - side / 2), int(cy - side / 2), cropped)
|
||||
|
||||
# 4. 覆在图上的雾面(毛玻璃「蒙在内容上」)
|
||||
veil = QLinearGradient(cx, cy - r, cx, cy + r)
|
||||
@@ -280,7 +323,11 @@ class FloatBall(QWidget):
|
||||
if self._dragging:
|
||||
new_pos = self.pos() + delta
|
||||
self._drag_start = event.globalPosition().toPoint()
|
||||
screen = QApplication.primaryScreen().availableGeometry()
|
||||
# 用鼠标当前位置所在屏幕来限制边界,支持多显示器拖动
|
||||
cursor_screen = QApplication.screenAt(event.globalPosition().toPoint())
|
||||
if cursor_screen is None:
|
||||
cursor_screen = QApplication.primaryScreen()
|
||||
screen = cursor_screen.availableGeometry()
|
||||
new_pos.setX(max(screen.left(), min(new_pos.x(), screen.right() - self.width())))
|
||||
new_pos.setY(max(screen.top(), min(new_pos.y(), screen.bottom() - self.height())))
|
||||
self.move(new_pos)
|
||||
@@ -307,7 +354,7 @@ class FloatBall(QWidget):
|
||||
self._glow_anim.start()
|
||||
|
||||
def _snap_to_edge(self):
|
||||
screen = QApplication.primaryScreen().availableGeometry()
|
||||
screen = self._current_screen_geometry()
|
||||
cx = self.x() + self.width() // 2
|
||||
cy = self.y() + self.height() // 2
|
||||
dists = {
|
||||
|
||||
Reference in New Issue
Block a user