mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
feat(shadow): testing bar shadows
This commit is contained in:
@@ -170,8 +170,11 @@ gdbus call --session --dest dev.noctalia.Debug --object-path /dev/noctalia/Debug
|
||||
|
||||
- [x] System tray
|
||||
- [~] Keyboard Input
|
||||
- [ ] Audio service (Pipewire) + volume OSD
|
||||
- [ ] Notification: indicator + popup + history panel
|
||||
- [x] Audio service (Pipewire)
|
||||
- [ ] Volume OSD
|
||||
- [x] Notification: indicator
|
||||
- [x] Notification: popup
|
||||
- [] Notification: history panel
|
||||
|
||||
### Hardware and networking
|
||||
|
||||
|
||||
@@ -181,6 +181,8 @@ void ConfigService::loadFromFile(const std::string& path) {
|
||||
bar.paddingH = static_cast<std::int32_t>(*v);
|
||||
if (auto v = (*barTbl)["widget_spacing"].value<int64_t>())
|
||||
bar.widgetSpacing = static_cast<std::int32_t>(*v);
|
||||
if (auto v = (*barTbl)["shadow_size"].value<int64_t>())
|
||||
bar.shadowSize = static_cast<std::int32_t>(*v);
|
||||
if (auto* n = (*barTbl)["start"].as_array())
|
||||
bar.startWidgets = readStringArray(*n);
|
||||
if (auto* n = (*barTbl)["center"].as_array())
|
||||
|
||||
@@ -26,13 +26,15 @@ struct BarConfig {
|
||||
std::string position = "top";
|
||||
bool enabled = true;
|
||||
std::int32_t height = Style::barHeightDefault;
|
||||
std::int32_t marginH = 0; // horizontal compositor margin (left = right = marginH)
|
||||
std::int32_t marginV = 0; // vertical compositor margin (gap between bar and screen edge)
|
||||
std::int32_t radius = Style::radiusXl;
|
||||
std::int32_t marginH = 12; // horizontal compositor margin (left = right = marginH)
|
||||
std::int32_t marginV = 6; // vertical compositor margin (gap between bar and screen edge)
|
||||
std::int32_t paddingH = 16; // horizontal padding from bar edges to start/end sections
|
||||
std::int32_t widgetSpacing = 8; // gap between widgets within a section
|
||||
std::int32_t shadowSize = 16; // shadow depth in pixels (0 = no shadow)
|
||||
std::vector<std::string> startWidgets = {};
|
||||
std::vector<std::string> centerWidgets = {"workspaces"};
|
||||
std::vector<std::string> endWidgets = {"test", "tray", "volume", "notifications", "clock"};
|
||||
std::vector<std::string> endWidgets = {"test", "tray", "notifications", "volume", "clock"};
|
||||
std::vector<BarMonitorOverride> monitorOverrides;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,16 +44,20 @@ uniform vec4 u_fill_end_color;
|
||||
uniform vec4 u_border_color;
|
||||
uniform int u_fill_mode;
|
||||
uniform vec2 u_gradient_direction;
|
||||
uniform float u_radius;
|
||||
uniform vec4 u_radii; // tl, tr, br, bl
|
||||
uniform float u_softness;
|
||||
uniform float u_border_width;
|
||||
varying vec2 v_pixel;
|
||||
|
||||
float rounded_rect_distance(vec2 point, vec2 size, float radius) {
|
||||
float rounded_rect_distance(vec2 point, vec2 size, vec4 radii) {
|
||||
vec2 half_size = size * 0.5;
|
||||
vec2 centered = point - half_size;
|
||||
vec2 q = abs(centered) - (half_size - vec2(radius));
|
||||
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
||||
// Select per-corner radius based on quadrant
|
||||
float r = centered.x < 0.0
|
||||
? (centered.y < 0.0 ? radii.x : radii.w)
|
||||
: (centered.y < 0.0 ? radii.y : radii.z);
|
||||
vec2 q = abs(centered) - (half_size - vec2(r));
|
||||
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
|
||||
}
|
||||
|
||||
void main() {
|
||||
@@ -61,13 +65,13 @@ void main() {
|
||||
vec2 local_point = v_pixel - u_rect.xy;
|
||||
vec2 uv = clamp(local_point / u_rect.zw, vec2(0.0), vec2(1.0));
|
||||
|
||||
float outer_distance = rounded_rect_distance(local_point, u_rect.zw, u_radius);
|
||||
float outer_distance = rounded_rect_distance(local_point, u_rect.zw, u_radii);
|
||||
float outer_coverage = 1.0 - smoothstep(-aa, aa, outer_distance);
|
||||
|
||||
float inner_radius = max(u_radius - u_border_width, 0.0);
|
||||
vec4 inner_radii = max(u_radii - vec4(u_border_width), vec4(0.0));
|
||||
vec2 inner_size = max(u_rect.zw - vec2(u_border_width * 2.0), vec2(0.0));
|
||||
vec2 inner_point = local_point - vec2(u_border_width);
|
||||
float inner_distance = rounded_rect_distance(inner_point, inner_size, inner_radius);
|
||||
float inner_distance = rounded_rect_distance(inner_point, inner_size, inner_radii);
|
||||
float inner_coverage = 1.0 - smoothstep(-aa, aa, inner_distance);
|
||||
|
||||
float gradient_t = clamp(dot(uv, u_gradient_direction), 0.0, 1.0);
|
||||
@@ -114,7 +118,7 @@ void RoundedRectProgram::ensureInitialized() {
|
||||
m_borderColorLocation = glGetUniformLocation(m_program.id(), "u_border_color");
|
||||
m_fillModeLocation = glGetUniformLocation(m_program.id(), "u_fill_mode");
|
||||
m_gradientDirectionLocation = glGetUniformLocation(m_program.id(), "u_gradient_direction");
|
||||
m_radiusLocation = glGetUniformLocation(m_program.id(), "u_radius");
|
||||
m_radiiLocation = glGetUniformLocation(m_program.id(), "u_radii");
|
||||
m_softnessLocation = glGetUniformLocation(m_program.id(), "u_softness");
|
||||
m_borderWidthLocation = glGetUniformLocation(m_program.id(), "u_border_width");
|
||||
|
||||
@@ -123,7 +127,7 @@ void RoundedRectProgram::ensureInitialized() {
|
||||
|
||||
if (m_positionLocation < 0 || m_surfaceSizeLocation < 0 || m_quadRectLocation < 0 || m_rectLocation < 0 ||
|
||||
m_colorLocation < 0 || m_fillEndColorLocation < 0 || m_borderColorLocation < 0 || m_fillModeLocation < 0 ||
|
||||
m_gradientDirectionLocation < 0 || m_radiusLocation < 0 || m_softnessLocation < 0 ||
|
||||
m_gradientDirectionLocation < 0 || m_radiiLocation < 0 || m_softnessLocation < 0 ||
|
||||
m_borderWidthLocation < 0 || m_rotationLocation < 0 || m_scaleLocation < 0) {
|
||||
throw std::runtime_error("failed to query rounded-rect shader locations");
|
||||
}
|
||||
@@ -140,7 +144,7 @@ void RoundedRectProgram::destroy() {
|
||||
m_borderColorLocation = -1;
|
||||
m_fillModeLocation = -1;
|
||||
m_gradientDirectionLocation = -1;
|
||||
m_radiusLocation = -1;
|
||||
m_radiiLocation = -1;
|
||||
m_softnessLocation = -1;
|
||||
m_borderWidthLocation = -1;
|
||||
m_rotationLocation = -1;
|
||||
@@ -173,7 +177,7 @@ void RoundedRectProgram::draw(float surfaceWidth, float surfaceHeight, float x,
|
||||
glUniform1i(m_fillModeLocation, style.fillMode == FillMode::Solid ? 0 : 1);
|
||||
glUniform2f(m_gradientDirectionLocation, style.gradientDirection == GradientDirection::Horizontal ? 1.0f : 0.0f,
|
||||
style.gradientDirection == GradientDirection::Vertical ? 1.0f : 0.0f);
|
||||
glUniform1f(m_radiusLocation, style.radius);
|
||||
glUniform4f(m_radiiLocation, style.radius.tl, style.radius.tr, style.radius.br, style.radius.bl);
|
||||
glUniform1f(m_softnessLocation, style.softness);
|
||||
glUniform1f(m_borderWidthLocation, style.borderWidth);
|
||||
glUniform1f(m_rotationLocation, rotation);
|
||||
|
||||
@@ -15,13 +15,27 @@ enum class GradientDirection {
|
||||
Vertical,
|
||||
};
|
||||
|
||||
// Per-corner radii: top-left, top-right, bottom-right, bottom-left.
|
||||
// Implicit construction from a single float sets all four corners uniformly,
|
||||
// so existing `.radius = value` assignments continue to compile unchanged.
|
||||
struct Radii {
|
||||
float tl = 0.0f;
|
||||
float tr = 0.0f;
|
||||
float br = 0.0f;
|
||||
float bl = 0.0f;
|
||||
|
||||
Radii() = default;
|
||||
/* implicit */ Radii(float r) : tl(r), tr(r), br(r), bl(r) {} // NOLINT(google-explicit-constructor)
|
||||
Radii(float tl, float tr, float br, float bl) : tl(tl), tr(tr), br(br), bl(bl) {}
|
||||
};
|
||||
|
||||
struct RoundedRectStyle {
|
||||
Color fill{};
|
||||
Color fillEnd{};
|
||||
Color border{};
|
||||
FillMode fillMode = FillMode::Solid;
|
||||
GradientDirection gradientDirection = GradientDirection::Horizontal;
|
||||
float radius = 0.0f;
|
||||
Radii radius{};
|
||||
float softness = 1.0f;
|
||||
float borderWidth = 0.0f;
|
||||
};
|
||||
@@ -51,7 +65,7 @@ private:
|
||||
GLint m_borderColorLocation = -1;
|
||||
GLint m_fillModeLocation = -1;
|
||||
GLint m_gradientDirectionLocation = -1;
|
||||
GLint m_radiusLocation = -1;
|
||||
GLint m_radiiLocation = -1;
|
||||
GLint m_softnessLocation = -1;
|
||||
GLint m_borderWidthLocation = -1;
|
||||
GLint m_rotationLocation = -1;
|
||||
|
||||
+107
-19
@@ -4,6 +4,7 @@
|
||||
#include "core/log.h"
|
||||
#include "dbus/tray/tray_service.h"
|
||||
#include "render/render_context.h"
|
||||
#include "render/scene/rect_node.h"
|
||||
#include "ui/controls/box.h"
|
||||
#include "shell/widget/widget.h"
|
||||
#include "time/time_service.h"
|
||||
@@ -160,19 +161,23 @@ void Bar::createInstance(const WaylandOutput& output, const BarConfig& barConfig
|
||||
const std::int32_t mV = barConfig.marginV;
|
||||
const std::int32_t totalExclusive = barConfig.height + mV;
|
||||
const auto uHeight = static_cast<std::uint32_t>(barConfig.height);
|
||||
const auto uShadow = static_cast<std::uint32_t>(std::max(0, barConfig.shadowSize));
|
||||
|
||||
// Surface is expanded by shadowSize in the away-from-edge direction so the
|
||||
// shadow can bleed into the window area. The exclusive zone is NOT expanded,
|
||||
// so windows keep the same reserved gap.
|
||||
auto surfaceConfig = LayerSurfaceConfig{
|
||||
.nameSpace = "noctalia-" + barConfig.name,
|
||||
.layer = LayerShellLayer::Top,
|
||||
.anchor = anchor,
|
||||
.width = vertical ? uHeight : 0,
|
||||
.height = vertical ? 0u : uHeight,
|
||||
.width = vertical ? uHeight + uShadow : 0,
|
||||
.height = vertical ? 0u : uHeight + uShadow,
|
||||
.exclusiveZone = totalExclusive,
|
||||
.marginTop = (barConfig.position == "top") ? mV : 0,
|
||||
.marginRight = mH,
|
||||
.marginBottom = (barConfig.position == "bottom") ? mV : 0,
|
||||
.marginLeft = mH,
|
||||
.defaultHeight = vertical ? 0u : uHeight,
|
||||
.defaultHeight = vertical ? 0u : uHeight + uShadow,
|
||||
};
|
||||
|
||||
instance->surface = std::make_unique<LayerSurface>(*m_wayland, std::move(surfaceConfig));
|
||||
@@ -223,6 +228,18 @@ void Bar::buildScene(BarInstance& instance, std::uint32_t width, std::uint32_t h
|
||||
const auto h = static_cast<float>(height);
|
||||
const float paddingH = static_cast<float>(instance.barConfig.paddingH);
|
||||
const float widgetSpacing = static_cast<float>(instance.barConfig.widgetSpacing);
|
||||
const float shadowSize = static_cast<float>(std::max(0, instance.barConfig.shadowSize));
|
||||
const float barH = static_cast<float>(instance.barConfig.height);
|
||||
const float radius = static_cast<float>(instance.barConfig.radius);
|
||||
const bool isBottom = instance.barConfig.position == "bottom";
|
||||
const bool isRight = instance.barConfig.position == "right";
|
||||
const bool isVertical = (instance.barConfig.position == "left" || instance.barConfig.position == "right");
|
||||
|
||||
// Coordinates of the bar's visual area within the (possibly taller/wider) surface
|
||||
const float barAreaX = isRight ? shadowSize : 0.0f;
|
||||
const float barAreaY = isBottom ? shadowSize : 0.0f;
|
||||
const float barAreaW = isVertical ? barH : w;
|
||||
const float barAreaH = isVertical ? h : barH;
|
||||
|
||||
if (instance.sceneRoot == nullptr) {
|
||||
instance.sceneRoot = std::make_unique<Node>();
|
||||
@@ -232,8 +249,15 @@ void Bar::buildScene(BarInstance& instance, std::uint32_t width, std::uint32_t h
|
||||
// Bar background
|
||||
auto bg = std::make_unique<Box>();
|
||||
bg->setFlatStyle();
|
||||
bg->setRadius(radius);
|
||||
instance.bg = instance.sceneRoot->addChild(std::move(bg));
|
||||
|
||||
// Shadow — gradient rect that bleeds into the window area
|
||||
if (shadowSize > 0.0f) {
|
||||
auto shadow = std::make_unique<RectNode>();
|
||||
instance.shadow = static_cast<RectNode*>(instance.sceneRoot->addChild(std::move(shadow)));
|
||||
}
|
||||
|
||||
// Create section boxes
|
||||
auto makeSection = [widgetSpacing]() {
|
||||
auto box = std::make_unique<Flex>();
|
||||
@@ -283,11 +307,66 @@ void Bar::buildScene(BarInstance& instance, std::uint32_t width, std::uint32_t h
|
||||
// Update root size on reconfigure
|
||||
instance.sceneRoot->setSize(w, h);
|
||||
|
||||
// Background fills the full surface — floating gap is handled by compositor margins.
|
||||
// Expand 1px beyond the surface on each side so SDF edge pixels land well inside
|
||||
// the rect (distance ≈ -1 < -aa=0.85), giving full coverage with no fringe.
|
||||
instance.bg->setPosition(-1.0f, -1.0f);
|
||||
instance.bg->setSize(w + 2.0f, h + 2.0f);
|
||||
// Background covers only the bar visual area (not the shadow extension).
|
||||
// Expand 1px beyond its edges so SDF fringe lands inside the rect.
|
||||
instance.bg->setPosition(barAreaX - 1.0f, barAreaY - 1.0f);
|
||||
instance.bg->setSize(barAreaW + 2.0f, barAreaH + 2.0f);
|
||||
|
||||
// Shadow — positioned flush against the bar's outer edge, with the two adjacent
|
||||
// corners matching the bar's radius so the shadow silhouette follows the bar shape.
|
||||
// Rendered behind the bar (z=-1) so the bar covers the rounded-corner junction.
|
||||
if (instance.shadow != nullptr) {
|
||||
const Color dark = rgba(0.0f, 0.0f, 0.0f, 0.35f);
|
||||
const Color clear = rgba(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
// Corner radii: the two corners adjacent to the bar carry the bar's radius;
|
||||
// the far corners are 0 so the shadow tapers cleanly to a straight edge.
|
||||
Radii shadowRadii{};
|
||||
if (!isVertical) {
|
||||
if (isBottom) {
|
||||
// Shadow above bottom bar — bottom corners match bar's top corners
|
||||
shadowRadii = Radii{0, 0, radius, radius};
|
||||
} else {
|
||||
// Shadow below top bar — top corners match bar's bottom corners
|
||||
shadowRadii = Radii{radius, radius, 0, 0};
|
||||
}
|
||||
} else {
|
||||
if (isRight) {
|
||||
// Shadow left of right bar — right corners match bar's left corners
|
||||
shadowRadii = Radii{0, radius, radius, 0};
|
||||
} else {
|
||||
// Shadow right of left bar — left corners match bar's right corners
|
||||
shadowRadii = Radii{radius, 0, 0, radius};
|
||||
}
|
||||
}
|
||||
|
||||
RoundedRectStyle shadowStyle{
|
||||
.fillMode = FillMode::LinearGradient,
|
||||
.gradientDirection = isVertical ? GradientDirection::Horizontal : GradientDirection::Vertical,
|
||||
.radius = shadowRadii,
|
||||
.softness = 0.0f,
|
||||
};
|
||||
shadowStyle.border = clear; // default border is opaque — zero it out
|
||||
if (isBottom || isRight) {
|
||||
shadowStyle.fill = clear;
|
||||
shadowStyle.fillEnd = dark;
|
||||
} else {
|
||||
shadowStyle.fill = dark;
|
||||
shadowStyle.fillEnd = clear;
|
||||
}
|
||||
instance.shadow->setStyle(shadowStyle);
|
||||
instance.shadow->setZIndex(-1);
|
||||
|
||||
if (isVertical) {
|
||||
const float shadowX = isRight ? 0.0f : barH;
|
||||
instance.shadow->setPosition(shadowX, 0.0f);
|
||||
instance.shadow->setSize(shadowSize, h);
|
||||
} else {
|
||||
const float shadowY = isBottom ? 0.0f : barH;
|
||||
instance.shadow->setPosition(0.0f, shadowY);
|
||||
instance.shadow->setSize(w, shadowSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Layout widgets
|
||||
auto layoutWidgets = [&](std::vector<std::unique_ptr<Widget>>& widgets) {
|
||||
@@ -304,16 +383,16 @@ void Bar::buildScene(BarInstance& instance, std::uint32_t width, std::uint32_t h
|
||||
instance.centerSection->layout(*renderer);
|
||||
instance.endSection->layout(*renderer);
|
||||
|
||||
// Position sections
|
||||
const float contentY = (h - instance.startSection->height()) * 0.5f;
|
||||
// Position sections — centre within the bar visual area, not the full surface
|
||||
const float contentY = barAreaY + (barAreaH - instance.startSection->height()) * 0.5f;
|
||||
instance.startSection->setPosition(paddingH, contentY);
|
||||
|
||||
const float centerX = (w - instance.centerSection->width()) * 0.5f;
|
||||
const float centerY = (h - instance.centerSection->height()) * 0.5f;
|
||||
const float centerX = barAreaX + (barAreaW - instance.centerSection->width()) * 0.5f;
|
||||
const float centerY = barAreaY + (barAreaH - instance.centerSection->height()) * 0.5f;
|
||||
instance.centerSection->setPosition(centerX, centerY);
|
||||
|
||||
const float endX = w - instance.endSection->width() - paddingH;
|
||||
const float endY = (h - instance.endSection->height()) * 0.5f;
|
||||
const float endX = barAreaX + barAreaW - instance.endSection->width() - paddingH;
|
||||
const float endY = barAreaY + (barAreaH - instance.endSection->height()) * 0.5f;
|
||||
instance.endSection->setPosition(endX, endY);
|
||||
}
|
||||
|
||||
@@ -326,6 +405,15 @@ void Bar::updateWidgets(BarInstance& instance) {
|
||||
const auto w = static_cast<float>(instance.surface->width());
|
||||
const auto h = static_cast<float>(instance.surface->height());
|
||||
const float paddingH = static_cast<float>(instance.barConfig.paddingH);
|
||||
const float shadowSize = static_cast<float>(std::max(0, instance.barConfig.shadowSize));
|
||||
const float barH = static_cast<float>(instance.barConfig.height);
|
||||
const bool isBottom = instance.barConfig.position == "bottom";
|
||||
const bool isRight = instance.barConfig.position == "right";
|
||||
const bool isVertical = (instance.barConfig.position == "left" || instance.barConfig.position == "right");
|
||||
const float barAreaX = isRight ? shadowSize : 0.0f;
|
||||
const float barAreaY = isBottom ? shadowSize : 0.0f;
|
||||
const float barAreaW = isVertical ? barH : w;
|
||||
const float barAreaH = isVertical ? h : barH;
|
||||
|
||||
auto updateSection = [&](std::vector<std::unique_ptr<Widget>>& widgets, Flex* section) {
|
||||
bool changed = false;
|
||||
@@ -347,15 +435,15 @@ void Bar::updateWidgets(BarInstance& instance) {
|
||||
|
||||
// Reposition sections if sizes changed
|
||||
if (instance.startSection->dirty() || instance.centerSection->dirty() || instance.endSection->dirty()) {
|
||||
const float contentY = (h - instance.startSection->height()) * 0.5f;
|
||||
const float contentY = barAreaY + (barAreaH - instance.startSection->height()) * 0.5f;
|
||||
instance.startSection->setPosition(paddingH, contentY);
|
||||
|
||||
const float centerX = (w - instance.centerSection->width()) * 0.5f;
|
||||
const float centerY = (h - instance.centerSection->height()) * 0.5f;
|
||||
const float centerX = barAreaX + (barAreaW - instance.centerSection->width()) * 0.5f;
|
||||
const float centerY = barAreaY + (barAreaH - instance.centerSection->height()) * 0.5f;
|
||||
instance.centerSection->setPosition(centerX, centerY);
|
||||
|
||||
const float endX = w - instance.endSection->width() - paddingH;
|
||||
const float endY = (h - instance.endSection->height()) * 0.5f;
|
||||
const float endX = barAreaX + barAreaW - instance.endSection->width() - paddingH;
|
||||
const float endY = barAreaY + (barAreaH - instance.endSection->height()) * 0.5f;
|
||||
instance.endSection->setPosition(endX, endY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
class Flex;
|
||||
class Node;
|
||||
class RectNode;
|
||||
|
||||
struct BarInstance {
|
||||
std::uint32_t outputName = 0;
|
||||
@@ -25,8 +26,9 @@ struct BarInstance {
|
||||
AnimationManager animations;
|
||||
InputDispatcher inputDispatcher;
|
||||
|
||||
// Bar background and layout sections (start/center/end along main axis)
|
||||
// Bar background, shadow, and layout sections (start/center/end along main axis)
|
||||
Node* bg = nullptr;
|
||||
RectNode* shadow = nullptr;
|
||||
Flex* startSection = nullptr;
|
||||
Flex* centerSection = nullptr;
|
||||
Flex* endSection = nullptr;
|
||||
|
||||
@@ -9,6 +9,7 @@ inline constexpr int barHeightDefault = 34;
|
||||
inline constexpr int radiusSm = 2;
|
||||
inline constexpr int radiusMd = 5;
|
||||
inline constexpr int radiusLg = 8;
|
||||
inline constexpr int radiusXl = 12;
|
||||
inline constexpr int radiusFull = 9999;
|
||||
|
||||
inline constexpr int borderWidth = 1;
|
||||
|
||||
Reference in New Issue
Block a user