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(®ion->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(®ion->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 = ®ion->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(®ion->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(®ion_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