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