Revert "UI: Tree-view scrolling and resizing support"

This reverts commit 6a1210ff3f.
This commit is contained in:
2025-09-27 09:23:45 -05:00
parent 8832af0d4b
commit 268b286c69
11 changed files with 73 additions and 346 deletions
@@ -1020,8 +1020,6 @@ def km_user_interface(_params):
("ui.list_start_filter", {"type": 'F', "value": 'PRESS', "ctrl": True}, None),
# UI views (polls check if there's a UI view under the cursor).
("ui.view_start_filter", {"type": 'F', "value": 'PRESS', "ctrl": True}, None),
("ui.view_scroll", {"type": 'WHEELUPMOUSE', "value": 'ANY'}, None),
("ui.view_scroll", {"type": 'WHEELDOWNMOUSE', "value": 'ANY'}, None),
])
return keymap
@@ -47,11 +47,6 @@ namespace blender::ui {
class AbstractViewItem;
class AbstractViewItemDragController;
enum class ViewScrollDirection {
UP,
DOWN,
};
class AbstractView {
friend class AbstractViewItem;
friend struct ::ViewLink;
@@ -91,13 +86,10 @@ class AbstractView {
*/
virtual bool begin_filtering(const bContext &C) const;
virtual void draw_overlays(const ARegion &region, const uiBlock &block) const;
virtual void draw_overlays(const ARegion &region) const;
virtual void foreach_view_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const = 0;
virtual bool supports_scrolling() const;
virtual void scroll(ViewScrollDirection direction);
/**
* Makes \a item valid for display in this view. Behavior is undefined for items not registered
* with this.
+10 -21
View File
@@ -112,36 +112,29 @@ using TreeViewOrItem = TreeViewItemContainer;
* \{ */
class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
/* Shared pointer so the pointer can be kept persistent over redraws. The grip button gets a
* pointer to modify the value on resizing, and it uses it to identify the button over redraws.*/
/* TODO support region zoom. */
std::shared_ptr<int> custom_height_ = nullptr;
std::shared_ptr<int> scroll_value_ = nullptr;
int min_rows_ = 0;
friend class AbstractTreeViewItem;
friend class TreeViewBuilder;
friend class TreeViewLayoutBuilder;
friend class TreeViewItemDropTarget;
public:
/* virtual */ ~AbstractTreeView() override = default;
void draw_overlays(const ARegion &region, const uiBlock &block) const override;
void draw_overlays(const ARegion &region) const override;
void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
void scroll(ViewScrollDirection direction) override;
/**
* \param xy: The mouse coordinates in window space.
*/
AbstractTreeViewItem *find_hovered(const ARegion &region, const int2 &xy);
/** Visual feature: Define a number of item rows the view will show by default. If there
/** 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
* drop target of the view includes them, but they are not interactive (e.g. no mouse-hover
* highlight). */
void set_default_rows(int min_rows);
void set_min_rows(int min_rows);
protected:
virtual void build_tree() = 0;
@@ -153,18 +146,14 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
const TreeViewOrItem &old_items);
static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item,
const TreeViewOrItem &items);
std::optional<int> tot_visible_row_count() const;
bool supports_scrolling() const override;
void draw_hierarchy_lines(const ARegion &region) const;
void draw_hierarchy_lines_recursive(const ARegion &region,
const TreeViewOrItem &parent,
const uint pos,
const float aspect) const;
void draw_hierarchy_lines(const ARegion &region, const uiBlock &block) const;
void get_hierarchy_lines(const ARegion &region,
const TreeViewOrItem &parent,
const float aspect,
Vector<std::pair<int2, int2>> &lines,
int &visible_item_index) const;
int count_visible_descendants(const AbstractTreeViewItem &parent) const;
AbstractTreeViewItem *find_last_visible_descendant(const AbstractTreeViewItem &parent) const;
};
/** \} */
@@ -52,7 +52,6 @@
#include "RNA_types.hh"
#include "UI_interface.hh"
#include "UI_abstract_view.hh"
#include "interface_intern.hh"
@@ -2404,23 +2403,18 @@ static void UI_OT_list_start_filter(wmOperatorType *ot)
/** \name UI View Start Filter Operator
* \{ */
static AbstractView *get_view_focused(bContext *C)
static bool ui_view_focused_poll(bContext *C)
{
const wmWindow *win = CTX_wm_window(C);
if (!(win && win->eventstate)) {
return nullptr;
return false;
}
const ARegion *region = CTX_wm_region(C);
if (!region) {
return nullptr;
return false;
}
return reinterpret_cast<AbstractView*>(UI_region_view_find_at(region, win->eventstate->xy, 0));
}
static bool ui_view_focused_poll(bContext *C)
{
const AbstractView *view = get_view_focused(C);
const uiViewHandle *view = UI_region_view_find_at(region, win->eventstate->xy, 0);
return view != nullptr;
}
@@ -2501,72 +2495,6 @@ static void UI_OT_view_drop(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI View Drop Operator
* \{ */
static bool ui_view_scroll_poll(bContext *C)
{
const AbstractView *view = get_view_focused(C);
if (!view) {
return false;
}
return view->supports_scrolling();
}
static int ui_view_scroll_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
int type = event->type;
bool invert_direction = false;
if (type == MOUSEPAN) {
int dummy_val;
ui_pan_to_scroll(event, &type, &dummy_val);
/* 'ui_pan_to_scroll' gives the absolute direction. */
if (event->flag & WM_EVENT_SCROLL_INVERT) {
invert_direction = true;
}
}
AbstractView *view = get_view_focused(C);
std::optional<ViewScrollDirection> direction =
[type, invert_direction]() -> std::optional<ViewScrollDirection> {
switch (type) {
case WHEELUPMOUSE:
return invert_direction ? ViewScrollDirection::DOWN : ViewScrollDirection::UP;
case WHEELDOWNMOUSE:
return invert_direction ? ViewScrollDirection::UP : ViewScrollDirection::DOWN;
default:
return std::nullopt;
}
}();
if (!direction) {
return OPERATOR_CANCELLED;
}
BLI_assert(view->supports_scrolling());
view->scroll(*direction);
ED_region_tag_redraw(region);
return OPERATOR_FINISHED;
}
static void UI_OT_view_scroll(wmOperatorType *ot)
{
ot->name = "View Scroll";
ot->idname = "UI_OT_view_scroll";
ot->invoke = ui_view_scroll_invoke;
ot->poll = ui_view_scroll_poll;
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI View Item Rename Operator
*
@@ -2710,7 +2638,6 @@ void ED_operatortypes_ui()
WM_operatortype_append(UI_OT_view_start_filter);
WM_operatortype_append(UI_OT_view_drop);
WM_operatortype_append(UI_OT_view_scroll);
WM_operatortype_append(UI_OT_view_item_rename);
WM_operatortype_append(UI_OT_override_type_set_button);
@@ -473,7 +473,7 @@ void uiTemplateBoneCollectionTree(uiLayout *layout, bContext *C)
*block,
"Bone Collection Tree View",
std::make_unique<blender::ui::bonecollections::BoneCollectionTreeView>(*armature));
tree_view->set_default_rows(3);
tree_view->set_min_rows(3);
ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
}
@@ -410,7 +410,7 @@ void uiTemplateGreasePencilLayerTree(uiLayout *layout, bContext *C)
*block,
"Grease Pencil Layer Tree View",
std::make_unique<blender::ui::greasepencil::LayerTreeView>(grease_pencil));
tree_view->set_default_rows(3);
tree_view->set_min_rows(3);
ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
}
@@ -399,7 +399,7 @@ void uiTemplateLightLinkingCollection(uiLayout *layout,
*block,
"Light Linking Collection Tree View",
std::make_unique<blender::ui::light_linking::CollectionView>(*context_layout, *collection));
tree_view->set_default_rows(3);
tree_view->set_min_rows(3);
blender::ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
}
@@ -517,7 +517,7 @@ void uiTemplateNodeTreeInterface(uiLayout *layout, PointerRNA *ptr)
*block,
"Node Tree Declaration Tree View",
std::make_unique<blender::ui::nodes::NodeTreeInterfaceView>(nodetree, interface));
tree_view->set_default_rows(3);
tree_view->set_min_rows(3);
blender::ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
}
@@ -111,21 +111,11 @@ bool AbstractView::begin_filtering(const bContext & /*C*/) const
return false;
}
void AbstractView::draw_overlays(const ARegion & /*region*/, const uiBlock & /*block*/) const
void AbstractView::draw_overlays(const ARegion & /*region*/) const
{
/* Nothing by default. */
}
bool AbstractView::supports_scrolling() const
{
return false;
}
void AbstractView::scroll(ViewScrollDirection /*direction*/)
{
BLI_assert_msg(false, "Unsupported for this view type");
}
/** \} */
/* ---------------------------------------------------------------------- */
@@ -145,7 +145,7 @@ void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *l
void ui_block_views_draw_overlays(const ARegion *region, const uiBlock *block)
{
LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
view_link->view->draw_overlays(*region, *block);
view_link->view->draw_overlays(*region);
}
}
@@ -18,7 +18,6 @@
#include "interface_intern.hh"
#include "UI_interface.hh"
#include "UI_view2d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
@@ -116,119 +115,74 @@ AbstractTreeViewItem *AbstractTreeView::find_hovered(const ARegion &region, cons
return hovered_item;
}
void AbstractTreeView::set_default_rows(int default_rows)
void AbstractTreeView::set_min_rows(int min_rows)
{
custom_height_ = std::make_unique<int>(default_rows * padded_item_height());
min_rows_ = min_rows;
}
int AbstractTreeView::count_visible_descendants(const AbstractTreeViewItem &parent) const
AbstractTreeViewItem *AbstractTreeView::find_last_visible_descendant(
const AbstractTreeViewItem &parent) const
{
if (parent.is_collapsed()) {
return 0;
}
int count = 0;
for (const auto &item : parent.children_) {
if (!item->is_filtered_visible()) {
continue;
}
count++;
count += count_visible_descendants(*item);
return nullptr;
}
return count;
AbstractTreeViewItem *last_descendant = parent.children_.last().get();
while (!last_descendant->children_.is_empty() && !last_descendant->is_collapsed()) {
last_descendant = last_descendant->children_.last().get();
}
return last_descendant;
}
void AbstractTreeView::get_hierarchy_lines(const ARegion &region,
const TreeViewOrItem &parent,
const float aspect,
Vector<std::pair<int2, int2>> &lines,
int &visible_item_index) const
void AbstractTreeView::draw_hierarchy_lines_recursive(const ARegion &region,
const TreeViewOrItem &parent,
const uint pos,
const float aspect) const
{
const int scroll_ofs = scroll_value_ ? *scroll_value_ : 0;
const int max_visible_row_count = tot_visible_row_count().value_or(
std::numeric_limits<int>::max());
for (const auto &item : parent.children_) {
if (!item->is_filtered_visible()) {
continue;
}
const int item_index = visible_item_index;
visible_item_index++;
if (!item->is_collapsible() || item->is_collapsed()) {
continue;
}
/* Draw a hierarchy line for the descendants of this item. */
draw_hierarchy_lines_recursive(region, *item, pos, aspect);
const AbstractTreeViewItem *first_descendant = item->children_.first().get();
const int descendant_count = count_visible_descendants(*item);
const int first_descendant_index = item_index + 1;
const int last_descendant_index = first_descendant_index + descendant_count;
const AbstractTreeViewItem *last_descendant = find_last_visible_descendant(*item);
if (!first_descendant->view_item_but_ || !last_descendant || !last_descendant->view_item_but_)
{
const bool line_ends_above_visible = last_descendant_index < scroll_ofs;
if (line_ends_above_visible) {
continue;
}
const bool line_starts_below_visible = first_descendant_index >
(scroll_ofs + long(max_visible_row_count));
/* Can return here even, following items won't be in view anymore. */
if (line_starts_below_visible) {
return;
}
return;
}
const uiButViewItem &first_child_but = *first_descendant->view_item_button();
const uiButViewItem &last_child_but = *last_descendant->view_item_button();
const int x = ((first_descendant->indent_width() + (5 * UI_SCALE_FAC) -
(0.5f * UI_ICON_SIZE) + U.pixelsize + UI_SCALE_FAC) /
aspect);
const int ymax = std::max(0, first_descendant_index - scroll_ofs) * padded_item_height();
const int ymin = std::min(max_visible_row_count, last_descendant_index - scroll_ofs) *
padded_item_height();
lines.append(std::make_pair(int2(x, ymax), int2(x, ymin)));
BLI_assert(first_child_but.block == last_child_but.block);
const uiBlock *block = first_child_but.block;
this->get_hierarchy_lines(region, *item, aspect, lines, visible_item_index);
rcti first_child_rect;
ui_but_to_pixelrect(&first_child_rect, &region, block, &first_child_but);
rcti last_child_rect;
ui_but_to_pixelrect(&last_child_rect, &region, block, &last_child_but);
const float x = first_child_rect.xmin + ((first_descendant->indent_width() -
(0.5f * UI_ICON_SIZE) + U.pixelsize + UI_SCALE_FAC) /
aspect);
const int first_child_top = first_child_rect.ymax - (2.0f * UI_SCALE_FAC / aspect);
const int last_child_bottom = last_child_rect.ymin + (4.0f * UI_SCALE_FAC / aspect);
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(pos, x, first_child_top);
immVertex2f(pos, x, last_child_bottom);
immEnd();
}
}
static uiButViewItem *find_first_view_item_but(const uiBlock &block, const AbstractTreeView &view)
{
LISTBASE_FOREACH (uiBut *, but, &block.buttons) {
if (but->type != UI_BTYPE_VIEW_ITEM) {
continue;
}
uiButViewItem *view_item_but = static_cast<uiButViewItem *>(but);
AbstractViewItem *view_item = reinterpret_cast<AbstractViewItem *>(view_item_but->view_item);
if (&view_item->get_view() == &view) {
return view_item_but;
}
}
return nullptr;
}
void AbstractTreeView::draw_hierarchy_lines(const ARegion &region, const uiBlock &block) const
void AbstractTreeView::draw_hierarchy_lines(const ARegion &region) const
{
const float aspect = (region.v2d.flag & V2D_IS_INIT) ?
BLI_rctf_size_y(&region.v2d.cur) /
(BLI_rcti_size_y(&region.v2d.mask) + 1) :
1.0f;
uiButViewItem *first_item_but = find_first_view_item_but(block, *this);
if (!first_item_but) {
return;
}
Vector<std::pair<int2, int2>> lines;
int index = 0;
get_hierarchy_lines(region, *this, aspect, lines, index);
if (lines.is_empty()) {
return;
}
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
@@ -236,34 +190,22 @@ void AbstractTreeView::draw_hierarchy_lines(const ARegion &region, const uiBlock
GPU_line_width(1.0f / aspect);
GPU_blend(GPU_BLEND_ALPHA);
rcti first_item_but_pixel_rect;
ui_but_to_pixelrect(&first_item_but_pixel_rect, &region, &block, first_item_but);
int2 top_left{first_item_but_pixel_rect.xmin, first_item_but_pixel_rect.ymax};
for (const auto &line : lines) {
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(pos, top_left.x + line.first.x, top_left.y - line.first.y);
immVertex2f(pos, top_left.x + line.second.x, top_left.y - line.second.y);
immEnd();
}
draw_hierarchy_lines_recursive(region, *this, pos, aspect);
GPU_blend(GPU_BLEND_NONE);
immUnbindProgram();
}
void AbstractTreeView::draw_overlays(const ARegion &region, const uiBlock &block) const
void AbstractTreeView::draw_overlays(const ARegion &region) const
{
draw_hierarchy_lines(region, block);
draw_hierarchy_lines(region);
}
void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
{
const AbstractTreeView &old_tree_view = dynamic_cast<const AbstractTreeView &>(old_view);
custom_height_ = old_tree_view.custom_height_;
scroll_value_ = old_tree_view.scroll_value_;
this->update_children_from_old_recursive(*this, old_tree_view);
update_children_from_old_recursive(*this, old_tree_view);
}
void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
@@ -295,31 +237,6 @@ AbstractTreeViewItem *AbstractTreeView::find_matching_child(
return nullptr;
}
std::optional<int> AbstractTreeView::tot_visible_row_count() const
{
if (!custom_height_) {
return {};
}
if (*custom_height_ < UI_UNIT_Y) {
return 1;
}
return round_fl_to_int(float(*custom_height_) / padded_item_height());
}
bool AbstractTreeView::supports_scrolling() const
{
return custom_height_ && scroll_value_;
}
void AbstractTreeView::scroll(ViewScrollDirection direction)
{
if (!supports_scrolling()) {
return;
}
/* Scroll value will be sanitized/clamped when drawing. */
*scroll_value_ += ((direction == ViewScrollDirection::UP) ? -1 : 1);
}
/* ---------------------------------------------------------------------- */
TreeViewItemDropTarget::TreeViewItemDropTarget(AbstractTreeViewItem &view_item,
@@ -696,7 +613,7 @@ class TreeViewLayoutBuilder {
friend TreeViewBuilder;
public:
void build_from_tree(AbstractTreeView &tree_view);
void build_from_tree(const AbstractTreeView &tree_view);
void build_row(AbstractTreeViewItem &item) const;
uiBlock &block() const;
@@ -711,99 +628,18 @@ TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiLayout &layout) : block_(*uiLayou
{
}
static int count_visible_items(AbstractTreeView &tree_view)
{
int item_count = 0;
tree_view.foreach_item([&](AbstractTreeViewItem &) { item_count++; },
AbstractTreeView::IterOptions::SkipCollapsed |
AbstractTreeView::IterOptions::SkipFiltered);
return item_count;
}
void TreeViewLayoutBuilder::build_from_tree(AbstractTreeView &tree_view)
void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view)
{
uiLayout &parent_layout = current_layout();
uiBlock *block = uiLayoutGetBlock(&parent_layout);
uiLayout *col = nullptr;
if (true) {
uiLayout *box = uiLayoutBox(&parent_layout);
col = uiLayoutColumn(box, true);
}
else {
col = uiLayoutColumn(&parent_layout, true);
}
/* Row for the tree-view and the scroll bar. */
uiLayout *row = uiLayoutRow(col, false);
uiLayout *box = uiLayoutBox(&parent_layout);
uiLayoutColumn(box, true);
const std::optional<int> visible_row_count = tree_view.tot_visible_row_count();
const int tot_items = count_visible_items(tree_view);
tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); },
AbstractTreeView::IterOptions::SkipCollapsed |
AbstractTreeView::IterOptions::SkipFiltered);
/* Column for the tree view. */
uiLayoutColumn(row, true);
/* Clamp scroll-value to valid range. */
if (tree_view.scroll_value_ && visible_row_count) {
*tree_view.scroll_value_ = std::clamp(
*tree_view.scroll_value_, 0, tot_items - *visible_row_count);
}
const int first_visible_index = tree_view.scroll_value_ ? *tree_view.scroll_value_ : 0;
const int max_visible_index = visible_row_count ? first_visible_index + *visible_row_count - 1 :
std::numeric_limits<int>::max();
int index = 0;
tree_view.foreach_item(
[&, this](AbstractTreeViewItem &item) {
if ((index >= first_visible_index) && (index <= max_visible_index)) {
this->build_row(item);
}
index++;
},
AbstractTreeView::IterOptions::SkipCollapsed | AbstractTreeView::IterOptions::SkipFiltered);
if (tree_view.custom_height_) {
uiLayoutColumn(row, false);
*tree_view.custom_height_ = visible_row_count.value_or(1) * padded_item_height();
if (!tree_view.scroll_value_) {
tree_view.scroll_value_ = std::make_unique<int>(0);
}
if (visible_row_count && (tot_items > *visible_row_count)) {
uiDefButI(block,
UI_BTYPE_SCROLL,
0,
"",
0,
0,
V2D_SCROLL_WIDTH,
*tree_view.custom_height_,
tree_view.scroll_value_.get(),
0,
tot_items - *visible_row_count,
*visible_row_count,
0.0,
"");
}
UI_block_layout_set_current(block, col);
uiDefIconButI(block,
UI_BTYPE_GRIP,
0,
ICON_GRIP,
0,
0,
UI_UNIT_X * 10,
UI_UNIT_Y * 0.5f,
tree_view.custom_height_.get(),
0,
0,
0,
0,
"");
}
UI_block_layout_set_current(block, &parent_layout);
UI_block_layout_set_current(&block(), &parent_layout);
}
void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const
@@ -859,21 +695,16 @@ uiLayout &TreeViewLayoutBuilder::current_layout() const
void TreeViewBuilder::ensure_min_rows_items(AbstractTreeView &tree_view)
{
const std::optional<int> visible_rows = tree_view.tot_visible_row_count();
if (!visible_rows) {
return;
}
int tot_visible_items = 0;
tree_view.foreach_item(
[&tot_visible_items](AbstractTreeViewItem & /*item*/) { tot_visible_items++; },
AbstractTreeView::IterOptions::SkipCollapsed | AbstractTreeView::IterOptions::SkipFiltered);
if (tot_visible_items >= *visible_rows) {
if (tot_visible_items >= tree_view.min_rows_) {
return;
}
for (int i = 0; i < (*visible_rows - tot_visible_items); i++) {
for (int i = 0; i < (tree_view.min_rows_ - tot_visible_items); i++) {
BasicTreeViewItem &new_item = tree_view.add_tree_item<BasicTreeViewItem>("");
new_item.disable_interaction();
}