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:
parent
3df2e4e888
commit
925b82efb0
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue