Python: add Python API for layout panels
This adds a Python API for layout panels that have been introduced in #113584. Two new methods on `UILayout` are added: * `.panel(idname, text="...", default_closed=False) -> Optional[UILayout]` * `.panel_prop(owner, prop_name, text="...") -> Optional[UILayout]` Both create a panel and return `None` if the panel is collapsed. The difference lies in how the open-close-state is stored. The first method internally manages the open-close-state based on the provided identifier. The second one allows for providing a boolean property that stores whether the panel is open. This is useful when creating a dynamic of panels and when it is difficult to create a unique idname. For the `.panel(...)` method, a new internal map on `Panel` is created which keeps track of all the panel states based on the idname. Currently, there is no mechanism for freeing any elements once they have been added to the map. This is unlikely to cause a problem anytime soon, but we might need some kind of garbage collection in the future. ```python import bpy from bpy.props import BoolProperty class LayoutDemoPanel(bpy.types.Panel): bl_label = "Layout Panel Demo" bl_idname = "SCENE_PT_layout_panel" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "scene" def draw(self, context): layout = self.layout scene = context.scene layout.label(text="Before") if panel := layout.panel("my_panel_id", text="Hello World", default_closed=False): panel.label(text="Success") if panel := layout.panel_prop(scene, "show_demo_panel", text="My Panel"): panel.prop(scene, "frame_start") panel.prop(scene, "frame_end") layout.label(text="After") bpy.utils.register_class(LayoutDemoPanel) bpy.types.Scene.show_demo_panel = BoolProperty(default=False) ``` Pull Request: https://projects.blender.org/blender/blender/pulls/116949
This commit is contained in:
@@ -27,6 +27,7 @@ struct BlendWriter;
|
||||
struct Header;
|
||||
struct ID;
|
||||
struct IDRemapper;
|
||||
struct LayoutPanelState;
|
||||
struct LibraryForeachIDData;
|
||||
struct ListBase;
|
||||
struct Menu;
|
||||
@@ -606,6 +607,14 @@ void BKE_screen_area_free(ScrArea *area);
|
||||
void BKE_region_callback_free_gizmomap_set(void (*callback)(wmGizmoMap *));
|
||||
void BKE_region_callback_refresh_tag_gizmomap_set(void (*callback)(wmGizmoMap *));
|
||||
|
||||
/**
|
||||
* Get the layout panel state for the given idname. If it does not exist yet, initialize a new
|
||||
* panel state with the given default value.
|
||||
*/
|
||||
LayoutPanelState *BKE_panel_layout_panel_state_ensure(Panel *panel,
|
||||
const char *idname,
|
||||
bool default_closed);
|
||||
|
||||
/**
|
||||
* Find a region of type \a region_type in provided \a regionbase.
|
||||
*
|
||||
|
||||
@@ -323,6 +323,14 @@ static void panel_list_copy(ListBase *newlb, const ListBase *lb)
|
||||
new_panel->runtime = new_runtime;
|
||||
new_panel->activedata = nullptr;
|
||||
new_panel->drawname = nullptr;
|
||||
|
||||
BLI_listbase_clear(&new_panel->layout_panel_states);
|
||||
LISTBASE_FOREACH (LayoutPanelState *, src_state, &old_panel->layout_panel_states) {
|
||||
LayoutPanelState *new_state = MEM_new<LayoutPanelState>(__func__, *src_state);
|
||||
new_state->idname = BLI_strdup(src_state->idname);
|
||||
BLI_addtail(&new_panel->layout_panel_states, new_state);
|
||||
}
|
||||
|
||||
BLI_addtail(newlb, new_panel);
|
||||
panel_list_copy(&new_panel->children, &old_panel->children);
|
||||
}
|
||||
@@ -487,6 +495,22 @@ void BKE_region_callback_free_gizmomap_set(void (*callback)(wmGizmoMap *))
|
||||
region_free_gizmomap_callback = callback;
|
||||
}
|
||||
|
||||
LayoutPanelState *BKE_panel_layout_panel_state_ensure(Panel *panel,
|
||||
const char *idname,
|
||||
const bool default_closed)
|
||||
{
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
if (STREQ(state->idname, idname)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
LayoutPanelState *state = MEM_cnew<LayoutPanelState>(__func__);
|
||||
state->idname = BLI_strdup(idname);
|
||||
SET_FLAG_FROM_TEST(state->flag, !default_closed, LAYOUT_PANEL_STATE_FLAG_OPEN);
|
||||
BLI_addtail(&panel->layout_panel_states, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
Panel *BKE_panel_new(PanelType *panel_type)
|
||||
{
|
||||
Panel *panel = MEM_cnew<Panel>(__func__);
|
||||
@@ -502,6 +526,12 @@ void BKE_panel_free(Panel *panel)
|
||||
{
|
||||
MEM_SAFE_FREE(panel->activedata);
|
||||
MEM_SAFE_FREE(panel->drawname);
|
||||
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
MEM_freeN(state->idname);
|
||||
}
|
||||
BLI_freelistN(&panel->layout_panel_states);
|
||||
|
||||
MEM_delete(panel->runtime);
|
||||
MEM_freeN(panel);
|
||||
}
|
||||
@@ -1054,6 +1084,10 @@ static void write_panel_list(BlendWriter *writer, ListBase *lb)
|
||||
{
|
||||
LISTBASE_FOREACH (Panel *, panel, lb) {
|
||||
BLO_write_struct(writer, Panel, panel);
|
||||
BLO_write_struct_list(writer, LayoutPanelState, &panel->layout_panel_states);
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
BLO_write_string(writer, state->idname);
|
||||
}
|
||||
write_panel_list(writer, &panel->children);
|
||||
}
|
||||
}
|
||||
@@ -1116,6 +1150,10 @@ static void direct_link_panel_list(BlendDataReader *reader, ListBase *lb)
|
||||
panel->activedata = nullptr;
|
||||
panel->type = nullptr;
|
||||
panel->drawname = nullptr;
|
||||
BLO_read_list(reader, &panel->layout_panel_states);
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
BLO_read_data_address(reader, &state->idname);
|
||||
}
|
||||
direct_link_panel_list(reader, &panel->children);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2280,6 +2280,7 @@ float uiLayoutGetUnitsY(uiLayout *layout);
|
||||
eUIEmbossType uiLayoutGetEmboss(uiLayout *layout);
|
||||
bool uiLayoutGetPropSep(uiLayout *layout);
|
||||
bool uiLayoutGetPropDecorate(uiLayout *layout);
|
||||
Panel *uiLayoutGetRootPanel(uiLayout *layout);
|
||||
|
||||
/* Layout create functions. */
|
||||
|
||||
|
||||
@@ -5307,6 +5307,11 @@ void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
|
||||
SET_FLAG_FROM_TEST(layout->item.flag, is_sep, UI_ITEM_PROP_DECORATE);
|
||||
}
|
||||
|
||||
Panel *uiLayoutGetRootPanel(uiLayout *layout)
|
||||
{
|
||||
return layout->root->block->panel;
|
||||
}
|
||||
|
||||
bool uiLayoutGetActive(uiLayout *layout)
|
||||
{
|
||||
return layout->active;
|
||||
|
||||
@@ -2005,6 +2005,7 @@ static void ui_panel_drag_collapse(const bContext *C,
|
||||
const_cast<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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2109,6 +2110,7 @@ static void ui_handle_layout_panel_header(
|
||||
const_cast<bContext *>(C),
|
||||
&header->open_owner_ptr,
|
||||
RNA_struct_find_property(&header->open_owner_ptr, header->open_prop_name.c_str()));
|
||||
ED_region_tag_redraw(CTX_wm_region(C));
|
||||
|
||||
if (event_type == LEFTMOUSE) {
|
||||
ui_panel_drag_collapse_handler_add(C, is_open);
|
||||
|
||||
@@ -129,6 +129,19 @@ typedef struct ScrAreaMap {
|
||||
ListBase areabase;
|
||||
} ScrAreaMap;
|
||||
|
||||
typedef struct LayoutPanelState {
|
||||
struct LayoutPanelState *next, *prev;
|
||||
/** Identifier of the panel. */
|
||||
char *idname;
|
||||
uint8_t flag;
|
||||
char _pad[7];
|
||||
} LayoutPanelState;
|
||||
|
||||
enum LayoutPanelStateFlag {
|
||||
/** If set, the panel is currently open. Otherwise it is collapsed. */
|
||||
LAYOUT_PANEL_STATE_FLAG_OPEN = (1 << 0),
|
||||
};
|
||||
|
||||
/** The part from uiBlock that needs saved in file. */
|
||||
typedef struct Panel {
|
||||
struct Panel *next, *prev;
|
||||
@@ -158,6 +171,12 @@ typedef struct Panel {
|
||||
/** Sub panels. */
|
||||
ListBase children;
|
||||
|
||||
/**
|
||||
* List of #LayoutPanelState. This stores the open-close-state of layout-panels created with
|
||||
* `layout.panel(...)` in Python. For more information on layout-panels, see `uiLayoutPanel`.
|
||||
*/
|
||||
ListBase layout_panel_states;
|
||||
|
||||
struct Panel_Runtime *runtime;
|
||||
} Panel;
|
||||
|
||||
|
||||
@@ -2383,6 +2383,18 @@ static void rna_def_file_handler(BlenderRNA *brna)
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
|
||||
static void rna_def_layout_panel_state(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "LayoutPanelState", nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "is_open", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flag", LAYOUT_PANEL_STATE_FLAG_OPEN);
|
||||
RNA_def_property_ui_text(prop, "Is Open", "");
|
||||
}
|
||||
|
||||
void RNA_def_ui(BlenderRNA *brna)
|
||||
{
|
||||
rna_def_ui_layout(brna);
|
||||
@@ -2392,6 +2404,7 @@ void RNA_def_ui(BlenderRNA *brna)
|
||||
rna_def_menu(brna);
|
||||
rna_def_asset_shelf(brna);
|
||||
rna_def_file_handler(brna);
|
||||
rna_def_layout_panel_state(brna);
|
||||
}
|
||||
|
||||
#endif /* RNA_RUNTIME */
|
||||
|
||||
@@ -780,6 +780,33 @@ static uiLayout *rna_uiLayoutColumnWithHeading(
|
||||
return uiLayoutColumnWithHeading(layout, align, heading);
|
||||
}
|
||||
|
||||
struct uiLayout *rna_uiLayoutPanelProp(uiLayout *layout,
|
||||
bContext *C,
|
||||
PointerRNA *data,
|
||||
const char *property,
|
||||
const char *text,
|
||||
const char *text_ctxt,
|
||||
const bool translate)
|
||||
{
|
||||
text = rna_translate_ui_text(text, text_ctxt, nullptr, nullptr, translate);
|
||||
return uiLayoutPanel(C, layout, text, data, property);
|
||||
}
|
||||
|
||||
struct uiLayout *rna_uiLayoutPanel(uiLayout *layout,
|
||||
bContext *C,
|
||||
const char *idname,
|
||||
const char *text,
|
||||
const char *text_ctxt,
|
||||
const bool translate,
|
||||
const bool default_closed)
|
||||
{
|
||||
text = RNA_translate_ui_text(text, text_ctxt, nullptr, nullptr, translate);
|
||||
Panel *panel = uiLayoutGetRootPanel(layout);
|
||||
LayoutPanelState *state = BKE_panel_layout_panel_state_ensure(panel, idname, default_closed);
|
||||
PointerRNA state_ptr = RNA_pointer_create(nullptr, &RNA_LayoutPanelState, state);
|
||||
return uiLayoutPanel(C, layout, text, &state_ptr, "is_open");
|
||||
}
|
||||
|
||||
static void rna_uiLayout_template_node_asset_menu_items(uiLayout *layout,
|
||||
bContext *C,
|
||||
const char *catalog_path)
|
||||
@@ -1048,6 +1075,53 @@ void RNA_api_ui_layout(StructRNA *srna)
|
||||
RNA_def_boolean(func, "align", false, "", "Align buttons to each other");
|
||||
api_ui_item_common_heading(func);
|
||||
|
||||
func = RNA_def_function(srna, "panel", "rna_uiLayoutPanel");
|
||||
RNA_def_function_ui_description(func,
|
||||
"Creates a collapsable panel. Whether it is open or closed is "
|
||||
"stored in the region using the given idname");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
|
||||
parm = RNA_def_string(func, "idname", nullptr, 0, "", "Identifier of the panel");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
|
||||
api_ui_item_common_text(func);
|
||||
RNA_def_boolean(func,
|
||||
"default_closed",
|
||||
false,
|
||||
"Open by Default",
|
||||
"When true, the panel will be open the first time it is shown");
|
||||
parm = RNA_def_pointer(func,
|
||||
"layout",
|
||||
"UILayout",
|
||||
"",
|
||||
"Sub-layout to put items in. Will be none is the panel is collapsed");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "panel_prop", "rna_uiLayoutPanelProp");
|
||||
RNA_def_function_ui_description(
|
||||
func,
|
||||
"Similar to `.panel(...)` but instead of storing whether it is open or closed in the "
|
||||
"region, it is stored in the provided boolean property. This should be used when multiple "
|
||||
"instances of the same panel can exist. For example one for every item in a collection "
|
||||
"property or list");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
|
||||
parm = RNA_def_pointer(
|
||||
func, "data", "AnyType", "", "Data from which to take the open-state property");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
parm = RNA_def_string(
|
||||
func,
|
||||
"property",
|
||||
nullptr,
|
||||
0,
|
||||
"",
|
||||
"Identifier of the boolean property that determines whether the panel is open or closed");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
api_ui_item_common_text(func);
|
||||
parm = RNA_def_pointer(func,
|
||||
"layout",
|
||||
"UILayout",
|
||||
"",
|
||||
"Sub-layout to put items in. Will be none is the panel is collapsed");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "column_flow", "uiLayoutColumnFlow");
|
||||
RNA_def_int(func, "columns", 0, 0, INT_MAX, "", "Number of columns, 0 is automatic", 0, INT_MAX);
|
||||
parm = RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in");
|
||||
|
||||
Reference in New Issue
Block a user