From 08bb221699af516403da3f383a085d665414f361 Mon Sep 17 00:00:00 2001 From: zach Date: Mon, 31 Aug 2026 18:43:41 +0200 Subject: [PATCH] feat: config object migration path and add new options to settings --- Greeter/Services/Colors.qml | 15 +-- Helpers/ModeScheduler.qml | 12 +- Helpers/Nightlight.qml | 14 +- Helpers/Wallpapers.qml | 6 +- Modules/Settings/Pages.qml | 22 ++-- Modules/Settings/Pages/ServicesPage.qml | 4 +- .../Settings/Pages/Wallpaper/ColorsFonts.qml | 14 +- Modules/Settings/Pages/WallpaperPage.qml | 79 ++++++++--- Plugins/ZShell/Config/CMakeLists.txt | 7 +- Plugins/ZShell/Config/migration.cpp | 123 ++++++++++++++++++ Plugins/ZShell/Config/migration.hpp | 38 ++++++ Plugins/ZShell/Services/nightlightmanager.cpp | 2 - Services/Colors.qml | 14 +- cli/src/zshell/subcommands/scheme.py | 4 +- 14 files changed, 274 insertions(+), 80 deletions(-) create mode 100644 Plugins/ZShell/Config/migration.cpp create mode 100644 Plugins/ZShell/Config/migration.hpp diff --git a/Greeter/Services/Colors.qml b/Greeter/Services/Colors.qml index ee9d678..ac4dce6 100644 --- a/Greeter/Services/Colors.qml +++ b/Greeter/Services/Colors.qml @@ -13,21 +13,17 @@ import qs.Paths Singleton { id: root - readonly property M3Palette current: M3Palette { - } + readonly property M3Palette current: M3Palette {} property bool currentLight property string flavour readonly property bool light: showPreview ? previewLight : currentLight readonly property M3Palette palette: showPreview ? preview : current - readonly property M3Palette preview: M3Palette { - } + readonly property M3Palette preview: M3Palette {} property bool previewLight property string scheme property bool showPreview - readonly property M3TPalette tPalette: M3TPalette { - } - readonly property Transparency transparency: Transparency { - } + readonly property M3TPalette tPalette: M3TPalette {} + readonly property Transparency transparency: Transparency {} readonly property alias wallLuminance: analyser.luminance function alterColor(c: color, a: real, layer: int): color { @@ -112,8 +108,7 @@ Singleton { function setMode(mode: string): void { Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]); - Config.general.color.mode = mode; - Config.save(); + Config.colors.mode = mode; } function swapRG(c: color): color { diff --git a/Helpers/ModeScheduler.qml b/Helpers/ModeScheduler.qml index a45e15a..14b6aa8 100644 --- a/Helpers/ModeScheduler.qml +++ b/Helpers/ModeScheduler.qml @@ -11,14 +11,14 @@ import qs.Services Singleton { id: root - readonly property int darkEnd: Config.general.color.scheduleDarkEnd - readonly property int darkStart: Config.general.color.scheduleDarkStart - readonly property bool enabled: Config.general.color.scheduleDark && Config.general.color.schemeGeneration + readonly property int darkEnd: Config.colors.scheduleDarkEnd + readonly property int darkStart: Config.colors.scheduleDarkStart + readonly property bool enabled: Config.colors.scheduleDark && Config.colors.schemeGen function applyDarkMode() { Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]); - Config.general.color.mode = "dark"; + Config.colors.mode = "dark"; Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]); @@ -30,13 +30,13 @@ Singleton { function applyLightMode() { Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "light"]); - Config.general.color.mode = "light"; + Config.colors.mode = "light"; Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]); Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]); - if (Config.general.color.neovimColors) + if (Config.colors.neovimColors) Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]); } diff --git a/Helpers/Nightlight.qml b/Helpers/Nightlight.qml index 540091d..29857f3 100644 --- a/Helpers/Nightlight.qml +++ b/Helpers/Nightlight.qml @@ -9,13 +9,13 @@ Singleton { id: root readonly property bool enabled: service.enabled - readonly property int end: Config.general.color.scheduleHyprsunsetEnd + readonly property int end: Config.display.nightlight.scheduleEnd property bool manualToggle: false - readonly property int start: Config.general.color.scheduleHyprsunsetStart - readonly property int temp: Config.general.color.hyprsunsetTemp + readonly property int start: Config.display.nightlight.scheduleStart + readonly property int temp: Config.display.nightlight.temp function checkStartup(): void { - if (!Config.general.color.scheduleHyprsunset) + if (!Config.display.nightlight.schedule) return; service.apply(); @@ -29,11 +29,11 @@ Singleton { NightlightManager { id: service - activeAuto: Config.general.color.scheduleHyprsunset + activeAuto: Config.display.nightlight.schedule endTime: root.end startTime: root.start temp: root.temp - fadeDuration: Config.general.color.nativeFadeDuration / 1000 - useNativeNightlight: Config.general.color.useNativeNightlight + fadeDuration: Config.display.nightlight.fadeDuration / 1000 + useNativeNightlight: Config.display.nightlight.useNative } } diff --git a/Helpers/Wallpapers.qml b/Helpers/Wallpapers.qml index ba4b64c..d02b01a 100644 --- a/Helpers/Wallpapers.qml +++ b/Helpers/Wallpapers.qml @@ -38,7 +38,7 @@ Searcher { previewPath = path; showPreview = true; - if (Config.general.color.schemeGeneration) + if (Config.colors.schemeGen) previewColorsProc.running = true; } @@ -62,8 +62,8 @@ Searcher { WallpaperPath.currentWallpaperPath = path; Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0)); Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]); - if (Config.general.color.schemeGeneration) - Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); + if (Config.colors.schemeGen) + Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.colors.mode}`]); } function stopPreview(): void { diff --git a/Modules/Settings/Pages.qml b/Modules/Settings/Pages.qml index ad148b1..4aacc6d 100644 --- a/Modules/Settings/Pages.qml +++ b/Modules/Settings/Pages.qml @@ -43,17 +43,17 @@ Item { Component.onCompleted: root.loadPage(root.sState.currentPageIdx) - CustomRect { - anchors.fill: parent - color: Qt.alpha(Colors.palette.m3shadow, 0.3) - opacity: root.sState.dimmed ? 1 : 0 - visible: opacity > 0 - z: 0 - - Behavior on opacity { - Anim {} - } - } + // CustomRect { + // anchors.fill: parent + // color: Qt.alpha(Colors.palette.m3shadow, 0.3) + // opacity: root.sState.dimmed ? 1 : 0 + // visible: opacity > 0 + // z: 0 + // + // Behavior on opacity { + // Anim {} + // } + // } } Connections { diff --git a/Modules/Settings/Pages/ServicesPage.qml b/Modules/Settings/Pages/ServicesPage.qml index 11b655e..fe827bf 100644 --- a/Modules/Settings/Pages/ServicesPage.qml +++ b/Modules/Settings/Pages/ServicesPage.qml @@ -202,12 +202,12 @@ PageBase { } ToggleRow { - checked: Config.general.color.smart + checked: Config.colors.smart settingAnchor: "services-smart-color-scheme" subtext: qsTr("Derive theme mode from the wallpaper") text: qsTr("Smart color scheme") - onToggled: Config.general.color.smart = checked + onToggled: Config.colors.smart = checked } SelectRow { diff --git a/Modules/Settings/Pages/Wallpaper/ColorsFonts.qml b/Modules/Settings/Pages/Wallpaper/ColorsFonts.qml index 44a490e..be09d14 100644 --- a/Modules/Settings/Pages/Wallpaper/ColorsFonts.qml +++ b/Modules/Settings/Pages/Wallpaper/ColorsFonts.qml @@ -73,15 +73,15 @@ PageBase { ToggleRow { text: qsTr("Dynamic colors") first: true - subtext: qsTr("Automatic color scheme based on wallpaper is %1").arg(Config.general.color.schemeGeneration ? "enabled" : "disabled") - checked: Config.general.color.schemeGeneration + subtext: qsTr("Automatic color scheme based on wallpaper is %1").arg(Config.colors.schemeGen ? "enabled" : "disabled") + checked: Config.colors.schemeGen settingAnchor: "colors-fonts-dynamic-colors" onToggled: { - Config.general.color.schemeGeneration = checked; + Config.colors.schemeGen = checked; if (checked) - Colors.setMode(Config.general.color.mode); + Colors.setMode(Config.colors.mode); } } @@ -89,7 +89,7 @@ PageBase { settingAnchor: "colors-fonts-preset-scheme" model: root.schemes acceptLabel: qsTr("Confirm") - enabled: !Config.general.color.schemeGeneration + enabled: !Config.colors.schemeGen header: qsTr("Select scheme") rowButton.activeItem: root.schemes.find(s => { var regex = new RegExp(Config.colors.presets.name, "i"); @@ -157,7 +157,7 @@ PageBase { } // qmlformat on acceptLabel: qsTr("Confirm") - enabled: !Config.general.color.schemeGeneration && model.length > 1 + enabled: !Config.colors.schemeGen && model.length > 1 header: qsTr("Select variant") rowButton.activeItem: { const v = Config.colors.presets.variant ?? ""; @@ -210,7 +210,7 @@ PageBase { } // qmlformat on acceptLabel: qsTr("Confirm") - enabled: !Config.general.color.schemeGeneration && model.length > 1 + enabled: !Config.colors.schemeGen && model.length > 1 header: qsTr("Select accent") rowButton.activeItem: { const a = Config.colors.presets.accent ?? ""; diff --git a/Modules/Settings/Pages/WallpaperPage.qml b/Modules/Settings/Pages/WallpaperPage.qml index ee78ddc..570019f 100644 --- a/Modules/Settings/Pages/WallpaperPage.qml +++ b/Modules/Settings/Pages/WallpaperPage.qml @@ -96,20 +96,21 @@ PageBase { TimeDialogSelect { readonly property string endTime: { var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(Config.general.color.scheduleDarkEnd); + d.setMinutes(Config.colors.scheduleDarkEnd); return Qt.formatTime(d, "hh:mm AP"); } readonly property string startTime: { var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(Config.general.color.scheduleDarkStart); + d.setMinutes(Config.colors.scheduleDarkStart); return Qt.formatTime(d, "hh:mm AP"); } - rowButton.checked: Config.general.color.scheduleDark - rowButton.onToggled: Config.general.color.scheduleDark = rowButton.checked - start: Config.general.color.scheduleDarkStart - end: Config.general.color.scheduleDarkEnd + rowButton.checked: Config.colors.scheduleDark + rowButton.onToggled: Config.colors.scheduleDark = rowButton.checked + start: Config.colors.scheduleDarkStart + end: Config.colors.scheduleDarkEnd first: true + last: true settingAnchor: "style-schedule-dark-mode" acceptLabel: qsTr("Apply") rootParent: root.flickable @@ -122,8 +123,8 @@ PageBase { } onAccepted: { - Config.general.color.scheduleDarkStart = startCurrent; - Config.general.color.scheduleDarkEnd = endCurrent; + Config.colors.scheduleDarkStart = startCurrent; + Config.colors.scheduleDarkEnd = endCurrent; ModeScheduler.checkStartup(); } } @@ -131,27 +132,26 @@ PageBase { TimeDialogSelect { readonly property string endTime: { var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(Config.general.color.scheduleHyprsunsetEnd); + d.setMinutes(Config.display.nightlight.scheduleEnd); return Qt.formatTime(d, "hh:mm AP"); } readonly property string startTime: { var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(Config.general.color.scheduleHyprsunsetStart); + d.setMinutes(Config.display.nightlight.scheduleStart); return Qt.formatTime(d, "hh:mm AP"); } - Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing - rowButton.checked: Config.general.color.scheduleHyprsunset - rowButton.onToggled: Config.general.color.scheduleHyprsunset = rowButton.checked - start: Config.general.color.scheduleHyprsunsetStart - end: Config.general.color.scheduleHyprsunsetEnd - last: true - settingAnchor: "style-schedule-hyprsunset" + rowButton.checked: Config.display.nightlight.schedule + rowButton.onToggled: Config.display.nightlight.schedule = rowButton.checked + start: Config.display.nightlight.scheduleStart + end: Config.display.nightlight.scheduleEnd + first: true + settingAnchor: "style-schedule-nightlight" acceptLabel: qsTr("Apply") rootParent: root.flickable header: qsTr("Select time") - subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) - label: qsTr("Schedule hyprsunset") + subtext: qsTr("Nightlight will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) + label: qsTr("Schedule nightlight") onOpenChanged: { root.sState.dimmed = open; @@ -163,5 +163,46 @@ PageBase { Nightlight.checkStartup(); } } + + ToggleRow { + Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing + + checked: Config.display.nightlight.useNative + settingAnchor: "display-nightlight-useNative" + text: qsTr("Use native") + subtext: qsTr("Use native shader nightlight for more features") + + onToggled: Config.display.nightlight.useNative = checked + } + + SpinRow { + Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing + + from: 2000 + settingAnchor: "display-nightlight-temp" + stepSize: 100 + subtext: qsTr("How warm/orange the screen will be. Lower is warmer (Kelvin)") + text: qsTr("Temperature") + to: 6500 + value: Config.display.nightlight.temp + + onMoved: v => Config.display.nightlight.temp = v + } + + SpinRow { + Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing + + from: 0 + settingAnchor: "display-nightlight-fade-duration" + enabled: Config.display.nightlight.useNative + last: true + stepSize: 1 + subtext: qsTr("How long it takes for the gradual nightlight to finish (s)") + text: qsTr("Fade duration") + to: 60 * 5 + value: Config.display.nightlight.fadeDuration / 1000 + + onMoved: v => Config.display.nightlight.fadeDuration = v * 1000 + } } } diff --git a/Plugins/ZShell/Config/CMakeLists.txt b/Plugins/ZShell/Config/CMakeLists.txt index cb55a84..350ab9a 100644 --- a/Plugins/ZShell/Config/CMakeLists.txt +++ b/Plugins/ZShell/Config/CMakeLists.txt @@ -4,8 +4,10 @@ qml_module(ZShell-config DEPENDENCIES ZShell.Settings SOURCES - common.hpp common.cpp - rootnodes.hpp rootnodes.cpp + confignode.hpp confignode.cpp + configobject.hpp configobject.cpp + configlist.hpp configlist.cpp + config.hpp config.cpp migration.hpp migration.cpp anim.hpp anim.cpp fontbuilder.hpp fontbuilder.cpp @@ -17,6 +19,7 @@ qml_module(ZShell-config clipboard.hpp colors.hpp dashboard.hpp + display.hpp dock.hpp enums.hpp general.hpp diff --git a/Plugins/ZShell/Config/migration.cpp b/Plugins/ZShell/Config/migration.cpp new file mode 100644 index 0000000..da78206 --- /dev/null +++ b/Plugins/ZShell/Config/migration.cpp @@ -0,0 +1,123 @@ +#include "migration.hpp" + +#include + +namespace ZShell::config { + +namespace { + +// A stored value is "missing" if the path does not resolve to an entry. +// Note: default-constructed QJsonValue is Null, not Undefined. +bool isMissing(const QJsonValue& v) { + return v.isUndefined() || v.isNull(); +} + +QJsonValue valueAt(const QJsonObject& obj, const QStringList& path) { + QJsonObject cur = obj; + + for (int i = 0; i < path.size() - 1; ++i) { + if (!cur.value(path.at(i)).isObject()) return QJsonValue::Undefined; + cur = cur.value(path.at(i)).toObject(); + } + + const QString key = path.last(); + if (!cur.contains(key)) return QJsonValue::Undefined; + return cur.value(key); +} + +QJsonObject placeAt(QJsonObject obj, QStringList path, const QJsonValue& value) { + const QString head = path.takeFirst(); + + if (path.isEmpty()) { + obj[head] = value; + return obj; + } + + QJsonObject child = obj.value(head).isObject() ? obj.value(head).toObject() + : QJsonObject(); + obj[head] = placeAt(child, path, value); + return obj; +} + +// Removes `path` from `obj`. Returns true if a value was removed. +// Parent objects left empty are removed as well. +bool removeAt(QJsonObject& obj, const QStringList& path) { + const QString head = path.first(); + + if (path.size() == 1) { + if (!obj.contains(head)) return false; + obj.remove(head); + return true; + } + + if (!obj.value(head).isObject()) return false; + + QJsonObject child = obj.value(head).toObject(); + const bool removed = removeAt(child, path.mid(1)); + + if (child.isEmpty()) + obj.remove(head); + else + obj[head] = child; + + return removed; +} + +} // namespace + +const QList& ConfigMigrations::rules() { + static const QList s_rules = { + migrate( + "general.color.scheduleHyprsunset", "display.nightlight.schedule"), + migrate( + "general.color.scheduleHyprsunsetStart", + "display.nightlight.scheduleStart"), + migrate( + "general.color.scheduleHyprsunsetEnd", + "display.nightlight.scheduleEnd"), + migrate("general.color.scheduleDark", "colors.scheduleDark"), + migrate("general.color.scheduleDarkStart", "colors.scheduleDarkStart"), + migrate("general.color.scheduleDarkEnd", "colors.scheduleDarkEnd"), + migrate("general.color.schemeGeneration", "colors.schemeGen"), + migrate( + "general.color.useNativeNightlight", + "display.nightlight.useNative"), + migrate( + "general.color.nativeFadeDuration", + "display.nightlight.fadeDuration"), + migrate("general.color.hyprsunsetTemp", "display.nightlight.temp"), + migrate("general.color.neovimColors", "colors.neovimColors"), + migrate("general.color.mode", "colors.mode"), + migrate("general.color.smart", "colors.smart"), + }; + return s_rules; +} + +QJsonObject ConfigMigrations::apply(const QJsonObject& json) { + QJsonObject obj = json; + + for (const auto& rule : rules()) { + const QStringList from = + rule.from.split(QLatin1Char('.'), Qt::SkipEmptyParts); + const QStringList to = + rule.to.split(QLatin1Char('.'), Qt::SkipEmptyParts); + + if (from.isEmpty() || to.isEmpty()) continue; + + const QJsonValue oldValue = valueAt(obj, from); + if (isMissing(oldValue)) continue; + + const bool targetExists = !isMissing(valueAt(obj, to)); + if (!targetExists) obj = placeAt(obj, to, oldValue); + + if (removeAt(obj, from)) { + qInfo() << "Config migration:" << rule.from << "->" << rule.to + << (targetExists ? "(target already set, old value dropped)" + : "(value moved)"); + } + } + + return obj; +} + +} // namespace ZShell::config diff --git a/Plugins/ZShell/Config/migration.hpp b/Plugins/ZShell/Config/migration.hpp new file mode 100644 index 0000000..1c03505 --- /dev/null +++ b/Plugins/ZShell/Config/migration.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +namespace ZShell::config { + +// A single migration rule: move the value stored at the dotted `from` path +// to the dotted `to` path, then delete the old key (and any parent objects +// left empty). +// +// Rules are applied to the raw JSON file on every load, before the schema +// loads it. They are idempotent: once the old key is gone the rule never +// fires again, so no version stamping is needed. Old rules can stay in the +// table forever. +// +// WARNING: the target path must exist in the schema (i.e. a ConfigObject +// property somewhere in the tree). If it does not, the migrated value is +// loaded as an unknown key and silently dropped on the next save. +struct ConfigMigrationRule { + QString from; + QString to; +}; + +// Helper for writing rules: migrate("general.color.foo", "display.bar.foo") +inline ConfigMigrationRule migrate(const QString& from, const QString& to) { + return {from, to}; +} + +class ConfigMigrations { + public: + [[nodiscard]] static const QList& rules(); + + // Applies all rules to `json`, returning the migrated object. + [[nodiscard]] static QJsonObject apply(const QJsonObject& json); +}; + +} // namespace ZShell::config diff --git a/Plugins/ZShell/Services/nightlightmanager.cpp b/Plugins/ZShell/Services/nightlightmanager.cpp index 78658d4..ea50b1d 100644 --- a/Plugins/ZShell/Services/nightlightmanager.cpp +++ b/Plugins/ZShell/Services/nightlightmanager.cpp @@ -10,7 +10,6 @@ namespace ZShell::services { namespace { constexpr const char* NL_EVENT = "zshell-nightlight"; -constexpr int NEUTRAL_KELVIN = 6500; constexpr int RECONNECT_MS = 2000; } // namespace @@ -404,7 +403,6 @@ void NightlightManager::handleEventLine(const QByteArray& line) { if (line.left(sep) != NL_EVENT) return; - // data = enabled,animating,last,current const auto fields = line.mid(sep + 2).split(','); if (fields.size() < 4) return; diff --git a/Services/Colors.qml b/Services/Colors.qml index 863c03f..970281d 100644 --- a/Services/Colors.qml +++ b/Services/Colors.qml @@ -13,21 +13,17 @@ import qs.Paths Singleton { id: root - readonly property M3Palette current: M3Palette { - } + readonly property M3Palette current: M3Palette {} property bool currentLight property string flavour readonly property bool light: showPreview ? previewLight : currentLight readonly property M3Palette palette: showPreview ? preview : current - readonly property M3Palette preview: M3Palette { - } + readonly property M3Palette preview: M3Palette {} property bool previewLight property string scheme property bool showPreview - readonly property M3TPalette tPalette: M3TPalette { - } - readonly property Transparency transparency: Transparency { - } + readonly property M3TPalette tPalette: M3TPalette {} + readonly property Transparency transparency: Transparency {} readonly property alias wallLuminance: analyser.luminance function alterColor(c: color, a: real, layer: int): color { @@ -112,7 +108,7 @@ Singleton { function setMode(mode: string): void { Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]); - Config.general.color.mode = mode; + Config.colors.mode = mode; } function swapRG(c: color): color { diff --git a/cli/src/zshell/subcommands/scheme.py b/cli/src/zshell/subcommands/scheme.py index 2a65612..f0c8e72 100644 --- a/cli/src/zshell/subcommands/scheme.py +++ b/cli/src/zshell/subcommands/scheme.py @@ -625,8 +625,8 @@ def generate( scheme_type = config["colors"].get("schemeType", "fruit-salad") scheme = scheme or scheme_type assert isinstance(scheme, str) - config_mode = config["general"]["color"]["mode"] - smart = bool(config["general"]["color"].get("smart", False)) + config_mode = config["colors"]["mode"] + smart = bool(config["colors"].get("smart", False)) scheme_class = get_scheme_class(scheme) p_variant = "default"