This commit is contained in:
Lysec
2026-04-08 18:20:17 +02:00
7 changed files with 234 additions and 37 deletions
+1
View File
@@ -410,6 +410,7 @@ add_executable(noctalia
src/system/weather_service.cpp
src/time/time_service.cpp
src/ui/controls/box.cpp
src/ui/controls/audio_spectrum.cpp
src/ui/controls/button.cpp
src/ui/controls/chip.cpp
src/ui/controls/flex.cpp
+5 -6
View File
@@ -216,9 +216,9 @@ gdbus call --session --dest dev.noctalia.Debug --object-path /dev/noctalia/Debug
- [ ] Network
- [ ] Bluetooth
- [ ] Brightness
- [ ] Microphone
- [ ] Microphone <- unsure we need it
- [ ] Power profile
- [ ] System monitor
- [~] System monitor <- need graph fix
- [ ] Dock
- [ ] Keyboard layout
- [ ] Lock keys (Caps/Num)
@@ -226,12 +226,11 @@ gdbus call --session --dest dev.noctalia.Debug --object-path /dev/noctalia/Debug
- [ ] Night light button
- [ ] Keep awake (idle inhibitor)
- [ ] Audio visualizer
- [ ] Launcher button
- [ ] Control center button
- [x] Launcher button
- [ ] Session menu button
- [ ] Settings button
- [ ] Wallpaper selector button
- [ ] Custom button (user-defined IPC)
- [ ] Custom button (with user-defined IPC)
- [ ] Settings button
### Desktop Widgets
+3
View File
@@ -48,6 +48,8 @@ Application::Application() : m_weatherService(m_configService, m_httpClient) {
}
Application::~Application() {
m_wayland.setClipboardService(nullptr);
if (m_systemBus != nullptr) {
m_systemBus->processPendingEvents();
m_upowerService.reset();
@@ -240,6 +242,7 @@ void Application::initUi() {
// Panel manager must be before bar so widgets can access PanelManager::instance()
m_panelManager.initialize(m_wayland, &m_configService, &m_renderContext);
m_configService.addReloadCallback([this]() { m_panelManager.close(); });
m_panelManager.registerPanel("clipboard", std::make_unique<ClipboardPanel>(&m_clipboardService));
m_panelManager.registerPanel("test", std::make_unique<TestPanel>());
m_panelManager.registerPanel(
+36 -30
View File
@@ -7,6 +7,7 @@
#include "render/core/renderer.h"
#include "shell/control_center/tab.h"
#include "shell/panel/panel_manager.h"
#include "ui/controls/audio_spectrum.h"
#include "ui/controls/button.h"
#include "ui/controls/flex.h"
#include "ui/controls/image.h"
@@ -32,8 +33,6 @@ namespace {
const Logger kLog{"media_tab"};
constexpr float kArtworkSize = Style::controlHeightLg * 6;
constexpr float kMediaColumnMinWidth = Style::controlHeightLg * 9;
constexpr float kVisualizerColumnMinWidth = Style::controlHeightLg * 8;
constexpr float kMediaNowCardMinHeight = Style::controlHeightLg * 11 + Style::spaceSm * 2;
constexpr float kMediaControlsHeight = Style::controlHeightLg + Style::spaceXs;
constexpr float kMediaPlayPauseHeight = Style::controlHeightLg + Style::spaceSm;
@@ -184,7 +183,11 @@ ButtonVariant toggleVariant(bool active) { return active ? ButtonVariant::Accent
} // namespace
MediaTab::MediaTab(MprisService* mpris, HttpClient* httpClient, PipeWireSpectrum* spectrum)
: m_mpris(mpris), m_httpClient(httpClient), m_spectrum(spectrum) {}
: m_mpris(mpris), m_httpClient(httpClient), m_spectrum(spectrum) {
if (m_spectrum != nullptr) {
m_spectrum->setBandCount(32);
}
}
std::unique_ptr<Flex> MediaTab::build(Renderer& /*renderer*/) {
const float scale = contentScale();
@@ -200,7 +203,6 @@ std::unique_ptr<Flex> MediaTab::build(Renderer& /*renderer*/) {
mediaColumn->setAlign(FlexAlign::Stretch);
mediaColumn->setGap(Style::spaceSm * scale);
mediaColumn->setFlexGrow(3.0f);
mediaColumn->setMinWidth(kMediaColumnMinWidth * scale);
m_mediaColumn = mediaColumn.get();
auto nowCard = std::make_unique<Flex>();
@@ -209,7 +211,6 @@ std::unique_ptr<Flex> MediaTab::build(Renderer& /*renderer*/) {
nowCard->setGap(Style::spaceMd * scale);
nowCard->setFlexGrow(1.0f);
nowCard->setMinHeight(kMediaNowCardMinHeight * scale);
nowCard->setMinWidth(kMediaColumnMinWidth * scale);
m_nowCard = nowCard.get();
auto mediaStack = std::make_unique<Flex>();
@@ -402,32 +403,25 @@ std::unique_ptr<Flex> MediaTab::build(Renderer& /*renderer*/) {
auto visualizerColumn = std::make_unique<Flex>();
visualizerColumn->setDirection(FlexDirection::Vertical);
visualizerColumn->setAlign(FlexAlign::Stretch);
visualizerColumn->setGap(Style::spaceSm * scale);
visualizerColumn->setGap(0.0f);
visualizerColumn->setFlexGrow(2.0f);
visualizerColumn->setMinWidth(kVisualizerColumnMinWidth * scale);
visualizerColumn->setPadding(Style::spaceSm * scale);
visualizerColumn->setBackground(palette.surfaceVariant);
visualizerColumn->setRadius(Style::radiusLg * scale);
visualizerColumn->setBorderWidth(0.0f);
visualizerColumn->setSoftness(1.0f);
visualizerColumn->setClipChildren(true);
m_visualizerColumn = visualizerColumn.get();
auto visualizerCard = std::make_unique<Flex>();
applyCard(*visualizerCard, scale);
visualizerCard->setAlign(FlexAlign::Stretch);
visualizerCard->setJustify(FlexJustify::Center);
visualizerCard->setFlexGrow(1.0f);
m_visualizerCard = visualizerCard.get();
auto visualizerTitle = std::make_unique<Label>();
visualizerTitle->setText("Audio Visualizer");
visualizerTitle->setBold(true);
visualizerTitle->setFontSize(Style::fontSizeTitle * scale);
visualizerTitle->setColor(palette.onSurface);
visualizerCard->addChild(std::move(visualizerTitle));
auto visualizerBody = std::make_unique<Label>();
visualizerBody->setText("Reserved space for the future visualizer.");
visualizerBody->setFontSize(Style::fontSizeBody * scale);
visualizerBody->setColor(palette.onSurfaceVariant);
visualizerCard->addChild(std::move(visualizerBody));
visualizerColumn->addChild(std::move(visualizerCard));
auto visualizerSpectrum = std::make_unique<AudioSpectrum>();
visualizerSpectrum->setGradient(palette.secondary, palette.tertiary);
visualizerSpectrum->setSpacingRatio(0.5f);
visualizerSpectrum->setOrientation(AudioSpectrumOrientation::Vertical);
visualizerSpectrum->setMirrored(true);
visualizerSpectrum->setCentered(true);
visualizerSpectrum->setFlexGrow(1.0f);
m_visualizerSpectrum = visualizerSpectrum.get();
visualizerColumn->addChild(std::move(visualizerSpectrum));
tab->addChild(std::move(mediaColumn));
tab->addChild(std::move(visualizerColumn));
return tab;
@@ -444,7 +438,7 @@ void MediaTab::layout(Renderer& renderer, float contentWidth, float bodyHeight)
const float cardInnerWidth =
std::max(0.0f, m_nowCard->width() - (m_nowCard->paddingLeft() + m_nowCard->paddingRight()));
const float mediaWidth = std::clamp(cardInnerWidth, kMediaColumnMinWidth * scale, Style::controlHeightLg * 11.0f * scale);
const float mediaWidth = std::clamp(cardInnerWidth, 1.0f, Style::controlHeightLg * 11.0f * scale);
m_mediaStack->setSize(mediaWidth, 0.0f);
if (m_playerSelect != nullptr) {
@@ -512,6 +506,15 @@ void MediaTab::layout(Renderer& renderer, float contentWidth, float bodyHeight)
if (m_progressSlider != nullptr) {
m_progressSlider->setSize(mediaWidth, 0.0f);
}
if (m_visualizerColumn != nullptr && m_visualizerSpectrum != nullptr) {
const float innerWidth =
std::max(0.0f, m_visualizerColumn->width() - (m_visualizerColumn->paddingLeft() + m_visualizerColumn->paddingRight()));
const float innerHeight =
std::max(0.0f, m_visualizerColumn->height() - (m_visualizerColumn->paddingTop() + m_visualizerColumn->paddingBottom()));
m_visualizerSpectrum->setPosition(0.0f, 0.0f);
m_visualizerSpectrum->setSize(innerWidth, innerHeight);
m_visualizerSpectrum->layout(renderer);
}
m_rootLayout->layout(renderer);
}
@@ -520,6 +523,9 @@ void MediaTab::update(Renderer& renderer) {
if (!m_active) {
return;
}
if (m_visualizerSpectrum != nullptr && m_spectrum != nullptr) {
m_visualizerSpectrum->setValues(m_spectrum->values());
}
refresh(renderer);
}
@@ -538,10 +544,10 @@ void MediaTab::onClose() {
m_rootLayout = nullptr;
m_mediaColumn = nullptr;
m_visualizerColumn = nullptr;
m_visualizerSpectrum = nullptr;
m_artwork = nullptr;
m_nowCard = nullptr;
m_mediaStack = nullptr;
m_visualizerCard = nullptr;
m_trackTitle = nullptr;
m_trackArtist = nullptr;
m_trackAlbum = nullptr;
+2 -1
View File
@@ -16,6 +16,7 @@ class MprisService;
class PipeWireSpectrum;
class Select;
class Slider;
class AudioSpectrum;
class MediaTab : public Tab {
public:
@@ -39,10 +40,10 @@ private:
Flex* m_rootLayout = nullptr;
Flex* m_mediaColumn = nullptr;
Flex* m_visualizerColumn = nullptr;
AudioSpectrum* m_visualizerSpectrum = nullptr;
Image* m_artwork = nullptr;
Flex* m_nowCard = nullptr;
Flex* m_mediaStack = nullptr;
Flex* m_visualizerCard = nullptr;
Label* m_trackTitle = nullptr;
Label* m_trackArtist = nullptr;
Label* m_trackAlbum = nullptr;
+145
View File
@@ -0,0 +1,145 @@
#include "ui/controls/audio_spectrum.h"
#include "render/core/color.h"
#include "ui/controls/box.h"
#include <algorithm>
#include <cmath>
#include <memory>
AudioSpectrum::AudioSpectrum() {
m_lowColor = hex("#ebbcba");
m_highColor = hex("#9ccfd8");
}
void AudioSpectrum::setValues(const std::vector<float>& values) {
m_values = values;
ensureBarCount(m_mirrored ? m_values.size() * 2 : m_values.size());
markDirty();
}
void AudioSpectrum::setGradient(const Color& lowColor, const Color& highColor) {
m_lowColor = lowColor;
m_highColor = highColor;
recolorBars();
}
void AudioSpectrum::setSpacingRatio(float ratio) {
const float clamped = std::max(0.0f, ratio);
if (m_spacingRatio == clamped) {
return;
}
m_spacingRatio = clamped;
markDirty();
}
void AudioSpectrum::setOrientation(AudioSpectrumOrientation orientation) {
if (m_orientation == orientation) {
return;
}
m_orientation = orientation;
markDirty();
}
void AudioSpectrum::setMirrored(bool mirrored) {
if (m_mirrored == mirrored) {
return;
}
m_mirrored = mirrored;
ensureBarCount(m_mirrored ? m_values.size() * 2 : m_values.size());
markDirty();
}
void AudioSpectrum::setCentered(bool centered) {
if (m_centered == centered) {
return;
}
m_centered = centered;
markDirty();
}
void AudioSpectrum::layout(Renderer& /*renderer*/) {
const int barCount = static_cast<int>(m_bars.size());
if (barCount <= 0 || width() <= 0.0f || height() <= 0.0f) {
return;
}
const float slotUnits = static_cast<float>(barCount) + m_spacingRatio * static_cast<float>(std::max(0, barCount - 1));
if (m_orientation == AudioSpectrumOrientation::Horizontal) {
const float barWidth = std::max(1.0f, std::floor(width() / std::max(1.0f, slotUnits)));
const float gap = std::floor(barWidth * m_spacingRatio);
const float usedWidth =
barWidth * static_cast<float>(barCount) + gap * static_cast<float>(std::max(0, barCount - 1));
float x = std::floor(std::max(0.0f, (width() - usedWidth) * 0.5f));
for (int i = 0; i < barCount; ++i) {
const int valueIndex =
m_mirrored ? (i < static_cast<int>(m_values.size()) ? static_cast<int>(m_values.size()) - 1 - i
: i - static_cast<int>(m_values.size()))
: i;
const float value =
valueIndex >= 0 && valueIndex < static_cast<int>(m_values.size())
? std::clamp(m_values[static_cast<std::size_t>(valueIndex)], 0.0f, 1.0f)
: 0.0f;
const float barHeight = std::floor(value * height() + 0.5f);
const float y = m_centered ? std::floor((height() - barHeight) * 0.5f) : std::floor(height() - barHeight);
if (auto* bar = m_bars[static_cast<std::size_t>(i)]; bar != nullptr) {
bar->setPosition(x, y);
bar->setSize(barWidth, barHeight);
}
x += barWidth + gap;
}
return;
}
const float barHeight = std::max(1.0f, std::floor(height() / std::max(1.0f, slotUnits)));
const float gap = std::floor(barHeight * m_spacingRatio);
const float usedHeight =
barHeight * static_cast<float>(barCount) + gap * static_cast<float>(std::max(0, barCount - 1));
float y = std::floor(std::max(0.0f, (height() - usedHeight) * 0.5f));
for (int i = 0; i < barCount; ++i) {
const int valueIndex =
m_mirrored ? (i < static_cast<int>(m_values.size()) ? static_cast<int>(m_values.size()) - 1 - i
: i - static_cast<int>(m_values.size()))
: i;
const float value =
valueIndex >= 0 && valueIndex < static_cast<int>(m_values.size())
? std::clamp(m_values[static_cast<std::size_t>(valueIndex)], 0.0f, 1.0f)
: 0.0f;
const float barWidth = std::floor(value * width() + 0.5f);
const float x = m_centered ? std::floor((width() - barWidth) * 0.5f) : std::floor(width() - barWidth);
if (auto* bar = m_bars[static_cast<std::size_t>(i)]; bar != nullptr) {
bar->setPosition(x, y);
bar->setSize(barWidth, barHeight);
}
y += barHeight + gap;
}
}
void AudioSpectrum::ensureBarCount(std::size_t count) {
while (m_bars.size() < count) {
auto bar = std::make_unique<Box>();
bar->setBorder(rgba(0, 0, 0, 0), 0.0f);
bar->setRadius(0.0f);
bar->setSoftness(0.0f);
m_bars.push_back(static_cast<Box*>(addChild(std::move(bar))));
}
while (m_bars.size() > count) {
removeChild(m_bars.back());
m_bars.pop_back();
}
recolorBars();
}
void AudioSpectrum::recolorBars() {
const std::size_t lastIndex = m_bars.empty() ? 0 : m_bars.size() - 1;
for (std::size_t i = 0; i < m_bars.size(); ++i) {
const float t = lastIndex == 0 ? 0.0f : static_cast<float>(i) / static_cast<float>(lastIndex);
if (auto* bar = m_bars[i]; bar != nullptr) {
bar->setFill(lerpColor(m_lowColor, m_highColor, t));
}
}
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "render/core/color.h"
#include "render/scene/node.h"
#include <cstdint>
#include <vector>
class Box;
class Renderer;
enum class AudioSpectrumOrientation : std::uint8_t {
Horizontal,
Vertical,
};
class AudioSpectrum : public Node {
public:
AudioSpectrum();
void setValues(const std::vector<float>& values);
void setGradient(const Color& lowColor, const Color& highColor);
void setSpacingRatio(float ratio);
void setOrientation(AudioSpectrumOrientation orientation);
void setMirrored(bool mirrored);
void setCentered(bool centered);
void layout(Renderer& renderer) override;
private:
void ensureBarCount(std::size_t count);
void recolorBars();
std::vector<float> m_values;
std::vector<Box*> m_bars;
Color m_lowColor = {};
Color m_highColor = {};
float m_spacingRatio = 0.5f;
AudioSpectrumOrientation m_orientation = AudioSpectrumOrientation::Horizontal;
bool m_mirrored = false;
bool m_centered = false;
};