UI: support layout panels in popups

Enables operators that uses `WM_operator_props_dialog_popup`
or `redo` popup to use layout panels.

Other popups would likely also support layout panels, only
they need to set its dummy panel with `UI_popup_dummy_panel_set`.
Popups don't normally use `Panel`, but that's the type that stores the
layout panel states. Therefore, to use layout panels in a popup, one
currently needs to create a dummy panel whose purpose is to store the
layout panel states for as long as the popup is open.

Alternatively, we could potentially also store the layout panel states
somewhere else in the future for popups, but that might be a more involved
change for now.

See #119519 for an example script that uses layout panels in a popup.

Pull Request: https://projects.blender.org/blender/blender/pulls/119519
This commit is contained in:
Guillermo Venegas 2024-03-21 10:31:05 +01:00 committed by Jacques Lucke
parent e22eaf8783
commit aa03646a74
7 changed files with 153 additions and 45 deletions

View File

@ -712,6 +712,21 @@ int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) A
* E.g. WM might need to do this for exiting files correctly.
*/
void UI_popup_menu_retval_set(const uiBlock *block, int retval, bool enable);
/**
* Set a dummy panel in the popup `block` to support using layout panels, the panel is linked
* to the popup `region` so layout panels state can be persistent until the popup is closed.
*/
void UI_popup_dummy_panel_set(ARegion *region, uiBlock *block);
/** Toggles layout panel open state and returns the new state. */
bool UI_layout_panel_toggle_open(const bContext *C, struct LayoutPanelHeader *header);
void UI_panel_drag_collapse_handler_add(const bContext *C, const bool was_open);
LayoutPanelHeader *UI_layout_panel_header_under_mouse(const Panel &panel, const int my);
/** Apply scroll to layout panels when the main panel is used in popups. */
void UI_layout_panel_popup_scroll_apply(Panel *panel, const float dy);
void UI_draw_layout_panels_backdrop(const ARegion *region,
const Panel *panel,
const float radius,
float subpanel_backcolor[4]);
/**
* Setting the button makes the popup open from the button instead of the cursor.
*/

View File

@ -2096,6 +2096,15 @@ void UI_block_draw(const bContext *C, uiBlock *block)
UI_panel_should_show_background(region, block->panel->type),
region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE);
}
/* Shared layout panel backdrop style between redo region and popups. */
if (block->panel && ELEM(region->regiontype, RGN_TYPE_HUD, RGN_TYPE_TEMPORARY)) {
/* TODO: Add as theme color. */
float subpanel_backcolor[4]{0.2f, 0.3f, 0.33f, 0.05f};
const bTheme *btheme = UI_GetTheme();
const float aspect = block->panel->runtime->block->aspect;
const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f / aspect;
UI_draw_layout_panels_backdrop(region, block->panel, radius, subpanel_backcolor);
}
BLF_batch_draw_begin();
UI_icon_draw_cache_begin();

View File

@ -55,6 +55,7 @@
#include "ED_undo.hh"
#include "UI_interface.hh"
#include "UI_interface_c.hh"
#include "UI_string_search.hh"
#include "BLF_api.hh"
@ -10124,6 +10125,8 @@ static void ui_menu_scroll_apply_offset_y(ARegion *region, uiBlock *block, float
/* remember scroll offset for refreshes */
block->handle->scrolloffset += dy;
/* Apply popup scroll delta to layout panels too. */
UI_layout_panel_popup_scroll_apply(block->panel, dy);
/* apply scroll offset */
LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
@ -11428,6 +11431,25 @@ static int ui_handle_menus_recursive(bContext *C,
C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false);
}
}
else if (event->val == KM_PRESS && event->type == LEFTMOUSE) {
LISTBASE_FOREACH (uiBlock *, block, &menu->region->uiblocks) {
if (block->panel) {
int mx = event->xy[0];
int my = event->xy[1];
ui_window_to_block(menu->region, block, &mx, &my);
if (!IN_RANGE(float(mx), block->rect.xmin, block->rect.xmax)) {
break;
}
LayoutPanelHeader *header = UI_layout_panel_header_under_mouse(*block->panel, my);
if (header) {
ED_region_tag_redraw(menu->region);
ED_region_tag_refresh_ui(menu->region);
UI_panel_drag_collapse_handler_add(C, !UI_layout_panel_toggle_open(C, header));
retval = WM_UI_HANDLER_BREAK;
}
}
}
}
/* now handle events for our own menu */
if (retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) {

View File

@ -38,6 +38,7 @@
#include "ED_screen.hh"
#include "UI_interface.hh"
#include "UI_interface_c.hh"
#include "UI_interface_icons.hh"
#include "UI_resources.hh"
#include "UI_view2d.hh"
@ -1162,6 +1163,36 @@ static int layout_panel_y_offset()
return UI_style_get_dpi()->panelspace;
}
void UI_draw_layout_panels_backdrop(const ARegion *region,
const Panel *panel,
const float radius,
float subpanel_backcolor[4])
{
/* Draw backdrops for layout panels. */
for (const LayoutPanelBody &body : panel->runtime->layout_panels.bodies) {
rctf panel_blockspace = panel->runtime->block->rect;
panel_blockspace.ymax = panel->runtime->block->rect.ymax + body.end_y;
panel_blockspace.ymin = panel->runtime->block->rect.ymax + body.start_y;
BLI_rctf_translate(&panel_blockspace, 0, -layout_panel_y_offset());
/* If the layout panel is at the end of the root panel, it's bottom corners are rounded. */
const bool is_main_panel_end = panel_blockspace.ymin - panel->runtime->block->rect.ymin < 10;
if (is_main_panel_end) {
panel_blockspace.ymin = panel->runtime->block->rect.ymin;
UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT);
}
else {
UI_draw_roundbox_corner_set(UI_CNR_NONE);
}
rcti panel_pixelspace = ui_to_pixelrect(region, panel->runtime->block, &panel_blockspace);
rctf panel_pixelspacef;
BLI_rctf_rcti_copy(&panel_pixelspacef, &panel_pixelspace);
UI_draw_roundbox_4fv(&panel_pixelspacef, true, radius, subpanel_backcolor);
}
}
static void panel_draw_aligned_backdrop(const ARegion *region,
const Panel *panel,
const rcti *rect,
@ -1200,31 +1231,9 @@ static void panel_draw_aligned_backdrop(const ARegion *region,
box_rect.ymax = rect->ymax;
UI_draw_roundbox_4fv(&box_rect, true, radius, panel_backcolor);
/* Draw backdrops for layout panels. */
for (const LayoutPanelBody &body : panel->runtime->layout_panels.bodies) {
float subpanel_backcolor[4];
UI_GetThemeColor4fv(TH_PANEL_SUB_BACK, subpanel_backcolor);
rctf panel_blockspace = panel->runtime->block->rect;
panel_blockspace.ymax = panel->runtime->block->rect.ymax + body.end_y;
panel_blockspace.ymin = panel->runtime->block->rect.ymax + body.start_y;
BLI_rctf_translate(&panel_blockspace, 0, -layout_panel_y_offset());
/* If the layout panel is at the end of the root panel, it's bottom corners are rounded. */
const bool is_main_panel_end = panel_blockspace.ymin - panel->runtime->block->rect.ymin < 10;
if (is_main_panel_end) {
panel_blockspace.ymin = panel->runtime->block->rect.ymin;
UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT);
}
else {
UI_draw_roundbox_corner_set(UI_CNR_NONE);
}
rcti panel_pixelspace = ui_to_pixelrect(region, panel->runtime->block, &panel_blockspace);
rctf panel_pixelspacef;
BLI_rctf_rcti_copy(&panel_pixelspacef, &panel_pixelspace);
UI_draw_roundbox_4fv(&panel_pixelspacef, true, radius, subpanel_backcolor);
}
float subpanel_backcolor[4];
UI_GetThemeColor4fv(TH_PANEL_SUB_BACK, subpanel_backcolor);
UI_draw_layout_panels_backdrop(region, panel, radius, subpanel_backcolor);
}
/* Panel header backdrops for non sub-panels. */
@ -1931,7 +1940,7 @@ static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel)
/** \name Region Level Panel Interaction
* \{ */
static LayoutPanelHeader *get_layout_panel_header_under_mouse(const Panel &panel, const int my)
LayoutPanelHeader *UI_layout_panel_header_under_mouse(const Panel &panel, const int my)
{
for (LayoutPanelHeader &header : panel.runtime->layout_panels.headers) {
if (IN_RANGE(float(my - panel.runtime->block->rect.ymax + layout_panel_y_offset()),
@ -1956,7 +1965,7 @@ static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block,
if (IN_RANGE(float(my), block->rect.ymax, block->rect.ymax + PNL_HEADER)) {
return PANEL_MOUSE_INSIDE_HEADER;
}
if (get_layout_panel_header_under_mouse(*panel, my) != nullptr) {
if (UI_layout_panel_header_under_mouse(*panel, my) != nullptr) {
return PANEL_MOUSE_INSIDE_LAYOUT_PANEL_HEADER;
}
@ -1984,8 +1993,10 @@ static void ui_panel_drag_collapse(const bContext *C,
const uiPanelDragCollapseHandle *dragcol_data,
const int xy_dst[2])
{
ARegion *region = CTX_wm_region(C);
ARegion *region = CTX_wm_menu(C);
if (!region) {
region = CTX_wm_region(C);
}
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
float xy_a_block[2] = {float(dragcol_data->xy_init[0]), float(dragcol_data->xy_init[1])};
float xy_b_block[2] = {float(xy_dst[0]), float(xy_dst[1])};
@ -2015,6 +2026,7 @@ static void ui_panel_drag_collapse(const bContext *C,
&header.open_owner_ptr,
RNA_struct_find_property(&header.open_owner_ptr, header.open_prop_name.c_str()));
ED_region_tag_redraw(region);
ED_region_tag_refresh_ui(region);
}
}
@ -2080,7 +2092,7 @@ static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, voi
return retval;
}
static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open)
void UI_panel_drag_collapse_handler_add(const bContext *C, const bool was_open)
{
wmWindow *win = CTX_wm_window(C);
const wmEvent *event = win->eventstate;
@ -2097,32 +2109,32 @@ static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was
eWM_EventHandlerFlag(0));
}
bool UI_layout_panel_toggle_open(const bContext *C, LayoutPanelHeader *header)
{
const bool is_open = RNA_boolean_get(&header->open_owner_ptr, header->open_prop_name.c_str());
RNA_boolean_set(&header->open_owner_ptr, header->open_prop_name.c_str(), !is_open);
RNA_property_update(
const_cast<bContext *>(C),
&header->open_owner_ptr,
RNA_struct_find_property(&header->open_owner_ptr, header->open_prop_name.c_str()));
return !is_open;
}
static void ui_handle_layout_panel_header(
const bContext *C, const uiBlock *block, const int /*mx*/, const int my, const int event_type)
{
Panel *panel = block->panel;
BLI_assert(panel->type != nullptr);
LayoutPanelHeader *header = get_layout_panel_header_under_mouse(*panel, my);
LayoutPanelHeader *header = UI_layout_panel_header_under_mouse(*panel, my);
if (header == nullptr) {
return;
}
const bool is_open = RNA_boolean_get(&header->open_owner_ptr, header->open_prop_name.c_str());
if (is_open) {
RNA_boolean_set(&header->open_owner_ptr, header->open_prop_name.c_str(), false);
}
else {
RNA_boolean_set(&header->open_owner_ptr, header->open_prop_name.c_str(), true);
}
RNA_property_update(
const_cast<bContext *>(C),
&header->open_owner_ptr,
RNA_struct_find_property(&header->open_owner_ptr, header->open_prop_name.c_str()));
const bool new_state = UI_layout_panel_toggle_open(C, header);
ED_region_tag_redraw(CTX_wm_region(C));
if (event_type == LEFTMOUSE) {
ui_panel_drag_collapse_handler_add(C, is_open);
UI_panel_drag_collapse_handler_add(C, !new_state);
}
}
@ -2188,7 +2200,7 @@ static void ui_handle_panel_header(const bContext *C,
SET_FLAG_FROM_TEST(panel->flag, !UI_panel_is_closed(panel), PNL_CLOSED);
if (event_type == LEFTMOUSE) {
ui_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel));
UI_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel));
}
/* Set panel custom data (modifier) active when expanding sub-panels, but not top-level

View File

@ -576,6 +576,38 @@ static void ui_popup_block_remove(bContext *C, uiPopupBlockHandle *handle)
}
}
void UI_layout_panel_popup_scroll_apply(Panel *panel, const float dy)
{
if (!panel || dy == 0.0f) {
return;
}
for (LayoutPanelBody &body : panel->runtime->layout_panels.bodies) {
body.start_y += dy;
body.end_y += dy;
}
for (LayoutPanelHeader &headcer : panel->runtime->layout_panels.headers) {
headcer.start_y += dy;
headcer.end_y += dy;
}
}
void UI_popup_dummy_panel_set(ARegion *region, uiBlock *block)
{
Panel *&panel = region->runtime.popup_block_panel;
if (!panel) {
/* Dummy popup panel type. */
static PanelType panel_type = []() {
PanelType type{};
type.flag = PANEL_TYPE_NO_HEADER;
return type;
}();
panel = BKE_panel_new(&panel_type);
}
panel->runtime->layout_panels.clear();
block->panel = panel;
panel->runtime->block = block;
}
uiBlock *ui_popup_block_refresh(bContext *C,
uiPopupBlockHandle *handle,
ARegion *butregion,
@ -753,7 +785,14 @@ uiBlock *ui_popup_block_refresh(bContext *C,
region->winrct.ymax = block->rect.ymax + UI_POPUP_MENU_TOP;
UI_block_translate(block, -region->winrct.xmin, -region->winrct.ymin);
/* Popups can change size, fix scroll offset if a panel was closed. */
float ymin = FLT_MAX;
LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
ymin = min_ff(ymin, bt->rect.ymin);
}
handle->scrolloffset = std::clamp<float>(
handle->scrolloffset, 0.0f, std::max<float>(block->rect.ymin - ymin, 0.0f));
/* apply scroll offset */
if (handle->scrolloffset != 0.0f) {
LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
@ -762,6 +801,8 @@ uiBlock *ui_popup_block_refresh(bContext *C,
}
}
}
/* Apply popup scroll offset to layout panels. */
UI_layout_panel_popup_scroll_apply(block->panel, handle->scrolloffset);
if (block_old) {
block->oldblock = block_old;
@ -875,6 +916,10 @@ void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
handle->popup_create_vars.arg_free(handle->popup_create_vars.arg);
}
if (handle->region->runtime.popup_block_panel) {
BKE_panel_free(handle->region->runtime.popup_block_panel);
}
ui_popup_block_remove(C, handle);
MEM_freeN(handle);

View File

@ -454,6 +454,9 @@ typedef struct ARegion_Runtime {
/** Maps #uiBlock::name to uiBlock for faster lookups. */
struct GHash *block_name_map;
/* Dummy panel used in popups so they can support layout panels. */
Panel *popup_block_panel;
} ARegion_Runtime;
typedef struct ARegion {

View File

@ -1391,6 +1391,7 @@ static uiBlock *wm_block_create_redo(bContext *C, ARegion *region, void *arg_op)
BLI_assert(op->type->flag & OPTYPE_REGISTER);
UI_block_func_handle_set(block, wm_block_redo_cb, arg_op);
UI_popup_dummy_panel_set(region, block);
uiLayout *layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, width, UI_UNIT_Y, 0, style);
@ -1476,6 +1477,7 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
UI_block_flag_disable(block, UI_BLOCK_LOOP);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
UI_popup_dummy_panel_set(region, block);
if (data->mouse_move_quit) {
UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);