simpleui-py 1.2.2__tar.gz → 1.3.0__tar.gz
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.
- {simpleui_py-1.2.2/simpleui_py.egg-info → simpleui_py-1.3.0}/PKG-INFO +3 -2
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/README.md +2 -1
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/pyproject.toml +1 -1
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/__init__.py +9 -13
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/components/__init__.py +3 -3
- simpleui_py-1.3.0/simpleui/components/button.py +105 -0
- simpleui_py-1.3.0/simpleui/components/input.py +85 -0
- simpleui_py-1.3.0/simpleui/components/layout.py +57 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/components/window.py +113 -23
- simpleui_py-1.3.0/simpleui/core.py +118 -0
- simpleui_py-1.3.0/simpleui/icons.py +100 -0
- simpleui_py-1.3.0/simpleui/stage.py +245 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0/simpleui_py.egg-info}/PKG-INFO +3 -2
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui_py.egg-info/SOURCES.txt +2 -0
- simpleui_py-1.2.2/simpleui/components/button.py +0 -259
- simpleui_py-1.2.2/simpleui/components/input.py +0 -264
- simpleui_py-1.2.2/simpleui/components/layout.py +0 -174
- simpleui_py-1.2.2/simpleui/core.py +0 -1177
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/LICENSE +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/MANIFEST.in +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/setup.cfg +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/components/alert.py +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/components/card.py +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/components/progress.py +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/components/switch.py +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/components/tag.py +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/py.typed +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui/utils.py +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui_py.egg-info/dependency_links.txt +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui_py.egg-info/entry_points.txt +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui_py.egg-info/requires.txt +0 -0
- {simpleui_py-1.2.2 → simpleui_py-1.3.0}/simpleui_py.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simpleui-py
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Fluent风格Python UI组件库 - 简约设计,无限可能
|
|
5
5
|
Author-email: SimpleUI Team <hello@simpleui.dev>
|
|
6
6
|
License: MIT
|
|
@@ -46,7 +46,8 @@ Dynamic: license-file
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
\##
|
|
49
|
+
\## 安装:pip install simpleui=={版本} 引用:from simpleui import {内容}
|
|
50
|
+
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
SimpleUI - Fluent 风格 Python UI 组件库
|
|
3
|
-
简约设计,无限可能
|
|
4
3
|
"""
|
|
5
4
|
|
|
6
|
-
__version__ = "1.
|
|
5
|
+
__version__ = "1.3.0"
|
|
7
6
|
__author__ = "SimpleUI Team"
|
|
8
7
|
|
|
9
|
-
# 从 core 导入核心功能
|
|
10
8
|
from .core import (
|
|
11
9
|
Component,
|
|
12
10
|
Theme,
|
|
@@ -17,7 +15,6 @@ from .core import (
|
|
|
17
15
|
get_theme,
|
|
18
16
|
)
|
|
19
17
|
|
|
20
|
-
# 从 components 导入所有组件
|
|
21
18
|
from .components import (
|
|
22
19
|
Button,
|
|
23
20
|
ButtonGroup,
|
|
@@ -31,18 +28,17 @@ from .components import (
|
|
|
31
28
|
Container,
|
|
32
29
|
Row,
|
|
33
30
|
Column,
|
|
31
|
+
Window,
|
|
32
|
+
WindowManager,
|
|
34
33
|
)
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
from .stage import Stage, Sprite # 新增
|
|
36
|
+
|
|
37
37
|
from .utils import preview, serve, demo, playground
|
|
38
38
|
|
|
39
|
-
# 导出列表
|
|
40
39
|
__all__ = [
|
|
41
|
-
# 版本信息
|
|
42
40
|
"__version__",
|
|
43
41
|
"__author__",
|
|
44
|
-
|
|
45
|
-
# 核心类
|
|
46
42
|
"Component",
|
|
47
43
|
"Theme",
|
|
48
44
|
"ThemeType",
|
|
@@ -50,8 +46,6 @@ __all__ = [
|
|
|
50
46
|
"export_html",
|
|
51
47
|
"set_theme",
|
|
52
48
|
"get_theme",
|
|
53
|
-
|
|
54
|
-
# 组件
|
|
55
49
|
"Button",
|
|
56
50
|
"ButtonGroup",
|
|
57
51
|
"Input",
|
|
@@ -64,8 +58,10 @@ __all__ = [
|
|
|
64
58
|
"Container",
|
|
65
59
|
"Row",
|
|
66
60
|
"Column",
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
"Window",
|
|
62
|
+
"WindowManager",
|
|
63
|
+
"Stage", # 新增
|
|
64
|
+
"Sprite", # 新增
|
|
69
65
|
"preview",
|
|
70
66
|
"serve",
|
|
71
67
|
"demo",
|
|
@@ -10,7 +10,7 @@ from .tag import Tag
|
|
|
10
10
|
from .progress import Progress
|
|
11
11
|
from .alert import Alert
|
|
12
12
|
from .layout import Container, Row, Column
|
|
13
|
-
from .window import Window, WindowManager
|
|
13
|
+
from .window import Window, WindowManager
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
16
|
"Button",
|
|
@@ -25,6 +25,6 @@ __all__ = [
|
|
|
25
25
|
"Container",
|
|
26
26
|
"Row",
|
|
27
27
|
"Column",
|
|
28
|
-
"Window",
|
|
29
|
-
"WindowManager",
|
|
28
|
+
"Window",
|
|
29
|
+
"WindowManager",
|
|
30
30
|
]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""按钮组件"""
|
|
2
|
+
from typing import Optional, Literal
|
|
3
|
+
from ..core import Component
|
|
4
|
+
from ..icons import get_icon
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Button(Component):
|
|
8
|
+
"""按钮组件"""
|
|
9
|
+
|
|
10
|
+
VARIANT_CLASSES = {
|
|
11
|
+
"primary": "sui-btn-primary",
|
|
12
|
+
"secondary": "sui-btn-secondary",
|
|
13
|
+
"outline": "sui-btn-outline",
|
|
14
|
+
"ghost": "sui-btn-ghost",
|
|
15
|
+
"danger": "sui-btn-danger",
|
|
16
|
+
"success": "sui-btn-success",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
SIZE_CLASSES = {
|
|
20
|
+
"sm": "sui-btn-sm",
|
|
21
|
+
"md": "",
|
|
22
|
+
"lg": "sui-btn-lg",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
text: Optional[str] = None,
|
|
28
|
+
variant: str = "primary",
|
|
29
|
+
size: str = "md",
|
|
30
|
+
icon: Optional[str] = None,
|
|
31
|
+
icon_position: str = "left",
|
|
32
|
+
loading: bool = False,
|
|
33
|
+
disabled: bool = False,
|
|
34
|
+
href: Optional[str] = None,
|
|
35
|
+
onclick: Optional[str] = None,
|
|
36
|
+
**kwargs
|
|
37
|
+
):
|
|
38
|
+
super().__init__(**kwargs)
|
|
39
|
+
self.text = text
|
|
40
|
+
self.variant = variant
|
|
41
|
+
self.size = size
|
|
42
|
+
self.icon = icon
|
|
43
|
+
self.icon_position = icon_position
|
|
44
|
+
self.loading = loading
|
|
45
|
+
self.disabled = disabled
|
|
46
|
+
self.href = href
|
|
47
|
+
self.onclick = onclick
|
|
48
|
+
|
|
49
|
+
def _get_base_classes(self) -> str:
|
|
50
|
+
classes = ["sui-btn", self.VARIANT_CLASSES.get(self.variant, ""), self.SIZE_CLASSES.get(self.size, "")]
|
|
51
|
+
if self.loading:
|
|
52
|
+
classes.append("sui-btn-loading")
|
|
53
|
+
if self.class_name:
|
|
54
|
+
classes.append(self.class_name)
|
|
55
|
+
return " ".join(classes)
|
|
56
|
+
|
|
57
|
+
def _get_attributes_str(self) -> str:
|
|
58
|
+
attrs = super()._get_attributes_str()
|
|
59
|
+
if self.disabled or self.loading:
|
|
60
|
+
attrs += " disabled"
|
|
61
|
+
if self.onclick and not self.disabled:
|
|
62
|
+
attrs += f' onclick="{self.onclick}"'
|
|
63
|
+
return attrs
|
|
64
|
+
|
|
65
|
+
def _render_icon(self) -> str:
|
|
66
|
+
if not self.icon:
|
|
67
|
+
return ""
|
|
68
|
+
|
|
69
|
+
icon_path = get_icon(self.icon)
|
|
70
|
+
if not icon_path:
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
icon_html = f'<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16">{icon_path}</svg>'
|
|
74
|
+
|
|
75
|
+
if self.icon_position == "right":
|
|
76
|
+
return f'<span>{self.text or ""}</span>{icon_html}'
|
|
77
|
+
return f'{icon_html}<span>{self.text or ""}</span>'
|
|
78
|
+
|
|
79
|
+
def render(self) -> str:
|
|
80
|
+
content = self._render_icon() if self.icon else (self.text or "")
|
|
81
|
+
content += self._render_children()
|
|
82
|
+
|
|
83
|
+
if self.href:
|
|
84
|
+
return f'<a href="{self.href}" {self._get_attributes_str()}>{content}</a>'
|
|
85
|
+
return f'<button {self._get_attributes_str()}>{content}</button>'
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def icon_button(cls, icon: str, variant: str = "primary", size: str = "md", **kwargs) -> "Button":
|
|
89
|
+
"""创建图标按钮"""
|
|
90
|
+
return cls(variant=variant, size=size, icon=icon, class_name="sui-btn-icon " + kwargs.pop("class_name", ""), **kwargs)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ButtonGroup(Component):
|
|
94
|
+
"""按钮组"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, *buttons, **kwargs):
|
|
97
|
+
super().__init__(**kwargs)
|
|
98
|
+
for btn in buttons:
|
|
99
|
+
self.add_child(btn)
|
|
100
|
+
|
|
101
|
+
def _get_base_classes(self) -> str:
|
|
102
|
+
return f"sui-btn-group {self.class_name}".strip()
|
|
103
|
+
|
|
104
|
+
def render(self) -> str:
|
|
105
|
+
return f'<div {self._get_attributes_str()}>{self._render_children()}</div>'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""输入框组件"""
|
|
2
|
+
from typing import Optional, Literal
|
|
3
|
+
from ..core import Component
|
|
4
|
+
from ..icons import get_icon
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Input(Component):
|
|
8
|
+
"""输入框组件"""
|
|
9
|
+
|
|
10
|
+
STATUS_CLASSES = {"default": "", "success": "sui-input-success", "error": "sui-input-error"}
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
label: Optional[str] = None,
|
|
15
|
+
placeholder: Optional[str] = None,
|
|
16
|
+
value: Optional[str] = None,
|
|
17
|
+
type: str = "text",
|
|
18
|
+
status: str = "default",
|
|
19
|
+
helper_text: Optional[str] = None,
|
|
20
|
+
icon: Optional[str] = None, # 直接使用图标名
|
|
21
|
+
disabled: bool = False,
|
|
22
|
+
required: bool = False,
|
|
23
|
+
**kwargs
|
|
24
|
+
):
|
|
25
|
+
super().__init__(**kwargs)
|
|
26
|
+
self.label = label
|
|
27
|
+
self.placeholder = placeholder
|
|
28
|
+
self.value = value
|
|
29
|
+
self.type = type
|
|
30
|
+
self.status = status
|
|
31
|
+
self.helper_text = helper_text
|
|
32
|
+
self.icon = icon
|
|
33
|
+
self.disabled = disabled
|
|
34
|
+
self.required = required
|
|
35
|
+
|
|
36
|
+
def render(self) -> str:
|
|
37
|
+
html = ['<div class="sui-input-wrapper">']
|
|
38
|
+
|
|
39
|
+
if self.label:
|
|
40
|
+
html.append(f'<label class="sui-input-label">{self.label}{" *" if self.required else ""}</label>')
|
|
41
|
+
|
|
42
|
+
input_class = f"sui-input {self.STATUS_CLASSES.get(self.status, '')}".strip()
|
|
43
|
+
attrs = [f'type="{self.type}"', f'class="{input_class}"']
|
|
44
|
+
|
|
45
|
+
if self.placeholder:
|
|
46
|
+
attrs.append(f'placeholder="{self.placeholder}"')
|
|
47
|
+
if self.value:
|
|
48
|
+
attrs.append(f'value="{self.value}"')
|
|
49
|
+
if self.disabled:
|
|
50
|
+
attrs.append("disabled")
|
|
51
|
+
if self.required:
|
|
52
|
+
attrs.append("required")
|
|
53
|
+
|
|
54
|
+
if self.icon:
|
|
55
|
+
icon_path = get_icon(self.icon)
|
|
56
|
+
html.append('<div class="sui-input-icon-wrapper">')
|
|
57
|
+
html.append(f'<span class="sui-input-icon"><svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">{icon_path}</svg></span>')
|
|
58
|
+
html.append(f'<input {" ".join(attrs)}>')
|
|
59
|
+
html.append('</div>')
|
|
60
|
+
else:
|
|
61
|
+
html.append(f'<input {" ".join(attrs)}>')
|
|
62
|
+
|
|
63
|
+
if self.helper_text:
|
|
64
|
+
h_class = "sui-input-helper" + (" sui-input-error-text" if self.status == "error" else "")
|
|
65
|
+
html.append(f'<span class="{h_class}">{self.helper_text}</span>')
|
|
66
|
+
|
|
67
|
+
html.append('</div>')
|
|
68
|
+
return "".join(html)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TextArea(Component):
|
|
72
|
+
"""多行文本"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, label: Optional[str] = None, placeholder: Optional[str] = None, rows: int = 4, **kwargs):
|
|
75
|
+
super().__init__(**kwargs)
|
|
76
|
+
self.label = label
|
|
77
|
+
self.placeholder = placeholder
|
|
78
|
+
self.rows = rows
|
|
79
|
+
|
|
80
|
+
def render(self) -> str:
|
|
81
|
+
html = ['<div class="sui-input-wrapper">']
|
|
82
|
+
if self.label:
|
|
83
|
+
html.append(f'<label class="sui-input-label">{self.label}</label>')
|
|
84
|
+
html.append(f'<textarea class="sui-input sui-textarea" rows="{self.rows}" placeholder="{self.placeholder or ""}"></textarea></div>')
|
|
85
|
+
return "".join(html)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""布局组件"""
|
|
2
|
+
from typing import Union
|
|
3
|
+
from ..core import Component
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Container(Component):
|
|
7
|
+
"""容器"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, *children, max_width: str = "1200px", **kwargs):
|
|
10
|
+
super().__init__(**kwargs)
|
|
11
|
+
self.max_width = max_width
|
|
12
|
+
for child in children:
|
|
13
|
+
self.add_child(child)
|
|
14
|
+
|
|
15
|
+
def _get_style_str(self) -> str:
|
|
16
|
+
return f"max-width:{self.max_width};margin:0 auto;padding:2rem"
|
|
17
|
+
|
|
18
|
+
def render(self) -> str:
|
|
19
|
+
return f'<div class="sui-container" style="{self._get_style_str()}">{self._render_children()}</div>'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Row(Component):
|
|
23
|
+
"""行布局"""
|
|
24
|
+
|
|
25
|
+
ALIGN = {"start": "flex-start", "center": "center", "end": "flex-end"}
|
|
26
|
+
JUSTIFY = {"start": "flex-start", "center": "center", "end": "flex-end", "between": "space-between",
|
|
27
|
+
"around": "space-around"}
|
|
28
|
+
|
|
29
|
+
def __init__(self, *children, gap: str = "1rem", justify: str = "start", align: str = "center", **kwargs):
|
|
30
|
+
super().__init__(**kwargs)
|
|
31
|
+
self.gap = gap
|
|
32
|
+
self.justify = justify
|
|
33
|
+
self.align = align
|
|
34
|
+
for child in children:
|
|
35
|
+
self.add_child(child)
|
|
36
|
+
|
|
37
|
+
def _get_style_str(self) -> str:
|
|
38
|
+
return f"display:flex;flex-wrap:wrap;gap:{self.gap};justify-content:{self.JUSTIFY.get(self.justify, self.justify)};align-items:{self.ALIGN.get(self.align, self.align)}"
|
|
39
|
+
|
|
40
|
+
def render(self) -> str:
|
|
41
|
+
return f'<div class="sui-row" style="{self._get_style_str()}">{self._render_children()}</div>'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Column(Component):
|
|
45
|
+
"""列布局"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, *children, flex: int = 1, **kwargs):
|
|
48
|
+
super().__init__(**kwargs)
|
|
49
|
+
self.flex = flex
|
|
50
|
+
for child in children:
|
|
51
|
+
self.add_child(child)
|
|
52
|
+
|
|
53
|
+
def _get_style_str(self) -> str:
|
|
54
|
+
return f"flex:{self.flex};min-width:200px"
|
|
55
|
+
|
|
56
|
+
def render(self) -> str:
|
|
57
|
+
return f'<div class="sui-column" style="{self._get_style_str()}">{self._render_children()}</div>'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
窗口组件
|
|
3
|
-
|
|
3
|
+
可拖动的窗口,使用 TurboWarp 坐标系
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from typing import Optional, List, Union
|
|
@@ -11,12 +11,19 @@ class Window(Component):
|
|
|
11
11
|
"""
|
|
12
12
|
可拖动窗口组件
|
|
13
13
|
|
|
14
|
+
使用 TurboWarp 坐标系:
|
|
15
|
+
- 原点在舞台中心
|
|
16
|
+
- X轴:右为正,左为负
|
|
17
|
+
- Y轴:上为正,下为负
|
|
18
|
+
|
|
14
19
|
参数:
|
|
15
20
|
title: 窗口标题
|
|
16
21
|
width: 窗口宽度
|
|
17
22
|
height: 窗口高度
|
|
18
|
-
x:
|
|
19
|
-
y:
|
|
23
|
+
x: X坐标(中心为0,右为正)
|
|
24
|
+
y: Y坐标(中心为0,上为正)
|
|
25
|
+
stage_width: 舞台宽度
|
|
26
|
+
stage_height: 舞台高度
|
|
20
27
|
draggable: 是否可拖动
|
|
21
28
|
resizable: 是否可调整大小
|
|
22
29
|
closable: 是否可关闭
|
|
@@ -25,7 +32,7 @@ class Window(Component):
|
|
|
25
32
|
fixed_child: 子组件是否随窗口移动
|
|
26
33
|
|
|
27
34
|
示例:
|
|
28
|
-
win = Window(title="
|
|
35
|
+
win = Window(title="窗口", x=100, y=100)
|
|
29
36
|
win.add_child(Button("按钮", variant="primary"))
|
|
30
37
|
preview(win)
|
|
31
38
|
"""
|
|
@@ -35,30 +42,93 @@ class Window(Component):
|
|
|
35
42
|
title: str = "窗口",
|
|
36
43
|
width: str = "400px",
|
|
37
44
|
height: str = "300px",
|
|
38
|
-
x: int =
|
|
39
|
-
y: int =
|
|
45
|
+
x: int = 0,
|
|
46
|
+
y: int = 0,
|
|
47
|
+
stage_width: int = 800,
|
|
48
|
+
stage_height: int = 600,
|
|
40
49
|
draggable: bool = True,
|
|
41
50
|
resizable: bool = True,
|
|
42
51
|
closable: bool = True,
|
|
43
52
|
minimize: bool = True,
|
|
44
53
|
header: bool = True,
|
|
45
|
-
fixed_child: bool = True,
|
|
54
|
+
fixed_child: bool = True,
|
|
46
55
|
**kwargs
|
|
47
56
|
):
|
|
48
57
|
super().__init__(**kwargs)
|
|
49
58
|
self.title = title
|
|
50
59
|
self.width = width
|
|
51
60
|
self.height = height
|
|
52
|
-
self.
|
|
53
|
-
self.
|
|
61
|
+
self._x = x
|
|
62
|
+
self._y = y
|
|
63
|
+
self.stage_width = stage_width
|
|
64
|
+
self.stage_height = stage_height
|
|
54
65
|
self.draggable = draggable
|
|
55
66
|
self.resizable = resizable
|
|
56
67
|
self.closable = closable
|
|
57
68
|
self.minimize = minimize
|
|
58
69
|
self.header = header
|
|
59
|
-
self.fixed_child = fixed_child
|
|
70
|
+
self.fixed_child = fixed_child
|
|
60
71
|
self._children: List[Union[Component, str]] = []
|
|
61
72
|
self._minimized = False
|
|
73
|
+
|
|
74
|
+
# 计算中心点
|
|
75
|
+
self._center_x = stage_width // 2
|
|
76
|
+
self._center_y = stage_height // 2
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def x(self) -> int:
|
|
80
|
+
"""获取 X 坐标"""
|
|
81
|
+
return self._x
|
|
82
|
+
|
|
83
|
+
@x.setter
|
|
84
|
+
def x(self, value: int):
|
|
85
|
+
"""设置 X 坐标"""
|
|
86
|
+
self._x = value
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def y(self) -> int:
|
|
90
|
+
"""获取 Y 坐标"""
|
|
91
|
+
return self._y
|
|
92
|
+
|
|
93
|
+
@y.setter
|
|
94
|
+
def y(self, value: int):
|
|
95
|
+
"""设置 Y 坐标"""
|
|
96
|
+
self._y = value
|
|
97
|
+
|
|
98
|
+
def to_screen_coords(self) -> tuple:
|
|
99
|
+
"""
|
|
100
|
+
将 TurboWarp 坐标转换为屏幕坐标
|
|
101
|
+
|
|
102
|
+
返回:
|
|
103
|
+
(screen_x, screen_y) 屏幕坐标
|
|
104
|
+
"""
|
|
105
|
+
screen_x = self._center_x + self._x
|
|
106
|
+
screen_y = self._center_y - self._y # Y轴反转
|
|
107
|
+
return int(screen_x), int(screen_y)
|
|
108
|
+
|
|
109
|
+
def move(self, dx: int, dy: int) -> 'Window':
|
|
110
|
+
"""
|
|
111
|
+
移动窗口
|
|
112
|
+
|
|
113
|
+
参数:
|
|
114
|
+
dx: X方向移动量(右为正)
|
|
115
|
+
dy: Y方向移动量(上为正)
|
|
116
|
+
"""
|
|
117
|
+
self._x += dx
|
|
118
|
+
self._y += dy
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
def goto(self, x: int, y: int) -> 'Window':
|
|
122
|
+
"""
|
|
123
|
+
移动到指定位置
|
|
124
|
+
|
|
125
|
+
参数:
|
|
126
|
+
x: X坐标
|
|
127
|
+
y: Y坐标
|
|
128
|
+
"""
|
|
129
|
+
self._x = x
|
|
130
|
+
self._y = y
|
|
131
|
+
return self
|
|
62
132
|
|
|
63
133
|
def add_child(self, child: Union[Component, str]) -> 'Window':
|
|
64
134
|
"""添加子组件"""
|
|
@@ -80,12 +150,15 @@ class Window(Component):
|
|
|
80
150
|
|
|
81
151
|
def render(self) -> str:
|
|
82
152
|
"""渲染窗口"""
|
|
153
|
+
# 转换坐标
|
|
154
|
+
screen_x, screen_y = self.to_screen_coords()
|
|
155
|
+
|
|
83
156
|
# 窗口样式
|
|
84
157
|
window_style = f"""
|
|
85
158
|
width: {self.width};
|
|
86
159
|
height: {self.height};
|
|
87
|
-
left: {
|
|
88
|
-
top: {
|
|
160
|
+
left: {screen_x}px;
|
|
161
|
+
top: {screen_y}px;
|
|
89
162
|
"""
|
|
90
163
|
|
|
91
164
|
# 标题栏按钮
|
|
@@ -101,11 +174,14 @@ class Window(Component):
|
|
|
101
174
|
# 调整大小
|
|
102
175
|
resize_handle = '<div class="sui-window-resize"></div>' if self.resizable else ''
|
|
103
176
|
|
|
104
|
-
# 子组件容器
|
|
177
|
+
# 子组件容器
|
|
105
178
|
children_class = "sui-window-content" if self.fixed_child else "sui-window-content-absolute"
|
|
106
179
|
|
|
180
|
+
# 存储坐标信息
|
|
181
|
+
data_attrs = f'data-x="{self._x}" data-y="{self._y}" data-stage-width="{self.stage_width}" data-stage-height="{self.stage_height}"'
|
|
182
|
+
|
|
107
183
|
html = f'''
|
|
108
|
-
<div class="{self._get_base_classes()}" style="{window_style}" {drag_attr}>
|
|
184
|
+
<div class="{self._get_base_classes()}" style="{window_style}" {drag_attr} {data_attrs}>
|
|
109
185
|
<!-- 标题栏 -->
|
|
110
186
|
{'<div class="sui-window-header">' if self.header else '<div class="sui-window-header" style="display:none">'}
|
|
111
187
|
<span class="sui-window-title">{self.title}</span>
|
|
@@ -129,26 +205,40 @@ class Window(Component):
|
|
|
129
205
|
class WindowManager:
|
|
130
206
|
"""
|
|
131
207
|
窗口管理器
|
|
132
|
-
用于管理多个窗口
|
|
133
208
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
wm.add_window(Window(title="窗口2"))
|
|
138
|
-
preview(wm)
|
|
209
|
+
参数:
|
|
210
|
+
stage_width: 舞台宽度
|
|
211
|
+
stage_height: 舞台高度
|
|
139
212
|
"""
|
|
140
213
|
|
|
141
|
-
def __init__(
|
|
214
|
+
def __init__(
|
|
215
|
+
self,
|
|
216
|
+
stage_width: int = 800,
|
|
217
|
+
stage_height: int = 600
|
|
218
|
+
):
|
|
142
219
|
self.windows: List[Window] = []
|
|
220
|
+
self.stage_width = stage_width
|
|
221
|
+
self.stage_height = stage_height
|
|
143
222
|
|
|
144
223
|
def add_window(self, window: Window) -> 'WindowManager':
|
|
145
224
|
"""添加窗口"""
|
|
146
225
|
self.windows.append(window)
|
|
147
226
|
return self
|
|
148
227
|
|
|
149
|
-
def create_window(
|
|
228
|
+
def create_window(
|
|
229
|
+
self,
|
|
230
|
+
title: str = "窗口",
|
|
231
|
+
x: int = 0,
|
|
232
|
+
y: int = 0,
|
|
233
|
+
**kwargs
|
|
234
|
+
) -> Window:
|
|
150
235
|
"""创建并添加窗口"""
|
|
151
|
-
|
|
236
|
+
if 'stage_width' not in kwargs:
|
|
237
|
+
kwargs['stage_width'] = self.stage_width
|
|
238
|
+
if 'stage_height' not in kwargs:
|
|
239
|
+
kwargs['stage_height'] = self.stage_height
|
|
240
|
+
|
|
241
|
+
win = Window(title=title, x=x, y=y, **kwargs)
|
|
152
242
|
self.windows.append(win)
|
|
153
243
|
return win
|
|
154
244
|
|