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