Cleanup: Move area geometry management into an own file
[blender.git] / source / blender / editors / screen / screen_geometry.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * ***** END GPL LICENSE BLOCK *****
19  */
20
21 /** \file blender/editors/screen/screen_geometry.c
22  *  \ingroup edscr
23  *  \brief Functions for screen vertices and edges
24  *
25  * Screen geometry refers to the vertices (ScrVert) and edges (ScrEdge) through
26  * which the flexible screen-layout system of Blender is established.
27  */
28
29 #include "BLI_listbase.h"
30 #include "BLI_math.h"
31 #include "BLI_rect.h"
32
33 #include "BKE_screen.h"
34
35 #include "DNA_screen_types.h"
36 #include "DNA_windowmanager_types.h"
37
38 #include "ED_screen.h"
39
40 #include "MEM_guardedalloc.h"
41
42 #include "WM_api.h"
43
44 #include "screen_intern.h"
45
46
47 int screen_geom_area_height(const ScrArea *area)
48 {
49         return area->v2->vec.y - area->v1->vec.y + 1;
50 }
51 int screen_geom_area_width(const ScrArea *area)
52 {
53         return area->v4->vec.x - area->v1->vec.x + 1;
54 }
55
56 ScrVert *screen_geom_vertex_add_ex(ScrAreaMap *area_map, short x, short y)
57 {
58         ScrVert *sv = MEM_callocN(sizeof(ScrVert), "addscrvert");
59         sv->vec.x = x;
60         sv->vec.y = y;
61
62         BLI_addtail(&area_map->vertbase, sv);
63         return sv;
64 }
65 ScrVert *screen_geom_vertex_add(bScreen *sc, short x, short y)
66 {
67         return screen_geom_vertex_add_ex(AREAMAP_FROM_SCREEN(sc), x, y);
68 }
69
70 ScrEdge *screen_geom_edge_add_ex(ScrAreaMap *area_map, ScrVert *v1, ScrVert *v2)
71 {
72         ScrEdge *se = MEM_callocN(sizeof(ScrEdge), "addscredge");
73
74         BKE_screen_sort_scrvert(&v1, &v2);
75         se->v1 = v1;
76         se->v2 = v2;
77
78         BLI_addtail(&area_map->edgebase, se);
79         return se;
80 }
81 ScrEdge *screen_geom_edge_add(bScreen *sc, ScrVert *v1, ScrVert *v2)
82 {
83         return screen_geom_edge_add_ex(AREAMAP_FROM_SCREEN(sc), v1, v2);
84 }
85
86 bool screen_geom_edge_is_horizontal(ScrEdge *se)
87 {
88         return (se->v1->vec.y == se->v2->vec.y);
89 }
90
91 /**
92  * \param bounds_rect: Either window or screen bounds. Used to exclude edges along window/screen edges.
93  */
94 ScrEdge *screen_geom_area_map_find_active_scredge(
95         const ScrAreaMap *area_map,
96         const rcti *bounds_rect,
97         const int mx, const int my)
98 {
99         int safety = U.widget_unit / 10;
100
101         CLAMP_MIN(safety, 2);
102
103         for (ScrEdge *se = area_map->edgebase.first; se; se = se->next) {
104                 if (screen_geom_edge_is_horizontal(se)) {
105                         if ((se->v1->vec.y > bounds_rect->ymin) && (se->v1->vec.y < (bounds_rect->ymax - 1))) {
106                                 short min, max;
107                                 min = MIN2(se->v1->vec.x, se->v2->vec.x);
108                                 max = MAX2(se->v1->vec.x, se->v2->vec.x);
109
110                                 if (abs(my - se->v1->vec.y) <= safety && mx >= min && mx <= max)
111                                         return se;
112                         }
113                 }
114                 else {
115                         if ((se->v1->vec.x > bounds_rect->xmin) && (se->v1->vec.x < (bounds_rect->xmax - 1))) {
116                                 short min, max;
117                                 min = MIN2(se->v1->vec.y, se->v2->vec.y);
118                                 max = MAX2(se->v1->vec.y, se->v2->vec.y);
119
120                                 if (abs(mx - se->v1->vec.x) <= safety && my >= min && my <= max)
121                                         return se;
122                         }
123                 }
124         }
125
126         return NULL;
127 }
128
129 /* need win size to make sure not to include edges along screen edge */
130 ScrEdge *screen_geom_find_active_scredge(
131         const wmWindow *win, const bScreen *screen,
132         const int mx, const int my)
133 {
134         /* Use layout size (screen excluding global areas) for screen-layout area edges */
135         rcti screen_rect;
136         ScrEdge *se;
137
138         WM_window_screen_rect_calc(win, &screen_rect);
139         se = screen_geom_area_map_find_active_scredge(AREAMAP_FROM_SCREEN(screen), &screen_rect, mx, my);
140
141         if (!se) {
142                 /* Use entire window size (screen including global areas) for global area edges */
143                 rcti win_rect;
144                 WM_window_rect_calc(win, &win_rect);
145                 se = screen_geom_area_map_find_active_scredge(&win->global_areas, &win_rect, mx, my);
146         }
147         return se;
148 }
149
150 /**
151  * \brief Main screen-layout calculation function.
152  *
153  * * Scale areas nicely on window size and DPI changes.
154  * * Ensure areas have a minimum height.
155  * * Correctly set global areas to their fixed height.
156  */
157 void screen_geom_vertices_scale(const wmWindow *win, bScreen *sc)
158 {
159         /* clamp Y size of header sized areas when expanding windows
160          * avoids annoying empty space around file menu */
161 #define USE_HEADER_SIZE_CLAMP
162
163         rcti window_rect, screen_rect;
164
165         WM_window_rect_calc(win, &window_rect);
166         WM_window_screen_rect_calc(win, &screen_rect);
167
168         const int headery_init = ED_area_headersize();
169         const int screen_size_x = BLI_rcti_size_x(&screen_rect);
170         const int screen_size_y = BLI_rcti_size_y(&screen_rect);
171         ScrVert *sv = NULL;
172         ScrArea *sa;
173         int screen_size_x_prev, screen_size_y_prev;
174         float min[2], max[2];
175
176         /* calculate size */
177         min[0] = min[1] = 20000.0f;
178         max[0] = max[1] = 0.0f;
179
180         for (sv = sc->vertbase.first; sv; sv = sv->next) {
181                 const float fv[2] = {(float)sv->vec.x, (float)sv->vec.y};
182                 minmax_v2v2_v2(min, max, fv);
183         }
184
185         screen_size_x_prev = (max[0] - min[0]) + 1;
186         screen_size_y_prev = (max[1] - min[1]) + 1;
187
188
189 #ifdef USE_HEADER_SIZE_CLAMP
190 #define TEMP_BOTTOM 1
191 #define TEMP_TOP 2
192
193         /* if the window's Y axis grows, clamp header sized areas */
194         if (screen_size_y_prev < screen_size_y) {  /* growing? */
195                 const int headery_margin_max = headery_init + 5;
196                 for (sa = sc->areabase.first; sa; sa = sa->next) {
197                         ARegion *ar = BKE_area_find_region_type(sa, RGN_TYPE_HEADER);
198                         sa->temp = 0;
199
200                         if (ar && !(ar->flag & RGN_FLAG_HIDDEN)) {
201                                 if (sa->v2->vec.y == max[1]) {
202                                         if (screen_geom_area_height(sa) < headery_margin_max) {
203                                                 sa->temp = TEMP_TOP;
204                                         }
205                                 }
206                                 else if (sa->v1->vec.y == min[1]) {
207                                         if (screen_geom_area_height(sa) < headery_margin_max) {
208                                                 sa->temp = TEMP_BOTTOM;
209                                         }
210                                 }
211                         }
212                 }
213         }
214 #endif
215
216
217         if (screen_size_x_prev != screen_size_x || screen_size_y_prev != screen_size_y) {
218                 const float facx = ((float)screen_size_x - 1) / ((float)screen_size_x_prev - 1);
219                 const float facy = ((float)screen_size_y - 1) / ((float)screen_size_y_prev - 1);
220
221                 /* make sure it fits! */
222                 for (sv = sc->vertbase.first; sv; sv = sv->next) {
223                         sv->vec.x = screen_rect.xmin + round_fl_to_short((sv->vec.x - min[0]) * facx);
224                         CLAMP(sv->vec.x, screen_rect.xmin, screen_rect.xmax - 1);
225
226                         sv->vec.y = screen_rect.ymin + round_fl_to_short((sv->vec.y - min[1]) * facy);
227                         CLAMP(sv->vec.y, screen_rect.ymin, screen_rect.ymax - 1);
228                 }
229         }
230
231
232 #ifdef USE_HEADER_SIZE_CLAMP
233         if (screen_size_y_prev < screen_size_y) {  /* growing? */
234                 for (sa = sc->areabase.first; sa; sa = sa->next) {
235                         ScrEdge *se = NULL;
236
237                         if (sa->temp == 0)
238                                 continue;
239
240                         if (sa->v1 == sa->v2)
241                                 continue;
242
243                         /* adjust headery if verts are along the edge of window */
244                         if (sa->temp == TEMP_TOP) {
245                                 /* lower edge */
246                                 const int yval = sa->v2->vec.y - headery_init;
247                                 se = BKE_screen_find_edge(sc, sa->v4, sa->v1);
248                                 if (se != NULL) {
249                                         screen_geom_select_connected_edge(win, se);
250                                 }
251                                 for (sv = sc->vertbase.first; sv; sv = sv->next) {
252                                         if (sv != sa->v2 && sv != sa->v3) {
253                                                 if (sv->flag) {
254                                                         sv->vec.y = yval;
255                                                 }
256                                         }
257                                 }
258                         }
259                         else {
260                                 /* upper edge */
261                                 const int yval = sa->v1->vec.y + headery_init;
262                                 se = BKE_screen_find_edge(sc, sa->v2, sa->v3);
263                                 if (se != NULL) {
264                                         screen_geom_select_connected_edge(win, se);
265                                 }
266                                 for (sv = sc->vertbase.first; sv; sv = sv->next) {
267                                         if (sv != sa->v1 && sv != sa->v4) {
268                                                 if (sv->flag) {
269                                                         sv->vec.y = yval;
270                                                 }
271                                         }
272                                 }
273                         }
274                 }
275         }
276
277 #undef USE_HEADER_SIZE_CLAMP
278 #undef TEMP_BOTTOM
279 #undef TEMP_TOP
280 #endif
281
282
283         /* test for collapsed areas. This could happen in some blender version... */
284         /* ton: removed option now, it needs Context... */
285
286         /* make each window at least ED_area_headersize() high */
287         for (sa = sc->areabase.first; sa; sa = sa->next) {
288                 int headery = headery_init;
289
290                 /* adjust headery if verts are along the edge of window */
291                 if (sa->v1->vec.y > window_rect.ymin)
292                         headery += U.pixelsize;
293                 if (sa->v2->vec.y < (window_rect.ymax - 1))
294                         headery += U.pixelsize;
295
296                 if (screen_geom_area_height(sa) < headery) {
297                         /* lower edge */
298                         ScrEdge *se = BKE_screen_find_edge(sc, sa->v4, sa->v1);
299                         if (se && sa->v1 != sa->v2) {
300                                 const int yval = sa->v2->vec.y - headery + 1;
301
302                                 screen_geom_select_connected_edge(win, se);
303
304                                 /* all selected vertices get the right offset */
305                                 for (sv = sc->vertbase.first; sv; sv = sv->next) {
306                                         /* if is a collapsed area */
307                                         if (sv != sa->v2 && sv != sa->v3) {
308                                                 if (sv->flag) {
309                                                         sv->vec.y = yval;
310                                                 }
311                                         }
312                                 }
313                         }
314                 }
315         }
316
317         /* Global areas have a fixed size that only changes with the DPI. Here we ensure that exactly this size is set. */
318         for (ScrArea *area = win->global_areas.areabase.first; area; area = area->next) {
319                 if (area->global->flag & GLOBAL_AREA_IS_HIDDEN) {
320                         continue;
321                 }
322
323                 int height = ED_area_global_size_y(area) - 1;
324
325                 if (area->v1->vec.y > window_rect.ymin) {
326                         height += U.pixelsize;
327                 }
328                 if (area->v2->vec.y < (window_rect.ymax - 1)) {
329                         height += U.pixelsize;
330                 }
331
332                 /* width */
333                 area->v1->vec.x = area->v2->vec.x = window_rect.xmin;
334                 area->v3->vec.x = area->v4->vec.x = window_rect.xmax - 1;
335                 /* height */
336                 area->v1->vec.y = area->v4->vec.y = window_rect.ymin;
337                 area->v2->vec.y = area->v3->vec.y = window_rect.ymax - 1;
338
339                 switch (area->global->align) {
340                         case GLOBAL_AREA_ALIGN_TOP:
341                                 area->v1->vec.y = area->v4->vec.y = area->v2->vec.y - height;
342                                 break;
343                         case GLOBAL_AREA_ALIGN_BOTTOM:
344                                 area->v2->vec.y = area->v3->vec.y = area->v1->vec.y + height;
345                                 break;
346                 }
347         }
348 }
349
350 /**
351  * \return 0 if no split is possible, otherwise the screen-coordinate at which to split.
352  */
353 short screen_geom_find_area_split_point(const ScrArea *sa, const rcti *window_rect, char dir, float fac)
354 {
355         short x, y;
356         const int cur_area_width = screen_geom_area_width(sa);
357         const int cur_area_height = screen_geom_area_height(sa);
358         const short area_min_x = AREAMINX;
359         const short area_min_y = ED_area_headersize();
360         int area_min;
361
362         // area big enough?
363         if ((dir == 'v') && (cur_area_width <= 2 * area_min_x)) {
364                 return 0;
365         }
366         if ((dir == 'h') && (cur_area_height <= 2 * area_min_y)) {
367                 return 0;
368         }
369
370         // to be sure
371         CLAMP(fac, 0.0f, 1.0f);
372
373         if (dir == 'h') {
374                 y = sa->v1->vec.y + round_fl_to_short(fac * cur_area_height);
375
376                 area_min = area_min_y;
377
378                 if (sa->v1->vec.y > window_rect->ymin) {
379                         area_min += U.pixelsize;
380                 }
381                 if (sa->v2->vec.y < (window_rect->ymax - 1)) {
382                         area_min += U.pixelsize;
383                 }
384
385                 if (y - sa->v1->vec.y < area_min) {
386                         y = sa->v1->vec.y + area_min;
387                 }
388                 else if (sa->v2->vec.y - y < area_min) {
389                         y = sa->v2->vec.y - area_min;
390                 }
391
392                 return y;
393         }
394         else {
395                 x = sa->v1->vec.x + round_fl_to_short(fac * cur_area_width);
396
397                 area_min = area_min_x;
398
399                 if (sa->v1->vec.x > window_rect->xmin) {
400                         area_min += U.pixelsize;
401                 }
402                 if (sa->v4->vec.x < (window_rect->xmax - 1)) {
403                         area_min += U.pixelsize;
404                 }
405
406                 if (x - sa->v1->vec.x < area_min) {
407                         x = sa->v1->vec.x + area_min;
408                 }
409                 else if (sa->v4->vec.x - x < area_min) {
410                         x = sa->v4->vec.x - area_min;
411                 }
412
413                 return x;
414         }
415 }
416
417 /**
418  * Select all edges that are directly or indirectly connected to \a edge.
419  */
420 void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge)
421 {
422         bScreen *sc = WM_window_get_active_screen(win);
423         bool oneselected = true;
424         char dir;
425
426         /* select connected, only in the right direction */
427         /* 'dir' is the direction of EDGE */
428
429         if (edge->v1->vec.x == edge->v2->vec.x) {
430                 dir = 'v';
431         }
432         else {
433                 dir = 'h';
434         }
435
436         ED_screen_verts_iter(win, sc, sv) {
437                 sv->flag = 0;
438         }
439
440         edge->v1->flag = 1;
441         edge->v2->flag = 1;
442
443         while (oneselected) {
444                 oneselected = false;
445                 for (ScrEdge *se = sc->edgebase.first; se; se = se->next) {
446                         if (se->v1->flag + se->v2->flag == 1) {
447                                 if (dir == 'h') {
448                                         if (se->v1->vec.y == se->v2->vec.y) {
449                                                 se->v1->flag = se->v2->flag = 1;
450                                                 oneselected = true;
451                                         }
452                                 }
453                                 if (dir == 'v') {
454                                         if (se->v1->vec.x == se->v2->vec.x) {
455                                                 se->v1->flag = se->v2->flag = 1;
456                                                 oneselected = true;
457                                         }
458                                 }
459                         }
460                 }
461         }
462 }