From 970305eee2fd24b23b998b71e73d06e7ad542218 Mon Sep 17 00:00:00 2001 From: Ben Niemann Date: Tue, 23 Nov 2021 09:36:05 +0100 Subject: [PATCH] Setting QML style and Material theme, variant. --- noisicaa.nix | 1 + noisicaa/ui/main.py | 25 ++++++++++++++++++ noisicaa/ui/qml/GraphView.qml | 19 ++++++++------ noisicaa/ui/qml/ProjectWindow.qml | 4 +-- noisicaa/ui/qml/SettingsDialog.py | 40 +++++++++++++++++++++++++++-- noisicaa/ui/qml/SettingsDialog.qml | 31 ++++++++++++++++++++++ noisicaa/ui/qt_helper.cpp | 41 ++++++++++++++++++++++++++++++ noisicaa/ui/qt_helper.h | 30 ++++++++++++++++++++++ noisicaa/ui/qt_helper.pyx | 30 ++++++++++++++++++++++ noisicaa/ui/wscript | 11 ++++++++ wscript | 2 +- 11 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 noisicaa/ui/qt_helper.cpp create mode 100644 noisicaa/ui/qt_helper.h create mode 100644 noisicaa/ui/qt_helper.pyx diff --git a/noisicaa.nix b/noisicaa.nix index 44d681f2..1c0b3c6f 100644 --- a/noisicaa.nix +++ b/noisicaa.nix @@ -158,6 +158,7 @@ in stdenv.mkDerivation { nixpkgs.fmt qt5.qmake qt5.qtbase + qt5.qtquickcontrols2.dev qt5.qttools.dev ] ++ lib.lists.optional withTests [ nixpkgs.gtest.dev diff --git a/noisicaa/ui/main.py b/noisicaa/ui/main.py index 58206ce8..c18afd35 100644 --- a/noisicaa/ui/main.py +++ b/noisicaa/ui/main.py @@ -22,6 +22,7 @@ import argparse import asyncio import contextlib import functools +import os import os.path import signal import sys @@ -40,6 +41,7 @@ from . import ui_base from . import project from . import node_db from . import qml +from . import qt_helper from . import ui_rc logger = None # type: logging.Logger @@ -57,6 +59,9 @@ class QEventLoopPolicy(asyncio.DefaultEventLoopPolicy): class App(ui_base.PropertyContainer, QtCore.QObject): needsRestart, needsRestartChanged = ui_base.Property('needsRestart', bool) displayMultisampling, displayMultisamplingChanged = ui_base.Property('displayMultisampling', bool) + displayStyle, displayStyleChanged = ui_base.Property('displayStyle', str) + displayMaterialTheme, displayMaterialThemeChanged = ui_base.Property('displayMaterialTheme', str) + displayMaterialVariant, displayMaterialVariantChanged = ui_base.Property('displayMaterialVariant', str) def __init__(self) -> None: super().__init__() @@ -137,6 +142,12 @@ class App(ui_base.PropertyContainer, QtCore.QObject): with self.settingsGroup('display') as settings: self.displayMultisampling = (settings.value('multisampling', 'true') == 'true') self.displayMultisamplingChanged.connect(self.__displayMultisamplingChanged) + self.displayStyle = settings.value('style', 'Material') + self.displayStyleChanged.connect(self.__displayStyleChanged) + self.displayMaterialTheme = settings.value('materialTheme', 'System') + self.displayMaterialThemeChanged.connect(self.__displayMaterialThemeChanged) + self.displayMaterialVariant = settings.value('materialVariant', 'Normal') + self.displayMaterialVariantChanged.connect(self.__displayMaterialVariantChanged) if self.displayMultisampling: logger.info("Enable multisampling.") @@ -144,6 +155,9 @@ class App(ui_base.PropertyContainer, QtCore.QObject): format.setSamples(16) QtGui.QSurfaceFormat.setDefaultFormat(format) + os.environ["QT_QUICK_CONTROLS_STYLE"] = self.displayStyle + os.environ["QT_QUICK_CONTROLS_MATERIAL_VARIANT"] = self.displayMaterialVariant + self.__qmlEngine = QtQml.QQmlEngine(self.__qtApp) self.__qmlEngine.warnings.connect(self.__qmlEngineWarnings) self.__qmlEngine.setOutputWarningsToStandardError(False) @@ -258,6 +272,17 @@ class App(ui_base.PropertyContainer, QtCore.QObject): self.__settings.setValue('display/multisampling', 'true' if v else 'false') self.needsRestart = True + def __displayStyleChanged(self, v): + self.__settings.setValue('display/style', v) + self.needsRestart = True + + def __displayMaterialThemeChanged(self, v): + self.__settings.setValue('display/materialTheme', v) + + def __displayMaterialVariantChanged(self, v): + self.__settings.setValue('display/materialVariant', v) + self.needsRestart = True + def quit(self, exit_code: int = 0) -> None: self.__exit_code = exit_code self.__quit.set() diff --git a/noisicaa/ui/qml/GraphView.qml b/noisicaa/ui/qml/GraphView.qml index c2aa5773..91e67bd0 100644 --- a/noisicaa/ui/qml/GraphView.qml +++ b/noisicaa/ui/qml/GraphView.qml @@ -26,12 +26,11 @@ import QtQuick.Controls 2.15 import "." as Noisicaa import "matrix.js" as Transform -Rectangle { +Control { id: root property QtObject d - - color: "#ffffff" + property color bgColor: ApplicationWindow.window.color Menu { id: contextMenu @@ -160,7 +159,7 @@ Rectangle { property real step: 20 * d.zoom property real offsetx: d.offset.x % step property real offsety: d.offset.y % step - + property color color: Qt.tint(root.bgColor, "#20808080") visible: step > 5 Repeater { @@ -171,7 +170,7 @@ Rectangle { ShapePath { fillColor: "transparent" strokeWidth: 1 - strokeColor: "#f8f8f8" + strokeColor: grid.color startX: 0 startY: 0 PathLine { @@ -190,7 +189,7 @@ Rectangle { ShapePath { fillColor: "transparent" strokeWidth: 1 - strokeColor: "#f8f8f8" + strokeColor: grid.color startX: 0 startY: 0 PathLine { @@ -375,8 +374,9 @@ Rectangle { } Rectangle { - color: "#ffffff" - border { width: 1; color: "#000000" } + color: root.bgColor + border.width: 1 + border.color: Qt.tint(root.bgColor, "#a0808080") anchors { left: parent.left leftMargin: 20 @@ -391,6 +391,7 @@ Rectangle { anchors.fill: parent anchors.margins: 2 clip: true + color: parent.color property var complete: false property rect contentRect: d.contentRect @@ -403,8 +404,10 @@ Rectangle { var t = new Transform.Matrix(); if (d.nodes.rowCount() == 0) { + minimapView.visible = false; return; } + minimapView.visible = true; var sx = width / contentRect.width; var sy = height / contentRect.height; diff --git a/noisicaa/ui/qml/ProjectWindow.qml b/noisicaa/ui/qml/ProjectWindow.qml index a26ef4ff..74d3c93b 100644 --- a/noisicaa/ui/qml/ProjectWindow.qml +++ b/noisicaa/ui/qml/ProjectWindow.qml @@ -1,5 +1,6 @@ import QtQuick 2.12 import QtQuick.Controls 2.15 +import QtQuick.Controls.Material 2.12 ApplicationWindow { id: root @@ -9,8 +10,7 @@ ApplicationWindow { width: 1000 height: 800 - Component.onCompleted: { - } + Material.theme: d.app.displayMaterialTheme onClosing: function (event) { d.closeProject() diff --git a/noisicaa/ui/qml/SettingsDialog.py b/noisicaa/ui/qml/SettingsDialog.py index 254de40c..08cfe21c 100644 --- a/noisicaa/ui/qml/SettingsDialog.py +++ b/noisicaa/ui/qml/SettingsDialog.py @@ -25,6 +25,7 @@ from PySide2.QtCore import Qt from PySide2 import QtCore from noisicaa.ui import ui_base +from noisicaa.ui import qt_helper logger = logging.getLogger(__name__) @@ -33,12 +34,18 @@ class SettingsDialog(ui_base.CommonMixin, ui_base.PropertyContainer, QtCore.QObj modified, modifiedChanged = ui_base.Property('modified', bool) audioBackendIndex, audioBackendIndexChanged = ui_base.Property('audioBackendIndex', int) displayMultisampling, displayMultisamplingChanged = ui_base.Property('displayMultisampling', bool) + displayStyleIndex, displayStyleIndexChanged = ui_base.Property('displayStyleIndex', int) + displayMaterialThemeIndex, displayMaterialThemeIndexChanged = ui_base.Property('displayMaterialThemeIndex', int) + displayMaterialVariantIndex, displayMaterialVariantIndexChanged = ui_base.Property('displayMaterialVariantIndex', int) def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.audioBackendIndexChanged.connect(self.__setModified) self.displayMultisamplingChanged.connect(self.__setModified) + self.displayStyleIndexChanged.connect(self.__setModified) + self.displayMaterialThemeIndexChanged.connect(self.__setModified) + self.displayMaterialVariantIndexChanged.connect(self.__setModified) def __setModified(self, *args): self.modified = True @@ -47,13 +54,39 @@ class SettingsDialog(ui_base.CommonMixin, ui_base.PropertyContainer, QtCore.QObj def audioBackendList(self): return ['jack', 'dummy'] + @ui_base.ConstProperty(list) + def displayStyleList(self): + return qt_helper.availableQuickStyles() + + @ui_base.ConstProperty(list) + def displayMaterialThemeList(self): + return ['Light', 'Dark', 'System'] + + @ui_base.ConstProperty(list) + def displayMaterialVariantList(self): + return ['Normal', 'Dense'] + @QtCore.Slot() def reset(self): with self.app.settingsGroup('audio') as settings: - self.audioBackendIndex = self.audioBackendList.index(settings.value('backend', 'jack')) + try: + self.audioBackendIndex = self.audioBackendList.index(settings.value('backend', 'jack')) + except ValueError: + self.audioBackendIndex = 0 self.displayMultisampling = self.app.displayMultisampling - + try: + self.displayStyleIndex = self.displayStyleList.index(self.app.displayStyle) + except ValueError: + self.displayStyleIndex = 0 + try: + self.displayMaterialThemeIndex = self.displayMaterialThemeList.index(self.app.displayMaterialTheme) + except ValueError: + self.displayMaterialThemeIndex = 0 + try: + self.displayMaterialVariantIndex = self.displayMaterialVariantList.index(self.app.displayMaterialVariant) + except ValueError: + self.displayMaterialVariantIndex = 0 self.modified = False @QtCore.Slot() @@ -63,5 +96,8 @@ class SettingsDialog(ui_base.CommonMixin, ui_base.PropertyContainer, QtCore.QObj self.app.updateAudioBackend() self.app.displayMultisampling = self.displayMultisampling + self.app.displayStyle = self.displayStyleList[self.displayStyleIndex] + self.app.displayMaterialTheme = self.displayMaterialThemeList[self.displayMaterialThemeIndex] + self.app.displayMaterialVariant = self.displayMaterialVariantList[self.displayMaterialVariantIndex] self.modified = False diff --git a/noisicaa/ui/qml/SettingsDialog.qml b/noisicaa/ui/qml/SettingsDialog.qml index 55d356c8..7fa161db 100644 --- a/noisicaa/ui/qml/SettingsDialog.qml +++ b/noisicaa/ui/qml/SettingsDialog.qml @@ -94,6 +94,37 @@ Dialog { checked: d.displayMultisampling onToggled: d.displayMultisampling = checked; } + + Label { + text: "Style" + } + ComboBox { + model: d.displayStyleList + currentIndex: d.displayStyleIndex + onActivated: d.displayStyleIndex = currentIndex; + } + + Label { + visible: d.displayStyleList[d.displayStyleIndex] == "Material" + text: "Theme" + } + ComboBox { + visible: d.displayStyleList[d.displayStyleIndex] == "Material" + model: d.displayMaterialThemeList + currentIndex: d.displayMaterialThemeIndex + onActivated: d.displayMaterialThemeIndex = currentIndex; + } + + Label { + visible: d.displayStyleList[d.displayStyleIndex] == "Material" + text: "Variant" + } + ComboBox { + visible: d.displayStyleList[d.displayStyleIndex] == "Material" + model: d.displayMaterialVariantList + currentIndex: d.displayMaterialVariantIndex + onActivated: d.displayMaterialVariantIndex = currentIndex; + } } } } diff --git a/noisicaa/ui/qt_helper.cpp b/noisicaa/ui/qt_helper.cpp new file mode 100644 index 00000000..4b82e742 --- /dev/null +++ b/noisicaa/ui/qt_helper.cpp @@ -0,0 +1,41 @@ +/* + * @begin:license + * + * Copyright (c) 2015-2021, Ben Niemann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @end:license + */ + +#include +#include + +#include +#include + +namespace noisicaa::ui { + +// QtQuickControls2.QQuickStyle is not available in PySide2 +std::vector c_availableQuickStyles() { + std::vector result; + QStringList styles = QQuickStyle::availableStyles(); + for (auto i = styles.constBegin(); i < styles.constEnd(); ++i) { + result.emplace_back(i->toLocal8Bit().constData()); + } + return result; +} + +} diff --git a/noisicaa/ui/qt_helper.h b/noisicaa/ui/qt_helper.h new file mode 100644 index 00000000..355ec2e4 --- /dev/null +++ b/noisicaa/ui/qt_helper.h @@ -0,0 +1,30 @@ +/* + * @begin:license + * + * Copyright (c) 2015-2021, Ben Niemann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @end:license + */ + +#include +#include + +namespace noisicaa::ui { + +std::vector c_availableQuickStyles(); + +} diff --git a/noisicaa/ui/qt_helper.pyx b/noisicaa/ui/qt_helper.pyx new file mode 100644 index 00000000..f43db6b7 --- /dev/null +++ b/noisicaa/ui/qt_helper.pyx @@ -0,0 +1,30 @@ +# @begin:license +# +# Copyright (c) 2015-2019, Benjamin Niemann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# @end:license + +from libcpp.string cimport string +from libcpp.vector cimport vector + +cdef extern from "noisicaa/ui/qt_helper.h" namespace "noisicaa::ui" nogil: + vector[string] c_availableQuickStyles() + + +def availableQuickStyles(): + cdef vector[string] styles = c_availableQuickStyles() + return [s.decode('utf-8') for s in styles] diff --git a/noisicaa/ui/wscript b/noisicaa/ui/wscript index 6bfc24e2..4107ae3d 100644 --- a/noisicaa/ui/wscript +++ b/noisicaa/ui/wscript @@ -22,7 +22,18 @@ from waflib.Tools import qt5 def build(ctx): + ctx.shlib( + features='qt5 cxx cxxshlib', + uselib='QT5CORE QT5GUI QT5QUICK QT5QUICKCONTROLS2', + source=['qt_helper.cpp'], + # moc = [ + # 'beziercurve.h', + # ], + target = 'noisicaa-ui-qt_helper', + ) + ctx.py_module('__init__.py') + ctx.cy_module('qt_helper.pyx', use=['noisicaa-ui-qt_helper']) ctx.py_module('main.py') ctx.py_module('ui_base.py') ctx.py_module('project.py') diff --git a/wscript b/wscript index c2c2587f..e662f09b 100644 --- a/wscript +++ b/wscript @@ -82,7 +82,7 @@ def configure(ctx): raise ConfigurationError("--with-coverage requires --with-tests") ctx.load('compiler_cxx') - ctx.qt5_vars = ['Qt5Core', 'Qt5Gui', 'Qt5Widgets', 'Qt5Svg', 'Qt5Quick', 'Qt5Qml']; + ctx.qt5_vars = ['Qt5Core', 'Qt5Gui', 'Qt5Widgets', 'Qt5Svg', 'Qt5Quick', 'Qt5QuickControls2', 'Qt5Qml']; ctx.load('qt5') ctx.load('python') ctx.load('build_utils.waf.local_rpath', tooldir='.')