diff options
Diffstat (limited to 'source/blender/editors/interface/interface_align.c')
-rw-r--r-- | source/blender/editors/interface/interface_align.c | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/source/blender/editors/interface/interface_align.c b/source/blender/editors/interface/interface_align.c new file mode 100644 index 00000000000..500744c366d --- /dev/null +++ b/source/blender/editors/interface/interface_align.c @@ -0,0 +1,651 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2015 by Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation 2002-2008, full recode. + * Bastien Montagne 2015, full recode. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/interface/interface_align.c + * \ingroup edinterface + */ + +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_alloca.h" +#include "BLI_math.h" +#include "BLI_rect.h" + +#include "UI_interface.h" + +#include "interface_intern.h" + +#ifdef USE_UIBUT_SPATIAL_ALIGN + +/** + * This struct stores a (simplified) 2D representation of all buttons of a same align group, with their + * immediate neighbors (if found), and needed value to compute 'stitching' of aligned buttons. + * + * \note This simplistic struct cannot fully represent complex layouts where buttons share some 'align space' with + * several others (see schema below), we'd need linked list and more complex code to handle that. + * However, looks like we can do without that for now, which is rather lucky! + * + * <pre> + * +-----------+-----------+ + * | BUT 1 | BUT 2 | BUT 3 has two 'top' neighbors... + * |-----------------------| => In practice, we only store one of BUT 1 or 2 (which ones is not + * | BUT 3 | really deterministic), and assume the other stores a ref to BUT 3. + * +-----------------------+ + * </pre> + * + * This will probably not work in all possible cases, but not sure we want to support such exotic cases anyway. + */ +typedef struct ButAlign { + uiBut *but; + + /* Neighbor buttons */ + struct ButAlign *neighbors[4]; + + /* Pointers to coordinates (rctf values) of the button. */ + float *borders[4]; + + /* Distances to the neighbors. */ + float dists[4]; + + /* Flags, used to mark whether we should 'stitch' the corners of this button with its neighbors' ones. */ + char flags[4]; +} ButAlign; + +/* Side-related enums and flags. */ +enum { + /* Sides (used as indices, order is **crucial**, this allows us to factorize code in a loop over the four sides). */ + LEFT = 0, + TOP = 1, + RIGHT = 2, + DOWN = 3, + TOTSIDES = 4, + + /* Stitch flags, built from sides values. */ + STITCH_LEFT = 1 << LEFT, + STITCH_TOP = 1 << TOP, + STITCH_RIGHT = 1 << RIGHT, + STITCH_DOWN = 1 << DOWN, +}; + +/* Mapping between 'our' sides and 'public' UI_BUT_ALIGN flags, order must match enum above. */ +#define SIDE_TO_UI_BUT_ALIGN {UI_BUT_ALIGN_LEFT, UI_BUT_ALIGN_TOP, UI_BUT_ALIGN_RIGHT, UI_BUT_ALIGN_DOWN} + +/* Given one side, compute the three other ones */ +#define SIDE1(_s) (((_s) + 1) % TOTSIDES) +#define OPPOSITE(_s) (((_s) + 2) % TOTSIDES) +#define SIDE2(_s) (((_s) + 3) % TOTSIDES) + +/* 0: LEFT/RIGHT sides; 1 = TOP/DOWN sides. */ +#define IS_COLUMN(_s) ((_s) % 2) + +/* Stitch flag from side value. */ +#define STITCH(_s) (1 << (_s)) + +/* Max distance between to buttons for them to be 'mergeable'. */ +#define MAX_DELTA 0.45f * max_ii(UI_UNIT_Y, UI_UNIT_X) + +bool ui_but_can_align(const uiBut *but) +{ + return ( + !ELEM(but->type, UI_BTYPE_LABEL, UI_BTYPE_CHECKBOX, UI_BTYPE_CHECKBOX_N, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE) && + (BLI_rctf_size_x(&but->rect) > 0.0f) && (BLI_rctf_size_y(&but->rect) > 0.0f)); +} + +/** + * This function checks a pair of buttons (assumed in a same align group), and if they are neighbors, + * set needed data accordingly. + * + * \note It is designed to be called in total random order of buttons. Order-based optimizations are done by caller. + */ +static void block_align_proximity_compute(ButAlign *butal, ButAlign *butal_other) +{ + /* That's the biggest gap between two borders to consider them 'alignable'. */ + const float max_delta = MAX_DELTA; + float delta, delta_side_opp; + int side, side_opp; + + const bool butal_can_align = ui_but_can_align(butal->but); + const bool butal_other_can_align = ui_but_can_align(butal_other->but); + + const bool buts_share[2] = { + /* Sharing same line? */ + !((*butal->borders[DOWN] >= *butal_other->borders[TOP]) || + (*butal->borders[TOP] <= *butal_other->borders[DOWN])), + /* Sharing same column? */ + !((*butal->borders[LEFT] >= *butal_other->borders[RIGHT]) || + (*butal->borders[RIGHT] <= *butal_other->borders[LEFT])), + }; + + /* Early out in case buttons share no column or line, or if none can align... */ + if (!(buts_share[0] || buts_share[1]) || !(butal_can_align || butal_other_can_align)) { + return; + } + + for (side = 0; side < RIGHT; side++) { + /* We are only interested in buttons which share a same line (LEFT/RIGHT sides) or column (TOP/DOWN sides). */ + if (buts_share[IS_COLUMN(side)]) { + side_opp = OPPOSITE(side); + + /* We check both opposite sides at once, because with very small buttons, delta could be below max_delta for + * the wrong side (that is, in horizontal case, the total width of two buttons can be below max_delta). + * We rely on exact zero value here as an 'already processed' flag, so ensure we never actually + * set a zero value at this stage. FLT_MIN is zero-enough for UI position computing. ;) */ + delta = max_ff(fabsf(*butal->borders[side] - *butal_other->borders[side_opp]), FLT_MIN); + delta_side_opp = max_ff(fabsf(*butal->borders[side_opp] - *butal_other->borders[side]), FLT_MIN); + if (delta_side_opp < delta) { + SWAP(int, side, side_opp); + delta = delta_side_opp; + } + + if (delta < max_delta) { + /* We are only interested in neighbors that are at least as close as already found ones. */ + if (delta <= butal->dists[side]) { + { + /* We found an as close or closer neighbor. + * If both buttons are alignable, we set them as each other neighbors. + * Else, we have an unalignable one, we need to reset the others matching neighbor to NULL + * if its 'proximity distance' is really lower with current one. + * + * NOTE: We cannot only execute that piece of code in case we found a **closer** neighbor, + * due to the limited way we represent neighbors (buttons only know **one** neighbor + * on each side, when they can actually have several ones), it would prevent + * some buttons to be properly 'neighborly-initialized'. */ + if (butal_can_align && butal_other_can_align) { + butal->neighbors[side] = butal_other; + butal_other->neighbors[side_opp] = butal; + } + else if (butal_can_align && (delta < butal->dists[side])) { + butal->neighbors[side] = NULL; + } + else if (butal_other_can_align && (delta < butal_other->dists[side_opp])) { + butal_other->neighbors[side_opp] = NULL; + } + butal->dists[side] = butal_other->dists[side_opp] = delta; + } + + if (butal_can_align && butal_other_can_align) { + const int side_s1 = SIDE1(side); + const int side_s2 = SIDE2(side); + + const int stitch = STITCH(side); + const int stitch_opp = STITCH(side_opp); + + if (butal->neighbors[side] == NULL) { + butal->neighbors[side] = butal_other; + } + if (butal_other->neighbors[side_opp] == NULL) { + butal_other->neighbors[side_opp] = butal; + } + + /* We have a pair of neighbors, we have to check whether we can stitch their matching corners. + * E.g. if butal_other is on the left of butal (that is, side == LEFT), + * if both TOP (side_s1) coordinates of buttons are close enough, we can stitch + * their upper matching corners, and same for DOWN (side_s2) side. */ + delta = fabsf(*butal->borders[side_s1] - *butal_other->borders[side_s1]); + if (delta < max_delta) { + butal->flags[side_s1] |= stitch; + butal_other->flags[side_s1] |= stitch_opp; + } + delta = fabsf(*butal->borders[side_s2] - *butal_other->borders[side_s2]); + if (delta < max_delta) { + butal->flags[side_s2] |= stitch; + butal_other->flags[side_s2] |= stitch_opp; + } + } + } + /* We assume two buttons can only share one side at most - for until we have sperical UI... */ + return; + } + } + } +} + +/** + * This function takes care of case described in this schema: + * + * <pre> + * +-----------+-----------+ + * | BUT 1 | BUT 2 | + * |-----------------------+ + * | BUT 3 | + * +-----------+ + * </pre> + * + * Here, BUT 3 RIGHT side would not get 'dragged' to align with BUT 1 RIGHT side, since BUT 3 has not RIGHT neighbor. + * So, this function, when called with BUT 1, will 'walk' the whole column in \a side_s1 direction (TOP or DOWN when + * called for RIGHT side), and force buttons like BUT 3 to align as needed, if BUT 1 and BUT 3 were detected as needing + * top-right corner stitching in \a block_align_proximity_compute() step. + * + * \note To avoid doing this twice, some stitching flags are cleared to break the 'stitching connection' + * between neighbors. + */ +static void block_align_stitch_neighbors( + ButAlign *butal, + const int side, const int side_opp, const int side_s1, const int side_s2, + const int align, const int align_opp, const float co) +{ + ButAlign *butal_neighbor; + + const int stitch_s1 = STITCH(side_s1); + const int stitch_s2 = STITCH(side_s2); + + /* We have to check stitching flags on both sides of the stitching, since we only clear one of them flags to break + * any future loop on same 'columns/side' case. + * Also, if butal is spanning over several rows or columns of neighbors, it may have both of its stitching flags + * set, but would not be the case of its immediate neighbor! */ + while ((butal->flags[side] & stitch_s1) && + (butal = butal->neighbors[side_s1]) && + (butal->flags[side] & stitch_s2)) + { + butal_neighbor = butal->neighbors[side]; + + /* If we actually do have a neighbor, we directly set its values accordingly, and clear its matching 'dist' + * to prevent it being set again later... */ + if (butal_neighbor) { + butal->but->drawflag |= align; + butal_neighbor->but->drawflag |= align_opp; + *butal_neighbor->borders[side_opp] = co; + butal_neighbor->dists[side_opp] = 0.0f; + } + /* See definition of UI_BUT_ALIGN_STITCH_LEFT/TOP for reason of this... */ + else if (side == LEFT) { + butal->but->drawflag |= UI_BUT_ALIGN_STITCH_LEFT; + } + else if (side == TOP) { + butal->but->drawflag |= UI_BUT_ALIGN_STITCH_TOP; + } + *butal->borders[side] = co; + butal->dists[side] = 0.0f; + /* Clearing one of the 'flags pair' here is enough to prevent this loop running on + * the same column, side and direction again. */ + butal->flags[side] &= ~stitch_s2; + } +} + +/** + * Helper to sort ButAlign items by: + * - Their align group. + * - Their vertical position. + * - Their horizontal position. + */ +static int ui_block_align_butal_cmp(const void *a, const void *b) +{ + const ButAlign *butal = a; + const ButAlign *butal_other = b; + + /* Sort by align group. */ + if (butal->but->alignnr != butal_other->but->alignnr) { + return butal->but->alignnr - butal_other->but->alignnr; + } + + /* Sort vertically. + * Note that Y of buttons is decreasing (first buttons have higher Y value than later ones). */ + if (*butal->borders[TOP] != *butal_other->borders[TOP]) { + return (*butal_other->borders[TOP] > *butal->borders[TOP]) ? 1 : -1; + } + + /* Sort horizontally. */ + if (*butal->borders[LEFT] != *butal_other->borders[LEFT]) { + return (*butal->borders[LEFT] > *butal_other->borders[LEFT]) ? 1 : -1; + } + + /* XXX We cannot actually assert here, since in some very compressed space cases, stupid UI code produces + * widgets which have the same TOP and LEFT positions... + * We do not care really, because this happens when UI is way too small to be usable anyway. */ + /* BLI_assert(0); */ + return 0; +} + +/** + * Compute the alignment of all 'align groups' of buttons in given block. + * + * This is using an order-independent algorithm, i.e. alignment of buttons should be OK regardless of order in which + * they are added to the block. + */ +void ui_block_align_calc(uiBlock *block) +{ + uiBut *but; + int num_buttons = 0; + + const int sides_to_ui_but_align_flags[4] = SIDE_TO_UI_BUT_ALIGN; + + ButAlign *butal_array; + ButAlign *butal, *butal_other; + int side; + int i, j; + + /* First loop: we count number of buttons belonging to an align group, and clear their align flag. */ + for (but = block->buttons.first; but; but = but->next) { + /* Clear old align flags. */ + but->drawflag &= ~UI_BUT_ALIGN_ALL; + + if (but->alignnr != 0) { + num_buttons++; + } + } + + if (num_buttons < 2) { + /* No need to go further if we have nothing to align... */ + return; + } + + butal_array = alloca(sizeof(*butal_array) * (size_t)num_buttons); + memset(butal_array, 0, sizeof(*butal_array) * (size_t)num_buttons); + + /* Second loop: we initialize our ButAlign data for each button. */ + for (but = block->buttons.first, butal = butal_array; but; but = but->next) { + if (but->alignnr != 0) { + butal->but = but; + butal->borders[LEFT] = &but->rect.xmin; + butal->borders[RIGHT] = &but->rect.xmax; + butal->borders[DOWN] = &but->rect.ymin; + butal->borders[TOP] = &but->rect.ymax; + copy_v4_fl(butal->dists, FLT_MAX); + butal++; + } + } + + /* This will give us ButAlign items regrouped by align group, vertical and horizontal location. + * Note that, given how buttons are defined in UI code, butal_array shall already be "nearly sorted"... */ + qsort(butal_array, (size_t)num_buttons, sizeof(*butal_array), ui_block_align_butal_cmp); + + /* Third loop: for each pair of buttons in the same align group, we compute their potential proximity. + * Note that each pair is checked only once, and that we break early in case we know all remaining pairs will + * always be too far away. */ + for (i = 0, butal = butal_array; i < num_buttons; i++, butal++) { + const short alignnr = butal->but->alignnr; + + for (j = i + 1, butal_other = &butal_array[i + 1]; j < num_buttons; j++, butal_other++) { + const float max_delta = MAX_DELTA; + + /* Since they are sorted, buttons after current butal can only be of same or higher group, and once + * they are not of same group, we know we can break this sub-loop and start checking with next butal. */ + if (butal_other->but->alignnr != alignnr) { + break; + } + + /* Since they are sorted vertically first, buttons after current butal can only be at same or lower height, + * and once they are lower than a given threshold, we know we can break this sub-loop and + * start checking with next butal. */ + if ((*butal->borders[DOWN] - *butal_other->borders[TOP]) > max_delta) { + break; + } + + block_align_proximity_compute(butal, butal_other); + } + } + + /* Fourth loop: we have all our 'aligned' buttons as a 'map' in butal_array. We need to: + * - update their relevant coordinates to stitch them. + * - assign them valid flags. + */ + for (i = 0; i < num_buttons; i++) { + butal = &butal_array[i]; + + for (side = 0; side < TOTSIDES; side++) { + butal_other = butal->neighbors[side]; + + if (butal_other) { + const int side_opp = OPPOSITE(side); + const int side_s1 = SIDE1(side); + const int side_s2 = SIDE2(side); + + const int align = sides_to_ui_but_align_flags[side]; + const int align_opp = sides_to_ui_but_align_flags[side_opp]; + + float co; + + butal->but->drawflag |= align; + butal_other->but->drawflag |= align_opp; + if (butal->dists[side]) { + float *delta = &butal->dists[side]; + + if (*butal->borders[side] < *butal_other->borders[side_opp]) { + *delta *= 0.5f; + } + else { + *delta *= -0.5f; + } + co = (*butal->borders[side] += *delta); + + if (butal_other->dists[side_opp]) { + BLI_assert(butal_other->dists[side_opp] * 0.5f == fabsf(*delta)); + *butal_other->borders[side_opp] = co; + butal_other->dists[side_opp] = 0.0f; + } + *delta = 0.0f; + } + else { + co = *butal->borders[side]; + } + + block_align_stitch_neighbors(butal, side, side_opp, side_s1, side_s2, align, align_opp, co); + block_align_stitch_neighbors(butal, side, side_opp, side_s2, side_s1, align, align_opp, co); + } + } + } +} + +#undef SIDE_TO_UI_BUT_ALIGN +#undef SIDE1 +#undef OPPOSITE +#undef SIDE2 +#undef IS_COLUMN +#undef STITCH +#undef MAX_DELTA + +#else + +bool ui_but_can_align(uiBut *but) +{ + return !ELEM(but->type, UI_BTYPE_LABEL, UI_BTYPE_CHECKBOX, UI_BTYPE_CHECKBOX_N, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE); +} + +static bool buts_are_horiz(uiBut *but1, uiBut *but2) +{ + float dx, dy; + + /* simple case which can fail if buttons shift apart + * with proportional layouts, see: [#38602] */ + if ((but1->rect.ymin == but2->rect.ymin) && + (but1->rect.xmin != but2->rect.xmin)) + { + return true; + } + + dx = fabsf(but1->rect.xmax - but2->rect.xmin); + dy = fabsf(but1->rect.ymin - but2->rect.ymax); + + return (dx <= dy); +} + +static void ui_block_align_calc_but(uiBut *first, short nr) +{ + uiBut *prev, *but = NULL, *next; + int flag = 0, cols = 0, rows = 0; + + /* auto align */ + + for (but = first; but && but->alignnr == nr; but = but->next) { + if (but->next && but->next->alignnr == nr) { + if (buts_are_horiz(but, but->next)) cols++; + else rows++; + } + } + + /* rows == 0: 1 row, cols == 0: 1 column */ + + /* note; how it uses 'flag' in loop below (either set it, or OR it) is confusing */ + for (but = first, prev = NULL; but && but->alignnr == nr; prev = but, but = but->next) { + next = but->next; + if (next && next->alignnr != nr) + next = NULL; + + /* clear old flag */ + but->drawflag &= ~UI_BUT_ALIGN; + + if (flag == 0) { /* first case */ + if (next) { + if (buts_are_horiz(but, next)) { + if (rows == 0) + flag = UI_BUT_ALIGN_RIGHT; + else + flag = UI_BUT_ALIGN_DOWN | UI_BUT_ALIGN_RIGHT; + } + else { + flag = UI_BUT_ALIGN_DOWN; + } + } + } + else if (next == NULL) { /* last case */ + if (prev) { + if (buts_are_horiz(prev, but)) { + if (rows == 0) + flag = UI_BUT_ALIGN_LEFT; + else + flag = UI_BUT_ALIGN_TOP | UI_BUT_ALIGN_LEFT; + } + else { + flag = UI_BUT_ALIGN_TOP; + } + } + } + else if (buts_are_horiz(but, next)) { + /* check if this is already second row */ + if (prev && buts_are_horiz(prev, but) == 0) { + flag &= ~UI_BUT_ALIGN_LEFT; + flag |= UI_BUT_ALIGN_TOP; + /* exception case: bottom row */ + if (rows > 0) { + uiBut *bt = but; + while (bt && bt->alignnr == nr) { + if (bt->next && bt->next->alignnr == nr && buts_are_horiz(bt, bt->next) == 0) { + break; + } + bt = bt->next; + } + if (bt == NULL || bt->alignnr != nr) flag = UI_BUT_ALIGN_TOP | UI_BUT_ALIGN_RIGHT; + } + } + else { + flag |= UI_BUT_ALIGN_LEFT; + } + } + else { + if (cols == 0) { + flag |= UI_BUT_ALIGN_TOP; + } + else { /* next button switches to new row */ + + if (prev && buts_are_horiz(prev, but)) + flag |= UI_BUT_ALIGN_LEFT; + else { + flag &= ~UI_BUT_ALIGN_LEFT; + flag |= UI_BUT_ALIGN_TOP; + } + + if ((flag & UI_BUT_ALIGN_TOP) == 0) { /* still top row */ + if (prev) { + if (next && buts_are_horiz(but, next)) + flag = UI_BUT_ALIGN_DOWN | UI_BUT_ALIGN_LEFT | UI_BUT_ALIGN_RIGHT; + else { + /* last button in top row */ + flag = UI_BUT_ALIGN_DOWN | UI_BUT_ALIGN_LEFT; + } + } + else + flag |= UI_BUT_ALIGN_DOWN; + } + else + flag |= UI_BUT_ALIGN_TOP; + } + } + + but->drawflag |= flag; + + /* merge coordinates */ + if (prev) { + /* simple cases */ + if (rows == 0) { + but->rect.xmin = (prev->rect.xmax + but->rect.xmin) / 2.0f; + prev->rect.xmax = but->rect.xmin; + } + else if (cols == 0) { + but->rect.ymax = (prev->rect.ymin + but->rect.ymax) / 2.0f; + prev->rect.ymin = but->rect.ymax; + } + else { + if (buts_are_horiz(prev, but)) { + but->rect.xmin = (prev->rect.xmax + but->rect.xmin) / 2.0f; + prev->rect.xmax = but->rect.xmin; + /* copy height too */ + but->rect.ymax = prev->rect.ymax; + } + else if (prev->prev && buts_are_horiz(prev->prev, prev) == 0) { + /* the previous button is a single one in its row */ + but->rect.ymax = (prev->rect.ymin + but->rect.ymax) / 2.0f; + prev->rect.ymin = but->rect.ymax; + + but->rect.xmin = prev->rect.xmin; + if (next && buts_are_horiz(but, next) == 0) + but->rect.xmax = prev->rect.xmax; + } + else { + /* the previous button is not a single one in its row */ + but->rect.ymax = prev->rect.ymin; + } + } + } + } +} + +void ui_block_align_calc(uiBlock *block) +{ + uiBut *but; + short nr; + + /* align buttons with same align nr */ + for (but = block->buttons.first; but; ) { + if (but->alignnr) { + nr = but->alignnr; + ui_block_align_calc_but(but, nr); + + /* skip with same number */ + for (; but && but->alignnr == nr; but = but->next) { + /* pass */ + } + + if (!but) { + break; + } + } + else { + but = but->next; + } + } +} +#endif |