Fix T67191: Redo fails for 3D text operations
[blender.git] / source / blender / editors / interface / interface_region_hud.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup 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_string.h"
34 #include "BLI_rect.h"
35 #include "BLI_listbase.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 "interface_intern.h"
55 #include "GPU_framebuffer.h"
56
57 /* -------------------------------------------------------------------- */
58 /** \name Utilities
59  * \{ */
60
61 static bool last_redo_poll(const bContext *C)
62 {
63   wmOperator *op = WM_operator_last_redo(C);
64   if (op == NULL) {
65     return false;
66   }
67   bool success = false;
68   if (WM_operator_repeat_check(C, op) && WM_operator_check_ui_empty(op->type) == false) {
69     success = WM_operator_poll((bContext *)C, op->type);
70   }
71   return success;
72 }
73
74 static void hud_region_hide(ARegion *ar)
75 {
76   ar->flag |= RGN_FLAG_HIDDEN;
77   /* Avoids setting 'AREA_FLAG_REGION_SIZE_UPDATE'
78    * since other regions don't depend on this. */
79   BLI_rcti_init(&ar->winrct, 0, 0, 0, 0);
80 }
81
82 /** \} */
83
84 /* -------------------------------------------------------------------- */
85 /** \name Redo Panel
86  * \{ */
87
88 static bool hud_panel_operator_redo_poll(const bContext *C, PanelType *UNUSED(pt))
89 {
90   return last_redo_poll(C);
91 }
92
93 static void hud_panel_operator_redo_draw_header(const bContext *C, Panel *pa)
94 {
95   wmOperator *op = WM_operator_last_redo(C);
96   BLI_strncpy(pa->drawname, WM_operatortype_name(op->type, op->ptr), sizeof(pa->drawname));
97 }
98
99 static void hud_panel_operator_redo_draw(const bContext *C, Panel *pa)
100 {
101   wmOperator *op = WM_operator_last_redo(C);
102   if (op == NULL) {
103     return;
104   }
105   if (!WM_operator_check_ui_enabled(C, op->type->name)) {
106     uiLayoutSetEnabled(pa->layout, false);
107   }
108   uiLayout *col = uiLayoutColumn(pa->layout, false);
109   uiTemplateOperatorRedoProperties(col, C);
110 }
111
112 static void hud_panels_register(ARegionType *art, int space_type, int region_type)
113 {
114   PanelType *pt;
115
116   pt = MEM_callocN(sizeof(PanelType), __func__);
117   strcpy(pt->idname, "OPERATOR_PT_redo");
118   strcpy(pt->label, N_("Redo"));
119   strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
120   pt->draw_header = hud_panel_operator_redo_draw_header;
121   pt->draw = hud_panel_operator_redo_draw;
122   pt->poll = hud_panel_operator_redo_poll;
123   pt->space_type = space_type;
124   pt->region_type = region_type;
125   pt->flag |= PNL_DEFAULT_CLOSED;
126   BLI_addtail(&art->paneltypes, pt);
127 }
128
129 /** \} */
130
131 /* -------------------------------------------------------------------- */
132 /** \name Callbacks for Floating Region
133  * \{ */
134
135 struct HudRegionData {
136   short regionid;
137 };
138
139 static void hud_region_init(wmWindowManager *wm, ARegion *ar)
140 {
141   ED_region_panels_init(wm, ar);
142   UI_region_handlers_add(&ar->handlers);
143   ar->flag |= RGN_FLAG_TEMP_REGIONDATA;
144 }
145
146 static void hud_region_free(ARegion *ar)
147 {
148   MEM_SAFE_FREE(ar->regiondata);
149 }
150
151 static void hud_region_layout(const bContext *C, ARegion *ar)
152 {
153   bool ok = false;
154
155   {
156     struct HudRegionData *hrd = ar->regiondata;
157     if (hrd != NULL) {
158       ScrArea *sa = CTX_wm_area(C);
159       ARegion *ar_op = (hrd->regionid != -1) ? BKE_area_find_region_type(sa, hrd->regionid) : NULL;
160       ARegion *ar_prev = CTX_wm_region(C);
161       CTX_wm_region_set((bContext *)C, ar_op);
162       ok = last_redo_poll(C);
163       CTX_wm_region_set((bContext *)C, ar_prev);
164     }
165   }
166
167   if (!ok) {
168     ED_region_tag_redraw(ar);
169     hud_region_hide(ar);
170     return;
171   }
172
173   int size_y = ar->sizey;
174
175   ED_region_panels_layout(C, ar);
176
177   if (ar->panels.first && (ar->sizey != size_y)) {
178     int winx_new = UI_DPI_FAC * (ar->sizex + 0.5f);
179     int winy_new = UI_DPI_FAC * (ar->sizey + 0.5f);
180     View2D *v2d = &ar->v2d;
181
182     if (ar->flag & RGN_FLAG_SIZE_CLAMP_X) {
183       CLAMP_MAX(winx_new, ar->winx);
184     }
185     if (ar->flag & RGN_FLAG_SIZE_CLAMP_Y) {
186       CLAMP_MAX(winy_new, ar->winy);
187     }
188
189     ar->winx = winx_new;
190     ar->winy = winy_new;
191
192     ar->winrct.xmax = (ar->winrct.xmin + ar->winx) - 1;
193     ar->winrct.ymax = (ar->winrct.ymin + ar->winy) - 1;
194
195     UI_view2d_region_reinit(v2d, V2D_COMMONVIEW_PANELS_UI, ar->winx, ar->winy);
196
197     /* Weak, but needed to avoid glitches, especially with hi-dpi
198      * (where resizing the view glitches often).
199      * Fortunately this only happens occasionally. */
200     ED_region_panels_layout(C, ar);
201   }
202
203   /* restore view matrix */
204   UI_view2d_view_restore(C);
205 }
206
207 static void hud_region_draw(const bContext *C, ARegion *ar)
208 {
209   UI_view2d_view_ortho(&ar->v2d);
210   wmOrtho2_region_pixelspace(ar);
211   GPU_clear_color(0, 0, 0, 0.0f);
212   GPU_clear(GPU_COLOR_BIT);
213
214   if ((ar->flag & RGN_FLAG_HIDDEN) == 0) {
215     ui_draw_menu_back(NULL,
216                       NULL,
217                       &(rcti){
218                           .xmax = ar->winx,
219                           .ymax = ar->winy,
220                       });
221     ED_region_panels_draw(C, ar);
222   }
223 }
224
225 ARegionType *ED_area_type_hud(int space_type)
226 {
227   ARegionType *art = MEM_callocN(sizeof(ARegionType), __func__);
228   art->regionid = RGN_TYPE_HUD;
229   art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D;
230   art->layout = hud_region_layout;
231   art->draw = hud_region_draw;
232   art->init = hud_region_init;
233   art->free = hud_region_free;
234
235   /* We need to indicate a preferred size to avoid false `RGN_FLAG_TOO_SMALL`
236    * the first time the region is created. */
237   art->prefsizex = AREAMINX;
238   art->prefsizey = HEADERY;
239
240   hud_panels_register(art, space_type, art->regionid);
241
242   art->lock = 1; /* can become flag, see BKE_spacedata_draw_locks */
243   return art;
244 }
245
246 static ARegion *hud_region_add(ScrArea *sa)
247 {
248   ARegion *ar = MEM_callocN(sizeof(ARegion), "area region");
249   ARegion *ar_win = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
250   if (ar_win) {
251     BLI_insertlinkbefore(&sa->regionbase, ar_win, ar);
252   }
253   else {
254     BLI_addtail(&sa->regionbase, ar);
255   }
256   ar->regiontype = RGN_TYPE_HUD;
257   ar->alignment = RGN_ALIGN_FLOAT;
258   ar->overlap = true;
259   ar->flag |= RGN_FLAG_DYNAMIC_SIZE;
260
261   return ar;
262 }
263
264 void ED_area_type_hud_clear(wmWindowManager *wm, ScrArea *sa_keep)
265 {
266   for (wmWindow *win = wm->windows.first; win; win = win->next) {
267     bScreen *screen = WM_window_get_active_screen(win);
268     for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
269       if (sa != sa_keep) {
270         for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
271           if (ar->regiontype == RGN_TYPE_HUD) {
272             if ((ar->flag & RGN_FLAG_HIDDEN) == 0) {
273               hud_region_hide(ar);
274               ED_region_tag_redraw(ar);
275               ED_area_tag_redraw(sa);
276             }
277           }
278         }
279       }
280     }
281   }
282 }
283
284 void ED_area_type_hud_ensure(bContext *C, ScrArea *sa)
285 {
286   wmWindowManager *wm = CTX_wm_manager(C);
287   ED_area_type_hud_clear(wm, sa);
288
289   ARegionType *art = BKE_regiontype_from_id(sa->type, RGN_TYPE_HUD);
290   if (art == NULL) {
291     return;
292   }
293
294   ARegion *ar = BKE_area_find_region_type(sa, RGN_TYPE_HUD);
295
296   if (ar && (ar->flag & RGN_FLAG_HIDDEN_BY_USER)) {
297     /* The region is intentionally hidden by the user, don't show it. */
298     hud_region_hide(ar);
299     return;
300   }
301
302   bool init = false;
303   bool was_hidden = ar == NULL || ar->visible == false;
304   if (!last_redo_poll(C)) {
305     if (ar) {
306       ED_region_tag_redraw(ar);
307       hud_region_hide(ar);
308     }
309     return;
310   }
311
312   if (ar == NULL) {
313     init = true;
314     ar = hud_region_add(sa);
315     ar->type = art;
316   }
317
318   /* Let 'ED_area_update_region_sizes' do the work of placing the region.
319    * Otherwise we could set the 'ar->winrct' & 'ar->winx/winy' here. */
320   if (init) {
321     sa->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
322   }
323   else {
324     if (ar->flag & RGN_FLAG_HIDDEN) {
325       sa->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
326     }
327     ar->flag &= ~RGN_FLAG_HIDDEN;
328   }
329
330   {
331     ARegion *ar_op = CTX_wm_region(C);
332     BLI_assert((ar_op == NULL) || (ar_op->regiontype != RGN_TYPE_HUD));
333     struct HudRegionData *hrd = ar->regiondata;
334     if (hrd == NULL) {
335       hrd = MEM_callocN(sizeof(*hrd), __func__);
336       ar->regiondata = hrd;
337     }
338     if (ar_op) {
339       hrd->regionid = ar_op->regiontype;
340     }
341     else {
342       hrd->regionid = -1;
343     }
344   }
345
346   if (init) {
347     /* This is needed or 'winrct' will be invalid. */
348     wmWindow *win = CTX_wm_window(C);
349     ED_area_update_region_sizes(wm, win, sa);
350   }
351
352   ED_region_init(ar);
353   ED_region_tag_redraw(ar);
354
355   /* Reset zoom level (not well supported). */
356   ar->v2d.cur = ar->v2d.tot = (rctf){
357       .xmax = ar->winx,
358       .ymax = ar->winy,
359   };
360   ar->v2d.minzoom = 1.0f;
361   ar->v2d.maxzoom = 1.0f;
362
363   ar->visible = !(ar->flag & RGN_FLAG_HIDDEN);
364
365   /* We shouldn't need to do this every time :S */
366   /* XXX, this is evil! - it also makes the menu show on first draw. :( */
367   if (ar->visible) {
368     ARegion *ar_prev = CTX_wm_region(C);
369     CTX_wm_region_set((bContext *)C, ar);
370     hud_region_layout(C, ar);
371     if (was_hidden) {
372       ar->winx = ar->v2d.winx;
373       ar->winy = ar->v2d.winy;
374       ar->v2d.cur = ar->v2d.tot = (rctf){
375           .xmax = ar->winx,
376           .ymax = ar->winy,
377       };
378     }
379     CTX_wm_region_set((bContext *)C, ar_prev);
380   }
381
382   ar->visible = !((ar->flag & RGN_FLAG_HIDDEN) || (ar->flag & RGN_FLAG_TOO_SMALL));
383 }
384
385 /** \} */