pyloid 0.11.4__py3-none-any.whl → 0.12.0__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.
- pyloid/custom/titlebar.py +116 -0
- pyloid/pyloid.py +129 -23
- {pyloid-0.11.4.dist-info → pyloid-0.12.0.dist-info}/LICENSE +1 -1
- {pyloid-0.11.4.dist-info → pyloid-0.12.0.dist-info}/METADATA +1 -1
- {pyloid-0.11.4.dist-info → pyloid-0.12.0.dist-info}/RECORD +6 -5
- {pyloid-0.11.4.dist-info → pyloid-0.12.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
from PySide6.QtWidgets import (
|
2
|
+
QWidget,
|
3
|
+
QHBoxLayout,
|
4
|
+
QLabel,
|
5
|
+
QPushButton,
|
6
|
+
QVBoxLayout,
|
7
|
+
QApplication,
|
8
|
+
)
|
9
|
+
from PySide6.QtGui import QColor, QPalette, QPixmap
|
10
|
+
from PySide6.QtCore import Qt
|
11
|
+
|
12
|
+
|
13
|
+
class CustomTitleBar(QWidget):
|
14
|
+
def __init__(self, parent=None):
|
15
|
+
super().__init__(parent)
|
16
|
+
self.layout = QHBoxLayout(self)
|
17
|
+
self.layout.setContentsMargins(5, 0, 5, 0)
|
18
|
+
self.layout.setSpacing(0)
|
19
|
+
|
20
|
+
self.icon_label = QLabel()
|
21
|
+
self.icon_label.setFixedSize(20, 20)
|
22
|
+
self.title = QLabel("Custom Title")
|
23
|
+
|
24
|
+
self.minimize_button = QPushButton("-")
|
25
|
+
self.maximize_button = QPushButton("❐")
|
26
|
+
self.close_button = QPushButton("×")
|
27
|
+
|
28
|
+
for button in (self.minimize_button, self.maximize_button, self.close_button):
|
29
|
+
button.setFixedSize(45, 30)
|
30
|
+
button.setFlat(True)
|
31
|
+
|
32
|
+
self.layout.addWidget(self.icon_label)
|
33
|
+
self.layout.addSpacing(5)
|
34
|
+
self.layout.addWidget(self.title)
|
35
|
+
self.layout.addStretch(1)
|
36
|
+
self.layout.addWidget(self.minimize_button)
|
37
|
+
self.layout.addWidget(self.maximize_button)
|
38
|
+
self.layout.addWidget(self.close_button)
|
39
|
+
|
40
|
+
self.minimize_button.clicked.connect(self.window().showMinimized)
|
41
|
+
self.maximize_button.clicked.connect(self.toggle_maximize)
|
42
|
+
self.close_button.clicked.connect(self.window().close)
|
43
|
+
|
44
|
+
self.setFixedHeight(30)
|
45
|
+
self.set_style("darkblue", "white")
|
46
|
+
|
47
|
+
def set_style(self, bg_color, text_color):
|
48
|
+
self.setAutoFillBackground(True)
|
49
|
+
palette = self.palette()
|
50
|
+
bg_qcolor = QColor(bg_color)
|
51
|
+
text_qcolor = QColor(text_color)
|
52
|
+
palette.setColor(QPalette.Window, bg_qcolor)
|
53
|
+
palette.setColor(QPalette.WindowText, text_qcolor)
|
54
|
+
self.setPalette(palette)
|
55
|
+
|
56
|
+
self.title.setStyleSheet(f"color: {text_color}; font-weight: bold;")
|
57
|
+
|
58
|
+
button_style = f"""
|
59
|
+
QPushButton {{
|
60
|
+
background-color: {bg_color};
|
61
|
+
color: {text_color};
|
62
|
+
border: none;
|
63
|
+
font-family: Arial;
|
64
|
+
font-size: 14px;
|
65
|
+
padding: 0px;
|
66
|
+
text-align: center;
|
67
|
+
}}
|
68
|
+
QPushButton:hover {{
|
69
|
+
background-color: {bg_qcolor.lighter(120).name()};
|
70
|
+
}}
|
71
|
+
QPushButton:pressed {{
|
72
|
+
background-color: {bg_qcolor.darker(110).name()};
|
73
|
+
}}
|
74
|
+
"""
|
75
|
+
for button in (self.minimize_button, self.maximize_button, self.close_button):
|
76
|
+
button.setStyleSheet(button_style)
|
77
|
+
|
78
|
+
self.close_button.setStyleSheet(
|
79
|
+
button_style
|
80
|
+
+ f"""
|
81
|
+
QPushButton:hover {{
|
82
|
+
background-color: #e81123;
|
83
|
+
color: white;
|
84
|
+
}}
|
85
|
+
"""
|
86
|
+
)
|
87
|
+
|
88
|
+
def mousePressEvent(self, event):
|
89
|
+
if event.button() == Qt.LeftButton:
|
90
|
+
self.window().moving = True
|
91
|
+
self.window().offset = event.pos()
|
92
|
+
|
93
|
+
def mouseMoveEvent(self, event):
|
94
|
+
if self.window().moving:
|
95
|
+
self.window().move(event.globalPos() - self.window().offset)
|
96
|
+
|
97
|
+
def mouseReleaseEvent(self, event):
|
98
|
+
if event.button() == Qt.LeftButton:
|
99
|
+
self.window().moving = False
|
100
|
+
|
101
|
+
def toggle_maximize(self):
|
102
|
+
if self.window().isMaximized():
|
103
|
+
self.window().showNormal()
|
104
|
+
self.maximize_button.setText("❐")
|
105
|
+
else:
|
106
|
+
self.window().showMaximized()
|
107
|
+
self.maximize_button.setText("❐")
|
108
|
+
|
109
|
+
def set_icon(self, icon_path):
|
110
|
+
pixmap = QPixmap(icon_path)
|
111
|
+
self.icon_label.setPixmap(
|
112
|
+
pixmap.scaled(20, 20, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
113
|
+
)
|
114
|
+
|
115
|
+
def set_title(self, title):
|
116
|
+
self.title.setText(title)
|
pyloid/pyloid.py
CHANGED
@@ -15,10 +15,8 @@ from PySide6.QtGui import (
|
|
15
15
|
QClipboard,
|
16
16
|
QImage,
|
17
17
|
QAction,
|
18
|
-
QPalette,
|
19
|
-
QColor,
|
20
18
|
)
|
21
|
-
from PySide6.QtCore import Qt, Signal, QUrl, QObject, QTimer
|
19
|
+
from PySide6.QtCore import Qt, Signal, QPoint, QUrl, QObject, QTimer, QSize, QEvent
|
22
20
|
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
23
21
|
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings
|
24
22
|
from .api import PyloidAPI, Bridge
|
@@ -32,7 +30,11 @@ import json
|
|
32
30
|
from .autostart import AutoStart
|
33
31
|
from .filewatcher import FileWatcher
|
34
32
|
import logging
|
35
|
-
from PySide6.
|
33
|
+
from PySide6.QtWidgets import (
|
34
|
+
QWidget,
|
35
|
+
QVBoxLayout,
|
36
|
+
)
|
37
|
+
from .custom.titlebar import CustomTitleBar
|
36
38
|
|
37
39
|
# for linux debug
|
38
40
|
os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
|
@@ -187,39 +189,48 @@ class WindowAPI(PyloidAPI):
|
|
187
189
|
"""Returns whether the window has a frame."""
|
188
190
|
window = self.app.get_window_by_id(self.window_id)
|
189
191
|
return window.frame if window else False
|
190
|
-
|
192
|
+
|
191
193
|
@Bridge(result=bool)
|
192
194
|
def getContextMenu(self):
|
193
195
|
"""Returns whether the window has a context menu."""
|
194
196
|
window = self.app.get_window_by_id(self.window_id)
|
195
197
|
return window.context_menu if window else False
|
196
|
-
|
198
|
+
|
197
199
|
@Bridge(result=bool)
|
198
200
|
def getDevTools(self):
|
199
201
|
"""Returns whether the window has developer tools."""
|
200
202
|
window = self.app.get_window_by_id(self.window_id)
|
201
203
|
return window.dev_tools if window else False
|
202
|
-
|
204
|
+
|
203
205
|
@Bridge(result=str)
|
204
206
|
def getTitle(self):
|
205
207
|
"""Returns the title of the window."""
|
206
208
|
window = self.app.get_window_by_id(self.window_id)
|
207
209
|
return window.title if window else ""
|
208
|
-
|
210
|
+
|
209
211
|
@Bridge(result=dict)
|
210
212
|
def getSize(self):
|
211
213
|
"""Returns the size of the window."""
|
212
214
|
window = self.app.get_window_by_id(self.window_id)
|
213
|
-
return
|
214
|
-
|
215
|
+
return (
|
216
|
+
{"width": window.width, "height": window.height}
|
217
|
+
if window
|
218
|
+
else {"width": 0, "height": 0}
|
219
|
+
)
|
220
|
+
|
215
221
|
@Bridge(result=dict)
|
216
222
|
def getPosition(self):
|
217
223
|
"""Returns the position of the window."""
|
218
224
|
window = self.app.get_window_by_id(self.window_id)
|
219
225
|
return {"x": window.x, "y": window.y} if window else {"x": 0, "y": 0}
|
220
|
-
|
221
|
-
|
222
|
-
|
226
|
+
|
227
|
+
@Bridge()
|
228
|
+
def startSystemDrag(self):
|
229
|
+
"""Starts the system drag."""
|
230
|
+
window = self.app.get_window_by_id(self.window_id)
|
231
|
+
if window:
|
232
|
+
window.web_view.start_system_drag()
|
233
|
+
|
223
234
|
|
224
235
|
# class EventAPI(PylonAPI):
|
225
236
|
# def __init__(self, window_id: str, app):
|
@@ -243,6 +254,44 @@ class WindowAPI(PyloidAPI):
|
|
243
254
|
# callback(*args, **kwargs)
|
244
255
|
|
245
256
|
|
257
|
+
# 어차피 load 부분에만 쓰이니까 나중에 분리해서 load 위에서 선언하자.
|
258
|
+
class CustomWebEngineView(QWebEngineView):
|
259
|
+
def __init__(self, parent=None):
|
260
|
+
super().__init__(parent._window)
|
261
|
+
self.parent = parent
|
262
|
+
self.drag_relative_position = None
|
263
|
+
self.is_dragging = False
|
264
|
+
|
265
|
+
def mouse_press_event(self, event):
|
266
|
+
if event.button() == Qt.LeftButton:
|
267
|
+
self.drag_relative_position = event.pos()
|
268
|
+
|
269
|
+
def start_system_drag(self):
|
270
|
+
self.is_dragging = True
|
271
|
+
|
272
|
+
def mouse_move_event(self, event):
|
273
|
+
if not self.parent.frame and self.is_dragging:
|
274
|
+
# 현재 마우스 위치를 전역 좌표로 가져옵니다
|
275
|
+
current_global_pos = event.globalPos()
|
276
|
+
# 새로운 창 위치를 계산합니다
|
277
|
+
new_window_pos = current_global_pos - self.drag_relative_position
|
278
|
+
# 창을 새 위치로 이동합니다
|
279
|
+
self.parent._window.move(new_window_pos)
|
280
|
+
|
281
|
+
def mouse_release_event(self, event):
|
282
|
+
if event.button() == Qt.LeftButton:
|
283
|
+
self.is_dragging = False
|
284
|
+
|
285
|
+
def eventFilter(self, source, event):
|
286
|
+
if self.focusProxy() is source and event.type() == QEvent.MouseButtonPress:
|
287
|
+
self.mouse_press_event(event)
|
288
|
+
if self.focusProxy() is source and event.type() == QEvent.MouseMove:
|
289
|
+
self.mouse_move_event(event)
|
290
|
+
elif self.focusProxy() is source and event.type() == QEvent.MouseButtonRelease:
|
291
|
+
self.mouse_release_event(event)
|
292
|
+
return super().eventFilter(source, event)
|
293
|
+
|
294
|
+
|
246
295
|
class BrowserWindow:
|
247
296
|
def __init__(
|
248
297
|
self,
|
@@ -259,9 +308,8 @@ class BrowserWindow:
|
|
259
308
|
):
|
260
309
|
###########################################################################################
|
261
310
|
self.id = str(uuid.uuid4()) # Generate unique ID
|
262
|
-
|
263
311
|
self._window = QMainWindow()
|
264
|
-
self.web_view =
|
312
|
+
self.web_view = CustomWebEngineView(self)
|
265
313
|
|
266
314
|
self._window.closeEvent = self.closeEvent # Override closeEvent method
|
267
315
|
###########################################################################################
|
@@ -280,10 +328,49 @@ class BrowserWindow:
|
|
280
328
|
self.shortcuts = {}
|
281
329
|
###########################################################################################
|
282
330
|
|
331
|
+
def _set_custom_frame(
|
332
|
+
self,
|
333
|
+
use_custom: bool,
|
334
|
+
title: str = "Custom Title",
|
335
|
+
bg_color: str = "darkblue",
|
336
|
+
text_color: str = "white",
|
337
|
+
icon_path: str = None,
|
338
|
+
):
|
339
|
+
"""Sets or removes the custom frame."""
|
340
|
+
if use_custom:
|
341
|
+
self._window.setWindowFlags(Qt.FramelessWindowHint)
|
342
|
+
self.custom_title_bar = CustomTitleBar(self._window)
|
343
|
+
self.custom_title_bar.set_style(bg_color, text_color)
|
344
|
+
self.custom_title_bar.set_title(title)
|
345
|
+
|
346
|
+
if icon_path:
|
347
|
+
self.custom_title_bar.set_icon(icon_path)
|
348
|
+
|
349
|
+
layout = QVBoxLayout()
|
350
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
351
|
+
layout.setSpacing(0)
|
352
|
+
layout.addWidget(self.custom_title_bar)
|
353
|
+
layout.addWidget(self.web_view)
|
354
|
+
|
355
|
+
central_widget = QWidget()
|
356
|
+
central_widget.setLayout(layout)
|
357
|
+
self._window.setCentralWidget(central_widget)
|
358
|
+
|
359
|
+
# Add properties for window movement
|
360
|
+
self._window.moving = False
|
361
|
+
self._window.offset = QPoint()
|
362
|
+
else:
|
363
|
+
self._window.setWindowFlags(Qt.Window)
|
364
|
+
self._window.setCentralWidget(self.web_view)
|
365
|
+
self.custom_title_bar = None
|
366
|
+
|
367
|
+
self._window.show()
|
368
|
+
|
283
369
|
def _load(self):
|
284
|
-
self.
|
370
|
+
self.set_title(self.title)
|
285
371
|
|
286
|
-
self.
|
372
|
+
self.set_size(self.width, self.height)
|
373
|
+
self.set_position(self.x, self.y)
|
287
374
|
|
288
375
|
# allow local file access to remote urls
|
289
376
|
self.web_view.settings().setAttribute(
|
@@ -357,6 +444,12 @@ class BrowserWindow:
|
|
357
444
|
console.log('pyloid.EventAPI object initialized:', window.pyloid.EventAPI);
|
358
445
|
|
359
446
|
%s
|
447
|
+
|
448
|
+
document.addEventListener('mousedown', function (e) {
|
449
|
+
if (e.target.hasAttribute('data-pyloid-drag-region')) {
|
450
|
+
window.pyloid.WindowAPI.startSystemDrag();
|
451
|
+
}
|
452
|
+
});
|
360
453
|
|
361
454
|
// Dispatch a custom event to signal that the initialization is ready
|
362
455
|
const event = new CustomEvent('pyloidReady');
|
@@ -385,11 +478,13 @@ class BrowserWindow:
|
|
385
478
|
self._load()
|
386
479
|
file_path = os.path.abspath(file_path) # absolute path
|
387
480
|
self.web_view.setUrl(QUrl.fromLocalFile(file_path))
|
481
|
+
self.web_view.focusProxy().installEventFilter(self.web_view)
|
388
482
|
|
389
483
|
def load_url(self, url):
|
390
484
|
"""Sets the URL of the window."""
|
391
485
|
self._load()
|
392
486
|
self.web_view.setUrl(QUrl(url))
|
487
|
+
self.web_view.focusProxy().installEventFilter(self.web_view)
|
393
488
|
|
394
489
|
###########################################################################################
|
395
490
|
# Set Parameters
|
@@ -602,6 +697,7 @@ class BrowserWindow:
|
|
602
697
|
}})();
|
603
698
|
"""
|
604
699
|
self.web_view.page().runJavaScript(script)
|
700
|
+
|
605
701
|
###########################################################################################
|
606
702
|
# Get Properties
|
607
703
|
###########################################################################################
|
@@ -622,7 +718,7 @@ class BrowserWindow:
|
|
622
718
|
def get_id(self):
|
623
719
|
"""Returns the ID of the window."""
|
624
720
|
return self.id
|
625
|
-
|
721
|
+
|
626
722
|
def get_size(self) -> Dict[str, int]:
|
627
723
|
"""Returns the size of the window."""
|
628
724
|
return {"width": self.width, "height": self.height}
|
@@ -630,7 +726,7 @@ class BrowserWindow:
|
|
630
726
|
def get_position(self) -> Dict[str, int]:
|
631
727
|
"""Returns the position of the window."""
|
632
728
|
return {"x": self.x, "y": self.y}
|
633
|
-
|
729
|
+
|
634
730
|
def get_title(self) -> str:
|
635
731
|
"""Returns the title of the window."""
|
636
732
|
return self.title
|
@@ -638,13 +734,23 @@ class BrowserWindow:
|
|
638
734
|
def get_url(self) -> str:
|
639
735
|
"""Returns the URL of the window."""
|
640
736
|
return self.web_view.url().toString()
|
641
|
-
|
737
|
+
|
642
738
|
def get_visible(self) -> bool:
|
643
739
|
"""Returns the visibility of the window."""
|
644
740
|
return self._window.isVisible()
|
645
741
|
|
646
|
-
|
647
|
-
|
742
|
+
def set_resizable(self, resizable: bool):
|
743
|
+
"""창의 크기 조절 가능 여부를 설정합니다."""
|
744
|
+
self.resizable = resizable
|
745
|
+
if resizable:
|
746
|
+
self._window.setWindowFlags(
|
747
|
+
self._window.windowFlags() & ~Qt.MSWindowsFixedSizeDialogHint
|
748
|
+
)
|
749
|
+
else:
|
750
|
+
self._window.setWindowFlags(
|
751
|
+
self._window.windowFlags() | Qt.MSWindowsFixedSizeDialogHint
|
752
|
+
)
|
753
|
+
self._window.show() # 변경사항을 적용하기 위해 창을 다시 표시합니다.
|
648
754
|
|
649
755
|
|
650
756
|
class _WindowController(QObject):
|
@@ -683,7 +789,7 @@ class Pyloid(QApplication):
|
|
683
789
|
|
684
790
|
self.app_name = app_name
|
685
791
|
self.app_path = sys.executable
|
686
|
-
|
792
|
+
|
687
793
|
self.auto_start = AutoStart(self.app_name, self.app_path)
|
688
794
|
|
689
795
|
self.animation_timer = None
|
@@ -198,4 +198,4 @@ Apache License
|
|
198
198
|
distributed under the License is distributed on an "AS IS" BASIS,
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200
200
|
See the License for the specific language governing permissions and
|
201
|
-
limitations under the License.
|
201
|
+
limitations under the License.
|
@@ -1,13 +1,14 @@
|
|
1
1
|
pyloid/__init__.py,sha256=OOPhOKNQVmAM8hnfTeE7lHzxb8LsFNcgegBAvDrA-vY,293
|
2
2
|
pyloid/api.py,sha256=whgfvPr1A6iwZ1Ewo-0FnOUNnt1K58c-P7YjzuQHcUM,194
|
3
3
|
pyloid/autostart.py,sha256=K7DQYl4LHItvPp0bt1V9WwaaZmVSTeGvadkcwG-KKrI,3899
|
4
|
+
pyloid/custom/titlebar.py,sha256=itzK9pJbZMQ7BKca9kdbuHMffurrw15UijR6OU03Xsk,3894
|
4
5
|
pyloid/filewatcher.py,sha256=n8N56D65le5TpsgxXb7z-FO_0lqv4UYD4yGq_UuMrAs,1285
|
5
6
|
pyloid/monitor.py,sha256=fqDnZ_7dpxVZLVJ5gCluDRY2USrQ5YL_fw1AnYivhsk,12741
|
6
|
-
pyloid/pyloid.py,sha256=
|
7
|
+
pyloid/pyloid.py,sha256=MGfBrB6uIrFpKWkoznu52i7sd4WDRTSorqak3zzzsus,48199
|
7
8
|
pyloid/timer.py,sha256=1bYhqte3rV77vaeMUkcTgmx2ux7FtCqLCx9lIC2-COg,4360
|
8
9
|
pyloid/tray.py,sha256=rXgdkvzGxtie_EIcTSA7fjuta4nJk5THhNkGFcfv5Ew,634
|
9
10
|
pyloid/utils.py,sha256=DQerZWU_0o8dHcJ5y3yXf9i5OXn7KQZqU-hVBq3uPUA,711
|
10
|
-
pyloid-0.
|
11
|
-
pyloid-0.
|
12
|
-
pyloid-0.
|
13
|
-
pyloid-0.
|
11
|
+
pyloid-0.12.0.dist-info/LICENSE,sha256=MTYF-6xpRekyTUglRweWtbfbwBL1I_3Bgfbm_SNOuI8,11525
|
12
|
+
pyloid-0.12.0.dist-info/METADATA,sha256=3nNffa8kFieZFpiJjE5nXHq9XA27JtvpSbBAWKBRndE,6069
|
13
|
+
pyloid-0.12.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
14
|
+
pyloid-0.12.0.dist-info/RECORD,,
|
File without changes
|