feat(bar): add unread indicator to notification widget

This commit is contained in:
Lysec
2026-04-04 13:16:58 +02:00
parent ca15d2b6f6
commit 54cb47a0e0
9 changed files with 107 additions and 13 deletions
+11 -3
View File
@@ -21,10 +21,18 @@ void signal_handler(int signum) {
Application::Application() : m_internalNotifications(m_manager) {
logInfo("noctalia hello");
m_manager.setEventCallback([](const Notification& n, NotificationEvent event) {
const char* kind = (event == NotificationEvent::Added) ? "added" : "updated";
m_manager.setEventCallback([this](const Notification& n, NotificationEvent event) {
const char* kind = "updated";
if (event == NotificationEvent::Added) {
kind = "added";
} else if (event == NotificationEvent::Closed) {
kind = "closed";
}
const char* origin = (n.origin == NotificationOrigin::Internal) ? "internal" : "external";
logDebug("notification {} id={} origin={}", kind, n.id, origin);
// Keep bar widgets in sync with notification state changes.
m_bar.onWorkspaceChange();
});
}
@@ -69,7 +77,7 @@ void Application::run() {
m_wallpaper.initialize(m_wayland, &m_configService, &m_stateService);
// Initialize bar (top layer)
m_bar.initialize(m_wayland, &m_configService, &m_timeService);
m_bar.initialize(m_wayland, &m_configService, &m_timeService, &m_manager);
try {
m_systemMonitor = std::make_unique<SystemMonitorService>();
+5
View File
@@ -122,6 +122,7 @@ bool NotificationManager::close(uint32_t id, CloseReason reason) {
}
const size_t index = it->second;
const Notification closed = m_notifications[index];
const char* reason_str = (reason == CloseReason::Expired) ? "expired"
: (reason == CloseReason::Dismissed) ? "dismissed"
: "closed";
@@ -134,6 +135,10 @@ bool NotificationManager::close(uint32_t id, CloseReason reason) {
m_id_to_index[m_notifications[i].id] = i;
}
if (m_event_callback) {
m_event_callback(closed, NotificationEvent::Closed);
}
return true;
}
+1
View File
@@ -11,6 +11,7 @@
enum class NotificationEvent {
Added,
Updated,
Closed,
};
class NotificationManager {
+5 -3
View File
@@ -34,12 +34,14 @@ std::uint32_t positionToAnchor(const std::string& position) {
Bar::Bar() = default;
bool Bar::initialize(WaylandConnection& wayland, ConfigService* config, TimeService* timeService) {
bool Bar::initialize(WaylandConnection& wayland, ConfigService* config, TimeService* timeService,
NotificationManager* notifications) {
m_wayland = &wayland;
m_config = config;
m_time = timeService;
m_notifications = notifications;
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config());
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config(), m_notifications);
if (timeService != nullptr) {
timeService->setTickSecondCallback([this]() {
@@ -66,7 +68,7 @@ bool Bar::initialize(WaylandConnection& wayland, ConfigService* config, TimeServ
void Bar::reload() {
logInfo("bar: reloading config");
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config());
m_widgetFactory = std::make_unique<WidgetFactory>(*m_wayland, m_time, m_config->config(), m_notifications);
m_instances.clear();
m_surfaceMap.clear();
m_hoveredInstance = nullptr;
+4 -1
View File
@@ -8,6 +8,7 @@
#include <vector>
class ConfigService;
class NotificationManager;
class TimeService;
class WaylandConnection;
struct PointerEvent;
@@ -17,7 +18,8 @@ class Bar {
public:
Bar();
bool initialize(WaylandConnection& wayland, ConfigService* config, TimeService* timeService);
bool initialize(WaylandConnection& wayland, ConfigService* config, TimeService* timeService,
NotificationManager* notifications);
void reload();
void closeAllInstances();
void onOutputChange();
@@ -37,6 +39,7 @@ private:
WaylandConnection* m_wayland = nullptr;
ConfigService* m_config = nullptr;
TimeService* m_time = nullptr;
NotificationManager* m_notifications = nullptr;
std::unique_ptr<WidgetFactory> m_widgetFactory;
std::vector<std::unique_ptr<BarInstance>> m_instances;
+5 -3
View File
@@ -2,13 +2,15 @@
#include "config/ConfigService.h"
#include "core/Log.h"
#include "notification/NotificationManager.h"
#include "shell/widgets/ClockWidget.h"
#include "shell/widgets/NotificationWidget.h"
#include "shell/widgets/SpacerWidget.h"
#include "shell/widgets/WorkspacesWidget.h"
WidgetFactory::WidgetFactory(WaylandConnection& wayland, TimeService* time, const Config& config)
: m_wayland(wayland), m_time(time), m_config(config) {}
WidgetFactory::WidgetFactory(WaylandConnection& wayland, TimeService* time, const Config& config,
NotificationManager* notifications)
: m_wayland(wayland), m_time(time), m_config(config), m_notifications(notifications) {}
std::unique_ptr<Widget> WidgetFactory::create(const std::string& name, wl_output* output) const {
if (name == "clock") {
@@ -24,7 +26,7 @@ std::unique_ptr<Widget> WidgetFactory::create(const std::string& name, wl_output
}
if (name == "notifications") {
return std::make_unique<NotificationWidget>();
return std::make_unique<NotificationWidget>(m_notifications);
}
if (name == "spacer") {
+4 -1
View File
@@ -6,13 +6,15 @@
#include <string>
struct Config;
class NotificationManager;
struct wl_output;
class TimeService;
class WaylandConnection;
class WidgetFactory {
public:
WidgetFactory(WaylandConnection& wayland, TimeService* time, const Config& config);
WidgetFactory(WaylandConnection& wayland, TimeService* time, const Config& config,
NotificationManager* notifications);
[[nodiscard]] std::unique_ptr<Widget> create(const std::string& name, wl_output* output) const;
@@ -20,4 +22,5 @@ private:
WaylandConnection& m_wayland;
TimeService* m_time;
const Config& m_config;
NotificationManager* m_notifications;
};
+62 -2
View File
@@ -1,15 +1,75 @@
#include "shell/widgets/NotificationWidget.h"
#include "notification/NotificationManager.h"
#include "render/programs/RoundedRectProgram.h"
#include "render/scene/RectNode.h"
#include "ui/controls/Icon.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include <algorithm>
#include <memory>
NotificationWidget::NotificationWidget(NotificationManager* manager) : m_manager(manager) {}
void NotificationWidget::create(Renderer& renderer) {
auto root = std::make_unique<Node>();
auto icon = std::make_unique<Icon>();
icon->setIcon("bell");
icon->setColor(palette.onSurface);
m_icon = icon.get();
m_root = std::move(icon);
m_icon->measure(renderer);
root->addChild(std::move(icon));
auto dot = std::make_unique<RectNode>();
auto dotStyle = dot->style();
dotStyle.fill = palette.primary;
dotStyle.border = palette.primary;
dotStyle.fillMode = FillMode::Solid;
dotStyle.radius = 3.0f;
dotStyle.softness = 1.0f;
dotStyle.borderWidth = 0.0f;
dot->setStyle(dotStyle);
dot->setVisible(false);
m_dot = static_cast<RectNode*>(root->addChild(std::move(dot)));
m_root = std::move(root);
refreshIndicatorState();
layout(renderer, 0.0f, 0.0f);
}
void NotificationWidget::layout(Renderer& renderer, float /*containerWidth*/, float /*containerHeight*/) {
auto* rootNode = root();
if (m_icon == nullptr || rootNode == nullptr) {
return;
}
m_icon->measure(renderer);
m_icon->setPosition(0.0f, 0.0f);
rootNode->setSize(m_icon->width(), m_icon->height());
if (m_dot != nullptr && m_dot->visible()) {
constexpr float kDotSize = 5.0f;
const float dotX = std::max(0.0f, m_icon->width() - kDotSize + 1.0f);
const float dotY = -1.0f;
m_dot->setPosition(dotX, dotY);
m_dot->setSize(kDotSize, kDotSize);
}
}
void NotificationWidget::update(Renderer& renderer) {
refreshIndicatorState();
Widget::update(renderer);
}
void NotificationWidget::refreshIndicatorState() {
const bool hasNotifications = (m_manager != nullptr) && !m_manager->all().empty();
if (hasNotifications == m_hasNotifications) {
return;
}
m_hasNotifications = hasNotifications;
if (m_dot != nullptr) {
m_dot->setVisible(m_hasNotifications);
}
}
+10
View File
@@ -3,12 +3,22 @@
#include "shell/Widget.h"
class Icon;
class NotificationManager;
class RectNode;
class NotificationWidget : public Widget {
public:
explicit NotificationWidget(NotificationManager* manager);
void create(Renderer& renderer) override;
void layout(Renderer& renderer, float containerWidth, float containerHeight) override;
void update(Renderer& renderer) override;
private:
void refreshIndicatorState();
NotificationManager* m_manager = nullptr;
Icon* m_icon = nullptr;
RectNode* m_dot = nullptr;
bool m_hasNotifications = false;
};