226 lines
8.4 KiB
Python
226 lines
8.4 KiB
Python
from ngSkinTools2 import api, signal
|
|
from ngSkinTools2.api import python_compatibility
|
|
from ngSkinTools2.api.log import getLogger
|
|
from ngSkinTools2.api.pyside import QtCore, QtWidgets
|
|
from ngSkinTools2.api.session import session
|
|
from ngSkinTools2.ui import qt
|
|
from ngSkinTools2.ui.layout import scale_multiplier
|
|
|
|
if python_compatibility.PY3:
|
|
from typing import Union
|
|
|
|
|
|
log = getLogger("layersView")
|
|
|
|
|
|
def build_view(parent, actions):
|
|
from ngSkinTools2.operations import layers
|
|
|
|
layer_icon_size = 20
|
|
visibility_icon_size = 13
|
|
|
|
icon_layer = qt.scaled_icon(":/layeredTexture.svg", layer_icon_size, layer_icon_size)
|
|
icon_layer_disabled = qt.scaled_icon(":/layerEditor.png", layer_icon_size, layer_icon_size)
|
|
icon_visible = qt.scaled_icon("eye-fill.svg", visibility_icon_size, visibility_icon_size)
|
|
icon_hidden = qt.scaled_icon("eye-slash-fill.svg", visibility_icon_size, visibility_icon_size)
|
|
|
|
layer_data_role = QtCore.Qt.UserRole + 1
|
|
|
|
def item_to_layer(item):
|
|
# type: (QtWidgets.QTreeWidgetItem) -> Union[api.Layer, None]
|
|
if item is None:
|
|
return None
|
|
return item.data(0, layer_data_role)
|
|
|
|
# noinspection PyShadowingNames
|
|
def sync_layer_parents_to_widget_items(view):
|
|
"""
|
|
after drag/drop tree reordering, just brute-force check
|
|
that rearranged items match layers parents
|
|
:return:
|
|
"""
|
|
|
|
def sync_item(tree_item, parent_layer_id):
|
|
for i in range(tree_item.childCount()):
|
|
child = tree_item.child(i)
|
|
rebuild_buttons(child)
|
|
|
|
child_layer = item_to_layer(child)
|
|
|
|
if child_layer.parent_id != parent_layer_id:
|
|
log.info("changing layer parent: %r->%r (was %r)", parent_layer_id, child_layer, child_layer.parent_id)
|
|
child_layer.parent = parent_layer_id
|
|
|
|
new_index = tree_item.childCount() - i - 1
|
|
if child_layer.index != new_index:
|
|
log.info("changing layer index: %r->%r (was %r)", child_layer, new_index, child_layer.index)
|
|
child_layer.index = new_index
|
|
|
|
sync_item(child, child_layer.id)
|
|
|
|
with qt.signals_blocked(view):
|
|
sync_item(view.invisibleRootItem(), None)
|
|
|
|
# noinspection PyPep8Naming
|
|
class LayersWidget(QtWidgets.QTreeWidget):
|
|
def dropEvent(self, event):
|
|
QtWidgets.QTreeWidget.dropEvent(self, event)
|
|
sync_layer_parents_to_widget_items(self)
|
|
|
|
view = LayersWidget(parent)
|
|
view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
|
view.setUniformRowHeights(True)
|
|
view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
|
|
|
# enable drag/drop
|
|
view.setDragEnabled(True)
|
|
view.viewport().setAcceptDrops(True)
|
|
view.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
|
|
view.setDropIndicatorShown(True)
|
|
|
|
# add context menu
|
|
view.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
|
actions.addLayersActions(view)
|
|
|
|
view.setHeaderLabels(["Layers", ""])
|
|
# view.setHeaderHidden(True)
|
|
view.header().setMinimumSectionSize(1)
|
|
view.header().setStretchLastSection(False)
|
|
view.header().swapSections(0, 1)
|
|
view.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
|
view.header().setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
|
|
view.setColumnWidth(1, 25 * scale_multiplier)
|
|
view.setIndentation(15 * scale_multiplier)
|
|
view.setIconSize(QtCore.QSize(layer_icon_size * scale_multiplier, layer_icon_size * scale_multiplier))
|
|
|
|
tree_items = {}
|
|
|
|
def rebuild_buttons(item):
|
|
layer = item_to_layer(item)
|
|
bar = QtWidgets.QToolBar(parent=parent)
|
|
bar.setMovable(False)
|
|
bar.setIconSize(QtCore.QSize(visibility_icon_size * scale_multiplier, visibility_icon_size * scale_multiplier))
|
|
a = bar.addAction(icon_visible if layer is None or layer.enabled else icon_hidden, "Toggle enabled/disabled")
|
|
|
|
@qt.on(a.triggered)
|
|
def handler():
|
|
layer.enabled = not layer.enabled
|
|
session.events.layerListChanged.emitIfChanged()
|
|
|
|
view.setItemWidget(item, 1, bar)
|
|
|
|
def build_items(layer_infos):
|
|
"""
|
|
sync items in view with provided layer values, trying to delete as little items on the view as possible
|
|
:type layer_infos: list[api.Layer]
|
|
"""
|
|
|
|
# build map "parent id->list of children "
|
|
|
|
log.info("syncing items...")
|
|
|
|
# save selected layers IDs to restore item selection later
|
|
selected_layer_ids = {item_to_layer(item).id for item in view.selectedItems()}
|
|
log.info("selected layer IDs: %r", selected_layer_ids)
|
|
current_item_id = None if view.currentItem() is None else item_to_layer(view.currentItem()).id
|
|
|
|
hierarchy = {}
|
|
for child in layer_infos:
|
|
if child.parent_id not in hierarchy:
|
|
hierarchy[child.parent_id] = []
|
|
hierarchy[child.parent_id].append(child)
|
|
|
|
def sync(parent_tree_item, children_list):
|
|
while parent_tree_item.childCount() > len(children_list):
|
|
parent_tree_item.removeChild(parent_tree_item.child(len(children_list)))
|
|
|
|
for index, child in enumerate(reversed(children_list)):
|
|
if index >= parent_tree_item.childCount():
|
|
item = QtWidgets.QTreeWidgetItem()
|
|
item.setSizeHint(1, QtCore.QSize(1 * scale_multiplier, 25 * scale_multiplier))
|
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
|
|
parent_tree_item.addChild(item)
|
|
else:
|
|
item = parent_tree_item.child(index)
|
|
|
|
tree_items[child.id] = item
|
|
|
|
item.setData(0, layer_data_role, child)
|
|
item.setText(0, child.name)
|
|
item.setIcon(0, icon_layer if child.enabled else icon_layer_disabled)
|
|
rebuild_buttons(item)
|
|
|
|
sync(item, hierarchy.get(child.id, []))
|
|
|
|
with qt.signals_blocked(view):
|
|
tree_items.clear()
|
|
sync(view.invisibleRootItem(), hierarchy.get(None, []))
|
|
|
|
current_item = tree_items.get(current_item_id, None)
|
|
if current_item is not None:
|
|
view.setCurrentItem(current_item, 0, QtCore.QItemSelectionModel.NoUpdate)
|
|
|
|
for i in selected_layer_ids:
|
|
item = tree_items.get(i, None)
|
|
if item is not None:
|
|
item.setSelected(True)
|
|
|
|
@signal.on(session.events.layerListChanged, qtParent=view)
|
|
def refresh_layer_list():
|
|
log.info("event handler for layer list changed")
|
|
if not session.state.layersAvailable:
|
|
build_items([])
|
|
else:
|
|
build_items(session.state.all_layers)
|
|
|
|
update_selected_items()
|
|
|
|
@signal.on(session.events.currentLayerChanged, qtParent=view)
|
|
def current_layer_changed():
|
|
log.info("event handler for currentLayerChanged")
|
|
layer = session.state.currentLayer.layer
|
|
current_item = view.currentItem()
|
|
if layer is None:
|
|
view.setCurrentItem(None)
|
|
return
|
|
|
|
prev_layer = None if current_item is None else item_to_layer(current_item)
|
|
|
|
if prev_layer is None or prev_layer.id != layer.id:
|
|
item = tree_items.get(layer.id, None)
|
|
if item is not None:
|
|
log.info("setting current item to " + item.text(0))
|
|
view.setCurrentItem(item, 0, QtCore.QItemSelectionModel.SelectCurrent | QtCore.QItemSelectionModel.ClearAndSelect)
|
|
|
|
item.setSelected(True)
|
|
|
|
@qt.on(view.currentItemChanged)
|
|
def current_item_changed(curr, _):
|
|
log.info("current item changed")
|
|
if curr is None:
|
|
return
|
|
|
|
selected_layer = item_to_layer(curr)
|
|
|
|
if layers.getCurrentLayer() == selected_layer:
|
|
return
|
|
|
|
layers.setCurrentLayer(selected_layer)
|
|
|
|
@qt.on(view.itemChanged)
|
|
def item_changed(item, column):
|
|
log.info("item changed")
|
|
layers.renameLayer(item_to_layer(item), item.text(column))
|
|
|
|
@qt.on(view.itemSelectionChanged)
|
|
def update_selected_items():
|
|
selection = [item_to_layer(item) for item in view.selectedItems()]
|
|
|
|
if selection != session.context.selected_layers(default=[]):
|
|
log.info("new selected layers: %r", selection)
|
|
session.context.selected_layers.set(selection)
|
|
|
|
refresh_layer_list()
|
|
|
|
return view
|