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 edinterface
22  *
23  * Floating Persistent Region
24  */
25 
26 #include <string.h>
27 
28 #include "MEM_guardedalloc.h"
29 
30 #include "DNA_screen_types.h"
31 #include "DNA_userdef_types.h"
32 
33 #include "BLI_listbase.h"
34 #include "BLI_rect.h"
35 #include "BLI_string.h"
36 #include "BLI_utildefines.h"
37 
38 #include "BKE_context.h"
39 #include "BKE_screen.h"
40 
41 #include "WM_api.h"
42 #include "WM_types.h"
43 
44 #include "RNA_access.h"
45 
46 #include "UI_interface.h"
47 #include "UI_view2d.h"
48 
49 #include "BLT_translation.h"
50 
51 #include "ED_screen.h"
52 #include "ED_undo.h"
53 
54 #include "GPU_framebuffer.h"
55 #include "interface_intern.h"
56 
57 /* -------------------------------------------------------------------- */
58 /** \name Utilities
59  * \{ */
60 struct HudRegionData {
61   short regionid;
62 };
63 
last_redo_poll(const bContext * C,short region_type)64 static bool last_redo_poll(const bContext *C, short region_type)
65 {
66   wmOperator *op = WM_operator_last_redo(C);
67   if (op == NULL) {
68     return false;
69   }
70 
71   bool success = false;
72   {
73     /* Make sure that we are using the same region type as the original
74      * operator call. Otherwise we would be polling the operator with the
75      * wrong context.
76      */
77     ScrArea *area = CTX_wm_area(C);
78     ARegion *region_op = (region_type != -1) ? BKE_area_find_region_type(area, region_type) : NULL;
79     ARegion *region_prev = CTX_wm_region(C);
80     CTX_wm_region_set((bContext *)C, region_op);
81 
82     if (WM_operator_repeat_check(C, op) && WM_operator_check_ui_empty(op->type) == false) {
83       success = WM_operator_poll((bContext *)C, op->type);
84     }
85     CTX_wm_region_set((bContext *)C, region_prev);
86   }
87   return success;
88 }
89 
hud_region_hide(ARegion * region)90 static void hud_region_hide(ARegion *region)
91 {
92   region->flag |= RGN_FLAG_HIDDEN;
93   /* Avoids setting 'AREA_FLAG_REGION_SIZE_UPDATE'
94    * since other regions don't depend on this. */
95   BLI_rcti_init(&region->winrct, 0, 0, 0, 0);
96 }
97 
98 /** \} */
99 
100 /* -------------------------------------------------------------------- */
101 /** \name Redo Panel
102  * \{ */
103 
hud_panel_operator_redo_poll(const bContext * C,PanelType * UNUSED (pt))104 static bool hud_panel_operator_redo_poll(const bContext *C, PanelType *UNUSED(pt))
105 {
106   ScrArea *area = CTX_wm_area(C);
107   ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD);
108   if (region != NULL) {
109     struct HudRegionData *hrd = region->regiondata;
110     if (hrd != NULL) {
111       return last_redo_poll(C, hrd->regionid);
112     }
113   }
114   return false;
115 }
116 
hud_panel_operator_redo_draw_header(const bContext * C,Panel * panel)117 static void hud_panel_operator_redo_draw_header(const bContext *C, Panel *panel)
118 {
119   wmOperator *op = WM_operator_last_redo(C);
120   BLI_strncpy(panel->drawname, WM_operatortype_name(op->type, op->ptr), sizeof(panel->drawname));
121 }
122 
hud_panel_operator_redo_draw(const bContext * C,Panel * panel)123 static void hud_panel_operator_redo_draw(const bContext *C, Panel *panel)
124 {
125   wmOperator *op = WM_operator_last_redo(C);
126   if (op == NULL) {
127     return;
128   }
129   if (!WM_operator_check_ui_enabled(C, op->type->name)) {
130     uiLayoutSetEnabled(panel->layout, false);
131   }
132   uiLayout *col = uiLayoutColumn(panel->layout, false);
133   uiTemplateOperatorRedoProperties(col, C);
134 }
135 
hud_panels_register(ARegionType * art,int space_type,int region_type)136 static void hud_panels_register(ARegionType *art, int space_type, int region_type)
137 {
138   PanelType *pt;
139 
140   pt = MEM_callocN(sizeof(PanelType), __func__);
141   strcpy(pt->idname, "OPERATOR_PT_redo");
142   strcpy(pt->label, N_("Redo"));
143   strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
144   pt->draw_header = hud_panel_operator_redo_draw_header;
145   pt->draw = hud_panel_operator_redo_draw;
146   pt->poll = hud_panel_operator_redo_poll;
147   pt->space_type = space_type;
148   pt->region_type = region_type;
149   pt->flag |= PNL_DEFAULT_CLOSED;
150   BLI_addtail(&art->paneltypes, pt);
151 }
152 
153 /** \} */
154 
155 /* -------------------------------------------------------------------- */
156 /** \name Callbacks for Floating Region
157  * \{ */
158 
hud_region_init(wmWindowManager * wm,ARegion * region)159 static void hud_region_init(wmWindowManager *wm, ARegion *region)
160 {
161   ED_region_panels_init(wm, region);
162   UI_region_handlers_add(&region->handlers);
163   region->flag |= RGN_FLAG_TEMP_REGIONDATA;
164 }
165 
hud_region_free(ARegion * region)166 static void hud_region_free(ARegion *region)
167 {
168   MEM_SAFE_FREE(region->regiondata);
169 }
170 
hud_region_layout(const bContext * C,ARegion * region)171 static void hud_region_layout(const bContext *C, ARegion *region)
172 {
173   struct HudRegionData *hrd = region->regiondata;
174   if (hrd == NULL || !last_redo_poll(C, hrd->regionid)) {
175     ED_region_tag_redraw(region);
176     hud_region_hide(region);
177     return;
178   }
179 
180   ScrArea *area = CTX_wm_area(C);
181   const int size_y = region->sizey;
182 
183   ED_region_panels_layout(C, region);
184 
185   if (region->panels.first &&
186       ((area->flag & AREA_FLAG_REGION_SIZE_UPDATE) || (region->sizey != size_y))) {
187     int winx_new = UI_DPI_FAC * (region->sizex + 0.5f);
188     int winy_new = UI_DPI_FAC * (region->sizey + 0.5f);
189     View2D *v2d = &region->v2d;
190 
191     if (region->flag & RGN_FLAG_SIZE_CLAMP_X) {
192       CLAMP_MAX(winx_new, region->winx);
193     }
194     if (region->flag & RGN_FLAG_SIZE_CLAMP_Y) {
195       CLAMP_MAX(winy_new, region->winy);
196     }
197 
198     region->winx = winx_new;
199     region->winy = winy_new;
200 
201     region->winrct.xmax = (region->winrct.xmin + region->winx) - 1;
202     region->winrct.ymax = (region->winrct.ymin + region->winy) - 1;
203 
204     UI_view2d_region_reinit(v2d, V2D_COMMONVIEW_LIST, region->winx, region->winy);
205 
206     /* Weak, but needed to avoid glitches, especially with hi-dpi
207      * (where resizing the view glitches often).
208      * Fortunately this only happens occasionally. */
209     ED_region_panels_layout(C, region);
210   }
211 
212   /* restore view matrix */
213   UI_view2d_view_restore(C);
214 }
215 
hud_region_draw(const bContext * C,ARegion * region)216 static void hud_region_draw(const bContext *C, ARegion *region)
217 {
218   UI_view2d_view_ortho(&region->v2d);
219   wmOrtho2_region_pixelspace(region);
220   GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);
221 
222   if ((region->flag & RGN_FLAG_HIDDEN) == 0) {
223     ui_draw_menu_back(NULL,
224                       NULL,
225                       &(rcti){
226                           .xmax = region->winx,
227                           .ymax = region->winy,
228                       });
229     ED_region_panels_draw(C, region);
230   }
231 }
232 
ED_area_type_hud(int space_type)233 ARegionType *ED_area_type_hud(int space_type)
234 {
235   ARegionType *art = MEM_callocN(sizeof(ARegionType), __func__);
236   art->regionid = RGN_TYPE_HUD;
237   art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D;
238   art->layout = hud_region_layout;
239   art->draw = hud_region_draw;
240   art->init = hud_region_init;
241   art->free = hud_region_free;
242 
243   /* We need to indicate a preferred size to avoid false `RGN_FLAG_TOO_SMALL`
244    * the first time the region is created. */
245   art->prefsizex = AREAMINX;
246   art->prefsizey = HEADERY;
247 
248   hud_panels_register(art, space_type, art->regionid);
249 
250   art->lock = 1; /* can become flag, see BKE_spacedata_draw_locks */
251   return art;
252 }
253 
hud_region_add(ScrArea * area)254 static ARegion *hud_region_add(ScrArea *area)
255 {
256   ARegion *region = MEM_callocN(sizeof(ARegion), "area region");
257   ARegion *region_win = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
258   if (region_win) {
259     BLI_insertlinkbefore(&area->regionbase, region_win, region);
260   }
261   else {
262     BLI_addtail(&area->regionbase, region);
263   }
264   region->regiontype = RGN_TYPE_HUD;
265   region->alignment = RGN_ALIGN_FLOAT;
266   region->overlap = true;
267   region->flag |= RGN_FLAG_DYNAMIC_SIZE;
268 
269   if (region_win) {
270     float x, y;
271 
272     UI_view2d_scroller_size_get(&region_win->v2d, &x, &y);
273     region->runtime.offset_x = x;
274     region->runtime.offset_y = y;
275   }
276 
277   return region;
278 }
279 
ED_area_type_hud_clear(wmWindowManager * wm,ScrArea * area_keep)280 void ED_area_type_hud_clear(wmWindowManager *wm, ScrArea *area_keep)
281 {
282   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
283     bScreen *screen = WM_window_get_active_screen(win);
284     LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
285       if (area != area_keep) {
286         LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
287           if (region->regiontype == RGN_TYPE_HUD) {
288             if ((region->flag & RGN_FLAG_HIDDEN) == 0) {
289               hud_region_hide(region);
290               ED_region_tag_redraw(region);
291               ED_area_tag_redraw(area);
292             }
293           }
294         }
295       }
296     }
297   }
298 }
299 
ED_area_type_hud_ensure(bContext * C,ScrArea * area)300 void ED_area_type_hud_ensure(bContext *C, ScrArea *area)
301 {
302   wmWindowManager *wm = CTX_wm_manager(C);
303   ED_area_type_hud_clear(wm, area);
304 
305   ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_HUD);
306   if (art == NULL) {
307     return;
308   }
309 
310   ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD);
311 
312   if (region && (region->flag & RGN_FLAG_HIDDEN_BY_USER)) {
313     /* The region is intentionally hidden by the user, don't show it. */
314     hud_region_hide(region);
315     return;
316   }
317 
318   bool init = false;
319   const bool was_hidden = region == NULL || region->visible == false;
320   ARegion *region_op = CTX_wm_region(C);
321   BLI_assert((region_op == NULL) || (region_op->regiontype != RGN_TYPE_HUD));
322   if (!last_redo_poll(C, region_op ? region_op->regiontype : -1)) {
323     if (region) {
324       ED_region_tag_redraw(region);
325       hud_region_hide(region);
326     }
327     return;
328   }
329 
330   if (region == NULL) {
331     init = true;
332     region = hud_region_add(area);
333     region->type = art;
334   }
335 
336   /* Let 'ED_area_update_region_sizes' do the work of placing the region.
337    * Otherwise we could set the 'region->winrct' & 'region->winx/winy' here. */
338   if (init) {
339     area->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
340   }
341   else {
342     if (region->flag & RGN_FLAG_HIDDEN) {
343       /* Also forces recalculating HUD size in hud_region_layout(). */
344       area->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
345     }
346     region->flag &= ~RGN_FLAG_HIDDEN;
347   }
348 
349   {
350     struct HudRegionData *hrd = region->regiondata;
351     if (hrd == NULL) {
352       hrd = MEM_callocN(sizeof(*hrd), __func__);
353       region->regiondata = hrd;
354     }
355     if (region_op) {
356       hrd->regionid = region_op->regiontype;
357     }
358     else {
359       hrd->regionid = -1;
360     }
361   }
362 
363   if (init) {
364     /* This is needed or 'winrct' will be invalid. */
365     wmWindow *win = CTX_wm_window(C);
366     ED_area_update_region_sizes(wm, win, area);
367   }
368 
369   ED_region_floating_init(region);
370   ED_region_tag_redraw(region);
371 
372   /* Reset zoom level (not well supported). */
373   region->v2d.cur = region->v2d.tot = (rctf){
374       .xmax = region->winx,
375       .ymax = region->winy,
376   };
377   region->v2d.minzoom = 1.0f;
378   region->v2d.maxzoom = 1.0f;
379 
380   region->visible = !(region->flag & RGN_FLAG_HIDDEN);
381 
382   /* We shouldn't need to do this every time :S */
383   /* XXX, this is evil! - it also makes the menu show on first draw. :( */
384   if (region->visible) {
385     ARegion *region_prev = CTX_wm_region(C);
386     CTX_wm_region_set((bContext *)C, region);
387     hud_region_layout(C, region);
388     if (was_hidden) {
389       region->winx = region->v2d.winx;
390       region->winy = region->v2d.winy;
391       region->v2d.cur = region->v2d.tot = (rctf){
392           .xmax = region->winx,
393           .ymax = region->winy,
394       };
395     }
396     CTX_wm_region_set((bContext *)C, region_prev);
397   }
398 
399   region->visible = !((region->flag & RGN_FLAG_HIDDEN) || (region->flag & RGN_FLAG_TOO_SMALL));
400 }
401 
402 /** \} */
403