UI: Tree-view scrolling and resizing support
Implements scrolling support for tree-views, as well as changing the size of the
scroll-able tree-view. This is important to be able to place them in many places
in the UI, where a compact layout is preferred over an every expanding one
(causing following contents to be scrolled out of view). UI-lists would use
scrolling and resizing to ensure this, now tree-views are on par.
Enables scrolling and resizing for:
- Bone collection UI (`UILayout.template_bone_collection_tree()`)
- Grease Pencil layer UI (`UILayout.template_grease_pencil_layer_tree()`)
- Light link collection UI (`UILayout.template_light_linking_collection()`)
- UI to define a node tree interface (`UILayout.template_node_tree_interface()`)
These are all cases where compact UIs make more sense than expanding ones.
Internally this is enabled by calling the `set_default_rows()` method of the
tree-view, although the API might change still. It shouldn't be quite simple to
implement this for grid-views too if necessary, or other potential view types.
Although I'd like to do some smaller code quality improvements still, this
feature is important for some other modules (e.g. grease pencil module for the
layers UI in grease pencil v3), so I decided to prioritize merging this.
Pull Request: https://projects.blender.org/blender/blender/pulls/119668
(cherry picked from commit 78e330923d)
This commit is contained in:
@@ -1020,6 +1020,8 @@ def km_user_interface(_params):
|
|||||||
("ui.list_start_filter", {"type": 'F', "value": 'PRESS', "ctrl": True}, None),
|
("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 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_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
|
return keymap
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ namespace blender::ui {
|
|||||||
class AbstractViewItem;
|
class AbstractViewItem;
|
||||||
class AbstractViewItemDragController;
|
class AbstractViewItemDragController;
|
||||||
|
|
||||||
|
enum class ViewScrollDirection {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
};
|
||||||
|
|
||||||
class AbstractView {
|
class AbstractView {
|
||||||
friend class AbstractViewItem;
|
friend class AbstractViewItem;
|
||||||
friend struct ::ViewLink;
|
friend struct ::ViewLink;
|
||||||
@@ -86,10 +91,13 @@ class AbstractView {
|
|||||||
*/
|
*/
|
||||||
virtual bool begin_filtering(const bContext &C) const;
|
virtual bool begin_filtering(const bContext &C) const;
|
||||||
|
|
||||||
virtual void draw_overlays(const ARegion ®ion) const;
|
virtual void draw_overlays(const ARegion ®ion, const uiBlock &block) const;
|
||||||
|
|
||||||
virtual void foreach_view_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const = 0;
|
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
|
* Makes \a item valid for display in this view. Behavior is undefined for items not registered
|
||||||
* with this.
|
* with this.
|
||||||
|
|||||||
@@ -112,29 +112,36 @@ using TreeViewOrItem = TreeViewItemContainer;
|
|||||||
* \{ */
|
* \{ */
|
||||||
|
|
||||||
class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
|
class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
|
||||||
int min_rows_ = 0;
|
/* 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;
|
||||||
|
|
||||||
friend class AbstractTreeViewItem;
|
friend class AbstractTreeViewItem;
|
||||||
friend class TreeViewBuilder;
|
friend class TreeViewBuilder;
|
||||||
|
friend class TreeViewLayoutBuilder;
|
||||||
friend class TreeViewItemDropTarget;
|
friend class TreeViewItemDropTarget;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* virtual */ ~AbstractTreeView() override = default;
|
/* virtual */ ~AbstractTreeView() override = default;
|
||||||
|
|
||||||
void draw_overlays(const ARegion ®ion) const override;
|
void draw_overlays(const ARegion ®ion, const uiBlock &block) const override;
|
||||||
|
|
||||||
void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
|
void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
|
||||||
|
|
||||||
|
void scroll(ViewScrollDirection direction) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \param xy: The mouse coordinates in window space.
|
* \param xy: The mouse coordinates in window space.
|
||||||
*/
|
*/
|
||||||
AbstractTreeViewItem *find_hovered(const ARegion ®ion, const int2 &xy);
|
AbstractTreeViewItem *find_hovered(const ARegion ®ion, const int2 &xy);
|
||||||
|
|
||||||
/** Visual feature: Define a number of item rows the view will always show at minimum. If there
|
/** Visual feature: Define a number of item rows the view will show by default. If there
|
||||||
* are fewer items, empty dummy items will be added. These contribute to the view bounds, so the
|
* 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
|
* drop target of the view includes them, but they are not interactive (e.g. no mouse-hover
|
||||||
* highlight). */
|
* highlight). */
|
||||||
void set_min_rows(int min_rows);
|
void set_default_rows(int min_rows);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void build_tree() = 0;
|
virtual void build_tree() = 0;
|
||||||
@@ -146,14 +153,18 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
|
|||||||
const TreeViewOrItem &old_items);
|
const TreeViewOrItem &old_items);
|
||||||
static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item,
|
static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item,
|
||||||
const TreeViewOrItem &items);
|
const TreeViewOrItem &items);
|
||||||
|
std::optional<int> tot_visible_row_count() const;
|
||||||
|
|
||||||
void draw_hierarchy_lines(const ARegion ®ion) const;
|
bool supports_scrolling() const override;
|
||||||
void draw_hierarchy_lines_recursive(const ARegion ®ion,
|
|
||||||
const TreeViewOrItem &parent,
|
|
||||||
const uint pos,
|
|
||||||
const float aspect) const;
|
|
||||||
|
|
||||||
AbstractTreeViewItem *find_last_visible_descendant(const AbstractTreeViewItem &parent) const;
|
void draw_hierarchy_lines(const ARegion ®ion, const uiBlock &block) const;
|
||||||
|
void get_hierarchy_lines(const ARegion ®ion,
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
#include "RNA_types.hh"
|
#include "RNA_types.hh"
|
||||||
|
|
||||||
#include "UI_interface.hh"
|
#include "UI_interface.hh"
|
||||||
|
#include "UI_abstract_view.hh"
|
||||||
|
|
||||||
#include "interface_intern.hh"
|
#include "interface_intern.hh"
|
||||||
|
|
||||||
@@ -2403,18 +2404,23 @@ static void UI_OT_list_start_filter(wmOperatorType *ot)
|
|||||||
/** \name UI View Start Filter Operator
|
/** \name UI View Start Filter Operator
|
||||||
* \{ */
|
* \{ */
|
||||||
|
|
||||||
static bool ui_view_focused_poll(bContext *C)
|
static AbstractView *get_view_focused(bContext *C)
|
||||||
{
|
{
|
||||||
const wmWindow *win = CTX_wm_window(C);
|
const wmWindow *win = CTX_wm_window(C);
|
||||||
if (!(win && win->eventstate)) {
|
if (!(win && win->eventstate)) {
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ARegion *region = CTX_wm_region(C);
|
const ARegion *region = CTX_wm_region(C);
|
||||||
if (!region) {
|
if (!region) {
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const uiViewHandle *view = UI_region_view_find_at(region, win->eventstate->xy, 0);
|
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);
|
||||||
return view != nullptr;
|
return view != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2495,6 +2501,72 @@ 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
|
/** \name UI View Item Rename Operator
|
||||||
*
|
*
|
||||||
@@ -2638,6 +2710,7 @@ void ED_operatortypes_ui()
|
|||||||
|
|
||||||
WM_operatortype_append(UI_OT_view_start_filter);
|
WM_operatortype_append(UI_OT_view_start_filter);
|
||||||
WM_operatortype_append(UI_OT_view_drop);
|
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_view_item_rename);
|
||||||
|
|
||||||
WM_operatortype_append(UI_OT_override_type_set_button);
|
WM_operatortype_append(UI_OT_override_type_set_button);
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ void uiTemplateBoneCollectionTree(uiLayout *layout, bContext *C)
|
|||||||
*block,
|
*block,
|
||||||
"Bone Collection Tree View",
|
"Bone Collection Tree View",
|
||||||
std::make_unique<blender::ui::bonecollections::BoneCollectionTreeView>(*armature));
|
std::make_unique<blender::ui::bonecollections::BoneCollectionTreeView>(*armature));
|
||||||
tree_view->set_min_rows(3);
|
tree_view->set_default_rows(3);
|
||||||
|
|
||||||
ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ void uiTemplateGreasePencilLayerTree(uiLayout *layout, bContext *C)
|
|||||||
*block,
|
*block,
|
||||||
"Grease Pencil Layer Tree View",
|
"Grease Pencil Layer Tree View",
|
||||||
std::make_unique<blender::ui::greasepencil::LayerTreeView>(grease_pencil));
|
std::make_unique<blender::ui::greasepencil::LayerTreeView>(grease_pencil));
|
||||||
tree_view->set_min_rows(3);
|
tree_view->set_default_rows(3);
|
||||||
|
|
||||||
ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ void uiTemplateLightLinkingCollection(uiLayout *layout,
|
|||||||
*block,
|
*block,
|
||||||
"Light Linking Collection Tree View",
|
"Light Linking Collection Tree View",
|
||||||
std::make_unique<blender::ui::light_linking::CollectionView>(*context_layout, *collection));
|
std::make_unique<blender::ui::light_linking::CollectionView>(*context_layout, *collection));
|
||||||
tree_view->set_min_rows(3);
|
tree_view->set_default_rows(3);
|
||||||
|
|
||||||
blender::ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
blender::ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ void uiTemplateNodeTreeInterface(uiLayout *layout, PointerRNA *ptr)
|
|||||||
*block,
|
*block,
|
||||||
"Node Tree Declaration Tree View",
|
"Node Tree Declaration Tree View",
|
||||||
std::make_unique<blender::ui::nodes::NodeTreeInterfaceView>(nodetree, interface));
|
std::make_unique<blender::ui::nodes::NodeTreeInterfaceView>(nodetree, interface));
|
||||||
tree_view->set_min_rows(3);
|
tree_view->set_default_rows(3);
|
||||||
|
|
||||||
blender::ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
blender::ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,11 +111,21 @@ bool AbstractView::begin_filtering(const bContext & /*C*/) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractView::draw_overlays(const ARegion & /*region*/) const
|
void AbstractView::draw_overlays(const ARegion & /*region*/, const uiBlock & /*block*/) const
|
||||||
{
|
{
|
||||||
/* Nothing by default. */
|
/* 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)
|
void ui_block_views_draw_overlays(const ARegion *region, const uiBlock *block)
|
||||||
{
|
{
|
||||||
LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
|
LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
|
||||||
view_link->view->draw_overlays(*region);
|
view_link->view->draw_overlays(*region, *block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "interface_intern.hh"
|
#include "interface_intern.hh"
|
||||||
|
|
||||||
#include "UI_interface.hh"
|
#include "UI_interface.hh"
|
||||||
|
#include "UI_view2d.hh"
|
||||||
|
|
||||||
#include "WM_api.hh"
|
#include "WM_api.hh"
|
||||||
#include "WM_types.hh"
|
#include "WM_types.hh"
|
||||||
@@ -115,74 +116,119 @@ AbstractTreeViewItem *AbstractTreeView::find_hovered(const ARegion ®ion, cons
|
|||||||
return hovered_item;
|
return hovered_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractTreeView::set_min_rows(int min_rows)
|
void AbstractTreeView::set_default_rows(int default_rows)
|
||||||
{
|
{
|
||||||
min_rows_ = min_rows;
|
custom_height_ = std::make_unique<int>(default_rows * padded_item_height());
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractTreeViewItem *AbstractTreeView::find_last_visible_descendant(
|
int AbstractTreeView::count_visible_descendants(const AbstractTreeViewItem &parent) const
|
||||||
const AbstractTreeViewItem &parent) const
|
|
||||||
{
|
{
|
||||||
if (parent.is_collapsed()) {
|
if (parent.is_collapsed()) {
|
||||||
return nullptr;
|
return 0;
|
||||||
|
}
|
||||||
|
int count = 0;
|
||||||
|
for (const auto &item : parent.children_) {
|
||||||
|
if (!item->is_filtered_visible()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
count += count_visible_descendants(*item);
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractTreeViewItem *last_descendant = parent.children_.last().get();
|
return count;
|
||||||
while (!last_descendant->children_.is_empty() && !last_descendant->is_collapsed()) {
|
|
||||||
last_descendant = last_descendant->children_.last().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return last_descendant;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractTreeView::draw_hierarchy_lines_recursive(const ARegion ®ion,
|
|
||||||
const TreeViewOrItem &parent,
|
void AbstractTreeView::get_hierarchy_lines(const ARegion ®ion,
|
||||||
const uint pos,
|
const TreeViewOrItem &parent,
|
||||||
const float aspect) const
|
const float aspect,
|
||||||
|
Vector<std::pair<int2, int2>> &lines,
|
||||||
|
int &visible_item_index) 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_) {
|
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()) {
|
if (!item->is_collapsible() || item->is_collapsed()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_hierarchy_lines_recursive(region, *item, pos, aspect);
|
/* Draw a hierarchy line for the descendants of this item. */
|
||||||
|
|
||||||
const AbstractTreeViewItem *first_descendant = item->children_.first().get();
|
const AbstractTreeViewItem *first_descendant = item->children_.first().get();
|
||||||
const AbstractTreeViewItem *last_descendant = find_last_visible_descendant(*item);
|
const int descendant_count = count_visible_descendants(*item);
|
||||||
if (!first_descendant->view_item_but_ || !last_descendant || !last_descendant->view_item_but_)
|
|
||||||
|
const int first_descendant_index = item_index + 1;
|
||||||
|
const int last_descendant_index = first_descendant_index + descendant_count;
|
||||||
|
|
||||||
{
|
{
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const uiButViewItem &first_child_but = *first_descendant->view_item_button();
|
|
||||||
const uiButViewItem &last_child_but = *last_descendant->view_item_button();
|
|
||||||
|
|
||||||
BLI_assert(first_child_but.block == last_child_but.block);
|
const int x = ((first_descendant->indent_width() + (5 * UI_SCALE_FAC) -
|
||||||
const uiBlock *block = first_child_but.block;
|
(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)));
|
||||||
|
|
||||||
rcti first_child_rect;
|
this->get_hierarchy_lines(region, *item, aspect, lines, visible_item_index);
|
||||||
ui_but_to_pixelrect(&first_child_rect, ®ion, block, &first_child_but);
|
|
||||||
rcti last_child_rect;
|
|
||||||
ui_but_to_pixelrect(&last_child_rect, ®ion, 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractTreeView::draw_hierarchy_lines(const ARegion ®ion) const
|
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 ®ion, const uiBlock &block) const
|
||||||
{
|
{
|
||||||
const float aspect = (region.v2d.flag & V2D_IS_INIT) ?
|
const float aspect = (region.v2d.flag & V2D_IS_INIT) ?
|
||||||
BLI_rctf_size_y(®ion.v2d.cur) /
|
BLI_rctf_size_y(®ion.v2d.cur) /
|
||||||
(BLI_rcti_size_y(®ion.v2d.mask) + 1) :
|
(BLI_rcti_size_y(®ion.v2d.mask) + 1) :
|
||||||
1.0f;
|
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();
|
GPUVertFormat *format = immVertexFormat();
|
||||||
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
||||||
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
|
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
|
||||||
@@ -190,22 +236,34 @@ void AbstractTreeView::draw_hierarchy_lines(const ARegion ®ion) const
|
|||||||
|
|
||||||
GPU_line_width(1.0f / aspect);
|
GPU_line_width(1.0f / aspect);
|
||||||
GPU_blend(GPU_BLEND_ALPHA);
|
GPU_blend(GPU_BLEND_ALPHA);
|
||||||
draw_hierarchy_lines_recursive(region, *this, pos, aspect);
|
|
||||||
|
rcti first_item_but_pixel_rect;
|
||||||
|
ui_but_to_pixelrect(&first_item_but_pixel_rect, ®ion, &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();
|
||||||
|
}
|
||||||
GPU_blend(GPU_BLEND_NONE);
|
GPU_blend(GPU_BLEND_NONE);
|
||||||
|
|
||||||
immUnbindProgram();
|
immUnbindProgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractTreeView::draw_overlays(const ARegion ®ion) const
|
void AbstractTreeView::draw_overlays(const ARegion ®ion, const uiBlock &block) const
|
||||||
{
|
{
|
||||||
draw_hierarchy_lines(region);
|
draw_hierarchy_lines(region, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
|
void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
|
||||||
{
|
{
|
||||||
const AbstractTreeView &old_tree_view = dynamic_cast<const AbstractTreeView &>(old_view);
|
const AbstractTreeView &old_tree_view = dynamic_cast<const AbstractTreeView &>(old_view);
|
||||||
|
|
||||||
update_children_from_old_recursive(*this, old_tree_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
|
void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
|
||||||
@@ -237,6 +295,31 @@ AbstractTreeViewItem *AbstractTreeView::find_matching_child(
|
|||||||
return nullptr;
|
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,
|
TreeViewItemDropTarget::TreeViewItemDropTarget(AbstractTreeViewItem &view_item,
|
||||||
@@ -613,7 +696,7 @@ class TreeViewLayoutBuilder {
|
|||||||
friend TreeViewBuilder;
|
friend TreeViewBuilder;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void build_from_tree(const AbstractTreeView &tree_view);
|
void build_from_tree(AbstractTreeView &tree_view);
|
||||||
void build_row(AbstractTreeViewItem &item) const;
|
void build_row(AbstractTreeViewItem &item) const;
|
||||||
|
|
||||||
uiBlock &block() const;
|
uiBlock &block() const;
|
||||||
@@ -628,18 +711,99 @@ TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiLayout &layout) : block_(*uiLayou
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view)
|
static int count_visible_items(AbstractTreeView &tree_view)
|
||||||
{
|
{
|
||||||
uiLayout &parent_layout = current_layout();
|
int item_count = 0;
|
||||||
|
tree_view.foreach_item([&](AbstractTreeViewItem &) { item_count++; },
|
||||||
uiLayout *box = uiLayoutBox(&parent_layout);
|
|
||||||
uiLayoutColumn(box, true);
|
|
||||||
|
|
||||||
tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); },
|
|
||||||
AbstractTreeView::IterOptions::SkipCollapsed |
|
AbstractTreeView::IterOptions::SkipCollapsed |
|
||||||
AbstractTreeView::IterOptions::SkipFiltered);
|
AbstractTreeView::IterOptions::SkipFiltered);
|
||||||
|
return item_count;
|
||||||
|
}
|
||||||
|
|
||||||
UI_block_layout_set_current(&block(), &parent_layout);
|
void TreeViewLayoutBuilder::build_from_tree(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);
|
||||||
|
|
||||||
|
const std::optional<int> visible_row_count = tree_view.tot_visible_row_count();
|
||||||
|
const int tot_items = count_visible_items(tree_view);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const
|
void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const
|
||||||
@@ -695,16 +859,21 @@ uiLayout &TreeViewLayoutBuilder::current_layout() const
|
|||||||
|
|
||||||
void TreeViewBuilder::ensure_min_rows_items(AbstractTreeView &tree_view)
|
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;
|
int tot_visible_items = 0;
|
||||||
tree_view.foreach_item(
|
tree_view.foreach_item(
|
||||||
[&tot_visible_items](AbstractTreeViewItem & /*item*/) { tot_visible_items++; },
|
[&tot_visible_items](AbstractTreeViewItem & /*item*/) { tot_visible_items++; },
|
||||||
AbstractTreeView::IterOptions::SkipCollapsed | AbstractTreeView::IterOptions::SkipFiltered);
|
AbstractTreeView::IterOptions::SkipCollapsed | AbstractTreeView::IterOptions::SkipFiltered);
|
||||||
|
|
||||||
if (tot_visible_items >= tree_view.min_rows_) {
|
if (tot_visible_items >= *visible_rows) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < (tree_view.min_rows_ - tot_visible_items); i++) {
|
for (int i = 0; i < (*visible_rows - tot_visible_items); i++) {
|
||||||
BasicTreeViewItem &new_item = tree_view.add_tree_item<BasicTreeViewItem>("");
|
BasicTreeViewItem &new_item = tree_view.add_tree_item<BasicTreeViewItem>("");
|
||||||
new_item.disable_interaction();
|
new_item.disable_interaction();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user