Blender V4.5
grid_view.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cfloat>
10#include <cmath>
11#include <limits>
12#include <optional>
13#include <stdexcept>
14
15#include "BKE_context.hh"
16#include "BKE_icons.h"
17
18#include "BLI_index_range.hh"
19
20#include "WM_types.hh"
21
22#include "RNA_access.hh"
23
24#include "UI_interface.hh"
25#include "UI_view2d.hh"
26#include "interface_intern.hh"
27
28#include "UI_grid_view.hh"
29
30namespace blender::ui {
31
32/* ---------------------------------------------------------------------- */
33
37
38AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr<AbstractGridViewItem> item)
39{
40 items_.append(std::move(item));
41
42 AbstractGridViewItem &added_item = *items_.last();
43 item_map_.add(added_item.identifier_, &added_item);
44 this->register_item(added_item);
45
46 return added_item;
47}
48
49void AbstractGridView::foreach_view_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const
50{
51 /* Implementation for the base class virtual function. More specialized iterators below. */
52
53 for (const auto &item_ptr : items_) {
54 iter_fn(*item_ptr);
55 }
56}
57
59{
60 for (const auto &item_ptr : items_) {
61 iter_fn(*item_ptr);
62 }
63}
64
66{
67 for (const auto &item_ptr : items_) {
68 if (item_ptr->is_filtered_visible()) {
69 iter_fn(*item_ptr);
70 }
71 }
72}
73
74AbstractGridViewItem *AbstractGridView::find_matching_item(
75 const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const
76{
77 AbstractGridViewItem *const *match = view_to_search_in.item_map_.lookup_ptr(
78 item_to_match.identifier_);
79 BLI_assert(!match || item_to_match.matches(**match));
80
81 return match ? *match : nullptr;
82}
83
84void AbstractGridView::update_children_from_old(const AbstractView &old_view)
85{
86 const AbstractGridView &old_grid_view = dynamic_cast<const AbstractGridView &>(old_view);
87
88 this->foreach_item([this, &old_grid_view](AbstractGridViewItem &new_item) {
89 const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_grid_view);
90 if (!matching_old_item) {
91 return;
92 }
93
94 new_item.update_from_old(*matching_old_item);
95 });
96}
97
99{
100 return style_;
101}
102
104{
105 return items_.size();
106}
107
109{
111 return *item_count_filtered_;
112 }
113
114 int i = 0;
115 this->foreach_filtered_item([&i](const auto &) { i++; });
116
119 return i;
120}
121
122void AbstractGridView::set_tile_size(int tile_width, int tile_height)
123{
124 style_.tile_width = tile_width;
125 style_.tile_height = tile_height;
126}
127
128GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height) {}
129
130/* ---------------------------------------------------------------------- */
131
133
135{
136 const AbstractGridViewItem &other_grid_item = dynamic_cast<const AbstractGridViewItem &>(other);
137 return identifier_ == other_grid_item.identifier_;
138}
139
140void AbstractGridViewItem::grid_tile_click_fn(bContext *C, void *but_arg1, void * /*arg2*/)
141{
142 uiButViewItem *view_item_but = (uiButViewItem *)but_arg1;
143 AbstractGridViewItem &grid_item = reinterpret_cast<AbstractGridViewItem &>(
144 *view_item_but->view_item);
145
146 grid_item.activate(*C);
147}
148
149void AbstractGridViewItem::add_grid_tile_button(uiBlock &block)
150{
151 const GridViewStyle &style = this->get_view().get_style();
154 0,
155 "",
156 0,
157 0,
158 style.tile_width,
159 style.tile_height,
160 nullptr,
161 0,
162 0,
163 "");
164
166 UI_but_func_set(view_item_but_, grid_tile_click_fn, view_item_but_, nullptr);
167}
168
169std::optional<std::string> AbstractGridViewItem::debug_name() const
170{
171 return identifier_;
172}
173
175{
176 if (UNLIKELY(!view_)) {
177 throw std::runtime_error(
178 "Invalid state, item must be added through AbstractGridView::add_item()");
179 }
180 return dynamic_cast<AbstractGridView &>(*view_);
181}
182
183/* ---------------------------------------------------------------------- */
184
185std::unique_ptr<DropTargetInterface> AbstractGridViewItem::create_item_drop_target()
186{
187 return create_drop_target();
188}
189
190std::unique_ptr<GridViewItemDropTarget> AbstractGridViewItem::create_drop_target()
191{
192 return nullptr;
193}
194
196
197/* ---------------------------------------------------------------------- */
198
216 const AbstractGridView &grid_view_;
217 const GridViewStyle &style_;
218 const int cols_per_row_ = 0;
219 /* Indices of items within the view. Calculated by constructor. If this is unset it means all
220 * items/buttons should be drawn. */
221 std::optional<IndexRange> visible_items_range_;
222
223 public:
225 const AbstractGridView &grid_view,
226 int cols_per_row,
227 const AbstractGridViewItem *force_visible_item);
228
229 bool is_item_visible(int item_idx) const;
230 void fill_layout_before_visible(uiBlock &block) const;
231 void fill_layout_after_visible(uiBlock &block) const;
232
233 private:
234 IndexRange get_visible_range(const View2D &v2d,
235 const AbstractGridViewItem *force_visible_item) const;
236 void add_spacer_button(uiBlock &block, int row_count) const;
237};
238
240 const View2D &v2d,
241 const AbstractGridView &grid_view,
242 const int cols_per_row,
243 const AbstractGridViewItem *force_visible_item)
244 : grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row)
245{
246 if (v2d.flag & V2D_IS_INIT && grid_view.get_item_count_filtered()) {
247 visible_items_range_ = this->get_visible_range(v2d, force_visible_item);
248 }
249}
250
251static std::optional<int> find_filtered_item_index(const AbstractGridViewItem &item)
252{
254
255 const AbstractGridView &view = item.get_view();
256 std::optional<int> index;
257
258 int i = 0;
259 view.foreach_filtered_item([&](AbstractGridViewItem &iter_item) {
260 if (&item == &iter_item) {
261 index = i;
262 }
263 i++;
264 });
265
266 return index;
267}
268
269IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range(
270 const View2D &v2d, const AbstractGridViewItem *force_visible_item) const
271{
273
274 int first_idx_in_view = 0;
275
276 const float scroll_ofs_y = std::abs(v2d.cur.ymax - v2d.tot.ymax);
277 if (!IS_EQF(scroll_ofs_y, 0)) {
278 const int scrolled_away_rows = int(scroll_ofs_y) / style_.tile_height;
279
280 first_idx_in_view = scrolled_away_rows * cols_per_row_;
281 }
282
283 const int view_height = BLI_rcti_size_y(&v2d.mask);
284 const int count_rows_in_view = std::max(view_height / style_.tile_height, 1);
285 const int max_items_in_view = (count_rows_in_view + 1) * cols_per_row_;
286 BLI_assert(max_items_in_view > 0);
287
288 IndexRange visible_items(first_idx_in_view, max_items_in_view);
289
290 /* Ensure #visible_items contains #force_visible_item, adjust if necessary. */
291 if (force_visible_item && force_visible_item->is_filtered_visible()) {
292 if (std::optional<int> item_idx = find_filtered_item_index(*force_visible_item)) {
293 if (!visible_items.contains(*item_idx)) {
294 /* Move range so the first row contains #force_visible_item. */
295 return IndexRange((item_idx == 0) ? 0 : *item_idx % cols_per_row_, max_items_in_view);
296 }
297 }
298 }
299
300 return visible_items;
301}
302
304{
305 return !visible_items_range_ || visible_items_range_->contains(item_idx);
306}
307
309{
310 if (!visible_items_range_ || visible_items_range_->is_empty()) {
311 return;
312 }
313 const int first_idx_in_view = visible_items_range_->first();
314 if (first_idx_in_view < 1) {
315 return;
316 }
317 const int tot_tiles_before_visible = first_idx_in_view;
318 const int scrolled_away_rows = tot_tiles_before_visible / cols_per_row_;
319 this->add_spacer_button(block, scrolled_away_rows);
320}
321
323{
324 if (!visible_items_range_ || visible_items_range_->is_empty()) {
325 return;
326 }
327 const int last_item_idx = grid_view_.get_item_count_filtered() - 1;
328 const int last_visible_idx = visible_items_range_->last();
329
330 if (last_item_idx > last_visible_idx) {
331 const int remaining_rows = (cols_per_row_ > 0) ? ceilf((last_item_idx - last_visible_idx) /
332 float(cols_per_row_)) :
333 0;
334 BuildOnlyVisibleButtonsHelper::add_spacer_button(block, remaining_rows);
335 }
336}
337
338void BuildOnlyVisibleButtonsHelper::add_spacer_button(uiBlock &block, const int row_count) const
339{
340 /* UI code only supports button dimensions of `signed short` size, the layout height we want to
341 * fill may be bigger than that. So add multiple labels of the maximum size if necessary. */
342 for (int remaining_rows = row_count; remaining_rows > 0;) {
343 const short row_count_this_iter = std::min(
344 std::numeric_limits<short>::max() / style_.tile_height, remaining_rows);
345
346 uiDefBut(&block,
348 0,
349 "",
350 0,
351 0,
352 UI_UNIT_X,
353 row_count_this_iter * style_.tile_height,
354 nullptr,
355 0,
356 0,
357 "");
358 remaining_rows -= row_count_this_iter;
359 }
360}
361
362/* ---------------------------------------------------------------------- */
363
365 uiBlock &block_;
366
367 friend class GridViewBuilder;
368
369 public:
371
372 void build_from_view(const bContext &C,
373 const AbstractGridView &grid_view,
374 const View2D &v2d) const;
375
376 private:
377 void build_grid_tile(const bContext &C, uiLayout &grid_layout, AbstractGridViewItem &item) const;
378
379 uiLayout *current_layout() const;
380};
381
385
386void GridViewLayoutBuilder::build_grid_tile(const bContext &C,
387 uiLayout &grid_layout,
388 AbstractGridViewItem &item) const
389{
390 uiLayout *overlap = &grid_layout.overlap();
391 uiLayoutSetFixedSize(overlap, true);
392
393 item.add_grid_tile_button(block_);
394 item.build_grid_tile(C, overlap->row(false));
395}
396
398 const AbstractGridView &grid_view,
399 const View2D &v2d) const
400{
401 uiLayout *parent_layout = this->current_layout();
402
403 uiLayout &layout = parent_layout->column(true);
404 const GridViewStyle &style = grid_view.get_style();
405
406 /* We might not actually know the width available for the grid view. Let's just assume that
407 * either there is a fixed width defined via #uiLayoutSetUnitsX() or that the layout is close to
408 * the root level and inherits its width. Might need a more reliable method. */
409 const int guessed_layout_width = (uiLayoutGetUnitsX(parent_layout) > 0) ?
410 uiLayoutGetUnitsX(parent_layout) * UI_UNIT_X :
411 uiLayoutGetWidth(parent_layout);
412 const int cols_per_row = std::max(guessed_layout_width / style.tile_width, 1);
413
414 const AbstractGridViewItem *search_highlight_item = dynamic_cast<const AbstractGridViewItem *>(
415 grid_view.search_highlight_item());
416
417 BuildOnlyVisibleButtonsHelper build_visible_helper(
418 v2d, grid_view, cols_per_row, search_highlight_item);
419
420 build_visible_helper.fill_layout_before_visible(block_);
421
422 int item_idx = 0;
423 uiLayout *row = nullptr;
424 grid_view.foreach_filtered_item([&](AbstractGridViewItem &item) {
425 /* Skip if item isn't visible. */
426 if (!build_visible_helper.is_item_visible(item_idx)) {
427 item_idx++;
428 return;
429 }
430
431 /* Start a new row for every first item in the row. */
432 if ((item_idx % cols_per_row) == 0) {
433 row = &layout.row(true);
434 }
435
436 this->build_grid_tile(C, *row, item);
437 item_idx++;
438 });
439
440 UI_block_layout_set_current(&block_, parent_layout);
441
442 build_visible_helper.fill_layout_after_visible(block_);
443}
444
445uiLayout *GridViewLayoutBuilder::current_layout() const
446{
447 return block_.curlayout;
448}
449
450/* ---------------------------------------------------------------------- */
451
453
455 AbstractGridView &grid_view,
456 uiLayout &layout,
457 std::optional<StringRef> search_string)
458{
459 uiBlock &block = *uiLayoutGetBlock(&layout);
460
462 ui_block_view_persistent_state_restore(*region, block, grid_view);
463
464 grid_view.build_items();
465 grid_view.update_from_old(block);
466 grid_view.change_state_delayed();
467 grid_view.filter(search_string);
468
469 /* Ensure the given layout is actually active. */
470 UI_block_layout_set_current(&block, &layout);
471
472 GridViewLayoutBuilder builder(layout);
473 builder.build_from_view(C, grid_view, region->v2d);
474}
475
476/* ---------------------------------------------------------------------- */
477
482
484 BIFIconID override_preview_icon_id) const
485{
486 const GridViewStyle &style = this->get_view().get_style();
487 uiBlock *block = uiLayoutGetBlock(&layout);
488
490 [this](const uiBut * /*but*/) { return label; });
491
492 uiBut *but = uiDefBut(block,
494 0,
495 hide_label_ ? "" : label,
496 0,
497 0,
498 style.tile_width,
499 style.tile_height,
500 nullptr,
501 0,
502 0,
503 "");
504
505 const BIFIconID icon_id = override_preview_icon_id ? override_preview_icon_id : preview_icon_id;
506
507 ui_def_but_icon(but,
508 icon_id,
509 /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */
511 but->emboss = blender::ui::EmbossType::None;
512}
513
514void PreviewGridItem::build_grid_tile(const bContext & /*C*/, uiLayout &layout) const
515{
516 this->build_grid_tile_button(layout);
517}
518
523
528
530{
531 hide_label_ = true;
532}
533
534void PreviewGridItem::on_activate(bContext &C)
535{
536 if (activate_fn_) {
537 activate_fn_(C, *this);
538 }
539}
540
541std::optional<bool> PreviewGridItem::should_be_active() const
542{
543 if (is_active_fn_) {
544 return is_active_fn_();
545 }
546 return std::nullopt;
547}
548
549} // namespace blender::ui
ARegion * CTX_wm_region_popup(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
#define BLI_assert(a)
Definition BLI_assert.h:46
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:198
#define UNLIKELY(x)
#define IS_EQF(a, b)
@ V2D_IS_INIT
int BIFIconID
Definition ED_asset.hh:29
static AppView * view
#define C
Definition RandGen.cpp:29
void UI_but_func_set(uiBut *but, std::function< void(bContext &)> func)
uiBut * uiDefBut(uiBlock *block, int type, int retval, blender::StringRef str, int x, int y, short width, short height, void *poin, float min, float max, std::optional< blender::StringRef > tip)
int UI_preview_tile_size_y(const int size_px=96)
int UI_preview_tile_size_x(const int size_px=96)
#define UI_UNIT_X
@ UI_BTYPE_VIEW_ITEM
@ UI_BTYPE_PREVIEW_TILE
@ UI_BTYPE_LABEL
@ UI_BUT_ICON_PREVIEW
void UI_but_func_quick_tooltip_set(uiBut *but, std::function< std::string(const uiBut *but)> func)
void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size)
uiBlock * uiLayoutGetBlock(uiLayout *layout)
float uiLayoutGetUnitsX(uiLayout *layout)
int uiLayoutGetWidth(uiLayout *layout)
void UI_block_layout_set_current(uiBlock *block, uiLayout *layout)
virtual void build_grid_tile(const bContext &C, uiLayout &layout) const =0
std::optional< std::string > debug_name() const override
Definition grid_view.cc:169
AbstractGridView & get_view() const
Definition grid_view.cc:174
virtual std::unique_ptr< GridViewItemDropTarget > create_drop_target()
Definition grid_view.cc:190
AbstractGridViewItem(StringRef identifier)
Definition grid_view.cc:132
std::unique_ptr< DropTargetInterface > create_item_drop_target() final
Definition grid_view.cc:185
bool matches(const AbstractViewItem &other) const override
Definition grid_view.cc:134
Map< StringRef, AbstractGridViewItem * > item_map_
ItemT & add_item(Args &&...args)
void set_tile_size(int tile_width, int tile_height)
Definition grid_view.cc:122
void foreach_filtered_item(ItemIterFn iter_fn) const
Definition grid_view.cc:65
FunctionRef< void(AbstractGridViewItem &)> ItemIterFn
void foreach_item(ItemIterFn iter_fn) const
Definition grid_view.cc:58
std::optional< int > item_count_filtered_
const GridViewStyle & get_style() const
Definition grid_view.cc:98
virtual void build_items()=0
Vector< std::unique_ptr< AbstractGridViewItem > > items_
uiButViewItem * view_item_button() const
virtual std::optional< bool > should_be_active() const
const AbstractViewItem * search_highlight_item() const
void update_from_old(uiBlock &new_block)
virtual void change_state_delayed()
void register_item(AbstractViewItem &item)
void filter(std::optional< StringRef > filter_str)
bool is_item_visible(int item_idx) const
Definition grid_view.cc:303
void fill_layout_after_visible(uiBlock &block) const
Definition grid_view.cc:322
void fill_layout_before_visible(uiBlock &block) const
Definition grid_view.cc:308
BuildOnlyVisibleButtonsHelper(const View2D &v2d, const AbstractGridView &grid_view, int cols_per_row, const AbstractGridViewItem *force_visible_item)
Definition grid_view.cc:239
void build_grid_view(const bContext &C, AbstractGridView &grid_view, uiLayout &layout, std::optional< StringRef > search_string={})
Definition grid_view.cc:454
GridViewBuilder(uiBlock &block)
Definition grid_view.cc:452
GridViewItemDropTarget(AbstractGridView &view)
Definition grid_view.cc:195
GridViewLayoutBuilder(uiLayout &layout)
Definition grid_view.cc:382
void build_from_view(const bContext &C, const AbstractGridView &grid_view, const View2D &v2d) const
Definition grid_view.cc:397
PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id)
Definition grid_view.cc:478
void set_is_active_fn(IsActiveFn fn)
Definition grid_view.cc:524
void build_grid_tile_button(uiLayout &layout, BIFIconID override_preview_icon_id=ICON_NONE) const
Definition grid_view.cc:483
std::function< void(bContext &C, PreviewGridItem &new_active)> ActivateFn
void set_on_activate_fn(ActivateFn fn)
Definition grid_view.cc:519
std::function< bool()> IsActiveFn
void build_grid_tile(const bContext &C, uiLayout &layout) const override
Definition grid_view.cc:514
#define ceilf(x)
void ui_def_but_icon(uiBut *but, const int icon, const int flag)
@ UI_HAS_ICON
void ui_block_view_persistent_state_restore(const ARegion &region, const uiBlock &block, blender::ui::AbstractView &view)
static std::optional< int > find_filtered_item_index(const AbstractGridViewItem &item)
Definition grid_view.cc:251
GridViewStyle(int width, int height)
Definition grid_view.cc:128
float ymax
uiLayout * curlayout
blender::ui::AbstractViewItem * view_item
uiLayout & column(bool align)
uiLayout & row(bool align)
uiLayout & overlap()
i
Definition text_draw.cc:230