If a text button is activated that is not in view (i.e. scrolled away), the scrolling will now be adjusted to have it in view (with some small additional margin). While entering text, the view may also be updated should the button move out of view, for whatever reason. For the most part, this feature shouldn't be needed and won't kick in, except when a clicked on text button is partially out of view or very close to the region edge. It's however quite important for the previously committed feature, that is, pressing Ctrl+F to start searching in a UI list. The end of the list where the scroll button appears may not be in view. Plus while filtering the number of visible items changes so the scrolling has to be updated to keep the search button visible. Note that I disabled the auto-scrolling for when the text button spawned an additional popup, like for search-box buttons. That is because current code assumes the button to have a fixed position while the popup is open. There is no code to update the popup position together with the button/scrolling. I also think that the logic added here could be used in more places, e.g. for the "ensure file in view" logic the File Browser does.
804 lines
20 KiB
C
804 lines
20 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup edinterface
|
|
*
|
|
* Utilities to inspect the interface, extract information.
|
|
*/
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_screen_types.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_view2d.h"
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "interface_intern.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Button (#uiBut) State
|
|
* \{ */
|
|
|
|
bool ui_but_is_editable(const uiBut *but)
|
|
{
|
|
return !ELEM(but->type,
|
|
UI_BTYPE_LABEL,
|
|
UI_BTYPE_SEPR,
|
|
UI_BTYPE_SEPR_LINE,
|
|
UI_BTYPE_ROUNDBOX,
|
|
UI_BTYPE_LISTBOX,
|
|
UI_BTYPE_PROGRESS_BAR);
|
|
}
|
|
|
|
bool ui_but_is_editable_as_text(const uiBut *but)
|
|
{
|
|
return ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_SEARCH_MENU);
|
|
}
|
|
|
|
bool ui_but_is_toggle(const uiBut *but)
|
|
{
|
|
return ELEM(but->type,
|
|
UI_BTYPE_BUT_TOGGLE,
|
|
UI_BTYPE_TOGGLE,
|
|
UI_BTYPE_ICON_TOGGLE,
|
|
UI_BTYPE_ICON_TOGGLE_N,
|
|
UI_BTYPE_TOGGLE_N,
|
|
UI_BTYPE_CHECKBOX,
|
|
UI_BTYPE_CHECKBOX_N,
|
|
UI_BTYPE_ROW,
|
|
UI_BTYPE_DATASETROW);
|
|
}
|
|
|
|
/**
|
|
* Can we mouse over the button or is it hidden/disabled/layout.
|
|
* \note ctrl is kind of a hack currently,
|
|
* so that non-embossed UI_BTYPE_TEXT button behaves as a label when ctrl is not pressed.
|
|
*/
|
|
bool ui_but_is_interactive(const uiBut *but, const bool labeledit)
|
|
{
|
|
/* NOTE: #UI_BTYPE_LABEL is included for highlights, this allows drags. */
|
|
if ((but->type == UI_BTYPE_LABEL) && but->dragpoin == NULL) {
|
|
return false;
|
|
}
|
|
if (ELEM(but->type, UI_BTYPE_ROUNDBOX, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_LISTBOX)) {
|
|
return false;
|
|
}
|
|
if (but->flag & UI_HIDDEN) {
|
|
return false;
|
|
}
|
|
if (but->flag & UI_SCROLLED) {
|
|
return false;
|
|
}
|
|
if ((but->type == UI_BTYPE_TEXT) &&
|
|
(ELEM(but->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) && !labeledit) {
|
|
return false;
|
|
}
|
|
if ((but->type == UI_BTYPE_LISTROW) && labeledit) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* file selectors are exempt from utf-8 checks */
|
|
bool UI_but_is_utf8(const uiBut *but)
|
|
{
|
|
if (but->rnaprop) {
|
|
const int subtype = RNA_property_subtype(but->rnaprop);
|
|
return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
|
|
}
|
|
return !(but->flag & UI_BUT_NO_UTF8);
|
|
}
|
|
|
|
#ifdef USE_UI_POPOVER_ONCE
|
|
bool ui_but_is_popover_once_compat(const uiBut *but)
|
|
{
|
|
return (ELEM(but->type, UI_BTYPE_BUT, UI_BTYPE_DECORATOR) || ui_but_is_toggle(but));
|
|
}
|
|
#endif
|
|
|
|
bool ui_but_has_array_value(const uiBut *but)
|
|
{
|
|
return (but->rnapoin.data && but->rnaprop &&
|
|
ELEM(RNA_property_subtype(but->rnaprop),
|
|
PROP_COLOR,
|
|
PROP_TRANSLATION,
|
|
PROP_DIRECTION,
|
|
PROP_VELOCITY,
|
|
PROP_ACCELERATION,
|
|
PROP_MATRIX,
|
|
PROP_EULER,
|
|
PROP_QUATERNION,
|
|
PROP_AXISANGLE,
|
|
PROP_XYZ,
|
|
PROP_XYZ_LENGTH,
|
|
PROP_COLOR_GAMMA,
|
|
PROP_COORDS));
|
|
}
|
|
|
|
static wmOperatorType *g_ot_tool_set_by_id = NULL;
|
|
bool UI_but_is_tool(const uiBut *but)
|
|
{
|
|
/* very evil! */
|
|
if (but->optype != NULL) {
|
|
if (g_ot_tool_set_by_id == NULL) {
|
|
g_ot_tool_set_by_id = WM_operatortype_find("WM_OT_tool_set_by_id", false);
|
|
}
|
|
if (but->optype == g_ot_tool_set_by_id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UI_but_has_tooltip_label(const uiBut *but)
|
|
{
|
|
if ((but->drawstr[0] == '\0') && !ui_block_is_popover(but->block)) {
|
|
return UI_but_is_tool(but);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int ui_but_icon(const uiBut *but)
|
|
{
|
|
if (!(but->flag & UI_HAS_ICON)) {
|
|
return ICON_NONE;
|
|
}
|
|
|
|
/* Consecutive icons can be toggle between. */
|
|
if (but->drawflag & UI_BUT_ICON_REVERSE) {
|
|
return but->icon - but->iconadd;
|
|
}
|
|
return but->icon + but->iconadd;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Button (#uiBut) Spatial
|
|
* \{ */
|
|
|
|
void ui_but_pie_dir(RadialDirection dir, float vec[2])
|
|
{
|
|
float angle;
|
|
|
|
BLI_assert(dir != UI_RADIAL_NONE);
|
|
|
|
angle = DEG2RADF((float)ui_radial_dir_to_angle[dir]);
|
|
vec[0] = cosf(angle);
|
|
vec[1] = sinf(angle);
|
|
}
|
|
|
|
static bool ui_but_isect_pie_seg(const uiBlock *block, const uiBut *but)
|
|
{
|
|
const float angle_range = (block->pie_data.flags & UI_PIE_DEGREES_RANGE_LARGE) ? M_PI_4 :
|
|
M_PI_4 / 2.0;
|
|
float vec[2];
|
|
|
|
if (block->pie_data.flags & UI_PIE_INVALID_DIR) {
|
|
return false;
|
|
}
|
|
|
|
ui_but_pie_dir(but->pie_dir, vec);
|
|
|
|
if (saacos(dot_v2v2(vec, block->pie_data.pie_dir)) < angle_range) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ui_but_contains_pt(const uiBut *but, float mx, float my)
|
|
{
|
|
return BLI_rctf_isect_pt(&but->rect, mx, my);
|
|
}
|
|
|
|
bool ui_but_contains_rect(const uiBut *but, const rctf *rect)
|
|
{
|
|
return BLI_rctf_isect(&but->rect, rect, NULL);
|
|
}
|
|
|
|
bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, int x, int y)
|
|
{
|
|
uiBlock *block = but->block;
|
|
if (!ui_region_contains_point_px(region, x, y)) {
|
|
return false;
|
|
}
|
|
|
|
float mx = x, my = y;
|
|
ui_window_to_block_fl(region, block, &mx, &my);
|
|
|
|
if (but->pie_dir != UI_RADIAL_NONE) {
|
|
if (!ui_but_isect_pie_seg(block, but)) {
|
|
return false;
|
|
}
|
|
}
|
|
else if (!ui_but_contains_pt(but, mx, my)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEvent *event)
|
|
{
|
|
rcti rect;
|
|
int x = event->x, y = event->y;
|
|
|
|
ui_window_to_block(region, but->block, &x, &y);
|
|
|
|
BLI_rcti_rctf_copy(&rect, &but->rect);
|
|
|
|
if (but->imb || but->type == UI_BTYPE_COLOR) {
|
|
/* use button size itself */
|
|
}
|
|
else if (but->drawflag & UI_BUT_ICON_LEFT) {
|
|
rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
|
|
}
|
|
else {
|
|
const int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
|
|
rect.xmin += delta / 2;
|
|
rect.xmax -= delta / 2;
|
|
}
|
|
|
|
return BLI_rcti_isect_pt(&rect, x, y);
|
|
}
|
|
|
|
static uiBut *ui_but_find(const ARegion *region,
|
|
const uiButFindPollFn find_poll,
|
|
const void *find_custom_data)
|
|
{
|
|
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
|
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
|
|
if (find_poll && find_poll(but, find_custom_data) == false) {
|
|
continue;
|
|
}
|
|
return but;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* x and y are only used in case event is NULL... */
|
|
uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
|
|
const int x,
|
|
const int y,
|
|
const bool labeledit,
|
|
const uiButFindPollFn find_poll,
|
|
const void *find_custom_data)
|
|
{
|
|
uiBut *butover = NULL;
|
|
|
|
if (!ui_region_contains_point_px(region, x, y)) {
|
|
return NULL;
|
|
}
|
|
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
|
float mx = x, my = y;
|
|
ui_window_to_block_fl(region, block, &mx, &my);
|
|
|
|
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
|
|
if (find_poll && find_poll(but, find_custom_data) == false) {
|
|
continue;
|
|
}
|
|
if (ui_but_is_interactive(but, labeledit)) {
|
|
if (but->pie_dir != UI_RADIAL_NONE) {
|
|
if (ui_but_isect_pie_seg(block, but)) {
|
|
butover = but;
|
|
break;
|
|
}
|
|
}
|
|
else if (ui_but_contains_pt(but, mx, my)) {
|
|
butover = but;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* CLIP_EVENTS prevents the event from reaching other blocks */
|
|
if (block->flag & UI_BLOCK_CLIP_EVENTS) {
|
|
/* check if mouse is inside block */
|
|
if (BLI_rctf_isect_pt(&block->rect, mx, my)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return butover;
|
|
}
|
|
|
|
uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event)
|
|
{
|
|
return ui_but_find_mouse_over_ex(region, event->x, event->y, event->ctrl != 0, NULL, NULL);
|
|
}
|
|
|
|
uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px)
|
|
{
|
|
if (!ui_region_contains_rect_px(region, rect_px)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Currently no need to expose this at the moment. */
|
|
const bool labeledit = true;
|
|
rctf rect_px_fl;
|
|
BLI_rctf_rcti_copy(&rect_px_fl, rect_px);
|
|
uiBut *butover = NULL;
|
|
|
|
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
|
rctf rect_block;
|
|
ui_window_to_block_rctf(region, block, &rect_block, &rect_px_fl);
|
|
|
|
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
|
|
if (ui_but_is_interactive(but, labeledit)) {
|
|
/* No pie menu support. */
|
|
BLI_assert(but->pie_dir == UI_RADIAL_NONE);
|
|
if (ui_but_contains_rect(but, &rect_block)) {
|
|
butover = but;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* CLIP_EVENTS prevents the event from reaching other blocks */
|
|
if (block->flag & UI_BLOCK_CLIP_EVENTS) {
|
|
/* check if mouse is inside block */
|
|
if (BLI_rctf_isect(&block->rect, &rect_block, NULL)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return butover;
|
|
}
|
|
|
|
uiBut *ui_list_find_mouse_over_ex(const ARegion *region, int x, int y)
|
|
{
|
|
if (!ui_region_contains_point_px(region, x, y)) {
|
|
return NULL;
|
|
}
|
|
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
|
float mx = x, my = y;
|
|
ui_window_to_block_fl(region, block, &mx, &my);
|
|
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
|
|
if (but->type == UI_BTYPE_LISTBOX && ui_but_contains_pt(but, mx, my)) {
|
|
return but;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
uiBut *ui_list_find_mouse_over(const ARegion *region, const wmEvent *event)
|
|
{
|
|
if (event == NULL) {
|
|
/* If there is no info about the mouse, just act as if there is nothing underneath it. */
|
|
return NULL;
|
|
}
|
|
return ui_list_find_mouse_over_ex(region, event->x, event->y);
|
|
}
|
|
|
|
uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event)
|
|
{
|
|
uiBut *list_but = ui_list_find_mouse_over(region, event);
|
|
if (!list_but) {
|
|
return NULL;
|
|
}
|
|
|
|
return list_but->custom_data;
|
|
}
|
|
|
|
static bool ui_list_contains_row(const uiBut *listbox_but, const uiBut *listrow_but)
|
|
{
|
|
BLI_assert(listbox_but->type == UI_BTYPE_LISTBOX);
|
|
BLI_assert(listrow_but->type == UI_BTYPE_LISTROW);
|
|
/* The list box and its rows have the same RNA data (active data pointer/prop). */
|
|
return ui_but_rna_equals(listbox_but, listrow_but);
|
|
}
|
|
|
|
static bool ui_but_is_listbox_with_row(const uiBut *but, const void *customdata)
|
|
{
|
|
const uiBut *row_but = customdata;
|
|
return (but->type == UI_BTYPE_LISTBOX) && ui_list_contains_row(but, row_but);
|
|
}
|
|
|
|
uiBut *ui_list_find_from_row(const ARegion *region, const uiBut *row_but)
|
|
{
|
|
return ui_but_find(region, ui_but_is_listbox_with_row, row_but);
|
|
}
|
|
|
|
static bool ui_but_is_listrow(const uiBut *but, const void *UNUSED(customdata))
|
|
{
|
|
return but->type == UI_BTYPE_LISTROW;
|
|
}
|
|
|
|
uiBut *ui_list_row_find_mouse_over(const ARegion *region, const int x, const int y)
|
|
{
|
|
return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_listrow, NULL);
|
|
}
|
|
|
|
struct ListRowFindIndexData {
|
|
int index;
|
|
uiBut *listbox;
|
|
};
|
|
|
|
static bool ui_but_is_listrow_at_index(const uiBut *but, const void *customdata)
|
|
{
|
|
const struct ListRowFindIndexData *find_data = customdata;
|
|
|
|
return ui_but_is_listrow(but, NULL) && ui_list_contains_row(find_data->listbox, but) &&
|
|
(but->hardmax == find_data->index);
|
|
}
|
|
|
|
uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut *listbox)
|
|
{
|
|
BLI_assert(listbox->type == UI_BTYPE_LISTBOX);
|
|
struct ListRowFindIndexData data = {
|
|
.index = index,
|
|
.listbox = listbox,
|
|
};
|
|
return ui_but_find(region, ui_but_is_listrow_at_index, &data);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Button (#uiBut) Relations
|
|
* \{ */
|
|
|
|
uiBut *ui_but_prev(uiBut *but)
|
|
{
|
|
while (but->prev) {
|
|
but = but->prev;
|
|
if (ui_but_is_editable(but)) {
|
|
return but;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
uiBut *ui_but_next(uiBut *but)
|
|
{
|
|
while (but->next) {
|
|
but = but->next;
|
|
if (ui_but_is_editable(but)) {
|
|
return but;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
uiBut *ui_but_first(uiBlock *block)
|
|
{
|
|
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
|
|
if (ui_but_is_editable(but)) {
|
|
return but;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
uiBut *ui_but_last(uiBlock *block)
|
|
{
|
|
uiBut *but;
|
|
|
|
but = block->buttons.last;
|
|
while (but) {
|
|
if (ui_but_is_editable(but)) {
|
|
return but;
|
|
}
|
|
but = but->prev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool ui_but_is_cursor_warp(const uiBut *but)
|
|
{
|
|
if (U.uiflag & USER_CONTINUOUS_MOUSE) {
|
|
if (ELEM(but->type,
|
|
UI_BTYPE_NUM,
|
|
UI_BTYPE_NUM_SLIDER,
|
|
UI_BTYPE_TRACK_PREVIEW,
|
|
UI_BTYPE_HSVCUBE,
|
|
UI_BTYPE_HSVCIRCLE,
|
|
UI_BTYPE_CURVE,
|
|
UI_BTYPE_CURVEPROFILE)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ui_but_contains_password(const uiBut *but)
|
|
{
|
|
return but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Button (#uiBut) Text
|
|
* \{ */
|
|
|
|
size_t ui_but_drawstr_len_without_sep_char(const uiBut *but)
|
|
{
|
|
if (but->flag & UI_BUT_HAS_SEP_CHAR) {
|
|
const char *str_sep = strrchr(but->drawstr, UI_SEP_CHAR);
|
|
if (str_sep != NULL) {
|
|
return (str_sep - but->drawstr);
|
|
}
|
|
}
|
|
return strlen(but->drawstr);
|
|
}
|
|
|
|
size_t ui_but_tip_len_only_first_line(const uiBut *but)
|
|
{
|
|
if (but->tip == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
const char *str_sep = strchr(but->tip, '\n');
|
|
if (str_sep != NULL) {
|
|
return (str_sep - but->tip);
|
|
}
|
|
return strlen(but->tip);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Block (#uiBlock) State
|
|
* \{ */
|
|
|
|
uiBut *ui_block_active_but_get(const uiBlock *block)
|
|
{
|
|
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
|
|
if (but->active) {
|
|
return but;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool ui_block_is_menu(const uiBlock *block)
|
|
{
|
|
return (((block->flag & UI_BLOCK_LOOP) != 0) &&
|
|
/* non-menu popups use keep-open, so check this is off */
|
|
((block->flag & UI_BLOCK_KEEP_OPEN) == 0));
|
|
}
|
|
|
|
bool ui_block_is_popover(const uiBlock *block)
|
|
{
|
|
return (block->flag & UI_BLOCK_POPOVER) != 0;
|
|
}
|
|
|
|
bool ui_block_is_pie_menu(const uiBlock *block)
|
|
{
|
|
return ((block->flag & UI_BLOCK_RADIAL) != 0);
|
|
}
|
|
|
|
bool ui_block_is_popup_any(const uiBlock *block)
|
|
{
|
|
return (ui_block_is_menu(block) || ui_block_is_popover(block) || ui_block_is_pie_menu(block));
|
|
}
|
|
|
|
static const uiBut *ui_but_next_non_separator(const uiBut *but)
|
|
{
|
|
for (; but; but = but->next) {
|
|
if (!ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) {
|
|
return but;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool UI_block_is_empty_ex(const uiBlock *block, const bool skip_title)
|
|
{
|
|
const uiBut *but = block->buttons.first;
|
|
if (skip_title) {
|
|
/* Skip the first label, since popups often have a title,
|
|
* we may want to consider the block empty in this case. */
|
|
but = ui_but_next_non_separator(but);
|
|
if (but && but->type == UI_BTYPE_LABEL) {
|
|
but = but->next;
|
|
}
|
|
}
|
|
return (ui_but_next_non_separator(but) == NULL);
|
|
}
|
|
|
|
bool UI_block_is_empty(const uiBlock *block)
|
|
{
|
|
return UI_block_is_empty_ex(block, false);
|
|
}
|
|
|
|
bool UI_block_can_add_separator(const uiBlock *block)
|
|
{
|
|
if (ui_block_is_menu(block) && !ui_block_is_pie_menu(block)) {
|
|
const uiBut *but = block->buttons.last;
|
|
return (but && !ELEM(but->type, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Block (#uiBlock) Spatial
|
|
* \{ */
|
|
|
|
uiBlock *ui_block_find_mouse_over_ex(const ARegion *region,
|
|
const int x,
|
|
const int y,
|
|
bool only_clip)
|
|
{
|
|
if (!ui_region_contains_point_px(region, x, y)) {
|
|
return NULL;
|
|
}
|
|
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
|
if (only_clip) {
|
|
if ((block->flag & UI_BLOCK_CLIP_EVENTS) == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
float mx = x, my = y;
|
|
ui_window_to_block_fl(region, block, &mx, &my);
|
|
if (BLI_rctf_isect_pt(&block->rect, mx, my)) {
|
|
return block;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
uiBlock *ui_block_find_mouse_over(const ARegion *region, const wmEvent *event, bool only_clip)
|
|
{
|
|
return ui_block_find_mouse_over_ex(region, event->x, event->y, only_clip);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Region (#ARegion) State
|
|
* \{ */
|
|
|
|
uiBut *ui_region_find_active_but(ARegion *region)
|
|
{
|
|
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
|
uiBut *but = ui_block_active_but_get(block);
|
|
if (but) {
|
|
return but;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
uiBut *ui_region_find_first_but_test_flag(ARegion *region, int flag_include, int flag_exclude)
|
|
{
|
|
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
|
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
|
|
if (((but->flag & flag_include) == flag_include) && ((but->flag & flag_exclude) == 0)) {
|
|
return but;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Region (#ARegion) Spatial
|
|
* \{ */
|
|
|
|
bool ui_region_contains_point_px(const ARegion *region, int x, int y)
|
|
{
|
|
rcti winrct;
|
|
ui_region_winrct_get_no_margin(region, &winrct);
|
|
if (!BLI_rcti_isect_pt(&winrct, x, y)) {
|
|
return false;
|
|
}
|
|
|
|
/* also, check that with view2d, that the mouse is not over the scroll-bars
|
|
* NOTE: care is needed here, since the mask rect may include the scroll-bars
|
|
* even when they are not visible, so we need to make a copy of the mask to
|
|
* use to check
|
|
*/
|
|
if (region->v2d.mask.xmin != region->v2d.mask.xmax) {
|
|
const View2D *v2d = ®ion->v2d;
|
|
int mx = x, my = y;
|
|
|
|
ui_window_to_region(region, &mx, &my);
|
|
if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) ||
|
|
UI_view2d_mouse_in_scrollers(region, ®ion->v2d, x, y)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ui_region_contains_rect_px(const ARegion *region, const rcti *rect_px)
|
|
{
|
|
rcti winrct;
|
|
ui_region_winrct_get_no_margin(region, &winrct);
|
|
if (!BLI_rcti_isect(&winrct, rect_px, NULL)) {
|
|
return false;
|
|
}
|
|
|
|
/* See comment in 'ui_region_contains_point_px' */
|
|
if (region->v2d.mask.xmin != region->v2d.mask.xmax) {
|
|
const View2D *v2d = ®ion->v2d;
|
|
rcti rect_region;
|
|
ui_window_to_region_rcti(region, &rect_region, rect_px);
|
|
if (!BLI_rcti_isect(&v2d->mask, &rect_region, NULL) ||
|
|
UI_view2d_rect_in_scrollers(region, ®ion->v2d, rect_px)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Screen (#bScreen) Spatial
|
|
* \{ */
|
|
|
|
/** Check if the cursor is over any popups. */
|
|
ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, int x, int y)
|
|
{
|
|
LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
|
|
rcti winrct;
|
|
|
|
ui_region_winrct_get_no_margin(region, &winrct);
|
|
|
|
if (BLI_rcti_isect_pt(&winrct, x, y)) {
|
|
return region;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ARegion *ui_screen_region_find_mouse_over(bScreen *screen, const wmEvent *event)
|
|
{
|
|
return ui_screen_region_find_mouse_over_ex(screen, event->x, event->y);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Manage Internal State
|
|
* \{ */
|
|
|
|
void ui_interface_tag_script_reload_queries(void)
|
|
{
|
|
g_ot_tool_set_by_id = NULL;
|
|
}
|
|
|
|
/** \} */
|