[Bf-blender-cvs] [23d2e77a54f] master: UI: Add initial "grid view"

Julian Eisel noreply at git.blender.org
Thu Jun 16 19:25:56 CEST 2022


Commit: 23d2e77a54f4f813d7ee38ddb06c14ecc0943e4e
Author: Julian Eisel
Date:   Thu Jun 16 11:29:20 2022 +0200
Branches: master
https://developer.blender.org/rB23d2e77a54f4f813d7ee38ddb06c14ecc0943e4e

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.

===================================================================

M	release/datafiles/userdef/userdef_default_theme.c
M	release/scripts/startup/bl_ui/space_userpref.py
M	source/blender/blenloader/intern/versioning_userdef.c
A	source/blender/editors/include/UI_grid_view.hh
M	source/blender/editors/include/UI_interface.h
M	source/blender/editors/include/UI_interface.hh
M	source/blender/editors/include/UI_tree_view.hh
M	source/blender/editors/interface/CMakeLists.txt
A	source/blender/editors/interface/grid_view.cc
M	source/blender/editors/interface/interface.cc
M	source/blender/editors/interface/interface_handlers.c
M	source/blender/editors/interface/interface_intern.h
M	source/blender/editors/interface/interface_query.cc
M	source/blender/editors/interface/interface_template_list.cc
M	source/blender/editors/interface/interface_view.cc
M	source/blender/editors/interface/interface_widgets.c
M	source/blender/editors/interface/tree_view.cc
M	source/blender/editors/screen/area.c
M	source/blender/editors/space_file/filesel.c
M	source/blender/editors/util/CMakeLists.txt
M	source/blender/makesdna/DNA_userdef_types.h
M	source/blender/makesrna/intern/rna_userdef.c

===================================================================

diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c
index 19798e3f165..04ad04d4812 100644
--- a/release/datafiles/userdef/userdef_default_theme.c
+++ b/release/datafiles/userdef/userdef_default_theme.c
@@ -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),
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index 191066df89f..5070bd04142 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -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"),
         ]
 
diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c
index b3f3b9cbf7d..40359500d3c 100644
--- a/source/blender/blenloader/intern/versioning_userdef.c
+++ b/source/blender/blenloader/intern/versioning_userdef.c
@@ -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
diff --git a/source/blender/editors/include/UI_grid_view.hh b/source/blender/editors/include/UI_grid_view.hh
new file mode 100644
index 00000000000..6f553f4fad1
--- /dev/null
+++ b/source/blender/editors/include/UI_grid_view.hh
@@ -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 impleme

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list