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 <math.h>
25 #include <string.h>
26
27 #include "MEM_guardedalloc.h"
28
29 #include "BLI_blenlib.h"
30 #include "BLI_dlrbTree.h"
31 #include "BLI_math.h"
32 #include "BLI_utildefines.h"
33
34 #include "BLT_translation.h"
35
36 #include "DNA_anim_types.h"
37 #include "DNA_armature_types.h"
38 #include "DNA_curve_types.h"
39 #include "DNA_lattice_types.h"
40 #include "DNA_mask_types.h"
41 #include "DNA_mesh_types.h"
42 #include "DNA_meta_types.h"
43 #include "DNA_node_types.h"
44 #include "DNA_object_types.h"
45 #include "DNA_scene_types.h"
46 #include "DNA_userdef_types.h"
47 #include "DNA_workspace_types.h"
48
49 #include "BKE_context.h"
50 #include "BKE_editmesh.h"
51 #include "BKE_fcurve.h"
52 #include "BKE_global.h"
53 #include "BKE_icons.h"
54 #include "BKE_lib_id.h"
55 #include "BKE_main.h"
56 #include "BKE_mask.h"
57 #include "BKE_object.h"
58 #include "BKE_report.h"
59 #include "BKE_scene.h"
60 #include "BKE_screen.h"
61 #include "BKE_sound.h"
62 #include "BKE_workspace.h"
63
64 #include "WM_api.h"
65 #include "WM_types.h"
66
67 #include "DEG_depsgraph.h"
68 #include "DEG_depsgraph_query.h"
69
70 #include "ED_anim_api.h"
71 #include "ED_armature.h"
72 #include "ED_clip.h"
73 #include "ED_image.h"
74 #include "ED_keyframes_draw.h"
75 #include "ED_mesh.h"
76 #include "ED_object.h"
77 #include "ED_screen.h"
78 #include "ED_screen_types.h"
79 #include "ED_sequencer.h"
80 #include "ED_undo.h"
81 #include "ED_util.h"
82 #include "ED_view3d.h"
83
84 #include "RNA_access.h"
85 #include "RNA_define.h"
86 #include "RNA_enum_types.h"
87
88 #include "UI_interface.h"
89 #include "UI_resources.h"
90 #include "UI_view2d.h"
91
92 #include "GPU_capabilities.h"
93
94 #include "screen_intern.h" /* own module include */
95
96 #define KM_MODAL_CANCEL 1
97 #define KM_MODAL_APPLY 2
98 #define KM_MODAL_SNAP_ON 3
99 #define KM_MODAL_SNAP_OFF 4
100
101 /* -------------------------------------------------------------------- */
102 /** \name Public Poll API
103 * \{ */
104
ED_operator_regionactive(bContext * C)105 bool ED_operator_regionactive(bContext *C)
106 {
107 if (CTX_wm_window(C) == NULL) {
108 return false;
109 }
110 if (CTX_wm_screen(C) == NULL) {
111 return false;
112 }
113 if (CTX_wm_region(C) == NULL) {
114 return false;
115 }
116 return true;
117 }
118
ED_operator_areaactive(bContext * C)119 bool ED_operator_areaactive(bContext *C)
120 {
121 if (CTX_wm_window(C) == NULL) {
122 return false;
123 }
124 if (CTX_wm_screen(C) == NULL) {
125 return false;
126 }
127 if (CTX_wm_area(C) == NULL) {
128 return false;
129 }
130 return true;
131 }
132
ED_operator_screenactive(bContext * C)133 bool ED_operator_screenactive(bContext *C)
134 {
135 if (CTX_wm_window(C) == NULL) {
136 return false;
137 }
138 if (CTX_wm_screen(C) == NULL) {
139 return false;
140 }
141 return true;
142 }
143
144 /* XXX added this to prevent anim state to change during renders */
ED_operator_screenactive_norender(bContext * C)145 static bool ED_operator_screenactive_norender(bContext *C)
146 {
147 if (G.is_rendering) {
148 return false;
149 }
150 if (CTX_wm_window(C) == NULL) {
151 return false;
152 }
153 if (CTX_wm_screen(C) == NULL) {
154 return false;
155 }
156 return true;
157 }
158
159 /* when mouse is over area-edge */
ED_operator_screen_mainwinactive(bContext * C)160 bool ED_operator_screen_mainwinactive(bContext *C)
161 {
162 if (CTX_wm_window(C) == NULL) {
163 return false;
164 }
165 bScreen *screen = CTX_wm_screen(C);
166 if (screen == NULL) {
167 return false;
168 }
169 if (screen->active_region != NULL) {
170 return false;
171 }
172 return true;
173 }
174
ED_operator_scene(bContext * C)175 bool ED_operator_scene(bContext *C)
176 {
177 Scene *scene = CTX_data_scene(C);
178 if (scene) {
179 return true;
180 }
181 return false;
182 }
183
ED_operator_scene_editable(bContext * C)184 bool ED_operator_scene_editable(bContext *C)
185 {
186 Scene *scene = CTX_data_scene(C);
187 if (scene && !ID_IS_LINKED(scene)) {
188 return true;
189 }
190 return false;
191 }
192
ED_operator_objectmode(bContext * C)193 bool ED_operator_objectmode(bContext *C)
194 {
195 Scene *scene = CTX_data_scene(C);
196 Object *obact = CTX_data_active_object(C);
197
198 if (scene == NULL || ID_IS_LINKED(scene)) {
199 return false;
200 }
201 if (CTX_data_edit_object(C)) {
202 return false;
203 }
204
205 /* add a check for ob->mode too? */
206 if (obact && (obact->mode != OB_MODE_OBJECT)) {
207 return false;
208 }
209
210 return true;
211 }
212
ed_spacetype_test(bContext * C,int type)213 static bool ed_spacetype_test(bContext *C, int type)
214 {
215 if (ED_operator_areaactive(C)) {
216 SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
217 return sl && (sl->spacetype == type);
218 }
219 return false;
220 }
221
ED_operator_view3d_active(bContext * C)222 bool ED_operator_view3d_active(bContext *C)
223 {
224 return ed_spacetype_test(C, SPACE_VIEW3D);
225 }
226
ED_operator_region_view3d_active(bContext * C)227 bool ED_operator_region_view3d_active(bContext *C)
228 {
229 if (CTX_wm_region_view3d(C)) {
230 return true;
231 }
232
233 CTX_wm_operator_poll_msg_set(C, "expected a view3d region");
234 return false;
235 }
236
237 /* generic for any view2d which uses anim_ops */
ED_operator_animview_active(bContext * C)238 bool ED_operator_animview_active(bContext *C)
239 {
240 if (ED_operator_areaactive(C)) {
241 SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
242 if (sl && (ELEM(sl->spacetype, SPACE_SEQ, SPACE_ACTION, SPACE_NLA, SPACE_GRAPH))) {
243 return true;
244 }
245 }
246
247 CTX_wm_operator_poll_msg_set(C, "expected a timeline/animation area to be active");
248 return false;
249 }
250
ED_operator_outliner_active(bContext * C)251 bool ED_operator_outliner_active(bContext *C)
252 {
253 return ed_spacetype_test(C, SPACE_OUTLINER);
254 }
255
ED_operator_outliner_active_no_editobject(bContext * C)256 bool ED_operator_outliner_active_no_editobject(bContext *C)
257 {
258 if (ed_spacetype_test(C, SPACE_OUTLINER)) {
259 Object *ob = ED_object_active_context(C);
260 Object *obedit = CTX_data_edit_object(C);
261 if (ob && ob == obedit) {
262 return false;
263 }
264 return true;
265 }
266 return false;
267 }
268
ED_operator_file_active(bContext * C)269 bool ED_operator_file_active(bContext *C)
270 {
271 return ed_spacetype_test(C, SPACE_FILE);
272 }
273
ED_operator_action_active(bContext * C)274 bool ED_operator_action_active(bContext *C)
275 {
276 return ed_spacetype_test(C, SPACE_ACTION);
277 }
278
ED_operator_buttons_active(bContext * C)279 bool ED_operator_buttons_active(bContext *C)
280 {
281 return ed_spacetype_test(C, SPACE_PROPERTIES);
282 }
283
ED_operator_node_active(bContext * C)284 bool ED_operator_node_active(bContext *C)
285 {
286 SpaceNode *snode = CTX_wm_space_node(C);
287
288 if (snode && snode->edittree) {
289 return true;
290 }
291
292 return false;
293 }
294
ED_operator_node_editable(bContext * C)295 bool ED_operator_node_editable(bContext *C)
296 {
297 SpaceNode *snode = CTX_wm_space_node(C);
298
299 if (snode && snode->edittree && !ID_IS_LINKED(snode->edittree)) {
300 return true;
301 }
302
303 return false;
304 }
305
ED_operator_graphedit_active(bContext * C)306 bool ED_operator_graphedit_active(bContext *C)
307 {
308 return ed_spacetype_test(C, SPACE_GRAPH);
309 }
310
ED_operator_sequencer_active(bContext * C)311 bool ED_operator_sequencer_active(bContext *C)
312 {
313 return ed_spacetype_test(C, SPACE_SEQ);
314 }
315
ED_operator_sequencer_active_editable(bContext * C)316 bool ED_operator_sequencer_active_editable(bContext *C)
317 {
318 return ed_spacetype_test(C, SPACE_SEQ) && ED_operator_scene_editable(C);
319 }
320
ED_operator_image_active(bContext * C)321 bool ED_operator_image_active(bContext *C)
322 {
323 return ed_spacetype_test(C, SPACE_IMAGE);
324 }
325
ED_operator_nla_active(bContext * C)326 bool ED_operator_nla_active(bContext *C)
327 {
328 return ed_spacetype_test(C, SPACE_NLA);
329 }
330
ED_operator_info_active(bContext * C)331 bool ED_operator_info_active(bContext *C)
332 {
333 return ed_spacetype_test(C, SPACE_INFO);
334 }
335
ED_operator_console_active(bContext * C)336 bool ED_operator_console_active(bContext *C)
337 {
338 return ed_spacetype_test(C, SPACE_CONSOLE);
339 }
340
ed_object_hidden(const Object * ob)341 static bool ed_object_hidden(const Object *ob)
342 {
343 /* if hidden but in edit mode, we still display, can happen with animation */
344 return ((ob->restrictflag & OB_RESTRICT_VIEWPORT) && !(ob->mode & OB_MODE_EDIT));
345 }
346
ED_operator_object_active(bContext * C)347 bool ED_operator_object_active(bContext *C)
348 {
349 Object *ob = ED_object_active_context(C);
350 return ((ob != NULL) && !ed_object_hidden(ob));
351 }
352
ED_operator_object_active_editable_ex(bContext * UNUSED (C),const Object * ob)353 bool ED_operator_object_active_editable_ex(bContext *UNUSED(C), const Object *ob)
354 {
355 return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob));
356 }
357
ED_operator_object_active_editable(bContext * C)358 bool ED_operator_object_active_editable(bContext *C)
359 {
360 Object *ob = ED_object_active_context(C);
361 return ED_operator_object_active_editable_ex(C, ob);
362 }
363
364 /** Object must be editable and fully local (i.e. not an override). */
ED_operator_object_active_local_editable_ex(bContext * C,const Object * ob)365 bool ED_operator_object_active_local_editable_ex(bContext *C, const Object *ob)
366 {
367 return ED_operator_object_active_editable_ex(C, ob) && !ID_IS_OVERRIDE_LIBRARY(ob);
368 }
369
ED_operator_object_active_local_editable(bContext * C)370 bool ED_operator_object_active_local_editable(bContext *C)
371 {
372 Object *ob = ED_object_active_context(C);
373 return ED_operator_object_active_editable_ex(C, ob) && !ID_IS_OVERRIDE_LIBRARY(ob);
374 }
375
ED_operator_object_active_editable_mesh(bContext * C)376 bool ED_operator_object_active_editable_mesh(bContext *C)
377 {
378 Object *ob = ED_object_active_context(C);
379 return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob) && (ob->type == OB_MESH) &&
380 !ID_IS_LINKED(ob->data) && !ID_IS_OVERRIDE_LIBRARY(ob->data));
381 }
382
ED_operator_object_active_editable_font(bContext * C)383 bool ED_operator_object_active_editable_font(bContext *C)
384 {
385 Object *ob = ED_object_active_context(C);
386 return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob) && (ob->type == OB_FONT) &&
387 !ID_IS_LINKED(ob->data) && !ID_IS_OVERRIDE_LIBRARY(ob->data));
388 }
389
ED_operator_editable_mesh(bContext * C)390 bool ED_operator_editable_mesh(bContext *C)
391 {
392 Mesh *mesh = ED_mesh_context(C);
393 return (mesh != NULL) && !ID_IS_LINKED(mesh) && !ID_IS_OVERRIDE_LIBRARY(mesh);
394 }
395
ED_operator_editmesh(bContext * C)396 bool ED_operator_editmesh(bContext *C)
397 {
398 Object *obedit = CTX_data_edit_object(C);
399 if (obedit && obedit->type == OB_MESH) {
400 return NULL != BKE_editmesh_from_object(obedit);
401 }
402 return false;
403 }
404
ED_operator_editmesh_view3d(bContext * C)405 bool ED_operator_editmesh_view3d(bContext *C)
406 {
407 return ED_operator_editmesh(C) && ED_operator_view3d_active(C);
408 }
409
ED_operator_editmesh_region_view3d(bContext * C)410 bool ED_operator_editmesh_region_view3d(bContext *C)
411 {
412 if (ED_operator_editmesh(C) && CTX_wm_region_view3d(C)) {
413 return true;
414 }
415
416 CTX_wm_operator_poll_msg_set(C, "expected a view3d region & editmesh");
417 return false;
418 }
419
ED_operator_editmesh_auto_smooth(bContext * C)420 bool ED_operator_editmesh_auto_smooth(bContext *C)
421 {
422 Object *obedit = CTX_data_edit_object(C);
423 if (obedit && obedit->type == OB_MESH && (((Mesh *)(obedit->data))->flag & ME_AUTOSMOOTH)) {
424 return NULL != BKE_editmesh_from_object(obedit);
425 }
426 return false;
427 }
428
ED_operator_editarmature(bContext * C)429 bool ED_operator_editarmature(bContext *C)
430 {
431 Object *obedit = CTX_data_edit_object(C);
432 if (obedit && obedit->type == OB_ARMATURE) {
433 return NULL != ((bArmature *)obedit->data)->edbo;
434 }
435 return false;
436 }
437
438 /**
439 * \brief check for pose mode (no mixed modes)
440 *
441 * We want to enable most pose operations in weight paint mode,
442 * when it comes to transforming bones, but managing bones layers/groups
443 * can be left for pose mode only. (not weight paint mode)
444 */
ED_operator_posemode_exclusive(bContext * C)445 bool ED_operator_posemode_exclusive(bContext *C)
446 {
447 Object *obact = CTX_data_active_object(C);
448
449 if (obact && !(obact->mode & OB_MODE_EDIT)) {
450 Object *obpose = BKE_object_pose_armature_get(obact);
451 if (obpose != NULL) {
452 if (obact == obpose) {
453 return true;
454 }
455 }
456 }
457
458 return false;
459 }
460
461 /* allows for pinned pose objects to be used in the object buttons
462 * and the non-active pose object to be used in the 3D view */
ED_operator_posemode_context(bContext * C)463 bool ED_operator_posemode_context(bContext *C)
464 {
465 Object *obpose = ED_pose_object_from_context(C);
466
467 if (obpose && !(obpose->mode & OB_MODE_EDIT)) {
468 if (BKE_object_pose_context_check(obpose)) {
469 return true;
470 }
471 }
472
473 return false;
474 }
475
ED_operator_posemode(bContext * C)476 bool ED_operator_posemode(bContext *C)
477 {
478 Object *obact = CTX_data_active_object(C);
479
480 if (obact && !(obact->mode & OB_MODE_EDIT)) {
481 Object *obpose = BKE_object_pose_armature_get(obact);
482 if (obpose != NULL) {
483 if ((obact == obpose) || (obact->mode & OB_MODE_ALL_WEIGHT_PAINT)) {
484 return true;
485 }
486 }
487 }
488
489 return false;
490 }
491
ED_operator_posemode_local(bContext * C)492 bool ED_operator_posemode_local(bContext *C)
493 {
494 if (ED_operator_posemode(C)) {
495 Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
496 bArmature *arm = ob->data;
497 return !(ID_IS_LINKED(&ob->id) || ID_IS_LINKED(&arm->id));
498 }
499 return false;
500 }
501
502 /* wrapper for ED_space_image_show_uvedit */
ED_operator_uvedit(bContext * C)503 bool ED_operator_uvedit(bContext *C)
504 {
505 SpaceImage *sima = CTX_wm_space_image(C);
506 Object *obedit = CTX_data_edit_object(C);
507 return ED_space_image_show_uvedit(sima, obedit);
508 }
509
ED_operator_uvedit_space_image(bContext * C)510 bool ED_operator_uvedit_space_image(bContext *C)
511 {
512 SpaceImage *sima = CTX_wm_space_image(C);
513 Object *obedit = CTX_data_edit_object(C);
514 return sima && ED_space_image_show_uvedit(sima, obedit);
515 }
516
ED_operator_uvmap(bContext * C)517 bool ED_operator_uvmap(bContext *C)
518 {
519 Object *obedit = CTX_data_edit_object(C);
520 BMEditMesh *em = NULL;
521
522 if (obedit && obedit->type == OB_MESH) {
523 em = BKE_editmesh_from_object(obedit);
524 }
525
526 if (em && (em->bm->totface)) {
527 return true;
528 }
529
530 return false;
531 }
532
ED_operator_editsurfcurve(bContext * C)533 bool ED_operator_editsurfcurve(bContext *C)
534 {
535 Object *obedit = CTX_data_edit_object(C);
536 if (obedit && ELEM(obedit->type, OB_CURVE, OB_SURF)) {
537 return NULL != ((Curve *)obedit->data)->editnurb;
538 }
539 return false;
540 }
541
ED_operator_editsurfcurve_region_view3d(bContext * C)542 bool ED_operator_editsurfcurve_region_view3d(bContext *C)
543 {
544 if (ED_operator_editsurfcurve(C) && CTX_wm_region_view3d(C)) {
545 return true;
546 }
547
548 CTX_wm_operator_poll_msg_set(C, "expected a view3d region & editcurve");
549 return false;
550 }
551
ED_operator_editcurve(bContext * C)552 bool ED_operator_editcurve(bContext *C)
553 {
554 Object *obedit = CTX_data_edit_object(C);
555 if (obedit && obedit->type == OB_CURVE) {
556 return NULL != ((Curve *)obedit->data)->editnurb;
557 }
558 return false;
559 }
560
ED_operator_editcurve_3d(bContext * C)561 bool ED_operator_editcurve_3d(bContext *C)
562 {
563 Object *obedit = CTX_data_edit_object(C);
564 if (obedit && obedit->type == OB_CURVE) {
565 Curve *cu = (Curve *)obedit->data;
566
567 return (cu->flag & CU_3D) && (NULL != cu->editnurb);
568 }
569 return false;
570 }
571
ED_operator_editsurf(bContext * C)572 bool ED_operator_editsurf(bContext *C)
573 {
574 Object *obedit = CTX_data_edit_object(C);
575 if (obedit && obedit->type == OB_SURF) {
576 return NULL != ((Curve *)obedit->data)->editnurb;
577 }
578 return false;
579 }
580
ED_operator_editfont(bContext * C)581 bool ED_operator_editfont(bContext *C)
582 {
583 Object *obedit = CTX_data_edit_object(C);
584 if (obedit && obedit->type == OB_FONT) {
585 return NULL != ((Curve *)obedit->data)->editfont;
586 }
587 return false;
588 }
589
ED_operator_editlattice(bContext * C)590 bool ED_operator_editlattice(bContext *C)
591 {
592 Object *obedit = CTX_data_edit_object(C);
593 if (obedit && obedit->type == OB_LATTICE) {
594 return NULL != ((Lattice *)obedit->data)->editlatt;
595 }
596 return false;
597 }
598
ED_operator_editmball(bContext * C)599 bool ED_operator_editmball(bContext *C)
600 {
601 Object *obedit = CTX_data_edit_object(C);
602 if (obedit && obedit->type == OB_MBALL) {
603 return NULL != ((MetaBall *)obedit->data)->editelems;
604 }
605 return false;
606 }
607
ED_operator_mask(bContext * C)608 bool ED_operator_mask(bContext *C)
609 {
610 ScrArea *area = CTX_wm_area(C);
611 if (area && area->spacedata.first) {
612 switch (area->spacetype) {
613 case SPACE_CLIP: {
614 SpaceClip *screen = area->spacedata.first;
615 return ED_space_clip_check_show_maskedit(screen);
616 }
617 case SPACE_SEQ: {
618 SpaceSeq *sseq = area->spacedata.first;
619 Scene *scene = CTX_data_scene(C);
620 return ED_space_sequencer_check_show_maskedit(sseq, scene);
621 }
622 case SPACE_IMAGE: {
623 SpaceImage *sima = area->spacedata.first;
624 ViewLayer *view_layer = CTX_data_view_layer(C);
625 Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
626 return ED_space_image_check_show_maskedit(sima, obedit);
627 }
628 }
629 }
630
631 return false;
632 }
633
ED_operator_camera(bContext * C)634 bool ED_operator_camera(bContext *C)
635 {
636 struct Camera *cam = CTX_data_pointer_get_type(C, "camera", &RNA_Camera).data;
637 return (cam != NULL);
638 }
639
640 /** \} */
641
642 /* -------------------------------------------------------------------- */
643 /** \name Internal Screen Utilities
644 * \{ */
645
screen_active_editable(bContext * C)646 static bool screen_active_editable(bContext *C)
647 {
648 if (ED_operator_screenactive(C)) {
649 /* no full window splitting allowed */
650 if (CTX_wm_screen(C)->state != SCREENNORMAL) {
651 return false;
652 }
653 return true;
654 }
655 return false;
656 }
657
658 /** \} */
659
660 /* -------------------------------------------------------------------- */
661 /** \name Action Zone Operator
662 * \{ */
663
664 /* operator state vars used:
665 * none
666 *
667 * functions:
668 *
669 * apply() set action-zone event
670 *
671 * exit() free customdata
672 *
673 * callbacks:
674 *
675 * exec() never used
676 *
677 * invoke() check if in zone
678 * add customdata, put mouseco and area in it
679 * add modal handler
680 *
681 * modal() accept modal events while doing it
682 * call apply() with gesture info, active window, nonactive window
683 * call exit() and remove handler when LMB confirm
684 */
685
686 typedef struct sActionzoneData {
687 ScrArea *sa1, *sa2;
688 AZone *az;
689 int x, y, gesture_dir, modifier;
690 } sActionzoneData;
691
692 /* quick poll to save operators to be created and handled */
actionzone_area_poll(bContext * C)693 static bool actionzone_area_poll(bContext *C)
694 {
695 wmWindow *win = CTX_wm_window(C);
696 bScreen *screen = WM_window_get_active_screen(win);
697
698 if (screen && win && win->eventstate) {
699 const int *xy = &win->eventstate->x;
700
701 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
702 LISTBASE_FOREACH (AZone *, az, &area->actionzones) {
703 if (BLI_rcti_isect_pt_v(&az->rect, xy)) {
704 return true;
705 }
706 }
707 }
708 }
709 return false;
710 }
711
712 /* the debug drawing of the click_rect is in area_draw_azone_fullscreen, keep both in sync */
fullscreen_click_rcti_init(rcti * rect,const short UNUSED (x1),const short UNUSED (y1),const short x2,const short y2)713 static void fullscreen_click_rcti_init(
714 rcti *rect, const short UNUSED(x1), const short UNUSED(y1), const short x2, const short y2)
715 {
716 BLI_rcti_init(rect, x2 - U.widget_unit, x2, y2 - U.widget_unit, y2);
717 }
718
azone_clipped_rect_calc(const AZone * az,rcti * r_rect_clip)719 static bool azone_clipped_rect_calc(const AZone *az, rcti *r_rect_clip)
720 {
721 const ARegion *region = az->region;
722 *r_rect_clip = az->rect;
723 if (az->type == AZONE_REGION) {
724 if (region->overlap && (region->v2d.keeptot != V2D_KEEPTOT_STRICT) &&
725 /* Only when this isn't hidden (where it's displayed as an button that expands). */
726 ((az->region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) == 0)) {
727 /* A floating region to be resized, clip by the visible region. */
728 switch (az->edge) {
729 case AE_TOP_TO_BOTTOMRIGHT:
730 case AE_BOTTOM_TO_TOPLEFT: {
731 r_rect_clip->xmin = max_ii(
732 r_rect_clip->xmin,
733 (region->winrct.xmin +
734 UI_view2d_view_to_region_x(®ion->v2d, region->v2d.tot.xmin)) -
735 UI_REGION_OVERLAP_MARGIN);
736 r_rect_clip->xmax = min_ii(
737 r_rect_clip->xmax,
738 (region->winrct.xmin +
739 UI_view2d_view_to_region_x(®ion->v2d, region->v2d.tot.xmax)) +
740 UI_REGION_OVERLAP_MARGIN);
741 return true;
742 }
743 case AE_LEFT_TO_TOPRIGHT:
744 case AE_RIGHT_TO_TOPLEFT: {
745 r_rect_clip->ymin = max_ii(
746 r_rect_clip->ymin,
747 (region->winrct.ymin +
748 UI_view2d_view_to_region_y(®ion->v2d, region->v2d.tot.ymin)) -
749 UI_REGION_OVERLAP_MARGIN);
750 r_rect_clip->ymax = min_ii(
751 r_rect_clip->ymax,
752 (region->winrct.ymin +
753 UI_view2d_view_to_region_y(®ion->v2d, region->v2d.tot.ymax)) +
754 UI_REGION_OVERLAP_MARGIN);
755 return true;
756 }
757 }
758 }
759 }
760 return false;
761 }
762
area_actionzone_refresh_xy(ScrArea * area,const int xy[2],const bool test_only)763 static AZone *area_actionzone_refresh_xy(ScrArea *area, const int xy[2], const bool test_only)
764 {
765 AZone *az = NULL;
766
767 for (az = area->actionzones.first; az; az = az->next) {
768 rcti az_rect_clip;
769 if (BLI_rcti_isect_pt_v(&az->rect, xy) &&
770 /* Check clipping if this is clipped */
771 (!azone_clipped_rect_calc(az, &az_rect_clip) || BLI_rcti_isect_pt_v(&az_rect_clip, xy))) {
772
773 if (az->type == AZONE_AREA) {
774 break;
775 }
776 if (az->type == AZONE_REGION) {
777 break;
778 }
779 if (az->type == AZONE_FULLSCREEN) {
780 rcti click_rect;
781 fullscreen_click_rcti_init(&click_rect, az->x1, az->y1, az->x2, az->y2);
782 const bool click_isect = BLI_rcti_isect_pt_v(&click_rect, xy);
783
784 if (test_only) {
785 if (click_isect) {
786 break;
787 }
788 }
789 else {
790 if (click_isect) {
791 az->alpha = 1.0f;
792 }
793 else {
794 const int mouse_sq = square_i(xy[0] - az->x2) + square_i(xy[1] - az->y2);
795 const int spot_sq = square_i(AZONESPOTW);
796 const int fadein_sq = square_i(AZONEFADEIN);
797 const int fadeout_sq = square_i(AZONEFADEOUT);
798
799 if (mouse_sq < spot_sq) {
800 az->alpha = 1.0f;
801 }
802 else if (mouse_sq < fadein_sq) {
803 az->alpha = 1.0f;
804 }
805 else if (mouse_sq < fadeout_sq) {
806 az->alpha = 1.0f -
807 ((float)(mouse_sq - fadein_sq)) / ((float)(fadeout_sq - fadein_sq));
808 }
809 else {
810 az->alpha = 0.0f;
811 }
812
813 /* fade in/out but no click */
814 az = NULL;
815 }
816
817 /* XXX force redraw to show/hide the action zone */
818 ED_area_tag_redraw(area);
819 break;
820 }
821 }
822 else if (az->type == AZONE_REGION_SCROLL) {
823 ARegion *region = az->region;
824 View2D *v2d = ®ion->v2d;
825 int scroll_flag = 0;
826 const int isect_value = UI_view2d_mouse_in_scrollers_ex(
827 region, v2d, xy[0], xy[1], &scroll_flag);
828
829 /* Check if we even have scroll bars. */
830 if (((az->direction == AZ_SCROLL_HOR) && !(scroll_flag & V2D_SCROLL_HORIZONTAL)) ||
831 ((az->direction == AZ_SCROLL_VERT) && !(scroll_flag & V2D_SCROLL_VERTICAL))) {
832 /* no scrollbars, do nothing. */
833 }
834 else if (test_only) {
835 if (isect_value != 0) {
836 break;
837 }
838 }
839 else {
840 bool redraw = false;
841
842 if (isect_value == 'h') {
843 if (az->direction == AZ_SCROLL_HOR) {
844 az->alpha = 1.0f;
845 v2d->alpha_hor = 255;
846 redraw = true;
847 }
848 }
849 else if (isect_value == 'v') {
850 if (az->direction == AZ_SCROLL_VERT) {
851 az->alpha = 1.0f;
852 v2d->alpha_vert = 255;
853 redraw = true;
854 }
855 }
856 else {
857 const int local_xy[2] = {xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin};
858 float dist_fac = 0.0f, alpha = 0.0f;
859
860 if (az->direction == AZ_SCROLL_HOR) {
861 dist_fac = BLI_rcti_length_y(&v2d->hor, local_xy[1]) / AZONEFADEIN;
862 CLAMP(dist_fac, 0.0f, 1.0f);
863 alpha = 1.0f - dist_fac;
864
865 v2d->alpha_hor = alpha * 255;
866 }
867 else if (az->direction == AZ_SCROLL_VERT) {
868 dist_fac = BLI_rcti_length_x(&v2d->vert, local_xy[0]) / AZONEFADEIN;
869 CLAMP(dist_fac, 0.0f, 1.0f);
870 alpha = 1.0f - dist_fac;
871
872 v2d->alpha_vert = alpha * 255;
873 }
874 az->alpha = alpha;
875 redraw = true;
876 }
877
878 if (redraw) {
879 ED_region_tag_redraw_no_rebuild(region);
880 }
881 /* Don't return! */
882 }
883 }
884 }
885 else if (!test_only && !IS_EQF(az->alpha, 0.0f)) {
886 if (az->type == AZONE_FULLSCREEN) {
887 az->alpha = 0.0f;
888 area->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
889 ED_area_tag_redraw_no_rebuild(area);
890 }
891 else if (az->type == AZONE_REGION_SCROLL) {
892 if (az->direction == AZ_SCROLL_VERT) {
893 az->alpha = az->region->v2d.alpha_vert = 0;
894 area->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
895 ED_region_tag_redraw_no_rebuild(az->region);
896 }
897 else if (az->direction == AZ_SCROLL_HOR) {
898 az->alpha = az->region->v2d.alpha_hor = 0;
899 area->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
900 ED_region_tag_redraw_no_rebuild(az->region);
901 }
902 else {
903 BLI_assert(false);
904 }
905 }
906 }
907 }
908
909 return az;
910 }
911
912 /* Finds an action-zone by position in entire screen so azones can overlap. */
screen_actionzone_find_xy(bScreen * screen,const int xy[2])913 static AZone *screen_actionzone_find_xy(bScreen *screen, const int xy[2])
914 {
915 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
916 AZone *az = area_actionzone_refresh_xy(area, xy, true);
917 if (az != NULL) {
918 return az;
919 }
920 }
921 return NULL;
922 }
923
924 /* Returns the area that the azone belongs to */
screen_actionzone_area(bScreen * screen,const AZone * az)925 static ScrArea *screen_actionzone_area(bScreen *screen, const AZone *az)
926 {
927 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
928 LISTBASE_FOREACH (AZone *, zone, &area->actionzones) {
929 if (zone == az) {
930 return area;
931 }
932 }
933 }
934 return NULL;
935 }
936
ED_area_actionzone_find_xy(ScrArea * area,const int xy[2])937 AZone *ED_area_actionzone_find_xy(ScrArea *area, const int xy[2])
938 {
939 return area_actionzone_refresh_xy(area, xy, true);
940 }
941
ED_area_azones_update(ScrArea * area,const int xy[2])942 AZone *ED_area_azones_update(ScrArea *area, const int xy[2])
943 {
944 return area_actionzone_refresh_xy(area, xy, false);
945 }
946
actionzone_exit(wmOperator * op)947 static void actionzone_exit(wmOperator *op)
948 {
949 if (op->customdata) {
950 MEM_freeN(op->customdata);
951 }
952 op->customdata = NULL;
953
954 G.moving &= ~G_TRANSFORM_WM;
955 }
956
957 /* send EVT_ACTIONZONE event */
actionzone_apply(bContext * C,wmOperator * op,int type)958 static void actionzone_apply(bContext *C, wmOperator *op, int type)
959 {
960 wmWindow *win = CTX_wm_window(C);
961 sActionzoneData *sad = op->customdata;
962
963 sad->modifier = RNA_int_get(op->ptr, "modifier");
964
965 wmEvent event;
966 wm_event_init_from_window(win, &event);
967
968 if (type == AZONE_AREA) {
969 event.type = EVT_ACTIONZONE_AREA;
970 }
971 else if (type == AZONE_FULLSCREEN) {
972 event.type = EVT_ACTIONZONE_FULLSCREEN;
973 }
974 else {
975 event.type = EVT_ACTIONZONE_REGION;
976 }
977
978 event.val = KM_NOTHING;
979 event.is_repeat = false;
980 event.customdata = op->customdata;
981 event.customdatafree = true;
982 op->customdata = NULL;
983
984 wm_event_add(win, &event);
985 }
986
actionzone_invoke(bContext * C,wmOperator * op,const wmEvent * event)987 static int actionzone_invoke(bContext *C, wmOperator *op, const wmEvent *event)
988 {
989 bScreen *screen = CTX_wm_screen(C);
990 AZone *az = screen_actionzone_find_xy(screen, &event->x);
991
992 /* Quick escape - Scroll azones only hide/unhide the scroll-bars,
993 * they have their own handling. */
994 if (az == NULL || ELEM(az->type, AZONE_REGION_SCROLL)) {
995 return OPERATOR_PASS_THROUGH;
996 }
997
998 /* ok we do the action-zone */
999 sActionzoneData *sad = op->customdata = MEM_callocN(sizeof(sActionzoneData), "sActionzoneData");
1000 sad->sa1 = screen_actionzone_area(screen, az);
1001 sad->az = az;
1002 sad->x = event->x;
1003 sad->y = event->y;
1004
1005 /* region azone directly reacts on mouse clicks */
1006 if (ELEM(sad->az->type, AZONE_REGION, AZONE_FULLSCREEN)) {
1007 actionzone_apply(C, op, sad->az->type);
1008 actionzone_exit(op);
1009 return OPERATOR_FINISHED;
1010 }
1011
1012 BLI_assert(ELEM(sad->az->type, AZONE_AREA, AZONE_REGION_SCROLL));
1013
1014 /* add modal handler */
1015 G.moving |= G_TRANSFORM_WM;
1016 WM_event_add_modal_handler(C, op);
1017 return OPERATOR_RUNNING_MODAL;
1018 }
1019
actionzone_modal(bContext * C,wmOperator * op,const wmEvent * event)1020 static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event)
1021 {
1022 bScreen *screen = CTX_wm_screen(C);
1023 sActionzoneData *sad = op->customdata;
1024
1025 switch (event->type) {
1026 case MOUSEMOVE: {
1027 const int delta_x = (event->x - sad->x);
1028 const int delta_y = (event->y - sad->y);
1029
1030 /* Movement in dominant direction. */
1031 const int delta_max = max_ii(abs(delta_x), abs(delta_y));
1032
1033 /* Movement in dominant direction before action taken. */
1034 const int join_threshold = (0.6 * U.widget_unit);
1035 const int split_threshold = (1.2 * U.widget_unit);
1036 const int area_threshold = (0.1 * U.widget_unit);
1037
1038 /* Calculate gesture cardinal direction. */
1039 if (delta_y > abs(delta_x)) {
1040 sad->gesture_dir = 'n';
1041 }
1042 else if (delta_x >= abs(delta_y)) {
1043 sad->gesture_dir = 'e';
1044 }
1045 else if (delta_y < -abs(delta_x)) {
1046 sad->gesture_dir = 's';
1047 }
1048 else {
1049 sad->gesture_dir = 'w';
1050 }
1051
1052 bool is_gesture;
1053 if (sad->az->type == AZONE_AREA) {
1054 wmWindow *win = CTX_wm_window(C);
1055
1056 rcti screen_rect;
1057 WM_window_screen_rect_calc(win, &screen_rect);
1058
1059 /* Have we dragged off the zone and are not on an edge? */
1060 if ((ED_area_actionzone_find_xy(sad->sa1, &event->x) != sad->az) &&
1061 (screen_geom_area_map_find_active_scredge(
1062 AREAMAP_FROM_SCREEN(screen), &screen_rect, event->x, event->y) == NULL)) {
1063 /* Are we still in same area? */
1064 if (BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) {
1065 /* Same area, so possible split. */
1066 WM_cursor_set(
1067 win, (ELEM(sad->gesture_dir, 'n', 's')) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT);
1068 is_gesture = (delta_max > split_threshold);
1069 }
1070 else {
1071 /* Different area, so possible join. */
1072 if (sad->gesture_dir == 'n') {
1073 WM_cursor_set(win, WM_CURSOR_N_ARROW);
1074 }
1075 else if (sad->gesture_dir == 's') {
1076 WM_cursor_set(win, WM_CURSOR_S_ARROW);
1077 }
1078 else if (sad->gesture_dir == 'e') {
1079 WM_cursor_set(win, WM_CURSOR_E_ARROW);
1080 }
1081 else {
1082 WM_cursor_set(win, WM_CURSOR_W_ARROW);
1083 }
1084 is_gesture = (delta_max > join_threshold);
1085 }
1086 }
1087 else {
1088 WM_cursor_set(CTX_wm_window(C), WM_CURSOR_CROSS);
1089 is_gesture = false;
1090 }
1091 }
1092 else {
1093 is_gesture = (delta_max > area_threshold);
1094 }
1095
1096 /* gesture is large enough? */
1097 if (is_gesture) {
1098 /* second area, for join when (sa1 != sa2) */
1099 sad->sa2 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y);
1100 /* apply sends event */
1101 actionzone_apply(C, op, sad->az->type);
1102 actionzone_exit(op);
1103
1104 return OPERATOR_FINISHED;
1105 }
1106 break;
1107 }
1108 case EVT_ESCKEY:
1109 actionzone_exit(op);
1110 return OPERATOR_CANCELLED;
1111 case LEFTMOUSE:
1112 actionzone_exit(op);
1113 return OPERATOR_CANCELLED;
1114 }
1115
1116 return OPERATOR_RUNNING_MODAL;
1117 }
1118
actionzone_cancel(bContext * UNUSED (C),wmOperator * op)1119 static void actionzone_cancel(bContext *UNUSED(C), wmOperator *op)
1120 {
1121 actionzone_exit(op);
1122 }
1123
SCREEN_OT_actionzone(wmOperatorType * ot)1124 static void SCREEN_OT_actionzone(wmOperatorType *ot)
1125 {
1126 /* identifiers */
1127 ot->name = "Handle Area Action Zones";
1128 ot->description = "Handle area action zones for mouse actions/gestures";
1129 ot->idname = "SCREEN_OT_actionzone";
1130
1131 ot->invoke = actionzone_invoke;
1132 ot->modal = actionzone_modal;
1133 ot->poll = actionzone_area_poll;
1134 ot->cancel = actionzone_cancel;
1135
1136 /* flags */
1137 ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
1138
1139 RNA_def_int(ot->srna, "modifier", 0, 0, 2, "Modifier", "Modifier state", 0, 2);
1140 }
1141
1142 /** \} */
1143
1144 /* -------------------------------------------------------------------- */
1145 /** \name Area edge detection utility
1146 * \{ */
1147
screen_area_edge_from_cursor(const bContext * C,const int cursor[2],ScrArea ** r_sa1,ScrArea ** r_sa2)1148 static ScrEdge *screen_area_edge_from_cursor(const bContext *C,
1149 const int cursor[2],
1150 ScrArea **r_sa1,
1151 ScrArea **r_sa2)
1152 {
1153 wmWindow *win = CTX_wm_window(C);
1154 bScreen *screen = CTX_wm_screen(C);
1155 rcti window_rect;
1156 WM_window_rect_calc(win, &window_rect);
1157 ScrEdge *actedge = screen_geom_area_map_find_active_scredge(
1158 AREAMAP_FROM_SCREEN(screen), &window_rect, cursor[0], cursor[1]);
1159 *r_sa1 = NULL;
1160 *r_sa2 = NULL;
1161 if (actedge == NULL) {
1162 return NULL;
1163 }
1164 int borderwidth = (4 * UI_DPI_FAC);
1165 ScrArea *sa1, *sa2;
1166 if (screen_geom_edge_is_horizontal(actedge)) {
1167 sa1 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0], cursor[1] + borderwidth);
1168 sa2 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0], cursor[1] - borderwidth);
1169 }
1170 else {
1171 sa1 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0] + borderwidth, cursor[1]);
1172 sa2 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0] - borderwidth, cursor[1]);
1173 }
1174 bool isGlobal = ((sa1 && ED_area_is_global(sa1)) || (sa2 && ED_area_is_global(sa2)));
1175 if (!isGlobal) {
1176 *r_sa1 = sa1;
1177 *r_sa2 = sa2;
1178 }
1179 return actedge;
1180 }
1181
1182 /** \} */
1183
1184 /* -------------------------------------------------------------------- */
1185 /** \name Swap Area Operator
1186 * \{ */
1187
1188 /* operator state vars used:
1189 * sa1 start area
1190 * sa2 area to swap with
1191 *
1192 * functions:
1193 *
1194 * init() set custom data for operator, based on action-zone event custom data
1195 *
1196 * cancel() cancel the operator
1197 *
1198 * exit() cleanup, send notifier
1199 *
1200 * callbacks:
1201 *
1202 * invoke() gets called on shift+lmb drag in action-zone
1203 * exec() execute without any user interaction, based on properties
1204 * call init(), add handler
1205 *
1206 * modal() accept modal events while doing it
1207 */
1208
1209 typedef struct sAreaSwapData {
1210 ScrArea *sa1, *sa2;
1211 } sAreaSwapData;
1212
area_swap_init(wmOperator * op,const wmEvent * event)1213 static bool area_swap_init(wmOperator *op, const wmEvent *event)
1214 {
1215 sActionzoneData *sad = event->customdata;
1216
1217 if (sad == NULL || sad->sa1 == NULL) {
1218 return false;
1219 }
1220
1221 sAreaSwapData *sd = MEM_callocN(sizeof(sAreaSwapData), "sAreaSwapData");
1222 sd->sa1 = sad->sa1;
1223 sd->sa2 = sad->sa2;
1224 op->customdata = sd;
1225
1226 return true;
1227 }
1228
area_swap_exit(bContext * C,wmOperator * op)1229 static void area_swap_exit(bContext *C, wmOperator *op)
1230 {
1231 WM_cursor_modal_restore(CTX_wm_window(C));
1232 if (op->customdata) {
1233 MEM_freeN(op->customdata);
1234 }
1235 op->customdata = NULL;
1236 }
1237
area_swap_cancel(bContext * C,wmOperator * op)1238 static void area_swap_cancel(bContext *C, wmOperator *op)
1239 {
1240 area_swap_exit(C, op);
1241 }
1242
area_swap_invoke(bContext * C,wmOperator * op,const wmEvent * event)1243 static int area_swap_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1244 {
1245 if (!area_swap_init(op, event)) {
1246 return OPERATOR_PASS_THROUGH;
1247 }
1248
1249 /* add modal handler */
1250 WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_SWAP_AREA);
1251 WM_event_add_modal_handler(C, op);
1252
1253 return OPERATOR_RUNNING_MODAL;
1254 }
1255
area_swap_modal(bContext * C,wmOperator * op,const wmEvent * event)1256 static int area_swap_modal(bContext *C, wmOperator *op, const wmEvent *event)
1257 {
1258 sActionzoneData *sad = op->customdata;
1259
1260 switch (event->type) {
1261 case MOUSEMOVE:
1262 /* second area, for join */
1263 sad->sa2 = BKE_screen_find_area_xy(CTX_wm_screen(C), SPACE_TYPE_ANY, event->x, event->y);
1264 break;
1265 case LEFTMOUSE: /* release LMB */
1266 if (event->val == KM_RELEASE) {
1267 if (!sad->sa2 || sad->sa1 == sad->sa2) {
1268 area_swap_cancel(C, op);
1269 return OPERATOR_CANCELLED;
1270 }
1271
1272 ED_area_tag_redraw(sad->sa1);
1273 ED_area_tag_redraw(sad->sa2);
1274
1275 ED_area_swapspace(C, sad->sa1, sad->sa2);
1276
1277 area_swap_exit(C, op);
1278
1279 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1280
1281 return OPERATOR_FINISHED;
1282 }
1283 break;
1284
1285 case EVT_ESCKEY:
1286 area_swap_cancel(C, op);
1287 return OPERATOR_CANCELLED;
1288 }
1289 return OPERATOR_RUNNING_MODAL;
1290 }
1291
area_swap_exec(bContext * C,wmOperator * op)1292 static int area_swap_exec(bContext *C, wmOperator *op)
1293 {
1294 ScrArea *sa1, *sa2;
1295 int cursor[2];
1296 RNA_int_get_array(op->ptr, "cursor", cursor);
1297 screen_area_edge_from_cursor(C, cursor, &sa1, &sa2);
1298 if (sa1 == NULL || sa2 == NULL) {
1299 return OPERATOR_CANCELLED;
1300 }
1301 ED_area_swapspace(C, sa1, sa2);
1302 return OPERATOR_FINISHED;
1303 }
1304
SCREEN_OT_area_swap(wmOperatorType * ot)1305 static void SCREEN_OT_area_swap(wmOperatorType *ot)
1306 {
1307 ot->name = "Swap Areas";
1308 ot->description = "Swap selected areas screen positions";
1309 ot->idname = "SCREEN_OT_area_swap";
1310
1311 ot->invoke = area_swap_invoke;
1312 ot->modal = area_swap_modal;
1313 ot->exec = area_swap_exec;
1314 ot->poll = screen_active_editable;
1315 ot->cancel = area_swap_cancel;
1316
1317 ot->flag = OPTYPE_BLOCKING;
1318
1319 /* rna */
1320 RNA_def_int_vector(
1321 ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX);
1322 }
1323
1324 /** \} */
1325
1326 /* -------------------------------------------------------------------- */
1327 /** \name Area Duplicate Operator
1328 *
1329 * Create new window from area.
1330 * \{ */
1331
1332 /* operator callback */
area_dupli_invoke(bContext * C,wmOperator * op,const wmEvent * event)1333 static int area_dupli_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1334 {
1335 Main *bmain = CTX_data_main(C);
1336 wmWindow *win = CTX_wm_window(C);
1337 WorkSpace *workspace = WM_window_get_active_workspace(win);
1338 WorkSpaceLayout *layout_old = WM_window_get_active_layout(win);
1339
1340 Scene *scene = CTX_data_scene(C);
1341 ScrArea *area = CTX_wm_area(C);
1342
1343 /* XXX hrmf! */
1344 if (event->type == EVT_ACTIONZONE_AREA) {
1345 sActionzoneData *sad = event->customdata;
1346
1347 if (sad == NULL) {
1348 return OPERATOR_PASS_THROUGH;
1349 }
1350
1351 area = sad->sa1;
1352 }
1353
1354 /* adds window to WM */
1355 rcti rect = area->totrct;
1356 BLI_rcti_translate(&rect, win->posx, win->posy);
1357 rect.xmax = rect.xmin + BLI_rcti_size_x(&rect) / U.pixelsize;
1358 rect.ymax = rect.ymin + BLI_rcti_size_y(&rect) / U.pixelsize;
1359
1360 wmWindow *newwin = WM_window_open(C, &rect);
1361 if (newwin == NULL) {
1362 BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
1363 goto finally;
1364 }
1365
1366 *newwin->stereo3d_format = *win->stereo3d_format;
1367
1368 newwin->scene = scene;
1369
1370 STRNCPY(newwin->view_layer_name, win->view_layer_name);
1371
1372 BKE_workspace_active_set(newwin->workspace_hook, workspace);
1373 /* allocs new screen and adds to newly created window, using window size */
1374 WorkSpaceLayout *layout_new = ED_workspace_layout_add(
1375 bmain, workspace, newwin, BKE_workspace_layout_name_get(layout_old));
1376 bScreen *newsc = BKE_workspace_layout_screen_get(layout_new);
1377 WM_window_set_active_layout(newwin, workspace, layout_new);
1378
1379 /* copy area to new screen */
1380 ED_area_data_copy((ScrArea *)newsc->areabase.first, area, true);
1381
1382 ED_area_tag_redraw((ScrArea *)newsc->areabase.first);
1383
1384 /* screen, areas init */
1385 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1386
1387 finally:
1388 if (event->type == EVT_ACTIONZONE_AREA) {
1389 actionzone_exit(op);
1390 }
1391
1392 if (newwin) {
1393 return OPERATOR_FINISHED;
1394 }
1395 return OPERATOR_CANCELLED;
1396 }
1397
SCREEN_OT_area_dupli(wmOperatorType * ot)1398 static void SCREEN_OT_area_dupli(wmOperatorType *ot)
1399 {
1400 ot->name = "Duplicate Area into New Window";
1401 ot->description = "Duplicate selected area into new window";
1402 ot->idname = "SCREEN_OT_area_dupli";
1403
1404 ot->invoke = area_dupli_invoke;
1405 ot->poll = ED_operator_areaactive;
1406 }
1407
1408 /** \} */
1409
1410 /* -------------------------------------------------------------------- */
1411 /** \name Move Area Edge Operator
1412 * \{ */
1413
1414 /* operator state vars used:
1415 * x, y mouse coord near edge
1416 * delta movement of edge
1417 *
1418 * functions:
1419 *
1420 * init() set default property values, find edge based on mouse coords, test
1421 * if the edge can be moved, select edges, calculate min and max movement
1422 *
1423 * apply() apply delta on selection
1424 *
1425 * exit() cleanup, send notifier
1426 *
1427 * cancel() cancel moving
1428 *
1429 * callbacks:
1430 *
1431 * exec() execute without any user interaction, based on properties
1432 * call init(), apply(), exit()
1433 *
1434 * invoke() gets called on mouse click near edge
1435 * call init(), add handler
1436 *
1437 * modal() accept modal events while doing it
1438 * call apply() with delta motion
1439 * call exit() and remove handler
1440 */
1441
1442 typedef struct sAreaMoveData {
1443 int bigger, smaller, origval, step;
1444 char dir;
1445 enum AreaMoveSnapType {
1446 /* Snapping disabled */
1447 SNAP_NONE = 0,
1448 /* Snap to an invisible grid with a unit defined in AREAGRID */
1449 SNAP_AREAGRID,
1450 /* Snap to fraction (half, third.. etc) and adjacent edges. */
1451 SNAP_FRACTION_AND_ADJACENT,
1452 /* Snap to either bigger or smaller, nothing in-between (used for
1453 * global areas). This has priority over other snap types, if it is
1454 * used, toggling SNAP_FRACTION_AND_ADJACENT doesn't work. */
1455 SNAP_BIGGER_SMALLER_ONLY,
1456 } snap_type;
1457 } sAreaMoveData;
1458
1459 /* helper call to move area-edge, sets limits
1460 * need window bounds in order to get correct limits */
area_move_set_limits(wmWindow * win,bScreen * screen,int dir,int * bigger,int * smaller,bool * use_bigger_smaller_snap)1461 static void area_move_set_limits(wmWindow *win,
1462 bScreen *screen,
1463 int dir,
1464 int *bigger,
1465 int *smaller,
1466 bool *use_bigger_smaller_snap)
1467 {
1468 /* we check all areas and test for free space with MINSIZE */
1469 *bigger = *smaller = 100000;
1470
1471 if (use_bigger_smaller_snap != NULL) {
1472 *use_bigger_smaller_snap = false;
1473 LISTBASE_FOREACH (ScrArea *, area, &win->global_areas.areabase) {
1474 int size_min = ED_area_global_min_size_y(area) - 1;
1475 int size_max = ED_area_global_max_size_y(area) - 1;
1476
1477 size_min = max_ii(size_min, 0);
1478 BLI_assert(size_min <= size_max);
1479
1480 /* logic here is only tested for lower edge :) */
1481 /* left edge */
1482 if ((area->v1->editflag && area->v2->editflag)) {
1483 *smaller = area->v4->vec.x - size_max;
1484 *bigger = area->v4->vec.x - size_min;
1485 *use_bigger_smaller_snap = true;
1486 return;
1487 }
1488 /* top edge */
1489 if ((area->v2->editflag && area->v3->editflag)) {
1490 *smaller = area->v1->vec.y + size_min;
1491 *bigger = area->v1->vec.y + size_max;
1492 *use_bigger_smaller_snap = true;
1493 return;
1494 }
1495 /* right edge */
1496 if ((area->v3->editflag && area->v4->editflag)) {
1497 *smaller = area->v1->vec.x + size_min;
1498 *bigger = area->v1->vec.x + size_max;
1499 *use_bigger_smaller_snap = true;
1500 return;
1501 }
1502 /* lower edge */
1503 if ((area->v4->editflag && area->v1->editflag)) {
1504 *smaller = area->v2->vec.y - size_max;
1505 *bigger = area->v2->vec.y - size_min;
1506 *use_bigger_smaller_snap = true;
1507 return;
1508 }
1509 }
1510 }
1511
1512 rcti window_rect;
1513 WM_window_rect_calc(win, &window_rect);
1514
1515 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
1516 if (dir == 'h') {
1517 int areamin = ED_area_headersize();
1518
1519 if (area->v1->vec.y > window_rect.ymin) {
1520 areamin += U.pixelsize;
1521 }
1522 if (area->v2->vec.y < (window_rect.ymax - 1)) {
1523 areamin += U.pixelsize;
1524 }
1525
1526 int y1 = screen_geom_area_height(area) - areamin;
1527
1528 /* if top or down edge selected, test height */
1529 if (area->v1->editflag && area->v4->editflag) {
1530 *bigger = min_ii(*bigger, y1);
1531 }
1532 else if (area->v2->editflag && area->v3->editflag) {
1533 *smaller = min_ii(*smaller, y1);
1534 }
1535 }
1536 else {
1537 int areamin = AREAMINX;
1538
1539 if (area->v1->vec.x > window_rect.xmin) {
1540 areamin += U.pixelsize;
1541 }
1542 if (area->v4->vec.x < (window_rect.xmax - 1)) {
1543 areamin += U.pixelsize;
1544 }
1545
1546 int x1 = screen_geom_area_width(area) - areamin;
1547
1548 /* if left or right edge selected, test width */
1549 if (area->v1->editflag && area->v2->editflag) {
1550 *bigger = min_ii(*bigger, x1);
1551 }
1552 else if (area->v3->editflag && area->v4->editflag) {
1553 *smaller = min_ii(*smaller, x1);
1554 }
1555 }
1556 }
1557 }
1558
1559 /* validate selection inside screen, set variables OK */
1560 /* return false: init failed */
area_move_init(bContext * C,wmOperator * op)1561 static bool area_move_init(bContext *C, wmOperator *op)
1562 {
1563 bScreen *screen = CTX_wm_screen(C);
1564 wmWindow *win = CTX_wm_window(C);
1565
1566 /* required properties */
1567 int x = RNA_int_get(op->ptr, "x");
1568 int y = RNA_int_get(op->ptr, "y");
1569
1570 /* setup */
1571 ScrEdge *actedge = screen_geom_find_active_scredge(win, screen, x, y);
1572 if (actedge == NULL) {
1573 return false;
1574 }
1575
1576 sAreaMoveData *md = MEM_callocN(sizeof(sAreaMoveData), "sAreaMoveData");
1577 op->customdata = md;
1578
1579 md->dir = screen_geom_edge_is_horizontal(actedge) ? 'h' : 'v';
1580 if (md->dir == 'h') {
1581 md->origval = actedge->v1->vec.y;
1582 }
1583 else {
1584 md->origval = actedge->v1->vec.x;
1585 }
1586
1587 screen_geom_select_connected_edge(win, actedge);
1588 /* now all vertices with 'flag == 1' are the ones that can be moved. Move this to editflag */
1589 ED_screen_verts_iter(win, screen, v1)
1590 {
1591 v1->editflag = v1->flag;
1592 }
1593
1594 bool use_bigger_smaller_snap = false;
1595 area_move_set_limits(win, screen, md->dir, &md->bigger, &md->smaller, &use_bigger_smaller_snap);
1596
1597 md->snap_type = use_bigger_smaller_snap ? SNAP_BIGGER_SMALLER_ONLY : SNAP_AREAGRID;
1598
1599 return true;
1600 }
1601
area_snap_calc_location(const bScreen * screen,const enum AreaMoveSnapType snap_type,const int delta,const int origval,const int dir,const int bigger,const int smaller)1602 static int area_snap_calc_location(const bScreen *screen,
1603 const enum AreaMoveSnapType snap_type,
1604 const int delta,
1605 const int origval,
1606 const int dir,
1607 const int bigger,
1608 const int smaller)
1609 {
1610 BLI_assert(snap_type != SNAP_NONE);
1611 int m_cursor_final = -1;
1612 const int m_cursor = origval + delta;
1613 const int m_span = (float)(bigger + smaller);
1614 const int m_min = origval - smaller;
1615 // const int axis_max = axis_min + m_span;
1616
1617 switch (snap_type) {
1618 case SNAP_AREAGRID:
1619 m_cursor_final = m_cursor;
1620 if (delta != bigger && delta != -smaller) {
1621 m_cursor_final -= (m_cursor % AREAGRID);
1622 CLAMP(m_cursor_final, origval - smaller, origval + bigger);
1623 }
1624 break;
1625
1626 case SNAP_BIGGER_SMALLER_ONLY:
1627 m_cursor_final = (m_cursor >= bigger) ? bigger : smaller;
1628 break;
1629
1630 case SNAP_FRACTION_AND_ADJACENT: {
1631 const int axis = (dir == 'v') ? 0 : 1;
1632 int snap_dist_best = INT_MAX;
1633 {
1634 const float div_array[] = {
1635 /* Middle. */
1636 1.0f / 2.0f,
1637 /* Thirds. */
1638 1.0f / 3.0f,
1639 2.0f / 3.0f,
1640 /* Quaters. */
1641 1.0f / 4.0f,
1642 3.0f / 4.0f,
1643 /* Eighth. */
1644 1.0f / 8.0f,
1645 3.0f / 8.0f,
1646 5.0f / 8.0f,
1647 7.0f / 8.0f,
1648 };
1649 /* Test the snap to the best division. */
1650 for (int i = 0; i < ARRAY_SIZE(div_array); i++) {
1651 const int m_cursor_test = m_min + round_fl_to_int(m_span * div_array[i]);
1652 const int snap_dist_test = abs(m_cursor - m_cursor_test);
1653 if (snap_dist_best >= snap_dist_test) {
1654 snap_dist_best = snap_dist_test;
1655 m_cursor_final = m_cursor_test;
1656 }
1657 }
1658 }
1659
1660 LISTBASE_FOREACH (const ScrVert *, v1, &screen->vertbase) {
1661 if (!v1->editflag) {
1662 continue;
1663 }
1664 const int v_loc = (&v1->vec.x)[!axis];
1665
1666 LISTBASE_FOREACH (const ScrVert *, v2, &screen->vertbase) {
1667 if (v2->editflag) {
1668 continue;
1669 }
1670 if (v_loc == (&v2->vec.x)[!axis]) {
1671 const int v_loc2 = (&v2->vec.x)[axis];
1672 /* Do not snap to the vertices at the ends. */
1673 if ((origval - smaller) < v_loc2 && v_loc2 < (origval + bigger)) {
1674 const int snap_dist_test = abs(m_cursor - v_loc2);
1675 if (snap_dist_best >= snap_dist_test) {
1676 snap_dist_best = snap_dist_test;
1677 m_cursor_final = v_loc2;
1678 }
1679 }
1680 }
1681 }
1682 }
1683 break;
1684 }
1685 case SNAP_NONE:
1686 break;
1687 }
1688
1689 BLI_assert(ELEM(snap_type, SNAP_BIGGER_SMALLER_ONLY) ||
1690 IN_RANGE_INCL(m_cursor_final, origval - smaller, origval + bigger));
1691
1692 return m_cursor_final;
1693 }
1694
1695 /* moves selected screen edge amount of delta, used by split & move */
area_move_apply_do(const bContext * C,int delta,const int origval,const int dir,const int bigger,const int smaller,const enum AreaMoveSnapType snap_type)1696 static void area_move_apply_do(const bContext *C,
1697 int delta,
1698 const int origval,
1699 const int dir,
1700 const int bigger,
1701 const int smaller,
1702 const enum AreaMoveSnapType snap_type)
1703 {
1704 wmWindow *win = CTX_wm_window(C);
1705 bScreen *screen = CTX_wm_screen(C);
1706 short final_loc = -1;
1707 bool doredraw = false;
1708
1709 if (snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1710 CLAMP(delta, -smaller, bigger);
1711 }
1712
1713 if (snap_type == SNAP_NONE) {
1714 final_loc = origval + delta;
1715 }
1716 else {
1717 final_loc = area_snap_calc_location(screen, snap_type, delta, origval, dir, bigger, smaller);
1718 }
1719
1720 BLI_assert(final_loc != -1);
1721 short axis = (dir == 'v') ? 0 : 1;
1722
1723 ED_screen_verts_iter(win, screen, v1)
1724 {
1725 if (v1->editflag) {
1726 short oldval = (&v1->vec.x)[axis];
1727 (&v1->vec.x)[axis] = final_loc;
1728
1729 if (oldval == final_loc) {
1730 /* nothing will change to the other vertices either. */
1731 break;
1732 }
1733 doredraw = true;
1734 }
1735 }
1736
1737 /* only redraw if we actually moved a screen vert, for AREAGRID */
1738 if (doredraw) {
1739 bool redraw_all = false;
1740 ED_screen_areas_iter (win, screen, area) {
1741 if (area->v1->editflag || area->v2->editflag || area->v3->editflag || area->v4->editflag) {
1742 if (ED_area_is_global(area)) {
1743 /* Snap to minimum or maximum for global areas. */
1744 int height = round_fl_to_int(screen_geom_area_height(area) / UI_DPI_FAC);
1745 if (abs(height - area->global->size_min) < abs(height - area->global->size_max)) {
1746 area->global->cur_fixed_height = area->global->size_min;
1747 }
1748 else {
1749 area->global->cur_fixed_height = area->global->size_max;
1750 }
1751
1752 screen->do_refresh = true;
1753 redraw_all = true;
1754 }
1755 ED_area_tag_redraw_no_rebuild(area);
1756 }
1757 }
1758 if (redraw_all) {
1759 ED_screen_areas_iter (win, screen, area) {
1760 ED_area_tag_redraw(area);
1761 }
1762 }
1763
1764 ED_screen_global_areas_sync(win);
1765
1766 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); /* redraw everything */
1767 /* Update preview thumbnail */
1768 BKE_icon_changed(screen->id.icon_id);
1769 }
1770 }
1771
area_move_apply(bContext * C,wmOperator * op)1772 static void area_move_apply(bContext *C, wmOperator *op)
1773 {
1774 sAreaMoveData *md = op->customdata;
1775 int delta = RNA_int_get(op->ptr, "delta");
1776
1777 area_move_apply_do(C, delta, md->origval, md->dir, md->bigger, md->smaller, md->snap_type);
1778 }
1779
area_move_exit(bContext * C,wmOperator * op)1780 static void area_move_exit(bContext *C, wmOperator *op)
1781 {
1782 if (op->customdata) {
1783 MEM_freeN(op->customdata);
1784 }
1785 op->customdata = NULL;
1786
1787 /* this makes sure aligned edges will result in aligned grabbing */
1788 BKE_screen_remove_double_scrverts(CTX_wm_screen(C));
1789 BKE_screen_remove_double_scredges(CTX_wm_screen(C));
1790
1791 G.moving &= ~G_TRANSFORM_WM;
1792 }
1793
area_move_exec(bContext * C,wmOperator * op)1794 static int area_move_exec(bContext *C, wmOperator *op)
1795 {
1796 if (!area_move_init(C, op)) {
1797 return OPERATOR_CANCELLED;
1798 }
1799
1800 area_move_apply(C, op);
1801 area_move_exit(C, op);
1802
1803 return OPERATOR_FINISHED;
1804 }
1805
1806 /* interaction callback */
area_move_invoke(bContext * C,wmOperator * op,const wmEvent * event)1807 static int area_move_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1808 {
1809 RNA_int_set(op->ptr, "x", event->x);
1810 RNA_int_set(op->ptr, "y", event->y);
1811
1812 if (!area_move_init(C, op)) {
1813 return OPERATOR_PASS_THROUGH;
1814 }
1815
1816 /* add temp handler */
1817 G.moving |= G_TRANSFORM_WM;
1818 WM_event_add_modal_handler(C, op);
1819
1820 return OPERATOR_RUNNING_MODAL;
1821 }
1822
area_move_cancel(bContext * C,wmOperator * op)1823 static void area_move_cancel(bContext *C, wmOperator *op)
1824 {
1825
1826 RNA_int_set(op->ptr, "delta", 0);
1827 area_move_apply(C, op);
1828 area_move_exit(C, op);
1829 }
1830
1831 /* modal callback for while moving edges */
area_move_modal(bContext * C,wmOperator * op,const wmEvent * event)1832 static int area_move_modal(bContext *C, wmOperator *op, const wmEvent *event)
1833 {
1834 sAreaMoveData *md = op->customdata;
1835
1836 /* execute the events */
1837 switch (event->type) {
1838 case MOUSEMOVE: {
1839 int x = RNA_int_get(op->ptr, "x");
1840 int y = RNA_int_get(op->ptr, "y");
1841
1842 int delta = (md->dir == 'v') ? event->x - x : event->y - y;
1843 RNA_int_set(op->ptr, "delta", delta);
1844
1845 area_move_apply(C, op);
1846 break;
1847 }
1848 case EVT_MODAL_MAP: {
1849 switch (event->val) {
1850 case KM_MODAL_APPLY:
1851 area_move_exit(C, op);
1852 return OPERATOR_FINISHED;
1853
1854 case KM_MODAL_CANCEL:
1855 area_move_cancel(C, op);
1856 return OPERATOR_CANCELLED;
1857
1858 case KM_MODAL_SNAP_ON:
1859 if (md->snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1860 md->snap_type = SNAP_FRACTION_AND_ADJACENT;
1861 }
1862 break;
1863
1864 case KM_MODAL_SNAP_OFF:
1865 if (md->snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1866 md->snap_type = SNAP_AREAGRID;
1867 }
1868 break;
1869 }
1870 break;
1871 }
1872 }
1873
1874 return OPERATOR_RUNNING_MODAL;
1875 }
1876
SCREEN_OT_area_move(wmOperatorType * ot)1877 static void SCREEN_OT_area_move(wmOperatorType *ot)
1878 {
1879 /* identifiers */
1880 ot->name = "Move Area Edges";
1881 ot->description = "Move selected area edges";
1882 ot->idname = "SCREEN_OT_area_move";
1883
1884 ot->exec = area_move_exec;
1885 ot->invoke = area_move_invoke;
1886 ot->cancel = area_move_cancel;
1887 ot->modal = area_move_modal;
1888 ot->poll = ED_operator_screen_mainwinactive; /* when mouse is over area-edge */
1889
1890 /* flags */
1891 ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
1892
1893 /* rna */
1894 RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
1895 RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
1896 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
1897 }
1898
1899 /** \} */
1900
1901 /* -------------------------------------------------------------------- */
1902 /** \name Split Area Operator
1903 * \{ */
1904
1905 /*
1906 * operator state vars:
1907 * fac spit point
1908 * dir direction 'v' or 'h'
1909 *
1910 * operator customdata:
1911 * area pointer to (active) area
1912 * x, y last used mouse pos
1913 * (more, see below)
1914 *
1915 * functions:
1916 *
1917 * init() set default property values, find area based on context
1918 *
1919 * apply() split area based on state vars
1920 *
1921 * exit() cleanup, send notifier
1922 *
1923 * cancel() remove duplicated area
1924 *
1925 * callbacks:
1926 *
1927 * exec() execute without any user interaction, based on state vars
1928 * call init(), apply(), exit()
1929 *
1930 * invoke() gets called on mouse click in action-widget
1931 * call init(), add modal handler
1932 * call apply() with initial motion
1933 *
1934 * modal() accept modal events while doing it
1935 * call move-areas code with delta motion
1936 * call exit() or cancel() and remove handler
1937 */
1938
1939 typedef struct sAreaSplitData {
1940 int origval; /* for move areas */
1941 int bigger, smaller; /* constraints for moving new edge */
1942 int delta; /* delta move edge */
1943 int origmin, origsize; /* to calculate fac, for property storage */
1944 int previewmode; /* draw previewline, then split */
1945 void *draw_callback; /* call `ED_screen_draw_split_preview` */
1946 bool do_snap;
1947
1948 ScrEdge *nedge; /* new edge */
1949 ScrArea *sarea; /* start area */
1950 ScrArea *narea; /* new area */
1951
1952 } sAreaSplitData;
1953
area_split_draw_cb(const struct wmWindow * UNUSED (win),void * userdata)1954 static void area_split_draw_cb(const struct wmWindow *UNUSED(win), void *userdata)
1955 {
1956 const wmOperator *op = userdata;
1957
1958 sAreaSplitData *sd = op->customdata;
1959 if (sd->sarea) {
1960 int dir = RNA_enum_get(op->ptr, "direction");
1961 float fac = RNA_float_get(op->ptr, "factor");
1962
1963 ED_screen_draw_split_preview(sd->sarea, dir, fac);
1964 }
1965 }
1966
1967 /* generic init, menu case, doesn't need active area */
area_split_menu_init(bContext * C,wmOperator * op)1968 static bool area_split_menu_init(bContext *C, wmOperator *op)
1969 {
1970 /* custom data */
1971 sAreaSplitData *sd = (sAreaSplitData *)MEM_callocN(sizeof(sAreaSplitData), "op_area_split");
1972 op->customdata = sd;
1973
1974 sd->sarea = CTX_wm_area(C);
1975
1976 return true;
1977 }
1978
1979 /* generic init, no UI stuff here, assumes active area */
area_split_init(bContext * C,wmOperator * op)1980 static bool area_split_init(bContext *C, wmOperator *op)
1981 {
1982 ScrArea *area = CTX_wm_area(C);
1983
1984 /* required context */
1985 if (area == NULL) {
1986 return false;
1987 }
1988
1989 /* required properties */
1990 int dir = RNA_enum_get(op->ptr, "direction");
1991
1992 /* minimal size */
1993 if (dir == 'v' && area->winx < 2 * AREAMINX) {
1994 return false;
1995 }
1996 if (dir == 'h' && area->winy < 2 * ED_area_headersize()) {
1997 return false;
1998 }
1999
2000 /* custom data */
2001 sAreaSplitData *sd = (sAreaSplitData *)MEM_callocN(sizeof(sAreaSplitData), "op_area_split");
2002 op->customdata = sd;
2003
2004 sd->sarea = area;
2005 if (dir == 'v') {
2006 sd->origmin = area->v1->vec.x;
2007 sd->origsize = area->v4->vec.x - sd->origmin;
2008 }
2009 else {
2010 sd->origmin = area->v1->vec.y;
2011 sd->origsize = area->v2->vec.y - sd->origmin;
2012 }
2013
2014 return true;
2015 }
2016
2017 /* with area as center, sb is located at: 0=W, 1=N, 2=E, 3=S */
2018 /* used with split operator */
area_findsharededge(bScreen * screen,ScrArea * area,ScrArea * sb)2019 static ScrEdge *area_findsharededge(bScreen *screen, ScrArea *area, ScrArea *sb)
2020 {
2021 ScrVert *sav1 = area->v1;
2022 ScrVert *sav2 = area->v2;
2023 ScrVert *sav3 = area->v3;
2024 ScrVert *sav4 = area->v4;
2025 ScrVert *sbv1 = sb->v1;
2026 ScrVert *sbv2 = sb->v2;
2027 ScrVert *sbv3 = sb->v3;
2028 ScrVert *sbv4 = sb->v4;
2029
2030 if (sav1 == sbv4 && sav2 == sbv3) { /* area to right of sb = W */
2031 return BKE_screen_find_edge(screen, sav1, sav2);
2032 }
2033 if (sav2 == sbv1 && sav3 == sbv4) { /* area to bottom of sb = N */
2034 return BKE_screen_find_edge(screen, sav2, sav3);
2035 }
2036 if (sav3 == sbv2 && sav4 == sbv1) { /* area to left of sb = E */
2037 return BKE_screen_find_edge(screen, sav3, sav4);
2038 }
2039 if (sav1 == sbv2 && sav4 == sbv3) { /* area on top of sb = S*/
2040 return BKE_screen_find_edge(screen, sav1, sav4);
2041 }
2042
2043 return NULL;
2044 }
2045
2046 /* do the split, return success */
area_split_apply(bContext * C,wmOperator * op)2047 static bool area_split_apply(bContext *C, wmOperator *op)
2048 {
2049 const wmWindow *win = CTX_wm_window(C);
2050 bScreen *screen = CTX_wm_screen(C);
2051 sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2052
2053 float fac = RNA_float_get(op->ptr, "factor");
2054 int dir = RNA_enum_get(op->ptr, "direction");
2055
2056 sd->narea = area_split(win, screen, sd->sarea, dir, fac, 0); /* 0 = no merge */
2057
2058 if (sd->narea == NULL) {
2059 return false;
2060 }
2061
2062 sd->nedge = area_findsharededge(screen, sd->sarea, sd->narea);
2063
2064 /* select newly created edge, prepare for moving edge */
2065 ED_screen_verts_iter(win, screen, sv)
2066 {
2067 sv->editflag = 0;
2068 }
2069
2070 sd->nedge->v1->editflag = 1;
2071 sd->nedge->v2->editflag = 1;
2072
2073 if (dir == 'h') {
2074 sd->origval = sd->nedge->v1->vec.y;
2075 }
2076 else {
2077 sd->origval = sd->nedge->v1->vec.x;
2078 }
2079
2080 ED_area_tag_redraw(sd->sarea);
2081 ED_area_tag_redraw(sd->narea);
2082
2083 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2084 /* Update preview thumbnail */
2085 BKE_icon_changed(screen->id.icon_id);
2086
2087 return true;
2088 }
2089
area_split_exit(bContext * C,wmOperator * op)2090 static void area_split_exit(bContext *C, wmOperator *op)
2091 {
2092 if (op->customdata) {
2093 sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2094 if (sd->sarea) {
2095 ED_area_tag_redraw(sd->sarea);
2096 }
2097 if (sd->narea) {
2098 ED_area_tag_redraw(sd->narea);
2099 }
2100
2101 if (sd->draw_callback) {
2102 WM_draw_cb_exit(CTX_wm_window(C), sd->draw_callback);
2103 }
2104
2105 MEM_freeN(op->customdata);
2106 op->customdata = NULL;
2107 }
2108
2109 WM_cursor_modal_restore(CTX_wm_window(C));
2110 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2111
2112 /* this makes sure aligned edges will result in aligned grabbing */
2113 BKE_screen_remove_double_scrverts(CTX_wm_screen(C));
2114 BKE_screen_remove_double_scredges(CTX_wm_screen(C));
2115
2116 G.moving &= ~G_TRANSFORM_WM;
2117 }
2118
area_split_preview_update_cursor(bContext * C,wmOperator * op)2119 static void area_split_preview_update_cursor(bContext *C, wmOperator *op)
2120 {
2121 wmWindow *win = CTX_wm_window(C);
2122 int dir = RNA_enum_get(op->ptr, "direction");
2123 WM_cursor_set(win, dir == 'h' ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT);
2124 }
2125
2126 /* UI callback, adds new handler */
area_split_invoke(bContext * C,wmOperator * op,const wmEvent * event)2127 static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2128 {
2129 wmWindow *win = CTX_wm_window(C);
2130 bScreen *screen = CTX_wm_screen(C);
2131
2132 /* no full window splitting allowed */
2133 BLI_assert(screen->state == SCREENNORMAL);
2134
2135 PropertyRNA *prop_dir = RNA_struct_find_property(op->ptr, "direction");
2136 PropertyRNA *prop_factor = RNA_struct_find_property(op->ptr, "factor");
2137 PropertyRNA *prop_cursor = RNA_struct_find_property(op->ptr, "cursor");
2138
2139 int dir;
2140 if (event->type == EVT_ACTIONZONE_AREA) {
2141 sActionzoneData *sad = event->customdata;
2142
2143 if (sad == NULL || sad->modifier > 0) {
2144 return OPERATOR_PASS_THROUGH;
2145 }
2146
2147 /* verify *sad itself */
2148 if (sad->sa1 == NULL || sad->az == NULL) {
2149 return OPERATOR_PASS_THROUGH;
2150 }
2151
2152 /* is this our *sad? if areas not equal it should be passed on */
2153 if (CTX_wm_area(C) != sad->sa1 || sad->sa1 != sad->sa2) {
2154 return OPERATOR_PASS_THROUGH;
2155 }
2156
2157 /* The factor will be close to 1.0f when near the top-left and the bottom-right corners. */
2158 const float factor_v = ((float)(event->y - sad->sa1->v1->vec.y)) / (float)sad->sa1->winy;
2159 const float factor_h = ((float)(event->x - sad->sa1->v1->vec.x)) / (float)sad->sa1->winx;
2160 const bool is_left = factor_v < 0.5f;
2161 const bool is_bottom = factor_h < 0.5f;
2162 const bool is_right = !is_left;
2163 const bool is_top = !is_bottom;
2164 float factor;
2165
2166 /* Prepare operator state vars. */
2167 if (ELEM(sad->gesture_dir, 'n', 's')) {
2168 dir = 'h';
2169 factor = factor_h;
2170 }
2171 else {
2172 dir = 'v';
2173 factor = factor_v;
2174 }
2175
2176 if ((is_top && is_left) || (is_bottom && is_right)) {
2177 factor = 1.0f - factor;
2178 }
2179
2180 RNA_property_float_set(op->ptr, prop_factor, factor);
2181
2182 RNA_property_enum_set(op->ptr, prop_dir, dir);
2183
2184 /* general init, also non-UI case, adds customdata, sets area and defaults */
2185 if (!area_split_init(C, op)) {
2186 return OPERATOR_PASS_THROUGH;
2187 }
2188 }
2189 else if (RNA_property_is_set(op->ptr, prop_dir)) {
2190 ScrArea *area = CTX_wm_area(C);
2191 if (area == NULL) {
2192 return OPERATOR_CANCELLED;
2193 }
2194 dir = RNA_property_enum_get(op->ptr, prop_dir);
2195 if (dir == 'h') {
2196 RNA_property_float_set(
2197 op->ptr, prop_factor, ((float)(event->x - area->v1->vec.x)) / (float)area->winx);
2198 }
2199 else {
2200 RNA_property_float_set(
2201 op->ptr, prop_factor, ((float)(event->y - area->v1->vec.y)) / (float)area->winy);
2202 }
2203
2204 if (!area_split_init(C, op)) {
2205 return OPERATOR_CANCELLED;
2206 }
2207 }
2208 else {
2209 int event_co[2];
2210
2211 /* retrieve initial mouse coord, so we can find the active edge */
2212 if (RNA_property_is_set(op->ptr, prop_cursor)) {
2213 RNA_property_int_get_array(op->ptr, prop_cursor, event_co);
2214 }
2215 else {
2216 copy_v2_v2_int(event_co, &event->x);
2217 }
2218
2219 rcti window_rect;
2220 WM_window_rect_calc(win, &window_rect);
2221
2222 ScrEdge *actedge = screen_geom_area_map_find_active_scredge(
2223 AREAMAP_FROM_SCREEN(screen), &window_rect, event_co[0], event_co[1]);
2224 if (actedge == NULL) {
2225 return OPERATOR_CANCELLED;
2226 }
2227
2228 dir = screen_geom_edge_is_horizontal(actedge) ? 'v' : 'h';
2229
2230 RNA_property_enum_set(op->ptr, prop_dir, dir);
2231
2232 /* special case, adds customdata, sets defaults */
2233 if (!area_split_menu_init(C, op)) {
2234 return OPERATOR_CANCELLED;
2235 }
2236 }
2237
2238 sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2239
2240 if (event->type == EVT_ACTIONZONE_AREA) {
2241 /* do the split */
2242 if (area_split_apply(C, op)) {
2243 area_move_set_limits(win, screen, dir, &sd->bigger, &sd->smaller, NULL);
2244
2245 /* add temp handler for edge move or cancel */
2246 G.moving |= G_TRANSFORM_WM;
2247 WM_event_add_modal_handler(C, op);
2248
2249 return OPERATOR_RUNNING_MODAL;
2250 }
2251 }
2252 else {
2253 sd->previewmode = 1;
2254 sd->draw_callback = WM_draw_cb_activate(win, area_split_draw_cb, op);
2255 /* add temp handler for edge move or cancel */
2256 WM_event_add_modal_handler(C, op);
2257 area_split_preview_update_cursor(C, op);
2258
2259 return OPERATOR_RUNNING_MODAL;
2260 }
2261
2262 return OPERATOR_PASS_THROUGH;
2263 }
2264
2265 /* function to be called outside UI context, or for redo */
area_split_exec(bContext * C,wmOperator * op)2266 static int area_split_exec(bContext *C, wmOperator *op)
2267 {
2268 if (!area_split_init(C, op)) {
2269 return OPERATOR_CANCELLED;
2270 }
2271
2272 area_split_apply(C, op);
2273 area_split_exit(C, op);
2274
2275 return OPERATOR_FINISHED;
2276 }
2277
area_split_cancel(bContext * C,wmOperator * op)2278 static void area_split_cancel(bContext *C, wmOperator *op)
2279 {
2280 sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2281
2282 if (sd->previewmode) {
2283 /* pass */
2284 }
2285 else {
2286 if (screen_area_join(C, CTX_wm_screen(C), sd->sarea, sd->narea)) {
2287 if (CTX_wm_area(C) == sd->narea) {
2288 CTX_wm_area_set(C, NULL);
2289 CTX_wm_region_set(C, NULL);
2290 }
2291 sd->narea = NULL;
2292 }
2293 }
2294 area_split_exit(C, op);
2295 }
2296
area_split_modal(bContext * C,wmOperator * op,const wmEvent * event)2297 static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event)
2298 {
2299 sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2300 PropertyRNA *prop_dir = RNA_struct_find_property(op->ptr, "direction");
2301 bool update_factor = false;
2302
2303 /* execute the events */
2304 switch (event->type) {
2305 case MOUSEMOVE:
2306 update_factor = true;
2307 break;
2308
2309 case LEFTMOUSE:
2310 if (sd->previewmode) {
2311 area_split_apply(C, op);
2312 area_split_exit(C, op);
2313 return OPERATOR_FINISHED;
2314 }
2315 else {
2316 if (event->val == KM_RELEASE) { /* mouse up */
2317 area_split_exit(C, op);
2318 return OPERATOR_FINISHED;
2319 }
2320 }
2321 break;
2322
2323 case MIDDLEMOUSE:
2324 case EVT_TABKEY:
2325 if (sd->previewmode == 0) {
2326 /* pass */
2327 }
2328 else {
2329 if (event->val == KM_PRESS) {
2330 if (sd->sarea) {
2331 int dir = RNA_property_enum_get(op->ptr, prop_dir);
2332 RNA_property_enum_set(op->ptr, prop_dir, (dir == 'v') ? 'h' : 'v');
2333 area_split_preview_update_cursor(C, op);
2334 update_factor = true;
2335 }
2336 }
2337 }
2338
2339 break;
2340
2341 case RIGHTMOUSE: /* cancel operation */
2342 case EVT_ESCKEY:
2343 area_split_cancel(C, op);
2344 return OPERATOR_CANCELLED;
2345
2346 case EVT_LEFTCTRLKEY:
2347 sd->do_snap = event->val == KM_PRESS;
2348 update_factor = true;
2349 break;
2350 }
2351
2352 if (update_factor) {
2353 const int dir = RNA_property_enum_get(op->ptr, prop_dir);
2354
2355 sd->delta = (dir == 'v') ? event->x - sd->origval : event->y - sd->origval;
2356
2357 if (sd->previewmode == 0) {
2358 if (sd->do_snap) {
2359 const int snap_loc = area_snap_calc_location(CTX_wm_screen(C),
2360 SNAP_FRACTION_AND_ADJACENT,
2361 sd->delta,
2362 sd->origval,
2363 dir,
2364 sd->bigger,
2365 sd->smaller);
2366 sd->delta = snap_loc - sd->origval;
2367 }
2368 area_move_apply_do(C, sd->delta, sd->origval, dir, sd->bigger, sd->smaller, SNAP_NONE);
2369 }
2370 else {
2371 if (sd->sarea) {
2372 ED_area_tag_redraw(sd->sarea);
2373 }
2374 /* area context not set */
2375 sd->sarea = BKE_screen_find_area_xy(CTX_wm_screen(C), SPACE_TYPE_ANY, event->x, event->y);
2376
2377 if (sd->sarea) {
2378 ScrArea *area = sd->sarea;
2379 if (dir == 'v') {
2380 sd->origmin = area->v1->vec.x;
2381 sd->origsize = area->v4->vec.x - sd->origmin;
2382 }
2383 else {
2384 sd->origmin = area->v1->vec.y;
2385 sd->origsize = area->v2->vec.y - sd->origmin;
2386 }
2387
2388 if (sd->do_snap) {
2389 area->v1->editflag = area->v2->editflag = area->v3->editflag = area->v4->editflag = 1;
2390
2391 const int snap_loc = area_snap_calc_location(CTX_wm_screen(C),
2392 SNAP_FRACTION_AND_ADJACENT,
2393 sd->delta,
2394 sd->origval,
2395 dir,
2396 sd->origmin + sd->origsize,
2397 -sd->origmin);
2398
2399 area->v1->editflag = area->v2->editflag = area->v3->editflag = area->v4->editflag = 0;
2400 sd->delta = snap_loc - sd->origval;
2401 }
2402
2403 ED_area_tag_redraw(sd->sarea);
2404 }
2405
2406 CTX_wm_screen(C)->do_draw = true;
2407 }
2408
2409 float fac = (float)(sd->delta + sd->origval - sd->origmin) / sd->origsize;
2410 RNA_float_set(op->ptr, "factor", fac);
2411 }
2412
2413 return OPERATOR_RUNNING_MODAL;
2414 }
2415
2416 static const EnumPropertyItem prop_direction_items[] = {
2417 {'h', "HORIZONTAL", 0, "Horizontal", ""},
2418 {'v', "VERTICAL", 0, "Vertical", ""},
2419 {0, NULL, 0, NULL, NULL},
2420 };
2421
SCREEN_OT_area_split(wmOperatorType * ot)2422 static void SCREEN_OT_area_split(wmOperatorType *ot)
2423 {
2424 ot->name = "Split Area";
2425 ot->description = "Split selected area into new windows";
2426 ot->idname = "SCREEN_OT_area_split";
2427
2428 ot->exec = area_split_exec;
2429 ot->invoke = area_split_invoke;
2430 ot->modal = area_split_modal;
2431 ot->cancel = area_split_cancel;
2432
2433 ot->poll = screen_active_editable;
2434
2435 /* flags */
2436 ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
2437
2438 /* rna */
2439 RNA_def_enum(ot->srna, "direction", prop_direction_items, 'h', "Direction", "");
2440 RNA_def_float(ot->srna, "factor", 0.5f, 0.0, 1.0, "Factor", "", 0.0, 1.0);
2441 RNA_def_int_vector(
2442 ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX);
2443 }
2444
2445 /** \} */
2446
2447 /* -------------------------------------------------------------------- */
2448 /** \name Scale Region Edge Operator
2449 * \{ */
2450
2451 typedef struct RegionMoveData {
2452 AZone *az;
2453 ARegion *region;
2454 ScrArea *area;
2455 int bigger, smaller, origval;
2456 int origx, origy;
2457 int maxsize;
2458 AZEdge edge;
2459
2460 } RegionMoveData;
2461
area_max_regionsize(ScrArea * area,ARegion * scalear,AZEdge edge)2462 static int area_max_regionsize(ScrArea *area, ARegion *scalear, AZEdge edge)
2463 {
2464 int dist;
2465
2466 /* regions in regions. */
2467 if (scalear->alignment & RGN_SPLIT_PREV) {
2468 const int align = RGN_ALIGN_ENUM_FROM_MASK(scalear->alignment);
2469
2470 if (ELEM(align, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
2471 ARegion *region = scalear->prev;
2472 dist = region->winy + scalear->winy - U.pixelsize;
2473 }
2474 else /* if (ELEM(align, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) */ {
2475 ARegion *region = scalear->prev;
2476 dist = region->winx + scalear->winx - U.pixelsize;
2477 }
2478 }
2479 else {
2480 if (edge == AE_RIGHT_TO_TOPLEFT || edge == AE_LEFT_TO_TOPRIGHT) {
2481 dist = BLI_rcti_size_x(&area->totrct);
2482 }
2483 else { /* AE_BOTTOM_TO_TOPLEFT, AE_TOP_TO_BOTTOMRIGHT */
2484 dist = BLI_rcti_size_y(&area->totrct);
2485 }
2486
2487 /* subtractwidth of regions on opposite side
2488 * prevents dragging regions into other opposite regions */
2489 LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
2490 if (region == scalear) {
2491 continue;
2492 }
2493
2494 if (scalear->alignment == RGN_ALIGN_LEFT && region->alignment == RGN_ALIGN_RIGHT) {
2495 dist -= region->winx;
2496 }
2497 else if (scalear->alignment == RGN_ALIGN_RIGHT && region->alignment == RGN_ALIGN_LEFT) {
2498 dist -= region->winx;
2499 }
2500 else if (scalear->alignment == RGN_ALIGN_TOP &&
2501 (region->alignment == RGN_ALIGN_BOTTOM ||
2502 ELEM(
2503 region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) {
2504 dist -= region->winy;
2505 }
2506 else if (scalear->alignment == RGN_ALIGN_BOTTOM &&
2507 (region->alignment == RGN_ALIGN_TOP ||
2508 ELEM(
2509 region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) {
2510 dist -= region->winy;
2511 }
2512 }
2513 }
2514
2515 dist /= UI_DPI_FAC;
2516 return dist;
2517 }
2518
is_split_edge(const int alignment,const AZEdge edge)2519 static bool is_split_edge(const int alignment, const AZEdge edge)
2520 {
2521 return ((alignment == RGN_ALIGN_BOTTOM) && (edge == AE_TOP_TO_BOTTOMRIGHT)) ||
2522 ((alignment == RGN_ALIGN_TOP) && (edge == AE_BOTTOM_TO_TOPLEFT)) ||
2523 ((alignment == RGN_ALIGN_LEFT) && (edge == AE_RIGHT_TO_TOPLEFT)) ||
2524 ((alignment == RGN_ALIGN_RIGHT) && (edge == AE_LEFT_TO_TOPRIGHT));
2525 }
2526
region_scale_exit(wmOperator * op)2527 static void region_scale_exit(wmOperator *op)
2528 {
2529 MEM_freeN(op->customdata);
2530 op->customdata = NULL;
2531
2532 G.moving &= ~G_TRANSFORM_WM;
2533 }
2534
region_scale_invoke(bContext * C,wmOperator * op,const wmEvent * event)2535 static int region_scale_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2536 {
2537 sActionzoneData *sad = event->customdata;
2538
2539 if (event->type != EVT_ACTIONZONE_REGION) {
2540 BKE_report(op->reports, RPT_ERROR, "Can only scale region size from an action zone");
2541 return OPERATOR_CANCELLED;
2542 }
2543
2544 AZone *az = sad->az;
2545
2546 if (az->region) {
2547 RegionMoveData *rmd = MEM_callocN(sizeof(RegionMoveData), "RegionMoveData");
2548
2549 op->customdata = rmd;
2550
2551 rmd->az = az;
2552 /* special case for region within region - this allows the scale of
2553 * the parent region if the azone edge is not the edge splitting
2554 * both regions */
2555 if ((az->region->alignment & RGN_SPLIT_PREV) && az->region->prev &&
2556 !is_split_edge(RGN_ALIGN_ENUM_FROM_MASK(az->region->alignment), az->edge)) {
2557 rmd->region = az->region->prev;
2558 }
2559 else {
2560 rmd->region = az->region;
2561 }
2562 rmd->area = sad->sa1;
2563 rmd->edge = az->edge;
2564 rmd->origx = event->x;
2565 rmd->origy = event->y;
2566 rmd->maxsize = area_max_regionsize(rmd->area, rmd->region, rmd->edge);
2567
2568 /* if not set we do now, otherwise it uses type */
2569 if (rmd->region->sizex == 0) {
2570 rmd->region->sizex = rmd->region->winx;
2571 }
2572 if (rmd->region->sizey == 0) {
2573 rmd->region->sizey = rmd->region->winy;
2574 }
2575
2576 /* now copy to regionmovedata */
2577 if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2578 rmd->origval = rmd->region->sizex;
2579 }
2580 else {
2581 rmd->origval = rmd->region->sizey;
2582 }
2583
2584 CLAMP(rmd->maxsize, 0, 1000);
2585
2586 /* add temp handler */
2587 G.moving |= G_TRANSFORM_WM;
2588 WM_event_add_modal_handler(C, op);
2589
2590 return OPERATOR_RUNNING_MODAL;
2591 }
2592
2593 return OPERATOR_FINISHED;
2594 }
2595
region_scale_validate_size(RegionMoveData * rmd)2596 static void region_scale_validate_size(RegionMoveData *rmd)
2597 {
2598 if ((rmd->region->flag & RGN_FLAG_HIDDEN) == 0) {
2599 short *size, maxsize = -1;
2600
2601 if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2602 size = &rmd->region->sizex;
2603 }
2604 else {
2605 size = &rmd->region->sizey;
2606 }
2607
2608 maxsize = rmd->maxsize - (UI_UNIT_Y / UI_DPI_FAC);
2609
2610 if (*size > maxsize && maxsize > 0) {
2611 *size = maxsize;
2612 }
2613 }
2614 }
2615
region_scale_toggle_hidden(bContext * C,RegionMoveData * rmd)2616 static void region_scale_toggle_hidden(bContext *C, RegionMoveData *rmd)
2617 {
2618 /* hidden areas may have bad 'View2D.cur' value,
2619 * correct before displaying. see T45156 */
2620 if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2621 UI_view2d_curRect_validate(&rmd->region->v2d);
2622 }
2623
2624 region_toggle_hidden(C, rmd->region, 0);
2625 region_scale_validate_size(rmd);
2626
2627 if ((rmd->region->flag & RGN_FLAG_HIDDEN) == 0) {
2628 if (rmd->region->regiontype == RGN_TYPE_HEADER) {
2629 ARegion *region_tool_header = BKE_area_find_region_type(rmd->area, RGN_TYPE_TOOL_HEADER);
2630 if (region_tool_header != NULL) {
2631 if ((region_tool_header->flag & RGN_FLAG_HIDDEN_BY_USER) == 0 &&
2632 (region_tool_header->flag & RGN_FLAG_HIDDEN) != 0) {
2633 region_toggle_hidden(C, region_tool_header, 0);
2634 }
2635 }
2636 }
2637 }
2638 }
2639
region_scale_modal(bContext * C,wmOperator * op,const wmEvent * event)2640 static int region_scale_modal(bContext *C, wmOperator *op, const wmEvent *event)
2641 {
2642 RegionMoveData *rmd = op->customdata;
2643 int delta;
2644
2645 /* execute the events */
2646 switch (event->type) {
2647 case MOUSEMOVE: {
2648 const float aspect = BLI_rctf_size_x(&rmd->region->v2d.cur) /
2649 (BLI_rcti_size_x(&rmd->region->v2d.mask) + 1);
2650 const int snap_size_threshold = (U.widget_unit * 2) / aspect;
2651 if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2652 delta = event->x - rmd->origx;
2653 if (rmd->edge == AE_LEFT_TO_TOPRIGHT) {
2654 delta = -delta;
2655 }
2656
2657 /* region sizes now get multiplied */
2658 delta /= UI_DPI_FAC;
2659
2660 const int size_no_snap = rmd->origval + delta;
2661 rmd->region->sizex = size_no_snap;
2662
2663 if (rmd->region->type->snap_size) {
2664 short sizex_test = rmd->region->type->snap_size(rmd->region, rmd->region->sizex, 0);
2665 if (abs(rmd->region->sizex - sizex_test) < snap_size_threshold) {
2666 rmd->region->sizex = sizex_test;
2667 }
2668 }
2669 CLAMP(rmd->region->sizex, 0, rmd->maxsize);
2670
2671 if (size_no_snap < UI_UNIT_X / aspect) {
2672 rmd->region->sizex = rmd->origval;
2673 if (!(rmd->region->flag & RGN_FLAG_HIDDEN)) {
2674 region_scale_toggle_hidden(C, rmd);
2675 }
2676 }
2677 else if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2678 region_scale_toggle_hidden(C, rmd);
2679 }
2680 else if (rmd->region->flag & RGN_FLAG_DYNAMIC_SIZE) {
2681 rmd->region->sizex = rmd->origval;
2682 }
2683 }
2684 else {
2685 delta = event->y - rmd->origy;
2686 if (rmd->edge == AE_BOTTOM_TO_TOPLEFT) {
2687 delta = -delta;
2688 }
2689
2690 /* region sizes now get multiplied */
2691 delta /= UI_DPI_FAC;
2692
2693 const int size_no_snap = rmd->origval + delta;
2694 rmd->region->sizey = size_no_snap;
2695
2696 if (rmd->region->type->snap_size) {
2697 short sizey_test = rmd->region->type->snap_size(rmd->region, rmd->region->sizey, 1);
2698 if (abs(rmd->region->sizey - sizey_test) < snap_size_threshold) {
2699 rmd->region->sizey = sizey_test;
2700 }
2701 }
2702 CLAMP(rmd->region->sizey, 0, rmd->maxsize);
2703
2704 /* note, 'UI_UNIT_Y/4' means you need to drag the footer and execute region
2705 * almost all the way down for it to become hidden, this is done
2706 * otherwise its too easy to do this by accident */
2707 if (size_no_snap < (UI_UNIT_Y / 4) / aspect) {
2708 rmd->region->sizey = rmd->origval;
2709 if (!(rmd->region->flag & RGN_FLAG_HIDDEN)) {
2710 region_scale_toggle_hidden(C, rmd);
2711 }
2712 }
2713 else if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2714 region_scale_toggle_hidden(C, rmd);
2715 }
2716 else if (rmd->region->flag & RGN_FLAG_DYNAMIC_SIZE) {
2717 rmd->region->sizey = rmd->origval;
2718 }
2719 }
2720 ED_area_tag_redraw(rmd->area);
2721 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2722
2723 break;
2724 }
2725 case LEFTMOUSE:
2726 if (event->val == KM_RELEASE) {
2727 if (len_manhattan_v2v2_int(&event->x, &rmd->origx) <= WM_EVENT_CURSOR_MOTION_THRESHOLD) {
2728 if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2729 region_scale_toggle_hidden(C, rmd);
2730 }
2731 else if (rmd->region->flag & RGN_FLAG_TOO_SMALL) {
2732 region_scale_validate_size(rmd);
2733 }
2734
2735 ED_area_tag_redraw(rmd->area);
2736 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2737 }
2738
2739 region_scale_exit(op);
2740
2741 return OPERATOR_FINISHED;
2742 }
2743 break;
2744
2745 case EVT_ESCKEY:
2746 break;
2747 }
2748
2749 return OPERATOR_RUNNING_MODAL;
2750 }
2751
region_scale_cancel(bContext * UNUSED (C),wmOperator * op)2752 static void region_scale_cancel(bContext *UNUSED(C), wmOperator *op)
2753 {
2754 region_scale_exit(op);
2755 }
2756
SCREEN_OT_region_scale(wmOperatorType * ot)2757 static void SCREEN_OT_region_scale(wmOperatorType *ot)
2758 {
2759 /* identifiers */
2760 ot->name = "Scale Region Size";
2761 ot->description = "Scale selected area";
2762 ot->idname = "SCREEN_OT_region_scale";
2763
2764 ot->invoke = region_scale_invoke;
2765 ot->modal = region_scale_modal;
2766 ot->cancel = region_scale_cancel;
2767
2768 ot->poll = ED_operator_areaactive;
2769
2770 /* flags */
2771 ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
2772 }
2773
2774 /** \} */
2775
2776 /* -------------------------------------------------------------------- */
2777 /** \name Frame Change Operator
2778 * \{ */
2779
areas_do_frame_follow(bContext * C,bool middle)2780 static void areas_do_frame_follow(bContext *C, bool middle)
2781 {
2782 bScreen *screen_ctx = CTX_wm_screen(C);
2783 Scene *scene = CTX_data_scene(C);
2784 wmWindowManager *wm = CTX_wm_manager(C);
2785 LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
2786 const bScreen *screen = WM_window_get_active_screen(window);
2787
2788 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
2789 LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
2790 /* do follow here if editor type supports it */
2791 if ((screen_ctx->redraws_flag & TIME_FOLLOW)) {
2792 if ((region->regiontype == RGN_TYPE_WINDOW &&
2793 ELEM(area->spacetype, SPACE_SEQ, SPACE_GRAPH, SPACE_ACTION, SPACE_NLA)) ||
2794 (area->spacetype == SPACE_CLIP && region->regiontype == RGN_TYPE_PREVIEW)) {
2795 float w = BLI_rctf_size_x(®ion->v2d.cur);
2796
2797 if (middle) {
2798 if ((scene->r.cfra < region->v2d.cur.xmin) ||
2799 (scene->r.cfra > region->v2d.cur.xmax)) {
2800 region->v2d.cur.xmax = scene->r.cfra + (w / 2);
2801 region->v2d.cur.xmin = scene->r.cfra - (w / 2);
2802 }
2803 }
2804 else {
2805 if (scene->r.cfra < region->v2d.cur.xmin) {
2806 region->v2d.cur.xmax = scene->r.cfra;
2807 region->v2d.cur.xmin = region->v2d.cur.xmax - w;
2808 }
2809 else if (scene->r.cfra > region->v2d.cur.xmax) {
2810 region->v2d.cur.xmin = scene->r.cfra;
2811 region->v2d.cur.xmax = region->v2d.cur.xmin + w;
2812 }
2813 }
2814 }
2815 }
2816 }
2817 }
2818 }
2819 }
2820
2821 /* function to be called outside UI context, or for redo */
frame_offset_exec(bContext * C,wmOperator * op)2822 static int frame_offset_exec(bContext *C, wmOperator *op)
2823 {
2824 Scene *scene = CTX_data_scene(C);
2825
2826 int delta = RNA_int_get(op->ptr, "delta");
2827
2828 CFRA += delta;
2829 FRAMENUMBER_MIN_CLAMP(CFRA);
2830 SUBFRA = 0.f;
2831
2832 areas_do_frame_follow(C, false);
2833
2834 DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
2835
2836 WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
2837
2838 return OPERATOR_FINISHED;
2839 }
2840
SCREEN_OT_frame_offset(wmOperatorType * ot)2841 static void SCREEN_OT_frame_offset(wmOperatorType *ot)
2842 {
2843 ot->name = "Frame Offset";
2844 ot->idname = "SCREEN_OT_frame_offset";
2845 ot->description = "Move current frame forward/backward by a given number";
2846
2847 ot->exec = frame_offset_exec;
2848
2849 ot->poll = ED_operator_screenactive_norender;
2850 ot->flag = OPTYPE_UNDO_GROUPED;
2851 ot->undo_group = "Frame Change";
2852
2853 /* rna */
2854 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
2855 }
2856
2857 /** \} */
2858
2859 /* -------------------------------------------------------------------- */
2860 /** \name Frame Jump Operator
2861 * \{ */
2862
2863 /* function to be called outside UI context, or for redo */
frame_jump_exec(bContext * C,wmOperator * op)2864 static int frame_jump_exec(bContext *C, wmOperator *op)
2865 {
2866 Scene *scene = CTX_data_scene(C);
2867 wmTimer *animtimer = CTX_wm_screen(C)->animtimer;
2868
2869 /* Don't change CFRA directly if animtimer is running as this can cause
2870 * first/last frame not to be actually shown (bad since for example physics
2871 * simulations aren't reset properly).
2872 */
2873 if (animtimer) {
2874 ScreenAnimData *sad = animtimer->customdata;
2875
2876 sad->flag |= ANIMPLAY_FLAG_USE_NEXT_FRAME;
2877
2878 if (RNA_boolean_get(op->ptr, "end")) {
2879 sad->nextfra = PEFRA;
2880 }
2881 else {
2882 sad->nextfra = PSFRA;
2883 }
2884 }
2885 else {
2886 if (RNA_boolean_get(op->ptr, "end")) {
2887 CFRA = PEFRA;
2888 }
2889 else {
2890 CFRA = PSFRA;
2891 }
2892
2893 areas_do_frame_follow(C, true);
2894
2895 DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
2896
2897 WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
2898 }
2899
2900 return OPERATOR_FINISHED;
2901 }
2902
SCREEN_OT_frame_jump(wmOperatorType * ot)2903 static void SCREEN_OT_frame_jump(wmOperatorType *ot)
2904 {
2905 ot->name = "Jump to Endpoint";
2906 ot->description = "Jump to first/last frame in frame range";
2907 ot->idname = "SCREEN_OT_frame_jump";
2908
2909 ot->exec = frame_jump_exec;
2910
2911 ot->poll = ED_operator_screenactive_norender;
2912 ot->flag = OPTYPE_UNDO_GROUPED;
2913 ot->undo_group = "Frame Change";
2914
2915 /* rna */
2916 RNA_def_boolean(ot->srna, "end", 0, "Last Frame", "Jump to the last frame of the frame range");
2917 }
2918
2919 /** \} */
2920
2921 /* -------------------------------------------------------------------- */
2922 /** \name Jump to Key-Frame Operator
2923 * \{ */
2924
2925 /* function to be called outside UI context, or for redo */
keyframe_jump_exec(bContext * C,wmOperator * op)2926 static int keyframe_jump_exec(bContext *C, wmOperator *op)
2927 {
2928 Scene *scene = CTX_data_scene(C);
2929 Object *ob = CTX_data_active_object(C);
2930 bDopeSheet ads = {NULL};
2931 const bool next = RNA_boolean_get(op->ptr, "next");
2932 bool done = false;
2933
2934 /* sanity checks */
2935 if (scene == NULL) {
2936 return OPERATOR_CANCELLED;
2937 }
2938
2939 float cfra = (float)(CFRA);
2940
2941 /* init binarytree-list for getting keyframes */
2942 DLRBT_Tree keys;
2943 BLI_dlrbTree_init(&keys);
2944
2945 /* seed up dummy dopesheet context with flags to perform necessary filtering */
2946 if ((scene->flag & SCE_KEYS_NO_SELONLY) == 0) {
2947 /* only selected channels are included */
2948 ads.filterflag |= ADS_FILTER_ONLYSEL;
2949 }
2950
2951 /* populate tree with keyframe nodes */
2952 scene_to_keylist(&ads, scene, &keys, 0);
2953
2954 if (ob) {
2955 ob_to_keylist(&ads, ob, &keys, 0);
2956
2957 if (ob->type == OB_GPENCIL) {
2958 const bool active = !(scene->flag & SCE_KEYS_NO_SELONLY);
2959 gpencil_to_keylist(&ads, ob->data, &keys, active);
2960 }
2961 }
2962
2963 {
2964 Mask *mask = CTX_data_edit_mask(C);
2965 if (mask) {
2966 MaskLayer *masklay = BKE_mask_layer_active(mask);
2967 mask_to_keylist(&ads, masklay, &keys);
2968 }
2969 }
2970
2971 /* find matching keyframe in the right direction */
2972 ActKeyColumn *ak;
2973 if (next) {
2974 ak = (ActKeyColumn *)BLI_dlrbTree_search_next(&keys, compare_ak_cfraPtr, &cfra);
2975 }
2976 else {
2977 ak = (ActKeyColumn *)BLI_dlrbTree_search_prev(&keys, compare_ak_cfraPtr, &cfra);
2978 }
2979
2980 while ((ak != NULL) && (done == false)) {
2981 if (CFRA != (int)ak->cfra) {
2982 /* this changes the frame, so set the frame and we're done */
2983 CFRA = (int)ak->cfra;
2984 done = true;
2985 }
2986 else {
2987 /* take another step... */
2988 if (next) {
2989 ak = ak->next;
2990 }
2991 else {
2992 ak = ak->prev;
2993 }
2994 }
2995 }
2996
2997 /* free temp stuff */
2998 BLI_dlrbTree_free(&keys);
2999
3000 /* any success? */
3001 if (done == false) {
3002 BKE_report(op->reports, RPT_INFO, "No more keyframes to jump to in this direction");
3003
3004 return OPERATOR_CANCELLED;
3005 }
3006
3007 areas_do_frame_follow(C, true);
3008
3009 DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
3010
3011 WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
3012
3013 return OPERATOR_FINISHED;
3014 }
3015
SCREEN_OT_keyframe_jump(wmOperatorType * ot)3016 static void SCREEN_OT_keyframe_jump(wmOperatorType *ot)
3017 {
3018 ot->name = "Jump to Keyframe";
3019 ot->description = "Jump to previous/next keyframe";
3020 ot->idname = "SCREEN_OT_keyframe_jump";
3021
3022 ot->exec = keyframe_jump_exec;
3023
3024 ot->poll = ED_operator_screenactive_norender;
3025 ot->flag = OPTYPE_UNDO_GROUPED;
3026 ot->undo_group = "Frame Change";
3027
3028 /* properties */
3029 RNA_def_boolean(ot->srna, "next", true, "Next Keyframe", "");
3030 }
3031
3032 /** \} */
3033
3034 /* -------------------------------------------------------------------- */
3035 /** \name Jump to Marker Operator
3036 * \{ */
3037
3038 /* function to be called outside UI context, or for redo */
marker_jump_exec(bContext * C,wmOperator * op)3039 static int marker_jump_exec(bContext *C, wmOperator *op)
3040 {
3041 Scene *scene = CTX_data_scene(C);
3042 int closest = CFRA;
3043 const bool next = RNA_boolean_get(op->ptr, "next");
3044 bool found = false;
3045
3046 /* find matching marker in the right direction */
3047 LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
3048 if (next) {
3049 if ((marker->frame > CFRA) && (!found || closest > marker->frame)) {
3050 closest = marker->frame;
3051 found = true;
3052 }
3053 }
3054 else {
3055 if ((marker->frame < CFRA) && (!found || closest < marker->frame)) {
3056 closest = marker->frame;
3057 found = true;
3058 }
3059 }
3060 }
3061
3062 /* any success? */
3063 if (!found) {
3064 BKE_report(op->reports, RPT_INFO, "No more markers to jump to in this direction");
3065
3066 return OPERATOR_CANCELLED;
3067 }
3068
3069 CFRA = closest;
3070
3071 areas_do_frame_follow(C, true);
3072
3073 DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
3074
3075 WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
3076
3077 return OPERATOR_FINISHED;
3078 }
3079
SCREEN_OT_marker_jump(wmOperatorType * ot)3080 static void SCREEN_OT_marker_jump(wmOperatorType *ot)
3081 {
3082 ot->name = "Jump to Marker";
3083 ot->description = "Jump to previous/next marker";
3084 ot->idname = "SCREEN_OT_marker_jump";
3085
3086 ot->exec = marker_jump_exec;
3087
3088 ot->poll = ED_operator_screenactive_norender;
3089 ot->flag = OPTYPE_UNDO_GROUPED;
3090 ot->undo_group = "Frame Change";
3091
3092 /* properties */
3093 RNA_def_boolean(ot->srna, "next", true, "Next Marker", "");
3094 }
3095
3096 /** \} */
3097
3098 /* -------------------------------------------------------------------- */
3099 /** \name Set Screen Operator
3100 * \{ */
3101
3102 /* function to be called outside UI context, or for redo */
screen_set_exec(bContext * C,wmOperator * op)3103 static int screen_set_exec(bContext *C, wmOperator *op)
3104 {
3105 WorkSpace *workspace = CTX_wm_workspace(C);
3106 int delta = RNA_int_get(op->ptr, "delta");
3107
3108 if (ED_workspace_layout_cycle(workspace, delta, C)) {
3109 return OPERATOR_FINISHED;
3110 }
3111
3112 return OPERATOR_CANCELLED;
3113 }
3114
SCREEN_OT_screen_set(wmOperatorType * ot)3115 static void SCREEN_OT_screen_set(wmOperatorType *ot)
3116 {
3117 ot->name = "Set Screen";
3118 ot->description = "Cycle through available screens";
3119 ot->idname = "SCREEN_OT_screen_set";
3120
3121 ot->exec = screen_set_exec;
3122 ot->poll = ED_operator_screenactive;
3123
3124 /* rna */
3125 RNA_def_int(ot->srna, "delta", 1, -1, 1, "Delta", "", -1, 1);
3126 }
3127
3128 /** \} */
3129
3130 /* -------------------------------------------------------------------- */
3131 /** \name Screen Full-Area Operator
3132 * \{ */
3133
3134 /* function to be called outside UI context, or for redo */
screen_maximize_area_exec(bContext * C,wmOperator * op)3135 static int screen_maximize_area_exec(bContext *C, wmOperator *op)
3136 {
3137 bScreen *screen = CTX_wm_screen(C);
3138 ScrArea *area = NULL;
3139 const bool hide_panels = RNA_boolean_get(op->ptr, "use_hide_panels");
3140
3141 /* search current screen for 'fullscreen' areas */
3142 /* prevents restoring info header, when mouse is over it */
3143 LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) {
3144 if (area_iter->full) {
3145 area = area_iter;
3146 break;
3147 }
3148 }
3149
3150 if (area == NULL) {
3151 area = CTX_wm_area(C);
3152 }
3153
3154 if (hide_panels) {
3155 if (!ELEM(screen->state, SCREENNORMAL, SCREENFULL)) {
3156 return OPERATOR_CANCELLED;
3157 }
3158 ED_screen_state_toggle(C, CTX_wm_window(C), area, SCREENFULL);
3159 }
3160 else {
3161 if (!ELEM(screen->state, SCREENNORMAL, SCREENMAXIMIZED)) {
3162 return OPERATOR_CANCELLED;
3163 }
3164 ED_screen_state_toggle(C, CTX_wm_window(C), area, SCREENMAXIMIZED);
3165 }
3166
3167 return OPERATOR_FINISHED;
3168 }
3169
screen_maximize_area_poll(bContext * C)3170 static bool screen_maximize_area_poll(bContext *C)
3171 {
3172 const bScreen *screen = CTX_wm_screen(C);
3173 const ScrArea *area = CTX_wm_area(C);
3174 return ED_operator_areaactive(C) &&
3175 /* Don't allow maximizing global areas but allow minimizing from them. */
3176 ((screen->state != SCREENNORMAL) || !ED_area_is_global(area));
3177 }
3178
SCREEN_OT_screen_full_area(wmOperatorType * ot)3179 static void SCREEN_OT_screen_full_area(wmOperatorType *ot)
3180 {
3181 PropertyRNA *prop;
3182
3183 ot->name = "Toggle Maximize Area";
3184 ot->description = "Toggle display selected area as fullscreen/maximized";
3185 ot->idname = "SCREEN_OT_screen_full_area";
3186
3187 ot->exec = screen_maximize_area_exec;
3188 ot->poll = screen_maximize_area_poll;
3189 ot->flag = 0;
3190
3191 prop = RNA_def_boolean(ot->srna, "use_hide_panels", false, "Hide Panels", "Hide all the panels");
3192 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3193 }
3194
3195 /** \} */
3196
3197 /* -------------------------------------------------------------------- */
3198 /** \name Screen Join-Area Operator
3199 * \{ */
3200
3201 /* operator state vars used:
3202 * x1, y1 mouse coord in first area, which will disappear
3203 * x2, y2 mouse coord in 2nd area, which will become joined
3204 *
3205 * functions:
3206 *
3207 * init() find edge based on state vars
3208 * test if the edge divides two areas,
3209 * store active and nonactive area,
3210 *
3211 * apply() do the actual join
3212 *
3213 * exit() cleanup, send notifier
3214 *
3215 * callbacks:
3216 *
3217 * exec() calls init, apply, exit
3218 *
3219 * invoke() sets mouse coords in x,y
3220 * call init()
3221 * add modal handler
3222 *
3223 * modal() accept modal events while doing it
3224 * call apply() with active window and nonactive window
3225 * call exit() and remove handler when LMB confirm
3226 */
3227
3228 typedef struct sAreaJoinData {
3229 ScrArea *sa1; /* first area to be considered */
3230 ScrArea *sa2; /* second area to be considered */
3231 void *draw_callback; /* call `ED_screen_draw_join_shape` */
3232
3233 } sAreaJoinData;
3234
area_join_draw_cb(const struct wmWindow * UNUSED (win),void * userdata)3235 static void area_join_draw_cb(const struct wmWindow *UNUSED(win), void *userdata)
3236 {
3237 const wmOperator *op = userdata;
3238
3239 sAreaJoinData *sd = op->customdata;
3240 if (sd->sa1 && sd->sa2) {
3241 ED_screen_draw_join_shape(sd->sa1, sd->sa2);
3242 }
3243 }
3244
3245 /* validate selection inside screen, set variables OK */
3246 /* return false: init failed */
area_join_init(bContext * C,wmOperator * op,ScrArea * sa1,ScrArea * sa2)3247 static bool area_join_init(bContext *C, wmOperator *op, ScrArea *sa1, ScrArea *sa2)
3248 {
3249 if (sa1 == NULL || sa2 == NULL) {
3250 /* Get areas from cursor location if not specified. */
3251 int cursor[2];
3252 RNA_int_get_array(op->ptr, "cursor", cursor);
3253 screen_area_edge_from_cursor(C, cursor, &sa1, &sa2);
3254 }
3255 if (sa1 == NULL || sa2 == NULL) {
3256 return false;
3257 }
3258
3259 sAreaJoinData *jd = MEM_callocN(sizeof(sAreaJoinData), "op_area_join");
3260
3261 jd->sa1 = sa1;
3262 jd->sa2 = sa2;
3263
3264 op->customdata = jd;
3265
3266 jd->draw_callback = WM_draw_cb_activate(CTX_wm_window(C), area_join_draw_cb, op);
3267
3268 return true;
3269 }
3270
3271 /* apply the join of the areas (space types) */
area_join_apply(bContext * C,wmOperator * op)3272 static bool area_join_apply(bContext *C, wmOperator *op)
3273 {
3274 sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3275 if (!jd) {
3276 return false;
3277 }
3278
3279 if (!screen_area_join(C, CTX_wm_screen(C), jd->sa1, jd->sa2)) {
3280 return false;
3281 }
3282 if (CTX_wm_area(C) == jd->sa2) {
3283 CTX_wm_area_set(C, NULL);
3284 CTX_wm_region_set(C, NULL);
3285 }
3286
3287 return true;
3288 }
3289
3290 /* finish operation */
area_join_exit(bContext * C,wmOperator * op)3291 static void area_join_exit(bContext *C, wmOperator *op)
3292 {
3293 sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3294
3295 if (jd) {
3296 if (jd->draw_callback) {
3297 WM_draw_cb_exit(CTX_wm_window(C), jd->draw_callback);
3298 }
3299
3300 MEM_freeN(jd);
3301 op->customdata = NULL;
3302 }
3303
3304 /* this makes sure aligned edges will result in aligned grabbing */
3305 BKE_screen_remove_double_scredges(CTX_wm_screen(C));
3306 BKE_screen_remove_unused_scredges(CTX_wm_screen(C));
3307 BKE_screen_remove_unused_scrverts(CTX_wm_screen(C));
3308 }
3309
area_join_exec(bContext * C,wmOperator * op)3310 static int area_join_exec(bContext *C, wmOperator *op)
3311 {
3312 if (!area_join_init(C, op, NULL, NULL)) {
3313 return OPERATOR_CANCELLED;
3314 }
3315
3316 area_join_apply(C, op);
3317 area_join_exit(C, op);
3318
3319 return OPERATOR_FINISHED;
3320 }
3321
3322 /* interaction callback */
area_join_invoke(bContext * C,wmOperator * op,const wmEvent * event)3323 static int area_join_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3324 {
3325 if (event->type == EVT_ACTIONZONE_AREA) {
3326 sActionzoneData *sad = event->customdata;
3327
3328 if (sad == NULL || sad->modifier > 0) {
3329 return OPERATOR_PASS_THROUGH;
3330 }
3331
3332 /* verify *sad itself */
3333 if (sad->sa1 == NULL || sad->sa2 == NULL) {
3334 return OPERATOR_PASS_THROUGH;
3335 }
3336
3337 /* is this our *sad? if areas equal it should be passed on */
3338 if (sad->sa1 == sad->sa2) {
3339 return OPERATOR_PASS_THROUGH;
3340 }
3341 if (!area_join_init(C, op, sad->sa1, sad->sa2)) {
3342 return OPERATOR_CANCELLED;
3343 }
3344 }
3345
3346 /* add temp handler */
3347 WM_event_add_modal_handler(C, op);
3348
3349 return OPERATOR_RUNNING_MODAL;
3350 }
3351
area_join_cancel(bContext * C,wmOperator * op)3352 static void area_join_cancel(bContext *C, wmOperator *op)
3353 {
3354 WM_event_add_notifier(C, NC_WINDOW, NULL);
3355
3356 area_join_exit(C, op);
3357 }
3358
3359 /* modal callback while selecting area (space) that will be removed */
area_join_modal(bContext * C,wmOperator * op,const wmEvent * event)3360 static int area_join_modal(bContext *C, wmOperator *op, const wmEvent *event)
3361 {
3362 bScreen *screen = CTX_wm_screen(C);
3363 wmWindow *win = CTX_wm_window(C);
3364
3365 if (op->customdata == NULL) {
3366 if (!area_join_init(C, op, NULL, NULL)) {
3367 return OPERATOR_CANCELLED;
3368 }
3369 }
3370 sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3371
3372 /* execute the events */
3373 switch (event->type) {
3374
3375 case MOUSEMOVE: {
3376 ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y);
3377 int dir = -1;
3378
3379 if (area) {
3380 if (jd->sa1 != area) {
3381 dir = area_getorientation(jd->sa1, area);
3382 if (dir != -1) {
3383 jd->sa2 = area;
3384 }
3385 else {
3386 /* we are not bordering on the previously selected area
3387 * we check if area has common border with the one marked for removal
3388 * in this case we can swap areas.
3389 */
3390 dir = area_getorientation(area, jd->sa2);
3391 if (dir != -1) {
3392 jd->sa1 = jd->sa2;
3393 jd->sa2 = area;
3394 }
3395 else {
3396 jd->sa2 = NULL;
3397 }
3398 }
3399 WM_event_add_notifier(C, NC_WINDOW, NULL);
3400 }
3401 else {
3402 /* we are back in the area previously selected for keeping
3403 * we swap the areas if possible to allow user to choose */
3404 if (jd->sa2 != NULL) {
3405 jd->sa1 = jd->sa2;
3406 jd->sa2 = area;
3407 dir = area_getorientation(jd->sa1, jd->sa2);
3408 if (dir == -1) {
3409 printf("oops, didn't expect that!\n");
3410 }
3411 }
3412 else {
3413 dir = area_getorientation(jd->sa1, area);
3414 if (dir != -1) {
3415 jd->sa2 = area;
3416 }
3417 }
3418 WM_event_add_notifier(C, NC_WINDOW, NULL);
3419 }
3420 }
3421
3422 if (dir == 1) {
3423 WM_cursor_set(win, WM_CURSOR_N_ARROW);
3424 }
3425 else if (dir == 3) {
3426 WM_cursor_set(win, WM_CURSOR_S_ARROW);
3427 }
3428 else if (dir == 2) {
3429 WM_cursor_set(win, WM_CURSOR_E_ARROW);
3430 }
3431 else if (dir == 0) {
3432 WM_cursor_set(win, WM_CURSOR_W_ARROW);
3433 }
3434 else {
3435 WM_cursor_set(win, WM_CURSOR_STOP);
3436 }
3437
3438 break;
3439 }
3440 case LEFTMOUSE:
3441 if (event->val == KM_RELEASE) {
3442 ED_area_tag_redraw(jd->sa1);
3443 ED_area_tag_redraw(jd->sa2);
3444
3445 area_join_apply(C, op);
3446 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3447 area_join_exit(C, op);
3448 return OPERATOR_FINISHED;
3449 }
3450 break;
3451
3452 case RIGHTMOUSE:
3453 case EVT_ESCKEY:
3454 area_join_cancel(C, op);
3455 return OPERATOR_CANCELLED;
3456 }
3457
3458 return OPERATOR_RUNNING_MODAL;
3459 }
3460
3461 /* Operator for joining two areas (space types) */
SCREEN_OT_area_join(wmOperatorType * ot)3462 static void SCREEN_OT_area_join(wmOperatorType *ot)
3463 {
3464 /* identifiers */
3465 ot->name = "Join Area";
3466 ot->description = "Join selected areas into new window";
3467 ot->idname = "SCREEN_OT_area_join";
3468
3469 /* api callbacks */
3470 ot->exec = area_join_exec;
3471 ot->invoke = area_join_invoke;
3472 ot->modal = area_join_modal;
3473 ot->poll = screen_active_editable;
3474 ot->cancel = area_join_cancel;
3475
3476 /* flags */
3477 ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
3478
3479 /* rna */
3480 RNA_def_int_vector(
3481 ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX);
3482 }
3483
3484 /** \} */
3485
3486 /* -------------------------------------------------------------------- */
3487 /** \name Screen Area Options Operator
3488 * \{ */
3489
screen_area_options_invoke(bContext * C,wmOperator * op,const wmEvent * event)3490 static int screen_area_options_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3491 {
3492 ScrArea *sa1, *sa2;
3493 if (screen_area_edge_from_cursor(C, &event->x, &sa1, &sa2) == NULL) {
3494 return OPERATOR_CANCELLED;
3495 }
3496
3497 uiPopupMenu *pup = UI_popup_menu_begin(C, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
3498 uiLayout *layout = UI_popup_menu_layout(pup);
3499
3500 /* Vertical Split */
3501 PointerRNA ptr;
3502 uiItemFullO(layout,
3503 "SCREEN_OT_area_split",
3504 IFACE_("Vertical Split"),
3505 ICON_NONE,
3506 NULL,
3507 WM_OP_INVOKE_DEFAULT,
3508 0,
3509 &ptr);
3510 /* store initial mouse cursor position. */
3511 RNA_int_set_array(&ptr, "cursor", &event->x);
3512 RNA_enum_set(&ptr, "direction", 'v');
3513
3514 /* Horizontal Split */
3515 uiItemFullO(layout,
3516 "SCREEN_OT_area_split",
3517 IFACE_("Horizontal Split"),
3518 ICON_NONE,
3519 NULL,
3520 WM_OP_INVOKE_DEFAULT,
3521 0,
3522 &ptr);
3523 /* store initial mouse cursor position. */
3524 RNA_int_set_array(&ptr, "cursor", &event->x);
3525 RNA_enum_set(&ptr, "direction", 'h');
3526
3527 if (sa1 && sa2) {
3528 uiItemS(layout);
3529 }
3530
3531 /* Join needs two very similar areas. */
3532 if (sa1 && sa2 && (area_getorientation(sa1, sa2) != -1)) {
3533 uiItemFullO(layout,
3534 "SCREEN_OT_area_join",
3535 IFACE_("Join Areas"),
3536 ICON_NONE,
3537 NULL,
3538 WM_OP_INVOKE_DEFAULT,
3539 0,
3540 &ptr);
3541 RNA_int_set_array(&ptr, "cursor", &event->x);
3542 }
3543
3544 /* Swap just needs two areas. */
3545 if (sa1 && sa2) {
3546 uiItemFullO(layout,
3547 "SCREEN_OT_area_swap",
3548 IFACE_("Swap Areas"),
3549 ICON_NONE,
3550 NULL,
3551 WM_OP_EXEC_DEFAULT,
3552 0,
3553 &ptr);
3554 RNA_int_set_array(&ptr, "cursor", &event->x);
3555 }
3556
3557 UI_popup_menu_end(C, pup);
3558
3559 return OPERATOR_INTERFACE;
3560 }
3561
SCREEN_OT_area_options(wmOperatorType * ot)3562 static void SCREEN_OT_area_options(wmOperatorType *ot)
3563 {
3564 /* identifiers */
3565 ot->name = "Area Options";
3566 ot->description = "Operations for splitting and merging";
3567 ot->idname = "SCREEN_OT_area_options";
3568
3569 /* api callbacks */
3570 ot->invoke = screen_area_options_invoke;
3571
3572 ot->poll = ED_operator_screen_mainwinactive;
3573
3574 /* flags */
3575 ot->flag = OPTYPE_INTERNAL;
3576 }
3577
3578 /** \} */
3579
3580 /* -------------------------------------------------------------------- */
3581 /** \name Space Data Cleanup Operator
3582 * \{ */
3583
spacedata_cleanup_exec(bContext * C,wmOperator * op)3584 static int spacedata_cleanup_exec(bContext *C, wmOperator *op)
3585 {
3586 Main *bmain = CTX_data_main(C);
3587 int tot = 0;
3588
3589 LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
3590 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
3591 if (area->spacedata.first != area->spacedata.last) {
3592 SpaceLink *sl = area->spacedata.first;
3593
3594 BLI_remlink(&area->spacedata, sl);
3595 tot += BLI_listbase_count(&area->spacedata);
3596 BKE_spacedata_freelist(&area->spacedata);
3597 BLI_addtail(&area->spacedata, sl);
3598 }
3599 }
3600 }
3601 BKE_reportf(op->reports, RPT_INFO, "Removed amount of editors: %d", tot);
3602
3603 return OPERATOR_FINISHED;
3604 }
3605
SCREEN_OT_spacedata_cleanup(wmOperatorType * ot)3606 static void SCREEN_OT_spacedata_cleanup(wmOperatorType *ot)
3607 {
3608 /* identifiers */
3609 ot->name = "Clean-up Space-data";
3610 ot->description = "Remove unused settings for invisible editors";
3611 ot->idname = "SCREEN_OT_spacedata_cleanup";
3612
3613 /* api callbacks */
3614 ot->exec = spacedata_cleanup_exec;
3615 ot->poll = WM_operator_winactive;
3616 }
3617
3618 /** \} */
3619
3620 /* -------------------------------------------------------------------- */
3621 /** \name Repeat Last Operator
3622 * \{ */
3623
repeat_history_poll(bContext * C)3624 static bool repeat_history_poll(bContext *C)
3625 {
3626 if (!ED_operator_screenactive(C)) {
3627 return false;
3628 }
3629 wmWindowManager *wm = CTX_wm_manager(C);
3630 return !BLI_listbase_is_empty(&wm->operators);
3631 }
3632
repeat_last_exec(bContext * C,wmOperator * UNUSED (op))3633 static int repeat_last_exec(bContext *C, wmOperator *UNUSED(op))
3634 {
3635 wmWindowManager *wm = CTX_wm_manager(C);
3636 wmOperator *lastop = wm->operators.last;
3637
3638 /* Seek last registered operator */
3639 while (lastop) {
3640 if (lastop->type->flag & OPTYPE_REGISTER) {
3641 break;
3642 }
3643 lastop = lastop->prev;
3644 }
3645
3646 if (lastop) {
3647 WM_operator_free_all_after(wm, lastop);
3648 WM_operator_repeat_last(C, lastop);
3649 }
3650
3651 return OPERATOR_CANCELLED;
3652 }
3653
SCREEN_OT_repeat_last(wmOperatorType * ot)3654 static void SCREEN_OT_repeat_last(wmOperatorType *ot)
3655 {
3656 /* identifiers */
3657 ot->name = "Repeat Last";
3658 ot->description = "Repeat last action";
3659 ot->idname = "SCREEN_OT_repeat_last";
3660
3661 /* api callbacks */
3662 ot->exec = repeat_last_exec;
3663
3664 ot->poll = repeat_history_poll;
3665 }
3666
3667 /** \} */
3668
3669 /* -------------------------------------------------------------------- */
3670 /** \name Repeat History Operator
3671 * \{ */
3672
repeat_history_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))3673 static int repeat_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
3674 {
3675 wmWindowManager *wm = CTX_wm_manager(C);
3676
3677 int items = BLI_listbase_count(&wm->operators);
3678 if (items == 0) {
3679 return OPERATOR_CANCELLED;
3680 }
3681
3682 uiPopupMenu *pup = UI_popup_menu_begin(C, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
3683 uiLayout *layout = UI_popup_menu_layout(pup);
3684
3685 wmOperator *lastop;
3686 int i;
3687 for (i = items - 1, lastop = wm->operators.last; lastop; lastop = lastop->prev, i--) {
3688 if ((lastop->type->flag & OPTYPE_REGISTER) && WM_operator_repeat_check(C, lastop)) {
3689 uiItemIntO(layout,
3690 WM_operatortype_name(lastop->type, lastop->ptr),
3691 ICON_NONE,
3692 op->type->idname,
3693 "index",
3694 i);
3695 }
3696 }
3697
3698 UI_popup_menu_end(C, pup);
3699
3700 return OPERATOR_INTERFACE;
3701 }
3702
repeat_history_exec(bContext * C,wmOperator * op)3703 static int repeat_history_exec(bContext *C, wmOperator *op)
3704 {
3705 wmWindowManager *wm = CTX_wm_manager(C);
3706
3707 op = BLI_findlink(&wm->operators, RNA_int_get(op->ptr, "index"));
3708 if (op) {
3709 /* let's put it as last operator in list */
3710 BLI_remlink(&wm->operators, op);
3711 BLI_addtail(&wm->operators, op);
3712
3713 WM_operator_repeat(C, op);
3714 }
3715
3716 return OPERATOR_FINISHED;
3717 }
3718
SCREEN_OT_repeat_history(wmOperatorType * ot)3719 static void SCREEN_OT_repeat_history(wmOperatorType *ot)
3720 {
3721 /* identifiers */
3722 ot->name = "Repeat History";
3723 ot->description = "Display menu for previous actions performed";
3724 ot->idname = "SCREEN_OT_repeat_history";
3725
3726 /* api callbacks */
3727 ot->invoke = repeat_history_invoke;
3728 ot->exec = repeat_history_exec;
3729 ot->poll = repeat_history_poll;
3730
3731 RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
3732 }
3733
3734 /** \} */
3735
3736 /* -------------------------------------------------------------------- */
3737 /** \name Redo Operator
3738 * \{ */
3739
redo_last_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * UNUSED (event))3740 static int redo_last_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event))
3741 {
3742 wmOperator *lastop = WM_operator_last_redo(C);
3743
3744 if (lastop) {
3745 WM_operator_redo_popup(C, lastop);
3746 }
3747
3748 return OPERATOR_CANCELLED;
3749 }
3750
SCREEN_OT_redo_last(wmOperatorType * ot)3751 static void SCREEN_OT_redo_last(wmOperatorType *ot)
3752 {
3753 /* identifiers */
3754 ot->name = "Redo Last";
3755 ot->description = "Display parameters for last action performed";
3756 ot->idname = "SCREEN_OT_redo_last";
3757
3758 /* api callbacks */
3759 ot->invoke = redo_last_invoke;
3760 ot->poll = repeat_history_poll;
3761 }
3762
3763 /** \} */
3764
3765 /* -------------------------------------------------------------------- */
3766 /** \name Region Quad-View Operator
3767 * \{ */
3768
view3d_localview_update_rv3d(struct RegionView3D * rv3d)3769 static void view3d_localview_update_rv3d(struct RegionView3D *rv3d)
3770 {
3771 if (rv3d->localvd) {
3772 rv3d->localvd->view = rv3d->view;
3773 rv3d->localvd->persp = rv3d->persp;
3774 copy_qt_qt(rv3d->localvd->viewquat, rv3d->viewquat);
3775 }
3776 }
3777
region_quadview_init_rv3d(ScrArea * area,ARegion * region,const char viewlock,const char view,const char persp)3778 static void region_quadview_init_rv3d(
3779 ScrArea *area, ARegion *region, const char viewlock, const char view, const char persp)
3780 {
3781 RegionView3D *rv3d = region->regiondata;
3782
3783 if (persp == RV3D_CAMOB) {
3784 ED_view3d_lastview_store(rv3d);
3785 }
3786
3787 rv3d->viewlock = viewlock;
3788 rv3d->runtime_viewlock = 0;
3789 rv3d->view = view;
3790 rv3d->view_axis_roll = RV3D_VIEW_AXIS_ROLL_0;
3791 rv3d->persp = persp;
3792
3793 ED_view3d_lock(rv3d);
3794 view3d_localview_update_rv3d(rv3d);
3795 if ((viewlock & RV3D_BOXCLIP) && (persp == RV3D_ORTHO)) {
3796 ED_view3d_quadview_update(area, region, true);
3797 }
3798 }
3799
3800 /* insert a region in the area region list */
region_quadview_exec(bContext * C,wmOperator * op)3801 static int region_quadview_exec(bContext *C, wmOperator *op)
3802 {
3803 ARegion *region = CTX_wm_region(C);
3804
3805 /* some rules... */
3806 if (region->regiontype != RGN_TYPE_WINDOW) {
3807 BKE_report(op->reports, RPT_ERROR, "Only window region can be 4-split");
3808 }
3809 else if (region->alignment == RGN_ALIGN_QSPLIT) {
3810 /* Exit quad-view */
3811 ScrArea *area = CTX_wm_area(C);
3812
3813 /* keep current region */
3814 region->alignment = 0;
3815
3816 if (area->spacetype == SPACE_VIEW3D) {
3817 RegionView3D *rv3d = region->regiondata;
3818
3819 /* if this is a locked view, use settings from 'User' view */
3820 if (rv3d->viewlock) {
3821 View3D *v3d_user;
3822 ARegion *region_user;
3823
3824 if (ED_view3d_context_user_region(C, &v3d_user, ®ion_user)) {
3825 if (region != region_user) {
3826 SWAP(void *, region->regiondata, region_user->regiondata);
3827 rv3d = region->regiondata;
3828 }
3829 }
3830 }
3831
3832 rv3d->viewlock_quad = RV3D_VIEWLOCK_INIT;
3833 rv3d->viewlock = 0;
3834
3835 /* FIXME: This fixes missing update to workbench TAA. (see T76216)
3836 * However, it would be nice if the tagging should be done in a more conventional way. */
3837 rv3d->rflag |= RV3D_GPULIGHT_UPDATE;
3838
3839 /* Accumulate locks, in case they're mixed. */
3840 LISTBASE_FOREACH (ARegion *, region_iter, &area->regionbase) {
3841 if (region_iter->regiontype == RGN_TYPE_WINDOW) {
3842 RegionView3D *rv3d_iter = region_iter->regiondata;
3843 rv3d->viewlock_quad |= rv3d_iter->viewlock;
3844 }
3845 }
3846 }
3847
3848 LISTBASE_FOREACH_MUTABLE (ARegion *, region_iter, &area->regionbase) {
3849 if (region_iter->alignment == RGN_ALIGN_QSPLIT) {
3850 ED_region_remove(C, area, region_iter);
3851 }
3852 }
3853 ED_area_tag_redraw(area);
3854 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3855 }
3856 else if (region->next) {
3857 BKE_report(op->reports, RPT_ERROR, "Only last region can be 4-split");
3858 }
3859 else {
3860 /* Enter quad-view */
3861 ScrArea *area = CTX_wm_area(C);
3862
3863 region->alignment = RGN_ALIGN_QSPLIT;
3864
3865 for (int count = 0; count < 3; count++) {
3866 ARegion *new_region = BKE_area_region_copy(area->type, region);
3867 BLI_addtail(&area->regionbase, new_region);
3868 }
3869
3870 /* lock views and set them */
3871 if (area->spacetype == SPACE_VIEW3D) {
3872 View3D *v3d = area->spacedata.first;
3873 int index_qsplit = 0;
3874
3875 /* run ED_view3d_lock() so the correct 'rv3d->viewquat' is set,
3876 * otherwise when restoring rv3d->localvd the 'viewquat' won't
3877 * match the 'view', set on entering localview See: T26315,
3878 *
3879 * We could avoid manipulating rv3d->localvd here if exiting
3880 * localview with a 4-split would assign these view locks */
3881 RegionView3D *rv3d = region->regiondata;
3882 const char viewlock = (rv3d->viewlock_quad & RV3D_VIEWLOCK_INIT) ?
3883 (rv3d->viewlock_quad & ~RV3D_VIEWLOCK_INIT) :
3884 RV3D_LOCK_ROTATION;
3885
3886 region_quadview_init_rv3d(
3887 area, region, viewlock, ED_view3d_lock_view_from_index(index_qsplit++), RV3D_ORTHO);
3888 region_quadview_init_rv3d(area,
3889 (region = region->next),
3890 viewlock,
3891 ED_view3d_lock_view_from_index(index_qsplit++),
3892 RV3D_ORTHO);
3893 region_quadview_init_rv3d(area,
3894 (region = region->next),
3895 viewlock,
3896 ED_view3d_lock_view_from_index(index_qsplit++),
3897 RV3D_ORTHO);
3898 /* forcing camera is distracting */
3899 #if 0
3900 if (v3d->camera) {
3901 region_quadview_init_rv3d(area, (region = region->next), 0, RV3D_VIEW_CAMERA, RV3D_CAMOB);
3902 }
3903 else {
3904 region_quadview_init_rv3d(area, (region = region->next), 0, RV3D_VIEW_USER, RV3D_PERSP);
3905 }
3906 #else
3907 (void)v3d;
3908 #endif
3909 }
3910 ED_area_tag_redraw(area);
3911 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3912 }
3913
3914 return OPERATOR_FINISHED;
3915 }
3916
SCREEN_OT_region_quadview(wmOperatorType * ot)3917 static void SCREEN_OT_region_quadview(wmOperatorType *ot)
3918 {
3919 /* identifiers */
3920 ot->name = "Toggle Quad View";
3921 ot->description = "Split selected area into camera, front, right & top views";
3922 ot->idname = "SCREEN_OT_region_quadview";
3923
3924 /* api callbacks */
3925 ot->exec = region_quadview_exec;
3926 ot->poll = ED_operator_region_view3d_active;
3927 ot->flag = 0;
3928 }
3929
3930 /** \} */
3931
3932 /* -------------------------------------------------------------------- */
3933 /** \name Region Toggle Operator
3934 * \{ */
3935
region_toggle_exec(bContext * C,wmOperator * op)3936 static int region_toggle_exec(bContext *C, wmOperator *op)
3937 {
3938 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "region_type");
3939
3940 ARegion *region;
3941 if (RNA_property_is_set(op->ptr, prop)) {
3942 region = BKE_area_find_region_type(CTX_wm_area(C), RNA_property_enum_get(op->ptr, prop));
3943 }
3944 else {
3945 region = CTX_wm_region(C);
3946 }
3947
3948 if (region && (region->alignment != RGN_ALIGN_NONE)) {
3949 ED_region_toggle_hidden(C, region);
3950 }
3951 ED_region_tag_redraw(region);
3952
3953 return OPERATOR_FINISHED;
3954 }
3955
region_toggle_poll(bContext * C)3956 static bool region_toggle_poll(bContext *C)
3957 {
3958 ScrArea *area = CTX_wm_area(C);
3959
3960 /* Don't flip anything around in top-bar. */
3961 if (area && area->spacetype == SPACE_TOPBAR) {
3962 CTX_wm_operator_poll_msg_set(C, "Toggling regions in the Top-bar is not allowed");
3963 return false;
3964 }
3965
3966 return ED_operator_areaactive(C);
3967 }
3968
SCREEN_OT_region_toggle(wmOperatorType * ot)3969 static void SCREEN_OT_region_toggle(wmOperatorType *ot)
3970 {
3971 /* identifiers */
3972 ot->name = "Toggle Region";
3973 ot->idname = "SCREEN_OT_region_toggle";
3974 ot->description = "Hide or unhide the region";
3975
3976 /* api callbacks */
3977 ot->exec = region_toggle_exec;
3978 ot->poll = region_toggle_poll;
3979 ot->flag = 0;
3980
3981 RNA_def_enum(ot->srna,
3982 "region_type",
3983 rna_enum_region_type_items,
3984 0,
3985 "Region Type",
3986 "Type of the region to toggle");
3987 }
3988
3989 /** \} */
3990
3991 /* -------------------------------------------------------------------- */
3992 /** \name Region Flip Operator
3993 * \{ */
3994
3995 /* flip a region alignment */
region_flip_exec(bContext * C,wmOperator * UNUSED (op))3996 static int region_flip_exec(bContext *C, wmOperator *UNUSED(op))
3997 {
3998 ARegion *region = CTX_wm_region(C);
3999
4000 if (!region) {
4001 return OPERATOR_CANCELLED;
4002 }
4003
4004 if (region->alignment == RGN_ALIGN_TOP) {
4005 region->alignment = RGN_ALIGN_BOTTOM;
4006 }
4007 else if (region->alignment == RGN_ALIGN_BOTTOM) {
4008 region->alignment = RGN_ALIGN_TOP;
4009 }
4010 else if (region->alignment == RGN_ALIGN_LEFT) {
4011 region->alignment = RGN_ALIGN_RIGHT;
4012 }
4013 else if (region->alignment == RGN_ALIGN_RIGHT) {
4014 region->alignment = RGN_ALIGN_LEFT;
4015 }
4016
4017 ED_area_tag_redraw(CTX_wm_area(C));
4018 WM_event_add_mousemove(CTX_wm_window(C));
4019 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
4020
4021 return OPERATOR_FINISHED;
4022 }
4023
region_flip_poll(bContext * C)4024 static bool region_flip_poll(bContext *C)
4025 {
4026 ScrArea *area = CTX_wm_area(C);
4027
4028 /* Don't flip anything around in top-bar. */
4029 if (area && area->spacetype == SPACE_TOPBAR) {
4030 CTX_wm_operator_poll_msg_set(C, "Flipping regions in the Top-bar is not allowed");
4031 return 0;
4032 }
4033
4034 return ED_operator_areaactive(C);
4035 }
4036
SCREEN_OT_region_flip(wmOperatorType * ot)4037 static void SCREEN_OT_region_flip(wmOperatorType *ot)
4038 {
4039 /* identifiers */
4040 ot->name = "Flip Region";
4041 ot->idname = "SCREEN_OT_region_flip";
4042 ot->description = "Toggle the region's alignment (left/right or top/bottom)";
4043
4044 /* api callbacks */
4045 ot->exec = region_flip_exec;
4046 ot->poll = region_flip_poll;
4047 ot->flag = 0;
4048 }
4049
4050 /** \} */
4051
4052 /* -------------------------------------------------------------------- */
4053 /** \name Header Toggle Menu Operator
4054 * \{ */
4055
4056 /* show/hide header text menus */
header_toggle_menus_exec(bContext * C,wmOperator * UNUSED (op))4057 static int header_toggle_menus_exec(bContext *C, wmOperator *UNUSED(op))
4058 {
4059 ScrArea *area = CTX_wm_area(C);
4060
4061 area->flag = area->flag ^ HEADER_NO_PULLDOWN;
4062
4063 ED_area_tag_redraw(area);
4064 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
4065
4066 return OPERATOR_FINISHED;
4067 }
4068
SCREEN_OT_header_toggle_menus(wmOperatorType * ot)4069 static void SCREEN_OT_header_toggle_menus(wmOperatorType *ot)
4070 {
4071 /* identifiers */
4072 ot->name = "Expand/Collapse Header Menus";
4073 ot->idname = "SCREEN_OT_header_toggle_menus";
4074 ot->description = "Expand or collapse the header pulldown menus";
4075
4076 /* api callbacks */
4077 ot->exec = header_toggle_menus_exec;
4078 ot->poll = ED_operator_areaactive;
4079 ot->flag = 0;
4080 }
4081
4082 /** \} */
4083
4084 /* -------------------------------------------------------------------- */
4085 /** \name Region Context Menu Operator (Header/Footer/Navbar)
4086 * \{ */
4087
ED_screens_header_tools_menu_create(bContext * C,uiLayout * layout,void * UNUSED (arg))4088 void ED_screens_header_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg))
4089 {
4090 ScrArea *area = CTX_wm_area(C);
4091 ARegion *region = CTX_wm_region(C);
4092 const char *but_flip_str = (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_TOP) ?
4093 IFACE_("Flip to Bottom") :
4094 IFACE_("Flip to Top");
4095 {
4096 PointerRNA ptr;
4097 RNA_pointer_create((ID *)CTX_wm_screen(C), &RNA_Space, area->spacedata.first, &ptr);
4098 if (!ELEM(area->spacetype, SPACE_TOPBAR)) {
4099 uiItemR(layout, &ptr, "show_region_header", 0, IFACE_("Show Header"), ICON_NONE);
4100 }
4101
4102 ARegion *region_header = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
4103 uiLayout *col = uiLayoutColumn(layout, 0);
4104 uiLayoutSetActive(col, (region_header->flag & RGN_FLAG_HIDDEN) == 0);
4105
4106 if (BKE_area_find_region_type(area, RGN_TYPE_TOOL_HEADER)) {
4107 uiItemR(col, &ptr, "show_region_tool_header", 0, IFACE_("Show Tool Settings"), ICON_NONE);
4108 }
4109
4110 uiItemO(col,
4111 IFACE_("Show Menus"),
4112 (area->flag & HEADER_NO_PULLDOWN) ? ICON_CHECKBOX_DEHLT : ICON_CHECKBOX_HLT,
4113 "SCREEN_OT_header_toggle_menus");
4114 }
4115
4116 /* default is WM_OP_INVOKE_REGION_WIN, which we don't want here. */
4117 uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
4118
4119 if (!ELEM(area->spacetype, SPACE_TOPBAR)) {
4120 uiItemS(layout);
4121
4122 uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip");
4123 }
4124
4125 /* File browser should be fullscreen all the time, top-bar should
4126 * never be. But other regions can be maximized/restored. */
4127 if (!ELEM(area->spacetype, SPACE_FILE, SPACE_TOPBAR)) {
4128 uiItemS(layout);
4129
4130 const char *but_str = area->full ? IFACE_("Tile Area") : IFACE_("Maximize Area");
4131 uiItemO(layout, but_str, ICON_NONE, "SCREEN_OT_screen_full_area");
4132 }
4133 }
4134
ED_screens_footer_tools_menu_create(bContext * C,uiLayout * layout,void * UNUSED (arg))4135 void ED_screens_footer_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg))
4136 {
4137 ScrArea *area = CTX_wm_area(C);
4138 ARegion *region = CTX_wm_region(C);
4139 const char *but_flip_str = (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_TOP) ?
4140 IFACE_("Flip to Bottom") :
4141 IFACE_("Flip to Top");
4142 {
4143 PointerRNA ptr;
4144 RNA_pointer_create((ID *)CTX_wm_screen(C), &RNA_Space, area->spacedata.first, &ptr);
4145 uiItemR(layout, &ptr, "show_region_footer", 0, IFACE_("Show Footer"), ICON_NONE);
4146 }
4147
4148 /* default is WM_OP_INVOKE_REGION_WIN, which we don't want here. */
4149 uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
4150
4151 uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip");
4152
4153 /* File browser should be fullscreen all the time, top-bar should
4154 * never be. But other regions can be maximized/restored... */
4155 if (!ELEM(area->spacetype, SPACE_FILE, SPACE_TOPBAR)) {
4156 uiItemS(layout);
4157
4158 const char *but_str = area->full ? IFACE_("Tile Area") : IFACE_("Maximize Area");
4159 uiItemO(layout, but_str, ICON_NONE, "SCREEN_OT_screen_full_area");
4160 }
4161 }
4162
ED_screens_navigation_bar_tools_menu_create(bContext * C,uiLayout * layout,void * UNUSED (arg))4163 void ED_screens_navigation_bar_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg))
4164 {
4165 const ARegion *region = CTX_wm_region(C);
4166 const char *but_flip_str = (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_LEFT) ?
4167 IFACE_("Flip to Right") :
4168 IFACE_("Flip to Left");
4169
4170 /* default is WM_OP_INVOKE_REGION_WIN, which we don't want here. */
4171 uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
4172
4173 uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip");
4174 }
4175
ed_screens_statusbar_menu_create(uiLayout * layout,void * UNUSED (arg))4176 static void ed_screens_statusbar_menu_create(uiLayout *layout, void *UNUSED(arg))
4177 {
4178 PointerRNA ptr;
4179
4180 RNA_pointer_create(NULL, &RNA_PreferencesView, &U, &ptr);
4181 uiItemR(layout, &ptr, "show_statusbar_stats", 0, IFACE_("Scene Statistics"), ICON_NONE);
4182 uiItemR(layout, &ptr, "show_statusbar_memory", 0, IFACE_("System Memory"), ICON_NONE);
4183 if (GPU_mem_stats_supported()) {
4184 uiItemR(layout, &ptr, "show_statusbar_vram", 0, IFACE_("Video Memory"), ICON_NONE);
4185 }
4186 uiItemR(layout, &ptr, "show_statusbar_version", 0, IFACE_("Blender Version"), ICON_NONE);
4187 }
4188
screen_context_menu_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * UNUSED (event))4189 static int screen_context_menu_invoke(bContext *C,
4190 wmOperator *UNUSED(op),
4191 const wmEvent *UNUSED(event))
4192 {
4193 const ScrArea *area = CTX_wm_area(C);
4194 const ARegion *region = CTX_wm_region(C);
4195
4196 if (area && area->spacetype == SPACE_STATUSBAR) {
4197 uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Status Bar"), ICON_NONE);
4198 uiLayout *layout = UI_popup_menu_layout(pup);
4199 ed_screens_statusbar_menu_create(layout, NULL);
4200 UI_popup_menu_end(C, pup);
4201 }
4202 else if (ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
4203 uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Header"), ICON_NONE);
4204 uiLayout *layout = UI_popup_menu_layout(pup);
4205 ED_screens_header_tools_menu_create(C, layout, NULL);
4206 UI_popup_menu_end(C, pup);
4207 }
4208 else if (region->regiontype == RGN_TYPE_FOOTER) {
4209 uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Footer"), ICON_NONE);
4210 uiLayout *layout = UI_popup_menu_layout(pup);
4211 ED_screens_footer_tools_menu_create(C, layout, NULL);
4212 UI_popup_menu_end(C, pup);
4213 }
4214 else if (region->regiontype == RGN_TYPE_NAV_BAR) {
4215 uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Navigation Bar"), ICON_NONE);
4216 uiLayout *layout = UI_popup_menu_layout(pup);
4217 ED_screens_navigation_bar_tools_menu_create(C, layout, NULL);
4218 UI_popup_menu_end(C, pup);
4219 }
4220
4221 return OPERATOR_INTERFACE;
4222 }
4223
SCREEN_OT_region_context_menu(wmOperatorType * ot)4224 static void SCREEN_OT_region_context_menu(wmOperatorType *ot)
4225 {
4226 /* identifiers */
4227 ot->name = "Region Context Menu";
4228 ot->description = "Display region context menu";
4229 ot->idname = "SCREEN_OT_region_context_menu";
4230
4231 /* api callbacks */
4232 ot->invoke = screen_context_menu_invoke;
4233 }
4234
4235 /** \} */
4236
4237 /* -------------------------------------------------------------------- */
4238 /** \name Animation Step Operator
4239 *
4240 * Animation Step.
4241 * \{ */
screen_animation_region_supports_time_follow(eSpace_Type spacetype,eRegionType regiontype)4242 static bool screen_animation_region_supports_time_follow(eSpace_Type spacetype,
4243 eRegionType regiontype)
4244 {
4245 return (regiontype == RGN_TYPE_WINDOW &&
4246 ELEM(spacetype, SPACE_SEQ, SPACE_GRAPH, SPACE_ACTION, SPACE_NLA)) ||
4247 (spacetype == SPACE_CLIP && regiontype == RGN_TYPE_PREVIEW);
4248 }
4249
match_region_with_redraws(const ScrArea * area,eRegionType regiontype,eScreen_Redraws_Flag redraws,bool from_anim_edit)4250 static bool match_region_with_redraws(const ScrArea *area,
4251 eRegionType regiontype,
4252 eScreen_Redraws_Flag redraws,
4253 bool from_anim_edit)
4254 {
4255 const eSpace_Type spacetype = area->spacetype;
4256 if (regiontype == RGN_TYPE_WINDOW) {
4257
4258 switch (spacetype) {
4259 case SPACE_VIEW3D:
4260 if ((redraws & TIME_ALL_3D_WIN) || from_anim_edit) {
4261 return true;
4262 }
4263 break;
4264 case SPACE_GRAPH:
4265 case SPACE_NLA:
4266 if ((redraws & TIME_ALL_ANIM_WIN) || from_anim_edit) {
4267 return true;
4268 }
4269 break;
4270 case SPACE_ACTION:
4271 /* if only 1 window or 3d windows, we do timeline too
4272 * NOTE: Now we do action editor in all these cases, since timeline is here. */
4273 if ((redraws & (TIME_ALL_ANIM_WIN | TIME_REGION | TIME_ALL_3D_WIN)) || from_anim_edit) {
4274 return true;
4275 }
4276 break;
4277 case SPACE_PROPERTIES:
4278 if (redraws & TIME_ALL_BUTS_WIN) {
4279 return true;
4280 }
4281 break;
4282 case SPACE_SEQ:
4283 if ((redraws & (TIME_SEQ | TIME_ALL_ANIM_WIN)) || from_anim_edit) {
4284 return true;
4285 }
4286 break;
4287 case SPACE_NODE:
4288 if (redraws & TIME_NODES) {
4289 return true;
4290 }
4291 break;
4292 case SPACE_IMAGE:
4293 if ((redraws & TIME_ALL_IMAGE_WIN) || from_anim_edit) {
4294 return true;
4295 }
4296 break;
4297 case SPACE_CLIP:
4298 if ((redraws & TIME_CLIPS) || from_anim_edit) {
4299 return true;
4300 }
4301 break;
4302 default:
4303 break;
4304 }
4305 }
4306 else if (regiontype == RGN_TYPE_UI) {
4307 if (spacetype == SPACE_CLIP) {
4308 /* Track Preview button is on Properties Editor in SpaceClip,
4309 * and it's very common case when users want it be refreshing
4310 * during playback, so asking people to enable special option
4311 * for this is a bit tricky, so add exception here for refreshing
4312 * Properties Editor for SpaceClip always */
4313 return true;
4314 }
4315
4316 if (redraws & TIME_ALL_BUTS_WIN) {
4317 return true;
4318 }
4319 }
4320 else if (regiontype == RGN_TYPE_HEADER) {
4321 if (spacetype == SPACE_ACTION) {
4322 /* The timeline shows the current frame in the header. Other headers
4323 * don't need to be updated. */
4324 SpaceAction *saction = (SpaceAction *)area->spacedata.first;
4325 return saction->mode == SACTCONT_TIMELINE;
4326 }
4327 }
4328 else if (regiontype == RGN_TYPE_PREVIEW) {
4329 switch (spacetype) {
4330 case SPACE_SEQ:
4331 if (redraws & (TIME_SEQ | TIME_ALL_ANIM_WIN)) {
4332 return true;
4333 }
4334 break;
4335 case SPACE_CLIP:
4336 return true;
4337 default:
4338 break;
4339 }
4340 }
4341 return false;
4342 }
4343
screen_animation_region_tag_redraw(ScrArea * area,ARegion * region,const Scene * scene,eScreen_Redraws_Flag redraws)4344 static void screen_animation_region_tag_redraw(ScrArea *area,
4345 ARegion *region,
4346 const Scene *scene,
4347 eScreen_Redraws_Flag redraws)
4348 {
4349 /* Do follow time here if editor type supports it */
4350 if ((redraws & TIME_FOLLOW) &&
4351 (screen_animation_region_supports_time_follow(area->spacetype, region->regiontype))) {
4352 float w = BLI_rctf_size_x(®ion->v2d.cur);
4353 if (scene->r.cfra < region->v2d.cur.xmin) {
4354 region->v2d.cur.xmax = scene->r.cfra;
4355 region->v2d.cur.xmin = region->v2d.cur.xmax - w;
4356 ED_region_tag_redraw(region);
4357 return;
4358 }
4359 if (scene->r.cfra > region->v2d.cur.xmax) {
4360 region->v2d.cur.xmin = scene->r.cfra;
4361 region->v2d.cur.xmax = region->v2d.cur.xmin + w;
4362 ED_region_tag_redraw(region);
4363 return;
4364 }
4365 }
4366
4367 /* No need to do a full redraw as the current frame indicator is only updated.
4368 * We do need to redraw when this area is in full screen as no other areas
4369 * will be tagged for redrawing. */
4370 if ((region->regiontype == RGN_TYPE_WINDOW) &&
4371 (ELEM(area->spacetype, SPACE_GRAPH, SPACE_NLA, SPACE_ACTION)) && !area->full) {
4372 return;
4373 }
4374 ED_region_tag_redraw(region);
4375 }
4376
4377 //#define PROFILE_AUDIO_SYNCH
4378
screen_animation_step_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * event)4379 static int screen_animation_step_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
4380 {
4381 bScreen *screen = CTX_wm_screen(C);
4382 wmTimer *wt = screen->animtimer;
4383
4384 if (!(wt && wt == event->customdata)) {
4385 return OPERATOR_PASS_THROUGH;
4386 }
4387
4388 wmWindow *win = CTX_wm_window(C);
4389
4390 #ifdef PROFILE_AUDIO_SYNCH
4391 static int old_frame = 0;
4392 int newfra_int;
4393 #endif
4394
4395 Main *bmain = CTX_data_main(C);
4396 Scene *scene = CTX_data_scene(C);
4397 ViewLayer *view_layer = WM_window_get_active_view_layer(win);
4398 Depsgraph *depsgraph = BKE_scene_get_depsgraph(scene, view_layer);
4399 Scene *scene_eval = (depsgraph != NULL) ? DEG_get_evaluated_scene(depsgraph) : NULL;
4400 ScreenAnimData *sad = wt->customdata;
4401 wmWindowManager *wm = CTX_wm_manager(C);
4402 int sync;
4403 double time;
4404
4405 /* sync, don't sync, or follow scene setting */
4406 if (sad->flag & ANIMPLAY_FLAG_SYNC) {
4407 sync = 1;
4408 }
4409 else if (sad->flag & ANIMPLAY_FLAG_NO_SYNC) {
4410 sync = 0;
4411 }
4412 else {
4413 sync = (scene->flag & SCE_FRAME_DROP);
4414 }
4415
4416 if (scene_eval == NULL) {
4417 /* Happens when undo/redo system is used during playback, nothing meaningful we can do here. */
4418 }
4419 else if (scene_eval->id.recalc & ID_RECALC_AUDIO_SEEK) {
4420 /* Ignore seek here, the audio will be updated to the scene frame after jump during next
4421 * dependency graph update. */
4422 }
4423 else if ((scene->audio.flag & AUDIO_SYNC) && (sad->flag & ANIMPLAY_FLAG_REVERSE) == false &&
4424 isfinite(time = BKE_sound_sync_scene(scene_eval))) {
4425 double newfra = time * FPS;
4426
4427 /* give some space here to avoid jumps */
4428 if (newfra + 0.5 > scene->r.cfra && newfra - 0.5 < scene->r.cfra) {
4429 scene->r.cfra++;
4430 }
4431 else {
4432 scene->r.cfra = max_ii(scene->r.cfra, round(newfra));
4433 }
4434
4435 #ifdef PROFILE_AUDIO_SYNCH
4436 newfra_int = scene->r.cfra;
4437 if (newfra_int < old_frame) {
4438 printf("back jump detected, frame %d!\n", newfra_int);
4439 }
4440 else if (newfra_int > old_frame + 1) {
4441 printf("forward jump detected, frame %d!\n", newfra_int);
4442 }
4443 fflush(stdout);
4444 old_frame = newfra_int;
4445 #endif
4446 }
4447 else {
4448 if (sync) {
4449 /* Try to keep the playback in realtime by dropping frames. */
4450
4451 /* How much time (in frames) has passed since the last frame was drawn? */
4452 double delta_frames = wt->delta * FPS;
4453
4454 /* Add the remaining fraction from the last time step. */
4455 delta_frames += sad->lagging_frame_count;
4456
4457 if (delta_frames < 1.0) {
4458 /* We can render faster than the scene frame rate. However skipping or delaying frames
4459 * here seems to in practice lead to jittery playback so just step forward a minimum of
4460 * one frame. (Even though this can lead to too fast playback, the jitteryness is more
4461 * annoying)
4462 */
4463 delta_frames = 1.0f;
4464 sad->lagging_frame_count = 0;
4465 }
4466 else {
4467 /* Extract the delta frame fractions that will be skipped when converting to int. */
4468 sad->lagging_frame_count = delta_frames - (int)delta_frames;
4469 }
4470
4471 const int step = delta_frames;
4472
4473 /* skip frames */
4474 if (sad->flag & ANIMPLAY_FLAG_REVERSE) {
4475 scene->r.cfra -= step;
4476 }
4477 else {
4478 scene->r.cfra += step;
4479 }
4480 }
4481 else {
4482 /* one frame +/- */
4483 if (sad->flag & ANIMPLAY_FLAG_REVERSE) {
4484 scene->r.cfra--;
4485 }
4486 else {
4487 scene->r.cfra++;
4488 }
4489 }
4490 }
4491
4492 /* reset 'jumped' flag before checking if we need to jump... */
4493 sad->flag &= ~ANIMPLAY_FLAG_JUMPED;
4494
4495 if (sad->flag & ANIMPLAY_FLAG_REVERSE) {
4496 /* jump back to end? */
4497 if (PRVRANGEON) {
4498 if (scene->r.cfra < scene->r.psfra) {
4499 scene->r.cfra = scene->r.pefra;
4500 sad->flag |= ANIMPLAY_FLAG_JUMPED;
4501 }
4502 }
4503 else {
4504 if (scene->r.cfra < scene->r.sfra) {
4505 scene->r.cfra = scene->r.efra;
4506 sad->flag |= ANIMPLAY_FLAG_JUMPED;
4507 }
4508 }
4509 }
4510 else {
4511 /* jump back to start? */
4512 if (PRVRANGEON) {
4513 if (scene->r.cfra > scene->r.pefra) {
4514 scene->r.cfra = scene->r.psfra;
4515 sad->flag |= ANIMPLAY_FLAG_JUMPED;
4516 }
4517 }
4518 else {
4519 if (scene->r.cfra > scene->r.efra) {
4520 scene->r.cfra = scene->r.sfra;
4521 sad->flag |= ANIMPLAY_FLAG_JUMPED;
4522 }
4523 }
4524 }
4525
4526 /* next frame overridden by user action (pressed jump to first/last frame) */
4527 if (sad->flag & ANIMPLAY_FLAG_USE_NEXT_FRAME) {
4528 scene->r.cfra = sad->nextfra;
4529 sad->flag &= ~ANIMPLAY_FLAG_USE_NEXT_FRAME;
4530 sad->flag |= ANIMPLAY_FLAG_JUMPED;
4531 }
4532
4533 if (sad->flag & ANIMPLAY_FLAG_JUMPED) {
4534 DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
4535 #ifdef PROFILE_AUDIO_SYNCH
4536 old_frame = CFRA;
4537 #endif
4538 }
4539
4540 /* since we follow drawflags, we can't send notifier but tag regions ourselves */
4541 if (depsgraph != NULL) {
4542 ED_update_for_newframe(bmain, depsgraph);
4543 }
4544
4545 LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
4546 const bScreen *win_screen = WM_window_get_active_screen(window);
4547
4548 LISTBASE_FOREACH (ScrArea *, area, &win_screen->areabase) {
4549 LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
4550 bool redraw = false;
4551 if (region == sad->region) {
4552 redraw = true;
4553 }
4554 else if (match_region_with_redraws(
4555 area, region->regiontype, sad->redraws, sad->from_anim_edit)) {
4556 redraw = true;
4557 }
4558
4559 if (redraw) {
4560 screen_animation_region_tag_redraw(area, region, scene, sad->redraws);
4561 }
4562 }
4563 }
4564 }
4565
4566 /* update frame rate info too
4567 * NOTE: this may not be accurate enough, since we might need this after modifiers/etc.
4568 * have been calculated instead of just before updates have been done?
4569 */
4570 ED_refresh_viewport_fps(C);
4571
4572 /* Recalculate the time-step for the timer now that we've finished calculating this,
4573 * since the frames-per-second value may have been changed.
4574 */
4575 /* TODO: this may make evaluation a bit slower if the value doesn't change...
4576 * any way to avoid this? */
4577 wt->timestep = (1.0 / FPS);
4578
4579 return OPERATOR_FINISHED;
4580 }
4581
SCREEN_OT_animation_step(wmOperatorType * ot)4582 static void SCREEN_OT_animation_step(wmOperatorType *ot)
4583 {
4584 /* identifiers */
4585 ot->name = "Animation Step";
4586 ot->description = "Step through animation by position";
4587 ot->idname = "SCREEN_OT_animation_step";
4588
4589 /* api callbacks */
4590 ot->invoke = screen_animation_step_invoke;
4591
4592 ot->poll = ED_operator_screenactive_norender;
4593 }
4594
4595 /** \} */
4596
4597 /* -------------------------------------------------------------------- */
4598 /** \name Animation Playback Operator
4599 *
4600 * Animation Playback with Timer.
4601 * \{ */
4602
4603 /* find window that owns the animation timer */
ED_screen_animation_playing(const wmWindowManager * wm)4604 bScreen *ED_screen_animation_playing(const wmWindowManager *wm)
4605 {
4606 LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
4607 bScreen *screen = WM_window_get_active_screen(win);
4608
4609 if (screen->animtimer || screen->scrubbing) {
4610 return screen;
4611 }
4612 }
4613
4614 return NULL;
4615 }
4616
ED_screen_animation_no_scrub(const wmWindowManager * wm)4617 bScreen *ED_screen_animation_no_scrub(const wmWindowManager *wm)
4618 {
4619 LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
4620 bScreen *screen = WM_window_get_active_screen(win);
4621
4622 if (screen->animtimer) {
4623 return screen;
4624 }
4625 }
4626
4627 return NULL;
4628 }
4629
4630 /* toggle operator */
ED_screen_animation_play(bContext * C,int sync,int mode)4631 int ED_screen_animation_play(bContext *C, int sync, int mode)
4632 {
4633 bScreen *screen = CTX_wm_screen(C);
4634 Scene *scene = CTX_data_scene(C);
4635 Scene *scene_eval = DEG_get_evaluated_scene(CTX_data_ensure_evaluated_depsgraph(C));
4636
4637 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
4638 /* stop playback now */
4639 ED_screen_animation_timer(C, 0, 0, 0);
4640 BKE_sound_stop_scene(scene_eval);
4641
4642 WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
4643 }
4644 else {
4645 /* these settings are currently only available from a menu in the TimeLine */
4646 if (mode == 1) { /* XXX only play audio forwards!? */
4647 BKE_sound_play_scene(scene_eval);
4648 }
4649
4650 ED_screen_animation_timer(C, screen->redraws_flag, sync, mode);
4651
4652 if (screen->animtimer) {
4653 wmTimer *wt = screen->animtimer;
4654 ScreenAnimData *sad = wt->customdata;
4655
4656 sad->region = CTX_wm_region(C);
4657 }
4658 }
4659
4660 return OPERATOR_FINISHED;
4661 }
4662
screen_animation_play_exec(bContext * C,wmOperator * op)4663 static int screen_animation_play_exec(bContext *C, wmOperator *op)
4664 {
4665 int mode = (RNA_boolean_get(op->ptr, "reverse")) ? -1 : 1;
4666 int sync = -1;
4667
4668 if (RNA_struct_property_is_set(op->ptr, "sync")) {
4669 sync = (RNA_boolean_get(op->ptr, "sync"));
4670 }
4671
4672 return ED_screen_animation_play(C, sync, mode);
4673 }
4674
SCREEN_OT_animation_play(wmOperatorType * ot)4675 static void SCREEN_OT_animation_play(wmOperatorType *ot)
4676 {
4677 PropertyRNA *prop;
4678
4679 /* identifiers */
4680 ot->name = "Play Animation";
4681 ot->description = "Play animation";
4682 ot->idname = "SCREEN_OT_animation_play";
4683
4684 /* api callbacks */
4685 ot->exec = screen_animation_play_exec;
4686
4687 ot->poll = ED_operator_screenactive_norender;
4688
4689 prop = RNA_def_boolean(
4690 ot->srna, "reverse", 0, "Play in Reverse", "Animation is played backwards");
4691 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4692 prop = RNA_def_boolean(ot->srna, "sync", 0, "Sync", "Drop frames to maintain framerate");
4693 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4694 }
4695
4696 /** \} */
4697
4698 /* -------------------------------------------------------------------- */
4699 /** \name Animation Cancel Operator
4700 * \{ */
4701
screen_animation_cancel_exec(bContext * C,wmOperator * op)4702 static int screen_animation_cancel_exec(bContext *C, wmOperator *op)
4703 {
4704 bScreen *screen = ED_screen_animation_playing(CTX_wm_manager(C));
4705
4706 if (screen) {
4707 if (RNA_boolean_get(op->ptr, "restore_frame") && screen->animtimer) {
4708 ScreenAnimData *sad = screen->animtimer->customdata;
4709 Scene *scene = CTX_data_scene(C);
4710
4711 /* reset current frame before stopping, and just send a notifier to deal with the rest
4712 * (since playback still needs to be stopped)
4713 */
4714 scene->r.cfra = sad->sfra;
4715
4716 WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
4717 }
4718
4719 /* call the other "toggling" operator to clean up now */
4720 ED_screen_animation_play(C, 0, 0);
4721 }
4722
4723 return OPERATOR_PASS_THROUGH;
4724 }
4725
SCREEN_OT_animation_cancel(wmOperatorType * ot)4726 static void SCREEN_OT_animation_cancel(wmOperatorType *ot)
4727 {
4728 /* identifiers */
4729 ot->name = "Cancel Animation";
4730 ot->description = "Cancel animation, returning to the original frame";
4731 ot->idname = "SCREEN_OT_animation_cancel";
4732
4733 /* api callbacks */
4734 ot->exec = screen_animation_cancel_exec;
4735
4736 ot->poll = ED_operator_screenactive;
4737
4738 RNA_def_boolean(ot->srna,
4739 "restore_frame",
4740 true,
4741 "Restore Frame",
4742 "Restore the frame when animation was initialized");
4743 }
4744
4745 /** \} */
4746
4747 /* -------------------------------------------------------------------- */
4748 /** \name Box Select Operator (Template)
4749 * \{ */
4750
4751 /* operator state vars used: (added by default WM callbacks)
4752 * xmin, ymin
4753 * xmax, ymax
4754 *
4755 * customdata: the wmGesture pointer
4756 *
4757 * callbacks:
4758 *
4759 * exec() has to be filled in by user
4760 *
4761 * invoke() default WM function
4762 * adds modal handler
4763 *
4764 * modal() default WM function
4765 * accept modal events while doing it, calls exec(), handles ESC and border drawing
4766 *
4767 * poll() has to be filled in by user for context
4768 */
4769 #if 0
4770 static int box_select_exec(bContext *C, wmOperator *op)
4771 {
4772 int event_type = RNA_int_get(op->ptr, "event_type");
4773
4774 if (event_type == LEFTMOUSE) {
4775 printf("box select do select\n");
4776 }
4777 else if (event_type == RIGHTMOUSE) {
4778 printf("box select deselect\n");
4779 }
4780 else {
4781 printf("box select do something\n");
4782 }
4783
4784 return 1;
4785 }
4786
4787 static void SCREEN_OT_box_select(wmOperatorType *ot)
4788 {
4789 /* identifiers */
4790 ot->name = "Box Select";
4791 ot->idname = "SCREEN_OT_box_select";
4792
4793 /* api callbacks */
4794 ot->exec = box_select_exec;
4795 ot->invoke = WM_gesture_box_invoke;
4796 ot->modal = WM_gesture_box_modal;
4797 ot->cancel = WM_gesture_box_cancel;
4798
4799 ot->poll = ED_operator_areaactive;
4800
4801 /* rna */
4802 RNA_def_int(ot->srna, "event_type", 0, INT_MIN, INT_MAX, "Event Type", "", INT_MIN, INT_MAX);
4803 WM_operator_properties_border(ot);
4804 }
4805 #endif
4806
4807 /** \} */
4808
4809 /* -------------------------------------------------------------------- */
4810 /** \name Full Screen Back Operator
4811 *
4812 * Use for generic full-screen 'back' button.
4813 * \{ */
4814
fullscreen_back_exec(bContext * C,wmOperator * op)4815 static int fullscreen_back_exec(bContext *C, wmOperator *op)
4816 {
4817 bScreen *screen = CTX_wm_screen(C);
4818 ScrArea *area = NULL;
4819
4820 /* search current screen for 'fullscreen' areas */
4821 LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) {
4822 if (area_iter->full) {
4823 area = area_iter;
4824 break;
4825 }
4826 }
4827 if (!area) {
4828 BKE_report(op->reports, RPT_ERROR, "No fullscreen areas were found");
4829 return OPERATOR_CANCELLED;
4830 }
4831
4832 ED_screen_full_prevspace(C, area);
4833
4834 return OPERATOR_FINISHED;
4835 }
4836
SCREEN_OT_back_to_previous(struct wmOperatorType * ot)4837 static void SCREEN_OT_back_to_previous(struct wmOperatorType *ot)
4838 {
4839 /* identifiers */
4840 ot->name = "Back to Previous Screen";
4841 ot->description = "Revert back to the original screen layout, before fullscreen area overlay";
4842 ot->idname = "SCREEN_OT_back_to_previous";
4843
4844 /* api callbacks */
4845 ot->exec = fullscreen_back_exec;
4846 ot->poll = ED_operator_screenactive;
4847 }
4848
4849 /** \} */
4850
4851 /* -------------------------------------------------------------------- */
4852 /** \name Show User Preferences Operator
4853 * \{ */
4854
userpref_show_exec(bContext * C,wmOperator * op)4855 static int userpref_show_exec(bContext *C, wmOperator *op)
4856 {
4857 wmWindow *win_cur = CTX_wm_window(C);
4858 /* Use eventstate, not event from _invoke, so this can be called through exec(). */
4859 const wmEvent *event = win_cur->eventstate;
4860 int sizex = (500 + UI_NAVIGATION_REGION_WIDTH) * UI_DPI_FAC;
4861 int sizey = 520 * UI_DPI_FAC;
4862
4863 /* changes context! */
4864 if (WM_window_open_temp(C,
4865 IFACE_("Blender Preferences"),
4866 event->x,
4867 event->y,
4868 sizex,
4869 sizey,
4870 SPACE_USERPREF,
4871 false) != NULL) {
4872 /* The header only contains the editor switcher and looks empty.
4873 * So hiding in the temp window makes sense. */
4874 ScrArea *area = CTX_wm_area(C);
4875 ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
4876
4877 region->flag |= RGN_FLAG_HIDDEN;
4878 ED_region_visibility_change_update(C, area, region);
4879
4880 return OPERATOR_FINISHED;
4881 }
4882 BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
4883 return OPERATOR_CANCELLED;
4884 }
4885
SCREEN_OT_userpref_show(struct wmOperatorType * ot)4886 static void SCREEN_OT_userpref_show(struct wmOperatorType *ot)
4887 {
4888 /* identifiers */
4889 ot->name = "Show Preferences";
4890 ot->description = "Edit user preferences and system settings";
4891 ot->idname = "SCREEN_OT_userpref_show";
4892
4893 /* api callbacks */
4894 ot->exec = userpref_show_exec;
4895 ot->poll = ED_operator_screenactive;
4896 }
4897
4898 /** \} */
4899
4900 /* -------------------------------------------------------------------- */
4901 /** \name Show Drivers Editor Operator
4902 * \{ */
4903
drivers_editor_show_exec(bContext * C,wmOperator * op)4904 static int drivers_editor_show_exec(bContext *C, wmOperator *op)
4905 {
4906 wmWindow *win_cur = CTX_wm_window(C);
4907 /* Use eventstate, not event from _invoke, so this can be called through exec(). */
4908 const wmEvent *event = win_cur->eventstate;
4909
4910 int sizex = 900 * UI_DPI_FAC;
4911 int sizey = 580 * UI_DPI_FAC;
4912
4913 /* Get active property to show driver for
4914 * - Need to grab it first, or else this info disappears
4915 * after we've created the window
4916 */
4917 int index;
4918 PointerRNA ptr;
4919 PropertyRNA *prop;
4920 uiBut *but = UI_context_active_but_prop_get(C, &ptr, &prop, &index);
4921
4922 /* changes context! */
4923 if (WM_window_open_temp(C,
4924 IFACE_("Blender Drivers Editor"),
4925 event->x,
4926 event->y,
4927 sizex,
4928 sizey,
4929 SPACE_GRAPH,
4930 false) != NULL) {
4931 ED_drivers_editor_init(C, CTX_wm_area(C));
4932
4933 /* activate driver F-Curve for the property under the cursor */
4934 if (but) {
4935 bool driven, special;
4936 FCurve *fcu = BKE_fcurve_find_by_rna_context_ui(
4937 C, &ptr, prop, index, NULL, NULL, &driven, &special);
4938
4939 if (fcu) {
4940 /* Isolate this F-Curve... */
4941 bAnimContext ac;
4942 if (ANIM_animdata_get_context(C, &ac)) {
4943 int filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS;
4944 ANIM_anim_channels_select_set(&ac, ACHANNEL_SETFLAG_CLEAR);
4945 ANIM_set_active_channel(&ac, ac.data, ac.datatype, filter, fcu, ANIMTYPE_FCURVE);
4946 }
4947 else {
4948 /* Just blindly isolate...
4949 * This isn't the best, and shouldn't happen, but may be enough. */
4950 fcu->flag |= (FCURVE_ACTIVE | FCURVE_SELECTED);
4951 }
4952 }
4953 }
4954
4955 return OPERATOR_FINISHED;
4956 }
4957 BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
4958 return OPERATOR_CANCELLED;
4959 }
4960
SCREEN_OT_drivers_editor_show(struct wmOperatorType * ot)4961 static void SCREEN_OT_drivers_editor_show(struct wmOperatorType *ot)
4962 {
4963 /* identifiers */
4964 ot->name = "Show Drivers Editor";
4965 ot->description = "Show drivers editor in a separate window";
4966 ot->idname = "SCREEN_OT_drivers_editor_show";
4967
4968 /* api callbacks */
4969 ot->exec = drivers_editor_show_exec;
4970 ot->poll = ED_operator_screenactive;
4971 }
4972
4973 /** \} */
4974
4975 /* -------------------------------------------------------------------- */
4976 /** \name Show Info Log Operator
4977 * \{ */
4978
info_log_show_exec(bContext * C,wmOperator * op)4979 static int info_log_show_exec(bContext *C, wmOperator *op)
4980 {
4981 wmWindow *win_cur = CTX_wm_window(C);
4982 /* Use eventstate, not event from _invoke, so this can be called through exec(). */
4983 const wmEvent *event = win_cur->eventstate;
4984 int sizex = 900 * UI_DPI_FAC;
4985 int sizey = 580 * UI_DPI_FAC;
4986 int shift_y = 480;
4987
4988 /* changes context! */
4989 if (WM_window_open_temp(C,
4990 IFACE_("Blender Info Log"),
4991 event->x,
4992 event->y + shift_y,
4993 sizex,
4994 sizey,
4995 SPACE_INFO,
4996 false) != NULL) {
4997 return OPERATOR_FINISHED;
4998 }
4999 BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
5000 return OPERATOR_CANCELLED;
5001 }
5002
SCREEN_OT_info_log_show(struct wmOperatorType * ot)5003 static void SCREEN_OT_info_log_show(struct wmOperatorType *ot)
5004 {
5005 /* identifiers */
5006 ot->name = "Show Info Log";
5007 ot->description = "Show info log in a separate window";
5008 ot->idname = "SCREEN_OT_info_log_show";
5009
5010 /* api callbacks */
5011 ot->exec = info_log_show_exec;
5012 ot->poll = ED_operator_screenactive;
5013 }
5014
5015 /** \} */
5016
5017 /* -------------------------------------------------------------------- */
5018 /** \name New Screen Operator
5019 * \{ */
5020
screen_new_exec(bContext * C,wmOperator * UNUSED (op))5021 static int screen_new_exec(bContext *C, wmOperator *UNUSED(op))
5022 {
5023 Main *bmain = CTX_data_main(C);
5024 wmWindow *win = CTX_wm_window(C);
5025 WorkSpace *workspace = BKE_workspace_active_get(win->workspace_hook);
5026 WorkSpaceLayout *layout_old = BKE_workspace_active_layout_get(win->workspace_hook);
5027
5028 WorkSpaceLayout *layout_new = ED_workspace_layout_duplicate(bmain, workspace, layout_old, win);
5029
5030 WM_event_add_notifier(C, NC_SCREEN | ND_LAYOUTBROWSE, layout_new);
5031
5032 return OPERATOR_FINISHED;
5033 }
5034
SCREEN_OT_new(wmOperatorType * ot)5035 static void SCREEN_OT_new(wmOperatorType *ot)
5036 {
5037 /* identifiers */
5038 ot->name = "New Screen";
5039 ot->description = "Add a new screen";
5040 ot->idname = "SCREEN_OT_new";
5041
5042 /* api callbacks */
5043 ot->exec = screen_new_exec;
5044 ot->poll = WM_operator_winactive;
5045 }
5046
5047 /** \} */
5048
5049 /* -------------------------------------------------------------------- */
5050 /** \name Delete Screen Operator
5051 * \{ */
5052
screen_delete_exec(bContext * C,wmOperator * UNUSED (op))5053 static int screen_delete_exec(bContext *C, wmOperator *UNUSED(op))
5054 {
5055 bScreen *screen = CTX_wm_screen(C);
5056 WorkSpace *workspace = CTX_wm_workspace(C);
5057 WorkSpaceLayout *layout = BKE_workspace_layout_find(workspace, screen);
5058
5059 WM_event_add_notifier(C, NC_SCREEN | ND_LAYOUTDELETE, layout);
5060
5061 return OPERATOR_FINISHED;
5062 }
5063
SCREEN_OT_delete(wmOperatorType * ot)5064 static void SCREEN_OT_delete(wmOperatorType *ot)
5065 {
5066 /* identifiers */
5067 ot->name = "Delete Screen";
5068 ot->description = "Delete active screen";
5069 ot->idname = "SCREEN_OT_delete";
5070
5071 /* api callbacks */
5072 ot->exec = screen_delete_exec;
5073 }
5074
5075 /** \} */
5076
5077 /* -------------------------------------------------------------------- */
5078 /** \name Region Alpha Blending Operator
5079 *
5080 * Implementation note: a disappearing region needs at least 1 last draw with
5081 * 100% backbuffer texture over it - then triple buffer will clear it entirely.
5082 * This because flag #RGN_FLAG_HIDDEN is set in end - region doesn't draw at all then.
5083 *
5084 * \{ */
5085
5086 typedef struct RegionAlphaInfo {
5087 ScrArea *area;
5088 ARegion *region, *child_region; /* other region */
5089 int hidden;
5090 } RegionAlphaInfo;
5091
5092 #define TIMEOUT 0.1f
5093 #define TIMESTEP (1.0f / 60.0f)
5094
ED_region_blend_alpha(ARegion * region)5095 float ED_region_blend_alpha(ARegion *region)
5096 {
5097 /* check parent too */
5098 if (region->regiontimer == NULL && (region->alignment & RGN_SPLIT_PREV) && region->prev) {
5099 region = region->prev;
5100 }
5101
5102 if (region->regiontimer) {
5103 RegionAlphaInfo *rgi = region->regiontimer->customdata;
5104 float alpha;
5105
5106 alpha = (float)region->regiontimer->duration / TIMEOUT;
5107 /* makes sure the blend out works 100% - without area redraws */
5108 if (rgi->hidden) {
5109 alpha = 0.9f - TIMESTEP - alpha;
5110 }
5111
5112 CLAMP(alpha, 0.0f, 1.0f);
5113 return alpha;
5114 }
5115 return 1.0f;
5116 }
5117
5118 /* assumes region has running region-blend timer */
region_blend_end(bContext * C,ARegion * region,const bool is_running)5119 static void region_blend_end(bContext *C, ARegion *region, const bool is_running)
5120 {
5121 RegionAlphaInfo *rgi = region->regiontimer->customdata;
5122
5123 /* always send redraw */
5124 ED_region_tag_redraw(region);
5125 if (rgi->child_region) {
5126 ED_region_tag_redraw(rgi->child_region);
5127 }
5128
5129 /* if running timer was hiding, the flag toggle went wrong */
5130 if (is_running) {
5131 if (rgi->hidden) {
5132 rgi->region->flag &= ~RGN_FLAG_HIDDEN;
5133 }
5134 }
5135 else {
5136 if (rgi->hidden) {
5137 rgi->region->flag |= rgi->hidden;
5138 ED_area_init(CTX_wm_manager(C), CTX_wm_window(C), rgi->area);
5139 }
5140 /* area decoration needs redraw in end */
5141 ED_area_tag_redraw(rgi->area);
5142 }
5143 WM_event_remove_timer(CTX_wm_manager(C), NULL, region->regiontimer); /* frees rgi */
5144 region->regiontimer = NULL;
5145 }
5146 /**
5147 * \note Assumes that \a region itself is not a split version from previous region.
5148 */
ED_region_visibility_change_update_animated(bContext * C,ScrArea * area,ARegion * region)5149 void ED_region_visibility_change_update_animated(bContext *C, ScrArea *area, ARegion *region)
5150 {
5151 wmWindowManager *wm = CTX_wm_manager(C);
5152 wmWindow *win = CTX_wm_window(C);
5153
5154 /* end running timer */
5155 if (region->regiontimer) {
5156
5157 region_blend_end(C, region, true);
5158 }
5159 RegionAlphaInfo *rgi = MEM_callocN(sizeof(RegionAlphaInfo), "RegionAlphaInfo");
5160
5161 rgi->hidden = region->flag & RGN_FLAG_HIDDEN;
5162 rgi->area = area;
5163 rgi->region = region;
5164 region->flag &= ~RGN_FLAG_HIDDEN;
5165
5166 /* blend in, reinitialize regions because it got unhidden */
5167 if (rgi->hidden == 0) {
5168 ED_area_init(wm, win, area);
5169 }
5170 else {
5171 WM_event_remove_handlers(C, ®ion->handlers);
5172 }
5173
5174 if (region->next) {
5175 if (region->next->alignment & RGN_SPLIT_PREV) {
5176 rgi->child_region = region->next;
5177 }
5178 }
5179
5180 /* new timer */
5181 region->regiontimer = WM_event_add_timer(wm, win, TIMERREGION, TIMESTEP);
5182 region->regiontimer->customdata = rgi;
5183 }
5184
5185 /* timer runs in win->handlers, so it cannot use context to find area/region */
region_blend_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * event)5186 static int region_blend_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
5187 {
5188 wmTimer *timer = event->customdata;
5189
5190 /* event type is TIMERREGION, but we better check */
5191 if (event->type != TIMERREGION || timer == NULL) {
5192 return OPERATOR_PASS_THROUGH;
5193 }
5194
5195 RegionAlphaInfo *rgi = timer->customdata;
5196
5197 /* always send redraws */
5198 ED_region_tag_redraw(rgi->region);
5199 if (rgi->child_region) {
5200 ED_region_tag_redraw(rgi->child_region);
5201 }
5202
5203 /* end timer? */
5204 if (rgi->region->regiontimer->duration > (double)TIMEOUT) {
5205 region_blend_end(C, rgi->region, false);
5206 return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
5207 }
5208
5209 return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
5210 }
5211
SCREEN_OT_region_blend(wmOperatorType * ot)5212 static void SCREEN_OT_region_blend(wmOperatorType *ot)
5213 {
5214 /* identifiers */
5215 ot->name = "Region Alpha";
5216 ot->idname = "SCREEN_OT_region_blend";
5217 ot->description = "Blend in and out overlapping region";
5218
5219 /* api callbacks */
5220 ot->invoke = region_blend_invoke;
5221
5222 /* flags */
5223 ot->flag = OPTYPE_INTERNAL;
5224
5225 /* properties */
5226 }
5227
5228 /** \} */
5229
5230 /* -------------------------------------------------------------------- */
5231 /** \name Space Type Set or Cycle Operator
5232 * \{ */
5233
space_type_set_or_cycle_poll(bContext * C)5234 static bool space_type_set_or_cycle_poll(bContext *C)
5235 {
5236 ScrArea *area = CTX_wm_area(C);
5237 return (area && !ELEM(area->spacetype, SPACE_TOPBAR, SPACE_STATUSBAR));
5238 }
5239
space_type_set_or_cycle_exec(bContext * C,wmOperator * op)5240 static int space_type_set_or_cycle_exec(bContext *C, wmOperator *op)
5241 {
5242 const int space_type = RNA_enum_get(op->ptr, "space_type");
5243
5244 PointerRNA ptr;
5245 ScrArea *area = CTX_wm_area(C);
5246 RNA_pointer_create((ID *)CTX_wm_screen(C), &RNA_Area, area, &ptr);
5247 PropertyRNA *prop_type = RNA_struct_find_property(&ptr, "type");
5248 PropertyRNA *prop_ui_type = RNA_struct_find_property(&ptr, "ui_type");
5249
5250 if (area->spacetype != space_type) {
5251 /* Set the type. */
5252 RNA_property_enum_set(&ptr, prop_type, space_type);
5253 RNA_property_update(C, &ptr, prop_type);
5254 }
5255 else {
5256 /* Types match, cycle the subtype. */
5257 const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type);
5258 const EnumPropertyItem *item;
5259 int item_len;
5260 bool free;
5261 RNA_property_enum_items(C, &ptr, prop_ui_type, &item, &item_len, &free);
5262 int index = RNA_enum_from_value(item, space_type_ui);
5263 for (int i = 1; i < item_len; i++) {
5264 const EnumPropertyItem *item_test = &item[(index + i) % item_len];
5265 if ((item_test->value >> 16) == space_type) {
5266 RNA_property_enum_set(&ptr, prop_ui_type, item_test->value);
5267 RNA_property_update(C, &ptr, prop_ui_type);
5268 break;
5269 }
5270 }
5271 if (free) {
5272 MEM_freeN((void *)item);
5273 }
5274 }
5275
5276 return OPERATOR_FINISHED;
5277 }
5278
SCREEN_OT_space_type_set_or_cycle(wmOperatorType * ot)5279 static void SCREEN_OT_space_type_set_or_cycle(wmOperatorType *ot)
5280 {
5281 /* identifiers */
5282 ot->name = "Cycle Space Type Set";
5283 ot->description = "Set the space type or cycle sub-type";
5284 ot->idname = "SCREEN_OT_space_type_set_or_cycle";
5285
5286 /* api callbacks */
5287 ot->exec = space_type_set_or_cycle_exec;
5288 ot->poll = space_type_set_or_cycle_poll;
5289
5290 ot->flag = 0;
5291
5292 RNA_def_enum(ot->srna, "space_type", rna_enum_space_type_items, SPACE_EMPTY, "Type", "");
5293 }
5294
5295 /** \} */
5296
5297 /* -------------------------------------------------------------------- */
5298 /** \name Space Context Cycle Operator
5299 * \{ */
5300
5301 static const EnumPropertyItem space_context_cycle_direction[] = {
5302 {SPACE_CONTEXT_CYCLE_PREV, "PREV", 0, "Previous", ""},
5303 {SPACE_CONTEXT_CYCLE_NEXT, "NEXT", 0, "Next", ""},
5304 {0, NULL, 0, NULL, NULL},
5305 };
5306
space_context_cycle_poll(bContext * C)5307 static bool space_context_cycle_poll(bContext *C)
5308 {
5309 ScrArea *area = CTX_wm_area(C);
5310 /* area might be NULL if called out of window bounds */
5311 return (area && ELEM(area->spacetype, SPACE_PROPERTIES, SPACE_USERPREF));
5312 }
5313
5314 /**
5315 * Helper to get the correct RNA pointer/property pair for changing
5316 * the display context of active space type in \a area.
5317 */
context_cycle_prop_get(bScreen * screen,const ScrArea * area,PointerRNA * r_ptr,PropertyRNA ** r_prop)5318 static void context_cycle_prop_get(bScreen *screen,
5319 const ScrArea *area,
5320 PointerRNA *r_ptr,
5321 PropertyRNA **r_prop)
5322 {
5323 const char *propname;
5324
5325 switch (area->spacetype) {
5326 case SPACE_PROPERTIES:
5327 RNA_pointer_create(&screen->id, &RNA_SpaceProperties, area->spacedata.first, r_ptr);
5328 propname = "context";
5329 break;
5330 case SPACE_USERPREF:
5331 RNA_pointer_create(NULL, &RNA_Preferences, &U, r_ptr);
5332 propname = "active_section";
5333 break;
5334 default:
5335 BLI_assert(0);
5336 propname = "";
5337 }
5338
5339 *r_prop = RNA_struct_find_property(r_ptr, propname);
5340 }
5341
space_context_cycle_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))5342 static int space_context_cycle_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
5343 {
5344 const int direction = RNA_enum_get(op->ptr, "direction");
5345
5346 PointerRNA ptr;
5347 PropertyRNA *prop;
5348 context_cycle_prop_get(CTX_wm_screen(C), CTX_wm_area(C), &ptr, &prop);
5349 const int old_context = RNA_property_enum_get(&ptr, prop);
5350 const int new_context = RNA_property_enum_step(
5351 C, &ptr, prop, old_context, direction == SPACE_CONTEXT_CYCLE_PREV ? -1 : 1);
5352 RNA_property_enum_set(&ptr, prop, new_context);
5353 RNA_property_update(C, &ptr, prop);
5354
5355 return OPERATOR_FINISHED;
5356 }
5357
SCREEN_OT_space_context_cycle(wmOperatorType * ot)5358 static void SCREEN_OT_space_context_cycle(wmOperatorType *ot)
5359 {
5360 /* identifiers */
5361 ot->name = "Cycle Space Context";
5362 ot->description = "Cycle through the editor context by activating the next/previous one";
5363 ot->idname = "SCREEN_OT_space_context_cycle";
5364
5365 /* api callbacks */
5366 ot->invoke = space_context_cycle_invoke;
5367 ot->poll = space_context_cycle_poll;
5368
5369 ot->flag = 0;
5370
5371 RNA_def_enum(ot->srna,
5372 "direction",
5373 space_context_cycle_direction,
5374 SPACE_CONTEXT_CYCLE_NEXT,
5375 "Direction",
5376 "Direction to cycle through");
5377 }
5378
5379 /** \} */
5380
5381 /* -------------------------------------------------------------------- */
5382 /** \name Workspace Cycle Operator
5383 * \{ */
5384
space_workspace_cycle_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))5385 static int space_workspace_cycle_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
5386 {
5387 wmWindow *win = CTX_wm_window(C);
5388 if (WM_window_is_temp_screen(win)) {
5389 return OPERATOR_CANCELLED;
5390 }
5391
5392 Main *bmain = CTX_data_main(C);
5393 const int direction = RNA_enum_get(op->ptr, "direction");
5394 WorkSpace *workspace_src = WM_window_get_active_workspace(win);
5395 WorkSpace *workspace_dst = NULL;
5396
5397 ListBase ordered;
5398 BKE_id_ordered_list(&ordered, &bmain->workspaces);
5399
5400 LISTBASE_FOREACH (LinkData *, link, &ordered) {
5401 if (link->data == workspace_src) {
5402 if (direction == SPACE_CONTEXT_CYCLE_PREV) {
5403 workspace_dst = (link->prev) ? link->prev->data : NULL;
5404 }
5405 else {
5406 workspace_dst = (link->next) ? link->next->data : NULL;
5407 }
5408 }
5409 }
5410
5411 if (workspace_dst == NULL) {
5412 LinkData *link = (direction == SPACE_CONTEXT_CYCLE_PREV) ? ordered.last : ordered.first;
5413 workspace_dst = link->data;
5414 }
5415
5416 BLI_freelistN(&ordered);
5417
5418 if (workspace_src == workspace_dst) {
5419 return OPERATOR_CANCELLED;
5420 }
5421
5422 win->workspace_hook->temp_workspace_store = workspace_dst;
5423 WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_SET, workspace_dst);
5424 win->workspace_hook->temp_workspace_store = NULL;
5425
5426 return OPERATOR_FINISHED;
5427 }
5428
SCREEN_OT_workspace_cycle(wmOperatorType * ot)5429 static void SCREEN_OT_workspace_cycle(wmOperatorType *ot)
5430 {
5431 /* identifiers */
5432 ot->name = "Cycle Workspace";
5433 ot->description = "Cycle through workspaces";
5434 ot->idname = "SCREEN_OT_workspace_cycle";
5435
5436 /* api callbacks */
5437 ot->invoke = space_workspace_cycle_invoke;
5438 ot->poll = ED_operator_screenactive;
5439
5440 ot->flag = 0;
5441
5442 RNA_def_enum(ot->srna,
5443 "direction",
5444 space_context_cycle_direction,
5445 SPACE_CONTEXT_CYCLE_NEXT,
5446 "Direction",
5447 "Direction to cycle through");
5448 }
5449
5450 /** \} */
5451
5452 /* -------------------------------------------------------------------- */
5453 /** \name Assigning Operator Types
5454 * \{ */
5455
5456 /* called in spacetypes.c */
ED_operatortypes_screen(void)5457 void ED_operatortypes_screen(void)
5458 {
5459 /* generic UI stuff */
5460 WM_operatortype_append(SCREEN_OT_actionzone);
5461 WM_operatortype_append(SCREEN_OT_repeat_last);
5462 WM_operatortype_append(SCREEN_OT_repeat_history);
5463 WM_operatortype_append(SCREEN_OT_redo_last);
5464
5465 /* screen tools */
5466 WM_operatortype_append(SCREEN_OT_area_move);
5467 WM_operatortype_append(SCREEN_OT_area_split);
5468 WM_operatortype_append(SCREEN_OT_area_join);
5469 WM_operatortype_append(SCREEN_OT_area_options);
5470 WM_operatortype_append(SCREEN_OT_area_dupli);
5471 WM_operatortype_append(SCREEN_OT_area_swap);
5472 WM_operatortype_append(SCREEN_OT_region_quadview);
5473 WM_operatortype_append(SCREEN_OT_region_scale);
5474 WM_operatortype_append(SCREEN_OT_region_toggle);
5475 WM_operatortype_append(SCREEN_OT_region_flip);
5476 WM_operatortype_append(SCREEN_OT_header_toggle_menus);
5477 WM_operatortype_append(SCREEN_OT_region_context_menu);
5478 WM_operatortype_append(SCREEN_OT_screen_set);
5479 WM_operatortype_append(SCREEN_OT_screen_full_area);
5480 WM_operatortype_append(SCREEN_OT_back_to_previous);
5481 WM_operatortype_append(SCREEN_OT_spacedata_cleanup);
5482 WM_operatortype_append(SCREEN_OT_screenshot);
5483 WM_operatortype_append(SCREEN_OT_userpref_show);
5484 WM_operatortype_append(SCREEN_OT_drivers_editor_show);
5485 WM_operatortype_append(SCREEN_OT_info_log_show);
5486 WM_operatortype_append(SCREEN_OT_region_blend);
5487 WM_operatortype_append(SCREEN_OT_space_type_set_or_cycle);
5488 WM_operatortype_append(SCREEN_OT_space_context_cycle);
5489 WM_operatortype_append(SCREEN_OT_workspace_cycle);
5490
5491 /*frame changes*/
5492 WM_operatortype_append(SCREEN_OT_frame_offset);
5493 WM_operatortype_append(SCREEN_OT_frame_jump);
5494 WM_operatortype_append(SCREEN_OT_keyframe_jump);
5495 WM_operatortype_append(SCREEN_OT_marker_jump);
5496
5497 WM_operatortype_append(SCREEN_OT_animation_step);
5498 WM_operatortype_append(SCREEN_OT_animation_play);
5499 WM_operatortype_append(SCREEN_OT_animation_cancel);
5500
5501 /* new/delete */
5502 WM_operatortype_append(SCREEN_OT_new);
5503 WM_operatortype_append(SCREEN_OT_delete);
5504
5505 /* tools shared by more space types */
5506 WM_operatortype_append(ED_OT_undo);
5507 WM_operatortype_append(ED_OT_undo_push);
5508 WM_operatortype_append(ED_OT_redo);
5509 WM_operatortype_append(ED_OT_undo_redo);
5510 WM_operatortype_append(ED_OT_undo_history);
5511
5512 WM_operatortype_append(ED_OT_flush_edits);
5513 }
5514
5515 /** \} */
5516
5517 /* -------------------------------------------------------------------- */
5518 /** \name Operator Key Map
5519 * \{ */
5520
keymap_modal_set(wmKeyConfig * keyconf)5521 static void keymap_modal_set(wmKeyConfig *keyconf)
5522 {
5523 static const EnumPropertyItem modal_items[] = {
5524 {KM_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
5525 {KM_MODAL_APPLY, "APPLY", 0, "Apply", ""},
5526 {KM_MODAL_SNAP_ON, "SNAP", 0, "Snap on", ""},
5527 {KM_MODAL_SNAP_OFF, "SNAP_OFF", 0, "Snap off", ""},
5528 {0, NULL, 0, NULL, NULL},
5529 };
5530
5531 /* Standard Modal keymap ------------------------------------------------ */
5532 wmKeyMap *keymap = WM_modalkeymap_ensure(keyconf, "Standard Modal Map", modal_items);
5533
5534 WM_modalkeymap_assign(keymap, "SCREEN_OT_area_move");
5535 }
5536
blend_file_drop_poll(bContext * UNUSED (C),wmDrag * drag,const wmEvent * UNUSED (event),const char ** UNUSED (r_tooltip))5537 static bool blend_file_drop_poll(bContext *UNUSED(C),
5538 wmDrag *drag,
5539 const wmEvent *UNUSED(event),
5540 const char **UNUSED(r_tooltip))
5541 {
5542 if (drag->type == WM_DRAG_PATH) {
5543 if (drag->icon == ICON_FILE_BLEND) {
5544 return true;
5545 }
5546 }
5547 return false;
5548 }
5549
blend_file_drop_copy(wmDrag * drag,wmDropBox * drop)5550 static void blend_file_drop_copy(wmDrag *drag, wmDropBox *drop)
5551 {
5552 /* copy drag path to properties */
5553 RNA_string_set(drop->ptr, "filepath", drag->path);
5554 }
5555
5556 /* called in spacetypes.c */
ED_keymap_screen(wmKeyConfig * keyconf)5557 void ED_keymap_screen(wmKeyConfig *keyconf)
5558 {
5559 /* Screen Editing ------------------------------------------------ */
5560 WM_keymap_ensure(keyconf, "Screen Editing", 0, 0);
5561
5562 /* Header Editing ------------------------------------------------ */
5563 /* note: this is only used when the cursor is inside the header */
5564 WM_keymap_ensure(keyconf, "Header", 0, 0);
5565
5566 /* Screen General ------------------------------------------------ */
5567 WM_keymap_ensure(keyconf, "Screen", 0, 0);
5568
5569 /* Anim Playback ------------------------------------------------ */
5570 WM_keymap_ensure(keyconf, "Frames", 0, 0);
5571
5572 /* dropbox for entire window */
5573 ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
5574 WM_dropbox_add(lb, "WM_OT_drop_blend_file", blend_file_drop_poll, blend_file_drop_copy);
5575 WM_dropbox_add(lb, "UI_OT_drop_color", UI_drop_color_poll, UI_drop_color_copy);
5576
5577 keymap_modal_set(keyconf);
5578 }
5579
5580 /** \} */
5581