Styling¶
Style an Existing Application¶
To give an application you already have the fxgui look, make its
QApplication a themed root:
from qtpy.QtWidgets import QApplication
from fxgui import fxstyle
application = QApplication([])
fxstyle.register_themed_root(application)
fxstyle.apply_theme("dracula")
That puts the theme's stylesheet, palette and font on the application,
and again on every later fxstyle.apply_theme(). FXApplication does
this on itself, so with it you call nothing.
Inside a host application (Houdini, Maya, Nuke), never register the
host's QApplication: register your own top-level window.
from qtpy.QtWidgets import QMainWindow
from fxgui import fxstyle
window = QMainWindow()
fxstyle.register_themed_root(window)
FXMainWindow, FXFloatingDialog, FXSplashScreen and the
FXSystemTray menu do this on themselves, so the host is never
restyled. While the QApplication is a themed root, registering a widget
does nothing: the application's sheet already reaches it.
There is no stylesheet to copy by hand: register_themed_root() is the
one way to theme a window, and it follows every switch. See Theming for what the stylesheet, the palette
and the font each carry, reading colours (fxstyle.colors()), and
registering your own widget styles (register_widget_style()).
Apply the Custom Google Material Icons¶
You can find a QProxyStyle subclass in fxstyle, called FXProxyStyle. When used on a QApplication instance, it allows you to switch the defaults icons provided by Qt for Google Material icons.
from qtpy.QtWidgets import QApplication
from fxgui import fxstyle
application = QApplication([])
application.setStyle(fxstyle.FXProxyStyle())
Tip
The FXApplication class found inside fxwidgets already applies this custom style.
You can now use the icons by doing:
from qtpy.QtWidgets import QStyle
from fxgui import fxwidgets
application = fxwidgets.FXApplication()
window = fxwidgets.FXMainWindow(title="My App")
style = window.style()
# Use standard icons that are automatically themed
print(style.standardIcon(QStyle.SP_MessageBoxCritical))
window.show()
application.exec()
Note
By default, the FXApplication found inside fxwidgets already applies this custom style.
Warning
Applying the FXProxyStyle is only allowed on a QApplication instance! So if you're instantiating a FXMainWindow inside a parent DCC, do not set the style on it.
Frame a Window Around Its Panes¶
FXMainWindow(framed=True) draws the window as one frame around its panes, the way a code editor does: the menu bar, the toolbars, the status bar and the window behind the central widget all paint the theme's frame color, with no lines between them.
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QListWidget, QSplitter, QTextEdit, QVBoxLayout, QWidget
from fxgui import fxstyle, fxwidgets
application = fxwidgets.FXApplication()
window = fxwidgets.FXMainWindow(title="My App", framed=True)
window.set_banner_text("My App")
window.set_banner_icon("widgets")
body = QWidget()
fxstyle.mark_as_frame(body)
layout = QVBoxLayout(body)
layout.setContentsMargins(6, 0, 6, 6)
splitter = QSplitter(Qt.Horizontal)
splitter.setHandleWidth(6)
fxstyle.mark_as_frame(splitter)
splitter.addWidget(QListWidget())
splitter.addWidget(QTextEdit())
layout.addWidget(splitter)
window.setCentralWidget(body)
window.show()
application.exec()
What changes in a framed window:
| Part | Plain window | Framed window |
|---|---|---|
| Menu bar, toolbars, status bar | surface_sunken / surface, with a line under each |
frame, no lines |
| Status bar accent line | Shown, with a 1 px line under it | Shown, with no line under it |
A status bar set later with setStatusBar() |
As given | Painted in frame too |
In both, the window's icon and name sit at the right end of the menu bar,
in window.title_corner (banner_icon and banner_label), with no fill
of their own. set_banner_text() and set_banner_icon() change them; the
icon defaults to 16 px.
A framed window paints the frame behind your central widget, but the
central widget itself keeps the surface: mark it, and any band of your own
inside it, with fxstyle.mark_as_frame(). A child of the window left
unmarked keeps the surface, which is what a pane should do.
What mark_as_frame() does¶
| Widget marked | Result |
|---|---|
| Any widget | Paints the frame color |
A disabled QToolButton placed directly in it |
No fill and no border, so it sits on the frame |
A QSplitter |
Its handles are gaps in the frame color with a short centred mark of five dots in the splitter_mark color; the handle keeps exactly the width setHandleWidth() gives it |
Call fxstyle.mark_as_frame(widget, False) to remove the mark. A plain label has no fill anywhere, so it shows whatever is behind it: the frame in a marked band, the pane's colour in a pane.
Note
The mark is painted, not loaded from an image: it follows theme switches, stays the same size on screen at 100 %, 150 % and 200 % scaling (each dot is two logical pixels, rounded to whole screen pixels), and reaches handles the splitter creates after it was marked. Set the handle width before or after marking; either order works.
For the 1 px edge of a pane sitting on the frame, use the @pane_border token (or fxstyle.colors().pane_border) rather than @border: in some themes border is nearly the frame's own color.
Flat Icon Buttons¶
An icon-only QPushButton given the fxRole="flat" property has no box: it shows a fill on hover and a darker one when pressed, an accent border when it has keyboard focus, and nothing when disabled.
from qtpy.QtWidgets import QPushButton
from fxgui import fxicons
back = QPushButton()
back.setProperty("fxRole", "flat")
fxicons.set_icon(back, "arrow_back")
Set the property before the button is shown, or call fxutils.repolish(button) after changing it.