From be2000aac1665fba70548a0608e353cfb01765ec Mon Sep 17 00:00:00 2001 From: Lemmy Date: Wed, 6 May 2026 11:56:34 -0400 Subject: [PATCH] settings: cc shortcuts editor --- assets/translations/en.json | 9 ++ src/config/config_overrides.cpp | 12 +++ src/config/config_service.cpp | 16 ++-- src/config/config_types.cpp | 6 ++ src/config/config_types.h | 17 ++-- src/shell/control_center/overview_tab.cpp | 5 +- src/shell/control_center/overview_tab.h | 2 - .../control_center/shortcut_registry.cpp | 22 +++++ src/shell/control_center/shortcut_registry.h | 7 ++ src/shell/settings/settings_content.cpp | 88 +++++++++++++++++++ src/shell/settings/settings_registry.cpp | 20 +++++ src/shell/settings/settings_registry.h | 13 ++- src/ui/controls/list_editor.cpp | 10 +++ src/ui/controls/list_editor.h | 2 + 14 files changed, 202 insertions(+), 27 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index ecfb8638f..d59b172d4 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -704,6 +704,7 @@ "appearance": "Appearance", "desktop": "Desktop", "dock": "Dock", + "panels": "Panels", "notifications": "Notifications", "backdrop": "Backdrop", "services": "Services", @@ -720,6 +721,7 @@ "built-in": "Built-in", "clipboard": "Clipboard", "community": "Community", + "control-center": "Control Center", "directories": "Directories", "effects": "Effects", "focus-styling": "Focus Styling", @@ -733,6 +735,7 @@ "network": "Network", "night-light": "Night Light", "osd": "OSD", + "overview": "Overview", "pinned-apps": "Pinned Apps", "profile": "Profile", "screen-corners": "Screen Corners", @@ -1319,6 +1322,12 @@ "description": "Reserve screen space for the dock" } }, + "panels": { + "overview-shortcuts": { + "label": "Overview Shortcuts", + "description": "Choose up to six shortcut buttons for the overview tab" + } + }, "backdrop": { "blur-intensity": { "label": "Blur Intensity", diff --git a/src/config/config_overrides.cpp b/src/config/config_overrides.cpp index 411e9d50f..8cdaf3985 100644 --- a/src/config/config_overrides.cpp +++ b/src/config/config_overrides.cpp @@ -369,6 +369,17 @@ namespace { array.push_back(item); } table.insert_or_assign(key, std::move(array)); + } else if constexpr (std::is_same_v>) { + toml::array array; + for (const auto& item : concrete) { + if (item.type.empty()) { + continue; + } + toml::table shortcut; + shortcut.insert_or_assign("type", item.type); + array.push_back(std::move(shortcut)); + } + table.insert_or_assign(key, std::move(array)); } else { table.insert_or_assign(key, concrete); } @@ -596,6 +607,7 @@ std::optional ConfigService::configForOverrides(const toml::table& overr .resumeCommand = "", }); parsed.bars.push_back(BarConfig{}); + parsed.controlCenter.shortcuts = defaultControlCenterShortcuts(); return parsed; } diff --git a/src/config/config_service.cpp b/src/config/config_service.cpp index 32e2216cb..20928b2da 100644 --- a/src/config/config_service.cpp +++ b/src/config/config_service.cpp @@ -835,6 +835,7 @@ void ConfigService::loadAll() { .resumeCommand = "", }); m_config.bars.push_back(BarConfig{}); + m_config.controlCenter.shortcuts = defaultControlCenterShortcuts(); return; } @@ -1644,8 +1645,10 @@ void ConfigService::parseTableInto(const toml::table& tbl, Config& config, bool } // Parse [[control_center.shortcuts]] + bool controlCenterShortcutsConfigured = false; if (auto* ccTbl = tbl["control_center"].as_table()) { if (auto* shortcutsArr = (*ccTbl)["shortcuts"].as_array()) { + controlCenterShortcutsConfigured = true; config.controlCenter.shortcuts.clear(); for (const auto& entry : *shortcutsArr) { auto* entryTbl = entry.as_table(); @@ -1656,23 +1659,14 @@ void ConfigService::parseTableInto(const toml::table& tbl, Config& config, bool if (auto v = (*entryTbl)["type"].value()) { sc.type = *v; } - if (auto v = (*entryTbl)["label"].value()) { - sc.label = *v; - } - if (auto v = (*entryTbl)["icon"].value()) { - sc.icon = *v; - } if (!sc.type.empty()) { config.controlCenter.shortcuts.push_back(std::move(sc)); } } } } - if (config.controlCenter.shortcuts.empty()) { - config.controlCenter.shortcuts = { - {"wifi", {}, {}}, {"bluetooth", {}, {}}, {"caffeine", {}, {}}, - {"nightlight", {}, {}}, {"notification", {}, {}}, {"power_profile", {}, {}}, - }; + if (!controlCenterShortcutsConfigured && config.controlCenter.shortcuts.empty()) { + config.controlCenter.shortcuts = defaultControlCenterShortcuts(); } // Parse [idle.behavior.*] diff --git a/src/config/config_types.cpp b/src/config/config_types.cpp index aed423224..9ff28cd8c 100644 --- a/src/config/config_types.cpp +++ b/src/config/config_types.cpp @@ -32,6 +32,12 @@ namespace { } // namespace +std::vector defaultControlCenterShortcuts() { + return { + {"wifi"}, {"bluetooth"}, {"caffeine"}, {"nightlight"}, {"notification"}, {"power_profile"}, + }; +} + std::string WidgetConfig::getString(const std::string& key, const std::string& fallback) const { auto it = settings.find(key); if (it == settings.end()) { diff --git a/src/config/config_types.h b/src/config/config_types.h index e355d6239..8ea36e945 100644 --- a/src/config/config_types.h +++ b/src/config/config_types.h @@ -98,8 +98,16 @@ struct BarConfig { bool operator==(const BarConfig&) const = default; }; +struct ShortcutConfig { + std::string type; + bool operator==(const ShortcutConfig&) const = default; +}; + +[[nodiscard]] std::vector defaultControlCenterShortcuts(); + using WidgetSettingValue = std::variant>; -using ConfigOverrideValue = std::variant>; +using ConfigOverrideValue = + std::variant, std::vector>; // Optional rounded “capsule” behind a bar widget (see `[widget.*] capsule_*` in CONFIG.md). // Corner shape (pill), border width, and edge softness are fixed in the shell code; padding is configurable. @@ -574,13 +582,6 @@ struct ThemeConfig { TemplatesConfig templates; }; -struct ShortcutConfig { - std::string type; - std::optional label; - std::optional icon; - bool operator==(const ShortcutConfig&) const = default; -}; - struct ControlCenterConfig { std::vector shortcuts; bool operator==(const ControlCenterConfig&) const = default; diff --git a/src/shell/control_center/overview_tab.cpp b/src/shell/control_center/overview_tab.cpp index 11ae96d33..1bd928730 100644 --- a/src/shell/control_center/overview_tab.cpp +++ b/src/shell/control_center/overview_tab.cpp @@ -334,7 +334,7 @@ std::unique_ptr OverviewTab::create() { continue; } - const std::string label = sc.label.has_value() ? *sc.label : shortcut->displayLabel(); + const std::string label = shortcut->displayLabel(); const bool enabled = shortcut->enabled(); const bool isActive = shortcut->isToggle() && shortcut->active(); @@ -378,7 +378,6 @@ std::unique_ptr OverviewTab::create() { pad.button = btnPtr; pad.glyph = btnPtr->glyph(); pad.label = btnPtr->label(); - pad.labelOverride = sc.label; m_shortcutPads.push_back(std::move(pad)); grid->addChild(std::move(btn)); } @@ -944,7 +943,7 @@ void OverviewTab::syncShortcuts() { pad.glyph->setGlyph(sc.displayIcon()); } if (pad.button != nullptr && pad.label != nullptr) { - const std::string label = pad.labelOverride.has_value() ? *pad.labelOverride : sc.displayLabel(); + const std::string label = sc.displayLabel(); if (pad.label->text() != label) { pad.button->setText(label); } diff --git a/src/shell/control_center/overview_tab.h b/src/shell/control_center/overview_tab.h index 1b54f1977..de7a34e1f 100644 --- a/src/shell/control_center/overview_tab.h +++ b/src/shell/control_center/overview_tab.h @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -28,7 +27,6 @@ struct ShortcutPad { Button* button = nullptr; Glyph* glyph = nullptr; Label* label = nullptr; - std::optional labelOverride; }; class OverviewTab : public Tab { diff --git a/src/shell/control_center/shortcut_registry.cpp b/src/shell/control_center/shortcut_registry.cpp index 0172ccf49..588e35a3d 100644 --- a/src/shell/control_center/shortcut_registry.cpp +++ b/src/shell/control_center/shortcut_registry.cpp @@ -20,12 +20,32 @@ #include "wayland/wayland_connection.h" #include +#include #include #include #include namespace { + constexpr std::array kShortcutCatalog{{ + {"wifi", "control-center.shortcuts.wifi"}, + {"bluetooth", "control-center.shortcuts.bluetooth"}, + {"nightlight", "control-center.shortcuts.nightlight"}, + {"notification", "control-center.shortcuts.notification"}, + {"dark_mode", "control-center.shortcuts.dark-mode.dark"}, + {"caffeine", "control-center.shortcuts.caffeine"}, + {"audio", "control-center.shortcuts.audio"}, + {"mic_mute", "control-center.shortcuts.mic-mute"}, + {"power_profile", "control-center.shortcuts.power-profile"}, + {"media", "control-center.shortcuts.media"}, + {"weather", "control-center.shortcuts.weather"}, + {"sysmon", "control-center.shortcuts.sysmon"}, + {"keyboard_layout", "control-center.shortcuts.keyboard-layout"}, + {"wallpaper", "control-center.shortcuts.wallpaper"}, + {"session", "control-center.shortcuts.session"}, + {"clipboard", "control-center.shortcuts.clipboard"}, + }}; + void openTab(std::string_view tab) { PanelManager::instance().togglePanel("control-center", PanelOpenRequest{.context = tab}); } @@ -499,6 +519,8 @@ namespace { } // namespace +std::span ShortcutRegistry::catalog() { return kShortcutCatalog; } + std::unique_ptr ShortcutRegistry::create(std::string_view type, const ShortcutServices& s) { if (type == "wifi") return std::make_unique(s.network); diff --git a/src/shell/control_center/shortcut_registry.h b/src/shell/control_center/shortcut_registry.h index 7035aa1f3..45a0f2404 100644 --- a/src/shell/control_center/shortcut_registry.h +++ b/src/shell/control_center/shortcut_registry.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -29,5 +30,11 @@ struct ShortcutServices; class ShortcutRegistry { public: + struct CatalogEntry { + std::string_view type; + std::string_view labelKey; + }; + + [[nodiscard]] static std::span catalog(); static std::unique_ptr create(std::string_view type, const ShortcutServices& services); }; diff --git a/src/shell/settings/settings_content.cpp b/src/shell/settings/settings_content.cpp index 17a4b9e99..9e4836935 100644 --- a/src/shell/settings/settings_content.cpp +++ b/src/shell/settings/settings_content.cpp @@ -1086,6 +1086,90 @@ namespace settings { section.addChild(std::move(block)); }; + const auto makeShortcutListBlock = [&](Flex& section, const SettingEntry& entry, + const ShortcutListSetting& shortcuts) { + const bool overridden = (ctx.configService != nullptr && ctx.configService->hasEffectiveOverride(entry.path)); + + auto block = std::make_unique(); + block->setDirection(FlexDirection::Vertical); + block->setAlign(FlexAlign::Stretch); + block->setGap(Style::spaceXs * scale); + block->setPadding(2.0f * scale, 0.0f); + + auto titleRow = std::make_unique(); + titleRow->setDirection(FlexDirection::Horizontal); + titleRow->setAlign(FlexAlign::Center); + titleRow->setGap(Style::spaceSm * scale); + titleRow->addChild( + makeLabel(entry.title, Style::fontSizeBody * scale, colorSpecFromRole(ColorRole::OnSurface), true)); + if (overridden) { + auto badge = std::make_unique(); + badge->setAlign(FlexAlign::Center); + badge->setPadding(1.0f * scale, Style::spaceXs * scale); + badge->setRadius(Style::radiusSm * scale); + badge->setFill(colorSpecFromRole(ColorRole::Primary, 0.15f)); + badge->addChild(makeLabel(i18n::tr("settings.badges.override"), Style::fontSizeCaption * scale, + colorSpecFromRole(ColorRole::Primary), true)); + titleRow->addChild(std::move(badge)); + } + if (overridden) { + titleRow->addChild(makeResetButton(entry.path)); + } + block->addChild(std::move(titleRow)); + + if (!entry.subtitle.empty()) { + block->addChild(makeLabel(entry.subtitle, Style::fontSizeCaption * scale, + colorSpecFromRole(ColorRole::OnSurfaceVariant), false)); + } + + std::vector itemTypes; + itemTypes.reserve(shortcuts.items.size()); + for (const auto& item : shortcuts.items) { + itemTypes.push_back(item.type); + } + + std::vector suggestedOptions; + suggestedOptions.reserve(shortcuts.suggestedOptions.size()); + for (const auto& opt : shortcuts.suggestedOptions) { + suggestedOptions.push_back(ListEditorOption{.value = opt.value, .label = opt.label}); + } + + auto listEditor = std::make_unique(); + listEditor->setScale(scale); + listEditor->setMaxItems(shortcuts.maxItems); + listEditor->setAddPlaceholder(i18n::tr("settings.controls.list.add-entry-placeholder")); + listEditor->setSuggestedOptions(std::move(suggestedOptions)); + listEditor->setItems(std::move(itemTypes)); + listEditor->setOnAddRequested( + [setOverride = ctx.setOverride, items = shortcuts.items, path = entry.path](std::string value) mutable { + if (value.empty() || std::any_of(items.begin(), items.end(), + [&value](const ShortcutConfig& item) { return item.type == value; })) { + return; + } + items.push_back(ShortcutConfig{std::move(value)}); + setOverride(path, items); + }); + listEditor->setOnRemoveRequested( + [setOverride = ctx.setOverride, items = shortcuts.items, path = entry.path](std::size_t index) mutable { + if (index >= items.size()) { + return; + } + items.erase(items.begin() + static_cast(index)); + setOverride(path, items); + }); + listEditor->setOnMoveRequested([setOverride = ctx.setOverride, items = shortcuts.items, + path = entry.path](std::size_t from, std::size_t to) mutable { + if (from >= items.size() || to >= items.size() || from == to) { + return; + } + std::swap(items[from], items[to]); + setOverride(path, items); + }); + block->addChild(std::move(listEditor)); + + section.addChild(std::move(block)); + }; + const auto makeControl = [&](const SettingEntry& entry) -> std::unique_ptr { return std::visit( [&](const auto& control) -> std::unique_ptr { @@ -1109,6 +1193,8 @@ namespace settings { return nullptr; } else if constexpr (std::is_same_v) { return nullptr; + } else if constexpr (std::is_same_v) { + return nullptr; } else if constexpr (std::is_same_v) { auto button = std::make_unique