UI: move top-bar into the spaces header
[blender.git] / source / blender / editors / screen / area.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  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup edscr
22  */
23
24 #include <string.h>
25 #include <stdio.h>
26
27 #include "MEM_guardedalloc.h"
28
29 #include "DNA_userdef_types.h"
30
31 #include "BLI_blenlib.h"
32 #include "BLI_math.h"
33 #include "BLI_utildefines.h"
34 #include "BLI_linklist_stack.h"
35
36 #include "BKE_context.h"
37 #include "BKE_global.h"
38 #include "BKE_image.h"
39 #include "BKE_screen.h"
40 #include "BKE_workspace.h"
41
42 #include "RNA_access.h"
43 #include "RNA_types.h"
44
45 #include "WM_api.h"
46 #include "WM_types.h"
47 #include "WM_message.h"
48 #include "WM_toolsystem.h"
49
50 #include "ED_screen.h"
51 #include "ED_screen_types.h"
52 #include "ED_space_api.h"
53
54 #include "GPU_immediate.h"
55 #include "GPU_immediate_util.h"
56 #include "GPU_matrix.h"
57 #include "GPU_draw.h"
58 #include "GPU_state.h"
59 #include "GPU_framebuffer.h"
60
61 #include "BLF_api.h"
62
63 #include "IMB_imbuf.h"
64 #include "IMB_imbuf_types.h"
65 #include "IMB_metadata.h"
66
67 #include "UI_interface.h"
68 #include "UI_interface_icons.h"
69 #include "UI_resources.h"
70 #include "UI_view2d.h"
71
72 #include "screen_intern.h"
73
74 enum RegionEmbossSide {
75   REGION_EMBOSS_LEFT = (1 << 0),
76   REGION_EMBOSS_TOP = (1 << 1),
77   REGION_EMBOSS_BOTTOM = (1 << 2),
78   REGION_EMBOSS_RIGHT = (1 << 3),
79   REGION_EMBOSS_ALL = REGION_EMBOSS_LEFT | REGION_EMBOSS_TOP | REGION_EMBOSS_RIGHT |
80                       REGION_EMBOSS_BOTTOM,
81 };
82
83 /* general area and region code */
84
85 static void region_draw_emboss(const ARegion *ar, const rcti *scirct, int sides)
86 {
87   rcti rect;
88
89   /* translate scissor rect to region space */
90   rect.xmin = scirct->xmin - ar->winrct.xmin;
91   rect.ymin = scirct->ymin - ar->winrct.ymin;
92   rect.xmax = scirct->xmax - ar->winrct.xmin;
93   rect.ymax = scirct->ymax - ar->winrct.ymin;
94
95   /* set transp line */
96   GPU_blend(true);
97   GPU_blend_set_func_separate(
98       GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
99
100   float color[4] = {0.0f, 0.0f, 0.0f, 0.25f};
101   UI_GetThemeColor3fv(TH_EDITOR_OUTLINE, color);
102
103   GPUVertFormat *format = immVertexFormat();
104   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
105   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
106   immUniformColor4fv(color);
107
108   immBeginAtMost(GPU_PRIM_LINES, 8);
109
110   /* right */
111   if (sides & REGION_EMBOSS_RIGHT) {
112     immVertex2f(pos, rect.xmax, rect.ymax);
113     immVertex2f(pos, rect.xmax, rect.ymin);
114   }
115
116   /* bottom */
117   if (sides & REGION_EMBOSS_BOTTOM) {
118     immVertex2f(pos, rect.xmax, rect.ymin);
119     immVertex2f(pos, rect.xmin, rect.ymin);
120   }
121
122   /* left */
123   if (sides & REGION_EMBOSS_LEFT) {
124     immVertex2f(pos, rect.xmin, rect.ymin);
125     immVertex2f(pos, rect.xmin, rect.ymax);
126   }
127
128   /* top */
129   if (sides & REGION_EMBOSS_TOP) {
130     immVertex2f(pos, rect.xmin, rect.ymax);
131     immVertex2f(pos, rect.xmax, rect.ymax);
132   }
133
134   immEnd();
135   immUnbindProgram();
136
137   GPU_blend(false);
138   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
139 }
140
141 void ED_region_pixelspace(ARegion *ar)
142 {
143   wmOrtho2_region_pixelspace(ar);
144   GPU_matrix_identity_set();
145 }
146
147 /* only exported for WM */
148 void ED_region_do_listen(
149     wmWindow *win, ScrArea *sa, ARegion *ar, wmNotifier *note, const Scene *scene)
150 {
151   /* generic notes first */
152   switch (note->category) {
153     case NC_WM:
154       if (note->data == ND_FILEREAD)
155         ED_region_tag_redraw(ar);
156       break;
157     case NC_WINDOW:
158       ED_region_tag_redraw(ar);
159       break;
160   }
161
162   if (ar->type && ar->type->listener)
163     ar->type->listener(win, sa, ar, note, scene);
164 }
165
166 /* only exported for WM */
167 void ED_area_do_listen(wmWindow *win, ScrArea *sa, wmNotifier *note, Scene *scene)
168 {
169   /* no generic notes? */
170   if (sa->type && sa->type->listener) {
171     sa->type->listener(win, sa, note, scene);
172   }
173 }
174
175 /* only exported for WM */
176 void ED_area_do_refresh(bContext *C, ScrArea *sa)
177 {
178   /* no generic notes? */
179   if (sa->type && sa->type->refresh) {
180     sa->type->refresh(C, sa);
181   }
182   sa->do_refresh = false;
183 }
184
185 /**
186  * \brief Corner widget use for quitting fullscreen.
187  */
188 static void area_draw_azone_fullscreen(short x1, short y1, short x2, short y2, float alpha)
189 {
190   int x = x2 - ((float)x2 - x1) * 0.5f / UI_DPI_FAC;
191   int y = y2 - ((float)y2 - y1) * 0.5f / UI_DPI_FAC;
192
193   /* adjust the icon distance from the corner */
194   x += 36.0f / UI_DPI_FAC;
195   y += 36.0f / UI_DPI_FAC;
196
197   /* draws from the left bottom corner of the icon */
198   x -= UI_DPI_ICON_SIZE;
199   y -= UI_DPI_ICON_SIZE;
200
201   alpha = min_ff(alpha, 0.75f);
202
203   UI_icon_draw_aspect(x, y, ICON_FULLSCREEN_EXIT, 0.7f / UI_DPI_FAC, alpha, NULL);
204
205   /* debug drawing :
206    * The click_rect is the same as defined in fullscreen_click_rcti_init
207    * Keep them both in sync */
208
209   if (G.debug_value == 101) {
210     rcti click_rect;
211     float icon_size = UI_DPI_ICON_SIZE + 7 * UI_DPI_FAC;
212
213     BLI_rcti_init(&click_rect, x, x + icon_size, y, y + icon_size);
214
215     GPUVertFormat *format = immVertexFormat();
216     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
217
218     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
219
220     immUniformColor4f(1.0f, 0.0f, 0.0f, alpha);
221     imm_draw_box_wire_2d(pos, click_rect.xmin, click_rect.ymin, click_rect.xmax, click_rect.ymax);
222
223     immUniformColor4f(0.0f, 1.0f, 1.0f, alpha);
224     immBegin(GPU_PRIM_LINES, 4);
225     immVertex2f(pos, click_rect.xmin, click_rect.ymin);
226     immVertex2f(pos, click_rect.xmax, click_rect.ymax);
227     immVertex2f(pos, click_rect.xmin, click_rect.ymax);
228     immVertex2f(pos, click_rect.xmax, click_rect.ymin);
229     immEnd();
230
231     immUnbindProgram();
232   }
233 }
234
235 /**
236  * \brief Corner widgets use for dragging and splitting the view.
237  */
238 static void area_draw_azone(short UNUSED(x1), short UNUSED(y1), short UNUSED(x2), short UNUSED(y2))
239 {
240   /* No drawing needed since all corners are action zone, and visually distinguishable. */
241 }
242
243 /**
244  * \brief Edge widgets to show hidden panels such as the toolbar and headers.
245  */
246 static void draw_azone_arrow(float x1, float y1, float x2, float y2, AZEdge edge)
247 {
248   const float size = 0.2f * U.widget_unit;
249   const float l = 1.0f;  /* arrow length */
250   const float s = 0.25f; /* arrow thickness */
251   const float hl = l / 2.0f;
252   const float points[6][2] = {
253       {0, -hl}, {l, hl}, {l - s, hl + s}, {0, s + s - hl}, {s - l, hl + s}, {-l, hl}};
254   const float center[2] = {(x1 + x2) / 2, (y1 + y2) / 2};
255
256   int axis;
257   int sign;
258   switch (edge) {
259     case AE_BOTTOM_TO_TOPLEFT:
260       axis = 0;
261       sign = 1;
262       break;
263     case AE_TOP_TO_BOTTOMRIGHT:
264       axis = 0;
265       sign = -1;
266       break;
267     case AE_LEFT_TO_TOPRIGHT:
268       axis = 1;
269       sign = 1;
270       break;
271     case AE_RIGHT_TO_TOPLEFT:
272       axis = 1;
273       sign = -1;
274       break;
275     default:
276       BLI_assert(0);
277       return;
278   }
279
280   GPUVertFormat *format = immVertexFormat();
281   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
282
283   GPU_blend(true);
284   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
285   immUniformColor4f(0.8f, 0.8f, 0.8f, 0.4f);
286
287   immBegin(GPU_PRIM_TRI_FAN, 6);
288   for (int i = 0; i < 6; i++) {
289     if (axis == 0) {
290       immVertex2f(pos, center[0] + points[i][0] * size, center[1] + points[i][1] * sign * size);
291     }
292     else {
293       immVertex2f(pos, center[0] + points[i][1] * sign * size, center[1] + points[i][0] * size);
294     }
295   }
296   immEnd();
297
298   immUnbindProgram();
299   GPU_blend(false);
300 }
301
302 static void region_draw_azone_tab_arrow(AZone *az)
303 {
304   GPU_blend(true);
305
306   /* add code to draw region hidden as 'too small' */
307   switch (az->edge) {
308     case AE_TOP_TO_BOTTOMRIGHT:
309       UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_TOP_RIGHT);
310       break;
311     case AE_BOTTOM_TO_TOPLEFT:
312       UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT);
313       break;
314     case AE_LEFT_TO_TOPRIGHT:
315       UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT);
316       break;
317     case AE_RIGHT_TO_TOPLEFT:
318       UI_draw_roundbox_corner_set(UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT);
319       break;
320   }
321
322   float color[4] = {0.05f, 0.05f, 0.05f, 0.4f};
323   UI_draw_roundbox_aa(
324       true, (float)az->x1, (float)az->y1, (float)az->x2, (float)az->y2, 4.0f, color);
325
326   draw_azone_arrow((float)az->x1, (float)az->y1, (float)az->x2, (float)az->y2, az->edge);
327 }
328
329 static void area_azone_tag_update(ScrArea *sa)
330 {
331   sa->flag |= AREA_FLAG_ACTIONZONES_UPDATE;
332 }
333
334 static void region_draw_azones(ScrArea *sa, ARegion *ar)
335 {
336   AZone *az;
337
338   if (!sa)
339     return;
340
341   GPU_line_width(1.0f);
342   GPU_blend(true);
343   GPU_blend_set_func_separate(
344       GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
345
346   GPU_matrix_push();
347   GPU_matrix_translate_2f(-ar->winrct.xmin, -ar->winrct.ymin);
348
349   for (az = sa->actionzones.first; az; az = az->next) {
350     /* test if action zone is over this region */
351     rcti azrct;
352     BLI_rcti_init(&azrct, az->x1, az->x2, az->y1, az->y2);
353
354     if (BLI_rcti_isect(&ar->drawrct, &azrct, NULL)) {
355       if (az->type == AZONE_AREA) {
356         area_draw_azone(az->x1, az->y1, az->x2, az->y2);
357       }
358       else if (az->type == AZONE_REGION) {
359         if (az->ar) {
360           /* only display tab or icons when the region is hidden */
361           if (az->ar->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) {
362             region_draw_azone_tab_arrow(az);
363           }
364         }
365       }
366       else if (az->type == AZONE_FULLSCREEN) {
367         area_draw_azone_fullscreen(az->x1, az->y1, az->x2, az->y2, az->alpha);
368       }
369     }
370     if (!IS_EQF(az->alpha, 0.0f) && ELEM(az->type, AZONE_FULLSCREEN, AZONE_REGION_SCROLL)) {
371       area_azone_tag_update(sa);
372     }
373   }
374
375   GPU_matrix_pop();
376
377   GPU_blend(false);
378 }
379
380 static void region_draw_status_text(ScrArea *sa, ARegion *ar)
381 {
382   bool overlap = ED_region_is_overlap(sa->spacetype, ar->regiontype);
383
384   if (overlap) {
385     GPU_clear_color(0.0, 0.0, 0.0, 0.0);
386     glClear(GL_COLOR_BUFFER_BIT);
387   }
388   else {
389     UI_ThemeClearColor(TH_HEADER);
390     glClear(GL_COLOR_BUFFER_BIT);
391   }
392
393   int fontid = BLF_set_default();
394
395   const float width = BLF_width(fontid, ar->headerstr, BLF_DRAW_STR_DUMMY_MAX);
396   const float x = UI_UNIT_X;
397   const float y = 0.4f * UI_UNIT_Y;
398
399   if (overlap) {
400     const float pad = 2.0f * UI_DPI_FAC;
401     const float x1 = x - (UI_UNIT_X - pad);
402     const float x2 = x + width + (UI_UNIT_X - pad);
403     const float y1 = pad;
404     const float y2 = ar->winy - pad;
405
406     GPU_blend_set_func_separate(
407         GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
408
409     float color[4] = {0.0f, 0.0f, 0.0f, 0.5f};
410     UI_GetThemeColor3fv(TH_BACK, color);
411     UI_draw_roundbox_corner_set(UI_CNR_ALL);
412     UI_draw_roundbox_aa(true, x1, y1, x2, y2, 4.0f, color);
413
414     UI_FontThemeColor(fontid, TH_TEXT);
415   }
416   else {
417     UI_FontThemeColor(fontid, TH_TEXT);
418   }
419
420   BLF_position(fontid, x, y, 0.0f);
421   BLF_draw(fontid, ar->headerstr, BLF_DRAW_STR_DUMMY_MAX);
422 }
423
424 void ED_region_do_msg_notify_tag_redraw(
425     /* Follow wmMsgNotifyFn spec */
426     bContext *UNUSED(C),
427     wmMsgSubscribeKey *UNUSED(msg_key),
428     wmMsgSubscribeValue *msg_val)
429 {
430   ARegion *ar = msg_val->owner;
431   ED_region_tag_redraw(ar);
432
433   /* This avoids _many_ situations where header/properties control display settings.
434    * the common case is space properties in the header */
435   if (ELEM(ar->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_UI)) {
436     while (ar && ar->prev) {
437       ar = ar->prev;
438     }
439     for (; ar; ar = ar->next) {
440       if (ELEM(ar->regiontype, RGN_TYPE_WINDOW, RGN_TYPE_CHANNELS)) {
441         ED_region_tag_redraw(ar);
442       }
443     }
444   }
445 }
446 /**
447  * Use #ED_region_do_msg_notify_tag_redraw where possible, this draws too much typically.
448  */
449 void ED_area_do_msg_notify_tag_redraw(
450     /* Follow wmMsgNotifyFn spec */
451     bContext *UNUSED(C),
452     wmMsgSubscribeKey *UNUSED(msg_key),
453     wmMsgSubscribeValue *msg_val)
454 {
455   ScrArea *sa = msg_val->owner;
456   ED_area_tag_redraw(sa);
457 }
458 void ED_area_do_msg_notify_tag_refresh(
459     /* Follow wmMsgNotifyFn spec */
460     bContext *UNUSED(C),
461     wmMsgSubscribeKey *UNUSED(msg_key),
462     wmMsgSubscribeValue *msg_val)
463 {
464   ScrArea *sa = msg_val->user_data;
465   ED_area_tag_refresh(sa);
466 }
467
468 void ED_area_do_mgs_subscribe_for_tool_header(
469     /* Follow ARegionType.message_subscribe */
470     const struct bContext *UNUSED(C),
471     struct WorkSpace *workspace,
472     struct Scene *UNUSED(scene),
473     struct bScreen *UNUSED(screen),
474     struct ScrArea *sa,
475     struct ARegion *UNUSED(ar),
476     struct wmMsgBus *mbus)
477 {
478   /* TODO(campbell): investigate why ED_region_do_msg_notify_tag_redraw doesn't work here. */
479   wmMsgSubscribeValue msg_sub_value_region_tag_redraw = {
480       .owner = sa,
481       .user_data = sa,
482       .notify = ED_area_do_msg_notify_tag_redraw,
483   };
484   WM_msg_subscribe_rna_prop(
485       mbus, &workspace->id, workspace, WorkSpace, tools, &msg_sub_value_region_tag_redraw);
486 }
487
488 /**
489  * Although there's no general support for minimizing areas, the status-bar can
490  * be snapped to be only a few pixels high. A few pixels rather than 0 so it
491  * can be un-minimized again. We consider it pseudo-minimized and don't draw
492  * it then.
493  */
494 static bool area_is_pseudo_minimized(const ScrArea *area)
495 {
496   return (area->winx < 3) || (area->winy < 3);
497 }
498
499 /* only exported for WM */
500 void ED_region_do_layout(bContext *C, ARegion *ar)
501 {
502   /* This is optional, only needed for dynamically sized regions. */
503   ScrArea *sa = CTX_wm_area(C);
504   ARegionType *at = ar->type;
505
506   if (!at->layout) {
507     return;
508   }
509
510   if (at->do_lock || (sa && area_is_pseudo_minimized(sa))) {
511     return;
512   }
513
514   ar->do_draw |= RGN_DRAWING;
515
516   UI_SetTheme(sa ? sa->spacetype : 0, at->regionid);
517   at->layout(C, ar);
518 }
519
520 /* only exported for WM */
521 void ED_region_do_draw(bContext *C, ARegion *ar)
522 {
523   wmWindow *win = CTX_wm_window(C);
524   ScrArea *sa = CTX_wm_area(C);
525   ARegionType *at = ar->type;
526
527   /* see BKE_spacedata_draw_locks() */
528   if (at->do_lock)
529     return;
530
531   ar->do_draw |= RGN_DRAWING;
532
533   /* Set viewport, scissor, ortho and ar->drawrct. */
534   wmPartialViewport(&ar->drawrct, &ar->winrct, &ar->drawrct);
535
536   wmOrtho2_region_pixelspace(ar);
537
538   UI_SetTheme(sa ? sa->spacetype : 0, at->regionid);
539
540   if (sa && area_is_pseudo_minimized(sa)) {
541     UI_ThemeClearColor(TH_EDITOR_OUTLINE);
542     glClear(GL_COLOR_BUFFER_BIT);
543     return;
544   }
545   /* optional header info instead? */
546   else if (ar->headerstr) {
547     region_draw_status_text(sa, ar);
548   }
549   else if (at->draw) {
550     at->draw(C, ar);
551   }
552
553   /* XXX test: add convention to end regions always in pixel space, for drawing of borders/gestures etc */
554   ED_region_pixelspace(ar);
555
556   ED_region_draw_cb_draw(C, ar, REGION_DRAW_POST_PIXEL);
557
558   region_draw_azones(sa, ar);
559
560   /* for debugging unneeded area redraws and partial redraw */
561 #if 0
562   GPU_blend(true);
563   GPUVertFormat *format = immVertexFormat();
564   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
565   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
566   immUniformColor4f(drand48(), drand48(), drand48(), 0.1f);
567   immRectf(pos,
568            ar->drawrct.xmin - ar->winrct.xmin,
569            ar->drawrct.ymin - ar->winrct.ymin,
570            ar->drawrct.xmax - ar->winrct.xmin,
571            ar->drawrct.ymax - ar->winrct.ymin);
572   immUnbindProgram();
573   GPU_blend(false);
574 #endif
575
576   memset(&ar->drawrct, 0, sizeof(ar->drawrct));
577
578   UI_blocklist_free_inactive(C, &ar->uiblocks);
579
580   if (sa) {
581     const bScreen *screen = WM_window_get_active_screen(win);
582
583     /* Only region emboss for top-bar */
584     if ((screen->state != SCREENFULL) && ED_area_is_global(sa)) {
585       region_draw_emboss(ar, &ar->winrct, (REGION_EMBOSS_LEFT | REGION_EMBOSS_RIGHT));
586     }
587     else if ((ar->regiontype == RGN_TYPE_WINDOW) && (ar->alignment == RGN_ALIGN_QSPLIT)) {
588
589       /* draw separating lines between the quad views */
590
591       float color[4] = {0.0f, 0.0f, 0.0f, 0.8f};
592       UI_GetThemeColor3fv(TH_EDITOR_OUTLINE, color);
593       GPUVertFormat *format = immVertexFormat();
594       uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
595       immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
596       immUniformColor4fv(color);
597       GPU_line_width(1.0f);
598       imm_draw_box_wire_2d(
599           pos, 0, 0, ar->winrct.xmax - ar->winrct.xmin + 1, ar->winrct.ymax - ar->winrct.ymin + 1);
600       immUnbindProgram();
601     }
602   }
603
604   /* We may want to detach message-subscriptions from drawing. */
605   {
606     WorkSpace *workspace = CTX_wm_workspace(C);
607     wmWindowManager *wm = CTX_wm_manager(C);
608     bScreen *screen = WM_window_get_active_screen(win);
609     Scene *scene = CTX_data_scene(C);
610     struct wmMsgBus *mbus = wm->message_bus;
611     WM_msgbus_clear_by_owner(mbus, ar);
612
613     /* Cheat, always subscribe to this space type properties.
614      *
615      * This covers most cases and avoids copy-paste similar code for each space type.
616      */
617     if (ELEM(ar->regiontype, RGN_TYPE_WINDOW, RGN_TYPE_CHANNELS, RGN_TYPE_UI, RGN_TYPE_TOOLS)) {
618       SpaceLink *sl = sa->spacedata.first;
619
620       PointerRNA ptr;
621       RNA_pointer_create(&screen->id, &RNA_Space, sl, &ptr);
622
623       wmMsgSubscribeValue msg_sub_value_region_tag_redraw = {
624           .owner = ar,
625           .user_data = ar,
626           .notify = ED_region_do_msg_notify_tag_redraw,
627       };
628       /* All properties for this space type. */
629       WM_msg_subscribe_rna(mbus, &ptr, NULL, &msg_sub_value_region_tag_redraw, __func__);
630     }
631
632     ED_region_message_subscribe(C, workspace, scene, screen, sa, ar, mbus);
633   }
634 }
635
636 /* **********************************
637  * maybe silly, but let's try for now
638  * to keep these tags protected
639  * ********************************** */
640
641 void ED_region_tag_redraw(ARegion *ar)
642 {
643   /* don't tag redraw while drawing, it shouldn't happen normally
644    * but python scripts can cause this to happen indirectly */
645   if (ar && !(ar->do_draw & RGN_DRAWING)) {
646     /* zero region means full region redraw */
647     ar->do_draw &= ~(RGN_DRAW_PARTIAL | RGN_DRAW_NO_REBUILD);
648     ar->do_draw |= RGN_DRAW;
649     memset(&ar->drawrct, 0, sizeof(ar->drawrct));
650   }
651 }
652
653 void ED_region_tag_redraw_overlay(ARegion *ar)
654 {
655   if (ar)
656     ar->do_draw_overlay = RGN_DRAW;
657 }
658
659 void ED_region_tag_redraw_no_rebuild(ARegion *ar)
660 {
661   if (ar && !(ar->do_draw & (RGN_DRAWING | RGN_DRAW))) {
662     ar->do_draw &= ~RGN_DRAW_PARTIAL;
663     ar->do_draw |= RGN_DRAW_NO_REBUILD;
664     memset(&ar->drawrct, 0, sizeof(ar->drawrct));
665   }
666 }
667
668 void ED_region_tag_refresh_ui(ARegion *ar)
669 {
670   if (ar) {
671     ar->do_draw |= RGN_DRAW_REFRESH_UI;
672   }
673 }
674
675 void ED_region_tag_redraw_partial(ARegion *ar, const rcti *rct)
676 {
677   if (ar && !(ar->do_draw & RGN_DRAWING)) {
678     if (!(ar->do_draw & (RGN_DRAW | RGN_DRAW_NO_REBUILD | RGN_DRAW_PARTIAL))) {
679       /* no redraw set yet, set partial region */
680       ar->do_draw |= RGN_DRAW_PARTIAL;
681       ar->drawrct = *rct;
682     }
683     else if (ar->drawrct.xmin != ar->drawrct.xmax) {
684       BLI_assert((ar->do_draw & RGN_DRAW_PARTIAL) != 0);
685       /* partial redraw already set, expand region */
686       BLI_rcti_union(&ar->drawrct, rct);
687     }
688     else {
689       BLI_assert((ar->do_draw & (RGN_DRAW | RGN_DRAW_NO_REBUILD)) != 0);
690       /* Else, full redraw is already requested, nothing to do here. */
691     }
692   }
693 }
694
695 void ED_area_tag_redraw(ScrArea *sa)
696 {
697   ARegion *ar;
698
699   if (sa)
700     for (ar = sa->regionbase.first; ar; ar = ar->next)
701       ED_region_tag_redraw(ar);
702 }
703
704 void ED_area_tag_redraw_no_rebuild(ScrArea *sa)
705 {
706   ARegion *ar;
707
708   if (sa)
709     for (ar = sa->regionbase.first; ar; ar = ar->next)
710       ED_region_tag_redraw_no_rebuild(ar);
711 }
712
713 void ED_area_tag_redraw_regiontype(ScrArea *sa, int regiontype)
714 {
715   ARegion *ar;
716
717   if (sa) {
718     for (ar = sa->regionbase.first; ar; ar = ar->next) {
719       if (ar->regiontype == regiontype) {
720         ED_region_tag_redraw(ar);
721       }
722     }
723   }
724 }
725
726 void ED_area_tag_refresh(ScrArea *sa)
727 {
728   if (sa)
729     sa->do_refresh = true;
730 }
731
732 /* *************************************************************** */
733
734 /* use NULL to disable it */
735 void ED_area_status_text(ScrArea *sa, const char *str)
736 {
737   ARegion *ar;
738
739   /* happens when running transform operators in background mode */
740   if (sa == NULL)
741     return;
742
743   for (ar = sa->regionbase.first; ar; ar = ar->next) {
744     if (ar->regiontype == RGN_TYPE_HEADER) {
745       if (str) {
746         if (ar->headerstr == NULL)
747           ar->headerstr = MEM_mallocN(UI_MAX_DRAW_STR, "headerprint");
748         BLI_strncpy(ar->headerstr, str, UI_MAX_DRAW_STR);
749         BLI_str_rstrip(ar->headerstr);
750       }
751       else if (ar->headerstr) {
752         MEM_freeN(ar->headerstr);
753         ar->headerstr = NULL;
754       }
755       ED_region_tag_redraw(ar);
756     }
757   }
758 }
759
760 void ED_workspace_status_text(bContext *C, const char *str)
761 {
762   wmWindow *win = CTX_wm_window(C);
763   WorkSpace *workspace = CTX_wm_workspace(C);
764
765   /* Can be NULL when running operators in background mode. */
766   if (workspace == NULL)
767     return;
768
769   if (str) {
770     if (workspace->status_text == NULL)
771       workspace->status_text = MEM_mallocN(UI_MAX_DRAW_STR, "headerprint");
772     BLI_strncpy(workspace->status_text, str, UI_MAX_DRAW_STR);
773   }
774   else if (workspace->status_text) {
775     MEM_freeN(workspace->status_text);
776     workspace->status_text = NULL;
777   }
778
779   /* Redraw status bar. */
780   for (ScrArea *sa = win->global_areas.areabase.first; sa; sa = sa->next) {
781     if (sa->spacetype == SPACE_STATUSBAR) {
782       ED_area_tag_redraw(sa);
783       break;
784     }
785   }
786 }
787
788 /* ************************************************************ */
789
790 static void area_azone_initialize(wmWindow *win, const bScreen *screen, ScrArea *sa)
791 {
792   AZone *az;
793
794   /* reinitialize entirely, regions and fullscreen add azones too */
795   BLI_freelistN(&sa->actionzones);
796
797   if (screen->state != SCREENNORMAL) {
798     return;
799   }
800
801   if (U.app_flag & USER_APP_LOCK_UI_LAYOUT) {
802     return;
803   }
804
805   if (ED_area_is_global(sa)) {
806     return;
807   }
808
809   if (screen->temp) {
810     return;
811   }
812
813   float coords[4][4] = {
814       /* Bottom-left. */
815       {sa->totrct.xmin - U.pixelsize,
816        sa->totrct.ymin - U.pixelsize,
817        sa->totrct.xmin + AZONESPOTW,
818        sa->totrct.ymin + AZONESPOTH},
819       /* Bottom-right. */
820       {sa->totrct.xmax - AZONESPOTW,
821        sa->totrct.ymin - U.pixelsize,
822        sa->totrct.xmax + U.pixelsize,
823        sa->totrct.ymin + AZONESPOTH},
824       /* Top-left. */
825       {sa->totrct.xmin - U.pixelsize,
826        sa->totrct.ymax - AZONESPOTH,
827        sa->totrct.xmin + AZONESPOTW,
828        sa->totrct.ymax + U.pixelsize},
829       /* Top-right. */
830       {sa->totrct.xmax - AZONESPOTW,
831        sa->totrct.ymax - AZONESPOTH,
832        sa->totrct.xmax + U.pixelsize,
833        sa->totrct.ymax + U.pixelsize},
834   };
835
836   for (int i = 0; i < 4; i++) {
837     /* can't click on bottom corners on OS X, already used for resizing */
838 #ifdef __APPLE__
839     if (!WM_window_is_fullscreen(win) &&
840         ((coords[i][0] == 0 && coords[i][1] == 0) ||
841          (coords[i][0] == WM_window_pixels_x(win) && coords[i][1] == 0))) {
842       continue;
843     }
844 #else
845     (void)win;
846 #endif
847
848     /* set area action zones */
849     az = (AZone *)MEM_callocN(sizeof(AZone), "actionzone");
850     BLI_addtail(&(sa->actionzones), az);
851     az->type = AZONE_AREA;
852     az->x1 = coords[i][0];
853     az->y1 = coords[i][1];
854     az->x2 = coords[i][2];
855     az->y2 = coords[i][3];
856     BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
857   }
858 }
859
860 static void fullscreen_azone_initialize(ScrArea *sa, ARegion *ar)
861 {
862   AZone *az;
863
864   if (ED_area_is_global(sa) || (ar->regiontype != RGN_TYPE_WINDOW))
865     return;
866
867   az = (AZone *)MEM_callocN(sizeof(AZone), "fullscreen action zone");
868   BLI_addtail(&(sa->actionzones), az);
869   az->type = AZONE_FULLSCREEN;
870   az->ar = ar;
871   az->alpha = 0.0f;
872
873   az->x1 = ar->winrct.xmax - (AZONEFADEOUT - 1);
874   az->y1 = ar->winrct.ymax - (AZONEFADEOUT - 1);
875   az->x2 = ar->winrct.xmax;
876   az->y2 = ar->winrct.ymax;
877   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
878 }
879
880 #define AZONEPAD_EDGE (0.1f * U.widget_unit)
881 #define AZONEPAD_ICON (0.45f * U.widget_unit)
882 static void region_azone_edge(AZone *az, ARegion *ar)
883 {
884   switch (az->edge) {
885     case AE_TOP_TO_BOTTOMRIGHT:
886       az->x1 = ar->winrct.xmin;
887       az->y1 = ar->winrct.ymax - AZONEPAD_EDGE;
888       az->x2 = ar->winrct.xmax;
889       az->y2 = ar->winrct.ymax + AZONEPAD_EDGE;
890       break;
891     case AE_BOTTOM_TO_TOPLEFT:
892       az->x1 = ar->winrct.xmin;
893       az->y1 = ar->winrct.ymin + AZONEPAD_EDGE;
894       az->x2 = ar->winrct.xmax;
895       az->y2 = ar->winrct.ymin - AZONEPAD_EDGE;
896       break;
897     case AE_LEFT_TO_TOPRIGHT:
898       az->x1 = ar->winrct.xmin - AZONEPAD_EDGE;
899       az->y1 = ar->winrct.ymin;
900       az->x2 = ar->winrct.xmin + AZONEPAD_EDGE;
901       az->y2 = ar->winrct.ymax;
902       break;
903     case AE_RIGHT_TO_TOPLEFT:
904       az->x1 = ar->winrct.xmax + AZONEPAD_EDGE;
905       az->y1 = ar->winrct.ymin;
906       az->x2 = ar->winrct.xmax - AZONEPAD_EDGE;
907       az->y2 = ar->winrct.ymax;
908       break;
909   }
910
911   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
912 }
913
914 /* region already made zero sized, in shape of edge */
915 static void region_azone_tab_plus(ScrArea *sa, AZone *az, ARegion *ar)
916 {
917   AZone *azt;
918   int tot = 0, add;
919   /* Edge offset multiplied by the  */
920
921   float edge_offset = 1.0f;
922   const float tab_size_x = 0.7f * U.widget_unit;
923   const float tab_size_y = 0.4f * U.widget_unit;
924
925   for (azt = sa->actionzones.first; azt; azt = azt->next) {
926     if (azt->edge == az->edge)
927       tot++;
928   }
929
930   switch (az->edge) {
931     case AE_TOP_TO_BOTTOMRIGHT:
932       add = (ar->winrct.ymax == sa->totrct.ymin) ? 1 : 0;
933       az->x1 = ar->winrct.xmax - ((edge_offset + 1.0f) * tab_size_x);
934       az->y1 = ar->winrct.ymax - add;
935       az->x2 = ar->winrct.xmax - (edge_offset * tab_size_x);
936       az->y2 = ar->winrct.ymax - add + tab_size_y;
937       break;
938     case AE_BOTTOM_TO_TOPLEFT:
939       az->x1 = ar->winrct.xmax - ((edge_offset + 1.0f) * tab_size_x);
940       az->y1 = ar->winrct.ymin - tab_size_y;
941       az->x2 = ar->winrct.xmax - (edge_offset * tab_size_x);
942       az->y2 = ar->winrct.ymin;
943       break;
944     case AE_LEFT_TO_TOPRIGHT:
945       az->x1 = ar->winrct.xmin - tab_size_y;
946       az->y1 = ar->winrct.ymax - ((edge_offset + 1.0f) * tab_size_x);
947       az->x2 = ar->winrct.xmin;
948       az->y2 = ar->winrct.ymax - (edge_offset * tab_size_x);
949       break;
950     case AE_RIGHT_TO_TOPLEFT:
951       az->x1 = ar->winrct.xmax;
952       az->y1 = ar->winrct.ymax - ((edge_offset + 1.0f) * tab_size_x);
953       az->x2 = ar->winrct.xmax + tab_size_y;
954       az->y2 = ar->winrct.ymax - (edge_offset * tab_size_x);
955       break;
956   }
957   /* rect needed for mouse pointer test */
958   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
959 }
960
961 static bool region_azone_edge_poll(const ARegion *ar, const bool is_fullscreen)
962 {
963   const bool is_hidden = (ar->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
964
965   if (is_hidden && is_fullscreen) {
966     return false;
967   }
968   if (!is_hidden && ELEM(ar->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
969     return false;
970   }
971
972   return true;
973 }
974
975 static void region_azone_edge_initialize(ScrArea *sa,
976                                          ARegion *ar,
977                                          AZEdge edge,
978                                          const bool is_fullscreen)
979 {
980   AZone *az = NULL;
981   const bool is_hidden = (ar->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
982
983   if (!region_azone_edge_poll(ar, is_fullscreen)) {
984     return;
985   }
986
987   az = (AZone *)MEM_callocN(sizeof(AZone), "actionzone");
988   BLI_addtail(&(sa->actionzones), az);
989   az->type = AZONE_REGION;
990   az->ar = ar;
991   az->edge = edge;
992
993   if (is_hidden) {
994     region_azone_tab_plus(sa, az, ar);
995   }
996   else {
997     region_azone_edge(az, ar);
998   }
999 }
1000
1001 static void region_azone_scrollbar_initialize(ScrArea *sa,
1002                                               ARegion *ar,
1003                                               AZScrollDirection direction)
1004 {
1005   rcti scroller_vert = (direction == AZ_SCROLL_VERT) ? ar->v2d.vert : ar->v2d.hor;
1006   AZone *az = MEM_callocN(sizeof(*az), __func__);
1007
1008   BLI_addtail(&sa->actionzones, az);
1009   az->type = AZONE_REGION_SCROLL;
1010   az->ar = ar;
1011   az->direction = direction;
1012
1013   if (direction == AZ_SCROLL_VERT) {
1014     az->ar->v2d.alpha_vert = 0;
1015   }
1016   else if (direction == AZ_SCROLL_HOR) {
1017     az->ar->v2d.alpha_hor = 0;
1018   }
1019
1020   BLI_rcti_translate(&scroller_vert, ar->winrct.xmin, ar->winrct.ymin);
1021   az->x1 = scroller_vert.xmin - AZONEFADEIN;
1022   az->y1 = scroller_vert.ymin - AZONEFADEIN;
1023   az->x2 = scroller_vert.xmax + AZONEFADEIN;
1024   az->y2 = scroller_vert.ymax + AZONEFADEIN;
1025
1026   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
1027 }
1028
1029 static void region_azones_scrollbars_initialize(ScrArea *sa, ARegion *ar)
1030 {
1031   const View2D *v2d = &ar->v2d;
1032
1033   if ((v2d->scroll & V2D_SCROLL_VERTICAL) && ((v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) == 0)) {
1034     region_azone_scrollbar_initialize(sa, ar, AZ_SCROLL_VERT);
1035   }
1036   if ((v2d->scroll & V2D_SCROLL_HORIZONTAL) &&
1037       ((v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) == 0)) {
1038     region_azone_scrollbar_initialize(sa, ar, AZ_SCROLL_HOR);
1039   }
1040 }
1041
1042 /* *************************************************************** */
1043
1044 static void region_azones_add(const bScreen *screen, ScrArea *sa, ARegion *ar, const int alignment)
1045 {
1046   const bool is_fullscreen = screen->state == SCREENFULL;
1047
1048   /* Only display tab or icons when the header region is hidden
1049    * (not the tool header - they overlap). */
1050   if (ar->regiontype == RGN_TYPE_TOOL_HEADER) {
1051     return;
1052   }
1053
1054   /* edge code (t b l r) is along which area edge azone will be drawn */
1055   if (alignment == RGN_ALIGN_TOP)
1056     region_azone_edge_initialize(sa, ar, AE_BOTTOM_TO_TOPLEFT, is_fullscreen);
1057   else if (alignment == RGN_ALIGN_BOTTOM)
1058     region_azone_edge_initialize(sa, ar, AE_TOP_TO_BOTTOMRIGHT, is_fullscreen);
1059   else if (alignment == RGN_ALIGN_RIGHT)
1060     region_azone_edge_initialize(sa, ar, AE_LEFT_TO_TOPRIGHT, is_fullscreen);
1061   else if (alignment == RGN_ALIGN_LEFT)
1062     region_azone_edge_initialize(sa, ar, AE_RIGHT_TO_TOPLEFT, is_fullscreen);
1063
1064   if (is_fullscreen) {
1065     fullscreen_azone_initialize(sa, ar);
1066   }
1067
1068   region_azones_scrollbars_initialize(sa, ar);
1069 }
1070
1071 /* dir is direction to check, not the splitting edge direction! */
1072 static int rct_fits(const rcti *rect, char dir, int size)
1073 {
1074   if (dir == 'h') {
1075     return BLI_rcti_size_x(rect) + 1 - size;
1076   }
1077   else { /* 'v' */
1078     return BLI_rcti_size_y(rect) + 1 - size;
1079   }
1080 }
1081
1082 /* *************************************************************** */
1083
1084 /* ar should be overlapping */
1085 /* function checks if some overlapping region was defined before - on same place */
1086 static void region_overlap_fix(ScrArea *sa, ARegion *ar)
1087 {
1088   ARegion *ar1;
1089   const int align = ar->alignment & ~RGN_SPLIT_PREV;
1090   int align1 = 0;
1091
1092   /* find overlapping previous region on same place */
1093   for (ar1 = ar->prev; ar1; ar1 = ar1->prev) {
1094     if (ar1->flag & (RGN_FLAG_HIDDEN)) {
1095       continue;
1096     }
1097
1098     if (ar1->overlap && ((ar1->alignment & RGN_SPLIT_PREV) == 0)) {
1099       if (ELEM(ar1->alignment, RGN_ALIGN_FLOAT)) {
1100         continue;
1101       }
1102       align1 = ar1->alignment;
1103       if (BLI_rcti_isect(&ar1->winrct, &ar->winrct, NULL)) {
1104         if (align1 != align) {
1105           /* Left overlapping right or vice-versa, forbid this! */
1106           ar->flag |= RGN_FLAG_TOO_SMALL;
1107           return;
1108         }
1109         /* Else, we have our previous region on same side. */
1110         break;
1111       }
1112     }
1113   }
1114
1115   /* translate or close */
1116   if (ar1) {
1117     if (align1 == RGN_ALIGN_LEFT) {
1118       if (ar->winrct.xmax + ar1->winx > sa->winx - U.widget_unit) {
1119         ar->flag |= RGN_FLAG_TOO_SMALL;
1120         return;
1121       }
1122       else {
1123         BLI_rcti_translate(&ar->winrct, ar1->winx, 0);
1124       }
1125     }
1126     else if (align1 == RGN_ALIGN_RIGHT) {
1127       if (ar->winrct.xmin - ar1->winx < U.widget_unit) {
1128         ar->flag |= RGN_FLAG_TOO_SMALL;
1129         return;
1130       }
1131       else {
1132         BLI_rcti_translate(&ar->winrct, -ar1->winx, 0);
1133       }
1134     }
1135   }
1136
1137   /* At this point, 'ar' is in its final position and still open.
1138    * Make a final check it does not overlap any previous 'other side' region. */
1139   for (ar1 = ar->prev; ar1; ar1 = ar1->prev) {
1140     if (ar1->flag & (RGN_FLAG_HIDDEN)) {
1141       continue;
1142     }
1143     if (ELEM(ar1->alignment, RGN_ALIGN_FLOAT)) {
1144       continue;
1145     }
1146
1147     if (ar1->overlap && (ar1->alignment & RGN_SPLIT_PREV) == 0) {
1148       if ((ar1->alignment != align) && BLI_rcti_isect(&ar1->winrct, &ar->winrct, NULL)) {
1149         /* Left overlapping right or vice-versa, forbid this! */
1150         ar->flag |= RGN_FLAG_TOO_SMALL;
1151         return;
1152       }
1153     }
1154   }
1155 }
1156
1157 /* overlapping regions only in the following restricted cases */
1158 bool ED_region_is_overlap(int spacetype, int regiontype)
1159 {
1160   if (regiontype == RGN_TYPE_HUD) {
1161     return true;
1162   }
1163   if (U.uiflag2 & USER_REGION_OVERLAP) {
1164     if (spacetype == SPACE_NODE) {
1165       if (regiontype == RGN_TYPE_TOOLS) {
1166         return true;
1167       }
1168     }
1169     else if (ELEM(spacetype, SPACE_VIEW3D, SPACE_IMAGE)) {
1170       if (ELEM(regiontype,
1171                RGN_TYPE_TOOLS,
1172                RGN_TYPE_UI,
1173                RGN_TYPE_TOOL_PROPS,
1174                RGN_TYPE_HEADER,
1175                RGN_TYPE_FOOTER)) {
1176         return true;
1177       }
1178     }
1179   }
1180
1181   return false;
1182 }
1183
1184 static void region_rect_recursive(
1185     ScrArea *sa, ARegion *ar, rcti *remainder, rcti *overlap_remainder, int quad)
1186 {
1187   rcti *remainder_prev = remainder;
1188
1189   if (ar == NULL)
1190     return;
1191
1192   int prev_winx = ar->winx;
1193   int prev_winy = ar->winy;
1194
1195   /* no returns in function, winrct gets set in the end again */
1196   BLI_rcti_init(&ar->winrct, 0, 0, 0, 0);
1197
1198   /* for test; allow split of previously defined region */
1199   if (ar->alignment & RGN_SPLIT_PREV)
1200     if (ar->prev)
1201       remainder = &ar->prev->winrct;
1202
1203   int alignment = ar->alignment & ~RGN_SPLIT_PREV;
1204
1205   /* set here, assuming userpref switching forces to call this again */
1206   ar->overlap = ED_region_is_overlap(sa->spacetype, ar->regiontype);
1207
1208   /* clear state flags first */
1209   ar->flag &= ~(RGN_FLAG_TOO_SMALL | RGN_FLAG_SIZE_CLAMP_X | RGN_FLAG_SIZE_CLAMP_Y);
1210   /* user errors */
1211   if ((ar->next == NULL) && !ELEM(alignment, RGN_ALIGN_QSPLIT, RGN_ALIGN_FLOAT)) {
1212     alignment = RGN_ALIGN_NONE;
1213   }
1214
1215   /* prefsize, taking into account DPI */
1216   int prefsizex = UI_DPI_FAC * ((ar->sizex > 1) ? ar->sizex + 0.5f : ar->type->prefsizex);
1217   int prefsizey;
1218
1219   if (ar->flag & RGN_FLAG_PREFSIZE_OR_HIDDEN) {
1220     prefsizex = UI_DPI_FAC * ar->type->prefsizex;
1221     prefsizey = UI_DPI_FAC * ar->type->prefsizey;
1222   }
1223   else if (ar->regiontype == RGN_TYPE_HEADER) {
1224     prefsizey = ED_area_headersize();
1225   }
1226   else if (ar->regiontype == RGN_TYPE_TOOL_HEADER) {
1227     prefsizey = ED_area_headersize();
1228   }
1229   else if (ar->regiontype == RGN_TYPE_FOOTER) {
1230     prefsizey = ED_area_footersize();
1231   }
1232   else if (ED_area_is_global(sa)) {
1233     prefsizey = ED_region_global_size_y();
1234   }
1235   else if (ar->regiontype == RGN_TYPE_UI && sa->spacetype == SPACE_FILE) {
1236     prefsizey = UI_UNIT_Y * 2 + (UI_UNIT_Y / 2);
1237   }
1238   else {
1239     prefsizey = UI_DPI_FAC * (ar->sizey > 1 ? ar->sizey + 0.5f : ar->type->prefsizey);
1240   }
1241
1242   if (ar->flag & RGN_FLAG_HIDDEN) {
1243     /* hidden is user flag */
1244   }
1245   else if (alignment == RGN_ALIGN_FLOAT) {
1246     /**
1247      * \note Currently this window type is only used for #RGN_TYPE_HUD,
1248      * We expect the panel to resize it's self to be larger.
1249      *
1250      * This aligns to the lower left of the area.
1251      */
1252     const int size_min[2] = {UI_UNIT_X, UI_UNIT_Y};
1253     rcti overlap_remainder_margin = *overlap_remainder;
1254     BLI_rcti_resize(&overlap_remainder_margin,
1255                     max_ii(0, BLI_rcti_size_x(overlap_remainder) - UI_UNIT_X / 2),
1256                     max_ii(0, BLI_rcti_size_y(overlap_remainder) - UI_UNIT_Y / 2));
1257     ar->winrct.xmin = overlap_remainder_margin.xmin;
1258     ar->winrct.ymin = overlap_remainder_margin.ymin;
1259     ar->winrct.xmax = ar->winrct.xmin + prefsizex - 1;
1260     ar->winrct.ymax = ar->winrct.ymin + prefsizey - 1;
1261
1262     BLI_rcti_isect(&ar->winrct, &overlap_remainder_margin, &ar->winrct);
1263
1264     if (BLI_rcti_size_x(&ar->winrct) != prefsizex - 1) {
1265       ar->flag |= RGN_FLAG_SIZE_CLAMP_X;
1266     }
1267     if (BLI_rcti_size_y(&ar->winrct) != prefsizey - 1) {
1268       ar->flag |= RGN_FLAG_SIZE_CLAMP_Y;
1269     }
1270
1271     /* We need to use a test that wont have been previously clamped. */
1272     rcti winrct_test = {
1273         .xmin = ar->winrct.xmin,
1274         .ymin = ar->winrct.ymin,
1275         .xmax = ar->winrct.xmin + size_min[0],
1276         .ymax = ar->winrct.ymin + size_min[1],
1277     };
1278     BLI_rcti_isect(&winrct_test, &overlap_remainder_margin, &winrct_test);
1279     if (BLI_rcti_size_x(&winrct_test) < size_min[0] ||
1280         BLI_rcti_size_y(&winrct_test) < size_min[1]) {
1281       ar->flag |= RGN_FLAG_TOO_SMALL;
1282     }
1283   }
1284   else if (rct_fits(remainder, 'v', 1) < 0 || rct_fits(remainder, 'h', 1) < 0) {
1285     /* remainder is too small for any usage */
1286     ar->flag |= RGN_FLAG_TOO_SMALL;
1287   }
1288   else if (alignment == RGN_ALIGN_NONE) {
1289     /* typically last region */
1290     ar->winrct = *remainder;
1291     BLI_rcti_init(remainder, 0, 0, 0, 0);
1292   }
1293   else if (alignment == RGN_ALIGN_TOP || alignment == RGN_ALIGN_BOTTOM) {
1294     rcti *winrct = (ar->overlap) ? overlap_remainder : remainder;
1295
1296     if (rct_fits(winrct, 'v', prefsizey) < 0) {
1297       ar->flag |= RGN_FLAG_TOO_SMALL;
1298     }
1299     else {
1300       int fac = rct_fits(winrct, 'v', prefsizey);
1301
1302       if (fac < 0)
1303         prefsizey += fac;
1304
1305       ar->winrct = *winrct;
1306
1307       if (alignment == RGN_ALIGN_TOP) {
1308         ar->winrct.ymin = ar->winrct.ymax - prefsizey + 1;
1309         winrct->ymax = ar->winrct.ymin - 1;
1310       }
1311       else {
1312         ar->winrct.ymax = ar->winrct.ymin + prefsizey - 1;
1313         winrct->ymin = ar->winrct.ymax + 1;
1314       }
1315     }
1316   }
1317   else if (ELEM(alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
1318     rcti *winrct = (ar->overlap) ? overlap_remainder : remainder;
1319
1320     if (rct_fits(winrct, 'h', prefsizex) < 0) {
1321       ar->flag |= RGN_FLAG_TOO_SMALL;
1322     }
1323     else {
1324       int fac = rct_fits(winrct, 'h', prefsizex);
1325
1326       if (fac < 0)
1327         prefsizex += fac;
1328
1329       ar->winrct = *winrct;
1330
1331       if (alignment == RGN_ALIGN_RIGHT) {
1332         ar->winrct.xmin = ar->winrct.xmax - prefsizex + 1;
1333         winrct->xmax = ar->winrct.xmin - 1;
1334       }
1335       else {
1336         ar->winrct.xmax = ar->winrct.xmin + prefsizex - 1;
1337         winrct->xmin = ar->winrct.xmax + 1;
1338       }
1339     }
1340   }
1341   else if (alignment == RGN_ALIGN_VSPLIT || alignment == RGN_ALIGN_HSPLIT) {
1342     /* percentage subdiv*/
1343     ar->winrct = *remainder;
1344
1345     if (alignment == RGN_ALIGN_HSPLIT) {
1346       if (rct_fits(remainder, 'h', prefsizex) > 4) {
1347         ar->winrct.xmax = BLI_rcti_cent_x(remainder);
1348         remainder->xmin = ar->winrct.xmax + 1;
1349       }
1350       else {
1351         BLI_rcti_init(remainder, 0, 0, 0, 0);
1352       }
1353     }
1354     else {
1355       if (rct_fits(remainder, 'v', prefsizey) > 4) {
1356         ar->winrct.ymax = BLI_rcti_cent_y(remainder);
1357         remainder->ymin = ar->winrct.ymax + 1;
1358       }
1359       else {
1360         BLI_rcti_init(remainder, 0, 0, 0, 0);
1361       }
1362     }
1363   }
1364   else if (alignment == RGN_ALIGN_QSPLIT) {
1365     ar->winrct = *remainder;
1366
1367     /* test if there's still 4 regions left */
1368     if (quad == 0) {
1369       ARegion *artest = ar->next;
1370       int count = 1;
1371
1372       while (artest) {
1373         artest->alignment = RGN_ALIGN_QSPLIT;
1374         artest = artest->next;
1375         count++;
1376       }
1377
1378       if (count != 4) {
1379         /* let's stop adding regions */
1380         BLI_rcti_init(remainder, 0, 0, 0, 0);
1381         if (G.debug & G_DEBUG)
1382           printf("region quadsplit failed\n");
1383       }
1384       else {
1385         quad = 1;
1386       }
1387     }
1388     if (quad) {
1389       if (quad == 1) { /* left bottom */
1390         ar->winrct.xmax = BLI_rcti_cent_x(remainder);
1391         ar->winrct.ymax = BLI_rcti_cent_y(remainder);
1392       }
1393       else if (quad == 2) { /* left top */
1394         ar->winrct.xmax = BLI_rcti_cent_x(remainder);
1395         ar->winrct.ymin = BLI_rcti_cent_y(remainder) + 1;
1396       }
1397       else if (quad == 3) { /* right bottom */
1398         ar->winrct.xmin = BLI_rcti_cent_x(remainder) + 1;
1399         ar->winrct.ymax = BLI_rcti_cent_y(remainder);
1400       }
1401       else { /* right top */
1402         ar->winrct.xmin = BLI_rcti_cent_x(remainder) + 1;
1403         ar->winrct.ymin = BLI_rcti_cent_y(remainder) + 1;
1404         BLI_rcti_init(remainder, 0, 0, 0, 0);
1405       }
1406
1407       quad++;
1408     }
1409   }
1410
1411   /* for speedup */
1412   ar->winx = BLI_rcti_size_x(&ar->winrct) + 1;
1413   ar->winy = BLI_rcti_size_y(&ar->winrct) + 1;
1414
1415   /* if region opened normally, we store this for hide/reveal usage */
1416   /* prevent rounding errors for UI_DPI_FAC mult and divide */
1417   if (ar->winx > 1)
1418     ar->sizex = (ar->winx + 0.5f) / UI_DPI_FAC;
1419   if (ar->winy > 1)
1420     ar->sizey = (ar->winy + 0.5f) / UI_DPI_FAC;
1421
1422   /* exception for multiple overlapping regions on same spot */
1423   if (ar->overlap && (alignment != RGN_ALIGN_FLOAT)) {
1424     region_overlap_fix(sa, ar);
1425   }
1426
1427   /* set winrect for azones */
1428   if (ar->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) {
1429     ar->winrct = (ar->overlap) ? *overlap_remainder : *remainder;
1430
1431     switch (alignment) {
1432       case RGN_ALIGN_TOP:
1433         ar->winrct.ymin = ar->winrct.ymax;
1434         break;
1435       case RGN_ALIGN_BOTTOM:
1436         ar->winrct.ymax = ar->winrct.ymin;
1437         break;
1438       case RGN_ALIGN_RIGHT:
1439         ar->winrct.xmin = ar->winrct.xmax;
1440         break;
1441       case RGN_ALIGN_LEFT:
1442       default:
1443         /* prevent winrct to be valid */
1444         ar->winrct.xmax = ar->winrct.xmin;
1445         break;
1446     }
1447   }
1448
1449   /* restore prev-split exception */
1450   if (ar->alignment & RGN_SPLIT_PREV) {
1451     if (ar->prev) {
1452       remainder = remainder_prev;
1453       ar->prev->winx = BLI_rcti_size_x(&ar->prev->winrct) + 1;
1454       ar->prev->winy = BLI_rcti_size_y(&ar->prev->winrct) + 1;
1455     }
1456   }
1457
1458   /* After non-overlapping region, all following overlapping regions
1459    * fit within the remaining space again. */
1460   if (!ar->overlap) {
1461     *overlap_remainder = *remainder;
1462   }
1463
1464   region_rect_recursive(sa, ar->next, remainder, overlap_remainder, quad);
1465
1466   /* Tag for redraw if size changes. */
1467   if (ar->winx != prev_winx || ar->winy != prev_winy) {
1468     ED_region_tag_redraw(ar);
1469   }
1470 }
1471
1472 static void area_calc_totrct(ScrArea *sa, const rcti *window_rect)
1473 {
1474   short px = (short)U.pixelsize;
1475
1476   sa->totrct.xmin = sa->v1->vec.x;
1477   sa->totrct.xmax = sa->v4->vec.x;
1478   sa->totrct.ymin = sa->v1->vec.y;
1479   sa->totrct.ymax = sa->v2->vec.y;
1480
1481   /* scale down totrct by 1 pixel on all sides not matching window borders */
1482   if (sa->totrct.xmin > window_rect->xmin) {
1483     sa->totrct.xmin += px;
1484   }
1485   if (sa->totrct.xmax < (window_rect->xmax - 1)) {
1486     sa->totrct.xmax -= px;
1487   }
1488   if (sa->totrct.ymin > window_rect->ymin) {
1489     sa->totrct.ymin += px;
1490   }
1491   if (sa->totrct.ymax < (window_rect->ymax - 1)) {
1492     sa->totrct.ymax -= px;
1493   }
1494   /* Although the following asserts are correct they lead to a very unstable Blender.
1495    * And the asserts would fail even in 2.7x (they were added in 2.8x as part of the top-bar commit).
1496    * For more details see T54864. */
1497 #if 0
1498   BLI_assert(sa->totrct.xmin >= 0);
1499   BLI_assert(sa->totrct.xmax >= 0);
1500   BLI_assert(sa->totrct.ymin >= 0);
1501   BLI_assert(sa->totrct.ymax >= 0);
1502 #endif
1503
1504   /* for speedup */
1505   sa->winx = BLI_rcti_size_x(&sa->totrct) + 1;
1506   sa->winy = BLI_rcti_size_y(&sa->totrct) + 1;
1507 }
1508
1509 /* used for area initialize below */
1510 static void region_subwindow(ARegion *ar)
1511 {
1512   bool hidden = (ar->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) != 0;
1513
1514   if ((ar->alignment & RGN_SPLIT_PREV) && ar->prev)
1515     hidden = hidden || (ar->prev->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
1516
1517   ar->visible = !hidden;
1518 }
1519
1520 /**
1521  * \param ar: Region, may be NULL when adding handlers for \a sa.
1522  */
1523 static void ed_default_handlers(
1524     wmWindowManager *wm, ScrArea *sa, ARegion *ar, ListBase *handlers, int flag)
1525 {
1526   BLI_assert(ar ? (&ar->handlers == handlers) : (&sa->handlers == handlers));
1527
1528   /* note, add-handler checks if it already exists */
1529
1530   /* XXX it would be good to have boundbox checks for some of these... */
1531   if (flag & ED_KEYMAP_UI) {
1532     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "User Interface", 0, 0);
1533     WM_event_add_keymap_handler(handlers, keymap);
1534
1535     /* user interface widgets */
1536     UI_region_handlers_add(handlers);
1537   }
1538   if (flag & ED_KEYMAP_GIZMO) {
1539     BLI_assert(ar && ar->type->regionid == RGN_TYPE_WINDOW);
1540     if (ar) {
1541       /* Anything else is confusing, only allow this. */
1542       BLI_assert(&ar->handlers == handlers);
1543       if (ar->gizmo_map == NULL) {
1544         ar->gizmo_map = WM_gizmomap_new_from_type(
1545             &(const struct wmGizmoMapType_Params){sa->spacetype, ar->type->regionid});
1546       }
1547       WM_gizmomap_add_handlers(ar, ar->gizmo_map);
1548     }
1549   }
1550   if (flag & ED_KEYMAP_TOOL) {
1551     WM_event_add_keymap_handler_dynamic(&ar->handlers, WM_event_get_keymap_from_toolsystem, sa);
1552   }
1553   if (flag & ED_KEYMAP_VIEW2D) {
1554     /* 2d-viewport handling+manipulation */
1555     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D", 0, 0);
1556     WM_event_add_keymap_handler(handlers, keymap);
1557   }
1558   if (flag & ED_KEYMAP_MARKERS) {
1559     /* time-markers */
1560     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Markers", 0, 0);
1561
1562     /* use a boundbox restricted map */
1563     /* same local check for all areas */
1564     static rcti rect = {0, 10000, 0, -1};
1565     rect.ymax = UI_MARKER_MARGIN_Y;
1566     BLI_assert(ar->type->regionid == RGN_TYPE_WINDOW);
1567     WM_event_add_keymap_handler_bb(handlers, keymap, &rect, &ar->winrct);
1568   }
1569   if (flag & ED_KEYMAP_ANIMATION) {
1570     /* frame changing and timeline operators (for time spaces) */
1571     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Animation", 0, 0);
1572     WM_event_add_keymap_handler(handlers, keymap);
1573   }
1574   if (flag & ED_KEYMAP_FRAMES) {
1575     /* frame changing/jumping (for all spaces) */
1576     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Frames", 0, 0);
1577     WM_event_add_keymap_handler(handlers, keymap);
1578   }
1579   if (flag & ED_KEYMAP_HEADER) {
1580     /* standard keymap for headers regions */
1581     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Header", 0, 0);
1582     WM_event_add_keymap_handler(handlers, keymap);
1583   }
1584   if (flag & ED_KEYMAP_FOOTER) {
1585     /* standard keymap for footer regions */
1586     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Footer", 0, 0);
1587     WM_event_add_keymap_handler(handlers, keymap);
1588   }
1589
1590   /* Keep last because of LMB/RMB handling, see: T57527. */
1591   if (flag & ED_KEYMAP_GPENCIL) {
1592     /* grease pencil */
1593     /* NOTE: This is now 4 keymaps - One for basic functionality,
1594      *       and others for special stroke modes (edit, paint and sculpt).
1595      *
1596      *       For now, it's easier to just include all,
1597      *       since you hardly want one without the others.
1598      */
1599     wmKeyMap *keymap_general = WM_keymap_ensure(wm->defaultconf, "Grease Pencil", 0, 0);
1600     WM_event_add_keymap_handler(handlers, keymap_general);
1601
1602     wmKeyMap *keymap_edit = WM_keymap_ensure(
1603         wm->defaultconf, "Grease Pencil Stroke Edit Mode", 0, 0);
1604     WM_event_add_keymap_handler(handlers, keymap_edit);
1605
1606     wmKeyMap *keymap_paint = WM_keymap_ensure(
1607         wm->defaultconf, "Grease Pencil Stroke Paint Mode", 0, 0);
1608     WM_event_add_keymap_handler(handlers, keymap_paint);
1609
1610     wmKeyMap *keymap_paint_draw = WM_keymap_ensure(
1611         wm->defaultconf, "Grease Pencil Stroke Paint (Draw brush)", 0, 0);
1612     WM_event_add_keymap_handler(handlers, keymap_paint_draw);
1613
1614     wmKeyMap *keymap_paint_erase = WM_keymap_ensure(
1615         wm->defaultconf, "Grease Pencil Stroke Paint (Erase)", 0, 0);
1616     WM_event_add_keymap_handler(handlers, keymap_paint_erase);
1617
1618     wmKeyMap *keymap_paint_fill = WM_keymap_ensure(
1619         wm->defaultconf, "Grease Pencil Stroke Paint (Fill)", 0, 0);
1620     WM_event_add_keymap_handler(handlers, keymap_paint_fill);
1621
1622     wmKeyMap *keymap_sculpt = WM_keymap_ensure(
1623         wm->defaultconf, "Grease Pencil Stroke Sculpt Mode", 0, 0);
1624     WM_event_add_keymap_handler(handlers, keymap_sculpt);
1625   }
1626 }
1627
1628 void ED_area_update_region_sizes(wmWindowManager *wm, wmWindow *win, ScrArea *area)
1629 {
1630   rcti rect, overlap_rect;
1631   rcti window_rect;
1632
1633   if (!(area->flag & AREA_FLAG_REGION_SIZE_UPDATE)) {
1634     return;
1635   }
1636   const bScreen *screen = WM_window_get_active_screen(win);
1637
1638   WM_window_rect_calc(win, &window_rect);
1639   area_calc_totrct(area, &window_rect);
1640
1641   /* region rect sizes */
1642   rect = area->totrct;
1643   overlap_rect = rect;
1644   region_rect_recursive(area, area->regionbase.first, &rect, &overlap_rect, 0);
1645
1646   /* Dynamically sized regions may have changed region sizes, so we have to force azone update. */
1647   area_azone_initialize(win, screen, area);
1648
1649   for (ARegion *ar = area->regionbase.first; ar; ar = ar->next) {
1650     region_subwindow(ar);
1651
1652     /* region size may have changed, init does necessary adjustments */
1653     if (ar->type->init) {
1654       ar->type->init(wm, ar);
1655     }
1656
1657     /* Some AZones use View2D data which is only updated in region init, so call that first! */
1658     region_azones_add(screen, area, ar, ar->alignment & ~RGN_SPLIT_PREV);
1659   }
1660   ED_area_azones_update(area, &win->eventstate->x);
1661
1662   area->flag &= ~AREA_FLAG_REGION_SIZE_UPDATE;
1663 }
1664
1665 /* called in screen_refresh, or screens_init, also area size changes */
1666 void ED_area_initialize(wmWindowManager *wm, wmWindow *win, ScrArea *sa)
1667 {
1668   WorkSpace *workspace = WM_window_get_active_workspace(win);
1669   const bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook);
1670   ViewLayer *view_layer = WM_window_get_active_view_layer(win);
1671   ARegion *ar;
1672   rcti rect, overlap_rect;
1673   rcti window_rect;
1674
1675   if (ED_area_is_global(sa) && (sa->global->flag & GLOBAL_AREA_IS_HIDDEN)) {
1676     return;
1677   }
1678   WM_window_rect_calc(win, &window_rect);
1679
1680   /* set typedefinitions */
1681   sa->type = BKE_spacetype_from_id(sa->spacetype);
1682
1683   if (sa->type == NULL) {
1684     sa->spacetype = SPACE_VIEW3D;
1685     sa->type = BKE_spacetype_from_id(sa->spacetype);
1686   }
1687
1688   for (ar = sa->regionbase.first; ar; ar = ar->next)
1689     ar->type = BKE_regiontype_from_id_or_first(sa->type, ar->regiontype);
1690
1691   /* area sizes */
1692   area_calc_totrct(sa, &window_rect);
1693
1694   /* region rect sizes */
1695   rect = sa->totrct;
1696   overlap_rect = rect;
1697   region_rect_recursive(sa, sa->regionbase.first, &rect, &overlap_rect, 0);
1698   sa->flag &= ~AREA_FLAG_REGION_SIZE_UPDATE;
1699
1700   /* default area handlers */
1701   ed_default_handlers(wm, sa, NULL, &sa->handlers, sa->type->keymapflag);
1702   /* checks spacedata, adds own handlers */
1703   if (sa->type->init)
1704     sa->type->init(wm, sa);
1705
1706   /* clear all azones, add the area triangle widgets */
1707   area_azone_initialize(win, screen, sa);
1708
1709   /* region windows, default and own handlers */
1710   for (ar = sa->regionbase.first; ar; ar = ar->next) {
1711     region_subwindow(ar);
1712
1713     if (ar->visible) {
1714       /* default region handlers */
1715       ed_default_handlers(wm, sa, ar, &ar->handlers, ar->type->keymapflag);
1716       /* own handlers */
1717       if (ar->type->init) {
1718         ar->type->init(wm, ar);
1719       }
1720     }
1721     else {
1722       /* prevent uiblocks to run */
1723       UI_blocklist_free(NULL, &ar->uiblocks);
1724     }
1725
1726     /* Some AZones use View2D data which is only updated in region init, so call that first! */
1727     region_azones_add(screen, sa, ar, ar->alignment & ~RGN_SPLIT_PREV);
1728   }
1729
1730   /* Avoid re-initializing tools while resizing the window. */
1731   if ((G.moving & G_TRANSFORM_WM) == 0) {
1732     if ((1 << sa->spacetype) & WM_TOOLSYSTEM_SPACE_MASK) {
1733       WM_toolsystem_refresh_screen_area(workspace, view_layer, sa);
1734       sa->flag |= AREA_FLAG_ACTIVE_TOOL_UPDATE;
1735     }
1736     else {
1737       sa->runtime.tool = NULL;
1738       sa->runtime.is_tool_set = true;
1739     }
1740   }
1741 }
1742
1743 static void region_update_rect(ARegion *ar)
1744 {
1745   ar->winx = BLI_rcti_size_x(&ar->winrct) + 1;
1746   ar->winy = BLI_rcti_size_y(&ar->winrct) + 1;
1747
1748   /* v2d mask is used to subtract scrollbars from a 2d view. Needs initialize here. */
1749   BLI_rcti_init(&ar->v2d.mask, 0, ar->winx - 1, 0, ar->winy - 1);
1750 }
1751
1752 /**
1753  * Call to move a popup window (keep OpenGL context free!)
1754  */
1755 void ED_region_update_rect(ARegion *ar)
1756 {
1757   region_update_rect(ar);
1758 }
1759
1760 /* externally called for floating regions like menus */
1761 void ED_region_init(ARegion *ar)
1762 {
1763   /* refresh can be called before window opened */
1764   region_subwindow(ar);
1765
1766   region_update_rect(ar);
1767 }
1768
1769 void ED_region_cursor_set(wmWindow *win, ScrArea *sa, ARegion *ar)
1770 {
1771   if (ar && sa && ar->type && ar->type->cursor) {
1772     ar->type->cursor(win, sa, ar);
1773   }
1774   else {
1775     if (WM_cursor_set_from_tool(win, sa, ar)) {
1776       return;
1777     }
1778     WM_cursor_set(win, CURSOR_STD);
1779   }
1780 }
1781
1782 /* for use after changing visibility of regions */
1783 void ED_region_visibility_change_update(bContext *C, ScrArea *sa, ARegion *ar)
1784 {
1785   if (ar->flag & RGN_FLAG_HIDDEN)
1786     WM_event_remove_handlers(C, &ar->handlers);
1787
1788   ED_area_initialize(CTX_wm_manager(C), CTX_wm_window(C), sa);
1789   ED_area_tag_redraw(sa);
1790 }
1791
1792 /* for quick toggle, can skip fades */
1793 void region_toggle_hidden(bContext *C, ARegion *ar, const bool do_fade)
1794 {
1795   ScrArea *sa = CTX_wm_area(C);
1796
1797   ar->flag ^= RGN_FLAG_HIDDEN;
1798
1799   if (do_fade && ar->overlap) {
1800     /* starts a timer, and in end calls the stuff below itself (region_sblend_invoke()) */
1801     ED_region_visibility_change_update_animated(C, sa, ar);
1802   }
1803   else {
1804     ED_region_visibility_change_update(C, sa, ar);
1805   }
1806 }
1807
1808 /* exported to all editors, uses fading default */
1809 void ED_region_toggle_hidden(bContext *C, ARegion *ar)
1810 {
1811   region_toggle_hidden(C, ar, true);
1812 }
1813
1814 /**
1815  * we swap spaces for fullscreen to keep all allocated data area vertices were set
1816  */
1817 void ED_area_data_copy(ScrArea *sa_dst, ScrArea *sa_src, const bool do_free)
1818 {
1819   SpaceType *st;
1820   ARegion *ar;
1821   const char spacetype = sa_dst->spacetype;
1822   const short flag_copy = HEADER_NO_PULLDOWN;
1823
1824   sa_dst->spacetype = sa_src->spacetype;
1825   sa_dst->type = sa_src->type;
1826
1827   sa_dst->flag = (sa_dst->flag & ~flag_copy) | (sa_src->flag & flag_copy);
1828
1829   /* area */
1830   if (do_free) {
1831     BKE_spacedata_freelist(&sa_dst->spacedata);
1832   }
1833   BKE_spacedata_copylist(&sa_dst->spacedata, &sa_src->spacedata);
1834
1835   /* Note; SPACE_EMPTY is possible on new screens */
1836
1837   /* regions */
1838   if (do_free) {
1839     st = BKE_spacetype_from_id(spacetype);
1840     for (ar = sa_dst->regionbase.first; ar; ar = ar->next)
1841       BKE_area_region_free(st, ar);
1842     BLI_freelistN(&sa_dst->regionbase);
1843   }
1844   st = BKE_spacetype_from_id(sa_src->spacetype);
1845   for (ar = sa_src->regionbase.first; ar; ar = ar->next) {
1846     ARegion *newar = BKE_area_region_copy(st, ar);
1847     BLI_addtail(&sa_dst->regionbase, newar);
1848   }
1849 }
1850
1851 void ED_area_data_swap(ScrArea *sa_dst, ScrArea *sa_src)
1852 {
1853   SWAP(char, sa_dst->spacetype, sa_src->spacetype);
1854   SWAP(SpaceType *, sa_dst->type, sa_src->type);
1855
1856   SWAP(ListBase, sa_dst->spacedata, sa_src->spacedata);
1857   SWAP(ListBase, sa_dst->regionbase, sa_src->regionbase);
1858 }
1859
1860 /* *********** Space switching code *********** */
1861
1862 void ED_area_swapspace(bContext *C, ScrArea *sa1, ScrArea *sa2)
1863 {
1864   ScrArea *tmp = MEM_callocN(sizeof(ScrArea), "addscrarea");
1865
1866   ED_area_exit(C, sa1);
1867   ED_area_exit(C, sa2);
1868
1869   ED_area_data_copy(tmp, sa1, false);
1870   ED_area_data_copy(sa1, sa2, true);
1871   ED_area_data_copy(sa2, tmp, true);
1872   ED_area_initialize(CTX_wm_manager(C), CTX_wm_window(C), sa1);
1873   ED_area_initialize(CTX_wm_manager(C), CTX_wm_window(C), sa2);
1874
1875   BKE_screen_area_free(tmp);
1876   MEM_freeN(tmp);
1877
1878   /* tell WM to refresh, cursor types etc */
1879   WM_event_add_mousemove(C);
1880
1881   ED_area_tag_redraw(sa1);
1882   ED_area_tag_refresh(sa1);
1883   ED_area_tag_redraw(sa2);
1884   ED_area_tag_refresh(sa2);
1885 }
1886
1887 /**
1888  * \param skip_ar_exit: Skip calling area exit callback. Set for opening temp spaces.
1889  */
1890 void ED_area_newspace(bContext *C, ScrArea *sa, int type, const bool skip_ar_exit)
1891 {
1892   wmWindow *win = CTX_wm_window(C);
1893
1894   if (sa->spacetype != type) {
1895     SpaceType *st;
1896     SpaceLink *slold;
1897     SpaceLink *sl;
1898     /* store sa->type->exit callback */
1899     void *sa_exit = sa->type ? sa->type->exit : NULL;
1900     /* When the user switches between space-types from the type-selector,
1901      * changing the header-type is jarring (especially when using Ctrl-MouseWheel).
1902      *
1903      * However, add-on install for example, forces the header to the top which shouldn't
1904      * be applied back to the previous space type when closing - see: T57724
1905      *
1906      * Newly created windows wont have any space data, use the alignment
1907      * the space type defaults to in this case instead
1908      * (needed for preferences to have space-type on bottom).
1909      */
1910     int header_alignment = ED_area_header_alignment_or_fallback(sa, -1);
1911     const bool sync_header_alignment = ((header_alignment != -1) &&
1912                                         (sa->flag & AREA_FLAG_TEMP_TYPE) == 0);
1913
1914     /* in some cases (opening temp space) we don't want to
1915      * call area exit callback, so we temporarily unset it */
1916     if (skip_ar_exit && sa->type) {
1917       sa->type->exit = NULL;
1918     }
1919
1920     ED_area_exit(C, sa);
1921
1922     /* restore old area exit callback */
1923     if (skip_ar_exit && sa->type) {
1924       sa->type->exit = sa_exit;
1925     }
1926
1927     st = BKE_spacetype_from_id(type);
1928     slold = sa->spacedata.first;
1929
1930     sa->spacetype = type;
1931     sa->type = st;
1932
1933     /* If st->new may be called, don't use context until then. The
1934      * sa->type->context() callback has changed but data may be invalid
1935      * (e.g. with properties editor) until space-data is properly created */
1936
1937     /* check previously stored space */
1938     for (sl = sa->spacedata.first; sl; sl = sl->next)
1939       if (sl->spacetype == type)
1940         break;
1941
1942     /* old spacedata... happened during work on 2.50, remove */
1943     if (sl && BLI_listbase_is_empty(&sl->regionbase)) {
1944       st->free(sl);
1945       BLI_freelinkN(&sa->spacedata, sl);
1946       if (slold == sl) {
1947         slold = NULL;
1948       }
1949       sl = NULL;
1950     }
1951
1952     if (sl) {
1953       /* swap regions */
1954       slold->regionbase = sa->regionbase;
1955       sa->regionbase = sl->regionbase;
1956       BLI_listbase_clear(&sl->regionbase);
1957
1958       /* put in front of list */
1959       BLI_remlink(&sa->spacedata, sl);
1960       BLI_addhead(&sa->spacedata, sl);
1961     }
1962     else {
1963       /* new space */
1964       if (st) {
1965         /* Don't get scene from context here which may depend on space-data. */
1966         Scene *scene = WM_window_get_active_scene(win);
1967         sl = st->new (sa, scene);
1968         BLI_addhead(&sa->spacedata, sl);
1969
1970         /* swap regions */
1971         if (slold)
1972           slold->regionbase = sa->regionbase;
1973         sa->regionbase = sl->regionbase;
1974         BLI_listbase_clear(&sl->regionbase);
1975       }
1976     }
1977
1978     /* Sync header alignment. */
1979     if (sync_header_alignment) {
1980       /* Spaces with footer. */
1981       if (st->spaceid == SPACE_TEXT) {
1982         for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
1983           if (ELEM(ar->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
1984             ar->alignment = header_alignment;
1985           }
1986           if (ar->regiontype == RGN_TYPE_FOOTER) {
1987             int footer_alignment = (header_alignment == RGN_ALIGN_BOTTOM) ? RGN_ALIGN_TOP :
1988                                                                             RGN_ALIGN_BOTTOM;
1989             ar->alignment = footer_alignment;
1990             break;
1991           }
1992         }
1993       }
1994       else {
1995         for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
1996           if (ELEM(ar->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
1997             ar->alignment = header_alignment;
1998             break;
1999           }
2000         }
2001       }
2002     }
2003
2004     ED_area_initialize(CTX_wm_manager(C), win, sa);
2005
2006     /* tell WM to refresh, cursor types etc */
2007     WM_event_add_mousemove(C);
2008
2009     /* send space change notifier */
2010     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_CHANGED, sa);
2011
2012     ED_area_tag_refresh(sa);
2013   }
2014
2015   /* also redraw when re-used */
2016   ED_area_tag_redraw(sa);
2017 }
2018
2019 void ED_area_prevspace(bContext *C, ScrArea *sa)
2020 {
2021   SpaceLink *sl = sa->spacedata.first;
2022
2023   if (sl && sl->next) {
2024     ED_area_newspace(C, sa, sl->next->spacetype, false);
2025
2026     /* keep old spacedata but move it to end, so calling
2027      * ED_area_prevspace once more won't open it again */
2028     BLI_remlink(&sa->spacedata, sl);
2029     BLI_addtail(&sa->spacedata, sl);
2030   }
2031   else {
2032     /* no change */
2033     return;
2034   }
2035   sa->flag &= ~(AREA_FLAG_STACKED_FULLSCREEN | AREA_FLAG_TEMP_TYPE);
2036
2037   ED_area_tag_redraw(sa);
2038
2039   /* send space change notifier */
2040   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_CHANGED, sa);
2041 }
2042
2043 /* returns offset for next button in header */
2044 int ED_area_header_switchbutton(const bContext *C, uiBlock *block, int yco)
2045 {
2046   ScrArea *sa = CTX_wm_area(C);
2047   bScreen *scr = CTX_wm_screen(C);
2048   PointerRNA areaptr;
2049   int xco = 0.4 * U.widget_unit;
2050
2051   RNA_pointer_create(&(scr->id), &RNA_Area, sa, &areaptr);
2052
2053   uiDefButR(block,
2054             UI_BTYPE_MENU,
2055             0,
2056             "",
2057             xco,
2058             yco,
2059             1.6 * U.widget_unit,
2060             U.widget_unit,
2061             &areaptr,
2062             "ui_type",
2063             0,
2064             0.0f,
2065             0.0f,
2066             0.0f,
2067             0.0f,
2068             "");
2069
2070   return xco + 1.7 * U.widget_unit;
2071 }
2072
2073 /************************ standard UI regions ************************/
2074
2075 static ThemeColorID region_background_color_id(const bContext *C, const ARegion *region)
2076 {
2077   ScrArea *area = CTX_wm_area(C);
2078
2079   switch (region->regiontype) {
2080     case RGN_TYPE_HEADER:
2081     case RGN_TYPE_TOOL_HEADER:
2082       if (ED_screen_area_active(C) || ED_area_is_global(area)) {
2083         return TH_HEADER;
2084       }
2085       else {
2086         return TH_HEADERDESEL;
2087       }
2088     case RGN_TYPE_PREVIEW:
2089       return TH_PREVIEW_BACK;
2090     default:
2091       return TH_BACK;
2092   }
2093 }
2094
2095 static void region_clear_color(const bContext *C, const ARegion *ar, ThemeColorID colorid)
2096 {
2097   if (ar->alignment == RGN_ALIGN_FLOAT) {
2098     /* handle our own drawing. */
2099   }
2100   else if (ar->overlap) {
2101     /* view should be in pixelspace */
2102     UI_view2d_view_restore(C);
2103
2104     float back[4];
2105     UI_GetThemeColor4fv(colorid, back);
2106     GPU_clear_color(back[3] * back[0], back[3] * back[1], back[3] * back[2], back[3]);
2107     GPU_clear(GPU_COLOR_BIT);
2108   }
2109   else {
2110     UI_ThemeClearColor(colorid);
2111     GPU_clear(GPU_COLOR_BIT);
2112   }
2113 }
2114
2115 BLI_INLINE bool streq_array_any(const char *s, const char *arr[])
2116 {
2117   for (uint i = 0; arr[i]; i++) {
2118     if (STREQ(arr[i], s)) {
2119       return true;
2120     }
2121   }
2122   return false;
2123 }
2124
2125 static void ed_panel_draw(const bContext *C,
2126                           ScrArea *sa,
2127                           ARegion *ar,
2128                           ListBase *lb,
2129                           PanelType *pt,
2130                           Panel *panel,
2131                           int w,
2132                           int em,
2133                           bool vertical)
2134 {
2135   uiStyle *style = UI_style_get_dpi();
2136
2137   /* draw panel */
2138   uiBlock *block = UI_block_begin(C, ar, pt->idname, UI_EMBOSS);
2139
2140   bool open;
2141   panel = UI_panel_begin(sa, ar, lb, block, pt, panel, &open);
2142
2143   /* bad fixed values */
2144   int xco, yco, h = 0;
2145
2146   if (pt->draw_header_preset && !(pt->flag & PNL_NO_HEADER) && (open || vertical)) {
2147     /* for preset menu */
2148     panel->layout = UI_block_layout(block,
2149                                     UI_LAYOUT_HORIZONTAL,
2150                                     UI_LAYOUT_HEADER,
2151                                     0,
2152                                     (UI_UNIT_Y * 1.1f) + style->panelspace,
2153                                     UI_UNIT_Y,
2154                                     1,
2155                                     0,
2156                                     style);
2157
2158     pt->draw_header_preset(C, panel);
2159
2160     int headerend = w - UI_UNIT_X;
2161
2162     UI_block_layout_resolve(block, &xco, &yco);
2163     UI_block_translate(block, headerend - xco, 0);
2164     panel->layout = NULL;
2165   }
2166
2167   if (pt->draw_header && !(pt->flag & PNL_NO_HEADER) && (open || vertical)) {
2168     int labelx, labely;
2169     UI_panel_label_offset(block, &labelx, &labely);
2170
2171     /* for enabled buttons */
2172     panel->layout = UI_block_layout(
2173         block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, labelx, labely, UI_UNIT_Y, 1, 0, style);
2174
2175     pt->draw_header(C, panel);
2176
2177     UI_block_layout_resolve(block, &xco, &yco);
2178     panel->labelofs = xco - labelx;
2179     panel->layout = NULL;
2180   }
2181   else {
2182     panel->labelofs = 0;
2183   }
2184
2185   if (open) {
2186     short panelContext;
2187
2188     /* panel context can either be toolbar region or normal panels region */
2189     if (pt->flag & PNL_LAYOUT_VERT_BAR) {
2190       panelContext = UI_LAYOUT_VERT_BAR;
2191     }
2192     else if (ar->regiontype == RGN_TYPE_TOOLS) {
2193       panelContext = UI_LAYOUT_TOOLBAR;
2194     }
2195     else {
2196       panelContext = UI_LAYOUT_PANEL;
2197     }
2198
2199     panel->layout = UI_block_layout(block,
2200                                     UI_LAYOUT_VERTICAL,
2201                                     panelContext,
2202                                     (pt->flag & PNL_LAYOUT_VERT_BAR) ? 0 : style->panelspace,
2203                                     0,
2204                                     (pt->flag & PNL_LAYOUT_VERT_BAR) ? 0 :
2205                                                                        w - 2 * style->panelspace,
2206                                     em,
2207                                     0,
2208                                     style);
2209
2210     pt->draw(C, panel);
2211
2212     UI_block_layout_resolve(block, &xco, &yco);
2213     panel->layout = NULL;
2214
2215     if (yco != 0) {
2216       h = -yco + 2 * style->panelspace;
2217     }
2218   }
2219
2220   UI_block_end(C, block);
2221
2222   /* Draw child panels. */
2223   if (open) {
2224     for (LinkData *link = pt->children.first; link; link = link->next) {
2225       PanelType *child_pt = link->data;
2226       Panel *child_panel = UI_panel_find_by_type(&panel->children, child_pt);
2227
2228       if (child_pt->draw && (!child_pt->poll || child_pt->poll(C, child_pt))) {
2229         ed_panel_draw(C, sa, ar, &panel->children, child_pt, child_panel, w, em, vertical);
2230       }
2231     }
2232   }
2233
2234   UI_panel_end(block, w, h);
2235 }
2236
2237 /**
2238  * \param contexts: A NULL terminated array of context strings to match against.
2239  * Matching against any of these strings will draw the panel.
2240  * Can be NULL to skip context checks.
2241  */
2242 void ED_region_panels_layout_ex(
2243     const bContext *C, ARegion *ar, const char *contexts[], int contextnr, const bool vertical)
2244 {
2245   ar->runtime.category = NULL;
2246
2247   const WorkSpace *workspace = CTX_wm_workspace(C);
2248   ScrArea *sa = CTX_wm_area(C);
2249   PanelType *pt;
2250   View2D *v2d = &ar->v2d;
2251   int x, y, w, em;
2252   bool is_context_new = 0;
2253   int scroll;
2254
2255   /* XXX, should use some better check? */
2256   /* For now also has hardcoded check for clip editor until it supports actual toolbar. */
2257   bool use_category_tabs = ((1 << ar->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) ||
2258                            (ar->regiontype == RGN_TYPE_TOOLS && sa->spacetype == SPACE_CLIP);
2259   /* offset panels for small vertical tab area */
2260   const char *category = NULL;
2261   const int category_tabs_width = UI_PANEL_CATEGORY_MARGIN_WIDTH;
2262   int margin_x = 0;
2263   const bool region_layout_based = ar->flag & RGN_FLAG_DYNAMIC_SIZE;
2264
2265   BLI_SMALLSTACK_DECLARE(pt_stack, PanelType *);
2266
2267   if (contextnr != -1)
2268     is_context_new = UI_view2d_tab_set(v2d, contextnr);
2269
2270   /* before setting the view */
2271   if (vertical) {
2272     /* only allow scrolling in vertical direction */
2273     v2d->keepofs |= V2D_LOCKOFS_X | V2D_KEEPOFS_Y;
2274     v2d->keepofs &= ~(V2D_LOCKOFS_Y | V2D_KEEPOFS_X);
2275     v2d->scroll &= ~(V2D_SCROLL_BOTTOM);
2276     v2d->scroll |= (V2D_SCROLL_RIGHT);
2277   }
2278   else {
2279     /* for now, allow scrolling in both directions (since layouts are optimized for vertical,
2280      * they often don't fit in horizontal layout)
2281      */
2282     v2d->keepofs &= ~(V2D_LOCKOFS_X | V2D_LOCKOFS_Y | V2D_KEEPOFS_X | V2D_KEEPOFS_Y);
2283     v2d->scroll |= (V2D_SCROLL_BOTTOM);
2284     v2d->scroll &= ~(V2D_SCROLL_RIGHT);
2285   }
2286
2287   scroll = v2d->scroll;
2288
2289   /* collect panels to draw */
2290   for (pt = ar->type->paneltypes.last; pt; pt = pt->prev) {
2291     /* Only draw top level panels. */
2292     if (pt->parent) {
2293       continue;
2294     }
2295
2296     /* verify context */
2297     if (contexts && pt->context[0] && !streq_array_any(pt->context, contexts)) {
2298       continue;
2299     }
2300
2301     /* If we're tagged, only use compatible. */
2302     if (pt->owner_id[0] && BKE_workspace_owner_id_check(workspace, pt->owner_id) == false) {
2303       continue;
2304     }
2305
2306     /* draw panel */
2307     if (pt->draw && (!pt->poll || pt->poll(C, pt))) {
2308       BLI_SMALLSTACK_PUSH(pt_stack, pt);
2309     }
2310   }
2311
2312   /* collect categories */
2313   if (use_category_tabs) {
2314     UI_panel_category_clear_all(ar);
2315
2316     /* gather unique categories */
2317     BLI_SMALLSTACK_ITER_BEGIN (pt_stack, pt) {
2318       if (pt->category[0]) {
2319         if (!UI_panel_category_find(ar, pt->category)) {
2320           UI_panel_category_add(ar, pt->category);
2321         }
2322       }
2323     }
2324     BLI_SMALLSTACK_ITER_END;
2325
2326     if (!UI_panel_category_is_visible(ar)) {
2327       use_category_tabs = false;
2328     }
2329     else {
2330       category = UI_panel_category_active_get(ar, true);
2331       margin_x = category_tabs_width;
2332     }
2333   }
2334
2335   if (vertical) {
2336     w = BLI_rctf_size_x(&v2d->cur);
2337     em = (ar->type->prefsizex) ? 10 : 20; /* works out to 10*UI_UNIT_X or 20*UI_UNIT_X */
2338   }
2339   else {
2340     w = UI_PANEL_WIDTH;
2341     em = (ar->type->prefsizex) ? 10 : 20;
2342   }
2343
2344   w -= margin_x;
2345
2346   /* create panels */
2347   UI_panels_begin(C, ar);
2348
2349   /* set view2d view matrix  - UI_block_begin() stores it */
2350   UI_view2d_view_ortho(v2d);
2351
2352   BLI_SMALLSTACK_ITER_BEGIN (pt_stack, pt) {
2353     Panel *panel = UI_panel_find_by_type(&ar->panels, pt);
2354
2355     if (use_category_tabs && pt->category[0] && !STREQ(category, pt->category)) {
2356       if ((panel == NULL) || ((panel->flag & PNL_PIN) == 0)) {
2357         continue;
2358       }
2359     }
2360
2361     ed_panel_draw(C, sa, ar, &ar->panels, pt, panel, w, em, vertical);
2362   }
2363   BLI_SMALLSTACK_ITER_END;
2364
2365   /* align panels and return size */
2366   UI_panels_end(C, ar, &x, &y);
2367
2368   /* before setting the view */
2369   if (region_layout_based) {
2370     /* XXX, only single panel support atm.
2371      * Can't use x/y values calculated above because they're not using the real height of panels,
2372      * instead they calculate offsets for the next panel to start drawing. */
2373     Panel *panel = ar->panels.last;
2374     if (panel != NULL) {
2375       int size_dyn[2] = {
2376           UI_UNIT_X * ((panel->flag & PNL_CLOSED) ? 8 : 14) / UI_DPI_FAC,
2377           UI_panel_size_y(panel) / UI_DPI_FAC,
2378       };
2379       /* region size is layout based and needs to be updated */
2380       if ((ar->sizex != size_dyn[0]) || (ar->sizey != size_dyn[1])) {
2381         ar->sizex = size_dyn[0];
2382         ar->sizey = size_dyn[1];
2383         sa->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
2384       }
2385       y = ABS(ar->sizey * UI_DPI_FAC - 1);
2386     }
2387   }
2388   else if (vertical) {
2389     /* we always keep the scroll offset - so the total view gets increased with the scrolled away part */
2390     if (v2d->cur.ymax < -FLT_EPSILON) {
2391       /* Clamp to lower view boundary */
2392       if (v2d->tot.ymin < -v2d->winy) {
2393         y = min_ii(y, 0);
2394       }
2395       else {
2396         y = min_ii(y, v2d->cur.ymin);
2397       }
2398     }
2399
2400     y = -y;
2401   }
2402   else {
2403     /* don't jump back when panels close or hide */
2404     if (!is_context_new) {
2405       if (v2d->tot.xmax > v2d->winx) {
2406         x = max_ii(x, 0);
2407       }
2408       else {
2409         x = max_ii(x, v2d->cur.xmax);
2410       }
2411     }
2412
2413     y = -y;
2414   }
2415
2416   /* this also changes the 'cur' */
2417   UI_view2d_totRect_set(v2d, x, y);
2418
2419   if (scroll != v2d->scroll) {
2420     /* Note: this code scales fine, but because of rounding differences, positions of elements
2421      * flip +1 or -1 pixel compared to redoing the entire layout again.
2422      * Leaving in commented code for future tests */
2423 #if 0
2424     UI_panels_scale(ar, BLI_rctf_size_x(&v2d->cur));
2425     break;
2426 #endif
2427   }
2428
2429   if (use_category_tabs) {
2430     ar->runtime.category = category;
2431   }
2432 }
2433 void ED_region_panels_layout(const bContext *C, ARegion *ar)
2434 {
2435   ED_region_panels_layout_ex(C, ar, NULL, -1, true);
2436 }
2437
2438 void ED_region_panels_draw(const bContext *C, ARegion *ar)
2439 {
2440   View2D *v2d = &ar->v2d;
2441
2442   if (ar->alignment != RGN_ALIGN_FLOAT) {
2443     region_clear_color(
2444         C, ar, (ar->type->regionid == RGN_TYPE_PREVIEW) ? TH_PREVIEW_BACK : TH_BACK);
2445   }
2446
2447   /* reset line width for drawing tabs */
2448   GPU_line_width(1.0f);
2449
2450   /* set the view */
2451   UI_view2d_view_ortho(v2d);
2452
2453   /* View2D matrix might have changed due to dynamic sized regions. */
2454   UI_blocklist_update_window_matrix(C, &ar->uiblocks);
2455
2456   /* draw panels */
2457   UI_panels_draw(C, ar);
2458
2459   /* restore view matrix */
2460   UI_view2d_view_restore(C);
2461
2462   /* Set in layout. */
2463   if (ar->runtime.category) {
2464     UI_panel_category_draw_all(ar, ar->runtime.category);
2465   }
2466
2467   /* scrollers */
2468   const rcti *mask = NULL;
2469   rcti mask_buf;
2470   if (ar->runtime.category && (ar->alignment == RGN_ALIGN_RIGHT)) {
2471     UI_view2d_mask_from_win(v2d, &mask_buf);
2472     mask_buf.xmax -= UI_PANEL_CATEGORY_MARGIN_WIDTH;
2473     mask = &mask_buf;
2474   }
2475   View2DScrollers *scrollers = UI_view2d_scrollers_calc(
2476       C, v2d, mask, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
2477   UI_view2d_scrollers_draw(C, v2d, scrollers);
2478   UI_view2d_scrollers_free(scrollers);
2479 }
2480
2481 void ED_region_panels_ex(
2482     const bContext *C, ARegion *ar, const char *contexts[], int contextnr, const bool vertical)
2483 {
2484   /* TODO: remove? */
2485   ED_region_panels_layout_ex(C, ar, contexts, contextnr, vertical);
2486   ED_region_panels_draw(C, ar);
2487 }
2488
2489 void ED_region_panels(const bContext *C, ARegion *ar)
2490 {
2491   /* TODO: remove? */
2492   ED_region_panels_layout(C, ar);
2493   ED_region_panels_draw(C, ar);
2494 }
2495
2496 void ED_region_panels_init(wmWindowManager *wm, ARegion *ar)
2497 {
2498   wmKeyMap *keymap;
2499
2500   UI_view2d_region_reinit(&ar->v2d, V2D_COMMONVIEW_PANELS_UI, ar->winx, ar->winy);
2501
2502   keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0);
2503   WM_event_add_keymap_handler(&ar->handlers, keymap);
2504 }
2505
2506 void ED_region_header_layout(const bContext *C, ARegion *ar)
2507 {
2508   uiStyle *style = UI_style_get_dpi();
2509   uiBlock *block;
2510   uiLayout *layout;
2511   HeaderType *ht;
2512   Header header = {NULL};
2513   bool region_layout_based = ar->flag & RGN_FLAG_DYNAMIC_SIZE;
2514
2515   /* Height of buttons and scaling needed to achieve it. */
2516   const int buttony = min_ii(UI_UNIT_Y, ar->winy - 2 * UI_DPI_FAC);
2517   const float buttony_scale = buttony / (float)UI_UNIT_Y;
2518
2519   /* Vertically center buttons. */
2520   int xco = UI_HEADER_OFFSET;
2521   int yco = buttony + (ar->winy - buttony) / 2;
2522   int maxco = xco;
2523
2524   /* XXX workaround for 1 px alignment issue. Not sure what causes it... Would prefer a proper fix - Julian */
2525   if (!ELEM(CTX_wm_area(C)->spacetype, SPACE_TOPBAR, SPACE_STATUSBAR)) {
2526     yco -= 1;
2527   }
2528
2529   /* set view2d view matrix for scrolling (without scrollers) */
2530   UI_view2d_view_ortho(&ar->v2d);
2531
2532   /* draw all headers types */
2533   for (ht = ar->type->headertypes.first; ht; ht = ht->next) {
2534     if (ht->poll && !ht->poll(C, ht)) {
2535       continue;
2536     }
2537
2538     block = UI_block_begin(C, ar, ht->idname, UI_EMBOSS);
2539     layout = UI_block_layout(
2540         block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, xco, yco, buttony, 1, 0, style);
2541
2542     if (buttony_scale != 1.0f) {
2543       uiLayoutSetScaleY(layout, buttony_scale);
2544     }
2545
2546     if (ht->draw) {
2547       header.type = ht;
2548       header.layout = layout;
2549       ht->draw(C, &header);
2550       if (ht->next) {
2551         uiItemS(layout);
2552       }
2553
2554       /* for view2d */
2555       xco = uiLayoutGetWidth(layout);
2556       if (xco > maxco)
2557         maxco = xco;
2558     }
2559
2560     UI_block_layout_resolve(block, &xco, &yco);
2561
2562     /* for view2d */
2563     if (xco > maxco)
2564       maxco = xco;
2565
2566     int new_sizex = (maxco + UI_HEADER_OFFSET) / UI_DPI_FAC;
2567
2568     if (region_layout_based && (ar->sizex != new_sizex)) {
2569       /* region size is layout based and needs to be updated */
2570       ScrArea *sa = CTX_wm_area(C);
2571
2572       ar->sizex = new_sizex;
2573       sa->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
2574     }
2575
2576     UI_block_end(C, block);
2577   }
2578
2579   if (!region_layout_based) {
2580     maxco += UI_HEADER_OFFSET;
2581   }
2582
2583   /* always as last  */
2584   UI_view2d_totRect_set(&ar->v2d, maxco, ar->winy);
2585
2586   /* restore view matrix */
2587   UI_view2d_view_restore(C);
2588 }
2589
2590 void ED_region_header_draw(const bContext *C, ARegion *ar)
2591 {
2592   /* clear */
2593   region_clear_color(C, ar, region_background_color_id(C, ar));
2594
2595   UI_view2d_view_ortho(&ar->v2d);
2596
2597   /* View2D matrix might have changed due to dynamic sized regions. */
2598   UI_blocklist_update_window_matrix(C, &ar->uiblocks);
2599
2600   /* draw blocks */
2601   UI_blocklist_draw(C, &ar->uiblocks);
2602
2603   /* restore view matrix */
2604   UI_view2d_view_restore(C);
2605 }
2606
2607 void ED_region_header(const bContext *C, ARegion *ar)
2608 {
2609   /* TODO: remove? */
2610   ED_region_header_layout(C, ar);
2611   ED_region_header_draw(C, ar);
2612 }
2613
2614 void ED_region_header_init(ARegion *ar)
2615 {
2616   UI_view2d_region_reinit(&ar->v2d, V2D_COMMONVIEW_HEADER, ar->winx, ar->winy);
2617 }
2618
2619 int ED_area_headersize(void)
2620 {
2621   /* Accomodate widget and padding. */
2622   return U.widget_unit + (int)(UI_DPI_FAC * HEADER_PADDING_Y);
2623 }
2624
2625 int ED_area_header_alignment_or_fallback(const ScrArea *area, int fallback)
2626 {
2627   for (ARegion *ar = area->regionbase.first; ar; ar = ar->next) {
2628     if (ar->regiontype == RGN_TYPE_HEADER) {
2629       return ar->alignment;
2630     }
2631   }
2632   return fallback;
2633 }
2634
2635 int ED_area_header_alignment(const ScrArea *area)
2636 {
2637   return ED_area_header_alignment_or_fallback(
2638       area, (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP);
2639 }
2640
2641 int ED_area_footersize(void)
2642 {
2643   return ED_area_headersize();
2644 }
2645
2646 int ED_area_footer_alignment_or_fallback(const ScrArea *area, int fallback)
2647 {
2648   for (ARegion *ar = area->regionbase.first; ar; ar = ar->next) {
2649     if (ar->regiontype == RGN_TYPE_FOOTER) {
2650       return ar->alignment;
2651     }
2652   }
2653   return fallback;
2654 }
2655
2656 int ED_area_footer_alignment(const ScrArea *area)
2657 {
2658   return ED_area_footer_alignment_or_fallback(
2659       area, (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_TOP : RGN_ALIGN_BOTTOM);
2660 }
2661
2662 /**
2663  * \return the final height of a global \a area, accounting for DPI.
2664  */
2665 int ED_area_global_size_y(const ScrArea *area)
2666 {
2667   BLI_assert(ED_area_is_global(area));
2668   return round_fl_to_int(area->global->cur_fixed_height * UI_DPI_FAC);
2669 }
2670 int ED_area_global_min_size_y(const ScrArea *area)
2671 {
2672   BLI_assert(ED_area_is_global(area));
2673   return round_fl_to_int(area->global->size_min * UI_DPI_FAC);
2674 }
2675 int ED_area_global_max_size_y(const ScrArea *area)
2676 {
2677   BLI_assert(ED_area_is_global(area));
2678   return round_fl_to_int(area->global->size_max * UI_DPI_FAC);
2679 }
2680
2681 bool ED_area_is_global(const ScrArea *area)
2682 {
2683   return area->global != NULL;
2684 }
2685
2686 ScrArea *ED_screen_areas_iter_first(const wmWindow *win, const bScreen *screen)
2687 {
2688   ScrArea *global_area = win->global_areas.areabase.first;
2689
2690   if (!global_area) {
2691     return screen->areabase.first;
2692   }
2693   else if ((global_area->global->flag & GLOBAL_AREA_IS_HIDDEN) == 0) {
2694     return global_area;
2695   }
2696   /* Find next visible area. */
2697   return ED_screen_areas_iter_next(screen, global_area);
2698 }
2699 ScrArea *ED_screen_areas_iter_next(const bScreen *screen, const ScrArea *area)
2700 {
2701   if (area->global) {
2702     for (ScrArea *area_iter = area->next; area_iter; area_iter = area_iter->next) {
2703       if ((area_iter->global->flag & GLOBAL_AREA_IS_HIDDEN) == 0) {
2704         return area_iter;
2705       }
2706     }
2707     /* No visible next global area found, start iterating over layout areas. */
2708     return screen->areabase.first;
2709   }
2710
2711   return area->next;
2712 }
2713
2714 /**
2715  * For now we just assume all global areas are made up out of horizontal bars
2716  * with the same size. A fixed size could be stored in ARegion instead if needed.
2717  *
2718  * \return the DPI aware height of a single bar/region in global areas.
2719  */
2720 int ED_region_global_size_y(void)
2721 {
2722   return ED_area_headersize(); /* same size as header */
2723 }
2724
2725 void ED_region_info_draw_multiline(ARegion *ar,
2726                                    const char *text_array[],
2727                                    float fill_color[4],
2728                                    const bool full_redraw)
2729 {
2730   const int header_height = UI_UNIT_Y;
2731   uiStyle *style = UI_style_get_dpi();
2732   int fontid = style->widget.uifont_id;
2733   int scissor[4];
2734   rcti rect;
2735   int num_lines = 0;
2736
2737   /* background box */
2738   ED_region_visible_rect(ar, &rect);
2739
2740   /* Box fill entire width or just around text. */
2741   if (!full_redraw) {
2742     const char **text = &text_array[0];
2743     while (*text) {
2744       rect.xmax = min_ii(rect.xmax,
2745                          rect.xmin + BLF_width(fontid, *text, BLF_DRAW_STR_DUMMY_MAX) +
2746                              1.2f * U.widget_unit);
2747       text++;
2748       num_lines++;
2749     }
2750   }
2751   /* Just count the line number. */
2752   else {
2753     const char **text = &text_array[0];
2754     while (*text) {
2755       text++;
2756       num_lines++;
2757     }
2758   }
2759
2760   rect.ymin = rect.ymax - header_height * num_lines;
2761
2762   /* setup scissor */
2763   GPU_scissor_get_i(scissor);
2764   GPU_scissor(rect.xmin, rect.ymin, BLI_rcti_size_x(&rect) + 1, BLI_rcti_size_y(&rect) + 1);
2765
2766   GPU_blend(true);
2767   GPU_blend_set_func_separate(
2768       GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
2769   GPUVertFormat *format = immVertexFormat();
2770   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
2771   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
2772   immUniformColor4fv(fill_color);
2773   immRecti(pos, rect.xmin, rect.ymin, rect.xmax + 1, rect.ymax + 1);
2774   immUnbindProgram();
2775   GPU_blend(false);
2776
2777   /* text */
2778   UI_FontThemeColor(fontid, TH_TEXT_HI);
2779   BLF_clipping(fontid, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
2780   BLF_enable(fontid, BLF_CLIPPING);
2781   int offset = num_lines - 1;
2782   {
2783     const char **text = &text_array[0];
2784     while (*text) {
2785       BLF_position(fontid,
2786                    rect.xmin + 0.6f * U.widget_unit,
2787                    rect.ymin + 0.3f * U.widget_unit + offset * header_height,
2788                    0.0f);
2789       BLF_draw(fontid, *text, BLF_DRAW_STR_DUMMY_MAX);
2790       text++;
2791       offset--;
2792     }
2793   }
2794
2795   BLF_disable(fontid, BLF_CLIPPING);
2796
2797   /* restore scissor as it was before */
2798   GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]);
2799 }
2800
2801 void ED_region_info_draw(ARegion *ar,
2802                          const char *text,
2803                          float fill_color[4],
2804                          const bool full_redraw)
2805 {
2806   ED_region_info_draw_multiline(ar, (const char * [2]){text, NULL}, fill_color, full_redraw);
2807 }
2808
2809 #define MAX_METADATA_STR 1024
2810
2811 static const char *meta_data_list[] = {
2812     "File",
2813     "Strip",
2814     "Date",
2815     "RenderTime",
2816     "Note",
2817     "Marker",
2818     "Time",
2819     "Frame",
2820     "Camera",
2821     "Scene",
2822 };
2823
2824 BLI_INLINE bool metadata_is_valid(ImBuf *ibuf, char *r_str, short index, int offset)
2825 {
2826   return (IMB_metadata_get_field(
2827               ibuf->metadata, meta_data_list[index], r_str + offset, MAX_METADATA_STR - offset) &&
2828           r_str[0]);
2829 }
2830
2831 BLI_INLINE bool metadata_is_custom_drawable(const char *field)
2832 {
2833   /* Metadata field stored by Blender for multilayer EXR images. Is rather
2834    * useless to be viewed all the time. Can still be seen in the Metadata
2835    * panel. */
2836   if (STREQ(field, "BlenderMultiChannel")) {
2837     return false;
2838   }
2839   /* Is almost always has value "scanlineimage", also useless to be seen
2840    * all the time. */
2841   if (STREQ(field, "type")) {
2842     return false;
2843   }
2844   return !BKE_stamp_is_known_field(field);
2845 }
2846
2847 typedef struct MetadataCustomDrawContext {
2848   int fontid;
2849   int xmin, ymin;
2850   int vertical_offset;
2851   int current_y;
2852 } MetadataCustomDrawContext;
2853
2854 static void metadata_custom_draw_fields(const char *field, const char *value, void *ctx_v)
2855 {
2856   if (!metadata_is_custom_drawable(field)) {
2857     return;
2858   }
2859   MetadataCustomDrawContext *ctx = (MetadataCustomDrawContext *)ctx_v;
2860   char temp_str[MAX_METADATA_STR];
2861   BLI_snprintf(temp_str, MAX_METADATA_STR, "%s: %s", field, value);
2862   BLF_position(ctx->fontid, ctx->xmin, ctx->ymin + ctx->current_y, 0.0f);
2863   BLF_draw(ctx->fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2864   ctx->current_y += ctx->vertical_offset;
2865 }
2866
2867 static void metadata_draw_imbuf(ImBuf *ibuf, const rctf *rect, int fontid, const bool is_top)
2868 {
2869   char temp_str[MAX_METADATA_STR];
2870   int line_width;
2871   int ofs_y = 0;
2872   short i;
2873   int len;
2874   const float height = BLF_height_max(fontid);
2875   const float margin = height / 8;
2876   const float vertical_offset = (height + margin);
2877
2878   /* values taking margins into account */
2879   const float descender = BLF_descender(fontid);
2880   const float xmin = (rect->xmin + margin);
2881   const float xmax = (rect->xmax - margin);
2882   const float ymin = (rect->ymin + margin) - descender;
2883   const float ymax = (rect->ymax - margin) - descender;
2884
2885   if (is_top) {
2886     for (i = 0; i < 4; i++) {
2887       /* first line */
2888       if (i == 0) {
2889         bool do_newline = false;
2890         len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[0]);
2891         if (metadata_is_valid(ibuf, temp_str, 0, len)) {
2892           BLF_position(fontid, xmin, ymax - vertical_offset, 0.0f);
2893           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2894           do_newline = true;
2895         }
2896
2897         len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[1]);
2898         if (metadata_is_valid(ibuf, temp_str, 1, len)) {
2899           line_width = BLF_width(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2900           BLF_position(fontid, xmax - line_width, ymax - vertical_offset, 0.0f);
2901           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2902           do_newline = true;
2903         }
2904
2905         if (do_newline)
2906           ofs_y += vertical_offset;
2907       } /* Strip */
2908       else if (i == 1 || i == 2) {
2909         len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i + 1]);
2910         if (metadata_is_valid(ibuf, temp_str, i + 1, len)) {
2911           BLF_position(fontid, xmin, ymax - vertical_offset - ofs_y, 0.0f);
2912           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2913           ofs_y += vertical_offset;
2914         }
2915       } /* Note (wrapped) */
2916       else if (i == 3) {
2917         len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i + 1]);
2918         if (metadata_is_valid(ibuf, temp_str, i + 1, len)) {
2919           struct ResultBLF info;
2920           BLF_enable(fontid, BLF_WORD_WRAP);
2921           BLF_wordwrap(fontid, ibuf->x - (margin * 2));
2922           BLF_position(fontid, xmin, ymax - vertical_offset - ofs_y, 0.0f);
2923           BLF_draw_ex(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX, &info);
2924           BLF_wordwrap(fontid, 0);
2925           BLF_disable(fontid, BLF_WORD_WRAP);
2926           ofs_y += vertical_offset * info.lines;
2927         }
2928       }
2929       else {
2930         len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i + 1]);
2931         if (metadata_is_valid(ibuf, temp_str, i + 1, len)) {
2932           line_width = BLF_width(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2933           BLF_position(fontid, xmax - line_width, ymax - vertical_offset - ofs_y, 0.0f);
2934           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2935           ofs_y += vertical_offset;
2936         }
2937       }
2938     }
2939   }
2940   else {
2941     MetadataCustomDrawContext ctx;
2942     ctx.fontid = fontid;
2943     ctx.xmin = xmin;
2944     ctx.ymin = ymin;
2945     ctx.vertical_offset = vertical_offset;
2946     ctx.current_y = ofs_y;
2947     ctx.vertical_offset = vertical_offset;
2948     IMB_metadata_foreach(ibuf, metadata_custom_draw_fields, &ctx);
2949     int ofs_x = 0;
2950     ofs_y = ctx.current_y;
2951     for (i = 5; i < 10; i++) {
2952       len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i]);
2953       if (metadata_is_valid(ibuf, temp_str, i, len)) {
2954         BLF_position(fontid, xmin + ofs_x, ymin + ofs_y, 0.0f);
2955         BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
2956
2957         ofs_x += BLF_width(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX) + UI_UNIT_X;
2958       }
2959     }
2960   }
2961 }
2962
2963 typedef struct MetadataCustomCountContext {
2964   int count;
2965 } MetadataCustomCountContext;
2966
2967 static void metadata_custom_count_fields(const char *field, const char *UNUSED(value), void *ctx_v)
2968 {
2969   if (!metadata_is_custom_drawable(field)) {
2970     return;
2971   }
2972   MetadataCustomCountContext *ctx = (MetadataCustomCountContext *)ctx_v;
2973   ctx->count++;
2974 }
2975
2976 static float metadata_box_height_get(ImBuf *ibuf, int fontid, const bool is_top)
2977 {
2978   const float height = BLF_height_max(fontid);
2979   const float margin = (height / 8);
2980   char str[MAX_METADATA_STR] = "";
2981   short i, count = 0;
2982
2983   if (is_top) {
2984     if (metadata_is_valid(ibuf, str, 0, 0) || metadata_is_valid(ibuf, str, 1, 0)) {
2985       count++;
2986     }
2987     for (i = 2; i < 5; i++) {
2988       if (metadata_is_valid(ibuf, str, i, 0)) {
2989         if (i == 4) {
2990           struct {
2991             struct ResultBLF info;
2992             rctf rect;
2993           } wrap;
2994
2995           BLF_enable(fontid, BLF_WORD_WRAP);
2996           BLF_wordwrap(fontid, ibuf->x - (margin * 2));
2997           BLF_boundbox_ex(fontid, str, sizeof(str), &wrap.rect, &wrap.info);
2998           BLF_wordwrap(fontid, 0);
2999           BLF_disable(fontid, BLF_WORD_WRAP);
3000
3001           count += wrap.info.lines;
3002         }
3003         else {
3004           count++;
3005         }
3006       }
3007     }
3008   }
3009   else {
3010     for (i = 5; i < 10; i++) {
3011       if (metadata_is_valid(ibuf, str, i, 0)) {
3012         count = 1;
3013         break;
3014       }
3015     }
3016     MetadataCustomCountContext ctx;
3017     ctx.count = 0;
3018     IMB_metadata_foreach(ibuf, metadata_custom_count_fields, &ctx);
3019     count += ctx.count;
3020   }
3021
3022   if (count) {
3023     return (height + margin) * count;
3024   }
3025
3026   return 0;
3027 }
3028
3029 #undef MAX_METADATA_STR
3030
3031 void ED_region_image_metadata_draw(
3032     int x, int y, ImBuf *ibuf, const rctf *frame, float zoomx, float zoomy)
3033 {
3034   float box_y;
3035   rctf rect;
3036   uiStyle *style = UI_style_get_dpi();
3037
3038   if (!ibuf->metadata)
3039     return;
3040
3041   /* find window pixel coordinates of origin */
3042   GPU_matrix_push();
3043
3044   /* offset and zoom using ogl */
3045   GPU_matrix_translate_2f(x, y);
3046   GPU_matrix_scale_2f(zoomx, zoomy);
3047
3048   BLF_size(blf_mono_font, style->widgetlabel.points * 1.5f * U.pixelsize, U.dpi);
3049
3050   /* *** upper box*** */
3051
3052   /* get needed box height */
3053   box_y = metadata_box_height_get(ibuf, blf_mono_font, true);
3054
3055   if (box_y) {
3056     /* set up rect */
3057     BLI_rctf_init(&rect, frame->xmin, frame->xmax, frame->ymax, frame->ymax + box_y);
3058     /* draw top box */
3059     GPUVertFormat *format = immVertexFormat();
3060     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3061     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3062     immUniformThemeColor(TH_METADATA_BG);
3063     immRectf(pos, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3064     immUnbindProgram();
3065
3066     BLF_clipping(blf_mono_font, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3067     BLF_enable(blf_mono_font, BLF_CLIPPING);
3068
3069     UI_FontThemeColor(blf_mono_font, TH_METADATA_TEXT);
3070     metadata_draw_imbuf(ibuf, &rect, blf_mono_font, true);
3071
3072     BLF_disable(blf_mono_font, BLF_CLIPPING);
3073   }
3074
3075   /* *** lower box*** */
3076
3077   box_y = metadata_box_height_get(ibuf, blf_mono_font, false);
3078
3079   if (box_y) {
3080     /* set up box rect */
3081     BLI_rctf_init(&rect, frame->xmin, frame->xmax, frame->ymin - box_y, frame->ymin);
3082     /* draw top box */
3083     GPUVertFormat *format = immVertexFormat();
3084     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3085     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3086     immUniformThemeColor(TH_METADATA_BG);
3087     immRectf(pos, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3088     immUnbindProgram();
3089
3090     BLF_clipping(blf_mono_font, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3091     BLF_enable(blf_mono_font, BLF_CLIPPING);
3092
3093     UI_FontThemeColor(blf_mono_font, TH_METADATA_TEXT);
3094     metadata_draw_imbuf(ibuf, &rect, blf_mono_font, false);
3095
3096     BLF_disable(blf_mono_font, BLF_CLIPPING);
3097   }
3098
3099   GPU_matrix_pop();
3100 }
3101
3102 typedef struct MetadataPanelDrawContext {
3103   uiLayout *layout;
3104 } MetadataPanelDrawContext;
3105
3106 static void metadata_panel_draw_field(const char *field, const char *value, void *ctx_v)
3107 {
3108   MetadataPanelDrawContext *ctx = (MetadataPanelDrawContext *)ctx_v;
3109   uiLayout *row = uiLayoutRow(ctx->layout, false);
3110   uiItemL(row, field, ICON_NONE);
3111   uiItemL(row, value, ICON_NONE);
3112 }
3113
3114 void ED_region_image_metadata_panel_draw(ImBuf *ibuf, uiLayout *layout)
3115 {
3116   MetadataPanelDrawContext ctx;
3117   ctx.layout = layout;
3118   IMB_metadata_foreach(ibuf, metadata_panel_draw_field, &ctx);
3119 }
3120
3121 void ED_region_grid_draw(ARegion *ar, float zoomx, float zoomy)
3122 {
3123   float gridsize, gridstep = 1.0f / 32.0f;
3124   float fac, blendfac;
3125   int x1, y1, x2, y2;
3126
3127   /* the image is located inside (0, 0), (1, 1) as set by view2d */
3128   UI_view2d_view_to_region(&ar->v2d, 0.0f, 0.0f, &x1, &y1);
3129   UI_view2d_view_to_region(&ar->v2d, 1.0f, 1.0f, &x2, &y2);
3130
3131   GPUVertFormat *format = immVertexFormat();
3132   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3133
3134   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3135   immUniformThemeColorShade(TH_BACK, 20);
3136   immRectf(pos, x1, y1, x2, y2);
3137   immUnbindProgram();
3138
3139   /* gridsize adapted to zoom level */
3140   gridsize = 0.5f * (zoomx + zoomy);
3141   if (gridsize <= 0.0f)
3142     return;
3143
3144   if (gridsize < 1.0f) {
3145     while (gridsize < 1.0f) {
3146       gridsize *= 4.0f;
3147       gridstep *= 4.0f;
3148     }
3149   }
3150   else {
3151     while (gridsize >= 4.0f) {
3152       gridsize /= 4.0f;
3153       gridstep /= 4.0f;
3154     }
3155   }
3156
3157   blendfac = 0.25f * gridsize - floorf(0.25f * gridsize);
3158   CLAMP(blendfac, 0.0f, 1.0f);
3159
3160   int count_fine = 1.0f / gridstep;
3161   int count_large = 1.0f / (4.0f * gridstep);
3162
3163   if (count_fine > 0) {
3164     GPU_vertformat_clear(format);
3165     pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3166     unsigned color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
3167
3168     immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
3169     immBegin(GPU_PRIM_LINES, 4 * count_fine + 4 * count_large);
3170
3171     float theme_color[3];
3172     UI_GetThemeColorShade3fv(TH_BACK, (int)(20.0f * (1.0f - blendfac)), theme_color);
3173     fac = 0.0f;
3174
3175     /* the fine resolution level */
3176     for (int i = 0; i < count_fine; i++) {
3177       immAttr3fv(color, theme_color);
3178       immVertex2f(pos, x1, y1 * (1.0f - fac) + y2 * fac);
3179       immAttr3fv(color, theme_color);
3180       immVertex2f(pos, x2, y1 * (1.0f - fac) + y2 * fac);
3181       immAttr3fv(color, theme_color);
3182       immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y1);
3183       immAttr3fv(color, theme_color);
3184       immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y2);
3185       fac += gridstep;
3186     }
3187
3188     if (count_large > 0) {
3189       UI_GetThemeColor3fv(TH_BACK, theme_color);
3190       fac = 0.0f;
3191
3192       /* the large resolution level */
3193       for (int i = 0; i < count_large; i++) {
3194         immAttr3fv(color, theme_color);
3195         immVertex2f(pos, x1, y1 * (1.0f - fac) + y2 * fac);
3196         immAttr3fv(color, theme_color);
3197         immVertex2f(pos, x2, y1 * (1.0f - fac) + y2 * fac);
3198         immAttr3fv(color, theme_color);
3199         immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y1);
3200         immAttr3fv(color, theme_color);
3201         immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y2);
3202         fac += 4.0f * gridstep;
3203       }
3204     }
3205
3206     immEnd();
3207     immUnbindProgram();
3208   }
3209 }
3210
3211 /* If the area has overlapping regions, it returns visible rect for Region *ar */
3212 /* rect gets returned in local region coordinates */
3213 void ED_region_visible_rect(ARegion *ar, rcti *rect)
3214 {
3215   ARegion *arn = ar;
3216
3217   /* allow function to be called without area */
3218   while (arn->prev)
3219     arn = arn->prev;
3220
3221   *rect = ar->winrct;
3222
3223   /* check if a region overlaps with the current one */
3224   for (; arn; arn = arn->next) {
3225     if (ar != arn && arn->overlap) {
3226       if (BLI_rcti_isect(rect, &arn->winrct, NULL)) {
3227         if (ELEM(arn->alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
3228           /* Overlap left, also check 1 pixel offset (2 regions on one side). */
3229           if (ABS(rect->xmin - arn->winrct.xmin) < 2) {
3230             rect->xmin = arn->winrct.xmax;
3231           }
3232
3233           /* Overlap right. */
3234           if (ABS(rect->xmax - arn->winrct.xmax) < 2) {
3235             rect->xmax = arn->winrct.xmin;
3236           }
3237         }
3238         else if (ELEM(arn->alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
3239           /* Same logic as above for vertical regions. */
3240           if (ABS(rect->ymin - arn->winrct.ymin) < 2) {
3241             rect->ymin = arn->winrct.ymax;
3242           }
3243           if (ABS(rect->ymax - arn->winrct.ymax) < 2) {
3244             rect->ymax = arn->winrct.ymin;
3245           }
3246         }
3247         else if (arn->alignment == RGN_ALIGN_FLOAT) {
3248           /* Skip floating. */
3249         }
3250         else {
3251           BLI_assert(!"Region overlap with unknown alignment");
3252         }
3253       }
3254     }
3255   }
3256   BLI_rcti_translate(rect, -ar->winrct.xmin, -ar->winrct.ymin);
3257 }
3258
3259 /* Cache display helpers */
3260
3261 void ED_region_cache_draw_background(const ARegion *ar)
3262 {
3263   uint pos = GPU_vertformat_attr_add(
3264       immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
3265   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3266   immUniformColor4ub(128, 128, 255, 64);
3267   immRecti(pos, 0, 0, ar->winx, 8 * UI_DPI_FAC);
3268   immUnbindProgram();
3269 }
3270
3271 void ED_region_cache_draw_curfra_label(const int framenr, const float x, const float y)
3272 {
3273   uiStyle *style = UI_style_get();
3274   int fontid = style->widget.uifont_id;
3275   char numstr[32];
3276   float font_dims[2] = {0.0f, 0.0f};
3277
3278   /* frame number */
3279   BLF_size(fontid, 11.0f * U.pixelsize, U.dpi);
3280   BLI_snprintf(numstr, sizeof(numstr), "%d", framenr);
3281
3282   BLF_width_and_height(fontid, numstr, sizeof(numstr), &font_dims[0], &font_dims[1]);
3283
3284   uint pos = GPU_vertformat_attr_add(
3285       immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
3286   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3287   immUniformThemeColor(TH_CFRAME);
3288   immRecti(pos, x, y, x + font_dims[0] + 6.0f, y + font_dims[1] + 4.0f);
3289   immUnbindProgram();
3290
3291   UI_FontThemeColor(fontid, TH_TEXT);
3292   BLF_position(fontid, x + 2.0f, y + 2.0f, 0.0f);
3293   BLF_draw(fontid, numstr, sizeof(numstr));
3294 }
3295
3296 void ED_region_cache_draw_cached_segments(
3297     const ARegion *ar, const int num_segments, const int *points, const int sfra, const int efra)
3298 {
3299   if (num_segments) {
3300     uint pos = GPU_vertformat_attr_add(
3301         immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
3302     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3303     immUniformColor4ub(128, 128, 255, 128);
3304
3305     for (int a = 0; a < num_segments; a++) {
3306       float x1 = (float)(points[a * 2] - sfra) / (efra - sfra + 1) * ar->winx;
3307       float x2 = (float)(points[a * 2 + 1] - sfra + 1) / (efra - sfra + 1) * ar->winx;
3308
3309       immRecti(pos, x1, 0, x2, 8 * UI_DPI_FAC);
3310       /* TODO(merwin): use primitive restart to draw multiple rects more efficiently */
3311     }
3312
3313     immUnbindProgram();
3314   }
3315 }
3316
3317 /**
3318  * Generate subscriptions for this region.
3319  */
3320 void ED_region_message_subscribe(bContext *C,
3321                                  struct WorkSpace *workspace,
3322                                  struct Scene *scene,
3323                                  struct bScreen *screen,
3324                                  struct ScrArea *sa,
3325                                  struct ARegion *ar,
3326                                  struct wmMsgBus *mbus)
3327 {
3328   if (ar->gizmo_map != NULL) {
3329     WM_gizmomap_message_subscribe(C, ar->gizmo_map, ar, mbus);
3330   }
3331
3332   if (!BLI_listbase_is_empty(&ar->uiblocks)) {
3333     UI_region_message_subscribe(ar, mbus);
3334   }
3335
3336   if (ar->type->message_subscribe != NULL) {
3337     ar->type->message_subscribe(C, workspace, scene, screen, sa, ar, mbus);
3338   }
3339 }
3340
3341 int ED_region_snap_size_test(const ARegion *ar)
3342 {
3343   /* Use a larger value because toggling scrollbars can jump in size. */
3344   const int snap_match_threshold = 16;
3345   if (ar->type->snap_size != NULL) {
3346     return ((((ar->sizex - ar->type->snap_size(ar, ar->sizex, 0)) <= snap_match_threshold) << 0) |
3347             (((ar->sizey - ar->type->snap_size(ar, ar->sizey, 1)) <= snap_match_threshold) << 1));
3348   }
3349   return 0;
3350 }
3351
3352 bool ED_region_snap_size_apply(ARegion *ar, int snap_flag)
3353 {
3354   bool changed = false;
3355   if (ar->type->snap_size != NULL) {
3356     if (snap_flag & (1 << 0)) {
3357       short snap_size = ar->type->snap_size(ar, ar->sizex, 0);
3358       if (snap_size != ar->sizex) {
3359         ar->sizex = snap_size;
3360         changed = true;
3361       }
3362     }
3363     if (snap_flag & (1 << 1)) {
3364       short snap_size = ar->type->snap_size(ar, ar->sizey, 1);
3365       if (snap_size != ar->sizey) {
3366         ar->sizey = snap_size;
3367         changed = true;
3368       }
3369     }
3370   }
3371   return changed;
3372 }