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 <stdio.h>
25 #include <string.h>
26 
27 #include "MEM_guardedalloc.h"
28 
29 #include "DNA_userdef_types.h"
30 
31 #include "BLI_blenlib.h"
32 #include "BLI_linklist_stack.h"
33 #include "BLI_math.h"
34 #include "BLI_rand.h"
35 #include "BLI_utildefines.h"
36 
37 #include "BKE_context.h"
38 #include "BKE_global.h"
39 #include "BKE_image.h"
40 #include "BKE_screen.h"
41 #include "BKE_workspace.h"
42 
43 #include "RNA_access.h"
44 #include "RNA_types.h"
45 
46 #include "WM_api.h"
47 #include "WM_message.h"
48 #include "WM_toolsystem.h"
49 #include "WM_types.h"
50 
51 #include "ED_buttons.h"
52 #include "ED_screen.h"
53 #include "ED_screen_types.h"
54 #include "ED_space_api.h"
55 #include "ED_time_scrub_ui.h"
56 
57 #include "GPU_framebuffer.h"
58 #include "GPU_immediate.h"
59 #include "GPU_immediate_util.h"
60 #include "GPU_matrix.h"
61 #include "GPU_state.h"
62 
63 #include "BLF_api.h"
64 
65 #include "IMB_imbuf_types.h"
66 #include "IMB_metadata.h"
67 
68 #include "UI_interface.h"
69 #include "UI_interface_icons.h"
70 #include "UI_resources.h"
71 #include "UI_view2d.h"
72 
73 #include "screen_intern.h"
74 
75 enum RegionEmbossSide {
76   REGION_EMBOSS_LEFT = (1 << 0),
77   REGION_EMBOSS_TOP = (1 << 1),
78   REGION_EMBOSS_BOTTOM = (1 << 2),
79   REGION_EMBOSS_RIGHT = (1 << 3),
80   REGION_EMBOSS_ALL = REGION_EMBOSS_LEFT | REGION_EMBOSS_TOP | REGION_EMBOSS_RIGHT |
81                       REGION_EMBOSS_BOTTOM,
82 };
83 
84 /* general area and region code */
85 
region_draw_emboss(const ARegion * region,const rcti * scirct,int sides)86 static void region_draw_emboss(const ARegion *region, const rcti *scirct, int sides)
87 {
88   /* translate scissor rect to region space */
89   const rcti rect = {.xmin = scirct->xmin - region->winrct.xmin,
90                      .xmax = scirct->xmax - region->winrct.xmin,
91                      .ymin = scirct->ymin - region->winrct.ymin,
92                      .ymax = scirct->ymax - region->winrct.ymin};
93 
94   /* set transp line */
95   GPU_blend(GPU_BLEND_ALPHA);
96 
97   float color[4] = {0.0f, 0.0f, 0.0f, 0.25f};
98   UI_GetThemeColor3fv(TH_EDITOR_OUTLINE, color);
99 
100   GPUVertFormat *format = immVertexFormat();
101   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
102   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
103   immUniformColor4fv(color);
104 
105   immBeginAtMost(GPU_PRIM_LINES, 8);
106 
107   /* right */
108   if (sides & REGION_EMBOSS_RIGHT) {
109     immVertex2f(pos, rect.xmax, rect.ymax);
110     immVertex2f(pos, rect.xmax, rect.ymin);
111   }
112 
113   /* bottom */
114   if (sides & REGION_EMBOSS_BOTTOM) {
115     immVertex2f(pos, rect.xmax, rect.ymin);
116     immVertex2f(pos, rect.xmin, rect.ymin);
117   }
118 
119   /* left */
120   if (sides & REGION_EMBOSS_LEFT) {
121     immVertex2f(pos, rect.xmin, rect.ymin);
122     immVertex2f(pos, rect.xmin, rect.ymax);
123   }
124 
125   /* top */
126   if (sides & REGION_EMBOSS_TOP) {
127     immVertex2f(pos, rect.xmin, rect.ymax);
128     immVertex2f(pos, rect.xmax, rect.ymax);
129   }
130 
131   immEnd();
132   immUnbindProgram();
133 
134   GPU_blend(GPU_BLEND_NONE);
135 }
136 
ED_region_pixelspace(ARegion * region)137 void ED_region_pixelspace(ARegion *region)
138 {
139   wmOrtho2_region_pixelspace(region);
140   GPU_matrix_identity_set();
141 }
142 
143 /* only exported for WM */
ED_region_do_listen(wmWindow * win,ScrArea * area,ARegion * region,wmNotifier * note,const Scene * scene)144 void ED_region_do_listen(
145     wmWindow *win, ScrArea *area, ARegion *region, wmNotifier *note, const Scene *scene)
146 {
147   /* generic notes first */
148   switch (note->category) {
149     case NC_WM:
150       if (note->data == ND_FILEREAD) {
151         ED_region_tag_redraw(region);
152       }
153       break;
154     case NC_WINDOW:
155       ED_region_tag_redraw(region);
156       break;
157   }
158 
159   if (region->type && region->type->listener) {
160     region->type->listener(win, area, region, note, scene);
161   }
162 }
163 
164 /* only exported for WM */
ED_area_do_listen(wmWindow * win,ScrArea * area,wmNotifier * note,Scene * scene)165 void ED_area_do_listen(wmWindow *win, ScrArea *area, wmNotifier *note, Scene *scene)
166 {
167   /* no generic notes? */
168   if (area->type && area->type->listener) {
169     area->type->listener(win, area, note, scene);
170   }
171 }
172 
173 /* only exported for WM */
ED_area_do_refresh(bContext * C,ScrArea * area)174 void ED_area_do_refresh(bContext *C, ScrArea *area)
175 {
176   /* no generic notes? */
177   if (area->type && area->type->refresh) {
178     area->type->refresh(C, area);
179   }
180   area->do_refresh = false;
181 }
182 
183 /**
184  * \brief Corner widget use for quitting fullscreen.
185  */
area_draw_azone_fullscreen(short UNUSED (x1),short UNUSED (y1),short x2,short y2,float alpha)186 static void area_draw_azone_fullscreen(
187     short UNUSED(x1), short UNUSED(y1), short x2, short y2, float alpha)
188 {
189   UI_icon_draw_ex(x2 - U.widget_unit,
190                   y2 - U.widget_unit,
191                   ICON_FULLSCREEN_EXIT,
192                   U.inv_dpi_fac,
193                   min_ff(alpha, 0.75f),
194                   0.0f,
195                   NULL,
196                   false);
197 }
198 
199 /**
200  * \brief Corner widgets use for dragging and splitting the view.
201  */
area_draw_azone(short UNUSED (x1),short UNUSED (y1),short UNUSED (x2),short UNUSED (y2))202 static void area_draw_azone(short UNUSED(x1), short UNUSED(y1), short UNUSED(x2), short UNUSED(y2))
203 {
204   /* No drawing needed since all corners are action zone, and visually distinguishable. */
205 }
206 
207 /**
208  * \brief Edge widgets to show hidden panels such as the toolbar and headers.
209  */
draw_azone_arrow(float x1,float y1,float x2,float y2,AZEdge edge)210 static void draw_azone_arrow(float x1, float y1, float x2, float y2, AZEdge edge)
211 {
212   const float size = 0.2f * U.widget_unit;
213   const float l = 1.0f;  /* arrow length */
214   const float s = 0.25f; /* arrow thickness */
215   const float hl = l / 2.0f;
216   const float points[6][2] = {
217       {0, -hl}, {l, hl}, {l - s, hl + s}, {0, s + s - hl}, {s - l, hl + s}, {-l, hl}};
218   const float center[2] = {(x1 + x2) / 2, (y1 + y2) / 2};
219 
220   int axis;
221   int sign;
222   switch (edge) {
223     case AE_BOTTOM_TO_TOPLEFT:
224       axis = 0;
225       sign = 1;
226       break;
227     case AE_TOP_TO_BOTTOMRIGHT:
228       axis = 0;
229       sign = -1;
230       break;
231     case AE_LEFT_TO_TOPRIGHT:
232       axis = 1;
233       sign = 1;
234       break;
235     case AE_RIGHT_TO_TOPLEFT:
236       axis = 1;
237       sign = -1;
238       break;
239     default:
240       BLI_assert(0);
241       return;
242   }
243 
244   GPUVertFormat *format = immVertexFormat();
245   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
246 
247   GPU_blend(GPU_BLEND_ALPHA);
248   /* NOTE(fclem): There is something strange going on with Mesa and GPU_SHADER_2D_UNIFORM_COLOR
249    * that causes a crash on some GPUs (see T76113). Using 3D variant avoid the issue. */
250   immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
251   immUniformColor4f(0.8f, 0.8f, 0.8f, 0.4f);
252 
253   immBegin(GPU_PRIM_TRI_FAN, 6);
254   for (int i = 0; i < 6; i++) {
255     if (axis == 0) {
256       immVertex2f(pos, center[0] + points[i][0] * size, center[1] + points[i][1] * sign * size);
257     }
258     else {
259       immVertex2f(pos, center[0] + points[i][1] * sign * size, center[1] + points[i][0] * size);
260     }
261   }
262   immEnd();
263 
264   immUnbindProgram();
265   GPU_blend(GPU_BLEND_NONE);
266 }
267 
region_draw_azone_tab_arrow(ScrArea * area,ARegion * region,AZone * az)268 static void region_draw_azone_tab_arrow(ScrArea *area, ARegion *region, AZone *az)
269 {
270   GPU_blend(GPU_BLEND_ALPHA);
271 
272   /* add code to draw region hidden as 'too small' */
273   switch (az->edge) {
274     case AE_TOP_TO_BOTTOMRIGHT:
275       UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_TOP_RIGHT);
276       break;
277     case AE_BOTTOM_TO_TOPLEFT:
278       UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT);
279       break;
280     case AE_LEFT_TO_TOPRIGHT:
281       UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT);
282       break;
283     case AE_RIGHT_TO_TOPLEFT:
284       UI_draw_roundbox_corner_set(UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT);
285       break;
286   }
287 
288   /* Workaround for different color spaces between normal areas and the ones using GPUViewports. */
289   float alpha = WM_region_use_viewport(area, region) ? 0.6f : 0.4f;
290   const float color[4] = {0.05f, 0.05f, 0.05f, alpha};
291   UI_draw_roundbox_aa(
292       true, (float)az->x1, (float)az->y1, (float)az->x2, (float)az->y2, 4.0f, color);
293 
294   draw_azone_arrow((float)az->x1, (float)az->y1, (float)az->x2, (float)az->y2, az->edge);
295 }
296 
area_azone_tag_update(ScrArea * area)297 static void area_azone_tag_update(ScrArea *area)
298 {
299   area->flag |= AREA_FLAG_ACTIONZONES_UPDATE;
300 }
301 
region_draw_azones(ScrArea * area,ARegion * region)302 static void region_draw_azones(ScrArea *area, ARegion *region)
303 {
304   if (!area) {
305     return;
306   }
307 
308   GPU_line_width(1.0f);
309   GPU_blend(GPU_BLEND_ALPHA);
310 
311   GPU_matrix_push();
312   GPU_matrix_translate_2f(-region->winrct.xmin, -region->winrct.ymin);
313 
314   LISTBASE_FOREACH (AZone *, az, &area->actionzones) {
315     /* test if action zone is over this region */
316     rcti azrct;
317     BLI_rcti_init(&azrct, az->x1, az->x2, az->y1, az->y2);
318 
319     if (BLI_rcti_isect(&region->drawrct, &azrct, NULL)) {
320       if (az->type == AZONE_AREA) {
321         area_draw_azone(az->x1, az->y1, az->x2, az->y2);
322       }
323       else if (az->type == AZONE_REGION) {
324         if (az->region) {
325           /* only display tab or icons when the region is hidden */
326           if (az->region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) {
327             region_draw_azone_tab_arrow(area, region, az);
328           }
329         }
330       }
331       else if (az->type == AZONE_FULLSCREEN) {
332         if (az->alpha > 0.0f) {
333           area_draw_azone_fullscreen(az->x1, az->y1, az->x2, az->y2, az->alpha);
334         }
335       }
336     }
337     if (!IS_EQF(az->alpha, 0.0f) && ELEM(az->type, AZONE_FULLSCREEN, AZONE_REGION_SCROLL)) {
338       area_azone_tag_update(area);
339     }
340   }
341 
342   GPU_matrix_pop();
343 
344   GPU_blend(GPU_BLEND_NONE);
345 }
346 
region_draw_status_text(ScrArea * area,ARegion * region)347 static void region_draw_status_text(ScrArea *area, ARegion *region)
348 {
349   bool overlap = ED_region_is_overlap(area->spacetype, region->regiontype);
350 
351   if (overlap) {
352     GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);
353   }
354   else {
355     UI_ThemeClearColor(TH_HEADER);
356   }
357 
358   int fontid = BLF_set_default();
359 
360   const float width = BLF_width(fontid, region->headerstr, BLF_DRAW_STR_DUMMY_MAX);
361   const float x = UI_UNIT_X;
362   const float y = 0.4f * UI_UNIT_Y;
363 
364   if (overlap) {
365     const float pad = 2.0f * UI_DPI_FAC;
366     const float x1 = x - (UI_UNIT_X - pad);
367     const float x2 = x + width + (UI_UNIT_X - pad);
368     const float y1 = pad;
369     const float y2 = region->winy - pad;
370 
371     GPU_blend(GPU_BLEND_ALPHA);
372 
373     float color[4] = {0.0f, 0.0f, 0.0f, 0.5f};
374     UI_GetThemeColor3fv(TH_BACK, color);
375     UI_draw_roundbox_corner_set(UI_CNR_ALL);
376     UI_draw_roundbox_aa(true, x1, y1, x2, y2, 4.0f, color);
377 
378     UI_FontThemeColor(fontid, TH_TEXT);
379   }
380   else {
381     UI_FontThemeColor(fontid, TH_TEXT);
382   }
383 
384   BLF_position(fontid, x, y, 0.0f);
385   BLF_draw(fontid, region->headerstr, BLF_DRAW_STR_DUMMY_MAX);
386 }
387 
ED_region_do_msg_notify_tag_redraw(bContext * UNUSED (C),wmMsgSubscribeKey * UNUSED (msg_key),wmMsgSubscribeValue * msg_val)388 void ED_region_do_msg_notify_tag_redraw(
389     /* Follow wmMsgNotifyFn spec */
390     bContext *UNUSED(C),
391     wmMsgSubscribeKey *UNUSED(msg_key),
392     wmMsgSubscribeValue *msg_val)
393 {
394   ARegion *region = msg_val->owner;
395   ED_region_tag_redraw(region);
396 
397   /* This avoids _many_ situations where header/properties control display settings.
398    * the common case is space properties in the header */
399   if (ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_UI)) {
400     while (region && region->prev) {
401       region = region->prev;
402     }
403     for (; region; region = region->next) {
404       if (ELEM(region->regiontype, RGN_TYPE_WINDOW, RGN_TYPE_CHANNELS)) {
405         ED_region_tag_redraw(region);
406       }
407     }
408   }
409 }
410 
ED_area_do_msg_notify_tag_refresh(bContext * UNUSED (C),wmMsgSubscribeKey * UNUSED (msg_key),wmMsgSubscribeValue * msg_val)411 void ED_area_do_msg_notify_tag_refresh(
412     /* Follow wmMsgNotifyFn spec */
413     bContext *UNUSED(C),
414     wmMsgSubscribeKey *UNUSED(msg_key),
415     wmMsgSubscribeValue *msg_val)
416 {
417   ScrArea *area = msg_val->user_data;
418   ED_area_tag_refresh(area);
419 }
420 
ED_area_do_mgs_subscribe_for_tool_header(const struct bContext * UNUSED (C),struct WorkSpace * workspace,struct Scene * UNUSED (scene),struct bScreen * UNUSED (screen),struct ScrArea * UNUSED (area),struct ARegion * region,struct wmMsgBus * mbus)421 void ED_area_do_mgs_subscribe_for_tool_header(
422     /* Follow ARegionType.message_subscribe */
423     const struct bContext *UNUSED(C),
424     struct WorkSpace *workspace,
425     struct Scene *UNUSED(scene),
426     struct bScreen *UNUSED(screen),
427     struct ScrArea *UNUSED(area),
428     struct ARegion *region,
429     struct wmMsgBus *mbus)
430 {
431   BLI_assert(region->regiontype == RGN_TYPE_TOOL_HEADER);
432   wmMsgSubscribeValue msg_sub_value_region_tag_redraw = {
433       .owner = region,
434       .user_data = region,
435       .notify = ED_region_do_msg_notify_tag_redraw,
436   };
437   WM_msg_subscribe_rna_prop(
438       mbus, &workspace->id, workspace, WorkSpace, tools, &msg_sub_value_region_tag_redraw);
439 }
440 
ED_area_do_mgs_subscribe_for_tool_ui(const struct bContext * UNUSED (C),struct WorkSpace * workspace,struct Scene * UNUSED (scene),struct bScreen * UNUSED (screen),struct ScrArea * UNUSED (area),struct ARegion * region,struct wmMsgBus * mbus)441 void ED_area_do_mgs_subscribe_for_tool_ui(
442     /* Follow ARegionType.message_subscribe */
443     const struct bContext *UNUSED(C),
444     struct WorkSpace *workspace,
445     struct Scene *UNUSED(scene),
446     struct bScreen *UNUSED(screen),
447     struct ScrArea *UNUSED(area),
448     struct ARegion *region,
449     struct wmMsgBus *mbus)
450 {
451   BLI_assert(region->regiontype == RGN_TYPE_UI);
452   const char *panel_category_tool = "Tool";
453   const char *category = UI_panel_category_active_get(region, false);
454 
455   bool update_region = false;
456   if (category && STREQ(category, panel_category_tool)) {
457     update_region = true;
458   }
459   else {
460     /* Check if a tool category panel is pinned and visible in another category. */
461     LISTBASE_FOREACH (Panel *, panel, &region->panels) {
462       if (UI_panel_is_active(panel) && panel->flag & PNL_PIN &&
463           STREQ(panel->type->category, panel_category_tool)) {
464         update_region = true;
465         break;
466       }
467     }
468   }
469 
470   if (update_region) {
471     wmMsgSubscribeValue msg_sub_value_region_tag_redraw = {
472         .owner = region,
473         .user_data = region,
474         .notify = ED_region_do_msg_notify_tag_redraw,
475     };
476     WM_msg_subscribe_rna_prop(
477         mbus, &workspace->id, workspace, WorkSpace, tools, &msg_sub_value_region_tag_redraw);
478   }
479 }
480 
481 /**
482  * Although there's no general support for minimizing areas, the status-bar can
483  * be snapped to be only a few pixels high. A few pixels rather than 0 so it
484  * can be un-minimized again. We consider it pseudo-minimized and don't draw
485  * it then.
486  */
area_is_pseudo_minimized(const ScrArea * area)487 static bool area_is_pseudo_minimized(const ScrArea *area)
488 {
489   return (area->winx < 3) || (area->winy < 3);
490 }
491 
492 /* only exported for WM */
ED_region_do_layout(bContext * C,ARegion * region)493 void ED_region_do_layout(bContext *C, ARegion *region)
494 {
495   /* This is optional, only needed for dynamically sized regions. */
496   ScrArea *area = CTX_wm_area(C);
497   ARegionType *at = region->type;
498 
499   if (!at->layout) {
500     return;
501   }
502 
503   if (at->do_lock || (area && area_is_pseudo_minimized(area))) {
504     return;
505   }
506 
507   region->do_draw |= RGN_DRAWING;
508 
509   UI_SetTheme(area ? area->spacetype : 0, at->regionid);
510   at->layout(C, region);
511 
512   /* Clear temporary update flag. */
513   region->flag &= ~RGN_FLAG_SEARCH_FILTER_UPDATE;
514 }
515 
516 /* only exported for WM */
ED_region_do_draw(bContext * C,ARegion * region)517 void ED_region_do_draw(bContext *C, ARegion *region)
518 {
519   wmWindow *win = CTX_wm_window(C);
520   ScrArea *area = CTX_wm_area(C);
521   ARegionType *at = region->type;
522 
523   /* see BKE_spacedata_draw_locks() */
524   if (at->do_lock) {
525     return;
526   }
527 
528   region->do_draw |= RGN_DRAWING;
529 
530   /* Set viewport, scissor, ortho and region->drawrct. */
531   wmPartialViewport(&region->drawrct, &region->winrct, &region->drawrct);
532 
533   wmOrtho2_region_pixelspace(region);
534 
535   UI_SetTheme(area ? area->spacetype : 0, at->regionid);
536 
537   if (area && area_is_pseudo_minimized(area)) {
538     UI_ThemeClearColor(TH_EDITOR_OUTLINE);
539     return;
540   }
541   /* optional header info instead? */
542   if (region->headerstr) {
543     region_draw_status_text(area, region);
544   }
545   else if (at->draw) {
546     at->draw(C, region);
547   }
548 
549   /* XXX test: add convention to end regions always in pixel space,
550    * for drawing of borders/gestures etc */
551   ED_region_pixelspace(region);
552 
553   /* Remove sRGB override by rebinding the framebuffer. */
554   GPUFrameBuffer *fb = GPU_framebuffer_active_get();
555   GPU_framebuffer_bind(fb);
556 
557   ED_region_draw_cb_draw(C, region, REGION_DRAW_POST_PIXEL);
558 
559   region_draw_azones(area, region);
560 
561   /* for debugging unneeded area redraws and partial redraw */
562   if (G.debug_value == 888) {
563     GPU_blend(GPU_BLEND_ALPHA);
564     GPUVertFormat *format = immVertexFormat();
565     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
566     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
567     immUniformColor4f(BLI_thread_frand(0), BLI_thread_frand(0), BLI_thread_frand(0), 0.1f);
568     immRectf(pos,
569              region->drawrct.xmin - region->winrct.xmin,
570              region->drawrct.ymin - region->winrct.ymin,
571              region->drawrct.xmax - region->winrct.xmin,
572              region->drawrct.ymax - region->winrct.ymin);
573     immUnbindProgram();
574     GPU_blend(GPU_BLEND_NONE);
575   }
576 
577   memset(&region->drawrct, 0, sizeof(region->drawrct));
578 
579   UI_blocklist_free_inactive(C, &region->uiblocks);
580 
581   if (area) {
582     const bScreen *screen = WM_window_get_active_screen(win);
583 
584     /* Only region emboss for top-bar */
585     if ((screen->state != SCREENFULL) && ED_area_is_global(area)) {
586       region_draw_emboss(region, &region->winrct, (REGION_EMBOSS_LEFT | REGION_EMBOSS_RIGHT));
587     }
588     else if ((region->regiontype == RGN_TYPE_WINDOW) && (region->alignment == RGN_ALIGN_QSPLIT)) {
589 
590       /* draw separating lines between the quad views */
591 
592       float color[4] = {0.0f, 0.0f, 0.0f, 0.8f};
593       UI_GetThemeColor3fv(TH_EDITOR_OUTLINE, color);
594       GPUVertFormat *format = immVertexFormat();
595       uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
596       immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
597       immUniformColor4fv(color);
598       GPU_line_width(1.0f);
599       imm_draw_box_wire_2d(pos,
600                            0,
601                            0,
602                            region->winrct.xmax - region->winrct.xmin + 1,
603                            region->winrct.ymax - region->winrct.ymin + 1);
604       immUnbindProgram();
605     }
606   }
607 
608   /* We may want to detach message-subscriptions from drawing. */
609   {
610     WorkSpace *workspace = CTX_wm_workspace(C);
611     wmWindowManager *wm = CTX_wm_manager(C);
612     bScreen *screen = WM_window_get_active_screen(win);
613     Scene *scene = CTX_data_scene(C);
614     struct wmMsgBus *mbus = wm->message_bus;
615     WM_msgbus_clear_by_owner(mbus, region);
616 
617     /* Cheat, always subscribe to this space type properties.
618      *
619      * This covers most cases and avoids copy-paste similar code for each space type.
620      */
621     if (ELEM(
622             region->regiontype, RGN_TYPE_WINDOW, RGN_TYPE_CHANNELS, RGN_TYPE_UI, RGN_TYPE_TOOLS)) {
623       SpaceLink *sl = area->spacedata.first;
624 
625       PointerRNA ptr;
626       RNA_pointer_create(&screen->id, &RNA_Space, sl, &ptr);
627 
628       wmMsgSubscribeValue msg_sub_value_region_tag_redraw = {
629           .owner = region,
630           .user_data = region,
631           .notify = ED_region_do_msg_notify_tag_redraw,
632       };
633       /* All properties for this space type. */
634       WM_msg_subscribe_rna(mbus, &ptr, NULL, &msg_sub_value_region_tag_redraw, __func__);
635     }
636 
637     ED_region_message_subscribe(C, workspace, scene, screen, area, region, mbus);
638   }
639 }
640 
641 /* **********************************
642  * maybe silly, but let's try for now
643  * to keep these tags protected
644  * ********************************** */
645 
ED_region_tag_redraw(ARegion * region)646 void ED_region_tag_redraw(ARegion *region)
647 {
648   /* don't tag redraw while drawing, it shouldn't happen normally
649    * but python scripts can cause this to happen indirectly */
650   if (region && !(region->do_draw & RGN_DRAWING)) {
651     /* zero region means full region redraw */
652     region->do_draw &= ~(RGN_DRAW_PARTIAL | RGN_DRAW_NO_REBUILD | RGN_DRAW_EDITOR_OVERLAYS);
653     region->do_draw |= RGN_DRAW;
654     memset(&region->drawrct, 0, sizeof(region->drawrct));
655   }
656 }
657 
ED_region_tag_redraw_cursor(ARegion * region)658 void ED_region_tag_redraw_cursor(ARegion *region)
659 {
660   if (region) {
661     region->do_draw_paintcursor = RGN_DRAW;
662   }
663 }
664 
ED_region_tag_redraw_no_rebuild(ARegion * region)665 void ED_region_tag_redraw_no_rebuild(ARegion *region)
666 {
667   if (region && !(region->do_draw & (RGN_DRAWING | RGN_DRAW))) {
668     region->do_draw &= ~(RGN_DRAW_PARTIAL | RGN_DRAW_EDITOR_OVERLAYS);
669     region->do_draw |= RGN_DRAW_NO_REBUILD;
670     memset(&region->drawrct, 0, sizeof(region->drawrct));
671   }
672 }
673 
ED_region_tag_refresh_ui(ARegion * region)674 void ED_region_tag_refresh_ui(ARegion *region)
675 {
676   if (region) {
677     region->do_draw |= RGN_REFRESH_UI;
678   }
679 }
680 
681 /**
682  * Tag editor overlays to be redrawn. If in doubt about which parts need to be redrawn (partial
683  * clipping rectangle set), redraw everything.
684  */
ED_region_tag_redraw_editor_overlays(struct ARegion * region)685 void ED_region_tag_redraw_editor_overlays(struct ARegion *region)
686 {
687   if (region && !(region->do_draw & (RGN_DRAWING | RGN_DRAW))) {
688     if (region->do_draw & RGN_DRAW_PARTIAL) {
689       ED_region_tag_redraw(region);
690     }
691     else {
692       region->do_draw |= RGN_DRAW_EDITOR_OVERLAYS;
693     }
694   }
695 }
696 
ED_region_tag_redraw_partial(ARegion * region,const rcti * rct,bool rebuild)697 void ED_region_tag_redraw_partial(ARegion *region, const rcti *rct, bool rebuild)
698 {
699   if (region && !(region->do_draw & RGN_DRAWING)) {
700     if (region->do_draw & RGN_DRAW_PARTIAL) {
701       /* Partial redraw already set, expand region. */
702       BLI_rcti_union(&region->drawrct, rct);
703       if (rebuild) {
704         region->do_draw &= ~RGN_DRAW_NO_REBUILD;
705       }
706     }
707     else if (region->do_draw & (RGN_DRAW | RGN_DRAW_NO_REBUILD)) {
708       /* Full redraw already requested. */
709       if (rebuild) {
710         region->do_draw &= ~RGN_DRAW_NO_REBUILD;
711       }
712     }
713     else {
714       /* No redraw set yet, set partial region. */
715       region->drawrct = *rct;
716       region->do_draw |= RGN_DRAW_PARTIAL;
717       if (!rebuild) {
718         region->do_draw |= RGN_DRAW_NO_REBUILD;
719       }
720     }
721   }
722 }
723 
ED_area_tag_redraw(ScrArea * area)724 void ED_area_tag_redraw(ScrArea *area)
725 {
726   if (area) {
727     LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
728       ED_region_tag_redraw(region);
729     }
730   }
731 }
732 
ED_area_tag_redraw_no_rebuild(ScrArea * area)733 void ED_area_tag_redraw_no_rebuild(ScrArea *area)
734 {
735   if (area) {
736     LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
737       ED_region_tag_redraw_no_rebuild(region);
738     }
739   }
740 }
741 
ED_area_tag_redraw_regiontype(ScrArea * area,int regiontype)742 void ED_area_tag_redraw_regiontype(ScrArea *area, int regiontype)
743 {
744   if (area) {
745     LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
746       if (region->regiontype == regiontype) {
747         ED_region_tag_redraw(region);
748       }
749     }
750   }
751 }
752 
ED_area_tag_refresh(ScrArea * area)753 void ED_area_tag_refresh(ScrArea *area)
754 {
755   if (area) {
756     area->do_refresh = true;
757   }
758 }
759 
760 /* *************************************************************** */
761 
762 /**
763  * Returns the search string if the space type and region type support property search.
764  */
ED_area_region_search_filter_get(const ScrArea * area,const ARegion * region)765 const char *ED_area_region_search_filter_get(const ScrArea *area, const ARegion *region)
766 {
767   /* Only the properties editor has a search string for now. */
768   if (area->spacetype == SPACE_PROPERTIES) {
769     SpaceProperties *sbuts = area->spacedata.first;
770     if (region->regiontype == RGN_TYPE_WINDOW) {
771       return ED_buttons_search_string_get(sbuts);
772     }
773   }
774 
775   return NULL;
776 }
777 
778 /**
779  * Set the temporary update flag for property search.
780  */
ED_region_search_filter_update(const ScrArea * area,ARegion * region)781 void ED_region_search_filter_update(const ScrArea *area, ARegion *region)
782 {
783   region->flag |= RGN_FLAG_SEARCH_FILTER_UPDATE;
784 
785   const char *search_filter = ED_area_region_search_filter_get(area, region);
786   SET_FLAG_FROM_TEST(region->flag,
787                      region->regiontype == RGN_TYPE_WINDOW && search_filter[0] != '\0',
788                      RGN_FLAG_SEARCH_FILTER_ACTIVE);
789 }
790 
791 /* *************************************************************** */
792 
793 /* use NULL to disable it */
ED_area_status_text(ScrArea * area,const char * str)794 void ED_area_status_text(ScrArea *area, const char *str)
795 {
796   /* happens when running transform operators in background mode */
797   if (area == NULL) {
798     return;
799   }
800 
801   LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
802     if (region->regiontype == RGN_TYPE_HEADER) {
803       if (str) {
804         if (region->headerstr == NULL) {
805           region->headerstr = MEM_mallocN(UI_MAX_DRAW_STR, "headerprint");
806         }
807         BLI_strncpy(region->headerstr, str, UI_MAX_DRAW_STR);
808         BLI_str_rstrip(region->headerstr);
809       }
810       else if (region->headerstr) {
811         MEM_freeN(region->headerstr);
812         region->headerstr = NULL;
813       }
814       ED_region_tag_redraw(region);
815     }
816   }
817 }
818 
ED_workspace_status_text(bContext * C,const char * str)819 void ED_workspace_status_text(bContext *C, const char *str)
820 {
821   wmWindow *win = CTX_wm_window(C);
822   WorkSpace *workspace = CTX_wm_workspace(C);
823 
824   /* Can be NULL when running operators in background mode. */
825   if (workspace == NULL) {
826     return;
827   }
828 
829   if (str) {
830     if (workspace->status_text == NULL) {
831       workspace->status_text = MEM_mallocN(UI_MAX_DRAW_STR, "headerprint");
832     }
833     BLI_strncpy(workspace->status_text, str, UI_MAX_DRAW_STR);
834   }
835   else if (workspace->status_text) {
836     MEM_freeN(workspace->status_text);
837     workspace->status_text = NULL;
838   }
839 
840   /* Redraw status bar. */
841   LISTBASE_FOREACH (ScrArea *, area, &win->global_areas.areabase) {
842     if (area->spacetype == SPACE_STATUSBAR) {
843       ED_area_tag_redraw(area);
844       break;
845     }
846   }
847 }
848 
849 /* ************************************************************ */
850 
area_azone_init(wmWindow * win,const bScreen * screen,ScrArea * area)851 static void area_azone_init(wmWindow *win, const bScreen *screen, ScrArea *area)
852 {
853   /* reinitialize entirely, regions and fullscreen add azones too */
854   BLI_freelistN(&area->actionzones);
855 
856   if (screen->state != SCREENNORMAL) {
857     return;
858   }
859 
860   if (U.app_flag & USER_APP_LOCK_UI_LAYOUT) {
861     return;
862   }
863 
864   if (ED_area_is_global(area)) {
865     return;
866   }
867 
868   if (screen->temp) {
869     return;
870   }
871 
872   const float coords[4][4] = {
873       /* Bottom-left. */
874       {area->totrct.xmin - U.pixelsize,
875        area->totrct.ymin - U.pixelsize,
876        area->totrct.xmin + AZONESPOTW,
877        area->totrct.ymin + AZONESPOTH},
878       /* Bottom-right. */
879       {area->totrct.xmax - AZONESPOTW,
880        area->totrct.ymin - U.pixelsize,
881        area->totrct.xmax + U.pixelsize,
882        area->totrct.ymin + AZONESPOTH},
883       /* Top-left. */
884       {area->totrct.xmin - U.pixelsize,
885        area->totrct.ymax - AZONESPOTH,
886        area->totrct.xmin + AZONESPOTW,
887        area->totrct.ymax + U.pixelsize},
888       /* Top-right. */
889       {area->totrct.xmax - AZONESPOTW,
890        area->totrct.ymax - AZONESPOTH,
891        area->totrct.xmax + U.pixelsize,
892        area->totrct.ymax + U.pixelsize},
893   };
894 
895   for (int i = 0; i < 4; i++) {
896     /* can't click on bottom corners on OS X, already used for resizing */
897 #ifdef __APPLE__
898     if (!WM_window_is_fullscreen(win) &&
899         ((coords[i][0] == 0 && coords[i][1] == 0) ||
900          (coords[i][0] == WM_window_pixels_x(win) && coords[i][1] == 0))) {
901       continue;
902     }
903 #else
904     (void)win;
905 #endif
906 
907     /* set area action zones */
908     AZone *az = (AZone *)MEM_callocN(sizeof(AZone), "actionzone");
909     BLI_addtail(&(area->actionzones), az);
910     az->type = AZONE_AREA;
911     az->x1 = coords[i][0];
912     az->y1 = coords[i][1];
913     az->x2 = coords[i][2];
914     az->y2 = coords[i][3];
915     BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
916   }
917 }
918 
fullscreen_azone_init(ScrArea * area,ARegion * region)919 static void fullscreen_azone_init(ScrArea *area, ARegion *region)
920 {
921   if (ED_area_is_global(area) || (region->regiontype != RGN_TYPE_WINDOW)) {
922     return;
923   }
924 
925   AZone *az = (AZone *)MEM_callocN(sizeof(AZone), "fullscreen action zone");
926   BLI_addtail(&(area->actionzones), az);
927   az->type = AZONE_FULLSCREEN;
928   az->region = region;
929   az->alpha = 0.0f;
930 
931   if (U.uiflag2 & USER_REGION_OVERLAP) {
932     const rcti *rect_visible = ED_region_visible_rect(region);
933     az->x2 = region->winrct.xmin + rect_visible->xmax;
934     az->y2 = region->winrct.ymin + rect_visible->ymax;
935   }
936   else {
937     az->x2 = region->winrct.xmax;
938     az->y2 = region->winrct.ymax;
939   }
940   az->x1 = az->x2 - AZONEFADEOUT;
941   az->y1 = az->y2 - AZONEFADEOUT;
942 
943   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
944 }
945 
946 #define AZONEPAD_EDGE (0.1f * U.widget_unit)
947 #define AZONEPAD_ICON (0.45f * U.widget_unit)
region_azone_edge(AZone * az,ARegion * region)948 static void region_azone_edge(AZone *az, ARegion *region)
949 {
950   switch (az->edge) {
951     case AE_TOP_TO_BOTTOMRIGHT:
952       az->x1 = region->winrct.xmin;
953       az->y1 = region->winrct.ymax - AZONEPAD_EDGE;
954       az->x2 = region->winrct.xmax;
955       az->y2 = region->winrct.ymax + AZONEPAD_EDGE;
956       break;
957     case AE_BOTTOM_TO_TOPLEFT:
958       az->x1 = region->winrct.xmin;
959       az->y1 = region->winrct.ymin + AZONEPAD_EDGE;
960       az->x2 = region->winrct.xmax;
961       az->y2 = region->winrct.ymin - AZONEPAD_EDGE;
962       break;
963     case AE_LEFT_TO_TOPRIGHT:
964       az->x1 = region->winrct.xmin - AZONEPAD_EDGE;
965       az->y1 = region->winrct.ymin;
966       az->x2 = region->winrct.xmin + AZONEPAD_EDGE;
967       az->y2 = region->winrct.ymax;
968       break;
969     case AE_RIGHT_TO_TOPLEFT:
970       az->x1 = region->winrct.xmax + AZONEPAD_EDGE;
971       az->y1 = region->winrct.ymin;
972       az->x2 = region->winrct.xmax - AZONEPAD_EDGE;
973       az->y2 = region->winrct.ymax;
974       break;
975   }
976   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
977 }
978 
979 /* region already made zero sized, in shape of edge */
region_azone_tab_plus(ScrArea * area,AZone * az,ARegion * region)980 static void region_azone_tab_plus(ScrArea *area, AZone *az, ARegion *region)
981 {
982   float edge_offset = 1.0f;
983   const float tab_size_x = 0.7f * U.widget_unit;
984   const float tab_size_y = 0.4f * U.widget_unit;
985 
986   int tot = 0;
987   LISTBASE_FOREACH (AZone *, azt, &area->actionzones) {
988     if (azt->edge == az->edge) {
989       tot++;
990     }
991   }
992 
993   switch (az->edge) {
994     case AE_TOP_TO_BOTTOMRIGHT: {
995       int add = (region->winrct.ymax == area->totrct.ymin) ? 1 : 0;
996       az->x1 = region->winrct.xmax - ((edge_offset + 1.0f) * tab_size_x);
997       az->y1 = region->winrct.ymax - add;
998       az->x2 = region->winrct.xmax - (edge_offset * tab_size_x);
999       az->y2 = region->winrct.ymax - add + tab_size_y;
1000       break;
1001     }
1002     case AE_BOTTOM_TO_TOPLEFT:
1003       az->x1 = region->winrct.xmax - ((edge_offset + 1.0f) * tab_size_x);
1004       az->y1 = region->winrct.ymin - tab_size_y;
1005       az->x2 = region->winrct.xmax - (edge_offset * tab_size_x);
1006       az->y2 = region->winrct.ymin;
1007       break;
1008     case AE_LEFT_TO_TOPRIGHT:
1009       az->x1 = region->winrct.xmin - tab_size_y;
1010       az->y1 = region->winrct.ymax - ((edge_offset + 1.0f) * tab_size_x);
1011       az->x2 = region->winrct.xmin;
1012       az->y2 = region->winrct.ymax - (edge_offset * tab_size_x);
1013       break;
1014     case AE_RIGHT_TO_TOPLEFT:
1015       az->x1 = region->winrct.xmax;
1016       az->y1 = region->winrct.ymax - ((edge_offset + 1.0f) * tab_size_x);
1017       az->x2 = region->winrct.xmax + tab_size_y;
1018       az->y2 = region->winrct.ymax - (edge_offset * tab_size_x);
1019       break;
1020   }
1021   /* rect needed for mouse pointer test */
1022   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
1023 }
1024 
region_azone_edge_poll(const ARegion * region,const bool is_fullscreen)1025 static bool region_azone_edge_poll(const ARegion *region, const bool is_fullscreen)
1026 {
1027   const bool is_hidden = (region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
1028 
1029   if (is_hidden && is_fullscreen) {
1030     return false;
1031   }
1032   if (!is_hidden && ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
1033     return false;
1034   }
1035 
1036   return true;
1037 }
1038 
region_azone_edge_init(ScrArea * area,ARegion * region,AZEdge edge,const bool is_fullscreen)1039 static void region_azone_edge_init(ScrArea *area,
1040                                    ARegion *region,
1041                                    AZEdge edge,
1042                                    const bool is_fullscreen)
1043 {
1044   const bool is_hidden = (region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
1045 
1046   if (!region_azone_edge_poll(region, is_fullscreen)) {
1047     return;
1048   }
1049 
1050   AZone *az = (AZone *)MEM_callocN(sizeof(AZone), "actionzone");
1051   BLI_addtail(&(area->actionzones), az);
1052   az->type = AZONE_REGION;
1053   az->region = region;
1054   az->edge = edge;
1055 
1056   if (is_hidden) {
1057     region_azone_tab_plus(area, az, region);
1058   }
1059   else {
1060     region_azone_edge(az, region);
1061   }
1062 }
1063 
region_azone_scrollbar_init(ScrArea * area,ARegion * region,AZScrollDirection direction)1064 static void region_azone_scrollbar_init(ScrArea *area,
1065                                         ARegion *region,
1066                                         AZScrollDirection direction)
1067 {
1068   rcti scroller_vert = (direction == AZ_SCROLL_VERT) ? region->v2d.vert : region->v2d.hor;
1069   AZone *az = MEM_callocN(sizeof(*az), __func__);
1070 
1071   BLI_addtail(&area->actionzones, az);
1072   az->type = AZONE_REGION_SCROLL;
1073   az->region = region;
1074   az->direction = direction;
1075 
1076   if (direction == AZ_SCROLL_VERT) {
1077     az->region->v2d.alpha_vert = 0;
1078   }
1079   else if (direction == AZ_SCROLL_HOR) {
1080     az->region->v2d.alpha_hor = 0;
1081   }
1082 
1083   BLI_rcti_translate(&scroller_vert, region->winrct.xmin, region->winrct.ymin);
1084   az->x1 = scroller_vert.xmin - AZONEFADEIN;
1085   az->y1 = scroller_vert.ymin - AZONEFADEIN;
1086   az->x2 = scroller_vert.xmax + AZONEFADEIN;
1087   az->y2 = scroller_vert.ymax + AZONEFADEIN;
1088 
1089   BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
1090 }
1091 
region_azones_scrollbars_init(ScrArea * area,ARegion * region)1092 static void region_azones_scrollbars_init(ScrArea *area, ARegion *region)
1093 {
1094   const View2D *v2d = &region->v2d;
1095 
1096   if ((v2d->scroll & V2D_SCROLL_VERTICAL) && ((v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) == 0)) {
1097     region_azone_scrollbar_init(area, region, AZ_SCROLL_VERT);
1098   }
1099   if ((v2d->scroll & V2D_SCROLL_HORIZONTAL) &&
1100       ((v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) == 0)) {
1101     region_azone_scrollbar_init(area, region, AZ_SCROLL_HOR);
1102   }
1103 }
1104 
1105 /* *************************************************************** */
region_azones_add_edge(ScrArea * area,ARegion * region,const int alignment,const bool is_fullscreen)1106 static void region_azones_add_edge(ScrArea *area,
1107                                    ARegion *region,
1108                                    const int alignment,
1109                                    const bool is_fullscreen)
1110 {
1111 
1112   /* edge code (t b l r) is along which area edge azone will be drawn */
1113   if (alignment == RGN_ALIGN_TOP) {
1114     region_azone_edge_init(area, region, AE_BOTTOM_TO_TOPLEFT, is_fullscreen);
1115   }
1116   else if (alignment == RGN_ALIGN_BOTTOM) {
1117     region_azone_edge_init(area, region, AE_TOP_TO_BOTTOMRIGHT, is_fullscreen);
1118   }
1119   else if (alignment == RGN_ALIGN_RIGHT) {
1120     region_azone_edge_init(area, region, AE_LEFT_TO_TOPRIGHT, is_fullscreen);
1121   }
1122   else if (alignment == RGN_ALIGN_LEFT) {
1123     region_azone_edge_init(area, region, AE_RIGHT_TO_TOPLEFT, is_fullscreen);
1124   }
1125 }
1126 
region_azones_add(const bScreen * screen,ScrArea * area,ARegion * region)1127 static void region_azones_add(const bScreen *screen, ScrArea *area, ARegion *region)
1128 {
1129   const bool is_fullscreen = screen->state == SCREENFULL;
1130 
1131   /* Only display tab or icons when the header region is hidden
1132    * (not the tool header - they overlap). */
1133   if (region->regiontype == RGN_TYPE_TOOL_HEADER) {
1134     return;
1135   }
1136 
1137   region_azones_add_edge(area, region, RGN_ALIGN_ENUM_FROM_MASK(region->alignment), is_fullscreen);
1138 
1139   /* For a split region also continue the azone edge from the next region if this region is aligned
1140    * with the next */
1141   if ((region->alignment & RGN_SPLIT_PREV) && region->prev) {
1142     region_azones_add_edge(
1143         area, region, RGN_ALIGN_ENUM_FROM_MASK(region->prev->alignment), is_fullscreen);
1144   }
1145 
1146   if (is_fullscreen) {
1147     fullscreen_azone_init(area, region);
1148   }
1149 
1150   region_azones_scrollbars_init(area, region);
1151 }
1152 
1153 /* dir is direction to check, not the splitting edge direction! */
rct_fits(const rcti * rect,char dir,int size)1154 static int rct_fits(const rcti *rect, char dir, int size)
1155 {
1156   if (dir == 'h') {
1157     return BLI_rcti_size_x(rect) + 1 - size;
1158   }
1159   /* 'v' */
1160   return BLI_rcti_size_y(rect) + 1 - size;
1161 }
1162 
1163 /* *************************************************************** */
1164 
1165 /* region should be overlapping */
1166 /* function checks if some overlapping region was defined before - on same place */
region_overlap_fix(ScrArea * area,ARegion * region)1167 static void region_overlap_fix(ScrArea *area, ARegion *region)
1168 {
1169   /* find overlapping previous region on same place */
1170   ARegion *ar1;
1171   int align1 = 0;
1172   const int align = RGN_ALIGN_ENUM_FROM_MASK(region->alignment);
1173   for (ar1 = region->prev; ar1; ar1 = ar1->prev) {
1174     if (ar1->flag & RGN_FLAG_HIDDEN) {
1175       continue;
1176     }
1177 
1178     if (ar1->overlap && ((ar1->alignment & RGN_SPLIT_PREV) == 0)) {
1179       if (ELEM(ar1->alignment, RGN_ALIGN_FLOAT)) {
1180         continue;
1181       }
1182       align1 = ar1->alignment;
1183       if (BLI_rcti_isect(&ar1->winrct, &region->winrct, NULL)) {
1184         if (align1 != align) {
1185           /* Left overlapping right or vice-versa, forbid this! */
1186           region->flag |= RGN_FLAG_TOO_SMALL;
1187           return;
1188         }
1189         /* Else, we have our previous region on same side. */
1190         break;
1191       }
1192     }
1193   }
1194 
1195   /* Guard against flags slipping through that would have to be masked out in usages below. */
1196   BLI_assert(align1 == RGN_ALIGN_ENUM_FROM_MASK(align1));
1197 
1198   /* translate or close */
1199   if (ar1) {
1200     if (align1 == RGN_ALIGN_LEFT) {
1201       if (region->winrct.xmax + ar1->winx > area->winx - U.widget_unit) {
1202         region->flag |= RGN_FLAG_TOO_SMALL;
1203         return;
1204       }
1205       BLI_rcti_translate(&region->winrct, ar1->winx, 0);
1206     }
1207     else if (align1 == RGN_ALIGN_RIGHT) {
1208       if (region->winrct.xmin - ar1->winx < U.widget_unit) {
1209         region->flag |= RGN_FLAG_TOO_SMALL;
1210         return;
1211       }
1212       BLI_rcti_translate(&region->winrct, -ar1->winx, 0);
1213     }
1214   }
1215 
1216   /* At this point, 'region' is in its final position and still open.
1217    * Make a final check it does not overlap any previous 'other side' region. */
1218   for (ar1 = region->prev; ar1; ar1 = ar1->prev) {
1219     if (ar1->flag & RGN_FLAG_HIDDEN) {
1220       continue;
1221     }
1222     if (ELEM(ar1->alignment, RGN_ALIGN_FLOAT)) {
1223       continue;
1224     }
1225 
1226     if (ar1->overlap && (ar1->alignment & RGN_SPLIT_PREV) == 0) {
1227       if ((ar1->alignment != align) && BLI_rcti_isect(&ar1->winrct, &region->winrct, NULL)) {
1228         /* Left overlapping right or vice-versa, forbid this! */
1229         region->flag |= RGN_FLAG_TOO_SMALL;
1230         return;
1231       }
1232     }
1233   }
1234 }
1235 
1236 /* overlapping regions only in the following restricted cases */
ED_region_is_overlap(int spacetype,int regiontype)1237 bool ED_region_is_overlap(int spacetype, int regiontype)
1238 {
1239   if (regiontype == RGN_TYPE_HUD) {
1240     return true;
1241   }
1242   if (U.uiflag2 & USER_REGION_OVERLAP) {
1243     if (spacetype == SPACE_NODE) {
1244       if (regiontype == RGN_TYPE_TOOLS) {
1245         return true;
1246       }
1247     }
1248     else if (ELEM(spacetype, SPACE_VIEW3D, SPACE_IMAGE)) {
1249       if (ELEM(regiontype,
1250                RGN_TYPE_TOOLS,
1251                RGN_TYPE_UI,
1252                RGN_TYPE_TOOL_PROPS,
1253                RGN_TYPE_HEADER,
1254                RGN_TYPE_FOOTER)) {
1255         return true;
1256       }
1257     }
1258   }
1259 
1260   return false;
1261 }
1262 
region_rect_recursive(ScrArea * area,ARegion * region,rcti * remainder,rcti * overlap_remainder,int quad)1263 static void region_rect_recursive(
1264     ScrArea *area, ARegion *region, rcti *remainder, rcti *overlap_remainder, int quad)
1265 {
1266   rcti *remainder_prev = remainder;
1267 
1268   if (region == NULL) {
1269     return;
1270   }
1271 
1272   int prev_winx = region->winx;
1273   int prev_winy = region->winy;
1274 
1275   /* no returns in function, winrct gets set in the end again */
1276   BLI_rcti_init(&region->winrct, 0, 0, 0, 0);
1277 
1278   /* for test; allow split of previously defined region */
1279   if (region->alignment & RGN_SPLIT_PREV) {
1280     if (region->prev) {
1281       remainder = &region->prev->winrct;
1282     }
1283   }
1284 
1285   int alignment = RGN_ALIGN_ENUM_FROM_MASK(region->alignment);
1286 
1287   /* set here, assuming userpref switching forces to call this again */
1288   region->overlap = ED_region_is_overlap(area->spacetype, region->regiontype);
1289 
1290   /* clear state flags first */
1291   region->flag &= ~(RGN_FLAG_TOO_SMALL | RGN_FLAG_SIZE_CLAMP_X | RGN_FLAG_SIZE_CLAMP_Y);
1292   /* user errors */
1293   if ((region->next == NULL) && !ELEM(alignment, RGN_ALIGN_QSPLIT, RGN_ALIGN_FLOAT)) {
1294     alignment = RGN_ALIGN_NONE;
1295   }
1296 
1297   /* If both the ARegion.sizex/y and the prefsize are 0, the region is tagged as too small, even
1298    * before the layout for dynamic regions is created. #wm_draw_window_offscreen() allows the
1299    * layout to be created despite the RGN_FLAG_TOO_SMALL flag being set. But there may still be
1300    * regions that don't have a separate ARegionType.layout callback. For those, set a default
1301    * prefsize so they can become visible. */
1302   if ((region->flag & RGN_FLAG_DYNAMIC_SIZE) && !(region->type->layout)) {
1303     if ((region->sizex == 0) && (region->type->prefsizex == 0)) {
1304       region->type->prefsizex = AREAMINX;
1305     }
1306     if ((region->sizey == 0) && (region->type->prefsizey == 0)) {
1307       region->type->prefsizey = HEADERY;
1308     }
1309   }
1310 
1311   /* prefsize, taking into account DPI */
1312   int prefsizex = UI_DPI_FAC *
1313                   ((region->sizex > 1) ? region->sizex + 0.5f : region->type->prefsizex);
1314   int prefsizey;
1315 
1316   if (region->flag & RGN_FLAG_PREFSIZE_OR_HIDDEN) {
1317     prefsizex = UI_DPI_FAC * region->type->prefsizex;
1318     prefsizey = UI_DPI_FAC * region->type->prefsizey;
1319   }
1320   else if (region->regiontype == RGN_TYPE_HEADER) {
1321     prefsizey = ED_area_headersize();
1322   }
1323   else if (region->regiontype == RGN_TYPE_TOOL_HEADER) {
1324     prefsizey = ED_area_headersize();
1325   }
1326   else if (region->regiontype == RGN_TYPE_FOOTER) {
1327     prefsizey = ED_area_footersize();
1328   }
1329   else if (ED_area_is_global(area)) {
1330     prefsizey = ED_region_global_size_y();
1331   }
1332   else {
1333     prefsizey = UI_DPI_FAC * (region->sizey > 1 ? region->sizey + 0.5f : region->type->prefsizey);
1334   }
1335 
1336   if (region->flag & RGN_FLAG_HIDDEN) {
1337     /* hidden is user flag */
1338   }
1339   else if (alignment == RGN_ALIGN_FLOAT) {
1340     /**
1341      * \note Currently this window type is only used for #RGN_TYPE_HUD,
1342      * We expect the panel to resize it's self to be larger.
1343      *
1344      * This aligns to the lower left of the area.
1345      */
1346     const int size_min[2] = {UI_UNIT_X, UI_UNIT_Y};
1347     rcti overlap_remainder_margin = *overlap_remainder;
1348 
1349     BLI_rcti_resize(&overlap_remainder_margin,
1350                     max_ii(0, BLI_rcti_size_x(overlap_remainder) - UI_UNIT_X / 2),
1351                     max_ii(0, BLI_rcti_size_y(overlap_remainder) - UI_UNIT_Y / 2));
1352     region->winrct.xmin = overlap_remainder_margin.xmin + region->runtime.offset_x;
1353     region->winrct.ymin = overlap_remainder_margin.ymin + region->runtime.offset_y;
1354     region->winrct.xmax = region->winrct.xmin + prefsizex - 1;
1355     region->winrct.ymax = region->winrct.ymin + prefsizey - 1;
1356 
1357     BLI_rcti_isect(&region->winrct, &overlap_remainder_margin, &region->winrct);
1358 
1359     if (BLI_rcti_size_x(&region->winrct) != prefsizex - 1) {
1360       region->flag |= RGN_FLAG_SIZE_CLAMP_X;
1361     }
1362     if (BLI_rcti_size_y(&region->winrct) != prefsizey - 1) {
1363       region->flag |= RGN_FLAG_SIZE_CLAMP_Y;
1364     }
1365 
1366     /* We need to use a test that wont have been previously clamped. */
1367     rcti winrct_test = {
1368         .xmin = region->winrct.xmin,
1369         .ymin = region->winrct.ymin,
1370         .xmax = region->winrct.xmin + size_min[0],
1371         .ymax = region->winrct.ymin + size_min[1],
1372     };
1373     BLI_rcti_isect(&winrct_test, &overlap_remainder_margin, &winrct_test);
1374     if (BLI_rcti_size_x(&winrct_test) < size_min[0] ||
1375         BLI_rcti_size_y(&winrct_test) < size_min[1]) {
1376       region->flag |= RGN_FLAG_TOO_SMALL;
1377     }
1378   }
1379   else if (rct_fits(remainder, 'v', 1) < 0 || rct_fits(remainder, 'h', 1) < 0) {
1380     /* remainder is too small for any usage */
1381     region->flag |= RGN_FLAG_TOO_SMALL;
1382   }
1383   else if (alignment == RGN_ALIGN_NONE) {
1384     /* typically last region */
1385     region->winrct = *remainder;
1386     BLI_rcti_init(remainder, 0, 0, 0, 0);
1387   }
1388   else if (alignment == RGN_ALIGN_TOP || alignment == RGN_ALIGN_BOTTOM) {
1389     rcti *winrct = (region->overlap) ? overlap_remainder : remainder;
1390 
1391     if ((prefsizey == 0) || (rct_fits(winrct, 'v', prefsizey) < 0)) {
1392       region->flag |= RGN_FLAG_TOO_SMALL;
1393     }
1394     else {
1395       int fac = rct_fits(winrct, 'v', prefsizey);
1396 
1397       if (fac < 0) {
1398         prefsizey += fac;
1399       }
1400 
1401       region->winrct = *winrct;
1402 
1403       if (alignment == RGN_ALIGN_TOP) {
1404         region->winrct.ymin = region->winrct.ymax - prefsizey + 1;
1405         winrct->ymax = region->winrct.ymin - 1;
1406       }
1407       else {
1408         region->winrct.ymax = region->winrct.ymin + prefsizey - 1;
1409         winrct->ymin = region->winrct.ymax + 1;
1410       }
1411       BLI_rcti_sanitize(winrct);
1412     }
1413   }
1414   else if (ELEM(alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
1415     rcti *winrct = (region->overlap) ? overlap_remainder : remainder;
1416 
1417     if ((prefsizex == 0) || (rct_fits(winrct, 'h', prefsizex) < 0)) {
1418       region->flag |= RGN_FLAG_TOO_SMALL;
1419     }
1420     else {
1421       int fac = rct_fits(winrct, 'h', prefsizex);
1422 
1423       if (fac < 0) {
1424         prefsizex += fac;
1425       }
1426 
1427       region->winrct = *winrct;
1428 
1429       if (alignment == RGN_ALIGN_RIGHT) {
1430         region->winrct.xmin = region->winrct.xmax - prefsizex + 1;
1431         winrct->xmax = region->winrct.xmin - 1;
1432       }
1433       else {
1434         region->winrct.xmax = region->winrct.xmin + prefsizex - 1;
1435         winrct->xmin = region->winrct.xmax + 1;
1436       }
1437       BLI_rcti_sanitize(winrct);
1438     }
1439   }
1440   else if (alignment == RGN_ALIGN_VSPLIT || alignment == RGN_ALIGN_HSPLIT) {
1441     /* percentage subdiv*/
1442     region->winrct = *remainder;
1443 
1444     if (alignment == RGN_ALIGN_HSPLIT) {
1445       if (rct_fits(remainder, 'h', prefsizex) > 4) {
1446         region->winrct.xmax = BLI_rcti_cent_x(remainder);
1447         remainder->xmin = region->winrct.xmax + 1;
1448       }
1449       else {
1450         BLI_rcti_init(remainder, 0, 0, 0, 0);
1451       }
1452     }
1453     else {
1454       if (rct_fits(remainder, 'v', prefsizey) > 4) {
1455         region->winrct.ymax = BLI_rcti_cent_y(remainder);
1456         remainder->ymin = region->winrct.ymax + 1;
1457       }
1458       else {
1459         BLI_rcti_init(remainder, 0, 0, 0, 0);
1460       }
1461     }
1462   }
1463   else if (alignment == RGN_ALIGN_QSPLIT) {
1464     region->winrct = *remainder;
1465 
1466     /* test if there's still 4 regions left */
1467     if (quad == 0) {
1468       ARegion *artest = region->next;
1469       int count = 1;
1470 
1471       while (artest) {
1472         artest->alignment = RGN_ALIGN_QSPLIT;
1473         artest = artest->next;
1474         count++;
1475       }
1476 
1477       if (count != 4) {
1478         /* let's stop adding regions */
1479         BLI_rcti_init(remainder, 0, 0, 0, 0);
1480         if (G.debug & G_DEBUG) {
1481           printf("region quadsplit failed\n");
1482         }
1483       }
1484       else {
1485         quad = 1;
1486       }
1487     }
1488     if (quad) {
1489       if (quad == 1) { /* left bottom */
1490         region->winrct.xmax = BLI_rcti_cent_x(remainder);
1491         region->winrct.ymax = BLI_rcti_cent_y(remainder);
1492       }
1493       else if (quad == 2) { /* left top */
1494         region->winrct.xmax = BLI_rcti_cent_x(remainder);
1495         region->winrct.ymin = BLI_rcti_cent_y(remainder) + 1;
1496       }
1497       else if (quad == 3) { /* right bottom */
1498         region->winrct.xmin = BLI_rcti_cent_x(remainder) + 1;
1499         region->winrct.ymax = BLI_rcti_cent_y(remainder);
1500       }
1501       else { /* right top */
1502         region->winrct.xmin = BLI_rcti_cent_x(remainder) + 1;
1503         region->winrct.ymin = BLI_rcti_cent_y(remainder) + 1;
1504         BLI_rcti_init(remainder, 0, 0, 0, 0);
1505       }
1506 
1507       /* Fix any negative dimensions. This can happen when a quad split 3d view gets to small. (see
1508        * T72200). */
1509       BLI_rcti_sanitize(&region->winrct);
1510 
1511       quad++;
1512     }
1513   }
1514 
1515   /* for speedup */
1516   region->winx = BLI_rcti_size_x(&region->winrct) + 1;
1517   region->winy = BLI_rcti_size_y(&region->winrct) + 1;
1518 
1519   /* if region opened normally, we store this for hide/reveal usage */
1520   /* prevent rounding errors for UI_DPI_FAC mult and divide */
1521   if (region->winx > 1) {
1522     region->sizex = (region->winx + 0.5f) / UI_DPI_FAC;
1523   }
1524   if (region->winy > 1) {
1525     region->sizey = (region->winy + 0.5f) / UI_DPI_FAC;
1526   }
1527 
1528   /* exception for multiple overlapping regions on same spot */
1529   if (region->overlap && (alignment != RGN_ALIGN_FLOAT)) {
1530     region_overlap_fix(area, region);
1531   }
1532 
1533   /* set winrect for azones */
1534   if (region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) {
1535     region->winrct = (region->overlap) ? *overlap_remainder : *remainder;
1536 
1537     switch (alignment) {
1538       case RGN_ALIGN_TOP:
1539         region->winrct.ymin = region->winrct.ymax;
1540         break;
1541       case RGN_ALIGN_BOTTOM:
1542         region->winrct.ymax = region->winrct.ymin;
1543         break;
1544       case RGN_ALIGN_RIGHT:
1545         region->winrct.xmin = region->winrct.xmax;
1546         break;
1547       case RGN_ALIGN_LEFT:
1548         region->winrct.xmax = region->winrct.xmin;
1549         break;
1550       default:
1551         /* prevent winrct to be valid */
1552         region->winrct.xmax = region->winrct.xmin;
1553         break;
1554     }
1555 
1556     /* Size on one axis is now 0, the other axis may still be invalid (negative) though. */
1557     BLI_rcti_sanitize(&region->winrct);
1558   }
1559 
1560   /* restore prev-split exception */
1561   if (region->alignment & RGN_SPLIT_PREV) {
1562     if (region->prev) {
1563       remainder = remainder_prev;
1564       region->prev->winx = BLI_rcti_size_x(&region->prev->winrct) + 1;
1565       region->prev->winy = BLI_rcti_size_y(&region->prev->winrct) + 1;
1566     }
1567   }
1568 
1569   /* After non-overlapping region, all following overlapping regions
1570    * fit within the remaining space again. */
1571   if (!region->overlap) {
1572     *overlap_remainder = *remainder;
1573   }
1574 
1575   BLI_assert(BLI_rcti_is_valid(&region->winrct));
1576 
1577   region_rect_recursive(area, region->next, remainder, overlap_remainder, quad);
1578 
1579   /* Tag for redraw if size changes. */
1580   if (region->winx != prev_winx || region->winy != prev_winy) {
1581     /* 3D View needs a full rebuild in case a progressive render runs. Rest can live with
1582      * no-rebuild (e.g. Outliner) */
1583     if (area->spacetype == SPACE_VIEW3D) {
1584       ED_region_tag_redraw(region);
1585     }
1586     else {
1587       ED_region_tag_redraw_no_rebuild(region);
1588     }
1589   }
1590 
1591   /* Clear, initialize on demand. */
1592   memset(&region->runtime.visible_rect, 0, sizeof(region->runtime.visible_rect));
1593 }
1594 
area_calc_totrct(ScrArea * area,const rcti * window_rect)1595 static void area_calc_totrct(ScrArea *area, const rcti *window_rect)
1596 {
1597   short px = (short)U.pixelsize;
1598 
1599   area->totrct.xmin = area->v1->vec.x;
1600   area->totrct.xmax = area->v4->vec.x;
1601   area->totrct.ymin = area->v1->vec.y;
1602   area->totrct.ymax = area->v2->vec.y;
1603 
1604   /* scale down totrct by 1 pixel on all sides not matching window borders */
1605   if (area->totrct.xmin > window_rect->xmin) {
1606     area->totrct.xmin += px;
1607   }
1608   if (area->totrct.xmax < (window_rect->xmax - 1)) {
1609     area->totrct.xmax -= px;
1610   }
1611   if (area->totrct.ymin > window_rect->ymin) {
1612     area->totrct.ymin += px;
1613   }
1614   if (area->totrct.ymax < (window_rect->ymax - 1)) {
1615     area->totrct.ymax -= px;
1616   }
1617   /* Although the following asserts are correct they lead to a very unstable Blender.
1618    * And the asserts would fail even in 2.7x
1619    * (they were added in 2.8x as part of the top-bar commit).
1620    * For more details see T54864. */
1621 #if 0
1622   BLI_assert(area->totrct.xmin >= 0);
1623   BLI_assert(area->totrct.xmax >= 0);
1624   BLI_assert(area->totrct.ymin >= 0);
1625   BLI_assert(area->totrct.ymax >= 0);
1626 #endif
1627 
1628   /* for speedup */
1629   area->winx = BLI_rcti_size_x(&area->totrct) + 1;
1630   area->winy = BLI_rcti_size_y(&area->totrct) + 1;
1631 }
1632 
1633 /* used for area initialize below */
region_subwindow(ARegion * region)1634 static void region_subwindow(ARegion *region)
1635 {
1636   bool hidden = (region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) != 0;
1637 
1638   if ((region->alignment & RGN_SPLIT_PREV) && region->prev) {
1639     hidden = hidden || (region->prev->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
1640   }
1641 
1642   region->visible = !hidden;
1643 }
1644 
event_in_markers_region(const ARegion * region,const wmEvent * event)1645 static bool event_in_markers_region(const ARegion *region, const wmEvent *event)
1646 {
1647   rcti rect = region->winrct;
1648   rect.ymax = rect.ymin + UI_MARKER_MARGIN_Y;
1649   return BLI_rcti_isect_pt(&rect, event->x, event->y);
1650 }
1651 
1652 /**
1653  * \param region: Region, may be NULL when adding handlers for \a area.
1654  */
ed_default_handlers(wmWindowManager * wm,ScrArea * area,ARegion * region,ListBase * handlers,int flag)1655 static void ed_default_handlers(
1656     wmWindowManager *wm, ScrArea *area, ARegion *region, ListBase *handlers, int flag)
1657 {
1658   BLI_assert(region ? (&region->handlers == handlers) : (&area->handlers == handlers));
1659 
1660   /* note, add-handler checks if it already exists */
1661 
1662   /* XXX it would be good to have boundbox checks for some of these... */
1663   if (flag & ED_KEYMAP_UI) {
1664     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "User Interface", 0, 0);
1665     WM_event_add_keymap_handler(handlers, keymap);
1666 
1667     /* user interface widgets */
1668     UI_region_handlers_add(handlers);
1669   }
1670   if (flag & ED_KEYMAP_GIZMO) {
1671     BLI_assert(region && ELEM(region->type->regionid, RGN_TYPE_WINDOW, RGN_TYPE_PREVIEW));
1672     if (region) {
1673       /* Anything else is confusing, only allow this. */
1674       BLI_assert(&region->handlers == handlers);
1675       if (region->gizmo_map == NULL) {
1676         region->gizmo_map = WM_gizmomap_new_from_type(
1677             &(const struct wmGizmoMapType_Params){area->spacetype, region->type->regionid});
1678       }
1679       WM_gizmomap_add_handlers(region, region->gizmo_map);
1680     }
1681   }
1682   if (flag & ED_KEYMAP_VIEW2D) {
1683     /* 2d-viewport handling+manipulation */
1684     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D", 0, 0);
1685     WM_event_add_keymap_handler(handlers, keymap);
1686   }
1687   if (flag & ED_KEYMAP_ANIMATION) {
1688     wmKeyMap *keymap;
1689 
1690     /* time-markers */
1691     keymap = WM_keymap_ensure(wm->defaultconf, "Markers", 0, 0);
1692     WM_event_add_keymap_handler_poll(handlers, keymap, event_in_markers_region);
1693 
1694     /* time-scrub */
1695     keymap = WM_keymap_ensure(wm->defaultconf, "Time Scrub", 0, 0);
1696     WM_event_add_keymap_handler_poll(handlers, keymap, ED_time_scrub_event_in_region);
1697 
1698     /* frame changing and timeline operators (for time spaces) */
1699     keymap = WM_keymap_ensure(wm->defaultconf, "Animation", 0, 0);
1700     WM_event_add_keymap_handler(handlers, keymap);
1701   }
1702   if (flag & ED_KEYMAP_TOOL) {
1703     WM_event_add_keymap_handler_dynamic(
1704         &region->handlers, WM_event_get_keymap_from_toolsystem_fallback, area);
1705     WM_event_add_keymap_handler_dynamic(
1706         &region->handlers, WM_event_get_keymap_from_toolsystem, area);
1707   }
1708   if (flag & ED_KEYMAP_FRAMES) {
1709     /* frame changing/jumping (for all spaces) */
1710     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Frames", 0, 0);
1711     WM_event_add_keymap_handler(handlers, keymap);
1712   }
1713   if (flag & ED_KEYMAP_HEADER) {
1714     /* standard keymap for headers regions */
1715     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Region Context Menu", 0, 0);
1716     WM_event_add_keymap_handler(handlers, keymap);
1717   }
1718   if (flag & ED_KEYMAP_FOOTER) {
1719     /* standard keymap for footer regions */
1720     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Region Context Menu", 0, 0);
1721     WM_event_add_keymap_handler(handlers, keymap);
1722   }
1723   if (flag & ED_KEYMAP_NAVBAR) {
1724     /* standard keymap for Navigation bar regions */
1725     wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "Region Context Menu", 0, 0);
1726     WM_event_add_keymap_handler(&region->handlers, keymap);
1727   }
1728 
1729   /* Keep last because of LMB/RMB handling, see: T57527. */
1730   if (flag & ED_KEYMAP_GPENCIL) {
1731     /* grease pencil */
1732     /* NOTE: This is now 4 keymaps - One for basic functionality,
1733      *       and others for special stroke modes (edit, paint and sculpt).
1734      *
1735      *       For now, it's easier to just include all,
1736      *       since you hardly want one without the others.
1737      */
1738     wmKeyMap *keymap_general = WM_keymap_ensure(wm->defaultconf, "Grease Pencil", 0, 0);
1739     WM_event_add_keymap_handler(handlers, keymap_general);
1740 
1741     wmKeyMap *keymap_edit = WM_keymap_ensure(
1742         wm->defaultconf, "Grease Pencil Stroke Edit Mode", 0, 0);
1743     WM_event_add_keymap_handler(handlers, keymap_edit);
1744 
1745     wmKeyMap *keymap_paint = WM_keymap_ensure(
1746         wm->defaultconf, "Grease Pencil Stroke Paint Mode", 0, 0);
1747     WM_event_add_keymap_handler(handlers, keymap_paint);
1748 
1749     wmKeyMap *keymap_paint_draw = WM_keymap_ensure(
1750         wm->defaultconf, "Grease Pencil Stroke Paint (Draw brush)", 0, 0);
1751     WM_event_add_keymap_handler(handlers, keymap_paint_draw);
1752 
1753     wmKeyMap *keymap_paint_erase = WM_keymap_ensure(
1754         wm->defaultconf, "Grease Pencil Stroke Paint (Erase)", 0, 0);
1755     WM_event_add_keymap_handler(handlers, keymap_paint_erase);
1756 
1757     wmKeyMap *keymap_paint_fill = WM_keymap_ensure(
1758         wm->defaultconf, "Grease Pencil Stroke Paint (Fill)", 0, 0);
1759     WM_event_add_keymap_handler(handlers, keymap_paint_fill);
1760 
1761     wmKeyMap *keymap_paint_tint = WM_keymap_ensure(
1762         wm->defaultconf, "Grease Pencil Stroke Paint (Tint)", 0, 0);
1763     WM_event_add_keymap_handler(handlers, keymap_paint_tint);
1764 
1765     wmKeyMap *keymap_sculpt = WM_keymap_ensure(
1766         wm->defaultconf, "Grease Pencil Stroke Sculpt Mode", 0, 0);
1767     WM_event_add_keymap_handler(handlers, keymap_sculpt);
1768 
1769     wmKeyMap *keymap_vertex = WM_keymap_ensure(
1770         wm->defaultconf, "Grease Pencil Stroke Vertex Mode", 0, 0);
1771     WM_event_add_keymap_handler(handlers, keymap_vertex);
1772 
1773     wmKeyMap *keymap_vertex_draw = WM_keymap_ensure(
1774         wm->defaultconf, "Grease Pencil Stroke Vertex (Draw)", 0, 0);
1775     WM_event_add_keymap_handler(handlers, keymap_vertex_draw);
1776 
1777     wmKeyMap *keymap_vertex_blur = WM_keymap_ensure(
1778         wm->defaultconf, "Grease Pencil Stroke Vertex (Blur)", 0, 0);
1779     WM_event_add_keymap_handler(handlers, keymap_vertex_blur);
1780 
1781     wmKeyMap *keymap_vertex_average = WM_keymap_ensure(
1782         wm->defaultconf, "Grease Pencil Stroke Vertex (Average)", 0, 0);
1783     WM_event_add_keymap_handler(handlers, keymap_vertex_average);
1784 
1785     wmKeyMap *keymap_vertex_smear = WM_keymap_ensure(
1786         wm->defaultconf, "Grease Pencil Stroke Vertex (Smear)", 0, 0);
1787     WM_event_add_keymap_handler(handlers, keymap_vertex_smear);
1788 
1789     wmKeyMap *keymap_vertex_replace = WM_keymap_ensure(
1790         wm->defaultconf, "Grease Pencil Stroke Vertex (Replace)", 0, 0);
1791     WM_event_add_keymap_handler(handlers, keymap_vertex_replace);
1792 
1793     wmKeyMap *keymap_sculpt_smooth = WM_keymap_ensure(
1794         wm->defaultconf, "Grease Pencil Stroke Sculpt (Smooth)", 0, 0);
1795     WM_event_add_keymap_handler(handlers, keymap_sculpt_smooth);
1796 
1797     wmKeyMap *keymap_sculpt_thickness = WM_keymap_ensure(
1798         wm->defaultconf, "Grease Pencil Stroke Sculpt (Thickness)", 0, 0);
1799     WM_event_add_keymap_handler(handlers, keymap_sculpt_thickness);
1800 
1801     wmKeyMap *keymap_sculpt_strength = WM_keymap_ensure(
1802         wm->defaultconf, "Grease Pencil Stroke Sculpt (Strength)", 0, 0);
1803     WM_event_add_keymap_handler(handlers, keymap_sculpt_strength);
1804 
1805     wmKeyMap *keymap_sculpt_grab = WM_keymap_ensure(
1806         wm->defaultconf, "Grease Pencil Stroke Sculpt (Grab)", 0, 0);
1807     WM_event_add_keymap_handler(handlers, keymap_sculpt_grab);
1808 
1809     wmKeyMap *keymap_sculpt_push = WM_keymap_ensure(
1810         wm->defaultconf, "Grease Pencil Stroke Sculpt (Push)", 0, 0);
1811     WM_event_add_keymap_handler(handlers, keymap_sculpt_push);
1812 
1813     wmKeyMap *keymap_sculpt_twist = WM_keymap_ensure(
1814         wm->defaultconf, "Grease Pencil Stroke Sculpt (Twist)", 0, 0);
1815     WM_event_add_keymap_handler(handlers, keymap_sculpt_twist);
1816 
1817     wmKeyMap *keymap_sculpt_pinch = WM_keymap_ensure(
1818         wm->defaultconf, "Grease Pencil Stroke Sculpt (Pinch)", 0, 0);
1819     WM_event_add_keymap_handler(handlers, keymap_sculpt_pinch);
1820 
1821     wmKeyMap *keymap_sculpt_randomize = WM_keymap_ensure(
1822         wm->defaultconf, "Grease Pencil Stroke Sculpt (Randomize)", 0, 0);
1823     WM_event_add_keymap_handler(handlers, keymap_sculpt_randomize);
1824 
1825     wmKeyMap *keymap_sculpt_clone = WM_keymap_ensure(
1826         wm->defaultconf, "Grease Pencil Stroke Sculpt (Clone)", 0, 0);
1827     WM_event_add_keymap_handler(handlers, keymap_sculpt_clone);
1828 
1829     wmKeyMap *keymap_weight = WM_keymap_ensure(
1830         wm->defaultconf, "Grease Pencil Stroke Weight Mode", 0, 0);
1831     WM_event_add_keymap_handler(handlers, keymap_weight);
1832 
1833     wmKeyMap *keymap_weight_draw = WM_keymap_ensure(
1834         wm->defaultconf, "Grease Pencil Stroke Weight (Draw)", 0, 0);
1835     WM_event_add_keymap_handler(handlers, keymap_weight_draw);
1836   }
1837 }
1838 
ED_area_update_region_sizes(wmWindowManager * wm,wmWindow * win,ScrArea * area)1839 void ED_area_update_region_sizes(wmWindowManager *wm, wmWindow *win, ScrArea *area)
1840 {
1841   if (!(area->flag & AREA_FLAG_REGION_SIZE_UPDATE)) {
1842     return;
1843   }
1844   const bScreen *screen = WM_window_get_active_screen(win);
1845 
1846   rcti window_rect;
1847   WM_window_rect_calc(win, &window_rect);
1848   area_calc_totrct(area, &window_rect);
1849 
1850   /* region rect sizes */
1851   rcti rect = area->totrct;
1852   rcti overlap_rect = rect;
1853   region_rect_recursive(area, area->regionbase.first, &rect, &overlap_rect, 0);
1854 
1855   /* Dynamically sized regions may have changed region sizes, so we have to force azone update. */
1856   area_azone_init(win, screen, area);
1857 
1858   LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
1859     region_subwindow(region);
1860 
1861     /* region size may have changed, init does necessary adjustments */
1862     if (region->type->init) {
1863       region->type->init(wm, region);
1864     }
1865 
1866     /* Some AZones use View2D data which is only updated in region init, so call that first! */
1867     region_azones_add(screen, area, region);
1868   }
1869   ED_area_azones_update(area, &win->eventstate->x);
1870 
1871   area->flag &= ~AREA_FLAG_REGION_SIZE_UPDATE;
1872 }
1873 
1874 /* called in screen_refresh, or screens_init, also area size changes */
ED_area_init(wmWindowManager * wm,wmWindow * win,ScrArea * area)1875 void ED_area_init(wmWindowManager *wm, wmWindow *win, ScrArea *area)
1876 {
1877   WorkSpace *workspace = WM_window_get_active_workspace(win);
1878   const bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook);
1879   ViewLayer *view_layer = WM_window_get_active_view_layer(win);
1880 
1881   if (ED_area_is_global(area) && (area->global->flag & GLOBAL_AREA_IS_HIDDEN)) {
1882     return;
1883   }
1884 
1885   rcti window_rect;
1886   WM_window_rect_calc(win, &window_rect);
1887 
1888   /* set typedefinitions */
1889   area->type = BKE_spacetype_from_id(area->spacetype);
1890 
1891   if (area->type == NULL) {
1892     area->spacetype = SPACE_VIEW3D;
1893     area->type = BKE_spacetype_from_id(area->spacetype);
1894   }
1895 
1896   LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
1897     region->type = BKE_regiontype_from_id_or_first(area->type, region->regiontype);
1898   }
1899 
1900   /* area sizes */
1901   area_calc_totrct(area, &window_rect);
1902 
1903   /* region rect sizes */
1904   rcti rect = area->totrct;
1905   rcti overlap_rect = rect;
1906   region_rect_recursive(area, area->regionbase.first, &rect, &overlap_rect, 0);
1907   area->flag &= ~AREA_FLAG_REGION_SIZE_UPDATE;
1908 
1909   /* default area handlers */
1910   ed_default_handlers(wm, area, NULL, &area->handlers, area->type->keymapflag);
1911   /* checks spacedata, adds own handlers */
1912   if (area->type->init) {
1913     area->type->init(wm, area);
1914   }
1915 
1916   /* clear all azones, add the area triangle widgets */
1917   area_azone_init(win, screen, area);
1918 
1919   /* region windows, default and own handlers */
1920   LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
1921     region_subwindow(region);
1922 
1923     if (region->visible) {
1924       /* default region handlers */
1925       ed_default_handlers(wm, area, region, &region->handlers, region->type->keymapflag);
1926       /* own handlers */
1927       if (region->type->init) {
1928         region->type->init(wm, region);
1929       }
1930     }
1931     else {
1932       /* prevent uiblocks to run */
1933       UI_blocklist_free(NULL, &region->uiblocks);
1934     }
1935 
1936     /* Some AZones use View2D data which is only updated in region init, so call that first! */
1937     region_azones_add(screen, area, region);
1938   }
1939 
1940   /* Avoid re-initializing tools while resizing the window. */
1941   if ((G.moving & G_TRANSFORM_WM) == 0) {
1942     if ((1 << area->spacetype) & WM_TOOLSYSTEM_SPACE_MASK) {
1943       WM_toolsystem_refresh_screen_area(workspace, view_layer, area);
1944       area->flag |= AREA_FLAG_ACTIVE_TOOL_UPDATE;
1945     }
1946     else {
1947       area->runtime.tool = NULL;
1948       area->runtime.is_tool_set = true;
1949     }
1950   }
1951 }
1952 
region_update_rect(ARegion * region)1953 static void region_update_rect(ARegion *region)
1954 {
1955   region->winx = BLI_rcti_size_x(&region->winrct) + 1;
1956   region->winy = BLI_rcti_size_y(&region->winrct) + 1;
1957 
1958   /* v2d mask is used to subtract scrollbars from a 2d view. Needs initialize here. */
1959   BLI_rcti_init(&region->v2d.mask, 0, region->winx - 1, 0, region->winy - 1);
1960 }
1961 
1962 /**
1963  * Call to move a popup window (keep OpenGL context free!)
1964  */
ED_region_update_rect(ARegion * region)1965 void ED_region_update_rect(ARegion *region)
1966 {
1967   region_update_rect(region);
1968 }
1969 
1970 /* externally called for floating regions like menus */
ED_region_floating_init(ARegion * region)1971 void ED_region_floating_init(ARegion *region)
1972 {
1973   BLI_assert(region->alignment == RGN_ALIGN_FLOAT);
1974 
1975   /* refresh can be called before window opened */
1976   region_subwindow(region);
1977 
1978   region_update_rect(region);
1979 }
1980 
ED_region_cursor_set(wmWindow * win,ScrArea * area,ARegion * region)1981 void ED_region_cursor_set(wmWindow *win, ScrArea *area, ARegion *region)
1982 {
1983   if (region != NULL) {
1984     if ((region->gizmo_map != NULL) && WM_gizmomap_cursor_set(region->gizmo_map, win)) {
1985       return;
1986     }
1987     if (area && region->type && region->type->cursor) {
1988       region->type->cursor(win, area, region);
1989       return;
1990     }
1991   }
1992 
1993   if (WM_cursor_set_from_tool(win, area, region)) {
1994     return;
1995   }
1996 
1997   WM_cursor_set(win, WM_CURSOR_DEFAULT);
1998 }
1999 
2000 /* for use after changing visibility of regions */
ED_region_visibility_change_update(bContext * C,ScrArea * area,ARegion * region)2001 void ED_region_visibility_change_update(bContext *C, ScrArea *area, ARegion *region)
2002 {
2003   if (region->flag & RGN_FLAG_HIDDEN) {
2004     WM_event_remove_handlers(C, &region->handlers);
2005   }
2006 
2007   ED_area_init(CTX_wm_manager(C), CTX_wm_window(C), area);
2008   ED_area_tag_redraw(area);
2009 }
2010 
2011 /* for quick toggle, can skip fades */
region_toggle_hidden(bContext * C,ARegion * region,const bool do_fade)2012 void region_toggle_hidden(bContext *C, ARegion *region, const bool do_fade)
2013 {
2014   ScrArea *area = CTX_wm_area(C);
2015 
2016   region->flag ^= RGN_FLAG_HIDDEN;
2017 
2018   if (do_fade && region->overlap) {
2019     /* starts a timer, and in end calls the stuff below itself (region_sblend_invoke()) */
2020     ED_region_visibility_change_update_animated(C, area, region);
2021   }
2022   else {
2023     ED_region_visibility_change_update(C, area, region);
2024   }
2025 }
2026 
2027 /* exported to all editors, uses fading default */
ED_region_toggle_hidden(bContext * C,ARegion * region)2028 void ED_region_toggle_hidden(bContext *C, ARegion *region)
2029 {
2030   region_toggle_hidden(C, region, true);
2031 }
2032 
2033 /**
2034  * we swap spaces for fullscreen to keep all allocated data area vertices were set
2035  */
ED_area_data_copy(ScrArea * area_dst,ScrArea * area_src,const bool do_free)2036 void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, const bool do_free)
2037 {
2038   const char spacetype = area_dst->spacetype;
2039   const short flag_copy = HEADER_NO_PULLDOWN;
2040 
2041   area_dst->spacetype = area_src->spacetype;
2042   area_dst->type = area_src->type;
2043 
2044   area_dst->flag = (area_dst->flag & ~flag_copy) | (area_src->flag & flag_copy);
2045 
2046   /* area */
2047   if (do_free) {
2048     BKE_spacedata_freelist(&area_dst->spacedata);
2049   }
2050   BKE_spacedata_copylist(&area_dst->spacedata, &area_src->spacedata);
2051 
2052   /* Note; SPACE_EMPTY is possible on new screens */
2053 
2054   /* regions */
2055   if (do_free) {
2056     SpaceType *st = BKE_spacetype_from_id(spacetype);
2057     LISTBASE_FOREACH (ARegion *, region, &area_dst->regionbase) {
2058       BKE_area_region_free(st, region);
2059     }
2060     BLI_freelistN(&area_dst->regionbase);
2061   }
2062   SpaceType *st = BKE_spacetype_from_id(area_src->spacetype);
2063   LISTBASE_FOREACH (ARegion *, region, &area_src->regionbase) {
2064     ARegion *newar = BKE_area_region_copy(st, region);
2065     BLI_addtail(&area_dst->regionbase, newar);
2066   }
2067 }
2068 
ED_area_data_swap(ScrArea * area_dst,ScrArea * area_src)2069 void ED_area_data_swap(ScrArea *area_dst, ScrArea *area_src)
2070 {
2071   SWAP(char, area_dst->spacetype, area_src->spacetype);
2072   SWAP(SpaceType *, area_dst->type, area_src->type);
2073 
2074   SWAP(ListBase, area_dst->spacedata, area_src->spacedata);
2075   SWAP(ListBase, area_dst->regionbase, area_src->regionbase);
2076 }
2077 
2078 /* -------------------------------------------------------------------- */
2079 /** \name Region Alignment Syncing for Space Switching
2080  * \{ */
2081 
2082 /**
2083  * Store the alignment & other info per region type
2084  * (use as a region-type aligned array).
2085  *
2086  * \note Currently this is only done for headers,
2087  * we might want to do this with the tool-bar in the future too.
2088  */
2089 struct RegionTypeAlignInfo {
2090   struct {
2091     /**
2092      * Values match #ARegion.alignment without flags (see #RGN_ALIGN_ENUM_FROM_MASK).
2093      * store all so we can sync alignment without adding extra checks.
2094      */
2095     short alignment;
2096     /**
2097      * Needed for detecting which header displays the space-type switcher.
2098      */
2099     bool hidden;
2100   } by_type[RGN_TYPE_LEN];
2101 };
2102 
region_align_info_from_area(ScrArea * area,struct RegionTypeAlignInfo * r_align_info)2103 static void region_align_info_from_area(ScrArea *area, struct RegionTypeAlignInfo *r_align_info)
2104 {
2105   for (int index = 0; index < RGN_TYPE_LEN; index++) {
2106     r_align_info->by_type[index].alignment = -1;
2107     /* Default to true, when it doesn't exist - it's effectively hidden. */
2108     r_align_info->by_type[index].hidden = true;
2109   }
2110 
2111   LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
2112     const int index = region->regiontype;
2113     if ((uint)index < RGN_TYPE_LEN) {
2114       r_align_info->by_type[index].alignment = RGN_ALIGN_ENUM_FROM_MASK(region->alignment);
2115       r_align_info->by_type[index].hidden = (region->flag & RGN_FLAG_HIDDEN) != 0;
2116     }
2117   }
2118 }
2119 
2120 /**
2121  * Keeping alignment between headers keep the space-type selector button in the same place.
2122  * This is complicated by the editor-type selector being placed on the header
2123  * closest to the screen edge which changes based on hidden state.
2124  *
2125  * The tool-header is used when visible, otherwise the header is used.
2126  */
region_alignment_from_header_and_tool_header_state(const struct RegionTypeAlignInfo * region_align_info,const short fallback)2127 static short region_alignment_from_header_and_tool_header_state(
2128     const struct RegionTypeAlignInfo *region_align_info, const short fallback)
2129 {
2130   const short header_alignment = region_align_info->by_type[RGN_TYPE_HEADER].alignment;
2131   const short tool_header_alignment = region_align_info->by_type[RGN_TYPE_TOOL_HEADER].alignment;
2132 
2133   const bool header_hidden = region_align_info->by_type[RGN_TYPE_HEADER].hidden;
2134   const bool tool_header_hidden = region_align_info->by_type[RGN_TYPE_TOOL_HEADER].hidden;
2135 
2136   if ((tool_header_alignment != -1) &&
2137       /* If tool-header is hidden, use header alignment. */
2138       ((tool_header_hidden == false) ||
2139        /* Don't prioritize the tool-header if both are hidden (behave as if both are visible).
2140         * Without this, switching to a space with headers hidden will flip the alignment
2141         * upon switching to a space with visible headers. */
2142        (header_hidden && tool_header_hidden))) {
2143     return tool_header_alignment;
2144   }
2145   if (header_alignment != -1) {
2146     return header_alignment;
2147   }
2148   return fallback;
2149 }
2150 
2151 /**
2152  * Notes on header alignment syncing.
2153  *
2154  * This is as involved as it is because:
2155  *
2156  * - There are currently 3 kinds of headers.
2157  * - All headers can independently visible & flipped to another side
2158  *   (except for the tool-header that depends on the header visibility).
2159  * - We don't want the space-switching button to flip when switching spaces.
2160  *   From the user perspective it feels like a bug to move the button you click on
2161  *   to the opposite side of the area.
2162  * - The space-switcher may be on either the header or the tool-header
2163  *   depending on the tool-header visibility.
2164  *
2165  * How this works:
2166  *
2167  * - When headers match on both spaces, we copy the alignment
2168  *   from the previous regions to the next regions when syncing.
2169  * - Otherwise detect the _primary_ header (the one that shows the space type)
2170  *   and use this to set alignment for the headers in the destination area.
2171  * - Header & tool-header/footer may be on opposite sides, this is preserved when syncing.
2172  */
region_align_info_to_area_for_headers(const struct RegionTypeAlignInfo * region_align_info_src,const struct RegionTypeAlignInfo * region_align_info_dst,ARegion * region_by_type[RGN_TYPE_LEN])2173 static void region_align_info_to_area_for_headers(
2174     const struct RegionTypeAlignInfo *region_align_info_src,
2175     const struct RegionTypeAlignInfo *region_align_info_dst,
2176     ARegion *region_by_type[RGN_TYPE_LEN])
2177 {
2178   /* Abbreviate access. */
2179   const short header_alignment_src = region_align_info_src->by_type[RGN_TYPE_HEADER].alignment;
2180   const short tool_header_alignment_src =
2181       region_align_info_src->by_type[RGN_TYPE_TOOL_HEADER].alignment;
2182 
2183   const bool tool_header_hidden_src = region_align_info_src->by_type[RGN_TYPE_TOOL_HEADER].hidden;
2184 
2185   const short primary_header_alignment_src = region_alignment_from_header_and_tool_header_state(
2186       region_align_info_src, -1);
2187 
2188   /* Neither alignments are usable, don't sync. */
2189   if (primary_header_alignment_src == -1) {
2190     return;
2191   }
2192 
2193   const short header_alignment_dst = region_align_info_dst->by_type[RGN_TYPE_HEADER].alignment;
2194   const short tool_header_alignment_dst =
2195       region_align_info_dst->by_type[RGN_TYPE_TOOL_HEADER].alignment;
2196   const short footer_alignment_dst = region_align_info_dst->by_type[RGN_TYPE_FOOTER].alignment;
2197 
2198   const bool tool_header_hidden_dst = region_align_info_dst->by_type[RGN_TYPE_TOOL_HEADER].hidden;
2199 
2200   /* New synchronized alignments to set (or ignore when left as -1). */
2201   short header_alignment_sync = -1;
2202   short tool_header_alignment_sync = -1;
2203   short footer_alignment_sync = -1;
2204 
2205   /* Both source/destination areas have same region configurations regarding headers.
2206    * Simply copy the values. */
2207   if (((header_alignment_src != -1) == (header_alignment_dst != -1)) &&
2208       ((tool_header_alignment_src != -1) == (tool_header_alignment_dst != -1)) &&
2209       (tool_header_hidden_src == tool_header_hidden_dst)) {
2210     if (header_alignment_dst != -1) {
2211       header_alignment_sync = header_alignment_src;
2212     }
2213     if (tool_header_alignment_dst != -1) {
2214       tool_header_alignment_sync = tool_header_alignment_src;
2215     }
2216   }
2217   else {
2218     /* Not an exact match, check the space selector isn't moving. */
2219     const short primary_header_alignment_dst = region_alignment_from_header_and_tool_header_state(
2220         region_align_info_dst, -1);
2221 
2222     if (primary_header_alignment_src != primary_header_alignment_dst) {
2223       if ((header_alignment_dst != -1) && (tool_header_alignment_dst != -1)) {
2224         if (header_alignment_dst == tool_header_alignment_dst) {
2225           /* Apply to both. */
2226           tool_header_alignment_sync = primary_header_alignment_src;
2227           header_alignment_sync = primary_header_alignment_src;
2228         }
2229         else {
2230           /* Keep on opposite sides. */
2231           tool_header_alignment_sync = primary_header_alignment_src;
2232           header_alignment_sync = (tool_header_alignment_sync == RGN_ALIGN_BOTTOM) ?
2233                                       RGN_ALIGN_TOP :
2234                                       RGN_ALIGN_BOTTOM;
2235         }
2236       }
2237       else {
2238         /* Apply what we can to regions that exist. */
2239         if (header_alignment_dst != -1) {
2240           header_alignment_sync = primary_header_alignment_src;
2241         }
2242         if (tool_header_alignment_dst != -1) {
2243           tool_header_alignment_sync = primary_header_alignment_src;
2244         }
2245       }
2246     }
2247   }
2248 
2249   if (footer_alignment_dst != -1) {
2250     if ((header_alignment_dst != -1) && (header_alignment_dst == footer_alignment_dst)) {
2251       /* Apply to both. */
2252       footer_alignment_sync = primary_header_alignment_src;
2253     }
2254     else {
2255       /* Keep on opposite sides. */
2256       footer_alignment_sync = (primary_header_alignment_src == RGN_ALIGN_BOTTOM) ?
2257                                   RGN_ALIGN_TOP :
2258                                   RGN_ALIGN_BOTTOM;
2259     }
2260   }
2261 
2262   /* Finally apply synchronized flags. */
2263   if (header_alignment_sync != -1) {
2264     ARegion *region = region_by_type[RGN_TYPE_HEADER];
2265     if (region != NULL) {
2266       region->alignment = RGN_ALIGN_ENUM_FROM_MASK(header_alignment_sync) |
2267                           RGN_ALIGN_FLAG_FROM_MASK(region->alignment);
2268     }
2269   }
2270 
2271   if (tool_header_alignment_sync != -1) {
2272     ARegion *region = region_by_type[RGN_TYPE_TOOL_HEADER];
2273     if (region != NULL) {
2274       region->alignment = RGN_ALIGN_ENUM_FROM_MASK(tool_header_alignment_sync) |
2275                           RGN_ALIGN_FLAG_FROM_MASK(region->alignment);
2276     }
2277   }
2278 
2279   if (footer_alignment_sync != -1) {
2280     ARegion *region = region_by_type[RGN_TYPE_FOOTER];
2281     if (region != NULL) {
2282       region->alignment = RGN_ALIGN_ENUM_FROM_MASK(footer_alignment_sync) |
2283                           RGN_ALIGN_FLAG_FROM_MASK(region->alignment);
2284     }
2285   }
2286 }
2287 
region_align_info_to_area(ScrArea * area,const struct RegionTypeAlignInfo region_align_info_src[RGN_TYPE_LEN])2288 static void region_align_info_to_area(
2289     ScrArea *area, const struct RegionTypeAlignInfo region_align_info_src[RGN_TYPE_LEN])
2290 {
2291   ARegion *region_by_type[RGN_TYPE_LEN] = {NULL};
2292   LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
2293     const int index = region->regiontype;
2294     if ((uint)index < RGN_TYPE_LEN) {
2295       region_by_type[index] = region;
2296     }
2297   }
2298 
2299   struct RegionTypeAlignInfo region_align_info_dst;
2300   region_align_info_from_area(area, &region_align_info_dst);
2301 
2302   if ((region_by_type[RGN_TYPE_HEADER] != NULL) ||
2303       (region_by_type[RGN_TYPE_TOOL_HEADER] != NULL)) {
2304     region_align_info_to_area_for_headers(
2305         region_align_info_src, &region_align_info_dst, region_by_type);
2306   }
2307 
2308   /* Note that we could support other region types. */
2309 }
2310 
2311 /** \} */
2312 
2313 /* *********** Space switching code *********** */
2314 
ED_area_swapspace(bContext * C,ScrArea * sa1,ScrArea * sa2)2315 void ED_area_swapspace(bContext *C, ScrArea *sa1, ScrArea *sa2)
2316 {
2317   ScrArea *tmp = MEM_callocN(sizeof(ScrArea), "addscrarea");
2318   wmWindow *win = CTX_wm_window(C);
2319 
2320   ED_area_exit(C, sa1);
2321   ED_area_exit(C, sa2);
2322 
2323   ED_area_data_copy(tmp, sa1, false);
2324   ED_area_data_copy(sa1, sa2, true);
2325   ED_area_data_copy(sa2, tmp, true);
2326   ED_area_init(CTX_wm_manager(C), win, sa1);
2327   ED_area_init(CTX_wm_manager(C), win, sa2);
2328 
2329   BKE_screen_area_free(tmp);
2330   MEM_freeN(tmp);
2331 
2332   /* tell WM to refresh, cursor types etc */
2333   WM_event_add_mousemove(win);
2334 
2335   ED_area_tag_redraw(sa1);
2336   ED_area_tag_refresh(sa1);
2337   ED_area_tag_redraw(sa2);
2338   ED_area_tag_refresh(sa2);
2339 }
2340 
2341 /**
2342  * \param skip_region_exit: Skip calling area exit callback. Set for opening temp spaces.
2343  */
ED_area_newspace(bContext * C,ScrArea * area,int type,const bool skip_region_exit)2344 void ED_area_newspace(bContext *C, ScrArea *area, int type, const bool skip_region_exit)
2345 {
2346   wmWindow *win = CTX_wm_window(C);
2347 
2348   if (area->spacetype != type) {
2349     SpaceLink *slold = area->spacedata.first;
2350     /* store area->type->exit callback */
2351     void *area_exit = area->type ? area->type->exit : NULL;
2352     /* When the user switches between space-types from the type-selector,
2353      * changing the header-type is jarring (especially when using Ctrl-MouseWheel).
2354      *
2355      * However, add-on install for example, forces the header to the top which shouldn't
2356      * be applied back to the previous space type when closing - see: T57724
2357      *
2358      * Newly created windows wont have any space data, use the alignment
2359      * the space type defaults to in this case instead
2360      * (needed for preferences to have space-type on bottom).
2361      */
2362 
2363     bool sync_header_alignment = false;
2364     struct RegionTypeAlignInfo region_align_info[RGN_TYPE_LEN];
2365     if ((slold != NULL) && (slold->link_flag & SPACE_FLAG_TYPE_TEMPORARY) == 0) {
2366       region_align_info_from_area(area, region_align_info);
2367       sync_header_alignment = true;
2368     }
2369 
2370     /* in some cases (opening temp space) we don't want to
2371      * call area exit callback, so we temporarily unset it */
2372     if (skip_region_exit && area->type) {
2373       area->type->exit = NULL;
2374     }
2375 
2376     ED_area_exit(C, area);
2377 
2378     /* restore old area exit callback */
2379     if (skip_region_exit && area->type) {
2380       area->type->exit = area_exit;
2381     }
2382 
2383     SpaceType *st = BKE_spacetype_from_id(type);
2384 
2385     area->spacetype = type;
2386     area->type = st;
2387 
2388     /* If st->create may be called, don't use context until then. The
2389      * area->type->context() callback has changed but data may be invalid
2390      * (e.g. with properties editor) until space-data is properly created */
2391 
2392     /* check previously stored space */
2393     SpaceLink *sl = NULL;
2394     LISTBASE_FOREACH (SpaceLink *, sl_iter, &area->spacedata) {
2395       if (sl_iter->spacetype == type) {
2396         sl = sl_iter;
2397         break;
2398       }
2399     }
2400 
2401     /* old spacedata... happened during work on 2.50, remove */
2402     if (sl && BLI_listbase_is_empty(&sl->regionbase)) {
2403       st->free(sl);
2404       BLI_freelinkN(&area->spacedata, sl);
2405       if (slold == sl) {
2406         slold = NULL;
2407       }
2408       sl = NULL;
2409     }
2410 
2411     if (sl) {
2412       /* swap regions */
2413       slold->regionbase = area->regionbase;
2414       area->regionbase = sl->regionbase;
2415       BLI_listbase_clear(&sl->regionbase);
2416       /* SPACE_FLAG_TYPE_WAS_ACTIVE is only used to go back to a previously active space that is
2417        * overlapped by temporary ones. It's now properly activated, so the flag should be cleared
2418        * at this point. */
2419       sl->link_flag &= ~SPACE_FLAG_TYPE_WAS_ACTIVE;
2420 
2421       /* put in front of list */
2422       BLI_remlink(&area->spacedata, sl);
2423       BLI_addhead(&area->spacedata, sl);
2424     }
2425     else {
2426       /* new space */
2427       if (st) {
2428         /* Don't get scene from context here which may depend on space-data. */
2429         Scene *scene = WM_window_get_active_scene(win);
2430         sl = st->create(area, scene);
2431         BLI_addhead(&area->spacedata, sl);
2432 
2433         /* swap regions */
2434         if (slold) {
2435           slold->regionbase = area->regionbase;
2436         }
2437         area->regionbase = sl->regionbase;
2438         BLI_listbase_clear(&sl->regionbase);
2439       }
2440     }
2441 
2442     /* Sync header alignment. */
2443     if (sync_header_alignment) {
2444       region_align_info_to_area(area, region_align_info);
2445     }
2446 
2447     ED_area_init(CTX_wm_manager(C), win, area);
2448 
2449     /* tell WM to refresh, cursor types etc */
2450     WM_event_add_mousemove(win);
2451 
2452     /* send space change notifier */
2453     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_CHANGED, area);
2454 
2455     ED_area_tag_refresh(area);
2456   }
2457 
2458   /* also redraw when re-used */
2459   ED_area_tag_redraw(area);
2460 }
2461 
area_get_prevspace(ScrArea * area)2462 static SpaceLink *area_get_prevspace(ScrArea *area)
2463 {
2464   SpaceLink *sl = area->spacedata.first;
2465 
2466   /* First toggle to the next temporary space in the list. */
2467   for (SpaceLink *sl_iter = sl->next; sl_iter; sl_iter = sl_iter->next) {
2468     if (sl_iter->link_flag & SPACE_FLAG_TYPE_TEMPORARY) {
2469       return sl_iter;
2470     }
2471   }
2472 
2473   /* No temporary space, find the item marked as last active. */
2474   for (SpaceLink *sl_iter = sl->next; sl_iter; sl_iter = sl_iter->next) {
2475     if (sl_iter->link_flag & SPACE_FLAG_TYPE_WAS_ACTIVE) {
2476       return sl_iter;
2477     }
2478   }
2479 
2480   /* If neither is found, we can just return to the regular previous one. */
2481   return sl->next;
2482 }
2483 
ED_area_prevspace(bContext * C,ScrArea * area)2484 void ED_area_prevspace(bContext *C, ScrArea *area)
2485 {
2486   SpaceLink *sl = area->spacedata.first;
2487   SpaceLink *prevspace = sl ? area_get_prevspace(area) : NULL;
2488 
2489   if (prevspace) {
2490     ED_area_newspace(C, area, prevspace->spacetype, false);
2491     /* We've exited the space, so it can't be considered temporary anymore. */
2492     sl->link_flag &= ~SPACE_FLAG_TYPE_TEMPORARY;
2493   }
2494   else {
2495     /* no change */
2496     return;
2497   }
2498   /* If this is a stacked fullscreen, changing to previous area exits it (meaning we're still in a
2499    * fullscreen, but not in a stacked one). */
2500   area->flag &= ~AREA_FLAG_STACKED_FULLSCREEN;
2501 
2502   ED_area_tag_redraw(area);
2503 
2504   /* send space change notifier */
2505   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_CHANGED, area);
2506 }
2507 
2508 /* returns offset for next button in header */
ED_area_header_switchbutton(const bContext * C,uiBlock * block,int yco)2509 int ED_area_header_switchbutton(const bContext *C, uiBlock *block, int yco)
2510 {
2511   ScrArea *area = CTX_wm_area(C);
2512   bScreen *screen = CTX_wm_screen(C);
2513   PointerRNA areaptr;
2514   int xco = 0.4 * U.widget_unit;
2515 
2516   RNA_pointer_create(&(screen->id), &RNA_Area, area, &areaptr);
2517 
2518   uiDefButR(block,
2519             UI_BTYPE_MENU,
2520             0,
2521             "",
2522             xco,
2523             yco,
2524             1.6 * U.widget_unit,
2525             U.widget_unit,
2526             &areaptr,
2527             "ui_type",
2528             0,
2529             0.0f,
2530             0.0f,
2531             0.0f,
2532             0.0f,
2533             "");
2534 
2535   return xco + 1.7 * U.widget_unit;
2536 }
2537 
2538 /************************ standard UI regions ************************/
2539 
region_background_color_id(const bContext * C,const ARegion * region)2540 static ThemeColorID region_background_color_id(const bContext *C, const ARegion *region)
2541 {
2542   ScrArea *area = CTX_wm_area(C);
2543 
2544   switch (region->regiontype) {
2545     case RGN_TYPE_HEADER:
2546     case RGN_TYPE_TOOL_HEADER:
2547       if (ED_screen_area_active(C) || ED_area_is_global(area)) {
2548         return TH_HEADER;
2549       }
2550       else {
2551         return TH_HEADERDESEL;
2552       }
2553     case RGN_TYPE_PREVIEW:
2554       return TH_PREVIEW_BACK;
2555     default:
2556       return TH_BACK;
2557   }
2558 }
2559 
region_clear_color(const bContext * C,const ARegion * region,ThemeColorID colorid)2560 static void region_clear_color(const bContext *C, const ARegion *region, ThemeColorID colorid)
2561 {
2562   if (region->alignment == RGN_ALIGN_FLOAT) {
2563     /* handle our own drawing. */
2564   }
2565   else if (region->overlap) {
2566     /* view should be in pixelspace */
2567     UI_view2d_view_restore(C);
2568 
2569     float back[4];
2570     UI_GetThemeColor4fv(colorid, back);
2571     GPU_clear_color(back[3] * back[0], back[3] * back[1], back[3] * back[2], back[3]);
2572   }
2573   else {
2574     UI_ThemeClearColor(colorid);
2575   }
2576 }
2577 
streq_array_any(const char * s,const char * arr[])2578 BLI_INLINE bool streq_array_any(const char *s, const char *arr[])
2579 {
2580   for (uint i = 0; arr[i]; i++) {
2581     if (STREQ(arr[i], s)) {
2582       return true;
2583     }
2584   }
2585   return false;
2586 }
2587 
2588 /**
2589  * Builds the panel layout for the input \a panel or type \a pt.
2590  *
2591  * \param panel: The panel to draw. Can be null,
2592  * in which case a panel with the type of \a pt will be created.
2593  * \param unique_panel_str: A unique identifier for the name of the \a uiBlock associated with the
2594  * panel. Used when the panel is an instanced panel so a unique identifier is needed to find the
2595  * correct old \a uiBlock, and NULL otherwise.
2596  */
ed_panel_draw(const bContext * C,ARegion * region,ListBase * lb,PanelType * pt,Panel * panel,int w,int em,char * unique_panel_str,const char * search_filter)2597 static void ed_panel_draw(const bContext *C,
2598                           ARegion *region,
2599                           ListBase *lb,
2600                           PanelType *pt,
2601                           Panel *panel,
2602                           int w,
2603                           int em,
2604                           char *unique_panel_str,
2605                           const char *search_filter)
2606 {
2607   const uiStyle *style = UI_style_get_dpi();
2608 
2609   /* Draw panel. */
2610 
2611   char block_name[BKE_ST_MAXNAME + INSTANCED_PANEL_UNIQUE_STR_LEN];
2612   strncpy(block_name, pt->idname, BKE_ST_MAXNAME);
2613   if (unique_panel_str != NULL) {
2614     /* Instanced panels should have already been added at this point. */
2615     strncat(block_name, unique_panel_str, INSTANCED_PANEL_UNIQUE_STR_LEN);
2616   }
2617   uiBlock *block = UI_block_begin(C, region, block_name, UI_EMBOSS);
2618 
2619   bool open;
2620   panel = UI_panel_begin(region, lb, block, pt, panel, &open);
2621 
2622   const bool search_filter_active = search_filter != NULL && search_filter[0] != '\0';
2623 
2624   /* bad fixed values */
2625   int xco, yco, h = 0;
2626   int headerend = w - UI_UNIT_X;
2627 
2628   UI_panel_header_buttons_begin(panel);
2629   if (pt->draw_header_preset && !(pt->flag & PNL_NO_HEADER)) {
2630     /* for preset menu */
2631     panel->layout = UI_block_layout(block,
2632                                     UI_LAYOUT_HORIZONTAL,
2633                                     UI_LAYOUT_HEADER,
2634                                     0,
2635                                     (UI_UNIT_Y * 1.1f) + style->panelspace,
2636                                     UI_UNIT_Y,
2637                                     1,
2638                                     0,
2639                                     style);
2640 
2641     pt->draw_header_preset(C, panel);
2642 
2643     UI_block_apply_search_filter(block, search_filter);
2644     UI_block_layout_resolve(block, &xco, &yco);
2645     UI_block_translate(block, headerend - xco, 0);
2646     panel->layout = NULL;
2647   }
2648 
2649   if (pt->draw_header && !(pt->flag & PNL_NO_HEADER)) {
2650     int labelx, labely;
2651     UI_panel_label_offset(block, &labelx, &labely);
2652 
2653     /* Unusual case: Use expanding layout (buttons stretch to available width). */
2654     if (pt->flag & PNL_LAYOUT_HEADER_EXPAND) {
2655       uiLayout *layout = UI_block_layout(block,
2656                                          UI_LAYOUT_VERTICAL,
2657                                          UI_LAYOUT_PANEL,
2658                                          labelx,
2659                                          labely,
2660                                          headerend - 2 * style->panelspace,
2661                                          1,
2662                                          0,
2663                                          style);
2664       panel->layout = uiLayoutRow(layout, false);
2665     }
2666     /* Regular case: Normal panel with fixed size buttons. */
2667     else {
2668       panel->layout = UI_block_layout(
2669           block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, labelx, labely, UI_UNIT_Y, 1, 0, style);
2670     }
2671 
2672     pt->draw_header(C, panel);
2673 
2674     UI_block_apply_search_filter(block, search_filter);
2675     UI_block_layout_resolve(block, &xco, &yco);
2676     panel->labelofs = xco - labelx;
2677     panel->layout = NULL;
2678   }
2679   else {
2680     panel->labelofs = 0;
2681   }
2682   UI_panel_header_buttons_end(panel);
2683 
2684   if (open || search_filter_active) {
2685     short panelContext;
2686 
2687     /* panel context can either be toolbar region or normal panels region */
2688     if (pt->flag & PNL_LAYOUT_VERT_BAR) {
2689       panelContext = UI_LAYOUT_VERT_BAR;
2690     }
2691     else if (region->regiontype == RGN_TYPE_TOOLS) {
2692       panelContext = UI_LAYOUT_TOOLBAR;
2693     }
2694     else {
2695       panelContext = UI_LAYOUT_PANEL;
2696     }
2697 
2698     panel->layout = UI_block_layout(block,
2699                                     UI_LAYOUT_VERTICAL,
2700                                     panelContext,
2701                                     (pt->flag & PNL_LAYOUT_VERT_BAR) ? 0 : style->panelspace,
2702                                     0,
2703                                     (pt->flag & PNL_LAYOUT_VERT_BAR) ? 0 :
2704                                                                        w - 2 * style->panelspace,
2705                                     em,
2706                                     0,
2707                                     style);
2708 
2709     pt->draw(C, panel);
2710 
2711     UI_block_apply_search_filter(block, search_filter);
2712     UI_block_layout_resolve(block, &xco, &yco);
2713     panel->layout = NULL;
2714 
2715     if (yco != 0) {
2716       h = -yco + 2 * style->panelspace;
2717     }
2718   }
2719 
2720   UI_block_end(C, block);
2721 
2722   /* Draw child panels. */
2723   if (open || search_filter_active) {
2724     LISTBASE_FOREACH (LinkData *, link, &pt->children) {
2725       PanelType *child_pt = link->data;
2726       Panel *child_panel = UI_panel_find_by_type(&panel->children, child_pt);
2727 
2728       if (child_pt->draw && (!child_pt->poll || child_pt->poll(C, child_pt))) {
2729         ed_panel_draw(C,
2730                       region,
2731                       &panel->children,
2732                       child_pt,
2733                       child_panel,
2734                       w,
2735                       em,
2736                       unique_panel_str,
2737                       search_filter);
2738       }
2739     }
2740   }
2741 
2742   UI_panel_end(panel, w, h);
2743 }
2744 
2745 /**
2746  * Check whether a panel should be added to the region's panel layout.
2747  */
panel_add_check(const bContext * C,const WorkSpace * workspace,const char * contexts[],const char * category_override,PanelType * panel_type)2748 static bool panel_add_check(const bContext *C,
2749                             const WorkSpace *workspace,
2750                             const char *contexts[],
2751                             const char *category_override,
2752                             PanelType *panel_type)
2753 {
2754   /* Only add top level panels. */
2755   if (panel_type->parent) {
2756     return false;
2757   }
2758   /* Check the category override first. */
2759   if (category_override) {
2760     if (!STREQ(panel_type->category, category_override)) {
2761       return false;
2762     }
2763   }
2764 
2765   /* Verify context. */
2766   if (contexts != NULL && panel_type->context[0]) {
2767     if (!streq_array_any(panel_type->context, contexts)) {
2768       return false;
2769     }
2770   }
2771 
2772   /* If we're tagged, only use compatible. */
2773   if (panel_type->owner_id[0]) {
2774     if (!BKE_workspace_owner_id_check(workspace, panel_type->owner_id)) {
2775       return false;
2776     }
2777   }
2778 
2779   if (LIKELY(panel_type->draw)) {
2780     if (panel_type->poll && !panel_type->poll(C, panel_type)) {
2781       return false;
2782     }
2783   }
2784 
2785   return true;
2786 }
2787 
region_uses_category_tabs(const ScrArea * area,const ARegion * region)2788 static bool region_uses_category_tabs(const ScrArea *area, const ARegion *region)
2789 {
2790   /* XXX, should use some better check? */
2791   /* For now also has hardcoded check for clip editor until it supports actual toolbar. */
2792   return ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) ||
2793          (region->regiontype == RGN_TYPE_TOOLS && area->spacetype == SPACE_CLIP);
2794 }
2795 
region_panels_collect_categories(ARegion * region,LinkNode * panel_types_stack,bool * use_category_tabs)2796 static const char *region_panels_collect_categories(ARegion *region,
2797                                                     LinkNode *panel_types_stack,
2798                                                     bool *use_category_tabs)
2799 {
2800   UI_panel_category_clear_all(region);
2801 
2802   /* gather unique categories */
2803   for (LinkNode *pt_link = panel_types_stack; pt_link; pt_link = pt_link->next) {
2804     PanelType *pt = pt_link->link;
2805     if (pt->category[0]) {
2806       if (!UI_panel_category_find(region, pt->category)) {
2807         UI_panel_category_add(region, pt->category);
2808       }
2809     }
2810   }
2811 
2812   if (UI_panel_category_is_visible(region)) {
2813     return UI_panel_category_active_get(region, true);
2814   }
2815 
2816   *use_category_tabs = false;
2817   return NULL;
2818 }
2819 
2820 /**
2821  * \param contexts: A NULL terminated array of context strings to match against.
2822  * Matching against any of these strings will draw the panel.
2823  * Can be NULL to skip context checks.
2824  */
ED_region_panels_layout_ex(const bContext * C,ARegion * region,ListBase * paneltypes,const char * contexts[],const char * category_override)2825 void ED_region_panels_layout_ex(const bContext *C,
2826                                 ARegion *region,
2827                                 ListBase *paneltypes,
2828                                 const char *contexts[],
2829                                 const char *category_override)
2830 {
2831   /* collect panels to draw */
2832   WorkSpace *workspace = CTX_wm_workspace(C);
2833   LinkNode *panel_types_stack = NULL;
2834   LISTBASE_FOREACH_BACKWARD (PanelType *, pt, paneltypes) {
2835     if (panel_add_check(C, workspace, contexts, category_override, pt)) {
2836       BLI_linklist_prepend_alloca(&panel_types_stack, pt);
2837     }
2838   }
2839 
2840   region->runtime.category = NULL;
2841 
2842   ScrArea *area = CTX_wm_area(C);
2843   View2D *v2d = &region->v2d;
2844 
2845   bool use_category_tabs = (category_override == NULL) && region_uses_category_tabs(area, region);
2846   /* offset panels for small vertical tab area */
2847   const char *category = NULL;
2848   const int category_tabs_width = UI_PANEL_CATEGORY_MARGIN_WIDTH;
2849   int margin_x = 0;
2850   const bool region_layout_based = region->flag & RGN_FLAG_DYNAMIC_SIZE;
2851   bool update_tot_size = true;
2852 
2853   /* only allow scrolling in vertical direction */
2854   v2d->keepofs |= V2D_LOCKOFS_X | V2D_KEEPOFS_Y;
2855   v2d->keepofs &= ~(V2D_LOCKOFS_Y | V2D_KEEPOFS_X);
2856   v2d->scroll &= ~V2D_SCROLL_BOTTOM;
2857   v2d->scroll |= V2D_SCROLL_RIGHT;
2858 
2859   /* collect categories */
2860   if (use_category_tabs) {
2861     category = region_panels_collect_categories(region, panel_types_stack, &use_category_tabs);
2862   }
2863   if (use_category_tabs) {
2864     margin_x = category_tabs_width;
2865   }
2866 
2867   const int w = BLI_rctf_size_x(&v2d->cur) - margin_x;
2868   /* Works out to 10 * UI_UNIT_X or 20 * UI_UNIT_X. */
2869   const int em = (region->type->prefsizex) ? 10 : 20;
2870 
2871   const int w_box_panel = w - UI_PANEL_BOX_STYLE_MARGIN * 2.0f;
2872 
2873   /* create panels */
2874   UI_panels_begin(C, region);
2875 
2876   /* Get search string for property search. */
2877   const char *search_filter = ED_area_region_search_filter_get(area, region);
2878 
2879   /* set view2d view matrix  - UI_block_begin() stores it */
2880   UI_view2d_view_ortho(v2d);
2881 
2882   bool has_instanced_panel = false;
2883   for (LinkNode *pt_link = panel_types_stack; pt_link; pt_link = pt_link->next) {
2884     PanelType *pt = pt_link->link;
2885 
2886     if (pt->flag & PNL_INSTANCED) {
2887       has_instanced_panel = true;
2888       continue;
2889     }
2890     Panel *panel = UI_panel_find_by_type(&region->panels, pt);
2891 
2892     if (use_category_tabs && pt->category[0] && !STREQ(category, pt->category)) {
2893       if ((panel == NULL) || ((panel->flag & PNL_PIN) == 0)) {
2894         continue;
2895       }
2896     }
2897 
2898     if (panel && UI_panel_is_dragging(panel)) {
2899       /* Prevent View2d.tot rectangle size changes while dragging panels. */
2900       update_tot_size = false;
2901     }
2902 
2903     ed_panel_draw(C,
2904                   region,
2905                   &region->panels,
2906                   pt,
2907                   panel,
2908                   (pt->flag & PNL_DRAW_BOX) ? w_box_panel : w,
2909                   em,
2910                   NULL,
2911                   search_filter);
2912   }
2913 
2914   /* Draw "polyinstantaited" panels that don't have a 1 to 1 correspondence with their types. */
2915   if (has_instanced_panel) {
2916     LISTBASE_FOREACH (Panel *, panel, &region->panels) {
2917       if (panel->type == NULL) {
2918         continue; /* Some panels don't have a type. */
2919       }
2920       if (!(panel->type->flag & PNL_INSTANCED)) {
2921         continue;
2922       }
2923       if (use_category_tabs && panel->type->category[0] &&
2924           !STREQ(category, panel->type->category)) {
2925         continue;
2926       }
2927 
2928       if (panel && UI_panel_is_dragging(panel)) {
2929         /* Prevent View2d.tot rectangle size changes while dragging panels. */
2930         update_tot_size = false;
2931       }
2932 
2933       /* Use a unique identifier for instanced panels, otherwise an old block for a different
2934        * panel of the same type might be found. */
2935       char unique_panel_str[INSTANCED_PANEL_UNIQUE_STR_LEN];
2936       UI_list_panel_unique_str(panel, unique_panel_str);
2937       ed_panel_draw(C,
2938                     region,
2939                     &region->panels,
2940                     panel->type,
2941                     panel,
2942                     (panel->type->flag & PNL_DRAW_BOX) ? w_box_panel : w,
2943                     em,
2944                     unique_panel_str,
2945                     search_filter);
2946     }
2947   }
2948 
2949   /* align panels and return size */
2950   int x, y;
2951   UI_panels_end(C, region, &x, &y);
2952 
2953   /* before setting the view */
2954   if (region_layout_based) {
2955     /* XXX, only single panel support atm.
2956      * Can't use x/y values calculated above because they're not using the real height of panels,
2957      * instead they calculate offsets for the next panel to start drawing. */
2958     Panel *panel = region->panels.last;
2959     if (panel != NULL) {
2960       const int size_dyn[2] = {
2961           UI_UNIT_X * (UI_panel_is_closed(panel) ? 8 : 14) / UI_DPI_FAC,
2962           UI_panel_size_y(panel) / UI_DPI_FAC,
2963       };
2964       /* region size is layout based and needs to be updated */
2965       if ((region->sizex != size_dyn[0]) || (region->sizey != size_dyn[1])) {
2966         region->sizex = size_dyn[0];
2967         region->sizey = size_dyn[1];
2968         area->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
2969       }
2970       y = fabsf(region->sizey * UI_DPI_FAC - 1);
2971     }
2972   }
2973   else {
2974     /* We always keep the scroll offset -
2975      * so the total view gets increased with the scrolled away part. */
2976     if (v2d->cur.ymax < -FLT_EPSILON) {
2977       /* Clamp to lower view boundary */
2978       if (v2d->tot.ymin < -v2d->winy) {
2979         y = min_ii(y, 0);
2980       }
2981       else {
2982         y = min_ii(y, v2d->cur.ymin);
2983       }
2984     }
2985 
2986     y = -y;
2987   }
2988 
2989   if (update_tot_size) {
2990     /* this also changes the 'cur' */
2991     UI_view2d_totRect_set(v2d, x, y);
2992   }
2993 
2994   if (use_category_tabs) {
2995     region->runtime.category = category;
2996   }
2997 }
2998 
ED_region_panels_layout(const bContext * C,ARegion * region)2999 void ED_region_panels_layout(const bContext *C, ARegion *region)
3000 {
3001   ED_region_panels_layout_ex(C, region, &region->type->paneltypes, NULL, NULL);
3002 }
3003 
ED_region_panels_draw(const bContext * C,ARegion * region)3004 void ED_region_panels_draw(const bContext *C, ARegion *region)
3005 {
3006   View2D *v2d = &region->v2d;
3007 
3008   if (region->alignment != RGN_ALIGN_FLOAT) {
3009     region_clear_color(
3010         C, region, (region->type->regionid == RGN_TYPE_PREVIEW) ? TH_PREVIEW_BACK : TH_BACK);
3011   }
3012 
3013   /* reset line width for drawing tabs */
3014   GPU_line_width(1.0f);
3015 
3016   /* set the view */
3017   UI_view2d_view_ortho(v2d);
3018 
3019   /* View2D matrix might have changed due to dynamic sized regions. */
3020   UI_blocklist_update_window_matrix(C, &region->uiblocks);
3021 
3022   /* draw panels */
3023   UI_panels_draw(C, region);
3024 
3025   /* restore view matrix */
3026   UI_view2d_view_restore(C);
3027 
3028   /* Set in layout. */
3029   if (region->runtime.category) {
3030     UI_panel_category_draw_all(region, region->runtime.category);
3031   }
3032 
3033   /* scrollers */
3034   bool use_mask = false;
3035   rcti mask;
3036   if (region->runtime.category &&
3037       (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_RIGHT)) {
3038     use_mask = true;
3039     UI_view2d_mask_from_win(v2d, &mask);
3040     mask.xmax -= UI_PANEL_CATEGORY_MARGIN_WIDTH;
3041   }
3042   UI_view2d_scrollers_draw(v2d, use_mask ? &mask : NULL);
3043 }
3044 
ED_region_panels_ex(const bContext * C,ARegion * region,const char * contexts[])3045 void ED_region_panels_ex(const bContext *C, ARegion *region, const char *contexts[])
3046 {
3047   /* TODO: remove? */
3048   ED_region_panels_layout_ex(C, region, &region->type->paneltypes, contexts, NULL);
3049   ED_region_panels_draw(C, region);
3050 }
3051 
ED_region_panels(const bContext * C,ARegion * region)3052 void ED_region_panels(const bContext *C, ARegion *region)
3053 {
3054   /* TODO: remove? */
3055   ED_region_panels_layout(C, region);
3056   ED_region_panels_draw(C, region);
3057 }
3058 
ED_region_panels_init(wmWindowManager * wm,ARegion * region)3059 void ED_region_panels_init(wmWindowManager *wm, ARegion *region)
3060 {
3061   UI_view2d_region_reinit(&region->v2d, V2D_COMMONVIEW_PANELS_UI, region->winx, region->winy);
3062 
3063   wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0);
3064   WM_event_add_keymap_handler(&region->handlers, keymap);
3065 }
3066 
3067 /**
3068  * Check whether any of the buttons generated by the \a panel_type's
3069  * layout callbacks match the \a search_filter.
3070  *
3071  * \param panel: If non-NULL, use this instead of adding a new panel for the \a panel_type.
3072  */
panel_property_search(const bContext * C,ARegion * region,const uiStyle * style,Panel * panel,PanelType * panel_type,const char * search_filter)3073 static bool panel_property_search(const bContext *C,
3074                                   ARegion *region,
3075                                   const uiStyle *style,
3076                                   Panel *panel,
3077                                   PanelType *panel_type,
3078                                   const char *search_filter)
3079 {
3080   uiBlock *block = UI_block_begin(C, region, panel_type->idname, UI_EMBOSS);
3081   UI_block_set_search_only(block, true);
3082 
3083   /* Skip panels that give meaningless search results. */
3084   if (panel_type->flag & PNL_NO_SEARCH) {
3085     return false;
3086   }
3087 
3088   if (panel == NULL) {
3089     bool open; /* Dummy variable. */
3090     panel = UI_panel_begin(region, &region->panels, block, panel_type, panel, &open);
3091   }
3092 
3093   /* Build the layouts. Because they are only used for search,
3094    * they don't need any of the proper style or layout information. */
3095   if (panel->type->draw_header_preset != NULL) {
3096     panel->layout = UI_block_layout(
3097         block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, 0, 0, 0, 0, 0, style);
3098     panel_type->draw_header_preset(C, panel);
3099   }
3100   if (panel->type->draw_header != NULL) {
3101     panel->layout = UI_block_layout(
3102         block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, 0, 0, 0, 0, 0, style);
3103     panel_type->draw_header(C, panel);
3104   }
3105   if (LIKELY(panel->type->draw != NULL)) {
3106     panel->layout = UI_block_layout(
3107         block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, 0, 0, 0, style);
3108     panel_type->draw(C, panel);
3109   }
3110 
3111   UI_block_layout_free(block);
3112 
3113   /* We could check after each layout to increase the likelihood of returning early,
3114    * but that probably wouldn't make much of a difference anyway. */
3115   if (UI_block_apply_search_filter(block, search_filter)) {
3116     return true;
3117   }
3118 
3119   LISTBASE_FOREACH (LinkData *, link, &panel_type->children) {
3120     PanelType *panel_type_child = link->data;
3121     if (!panel_type_child->poll || panel_type_child->poll(C, panel_type_child)) {
3122       /* Search for the existing child panel here because it might be an instanced
3123        * child panel with a custom data field that will be needed to build the layout. */
3124       Panel *child_panel = UI_panel_find_by_type(&panel->children, panel_type_child);
3125       if (panel_property_search(C, region, style, child_panel, panel_type_child, search_filter)) {
3126         return true;
3127       }
3128     }
3129   }
3130 
3131   return false;
3132 }
3133 
3134 /**
3135  * Build the same panel list as #ED_region_panels_layout_ex and checks whether any
3136  * of the panels contain a search result based on the area / region's search filter.
3137  */
ED_region_property_search(const bContext * C,ARegion * region,ListBase * paneltypes,const char * contexts[],const char * category_override)3138 bool ED_region_property_search(const bContext *C,
3139                                ARegion *region,
3140                                ListBase *paneltypes,
3141                                const char *contexts[],
3142                                const char *category_override)
3143 {
3144   ScrArea *area = CTX_wm_area(C);
3145   WorkSpace *workspace = CTX_wm_workspace(C);
3146   const uiStyle *style = UI_style_get_dpi();
3147   const char *search_filter = ED_area_region_search_filter_get(area, region);
3148 
3149   LinkNode *panel_types_stack = NULL;
3150   LISTBASE_FOREACH_BACKWARD (PanelType *, pt, paneltypes) {
3151     if (panel_add_check(C, workspace, contexts, category_override, pt)) {
3152       BLI_linklist_prepend_alloca(&panel_types_stack, pt);
3153     }
3154   }
3155 
3156   const char *category = NULL;
3157   bool use_category_tabs = (category_override == NULL) && region_uses_category_tabs(area, region);
3158   if (use_category_tabs) {
3159     category = region_panels_collect_categories(region, panel_types_stack, &use_category_tabs);
3160   }
3161 
3162   /* Run property search for each panel, stopping if a result is found. */
3163   bool has_result = true;
3164   bool has_instanced_panel = false;
3165   for (LinkNode *pt_link = panel_types_stack; pt_link; pt_link = pt_link->next) {
3166     PanelType *panel_type = pt_link->link;
3167     /* Note that these checks are duplicated from #ED_region_panels_layout_ex. */
3168     if (panel_type->flag & PNL_INSTANCED) {
3169       has_instanced_panel = true;
3170       continue;
3171     }
3172 
3173     if (use_category_tabs) {
3174       if (panel_type->category[0] && !STREQ(category, panel_type->category)) {
3175         continue;
3176       }
3177     }
3178 
3179     /* We start property search with an empty panel list, so there's
3180      * no point in trying to find an existing panel with this type. */
3181     has_result = panel_property_search(C, region, style, NULL, panel_type, search_filter);
3182     if (has_result) {
3183       break;
3184     }
3185   }
3186 
3187   /* Run property search for instanced panels (created in the layout calls of previous panels). */
3188   if (!has_result && has_instanced_panel) {
3189     LISTBASE_FOREACH (Panel *, panel, &region->panels) {
3190       /* Note that these checks are duplicated from #ED_region_panels_layout_ex. */
3191       if (panel->type == NULL || !(panel->type->flag & PNL_INSTANCED)) {
3192         continue;
3193       }
3194       if (use_category_tabs) {
3195         if (panel->type->category[0] && !STREQ(category, panel->type->category)) {
3196           continue;
3197         }
3198       }
3199 
3200       has_result = panel_property_search(C, region, style, panel, panel->type, search_filter);
3201       if (has_result) {
3202         break;
3203       }
3204     }
3205   }
3206 
3207   /* Free the panels and blocks, as they are only used for search. */
3208   UI_blocklist_free(C, &region->uiblocks);
3209   UI_panels_free_instanced(C, region);
3210   BKE_area_region_panels_free(&region->panels);
3211 
3212   return has_result;
3213 }
3214 
ED_region_header_layout(const bContext * C,ARegion * region)3215 void ED_region_header_layout(const bContext *C, ARegion *region)
3216 {
3217   const uiStyle *style = UI_style_get_dpi();
3218   bool region_layout_based = region->flag & RGN_FLAG_DYNAMIC_SIZE;
3219 
3220   /* Height of buttons and scaling needed to achieve it. */
3221   const int buttony = min_ii(UI_UNIT_Y, region->winy - 2 * UI_DPI_FAC);
3222   const float buttony_scale = buttony / (float)UI_UNIT_Y;
3223 
3224   /* Vertically center buttons. */
3225   int xco = UI_HEADER_OFFSET;
3226   int yco = buttony + (region->winy - buttony) / 2;
3227   int maxco = xco;
3228 
3229   /* XXX workaround for 1 px alignment issue. Not sure what causes it...
3230    * Would prefer a proper fix - Julian */
3231   if (!ELEM(CTX_wm_area(C)->spacetype, SPACE_TOPBAR, SPACE_STATUSBAR)) {
3232     yco -= 1;
3233   }
3234 
3235   /* set view2d view matrix for scrolling (without scrollers) */
3236   UI_view2d_view_ortho(&region->v2d);
3237 
3238   /* draw all headers types */
3239   LISTBASE_FOREACH (HeaderType *, ht, &region->type->headertypes) {
3240     if (ht->poll && !ht->poll(C, ht)) {
3241       continue;
3242     }
3243 
3244     uiBlock *block = UI_block_begin(C, region, ht->idname, UI_EMBOSS);
3245     uiLayout *layout = UI_block_layout(
3246         block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, xco, yco, buttony, 1, 0, style);
3247 
3248     if (buttony_scale != 1.0f) {
3249       uiLayoutSetScaleY(layout, buttony_scale);
3250     }
3251 
3252     Header header = {NULL};
3253     if (ht->draw) {
3254       header.type = ht;
3255       header.layout = layout;
3256       ht->draw(C, &header);
3257       if (ht->next) {
3258         uiItemS(layout);
3259       }
3260 
3261       /* for view2d */
3262       xco = uiLayoutGetWidth(layout);
3263       if (xco > maxco) {
3264         maxco = xco;
3265       }
3266     }
3267 
3268     UI_block_layout_resolve(block, &xco, &yco);
3269 
3270     /* for view2d */
3271     if (xco > maxco) {
3272       maxco = xco;
3273     }
3274 
3275     int new_sizex = (maxco + UI_HEADER_OFFSET) / UI_DPI_FAC;
3276 
3277     if (region_layout_based && (region->sizex != new_sizex)) {
3278       /* region size is layout based and needs to be updated */
3279       ScrArea *area = CTX_wm_area(C);
3280 
3281       region->sizex = new_sizex;
3282       area->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
3283     }
3284 
3285     UI_block_end(C, block);
3286 
3287     /* In most cases there is only ever one header, it never makes sense to draw more than one
3288      * header in the same region, this results in overlapping buttons, see: T60195. */
3289     break;
3290   }
3291 
3292   if (!region_layout_based) {
3293     maxco += UI_HEADER_OFFSET;
3294   }
3295 
3296   /* always as last  */
3297   UI_view2d_totRect_set(&region->v2d, maxco, region->winy);
3298 
3299   /* restore view matrix */
3300   UI_view2d_view_restore(C);
3301 }
3302 
ED_region_header_draw(const bContext * C,ARegion * region)3303 void ED_region_header_draw(const bContext *C, ARegion *region)
3304 {
3305   /* clear */
3306   region_clear_color(C, region, region_background_color_id(C, region));
3307 
3308   UI_view2d_view_ortho(&region->v2d);
3309 
3310   /* View2D matrix might have changed due to dynamic sized regions. */
3311   UI_blocklist_update_window_matrix(C, &region->uiblocks);
3312 
3313   /* draw blocks */
3314   UI_blocklist_draw(C, &region->uiblocks);
3315 
3316   /* restore view matrix */
3317   UI_view2d_view_restore(C);
3318 }
3319 
ED_region_header(const bContext * C,ARegion * region)3320 void ED_region_header(const bContext *C, ARegion *region)
3321 {
3322   /* TODO: remove? */
3323   ED_region_header_layout(C, region);
3324   ED_region_header_draw(C, region);
3325 }
3326 
ED_region_header_init(ARegion * region)3327 void ED_region_header_init(ARegion *region)
3328 {
3329   UI_view2d_region_reinit(&region->v2d, V2D_COMMONVIEW_HEADER, region->winx, region->winy);
3330 }
3331 
ED_area_headersize(void)3332 int ED_area_headersize(void)
3333 {
3334   /* Accommodate widget and padding. */
3335   return U.widget_unit + (int)(UI_DPI_FAC * HEADER_PADDING_Y);
3336 }
3337 
ED_area_footersize(void)3338 int ED_area_footersize(void)
3339 {
3340   return ED_area_headersize();
3341 }
3342 
3343 /**
3344  * \return the final height of a global \a area, accounting for DPI.
3345  */
ED_area_global_size_y(const ScrArea * area)3346 int ED_area_global_size_y(const ScrArea *area)
3347 {
3348   BLI_assert(ED_area_is_global(area));
3349   return round_fl_to_int(area->global->cur_fixed_height * UI_DPI_FAC);
3350 }
ED_area_global_min_size_y(const ScrArea * area)3351 int ED_area_global_min_size_y(const ScrArea *area)
3352 {
3353   BLI_assert(ED_area_is_global(area));
3354   return round_fl_to_int(area->global->size_min * UI_DPI_FAC);
3355 }
ED_area_global_max_size_y(const ScrArea * area)3356 int ED_area_global_max_size_y(const ScrArea *area)
3357 {
3358   BLI_assert(ED_area_is_global(area));
3359   return round_fl_to_int(area->global->size_max * UI_DPI_FAC);
3360 }
3361 
ED_area_is_global(const ScrArea * area)3362 bool ED_area_is_global(const ScrArea *area)
3363 {
3364   return area->global != NULL;
3365 }
3366 
ED_screen_areas_iter_first(const wmWindow * win,const bScreen * screen)3367 ScrArea *ED_screen_areas_iter_first(const wmWindow *win, const bScreen *screen)
3368 {
3369   ScrArea *global_area = win->global_areas.areabase.first;
3370 
3371   if (!global_area) {
3372     return screen->areabase.first;
3373   }
3374   if ((global_area->global->flag & GLOBAL_AREA_IS_HIDDEN) == 0) {
3375     return global_area;
3376   }
3377   /* Find next visible area. */
3378   return ED_screen_areas_iter_next(screen, global_area);
3379 }
ED_screen_areas_iter_next(const bScreen * screen,const ScrArea * area)3380 ScrArea *ED_screen_areas_iter_next(const bScreen *screen, const ScrArea *area)
3381 {
3382   if (area->global == NULL) {
3383     return area->next;
3384   }
3385 
3386   for (ScrArea *area_iter = area->next; area_iter; area_iter = area_iter->next) {
3387     if ((area_iter->global->flag & GLOBAL_AREA_IS_HIDDEN) == 0) {
3388       return area_iter;
3389     }
3390   }
3391   /* No visible next global area found, start iterating over layout areas. */
3392   return screen->areabase.first;
3393 }
3394 
3395 /**
3396  * For now we just assume all global areas are made up out of horizontal bars
3397  * with the same size. A fixed size could be stored in ARegion instead if needed.
3398  *
3399  * \return the DPI aware height of a single bar/region in global areas.
3400  */
ED_region_global_size_y(void)3401 int ED_region_global_size_y(void)
3402 {
3403   return ED_area_headersize(); /* same size as header */
3404 }
3405 
ED_region_info_draw_multiline(ARegion * region,const char * text_array[],float fill_color[4],const bool full_redraw)3406 void ED_region_info_draw_multiline(ARegion *region,
3407                                    const char *text_array[],
3408                                    float fill_color[4],
3409                                    const bool full_redraw)
3410 {
3411   const int header_height = UI_UNIT_Y;
3412   const uiStyle *style = UI_style_get_dpi();
3413   int fontid = style->widget.uifont_id;
3414   int scissor[4];
3415   int num_lines = 0;
3416 
3417   /* background box */
3418   rcti rect = *ED_region_visible_rect(region);
3419 
3420   /* Box fill entire width or just around text. */
3421   if (!full_redraw) {
3422     const char **text = &text_array[0];
3423     while (*text) {
3424       rect.xmax = min_ii(rect.xmax,
3425                          rect.xmin + BLF_width(fontid, *text, BLF_DRAW_STR_DUMMY_MAX) +
3426                              1.2f * U.widget_unit);
3427       text++;
3428       num_lines++;
3429     }
3430   }
3431   /* Just count the line number. */
3432   else {
3433     const char **text = &text_array[0];
3434     while (*text) {
3435       text++;
3436       num_lines++;
3437     }
3438   }
3439 
3440   rect.ymin = rect.ymax - header_height * num_lines;
3441 
3442   /* setup scissor */
3443   GPU_scissor_get(scissor);
3444   GPU_scissor(rect.xmin, rect.ymin, BLI_rcti_size_x(&rect) + 1, BLI_rcti_size_y(&rect) + 1);
3445 
3446   GPU_blend(GPU_BLEND_ALPHA);
3447   GPUVertFormat *format = immVertexFormat();
3448   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
3449   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3450   immUniformColor4fv(fill_color);
3451   immRecti(pos, rect.xmin, rect.ymin, rect.xmax + 1, rect.ymax + 1);
3452   immUnbindProgram();
3453   GPU_blend(GPU_BLEND_NONE);
3454 
3455   /* text */
3456   UI_FontThemeColor(fontid, TH_TEXT_HI);
3457   BLF_clipping(fontid, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3458   BLF_enable(fontid, BLF_CLIPPING);
3459   int offset = num_lines - 1;
3460   {
3461     const char **text = &text_array[0];
3462     while (*text) {
3463       BLF_position(fontid,
3464                    rect.xmin + 0.6f * U.widget_unit,
3465                    rect.ymin + 0.3f * U.widget_unit + offset * header_height,
3466                    0.0f);
3467       BLF_draw(fontid, *text, BLF_DRAW_STR_DUMMY_MAX);
3468       text++;
3469       offset--;
3470     }
3471   }
3472 
3473   BLF_disable(fontid, BLF_CLIPPING);
3474 
3475   /* restore scissor as it was before */
3476   GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]);
3477 }
3478 
ED_region_info_draw(ARegion * region,const char * text,float fill_color[4],const bool full_redraw)3479 void ED_region_info_draw(ARegion *region,
3480                          const char *text,
3481                          float fill_color[4],
3482                          const bool full_redraw)
3483 {
3484   const char *text_array[2] = {text, NULL};
3485   ED_region_info_draw_multiline(region, text_array, fill_color, full_redraw);
3486 }
3487 
3488 #define MAX_METADATA_STR 1024
3489 
3490 static const char *meta_data_list[] = {
3491     "File",
3492     "Strip",
3493     "Date",
3494     "RenderTime",
3495     "Note",
3496     "Marker",
3497     "Time",
3498     "Frame",
3499     "Camera",
3500     "Scene",
3501 };
3502 
metadata_is_valid(ImBuf * ibuf,char * r_str,short index,int offset)3503 BLI_INLINE bool metadata_is_valid(ImBuf *ibuf, char *r_str, short index, int offset)
3504 {
3505   return (IMB_metadata_get_field(
3506               ibuf->metadata, meta_data_list[index], r_str + offset, MAX_METADATA_STR - offset) &&
3507           r_str[0]);
3508 }
3509 
metadata_is_custom_drawable(const char * field)3510 BLI_INLINE bool metadata_is_custom_drawable(const char *field)
3511 {
3512   /* Metadata field stored by Blender for multilayer EXR images. Is rather
3513    * useless to be viewed all the time. Can still be seen in the Metadata
3514    * panel. */
3515   if (STREQ(field, "BlenderMultiChannel")) {
3516     return false;
3517   }
3518   /* Is almost always has value "scanlineimage", also useless to be seen
3519    * all the time. */
3520   if (STREQ(field, "type")) {
3521     return false;
3522   }
3523   return !BKE_stamp_is_known_field(field);
3524 }
3525 
3526 typedef struct MetadataCustomDrawContext {
3527   int fontid;
3528   int xmin, ymin;
3529   int vertical_offset;
3530   int current_y;
3531 } MetadataCustomDrawContext;
3532 
metadata_custom_draw_fields(const char * field,const char * value,void * ctx_v)3533 static void metadata_custom_draw_fields(const char *field, const char *value, void *ctx_v)
3534 {
3535   if (!metadata_is_custom_drawable(field)) {
3536     return;
3537   }
3538   MetadataCustomDrawContext *ctx = (MetadataCustomDrawContext *)ctx_v;
3539   char temp_str[MAX_METADATA_STR];
3540   BLI_snprintf(temp_str, MAX_METADATA_STR, "%s: %s", field, value);
3541   BLF_position(ctx->fontid, ctx->xmin, ctx->ymin + ctx->current_y, 0.0f);
3542   BLF_draw(ctx->fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3543   ctx->current_y += ctx->vertical_offset;
3544 }
3545 
metadata_draw_imbuf(ImBuf * ibuf,const rctf * rect,int fontid,const bool is_top)3546 static void metadata_draw_imbuf(ImBuf *ibuf, const rctf *rect, int fontid, const bool is_top)
3547 {
3548   char temp_str[MAX_METADATA_STR];
3549   int ofs_y = 0;
3550   const float height = BLF_height_max(fontid);
3551   const float margin = height / 8;
3552   const float vertical_offset = (height + margin);
3553 
3554   /* values taking margins into account */
3555   const float descender = BLF_descender(fontid);
3556   const float xmin = (rect->xmin + margin);
3557   const float xmax = (rect->xmax - margin);
3558   const float ymin = (rect->ymin + margin) - descender;
3559   const float ymax = (rect->ymax - margin) - descender;
3560 
3561   if (is_top) {
3562     for (int i = 0; i < 4; i++) {
3563       /* first line */
3564       if (i == 0) {
3565         bool do_newline = false;
3566         int len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[0]);
3567         if (metadata_is_valid(ibuf, temp_str, 0, len)) {
3568           BLF_position(fontid, xmin, ymax - vertical_offset, 0.0f);
3569           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3570           do_newline = true;
3571         }
3572 
3573         len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[1]);
3574         if (metadata_is_valid(ibuf, temp_str, 1, len)) {
3575           int line_width = BLF_width(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3576           BLF_position(fontid, xmax - line_width, ymax - vertical_offset, 0.0f);
3577           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3578           do_newline = true;
3579         }
3580 
3581         if (do_newline) {
3582           ofs_y += vertical_offset;
3583         }
3584       } /* Strip */
3585       else if (i == 1 || i == 2) {
3586         int len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i + 1]);
3587         if (metadata_is_valid(ibuf, temp_str, i + 1, len)) {
3588           BLF_position(fontid, xmin, ymax - vertical_offset - ofs_y, 0.0f);
3589           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3590           ofs_y += vertical_offset;
3591         }
3592       } /* Note (wrapped) */
3593       else if (i == 3) {
3594         int len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i + 1]);
3595         if (metadata_is_valid(ibuf, temp_str, i + 1, len)) {
3596           struct ResultBLF info;
3597           BLF_enable(fontid, BLF_WORD_WRAP);
3598           BLF_wordwrap(fontid, ibuf->x - (margin * 2));
3599           BLF_position(fontid, xmin, ymax - vertical_offset - ofs_y, 0.0f);
3600           BLF_draw_ex(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX, &info);
3601           BLF_wordwrap(fontid, 0);
3602           BLF_disable(fontid, BLF_WORD_WRAP);
3603           ofs_y += vertical_offset * info.lines;
3604         }
3605       }
3606       else {
3607         int len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i + 1]);
3608         if (metadata_is_valid(ibuf, temp_str, i + 1, len)) {
3609           int line_width = BLF_width(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3610           BLF_position(fontid, xmax - line_width, ymax - vertical_offset - ofs_y, 0.0f);
3611           BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3612           ofs_y += vertical_offset;
3613         }
3614       }
3615     }
3616   }
3617   else {
3618     MetadataCustomDrawContext ctx;
3619     ctx.fontid = fontid;
3620     ctx.xmin = xmin;
3621     ctx.ymin = ymin;
3622     ctx.current_y = ofs_y;
3623     ctx.vertical_offset = vertical_offset;
3624     IMB_metadata_foreach(ibuf, metadata_custom_draw_fields, &ctx);
3625     int ofs_x = 0;
3626     ofs_y = ctx.current_y;
3627     for (int i = 5; i < 10; i++) {
3628       int len = BLI_snprintf_rlen(temp_str, MAX_METADATA_STR, "%s: ", meta_data_list[i]);
3629       if (metadata_is_valid(ibuf, temp_str, i, len)) {
3630         BLF_position(fontid, xmin + ofs_x, ymin + ofs_y, 0.0f);
3631         BLF_draw(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX);
3632 
3633         ofs_x += BLF_width(fontid, temp_str, BLF_DRAW_STR_DUMMY_MAX) + UI_UNIT_X;
3634       }
3635     }
3636   }
3637 }
3638 
3639 typedef struct MetadataCustomCountContext {
3640   int count;
3641 } MetadataCustomCountContext;
3642 
metadata_custom_count_fields(const char * field,const char * UNUSED (value),void * ctx_v)3643 static void metadata_custom_count_fields(const char *field, const char *UNUSED(value), void *ctx_v)
3644 {
3645   if (!metadata_is_custom_drawable(field)) {
3646     return;
3647   }
3648   MetadataCustomCountContext *ctx = (MetadataCustomCountContext *)ctx_v;
3649   ctx->count++;
3650 }
3651 
metadata_box_height_get(ImBuf * ibuf,int fontid,const bool is_top)3652 static float metadata_box_height_get(ImBuf *ibuf, int fontid, const bool is_top)
3653 {
3654   const float height = BLF_height_max(fontid);
3655   const float margin = (height / 8);
3656   char str[MAX_METADATA_STR] = "";
3657   short count = 0;
3658 
3659   if (is_top) {
3660     if (metadata_is_valid(ibuf, str, 0, 0) || metadata_is_valid(ibuf, str, 1, 0)) {
3661       count++;
3662     }
3663     for (int i = 2; i < 5; i++) {
3664       if (metadata_is_valid(ibuf, str, i, 0)) {
3665         if (i == 4) {
3666           struct {
3667             struct ResultBLF info;
3668             rctf rect;
3669           } wrap;
3670 
3671           BLF_enable(fontid, BLF_WORD_WRAP);
3672           BLF_wordwrap(fontid, ibuf->x - (margin * 2));
3673           BLF_boundbox_ex(fontid, str, sizeof(str), &wrap.rect, &wrap.info);
3674           BLF_wordwrap(fontid, 0);
3675           BLF_disable(fontid, BLF_WORD_WRAP);
3676 
3677           count += wrap.info.lines;
3678         }
3679         else {
3680           count++;
3681         }
3682       }
3683     }
3684   }
3685   else {
3686     for (int i = 5; i < 10; i++) {
3687       if (metadata_is_valid(ibuf, str, i, 0)) {
3688         count = 1;
3689         break;
3690       }
3691     }
3692     MetadataCustomCountContext ctx;
3693     ctx.count = 0;
3694     IMB_metadata_foreach(ibuf, metadata_custom_count_fields, &ctx);
3695     count += ctx.count;
3696   }
3697 
3698   if (count) {
3699     return (height + margin) * count;
3700   }
3701 
3702   return 0;
3703 }
3704 
3705 #undef MAX_METADATA_STR
3706 
ED_region_image_metadata_draw(int x,int y,ImBuf * ibuf,const rctf * frame,float zoomx,float zoomy)3707 void ED_region_image_metadata_draw(
3708     int x, int y, ImBuf *ibuf, const rctf *frame, float zoomx, float zoomy)
3709 {
3710   const uiStyle *style = UI_style_get_dpi();
3711 
3712   if (!ibuf->metadata) {
3713     return;
3714   }
3715 
3716   /* find window pixel coordinates of origin */
3717   GPU_matrix_push();
3718 
3719   /* offset and zoom using ogl */
3720   GPU_matrix_translate_2f(x, y);
3721   GPU_matrix_scale_2f(zoomx, zoomy);
3722 
3723   BLF_size(blf_mono_font, style->widgetlabel.points * 1.5f * U.pixelsize, U.dpi);
3724 
3725   /* *** upper box*** */
3726 
3727   /* get needed box height */
3728   float box_y = metadata_box_height_get(ibuf, blf_mono_font, true);
3729 
3730   if (box_y) {
3731     /* set up rect */
3732     rctf rect;
3733     BLI_rctf_init(&rect, frame->xmin, frame->xmax, frame->ymax, frame->ymax + box_y);
3734     /* draw top box */
3735     GPUVertFormat *format = immVertexFormat();
3736     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3737     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3738     immUniformThemeColor(TH_METADATA_BG);
3739     immRectf(pos, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3740     immUnbindProgram();
3741 
3742     BLF_clipping(blf_mono_font, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3743     BLF_enable(blf_mono_font, BLF_CLIPPING);
3744 
3745     UI_FontThemeColor(blf_mono_font, TH_METADATA_TEXT);
3746     metadata_draw_imbuf(ibuf, &rect, blf_mono_font, true);
3747 
3748     BLF_disable(blf_mono_font, BLF_CLIPPING);
3749   }
3750 
3751   /* *** lower box*** */
3752 
3753   box_y = metadata_box_height_get(ibuf, blf_mono_font, false);
3754 
3755   if (box_y) {
3756     /* set up box rect */
3757     rctf rect;
3758     BLI_rctf_init(&rect, frame->xmin, frame->xmax, frame->ymin - box_y, frame->ymin);
3759     /* draw top box */
3760     GPUVertFormat *format = immVertexFormat();
3761     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3762     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3763     immUniformThemeColor(TH_METADATA_BG);
3764     immRectf(pos, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3765     immUnbindProgram();
3766 
3767     BLF_clipping(blf_mono_font, rect.xmin, rect.ymin, rect.xmax, rect.ymax);
3768     BLF_enable(blf_mono_font, BLF_CLIPPING);
3769 
3770     UI_FontThemeColor(blf_mono_font, TH_METADATA_TEXT);
3771     metadata_draw_imbuf(ibuf, &rect, blf_mono_font, false);
3772 
3773     BLF_disable(blf_mono_font, BLF_CLIPPING);
3774   }
3775 
3776   GPU_matrix_pop();
3777 }
3778 
3779 typedef struct MetadataPanelDrawContext {
3780   uiLayout *layout;
3781 } MetadataPanelDrawContext;
3782 
metadata_panel_draw_field(const char * field,const char * value,void * ctx_v)3783 static void metadata_panel_draw_field(const char *field, const char *value, void *ctx_v)
3784 {
3785   MetadataPanelDrawContext *ctx = (MetadataPanelDrawContext *)ctx_v;
3786   uiLayout *row = uiLayoutRow(ctx->layout, false);
3787   uiItemL(row, field, ICON_NONE);
3788   uiItemL(row, value, ICON_NONE);
3789 }
3790 
ED_region_image_metadata_panel_draw(ImBuf * ibuf,uiLayout * layout)3791 void ED_region_image_metadata_panel_draw(ImBuf *ibuf, uiLayout *layout)
3792 {
3793   MetadataPanelDrawContext ctx;
3794   ctx.layout = layout;
3795   IMB_metadata_foreach(ibuf, metadata_panel_draw_field, &ctx);
3796 }
3797 
ED_region_grid_draw(ARegion * region,float zoomx,float zoomy,float x0,float y0)3798 void ED_region_grid_draw(ARegion *region, float zoomx, float zoomy, float x0, float y0)
3799 {
3800   /* the image is located inside (x0, y0), (x0+1, y0+1) as set by view2d */
3801   int x1, y1, x2, y2;
3802   UI_view2d_view_to_region(&region->v2d, x0, y0, &x1, &y1);
3803   UI_view2d_view_to_region(&region->v2d, x0 + 1.0f, y0 + 1.0f, &x2, &y2);
3804 
3805   GPUVertFormat *format = immVertexFormat();
3806   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3807 
3808   float gridcolor[4];
3809   UI_GetThemeColor4fv(TH_GRID, gridcolor);
3810 
3811   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3812   /* To fake alpha-blending, color shading is reduced when alpha is nearing 0. */
3813   immUniformThemeColorBlendShade(TH_BACK, TH_GRID, gridcolor[3], 20 * gridcolor[3]);
3814   immRectf(pos, x1, y1, x2, y2);
3815   immUnbindProgram();
3816 
3817   /* gridsize adapted to zoom level */
3818   float gridsize = 0.5f * (zoomx + zoomy);
3819   float gridstep = 1.0f / 32.0f;
3820   if (gridsize <= 0.0f) {
3821     return;
3822   }
3823 
3824   if (gridsize < 1.0f) {
3825     while (gridsize < 1.0f) {
3826       gridsize *= 4.0f;
3827       gridstep *= 4.0f;
3828     }
3829   }
3830   else {
3831     while (gridsize >= 4.0f) {
3832       gridsize /= 4.0f;
3833       gridstep /= 4.0f;
3834     }
3835   }
3836 
3837   float blendfac = 0.25f * gridsize - floorf(0.25f * gridsize);
3838   CLAMP(blendfac, 0.0f, 1.0f);
3839 
3840   int count_fine = 1.0f / gridstep;
3841   int count_large = 1.0f / (4.0f * gridstep);
3842 
3843   if (count_fine > 0) {
3844     GPU_vertformat_clear(format);
3845     pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
3846     uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
3847 
3848     immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
3849     immBegin(GPU_PRIM_LINES, 4 * count_fine + 4 * count_large);
3850 
3851     float theme_color[3];
3852     UI_GetThemeColorShade3fv(TH_GRID, (int)(20.0f * (1.0f - blendfac)), theme_color);
3853     float fac = 0.0f;
3854 
3855     /* the fine resolution level */
3856     for (int i = 0; i < count_fine; i++) {
3857       immAttr3fv(color, theme_color);
3858       immVertex2f(pos, x1, y1 * (1.0f - fac) + y2 * fac);
3859       immAttr3fv(color, theme_color);
3860       immVertex2f(pos, x2, y1 * (1.0f - fac) + y2 * fac);
3861       immAttr3fv(color, theme_color);
3862       immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y1);
3863       immAttr3fv(color, theme_color);
3864       immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y2);
3865       fac += gridstep;
3866     }
3867 
3868     if (count_large > 0) {
3869       UI_GetThemeColor3fv(TH_GRID, theme_color);
3870       fac = 0.0f;
3871 
3872       /* the large resolution level */
3873       for (int i = 0; i < count_large; i++) {
3874         immAttr3fv(color, theme_color);
3875         immVertex2f(pos, x1, y1 * (1.0f - fac) + y2 * fac);
3876         immAttr3fv(color, theme_color);
3877         immVertex2f(pos, x2, y1 * (1.0f - fac) + y2 * fac);
3878         immAttr3fv(color, theme_color);
3879         immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y1);
3880         immAttr3fv(color, theme_color);
3881         immVertex2f(pos, x1 * (1.0f - fac) + x2 * fac, y2);
3882         fac += 4.0f * gridstep;
3883       }
3884     }
3885 
3886     immEnd();
3887     immUnbindProgram();
3888   }
3889 }
3890 
3891 /* If the area has overlapping regions, it returns visible rect for Region *region */
3892 /* rect gets returned in local region coordinates */
region_visible_rect_calc(ARegion * region,rcti * rect)3893 static void region_visible_rect_calc(ARegion *region, rcti *rect)
3894 {
3895   ARegion *arn = region;
3896 
3897   /* allow function to be called without area */
3898   while (arn->prev) {
3899     arn = arn->prev;
3900   }
3901 
3902   *rect = region->winrct;
3903 
3904   /* check if a region overlaps with the current one */
3905   for (; arn; arn = arn->next) {
3906     if (region != arn && arn->overlap) {
3907       if (BLI_rcti_isect(rect, &arn->winrct, NULL)) {
3908         int alignment = RGN_ALIGN_ENUM_FROM_MASK(arn->alignment);
3909 
3910         if (ELEM(alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
3911           /* Overlap left, also check 1 pixel offset (2 regions on one side). */
3912           if (abs(rect->xmin - arn->winrct.xmin) < 2) {
3913             rect->xmin = arn->winrct.xmax;
3914           }
3915 
3916           /* Overlap right. */
3917           if (abs(rect->xmax - arn->winrct.xmax) < 2) {
3918             rect->xmax = arn->winrct.xmin;
3919           }
3920         }
3921         else if (ELEM(alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
3922           /* Same logic as above for vertical regions. */
3923           if (abs(rect->ymin - arn->winrct.ymin) < 2) {
3924             rect->ymin = arn->winrct.ymax;
3925           }
3926           if (abs(rect->ymax - arn->winrct.ymax) < 2) {
3927             rect->ymax = arn->winrct.ymin;
3928           }
3929         }
3930         else if (alignment == RGN_ALIGN_FLOAT) {
3931           /* Skip floating. */
3932         }
3933         else {
3934           BLI_assert(!"Region overlap with unknown alignment");
3935         }
3936       }
3937     }
3938   }
3939   BLI_rcti_translate(rect, -region->winrct.xmin, -region->winrct.ymin);
3940 }
3941 
ED_region_visible_rect(ARegion * region)3942 const rcti *ED_region_visible_rect(ARegion *region)
3943 {
3944   rcti *rect = &region->runtime.visible_rect;
3945   if (rect->xmin == 0 && rect->ymin == 0 && rect->xmax == 0 && rect->ymax == 0) {
3946     region_visible_rect_calc(region, rect);
3947   }
3948   return rect;
3949 }
3950 
3951 /* Cache display helpers */
3952 
ED_region_cache_draw_background(ARegion * region)3953 void ED_region_cache_draw_background(ARegion *region)
3954 {
3955   /* Local coordinate visible rect inside region, to accommodate overlapping ui. */
3956   const rcti *rect_visible = ED_region_visible_rect(region);
3957   const int region_bottom = rect_visible->ymin;
3958 
3959   uint pos = GPU_vertformat_attr_add(
3960       immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
3961   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3962   immUniformColor4ub(128, 128, 255, 64);
3963   immRecti(pos, 0, region_bottom, region->winx, region_bottom + 8 * UI_DPI_FAC);
3964   immUnbindProgram();
3965 }
3966 
ED_region_cache_draw_curfra_label(const int framenr,const float x,const float y)3967 void ED_region_cache_draw_curfra_label(const int framenr, const float x, const float y)
3968 {
3969   const uiStyle *style = UI_style_get();
3970   int fontid = style->widget.uifont_id;
3971   char numstr[32];
3972   float font_dims[2] = {0.0f, 0.0f};
3973 
3974   /* frame number */
3975   BLF_size(fontid, 11.0f * U.pixelsize, U.dpi);
3976   BLI_snprintf(numstr, sizeof(numstr), "%d", framenr);
3977 
3978   BLF_width_and_height(fontid, numstr, sizeof(numstr), &font_dims[0], &font_dims[1]);
3979 
3980   uint pos = GPU_vertformat_attr_add(
3981       immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
3982   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
3983   immUniformThemeColor(TH_CFRAME);
3984   immRecti(pos, x, y, x + font_dims[0] + 6.0f, y + font_dims[1] + 4.0f);
3985   immUnbindProgram();
3986 
3987   UI_FontThemeColor(fontid, TH_TEXT);
3988   BLF_position(fontid, x + 2.0f, y + 2.0f, 0.0f);
3989   BLF_draw(fontid, numstr, sizeof(numstr));
3990 }
3991 
ED_region_cache_draw_cached_segments(ARegion * region,const int num_segments,const int * points,const int sfra,const int efra)3992 void ED_region_cache_draw_cached_segments(
3993     ARegion *region, const int num_segments, const int *points, const int sfra, const int efra)
3994 {
3995   if (num_segments) {
3996     /* Local coordinate visible rect inside region, to accommodate overlapping ui. */
3997     const rcti *rect_visible = ED_region_visible_rect(region);
3998     const int region_bottom = rect_visible->ymin;
3999 
4000     uint pos = GPU_vertformat_attr_add(
4001         immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
4002     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
4003     immUniformColor4ub(128, 128, 255, 128);
4004 
4005     for (int a = 0; a < num_segments; a++) {
4006       float x1 = (float)(points[a * 2] - sfra) / (efra - sfra + 1) * region->winx;
4007       float x2 = (float)(points[a * 2 + 1] - sfra + 1) / (efra - sfra + 1) * region->winx;
4008 
4009       immRecti(pos, x1, region_bottom, x2, region_bottom + 8 * UI_DPI_FAC);
4010       /* TODO(merwin): use primitive restart to draw multiple rects more efficiently */
4011     }
4012 
4013     immUnbindProgram();
4014   }
4015 }
4016 
4017 /**
4018  * Generate subscriptions for this region.
4019  */
ED_region_message_subscribe(bContext * C,struct WorkSpace * workspace,struct Scene * scene,struct bScreen * screen,struct ScrArea * area,struct ARegion * region,struct wmMsgBus * mbus)4020 void ED_region_message_subscribe(bContext *C,
4021                                  struct WorkSpace *workspace,
4022                                  struct Scene *scene,
4023                                  struct bScreen *screen,
4024                                  struct ScrArea *area,
4025                                  struct ARegion *region,
4026                                  struct wmMsgBus *mbus)
4027 {
4028   if (region->gizmo_map != NULL) {
4029     WM_gizmomap_message_subscribe(C, region->gizmo_map, region, mbus);
4030   }
4031 
4032   if (!BLI_listbase_is_empty(&region->uiblocks)) {
4033     UI_region_message_subscribe(region, mbus);
4034   }
4035 
4036   if (region->type->message_subscribe != NULL) {
4037     region->type->message_subscribe(C, workspace, scene, screen, area, region, mbus);
4038   }
4039 }
4040 
ED_region_snap_size_test(const ARegion * region)4041 int ED_region_snap_size_test(const ARegion *region)
4042 {
4043   /* Use a larger value because toggling scrollbars can jump in size. */
4044   const int snap_match_threshold = 16;
4045   if (region->type->snap_size != NULL) {
4046     return ((((region->sizex - region->type->snap_size(region, region->sizex, 0)) <=
4047               snap_match_threshold)
4048              << 0) |
4049             (((region->sizey - region->type->snap_size(region, region->sizey, 1)) <=
4050               snap_match_threshold)
4051              << 1));
4052   }
4053   return 0;
4054 }
4055 
ED_region_snap_size_apply(ARegion * region,int snap_flag)4056 bool ED_region_snap_size_apply(ARegion *region, int snap_flag)
4057 {
4058   bool changed = false;
4059   if (region->type->snap_size != NULL) {
4060     if (snap_flag & (1 << 0)) {
4061       short snap_size = region->type->snap_size(region, region->sizex, 0);
4062       if (snap_size != region->sizex) {
4063         region->sizex = snap_size;
4064         changed = true;
4065       }
4066     }
4067     if (snap_flag & (1 << 1)) {
4068       short snap_size = region->type->snap_size(region, region->sizey, 1);
4069       if (snap_size != region->sizey) {
4070         region->sizey = snap_size;
4071         changed = true;
4072       }
4073     }
4074   }
4075   return changed;
4076 }
4077