【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】
こんにちはMayaPython Advent Calendar 2017の7日目の記事です
全記事一覧です
【MayaPySide】ちょっとおしゃれなUIメソッド【1日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【2日目】
【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】
ちょっとおしゃれなUIメソッドの二日目です
今回は実は予定していなかったものなのですがフレームレスの需要が高まっている中
Windowのresize
や閉じるときのアニメーションの需要があるようなので今回記事にしました。
改良した
最小のサイズの設定とマスクの適応 pic.twitter.com/HtjoozAVIj— リンゴ酸@ryota unzai (@unpyside) December 5, 2017
リサイズはこんな感じの簡単なものを想定していたのですが
オシャレウィジェットをリサイズできるサンプルコードを置いときますね。https://t.co/0uMR4BJDjz
OceanTeaBaseMixinクラスをつかうとそのままでリサイズできるようになります。
おシャンティを継承しましょう(´ω`) pic.twitter.com/l3v6H6zZc2— ys (@blindys45) December 5, 2017
ysさんが素敵なサンプルを用意してくださったので
それを利用して今回の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 setMainWindow(QMainWindow):
def __init__(self):
super(setMainWindow, self).__init__(parent)
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()
def mouseMoveEvent(self, pos):
winX = pos.globalX() - self.mc_x
winY = pos.globalY() - self.mc_y
self.move(winX, winY)
class Example(setMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
self.setButton()
self.paletteUI()
def initUI(self):
self.setWindowOpacity(0.85)
self.setGeometry(300, 300, 200, 150)
self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)
def setButton(self):
btn = QPushButton("Close", self)
btn.move(50,50)
btn.clicked.connect(self.close)
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()
まず最初にクローズボタンのカスタマイズからしていきましょう
クローズボタンのカスタマイズは閉じる際にフェードアウトして消えていく、というアニメーションを付けます
使用するのはQTimer、QPropertyAnimationです
QTimerというのは繰り返すとシングルショットタイマーを使用することができるものです
1000の単位で1秒となります。
では作っていきましょう。
まず最初にCloseButton専用のclassを作っていきます
class SysButton(QPushButton):
closed = Signal()
def __init__(self):
super(SysButton, self).__init__()
self.clicked.connect(self.__closed)
def __closed(self):
self.closed.emit()
PySideにはシグナルとスロットというものがあります。
シグナルclosedを作成し、Close専用のQPushButtonを用意しました。
次にWidgetを用意します
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()
作成したWidgetを前回作ったsetButtonをsetWindget名前を変え、Widgetを埋め込むものに作り替えます
具体的にはclass Example(setMainWindow)のCentralWidgetにMainWidgetを埋め込みます
そしてgui_Close`という関数を作ります
内容はQTimerは0.6秒後にWindowをCloseさせ、QPropertyAnimationは0.6秒かけてWindowのOpacityを0にするという機能です
そしてその機能とSignalをコネクションします。
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()
次にWindowをリサイズする機能をsetMainWindowに追加していきます
class setMainWindow(QMainWindow):
def __init__(self):
super(setMainWindow, self).__init__(parent)
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)
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)
if event.type() == QEvent.Type.Leave:
QApplication.restoreOverrideCursor()
def mouseMoveEvent(self, pos):
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()
def mousePressEvent(self, pos)には上下左右斜のそれぞれ端から8pxの位置にマウスを押した場合、どの位置にあるかを取得する機能を設定しています
そして、def mouseMoveEvent(self, pos)でどの位置にあるかによってresize処理をどう実行させるかを設定しています。
また、PySideはeventFilter関数をつかうことでeventを受け取れる仕組みがあります。
eventを受け取るボタンをinstallEventFilterという関数でeventに登録し、event関数である eventFilterを定義し、そのeventを監視することができます
def eventFilter(self, obj, event)はWindowをResizeする際にマウスの見た目を変更する処理を入れています
def eventFilter(self, obj, event)を登録するためにself.installEventFilter(self)を読み込んでいます。
しかし、このまま実行するとUIがおかしくなります
なぜかというとmaskのサイズが変更されていないからです
ですのでサイズを大きくすれば変に伸びて、形が切れていきます
これを防ぐためにdef paletteUI(self)をベースに作った、maskUIという新しいクラスを用意します
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__()
内容は一緒です
そしてsetMainWindowの継承をmaskUIに変更します
コンストラクタはsetMainWindowでmayaをparentしているので空にしておきます。
そして、PaletteUIをmouseMoveEvent、Exmple、それぞれに読み込みます
内容は一緒です
def mouseMoveEvent(self, pos):
self.PaletteUI()
# ~~~~~~~~~~~~~~~~~~~~~~~~
class Example(setMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
self.setWidget()
self.PaletteUI()
次回はUIのデザイン部分をしていきます。
追記
イベントをフィルタリングする場合Trueを返し、それ以外の場合はFalseを返さないといけません
その処理を入れ忘れていたため、# RuntimeWarning:が出ていました
そのため、def eventFilter(self, obj, event)の処理を以下に変更する必要があります
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
次回はUIのデザイン部分をしていきます。
【MayaPySide】ちょっとおしゃれなUIメソッド【3日目】の記事でUIの見た目を設定していきます
MayaPython Advent Calendar 2017の8日目の記事はSEN_AさんのMaya Python API 2.0 を使ってCurveからclosestPointを取得するノードを作ろう!です
ディスカッション
コメント一覧
まだ、コメントがありません