mirror of https://gitlab.com/nakst/essence
3885 lines
148 KiB
C++
3885 lines
148 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#define UI_IMPLEMENTATION
|
|
#define ES_CRT_WITHOUT_PREFIX
|
|
#define EsPainter _EsPainter
|
|
#define EsPaintTarget _EsPaintTarget
|
|
#include "luigi.h"
|
|
#undef EsPaintTarget
|
|
#undef EsPainter
|
|
#ifndef OS_ESSENCE
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <limits.h>
|
|
#include <assert.h>
|
|
#endif
|
|
#include "hsluv.h"
|
|
|
|
// x86_64-w64-mingw32-gcc -O3 -o bin/designer2.exe -D UI_WINDOWS util/designer2.cpp -DUNICODE -lgdi32 -luser32 -lkernel32 -Wl,--subsystem,windows -fno-exceptions -fno-rtti
|
|
|
|
// TODO Needed to replace the old designer:
|
|
// Calculating opaqueInsets.
|
|
// Prototyping display: previewing state transitions.
|
|
|
|
// TODO Additional features:
|
|
// Inactive-and-disabled state.
|
|
// Undoing a delete does not preserve an instance's layer.
|
|
// Having to link to the end of a conditional object chain is a bit strange.
|
|
// Automatically cleaning up unused objects.
|
|
// Show error if property linked to object of incorrect type.
|
|
// Icons for different object types (especially color overwrite objects).
|
|
// Path layers: dashed contours.
|
|
// Picking objects: only highlight objects with an applicable type.
|
|
// Displaying radial gradients.
|
|
// Find object in graph by name.
|
|
// Auto-layout in the prototype display.
|
|
// Importing SVGs and TTFs.
|
|
// Schema-based verification of the document.
|
|
// Get text rendering on non-Essence platforms.
|
|
// Proper bezier path editor.
|
|
// Path boolean operations.
|
|
// Timeline editor for applying a given state change, with rows for possibly many different layers.
|
|
// Metrics: layoutVertical, textFigures.
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
#ifndef OS_ESSENCE
|
|
|
|
#define ES_TEXT_H_LEFT (1 << 0)
|
|
#define ES_TEXT_H_CENTER (1 << 1)
|
|
#define ES_TEXT_H_RIGHT (1 << 2)
|
|
#define ES_TEXT_V_TOP (1 << 3)
|
|
#define ES_TEXT_V_CENTER (1 << 4)
|
|
#define ES_TEXT_V_BOTTOM (1 << 5)
|
|
#define ES_TEXT_ELLIPSIS (1 << 6)
|
|
#define ES_TEXT_WRAP (1 << 7)
|
|
|
|
#define ES_FONT_SANS (0xFFFF)
|
|
#define ES_FONT_SERIF (0xFFFE)
|
|
#define ES_FONT_MONOSPACED (0xFFFD)
|
|
|
|
#define ES_RECT_4(a, b, c, d) UI_RECT_4((int32_t) (a), (int32_t) (b), (int32_t) (c), (int32_t) (d))
|
|
|
|
#define ES_FUNCTION_OPTIMISE_O2 __attribute__((optimize("-O2")))
|
|
#define ES_FUNCTION_OPTIMISE_O3 __attribute__((optimize("-O3")))
|
|
|
|
#define ES_INFINITY INFINITY
|
|
|
|
#define EsAssert assert
|
|
#define EsCRTacosf acosf
|
|
#define EsCRTatan2f atan2f
|
|
#define EsCRTceilf ceilf
|
|
#define EsCRTcosf cosf
|
|
#define EsCRTfabsf fabsf
|
|
#define EsCRTfloorf floorf
|
|
#define EsCRTfmodf fmodf
|
|
#define EsCRTisnanf isnan
|
|
#define EsCRTlog2f log2f
|
|
#define EsCRTsinf sinf
|
|
#define EsCRTsqrtf sqrtf
|
|
#define EsHeap void
|
|
#define EsMemoryCopy memcpy
|
|
#define EsMemoryCopyReverse memmove
|
|
#define EsMemoryCompare memcmp
|
|
#define EsMemoryZero(a, b) memset((a), 0, (b))
|
|
#define EsPanic(...) UI_ASSERT(false)
|
|
#define EsRectangle UIRectangle
|
|
|
|
#define ES_MEMORY_MOVE_BACKWARDS -
|
|
|
|
#define SHARED_COMMON_WANT_BUFFERS
|
|
|
|
struct EsBuffer {
|
|
union { const uint8_t *in; uint8_t *out; };
|
|
size_t position, bytes;
|
|
bool error;
|
|
void *context;
|
|
};
|
|
|
|
void *EsHeapAllocate(size_t bytes, bool zero, EsHeap *heap = nullptr) {
|
|
(void) heap;
|
|
return zero ? calloc(1, bytes) : malloc(bytes);
|
|
}
|
|
|
|
void EsHeapFree(void *pointer, size_t bytes = 0, EsHeap *heap = nullptr) {
|
|
(void) heap;
|
|
(void) bytes;
|
|
free(pointer);
|
|
}
|
|
|
|
void *EsHeapReallocate(void *oldAddress, size_t newAllocationSize, bool zeroNewSpace, EsHeap *) {
|
|
UI_ASSERT(!zeroNewSpace);
|
|
return realloc(oldAddress, newAllocationSize);
|
|
}
|
|
|
|
void EsMemoryMove(void *_start, void *_end, intptr_t amount, bool zeroEmptySpace) {
|
|
uint8_t *start = (uint8_t *) _start;
|
|
uint8_t *end = (uint8_t *) _end;
|
|
if (end < start) return;
|
|
|
|
if (amount > 0) {
|
|
EsMemoryCopyReverse(start + amount, start, end - start);
|
|
if (zeroEmptySpace) EsMemoryZero(start, amount);
|
|
} else if (amount < 0) {
|
|
EsMemoryCopy(start + amount, start, end - start);
|
|
if (zeroEmptySpace) EsMemoryZero(end + amount, -amount);
|
|
}
|
|
}
|
|
|
|
bool EsColorIsLight(uint32_t color) {
|
|
float r = (color & 0xFF0000) >> 16;
|
|
float g = (color & 0x00FF00) >> 8;
|
|
float b = (color & 0x0000FF) >> 0;
|
|
float brightness = r * r * 0.241f + g * g * 0.691f + b * b * 0.068f;
|
|
return brightness >= 180.0f * 180.0f;
|
|
}
|
|
|
|
#endif
|
|
|
|
struct EsPaintTarget {
|
|
void *bits;
|
|
uint32_t width, height, stride;
|
|
bool fullAlpha, readOnly, fromBitmap, forWindowManager;
|
|
};
|
|
|
|
struct EsPainter {
|
|
EsRectangle clip;
|
|
EsPaintTarget *target;
|
|
};
|
|
|
|
void SetBit(uint32_t *value, uint32_t bit, bool on) {
|
|
if (on) *value = *value | bit;
|
|
else *value = *value & ~bit;
|
|
}
|
|
|
|
UIRectangle ScaleRectangle(UIRectangle r, float by) {
|
|
return UI_RECT_4((int32_t) (r.l * by), (int32_t) (r.r * by), (int32_t) (r.t * by), (int32_t) (r.b * by));
|
|
}
|
|
|
|
#define IN_DESIGNER
|
|
#define DESIGNER2
|
|
|
|
#define SHARED_COMMON_WANT_RECTANGLES
|
|
#define SHARED_COMMON_WANT_RENDERING
|
|
#include "../shared/common.cpp"
|
|
#define SHARED_MATH_WANT_BASIC_UTILITIES
|
|
#define SHARED_MATH_WANT_INTERPOLATION
|
|
#include "../shared/math.cpp"
|
|
|
|
#include "../shared/array.cpp"
|
|
#include "../shared/crc.h"
|
|
#include "../shared/hash_table.cpp"
|
|
#include "../desktop/renderer.cpp"
|
|
#include "../desktop/theme.cpp"
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
const char *cPrimaryStateStrings[] = {
|
|
"Any", "Idle", "Hovered", "Pressed", "Disabled", "Inactive",
|
|
};
|
|
|
|
const char *cStateBitStrings[] = {
|
|
"Focus", "Check", "Indtm", "DefBtn", "Sel", "FcItem", "ListFc", "BfEnt", "AfExt",
|
|
};
|
|
|
|
const char *cCursorStrings[] = {
|
|
"Normal", "Text",
|
|
"Resize vertical", "Resize horizontal",
|
|
"Diagonal 1", "Diagonal 2",
|
|
"Split vertical", "Split horizontal",
|
|
"Hand hover", "Hand drag", "Hand point",
|
|
"Scroll up-left", "Scroll up", "Scroll up-right", "Scroll left",
|
|
"Scroll center", "Scroll right", "Scroll down-left", "Scroll down", "Scroll down-right",
|
|
"Select lines", "Drop text",
|
|
"Cross hair pick", "Cross hair resize",
|
|
"Move hover", "Move drag",
|
|
"Rotate hover", "Rotate drag",
|
|
"Blank",
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
#ifdef OS_ESSENCE
|
|
EsFileStore *fileStore;
|
|
|
|
const EsInstanceClassEditorSettings instanceClassEditorSettings = {
|
|
"untitled.designer", -1,
|
|
"New design", -1,
|
|
ES_ICON_TEXT_CSS,
|
|
};
|
|
#endif
|
|
|
|
struct Canvas : UIElement {
|
|
#define ARROW_MODE_NONE (0)
|
|
#define ARROW_MODE_ALL (1)
|
|
#define ARROW_MODE_FROM (2)
|
|
#define ARROW_MODE_TO (3)
|
|
#define ARROW_MODE_FROM_OR_TO (4)
|
|
#define ARROW_MODE_FROM_AND_TO (5)
|
|
uint8_t arrowMode;
|
|
|
|
bool showPrototype;
|
|
|
|
float zoom;
|
|
float panX, panY;
|
|
|
|
float swapZoom;
|
|
float swapPanX, swapPanY;
|
|
|
|
float lastPanPointX, lastPanPointY;
|
|
bool dragging, canDrag, selecting;
|
|
int32_t dragDeltaX, dragDeltaY;
|
|
int32_t selectX, selectY;
|
|
int32_t dragOffsetX, dragOffsetY;
|
|
int32_t leftDownX, leftDownY;
|
|
|
|
UIRectangle originalBounds;
|
|
UIRectangle resizeOffsets;
|
|
bool resizing;
|
|
UIElement *resizeHandles[4];
|
|
|
|
int32_t previewPrimaryState;
|
|
int32_t previewStateBits;
|
|
bool previewStateActive;
|
|
};
|
|
|
|
struct PatternEditor : UIElement {
|
|
uint8_t rows[8];
|
|
};
|
|
|
|
struct Prototype : UIElement {
|
|
};
|
|
|
|
UIWindow *window;
|
|
UIElement *inspector;
|
|
UIPanel *graphControls;
|
|
UIPanel *prototypeControls;
|
|
Canvas *canvas;
|
|
|
|
void InspectorAnnouncePropertyChanged(uint64_t objectID, const char *cPropertyName);
|
|
void InspectorPopulate();
|
|
void InspectorPickTargetEnd();
|
|
void CanvasSelectObject(struct Object *object);
|
|
void CanvasSwitchView(void *cp);
|
|
void ObjectAddCommand(void *cp);
|
|
void ObjectChangeTypeInternal(void *cp);
|
|
Rectangle8 ExportCalculatePaintOutsets(Object *object);
|
|
Rectangle8 ExportCalculateApproximateBorders(Object *object);
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
enum PropertyType : uint8_t {
|
|
PROP_NONE,
|
|
PROP_COLOR,
|
|
PROP_INT,
|
|
PROP_OBJECT,
|
|
PROP_FLOAT,
|
|
PROP_U64,
|
|
};
|
|
|
|
struct Property {
|
|
PropertyType type;
|
|
#define PROPERTY_NAME_SIZE (31)
|
|
char cName[PROPERTY_NAME_SIZE];
|
|
|
|
union {
|
|
int32_t integer;
|
|
uint64_t u64;
|
|
uint64_t object;
|
|
float floating;
|
|
};
|
|
};
|
|
|
|
enum ObjectType : uint8_t {
|
|
OBJ_NONE,
|
|
|
|
OBJ_STYLE,
|
|
OBJ_COMMENT,
|
|
OBJ_INSTANCE,
|
|
OBJ_SELECTOR,
|
|
|
|
OBJ_VAR_COLOR = 0x40,
|
|
OBJ_VAR_INT,
|
|
OBJ_VAR_TEXT_STYLE,
|
|
OBJ_VAR_CONTOUR_STYLE,
|
|
|
|
OBJ_PAINT_OVERWRITE = 0x60,
|
|
OBJ_PAINT_LINEAR_GRADIENT,
|
|
OBJ_PAINT_RADIAL_GRADIENT,
|
|
OBJ_PAINT_BIT_PATTERN,
|
|
|
|
OBJ_LAYER_BOX = 0x80,
|
|
OBJ_LAYER_METRICS,
|
|
OBJ_LAYER_TEXT,
|
|
OBJ_LAYER_GROUP,
|
|
OBJ_LAYER_PATH,
|
|
|
|
OBJ_MOD_COLOR = 0xC0,
|
|
OBJ_MOD_MULTIPLY,
|
|
};
|
|
|
|
struct Object {
|
|
ObjectType type;
|
|
#define OBJECT_NAME_SIZE (46)
|
|
char cName[OBJECT_NAME_SIZE];
|
|
#define OBJECT_IS_SELECTED (1 << 0)
|
|
#define OBJECT_IN_PROTOTYPE (1 << 1)
|
|
uint8_t flags;
|
|
uint64_t id;
|
|
Array<Property> properties;
|
|
};
|
|
|
|
enum StepType : uint8_t {
|
|
STEP_GROUP_MARKER,
|
|
STEP_MODIFY_PROPERTY,
|
|
STEP_RENAME_OBJECT,
|
|
STEP_ADD_OBJECT,
|
|
STEP_DELETE_OBJECT,
|
|
STEP_CHANGE_OBJECT_TYPE,
|
|
STEP_SET_OBJECT_DEPTH,
|
|
};
|
|
|
|
enum StepApplyMode {
|
|
STEP_APPLY_NORMAL,
|
|
STEP_APPLY_GROUPED,
|
|
STEP_APPLY_UNDO,
|
|
STEP_APPLY_REDO,
|
|
};
|
|
|
|
struct Step {
|
|
StepType type;
|
|
#define STEP_UPDATE_INSPECTOR (1 << 0)
|
|
uint32_t flags;
|
|
uint64_t objectID;
|
|
|
|
union {
|
|
Property property;
|
|
char cName[OBJECT_NAME_SIZE];
|
|
Object object;
|
|
uintptr_t depth;
|
|
};
|
|
};
|
|
|
|
struct ExportOffset {
|
|
uint64_t objectID;
|
|
uint64_t styleObjectID;
|
|
uintptr_t offset;
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
uint8_t overrideType;
|
|
};
|
|
|
|
struct ObjectTypeString {
|
|
ObjectType type;
|
|
const char *string;
|
|
};
|
|
|
|
ObjectTypeString cObjectTypeStrings[] = {
|
|
#define ADD_STRING(x) { x, #x }
|
|
ADD_STRING(OBJ_NONE),
|
|
ADD_STRING(OBJ_STYLE),
|
|
ADD_STRING(OBJ_COMMENT),
|
|
ADD_STRING(OBJ_INSTANCE),
|
|
ADD_STRING(OBJ_SELECTOR),
|
|
ADD_STRING(OBJ_VAR_COLOR),
|
|
ADD_STRING(OBJ_VAR_INT),
|
|
ADD_STRING(OBJ_VAR_TEXT_STYLE),
|
|
ADD_STRING(OBJ_VAR_CONTOUR_STYLE),
|
|
ADD_STRING(OBJ_PAINT_OVERWRITE),
|
|
ADD_STRING(OBJ_PAINT_LINEAR_GRADIENT),
|
|
ADD_STRING(OBJ_PAINT_BIT_PATTERN),
|
|
ADD_STRING(OBJ_PAINT_RADIAL_GRADIENT),
|
|
ADD_STRING(OBJ_LAYER_BOX),
|
|
ADD_STRING(OBJ_LAYER_METRICS),
|
|
ADD_STRING(OBJ_LAYER_TEXT),
|
|
ADD_STRING(OBJ_LAYER_GROUP),
|
|
ADD_STRING(OBJ_LAYER_PATH),
|
|
ADD_STRING(OBJ_MOD_COLOR),
|
|
ADD_STRING(OBJ_MOD_MULTIPLY),
|
|
#undef ADD_STRING
|
|
};
|
|
|
|
Array<Step> undoStack;
|
|
Array<Step> redoStack;
|
|
bool documentModified;
|
|
uint64_t selectedObjectID;
|
|
HashStore<uint64_t, uintptr_t> objectLookup;
|
|
Array<ExportOffset> exportOffsets;
|
|
uint8_t activeSelectorIndex;
|
|
|
|
// Document state:
|
|
Array<Object> objects;
|
|
uint64_t objectIDAllocator;
|
|
|
|
Property *PropertyFind(Object *object, const char *cName, uint8_t type = 0) {
|
|
if (object) {
|
|
for (uintptr_t i = 0; i < object->properties.Length(); i++) {
|
|
if (0 == strcmp(object->properties[i].cName, cName)) {
|
|
if (type && object->properties[i].type != type) {
|
|
return nullptr;
|
|
} else {
|
|
return &object->properties[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Object *ObjectFind(uint64_t id, bool resolveSelectors, int depth = 0) {
|
|
#if 0
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].id == id) {
|
|
return &objects[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
#else
|
|
if (!id) return nullptr;
|
|
uint64_t *index = objectLookup.Get(&id);
|
|
Object *object = index ? &objects[*index] : nullptr;
|
|
if (object) assert(object->id == id);
|
|
|
|
if (resolveSelectors && depth < 10 && object && object->type == OBJ_SELECTOR) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
snprintf(cPropertyName, sizeof(cPropertyName), "options_%d_link", activeSelectorIndex);
|
|
Property *property = PropertyFind(object, cPropertyName);
|
|
return property ? ObjectFind(property->object, true, depth + 1) : nullptr;
|
|
}
|
|
|
|
return object;
|
|
#endif
|
|
}
|
|
|
|
ExportOffset *ExportOffsetFindObject(uint64_t id) {
|
|
for (uintptr_t i = 0; i < exportOffsets.Length(); i++) {
|
|
if (exportOffsets[i].objectID == id) {
|
|
return &exportOffsets[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ExportOffset *ExportOffsetFindObjectForStyle(uint64_t id, uint64_t style) {
|
|
for (uintptr_t i = 0; i < exportOffsets.Length(); i++) {
|
|
if (exportOffsets[i].objectID == id && exportOffsets[i].styleObjectID == style) {
|
|
return &exportOffsets[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ExportOffset *ExportOffsetFindProperty(uint64_t objectID, const char *cPropertyName) {
|
|
for (uintptr_t i = 0; i < exportOffsets.Length(); i++) {
|
|
if (exportOffsets[i].objectID == objectID
|
|
&& 0 == strcmp(exportOffsets[i].cPropertyName, cPropertyName)) {
|
|
return &exportOffsets[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectSetSelected(uint64_t id, bool removeSelectedFlagFromPreviousSelection = true) {
|
|
if (selectedObjectID && removeSelectedFlagFromPreviousSelection) {
|
|
Object *object = ObjectFind(selectedObjectID, false);
|
|
if (object) object->flags &= ~OBJECT_IS_SELECTED;
|
|
}
|
|
|
|
selectedObjectID = id;
|
|
|
|
if (selectedObjectID) {
|
|
Object *object = ObjectFind(selectedObjectID, false);
|
|
if (object) object->flags |= OBJECT_IS_SELECTED;
|
|
}
|
|
|
|
if (!selectedObjectID) {
|
|
canvas->previewPrimaryState = THEME_PRIMARY_STATE_IDLE;
|
|
canvas->previewStateBits = 0;
|
|
}
|
|
}
|
|
|
|
int32_t PropertyReadInt32(Object *object, const char *cName, int32_t defaultValue = 0) {
|
|
Property *property = PropertyFind(object, cName);
|
|
return !property || property->type == PROP_OBJECT ? defaultValue : property->integer;
|
|
}
|
|
|
|
bool ObjectIsConditional(Object *object) {
|
|
return PropertyReadInt32(object, "_primaryState") || PropertyReadInt32(object, "_stateBits");
|
|
}
|
|
|
|
bool ObjectMatchesPreviewState(Object *object) {
|
|
int32_t primaryState = PropertyReadInt32(object, "_primaryState");
|
|
int32_t stateBits = PropertyReadInt32(object, "_stateBits");
|
|
|
|
return (primaryState == canvas->previewPrimaryState || !primaryState)
|
|
&& ((stateBits & canvas->previewStateBits) == stateBits);
|
|
}
|
|
|
|
Property *PropertyFindOrInherit(Object *object, const char *cName, uint8_t type = 0) {
|
|
uintptr_t depth = 0;
|
|
|
|
while (object && (depth++ < 100)) {
|
|
if (!ObjectIsConditional(object) || (canvas && canvas->previewStateActive && ObjectMatchesPreviewState(object))) {
|
|
// Return the value if the object has this property.
|
|
Property *property = PropertyFind(object, cName);
|
|
if (property) return type && property->type != type ? nullptr : property;
|
|
} else {
|
|
// Skip the object if it has a condition and it's not the original object.
|
|
}
|
|
|
|
// Go to the inheritance parent object.
|
|
Property *property = PropertyFind(object, "_parent", PROP_OBJECT);
|
|
object = ObjectFind(property ? property->object : 0, true);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int32_t PropertyFindOrInheritReadInt32(Object *object, const char *cName, int32_t defaultValue = 0) {
|
|
Property *property = PropertyFindOrInherit(object, cName, PROP_INT);
|
|
return property ? property->integer : defaultValue;
|
|
}
|
|
|
|
uint64_t PropertyFindOrInheritReadU64(Object *object, const char *cName, uint64_t defaultValue = 0) {
|
|
Property *property = PropertyFindOrInherit(object, cName, PROP_U64);
|
|
return property ? property->u64 : defaultValue;
|
|
}
|
|
|
|
float PropertyFindOrInheritReadFloat(Object *object, const char *cName, float defaultValue = 0) {
|
|
Property *property = PropertyFindOrInherit(object, cName, PROP_FLOAT);
|
|
return property ? property->floating : defaultValue;
|
|
}
|
|
|
|
Object *PropertyFindOrInheritReadObject(Object *object, const char *cName) {
|
|
Property *property = PropertyFindOrInherit(object, cName, PROP_OBJECT);
|
|
return property ? ObjectFind(property->object, true) : nullptr;
|
|
}
|
|
|
|
void ObjectLookupRebuild() {
|
|
objectLookup.Free();
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
*objectLookup.Put(&objects[i].id) = i;
|
|
}
|
|
}
|
|
|
|
void DocumentSave(void *) {
|
|
#ifdef OS_ESSENCE
|
|
EsBuffer buffer = { .canGrow = 1 };
|
|
#define fwrite(a, b, c, d) EsBufferWrite(&buffer, (a), (b) * (c))
|
|
#elif defined(UI_WINDOWS)
|
|
FILE *f = fopen("Theme Source.dat", "wb");
|
|
if (!f) return;
|
|
#else
|
|
FILE *f = fopen("res/Theme Source.dat", "wb");
|
|
#endif
|
|
|
|
uint32_t version = 1;
|
|
fwrite(&version, 1, sizeof(uint32_t), f);
|
|
uint32_t objectCount = objects.Length();
|
|
fwrite(&objectCount, 1, sizeof(uint32_t), f);
|
|
fwrite(&objectIDAllocator, 1, sizeof(uint64_t), f);
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object copy = objects[i];
|
|
uint32_t propertyCount = copy.properties.Length();
|
|
copy.properties.array = nullptr;
|
|
fwrite(©, 1, sizeof(Object), f);
|
|
fwrite(&propertyCount, 1, sizeof(uint32_t), f);
|
|
fwrite(objects[i].properties.array, 1, sizeof(Property) * propertyCount, f);
|
|
}
|
|
|
|
#ifdef OS_ESSENCE
|
|
EsFileStoreWriteAll(fileStore, buffer.out, buffer.position);
|
|
EsHeapFree(buffer.out);
|
|
#else
|
|
fclose(f);
|
|
#endif
|
|
documentModified = false;
|
|
}
|
|
|
|
void DocumentLoad() {
|
|
#ifdef OS_ESSENCE
|
|
EsBuffer buffer = {};
|
|
buffer.out = (uint8_t *) EsFileStoreReadAll(fileStore, &buffer.bytes);
|
|
#define fread(a, b, c, d) EsBufferReadInto(&buffer, (a), (b) * (c))
|
|
#elif defined(UI_WINDOWS)
|
|
FILE *f = fopen("Theme Source.dat", "rb");
|
|
if (!f) return;
|
|
#else
|
|
FILE *f = fopen("res/Theme Source.dat", "rb");
|
|
if (!f) return;
|
|
#endif
|
|
|
|
uint32_t version = 1;
|
|
fread(&version, 1, sizeof(uint32_t), f);
|
|
uint32_t objectCount = 0;
|
|
fread(&objectCount, 1, sizeof(uint32_t), f);
|
|
fread(&objectIDAllocator, 1, sizeof(uint64_t), f);
|
|
|
|
for (uintptr_t i = 0; i < objectCount; i++) {
|
|
Object object = {};
|
|
fread(&object, 1, sizeof(Object), f);
|
|
object.cName[OBJECT_NAME_SIZE - 1] = 0;
|
|
uint32_t propertyCount = 0;
|
|
fread(&propertyCount, 1, sizeof(uint32_t), f);
|
|
object.properties.InsertMany(0, propertyCount);
|
|
fread(object.properties.array, 1, sizeof(Property) * propertyCount, f);
|
|
object.flags &= ~OBJECT_IS_SELECTED;
|
|
|
|
for (uintptr_t i = 0; i < propertyCount; i++) {
|
|
object.properties[i].cName[PROPERTY_NAME_SIZE - 1] = 0;
|
|
}
|
|
|
|
objects.Add(object);
|
|
}
|
|
|
|
ObjectLookupRebuild();
|
|
|
|
#ifdef OS_ESSENCE
|
|
EsHeapFree(buffer.out);
|
|
#else
|
|
fclose(f);
|
|
#endif
|
|
}
|
|
|
|
void DocumentFree() {
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
objects[i].properties.Free();
|
|
}
|
|
|
|
objects.Free();
|
|
undoStack.Free();
|
|
redoStack.Free();
|
|
ObjectLookupRebuild();
|
|
}
|
|
|
|
void DocumentApplyStep(Step step, StepApplyMode mode = STEP_APPLY_NORMAL) {
|
|
bool allowMerge = false;
|
|
|
|
if (step.type == STEP_GROUP_MARKER) {
|
|
} else if (step.type == STEP_MODIFY_PROPERTY) {
|
|
Object *object = ObjectFind(step.objectID, false);
|
|
UI_ASSERT(object);
|
|
|
|
Property *property = PropertyFind(object, step.property.cName);
|
|
|
|
if (property) {
|
|
Property oldValue = *property;
|
|
|
|
if (step.property.type != PROP_NONE) {
|
|
// Update the property.
|
|
*property = step.property;
|
|
allowMerge = true;
|
|
} else {
|
|
// Remove the property.
|
|
for (uintptr_t i = 0; i < object->properties.Length(); i++) {
|
|
if (0 == strcmp(object->properties[i].cName, step.property.cName)) {
|
|
object->properties.DeleteSwap(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
step.property = oldValue;
|
|
} else {
|
|
if (step.property.type != PROP_NONE) {
|
|
// Add the property.
|
|
object->properties.Add(step.property);
|
|
step.property.type = PROP_NONE;
|
|
} else {
|
|
// Asking to remove a property that does not exist.
|
|
// Probably from a remove broadcast.
|
|
}
|
|
}
|
|
|
|
UIElementRefresh(canvas);
|
|
|
|
if (step.flags & STEP_UPDATE_INSPECTOR) {
|
|
InspectorPopulate();
|
|
} else {
|
|
InspectorAnnouncePropertyChanged(step.objectID, step.property.cName);
|
|
}
|
|
} else if (step.type == STEP_RENAME_OBJECT) {
|
|
Object *object = ObjectFind(step.objectID, false);
|
|
UI_ASSERT(object);
|
|
|
|
char oldName[OBJECT_NAME_SIZE];
|
|
strcpy(oldName, object->cName);
|
|
strcpy(object->cName, step.cName);
|
|
strcpy(step.cName, oldName);
|
|
|
|
UIElementRepaint(canvas, nullptr);
|
|
InspectorPopulate();
|
|
} else if (step.type == STEP_ADD_OBJECT) {
|
|
objects.Add(step.object);
|
|
ObjectLookupRebuild();
|
|
UIElementRepaint(canvas, nullptr);
|
|
step.objectID = step.object.id;
|
|
step.type = STEP_DELETE_OBJECT;
|
|
} else if (step.type == STEP_CHANGE_OBJECT_TYPE) {
|
|
Object *object = ObjectFind(step.object.id, false);
|
|
ObjectType oldType = object->type;
|
|
object->type = step.object.type;
|
|
step.object.type = oldType;
|
|
InspectorPopulate();
|
|
UIElementRefresh(canvas);
|
|
} else if (step.type == STEP_DELETE_OBJECT) {
|
|
if (selectedObjectID == step.objectID) {
|
|
ObjectSetSelected(0);
|
|
}
|
|
|
|
step.type = STEP_ADD_OBJECT;
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].id == step.objectID) {
|
|
step.object = objects[i];
|
|
objects.Delete(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ObjectLookupRebuild();
|
|
step.object.flags = 0;
|
|
UIElementRefresh(canvas);
|
|
InspectorPopulate();
|
|
} else if (step.type == STEP_SET_OBJECT_DEPTH) {
|
|
uintptr_t newDepth = step.depth;
|
|
bool found = false;
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].id == step.objectID) {
|
|
step.depth = i;
|
|
Object object = objects[i];
|
|
objects.Delete(i);
|
|
objects.Insert(object, newDepth);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
UI_ASSERT(found);
|
|
ObjectLookupRebuild();
|
|
UIElementRefresh(canvas);
|
|
} else {
|
|
UI_ASSERT(false);
|
|
}
|
|
|
|
if (mode == STEP_APPLY_NORMAL || mode == STEP_APPLY_GROUPED) {
|
|
bool merge = false;
|
|
|
|
if (allowMerge && undoStack.Length() > 2 && !redoStack.Length() && (~step.flags & STEP_UPDATE_INSPECTOR)) {
|
|
Step last = undoStack[undoStack.Length() - (undoStack.Last().type == STEP_GROUP_MARKER ? 2 : 1)];
|
|
|
|
if (step.type == STEP_MODIFY_PROPERTY && last.type == STEP_MODIFY_PROPERTY
|
|
&& last.objectID == step.objectID && 0 == strcmp(last.property.cName, step.property.cName)) {
|
|
merge = true;
|
|
}
|
|
}
|
|
|
|
if (!merge) {
|
|
undoStack.Add(step);
|
|
|
|
for (uintptr_t i = 0; i < redoStack.Length(); i++) {
|
|
if (redoStack[i].type == STEP_ADD_OBJECT) {
|
|
redoStack[i].object.properties.Free();
|
|
}
|
|
}
|
|
|
|
redoStack.Free();
|
|
|
|
if (mode != STEP_APPLY_GROUPED) {
|
|
Step step = {};
|
|
step.type = STEP_GROUP_MARKER;
|
|
undoStack.Add(step);
|
|
}
|
|
}
|
|
} else if (mode == STEP_APPLY_UNDO) {
|
|
redoStack.Add(step);
|
|
} else if (mode == STEP_APPLY_REDO) {
|
|
undoStack.Add(step);
|
|
}
|
|
|
|
documentModified = true;
|
|
InspectorPickTargetEnd();
|
|
|
|
#ifdef UI_ESSENCE
|
|
EsInstanceSetModified(ui.instance, true);
|
|
#endif
|
|
}
|
|
|
|
void DocumentUndoStep(void *) {
|
|
if (!undoStack.Length()) return;
|
|
|
|
Step marker = undoStack.Pop();
|
|
UI_ASSERT(marker.type == STEP_GROUP_MARKER);
|
|
|
|
do {
|
|
DocumentApplyStep(undoStack.Pop(), STEP_APPLY_UNDO);
|
|
} while (undoStack.Length() && undoStack.Last().type != STEP_GROUP_MARKER);
|
|
|
|
redoStack.Add(marker);
|
|
}
|
|
|
|
void DocumentRedoStep(void *) {
|
|
if (!redoStack.Length()) return;
|
|
|
|
Step marker = redoStack.Pop();
|
|
UI_ASSERT(marker.type == STEP_GROUP_MARKER);
|
|
|
|
do {
|
|
DocumentApplyStep(redoStack.Pop(), STEP_APPLY_REDO);
|
|
} while (redoStack.Length() && redoStack.Last().type != STEP_GROUP_MARKER);
|
|
|
|
undoStack.Add(marker);
|
|
}
|
|
|
|
void DocumentSwapPropertyPrefixes(Object *object, Step step, const char *cPrefix0, const char *cPrefix1, bool last, bool moveOnly) {
|
|
char cNewName[PROPERTY_NAME_SIZE];
|
|
Array<Step> steps = {};
|
|
|
|
for (uintptr_t i = 0; i < object->properties.Length(); i++) {
|
|
if (0 == memcmp(object->properties[i].cName, cPrefix0, strlen(cPrefix0))
|
|
|| 0 == memcmp(object->properties[i].cName, cPrefix1, strlen(cPrefix1))) {
|
|
strcpy(step.property.cName, object->properties[i].cName);
|
|
step.property.type = PROP_NONE;
|
|
steps.Add(step);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < object->properties.Length(); i++) {
|
|
if (!moveOnly && 0 == memcmp(object->properties[i].cName, cPrefix0, strlen(cPrefix0))) {
|
|
strcpy(cNewName, cPrefix1);
|
|
strcat(cNewName, object->properties[i].cName + strlen(cPrefix0));
|
|
step.property = object->properties[i];
|
|
strcpy(step.property.cName, cNewName);
|
|
steps.Add(step);
|
|
} else if (0 == memcmp(object->properties[i].cName, cPrefix1, strlen(cPrefix1))) {
|
|
strcpy(cNewName, cPrefix0);
|
|
strcat(cNewName, object->properties[i].cName + strlen(cPrefix1));
|
|
step.property = object->properties[i];
|
|
strcpy(step.property.cName, cNewName);
|
|
steps.Add(step);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < steps.Length(); i++) {
|
|
DocumentApplyStep(steps[i], (i == steps.Length() - 1 && last) ? STEP_APPLY_NORMAL : STEP_APPLY_GROUPED);
|
|
}
|
|
|
|
steps.Free();
|
|
}
|
|
|
|
int PropertyCompareNames(const void *left, const void *right) {
|
|
return strcmp(((*(Property *) left)).cName, ((*(Property *) right)).cName);
|
|
}
|
|
|
|
void TextStyleRemoveDuplicates(void *cp) {
|
|
// Remove all duplicates of this text style,
|
|
// and redirect objects that linked to them to this object.
|
|
|
|
Object *object = ObjectFind((uintptr_t) cp, false);
|
|
if (!object) return;
|
|
|
|
// TODO Support undo.
|
|
if (strcmp("OK", UIDialogShow(window, 0, "Warning: You cannot undo this action.\n%f%b%b", "OK", "Cancel"))) return;
|
|
|
|
// Start by sorting the property list so we can detect duplicates easier.
|
|
qsort(object->properties.array, object->properties.Length(), sizeof(Property), PropertyCompareNames);
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *other = &objects[i];
|
|
if (other->type != OBJ_VAR_TEXT_STYLE || other == object) continue;
|
|
qsort(other->properties.array, other->properties.Length(), sizeof(Property), PropertyCompareNames);
|
|
|
|
uintptr_t pi = 0, pj = 0;
|
|
|
|
while (pi != object->properties.Length() && pj != other->properties.Length()) {
|
|
// Ignore the graph position when checking for duplicates.
|
|
if (0 == strcmp(object->properties[pi].cName, "_graphX")) { pi++; continue; }
|
|
if (0 == strcmp(object->properties[pi].cName, "_graphY")) { pi++; continue; }
|
|
if (0 == strcmp(object->properties[pi].cName, "_graphW")) { pi++; continue; }
|
|
if (0 == strcmp(object->properties[pi].cName, "_graphH")) { pi++; continue; }
|
|
if (0 == strcmp(other ->properties[pj].cName, "_graphX")) { pj++; continue; }
|
|
if (0 == strcmp(other ->properties[pj].cName, "_graphY")) { pj++; continue; }
|
|
if (0 == strcmp(other ->properties[pj].cName, "_graphW")) { pj++; continue; }
|
|
if (0 == strcmp(other ->properties[pj].cName, "_graphH")) { pj++; continue; }
|
|
|
|
// Check if the properties are identical.
|
|
if (strcmp(object->properties[pi].cName, other->properties[pj].cName)) break;
|
|
if (object->properties[pi].type != other->properties[pj].type) break;
|
|
if (object->properties[pi].type == PROP_INT && object->properties[pi].integer != other->properties[pj].integer ) break;
|
|
if (object->properties[pi].type == PROP_COLOR && object->properties[pi].integer != other->properties[pj].integer ) break;
|
|
if (object->properties[pi].type == PROP_U64 && object->properties[pi].u64 != other->properties[pj].u64 ) break;
|
|
if (object->properties[pi].type == PROP_OBJECT && object->properties[pi].object != other->properties[pj].object ) break;
|
|
if (object->properties[pi].type == PROP_FLOAT && object->properties[pi].floating != other->properties[pj].floating) break;
|
|
|
|
pi++, pj++;
|
|
}
|
|
|
|
if (pi != object->properties.Length() || pj != other->properties.Length()) {
|
|
continue;
|
|
}
|
|
|
|
// Duplicate found. Delete and relink.
|
|
uint64_t replaceID = other->id;
|
|
objects.Delete(i);
|
|
ObjectLookupRebuild();
|
|
object = ObjectFind((uintptr_t) cp, false);
|
|
assert(object);
|
|
i--;
|
|
|
|
for (uintptr_t j = 0; j < objects.Length(); j++) {
|
|
Object *source = &objects[j];
|
|
|
|
for (uintptr_t k = 0; k < source->properties.Length(); k++) {
|
|
if (source->properties[k].type == PROP_OBJECT && source->properties[k].object == replaceID) {
|
|
source->properties[k].object = object->id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
enum InspectorElementType {
|
|
INSPECTOR_INVALID_ELEMENT,
|
|
INSPECTOR_REMOVE_BUTTON,
|
|
INSPECTOR_REMOVE_BUTTON_BROADCAST,
|
|
INSPECTOR_COLOR_PICKER,
|
|
INSPECTOR_COLOR_TEXTBOX,
|
|
INSPECTOR_INTEGER_TEXTBOX,
|
|
INSPECTOR_INTEGER_TEXTBOX_BROADCAST,
|
|
INSPECTOR_FLOAT_TEXTBOX,
|
|
INSPECTOR_LINK,
|
|
INSPECTOR_LINK_BROADCAST,
|
|
INSPECTOR_BOOLEAN_TOGGLE,
|
|
INSPECTOR_MASK_BIT_TOGGLE,
|
|
INSPECTOR_RADIO_SWITCH,
|
|
INSPECTOR_CURSOR_DROP_DOWN,
|
|
INSPECTOR_ADD_ARRAY_ITEM,
|
|
INSPECTOR_SWAP_ARRAY_ITEMS,
|
|
INSPECTOR_DELETE_ARRAY_ITEM,
|
|
INSPECTOR_PATTERN_EDITOR,
|
|
};
|
|
|
|
struct InspectorBindingData {
|
|
UIElement *element;
|
|
uint64_t objectID;
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
const char *cEnablePropertyName;
|
|
InspectorElementType elementType;
|
|
int32_t choiceValue;
|
|
};
|
|
|
|
Array<UIElement *> inspectorBoundElements;
|
|
UIElement *inspectorActivePropertyStepElement;
|
|
InspectorBindingData *inspectorMenuData;
|
|
InspectorBindingData *inspectorPickData;
|
|
|
|
void InspectorPickTargetEnd() {
|
|
if (inspectorPickData) {
|
|
inspectorPickData = nullptr;
|
|
UIElementRepaint(canvas, nullptr);
|
|
}
|
|
}
|
|
|
|
void InspectorUpdateSingleElementEnable(InspectorBindingData *data) {
|
|
UI_ASSERT(data->cEnablePropertyName);
|
|
bool enabled = PropertyReadInt32(ObjectFind(data->objectID, false), data->cEnablePropertyName);
|
|
SetBit(&data->element->flags, UI_ELEMENT_DISABLED, !enabled);
|
|
UIElementRefresh(data->element);
|
|
}
|
|
|
|
void InspectorUpdateSingleElement(InspectorBindingData *data) {
|
|
if (data->element->flags & UI_ELEMENT_DESTROY) {
|
|
return;
|
|
}
|
|
|
|
if (data->elementType == INSPECTOR_REMOVE_BUTTON || data->elementType == INSPECTOR_REMOVE_BUTTON_BROADCAST) {
|
|
UIButton *button = (UIButton *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName);
|
|
SetBit(&data->element->flags, UI_ELEMENT_DISABLED, !property);
|
|
button->label[0] = property ? 'X' : '-';
|
|
UIElementRefresh(&button->e);
|
|
} else if (data->elementType == INSPECTOR_BOOLEAN_TOGGLE) {
|
|
UICheckbox *box = (UICheckbox *) data->element;
|
|
box->check = PropertyReadInt32(ObjectFind(data->objectID, false), data->cPropertyName, 2);
|
|
if ((~box->e.flags & UI_CHECKBOX_ALLOW_INDETERMINATE)) box->check &= 1;
|
|
UIElementRefresh(&box->e);
|
|
} else if (data->elementType == INSPECTOR_MASK_BIT_TOGGLE) {
|
|
UICheckbox *box = (UICheckbox *) data->element;
|
|
box->check = (PropertyReadInt32(ObjectFind(data->objectID, false), data->cPropertyName) & data->choiceValue) ? UI_CHECK_CHECKED : UI_CHECK_UNCHECKED;
|
|
UIElementRefresh(&box->e);
|
|
} else if (data->elementType == INSPECTOR_RADIO_SWITCH) {
|
|
UIButton *button = (UIButton *) data->element;
|
|
int32_t value = PropertyReadInt32(ObjectFind(data->objectID, false), data->cPropertyName);
|
|
SetBit(&button->e.flags, UI_BUTTON_CHECKED, value == data->choiceValue);
|
|
UIElementRefresh(&button->e);
|
|
} else if (data->elementType == INSPECTOR_CURSOR_DROP_DOWN) {
|
|
UIButton *button = (UIButton *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_INT);
|
|
UI_FREE(button->label);
|
|
button->label = UIStringCopy(property ? cCursorStrings[property->integer] : "---", (button->labelBytes = -1));
|
|
UIElementRefresh(&button->e);
|
|
} else if (data->elementType == INSPECTOR_COLOR_PICKER) {
|
|
UIColorPicker *colorPicker = (UIColorPicker *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_COLOR);
|
|
uint32_t color = property ? property->integer : 0xFFFFFFFF;
|
|
colorPicker->opacity = (color >> 24) / 255.0f;
|
|
UIColorToHSV(color, &colorPicker->hue, &colorPicker->saturation, &colorPicker->value);
|
|
UIElementRefresh(&colorPicker->e);
|
|
} else if (data->elementType == INSPECTOR_COLOR_TEXTBOX) {
|
|
UITextbox *textbox = (UITextbox *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_COLOR);
|
|
char buffer[32] = "";
|
|
if (property) snprintf(buffer, sizeof(buffer), "%.8X", (uint32_t) property->integer);
|
|
UITextboxClear(textbox, false);
|
|
UITextboxReplace(textbox, buffer, -1, false);
|
|
UIElementRefresh(&textbox->e);
|
|
} else if (data->elementType == INSPECTOR_INTEGER_TEXTBOX || data->elementType == INSPECTOR_INTEGER_TEXTBOX_BROADCAST) {
|
|
UITextbox *textbox = (UITextbox *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_INT);
|
|
char buffer[32] = "";
|
|
if (property) snprintf(buffer, sizeof(buffer), "%d", property->integer);
|
|
UITextboxClear(textbox, false);
|
|
UITextboxReplace(textbox, buffer, -1, false);
|
|
UIElementRefresh(&textbox->e);
|
|
} else if (data->elementType == INSPECTOR_FLOAT_TEXTBOX) {
|
|
UITextbox *textbox = (UITextbox *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_FLOAT);
|
|
char buffer[32] = "";
|
|
if (property) snprintf(buffer, sizeof(buffer), "%.2f", property->floating);
|
|
UITextboxClear(textbox, false);
|
|
UITextboxReplace(textbox, buffer, -1, false);
|
|
UIElementRefresh(&textbox->e);
|
|
} else if (data->elementType == INSPECTOR_LINK || data->elementType == INSPECTOR_LINK_BROADCAST) {
|
|
UIButton *button = (UIButton *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_OBJECT);
|
|
Object *target = ObjectFind(property ? property->object : 0, false);
|
|
const char *string = target ? (target->cName[0] ? target->cName : "(untitled)") : "---";
|
|
UI_FREE(button->label);
|
|
button->label = UIStringCopy(string, (button->labelBytes = -1));
|
|
UIElementRefresh(&button->e);
|
|
} else if (data->elementType == INSPECTOR_PATTERN_EDITOR) {
|
|
PatternEditor *editor = (PatternEditor *) data->element;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_U64);
|
|
if (property) memcpy(editor->rows, &property->u64, sizeof(uint64_t));
|
|
else memset(editor->rows, 0, sizeof(uint64_t));
|
|
UIElementRefresh(editor);
|
|
} else if (data->elementType == INSPECTOR_ADD_ARRAY_ITEM || data->elementType == INSPECTOR_SWAP_ARRAY_ITEMS
|
|
|| data->elementType == INSPECTOR_DELETE_ARRAY_ITEM) {
|
|
} else {
|
|
UI_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
void InspectorAnnouncePropertyChanged(uint64_t objectID, const char *cPropertyName) {
|
|
for (uintptr_t i = 0; i < inspectorBoundElements.Length(); i++) {
|
|
InspectorBindingData *data = (InspectorBindingData *) inspectorBoundElements[i]->cp;
|
|
if (data->element == inspectorActivePropertyStepElement) continue;
|
|
if (data->objectID != objectID) continue;
|
|
|
|
if (data->cEnablePropertyName && 0 == strcmp(data->cEnablePropertyName, cPropertyName)) {
|
|
InspectorUpdateSingleElementEnable(data);
|
|
}
|
|
|
|
if (0 == strcmp(data->cPropertyName, cPropertyName)) {
|
|
InspectorUpdateSingleElement(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InspectorBroadcastStep(Step step, InspectorBindingData *data) {
|
|
if (data->elementType == INSPECTOR_INTEGER_TEXTBOX_BROADCAST || data->elementType == INSPECTOR_LINK_BROADCAST
|
|
|| data->elementType == INSPECTOR_REMOVE_BUTTON_BROADCAST) {
|
|
for (char i = '1'; i <= '3'; i++) {
|
|
step.property.cName[strlen(step.property.cName) - 1] = i;
|
|
DocumentApplyStep(step, STEP_APPLY_GROUPED);
|
|
}
|
|
|
|
strcpy(step.property.cName, data->cPropertyName);
|
|
}
|
|
}
|
|
|
|
void InspectorCursorDropDownMenuInvoke(void *cp) {
|
|
intptr_t index = (intptr_t) cp;
|
|
Step step = {};
|
|
step.type = STEP_MODIFY_PROPERTY;
|
|
step.objectID = inspectorMenuData->objectID;
|
|
strcpy(step.property.cName, inspectorMenuData->cPropertyName);
|
|
step.property.type = index == -1 ? PROP_NONE : PROP_INT;
|
|
step.property.integer = index;
|
|
DocumentApplyStep(step);
|
|
inspectorMenuData = nullptr;
|
|
}
|
|
|
|
int InspectorBoundMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
InspectorBindingData *data = (InspectorBindingData *) element->cp;
|
|
|
|
if (message == UI_MSG_DESTROY) {
|
|
inspectorBoundElements.FindAndDeleteSwap(element, true);
|
|
free(data);
|
|
data = nullptr;
|
|
} else if (message == UI_MSG_VALUE_CHANGED) {
|
|
Step step = {};
|
|
step.type = STEP_MODIFY_PROPERTY;
|
|
step.objectID = data->objectID;
|
|
strcpy(step.property.cName, data->cPropertyName);
|
|
|
|
if (data->elementType == INSPECTOR_COLOR_PICKER) {
|
|
UIColorPicker *colorPicker = (UIColorPicker *) element;
|
|
uint32_t color;
|
|
UIColorToRGB(colorPicker->hue, colorPicker->saturation, colorPicker->value, &color);
|
|
color |= (uint32_t) (colorPicker->opacity * 255.0f) << 24;
|
|
step.property.type = PROP_COLOR;
|
|
step.property.integer = (int32_t) color;
|
|
} else if (data->elementType == INSPECTOR_COLOR_TEXTBOX) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
char buffer[32];
|
|
int length = 31 > textbox->bytes ? textbox->bytes : 31;
|
|
memcpy(buffer, textbox->string, length);
|
|
buffer[length] = 0;
|
|
step.property.type = PROP_COLOR;
|
|
step.property.integer = (int32_t) strtoul(buffer, nullptr, 16);
|
|
} else if (data->elementType == INSPECTOR_INTEGER_TEXTBOX || data->elementType == INSPECTOR_INTEGER_TEXTBOX_BROADCAST) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
char buffer[32];
|
|
int length = 31 > textbox->bytes ? textbox->bytes : 31;
|
|
memcpy(buffer, textbox->string, length);
|
|
buffer[length] = 0;
|
|
step.property.type = PROP_INT;
|
|
step.property.integer = (int32_t) strtol(buffer, nullptr, 10);
|
|
InspectorBroadcastStep(step, data);
|
|
} else if (data->elementType == INSPECTOR_FLOAT_TEXTBOX) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
char buffer[32];
|
|
int length = 31 > textbox->bytes ? textbox->bytes : 31;
|
|
memcpy(buffer, textbox->string, length);
|
|
buffer[length] = 0;
|
|
step.property.type = PROP_FLOAT;
|
|
step.property.floating = strtof(buffer, nullptr);
|
|
InspectorBroadcastStep(step, data);
|
|
} else {
|
|
UI_ASSERT(false);
|
|
}
|
|
|
|
inspectorActivePropertyStepElement = element; // Don't tell this element about the step.
|
|
DocumentApplyStep(step);
|
|
inspectorActivePropertyStepElement = nullptr;
|
|
} else if (message == UI_MSG_CLICKED) {
|
|
Step step = {};
|
|
step.type = STEP_MODIFY_PROPERTY;
|
|
step.objectID = data->objectID;
|
|
strcpy(step.property.cName, data->cPropertyName);
|
|
|
|
if (data->elementType == INSPECTOR_REMOVE_BUTTON || data->elementType == INSPECTOR_REMOVE_BUTTON_BROADCAST) {
|
|
step.property.type = PROP_NONE; // Remove the property.
|
|
InspectorBroadcastStep(step, data);
|
|
DocumentApplyStep(step);
|
|
} else if (data->elementType == INSPECTOR_LINK || data->elementType == INSPECTOR_LINK_BROADCAST) {
|
|
char *name = nullptr;
|
|
const char *dialog = "Enter the name of the new link target object:\n%t\n\n%l\n\n%f%b%b";
|
|
const char *result = UIDialogShow(window, 0, dialog, &name, "OK", "Cancel");
|
|
|
|
if (0 == strcmp(result, "OK")) {
|
|
uint64_t id = 0;
|
|
|
|
if (name && name[0]) {
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (0 == strcmp(objects[i].cName, name)) {
|
|
id = objects[i].id;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!id) {
|
|
UIDialogShow(window, 0, "Error: The object was not found.\n%f%b", "OK");
|
|
} else {
|
|
step.property.type = PROP_OBJECT;
|
|
step.property.object = id;
|
|
InspectorBroadcastStep(step, data);
|
|
DocumentApplyStep(step);
|
|
}
|
|
}
|
|
|
|
free(name);
|
|
} else if (data->elementType == INSPECTOR_RADIO_SWITCH) {
|
|
step.property.type = PROP_INT;
|
|
step.property.integer = data->choiceValue;
|
|
DocumentApplyStep(step);
|
|
} else if (data->elementType == INSPECTOR_BOOLEAN_TOGGLE) {
|
|
UICheckbox *box = (UICheckbox *) element;
|
|
step.property.integer = (box->check + 1) % ((box->e.flags & UI_CHECKBOX_ALLOW_INDETERMINATE) ? 3 : 2);
|
|
step.property.type = step.property.integer == UI_CHECK_INDETERMINATE ? PROP_NONE : PROP_INT;
|
|
DocumentApplyStep(step);
|
|
return 1; // InspectorUpdateSingleElement will update the check.
|
|
} else if (data->elementType == INSPECTOR_MASK_BIT_TOGGLE) {
|
|
UICheckbox *box = (UICheckbox *) element;
|
|
step.property.type = PROP_INT;
|
|
step.property.integer = PropertyReadInt32(ObjectFind(data->objectID, false), data->cPropertyName);
|
|
SetBit((uint32_t *) &step.property.integer, data->choiceValue, box->check == UI_CHECK_UNCHECKED);
|
|
DocumentApplyStep(step);
|
|
return 1; // InspectorUpdateSingleElement will update the check.
|
|
} else if (data->elementType == INSPECTOR_CURSOR_DROP_DOWN) {
|
|
UIMenu *menu = UIMenuCreate(window->pressed, UI_MENU_NO_SCROLL);
|
|
UIMenuAddItem(menu, 0, "Inherit", -1, InspectorCursorDropDownMenuInvoke, (void *) (intptr_t) -1);
|
|
|
|
for (uintptr_t i = 0; i < sizeof(cCursorStrings) / sizeof(cCursorStrings[0]); i++) {
|
|
UIMenuAddItem(menu, 0, cCursorStrings[i], -1, InspectorCursorDropDownMenuInvoke, (void *) (intptr_t) i);
|
|
}
|
|
|
|
inspectorMenuData = data;
|
|
UIMenuShow(menu);
|
|
} else if (data->elementType == INSPECTOR_ADD_ARRAY_ITEM) {
|
|
step.property.type = PROP_INT;
|
|
step.property.integer = 1 + PropertyReadInt32(ObjectFind(data->objectID, false), data->cPropertyName);
|
|
step.flags |= STEP_UPDATE_INSPECTOR;
|
|
DocumentApplyStep(step);
|
|
} else if (data->elementType == INSPECTOR_SWAP_ARRAY_ITEMS) {
|
|
char cPrefix0[PROPERTY_NAME_SIZE];
|
|
char cPrefix1[PROPERTY_NAME_SIZE];
|
|
Object *object = ObjectFind(data->objectID, false);
|
|
|
|
strcpy(cPrefix0, data->cPropertyName);
|
|
strcpy(cPrefix1, data->cPropertyName);
|
|
|
|
for (intptr_t i = strlen(cPrefix0) - 2; i >= 0; i--) {
|
|
if (cPrefix0[i] == '_') {
|
|
int32_t index = atoi(cPrefix0 + i + 1);
|
|
sprintf(cPrefix1 + i + 1, "%d_", index + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DocumentSwapPropertyPrefixes(object, step, cPrefix0, cPrefix1, true /* last */, false);
|
|
} else if (data->elementType == INSPECTOR_DELETE_ARRAY_ITEM) {
|
|
char cPrefix0[PROPERTY_NAME_SIZE];
|
|
char cPrefix1[PROPERTY_NAME_SIZE];
|
|
int32_t index = -1;
|
|
int32_t count = -1;
|
|
intptr_t offset = strlen(data->cPropertyName) - 2;
|
|
Object *object = ObjectFind(data->objectID, false);
|
|
|
|
for (; offset >= 0; offset--) {
|
|
if (data->cPropertyName[offset] == '_') {
|
|
index = atoi(data->cPropertyName + offset + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
strcpy(cPrefix0, data->cPropertyName);
|
|
strcpy(cPrefix0 + offset + 1, "count");
|
|
count = PropertyReadInt32(ObjectFind(data->objectID, false), cPrefix0);
|
|
|
|
for (int32_t i = index; i < count - 1; i++) {
|
|
strcpy(cPrefix0, data->cPropertyName);
|
|
strcpy(cPrefix1, data->cPropertyName);
|
|
sprintf(cPrefix0 + offset + 1, "%d_", i + 0);
|
|
sprintf(cPrefix1 + offset + 1, "%d_", i + 1);
|
|
DocumentSwapPropertyPrefixes(object, step, cPrefix0, cPrefix1, false, true /* moveOnly */);
|
|
}
|
|
|
|
strcpy(cPrefix0, data->cPropertyName);
|
|
sprintf(cPrefix0 + offset + 1, "%d_", count - 1);
|
|
|
|
Array<Step> steps = {};
|
|
|
|
for (uintptr_t i = 0; i < object->properties.Length(); i++) {
|
|
if (0 == memcmp(object->properties[i].cName, cPrefix0, strlen(cPrefix0))) {
|
|
strcpy(step.property.cName, object->properties[i].cName);
|
|
step.property.type = PROP_NONE;
|
|
steps.Add(step);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < steps.Length(); i++) {
|
|
DocumentApplyStep(steps[i], STEP_APPLY_GROUPED);
|
|
}
|
|
|
|
steps.Free();
|
|
|
|
strcpy(step.property.cName, data->cPropertyName);
|
|
strcpy(step.property.cName + offset + 1, "count");
|
|
step.property.type = PROP_INT;
|
|
step.property.integer = count - 1;
|
|
step.flags |= STEP_UPDATE_INSPECTOR;
|
|
DocumentApplyStep(step);
|
|
}
|
|
} else if (message == UI_MSG_UPDATE) {
|
|
if (di == UI_UPDATE_FOCUSED && element->window->focused == element
|
|
&& (data->elementType == INSPECTOR_COLOR_TEXTBOX || data->elementType == INSPECTOR_INTEGER_TEXTBOX
|
|
|| data->elementType == INSPECTOR_INTEGER_TEXTBOX_BROADCAST || data->elementType == INSPECTOR_FLOAT_TEXTBOX)) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PatternEditorMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
PatternEditor *editor = (PatternEditor *) element;
|
|
const int zoom = 20;
|
|
|
|
bool leftDraw = (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1) || message == UI_MSG_LEFT_DOWN;
|
|
bool rightDraw = (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 3) || message == UI_MSG_RIGHT_DOWN;
|
|
|
|
if (message == UI_MSG_GET_WIDTH || message == UI_MSG_GET_HEIGHT) {
|
|
return 8 * zoom;
|
|
} else if (message == UI_MSG_PAINT) {
|
|
for (int32_t y = 0; y < 8; y++) {
|
|
for (int32_t x = 0; x < 8; x++) {
|
|
UIRectangle tile = UI_RECT_4(element->bounds.l + zoom * x, element->bounds.l + zoom * (x + 1),
|
|
element->bounds.t + zoom * y, element->bounds.t + zoom * (y + 1));
|
|
UIDrawBorder((UIPainter *) dp, tile, 0xFF000000, UI_RECT_1(1));
|
|
if ((editor->rows[y] >> x) & 1) UIDrawInvert((UIPainter *) dp, tile);
|
|
}
|
|
}
|
|
} else if (leftDraw || rightDraw) {
|
|
int32_t x = (element->window->cursorX - element->bounds.l) / zoom;
|
|
int32_t y = (element->window->cursorY - element->bounds.t) / zoom;
|
|
if (leftDraw && x >= 0 && x < 8 && y >= 0 && y < 8) editor->rows[y] |= 1 << x;
|
|
if (rightDraw && x >= 0 && x < 8 && y >= 0 && y < 8) editor->rows[y] &= ~(1 << x);
|
|
UIElementRepaint(element, nullptr);
|
|
|
|
InspectorBindingData *data = (InspectorBindingData *) element->cp;
|
|
Step step = {};
|
|
step.type = STEP_MODIFY_PROPERTY;
|
|
step.objectID = data->objectID;
|
|
strcpy(step.property.cName, data->cPropertyName);
|
|
step.property.type = PROP_U64;
|
|
memcpy(&step.property.u64, editor->rows, sizeof(uint64_t));
|
|
DocumentApplyStep(step);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
InspectorBindingData *InspectorBind(UIElement *element, uint64_t objectID, const char *cPropertyName, InspectorElementType elementType,
|
|
int32_t choiceValue = 0, const char *cEnablePropertyName = nullptr) {
|
|
InspectorBindingData *data = (InspectorBindingData *) calloc(1, sizeof(InspectorBindingData));
|
|
data->element = element;
|
|
data->objectID = objectID;
|
|
strcpy(data->cPropertyName, cPropertyName);
|
|
data->elementType = elementType;
|
|
data->choiceValue = choiceValue;
|
|
data->cEnablePropertyName = cEnablePropertyName;
|
|
element->cp = data;
|
|
element->messageUser = InspectorBoundMessage;
|
|
inspectorBoundElements.Add(element);
|
|
InspectorUpdateSingleElement(data);
|
|
if (cEnablePropertyName) InspectorUpdateSingleElementEnable(data);
|
|
return data;
|
|
}
|
|
|
|
void InspectorRenameObject(void *) {
|
|
Step step = {};
|
|
step.type = STEP_RENAME_OBJECT;
|
|
step.objectID = selectedObjectID;
|
|
|
|
char *name = nullptr;
|
|
const char *result = UIDialogShow(window, 0, "Enter the new name for the object:\n%t\n\n%l\n\n%f%b%b", &name, "OK", "Cancel");
|
|
|
|
if (0 == strcmp(result, "OK")) {
|
|
if (name && strlen(name) >= sizeof(step.cName) - 1) {
|
|
UIDialogShow(window, 0, "Error: Name cannot have more than 46 characters.\n%f%b", "OK");
|
|
} else {
|
|
strcpy(step.cName, name ?: "");
|
|
DocumentApplyStep(step);
|
|
}
|
|
}
|
|
|
|
free(name);
|
|
}
|
|
|
|
void InspectorAutoNameObject(void *) {
|
|
Object *object = ObjectFind(selectedObjectID, false);
|
|
|
|
if (!ObjectIsConditional(object)) {
|
|
UIDialogShow(window, 0, "Error: The object needs to be conditional to use auto-name.\n%f%b", "OK");
|
|
return;
|
|
}
|
|
|
|
int32_t primaryState = PropertyReadInt32(object, "_primaryState");
|
|
int32_t stateBits = PropertyReadInt32(object, "_stateBits");
|
|
|
|
Step step = {};
|
|
step.type = STEP_RENAME_OBJECT;
|
|
step.objectID = selectedObjectID;
|
|
|
|
snprintf(step.cName, sizeof(step.cName), "?%s", primaryState ? cPrimaryStateStrings[primaryState] : "");
|
|
|
|
for (uintptr_t i = 0; i < 16; i++) {
|
|
if (stateBits & (1 << (15 - i))) {
|
|
snprintf(step.cName + strlen(step.cName), sizeof(step.cName) - strlen(step.cName), "%s%s", i || primaryState ? "&" : "", cStateBitStrings[i]);
|
|
}
|
|
}
|
|
|
|
DocumentApplyStep(step);
|
|
}
|
|
|
|
void InspectorPickTargetCommand(void *cp) {
|
|
if (inspectorPickData) {
|
|
InspectorPickTargetEnd();
|
|
return;
|
|
}
|
|
|
|
inspectorPickData = (InspectorBindingData *) cp;
|
|
UIElementRepaint(canvas, nullptr);
|
|
}
|
|
|
|
void InspectorFindTargetCommand(void *cp) {
|
|
InspectorBindingData *data = (InspectorBindingData *) cp;
|
|
Property *property = PropertyFind(ObjectFind(data->objectID, false), data->cPropertyName, PROP_OBJECT);
|
|
Object *target = ObjectFind(property ? property->object : 0, false);
|
|
|
|
if (target) {
|
|
CanvasSelectObject(target);
|
|
} else {
|
|
UIDialogShow(window, 0, "Error: The object does not exist.\n%f%b", "OK");
|
|
}
|
|
}
|
|
|
|
void InspectorGoToInstanceStyle(void *) {
|
|
Property *property = PropertyFind(ObjectFind(selectedObjectID, false), "style", PROP_OBJECT);
|
|
Object *target = ObjectFind(property ? property->object : 0, false);
|
|
|
|
if (target) {
|
|
CanvasSelectObject(target);
|
|
} else {
|
|
UIDialogShow(window, 0, "Error: The object does not exist.\n%f%b", "OK");
|
|
}
|
|
}
|
|
|
|
void InspectorToFrontCommand(void *) {
|
|
Step step = {};
|
|
step.type = STEP_SET_OBJECT_DEPTH;
|
|
step.depth = objects.Length() - 1;
|
|
step.objectID = selectedObjectID;
|
|
DocumentApplyStep(step);
|
|
}
|
|
|
|
void InspectorToBackCommand(void *) {
|
|
Step step = {};
|
|
step.type = STEP_SET_OBJECT_DEPTH;
|
|
step.depth = 0;
|
|
step.objectID = selectedObjectID;
|
|
DocumentApplyStep(step);
|
|
}
|
|
|
|
int InspectorTabPaneMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_LEFT_DOWN) {
|
|
element->messageClass(element, message, di, dp);
|
|
UIElementRefresh(inspector);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void InspectorAddLink(Object *object, const char *cLabel, const char *cPropertyName,
|
|
bool broadcast = false, const char *cEnablePropertyName = nullptr) {
|
|
if (cLabel) UILabelCreate(0, 0, cLabel, -1);
|
|
UIElement *row = &UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e;
|
|
InspectorBindingData *data = InspectorBind(&UIButtonCreate(row, UI_ELEMENT_H_FILL, 0, 0)->e,
|
|
object->id, cPropertyName, broadcast ? INSPECTOR_LINK_BROADCAST : INSPECTOR_LINK, 0, cEnablePropertyName);
|
|
UIButton *pickTarget = UIButtonCreate(row, UI_BUTTON_SMALL, "Pick", -1);
|
|
pickTarget->e.cp = data;
|
|
pickTarget->invoke = InspectorPickTargetCommand;
|
|
UIButton *findTarget = UIButtonCreate(row, UI_BUTTON_SMALL, "Find", -1);
|
|
findTarget->e.cp = data;
|
|
findTarget->invoke = InspectorFindTargetCommand;
|
|
if (!broadcast) InspectorBind(&UIButtonCreate(row, UI_BUTTON_SMALL, "X", 1)->e, object->id, cPropertyName, INSPECTOR_REMOVE_BUTTON);
|
|
}
|
|
|
|
void InspectorAddIntegerTextbox(Object *object, const char *cLabel, const char *cPropertyName, bool broadcast = false, const char *cEnablePropertyName = nullptr) {
|
|
if (cLabel) UILabelCreate(0, 0, cLabel, -1);
|
|
InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, cPropertyName,
|
|
broadcast ? INSPECTOR_INTEGER_TEXTBOX_BROADCAST : INSPECTOR_INTEGER_TEXTBOX, 0, cEnablePropertyName);
|
|
}
|
|
|
|
void InspectorAddFloat(Object *object, const char *cLabel, const char *cPropertyName, const char *cEnablePropertyName = nullptr) {
|
|
if (cLabel) UILabelCreate(0, 0, cLabel, -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_H_FILL | UI_PANEL_HORIZONTAL);
|
|
InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, cPropertyName, INSPECTOR_FLOAT_TEXTBOX, 0, cEnablePropertyName);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, cPropertyName, INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
}
|
|
|
|
void InspectorAddInteger(Object *object, const char *cLabel, const char *cPropertyName) {
|
|
if (cLabel) UILabelCreate(0, 0, cLabel, -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_H_FILL | UI_PANEL_HORIZONTAL);
|
|
UITabPane *tabPane = UITabPaneCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_H_FILL, "Direct\tLink");
|
|
tabPane->e.messageUser = InspectorTabPaneMessage;
|
|
InspectorAddIntegerTextbox(object, nullptr, cPropertyName);
|
|
InspectorAddLink(object, nullptr, cPropertyName);
|
|
UIParentPop();
|
|
Property *property = PropertyFind(object, cPropertyName);
|
|
if (property && property->type == PROP_OBJECT) tabPane->active = 1;
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, cPropertyName, INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
}
|
|
|
|
void InspectorAddFourGroup(Object *object, const char *cLabel, const char *cPropertyNamePrefix,
|
|
const char *cEnablePropertyName = nullptr, bool defaultToIndividualTab = false) {
|
|
char cPropertyName0[PROPERTY_NAME_SIZE]; strcpy(cPropertyName0, cPropertyNamePrefix); strcat(cPropertyName0, "0");
|
|
char cPropertyName1[PROPERTY_NAME_SIZE]; strcpy(cPropertyName1, cPropertyNamePrefix); strcat(cPropertyName1, "1");
|
|
char cPropertyName2[PROPERTY_NAME_SIZE]; strcpy(cPropertyName2, cPropertyNamePrefix); strcat(cPropertyName2, "2");
|
|
char cPropertyName3[PROPERTY_NAME_SIZE]; strcpy(cPropertyName3, cPropertyNamePrefix); strcat(cPropertyName3, "3");
|
|
if (cLabel) UILabelCreate(0, 0, cLabel, -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_H_FILL | UI_PANEL_HORIZONTAL);
|
|
UITabPane *tabPane = UITabPaneCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_H_FILL, "Single\tIndividual\tLink");
|
|
tabPane->e.messageUser = InspectorTabPaneMessage;
|
|
InspectorAddIntegerTextbox(object, nullptr, cPropertyName0, true /* broadcast */, cEnablePropertyName);
|
|
UIElement *row = &UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e;
|
|
InspectorBind(&UITextboxCreate(row, UI_ELEMENT_H_FILL)->e, object->id, cPropertyName0, INSPECTOR_INTEGER_TEXTBOX, 0, cEnablePropertyName);
|
|
InspectorBind(&UITextboxCreate(row, UI_ELEMENT_H_FILL)->e, object->id, cPropertyName1, INSPECTOR_INTEGER_TEXTBOX, 0, cEnablePropertyName);
|
|
InspectorBind(&UITextboxCreate(row, UI_ELEMENT_H_FILL)->e, object->id, cPropertyName2, INSPECTOR_INTEGER_TEXTBOX, 0, cEnablePropertyName);
|
|
InspectorBind(&UITextboxCreate(row, UI_ELEMENT_H_FILL)->e, object->id, cPropertyName3, INSPECTOR_INTEGER_TEXTBOX, 0, cEnablePropertyName);
|
|
InspectorAddLink(object, nullptr, cPropertyName0, true /* broadcast */, cEnablePropertyName);
|
|
UIParentPop();
|
|
Property *property = PropertyFind(object, cPropertyName0);
|
|
int32_t b0 = PropertyReadInt32(object, cPropertyName0);
|
|
int32_t b1 = PropertyReadInt32(object, cPropertyName1);
|
|
int32_t b2 = PropertyReadInt32(object, cPropertyName2);
|
|
int32_t b3 = PropertyReadInt32(object, cPropertyName3);
|
|
if (property && property->type == PROP_OBJECT) tabPane->active = 2;
|
|
else if (defaultToIndividualTab || b0 != b1 || b1 != b2 || b2 != b3) tabPane->active = 1;
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, cPropertyName0, INSPECTOR_REMOVE_BUTTON_BROADCAST);
|
|
UIParentPop();
|
|
}
|
|
|
|
void InspectorAddBooleanToggle(Object *object, const char *cLabel, const char *cPropertyName) {
|
|
InspectorBind(&UICheckboxCreate(0, UI_CHECKBOX_ALLOW_INDETERMINATE, cLabel, -1)->e, object->id, cPropertyName, INSPECTOR_BOOLEAN_TOGGLE);
|
|
}
|
|
|
|
void InspectorAddRadioSwitch(Object *object, const char *cLabel, const char *cPropertyName, int32_t choiceValue) {
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, cLabel, -1)->e, object->id, cPropertyName, INSPECTOR_RADIO_SWITCH, choiceValue);
|
|
}
|
|
|
|
void InspectorAddMaskBitToggle(Object *object, const char *cLabel, const char *cPropertyName, int32_t bit) {
|
|
InspectorBind(&UICheckboxCreate(0, 0, cLabel, -1)->e, object->id, cPropertyName, INSPECTOR_MASK_BIT_TOGGLE, bit);
|
|
}
|
|
|
|
int InspectorPreviewPrimaryStateButtonMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_CLICKED) {
|
|
UIElement *sibling = element->parent->children;
|
|
canvas->previewPrimaryState = (uintptr_t) element->cp;
|
|
UIElementRefresh(canvas);
|
|
|
|
while (sibling) {
|
|
SetBit(&sibling->flags, UI_BUTTON_CHECKED, sibling == element);
|
|
UIElementRefresh(sibling);
|
|
sibling = sibling->next;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void InspectorAddPreviewPrimaryStateButton(const char *cLabel, int32_t value) {
|
|
UIButton *button = UIButtonCreate(0, UI_BUTTON_SMALL, cLabel, -1);
|
|
button->e.cp = (void *) (uintptr_t) value;
|
|
button->e.messageUser = InspectorPreviewPrimaryStateButtonMessage;
|
|
if (canvas->previewPrimaryState == value) button->e.flags |= UI_BUTTON_CHECKED;
|
|
}
|
|
|
|
void InspectorPreviewStateBitsCheckboxInvoke(void *cp) {
|
|
canvas->previewStateBits ^= (uintptr_t) cp;
|
|
UIElementRefresh(canvas);
|
|
}
|
|
|
|
void InspectorAddPreviewStateBitsCheckbox(const char *cLabel, int32_t bit) {
|
|
UICheckbox *box = UICheckboxCreate(0, 0, cLabel, -1);
|
|
box->e.cp = (void *) (uintptr_t) bit;
|
|
box->invoke = InspectorPreviewStateBitsCheckboxInvoke;
|
|
if (canvas->previewStateBits & bit) box->check = UI_CHECK_CHECKED;
|
|
}
|
|
|
|
void InspectorPopulate() {
|
|
UIElementDestroyDescendents(inspector);
|
|
UIParentPush(inspector);
|
|
|
|
Object *object = ObjectFind(selectedObjectID, false);
|
|
size_t referenceCount = 0;
|
|
|
|
if (object) {
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
for (uintptr_t j = 0; j < objects[i].properties.Length(); j++) {
|
|
if (objects[i].properties[j].type == PROP_OBJECT
|
|
&& objects[i].properties[j].object == object->id) {
|
|
referenceCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (object && object->type != OBJ_INSTANCE) {
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_BORDER | UI_PANEL_MEDIUM_SPACING | UI_PANEL_EXPAND);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
char buffer[256];
|
|
snprintf(buffer, sizeof(buffer), "%lu: %s", object->id, object->cName[0] ? object->cName : "(untitled)");
|
|
UILabelCreate(0, 0, buffer, -1);
|
|
UISpacerCreate(0, UI_ELEMENT_H_FILL, 0, 0);
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Auto", -1)->invoke = InspectorAutoNameObject;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Rename", -1)->invoke = InspectorRenameObject;
|
|
UIParentPop();
|
|
snprintf(buffer, sizeof(buffer), "%ld references", referenceCount);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(0, 0, buffer, -1);
|
|
UISpacerCreate(0, UI_ELEMENT_H_FILL, 0, 0);
|
|
UIButton *button = UIButtonCreate(0, UI_BUTTON_SMALL, "Change type \x19", -1);
|
|
button->invoke = ObjectAddCommand;
|
|
button->e.cp = (void *) ObjectChangeTypeInternal;
|
|
UIParentPop();
|
|
|
|
bool inheritWithAnimation = object->type == OBJ_VAR_TEXT_STYLE
|
|
|| object->type == OBJ_LAYER_BOX || object->type == OBJ_LAYER_TEXT || object->type == OBJ_LAYER_PATH
|
|
|| object->type == OBJ_PAINT_OVERWRITE || object->type == OBJ_PAINT_BIT_PATTERN
|
|
|| object->type == OBJ_PAINT_LINEAR_GRADIENT || object->type == OBJ_PAINT_RADIAL_GRADIENT
|
|
|| object->type == OBJ_VAR_CONTOUR_STYLE;
|
|
bool inheritWithoutAnimation = object->type == OBJ_STYLE || object->type == OBJ_LAYER_METRICS;
|
|
|
|
if (inheritWithAnimation || inheritWithoutAnimation) {
|
|
InspectorAddLink(object, "Inherit from:", "_parent");
|
|
|
|
if (inheritWithAnimation) {
|
|
UILabelCreate(0, 0, "Primary state:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Idle", "_primaryState", THEME_PRIMARY_STATE_IDLE);
|
|
InspectorAddRadioSwitch(object, "Hovered", "_primaryState", THEME_PRIMARY_STATE_HOVERED);
|
|
InspectorAddRadioSwitch(object, "Pressed", "_primaryState", THEME_PRIMARY_STATE_PRESSED);
|
|
InspectorAddRadioSwitch(object, "Disabled", "_primaryState", THEME_PRIMARY_STATE_DISABLED);
|
|
InspectorAddRadioSwitch(object, "Inactive", "_primaryState", THEME_PRIMARY_STATE_INACTIVE);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, "_primaryState", INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "State bits:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND)->gap = -5;
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL)->gap = 8;
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[0], "_stateBits", THEME_STATE_FOCUSED);
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[1], "_stateBits", THEME_STATE_CHECKED);
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[2], "_stateBits", THEME_STATE_INDETERMINATE);
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[3], "_stateBits", THEME_STATE_DEFAULT_BUTTON);
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[4], "_stateBits", THEME_STATE_SELECTED);
|
|
UIParentPop();
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL)->gap = 8;
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[5], "_stateBits", THEME_STATE_FOCUSED_ITEM);
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[6], "_stateBits", THEME_STATE_LIST_FOCUSED);
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[7], "_stateBits", THEME_STATE_BEFORE_ENTER);
|
|
InspectorAddMaskBitToggle(object, cStateBitStrings[8], "_stateBits", THEME_STATE_AFTER_EXIT);
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Transition duration:", -1);
|
|
InspectorAddInteger(object, nullptr, "_duration");
|
|
}
|
|
}
|
|
UIParentPop();
|
|
|
|
UISpacerCreate(0, 0, 0, 10);
|
|
|
|
if (object->type == OBJ_STYLE) {
|
|
InspectorAddLink(object, "Appearance:", "appearance");
|
|
InspectorAddLink(object, "Metrics:", "metrics");
|
|
InspectorAddLink(object, "Text style:", "textStyle");
|
|
InspectorAddBooleanToggle(object, "Public style", "isPublic");
|
|
|
|
char buffer[128];
|
|
snprintf(buffer, sizeof(buffer), "Header ID: %d", PropertyReadInt32(object, "headerID"));
|
|
UILabelCreate(0, 0, buffer, -1);
|
|
} else if (object->type == OBJ_SELECTOR) {
|
|
int32_t optionCount = PropertyReadInt32(object, "options_count");
|
|
if (optionCount < 0) optionCount = 0;
|
|
if (optionCount > 100) optionCount = 100;
|
|
|
|
for (int32_t i = 0; i < optionCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_BORDER | UI_PANEL_MEDIUM_SPACING | UI_PANEL_EXPAND);
|
|
sprintf(cPropertyName, "options_%d_", i);
|
|
UIPanel *row = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UISpacerCreate(&row->e, UI_ELEMENT_H_FILL, 0, 0);
|
|
InspectorBind(&UIButtonCreate(&row->e, UI_BUTTON_SMALL, "Delete", -1)->e, object->id, cPropertyName, INSPECTOR_DELETE_ARRAY_ITEM);
|
|
sprintf(cPropertyName, "options_%d_link", i);
|
|
InspectorAddLink(object, "Link:", cPropertyName);
|
|
UIParentPop();
|
|
|
|
if (i != optionCount - 1) {
|
|
sprintf(cPropertyName, "options_%d_", i);
|
|
InspectorBind(&UIButtonCreate(&UIPanelCreate(0, 0)->e, UI_BUTTON_SMALL, "Swap", -1)->e,
|
|
object->id, cPropertyName, INSPECTOR_SWAP_ARRAY_ITEMS);
|
|
}
|
|
}
|
|
|
|
InspectorBind(&UIButtonCreate(0, 0, "Add option", -1)->e, object->id, "options_count", INSPECTOR_ADD_ARRAY_ITEM);
|
|
} else if (object->type == OBJ_VAR_COLOR) {
|
|
InspectorBind(&UIColorPickerCreate(&UIPanelCreate(0, 0)->e, UI_COLOR_PICKER_HAS_OPACITY)->e, object->id, "color", INSPECTOR_COLOR_PICKER);
|
|
InspectorBind(&UITextboxCreate(0, 0)->e, object->id, "color", INSPECTOR_COLOR_TEXTBOX);
|
|
InspectorAddBooleanToggle(object, "Export to theme file", "isExported");
|
|
} else if (object->type == OBJ_VAR_INT) {
|
|
InspectorBind(&UITextboxCreate(0, 0)->e, object->id, "value", INSPECTOR_INTEGER_TEXTBOX);
|
|
InspectorAddBooleanToggle(object, "Export to theme file", "isExported");
|
|
InspectorAddBooleanToggle(object, "Apply UI scaling factor", "isScaled");
|
|
} else if (object->type == OBJ_LAYER_METRICS) {
|
|
UILabelCreate(0, 0, "General options:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddBooleanToggle(object, "Enable clipping", "clipEnabled");
|
|
InspectorAddBooleanToggle(object, "Wrap text", "wrapText");
|
|
InspectorAddBooleanToggle(object, "Ellipsis", "ellipsis");
|
|
UIParentPop();
|
|
|
|
InspectorAddFourGroup(object, "Insets:", "insets");
|
|
InspectorAddFourGroup(object, "Clip insets:", "clipInsets", "clipEnabled");
|
|
|
|
UILabelCreate(0, 0, "Preferred size:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddInteger(object, nullptr, "preferredWidth");
|
|
InspectorAddInteger(object, nullptr, "preferredHeight");
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Minimum size:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddInteger(object, nullptr, "minimumWidth");
|
|
InspectorAddInteger(object, nullptr, "minimumHeight");
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Maximum size:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddInteger(object, nullptr, "maximumWidth");
|
|
InspectorAddInteger(object, nullptr, "maximumHeight");
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Gaps:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddInteger(object, nullptr, "gapMajor");
|
|
InspectorAddInteger(object, nullptr, "gapMinor");
|
|
InspectorAddInteger(object, nullptr, "gapWrap");
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Horizontal text alignment:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Left", "horizontalTextAlign", 1);
|
|
InspectorAddRadioSwitch(object, "Center", "horizontalTextAlign", 2);
|
|
InspectorAddRadioSwitch(object, "Right", "horizontalTextAlign", 3);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, "horizontalTextAlign", INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Vertical text alignment:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Top", "verticalTextAlign", 1);
|
|
InspectorAddRadioSwitch(object, "Center", "verticalTextAlign", 2);
|
|
InspectorAddRadioSwitch(object, "Bottom", "verticalTextAlign", 3);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, "verticalTextAlign", INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Cursor style:", -1);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_DROP_DOWN, 0, 0)->e, object->id, "cursor", INSPECTOR_CURSOR_DROP_DOWN);
|
|
} else if (object->type == OBJ_VAR_TEXT_STYLE) {
|
|
InspectorAddLink(object, "Text color:", "textColor");
|
|
InspectorAddLink(object, "Selection background color:", "selectedBackground");
|
|
InspectorAddLink(object, "Selection text color:", "selectedText");
|
|
InspectorAddInteger(object, "Text size:", "textSize");
|
|
InspectorAddInteger(object, "Font weight:", "fontWeight");
|
|
|
|
UILabelCreate(0, 0, "Font options:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddBooleanToggle(object, "Italic", "isItalic");
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Font family:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Sans-serif", "fontFamily", ES_FONT_SANS);
|
|
InspectorAddRadioSwitch(object, "Serif", "fontFamily", ES_FONT_SERIF);
|
|
InspectorAddRadioSwitch(object, "Monospaced", "fontFamily", ES_FONT_MONOSPACED);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, "fontFamily", INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
|
|
InspectorAddLink(object, "Icon color:", "iconColor");
|
|
InspectorAddInteger(object, "Icon size:", "iconSize");
|
|
|
|
#if 0
|
|
UIButton *removeDuplicates = UIButtonCreate(0, 0, "Remove duplicates", -1);
|
|
removeDuplicates->e.cp = (void *) (uintptr_t) object->id;
|
|
removeDuplicates->invoke = TextStyleRemoveDuplicates;
|
|
#endif
|
|
} else if (object->type == OBJ_VAR_CONTOUR_STYLE) {
|
|
InspectorAddInteger(object, "Internal width:", "internalWidth");
|
|
InspectorAddInteger(object, "External width:", "externalWidth");
|
|
InspectorAddBooleanToggle(object, "When scaling, snap to integer widths", "integerWidthsOnly");
|
|
InspectorAddFloat(object, "Miter limit:", "miterLimit");
|
|
|
|
UILabelCreate(0, 0, "Join mode:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Miter", "joinMode", RAST_LINE_JOIN_MITER);
|
|
InspectorAddRadioSwitch(object, "Round", "joinMode", RAST_LINE_JOIN_ROUND);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, "joinMode", INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
|
|
UILabelCreate(0, 0, "Cap mode:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Flat", "capMode", RAST_LINE_CAP_FLAT);
|
|
InspectorAddRadioSwitch(object, "Square", "capMode", RAST_LINE_CAP_SQUARE);
|
|
InspectorAddRadioSwitch(object, "Round", "capMode", RAST_LINE_CAP_ROUND);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, "capMode", INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
} else if (object->type == OBJ_LAYER_BOX) {
|
|
InspectorAddFourGroup(object, "Border sizes:", "borders");
|
|
InspectorAddFourGroup(object, "Corner radii:", "corners");
|
|
InspectorAddLink(object, "Fill paint:", "mainPaint");
|
|
InspectorAddLink(object, "Border paint:", "borderPaint");
|
|
|
|
InspectorAddBooleanToggle(object, "Blurred", "isBlurred");
|
|
InspectorAddBooleanToggle(object, "Auto-corners", "autoCorners");
|
|
InspectorAddBooleanToggle(object, "Auto-borders", "autoBorders");
|
|
InspectorAddBooleanToggle(object, "Shadow hiding", "shadowHiding");
|
|
|
|
InspectorAddFourGroup(object, "Offset (dpx):", "offset", nullptr, true);
|
|
} else if (object->type == OBJ_LAYER_PATH) {
|
|
InspectorAddBooleanToggle(object, "Use even-odd fill rule", "pathFillEvenOdd");
|
|
InspectorAddBooleanToggle(object, "Path is closed", "pathClosed");
|
|
InspectorAddIntegerTextbox(object, "Alpha", "alpha");
|
|
|
|
int32_t pointCount = PropertyReadInt32(object, "points_count");
|
|
if (pointCount < 0) pointCount = 0;
|
|
if (pointCount > 100) pointCount = 100;
|
|
|
|
for (int32_t i = 0; i < pointCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
sprintf(cPropertyName, "points_%d_x0", i);
|
|
InspectorAddFloat(object, nullptr, cPropertyName);
|
|
sprintf(cPropertyName, "points_%d_y0", i);
|
|
InspectorAddFloat(object, nullptr, cPropertyName);
|
|
sprintf(cPropertyName, "points_%d_x1", i);
|
|
InspectorAddFloat(object, nullptr, cPropertyName);
|
|
sprintf(cPropertyName, "points_%d_y1", i);
|
|
InspectorAddFloat(object, nullptr, cPropertyName);
|
|
sprintf(cPropertyName, "points_%d_x2", i);
|
|
InspectorAddFloat(object, nullptr, cPropertyName);
|
|
sprintf(cPropertyName, "points_%d_y2", i);
|
|
InspectorAddFloat(object, nullptr, cPropertyName);
|
|
sprintf(cPropertyName, "points_%d_", i);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "Delete", -1)->e, object->id, cPropertyName, INSPECTOR_DELETE_ARRAY_ITEM);
|
|
UIParentPop();
|
|
}
|
|
|
|
InspectorBind(&UIButtonCreate(0, 0, "Add point", -1)->e, object->id, "points_count", INSPECTOR_ADD_ARRAY_ITEM);
|
|
|
|
int32_t fillCount = PropertyReadInt32(object, "fills_count");
|
|
if (fillCount < 0) fillCount = 0;
|
|
if (fillCount > 100) fillCount = 100;
|
|
|
|
for (int32_t i = 0; i < fillCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_BORDER | UI_PANEL_MEDIUM_SPACING | UI_PANEL_EXPAND);
|
|
sprintf(cPropertyName, "fills_%d_", i);
|
|
UIPanel *row = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UISpacerCreate(&row->e, UI_ELEMENT_H_FILL, 0, 0);
|
|
InspectorBind(&UIButtonCreate(&row->e, UI_BUTTON_SMALL, "Delete", -1)->e, object->id, cPropertyName, INSPECTOR_DELETE_ARRAY_ITEM);
|
|
sprintf(cPropertyName, "fills_%d_paint", i);
|
|
InspectorAddLink(object, "Paint:", cPropertyName);
|
|
sprintf(cPropertyName, "fills_%d_mode", i);
|
|
InspectorAddLink(object, "Mode:", cPropertyName);
|
|
UIParentPop();
|
|
|
|
if (i != fillCount - 1) {
|
|
sprintf(cPropertyName, "fills_%d_", i);
|
|
InspectorBind(&UIButtonCreate(&UIPanelCreate(0, 0)->e, UI_BUTTON_SMALL, "Swap", -1)->e,
|
|
object->id, cPropertyName, INSPECTOR_SWAP_ARRAY_ITEMS);
|
|
}
|
|
}
|
|
|
|
InspectorBind(&UIButtonCreate(0, 0, "Add fill", -1)->e, object->id, "fills_count", INSPECTOR_ADD_ARRAY_ITEM);
|
|
} else if (object->type == OBJ_LAYER_TEXT) {
|
|
InspectorAddLink(object, "Text color:", "color");
|
|
InspectorAddInteger(object, "Blur radius:", "blur");
|
|
} else if (object->type == OBJ_LAYER_GROUP) {
|
|
int32_t layerCount = PropertyReadInt32(object, "layers_count");
|
|
if (layerCount < 0) layerCount = 0;
|
|
if (layerCount > 100) layerCount = 100;
|
|
|
|
for (int32_t i = 0; i < layerCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_BORDER | UI_PANEL_MEDIUM_SPACING | UI_PANEL_EXPAND);
|
|
sprintf(cPropertyName, "layers_%d_", i);
|
|
UIPanel *row = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UISpacerCreate(&row->e, UI_ELEMENT_H_FILL, 0, 0);
|
|
InspectorBind(&UIButtonCreate(&row->e, UI_BUTTON_SMALL, "Delete", -1)->e, object->id, cPropertyName, INSPECTOR_DELETE_ARRAY_ITEM);
|
|
sprintf(cPropertyName, "layers_%d_layer", i);
|
|
InspectorAddLink(object, "Layer:", cPropertyName);
|
|
sprintf(cPropertyName, "layers_%d_offset", i);
|
|
InspectorAddFourGroup(object, "Offset (dpx):", cPropertyName, nullptr, true /* defaultToIndividualTab */);
|
|
sprintf(cPropertyName, "layers_%d_position", i);
|
|
InspectorAddFourGroup(object, "Position (%):", cPropertyName, nullptr, true /* defaultToIndividualTab */);
|
|
sprintf(cPropertyName, "layers_%d_mode", i);
|
|
UILabelCreate(0, 0, "Mode:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Background", cPropertyName, THEME_LAYER_MODE_BACKGROUND);
|
|
InspectorAddRadioSwitch(object, "Shadow", cPropertyName, THEME_LAYER_MODE_SHADOW);
|
|
InspectorAddRadioSwitch(object, "Content", cPropertyName, THEME_LAYER_MODE_CONTENT);
|
|
InspectorAddRadioSwitch(object, "Overlay", cPropertyName, THEME_LAYER_MODE_OVERLAY);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, cPropertyName, INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
if (i != layerCount - 1) {
|
|
sprintf(cPropertyName, "layers_%d_", i);
|
|
InspectorBind(&UIButtonCreate(&UIPanelCreate(0, 0)->e, UI_BUTTON_SMALL, "Swap", -1)->e,
|
|
object->id, cPropertyName, INSPECTOR_SWAP_ARRAY_ITEMS);
|
|
}
|
|
}
|
|
|
|
InspectorBind(&UIButtonCreate(0, 0, "Add layer", -1)->e, object->id, "layers_count", INSPECTOR_ADD_ARRAY_ITEM);
|
|
} else if (object->type == OBJ_PAINT_OVERWRITE) {
|
|
InspectorAddLink(object, "Color:", "color");
|
|
} else if (object->type == OBJ_PAINT_BIT_PATTERN) {
|
|
InspectorBind(UIElementCreate(sizeof(PatternEditor), 0, 0, PatternEditorMessage, "Pattern editor"), object->id, "pattern", INSPECTOR_PATTERN_EDITOR);
|
|
UILabelCreate(0, 0, "Color off:", -1);
|
|
InspectorBind(&UIColorPickerCreate(&UIPanelCreate(0, 0)->e, UI_COLOR_PICKER_HAS_OPACITY)->e, object->id, "color0", INSPECTOR_COLOR_PICKER);
|
|
InspectorBind(&UITextboxCreate(0, 0)->e, object->id, "color0", INSPECTOR_COLOR_TEXTBOX);
|
|
UILabelCreate(0, 0, "Color on:", -1);
|
|
InspectorBind(&UIColorPickerCreate(&UIPanelCreate(0, 0)->e, UI_COLOR_PICKER_HAS_OPACITY)->e, object->id, "color1", INSPECTOR_COLOR_PICKER);
|
|
InspectorBind(&UITextboxCreate(0, 0)->e, object->id, "color1", INSPECTOR_COLOR_TEXTBOX);
|
|
} else if (object->type == OBJ_PAINT_LINEAR_GRADIENT || object->type == OBJ_PAINT_RADIAL_GRADIENT) {
|
|
if (object->type == OBJ_PAINT_LINEAR_GRADIENT) {
|
|
InspectorAddFloat(object, "Transform X:", "transformX");
|
|
InspectorAddFloat(object, "Transform Y:", "transformY");
|
|
InspectorAddFloat(object, "Transform start:", "transformStart");
|
|
} else {
|
|
InspectorAddFloat(object, "Transform X scale:", "transform0");
|
|
InspectorAddFloat(object, "Transform X offset:", "transform2");
|
|
InspectorAddFloat(object, "Transform Y scale:", "transform4");
|
|
InspectorAddFloat(object, "Transform Y offset:", "transform5");
|
|
InspectorAddFloat(object, "Transform X skew:", "transform1");
|
|
InspectorAddFloat(object, "Transform Y skew:", "transform3");
|
|
}
|
|
|
|
UILabelCreate(0, 0, "Repeat mode:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddRadioSwitch(object, "Clamp", "repeatMode", RAST_REPEAT_CLAMP);
|
|
InspectorAddRadioSwitch(object, "Normal", "repeatMode", RAST_REPEAT_NORMAL);
|
|
InspectorAddRadioSwitch(object, "Mirror", "repeatMode", RAST_REPEAT_MIRROR);
|
|
InspectorBind(&UIButtonCreate(0, UI_BUTTON_SMALL, "X", 1)->e, object->id, "repeatMode", INSPECTOR_REMOVE_BUTTON);
|
|
UIParentPop();
|
|
|
|
InspectorAddBooleanToggle(object, "Use gamma interpolation", "useGammaInterpolation");
|
|
|
|
int32_t stopCount = PropertyReadInt32(object, "stops_count");
|
|
if (stopCount < 0) stopCount = 0;
|
|
if (stopCount > 100) stopCount = 100;
|
|
|
|
UILabelCreate(0, 0, "Gradient stops:", -1);
|
|
|
|
for (int32_t i = 0; i < stopCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_BORDER | UI_PANEL_MEDIUM_SPACING | UI_PANEL_EXPAND);
|
|
sprintf(cPropertyName, "stops_%d_", i);
|
|
UIPanel *row = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UISpacerCreate(&row->e, UI_ELEMENT_H_FILL, 0, 0);
|
|
InspectorBind(&UIButtonCreate(&row->e, UI_BUTTON_SMALL, "Delete", -1)->e, object->id, cPropertyName, INSPECTOR_DELETE_ARRAY_ITEM);
|
|
sprintf(cPropertyName, "stops_%d_color", i);
|
|
InspectorAddLink(object, "Color:", cPropertyName);
|
|
sprintf(cPropertyName, "stops_%d_position", i);
|
|
InspectorAddInteger(object, "Position (%):", cPropertyName);
|
|
sprintf(cPropertyName, "stops_%d_wci", i);
|
|
InspectorAddInteger(object, "Window color index:", cPropertyName);
|
|
UIParentPop();
|
|
|
|
if (i != stopCount - 1) {
|
|
sprintf(cPropertyName, "stops_%d_", i);
|
|
InspectorBind(&UIButtonCreate(&UIPanelCreate(0, 0)->e, UI_BUTTON_SMALL, "Swap", -1)->e,
|
|
object->id, cPropertyName, INSPECTOR_SWAP_ARRAY_ITEMS);
|
|
}
|
|
}
|
|
|
|
InspectorBind(&UIButtonCreate(0, 0, "Add stop", -1)->e, object->id, "stops_count", INSPECTOR_ADD_ARRAY_ITEM);
|
|
} else if (object->type == OBJ_MOD_COLOR) {
|
|
InspectorAddLink(object, "Base color:", "base");
|
|
UILabelCreate(0, 0, "Brightness (%):", -1);
|
|
InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, "brightness", INSPECTOR_INTEGER_TEXTBOX);
|
|
UILabelCreate(0, 0, "Hue shift (deg):", -1);
|
|
InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, "hueShift", INSPECTOR_INTEGER_TEXTBOX);
|
|
} else if (object->type == OBJ_MOD_MULTIPLY) {
|
|
InspectorAddLink(object, "Base integer:", "base");
|
|
UILabelCreate(0, 0, "Factor (%):", -1);
|
|
InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, "factor", INSPECTOR_INTEGER_TEXTBOX);
|
|
}
|
|
} else if (object && object->type == OBJ_INSTANCE) {
|
|
Property *property = PropertyFind(object, "style", PROP_OBJECT);
|
|
Object *style = ObjectFind(property ? property->object : 0, false);
|
|
char buffer[128];
|
|
|
|
if (style) {
|
|
snprintf(buffer, sizeof(buffer), "Instance of style '%s'.", style->cName);
|
|
} else {
|
|
snprintf(buffer, sizeof(buffer), "Instance of deleted style.");
|
|
}
|
|
|
|
UILabelCreate(0, 0, buffer, -1);
|
|
|
|
if (style) {
|
|
UIButtonCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e, 0, "View in graph \x1A", -1)->invoke = InspectorGoToInstanceStyle;
|
|
}
|
|
|
|
UISpacerCreate(0, 0, 0, 10);
|
|
|
|
UILabelCreate(0, 0, "Depth:", -1);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
UIButtonCreate(0, 0, "To front", -1)->invoke = InspectorToFrontCommand;
|
|
UIButtonCreate(0, 0, "To back", -1)->invoke = InspectorToBackCommand;
|
|
UIParentPop();
|
|
|
|
UISpacerCreate(0, 0, 0, 10);
|
|
|
|
UILabelCreate(0, 0, "Preview state:", -1);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
|
|
InspectorAddPreviewPrimaryStateButton(cPrimaryStateStrings[1], THEME_PRIMARY_STATE_IDLE);
|
|
InspectorAddPreviewPrimaryStateButton(cPrimaryStateStrings[2], THEME_PRIMARY_STATE_HOVERED);
|
|
InspectorAddPreviewPrimaryStateButton(cPrimaryStateStrings[3], THEME_PRIMARY_STATE_PRESSED);
|
|
InspectorAddPreviewPrimaryStateButton(cPrimaryStateStrings[4], THEME_PRIMARY_STATE_DISABLED);
|
|
InspectorAddPreviewPrimaryStateButton(cPrimaryStateStrings[5], THEME_PRIMARY_STATE_INACTIVE);
|
|
UIParentPop();
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND)->gap = -5;
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL)->gap = 8;
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[0], 1 << 15);
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[1], 1 << 14);
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[2], 1 << 13);
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[3], 1 << 12);
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[4], 1 << 11);
|
|
UIParentPop();
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL)->gap = 8;
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[5], 1 << 10);
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[6], 1 << 9);
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[7], 1 << 8);
|
|
InspectorAddPreviewStateBitsCheckbox(cStateBitStrings[8], 1 << 7);
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
Rectangle8 paintOutsets = ExportCalculatePaintOutsets(PropertyFindOrInheritReadObject(style, "appearance"));
|
|
char text[256];
|
|
snprintf(text, sizeof(text), "Paint outsets: %d, %d, %d, %d.", UI_RECT_ALL(paintOutsets));
|
|
UILabelCreate(0, 0, text, -1);
|
|
Rectangle8 approximateBorders = ExportCalculateApproximateBorders(PropertyFindOrInheritReadObject(style, "appearance"));
|
|
snprintf(text, sizeof(text), "Approximate borders: %d, %d, %d, %d.", UI_RECT_ALL(approximateBorders));
|
|
UILabelCreate(0, 0, text, -1);
|
|
} else {
|
|
UILabelCreate(0, 0, "Select an object to inspect.", -1);
|
|
}
|
|
|
|
UIParentPop();
|
|
UIElementRefresh(inspector);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
int32_t GraphGetInteger(Object *object, int depth = 0) {
|
|
if (!object || depth == 100) {
|
|
return 0;
|
|
}
|
|
|
|
if (object->type == OBJ_VAR_INT) {
|
|
return PropertyReadInt32(object, "value");
|
|
} else if (object->type == OBJ_MOD_MULTIPLY) {
|
|
Property *property = PropertyFind(object, "base", PROP_OBJECT);
|
|
int32_t base = GraphGetInteger(ObjectFind(property ? property->object : 0, true), depth + 1);
|
|
int32_t factor = PropertyReadInt32(object, "factor");
|
|
return base * factor / 100;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int32_t GraphGetIntegerFromProperty(Property *property) {
|
|
if (!property) {
|
|
return 0;
|
|
} else if (property->type == PROP_INT) {
|
|
return property->integer;
|
|
} else if (property->type == PROP_OBJECT) {
|
|
return GraphGetInteger(ObjectFind(property->object, true));
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32_t GraphGetColor(Object *object, int depth = 0) {
|
|
if (!object || depth == 100) {
|
|
return 0;
|
|
}
|
|
|
|
if (object->type == OBJ_VAR_COLOR) {
|
|
return PropertyReadInt32(object, "color");
|
|
} else if (object->type == OBJ_MOD_COLOR) {
|
|
Property *property = PropertyFind(object, "base", PROP_OBJECT);
|
|
uint32_t base = GraphGetColor(ObjectFind(property ? property->object : 0, true), depth + 1);
|
|
uint32_t alpha = base & 0xFF000000;
|
|
int32_t brightness = PropertyReadInt32(object, "brightness");
|
|
int32_t hueShift = PropertyReadInt32(object, "hueShift");
|
|
double hue, saturation, luminosity, red, green, blue;
|
|
rgb2hsluv(UI_COLOR_RED_F(base), UI_COLOR_GREEN_F(base), UI_COLOR_BLUE_F(base), &hue, &saturation, &luminosity);
|
|
luminosity += luminosity * brightness / 100.0f;
|
|
hue = fmod(hue + hueShift, 360.0);
|
|
if (luminosity < 0.0) luminosity = 0.0;
|
|
if (luminosity > 100.0) luminosity = 100.0;
|
|
hsluv2rgb(hue, saturation, luminosity, &red, &green, &blue);
|
|
return UI_COLOR_FROM_FLOAT(red, green, blue) | alpha;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32_t GraphGetColorFromProperty(Property *property) {
|
|
if (!property) {
|
|
return 0;
|
|
} else if (property->type == PROP_OBJECT) {
|
|
return GraphGetColor(ObjectFind(property->object, true));
|
|
} else if (property->type == PROP_COLOR) {
|
|
return (uint32_t) property->integer;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
Rectangle8 ExportCalculatePaintOutsets(Object *object) {
|
|
Rectangle8 paintOutsets = {};
|
|
|
|
int32_t layerCount = PropertyReadInt32(object, "layers_count");
|
|
if (layerCount < 0) layerCount = 0;
|
|
if (layerCount > 100) layerCount = 100;
|
|
|
|
for (int32_t i = 0; i < layerCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
sprintf(cPropertyName, "layers_%d_layer", i);
|
|
Property *layerProperty = PropertyFind(object, cPropertyName, PROP_OBJECT);
|
|
Object *layerObject = ObjectFind(layerProperty ? layerProperty->object : 0, true);
|
|
if (!layerObject) continue;
|
|
|
|
#define LAYER_READ_INT32(x) sprintf(cPropertyName, "layers_%d_" #x, i); int8_t x = PropertyReadInt32(object, cPropertyName)
|
|
LAYER_READ_INT32(offset0);
|
|
LAYER_READ_INT32(offset1);
|
|
LAYER_READ_INT32(offset2);
|
|
LAYER_READ_INT32(offset3);
|
|
LAYER_READ_INT32(position0);
|
|
LAYER_READ_INT32(position1);
|
|
LAYER_READ_INT32(position2);
|
|
LAYER_READ_INT32(position3);
|
|
#undef LAYER_READ_INT32
|
|
|
|
if (layerObject->type == OBJ_LAYER_BOX) {
|
|
Object *object = layerObject;
|
|
int depth = 0;
|
|
|
|
while (object && (depth++ < 100)) {
|
|
int32_t boxOffset0 = PropertyReadInt32(object, "offset0");
|
|
int32_t boxOffset1 = PropertyReadInt32(object, "offset1");
|
|
int32_t boxOffset2 = PropertyReadInt32(object, "offset2");
|
|
int32_t boxOffset3 = PropertyReadInt32(object, "offset3");
|
|
if (boxOffset0 < offset0) offset0 = boxOffset0;
|
|
if (boxOffset1 > offset1) offset1 = boxOffset1;
|
|
if (boxOffset2 < offset2) offset2 = boxOffset2;
|
|
if (boxOffset3 > offset3) offset3 = boxOffset3;
|
|
Property *property = PropertyFind(object, "_parent", PROP_OBJECT);
|
|
object = ObjectFind(property ? property->object : 0, true);
|
|
}
|
|
}
|
|
|
|
if (position0 == 0 && -offset0 > paintOutsets.l) paintOutsets.l = -offset0;
|
|
if (position1 == 100 && offset1 > paintOutsets.r) paintOutsets.r = offset1;
|
|
if (position2 == 0 && -offset2 > paintOutsets.t) paintOutsets.t = -offset2;
|
|
if (position3 == 100 && offset3 > paintOutsets.b) paintOutsets.b = offset3;
|
|
}
|
|
|
|
return paintOutsets;
|
|
}
|
|
|
|
Rectangle8 ExportCalculateOpaqueInsets(Object *object) {
|
|
return { 0x7F, 0x7F, 0x7F, 0x7F }; // TODO;
|
|
}
|
|
|
|
Rectangle8 ExportCalculateApproximateBorders(Object *object) {
|
|
int32_t layerCount = PropertyReadInt32(object, "layers_count");
|
|
if (layerCount < 0) layerCount = 0;
|
|
if (layerCount > 100) layerCount = 100;
|
|
|
|
for (int32_t i = 0; i < layerCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
sprintf(cPropertyName, "layers_%d_layer", i);
|
|
Property *layerProperty = PropertyFind(object, cPropertyName, PROP_OBJECT);
|
|
Object *layerObject = ObjectFind(layerProperty ? layerProperty->object : 0, true);
|
|
if (!layerObject) continue;
|
|
|
|
#define LAYER_READ_INT32(x) sprintf(cPropertyName, "layers_%d_" #x, i); int8_t x = PropertyReadInt32(object, cPropertyName)
|
|
LAYER_READ_INT32(position0);
|
|
LAYER_READ_INT32(position1);
|
|
LAYER_READ_INT32(position2);
|
|
LAYER_READ_INT32(position3);
|
|
LAYER_READ_INT32(mode);
|
|
#undef LAYER_READ_INT32
|
|
|
|
if (layerObject->type == OBJ_LAYER_BOX && position0 == 0 && position1 == 100 && position2 == 0 && position3 == 100
|
|
&& !PropertyReadInt32(layerObject, "shadowHiding") && !PropertyReadInt32(layerObject, "isBlurred")
|
|
&& mode == THEME_LAYER_MODE_BACKGROUND && PropertyFindOrInherit(layerObject, "borderPaint")) {
|
|
return {
|
|
(int8_t) PropertyFindOrInheritReadInt32(layerObject, "borders0"),
|
|
(int8_t) PropertyFindOrInheritReadInt32(layerObject, "borders1"),
|
|
(int8_t) PropertyFindOrInheritReadInt32(layerObject, "borders2"),
|
|
(int8_t) PropertyFindOrInheritReadInt32(layerObject, "borders3"),
|
|
};
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
struct ExportContext {
|
|
EsBuffer *baseData;
|
|
EsBuffer *overrideData;
|
|
uintptr_t overrideOffset;
|
|
};
|
|
|
|
ThemeVariant PropertyToThemeVariant(Property *property, uint8_t type) {
|
|
ThemeVariant value = {};
|
|
|
|
if (type == THEME_OVERRIDE_COLOR) {
|
|
value.u32 = GraphGetColorFromProperty(property);
|
|
} else if (type == THEME_OVERRIDE_I8) {
|
|
value.i8 = GraphGetIntegerFromProperty(property);
|
|
} else if (type == THEME_OVERRIDE_I16) {
|
|
value.i16 = GraphGetIntegerFromProperty(property);
|
|
} else if (type == THEME_OVERRIDE_F32) {
|
|
value.f32 = property && property->type == PROP_FLOAT ? property->floating : 0;
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
ThemeVariant ExportValue(ExportContext *data, uintptr_t additionalOffset, Object *object, const char *cPropertyName, uint8_t type) {
|
|
uintptr_t depth = 0;
|
|
ThemeVariant value = {};
|
|
|
|
if (data->overrideData) {
|
|
Array<ThemeOverride> themeOverrides = {};
|
|
|
|
while (object && (depth++ < 100)) {
|
|
Property *property = PropertyFind(object, cPropertyName);
|
|
|
|
if (property) {
|
|
uint16_t state = PropertyReadInt32(object, "_primaryState") | PropertyReadInt32(object, "_stateBits");
|
|
value = PropertyToThemeVariant(property, type);
|
|
|
|
if (state) {
|
|
ThemeOverride themeOverride = {};
|
|
themeOverride.state = state;
|
|
themeOverride.duration = GraphGetIntegerFromProperty(PropertyFind(object, "_duration"));
|
|
themeOverride.offset = data->overrideOffset + (data->baseData ? data->baseData->position : 0) + additionalOffset;
|
|
themeOverride.type = type;
|
|
themeOverride.data = value;
|
|
themeOverrides.Insert(themeOverride, 0);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Go to the inheritance parent object.
|
|
property = PropertyFind(object, "_parent", PROP_OBJECT);
|
|
object = ObjectFind(property ? property->object : 0, true);
|
|
}
|
|
|
|
EsBufferWrite(data->overrideData, &themeOverrides[0], themeOverrides.Length() * sizeof(ThemeOverride));
|
|
themeOverrides.Free();
|
|
} else {
|
|
value = PropertyToThemeVariant(PropertyFindOrInherit(object, cPropertyName), type);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
#define ExportColor(data, structure, field, object, cPropertyName) \
|
|
structure.field = ExportValue(data, ((uint8_t *) &structure.field - (uint8_t *) &structure), object, cPropertyName, THEME_OVERRIDE_COLOR).u32
|
|
#define ExportI8(data, structure, field, object, cPropertyName) \
|
|
structure.field = ExportValue(data, ((uint8_t *) &structure.field - (uint8_t *) &structure), object, cPropertyName, THEME_OVERRIDE_I8).i8
|
|
#define ExportI16(data, structure, field, object, cPropertyName) \
|
|
structure.field = ExportValue(data, ((uint8_t *) &structure.field - (uint8_t *) &structure), object, cPropertyName, THEME_OVERRIDE_I16).i16
|
|
#define ExportF32(data, structure, field, object, cPropertyName) \
|
|
structure.field = ExportValue(data, ((uint8_t *) &structure.field - (uint8_t *) &structure), object, cPropertyName, THEME_OVERRIDE_F32).f32
|
|
|
|
void ExportWrite(ExportContext *context, const void *buffer, size_t bytes) {
|
|
if (context->baseData) {
|
|
EsBufferWrite(context->baseData, buffer, bytes);
|
|
}
|
|
}
|
|
|
|
void ExportGradientStopArray(Object *object, ExportContext *data, size_t stopCount) {
|
|
for (uintptr_t i = 0; i < stopCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
ThemeGradientStop stop = {};
|
|
sprintf(cPropertyName, "stops_%d_color", (int32_t) i);
|
|
ExportColor(data, stop, color, object, cPropertyName);
|
|
sprintf(cPropertyName, "stops_%d_position", (int32_t) i);
|
|
ExportI8(data, stop, position, object, cPropertyName);
|
|
sprintf(cPropertyName, "stops_%d_wci", (int32_t) i);
|
|
ExportI8(data, stop, windowColorIndex, object, cPropertyName);
|
|
ExportWrite(data, &stop, sizeof(stop));
|
|
}
|
|
}
|
|
|
|
int8_t ExportPaint(Object *parentObject, const char *cPropertyNameInParent, ExportContext *data, int depth = 0) {
|
|
Object *object = PropertyFindOrInheritReadObject(parentObject, cPropertyNameInParent);
|
|
|
|
if (!object || depth == 100) {
|
|
return 0;
|
|
}
|
|
|
|
if (object->type == OBJ_VAR_COLOR || object->type == OBJ_MOD_COLOR) {
|
|
if (data) {
|
|
ThemePaintSolid solid = {};
|
|
ExportColor(data, solid, color, parentObject, cPropertyNameInParent);
|
|
ExportWrite(data, &solid, sizeof(solid));
|
|
}
|
|
|
|
return THEME_PAINT_SOLID;
|
|
} else if (object->type == OBJ_PAINT_OVERWRITE) {
|
|
ExportPaint(object, "color", data, depth + 1);
|
|
return THEME_PAINT_OVERWRITE;
|
|
} else if (object->type == OBJ_PAINT_BIT_PATTERN) {
|
|
if (data) {
|
|
ThemePaintBitPattern bitPattern = {};
|
|
ExportColor(data, bitPattern, colors[0], object, "color0");
|
|
ExportColor(data, bitPattern, colors[1], object, "color1");
|
|
uint64_t pattern = PropertyFindOrInheritReadU64(object, "pattern");
|
|
memcpy(bitPattern.rows, &pattern, sizeof(uint64_t));
|
|
ExportWrite(data, &bitPattern, sizeof(bitPattern));
|
|
}
|
|
|
|
return THEME_PAINT_BIT_PATTERN;
|
|
} else if (object->type == OBJ_PAINT_LINEAR_GRADIENT) {
|
|
if (data) {
|
|
ThemePaintLinearGradient paint = {};
|
|
ExportF32(data, paint, transform[0], object, "transformX");
|
|
ExportF32(data, paint, transform[1], object, "transformY");
|
|
ExportF32(data, paint, transform[2], object, "transformStart");
|
|
paint.stopCount = PropertyFindOrInheritReadInt32(object, "stops_count");
|
|
paint.useGammaInterpolation = !!PropertyFindOrInheritReadInt32(object, "useGammaInterpolation");
|
|
paint.repeatMode = PropertyFindOrInheritReadInt32(object, "repeatMode");
|
|
ExportWrite(data, &paint, sizeof(paint));
|
|
ExportGradientStopArray(object, data, paint.stopCount);
|
|
}
|
|
|
|
return THEME_PAINT_LINEAR_GRADIENT;
|
|
} else if (object->type == OBJ_PAINT_RADIAL_GRADIENT) {
|
|
if (data) {
|
|
ThemePaintRadialGradient paint = {};
|
|
ExportF32(data, paint, transform[0], object, "transform0");
|
|
ExportF32(data, paint, transform[1], object, "transform1");
|
|
ExportF32(data, paint, transform[2], object, "transform2");
|
|
ExportF32(data, paint, transform[3], object, "transform3");
|
|
ExportF32(data, paint, transform[4], object, "transform4");
|
|
ExportF32(data, paint, transform[5], object, "transform5");
|
|
paint.stopCount = PropertyFindOrInheritReadInt32(object, "stops_count");
|
|
paint.useGammaInterpolation = !!PropertyFindOrInheritReadInt32(object, "useGammaInterpolation");
|
|
paint.repeatMode = PropertyFindOrInheritReadInt32(object, "repeatMode");
|
|
ExportWrite(data, &paint, sizeof(paint));
|
|
ExportGradientStopArray(object, data, paint.stopCount);
|
|
}
|
|
|
|
return THEME_PAINT_RADIAL_GRADIENT;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void ExportLayerBox(Object *object, ExportContext *data) {
|
|
ThemeLayerBox box = {};
|
|
box.mainPaintType = ExportPaint(object, "mainPaint", nullptr);
|
|
box.borderPaintType = ExportPaint(object, "borderPaint", nullptr);
|
|
ExportI8(data, box, borders.l, object, "borders0");
|
|
ExportI8(data, box, borders.r, object, "borders1");
|
|
ExportI8(data, box, borders.t, object, "borders2");
|
|
ExportI8(data, box, borders.b, object, "borders3");
|
|
ExportI8(data, box, corners.tl, object, "corners0");
|
|
ExportI8(data, box, corners.tr, object, "corners1");
|
|
ExportI8(data, box, corners.bl, object, "corners2");
|
|
ExportI8(data, box, corners.br, object, "corners3");
|
|
ExportI8(data, box, offset.l, object, "offset0");
|
|
ExportI8(data, box, offset.r, object, "offset1");
|
|
ExportI8(data, box, offset.t, object, "offset2");
|
|
ExportI8(data, box, offset.b, object, "offset3");
|
|
if (GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "isBlurred" ))) box.flags |= THEME_LAYER_BOX_IS_BLURRED;
|
|
if (GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "autoCorners" ))) box.flags |= THEME_LAYER_BOX_AUTO_CORNERS;
|
|
if (GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "autoBorders" ))) box.flags |= THEME_LAYER_BOX_AUTO_BORDERS;
|
|
if (GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "shadowHiding"))) box.flags |= THEME_LAYER_BOX_SHADOW_HIDING;
|
|
ExportWrite(data, &box, sizeof(box));
|
|
ExportPaint(object, "mainPaint", data);
|
|
ExportPaint(object, "borderPaint", data);
|
|
}
|
|
|
|
void ExportLayerPath(Object *object, ExportContext *data) {
|
|
Property *pointCount = PropertyFindOrInherit(object, "points_count", PROP_INT);
|
|
Property *fillCount = PropertyFindOrInherit(object, "fills_count", PROP_INT);
|
|
|
|
ThemeLayerPath path = {};
|
|
if (GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "pathFillEvenOdd"))) path.flags |= THEME_LAYER_PATH_FILL_EVEN_ODD;
|
|
if (GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "pathClosed"))) path.flags |= THEME_LAYER_PATH_CLOSED;
|
|
ExportI16(data, path, alpha, object, "alpha");
|
|
path.pointCount = pointCount ? pointCount->integer : 0;
|
|
path.fillCount = fillCount ? fillCount->integer : 0;
|
|
ExportWrite(data, &path, sizeof(path));
|
|
|
|
for (uintptr_t i = 0; i < path.pointCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
ThemeVariant value;
|
|
#define LAYER_PATH_WRITE_POINT(x) \
|
|
sprintf(cPropertyName, "points_%d_" #x, (int32_t) i); \
|
|
value = ExportValue(data, 0, object, cPropertyName, THEME_OVERRIDE_F32); \
|
|
ExportWrite(data, &value.f32, sizeof(float));
|
|
LAYER_PATH_WRITE_POINT(x0);
|
|
LAYER_PATH_WRITE_POINT(y0);
|
|
LAYER_PATH_WRITE_POINT(x1);
|
|
LAYER_PATH_WRITE_POINT(y1);
|
|
LAYER_PATH_WRITE_POINT(x2);
|
|
LAYER_PATH_WRITE_POINT(y2);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < path.fillCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
ThemeLayerPathFill fill = {};
|
|
|
|
sprintf(cPropertyName, "fills_%d_paint", (int32_t) i);
|
|
fill.paintAndFillType |= ExportPaint(object, cPropertyName, nullptr);
|
|
|
|
sprintf(cPropertyName, "fills_%d_mode", (int32_t) i);
|
|
Object *mode = PropertyFindOrInheritReadObject(object, cPropertyName);
|
|
|
|
// TODO Dashed contours.
|
|
|
|
if (mode && mode->type == OBJ_VAR_CONTOUR_STYLE) {
|
|
fill.paintAndFillType |= THEME_PATH_FILL_CONTOUR;
|
|
} else {
|
|
fill.paintAndFillType |= THEME_PATH_FILL_SOLID;
|
|
}
|
|
|
|
ExportWrite(data, &fill, sizeof(fill));
|
|
|
|
sprintf(cPropertyName, "fills_%d_paint", (int32_t) i);
|
|
ExportPaint(object, cPropertyName, data);
|
|
|
|
if (mode && mode->type == OBJ_VAR_CONTOUR_STYLE) {
|
|
ThemeLayerPathFillContour contour = {};
|
|
ExportF32(data, contour, miterLimit, mode, "miterLimit");
|
|
ExportI8(data, contour, internalWidth, mode, "internalWidth");
|
|
ExportI8(data, contour, externalWidth, mode, "externalWidth");
|
|
contour.mode = PropertyFindOrInheritReadInt32(mode, "joinMode")
|
|
| (PropertyFindOrInheritReadInt32(mode, "capMode") << 2)
|
|
| (PropertyFindOrInheritReadInt32(mode, "integerWidthsOnly") ? 0x80 : 0);
|
|
ExportWrite(data, &contour, sizeof(contour));
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExportPaintAsLayerBox(Object *object, ExportContext *data) {
|
|
Object parentObject = {};
|
|
Property property = {};
|
|
property.type = PROP_OBJECT;
|
|
property.cName[0] = '.';
|
|
property.object = object->id;
|
|
parentObject.properties.Add(property);
|
|
ThemeLayerBox box = {};
|
|
box.mainPaintType = ExportPaint(&parentObject, ".", nullptr);
|
|
ExportWrite(data, &box, sizeof(box));
|
|
ExportPaint(&parentObject, ".", data);
|
|
parentObject.properties.Free();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
#define CANVAS_ALIGN (20)
|
|
|
|
UIRectangle CanvasGetObjectBounds(Object *object) {
|
|
int32_t x = (PropertyReadInt32(object, "_graphX") - canvas->panX) * canvas->zoom + canvas->bounds.l;
|
|
int32_t y = (PropertyReadInt32(object, "_graphY") - canvas->panY) * canvas->zoom + canvas->bounds.t;
|
|
int32_t w = PropertyReadInt32(object, "_graphW") * canvas->zoom;
|
|
int32_t h = PropertyReadInt32(object, "_graphH") * canvas->zoom;
|
|
|
|
if (w < 1) w = 1;
|
|
if (h < 1) h = 1;
|
|
|
|
UIRectangle bounds = UI_RECT_4(x, x + w, y, y + h);
|
|
|
|
if (object->flags & OBJECT_IS_SELECTED) {
|
|
if (canvas->dragging) {
|
|
UIRectangle offset = ScaleRectangle(UI_RECT_2(canvas->dragDeltaX, canvas->dragDeltaY), canvas->zoom);
|
|
bounds = UIRectangleAdd(bounds, offset);
|
|
}
|
|
|
|
if (canvas->resizing) {
|
|
bounds = UIRectangleAdd(bounds, canvas->resizeOffsets);
|
|
}
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
void CanvasSelectObject(Object *object) {
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
objects[i].flags &= ~OBJECT_IS_SELECTED;
|
|
}
|
|
|
|
if (canvas->showPrototype) CanvasSwitchView(nullptr);
|
|
UIRectangle bounds = CanvasGetObjectBounds(object);
|
|
canvas->panX += ((bounds.l + bounds.r) / 2 - UI_RECT_WIDTH(canvas->bounds) / 2) / canvas->zoom;
|
|
canvas->panY += ((bounds.t + bounds.b) / 2 - UI_RECT_HEIGHT(canvas->bounds) / 2) / canvas->zoom;
|
|
ObjectSetSelected(object->id);
|
|
UIElementRefresh(canvas);
|
|
InspectorPopulate();
|
|
}
|
|
|
|
void CanvasDrawArrow(UIPainter *painter, int x0, int y0, int x1, int y1, uint32_t color) {
|
|
if (!UIDrawLine(painter, x0, y0, x1, y1, color)) return;
|
|
float angle = atan2f(y1 - y0, x1 - x0);
|
|
UIDrawLine(painter, x0, y0, x0 + cosf(angle + 0.5f) * 15, y0 + sinf(angle + 0.5f) * 15, color);
|
|
UIDrawLine(painter, x0, y0, x0 + cosf(angle - 0.5f) * 15, y0 + sinf(angle - 0.5f) * 15, color);
|
|
}
|
|
|
|
void CanvasDrawLayerFromData(UIPainter *painter, UIRectangle bounds, EsBuffer data) {
|
|
EsPaintTarget paintTarget = {};
|
|
EsPainter themePainter = {};
|
|
themePainter.target = &paintTarget;
|
|
themePainter.clip.l = painter->clip.l;
|
|
themePainter.clip.r = painter->clip.r;
|
|
themePainter.clip.t = painter->clip.t;
|
|
themePainter.clip.b = painter->clip.b;
|
|
themePainter.target->bits = painter->bits;
|
|
themePainter.target->width = painter->width;
|
|
themePainter.target->height = painter->height;
|
|
themePainter.target->stride = painter->width * 4;
|
|
|
|
data.bytes = data.position;
|
|
data.position = 0;
|
|
|
|
ThemeDrawLayer(&themePainter, bounds, &data, canvas->zoom, UI_RECT_1(0));
|
|
}
|
|
|
|
void CanvasDrawColorSwatch(Object *object, UIRectangle bounds, UIPainter *painter) {
|
|
for (int32_t y = bounds.t, y0 = 0; y < bounds.b; y += 15, y0++) {
|
|
for (int32_t x = bounds.l, x0 = 0; x < bounds.r; x += 15, x0++) {
|
|
UIDrawBlock(painter, UIRectangleIntersection(UI_RECT_4(x, x + 15, y, y + 15), bounds), ((x0 ^ y0) & 1) ? 0xFF808080 : 0xFFC0C0C0);
|
|
}
|
|
}
|
|
|
|
uint8_t buffer[4096];
|
|
EsBuffer data = { .out = buffer, .bytes = sizeof(buffer) };
|
|
ExportContext context = { .baseData = &data };
|
|
ThemeLayer layer = { .position = { .r = 100, .b = 100 }, .type = THEME_LAYER_BOX };
|
|
ExportWrite(&context, &layer, sizeof(layer));
|
|
ExportPaintAsLayerBox(object, &context);
|
|
CanvasDrawLayerFromData(painter, bounds, data);
|
|
}
|
|
|
|
void CanvasDrawLayer(Object *object, UIRectangle bounds, UIPainter *painter, int depth = 0) {
|
|
if (!object || depth == 100) {
|
|
return;
|
|
}
|
|
|
|
if (object->type == OBJ_LAYER_BOX) {
|
|
uint8_t buffer[4096];
|
|
EsBuffer data = { .out = buffer, .bytes = sizeof(buffer) };
|
|
ExportContext context = { .baseData = &data };
|
|
ThemeLayer layer = { .position = { .r = 100, .b = 100 }, .type = THEME_LAYER_BOX };
|
|
ExportWrite(&context, &layer, sizeof(layer));
|
|
ExportLayerBox(object, &context);
|
|
CanvasDrawLayerFromData(painter, bounds, data);
|
|
} else if (object->type == OBJ_LAYER_TEXT) {
|
|
#ifdef OS_ESSENCE
|
|
EsPaintTarget paintTarget = {};
|
|
_EsPainter themePainter = {};
|
|
themePainter.target = (_EsPaintTarget *) &paintTarget;
|
|
themePainter.clip.l = painter->clip.l;
|
|
themePainter.clip.r = painter->clip.r;
|
|
themePainter.clip.t = painter->clip.t;
|
|
themePainter.clip.b = painter->clip.b;
|
|
paintTarget.bits = painter->bits;
|
|
paintTarget.width = painter->width;
|
|
paintTarget.height = painter->height;
|
|
paintTarget.stride = painter->width * 4;
|
|
EsTextStyle textStyle = {};
|
|
textStyle.font.family = ES_FONT_SANS;
|
|
textStyle.font.weight = 5;
|
|
textStyle.size = 10;
|
|
textStyle.color = GraphGetColorFromProperty(PropertyFindOrInherit(object, "color"));
|
|
textStyle.blur = GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "blur"));
|
|
if (textStyle.blur > 10) textStyle.blur = 10;
|
|
EsDrawTextSimple((_EsPainter *) &themePainter, ui.instance->window, bounds, "Sample", -1, textStyle, ES_TEXT_H_CENTER | ES_TEXT_V_CENTER);
|
|
#endif
|
|
} else if (object->type == OBJ_LAYER_PATH) {
|
|
uint8_t buffer[4096];
|
|
EsBuffer data = { .out = buffer, .bytes = sizeof(buffer) };
|
|
ExportContext context = { .baseData = &data };
|
|
ThemeLayer layer = { .position = { .r = 100, .b = 100 }, .type = THEME_LAYER_PATH };
|
|
ExportWrite(&context, &layer, sizeof(layer));
|
|
ExportLayerPath(object, &context);
|
|
CanvasDrawLayerFromData(painter, bounds, data);
|
|
} else if (object->type == OBJ_LAYER_GROUP) {
|
|
int32_t layerCount = PropertyReadInt32(object, "layers_count");
|
|
if (layerCount < 0) layerCount = 0;
|
|
if (layerCount > 100) layerCount = 100;
|
|
|
|
int32_t inWidth = UI_RECT_WIDTH(bounds);
|
|
int32_t inHeight = UI_RECT_HEIGHT(bounds);
|
|
|
|
for (int32_t i = 0; i < layerCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
sprintf(cPropertyName, "layers_%d_layer", i);
|
|
Property *layerProperty = PropertyFind(object, cPropertyName, PROP_OBJECT);
|
|
Object *layerObject = ObjectFind(layerProperty ? layerProperty->object : 0, true);
|
|
|
|
#define LAYER_READ_INT32(x) sprintf(cPropertyName, "layers_%d_" #x, i); int32_t x = PropertyReadInt32(object, cPropertyName)
|
|
LAYER_READ_INT32(offset0);
|
|
LAYER_READ_INT32(offset1);
|
|
LAYER_READ_INT32(offset2);
|
|
LAYER_READ_INT32(offset3);
|
|
LAYER_READ_INT32(position0);
|
|
LAYER_READ_INT32(position1);
|
|
LAYER_READ_INT32(position2);
|
|
LAYER_READ_INT32(position3);
|
|
#undef LAYER_READ_INT32
|
|
|
|
UIRectangle outBounds;
|
|
outBounds.l = bounds.l + offset0 * canvas->zoom + position0 * inWidth / 100;
|
|
outBounds.r = bounds.l + offset1 * canvas->zoom + position1 * inWidth / 100;
|
|
outBounds.t = bounds.t + offset2 * canvas->zoom + position2 * inHeight / 100;
|
|
outBounds.b = bounds.t + offset3 * canvas->zoom + position3 * inHeight / 100;
|
|
|
|
CanvasDrawLayer(layerObject, outBounds, painter, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanvasDrawStyle(Object *object, UIRectangle bounds, UIPainter *painter, int depth = 0) {
|
|
if (!object || depth == 100) {
|
|
return;
|
|
}
|
|
|
|
#ifdef OS_ESSENCE
|
|
EsPaintTarget paintTarget = {};
|
|
_EsPainter themePainter = {};
|
|
themePainter.target = (_EsPaintTarget *) &paintTarget;
|
|
themePainter.clip.l = painter->clip.l;
|
|
themePainter.clip.r = painter->clip.r;
|
|
themePainter.clip.t = painter->clip.t;
|
|
themePainter.clip.b = painter->clip.b;
|
|
paintTarget.bits = painter->bits;
|
|
paintTarget.width = painter->width;
|
|
paintTarget.height = painter->height;
|
|
paintTarget.stride = painter->width * 4;
|
|
|
|
if (object->type == OBJ_VAR_TEXT_STYLE) {
|
|
EsTextStyle textStyle = {};
|
|
textStyle.font.family = GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "fontFamily"));
|
|
textStyle.font.weight = GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "fontWeight"));
|
|
textStyle.font.flags = (GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "isItalic"))) ? ES_FONT_ITALIC : ES_FLAGS_DEFAULT;
|
|
textStyle.size = GraphGetIntegerFromProperty(PropertyFindOrInherit(object, "textSize")) * canvas->zoom;
|
|
textStyle.color = GraphGetColorFromProperty(PropertyFindOrInherit(object, "textColor"));
|
|
EsDrawTextSimple((_EsPainter *) &themePainter, ui.instance->window, bounds, "Sample", -1, textStyle, ES_TEXT_H_CENTER | ES_TEXT_V_CENTER);
|
|
#if 0
|
|
EsDrawStandardIcon((_EsPainter *) &themePainter, ES_ICON_GO_NEXT_SYMBOLIC,
|
|
GraphGetIntegerFromProperty(PropertyFindOrInherit(false, object, "iconSize")), bounds,
|
|
GraphGetColorFromProperty(PropertyFindOrInherit(false, object, "iconColor")));
|
|
#endif
|
|
}
|
|
#else
|
|
if (object->type == OBJ_VAR_TEXT_STYLE && depth == 0) {
|
|
UIDrawString(painter, bounds, "TxtStyle", -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
|
|
}
|
|
#endif
|
|
|
|
if (object->type == OBJ_STYLE) {
|
|
Property *appearance = PropertyFindOrInherit(object, "appearance");
|
|
Property *textStyle = PropertyFindOrInherit(object, "textStyle");
|
|
if (appearance) CanvasDrawLayer(ObjectFind(appearance->object, true), bounds, painter, depth + 1);
|
|
if (textStyle) CanvasDrawStyle(ObjectFind(textStyle->object, true), bounds, painter, depth + 1);
|
|
}
|
|
}
|
|
|
|
int ResizeHandleMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
uintptr_t side = (uintptr_t) element->cp;
|
|
|
|
if (message == UI_MSG_PAINT) {
|
|
UIDrawRectangle((UIPainter *) dp, element->bounds, 0xFFF8F8F8, 0xFF404040, UI_RECT_1(1));
|
|
} else if (message == UI_MSG_LEFT_DOWN) {
|
|
canvas->originalBounds = CanvasGetObjectBounds(ObjectFind(selectedObjectID, false));
|
|
} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1) {
|
|
if (side == 0) canvas->resizeOffsets = UI_RECT_4(element->window->cursorX - canvas->originalBounds.l, 0, 0, 0);
|
|
if (side == 1) canvas->resizeOffsets = UI_RECT_4(0, element->window->cursorX - canvas->originalBounds.r, 0, 0);
|
|
if (side == 2) canvas->resizeOffsets = UI_RECT_4(0, 0, element->window->cursorY - canvas->originalBounds.t, 0);
|
|
if (side == 3) canvas->resizeOffsets = UI_RECT_4(0, 0, 0, element->window->cursorY - canvas->originalBounds.b);
|
|
canvas->resizing = true;
|
|
UIElementRefresh(canvas);
|
|
} else if (message == UI_MSG_LEFT_UP) {
|
|
Object *object = ObjectFind(selectedObjectID, false);
|
|
int32_t x = PropertyReadInt32(object, "_graphX");
|
|
int32_t y = PropertyReadInt32(object, "_graphY");
|
|
int32_t w = PropertyReadInt32(object, "_graphW");
|
|
int32_t h = PropertyReadInt32(object, "_graphH");
|
|
UIRectangle oldBounds = UI_RECT_4(x, x + w, y, y + h);
|
|
UIRectangle newBounds = UIRectangleAdd(ScaleRectangle(canvas->resizeOffsets, 1.0f / canvas->zoom), oldBounds);
|
|
canvas->resizing = false;
|
|
|
|
if (object) {
|
|
Step step = {};
|
|
step.type = STEP_MODIFY_PROPERTY;
|
|
step.property.type = PROP_INT;
|
|
step.objectID = selectedObjectID;
|
|
|
|
strcpy(step.property.cName, "_graphX");
|
|
step.property.integer = newBounds.l;
|
|
DocumentApplyStep(step, STEP_APPLY_GROUPED);
|
|
strcpy(step.property.cName, "_graphY");
|
|
step.property.integer = newBounds.t;
|
|
DocumentApplyStep(step, STEP_APPLY_GROUPED);
|
|
strcpy(step.property.cName, "_graphW");
|
|
step.property.integer = UI_RECT_WIDTH(newBounds);
|
|
DocumentApplyStep(step, STEP_APPLY_GROUPED);
|
|
strcpy(step.property.cName, "_graphH");
|
|
step.property.integer = UI_RECT_HEIGHT(newBounds);
|
|
DocumentApplyStep(step);
|
|
}
|
|
} else if (message == UI_MSG_GET_CURSOR) {
|
|
if (side == 0) return UI_CURSOR_RESIZE_LEFT;
|
|
if (side == 1) return UI_CURSOR_RESIZE_RIGHT;
|
|
if (side == 2) return UI_CURSOR_RESIZE_UP;
|
|
if (side == 3) return UI_CURSOR_RESIZE_DOWN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_PAINT) {
|
|
UIPainter *painter = (UIPainter *) dp;
|
|
UIDrawBlock(painter, element->bounds, 0xFFC0C0C0);
|
|
UIRectangle selectionBounds = UI_RECT_4(MinimumInteger(canvas->leftDownX, canvas->selectX), MaximumInteger(canvas->leftDownX, canvas->selectX),
|
|
MinimumInteger(canvas->leftDownY, canvas->selectY), MaximumInteger(canvas->leftDownY, canvas->selectY));
|
|
|
|
if (canvas->selecting) {
|
|
UIDrawBlock(painter, selectionBounds, 0xFF99CCFF);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
|
|
UIRectangle bounds = CanvasGetObjectBounds(object);
|
|
|
|
if (bounds.r < element->bounds.l || bounds.l > element->bounds.r
|
|
|| bounds.b < element->bounds.t || bounds.t > element->bounds.b) {
|
|
continue;
|
|
}
|
|
|
|
UIRectangle selectionIntersection = UIRectangleIntersection(bounds, selectionBounds);
|
|
bool isSelected = (object->flags & OBJECT_IS_SELECTED) == (inspectorPickData == nullptr)
|
|
|| (canvas->selecting && UI_RECT_VALID(selectionIntersection));
|
|
bool isConditional = ObjectIsConditional(object);
|
|
|
|
if (!canvas->showPrototype) {
|
|
if (isSelected) {
|
|
UIDrawBorder(painter, UIRectangleAdd(bounds, UI_RECT_1I(-3)), 0xFF4092FF, UI_RECT_1(3));
|
|
}
|
|
|
|
if (object->type == OBJ_COMMENT || canvas->zoom > 0.1f) {
|
|
UIDrawString(painter, UI_RECT_4(bounds.l, element->bounds.r, bounds.t - ui.activeFont->glyphHeight, bounds.t),
|
|
object->cName, -1, 0xFF000000, UI_ALIGN_LEFT, nullptr);
|
|
}
|
|
|
|
UIDrawRectangle(painter, bounds, 0xFFE0E0E0, 0xFF404040, UI_RECT_1(1));
|
|
UIDrawBlock(painter, UI_RECT_4(bounds.l + 1, bounds.r + 1, bounds.b, bounds.b + 1), 0xFF404040);
|
|
UIDrawBlock(painter, UI_RECT_4(bounds.r, bounds.r + 1, bounds.t + 1, bounds.b + 1), 0xFF404040);
|
|
|
|
if (isConditional && canvas->zoom > 0.1f) {
|
|
UIRectangle indicator = UI_RECT_4(bounds.l - ui.activeFont->glyphWidth, bounds.l, bounds.t, bounds.t + ui.activeFont->glyphHeight);
|
|
UIDrawBlock(painter, indicator, 0xFFFFFF00);
|
|
UIDrawString(painter, indicator, "?", -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
|
|
}
|
|
|
|
bounds = UIRectangleAdd(bounds, UI_RECT_1I(5));
|
|
}
|
|
|
|
if (selectedObjectID == object->id && canvas->resizing) {
|
|
char buffer[32];
|
|
snprintf(buffer, sizeof(buffer), "%dx%d", UI_RECT_WIDTH(bounds), UI_RECT_HEIGHT(bounds));
|
|
UIDrawString(painter, UI_RECT_4(bounds.l, bounds.r, bounds.t - ui.activeFont->glyphHeight, bounds.t),
|
|
buffer, -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
|
|
}
|
|
|
|
if (isConditional) {
|
|
canvas->previewStateActive = true;
|
|
canvas->previewPrimaryState = PropertyReadInt32(object, "_primaryState");
|
|
canvas->previewStateBits = PropertyReadInt32(object, "_stateBits");
|
|
}
|
|
|
|
if (object->type == OBJ_SELECTOR) {
|
|
// Resolve the selector before painting.
|
|
Object *link = ObjectFind(object->id, true);
|
|
if (link) object = link;
|
|
}
|
|
|
|
if (object->type == OBJ_VAR_INT || object->type == OBJ_MOD_MULTIPLY) {
|
|
int32_t value = GraphGetInteger(object);
|
|
char buffer[32];
|
|
snprintf(buffer, sizeof(buffer), "%d", value);
|
|
UIDrawString(painter, bounds, buffer, -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
|
|
} else if (object->type == OBJ_LAYER_BOX || object->type == OBJ_LAYER_GROUP
|
|
|| object->type == OBJ_LAYER_TEXT || object->type == OBJ_LAYER_PATH) {
|
|
CanvasDrawLayer(object, bounds, painter);
|
|
} else if (object->type == OBJ_PAINT_LINEAR_GRADIENT || object->type == OBJ_PAINT_RADIAL_GRADIENT || object->type == OBJ_PAINT_BIT_PATTERN) {
|
|
CanvasDrawColorSwatch(object, bounds, painter);
|
|
} else if (object->type == OBJ_VAR_COLOR || object->type == OBJ_MOD_COLOR) {
|
|
CanvasDrawColorSwatch(object, bounds, painter);
|
|
|
|
if (canvas->zoom >= 1.0f) {
|
|
uint32_t color = GraphGetColor(object);
|
|
bool isLight = EsColorIsLight(color);
|
|
char buffer[32];
|
|
snprintf(buffer, sizeof(buffer), "%.8X", color);
|
|
UIRectangle area = UI_RECT_4(bounds.l, bounds.r, bounds.t, bounds.t + UIMeasureStringHeight());
|
|
UIDrawString(painter, area, buffer, -1, isLight ? 0xFF000000 : 0xFFFFFFFF, UI_ALIGN_CENTER, nullptr);
|
|
}
|
|
} else if (object->type == OBJ_VAR_TEXT_STYLE || object->type == OBJ_STYLE) {
|
|
CanvasDrawStyle(object, bounds, painter);
|
|
} else if (object->type == OBJ_INSTANCE) {
|
|
Property *style = PropertyFind(object, "style", PROP_OBJECT);
|
|
canvas->previewStateActive = object->id == selectedObjectID;
|
|
Object *styleObject = ObjectFind(style ? style->object : 0, false);
|
|
Rectangle8 paintOutsets = ExportCalculatePaintOutsets(PropertyFindOrInheritReadObject(styleObject, "appearance"));
|
|
UIRectangle clip = bounds;
|
|
clip.l -= ceilf(paintOutsets.l * canvas->zoom);
|
|
clip.r += ceilf(paintOutsets.r * canvas->zoom);
|
|
clip.t -= ceilf(paintOutsets.t * canvas->zoom);
|
|
clip.b += ceilf(paintOutsets.b * canvas->zoom);
|
|
UIPainter painter2 = *painter;
|
|
painter2.clip = UIRectangleIntersection(painter2.clip, clip);
|
|
CanvasDrawStyle(styleObject, bounds, &painter2);
|
|
} else if (object->type == OBJ_LAYER_METRICS) {
|
|
// TODO Visually show the preferred size, insets and gaps?
|
|
UIDrawString(painter, bounds, "Metrics", -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
|
|
} else {
|
|
// TODO Preview for OBJ_VAR_CONTOUR_STYLE.
|
|
}
|
|
|
|
canvas->previewStateActive = false;
|
|
|
|
if (!canvas->showPrototype) {
|
|
canvas->previewPrimaryState = THEME_PRIMARY_STATE_IDLE;
|
|
canvas->previewStateBits = 0;
|
|
}
|
|
}
|
|
|
|
if (canvas->arrowMode && !canvas->showPrototype) {
|
|
// Draw object connections.
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
|
|
UIRectangle b1 = CanvasGetObjectBounds(object);
|
|
|
|
for (uintptr_t j = 0; j < object->properties.Length(); j++) {
|
|
if (object->properties[j].type == PROP_OBJECT) {
|
|
Object *target = ObjectFind(object->properties[j].object, false);
|
|
if (!target) continue;
|
|
|
|
bool sourceIsSelected = object->flags & OBJECT_IS_SELECTED;
|
|
bool targetIsSelected = target->flags & OBJECT_IS_SELECTED;
|
|
|
|
if (canvas->arrowMode == ARROW_MODE_ALL) {
|
|
} else if (canvas->arrowMode == ARROW_MODE_FROM) {
|
|
if (!sourceIsSelected) continue;
|
|
} else if (canvas->arrowMode == ARROW_MODE_TO) {
|
|
if (!targetIsSelected) continue;
|
|
} else if (canvas->arrowMode == ARROW_MODE_FROM_OR_TO) {
|
|
if (!targetIsSelected && !sourceIsSelected) continue;
|
|
} else if (canvas->arrowMode == ARROW_MODE_FROM_AND_TO) {
|
|
if (!targetIsSelected || !sourceIsSelected) continue;
|
|
}
|
|
|
|
UIRectangle b2 = CanvasGetObjectBounds(target);
|
|
CanvasDrawArrow(painter, (b2.l + b2.r) / 2, (b2.t + b2.b) / 2, (b1.l + b1.r) / 2, (b1.t + b1.b) / 2, 0xFF000000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (message == UI_MSG_LEFT_DOWN) {
|
|
canvas->canDrag = true;
|
|
bool foundObjectToSelect = false;
|
|
|
|
for (uintptr_t i = objects.Length(); i > 0; i--) {
|
|
Object *object = &objects[i - 1];
|
|
if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
|
|
UIRectangle bounds = CanvasGetObjectBounds(object);
|
|
|
|
if (UIRectangleContains(bounds, element->window->cursorX, element->window->cursorY)) {
|
|
if (inspectorPickData) {
|
|
canvas->canDrag = false;
|
|
|
|
Step step = {};
|
|
step.type = STEP_MODIFY_PROPERTY;
|
|
step.objectID = inspectorPickData->objectID;
|
|
strcpy(step.property.cName, inspectorPickData->cPropertyName);
|
|
step.property.type = PROP_OBJECT;
|
|
step.property.object = object->id;
|
|
InspectorBroadcastStep(step, inspectorPickData);
|
|
DocumentApplyStep(step);
|
|
} else {
|
|
if (~object->flags & OBJECT_IS_SELECTED) {
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
objects[i].flags &= ~OBJECT_IS_SELECTED;
|
|
}
|
|
}
|
|
|
|
ObjectSetSelected(object->id, false /* do not clear selection flag from previous */);
|
|
canvas->dragOffsetX = bounds.l - element->window->cursorX;
|
|
canvas->dragOffsetY = bounds.t - element->window->cursorY;
|
|
}
|
|
|
|
foundObjectToSelect = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundObjectToSelect) {
|
|
ObjectSetSelected(0);
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
objects[i].flags &= ~OBJECT_IS_SELECTED;
|
|
}
|
|
}
|
|
|
|
canvas->leftDownX = element->window->cursorX;
|
|
canvas->leftDownY = element->window->cursorY;
|
|
|
|
UIElementRefresh(element);
|
|
InspectorPopulate();
|
|
InspectorPickTargetEnd();
|
|
} else if (message == UI_MSG_LEFT_UP && canvas->dragging) {
|
|
Object *object = ObjectFind(selectedObjectID, false);
|
|
int32_t oldX = PropertyReadInt32(object, "_graphX");
|
|
int32_t oldY = PropertyReadInt32(object, "_graphY");
|
|
|
|
if ((canvas->dragDeltaX || canvas->dragDeltaY) && object) {
|
|
Step step = {};
|
|
step.type = STEP_MODIFY_PROPERTY;
|
|
step.property.type = PROP_INT;
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
|
|
|
|
if ((object->flags & OBJECT_IS_SELECTED) && object->id != selectedObjectID) {
|
|
step.objectID = object->id;
|
|
strcpy(step.property.cName, "_graphX");
|
|
step.property.integer = PropertyReadInt32(object, "_graphX") + canvas->dragDeltaX;
|
|
DocumentApplyStep(step, STEP_APPLY_GROUPED);
|
|
strcpy(step.property.cName, "_graphY");
|
|
step.property.integer = PropertyReadInt32(object, "_graphY") + canvas->dragDeltaY;
|
|
DocumentApplyStep(step, STEP_APPLY_GROUPED);
|
|
}
|
|
}
|
|
|
|
step.objectID = selectedObjectID;
|
|
strcpy(step.property.cName, "_graphX");
|
|
step.property.integer = oldX + canvas->dragDeltaX;
|
|
DocumentApplyStep(step, STEP_APPLY_GROUPED);
|
|
strcpy(step.property.cName, "_graphY");
|
|
step.property.integer = oldY + canvas->dragDeltaY;
|
|
DocumentApplyStep(step);
|
|
}
|
|
|
|
canvas->dragging = false;
|
|
UIElementRefresh(canvas);
|
|
} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1 && selectedObjectID && canvas->canDrag) {
|
|
int32_t dx = canvas->leftDownX - element->window->cursorX;
|
|
int32_t dy = canvas->leftDownY - element->window->cursorY;
|
|
|
|
if (canvas->dragging || dx * dx + dy * dy > 200) {
|
|
int32_t canvasDragNewX = (element->window->cursorX + canvas->dragOffsetX - canvas->bounds.l) / canvas->zoom + canvas->panX;
|
|
int32_t canvasDragNewY = (element->window->cursorY + canvas->dragOffsetY - canvas->bounds.t) / canvas->zoom + canvas->panY;
|
|
if (!canvas->showPrototype) canvasDragNewX -= canvasDragNewX % CANVAS_ALIGN, canvasDragNewY -= canvasDragNewY % CANVAS_ALIGN;
|
|
canvas->dragDeltaX = canvasDragNewX - PropertyReadInt32(ObjectFind(selectedObjectID, false), "_graphX");
|
|
canvas->dragDeltaY = canvasDragNewY - PropertyReadInt32(ObjectFind(selectedObjectID, false), "_graphY");
|
|
canvas->dragging = true;
|
|
UIElementRefresh(canvas);
|
|
}
|
|
} else if (message == UI_MSG_LEFT_UP && canvas->selecting) {
|
|
UIRectangle selectionBounds = UI_RECT_4(MinimumInteger(canvas->leftDownX, canvas->selectX), MaximumInteger(canvas->leftDownX, canvas->selectX),
|
|
MinimumInteger(canvas->leftDownY, canvas->selectY), MaximumInteger(canvas->leftDownY, canvas->selectY));
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
|
|
|
|
UIRectangle bounds = CanvasGetObjectBounds(object);
|
|
UIRectangle selectionIntersection = UIRectangleIntersection(bounds, selectionBounds);
|
|
|
|
if (UI_RECT_VALID(selectionIntersection)) {
|
|
object->flags |= OBJECT_IS_SELECTED;
|
|
}
|
|
}
|
|
|
|
canvas->selecting = false;
|
|
UIElementRefresh(canvas);
|
|
} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1 && !selectedObjectID) {
|
|
canvas->selectX = element->window->cursorX;
|
|
canvas->selectY = element->window->cursorY;
|
|
canvas->selecting = true;
|
|
UIElementRefresh(canvas);
|
|
} else if (message == UI_MSG_MIDDLE_DOWN) {
|
|
canvas->lastPanPointX = element->window->cursorX;
|
|
canvas->lastPanPointY = element->window->cursorY;
|
|
_UIWindowSetCursor(element->window, UI_CURSOR_HAND);
|
|
} else if (message == UI_MSG_MIDDLE_UP) {
|
|
_UIWindowSetCursor(element->window, UI_CURSOR_ARROW);
|
|
} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 2) {
|
|
canvas->panX -= (element->window->cursorX - canvas->lastPanPointX) / canvas->zoom;
|
|
canvas->panY -= (element->window->cursorY - canvas->lastPanPointY) / canvas->zoom;
|
|
canvas->lastPanPointX = element->window->cursorX;
|
|
canvas->lastPanPointY = element->window->cursorY;
|
|
UIElementRefresh(canvas);
|
|
} else if (message == UI_MSG_MOUSE_WHEEL && !element->window->ctrl) {
|
|
canvas->panY += di / canvas->zoom;
|
|
UIElementRefresh(canvas);
|
|
} else if (message == UI_MSG_MOUSE_WHEEL && element->window->ctrl) {
|
|
int divisions = -di / 72;
|
|
float factor = 1, perDivision = 1.2f;
|
|
while (divisions > 0) factor *= perDivision, divisions--;
|
|
while (divisions < 0) factor /= perDivision, divisions++;
|
|
if (canvas->zoom * factor > 4) factor = 4 / canvas->zoom;
|
|
if (canvas->zoom * factor < 0.05) factor = 0.05 / canvas->zoom;
|
|
int mx = element->window->cursorX - element->bounds.l;
|
|
int my = element->window->cursorY - element->bounds.t;
|
|
canvas->zoom *= factor;
|
|
canvas->panX -= mx / canvas->zoom * (1 - factor);
|
|
canvas->panY -= my / canvas->zoom * (1 - factor);
|
|
UIElementRefresh(canvas);
|
|
} else if (message == UI_MSG_LAYOUT) {
|
|
UIElement *controls = canvas->showPrototype ? &prototypeControls->e : &graphControls->e;
|
|
(canvas->showPrototype ? &graphControls->e : &prototypeControls->e)->flags |= UI_ELEMENT_HIDE;
|
|
controls->flags &= ~UI_ELEMENT_HIDE;
|
|
int width = UIElementMessage(controls, UI_MSG_GET_WIDTH, 0, 0);
|
|
int height = UIElementMessage(controls, UI_MSG_GET_HEIGHT, 0, 0);
|
|
UIRectangle bounds = UI_RECT_4(element->bounds.l + 10, element->bounds.l + 10 + width, element->bounds.b - 10 - height, element->bounds.b - 10);
|
|
UIElementMove(controls, bounds, false);
|
|
|
|
Object *object = ObjectFind(selectedObjectID, false);
|
|
bool showHandles = canvas->showPrototype && selectedObjectID && object && (object->flags & OBJECT_IN_PROTOTYPE);
|
|
|
|
if (showHandles) {
|
|
UIRectangle bounds = CanvasGetObjectBounds(object);
|
|
int cx = (bounds.l + bounds.r) / 2, cy = (bounds.t + bounds.b) / 2;
|
|
const int size = 3;
|
|
UIElementMove(canvas->resizeHandles[0], UI_RECT_4(bounds.l - size, bounds.l + size + 1, cy - size, cy + size + 1), false);
|
|
UIElementMove(canvas->resizeHandles[1], UI_RECT_4(bounds.r - size - 1, bounds.r + size, cy - size, cy + size + 1), false);
|
|
UIElementMove(canvas->resizeHandles[2], UI_RECT_4(cx - size, cx + size + 1, bounds.t - size, bounds.t + size + 1), false);
|
|
UIElementMove(canvas->resizeHandles[3], UI_RECT_4(cx - size, cx + size + 1, bounds.b - size - 1, bounds.b + size), false);
|
|
}
|
|
|
|
SetBit(&canvas->resizeHandles[0]->flags, UI_ELEMENT_HIDE, !showHandles);
|
|
SetBit(&canvas->resizeHandles[1]->flags, UI_ELEMENT_HIDE, !showHandles);
|
|
SetBit(&canvas->resizeHandles[2]->flags, UI_ELEMENT_HIDE, !showHandles);
|
|
SetBit(&canvas->resizeHandles[3]->flags, UI_ELEMENT_HIDE, !showHandles);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CanvasArrowModeInternal(void *cp) {
|
|
canvas->arrowMode = (uintptr_t) cp;
|
|
UIElementRepaint(canvas, nullptr);
|
|
}
|
|
|
|
void CanvasArrowMode(void *) {
|
|
UIMenu *menu = UIMenuCreate(window->pressed, UI_MENU_NO_SCROLL | UI_MENU_PLACE_ABOVE);
|
|
UIMenuAddItem(menu, 0, "None", -1, CanvasArrowModeInternal, (void *) (uintptr_t) ARROW_MODE_NONE);
|
|
UIMenuAddItem(menu, 0, "All", -1, CanvasArrowModeInternal, (void *) (uintptr_t) ARROW_MODE_ALL);
|
|
UIMenuAddItem(menu, 0, "From selected", -1, CanvasArrowModeInternal, (void *) (uintptr_t) ARROW_MODE_FROM);
|
|
UIMenuAddItem(menu, 0, "To selected", -1, CanvasArrowModeInternal, (void *) (uintptr_t) ARROW_MODE_TO);
|
|
UIMenuAddItem(menu, 0, "From or to selected", -1, CanvasArrowModeInternal, (void *) (uintptr_t) ARROW_MODE_FROM_OR_TO);
|
|
UIMenuAddItem(menu, 0, "From and to selected", -1, CanvasArrowModeInternal, (void *) (uintptr_t) ARROW_MODE_FROM_AND_TO);
|
|
UIMenuShow(menu);
|
|
}
|
|
|
|
void CanvasSwitchView(void *) {
|
|
float z = canvas->swapZoom, x = canvas->swapPanX, y = canvas->swapPanY;
|
|
canvas->swapZoom = canvas->zoom, canvas->swapPanX = canvas->panX, canvas->swapPanY = canvas->panY;
|
|
canvas->zoom = z, canvas->panX = x, canvas->panY = y;
|
|
canvas->showPrototype = !canvas->showPrototype;
|
|
UIElementRefresh(canvas);
|
|
}
|
|
|
|
void CanvasSwitchSelectorIndex(void *) {
|
|
activeSelectorIndex = 1 - activeSelectorIndex;
|
|
UIElementRefresh(canvas);
|
|
}
|
|
|
|
void CanvasZoom100(void *) {
|
|
float factor = 1.0f / canvas->zoom;
|
|
canvas->zoom *= factor;
|
|
canvas->panX -= UI_RECT_WIDTH(canvas->bounds) / 2 / canvas->zoom * (1 - factor);
|
|
canvas->panY -= UI_RECT_HEIGHT(canvas->bounds) / 2 / canvas->zoom * (1 - factor);
|
|
UIElementRefresh(canvas);
|
|
}
|
|
|
|
void CanvasFindByID(void *) {
|
|
char *id = nullptr;
|
|
const char *dialog = "Enter the object ID:\n%t\n\n%l\n\n%f%b%b";
|
|
const char *result = UIDialogShow(window, 0, dialog, &id, "OK", "Cancel");
|
|
|
|
if (0 == strcmp(result, "OK")) {
|
|
uint64_t _id = strtoul(id, 0, 10);
|
|
Object *object = nullptr;
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].id == _id) {
|
|
object = &objects[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!object) {
|
|
UIDialogShow(window, 0, "Error: The object was not found.\n%f%b", "OK");
|
|
} else {
|
|
CanvasSelectObject(object);
|
|
}
|
|
}
|
|
|
|
free(id);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
void ObjectChangeTypeInternal(void *cp) {
|
|
Step step = {};
|
|
step.type = STEP_CHANGE_OBJECT_TYPE;
|
|
step.object.id = selectedObjectID;
|
|
step.object.type = (ObjectType) (uintptr_t) cp;
|
|
DocumentApplyStep(step);
|
|
}
|
|
|
|
void ObjectAddInternal(Object object) {
|
|
Step step = {};
|
|
step.type = STEP_ADD_OBJECT;
|
|
step.object = object;
|
|
DocumentApplyStep(step);
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
objects[i].flags &= ~OBJECT_IS_SELECTED;
|
|
}
|
|
|
|
ObjectSetSelected(object.id);
|
|
InspectorPopulate();
|
|
UIElementRefresh(canvas);
|
|
}
|
|
|
|
void ObjectAddCommandInternal(void *cp) {
|
|
Object object = {};
|
|
object.type = (ObjectType) (uintptr_t) cp;
|
|
object.id = ++objectIDAllocator;
|
|
Property p;
|
|
int32_t x = canvas->panX + UI_RECT_WIDTH(canvas->bounds) / 2 / canvas->zoom;
|
|
int32_t y = canvas->panY + UI_RECT_HEIGHT(canvas->bounds) / 2 / canvas->zoom;
|
|
x -= x % CANVAS_ALIGN, y -= y % CANVAS_ALIGN;
|
|
int32_t w = object.type == OBJ_COMMENT ? 30 : 80;
|
|
int32_t h = object.type == OBJ_COMMENT ? 10 : 60;
|
|
p = { .type = PROP_INT, .integer = x }; strcpy(p.cName, "_graphX"); object.properties.Add(p);
|
|
p = { .type = PROP_INT, .integer = y }; strcpy(p.cName, "_graphY"); object.properties.Add(p);
|
|
p = { .type = PROP_INT, .integer = w }; strcpy(p.cName, "_graphW"); object.properties.Add(p);
|
|
p = { .type = PROP_INT, .integer = h }; strcpy(p.cName, "_graphH"); object.properties.Add(p);
|
|
|
|
if (object.type == OBJ_STYLE) {
|
|
// TODO Prevent taking IDs of style objects in the clipboard?
|
|
|
|
uint8_t allocatedIDs[32768 / 8] = {};
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].type == OBJ_STYLE) {
|
|
int32_t id = PropertyReadInt32(&objects[i], "headerID");
|
|
|
|
if (id > 0 && id < 32768) {
|
|
allocatedIDs[id / 8] |= 1 << (id % 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool foundID = false;
|
|
|
|
for (int32_t i = 1; i < 32768; i++) {
|
|
if (!(allocatedIDs[i / 8] & (1 << (i % 8)))) {
|
|
p = { .type = PROP_INT, .integer = i }; strcpy(p.cName, "headerID"); object.properties.Add(p);
|
|
foundID = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundID) {
|
|
UIDialogShow(window, 0, "Error: No free header IDs.\n%f%b", "OK");
|
|
object.properties.Free();
|
|
return;
|
|
}
|
|
} else if (object.type == OBJ_PAINT_BIT_PATTERN) {
|
|
p = { .type = PROP_COLOR, .integer = (int32_t) 0xFFFFFFFF }; strcpy(p.cName, "color0"); object.properties.Add(p);
|
|
p = { .type = PROP_COLOR, .integer = (int32_t) 0xFF000000 }; strcpy(p.cName, "color1"); object.properties.Add(p);
|
|
}
|
|
|
|
ObjectAddInternal(object);
|
|
}
|
|
|
|
void ObjectAddInstanceCommandInternal(void *cp) {
|
|
Object *style = (Object *) cp;
|
|
Property *metricsProperty = PropertyFind(style, "metrics", PROP_OBJECT);
|
|
Object *metrics = ObjectFind(metricsProperty ? metricsProperty->object : 0, true);
|
|
int32_t preferredWidth = PropertyReadInt32(metrics, "preferredWidth");
|
|
int32_t preferredHeight = PropertyReadInt32(metrics, "preferredHeight");
|
|
Object object = {};
|
|
object.type = OBJ_INSTANCE;
|
|
object.id = ++objectIDAllocator;
|
|
object.flags |= OBJECT_IN_PROTOTYPE;
|
|
Property p;
|
|
int32_t x = canvas->panX + UI_RECT_WIDTH(canvas->bounds) / 2;
|
|
int32_t y = canvas->panY + UI_RECT_HEIGHT(canvas->bounds) / 2;
|
|
int32_t w = preferredWidth ?: 100;
|
|
int32_t h = preferredHeight ?: 100;
|
|
p = { .type = PROP_INT, .integer = x }; strcpy(p.cName, "_graphX"); object.properties.Add(p);
|
|
p = { .type = PROP_INT, .integer = y }; strcpy(p.cName, "_graphY"); object.properties.Add(p);
|
|
p = { .type = PROP_INT, .integer = w }; strcpy(p.cName, "_graphW"); object.properties.Add(p);
|
|
p = { .type = PROP_INT, .integer = h }; strcpy(p.cName, "_graphH"); object.properties.Add(p);
|
|
p = { .type = PROP_OBJECT, .object = style->id }; strcpy(p.cName, "style"); object.properties.Add(p);
|
|
ObjectAddInternal(object);
|
|
}
|
|
|
|
void ObjectAddCommand(void *cp) {
|
|
void (*invoke)(void *) = (void (*)(void *)) cp;
|
|
UIMenu *menu = UIMenuCreate(window->pressed, UI_MENU_NO_SCROLL | (invoke == ObjectChangeTypeInternal ? 0 : UI_MENU_PLACE_ABOVE));
|
|
UIMenuAddItem(menu, 0, "Style", -1, invoke, (void *) (uintptr_t) OBJ_STYLE);
|
|
UIMenuAddItem(menu, 0, "Comment", -1, invoke, (void *) (uintptr_t) OBJ_COMMENT);
|
|
UIMenuAddItem(menu, 0, "Selector", -1, invoke, (void *) (uintptr_t) OBJ_SELECTOR);
|
|
UIMenuAddItem(menu, 0, "Color variable", -1, invoke, (void *) (uintptr_t) OBJ_VAR_COLOR);
|
|
UIMenuAddItem(menu, 0, "Integer variable", -1, invoke, (void *) (uintptr_t) OBJ_VAR_INT);
|
|
UIMenuAddItem(menu, 0, "Text style", -1, invoke, (void *) (uintptr_t) OBJ_VAR_TEXT_STYLE);
|
|
UIMenuAddItem(menu, 0, "Contour style", -1, invoke, (void *) (uintptr_t) OBJ_VAR_CONTOUR_STYLE);
|
|
UIMenuAddItem(menu, 0, "Metrics", -1, invoke, (void *) (uintptr_t) OBJ_LAYER_METRICS);
|
|
UIMenuAddItem(menu, 0, "Overwrite paint", -1, invoke, (void *) (uintptr_t) OBJ_PAINT_OVERWRITE);
|
|
UIMenuAddItem(menu, 0, "Bit pattern paint", -1, invoke, (void *) (uintptr_t) OBJ_PAINT_BIT_PATTERN);
|
|
UIMenuAddItem(menu, 0, "Linear gradient", -1, invoke, (void *) (uintptr_t) OBJ_PAINT_LINEAR_GRADIENT);
|
|
UIMenuAddItem(menu, 0, "Radial gradient", -1, invoke, (void *) (uintptr_t) OBJ_PAINT_RADIAL_GRADIENT);
|
|
UIMenuAddItem(menu, 0, "Box layer", -1, invoke, (void *) (uintptr_t) OBJ_LAYER_BOX);
|
|
UIMenuAddItem(menu, 0, "Path layer", -1, invoke, (void *) (uintptr_t) OBJ_LAYER_PATH);
|
|
UIMenuAddItem(menu, 0, "Text layer", -1, invoke, (void *) (uintptr_t) OBJ_LAYER_TEXT);
|
|
UIMenuAddItem(menu, 0, "Layer group", -1, invoke, (void *) (uintptr_t) OBJ_LAYER_GROUP);
|
|
UIMenuAddItem(menu, 0, "Modify color", -1, invoke, (void *) (uintptr_t) OBJ_MOD_COLOR);
|
|
UIMenuAddItem(menu, 0, "Modify integer", -1, invoke, (void *) (uintptr_t) OBJ_MOD_MULTIPLY);
|
|
UIMenuShow(menu);
|
|
}
|
|
|
|
int ObjectCompareNames(const void *left, const void *right) {
|
|
return strcmp(((*(Object **) left))->cName, ((*(Object **) right))->cName);
|
|
}
|
|
|
|
void ObjectAddInstanceCommand(void *) {
|
|
Array<Object *> styles = {};
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].type == OBJ_STYLE) {
|
|
styles.Add(&objects[i]);
|
|
}
|
|
}
|
|
|
|
qsort(styles.array, styles.Length(), sizeof(Object *), ObjectCompareNames);
|
|
|
|
UIMenu *menu = UIMenuCreate(window->pressed, 0);
|
|
|
|
for (uintptr_t i = 0; i < styles.Length(); i++) {
|
|
Object *object = styles[i];
|
|
UIMenuAddItem(menu, 0, object->cName, -1, ObjectAddInstanceCommandInternal, (void *) (uintptr_t) object);
|
|
}
|
|
|
|
UIMenuShow(menu);
|
|
styles.Free();
|
|
}
|
|
|
|
void ObjectDeleteCommand(void *) {
|
|
Array<uint64_t> list = {};
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
|
|
|
|
if (object->flags & OBJECT_IS_SELECTED) {
|
|
list.Add(object->id);
|
|
}
|
|
}
|
|
|
|
Step step = {};
|
|
step.type = STEP_DELETE_OBJECT;
|
|
|
|
for (uintptr_t i = 0; i < list.Length(); i++) {
|
|
step.objectID = list[i];
|
|
DocumentApplyStep(step, i == list.Length() - 1 ? STEP_APPLY_NORMAL : STEP_APPLY_GROUPED);
|
|
}
|
|
|
|
list.Free();
|
|
}
|
|
|
|
void ObjectDuplicateCommand(void *) {
|
|
if (!selectedObjectID) return;
|
|
|
|
Object *source = ObjectFind(selectedObjectID, false);
|
|
UI_ASSERT(source);
|
|
|
|
Object object = {};
|
|
object.type = source->type;
|
|
object.id = ++objectIDAllocator;
|
|
object.properties.InsertMany(0, source->properties.Length());
|
|
memcpy(object.properties.array, source->properties.array, source->properties.Length() * sizeof(Property));
|
|
|
|
Property *graphX = PropertyFind(&object, "_graphX", PROP_INT);
|
|
if (graphX) graphX->integer += 60;
|
|
|
|
Step step = {};
|
|
step.type = STEP_ADD_OBJECT;
|
|
step.object = object;
|
|
DocumentApplyStep(step);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
#ifndef OS_ESSENCE
|
|
void Export() {
|
|
DocumentLoad();
|
|
|
|
// TODO Exporting modified integers and colors as constants.
|
|
// TODO Recursively exporting nested groups.
|
|
// TODO Handling styles that don't have metrics/textStyle.
|
|
|
|
// Create the list of styles.
|
|
|
|
FILE *output = fopen("desktop/styles.header", "wb");
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
|
|
if (object->type == OBJ_STYLE) {
|
|
bool isPublic = PropertyReadInt32(object, "isPublic");
|
|
int32_t headerID = PropertyReadInt32(object, "headerID");
|
|
|
|
if (!headerID) {
|
|
continue;
|
|
}
|
|
|
|
fprintf(output, "%sdefine ES_STYLE_", !isPublic ? "private " : "");
|
|
|
|
bool dot = false;
|
|
|
|
for (uintptr_t j = 0; object->cName[j]; j++) {
|
|
char c = object->cName[j];
|
|
|
|
if (c == '.') {
|
|
fprintf(output, "_");
|
|
dot = true;
|
|
} else if (c >= 'A' && c <= 'Z' && j && !dot) {
|
|
fprintf(output, "_%c", c);
|
|
} else if (c >= 'a' && c <= 'z') {
|
|
fprintf(output, "%c", c - 'a' + 'A');
|
|
dot = false;
|
|
} else {
|
|
fprintf(output, "%c", c);
|
|
dot = false;
|
|
}
|
|
}
|
|
|
|
fprintf(output, " (0x%.8X)\n", 0x80000000 | headerID);
|
|
}
|
|
}
|
|
|
|
fclose(output);
|
|
|
|
output = fopen("res/Theme.dat", "wb");
|
|
|
|
// Write the header.
|
|
|
|
ThemeHeader header = { 0 };
|
|
header.signature = THEME_HEADER_SIGNATURE;
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].type == OBJ_STYLE) {
|
|
header.styleCount++;
|
|
} else if ((objects[i].type == OBJ_VAR_COLOR || objects[i].type == OBJ_VAR_INT) && PropertyReadInt32(&objects[i], "isExported")) {
|
|
header.constantCount++;
|
|
}
|
|
}
|
|
|
|
fwrite(&header, 1, sizeof(header), output);
|
|
|
|
// Write the list of styles.
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
|
|
if (object->type != OBJ_STYLE) {
|
|
continue;
|
|
}
|
|
|
|
int32_t headerID = PropertyReadInt32(object, "headerID");
|
|
ThemeStyle entry = {};
|
|
entry.id = (uint32_t) headerID;
|
|
entry.layerCount = 1;
|
|
|
|
Object *appearance = PropertyFindOrInheritReadObject(object, "appearance");
|
|
|
|
if (appearance && appearance->type == OBJ_LAYER_GROUP) {
|
|
entry.layerCount += PropertyReadInt32(appearance, "layers_count");
|
|
entry.paintOutsets = ExportCalculatePaintOutsets(appearance);
|
|
entry.opaqueInsets = ExportCalculateOpaqueInsets(appearance);
|
|
entry.approximateBorders = ExportCalculateApproximateBorders(appearance);
|
|
}
|
|
|
|
fwrite(&entry, 1, sizeof(entry), output);
|
|
}
|
|
|
|
// Write the list of constants.
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
|
|
if ((object->type != OBJ_VAR_COLOR && object->type != OBJ_VAR_INT) || !PropertyReadInt32(object, "isExported")) {
|
|
continue;
|
|
}
|
|
|
|
ThemeConstant constant = {};
|
|
constant.scale = PropertyReadInt32(object, "isScaled");
|
|
constant.hash = CalculateCRC64(object->cName, strlen(object->cName), 0);
|
|
|
|
if (object->type == OBJ_VAR_COLOR) {
|
|
snprintf(constant.cValue, sizeof(constant.cValue), "0x%.8X", (uint32_t) PropertyReadInt32(object, "color"));
|
|
} else if (object->type == OBJ_VAR_INT) {
|
|
snprintf(constant.cValue, sizeof(constant.cValue), "%d", (int32_t) PropertyReadInt32(object, "value"));
|
|
}
|
|
|
|
fwrite(&constant, 1, sizeof(constant), output);
|
|
}
|
|
|
|
// Write out all layers.
|
|
|
|
Array<uint32_t> styleMetricsOffsets = {};
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
|
|
if (object->type != OBJ_STYLE) {
|
|
continue;
|
|
}
|
|
|
|
uint64_t styleObjectID = object->id;
|
|
|
|
Object *metrics = PropertyFindOrInheritReadObject(object, "metrics");
|
|
Object *textStyle = PropertyFindOrInheritReadObject(object, "textStyle");
|
|
Object *appearance = PropertyFindOrInheritReadObject(object, "appearance");
|
|
|
|
if (metrics && textStyle) {
|
|
styleMetricsOffsets.Add(ftell(output));
|
|
|
|
uint8_t overrideDataBuffer[4096];
|
|
EsBuffer overrideData = { .out = overrideDataBuffer, .bytes = sizeof(overrideDataBuffer) };
|
|
ExportContext context = { .overrideData = &overrideData, .overrideOffset = sizeof(ThemeLayer) };
|
|
|
|
ThemeMetrics _metrics = {};
|
|
|
|
_metrics.insets.l = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "insets0"));
|
|
_metrics.insets.r = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "insets1"));
|
|
_metrics.insets.t = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "insets2"));
|
|
_metrics.insets.b = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "insets3"));
|
|
_metrics.clipInsets.l = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "clipInsets0"));
|
|
_metrics.clipInsets.r = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "clipInsets1"));
|
|
_metrics.clipInsets.t = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "clipInsets2"));
|
|
_metrics.clipInsets.b = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "clipInsets3"));
|
|
_metrics.clipEnabled = PropertyFindOrInheritReadInt32(metrics, "clipEnabled");
|
|
_metrics.cursor = PropertyFindOrInheritReadInt32(metrics, "cursor");
|
|
_metrics.preferredWidth = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "preferredWidth"));
|
|
_metrics.preferredHeight = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "preferredHeight"));
|
|
_metrics.minimumWidth = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "minimumWidth"));
|
|
_metrics.minimumHeight = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "minimumHeight"));
|
|
_metrics.maximumWidth = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "maximumWidth"));
|
|
_metrics.maximumHeight = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "maximumHeight"));
|
|
_metrics.gapMajor = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "gapMajor"));
|
|
_metrics.gapMinor = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "gapMinor"));
|
|
_metrics.gapWrap = GraphGetIntegerFromProperty(PropertyFindOrInherit(metrics, "gapWrap"));
|
|
|
|
int32_t horizontalTextAlign = PropertyFindOrInheritReadInt32(metrics, "horizontalTextAlign");
|
|
int32_t verticalTextAlign = PropertyFindOrInheritReadInt32(metrics, "verticalTextAlign");
|
|
int32_t wrapText = PropertyFindOrInheritReadInt32(metrics, "wrapText");
|
|
int32_t ellipsis = PropertyFindOrInheritReadInt32(metrics, "ellipsis");
|
|
_metrics.textAlign = (wrapText ? ES_TEXT_WRAP : 0) | (ellipsis ? ES_TEXT_ELLIPSIS : 0)
|
|
| (horizontalTextAlign == 1 ? ES_TEXT_H_LEFT : horizontalTextAlign == 3 ? ES_TEXT_H_RIGHT : ES_TEXT_H_CENTER)
|
|
| (verticalTextAlign == 1 ? ES_TEXT_V_TOP : verticalTextAlign == 3 ? ES_TEXT_V_BOTTOM : ES_TEXT_V_CENTER);
|
|
|
|
_metrics.fontFamily = PropertyFindOrInheritReadInt32(textStyle, "fontFamily");
|
|
_metrics.isItalic = PropertyFindOrInheritReadInt32(textStyle, "isItalic");
|
|
ExportI8(&context, _metrics, fontWeight, textStyle, "fontWeight");
|
|
ExportI16(&context, _metrics, textSize, textStyle, "textSize");
|
|
ExportI16(&context, _metrics, iconSize, textStyle, "iconSize");
|
|
ExportColor(&context, _metrics, textColor, textStyle, "textColor");
|
|
ExportColor(&context, _metrics, selectedBackground, textStyle, "selectedBackground");
|
|
ExportColor(&context, _metrics, selectedText, textStyle, "selectedText");
|
|
ExportColor(&context, _metrics, iconColor, textStyle, "iconColor");
|
|
|
|
ThemeLayer layer = {};
|
|
layer.type = THEME_LAYER_METRICS;
|
|
layer.dataByteCount = sizeof(ThemeLayer) + sizeof(ThemeMetrics);
|
|
layer.overrideCount = overrideData.position / sizeof(ThemeOverride);
|
|
layer.overrideListOffset = layer.dataByteCount + ftell(output);
|
|
fwrite(&layer, 1, sizeof(layer), output);
|
|
|
|
#if 0
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
if (objects[i].type == OBJ_STYLE && PropertyFindOrInheritReadObject(&objects[i], "textStyle") == textStyle) {
|
|
fprintf(stderr, "%s %d %d\n", objects[i].cName, layer.overrideCount, layer.overrideListOffset);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
fwrite(&_metrics, 1, sizeof(_metrics), output);
|
|
fwrite(overrideData.out, 1, overrideData.position, output);
|
|
assert(!overrideData.error);
|
|
} else {
|
|
assert(false); // TODO.
|
|
}
|
|
|
|
if (appearance && appearance->type == OBJ_LAYER_GROUP) {
|
|
int32_t layerCount = PropertyReadInt32(appearance, "layers_count");
|
|
if (layerCount < 0) layerCount = 0;
|
|
if (layerCount > 100) layerCount = 100;
|
|
|
|
for (int32_t i = 0; i < layerCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
sprintf(cPropertyName, "layers_%d_layer", i);
|
|
Property *layerProperty = PropertyFind(appearance, cPropertyName, PROP_OBJECT);
|
|
Object *layerObject = ObjectFind(layerProperty ? layerProperty->object : 0, true);
|
|
if (!layerObject) continue;
|
|
|
|
ExportOffset exportOffset = {};
|
|
exportOffset.styleObjectID = styleObjectID;
|
|
exportOffset.objectID = layerObject->id;
|
|
exportOffset.offset = ftell(output);
|
|
exportOffsets.Add(exportOffset);
|
|
|
|
#define LAYER_READ_INT8(x) sprintf(cPropertyName, "layers_%d_" #x, i); int8_t x = PropertyReadInt32(appearance, cPropertyName)
|
|
LAYER_READ_INT8(offset0);
|
|
LAYER_READ_INT8(offset1);
|
|
LAYER_READ_INT8(offset2);
|
|
LAYER_READ_INT8(offset3);
|
|
LAYER_READ_INT8(position0);
|
|
LAYER_READ_INT8(position1);
|
|
LAYER_READ_INT8(position2);
|
|
LAYER_READ_INT8(position3);
|
|
LAYER_READ_INT8(mode);
|
|
#undef LAYER_READ_INT32
|
|
|
|
uint8_t baseDataBuffer[4096];
|
|
uint8_t overrideDataBuffer[4096];
|
|
EsBuffer baseData = { .out = baseDataBuffer, .bytes = sizeof(baseDataBuffer) };
|
|
EsBuffer overrideData = { .out = overrideDataBuffer, .bytes = sizeof(overrideDataBuffer) };
|
|
ExportContext context = { .baseData = &baseData, .overrideData = &overrideData, .overrideOffset = sizeof(ThemeLayer) };
|
|
ThemeLayer layer = {};
|
|
layer.position = { position0, position1, position2, position3 };
|
|
layer.offset = { offset0, offset1, offset2, offset3 };
|
|
|
|
if (layerObject->type == OBJ_LAYER_PATH) {
|
|
layer.type = THEME_LAYER_PATH;
|
|
ExportLayerPath(layerObject, &context);
|
|
} else if (layerObject->type == OBJ_LAYER_BOX) {
|
|
layer.type = THEME_LAYER_BOX;
|
|
ExportLayerBox(layerObject, &context);
|
|
} else if (layerObject->type == OBJ_LAYER_TEXT) {
|
|
layer.type = THEME_LAYER_TEXT;
|
|
ThemeLayerText text = {};
|
|
ExportI8(&context, text, blur, layerObject, "blur");
|
|
ExportColor(&context, text, color, layerObject, "color");
|
|
ExportWrite(&context, &text, sizeof(text));
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
layer.dataByteCount = baseData.position + sizeof(layer);
|
|
layer.overrideCount = overrideData.position / sizeof(ThemeOverride);
|
|
layer.overrideListOffset = layer.dataByteCount + ftell(output);
|
|
layer.mode = mode;
|
|
fwrite(&layer, 1, sizeof(layer), output);
|
|
fwrite(baseData.out, 1, baseData.position, output);
|
|
fwrite(overrideData.out, 1, overrideData.position, output);
|
|
assert(!baseData.error);
|
|
assert(!overrideData.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write out layer lists for styles.
|
|
|
|
uintptr_t styleIndex = 0;
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
|
|
if (object->type != OBJ_STYLE) {
|
|
continue;
|
|
}
|
|
|
|
uint64_t styleObjectID = object->id;
|
|
|
|
ExportOffset exportOffset = {};
|
|
exportOffset.objectID = object->id;
|
|
exportOffset.offset = ftell(output);
|
|
exportOffsets.Add(exportOffset);
|
|
|
|
uint32_t metricsExportOffset = styleMetricsOffsets[styleIndex++];
|
|
fwrite(&metricsExportOffset, 1, sizeof(metricsExportOffset), output);
|
|
|
|
Object *appearance = PropertyFindOrInheritReadObject(object, "appearance");
|
|
|
|
if (appearance && appearance->type == OBJ_LAYER_GROUP) {
|
|
int32_t layerCount = PropertyReadInt32(appearance, "layers_count");
|
|
if (layerCount < 0) layerCount = 0;
|
|
if (layerCount > 100) layerCount = 100;
|
|
|
|
for (int32_t i = 0; i < layerCount; i++) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
sprintf(cPropertyName, "layers_%d_layer", i);
|
|
Property *layerProperty = PropertyFind(appearance, cPropertyName, PROP_OBJECT);
|
|
Object *layerObject = ObjectFind(layerProperty ? layerProperty->object : 0, true);
|
|
if (!layerObject) continue;
|
|
uint32_t exportOffset = ExportOffsetFindObjectForStyle(layerObject->id, styleObjectID)->offset;
|
|
fwrite(&exportOffset, 1, sizeof(exportOffset), output);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the style list to point to the layer lists.
|
|
|
|
uintptr_t writeOffset = sizeof(ThemeHeader);
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
if (object->type != OBJ_STYLE) continue;
|
|
uint32_t exportOffset = ExportOffsetFindObject(object->id)->offset;
|
|
fseek(output, writeOffset, SEEK_SET);
|
|
fwrite(&exportOffset, 1, sizeof(exportOffset), output);
|
|
writeOffset += sizeof(ThemeStyle);
|
|
}
|
|
|
|
DocumentFree();
|
|
fclose(output);
|
|
exportOffsets.Free();
|
|
styleMetricsOffsets.Free();
|
|
}
|
|
|
|
void ExportJSON() {
|
|
DocumentLoad();
|
|
FILE *f = fopen("bin/designer2.json", "wb");
|
|
fprintf(f, "{\n\t\"version\": 1,\n\t\"objectIDAllocator\": %ld,\n\t\"objects\": [\n", objectIDAllocator);
|
|
|
|
for (uintptr_t i = 0; i < objects.Length(); i++) {
|
|
Object *object = &objects[i];
|
|
const char *type = "??";
|
|
|
|
for (uintptr_t i = 0; i < sizeof(cObjectTypeStrings) / sizeof(cObjectTypeStrings[0]); i++) {
|
|
if (cObjectTypeStrings[i].type == object->type) {
|
|
type = cObjectTypeStrings[i].string;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fprintf(f, "\t\t{\n\t\t\t\"ID\": %ld,\n\t\t\t\"Flags\": %d,\n\t\t\t\"Type\": \"%s\",\n\t\t\t\"Name\": \"%s\",\n",
|
|
object->id, object->flags, type, object->cName);
|
|
|
|
for (uintptr_t i = 0; i < object->properties.Length(); i++) {
|
|
Property *property = &object->properties[i];
|
|
fprintf(f, "\t\t\t\"%s\": ", property->cName);
|
|
if (property->type == PROP_COLOR) fprintf(f, "\"#%.8X\"", (uint32_t) property->integer);
|
|
if (property->type == PROP_INT) fprintf(f, "%d", property->integer);
|
|
if (property->type == PROP_OBJECT) fprintf(f, "\"@%ld\"", property->object);
|
|
if (property->type == PROP_U64) fprintf(f, "\"%ld\"", property->object);
|
|
if (property->type == PROP_FLOAT) fprintf(f, "%f", property->floating);
|
|
fprintf(f, "%s\n", i < object->properties.Length() - 1 ? "," : "");
|
|
}
|
|
|
|
fprintf(f, "\t\t}%s\n", i < objects.Length() - 1 ? "," : "");
|
|
}
|
|
|
|
fprintf(f, "\t]\n}\n");
|
|
fclose(f);
|
|
DocumentFree();
|
|
}
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
int WindowMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_WINDOW_CLOSE) {
|
|
#ifndef OS_ESSENCE
|
|
if (documentModified && !window->dialog) {
|
|
const char *dialog = "Document modified. Save changes?\n%f%b%b";
|
|
const char *result = UIDialogShow(window, 0, dialog, "Save", "Discard");
|
|
|
|
if (0 == strcmp(result, "Save")) {
|
|
DocumentSave(nullptr);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef OS_ESSENCE
|
|
void DocumentFileMenu(void *) {
|
|
EsFileMenuCreate(ui.instance, ui.instance->window, ES_MENU_AT_CURSOR);
|
|
}
|
|
|
|
int _UIInstanceCallback(EsInstance *instance, EsMessage *message) {
|
|
if (message->type == ES_MSG_INSTANCE_SAVE) {
|
|
fileStore = message->instanceSave.file;
|
|
DocumentSave(nullptr);
|
|
EsUniqueIdentifier type = (EsUniqueIdentifier)
|
|
{{ 0x26, 0x4A, 0xE0, 0x6C, 0x0F, 0xE8, 0x2C, 0xE7, 0xF4, 0x6E, 0x4E, 0x76, 0x5B, 0x4A, 0xCF, 0x4F }};
|
|
EsFileStoreSetContentType(fileStore, type);
|
|
EsInstanceSaveComplete(instance, fileStore, true);
|
|
fileStore = nullptr;
|
|
} else if (message->type == ES_MSG_INSTANCE_OPEN) {
|
|
DocumentFree();
|
|
fileStore = message->instanceOpen.file;
|
|
DocumentLoad();
|
|
EsInstanceOpenComplete(instance, fileStore, true);
|
|
fileStore = nullptr;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
#endif
|
|
|
|
void DocumentMenu(void *) {
|
|
UIMenu *menu = UIMenuCreate(window->pressed, UI_MENU_NO_SCROLL);
|
|
UIMenuAddItem(menu, 0, "Switch view", -1, CanvasSwitchView, 0);
|
|
UIMenuAddItem(menu, 0, "Switch selector index", -1, CanvasSwitchSelectorIndex, 0);
|
|
UIMenuAddItem(menu, 0, "Find by ID", -1, CanvasFindByID, 0);
|
|
UIMenuAddItem(menu, 0, "Zoom to 100%", -1, CanvasZoom100, 0);
|
|
UIMenuShow(menu);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
#ifndef OS_ESSENCE
|
|
if (argc == 2) {
|
|
if (0 == strcmp(argv[1], "export")) {
|
|
Export();
|
|
} else if (0 == strcmp(argv[1], "json")) {
|
|
ExportJSON();
|
|
} else {
|
|
fprintf(stderr, "Error: Unknown action '%s'.\n", argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
UIInitialise();
|
|
|
|
#ifdef OS_ESSENCE
|
|
ui.instance->callback = _UIInstanceCallback;
|
|
#endif
|
|
|
|
ui.theme = _uiThemeClassic;
|
|
window = UIWindowCreate(0, UI_ELEMENT_PARENT_PUSH | UI_WINDOW_MAXIMIZE, "Designer", 0, 0);
|
|
window->e.messageUser = WindowMessage;
|
|
|
|
UISplitPaneCreate(0, UI_ELEMENT_PARENT_PUSH, 0.75f);
|
|
canvas = (Canvas *) UIElementCreate(sizeof(Canvas), 0, 0, CanvasMessage, "Canvas");
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL | UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING);
|
|
#ifdef OS_ESSENCE
|
|
UIButtonCreate(0, UI_BUTTON_DROP_DOWN, "File", -1)->invoke = DocumentFileMenu;
|
|
#else
|
|
UIButtonCreate(0, 0, "Save", -1)->invoke = DocumentSave;
|
|
#endif
|
|
UIButtonCreate(0, 0, "Menu", -1)->invoke = DocumentMenu;
|
|
UIParentPop();
|
|
UISpacerCreate(0, UI_SPACER_LINE, 0, 1);
|
|
inspector = &UIPanelCreate(0, UI_ELEMENT_V_FILL | UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING | UI_PANEL_SCROLL | UI_PANEL_EXPAND)->e;
|
|
|
|
InspectorPopulate();
|
|
|
|
graphControls = UIPanelCreate(canvas, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
graphControls->gap = -1;
|
|
UIButton *button = UIButtonCreate(0, UI_BUTTON_SMALL, "Add object \x18", -1);
|
|
button->invoke = ObjectAddCommand;
|
|
button->e.cp = (void *) ObjectAddCommandInternal;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Arrow mode \x18", -1)->invoke = CanvasArrowMode;
|
|
UIParentPop();
|
|
|
|
prototypeControls = UIPanelCreate(canvas, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
prototypeControls->gap = -1;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Add instance \x18", -1)->invoke = ObjectAddInstanceCommand;
|
|
UIParentPop();
|
|
|
|
for (uintptr_t i = 0; i < 4; i++) {
|
|
canvas->resizeHandles[i] = UIElementCreate(sizeof(UIElement), canvas, 0, ResizeHandleMessage, "Resize handle");
|
|
canvas->resizeHandles[i]->cp = (void *) (uintptr_t) i;
|
|
}
|
|
|
|
canvas->zoom = canvas->swapZoom = 1.0f;
|
|
canvas->previewPrimaryState = THEME_PRIMARY_STATE_IDLE;
|
|
canvas->arrowMode = ARROW_MODE_FROM_OR_TO;
|
|
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('Z'), 1 /* ctrl */, 0, 0, DocumentUndoStep, 0));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('Y'), 1 /* ctrl */, 0, 0, DocumentRedoStep, 0));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('D'), 1 /* ctrl */, 0, 0, ObjectDuplicateCommand, 0));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_FKEY(2), 0, 0, 0, CanvasSwitchView, 0));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_FKEY(3), 0, 0, 0, CanvasSwitchSelectorIndex, 0));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_DIGIT('1'), 1 /* ctrl */, 0, 0, CanvasZoom100, 0));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_DELETE, 0, 0, 0, ObjectDeleteCommand, 0));
|
|
|
|
#ifdef OS_ESSENCE
|
|
EsWindowSetIcon(ui.instance->window, ES_ICON_APPLICATIONS_INTERFACEDESIGN);
|
|
EsInstanceSetClassEditor(ui.instance, &instanceClassEditorSettings);
|
|
#else
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('S'), 1 /* ctrl */, 0, 0, DocumentSave, 0));
|
|
DocumentLoad();
|
|
#endif
|
|
|
|
int result = UIMessageLoop();
|
|
DocumentFree();
|
|
UIElementDestroy(&window->e);
|
|
_UIUpdate();
|
|
inspectorBoundElements.Free();
|
|
return result;
|
|
}
|
|
|
|
#ifdef OS_ESSENCE
|
|
void _UIMessageProcess(EsMessage *message) {}
|
|
|
|
void _start() {
|
|
_init();
|
|
main(0, nullptr);
|
|
}
|
|
#endif
|