bubbleUI 0.4.4a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,94 @@
1
+ """Core frosted-glass toolkit for PySide6.
2
+
3
+ The package contains the reusable pieces only:
4
+
5
+ * :class:`FrostStyle` / :func:`paint_frost_overlay` -- material model and paint
6
+ helper. All material controls, including the vignette, live here. Every
7
+ field is also exposed as a QSS ``qproperty-frost*`` on the painting widgets
8
+ (:class:`GlassBackdrop`, :class:`GlassMenuBar`, :class:`GlassPanel`), so the
9
+ four presets in :mod:`.config` can be tuned from the theme style sheet; see
10
+ :func:`apply_glass_style` for the ``"thin"`` variants.
11
+ * :class:`BlurredGlassPanel` -- blur the content underneath one widget.
12
+ * :class:`DesktopSource` -- capture the screen and supply blurred regions for
13
+ live window-level glass.
14
+ * :class:`WallpaperSource` -- static system-wallpaper backdrop that reloads
15
+ only when the wallpaper changes (Mica-like idle behaviour).
16
+ * :class:`GlassRuntimeSettings` -- background source, FPS, GPU, energy saving
17
+ and focus-loss behaviour.
18
+ * :class:`GpuSurface` -- optional QOpenGLWidget painting surface.
19
+ * :class:`EnergyModeController` -- standard/energy switching with an animated
20
+ cross-fade, including automatic switching on focus loss.
21
+
22
+ Window chrome, parameter panels and demo styling belong to the application,
23
+ not to this library.
24
+ """
25
+
26
+ from .energy import EnergyModeController
27
+ from .glass import (
28
+ BlurSource,
29
+ BlurredGlassPanel,
30
+ FrostStyle,
31
+ apply_glass_style,
32
+ mix,
33
+ paint_frost_overlay,
34
+ qcolor,
35
+ rounded_path,
36
+ GlassMainWindow,
37
+ GlassDialog,
38
+ GlassMenuBar,
39
+ GlassPanel,
40
+ GlassBackdrop
41
+ )
42
+ from .gpu import GpuBackdrop, GpuSurface
43
+ from .imaging import (
44
+ DesktopSource,
45
+ WallpaperSource,
46
+ blur_qimage,
47
+ clear_texture_cache,
48
+ exclude_window_from_capture,
49
+ noise_texture,
50
+ pil_to_qimage,
51
+ qimage_to_pil,
52
+ system_wallpaper_path,
53
+ )
54
+ from .settings import (
55
+ BACKGROUND_SCREEN,
56
+ BACKGROUND_WALLPAPER,
57
+ GlassRuntimeSettings,
58
+ )
59
+ from .config import * # noqa: F403
60
+
61
+ __all__ = [
62
+ "BACKGROUND_SCREEN",
63
+ "BACKGROUND_WALLPAPER",
64
+ "BlurSource",
65
+ "BlurredGlassPanel",
66
+ "DesktopSource",
67
+ "EnergyModeController",
68
+ "FrostStyle",
69
+ "GlassRuntimeSettings",
70
+ "GpuBackdrop",
71
+ "GpuSurface",
72
+ "WallpaperSource",
73
+ "blur_qimage",
74
+ "clear_texture_cache",
75
+ "exclude_window_from_capture",
76
+ "mix",
77
+ "noise_texture",
78
+ "paint_frost_overlay",
79
+ "pil_to_qimage",
80
+ "qcolor",
81
+ "qimage_to_pil",
82
+ "rounded_path",
83
+ "system_wallpaper_path",
84
+ "BUBBLE_BACKGROUND_NORMAL",
85
+ "BUBBLE_BACKGROUND_THIN",
86
+ "BUBBLE_SIDEBAR_NORMAL",
87
+ "BUBBLE_SIDEBAR_THIN",
88
+ "GlassMainWindow",
89
+ "GlassDialog",
90
+ "GlassMenuBar",
91
+ "GlassPanel",
92
+ "FROST_PRESETS",
93
+ "apply_glass_style"
94
+ ]
@@ -0,0 +1,77 @@
1
+ from .settings import (
2
+ BACKGROUND_WALLPAPER,
3
+ GlassRuntimeSettings,
4
+ FrostStyle
5
+ )
6
+
7
+ RUNTIME = GlassRuntimeSettings(
8
+ background_mode=BACKGROUND_WALLPAPER,
9
+ fps=24,
10
+ gpu=False,
11
+ auto_energy_on_focus_loss=True,
12
+ transition_ms=250,
13
+ )
14
+ CAPTURE_SCALE = 0.5 # region capture scale; 1.0 = sharper, more CPU
15
+
16
+ # Whole-window desktop glass material.
17
+ BUBBLE_BACKGROUND_THIN = FrostStyle(
18
+ tint="#4c4c4c",
19
+ opacity=60,
20
+ blur=24,
21
+ frost=20,
22
+ noise=5,
23
+ sheen=15,
24
+ saturation=100,
25
+ brightness=100,
26
+ ) # 透明模式
27
+
28
+ # Whole-window desktop glass material.
29
+ BUBBLE_BACKGROUND_NORMAL = FrostStyle(
30
+ tint="#272727",
31
+ opacity=90,
32
+ blur=48,
33
+ frost=3,
34
+ noise=3,
35
+ sheen=0,
36
+ saturation=75,
37
+ brightness=60,
38
+ ) # 透明模式
39
+
40
+ # Whole-window desktop glass material.
41
+ BUBBLE_SIDEBAR_NORMAL = FrostStyle(
42
+ tint="#202020",
43
+ opacity=70,
44
+ blur=48,
45
+ frost=5,
46
+ noise=0,
47
+ sheen=40,
48
+ saturation=80,
49
+ brightness=55,
50
+ ) # 透明模式
51
+
52
+ # Whole-window desktop glass material.
53
+ BUBBLE_SIDEBAR_THIN = FrostStyle(
54
+ tint="#545454",
55
+ opacity=30,
56
+ blur=24,
57
+ frost=5,
58
+ noise=0,
59
+ sheen=10,
60
+ saturation=130,
61
+ brightness=120,
62
+ ) # 透明模式
63
+ #: The four built-in materials. The same values are written to the theme
64
+ #: style sheets (`res/styles/dark.qss` / `light.qss`) as
65
+ #: `qproperty-frost*` rules; those QSS rules are applied at runtime and are
66
+ #: the recommended place to tune the material. The presets below remain the
67
+ #: code-level defaults (what a standalone glasskit app gets without a style
68
+ #: sheet). Widget mapping used by the theme style sheets:
69
+ #:
70
+ #: * `background_normal` / `background_thin` -> :class:GlassBackdrop
71
+ #: * `sidebar_normal` / `sidebar_thin` -> :class:GlassMenuBar
72
+ FROST_PRESETS = {
73
+ "background_normal": BUBBLE_BACKGROUND_NORMAL,
74
+ "background_thin": BUBBLE_BACKGROUND_THIN,
75
+ "sidebar_normal": BUBBLE_SIDEBAR_NORMAL,
76
+ "sidebar_thin": BUBBLE_SIDEBAR_THIN,
77
+ }
@@ -0,0 +1,202 @@
1
+ """Standard <-> energy-saving mode switching for glass windows.
2
+
3
+ The controller owns:
4
+
5
+ * explicit energy-saving state and optional automatic switching on focus loss
6
+ * a 0.25s cross-fade between the standard and energy backgrounds
7
+ * the paint hook for that transition
8
+
9
+ The host supplies a ``render_background(energy: bool) -> QPixmap`` callback and
10
+ reacts to ``energyChanged`` / ``transitionFinished`` to pause its desktop
11
+ source, hide/show a GPU surface, or update its controls.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Callable
17
+
18
+ from PySide6.QtCore import (
19
+ QEasingCurve,
20
+ QEvent,
21
+ QObject,
22
+ QTimer,
23
+ Qt,
24
+ QVariantAnimation,
25
+ Signal,
26
+ )
27
+ from PySide6.QtGui import QPainter, QPixmap
28
+ from PySide6.QtWidgets import QWidget
29
+
30
+
31
+ class EnergyModeController(QObject):
32
+ """Drive standard/energy mode with an animated cross-fade."""
33
+
34
+ energyChanged = Signal(bool)
35
+ transitionFinished = Signal(bool)
36
+
37
+ def __init__(self, window: QWidget,
38
+ render_background: Callable[[bool], QPixmap],
39
+ *, duration_ms: int = 250,
40
+ auto_on_focus_loss: bool = True,
41
+ parent: QObject | None = None):
42
+ super().__init__(parent if parent is not None else window)
43
+ self._window = window
44
+ self._render_background = render_background
45
+ self._duration_ms = max(0, int(duration_ms))
46
+ self._auto = bool(auto_on_focus_loss)
47
+ self._explicit = False
48
+ self._energy_active = False
49
+ self._transition_active = False
50
+ self._transition_t = 0.0
51
+ self._transition_from: QPixmap | None = None
52
+ self._transition_to: QPixmap | None = None
53
+
54
+ self._anim = QVariantAnimation(self)
55
+ self._anim.setDuration(self._duration_ms)
56
+ self._anim.setEasingCurve(QEasingCurve.Type.InOutCubic)
57
+ self._anim.valueChanged.connect(self._on_anim_value)
58
+ self._anim.finished.connect(self._on_anim_finished)
59
+
60
+ self._window.installEventFilter(self)
61
+
62
+ # -- public state ------------------------------------------------------
63
+ @property
64
+ def active(self) -> bool:
65
+ """Whether the energy configuration is the current target."""
66
+ return self._energy_active
67
+
68
+ @property
69
+ def transitioning(self) -> bool:
70
+ return self._transition_active
71
+
72
+ @property
73
+ def progress(self) -> float:
74
+ return self._transition_t
75
+
76
+ @property
77
+ def explicit(self) -> bool:
78
+ return self._explicit
79
+
80
+ @property
81
+ def auto_on_focus_loss(self) -> bool:
82
+ return self._auto
83
+
84
+ @property
85
+ def duration_ms(self) -> int:
86
+ return self._duration_ms
87
+
88
+ def set_explicit(self, enabled: bool) -> None:
89
+ self._explicit = bool(enabled)
90
+ self._refresh()
91
+
92
+ def set_auto_on_focus_loss(self, enabled: bool) -> None:
93
+ self._auto = bool(enabled)
94
+ self._refresh()
95
+
96
+ def set_duration(self, duration_ms: int) -> None:
97
+ self._duration_ms = max(0, int(duration_ms))
98
+ self._anim.setDuration(self._duration_ms)
99
+
100
+ # -- painting ----------------------------------------------------------
101
+ def paint(self, painter: QPainter) -> bool:
102
+ """Draw the cross-fade. Returns True while a transition is active."""
103
+ if not self._transition_active:
104
+ return False
105
+ if self._transition_from is not None:
106
+ painter.drawPixmap(0, 0, self._transition_from)
107
+ if self._transition_to is not None:
108
+ painter.setOpacity(self._transition_t)
109
+ painter.drawPixmap(0, 0, self._transition_to)
110
+ return True
111
+
112
+ # -- internal state ----------------------------------------------------
113
+ def _refresh(self) -> None:
114
+ if not self._window.isVisible():
115
+ return
116
+ target = bool(self._explicit or (self._auto and not self._window.isActiveWindow()))
117
+ if target == self._energy_active:
118
+ return
119
+ self._start_transition(target)
120
+
121
+ def _start_transition(self, target: bool) -> None:
122
+ if self._transition_active:
123
+ from_pixmap = self._render_transition_frame()
124
+ self._transition_active = False
125
+ self._anim.stop()
126
+ else:
127
+ from_pixmap = self._render_background(self._energy_active)
128
+
129
+ self._energy_active = bool(target)
130
+ self.energyChanged.emit(self._energy_active)
131
+
132
+ if self._duration_ms <= 0:
133
+ self._transition_active = False
134
+ self.transitionFinished.emit(self._energy_active)
135
+ self._window.update()
136
+ return
137
+
138
+ to_pixmap = self._render_background(self._energy_active)
139
+ self._transition_from = from_pixmap
140
+ self._transition_to = to_pixmap
141
+ self._transition_t = 0.0
142
+ self._transition_active = True
143
+ self._anim.stop()
144
+ self._anim.setStartValue(0.0)
145
+ self._anim.setEndValue(1.0)
146
+ self._anim.start()
147
+ self._window.update()
148
+
149
+ def _on_anim_value(self, value) -> None:
150
+ if not self._transition_active:
151
+ return
152
+ self._transition_t = float(value)
153
+ self._window.update()
154
+
155
+ def _on_anim_finished(self) -> None:
156
+ if not self._transition_active:
157
+ return
158
+ self._transition_active = False
159
+ self._transition_from = None
160
+ self._transition_to = None
161
+ self._transition_t = 1.0
162
+ self.transitionFinished.emit(self._energy_active)
163
+ self._window.update()
164
+
165
+ def _render_transition_frame(self) -> QPixmap:
166
+ pixmap = QPixmap(self._window.size())
167
+ pixmap.fill(Qt.GlobalColor.transparent)
168
+ painter = QPainter(pixmap)
169
+ if self._transition_from is not None:
170
+ painter.drawPixmap(0, 0, self._transition_from)
171
+ if self._transition_to is not None:
172
+ painter.setOpacity(self._transition_t)
173
+ painter.drawPixmap(0, 0, self._transition_to)
174
+ painter.end()
175
+ return pixmap
176
+
177
+ def _scale_snapshots(self) -> None:
178
+ size = self._window.size()
179
+ if self._transition_from is not None:
180
+ self._transition_from = self._transition_from.scaled(
181
+ size, Qt.AspectRatioMode.IgnoreAspectRatio,
182
+ Qt.TransformationMode.SmoothTransformation,
183
+ )
184
+ if self._transition_to is not None:
185
+ self._transition_to = self._transition_to.scaled(
186
+ size, Qt.AspectRatioMode.IgnoreAspectRatio,
187
+ Qt.TransformationMode.SmoothTransformation,
188
+ )
189
+
190
+ # -- focus / resize ----------------------------------------------------
191
+ def eventFilter(self, obj, event) -> bool: # noqa: N802
192
+ if obj is self._window:
193
+ event_type = event.type()
194
+ if event_type == QEvent.Type.Show:
195
+ QTimer.singleShot(0, self._refresh)
196
+ elif event_type in (QEvent.Type.WindowActivate,
197
+ QEvent.Type.WindowDeactivate,
198
+ QEvent.Type.ActivationChange):
199
+ self._refresh()
200
+ elif event_type == QEvent.Type.Resize and self._transition_active:
201
+ self._scale_snapshots()
202
+ return super().eventFilter(obj, event)