├── LICENSE ├── README.md ├── ipc-scripts └── ipc-shade-toggle.py ├── meson.build ├── metadata ├── meson.build └── pixdecor.xml └── src ├── deco-button.cpp ├── deco-button.hpp ├── deco-effects.cpp ├── deco-effects.hpp ├── deco-layout.cpp ├── deco-layout.hpp ├── deco-subsurface.cpp ├── deco-subsurface.hpp ├── deco-theme.cpp ├── deco-theme.hpp ├── decoration.cpp ├── meson.build ├── shade.hpp └── smoke-shaders.hpp /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Scott Moreau 4 | Copyright (c) 2024 Ilia Bozhinov 5 | Copyright (c) 2024 Andrew Pliatsikas 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pixdecor 2 | ![pixdecor](https://github.com/soreau/pixdecor/assets/1450125/af891554-8eeb-4769-b571-fa587afd8350) 3 | 4 | A highly configurable decorator plugin for wayfire, pixdecor features antialiased rounded corners with shadows and optional animated effects. 5 | 6 | ## Installing 7 | 8 | Set `--prefix` to the same as the wayfire installation. 9 | 10 | ``` 11 | $ meson setup build --prefix=/usr 12 | $ ninja -C build 13 | # ninja -C build install 14 | ``` 15 | 16 | Restart wayfire. 17 | 18 | ## Running 19 | 20 | Disable other decorator plugins and enable pixdecor plugin in core section of `wayfire.ini`. 21 | -------------------------------------------------------------------------------- /ipc-scripts/ipc-shade-toggle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | from wayfire import WayfireSocket 6 | from wayfire.extra.wpe import WPE 7 | 8 | if len(sys.argv) < 2: 9 | print(f"Usage: {sys.argv[0]} ") 10 | exit(1) 11 | 12 | sock = WayfireSocket() 13 | wpe = WPE(sock) 14 | 15 | wpe.shade_toggle(int(sys.argv[1])) 16 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'pixdecor', 3 | 'c', 4 | 'cpp', 5 | version: '0.1.0', 6 | license: 'MIT', 7 | meson_version: '>=0.51.0', 8 | default_options: [ 9 | 'cpp_std=c++17', 10 | 'c_std=c11', 11 | ], 12 | ) 13 | 14 | add_project_arguments(['-DWLR_USE_UNSTABLE'], language: ['cpp', 'c']) 15 | add_project_arguments(['-DWAYFIRE_PLUGIN'], language: ['cpp', 'c']) 16 | add_project_link_arguments(['-rdynamic','-fPIC'], language:'cpp') 17 | 18 | wayfire = dependency('wayfire', version: '>=0.9.0') 19 | 20 | 21 | subdir('src') 22 | subdir('metadata') 23 | 24 | -------------------------------------------------------------------------------- /metadata/meson.build: -------------------------------------------------------------------------------- 1 | install_data( 'pixdecor.xml', install_dir: wayfire.get_variable( pkgconfig: 'metadatadir' ) ) -------------------------------------------------------------------------------- /metadata/pixdecor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_short>Pix Decoration 5 | <_long>A highly configurable and optionally fancy decorator plugin for wayfire. 6 | Effects 7 | 8 | <_short>General 9 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 76 | 82 | 87 | 92 | 98 | 104 | 110 | 127 | 144 | 209 | 214 | 219 | 225 | 231 | 236 | 237 | 238 | <_short>Shade 239 | 244 | 250 | 255 | 260 | 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /src/deco-button.cpp: -------------------------------------------------------------------------------- 1 | #include "deco-button.hpp" 2 | #include "deco-theme.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | #define NORMAL 1.0 8 | #define PRESSED 0.5 9 | #define HOVERED 0.25 10 | 11 | namespace wf 12 | { 13 | namespace pixdecor 14 | { 15 | button_t::button_t(pixdecor_theme_t& t, std::function damage) : 16 | theme(t), damage_callback(damage) 17 | {} 18 | 19 | wf::dimensions_t button_t::set_button_type(button_type_t type) 20 | { 21 | this->type = type; 22 | this->hover.animate(NORMAL, NORMAL); 23 | auto size = update_texture(); 24 | add_idle_damage(); 25 | 26 | return size; 27 | } 28 | 29 | button_type_t button_t::get_button_type() const 30 | { 31 | return this->type; 32 | } 33 | 34 | void button_t::set_hover(bool is_hovered) 35 | { 36 | this->is_hovered = is_hovered; 37 | if (!this->is_pressed) 38 | { 39 | if (is_hovered) 40 | { 41 | this->hover.animate(HOVERED); 42 | } else 43 | { 44 | this->hover.animate(NORMAL); 45 | } 46 | } 47 | 48 | add_idle_damage(); 49 | } 50 | 51 | /** 52 | * Set whether the button is pressed or not. 53 | * Affects appearance. 54 | */ 55 | void button_t::set_pressed(bool is_pressed) 56 | { 57 | this->is_pressed = is_pressed; 58 | if (is_pressed) 59 | { 60 | this->hover.animate(PRESSED); 61 | } else 62 | { 63 | this->hover.animate(is_hovered ? HOVERED : NORMAL); 64 | } 65 | 66 | add_idle_damage(); 67 | } 68 | 69 | void button_t::render(const wf::render_target_t& fb, wf::geometry_t geometry, const wf::region_t& scissor) 70 | { 71 | OpenGL::render_texture(button_texture.tex, fb, geometry, {1, 1, 1, this->hover}, 72 | OpenGL::TEXTURE_TRANSFORM_INVERT_Y | OpenGL::RENDER_FLAG_CACHED); 73 | for (auto& box : scissor) 74 | { 75 | fb.logic_scissor(wlr_box_from_pixman_box(box)); 76 | OpenGL::draw_cached(); 77 | } 78 | 79 | OpenGL::clear_cached(); 80 | 81 | if (this->hover.running()) 82 | { 83 | add_idle_damage(); 84 | } 85 | } 86 | 87 | wf::dimensions_t button_t::update_texture() 88 | { 89 | pixdecor_theme_t::button_state_t state = { 90 | .width = 1.0 * (theme.get_font_height_px() >= LARGE_ICON_THRESHOLD ? 26 : 18), 91 | .height = 1.0 * (theme.get_font_height_px() >= LARGE_ICON_THRESHOLD ? 26 : 18), 92 | .border = 1.0, 93 | .hover = this->is_hovered, 94 | }; 95 | 96 | auto surface = theme.get_button_surface(type, state, this->active); 97 | wf::dimensions_t size{cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface)}; 98 | 99 | OpenGL::render_begin(); 100 | cairo_surface_upload_to_texture(surface, this->button_texture); 101 | OpenGL::render_end(); 102 | 103 | cairo_surface_destroy(surface); 104 | 105 | return size; 106 | } 107 | 108 | void button_t::add_idle_damage() 109 | { 110 | this->idle_damage.run_once([=] () 111 | { 112 | this->damage_callback(); 113 | }); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/deco-button.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace wf 15 | { 16 | namespace pixdecor 17 | { 18 | class pixdecor_theme_t; 19 | 20 | enum button_type_t 21 | { 22 | BUTTON_CLOSE, 23 | BUTTON_TOGGLE_MAXIMIZE, 24 | BUTTON_MINIMIZE, 25 | }; 26 | 27 | class button_t 28 | { 29 | public: 30 | /** 31 | * Create a new button with the given theme. 32 | * @param theme The theme to use. 33 | * @param damage_callback A callback to execute when the button needs a 34 | * repaint. Damage won't be reported while render() is being called. 35 | */ 36 | button_t(pixdecor_theme_t& theme, 37 | std::function damage_callback); 38 | 39 | /** 40 | * Set the type of the button. This will affect the displayed icon and 41 | * potentially other appearance like colors. 42 | */ 43 | wf::dimensions_t set_button_type(button_type_t type); 44 | 45 | /** @return The type of the button */ 46 | button_type_t get_button_type() const; 47 | 48 | /** 49 | * Set the button hover state. 50 | * Affects appearance. 51 | */ 52 | void set_hover(bool is_hovered); 53 | 54 | /** 55 | * Set whether the button is pressed or not. 56 | * Affects appearance. 57 | */ 58 | void set_pressed(bool is_pressed); 59 | 60 | /** 61 | * Render the button on the given framebuffer at the given coordinates. 62 | * Precondition: set_button_type() has been called, otherwise result is no-op 63 | * 64 | * @param buffer The target framebuffer 65 | * @param geometry The geometry of the button, in logical coordinates 66 | * @param scissor The scissor rectangle to render. 67 | */ 68 | void render(const wf::render_target_t& fb, wf::geometry_t geometry, 69 | const wf::region_t& scissor); 70 | 71 | pixdecor_theme_t& theme; 72 | std::function damage_callback; 73 | 74 | private: 75 | 76 | button_type_t type; 77 | wf::simple_texture_t button_texture; 78 | bool active = false; 79 | wf::geometry_t geometry; 80 | 81 | /* Whether the button is currently being hovered */ 82 | bool is_hovered = false; 83 | /* Whether the button is currently being held */ 84 | bool is_pressed = false; 85 | /* The shade of button background to use. */ 86 | wf::animation::simple_animation_t hover{wf::create_option(500)}; 87 | 88 | wf::wl_idle_call idle_damage; 89 | /** Damage button the next time the main loop goes idle */ 90 | void add_idle_damage(); 91 | 92 | /** 93 | * Redraw the button surface and store it as a texture 94 | */ 95 | wf::dimensions_t update_texture(); 96 | }; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/deco-effects.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace wf 9 | { 10 | namespace pixdecor 11 | { 12 | class smoke_t 13 | { 14 | /** background effects */ 15 | GLuint motion_program, 16 | diffuse1_program, diffuse2_program, 17 | project1_program, project2_program, project3_program, 18 | project4_program, project5_program, project6_program, 19 | advect1_program, advect2_program, render_program, render_overlay_program, 20 | texture, b0u, b0v, b0d, b1u, b1v, b1d, neural_network_tex; 21 | 22 | int saved_width = -1, saved_height = -1; 23 | 24 | wf::option_wrapper_t effect_type{"pixdecor/effect_type"}; 25 | wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; 26 | wf::option_wrapper_t effect_animate{"pixdecor/animate"}; 27 | wf::option_wrapper_t rounded_corner_radius{"pixdecor/rounded_corner_radius"}; 28 | wf::option_wrapper_t shadow_color{"pixdecor/shadow_color"}; 29 | 30 | public: 31 | smoke_t(); 32 | ~smoke_t(); 33 | 34 | void run_shader(GLuint program, int width, int height, int title_height, int border_size, int radius); 35 | void run_shader_region(GLuint program, const wf::region_t & region, const wf::dimensions_t & size); 36 | void dispatch_region(const wf::region_t& region); 37 | 38 | void step_effect(const wf::render_target_t& fb, wf::geometry_t rectangle, 39 | bool ink, wf::pointf_t p, wf::color_t decor_color, wf::color_t effect_color, 40 | int title_height, int border_size, int shadow_radius); 41 | void render_effect(const wf::render_target_t& fb, wf::geometry_t rectangle, const wf::region_t& scissor); 42 | void recreate_textures(wf::geometry_t rectangle); 43 | void create_programs(); 44 | void destroy_programs(); 45 | void create_textures(); 46 | void destroy_textures(); 47 | void effect_updated(); 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/deco-layout.cpp: -------------------------------------------------------------------------------- 1 | #include "deco-layout.hpp" 2 | #include "deco-theme.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace wf 10 | { 11 | namespace pixdecor 12 | { 13 | /** 14 | * Represents an area of the decoration which reacts to input events. 15 | */ 16 | decoration_area_t::decoration_area_t(decoration_area_type_t type, wf::geometry_t g) 17 | { 18 | this->type = type; 19 | this->geometry = g; 20 | 21 | assert(type != DECORATION_AREA_BUTTON); 22 | } 23 | 24 | /** 25 | * Initialize a new decoration area holding a button 26 | */ 27 | decoration_area_t::decoration_area_t(wf::geometry_t g, 28 | std::function damage_callback, 29 | pixdecor_theme_t& theme) 30 | { 31 | this->type = DECORATION_AREA_BUTTON; 32 | this->geometry = g; 33 | this->damage_callback = damage_callback; 34 | 35 | this->button = std::make_unique(theme, 36 | std::bind(damage_callback, g)); 37 | } 38 | 39 | wf::geometry_t decoration_area_t::get_geometry() const 40 | { 41 | return geometry; 42 | } 43 | 44 | void decoration_area_t::set_geometry(wf::geometry_t g) 45 | { 46 | geometry = g; 47 | 48 | if (this->type == DECORATION_AREA_BUTTON) 49 | { 50 | this->button = std::make_unique(this->button->theme, 51 | std::bind(this->damage_callback, g)); 52 | } 53 | } 54 | 55 | button_t& decoration_area_t::as_button() 56 | { 57 | assert(button); 58 | 59 | return *button; 60 | } 61 | 62 | decoration_area_type_t decoration_area_t::get_type() const 63 | { 64 | return type; 65 | } 66 | 67 | pixdecor_layout_t::pixdecor_layout_t(pixdecor_theme_t& th, 68 | std::function callback) : 69 | 70 | titlebar_size(th.get_title_height()), 71 | border_size(th.get_input_size()), 72 | theme(th), 73 | damage_callback(callback) 74 | {} 75 | 76 | pixdecor_layout_t::~pixdecor_layout_t() 77 | { 78 | this->layout_areas.clear(); 79 | } 80 | 81 | wf::geometry_t pixdecor_layout_t::create_buttons(int width, int radius) 82 | { 83 | // read the string from settings; start at the colon and replace commas with spaces 84 | wf::option_wrapper_t button_spacing{"pixdecor/button_spacing"}; 85 | wf::option_wrapper_t button_x_offset{"pixdecor/button_x_offset"}; 86 | wf::option_wrapper_t button_y_offset{"pixdecor/button_y_offset"}; 87 | GSettings *settings = g_settings_new("org.gnome.desktop.wm.preferences"); 88 | gchar *b_layout = g_settings_get_string(settings, "button-layout"); 89 | gchar *ptr = b_layout + strlen(b_layout) - 1; 90 | while (ptr >= b_layout) 91 | { 92 | if (*ptr == ',') 93 | { 94 | *ptr = ' '; 95 | } 96 | 97 | if (*ptr == ':') 98 | { 99 | break; 100 | } 101 | 102 | ptr--; 103 | } 104 | 105 | std::string layout = (*ptr == ':') ? ptr + 1 : std::string(b_layout); 106 | g_free(b_layout); 107 | g_object_unref(settings); 108 | 109 | std::stringstream stream((std::string)layout); 110 | std::vector buttons; 111 | std::string button_name; 112 | while (stream >> button_name) 113 | { 114 | if (button_name == "minimize") 115 | { 116 | buttons.push_back(BUTTON_MINIMIZE); 117 | } 118 | 119 | if (button_name == "maximize") 120 | { 121 | buttons.push_back(BUTTON_TOGGLE_MAXIMIZE); 122 | } 123 | 124 | if (button_name == "close") 125 | { 126 | buttons.push_back(BUTTON_CLOSE); 127 | } 128 | } 129 | 130 | int total_width = 0; 131 | int per_button = 0; 132 | int border = theme.get_border_size(); 133 | wf::geometry_t button_geometry; 134 | button_geometry.x = (width - (maximized ? 4 : border)) + button_x_offset; 135 | 136 | for (auto type : wf::reverse(buttons)) 137 | { 138 | auto button_area = std::make_unique(button_geometry, damage_callback, theme); 139 | auto surface_size = button_area->as_button().set_button_type(type); 140 | button_geometry.width = surface_size.width; 141 | button_geometry.height = surface_size.height; 142 | int button_padding = (theme.get_title_height() - button_geometry.height) / 2 + button_y_offset; 143 | button_geometry.y = button_padding + border / 2 + (radius * 2); 144 | per_button = button_geometry.width + (buttons.back() == type ? 0 : button_spacing); 145 | button_geometry.x -= per_button; 146 | button_area->set_geometry(button_geometry); 147 | button_area->as_button().set_button_type(type); 148 | this->layout_areas.push_back(std::move(button_area)); 149 | total_width += per_button; 150 | } 151 | 152 | total_width -= button_x_offset; 153 | 154 | return { 155 | button_geometry.x, maximized ? 4 : border + (radius * 2), 156 | total_width, theme.get_title_height() 157 | }; 158 | } 159 | 160 | /** Regenerate layout using the new size */ 161 | void pixdecor_layout_t::resize(int width, int height) 162 | { 163 | wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; 164 | wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; 165 | wf::option_wrapper_t maximized_borders{"pixdecor/maximized_borders"}; 166 | bool rounded_corners = std::string(overlay_engine) == "rounded_corners"; 167 | 168 | int border = theme.get_border_size(); 169 | int radius = (rounded_corners && !maximized) ? int(shadow_radius) : 0; 170 | 171 | this->layout_areas.clear(); 172 | 173 | if (this->theme.get_title_height() > 0) 174 | { 175 | auto button_geometry_expanded = create_buttons(width - (radius * 2), radius); 176 | 177 | /* Padding around the button, allows move */ 178 | this->layout_areas.push_back(std::make_unique( 179 | DECORATION_AREA_MOVE, button_geometry_expanded)); 180 | 181 | /* Titlebar dragging area (for move) */ 182 | wf::geometry_t title_geometry = { 183 | border, 184 | maximized ? 0 : border / 2 + (radius * 2), 185 | /* Up to the button, but subtract the padding to the left of the 186 | * title and the padding between title and button */ 187 | button_geometry_expanded.x - border, 188 | theme.get_title_height() + (maximized ? 0 : border / 2 + 1), 189 | }; 190 | this->layout_areas.push_back(std::make_unique( 191 | DECORATION_AREA_TITLE, title_geometry)); 192 | 193 | this->cached_titlebar = { 194 | border, border, 195 | width - 2 * border, height - 2 * border 196 | }; 197 | } 198 | 199 | border = MIN_RESIZE_HANDLE_SIZE - theme.get_input_size(); 200 | auto inverse_border = MIN_RESIZE_HANDLE_SIZE - theme.get_border_size(); 201 | 202 | if (!maximized || maximized_borders) 203 | { 204 | /* Resizing edges - top */ 205 | wf::geometry_t border_geometry = 206 | {0 + (radius * 2), -inverse_border + (radius * 2), 207 | width - (radius * 4) + MIN_RESIZE_HANDLE_SIZE, border}; 208 | this->layout_areas.push_back(std::make_unique( 209 | DECORATION_AREA_RESIZE_TOP, border_geometry)); 210 | 211 | /* Resizing edges - bottom */ 212 | border_geometry = 213 | {0 + (radius * 2), (height - border + inverse_border) - (radius * 2), 214 | width - (radius * 4) + MIN_RESIZE_HANDLE_SIZE, border}; 215 | this->layout_areas.push_back(std::make_unique( 216 | DECORATION_AREA_RESIZE_BOTTOM, border_geometry)); 217 | 218 | /* Resizing edges - left */ 219 | border_geometry = 220 | {-inverse_border + (radius * 2), 0 + (radius * 2), border, 221 | height - (radius * 4) + MIN_RESIZE_HANDLE_SIZE}; 222 | this->layout_areas.push_back(std::make_unique( 223 | DECORATION_AREA_RESIZE_LEFT, border_geometry)); 224 | 225 | /* Resizing edges - right */ 226 | border_geometry = 227 | {(width - border + inverse_border) - (radius * 2), 0 + (radius * 2), border, 228 | height - (radius * 4) + MIN_RESIZE_HANDLE_SIZE}; 229 | this->layout_areas.push_back(std::make_unique( 230 | DECORATION_AREA_RESIZE_RIGHT, border_geometry)); 231 | 232 | if (rounded_corners) 233 | { 234 | /* Shadow - top */ 235 | border_geometry = {0, 0, width, radius* 2}; 236 | this->layout_areas.push_back(std::make_unique( 237 | DECORATION_AREA_SHADOW, border_geometry)); 238 | 239 | /* Shadow - bottom */ 240 | border_geometry = {0, height - radius * 2, width, radius* 2}; 241 | this->layout_areas.push_back(std::make_unique( 242 | DECORATION_AREA_SHADOW, border_geometry)); 243 | 244 | /* Shadow - left */ 245 | border_geometry = {0, radius* 2, radius* 2, height - radius * 4}; 246 | this->layout_areas.push_back(std::make_unique( 247 | DECORATION_AREA_SHADOW, border_geometry)); 248 | 249 | /* Shadow - right */ 250 | border_geometry = {width - radius * 2, radius* 2, radius* 2, height - radius * 4}; 251 | this->layout_areas.push_back(std::make_unique( 252 | DECORATION_AREA_SHADOW, border_geometry)); 253 | } 254 | } 255 | } 256 | 257 | /** 258 | * @return The decoration areas which need to be rendered, in top to bottom 259 | * order. 260 | */ 261 | std::vector> pixdecor_layout_t::get_renderable_areas() 262 | { 263 | std::vector> renderable; 264 | for (auto& area : layout_areas) 265 | { 266 | if (area->get_type() & DECORATION_AREA_RENDERABLE_BIT) 267 | { 268 | renderable.push_back({area}); 269 | } 270 | } 271 | 272 | return renderable; 273 | } 274 | 275 | wf::region_t pixdecor_layout_t::calculate_region() const 276 | { 277 | wf::region_t r{}; 278 | for (auto& area : layout_areas) 279 | { 280 | auto g = area->get_geometry(); 281 | auto b = theme.get_input_size(); 282 | if (maximized && (area->get_type() & DECORATION_AREA_MOVE_BIT)) 283 | { 284 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{b, b, b, b}); 285 | } 286 | 287 | if (area->get_type() & DECORATION_AREA_RESIZE_BIT) 288 | { 289 | if (b <= MIN_RESIZE_HANDLE_SIZE) 290 | { 291 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{b, b, b, b}); 292 | } else if ((area->get_type() == DECORATION_AREA_RESIZE_TOP) || 293 | (area->get_type() == DECORATION_AREA_RESIZE_BOTTOM)) 294 | { 295 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{0, 0, b, b}); 296 | } else if ((area->get_type() == DECORATION_AREA_RESIZE_LEFT) || 297 | (area->get_type() == DECORATION_AREA_RESIZE_RIGHT)) 298 | { 299 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{b, b, 0, 0}); 300 | } 301 | } 302 | 303 | if ((g.width > 0) && (g.height > 0)) 304 | { 305 | r |= g; 306 | } 307 | } 308 | 309 | return r; 310 | } 311 | 312 | wf::region_t pixdecor_layout_t::limit_region(wf::region_t & region) const 313 | { 314 | wf::region_t out = region & this->cached_titlebar; 315 | return out; 316 | } 317 | 318 | void pixdecor_layout_t::unset_hover(wf::point_t position) 319 | { 320 | auto area = find_area_at(position); 321 | if (area && (area->get_type() == DECORATION_AREA_BUTTON)) 322 | { 323 | area->as_button().set_hover(false); 324 | } 325 | } 326 | 327 | /** Handle motion event to (x, y) relative to the decoration */ 328 | pixdecor_layout_t::action_response_t pixdecor_layout_t::handle_motion( 329 | int x, int y) 330 | { 331 | auto previous_area = find_area_at(current_input); 332 | auto current_area = find_area_at({x, y}); 333 | 334 | if (previous_area == current_area) 335 | { 336 | if (is_grabbed && current_area && 337 | (current_area->get_type() & DECORATION_AREA_MOVE_BIT)) 338 | { 339 | is_grabbed = false; 340 | return {DECORATION_ACTION_MOVE, 0}; 341 | } 342 | } else 343 | { 344 | unset_hover(current_input); 345 | if (current_area && (current_area->get_type() == DECORATION_AREA_BUTTON)) 346 | { 347 | current_area->as_button().set_hover(true); 348 | } 349 | } 350 | 351 | this->current_input = {x, y}; 352 | update_cursor(); 353 | 354 | return {DECORATION_ACTION_NONE, 0}; 355 | } 356 | 357 | /** 358 | * Handle press or release event. 359 | * @param pressed Whether the event is a press(true) or release(false) 360 | * event. 361 | * @return The action which needs to be carried out in response to this 362 | * event. 363 | * */ 364 | pixdecor_layout_t::action_response_t pixdecor_layout_t::handle_press_event( 365 | bool pressed) 366 | { 367 | if (pressed) 368 | { 369 | auto area = find_area_at(current_input); 370 | if (area && (area->get_type() & DECORATION_AREA_MOVE_BIT)) 371 | { 372 | if (timer.is_connected()) 373 | { 374 | double_click_at_release = true; 375 | } else 376 | { 377 | timer.set_timeout(300, [] () { return false; }); 378 | } 379 | } 380 | 381 | if (area && (area->get_type() & DECORATION_AREA_RESIZE_BIT)) 382 | { 383 | return {DECORATION_ACTION_RESIZE, calculate_resize_edges()}; 384 | } 385 | 386 | if (area && (area->get_type() == DECORATION_AREA_BUTTON)) 387 | { 388 | area->as_button().set_pressed(true); 389 | } 390 | 391 | is_grabbed = true; 392 | grab_origin = current_input; 393 | } 394 | 395 | if (!pressed && double_click_at_release) 396 | { 397 | double_click_at_release = false; 398 | return {DECORATION_ACTION_TOGGLE_MAXIMIZE, 0}; 399 | } else if (!pressed && is_grabbed) 400 | { 401 | is_grabbed = false; 402 | auto begin_area = find_area_at(grab_origin); 403 | auto end_area = find_area_at(current_input); 404 | 405 | if (begin_area && (begin_area->get_type() == DECORATION_AREA_BUTTON)) 406 | { 407 | begin_area->as_button().set_pressed(false); 408 | if (end_area && (begin_area == end_area)) 409 | { 410 | switch (begin_area->as_button().get_button_type()) 411 | { 412 | case BUTTON_CLOSE: 413 | return {DECORATION_ACTION_CLOSE, 0}; 414 | 415 | case BUTTON_TOGGLE_MAXIMIZE: 416 | return {DECORATION_ACTION_TOGGLE_MAXIMIZE, 0}; 417 | 418 | case BUTTON_MINIMIZE: 419 | return {DECORATION_ACTION_MINIMIZE, 0}; 420 | 421 | default: 422 | break; 423 | } 424 | } 425 | } 426 | } 427 | 428 | return {DECORATION_ACTION_NONE, 0}; 429 | } 430 | 431 | pixdecor_layout_t::action_response_t pixdecor_layout_t::handle_axis_event( 432 | int delta) 433 | { 434 | if (delta < 0) 435 | { 436 | return {DECORATION_ACTION_SHADE, 0}; 437 | } else 438 | { 439 | return {DECORATION_ACTION_UNSHADE, 0}; 440 | } 441 | } 442 | 443 | /** 444 | * Find the layout area at the given coordinates, if any 445 | * @return The layout area or null on failure 446 | */ 447 | nonstd::observer_ptr pixdecor_layout_t::find_area_at( 448 | wf::point_t point) 449 | { 450 | for (auto& area : this->layout_areas) 451 | { 452 | auto g = area->get_geometry(); 453 | auto b = theme.get_input_size(); 454 | if (area->get_type() & DECORATION_AREA_MOVE_BIT) 455 | { 456 | continue; 457 | } 458 | 459 | if (area->get_type() & DECORATION_AREA_RESIZE_BIT) 460 | { 461 | if (b <= MIN_RESIZE_HANDLE_SIZE) 462 | { 463 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{b, b, b, b}); 464 | } else if ((area->get_type() == DECORATION_AREA_RESIZE_TOP) || 465 | (area->get_type() == DECORATION_AREA_RESIZE_BOTTOM)) 466 | { 467 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{0, 0, b, b}); 468 | } else if ((area->get_type() == DECORATION_AREA_RESIZE_LEFT) || 469 | (area->get_type() == DECORATION_AREA_RESIZE_RIGHT)) 470 | { 471 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{b, b, 0, 0}); 472 | } 473 | } 474 | 475 | if (maximized && (area->get_type() & DECORATION_AREA_MOVE_BIT)) 476 | { 477 | g.height += b; 478 | } 479 | 480 | if (g & point) 481 | { 482 | return {area}; 483 | } 484 | } 485 | 486 | for (auto& area : this->layout_areas) 487 | { 488 | auto g = area->get_geometry(); 489 | auto b = theme.get_input_size(); 490 | if (area->get_type() & DECORATION_AREA_RESIZE_BIT) 491 | { 492 | continue; 493 | } 494 | 495 | if (maximized && (area->get_type() & DECORATION_AREA_MOVE_BIT)) 496 | { 497 | g.height += b; 498 | } 499 | 500 | if (g & point) 501 | { 502 | return {area}; 503 | } 504 | } 505 | 506 | return nullptr; 507 | } 508 | 509 | /** Calculate resize edges based on @current_input */ 510 | uint32_t pixdecor_layout_t::calculate_resize_edges() const 511 | { 512 | wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; 513 | wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; 514 | int radius = (std::string(overlay_engine) == "rounded_corners") ? int(shadow_radius) : 0; 515 | uint32_t edges = 0; 516 | for (auto& area : layout_areas) 517 | { 518 | auto g = area->get_geometry(); 519 | auto b = theme.get_input_size(); 520 | g.width = g.width ?: 1; 521 | g.height = g.height ?: 1; 522 | if (area->get_type() & DECORATION_AREA_RESIZE_BIT) 523 | { 524 | if (b <= MIN_RESIZE_HANDLE_SIZE) 525 | { 526 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{b, b, b, b}); 527 | } else if ((area->get_type() == DECORATION_AREA_RESIZE_TOP) || 528 | (area->get_type() == DECORATION_AREA_RESIZE_BOTTOM)) 529 | { 530 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{0, 0, b, b}); 531 | } else if ((area->get_type() == DECORATION_AREA_RESIZE_LEFT) || 532 | (area->get_type() == DECORATION_AREA_RESIZE_RIGHT)) 533 | { 534 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{b, b, 0, 0}); 535 | } 536 | } 537 | 538 | if (((b - radius * 2) > MIN_RESIZE_HANDLE_SIZE) && (area->get_type() == DECORATION_AREA_RESIZE_TOP)) 539 | { 540 | g.height /= 2; 541 | } 542 | 543 | if (g & this->current_input) 544 | { 545 | if (area->get_type() & DECORATION_AREA_RESIZE_BIT) 546 | { 547 | edges |= (area->get_type() & ~DECORATION_AREA_RESIZE_BIT); 548 | } 549 | } 550 | } 551 | 552 | return edges; 553 | } 554 | 555 | /** Update the cursor based on @current_input */ 556 | void pixdecor_layout_t::update_cursor() 557 | { 558 | uint32_t edges = calculate_resize_edges(); 559 | auto area = find_area_at(this->current_input); 560 | if (area && (area->get_type() == DECORATION_AREA_BUTTON)) 561 | { 562 | wf::get_core().set_cursor("default"); 563 | return; 564 | } 565 | 566 | auto cursor_name = edges > 0 ? 567 | wlr_xcursor_get_resize_name((wlr_edges)edges) : "default"; 568 | wf::get_core().set_cursor(cursor_name); 569 | } 570 | 571 | void pixdecor_layout_t::set_maximize(bool state) 572 | { 573 | maximized = state; 574 | } 575 | 576 | void pixdecor_layout_t::handle_focus_lost() 577 | { 578 | if (is_grabbed) 579 | { 580 | this->is_grabbed = false; 581 | auto area = find_area_at(grab_origin); 582 | if (area && (area->get_type() == DECORATION_AREA_BUTTON)) 583 | { 584 | area->as_button().set_pressed(false); 585 | } 586 | } 587 | 588 | this->unset_hover(current_input); 589 | } 590 | } 591 | } 592 | -------------------------------------------------------------------------------- /src/deco-layout.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "deco-button.hpp" 6 | 7 | namespace wf 8 | { 9 | namespace pixdecor 10 | { 11 | static constexpr uint32_t DECORATION_AREA_RENDERABLE_BIT = (1 << 16); 12 | static constexpr uint32_t DECORATION_AREA_RESIZE_BIT = (1 << 17); 13 | static constexpr uint32_t DECORATION_AREA_MOVE_BIT = (1 << 18); 14 | static constexpr uint32_t DECORATION_AREA_SHADOW_BIT = (1 << 19); 15 | 16 | 17 | /** Different types of areas around the decoration */ 18 | enum decoration_area_type_t 19 | { 20 | DECORATION_AREA_MOVE = DECORATION_AREA_MOVE_BIT, 21 | DECORATION_AREA_TITLE = 22 | DECORATION_AREA_MOVE_BIT | DECORATION_AREA_RENDERABLE_BIT, 23 | DECORATION_AREA_BUTTON = DECORATION_AREA_RENDERABLE_BIT, 24 | DECORATION_AREA_SHADOW = DECORATION_AREA_SHADOW_BIT, 25 | DECORATION_AREA_RESIZE_LEFT = WLR_EDGE_LEFT | DECORATION_AREA_RESIZE_BIT, 26 | DECORATION_AREA_RESIZE_RIGHT = WLR_EDGE_RIGHT | DECORATION_AREA_RESIZE_BIT, 27 | DECORATION_AREA_RESIZE_TOP = WLR_EDGE_TOP | DECORATION_AREA_RESIZE_BIT, 28 | DECORATION_AREA_RESIZE_BOTTOM = WLR_EDGE_BOTTOM | DECORATION_AREA_RESIZE_BIT, 29 | }; 30 | 31 | /** 32 | * Represents an area of the decoration which reacts to input events. 33 | */ 34 | struct decoration_area_t 35 | { 36 | public: 37 | /** 38 | * Initialize a new decoration area with the given type and geometry 39 | */ 40 | decoration_area_t(decoration_area_type_t type, wf::geometry_t g); 41 | 42 | /** 43 | * Initialize a new decoration area holding a button. 44 | * 45 | * @param g The geometry of the button. 46 | * @param damage_callback Callback to execute when button needs repaint. 47 | * @param theme The theme to use for the button. 48 | */ 49 | decoration_area_t(wf::geometry_t g, 50 | std::function damage_callback, 51 | pixdecor_theme_t& theme); 52 | 53 | /** @return The geometry of the decoration area, relative to the layout */ 54 | wf::geometry_t get_geometry() const; 55 | void set_geometry(wf::geometry_t g); 56 | 57 | /** @return The area's button, if the area is a button. Otherwise UB */ 58 | button_t& as_button(); 59 | 60 | /** @return The type of the decoration area */ 61 | decoration_area_type_t get_type() const; 62 | 63 | private: 64 | std::function damage_callback; 65 | decoration_area_type_t type; 66 | wf::geometry_t geometry; 67 | 68 | /* For buttons only */ 69 | std::unique_ptr button; 70 | }; 71 | 72 | 73 | /** 74 | * Action which needs to be taken in response to an input event 75 | */ 76 | enum decoration_layout_action_t 77 | { 78 | DECORATION_ACTION_NONE = 0, 79 | /* Drag actions */ 80 | DECORATION_ACTION_MOVE = 1, 81 | DECORATION_ACTION_RESIZE = 2, 82 | /* Button actions */ 83 | DECORATION_ACTION_CLOSE = 3, 84 | DECORATION_ACTION_TOGGLE_MAXIMIZE = 4, 85 | DECORATION_ACTION_MINIMIZE = 5, 86 | /* Axis actions */ 87 | DECORATION_ACTION_SHADE = 6, 88 | DECORATION_ACTION_UNSHADE = 7, 89 | }; 90 | 91 | class pixdecor_theme_t; 92 | /** 93 | * Manages the layout of the decorations, i.e positioning of the title, 94 | * buttons, etc. 95 | * 96 | * Also dispatches the input events to the appropriate place. 97 | */ 98 | class pixdecor_layout_t 99 | { 100 | public: 101 | /** 102 | * Create a new decoration layout for the given theme. 103 | * When the theme changes, the decoration layout needs to be created again. 104 | * 105 | * @param damage_callback The function to be called when a part of the 106 | * layout needs a repaint. 107 | */ 108 | pixdecor_layout_t(pixdecor_theme_t& theme, 109 | std::function damage_callback); 110 | ~pixdecor_layout_t(); 111 | 112 | /** Regenerate layout using the new size */ 113 | void resize(int width, int height); 114 | 115 | /** 116 | * @return The decoration areas which need to be rendered, in top to bottom 117 | * order. 118 | */ 119 | std::vector> get_renderable_areas(); 120 | 121 | /** @return The combined region of all layout areas */ 122 | wf::region_t calculate_region() const; 123 | 124 | /** Limit region to title bar area by region intersection 125 | * @param region to limit by title area 126 | * @return the intersection between region and the title bar area */ 127 | wf::region_t limit_region(wf::region_t & region) const; 128 | 129 | struct action_response_t 130 | { 131 | decoration_layout_action_t action; 132 | /* For resizing action, determine the edges for resize request */ 133 | uint32_t edges; 134 | }; 135 | 136 | /** Handle motion event to (x, y) relative to the decoration */ 137 | action_response_t handle_motion(int x, int y); 138 | 139 | /** 140 | * Handle press or release event. 141 | * @param pressed Whether the event is a press(true) or release(false) 142 | * event. 143 | * @return The action which needs to be carried out in response to this 144 | * event. 145 | */ 146 | action_response_t handle_press_event(bool pressed = true); 147 | 148 | /** 149 | * Handle axis event. 150 | * @param delta The delta of the axis event denoting direction 151 | * @return The action which needs to be carried out in response to this 152 | * event. 153 | */ 154 | action_response_t handle_axis_event(int delta); 155 | 156 | /** 157 | * Handle focus lost event. 158 | */ 159 | void handle_focus_lost(); 160 | 161 | void set_maximize(bool state); 162 | 163 | private: 164 | const int titlebar_size; 165 | const int border_size; 166 | pixdecor_theme_t& theme; 167 | bool maximized; 168 | 169 | std::function damage_callback; 170 | 171 | std::vector> layout_areas; 172 | wf::geometry_t cached_titlebar; 173 | 174 | bool is_grabbed = false; 175 | /* Position where the grab has started */ 176 | wf::point_t grab_origin; 177 | /* Last position of the input */ 178 | wf::point_t current_input; 179 | /* double-click timer */ 180 | wf::wl_timer timer; 181 | bool double_click_at_release = false; 182 | 183 | /** Create buttons in the layout, and return their total geometry */ 184 | wf::geometry_t create_buttons(int width, int height); 185 | 186 | /** Calculate resize edges based on @current_input */ 187 | uint32_t calculate_resize_edges() const; 188 | /** Update the cursor based on @current_input */ 189 | void update_cursor(); 190 | 191 | /** 192 | * Find the layout area at the given coordinates, if any 193 | * @return The layout area or null on failure 194 | */ 195 | nonstd::observer_ptr find_area_at(wf::point_t point); 196 | 197 | /** Unset hover state of hovered button at @position, if any */ 198 | void unset_hover(wf::point_t position); 199 | }; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/deco-subsurface.cpp: -------------------------------------------------------------------------------- 1 | #include "wayfire/geometry.hpp" 2 | #include "wayfire/scene-input.hpp" 3 | #include "wayfire/scene-operations.hpp" 4 | #include "wayfire/scene-render.hpp" 5 | #include "wayfire/scene.hpp" 6 | #include "wayfire/signal-provider.hpp" 7 | #include "wayfire/toplevel.hpp" 8 | #include 9 | #define GLM_FORCE_RADIANS 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "deco-subsurface.hpp" 22 | #include "deco-layout.hpp" 23 | #include "deco-theme.hpp" 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include 32 | #include "shade.hpp" 33 | 34 | 35 | namespace wf 36 | { 37 | namespace pixdecor 38 | { 39 | wf::option_wrapper_t effect_color{"pixdecor/effect_color"}; 40 | wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; 41 | wf::option_wrapper_t titlebar_opt{"pixdecor/titlebar"}; 42 | wf::option_wrapper_t csd_titlebar_height{"pixdecor/csd_titlebar_height"}; 43 | wf::option_wrapper_t enable_shade{"pixdecor/enable_shade"}; 44 | wf::option_wrapper_t title_font{"pixdecor/title_font"}; 45 | wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; 46 | wf::option_wrapper_t effect_type{"pixdecor/effect_type"}; 47 | wf::option_wrapper_t maximized_borders{"pixdecor/maximized_borders"}; 48 | wf::option_wrapper_t maximized_shadows{"pixdecor/maximized_shadows"}; 49 | wf::option_wrapper_t title_text_align{"pixdecor/title_text_align"}; 50 | 51 | class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_interaction_t, 52 | public wf::touch_interaction_t 53 | { 54 | std::weak_ptr _view; 55 | wf::signal::connection_t title_set = 56 | [=] (wf::view_title_changed_signal *ev) 57 | { 58 | if (auto view = _view.lock()) 59 | { 60 | view->damage(); 61 | } 62 | }; 63 | 64 | void update_title(int width, int height, int t_width, int border, int buttons_width, double scale) 65 | { 66 | if (auto view = _view.lock()) 67 | { 68 | int target_width = width * scale; 69 | int target_height = height * scale; 70 | 71 | if ((int(title_text_align) != title_texture.title_text_align) || 72 | (view->get_title() != title_texture.current_text) || 73 | (target_width != title_texture.tex.width) || 74 | (std::string(title_font) != title_texture.title_font_string) || 75 | (target_height != title_texture.tex.height) || 76 | (view->activated != title_texture.rendered_for_activated_state)) 77 | { 78 | auto surface = theme.render_text(view->get_title(), 79 | target_width, target_height, t_width, border, buttons_width, view->activated); 80 | cairo_surface_upload_to_texture(surface, title_texture.tex); 81 | cairo_surface_destroy(surface); 82 | title_texture.title_font_string = title_font; 83 | title_texture.current_text = view->get_title(); 84 | title_texture.title_text_align = int(title_text_align); 85 | title_texture.rendered_for_activated_state = view->activated; 86 | } 87 | } 88 | } 89 | 90 | struct 91 | { 92 | wf::simple_texture_t tex; 93 | std::string current_text = ""; 94 | bool rendered_for_activated_state = false; 95 | int title_text_align = int(title_text_align); 96 | std::string title_font_string = title_font; 97 | } title_texture; 98 | 99 | public: 100 | pixdecor_theme_t theme; 101 | pixdecor_layout_t layout; 102 | wf::region_t cached_region; 103 | 104 | wf::dimensions_t size; 105 | 106 | int current_thickness; 107 | int current_titlebar; 108 | wf::pointf_t current_cursor_position; 109 | 110 | simple_decoration_node_t(wayfire_toplevel_view view) : 111 | node_t(false), 112 | theme{}, 113 | layout{theme, [=] (wlr_box box) { wf::scene::damage_node(shared_from_this(), box + get_offset()); }} 114 | { 115 | this->_view = view->weak_from_this(); 116 | view->connect(&title_set); 117 | 118 | // make sure to hide frame if the view is fullscreen 119 | update_decoration_size(); 120 | 121 | current_cursor_position.x = current_cursor_position.y = FLT_MIN; 122 | } 123 | 124 | ~simple_decoration_node_t() 125 | { 126 | remove_shade_transformers(); 127 | } 128 | 129 | wf::point_t get_offset() 130 | { 131 | auto view = _view.lock(); 132 | if (view && view->pending_tiled_edges() && !maximized_borders && !maximized_shadows) 133 | { 134 | return {0, -current_titlebar}; 135 | } 136 | 137 | return {-current_thickness, -current_titlebar}; 138 | } 139 | 140 | void render_title(const wf::render_target_t& fb, const wf::region_t& scissor, 141 | const wf::geometry_t& geometry, int t_width, int border, int buttons_width) 142 | { 143 | update_title(geometry.width, geometry.height, t_width, border, buttons_width, fb.scale); 144 | OpenGL::render_texture(title_texture.tex.tex, fb, geometry, 145 | glm::vec4(1.0f), OpenGL::TEXTURE_TRANSFORM_INVERT_Y | OpenGL::RENDER_FLAG_CACHED); 146 | 147 | for (auto& box : scissor) 148 | { 149 | fb.logic_scissor(wlr_box_from_pixman_box(box)); 150 | OpenGL::draw_cached(); 151 | } 152 | 153 | OpenGL::clear_cached(); 154 | } 155 | 156 | void render_region(const wf::render_target_t& fb, wf::point_t origin, const wf::region_t& region) 157 | { 158 | int border = theme.get_border_size(); 159 | wlr_box geometry{origin.x, origin.y, size.width, size.height}; 160 | 161 | bool activated = false; 162 | bool maximized = false; 163 | if (auto view = _view.lock()) 164 | { 165 | activated = view->activated; 166 | maximized = view->pending_tiled_edges(); 167 | } 168 | 169 | auto renderables = layout.get_renderable_areas(); 170 | auto offset = 171 | wf::point_t{origin.x, 172 | origin.y - 173 | ((maximized && (!maximized_shadows || !maximized_borders)) ? -border / 2 : border / 4)}; 174 | 175 | OpenGL::render_begin(fb); 176 | 177 | theme.render_background(fb, geometry, region, activated, current_cursor_position); 178 | 179 | if (!titlebar_opt) 180 | { 181 | OpenGL::render_end(); 182 | return; 183 | } 184 | 185 | int buttons_width = 0; 186 | for (auto item : renderables) 187 | { 188 | if (item->get_type() != DECORATION_AREA_TITLE) 189 | { 190 | buttons_width += item->get_geometry().width; 191 | } 192 | } 193 | 194 | /* Draw title & buttons */ 195 | auto title_border = border + ((std::string(overlay_engine) == "rounded_corners" && 196 | (!maximized || maximized_shadows)) ? int(shadow_radius) * 2 : 0); 197 | for (auto item : renderables) 198 | { 199 | if (item->get_type() == DECORATION_AREA_TITLE) 200 | { 201 | render_title(fb, region, 202 | item->get_geometry() + offset, size.width - border * 2, title_border, buttons_width); 203 | } else // button 204 | { 205 | item->as_button().render(fb, 206 | item->get_geometry() + origin, region); 207 | } 208 | } 209 | 210 | OpenGL::render_end(); 211 | } 212 | 213 | std::optional find_node_at(const wf::pointf_t& at) override 214 | { 215 | bool maximized = false; 216 | if (auto view = _view.lock()) 217 | { 218 | maximized = view->pending_tiled_edges(); 219 | } 220 | 221 | int border = theme.get_border_size(); 222 | int r = 223 | (std::string(overlay_engine) == "rounded_corners" && 224 | (!maximized || (maximized && maximized_shadows))) ? int(shadow_radius) * 2 : 0; 225 | r -= MIN_RESIZE_HANDLE_SIZE - std::min(border, MIN_RESIZE_HANDLE_SIZE); 226 | wf::pointf_t local = at - wf::pointf_t{get_offset()}; 227 | if (auto view = _view.lock()) 228 | { 229 | wf::geometry_t g = view->get_geometry(); 230 | g.x = g.y = 0; 231 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{-r, -r, -r, -r}); 232 | wf::region_t deco_region{g}; 233 | g = wf::expand_geometry_by_margins(g, wf::decoration_margins_t{-border, -border, -border, 234 | -theme.get_title_height() - border}); 235 | wf::region_t view_region{g}; 236 | deco_region ^= view_region; 237 | if (deco_region.contains_pointf(local)) 238 | { 239 | return wf::scene::input_node_t{ 240 | .node = this, 241 | .local_coords = local, 242 | }; 243 | } 244 | } 245 | 246 | return {}; 247 | } 248 | 249 | pointer_interaction_t& pointer_interaction() override 250 | { 251 | return *this; 252 | } 253 | 254 | touch_interaction_t& touch_interaction() override 255 | { 256 | return *this; 257 | } 258 | 259 | class decoration_render_instance_t : public wf::scene::render_instance_t 260 | { 261 | simple_decoration_node_t *self; 262 | wf::scene::damage_callback push_damage; 263 | 264 | wf::signal::connection_t on_surface_damage = 265 | [=] (wf::scene::node_damage_signal *data) 266 | { 267 | push_damage(data->region); 268 | }; 269 | 270 | public: 271 | decoration_render_instance_t(simple_decoration_node_t *self, wf::scene::damage_callback push_damage) 272 | { 273 | this->self = self; 274 | this->push_damage = push_damage; 275 | self->connect(&on_surface_damage); 276 | } 277 | 278 | void schedule_instructions(std::vector& instructions, 279 | const wf::render_target_t& target, wf::region_t& damage) override 280 | { 281 | auto our_region = self->cached_region + self->get_offset(); 282 | wf::region_t our_damage = damage & our_region; 283 | if (!our_damage.empty()) 284 | { 285 | instructions.push_back(wf::scene::render_instruction_t{ 286 | .instance = this, 287 | .target = target, 288 | .damage = std::move(our_damage), 289 | }); 290 | } 291 | } 292 | 293 | void render(const wf::render_target_t& target, 294 | const wf::region_t& region) override 295 | { 296 | auto offset = self->get_offset(); 297 | wlr_box rectangle{offset.x, offset.y, self->size.width, self->size.height}; 298 | bool activated = false; 299 | bool maximized = false; 300 | if (auto view = self->_view.lock()) 301 | { 302 | activated = view->activated; 303 | maximized = maximized_shadows ? false : view->pending_tiled_edges(); 304 | } 305 | 306 | if ((std::string(effect_type) != "none") || (std::string(overlay_engine) != "none")) 307 | { 308 | self->theme.smoke.step_effect(target, rectangle, std::string(effect_type) == "ink", 309 | self->current_cursor_position, self->theme.get_decor_color(activated), effect_color, 310 | self->theme.get_title_height(), self->theme.get_border_size(), 311 | (std::string(overlay_engine) == "rounded_corners" && !maximized) ? shadow_radius : 0); 312 | } 313 | 314 | self->render_region(target, offset, region); 315 | } 316 | }; 317 | 318 | void gen_render_instances(std::vector& instances, 319 | wf::scene::damage_callback push_damage, wf::output_t *output = nullptr) override 320 | { 321 | instances.push_back(std::make_unique(this, push_damage)); 322 | } 323 | 324 | wf::geometry_t get_bounding_box() override 325 | { 326 | return wf::construct_box(get_offset(), size); 327 | } 328 | 329 | /* wf::compositor_surface_t implementation */ 330 | void handle_pointer_enter(wf::pointf_t point) override 331 | { 332 | point -= wf::pointf_t{get_offset()}; 333 | layout.handle_motion(point.x, point.y); 334 | } 335 | 336 | void handle_pointer_leave() override 337 | { 338 | layout.handle_focus_lost(); 339 | current_cursor_position.x = current_cursor_position.y = FLT_MIN; 340 | } 341 | 342 | void handle_pointer_motion(wf::pointf_t to, uint32_t) override 343 | { 344 | to -= wf::pointf_t{get_offset()}; 345 | handle_action(layout.handle_motion(to.x, to.y)); 346 | current_cursor_position = to; 347 | } 348 | 349 | void handle_pointer_button(const wlr_pointer_button_event& ev) override 350 | { 351 | if (ev.button != BTN_LEFT) 352 | { 353 | return; 354 | } 355 | 356 | handle_action(layout.handle_press_event(ev.state == WL_POINTER_BUTTON_STATE_PRESSED)); 357 | } 358 | 359 | void handle_pointer_axis(const wlr_pointer_axis_event& ev) override 360 | { 361 | if (ev.orientation == WL_POINTER_AXIS_VERTICAL_SCROLL) 362 | { 363 | handle_action(layout.handle_axis_event(ev.delta)); 364 | } 365 | } 366 | 367 | void pop_transformer(wayfire_view view) 368 | { 369 | if (view->get_transformed_node()->get_transformer(shade_transformer_name)) 370 | { 371 | view->get_transformed_node()->rem_transformer(shade_transformer_name); 372 | } 373 | } 374 | 375 | void remove_shade_transformers() 376 | { 377 | for (auto& view : wf::get_core().get_all_views()) 378 | { 379 | pop_transformer(view); 380 | } 381 | } 382 | 383 | std::shared_ptr ensure_transformer(wayfire_view view, int titlebar_height) 384 | { 385 | auto tmgr = view->get_transformed_node(); 386 | if (auto tr = tmgr->get_transformer(shade_transformer_name)) 387 | { 388 | return tr; 389 | } 390 | 391 | auto node = std::make_shared(view, titlebar_height); 392 | tmgr->add_transformer(node, wf::TRANSFORMER_2D, shade_transformer_name); 393 | auto tr = tmgr->get_transformer(shade_transformer_name); 394 | 395 | return tr; 396 | } 397 | 398 | void init_shade(wayfire_view view, bool shade, int titlebar_height) 399 | { 400 | if (!bool(enable_shade)) 401 | { 402 | return; 403 | } 404 | 405 | if (shade) 406 | { 407 | if (view && view->is_mapped()) 408 | { 409 | auto tr = ensure_transformer(view, titlebar_height); 410 | tr->set_titlebar_height(titlebar_height); 411 | tr->init_animation(shade); 412 | } 413 | } else 414 | { 415 | if (auto tr = 416 | view->get_transformed_node()->get_transformer( 417 | shade_transformer_name)) 418 | { 419 | tr->set_titlebar_height(titlebar_height); 420 | tr->init_animation(shade); 421 | } 422 | } 423 | } 424 | 425 | void handle_action(pixdecor_layout_t::action_response_t action) 426 | { 427 | if (auto view = _view.lock()) 428 | { 429 | switch (action.action) 430 | { 431 | case DECORATION_ACTION_MOVE: 432 | return wf::get_core().default_wm->move_request(view); 433 | 434 | case DECORATION_ACTION_RESIZE: 435 | return wf::get_core().default_wm->resize_request(view, action.edges); 436 | 437 | case DECORATION_ACTION_CLOSE: 438 | return view->close(); 439 | 440 | case DECORATION_ACTION_TOGGLE_MAXIMIZE: 441 | if (view->pending_tiled_edges()) 442 | { 443 | return wf::get_core().default_wm->tile_request(view, 0); 444 | } else 445 | { 446 | return wf::get_core().default_wm->tile_request(view, wf::TILED_EDGES_ALL); 447 | } 448 | 449 | break; 450 | 451 | case DECORATION_ACTION_SHADE: 452 | init_shade(view, true, current_titlebar); 453 | break; 454 | 455 | case DECORATION_ACTION_UNSHADE: 456 | init_shade(view, false, current_titlebar); 457 | break; 458 | 459 | case DECORATION_ACTION_MINIMIZE: 460 | return wf::get_core().default_wm->minimize_request(view, true); 461 | break; 462 | 463 | default: 464 | break; 465 | } 466 | } 467 | } 468 | 469 | void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t position) override 470 | { 471 | handle_touch_motion(time_ms, finger_id, position); 472 | handle_action(layout.handle_press_event()); 473 | } 474 | 475 | void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override 476 | { 477 | handle_action(layout.handle_press_event(false)); 478 | layout.handle_focus_lost(); 479 | } 480 | 481 | void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override 482 | { 483 | position -= wf::pointf_t{get_offset()}; 484 | layout.handle_motion(position.x, position.y); 485 | current_cursor_position = position; 486 | } 487 | 488 | void recreate_frame() 489 | { 490 | update_decoration_size(); 491 | if (auto view = _view.lock()) 492 | { 493 | auto size = wf::dimensions(view->get_pending_geometry()); 494 | layout.resize(size.width, size.height); 495 | wf::get_core().tx_manager->schedule_object(view->toplevel()); 496 | } 497 | } 498 | 499 | void resize(wf::dimensions_t dims) 500 | { 501 | if (auto view = _view.lock()) 502 | { 503 | theme.set_maximize(view->pending_tiled_edges()); 504 | layout.set_maximize(maximized_shadows ? 0 : view->pending_tiled_edges()); 505 | view->damage(); 506 | size = dims; 507 | layout.resize(size.width, size.height); 508 | if (!view->toplevel()->current().fullscreen) 509 | { 510 | this->cached_region = layout.calculate_region(); 511 | } 512 | 513 | view->damage(); 514 | } 515 | } 516 | 517 | void update_decoration_size() 518 | { 519 | if (auto view = _view.lock()) 520 | { 521 | view->damage(); 522 | bool fullscreen = view->toplevel()->pending().fullscreen; 523 | bool maximized = view->toplevel()->pending().tiled_edges; 524 | if (fullscreen) 525 | { 526 | current_thickness = 0; 527 | current_titlebar = 0; 528 | this->cached_region.clear(); 529 | } else 530 | { 531 | int shadow_thickness = std::string(overlay_engine) == "rounded_corners" && 532 | (!maximized || (maximized && maximized_shadows)) ? int(shadow_radius) * 2 : 0; 533 | 534 | current_thickness = theme.get_border_size() + shadow_thickness; 535 | current_titlebar = theme.get_title_height() + 536 | ((maximized && !titlebar_opt && !maximized_borders && 537 | !maximized_shadows) ? 0 : current_thickness); 538 | this->cached_region = layout.calculate_region(); 539 | } 540 | 541 | if (auto tr = 542 | view->get_transformed_node()->get_transformer( 543 | shade_transformer_name)) 544 | { 545 | tr->set_titlebar_height(current_titlebar); 546 | } 547 | 548 | view->damage(); 549 | } 550 | } 551 | }; 552 | 553 | simple_decorator_t::simple_decorator_t(wayfire_toplevel_view view) 554 | { 555 | this->view = view; 556 | this->shadow_thickness = 0; 557 | deco = std::make_shared(view); 558 | deco->resize(wf::dimensions(view->get_pending_geometry())); 559 | wf::scene::add_back(view->get_surface_root_node(), deco); 560 | 561 | view->connect(&on_view_activated); 562 | view->connect(&on_view_geometry_changed); 563 | view->connect(&on_view_fullscreen); 564 | view->connect(&on_view_tiled); 565 | 566 | on_view_activated = [this] (auto) 567 | { 568 | wf::scene::damage_node(deco, deco->get_bounding_box()); 569 | }; 570 | 571 | on_view_geometry_changed = [this] (auto) 572 | { 573 | deco->resize(wf::dimensions(this->view->get_geometry())); 574 | wf::get_core().tx_manager->schedule_object(this->view->toplevel()); 575 | }; 576 | 577 | on_view_tiled = [this] (auto) 578 | { 579 | deco->resize(wf::dimensions(this->view->get_geometry())); 580 | wf::get_core().tx_manager->schedule_object(this->view->toplevel()); 581 | }; 582 | 583 | on_view_fullscreen = [this] (auto) 584 | { 585 | if (!this->view->toplevel()->pending().fullscreen) 586 | { 587 | deco->resize(wf::dimensions(this->view->get_geometry())); 588 | wf::get_core().tx_manager->schedule_object(this->view->toplevel()); 589 | } 590 | }; 591 | } 592 | 593 | simple_decorator_t::~simple_decorator_t() 594 | { 595 | wf::scene::remove_child(deco); 596 | deco.reset(); 597 | } 598 | 599 | int simple_decorator_t::get_titlebar_height() 600 | { 601 | return deco->current_titlebar; 602 | } 603 | 604 | void simple_decorator_t::recreate_frame() 605 | { 606 | deco->recreate_frame(); 607 | } 608 | 609 | void simple_decorator_t::update_decoration_size() 610 | { 611 | deco->update_decoration_size(); 612 | } 613 | 614 | void simple_decorator_t::update_colors() 615 | { 616 | deco->theme.update_colors(); 617 | } 618 | 619 | void simple_decorator_t::effect_updated() 620 | { 621 | deco->theme.smoke.effect_updated(); 622 | } 623 | 624 | wf::decoration_margins_t simple_decorator_t::get_margins(const wf::toplevel_state_t& state) 625 | { 626 | if (state.fullscreen) 627 | { 628 | return {0, 0, 0, 0}; 629 | } 630 | 631 | deco->theme.set_maximize(state.tiled_edges); 632 | 633 | this->shadow_thickness = std::string(overlay_engine) == "rounded_corners" && 634 | (!state.tiled_edges || (state.tiled_edges && maximized_shadows)) ? int(shadow_radius) * 2 : 0; 635 | 636 | int thickness = deco->theme.get_border_size() + this->shadow_thickness; 637 | int titlebar = deco->theme.get_title_height() + 638 | ((state.tiled_edges && !titlebar_opt && !maximized_borders && !maximized_shadows) ? 0 : thickness); 639 | if (state.tiled_edges && !maximized_borders) 640 | { 641 | if (maximized_shadows) 642 | { 643 | if (!titlebar_opt) 644 | { 645 | titlebar = thickness; 646 | } 647 | } else 648 | { 649 | thickness = 0; 650 | } 651 | } 652 | 653 | double shade_progress = 0.0; 654 | if (auto tr = 655 | view->get_transformed_node()->get_transformer( 656 | shade_transformer_name)) 657 | { 658 | tr->set_titlebar_height(titlebar); 659 | shade_progress = tr->progression.shade; 660 | } 661 | 662 | if (view->has_data(custom_data_name)) 663 | { 664 | view->get_data(custom_data_name)->set_margins( 665 | {shadow_thickness, shadow_thickness, shadow_thickness, 666 | shadow_thickness + 667 | int((view->get_geometry().height - shadow_thickness - titlebar) * shade_progress)}); 668 | } else 669 | { 670 | view->store_data(std::make_unique(), custom_data_name); 671 | view->get_data(custom_data_name)->set_margins( 672 | {shadow_thickness, shadow_thickness, shadow_thickness, 673 | shadow_thickness + 674 | int((view->get_geometry().height - shadow_thickness - titlebar) * shade_progress)}); 675 | } 676 | 677 | return wf::decoration_margins_t{ 678 | .left = thickness, 679 | .right = thickness, 680 | .bottom = thickness, 681 | .top = titlebar, 682 | }; 683 | } 684 | 685 | void simple_decorator_t::update_animation() 686 | { 687 | auto margins = get_margins(view->toplevel()->current()); 688 | auto bbox = deco->get_bounding_box(); 689 | 690 | wf::region_t region; 691 | region |= wlr_box{bbox.x, bbox.y, bbox.width, margins.top}; 692 | region |= wlr_box{bbox.x, bbox.y, margins.left, bbox.height}; 693 | region |= wlr_box{bbox.x, bbox.y + bbox.height - margins.bottom, bbox.width, margins.bottom}; 694 | region |= wlr_box{bbox.x + bbox.width - margins.right, bbox.y, margins.right, bbox.height}; 695 | wf::scene::damage_node(deco, region); 696 | } 697 | } 698 | } 699 | -------------------------------------------------------------------------------- /src/deco-subsurface.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DECO_SUBSURFACE_HPP 2 | #define DECO_SUBSURFACE_HPP 3 | 4 | #include "wayfire/object.hpp" 5 | #include "wayfire/toplevel.hpp" 6 | #include 7 | #include 8 | 9 | static std::string custom_data_name = "wf-decoration-shadow-margin"; 10 | 11 | class wf_shadow_margin_t : public wf::custom_data_t 12 | { 13 | public: 14 | wf::decoration_margins_t get_margins() 15 | { 16 | return margins; 17 | } 18 | 19 | void set_margins(wf::decoration_margins_t margins) 20 | { 21 | this->margins = margins; 22 | } 23 | 24 | private: 25 | wf::decoration_margins_t margins = {0, 0, 0, 0}; 26 | }; 27 | 28 | namespace wf 29 | { 30 | namespace pixdecor 31 | { 32 | class simple_decoration_node_t; 33 | /** 34 | * A decorator object attached as custom data to a toplevel object. 35 | */ 36 | class simple_decorator_t : public wf::custom_data_t 37 | { 38 | wayfire_toplevel_view view; 39 | std::shared_ptr deco; 40 | 41 | wf::signal::connection_t on_view_activated; 42 | wf::signal::connection_t on_view_geometry_changed; 43 | wf::signal::connection_t on_view_fullscreen; 44 | wf::signal::connection_t on_view_tiled; 45 | 46 | public: 47 | void update_colors(); 48 | void effect_updated(); 49 | void recreate_frame(); 50 | void update_decoration_size(); 51 | simple_decorator_t(wayfire_toplevel_view view); 52 | ~simple_decorator_t(); 53 | wf::decoration_margins_t get_margins(const wf::toplevel_state_t& state); 54 | int get_titlebar_height(); 55 | void update_animation(); 56 | int shadow_thickness; 57 | }; 58 | } 59 | } 60 | 61 | #endif /* end of include guard: DECO_SUBSURFACE_HPP */ 62 | -------------------------------------------------------------------------------- /src/deco-theme.cpp: -------------------------------------------------------------------------------- 1 | #include "deco-theme.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace wf 9 | { 10 | namespace pixdecor 11 | { 12 | wf::option_wrapper_t border_size{"pixdecor/border_size"}; 13 | wf::option_wrapper_t titlebar{"pixdecor/titlebar"}; 14 | wf::option_wrapper_t fg_color{"pixdecor/fg_color"}; 15 | wf::option_wrapper_t bg_color{"pixdecor/bg_color"}; 16 | wf::option_wrapper_t fg_text_color{"pixdecor/fg_text_color"}; 17 | wf::option_wrapper_t bg_text_color{"pixdecor/bg_text_color"}; 18 | wf::option_wrapper_t button_minimize_image{"pixdecor/button_minimize_image"}; 19 | wf::option_wrapper_t button_maximize_image{"pixdecor/button_maximize_image"}; 20 | wf::option_wrapper_t button_close_image{"pixdecor/button_close_image"}; 21 | wf::option_wrapper_t button_color{"pixdecor/button_color"}; 22 | wf::option_wrapper_t button_line_thickness{"pixdecor/button_line_thickness"}; 23 | /** Create a new theme with the default parameters */ 24 | pixdecor_theme_t::pixdecor_theme_t() 25 | { 26 | gs = g_settings_new("org.gnome.desktop.interface"); 27 | 28 | // read initial colours 29 | update_colors(); 30 | } 31 | 32 | pixdecor_theme_t::~pixdecor_theme_t() 33 | { 34 | g_object_unref(gs); 35 | } 36 | 37 | void pixdecor_theme_t::update_colors(void) 38 | { 39 | fg = wf::color_t(fg_color); 40 | bg = wf::color_t(bg_color); 41 | fg_text = wf::color_t(fg_text_color); 42 | bg_text = wf::color_t(bg_text_color); 43 | } 44 | 45 | /** 46 | * @return A PangoFontDescription representing either a provided font from 47 | * the Title Font option or the system font, scaled by the text-scaling-factor GSetting. 48 | */ 49 | PangoFontDescription*pixdecor_theme_t::create_font_description() 50 | { 51 | GSettings *gs = g_settings_new("org.gnome.desktop.interface"); 52 | 53 | std::string title_font_val(title_font); 54 | bool using_system_font{}; 55 | 56 | if (title_font_val.empty()) 57 | { 58 | char *font = g_settings_get_string(gs, "font-name"); 59 | title_font_val = font; 60 | using_system_font = true; 61 | g_free(font); 62 | } 63 | 64 | PangoFontDescription *font_desc = pango_font_description_from_string(title_font_val.c_str()); 65 | int font_size = pango_font_description_get_size(font_desc); 66 | bool font_size_absolute; 67 | 68 | // font size will be 0 if nothing is specified - grab from system font if this is the case 69 | // if the font is the system font, then just pray it works 70 | if (!font_size && !using_system_font) 71 | { 72 | char *font = g_settings_get_string(gs, "font-name"); 73 | PangoFontDescription *sys_font_desc = pango_font_description_from_string(font); 74 | 75 | font_size = pango_font_description_get_size(sys_font_desc); 76 | font_size_absolute = pango_font_description_get_size_is_absolute(sys_font_desc); 77 | 78 | pango_font_description_free(sys_font_desc); 79 | g_free(font); 80 | } else 81 | { 82 | font_size_absolute = pango_font_description_get_size_is_absolute(font_desc); 83 | } 84 | 85 | // scale the font size if we got it by text-scaling-factor 86 | if (font_size) 87 | { 88 | double scaling_factor = g_settings_get_double(gs, "text-scaling-factor"); 89 | if (!scaling_factor) // should default to 1.0, but better safe than sorry 90 | { 91 | scaling_factor = 1.0; 92 | } 93 | 94 | if (font_size_absolute) 95 | { 96 | pango_font_description_set_absolute_size(font_desc, font_size * scaling_factor); 97 | } else 98 | { 99 | pango_font_description_set_size(font_desc, font_size * scaling_factor); 100 | } 101 | } 102 | 103 | g_object_unref(gs); 104 | return font_desc; 105 | } 106 | 107 | /** 108 | * @return A PangoFontDescription from create_font_description(), 109 | * originating from a global, internally managed instance, freeing the data upon exit. 110 | * It will also be updated with changes to the Title Font option. 111 | */ 112 | PangoFontDescription*pixdecor_theme_t::get_font_description() 113 | { 114 | static std::unique_ptr font_desc( 115 | create_font_description(), &pango_font_description_free); 116 | 117 | static std::once_flag once_flag; 118 | std::call_once(once_flag, [&] 119 | { 120 | title_font.set_callback([&] 121 | { 122 | font_desc.reset(create_font_description()); 123 | }); 124 | }); 125 | 126 | return font_desc.get(); 127 | } 128 | 129 | /** @return The available height for displaying the title */ 130 | int pixdecor_theme_t::get_font_height_px() 131 | { 132 | PangoFontDescription *font_desc = get_font_description(); 133 | int font_height = pango_font_description_get_size(font_desc); 134 | 135 | if (!pango_font_description_get_size_is_absolute(font_desc)) 136 | { 137 | font_height *= 4; 138 | font_height /= 3; 139 | } 140 | 141 | return font_height / PANGO_SCALE; 142 | } 143 | 144 | int pixdecor_theme_t::get_title_height() 145 | { 146 | int height = get_font_height_px(); 147 | height *= 3; 148 | height /= 2; 149 | height += 8; 150 | if (height < MIN_BAR_HEIGHT) 151 | { 152 | height = MIN_BAR_HEIGHT; 153 | } 154 | 155 | return titlebar ? height + ((maximized && !maximized_borders) ? border_size : 0) : 0; 156 | } 157 | 158 | /** @return The available border for resizing */ 159 | int pixdecor_theme_t::get_border_size() const 160 | { 161 | return (!maximized_borders && maximized) ? 0 : border_size; 162 | } 163 | 164 | /** @return The input area for resizing */ 165 | int pixdecor_theme_t::get_input_size() const 166 | { 167 | return std::max(get_border_size(), MIN_RESIZE_HANDLE_SIZE); 168 | } 169 | 170 | wf::color_t pixdecor_theme_t::get_decor_color(bool active) const 171 | { 172 | return active ? fg : bg; 173 | } 174 | 175 | void pixdecor_theme_t::set_maximize(bool state) 176 | { 177 | maximized = state; 178 | } 179 | 180 | /** 181 | * Fill the given rectangle with the background color(s). 182 | * 183 | * @param fb The target framebuffer, must have been bound already 184 | * @param rectangle The rectangle to redraw. 185 | * @param scissor The GL scissor rectangle to use. 186 | * @param active Whether to use active or inactive colors 187 | */ 188 | void pixdecor_theme_t::render_background(const wf::render_target_t& fb, 189 | wf::geometry_t rectangle, const wf::region_t& scissor, bool active, wf::pointf_t p) 190 | { 191 | if ((std::string(effect_type) == "none") && (std::string(overlay_engine) == "none")) 192 | { 193 | for (auto& box : scissor) 194 | { 195 | fb.logic_scissor(wlr_box_from_pixman_box(box)); 196 | OpenGL::render_rectangle(rectangle, get_decor_color(active), fb.get_orthographic_projection()); 197 | } 198 | } else 199 | { 200 | smoke.render_effect(fb, rectangle, scissor); 201 | } 202 | } 203 | 204 | /** 205 | * Render the given text on a cairo_surface_t with the given size. 206 | * The caller is responsible for freeing the memory afterwards. 207 | */ 208 | cairo_surface_t*pixdecor_theme_t::render_text(std::string text, 209 | int width, int height, int t_width, int border, int buttons_width, bool active) 210 | { 211 | const auto format = CAIRO_FORMAT_ARGB32; 212 | auto surface = cairo_image_surface_create(format, width, height); 213 | 214 | if (height == 0) 215 | { 216 | return surface; 217 | } 218 | 219 | auto cr = cairo_create(surface); 220 | 221 | PangoFontDescription *font_desc; 222 | PangoLayout *layout; 223 | int x, w, h; 224 | 225 | // render text 226 | font_desc = get_font_description(); 227 | 228 | layout = pango_cairo_create_layout(cr); 229 | pango_layout_set_font_description(layout, font_desc); 230 | pango_layout_set_text(layout, text.c_str(), text.size()); 231 | cairo_set_source_rgba(cr, active ? fg_text.r : bg_text.r, active ? fg_text.g : bg_text.g, 232 | active ? fg_text.b : bg_text.b, 1); 233 | pango_layout_get_pixel_size(layout, &w, &h); 234 | switch (int(title_text_align)) 235 | { 236 | // left 237 | case 0: 238 | x = border; 239 | break; 240 | 241 | // right 242 | case 2: 243 | x = t_width - (w + buttons_width + border); 244 | break; 245 | 246 | // center 247 | case 1: 248 | default: 249 | x = (t_width - w) / 2; 250 | break; 251 | } 252 | 253 | cairo_translate(cr, x, (height - h) / 2); 254 | pango_cairo_show_layout(cr, layout); 255 | g_object_unref(layout); 256 | cairo_destroy(cr); 257 | 258 | return surface; 259 | } 260 | 261 | cairo_surface_t*pixdecor_theme_t::get_button_surface(button_type_t button, 262 | const button_state_t& state, bool active) const 263 | { 264 | cairo_surface_t *button_surface = NULL; 265 | 266 | switch (button) 267 | { 268 | case BUTTON_CLOSE: 269 | if (!std::string(button_close_image).empty()) 270 | { 271 | button_surface = cairo_image_surface_create_from_png(std::string(button_close_image).c_str()); 272 | } 273 | 274 | break; 275 | 276 | case BUTTON_TOGGLE_MAXIMIZE: 277 | if (!std::string(button_maximize_image).empty()) 278 | { 279 | button_surface = cairo_image_surface_create_from_png(std::string(button_maximize_image).c_str()); 280 | } 281 | 282 | break; 283 | 284 | case BUTTON_MINIMIZE: 285 | if (!std::string(button_minimize_image).empty()) 286 | { 287 | button_surface = cairo_image_surface_create_from_png(std::string(button_minimize_image).c_str()); 288 | } 289 | 290 | break; 291 | 292 | default: 293 | break; 294 | } 295 | 296 | if (button_surface && (cairo_surface_status(button_surface) == CAIRO_STATUS_SUCCESS)) 297 | { 298 | return button_surface; 299 | } 300 | 301 | auto w = state.width; 302 | auto h = state.height; 303 | 304 | button_surface = cairo_image_surface_create( 305 | CAIRO_FORMAT_ARGB32, w, h); 306 | 307 | auto cr = cairo_create(button_surface); 308 | cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT); 309 | 310 | cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); 311 | cairo_set_source_rgba(cr, 0, 0, 0, 0); 312 | cairo_rectangle(cr, 0, 0, w, h); 313 | cairo_fill(cr); 314 | 315 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 316 | 317 | /** Draw the button */ 318 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); 319 | cairo_set_source_rgba(cr, 320 | wf::color_t(button_color).r, 321 | wf::color_t(button_color).g, 322 | wf::color_t(button_color).b, 323 | wf::color_t(button_color).a); 324 | double line_width = button_line_thickness; 325 | switch (button) 326 | { 327 | case BUTTON_CLOSE: 328 | cairo_set_line_width(cr, line_width * state.border); 329 | cairo_move_to(cr, 1.0 * w / 4.0, 330 | 1.0 * h / 4.0); 331 | cairo_line_to(cr, 3.0 * w / 4.0, 332 | 3.0 * h / 4.0); 333 | cairo_move_to(cr, 3.0 * w / 4.0, 334 | 1.0 * h / 4.0); 335 | cairo_line_to(cr, 1.0 * w / 4.0, 336 | 3.0 * h / 4.0); 337 | cairo_stroke(cr); 338 | break; 339 | 340 | case BUTTON_TOGGLE_MAXIMIZE: 341 | cairo_set_line_width(cr, line_width * state.border); 342 | cairo_rectangle(cr, w / 4.0, h / 4.0, w / 2.0, h / 2.0); 343 | cairo_stroke(cr); 344 | break; 345 | 346 | case BUTTON_MINIMIZE: 347 | cairo_set_line_width(cr, line_width * state.border); 348 | cairo_move_to(cr, 1.0 * w / 4.0, 349 | 3.0 * h / 4.0); 350 | cairo_line_to(cr, 3.0 * w / 4.0, 351 | 3.0 * h / 4.0); 352 | cairo_stroke(cr); 353 | break; 354 | 355 | default: 356 | assert(false); 357 | } 358 | 359 | cairo_destroy(cr); 360 | 361 | return button_surface; 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /src/deco-theme.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "deco-button.hpp" 5 | #include "deco-effects.hpp" 6 | 7 | #define MIN_RESIZE_HANDLE_SIZE 5 8 | #define LARGE_ICON_THRESHOLD 20 9 | #define MIN_BAR_HEIGHT 20 10 | 11 | namespace wf 12 | { 13 | namespace pixdecor 14 | { 15 | /** 16 | * A class which manages the outlook of decorations. 17 | * It is responsible for determining the background colors, sizes, etc. 18 | */ 19 | class pixdecor_theme_t 20 | { 21 | public: 22 | wf::option_wrapper_t title_font{"pixdecor/title_font"}; 23 | wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; 24 | wf::option_wrapper_t effect_type{"pixdecor/effect_type"}; 25 | wf::option_wrapper_t maximized_borders{"pixdecor/maximized_borders"}; 26 | wf::option_wrapper_t maximized_shadows{"pixdecor/maximized_shadows"}; 27 | wf::option_wrapper_t title_text_align{"pixdecor/title_text_align"}; 28 | /** Create a new theme with the default parameters */ 29 | pixdecor_theme_t(); 30 | ~pixdecor_theme_t(); 31 | 32 | /** @return The height of the system font in pixels */ 33 | int get_font_height_px(); 34 | /** @return The available height for displaying the title */ 35 | int get_title_height(); 36 | /** @return The available border for rendering */ 37 | int get_border_size() const; 38 | /** @return The available border for resizing */ 39 | int get_input_size() const; 40 | /** @return The decoration color */ 41 | wf::color_t get_decor_color(bool active) const; 42 | PangoFontDescription *create_font_description(); 43 | PangoFontDescription *get_font_description(); 44 | 45 | void update_colors(void); 46 | 47 | /** 48 | * Fill the given rectangle with the background color(s). 49 | * 50 | * @param fb The target framebuffer, must have been bound already. 51 | * @param rectangle The rectangle to redraw. 52 | * @param scissor The GL scissor rectangle to use. 53 | * @param active Whether to use active or inactive colors 54 | */ 55 | void render_background(const wf::render_target_t& fb, 56 | wf::geometry_t rectangle, const wf::region_t& scissor, bool active, wf::pointf_t p); 57 | 58 | /** 59 | * Render the given text on a cairo_surface_t with the given size. 60 | * The caller is responsible for freeing the memory afterwards. 61 | */ 62 | cairo_surface_t *render_text(std::string text, int width, int height, int t_width, int border, 63 | int buttons_width, bool active); 64 | 65 | struct button_state_t 66 | { 67 | /** Button width */ 68 | double width; 69 | /** Button height */ 70 | double height; 71 | /** Button outline size */ 72 | double border; 73 | /* Hovering... */ 74 | bool hover; 75 | }; 76 | 77 | /** background effects */ 78 | smoke_t smoke; 79 | 80 | /** 81 | * Get the icon for the given button. 82 | * The caller is responsible for freeing the memory afterwards. 83 | * 84 | * @param button The button type. 85 | * @param state The button state. 86 | */ 87 | cairo_surface_t *get_button_surface(button_type_t button, 88 | const button_state_t& state, bool active) const; 89 | 90 | void set_maximize(bool state); 91 | 92 | private: 93 | 94 | GSettings *gs; 95 | wf::color_t fg; 96 | wf::color_t bg; 97 | wf::color_t fg_text; 98 | wf::color_t bg_text; 99 | bool maximized; 100 | }; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/decoration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "deco-subsurface.hpp" 12 | #include "wayfire/core.hpp" 13 | #include "wayfire/plugin.hpp" 14 | #include "wayfire/signal-provider.hpp" 15 | #include "wayfire/toplevel-view.hpp" 16 | #include "wayfire/toplevel.hpp" 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "shade.hpp" 24 | 25 | namespace wf 26 | { 27 | namespace pixdecor 28 | { 29 | int handle_theme_updated(int fd, uint32_t mask, void *data) 30 | { 31 | int bufsz = sizeof(inotify_event) + NAME_MAX + 1; 32 | char buf[bufsz]; 33 | 34 | if ((mask & WL_EVENT_READABLE) == 0) 35 | { 36 | return 0; 37 | } 38 | 39 | if (read(fd, buf, bufsz) < 0) 40 | { 41 | return 0; 42 | } 43 | 44 | (*((std::function*)data))(); 45 | 46 | return 0; 47 | } 48 | 49 | class wayfire_pixdecor : public wf::plugin_interface_t 50 | { 51 | wf::option_wrapper_t border_size{"pixdecor/border_size"}; 52 | wf::option_wrapper_t title_font{"pixdecor/title_font"}; 53 | wf::option_wrapper_t title_text_align{"pixdecor/title_text_align"}; 54 | wf::option_wrapper_t titlebar{"pixdecor/titlebar"}; 55 | wf::option_wrapper_t maximized_borders{"pixdecor/maximized_borders"}; 56 | wf::option_wrapper_t maximized_shadows{"pixdecor/maximized_shadows"}; 57 | wf::option_wrapper_t fg_color{"pixdecor/fg_color"}; 58 | wf::option_wrapper_t bg_color{"pixdecor/bg_color"}; 59 | wf::option_wrapper_t fg_text_color{"pixdecor/fg_text_color"}; 60 | wf::option_wrapper_t bg_text_color{"pixdecor/bg_text_color"}; 61 | wf::option_wrapper_t button_color{"pixdecor/button_color"}; 62 | wf::option_wrapper_t button_line_thickness{"pixdecor/button_line_thickness"}; 63 | wf::option_wrapper_t button_spacing{"pixdecor/button_spacing"}; 64 | wf::option_wrapper_t button_x_offset{"pixdecor/button_x_offset"}; 65 | wf::option_wrapper_t button_y_offset{"pixdecor/button_y_offset"}; 66 | wf::option_wrapper_t button_minimize_image{"pixdecor/button_minimize_image"}; 67 | wf::option_wrapper_t button_maximize_image{"pixdecor/button_maximize_image"}; 68 | wf::option_wrapper_t button_close_image{"pixdecor/button_close_image"}; 69 | wf::option_wrapper_t ignore_views_string{"pixdecor/ignore_views"}; 70 | wf::option_wrapper_t always_decorate_string{"pixdecor/always_decorate"}; 71 | wf::option_wrapper_t effect_type{"pixdecor/effect_type"}; 72 | wf::option_wrapper_t overlay_engine{"pixdecor/overlay_engine"}; 73 | wf::option_wrapper_t effect_animate{"pixdecor/animate"}; 74 | wf::option_wrapper_t rounded_corner_radius{"pixdecor/rounded_corner_radius"}; 75 | wf::option_wrapper_t shadow_radius{"pixdecor/shadow_radius"}; 76 | wf::option_wrapper_t shadow_color{"pixdecor/shadow_color"}; 77 | wf::view_matcher_t ignore_views{"pixdecor/ignore_views"}; 78 | wf::view_matcher_t always_decorate{"pixdecor/always_decorate"}; 79 | wf::option_wrapper_t shade_modifier{"pixdecor/shade_modifier"}; 80 | wf::option_wrapper_t csd_titlebar_height{"pixdecor/csd_titlebar_height"}; 81 | wf::option_wrapper_t enable_shade{"pixdecor/enable_shade"}; 82 | wf::ipc_activator_t pixdecor_toggle_shade{"pixdecor/shade_toggle"}; 83 | wf::wl_idle_call idle_update_views; 84 | int inotify_fd; 85 | int wd_cfg_file; 86 | int wd_cfg_dir; 87 | wl_event_source *evsrc; 88 | std::function update_event; 89 | wf::effect_hook_t pre_hook; 90 | bool hook_set = false; 91 | 92 | wf::axis_callback shade_axis_cb; 93 | 94 | wf::signal::connection_t on_new_tx = 95 | [=] (wf::txn::new_transaction_signal *ev) 96 | { 97 | // For each transaction, we need to consider what happens with participating views 98 | for (const auto& obj : ev->tx->get_objects()) 99 | { 100 | if (auto toplevel = std::dynamic_pointer_cast(obj)) 101 | { 102 | // First check whether the toplevel already has decoration 103 | // In that case, we should just set the correct margins 104 | if (auto deco = toplevel->get_data()) 105 | { 106 | deco->update_decoration_size(); 107 | toplevel->pending().margins = deco->get_margins(toplevel->pending()); 108 | continue; 109 | } 110 | 111 | // Second case: the view is already mapped, or the transaction does not map it. 112 | // The view is not being decorated, so nothing to do here. 113 | if (toplevel->current().mapped || !toplevel->pending().mapped) 114 | { 115 | continue; 116 | } 117 | 118 | // Third case: the transaction will map the toplevel. 119 | auto view = wf::find_view_for_toplevel(toplevel); 120 | wf::dassert(view != nullptr, "Mapping a toplevel means there must be a corresponding view!"); 121 | if (should_decorate_view(view)) 122 | { 123 | if (auto deco = toplevel->get_data()) 124 | { 125 | deco->update_decoration_size(); 126 | } 127 | 128 | adjust_new_decorations(view); 129 | } 130 | } 131 | } 132 | }; 133 | 134 | wf::signal::connection_t on_decoration_state_changed = 135 | [=] (wf::view_decoration_state_updated_signal *ev) 136 | { 137 | auto toplevel = wf::toplevel_cast(ev->view); 138 | if (toplevel) 139 | { 140 | remove_decoration(toplevel); 141 | } 142 | 143 | update_view_decoration(ev->view); 144 | }; 145 | 146 | // allows criteria containing maximized or floating check 147 | wf::signal::connection_t on_view_tiled = 148 | [=] (wf::view_tiled_signal *ev) 149 | { 150 | update_view_decoration(ev->view); 151 | }; 152 | 153 | wf::signal::connection_t on_app_id_changed = 154 | [=] (wf::view_app_id_changed_signal *ev) 155 | { 156 | update_view_decoration(ev->view); 157 | }; 158 | 159 | wf::signal::connection_t on_title_changed = 160 | [=] (wf::view_title_changed_signal *ev) 161 | { 162 | update_view_decoration(ev->view); 163 | }; 164 | 165 | wf::signal::connection_t on_output_added = 166 | [=] (wf::output_added_signal *ev) 167 | { 168 | if (effect_animate || (std::string(effect_type) == "smoke") || (std::string(effect_type) == "ink")) 169 | { 170 | ev->output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); 171 | } 172 | }; 173 | 174 | wf::signal::connection_t on_output_removed = 175 | [=] (wf::output_removed_signal *ev) 176 | { 177 | ev->output->render->rem_effect(&pre_hook); 178 | }; 179 | 180 | void pop_transformer(wayfire_view view) 181 | { 182 | if (view->get_transformed_node()->get_transformer(shade_transformer_name)) 183 | { 184 | view->get_transformed_node()->rem_transformer(shade_transformer_name); 185 | } 186 | } 187 | 188 | void remove_shade_transformers() 189 | { 190 | for (auto& view : wf::get_core().get_all_views()) 191 | { 192 | pop_transformer(view); 193 | } 194 | } 195 | 196 | std::shared_ptr ensure_transformer(wayfire_view view, int titlebar_height) 197 | { 198 | auto tmgr = view->get_transformed_node(); 199 | if (auto tr = tmgr->get_transformer(shade_transformer_name)) 200 | { 201 | return tr; 202 | } 203 | 204 | auto node = std::make_shared(view, titlebar_height); 205 | tmgr->add_transformer(node, wf::TRANSFORMER_2D, shade_transformer_name); 206 | auto tr = tmgr->get_transformer(shade_transformer_name); 207 | 208 | return tr; 209 | } 210 | 211 | void init_shade(wayfire_view view, bool shade, int titlebar_height) 212 | { 213 | if (!bool(enable_shade)) 214 | { 215 | return; 216 | } 217 | 218 | if (shade) 219 | { 220 | if (view && view->is_mapped()) 221 | { 222 | auto tr = ensure_transformer(view, titlebar_height); 223 | tr->set_titlebar_height(titlebar_height); 224 | tr->init_animation(shade); 225 | } 226 | } else 227 | { 228 | if (auto tr = 229 | view->get_transformed_node()->get_transformer( 230 | shade_transformer_name)) 231 | { 232 | tr->set_titlebar_height(titlebar_height); 233 | tr->init_animation(shade); 234 | } 235 | } 236 | } 237 | 238 | public: 239 | 240 | void init() override 241 | { 242 | auto& ol = wf::get_core().output_layout; 243 | ol->connect(&on_output_added); 244 | ol->connect(&on_output_removed); 245 | wf::get_core().connect(&on_decoration_state_changed); 246 | wf::get_core().tx_manager->connect(&on_new_tx); 247 | wf::get_core().connect(&on_app_id_changed); 248 | wf::get_core().connect(&on_title_changed); 249 | wf::get_core().connect(&on_view_tiled); 250 | 251 | if (bool(enable_shade)) 252 | { 253 | wf::get_core().bindings->add_axis(shade_modifier, &shade_axis_cb); 254 | } 255 | 256 | pixdecor_toggle_shade.set_handler([=] (wf::output_t *output, wayfire_view view) 257 | { 258 | if (!bool(enable_shade)) 259 | { 260 | return false; 261 | } 262 | 263 | if (auto toplevel = wf::toplevel_cast(view)) 264 | { 265 | bool direction = true; 266 | auto deco = toplevel->toplevel()->get_data(); 267 | if (auto tr = 268 | view->get_transformed_node()->get_transformer( 269 | shade_transformer_name)) 270 | { 271 | direction = !tr->get_direction(); 272 | } 273 | 274 | init_shade(view, direction, 275 | deco ? deco->get_titlebar_height() : csd_titlebar_height); 276 | return true; 277 | } 278 | 279 | return false; 280 | }); 281 | 282 | shade_axis_cb = [=] (wlr_pointer_axis_event *ev) 283 | { 284 | auto v = wf::get_core().get_cursor_focus_view(); 285 | if (ev->orientation == WL_POINTER_AXIS_VERTICAL_SCROLL) 286 | { 287 | if (auto toplevel = wf::toplevel_cast(v)) 288 | { 289 | auto deco = toplevel->toplevel()->get_data(); 290 | init_shade(v, ev->delta < 0 ? true : false, 291 | deco ? deco->get_titlebar_height() : csd_titlebar_height); 292 | return true; 293 | } 294 | 295 | return true; 296 | } 297 | 298 | return false; 299 | }; 300 | 301 | enable_shade.set_callback([=] 302 | { 303 | if (bool(enable_shade)) 304 | { 305 | wf::get_core().bindings->add_axis(shade_modifier, &shade_axis_cb); 306 | } 307 | 308 | { 309 | wf::get_core().bindings->rem_binding(&shade_axis_cb); 310 | remove_shade_transformers(); 311 | } 312 | }); 313 | 314 | csd_titlebar_height.set_callback([=] () 315 | { 316 | for (auto& view : wf::get_core().get_all_views()) 317 | { 318 | if (auto tr = 319 | view->get_transformed_node()->get_transformer( 320 | shade_transformer_name)) 321 | { 322 | auto toplevel = toplevel_cast(view); 323 | if (toplevel) 324 | { 325 | if (!toplevel->toplevel()->get_data()) 326 | { 327 | tr->set_titlebar_height(csd_titlebar_height); 328 | } 329 | } 330 | } 331 | } 332 | }); 333 | 334 | for (auto& view : wf::get_core().get_all_views()) 335 | { 336 | update_view_decoration(view); 337 | } 338 | 339 | border_size.set_callback([=] 340 | { 341 | for (auto& view : wf::get_core().get_all_views()) 342 | { 343 | auto toplevel = wf::toplevel_cast(view); 344 | if (!toplevel || !toplevel->toplevel()->get_data()) 345 | { 346 | continue; 347 | } 348 | 349 | remove_decoration(toplevel); 350 | adjust_new_decorations(toplevel); 351 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 352 | } 353 | }); 354 | 355 | fg_color.set_callback([=] { update_colors(); }); 356 | bg_color.set_callback([=] { update_colors(); }); 357 | fg_text_color.set_callback([=] { update_colors(); }); 358 | bg_text_color.set_callback([=] { update_colors(); }); 359 | ignore_views_string.set_callback([=] 360 | { 361 | idle_update_views.run_once([=] () 362 | { 363 | for (auto& view : wf::get_core().get_all_views()) 364 | { 365 | auto toplevel = wf::toplevel_cast(view); 366 | if (!toplevel) 367 | { 368 | continue; 369 | } 370 | 371 | update_view_decoration(view); 372 | } 373 | }); 374 | }); 375 | always_decorate_string.set_callback([=] 376 | { 377 | idle_update_views.run_once([=] () 378 | { 379 | for (auto& view : wf::get_core().get_all_views()) 380 | { 381 | auto toplevel = wf::toplevel_cast(view); 382 | if (!toplevel) 383 | { 384 | continue; 385 | } 386 | 387 | update_view_decoration(view); 388 | } 389 | }); 390 | }); 391 | 392 | pre_hook = [=] () 393 | { 394 | for (auto& view : wf::get_core().get_all_views()) 395 | { 396 | auto toplevel = wf::toplevel_cast(view); 397 | if (!toplevel || !toplevel->toplevel()->get_data()) 398 | { 399 | continue; 400 | } 401 | 402 | auto deco = toplevel->toplevel()->get_data(); 403 | deco->update_animation(); 404 | } 405 | }; 406 | 407 | if (std::string(effect_type) != "none") 408 | { 409 | for (auto& o : wf::get_core().output_layout->get_outputs()) 410 | { 411 | o->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); 412 | } 413 | 414 | hook_set = true; 415 | } 416 | 417 | titlebar.set_callback([=] {option_changed_cb(false, true);}); 418 | effect_type.set_callback([=] {option_changed_cb(false, false);}); 419 | overlay_engine.set_callback([=] {option_changed_cb(true, false);recreate_frames();}); 420 | effect_animate.set_callback([=] {option_changed_cb(false, false);}); 421 | button_color.set_callback([=] {recreate_frames();}); 422 | button_line_thickness.set_callback([=] {recreate_frames();}); 423 | button_spacing.set_callback([=] {recreate_frames();}); 424 | button_x_offset.set_callback([=] {recreate_frames();}); 425 | button_y_offset.set_callback([=] {recreate_frames();}); 426 | button_minimize_image.set_callback([=] {recreate_frames();}); 427 | button_maximize_image.set_callback([=] {recreate_frames();}); 428 | button_close_image.set_callback([=] {recreate_frames();}); 429 | title_text_align.set_callback([=] 430 | { 431 | for (auto& view : wf::get_core().get_all_views()) 432 | { 433 | view->damage(); 434 | } 435 | }); 436 | title_font.set_callback([=] {recreate_frames();}); 437 | shadow_radius.set_callback([=] 438 | { 439 | option_changed_cb(false, (std::string(overlay_engine) == "rounded_corners")); 440 | }); 441 | shadow_color.set_callback([=] 442 | { 443 | for (auto& view : wf::get_core().get_all_views()) 444 | { 445 | auto toplevel = wf::toplevel_cast(view); 446 | if (!toplevel || !toplevel->toplevel()->get_data()) 447 | { 448 | continue; 449 | } 450 | 451 | view->damage(); 452 | } 453 | }); 454 | rounded_corner_radius.set_callback([=] 455 | { 456 | for (auto& view : wf::get_core().get_all_views()) 457 | { 458 | auto toplevel = wf::toplevel_cast(view); 459 | if (!toplevel || !toplevel->toplevel()->get_data()) 460 | { 461 | continue; 462 | } 463 | 464 | view->damage(); 465 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 466 | } 467 | }); 468 | maximized_borders.set_callback([=] 469 | { 470 | for (auto& view : wf::get_core().get_all_views()) 471 | { 472 | auto toplevel = wf::toplevel_cast(view); 473 | if (!toplevel || !toplevel->toplevel()->get_data() || 474 | !toplevel->pending_tiled_edges()) 475 | { 476 | continue; 477 | } 478 | 479 | view->damage(); 480 | remove_decoration(toplevel); 481 | adjust_new_decorations(toplevel); 482 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 483 | } 484 | }); 485 | maximized_shadows.set_callback([=] 486 | { 487 | for (auto& view : wf::get_core().get_all_views()) 488 | { 489 | auto toplevel = wf::toplevel_cast(view); 490 | if (!toplevel || !toplevel->toplevel()->get_data() || 491 | !toplevel->pending_tiled_edges()) 492 | { 493 | continue; 494 | } 495 | 496 | view->damage(); 497 | remove_decoration(toplevel); 498 | adjust_new_decorations(toplevel); 499 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 500 | } 501 | }); 502 | 503 | // set up the watch on the xsettings file 504 | inotify_fd = inotify_init1(IN_CLOEXEC); 505 | evsrc = wl_event_loop_add_fd(wf::get_core().ev_loop, inotify_fd, WL_EVENT_READABLE, 506 | handle_theme_updated, &this->update_event); 507 | 508 | // enable watches on xsettings file 509 | char *conf_dir = g_build_filename(g_get_user_config_dir(), "xsettingsd/", NULL); 510 | char *conf_file = g_build_filename(conf_dir, "xsettingsd.conf", NULL); 511 | wd_cfg_dir = inotify_add_watch(inotify_fd, conf_dir, IN_CREATE); 512 | wd_cfg_file = inotify_add_watch(inotify_fd, conf_file, IN_CLOSE_WRITE); 513 | g_free(conf_file); 514 | g_free(conf_dir); 515 | 516 | update_event = [=] (void) 517 | { 518 | update_colors(); 519 | }; 520 | 521 | dlopen("libpangocairo-1.0.so", RTLD_LAZY); 522 | } 523 | 524 | void fini() override 525 | { 526 | for (auto view : wf::get_core().get_all_views()) 527 | { 528 | if (auto toplevel = wf::toplevel_cast(view)) 529 | { 530 | remove_decoration(toplevel); 531 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 532 | } 533 | } 534 | 535 | if (hook_set) 536 | { 537 | for (auto& o : wf::get_core().output_layout->get_outputs()) 538 | { 539 | o->render->rem_effect(&pre_hook); 540 | } 541 | } 542 | 543 | on_output_added.disconnect(); 544 | on_output_removed.disconnect(); 545 | on_decoration_state_changed.disconnect(); 546 | on_new_tx.disconnect(); 547 | on_app_id_changed.disconnect(); 548 | on_title_changed.disconnect(); 549 | 550 | wf::get_core().bindings->rem_binding(&shade_axis_cb); 551 | remove_shade_transformers(); 552 | 553 | wl_event_source_remove(evsrc); 554 | inotify_rm_watch(inotify_fd, wd_cfg_file); 555 | inotify_rm_watch(inotify_fd, wd_cfg_dir); 556 | close(inotify_fd); 557 | } 558 | 559 | void recreate_frame(wayfire_toplevel_view toplevel) 560 | { 561 | auto deco = toplevel->toplevel()->get_data(); 562 | if (!deco) 563 | { 564 | return; 565 | } 566 | 567 | deco->recreate_frame(); 568 | } 569 | 570 | void recreate_frames() 571 | { 572 | for (auto& view : wf::get_core().get_all_views()) 573 | { 574 | auto toplevel = wf::toplevel_cast(view); 575 | if (!toplevel) 576 | { 577 | continue; 578 | } 579 | 580 | recreate_frame(toplevel); 581 | } 582 | } 583 | 584 | void option_changed_cb(bool resize_decorations, bool recreate_decorations) 585 | { 586 | if (effect_animate || (std::string(effect_type) == "smoke") || (std::string(effect_type) == "ink")) 587 | { 588 | if (!hook_set) 589 | { 590 | for (auto& o : wf::get_core().output_layout->get_outputs()) 591 | { 592 | o->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); 593 | } 594 | 595 | hook_set = true; 596 | } 597 | } else 598 | { 599 | if (hook_set) 600 | { 601 | for (auto& o : wf::get_core().output_layout->get_outputs()) 602 | { 603 | o->render->rem_effect(&pre_hook); 604 | } 605 | 606 | hook_set = false; 607 | } 608 | } 609 | 610 | if (recreate_decorations) 611 | { 612 | for (auto& view : wf::get_core().get_all_views()) 613 | { 614 | auto toplevel = wf::toplevel_cast(view); 615 | if (!toplevel || !toplevel->toplevel()->get_data()) 616 | { 617 | continue; 618 | } 619 | 620 | remove_decoration(toplevel); 621 | adjust_new_decorations(toplevel); 622 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 623 | } 624 | 625 | return; 626 | } 627 | 628 | for (auto& view : wf::get_core().get_all_views()) 629 | { 630 | auto toplevel = wf::toplevel_cast(view); 631 | if (!toplevel || !toplevel->toplevel()->get_data()) 632 | { 633 | continue; 634 | } 635 | 636 | view->damage(); 637 | toplevel->toplevel()->get_data()->effect_updated(); 638 | 639 | auto& pending = toplevel->toplevel()->pending(); 640 | if (!resize_decorations || (pending.tiled_edges != 0)) 641 | { 642 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 643 | continue; 644 | } 645 | 646 | if (std::string(overlay_engine) == "rounded_corners") 647 | { 648 | pending.margins = 649 | {int(shadow_radius) * 2, int(shadow_radius) * 2, 650 | int(shadow_radius) * 2, int(shadow_radius) * 2}; 651 | pending.geometry = wf::expand_geometry_by_margins(pending.geometry, pending.margins); 652 | } else 653 | { 654 | pending.geometry = wf::shrink_geometry_by_margins(pending.geometry, pending.margins); 655 | pending.margins = toplevel->toplevel()->get_data()->get_margins( 656 | toplevel->toplevel()->pending()); 657 | pending.geometry = wf::expand_geometry_by_margins(pending.geometry, pending.margins); 658 | } 659 | 660 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 661 | } 662 | } 663 | 664 | void update_colors() 665 | { 666 | for (auto& view : wf::get_core().get_all_views()) 667 | { 668 | auto toplevel = wf::toplevel_cast(view); 669 | if (!toplevel || !toplevel->toplevel()->get_data()) 670 | { 671 | continue; 672 | } 673 | 674 | auto deco = toplevel->toplevel()->get_data(); 675 | deco->update_colors(); 676 | view->damage(); 677 | } 678 | } 679 | 680 | /** 681 | * Uses view_matcher_t to match whether the given view needs to be 682 | * ignored for decoration 683 | * 684 | * @param view The view to match 685 | * @return Whether the given view should be decorated? 686 | */ 687 | bool ignore_decoration_of_view(wayfire_view view) 688 | { 689 | return ignore_views.matches(view); 690 | } 691 | 692 | bool should_decorate_view(wayfire_toplevel_view view) 693 | { 694 | return (view->should_be_decorated() && !ignore_decoration_of_view(view)) || always_decorate.matches( 695 | view); 696 | } 697 | 698 | void adjust_new_decorations(wayfire_toplevel_view view) 699 | { 700 | auto toplevel = view->toplevel(); 701 | 702 | if (!toplevel->get_data()) 703 | { 704 | toplevel->store_data(std::make_unique(view)); 705 | } 706 | 707 | auto deco = toplevel->get_data(); 708 | auto& pending = toplevel->pending(); 709 | pending.margins = deco->get_margins(pending); 710 | 711 | if (!pending.fullscreen && !pending.tiled_edges) 712 | { 713 | toplevel->pending().geometry = wf::expand_geometry_by_margins( 714 | toplevel->pending().geometry, pending.margins); 715 | } 716 | } 717 | 718 | void remove_decoration(wayfire_toplevel_view view) 719 | { 720 | view->toplevel()->erase_data(); 721 | auto& pending = view->toplevel()->pending(); 722 | if (!pending.fullscreen && !pending.tiled_edges) 723 | { 724 | pending.geometry = wf::shrink_geometry_by_margins(pending.geometry, pending.margins); 725 | } 726 | 727 | pending.margins = {0, 0, 0, 0}; 728 | 729 | std::string custom_data_name = "wf-decoration-shadow-margin"; 730 | if (view->has_data(custom_data_name)) 731 | { 732 | view->erase_data(custom_data_name); 733 | } 734 | } 735 | 736 | void update_view_decoration(wayfire_view view) 737 | { 738 | if (auto toplevel = wf::toplevel_cast(view)) 739 | { 740 | if (!toplevel) 741 | { 742 | return; 743 | } 744 | 745 | if (!toplevel->toplevel()->get_data() && (always_decorate.matches(view) || 746 | (should_decorate_view(toplevel) 747 | && 748 | !ignore_views.matches(view)))) 749 | { 750 | adjust_new_decorations(toplevel); 751 | } else if ((!always_decorate.matches(view) && 752 | (!should_decorate_view(toplevel) || ignore_views.matches(view)))) 753 | { 754 | remove_decoration(toplevel); 755 | } 756 | 757 | wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); 758 | } 759 | } 760 | }; 761 | } 762 | } 763 | 764 | DECLARE_WAYFIRE_PLUGIN(wf::pixdecor::wayfire_pixdecor); 765 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | pixdecor = shared_module('pixdecor', 2 | ['decoration.cpp', 'deco-subsurface.cpp', 'deco-button.cpp', 'deco-layout.cpp', 'deco-theme.cpp', 'deco-effects.cpp'], 3 | dependencies: [wayfire], 4 | install: true, 5 | install_dir: join_paths(get_option('libdir'), 'wayfire')) 6 | -------------------------------------------------------------------------------- /src/shade.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "deco-subsurface.hpp" 16 | 17 | const std::string shade_transformer_name = "pixdecor_shade"; 18 | 19 | namespace wf 20 | { 21 | namespace pixdecor 22 | { 23 | using namespace wf::scene; 24 | using namespace wf::animation; 25 | class shade_animation_t : public duration_t 26 | { 27 | public: 28 | using duration_t::duration_t; 29 | timed_transition_t shade{*this}; 30 | }; 31 | class pixdecor_shade : public wf::scene::view_2d_transformer_t 32 | { 33 | nonstd::observer_ptr deco = nullptr; 34 | wayfire_view view; 35 | wf::output_t *output; 36 | int titlebar_height; 37 | wf::option_wrapper_t shade_duration{"pixdecor/shade_duration"}; 38 | 39 | public: 40 | bool last_direction; 41 | shade_animation_t progression{shade_duration}; 42 | class simple_node_render_instance_t : public wf::scene::transformer_render_instance_t 43 | { 44 | wf::signal::connection_t on_node_damaged = 45 | [=] (node_damage_signal *ev) 46 | { 47 | push_to_parent(ev->region); 48 | }; 49 | 50 | pixdecor_shade *self; 51 | wayfire_view view; 52 | damage_callback push_to_parent; 53 | 54 | public: 55 | simple_node_render_instance_t(pixdecor_shade *self, damage_callback push_damage, 56 | wayfire_view view) : wf::scene::transformer_render_instance_t(self, 57 | push_damage, 58 | view->get_output()) 59 | { 60 | this->self = self; 61 | this->view = view; 62 | this->push_to_parent = push_damage; 63 | self->connect(&on_node_damaged); 64 | } 65 | 66 | ~simple_node_render_instance_t() 67 | {} 68 | 69 | void schedule_instructions( 70 | std::vector& instructions, 71 | const wf::render_target_t& target, wf::region_t& damage) 72 | { 73 | // We want to render ourselves only, the node does not have children 74 | instructions.push_back(render_instruction_t{ 75 | .instance = this, 76 | .target = target, 77 | .damage = damage & self->get_bounding_box(), 78 | }); 79 | } 80 | 81 | void render(const wf::render_target_t& target, 82 | const wf::region_t& region) 83 | { 84 | auto src_box = self->get_children_bounding_box(); 85 | gl_geometry src_geometry = {(float)src_box.x, (float)src_box.y, 86 | (float)src_box.x + src_box.width, (float)src_box.y + src_box.height}; 87 | auto src_tex = wf::scene::transformer_render_instance_t::get_texture( 88 | 1.0); 89 | auto shade_region = region; 90 | int height = src_box.height; 91 | auto titlebar = self->titlebar_height + self->get_shadow_margin(); 92 | src_box.y += self->titlebar_height; 93 | src_box.height *= 1.0 - 94 | (self->progression.shade * 95 | ((src_box.height - titlebar) / float(src_box.height))); 96 | auto progress_height = src_box.height; 97 | shade_region &= wf::region_t{src_box}; 98 | OpenGL::render_begin(target); 99 | for (const auto& box : shade_region) 100 | { 101 | target.logic_scissor(wlr_box_from_pixman_box(box)); 102 | OpenGL::render_transformed_texture(src_tex, 103 | {src_geometry.x1, src_geometry.y1 - (height - progress_height), src_geometry.x2, 104 | src_geometry.y2 - (height - progress_height)}, {}, 105 | target.get_orthographic_projection(), glm::vec4(1.0), 0); 106 | } 107 | 108 | shade_region = region; 109 | src_box = self->get_children_bounding_box(); 110 | src_box.height = self->titlebar_height; 111 | shade_region &= wf::region_t{src_box}; 112 | for (const auto& box : shade_region) 113 | { 114 | target.logic_scissor(wlr_box_from_pixman_box(box)); 115 | OpenGL::render_transformed_texture(src_tex, src_geometry, {}, 116 | target.get_orthographic_projection(), glm::vec4(1.0), 0); 117 | } 118 | 119 | OpenGL::render_end(); 120 | } 121 | }; 122 | 123 | pixdecor_shade(wayfire_view view, int titlebar_height) : wf::scene::view_2d_transformer_t(view) 124 | { 125 | this->view = view; 126 | this->output = view->get_output(); 127 | this->titlebar_height = titlebar_height; 128 | this->progression.shade.set(0.0, 1.0); 129 | if (output) 130 | { 131 | output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); 132 | } 133 | 134 | if (auto toplevel = wf::toplevel_cast(view)) 135 | { 136 | this->deco = toplevel->toplevel()->get_data(); 137 | } 138 | } 139 | 140 | void pop_transformer(wayfire_view view) 141 | { 142 | if (view->get_transformed_node()->get_transformer(shade_transformer_name)) 143 | { 144 | view->get_transformed_node()->rem_transformer(shade_transformer_name); 145 | } 146 | 147 | if (!deco && view->has_data(custom_data_name)) 148 | { 149 | view->erase_data(custom_data_name); 150 | } 151 | } 152 | 153 | wf::effect_hook_t pre_hook = [=] () 154 | { 155 | if (auto toplevel = wf::toplevel_cast(view)) 156 | { 157 | if (deco) 158 | { 159 | /* SSD */ 160 | deco->get_margins(toplevel->toplevel()->pending()); 161 | } else 162 | { 163 | /* CSD */ 164 | auto bg = view->get_surface_root_node()->get_bounding_box(); 165 | auto vg = toplevel->get_geometry(); 166 | auto margins = 167 | wf::decoration_margins_t{vg.x - bg.x, vg.y - bg.y, 168 | bg.width - ((vg.x - bg.x) + vg.width), bg.height - ((vg.y - bg.y) + vg.height)}; 169 | if (view->has_data(custom_data_name)) 170 | { 171 | view->get_data(custom_data_name)->set_margins( 172 | {0, 0, 0, 173 | int(((toplevel->get_geometry().height + margins.bottom) - titlebar_height) * 174 | progression.shade)}); 175 | } else 176 | { 177 | view->store_data(std::make_unique(), custom_data_name); 178 | view->get_data(custom_data_name)->set_margins( 179 | {0, 0, 0, 180 | int(((toplevel->get_geometry().height + margins.bottom) - titlebar_height) * 181 | progression.shade)}); 182 | } 183 | } 184 | } 185 | 186 | view->damage(); 187 | if (!this->progression.running() && !last_direction) 188 | { 189 | if (output) 190 | { 191 | output->render->rem_effect(&pre_hook); 192 | } 193 | 194 | pop_transformer(view); 195 | } 196 | }; 197 | 198 | void gen_render_instances(std::vector& instances, 199 | damage_callback push_damage, wf::output_t *shown_on) override 200 | { 201 | instances.push_back(std::make_unique( 202 | this, push_damage, view)); 203 | } 204 | 205 | std::optional find_node_at(const wf::pointf_t& at) override 206 | { 207 | auto bbox = this->get_children_bounding_box(); 208 | if (((at.y - bbox.y) < 209 | ((1.0 - 210 | (this->progression.shade * ((bbox.height - this->titlebar_height) / float(bbox.height)))) * 211 | bbox.height)) && 212 | ((at.y - bbox.y) > 0.0)) 213 | { 214 | return floating_inner_node_t::find_node_at(at); 215 | } 216 | 217 | return {}; 218 | } 219 | 220 | void init_animation(bool shade) 221 | { 222 | if (shade == last_direction) 223 | { 224 | return; 225 | } 226 | 227 | if (this->progression.running()) 228 | { 229 | this->progression.reverse(); 230 | } else 231 | { 232 | if ((shade && !this->progression.get_direction()) || 233 | (!shade && this->progression.get_direction())) 234 | { 235 | this->progression.reverse(); 236 | } 237 | 238 | this->progression.start(); 239 | } 240 | 241 | last_direction = shade; 242 | } 243 | 244 | void set_titlebar_height(int titlebar_height) 245 | { 246 | this->titlebar_height = titlebar_height; 247 | } 248 | 249 | int get_shadow_margin() 250 | { 251 | if (deco) 252 | { 253 | return deco->shadow_thickness; 254 | } else 255 | { 256 | if (auto toplevel = wf::toplevel_cast(view)) 257 | { 258 | auto bg = view->get_surface_root_node()->get_bounding_box(); 259 | auto vg = toplevel->get_geometry(); 260 | return bg.height - ((vg.y - bg.y) + vg.height); 261 | } 262 | } 263 | 264 | return 0; 265 | } 266 | 267 | bool get_direction() 268 | { 269 | return this->last_direction; 270 | } 271 | 272 | virtual ~pixdecor_shade() 273 | { 274 | if (output) 275 | { 276 | output->render->rem_effect(&pre_hook); 277 | } 278 | } 279 | }; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/smoke-shaders.hpp: -------------------------------------------------------------------------------- 1 | static const char *motion_source = 2 | R"( 3 | #version 320 es 4 | 5 | precision lowp image2D; 6 | 7 | layout (binding = 1, r32f) uniform readonly image2D in_b0u; 8 | layout (binding = 1, r32f) uniform writeonly image2D out_b0u; 9 | layout (binding = 2, r32f) uniform readonly image2D in_b0v; 10 | layout (binding = 2, r32f) uniform writeonly image2D out_b0v; 11 | layout (binding = 3, r32f) uniform readonly image2D in_b0d; 12 | layout (binding = 3, r32f) uniform writeonly image2D out_b0d; 13 | layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; 14 | 15 | layout(location = 1) uniform int title_height; 16 | layout(location = 2) uniform int border_size; 17 | layout(location = 3) uniform int px; 18 | layout(location = 4) uniform int py; 19 | layout(location = 5) uniform int width; 20 | layout(location = 6) uniform int height; 21 | layout(location = 7) uniform int rand1; 22 | layout(location = 8) uniform int rand2; 23 | layout(location = 9) uniform int radius; 24 | 25 | void motion(int x, int y) 26 | { 27 | int i, i0, i1, j, j0, j1, d = 2; 28 | 29 | if (x - d < 1) 30 | i0 = 1; 31 | else 32 | i0 = x - d; 33 | if (i0 + 2 * d > width - 1) 34 | i1 = width - 1; 35 | else 36 | i1 = i0 + 2 * d; 37 | 38 | if (y - d < 1) 39 | j0 = 1; 40 | else 41 | j0 = y - d; 42 | if (j0 + 2 * d > height - 1) 43 | j1 = height - 1; 44 | else 45 | j1 = j0 + 2 * d; 46 | 47 | for (i = i0; i < i1; i++) 48 | { 49 | for (j = j0; j < j1; j++) { 50 | if (i < radius || j < radius || i > (width - 1) - radius || j > (height - 1) - radius || (i > border_size && i < (width - 1) - border_size && j > (title_height - 1) && j < (height - 1) - border_size)) 51 | { 52 | continue; 53 | } 54 | vec4 b0u = imageLoad(in_b0u, ivec2(i, j)); 55 | vec4 b0v = imageLoad(in_b0v, ivec2(i, j)); 56 | vec4 b0d = imageLoad(in_b0d, ivec2(i, j)); 57 | float u = b0u.x; 58 | float v = b0v.x; 59 | float d = b0d.x; 60 | imageStore(out_b0u, ivec2(i, j), vec4(u + float(256 - (rand1 & 512)), 0.0, 0.0, 0.0)); 61 | imageStore(out_b0v, ivec2(i, j), vec4(v + float(256 - (rand2 & 512)), 0.0, 0.0, 0.0)); 62 | imageStore(out_b0d, ivec2(i, j), vec4(d + 1.0, 0.0, 0.0, 0.0)); 63 | } 64 | } 65 | } 66 | 67 | void main() 68 | { 69 | motion(px, py); 70 | } 71 | )"; 72 | 73 | // Generic smoke shader beginning 74 | static const char *smoke_header = 75 | R"( 76 | #version 320 es 77 | 78 | precision lowp image2D; 79 | 80 | layout (binding = 0, rgba32f) uniform readonly image2D in_tex; 81 | layout (binding = 0, rgba32f) uniform writeonly image2D out_tex; 82 | layout (binding = 1, r32f) uniform readonly image2D in_b0u; 83 | layout (binding = 1, r32f) uniform writeonly image2D out_b0u; 84 | layout (binding = 2, r32f) uniform readonly image2D in_b0v; 85 | layout (binding = 2, r32f) uniform writeonly image2D out_b0v; 86 | layout (binding = 3, r32f) uniform readonly image2D in_b0d; 87 | layout (binding = 3, r32f) uniform writeonly image2D out_b0d; 88 | layout (binding = 4, r32f) uniform readonly image2D in_b1u; 89 | layout (binding = 4, r32f) uniform writeonly image2D out_b1u; 90 | layout (binding = 5, r32f) uniform readonly image2D in_b1v; 91 | layout (binding = 5, r32f) uniform writeonly image2D out_b1v; 92 | layout (binding = 6, r32f) uniform readonly image2D in_b1d; 93 | layout (binding = 6, r32f) uniform writeonly image2D out_b1d; 94 | layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 95 | 96 | layout(location = 5) uniform int width; 97 | layout(location = 6) uniform int height; 98 | layout(location = 10) uniform int regionInfo[20]; 99 | )"; 100 | 101 | // Generic main method for a compute shader which runs for a particular region 102 | static const char *effect_run_for_region_main = 103 | R"( 104 | void main() 105 | { 106 | ivec2 pos = ivec2(gl_GlobalInvocationID.xy); 107 | int z = int(gl_GlobalInvocationID.z); 108 | 109 | ivec4 myRegion = ivec4(regionInfo[z * 5], regionInfo[z * 5 + 1], regionInfo[z * 5 + 2], regionInfo[z * 5 + 3]); 110 | if (regionInfo[z * 5 + 4] > 0) 111 | { 112 | pos = pos.yx; 113 | } 114 | 115 | if (all(lessThan(pos, myRegion.zw))) 116 | { 117 | pos += myRegion.xy; 118 | run_pixel(pos.x, pos.y); 119 | } 120 | })"; 121 | 122 | static const char *diffuse1_source = 123 | R"( 124 | void run_pixel(int x, int y) 125 | { 126 | int k; 127 | float t, a = 0.0002; 128 | 129 | vec4 s = imageLoad(in_b0u, ivec2(x, y)); 130 | vec4 d1 = imageLoad(in_b1u, ivec2(x - 1, y)); 131 | vec4 d2 = imageLoad(in_b1u, ivec2(x + 1, y)); 132 | vec4 d3 = imageLoad(in_b1u, ivec2(x, y - 1)); 133 | vec4 d4 = imageLoad(in_b1u, ivec2(x, y + 1)); 134 | float sx = s.x; 135 | float du1 = d1.x; 136 | float du2 = d2.x; 137 | float du3 = d3.x; 138 | float du4 = d4.x; 139 | float t1 = du1 + du2 + du3 + du4; 140 | s = imageLoad(in_b0v, ivec2(x, y)); 141 | d1 = imageLoad(in_b1v, ivec2(x - 1, y)); 142 | d2 = imageLoad(in_b1v, ivec2(x + 1, y)); 143 | d3 = imageLoad(in_b1v, ivec2(x, y - 1)); 144 | d4 = imageLoad(in_b1v, ivec2(x, y + 1)); 145 | float sy = s.x; 146 | du1 = d1.x; 147 | du2 = d2.x; 148 | du3 = d3.x; 149 | du4 = d4.x; 150 | float t2 = du1 + du2 + du3 + du4; 151 | imageStore(out_b1u, ivec2(x, y), vec4((sx + a * t1) / (1.0 + 4.0 * a) * 0.995, 0.0, 0.0, 0.0)); 152 | imageStore(out_b1v, ivec2(x, y), vec4((sy + a * t2) / (1.0 + 4.0 * a) * 0.995, 0.0, 0.0, 0.0)); 153 | } 154 | )"; 155 | 156 | static const char *project1_source = 157 | R"( 158 | void run_pixel(int x, int y) { 159 | int k, l, s; 160 | float h; 161 | 162 | h = 1.0 / float(width); 163 | s = width; 164 | 165 | vec4 s1 = imageLoad(in_b1u, ivec2(x - 1, y)); 166 | vec4 s2 = imageLoad(in_b1u, ivec2(x + 1, y)); 167 | vec4 s3 = imageLoad(in_b1v, ivec2(x, y - 1)); 168 | vec4 s4 = imageLoad(in_b1v, ivec2(x, y + 1)); 169 | float u1 = s1.x; 170 | float u2 = s2.x; 171 | float v1 = s3.x; 172 | float v2 = s4.x; 173 | imageStore(out_b0u, ivec2(x, y), vec4(0.0, 0.0, 0.0, 0.0)); 174 | imageStore(out_b0v, ivec2(x, y), vec4(-0.5 * h * (u2 - u1 + v2 - v1), 0.0, 0.0, 0.0)); 175 | } 176 | )"; 177 | 178 | static const char *project2_source = 179 | R"( 180 | void run_pixel(int x, int y) 181 | { 182 | int k, l, s; 183 | float h; 184 | 185 | h = 1.0 / float(width); 186 | s = width; 187 | 188 | vec4 s0 = imageLoad(in_b0v, ivec2(x, y)); 189 | vec4 s1 = imageLoad(in_b0u, ivec2(x - 1, y)); 190 | vec4 s2 = imageLoad(in_b0u, ivec2(x + 1, y)); 191 | vec4 s3 = imageLoad(in_b0u, ivec2(x, y - 1)); 192 | vec4 s4 = imageLoad(in_b0u, ivec2(x, y + 1)); 193 | float u1 = s1.x; 194 | float u2 = s2.x; 195 | float u3 = s3.x; 196 | float u4 = s4.x; 197 | imageStore(out_b0u, ivec2(x, y), vec4((s0.x + u1 + u2 + u3 + u4) / 4.0, 0.0, 0.0, 0.0)); 198 | })"; 199 | 200 | static const char *project3_source = 201 | R"( 202 | void run_pixel(int x, int y) 203 | { 204 | int k, l, s; 205 | float h; 206 | 207 | h = 1.0 / float(width); 208 | s = width; 209 | 210 | vec4 s0x = imageLoad(in_b1u, ivec2(x, y)); 211 | vec4 s1 = imageLoad(in_b0u, ivec2(x - 1, y)); 212 | vec4 s2 = imageLoad(in_b0u, ivec2(x + 1, y)); 213 | vec4 s0y = imageLoad(in_b1v, ivec2(x, y)); 214 | vec4 s3 = imageLoad(in_b0u, ivec2(x, y - 1)); 215 | vec4 s4 = imageLoad(in_b0u, ivec2(x, y + 1)); 216 | float su = s0x.x; 217 | float u1 = s1.x; 218 | float u2 = s2.x; 219 | float sv = s0y.x; 220 | float u3 = s3.x; 221 | float u4 = s4.x; 222 | imageStore(out_b1u, ivec2(x, y), vec4(su - 0.5 * (u2 - u1) / h, 0.0, 0.0, 0.0)); 223 | imageStore(out_b1v, ivec2(x, y), vec4(sv - 0.5 * (u4 - u3) / h, 0.0, 0.0, 0.0)); 224 | })"; 225 | 226 | static const char *advect1_source = 227 | R"( 228 | void run_pixel(int x, int y) /* b1.u, b1.v, b1.u, b0.u */ 229 | { 230 | int stride; 231 | int i, j; 232 | float fx, fy; 233 | stride = width; 234 | 235 | vec4 sx = imageLoad(in_b1u, ivec2(x, y)); 236 | vec4 sy = imageLoad(in_b1v, ivec2(x, y)); 237 | float ix = float(x) - sx.x; 238 | float iy = float(y) - sy.x; 239 | if (ix < 0.5) 240 | ix = 0.5; 241 | if (iy < 0.5) 242 | iy = 0.5; 243 | if (ix > float(width) - 1.5) 244 | ix = float(width) - 1.5; 245 | if (iy > float(height) - 1.5) 246 | iy = float(height) - 1.5; 247 | i = int(ix); 248 | j = int(iy); 249 | fx = ix - float(i); 250 | fy = iy - float(j); 251 | vec4 s0x = imageLoad(in_b1u, ivec2(i, j)); 252 | vec4 s1x = imageLoad(in_b1u, ivec2(i + 1, j)); 253 | vec4 s2x = imageLoad(in_b1u, ivec2(i, j + 1)); 254 | vec4 s3x = imageLoad(in_b1u, ivec2(i + 1, j + 1)); 255 | float p1 = (s0x.x * (1.0 - fx) + s1x.x * fx) * (1.0 - fy) + (s2x.x * (1.0 - fx) + s3x.x * fx) * fy; 256 | imageStore(out_b0u, ivec2(x, y), vec4(p1, 0.0, 0.0, 0.0)); 257 | ix = float(x) - sx.x; 258 | iy = float(y) - sy.x; 259 | if (ix < 0.5) 260 | ix = 0.5; 261 | if (iy < 0.5) 262 | iy = 0.5; 263 | if (ix > float(width) - 1.5) 264 | ix = float(width) - 1.5; 265 | if (iy > float(height) - 1.5) 266 | iy = float(height) - 1.5; 267 | i = int(ix); 268 | j = int(iy); 269 | fx = ix - float(i); 270 | fy = iy - float(j); 271 | vec4 s0y = imageLoad(in_b1v, ivec2(i, j)); 272 | vec4 s1y = imageLoad(in_b1v, ivec2(i + 1, j)); 273 | vec4 s2y = imageLoad(in_b1v, ivec2(i, j + 1)); 274 | vec4 s3y = imageLoad(in_b1v, ivec2(i + 1, j + 1)); 275 | float p2 = (s0y.x * (1.0 - fx) + s1y.x * fx) * (1.0 - fy) + (s2y.x * (1.0 - fx) + s3y.x * fx) * fy; 276 | imageStore(out_b0v, ivec2(x, y), vec4(p2, 0.0, 0.0, 0.0)); 277 | })"; 278 | 279 | static const char *project4_source = 280 | R"( 281 | void run_pixel(int x, int y) 282 | { 283 | int k, l, s; 284 | float h; 285 | 286 | h = 1.0 / float(width); 287 | s = width; 288 | 289 | vec4 s1 = imageLoad(in_b0u, ivec2(x - 1, y)); 290 | vec4 s2 = imageLoad(in_b0u, ivec2(x + 1, y)); 291 | vec4 s3 = imageLoad(in_b0v, ivec2(x, y - 1)); 292 | vec4 s4 = imageLoad(in_b0v, ivec2(x, y + 1)); 293 | float u1 = s1.x; 294 | float u2 = s2.x; 295 | float v1 = s3.x; 296 | float v2 = s4.x; 297 | imageStore(out_b1u, ivec2(x, y), vec4(0.0, 0.0, 0.0, 0.0)); 298 | imageStore(out_b1v, ivec2(x, y), vec4(-0.5 * h * (u2 - u1 + v2 - v1), 0.0, 0.0, 0.0)); 299 | })"; 300 | 301 | static const char *project5_source = 302 | R"( 303 | void run_pixel(int x, int y) 304 | { 305 | int k, l, s; 306 | float h; 307 | 308 | h = 1.0 / float(width); 309 | s = width; 310 | 311 | vec4 s0 = imageLoad(in_b1v, ivec2(x, y)); 312 | vec4 s1 = imageLoad(in_b1u, ivec2(x - 1, y)); 313 | vec4 s2 = imageLoad(in_b1u, ivec2(x + 1, y)); 314 | vec4 s3 = imageLoad(in_b1u, ivec2(x, y - 1)); 315 | vec4 s4 = imageLoad(in_b1u, ivec2(x, y + 1)); 316 | float u1 = s1.x; 317 | float u2 = s2.x; 318 | float u3 = s3.x; 319 | float u4 = s4.x; 320 | imageStore(out_b1u, ivec2(x, y), vec4((s0.x + u1 + u2 + u3 + u4) / 4.0, 0.0, 0.0, 0.0)); 321 | })"; 322 | 323 | static const char *project6_source = 324 | R"( 325 | void run_pixel(int x, int y) 326 | { 327 | int k, l, s; 328 | float h; 329 | 330 | h = 1.0 / float(width); 331 | s = width; 332 | 333 | vec4 s0x = imageLoad(in_b0u, ivec2(x, y)); 334 | vec4 s1 = imageLoad(in_b1u, ivec2(x - 1, y)); 335 | vec4 s2 = imageLoad(in_b1u, ivec2(x + 1, y)); 336 | vec4 s0y = imageLoad(in_b0v, ivec2(x, y)); 337 | vec4 s3 = imageLoad(in_b1u, ivec2(x, y - 1)); 338 | vec4 s4 = imageLoad(in_b1u, ivec2(x, y + 1)); 339 | float su = s0x.x; 340 | float u1 = s1.x; 341 | float u2 = s2.x; 342 | float sv = s0y.x; 343 | float u3 = s3.x; 344 | float u4 = s4.x; 345 | imageStore(out_b0u, ivec2(x, y), vec4(su - 0.5 * (u2 - u1) / h, 0.0, 0.0, 0.0)); 346 | imageStore(out_b0v, ivec2(x, y), vec4(sv - 0.5 * (u4 - u3) / h, 0.0, 0.0, 0.0)); 347 | })"; 348 | 349 | static const char *diffuse2_source = 350 | R"( 351 | void run_pixel(int x, int y) 352 | { 353 | int k, stride; 354 | float t, a = 0.0002; 355 | stride = width; 356 | 357 | vec4 s = imageLoad(in_b0d, ivec2(x, y)); 358 | vec4 d1 = imageLoad(in_b1d, ivec2(x - 1, y)); 359 | vec4 d2 = imageLoad(in_b1d, ivec2(x + 1, y)); 360 | vec4 d3 = imageLoad(in_b1d, ivec2(x, y - 1)); 361 | vec4 d4 = imageLoad(in_b1d, ivec2(x, y + 1)); 362 | float sz = s.x; 363 | float du1 = d1.x; 364 | float du2 = d2.x; 365 | float du3 = d3.x; 366 | float du4 = d4.x; 367 | t = du1 + du2 + du3 + du4; 368 | imageStore(out_b1d, ivec2(x, y), vec4((sz + a * t) / (1.0 + 4.0 * a) * 0.995, 0.0, 0.0, 0.0)); 369 | })"; 370 | 371 | static const char *advect2_source = 372 | R"( 373 | void run_pixel(int x, int y) /*b0u, b0v, b1d, b0d*/ 374 | { 375 | int stride; 376 | int i, j; 377 | float fx, fy; 378 | stride = width; 379 | 380 | vec4 sx = imageLoad(in_b0u, ivec2(x, y)); 381 | vec4 sy = imageLoad(in_b0v, ivec2(x, y)); 382 | float ix = float(x) - sx.x; 383 | float iy = float(y) - sy.x; 384 | if (ix < 0.5) 385 | ix = 0.5; 386 | if (iy < 0.5) 387 | iy = 0.5; 388 | if (ix > float(width) - 1.5) 389 | ix = float(width) - 1.5; 390 | if (iy > float(height) - 1.5) 391 | iy = float(height) - 1.5; 392 | i = int(ix); 393 | j = int(iy); 394 | fx = ix - float(i); 395 | fy = iy - float(j); 396 | vec4 s0z = imageLoad(in_b1d, ivec2(i, j)); 397 | vec4 s1z = imageLoad(in_b1d, ivec2(i + 1, j)); 398 | vec4 s2z = imageLoad(in_b1d, ivec2(i, j + 1)); 399 | vec4 s3z = imageLoad(in_b1d, ivec2(i + 1, j + 1)); 400 | float p1 = (s0z.x * (1.0 - fx) + s1z.x * fx) * (1.0 - fy) + (s2z.x * (1.0 - fx) + s3z.x * fx) * fy; 401 | imageStore(out_b0d, ivec2(x, y), vec4(p1, 0.0, 0.0, 0.0)); 402 | })"; 403 | 404 | static const char *render_source = 405 | R"( 406 | #version 320 es 407 | 408 | precision lowp image2D; 409 | 410 | layout (binding = 0, rgba32f) uniform writeonly image2D out_tex; 411 | layout (binding = 3, r32f) uniform readonly image2D in_b0d; 412 | layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 413 | 414 | layout(location = 4) uniform bool ink; 415 | layout(location = 8) uniform vec4 smoke_color; 416 | layout(location = 9) uniform vec4 decor_color; 417 | layout(location = 10) uniform int regionInfo[20]; 418 | 419 | void run_pixel(int x, int y) 420 | { 421 | float c, r, g, b, a; 422 | 423 | vec4 s = imageLoad(in_b0d, ivec2(x, y)); 424 | c = s.x * 800.0; 425 | if (c > 255.0) 426 | c = 255.0; 427 | a = c; 428 | vec3 color; 429 | if (ink) 430 | { 431 | if (c > 2.0) 432 | color = mix(decor_color.rgb, smoke_color.rgb, clamp(a, 0.0, 1.0)); 433 | else 434 | color = mix(decor_color.rgb, vec3(0.0, 0.0, 0.0), clamp(a, 0.0, 1.0)); 435 | if (c > 1.5) 436 | imageStore(out_tex, ivec2(x, y), vec4(color, 1.0)); 437 | else 438 | imageStore(out_tex, ivec2(x, y), decor_color); 439 | return; 440 | } else 441 | { 442 | color = mix(decor_color.rgb, smoke_color.rgb, clamp(a, 0.0, 1.0)); 443 | } 444 | imageStore(out_tex, ivec2(x, y), vec4(color, decor_color.a)); 445 | })"; 446 | --------------------------------------------------------------------------------