diff --git a/metadata/pixdecor.xml b/metadata/pixdecor.xml index 2da2bc4..5ce4729 100644 --- a/metadata/pixdecor.xml +++ b/metadata/pixdecor.xml @@ -5,7 +5,7 @@ <_long>A highly configurable and optionally fancy decorator plugin for wayfire. Effects - <_short>General + <_short>Window frames + + + + + + + + + + + + <_short>General - - - - - - - <_short>Buttons diff --git a/src/deco-effects.cpp b/src/deco-effects.cpp index 7aa6393..f092244 100644 --- a/src/deco-effects.cpp +++ b/src/deco-effects.cpp @@ -33,6 +33,7 @@ #include "deco-effects.hpp" #include "smoke-shaders.hpp" +#include "sculpted-overlay.hpp" namespace wf @@ -2206,6 +2207,9 @@ void smoke_t::create_programs() if (std::string(overlay_engine) == "rounded_corners") { setup_shader(&render_overlay_program, rounded_corner_overlay); + } else if (std::string(overlay_engine) == "sculpted") + { + setup_shader(&render_overlay_program, sculpted_overlay); } else if (std::string(overlay_engine) == "beveled_glass") { setup_shader(&render_overlay_program, beveled_glass_overlay); @@ -2570,7 +2574,7 @@ void smoke_t::step_effect(const wf::scene::render_instruction_t& data, wf::geome GL_CALL(glDeleteFramebuffers(1, &fb)); } - if ((std::string(overlay_engine) == "rounded_corners") || + if ((uses_shadow_overlay(std::string(overlay_engine))) || (std::string(overlay_engine) == "beveled_glass")) { GL_CALL(glUseProgram(render_overlay_program)); @@ -2579,13 +2583,20 @@ void smoke_t::step_effect(const wf::scene::render_instruction_t& data, wf::geome GL_CALL(glUniform1i(5, rectangle.width)); GL_CALL(glUniform1i(6, rectangle.height)); GL_CALL(glUniform1i(7, rounded_corner_radius)); - if (std::string(overlay_engine) == "rounded_corners") + if (uses_shadow_overlay(std::string(overlay_engine))) { GLfloat shadow_color_f[4] = {GLfloat(shadow_color.r), GLfloat(shadow_color.g), GLfloat(shadow_color.b), GLfloat(shadow_color.a)}; GL_CALL(glUniform1i(8, radius)); GL_CALL(glUniform4fv(9, 1, shadow_color_f)); + if (std::string(overlay_engine) == "sculpted") + { + wf::color_t rim = pinstripe_color; + GLfloat rim_f[4] = {GLfloat(rim.r), GLfloat(rim.g), GLfloat(rim.b), GLfloat(rim.a)}; + GL_CALL(glUniform1f(10, border_depth)); + GL_CALL(glUniform4fv(11, 1, rim_f)); + } } GL_CALL(glDispatchCompute(round_up_div(rectangle.width, 16), round_up_div(rectangle.height, 16), diff --git a/src/deco-effects.hpp b/src/deco-effects.hpp index 94fa248..95e97fb 100644 --- a/src/deco-effects.hpp +++ b/src/deco-effects.hpp @@ -10,6 +10,11 @@ namespace wf { namespace pixdecor { +inline bool uses_shadow_overlay(const std::string& engine) +{ + return engine == "rounded_corners" || engine == "sculpted"; +} + class smoke_t { /** background effects */ @@ -25,6 +30,8 @@ class smoke_t wf::option_wrapper_t effect_type{"pixdecor/effect_type"}; wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; wf::option_wrapper_t effect_animate{"pixdecor/animate"}; + wf::option_wrapper_t border_depth{"pixdecor/border_depth"}; + wf::option_wrapper_t pinstripe_color{"pixdecor/pinstripe_color"}; wf::option_wrapper_t rounded_corner_radius{"pixdecor/rounded_corner_radius"}; public: diff --git a/src/deco-layout.cpp b/src/deco-layout.cpp index 394ba37..47b2c7b 100644 --- a/src/deco-layout.cpp +++ b/src/deco-layout.cpp @@ -242,7 +242,7 @@ void pixdecor_layout_t::resize(int width, int height) wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; wf::option_wrapper_t maximized_borders{"pixdecor/maximized_borders"}; - bool rounded_corners = std::string(overlay_engine) == "rounded_corners"; + bool rounded_corners = uses_shadow_overlay(std::string(overlay_engine)); double border = theme.get_border_size(); double radius = (rounded_corners && !maximized) ? int(shadow_radius) : 0; @@ -627,7 +627,7 @@ uint32_t pixdecor_layout_t::calculate_resize_edges() const { wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; - int radius = (std::string(overlay_engine) == "rounded_corners") ? int(shadow_radius) : 0; + int radius = (uses_shadow_overlay(std::string(overlay_engine))) ? int(shadow_radius) : 0; uint32_t edges = 0; for (auto& area : layout_areas) { diff --git a/src/deco-subsurface.cpp b/src/deco-subsurface.cpp index 5a51dee..f626204 100644 --- a/src/deco-subsurface.cpp +++ b/src/deco-subsurface.cpp @@ -6,6 +6,8 @@ #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel.hpp" #include +#include +#include #define GLM_FORCE_RADIANS #include @@ -35,6 +37,7 @@ namespace pixdecor { wf::option_wrapper_t effect_color{"pixdecor/effect_color"}; wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; +wf::option_wrapper_t rounded_corner_radius{"pixdecor/rounded_corner_radius"}; wf::option_wrapper_t titlebar_opt{"pixdecor/titlebar"}; wf::option_wrapper_t csd_titlebar_height{"pixdecor/csd_titlebar_height"}; wf::option_wrapper_t enable_shade{"pixdecor/enable_shade"}; @@ -208,7 +211,7 @@ class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_in } /* Draw title & buttons */ - auto title_border = border + ((std::string(overlay_engine) == "rounded_corners" && + auto title_border = border + ((uses_shadow_overlay(std::string(overlay_engine)) && (!maximized || maximized_shadows)) ? int(shadow_radius) * 2 : 0); for (auto item : renderables) { @@ -236,7 +239,7 @@ class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_in double border = theme.get_border_size(); double r = - (std::string(overlay_engine) == "rounded_corners" && + (uses_shadow_overlay(std::string(overlay_engine)) && (!maximized || (maximized && maximized_shadows))) ? int(shadow_radius) * 2 : 0; r -= MIN_RESIZE_HANDLE_SIZE - std::min((int)border, MIN_RESIZE_HANDLE_SIZE); wf::pointf_t local = at - wf::pointf_t{get_offset()}; @@ -249,6 +252,22 @@ class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_in if (deco_region.contains_pointf(local)) { + if (std::string(overlay_engine) == "sculpted") + { + const double shadow = uses_shadow_overlay(std::string(overlay_engine)) && + (!maximized || maximized_shadows) ? int(shadow_radius) * 2 : 0; + const double w = g.width + 2 * r - 2 * shadow; + const double h = g.height + 2 * r - 2 * shadow; + const double radius = std::max(0.0, std::min({double(rounded_corner_radius), + w / 2, h / 2, std::floor(border / (1.0 - 1.0 / std::sqrt(2.0)))})); + const double x = local.x - shadow, y = local.y - shadow; + const double dx = std::max({radius - x, 0.0, x - (w - radius)}); + const double dy = std::max({radius - y, 0.0, y - (h - radius)}); + if (dx > 0 && dy > 0 && dx * dx + dy * dy > radius * radius) + { + return {}; + } + } return wf::scene::input_node_t{ .node = this, .local_coords = local, @@ -321,7 +340,7 @@ class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_in self->current_cursor_position, self->theme.get_decor_color(activated), self->theme.get_shadow_color(activated), effect_color, self->theme.get_title_height(), self->theme.get_border_size(), - (std::string(overlay_engine) == "rounded_corners" && !maximized) ? shadow_radius : 0); + (uses_shadow_overlay(std::string(overlay_engine)) && !maximized) ? shadow_radius : 0); } self->render_region(data, offset); @@ -537,7 +556,7 @@ class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_in this->cached_region.clear(); } else { - int shadow_thickness = std::string(overlay_engine) == "rounded_corners" && + int shadow_thickness = uses_shadow_overlay(std::string(overlay_engine)) && (!maximized || (maximized && maximized_shadows)) ? int(shadow_radius) * 2 : 0; current_thickness = theme.get_border_size() + shadow_thickness; @@ -649,7 +668,7 @@ wf::decoration_margins_t simple_decorator_t::get_margins(const wf::toplevel_stat bool maximized = state.tiled_edges; deco->theme.set_maximize(maximized); - this->shadow_thickness = std::string(overlay_engine) == "rounded_corners" && + this->shadow_thickness = uses_shadow_overlay(std::string(overlay_engine)) && (!state.tiled_edges || (state.tiled_edges && maximized_shadows)) ? int(shadow_radius) * 2 : 0; double thickness = deco->theme.get_border_size() + this->shadow_thickness; diff --git a/src/decoration.cpp b/src/decoration.cpp index e5f3ec2..0ceb16f 100644 --- a/src/decoration.cpp +++ b/src/decoration.cpp @@ -19,6 +19,8 @@ #include #include #include "shade.hpp" +#include "deco-effects.hpp" +#include namespace wf { @@ -57,6 +59,8 @@ class wayfire_pixdecor : public wf::plugin_interface_t wf::option_wrapper_t effect_type{"pixdecor/effect_type"}; wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; wf::option_wrapper_t effect_animate{"pixdecor/animate"}; + wf::option_wrapper_t border_depth; + wf::option_wrapper_t pinstripe_color; wf::option_wrapper_t rounded_corner_radius{"pixdecor/rounded_corner_radius"}; wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; wf::option_wrapper_t shadow_color{"pixdecor/shadow_color"}; @@ -222,6 +226,23 @@ class wayfire_pixdecor : public wf::plugin_interface_t void init() override { + // Promote new options when replacing a module in an older resident core. + auto section = wf::get_core().config->get_section("pixdecor"); + auto promote = [&] (const std::string& name, auto value) + { + using T = decltype(value); + auto previous = section->get_option_or(name); + if (!std::dynamic_pointer_cast>(previous)) + { + auto option = std::make_shared>(name, value); + if (previous) option->set_value_str(previous->get_value_str()); + section->register_new_option(option); + } + }; + promote("border_depth", 0.65); + promote("pinstripe_color", wf::color_t{90.0 / 255, 113.0 / 255, 129.0 / 255, 36.0 / 255}); + border_depth.load_option("pixdecor/border_depth"); + pinstripe_color.load_option("pixdecor/pinstripe_color"); auto& ol = wf::get_core().output_layout; ol->connect(&on_output_added); ol->connect(&on_output_removed); @@ -427,7 +448,7 @@ class wayfire_pixdecor : public wf::plugin_interface_t title_font.set_callback([=] {recreate_frames();}); shadow_radius.set_callback([=] { - option_changed_cb(false, (std::string(overlay_engine) == "rounded_corners")); + option_changed_cb(false, (uses_shadow_overlay(std::string(overlay_engine)))); }); shadow_color.set_callback([=] { @@ -455,6 +476,21 @@ class wayfire_pixdecor : public wf::plugin_interface_t view->damage(); } }); + auto repaint_frames = [=] () + { + for (auto& view : wf::get_core().get_all_views()) + { + if (auto toplevel = wf::toplevel_cast(view)) + { + if (toplevel->toplevel()->get_data()) + { + view->damage(); + } + } + } + }; + border_depth.set_callback(repaint_frames); + pinstripe_color.set_callback(repaint_frames); rounded_corner_radius.set_callback([=] { for (auto& view : wf::get_core().get_all_views()) @@ -629,7 +665,7 @@ class wayfire_pixdecor : public wf::plugin_interface_t continue; } - if (std::string(overlay_engine) == "rounded_corners") + if (uses_shadow_overlay(std::string(overlay_engine))) { pending.margins = {double(shadow_radius * 2), double(shadow_radius * 2), diff --git a/src/meson.build b/src/meson.build index 000ce95..66f1573 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,6 @@ pixdecor = shared_module('pixdecor', ['decoration.cpp', 'deco-subsurface.cpp', 'deco-button.cpp', 'deco-layout.cpp', 'deco-theme.cpp', 'deco-effects.cpp'], dependencies: [wayfire], + link_args: ['-Wl,-Bsymbolic'], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) diff --git a/src/sculpted-overlay.hpp b/src/sculpted-overlay.hpp new file mode 100644 index 0000000..99f0568 --- /dev/null +++ b/src/sculpted-overlay.hpp @@ -0,0 +1,84 @@ +#pragma once + +namespace wf::pixdecor +{ +// Static shell material, composed over the selected border effect. Coordinates +// and shadow margins follow Pixdecor's existing rounded-corner overlay. +inline constexpr const char *sculpted_overlay = R"( +#version 320 es +precision highp float; +layout(binding = 0, rgba32f) readonly uniform highp image2D in_frame; +layout(binding = 0, rgba32f) writeonly uniform highp image2D out_frame; +layout(local_size_x = 16, local_size_y = 16) in; +layout(location = 1) uniform int title_height; +layout(location = 2) uniform int border_size; +layout(location = 5) uniform int width; +layout(location = 6) uniform int height; +layout(location = 7) uniform int corner_radius; +layout(location = 8) uniform int shadow_radius; +layout(location = 9) uniform vec4 shadow_color; +layout(location = 10) uniform float border_depth; +layout(location = 11) uniform vec4 pinstripe_color; + +vec4 over(vec4 base, vec4 tint) +{ + return vec4(tint.rgb * tint.a + base.rgb * (1.0 - tint.a), + tint.a + base.a * (1.0 - tint.a)); +} + +void main() +{ + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + if (pos.x >= width || pos.y >= height) return; + // Application pixels are owned by the child surface, never this overlay. + if (pos.x >= border_size && pos.x < width - border_size && + pos.y >= title_height && pos.y < height - border_size) return; + + float margin = 2.0 * float(shadow_radius); + float border = max(0.0, float(border_size) - margin); + vec2 lo = vec2(margin), hi = vec2(width, height) - lo; + vec2 halfsize = max(vec2(0.0), (hi - lo) * 0.5); + // Keep a square application's inner corners inside the rounded frame. + float radius = clamp(float(corner_radius), 0.0, + min(min(halfsize.x, halfsize.y), floor(border / (1.0 - inversesqrt(2.0))))); + vec2 p = vec2(pos) + 0.5; + vec2 center = (lo + hi) * 0.5; + vec2 q = abs(p - center) - (halfsize - radius); + float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; + float coverage = clamp(0.5 - d, 0.0, 1.0); + vec4 shadow = vec4(0.0); + if (shadow_radius > 0) + { + float a = shadow_color.a * exp(-pow(max(d, 0.0) / float(shadow_radius), 2.0)); + shadow = vec4(shadow_color.rgb * a, a); + } + if (coverage == 0.0) + { + imageStore(out_frame, pos, shadow); + return; + } + + vec4 body = imageLoad(in_frame, pos); + body.rgb *= body.a; + float inset = max(0.0, -d); + float strength = clamp(border_depth, 0.0, 1.0) / 0.65; + float falloff = border > 0.0 ? clamp(1.0 - inset / border, 0.0, 1.0) : 0.0; + vec2 outward = p - clamp(p, lo + radius, hi - radius); + if (dot(outward, outward) < 0.0001) + { + vec2 edges = min(p - lo, hi - p); + outward = edges.x < edges.y ? vec2(p.x < center.x ? -1.0 : 1.0, 0.0) : + vec2(0.0, p.y < center.y ? -1.0 : 1.0); + } + outward = normalize(outward); + float light = max(-outward.y, 0.0) * 0.170 + max(-outward.x, 0.0) * 0.045; + float shade = max(outward.y, 0.0) * 0.500 + max(outward.x, 0.0) * 0.160; + body = over(body, vec4(1.0, 1.0, 1.0, clamp(light * strength * falloff, 0.0, 1.0))); + body = over(body, vec4(0.0, 0.0, 0.0, clamp(shade * strength * falloff, 0.0, 1.0))); + vec4 rim = pinstripe_color; + rim.a *= 1.0 - smoothstep(0.5, 1.5, inset); + body = over(body, rim); + imageStore(out_frame, pos, body * coverage + shadow * (1.0 - coverage)); +} +)"; +}