pyloid 0.11.4__py3-none-any.whl → 0.12.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|