【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】
こんにちはMayaPython Advent Calendar 2017の15日目の記事です
全記事一覧です
【MayaPySide】ちょっとおしゃれなUIメソッド【1日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】
はい!本日はなんと特別なことがあります!
そう、それは
誕生日!!!
たくさんのプレゼント!!待ってます!!!
5000兆円とか!!!
ということで、ちょっとおしゃれなUIメソッドの三日目です。最終となります。
かなり量が多いので頑張っていきましょう!
#!/usr/bin/env python # -*- coding: utf-8 -*- import re import os import sys import maya.cmds as cmds import pymel.core as pm from maya import OpenMayaUI from Qt.QtWidgets import * from Qt.QtGui import * from Qt.QtCore import * try: import shiboken except: import shiboken2 as shiboken ptr = OpenMayaUI.MQtUtil.mainWindow() parent = shiboken.wrapInstance(long(ptr), QWidget) class maskUI(QMainWindow): def __init__(self): super(maskUI, self).__init__(parent) def PaletteUI(self): setColors = ['#54354e', '#6a86c7'] Palette = QPalette() gradient = QLinearGradient(QRectF( self.rect()).topLeft(), QRectF(self.rect()).topRight() ) gradient.setColorAt(0.0, setColors[0]) gradient.setColorAt(1.0, setColors[1]) Palette.setBrush(QPalette.Background, QBrush(gradient)) path = QPainterPath() path.addRoundedRect(self.rect(), 3, 3) region = QRegion(path.toFillPolygon().toPolygon()) self.setMask(region) self.setPalette(Palette) class setMainWindow(maskUI): def __init__(self): super(setMainWindow, self).__init__() self.installEventFilter(self) self.resize_mode = None def mouseReleaseEvent(self, pos): self.mc_x = pos.x() self.mc_y = pos.y() def mousePressEvent(self, pos): self.mc_x = pos.x() self.mc_y = pos.y() borderWidth = 8 resizeWidth = self.minimumWidth() != self.maximumWidth() resizeHeight = self.minimumHeight() != self.maximumHeight() self.pre_size_x = self.size().width() self.pre_size_y = self.size().height() self.size_w = self.size().width() self.size_h = self.size().height() self.sub_w = self.size_w - self.mc_x self.sub_h = self.size_h - self.mc_y resize_mode = None if resizeWidth is True: if self.mc_x < borderWidth: resize_mode = "left" if self.sub_w < borderWidth: resize_mode = "right" if resizeHeight is True: if self.mc_y < borderWidth: resize_mode = "top" if self.sub_h < borderWidth: resize_mode = "bottom" if resizeWidth is True and resizeHeight is True: if self.mc_x <= borderWidth and self.mc_y <= borderWidth: resize_mode = 'top_left' if self.sub_w <= borderWidth and self.sub_h <= borderWidth: resize_mode = 'bottom_right' if self.sub_w <= borderWidth and self.mc_y <= borderWidth: resize_mode = 'top_right' if self.mc_x <= borderWidth and self.sub_h <= borderWidth: resize_mode = 'bottom_left' if self.mc_x >= borderWidth and self.mc_y >= borderWidth\ and self.sub_w >= borderWidth\ and self.sub_h >= borderWidth: resize_mode = 'center' self.resize_mode = resize_mode return self.resize_mode def eventFilter(self, obj, event): if event.type() == QEvent.Type.Enter: QApplication.setOverrideCursor(Qt.ArrowCursor) return True if event.type() == QEvent.Type.HoverMove: pos = event.pos() cur_dict = { 'right': Qt.SizeHorCursor, 'left': Qt.SizeHorCursor, 'top': Qt.SizeVerCursor, 'bottom': Qt.SizeVerCursor, 'top_left': Qt.SizeFDiagCursor, 'bottom_right': Qt.SizeFDiagCursor, 'top_right': Qt.SizeBDiagCursor, 'bottom_left': Qt.SizeBDiagCursor, 'center': Qt.ArrowCursor, None: Qt.ArrowCursor } cur = cur_dict[self.resize_mode] current_cur = QApplication.overrideCursor().shape() cur_set = cur_dict[self.resize_mode] if cur != current_cur: QApplication.changeOverrideCursor(cur_set) return True return True if event.type() == QEvent.Type.Leave: QApplication.restoreOverrideCursor() return True else: return False def mouseMoveEvent(self, pos): self.PaletteUI() self.win_x = pos.globalX() - self.mc_x self.win_y = pos.globalY() - self.mc_y if self.resize_mode is None: self.resize_mode = 'center' if self.resize_mode is 'center': self.move(self.win_x, self.win_y) else: self.re_w = self.size().width() self.re_h = self.size().height() if 'right' in self.resize_mode: self.re_w = pos.x() + self.sub_w self.resize(self.re_w, self.re_h) if 'bottom' in self.resize_mode: self.re_h = pos.y() + self.sub_h self.resize(self.re_w, self.re_h) if 'left' in self.resize_mode: self.resub_w = pos.x() - self.mc_x self.re_w = self.re_w - self.resub_w self.resize(self.re_w, self.re_h) if self.size().width() != self.pre_size_x: self.win_x = pos.globalX() - self.mc_x self.move(self.win_x, self.pos().y()) self.pre_size_x = self.size().width() if 'top' in self.resize_mode: self.resub_h = pos.y() - self.mc_y self.re_h = self.re_h - self.resub_h self.resize(self.re_w, self.re_h) if self.size().height() != self.pre_size_y: self.win_y = pos.globalY() - self.mc_y self.move(self.pos().x(), self.win_y) self.pre_size_y = self.size().height() class SysButton(QPushButton): closed = Signal() def __init__(self): super(SysButton, self).__init__() self.clicked.connect(self.__closed) def __closed(self): self.closed.emit() class MainWidget(QWidget): closed = Signal() def __init__(self): super(MainWidget, self).__init__() self.sysButton = SysButton() self.sysButton.setText('Close') self.sysButton.closed.connect(self.__close) self.mainLayout = QVBoxLayout(self) self.mainLayout.addWidget(self.sysButton) self.mainLayout.addStretch(True) def __close(self): self.closed.emit() class Example(setMainWindow): def __init__(self): super(Example, self).__init__() self.initUI() self.setWidget() self.PaletteUI() def initUI(self): self.setWindowOpacity(0.85) self.setGeometry(300, 300, 200, 150) self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint) def setWidget(self): mainWidget = MainWidget() mainWidget.closed.connect(self.gui_Close) self.setCentralWidget(mainWidget) def gui_Close(self): self.timer = QTimer() self.timer.setInterval(600) self.timer.timeout.connect(self.close) self.timer.start() self.fade = QPropertyAnimation(self, "windowOpacity") self.fade.setStartValue(0.85) self.fade.setEndValue(0.0) self.fade.setKeyValueAt(0.5, 0.0) self.fade.setEasingCurve(QEasingCurve.InOutCubic) self.fade.setDuration(600) self.fade.start() def paletteUI(self): setColors = ['#54354e', '#6a86c7'] Palette = QPalette() gradient = QLinearGradient(QRectF( self.rect()).topLeft(), QRectF(self.rect()).topRight() ) gradient.setColorAt(0.0, setColors[0]) gradient.setColorAt(1.0, setColors[1]) Palette.setBrush(QPalette.Background, QBrush(gradient)) self.setPalette(Palette) path = QPainterPath() path.addRoundedRect(self.rect(), 10, 10) region = QRegion(path.toFillPolygon().toPolygon()) self.setMask(region) def Example_UI(): global Example_UI_ex app = QApplication.instance() Example_UI_ex = Example() Example_UI_ex.show() sys.exit() app.exec_() Example_UI()
上に記述している前回までのコードをコピーして使用してください
フォルダを構成します
C:\Users\<ユーザー名>\Documents\maya\scripts
Index
- init.py
- Example.py
resource- < imageName >.png
- < imageName >.png
Index直下には今まで作成してきたソースコードを
Example.py
という名前で保存します。
一番最後にあるExample_UI()
は消しておきます
一度うまくmaya読み込めるか、以下のコードを実行してみましょう
import Index.Example as IE IE.Example_UI()
import Index.Example as IEの後にreload(IE)を記述するとモジュールをリロードできます
さて、今回の記事で作っていくものはメインのスライダー
そして6つのボタン、closeボタンとなります。
それらを作っていくにはまず必要なイメージ画像を用意します
今回、私が使用している画像のサイズはこのような構成になっています
画像の種類 | サイズ |
---|---|
Closeボタン用の画像 | 12x12px |
スライダー用の画像 | 24x24px |
その他6種類のボタン | 32x32px |
6種類のボタンのサイズが大きいサイズなのはあまり気にしないでください
もともと大きいサイズでボタンを構成していた名残です
また、データは基本pngを使用しています
このリンクからDLして使用してください
DLして解凍した画像をIndex/resource
に入れて下さい
次にその画像をQRCを作成し.py
に変換して読み込んでいきます
通常であればDesignerのリソースブラウザで画像を読み込んで~うんたらかんたらとしなければならないのですが
KIWAMIDENさんが便利なScriptを用意してくださっているのでぜひ活用してみてください
resource.pyをうまく作れましたら
Index直下にresource.py
を移動させてください
それでは画像を設定していきます
try: import shiboken except: import shiboken2 as shiboken import Index.resource ptr = OpenMayaUI.MQtUtil.mainWindow() parent = shiboken.wrapInstance(long(ptr), QWidget) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class SysButton(QPushButton): closed = Signal() def __init__(self, *args, **kwargs): super(SysButton, self).__init__(*args, **kwargs) self.clicked.connect(self.__closed) def __closed(self): self.closed.emit() class MainWidget(QWidget): closed = Signal() def __init__(self): super(MainWidget, self).__init__() self.clsBtn_UI() def clsBtn_UI(self): self.clsBtn = SysButton(self) self.clsBtn.setIcon(QIcon(":/resource/close.png")) self.clsBtn.setIconSize(QSize(14, 14)) self.clsBtn.clicked.connect(self.__close) self.clsBtn.move(236, 10) def __close(self): self.closed.emit() class Example(setMainWindow): # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def initUI(self): self.setWindowOpacity(0.85) self.setGeometry(300, 300, 260, 100)
import Index.resource
でresource.py
を読み込みますclass SysButton(QPushButton):
に*args
, **kwargs
を記述します
これがあるとエラーなくうまくSysButtonを読み込むことができるようになります
そしてclass MainWidget(QWidget):
のclsBtn以前はQVBoxLayout
を使って配置していましたが
直接位置をしたいため内容を変更しましたsetIcon(QIcon(":/resource/close.png"))
でiconを読み込むことができます
moveの位置が設定したサイズより大きいためclass Example(setMainWindow):
のsetGeometry(300, 300, 260, 100)
に変更します
実行するとうまく画像が読み込めていると思います
しかしこれでは意図したものになっていません
イメージは画像をボタンにするからです
そこで使うのがQSSです
QSSは所謂HTMLなどWebでよく使われているCSSによく似たようなものと思ってもらえばいいと思います
Index直下にstyle.qss
というものを用意してください
以下のコードを記述して保存してください
QPushButton#Mask{ border-style:none; }
border-style
プロパティは、上下左右のボーダーのスタイルをまとめて指定する際に使用します
今回はborder-style
を使用しないnoneを設定しますborder-style
についての詳細はCSS3 Draft Specification for details.を読むといいです
CSSとQSSは違いますが似ているものもありとても参考になります
異なる点では例えばborderをまとめて指定する場合
太さ・スタイル・色の順番に指定しないと無視されます
次に、style.qss
をExample.py
で読み込めるようにdef Example_UI():
の内容を変更します
ついでに二つ起動しないようにcloseの設定もします
def Example_UI(): global Example_UI_ex dirname = os.path.dirname(__file__) try: Example_UI_ex.close() del Example_UI_ex except: pass try: styleFile = os.path.join(dirname, 'style.qss') with open(styleFile, 'r') as f: style = f.read() except: style = '' print styleFile app = QApplication.instance() app.setStyleSheet(style) Example_UI_ex = Example() Example_UI_ex.show() sys.exit() app.exec_()
しかし、このまま実行してもうまく画像を読むことができません
先ほど保存したqssに#Maskという文字が記述されていますがこれはObjectName
を設定しているものでsetObjectName
で名前を指定しないと読み込んでくれません。clsBtn
にsetObjectName
を設定し、実行してみます
def clsBtn_UI(self): self.clsBtn = SysButton(self) self.clsBtn.setIcon(QIcon(":/resource/close.png")) self.clsBtn.setIconSize(QSize(14, 14)) self.clsBtn.setObjectName('Mask') self.clsBtn.clicked.connect(self.__close) self.clsBtn.move(236, 10)
これでうまく画像だけのボタンを作ることができました
次にQLabel
を設定します
QLabel#Header{ font-family: Cast Iron Condensed; font-size: 30px; color: #fff1e6; }
class MainWidget(QWidget): closed = Signal() def __init__(self): super(MainWidget, self).__init__() self.initUI() self.clsBtn_UI() def initUI(self): self.text = QLabel("Darken", self) self.text.move(8, 10) self.text.setObjectName("Header")
fontはCast Iron Condensed
を使用していますが好きなものに設定してくださいfont-size
プロパティは、フォントのサイズを指定する際に使用しますcolor
プロパティは、カラーを指定する際に使用します
うまくテキストが表示されたでしょうか
次に6つのボタンを用意していくのですが
このボタンには右と左クリックを認識するように設定します
まず右と左を認識することができるクラスを用意します
class cCMRPushButton(QPushButton): Click = Signal() def __init__(self, string, *args, **kwargs): super(cCMRPushButton, self).__init__(string, *args, **kwargs) def mousePressEvent(self, event): QPushButton.mousePressEvent(self, event) if event.button() == Qt.RightButton: return elif event.button() == Qt.LeftButton: return
例のごとく、mousePressEvent
を使い右のクリックか左のクリックか認識できるように設定します
次にcCMRPushButton
を6つ用意します
左クリックを認識させるにはsetContextMenuPolicy
が必要です、そしてcustomContextMenuRequested.connect
でシグナルをコネクトします
今回は何が今押されているかをプリントする機能を用意します
def initUI(self): self.text = QLabel("Darken", self) self.text.move(8, 10) self.text.setObjectName("Header") Mtl_text = ["Btn_(0)", "Btn_(1)", "Btn_(2)", "Btn_(3)", "Btn_(4)", "Btn_(5)"] Mtl_num = int(len(Mtl_text)) self.Mtl_Btns = [] for i in range(Mtl_num): self.Mtl_Btns.append(cCMRPushButton(self)) self.Mtl_Btns[i].setContextMenuPolicy(Qt.CustomContextMenu) self.Mtl_Btns[i].setIcon( QIcon(":/resource/%s.png" % (Mtl_text[i])) ) self.Mtl_Btns[i].move(38 + i * 32, 76) self.Mtl_Btns[i].setObjectName('Mask') self.Mtl_Btns[i].clicked.connect(self.push_right) self.Mtl_Btns[i].customContextMenuRequested.connect( self.push_left ) def push_right(self): sender = self.sender() if sender is self.Mtl_Btns[0]: print "Mtl_Btns[0] -right" elif sender is self.Mtl_Btns[1]: print "Mtl_Btns[1] -right" elif sender is self.Mtl_Btns[2]: print "Mtl_Btns[2] -right" elif sender is self.Mtl_Btns[3]: print "Mtl_Btns[3] -right" elif sender is self.Mtl_Btns[4]: print "Mtl_Btns[4] -right" elif sender is self.Mtl_Btns[5]: print "Mtl_Btns[5] -right" def push_left(self): sender = self.sender() if sender is self.Mtl_Btns[0]: print "Mtl_Btns[0] -left" elif sender is self.Mtl_Btns[1]: print "Mtl_Btns[1] -left" elif sender is self.Mtl_Btns[2]: print "Mtl_Btns[2] -left" elif sender is self.Mtl_Btns[3]: print "Mtl_Btns[3] -left" elif sender is self.Mtl_Btns[4]: print "Mtl_Btns[4] -left" elif sender is self.Mtl_Btns[5]: print "Mtl_Btns[5] -left"
うまくいくと右クリックを押したときにどのボタンが押されたかを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です
ディスカッション
コメント一覧
まだ、コメントがありません