【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】
現在記事の修正中です
こんにちはMayaPython Advent Calendar 2017の15日目の記事です
全記事一覧です
【MayaPySide】ちょっとおしゃれなUIメソッド【1日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】
はい!本日はなんと特別なことがあります!
そう、それは
誕生日!!!
たくさんのプレゼント!!待ってます!!!
5000兆円とか!!!
ということで、ちょっとおしゃれなUIメソッドの三日目です。最終となります。
かなり量が多いので頑張っていきましょう!
from functools import cached_property
from typing import Any
from maya import OpenMayaUI
from PySide2 import QtCore, QtGui, QtWidgets
from shiboken2 import wrapInstance
class ClosePushButton(QtWidgets.QPushButton):
closed: QtCore.Signal = QtCore.Signal()
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super().__init__(parent)
self.clicked.connect(self._closed)
def _closed(self) -> None:
self.closed.emit()
class CornerGrip(QtWidgets.QWidget):
cursor_factory = {
QtCore.Qt.LeftEdge: QtCore.Qt.SizeHorCursor,
QtCore.Qt.TopEdge: QtCore.Qt.SizeVerCursor,
QtCore.Qt.RightEdge: QtCore.Qt.SizeHorCursor,
QtCore.Qt.BottomEdge: QtCore.Qt.SizeVerCursor,
}
resizeFunc: Any
mousePosition: QtCore.QPoint = None
def __init__(
self, parent: QtWidgets.QWidget = None, edge: QtCore.Qt.SizeAllCursor = None
):
super().__init__(parent)
cursor = self.cursor_factory.get(edge)
self.setCursor(cursor)
self.setResizeFunction(edge)
def setResizeFunction(self, edge) -> None:
if edge == QtCore.Qt.LeftEdge:
self.resizeFunc = self.resizeLeft
elif edge == QtCore.Qt.TopEdge:
self.resizeFunc = self.resizeTop
elif edge == QtCore.Qt.RightEdge:
self.resizeFunc = self.resizeRight
elif edge == QtCore.Qt.BottomEdge:
self.resizeFunc = self.resizeBottom
@cached_property
def window(self) -> QtWidgets.QWidget:
return super().window()
def resizeLeft(self, delta):
width = max(self.window.minimumWidth(), self.window.width() - delta.x())
geo = self.window.geometry()
geo.setLeft(geo.right() - width)
self.window.setGeometry(geo)
def resizeTop(self, delta):
height = max(self.window.minimumHeight(), self.window.height() - delta.y())
geo = self.window.geometry()
geo.setTop(geo.bottom() - height)
self.window.setGeometry(geo)
def resizeRight(self, delta):
width = max(self.window.minimumWidth(), self.window.width() + delta.x())
self.window.resize(width, self.window.height())
def resizeBottom(self, delta):
height = max(self.window.minimumHeight(), self.window.height() + delta.y())
self.window.resize(self.window.width(), height)
def mousePressEvent(self, event: QtGui.QMouseEvent) -> Any:
if event.button() == QtCore.Qt.LeftButton:
self.mousePosition = event.pos()
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> Any:
if self.mousePosition:
delta = event.pos() - self.mousePosition
self.resizeFunc(delta)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> Any:
self.mousePosition = None
class FrameGrips:
topLeft: QtWidgets.QSizeGrip
topRight: QtWidgets.QSizeGrip
bottomRight: QtWidgets.QSizeGrip
buttomLeft: QtWidgets.QSizeGrip
left: CornerGrip
top: CornerGrip
right: CornerGrip
bottom: CornerGrip
parent: QtWidgets.QWidget
__qss: str = "background-color: transparent;"
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
self.parent = parent
self.topLeft = QtWidgets.QSizeGrip(parent)
self.topRight = QtWidgets.QSizeGrip(parent)
self.bottomRight = QtWidgets.QSizeGrip(parent)
self.buttomLeft = QtWidgets.QSizeGrip(parent)
self.left = CornerGrip(parent, edge=QtCore.Qt.LeftEdge)
self.top = CornerGrip(parent, edge=QtCore.Qt.TopEdge)
self.right = CornerGrip(parent, edge=QtCore.Qt.RightEdge)
self.bottom = CornerGrip(parent, edge=QtCore.Qt.BottomEdge)
self.topLeft.setStyleSheet(self.__qss)
self.topRight.setStyleSheet(self.__qss)
self.bottomRight.setStyleSheet(self.__qss)
self.buttomLeft.setStyleSheet(self.__qss)
self.left.setStyleSheet(self.__qss)
self.top.setStyleSheet(self.__qss)
self.right.setStyleSheet(self.__qss)
self.bottom.setStyleSheet(self.__qss)
class FramelessMainWindow(QtWidgets.QMainWindow):
pos: QtCore.QPoint
radius: int = 15
backgroundColorCode: str = "#333333"
backgroundColor = QtGui.QColor(backgroundColorCode)
gradient: QtGui.QLinearGradient = QtGui.QLinearGradient()
gradient.setColorAt(0.0, "#54354e")
gradient.setColorAt(1.0, "#6a86c7")
isDrag: bool = False
isPress: bool = False
cornerLength = 10
frameGrips: FrameGrips
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super().__init__(parent)
self.setAutoFillBackground(True)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.installEventFilter(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.frameGrips = FrameGrips(self)
self.setContentsMargins(*[self.gripSize] * 4)
@property
def gripSize(self):
return self.cornerLength
def setGripSize(self, size):
if size == self.cornerLength:
return
self.cornerLength = max(2, size)
self.setContentsMargins(*[self.gripSize] * 4)
self.updateGrips()
def updateGrips(self) -> None:
outRect = self.rect()
# an "inner" rect used for reference to set the geometries of size grips
inRect = outRect.adjusted(
self.gripSize, self.gripSize, -self.gripSize, -self.gripSize
)
# top left
self.frameGrips.topLeft.setGeometry(
QtCore.QRect(outRect.topLeft(), inRect.topLeft())
)
# top right
self.frameGrips.topRight.setGeometry(
QtCore.QRect(outRect.topRight(), inRect.topRight()).normalized()
)
# bottom right
self.frameGrips.bottomRight.setGeometry(
QtCore.QRect(inRect.bottomRight(), outRect.bottomRight())
)
# bottom left
self.frameGrips.buttomLeft.setGeometry(
QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized()
)
# left edge
self.frameGrips.left.setGeometry(
0, inRect.top(), self.gripSize, inRect.height()
)
# top edge
self.frameGrips.top.setGeometry(inRect.left(), 0, inRect.width(), self.gripSize)
# right edge
self.frameGrips.right.setGeometry(
inRect.left() + inRect.width(), inRect.top(), self.gripSize, inRect.height()
)
# bottom edge
self.frameGrips.bottom.setGeometry(
self.gripSize, inRect.top() + inRect.height(), inRect.width(), self.gripSize
)
def resizeEvent(self, event):
QtWidgets.QMainWindow.resizeEvent(self, event)
self.updateGrips()
def paintEvent(self, event: QtGui.QMouseEvent) -> Any:
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
path = QtGui.QPainterPath()
self.gradient.setStart(QtCore.QRectF(self.rect()).topLeft())
self.gradient.setFinalStop(QtCore.QRectF(self.rect()).topRight())
brush = QtGui.QBrush(self.gradient)
painter.setBrush(brush)
path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
painter.setClipPath(path)
painter.fillPath(path, painter.brush())
return super().paintEvent(event)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
self.isPress = False
self.pressPos = event.pos()
super().mouseReleaseEvent(event)
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
self.isPress = True
self.pressPos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
if self.isPress or self.isDrag:
self.move(
event.globalX() - self.pressPos.x(), event.globalY() - self.pressPos.y()
)
super().mouseMoveEvent(event)
def eventFilter(self, obj: QtCore.QObject, event: QtCore.QEvent) -> Any:
if event.type() == QtCore.QEvent.Type.MouseButtonPress:
self.isDrag = True
return super().eventFilter(obj, event)
if event.type() == QtCore.QEvent.Type.MouseButtonRelease:
self.isDrag = False
return super().eventFilter(obj, event)
class ExampleCentralWidget(QtWidgets.QWidget):
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super(ExampleCentralWidget, self).__init__(parent)
self.closePushButton = ClosePushButton(self)
self.closePushButton.setText("Close")
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.closePushButton)
self.mainLayout.addStretch(True)
self.setLayout(self.mainLayout)
class FramelessMainWindow(QtWidgets.QMainWindow):
pos: QtCore.QPoint
radius: int = 15
backgroundColorCode: str = "#333333"
backgroundColor = QtGui.QColor(backgroundColorCode)
gradient: QtGui.QLinearGradient = QtGui.QLinearGradient()
gradient.setColorAt(0.0, "#54354e")
gradient.setColorAt(1.0, "#6a86c7")
isDrag: bool = False
isPress: bool = False
cornerLength = 10
frameGrips: FrameGrips
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super().__init__(parent)
self.setAutoFillBackground(True)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.installEventFilter(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.frameGrips = FrameGrips(self)
self.setContentsMargins(*[self.gripSize] * 4)
@property
def gripSize(self):
return self.cornerLength
def setGripSize(self, size):
if size == self.cornerLength:
return
self.cornerLength = max(2, size)
self.setContentsMargins(*[self.gripSize] * 4)
self.updateGrips()
def updateGrips(self) -> None:
outRect = self.rect()
# an "inner" rect used for reference to set the geometries of size grips
inRect = outRect.adjusted(
self.gripSize, self.gripSize, -self.gripSize, -self.gripSize
)
# top left
self.frameGrips.topLeft.setGeometry(
QtCore.QRect(outRect.topLeft(), inRect.topLeft())
)
# top right
self.frameGrips.topRight.setGeometry(
QtCore.QRect(outRect.topRight(), inRect.topRight()).normalized()
)
# bottom right
self.frameGrips.bottomRight.setGeometry(
QtCore.QRect(inRect.bottomRight(), outRect.bottomRight())
)
# bottom left
self.frameGrips.buttomLeft.setGeometry(
QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized()
)
# left edge
self.frameGrips.left.setGeometry(
0, inRect.top(), self.gripSize, inRect.height()
)
# top edge
self.frameGrips.top.setGeometry(inRect.left(), 0, inRect.width(), self.gripSize)
# right edge
self.frameGrips.right.setGeometry(
inRect.left() + inRect.width(), inRect.top(), self.gripSize, inRect.height()
)
# bottom edge
self.frameGrips.bottom.setGeometry(
self.gripSize, inRect.top() + inRect.height(), inRect.width(), self.gripSize
)
def resizeEvent(self, event):
QtWidgets.QMainWindow.resizeEvent(self, event)
self.updateGrips()
def paintEvent(self, event: QtGui.QMouseEvent) -> Any:
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
path = QtGui.QPainterPath()
self.gradient.setStart(QtCore.QRectF(self.rect()).topLeft())
self.gradient.setFinalStop(QtCore.QRectF(self.rect()).topRight())
brush = QtGui.QBrush(self.gradient)
painter.setBrush(brush)
path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
painter.setClipPath(path)
painter.fillPath(path, painter.brush())
return super().paintEvent(event)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
self.isPress = False
self.pressPos = event.pos()
super().mouseReleaseEvent(event)
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
self.isPress = True
self.pressPos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
if self.isPress or self.isDrag:
self.move(
event.globalX() - self.pressPos.x(), event.globalY() - self.pressPos.y()
)
super().mouseMoveEvent(event)
def eventFilter(self, obj: QtCore.QObject, event: QtCore.QEvent) -> Any:
if event.type() == QtCore.QEvent.Type.MouseButtonPress:
self.isDrag = True
return super().eventFilter(obj, event)
if event.type() == QtCore.QEvent.Type.MouseButtonRelease:
self.isDrag = False
return super().eventFilter(obj, event)
class Example(FramelessMainWindow):
timer: QtCore.QTimer = QtCore.QTimer()
fade: QtCore.QPropertyAnimation
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super(Example, self).__init__(parent)
self.__initUI()
self.setWidget()
def __initUI(self) -> None:
self.installEventFilter(self)
self.setWindowOpacity(0.85)
self.setGeometry(300, 300, 500, 350)
self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)
def setWidget(self) -> None:
self.setCentralWidget(ExampleCentralWidget(self))
self.centralWidget().ClosePushButton.closed.connect(self.close)
def close(self) -> None:
self.timer.setInterval(600)
self.timer.timeout.connect(super().close)
self.timer.start()
self.fade = QtCore.QPropertyAnimation(self, b"windowOpacity")
self.fade.setStartValue(0.85)
self.fade.setEndValue(0.0)
self.fade.setKeyValueAt(0.5, 0.0)
self.fade.setEasingCurve(QtCore.QEasingCurve.InOutCubic)
self.fade.setDuration(600)
self.fade.start()
def main() -> None:
mayaMainWindow = wrapInstance(
int(OpenMayaUI.MQtUtil.mainWindow()), QtWidgets.QMainWindow
)
ex = Example(mayaMainWindow)
ex.show()
# main()
上に記述している前回までのコードをコピーして使用してください。
下の表のようなフォルダファイル構成を作っていきます。
C:\Users\<ユーザー名>\Documents\maya\scripts\unpyside/
├── __init__.py/
├── ac2017/
| ├── __init__.py
| ├── example.py
| └── resource/
| ├── < imageName >.png
| └── < imageName >.png
Index直下には今まで作成してきたソースコードをexample.py
という名前で保存します。
一番最後の行にあるmain()
は消しておきます。一度うまくmaya読み込めるか、以下のコードをスクリプトエディターで実行してみましょう
import unpyside.ac2017.example
unpyside.ac2017.example.main()
import unpyside.ac2017.exampleの次の行にfrom importlib import reload
と`reload(index.example)を記述するとモジュールをリロードできます
from importlib import reload
import unpyside.ac2017.example
reload(unpyside.ac2017.example)
unpyside.ac2017.example.main()
さて、今回の記事で作っていくものはメインのスライダー
そして6つのボタン、closeボタンとなります。
それらを作っていくにはまず必要なイメージ画像を用意します
今回、私が使用している画像のサイズはこのような構成になっています
画像の種類 | サイズ |
---|---|
Closeボタン用の画像 | 12x12px |
スライダー用の画像 | 24x24px |
その他6種類のボタン | 32x32px |
6種類のボタンのサイズが大きいサイズなのはあまり気にしないでください
もともと大きいサイズでボタンを構成していた名残です
また、データは基本pngを使用しています
このリンクからDLして使用してください
DLして解凍した画像をunpyside/ac2017/resource
に入れて下さい。
次にその画像をQRCを作成し.py
に変換して読み込んでいきます。
C:\Users\<ユーザー名>\Documents\maya\scripts\unpyside/
├── __init__.py/
├── ac2017/
| ├── __init__.py
| ├── example.py
| ├── create_qrc.py
| ├── create_qrc.bat
| └── resource/
| ├── < imageName >.png
| └── < imageName >.png
create_qrc.pyとcreate_qrc.batをexample.pyと同じ階層に作成して、以下のコードのようにしてください
create_qrc.py
import sys
from pathlib import Path
PREFIX = "/resource/"
def searchFiles(root: Path) -> list[Path]:
result = []
for iter in root.iterdir():
if iter.suffix in [".png", ".jpg", ".svg"]:
result.append(iter)
return result
def createCode(root: Path) -> str:
code = f''
for file in searchFiles(root):
code += f"{file} "
code += " "
return code
def main():
print(sys.argv)
root = Path(sys.argv[1])
qrc = root / "resource.qrc"
with open(qrc, "w") as f:
f.write(createCode(root))
if __name__ == "__main__":
main()
MAYADIR
は自分の使用しているMayaのバージョンに合わせてくださいcreate_qrc.bat
@echo off
chcp 65001 > nul
cd /d %~dp0
setlocal enabledelayedexpansion
set EXPORTPYTHON=%1\resource.py
set QRCPATH=%1\resource.qrc
set MAYADIR="C:\Program Files\Autodesk\Maya2023\bin\"
%MAYADIR%mayapy.exe %~dp0create_qrc.py %1
%MAYADIR%pyside2-rcc.exe -o %EXPORTPYTHON% %QRCPATH%
move %EXPORTPYTHON% %~dp1
create_qrc.batにresourceフォルダドラックアンドドロップし、しばらくするとresource.py
が作られます
C:\Users\<ユーザー名>\Documents\maya\scripts\unpyside/
├── __init__.py/
├── ac2017/
| ├── __init__.py
| ├── example.py
| ├── create_qrc.py
| ├── create_qrc.bat
| ├── resource.py
| └── resource/
| ├── < imageName >.png
| └── < imageName >.png
resouceの準備ができたら、resource.pyをimportします
from functools import cached_property
from typing import Any
from maya import OpenMayaUI
from PySide2 import QtCore, QtGui, QtWidgets
from shiboken2 import wrapInstance
from . import resource
class ClosePushButton(QtWidgets.QPushButton):
closed: QtCore.Signal = QtCore.Signal()
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super().__init__(parent)
self.clicked.connect(self._closed)
self.setIcon(QtGui.QIcon(":/resource/close.png"))
self.setIconSize(QtCore.QSize(14, 14))
...
class CornerGrip(QtWidgets.QWidget):
...
class FrameGrips:
...
class ExampleCentralWidget(QtWidgets.QWidget):
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super(ExampleCentralWidget, self).__init__(parent)
self.closePushButton = ClosePushButton(self)
# self.closePushButton.setText("Close")
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.closePushButton)
self.mainLayout.addStretch(True)
self.setLayout(self.mainLayout)
class FramelessMainWindow(QtWidgets.QMainWindow):
...
class Example(FramelessMainWindow):
...
def main() -> None:
...
from . import resource
でresource.py
を読み込みますClosePushButton
がインスタンス化する際に、アイコンを設定するようにsetIcon(QtGui.QIcon(":/resource/close.png"))
とsetIconSize(QtCore.QSize(14, 14))
を設定しています。
下の画像のように画像がボタンに表示されていればうまくできています。
ここで完成図を改めてみてみます
ざっくり、まとめると以下のような構成になっています
- 上部に文字と閉じるボタン
- 中心部にスライダー
- 下部にボタンを配置
QGridLayoutを使うことももちろん可能なのですがメンテナンスがめんどくさいので、上中下をQVBoxLayoutを使用して一つにまとめ、それぞれの行はQHBoxLayoutを使用するという方法で構成していきます。
Qt Designerを使ってUIを構築します
ExampleCentralWidget.ui/
├── Form(QHBoxLayout)/
| ├── horizontalLayout_menu
| | ├── horizontalSpacer
| | └── label_title
| ├── horizontalLayout_menu
| ├── horizontalLayout_silider
| ├── verticalSpacer_bottom
| └── verticalSpacer_top/
from __future__ import annotations
from functools import cached_property
from pathlib import Path
from typing import Any, Final, Type
from maya import OpenMayaUI
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtUiTools import loadUiType
from shiboken2 import wrapInstance
from . import resource
ROOT_DIR: Final = Path(__file__).parent
EXAMPLECENTRALWIDEGET_UI_PATH: Final = ROOT_DIR / "ui" / "ExampleCentralWidget.ui"
def loadWidgetUiType(
file: Path | str,
) -> tuple[Type[QtWidgets.QWidget], Type[QtWidgets.QWidget]]:
if isinstance(file, Path):
file = file.as_posix()
return loadUiType(file)
_, base = loadWidgetUiType(EXAMPLECENTRALWIDEGET_UI_PATH)
class ClosePushButton(QtWidgets.QPushButton):
closed: QtCore.Signal = QtCore.Signal()
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super().__init__(parent)
self.clicked.connect(self._closed)
self.setIcon(QtGui.QIcon(":/resource/close.png"))
self.setIconSize(QtCore.QSize(14, 14))
def _closed(self) -> None:
self.closed.emit()
class ExampleCentralWidget(_, base):
label_title: QtWidgets.QLabel
horizontalLayout_menu: QtWidgets.QHBoxLayout
horizontalLayout_silider: QtWidgets.QHBoxLayout
horizontalLayout_buttons: QtWidgets.QHBoxLayout
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super().__init__(parent)
self.setupUi(self)
self.__init_buttons()
def __init_buttons(self):
self.closePushButton = ClosePushButton(self)
self.horizontalLayout_menu.addWidget(self.closePushButton)
for num in range(5):
btn = QtWidgets.QPushButton(self)
btn.setIcon(QtGui.QIcon(f":/resource/Btn_({num}).png"))
btn.setIconSize(QtCore.QSize(14, 14))
self.horizontalLayout_buttons.addWidget(btn)
class CornerGrip(QtWidgets.QWidget):
cursor_factory = {
QtCore.Qt.LeftEdge: QtCore.Qt.SizeHorCursor,
QtCore.Qt.TopEdge: QtCore.Qt.SizeVerCursor,
QtCore.Qt.RightEdge: QtCore.Qt.SizeHorCursor,
QtCore.Qt.BottomEdge: QtCore.Qt.SizeVerCursor,
}
resizeFunc: Any
mousePosition: QtCore.QPoint = None
def __init__(
self, parent: QtWidgets.QWidget = None, edge: QtCore.Qt.SizeAllCursor = None
):
super().__init__(parent)
cursor = self.cursor_factory.get(edge)
self.setCursor(cursor)
self.setResizeFunction(edge)
def setResizeFunction(self, edge) -> None:
if edge == QtCore.Qt.LeftEdge:
self.resizeFunc = self.resizeLeft
elif edge == QtCore.Qt.TopEdge:
self.resizeFunc = self.resizeTop
elif edge == QtCore.Qt.RightEdge:
self.resizeFunc = self.resizeRight
elif edge == QtCore.Qt.BottomEdge:
self.resizeFunc = self.resizeBottom
@cached_property
def window(self) -> QtWidgets.QWidget:
return super().window()
def resizeLeft(self, delta):
width = max(self.window.minimumWidth(), self.window.width() - delta.x())
geo = self.window.geometry()
geo.setLeft(geo.right() - width)
self.window.setGeometry(geo)
def resizeTop(self, delta):
height = max(self.window.minimumHeight(), self.window.height() - delta.y())
geo = self.window.geometry()
geo.setTop(geo.bottom() - height)
self.window.setGeometry(geo)
def resizeRight(self, delta):
width = max(self.window.minimumWidth(), self.window.width() + delta.x())
self.window.resize(width, self.window.height())
def resizeBottom(self, delta):
height = max(self.window.minimumHeight(), self.window.height() + delta.y())
self.window.resize(self.window.width(), height)
def mousePressEvent(self, event: QtGui.QMouseEvent) -> Any:
if event.button() == QtCore.Qt.LeftButton:
self.mousePosition = event.pos()
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> Any:
if self.mousePosition:
delta = event.pos() - self.mousePosition
self.resizeFunc(delta)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> Any:
self.mousePosition = None
class FrameGrips:
topLeft: QtWidgets.QSizeGrip
topRight: QtWidgets.QSizeGrip
bottomRight: QtWidgets.QSizeGrip
buttomLeft: QtWidgets.QSizeGrip
left: CornerGrip
top: CornerGrip
right: CornerGrip
bottom: CornerGrip
parent: QtWidgets.QWidget
__qss: str = "background-color: transparent;"
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
self.parent = parent
self.topLeft = QtWidgets.QSizeGrip(parent)
self.topRight = QtWidgets.QSizeGrip(parent)
self.bottomRight = QtWidgets.QSizeGrip(parent)
self.buttomLeft = QtWidgets.QSizeGrip(parent)
self.left = CornerGrip(parent, edge=QtCore.Qt.LeftEdge)
self.top = CornerGrip(parent, edge=QtCore.Qt.TopEdge)
self.right = CornerGrip(parent, edge=QtCore.Qt.RightEdge)
self.bottom = CornerGrip(parent, edge=QtCore.Qt.BottomEdge)
self.topLeft.setStyleSheet(self.__qss)
self.topRight.setStyleSheet(self.__qss)
self.bottomRight.setStyleSheet(self.__qss)
self.buttomLeft.setStyleSheet(self.__qss)
self.left.setStyleSheet(self.__qss)
self.top.setStyleSheet(self.__qss)
self.right.setStyleSheet(self.__qss)
self.bottom.setStyleSheet(self.__qss)
class FramelessMainWindow(QtWidgets.QMainWindow):
pos: QtCore.QPoint
radius: int = 15
backgroundColorCode: str = "#333333"
backgroundColor = QtGui.QColor(backgroundColorCode)
gradient: QtGui.QLinearGradient = QtGui.QLinearGradient()
gradient.setColorAt(0.0, "#54354e")
gradient.setColorAt(1.0, "#6a86c7")
isDrag: bool = False
isPress: bool = False
cornerLength = 10
frameGrips: FrameGrips
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super().__init__(parent)
self.setAutoFillBackground(True)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.installEventFilter(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.frameGrips = FrameGrips(self)
self.setContentsMargins(*[self.gripSize] * 4)
@property
def gripSize(self):
return self.cornerLength
def setGripSize(self, size):
if size == self.cornerLength:
return
self.cornerLength = max(2, size)
self.setContentsMargins(*[self.gripSize] * 4)
self.updateGrips()
def updateGrips(self) -> None:
outRect = self.rect()
# an "inner" rect used for reference to set the geometries of size grips
inRect = outRect.adjusted(
self.gripSize, self.gripSize, -self.gripSize, -self.gripSize
)
# top left
self.frameGrips.topLeft.setGeometry(
QtCore.QRect(outRect.topLeft(), inRect.topLeft())
)
# top right
self.frameGrips.topRight.setGeometry(
QtCore.QRect(outRect.topRight(), inRect.topRight()).normalized()
)
# bottom right
self.frameGrips.bottomRight.setGeometry(
QtCore.QRect(inRect.bottomRight(), outRect.bottomRight())
)
# bottom left
self.frameGrips.buttomLeft.setGeometry(
QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized()
)
# left edge
self.frameGrips.left.setGeometry(
0, inRect.top(), self.gripSize, inRect.height()
)
# top edge
self.frameGrips.top.setGeometry(inRect.left(), 0, inRect.width(), self.gripSize)
# right edge
self.frameGrips.right.setGeometry(
inRect.left() + inRect.width(), inRect.top(), self.gripSize, inRect.height()
)
# bottom edge
self.frameGrips.bottom.setGeometry(
self.gripSize, inRect.top() + inRect.height(), inRect.width(), self.gripSize
)
def resizeEvent(self, event):
QtWidgets.QMainWindow.resizeEvent(self, event)
self.updateGrips()
def paintEvent(self, event: QtGui.QMouseEvent) -> Any:
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
path = QtGui.QPainterPath()
self.gradient.setStart(QtCore.QRectF(self.rect()).topLeft())
self.gradient.setFinalStop(QtCore.QRectF(self.rect()).topRight())
brush = QtGui.QBrush(self.gradient)
painter.setBrush(brush)
path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
painter.setClipPath(path)
painter.fillPath(path, painter.brush())
return super().paintEvent(event)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
self.isPress = False
self.pressPos = event.pos()
super().mouseReleaseEvent(event)
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
self.isPress = True
self.pressPos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
if self.isPress or self.isDrag:
self.move(
event.globalX() - self.pressPos.x(), event.globalY() - self.pressPos.y()
)
super().mouseMoveEvent(event)
def eventFilter(self, obj: QtCore.QObject, event: QtCore.QEvent) -> Any:
if event.type() == QtCore.QEvent.Type.MouseButtonPress:
self.isDrag = True
return super().eventFilter(obj, event)
if event.type() == QtCore.QEvent.Type.MouseButtonRelease:
self.isDrag = False
return super().eventFilter(obj, event)
class Example(FramelessMainWindow):
# btn: QtWidgets.QPushButton
timer: QtCore.QTimer = QtCore.QTimer()
fade: QtCore.QPropertyAnimation
def __init__(self, parent: QtWidgets.QWidget = None) -> None:
super(Example, self).__init__(parent)
self.__initUI()
self.setWidget()
def __initUI(self) -> None:
self.setMouseTracking(True)
self.setWindowOpacity(0.85)
self.setGeometry(300, 300, 500, 350)
self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)
def setWidget(self) -> None:
self.setCentralWidget(ExampleCentralWidget(self))
self.centralWidget().closePushButton.closed.connect(self.close)
def close(self) -> None:
self.timer.setInterval(600)
self.timer.timeout.connect(super().close)
self.timer.start()
self.fade = QtCore.QPropertyAnimation(self, b"windowOpacity")
self.fade.setStartValue(0.85)
self.fade.setEndValue(0.0)
self.fade.setKeyValueAt(0.5, 0.0)
self.fade.setEasingCurve(QtCore.QEasingCurve.InOutCubic)
self.fade.setDuration(600)
self.fade.start()
def main() -> None:
mayaMainWindow = wrapInstance(
int(OpenMayaUI.MQtUtil.mainWindow()), QtWidgets.QMainWindow
)
ex = Example(mayaMainWindow)
ex.show()
うまくいくと右クリックを押したときにどのボタンが押されたかをScriptEditorで結果が出てきます
さて、ここまででqssを使ったボタンの作成やclassを使って新しいボタンの作成をしてきました
次はこれまでしてきたことを応用してスライダーを作っていきます
実はこの画像にあるスライダーはQSlider
だけを使って構成しているのではなく
組み合わせて表現しています
このスライダーの構成はQSlider
、QPushButton
、QLineEdit
です。
配置イメージはこうなります
QPushButton#ColorButton{
border-style: solid;
border-radius: 12px;
border-width: 4px;
border-color: #335b84;
height: 16px;
}
class ColorButton(QPushButton):
colorChanged = Signal(QColor)
def __init__(self, *args, **kwargs):
super(ColorButton, self).__init__(*args, **kwargs)
self.setFlat(True)
self.clicked.connect(self.showDialog)
self.__color = QColor(255, 255, 255)
self.__update()
self.setObjectName(ColorButton)
def showDialog(self):
color = QColorDialog.getColor(self.__color)
if not color.isValid():
return
self.__color = color
self.__update()
self.colorChanged.emit(self.__color)
def __update(self):
r, g, b, a = self.__color.getRgb()
self.setStyleSheet(
*{
background:rgb(%s, %s, %s);
}
% (r, g, b)
)
def color(self):
return self.__color
def setColor(self, color):
self.__color = color
self.__update()
self.colorChanged.emit(self.__color)
def rgba(self):
return self.__color.getRgb()
def setRgba(self, r, g, b, a=255):
self.__color.setRgb(r, g, b, a)
self.__update()
self.colorChanged.emit(self.__color)
def hsv(self):
return self.__color.getHsv()
def setHsv(self, h, s, v, a=255):
self.__color.setHsv(h, s, v, a)
self.__update()
self.colorChanged.emit(self.__color)
このColorButton
はスライダーが動いた際に色が変更したり、このボタンから色を選択することができます
実はKIWAMIDENさんのPySideでcolorSliderGrpモドキを作る方法をベースに作成しています
詳細はKIWAMIDENさんの記事を読むとよくわかるかと思います。
それぞれを設定していきます
以下のコードを追記してください
QSlider#Slider{
border-color: qlineargradient(
x1: 0, y1: 0, x2: 2, y2: 0, stop: 0 #335b84, stop: 1 #347881
);
border-radius: 24px;
}
QSlider::groove:horizontal#Slider{
height: 24px;
border-radius: 24px;
margin: 0 0.5px;
}
QSlider::handle:horizontal#Slider{
width: 24px;
height: 24px;
background-image: url(:/resource/handle.png)
}
QSlider::add-page:qlineargradient#Slider{
background: #347881;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
QSlider::sub-page:qlineargradient#Slider{
background: #335b84;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
}
QLineEdit#Line{
font-family: Montserrat;
font-style: Bold;
background: #347881;
border-color: #347881;
border-radius: 12px;
color: #fff1e6;
width: 58px;
height: 24px;
}
class MainWidget(QWidget):
SliderValueChanged = Signal(int)
closed = Signal()
def __init__(self):
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def initUI(self):
Mtl_Slider_P = 42
self.clrBtn = ColorButton(self)
self.clrBtn.setMinimumWidth(70)
self.clrBtn.setMinimumHeight(10)
self.clrBtn.colorChanged[QColor].connect(self.emitInt)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
self.resultText = QLineEdit(self)
self.resultText.setFrame(False)
self.resultText.setAlignment(Qt.AlignRight)
self.resultText.setObjectName('Line')
self.resultText.setTextMargins(0, 0, 2, 1)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
self.sld = QSlider(Qt.Horizontal, self)
self.sld.valueChanged[int].connect(self.emitInt)
self.sld.setRange(0, 100)
self.sld.setValue(85)
self.sld.setObjectName('Slider')
self.resultText.move(194, Mtl_Slider_P)
self.sld.setGeometry(36, Mtl_Slider_P, 188, 25)
self.clrBtn.move(6, Mtl_Slider_P)
def emitInt(self, value):
try:
value = int(round(value.value() / 2.55))
colorsvalue = value
except:
pass
self.resultText.setText(%s % (self.sld.value()))
value = int(value * 2.55)
sender = self.sender()
if sender == self.clrBtn:
color = self.clrBtn.color()
self.sld.blockSignals(True)
self.sld.setValue(color.value())
self.sld.blockSignals(False)
self.resultText.setText(%s % (colorsvalue))
self.sld.setValue(colorsvalue)
elif sender == self.sld:
hsv = self.clrBtn.hsv()
self.clrBtn.blockSignals(True)
self.clrBtn.setHsv(hsv[0], hsv[1], value)
self.clrBtn.blockSignals(False)
return self.sld.value()
def output(self):
try:
num = int(self.resultText.text())
return num
except:
return Text wasn't numeral.
emitInt(self)
スライダーの値を変更し、ボタンの色を変更したりする機能ですoutput(self)
今現在のテキスト返す機能ですPushBtn
にそれぞれ出力できるようにoutput
をセットしていきます
def push_right(self):
sender = self.sender()
if sender is self.Mtl_Btns[0]:
print Mtl_Btns[0] -right\nValue: %s % self.output()
elif sender is self.Mtl_Btns[1]:
print Mtl_Btns[1] -right\nValue: %s % self.output()
elif sender is self.Mtl_Btns[2]:
print Mtl_Btns[2] -right\nValue: %s % self.output()
elif sender is self.Mtl_Btns[3]:
print Mtl_Btns[3] -right\nValue: %s % self.output()
elif sender is self.Mtl_Btns[4]:
print Mtl_Btns[4] -right\nValue: %s % self.output()
elif sender is self.Mtl_Btns[5]:
print Mtl_Btns[5] -right\nValue: %s % self.output()
def push_left(self):
sender = self.sender()
if sender is self.Mtl_Btns[0]:
print Mtl_Btns[0] -left\nValue: %s % self.output()
elif sender is self.Mtl_Btns[1]:
print Mtl_Btns[1] -left\nValue: %s % self.output()
elif sender is self.Mtl_Btns[2]:
print Mtl_Btns[2] -left\nValue: %s % self.output()
elif sender is self.Mtl_Btns[3]:
print Mtl_Btns[3] -left\nValue: %s % self.output()
elif sender is self.Mtl_Btns[4]:
print Mtl_Btns[4] -left\nValue: %s % self.output()
elif sender is self.Mtl_Btns[5]:
print Mtl_Btns[5] -left\nValue: %s % self.output()
以上がちょっとおしゃれなUIメソッドとなります
QSSのあたりやスライダーのあたりはかなり駆け足になってしまっているので後日詳しい詳細などを記事にできたらなとおもっています
これを機会におしゃれなUIを作成して見てください!
MayaPython Advent Calendar 2017の16日目の記事はsho7nokaさんのprint デバッグから卒業したい人のための Maya+PyCharmです
ディスカッション
コメント一覧
まだ、コメントがありません