Blender V5.0
interface_align.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2015 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "DNA_screen_types.h"
10#include "DNA_userdef_types.h"
11
12#include "BLI_listbase.h"
13#include "BLI_math_vector.h"
15#include "BLI_rect.h"
16#include "BLI_vector.hh"
17
18#include "interface_intern.hh"
19
20#include "MEM_guardedalloc.h"
21
43struct ButAlign {
45
46 /* Neighbor buttons */
48
49 /* Pointers to coordinates (rctf values) of the button. */
50 std::array<float *, 4> borders;
51
52 /* Distances to the neighbors. */
54
55 /* Flags, used to mark whether we should 'stitch'
56 * the corners of this button with its neighbors' ones. */
58};
59
60/* Side-related enums and flags. */
61enum {
62 /* Sides (used as indices, order is **crucial**,
63 * this allows us to factorize code in a loop over the four sides). */
64 LEFT = 0,
65 TOP = 1,
66 RIGHT = 2,
67 DOWN = 3,
69
70 /* Stitch flags, built from sides values. */
75};
76
77/* Mapping between 'our' sides and 'public' UI_BUT_ALIGN flags, order must match enum above. */
78#define SIDE_TO_UI_BUT_ALIGN \
79 {UI_BUT_ALIGN_LEFT, UI_BUT_ALIGN_TOP, UI_BUT_ALIGN_RIGHT, UI_BUT_ALIGN_DOWN}
80
81/* Given one side, compute the three other ones */
82#define SIDE1(_s) (((_s) + 1) % TOTSIDES)
83#define OPPOSITE(_s) (((_s) + 2) % TOTSIDES)
84#define SIDE2(_s) (((_s) + 3) % TOTSIDES)
85
86/* 0: LEFT/RIGHT sides; 1 = TOP/DOWN sides. */
87#define IS_COLUMN(_s) ((_s) % 2)
88
89/* Stitch flag from side value. */
90#define STITCH(_s) (1 << (_s))
91
92/* Max distance between to buttons for them to be 'mergeable'. */
93#define MAX_DELTA 0.45f * max_ii(UI_UNIT_Y, UI_UNIT_X)
94
95bool ui_but_can_align(const uiBut *but)
96{
97 const bool btype_can_align = !ELEM(but->type,
105 return (btype_can_align && !BLI_rctf_is_empty(&but->rect));
106}
107
115static void block_align_proximity_compute(ButAlign *butal, ButAlign *butal_other)
116{
117 /* That's the biggest gap between two borders to consider them 'alignable'. */
118 const float max_delta = MAX_DELTA;
119 float delta, delta_side_opp;
120 int side, side_opp;
121
122 const bool butal_can_align = ui_but_can_align(butal->but);
123 const bool butal_other_can_align = ui_but_can_align(butal_other->but);
124
125 const bool buts_share[2] = {
126 /* Sharing same line? */
127 !((*butal->borders[DOWN] >= *butal_other->borders[TOP]) ||
128 (*butal->borders[TOP] <= *butal_other->borders[DOWN])),
129 /* Sharing same column? */
130 !((*butal->borders[LEFT] >= *butal_other->borders[RIGHT]) ||
131 (*butal->borders[RIGHT] <= *butal_other->borders[LEFT])),
132 };
133
134 /* Early out in case buttons share no column or line, or if none can align... */
135 if (!(buts_share[0] || buts_share[1]) || !(butal_can_align || butal_other_can_align)) {
136 return;
137 }
138
139 for (side = 0; side < RIGHT; side++) {
140 /* We are only interested in buttons which share a same line
141 * (LEFT/RIGHT sides) or column (TOP/DOWN sides). */
142 if (buts_share[IS_COLUMN(side)]) {
143 side_opp = OPPOSITE(side);
144
145 /* We check both opposite sides at once, because with very small buttons,
146 * delta could be below max_delta for the wrong side
147 * (that is, in horizontal case, the total width of two buttons can be below max_delta).
148 * We rely on exact zero value here as an 'already processed' flag,
149 * so ensure we never actually set a zero value at this stage.
150 * FLT_MIN is zero-enough for UI position computing. ;) */
151 delta = max_ff(fabsf(*butal->borders[side] - *butal_other->borders[side_opp]), FLT_MIN);
152 delta_side_opp = max_ff(fabsf(*butal->borders[side_opp] - *butal_other->borders[side]),
153 FLT_MIN);
154 if (delta_side_opp < delta) {
155 std::swap(side, side_opp);
156 delta = delta_side_opp;
157 }
158
159 if (delta < max_delta) {
160 /* We are only interested in neighbors that are
161 * at least as close as already found ones. */
162 if (delta <= butal->dists[side]) {
163 {
164 /* We found an as close or closer neighbor.
165 * If both buttons are alignable, we set them as each other neighbors.
166 * Else, we have an unalignable one, we need to reset the others matching
167 * neighbor to nullptr if its 'proximity distance'
168 * is really lower with current one.
169 *
170 * NOTE: We cannot only execute that piece of code in case we found a
171 * **closer** neighbor, due to the limited way we represent neighbors
172 * (buttons only know **one** neighbor on each side, when they can
173 * actually have several ones), it would prevent some buttons to be
174 * properly 'neighborly-initialized'. */
175 if (butal_can_align && butal_other_can_align) {
176 butal->neighbors[side] = butal_other;
177 butal_other->neighbors[side_opp] = butal;
178 }
179 else if (butal_can_align && (delta < butal->dists[side])) {
180 butal->neighbors[side] = nullptr;
181 }
182 else if (butal_other_can_align && (delta < butal_other->dists[side_opp])) {
183 butal_other->neighbors[side_opp] = nullptr;
184 }
185 butal->dists[side] = butal_other->dists[side_opp] = delta;
186 }
187
188 if (butal_can_align && butal_other_can_align) {
189 const int side_s1 = SIDE1(side);
190 const int side_s2 = SIDE2(side);
191
192 const int stitch = STITCH(side);
193 const int stitch_opp = STITCH(side_opp);
194
195 if (butal->neighbors[side] == nullptr) {
196 butal->neighbors[side] = butal_other;
197 }
198 if (butal_other->neighbors[side_opp] == nullptr) {
199 butal_other->neighbors[side_opp] = butal;
200 }
201
202 /* We have a pair of neighbors, we have to check whether we
203 * can stitch their matching corners.
204 * E.g. if butal_other is on the left of butal (that is, side == LEFT),
205 * if both TOP (side_s1) coordinates of buttons are close enough,
206 * we can stitch their upper matching corners,
207 * and same for DOWN (side_s2) side. */
208 delta = fabsf(*butal->borders[side_s1] - *butal_other->borders[side_s1]);
209 if (delta < max_delta) {
210 butal->flags[side_s1] |= stitch;
211 butal_other->flags[side_s1] |= stitch_opp;
212 }
213 delta = fabsf(*butal->borders[side_s2] - *butal_other->borders[side_s2]);
214 if (delta < max_delta) {
215 butal->flags[side_s2] |= stitch;
216 butal_other->flags[side_s2] |= stitch_opp;
217 }
218 }
219 }
220 /* We assume two buttons can only share one side at most - for until
221 * we have spherical UI. */
222 return;
223 }
224 }
225 }
226}
227
250 const int side,
251 const int side_opp,
252 const int side_s1,
253 const int side_s2,
254 const int align,
255 const int align_opp,
256 const float co)
257{
258 ButAlign *butal_neighbor;
259
260 const int stitch_s1 = STITCH(side_s1);
261 const int stitch_s2 = STITCH(side_s2);
262
263 /* We have to check stitching flags on both sides of the stitching,
264 * since we only clear one of them flags to break any future loop on same 'columns/side' case.
265 * Also, if butal is spanning over several rows or columns of neighbors,
266 * it may have both of its stitching flags
267 * set, but would not be the case of its immediate neighbor! */
268 while ((butal->flags[side] & stitch_s1) && (butal = butal->neighbors[side_s1]) &&
269 (butal->flags[side] & stitch_s2))
270 {
271 butal_neighbor = butal->neighbors[side];
272
273 /* If we actually do have a neighbor, we directly set its values accordingly,
274 * and clear its matching 'dist' to prevent it being set again later... */
275 if (butal_neighbor) {
276 butal->but->drawflag |= align;
277 butal_neighbor->but->drawflag |= align_opp;
278 *butal_neighbor->borders[side_opp] = co;
279 butal_neighbor->dists[side_opp] = 0.0f;
280 }
281 /* See definition of UI_BUT_ALIGN_STITCH_LEFT/TOP for reason of this... */
282 else if (side == LEFT) {
284 }
285 else if (side == TOP) {
287 }
288 *butal->borders[side] = co;
289 butal->dists[side] = 0.0f;
290 /* Clearing one of the 'flags pair' here is enough to prevent this loop running on
291 * the same column, side and direction again. */
292 butal->flags[side] &= ~stitch_s2;
293 }
294}
295
302static bool ui_block_align_butal_cmp(const ButAlign &butal, const ButAlign &butal_other)
303{
304 /* Sort by align group. */
305 if (butal.but->alignnr != butal_other.but->alignnr) {
306 return butal.but->alignnr < butal_other.but->alignnr;
307 }
308
309 /* Sort vertically in descending order (first buttons have higher Y value than later ones). */
310 if (*butal.borders[TOP] != *butal_other.borders[TOP]) {
311 return *butal.borders[TOP] > *butal_other.borders[TOP];
312 }
313
314 /* Sort horizontally. */
315 if (*butal.borders[LEFT] != *butal_other.borders[LEFT]) {
316 return *butal.borders[LEFT] < *butal_other.borders[LEFT];
317 }
318 /* In very compressed layouts or overlapping layouts, UI can produce overlapping
319 * widgets which can have the same top-left corner, so do not assert here.
320 */
321 return false;
322}
323
324static void ui_block_align_but_to_region(uiBut *but, const ARegion *region)
325{
326 rctf *rect = &but->rect;
327 const float but_width = BLI_rctf_size_x(rect);
328 const float but_height = BLI_rctf_size_y(rect);
329 const float outline_px = U.pixelsize; /* This may have to be made more variable. */
330
331 switch (but->drawflag & UI_BUT_ALIGN) {
332 case UI_BUT_ALIGN_TOP:
333 rect->ymax = region->winy + outline_px;
334 rect->ymin = but->rect.ymax - but_height;
335 break;
337 rect->ymin = -outline_px;
338 rect->ymax = rect->ymin + but_height;
339 break;
341 rect->xmin = -outline_px;
342 rect->xmax = rect->xmin + but_width;
343 break;
345 rect->xmax = region->winx + outline_px;
346 rect->xmin = rect->xmax - but_width;
347 break;
348 default:
349 /* Tabs may be shown in unaligned regions too, they just appear as regular buttons then. */
350 rect->ymin += UI_SCALE_FAC;
351 rect->ymax += UI_SCALE_FAC;
352 break;
353 }
354}
355
356void ui_block_align_calc(uiBlock *block, const ARegion *region)
357{
358
359 const int sides_to_ui_but_align_flags[4] = SIDE_TO_UI_BUT_ALIGN;
360
361 blender::Vector<ButAlign, 256> butal_array(block->buttons.size());
362
363 int n = 0;
364 /* First loop: Initialize ButAlign data for each button and clear their align flag.
365 * Tabs get some special treatment here, they get aligned to region border. */
366 for (const std::unique_ptr<uiBut> &but : block->buttons) {
367 /* special case: tabs need to be aligned to a region border, drawflag tells which one */
368 if (but->type == ButType::Tab) {
369 ui_block_align_but_to_region(but.get(), region);
370 }
371 else {
372 /* Clear old align flags. */
373 but->drawflag &= ~UI_BUT_ALIGN_ALL;
374 }
375
376 if (but->alignnr == 0) {
377 continue;
378 }
379 ButAlign &butal = butal_array[n++];
380 butal = {};
381 butal.but = but.get();
382 butal.borders[LEFT] = &but->rect.xmin;
383 butal.borders[RIGHT] = &but->rect.xmax;
384 butal.borders[DOWN] = &but->rect.ymin;
385 butal.borders[TOP] = &but->rect.ymax;
387 }
388 butal_array.resize(n);
389
390 if (butal_array.size() < 2) {
391 /* No need to go further if we have nothing to align... */
392 return;
393 }
394
395 /* This will give us ButAlign items regrouped by align group, vertical and horizontal location.
396 * Note that, given how buttons are defined in UI code,
397 * butal_array shall already be "nearly sorted"... */
398 std::sort(butal_array.begin(), butal_array.end(), ui_block_align_butal_cmp);
399
400 /* Second loop: for each pair of buttons in the same align group,
401 * we compute their potential proximity. Note that each pair is checked only once, and that we
402 * break early in case we know all remaining pairs will always be too far away. */
403 for (const int i : butal_array.index_range()) {
404 ButAlign &butal = butal_array[i];
405 const short alignnr = butal.but->alignnr;
406
407 for (ButAlign &butal_other : butal_array.as_mutable_span().drop_front(i + 1)) {
408 const float max_delta = MAX_DELTA;
409
410 /* Since they are sorted, buttons after current butal can only be of same or higher
411 * group, and once they are not of same group, we know we can break this sub-loop and
412 * start checking with next butal. */
413 if (butal_other.but->alignnr != alignnr) {
414 break;
415 }
416
417 /* Since they are sorted vertically first, buttons after current butal can only be at
418 * same or lower height, and once they are lower than a given threshold, we know we can
419 * break this sub-loop and start checking with next butal. */
420 if ((*butal.borders[DOWN] - *butal_other.borders[TOP]) > max_delta) {
421 break;
422 }
423
424 block_align_proximity_compute(&butal, &butal_other);
425 }
426 }
427
428 /* Third loop: we have all our 'aligned' buttons as a 'map' in butal_array. We need to:
429 * - update their relevant coordinates to stitch them.
430 * - assign them valid flags.
431 */
432 for (ButAlign &butal : butal_array) {
433
434 for (int side = 0; side < TOTSIDES; side++) {
435 ButAlign *butal_other = butal.neighbors[side];
436
437 if (butal_other) {
438 const int side_opp = OPPOSITE(side);
439 const int side_s1 = SIDE1(side);
440 const int side_s2 = SIDE2(side);
441
442 const int align = sides_to_ui_but_align_flags[side];
443 const int align_opp = sides_to_ui_but_align_flags[side_opp];
444
445 float co;
446
447 butal.but->drawflag |= align;
448 butal_other->but->drawflag |= align_opp;
449 if (!IS_EQF(butal.dists[side], 0.0f)) {
450 float *delta = &butal.dists[side];
451
452 if (*butal.borders[side] < *butal_other->borders[side_opp]) {
453 *delta *= 0.5f;
454 }
455 else {
456 *delta *= -0.5f;
457 }
458 co = (*butal.borders[side] += *delta);
459
460 if (!IS_EQF(butal_other->dists[side_opp], 0.0f)) {
461 BLI_assert(butal_other->dists[side_opp] * 0.5f == fabsf(*delta));
462 *butal_other->borders[side_opp] = co;
463 butal_other->dists[side_opp] = 0.0f;
464 }
465 *delta = 0.0f;
466 }
467 else {
468 co = *butal.borders[side];
469 }
470
472 &butal, side, side_opp, side_s1, side_s2, align, align_opp, co);
474 &butal, side, side_opp, side_s2, side_s1, align, align_opp, co);
475 }
476 }
477 }
478}
479
480#undef SIDE_TO_UI_BUT_ALIGN
481#undef SIDE1
482#undef OPPOSITE
483#undef SIDE2
484#undef IS_COLUMN
485#undef STITCH
486#undef MAX_DELTA
487
489{
490 const ARegion *align_region = (region->alignment & RGN_SPLIT_PREV && region->prev) ?
491 region->prev :
492 region;
493
494 switch (RGN_ALIGN_ENUM_FROM_MASK(align_region->alignment)) {
495 case RGN_ALIGN_TOP:
496 return UI_BUT_ALIGN_DOWN;
497 case RGN_ALIGN_BOTTOM:
498 return UI_BUT_ALIGN_TOP;
499 case RGN_ALIGN_LEFT:
500 return UI_BUT_ALIGN_RIGHT;
501 case RGN_ALIGN_RIGHT:
502 return UI_BUT_ALIGN_LEFT;
503 }
504
505 return 0;
506}
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE float max_ff(float a, float b)
bool BLI_rctf_is_empty(const struct rctf *rect)
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
#define ELEM(...)
#define IS_EQF(a, b)
#define RGN_ALIGN_ENUM_FROM_MASK(align)
@ RGN_ALIGN_BOTTOM
@ RGN_ALIGN_LEFT
@ RGN_ALIGN_TOP
@ RGN_ALIGN_RIGHT
@ RGN_SPLIT_PREV
#define UI_SCALE_FAC
Read Guarded memory(de)allocation.
@ UI_BUT_ALIGN_ALL
@ UI_BUT_ALIGN_DOWN
@ UI_BUT_ALIGN_TOP
@ UI_BUT_ALIGN
@ UI_BUT_ALIGN_STITCH_TOP
@ UI_BUT_ALIGN_STITCH_LEFT
@ UI_BUT_ALIGN_RIGHT
@ UI_BUT_ALIGN_LEFT
#define U
int64_t size() const
int64_t size() const
IndexRange index_range() const
void resize(const int64_t new_size)
MutableSpan< T > as_mutable_span()
#define IS_COLUMN(_s)
#define MAX_DELTA
bool ui_but_can_align(const uiBut *but)
@ TOP
@ STITCH_DOWN
@ STITCH_RIGHT
@ STITCH_LEFT
@ STITCH_TOP
@ DOWN
@ TOTSIDES
@ LEFT
@ RIGHT
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)
static void ui_block_align_but_to_region(uiBut *but, const ARegion *region)
int ui_but_align_opposite_to_area_align_get(const ARegion *region)
static void block_align_proximity_compute(ButAlign *butal, ButAlign *butal_other)
static bool ui_block_align_butal_cmp(const ButAlign &butal, const ButAlign &butal_other)
#define SIDE_TO_UI_BUT_ALIGN
#define SIDE2(_s)
#define STITCH(_s)
#define SIDE1(_s)
#define OPPOSITE(_s)
void ui_block_align_calc(uiBlock *block, const ARegion *region)
VecBase< float, 4 > float4
blender::VecBase< int8_t, 4 > char4
#define fabsf
#define FLT_MAX
Definition stdcycles.h:14
struct ARegion * prev
ButAlign * neighbors[4]
std::array< float *, 4 > borders
blender::char4 flags
blender::float4 dists
float xmax
float xmin
float ymax
float ymin
blender::Vector< std::unique_ptr< uiBut > > buttons
ButType type
i
Definition text_draw.cc:230