From 8b049e4c2a53e51db295ab300bf8377d63086708 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Thu, 22 Apr 2021 19:57:49 -0700 Subject: UI: Join or Close Any Screen Area Corner action zones allow joining any neighbors. New 'Area Close' operator. Improved Header Context Menu. Differential Revision: https://developer.blender.org/D8084 Reviewed by Campbell Barton --- source/blender/editors/screen/screen_edit.c | 190 +++++++++++++++++++++++----- 1 file changed, 160 insertions(+), 30 deletions(-) (limited to 'source/blender/editors/screen/screen_edit.c') diff --git a/source/blender/editors/screen/screen_edit.c b/source/blender/editors/screen/screen_edit.c index 7ad8eada3b9..8e51772d801 100644 --- a/source/blender/editors/screen/screen_edit.c +++ b/source/blender/editors/screen/screen_edit.c @@ -284,42 +284,37 @@ void screen_new_activate_prepare(const wmWindow *win, bScreen *screen_new) /* used with join operator */ int area_getorientation(ScrArea *area, ScrArea *sb) { - if (area == NULL || sb == NULL) { + if (area == NULL || sb == NULL || area == sb) { return -1; } - ScrVert *saBL = area->v1; - ScrVert *saTL = area->v2; - ScrVert *saTR = area->v3; - ScrVert *saBR = area->v4; + vec2s saBL = area->v1->vec; + vec2s saTL = area->v2->vec; + vec2s saTR = area->v3->vec; + vec2s saBR = area->v4->vec; - ScrVert *sbBL = sb->v1; - ScrVert *sbTL = sb->v2; - ScrVert *sbTR = sb->v3; - ScrVert *sbBR = sb->v4; + vec2s sbBL = sb->v1->vec; + vec2s sbTL = sb->v2->vec; + vec2s sbTR = sb->v3->vec; + vec2s sbBR = sb->v4->vec; - if (saBL->vec.x == sbBR->vec.x && saTL->vec.x == sbTR->vec.x) { /* area to right of sb = W */ - if ((abs(saBL->vec.y - sbBR->vec.y) <= AREAJOINTOLERANCE) && - (abs(saTL->vec.y - sbTR->vec.y) <= AREAJOINTOLERANCE)) { + if (saBL.x == sbBR.x && saTL.x == sbTR.x) { /* area to right of sb = W */ + if ((MIN2(saTL.y, sbTR.y) - MAX2(saBL.y, sbBR.y)) > AREAJOINTOLERANCEY) { return 0; } } - else if (saTL->vec.y == sbBL->vec.y && - saTR->vec.y == sbBR->vec.y) { /* area to bottom of sb = N */ - if ((abs(saTL->vec.x - sbBL->vec.x) <= AREAJOINTOLERANCE) && - (abs(saTR->vec.x - sbBR->vec.x) <= AREAJOINTOLERANCE)) { + else if (saTL.y == sbBL.y && saTR.y == sbBR.y) { /* area to bottom of sb = N */ + if ((MIN2(saTR.x, sbBR.x) - MAX2(saTL.x, sbBL.x)) > AREAJOINTOLERANCEX) { return 1; } } - else if (saTR->vec.x == sbTL->vec.x && saBR->vec.x == sbBL->vec.x) { /* area to left of sb = E */ - if ((abs(saTR->vec.y - sbTL->vec.y) <= AREAJOINTOLERANCE) && - (abs(saBR->vec.y - sbBL->vec.y) <= AREAJOINTOLERANCE)) { + else if (saTR.x == sbTL.x && saBR.x == sbBL.x) { /* area to left of sb = E */ + if ((MIN2(saTR.y, sbTL.y) - MAX2(saBR.y, sbBL.y)) > AREAJOINTOLERANCEY) { return 2; } } - else if (saBL->vec.y == sbTL->vec.y && saBR->vec.y == sbTR->vec.y) { /* area on top of sb = S*/ - if ((abs(saBL->vec.x - sbTL->vec.x) <= AREAJOINTOLERANCE) && - (abs(saBR->vec.x - sbTR->vec.x) <= AREAJOINTOLERANCE)) { + else if (saBL.y == sbTL.y && saBR.y == sbTR.y) { /* area on top of sb = S */ + if ((MIN2(saBR.x, sbTR.x) - MAX2(saBL.x, sbTL.x)) > AREAJOINTOLERANCEX) { return 3; } } @@ -327,6 +322,35 @@ int area_getorientation(ScrArea *area, ScrArea *sb) return -1; } +/* Get alignment offset of adjacent areas. 'dir' value is like area_getorientation(). */ +void area_getoffsets(ScrArea *area, ScrArea *sb, const int dir, int *offset1, int *offset2) +{ + if (area == NULL || sb == NULL) { + *offset1 = INT_MAX; + *offset2 = INT_MAX; + } + else if (dir == 0) { /* West: sa on right and sb to the left. */ + *offset1 = sb->v3->vec.y - area->v2->vec.y; + *offset2 = sb->v4->vec.y - area->v1->vec.y; + } + else if (dir == 1) { /* North: sa below and sb above. */ + *offset1 = area->v2->vec.x - sb->v1->vec.x; + *offset2 = area->v3->vec.x - sb->v4->vec.x; + } + else if (dir == 2) { /* East: sa on left and sb to the right. */ + *offset1 = sb->v2->vec.y - area->v3->vec.y; + *offset2 = sb->v1->vec.y - area->v4->vec.y; + } + else if (dir == 3) { /* South: sa above and sb below. */ + *offset1 = area->v1->vec.x - sb->v2->vec.x; + *offset2 = area->v4->vec.x - sb->v3->vec.x; + } + else { + *offset1 = INT_MAX; + *offset2 = INT_MAX; + } +} + /* Screen verts with horizontal position equal to from_x are moved to to_x. */ static void screen_verts_halign(const wmWindow *win, const bScreen *screen, @@ -390,18 +414,24 @@ static void screen_areas_align( } } -/* Helper function to join 2 areas, it has a return value, 0=failed 1=success - * used by the split, join operators - */ -int screen_area_join(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) +/* Simple join of two areas without any splitting. Will return false if not possible. */ +static bool screen_area_join_aligned(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) { int dir = area_getorientation(sa1, sa2); - if (dir == -1) { - return 0; + return false; + } + + int offset1; + int offset2; + area_getoffsets(sa1, sa2, dir, &offset1, &offset2); + + int tolerance = ELEM(dir, 0, 2) ? AREAJOINTOLERANCEY : AREAJOINTOLERANCEX; + if ((abs(offset1) >= tolerance) || (abs(offset2) >= tolerance)) { + return false; } - /* Align areas if they are not. Do sanity checking before getting here. */ + /* Align areas if they are not. */ screen_areas_align(C, screen, sa1, sa2, dir); if (dir == 0) { /* sa1 to right of sa2 = W */ @@ -434,7 +464,107 @@ int screen_area_join(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) /* Update preview thumbnail */ BKE_icon_changed(screen->id.icon_id); - return 1; + return true; +} + +/* Slice off and return new area. "Reverse" gives right/bottom, rather than left/top. */ +static ScrArea *screen_area_trim( + bContext *C, bScreen *screen, ScrArea **area, int size, int dir, bool reverse) +{ + bool vertical = ELEM(dir, 1, 3); + if (abs(size) < (vertical ? AREAJOINTOLERANCEX : AREAJOINTOLERANCEY)) { + return NULL; + } + + /* Measurement with ScrVerts because winx and winy might not be correct at this time. */ + float fac = abs(size) / (float)(vertical ? ((*area)->v3->vec.x - (*area)->v1->vec.x) : + ((*area)->v3->vec.y - (*area)->v1->vec.y)); + fac = (reverse == vertical) ? 1.0f - fac : fac; + ScrArea *newsa = area_split(CTX_wm_window(C), screen, *area, vertical ? 'v' : 'h', fac, 1); + + /* area_split always returns smallest of the two areas, so might have to swap. */ + if (((fac > 0.5f) == vertical) != reverse) { + ScrArea *temp = *area; + *area = newsa; + newsa = temp; + } + + return newsa; +} + +/* Join any two neighboring areas. Might create new areas, kept if over min_remainder. */ +static bool screen_area_join_ex( + bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, bool close_all_remainders) +{ + int dir = area_getorientation(sa1, sa2); + if (dir == -1) { + return false; + } + + int offset1; + int offset2; + area_getoffsets(sa1, sa2, dir, &offset1, &offset2); + + /* Split Left/Top into new area if overhanging. */ + ScrArea *side1 = screen_area_trim(C, screen, (offset1 > 0) ? &sa2 : &sa1, offset1, dir, false); + + /* Split Right/Bottom into new area if overhanging. */ + ScrArea *side2 = screen_area_trim(C, screen, (offset2 > 0) ? &sa1 : &sa2, offset2, dir, true); + + /* The two areas now line up, so join them. */ + screen_area_join_aligned(C, screen, sa1, sa2); + + if (close_all_remainders || offset1 < 0 || offset2 > 0) { + /* Close both if trimiming sa1. */ + screen_area_close(C, screen, side1); + screen_area_close(C, screen, side2); + } + + BKE_icon_changed(screen->id.icon_id); + return true; +} + +/* Join any two neighboring areas. Might involve complex changes. */ +int screen_area_join(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) +{ + return screen_area_join_ex(C, screen, sa1, sa2, false); +} + +/* Close a screen area, allowing any neighbor to take its place. */ +bool screen_area_close(struct bContext *C, bScreen *screen, ScrArea *area) +{ + if (area == NULL) { + return false; + } + + ScrArea *sa2 = NULL; + + /* Find the most-aligned joinable area. Larger size breaks ties. */ + int min_alignment = INT_MAX; + int max_size = 0; + LISTBASE_FOREACH (ScrArea *, ar, &screen->areabase) { + int dir = area_getorientation(area, ar); + if (dir != -1) { + int offset1; + int offset2; + area_getoffsets(area, ar, dir, &offset1, &offset2); + int area_alignment = abs(offset1) + abs(offset2); + if (area_alignment < min_alignment) { + min_alignment = area_alignment; + max_size = ar->winx * ar->winy; + sa2 = ar; + } + else if (area_alignment == min_alignment) { + int area_size = ar->winx * ar->winy; + if (area_size > max_size) { + max_size = area_size; + sa2 = ar; + } + } + } + } + + return screen_area_join_ex(C, screen, sa2, area, true); } /* ****************** EXPORTED API TO OTHER MODULES *************************** */ -- cgit v1.2.3