UI: Previews for screen layouts

Basically all this does is drawing layout previews into the opened layout search menu.
https://youtu.be/RHYWtZP7pyA

The previews are drawn using offscreen rendering so they can't use multi-threading (yet!). But that shouldn't be an issue since only a handful of previews are drawn at the same time. Normally we only need to redraw the preview if a screen layout was changed. Would be nice if PreviewImage could store if it supports threaded rendering.
Previews are saved in files, might be useful if you later want to support appending layouts.
Adds a new file screen_draw.c.
This commit is contained in:
Julian Eisel 2016-12-01 16:43:57 +01:00
parent 4ff4dbc0ab
commit 52ec962516
16 changed files with 189 additions and 13 deletions

View File

@ -40,7 +40,7 @@ class INFO_HT_header(Header):
layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")
layout.separator()
else:
layout.template_ID(context.window, "screen", new="screen.new", unlink="screen.delete")
layout.template_ID_preview(context.window, "screen", new="screen.new", unlink="screen.delete", rows=2, cols=6)
layout.template_ID(context.screen, "scene", new="scene.new", unlink="scene.delete")
layout.separator()

View File

@ -42,6 +42,7 @@
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_texture_types.h"
#include "DNA_world_types.h"
#include "DNA_brush_types.h"
@ -252,6 +253,7 @@ PreviewImage **BKE_previewimg_id_get_p(ID *id)
ID_PRV_CASE(ID_OB, Object);
ID_PRV_CASE(ID_GR, Group);
ID_PRV_CASE(ID_SCE, Scene);
ID_PRV_CASE(ID_SCR, bScreen);
#undef ID_PRV_CASE
}

View File

@ -50,6 +50,7 @@
#include "BLI_utildefines.h"
#include "BLI_rect.h"
#include "BKE_icons.h"
#include "BKE_idprop.h"
#include "BKE_screen.h"
@ -394,6 +395,8 @@ void BKE_screen_free(bScreen *sc)
BLI_freelistN(&sc->vertbase);
BLI_freelistN(&sc->edgebase);
BLI_freelistN(&sc->areabase);
BKE_previewimg_free(&sc->preview);
}
/* for depsgraph */

View File

@ -6567,6 +6567,8 @@ static bool direct_link_screen(FileData *fd, bScreen *sc)
sc->mainwin = sc->subwinactive= 0; /* indices */
sc->swap = 0;
sc->preview = direct_link_preview_image(fd, sc->preview);
/* edges */
for (se = sc->edgebase.first; se; se = se->next) {
se->v1 = newdataadr(fd, se->v1);

View File

@ -2781,6 +2781,8 @@ static void write_screens(WriteData *wd, ListBase *scrbase)
writestruct(wd, ID_SCRN, bScreen, 1, sc);
write_iddata(wd, &sc->id);
write_previews(wd, sc->preview);
/* direct data */
for (sv = sc->vertbase.first; sv; sv = sv->next) {
writestruct(wd, DATA, ScrVert, 1, sv);

View File

@ -36,6 +36,8 @@
#include "DNA_view2d_types.h"
#include "DNA_view3d_types.h"
#include "BLI_compiler_attrs.h"
struct wmWindowManager;
struct wmWindow;
struct wmNotifier;
@ -47,6 +49,7 @@ struct bScreen;
struct ARegion;
struct uiBlock;
struct rcti;
struct Main;
/* regions */
void ED_region_do_listen(struct bScreen *sc, struct ScrArea *sa, struct ARegion *ar, struct wmNotifier *note);
@ -118,6 +121,7 @@ void ED_screen_full_restore(struct bContext *C, ScrArea *sa);
struct ScrArea *ED_screen_state_toggle(struct bContext *C, struct wmWindow *win, struct ScrArea *sa, const short state);
void ED_screens_header_tools_menu_create(struct bContext *C, struct uiLayout *layout, void *arg);
bool ED_screen_stereo3d_required(struct bScreen *screen);
void ED_screen_preview_render(const struct bScreen *screen, int size_x, int size_y, unsigned int *r_rect) ATTR_NONNULL();
/* anim */
void ED_update_for_newframe(struct Main *bmain, struct Scene *scene, int mute);

View File

@ -851,12 +851,15 @@ void ui_icon_ensure_deferred(const bContext *C, const int icon_id, const bool bi
{
ID *id = (icon->type != 0) ? icon->obj : NULL;
PreviewImage *prv = id ? BKE_previewimg_id_ensure(id) : icon->obj;
/* Using jobs for screen previews crashes due to offscreen rendering.
* XXX would be nicer if PreviewImage could store if it supports jobs */
const bool use_jobs = !id || (GS(id->name) != ID_SCR);
if (prv) {
const int size = big ? ICON_SIZE_PREVIEW : ICON_SIZE_ICON;
if (id || (prv->tag & PRV_TAG_DEFFERED) != 0) {
ui_id_preview_image_render_size(C, NULL, id, prv, size, true);
ui_id_preview_image_render_size(C, NULL, id, prv, size, use_jobs);
}
}
break;
@ -1169,7 +1172,7 @@ void UI_id_icon_render(const bContext *C, Scene *scene, ID *id, const bool big,
}
}
static void ui_id_brush_render(const bContext *C, ID *id)
static void ui_id_icon_render(const bContext *C, ID *id, bool use_jobs)
{
PreviewImage *pi = BKE_previewimg_id_ensure(id);
enum eIconSizes i;
@ -1181,7 +1184,7 @@ static void ui_id_brush_render(const bContext *C, ID *id)
/* check if rect needs to be created; changed
* only set by dynamic icons */
if (((pi->flag[i] & PRV_CHANGED) || !pi->rect[i])) {
icon_set_image(C, NULL, id, pi, i, true);
icon_set_image(C, NULL, id, pi, i, use_jobs);
pi->flag[i] &= ~PRV_CHANGED;
}
}
@ -1194,7 +1197,7 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id)
if (br->flag & BRUSH_CUSTOM_ICON) {
BKE_icon_id_ensure(id);
ui_id_brush_render(C, id);
ui_id_icon_render(C, id, true);
}
else {
Object *ob = CTX_data_active_object(C);
@ -1241,6 +1244,15 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id)
return id->icon_id;
}
static int ui_id_screen_get_icon(const bContext *C, ID *id)
{
BKE_icon_id_ensure(id);
/* Don't use jobs here, offscreen rendering doesn't like this and crashes. */
ui_id_icon_render(C, id, false);
return id->icon_id;
}
int ui_id_icon_get(const bContext *C, ID *id, const bool big)
{
int iconid = 0;
@ -1259,6 +1271,9 @@ int ui_id_icon_get(const bContext *C, ID *id, const bool big)
/* checks if not exists, or changed */
UI_id_icon_render(C, NULL, id, big, true);
break;
case ID_SCR:
iconid = ui_id_screen_get_icon(C, id);
break;
default:
break;
}

View File

@ -1059,7 +1059,7 @@ int ui_searchbox_autocomplete(bContext *C, ARegion *ar, uiBut *but, char *str)
return match;
}
static void ui_searchbox_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *ar)
{
uiSearchboxData *data = ar->regiondata;
@ -1077,6 +1077,9 @@ static void ui_searchbox_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
if (data->preview) {
/* draw items */
for (a = 0; a < data->items.totitem; a++) {
/* ensure icon is up-to-date */
ui_icon_ensure_deferred(C, data->items.icons[a], data->preview);
ui_searchbox_butrect(&rect, data, a);
/* widget itself */

View File

@ -415,17 +415,30 @@ static void template_ID(
type = idptr.type;
if (flag & UI_ID_PREVIEWS) {
ARegion *region = CTX_wm_region(C);
const bool use_big_size = (region->regiontype != RGN_TYPE_HEADER); /* silly check, could be more generic */
/* Ugly exception for screens here, drawing their preview in icon size looks ugly/useless */
const bool use_preview_icon = use_big_size || (id && (GS(id->name) != ID_SCR));
const short width = UI_UNIT_X * (use_big_size ? 6 : 1.6f);
const short height = UI_UNIT_Y * (use_big_size ? 6: 1);
template->preview = true;
but = uiDefBlockButN(block, id_search_menu, MEM_dupallocN(template), "", 0, 0, UI_UNIT_X * 6, UI_UNIT_Y * 6,
but = uiDefBlockButN(block, id_search_menu, MEM_dupallocN(template), "", 0, 0, width, height,
TIP_(template_id_browse_tip(type)));
ui_def_but_icon(but, id ? ui_id_icon_get(C, id, true) : RNA_struct_ui_icon(type),
UI_HAS_ICON | UI_BUT_ICON_PREVIEW);
if (use_preview_icon) {
ui_def_but_icon(but, ui_id_icon_get(C, id, use_big_size), UI_HAS_ICON | UI_BUT_ICON_PREVIEW);
}
else {
ui_def_but_icon(but, RNA_struct_ui_icon(type), UI_HAS_ICON);
UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT);
}
if ((idfrom && idfrom->lib) || !editable)
UI_but_flag_enable(but, UI_BUT_DISABLED);
uiLayoutRow(layout, true);
if (use_big_size) {
uiLayoutRow(layout, true);
}
}
else if (flag & UI_ID_BROWSE) {
but = uiDefBlockButN(block, id_search_menu, MEM_dupallocN(template), "", 0, 0, UI_UNIT_X * 1.6, UI_UNIT_Y,

View File

@ -94,6 +94,7 @@
#include "ED_datafiles.h"
#include "ED_render.h"
#include "ED_screen.h"
#ifndef NDEBUG
/* Used for database init assert(). */
@ -1022,6 +1023,12 @@ static void icon_preview_startjob(void *customdata, short *stop, short *do_updat
*do_update = true;
}
else if (idtype == ID_SCR) {
bScreen *screen = (bScreen *)id;
ED_screen_preview_render(screen, sp->sizex, sp->sizey, sp->pr_rect);
*do_update = true;
}
else {
/* re-use shader job */
shader_preview_startjob(customdata, stop, do_update);

View File

@ -42,6 +42,7 @@ set(SRC
area.c
glutil.c
screen_context.c
screen_draw.c
screen_edit.c
screen_ops.c
screendump.c

View File

@ -0,0 +1,111 @@
/*
* ***** 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.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/screen/screen_draw.c
* \ingroup edscr
*/
#include "ED_screen.h"
#include "GPU_framebuffer.h"
#include "GPU_immediate.h"
#include "WM_api.h"
/**
* Calculates a scale factor to squash the preview for \a screen into a rectangle of given size and aspect.
*/
static void screen_preview_scale_get(
const bScreen *screen, float size_x, float size_y,
const float asp[2],
float r_scale[2])
{
float max_x = 0, max_y = 0;
for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
max_x = MAX2(max_x, sa->totrct.xmax);
max_y = MAX2(max_y, sa->totrct.ymax);
}
r_scale[0] = (size_x * asp[0]) / max_x;
r_scale[1] = (size_y * asp[1]) / max_y;
}
static void screen_preview_draw_areas(const bScreen *screen, const float scale[2], const float col[4],
const float ofs_between_areas)
{
const float ofs_h = ofs_between_areas * 0.5f;
unsigned int pos = add_attrib(immVertexFormat(), "pos", GL_FLOAT, 2, KEEP_FLOAT);
rctf rect;
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
immUniformColor4fv(col);
for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
rect.xmin = sa->totrct.xmin * scale[0] + ofs_h;
rect.xmax = sa->totrct.xmax * scale[0] - ofs_h;
rect.ymin = sa->totrct.ymin * scale[1] + ofs_h;
rect.ymax = sa->totrct.ymax * scale[1] - ofs_h;
immBegin(PRIM_TRIANGLE_FAN, 4);
immVertex2f(pos, rect.xmin, rect.ymin);
immVertex2f(pos, rect.xmax, rect.ymin);
immVertex2f(pos, rect.xmax, rect.ymax);
immVertex2f(pos, rect.xmin, rect.ymax);
immEnd();
}
immUnbindProgram();
}
static void screen_preview_draw(const bScreen *screen, int size_x, int size_y)
{
const float asp[2] = {1.0f, 0.8f}; /* square previews look a bit ugly */
/* could use theme color (tui.wcol_menu_item.text), but then we'd need to regenerate all previews when changing */
const float col[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float scale[2];
wmOrtho2(0.0f, size_x, 0.0f, size_y);
/* center */
glTranslatef(size_x * (1.0f - asp[0]) * 0.5f, size_y * (1.0f - asp[1]) * 0.5f, 0.0f);
screen_preview_scale_get(screen, size_x, size_y, asp, scale);
screen_preview_draw_areas(screen, scale, col, 1.5f);
}
/**
* Render the preview for a screen layout in \a screen.
*/
void ED_screen_preview_render(const bScreen *screen, int size_x, int size_y, unsigned int *r_rect)
{
char err_out[256] = "unknown";
GPUOffScreen *offscreen = GPU_offscreen_create(size_x, size_y, 0, err_out);
GPU_offscreen_bind(offscreen, true);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
screen_preview_draw(screen, size_x, size_y);
GPU_offscreen_read_pixels(offscreen, GL_UNSIGNED_BYTE, r_rect);
GPU_offscreen_unbind(offscreen, true);
GPU_offscreen_free(offscreen);
}

View File

@ -42,6 +42,7 @@
#include "BKE_context.h"
#include "BKE_depsgraph.h"
#include "BKE_icons.h"
#include "BKE_image.h"
#include "BKE_global.h"
#include "BKE_library.h"
@ -615,7 +616,9 @@ int screen_area_join(bContext *C, bScreen *scr, ScrArea *sa1, ScrArea *sa2)
screen_delarea(C, scr, sa2);
removedouble_scrverts(scr);
sa1->flag &= ~AREA_FLAG_DRAWJOINFROM;
/* Update preview thumbnail */
BKE_icon_changed(scr->id.icon_id);
return 1;
}

View File

@ -53,6 +53,7 @@
#include "BKE_context.h"
#include "BKE_customdata.h"
#include "BKE_global.h"
#include "BKE_icons.h"
#include "BKE_main.h"
#include "BKE_object.h"
#include "BKE_report.h"
@ -1220,6 +1221,8 @@ static void area_move_apply_do(bContext *C, int origval, int delta, int dir, int
}
WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); /* redraw everything */
/* Update preview thumbnail */
BKE_icon_changed(sc->id.icon_id);
}
}
@ -1508,7 +1511,9 @@ static int area_split_apply(bContext *C, wmOperator *op)
ED_area_tag_redraw(sd->narea);
WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
/* Update preview thumbnail */
BKE_icon_changed(sc->id.icon_id);
return 1;
}

View File

@ -76,6 +76,8 @@ typedef struct bScreen {
struct wmTimer *animtimer; /* if set, screen has timer handler added in window */
void *context; /* context callback */
PreviewImage *preview;
} bScreen;
typedef struct ScrVert {

View File

@ -51,6 +51,7 @@
#include "BKE_blender.h"
#include "BKE_context.h"
#include "BKE_icons.h"
#include "BKE_library.h"
#include "BKE_global.h"
#include "BKE_main.h"
@ -1105,6 +1106,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
wm_window_make_drawable(wm, win);
wm_draw_window_clear(win);
BKE_icon_changed(win->screen->id.icon_id);
WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
@ -1206,6 +1208,7 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
if (U.pixelsize != prev_pixelsize) {
BKE_blender_userdef_refresh();
BKE_icon_changed(win->screen->id.icon_id);
// close all popups since they are positioned with the pixel
// size baked in and it's difficult to correct them