UI: Add initial "grid view"
Part of T98560. See https://wiki.blender.org/wiki/Source/Interface/Views Adds all the basic functionality needed for grid views. They display items in a grid of rows and columns, typically with a preview image and a label underneath. Think of the main region in the Asset Browser. Current features: - Active item - Notifier listening (also added this to the tree view) - Performance: Skip adding buttons that are not scrolled into view (solves performance problems for big asset libraries, for example). - Custom item size - Preview items (items that draw a preview with a label underneath) - Margins between items scale so the entire region width is filled with column, rather than leaving a big empty block at the right if there's not enough space for another column (like the File and current Asset Browser does it). - "Data-View Item" theme colors. Not shown in the UI yet. No user visible changes expected since the grid views aren't used for anything yet. This was developed as part of a rewrite of the Asset Browser UI (`asset-browser-grid-view` branch), see T95653. There's no reason to keep this part in a branch, continuing development in master makes things easier. Grid and tree views have a lot of very similar code, so I'm planning to unify them to a degree. I kept things separate for the start to first find out how much and what exactly makes sense to override.
This commit is contained in:
@@ -194,6 +194,15 @@ const bTheme U_theme_default = {
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
.roundness = 0.2f,
|
||||
},
|
||||
.wcol_view_item = {
|
||||
.outline = RGBA(0x2d2d2dff),
|
||||
.inner = RGBA(0x303030ff),
|
||||
.inner_sel = RGBA(0x4772b3ff),
|
||||
.item = RGBA(0x4772b3ff),
|
||||
.text = RGBA(0xccccccff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
.roundness = 0.2f,
|
||||
},
|
||||
.wcol_pie_menu = {
|
||||
.outline = RGBA(0x242424ff),
|
||||
.inner = RGBA(0x181818ff),
|
||||
|
||||
@@ -1196,6 +1196,8 @@ class ThemeGenericClassGenerator:
|
||||
("Scroll Bar", "wcol_scroll"),
|
||||
("Progress Bar", "wcol_progress"),
|
||||
("List Item", "wcol_list_item"),
|
||||
# Not used yet, so hide this from the UI.
|
||||
# ("Data-View Item", "wcol_view_item"),
|
||||
("Tab", "wcol_tab"),
|
||||
]
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
|
||||
*/
|
||||
{
|
||||
/* Keep this block, even when empty. */
|
||||
btheme->tui.wcol_view_item = U_theme_default.tui.wcol_view_item;
|
||||
}
|
||||
|
||||
#undef FROM_DEFAULT_V4_UCHAR
|
||||
|
||||
264
source/blender/editors/include/UI_grid_view.hh
Normal file
264
source/blender/editors/include/UI_grid_view.hh
Normal file
@@ -0,0 +1,264 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup editorui
|
||||
*
|
||||
* API for simple creation of grid UIs, supporting typically needed features.
|
||||
* https://wiki.blender.org/wiki/Source/Interface/Views/Grid_Views
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "UI_resources.h"
|
||||
|
||||
struct bContext;
|
||||
struct PreviewImage;
|
||||
struct uiBlock;
|
||||
struct uiButGridTile;
|
||||
struct uiLayout;
|
||||
struct View2D;
|
||||
struct wmNotifier;
|
||||
|
||||
namespace blender::ui {
|
||||
|
||||
class AbstractGridView;
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/** \name Grid-View Item Type
|
||||
* \{ */
|
||||
|
||||
class AbstractGridViewItem {
|
||||
friend class AbstractGridView;
|
||||
friend class GridViewLayoutBuilder;
|
||||
|
||||
const AbstractGridView *view_;
|
||||
|
||||
bool is_active_ = false;
|
||||
|
||||
protected:
|
||||
/** Reference to a string that uniquely identifies this item in the view. */
|
||||
StringRef identifier_{};
|
||||
/** Every visible item gets a button of type #UI_BTYPE_GRID_TILE during the layout building. */
|
||||
uiButGridTile *grid_tile_but_ = nullptr;
|
||||
|
||||
public:
|
||||
virtual ~AbstractGridViewItem() = default;
|
||||
|
||||
virtual void build_grid_tile(uiLayout &layout) const = 0;
|
||||
|
||||
/**
|
||||
* Compare this item's identifier to \a other to check if they represent the same data.
|
||||
* Used to recognize an item from a previous redraw, to be able to keep its state (e.g. active,
|
||||
* renaming, etc.).
|
||||
*/
|
||||
bool matches(const AbstractGridViewItem &other) const;
|
||||
|
||||
const AbstractGridView &get_view() const;
|
||||
|
||||
/**
|
||||
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we
|
||||
* can't be sure about the item state.
|
||||
*/
|
||||
bool is_active() const;
|
||||
|
||||
protected:
|
||||
AbstractGridViewItem(StringRef identifier);
|
||||
|
||||
/** Called when the item's state changes from inactive to active. */
|
||||
virtual void on_activate();
|
||||
/**
|
||||
* If the result is not empty, it controls whether the item should be active or not,
|
||||
* usually depending on the data that the view represents.
|
||||
*/
|
||||
virtual std::optional<bool> should_be_active() const;
|
||||
|
||||
/**
|
||||
* Copy persistent state (e.g. active, selection, etc.) from a matching item of
|
||||
* the last redraw to this item. If sub-classes introduce more advanced state they should
|
||||
* override this and make it update their state accordingly.
|
||||
*/
|
||||
virtual void update_from_old(const AbstractGridViewItem &old);
|
||||
|
||||
/**
|
||||
* Activates this item, deactivates other items, and calls the
|
||||
* #AbstractGridViewItem::on_activate() function.
|
||||
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise the
|
||||
* actual item state is unknown, possibly calling state-change update functions incorrectly.
|
||||
*/
|
||||
void activate();
|
||||
void deactivate();
|
||||
|
||||
private:
|
||||
/** See #AbstractTreeView::change_state_delayed() */
|
||||
void change_state_delayed();
|
||||
static void grid_tile_click_fn(bContext *, void *but_arg1, void *);
|
||||
void add_grid_tile_button(uiBlock &block);
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/** \name Grid-View Base Class
|
||||
* \{ */
|
||||
|
||||
struct GridViewStyle {
|
||||
GridViewStyle(int width, int height);
|
||||
int tile_width = 0;
|
||||
int tile_height = 0;
|
||||
};
|
||||
|
||||
class AbstractGridView {
|
||||
friend class AbstractGridViewItem;
|
||||
friend class GridViewBuilder;
|
||||
friend class GridViewLayoutBuilder;
|
||||
|
||||
protected:
|
||||
Vector<std::unique_ptr<AbstractGridViewItem>> items_;
|
||||
/** <identifier, item> map to lookup items by identifier, used for efficient lookups in
|
||||
* #update_from_old(). */
|
||||
Map<StringRef, AbstractGridViewItem *> item_map_;
|
||||
GridViewStyle style_;
|
||||
bool is_reconstructed_ = false;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
int get_item_count() const;
|
||||
|
||||
protected:
|
||||
virtual void build_items() = 0;
|
||||
|
||||
/**
|
||||
* Check if the view is fully (re-)constructed. That means, both #build_items() and
|
||||
* #update_from_old() have finished.
|
||||
*/
|
||||
bool is_reconstructed() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Match the grid-view against an earlier version of itself (if any) and copy the old UI state
|
||||
* (e.g. active, selected, renaming, etc.) to the new one. See
|
||||
* #AbstractGridViewItem.update_from_old().
|
||||
*/
|
||||
void update_from_old(uiBlock &new_block);
|
||||
AbstractGridViewItem *find_matching_item(const AbstractGridViewItem &item_to_match,
|
||||
const AbstractGridView &view_to_search_in) const;
|
||||
/**
|
||||
* Items may want to do additional work when state changes. But these state changes can only be
|
||||
* reliably detected after the view has completed reconstruction (see #is_reconstructed()). So
|
||||
* the actual state changes are done in a delayed manner through this function.
|
||||
*/
|
||||
void change_state_delayed();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/** Build \a grid_view into the previously provided block, clipped by \a view_bounds (view space,
|
||||
* typically `View2D.cur`). */
|
||||
void build_grid_view(AbstractGridView &grid_view, const View2D &v2d);
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/** \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:
|
||||
using IsActiveFn = std::function<bool()>;
|
||||
using ActivateFn = std::function<void(PreviewGridItem &new_active)>;
|
||||
|
||||
protected:
|
||||
/** See #set_on_activate_fn() */
|
||||
ActivateFn activate_fn_;
|
||||
/** See #set_is_active_fn() */
|
||||
IsActiveFn is_active_fn_;
|
||||
|
||||
public:
|
||||
std::string label{};
|
||||
int preview_icon_id = ICON_NONE;
|
||||
|
||||
PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id);
|
||||
|
||||
void build_grid_tile(uiLayout &layout) const override;
|
||||
|
||||
/**
|
||||
* Set a custom callback to execute when activating this view item. This way users don't have to
|
||||
* sub-class #PreviewGridItem, just to implement custom activation behavior (a common thing to
|
||||
* do).
|
||||
*/
|
||||
void set_on_activate_fn(ActivateFn fn);
|
||||
/**
|
||||
* Set a custom callback to check if this item should be active.
|
||||
*/
|
||||
void set_is_active_fn(IsActiveFn fn);
|
||||
|
||||
private:
|
||||
std::optional<bool> should_be_active() const override;
|
||||
void on_activate() 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
|
||||
@@ -64,6 +64,7 @@ struct wmKeyMapItem;
|
||||
struct wmMsgBus;
|
||||
struct wmOperator;
|
||||
struct wmOperatorType;
|
||||
struct wmRegionListenerParams;
|
||||
struct wmWindow;
|
||||
|
||||
typedef struct uiBlock uiBlock;
|
||||
@@ -75,6 +76,10 @@ typedef struct uiPopupBlockHandle uiPopupBlockHandle;
|
||||
typedef struct uiTreeViewHandle uiTreeViewHandle;
|
||||
/* C handle for C++ #ui::AbstractTreeViewItem type. */
|
||||
typedef struct uiTreeViewItemHandle uiTreeViewItemHandle;
|
||||
/* C handle for C++ #ui::AbstractGridView type. */
|
||||
typedef struct uiGridViewHandle uiGridViewHandle;
|
||||
/* C handle for C++ #ui::AbstractGridViewItem type. */
|
||||
typedef struct uiGridViewItemHandle uiGridViewItemHandle;
|
||||
|
||||
/* Defines */
|
||||
|
||||
@@ -390,6 +395,8 @@ typedef enum {
|
||||
UI_BTYPE_DECORATOR = 58 << 9,
|
||||
/* An item in a tree view. Parent items may be collapsible. */
|
||||
UI_BTYPE_TREEROW = 59 << 9,
|
||||
/* An item in a grid view. */
|
||||
UI_BTYPE_GRID_TILE = 60 << 9,
|
||||
} eButType;
|
||||
|
||||
#define BUTTYPE (63 << 9)
|
||||
@@ -1740,6 +1747,14 @@ struct PointerRNA *UI_but_extra_operator_icon_add(uiBut *but,
|
||||
struct wmOperatorType *UI_but_extra_operator_icon_optype_get(struct uiButExtraOpIcon *extra_icon);
|
||||
struct PointerRNA *UI_but_extra_operator_icon_opptr_get(struct uiButExtraOpIcon *extra_icon);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/* Autocomplete
|
||||
*
|
||||
* Tab complete helper functions, for use in uiButCompleteFunc callbacks.
|
||||
@@ -3185,7 +3200,12 @@ void UI_interface_tag_script_reload(void);
|
||||
/* Support click-drag motion which presses the button and closes a popover (like a menu). */
|
||||
#define USE_UI_POPOVER_ONCE
|
||||
|
||||
void UI_block_views_listen(const uiBlock *block,
|
||||
const struct wmRegionListenerParams *listener_params);
|
||||
|
||||
bool UI_grid_view_item_is_active(const uiGridViewItemHandle *item_handle);
|
||||
bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item);
|
||||
bool UI_grid_view_item_matches(const uiGridViewItemHandle *a, const uiGridViewItemHandle *b);
|
||||
bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b);
|
||||
/**
|
||||
* Attempt to start dragging the tree-item \a item_. This will not work if the tree item doesn't
|
||||
@@ -3223,6 +3243,15 @@ uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *regi
|
||||
const int xy[2]) ATTR_NONNULL(1, 2);
|
||||
uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const struct ARegion *region);
|
||||
|
||||
/**
|
||||
* Listen to \a notifier, returning true if the region should redraw.
|
||||
*/
|
||||
bool UI_tree_view_listen_should_redraw(const uiTreeViewHandle *view, const wmNotifier *notifier);
|
||||
/**
|
||||
* Listen to \a notifier, returning true if the region should redraw.
|
||||
*/
|
||||
bool UI_grid_view_listen_should_redraw(const uiGridViewHandle *view, const wmNotifier *notifier);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,7 @@ struct uiSearchItems;
|
||||
|
||||
namespace blender::ui {
|
||||
|
||||
class AbstractGridView;
|
||||
class AbstractTreeView;
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,10 @@ void attribute_search_add_items(
|
||||
/**
|
||||
* Override this for all available tree types.
|
||||
*/
|
||||
blender::ui::AbstractGridView *UI_block_add_view(
|
||||
uiBlock &block,
|
||||
blender::StringRef idname,
|
||||
std::unique_ptr<blender::ui::AbstractGridView> tree_view);
|
||||
blender::ui::AbstractTreeView *UI_block_add_view(
|
||||
uiBlock &block,
|
||||
blender::StringRef idname,
|
||||
|
||||
@@ -28,6 +28,7 @@ struct uiButTreeRow;
|
||||
struct uiLayout;
|
||||
struct wmDrag;
|
||||
struct wmEvent;
|
||||
struct wmNotifier;
|
||||
|
||||
namespace blender::ui {
|
||||
|
||||
@@ -128,6 +129,9 @@ class AbstractTreeView : public TreeViewItemContainer {
|
||||
|
||||
void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
|
||||
|
||||
/** Listen to a notifier, returning true if a redraw is needed. */
|
||||
virtual bool listen(const wmNotifier &) const;
|
||||
|
||||
/** Only one item can be renamed at a time. */
|
||||
bool is_renaming() const;
|
||||
|
||||
@@ -185,7 +189,7 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
|
||||
bool is_renaming_ = false;
|
||||
|
||||
protected:
|
||||
/** This label is used for identifying an item within its parent. */
|
||||
/** This label is used as the default way to identifying an item within its parent. */
|
||||
std::string label_{};
|
||||
/** Every visible item gets a button of type #UI_BTYPE_TREEROW during the layout building. */
|
||||
uiButTreeRow *tree_row_but_ = nullptr;
|
||||
|
||||
@@ -25,6 +25,7 @@ set(INC
|
||||
)
|
||||
|
||||
set(SRC
|
||||
grid_view.cc
|
||||
interface.cc
|
||||
interface_align.c
|
||||
interface_anim.c
|
||||
|
||||
525
source/blender/editors/interface/grid_view.cc
Normal file
525
source/blender/editors/interface/grid_view.cc
Normal file
@@ -0,0 +1,525 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edinterface
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "BLI_index_range.hh"
|
||||
|
||||
#include "WM_types.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "interface_intern.h"
|
||||
|
||||
#include "UI_grid_view.hh"
|
||||
|
||||
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;
|
||||
|
||||
item_map_.add(added_item.identifier_, &added_item);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
AbstractGridViewItem *AbstractGridView::find_matching_item(
|
||||
const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const
|
||||
{
|
||||
AbstractGridViewItem *const *match = view_to_search_in.item_map_.lookup_ptr(
|
||||
item_to_match.identifier_);
|
||||
BLI_assert(!match || item_to_match.matches(**match));
|
||||
|
||||
return match ? *match : nullptr;
|
||||
}
|
||||
|
||||
void AbstractGridView::change_state_delayed()
|
||||
{
|
||||
BLI_assert_msg(
|
||||
is_reconstructed(),
|
||||
"These state changes are supposed to be delayed until reconstruction is completed");
|
||||
foreach_item([](AbstractGridViewItem &item) { item.change_state_delayed(); });
|
||||
}
|
||||
|
||||
void AbstractGridView::update_from_old(uiBlock &new_block)
|
||||
{
|
||||
uiGridViewHandle *old_view_handle = ui_block_grid_view_find_matching_in_old_block(
|
||||
&new_block, reinterpret_cast<uiGridViewHandle *>(this));
|
||||
if (!old_view_handle) {
|
||||
/* Initial construction, nothing to update. */
|
||||
is_reconstructed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractGridView &old_view = reinterpret_cast<AbstractGridView &>(*old_view_handle);
|
||||
|
||||
foreach_item([this, &old_view](AbstractGridViewItem &new_item) {
|
||||
const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_view);
|
||||
if (!matching_old_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
new_item.update_from_old(*matching_old_item);
|
||||
});
|
||||
|
||||
/* Finished (re-)constructing the tree. */
|
||||
is_reconstructed_ = true;
|
||||
}
|
||||
|
||||
bool AbstractGridView::is_reconstructed() const
|
||||
{
|
||||
return is_reconstructed_;
|
||||
}
|
||||
|
||||
const GridViewStyle &AbstractGridView::get_style() const
|
||||
{
|
||||
return style_;
|
||||
}
|
||||
|
||||
int AbstractGridView::get_item_count() const
|
||||
{
|
||||
return items_.size();
|
||||
}
|
||||
|
||||
GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height)
|
||||
{
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
AbstractGridViewItem::AbstractGridViewItem(StringRef identifier) : identifier_(identifier)
|
||||
{
|
||||
}
|
||||
|
||||
bool AbstractGridViewItem::matches(const AbstractGridViewItem &other) const
|
||||
{
|
||||
return identifier_ == other.identifier_;
|
||||
}
|
||||
|
||||
void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/,
|
||||
void *but_arg1,
|
||||
void * /*arg2*/)
|
||||
{
|
||||
uiButGridTile *grid_tile_but = (uiButGridTile *)but_arg1;
|
||||
AbstractGridViewItem &grid_item = reinterpret_cast<AbstractGridViewItem &>(
|
||||
*grid_tile_but->view_item);
|
||||
|
||||
grid_item.activate();
|
||||
}
|
||||
|
||||
void AbstractGridViewItem::add_grid_tile_button(uiBlock &block)
|
||||
{
|
||||
const GridViewStyle &style = get_view().get_style();
|
||||
grid_tile_but_ = (uiButGridTile *)uiDefBut(&block,
|
||||
UI_BTYPE_GRID_TILE,
|
||||
0,
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
style.tile_width,
|
||||
style.tile_height,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"");
|
||||
|
||||
grid_tile_but_->view_item = reinterpret_cast<uiGridViewItemHandle *>(this);
|
||||
UI_but_func_set(&grid_tile_but_->but, grid_tile_click_fn, grid_tile_but_, nullptr);
|
||||
}
|
||||
|
||||
bool AbstractGridViewItem::is_active() const
|
||||
{
|
||||
BLI_assert_msg(get_view().is_reconstructed(),
|
||||
"State can't be queried until reconstruction is completed");
|
||||
return is_active_;
|
||||
}
|
||||
|
||||
void AbstractGridViewItem::on_activate()
|
||||
{
|
||||
/* Do nothing by default. */
|
||||
}
|
||||
|
||||
std::optional<bool> AbstractGridViewItem::should_be_active() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void AbstractGridViewItem::change_state_delayed()
|
||||
{
|
||||
const std::optional<bool> should_be_active = this->should_be_active();
|
||||
if (should_be_active.has_value() && *should_be_active) {
|
||||
activate();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractGridViewItem::update_from_old(const AbstractGridViewItem &old)
|
||||
{
|
||||
is_active_ = old.is_active_;
|
||||
}
|
||||
|
||||
void AbstractGridViewItem::activate()
|
||||
{
|
||||
BLI_assert_msg(get_view().is_reconstructed(),
|
||||
"Item activation can't be done until reconstruction is completed");
|
||||
|
||||
if (is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Deactivate other items in the tree. */
|
||||
get_view().foreach_item([](auto &item) { item.deactivate(); });
|
||||
|
||||
on_activate();
|
||||
|
||||
is_active_ = true;
|
||||
}
|
||||
|
||||
void AbstractGridViewItem::deactivate()
|
||||
{
|
||||
is_active_ = false;
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Helper for only adding layout items for grid items that are actually in view. 3 main functions:
|
||||
* - #is_item_visible(): Query if an item of a given index is visible in the view (others should be
|
||||
* skipped when building the layout).
|
||||
* - #fill_layout_before_visible(): Add empty space to the layout before a visible row is drawn, so
|
||||
* the layout height is the same as if all items were added (important to get the correct scroll
|
||||
* height).
|
||||
* - #fill_layout_after_visible(): Same thing, just adds empty space for after the last visible
|
||||
* row.
|
||||
*
|
||||
* Does two assumptions:
|
||||
* - Top-to-bottom flow (ymax = 0 and ymin < 0). If that's not good enough, View2D should
|
||||
* probably provide queries for the scroll offset.
|
||||
* - Only vertical scrolling. For horizontal scrolling, spacers would have to be added on the
|
||||
* side(s) as well.
|
||||
*/
|
||||
class BuildOnlyVisibleButtonsHelper {
|
||||
const View2D &v2d_;
|
||||
const AbstractGridView &grid_view_;
|
||||
const GridViewStyle &style_;
|
||||
const int cols_per_row_ = 0;
|
||||
/* Indices of items within the view. Calculated by constructor */
|
||||
IndexRange visible_items_range_{};
|
||||
|
||||
public:
|
||||
BuildOnlyVisibleButtonsHelper(const View2D &,
|
||||
const AbstractGridView &grid_view,
|
||||
int cols_per_row);
|
||||
|
||||
bool is_item_visible(int item_idx) const;
|
||||
void fill_layout_before_visible(uiBlock &) const;
|
||||
void fill_layout_after_visible(uiBlock &) const;
|
||||
|
||||
private:
|
||||
IndexRange get_visible_range() const;
|
||||
void add_spacer_button(uiBlock &, int row_count) const;
|
||||
};
|
||||
|
||||
BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d,
|
||||
const AbstractGridView &grid_view,
|
||||
const int cols_per_row)
|
||||
: v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row)
|
||||
{
|
||||
visible_items_range_ = get_visible_range();
|
||||
}
|
||||
|
||||
IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const
|
||||
{
|
||||
int first_idx_in_view = 0;
|
||||
int max_items_in_view = 0;
|
||||
|
||||
const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax);
|
||||
if (!IS_EQF(scroll_ofs_y, 0)) {
|
||||
const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height;
|
||||
|
||||
first_idx_in_view = scrolled_away_rows * cols_per_row_;
|
||||
}
|
||||
|
||||
const float view_height = BLI_rctf_size_y(&v2d_.cur);
|
||||
const int count_rows_in_view = std::max(round_fl_to_int(view_height / style_.tile_height), 1);
|
||||
max_items_in_view = (count_rows_in_view + 1) * cols_per_row_;
|
||||
|
||||
BLI_assert(max_items_in_view > 0);
|
||||
return IndexRange(first_idx_in_view, max_items_in_view);
|
||||
}
|
||||
|
||||
bool BuildOnlyVisibleButtonsHelper::is_item_visible(const int item_idx) const
|
||||
{
|
||||
return visible_items_range_.contains(item_idx);
|
||||
}
|
||||
|
||||
void BuildOnlyVisibleButtonsHelper::fill_layout_before_visible(uiBlock &block) const
|
||||
{
|
||||
const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax);
|
||||
|
||||
if (IS_EQF(scroll_ofs_y, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height;
|
||||
add_spacer_button(block, scrolled_away_rows);
|
||||
}
|
||||
|
||||
void BuildOnlyVisibleButtonsHelper::fill_layout_after_visible(uiBlock &block) const
|
||||
{
|
||||
const int last_item_idx = grid_view_.get_item_count() - 1;
|
||||
const int last_visible_idx = visible_items_range_.last();
|
||||
|
||||
if (last_item_idx > last_visible_idx) {
|
||||
const int remaining_rows = (cols_per_row_ > 0) ?
|
||||
(last_item_idx - last_visible_idx) / cols_per_row_ :
|
||||
0;
|
||||
BuildOnlyVisibleButtonsHelper::add_spacer_button(block, remaining_rows);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildOnlyVisibleButtonsHelper::add_spacer_button(uiBlock &block, const int row_count) const
|
||||
{
|
||||
/* UI code only supports button dimensions of `signed short` size, the layout height we want to
|
||||
* fill may be bigger than that. So add multiple labels of the maximum size if necessary. */
|
||||
for (int remaining_rows = row_count; remaining_rows > 0;) {
|
||||
const short row_count_this_iter = std::min(
|
||||
std::numeric_limits<short>::max() / style_.tile_height, remaining_rows);
|
||||
|
||||
uiDefBut(&block,
|
||||
UI_BTYPE_LABEL,
|
||||
0,
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
UI_UNIT_X,
|
||||
row_count_this_iter * style_.tile_height,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"");
|
||||
remaining_rows -= row_count_this_iter;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
class GridViewLayoutBuilder {
|
||||
uiBlock &block_;
|
||||
|
||||
friend class GridViewBuilder;
|
||||
|
||||
public:
|
||||
GridViewLayoutBuilder(uiBlock &block);
|
||||
|
||||
void build_from_view(const AbstractGridView &grid_view, const View2D &v2d) const;
|
||||
|
||||
private:
|
||||
void build_grid_tile(uiLayout &grid_layout, AbstractGridViewItem &item) const;
|
||||
|
||||
uiLayout *current_layout() const;
|
||||
};
|
||||
|
||||
GridViewLayoutBuilder::GridViewLayoutBuilder(uiBlock &block) : block_(block)
|
||||
{
|
||||
}
|
||||
|
||||
void GridViewLayoutBuilder::build_grid_tile(uiLayout &grid_layout,
|
||||
AbstractGridViewItem &item) const
|
||||
{
|
||||
uiLayout *overlap = uiLayoutOverlap(&grid_layout);
|
||||
|
||||
item.add_grid_tile_button(block_);
|
||||
item.build_grid_tile(*uiLayoutRow(overlap, false));
|
||||
}
|
||||
|
||||
void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view,
|
||||
const View2D &v2d) const
|
||||
{
|
||||
uiLayout *prev_layout = current_layout();
|
||||
|
||||
uiLayout &layout = *uiLayoutColumn(current_layout(), false);
|
||||
const GridViewStyle &style = grid_view.get_style();
|
||||
|
||||
const int cols_per_row = std::max(uiLayoutGetWidth(&layout) / style.tile_width, 1);
|
||||
|
||||
BuildOnlyVisibleButtonsHelper build_visible_helper(v2d, grid_view, cols_per_row);
|
||||
|
||||
build_visible_helper.fill_layout_before_visible(block_);
|
||||
|
||||
/* Use `-cols_per_row` because the grid layout uses a multiple of the passed absolute value for
|
||||
* the number of columns then, rather than distributing the number of items evenly over rows and
|
||||
* stretching the items to fit (see #uiLayoutItemGridFlow.columns_len). */
|
||||
uiLayout *grid_layout = uiLayoutGridFlow(&layout, true, -cols_per_row, true, true, true);
|
||||
|
||||
int item_idx = 0;
|
||||
grid_view.foreach_item([&](AbstractGridViewItem &item) {
|
||||
/* Skip if item isn't visible. */
|
||||
if (!build_visible_helper.is_item_visible(item_idx)) {
|
||||
item_idx++;
|
||||
return;
|
||||
}
|
||||
|
||||
build_grid_tile(*grid_layout, item);
|
||||
item_idx++;
|
||||
});
|
||||
|
||||
/* If there are not enough items to fill the layout, add padding items so the layout doesn't
|
||||
* stretch over the entire width. */
|
||||
if (grid_view.get_item_count() < cols_per_row) {
|
||||
for (int padding_item_idx = 0; padding_item_idx < (cols_per_row - grid_view.get_item_count());
|
||||
padding_item_idx++) {
|
||||
uiItemS(grid_layout);
|
||||
}
|
||||
}
|
||||
|
||||
UI_block_layout_set_current(&block_, prev_layout);
|
||||
|
||||
build_visible_helper.fill_layout_after_visible(block_);
|
||||
}
|
||||
|
||||
uiLayout *GridViewLayoutBuilder::current_layout() const
|
||||
{
|
||||
return block_.curlayout;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
GridViewBuilder::GridViewBuilder(uiBlock &block) : block_(block)
|
||||
{
|
||||
}
|
||||
|
||||
void GridViewBuilder::build_grid_view(AbstractGridView &grid_view, const View2D &v2d)
|
||||
{
|
||||
grid_view.build_items();
|
||||
grid_view.update_from_old(block_);
|
||||
grid_view.change_state_delayed();
|
||||
|
||||
GridViewLayoutBuilder builder(block_);
|
||||
builder.build_from_view(grid_view, v2d);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
PreviewGridItem::PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id)
|
||||
: AbstractGridViewItem(identifier), 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 = uiDefBut(block,
|
||||
UI_BTYPE_PREVIEW_TILE,
|
||||
0,
|
||||
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);
|
||||
}
|
||||
|
||||
void PreviewGridItem::set_on_activate_fn(ActivateFn fn)
|
||||
{
|
||||
activate_fn_ = fn;
|
||||
}
|
||||
|
||||
void PreviewGridItem::set_is_active_fn(IsActiveFn fn)
|
||||
{
|
||||
is_active_fn_ = fn;
|
||||
}
|
||||
|
||||
void PreviewGridItem::on_activate()
|
||||
{
|
||||
if (activate_fn_) {
|
||||
activate_fn_(*this);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<bool> PreviewGridItem::should_be_active() const
|
||||
{
|
||||
if (is_active_fn_) {
|
||||
return is_active_fn_();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace blender::ui
|
||||
|
||||
using namespace blender::ui;
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* C-API */
|
||||
|
||||
using namespace blender::ui;
|
||||
|
||||
bool UI_grid_view_item_is_active(const uiGridViewItemHandle *item_handle)
|
||||
{
|
||||
const AbstractGridViewItem &item = reinterpret_cast<const AbstractGridViewItem &>(*item_handle);
|
||||
return item.is_active();
|
||||
}
|
||||
|
||||
bool UI_grid_view_listen_should_redraw(const uiGridViewHandle *view_handle,
|
||||
const wmNotifier *notifier)
|
||||
{
|
||||
const AbstractGridView &view = *reinterpret_cast<const AbstractGridView *>(view_handle);
|
||||
return view.listen(*notifier);
|
||||
}
|
||||
|
||||
bool UI_grid_view_item_matches(const uiGridViewItemHandle *a_handle,
|
||||
const uiGridViewItemHandle *b_handle)
|
||||
{
|
||||
const AbstractGridViewItem &a = reinterpret_cast<const AbstractGridViewItem &>(*a_handle);
|
||||
const AbstractGridViewItem &b = reinterpret_cast<const AbstractGridViewItem &>(*b_handle);
|
||||
return a.matches(b);
|
||||
}
|
||||
@@ -778,6 +778,15 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
|
||||
}
|
||||
}
|
||||
|
||||
if ((but->type == UI_BTYPE_GRID_TILE) && (oldbut->type == UI_BTYPE_GRID_TILE)) {
|
||||
uiButGridTile *but_gridtile = (uiButGridTile *)but;
|
||||
uiButGridTile *oldbut_gridtile = (uiButGridTile *)oldbut;
|
||||
if (!but_gridtile->view_item || !oldbut_gridtile->view_item ||
|
||||
!UI_grid_view_item_matches(but_gridtile->view_item, oldbut_gridtile->view_item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -904,6 +913,12 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
|
||||
SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item);
|
||||
break;
|
||||
}
|
||||
case UI_BTYPE_GRID_TILE: {
|
||||
uiButGridTile *gridtile_oldbut = (uiButGridTile *)oldbut;
|
||||
uiButGridTile *gridtile_newbut = (uiButGridTile *)but;
|
||||
SWAP(uiGridViewItemHandle *, gridtile_newbut->view_item, gridtile_oldbut->view_item);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -996,9 +1011,9 @@ static bool ui_but_update_from_old_block(const bContext *C,
|
||||
else {
|
||||
int flag_copy = UI_BUT_DRAG_MULTI;
|
||||
|
||||
/* Stupid special case: The active button may be inside (as in, overlapped on top) a tree-row
|
||||
/* Stupid special case: The active button may be inside (as in, overlapped on top) a view-item
|
||||
* button which we also want to keep highlighted then. */
|
||||
if (but->type == UI_BTYPE_TREEROW) {
|
||||
if (ui_but_is_view_item(but)) {
|
||||
flag_copy |= UI_ACTIVE;
|
||||
}
|
||||
|
||||
@@ -2239,6 +2254,15 @@ int ui_but_is_pushed_ex(uiBut *but, double *value)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UI_BTYPE_GRID_TILE: {
|
||||
uiButGridTile *grid_tile_but = (uiButGridTile *)but;
|
||||
|
||||
is_push = -1;
|
||||
if (grid_tile_but->view_item) {
|
||||
is_push = UI_grid_view_item_is_active(grid_tile_but->view_item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
is_push = -1;
|
||||
break;
|
||||
@@ -3995,6 +4019,10 @@ static void ui_but_alloc_info(const eButType type,
|
||||
alloc_size = sizeof(uiButHotkeyEvent);
|
||||
alloc_str = "uiButHotkeyEvent";
|
||||
break;
|
||||
case UI_BTYPE_GRID_TILE:
|
||||
alloc_size = sizeof(uiButGridTile);
|
||||
alloc_str = "uiButGridTile";
|
||||
break;
|
||||
default:
|
||||
alloc_size = sizeof(uiBut);
|
||||
alloc_str = "uiBut";
|
||||
@@ -4969,6 +4997,33 @@ int UI_autocomplete_end(AutoComplete *autocpl, char *autoname)
|
||||
return match;
|
||||
}
|
||||
|
||||
#define PREVIEW_TILE_PAD (0.15f * UI_UNIT_X)
|
||||
|
||||
int UI_preview_tile_size_x(void)
|
||||
{
|
||||
const float pad = PREVIEW_TILE_PAD;
|
||||
return round_fl_to_int((96.0f / 20.0f) * UI_UNIT_X + 2.0f * pad);
|
||||
}
|
||||
|
||||
int UI_preview_tile_size_y(void)
|
||||
{
|
||||
const uiStyle *style = UI_style_get();
|
||||
const float font_height = style->widget.points * UI_DPI_FAC;
|
||||
const float pad = PREVIEW_TILE_PAD;
|
||||
|
||||
return round_fl_to_int(UI_preview_tile_size_y_no_label() + font_height +
|
||||
/* Add some extra padding to make things less tight vertically. */
|
||||
pad);
|
||||
}
|
||||
|
||||
int UI_preview_tile_size_y_no_label(void)
|
||||
{
|
||||
const float pad = PREVIEW_TILE_PAD;
|
||||
return round_fl_to_int((96.0f / 20.0f) * UI_UNIT_Y + 2.0f * pad);
|
||||
}
|
||||
|
||||
#undef PREVIEW_TILE_PAD
|
||||
|
||||
static void ui_but_update_and_icon_set(uiBut *but, int icon)
|
||||
{
|
||||
if (icon) {
|
||||
|
||||
@@ -2289,6 +2289,9 @@ static void ui_apply_but(
|
||||
case UI_BTYPE_ROW:
|
||||
ui_apply_but_ROW(C, block, but, data);
|
||||
break;
|
||||
case UI_BTYPE_GRID_TILE:
|
||||
ui_apply_but_ROW(C, block, but, data);
|
||||
break;
|
||||
case UI_BTYPE_TREEROW:
|
||||
ui_apply_but_TREEROW(C, block, but, data);
|
||||
break;
|
||||
@@ -4810,6 +4813,47 @@ static int ui_do_but_TREEROW(bContext *C,
|
||||
return WM_UI_HANDLER_CONTINUE;
|
||||
}
|
||||
|
||||
static int ui_do_but_GRIDTILE(bContext *C,
|
||||
uiBut *but,
|
||||
uiHandleButtonData *data,
|
||||
const wmEvent *event)
|
||||
{
|
||||
uiButGridTile *grid_tile_but = (uiButGridTile *)but;
|
||||
BLI_assert(grid_tile_but->but.type == UI_BTYPE_GRID_TILE);
|
||||
|
||||
if (data->state == BUTTON_STATE_HIGHLIGHT) {
|
||||
if (event->type == LEFTMOUSE) {
|
||||
switch (event->val) {
|
||||
case KM_PRESS:
|
||||
/* Extra icons have priority, don't mess with them. */
|
||||
if (ui_but_extra_operator_icon_mouse_over_get(but, data->region, event)) {
|
||||
return WM_UI_HANDLER_BREAK;
|
||||
}
|
||||
button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
|
||||
data->dragstartx = event->xy[0];
|
||||
data->dragstarty = event->xy[1];
|
||||
return WM_UI_HANDLER_CONTINUE;
|
||||
|
||||
case KM_CLICK:
|
||||
button_activate_state(C, but, BUTTON_STATE_EXIT);
|
||||
return WM_UI_HANDLER_BREAK;
|
||||
|
||||
case KM_DBL_CLICK:
|
||||
data->cancel = true;
|
||||
// UI_tree_view_item_begin_rename(grid_tile_but->tree_item);
|
||||
ED_region_tag_redraw(CTX_wm_region(C));
|
||||
return WM_UI_HANDLER_BREAK;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data->state == BUTTON_STATE_WAIT_DRAG) {
|
||||
/* Let "default" button handling take care of the drag logic. */
|
||||
return ui_do_but_EXIT(C, but, data, event);
|
||||
}
|
||||
|
||||
return WM_UI_HANDLER_CONTINUE;
|
||||
}
|
||||
|
||||
static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
|
||||
{
|
||||
if (data->state == BUTTON_STATE_HIGHLIGHT) {
|
||||
@@ -4851,6 +4895,10 @@ static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, con
|
||||
ret = WM_UI_HANDLER_CONTINUE;
|
||||
}
|
||||
}
|
||||
const uiBut *view_but = ui_view_item_find_mouse_over(data->region, event->xy);
|
||||
if (view_but) {
|
||||
ret = WM_UI_HANDLER_CONTINUE;
|
||||
}
|
||||
button_activate_state(C, but, BUTTON_STATE_EXIT);
|
||||
return ret;
|
||||
}
|
||||
@@ -8005,6 +8053,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
|
||||
case UI_BTYPE_ROW:
|
||||
retval = ui_do_but_TOG(C, but, data, event);
|
||||
break;
|
||||
case UI_BTYPE_GRID_TILE:
|
||||
retval = ui_do_but_GRIDTILE(C, but, data, event);
|
||||
break;
|
||||
case UI_BTYPE_TREEROW:
|
||||
retval = ui_do_but_TREEROW(C, but, data, event);
|
||||
break;
|
||||
@@ -9672,31 +9723,31 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ui_handle_tree_hover(const wmEvent *event, const ARegion *region)
|
||||
static int ui_handle_view_items_hover(const wmEvent *event, const ARegion *region)
|
||||
{
|
||||
bool has_treerows = false;
|
||||
bool has_view_item = false;
|
||||
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
||||
/* Avoid unnecessary work: Tree-rows are assumed to be inside tree-views. */
|
||||
/* Avoid unnecessary work: view item buttons are assumed to be inside views. */
|
||||
if (BLI_listbase_is_empty(&block->views)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
|
||||
if (but->type == UI_BTYPE_TREEROW) {
|
||||
if (ui_but_is_view_item(but)) {
|
||||
but->flag &= ~UI_ACTIVE;
|
||||
has_treerows = true;
|
||||
has_view_item = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_treerows) {
|
||||
if (!has_view_item) {
|
||||
/* Avoid unnecessary lookup. */
|
||||
return WM_UI_HANDLER_CONTINUE;
|
||||
}
|
||||
|
||||
/* Always highlight the hovered tree-row, even if the mouse hovers another button inside of it.
|
||||
/* Always highlight the hovered view item, even if the mouse hovers another button inside of it.
|
||||
*/
|
||||
uiBut *hovered_row_but = ui_tree_row_find_mouse_over(region, event->xy);
|
||||
uiBut *hovered_row_but = ui_view_item_find_mouse_over(region, event->xy);
|
||||
if (hovered_row_but) {
|
||||
hovered_row_but->flag |= UI_ACTIVE;
|
||||
}
|
||||
@@ -9704,6 +9755,21 @@ static int ui_handle_tree_hover(const wmEvent *event, const ARegion *region)
|
||||
return WM_UI_HANDLER_CONTINUE;
|
||||
}
|
||||
|
||||
static int ui_handle_view_item_event(bContext *C,
|
||||
const wmEvent *event,
|
||||
ARegion *region,
|
||||
uiBut *view_but)
|
||||
{
|
||||
BLI_assert(ui_but_is_view_item(view_but));
|
||||
if (event->type == LEFTMOUSE) {
|
||||
/* Will free active button if there already is one. */
|
||||
ui_handle_button_activate(C, region, view_but, BUTTON_ACTIVATE_OVER);
|
||||
return ui_do_button(C, view_but->block, view_but, event);
|
||||
}
|
||||
|
||||
return WM_UI_HANDLER_CONTINUE;
|
||||
}
|
||||
|
||||
static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but)
|
||||
{
|
||||
uiHandleButtonData *data = but->active;
|
||||
@@ -11304,9 +11370,15 @@ static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(use
|
||||
ui_blocks_set_tooltips(region, true);
|
||||
}
|
||||
|
||||
/* Always do this, to reliably update tree-row highlighting, even if the mouse hovers a button
|
||||
* inside the row (it's an overlapping layout). */
|
||||
ui_handle_tree_hover(event, region);
|
||||
/* Always do this, to reliably update view item highlighting, even if the mouse hovers a button
|
||||
* nested in the item (it's an overlapping layout). */
|
||||
ui_handle_view_items_hover(event, region);
|
||||
if (retval == WM_UI_HANDLER_CONTINUE) {
|
||||
uiBut *view_item = ui_view_item_find_mouse_over(region, event->xy);
|
||||
if (view_item) {
|
||||
retval = ui_handle_view_item_event(C, event, region, view_item);
|
||||
}
|
||||
}
|
||||
|
||||
/* delayed apply callbacks */
|
||||
ui_apply_but_funcs_after(C);
|
||||
|
||||
@@ -351,6 +351,13 @@ typedef struct uiButTreeRow {
|
||||
int indentation;
|
||||
} uiButTreeRow;
|
||||
|
||||
/** Derived struct for #UI_BTYPE_GRID_TILE. */
|
||||
typedef struct uiButGridTile {
|
||||
uiBut but;
|
||||
|
||||
uiGridViewItemHandle *view_item;
|
||||
} uiButGridTile;
|
||||
|
||||
/** Derived struct for #UI_BTYPE_HSVCUBE. */
|
||||
typedef struct uiButHSVCube {
|
||||
uiBut but;
|
||||
@@ -1365,6 +1372,7 @@ void ui_but_anim_decorate_update_from_flag(uiButDecorator *but);
|
||||
bool ui_but_is_editable(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
|
||||
bool ui_but_is_editable_as_text(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
|
||||
bool ui_but_is_toggle(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
|
||||
bool ui_but_is_view_item(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
|
||||
/**
|
||||
* Can we mouse over the button or is it hidden/disabled/layout.
|
||||
* \note ctrl is kind of a hack currently,
|
||||
@@ -1396,6 +1404,8 @@ uiBut *ui_list_row_find_mouse_over(const struct ARegion *region, const int xy[2]
|
||||
uiBut *ui_list_row_find_from_index(const struct ARegion *region,
|
||||
int index,
|
||||
uiBut *listbox) ATTR_WARN_UNUSED_RESULT;
|
||||
uiBut *ui_view_item_find_mouse_over(const struct ARegion *region, const int xy[2])
|
||||
ATTR_NONNULL(1, 2);
|
||||
uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int xy[2])
|
||||
ATTR_NONNULL(1, 2);
|
||||
uiBut *ui_tree_row_find_active(const struct ARegion *region);
|
||||
@@ -1533,8 +1543,10 @@ void ui_interface_tag_script_reload_queries(void);
|
||||
/* interface_view.cc */
|
||||
|
||||
void ui_block_free_views(struct uiBlock *block);
|
||||
uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
|
||||
const uiTreeViewHandle *new_view);
|
||||
uiTreeViewHandle *ui_block_tree_view_find_matching_in_old_block(const uiBlock *new_block,
|
||||
const uiTreeViewHandle *new_view);
|
||||
uiGridViewHandle *ui_block_grid_view_find_matching_in_old_block(
|
||||
const uiBlock *new_block, const uiGridViewHandle *new_view_handle);
|
||||
uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
|
||||
const uiTreeViewItemHandle *new_item_handle);
|
||||
|
||||
|
||||
@@ -58,6 +58,11 @@ bool ui_but_is_toggle(const uiBut *but)
|
||||
UI_BTYPE_TREEROW);
|
||||
}
|
||||
|
||||
bool ui_but_is_view_item(const uiBut *but)
|
||||
{
|
||||
return ELEM(but->type, UI_BTYPE_TREEROW, UI_BTYPE_GRID_TILE);
|
||||
}
|
||||
|
||||
bool ui_but_is_interactive_ex(const uiBut *but, const bool labeledit, const bool for_tooltip)
|
||||
{
|
||||
/* NOTE: #UI_BTYPE_LABEL is included for highlights, this allows drags. */
|
||||
@@ -462,6 +467,16 @@ static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata))
|
||||
return but->type == UI_BTYPE_TREEROW;
|
||||
}
|
||||
|
||||
static bool ui_but_is_view_item_fn(const uiBut *but, const void *UNUSED(customdata))
|
||||
{
|
||||
return ui_but_is_view_item(but);
|
||||
}
|
||||
|
||||
uiBut *ui_view_item_find_mouse_over(const ARegion *region, const int xy[2])
|
||||
{
|
||||
return ui_but_find_mouse_over_ex(region, xy, false, false, ui_but_is_view_item_fn, nullptr);
|
||||
}
|
||||
|
||||
uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2])
|
||||
{
|
||||
return ui_but_find_mouse_over_ex(region, xy, false, false, ui_but_is_treerow, nullptr);
|
||||
|
||||
@@ -945,13 +945,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);
|
||||
|
||||
@@ -10,15 +10,22 @@
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
#include "DNA_screen_types.h"
|
||||
|
||||
#include "BKE_screen.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "ED_screen.h"
|
||||
|
||||
#include "interface_intern.h"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
|
||||
#include "UI_grid_view.hh"
|
||||
#include "UI_tree_view.hh"
|
||||
|
||||
using namespace blender;
|
||||
@@ -30,29 +37,51 @@ using namespace blender::ui;
|
||||
*/
|
||||
struct ViewLink : public Link {
|
||||
using TreeViewPtr = std::unique_ptr<AbstractTreeView>;
|
||||
using GridViewPtr = std::unique_ptr<AbstractGridView>;
|
||||
|
||||
std::string idname;
|
||||
/* NOTE: Can't use std::get() on this until minimum macOS deployment target is 10.14. */
|
||||
std::variant<TreeViewPtr> view;
|
||||
std::variant<TreeViewPtr, GridViewPtr> view;
|
||||
};
|
||||
|
||||
template<class T> constexpr void check_if_valid_view_type()
|
||||
{
|
||||
static_assert(std::is_same_v<T, AbstractTreeView> || std::is_same_v<T, AbstractGridView>,
|
||||
"Unsupported view type");
|
||||
}
|
||||
|
||||
template<class T> T *get_view_from_link(ViewLink &link)
|
||||
{
|
||||
auto *t_uptr = std::get_if<std::unique_ptr<T>>(&link.view);
|
||||
return t_uptr ? t_uptr->get() : nullptr;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static T *ui_block_add_view_impl(uiBlock &block, StringRef idname, std::unique_ptr<T> view)
|
||||
{
|
||||
check_if_valid_view_type<T>();
|
||||
|
||||
ViewLink *view_link = MEM_new<ViewLink>(__func__);
|
||||
BLI_addtail(&block.views, view_link);
|
||||
|
||||
view_link->view = std::move(view);
|
||||
view_link->idname = idname;
|
||||
|
||||
return get_view_from_link<T>(*view_link);
|
||||
}
|
||||
|
||||
AbstractGridView *UI_block_add_view(uiBlock &block,
|
||||
StringRef idname,
|
||||
std::unique_ptr<AbstractGridView> tree_view)
|
||||
{
|
||||
return ui_block_add_view_impl<AbstractGridView>(block, idname, std::move(tree_view));
|
||||
}
|
||||
|
||||
AbstractTreeView *UI_block_add_view(uiBlock &block,
|
||||
StringRef idname,
|
||||
std::unique_ptr<AbstractTreeView> tree_view)
|
||||
{
|
||||
ViewLink *view_link = MEM_new<ViewLink>(__func__);
|
||||
BLI_addtail(&block.views, view_link);
|
||||
|
||||
view_link->view = std::move(tree_view);
|
||||
view_link->idname = idname;
|
||||
|
||||
return get_view_from_link<AbstractTreeView>(*view_link);
|
||||
return ui_block_add_view_impl<AbstractTreeView>(block, idname, std::move(tree_view));
|
||||
}
|
||||
|
||||
void ui_block_free_views(uiBlock *block)
|
||||
@@ -62,6 +91,26 @@ void ui_block_free_views(uiBlock *block)
|
||||
}
|
||||
}
|
||||
|
||||
void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params)
|
||||
{
|
||||
ARegion *region = listener_params->region;
|
||||
|
||||
LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
|
||||
if (AbstractGridView *grid_view = get_view_from_link<AbstractGridView>(*view_link)) {
|
||||
if (UI_grid_view_listen_should_redraw(reinterpret_cast<uiGridViewHandle *>(grid_view),
|
||||
listener_params->notifier)) {
|
||||
ED_region_tag_redraw(region);
|
||||
}
|
||||
}
|
||||
else if (AbstractTreeView *tree_view = get_view_from_link<AbstractTreeView>(*view_link)) {
|
||||
if (UI_tree_view_listen_should_redraw(reinterpret_cast<uiTreeViewHandle *>(tree_view),
|
||||
listener_params->notifier)) {
|
||||
ED_region_tag_redraw(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region, const int xy[2])
|
||||
{
|
||||
uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, xy);
|
||||
@@ -82,11 +131,13 @@ uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const ARegion *region)
|
||||
return tree_row_but->tree_item;
|
||||
}
|
||||
|
||||
static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractTreeView &view)
|
||||
template<class T> static StringRef ui_block_view_find_idname(const uiBlock &block, const T &view)
|
||||
{
|
||||
check_if_valid_view_type<T>();
|
||||
|
||||
/* First get the idname the of the view we're looking for. */
|
||||
LISTBASE_FOREACH (ViewLink *, view_link, &block.views) {
|
||||
if (get_view_from_link<AbstractTreeView>(*view_link) == &view) {
|
||||
if (get_view_from_link<T>(*view_link) == &view) {
|
||||
return view_link->idname;
|
||||
}
|
||||
}
|
||||
@@ -94,9 +145,11 @@ static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractT
|
||||
return {};
|
||||
}
|
||||
|
||||
static AbstractTreeView *ui_block_view_find_matching_in_old_block(const uiBlock &new_block,
|
||||
const AbstractTreeView &new_view)
|
||||
template<class T>
|
||||
static T *ui_block_view_find_matching_in_old_block(const uiBlock &new_block, const T &new_view)
|
||||
{
|
||||
check_if_valid_view_type<T>();
|
||||
|
||||
uiBlock *old_block = new_block.oldblock;
|
||||
if (!old_block) {
|
||||
return nullptr;
|
||||
@@ -109,15 +162,15 @@ static AbstractTreeView *ui_block_view_find_matching_in_old_block(const uiBlock
|
||||
|
||||
LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) {
|
||||
if (old_view_link->idname == idname) {
|
||||
return get_view_from_link<AbstractTreeView>(*old_view_link);
|
||||
return get_view_from_link<T>(*old_view_link);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
|
||||
const uiTreeViewHandle *new_view_handle)
|
||||
uiTreeViewHandle *ui_block_tree_view_find_matching_in_old_block(
|
||||
const uiBlock *new_block, const uiTreeViewHandle *new_view_handle)
|
||||
{
|
||||
BLI_assert(new_block && new_view_handle);
|
||||
const AbstractTreeView &new_view = reinterpret_cast<const AbstractTreeView &>(*new_view_handle);
|
||||
@@ -126,6 +179,16 @@ uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_bl
|
||||
return reinterpret_cast<uiTreeViewHandle *>(old_view);
|
||||
}
|
||||
|
||||
uiGridViewHandle *ui_block_grid_view_find_matching_in_old_block(
|
||||
const uiBlock *new_block, const uiGridViewHandle *new_view_handle)
|
||||
{
|
||||
BLI_assert(new_block && new_view_handle);
|
||||
const AbstractGridView &new_view = reinterpret_cast<const AbstractGridView &>(*new_view_handle);
|
||||
|
||||
AbstractGridView *old_view = ui_block_view_find_matching_in_old_block(*new_block, new_view);
|
||||
return reinterpret_cast<uiGridViewHandle *>(old_view);
|
||||
}
|
||||
|
||||
uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
|
||||
const uiTreeViewItemHandle *new_item_handle)
|
||||
{
|
||||
|
||||
@@ -105,6 +105,7 @@ typedef enum {
|
||||
UI_WTYPE_PROGRESSBAR,
|
||||
UI_WTYPE_NODESOCKET,
|
||||
UI_WTYPE_TREEROW,
|
||||
UI_WTYPE_GRID_TILE,
|
||||
} uiWidgetTypeEnum;
|
||||
|
||||
/**
|
||||
@@ -3706,6 +3707,16 @@ static void widget_treerow(uiBut *but,
|
||||
widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation, zoom);
|
||||
}
|
||||
|
||||
static void widget_gridtile(uiWidgetColors *wcol,
|
||||
rcti *rect,
|
||||
const uiWidgetStateInfo *state,
|
||||
int roundboxalign,
|
||||
const float zoom)
|
||||
{
|
||||
/* TODO Reuse tree-row drawing. */
|
||||
widget_treerow_exec(wcol, rect, state, roundboxalign, 0, zoom);
|
||||
}
|
||||
|
||||
static void widget_nodesocket(uiBut *but,
|
||||
uiWidgetColors *wcol,
|
||||
rcti *rect,
|
||||
@@ -4598,9 +4609,15 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type)
|
||||
break;
|
||||
|
||||
case UI_WTYPE_TREEROW:
|
||||
wt.wcol_theme = &btheme->tui.wcol_view_item;
|
||||
wt.custom = widget_treerow;
|
||||
break;
|
||||
|
||||
case UI_WTYPE_GRID_TILE:
|
||||
wt.wcol_theme = &btheme->tui.wcol_view_item;
|
||||
wt.draw = widget_gridtile;
|
||||
break;
|
||||
|
||||
case UI_WTYPE_NODESOCKET:
|
||||
wt.custom = widget_nodesocket;
|
||||
break;
|
||||
@@ -4937,6 +4954,11 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
|
||||
fstyle = &style->widgetlabel;
|
||||
break;
|
||||
|
||||
case UI_BTYPE_GRID_TILE:
|
||||
wt = widget_type(UI_WTYPE_GRID_TILE);
|
||||
fstyle = &style->widgetlabel;
|
||||
break;
|
||||
|
||||
case UI_BTYPE_SCROLL:
|
||||
wt = widget_type(UI_WTYPE_SCROLL);
|
||||
break;
|
||||
|
||||
@@ -68,6 +68,12 @@ void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) con
|
||||
foreach_item_recursive(iter_fn, options);
|
||||
}
|
||||
|
||||
bool AbstractTreeView::listen(const wmNotifier &) const
|
||||
{
|
||||
/* Nothing by default. */
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AbstractTreeView::is_renaming() const
|
||||
{
|
||||
return rename_buffer_ != nullptr;
|
||||
@@ -82,7 +88,7 @@ void AbstractTreeView::update_from_old(uiBlock &new_block)
|
||||
return;
|
||||
}
|
||||
|
||||
uiTreeViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block(
|
||||
uiTreeViewHandle *old_view_handle = ui_block_tree_view_find_matching_in_old_block(
|
||||
&new_block, reinterpret_cast<uiTreeViewHandle *>(this));
|
||||
if (old_view_handle == nullptr) {
|
||||
is_reconstructed_ = true;
|
||||
@@ -805,6 +811,13 @@ class TreeViewItemAPIWrapper {
|
||||
|
||||
using namespace blender::ui;
|
||||
|
||||
bool UI_tree_view_listen_should_redraw(const uiTreeViewHandle *view_handle,
|
||||
const wmNotifier *notifier)
|
||||
{
|
||||
const AbstractTreeView &view = *reinterpret_cast<const AbstractTreeView *>(view_handle);
|
||||
return view.listen(*notifier);
|
||||
}
|
||||
|
||||
bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle)
|
||||
{
|
||||
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle);
|
||||
|
||||
@@ -145,6 +145,10 @@ void ED_region_do_listen(wmRegionListenerParams *params)
|
||||
region->type->listener(params);
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
|
||||
UI_block_views_listen(block, params);
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (uiList *, list, ®ion->ui_lists) {
|
||||
if (list->type && list->type->listener) {
|
||||
list->type->listener(list, params);
|
||||
|
||||
@@ -983,6 +983,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;
|
||||
@@ -1009,6 +1010,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;
|
||||
@@ -1030,6 +1032,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;
|
||||
|
||||
@@ -91,6 +91,7 @@ set(SRC
|
||||
../include/ED_uvedit.h
|
||||
../include/ED_view3d.h
|
||||
../include/ED_view3d_offscreen.h
|
||||
../include/UI_grid_view.hh
|
||||
../include/UI_icons.h
|
||||
../include/UI_interface.h
|
||||
../include/UI_interface.hh
|
||||
|
||||
@@ -146,6 +146,7 @@ typedef struct ThemeUI {
|
||||
uiWidgetColors wcol_num, wcol_numslider, wcol_tab;
|
||||
uiWidgetColors wcol_menu, wcol_pulldown, wcol_menu_back, wcol_menu_item, wcol_tooltip;
|
||||
uiWidgetColors wcol_box, wcol_scroll, wcol_progress, wcol_list_item, wcol_pie_menu;
|
||||
uiWidgetColors wcol_view_item;
|
||||
|
||||
uiWidgetStateColors wcol_state;
|
||||
|
||||
|
||||
@@ -1531,6 +1531,11 @@ static void rna_def_userdef_theme_ui(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(prop, "List Item Colors", "");
|
||||
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
|
||||
|
||||
prop = RNA_def_property(srna, "wcol_view_item", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_NEVER_NULL);
|
||||
RNA_def_property_ui_text(prop, "Data-View Item Colors", "");
|
||||
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
|
||||
|
||||
prop = RNA_def_property(srna, "wcol_state", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_NEVER_NULL);
|
||||
RNA_def_property_ui_text(prop, "State Colors", "");
|
||||
|
||||
Reference in New Issue
Block a user