Skip to content

Commit 4f585a8

Browse files
author
Roberto De Ioris
committed
UserWidget drawing api first round
1 parent aa23b34 commit 4f585a8

6 files changed

Lines changed: 229 additions & 25 deletions

File tree

‎Source/UnrealEnginePython/Private/PyUserWidget.cpp‎

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,71 +4,64 @@
44
#include "PythonDelegate.h"
55

66
#include "Slate/UEPyFGeometry.h"
7+
#include "Slate/UEPyFPaintContext.h"
78

8-
bool UPyUserWidget::Initialize()
9+
void UPyUserWidget::NativeConstruct()
910
{
10-
if (!Super::Initialize())
11-
return false;
11+
Super::NativeConstruct();
1212

1313
if (PythonModule.IsEmpty())
14-
return false;
14+
return;
1515

1616
FScopePythonGIL gil;
1717

1818
py_uobject = ue_get_python_wrapper(this);
1919
if (!py_uobject) {
2020
unreal_engine_py_log_error();
21-
return false;
21+
return;
2222
}
2323

2424
PyObject *py_user_widget_module = PyImport_ImportModule(TCHAR_TO_UTF8(*PythonModule));
2525
if (!py_user_widget_module) {
2626
unreal_engine_py_log_error();
27-
return false;
27+
return;
2828
}
2929

3030
#if WITH_EDITOR
3131
// todo implement autoreload with a dictionary of module timestamps
3232
py_user_widget_module = PyImport_ReloadModule(py_user_widget_module);
3333
if (!py_user_widget_module) {
3434
unreal_engine_py_log_error();
35-
return false;
35+
return;
3636
}
3737
#endif
3838

3939
if (PythonClass.IsEmpty())
40-
return false;
40+
return;
4141

4242
PyObject *py_user_widget_module_dict = PyModule_GetDict(py_user_widget_module);
4343
PyObject *py_user_widget_class = PyDict_GetItemString(py_user_widget_module_dict, TCHAR_TO_UTF8(*PythonClass));
4444

4545
if (!py_user_widget_class) {
4646
UE_LOG(LogPython, Error, TEXT("Unable to find class %s in module %s"), *PythonClass, *PythonModule);
47-
return false;
47+
return;
4848
}
4949

5050
py_user_widget_instance = PyObject_CallObject(py_user_widget_class, NULL);
5151
if (!py_user_widget_instance) {
5252
unreal_engine_py_log_error();
53-
return false;
53+
return;
5454
}
5555

5656
py_uobject->py_proxy = py_user_widget_instance;
5757

5858
PyObject_SetAttrString(py_user_widget_instance, (char*)"uobject", (PyObject *)py_uobject);
5959

60-
return true;
61-
}
62-
60+
if (PythonTickForceDisabled)
61+
bCanEverTick = false;
6362

64-
void UPyUserWidget::NativeConstruct()
65-
{
66-
Super::NativeConstruct();
67-
68-
if (!py_user_widget_instance)
69-
return;
70-
71-
FScopePythonGIL gil;
63+
if (PythonPaintForceDisabled)
64+
bCanEverPaint = false;
7265

7366
if (!PyObject_HasAttrString(py_user_widget_instance, (char *)"construct"))
7467
return;
@@ -120,7 +113,49 @@ void UPyUserWidget::NativeTick(const FGeometry & MyGeometry, float InDeltaTime)
120113
return;
121114
}
122115
Py_DECREF(ret);
116+
}
123117

118+
bool UPyUserWidget::NativeIsInteractable() const
119+
{
120+
if (!py_user_widget_instance)
121+
return false;
122+
123+
FScopePythonGIL gil;
124+
125+
if (!PyObject_HasAttrString(py_user_widget_instance, (char *)"is_interactable"))
126+
return false;
127+
128+
PyObject *ret = PyObject_CallMethod(py_user_widget_instance, (char *)"is_interactable", nullptr);
129+
if (!ret) {
130+
unreal_engine_py_log_error();
131+
return false;
132+
}
133+
134+
if (PyObject_IsTrue(ret)) {
135+
Py_DECREF(ret);
136+
return true;
137+
}
138+
139+
Py_DECREF(ret);
140+
return false;
141+
}
142+
143+
void UPyUserWidget::NativePaint(FPaintContext & InContext) const
144+
{
145+
if (!py_user_widget_instance)
146+
return;
147+
148+
FScopePythonGIL gil;
149+
150+
if (!PyObject_HasAttrString(py_user_widget_instance, (char *)"paint"))
151+
return;
152+
153+
PyObject *ret = PyObject_CallMethod(py_user_widget_instance, (char *)"paint", (char *)"O", py_ue_new_fpaint_context(InContext));
154+
if (!ret) {
155+
unreal_engine_py_log_error();
156+
return;
157+
}
158+
Py_DECREF(ret);
124159
}
125160

126161
UPyUserWidget::~UPyUserWidget()
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#include "UnrealEnginePythonPrivatePCH.h"
2+
3+
#include "UEPyFPaintContext.h"
4+
5+
static PyObject *py_ue_fpaint_context_draw_line(ue_PyFPaintContext *self, PyObject * args) {
6+
float x1, y1, x2, y2;
7+
PyObject *py_linear_color = nullptr;
8+
PyObject *py_antialias = nullptr;
9+
float thickness = 1.0;
10+
11+
if (!PyArg_ParseTuple(args, "(ff)(ff)|OOf:draw_line", &x1, &y1, &x2, &y2, &py_linear_color, &py_antialias, &thickness))
12+
return nullptr;
13+
14+
FLinearColor tint = FLinearColor::White;
15+
16+
if (py_linear_color) {
17+
if (ue_PyFLinearColor *linear_color = py_ue_is_flinearcolor(py_linear_color)) {
18+
tint = linear_color->color;
19+
}
20+
else {
21+
return PyErr_Format(PyExc_Exception, "argument is not a FlinearColor");
22+
}
23+
}
24+
25+
FPaintContext context = self->paint_context;
26+
27+
context.MaxLayer++;
28+
29+
TArray<FVector2D> points;
30+
points.Add(FVector2D(x1, y1));
31+
points.Add(FVector2D(x2, y2));
32+
33+
FSlateDrawElement::MakeLines(context.OutDrawElements, context.MaxLayer, context.AllottedGeometry.ToPaintGeometry(),
34+
points, context.MyClippingRect, ESlateDrawEffect::None, tint, (py_antialias && !PyObject_IsTrue(py_antialias)), thickness);
35+
36+
Py_RETURN_NONE;
37+
}
38+
39+
static PyObject *py_ue_fpaint_context_draw_lines(ue_PyFPaintContext *self, PyObject * args) {
40+
PyObject *py_points;
41+
PyObject *py_linear_color = nullptr;
42+
PyObject *py_antialias = nullptr;
43+
float thickness = 1.0;
44+
45+
if (!PyArg_ParseTuple(args, "O|OOf:draw_line", &py_points, &py_linear_color, &py_antialias, &thickness))
46+
return nullptr;
47+
48+
TArray<FVector2D> points;
49+
50+
PyObject *py_iter = PyObject_GetIter(py_points);
51+
if (!py_iter)
52+
return PyErr_Format(PyExc_Exception, "argument is not an iterable of tuples");
53+
54+
while (PyObject *py_item = PyIter_Next(py_iter)) {
55+
if (!PyTuple_Check(py_item)) {
56+
return PyErr_Format(PyExc_Exception, "iterable item is not a tuple");
57+
Py_DECREF(py_iter);
58+
}
59+
60+
float x, y;
61+
if (!PyArg_ParseTuple(py_item, "ff", &x, &y)) {
62+
Py_DECREF(py_iter);
63+
return nullptr;
64+
}
65+
points.Add(FVector2D(x, y));
66+
}
67+
Py_DECREF(py_iter);
68+
69+
FLinearColor tint = FLinearColor::White;
70+
71+
if (py_linear_color) {
72+
if (ue_PyFLinearColor *linear_color = py_ue_is_flinearcolor(py_linear_color)) {
73+
tint = linear_color->color;
74+
}
75+
else {
76+
return PyErr_Format(PyExc_Exception, "argument is not a FlinearColor");
77+
}
78+
}
79+
80+
FPaintContext context = self->paint_context;
81+
82+
context.MaxLayer++;
83+
84+
FSlateDrawElement::MakeLines(context.OutDrawElements, context.MaxLayer, context.AllottedGeometry.ToPaintGeometry(),
85+
points, context.MyClippingRect, ESlateDrawEffect::None, tint, (py_antialias && !PyObject_IsTrue(py_antialias)), thickness);
86+
87+
Py_RETURN_NONE;
88+
}
89+
90+
static PyMethodDef ue_PyFPaintContext_methods[] = {
91+
{ "draw_line", (PyCFunction)py_ue_fpaint_context_draw_line, METH_VARARGS, "" },
92+
{ "draw_lines", (PyCFunction)py_ue_fpaint_context_draw_lines, METH_VARARGS, "" },
93+
{ NULL } /* Sentinel */
94+
};
95+
96+
static PyObject *ue_PyFPaintContext_str(ue_PyFPaintContext *self)
97+
{
98+
return PyUnicode_FromFormat("<unreal_engine.FPaintContext '%s'>",
99+
TCHAR_TO_UTF8(*self->paint_context.AllottedGeometry.ToString()));
100+
}
101+
102+
static PyTypeObject ue_PyFPaintContextType = {
103+
PyVarObject_HEAD_INIT(NULL, 0)
104+
"unreal_engine.FPaintContext", /* tp_name */
105+
sizeof(ue_PyFPaintContext), /* tp_basicsize */
106+
0, /* tp_itemsize */
107+
0, /* tp_dealloc */
108+
0, /* tp_print */
109+
0, /* tp_getattr */
110+
0, /* tp_setattr */
111+
0, /* tp_reserved */
112+
0, /* tp_repr */
113+
0, /* tp_as_number */
114+
0, /* tp_as_sequence */
115+
0, /* tp_as_mapping */
116+
0, /* tp_hash */
117+
0, /* tp_call */
118+
(reprfunc)ue_PyFPaintContext_str, /* tp_str */
119+
0, /* tp_getattro */
120+
0, /* tp_setattro */
121+
0, /* tp_as_buffer */
122+
Py_TPFLAGS_DEFAULT, /* tp_flags */
123+
"Unreal Engine FPaintContext", /* tp_doc */
124+
0, /* tp_traverse */
125+
0, /* tp_clear */
126+
0, /* tp_richcompare */
127+
0, /* tp_weaklistoffset */
128+
0, /* tp_iter */
129+
0, /* tp_iternext */
130+
ue_PyFPaintContext_methods, /* tp_methods */
131+
};
132+
133+
void ue_python_init_fpaint_context(PyObject *ue_module) {
134+
ue_PyFPaintContextType.tp_new = PyType_GenericNew;
135+
136+
if (PyType_Ready(&ue_PyFPaintContextType) < 0)
137+
return;
138+
139+
Py_INCREF(&ue_PyFPaintContextType);
140+
PyModule_AddObject(ue_module, "FPaintContext", (PyObject *)&ue_PyFPaintContextType);
141+
}
142+
143+
PyObject *py_ue_new_fpaint_context(FPaintContext paint_context) {
144+
ue_PyFPaintContext *ret = (ue_PyFPaintContext *)PyObject_New(ue_PyFPaintContext, &ue_PyFPaintContextType);
145+
ret->paint_context = paint_context;
146+
return (PyObject *)ret;
147+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#include "UnrealEnginePython.h"
4+
5+
#include "Runtime/UMG/Public/Blueprint/UserWidget.h"
6+
7+
typedef struct {
8+
PyObject_HEAD
9+
/* Type-specific fields go here. */
10+
FPaintContext paint_context;
11+
} ue_PyFPaintContext;
12+
13+
void ue_python_init_fpaint_context(PyObject *);
14+
15+
PyObject *py_ue_new_fpaint_context(FPaintContext);

‎Source/UnrealEnginePython/Private/Slate/UEPySlate.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,9 @@ void ue_python_init_slate(PyObject *module) {
614614
ue_python_init_fslate_style_set(module);
615615
ue_python_init_ftool_bar_builder(module);
616616
ue_python_init_fslate_icon(module);
617+
618+
ue_python_init_fgeometry(module);
619+
ue_python_init_fpaint_context(module);
617620
}
618621

619622
PyObject *ue_py_dict_get_item(PyObject *dict, const char *key) {

‎Source/UnrealEnginePython/Private/Slate/UEPySlate.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
#include "UEPyFToolBarBuilder.h"
6464
#include "UEPyFSlateIcon.h"
6565

66+
#include "UEPyFGeometry.h"
67+
#include "UEPyFPaintContext.h"
68+
6669
#if WITH_EDITOR
6770
#include "UEPySEditorViewport.h"
6871
#include "UEPySLevelViewport.h"

‎Source/UnrealEnginePython/Public/PyUserWidget.h‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@ class UPyUserWidget : public UUserWidget
1515

1616
~UPyUserWidget();
1717

18-
virtual bool Initialize() override;
19-
2018
virtual void NativeConstruct() override;
2119
virtual void NativeDestruct() override;
2220

2321
// Called every frame
2422
virtual void NativeTick(const FGeometry & MyGeometry, float InDeltaTime) override;
2523

26-
//virtual void NativePaint(FPaintContext & InContext) const override;
24+
virtual void NativePaint(FPaintContext & InContext) const override;
2725

28-
//virtual bool NativeIsInteractable() const override;
26+
virtual bool NativeIsInteractable() const override;
2927

3028
UPROPERTY(EditAnywhere, Category = "Python", BlueprintReadWrite, meta = (ExposeOnSpawn = true))
3129
FString PythonModule;
@@ -36,6 +34,9 @@ class UPyUserWidget : public UUserWidget
3634
UPROPERTY(EditAnywhere, Category = "Python", BlueprintReadWrite, meta = (ExposeOnSpawn = true))
3735
bool PythonTickForceDisabled;
3836

37+
UPROPERTY(EditAnywhere, Category = "Python", BlueprintReadWrite, meta = (ExposeOnSpawn = true))
38+
bool PythonPaintForceDisabled;
39+
3940
private:
4041
PyObject *py_user_widget_instance;
4142
// mapped uobject, required for debug and advanced reflection

0 commit comments

Comments
 (0)