实现一个现代桌面挂件的代码。它具备以下特性:

  1. 无边框与全透明:图片像直接贴在桌面上一样。
  2. 鼠标拖动:因为去掉了系统边框,我们手动实现点击图片即可拖拽。
  3. 圆角处理:利用 Shader 实时裁剪图片为圆角。
  4. 置顶显示:永远浮在其他窗口上方。

1. 准备工作

请确保目录下有一张名为 image.png 的图片,并创建一个 mask.fs 文件用于圆角裁剪:

mask.fs (圆角裁剪着色器)

OpenGL Shading Language

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#version 330
in vec2 fragTexCoord;
out vec4 finalColor;
uniform sampler2D texture0;
uniform float roundness; // 0.0 到 0.5

void main() {
vec2 p = fragTexCoord - vec2(0.5);
vec2 q = abs(p) - vec2(0.5 - roundness);
float dist = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - roundness;

vec4 texel = texture(texture0, fragTexCoord);
float alpha = 1.0 - smoothstep(-0.01, 0.01, dist);
finalColor = vec4(texel.rgb, texel.a * alpha);
}

2. 核心 C++ 代码

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include "raylib.h"

int main() {
// 1. 配置窗口:透明、无边框、置顶
SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED | FLAG_WINDOW_TOPMOST);
InitWindow(400, 400, "Desktop Widget");

// 2. 加载资源
Texture2D img = LoadTexture("bronyaMoe.png"); // 请确保图片路径正确
Shader maskShader = LoadShader(0, "mask.fs");

float roundness = 0.1f;
int roundLoc = GetShaderLocation(maskShader, "roundness");
SetShaderValue(maskShader, roundLoc, &roundness, SHADER_UNIFORM_FLOAT);

// 窗口拖拽逻辑变量
Vector2 dragOffset = { 0, 0 };
bool isDragging = false;

SetTargetFPS(60);

while (!WindowShouldClose()) {
// --- 逻辑区:手动实现窗口拖拽 ---
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
if (CheckCollisionPointRec(GetMousePosition(), Rectangle{ 0, 0, (float)GetScreenWidth(), (float)GetScreenHeight() })) {
isDragging = true;
dragOffset = GetMousePosition();
}
}

if (isDragging) {
Vector2 mousePos = GetWindowPosition();
Vector2 delta = GetMousePosition();
SetWindowPosition((int)(mousePos.x + (delta.x - dragOffset.x)),
(int)(mousePos.y + (delta.y - dragOffset.y)));

if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) isDragging = false;
}

// --- 渲染区 ---
BeginDrawing();
ClearBackground(BLANK); // 关键:让窗口背景变透明,露出桌面

// 使用 Shader 绘制带圆角的图片
BeginShaderMode(maskShader);
// 将图片拉伸填充到窗口大小
DrawTexturePro(img,
Rectangle{ 0, 0, (float)img.width, (float)img.height },
Rectangle{ 0, 0, (float)GetScreenWidth(), (float)GetScreenHeight() },
Vector2{ 0, 0 }, 0.0f, WHITE);
EndShaderMode();

// 悬停时显示一个淡淡的边框提示
if (CheckCollisionPointRec(GetMousePosition(), Rectangle{ 0, 0, (float)GetScreenWidth(), (float)GetScreenHeight() })) {
DrawRectangleRoundedLines(Rectangle{ 0, 0, (float)GetScreenWidth(), (float)GetScreenHeight() }, 0.1f, 16, SKYBLUE);
}

EndDrawing();
}

// 释放资源
UnloadTexture(img);
UnloadShader(maskShader);
CloseWindow();

return 0;
}

3. 代码要点解析

  • 透明穿透ClearBackground(BLANK) 配合 FLAG_WINDOW_TRANSPARENT 是实现“挂件感”的核心。
  • Shader 裁剪:普通的 DrawTexture 只能画矩形。通过 mask.fs,根据像素到中心的距离动态计算 Alpha 值,实现了抗锯齿的圆角效果。
  • 窗口坐标同步:由于是无边框窗口,我们利用 GetWindowPosition() 和鼠标偏移量来更新窗口位置。这使得图片可以在桌面上被随意拖动。

4. 进阶方向

可以尝试以下改进:

  1. 鼠标穿透:如果希望图片“看得见摸不着”(点击图片能点到后面的图标),需要在 WindowEffects 里通过 Win32 API 给窗口添加 WS_EX_TRANSPARENT 属性。
  2. 右键退出:目前没有关闭按钮,可以添加:if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) break; 来快速退出。

让鼠标忽略图片的空白部分

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include "raylib.h"

int main() {
// 1. 配置透明置顶窗口
SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED | FLAG_WINDOW_TOPMOST);
InitWindow(400, 400, "Desktop Pet");

// 2. 加载资源
// 关键点:我们需要同时加载 Image (CPU内存) 和 Texture (显存)
Image petImage = LoadImage("bronyaMoe.png"); // 保留在内存里查像素
Texture2D img = LoadTextureFromImage(petImage); // 转成显存纹理画图

// 如果没有圆角需求,可以不加载 Shader;如果只是不规则图片,靠图片本身的 Alpha 即可
Shader maskShader = LoadShader(0, "mask.fs");

Vector2 dragOffset = { 0, 0 };
bool isDragging = false;

SetTargetFPS(60);

while (!WindowShouldClose()) {
Vector2 mousePos = GetMousePosition();

// --- 逻辑区:带像素检测的点击 ---
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
// 首先判断是否在窗口矩形内
if (CheckCollisionPointRec(mousePos, Rectangle{ 0, 0, (float)GetScreenWidth(), (float)GetScreenHeight() })) {

// 关键点:将鼠标在窗口的坐标映射回图片的实际像素坐标
int mapX = (int)(mousePos.x * (petImage.width / (float)GetScreenWidth()));
int mapY = (int)(mousePos.y * (petImage.height / (float)GetScreenHeight()));

// 检查该点像素是否透明
if (mapX >= 0 && mapX < petImage.width && mapY >= 0 && mapY < petImage.height) {
Color pixel = GetImageColor(petImage, mapX, mapY); //

if (pixel.a > 50) { // 只有点到不透明的地方才触发拖拽
isDragging = true;
dragOffset = mousePos;
}
}
}
}

if (isDragging) {
Vector2 winPos = GetWindowPosition();
Vector2 delta = GetMousePosition();
SetWindowPosition((int)(winPos.x + (delta.x - dragOffset.x)),
(int)(winPos.y + (delta.y - dragOffset.y)));
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) isDragging = false;
}

// --- 渲染区 ---
BeginDrawing();
ClearBackground(BLANK); //

// 绘制宠物
BeginShaderMode(maskShader);
DrawTexturePro(img,
Rectangle{ 0, 0, (float)img.width, (float)img.height },
Rectangle{ 0, 0, (float)GetScreenWidth(), (float)GetScreenHeight() },
Vector2{ 0, 0 }, 0.0f, WHITE);
EndShaderMode();

EndDrawing();
}

// 释放资源
UnloadImage(petImage);
UnloadTexture(img);
UnloadShader(maskShader);
CloseWindow();

return 0;
}