├── .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 | 
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 | 
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 | 
249 |
250 |
251 |
252 | ## Screenshots
253 | Left side decoration:
254 | 
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 | 
263 |
264 | ?̷̛͈͐̃̈́̀̇́̑͛̓͋̌?̴̡̘̯͙̩̂̑̅̆̕?̶͍̣́̅̐̔͂̅͐̿͌͝:
265 | 
266 | (very laggy)
267 |
268 | ### Some ideas for accents
269 | - Selective accents:
270 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
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