Fix API design issue with activating UI view items

The `on_activate()` function of an item should only be called when the
item was activated through the view, not through an external data change
(e.g. changing an active item through Python). That is important because
`on_activate()` is expected to do things like sending an undo push. This
is now respected in tree and grid views.
This commit is contained in:
Julian Eisel
2023-07-26 15:32:26 +02:00
parent add3ec71d2
commit dedd55df33
4 changed files with 65 additions and 21 deletions

View File

@@ -56,11 +56,17 @@ class AbstractGridViewItem : public AbstractViewItem {
/** See AbstractViewItem::matches(). */
/* virtual */ bool matches(const AbstractViewItem &other) const override;
/** Called when the item's state changes from inactive to active. */
/**
* Called when the grid view changes an item's state from inactive to active. Will only be called
* if the state change is triggered through the grid view, not through external changes. E.g. a
* click on an item calls it, a change in the value returned by #should_be_active() to reflect an
* external state change does not.
*/
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.
* 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. Note that since this is meant to reflect
* externally managed state changes, #on_activate() will never be called if this returns true.
*/
virtual std::optional<bool> should_be_active() const;
@@ -69,7 +75,10 @@ class AbstractGridViewItem : public AbstractViewItem {
/**
* Activates this item, deactivates other items, and calls the
* #AbstractGridViewItem::on_activate() function.
* #AbstractGridViewItem::on_activate() function. Should only be called when the item was
* activated through the grid view (e.g. through a click), not if the grid view reflects an
* external change (e.g. #AbstractGridViewItem::should_be_active() changes from returning false
* to returning true).
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise the
* actual item state is unknown, possibly calling state-change update functions incorrectly.
*/
@@ -79,6 +88,12 @@ class AbstractGridViewItem : public AbstractViewItem {
private:
/** See #AbstractTreeView::change_state_delayed() */
void change_state_delayed();
/**
* Like #activate() but does not call #on_activate(). Use it to reflect changes in the active
* state that happened externally.
* \return true of the item was activated.
*/
bool set_state_active();
static void grid_tile_click_fn(bContext *, void *but_arg1, void *);
void add_grid_tile_button(uiBlock &block);
};

View File

@@ -210,12 +210,16 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain
protected:
/**
* Called when the items state changes from inactive to active.
* Called when the tree view changes an item's state from inactive to active. Will only be called
* if the state change is triggered through the tree view, not through external changes. E.g. a
* click on an item calls it, a change in the value returned by #should_be_active() to reflect an
* external state change does not.
*/
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.
* 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. Note that since this is meant to reflect
* externally managed state changes, #on_activate() will never be called if this returns true.
*/
virtual std::optional<bool> should_be_active() const;
@@ -250,7 +254,10 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain
/**
* Activates this item, deactivates other items, calls the #AbstractTreeViewItem::on_activate()
* function and ensures this item's parents are not collapsed (so the item is visible).
* function and ensures this item's parents are not collapsed (so the item is visible). Should
* only be called when the item was activated through the tree view (e.g. through a click), not
* if the tree view reflects an external change (e.g. #AbstractTreeViewItem::should_be_active()
* changes from returning false to returning true).
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise the
* actual item state is unknown, possibly calling state-change update functions incorrectly.
*/
@@ -273,6 +280,12 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain
/** See #AbstractTreeView::change_state_delayed() */
void change_state_delayed();
/**
* Like #activate() but does not call #on_activate(). Use it to reflect changes in the active
* state that happened externally.
* \return true of the item was activated.
*/
bool set_state_active();
void add_treerow_button(uiBlock &block);
int indent_width() const;

View File

@@ -186,28 +186,36 @@ void AbstractGridViewItem::change_state_delayed()
{
const std::optional<bool> should_be_active = this->should_be_active();
if (should_be_active.has_value() && *should_be_active) {
activate();
/* Don't call #activate() here, since this reflects an external state change and therefore
* shouldn't call #on_activate(). */
set_state_active();
}
}
void AbstractGridViewItem::activate()
bool AbstractGridViewItem::set_state_active()
{
BLI_assert_msg(get_view().is_reconstructed(),
"Item activation can't be done until reconstruction is completed");
if (!is_activatable_) {
return;
return false;
}
if (is_active()) {
return;
return false;
}
/* Deactivate other items in the tree. */
/* Deactivate other items in the view. */
get_view().foreach_item([](auto &item) { item.deactivate(); });
on_activate();
is_active_ = true;
return true;
}
void AbstractGridViewItem::activate()
{
if (set_state_active()) {
on_activate();
}
}
void AbstractGridViewItem::deactivate()

View File

@@ -494,26 +494,32 @@ int AbstractTreeViewItem::count_parents() const
return i;
}
void AbstractTreeViewItem::activate()
bool AbstractTreeViewItem::set_state_active()
{
BLI_assert_msg(get_tree_view().is_reconstructed(),
"Item activation can't be done until reconstruction is completed");
if (!is_activatable_) {
return;
return false;
}
if (is_active()) {
return;
return false;
}
/* Deactivate other items in the tree. */
get_tree_view().foreach_item([](auto &item) { item.deactivate(); });
on_activate();
/* Make sure the active item is always visible. */
ensure_parents_uncollapsed();
is_active_ = true;
return true;
}
void AbstractTreeViewItem::activate()
{
if (set_state_active()) {
on_activate();
}
}
void AbstractTreeViewItem::deactivate()
@@ -595,7 +601,9 @@ void AbstractTreeViewItem::change_state_delayed()
{
const std::optional<bool> should_be_active = this->should_be_active();
if (should_be_active.has_value() && *should_be_active) {
activate();
/* Don't call #activate() here, since this reflects an external state change and therefore
* shouldn't call #on_activate(). */
set_state_active();
}
}