Basic grid layout with big preview tiles

Draws the asset previews in a grid layout, similar to the "old" Asset
Browser. No interactivity is supported yet.
The layout is managed through the grid-view.
This commit is contained in:
Julian Eisel 2022-02-04 15:18:40 +01:00
parent 3df2e4e888
commit 925b82efb0
10 changed files with 287 additions and 28 deletions

View File

@ -23,27 +23,145 @@
#pragma once
#include "BLI_function_ref.hh"
#include "BLI_vector.hh"
#include "UI_resources.h"
struct PreviewImage;
struct uiBlock;
struct uiLayout;
struct wmNotifier;
namespace blender::ui {
class AbstractGridView;
/* ---------------------------------------------------------------------- */
/** \name Grid-View Item Type
* \{ */
class AbstractGridViewItem {
friend AbstractGridView;
const AbstractGridView *view_;
public:
virtual ~AbstractGridViewItem() = default;
virtual void build_grid_tile(uiLayout &layout) const = 0;
const AbstractGridView &get_view() const;
protected:
AbstractGridViewItem() = default;
};
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Grid-View Base Class
* \{ */
struct GridViewStyle {
GridViewStyle(int width, int height);
int tile_width = 0;
int tile_height = 0;
};
class AbstractGridView {
friend class GridViewBuilder;
friend class GridViewLayoutBuilder;
protected:
Vector<std::unique_ptr<AbstractGridViewItem>> items_;
GridViewStyle style_;
public:
AbstractGridView();
virtual ~AbstractGridView() = default;
using ItemIterFn = FunctionRef<void(AbstractGridViewItem &)>;
void foreach_item(ItemIterFn iter_fn) const;
/** Listen to a notifier, returning true if a redraw is needed. */
virtual bool listen(const wmNotifier &) const;
// protected:
virtual void build() = 0;
/**
* Convenience wrapper constructing the item by forwarding given arguments to the constructor of
* the type (\a ItemT).
*
* E.g. if your grid-item type has the following constructor:
* \code{.cpp}
* MyGridItem(std::string str, int i);
* \endcode
* You can add an item like this:
* \code
* add_item<MyGridItem>("blabla", 42);
* \endcode
*/
template<class ItemT, typename... Args> inline ItemT &add_item(Args &&...args);
const GridViewStyle &get_style() const;
protected:
virtual void build_items() = 0;
private:
/**
* Add an already constructed item, moving ownership to the grid-view.
* All items must be added through this, it handles important invariants!
*/
AbstractGridViewItem &add_item(std::unique_ptr<AbstractGridViewItem> item);
};
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Grid-View Builder
*
* TODO unify this with `TreeViewBuilder` and call view-specific functions via type erased view?
* \{ */
class GridViewBuilder {
uiBlock &block_;
public:
GridViewBuilder(uiBlock &block);
void build_grid_view(AbstractGridView &grid_view);
};
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Predefined Grid-View Item Types
*
* Common, Basic Grid-View Item Types.
* \{ */
/**
* A grid item that shows preview image icons at a nicely readable size (multiple of the normal UI
* unit size).
*/
class PreviewGridItem : public AbstractGridViewItem {
public:
std::string label{};
int preview_icon_id = ICON_NONE;
PreviewGridItem(StringRef label, int preview_icon_id);
void build_grid_tile(uiLayout &layout) const override;
};
/** \} */
/* ---------------------------------------------------------------------- */
template<class ItemT, typename... Args> inline ItemT &AbstractGridView::add_item(Args &&...args)
{
static_assert(std::is_base_of<AbstractGridViewItem, ItemT>::value,
"Type must derive from and implement the AbstractGridViewItem interface");
return dynamic_cast<ItemT &>(add_item(std::make_unique<ItemT>(std::forward<Args>(args)...)));
}
} // namespace blender::ui

View File

@ -1788,6 +1788,14 @@ AutoComplete *UI_autocomplete_begin(const char *startname, size_t maxlen);
void UI_autocomplete_update_name(AutoComplete *autocpl, const char *name);
int UI_autocomplete_end(AutoComplete *autocpl, char *autoname);
/**
* A decent size for a button (typically #UI_BTYPE_PREVIEW_TILE) to display a nicely readable
* preview with label in.
*/
int UI_preview_tile_size_x(void);
int UI_preview_tile_size_y(void);
int UI_preview_tile_size_y_no_label(void);
/* Panels
*
* Functions for creating, freeing and drawing panels. The API here

View File

@ -18,9 +18,12 @@
* \ingroup edinterface
*/
#include <stdexcept>
#include "WM_types.h"
#include "UI_interface.h"
#include "interface_intern.h"
#include "UI_grid_view.hh"
@ -28,12 +31,137 @@ namespace blender::ui {
/* ---------------------------------------------------------------------- */
AbstractGridView::AbstractGridView() : style_(UI_preview_tile_size_x(), UI_preview_tile_size_y())
{
}
AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr<AbstractGridViewItem> item)
{
items_.append(std::move(item));
AbstractGridViewItem &added_item = *items_.last();
added_item.view_ = this;
return added_item;
}
void AbstractGridView::foreach_item(ItemIterFn iter_fn) const
{
for (auto &item_ptr : items_) {
iter_fn(*item_ptr);
}
}
bool AbstractGridView::listen(const wmNotifier &) const
{
/* Nothing by default. */
return false;
}
const GridViewStyle &AbstractGridView::get_style() const
{
return style_;
}
GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height)
{
}
/* ---------------------------------------------------------------------- */
const AbstractGridView &AbstractGridViewItem::get_view() const
{
if (UNLIKELY(!view_)) {
throw std::runtime_error(
"Invalid state, item must be added through AbstractGridView::add_item()");
}
return *view_;
}
/* ---------------------------------------------------------------------- */
class GridViewLayoutBuilder {
uiBlock &block_;
friend GridViewBuilder;
public:
GridViewLayoutBuilder(uiBlock &block);
void build_from_view(const AbstractGridView &grid_view);
uiLayout *current_layout() const;
};
GridViewLayoutBuilder::GridViewLayoutBuilder(uiBlock &block) : block_(block)
{
}
void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view)
{
uiLayout &layout = *uiLayoutColumn(current_layout(), false);
const GridViewStyle &style = grid_view.get_style();
const int cols_per_row = uiLayoutGetWidth(&layout) / style.tile_width;
uiLayout *grid_layout = uiLayoutGridFlow(&layout, true, cols_per_row, true, true, true);
grid_view.foreach_item([&](AbstractGridViewItem &item) { item.build_grid_tile(*grid_layout); });
}
uiLayout *GridViewLayoutBuilder::current_layout() const
{
return block_.curlayout;
}
/* ---------------------------------------------------------------------- */
GridViewBuilder::GridViewBuilder(uiBlock &block) : block_(block)
{
}
void GridViewBuilder::build_grid_view(AbstractGridView &grid_view)
{
grid_view.build_items();
GridViewLayoutBuilder builder(block_);
builder.build_from_view(grid_view);
// grid_view.update_from_old(block_);
// grid_view.change_state_delayed();
// TreeViewLayoutBuilder builder(block_);
// builder.build_from_tree(tree_view);
}
/* ---------------------------------------------------------------------- */
PreviewGridItem::PreviewGridItem(StringRef label, int preview_icon_id)
: label(label), preview_icon_id(preview_icon_id)
{
}
void PreviewGridItem::build_grid_tile(uiLayout &layout) const
{
const GridViewStyle &style = get_view().get_style();
uiBlock *block = uiLayoutGetBlock(&layout);
uiBut *but = uiDefIconTextBut(block,
UI_BTYPE_PREVIEW_TILE,
0,
preview_icon_id,
label.c_str(),
0,
0,
style.tile_width,
style.tile_height,
nullptr,
0,
0,
0,
0,
"");
ui_def_but_icon(but,
preview_icon_id,
/* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */
UI_HAS_ICON | UI_BUT_ICON_PREVIEW);
}
} // namespace blender::ui
using namespace blender::ui;

View File

@ -4910,6 +4910,21 @@ int UI_autocomplete_end(AutoComplete *autocpl, char *autoname)
return match;
}
int UI_preview_tile_size_x(void)
{
return round_fl_to_int((96.0f / 20.0f) * UI_UNIT_X);
}
int UI_preview_tile_size_y(void)
{
return round_fl_to_int((96.0f / 20.0f) * UI_UNIT_Y);
}
int UI_preview_tile_size_y_no_label(void)
{
return round_fl_to_int((96.0f / 20.0f) * UI_UNIT_Y - UI_UNIT_Y);
}
static void ui_but_update_and_icon_set(uiBut *but, int icon)
{
if (icon) {

View File

@ -98,9 +98,8 @@ static void asset_view_draw_item(uiList *ui_list,
uiBlock *block = uiLayoutGetBlock(layout);
const bool show_names = list_data->show_names;
/* TODO ED_fileselect_init_layout(). Share somehow? */
const float size_x = (96.0f / 20.0f) * UI_UNIT_X;
const float size_y = (96.0f / 20.0f) * UI_UNIT_Y - (show_names ? 0 : UI_UNIT_Y);
const int size_x = UI_preview_tile_size_x();
const int size_y = show_names ? UI_preview_tile_size_y() : UI_preview_tile_size_y_no_label();
uiBut *but = uiDefIconTextBut(block,
UI_BTYPE_PREVIEW_TILE,
0,

View File

@ -958,13 +958,8 @@ static void ui_template_list_layout_draw(bContext *C,
const bool show_names = (flags & UI_TEMPLATE_LIST_NO_NAMES) == 0;
/* TODO ED_fileselect_init_layout(). Share somehow? */
float size_x = (96.0f / 20.0f) * UI_UNIT_X;
float size_y = (96.0f / 20.0f) * UI_UNIT_Y;
if (!show_names) {
size_y -= UI_UNIT_Y;
}
const int size_x = UI_preview_tile_size_x();
const int size_y = show_names ? UI_preview_tile_size_y() : UI_preview_tile_size_y_no_label();
const int cols_per_row = MAX2((uiLayoutGetWidth(box) - V2D_SCROLL_WIDTH) / size_x, 1);
uiLayout *grid = uiLayoutGridFlow(row, true, cols_per_row, true, true, true);

View File

@ -47,15 +47,8 @@ void asset_browser_main_region_draw(const bContext *C, ARegion *region)
const uiStyle *style = UI_style_get_dpi();
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
uiLayout *layout = UI_block_layout(block,
UI_LAYOUT_VERTICAL,
UI_LAYOUT_PANEL,
style->panelspace,
0,
region->sizex,
1,
0,
style);
uiLayout *layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, style->panelspace, 0, region->winx, 1, 0, style);
asset_view_create_in_layout(*C, asset_space->asset_library_ref, *layout);

View File

@ -38,11 +38,11 @@ AssetGridView::AssetGridView(const AssetLibraryReference &asset_library_ref, uiL
{
}
void AssetGridView::build()
void AssetGridView::build_items()
{
ED_assetlist_iterate(asset_library_ref_, [this](AssetHandle asset) {
uiItemL(
&layout, ED_asset_handle_get_name(&asset), ED_asset_handle_get_preview_icon_id(&asset));
add_item<ui::PreviewGridItem>(ED_asset_handle_get_name(&asset),
ED_asset_handle_get_preview_icon_id(&asset));
return true;
});
}
@ -57,16 +57,16 @@ void asset_view_create_in_layout(const bContext &C,
uiLayout &layout)
{
uiBlock *block = uiLayoutGetBlock(&layout);
UI_block_layout_set_current(block, &layout);
ED_assetlist_storage_fetch(&asset_library_ref, &C);
ED_assetlist_ensure_previews_job(&asset_library_ref, &C);
UI_block_layout_set_current(block, &layout);
ui::AbstractGridView *grid_view = UI_block_add_view(
*block, "asset grid view", std::make_unique<AssetGridView>(asset_library_ref, layout));
grid_view->build();
ui::GridViewBuilder builder(*block);
builder.build_grid_view(*grid_view);
}
} // namespace blender::ed::asset_browser

View File

@ -37,7 +37,7 @@ class AssetGridView : public blender::ui::AbstractGridView {
public:
AssetGridView(const AssetLibraryReference &, uiLayout &layout);
void build() override;
void build_items() override;
bool listen(const wmNotifier &) const override;
};

View File

@ -1003,6 +1003,7 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region)
if (params->display == FILE_IMGDISPLAY) {
const float pad_fac = compact ? 0.15f : 0.3f;
/* Matches UI_preview_tile_size_x()/_y() by default. */
layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X;
layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y;
layout->tile_border_x = pad_fac * UI_UNIT_X;
@ -1029,6 +1030,7 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region)
else if (params->display == FILE_VERTICALDISPLAY) {
int rowcount;
/* Matches UI_preview_tile_size_x()/_y() by default. */
layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X;
layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y;
layout->tile_border_x = 0.4f * UI_UNIT_X;
@ -1050,6 +1052,7 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region)
layout->flag = FILE_LAYOUT_VER;
}
else if (params->display == FILE_HORIZONTALDISPLAY) {
/* Matches UI_preview_tile_size_x()/_y() by default. */
layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X;
layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y;
layout->tile_border_x = 0.4f * UI_UNIT_X;