How to make animated backgrounds?
-
@Bonnie in theory you can place small QGraphicsView and scene the size of your desired label, attach animated item as a background with the text item on top acting as label. You'll have to give up (or write your own) accelerator routine or onClick handler though.
Also, the above is for sure an overkill and I can't speak about the performance penalty of such a contraption but in theory that could provide the desired effect... -
@Bonnie So like mentioned before, that's the problem, you can't place other widgets in QLabel
@Mizmas said in How to make animated backgrounds?:
@Bonnie So like mentioned before, that's the problem, you can't place other widgets in QLabel
Actually you can, you just can't do that in the designer.
QLabel is just a QWidget, it is possible to set layout on it and add child widgets to it. -
@Bonnie in theory you can place small QGraphicsView and scene the size of your desired label, attach animated item as a background with the text item on top acting as label. You'll have to give up (or write your own) accelerator routine or onClick handler though.
Also, the above is for sure an overkill and I can't speak about the performance penalty of such a contraption but in theory that could provide the desired effect...@artwaw If we are talking about handling all the animation by ourselves, sure it is possible.
You don't even need to use QGraphicsView, just reimplementing QWidget would be enough :)
But it is so not worth it because we already have QLabel+QMovie that handling all we need. -
@artwaw I haven't tried it yet, but would it be possible to use a QStackedWidget, have the QLabel in one page, and the QFrame widget on another page. Could you then display both pages at the same time?
@Mizmas said in How to make animated backgrounds?:
@artwaw I haven't tried it yet, but would it be possible to use a QStackedWidget, have the QLabel in one page, and the QFrame widget on another page. Could you then display both pages at the same time?
This is also possible, but not in designer :)
-
@Mizmas said in How to make animated backgrounds?:
@artwaw I haven't tried it yet, but would it be possible to use a QStackedWidget, have the QLabel in one page, and the QFrame widget on another page. Could you then display both pages at the same time?
This is also possible, but not in designer :)
@Bonnie said in How to make animated backgrounds?:
@artwaw I haven't tried it yet, but would it be possible to use a QStackedWidget, have the QLabel in one page, and the QFrame widget on another page. Could you then display both pages at the same time?
This is also possible, but not in designer :)
How? I've used QStackedWidget a couple of times only setting the pages with setCurrentIndex (index)
-
@Bonnie said in How to make animated backgrounds?:
@artwaw I haven't tried it yet, but would it be possible to use a QStackedWidget, have the QLabel in one page, and the QFrame widget on another page. Could you then display both pages at the same time?
This is also possible, but not in designer :)
How? I've used QStackedWidget a couple of times only setting the pages with setCurrentIndex (index)
@Mizmas said in How to make animated backgrounds?:
@Bonnie said in How to make animated backgrounds?:
@artwaw I haven't tried it yet, but would it be possible to use a QStackedWidget, have the QLabel in one page, and the QFrame widget on another page. Could you then display both pages at the same time?
This is also possible, but not in designer :)
How? I've used QStackedWidget a couple of times only setting the pages with setCurrentIndex (index)
QStackedWidget
usesQStackedLayout
class to manage child widgets.
QStackedLayout
can setstackingMode
toQStackedLayout::StackAll
to show all the widgets.
So just need to get aQLayout *
by calling the stackedWidget'slayout()
method and convert it toQStackedLayout *
. -
@Mizmas said in How to make animated backgrounds?:
@Bonnie said in How to make animated backgrounds?:
@artwaw I haven't tried it yet, but would it be possible to use a QStackedWidget, have the QLabel in one page, and the QFrame widget on another page. Could you then display both pages at the same time?
This is also possible, but not in designer :)
How? I've used QStackedWidget a couple of times only setting the pages with setCurrentIndex (index)
QStackedWidget
usesQStackedLayout
class to manage child widgets.
QStackedLayout
can setstackingMode
toQStackedLayout::StackAll
to show all the widgets.
So just need to get aQLayout *
by calling the stackedWidget'slayout()
method and convert it toQStackedLayout *
.@Bonnie Thanks for all the tips, I've tried it with a random gif in a stacked widget and it works with your method.
If anyone finds this thread in the future I did it like this:
self.your_stacked_widget = self.findChild(QStackedWidget, 'your_stacked_widget') self.background_label = self.findChild(QLabel, 'background_label') movie = QMovie(":/your_gif.gif") movie.setScaledSize(self.your_stacked_widget.size()) self.background_label.setMovie(movie) movie.start() if self.your_stacked_widget: # Get the QLayout from the stacked widget and cast it to QStackedLayout self.stackedLayout = self.your_stacked_widget.layout() if isinstance(self.stackedLayout, QStackedLayout): # Set the stacking mode to StackAll to show all widgets self.stackedLayout.setStackingMode(QStackedLayout.StackingMode.StackAll)
Would this be most straightforward method if you want to lay everything out in QT Designer?
-
@Bonnie Thanks for all the tips, I've tried it with a random gif in a stacked widget and it works with your method.
If anyone finds this thread in the future I did it like this:
self.your_stacked_widget = self.findChild(QStackedWidget, 'your_stacked_widget') self.background_label = self.findChild(QLabel, 'background_label') movie = QMovie(":/your_gif.gif") movie.setScaledSize(self.your_stacked_widget.size()) self.background_label.setMovie(movie) movie.start() if self.your_stacked_widget: # Get the QLayout from the stacked widget and cast it to QStackedLayout self.stackedLayout = self.your_stacked_widget.layout() if isinstance(self.stackedLayout, QStackedLayout): # Set the stacking mode to StackAll to show all widgets self.stackedLayout.setStackingMode(QStackedLayout.StackingMode.StackAll)
Would this be most straightforward method if you want to lay everything out in QT Designer?
-
@Bonnie Thanks for all the tips, I've tried it with a random gif in a stacked widget and it works with your method.
If anyone finds this thread in the future I did it like this:
self.your_stacked_widget = self.findChild(QStackedWidget, 'your_stacked_widget') self.background_label = self.findChild(QLabel, 'background_label') movie = QMovie(":/your_gif.gif") movie.setScaledSize(self.your_stacked_widget.size()) self.background_label.setMovie(movie) movie.start() if self.your_stacked_widget: # Get the QLayout from the stacked widget and cast it to QStackedLayout self.stackedLayout = self.your_stacked_widget.layout() if isinstance(self.stackedLayout, QStackedLayout): # Set the stacking mode to StackAll to show all widgets self.stackedLayout.setStackingMode(QStackedLayout.StackingMode.StackAll)
Would this be most straightforward method if you want to lay everything out in QT Designer?
@Mizmas said in How to make animated backgrounds?:
Would this be most straightforward method if you want to lay everything out in QT Designer?
I have another approach, but I don't know how to do that in PyQt, so I'll just show it in C++.
To make a widget in designer still acting like a QWidget/QFrame, but actually being a QLabel, we can use promoting.
(In case anyone don't know about promoting widgets, here's the documentation: https://doc.qt.io/qt-6/designer-using-custom-widgets.html#promoting-widgets)
So right click the background widget and select "Promote to ...".
It would be great if we can promote it to QLabel directly, sadly designer doesn't allow that, so we promote it to a custom class name, say "QLabelWidget" with the header file "qlabelwidget.h".
Then create "qlabelwidget.h" with below content:#ifndef QLABELWIDGET_H #define QLABELWIDGET_H #include <QLabel> typedef QLabel QLabelWidget; #endif // QLABELWIDGET_H
So the background widget will become a QLabelWidget, aka QLabel. You cannot edit its QLabel properties in the designer, but it can call QLabel member functions in the code. Since QMovie can only be set in the code, I think that's not a problem.
-
@Mizmas said in How to make animated backgrounds?:
Would this be most straightforward method if you want to lay everything out in QT Designer?
I have another approach, but I don't know how to do that in PyQt, so I'll just show it in C++.
To make a widget in designer still acting like a QWidget/QFrame, but actually being a QLabel, we can use promoting.
(In case anyone don't know about promoting widgets, here's the documentation: https://doc.qt.io/qt-6/designer-using-custom-widgets.html#promoting-widgets)
So right click the background widget and select "Promote to ...".
It would be great if we can promote it to QLabel directly, sadly designer doesn't allow that, so we promote it to a custom class name, say "QLabelWidget" with the header file "qlabelwidget.h".
Then create "qlabelwidget.h" with below content:#ifndef QLABELWIDGET_H #define QLABELWIDGET_H #include <QLabel> typedef QLabel QLabelWidget; #endif // QLABELWIDGET_H
So the background widget will become a QLabelWidget, aka QLabel. You cannot edit its QLabel properties in the designer, but it can call QLabel member functions in the code. Since QMovie can only be set in the code, I think that's not a problem.
-
I've been experimenting some more with gif backgrounds, subclassing a QFrame using QLabel.
I tried making a background for a push button, and the background gif changes based whether the cursor is hovering over the button or not.
This is what I have so far: https://streamable.com/k02xqk# set initial state for website button self.website_button_frame = self.findChild(GifFrame, 'website_button_frame') self.gif = QMovie(':/animations/website_logo.gif') self.gif.setScaledSize(self.website_button_frame.size()) self.website_button_frame.setMovie(self.gif) self.gif.start() # Install an event filter on the website button self.website_button.installEventFilter(self) # website_button hover/not hover filter def eventFilter(self, watched, event): if watched == self.website_button: if event.type() == QEvent.Type.Enter: # Action when hovering gif = QMovie(":/animations/website_logo_purple.gif") gif.setScaledSize(self.website_button_frame.size()) self.website_button_frame.setMovie(gif) gif.start() elif event.type() == QEvent.Type.Leave: # Action when not hovering gif = QMovie(":/animations/website_logo.gif") gif.setScaledSize(self.website_button_frame.size()) self.website_button_frame.setMovie(gif) gif.start() return super().eventFilter(watched, event)
Basically, I'm using two gifs with two different colors, they both have transparent backgrounds and solid color objects. I'm using an event filter to change the two, based on whether the cursor is hovering over the button or not. The problem is that this transition is not smooth and jittery. This is because both the QMovie objects start over again from the start and not on the frame that was displayed before. I've tried using methods of the QMovie like currentFrameNumber() and jumpToFrame(), but they only seem to work if you're using the same QMovie (at least I couldn't get it to work).
Is there some approach I can take to work with a single .gif file, and somehow programmatically change the color of the solid color objects while the gif is playing? Or is there some different stuff I can try, maybe outside of QT?
-
There is some different stuff you can try, inside Qt. Namely Qt Quick.
In my opinion it is much more suited to the kind of application you appear to make.
QtQuick.Effects's MultiEffect can color any abritrary itemFor fun I tried to recreate the stuff you showed in QML with Qt Quick, here's the quick and a bit ugly code:
import QtQuick import QtQuick.Controls import QtQuick.Effects ApplicationWindow { id: window width: 640 height: 480 visible: true property real radius: 8 flags: Qt.FramelessWindowHint color: "transparent" Rectangle { parent: window.contentItem.parent z: -1 anchors.fill: parent color: "#F7BF8A" radius: window.radius border.width: 1 } footer: Rectangle { color: "#B1C368" height: 50 topLeftRadius: 0 topRightRadius: 0 bottomLeftRadius: window.radius bottomRightRadius: window.radius border.width: 1 Text { id: siteLink anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 20 } text: "By midimagic" font.family: "Courier New" color: hoverHandler.hovered ? "#8D7BDE" : "black" HoverHandler { id: hoverHandler cursorShape: Qt.PointingHandCursor } TapHandler { onTapped: Qt.openUrlExternally("https://forum.qt.io") } AnimatedImage { id: siteGif visible: false anchors { fill: parent margins: -15 } source: "https://emoji.discadia.com/emojis/d3897e57-3a33-4136-a6bb-882343233261.gif" fillMode: Image.PreserveAspectFit } MultiEffect { anchors.fill: siteGif source: siteGif colorizationColor: siteLink.color colorization: 1 } } } }
Here's the result: ( the sluggyness is only in the recording, it runs smooth)
-
There is some different stuff you can try, inside Qt. Namely Qt Quick.
In my opinion it is much more suited to the kind of application you appear to make.
QtQuick.Effects's MultiEffect can color any abritrary itemFor fun I tried to recreate the stuff you showed in QML with Qt Quick, here's the quick and a bit ugly code:
import QtQuick import QtQuick.Controls import QtQuick.Effects ApplicationWindow { id: window width: 640 height: 480 visible: true property real radius: 8 flags: Qt.FramelessWindowHint color: "transparent" Rectangle { parent: window.contentItem.parent z: -1 anchors.fill: parent color: "#F7BF8A" radius: window.radius border.width: 1 } footer: Rectangle { color: "#B1C368" height: 50 topLeftRadius: 0 topRightRadius: 0 bottomLeftRadius: window.radius bottomRightRadius: window.radius border.width: 1 Text { id: siteLink anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 20 } text: "By midimagic" font.family: "Courier New" color: hoverHandler.hovered ? "#8D7BDE" : "black" HoverHandler { id: hoverHandler cursorShape: Qt.PointingHandCursor } TapHandler { onTapped: Qt.openUrlExternally("https://forum.qt.io") } AnimatedImage { id: siteGif visible: false anchors { fill: parent margins: -15 } source: "https://emoji.discadia.com/emojis/d3897e57-3a33-4136-a6bb-882343233261.gif" fillMode: Image.PreserveAspectFit } MultiEffect { anchors.fill: siteGif source: siteGif colorizationColor: siteLink.color colorization: 1 } } } }
Here's the result: ( the sluggyness is only in the recording, it runs smooth)
-
Alternatively if you want to stick with QWidgets you could retrieve the current frame number of the running QMovie and set it to the new one when switching. With
currentFrameNumber
andjumpToFrame
. -
Alternatively if you want to stick with QWidgets you could retrieve the current frame number of the running QMovie and set it to the new one when switching. With
currentFrameNumber
andjumpToFrame
. -
Alternatively if you want to stick with QWidgets you could retrieve the current frame number of the running QMovie and set it to the new one when switching. With
currentFrameNumber
andjumpToFrame
. -
@GrecKo Though, there are times where the gifs begin to lag and slow down for a moment, even when the cursor is not interacting with them. This happens on all other QMovie objects with gifs that I've tried, even if there's no cursor interaction coded