Skip to content

Commit de7108b

Browse files
feat(shell): self drawn border & shadow
close #201
1 parent 68ce0bc commit de7108b

File tree

7 files changed

+272
-25
lines changed

7 files changed

+272
-25
lines changed

src/inject/inject.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,7 @@ struct breeze_icon : public ui::widget {
573573
if (!image) {
574574
image = nvgCreateImageMem(ctx.ctx, 0, g_icon_png, sizeof(g_icon_png));
575575
}
576-
auto paint = nvgImagePattern(ctx.ctx, *x + ctx.offset_x, *y + ctx.offset_y,
577-
*height, *height, 0, *image, 1);
576+
auto paint = ctx.imagePattern(*x, *y, *height, *height, 0, *image, 1);
578577
ctx.fillPaint(paint);
579578
ctx.fillRect(*x, *y, *height, *height);
580579
}

src/shell/config.cc

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@
1313
#include "utils.h"
1414
#include "windows.h"
1515

16+
namespace rfl {
17+
template <>
18+
struct Reflector<mb_shell::config::paint_color> {
19+
using ReflType = std::string;
20+
21+
static mb_shell::config::paint_color to(const ReflType& v) noexcept {
22+
return mb_shell::config::paint_color::from_string(v);
23+
}
24+
25+
static ReflType from(const mb_shell::config::paint_color& v) {
26+
return v.to_string();
27+
}
28+
};
29+
}
30+
1631
namespace mb_shell {
1732
std::unique_ptr<config> config::current;
1833
config::animated_float_conf config::_default_animation{
@@ -138,4 +153,89 @@ std::filesystem::path config::default_fallback_font() {
138153
return std::filesystem::path(env("WINDIR").value()) / "Fonts" / "msyh.ttc";
139154
}
140155
std::string config::dump_config() { return rfl::json::write(*config::current); }
156+
config::paint_color config::paint_color::from_string(const std::string &str) {
157+
paint_color res;
158+
// Trim whitespace
159+
std::string trimmed = str;
160+
trimmed.erase(0, trimmed.find_first_not_of(" \t\n\r"));
161+
trimmed.erase(trimmed.find_last_not_of(" \t\n\r") + 1);
162+
163+
if (trimmed.starts_with("solid(") && trimmed.ends_with(")")) {
164+
std::string color_str = trimmed.substr(6, trimmed.length() - 7);
165+
res.type = type::solid;
166+
res.color = parse_color(color_str);
167+
} else if (trimmed.starts_with("linear-gradient(") &&
168+
trimmed.ends_with(")")) {
169+
std::string params = trimmed.substr(16, trimmed.length() - 17);
170+
171+
// Split by commas
172+
std::vector<std::string> parts;
173+
size_t start = 0, end = 0;
174+
while ((end = params.find(',', start)) != std::string::npos) {
175+
std::string part = params.substr(start, end - start);
176+
part.erase(0, part.find_first_not_of(" \t"));
177+
part.erase(part.find_last_not_of(" \t") + 1);
178+
parts.push_back(part);
179+
start = end + 1;
180+
}
181+
std::string last_part = params.substr(start);
182+
last_part.erase(0, last_part.find_first_not_of(" \t"));
183+
last_part.erase(last_part.find_last_not_of(" \t") + 1);
184+
parts.push_back(last_part);
185+
186+
if (parts.size() >= 3) {
187+
res.type = type::linear_gradient;
188+
res.angle = std::stof(parts[0]) * std::numbers::pi /
189+
180.0f; // Convert degrees to radians
190+
res.color = parse_color(parts[1]);
191+
res.color2 = parse_color(parts[2]);
192+
}
193+
} else if (trimmed.starts_with("radial-gradient(") &&
194+
trimmed.ends_with(")")) {
195+
std::string params = trimmed.substr(16, trimmed.length() - 17);
196+
197+
// Split by commas
198+
std::vector<std::string> parts;
199+
size_t start = 0, end = 0;
200+
while ((end = params.find(',', start)) != std::string::npos) {
201+
std::string part = params.substr(start, end - start);
202+
part.erase(0, part.find_first_not_of(" \t"));
203+
part.erase(part.find_last_not_of(" \t") + 1);
204+
parts.push_back(part);
205+
start = end + 1;
206+
}
207+
std::string last_part = params.substr(start);
208+
last_part.erase(0, last_part.find_first_not_of(" \t"));
209+
last_part.erase(last_part.find_last_not_of(" \t") + 1);
210+
parts.push_back(last_part);
211+
212+
if (parts.size() >= 3) {
213+
res.type = type::radial_gradient;
214+
res.radius = std::stof(parts[0]);
215+
res.color = parse_color(parts[1]);
216+
res.color2 = parse_color(parts[2]);
217+
res.radius2 = res.radius * 2; // Default outer radius
218+
}
219+
} else {
220+
// Default to solid color
221+
res.type = type::solid;
222+
res.color = parse_color(trimmed);
223+
}
224+
225+
return res;
226+
}
227+
std::string config::paint_color::to_string() const {
228+
switch (type) {
229+
case type::solid:
230+
return "solid(" + format_color(color) + ")";
231+
case type::linear_gradient:
232+
return "linear-gradient(" +
233+
std::to_string(angle * 180.0f / std::numbers::pi) + ", " +
234+
format_color(color) + ", " + format_color(color2) + ")";
235+
case type::radial_gradient:
236+
return "radial-gradient(" + std::to_string(radius) + ", " +
237+
format_color(color) + ", " + format_color(color2) + ")";
238+
}
239+
return "";
240+
}
141241
} // namespace mb_shell

src/shell/config.h

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
#pragma once
22

33
#include "animator.h"
4+
#include "nanovg.h"
5+
#include "nanovg_wrapper.h"
6+
#include "utils.h"
47
#include <filesystem>
58
#include <memory>
9+
#include <numbers>
610
#include <vector>
11+
712
namespace mb_shell {
813

914
struct config {
@@ -18,6 +23,58 @@ struct config {
1823
void apply_to(ui::sp_anim_float &anim, float delay = 0);
1924
void operator()(ui::sp_anim_float &anim, float delay = 0);
2025
} default_animation;
26+
27+
struct paint_color {
28+
enum class type {
29+
solid,
30+
linear_gradient,
31+
radial_gradient
32+
} type = type::solid;
33+
NVGcolor color = parse_color("#000000");
34+
NVGcolor color2 = parse_color("#000000");
35+
float radius = 0;
36+
float radius2 = 0;
37+
float angle = 0;
38+
void apply_to_ctx(ui::nanovg_context &ctx, float x, float y, float width,
39+
float height) const {
40+
41+
switch (type) {
42+
case type::solid: {
43+
ctx.fillColor(color);
44+
ctx.strokeColor(color);
45+
return;
46+
}
47+
case type::linear_gradient: {
48+
auto paint = ctx.linearGradient(x, y, x + width * cos(angle),
49+
y + height * sin(angle), color, color2);
50+
ctx.fillPaint(paint);
51+
ctx.strokePaint(paint);
52+
return;
53+
}
54+
case type::radial_gradient: {
55+
auto paint = ctx.radialGradient(x + width / 2, y + height / 2, radius,
56+
radius2, color, color2);
57+
ctx.fillPaint(paint);
58+
ctx.strokePaint(paint);
59+
return;
60+
}
61+
}
62+
throw std::runtime_error("Unknown paint color type");
63+
}
64+
65+
// supported patterns:
66+
// #RRGGBBAA
67+
// #RRGGBB
68+
// #RRGGBB00
69+
// rgba(R, G, B, A)
70+
// rgb(R, G, B)
71+
// linear-gradient(angle, color1, color2)
72+
// radial-gradient(radius, color1, color2)
73+
// solid(color)
74+
static paint_color from_string(const std::string &str);
75+
std::string to_string() const;
76+
};
77+
2178
static animated_float_conf _default_animation;
2279
struct context_menu {
2380
struct theme {
@@ -41,6 +98,22 @@ struct config {
4198
std::string acrylic_color_light = "#fefefe00";
4299
std::string acrylic_color_dark = "#28282800";
43100

101+
bool use_self_drawn_border = false;
102+
// These values are used when use_self_drawn_border is true
103+
paint_color border_color_light = paint_color::from_string("#00000022");
104+
paint_color border_color_dark = paint_color::from_string("#ffffff22");
105+
std::string shadow_color_light_from = "#00000020";
106+
std::string shadow_color_light_to = "#00000000";
107+
std::string shadow_color_dark_from = "#00000033";
108+
std::string shadow_color_dark_to = "#00000000";
109+
float shadow_blur = 10;
110+
float shadow_offset_x = 0;
111+
float shadow_offset_y = 0;
112+
float shadow_opacity = 0.2;
113+
float shadow_size = 10;
114+
float border_width = 1;
115+
bool inset_border = false;
116+
44117
// unused, only for backward compatibility
45118
float acrylic_opacity = 0.1;
46119

@@ -80,7 +153,7 @@ struct config {
80153
bool res_string_loader_use_hook = false;
81154
bool avoid_resize_ui = false;
82155
std::vector<std::string> plugin_load_order = {};
83-
156+
84157
std::string $schema;
85158
static std::unique_ptr<config> current;
86159
static void read_config();

src/shell/contextmenu/menu_widget.cc

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "hbitmap_utils.h"
77
#include "menu_render.h"
88
#include "nanovg.h"
9+
#include "nanovg_wrapper.h"
910
#include "ui.h"
1011
#include "widget.h"
1112
#include <algorithm>
@@ -320,13 +321,75 @@ bool mb_shell::menu_widget::check_hit(const ui::update_context &ctx) {
320321
}
321322

322323
void mb_shell::menu_widget::render(ui::nanovg_context ctx) {
323-
if (bg) {
324-
ctx.transaction([&]() {
324+
325+
auto bg_filler_factory = [&](auto bg, ui::nanovg_context &ctx) {
326+
return [bg, ctx]() mutable {
327+
ctx.globalAlpha(*bg->opacity / 255.f);
328+
auto &theme = config::current->context_menu.theme;
329+
bool light = menu_render::current.value()->light_color;
330+
331+
float boarder_width =
332+
theme.use_self_drawn_border ? theme.border_width : 0.0f;
333+
334+
if (theme.use_self_drawn_border) {
335+
float shadow_size = theme.shadow_size,
336+
shadow_offset_x = theme.shadow_offset_x,
337+
shadow_offset_y = theme.shadow_offset_y;
338+
float corner_radius = theme.radius;
339+
NVGcolor shadow_color_from =
340+
parse_color(light ? theme.shadow_color_light_from
341+
: theme.shadow_color_dark_from),
342+
shadow_color_to =
343+
parse_color(light ? theme.shadow_color_light_to
344+
: theme.shadow_color_dark_to);
345+
346+
ctx.beginPath();
347+
ctx.beginPath();
348+
349+
ctx.roundedRect(*bg->x - shadow_size + shadow_offset_x,
350+
*bg->y - shadow_size + shadow_offset_y,
351+
*bg->width + shadow_size * 2,
352+
*bg->height + shadow_size * 2,
353+
corner_radius + shadow_size);
354+
ctx.fillPaint(ctx.boxGradient(*bg->x + shadow_offset_x,
355+
*bg->y + shadow_offset_y, *bg->width,
356+
*bg->height, corner_radius, shadow_size,
357+
shadow_color_from, shadow_color_to));
358+
ctx.fill();
359+
360+
// Draw the border
361+
ctx.beginPath();
362+
363+
if (theme.inset_border) {
364+
ctx.roundedRect(*bg->x + boarder_width / 2, *bg->y + boarder_width / 2,
365+
*bg->width - boarder_width,
366+
*bg->height - boarder_width, corner_radius);
367+
} else {
368+
ctx.roundedRect(*bg->x, *bg->y, *bg->width, *bg->height,
369+
corner_radius);
370+
}
371+
ctx.strokeWidth(boarder_width);
372+
auto border_color =
373+
light ? theme.border_color_light : theme.border_color_dark;
374+
border_color.apply_to_ctx(ctx, *bg->x, *bg->y, *bg->width, *bg->height);
375+
ctx.stroke();
376+
}
377+
325378
ctx.globalCompositeOperation(NVG_DESTINATION_IN);
379+
ctx.globalAlpha(1);
326380
auto cl = nvgRGBAf(0, 0, 0, 1 - *bg->opacity / 255.f);
327381
ctx.fillColor(cl);
328-
ctx.fillRoundedRect(*bg->x, *bg->y, *bg->width, *bg->height, *bg->radius);
329-
});
382+
if (theme.inset_border)
383+
ctx.fillRoundedRect(*bg->x + boarder_width, *bg->y + boarder_width,
384+
*bg->width - boarder_width * 2,
385+
*bg->height - boarder_width * 2, *bg->radius);
386+
else
387+
ctx.fillRoundedRect(*bg->x, *bg->y, *bg->width, *bg->height,
388+
*bg->radius);
389+
};
390+
};
391+
if (bg) {
392+
ctx.transaction(bg_filler_factory(bg, ctx));
330393
bg->render(ctx);
331394
}
332395

@@ -339,14 +402,7 @@ void mb_shell::menu_widget::render(ui::nanovg_context ctx) {
339402
auto ctx2 = ctx.with_offset(*x, *y);
340403

341404
if (bg_submenu) {
342-
ctx2.transaction([&]() {
343-
ctx2.globalCompositeOperation(NVG_DESTINATION_IN);
344-
ctx2.resetScissor();
345-
auto cl = nvgRGBAf(0, 0, 0, 1 - *bg_submenu->opacity / 255.f);
346-
ctx2.fillColor(cl);
347-
ctx2.fillRoundedRect(*bg_submenu->x, *bg_submenu->y, *bg_submenu->width,
348-
*bg_submenu->height, *bg_submenu->radius);
349-
});
405+
ctx2.transaction(bg_filler_factory(bg_submenu, ctx2));
350406
bg_submenu->render(ctx2);
351407
}
352408
render_children(ctx2, rendering_submenus);

src/shell/utils.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,19 @@ void mb_shell::perf_counter::end(std::optional<std::string> block_name) {
212212
}
213213
last_end = now;
214214
}
215+
std::vector<std::string> mb_shell::split_string(const std::string &str,
216+
char delimiter) {
217+
std::vector<std::string> result;
218+
std::string token;
219+
std::istringstream tokenStream(str);
220+
while (std::getline(tokenStream, token, delimiter)) {
221+
result.push_back(token);
222+
}
223+
return result;
224+
}
225+
std::string mb_shell::format_color(NVGcolor color) {
226+
return std::format(
227+
"#{0:02x}{1:02x}{2:02x}{3:02x}", static_cast<int>(color.r * 255),
228+
static_cast<int>(color.g * 255), static_cast<int>(color.b * 255),
229+
static_cast<int>(color.a * 255));
230+
}

src/shell/utils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ bool is_acrylic_available();
2121
std::optional<std::string> env(const std::string &name);
2222
bool is_memory_readable(const void *ptr);
2323
NVGcolor parse_color(const std::string &str);
24+
std::string format_color(NVGcolor color);
2425
void set_thread_locale_utf8();
2526

27+
std::vector<std::string> split_string(const std::string &str, char delimiter);
28+
2629
struct task_queue {
2730
public:
2831
task_queue();

0 commit comments

Comments
 (0)