UI: Highlight first view item on type to search & activate on enter

In particular, this makes the asset shelf popup search highlight the
first asset when changing the search filter using text input. Pressing
Enter will activate this asset then. The feature is implemented
generally for grid and tree views, but only the asset shelf implements
filtering so far. Plus, it requires the
`UI_BUT2_FORCE_SEMI_MODAL_ACTIVE` behavior on the filter text button,
otherwise it captures all input. Only the popup version of the asset
shelf uses this currently. Moving the mouse makes the highlight jump
back to the brush under the cursor again. This is how search menus
behave too.

Part of the brush assets project, see blender/blender!123853. It's made
so it's possible to quickly spawn the brush asset shelf popup, input
text to search a brush and press Enter to activate it. Based on user
feedback this is an important workflow to support well.

More info about the changes in the pull request.

Pull Request: https://projects.blender.org/blender/blender/pulls/123853
This commit is contained in:
Julian Eisel
2024-07-01 20:21:25 +02:00
parent 0363650990
commit 4a9e8087a7
14 changed files with 297 additions and 95 deletions

View File

@@ -40,9 +40,6 @@ class AssetView : public ui::AbstractGridView {
const AssetLibraryReference library_ref_;
const AssetShelf &shelf_;
std::optional<AssetWeakReference> active_asset_;
/** Copy of the filter string from #AssetShelfSettings, with extra '*' added to the beginning and
* end of the string, for `fnmatch()` to work. */
char search_string[sizeof(AssetShelfSettings::search_string) + 2] = "";
std::optional<asset_system::AssetCatalogFilter> catalog_filter_ = std::nullopt;
bool is_popup_ = false;
@@ -73,7 +70,7 @@ class AssetViewItem : public ui::PreviewGridItem {
void build_context_menu(bContext &C, uiLayout &column) const override;
void on_activate(bContext &C) override;
std::optional<bool> should_be_active() const override;
bool is_filtered_visible() const override;
bool should_be_filtered_visible(StringRefNull filter_string) const override;
std::unique_ptr<ui::AbstractViewItemDragController> create_drag_controller() const override;
};
@@ -93,10 +90,6 @@ AssetView::AssetView(const AssetLibraryReference &library_ref,
const bool is_popup)
: library_ref_(library_ref), shelf_(shelf), is_popup_(is_popup)
{
if (shelf.settings.search_string[0]) {
BLI_strncpy_ensure_pad(
search_string, shelf.settings.search_string, '*', sizeof(search_string));
}
if (shelf.type->get_active_asset) {
if (const AssetWeakReference *weak_ref = shelf.type->get_active_asset(shelf.type)) {
active_asset_ = *weak_ref;
@@ -261,15 +254,10 @@ std::optional<bool> AssetViewItem::should_be_active() const
return matches;
}
bool AssetViewItem::is_filtered_visible() const
bool AssetViewItem::should_be_filtered_visible(const StringRefNull filter_str) const
{
const AssetView &asset_view = dynamic_cast<const AssetView &>(this->get_view());
if (asset_view.search_string[0] == '\0') {
return true;
}
const StringRefNull asset_name = handle_get_representation(&asset_)->get_name();
return fnmatch(asset_view.search_string, asset_name.c_str(), FNM_CASEFOLD) == 0;
return fnmatch(filter_str.c_str(), asset_name.c_str(), FNM_CASEFOLD) == 0;
}
std::unique_ptr<ui::AbstractViewItemDragController> AssetViewItem::create_drag_controller() const
@@ -283,6 +271,15 @@ std::unique_ptr<ui::AbstractViewItemDragController> AssetViewItem::create_drag_c
/* ---------------------------------------------------------------------- */
static std::string filter_string_get(const AssetShelf &shelf)
{
/* Copy of the filter string from #AssetShelfSettings, with extra '*' added to the beginning and
* end of the string, for `fnmatch()` to work. */
char search_string[sizeof(AssetShelfSettings::search_string) + 2];
BLI_strncpy_ensure_pad(search_string, shelf.settings.search_string, '*', sizeof(search_string));
return search_string;
}
void build_asset_view(uiLayout &layout,
const AssetLibraryReference &library_ref,
const AssetShelf &shelf,
@@ -312,7 +309,7 @@ void build_asset_view(uiLayout &layout,
*block, "asset shelf asset view", std::move(asset_view));
ui::GridViewBuilder builder(*block);
builder.build_grid_view(*grid_view, region.v2d, layout);
builder.build_grid_view(*grid_view, region.v2d, layout, filter_string_get(shelf));
}
/* ---------------------------------------------------------------------- */

View File

@@ -22,6 +22,7 @@
#include <array>
#include <memory>
#include <optional>
#include <string>
#include "DNA_defs.h"
#include "DNA_vec_types.h"
@@ -59,6 +60,10 @@ class AbstractView {
* may be able to bind the button to a `std::string` or similar.
*/
std::unique_ptr<std::array<char, MAX_NAME>> rename_buffer_;
/* Search/filter string from the previous redraw, stored to detect changes. */
std::string prev_filter_string_;
bool needs_filtering_ = true;
/* See #get_bounds(). */
std::optional<rcti> bounds_;
@@ -112,6 +117,8 @@ class AbstractView {
std::string get_context_menu_title() const;
void set_context_menu_title(const std::string &title);
void clear_search_highlight();
protected:
AbstractView() = default;
@@ -138,6 +145,9 @@ class AbstractView {
* #update_from_old() have finished.
*/
bool is_reconstructed() const;
void filter(std::optional<StringRef> str);
const AbstractViewItem *search_highlight_item() const;
};
class AbstractViewItem {
@@ -156,9 +166,11 @@ class AbstractViewItem {
bool is_interactive_ = true;
bool is_active_ = false;
bool is_renaming_ = false;
/** See #is_search_highlight(). */
bool is_highlighted_search_ = false;
/** Cache filtered state here to avoid having to re-query. */
mutable std::optional<bool> is_filtered_visible_;
bool is_filtered_visible_ = true;
public:
virtual ~AbstractViewItem() = default;
@@ -219,9 +231,7 @@ class AbstractViewItem {
*/
virtual std::optional<std::string> debug_name() const;
/** Return the result of #is_filtered_visible(), but ensure the result is cached so it's only
* queried once per redraw. */
bool is_filtered_visible_cached() const;
bool is_filtered_visible() const;
/** Get the view this item is registered for using #AbstractView::register_item(). */
AbstractView &get_view() const;
@@ -255,6 +265,11 @@ class AbstractViewItem {
* can't be sure about the item state.
*/
bool is_active() const;
/**
* Should this item be highlighted as matching search result? Only one item should be highlighted
* this way at a time. Pressing enter will activate it.
*/
bool is_search_highlight() const;
bool is_renaming() const;
void begin_renaming();
@@ -298,9 +313,9 @@ class AbstractViewItem {
/**
* \note Do not call this directly to avoid constantly rechecking the filter state. Instead use
* #is_filtered_visible_cached() for querying.
* #is_filtered_visible() for querying.
*/
virtual bool is_filtered_visible() const;
virtual bool should_be_filtered_visible(StringRefNull filter_string) const;
/**
* Add a text button for renaming the item to \a block. This must be used for the built-in

View File

@@ -11,6 +11,8 @@
#pragma once
#include <optional>
#include "BLI_function_ref.hh"
#include "BLI_map.hh"
#include "BLI_vector.hh"
@@ -170,9 +172,10 @@ class GridViewBuilder {
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, uiLayout &layout);
void build_grid_view(AbstractGridView &grid_view,
const View2D &v2d,
uiLayout &layout,
std::optional<StringRef> search_string = {});
};
/** \} */

View File

@@ -1368,6 +1368,11 @@ uiBut *uiDefIconTextButO_ptr(uiBlock *block,
short height,
const char *tip);
void UI_but_operator_set(uiBut *but,
wmOperatorType *optype,
wmOperatorCallContext opcontext,
const PointerRNA *opptr = nullptr);
/** For passing inputs to ButO buttons. */
PointerRNA *UI_but_operator_ptr_ensure(uiBut *but);
@@ -3399,3 +3404,4 @@ blender::ui::AbstractViewItem *UI_region_views_find_item_at(const ARegion &regio
const int xy[2]);
blender::ui::AbstractViewItem *UI_region_views_find_active_item(const ARegion *region);
uiBut *UI_region_views_find_active_item_but(const ARegion *region);
void UI_region_views_clear_search_highlight(const ARegion *region);

View File

@@ -392,7 +392,9 @@ class TreeViewItemDropTarget : public DropTargetInterface {
class TreeViewBuilder {
public:
static void build_tree_view(AbstractTreeView &tree_view, uiLayout &layout);
static void build_tree_view(AbstractTreeView &tree_view,
uiLayout &layout,
std::optional<StringRef> search_string = {});
private:
static void ensure_min_rows_items(AbstractTreeView &tree_view);

View File

@@ -4728,9 +4728,7 @@ static uiBut *ui_def_but_operator_ptr(uiBlock *block,
}
uiBut *but = ui_def_but(block, type, -1, str, x, y, width, height, nullptr, 0, 0, tip);
but->optype = ot;
but->opcontext = opcontext;
but->flag &= ~UI_BUT_UNDO; /* no need for ui_but_is_rna_undo(), we never need undo here */
UI_but_operator_set(but, ot, opcontext);
/* Enable quick tooltip label if this is a tool button without a label. */
if (str.is_empty() && !ui_block_is_popover(block) && UI_but_is_tool(but)) {
@@ -5618,6 +5616,21 @@ uiBut *uiDefIconTextButO(uiBlock *block,
return uiDefIconTextButO_ptr(block, type, ot, opcontext, icon, str, x, y, width, height, tip);
}
void UI_but_operator_set(uiBut *but,
wmOperatorType *optype,
wmOperatorCallContext opcontext,
const PointerRNA *op_props)
{
but->optype = optype;
but->opcontext = opcontext;
but->flag &= ~UI_BUT_UNDO; /* no need for ui_but_is_rna_undo(), we never need undo here */
MEM_SAFE_FREE(but->opptr);
if (op_props) {
but->opptr = MEM_new<PointerRNA>(__func__, *op_props);
}
}
/* END Button containing both string label and icon */
/* cruft to make uiBlock and uiBut private */

View File

@@ -9849,30 +9849,62 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
}
/* Handle mouse hover for Views and UiList rows. */
static int ui_handle_viewlist_items_hover(const wmEvent *event, const ARegion *region)
static int ui_handle_viewlist_items_hover(const wmEvent *event, ARegion *region)
{
bool has_item = false;
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
if (ELEM(but->type, UI_BTYPE_VIEW_ITEM, UI_BTYPE_LISTROW)) {
but->flag &= ~UI_HOVER;
has_item = true;
const bool has_list = !BLI_listbase_is_empty(&region->ui_lists);
const bool has_view = [&]() {
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
if (!BLI_listbase_is_empty(&block->views)) {
return true;
}
}
}
return false;
}();
if (!has_item) {
if (!has_view && !has_list) {
/* Avoid unnecessary lookup. */
return WM_UI_HANDLER_CONTINUE;
}
/* Always highlight the hovered view item, even if the mouse hovers another button inside. */
uiBut *hovered_row_but = ui_view_item_find_mouse_over(region, event->xy);
if (!hovered_row_but) {
hovered_row_but = ui_list_row_find_mouse_over(region, event->xy);
uiBut *highlight_row_but = [&]() -> uiBut * {
if (uiBut *but = ui_view_item_find_search_highlight(region)) {
return but;
}
if (uiBut *but = ui_view_item_find_mouse_over(region, event->xy)) {
return but;
}
if (uiBut *but = ui_list_row_find_mouse_over(region, event->xy)) {
return but;
}
return nullptr;
}();
bool changed = false;
if (highlight_row_but && !(highlight_row_but->flag & UI_HOVER)) {
highlight_row_but->flag |= UI_HOVER;
changed = true;
}
if (hovered_row_but) {
hovered_row_but->flag |= UI_HOVER;
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
if (but == highlight_row_but) {
continue;
}
if (!ELEM(but->type, UI_BTYPE_VIEW_ITEM, UI_BTYPE_LISTROW)) {
continue;
}
if (but->flag & UI_HOVER) {
but->flag &= ~UI_HOVER;
changed = true;
}
}
}
if (changed) {
ED_region_tag_redraw_no_rebuild(region);
}
return WM_UI_HANDLER_CONTINUE;
@@ -9883,16 +9915,36 @@ static int ui_handle_view_item_event(bContext *C,
uiBut *active_but,
ARegion *region)
{
if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS)) {
/* Only bother finding the active view item button if the active button isn't already a view
* item. */
uiBut *view_but = (active_but && active_but->type == UI_BTYPE_VIEW_ITEM) ?
active_but :
ui_view_item_find_mouse_over(region, event->xy);
/* Will free active button if there already is one. */
if (view_but) {
UI_but_execute(C, region, view_but);
}
switch (event->type) {
case MOUSEMOVE:
if (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1]) {
UI_region_views_clear_search_highlight(region);
}
break;
case LEFTMOUSE:
if (event->val == KM_PRESS) {
/* Only bother finding the active view item button if the active button isn't already a
* view item. */
uiBut *view_but = (active_but && active_but->type == UI_BTYPE_VIEW_ITEM) ?
active_but :
ui_view_item_find_mouse_over(region, event->xy);
/* Will free active button if there already is one. */
if (view_but) {
UI_but_execute(C, region, view_but);
}
}
break;
case EVT_RETKEY:
case EVT_PADENTER:
if (event->val == KM_PRESS) {
if (uiBut *search_highlight_but = ui_view_item_find_search_highlight(region)) {
UI_but_execute(C, region, search_highlight_but);
return WM_UI_HANDLER_BREAK;
}
}
break;
default:
break;
}
return WM_UI_HANDLER_CONTINUE;

View File

@@ -1443,6 +1443,7 @@ uiBut *ui_list_row_find_index(const ARegion *region,
uiBut *listbox) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_view_item_find_mouse_over(const ARegion *region, const int xy[2]) ATTR_NONNULL(1, 2);
uiBut *ui_view_item_find_active(const ARegion *region);
uiBut *ui_view_item_find_search_highlight(const ARegion *region);
using uiButFindPollFn = bool (*)(const uiBut *but, const void *customdata);
/**

View File

@@ -65,7 +65,7 @@ bool ui_but_is_toggle(const uiBut *but)
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. */
if (but->type == UI_BTYPE_LABEL) {
if (ELEM(but->type, UI_BTYPE_LABEL, UI_BTYPE_PREVIEW_TILE)) {
if (for_tooltip) {
/* It's important labels are considered interactive for the purpose of showing tooltip. */
if (!ui_but_drag_is_draggable(but) && but->tip_func == nullptr &&
@@ -511,6 +511,21 @@ uiBut *ui_view_item_find_active(const ARegion *region)
return ui_but_find(region, ui_but_is_active_view_item, nullptr);
}
uiBut *ui_view_item_find_search_highlight(const ARegion *region)
{
return ui_but_find(
region,
[](const uiBut *but, const void * /*find_custom_data*/) {
if (but->type != UI_BTYPE_VIEW_ITEM) {
return false;
}
const uiButViewItem *view_item_but = static_cast<const uiButViewItem *>(but);
return view_item_but->view_item->is_search_highlight();
},
nullptr);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -30,6 +30,18 @@ bool AbstractView::is_reconstructed() const
return is_reconstructed_;
}
const AbstractViewItem *AbstractView::search_highlight_item() const
{
const AbstractViewItem *found_item = nullptr;
this->foreach_view_item([&](const AbstractViewItem &item) {
if (!found_item && item.is_search_highlight()) {
found_item = &item;
}
});
return found_item;
}
void AbstractView::update_from_old(uiBlock &new_block)
{
uiBlock *old_block = new_block.oldblock;
@@ -46,6 +58,7 @@ void AbstractView::update_from_old(uiBlock &new_block)
}
/* Update own persistent data. */
prev_filter_string_ = old_view->prev_filter_string_;
/* Keep the rename buffer persistent while renaming! The rename button uses the buffer's
* pointer to identify itself over redraws. */
rename_buffer_ = std::move(old_view->rename_buffer_);
@@ -115,6 +128,39 @@ void AbstractView::draw_overlays(const ARegion & /*region*/) const
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Filtering
* \{ */
void AbstractView::filter(std::optional<StringRef> filter_str)
{
needs_filtering_ = false;
if (!filter_str) {
return;
}
const bool is_empty = filter_str->is_empty();
const bool filter_changed = filter_str != prev_filter_string_;
prev_filter_string_ = *filter_str;
bool has_search_highlight = false;
this->foreach_view_item([&](AbstractViewItem &item) {
item.is_filtered_visible_ = is_empty ||
item.should_be_filtered_visible(StringRefNull(*filter_str));
if (filter_changed) {
item.is_highlighted_search_ = false;
/* On new filtering input, force the first visible item to be highlighted and in view, so
* enter activates it. */
if (item.is_filtered_visible_ && !has_search_highlight) {
item.is_highlighted_search_ = true;
has_search_highlight = true;
}
}
});
}
/* ---------------------------------------------------------------------- */
/** \name Renaming
* \{ */
@@ -163,6 +209,11 @@ void AbstractView::set_context_menu_title(const std::string &title)
{
context_menu_title = title;
}
void AbstractView::clear_search_highlight()
{
this->foreach_view_item([](AbstractViewItem &item) { item.is_highlighted_search_ = false; });
}
/** \} */
} // namespace blender::ui

View File

@@ -29,6 +29,7 @@ void AbstractViewItem::update_from_old(const AbstractViewItem &old)
{
is_active_ = old.is_active_;
is_renaming_ = old.is_renaming_;
is_highlighted_search_ = old.is_highlighted_search_;
}
/** \} */
@@ -236,19 +237,15 @@ void AbstractViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*
/** \name Filtering
* \{ */
bool AbstractViewItem::is_filtered_visible() const
bool AbstractViewItem::should_be_filtered_visible(const StringRefNull /*filter_string*/) const
{
return true;
}
bool AbstractViewItem::is_filtered_visible_cached() const
bool AbstractViewItem::is_filtered_visible() const
{
if (is_filtered_visible_.has_value()) {
return *is_filtered_visible_;
}
is_filtered_visible_ = is_filtered_visible();
return *is_filtered_visible_;
BLI_assert(get_view().needs_filtering_ == false);
return is_filtered_visible_;
}
/** \} */
@@ -323,6 +320,11 @@ bool AbstractViewItem::is_active() const
return is_active_;
}
bool AbstractViewItem::is_search_highlight() const
{
return is_highlighted_search_;
}
/** \} */
} // namespace blender::ui

View File

@@ -21,6 +21,7 @@
#include "RNA_access.hh"
#include "UI_interface.hh"
#include "UI_view2d.hh"
#include "interface_intern.hh"
#include "UI_grid_view.hh"
@@ -62,7 +63,7 @@ void AbstractGridView::foreach_item(ItemIterFn iter_fn) const
void AbstractGridView::foreach_filtered_item(ItemIterFn iter_fn) const
{
for (const auto &item_ptr : items_) {
if (item_ptr->is_filtered_visible_cached()) {
if (item_ptr->is_filtered_visible()) {
iter_fn(*item_ptr);
}
}
@@ -220,28 +221,51 @@ class BuildOnlyVisibleButtonsHelper {
public:
BuildOnlyVisibleButtonsHelper(const View2D &v2d,
const AbstractGridView &grid_view,
int cols_per_row);
int cols_per_row,
const AbstractGridViewItem *force_visible_item);
bool is_item_visible(int item_idx) const;
void fill_layout_before_visible(uiBlock &block) const;
void fill_layout_after_visible(uiBlock &block) const;
private:
IndexRange get_visible_range(const View2D &v2d) const;
IndexRange get_visible_range(const View2D &v2d,
const AbstractGridViewItem *force_visible_item) const;
void add_spacer_button(uiBlock &block, int row_count) const;
};
BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d,
const AbstractGridView &grid_view,
const int cols_per_row)
BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(
const View2D &v2d,
const AbstractGridView &grid_view,
const int cols_per_row,
const AbstractGridViewItem *force_visible_item)
: grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row)
{
if ((v2d.flag & V2D_IS_INIT) && grid_view.get_item_count_filtered()) {
visible_items_range_ = this->get_visible_range(v2d);
visible_items_range_ = this->get_visible_range(v2d, force_visible_item);
}
}
IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range(const View2D &v2d) const
static std::optional<int> find_filtered_item_index(const AbstractGridViewItem &item)
{
BLI_assert(item.is_filtered_visible());
const AbstractGridView &view = item.get_view();
std::optional<int> index;
int i = 0;
view.foreach_filtered_item([&](AbstractGridViewItem &iter_item) {
if (&item == &iter_item) {
index = i;
}
i++;
});
return index;
}
IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range(
const View2D &v2d, const AbstractGridViewItem *force_visible_item) const
{
BLI_assert(v2d.flag & V2D_IS_INIT);
@@ -257,9 +281,21 @@ IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range(const View2D &v2d) c
const int view_height = BLI_rcti_size_y(&v2d.mask);
const int count_rows_in_view = std::max(view_height / style_.tile_height, 1);
const int 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);
IndexRange visible_items(first_idx_in_view, max_items_in_view);
/* Ensure #visible_items contains #force_visible_item, adjust if necessary. */
if (force_visible_item && force_visible_item->is_filtered_visible()) {
if (std::optional<int> item_idx = find_filtered_item_index(*force_visible_item)) {
if (!visible_items.contains(*item_idx)) {
/* Move range so the first row contains #force_visible_item. */
return IndexRange((item_idx == 0) ? 0 : *item_idx % cols_per_row_, max_items_in_view);
}
}
}
return visible_items;
}
bool BuildOnlyVisibleButtonsHelper::is_item_visible(const int item_idx) const
@@ -369,7 +405,11 @@ void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view,
uiLayoutGetWidth(parent_layout);
const int cols_per_row = std::max(guessed_layout_width / style.tile_width, 1);
BuildOnlyVisibleButtonsHelper build_visible_helper(v2d, grid_view, cols_per_row);
const AbstractGridViewItem *search_highlight_item = dynamic_cast<const AbstractGridViewItem *>(
grid_view.search_highlight_item());
BuildOnlyVisibleButtonsHelper build_visible_helper(
v2d, grid_view, cols_per_row, search_highlight_item);
build_visible_helper.fill_layout_before_visible(block_);
@@ -407,13 +447,15 @@ GridViewBuilder::GridViewBuilder(uiBlock & /*block*/) {}
void GridViewBuilder::build_grid_view(AbstractGridView &grid_view,
const View2D &v2d,
uiLayout &layout)
uiLayout &layout,
std::optional<StringRef> search_string)
{
uiBlock &block = *uiLayoutGetBlock(&layout);
grid_view.build_items();
grid_view.update_from_old(block);
grid_view.change_state_delayed();
grid_view.filter(search_string);
/* Ensure the given layout is actually active. */
UI_block_layout_set_current(&block, &layout);
@@ -436,34 +478,25 @@ void PreviewGridItem::build_grid_tile_button(uiLayout &layout,
const GridViewStyle &style = this->get_view().get_style();
uiBlock *block = uiLayoutGetBlock(&layout);
uiBut *but;
if (ot) {
but = uiDefButO_ptr(block,
UI_BTYPE_PREVIEW_TILE,
UI_but_operator_set(this->view_item_button(),
const_cast<wmOperatorType *>(ot),
WM_OP_INVOKE_REGION_WIN,
op_props);
}
uiBut *but = uiDefBut(block,
UI_BTYPE_PREVIEW_TILE,
0,
hide_label_ ? "" : label,
0,
0,
style.tile_width,
style.tile_height,
nullptr,
0,
0,
"");
but->opptr = MEM_new<PointerRNA>(__func__, *op_props);
}
else {
but = uiDefBut(block,
UI_BTYPE_PREVIEW_TILE,
0,
hide_label_ ? "" : label,
0,
0,
style.tile_width,
style.tile_height,
nullptr,
0,
0,
"");
}
/* Draw icons that are not previews or images as normal icons with a fixed icon size. Otherwise
* they will be upscaled to the button size. Should probably be done by the widget code. */

View File

@@ -206,6 +206,15 @@ uiBut *UI_region_views_find_active_item_but(const ARegion *region)
return ui_view_item_find_active(region);
}
void UI_region_views_clear_search_highlight(const ARegion *region)
{
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
view_link->view->clear_search_highlight();
}
}
}
namespace blender::ui {
std::unique_ptr<DropTargetInterface> region_views_find_drop_target_at(const ARegion *region,

View File

@@ -67,7 +67,7 @@ void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptio
{
for (const auto &child : children_) {
bool skip = false;
if (bool(options & IterOptions::SkipFiltered) && !child->is_filtered_visible_cached()) {
if (bool(options & IterOptions::SkipFiltered) && !child->is_filtered_visible()) {
skip = true;
}
@@ -725,13 +725,16 @@ void TreeViewBuilder::ensure_min_rows_items(AbstractTreeView &tree_view)
}
}
void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view, uiLayout &layout)
void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view,
uiLayout &layout,
std::optional<StringRef> search_string)
{
uiBlock &block = *uiLayoutGetBlock(&layout);
tree_view.build_tree();
tree_view.update_from_old(block);
tree_view.change_state_delayed();
tree_view.filter(search_string);
ensure_min_rows_items(tree_view);