Blender  V3.3
grid_view.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
7 #include <limits>
8 #include <stdexcept>
9 
10 #include "BLI_index_range.hh"
11 
12 #include "WM_types.h"
13 
14 #include "UI_interface.h"
15 #include "interface_intern.h"
16 
17 #include "UI_grid_view.hh"
18 
19 namespace blender::ui {
20 
21 /* ---------------------------------------------------------------------- */
22 
24 {
25 }
26 
27 AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr<AbstractGridViewItem> item)
28 {
29  items_.append(std::move(item));
30 
31  AbstractGridViewItem &added_item = *items_.last();
32  item_map_.add(added_item.identifier_, &added_item);
33  register_item(added_item);
34 
35  return added_item;
36 }
37 
39 {
40  for (const auto &item_ptr : items_) {
41  iter_fn(*item_ptr);
42  }
43 }
44 
45 AbstractGridViewItem *AbstractGridView::find_matching_item(
46  const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const
47 {
48  AbstractGridViewItem *const *match = view_to_search_in.item_map_.lookup_ptr(
49  item_to_match.identifier_);
50  BLI_assert(!match || item_to_match.matches(**match));
51 
52  return match ? *match : nullptr;
53 }
54 
55 void AbstractGridView::change_state_delayed()
56 {
59  "These state changes are supposed to be delayed until reconstruction is completed");
60  foreach_item([](AbstractGridViewItem &item) { item.change_state_delayed(); });
61 }
62 
63 void AbstractGridView::update_children_from_old(const AbstractView &old_view)
64 {
65  const AbstractGridView &old_grid_view = dynamic_cast<const AbstractGridView &>(old_view);
66 
67  foreach_item([this, &old_grid_view](AbstractGridViewItem &new_item) {
68  const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_grid_view);
69  if (!matching_old_item) {
70  return;
71  }
72 
73  new_item.update_from_old(*matching_old_item);
74  });
75 }
76 
78 {
79  return style_;
80 }
81 
83 {
84  return items_.size();
85 }
86 
87 GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height)
88 {
89 }
90 
91 /* ---------------------------------------------------------------------- */
92 
93 AbstractGridViewItem::AbstractGridViewItem(StringRef identifier) : identifier_(identifier)
94 {
95 }
96 
98 {
99  const AbstractGridViewItem &other_grid_item = dynamic_cast<const AbstractGridViewItem &>(other);
100  return identifier_ == other_grid_item.identifier_;
101 }
102 
103 void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/,
104  void *but_arg1,
105  void * /*arg2*/)
106 {
107  uiButViewItem *view_item_but = (uiButViewItem *)but_arg1;
108  AbstractGridViewItem &grid_item = reinterpret_cast<AbstractGridViewItem &>(
109  *view_item_but->view_item);
110 
111  grid_item.activate();
112 }
113 
114 void AbstractGridViewItem::add_grid_tile_button(uiBlock &block)
115 {
116  const GridViewStyle &style = get_view().get_style();
119  0,
120  "",
121  0,
122  0,
123  style.tile_width,
124  style.tile_height,
125  nullptr,
126  0,
127  0,
128  0,
129  0,
130  "");
131 
132  view_item_but_->view_item = reinterpret_cast<uiViewItemHandle *>(this);
133  UI_but_func_set(&view_item_but_->but, grid_tile_click_fn, view_item_but_, nullptr);
134 }
135 
137 {
138  /* Do nothing by default. */
139 }
140 
141 std::optional<bool> AbstractGridViewItem::should_be_active() const
142 {
143  return std::nullopt;
144 }
145 
146 void AbstractGridViewItem::change_state_delayed()
147 {
148  const std::optional<bool> should_be_active = this->should_be_active();
149  if (should_be_active.has_value() && *should_be_active) {
150  activate();
151  }
152 }
153 
155 {
156  BLI_assert_msg(get_view().is_reconstructed(),
157  "Item activation can't be done until reconstruction is completed");
158 
159  if (is_active()) {
160  return;
161  }
162 
163  /* Deactivate other items in the tree. */
164  get_view().foreach_item([](auto &item) { item.deactivate(); });
165 
166  on_activate();
167 
168  is_active_ = true;
169 }
170 
172 {
173  is_active_ = false;
174 }
175 
177 {
178  if (UNLIKELY(!view_)) {
179  throw std::runtime_error(
180  "Invalid state, item must be added through AbstractGridView::add_item()");
181  }
182  return dynamic_cast<AbstractGridView &>(*view_);
183 }
184 
185 /* ---------------------------------------------------------------------- */
186 
204  const View2D &v2d_;
205  const AbstractGridView &grid_view_;
206  const GridViewStyle &style_;
207  const int cols_per_row_ = 0;
208  /* Indices of items within the view. Calculated by constructor */
209  IndexRange visible_items_range_{};
210 
211  public:
213  const AbstractGridView &grid_view,
214  int cols_per_row);
215 
216  bool is_item_visible(int item_idx) const;
217  void fill_layout_before_visible(uiBlock &block) const;
218  void fill_layout_after_visible(uiBlock &block) const;
219 
220  private:
221  IndexRange get_visible_range() const;
222  void add_spacer_button(uiBlock &block, int row_count) const;
223 };
224 
226  const AbstractGridView &grid_view,
227  const int cols_per_row)
228  : v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row)
229 {
230  visible_items_range_ = get_visible_range();
231 }
232 
233 IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const
234 {
235  int first_idx_in_view = 0;
236  int max_items_in_view = 0;
237 
238  const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax);
239  if (!IS_EQF(scroll_ofs_y, 0)) {
240  const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height;
241 
242  first_idx_in_view = scrolled_away_rows * cols_per_row_;
243  }
244 
245  const float view_height = BLI_rctf_size_y(&v2d_.cur);
246  const int count_rows_in_view = std::max(round_fl_to_int(view_height / style_.tile_height), 1);
247  max_items_in_view = (count_rows_in_view + 1) * cols_per_row_;
248 
249  BLI_assert(max_items_in_view > 0);
250  return IndexRange(first_idx_in_view, max_items_in_view);
251 }
252 
254 {
255  return visible_items_range_.contains(item_idx);
256 }
257 
259 {
260  const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax);
261 
262  if (IS_EQF(scroll_ofs_y, 0)) {
263  return;
264  }
265 
266  const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height;
267  add_spacer_button(block, scrolled_away_rows);
268 }
269 
271 {
272  const int last_item_idx = grid_view_.get_item_count() - 1;
273  const int last_visible_idx = visible_items_range_.last();
274 
275  if (last_item_idx > last_visible_idx) {
276  const int remaining_rows = (cols_per_row_ > 0) ?
277  (last_item_idx - last_visible_idx) / cols_per_row_ :
278  0;
279  BuildOnlyVisibleButtonsHelper::add_spacer_button(block, remaining_rows);
280  }
281 }
282 
283 void BuildOnlyVisibleButtonsHelper::add_spacer_button(uiBlock &block, const int row_count) const
284 {
285  /* UI code only supports button dimensions of `signed short` size, the layout height we want to
286  * fill may be bigger than that. So add multiple labels of the maximum size if necessary. */
287  for (int remaining_rows = row_count; remaining_rows > 0;) {
288  const short row_count_this_iter = std::min(
289  std::numeric_limits<short>::max() / style_.tile_height, remaining_rows);
290 
291  uiDefBut(&block,
293  0,
294  "",
295  0,
296  0,
297  UI_UNIT_X,
298  row_count_this_iter * style_.tile_height,
299  nullptr,
300  0,
301  0,
302  0,
303  0,
304  "");
305  remaining_rows -= row_count_this_iter;
306  }
307 }
308 
309 /* ---------------------------------------------------------------------- */
310 
312  uiBlock &block_;
313 
314  friend class GridViewBuilder;
315 
316  public:
318 
319  void build_from_view(const AbstractGridView &grid_view, const View2D &v2d) const;
320 
321  private:
322  void build_grid_tile(uiLayout &grid_layout, AbstractGridViewItem &item) const;
323 
324  uiLayout *current_layout() const;
325 };
326 
328 {
329 }
330 
331 void GridViewLayoutBuilder::build_grid_tile(uiLayout &grid_layout,
332  AbstractGridViewItem &item) const
333 {
334  uiLayout *overlap = uiLayoutOverlap(&grid_layout);
335 
336  item.add_grid_tile_button(block_);
337  item.build_grid_tile(*uiLayoutRow(overlap, false));
338 }
339 
341  const View2D &v2d) const
342 {
343  uiLayout *prev_layout = current_layout();
344 
345  uiLayout &layout = *uiLayoutColumn(current_layout(), false);
346  const GridViewStyle &style = grid_view.get_style();
347 
348  const int cols_per_row = std::max(uiLayoutGetWidth(&layout) / style.tile_width, 1);
349 
350  BuildOnlyVisibleButtonsHelper build_visible_helper(v2d, grid_view, cols_per_row);
351 
352  build_visible_helper.fill_layout_before_visible(block_);
353 
354  /* Use `-cols_per_row` because the grid layout uses a multiple of the passed absolute value for
355  * the number of columns then, rather than distributing the number of items evenly over rows and
356  * stretching the items to fit (see #uiLayoutItemGridFlow.columns_len). */
357  uiLayout *grid_layout = uiLayoutGridFlow(&layout, true, -cols_per_row, true, true, true);
358 
359  int item_idx = 0;
360  grid_view.foreach_item([&](AbstractGridViewItem &item) {
361  /* Skip if item isn't visible. */
362  if (!build_visible_helper.is_item_visible(item_idx)) {
363  item_idx++;
364  return;
365  }
366 
367  build_grid_tile(*grid_layout, item);
368  item_idx++;
369  });
370 
371  /* If there are not enough items to fill the layout, add padding items so the layout doesn't
372  * stretch over the entire width. */
373  if (grid_view.get_item_count() < cols_per_row) {
374  for (int padding_item_idx = 0; padding_item_idx < (cols_per_row - grid_view.get_item_count());
375  padding_item_idx++) {
376  uiItemS(grid_layout);
377  }
378  }
379 
380  UI_block_layout_set_current(&block_, prev_layout);
381 
382  build_visible_helper.fill_layout_after_visible(block_);
383 }
384 
385 uiLayout *GridViewLayoutBuilder::current_layout() const
386 {
387  return block_.curlayout;
388 }
389 
390 /* ---------------------------------------------------------------------- */
391 
393 {
394 }
395 
397 {
398  grid_view.build_items();
399  grid_view.update_from_old(block_);
400  grid_view.change_state_delayed();
401 
402  GridViewLayoutBuilder builder(block_);
403  builder.build_from_view(grid_view, v2d);
404 }
405 
406 /* ---------------------------------------------------------------------- */
407 
409  : AbstractGridViewItem(identifier), label(label), preview_icon_id(preview_icon_id)
410 {
411 }
412 
414 {
415  const GridViewStyle &style = get_view().get_style();
416  uiBlock *block = uiLayoutGetBlock(&layout);
417 
418  uiBut *but = uiDefBut(block,
420  0,
421  label.c_str(),
422  0,
423  0,
424  style.tile_width,
425  style.tile_height,
426  nullptr,
427  0,
428  0,
429  0,
430  0,
431  "");
432  ui_def_but_icon(but,
434  /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */
436 }
437 
439 {
440  activate_fn_ = fn;
441 }
442 
444 {
445  is_active_fn_ = fn;
446 }
447 
448 void PreviewGridItem::on_activate()
449 {
450  if (activate_fn_) {
451  activate_fn_(*this);
452  }
453 }
454 
455 std::optional<bool> PreviewGridItem::should_be_active() const
456 {
457  if (is_active_fn_) {
458  return is_active_fn_();
459  }
460  return std::nullopt;
461 }
462 
463 } // namespace blender::ui
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition: BLI_assert.h:53
MINLINE int round_fl_to_int(float a)
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition: BLI_rect.h:198
#define UNLIKELY(x)
#define IS_EQF(a, b)
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei height
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum const void *lists _GL_VOID_RET _GL_VOID const GLdouble *equation _GL_VOID_RET _GL_VOID GLdouble GLdouble blue _GL_VOID_RET _GL_VOID GLfloat GLfloat blue _GL_VOID_RET _GL_VOID GLint GLint blue _GL_VOID_RET _GL_VOID GLshort GLshort blue _GL_VOID_RET _GL_VOID GLubyte GLubyte blue _GL_VOID_RET _GL_VOID GLuint GLuint blue _GL_VOID_RET _GL_VOID GLushort GLushort blue _GL_VOID_RET _GL_VOID GLbyte GLbyte GLbyte alpha _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble alpha _GL_VOID_RET _GL_VOID GLfloat GLfloat GLfloat alpha _GL_VOID_RET _GL_VOID GLint GLint GLint alpha _GL_VOID_RET _GL_VOID GLshort GLshort GLshort alpha _GL_VOID_RET _GL_VOID GLubyte GLubyte GLubyte alpha _GL_VOID_RET _GL_VOID GLuint GLuint GLuint alpha _GL_VOID_RET _GL_VOID GLushort GLushort GLushort alpha _GL_VOID_RET _GL_VOID GLenum mode _GL_VOID_RET _GL_VOID GLint GLsizei width
uiBlock * uiLayoutGetBlock(uiLayout *layout)
@ UI_BUT_ICON_PREVIEW
Definition: UI_interface.h:190
uiLayout * uiLayoutColumn(uiLayout *layout, bool align)
uiLayout * uiLayoutGridFlow(uiLayout *layout, bool row_major, int columns_len, bool even_columns, bool even_rows, bool align)
uiLayout * uiLayoutOverlap(uiLayout *layout)
uiBut * uiDefBut(uiBlock *block, int type, int retval, const char *str, int x, int y, short width, short height, void *poin, float min, float max, float a1, float a2, const char *tip)
Definition: interface.cc:4806
void uiItemS(uiLayout *layout)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
int UI_preview_tile_size_x(void)
Definition: interface.cc:4973
void UI_but_func_set(uiBut *but, uiButHandleFunc func, void *arg1, void *arg2)
Definition: interface.cc:6000
int uiLayoutGetWidth(uiLayout *layout)
int UI_preview_tile_size_y(void)
Definition: interface.cc:4979
void UI_block_layout_set_current(uiBlock *block, uiLayout *layout)
#define UI_UNIT_X
struct uiViewItemHandle uiViewItemHandle
Definition: UI_interface.h:78
@ UI_BTYPE_VIEW_ITEM
Definition: UI_interface.h:393
@ UI_BTYPE_PREVIEW_TILE
Definition: UI_interface.h:376
@ UI_BTYPE_LABEL
Definition: UI_interface.h:354
constexpr int64_t last(const int64_t n=0) const
constexpr bool contains(int64_t value) const
virtual std::optional< bool > should_be_active() const
Definition: grid_view.cc:141
AbstractGridViewItem(StringRef identifier)
Definition: grid_view.cc:93
virtual void build_grid_tile(uiLayout &layout) const =0
const AbstractGridView & get_view() const
Definition: grid_view.cc:176
virtual bool matches(const AbstractViewItem &other) const override
Definition: grid_view.cc:97
Map< StringRef, AbstractGridViewItem * > item_map_
ItemT & add_item(Args &&...args)
void foreach_item(ItemIterFn iter_fn) const
Definition: grid_view.cc:38
const GridViewStyle & get_style() const
Definition: grid_view.cc:77
virtual void build_items()=0
Vector< std::unique_ptr< AbstractGridViewItem > > items_
void update_from_old(uiBlock &new_block)
void register_item(AbstractViewItem &item)
bool is_item_visible(int item_idx) const
Definition: grid_view.cc:253
void fill_layout_after_visible(uiBlock &block) const
Definition: grid_view.cc:270
BuildOnlyVisibleButtonsHelper(const View2D &v2d, const AbstractGridView &grid_view, int cols_per_row)
Definition: grid_view.cc:225
void fill_layout_before_visible(uiBlock &block) const
Definition: grid_view.cc:258
GridViewBuilder(uiBlock &block)
Definition: grid_view.cc:392
void build_grid_view(AbstractGridView &grid_view, const View2D &v2d)
Definition: grid_view.cc:396
GridViewLayoutBuilder(uiBlock &block)
Definition: grid_view.cc:327
void build_from_view(const AbstractGridView &grid_view, const View2D &v2d) const
Definition: grid_view.cc:340
std::function< void(PreviewGridItem &new_active)> ActivateFn
PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id)
Definition: grid_view.cc:408
void build_grid_tile(uiLayout &layout) const override
Definition: grid_view.cc:413
void set_is_active_fn(IsActiveFn fn)
Definition: grid_view.cc:443
void set_on_activate_fn(ActivateFn fn)
Definition: grid_view.cc:438
std::function< bool()> IsActiveFn
const char * label
void ui_def_but_icon(uiBut *but, const int icon, const int flag)
Definition: interface.cc:4244
@ UI_HAS_ICON
T abs(const T &a)
#define min(a, b)
Definition: sort.c:35
GridViewStyle(int width, int height)
Definition: grid_view.cc:87
float ymax
Definition: DNA_vec_types.h:70
struct uiLayout * curlayout
uiViewItemHandle * view_item
float max