settings: cc shortcuts editor

This commit is contained in:
Lemmy
2026-05-06 11:56:34 -04:00
parent 0325eb1d4e
commit be2000aac1
14 changed files with 202 additions and 27 deletions
+9
View File
@@ -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",
+12
View File
@@ -369,6 +369,17 @@ namespace {
array.push_back(item);
}
table.insert_or_assign(key, std::move(array));
} else if constexpr (std::is_same_v<T, std::vector<ShortcutConfig>>) {
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<Config> ConfigService::configForOverrides(const toml::table& overr
.resumeCommand = "",
});
parsed.bars.push_back(BarConfig{});
parsed.controlCenter.shortcuts = defaultControlCenterShortcuts();
return parsed;
}
+5 -11
View File
@@ -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<std::string>()) {
sc.type = *v;
}
if (auto v = (*entryTbl)["label"].value<std::string>()) {
sc.label = *v;
}
if (auto v = (*entryTbl)["icon"].value<std::string>()) {
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.*]
+6
View File
@@ -32,6 +32,12 @@ namespace {
} // namespace
std::vector<ShortcutConfig> 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()) {
+9 -8
View File
@@ -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<ShortcutConfig> defaultControlCenterShortcuts();
using WidgetSettingValue = std::variant<bool, std::int64_t, double, std::string, std::vector<std::string>>;
using ConfigOverrideValue = std::variant<bool, std::int64_t, double, std::string, std::vector<std::string>>;
using ConfigOverrideValue =
std::variant<bool, std::int64_t, double, std::string, std::vector<std::string>, std::vector<ShortcutConfig>>;
// 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<std::string> label;
std::optional<std::string> icon;
bool operator==(const ShortcutConfig&) const = default;
};
struct ControlCenterConfig {
std::vector<ShortcutConfig> shortcuts;
bool operator==(const ControlCenterConfig&) const = default;
+2 -3
View File
@@ -334,7 +334,7 @@ std::unique_ptr<Flex> 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<Flex> 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);
}
-2
View File
@@ -7,7 +7,6 @@
#include <chrono>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@@ -28,7 +27,6 @@ struct ShortcutPad {
Button* button = nullptr;
Glyph* glyph = nullptr;
Label* label = nullptr;
std::optional<std::string> labelOverride;
};
class OverviewTab : public Tab {
@@ -20,12 +20,32 @@
#include "wayland/wayland_connection.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <format>
#include <vector>
namespace {
constexpr std::array<ShortcutRegistry::CatalogEntry, 16> 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<const ShortcutRegistry::CatalogEntry> ShortcutRegistry::catalog() { return kShortcutCatalog; }
std::unique_ptr<Shortcut> ShortcutRegistry::create(std::string_view type, const ShortcutServices& s) {
if (type == "wifi")
return std::make_unique<WifiShortcut>(s.network);
@@ -1,6 +1,7 @@
#pragma once
#include <memory>
#include <span>
#include <string>
#include <string_view>
@@ -29,5 +30,11 @@ struct ShortcutServices;
class ShortcutRegistry {
public:
struct CatalogEntry {
std::string_view type;
std::string_view labelKey;
};
[[nodiscard]] static std::span<const CatalogEntry> catalog();
static std::unique_ptr<Shortcut> create(std::string_view type, const ShortcutServices& services);
};
+88
View File
@@ -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<Flex>();
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<Flex>();
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<Flex>();
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<std::string> itemTypes;
itemTypes.reserve(shortcuts.items.size());
for (const auto& item : shortcuts.items) {
itemTypes.push_back(item.type);
}
std::vector<ListEditorOption> 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>();
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<std::ptrdiff_t>(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<Node> {
return std::visit(
[&](const auto& control) -> std::unique_ptr<Node> {
@@ -1109,6 +1193,8 @@ namespace settings {
return nullptr;
} else if constexpr (std::is_same_v<T, ListSetting>) {
return nullptr;
} else if constexpr (std::is_same_v<T, ShortcutListSetting>) {
return nullptr;
} else if constexpr (std::is_same_v<T, ButtonSetting>) {
auto button = std::make_unique<Button>();
button->setVariant(ButtonVariant::Outline);
@@ -1212,6 +1298,8 @@ namespace settings {
} else if (!isBarWidgetListPath(entry.path)) {
makeListBlock(*activeSection, entry, *list);
}
} else if (const auto* shortcuts = std::get_if<ShortcutListSetting>(&entry.control)) {
makeShortcutListBlock(*activeSection, entry, *shortcuts);
} else if (const auto* picker = std::get_if<SearchPickerSetting>(&entry.control)) {
if (ctx.openSearchPickerPath == pathKey(entry.path)) {
makeSearchPickerBlock(*activeSection, entry, *picker);
+20
View File
@@ -2,6 +2,7 @@
#include "i18n/i18n.h"
#include "render/core/color.h"
#include "shell/control_center/shortcut_registry.h"
#include "theme/builtin_palettes.h"
#include "theme/builtin_templates.h"
@@ -69,6 +70,15 @@ namespace settings {
selected);
}
std::vector<SelectOption> controlCenterShortcutOptions() {
std::vector<SelectOption> opts;
opts.reserve(ShortcutRegistry::catalog().size());
for (const auto& shortcut : ShortcutRegistry::catalog()) {
opts.push_back(SelectOption{std::string(shortcut.type), i18n::tr(shortcut.labelKey)});
}
return opts;
}
SelectSetting languageSelect(std::string_view selected) {
std::vector<SelectOption> opts;
opts.reserve(i18n::kSupportedLanguages.size() + 1);
@@ -202,6 +212,8 @@ namespace settings {
return "app-window";
if (section == "dock")
return "layout-bottombar";
if (section == "panels")
return "layout-dashboard";
if (section == "backdrop")
return "niri";
if (section == "wallpaper")
@@ -518,6 +530,14 @@ namespace settings {
tr("settings.schema.dock.pinned-apps.description"), {"dock", "pinned"},
ListSetting{.items = cfg.dock.pinned}, "favorites"));
// Panels
entries.push_back(makeEntry(
"panels", "control-center", tr("settings.schema.panels.overview-shortcuts.label"),
tr("settings.schema.panels.overview-shortcuts.description"), {"control_center", "shortcuts"},
ShortcutListSetting{
.items = cfg.controlCenter.shortcuts, .suggestedOptions = controlCenterShortcutOptions(), .maxItems = 6},
"quick settings shortcuts toggles wifi bluetooth caffeine night light dnd power media weather clipboard"));
// Desktop
entries.push_back(makeEntry("desktop", "widgets", tr("settings.schema.desktop.widgets.label"),
tr("settings.schema.desktop.widgets.description"), {"desktop_widgets", "enabled"},
+10 -3
View File
@@ -3,6 +3,7 @@
#include "config/config_service.h"
#include "ui/palette.h"
#include <cstddef>
#include <functional>
#include <optional>
#include <string>
@@ -75,6 +76,12 @@ namespace settings {
std::vector<SelectOption> suggestedOptions = {};
};
struct ShortcutListSetting {
std::vector<ShortcutConfig> items;
std::vector<SelectOption> suggestedOptions = {};
std::size_t maxItems = 0;
};
struct ColorSetting {
std::string hex; // current resolved value as #RRGGBB; empty when unset
bool unset = true;
@@ -97,9 +104,9 @@ namespace settings {
bool allowNone = false;
};
using SettingControl =
std::variant<ToggleSetting, SelectSetting, SliderSetting, TextSetting, OptionalNumberSetting, ListSetting,
ColorSetting, MultiSelectSetting, ButtonSetting, ColorRolePickerSetting, SearchPickerSetting>;
using SettingControl = std::variant<ToggleSetting, SelectSetting, SliderSetting, TextSetting, OptionalNumberSetting,
ListSetting, ShortcutListSetting, ColorSetting, MultiSelectSetting, ButtonSetting,
ColorRolePickerSetting, SearchPickerSetting>;
struct SettingEntry {
std::string section;
+10
View File
@@ -56,6 +56,11 @@ void ListEditor::setScale(float scale) {
rebuildRows();
}
void ListEditor::setMaxItems(std::size_t maxItems) {
m_maxItems = maxItems;
rebuildRows();
}
void ListEditor::setOnAddRequested(std::function<void(std::string)> callback) {
m_onAddRequested = std::move(callback);
}
@@ -135,6 +140,11 @@ void ListEditor::rebuildRows() {
addChild(std::move(itemRow));
}
if (m_maxItems > 0 && m_items.size() >= m_maxItems) {
markLayoutDirty();
return;
}
auto addRow = std::make_unique<Flex>();
addRow->setDirection(FlexDirection::Horizontal);
addRow->setAlign(FlexAlign::Center);
+2
View File
@@ -21,6 +21,7 @@ public:
void setSuggestedOptions(std::vector<ListEditorOption> options);
void setAddPlaceholder(std::string_view placeholder);
void setScale(float scale);
void setMaxItems(std::size_t maxItems);
void setOnAddRequested(std::function<void(std::string)> callback);
void setOnRemoveRequested(std::function<void(std::size_t)> callback);
void setOnMoveRequested(std::function<void(std::size_t, std::size_t)> callback);
@@ -37,6 +38,7 @@ private:
std::vector<std::string> m_items;
std::vector<ListEditorOption> m_suggestedOptions;
std::string m_addPlaceholder;
std::size_t m_maxItems = 0;
std::function<void(std::string)> m_onAddRequested;
std::function<void(std::size_t)> m_onRemoveRequested;
std::function<void(std::size_t, std::size_t)> m_onMoveRequested;