Skip to content

Commit 0dddcb2

Browse files
HackLinjiuyuexushengj
authored andcommitted
[UI] Add AssetBrowser tool
1 parent 5929cfa commit 0dddcb2

File tree

12 files changed

+2105
-3
lines changed

12 files changed

+2105
-3
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ src/preppipe_gui_pyside6/forms/generated
171171
*.settings.db
172172
*.settings.db.*
173173

174+
# thumbnails
175+
thumbnails
176+
174177
# version files for pyinstaller build
175178
versionfile_*.txt
176179

src/preppipe_gui_pyside6/componentwidgets/assetcardwidget.py

Lines changed: 454 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
from PySide6.QtCore import QRect, QSize, Qt
2+
from PySide6.QtWidgets import QLayout, QWidgetItem
3+
4+
5+
class FlowLayout(QLayout):
6+
"""
7+
流式布局管理器,将组件按水平方向排列,一行空间不足时自动换行。
8+
"""
9+
_horizontal_spacing: int
10+
_vertical_spacing: int
11+
_item_list: list[QWidgetItem]
12+
_item_size: QSize
13+
14+
def __init__(self, parent=None, margin=4, spacing=3):
15+
super().__init__(parent)
16+
if parent is not None:
17+
self.setContentsMargins(margin, margin, margin, margin)
18+
self._horizontal_spacing = spacing
19+
self._vertical_spacing = spacing
20+
self._item_list = []
21+
self._item_size = QSize(160, 200)
22+
23+
def setHorizontalSpacing(self, spacing):
24+
self._horizontal_spacing = spacing
25+
self.update()
26+
27+
def setVerticalSpacing(self, spacing):
28+
self._vertical_spacing = spacing
29+
self.update()
30+
31+
def setSpacing(self, spacing):
32+
self._horizontal_spacing = spacing
33+
self._vertical_spacing = spacing
34+
self.update()
35+
36+
def spacing(self):
37+
return self._horizontal_spacing
38+
39+
def horizontalSpacing(self):
40+
return self._horizontal_spacing
41+
42+
def verticalSpacing(self):
43+
return self._vertical_spacing
44+
45+
def addItem(self, item):
46+
self._item_list.append(item)
47+
48+
def insertWidget(self, index, widget):
49+
item = QWidgetItem(widget)
50+
self._item_list.insert(index, item)
51+
self.update()
52+
53+
def count(self):
54+
return len(self._item_list)
55+
56+
def itemAt(self, index):
57+
if 0 <= index < len(self._item_list):
58+
return self._item_list[index]
59+
return None
60+
61+
def takeAt(self, index):
62+
if 0 <= index < len(self._item_list):
63+
return self._item_list.pop(index)
64+
return None
65+
66+
def expandingDirections(self):
67+
return Qt.Orientations(Qt.Orientation(0))
68+
69+
def hasHeightForWidth(self):
70+
return True
71+
72+
def heightForWidth(self, width):
73+
return self._do_layout(QRect(0, 0, width, 0), False)
74+
75+
def setGeometry(self, rect):
76+
super().setGeometry(rect)
77+
self._do_layout(rect, True)
78+
79+
def sizeHint(self):
80+
return self.minimumSize()
81+
82+
def minimumSize(self):
83+
if not self._item_list:
84+
return QSize()
85+
86+
size = QSize()
87+
for item in self._item_list:
88+
size = size.expandedTo(item.minimumSize())
89+
90+
margins = self.contentsMargins()
91+
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom())
92+
return size
93+
94+
def _do_layout(self, rect, test_only):
95+
margins = self.contentsMargins()
96+
margin_left = int(margins.left())
97+
margin_top = int(margins.top())
98+
margin_right = int(margins.right())
99+
margin_bottom = int(margins.bottom())
100+
101+
x = rect.x() + margin_left
102+
y = rect.y() + margin_top
103+
line_height = 0
104+
105+
max_width = int(rect.width() - margin_left - margin_right)
106+
content_right = rect.x() + rect.width() - margin_right
107+
108+
# 直接访问间距属性,避免方法调用可能的问题
109+
horizontal_spacing = int(self._horizontal_spacing)
110+
vertical_spacing = int(self._vertical_spacing)
111+
112+
for item in self._item_list:
113+
item_size = item.sizeHint() if item.sizeHint().isValid() else self._item_size
114+
item_width = int(item_size.width())
115+
item_height = int(item_size.height())
116+
117+
if item_width > max_width:
118+
item_width = max_width
119+
120+
next_x = x + item_width + horizontal_spacing
121+
122+
if next_x > content_right and line_height > 0:
123+
# 换行处理
124+
x = rect.x() + margin_left
125+
y = y + line_height + vertical_spacing
126+
next_x = x + item_width + horizontal_spacing
127+
line_height = 0
128+
129+
safe_width = item_width
130+
safe_height = item_height
131+
132+
if not test_only:
133+
# 确保所有坐标使用整数,避免像素对齐问题
134+
item.setGeometry(QRect(int(x), int(y), safe_width, safe_height))
135+
136+
x = next_x
137+
line_height = max(line_height, safe_height)
138+
139+
final_height = y + line_height + margin_bottom - rect.y()
140+
return final_height
141+
142+
def setItemSize(self, size):
143+
self._item_size = size
144+
self.update()
145+
146+
def itemSize(self):
147+
return self._item_size
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<customwidgets>
4+
<customwidget>
5+
<class>FlowLayout</class>
6+
<extends>QLayout</extends>
7+
<header>preppipe_gui_pyside6.componentwidgets.flowlayout</header>
8+
</customwidget>
9+
</customwidgets>
10+
<class>AssetBrowserWidget</class>
11+
<widget class="QWidget" name="AssetBrowserWidget">
12+
<property name="geometry">
13+
<rect>
14+
<x>0</x>
15+
<y>0</y>
16+
<width>900</width>
17+
<height>600</height>
18+
</rect>
19+
</property>
20+
<property name="windowTitle">
21+
<string>Asset Browser</string>
22+
</property>
23+
<layout class="QVBoxLayout" name="verticalLayout">
24+
<item>
25+
<widget class="QSplitter" name="splitter">
26+
<property name="sizePolicy">
27+
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
28+
<horstretch>0</horstretch>
29+
<verstretch>0</verstretch>
30+
</sizepolicy>
31+
</property>
32+
<property name="orientation">
33+
<enum>Qt::Orientation::Horizontal</enum>
34+
</property>
35+
<widget class="QFrame" name="categoriesFrame">
36+
<property name="sizePolicy">
37+
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
38+
<horstretch>0</horstretch>
39+
<verstretch>0</verstretch>
40+
</sizepolicy>
41+
</property>
42+
<property name="minimumSize">
43+
<size>
44+
<width>120</width>
45+
<height>0</height>
46+
</size>
47+
</property>
48+
<property name="frameShape">
49+
<enum>QFrame::Shape::StyledPanel</enum>
50+
</property>
51+
<property name="frameShadow">
52+
<enum>QFrame::Shadow::Raised</enum>
53+
</property>
54+
<layout class="QVBoxLayout" name="categoriesLayout">
55+
<item>
56+
<widget class="QListWidget" name="categoriesListWidget"/>
57+
</item>
58+
59+
</layout>
60+
</widget>
61+
<widget class="QFrame" name="thumbnailsFrame">
62+
<property name="sizePolicy">
63+
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
64+
<horstretch>0</horstretch>
65+
<verstretch>0</verstretch>
66+
</sizepolicy>
67+
</property>
68+
<property name="frameShape">
69+
<enum>QFrame::Shape::StyledPanel</enum>
70+
</property>
71+
<property name="frameShadow">
72+
<enum>QFrame::Shadow::Raised</enum>
73+
</property>
74+
<layout class="QVBoxLayout" name="thumbnailsLayout">
75+
<item>
76+
<widget class="QScrollArea" name="thumbnailsScrollArea">
77+
<property name="widgetResizable">
78+
<bool>true</bool>
79+
</property>
80+
<widget class="QWidget" name="thumbnailsScrollAreaWidgetContents">
81+
<property name="geometry">
82+
<rect>
83+
<x>0</x>
84+
<y>0</y>
85+
<width>730</width>
86+
<height>560</height>
87+
</rect>
88+
</property>
89+
<layout class="QVBoxLayout" name="thumbnailsScrollAreaLayout">
90+
<item>
91+
<widget class="QLabel" name="categoryTitleLabel">
92+
<property name="sizePolicy">
93+
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
94+
<horstretch>0</horstretch>
95+
<verstretch>0</verstretch>
96+
</sizepolicy>
97+
</property>
98+
<property name="text">
99+
<string>Select a category</string>
100+
</property>
101+
<property name="alignment">
102+
<set>Qt::AlignCenter</set>
103+
</property>
104+
</widget>
105+
</item>
106+
<item>
107+
<widget class="QWidget" name="thumbnailsContainerWidget">
108+
<property name="sizePolicy">
109+
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
110+
<horstretch>0</horstretch>
111+
<verstretch>0</verstretch>
112+
</sizepolicy>
113+
</property>
114+
<layout class="FlowLayout" name="thumbnailsFlowLayout">
115+
<property name="horizontalSpacing">
116+
<number>10</number>
117+
</property>
118+
<property name="verticalSpacing">
119+
<number>10</number>
120+
</property>
121+
</layout>
122+
</widget>
123+
</item>
124+
</layout>
125+
</widget>
126+
</widget>
127+
</item>
128+
</layout>
129+
</widget>
130+
</widget>
131+
</item>
132+
</layout>
133+
</widget>
134+
<resources/>
135+
<connections/>
136+
</ui>

src/preppipe_gui_pyside6/mainwindow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def requestOpenDocument(self, relpath : str | None = None) -> None:
130130
if docs := os.environ.get("PREPPIPE_DOCS"):
131131
docs_root = os.path.abspath(docs)
132132
if len(docs_root) == 0:
133-
docs_root = os.path.join(get_executable_base_dir(), 'docs')
133+
docs_root = os.path.join(SettingsDict.get_executable_base_dir(), 'docs')
134134
if not os.path.isdir(docs_root):
135135
QMessageBox.critical(self, self._tr_documentation_not_found_title.get(), self._tr_documentation_not_found_details_dir.format(dir=docs_root))
136136
return

src/preppipe_gui_pyside6/navigatorwidget.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .toolwidgets.imagepack import *
1313
from .toolwidgets.setting import *
1414
from .toolwidgets.maininput import *
15+
from .toolwidgets.assetbrowser import *
1516

1617
class ToolNode:
1718
info : ToolWidgetInfo | None
@@ -24,6 +25,7 @@ class ToolNode:
2425
NAVIGATION_LIST : typing.ClassVar[list] = [
2526
SettingWidget,
2627
MainInputWidget,
28+
AssetBrowserWidget,
2729
(ImagePackWidget, {"category_kind": ImagePackDescriptor.ImagePackType.BACKGROUND}),
2830
(ImagePackWidget, {"category_kind": ImagePackDescriptor.ImagePackType.CHARACTER}),
2931
]

src/preppipe_gui_pyside6/settingsdict.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
import tempfile
1212
from preppipe.language import *
1313

14-
def get_executable_base_dir() -> str:
14+
def _get_executable_base_dir() -> str:
1515
if getattr(sys, 'frozen', False):
1616
return os.path.dirname(sys.executable)
1717
return os.path.dirname(os.path.abspath(__file__))
1818

1919
class SettingsDict(collections.abc.MutableMapping):
20-
_executable_base_dir : typing.ClassVar[str] = get_executable_base_dir()
20+
_executable_base_dir : typing.ClassVar[str] = _get_executable_base_dir()
2121
_settings_instance : typing.ClassVar['SettingsDict | None'] = None
2222

2323
lock : threading.Lock
@@ -47,6 +47,13 @@ def try_set_settings_dir(path : str) -> None:
4747
if not os.path.isdir(SettingsDict._executable_base_dir):
4848
raise FileNotFoundError(f"Path '{SettingsDict._executable_base_dir}' does not exist")
4949

50+
@staticmethod
51+
def get_executable_base_dir():
52+
# 提供给其他模块使用
53+
# 没打包成可执行文件时,只用 _get_executable_base_dir() 取路径的话,
54+
# __file__ 取得的路径可能会不一致,因此将结果保存下来反复使用
55+
return SettingsDict._executable_base_dir
56+
5057
def __init__(self, filename='settings.db'):
5158
self.filename = filename
5259
self.lock = threading.Lock()

0 commit comments

Comments
 (0)