Mouse hover highlight for grid items

Like the tree-view rows, grid items use an overlapping layout to draw
the background and a custom layout on top. There is a new dedicated
button type for the grid view items.
Adds some related bits needed for persistent view-item state storage.
Also a bunch of code for the grid item button type can be shared with
the tree-row one, but I prefer doing that separately.
This commit is contained in:
Julian Eisel 2022-02-09 14:41:11 +01:00
parent 29fdd43605
commit 400d7235c3
9 changed files with 217 additions and 18 deletions

View File

@ -27,8 +27,10 @@
#include "BLI_vector.hh"
#include "UI_resources.h"
struct bContext;
struct PreviewImage;
struct uiBlock;
struct uiButGridTile;
struct uiLayout;
struct wmNotifier;
@ -41,10 +43,17 @@ class AbstractGridView;
* \{ */
class AbstractGridViewItem {
friend AbstractGridView;
friend class AbstractGridView;
friend class GridViewLayoutBuilder;
const AbstractGridView *view_;
protected:
/** This label is used as the default way to identifying an item in the view. */
std::string label_{};
/** 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;
@ -52,8 +61,20 @@ class AbstractGridViewItem {
const AbstractGridView &get_view() const;
/**
* Compare this item 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.). By default this just matches the item's label. If that isn't good enough for
* a sub-class, that can override it.
*/
virtual bool matches(const AbstractGridViewItem &other) const;
protected:
AbstractGridViewItem() = default;
private:
static void grid_tile_click_fn(bContext *, void *but_arg1, void *);
void add_grid_tile_button(uiBlock &block);
};
/** \} */

View File

@ -94,6 +94,8 @@ typedef struct uiTreeViewHandle uiTreeViewHandle;
typedef struct uiTreeViewItemHandle uiTreeViewItemHandle;
/* C handle for C++ #ui::AbstractGridView type. */
typedef struct uiGridViewHandle uiGridViewHandle;
/* C handle for C++ #ui::AbstractGridViewItem type. */
typedef struct uiGridViewItemHandle uiGridViewItemHandle;
/* Defines */
@ -407,6 +409,8 @@ typedef enum {
UI_BTYPE_DECORATOR = 58 << 9,
/* An item in a tree view. Parent items may be collapsible. */
UI_BTYPE_TREEROW = 59 << 9,
/* An item in a grid view. */
UI_BTYPE_GRID_TILE = 60 << 9,
} eButType;
#define BUTTYPE (63 << 9)
@ -3178,6 +3182,7 @@ void UI_block_views_listen(const uiBlock *block,
const struct wmRegionListenerParams *listener_params);
bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item);
bool UI_grid_view_item_matches(const uiGridViewItemHandle *a, const uiGridViewItemHandle *b);
bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b);
/**
* Attempt to start dragging the tree-item \a item_. This will not work if the tree item doesn't

View File

@ -203,7 +203,7 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
bool is_renaming_ = false;
protected:
/** This label is used for identifying an item within its parent. */
/** This label is used as the default way to identifying an item within its parent. */
std::string label_{};
/** Every visible item gets a button of type #UI_BTYPE_TREEROW during the layout building. */
uiButTreeRow *tree_row_but_ = nullptr;

View File

@ -68,6 +68,44 @@ GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_he
/* ---------------------------------------------------------------------- */
bool AbstractGridViewItem::matches(const AbstractGridViewItem &other) const
{
return label_ == other.label_;
}
void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/,
void *but_arg1,
void * /*arg2*/)
{
uiButGridTile *grid_tile_but = (uiButGridTile *)but_arg1;
AbstractGridViewItem &grid_item = reinterpret_cast<AbstractGridViewItem &>(
*grid_tile_but->view_item);
// tree_item.activate();
}
void AbstractGridViewItem::add_grid_tile_button(uiBlock &block)
{
const GridViewStyle &style = get_view().get_style();
grid_tile_but_ = (uiButGridTile *)uiDefBut(&block,
UI_BTYPE_GRID_TILE,
0,
"",
0,
0,
style.tile_width,
style.tile_height,
nullptr,
0,
0,
0,
0,
"");
grid_tile_but_->view_item = reinterpret_cast<uiGridViewItemHandle *>(this);
UI_but_func_set(&grid_tile_but_->but, grid_tile_click_fn, grid_tile_but_, nullptr);
}
const AbstractGridView &AbstractGridViewItem::get_view() const
{
if (UNLIKELY(!view_)) {
@ -87,7 +125,9 @@ class GridViewLayoutBuilder {
public:
GridViewLayoutBuilder(uiBlock &block);
void build_from_view(const AbstractGridView &grid_view);
void build_from_view(const AbstractGridView &grid_view) const;
void build_grid_tile(uiLayout &grid_layout, AbstractGridViewItem &item) const;
uiLayout *current_layout() const;
};
@ -95,8 +135,19 @@ GridViewLayoutBuilder::GridViewLayoutBuilder(uiBlock &block) : block_(block)
{
}
void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view)
void GridViewLayoutBuilder::build_grid_tile(uiLayout &grid_layout,
AbstractGridViewItem &item) const
{
uiLayout *overlap = uiLayoutOverlap(&grid_layout);
item.add_grid_tile_button(block_);
item.build_grid_tile(*uiLayoutRow(overlap, false));
}
void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view) const
{
uiLayout *prev_layout = current_layout();
uiLayout &layout = *uiLayoutColumn(current_layout(), false);
const GridViewStyle &style = grid_view.get_style();
@ -108,7 +159,7 @@ void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view)
int item_count = 0;
grid_view.foreach_item([&](AbstractGridViewItem &item) {
item.build_grid_tile(*grid_layout);
build_grid_tile(*grid_layout, item);
item_count++;
});
@ -120,6 +171,8 @@ void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view)
uiItemS(grid_layout);
}
}
UI_block_layout_set_current(&block_, prev_layout);
}
uiLayout *GridViewLayoutBuilder::current_layout() const
@ -183,6 +236,9 @@ void PreviewGridItem::build_grid_tile(uiLayout &layout) const
using namespace blender::ui;
/* ---------------------------------------------------------------------- */
/* C-API */
using namespace blender::ui;
bool UI_grid_view_listen_should_redraw(const uiGridViewHandle *view_handle,
const wmNotifier *notifier)
@ -190,3 +246,11 @@ bool UI_grid_view_listen_should_redraw(const uiGridViewHandle *view_handle,
const AbstractGridView &view = *reinterpret_cast<const AbstractGridView *>(view_handle);
return view.listen(*notifier);
}
bool UI_grid_view_item_matches(const uiGridViewItemHandle *a_handle,
const uiGridViewItemHandle *b_handle)
{
const AbstractGridViewItem &a = reinterpret_cast<const AbstractGridViewItem &>(*a_handle);
const AbstractGridViewItem &b = reinterpret_cast<const AbstractGridViewItem &>(*b_handle);
return a.matches(b);
}

View File

@ -781,6 +781,15 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
}
}
if ((but->type == UI_BTYPE_GRID_TILE) && (oldbut->type == UI_BTYPE_GRID_TILE)) {
uiButGridTile *but_gridtile = (uiButGridTile *)but;
uiButGridTile *oldbut_gridtile = (uiButGridTile *)oldbut;
if (!but_gridtile->view_item || !oldbut_gridtile->view_item ||
!UI_grid_view_item_matches(but_gridtile->view_item, oldbut_gridtile->view_item)) {
return false;
}
}
return true;
}
@ -907,6 +916,12 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item);
break;
}
case UI_BTYPE_GRID_TILE: {
uiButGridTile *gridtile_oldbut = (uiButGridTile *)oldbut;
uiButGridTile *gridtile_newbut = (uiButGridTile *)but;
SWAP(uiGridViewItemHandle *, gridtile_newbut->view_item, gridtile_oldbut->view_item);
break;
}
default:
break;
}
@ -996,9 +1011,9 @@ static bool ui_but_update_from_old_block(const bContext *C,
else {
int flag_copy = UI_BUT_DRAG_MULTI;
/* Stupid special case: The active button may be inside (as in, overlapped on top) a tree-row
/* Stupid special case: The active button may be inside (as in, overlapped on top) a view-item
* button which we also want to keep highlighted then. */
if (but->type == UI_BTYPE_TREEROW) {
if (ui_but_is_view_item(but)) {
flag_copy |= UI_ACTIVE;
}
@ -3967,6 +3982,10 @@ static void ui_but_alloc_info(const eButType type,
alloc_size = sizeof(uiButTreeRow);
alloc_str = "uiButTreeRow";
break;
case UI_BTYPE_GRID_TILE:
alloc_size = sizeof(uiButGridTile);
alloc_str = "uiButGridTile";
break;
default:
alloc_size = sizeof(uiBut);
alloc_str = "uiBut";

View File

@ -2312,6 +2312,9 @@ static void ui_apply_but(
case UI_BTYPE_ROW:
ui_apply_but_ROW(C, block, but, data);
break;
case UI_BTYPE_GRID_TILE:
ui_apply_but_ROW(C, block, but, data);
break;
case UI_BTYPE_TREEROW:
ui_apply_but_TREEROW(C, block, but, data);
break;
@ -4840,6 +4843,48 @@ static int ui_do_but_TREEROW(bContext *C,
return WM_UI_HANDLER_CONTINUE;
}
static int ui_do_but_GRIDTILE(bContext *C,
uiBut *but,
uiHandleButtonData *data,
const wmEvent *event)
{
uiButGridTile *grid_tile_but = (uiButGridTile *)but;
BLI_assert(grid_tile_but->but.type == UI_BTYPE_GRID_TILE);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
if (event->type == LEFTMOUSE) {
switch (event->val) {
case KM_PRESS:
/* Extra icons have priority, don't mess with them. */
if (ui_but_extra_operator_icon_mouse_over_get(but, data, event)) {
return WM_UI_HANDLER_BREAK;
}
button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
data->dragstartx = event->xy[0];
data->dragstarty = event->xy[1];
return WM_UI_HANDLER_CONTINUE;
case KM_CLICK:
button_activate_state(C, but, BUTTON_STATE_EXIT);
return WM_UI_HANDLER_BREAK;
case KM_DBL_CLICK:
data->cancel = true;
// UI_tree_view_item_begin_rename(grid_tile_but->tree_item);
printf("rename\n");
ED_region_tag_redraw(CTX_wm_region(C));
return WM_UI_HANDLER_BREAK;
}
}
}
else if (data->state == BUTTON_STATE_WAIT_DRAG) {
/* Let "default" button handling take care of the drag logic. */
return ui_do_but_EXIT(C, but, data, event);
}
return WM_UI_HANDLER_CONTINUE;
}
static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
{
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@ -8017,6 +8062,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
case UI_BTYPE_ROW:
retval = ui_do_but_TOG(C, but, data, event);
break;
case UI_BTYPE_GRID_TILE:
retval = ui_do_but_GRIDTILE(C, but, data, event);
break;
case UI_BTYPE_TREEROW:
retval = ui_do_but_TREEROW(C, but, data, event);
break;
@ -9656,31 +9704,31 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
return retval;
}
static int ui_handle_tree_hover(const wmEvent *event, const ARegion *region)
static int ui_handle_view_items_hover(const wmEvent *event, const ARegion *region)
{
bool has_treerows = false;
bool has_view_item = false;
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
/* Avoid unnecessary work: Tree-rows are assumed to be inside tree-views. */
/* Avoid unnecessary work: view item buttons are assumed to be inside views. */
if (BLI_listbase_is_empty(&block->views)) {
continue;
}
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
if (but->type == UI_BTYPE_TREEROW) {
if (ui_but_is_view_item(but)) {
but->flag &= ~UI_ACTIVE;
has_treerows = true;
has_view_item = true;
}
}
}
if (!has_treerows) {
if (!has_view_item) {
/* Avoid unnecessary lookup. */
return WM_UI_HANDLER_CONTINUE;
}
/* Always highlight the hovered tree-row, even if the mouse hovers another button inside of it.
/* Always highlight the hovered view item, even if the mouse hovers another button inside of it.
*/
uiBut *hovered_row_but = ui_tree_row_find_mouse_over(region, event->xy);
uiBut *hovered_row_but = ui_view_item_find_mouse_over(region, event->xy);
if (hovered_row_but) {
hovered_row_but->flag |= UI_ACTIVE;
}
@ -11288,9 +11336,9 @@ static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(use
ui_blocks_set_tooltips(region, true);
}
/* Always do this, to reliably update tree-row highlighting, even if the mouse hovers a button
* inside the row (it's an overlapping layout). */
ui_handle_tree_hover(event, region);
/* Always do this, to reliably update view item highlighting, even if the mouse hovers a button
* nested in the item (it's an overlapping layout). */
ui_handle_view_items_hover(event, region);
/* delayed apply callbacks */
ui_apply_but_funcs_after(C);

View File

@ -359,6 +359,13 @@ typedef struct uiButTreeRow {
int indentation;
} uiButTreeRow;
/** Derived struct for #UI_BTYPE_GRID_TILE. */
typedef struct uiButGridTile {
uiBut but;
uiGridViewItemHandle *view_item;
} uiButGridTile;
/** Derived struct for #UI_BTYPE_HSVCUBE. */
typedef struct uiButHSVCube {
uiBut but;
@ -1357,6 +1364,7 @@ void ui_but_anim_decorate_update_from_flag(uiButDecorator *but);
bool ui_but_is_editable(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
bool ui_but_is_editable_as_text(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
bool ui_but_is_toggle(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
bool ui_but_is_view_item(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
/**
* Can we mouse over the button or is it hidden/disabled/layout.
* \note ctrl is kind of a hack currently,
@ -1387,6 +1395,8 @@ uiBut *ui_list_row_find_mouse_over(const struct ARegion *region, const int xy[2]
uiBut *ui_list_row_find_from_index(const struct ARegion *region,
int index,
uiBut *listbox) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_view_item_find_mouse_over(const struct ARegion *region, const int xy[2])
ATTR_NONNULL(1, 2);
uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int xy[2])
ATTR_NONNULL(1, 2);
uiBut *ui_tree_row_find_active(const struct ARegion *region);

View File

@ -72,6 +72,11 @@ bool ui_but_is_toggle(const uiBut *but)
UI_BTYPE_TREEROW);
}
bool ui_but_is_view_item(const uiBut *but)
{
return ELEM(but->type, UI_BTYPE_TREEROW, UI_BTYPE_GRID_TILE);
}
bool ui_but_is_interactive(const uiBut *but, const bool labeledit)
{
/* NOTE: #UI_BTYPE_LABEL is included for highlights, this allows drags. */
@ -459,6 +464,16 @@ static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata))
return but->type == UI_BTYPE_TREEROW;
}
static bool ui_but_is_view_item_fn(const uiBut *but, const void *UNUSED(customdata))
{
return ui_but_is_view_item(but);
}
uiBut *ui_view_item_find_mouse_over(const ARegion *region, const int xy[2])
{
return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_view_item_fn, NULL);
}
uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2])
{
return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_treerow, NULL);

View File

@ -121,6 +121,7 @@ typedef enum {
UI_WTYPE_PROGRESSBAR,
UI_WTYPE_NODESOCKET,
UI_WTYPE_TREEROW,
UI_WTYPE_GRID_TILE,
} uiWidgetTypeEnum;
/* Button state argument shares bits with 'uiBut.flag'.
@ -3713,6 +3714,13 @@ static void widget_treerow(
widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation, zoom);
}
static void widget_gridtile(
uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign, const float zoom)
{
/* TODO Reuse tree-row drawing. */
widget_treerow_exec(wcol, rect, state, roundboxalign, 0, zoom);
}
static void widget_nodesocket(uiBut *but,
uiWidgetColors *wcol,
rcti *rect,
@ -4567,6 +4575,10 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type)
wt.custom = widget_treerow;
break;
case UI_WTYPE_GRID_TILE:
wt.draw = widget_gridtile;
break;
case UI_WTYPE_NODESOCKET:
wt.custom = widget_nodesocket;
break;
@ -4903,6 +4915,11 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
fstyle = &style->widgetlabel;
break;
case UI_BTYPE_GRID_TILE:
wt = widget_type(UI_WTYPE_GRID_TILE);
fstyle = &style->widgetlabel;
break;
case UI_BTYPE_SCROLL:
wt = widget_type(UI_WTYPE_SCROLL);
break;