WIP: Rewrite asset browser as separate editor #107576

Draft
Julian Eisel wants to merge 95 commits from asset-browser-frontend-split into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
13 changed files with 202 additions and 10 deletions
Showing only changes of commit c90dfcc52d - Show all commits

View File

@ -874,7 +874,7 @@ def km_view2d_buttons_list(_params):
return keymap
def km_user_interface(_params):
def km_user_interface(params):
items = []
keymap = (
"User Interface",
@ -908,6 +908,8 @@ def km_user_interface(_params):
("ui.reset_default_button", {"type": 'BACK_SPACE', "value": 'PRESS'}, {"properties": [("all", True)]}),
# UI lists (polls check if there's a UI list under the cursor).
("ui.list_start_filter", {"type": 'F', "value": 'PRESS', "ctrl": True}, None),
# UI views (polls check if there's a UI view under the cursor).
*_template_items_select_actions(params, "ui.view_select_all"),
])
return keymap

View File

@ -15,12 +15,12 @@ extern "C" {
struct KDTree_1d;
struct wmOperatorType;
enum {
typedef enum SelectAction {
SEL_TOGGLE = 0,
SEL_SELECT = 1,
SEL_DESELECT = 2,
SEL_INVERT = 3,
};
} SelectAction;
typedef enum WalkSelectDirection {
UI_SELECT_WALK_UP,

View File

@ -76,6 +76,12 @@ class AbstractView {
*/
virtual std::unique_ptr<AbstractViewDropTarget> create_drop_target();
/**
* Iterate over all elements in the view executing \a iter_fn for each. Typically views would
* want to implement their own `foreach_item()` function with a more specific type.
*/
virtual void foreach_abstract_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const = 0;
/** Listen to a notifier, returning true if a redraw is needed. */
virtual bool listen(const wmNotifier &) const;
@ -129,6 +135,7 @@ class AbstractViewItem {
AbstractView *view_ = nullptr;
bool is_interactive_ = true;
bool is_active_ = false;
bool is_selected_ = false;
bool is_renaming_ = false;
public:
@ -183,6 +190,22 @@ class AbstractViewItem {
*/
bool is_active() const;
/**
* Mark this item as selected.
* \return True if the selection state changed (redraw needed).
*/
bool select();
/**
* Mark this item as not selected.
* \return True if the selection state changed (redraw needed).
*/
bool deselect();
/**
* Requires the view to have completed reconstruction, see #is_reconstructed(). Otherwise we
* can't be sure about the item state.
*/
bool is_selected() const;
bool is_renaming() const;
void begin_renaming();
void end_renaming();

View File

@ -105,6 +105,7 @@ class AbstractGridView : public AbstractView {
AbstractGridView();
virtual ~AbstractGridView() = default;
void foreach_abstract_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const override;
using ItemIterFn = FunctionRef<void(AbstractGridViewItem &)>;
void foreach_item(ItemIterFn iter_fn) const;

View File

@ -3260,6 +3260,7 @@ void UI_interface_tag_script_reload(void);
bool UI_view_item_is_interactive(const uiViewItemHandle *item_handle);
bool UI_view_item_is_active(const uiViewItemHandle *item_handle);
bool UI_view_item_is_selected(const uiViewItemHandle *item_handle);
bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle);
/**
* Can \a item_handle be renamed right now? Note that this isn't just a mere wrapper around
@ -3284,7 +3285,9 @@ bool UI_view_item_drag_start(struct bContext *C, const uiViewItemHandle *item_);
* \param xy: Coordinate to find a view item at, in window space.
* \param pad: Extra padding added to the bounding box of the view.
*/
uiViewHandle *UI_region_view_find_at(const struct ARegion *region, const int xy[2], int pad);
uiViewHandle *UI_region_view_find_at(const struct ARegion *region,
const int xy[2],
int pad CPP_ARG_DEFAULT(0));
/**
* \param xy: Coordinate to find a view item at, in window space.
*/

View File

@ -121,6 +121,11 @@ std::unique_ptr<DropTargetInterface> view_item_drop_target(uiViewItemHandle *ite
std::unique_ptr<DropTargetInterface> region_views_find_drop_target_at(const ARegion *region,
const int xy[2]);
/**
* \return True if any selection state changed (redraw necessary).
*/
bool view_select_all_items(uiViewHandle *view_handle, int /*SelectAction*/ action);
} // namespace blender::ui
enum eUIListFilterResult {

View File

@ -67,7 +67,6 @@ class TreeViewItemContainer {
/* Keep ENUM_OPERATORS() below updated! */
};
using ItemIterFn = FunctionRef<void(AbstractTreeViewItem &)>;
/**
* Convenience wrapper constructing the item by forwarding given arguments to the constructor of
@ -90,7 +89,8 @@ class TreeViewItemContainer {
AbstractTreeViewItem &add_tree_item(std::unique_ptr<AbstractTreeViewItem> item);
protected:
void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
void foreach_item_recursive(FunctionRef<void(AbstractTreeViewItem &)> iter_fn,
IterOptions options = IterOptions::None) const;
};
ENUM_OPERATORS(TreeViewItemContainer::IterOptions,
@ -116,7 +116,9 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
public:
virtual ~AbstractTreeView() = default;
void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
void foreach_abstract_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const override;
void foreach_item(FunctionRef<void(AbstractTreeViewItem &)> iter_fn,
IterOptions options = IterOptions::None) const;
/** Visual feature: Define a number of item rows the view will always show at minimum. If there
* are fewer items, empty dummy items will be added. These contribute to the view bounds, so the

View File

@ -2276,7 +2276,10 @@ int ui_but_is_pushed_ex(uiBut *but, double *value)
is_push = -1;
if (view_item_but->view_item) {
is_push = UI_view_item_is_active(view_item_but->view_item);
/* Consider both active and selected as pushed state. Drawing can differentiate the state
* further for visual feedback. */
is_push = UI_view_item_is_active(view_item_but->view_item) ||
UI_view_item_is_selected(view_item_but->view_item);
}
break;
}

View File

@ -2368,6 +2368,48 @@ static void UI_OT_list_start_filter(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI View Select All Operator
* \{ */
static bool ui_view_under_cursor_poll(bContext *C)
{
const wmWindow *win = CTX_wm_window(C);
const ARegion *region = CTX_wm_region(C);
if (region == nullptr) {
return false;
}
return UI_region_view_find_at(region, win->eventstate->xy) != nullptr;
}
static int ui_view_select_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
uiViewHandle *view = UI_region_view_find_at(region, event->xy);
const int action = RNA_enum_get(op->ptr, "action");
if (view_select_all_items(view, action)) {
ED_region_tag_redraw(region);
}
return OPERATOR_FINISHED;
}
static void UI_OT_view_select_all(wmOperatorType *ot)
{
ot->name = "View Select All";
ot->idname = "UI_OT_view_select_all";
ot->description = "Select or deselect all items in the view";
ot->invoke = ui_view_select_all_invoke;
ot->poll = ui_view_under_cursor_poll;
WM_operator_properties_select_all(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI View Drop Operator
* \{ */
@ -2555,6 +2597,7 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_list_start_filter);
WM_operatortype_append(UI_OT_view_select_all);
WM_operatortype_append(UI_OT_view_drop);
WM_operatortype_append(UI_OT_view_item_rename);

View File

@ -4,6 +4,8 @@
* \ingroup edinterface
*/
#include "ED_select_utils.h"
#include "interface_intern.hh"
#include "UI_abstract_view.hh"
@ -117,6 +119,70 @@ std::optional<rcti> AbstractView::get_bounds() const
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Selection API
* \{ */
/**
* If \a action is #SEL_TOGGLE, check the selection if the actual action to apply should be
* #SEL_DESELECT or #SEL_SELECT and return that.
*/
static SelectAction select_all_refine_action_type(const AbstractView &view, SelectAction action)
{
if (action != SEL_TOGGLE) {
return action;
}
bool any_selected = false;
view.foreach_abstract_item([&any_selected](AbstractViewItem &item) {
if (item.is_selected()) {
any_selected = true;
}
});
return any_selected ? SEL_DESELECT : SEL_SELECT;
}
bool view_select_all_items(uiViewHandle *view_handle, const int /*SelectAction*/ action)
{
AbstractView &view = reinterpret_cast<AbstractView &>(*view_handle);
const SelectAction refined_action = select_all_refine_action_type(view, SelectAction(action));
bool changed = false;
view.foreach_abstract_item([&changed, refined_action](AbstractViewItem &item) {
switch (refined_action) {
case SEL_SELECT:
if (item.select()) {
changed = true;
}
break;
case SEL_DESELECT:
if (item.deselect()) {
changed = true;
}
break;
case SEL_INVERT:
if (item.is_selected()) {
item.deselect();
}
else {
item.select();
}
changed = true;
break;
case SEL_TOGGLE:
BLI_assert_msg(false,
"TOGGLE action should have been refined to be either SELECT or DESELECT at "
"this point");
break;
}
});
return changed;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name General API functions
* \{ */

View File

@ -25,6 +25,7 @@ namespace blender::ui {
void AbstractViewItem::update_from_old(const AbstractViewItem &old)
{
is_active_ = old.is_active_;
is_selected_ = old.is_selected_;
is_renaming_ = old.is_renaming_;
}
@ -221,6 +222,31 @@ bool AbstractViewItem::is_active() const
return is_active_;
}
bool AbstractViewItem::select()
{
if (is_selected_) {
return false;
}
is_selected_ = true;
return true;
}
bool AbstractViewItem::deselect()
{
if (!is_selected_) {
return false;
}
is_selected_ = false;
return true;
}
bool AbstractViewItem::is_selected() const
{
BLI_assert_msg(get_view().is_reconstructed(),
"State can't be queried until reconstruction is completed");
return is_selected_;
}
/** \} */
/* ---------------------------------------------------------------------- */
@ -300,6 +326,12 @@ bool UI_view_item_is_active(const uiViewItemHandle *item_handle)
return item.is_active();
}
bool UI_view_item_is_selected(const uiViewItemHandle *item_handle)
{
const AbstractViewItem &item = reinterpret_cast<const AbstractViewItem &>(*item_handle);
return item.is_selected();
}
bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle)
{
const AbstractViewItem &a = reinterpret_cast<const AbstractViewItem &>(*a_handle);

View File

@ -35,6 +35,11 @@ AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr<AbstractGridVie
return added_item;
}
void AbstractGridView::foreach_abstract_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const
{
foreach_item([&iter_fn](AbstractGridViewItem &item) { iter_fn(item); });
}
void AbstractGridView::foreach_item(ItemIterFn iter_fn) const
{
for (const auto &item_ptr : items_) {

View File

@ -47,7 +47,8 @@ AbstractTreeViewItem &TreeViewItemContainer::add_tree_item(
return added_item;
}
void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const
void TreeViewItemContainer::foreach_item_recursive(
FunctionRef<void(AbstractTreeViewItem &)> iter_fn, IterOptions options) const
{
for (const auto &child : children_) {
iter_fn(*child);
@ -61,7 +62,13 @@ void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptio
/* ---------------------------------------------------------------------- */
void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const
void AbstractTreeView::foreach_abstract_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const
{
foreach_item_recursive([&iter_fn](AbstractTreeViewItem &item) { iter_fn(item); });
}
void AbstractTreeView::foreach_item(FunctionRef<void(AbstractTreeViewItem &)> iter_fn,
IterOptions options) const
{
foreach_item_recursive(iter_fn, options);
}