From dc1e81aedefe46c4dbdf4d2e6444b4795ef6221f Mon Sep 17 00:00:00 2001 From: Jin Liu Date: Mon, 27 Jul 2026 17:25:46 +0800 Subject: [PATCH 1/3] feat(widgets/sysmon): add an option to hide the icon This allows a "minimal" style where only a gauge is displayed. --- src/shell/bar/widgets/sysmon_widget.cpp | 70 +++++++++++-------- src/shell/bar/widgets/sysmon_widget.h | 2 + .../bar/widgets/sysmon_widget_definition.cpp | 21 +++++- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/shell/bar/widgets/sysmon_widget.cpp b/src/shell/bar/widgets/sysmon_widget.cpp index 514027fdac..70e111ae63 100644 --- a/src/shell/bar/widgets/sysmon_widget.cpp +++ b/src/shell/bar/widgets/sysmon_widget.cpp @@ -177,7 +177,7 @@ namespace { SysmonWidget::SysmonWidget(SystemMonitorService* monitor, ConfigService& configService, Options options) : m_monitor(monitor), m_stat(options.stat), m_displayMode(options.displayMode), - m_highlightColor(options.highlightColor), m_configService(configService), + m_highlightColor(options.highlightColor), m_configService(configService), m_showIcon(options.showIcon), m_showLabel(options.showLabel && options.displayMode != SysmonDisplayMode::None), m_labelMinWidth(static_cast(options.labelMinWidth)), m_diskPath(FileUtils::expandUserPath(options.diskPath).string()), @@ -230,15 +230,17 @@ SysmonWidget::~SysmonWidget() { void SysmonWidget::create() { auto container = ui::inputArea({}); std::unique_ptr glyphNode; - if (m_customImage.enabled()) { - glyphNode = ui::image({.out = &m_image, .fit = ImageFit::Contain}); - } else { - glyphNode = ui::glyph({ - .out = &m_glyph, - .glyph = m_glyphOverride.empty() ? glyphName(m_stat) : m_glyphOverride, - .glyphSize = Style::baseGlyphSize * m_contentScale, - .color = widgetIconColorOr(colorSpecFromRole(ColorRole::OnSurface)), - }); + if (m_showIcon) { + if (m_customImage.enabled()) { + glyphNode = ui::image({.out = &m_image, .fit = ImageFit::Contain}); + } else { + glyphNode = ui::glyph({ + .out = &m_glyph, + .glyph = m_glyphOverride.empty() ? glyphName(m_stat) : m_glyphOverride, + .glyphSize = Style::baseGlyphSize * m_contentScale, + .color = widgetIconColorOr(colorSpecFromRole(ColorRole::OnSurface)), + }); + } } std::unique_ptr graphOrGaugeNode; @@ -276,7 +278,9 @@ void SysmonWidget::create() { m_containerRow = static_cast(container->addChild(ui::row({.gap = Style::spaceXs * m_contentScale}))); if (m_glyphPosition == SysmonGlyphPosition::Before) { - m_containerRow->addChild(std::move(glyphNode)); + if (glyphNode) { + m_containerRow->addChild(std::move(glyphNode)); + } if (graphOrGaugeNode != nullptr) { m_containerRow->addChild(std::move(graphOrGaugeNode)); } @@ -290,7 +294,9 @@ void SysmonWidget::create() { if (graphOrGaugeNode != nullptr) { m_containerRow->addChild(std::move(graphOrGaugeNode)); } - m_containerRow->addChild(std::move(glyphNode)); + if (glyphNode) { + m_containerRow->addChild(std::move(glyphNode)); + } } setRoot(std::move(container)); @@ -497,7 +503,7 @@ void SysmonWidget::syncGaugeProgress(double normalized) { void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float containerHeight) { auto* rootNode = root(); - if ((m_glyph == nullptr && m_image == nullptr) || rootNode == nullptr) { + if ((m_showIcon && m_glyph == nullptr && m_image == nullptr) || rootNode == nullptr) { return; } const bool isVerticalBar = containerHeight > containerWidth; @@ -508,9 +514,13 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont syncVisualPalette(); syncIcon(renderer); + const float iconW = iconWidth(); const float iconH = iconHeight(); const float gap = Style::spaceXs * m_contentScale; + const float iconWPlusGap = m_showIcon ? iconW + gap : 0.0f; + const float iconHPlusGap = m_showIcon ? iconH + gap : 0.0f; + const float baseSize = Style::fontSizeBody * m_contentScale; const bool verticalBar = m_isVerticalBar; if (m_label != nullptr) { @@ -524,8 +534,7 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont const float labelH = m_label != nullptr ? m_label->height() : 0.0f; if (m_displayMode == SysmonDisplayMode::Gauge && m_gauge != nullptr) { - const float baseSize = Style::fontSizeBody * m_contentScale; - const float gaugeStem = std::round(baseSize * 0.85f); + const float gaugeStem = m_showIcon ? std::round(baseSize * 0.85f) : std::round(baseSize * 1.2f); const float gaugeThickness = std::max(3.0f, roundf(baseSize * 0.3f)); if (verticalBar) { @@ -537,9 +546,9 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont if (m_label != nullptr) contentW = std::max(contentW, labelW); setIconPosition((contentW - iconW) * 0.5f, 0.0f); - m_gauge->setPosition(std::round((contentW - trackW) * 0.5f), iconH + gap); + m_gauge->setPosition(std::round((contentW - trackW) * 0.5f), iconHPlusGap); m_gauge->setSize(trackW, trackH); - float totalH = iconH + gap + trackH; + float totalH = iconHPlusGap + trackH; if (m_label != nullptr) { m_label->setPosition((contentW - labelW) * 0.5f, totalH + gap); totalH += gap + labelH; @@ -555,7 +564,7 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont contentH = std::max(contentH, labelH); const float gaugeY = std::round((contentH - gaugeH) * 0.5f); setIconPosition(0.0f, (contentH - iconH) * 0.5f); - m_gauge->setPosition(iconW + gap, gaugeY); + m_gauge->setPosition(iconWPlusGap, gaugeY); m_gauge->setSize(gaugeW, gaugeH); float totalW = m_gauge->x() + gaugeW; if (m_label != nullptr) { @@ -572,38 +581,39 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont if (m_displayMode == SysmonDisplayMode::Graph && m_chartBg != nullptr) { const float chartW = verticalBar ? std::min(50.0f * m_contentScale, std::max(1.0f, containerWidth)) : 50.0f * m_contentScale; + const float chartH = m_showIcon ? iconH : std::round(baseSize * 1.2f); if (verticalBar) { float contentW = std::max(iconW, chartW); if (m_label != nullptr) contentW = std::max(contentW, labelW); setIconPosition((contentW - iconW) * 0.5f, 0.0f); - const float chartY = iconH + gap; + const float chartY = iconHPlusGap; m_chartBg->setPosition(std::round((contentW - chartW) * 0.5f), chartY); - m_chartBg->setSize(chartW, iconH); + m_chartBg->setSize(chartW, chartH); if (m_graph != nullptr) { m_graph->setPosition(0.0f, 0.0f); - m_graph->setSize(chartW, iconH); + m_graph->setSize(chartW, chartH); } - float totalH = chartY + iconH; + float totalH = chartY + chartH; if (m_label != nullptr) { m_label->setPosition((contentW - labelW) * 0.5f, totalH + gap); totalH += gap + labelH; } rootNode->setSize(contentW, totalH); } else { - float contentH = iconH; + float contentH = chartH; if (m_label != nullptr) contentH = std::max(contentH, labelH); setIconPosition(0.0f, (contentH - iconH) * 0.5f); - m_chartBg->setPosition(iconW + gap, std::round((contentH - iconH) * 0.5f)); - m_chartBg->setSize(chartW, iconH); + m_chartBg->setPosition(iconWPlusGap, std::round((contentH - chartH) * 0.5f)); + m_chartBg->setSize(chartW, chartH); if (m_graph != nullptr) { m_graph->setPosition(0.0f, 0.0f); - m_graph->setSize(chartW, iconH); + m_graph->setSize(chartW, chartH); } float totalW = m_chartBg->x() + chartW; @@ -616,12 +626,12 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont } else if (m_label != nullptr && verticalBar) { const float contentW = std::max(iconW, labelW); setIconPosition((contentW - iconW) * 0.5f, 0.0f); - m_label->setPosition((contentW - labelW) * 0.5f, iconH + gap); - rootNode->setSize(contentW, iconH + gap + labelH); + m_label->setPosition((contentW - labelW) * 0.5f, iconHPlusGap); + rootNode->setSize(contentW, iconHPlusGap + labelH); } else if (m_label != nullptr) { const float contentH = std::max(iconH, labelH); setIconPosition(0.0f, (contentH - iconH) * 0.5f); - m_label->setPosition(iconW + gap, (contentH - labelH) * 0.5f); + m_label->setPosition(iconWPlusGap, (contentH - labelH) * 0.5f); rootNode->setSize(m_label->x() + labelW, contentH); } else { setIconPosition(0.0f, 0.0f); @@ -630,7 +640,7 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont } void SysmonWidget::doUpdate(Renderer& renderer) { - if (m_glyph == nullptr && m_image == nullptr) { + if (m_showIcon && m_glyph == nullptr && m_image == nullptr) { return; } diff --git a/src/shell/bar/widgets/sysmon_widget.h b/src/shell/bar/widgets/sysmon_widget.h index a1ffcb48bb..ced69bb71e 100644 --- a/src/shell/bar/widgets/sysmon_widget.h +++ b/src/shell/bar/widgets/sysmon_widget.h @@ -59,6 +59,7 @@ class SysmonWidget : public Widget { bool networkSpeedCompact = false; SysmonDisplayMode displayMode = SysmonDisplayMode::Gauge; ColorSpec highlightColor = colorSpecFromRole(ColorRole::Error); + bool showIcon = true; bool showLabel = true; int labelMinWidth = 0; bool showUnits = true; @@ -105,6 +106,7 @@ class SysmonWidget : public Widget { SysmonDisplayMode m_displayMode; ColorSpec m_highlightColor = colorSpecFromRole(ColorRole::Error); ConfigService& m_configService; + bool m_showIcon; bool m_showLabel; float m_labelMinWidth = 0.0f; std::string m_diskPath; diff --git a/src/shell/bar/widgets/sysmon_widget_definition.cpp b/src/shell/bar/widgets/sysmon_widget_definition.cpp index 4a6554f32f..0de29a7028 100644 --- a/src/shell/bar/widgets/sysmon_widget_definition.cpp +++ b/src/shell/bar/widgets/sysmon_widget_definition.cpp @@ -23,6 +23,13 @@ const noctalia::bar::WidgetDefinition definition{ .type = "sysmon", @@ -103,19 +110,31 @@ const noctalia::bar::WidgetDefinition({ + .key = "show_icon", + }), field<&Options::glyph>({ .key = "glyph", .control = settings::WidgetControlKind::Glyph, .presentation = settings::WidgetSettingPresentation{ .descriptionKey = "settings.widgets.settings.glyph.sysmon-description", + .visibleWhen = showIcon, }, }), field<&Options::customImage>({ .key = "custom_image", + .presentation = + settings::WidgetSettingPresentation{ + .visibleWhen = showIcon, + }, }), field<&Options::customImageColorize>({ .key = "custom_image_colorize", + .presentation = + settings::WidgetSettingPresentation{ + .visibleWhen = showIcon, + }, }), field<&Options::diskPath>({ .key = "path", @@ -238,7 +257,7 @@ const noctalia::bar::WidgetDefinition Date: Thu, 30 Jul 2026 19:34:09 -0400 Subject: [PATCH 2/3] refactor(sysmon): simplify presentation settings --- assets/translations/en.json | 16 +++ src/config/config_migrations.cpp | 89 ++++++++++++ src/config/config_service.cpp | 16 +++ src/shell/bar/widgets/sysmon_widget.cpp | 37 +++-- src/shell/bar/widgets/sysmon_widget.h | 14 +- .../bar/widgets/sysmon_widget_definition.cpp | 134 +++++++++--------- tests/config_migration_test.cpp | 107 ++++++++++++++ 7 files changed, 320 insertions(+), 93 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 12d5a28d7d..7aa2efdb40 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -3596,6 +3596,10 @@ "description": "Show placeholder text when no window is focused instead of hiding the widget", "label": "Show Empty Label" }, + "show-glyph": { + "description": "Show the widget glyph or custom image", + "label": "Show Glyph" + }, "show-icon": { "description": "Show this widget's icon", "label": "Show Icon" @@ -3616,6 +3620,10 @@ "description": "Show current temperature text", "label": "Show Temperature" }, + "show-value": { + "description": "Show the current value next to the visualization", + "label": "Show Value" + }, "show-vpn-label": { "description": "Replaces network label with VPN label in replace mode; adds VPN label next to VPN icon in both mode", "label": "Show VPN Label" @@ -3673,6 +3681,14 @@ "description": "Color role used for urgent workspaces; fixed hex colors are also supported", "label": "Urgent Color" }, + "value-min-width": { + "description": "Minimum value width in pixels to prevent resizing", + "label": "Value Min Width" + }, + "visualization": { + "description": "Gauge, graph, or no data visualization", + "label": "Visualization" + }, "vertical-format": { "description": "Optional format used on vertical bars", "label": "Vertical Format" diff --git a/src/config/config_migrations.cpp b/src/config/config_migrations.cpp index 9173878279..a1f40abe37 100644 --- a/src/config/config_migrations.cpp +++ b/src/config/config_migrations.cpp @@ -21,6 +21,7 @@ namespace noctalia::config { constexpr int kCustomButtonCommandsMigrationVersion = 6; constexpr int kDeadZoneActionsMigrationVersion = 7; constexpr int kLockscreenLoginBoxDeprecatedSettingsMigrationVersion = 8; + constexpr int kSysmonPresentationMigrationVersion = 9; constexpr std::int64_t kMaxBarRadius = 500; constexpr std::array kBarRadiusKeys = { "radius", "radius_top_left", "radius_top_right", "radius_bottom_left", "radius_bottom_right", @@ -416,6 +417,82 @@ namespace noctalia::config { }); } + template void migrateSysmonPresentationSettings(toml::table& root, OnChanged&& onChanged) { + auto* widgets = root["widget"].as_table(); + if (widgets == nullptr) { + return; + } + + for (auto& [widgetName, widgetNode] : *widgets) { + auto* widget = widgetNode.as_table(); + if (widget == nullptr || (*widget)["type"].value_or(std::string(widgetName.str())) != "sysmon") { + continue; + } + + bool changed = false; + if (const auto showIcon = (*widget)["show_icon"].value(); showIcon.has_value()) { + if (!widget->contains("show_glyph")) { + widget->insert_or_assign("show_glyph", *showIcon); + } + widget->erase("show_icon"); + changed = true; + } + + const auto display = (*widget)["display"].value(); + if (display.has_value()) { + if (!widget->contains("visualization")) { + const std::string visualization = *display == "text" ? "none" : *display; + widget->insert_or_assign("visualization", visualization); + } + widget->erase("display"); + changed = true; + } + + if (const auto showLabel = (*widget)["show_label"].value(); showLabel.has_value()) { + if (!widget->contains("show_value")) { + bool showValue = *showLabel; + if (display == "text") { + showValue = true; + } else if (display == "none") { + showValue = false; + } + widget->insert_or_assign("show_value", showValue); + } + widget->erase("show_label"); + changed = true; + } else if (display.has_value() && !widget->contains("show_value")) { + widget->insert_or_assign("show_value", *display != "none"); + changed = true; + } + + if (const auto showUnits = (*widget)["label_show_units"].value(); showUnits.has_value()) { + if (!widget->contains("show_units")) { + widget->insert_or_assign("show_units", *showUnits); + } + widget->erase("label_show_units"); + changed = true; + } + + if (const auto minWidth = (*widget)["label_min_width"].value(); minWidth.has_value()) { + if (!widget->contains("value_min_width")) { + widget->insert_or_assign("value_min_width", *minWidth); + } + widget->erase("label_min_width"); + changed = true; + } + + if (changed) { + onChanged("widget." + std::string(widgetName.str())); + } + } + } + + void migrateSysmonPresentationSettingsSidecar(toml::table& root, schema::Diagnostics& diag) { + migrateSysmonPresentationSettings(root, [&diag](const std::string& path) { + diag.warn(path, "migrated sysmon presentation settings to their canonical names"); + }); + } + void migrateCustomButtonCommandsSidecar(toml::table& root, schema::Diagnostics& diag) { migrateCustomButtonCommands(root, [&diag](const std::string& path, std::string_view message) { diag.warn(path, std::string(message)); @@ -517,6 +594,11 @@ namespace noctalia::config { .summary = "lockscreen: drop removed login box show_password_hint setting", .apply = migrateLockscreenLoginBoxDeprecatedSettingsSidecar, }, + { + .toVersion = kSysmonPresentationMigrationVersion, + .summary = "widget: migrate sysmon presentation settings", + .apply = migrateSysmonPresentationSettingsSidecar, + }, }; return migrations; } @@ -625,6 +707,13 @@ namespace noctalia::config { .message = "removed deprecated show_password_hint", }); }); + migrateSysmonPresentationSettings(root, [&issues](const std::string& path) { + issues.push_back({ + .migrationVersion = kSysmonPresentationMigrationVersion, + .path = path, + .message = "sysmon display settings are now visualization, show_value, and show_glyph", + }); + }); } std::string legacyConfigIssueFingerprint(const LegacyConfigIssues& issues) { diff --git a/src/config/config_service.cpp b/src/config/config_service.cpp index 44fdddff9a..1235b0aca7 100644 --- a/src/config/config_service.cpp +++ b/src/config/config_service.cpp @@ -182,10 +182,26 @@ namespace { } } + void validateSysmonWidgetSettings(std::string_view widgetName, const WidgetConfig& widget) { + if (widget.type != "sysmon") { + return; + } + + const bool showGlyph = widget.getBool("show_glyph", true); + const bool showValue = widget.getBool("show_value", true); + const std::string visualization = widget.getString("visualization", "gauge"); + if (!showGlyph && !showValue && visualization == "none") { + throw std::runtime_error( + "widget." + std::string(widgetName) + ": show_glyph, show_value, and visualization cannot all be disabled" + ); + } + } + void validateWidgetSettings(std::string_view widgetName, const WidgetConfig& widget) { validateWidgetColorSettings(widgetName, widget); validateWidgetScaleSetting(widgetName, widget); validateKeyboardLayoutWidgetSettings(widgetName, widget); + validateSysmonWidgetSettings(widgetName, widget); } std::optional componentOwnerId(std::string_view ownerPath, std::string_view prefix) { diff --git a/src/shell/bar/widgets/sysmon_widget.cpp b/src/shell/bar/widgets/sysmon_widget.cpp index 70e111ae63..2ec3d4b643 100644 --- a/src/shell/bar/widgets/sysmon_widget.cpp +++ b/src/shell/bar/widgets/sysmon_widget.cpp @@ -176,10 +176,9 @@ namespace { } // namespace SysmonWidget::SysmonWidget(SystemMonitorService* monitor, ConfigService& configService, Options options) - : m_monitor(monitor), m_stat(options.stat), m_displayMode(options.displayMode), - m_highlightColor(options.highlightColor), m_configService(configService), m_showIcon(options.showIcon), - m_showLabel(options.showLabel && options.displayMode != SysmonDisplayMode::None), - m_labelMinWidth(static_cast(options.labelMinWidth)), + : m_monitor(monitor), m_stat(options.stat), m_visualization(options.visualization), + m_highlightColor(options.highlightColor), m_configService(configService), m_showGlyph(options.showGlyph), + m_showValue(options.showValue), m_labelMinWidth(static_cast(options.labelMinWidth)), m_diskPath(FileUtils::expandUserPath(options.diskPath).string()), m_networkInterface(std::move(options.networkInterface)), m_networkSpeedUnit(options.networkSpeedUnit), m_networkSpeedLabelStyle( @@ -230,7 +229,7 @@ SysmonWidget::~SysmonWidget() { void SysmonWidget::create() { auto container = ui::inputArea({}); std::unique_ptr glyphNode; - if (m_showIcon) { + if (m_showGlyph) { if (m_customImage.enabled()) { glyphNode = ui::image({.out = &m_image, .fit = ImageFit::Contain}); } else { @@ -244,7 +243,7 @@ void SysmonWidget::create() { } std::unique_ptr graphOrGaugeNode; - if (m_displayMode == SysmonDisplayMode::Graph) { + if (m_visualization == SysmonVisualization::Graph) { graphOrGaugeNode = ui::box(); m_chartBg = static_cast(graphOrGaugeNode.get()); @@ -254,7 +253,7 @@ void SysmonWidget::create() { m_graph = static_cast(m_chartBg->addChild(std::move(graph))); } - if (m_displayMode == SysmonDisplayMode::Gauge) { + if (m_visualization == SysmonVisualization::Gauge) { const ColorSpec base = widgetForegroundOr(colorSpecFromRole(ColorRole::OnSurface)); graphOrGaugeNode = ui::progressBar({ .fill = base, @@ -265,7 +264,7 @@ void SysmonWidget::create() { } std::unique_ptr textNode; - if (m_displayMode == SysmonDisplayMode::Text || m_showLabel) { + if (m_showValue) { textNode = ui::label({ .out = &m_label, .fontSize = Style::fontSizeBody * m_contentScale, @@ -503,7 +502,7 @@ void SysmonWidget::syncGaugeProgress(double normalized) { void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float containerHeight) { auto* rootNode = root(); - if ((m_showIcon && m_glyph == nullptr && m_image == nullptr) || rootNode == nullptr) { + if ((m_showGlyph && m_glyph == nullptr && m_image == nullptr) || rootNode == nullptr) { return; } const bool isVerticalBar = containerHeight > containerWidth; @@ -518,8 +517,8 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont const float iconW = iconWidth(); const float iconH = iconHeight(); const float gap = Style::spaceXs * m_contentScale; - const float iconWPlusGap = m_showIcon ? iconW + gap : 0.0f; - const float iconHPlusGap = m_showIcon ? iconH + gap : 0.0f; + const float iconWPlusGap = m_showGlyph ? iconW + gap : 0.0f; + const float iconHPlusGap = m_showGlyph ? iconH + gap : 0.0f; const float baseSize = Style::fontSizeBody * m_contentScale; const bool verticalBar = m_isVerticalBar; @@ -533,8 +532,8 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont const float labelW = m_label != nullptr ? m_label->width() : 0.0f; const float labelH = m_label != nullptr ? m_label->height() : 0.0f; - if (m_displayMode == SysmonDisplayMode::Gauge && m_gauge != nullptr) { - const float gaugeStem = m_showIcon ? std::round(baseSize * 0.85f) : std::round(baseSize * 1.2f); + if (m_visualization == SysmonVisualization::Gauge && m_gauge != nullptr) { + const float gaugeStem = m_showGlyph ? std::round(baseSize * 0.85f) : std::round(baseSize * 1.2f); const float gaugeThickness = std::max(3.0f, roundf(baseSize * 0.3f)); if (verticalBar) { @@ -578,10 +577,10 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont return; } - if (m_displayMode == SysmonDisplayMode::Graph && m_chartBg != nullptr) { + if (m_visualization == SysmonVisualization::Graph && m_chartBg != nullptr) { const float chartW = verticalBar ? std::min(50.0f * m_contentScale, std::max(1.0f, containerWidth)) : 50.0f * m_contentScale; - const float chartH = m_showIcon ? iconH : std::round(baseSize * 1.2f); + const float chartH = m_showGlyph ? iconH : std::round(baseSize * 1.2f); if (verticalBar) { float contentW = std::max(iconW, chartW); @@ -640,7 +639,7 @@ void SysmonWidget::doLayout(Renderer& renderer, float containerWidth, float cont } void SysmonWidget::doUpdate(Renderer& renderer) { - if (m_showIcon && m_glyph == nullptr && m_image == nullptr) { + if (m_showGlyph && m_glyph == nullptr && m_image == nullptr) { return; } @@ -657,12 +656,12 @@ void SysmonWidget::doUpdate(Renderer& renderer) { static_cast(rootNode)->setTooltip(buildTooltipRows(value)); } - if (m_displayMode == SysmonDisplayMode::Gauge) { + if (m_visualization == SysmonVisualization::Gauge) { syncGaugeProgress(currentNormalized()); return; } - if (m_displayMode == SysmonDisplayMode::Graph) { + if (m_visualization == SysmonVisualization::Graph) { if (m_monitor != nullptr && m_monitor->isRunning()) { updateGraph(renderer); scheduleNextUpdate(m_monitor->latest().sampledAt); @@ -691,7 +690,7 @@ void SysmonWidget::onFrameTick(float deltaMs) { } bool SysmonWidget::needsFrameTick() const { - return m_displayMode == SysmonDisplayMode::Graph && m_scrollProgress < 1.0f; + return m_visualization == SysmonVisualization::Graph && m_scrollProgress < 1.0f; } void SysmonWidget::scheduleNextUpdate(std::chrono::steady_clock::time_point latestSampleAt) { diff --git a/src/shell/bar/widgets/sysmon_widget.h b/src/shell/bar/widgets/sysmon_widget.h index ced69bb71e..63ce413573 100644 --- a/src/shell/bar/widgets/sysmon_widget.h +++ b/src/shell/bar/widgets/sysmon_widget.h @@ -43,7 +43,7 @@ enum class SysmonStat { NetRx, NetTx }; -enum class SysmonDisplayMode { Text, Graph, Gauge, None }; +enum class SysmonVisualization { Graph, Gauge, None }; enum class SysmonGlyphPosition { Before, After }; class SysmonWidget : public Widget { @@ -57,10 +57,10 @@ class SysmonWidget : public Widget { std::string networkInterface; FormatUnits::DecimalByteRateUnit networkSpeedUnit = FormatUnits::DecimalByteRateUnit::Auto; bool networkSpeedCompact = false; - SysmonDisplayMode displayMode = SysmonDisplayMode::Gauge; + SysmonVisualization visualization = SysmonVisualization::Gauge; ColorSpec highlightColor = colorSpecFromRole(ColorRole::Error); - bool showIcon = true; - bool showLabel = true; + bool showGlyph = true; + bool showValue = true; int labelMinWidth = 0; bool showUnits = true; SysmonGlyphPosition glyphPosition = SysmonGlyphPosition::Before; @@ -103,11 +103,11 @@ class SysmonWidget : public Widget { SystemMonitorService* m_monitor; SysmonStat m_stat; - SysmonDisplayMode m_displayMode; + SysmonVisualization m_visualization; ColorSpec m_highlightColor = colorSpecFromRole(ColorRole::Error); ConfigService& m_configService; - bool m_showIcon; - bool m_showLabel; + bool m_showGlyph; + bool m_showValue; float m_labelMinWidth = 0.0f; std::string m_diskPath; std::string m_networkInterface; diff --git a/src/shell/bar/widgets/sysmon_widget_definition.cpp b/src/shell/bar/widgets/sysmon_widget_definition.cpp index 0de29a7028..a6f36ce8bc 100644 --- a/src/shell/bar/widgets/sysmon_widget_definition.cpp +++ b/src/shell/bar/widgets/sysmon_widget_definition.cpp @@ -1,18 +1,5 @@ #include "shell/bar/widgets/sysmon_widget_definition.h" -namespace { - - settings::WidgetSettingVisibility labelMinWidthVisibility() { - settings::WidgetSettingVisibility visibility; - visibility.all = { - {"display", {"gauge", "graph", "text"}}, - {"show_label", {"true"}}, - }; - return visibility; - } - -} // namespace - const noctalia::bar::WidgetDefinition& sysmonWidgetDefinition() { using noctalia::bar::field; using Options = SysmonWidget::Options; @@ -21,14 +8,16 @@ const noctalia::bar::WidgetDefinition definition{ @@ -110,32 +99,6 @@ const noctalia::bar::WidgetDefinition({ - .key = "show_icon", - }), - field<&Options::glyph>({ - .key = "glyph", - .control = settings::WidgetControlKind::Glyph, - .presentation = - settings::WidgetSettingPresentation{ - .descriptionKey = "settings.widgets.settings.glyph.sysmon-description", - .visibleWhen = showIcon, - }, - }), - field<&Options::customImage>({ - .key = "custom_image", - .presentation = - settings::WidgetSettingPresentation{ - .visibleWhen = showIcon, - }, - }), - field<&Options::customImageColorize>({ - .key = "custom_image_colorize", - .presentation = - settings::WidgetSettingPresentation{ - .visibleWhen = showIcon, - }, - }), field<&Options::diskPath>({ .key = "path", .presentation = @@ -182,61 +145,89 @@ const noctalia::bar::WidgetDefinition({ - .key = "display", + field<&Options::visualization>({ + .key = "visualization", .choices = { { - .value = SysmonDisplayMode::Gauge, + .value = SysmonVisualization::Gauge, .configValue = "gauge", .labelKey = "settings.widgets.options.gauge", }, { - .value = SysmonDisplayMode::Graph, + .value = SysmonVisualization::Graph, .configValue = "graph", .labelKey = "settings.widgets.options.graph", }, { - .value = SysmonDisplayMode::Text, - .configValue = "text", - .labelKey = "settings.widgets.options.text", - }, - { - .value = SysmonDisplayMode::None, + .value = SysmonVisualization::None, .configValue = "none", .labelKey = "settings.widgets.options.none", }, }, .presentation = settings::WidgetSettingPresentation{ + .group = "presentation", .segmented = true, }, }), - field<&Options::highlightColor>({ - .key = "highlight_color", + field<&Options::showValue>({ + .key = "show_value", + .presentation = + settings::WidgetSettingPresentation{ + .group = "presentation", + }, }), - field<&Options::showLabel>({ - .key = "show_label", + field<&Options::showUnits>({ + .key = "show_units", .presentation = settings::WidgetSettingPresentation{ - .visibleWhen = hasDisplay, + .group = "presentation", + .visibleWhen = showValue, }, }), field<&Options::labelMinWidth>({ - .key = "label_min_width", + .key = "value_min_width", .minValue = 0.0, .maxValue = 200.0, .step = 1.0, .presentation = settings::WidgetSettingPresentation{ - .visibleWhen = labelMinWidthVisibility(), + .group = "presentation", + .visibleWhen = showValue, }, }), - field<&Options::showUnits>({ - .key = "label_show_units", + field<&Options::showGlyph>({ + .key = "show_glyph", + .presentation = + settings::WidgetSettingPresentation{ + .group = "presentation", + }, + }), + field<&Options::glyph>({ + .key = "glyph", + .control = settings::WidgetControlKind::Glyph, + .presentation = + settings::WidgetSettingPresentation{ + .descriptionKey = "settings.widgets.settings.glyph.sysmon-description", + .group = "presentation", + .visibleWhen = showGlyph, + }, + }), + field<&Options::customImage>({ + .key = "custom_image", .presentation = settings::WidgetSettingPresentation{ - .visibleWhen = showLabel, + .group = "presentation", + .visibleWhen = showGlyph, + }, + }), + field<&Options::customImageColorize>({ + .key = "custom_image_colorize", + .presentation = + settings::WidgetSettingPresentation{ + .group = "presentation", + .visibleWhen = showGlyph, }, }), field<&Options::glyphPosition>({ @@ -256,14 +247,23 @@ const noctalia::bar::WidgetDefinition({ + .key = "highlight_color", + .presentation = + settings::WidgetSettingPresentation{ + .group = "presentation", + .visibleWhen = hasVisualization, + }, + }), }, .finalize = [](Options& options, const SysmonWidgetDefinitionContext& context) { - if (context.verticalBar && options.displayMode == SysmonDisplayMode::Graph) { - options.displayMode = SysmonDisplayMode::Gauge; + if (context.verticalBar && options.visualization == SysmonVisualization::Graph) { + options.visualization = SysmonVisualization::Gauge; } }, }; diff --git a/tests/config_migration_test.cpp b/tests/config_migration_test.cpp index 37534ce0b5..0d670d0582 100644 --- a/tests/config_migration_test.cpp +++ b/tests/config_migration_test.cpp @@ -454,6 +454,112 @@ middle_command = "" expect(secondPassIssues.empty(), "dead zone normalization was not idempotent"); } + void checkSysmonPresentationMigration() { + toml::table config = toml::parse(R"( +[widget.gauge] +type = "sysmon" +display = "gauge" +show_label = false +show_icon = false +label_show_units = false +label_min_width = 42 + +[widget.text] +type = "sysmon" +display = "text" +show_label = false + +[widget.none] +type = "sysmon" +display = "none" +show_label = true + +[widget.sysmon] +display = "graph" + +[widget.canonical] +type = "sysmon" +display = "text" +visualization = "graph" +show_label = true +show_value = false +label_show_units = false +show_units = true +label_min_width = 20 +value_min_width = 30 +show_icon = false +show_glyph = true + +[widget.clock] +display = "text" +show_label = false +)"); + noctalia::config::LegacyConfigIssues issues; + noctalia::config::normalizeLegacyConfig(config, issues); + + expect( + config["widget"]["gauge"]["visualization"].value() == std::optional{"gauge"}, + "gauge display was not migrated" + ); + expect( + config["widget"]["gauge"]["show_value"].value() == std::optional{false}, + "gauge show_label was not migrated" + ); + expect( + config["widget"]["gauge"]["show_glyph"].value() == std::optional{false}, + "show_icon was not migrated" + ); + expect( + config["widget"]["gauge"]["show_units"].value() == std::optional{false} + && config["widget"]["gauge"]["value_min_width"].value() == std::optional{42}, + "value detail settings were not migrated" + ); + expect( + config["widget"]["text"]["visualization"].value() == std::optional{"none"} + && config["widget"]["text"]["show_value"].value() == std::optional{true}, + "text display did not preserve its always-visible value" + ); + expect( + config["widget"]["none"]["visualization"].value() == std::optional{"none"} + && config["widget"]["none"]["show_value"].value() == std::optional{false}, + "none display did not preserve its hidden value" + ); + expect( + config["widget"]["sysmon"]["visualization"].value() == std::optional{"graph"} + && config["widget"]["sysmon"]["show_value"].value() == std::optional{true}, + "implicit sysmon defaults were not preserved" + ); + expect( + config["widget"]["canonical"]["visualization"].value() == std::optional{"graph"} + && config["widget"]["canonical"]["show_value"].value() == std::optional{false} + && config["widget"]["canonical"]["show_glyph"].value() == std::optional{true}, + "legacy sysmon settings overwrote canonical settings" + ); + expect( + config["widget"]["canonical"]["show_units"].value() == std::optional{true} + && config["widget"]["canonical"]["value_min_width"].value() + == std::optional{30}, + "legacy value details overwrote canonical settings" + ); + expect( + !config["widget"]["canonical"].as_table()->contains("display") + && !config["widget"]["canonical"].as_table()->contains("show_label") + && !config["widget"]["canonical"].as_table()->contains("show_icon") + && !config["widget"]["canonical"].as_table()->contains("label_show_units") + && !config["widget"]["canonical"].as_table()->contains("label_min_width"), + "legacy sysmon keys were retained" + ); + expect( + config["widget"]["clock"]["display"].value() == std::optional{"text"}, + "another widget type was migrated as sysmon" + ); + expect(issues.size() == 5, "expected one migration issue per sysmon widget"); + + noctalia::config::LegacyConfigIssues secondPassIssues; + noctalia::config::normalizeLegacyConfig(config, secondPassIssues); + expect(secondPassIssues.empty(), "sysmon presentation normalization was not idempotent"); + } + void checkVersionGating() { toml::table legacy = toml::parse(R"( [bar.main] @@ -599,6 +705,7 @@ int main() { checkRemainingWidgetGesturesMigration(); checkCustomButtonCommandsMigration(); checkDeadZoneActionsMigration(); + checkSysmonPresentationMigration(); checkVersionGating(); checkReminderFingerprint(); checkRegistryOrdering(); From 764fa9cba843859080ab8519e066981340710862 Mon Sep 17 00:00:00 2001 From: Lemmy Date: Thu, 30 Jul 2026 19:43:08 -0400 Subject: [PATCH 3/3] refactor(sysmon): limit presentation settings migration --- assets/translations/en.json | 4 ---- src/config/config_migrations.cpp | 16 --------------- .../bar/widgets/sysmon_widget_definition.cpp | 4 ++-- tests/config_migration_test.cpp | 20 ++++--------------- 4 files changed, 6 insertions(+), 38 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 591dfd12a4..e1c370a1b8 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -3730,10 +3730,6 @@ "description": "Color role used for urgent workspaces; fixed hex colors are also supported", "label": "Urgent Color" }, - "value-min-width": { - "description": "Minimum value width in pixels to prevent resizing", - "label": "Value Min Width" - }, "visualization": { "description": "Gauge, graph, or no data visualization", "label": "Visualization" diff --git a/src/config/config_migrations.cpp b/src/config/config_migrations.cpp index a1f40abe37..efb1f86689 100644 --- a/src/config/config_migrations.cpp +++ b/src/config/config_migrations.cpp @@ -465,22 +465,6 @@ namespace noctalia::config { changed = true; } - if (const auto showUnits = (*widget)["label_show_units"].value(); showUnits.has_value()) { - if (!widget->contains("show_units")) { - widget->insert_or_assign("show_units", *showUnits); - } - widget->erase("label_show_units"); - changed = true; - } - - if (const auto minWidth = (*widget)["label_min_width"].value(); minWidth.has_value()) { - if (!widget->contains("value_min_width")) { - widget->insert_or_assign("value_min_width", *minWidth); - } - widget->erase("label_min_width"); - changed = true; - } - if (changed) { onChanged("widget." + std::string(widgetName.str())); } diff --git a/src/shell/bar/widgets/sysmon_widget_definition.cpp b/src/shell/bar/widgets/sysmon_widget_definition.cpp index a6f36ce8bc..75bf868ec6 100644 --- a/src/shell/bar/widgets/sysmon_widget_definition.cpp +++ b/src/shell/bar/widgets/sysmon_widget_definition.cpp @@ -179,7 +179,7 @@ const noctalia::bar::WidgetDefinition({ - .key = "show_units", + .key = "label_show_units", .presentation = settings::WidgetSettingPresentation{ .group = "presentation", @@ -187,7 +187,7 @@ const noctalia::bar::WidgetDefinition({ - .key = "value_min_width", + .key = "label_min_width", .minValue = 0.0, .maxValue = 200.0, .step = 1.0, diff --git a/tests/config_migration_test.cpp b/tests/config_migration_test.cpp index 0d670d0582..58648fc515 100644 --- a/tests/config_migration_test.cpp +++ b/tests/config_migration_test.cpp @@ -483,10 +483,6 @@ display = "text" visualization = "graph" show_label = true show_value = false -label_show_units = false -show_units = true -label_min_width = 20 -value_min_width = 30 show_icon = false show_glyph = true @@ -510,9 +506,9 @@ show_label = false "show_icon was not migrated" ); expect( - config["widget"]["gauge"]["show_units"].value() == std::optional{false} - && config["widget"]["gauge"]["value_min_width"].value() == std::optional{42}, - "value detail settings were not migrated" + config["widget"]["gauge"]["label_show_units"].value() == std::optional{false} + && config["widget"]["gauge"]["label_min_width"].value() == std::optional{42}, + "unchanged label detail settings were not preserved" ); expect( config["widget"]["text"]["visualization"].value() == std::optional{"none"} @@ -535,18 +531,10 @@ show_label = false && config["widget"]["canonical"]["show_glyph"].value() == std::optional{true}, "legacy sysmon settings overwrote canonical settings" ); - expect( - config["widget"]["canonical"]["show_units"].value() == std::optional{true} - && config["widget"]["canonical"]["value_min_width"].value() - == std::optional{30}, - "legacy value details overwrote canonical settings" - ); expect( !config["widget"]["canonical"].as_table()->contains("display") && !config["widget"]["canonical"].as_table()->contains("show_label") - && !config["widget"]["canonical"].as_table()->contains("show_icon") - && !config["widget"]["canonical"].as_table()->contains("label_show_units") - && !config["widget"]["canonical"].as_table()->contains("label_min_width"), + && !config["widget"]["canonical"].as_table()->contains("show_icon"), "legacy sysmon keys were retained" ); expect(