UI: better support for dynamically sized regions in topbar.

Dynamically sized regions in the topbar were flickering due to only updating
their size after redraws. Now there is an optional layout() callback for
all regions in an area to do UI layout first, then refresh the region layout,
and then do the actual drawing for each region.

Task T54753
This commit is contained in:
Brecht Van Lommel 2018-04-29 12:24:08 +02:00
parent 0c512a6f95
commit 7a9f64e665
10 changed files with 153 additions and 104 deletions

View File

@ -140,8 +140,8 @@ typedef struct ARegionType {
void (*exit)(struct wmWindowManager *, struct ARegion *);
/* draw entirely, view changes should be handled here */
void (*draw)(const struct bContext *, struct ARegion *);
/* optional, refresh popup before drawing */
void (*refresh)(const struct bContext *, struct ARegion *);
/* optional, compute button layout before drawing for dynamic size */
void (*layout)(const struct bContext *, struct ARegion *);
/* snap the size of the region (can be NULL for no snapping). */
int (*snap_size)(const struct ARegion *ar, int size, int axis);
/* contextual changes should be handled here */

View File

@ -65,6 +65,7 @@ struct wmMsgSubscribeValue;
void ED_region_do_listen(
struct bScreen *sc, struct ScrArea *sa, struct ARegion *ar,
struct wmNotifier *note, const Scene *scene);
void ED_region_do_layout(struct bContext *C, struct ARegion *ar);
void ED_region_do_draw(struct bContext *C, struct ARegion *ar);
void ED_region_exit(struct bContext *C, struct ARegion *ar);
void ED_region_pixelspace(struct ARegion *ar);
@ -81,6 +82,8 @@ void ED_region_panels(
const bool vertical);
void ED_region_header_init(struct ARegion *ar);
void ED_region_header(const struct bContext *C, struct ARegion *ar);
void ED_region_header_layout(const struct bContext *C, struct ARegion *ar);
void ED_region_header_draw(const struct bContext *C, struct ARegion *ar);
void ED_region_cursor_set(struct wmWindow *win, struct ScrArea *sa, struct ARegion *ar);
void ED_region_toggle_hidden(struct bContext *C, struct ARegion *ar);
void ED_region_visibility_change_update(struct bContext *C, struct ARegion *ar);
@ -135,6 +138,7 @@ int ED_area_headersize(void);
int ED_area_global_size_y(const ScrArea *area);
bool ED_area_is_global(const ScrArea *area);
int ED_region_global_size_y(void);
void ED_area_update_region_sizes(struct wmWindowManager *wm, struct wmWindow *win, struct ScrArea *area);
ScrArea *ED_screen_areas_iter_first(const struct wmWindow *win, const bScreen *screen);
ScrArea *ED_screen_areas_iter_next(const bScreen *screen, const ScrArea *area);

View File

@ -475,6 +475,8 @@ uiBlock *UI_block_begin(const struct bContext *C, struct ARegion *region, const
void UI_block_end_ex(const struct bContext *C, uiBlock *block, const int xy[2], int r_xy[2]);
void UI_block_end(const struct bContext *C, uiBlock *block);
void UI_block_draw(const struct bContext *C, struct uiBlock *block);
void UI_blocklist_update_window_matrix(const struct bContext *C, const struct ListBase *lb);
void UI_blocklist_draw(const struct bContext *C, const struct ListBase *lb);
void UI_block_update_from_old(const struct bContext *C, struct uiBlock *block);
uiBlock *UI_block_find_in_region(const char *name, struct ARegion *ar);

View File

@ -224,6 +224,26 @@ void ui_region_to_window(const ARegion *ar, int *x, int *y)
*y += ar->winrct.ymin;
}
static void ui_update_window_matrix(const wmWindow *window, const ARegion *region, uiBlock *block)
{
/* window matrix and aspect */
if (region && region->visible) {
/* Get projection matrix which includes View2D translation and zoom. */
gpuGetProjectionMatrix(block->winmat);
block->aspect = 2.0f / fabsf(region->winx * block->winmat[0][0]);
}
else {
/* No subwindow created yet, for menus for example, so we use the main
* window instead, since buttons are created there anyway. */
int width = WM_window_pixels_x(window);
int height = WM_window_pixels_y(window);
rcti winrct = {0, width - 1, 0, height - 1};
wmGetProjectionMatrix(block->winmat, &winrct);
block->aspect = 2.0f / fabsf(width * block->winmat[0][0]);
}
}
/**
* Popups will add a margin to #ARegion.winrct for shadow,
* for interactivity (point-inside tests for eg), we want the winrct without the margin added.
@ -2670,6 +2690,27 @@ void UI_block_free(const bContext *C, uiBlock *block)
MEM_freeN(block);
}
void UI_blocklist_update_window_matrix(const bContext *C, const ListBase *lb)
{
ARegion *region = CTX_wm_region(C);
wmWindow *window = CTX_wm_window(C);
for (uiBlock *block = lb->first; block; block = block->next) {
if (block->active) {
ui_update_window_matrix(window, region, block);
}
}
}
void UI_blocklist_draw(const bContext *C, const ListBase *lb)
{
for (uiBlock *block = lb->first; block; block = block->next) {
if (block->active) {
UI_block_draw(C, block);
}
}
}
/* can be called with C==NULL */
void UI_blocklist_free(const bContext *C, ListBase *lb)
{
@ -2755,25 +2796,13 @@ uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, sh
if (region)
UI_block_region_set(block, region);
/* window matrix and aspect */
if (region && region->visible) {
gpuGetProjectionMatrix(block->winmat);
/* Set window matrix and aspect for region and OpenGL state. */
ui_update_window_matrix(window, region, block);
block->aspect = 2.0f / fabsf(region->winx * block->winmat[0][0]);
}
else {
/* no subwindow created yet, for menus for example, so we
* use the main window instead, since buttons are created
* there anyway */
int width = WM_window_pixels_x(window);
int height = WM_window_pixels_y(window);
rcti winrct = {0, width -1, 0, height - 1};
wmGetProjectionMatrix(block->winmat, &winrct);
block->aspect = 2.0f / fabsf(width * block->winmat[0][0]);
/* Tag as popup menu if not created within a region. */
if (!(region && region->visible)) {
block->auto_open = true;
block->flag |= UI_BLOCK_LOOP; /* tag as menu */
block->flag |= UI_BLOCK_LOOP;
}
return block;

View File

@ -682,7 +682,7 @@ uiPopupBlockHandle *ui_popup_block_create(
memset(&type, 0, sizeof(ARegionType));
type.draw = ui_block_region_draw;
type.refresh = ui_block_region_refresh;
type.layout = ui_block_region_refresh;
type.regionid = RGN_TYPE_TEMPORARY;
ar->type = &type;

View File

@ -500,6 +500,27 @@ void ED_area_do_msg_notify_tag_refresh(
ED_area_tag_refresh(sa);
}
/* only exported for WM */
void ED_region_do_layout(bContext *C, ARegion *ar)
{
/* This is optional, only needed for dynamically sized regions. */
ScrArea *sa = CTX_wm_area(C);
ARegionType *at = ar->type;
if (!at->layout) {
return;
}
if (at->do_lock) {
return;
}
ar->do_draw |= RGN_DRAWING;
UI_SetTheme(sa ? sa->spacetype : 0, at->regionid);
at->layout(C, ar);
}
/* only exported for WM */
void ED_region_do_draw(bContext *C, ARegion *ar)
{
@ -1187,9 +1208,8 @@ static void region_rect_recursive(wmWindow *win, ScrArea *sa, ARegion *ar, rcti
if (ar->next == NULL && alignment != RGN_ALIGN_QSPLIT)
alignment = RGN_ALIGN_NONE;
/* prefsize, for header we stick to exception (prevent dpi rounding error) */
const float sizex_dpi_fac = (ar->flag & RGN_SIZEX_DPI_APPLIED) ? 1.0f : UI_DPI_FAC;
prefsizex = sizex_dpi_fac * ((ar->sizex > 1) ? ar->sizex + 0.5f : ar->type->prefsizex);
/* prefsize, taking into account DPI */
prefsizex = UI_DPI_FAC * ((ar->sizex > 1) ? ar->sizex + 0.5f : ar->type->prefsizex);
if (ar->regiontype == RGN_TYPE_HEADER) {
prefsizey = ED_area_headersize();
@ -1514,8 +1534,12 @@ static void ed_default_handlers(wmWindowManager *wm, ScrArea *sa, ListBase *hand
}
}
void screen_area_update_region_sizes(wmWindowManager *wm, wmWindow *win, ScrArea *area)
void ED_area_update_region_sizes(wmWindowManager *wm, wmWindow *win, ScrArea *area)
{
if (!(area->flag & AREA_FLAG_REGION_SIZE_UPDATE)) {
return;
}
const int size_x = WM_window_pixels_x(win);
const int size_y = WM_window_pixels_y(win);
rcti rect;
@ -1898,6 +1922,23 @@ static ThemeColorID region_background_color_id(const bContext *C, const ARegion
}
}
static void region_clear_color(const bContext *C, const ARegion *ar, ThemeColorID colorid)
{
if (ar->overlap) {
/* view should be in pixelspace */
UI_view2d_view_restore(C);
float back[4];
UI_GetThemeColor4fv(colorid, back);
glClearColor(back[3] * back[0], back[3] * back[1], back[3] * back[2], back[3]);
glClear(GL_COLOR_BUFFER_BIT);
}
else {
UI_ThemeClearColor(colorid);
glClear(GL_COLOR_BUFFER_BIT);
}
}
void ED_region_panels(const bContext *C, ARegion *ar, const char *context, int contextnr, const bool vertical)
{
const WorkSpace *workspace = CTX_wm_workspace(C);
@ -2124,20 +2165,7 @@ void ED_region_panels(const bContext *C, ARegion *ar, const char *context, int c
}
}
/* clear */
if (ar->overlap) {
/* view should be in pixelspace */
UI_view2d_view_restore(C);
float back[4];
UI_GetThemeColor4fv((ar->type->regionid == RGN_TYPE_PREVIEW) ? TH_PREVIEW_BACK : TH_BACK, back);
glClearColor(back[3] * back[0], back[3] * back[1], back[3] * back[2], back[3]);
glClear(GL_COLOR_BUFFER_BIT);
}
else {
UI_ThemeClearColor((ar->type->regionid == RGN_TYPE_PREVIEW) ? TH_PREVIEW_BACK : TH_BACK);
glClear(GL_COLOR_BUFFER_BIT);
}
region_clear_color(C, ar, (ar->type->regionid == RGN_TYPE_PREVIEW) ? TH_PREVIEW_BACK : TH_BACK);
/* reset line width for drawing tabs */
glLineWidth(1.0f);
@ -2171,7 +2199,7 @@ void ED_region_panels_init(wmWindowManager *wm, ARegion *ar)
WM_event_add_keymap_handler(&ar->handlers, keymap);
}
void ED_region_header(const bContext *C, ARegion *ar)
void ED_region_header_layout(const bContext *C, ARegion *ar)
{
uiStyle *style = UI_style_get_dpi();
uiBlock *block;
@ -2183,10 +2211,6 @@ void ED_region_header(const bContext *C, ARegion *ar)
const int start_ofs = 0.4f * UI_UNIT_X;
bool region_layout_based = ar->flag & RGN_FLAG_DYNAMIC_SIZE;
/* clear */
UI_ThemeClearColor(region_background_color_id(C, ar));
glClear(GL_COLOR_BUFFER_BIT);
/* set view2d view matrix for scrolling (without scrollers) */
UI_view2d_view_ortho(&ar->v2d);
@ -2221,27 +2245,50 @@ void ED_region_header(const bContext *C, ARegion *ar)
if (xco > maxco)
maxco = xco;
if (region_layout_based && (ar->sizex != (maxco + start_ofs))) {
int new_sizex = (maxco + start_ofs) / UI_DPI_FAC;
if (region_layout_based && (ar->sizex != new_sizex)) {
/* region size is layout based and needs to be updated */
ScrArea *sa = CTX_wm_area(C);
ar->sizex = maxco + start_ofs;
UI_view2d_region_reinit(&ar->v2d, V2D_COMMONVIEW_HEADER, ar->sizex, ar->winy);
ar->sizex = new_sizex;
sa->flag |= AREA_FLAG_REGION_SIZE_UPDATE;
ar->flag |= RGN_SIZEX_DPI_APPLIED;
}
UI_block_end(C, block);
UI_block_draw(C, block);
}
/* always as last */
UI_view2d_totRect_set(&ar->v2d, maxco + (region_layout_based ? 0 : UI_UNIT_X + 80), headery);
/* restore view matrix? */
/* restore view matrix */
UI_view2d_view_restore(C);
}
void ED_region_header_draw(const bContext *C, ARegion *ar)
{
UI_view2d_view_ortho(&ar->v2d);
/* clear */
region_clear_color(C, ar, region_background_color_id(C, ar));
/* View2D matrix might have changed due to dynamic sized regions. */
UI_blocklist_update_window_matrix(C, &ar->uiblocks);
/* draw blocks */
UI_blocklist_draw(C, &ar->uiblocks);
/* restore view matrix */
UI_view2d_view_restore(C);
}
void ED_region_header(const bContext *C, ARegion *ar)
{
/* TODO: remove? */
ED_region_header_layout(C, ar);
ED_region_header_draw(C, ar);
}
void ED_region_header_init(ARegion *ar)
{
UI_view2d_region_reinit(&ar->v2d, V2D_COMMONVIEW_HEADER, ar->winx, ar->winy);

View File

@ -835,36 +835,6 @@ void ED_screen_refresh(wmWindowManager *wm, wmWindow *win)
screen->context = ed_screen_context;
}
static bool screen_regions_need_size_refresh(
const wmWindow *win, const bScreen *screen)
{
ED_screen_areas_iter(win, screen, area) {
if (area->flag & AREA_FLAG_REGION_SIZE_UPDATE) {
return true;
}
}
return false;
}
static void screen_refresh_region_sizes_only(
wmWindowManager *wm, wmWindow *win,
bScreen *screen)
{
const int window_size_x = WM_window_pixels_x(win);
const int window_size_y = WM_window_pixels_y(win);
const int screen_size_x = WM_window_screen_pixels_x(win);
const int screen_size_y = WM_window_screen_pixels_y(win);
screen_vertices_scale(win, screen, window_size_x, window_size_y, screen_size_x, screen_size_y);
ED_screen_areas_iter(win, screen, area) {
screen_area_update_region_sizes(wm, win, area);
/* XXX hack to force drawing */
ED_area_tag_redraw(area);
}
}
/* file read, set all screens, ... */
void ED_screens_initialize(wmWindowManager *wm)
{
@ -887,9 +857,6 @@ void ED_screen_ensure_updated(wmWindowManager *wm, wmWindow *win, bScreen *scree
if (screen->do_refresh) {
ED_screen_refresh(wm, win);
}
else if (screen_regions_need_size_refresh(win, screen)) {
screen_refresh_region_sizes_only(wm, win, screen);
}
}

View File

@ -132,11 +132,6 @@ static void topbar_main_region_init(wmWindowManager *wm, ARegion *region)
WM_event_add_keymap_handler(&region->handlers, keymap);
}
static void topbar_main_region_draw(const bContext *C, ARegion *region)
{
ED_region_header(C, region);
}
static void topbar_operatortypes(void)
{
@ -156,11 +151,6 @@ static void topbar_header_region_init(wmWindowManager *UNUSED(wm), ARegion *ar)
ED_region_header_init(ar);
}
static void topbar_header_region_draw(const bContext *C, ARegion *ar)
{
ED_region_header(C, ar);
}
static void topbar_main_region_listener(bScreen *UNUSED(sc), ScrArea *UNUSED(sa), ARegion *ar,
wmNotifier *wmn, const Scene *UNUSED(scene))
{
@ -263,7 +253,8 @@ void ED_spacetype_topbar(void)
art = MEM_callocN(sizeof(ARegionType), "spacetype topbar main region");
art->regionid = RGN_TYPE_WINDOW;
art->init = topbar_main_region_init;
art->draw = topbar_main_region_draw;
art->layout = ED_region_header_layout;
art->draw = ED_region_header_draw;
art->listener = topbar_main_region_listener;
art->prefsizex = UI_UNIT_X * 5; /* Mainly to avoid glitches */
art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D | ED_KEYMAP_HEADER;
@ -279,7 +270,8 @@ void ED_spacetype_topbar(void)
art->listener = topbar_header_listener;
art->message_subscribe = topbar_header_region_message_subscribe;
art->init = topbar_header_region_init;
art->draw = topbar_header_region_draw;
art->layout = ED_region_header_layout;
art->draw = ED_region_header_draw;
BLI_addhead(&st->regiontypes, art);

View File

@ -464,11 +464,6 @@ enum {
* just big enough to show all its content (if enough space is available).
* Note that only ED_region_header supports this right now. */
RGN_FLAG_DYNAMIC_SIZE = (1 << 2),
/* The region width stored in ARegion.sizex already has the DPI
* factor applied, skip applying it again (in region_rect_recursive).
* XXX Not nice at all. Leaving for now as temporary solution, but
* it might cause issues if we change how ARegion.sizex is used... */
RGN_SIZEX_DPI_APPLIED = (1 << 3),
};
/* region do_draw */

View File

@ -489,12 +489,25 @@ GPUViewport *WM_draw_region_get_bound_viewport(ARegion *ar)
static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo)
{
wmWindowManager *wm = CTX_wm_manager(C);
bScreen *screen = WM_window_get_active_screen(win);
/* Draw screen areas into own frame buffer. */
ED_screen_areas_iter(win, screen, sa) {
CTX_wm_area_set(C, sa);
/* Compute UI layouts for dynamically size regions. */
for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
if (ar->visible && ar->do_draw && ar->type && ar->type->layout) {
CTX_wm_region_set(C, ar);
ED_region_do_layout(C, ar);
CTX_wm_region_set(C, NULL);
}
}
ED_area_update_region_sizes(wm, win, sa);
/* Then do actual drawing of regions. */
for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
if (ar->visible && ar->do_draw) {
CTX_wm_region_set(C, ar);
@ -539,11 +552,11 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo)
if (ar->visible) {
CTX_wm_menu_set(C, ar);
if (ar->type && ar->type->refresh) {
/* UI code reads the OpenGL state, but we have to
* refresh beforehand in case the menu size changes. */
if (ar->type && ar->type->layout) {
/* UI code reads the OpenGL state, but we have to refesh
* the UI layout beforehand in case the menu size changes. */
wmViewport(&ar->winrct);
ar->type->refresh(C, ar);
ar->type->layout(C, ar);
}
wm_draw_region_buffer_create(ar, false, false);