UI: Region polling support #105088

Merged
Julian Eisel merged 39 commits from JulianEisel/blender:temp-region-poll into main 2023-04-05 15:30:46 +02:00
8 changed files with 125 additions and 130 deletions
Showing only changes of commit 5fc5d742a7 - Show all commits

View File

@ -164,6 +164,10 @@ typedef struct ARegionType {
void (*init)(struct wmWindowManager *wm, struct ARegion *region);
/* exit is called when the region is hidden or removed */
void (*exit)(struct wmWindowManager *wm, struct ARegion *region);
/** Optional callback to decide whether the region should be treated as existing given the
JulianEisel marked this conversation as resolved Outdated
  /** 
   * Optional callback to decide whether the region should be treated as existing given the
   * current context. When returning false, the region will be kept in storage, but is not
   * available to the user in any way. Callbacks can assume that context has the owning area and
   * space-data set.
   */
``` /** * Optional callback to decide whether the region should be treated as existing given the * current context. When returning false, the region will be kept in storage, but is not * available to the user in any way. Callbacks can assume that context has the owning area and * space-data set. */
current context. When returning false, the region will be kept in storage, but is not available
to the user in any way. */
bool (*poll)(const struct bContext *C);
/* draw entirely, view changes should be handled here */
void (*draw)(const struct bContext *C, struct ARegion *region);
/**
JulianEisel marked this conversation as resolved Outdated

As this function runs a lot I think it would be best to take screen variables directly, e.g. (wm, area, region) arguments and not set the context.


To expand on this, in tests with some basic interaction the region_poll function runs ~19 times on frame change with the default scene, the same on mouse wheel and mouse motion with some modal operators.
Testing some basic interactivity - this function can run 10's of thousands of times in a minute or so
While I don't see this as a problem for simple checks - passing in the necessary arguments ensures slower lookups aren't used. It's also in keeping with other callbacks which take windowing arguments.

Even in this patch the function calls for data-access aren't as directly as they might be: CTX_wm_space_clip(C) calls into CTX_wm_area(..) -> ctx_wm_python_context_get(..), then CTX_py_dict_get(C) & BLI_thread_is_main().

If you anticipate needing access to data besides the area/region in the future, a parameters struct could be used instead of multiple arguments - so the scene or window can be added without having to update the function signature for every poll() function.

As this function runs a _lot_ I think it would be best to take screen variables directly, e.g. `(wm, area, region)` arguments and not set the context. ---- To expand on this, in tests with some basic interaction the `region_poll` function runs ~19 times on frame change with the default scene, the same on mouse wheel and mouse motion with some modal operators. Testing some basic interactivity - this function can run 10's of thousands of times in a minute or so While I don't see this as a problem for simple checks - passing in the necessary arguments ensures slower lookups aren't used. It's also in keeping with other callbacks which take windowing arguments. Even in this patch the function calls for data-access aren't as directly as they might be: `CTX_wm_space_clip(C)` calls into `CTX_wm_area(..)` -> `ctx_wm_python_context_get(..)`, then `CTX_py_dict_get(C)` & `BLI_thread_is_main()`. If you anticipate needing access to data besides the area/region in the future, a parameters struct could be used instead of multiple arguments - so the scene or window can be added without having to update the function signature for every poll() function.
Review

Doesn't seem like a real issue, for the majority of regions there is no poll function and so region_poll() early exists before any context access. But sure, easy enough of a change.

Doesn't seem like a real issue, for the majority of regions there is no poll function and so `region_poll()` early exists before any context access. But sure, easy enough of a change.

View File

@ -1200,7 +1200,8 @@ static void direct_link_region(BlendDataReader *reader, ARegion *region, int spa
BLO_read_list(reader, &region->ui_lists);
/* The area's search filter is runtime only, so we need to clear the active flag on read. */
region->flag &= ~RGN_FLAG_SEARCH_FILTER_ACTIVE;
/* Clear runtime flags (e.g. search filter is runtime only). */
region->flag &= ~(RGN_FLAG_SEARCH_FILTER_ACTIVE | RGN_FLAG_POLL_FAILED);
LISTBASE_FOREACH (uiList *, ui_list, &region->ui_lists) {
ui_list->type = NULL;

View File

@ -292,7 +292,8 @@ void ED_screen_draw_edges(struct wmWindow *win);
* for file read and first use, for scaling window, area moves.
*/
void ED_screen_refresh(struct wmWindowManager *wm, struct wmWindow *win);
void ED_screen_ensure_updated(struct wmWindowManager *wm,
void ED_screen_ensure_updated(struct bContext *C,
struct wmWindowManager *wm,
struct wmWindow *win,
struct bScreen *screen);
void ED_screen_do_listen(struct bContext *C, const struct wmNotifier *note);

View File

@ -322,7 +322,7 @@ static void region_draw_azones(ScrArea *area, ARegion *region)
area_draw_azone(az->x1, az->y1, az->x2, az->y2);
}
else if (az->type == AZONE_REGION) {
if (az->region) {
if (az->region && !(az->region->flag & RGN_FLAG_POLL_FAILED)) {
/* only display tab or icons when the region is hidden */
if (az->region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) {
region_draw_azone_tab_arrow(area, region, az);
@ -1024,6 +1024,10 @@ static void region_azone_tab_plus(ScrArea *area, AZone *az, ARegion *region)
static bool region_azone_edge_poll(const ARegion *region, const bool is_fullscreen)
{
if (region->flag & RGN_FLAG_POLL_FAILED) {
return false;
}
const bool is_hidden = (region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
if (is_hidden && is_fullscreen) {
@ -1182,7 +1186,7 @@ static void region_overlap_fix(ScrArea *area, ARegion *region)
int align1 = 0;
const int align = RGN_ALIGN_ENUM_FROM_MASK(region->alignment);
for (region_iter = region->prev; region_iter; region_iter = region_iter->prev) {
if (region_iter->flag & RGN_FLAG_HIDDEN) {
if (region_iter->flag & (RGN_FLAG_POLL_FAILED | RGN_FLAG_HIDDEN)) {
continue;
}
@ -1227,7 +1231,7 @@ static void region_overlap_fix(ScrArea *area, ARegion *region)
/* At this point, 'region' is in its final position and still open.
* Make a final check it does not overlap any previous 'other side' region. */
for (region_iter = region->prev; region_iter; region_iter = region_iter->prev) {
if (region_iter->flag & RGN_FLAG_HIDDEN) {
if (region_iter->flag & (RGN_FLAG_POLL_FAILED | RGN_FLAG_HIDDEN)) {
continue;
}
if (ELEM(region_iter->alignment, RGN_ALIGN_FLOAT)) {
@ -1345,7 +1349,7 @@ static void region_rect_recursive(
prefsizey = UI_DPI_FAC * (region->sizey > 1 ? region->sizey + 0.5f : region->type->prefsizey);
}
if (region->flag & RGN_FLAG_HIDDEN) {
if (region->flag & (RGN_FLAG_POLL_FAILED | RGN_FLAG_HIDDEN)) {
/* hidden is user flag */
}
else if (alignment == RGN_ALIGN_FLOAT) {
@ -1646,7 +1650,8 @@ static void area_calc_totrct(ScrArea *area, const rcti *window_rect)
/* used for area initialize below */
static void region_subwindow(ARegion *region)
{
bool hidden = (region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) != 0;
bool hidden = (region->flag & (RGN_FLAG_POLL_FAILED | RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) !=
0;
if ((region->alignment & RGN_SPLIT_PREV) && region->prev) {
hidden = hidden || (region->prev->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL));
@ -2197,6 +2202,10 @@ static void region_align_info_from_area(ScrArea *area, struct RegionTypeAlignInf
}
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->flag & RGN_FLAG_POLL_FAILED) {
continue;
}
const int index = region->regiontype;
if ((uint)index < RGN_TYPE_NUM) {
r_align_info->by_type[index].alignment = RGN_ALIGN_ENUM_FROM_MASK(region->alignment);

View File

@ -713,8 +713,56 @@ void ED_screens_init(Main *bmain, wmWindowManager *wm)
}
}
void ED_screen_ensure_updated(wmWindowManager *wm, wmWindow *win, bScreen *screen)
static bool region_poll(const bContext *C, const ARegion *region)
{
if (!region->type || !region->type->poll) {
/* Show region by default. */
return true;
}
return region->type->poll(C);
}
static void screen_regions_poll(bContext *C, const wmWindow *win, bScreen *screen)
{
ScrArea *previous_area = CTX_wm_area(C);
ARegion *previous_region = CTX_wm_region(C);
bool changed = false;
ED_screen_areas_iter (win, screen, area) {
CTX_wm_area_set(C, area);
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
const int old_region_flag = region->flag;
region->flag &= ~RGN_FLAG_POLL_FAILED;
CTX_wm_region_set(C, region);
if (region_poll(C, region) == false) {
region->flag |= RGN_FLAG_POLL_FAILED;
}
if (old_region_flag != region->flag) {
/* Enforce complete re-init. */
region->v2d.flag &= ~V2D_IS_INIT;
changed = true;
ED_region_visibility_change_update(C, area, region);
}
}
}
if (changed) {
screen->do_refresh = true;
// ED_area_tag_redraw();
}
CTX_wm_area_set(C, previous_area);
CTX_wm_region_set(C, previous_region);
}
void ED_screen_ensure_updated(bContext *C, wmWindowManager *wm, wmWindow *win, bScreen *screen)
{
screen_regions_poll(C, win, screen);
if (screen->do_refresh) {
ED_screen_refresh(wm, win);
}

View File

@ -49,60 +49,9 @@
#include "filelist.h"
#include "fsmenu.h"
static ARegion *file_ui_region_ensure(ScrArea *area, ARegion *region_prev)
{
ARegion *region;
if ((region = BKE_area_find_region_type(area, RGN_TYPE_UI)) != NULL) {
return region;
}
region = MEM_callocN(sizeof(ARegion), "execute region for file");
BLI_insertlinkafter(&area->regionbase, region_prev, region);
region->regiontype = RGN_TYPE_UI;
region->alignment = RGN_ALIGN_TOP;
region->flag = RGN_FLAG_DYNAMIC_SIZE;
return region;
}
static ARegion *file_execute_region_ensure(ScrArea *area, ARegion *region_prev)
{
ARegion *region;
if ((region = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE)) != NULL) {
return region;
}
region = MEM_callocN(sizeof(ARegion), "execute region for file");
BLI_insertlinkafter(&area->regionbase, region_prev, region);
region->regiontype = RGN_TYPE_EXECUTE;
region->alignment = RGN_ALIGN_BOTTOM;
region->flag = RGN_FLAG_DYNAMIC_SIZE;
return region;
}
static ARegion *file_tool_props_region_ensure(ScrArea *area, ARegion *region_prev)
{
ARegion *region;
if ((region = BKE_area_find_region_type(area, RGN_TYPE_TOOL_PROPS)) != NULL) {
return region;
}
/* add subdiv level; after execute region */
region = MEM_callocN(sizeof(ARegion), "tool props for file");
BLI_insertlinkafter(&area->regionbase, region_prev, region);
region->regiontype = RGN_TYPE_TOOL_PROPS;
region->alignment = RGN_ALIGN_RIGHT;
region->flag = RGN_FLAG_HIDDEN;
return region;
}
/* ******************** default callbacks for file space ***************** */
/* TODO create regions in versioning? */
static SpaceLink *file_create(const ScrArea *UNUSED(area), const Scene *UNUSED(scene))
{
ARegion *region;
@ -131,7 +80,19 @@ static SpaceLink *file_create(const ScrArea *UNUSED(area), const Scene *UNUSED(s
region->alignment = RGN_ALIGN_TOP;
region->flag |= RGN_FLAG_DYNAMIC_SIZE;
/* Tool props and execute region are added as needed, see file_refresh(). */
/* execute region */
region = MEM_callocN(sizeof(ARegion), "execute region for file");
BLI_addtail(&sfile->regionbase, region);
region->regiontype = RGN_TYPE_EXECUTE;
region->alignment = RGN_ALIGN_BOTTOM;
region->flag = RGN_FLAG_DYNAMIC_SIZE;
/* tools props region */
region = MEM_callocN(sizeof(ARegion), "tool props for file");
BLI_addtail(&sfile->regionbase, region);
region->regiontype = RGN_TYPE_TOOL_PROPS;
region->alignment = RGN_ALIGN_RIGHT;
region->flag = RGN_FLAG_HIDDEN;
/* main region */
region = MEM_callocN(sizeof(ARegion), "main region for file");
@ -184,6 +145,17 @@ static void file_init(wmWindowManager *UNUSED(wm), ScrArea *area)
}
/* Validate the params right after file read. */
fileselect_refresh_params(sfile);
ARegion *region_props = BKE_area_find_region_type(area, RGN_TYPE_TOOL_PROPS);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params && !(region_props->v2d.flag & V2D_IS_INIT)) {
if (params->flag & FILE_HIDE_TOOL_PROPS) {
region_props->flag |= RGN_FLAG_HIDDEN;
}
else {
region_props->flag &= ~RGN_FLAG_HIDDEN;
}
}
}
static void file_exit(wmWindowManager *wm, ScrArea *area)
@ -231,69 +203,6 @@ static SpaceLink *file_duplicate(SpaceLink *sl)
return (SpaceLink *)sfilen;
}
static void file_ensure_valid_region_state(bContext *C,
wmWindowManager *wm,
wmWindow *win,
ScrArea *area,
SpaceFile *sfile,
FileSelectParams *params)
{
ARegion *region_tools = BKE_area_find_region_type(area, RGN_TYPE_TOOLS);
bool needs_init = false; /* To avoid multiple ED_area_init() calls. */
BLI_assert(region_tools);
if (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS) {
file_tool_props_region_ensure(area, region_tools);
ARegion *region_execute = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE);
if (region_execute) {
ED_region_remove(C, area, region_execute);
needs_init = true;
}
ARegion *region_ui = BKE_area_find_region_type(area, RGN_TYPE_UI);
if (region_ui) {
ED_region_remove(C, area, region_ui);
needs_init = true;
}
}
/* If there's an file-operation, ensure we have the option and execute region */
else if (sfile->op && !BKE_area_find_region_type(area, RGN_TYPE_TOOL_PROPS)) {
ARegion *region_ui = file_ui_region_ensure(area, region_tools);
ARegion *region_execute = file_execute_region_ensure(area, region_ui);
ARegion *region_props = file_tool_props_region_ensure(area, region_execute);
if (params->flag & FILE_HIDE_TOOL_PROPS) {
region_props->flag |= RGN_FLAG_HIDDEN;
}
else {
region_props->flag &= ~RGN_FLAG_HIDDEN;
}
needs_init = true;
}
/* If there's _no_ file-operation, ensure we _don't_ have the option and execute region */
else if (!sfile->op) {
ARegion *region_props = BKE_area_find_region_type(area, RGN_TYPE_TOOL_PROPS);
ARegion *region_execute = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE);
ARegion *region_ui = file_ui_region_ensure(area, region_tools);
UNUSED_VARS(region_ui);
if (region_execute) {
ED_region_remove(C, area, region_execute);
needs_init = true;
}
if (region_props) {
ED_region_remove(C, area, region_props);
needs_init = true;
}
}
if (needs_init) {
ED_area_init(wm, win, area);
}
}
static void file_refresh(const bContext *C, ScrArea *area)
{
wmWindowManager *wm = CTX_wm_manager(C);
@ -389,11 +298,6 @@ static void file_refresh(const bContext *C, ScrArea *area)
sfile->layout->dirty = true;
}
/* Might be called with NULL area, see file_main_region_draw() below. */
if (area) {
file_ensure_valid_region_state((bContext *)C, wm, win, area, sfile, params);
}
ED_area_tag_redraw(area);
}
@ -714,6 +618,25 @@ static void file_keymap(struct wmKeyConfig *keyconf)
WM_keymap_ensure(keyconf, "File Browser Buttons", SPACE_FILE, 0);
}
static bool file_ui_region_poll(const bContext *C)
{
const SpaceFile *sfile = CTX_wm_space_file(C);
/* Always visible except when browsing assets. */
return sfile->browse_mode != FILE_BROWSE_MODE_ASSETS;
}
static bool file_tool_props_region_poll(const bContext *C)
{
const SpaceFile *sfile = CTX_wm_space_file(C);
return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS) || (sfile->op != NULL);
}
static bool file_execution_region_poll(const bContext *C)
{
const SpaceFile *sfile = CTX_wm_space_file(C);
return sfile->op != NULL;
}
static void file_tools_region_init(wmWindowManager *wm, ARegion *region)
{
wmKeyMap *keymap;
@ -1085,6 +1008,7 @@ void ED_spacetype_file(void)
art = MEM_callocN(sizeof(ARegionType), "spacetype file region");
art->regionid = RGN_TYPE_UI;
art->keymapflag = ED_KEYMAP_UI;
art->poll = file_ui_region_poll;
art->listener = file_ui_region_listener;
art->init = file_ui_region_init;
art->draw = file_ui_region_draw;
@ -1094,6 +1018,7 @@ void ED_spacetype_file(void)
art = MEM_callocN(sizeof(ARegionType), "spacetype file region");
art->regionid = RGN_TYPE_EXECUTE;
art->keymapflag = ED_KEYMAP_UI;
art->poll = file_execution_region_poll;
art->listener = file_ui_region_listener;
art->init = file_execution_region_init;
art->draw = file_execution_region_draw;
@ -1118,6 +1043,7 @@ void ED_spacetype_file(void)
art->prefsizex = 240;
art->prefsizey = 60;
art->keymapflag = ED_KEYMAP_UI;
art->poll = file_tool_props_region_poll;
art->listener = file_tool_props_region_listener;
art->init = file_tools_region_init;
art->draw = file_tools_region_draw;

View File

@ -722,6 +722,9 @@ enum {
* region's layout pass. so that expansion is still interactive,
*/
RGN_FLAG_SEARCH_FILTER_UPDATE = (1 << 9),
/** #ARegionType.poll() failed for the current context, and the region should be treated as if it
* wouldn't exist. Runtime only flag. */
RGN_FLAG_POLL_FAILED = (1 << 10),
};
/** #ARegion.do_draw */

View File

@ -894,6 +894,9 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo)
/* Compute UI layouts for dynamically size regions. */
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->flag & RGN_FLAG_POLL_FAILED) {
continue;
}
/* Dynamic region may have been flagged as too small because their size on init is 0.
* ARegion.visible is false then, as expected. The layout should still be created then, so
* the region size can be updated (it may turn out to be not too small then). */
@ -1379,7 +1382,7 @@ void wm_draw_update(bContext *C)
wm_window_make_drawable(wm, win);
/* notifiers for screen redraw */
ED_screen_ensure_updated(wm, win, screen);
ED_screen_ensure_updated(C, wm, win, screen);
wm_draw_window(C, win);
wm_draw_update_clear_window(C, win);