BPicture: add gradient support
Fixes #9680. Change-Id: I0013326559cc40ff26cf7b44794c0b32aea832ba Reviewed-on: https://review.haiku-os.org/c/haiku/+/2829 Reviewed-by: Stephan Aßmus <[email protected]>
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
|
|
||||||
class Layer;
|
class Layer;
|
||||||
|
class BGradient;
|
||||||
class BPositionIO;
|
class BPositionIO;
|
||||||
class BRegion;
|
class BRegion;
|
||||||
|
|
||||||
@@ -91,6 +92,24 @@ public:
|
|||||||
status_t WriteDrawShape(const int32& opCount,
|
status_t WriteDrawShape(const int32& opCount,
|
||||||
const void* opList, const int32& ptCount,
|
const void* opList, const int32& ptCount,
|
||||||
const void* ptList, const bool& fill);
|
const void* ptList, const bool& fill);
|
||||||
|
status_t WriteDrawRectGradient(const BRect& rect, const BGradient& gradient,
|
||||||
|
const bool& fill);
|
||||||
|
status_t WriteDrawRoundRectGradient(const BRect& rect,
|
||||||
|
const BPoint& radius, const BGradient& gradient, const bool& fill);
|
||||||
|
status_t WriteDrawBezierGradient(const BPoint points[4], const BGradient& gradient,
|
||||||
|
const bool& fill);
|
||||||
|
status_t WriteDrawArcGradient(const BPoint& center,
|
||||||
|
const BPoint& radius,
|
||||||
|
const float& startTheta,
|
||||||
|
const float& arcTheta, const BGradient& gradient, const bool& fill);
|
||||||
|
status_t WriteDrawEllipseGradient(const BRect& rect, const BGradient& gradient,
|
||||||
|
const bool& fill);
|
||||||
|
status_t WriteDrawPolygonGradient(const int32& numPoints,
|
||||||
|
BPoint* points, const bool& isClosed, const BGradient& gradient,
|
||||||
|
const bool& fill);
|
||||||
|
status_t WriteDrawShapeGradient(const int32& opCount,
|
||||||
|
const void* opList, const int32& ptCount,
|
||||||
|
const void* ptList, const BGradient& gradient, const bool& fill);
|
||||||
status_t WriteDrawBitmap(const BRect& srcRect,
|
status_t WriteDrawBitmap(const BRect& srcRect,
|
||||||
const BRect& dstRect, const int32& width,
|
const BRect& dstRect, const int32& width,
|
||||||
const int32& height,
|
const int32& height,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
|
|
||||||
class BAffineTransform;
|
class BAffineTransform;
|
||||||
|
class BGradient;
|
||||||
class BList;
|
class BList;
|
||||||
class BPicture;
|
class BPicture;
|
||||||
class BShape;
|
class BShape;
|
||||||
@@ -94,6 +95,13 @@ struct picture_player_callbacks {
|
|||||||
int32 ptCount, const BPoint ptList[], bool inverse);
|
int32 ptCount, const BPoint ptList[], bool inverse);
|
||||||
void (*draw_string_locations)(void* userData, const char* string,
|
void (*draw_string_locations)(void* userData, const char* string,
|
||||||
size_t length, const BPoint locations[], size_t locationCount);
|
size_t length, const BPoint locations[], size_t locationCount);
|
||||||
|
void (*draw_rect_gradient)(void* userData, const BRect& rect, BGradient& gradient, bool fill);
|
||||||
|
void (*draw_round_rect_gradient)(void* userData, const BRect& rect, const BPoint& radii, BGradient& gradient, bool fill);
|
||||||
|
void (*draw_bezier_gradient)(void* userData, size_t numControlPoints, const BPoint controlPoints[], BGradient& gradient, bool fill);
|
||||||
|
void (*draw_arc_gradient)(void* userData, const BPoint& center, const BPoint& radii, float startTheta, float arcTheta, BGradient& gradient, bool fill);
|
||||||
|
void (*draw_ellipse_gradient)(void* userData, const BRect& rect, BGradient& gradient, bool fill);
|
||||||
|
void (*draw_polygon_gradient)(void* userData, size_t numPoints, const BPoint points[], bool isClosed, BGradient& gradient, bool fill);
|
||||||
|
void (*draw_shape_gradient)(void* userData, const BShape& shape, BGradient& gradient, bool fill);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ enum {
|
|||||||
B_PIC_STROKE_ELLIPSE = 0x0115,
|
B_PIC_STROKE_ELLIPSE = 0x0115,
|
||||||
B_PIC_FILL_ELLIPSE = 0x0116,
|
B_PIC_FILL_ELLIPSE = 0x0116,
|
||||||
B_PIC_DRAW_STRING_LOCATIONS = 0x0117,
|
B_PIC_DRAW_STRING_LOCATIONS = 0x0117,
|
||||||
|
B_PIC_STROKE_RECT_GRADIENT = 0x0118,
|
||||||
|
B_PIC_FILL_RECT_GRADIENT = 0x0119,
|
||||||
|
B_PIC_STROKE_ROUND_RECT_GRADIENT = 0x011A,
|
||||||
|
B_PIC_FILL_ROUND_RECT_GRADIENT = 0x011B,
|
||||||
|
B_PIC_STROKE_BEZIER_GRADIENT = 0x011C,
|
||||||
|
B_PIC_FILL_BEZIER_GRADIENT = 0x011D,
|
||||||
|
B_PIC_STROKE_POLYGON_GRADIENT = 0x011E,
|
||||||
|
B_PIC_FILL_POLYGON_GRADIENT = 0x011F,
|
||||||
|
B_PIC_STROKE_SHAPE_GRADIENT = 0x0120,
|
||||||
|
B_PIC_FILL_SHAPE_GRADIENT = 0x0121,
|
||||||
|
B_PIC_STROKE_ARC_GRADIENT = 0x0122,
|
||||||
|
B_PIC_FILL_ARC_GRADIENT = 0x0123,
|
||||||
|
B_PIC_STROKE_ELLIPSE_GRADIENT = 0x0124,
|
||||||
|
B_PIC_FILL_ELLIPSE_GRADIENT = 0x0125,
|
||||||
|
|
||||||
B_PIC_ENTER_STATE_CHANGE = 0x0200,
|
B_PIC_ENTER_STATE_CHANGE = 0x0200,
|
||||||
B_PIC_SET_CLIPPING_RECTS = 0x0201,
|
B_PIC_SET_CLIPPING_RECTS = 0x0201,
|
||||||
@@ -62,7 +76,7 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const static uint32 kOpsTableSize = 51;
|
const static uint32 kOpsTableSize = 70;
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <DataIO.h>
|
#include <DataIO.h>
|
||||||
|
#include <Gradient.h>
|
||||||
#include <Point.h>
|
#include <Point.h>
|
||||||
#include <Rect.h>
|
#include <Rect.h>
|
||||||
#include <Region.h>
|
#include <Region.h>
|
||||||
@@ -519,6 +520,137 @@ PictureDataWriter::WriteDrawShape(const int32& opCount, const void* opList,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
PictureDataWriter::WriteDrawRectGradient(const BRect& rect, const BGradient& gradient, const bool& fill)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
BeginOp(fill ? B_PIC_FILL_RECT_GRADIENT : B_PIC_STROKE_RECT_GRADIENT);
|
||||||
|
Write<BRect>(rect);
|
||||||
|
gradient.Flatten(fData);
|
||||||
|
EndOp();
|
||||||
|
} catch (status_t& status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
PictureDataWriter::WriteDrawRoundRectGradient(const BRect& rect, const BPoint& radius, const BGradient& gradient,
|
||||||
|
const bool& fill)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
BeginOp(fill ? B_PIC_FILL_ROUND_RECT_GRADIENT : B_PIC_STROKE_ROUND_RECT_GRADIENT);
|
||||||
|
Write<BRect>(rect);
|
||||||
|
Write<BPoint>(radius);
|
||||||
|
gradient.Flatten(fData);
|
||||||
|
EndOp();
|
||||||
|
} catch (status_t& status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
PictureDataWriter::WriteDrawBezierGradient(const BPoint points[4], const BGradient& gradient, const bool& fill)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
BeginOp(fill ? B_PIC_FILL_BEZIER_GRADIENT : B_PIC_STROKE_BEZIER_GRADIENT);
|
||||||
|
for (int32 i = 0; i < 4; i++)
|
||||||
|
Write<BPoint>(points[i]);
|
||||||
|
|
||||||
|
gradient.Flatten(fData);
|
||||||
|
EndOp();
|
||||||
|
} catch (status_t& status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
PictureDataWriter::WriteDrawArcGradient(const BPoint& center, const BPoint& radius,
|
||||||
|
const float& startTheta, const float& arcTheta, const BGradient& gradient, const bool& fill)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
BeginOp(fill ? B_PIC_FILL_ARC_GRADIENT : B_PIC_STROKE_ARC_GRADIENT);
|
||||||
|
Write<BPoint>(center);
|
||||||
|
Write<BPoint>(radius);
|
||||||
|
Write<float>(startTheta);
|
||||||
|
Write<float>(arcTheta);
|
||||||
|
gradient.Flatten(fData);
|
||||||
|
EndOp();
|
||||||
|
} catch (status_t& status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
PictureDataWriter::WriteDrawEllipseGradient(const BRect& rect, const BGradient& gradient, const bool& fill)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
BeginOp(fill ? B_PIC_FILL_ELLIPSE_GRADIENT : B_PIC_STROKE_ELLIPSE_GRADIENT);
|
||||||
|
Write<BRect>(rect);
|
||||||
|
gradient.Flatten(fData);
|
||||||
|
EndOp();
|
||||||
|
} catch (status_t& status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
PictureDataWriter::WriteDrawPolygonGradient(const int32& numPoints, BPoint* points,
|
||||||
|
const bool& isClosed, const BGradient& gradient, const bool& fill)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
BeginOp(fill ? B_PIC_FILL_POLYGON_GRADIENT : B_PIC_STROKE_POLYGON_GRADIENT);
|
||||||
|
Write<int32>(numPoints);
|
||||||
|
for (int32 i = 0; i < numPoints; i++)
|
||||||
|
Write<BPoint>(points[i]);
|
||||||
|
|
||||||
|
if (!fill)
|
||||||
|
Write<uint8>((uint8)isClosed);
|
||||||
|
|
||||||
|
gradient.Flatten(fData);
|
||||||
|
EndOp();
|
||||||
|
} catch (status_t& status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
PictureDataWriter::WriteDrawShapeGradient(const int32& opCount, const void* opList,
|
||||||
|
const int32& ptCount, const void* ptList, const BGradient& gradient, const bool& fill)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
BeginOp(fill ? B_PIC_FILL_SHAPE_GRADIENT : B_PIC_STROKE_SHAPE_GRADIENT);
|
||||||
|
Write<int32>(opCount);
|
||||||
|
Write<int32>(ptCount);
|
||||||
|
WriteData(opList, opCount * sizeof(uint32));
|
||||||
|
WriteData(ptList, ptCount * sizeof(BPoint));
|
||||||
|
gradient.Flatten(fData);
|
||||||
|
EndOp();
|
||||||
|
} catch (status_t& status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
PictureDataWriter::WriteDrawBitmap(const BRect& srcRect, const BRect& dstRect,
|
PictureDataWriter::WriteDrawBitmap(const BRect& srcRect, const BRect& dstRect,
|
||||||
const int32& width, const int32& height, const int32& bytesPerRow,
|
const int32& width, const int32& height, const int32& bytesPerRow,
|
||||||
|
|||||||
@@ -18,9 +18,12 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <AffineTransform.h>
|
#include <AffineTransform.h>
|
||||||
|
#include <DataIO.h>
|
||||||
|
#include <Gradient.h>
|
||||||
#include <PictureProtocol.h>
|
#include <PictureProtocol.h>
|
||||||
#include <Shape.h>
|
#include <Shape.h>
|
||||||
|
|
||||||
|
#include <AutoDeleter.h>
|
||||||
#include <StackOrHeapArray.h>
|
#include <StackOrHeapArray.h>
|
||||||
|
|
||||||
|
|
||||||
@@ -520,6 +523,88 @@ draw_string_locations(void* _context, const char* _string, size_t length,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_rect_gradient(void* _context, const BRect& rect, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
adapter_context* context = reinterpret_cast<adapter_context*>(_context);
|
||||||
|
((void (*)(void*, BRect, BGradient&))context->function_table[fill ? 56 : 57])(
|
||||||
|
context->user_data, rect, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_round_rect_gradient(void* _context, const BRect& rect, const BPoint& radii, BGradient& gradient,
|
||||||
|
bool fill)
|
||||||
|
{
|
||||||
|
adapter_context* context = reinterpret_cast<adapter_context*>(_context);
|
||||||
|
((void (*)(void*, BRect, BPoint, BGradient&))context->function_table[fill ? 58 : 59])(
|
||||||
|
context->user_data, rect, radii, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_bezier_gradient(void* _context, size_t numPoints, const BPoint _points[], BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
adapter_context* context = reinterpret_cast<adapter_context*>(_context);
|
||||||
|
if (numPoints != 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BPoint points[4] = { _points[0], _points[1], _points[2], _points[3] };
|
||||||
|
((void (*)(void*, BPoint*, BGradient&))context->function_table[fill ? 60 : 61])(
|
||||||
|
context->user_data, points, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_arc_gradient(void* _context, const BPoint& center, const BPoint& radii,
|
||||||
|
float startTheta, float arcTheta, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
adapter_context* context = reinterpret_cast<adapter_context*>(_context);
|
||||||
|
((void (*)(void*, BPoint, BPoint, float, float, BGradient&))
|
||||||
|
context->function_table[fill ? 62 : 63])(context->user_data, center,
|
||||||
|
radii, startTheta, arcTheta, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_ellipse_gradient(void* _context, const BRect& rect, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
adapter_context* context = reinterpret_cast<adapter_context*>(_context);
|
||||||
|
BPoint radii((rect.Width() + 1) / 2.0f, (rect.Height() + 1) / 2.0f);
|
||||||
|
BPoint center = rect.LeftTop() + radii;
|
||||||
|
((void (*)(void*, BPoint, BPoint, BGradient&))
|
||||||
|
context->function_table[fill ? 64 : 65])(context->user_data, center,
|
||||||
|
radii, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_polygon_gradient(void* _context, size_t numPoints, const BPoint _points[],
|
||||||
|
bool isClosed, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
adapter_context* context = reinterpret_cast<adapter_context*>(_context);
|
||||||
|
|
||||||
|
BStackOrHeapArray<BPoint, 200> points(numPoints);
|
||||||
|
if (!points.IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
memcpy((void*)points, _points, numPoints * sizeof(BPoint));
|
||||||
|
|
||||||
|
((void (*)(void*, int32, BPoint*, bool, BGradient&))
|
||||||
|
context->function_table[fill ? 66 : 67])(context->user_data, numPoints,
|
||||||
|
points, isClosed, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_shape_gradient(void* _context, const BShape& shape, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
adapter_context* context = reinterpret_cast<adapter_context*>(_context);
|
||||||
|
((void (*)(void*, BShape, BGradient&))context->function_table[fill ? 68 : 69])(
|
||||||
|
context->user_data, shape, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG > 1
|
#if DEBUG > 1
|
||||||
static const char *
|
static const char *
|
||||||
@@ -655,7 +740,14 @@ PicturePlayer::Play(void** callBackTable, int32 tableEntries, void* userData)
|
|||||||
blend_layer,
|
blend_layer,
|
||||||
clip_to_rect,
|
clip_to_rect,
|
||||||
clip_to_shape,
|
clip_to_shape,
|
||||||
draw_string_locations
|
draw_string_locations,
|
||||||
|
draw_rect_gradient,
|
||||||
|
draw_round_rect_gradient,
|
||||||
|
draw_bezier_gradient,
|
||||||
|
draw_arc_gradient,
|
||||||
|
draw_ellipse_gradient,
|
||||||
|
draw_polygon_gradient,
|
||||||
|
draw_shape_gradient
|
||||||
};
|
};
|
||||||
|
|
||||||
// We don't check if the functions in the table are NULL, but we
|
// We don't check if the functions in the table are NULL, but we
|
||||||
@@ -719,6 +811,20 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GetGradient(BGradient*& gradient)
|
||||||
|
{
|
||||||
|
BMemoryIO stream(fBuffer, fRemaining);
|
||||||
|
printf("fRemaining: %ld\n", fRemaining);
|
||||||
|
if (BGradient::Unflatten(gradient, &stream) != B_OK) {
|
||||||
|
printf("BGradient::Unflatten(_gradient, &stream) != B_OK\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fRemaining -= stream.Position();
|
||||||
|
fBuffer += stream.Position();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
bool
|
bool
|
||||||
GetRemaining(const T*& buffer, size_t& size)
|
GetRemaining(const T*& buffer, size_t& size)
|
||||||
@@ -936,6 +1042,141 @@ PicturePlayer::_Play(const picture_player_callbacks& callbacks, void* userData,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case B_PIC_STROKE_RECT_GRADIENT:
|
||||||
|
case B_PIC_FILL_RECT_GRADIENT:
|
||||||
|
{
|
||||||
|
const BRect* rect;
|
||||||
|
BGradient* gradient;
|
||||||
|
if (callbacks.draw_rect_gradient == NULL || !reader.Get(rect) || !reader.GetGradient(gradient))
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
callbacks.draw_rect_gradient(userData, *rect, *gradient,
|
||||||
|
header->op == B_PIC_FILL_RECT_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case B_PIC_STROKE_ROUND_RECT_GRADIENT:
|
||||||
|
case B_PIC_FILL_ROUND_RECT_GRADIENT:
|
||||||
|
{
|
||||||
|
const BRect* rect;
|
||||||
|
const BPoint* radii;
|
||||||
|
BGradient* gradient;
|
||||||
|
if (callbacks.draw_round_rect_gradient == NULL || !reader.Get(rect)
|
||||||
|
|| !reader.Get(radii) || !reader.GetGradient(gradient)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
callbacks.draw_round_rect_gradient(userData, *rect, *radii, *gradient,
|
||||||
|
header->op == B_PIC_FILL_ROUND_RECT_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case B_PIC_STROKE_BEZIER_GRADIENT:
|
||||||
|
case B_PIC_FILL_BEZIER_GRADIENT:
|
||||||
|
{
|
||||||
|
const size_t kNumControlPoints = 4;
|
||||||
|
const BPoint* controlPoints;
|
||||||
|
BGradient* gradient;
|
||||||
|
if (callbacks.draw_bezier_gradient == NULL
|
||||||
|
|| !reader.Get(controlPoints, kNumControlPoints) || !reader.GetGradient(gradient)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
callbacks.draw_bezier_gradient(userData, kNumControlPoints,
|
||||||
|
controlPoints, *gradient, header->op == B_PIC_FILL_BEZIER_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case B_PIC_STROKE_POLYGON_GRADIENT:
|
||||||
|
case B_PIC_FILL_POLYGON_GRADIENT:
|
||||||
|
{
|
||||||
|
const uint32* numPoints;
|
||||||
|
const BPoint* points;
|
||||||
|
BGradient* gradient;
|
||||||
|
if (callbacks.draw_polygon_gradient == NULL || !reader.Get(numPoints)
|
||||||
|
|| !reader.Get(points, *numPoints)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isClosed = true;
|
||||||
|
const bool* closedPointer;
|
||||||
|
if (header->op != B_PIC_FILL_POLYGON_GRADIENT) {
|
||||||
|
if (!reader.Get(closedPointer))
|
||||||
|
break;
|
||||||
|
|
||||||
|
isClosed = *closedPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reader.GetGradient(gradient))
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
callbacks.draw_polygon_gradient(userData, *numPoints, points, isClosed, *gradient,
|
||||||
|
header->op == B_PIC_FILL_POLYGON_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case B_PIC_STROKE_SHAPE_GRADIENT:
|
||||||
|
case B_PIC_FILL_SHAPE_GRADIENT:
|
||||||
|
{
|
||||||
|
const uint32* opCount;
|
||||||
|
const uint32* pointCount;
|
||||||
|
const uint32* opList;
|
||||||
|
const BPoint* pointList;
|
||||||
|
BGradient* gradient;
|
||||||
|
if (callbacks.draw_shape_gradient == NULL || !reader.Get(opCount)
|
||||||
|
|| !reader.Get(pointCount) || !reader.Get(opList, *opCount)
|
||||||
|
|| !reader.Get(pointList, *pointCount) || !reader.GetGradient(gradient)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
// TODO: remove BShape data copying
|
||||||
|
BShape shape;
|
||||||
|
shape.SetData(*opCount, *pointCount, opList, pointList);
|
||||||
|
|
||||||
|
callbacks.draw_shape_gradient(userData, shape, *gradient,
|
||||||
|
header->op == B_PIC_FILL_SHAPE_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case B_PIC_STROKE_ARC_GRADIENT:
|
||||||
|
case B_PIC_FILL_ARC_GRADIENT:
|
||||||
|
{
|
||||||
|
const BPoint* center;
|
||||||
|
const BPoint* radii;
|
||||||
|
const float* startTheta;
|
||||||
|
const float* arcTheta;
|
||||||
|
BGradient* gradient;
|
||||||
|
if (callbacks.draw_arc_gradient == NULL || !reader.Get(center)
|
||||||
|
|| !reader.Get(radii) || !reader.Get(startTheta)
|
||||||
|
|| !reader.Get(arcTheta) || !reader.GetGradient(gradient)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
callbacks.draw_arc_gradient(userData, *center, *radii, *startTheta,
|
||||||
|
*arcTheta, *gradient, header->op == B_PIC_FILL_ARC_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case B_PIC_STROKE_ELLIPSE_GRADIENT:
|
||||||
|
case B_PIC_FILL_ELLIPSE_GRADIENT:
|
||||||
|
{
|
||||||
|
const BRect* rect;
|
||||||
|
BGradient* gradient;
|
||||||
|
if (callbacks.draw_ellipse_gradient == NULL || !reader.Get(rect) || !reader.GetGradient(gradient))
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
callbacks.draw_ellipse_gradient(userData, *rect, *gradient,
|
||||||
|
header->op == B_PIC_FILL_ELLIPSE_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case B_PIC_DRAW_STRING:
|
case B_PIC_DRAW_STRING:
|
||||||
{
|
{
|
||||||
const float* escapementSpace;
|
const float* escapementSpace;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ using std::stack;
|
|||||||
|
|
||||||
class ShapePainter : public BShapeIterator {
|
class ShapePainter : public BShapeIterator {
|
||||||
public:
|
public:
|
||||||
ShapePainter(Canvas* canvas);
|
ShapePainter(Canvas* canvas, BGradient* gradient);
|
||||||
virtual ~ShapePainter();
|
virtual ~ShapePainter();
|
||||||
|
|
||||||
status_t Iterate(const BShape* shape);
|
status_t Iterate(const BShape* shape);
|
||||||
@@ -65,14 +65,16 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Canvas* fCanvas;
|
Canvas* fCanvas;
|
||||||
|
BGradient* fGradient;
|
||||||
stack<uint32> fOpStack;
|
stack<uint32> fOpStack;
|
||||||
stack<BPoint> fPtStack;
|
stack<BPoint> fPtStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
ShapePainter::ShapePainter(Canvas* canvas)
|
ShapePainter::ShapePainter(Canvas* canvas, BGradient* gradient)
|
||||||
:
|
:
|
||||||
fCanvas(canvas)
|
fCanvas(canvas),
|
||||||
|
fGradient(gradient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,8 +219,14 @@ ShapePainter::Draw(BRect frame, bool filled)
|
|||||||
transform.Apply(&screenOffset);
|
transform.Apply(&screenOffset);
|
||||||
transform.Apply(&frame);
|
transform.Apply(&frame);
|
||||||
|
|
||||||
fCanvas->GetDrawingEngine()->DrawShape(frame, opCount, opList,
|
/* stroked gradients are not yet supported */
|
||||||
ptCount, ptList, filled, screenOffset, fCanvas->Scale());
|
if (fGradient != NULL && filled) {
|
||||||
|
fCanvas->GetDrawingEngine()->FillShape(frame, opCount, opList,
|
||||||
|
ptCount, ptList, *fGradient, screenOffset, fCanvas->Scale());
|
||||||
|
} else {
|
||||||
|
fCanvas->GetDrawingEngine()->DrawShape(frame, opCount, opList,
|
||||||
|
ptCount, ptList, filled, screenOffset, fCanvas->Scale());
|
||||||
|
}
|
||||||
|
|
||||||
delete[] opList;
|
delete[] opList;
|
||||||
delete[] ptList;
|
delete[] ptList;
|
||||||
@@ -381,7 +389,125 @@ static void
|
|||||||
draw_shape(void* _canvas, const BShape& shape, bool fill)
|
draw_shape(void* _canvas, const BShape& shape, bool fill)
|
||||||
{
|
{
|
||||||
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
ShapePainter drawShape(canvas);
|
ShapePainter drawShape(canvas, NULL);
|
||||||
|
|
||||||
|
drawShape.Iterate(&shape);
|
||||||
|
drawShape.Draw(shape.Bounds(), fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_rect_gradient(void* _canvas, const BRect& _rect, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
|
BRect rect = _rect;
|
||||||
|
|
||||||
|
const SimpleTransform transform =
|
||||||
|
canvas->PenToScreenTransform();
|
||||||
|
transform.Apply(&rect);
|
||||||
|
transform.Apply(&gradient);
|
||||||
|
|
||||||
|
canvas->GetDrawingEngine()->FillRect(rect, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_round_rect_gradient(void* _canvas, const BRect& _rect, const BPoint& radii, BGradient& gradient,
|
||||||
|
bool fill)
|
||||||
|
{
|
||||||
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
|
BRect rect = _rect;
|
||||||
|
|
||||||
|
const SimpleTransform transform =
|
||||||
|
canvas->PenToScreenTransform();
|
||||||
|
transform.Apply(&rect);
|
||||||
|
transform.Apply(&gradient);
|
||||||
|
float scale = canvas->CurrentState()->CombinedScale();
|
||||||
|
canvas->GetDrawingEngine()->FillRoundRect(rect, radii.x * scale,
|
||||||
|
radii.y * scale, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_bezier_gradient(void* _canvas, size_t numPoints, const BPoint viewPoints[], BGradient& gradient,
|
||||||
|
bool fill)
|
||||||
|
{
|
||||||
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
|
|
||||||
|
const size_t kSupportedPoints = 4;
|
||||||
|
if (numPoints != kSupportedPoints)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BPoint points[kSupportedPoints];
|
||||||
|
const SimpleTransform transform =
|
||||||
|
canvas->PenToScreenTransform();
|
||||||
|
transform.Apply(points, viewPoints, kSupportedPoints);
|
||||||
|
transform.Apply(&gradient);
|
||||||
|
canvas->GetDrawingEngine()->FillBezier(points, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_arc_gradient(void* _canvas, const BPoint& center, const BPoint& radii,
|
||||||
|
float startTheta, float arcTheta, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
|
|
||||||
|
BRect rect(center.x - radii.x, center.y - radii.y,
|
||||||
|
center.x + radii.x - 1, center.y + radii.y - 1);
|
||||||
|
const SimpleTransform transform =
|
||||||
|
canvas->PenToScreenTransform();
|
||||||
|
transform.Apply(&rect);
|
||||||
|
transform.Apply(&gradient);
|
||||||
|
canvas->GetDrawingEngine()->FillArc(rect, startTheta, arcTheta, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_ellipse_gradient(void* _canvas, const BRect& _rect, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
|
BRect rect = _rect;
|
||||||
|
|
||||||
|
const SimpleTransform transform =
|
||||||
|
canvas->PenToScreenTransform();
|
||||||
|
transform.Apply(&rect);
|
||||||
|
transform.Apply(&gradient);
|
||||||
|
canvas->GetDrawingEngine()->FillEllipse(rect, gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_polygon_gradient(void* _canvas, size_t numPoints, const BPoint viewPoints[],
|
||||||
|
bool isClosed, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
|
|
||||||
|
if (numPoints == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BStackOrHeapArray<BPoint, 200> points(numPoints);
|
||||||
|
if (!points.IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const SimpleTransform transform =
|
||||||
|
canvas->PenToScreenTransform();
|
||||||
|
transform.Apply(points, viewPoints, numPoints);
|
||||||
|
transform.Apply(&gradient);
|
||||||
|
|
||||||
|
BRect polyFrame;
|
||||||
|
get_polygon_frame(points, numPoints, &polyFrame);
|
||||||
|
|
||||||
|
canvas->GetDrawingEngine()->FillPolygon(points, numPoints, polyFrame,
|
||||||
|
gradient, isClosed && numPoints > 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_shape_gradient(void* _canvas, const BShape& shape, BGradient& gradient, bool fill)
|
||||||
|
{
|
||||||
|
Canvas* const canvas = reinterpret_cast<Canvas*>(_canvas);
|
||||||
|
ShapePainter drawShape(canvas, &gradient);
|
||||||
|
|
||||||
drawShape.Iterate(&shape);
|
drawShape.Iterate(&shape);
|
||||||
drawShape.Draw(shape.Bounds(), fill);
|
drawShape.Draw(shape.Bounds(), fill);
|
||||||
@@ -929,7 +1055,14 @@ static const BPrivate::picture_player_callbacks kPicturePlayerCallbacks = {
|
|||||||
blend_layer,
|
blend_layer,
|
||||||
clip_to_rect,
|
clip_to_rect,
|
||||||
clip_to_shape,
|
clip_to_shape,
|
||||||
draw_string_locations
|
draw_string_locations,
|
||||||
|
draw_rect_gradient,
|
||||||
|
draw_round_rect_gradient,
|
||||||
|
draw_bezier_gradient,
|
||||||
|
draw_arc_gradient,
|
||||||
|
draw_ellipse_gradient,
|
||||||
|
draw_polygon_gradient,
|
||||||
|
draw_shape_gradient
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3507,6 +3507,193 @@ ServerWindow::_DispatchPictureMessage(int32 code, BPrivate::LinkReceiver& link)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_RECT_GRADIENT:
|
||||||
|
case AS_FILL_RECT_GRADIENT:
|
||||||
|
{
|
||||||
|
BRect rect;
|
||||||
|
link.Read<BRect>(&rect);
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
picture->WriteDrawRectGradient(rect, *gradient, code == AS_FILL_RECT_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_ARC_GRADIENT:
|
||||||
|
case AS_FILL_ARC_GRADIENT:
|
||||||
|
{
|
||||||
|
BRect rect;
|
||||||
|
link.Read<BRect>(&rect);
|
||||||
|
float startTheta, arcTheta;
|
||||||
|
link.Read<float>(&startTheta);
|
||||||
|
link.Read<float>(&arcTheta);
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
BPoint radii((rect.Width() + 1) / 2, (rect.Height() + 1) / 2);
|
||||||
|
BPoint center = rect.LeftTop() + radii;
|
||||||
|
|
||||||
|
picture->WriteDrawArcGradient(center, radii, startTheta, arcTheta, *gradient,
|
||||||
|
code == AS_FILL_ARC_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_BEZIER_GRADIENT:
|
||||||
|
case AS_FILL_BEZIER_GRADIENT:
|
||||||
|
{
|
||||||
|
BPoint points[4];
|
||||||
|
for (int32 i = 0; i < 4; i++) {
|
||||||
|
link.Read<BPoint>(&(points[i]));
|
||||||
|
}
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
picture->WriteDrawBezierGradient(points, *gradient, code == AS_FILL_BEZIER_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_ELLIPSE_GRADIENT:
|
||||||
|
case AS_FILL_ELLIPSE_GRADIENT:
|
||||||
|
{
|
||||||
|
BRect rect;
|
||||||
|
link.Read<BRect>(&rect);
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
picture->WriteDrawEllipseGradient(rect, *gradient, code == AS_FILL_ELLIPSE_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_ROUNDRECT_GRADIENT:
|
||||||
|
case AS_FILL_ROUNDRECT_GRADIENT:
|
||||||
|
{
|
||||||
|
BRect rect;
|
||||||
|
link.Read<BRect>(&rect);
|
||||||
|
|
||||||
|
BPoint radii;
|
||||||
|
link.Read<float>(&radii.x);
|
||||||
|
link.Read<float>(&radii.y);
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
picture->WriteDrawRoundRectGradient(rect, radii, *gradient, code == AS_FILL_ROUNDRECT_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_TRIANGLE_GRADIENT:
|
||||||
|
case AS_FILL_TRIANGLE_GRADIENT:
|
||||||
|
{
|
||||||
|
// There is no B_PIC_FILL/STROKE_TRIANGLE op,
|
||||||
|
// we implement it using B_PIC_FILL/STROKE_POLYGON
|
||||||
|
BPoint points[3];
|
||||||
|
|
||||||
|
for (int32 i = 0; i < 3; i++) {
|
||||||
|
link.Read<BPoint>(&(points[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
BRect rect;
|
||||||
|
link.Read<BRect>(&rect);
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
picture->WriteDrawPolygonGradient(3, points,
|
||||||
|
true, *gradient, code == AS_FILL_TRIANGLE_GRADIENT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_POLYGON_GRADIENT:
|
||||||
|
case AS_FILL_POLYGON_GRADIENT:
|
||||||
|
{
|
||||||
|
BRect polyFrame;
|
||||||
|
bool isClosed = true;
|
||||||
|
int32 pointCount;
|
||||||
|
const bool fill = (code == AS_FILL_POLYGON_GRADIENT);
|
||||||
|
|
||||||
|
link.Read<BRect>(&polyFrame);
|
||||||
|
if (code == AS_STROKE_POLYGON)
|
||||||
|
link.Read<bool>(&isClosed);
|
||||||
|
link.Read<int32>(&pointCount);
|
||||||
|
|
||||||
|
ArrayDeleter<BPoint> pointList(new(nothrow) BPoint[pointCount]);
|
||||||
|
if (link.Read(pointList.Get(), pointCount * sizeof(BPoint)) != B_OK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
picture->WriteDrawPolygonGradient(pointCount, pointList.Get(),
|
||||||
|
isClosed && pointCount > 2, *gradient, fill);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//case AS_STROKE_SHAPE_GRADIENT:
|
||||||
|
case AS_FILL_SHAPE_GRADIENT:
|
||||||
|
{
|
||||||
|
BRect shapeFrame;
|
||||||
|
int32 opCount;
|
||||||
|
int32 ptCount;
|
||||||
|
|
||||||
|
link.Read<BRect>(&shapeFrame);
|
||||||
|
link.Read<int32>(&opCount);
|
||||||
|
link.Read<int32>(&ptCount);
|
||||||
|
|
||||||
|
ArrayDeleter<uint32> opList(new(std::nothrow) uint32[opCount]);
|
||||||
|
ArrayDeleter<BPoint> ptList(new(std::nothrow) BPoint[ptCount]);
|
||||||
|
if (opList.Get() == NULL || ptList.Get() == NULL
|
||||||
|
|| link.Read(opList.Get(), opCount * sizeof(uint32)) != B_OK
|
||||||
|
|| link.Read(ptList.Get(), ptCount * sizeof(BPoint)) != B_OK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
// This might seem a bit weird, but under BeOS, the shapes
|
||||||
|
// are always offset by the current pen location
|
||||||
|
BPoint penLocation
|
||||||
|
= fCurrentView->CurrentState()->PenLocation();
|
||||||
|
for (int32 i = 0; i < ptCount; i++) {
|
||||||
|
ptList.Get()[i] += penLocation;
|
||||||
|
}
|
||||||
|
const bool fill = (code == AS_FILL_SHAPE_GRADIENT);
|
||||||
|
picture->WriteDrawShapeGradient(opCount, opList.Get(), ptCount, ptList.Get(), *gradient, fill);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AS_FILL_REGION_GRADIENT:
|
||||||
|
{
|
||||||
|
// There is no B_PIC_FILL_REGION op, we have to
|
||||||
|
// implement it using B_PIC_FILL_RECT
|
||||||
|
BRegion region;
|
||||||
|
if (link.ReadRegion(®ion) < B_OK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
BGradient* gradient;
|
||||||
|
if (link.ReadGradient(&gradient) != B_OK)
|
||||||
|
break;
|
||||||
|
ObjectDeleter<BGradient> gradientDeleter(gradient);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < region.CountRects(); i++)
|
||||||
|
picture->WriteDrawRectGradient(region.RectAt(i), *gradient, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case AS_STROKE_LINE:
|
case AS_STROKE_LINE:
|
||||||
{
|
{
|
||||||
ViewStrokeLineInfo info;
|
ViewStrokeLineInfo info;
|
||||||
|
|||||||
Reference in New Issue
Block a user