├── .gitignore ├── README.md ├── assets ├── accent-outlines.png ├── default-layout.png ├── framed.png ├── left-side-decoration.png ├── partial-outline.png ├── personal-layout.png ├── scroll.png ├── strange-1.png ├── strange-2.png ├── top-and-bottom-wrap.png └── top-left-and-right.png ├── meson.build ├── metadata ├── firedecor.xml └── meson.build └── src ├── cairo-simpler.hpp ├── executable.svg ├── firedecor-buttons.cpp ├── firedecor-buttons.hpp ├── firedecor-layout.cpp ├── firedecor-layout.hpp ├── firedecor-subsurface.cpp ├── firedecor-subsurface.hpp ├── firedecor-theme.cpp ├── firedecor-theme.hpp ├── firedecor.cpp └── meson.build /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .* 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE**: As I am no longer interested in wayfire (or window managers in general), 2 | this repository has been archived, and will no longer be maintained. 3 | 4 | # Firedecor 5 | An advanced window decoration plugin for the Wayfire window manager. 6 | 7 | ## Dependencies 8 | - `wayfire` (duh) 9 | - `librsvg` 10 | - `boost` 11 | 12 | ## Installation 13 | - Using the AUR: 14 | ``` 15 | yay -S wayfire-firedecor-git 16 | ``` 17 | - Building from source: 18 | ``` 19 | git clone https://github.com/AhoyISki/Firedecor 20 | cd Firedecor 21 | meson build 22 | meson compile -C build 23 | sudo meson install -C build 24 | ``` 25 | 26 | ## Goals 27 | - [x] Implement rounded corners; 28 | - [x] Implement individual border sizes; 29 | - [x] Implement app icons; 30 | - [x] Implement title bars on any direction; 31 | - [x] Implement completely modular decoration placement; 32 | - [x] Implement individual corner rounding; 33 | - [x] Multiple corner radii *Implemented as either the corner is there, or it is not; 34 | - [x] Implement multiple themes. 35 | - [x] Add accent colors to be used on anywhere on edges; 36 | - [x] Implement a maximum title size with potentially animated title scrolling; 37 | - [ ] Declare a minimum window size so the decorations can not look disgusting on small windows; 38 | - [ ] Implement shadows. 39 | 40 | ## Configuration 41 | 42 |
Font options 43 | 44 | - `font` will set what font will be used for titles. Default is `sans-serif`; 45 | - `font_size` will set the font size, in pixels, for the title. Default is `21`; 46 | - `active_title` will set the color for the font of active windows. Default is `\#1d1f21ff`; 47 | - `inactive_title` will set the color for the font of inactive windows. Default is `\#1d1f21ff`; 48 | - `max_title_size` will set a maximum title size, in pixels. If the title is bigger than this value, it will be capped so that the title, plus `...` can fit in the maximum title size. The default is `750`; 49 | 50 |
51 | 52 |
Border options 53 | 54 | - `border_size` can take up to 4 parameters. If one is used, it will be the border size for every edge of the windows. If 2 parameters are used, the first one determines the size of the top edge, and the second one determines the size of the remaining edges. If 3 are used, the first one will be used for all edges. If 4 parameters are used, they are used in the following order: top, left, bottom, right. Default is `30 10`; 55 | - `active_border` will set the color for the border of active windows. Default is `#1d1f21e6`; 56 | - `inactive_border` will set the color for the border of inactive windows. Default is `#1d1f21e6`; 57 | - `corner_radius` will set the radius for the corners of the windows. Use 0 for no radius. Default is `0`; 58 | 59 |
60 | 61 |
Outline options 62 | 63 | - `outline_size` will set the size for the outline of the window. Default is 0; 64 | - `active_outline` will set the color for the outline of active windows. Default is `#000000ff`; 65 | - `inactive_outline` will set the color for the outline of inactive windows. Default is `#000000ff`; 66 | 67 |
68 | 69 |
Button options 70 | 71 | - `button_size` will set the size of the buttons, in pixels. Default is 20; 72 | - `button_style` is a string that sets the style used for the buttons. By default, there are 3 styles: 73 | - `wayfire`, witch is similar to the one used by wayfire by default; 74 | - `firedecor`, my own spin on a buttons style, with animated symbols that change in size, and a different maximize symbol; 75 | - `simple`, where the buttons have no symbols inside of them, they are simple circles. 76 | 77 | If you place anything else on this string, say, something like `my_theme`, you will have to provide `png`s or `svg`s so that the plugin can draw custom buttons. To accomplish that, do the following: 78 | 1. Create the folder `~/.config/firedecor/button-styles/`; 79 | 2. In it, create a folder with the name `my_theme`; 80 | 3. Place figures for the buttons. They'll have to be called something like `type-status.png`, where `type` can be `close`, `minimize`, or `toggle-maximize`, and `status` can be `hovered`, `pressed`, or nothing. E.g. close.png, toggle-maximize-hover.png, minimize-pressed.png. Additionally, if `inactive_buttons` is set to `true`, you have to add a additional images with the `status` of `inactive`. You **Must** provide an image for each of the `type`s and `status`es listed above, so 9 images if `inactive_buttons == false`, and 12 images if `inactive_buttons == true`. The images can be equal to each other, if you don't want do differentiate between different `type`s or `status`es, just make sure that every entry is placed. 81 | - `normal_min`, `normal_max`, and `normal_close` set their respective button colors when the button isn't hovered. Default values are `#c89e2bff`, `#2ebb3aff`, and `#c24045ff`, respectively. 82 | - `hovered_min`, `hovered_max`, and `hovered_close` set their respective button colors when the button is hovered. Default values are `#ffe450ff`, `#60fc79ff`, and `#ff6572ff`, respectively. 83 | - `inactive_buttons` is a `bool` that tells the plugin to draw buttons differently, depending on them being in an active or an inactive window. Default is `false`; The default is `wayfire`; 84 | 85 |
86 | 87 |
Icon options 88 | 89 | - `icon_size` sets the size for the icons, in pixels. Default is `20`; 90 | - `icon_theme` sets the theme to be used for the icons, make sure that a folder exists on an appropriate position. Default is `hicolor`; 91 | 92 |
93 | 94 |
Accent options 95 | 96 | - Accents are areas in the decoration's background that you want to be colored differently, they are placed in the layout, seen on the section below; 97 | - `active_accent` sets the color for active accents. Default is `#f5f5f5ff`. 98 | - `inactive_accent` sets the color for inactive accents. Default is `#e1dffeff`. 99 | 100 |
101 | 102 |
Layout options 103 | 104 | - `layout` is a long string that determines where things should be placed on the edges of a window. Here's how it works: 105 | - Every symbol must be separated by a space; 106 | - The symbols `title`, `icon`, `maximize`, `minimize`, and `close`, will place their respective symbols on the window; 107 | - The symbol `p` will introduce a standardized padding, set by the `padding_size` option. The symbol `P` followed by a number, will place that many pixels of padding, for example, `P7` places 7 pixels of padding on the edge; 108 | - The symbol `|` changes where the symbols are being placed. Normally, they're on the left of the edge, if you place a `|`, they will be on the center, if you place another `|`, they will be placed on the right. Further `|`s will not change position; 109 | - The symbol `-` will change the edge the symbols are being placed in. By default, it will be the top edge, and every `-` will change the edge, counter-clockwise. In previous versions of `wayfire-firedecor`, you needed to end the layout with `-`, that is no longer the case. 110 | - The symbol `a` will initiate/end an accented area, it will start one if there wasn't one already, and it will end one if there was. You can more precisely position accents by using paddings, for example `a P5 title P5 a` will place a padding between each end of the accent, giving some space for the title. All corners will be rounded with this option. 111 | - The symbol `A` is much like `a`, but it is followed by a spaceless string, which tells the program what should be done to the edges of the accent. The default behaviour is to create 2 flat edges, and the available options are: 112 | - Any of `br tr tl bl` will round the respective corner (`t`op and `b`ottom `l`eft and `r`ight). These can be placed in any order, e.g. `Abltr` will round the top right and bottom left corners. 113 | - `/` and `\\` (must be 2 backslashes) will create a diagonal ending on the respective edge. For example, `A\\/` will create a diagonal that looks like \ on the left edge, and / on the right. This is positioned in relation to the text direction, specifically, they rotate based on the edge they're on. This option will not work if one of the corners on a respective edge is rounded, e.g. `Atr//` will only diagonalize the left edge. 114 | - `!` is a flat edge. This is just used to skip diagonalization of the left edge, for example, `A!\\` will diagonalize the right edge but keep the left edge flat. 115 | 116 | The default layout is `P5 title | | minimize p maximize p close P5 -`. Here's what this means: 117 | 1. Place a padding with 5 pixels of size, followed by title on the left; 118 | 2. Move to the center, do nothing; 119 | 3. Move to the right; 120 | 4. Place a minimize button, followed by a toggle maximize button and a close button, all separated by a standardized padding; 121 | 5. Place a padding with 5 pixels of size; 122 | 6. Finish the top edge and move on to the left edge, do nothing there; 123 | 124 | Here's what this layout looks like: 125 | ![Default Layout](/assets/default-layout.png) 126 | - `padding_size` determines the size used for `p` on `layout`. Default is `2`; 127 | 128 |
129 | 130 |
Other options 131 | 132 | - `ignore_views` is of `criteria` type, and determines witch windows will be ignored for decorations. In the future, I plan on adding the ability to create multiple themes and use them selectively, for example, a light and dark theme. 133 | - `debug_mode` turns the titles of windows into their respective `app_id`s, followed by the maximum pixel size of the current font, which often differs from the `font_size`. This is used when the plugin fails at finding the icon for an app, or if you want more precision in the positioning of the decorations. More in [App Icon Debugging](#app-icon-debugging). Default is `false`; 134 | - `round_on` chooses which corners will be rounded. `tr` means top right, `tl` is top left, `bl` is bottom left, `br` is bottom right, and `all` is all of them, e.g. `tl br` will round the top left and bottom right corners. Default is `all`; 135 | 136 |
137 | 138 |
Extra theme options 139 | 140 | - `extra_themes` will be the declaration of existance for any extra themes you want to use, e.g. `dark light discord`. If the theme is not in here, no windows will use it. The default is ``; 141 | - When it comes to extra themes, the configuration section will look exactly like the regular `firedecor` section, except you won't have the `ignore_views` and `extra_themes` options, and will gain the `uses_if` option; 142 | - `uses_if` is of `criteria` type, and will match all the windows that should use the theme of the current section. There is no default, so if it is not present, no window will use the theme; 143 | - When declaring new themes, you don't need to use every single option on the list. If the option isn't present, the theme will simply use the value from the default `firedecor` theme section, so something like: 144 | ```ini 145 | [firedecor] 146 | border_size = 10 10 10 10 147 | 148 | title_color = 0.0 0.0 0.0 1.0 149 | 150 | extra_themes = white_title 151 | 152 | [white_title] 153 | uses_if = app_id is "kitty" 154 | 155 | title_color = 1.0 1.0 1.0 1.0 156 | ``` 157 | Will change the `title_color` on views with `app_id is "kitty"`, but the `border_size` will stay at `10 10 10 10`. 158 | 159 |
160 | 161 | ### Example Configs 162 | 163 | Here's what the default configuration would look like: 164 |
Default config 165 | 166 | ```ini 167 | [firedecor] 168 | font = sans-serif 169 | font_size = 21 170 | active_font = \#1d1f21ff 171 | inactive_font = \#1d1f21ff 172 | 173 | border_size = 35 10 174 | active_border = \#1d1f21e6 175 | inactive_border = \#1d1f21e6 176 | corner_radius = 15 177 | 178 | outline_size = 0 179 | active_outline = \#000000ff 180 | inactive_outline = \#000000ff 181 | 182 | button_size = 18 183 | button_style = simple 184 | normal_min = \#c89e2bff 185 | hovered_min = \#fac636ff 186 | normal_max = \#2ebb3aff 187 | hovered_max = \#39ea49ff 188 | normal_close = \#c24045ff 189 | hovered_close = \#f25056ff 190 | inactive_buttons = false 191 | 192 | icon_size = 20 193 | icon_theme = hicolor 194 | 195 | active_accent = \#f5f5f5ff 196 | inactive_accent = \#e1dfe1ff 197 | 198 | layout = a | icon P4 title | minimize p maximize p close p Atrtl - 199 | padding_size = 8 200 | 201 | ignore_views = none 202 | debug_mode = false 203 | ``` 204 | ![Default layout](/assets/default-layout.png) 205 | 206 |
207 | 208 | It's supposed to give off a "macOS" feel. Paste this into your `wayfire.ini` and add firedecor to your active plugins to get started. 209 | 210 | This is my own configuration, feel free to copy it if you want 211 |
My own config 212 | 213 | ```ini 214 | [firedecor] 215 | font = Clear Sans 216 | active_title = \#c5c8c6ff 217 | inactive_title = \#c5c8c6ff 218 | 219 | active_accent = \#18171aff 220 | inactive_accent = \#1d1f21ff 221 | 222 | ignore_views = title contains "steam" | title contains "Steam" 223 | extra_themes = Discord Firefox 224 | 225 | [Discord] 226 | uses_if = app_id is "discord" 227 | 228 | active_border = \#202225ff 229 | inactive_border = \#1d1f21ff 230 | border_size = 35 0 231 | round_on = tr tl 232 | 233 | layout = | icon P4 title | minimize p maximize p close p 234 | 235 | [Firefox] 236 | uses_if = app_id is "firefox" 237 | 238 | active_title = \#1d1f21ff 239 | inactive_title = \#1d1f21ff 240 | 241 | active_border = \#f0f0f4ff 242 | inactive_border = \#e1dfe1ff 243 | border_size = 35 0 244 | round_on = tr tl 245 | 246 | layout = | icon P4 title | minimize p maximize p close p 247 | ``` 248 | ![Personal layout](/assets/personal-layout.png) 249 | 250 |
251 | 252 | ## Screenshots 253 | Left side decoration: 254 | ![Left side decoration](/assets/left-side-decoration.png) 255 | Using: 256 | ```ini 257 | border_size = 10 30 10 10 258 | layout = - P5 title | | minimize p maximize p close P5 - 259 | ``` 260 | 261 | ???: 262 | ![Strange 1](/assets/strange-1.png) 263 | 264 | ?̷̛͈͐̃̈́̀̇́̑͛̓͋̌?̴̡̘̯͙̩̂̑̅̆̕?̶͍̣́̅̐̔͂̅͐̿͌͝: 265 | ![Strange 2](/assets/strange-2.png) 266 | (very laggy) 267 | 268 | ### Some ideas for accents 269 | - Selective accents: 270 | ![top left and right](/assets/top-left-and-right.png) 271 | ```ini 272 | layout = a P7 icon p title P7 a | | a P7 minimize p maximize p close P7 a 273 | ``` 274 | - Connecting accents: 275 | ![top and bottom wrap](/assets/top-and-bottom-wrap.png) 276 | ```ini 277 | layout = a | icon p title | P7 minimize p maximize p close P7 Atrtl - a P80 Atl - a | | Ablbr - a P80 Abr 278 | ``` 279 | - Framed outline: 280 | ![framed](/assets/framed.png) 281 | ```ini 282 | border_size = 10 283 | layout = a P100 Atl!\\ | | a P100 Atr/ - a P90 A!\\ | | a P90 A/ - a P100 Abl!/ | | a P100 Abr\\ - a P90 A!\\ | | a P90 A/ 284 | ``` 285 | - Partial outlines using the same color for accents and the background: 286 | ![partial outline](/assets/partial-outline.png) 287 | ```ini 288 | layout = P7 icon p title p a | | A p minimize p maximize p close P7 - a | | A - a | | Ablbr - a | | A 289 | ``` 290 | - Scroll-like decoration using visible outlines with an invisible background: 291 | ![scroll](/assets/scroll.png) 292 | ```ini 293 | layout = a | icon p title | minimize p maximize p close P7 a - - P5 a | | a P5 294 | ``` 295 | - Connecting outlines with accents for cool effects (I think this one looks really cool): 296 | ![accent outlines](/assets/accent-outlines.png) 297 | ```ini 298 | layout = | a P15 icon p title P15 A\/ | 299 | ``` 300 | 301 | ## App Icon Debugging 302 | The plugin will automatically try to retrieve icons from the file system, in order to display them on `icon` symbols on your windows. It will first look for folders matching your `icon_theme`. If it doesn't find the icons there, it will look in the remaining folders (hicolor, adwaita, breeze, in that order). However, sometimes, it just fails, and even if there is an icon for said app, the app's `app_id` is too terrible to find a suitable image, e.g. Osu!lazer has an `app_id` of "dotnet", which is completely unusable. 303 | If this ends up happening, the plugin will use a backup icon, provided by the plugin itself. But you also have the ability to manually set icons for your apps. Here's how: 304 | 1. Set `debug_mode` to true; 305 | 2. Open your app, this should tell you what its `app_id` is, if you have a `title` in `layout`; 306 | 3. Find the icon for this app, it can be anywhere in the computer, and can be either a `png` or an `svg` file; 307 | 4. Find the file `~/.local/share/firedecor_icons`, it should be automatically created by the plugin; 308 | 5. Find the line containing the `app_id`, it should look like `my_app_id /full/path/to/default/icon`; 309 | 6. Replace the path in that line with the one you found earlier; 310 | 7. Done! 311 | -------------------------------------------------------------------------------- /assets/accent-outlines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/accent-outlines.png -------------------------------------------------------------------------------- /assets/default-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/default-layout.png -------------------------------------------------------------------------------- /assets/framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/framed.png -------------------------------------------------------------------------------- /assets/left-side-decoration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/left-side-decoration.png -------------------------------------------------------------------------------- /assets/partial-outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/partial-outline.png -------------------------------------------------------------------------------- /assets/personal-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/personal-layout.png -------------------------------------------------------------------------------- /assets/scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/scroll.png -------------------------------------------------------------------------------- /assets/strange-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/strange-1.png -------------------------------------------------------------------------------- /assets/strange-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/strange-2.png -------------------------------------------------------------------------------- /assets/top-and-bottom-wrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/top-and-bottom-wrap.png -------------------------------------------------------------------------------- /assets/top-left-and-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AhoyISki/Firedecor/3060c8ef3d7409be23fa3c9b1c5ad7771367a511/assets/top-left-and-right.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'firedecor', 3 | 'c', 4 | 'cpp', 5 | version: '0.1', 6 | license: 'MIT', 7 | meson_version: '>=0.53.0', 8 | default_options: [ 9 | 'cpp_std=c++20', 10 | 'c_std=c11', 11 | 'warning_level=2', 12 | 'werror=false', 13 | ], 14 | ) 15 | 16 | wayfire = dependency('wayfire') 17 | wf_config = dependency('wf-config') 18 | wlroots = dependency('wlroots') 19 | rsvg = dependency('librsvg-2.0') 20 | pixman = dependency('pixman-1') 21 | cairo = dependency('cairo') 22 | pango = dependency('pango') 23 | pangocairo = dependency('pangocairo') 24 | glib = dependency('glib-2.0') 25 | gdk_pixbuf = dependency('gdk-pixbuf-2.0') 26 | boost = dependency('boost') 27 | 28 | add_project_arguments(['-DWLR_USE_UNSTABLE'], language: ['cpp', 'c']) 29 | add_project_arguments(['-DWAYFIRE_PLUGIN'], language: ['cpp', 'c']) 30 | add_project_link_arguments(['-rdynamic'], language:'cpp') 31 | 32 | install_data('src/executable.svg', install_dir: join_paths(get_option('datadir'), 'firedecor')) 33 | 34 | subdir('src') 35 | subdir('metadata') 36 | 37 | summary = [ 38 | 39 | '', 40 | '----------------', 41 | 'firedecor @0@'.format(meson.project_version()), 42 | '----------------', 43 | '' 44 | ] 45 | message('\n'.join(summary)) 46 | -------------------------------------------------------------------------------- /metadata/firedecor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_short>Firedecor 5 | <_long>Alternative decoration for wayfire windows. 6 | Effects 7 | 8 | 13 | 18 | 23 | 28 | 33 | 34 | 39 | 44 | 49 | 54 | 55 | 60 | 65 | 70 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 117 | 122 | 127 | 128 | 133 | 138 | 139 | 144 | 149 | 150 | 155 | 160 | 165 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /metadata/meson.build: -------------------------------------------------------------------------------- 1 | install_data('firedecor.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir')) 2 | -------------------------------------------------------------------------------- /src/cairo-simpler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void cairo_move_to(cairo_t *cr, wf::point_t point) { 8 | cairo_move_to(cr, point.x, point.y); 9 | } 10 | 11 | void cairo_line_to(cairo_t *cr, wf::point_t point) { 12 | cairo_line_to(cr, point.x, point.y); 13 | } 14 | 15 | void cairo_arc(cairo_t *cr, wf::point_t point, double r, double a1, double a2) { 16 | cairo_arc(cr, point.x, point.y, r, a1, a2); 17 | } 18 | 19 | void cairo_rectangle(cairo_t *cr, wf::point_t point, wf::dimensions_t size) { 20 | cairo_rectangle(cr, point.x, point.y, size.width, size.height); 21 | } 22 | 23 | void cairo_rectangle(cairo_t *cr, wf::point_t point, int width, int height) { 24 | cairo_rectangle(cr, point.x, point.y, width, height); 25 | } 26 | 27 | void cairo_rectangle(cairo_t *cr, wf::geometry_t geometry) { 28 | cairo_rectangle(cr, geometry.x, geometry.y, geometry.width, geometry.height); 29 | } 30 | 31 | void cairo_set_source_rgba(cairo_t *cr, wf::color_t color) { 32 | cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); 33 | } 34 | 35 | void cairo_translate(cairo_t *cr, wf::point_t point) { 36 | cairo_translate(cr, (double)point.x, (double)point.y); 37 | } 38 | -------------------------------------------------------------------------------- /src/executable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/firedecor-buttons.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "firedecor-buttons.hpp" 5 | #include "firedecor-theme.hpp" 6 | 7 | #define HOVERED 1.0 8 | #define NORMAL 0.0 9 | #define PRESSED -0.7 10 | 11 | namespace wf { 12 | namespace firedecor { 13 | button_t::button_t(const decoration_theme_t& t, std::function damage) : 14 | theme(t), damage_callback(damage) {} 15 | 16 | void button_t::set_button_type(button_type_t type) { 17 | this->type = type; 18 | this->hover.animate(0, 0); 19 | update_texture(); 20 | add_idle_damage(); 21 | } 22 | 23 | button_type_t button_t::get_button_type() const { 24 | return this->type; 25 | } 26 | 27 | void button_t::set_active(bool active) { 28 | if (this->active != active) { 29 | this->active = active; 30 | update_texture(); 31 | add_idle_damage(); 32 | } 33 | } 34 | 35 | void button_t::set_maximized(uint32_t edges) { 36 | if (this->maximized != (edges == wf::TILED_EDGES_ALL)) { 37 | this->maximized = (edges == wf::TILED_EDGES_ALL); 38 | update_texture(); 39 | add_idle_damage(); 40 | } 41 | } 42 | 43 | void button_t::set_hover(bool is_hovered) { 44 | this->is_hovered = is_hovered; 45 | if (!this->is_pressed) { 46 | if (is_hovered) { 47 | this->hover.animate(HOVERED); 48 | } else { 49 | this->hover.animate(NORMAL); 50 | } 51 | } 52 | 53 | update_texture(); 54 | add_idle_damage(); 55 | } 56 | 57 | /** 58 | * Set whether the button is pressed or not. 59 | * Affects appearance. 60 | */ 61 | void button_t::set_pressed(bool is_pressed) { 62 | this->is_pressed = is_pressed; 63 | if (is_pressed) { 64 | this->hover.animate(PRESSED); 65 | } else { 66 | this->hover.animate(is_hovered ? HOVERED : NORMAL); 67 | } 68 | 69 | add_idle_damage(); 70 | } 71 | 72 | void button_t::render(const wf::render_target_t& fb, wf::geometry_t geometry, 73 | wf::geometry_t scissor) { 74 | OpenGL::render_begin(fb); 75 | fb.logic_scissor(scissor); 76 | OpenGL::render_texture(button_texture.tex, fb, geometry, {1, 1, 1, 1}, 77 | OpenGL::TEXTURE_TRANSFORM_INVERT_Y); 78 | OpenGL::render_end(); 79 | 80 | if (this->hover.running()) { 81 | add_idle_damage(); 82 | } 83 | } 84 | 85 | void button_t::update_texture() { 86 | auto surface = theme.form_button(type, hover, active, maximized); 87 | OpenGL::render_begin(); 88 | cairo_surface_upload_to_texture(surface, this->button_texture); 89 | OpenGL::render_end(); 90 | cairo_surface_destroy(surface); 91 | } 92 | 93 | void button_t::add_idle_damage() { 94 | this->idle_damage.run_once([=] () { 95 | this->damage_callback(); 96 | update_texture(); 97 | }); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/firedecor-buttons.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace wf { 16 | namespace firedecor { 17 | class decoration_theme_t; 18 | 19 | enum button_type_t { 20 | BUTTON_CLOSE, 21 | BUTTON_TOGGLE_MAXIMIZE, 22 | BUTTON_MINIMIZE, 23 | }; 24 | 25 | class button_t { 26 | public: 27 | /** 28 | * Create a new button with the given theme. 29 | * @param theme The theme to use. 30 | * @param damage_callback A callback to execute when the button needs a 31 | * repaint. Damage won't be reported while render() is being called. 32 | */ 33 | button_t(const decoration_theme_t& theme, std::function damage_callback); 34 | 35 | ~button_t() = default; 36 | button_t(const button_t &) = delete; 37 | button_t(button_t &&) = delete; 38 | button_t& operator =(const button_t&) = delete; 39 | button_t& operator =(button_t&&) = delete; 40 | 41 | /** 42 | * Set the type of the button. This will affect the displayed icon and 43 | * potentially other appearance like colors. 44 | */ 45 | void set_button_type(button_type_t type); 46 | 47 | /** @return The type of the button */ 48 | button_type_t get_button_type() const; 49 | 50 | /** Set the activation status of the button's view */ 51 | void set_active(bool active); 52 | /** Set the maximized status of the button's view */ 53 | void set_maximized(uint32_t edges); 54 | 55 | /** 56 | * Set the button hover state. 57 | * Affects appearance. 58 | */ 59 | void set_hover(bool is_hovered); 60 | 61 | /** 62 | * Set whether the button is pressed or not. 63 | * Affects appearance. 64 | */ 65 | void set_pressed(bool is_pressed); 66 | 67 | /** 68 | * Render the button on the given framebuffer at the given coordinates. 69 | * Precondition: set_button_type() has been called, otherwise result is no-op 70 | * 71 | * @param buffer The target framebuffer 72 | * @param geometry The geometry of the button, in logical coordinates 73 | * @param scissor The scissor rectangle to render. 74 | */ 75 | void render(const wf::render_target_t& buffer, wf::geometry_t geometry, 76 | wf::geometry_t scissor); 77 | 78 | private: 79 | const decoration_theme_t& theme; 80 | 81 | /* Whether the button needs repaint */ 82 | button_type_t type; 83 | wf::simple_texture_t button_texture; 84 | 85 | /* Whether the button is currently being hovered */ 86 | bool is_hovered = false; 87 | /* Whether the button is currently being held */ 88 | bool is_pressed = false; 89 | /* Whether the button's view is active or not */ 90 | bool active = true; 91 | /* Whether the button's view is maximized or not */ 92 | bool maximized = false; 93 | 94 | /* The shade of button background to use. */ 95 | wf::animation::simple_animation_t hover{wf::create_option(100)}; 96 | 97 | std::function damage_callback; 98 | wf::wl_idle_call idle_damage; 99 | /** Damage button the next time the main loop goes idle */ 100 | void add_idle_damage(); 101 | 102 | /** 103 | * Redraw the button surface and store it as a texture 104 | */ 105 | void update_texture(); 106 | }; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/firedecor-layout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "firedecor-layout.hpp" 7 | #include "firedecor-theme.hpp" 8 | 9 | #include 10 | 11 | namespace wf { 12 | namespace firedecor { 13 | /** Initialize a new decoration area holding an icon */ 14 | decoration_area_t::decoration_area_t(decoration_area_type_t type, 15 | wf::geometry_t g, edge_t edge) { 16 | this->type = type; 17 | this->geometry = g; 18 | this->edge = edge; 19 | } 20 | 21 | /** Initialize a new decoration area holding a title */ 22 | decoration_area_t::decoration_area_t(wf::geometry_t g, wf::geometry_t g_dots, 23 | edge_t edge) { 24 | this->type = DECORATION_AREA_TITLE; 25 | this->geometry = g; 26 | this->dots_geometry = g_dots; 27 | this->edge = edge; 28 | } 29 | 30 | /** Initialize a new decoration area holding a button */ 31 | decoration_area_t::decoration_area_t(wf::geometry_t g, 32 | std::function damage_callback, 33 | const decoration_theme_t& theme) { 34 | this->type = DECORATION_AREA_BUTTON; 35 | this->geometry = g; 36 | 37 | this->button = std::make_unique(theme, std::bind(damage_callback, g)); 38 | } 39 | 40 | /** Initialize a new decoration area where the edge does not matter */ 41 | decoration_area_t::decoration_area_t(decoration_area_type_t type, wf::geometry_t g) { 42 | this->type = type; 43 | this->geometry = g; 44 | } 45 | 46 | /** Initialize a decoration area for background areas */ 47 | decoration_area_t::decoration_area_t(decoration_area_type_t type, wf::geometry_t g, 48 | std::string c, matrix m, edge_t edge) { 49 | this->type = type; 50 | this->geometry = g; 51 | this->corners = c; 52 | this->m = m; 53 | this->edge = edge; 54 | } 55 | 56 | decoration_area_type_t decoration_area_t::get_type() const { 57 | return type; 58 | } 59 | 60 | wf::geometry_t decoration_area_t::get_geometry() const { 61 | return geometry; 62 | } 63 | 64 | wf::geometry_t decoration_area_t::get_dots_geometry() const { 65 | return dots_geometry; 66 | } 67 | 68 | edge_t decoration_area_t::get_edge() const { 69 | return edge; 70 | } 71 | 72 | std::string decoration_area_t::get_corners() const { 73 | return corners; 74 | } 75 | 76 | matrix decoration_area_t::get_m() const { 77 | return m; 78 | } 79 | 80 | button_t& decoration_area_t::as_button() { 81 | assert(button); 82 | 83 | return *button; 84 | } 85 | 86 | border_size_t decoration_layout_t::parse_border(const std::string border_size_str) { 87 | std::stringstream stream((std::string)border_size_str); 88 | int current_size; 89 | int border_size[4]; 90 | int indices = 0; 91 | 92 | while (stream >> current_size) { 93 | border_size[indices] = current_size; 94 | indices++; 95 | } 96 | 97 | if (indices == 1 || indices == 3) { 98 | return { border_size[0], border_size[0], border_size[0], border_size[0] }; 99 | } else if (indices == 2) { 100 | return { border_size[0], border_size[1], border_size[1], border_size[1] }; 101 | } else { 102 | return { border_size[0], border_size[1], border_size[2], border_size[3] }; 103 | } 104 | } 105 | 106 | decoration_layout_t::decoration_layout_t(const decoration_theme_t& theme, 107 | std::function callback) : 108 | 109 | layout(theme.get_layout()), 110 | border_size_str(theme.get_border_size()), 111 | border_size(parse_border(border_size_str)), 112 | corner_radius(theme.get_corner_radius()), 113 | outline_size(theme.get_outline_size()), 114 | button_size(theme.get_button_size()), 115 | icon_size(theme.get_icon_size()), 116 | padding_size(theme.get_padding_size()), 117 | theme(theme), 118 | damage_callback(callback) 119 | {} 120 | 121 | void decoration_layout_t::create_areas(int width, int height, 122 | wf::dimensions_t title_size, 123 | wf::dimensions_t dots_size) { 124 | int count = std::count(layout.begin(), layout.end(), '-'); 125 | std::string layout_str = layout; 126 | for (int i = 4; i > count; i--) { 127 | layout_str.append(" -"); 128 | } 129 | std::stringstream stream(layout_str); 130 | std::vector left, center, right; 131 | std::string current_symbol; 132 | 133 | edge_t cur_edge = EDGE_TOP; 134 | std::string current_position = "left"; 135 | wf::point_t o = { 0, (border_size.top - max_height) / 2 }; 136 | 137 | /** The values that are used to determine the updated geometry for areas */ 138 | int shift = 0, out_padding = 0; 139 | 140 | /**** Elements that can be transformed to work on any edge */ 141 | auto p = [&]() -> wf::point_t { return { shift, out_padding }; }; 142 | const wf::point_t &l = { width, height - border_size.top - border_size.bottom }; 143 | const wf::point_t &title = { title_size.width, title_size.height }; 144 | const wf::point_t &dots = { dots_size.width, dots_size.height }; 145 | 146 | /** Matrix that transforms said elements */ 147 | matrix m = { 1, 0, 0, 1 }; 148 | 149 | /** Transformation lambda */ 150 | auto trans = [&](wf::point_t v) -> wf::point_t { 151 | return { v.x * m.xx + v.y * m.xy, v.x * m.yx + v.y * m.yy }; 152 | }; 153 | /****/ 154 | 155 | /**** For background geometry calculations */ 156 | /** Background origin and the background's final point */ 157 | wf::point_t b_o = { 0, 0 }, b_f = { width - corner_radius, border_size.top }; 158 | 159 | /** Background points 1 and 2 */ 160 | wf::point_t b_p1 = { corner_radius, 0 }, b_p2; 161 | 162 | /** Minimum shift needed for regular background, so it doesn't overlap corner */ 163 | int min_shift = corner_radius; 164 | 165 | /** "Height" of the current edge, that is, the border_size */ 166 | int edge_height = border_size.top; 167 | 168 | /** The cutoff lenghts for the background */ 169 | int corner_h = std::max({ border_size.top, border_size.bottom, corner_radius }); 170 | /****/ 171 | 172 | while (stream >> current_symbol) { 173 | if (current_symbol == "|") { 174 | current_position = (current_position == "left") ? "center" : "right"; 175 | } else if (current_symbol == "-") { 176 | /** Variables for background and accent definition */ 177 | std::string last_accent; 178 | int counter = 0; 179 | for (auto vec : { left, center, right }) { 180 | if (vec != left) { 181 | int region_lenght = 0; 182 | 183 | for (auto type : vec) { 184 | if (type == "title") { 185 | region_lenght += title_size.width; 186 | } else if (type == "icon") { 187 | region_lenght += icon_size; 188 | } else if (type == "p") { 189 | region_lenght += padding_size; 190 | } else if (type[0] == 'P') { 191 | int delta; 192 | std::stringstream num; 193 | num << type.substr(1); 194 | num >> delta; 195 | region_lenght += delta; 196 | } else if (type != "a" && type[0] != 'A') { 197 | region_lenght += button_size; 198 | } 199 | } 200 | 201 | if (vec == center) { 202 | shift = (abs(trans(l).x) - region_lenght) / 2; 203 | } else { 204 | shift = abs(trans(l).x) - region_lenght; 205 | } 206 | } else { 207 | shift = 0; 208 | } 209 | 210 | wf::geometry_t cur_g, cur_dots_g; 211 | for (auto type : vec) { 212 | int delta = 0; 213 | 214 | if (type == "title") { 215 | delta = title_size.width + dots.x; 216 | out_padding = (max_height - title_size.height) / 2; 217 | cur_g = { 218 | o.x + trans(p()).x, o.y + trans(p()).y, 219 | trans(title).x, trans(title).y 220 | }; 221 | wf::point_t dots_o = { title_size.width, 0 }; 222 | dots_o = dots_o + p(); 223 | cur_dots_g = { 224 | o.x + trans(dots_o).x, o.y + trans(dots_o).y, 225 | trans(dots).x, trans(dots).y 226 | }; 227 | 228 | layout_areas.push_back(std::make_unique( 229 | cur_g, cur_dots_g, cur_edge)); 230 | } else if (type == "icon") { 231 | delta = icon_size; 232 | out_padding = (max_height - icon_size) / 2; 233 | cur_g = { 234 | o.x + trans(p()).x, o.y + trans(p()).y, 235 | (m.xx + m.xy) * icon_size, (m.yx + m.yy) * icon_size 236 | }; 237 | layout_areas.push_back(std::make_unique( 238 | DECORATION_AREA_ICON, cur_g, cur_edge)); 239 | } else if (type == "p") { 240 | delta = padding_size; 241 | } else if (type[0] == 'P') { 242 | std::stringstream num; 243 | num << type.substr(1); 244 | num >> delta; 245 | } else if (type == "a" || type[0] == 'A') { 246 | counter = (counter + 1) % 2; 247 | out_padding = counter * edge_height; 248 | b_p2 = { b_o.x + trans(p()).x, b_o.y + trans(p()).y }; 249 | cur_g = { 250 | std::min(b_p1.x, b_p2.x), std::min(b_p1.y, b_p2.y), 251 | abs(b_p2.x - b_p1.x), abs(b_p2.y - b_p1.y) 252 | }; 253 | auto bg = (counter == 0) ? DECORATION_AREA_ACCENT : 254 | DECORATION_AREA_BACKGROUND; 255 | if (abs(cur_g.width) > 0 && abs(cur_g.height) > 0 && 256 | (shift > min_shift || counter == 0)) { 257 | background_areas.push_back( 258 | std::make_unique(bg, cur_g, type, 259 | m, cur_edge)); 260 | } 261 | b_p1 = b_p2; 262 | last_accent = type; 263 | } else { 264 | delta = button_size; 265 | out_padding = (max_height - button_size) / 2; 266 | cur_g = { 267 | o.x + trans(p()).x, o.y + trans(p()).y, 268 | (m.xx + m.xy) * button_size, (m.yx + m.yy) * button_size 269 | }; 270 | 271 | button_type_t button = (type == "minimize") ? 272 | BUTTON_MINIMIZE : ((type == "maximize") ? 273 | BUTTON_TOGGLE_MAXIMIZE : BUTTON_CLOSE); 274 | 275 | layout_areas.push_back(std::make_unique( 276 | cur_g, damage_callback, theme)); 277 | layout_areas.back()->as_button().set_button_type(button); 278 | } 279 | 280 | shift += delta; 281 | } 282 | } 283 | 284 | shift = 0; 285 | out_padding = counter * edge_height; 286 | b_p2 = { b_f.x + trans(p()).x, b_f.y + trans(p()).y }; 287 | wf::geometry_t final_g = { 288 | std::min(b_p1.x, b_p2.x), std::min(b_p1.y, b_p2.y), 289 | (m.xx + m.xy) * (b_p2.x - b_p1.x), (m.yx + m.yy) * (b_p2.y - b_p1.y) 290 | }; 291 | if (final_g.width > 0 && final_g.height > 0) { 292 | auto type = DECORATION_AREA_BACKGROUND; 293 | background_areas.push_back( 294 | std::make_unique(type, final_g, "", m, 295 | cur_edge)); 296 | } 297 | counter = 0; 298 | 299 | if (cur_edge == EDGE_TOP) { 300 | cur_edge = EDGE_LEFT; 301 | m = { 0, 1, -1, 0 }; 302 | o = { (border_size.left - max_height) / 2, 303 | height - border_size.bottom }; 304 | b_o = { 0, height - border_size.bottom }; 305 | b_p1 = { 0, height - corner_h }; 306 | b_f = { border_size.left, corner_h }; 307 | edge_height = border_size.left; 308 | min_shift = corner_radius - border_size.bottom; 309 | } else if (cur_edge == EDGE_LEFT) { 310 | cur_edge = EDGE_BOTTOM; 311 | m = { 1, 0, 0, 1 }; 312 | o = b_o = { 0, height - (border_size.bottom + max_height) / 2 }; 313 | b_p1 = { corner_radius, height - border_size.bottom }; 314 | b_f = { width - corner_radius, height }; 315 | edge_height = border_size.bottom; 316 | min_shift = corner_radius; 317 | } else { 318 | cur_edge = EDGE_RIGHT; 319 | m = { 0, -1, 1, 0 }; 320 | o = { width - (border_size.right + max_height) / 2, 321 | border_size.top }; 322 | b_o = { width, border_size.top }; 323 | b_p1 = { width, corner_h }; 324 | b_f = { width - border_size.right, height - corner_h }; 325 | edge_height = border_size.right; 326 | min_shift = corner_radius - border_size.top; 327 | } 328 | 329 | left.clear(); 330 | center.clear(); 331 | right.clear(); 332 | current_position = "left"; 333 | 334 | } else { 335 | if (current_position == "left") { 336 | left.push_back(current_symbol); 337 | } else if (current_position == "center") { 338 | center.push_back(current_symbol); 339 | } else { 340 | right.push_back(current_symbol); 341 | } 342 | } 343 | 344 | } 345 | } 346 | 347 | /** Regenerate layout using a new size */ 348 | void decoration_layout_t::resize(int width, int height, wf::dimensions_t title_size, 349 | wf::dimensions_t dots_size) { 350 | max_height = std::max({ title_size.height, icon_size, button_size }); 351 | this->background_areas.clear(); 352 | this->layout_areas.clear(); 353 | 354 | create_areas(width, height, title_size, dots_size); 355 | 356 | /* Areas for resizing only, used for movement area calculation */ 357 | int top_resize = std::min(std::max(border_size.top - max_height, 7), 358 | border_size.top); 359 | int left_resize = std::min(std::max(border_size.left - max_height, 7), 360 | border_size.left); 361 | int bottom_resize = std::min(std::max(border_size.bottom - max_height, 7), 362 | border_size.bottom); 363 | int right_resize = std::min(std::max(border_size.right - max_height, 7), 364 | border_size.right); 365 | /* Moving edges */ 366 | for (wf::geometry_t g : { 367 | (wf::geometry_t){ left_resize, top_resize, width - left_resize - right_resize, 368 | std::max(border_size.top - top_resize, 0) }, 369 | (wf::geometry_t){ left_resize, border_size.top, 370 | std::max(border_size.left - left_resize, 0), 371 | height - border_size.top - border_size.bottom }, 372 | (wf::geometry_t){ left_resize, height - border_size.bottom, 373 | width - left_resize - right_resize, 374 | std::max(border_size.bottom - bottom_resize, 0) }, 375 | (wf::geometry_t){ width - border_size.right, border_size.top, 376 | std::max(border_size.right - right_resize, 0), 377 | height - border_size.top - border_size.bottom } 378 | } ) { 379 | this->layout_areas.push_back(std::make_unique( 380 | DECORATION_AREA_MOVE, g)); 381 | } 382 | 383 | /* Resizing edges - top */ 384 | wf::geometry_t border_geometry = { 0, 0, width, top_resize }; 385 | this->layout_areas.push_back(std::make_unique( 386 | DECORATION_AREA_RESIZE_TOP, border_geometry, EDGE_TOP)); 387 | 388 | /* Resizing edges - left */ 389 | border_geometry = { 0, 0, left_resize, height }; 390 | this->layout_areas.push_back(std::make_unique( 391 | DECORATION_AREA_RESIZE_LEFT, border_geometry, EDGE_LEFT)); 392 | 393 | /* Resizing edges - bottom */ 394 | border_geometry = { 0, height - bottom_resize, width, bottom_resize }; 395 | this->layout_areas.push_back(std::make_unique( 396 | DECORATION_AREA_RESIZE_BOTTOM, border_geometry, EDGE_BOTTOM)); 397 | 398 | /* Resizing edges - right */ 399 | border_geometry = { width - right_resize, 0, right_resize, height }; 400 | this->layout_areas.push_back(std::make_unique( 401 | DECORATION_AREA_RESIZE_RIGHT, border_geometry, EDGE_RIGHT)); 402 | } 403 | 404 | /** 405 | * @return The decoration areas which need to be rendered, in top to bottom 406 | * order. 407 | */ 408 | std::vector> decoration_layout_t:: 409 | get_renderable_areas() { 410 | std::vector> renderable; 411 | for (auto& area : layout_areas) { 412 | if (area->get_type() & AREA_RENDERABLE_BIT) { 413 | renderable.push_back({area}); 414 | } 415 | } 416 | 417 | return renderable; 418 | } 419 | 420 | std::vector> decoration_layout_t:: 421 | get_background_areas() { 422 | std::vector> areas; 423 | for (auto& area : background_areas) { 424 | areas.push_back({area}); 425 | } 426 | return areas; 427 | } 428 | 429 | wf::region_t decoration_layout_t::calculate_region() const { 430 | wf::region_t r{}; 431 | for (auto& area : layout_areas) { 432 | r |= area->get_geometry(); 433 | } 434 | 435 | return r; 436 | } 437 | 438 | void decoration_layout_t::unset_hover(wf::point_t position) { 439 | auto area = find_area_at(position); 440 | if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { 441 | area->as_button().set_hover(false); 442 | } 443 | } 444 | 445 | /** Handle motion event to (x, y) relative to the decoration */ 446 | decoration_layout_t::action_response_t decoration_layout_t::handle_motion( 447 | int x, int y) { 448 | auto previous_area = find_area_at(current_input); 449 | auto current_area = find_area_at({x, y}); 450 | 451 | if (previous_area == current_area) { 452 | if (is_grabbed && current_area && (current_area->get_type() & AREA_MOVE_BIT)) { 453 | is_grabbed = false; 454 | return {DECORATION_ACTION_MOVE, 0}; 455 | } 456 | } else { 457 | unset_hover(current_input); 458 | if (current_area && (current_area->get_type() == DECORATION_AREA_BUTTON)) { 459 | current_area->as_button().set_hover(true); 460 | } 461 | } 462 | 463 | this->current_input = {x, y}; 464 | update_cursor(); 465 | 466 | return { DECORATION_ACTION_NONE, 0 }; 467 | } 468 | 469 | /** 470 | * Handle press or release event. 471 | * @param pressed Whether the event is a press(true) or release(false) 472 | * event. 473 | * @return The action which needs to be carried out in response to this 474 | * event. 475 | * */ 476 | decoration_layout_t::action_response_t decoration_layout_t::handle_press_event( 477 | bool pressed) { 478 | if (pressed) { 479 | auto area = find_area_at(current_input); 480 | if (area && (area->get_type() & AREA_MOVE_BIT)) { 481 | if (timer.is_connected()) { 482 | double_click_at_release = true; 483 | } else { 484 | timer.set_timeout(300, [] () { return false; }); 485 | } 486 | } 487 | 488 | if (area && (area->get_type() & AREA_RESIZE_BIT)) { 489 | return {DECORATION_ACTION_RESIZE, calculate_resize_edges()}; 490 | } 491 | 492 | if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { 493 | area->as_button().set_pressed(true); 494 | } 495 | 496 | is_grabbed = true; 497 | grab_origin = current_input; 498 | } 499 | 500 | if (!pressed && double_click_at_release) { 501 | double_click_at_release = false; 502 | return { DECORATION_ACTION_TOGGLE_MAXIMIZE, 0 }; 503 | } else if (!pressed && is_grabbed) { 504 | is_grabbed = false; 505 | auto begin_area = find_area_at(grab_origin); 506 | auto end_area = find_area_at(current_input); 507 | 508 | if (begin_area && (begin_area->get_type() == DECORATION_AREA_BUTTON)) { 509 | begin_area->as_button().set_pressed(false); 510 | if (end_area && (begin_area == end_area)) { 511 | switch (begin_area->as_button().get_button_type()) { 512 | case BUTTON_CLOSE: 513 | return { DECORATION_ACTION_CLOSE, 0 }; 514 | 515 | case BUTTON_TOGGLE_MAXIMIZE: 516 | return { DECORATION_ACTION_TOGGLE_MAXIMIZE, 0 }; 517 | 518 | case BUTTON_MINIMIZE: 519 | return { DECORATION_ACTION_MINIMIZE, 0 }; 520 | 521 | default: 522 | break; 523 | } 524 | } 525 | } 526 | } 527 | 528 | return { DECORATION_ACTION_NONE, 0}; 529 | } 530 | 531 | /** 532 | * Find the layout area at the given coordinates, if any 533 | * @return The layout area or null on failure 534 | */ 535 | nonstd::observer_ptr decoration_layout_t::find_area_at( wf::point_t point) { 536 | for (auto& area : this->layout_areas) { 537 | if (area->get_geometry() & point) { 538 | return {area}; 539 | } 540 | } 541 | 542 | return nullptr; 543 | } 544 | 545 | /** Calculate resize edges based on @current_input */ 546 | uint32_t decoration_layout_t::calculate_resize_edges() const { 547 | uint32_t edges = 0; 548 | for (auto& area : layout_areas) { 549 | if (area->get_geometry() & this->current_input) { 550 | if (area->get_type() & AREA_RESIZE_BIT) { 551 | edges |= (area->get_type() & ~AREA_RESIZE_BIT); 552 | } 553 | } 554 | } 555 | 556 | return edges; 557 | } 558 | 559 | /** Update the cursor based on @current_input */ 560 | void decoration_layout_t::update_cursor() const { 561 | uint32_t edges = calculate_resize_edges(); 562 | auto cursor_name = edges > 0 ? 563 | wlr_xcursor_get_resize_name((wlr_edges)edges) : "default"; 564 | wf::get_core().set_cursor(cursor_name); 565 | } 566 | 567 | void decoration_layout_t::handle_focus_lost() { 568 | if (is_grabbed) { 569 | this->is_grabbed = false; 570 | auto area = find_area_at(grab_origin); 571 | if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { 572 | area->as_button().set_pressed(false); 573 | } 574 | } 575 | 576 | this->unset_hover(current_input); 577 | } 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /src/firedecor-layout.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "firedecor-buttons.hpp" 7 | #include "firedecor-theme.hpp" 8 | 9 | namespace wf { 10 | namespace firedecor { 11 | 12 | static constexpr uint32_t AREA_RENDERABLE_BIT = (1 << 16); 13 | static constexpr uint32_t AREA_RESIZE_BIT = (1 << 17); 14 | static constexpr uint32_t AREA_MOVE_BIT = (1 << 18); 15 | static constexpr uint32_t AREA_TEXT_BIT = (1 << 19); 16 | static constexpr uint32_t AREA_BACKGROUND_BIT = (1 << 20); 17 | static constexpr uint32_t AREA_ACCENT_BIT = (1 << 21); 18 | 19 | /** Different types of areas around the decoration */ 20 | enum decoration_area_type_t { 21 | DECORATION_AREA_MOVE = AREA_MOVE_BIT, 22 | DECORATION_AREA_TITLE = AREA_MOVE_BIT | AREA_RENDERABLE_BIT | AREA_TEXT_BIT, 23 | DECORATION_AREA_ICON = AREA_MOVE_BIT | AREA_RENDERABLE_BIT, 24 | DECORATION_AREA_BUTTON = AREA_RENDERABLE_BIT, 25 | DECORATION_AREA_RESIZE_LEFT = WLR_EDGE_LEFT | AREA_RESIZE_BIT, 26 | DECORATION_AREA_RESIZE_RIGHT = WLR_EDGE_RIGHT | AREA_RESIZE_BIT, 27 | DECORATION_AREA_RESIZE_TOP = WLR_EDGE_TOP | AREA_RESIZE_BIT, 28 | DECORATION_AREA_RESIZE_BOTTOM = WLR_EDGE_BOTTOM | AREA_RESIZE_BIT, 29 | DECORATION_AREA_BACKGROUND = AREA_BACKGROUND_BIT, 30 | DECORATION_AREA_ACCENT = AREA_ACCENT_BIT, 31 | }; 32 | 33 | /** 34 | * Represents an area of the decoration which reacts to input events. 35 | */ 36 | struct decoration_area_t { 37 | public: 38 | /** 39 | * Initialize a new decoration area holding an icon. 40 | * 41 | * @param type The type of the area. 42 | * @param g The geometry of the area. 43 | * @param edge The edge where this area is placed. 44 | */ 45 | decoration_area_t(decoration_area_type_t type, wf::geometry_t g, edge_t edge); 46 | 47 | /** 48 | * Initialize a new decoration area holding a title. 49 | * 50 | * @param g The geometry of the title area. 51 | * @param g_dots The geometry of the dots area. 52 | * @param edge The edge where this area is placed. 53 | */ 54 | decoration_area_t(wf::geometry_t g, wf::geometry_t g_dots, edge_t edge); 55 | 56 | /** 57 | * Initialize a new decoration area holding a button. 58 | * 59 | * @param g The geometry of the button. 60 | * @param damage_callback Callback to execute when button needs repaint. 61 | * @param theme The theme to use for the button. 62 | */ 63 | decoration_area_t( 64 | wf::geometry_t g, std::function damage_callback, 65 | const decoration_theme_t& theme); 66 | 67 | /** 68 | * Initialize a new decoration area where the edge does not matter 69 | * @param type The type of the area. 70 | * @param g The geometry of the area. 71 | */ 72 | decoration_area_t(decoration_area_type_t type, wf::geometry_t g); 73 | 74 | /** Initialize a new decoration area holding background areas 75 | * 76 | * @param g The geometry of the area. 77 | * @param type The type of the area. 78 | * @param c The parameters given to the area, if it is of accent type. 79 | * @param m The transformation matrix 80 | */ 81 | decoration_area_t(decoration_area_type_t type, wf::geometry_t g, std::string c, 82 | matrix m, edge_t edge); 83 | 84 | /** @return The type of the decoration area */ 85 | decoration_area_type_t get_type() const; 86 | 87 | /** @return The geometry of the decoration area, relative to the layout */ 88 | wf::geometry_t get_geometry() const; 89 | 90 | /** @return The geometry of the decoration area's dots, relative to the layout */ 91 | wf::geometry_t get_dots_geometry() const; 92 | 93 | /** @return The edge of the decoration area */ 94 | edge_t get_edge() const; 95 | 96 | /** @return The corners of the decoration area */ 97 | std::string get_corners() const; 98 | 99 | /** @return The transformation matrix of the area */ 100 | matrix get_m() const; 101 | 102 | /** @return The area's button, if the area is a button. Otherwise UB */ 103 | button_t& as_button(); 104 | 105 | /** This needs to be public for later appendages */ 106 | decoration_area_type_t type; 107 | 108 | private: 109 | wf::geometry_t geometry; 110 | edge_t edge; 111 | 112 | /** For titles only */ 113 | wf::geometry_t dots_geometry; 114 | 115 | /** For buttons only */ 116 | std::unique_ptr button; 117 | 118 | /** For backgrounds and accents only */ 119 | std::string corners; 120 | 121 | /** For accent corners */ 122 | matrix m; 123 | }; 124 | 125 | /** 126 | * Action which needs to be taken in response to an input event 127 | */ 128 | enum decoration_layout_action_t { 129 | DECORATION_ACTION_NONE = 0, 130 | /* Drag actions */ 131 | DECORATION_ACTION_MOVE = 1, 132 | DECORATION_ACTION_RESIZE = 2, 133 | /* Button actions */ 134 | DECORATION_ACTION_CLOSE = 3, 135 | DECORATION_ACTION_TOGGLE_MAXIMIZE = 4, 136 | DECORATION_ACTION_MINIMIZE = 5 137 | }; 138 | 139 | struct border_size_t { 140 | int top, left, bottom, right; 141 | 142 | border_size_t& operator =(const border_size_t& other) = default; 143 | }; 144 | 145 | class decoration_theme_t; 146 | /** 147 | * Manages the layout of the decorations, i.e positioning of the title, 148 | * buttons, etc. 149 | * 150 | * Also dispatches the input events to the appropriate place. 151 | */ 152 | class decoration_layout_t { 153 | public: 154 | /** 155 | * Create a new decoration layout for the given theme. 156 | * When the theme changes, the decoration layout needs to be created again. 157 | * 158 | * @param damage_callback The function to be called when a part of the 159 | * layout needs a repaint. 160 | */ 161 | decoration_layout_t(const decoration_theme_t& theme, 162 | std::function damage_callback); 163 | 164 | /** 165 | * Translate the border into four numbers, representing the top, left, bottom, and right border sizes, respectively. 166 | */ 167 | border_size_t parse_border(std::string border_size); 168 | 169 | /** Create buttons in the layout, and return their total geometry */ 170 | void create_areas(int width, int height, wf::dimensions_t title_size, 171 | wf::dimensions_t dots_size); 172 | 173 | /** Regenerate layout using the new size */ 174 | void resize(int width, int height, wf::dimensions_t title_size, 175 | wf::dimensions_t dims_size); 176 | 177 | /** 178 | * @return The decoration areas which need to be rendered, in top to bottom 179 | * order. 180 | */ 181 | std::vector> get_renderable_areas(); 182 | 183 | /** 184 | * @return The background areas of the decoration */ 185 | std::vector> get_background_areas(); 186 | 187 | /** @return The combined region of all layout areas */ 188 | wf::region_t calculate_region() const; 189 | 190 | struct action_response_t { 191 | decoration_layout_action_t action; 192 | /* For resizing action, determine the edges for resize request */ 193 | uint32_t edges; 194 | }; 195 | 196 | /** Handle motion event to (x, y) relative to the decoration */ 197 | action_response_t handle_motion(int x, int y); 198 | 199 | /** 200 | * Handle press or release event. 201 | * @param pressed Whether the event is a press(true) or release(false) 202 | * event. 203 | * @return The action which needs to be carried out in response to this 204 | * event. 205 | */ 206 | action_response_t handle_press_event(bool pressed = true); 207 | 208 | /** 209 | * Handle focus lost event. 210 | */ 211 | void handle_focus_lost(); 212 | 213 | private: 214 | const std::string layout; 215 | const std::string border_size_str; 216 | const border_size_t border_size; 217 | const int corner_radius; 218 | const int outline_size; 219 | const int button_size; 220 | const int icon_size; 221 | const int padding_size; 222 | 223 | const decoration_theme_t& theme; 224 | 225 | int max_height; 226 | 227 | std::function damage_callback; 228 | 229 | std::vector> layout_areas; 230 | 231 | std::vector> background_areas; 232 | 233 | bool is_grabbed = false; 234 | /* Position where the grab has started */ 235 | wf::point_t grab_origin; 236 | /* Last position of the input */ 237 | wf::point_t current_input; 238 | /* double-click timer */ 239 | wf::wl_timer timer; 240 | bool double_click_at_release = false; 241 | 242 | /** Calculate resize edges based on @current_input */ 243 | uint32_t calculate_resize_edges() const; 244 | /** Update the cursor based on @current_input */ 245 | void update_cursor() const; 246 | 247 | /** 248 | * Find the layout area at the given coordinates, if any 249 | * @return The layout area or null on failure 250 | */ 251 | nonstd::observer_ptr find_area_at(wf::point_t point); 252 | 253 | /** Unset hover state of hovered button at @position, if any */ 254 | void unset_hover(wf::point_t position); 255 | }; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/firedecor-subsurface.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "firedecor-layout.hpp" 15 | #include "firedecor-theme.hpp" 16 | 17 | #include "cairo-simpler.hpp" 18 | 19 | #include 20 | 21 | //#include 22 | 23 | #define INACTIVE 0 24 | #define ACTIVE 1 25 | #define FORCE true 26 | #define DONT_FORCE false 27 | 28 | #include 29 | 30 | namespace wf::firedecor { 31 | 32 | class simple_decoration_surface : public surface_interface_t, 33 | public compositor_surface_t { 34 | bool _mapped = true; 35 | wayfire_view view; 36 | 37 | signal_connection_t title_set = [=, this] (signal_data_t *data) { 38 | if (get_signaled_view(data) == view) { 39 | update_layout(FORCE); 40 | view->damage(); // trigger re-render 41 | } 42 | }; 43 | 44 | void update_title(double scale) { 45 | dimensions_t title_size = { 46 | (int)(title.dims.width * scale), (int)(title.dims.height * scale) 47 | }; 48 | dimensions_t dots_size = { 49 | (int)(title.dots_dims.width * scale), 50 | (int)(title.dots_dims.height * scale) 51 | }; 52 | 53 | auto o = HORIZONTAL; 54 | std::string text = title.text; 55 | int count = 0; 56 | wf::dimensions_t size = title_size; 57 | 58 | for (auto texture : { title.hor, title.ver, 59 | title.hor_dots, title.ver_dots }) { 60 | for (auto state : { ACTIVE, INACTIVE }) { 61 | cairo_surface_t *surface; 62 | surface = theme.form_title(text, size, state, o); 63 | cairo_surface_upload_to_texture(surface, texture[state]); 64 | cairo_surface_destroy(surface); 65 | } 66 | 67 | o = (o == HORIZONTAL) ? VERTICAL : HORIZONTAL; 68 | if (count == 1) { 69 | text = "..."; 70 | size = dots_size; 71 | } 72 | count++; 73 | }; 74 | title_needs_update = false; 75 | } 76 | 77 | void update_icon() { 78 | if (view->get_app_id() != icon.app_id) { 79 | icon.app_id = view->get_app_id(); 80 | auto surface = theme.form_icon(icon.app_id); 81 | cairo_surface_upload_to_texture(surface, icon.texture); 82 | cairo_surface_destroy(surface); 83 | } 84 | } 85 | 86 | void update_layout(bool force) { 87 | if ((title.colors != theme.get_title_colors()) || force) { 88 | /** Updating the cached variables */ 89 | title.colors = theme.get_title_colors(); 90 | title.text = (theme.get_debug_mode()) ? "a" : view->get_title(); 91 | 92 | wf::dimensions_t cur_size = theme.get_text_size(title.text, size.width); 93 | 94 | if (theme.get_debug_mode()) { 95 | title.text = view->get_app_id() + " " + 96 | std::to_string(cur_size.height) + "px"; 97 | cur_size = theme.get_text_size(title.text, size.width); 98 | } 99 | 100 | title.dims.height = cur_size.height; 101 | 102 | if (cur_size.width <= theme.get_max_title_size()) { 103 | title.dims.width = cur_size.width; 104 | title.too_big = false; 105 | title.dots_dims = { 0, 0 }; 106 | } else { 107 | wf::dimensions_t dots_size = theme.get_text_size("...", size.width); 108 | 109 | title.dims.width = theme.get_max_title_size() - dots_size.width; 110 | title.too_big = true; 111 | title.dots_dims = dots_size; 112 | } 113 | 114 | title_needs_update = true; 115 | 116 | /** Necessary in order to immediately place areas correctly */ 117 | layout.resize(size.width, size.height, title.dims, title.dots_dims); 118 | } 119 | 120 | } 121 | 122 | /** Title variables */ 123 | struct { 124 | simple_texture_t hor[2], hor_dots[2]; 125 | simple_texture_t ver[2], ver_dots[2]; 126 | std::string text = ""; 127 | color_set_t colors; 128 | dimensions_t dims, dots_dims; 129 | bool dots_set = false, too_big = true; 130 | } title; 131 | 132 | bool title_needs_update = false; 133 | 134 | /** Icon variables */ 135 | struct { 136 | simple_texture_t texture; 137 | std::string app_id = ""; 138 | } icon; 139 | 140 | /** Corner variables */ 141 | struct corner_texture_t { 142 | simple_texture_t tex[2]; 143 | cairo_surface_t *surf[2]; 144 | geometry_t g; 145 | int r; 146 | }; 147 | 148 | struct { 149 | corner_texture_t tr, tl, bl, br; 150 | } corners; 151 | 152 | /** Edge variables */ 153 | struct edge_colors_t { 154 | color_set_t border, outline; 155 | } edges; 156 | 157 | /** Accent variables */ 158 | struct accent_texture_t { 159 | simple_texture_t t_trbr[2]; 160 | simple_texture_t t_tlbl[2]; 161 | int radius; 162 | }; 163 | 164 | std::vector accent_textures; 165 | 166 | /** Other general variables */ 167 | decoration_theme_t theme; 168 | decoration_layout_t layout; 169 | region_t cached_region; 170 | dimensions_t size; 171 | 172 | void update_corners(edge_colors_t colors, int corner_radius, double scale) { 173 | if ((this->corner_radius != corner_radius) || 174 | (edges.border != colors.border) || 175 | (edges.outline != colors.outline)) { 176 | corners.tr.r = corners.tl.r = corners.bl.r = corners.br.r = 0; 177 | 178 | std::stringstream round_on_str(theme.get_round_on()); 179 | std::string corner; 180 | while (round_on_str >> corner) { 181 | if (corner == "all") { 182 | corners.tr.r = corners.tl.r = corners.bl.r = corners.br.r 183 | = theme.get_corner_radius() * scale; 184 | break; 185 | } else if (corner == "tr") { 186 | corners.tr.r = (theme.get_corner_radius() * scale); 187 | } else if (corner == "tl") { 188 | corners.tl.r = (theme.get_corner_radius() * scale); 189 | } else if (corner == "bl") { 190 | corners.bl.r = (theme.get_corner_radius() * scale); 191 | } else if (corner == "br") { 192 | corners.br.r = (theme.get_corner_radius() * scale); 193 | } 194 | } 195 | int height = std::max( { corner_radius, border_size.top, 196 | border_size.bottom }); 197 | auto create_s_and_t = [&](corner_texture_t& t, matrix m, int r) { 198 | for (auto a : { ACTIVE, INACTIVE }) { 199 | t.surf[a] = theme.form_corner(a, r, m, height); 200 | cairo_surface_upload_to_texture(t.surf[a], t.tex[a]); 201 | } 202 | }; 203 | /** The transformations are how we create 4 different corners */ 204 | create_s_and_t(corners.tr, { scale, 0, 0, scale }, corners.tr.r); 205 | create_s_and_t(corners.tl, { -scale, 0, 0, scale }, corners.tl.r); 206 | create_s_and_t(corners.bl, { -scale, 0, 0, -scale }, corners.bl.r); 207 | create_s_and_t(corners.br, { scale, 0, 0, -scale }, corners.br.r); 208 | 209 | corners.tr.g = { size.width - corner_radius, 0, 210 | corner_radius, height }; 211 | corners.tl.g = { 0, 0, corner_radius, height }; 212 | corners.bl.g = { 0, size.height - height, corner_radius, height }; 213 | corners.br.g = { size.width - corner_radius, size.height - height, 214 | corner_radius, height }; 215 | 216 | edges.border.active = colors.border.active; 217 | edges.border.inactive = colors.border.inactive; 218 | edges.outline.active = colors.outline.active; 219 | edges.outline.inactive = colors.outline.inactive; 220 | this->corner_radius = corner_radius; 221 | } 222 | } 223 | 224 | public: 225 | border_size_t border_size; 226 | int corner_radius; 227 | 228 | simple_decoration_surface(wayfire_view view, theme_options options) 229 | : theme{options}, 230 | layout{theme, [=, this] (wlr_box box) {this->damage_surface_box(box); }} { 231 | this->view = view; 232 | view->connect_signal("title-changed", &title_set); 233 | 234 | // make sure to hide frame if the view is fullscreen 235 | update_decoration_size(); 236 | } 237 | 238 | virtual bool is_mapped() const final { 239 | return _mapped; 240 | } 241 | 242 | point_t get_offset() final { 243 | return { -border_size.left, -border_size.top }; 244 | } 245 | 246 | virtual dimensions_t get_size() const final { 247 | return size; 248 | } 249 | 250 | void render_title(const render_target_t& fb, geometry_t geometry, 251 | geometry_t dots_geometry, edge_t edge, geometry_t scissor) { 252 | if (title_needs_update) { 253 | update_title(fb.scale); 254 | } 255 | 256 | simple_texture_t *texture, *dots_texture; 257 | uint32_t bits = 0; 258 | if (edge == EDGE_TOP || edge == EDGE_BOTTOM) { 259 | bits = OpenGL::TEXTURE_TRANSFORM_INVERT_Y; 260 | texture = &title.hor[view->activated]; 261 | dots_texture = &title.hor_dots[view->activated]; 262 | } else { 263 | texture = &title.ver[view->activated]; 264 | dots_texture = &title.ver_dots[view->activated]; 265 | } 266 | 267 | OpenGL::render_begin(fb); 268 | fb.logic_scissor(scissor); 269 | OpenGL::render_texture(texture->tex, fb, geometry, glm::vec4(1.0f), bits); 270 | std::ofstream test{"/home/mateus/Development/wayfire-firedecor/test", std::ofstream::app}; 271 | test << "title: " << geometry << std::endl; 272 | if (title.too_big) { 273 | OpenGL::render_texture(dots_texture->tex, fb, dots_geometry, 274 | glm::vec4(1.0f), bits); 275 | } 276 | OpenGL::render_end(); 277 | } 278 | 279 | void render_icon(const render_target_t& fb, geometry_t g, 280 | const geometry_t& scissor, int32_t bits) { 281 | update_icon(); 282 | OpenGL::render_begin(fb); 283 | fb.logic_scissor(scissor); 284 | OpenGL::render_texture(icon.texture.tex, fb, g, glm::vec4(1.0f), bits); 285 | OpenGL::render_end(); 286 | } 287 | 288 | color_t alpha_trans(color_t c) { 289 | return { c.r * c.a, c.g * c.a, c.b * c.a, c.a }; 290 | } 291 | 292 | void form_accent_corners(int r, geometry_t accent, std::string corner_style, 293 | matrix m, edge_t edge) { 294 | const auto format = CAIRO_FORMAT_ARGB32; 295 | cairo_surface_t *surfaces[4]; 296 | double angle = 0; 297 | 298 | /** Colors of the accent and background, respectively */ 299 | color_t a_color; 300 | color_t b_color; 301 | 302 | wf::point_t a_origin = { accent.x, accent.y }; 303 | 304 | int h = std::max({ corner_radius, border_size.top, border_size.bottom }); 305 | 306 | wf::geometry_t a_edges[2]; 307 | if (m.xx == 1) { 308 | a_edges[0] = { 0, 0, r, accent.height }; 309 | a_edges[1] = { accent.width - r, 0, r, accent.height }; 310 | } else { 311 | a_edges[0] = { 0, 0, accent.width, r }; 312 | a_edges[1] = { 0, accent.height - r, accent.width, r }; 313 | } 314 | 315 | wf::geometry_t cut; 316 | if (m.xy == 0) { 317 | cut = { accent.x + r, accent.y, accent.width - 2 * r, accent.height }; 318 | } else { 319 | cut = { accent.x, accent.y + r, accent.width, accent.height - 2 * r }; 320 | } 321 | 322 | /**** Creation of the master path, containing all accent edge textures */ 323 | const cairo_matrix_t matrix = { 324 | (double)m.xx, (double)m.xy, (double)m.yx, (double)m.yy, 0, 0 325 | }; 326 | 327 | /** Array used to determine if a line starts on the corner or not */ 328 | struct { int tr = 0, br = 0, bl = 0, tl = 0; } retract; 329 | 330 | /** Calculate where to retract, based on diagonality */ 331 | for (int i = 0; auto c : corner_style) { 332 | if (c == '/') { 333 | if (i == 0) { 334 | retract.tl = r ; 335 | } else { 336 | retract.br = r; 337 | } 338 | i++; 339 | } else if (c == '\\') { 340 | if (i == 0) { 341 | retract.bl = r ; 342 | } else { 343 | retract.tr = r; 344 | } 345 | i++; 346 | } else if (c == '!') { 347 | i++; 348 | } 349 | } 350 | 351 | /** "Untransformed" accent area, used for correct transformations later on */ 352 | const wf::dimensions_t mod_a = { 353 | abs(accent.width * m.xx + accent.height * m.xy), 354 | abs(accent.width * m.yx + accent.height * m.yy) 355 | }; 356 | 357 | auto full_surface = cairo_image_surface_create(format, accent.width, 358 | accent.height); 359 | auto cr = cairo_create(full_surface); 360 | 361 | /** A corner indexing array and the string with the final corners to round */ 362 | std::string c_strs[4] = { "br", "tr", "tl", "bl" }; 363 | std::string to_round; 364 | 365 | /** Deciding which corners to round, based on the string and on rotation */ 366 | if (corner_style == "a") { 367 | to_round = "brtrtlbl"; 368 | retract.br = r; 369 | } else { 370 | /** True mathematical modulo */ 371 | auto modulo = [](int a, int b) -> int { 372 | return a - b * floor((double)a / b); 373 | }; 374 | 375 | for (int c_to_rotate = 0; auto str : c_strs) { 376 | if (corner_style.find(str) != std::string::npos) { 377 | 378 | /** 379 | * This function effectively rotates the chosen corner. 380 | * When m.xy == 1 (left edge), br becomes tr, tr becomes tl, etc. 381 | * On the right edge, the opposite happens. 382 | * This is to keep the correct corners rounded for the end user. 383 | */ 384 | to_round += c_strs[modulo(c_to_rotate - m.xy, 4)]; 385 | if (modulo(c_to_rotate - m.xy, 4) == 0) { retract.br = r; } 386 | } 387 | c_to_rotate++; 388 | } 389 | } 390 | 391 | /** Point of rotation, in case it is needed */ 392 | int rotation_x_d = ((m.xy == 1) ? mod_a.height : mod_a.width ) / 2; 393 | wf::point_t rotation_point = { rotation_x_d, rotation_x_d }; 394 | 395 | /** Rotation depending on the edge */ 396 | cairo_translate(cr, rotation_point); 397 | cairo_transform(cr, &matrix); 398 | cairo_translate(cr, -rotation_point); 399 | 400 | /** Lambda that creates a rounded or flat corner */ 401 | auto create_corner = [&](int w, int h, int i) { 402 | if (to_round.find(c_strs[i]) != std::string::npos) { 403 | cairo_arc(cr, w + ((i < 2) ? -r : r), h + ((i % 3 == 0) ? r : -r), r, 404 | M_PI_2 * (i - 1), M_PI_2 * i); 405 | } else { 406 | if (i % 2 == 0) { cairo_line_to(cr, w, h); } 407 | cairo_line_to(cr, w, h + ((i == 0) ? r : ((i == 2) ? -r : 0))); 408 | } 409 | }; 410 | 411 | cairo_move_to(cr, mod_a.width - retract.br, 0); 412 | if (to_round.find("r") == std::string::npos) { 413 | cairo_line_to(cr, mod_a.width - retract.tr, mod_a.height); 414 | } else { 415 | create_corner(mod_a.width, 0, 0); 416 | create_corner(mod_a.width, mod_a.height, 1); 417 | } 418 | if (to_round.find("l") == std::string::npos) { 419 | cairo_line_to(cr, retract.tl, mod_a.height); 420 | cairo_line_to(cr, retract.bl, 0); 421 | } else { 422 | create_corner(0, mod_a.height, 2); 423 | create_corner(0, 0, 3); 424 | } 425 | cairo_close_path(cr); 426 | auto master_path = cairo_copy_path(cr); 427 | cairo_destroy(cr); 428 | cairo_surface_destroy(full_surface); 429 | /****/ 430 | 431 | for (int i = 0, j = 0; i < 4; i++, angle += M_PI / 2, j = i % 2) { 432 | if (i < 2) { 433 | a_color = theme.get_accent_colors().inactive; 434 | b_color = theme.get_border_colors().inactive; 435 | } else { 436 | a_color = theme.get_accent_colors().active; 437 | b_color = theme.get_border_colors().active; 438 | } 439 | int width = a_edges[j].width; 440 | int height = a_edges[j].height; 441 | 442 | surfaces[i] = cairo_image_surface_create(format, width, height); 443 | auto cr_a = cairo_create(surfaces[i]); 444 | 445 | /** Background rectangle, behind the accent's corner */ 446 | cairo_set_source_rgba(cr_a, b_color); 447 | cairo_rectangle(cr_a, 0, 0, a_edges[j].width, a_edges[j].height); 448 | cairo_fill(cr_a); 449 | 450 | /**** Outline, done early so it can also be cropped off */ 451 | /** Translation to the correct rotation */ 452 | cairo_translate(cr_a , rotation_point); 453 | cairo_transform(cr_a , &matrix); 454 | cairo_translate(cr_a , -rotation_point); 455 | 456 | int o_size = theme.get_outline_size(); 457 | auto outline_color = (view->activated) ? 458 | alpha_trans(theme.get_outline_colors().active) : 459 | alpha_trans(theme.get_outline_colors().inactive); 460 | 461 | 462 | /** Draw outline on the bottom in case it is in the bottom edge */ 463 | int h_offset = (edge == EDGE_BOTTOM) ? mod_a.height : o_size; 464 | 465 | cairo_set_source_rgba(cr_a, outline_color); 466 | cairo_rectangle(cr_a, 0, mod_a.height - h_offset, mod_a.width, o_size); 467 | cairo_fill(cr_a); 468 | /****/ 469 | 470 | /** Dealing with intersection between the view's corners and accent */ 471 | for (auto *c : { &corners.tr, &corners.tl, &corners.bl, &corners.br } ) { 472 | 473 | /** Accent edge translated by the accent's origin */ 474 | wf::geometry_t a_edge = a_edges[j] + a_origin; 475 | 476 | /** Skip everything if the areas don't intersect */ 477 | auto in = geometry_intersection(a_edge, c->g); 478 | if (in.width == 0 || in.height == 0) { continue; } 479 | 480 | /**** Rectangle to cut the background from the accent's corner */ 481 | /** View's corner position relative to the accent's corner */ 482 | point_t v_rel_a = { 483 | c->g.x - a_edge.x, c->g.y - a_edge.y 484 | }; 485 | 486 | cairo_set_operator(cr_a, CAIRO_OPERATOR_CLEAR); 487 | cairo_rectangle(cr_a, v_rel_a, corner_radius, h); 488 | cairo_fill(cr_a); 489 | /****/ 490 | 491 | /** Removal of intersecting areas from the view corner */ 492 | for (auto active : { ACTIVE, INACTIVE }) { 493 | 494 | /**** Removing the edges with cut, flat, or diagonal corners */ 495 | /** Surface to remove from, the view corner in this case */ 496 | auto cr_v = cairo_create(c->surf[active]); 497 | cairo_set_operator(cr_v, CAIRO_OPERATOR_CLEAR); 498 | 499 | /** Transformed br accent corner, relative to the view corner */ 500 | wf::point_t t_br_rel_v = { 501 | (a_origin.x) - c->g.x, 502 | (c->g.y + c->g.height) - (a_origin.y + accent.height) 503 | }; 504 | 505 | cairo_translate(cr_v, t_br_rel_v.x, t_br_rel_v.y); 506 | 507 | cairo_translate(cr_v , rotation_point); 508 | cairo_transform(cr_v , &matrix); 509 | cairo_translate(cr_v , -rotation_point); 510 | 511 | cairo_append_path(cr_v, master_path); 512 | cairo_fill(cr); 513 | /****/ 514 | 515 | /**** Clear the view's corner with the accent rectangles */ 516 | /** Bottom of the rectangle relative to the view's corner */ 517 | wf::point_t reb_rel_v; 518 | reb_rel_v.y = (c->g.y + c->g.height) - (cut.y + cut.height); 519 | reb_rel_v.x = (cut.x - c->g.x); 520 | 521 | cairo_set_operator(cr_v, CAIRO_OPERATOR_CLEAR); 522 | cairo_rectangle(cr_v, reb_rel_v, cut.width, cut.height); 523 | cairo_fill(cr_v); 524 | /****/ 525 | 526 | cairo_surface_upload_to_texture(c->surf[active], c->tex[active]); 527 | cairo_destroy(cr_v); 528 | } 529 | } 530 | 531 | /**** Final drawing of accent corner, overlaying the drawn rectangle */ 532 | cairo_set_operator(cr_a, CAIRO_OPERATOR_SOURCE); 533 | 534 | wf::point_t t_br = { 535 | (r - mod_a.width) * (m.xx * (i % 2) + m.xy * (1 - i % 2)), 0 536 | }; 537 | 538 | cairo_set_source_rgba(cr_a, a_color); 539 | cairo_translate(cr_a, t_br.x, t_br.y); 540 | cairo_append_path(cr_a, master_path); 541 | cairo_fill(cr_a); 542 | cairo_destroy(cr_a); 543 | /****/ 544 | } 545 | auto& texture = accent_textures.back(); 546 | cairo_surface_upload_to_texture(surfaces[0], texture.t_trbr[INACTIVE]); 547 | cairo_surface_upload_to_texture(surfaces[1], texture.t_tlbl[INACTIVE]); 548 | cairo_surface_upload_to_texture(surfaces[2], texture.t_trbr[ACTIVE]); 549 | cairo_surface_upload_to_texture(surfaces[3], texture.t_tlbl[ACTIVE]); 550 | texture.radius = r; 551 | 552 | for (auto surface : surfaces) { cairo_surface_destroy(surface); } 553 | cairo_path_destroy(master_path); 554 | } 555 | 556 | void render_background_area(const render_target_t& fb, geometry_t g, 557 | point_t rect, geometry_t scissor, 558 | std::string rounded, unsigned long i, 559 | decoration_area_type_t type, matrix m, 560 | edge_t edge) { 561 | /** The view's origin */ 562 | point_t o = { rect.x, rect.y }; 563 | 564 | if (type == DECORATION_AREA_ACCENT) { 565 | /**** Render the corners of an accent */ 566 | int r; 567 | 568 | /** Create the corners, it should happen once per accent */ 569 | if (accent_textures.size() <= i) { 570 | accent_textures.resize(i + 1); 571 | r = std::min({ ceil((double)g.height / 2), ceil((double)g.width / 2), 572 | (double)corner_radius}); 573 | form_accent_corners(r, g, rounded, m, edge); 574 | } 575 | 576 | r = accent_textures.at(i).radius; 577 | 578 | geometry_t a_edges[2]; 579 | if (m.xx == 1) { 580 | a_edges[0] = { g.x, g.y, r, g.height }; 581 | a_edges[1] = { g.x + g.width - r, g.y, r, g.height }; 582 | } else { 583 | a_edges[0] = { g.x, g.y, g.width, r }; 584 | a_edges[1] = { g.x, g.y + g.height - r, g.width, r }; 585 | } 586 | 587 | OpenGL::render_begin(fb); 588 | fb.logic_scissor(scissor); 589 | OpenGL::render_texture(accent_textures.at(i).t_trbr[view->activated].tex, 590 | fb, a_edges[0] + o, glm::vec4(1.0)); 591 | OpenGL::render_texture(accent_textures.at(i).t_tlbl[view->activated].tex, 592 | fb, a_edges[1] + o, glm::vec4(1.0)); 593 | /****/ 594 | 595 | /**** Render the internal rectangles of the accent */ 596 | wf::geometry_t accent_rect; 597 | if (m.xy == 0) { 598 | accent_rect = { g.x + r, g.y, g.width - 2 * r, g.height }; 599 | } else { 600 | accent_rect = { g.x, g.y + r, g.width, g.height - 2 * r }; 601 | } 602 | 603 | color_t color = (view->activated) ? 604 | alpha_trans(theme.get_accent_colors().active) : 605 | alpha_trans(theme.get_accent_colors().inactive); 606 | OpenGL::render_rectangle(accent_rect + o, color, 607 | fb.get_orthographic_projection()); 608 | /****/ 609 | OpenGL::render_end(); 610 | } else { 611 | /**** Render a single rectangle when the area is a background */ 612 | color_t color = (view->activated) ? 613 | alpha_trans(theme.get_border_colors().active) : 614 | alpha_trans(theme.get_border_colors().inactive); 615 | color_t o_color = (view->activated) ? 616 | alpha_trans(theme.get_outline_colors().active) : 617 | alpha_trans(theme.get_outline_colors().inactive); 618 | 619 | wf::geometry_t g_o; 620 | int o_s = theme.get_outline_size(); 621 | if (edge == wf::firedecor::EDGE_TOP) { 622 | g_o = { g.x, g.y, g.width, o_s }; 623 | g = { g.x, g.y + o_s, g.width, g.height - o_s }; 624 | } else if (edge == wf::firedecor::EDGE_LEFT) { 625 | g_o = { g.x, g.y, o_s, g.height }; 626 | g = { g.x + o_s, g.y, g.width - o_s, g.height }; 627 | } else if (edge == wf::firedecor::EDGE_BOTTOM) { 628 | g_o = { g.x, g.y + g.height - o_s, g.width, o_s }; 629 | g = { g.x, g.y, g.width, g.height - o_s }; 630 | } else if (edge == wf::firedecor::EDGE_RIGHT) { 631 | g_o = { g.x + g.width - o_s, g.y, o_s, g.height }; 632 | g = { g.x, g.y, g.width - o_s, g.height }; 633 | } 634 | g_o = g_o + o; 635 | 636 | OpenGL::render_begin(fb); 637 | fb.logic_scissor(scissor); 638 | OpenGL::render_rectangle(g + o, color, fb.get_orthographic_projection()); 639 | OpenGL::render_rectangle(g_o, o_color, fb.get_orthographic_projection()); 640 | OpenGL::render_end(); 641 | /****/ 642 | } 643 | } 644 | 645 | void render_background(const render_target_t& fb, geometry_t rect, 646 | const geometry_t& scissor) { 647 | edge_colors_t colors = { 648 | theme.get_border_colors(), theme.get_outline_colors() 649 | }; 650 | 651 | colors.border.active = alpha_trans(colors.border.active); 652 | colors.border.inactive = alpha_trans(colors.border.inactive); 653 | 654 | int r = theme.get_corner_radius() * fb.scale; 655 | update_corners(colors, r, fb.scale); 656 | 657 | /** Borders */ 658 | unsigned long i = 0; 659 | point_t rect_o = { rect.x, rect.y }; 660 | for (auto area : layout.get_background_areas()) { 661 | render_background_area(fb, area->get_geometry(), rect_o, scissor, 662 | area->get_corners(), i, area->get_type(), 663 | area->get_m(), area->get_edge()); 664 | i++; 665 | } 666 | 667 | OpenGL::render_begin(fb); 668 | fb.logic_scissor(scissor); 669 | 670 | /** Outlines */ 671 | bool a = view->activated; 672 | point_t o = { rect.x, rect.y }; 673 | /** Rendering all corners */ 674 | for (auto *c : { &corners.tr, &corners.tl, &corners.bl, &corners.br }) { 675 | OpenGL::render_texture(c->tex[a].tex, fb, c->g + o, glm::vec4(1.0f)); 676 | } 677 | OpenGL::render_end(); 678 | } 679 | 680 | void render_scissor_box(const render_target_t& fb, point_t origin, 681 | const wlr_box& scissor) { 682 | /** Draw the background (corners and border) */ 683 | wlr_box geometry{origin.x, origin.y, size.width, size.height}; 684 | render_background(fb, geometry, scissor); 685 | 686 | auto renderables = layout.get_renderable_areas(); 687 | for (auto item : renderables) { 688 | int32_t bits = 0; 689 | if (item->get_edge() == EDGE_LEFT) { 690 | bits = OpenGL::TEXTURE_TRANSFORM_INVERT_Y; 691 | } else if (item->get_edge() == EDGE_RIGHT) { 692 | bits = OpenGL::TEXTURE_TRANSFORM_INVERT_X; 693 | } 694 | if (item->get_type() == DECORATION_AREA_TITLE) { 695 | render_title(fb, item->get_geometry() + origin, 696 | item->get_dots_geometry() + origin, item->get_edge(), 697 | scissor); 698 | } else if (item->get_type() == DECORATION_AREA_BUTTON) { 699 | item->as_button().set_active(view->activated); 700 | item->as_button().set_maximized(view->tiled_edges); 701 | item->as_button().render(fb, item->get_geometry() + origin, scissor); 702 | } else if (item->get_type() == DECORATION_AREA_ICON) { 703 | render_icon(fb, item->get_geometry() + origin, scissor, bits); 704 | } 705 | } 706 | } 707 | 708 | virtual void simple_render(const render_target_t& fb, int x, int y, 709 | const region_t& damage) override { 710 | region_t frame = this->cached_region + (point_t){x, y}; 711 | frame &= damage; 712 | 713 | update_layout(DONT_FORCE); 714 | 715 | int h = std::max({ corner_radius, border_size.top, border_size.bottom }); 716 | corners.tr.g = { size.width - corner_radius, 0, corner_radius, h }; 717 | corners.bl.g = { 0, size.height - h, corner_radius, h }; 718 | corners.br.g = { size.width - corner_radius, 719 | size.height - h, corner_radius, h }; 720 | 721 | for (const auto& box : frame) { 722 | render_scissor_box(fb, {x, y}, wlr_box_from_pixman_box(box)); 723 | } 724 | } 725 | 726 | bool accepts_input(int32_t sx, int32_t sy) override { 727 | return pixman_region32_contains_point(cached_region.to_pixman(), 728 | sx, sy, NULL); 729 | } 730 | 731 | virtual void on_pointer_enter(int x, int y) override { 732 | layout.handle_motion(x, y); 733 | } 734 | 735 | virtual void on_pointer_leave() override { 736 | layout.handle_focus_lost(); 737 | } 738 | 739 | virtual void on_pointer_motion(int x, int y) override { 740 | handle_action(layout.handle_motion(x, y)); 741 | } 742 | 743 | virtual void on_pointer_button(uint32_t button, uint32_t state) override { 744 | if (button != BTN_LEFT) { 745 | return; 746 | } 747 | 748 | handle_action(layout.handle_press_event(state == WLR_BUTTON_PRESSED)); 749 | } 750 | 751 | // TODO: implement a pinning button. 752 | void handle_action(decoration_layout_t::action_response_t action) { 753 | switch (action.action) { 754 | case DECORATION_ACTION_MOVE: 755 | return view->move_request(); 756 | 757 | case DECORATION_ACTION_RESIZE: 758 | return view->resize_request(action.edges); 759 | 760 | case DECORATION_ACTION_CLOSE: 761 | return view->close(); 762 | 763 | case DECORATION_ACTION_TOGGLE_MAXIMIZE: 764 | if (view->tiled_edges) { 765 | view->tile_request(0); 766 | } else { 767 | view->tile_request(TILED_EDGES_ALL); 768 | } 769 | 770 | break; 771 | 772 | case DECORATION_ACTION_MINIMIZE: 773 | view->minimize_request(true); 774 | break; 775 | 776 | default: 777 | break; 778 | } 779 | } 780 | 781 | virtual void on_touch_down(int x, int y) override { 782 | layout.handle_motion(x, y); 783 | handle_action(layout.handle_press_event()); 784 | } 785 | 786 | virtual void on_touch_motion(int x, int y) override { 787 | handle_action(layout.handle_motion(x, y)); 788 | } 789 | 790 | virtual void on_touch_up() override { 791 | handle_action(layout.handle_press_event(false)); 792 | layout.handle_focus_lost(); 793 | } 794 | 795 | void unmap() { 796 | _mapped = false; 797 | emit_map_state_change(this); 798 | } 799 | 800 | void resize(dimensions_t dims) { 801 | view->damage(); 802 | size = dims; 803 | layout.resize(size.width, size.height, title.dims, title.dots_dims); 804 | if (!view->fullscreen) { 805 | this->cached_region = layout.calculate_region(); 806 | } 807 | 808 | view->damage(); 809 | } 810 | 811 | void update_decoration_size() { 812 | if (view->fullscreen) { 813 | border_size = { 0, 0, 0, 0 }; 814 | this->cached_region.clear(); 815 | } else { 816 | border_size = layout.parse_border(theme.get_border_size()); 817 | this->cached_region = layout.calculate_region(); 818 | } 819 | } 820 | }; 821 | 822 | class simple_decorator_t : public decorator_frame_t_t { 823 | wayfire_view view; 824 | nonstd::observer_ptr deco; 825 | 826 | public: 827 | simple_decorator_t(wayfire_view view, theme_options options) { 828 | this->view = view; 829 | 830 | auto sub = std::make_unique(view, options); 831 | deco = {sub}; 832 | view->add_subsurface(std::move(sub), true); 833 | view->damage(); 834 | view->connect_signal("subsurface-removed", &on_subsurface_removed); 835 | } 836 | 837 | ~simple_decorator_t() { 838 | if (deco) { 839 | // subsurface_removed unmaps it 840 | view->remove_subsurface(deco); 841 | } 842 | } 843 | 844 | simple_decorator_t(const simple_decorator_t &) = delete; 845 | simple_decorator_t(simple_decorator_t &&) = delete; 846 | simple_decorator_t& operator =(const simple_decorator_t&) = delete; 847 | simple_decorator_t& operator =(simple_decorator_t&&) = delete; 848 | 849 | signal_connection_t on_subsurface_removed = [&] (auto data) { 850 | auto ev = static_cast(data); 851 | if (ev->subsurface.get() == deco.get()) { 852 | deco->unmap(); 853 | deco = nullptr; 854 | } 855 | }; 856 | 857 | virtual geometry_t expand_wm_geometry( geometry_t contained_wm_geometry) override { 858 | contained_wm_geometry.x -= deco->border_size.left; 859 | contained_wm_geometry.y -= deco->border_size.top; 860 | contained_wm_geometry.width += deco->border_size.left + 861 | deco->border_size.right; 862 | contained_wm_geometry.height += deco->border_size.top + 863 | deco->border_size.bottom; 864 | 865 | return contained_wm_geometry; 866 | } 867 | 868 | // TODO: Minimum size must fit buttons, icon, a truncated title, and corners. 869 | virtual void calculate_resize_size( int& target_width, int& target_height) override { 870 | target_width -= deco->border_size.left + 871 | deco->border_size.right; 872 | target_height -= deco->border_size.top + deco->border_size.bottom; 873 | 874 | target_width = std::max(target_width, 1); 875 | target_height = std::max(target_height, 1); 876 | } 877 | 878 | virtual void notify_view_activated(bool active) override { 879 | (void)active; 880 | view->damage(); 881 | } 882 | 883 | virtual void notify_view_resized(geometry_t view_geometry) override { 884 | deco->resize(dimensions(view_geometry)); 885 | } 886 | 887 | virtual void notify_view_tiled() override {} 888 | 889 | virtual void notify_view_fullscreen() override { 890 | deco->update_decoration_size(); 891 | 892 | if (!view->fullscreen) { 893 | notify_view_resized(view->get_wm_geometry()); 894 | } 895 | } 896 | }; 897 | 898 | void init_view(wayfire_view view, theme_options options) { 899 | auto firedecor = std::make_unique(view, options); 900 | view->set_decoration(std::move(firedecor)); 901 | } 902 | 903 | void deinit_view(wayfire_view view) { 904 | view->set_decoration(nullptr); 905 | } 906 | } 907 | 908 | -------------------------------------------------------------------------------- /src/firedecor-subsurface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "firedecor-theme.hpp" 6 | 7 | namespace wf::firedecor { 8 | 9 | void init_view(wayfire_view view, wf::firedecor::theme_options options); 10 | void deinit_view(wayfire_view view); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/firedecor-theme.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "firedecor-theme.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace wf { 20 | namespace firedecor { 21 | /** Create a new theme with the default parameters */ 22 | decoration_theme_t::decoration_theme_t(wf::firedecor::theme_options options) : 23 | theme_options{options} {} 24 | 25 | std::string decoration_theme_t::get_layout() const { 26 | return layout.get_value(); 27 | } 28 | 29 | /* Size return functions */ 30 | std::string decoration_theme_t::get_border_size() const { 31 | return border_size.get_value(); 32 | } 33 | int decoration_theme_t::get_outline_size() const { 34 | return outline_size.get_value(); 35 | } 36 | int decoration_theme_t::get_font_size() const { 37 | return font_size.get_value(); 38 | } 39 | int decoration_theme_t::get_max_title_size() const { 40 | return max_title_size.get_value(); 41 | } 42 | int decoration_theme_t::get_corner_radius() const { 43 | return corner_radius.get_value(); 44 | } 45 | int decoration_theme_t::get_button_size() const { 46 | return button_size.get_value(); 47 | } 48 | int decoration_theme_t::get_icon_size() const { 49 | return icon_size.get_value(); 50 | } 51 | int decoration_theme_t::get_padding_size() const { 52 | return padding_size.get_value(); 53 | } 54 | 55 | /* Color return functions */ 56 | color_set_t decoration_theme_t::get_border_colors() const { 57 | return { active_border.get_value(), inactive_border.get_value() }; 58 | } 59 | color_set_t decoration_theme_t::get_outline_colors() const { 60 | return { active_outline.get_value(), inactive_outline.get_value() }; 61 | } 62 | color_set_t decoration_theme_t::get_title_colors() const { 63 | return { active_title.get_value(), inactive_title.get_value() }; 64 | } 65 | color_set_t decoration_theme_t::get_accent_colors() const { 66 | return { active_accent.get_value(), inactive_accent.get_value() }; 67 | } 68 | 69 | /* Other return functions */ 70 | bool decoration_theme_t::has_title_orientation(orientation_t orientation) const { 71 | std::stringstream stream(layout.get_value()); 72 | std::string current_symbol; 73 | 74 | edge_t current_edge = EDGE_TOP; 75 | 76 | while (stream >> current_symbol) { 77 | if (current_symbol == "-") { 78 | if (current_edge == EDGE_TOP) { 79 | current_edge = EDGE_LEFT; 80 | } else if (current_edge == EDGE_LEFT) { 81 | current_edge = EDGE_BOTTOM; 82 | } else { 83 | current_edge = EDGE_RIGHT; 84 | } 85 | } else if (current_symbol == "title") { 86 | if (((current_edge == EDGE_TOP || current_edge == EDGE_BOTTOM) && 87 | orientation == HORIZONTAL) || 88 | ((current_edge == EDGE_LEFT || current_edge == EDGE_RIGHT) && 89 | orientation == VERTICAL)) { 90 | return true; 91 | } 92 | } 93 | } 94 | return false; 95 | } 96 | bool decoration_theme_t::get_debug_mode() const { 97 | return debug_mode.get_value(); 98 | } 99 | std::string decoration_theme_t::get_round_on() const { 100 | return round_on.get_value(); 101 | } 102 | 103 | wf::dimensions_t decoration_theme_t::get_text_size(std::string text, int width) const { 104 | const auto format = CAIRO_FORMAT_ARGB32; 105 | auto surface = cairo_image_surface_create(format, width, font_size.get_value()); 106 | auto cr = cairo_create(surface); 107 | 108 | PangoFontDescription *font_desc; 109 | PangoLayout *layout; 110 | PangoRectangle text_size; 111 | 112 | font_desc = pango_font_description_from_string(((std::string)font.get_value()).c_str()); 113 | pango_font_description_set_absolute_size(font_desc, font_size.get_value() * PANGO_SCALE); 114 | layout = pango_cairo_create_layout(cr); 115 | pango_layout_set_font_description(layout, font_desc); 116 | pango_layout_set_text(layout, text.c_str(), text.size()); 117 | pango_layout_get_pixel_extents(layout, NULL, &text_size); 118 | pango_font_description_free(font_desc); 119 | g_object_unref(layout); 120 | cairo_destroy(cr); 121 | cairo_surface_destroy(surface); 122 | 123 | return { text_size.width, text_size.height }; 124 | } 125 | 126 | cairo_surface_t* decoration_theme_t::form_title(std::string text, 127 | wf::dimensions_t title_size, bool active, orientation_t orientation) const { 128 | const auto format = CAIRO_FORMAT_ARGB32; 129 | cairo_surface_t* surface; 130 | if (orientation == HORIZONTAL) { 131 | surface = cairo_image_surface_create( 132 | format, title_size.width, title_size.height); 133 | } else { 134 | surface = cairo_image_surface_create( 135 | format, title_size.height, title_size.width); 136 | } 137 | 138 | wf::color_t color = (active) ? active_title.get_value() : inactive_title.get_value(); 139 | 140 | auto cr = cairo_create(surface); 141 | if (orientation == VERTICAL) { 142 | double radius = (double)title_size.width / 2;; 143 | cairo_translate(cr, radius, radius); 144 | cairo_rotate(cr, -M_PI / 2); 145 | cairo_translate(cr, -radius, -radius); 146 | } 147 | 148 | PangoFontDescription *font_desc; 149 | PangoLayout *layout; 150 | 151 | // render text 152 | font_desc = pango_font_description_from_string(((std::string)font.get_value()).c_str()); 153 | pango_font_description_set_absolute_size(font_desc, font_size.get_value() * PANGO_SCALE); 154 | 155 | layout = pango_cairo_create_layout(cr); 156 | pango_layout_set_font_description(layout, font_desc); 157 | pango_layout_set_text(layout, text.c_str(), text.size()); 158 | cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); 159 | pango_cairo_show_layout(cr, layout); 160 | pango_font_description_free(font_desc); 161 | g_object_unref(layout); 162 | cairo_destroy(cr); 163 | 164 | return surface; 165 | } 166 | 167 | cairo_surface_t *decoration_theme_t::form_corner(bool active, int r, 168 | matrix m, 169 | int height) const { 170 | double c_r = corner_radius.get_value() * abs(m.xx); 171 | double o_r = c_r - abs(m.xx) * (double)outline_size.get_value() / 2; 172 | 173 | const auto format = CAIRO_FORMAT_ARGB32; 174 | auto *surface = cairo_image_surface_create(format, c_r, height); 175 | auto cr = cairo_create(surface); 176 | 177 | cairo_translate(cr, c_r / 2, (double)height / 2); 178 | cairo_scale(cr, m.xx, m.yy); 179 | cairo_translate(cr, -c_r / 2, -(double)height / 2); 180 | 181 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 182 | /* Border */ 183 | wf::color_t color = active ? active_border.get_value() : 184 | inactive_border.get_value(); 185 | cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); 186 | if (r > 0) { 187 | cairo_move_to(cr, 0, (int)(height - c_r)); 188 | cairo_arc(cr, 0, (int)(height - c_r), c_r, 0, M_PI / 2); 189 | cairo_fill(cr); 190 | 191 | cairo_rectangle(cr, 0, 0, c_r, height - c_r); 192 | cairo_fill(cr); 193 | } else { 194 | cairo_rectangle(cr, 0, 0, c_r, height); 195 | cairo_fill(cr); 196 | } 197 | 198 | /* Outline */ 199 | color = active ? active_outline.get_value() : inactive_outline.get_value(); 200 | cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); 201 | cairo_set_line_width(cr, outline_size.get_value() * abs(m.xx)); 202 | if (r > 0) { 203 | cairo_move_to(cr, o_r, 0); 204 | cairo_line_to(cr, o_r, height - c_r); 205 | cairo_arc(cr, 0, (int)(height - c_r), o_r, 0, M_PI / 2); 206 | cairo_stroke(cr); 207 | } else { 208 | cairo_move_to(cr, o_r, 0); 209 | cairo_line_to(cr, o_r, height - c_r + o_r); 210 | cairo_line_to(cr, 0, height - c_r + o_r); 211 | } 212 | cairo_stroke(cr); 213 | cairo_destroy(cr); 214 | 215 | return surface; 216 | } 217 | 218 | cairo_surface_t *decoration_theme_t::form_button(button_type_t button, double hover, 219 | bool active, bool maximized) const { 220 | if ((std::string)button_style.get_value() != "wayfire" && 221 | (std::string)button_style.get_value() != "firedecor" && 222 | (std::string)button_style.get_value() != "simple") { 223 | std::string directory = (std::string)getenv("HOME") + 224 | "/.config/firedecor/button-styles/" + 225 | (std::string)button_style.get_value() + "/"; 226 | std::string status; 227 | std::string path; 228 | 229 | if (hover == 0.0) { 230 | if (!active && inactive_buttons.get_value()) { 231 | status = "-inactive."; 232 | } else { 233 | status = "."; 234 | } 235 | } else if (hover < 0.0) { 236 | status = "-pressed."; 237 | } else { 238 | status = "-hovered."; 239 | } 240 | 241 | switch (button) { 242 | case BUTTON_CLOSE: 243 | path = directory + "close" + status; 244 | break; 245 | case BUTTON_TOGGLE_MAXIMIZE: 246 | path = directory + "toggle-maximize" + status; 247 | break; 248 | case BUTTON_MINIMIZE: 249 | path = directory + "minimize" + status; 250 | break; 251 | default: 252 | assert(false); 253 | } 254 | if (auto full_path = path + "png"; exists(full_path)) { 255 | return surface_png(full_path, button_size.get_value()); 256 | } else if (auto full_path = path + "svg"; exists(full_path)) { 257 | return surface_svg(full_path, button_size.get_value()); 258 | } 259 | } 260 | 261 | cairo_surface_t *button_surface = cairo_image_surface_create( 262 | CAIRO_FORMAT_ARGB32, button_size.get_value(), button_size.get_value()); 263 | 264 | auto cr = cairo_create(button_surface); 265 | cairo_set_antialias(cr, CAIRO_ANTIALIAS_BEST); 266 | 267 | /* Clear the button background */ 268 | cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); 269 | cairo_set_source_rgba(cr, 0, 0, 0, 0); 270 | cairo_rectangle(cr, 0, 0, button_size.get_value(), button_size.get_value()); 271 | cairo_fill(cr); 272 | 273 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 274 | 275 | color_t base, hovered; 276 | double line; 277 | double base_qty; 278 | 279 | /** Coloured base on hover/press. Don't compare float to 0 */ 280 | if (fabs(hover) > 1e-3 || (inactive_buttons.get_value() && active) || 281 | !inactive_buttons.get_value()) { 282 | switch (button) { 283 | case BUTTON_CLOSE: 284 | base = normal_close.get_value(); 285 | hovered = hovered_close.get_value(); 286 | break; 287 | case BUTTON_TOGGLE_MAXIMIZE: 288 | base = normal_max.get_value(); 289 | hovered = hovered_max.get_value(); 290 | break; 291 | case BUTTON_MINIMIZE: 292 | base = normal_min.get_value(); 293 | hovered = hovered_min.get_value(); 294 | break; 295 | default: 296 | assert(false); 297 | } 298 | line = 0.54; 299 | base_qty = 0.6; 300 | } else { 301 | base = { 0.40, 0.40, 0.43, 1.0 }; 302 | line = 0.27; 303 | base_qty = 1.0; 304 | } 305 | 306 | 307 | /** Draw the base */ 308 | cairo_set_source_rgba(cr, 309 | base.r * (1.0 - hover) + hovered.r * hover, 310 | base.g * (1.0 - hover) + hovered.g * hover, 311 | base.b * (1.0 - hover) + hovered.b * hover, 312 | base.a); 313 | cairo_arc(cr, (double)button_size.get_value() / 2, (double)button_size.get_value() / 2, 314 | (double)button_size.get_value() / 2, 0, 2 * M_PI); 315 | cairo_fill(cr); 316 | 317 | /** Draw the border */ 318 | cairo_set_line_width(cr, 1.0); 319 | cairo_set_source_rgba(cr, 0.00, 0.00, 0.00, line / 2); 320 | double r = (double)button_size.get_value() / 2 - 0.5 * 1.0; 321 | cairo_arc(cr, (double)button_size.get_value() / 2, (double)button_size.get_value() / 2, r, 0, 2 * M_PI); 322 | cairo_stroke(cr); 323 | 324 | /** Draw the icon */ 325 | //cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); 326 | if (hover != 0) { 327 | cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); 328 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); 329 | } 330 | if ((std::string)button_style.get_value() == "wayfire") { 331 | switch (button) { 332 | case BUTTON_CLOSE: 333 | cairo_set_line_width(cr, 1.5 * 1.0); 334 | cairo_move_to(cr, 1.0 * button_size.get_value() / 4.0, 1.0 * button_size.get_value() / 4.0); 335 | cairo_line_to(cr, 3.0 * button_size.get_value() / 4.0, 3.0 * button_size.get_value() / 4.0); 336 | cairo_move_to(cr, 3.0 * button_size.get_value() / 4.0, 1.0 * button_size.get_value() / 4.0); 337 | cairo_line_to(cr, 1.0 * button_size.get_value() / 4.0, 3.0 * button_size.get_value() / 4.0); 338 | cairo_stroke(cr); 339 | break; 340 | case BUTTON_TOGGLE_MAXIMIZE: 341 | cairo_set_line_width(cr, 1.5 * 1.0); 342 | cairo_rectangle(cr, button_size.get_value() / 4.0, button_size.get_value() / 4.0, 343 | button_size.get_value() / 2.0, button_size.get_value() / 2.0); 344 | cairo_stroke(cr); 345 | break; 346 | case BUTTON_MINIMIZE: 347 | cairo_set_line_width(cr, 1.75); 348 | cairo_move_to(cr, 1.0 * button_size.get_value() / 4.0, button_size.get_value() / 2.0); 349 | cairo_line_to(cr, 3.0 * button_size.get_value() / 4.0, button_size.get_value() / 2.0); 350 | cairo_stroke(cr); 351 | break; 352 | default: 353 | assert(false); 354 | } 355 | } else if ((std::string)button_style.get_value() == "firedecor") { 356 | switch (button) { 357 | case BUTTON_CLOSE: 358 | { 359 | cairo_set_line_width(cr, 1.5); 360 | /* Line from top left to bottom right */ 361 | cairo_move_to(cr, (double)button_size.get_value() / 2, (double)button_size.get_value() / 2); 362 | cairo_rel_move_to(cr, -0.25 * button_size.get_value() * hover, 363 | -0.25 * button_size.get_value() * hover); 364 | cairo_rel_line_to(cr, 0.5 * button_size.get_value() * hover, 365 | 0.5 * button_size.get_value() * hover); 366 | /* Line from bottom left to top right */ 367 | cairo_move_to(cr, (double)button_size.get_value() / 2, (double)button_size.get_value() / 2); 368 | cairo_rel_move_to(cr, -0.25 * button_size.get_value() * hover, 369 | 0.25 * button_size.get_value() * hover); 370 | cairo_rel_line_to(cr, 0.5 * button_size.get_value() * hover, 371 | -0.5 * button_size.get_value() * hover); 372 | cairo_stroke(cr); 373 | } 374 | break; 375 | case BUTTON_TOGGLE_MAXIMIZE: 376 | { 377 | cairo_set_line_width(cr, 1.5); 378 | wf::pointf_t north_east_arrow_pos, south_west_arrow_pos; 379 | if (maximized) { 380 | north_east_arrow_pos = { 0.28 * button_size.get_value(), 0.72 * button_size.get_value() }; 381 | south_west_arrow_pos = { 0.72 * button_size.get_value(), 0.28 * button_size.get_value() }; 382 | } else { 383 | north_east_arrow_pos = { 0.563 * button_size.get_value(), 0.437 * button_size.get_value() }; 384 | south_west_arrow_pos = { 0.437 * button_size.get_value(), 0.563 * button_size.get_value() }; 385 | } 386 | 387 | /* Top right arrow */ 388 | cairo_move_to(cr, north_east_arrow_pos.x, north_east_arrow_pos.y); 389 | cairo_rel_move_to(cr, -0.175 * button_size.get_value() * hover, 390 | -0.175 * button_size.get_value() * hover); 391 | cairo_rel_line_to(cr, 0.35 * button_size.get_value() * hover, 0); 392 | cairo_rel_line_to(cr, 0, 0.35 * button_size.get_value() * hover); 393 | cairo_stroke(cr); 394 | /* Bottom left arrow */ 395 | cairo_move_to(cr, south_west_arrow_pos.x, south_west_arrow_pos.y); 396 | cairo_rel_move_to(cr, -0.175 * button_size.get_value() * hover, 397 | -0.175 * button_size.get_value() * hover); 398 | cairo_rel_line_to(cr, 0, 0.35 * button_size.get_value() * hover); 399 | cairo_rel_line_to(cr, 0.35 * button_size.get_value() * hover, 0); 400 | cairo_stroke(cr); 401 | } 402 | break; 403 | case BUTTON_MINIMIZE: 404 | { 405 | cairo_set_line_width(cr, 2.0); 406 | cairo_move_to(cr, (double)button_size.get_value() / 2, (double)button_size.get_value() / 2); 407 | cairo_rel_move_to(cr, -0.25 * button_size.get_value() * hover, 0); 408 | cairo_rel_line_to(cr, 0.5 * button_size.get_value() * hover, 0); 409 | cairo_stroke(cr); 410 | } 411 | break; 412 | } 413 | } 414 | 415 | cairo_fill(cr); 416 | cairo_destroy(cr); 417 | 418 | return button_surface; 419 | } 420 | 421 | bool exists(std::string path) { 422 | if (path.back() == '/') { 423 | return std::filesystem::exists(path); 424 | } 425 | 426 | std::string path_head = path.substr(0, path.rfind('/')); 427 | 428 | if (!std::filesystem::exists(path_head)) { 429 | return false; 430 | } 431 | 432 | for (auto& dir_entry : std::filesystem::directory_iterator(path_head)) { 433 | if (boost::iequals(path, (std::string)dir_entry.path())) { 434 | return true; 435 | } 436 | } 437 | 438 | return false; 439 | } 440 | 441 | std::string get_real_name(std::string path) { 442 | std::string path_head = path.substr(0, path.rfind('/')); 443 | 444 | for (auto& dir_entry : std::filesystem::directory_iterator(path_head)) { 445 | if (boost::iequals(path, (std::string)dir_entry.path())) { 446 | return (std::string)dir_entry.path(); 447 | } 448 | } 449 | 450 | return path; 451 | } 452 | 453 | cairo_surface_t *decoration_theme_t::surface_svg(std::string path, int size) const { 454 | auto surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size); 455 | auto surface_rsvg = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size); 456 | auto cr = cairo_create(surface); 457 | auto cr_rsvg = cairo_create(surface_rsvg); 458 | 459 | GFile *file = g_file_new_for_path(path.c_str()); 460 | RsvgHandle *svg = rsvg_handle_new_from_gfile_sync(file, RSVG_HANDLE_FLAGS_NONE, 461 | NULL, NULL); 462 | RsvgRectangle rect { 0, 0, (double)size, (double)size }; 463 | rsvg_handle_render_document(svg, cr_rsvg, &rect, nullptr); 464 | cairo_destroy(cr_rsvg); 465 | 466 | cairo_translate(cr, (double)size / 2, (double)size / 2); 467 | cairo_scale(cr, 1.0, -1.0); 468 | cairo_translate(cr, -(double)size / 2, -(double)size / 2); 469 | 470 | cairo_set_source_surface(cr, surface_rsvg, 0, 0); 471 | cairo_paint(cr); 472 | cairo_surface_destroy(surface_rsvg); 473 | 474 | cairo_destroy(cr); 475 | 476 | g_object_unref(svg); 477 | g_object_unref(file); 478 | 479 | return surface; 480 | } 481 | 482 | cairo_surface_t * decoration_theme_t::surface_png(std::string path, int size) const { 483 | auto surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size); 484 | auto cr = cairo_create(surface); 485 | 486 | auto image = cairo_image_surface_create_from_png(path.c_str()); 487 | double width = cairo_image_surface_get_width(image); 488 | double height = cairo_image_surface_get_height(image); 489 | 490 | cairo_translate(cr, (double)size / 2, (double)size / 2); 491 | cairo_scale(cr, (double)size / width, -(double)size / height); 492 | cairo_translate(cr, -(double)size / 2, -(double)size / 2); 493 | 494 | cairo_set_source_surface(cr, image, (size - width) / 2, (size - height) / 2); 495 | cairo_paint(cr); 496 | cairo_surface_destroy(image); 497 | cairo_destroy(cr); 498 | 499 | return surface; 500 | } 501 | 502 | std::vector get_desktops( std::string path ) { 503 | std::vector desktops; 504 | 505 | if (!exists(path)) { 506 | return desktops; 507 | } 508 | 509 | for (const auto& entry : std::filesystem::directory_iterator(path)) { 510 | if (entry.is_regular_file() && entry.path().extension() == ".desktop") { 511 | desktops.push_back(entry.path()); 512 | } 513 | } 514 | 515 | return desktops; 516 | } 517 | 518 | std::string get_from_desktop(std::string path, std::string var) { 519 | std::ifstream input_file(path); 520 | std::string line; 521 | while(std::getline(input_file, line)) { 522 | if (auto index = line.find(var); index != std::string::npos) { 523 | return (line.substr(index + var.length())); 524 | } 525 | } 526 | if (var == "Icon") { 527 | return "application-x-executable"; 528 | } else { 529 | return "This_name_is_not_supposed_to_work.1234lol"; 530 | } 531 | } 532 | 533 | cairo_surface_t *decoration_theme_t::form_icon(std::string app_id) const { 534 | std::string line; 535 | std::string icons = (std::string)getenv("HOME") + "/.local/share/firedecor_icons"; 536 | std::ofstream icon_file_out(icons, std::ofstream::out | std::ofstream::app); 537 | 538 | while (true) { 539 | /** 540 | * First, check if the icon has already been found, 541 | * this will be true the vast majority of the time, 542 | * drastically improving speed. 543 | */ 544 | std::ifstream icon_file_in(icons); 545 | while (std::getline(icon_file_in, line)) { 546 | if (line.find(app_id + " ") == 0) { 547 | std::string path = line.substr(line.find(' ') + 1); 548 | if (line.rfind(".svg") != std::string::npos) { 549 | return surface_svg(path, icon_size.get_value()); 550 | } else if (line.rfind(".png") != std::string::npos) { 551 | return surface_png(path, icon_size.get_value()); 552 | } 553 | } 554 | } 555 | icon_file_in.close(); 556 | 557 | std::string icon_name = (std::string)getenv("HOME") + 558 | ".config/firedecor/executable.svg"; 559 | bool found = false; 560 | 561 | std::vector app_dirs = { 562 | (std::string)getenv("HOME") + "/.local/share/applications/", 563 | "/usr/local/share/applications/", 564 | "/usr/share/applications/" 565 | }; 566 | 567 | /** Helpful specific case for some steam games */ 568 | if (app_id.substr(0, 10) == "steam_app_") { 569 | icon_name = "steam_icon_" + app_id.substr(10); 570 | found = true; 571 | } 572 | 573 | for (auto path : app_dirs) { 574 | if (found) { break; } 575 | if (auto icon_path = path + app_id + ".desktop"; exists(icon_path)) { 576 | /* This definition is here mostly to filter for capitalized names */ 577 | icon_path = get_real_name(icon_path); 578 | icon_name = get_from_desktop(icon_path, "Icon"); 579 | found = true; 580 | break; 581 | } 582 | } 583 | 584 | if (!found) { 585 | for (auto path : app_dirs) { 586 | std::vector desktops = get_desktops(path); 587 | for (auto desktop : desktops) { 588 | /* Check the executable name */ 589 | std::stringstream ess(get_from_desktop(desktop, "Exec")); 590 | std::string exec; 591 | ess >> exec; 592 | 593 | if (boost::iequals((std::string)std::filesystem::path(exec) 594 | .filename(), app_id)) { 595 | icon_name = get_from_desktop(desktop, "Icon"); 596 | break; 597 | } 598 | 599 | /* Check the name */ 600 | std::stringstream nss(get_from_desktop(desktop, "Name")); 601 | std::string name; 602 | nss >> name; 603 | if (boost::iequals(name, app_id)) { 604 | icon_name = get_from_desktop(desktop, "Icon"); 605 | break; 606 | } 607 | 608 | /* Check StartupWMClass for electron apps */ 609 | std::stringstream sss(get_from_desktop(desktop, 610 | "StartupWMClas")); 611 | std::string startupWMClass; 612 | sss >> startupWMClass; 613 | if (boost::iequals(startupWMClass, app_id)) { 614 | icon_name = get_from_desktop(desktop, "Icon"); 615 | break; 616 | } 617 | } 618 | } 619 | } 620 | 621 | /** Case for absolute paths */ 622 | if ((icon_name.at(0) == '/') && exists(icon_name)) { 623 | icon_file_out << app_id + " " + icon_name << std::endl; 624 | continue; 625 | } 626 | 627 | std::vector icon_names; 628 | 629 | icon_names.push_back(icon_name); 630 | if (auto name = boost::to_lower_copy(icon_name); 631 | name != icon_name) { 632 | icon_names.push_back(name); 633 | } 634 | icon_names.push_back(app_id); 635 | if (auto name = boost::to_lower_copy(app_id); 636 | name != app_id) { 637 | icon_names.push_back(name); 638 | } 639 | 640 | std::vector icon_themes; 641 | 642 | /* Locations where the icon_theme.get_value() would be expected to be */ 643 | std::vector icon_dirs = { 644 | (std::string)getenv("HOME") + "/.local/share/icons/", 645 | "/usr/local/share/icons/", 646 | "/usr/share/icons/" 647 | }; 648 | 649 | std::vector default_icon_themes = { 650 | "/usr/share/icons/hicolor/", 651 | "/usr/share/icons/Adwaita/", 652 | "/usr/share/icons/breeze/" 653 | }; 654 | 655 | /* Check for the existance of the icon_theme.get_value() on all reasonable locations */ 656 | for (auto icon_dir : icon_dirs) { 657 | if (auto dir = icon_dir + (std::string)icon_theme.get_value(); 658 | std::count(default_icon_themes.begin(), default_icon_themes.end(), 659 | dir) == 0) { 660 | if (exists(dir)) { 661 | icon_themes.push_back(dir + "/"); 662 | } 663 | } 664 | } 665 | 666 | for (auto icon_theme : default_icon_themes) { 667 | if (exists(icon_theme)) { 668 | icon_themes.push_back(icon_theme); 669 | } 670 | } 671 | 672 | /** Look for the icons on every folder, on every resolution */ 673 | bool icon_found = false; 674 | for (auto path : icon_themes) { 675 | for (auto res : { 676 | "scalable/", "32x32/", "48x48/", "72x72/", "96x96/", 677 | "128x128/", "256x256/" 678 | }) { 679 | for (auto icon_name : icon_names) { 680 | for (auto e : { ".svg", ".png" }) { 681 | if (auto icon_path = path + res + "apps/" + icon_name + e; 682 | exists(icon_path)) { 683 | icon_found = true; 684 | icon_file_out << app_id + " " + icon_path << std::endl; 685 | break; 686 | } 687 | } 688 | } 689 | if (icon_found) { break; } 690 | } 691 | if (icon_found) { break; } 692 | } 693 | 694 | /** Absolute last resorts */ 695 | for (auto icon_name : icon_names) { 696 | for (auto e : { ".svg", ".png" }) { 697 | if (auto icon_path = "/usr/share/pixmaps/" + icon_name + e; 698 | exists(icon_path)) { 699 | icon_found = true; 700 | icon_file_out << app_id + " " + icon_path << std::endl; 701 | break; 702 | } 703 | if (icon_found) { break; } 704 | } 705 | if (icon_found) { break; } 706 | } 707 | 708 | 709 | if (!icon_found) { 710 | std::string icon_path = " /usr/local/share/firedecor/executable.svg"; 711 | icon_file_out << app_id + icon_path << std::endl; 712 | } 713 | } 714 | } 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /src/firedecor-theme.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "firedecor-buttons.hpp" 5 | 6 | namespace wf { 7 | namespace firedecor { 8 | 9 | struct color_set_t { 10 | color_t active, inactive; 11 | 12 | bool operator ==(const color_set_t& other) const { 13 | return (this->active == other.active) && (this->inactive == other.inactive); 14 | }; 15 | 16 | bool operator !=(const color_set_t& other) const { 17 | return !(this->active == other.active) || !(this->inactive == other.inactive); 18 | }; 19 | }; 20 | 21 | template 22 | struct matrix { 23 | T xx, xy, yx, yy; 24 | }; 25 | 26 | enum orientation_t { 27 | HORIZONTAL = 0, 28 | VERTICAL = 1 29 | }; 30 | 31 | enum edge_t { 32 | EDGE_TOP = 0, 33 | EDGE_LEFT = 1, 34 | EDGE_BOTTOM = 2, 35 | EDGE_RIGHT = 3 36 | }; 37 | 38 | /** 39 | * Checks if a file exists in storage 40 | * @param path The path of the file to find 41 | */ 42 | bool exists(std::string path); 43 | 44 | /** 45 | * Gets the real name of a file, dealing with capitalization, for example. 46 | * @param path The path to be corrected. 47 | */ 48 | std::string get_real_name(std::string path); 49 | 50 | /** 51 | * Gets a vector containing al the .desktop files in a specifi path. 52 | * @param path The path where the .desktop files will be searched. 53 | */ 54 | std::vector get_desktops(std::string path); 55 | 56 | /** 57 | * Gets a value from a .desktop file. 58 | * @param path The path to the .desktop file. 59 | * @var The variable to be returned. 60 | */ 61 | std::string get_from_desktop(std::string path, std::string var); 62 | 63 | template 64 | struct theme_option_t { 65 | public: 66 | theme_option_t(T value) : 67 | value{value} {} 68 | 69 | T get_value() const { 70 | return value; 71 | } 72 | 73 | private: 74 | T value; 75 | }; 76 | 77 | struct theme_options { 78 | public: 79 | theme_option_t font; 80 | theme_option_t font_size; 81 | theme_option_t active_title; 82 | theme_option_t inactive_title; 83 | theme_option_t max_title_size; 84 | 85 | theme_option_t border_size; 86 | theme_option_t active_border; 87 | theme_option_t inactive_border; 88 | theme_option_t corner_radius; 89 | 90 | theme_option_t outline_size; 91 | theme_option_t active_outline; 92 | theme_option_t inactive_outline; 93 | 94 | theme_option_t button_size; 95 | theme_option_t button_style; 96 | theme_option_t normal_min; 97 | theme_option_t hovered_min; 98 | theme_option_t normal_max; 99 | theme_option_t hovered_max; 100 | theme_option_t normal_close; 101 | theme_option_t hovered_close; 102 | theme_option_t inactive_buttons; 103 | 104 | theme_option_t icon_size; 105 | theme_option_t icon_theme; 106 | 107 | theme_option_t active_accent; 108 | theme_option_t inactive_accent; 109 | 110 | theme_option_t padding_size; 111 | theme_option_t layout; 112 | 113 | theme_option_t ignore_views; 114 | theme_option_t debug_mode; 115 | theme_option_t round_on; 116 | }; 117 | 118 | class decoration_theme_t : private theme_options { 119 | public: 120 | decoration_theme_t(theme_options extra_options); 121 | 122 | /** @return The theme's layout */ 123 | std::string get_layout() const; 124 | 125 | /* Size return functions */ 126 | /** @return The available border for resizing */ 127 | std::string get_border_size() const; 128 | /** @return The font size */ 129 | int get_font_size() const; 130 | /** @return The maximum text size, in pixels */ 131 | int get_max_title_size() const; 132 | /** @return The available outline for resizing */ 133 | int get_outline_size() const; 134 | /** @return The corner radius */ 135 | int get_corner_radius() const; 136 | /** @return The equal width and height of the button */ 137 | int get_button_size() const; 138 | /** @return The icon size */ 139 | int get_icon_size() const; 140 | /** @return The padding size */ 141 | int get_padding_size() const; 142 | 143 | /* Color return functions */ 144 | /** @return The active and inactive colors for the border */ 145 | color_set_t get_border_colors() const; 146 | /** @return The active and inactive colors for the outline */ 147 | color_set_t get_outline_colors() const; 148 | /** @return The active and inactive colors for the title */ 149 | color_set_t get_title_colors() const; 150 | /** @return The active and inactive colors for the title */ 151 | color_set_t get_accent_colors() const; 152 | 153 | /* Other return functions */ 154 | /** @return True if there is a title of said orientation in the layout */ 155 | bool has_title_orientation(orientation_t orientation) const; 156 | /** @return True if debug_mode is on */ 157 | bool get_debug_mode() const; 158 | /** @return Where corners should be drawn */ 159 | std::string get_round_on() const; 160 | 161 | /** 162 | * Get what the title size should be, given a text for the title, useful for 163 | * centered and right positioned layouts on an edge. 164 | */ 165 | wf::dimensions_t get_text_size(std::string title, int width) const; 166 | 167 | /** 168 | * Render the given text on a cairo_surface_t with the given size. 169 | * The caller is responsible for freeing the memory afterwards. 170 | */ 171 | cairo_surface_t *form_title(std::string text, wf::dimensions_t title_size, 172 | bool active, orientation_t orientation) const; 173 | 174 | /** 175 | * Render the corners for active and inactive windows. 176 | * @param active The activation state of the window. 177 | * @param r the radius of the corner. 178 | * @param scale The scale of the framebuffer. 179 | * @param m The matrix to transform the corner. 180 | * @param height The height of the corner, set by radius or the border size. 181 | */ 182 | cairo_surface_t *form_corner(bool active, int r, matrix m, 183 | int height) const; 184 | 185 | /** 186 | * Get the icon for the given button. 187 | * The caller is responsible for freeing the memory afterwards. 188 | * 189 | * @param button The button type. 190 | * @param state The button state. 191 | */ 192 | cairo_surface_t *form_button(button_type_t button, double hover, 193 | bool active, bool maximized) const; 194 | 195 | /** 196 | * Gets a cairo surface with an svg texture. 197 | * @param path The path to said the svg file, must contain .svg at the end. 198 | */ 199 | cairo_surface_t *surface_svg(std::string path, int size) const; 200 | 201 | /** 202 | * Gets a cairo surface with a png texture. 203 | * @param path The path to said the png file, must contain .png at the end. 204 | */ 205 | cairo_surface_t* surface_png(std::string path, int size) const; 206 | /** 207 | * Get the icon for the given application icon. 208 | * @param title The icon for the window. 209 | */ 210 | cairo_surface_t *form_icon(std::string app_id) const; 211 | }; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/firedecor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "firedecor-subsurface.hpp" 9 | 10 | namespace { 11 | struct wayfire_decoration_global_cleanup_t { 12 | wayfire_decoration_global_cleanup_t() = default; 13 | ~wayfire_decoration_global_cleanup_t() { 14 | for (auto view : wf::get_core().get_all_views()) { 15 | wf::firedecor::deinit_view(view); 16 | } 17 | } 18 | 19 | wayfire_decoration_global_cleanup_t(const wayfire_decoration_global_cleanup_t &) 20 | = delete; 21 | wayfire_decoration_global_cleanup_t(wayfire_decoration_global_cleanup_t &&) = 22 | delete; 23 | wayfire_decoration_global_cleanup_t& operator =( 24 | const wayfire_decoration_global_cleanup_t&) = delete; 25 | wayfire_decoration_global_cleanup_t& operator =( 26 | wayfire_decoration_global_cleanup_t&&) = delete; 27 | }; 28 | 29 | class wayfire_firedecor_t : 30 | public wf::singleton_plugin_t { 31 | 32 | wf::view_matcher_t ignore_views{"firedecor/ignore_views"}; 33 | wf::option_wrapper_t extra_themes{"firedecor/extra_themes"}; 34 | 35 | wf::signal_connection_t view_updated{ [=] (wf::signal_data_t *data) { 36 | update_view_decoration(get_signaled_view(data)); 37 | } 38 | }; 39 | 40 | wf::config::config_manager_t& config = wf::get_core().config; 41 | 42 | public: 43 | 44 | void init() override { 45 | grab_interface->name = "firedecor"; 46 | grab_interface->capabilities = wf::CAPABILITY_VIEW_DECORATOR; 47 | 48 | output->connect_signal("view-mapped", &view_updated); 49 | output->connect_signal("view-decoration-state-updated", &view_updated); 50 | for (auto& view : output->workspace->get_views_in_layer(wf::ALL_LAYERS)) { 51 | update_view_decoration(view); 52 | } 53 | } 54 | 55 | wf::wl_idle_call idle_deactivate; 56 | 57 | template 58 | T get_option(std::string theme, std::string option_name) { 59 | 60 | auto option = config.get_option(theme + "/" + option_name); 61 | if (option == nullptr || theme == "invalid") { 62 | return config.get_option("firedecor/" + option_name)->get_value(); 63 | } else { 64 | return wf::option_type::from_string(option->get_value()).value(); 65 | } 66 | } 67 | 68 | wf::firedecor::theme_options get_options(std::string theme) { 69 | wf::firedecor::theme_options options = { 70 | get_option(theme, "font"), 71 | get_option(theme, "font_size"), 72 | get_option(theme, "active_title"), 73 | get_option(theme, "inactive_title"), 74 | get_option(theme, "max_title_size"), 75 | 76 | get_option(theme, "border_size"), 77 | get_option(theme, "active_border"), 78 | get_option(theme, "inactive_border"), 79 | get_option(theme, "corner_radius"), 80 | 81 | get_option(theme, "outline_size"), 82 | get_option(theme, "active_outline"), 83 | get_option(theme, "inactive_outline"), 84 | 85 | get_option(theme, "button_size"), 86 | get_option(theme, "button_style"), 87 | get_option(theme, "normal_min"), 88 | get_option(theme, "hovered_min"), 89 | get_option(theme, "normal_max"), 90 | get_option(theme, "hovered_max"), 91 | get_option(theme, "normal_close"), 92 | get_option(theme, "hovered_close"), 93 | get_option(theme, "inactive_buttons"), 94 | 95 | get_option(theme, "icon_size"), 96 | get_option(theme, "icon_theme"), 97 | 98 | get_option(theme, "active_accent"), 99 | get_option(theme, "inactive_accent"), 100 | 101 | get_option(theme, "padding_size"), 102 | get_option(theme, "layout"), 103 | 104 | get_option(theme, "ignore_views"), 105 | get_option(theme, "debug_mode"), 106 | get_option(theme, "round_on") 107 | }; 108 | return options; 109 | } 110 | 111 | void update_view_decoration(wayfire_view view) { 112 | if (view->should_be_decorated() && !ignore_views.matches(view)) { 113 | if (output->activate_plugin(grab_interface)) { 114 | idle_deactivate.run_once([this] () { 115 | output->deactivate_plugin(grab_interface); 116 | }); 117 | std::stringstream themes{extra_themes.value()}; 118 | std::string theme; 119 | while (themes >> theme) { 120 | try { 121 | wf::view_matcher_t matcher{theme + "/uses_if"}; 122 | if (matcher.matches(view)) { 123 | wf::firedecor::init_view(view, get_options(theme)); 124 | return; 125 | } 126 | } catch (...) { 127 | } 128 | } 129 | wf::firedecor::init_view(view, get_options("invalid")); 130 | } 131 | } else { 132 | wf::firedecor::deinit_view(view); 133 | } 134 | } 135 | 136 | void fini() override { 137 | for (auto& view : output->workspace->get_views_in_layer(wf::ALL_LAYERS)) { 138 | wf::firedecor::deinit_view(view); 139 | } 140 | } 141 | }; 142 | } 143 | 144 | DECLARE_WAYFIRE_PLUGIN(wayfire_firedecor_t); 145 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | firedecor = shared_module( 2 | 'firedecor', [ 'firedecor.cpp', 'firedecor-subsurface.cpp', 3 | 'firedecor-buttons.cpp', 'firedecor-layout.cpp', 4 | 'firedecor-theme.cpp' ], 5 | dependencies: [ wf_config, wlroots, rsvg , pixman, glib, gdk_pixbuf, cairo, pango, 6 | pangocairo], 7 | install: true, install_dir: wayfire.get_variable(pkgconfig: 'plugindir')) 8 | --------------------------------------------------------------------------------