mirror of https://gitlab.com/nakst/essence
6334 lines
220 KiB
C
6334 lines
220 KiB
C
// TODO I think this needs to be rewritten...
|
|
// TODO Converting linear gradients where all stops are the same color to a solid paint.
|
|
// TODO Why does cap mode on dashes not show selected?
|
|
// TODO Undoing creating layer, styles, constants.
|
|
// TODO Prevent deleting metrics layer.
|
|
// TODO Overrides on reused layers (additionally on non-interpolables; but not structural values).
|
|
// TODO Global layer list.
|
|
// TODO Animations with multiple keyframes.
|
|
// TODO More paint types.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
|
|
#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 EsCRTsqrtf sqrtf
|
|
#define EsAssert assert
|
|
#define EsHeapAllocate(x, y) ((y) ? calloc(1, (x)) : malloc((x)))
|
|
#define EsHeapFree free
|
|
#define EsCRTfabsf fabsf
|
|
#define EsCRTfmodf fmodf
|
|
#define EsCRTisnanf isnan
|
|
#define EsCRTfloorf floorf
|
|
#define AbsoluteFloat fabsf
|
|
#define EsCRTatan2f atan2f
|
|
#define EsCRTsinf sinf
|
|
#define EsCRTcosf cosf
|
|
#define EsCRTacosf acosf
|
|
#define EsCRTceilf ceilf
|
|
#define EsMemoryCopy memcpy
|
|
#define EsMemoryZero(a, b) memset((a), 0, (b))
|
|
#define ES_FUNCTION_OPTIMISE_O2 __attribute__((optimize("-O2")))
|
|
#define ES_INFINITY INFINITY
|
|
#define IN_DESIGNER
|
|
|
|
typedef struct EsBuffer {
|
|
union { const uint8_t *in; uint8_t *out; };
|
|
size_t position, bytes;
|
|
bool error;
|
|
void *context;
|
|
} EsBuffer;
|
|
|
|
#include "../luigi.h"
|
|
#define NANOSVG_IMPLEMENTATION
|
|
#include "../nanosvg.h"
|
|
#define STB_TRUETYPE_IMPLEMENTATION
|
|
#include "../stb_truetype.h"
|
|
#define STB_DS_IMPLEMENTATION
|
|
#include "../stb_ds.h"
|
|
#include "../../shared/hash.cpp"
|
|
#define SHARED_COMMON_WANT_BUFFERS
|
|
#include "../../shared/common.cpp"
|
|
|
|
#define OP_DO_MOD (1)
|
|
#define OP_MAKE_UI (2)
|
|
#define OP_EXPORT (3)
|
|
#define OP_GET_PALETTE (4)
|
|
#define OP_REPLACE_COLOR (5)
|
|
#define OP_FIND_COLOR_USERS (6)
|
|
|
|
#define PATH_IN_KEYFRAME (0xFFFFFFFE)
|
|
#define PATH_ANY (0xFFFFFFFD)
|
|
|
|
typedef struct StringOption {
|
|
const char *string;
|
|
} StringOption;
|
|
|
|
typedef struct ModContext {
|
|
struct Style *style;
|
|
struct Layer *layer;
|
|
struct Sequence *sequence;
|
|
struct Keyframe *keyframe;
|
|
} ModContext;
|
|
|
|
const uint32_t saveFormatVersion = 22;
|
|
|
|
void SetSelectedItems(ModContext context);
|
|
void ColorListRefresh();
|
|
|
|
#define MOD_CONTEXT(style, layer, sequence, keyframe) ((ModContext) { (style), (layer), (sequence), (keyframe) })
|
|
|
|
#define REFLECT_IMPLEMENTATION
|
|
#include "reflect.h"
|
|
#include "../../bin/designer.h"
|
|
|
|
typedef struct PaletteItem {
|
|
uint32_t key;
|
|
int value;
|
|
} PaletteItem;
|
|
|
|
typedef struct ColorUsersItem {
|
|
uint32_t key;
|
|
Style **value;
|
|
} ColorUsersItem;
|
|
|
|
PaletteItem *palette;
|
|
ColorUsersItem *colorUsers;
|
|
uint32_t replaceColorFrom, replaceColorTo;
|
|
void *currentPaletteOpLayer;
|
|
|
|
void PropertyOp(RfState *state, RfItem *item, void *pointer) {
|
|
Property *property = (Property *) pointer;
|
|
|
|
if (state->op == RF_OP_FREE) {
|
|
free(property->path);
|
|
RfStructOp(state, item, pointer);
|
|
} else if (state->op == RF_OP_SAVE) {
|
|
uint8_t count = 0;
|
|
|
|
if (property->path) {
|
|
for (; property->path[count] != RF_PATH_TERMINATOR; count++);
|
|
state->access(state, &count, sizeof(count));
|
|
|
|
for (uintptr_t i = 0; property->path[i] != RF_PATH_TERMINATOR; i++) {
|
|
RfIntegerSave(state, property->path + i, sizeof(uint32_t));
|
|
}
|
|
} else {
|
|
count = 0xFF;
|
|
state->access(state, &count, sizeof(count));
|
|
}
|
|
|
|
RfStructOp(state, item, pointer);
|
|
} else if (state->op == RF_OP_LOAD) {
|
|
uint8_t count = 0;
|
|
state->access(state, &count, sizeof(count));
|
|
|
|
if (count != 0xFF) {
|
|
property->path = malloc(sizeof(uint32_t) * (count + 1));
|
|
property->path[count] = RF_PATH_TERMINATOR;
|
|
|
|
for (uintptr_t i = 0; i < count; i++) {
|
|
RfIntegerLoad(state, property->path + i, sizeof(uint32_t));
|
|
}
|
|
} else {
|
|
property->path = NULL;
|
|
}
|
|
|
|
RfStructOp(state, item, pointer);
|
|
} else if (state->op == OP_GET_PALETTE || state->op == OP_REPLACE_COLOR || state->op == OP_FIND_COLOR_USERS) {
|
|
void *pointer = currentPaletteOpLayer;
|
|
RfItem item = {};
|
|
item.type = &Layer_Type;
|
|
item.byteCount = sizeof(Layer);
|
|
bool success = RfPathResolve((RfPath *) (property->path + 1), &item, &pointer);
|
|
assert(success);
|
|
|
|
if (item.type->op == StyleColorOp) {
|
|
item.type->op(state, &item, property->data.buffer);
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
#define MSG_PROPERTY_CHANGED ((UIMessage) (UI_MSG_USER + 1))
|
|
|
|
typedef struct MakeUIState {
|
|
RfState s;
|
|
uint32_t index;
|
|
struct MakeUIState *parent;
|
|
uint32_t *basePath;
|
|
bool recurse, inKeyframe;
|
|
} MakeUIState;
|
|
|
|
UIWindow *window;
|
|
UITable *tableLayers, *tableSequences, *tableKeyframes;
|
|
UIButton *buttonAddLayer, *buttonAddExistingLayer, *buttonPublicStyle;
|
|
|
|
typedef struct AnimatingValue {
|
|
uint16_t offset;
|
|
#define ANIMATING_VALUE_TYPE_I8 (1)
|
|
#define ANIMATING_VALUE_TYPE_I16 (2)
|
|
#define ANIMATING_VALUE_TYPE_COLOR (3)
|
|
#define ANIMATING_VALUE_TYPE_FLOAT (4)
|
|
#define ANIMATING_VALUE_TYPE_UNUSED (1 << 7)
|
|
uint8_t type;
|
|
uint8_t layer;
|
|
uint16_t duration, elapsed; // Milliseconds.
|
|
union { int8_t i8; int16_t i16; uint32_t u32; float f32; } from, to;
|
|
} AnimatingValue;
|
|
|
|
typedef struct SequenceStateSelector {
|
|
uint32_t primary;
|
|
bool focused, checked, indeterminate, _default, itemFocus, listFocus, selected, enter, exit;
|
|
} SequenceStateSelector;
|
|
|
|
UIElement *elementCanvas;
|
|
UISlider *previewWidth, *previewHeight, *previewScale;
|
|
UIButton *previewTransition, *previewShowGuides, *previewShowComputed, *previewFixAspectRatio;
|
|
uint64_t previewTransitionLastTime;
|
|
AnimatingValue *animatingValues;
|
|
UIPanel *previewPrimaryStatePanel;
|
|
UIButton *previewPrimaryStateIdle;
|
|
UIButton *previewPrimaryStateHovered;
|
|
UIButton *previewPrimaryStatePressed;
|
|
UIButton *previewPrimaryStateDisabled;
|
|
UIButton *previewPrimaryStateInactive;
|
|
UIButton *previewStateFocused;
|
|
UIButton *previewStateChecked;
|
|
UIButton *previewStateIndeterminate;
|
|
UIButton *previewStateDefault;
|
|
UIButton *previewStateItemFocus;
|
|
UIButton *previewStateListFocus;
|
|
UIButton *previewStateSelected;
|
|
UIButton *previewStateBeforeEnter;
|
|
UIButton *previewStateAfterExit;
|
|
UIButton *editPoints;
|
|
UIColorPicker *previewBackgroundColor;
|
|
uint32_t previewPrimaryState;
|
|
SequenceStateSelector currentStateSelector;
|
|
|
|
UIPanel *panelInspector;
|
|
UIElement **inspectorSubscriptions;
|
|
|
|
UIWindow *importDialog;
|
|
UITextbox *importPathTextbox;
|
|
UILabel *importPathMessage;
|
|
|
|
Mod *undoStack, *redoStack;
|
|
bool modApplyUndo;
|
|
|
|
uint32_t *menuPath;
|
|
|
|
StyleSet styleSet;
|
|
ModContext selected = MOD_CONTEXT(NULL, NULL, NULL, NULL);
|
|
|
|
char temporaryOverride[4096];
|
|
|
|
char *filePath, *exportPath, *stylesPath;
|
|
|
|
void ModApply(ModData *mod);
|
|
|
|
char *LoadFile(const char *inputFileName, size_t *byteCount);
|
|
|
|
void MakeUI(MakeUIState *state, RfItem *item, void *pointer);
|
|
void MakeHeaderAndIndentUI(const char *format, RfState *state, RfItem *item, void *pointer);
|
|
|
|
#define MAKE_UI(c, p, k) \
|
|
do { \
|
|
for (uintptr_t i = 0; i < (c ## _Type).fieldCount; i++) { \
|
|
MakeUIState state = { 0 }; \
|
|
state.s.op = OP_MAKE_UI; \
|
|
state.index = i; \
|
|
state.recurse = true; \
|
|
state.inKeyframe = k; \
|
|
RfItem item = (c ## _Type).fields[i].item; \
|
|
void *pointer = (uint8_t *) p + (c ## _Type).fields[i].offset; \
|
|
item.type->op(&state.s, &item, pointer); \
|
|
if (state.recurse) MakeUI(&state, &item, pointer); \
|
|
} \
|
|
} while (0)
|
|
|
|
// ------------------- Renderer -------------------
|
|
|
|
int ClampInteger(int from, int to, int value) {
|
|
if (value < from) return from;
|
|
if (value > to) return to;
|
|
return value;
|
|
}
|
|
|
|
float LinearInterpolate(float from, float to, float progress) {
|
|
return from + progress * (to - from);
|
|
}
|
|
|
|
float GammaInterpolate(float from, float to, float progress) {
|
|
from = from * from;
|
|
to = to * to;
|
|
return sqrtf(from + progress * (to - from));
|
|
}
|
|
|
|
int MaximumInteger3(int a, int b, int c) {
|
|
if (a >= b && a >= c) {
|
|
return a;
|
|
} else if (b >= c) {
|
|
return b;
|
|
} else {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
int MinimumInteger3(int a, int b, int c) {
|
|
if (a <= b && a <= c) {
|
|
return a;
|
|
} else if (b <= c) {
|
|
return b;
|
|
} else {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
int MaximumInteger(int a, int b) {
|
|
if (a >= b) {
|
|
return a;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
|
|
int MinimumInteger(int a, int b) {
|
|
if (a <= b) {
|
|
return a;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
|
|
typedef struct Corners32 { int32_t tl, tr, bl, br; } Corners32;
|
|
typedef struct Rectangle32 { int32_t l, r, t, b; } Rectangle32;
|
|
typedef struct EsRectangle { int32_t l, r, t, b; } EsRectangle;
|
|
|
|
bool EsRectangleClip(EsRectangle parent, EsRectangle rectangle, EsRectangle *output) {
|
|
EsRectangle current = parent;
|
|
EsRectangle intersection = { 0 };
|
|
|
|
if (!((current.l > rectangle.r && current.r > rectangle.l)
|
|
|| (current.t > rectangle.b && current.b > rectangle.t))) {
|
|
intersection.l = current.l > rectangle.l ? current.l : rectangle.l;
|
|
intersection.t = current.t > rectangle.t ? current.t : rectangle.t;
|
|
intersection.r = current.r < rectangle.r ? current.r : rectangle.r;
|
|
intersection.b = current.b < rectangle.b ? current.b : rectangle.b;
|
|
}
|
|
|
|
if (output) {
|
|
*output = intersection;
|
|
}
|
|
|
|
return intersection.l < intersection.r && intersection.t < intersection.b;
|
|
}
|
|
|
|
void BlendPixel(uint32_t *destinationPixel, uint32_t modified, bool fullAlpha) {
|
|
if ((modified & 0xFF000000) == 0xFF000000) {
|
|
*destinationPixel = modified;
|
|
} else if ((modified & 0xFF000000) == 0x00000000) {
|
|
} else if ((*destinationPixel & 0xFF000000) != 0xFF000000 && fullAlpha) {
|
|
uint32_t original = *destinationPixel;
|
|
uint32_t alpha1 = (modified & 0xFF000000) >> 24;
|
|
uint32_t alpha2 = 255 - alpha1;
|
|
uint32_t alphaD = (original & 0xFF000000) >> 24;
|
|
uint32_t alphaD2 = alphaD * alpha2;
|
|
uint32_t alphaOut = alpha1 + (alphaD2 >> 8);
|
|
|
|
if (alphaOut) {
|
|
uint32_t m2 = alphaD2 / alphaOut;
|
|
uint32_t m1 = (alpha1 << 8) / alphaOut;
|
|
if (m2 == 0x100) m2--;
|
|
if (m1 == 0x100) m1--;
|
|
uint32_t r2 = m2 * ((original & 0x000000FF) >> 0);
|
|
uint32_t g2 = m2 * ((original & 0x0000FF00) >> 8);
|
|
uint32_t b2 = m2 * ((original & 0x00FF0000) >> 16);
|
|
uint32_t r1 = m1 * ((modified & 0x000000FF) >> 0);
|
|
uint32_t g1 = m1 * ((modified & 0x0000FF00) >> 8);
|
|
uint32_t b1 = m1 * ((modified & 0x00FF0000) >> 16);
|
|
uint32_t result = (alphaOut << 24)
|
|
| (0x00FF0000 & ((b1 + b2) << 8))
|
|
| (0x0000FF00 & ((g1 + g2) << 0))
|
|
| (0x000000FF & ((r1 + r2) >> 8));
|
|
*destinationPixel = result;
|
|
}
|
|
} else {
|
|
uint32_t original = *destinationPixel;
|
|
uint32_t alpha1 = (modified & 0xFF000000) >> 24;
|
|
uint32_t alpha2 = 255 - alpha1;
|
|
uint32_t r2 = alpha2 * ((original & 0x000000FF) >> 0);
|
|
uint32_t g2 = alpha2 * ((original & 0x0000FF00) >> 8);
|
|
uint32_t b2 = alpha2 * ((original & 0x00FF0000) >> 16);
|
|
uint32_t r1 = alpha1 * ((modified & 0x000000FF) >> 0);
|
|
uint32_t g1 = alpha1 * ((modified & 0x0000FF00) >> 8);
|
|
uint32_t b1 = alpha1 * ((modified & 0x00FF0000) >> 16);
|
|
uint32_t result = 0xFF000000 | (0x00FF0000 & ((b1 + b2) << 8))
|
|
| (0x0000FF00 & ((g1 + g2) << 0))
|
|
| (0x000000FF & ((r1 + r2) >> 8));
|
|
*destinationPixel = result;
|
|
}
|
|
}
|
|
|
|
typedef struct EsPaintTarget {
|
|
void *bits;
|
|
uint32_t width, height, stride;
|
|
bool fullAlpha, readOnly;
|
|
} EsPaintTarget;
|
|
|
|
typedef struct EsPainter {
|
|
EsRectangle clip;
|
|
EsPaintTarget *target;
|
|
} EsPainter;
|
|
|
|
#include "../../desktop/renderer.cpp"
|
|
#include "../../desktop/theme.cpp"
|
|
|
|
// ------------------- Reflect utilities -------------------
|
|
|
|
int StringCompareRaw(const char *s1, ptrdiff_t length1, const char *s2, ptrdiff_t length2) {
|
|
while (length1 || length2) {
|
|
if (!length1) return -1;
|
|
if (!length2) return 1;
|
|
|
|
char c1 = *s1;
|
|
char c2 = *s2;
|
|
|
|
if (c1 != c2) {
|
|
return c1 - c2;
|
|
}
|
|
|
|
length1--;
|
|
length2--;
|
|
s1++;
|
|
s2++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
RfData SaveToGrowableBuffer(RfType *type, size_t byteCount, void *options, void *pointer) {
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.op = RF_OP_SAVE;
|
|
state.s.allocate = RfRealloc;
|
|
state.s.access = RfWriteGrowableBuffer;
|
|
RfItem item = { 0 };
|
|
item.type = type;
|
|
item.byteCount = byteCount;
|
|
item.options = options;
|
|
type->op(&state.s, &item, pointer);
|
|
state.data.buffer = realloc(state.data.buffer, state.data.byteCount);
|
|
return state.data;
|
|
}
|
|
|
|
bool ArePathsEqual(uint32_t *path1, uint32_t *path2) {
|
|
if (!path1 && !path2) return true;
|
|
if (!path1 || !path2) return false;
|
|
|
|
while (true) {
|
|
if (*path1 != *path2) {
|
|
return false;
|
|
}
|
|
|
|
if (*path1 == RF_PATH_TERMINATOR) {
|
|
return true;
|
|
}
|
|
|
|
path1++;
|
|
path2++;
|
|
}
|
|
}
|
|
|
|
uint32_t *DuplicatePath(uint32_t *indices) {
|
|
uintptr_t count = 0;
|
|
for (; indices[count] != RF_PATH_TERMINATOR; count++);
|
|
uint32_t *path = (uint32_t *) malloc((count + 1) * sizeof(uint32_t));
|
|
memcpy(path, indices, (count + 1) * sizeof(uint32_t));
|
|
return path;
|
|
}
|
|
|
|
void PrintPath(uint32_t *indices) {
|
|
printf("{ ");
|
|
|
|
for (uintptr_t i = 0; indices[i] != RF_PATH_TERMINATOR; i++) {
|
|
printf("%d, ", (int32_t) indices[i]);
|
|
}
|
|
|
|
printf(" }\n");
|
|
}
|
|
|
|
void *ResolveDataObject(RfPath *path, RfItem *item) {
|
|
RfItem _item;
|
|
if (!item) item = &_item;
|
|
|
|
void *pointer;
|
|
item->options = NULL;
|
|
|
|
bool inKeyframe = false;
|
|
|
|
if (path->indices[0] == PATH_IN_KEYFRAME) {
|
|
inKeyframe = true;
|
|
|
|
// PrintPath(path->indices);
|
|
|
|
// Has the property been overridden in the keyframe?
|
|
for (uintptr_t i = 0; i < arrlenu(selected.keyframe->properties); i++) {
|
|
// PrintPath(selected.keyframe->properties[i].path);
|
|
|
|
if (ArePathsEqual(path->indices, selected.keyframe->properties[i].path)) {
|
|
// Get the RfItem.
|
|
pointer = selected.layer;
|
|
item->type = &Layer_Type;
|
|
item->byteCount = sizeof(Layer);
|
|
bool success = RfPathResolve(path + 1, item, &pointer);
|
|
assert(success);
|
|
assert(sizeof(temporaryOverride) >= sizeof(item->byteCount));
|
|
|
|
// Load the override.
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.allocate = RfRealloc;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.s.op = RF_OP_LOAD;
|
|
state.data = selected.keyframe->properties[i].data;
|
|
item->type->op(&state.s, item, temporaryOverride);
|
|
|
|
// Return the temporary buffer.
|
|
return temporaryOverride;
|
|
}
|
|
}
|
|
|
|
path++;
|
|
}
|
|
|
|
if (selected.layer && selected.sequence && selected.keyframe && !inKeyframe) {
|
|
pointer = selected.keyframe;
|
|
item->type = &Keyframe_Type;
|
|
item->byteCount = sizeof(Keyframe);
|
|
} else if (selected.layer && selected.sequence && !inKeyframe) {
|
|
pointer = selected.sequence;
|
|
item->type = &Sequence_Type;
|
|
item->byteCount = sizeof(Sequence);
|
|
} else if (selected.layer) {
|
|
pointer = selected.layer;
|
|
item->type = &Layer_Type;
|
|
item->byteCount = sizeof(Layer);
|
|
} else {
|
|
pointer = selected.style;
|
|
item->type = &Style_Type;
|
|
item->byteCount = sizeof(Style);
|
|
}
|
|
|
|
bool success = RfPathResolve(path, item, &pointer);
|
|
assert(success);
|
|
return pointer;
|
|
}
|
|
|
|
// ------------------- Exporting -------------------
|
|
|
|
typedef struct PathToOffset {
|
|
uint32_t *path;
|
|
uintptr_t offset;
|
|
} PathToOffset;
|
|
|
|
typedef struct ExportState {
|
|
RfGrowableBuffer buffer;
|
|
uint32_t *pathStack;
|
|
PathToOffset *pathToOffsetList;
|
|
} ExportState;
|
|
|
|
void ExportAddPathToOffset(ExportState *export, uint32_t last, ptrdiff_t offset) {
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
arrput(export->pathStack, last);
|
|
arrput(export->pathStack, RF_PATH_TERMINATOR);
|
|
PathToOffset pathToOffset = { 0 };
|
|
pathToOffset.path = DuplicatePath(export->pathStack);
|
|
pathToOffset.offset = export->buffer.data.byteCount + offset;
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
arrput(export->pathToOffsetList, pathToOffset);
|
|
}
|
|
|
|
void ExportAddPathToOffset2(ExportState *export, uint32_t last1, uint32_t last2, ptrdiff_t offset) {
|
|
arrput(export->pathStack, last1);
|
|
ExportAddPathToOffset(export, last2, offset);
|
|
(void) arrpop(export->pathStack);
|
|
}
|
|
|
|
void ExportAddPathToOffsetForRectangle(ExportState *export, uint32_t last, ptrdiff_t offset) {
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
arrput(export->pathStack, last);
|
|
arrput(export->pathStack, RF_PATH_TERMINATOR);
|
|
PathToOffset pathToOffset = { 0 };
|
|
pathToOffset.path = DuplicatePath(export->pathStack);
|
|
pathToOffset.offset = export->buffer.data.byteCount + offset;
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
arrput(export->pathToOffsetList, pathToOffset);
|
|
}
|
|
|
|
void ExportFreePathToOffsetList(PathToOffset *pathToOffsetList) {
|
|
for (uintptr_t i = 0; i < arrlenu(pathToOffsetList); i++) {
|
|
free(pathToOffsetList[i].path);
|
|
}
|
|
|
|
arrfree(pathToOffsetList);
|
|
}
|
|
|
|
RfData ExportToGrowableBuffer(RfType *type, size_t byteCount, void *options, void *pointer, PathToOffset **pathToOffsetList) {
|
|
ExportState state = { 0 };
|
|
state.buffer.s.op = OP_EXPORT;
|
|
state.buffer.s.allocate = RfRealloc;
|
|
state.buffer.s.access = RfWriteGrowableBuffer;
|
|
|
|
RfItem item = { 0 };
|
|
item.type = type;
|
|
item.byteCount = byteCount;
|
|
item.options = options;
|
|
|
|
type->op(&state.buffer.s, &item, pointer);
|
|
state.buffer.data.buffer = realloc(state.buffer.data.buffer, state.buffer.data.byteCount);
|
|
|
|
arrfree(state.pathStack);
|
|
|
|
*pathToOffsetList = state.pathToOffsetList;
|
|
return state.buffer.data;
|
|
}
|
|
|
|
uint32_t ColorLookup(uint32_t id) {
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
if (styleSet.colors[i]->id == id) {
|
|
return styleSet.colors[i]->value;
|
|
}
|
|
}
|
|
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
Color *ColorLookupPointer(uint32_t id) {
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
if (styleSet.colors[i]->id == id) {
|
|
return styleSet.colors[i];
|
|
}
|
|
}
|
|
|
|
assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
#define EXPORT_FIELD(fromType, fromVariable, toType, toVariable, fromField, toField) \
|
|
ExportAddPathToOffset(export, fromType ## _ ## fromField, offsetof(toType, toField)); \
|
|
toVariable.toField = fromVariable->fromField;
|
|
#define EXPORT_FIELD_COLOR(fromType, fromVariable, toType, toVariable, fromField, toField) \
|
|
ExportAddPathToOffset(export, fromType ## _ ## fromField, offsetof(toType, toField)); \
|
|
toVariable.toField = ColorLookup(fromVariable->fromField);
|
|
#define EXPORT_FIELD_ALIGN(fromType, fromVariable, toType, toVariable, fromHField, fromVField, toField) \
|
|
toVariable.toField = ((fromVariable->fromHField == ALIGN_START) ? ES_TEXT_H_LEFT : 0) \
|
|
| ((fromVariable->fromHField == ALIGN_CENTER) ? ES_TEXT_H_CENTER : 0) \
|
|
| ((fromVariable->fromHField == ALIGN_END) ? ES_TEXT_H_RIGHT : 0) \
|
|
| ((fromVariable->fromVField == ALIGN_START) ? ES_TEXT_V_TOP : 0) \
|
|
| ((fromVariable->fromVField == ALIGN_CENTER) ? ES_TEXT_V_CENTER : 0) \
|
|
| ((fromVariable->fromVField == ALIGN_END) ? ES_TEXT_V_BOTTOM : 0);
|
|
#define EXPORT_RECTANGLE8_FIELD(fromType, fromVariable, toType, toVariable, fromField, toField) \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle8_l, offsetof(toType, toField.l)); \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle8_r, offsetof(toType, toField.r)); \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle8_t, offsetof(toType, toField.t)); \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle8_b, offsetof(toType, toField.b)); \
|
|
toVariable.toField = fromVariable->fromField;
|
|
#define EXPORT_RECTANGLE16_FIELD(fromType, fromVariable, toType, toVariable, fromField, toField) \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle16_l, offsetof(toType, toField.l)); \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle16_r, offsetof(toType, toField.r)); \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle16_t, offsetof(toType, toField.t)); \
|
|
ExportAddPathToOffset2(export, fromType ## _ ## fromField, Rectangle16_b, offsetof(toType, toField.b)); \
|
|
toVariable.toField.l = fromVariable->fromField.l; \
|
|
toVariable.toField.r = fromVariable->fromField.r; \
|
|
toVariable.toField.t = fromVariable->fromField.t; \
|
|
toVariable.toField.b = fromVariable->fromField.b;
|
|
|
|
void PaintSolidOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
PaintSolid *solid = (PaintSolid *) pointer;
|
|
ThemePaintSolid themeSolid = { 0 };
|
|
ExportAddPathToOffset((ExportState *) state, PaintSolid_color, offsetof(ThemePaintSolid, color));
|
|
themeSolid.color = ColorLookup(solid->color);
|
|
state->access(state, &themeSolid, sizeof(themeSolid));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PaintOverwriteOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
PaintOverwrite *overwrite = (PaintOverwrite *) pointer;
|
|
ThemePaintSolid themeSolid = { 0 };
|
|
ExportAddPathToOffset((ExportState *) state, PaintOverwrite_color, offsetof(ThemePaintSolid, color));
|
|
themeSolid.color = ColorLookup(overwrite->color);
|
|
state->access(state, &themeSolid, sizeof(themeSolid));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void GradientStopOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
GradientStop *stop = (GradientStop *) pointer;
|
|
ThemeGradientStop themeStop = { 0 };
|
|
ExportAddPathToOffset((ExportState *) state, GradientStop_color, offsetof(ThemeGradientStop, color));
|
|
themeStop.color = ColorLookup(stop->color);
|
|
ExportAddPathToOffset((ExportState *) state, GradientStop_position, offsetof(ThemeGradientStop, position));
|
|
themeStop.position = stop->position;
|
|
state->access(state, &themeStop, sizeof(themeStop));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PaintLinearGradientOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
PaintLinearGradient *gradient = (PaintLinearGradient *) pointer;
|
|
ThemePaintLinearGradient themeGradient = { 0 };
|
|
EXPORT_FIELD(PaintLinearGradient, gradient, ThemePaintLinearGradient, themeGradient, transformX, transform[0]);
|
|
EXPORT_FIELD(PaintLinearGradient, gradient, ThemePaintLinearGradient, themeGradient, transformY, transform[1]);
|
|
EXPORT_FIELD(PaintLinearGradient, gradient, ThemePaintLinearGradient, themeGradient, transformStart, transform[2]);
|
|
themeGradient.useGammaInterpolation = gradient->useGammaInterpolation;
|
|
themeGradient.useDithering = gradient->useDithering;
|
|
themeGradient.useSystemColor = gradient->useSystemHue;
|
|
themeGradient.stopCount = arrlenu(gradient->stops);
|
|
themeGradient.repeatMode = gradient->repeat == GRADIENT_REPEAT_CLAMP ? RAST_REPEAT_CLAMP
|
|
: gradient->repeat == GRADIENT_REPEAT_NORMAL ? RAST_REPEAT_NORMAL
|
|
: gradient->repeat == GRADIENT_REPEAT_MIRROR ? RAST_REPEAT_MIRROR : 0;
|
|
state->access(state, &themeGradient, sizeof(themeGradient));
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(gradient->stops); i++) {
|
|
arrput(export->pathStack, PaintLinearGradient_stops);
|
|
arrput(export->pathStack, i);
|
|
GradientStopOp(state, NULL, gradient->stops + i);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PaintRadialGradientOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
PaintRadialGradient *gradient = (PaintRadialGradient *) pointer;
|
|
ThemePaintRadialGradient themeGradient = { 0 };
|
|
EXPORT_FIELD(PaintRadialGradient, gradient, ThemePaintRadialGradient, themeGradient, transform0, transform[0]);
|
|
EXPORT_FIELD(PaintRadialGradient, gradient, ThemePaintRadialGradient, themeGradient, transform1, transform[1]);
|
|
EXPORT_FIELD(PaintRadialGradient, gradient, ThemePaintRadialGradient, themeGradient, transform2, transform[2]);
|
|
EXPORT_FIELD(PaintRadialGradient, gradient, ThemePaintRadialGradient, themeGradient, transform3, transform[3]);
|
|
EXPORT_FIELD(PaintRadialGradient, gradient, ThemePaintRadialGradient, themeGradient, transform4, transform[4]);
|
|
EXPORT_FIELD(PaintRadialGradient, gradient, ThemePaintRadialGradient, themeGradient, transform5, transform[5]);
|
|
themeGradient.useGammaInterpolation = gradient->useGammaInterpolation;
|
|
themeGradient.stopCount = arrlenu(gradient->stops);
|
|
themeGradient.repeatMode = gradient->repeat == GRADIENT_REPEAT_CLAMP ? RAST_REPEAT_CLAMP
|
|
: gradient->repeat == GRADIENT_REPEAT_NORMAL ? RAST_REPEAT_NORMAL
|
|
: gradient->repeat == GRADIENT_REPEAT_MIRROR ? RAST_REPEAT_MIRROR : 0;
|
|
state->access(state, &themeGradient, sizeof(themeGradient));
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(gradient->stops); i++) {
|
|
arrput(export->pathStack, PaintRadialGradient_stops);
|
|
arrput(export->pathStack, i);
|
|
GradientStopOp(state, NULL, gradient->stops + i);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
Layer *LayerLookup(uint64_t id) {
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.layers); i++) {
|
|
if (styleSet.layers[i]->id == id) {
|
|
return styleSet.layers[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void LayerBoxOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
LayerBox *box = (LayerBox *) pointer;
|
|
|
|
#if 0
|
|
#define EsContainerOf(type, member, pointer) ((type *) ((uint8_t *) pointer - offsetof(type, member)))
|
|
uint64_t layerID = EsContainerOf(Layer, base.box, box)->id;
|
|
#endif
|
|
|
|
ThemeLayerBox themeBox = { 0 };
|
|
ExportAddPathToOffset2(export, LayerBox_borders, Rectangle8_l, offsetof(ThemeLayerBox, borders.l));
|
|
ExportAddPathToOffset2(export, LayerBox_borders, Rectangle8_r, offsetof(ThemeLayerBox, borders.r));
|
|
ExportAddPathToOffset2(export, LayerBox_borders, Rectangle8_t, offsetof(ThemeLayerBox, borders.t));
|
|
ExportAddPathToOffset2(export, LayerBox_borders, Rectangle8_b, offsetof(ThemeLayerBox, borders.b));
|
|
themeBox.borders = box->borders;
|
|
ExportAddPathToOffset2(export, LayerBox_corners, Corners8_tl, offsetof(ThemeLayerBox, corners.tl));
|
|
ExportAddPathToOffset2(export, LayerBox_corners, Corners8_tr, offsetof(ThemeLayerBox, corners.tr));
|
|
ExportAddPathToOffset2(export, LayerBox_corners, Corners8_bl, offsetof(ThemeLayerBox, corners.bl));
|
|
ExportAddPathToOffset2(export, LayerBox_corners, Corners8_br, offsetof(ThemeLayerBox, corners.br));
|
|
themeBox.corners = box->corners;
|
|
if (box->blurred) themeBox.flags |= THEME_LAYER_BOX_IS_BLURRED;
|
|
if (box->autoCorners) themeBox.flags |= THEME_LAYER_BOX_AUTO_CORNERS;
|
|
if (box->autoBorders) themeBox.flags |= THEME_LAYER_BOX_AUTO_BORDERS;
|
|
if (box->shadowHiding) themeBox.flags |= THEME_LAYER_BOX_SHADOW_HIDING;
|
|
themeBox.mainPaintType = box->mainPaint.tag == Paint_solid + 1 ? THEME_PAINT_SOLID
|
|
: box->mainPaint.tag == Paint_linearGradient + 1 ? THEME_PAINT_LINEAR_GRADIENT
|
|
: box->mainPaint.tag == Paint_overwrite + 1 ? THEME_PAINT_OVERWRITE : 0;
|
|
themeBox.borderPaintType = box->borderPaint.tag == Paint_solid + 1 ? THEME_PAINT_SOLID
|
|
: box->borderPaint.tag == Paint_linearGradient + 1 ? THEME_PAINT_LINEAR_GRADIENT
|
|
: box->borderPaint.tag == Paint_overwrite + 1 ? THEME_PAINT_OVERWRITE : 0;
|
|
state->access(state, &themeBox, sizeof(themeBox));
|
|
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
|
|
if (box->mainPaint.tag) {
|
|
RfField *mainPaintField = Paint_Type.fields + box->mainPaint.tag - 1;
|
|
arrput(export->pathStack, LayerBox_mainPaint);
|
|
arrput(export->pathStack, box->mainPaint.tag - 1);
|
|
mainPaintField->item.type->op(state, &mainPaintField->item, (uint8_t *) pointer
|
|
+ mainPaintField->offset + LayerBox_Type.fields[LayerBox_mainPaint].offset);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
|
|
if (box->borderPaint.tag) {
|
|
RfField *borderPaintField = Paint_Type.fields + box->borderPaint.tag - 1;
|
|
arrput(export->pathStack, LayerBox_borderPaint);
|
|
arrput(export->pathStack, box->borderPaint.tag - 1);
|
|
borderPaintField->item.type->op(state, &borderPaintField->item, (uint8_t *) pointer
|
|
+ borderPaintField->offset + LayerBox_Type.fields[LayerBox_borderPaint].offset);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void LayerTextOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
LayerText *layer = (LayerText *) pointer;
|
|
|
|
ThemeLayerText themeLayer = { 0 };
|
|
|
|
EXPORT_FIELD_COLOR(LayerText, layer, ThemeLayerText, themeLayer, color, color);
|
|
EXPORT_FIELD(LayerText, layer, ThemeLayerText, themeLayer, blur, blur);
|
|
|
|
state->access(state, &themeLayer, sizeof(themeLayer));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PathFillSolidOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PathFillContourOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
PathFillContour *fill = (PathFillContour *) pointer;
|
|
|
|
ThemeLayerPathFillContour themeFill = { 0 };
|
|
|
|
EXPORT_FIELD(PathFillContour, fill, ThemeLayerPathFillContour, themeFill, internalWidth, internalWidth);
|
|
EXPORT_FIELD(PathFillContour, fill, ThemeLayerPathFillContour, themeFill, externalWidth, externalWidth);
|
|
|
|
if (fill->joinMode != JOIN_MODE_BEVEL) {
|
|
EXPORT_FIELD(PathFillContour, fill, ThemeLayerPathFillContour, themeFill, miterLimit, miterLimit);
|
|
}
|
|
|
|
themeFill.mode = (fill->joinMode == JOIN_MODE_MITER ? RAST_LINE_JOIN_MITER
|
|
: fill->joinMode == JOIN_MODE_ROUND ? RAST_LINE_JOIN_ROUND
|
|
: fill->joinMode == JOIN_MODE_BEVEL ? RAST_LINE_JOIN_MITER : 0)
|
|
| ((fill->capMode == CAP_MODE_FLAT ? RAST_LINE_CAP_FLAT
|
|
: fill->capMode == CAP_MODE_ROUND ? RAST_LINE_CAP_ROUND
|
|
: fill->capMode == CAP_MODE_SQUARE ? RAST_LINE_CAP_SQUARE : 0) << 2)
|
|
| (fill->integerWidthsOnly ? 0x80 : 0);
|
|
|
|
state->access(state, &themeFill, sizeof(themeFill));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PathFillDashOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
PathFillDash *fill = (PathFillDash *) pointer;
|
|
|
|
ThemeLayerPathFillDash themeFill = { 0 };
|
|
|
|
EXPORT_FIELD(PathFillDash, fill, ThemeLayerPathFillDash, themeFill, gap, gap);
|
|
EXPORT_FIELD(PathFillDash, fill, ThemeLayerPathFillDash, themeFill, length, length);
|
|
|
|
arrput(export->pathStack, PathFillDash_contour);
|
|
|
|
EXPORT_FIELD(PathFillContour, (&fill->contour), ThemeLayerPathFillContour, themeFill.contour, internalWidth, internalWidth);
|
|
EXPORT_FIELD(PathFillContour, (&fill->contour), ThemeLayerPathFillContour, themeFill.contour, externalWidth, externalWidth);
|
|
|
|
if (fill->contour.joinMode != JOIN_MODE_BEVEL) {
|
|
EXPORT_FIELD(PathFillContour, (&fill->contour), ThemeLayerPathFillContour, themeFill.contour, miterLimit, miterLimit);
|
|
}
|
|
|
|
(void) arrpop(export->pathStack);
|
|
|
|
themeFill.contour.mode = (fill->contour.joinMode == JOIN_MODE_MITER ? RAST_LINE_JOIN_MITER
|
|
: fill->contour.joinMode == JOIN_MODE_ROUND ? RAST_LINE_JOIN_ROUND
|
|
: fill->contour.joinMode == JOIN_MODE_BEVEL ? RAST_LINE_JOIN_MITER : 0)
|
|
| ((fill->contour.capMode == CAP_MODE_FLAT ? RAST_LINE_CAP_FLAT
|
|
: fill->contour.capMode == CAP_MODE_ROUND ? RAST_LINE_CAP_ROUND
|
|
: fill->contour.capMode == CAP_MODE_SQUARE ? RAST_LINE_CAP_SQUARE : 0) << 2)
|
|
| (fill->contour.integerWidthsOnly ? 0x80 : 0);
|
|
|
|
state->access(state, &themeFill, sizeof(themeFill));
|
|
} else if (state->op == OP_MAKE_UI) {
|
|
MakeHeaderAndIndentUI("Dash %d", state, item, pointer);
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PathFillDashedOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
PathFillDashed *fill = (PathFillDashed *) pointer;
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(fill->dashes); i++) {
|
|
arrput(export->pathStack, PathFillDashed_dashes);
|
|
arrput(export->pathStack, i);
|
|
PathFillDashOp(state, NULL, fill->dashes + i);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PathFillOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
PathFill *fill = (PathFill *) pointer;
|
|
|
|
ThemeLayerPathFill themeFill = { 0 };
|
|
themeFill.paintAndFillType |= fill->paint.tag == Paint_solid + 1 ? THEME_PAINT_SOLID
|
|
: fill->paint.tag == Paint_linearGradient + 1 ? THEME_PAINT_LINEAR_GRADIENT
|
|
: fill->paint.tag == Paint_radialGradient + 1 ? THEME_PAINT_RADIAL_GRADIENT
|
|
: fill->paint.tag == Paint_overwrite + 1 ? THEME_PAINT_OVERWRITE : 0;
|
|
themeFill.paintAndFillType |= fill->mode.tag == PathFillMode_solid + 1 ? THEME_PATH_FILL_SOLID
|
|
: fill->mode.tag == PathFillMode_contour + 1 ? THEME_PATH_FILL_CONTOUR
|
|
: fill->mode.tag == PathFillMode_dashed + 1 ? THEME_PATH_FILL_DASHED : 0;
|
|
themeFill.dashCount = fill->mode.tag == PathFillMode_dashed + 1 ? arrlen(fill->mode.dashed.dashes) : 0;
|
|
state->access(state, &themeFill, sizeof(themeFill));
|
|
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
|
|
if (fill->paint.tag) {
|
|
RfField *paintField = Paint_Type.fields + fill->paint.tag - 1;
|
|
arrput(export->pathStack, PathFill_paint);
|
|
arrput(export->pathStack, fill->paint.tag - 1);
|
|
paintField->item.type->op(state, &paintField->item, (uint8_t *) pointer
|
|
+ paintField->offset + PathFill_Type.fields[PathFill_paint].offset);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
|
|
if (fill->mode.tag) {
|
|
RfField *modeField = PathFillMode_Type.fields + fill->mode.tag - 1;
|
|
arrput(export->pathStack, PathFill_mode);
|
|
arrput(export->pathStack, fill->mode.tag - 1);
|
|
modeField->item.type->op(state, &modeField->item, (uint8_t *) pointer
|
|
+ modeField->offset + PathFill_Type.fields[PathFill_mode].offset);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
} else if (state->op == OP_MAKE_UI) {
|
|
MakeHeaderAndIndentUI("Fill %d", state, item, pointer);
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void LayerPathOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
LayerPath *layer = (LayerPath *) pointer;
|
|
|
|
ThemeLayerPath themeLayer = { 0 };
|
|
if (layer->evenOdd) themeLayer.flags |= THEME_LAYER_PATH_FILL_EVEN_ODD;
|
|
if (layer->closed) themeLayer.flags |= THEME_LAYER_PATH_CLOSED;
|
|
themeLayer.pointCount = arrlen(layer->points);
|
|
themeLayer.fillCount = arrlen(layer->fills);
|
|
EXPORT_FIELD(LayerPath, layer, ThemeLayerPath, themeLayer, alpha, alpha);
|
|
state->access(state, &themeLayer, sizeof(themeLayer));
|
|
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(layer->points); i++) {
|
|
arrput(export->pathStack, LayerPath_points);
|
|
arrput(export->pathStack, i);
|
|
PathPointOp(state, NULL, layer->points + i);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(layer->fills); i++) {
|
|
arrput(export->pathStack, LayerPath_fills);
|
|
arrput(export->pathStack, i);
|
|
PathFillOp(state, NULL, layer->fills + i);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
Rectangle8 StyleCalculateMaximumGlobalOutsets(uint64_t *styleLayers) {
|
|
Rectangle8 globalOutsets = { 0 };
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleLayers); i++) {
|
|
Layer *layer = LayerLookup(styleLayers[i]);
|
|
|
|
if (layer->base.tag != LayerBase_metrics + 1) {
|
|
continue;
|
|
}
|
|
|
|
globalOutsets.l = MaximumInteger(0, -layer->base.metrics.globalOffset.l);
|
|
globalOutsets.r = MaximumInteger(0, layer->base.metrics.globalOffset.r);
|
|
globalOutsets.t = MaximumInteger(0, -layer->base.metrics.globalOffset.t);
|
|
globalOutsets.b = MaximumInteger(0, layer->base.metrics.globalOffset.b);
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(layer->sequences); j++) {
|
|
for (uintptr_t k = 0; k < arrlenu(layer->sequences[j]->keyframes); k++) {
|
|
for (uintptr_t l = 0; l < arrlenu(layer->sequences[j]->keyframes[k]->properties); l++) {
|
|
Property property = layer->sequences[j]->keyframes[k]->properties[l];
|
|
|
|
if (property.path[0] != PATH_IN_KEYFRAME || property.path[1] != Layer_base
|
|
|| property.path[2] != 0 || property.path[3] != LayerMetrics_globalOffset) {
|
|
continue;
|
|
}
|
|
|
|
int8_t value = 0;
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property.data;
|
|
state.s.access(&state.s, &value, sizeof(int8_t));
|
|
|
|
if (layer->position.l == 0 && -value > globalOutsets.l && property.path[4] == Rectangle8_l) {
|
|
globalOutsets.l = -value;
|
|
}
|
|
|
|
if (layer->position.r == 100 && value > globalOutsets.r && property.path[4] == Rectangle8_r) {
|
|
globalOutsets.r = value;
|
|
}
|
|
|
|
if (layer->position.t == 0 && -value > globalOutsets.t && property.path[4] == Rectangle8_t) {
|
|
globalOutsets.t = -value;
|
|
}
|
|
|
|
if (layer->position.b == 100 && value > globalOutsets.b && property.path[4] == Rectangle8_b) {
|
|
globalOutsets.b = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return globalOutsets;
|
|
}
|
|
|
|
Rectangle8 Rectangle8Add(Rectangle8 a, Rectangle8 b) {
|
|
a.l += b.l;
|
|
a.t += b.t;
|
|
a.r += b.r;
|
|
a.b += b.b;
|
|
return a;
|
|
}
|
|
|
|
Rectangle8 StyleCalculatePaintOutsets(uint64_t *styleLayers) {
|
|
Rectangle8 paintOutsets = { 0 };
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleLayers); i++) {
|
|
Layer *layer = LayerLookup(styleLayers[i]);
|
|
|
|
if (layer->position.l == 0 && -layer->offset.l > paintOutsets.l) {
|
|
paintOutsets.l = -layer->offset.l;
|
|
}
|
|
|
|
if (layer->position.r == 100 && layer->offset.r > paintOutsets.r) {
|
|
paintOutsets.r = layer->offset.r;
|
|
}
|
|
|
|
if (layer->position.t == 0 && -layer->offset.t > paintOutsets.t) {
|
|
paintOutsets.t = -layer->offset.t;
|
|
}
|
|
|
|
if (layer->position.b == 100 && layer->offset.b > paintOutsets.b) {
|
|
paintOutsets.b = layer->offset.b;
|
|
}
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(layer->sequences); j++) {
|
|
for (uintptr_t k = 0; k < arrlenu(layer->sequences[j]->keyframes); k++) {
|
|
for (uintptr_t l = 0; l < arrlenu(layer->sequences[j]->keyframes[k]->properties); l++) {
|
|
Property property = layer->sequences[j]->keyframes[k]->properties[l];
|
|
|
|
if (property.path[0] != PATH_IN_KEYFRAME || property.path[1] != Layer_offset) {
|
|
continue;
|
|
}
|
|
|
|
int8_t value = 0;
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property.data;
|
|
state.s.access(&state.s, &value, sizeof(int8_t));
|
|
|
|
if (layer->position.l == 0 && -value > paintOutsets.l && property.path[2] == Rectangle8_l) {
|
|
paintOutsets.l = -value;
|
|
}
|
|
|
|
if (layer->position.r == 100 && value > paintOutsets.r && property.path[2] == Rectangle8_r) {
|
|
paintOutsets.r = value;
|
|
}
|
|
|
|
if (layer->position.t == 0 && -value > paintOutsets.t && property.path[2] == Rectangle8_t) {
|
|
paintOutsets.t = -value;
|
|
}
|
|
|
|
if (layer->position.b == 100 && value > paintOutsets.b && property.path[2] == Rectangle8_b) {
|
|
paintOutsets.b = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Rectangle8Add(paintOutsets, StyleCalculateMaximumGlobalOutsets(styleLayers));
|
|
}
|
|
|
|
Rectangle8 StyleCalculateOpaqueInsets(uint64_t *styleLayers) {
|
|
Rectangle8 opaqueInsets = (Rectangle8) { 0x7F, 0x7F, 0x7F, 0x7F };
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleLayers); i++) {
|
|
Layer *layer = LayerLookup(styleLayers[i]);
|
|
|
|
if (layer->position.l != 0 || layer->position.r != 100 || layer->position.t != 0 || layer->position.b != 100
|
|
|| layer->base.tag != LayerBase_box + 1 || layer->base.box.shadowHiding || layer->base.box.blurred) {
|
|
continue;
|
|
}
|
|
|
|
Paint *paint = &layer->base.box.mainPaint;
|
|
|
|
bool isOpaque = false;
|
|
|
|
if (paint->tag == Paint_solid + 1) {
|
|
isOpaque = (paint->solid.color & 0xFF000000) == 0xFF000000;
|
|
} else if (paint->tag == Paint_linearGradient + 1) {
|
|
isOpaque = true;
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(paint->linearGradient.stops); j++) {
|
|
if ((paint->linearGradient.stops[j].color & 0xFF000000) != 0xFF000000) {
|
|
isOpaque = false;
|
|
}
|
|
}
|
|
} else if (paint->tag == Paint_overwrite + 1) {
|
|
isOpaque = true;
|
|
}
|
|
|
|
Rectangle8 largestBorders = layer->base.box.borders;
|
|
Corners8 largestCorners = layer->base.box.corners;
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(layer->sequences); j++) {
|
|
for (uintptr_t k = 0; k < arrlenu(layer->sequences[j]->keyframes); k++) {
|
|
for (uintptr_t l = 0; l < arrlenu(layer->sequences[j]->keyframes[k]->properties); l++) {
|
|
Property property = layer->sequences[j]->keyframes[k]->properties[l];
|
|
|
|
if (property.path[0] == PATH_IN_KEYFRAME && property.path[1] == Layer_base
|
|
&& property.path[2] == LayerBase_box && property.path[3] == LayerBox_borders) {
|
|
int8_t value = 0;
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property.data;
|
|
state.s.access(&state.s, &value, sizeof(int8_t));
|
|
|
|
if (property.path[4] == Rectangle8_l && largestBorders.l < value) largestBorders.l = value;
|
|
if (property.path[4] == Rectangle8_r && largestBorders.r < value) largestBorders.r = value;
|
|
if (property.path[4] == Rectangle8_t && largestBorders.t < value) largestBorders.t = value;
|
|
if (property.path[4] == Rectangle8_b && largestBorders.b < value) largestBorders.b = value;
|
|
} else if (property.path[0] == PATH_IN_KEYFRAME && property.path[1] == Layer_base
|
|
&& property.path[2] == LayerBase_box && property.path[3] == LayerBox_corners) {
|
|
int8_t value = 0;
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property.data;
|
|
state.s.access(&state.s, &value, sizeof(int8_t));
|
|
|
|
if (property.path[4] == Corners8_tl && largestCorners.tl < value) largestCorners.tl = value;
|
|
if (property.path[4] == Corners8_tr && largestCorners.tr < value) largestCorners.tr = value;
|
|
if (property.path[4] == Corners8_bl && largestCorners.bl < value) largestCorners.bl = value;
|
|
if (property.path[4] == Corners8_br && largestCorners.br < value) largestCorners.br = value;
|
|
}
|
|
|
|
if (property.path[0] != PATH_IN_KEYFRAME || property.path[1] != Layer_base
|
|
|| property.path[2] != LayerBase_box || property.path[3] != LayerBox_mainPaint
|
|
|| property.path[4] != paint->tag - 1) {
|
|
continue;
|
|
}
|
|
|
|
if (paint->tag == Paint_solid + 1) {
|
|
if (property.path[5] != PaintSolid_color) {
|
|
continue;
|
|
}
|
|
} else if (paint->tag == Paint_linearGradient + 1) {
|
|
if (property.path[5] != PaintLinearGradient_stops
|
|
|| property.path[7] != GradientStop_color) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
uint32_t value = 0;
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property.data;
|
|
state.s.access(&state.s, &value, sizeof(uint32_t));
|
|
|
|
if ((value & 0xFF000000) != 0xFF000000) {
|
|
isOpaque = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isOpaque) {
|
|
opaqueInsets.l = MinimumInteger(opaqueInsets.l, MaximumInteger3(largestCorners.tl, largestCorners.bl, largestBorders.l));
|
|
opaqueInsets.r = MinimumInteger(opaqueInsets.r, MaximumInteger3(largestCorners.tr, largestCorners.br, largestBorders.r));
|
|
opaqueInsets.t = MinimumInteger(opaqueInsets.t, MaximumInteger3(largestCorners.tl, largestCorners.tr, largestBorders.t));
|
|
opaqueInsets.b = MinimumInteger(opaqueInsets.b, MaximumInteger3(largestCorners.bl, largestCorners.br, largestBorders.b));
|
|
}
|
|
}
|
|
|
|
return Rectangle8Add(opaqueInsets, StyleCalculateMaximumGlobalOutsets(styleLayers));
|
|
}
|
|
|
|
Rectangle8 StyleCalculateApproximateBorders(uint64_t *styleLayers) {
|
|
for (uintptr_t i = 0; i < arrlenu(styleLayers); i++) {
|
|
Layer *layer = LayerLookup(styleLayers[i]);
|
|
|
|
if (layer->position.l != 0 || layer->position.r != 100 || layer->position.t != 0 || layer->position.b != 100
|
|
|| layer->base.tag != LayerBase_box + 1 || layer->base.box.shadowHiding
|
|
|| layer->base.box.blurred || !layer->base.box.borderPaint.tag
|
|
|| layer->mode != LAYER_MODE_BACKGROUND) {
|
|
continue;
|
|
}
|
|
|
|
return layer->base.box.borders;
|
|
}
|
|
|
|
return (Rectangle8) { 0 };
|
|
}
|
|
|
|
void LayerMetricsOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ThemeMetrics metrics = { 0 };
|
|
ExportState *export = (ExportState *) state;
|
|
LayerMetrics *layer = (LayerMetrics *) pointer;
|
|
LayerMetrics *inherit = NULL;
|
|
|
|
if (layer->inheritText.byteCount) {
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
Style *style = styleSet.styles[i];
|
|
|
|
if (layer->inheritText.byteCount == style->name.byteCount
|
|
&& 0 == memcmp(layer->inheritText.buffer, style->name.buffer, style->name.byteCount)) {
|
|
inherit = &LayerLookup(style->layers[0])->base.metrics;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
metrics.insets.l = layer->insets.l;
|
|
metrics.insets.r = layer->insets.r;
|
|
metrics.insets.t = layer->insets.t;
|
|
metrics.insets.b = layer->insets.b;
|
|
metrics.clipEnabled = layer->clipEnabled == CLIP_MODE_ENABLED;
|
|
metrics.clipInsets.l = layer->clipInsets.l;
|
|
metrics.clipInsets.r = layer->clipInsets.r;
|
|
metrics.clipInsets.t = layer->clipInsets.t;
|
|
metrics.clipInsets.b = layer->clipInsets.b;
|
|
metrics.cursor = layer->cursor;
|
|
metrics.preferredWidth = layer->preferredSize.width;
|
|
metrics.preferredHeight = layer->preferredSize.height;
|
|
metrics.minimumWidth = layer->minimumSize.width;
|
|
metrics.minimumHeight = layer->minimumSize.height;
|
|
metrics.maximumWidth = layer->maximumSize.width;
|
|
metrics.maximumHeight = layer->maximumSize.height;
|
|
metrics.gapMajor = layer->gaps.major;
|
|
metrics.gapMinor = layer->gaps.minor;
|
|
metrics.gapWrap = layer->gaps.wrap;
|
|
|
|
int fontFamily = inherit ? inherit->fontFamily : layer->fontFamily;
|
|
metrics.fontFamily = fontFamily == FONT_FAMILY_SANS ? 0xFFFF : fontFamily == FONT_FAMILY_SERIF ? 0xFFFE : 0xFFFD;
|
|
|
|
if (inherit) {
|
|
EXPORT_FIELD_COLOR(LayerMetrics, inherit, ThemeMetrics, metrics, textColor, textColor);
|
|
EXPORT_FIELD_COLOR(LayerMetrics, inherit, ThemeMetrics, metrics, selectedBackground, selectedBackground);
|
|
EXPORT_FIELD_COLOR(LayerMetrics, inherit, ThemeMetrics, metrics, selectedText, selectedText);
|
|
EXPORT_FIELD(LayerMetrics, inherit, ThemeMetrics, metrics, textSize, textSize);
|
|
EXPORT_FIELD(LayerMetrics, inherit, ThemeMetrics, metrics, fontWeight, fontWeight);
|
|
EXPORT_FIELD(LayerMetrics, inherit, ThemeMetrics, metrics, italic, isItalic);
|
|
} else {
|
|
EXPORT_FIELD_COLOR(LayerMetrics, layer, ThemeMetrics, metrics, textColor, textColor);
|
|
EXPORT_FIELD_COLOR(LayerMetrics, layer, ThemeMetrics, metrics, selectedBackground, selectedBackground);
|
|
EXPORT_FIELD_COLOR(LayerMetrics, layer, ThemeMetrics, metrics, selectedText, selectedText);
|
|
EXPORT_FIELD(LayerMetrics, layer, ThemeMetrics, metrics, textSize, textSize);
|
|
EXPORT_FIELD(LayerMetrics, layer, ThemeMetrics, metrics, fontWeight, fontWeight);
|
|
EXPORT_FIELD(LayerMetrics, layer, ThemeMetrics, metrics, italic, isItalic);
|
|
}
|
|
|
|
EXPORT_FIELD(LayerMetrics, layer, ThemeMetrics, metrics, iconSize, iconSize);
|
|
EXPORT_FIELD_COLOR(LayerMetrics, layer, ThemeMetrics, metrics, iconColor, iconColor);
|
|
EXPORT_FIELD_ALIGN(LayerMetrics, layer, ThemeMetrics, metrics, textHorizontalAlign, textVerticalAlign, textAlign);
|
|
if (layer->ellipsis) metrics.textAlign |= ES_TEXT_ELLIPSIS;
|
|
if (layer->wrapText) metrics.textAlign |= ES_TEXT_WRAP;
|
|
state->access(state, &metrics, sizeof(metrics));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void LayerOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
Layer *layer = (Layer *) pointer;
|
|
ThemeLayer themeLayer = { 0 };
|
|
ExportAddPathToOffset2(export, Layer_offset, Rectangle8_l, offsetof(ThemeLayer, offset.l));
|
|
ExportAddPathToOffset2(export, Layer_offset, Rectangle8_r, offsetof(ThemeLayer, offset.r));
|
|
ExportAddPathToOffset2(export, Layer_offset, Rectangle8_t, offsetof(ThemeLayer, offset.t));
|
|
ExportAddPathToOffset2(export, Layer_offset, Rectangle8_b, offsetof(ThemeLayer, offset.b));
|
|
themeLayer.offset = layer->offset;
|
|
ExportAddPathToOffset2(export, Layer_position, Rectangle8_l, offsetof(ThemeLayer, position.l));
|
|
ExportAddPathToOffset2(export, Layer_position, Rectangle8_r, offsetof(ThemeLayer, position.r));
|
|
ExportAddPathToOffset2(export, Layer_position, Rectangle8_t, offsetof(ThemeLayer, position.t));
|
|
ExportAddPathToOffset2(export, Layer_position, Rectangle8_b, offsetof(ThemeLayer, position.b));
|
|
themeLayer.position = layer->position;
|
|
themeLayer.mode = layer->mode;
|
|
if (layer->base.tag == LayerBase_box + 1) themeLayer.type = THEME_LAYER_BOX;
|
|
if (layer->base.tag == LayerBase_metrics + 1) themeLayer.type = THEME_LAYER_METRICS;
|
|
if (layer->base.tag == LayerBase_text + 1) themeLayer.type = THEME_LAYER_TEXT;
|
|
if (layer->base.tag == LayerBase_path + 1) themeLayer.type = THEME_LAYER_PATH;
|
|
assert(themeLayer.type);
|
|
state->access(state, &themeLayer, sizeof(themeLayer));
|
|
uintptr_t stackPosition = arrlenu(export->pathStack);
|
|
RfField *baseField = LayerBase_Type.fields + layer->base.tag - 1;
|
|
arrput(export->pathStack, Layer_base);
|
|
arrput(export->pathStack, 0);
|
|
baseField->item.type->op(state, &baseField->item, (uint8_t *) pointer
|
|
+ baseField->offset + Layer_Type.fields[Layer_base].offset);
|
|
arrsetlen(export->pathStack, stackPosition);
|
|
} else if (state->op == OP_GET_PALETTE || state->op == OP_REPLACE_COLOR || state->op == OP_FIND_COLOR_USERS) {
|
|
currentPaletteOpLayer = pointer;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleSetOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
ExportState *export = (ExportState *) state;
|
|
StyleSet *styleSet = (StyleSet *) pointer;
|
|
|
|
// Write the header.
|
|
|
|
ThemeHeader header = { 0 };
|
|
header.signature = THEME_HEADER_SIGNATURE;
|
|
header.styleCount = arrlenu(styleSet->styles);
|
|
header.constantCount = arrlenu(styleSet->constants);
|
|
state->access(state, &header, sizeof(header));
|
|
assert((export->buffer.data.byteCount & 3) == 0);
|
|
|
|
// Write the list of styles.
|
|
|
|
uint32_t styleListOffset = export->buffer.data.byteCount;
|
|
|
|
FILE *f = stylesPath ? fopen(stylesPath, "wb") : NULL;
|
|
|
|
for (uintptr_t i = 0; i < header.styleCount; i++) {
|
|
Style *style = styleSet->styles[i];
|
|
ThemeStyle entry = { 0 };
|
|
entry.id = (style->id << 1) | 1;
|
|
entry.layerCount = arrlenu(style->layers);
|
|
entry.paintOutsets = StyleCalculatePaintOutsets(style->layers);
|
|
entry.opaqueInsets = StyleCalculateOpaqueInsets(style->layers);
|
|
entry.approximateBorders = StyleCalculateApproximateBorders(style->layers);
|
|
state->access(state, &entry, sizeof(entry));
|
|
assert((export->buffer.data.byteCount & 3) == 0);
|
|
|
|
printf("exporting '%.*s' (id: %ld)\n", (int) style->name.byteCount, (char *) style->name.buffer, (style->id << 1) | 1);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(style->layers); i++) {
|
|
printf("\thas layer %ld\n", style->layers[i]);
|
|
}
|
|
|
|
if (style->id && stylesPath) {
|
|
fprintf(f, "%s ES_STYLE_", style->publicStyle ? "define" : "private define");
|
|
|
|
bool dot = false;
|
|
|
|
for (uintptr_t j = 0; j < style->name.byteCount; j++) {
|
|
char c = ((const char *) style->name.buffer)[j];
|
|
|
|
if (c == '.') {
|
|
fprintf(f, "_");
|
|
dot = true;
|
|
} else if (c >= 'A' && c <= 'Z' && j && !dot) {
|
|
fprintf(f, "_%c", c);
|
|
} else {
|
|
fprintf(f, "%c", toupper(c));
|
|
dot = false;
|
|
}
|
|
}
|
|
|
|
fprintf(f, " (ES_STYLE_CAST(%ld))\n", (style->id << 1) | 1);
|
|
}
|
|
}
|
|
|
|
if (stylesPath) fclose(f);
|
|
|
|
// Write the list of constants.
|
|
|
|
for (uintptr_t i = 0; i < header.constantCount; i++) {
|
|
Constant *constant = styleSet->constants[i];
|
|
ThemeConstant entry = { 0 };
|
|
assert(constant->value.byteCount + 1 < sizeof(entry.cValue));
|
|
entry.hash = CalculateCRC64(constant->key.buffer, constant->key.byteCount, 0);
|
|
entry.scale = constant->scale;
|
|
memcpy(entry.cValue, constant->value.buffer, constant->value.byteCount);
|
|
entry.cValue[constant->value.byteCount] = 0;
|
|
state->access(state, &entry, sizeof(entry));
|
|
assert((export->buffer.data.byteCount & 3) == 0);
|
|
}
|
|
|
|
// Write out all layers.
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet->layers); i++) {
|
|
Layer *layer = styleSet->layers[i];
|
|
layer->exportOffset = export->buffer.data.byteCount;
|
|
assert((layer->exportOffset & 3) == 0);
|
|
RfItem item = { 0 };
|
|
item.type = &Layer_Type;
|
|
item.byteCount = sizeof(Layer);
|
|
LayerOp(state, &item, layer);
|
|
ThemeLayer *entry = (ThemeLayer *) ((uint8_t *) export->buffer.data.buffer + layer->exportOffset);
|
|
entry->dataByteCount = export->buffer.data.byteCount - layer->exportOffset;
|
|
entry->sequenceDataOffset = arrlenu(layer->sequences) ? export->buffer.data.byteCount : 0;
|
|
|
|
Layer *previousLayer = selected.layer;
|
|
selected.layer = layer; // HACK!
|
|
|
|
// Write out the sequences.
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(layer->sequences); j++) {
|
|
Sequence *sequence = layer->sequences[j];
|
|
uint32_t headerOffset = export->buffer.data.byteCount;
|
|
|
|
{
|
|
ThemeSequenceHeader header = { 0 };
|
|
header.state = sequence->primaryState
|
|
| (sequence->flagFocused ? THEME_STATE_FOCUSED : 0)
|
|
| (sequence->flagChecked ? THEME_STATE_CHECKED : 0)
|
|
| (sequence->flagIndeterminate ? THEME_STATE_INDETERMINATE : 0)
|
|
| (sequence->flagDefault ? THEME_STATE_DEFAULT_BUTTON : 0)
|
|
| (sequence->flagItemFocus ? THEME_STATE_FOCUSED_ITEM : 0)
|
|
| (sequence->flagListFocus ? THEME_STATE_LIST_FOCUSED : 0)
|
|
| (sequence->flagBeforeEnter ? THEME_STATE_BEFORE_ENTER : 0)
|
|
| (sequence->flagAfterExit ? THEME_STATE_AFTER_EXIT : 0)
|
|
| (sequence->flagSelected ? THEME_STATE_SELECTED : 0);
|
|
header.duration = sequence->duration;
|
|
header.isLastSequence = j == arrlenu(layer->sequences) - 1;
|
|
state->access(state, &header, sizeof(header));
|
|
|
|
if (sequence->flagBeforeEnter) printf("before enter %ld\n", layer->id);
|
|
if (sequence->flagAfterExit) printf("after exit %ld\n", layer->id);
|
|
}
|
|
|
|
uint32_t overrideCount = 0;
|
|
|
|
for (uintptr_t k = 0; k < arrlenu(sequence->keyframes); k++) {
|
|
for (uintptr_t l = 0; l < arrlenu(sequence->keyframes[k]->properties); l++) {
|
|
Property *property = sequence->keyframes[k]->properties + l;
|
|
|
|
for (uintptr_t m = 0; m < arrlenu(export->pathToOffsetList); m++) {
|
|
const PathToOffset *pathToOffset = export->pathToOffsetList + m;
|
|
|
|
if (!ArePathsEqual(pathToOffset->path, property->path + 1)) {
|
|
continue;
|
|
}
|
|
|
|
RfItem item;
|
|
Keyframe *previousKeyframe = selected.keyframe;
|
|
selected.keyframe = (Keyframe *) sequence->keyframes[k]; // HACK!
|
|
void *source = ResolveDataObject((RfPath *) property->path, &item);
|
|
selected.keyframe = previousKeyframe;
|
|
|
|
ThemeOverride override = { 0 };
|
|
override.offset = pathToOffset->offset - layer->exportOffset;
|
|
|
|
if (item.type == &StyleI8_Type) {
|
|
override.type = THEME_OVERRIDE_I8;
|
|
override.data.i8 = *(int8_t *) source;
|
|
} else if (item.type == &StyleI16_Type) {
|
|
override.type = THEME_OVERRIDE_I16;
|
|
override.data.i16 = *(int16_t *) source;
|
|
} else if (item.type == &StyleFloat_Type) {
|
|
override.type = THEME_OVERRIDE_F32;
|
|
override.data.f32 = *(float *) source;
|
|
} else if (item.type == &StyleColor_Type) {
|
|
override.type = THEME_OVERRIDE_COLOR;
|
|
override.data.u32 = ColorLookup(*(uint32_t *) source);
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
overrideCount++;
|
|
state->access(state, &override, sizeof(override));
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ThemeSequenceHeader *header = (ThemeSequenceHeader *) ((uint8_t *) export->buffer.data.buffer + headerOffset);
|
|
header->overrideCount = overrideCount;
|
|
}
|
|
|
|
selected.layer = previousLayer;
|
|
|
|
ExportFreePathToOffsetList(export->pathToOffsetList);
|
|
export->pathToOffsetList = NULL;
|
|
}
|
|
|
|
// Write out layer lists for styles, and update the style list to point to them.
|
|
|
|
for (uintptr_t i = 0; i < header.styleCount; i++) {
|
|
Style *style = styleSet->styles[i];
|
|
uint32_t layerListOffset = export->buffer.data.byteCount;
|
|
assert((export->buffer.data.byteCount & 3) == 0);
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(style->layers); j++) {
|
|
uint32_t layerOffset = LayerLookup(style->layers[j])->exportOffset;
|
|
state->access(state, &layerOffset, sizeof(layerOffset));
|
|
}
|
|
|
|
ThemeStyle *entry = (ThemeStyle *) ((uint8_t *) export->buffer.data.buffer + styleListOffset) + i;
|
|
entry->layerListOffset = layerListOffset;
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ActionExport(void *_unused) {
|
|
PathToOffset *pathToOffsetList;
|
|
RfData data = ExportToGrowableBuffer(&StyleSet_Type, sizeof(StyleSet), NULL, &styleSet, &pathToOffsetList);
|
|
ExportFreePathToOffsetList(pathToOffsetList);
|
|
FILE *f = fopen(exportPath, "wb");
|
|
fwrite(data.buffer, 1, data.byteCount, f);
|
|
fclose(f);
|
|
free(data.buffer);
|
|
|
|
{
|
|
RfState state = { 0 };
|
|
state.op = OP_FIND_COLOR_USERS;
|
|
RfItem item = { 0 };
|
|
item.type = &StyleSet_Type;
|
|
item.byteCount = sizeof(StyleSet);
|
|
item.options = NULL;
|
|
RfBroadcast(&state, &item, &styleSet, true);
|
|
|
|
for (uintptr_t i = 0; i < hmlenu(colorUsers); i++) {
|
|
Color *color = ColorLookupPointer(colorUsers[i].key);
|
|
|
|
printf("%.*s - ", (int) color->key.byteCount, (char *) color->key.buffer);
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(colorUsers[i].value); j++) {
|
|
Style *style = colorUsers[i].value[j];
|
|
printf("%.*s, ", (int) style->name.byteCount, (char *) style->name.buffer);
|
|
}
|
|
|
|
printf("\n");
|
|
arrfree(colorUsers[i].value);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
printf("%.*s\t%.8X\n", (int) styleSet.colors[i]->key.byteCount, (char *) styleSet.colors[i]->key.buffer, styleSet.colors[i]->value);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
for (uintptr_t j = 0; j < hmlenu(colorUsers); j++) {
|
|
if (styleSet.colors[i]->id == colorUsers[j].key) {
|
|
goto used;
|
|
}
|
|
}
|
|
|
|
printf("Color '%.*s' is unused.\n", (int) styleSet.colors[i]->key.byteCount, (char *) styleSet.colors[i]->key.buffer);
|
|
used:;
|
|
}
|
|
|
|
hmfree(colorUsers);
|
|
}
|
|
}
|
|
|
|
// ------------------- Exporting to Designer2 -------------------
|
|
|
|
enum PropertyType {
|
|
PROP_NONE,
|
|
PROP_COLOR,
|
|
PROP_INT,
|
|
PROP_OBJECT,
|
|
PROP_FLOAT,
|
|
};
|
|
|
|
typedef struct Property2 {
|
|
uint8_t type;
|
|
#define PROPERTY_NAME_SIZE (31)
|
|
char cName[PROPERTY_NAME_SIZE];
|
|
|
|
union {
|
|
int32_t integer;
|
|
uint64_t object;
|
|
float floating;
|
|
};
|
|
} Property2;
|
|
|
|
enum ObjectType {
|
|
OBJ_NONE,
|
|
|
|
OBJ_STYLE,
|
|
OBJ_COMMENT,
|
|
OBJ_INSTANCE,
|
|
|
|
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_LAYER_BOX = 0x80,
|
|
OBJ_LAYER_METRICS,
|
|
OBJ_LAYER_TEXT,
|
|
OBJ_LAYER_GROUP,
|
|
OBJ_LAYER_PATH,
|
|
|
|
OBJ_MOD_COLOR = 0xC0,
|
|
OBJ_MOD_MULTIPLY,
|
|
};
|
|
|
|
typedef struct Object2 {
|
|
uint8_t 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;
|
|
Property2 *properties;
|
|
} Object2;
|
|
|
|
void ObjectAddIntegerProperty(Object2 *object, const char *cName, int32_t value) {
|
|
Property2 property = { 0 };
|
|
property.type = PROP_INT;
|
|
strcpy(property.cName, cName);
|
|
property.integer = value;
|
|
arrput(object->properties, property);
|
|
}
|
|
|
|
void ObjectAddColorProperty(Object2 *object, const char *cName, uint32_t value) {
|
|
Property2 property = { 0 };
|
|
property.type = PROP_COLOR;
|
|
strcpy(property.cName, cName);
|
|
property.integer = value;
|
|
arrput(object->properties, property);
|
|
}
|
|
|
|
void ObjectAddFloatProperty(Object2 *object, const char *cName, float value) {
|
|
Property2 property = { 0 };
|
|
property.type = PROP_FLOAT;
|
|
strcpy(property.cName, cName);
|
|
property.floating = value;
|
|
arrput(object->properties, property);
|
|
}
|
|
|
|
void ObjectAddObjectProperty(Object2 *object, const char *cName, uint64_t value) {
|
|
Property2 property = { 0 };
|
|
property.type = PROP_OBJECT;
|
|
strcpy(property.cName, cName);
|
|
property.object = value;
|
|
arrput(object->properties, property);
|
|
}
|
|
|
|
void AutoNameOverrideObject(Object2 *override, uint32_t primaryState, uint32_t stateBits) {
|
|
const char *cPrimaryStateStrings[] = {
|
|
"Any", "Idle", "Hovered", "Pressed", "Disabled", "Inactive",
|
|
};
|
|
|
|
const char *cStateBitStrings[] = {
|
|
"Focus", "Check", "Indtm", "DefBtn", "Sel", "FcItem", "ListFc", "BfEnt", "AfExt",
|
|
};
|
|
|
|
snprintf(override->cName, sizeof(override->cName), "?%s", primaryState ? cPrimaryStateStrings[primaryState] : "");
|
|
|
|
for (uintptr_t i = 0; i < 16; i++) {
|
|
if (stateBits & (1 << (15 - i))) {
|
|
snprintf(override->cName + strlen(override->cName), sizeof(override->cName) - strlen(override->cName),
|
|
"%s%s", i || primaryState ? "&" : "", cStateBitStrings[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t ExportPaint2(Paint *paint, int *x, int y, Object2 **objects, uint64_t *idAllocator, Layer *layer) {
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
|
|
if (!paint->tag) {
|
|
return 0;
|
|
} else if (paint->tag == Paint_solid + 1) {
|
|
return ColorLookupPointer(paint->solid.color)->object2ID;
|
|
} else if (paint->tag == Paint_linearGradient + 1) {
|
|
Object2 object = { .type = OBJ_PAINT_LINEAR_GRADIENT, .id = ++(*idAllocator) };
|
|
|
|
ObjectAddIntegerProperty(&object, "_graphX", *x);
|
|
ObjectAddIntegerProperty(&object, "_graphY", y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
ObjectAddFloatProperty(&object, "transformX", paint->linearGradient.transformX);
|
|
ObjectAddFloatProperty(&object, "transformY", paint->linearGradient.transformY);
|
|
ObjectAddFloatProperty(&object, "transformStart", paint->linearGradient.transformStart);
|
|
ObjectAddIntegerProperty(&object, "repeatMode", paint->linearGradient.repeat);
|
|
ObjectAddIntegerProperty(&object, "useGammaInterpolation", paint->linearGradient.useGammaInterpolation);
|
|
ObjectAddIntegerProperty(&object, "useSystemColor", paint->linearGradient.useSystemHue);
|
|
ObjectAddIntegerProperty(&object, "stops_count", arrlenu(paint->linearGradient.stops));
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(paint->linearGradient.stops); i++) {
|
|
sprintf(cPropertyName, "stops_%d_position", (int) i);
|
|
ObjectAddIntegerProperty(&object, cPropertyName, paint->linearGradient.stops[i].position);
|
|
sprintf(cPropertyName, "stops_%d_color", (int) i);
|
|
ObjectAddObjectProperty(&object, cPropertyName, ColorLookupPointer(paint->linearGradient.stops[i].color)->object2ID);
|
|
}
|
|
|
|
*x += 100;
|
|
arrput(*objects, object);
|
|
|
|
if (layer) {
|
|
for (uintptr_t i = 0; i < arrlenu(layer->sequences); i++) {
|
|
Sequence *s = layer->sequences[i];
|
|
assert(arrlenu(s->keyframes) == 1);
|
|
Keyframe *keyframe = s->keyframes[0];
|
|
|
|
uint32_t stateBits = 0;
|
|
if (s->flagFocused) stateBits |= THEME_STATE_FOCUSED;
|
|
if (s->flagChecked) stateBits |= THEME_STATE_CHECKED;
|
|
if (s->flagIndeterminate) stateBits |= THEME_STATE_INDETERMINATE;
|
|
if (s->flagDefault) stateBits |= THEME_STATE_DEFAULT_BUTTON;
|
|
if (s->flagItemFocus) stateBits |= THEME_STATE_FOCUSED_ITEM;
|
|
if (s->flagListFocus) stateBits |= THEME_STATE_LIST_FOCUSED;
|
|
if (s->flagBeforeEnter) stateBits |= THEME_STATE_BEFORE_ENTER;
|
|
if (s->flagAfterExit) stateBits |= THEME_STATE_AFTER_EXIT;
|
|
if (s->flagSelected) stateBits |= THEME_STATE_SELECTED;
|
|
|
|
Object2 override = { .type = object.type, .id = ++(*idAllocator) };
|
|
ObjectAddIntegerProperty(&override, "_graphX", *x);
|
|
ObjectAddIntegerProperty(&override, "_graphY", y);
|
|
ObjectAddIntegerProperty(&override, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&override, "_graphH", 60);
|
|
ObjectAddObjectProperty(&override, "_parent", object.id);
|
|
ObjectAddIntegerProperty(&override, "_primaryState", s->primaryState);
|
|
ObjectAddIntegerProperty(&override, "_stateBits", stateBits);
|
|
ObjectAddIntegerProperty(&override, "_duration", s->duration);
|
|
AutoNameOverrideObject(&override, s->primaryState, stateBits);
|
|
|
|
bool addObject = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(keyframe->properties); i++) {
|
|
Property *property = &keyframe->properties[i];
|
|
|
|
if (layer->base.tag == LayerBase_box + 1 && property->path[0] == (uint32_t) -2 && property->path[1] == 8
|
|
&& property->path[2] == 0 && property->path[3] == 2 && property->path[4] == 2) {
|
|
assert(property->path[5] == 2 && property->path[7] == 0);
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.op = RF_OP_LOAD;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property->data;
|
|
RfItem item = GradientStop_Type.fields[GradientStop_color].item;
|
|
uint32_t value; item.type->op(&state.s, &item, &value);
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
snprintf(cPropertyName, sizeof(cPropertyName), "stops_%d_color", property->path[6]);
|
|
ObjectAddObjectProperty(&override, cPropertyName, ColorLookupPointer(value)->object2ID);
|
|
addObject = true;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (addObject) {
|
|
arrput(*objects, override);
|
|
object.id = override.id;
|
|
*x += 100;
|
|
} else {
|
|
arrfree(override.properties);
|
|
}
|
|
}
|
|
}
|
|
|
|
return object.id;
|
|
} else if (paint->tag == Paint_radialGradient + 1) {
|
|
Object2 object = { .type = OBJ_PAINT_RADIAL_GRADIENT, .id = ++(*idAllocator) };
|
|
|
|
ObjectAddIntegerProperty(&object, "_graphX", *x);
|
|
ObjectAddIntegerProperty(&object, "_graphY", y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
ObjectAddFloatProperty(&object, "transform0", paint->radialGradient.transform0);
|
|
ObjectAddFloatProperty(&object, "transform1", paint->radialGradient.transform1);
|
|
ObjectAddFloatProperty(&object, "transform2", paint->radialGradient.transform2);
|
|
ObjectAddFloatProperty(&object, "transform3", paint->radialGradient.transform3);
|
|
ObjectAddFloatProperty(&object, "transform4", paint->radialGradient.transform4);
|
|
ObjectAddFloatProperty(&object, "transform5", paint->radialGradient.transform5);
|
|
ObjectAddIntegerProperty(&object, "repeatMode", paint->radialGradient.repeat);
|
|
ObjectAddIntegerProperty(&object, "useGammaInterpolation", paint->radialGradient.useGammaInterpolation);
|
|
ObjectAddIntegerProperty(&object, "stops_count", arrlenu(paint->radialGradient.stops));
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(paint->radialGradient.stops); i++) {
|
|
sprintf(cPropertyName, "stops_%d_position", (int) i);
|
|
ObjectAddIntegerProperty(&object, cPropertyName, paint->radialGradient.stops[i].position);
|
|
sprintf(cPropertyName, "stops_%d_color", (int) i);
|
|
ObjectAddObjectProperty(&object, cPropertyName, ColorLookupPointer(paint->radialGradient.stops[i].color)->object2ID);
|
|
}
|
|
|
|
*x += 100;
|
|
arrput(*objects, object);
|
|
return object.id;
|
|
} else if (paint->tag == Paint_overwrite + 1) {
|
|
Object2 object = { .type = OBJ_PAINT_OVERWRITE, .id = ++(*idAllocator) };
|
|
ObjectAddIntegerProperty(&object, "_graphX", *x);
|
|
ObjectAddIntegerProperty(&object, "_graphY", y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
ObjectAddObjectProperty(&object, "color", ColorLookupPointer(paint->overwrite.color)->object2ID);
|
|
*x += 100;
|
|
arrput(*objects, object);
|
|
|
|
if (layer) {
|
|
for (uintptr_t i = 0; i < arrlenu(layer->sequences); i++) {
|
|
Sequence *s = layer->sequences[i];
|
|
assert(arrlenu(s->keyframes) == 1);
|
|
Keyframe *keyframe = s->keyframes[0];
|
|
|
|
uint32_t stateBits = 0;
|
|
if (s->flagFocused) stateBits |= THEME_STATE_FOCUSED;
|
|
if (s->flagChecked) stateBits |= THEME_STATE_CHECKED;
|
|
if (s->flagIndeterminate) stateBits |= THEME_STATE_INDETERMINATE;
|
|
if (s->flagDefault) stateBits |= THEME_STATE_DEFAULT_BUTTON;
|
|
if (s->flagItemFocus) stateBits |= THEME_STATE_FOCUSED_ITEM;
|
|
if (s->flagListFocus) stateBits |= THEME_STATE_LIST_FOCUSED;
|
|
if (s->flagBeforeEnter) stateBits |= THEME_STATE_BEFORE_ENTER;
|
|
if (s->flagAfterExit) stateBits |= THEME_STATE_AFTER_EXIT;
|
|
if (s->flagSelected) stateBits |= THEME_STATE_SELECTED;
|
|
|
|
Object2 override = { .type = object.type, .id = ++(*idAllocator) };
|
|
ObjectAddIntegerProperty(&override, "_graphX", *x);
|
|
ObjectAddIntegerProperty(&override, "_graphY", y);
|
|
ObjectAddIntegerProperty(&override, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&override, "_graphH", 60);
|
|
ObjectAddObjectProperty(&override, "_parent", object.id);
|
|
ObjectAddIntegerProperty(&override, "_primaryState", s->primaryState);
|
|
ObjectAddIntegerProperty(&override, "_stateBits", stateBits);
|
|
ObjectAddIntegerProperty(&override, "_duration", s->duration);
|
|
AutoNameOverrideObject(&override, s->primaryState, stateBits);
|
|
|
|
bool addObject = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(keyframe->properties); i++) {
|
|
Property *property = &keyframe->properties[i];
|
|
|
|
if (layer->base.tag == LayerBase_box + 1 && property->path[0] == (uint32_t) -2 && property->path[1] == 8
|
|
&& property->path[2] == 0 && property->path[3] == 2 && property->path[4] == 3) {
|
|
assert(property->path[5] == 0);
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.op = RF_OP_LOAD;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property->data;
|
|
RfItem item = PaintOverwrite_Type.fields[PaintOverwrite_color].item;
|
|
uint32_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddObjectProperty(&override, "color", ColorLookupPointer(value)->object2ID);
|
|
addObject = true;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (addObject) {
|
|
arrput(*objects, override);
|
|
object.id = override.id;
|
|
*x += 100;
|
|
}
|
|
}
|
|
}
|
|
|
|
return object.id;
|
|
} else {
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint64_t ExportFillMode2(PathFillMode *fill, int *x, int y, Object2 **objects, uint64_t *idAllocator) {
|
|
if (fill->tag == PathFillMode_solid + 1) {
|
|
return 0;
|
|
} else if (fill->tag == PathFillMode_contour + 1) {
|
|
Object2 object = { .type = OBJ_VAR_CONTOUR_STYLE, .id = ++(*idAllocator) };
|
|
ObjectAddIntegerProperty(&object, "_graphX", *x);
|
|
ObjectAddIntegerProperty(&object, "_graphY", y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
ObjectAddIntegerProperty(&object, "internalWidth", fill->contour.internalWidth);
|
|
ObjectAddIntegerProperty(&object, "externalWidth", fill->contour.externalWidth);
|
|
ObjectAddIntegerProperty(&object, "integerWidthsOnly", fill->contour.integerWidthsOnly);
|
|
ObjectAddIntegerProperty(&object, "joinMode", fill->contour.joinMode == JOIN_MODE_ROUND ? RAST_LINE_JOIN_ROUND : RAST_LINE_JOIN_MITER);
|
|
ObjectAddIntegerProperty(&object, "capMode", fill->contour.capMode == CAP_MODE_FLAT ? RAST_LINE_CAP_FLAT
|
|
: fill->contour.capMode == CAP_MODE_ROUND ? RAST_LINE_CAP_ROUND : RAST_LINE_CAP_SQUARE);
|
|
ObjectAddFloatProperty(&object, "miterLimit", fill->contour.joinMode == JOIN_MODE_BEVEL ? 0.0f : fill->contour.miterLimit);
|
|
*x += 100;
|
|
arrput(*objects, object);
|
|
return object.id;
|
|
} else {
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void ExportProperty2(Layer *layer, Property *property, Object2 *override) {
|
|
bool unhandled = false;
|
|
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.op = RF_OP_LOAD;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = property->data;
|
|
|
|
if (property->path[0] == (uint32_t) -2 && property->path[1] == 8 && property->path[2] == 0) {
|
|
if (layer->base.tag == LayerBase_box + 1) {
|
|
if (property->path[3] == LayerBox_borders && property->path[4] == Rectangle8_l) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_l].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "borders0", value);
|
|
} else if (property->path[3] == LayerBox_borders && property->path[4] == Rectangle8_r) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_r].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "borders1", value);
|
|
} else if (property->path[3] == LayerBox_borders && property->path[4] == Rectangle8_t) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_t].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "borders2", value);
|
|
} else if (property->path[3] == LayerBox_borders && property->path[4] == Rectangle8_b) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_b].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "borders3", value);
|
|
} else if (property->path[3] == LayerBox_corners && property->path[4] == Corners8_tl) {
|
|
RfItem item = Corners8_Type.fields[Corners8_tl].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "corners0", value);
|
|
} else if (property->path[3] == LayerBox_corners && property->path[4] == Corners8_tr) {
|
|
RfItem item = Corners8_Type.fields[Corners8_tr].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "corners1", value);
|
|
} else if (property->path[3] == LayerBox_corners && property->path[4] == Corners8_bl) {
|
|
RfItem item = Corners8_Type.fields[Corners8_bl].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "corners2", value);
|
|
} else if (property->path[3] == LayerBox_corners && property->path[4] == Corners8_br) {
|
|
RfItem item = Corners8_Type.fields[Corners8_br].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "corners3", value);
|
|
} else if (property->path[3] == LayerBox_mainPaint && property->path[4] == Paint_solid && property->path[5] == PaintSolid_color) {
|
|
RfItem item = PaintSolid_Type.fields[PaintSolid_color].item;
|
|
uint32_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddObjectProperty(override, "mainPaint", ColorLookupPointer(value)->object2ID);
|
|
} else if (property->path[3] == LayerBox_borderPaint && property->path[4] == Paint_solid && property->path[5] == PaintSolid_color) {
|
|
RfItem item = PaintSolid_Type.fields[PaintSolid_color].item;
|
|
uint32_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddObjectProperty(override, "borderPaint", ColorLookupPointer(value)->object2ID);
|
|
} else {
|
|
unhandled = true;
|
|
}
|
|
} else if (layer->base.tag == LayerBase_metrics + 1) {
|
|
if (property->path[3] == LayerMetrics_textSize || property->path[3] == LayerMetrics_selectedText) {
|
|
// Ignore.
|
|
} else if (property->path[3] == LayerMetrics_textColor) {
|
|
RfItem item = LayerMetrics_Type.fields[LayerMetrics_textColor].item;
|
|
uint32_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddObjectProperty(override, "textColor", ColorLookupPointer(value)->object2ID);
|
|
} else if (property->path[3] == LayerMetrics_iconColor) {
|
|
RfItem item = LayerMetrics_Type.fields[LayerMetrics_iconColor].item;
|
|
uint32_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddObjectProperty(override, "iconColor", ColorLookupPointer(value)->object2ID);
|
|
} else {
|
|
unhandled = true;
|
|
}
|
|
} else if (layer->base.tag == LayerBase_path + 1) {
|
|
if (property->path[3] == LayerPath_alpha) {
|
|
RfItem item = LayerPath_Type.fields[LayerPath_alpha].item;
|
|
int16_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "alpha", value);
|
|
} else if (property->path[3] == LayerPath_fills && property->path[4] == 0 && property->path[5] == PathFill_paint
|
|
&& property->path[6] == Paint_solid && property->path[7] == PaintSolid_color) {
|
|
RfItem item = PaintSolid_Type.fields[PaintSolid_color].item;
|
|
uint32_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddObjectProperty(override, "fills_0_paint", ColorLookupPointer(value)->object2ID);
|
|
ObjectAddIntegerProperty(override, "fills_count", 1);
|
|
} else {
|
|
unhandled = true;
|
|
}
|
|
} else {
|
|
unhandled = true;
|
|
}
|
|
} else if (property->path[0] == (uint32_t) -2 && property->path[1] == 5) {
|
|
if (layer->base.tag == LayerBase_box + 1) {
|
|
if (property->path[2] == Rectangle8_l) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_l].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "offset0", value - layer->offset.l);
|
|
} else if (property->path[2] == Rectangle8_r) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_r].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "offset1", value - layer->offset.r);
|
|
} else if (property->path[2] == Rectangle8_t) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_t].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "offset2", value - layer->offset.t);
|
|
} else if (property->path[2] == Rectangle8_b) {
|
|
RfItem item = Rectangle8_Type.fields[Rectangle8_b].item;
|
|
int8_t value; item.type->op(&state.s, &item, &value);
|
|
ObjectAddIntegerProperty(override, "offset3", value - layer->offset.b);
|
|
}
|
|
} else {
|
|
unhandled = true;
|
|
}
|
|
} else {
|
|
unhandled = true;
|
|
}
|
|
|
|
assert(!unhandled);
|
|
|
|
#if 0
|
|
if (unhandled) {
|
|
fprintf(stderr, "\tunhandled on %s: ", LayerBase_Type.fields[layer->base.tag - 1].cName);
|
|
|
|
for (uintptr_t i = 0; property->path[i] != RF_PATH_TERMINATOR; i++) {
|
|
fprintf(stderr, "%d, ", property->path[i]);
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ActionExportDesigner2(void *cp) {
|
|
// TODO Merging identical layers and styles.
|
|
|
|
Object2 *objects = NULL;
|
|
uint64_t objectIDAllocator = 0;
|
|
char cPropertyName[PROPERTY_NAME_SIZE];
|
|
|
|
int y = 0;
|
|
|
|
// Colors.
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
Object2 object = { .type = OBJ_VAR_COLOR, .id = ++objectIDAllocator };
|
|
snprintf(object.cName, sizeof(object.cName), "%.*s", (int) styleSet.colors[i]->key.byteCount, (const char *) styleSet.colors[i]->key.buffer);
|
|
ObjectAddIntegerProperty(&object, "_graphX", (i % 10) * 180);
|
|
ObjectAddIntegerProperty(&object, "_graphY", (i / 10) * 100 + y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
ObjectAddColorProperty(&object, "color", styleSet.colors[i]->value);
|
|
ObjectAddIntegerProperty(&object, "isExported", 0);
|
|
arrput(objects, object);
|
|
styleSet.colors[i]->object2ID = object.id;
|
|
}
|
|
|
|
y += (arrlenu(styleSet.colors) / 10) * 100 + 200;
|
|
|
|
// Constants.
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.constants); i++) {
|
|
char value[64];
|
|
snprintf(value, sizeof(value), "%.*s", (int) styleSet.constants[i]->value.byteCount, (const char *) styleSet.constants[i]->value.buffer);
|
|
bool isColor = value[0] == '0' && value[1] == 'x';
|
|
Object2 object = { .type = isColor ? OBJ_VAR_COLOR : OBJ_VAR_INT, .id = ++objectIDAllocator };
|
|
snprintf(object.cName, sizeof(object.cName), "%.*s", (int) styleSet.constants[i]->key.byteCount, (const char *) styleSet.constants[i]->key.buffer);
|
|
ObjectAddIntegerProperty(&object, "_graphX", (i % 5) * 360);
|
|
ObjectAddIntegerProperty(&object, "_graphY", (i / 5) * 100 + y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
if (isColor) ObjectAddColorProperty(&object, "color", strtol(value, NULL, 0));
|
|
else ObjectAddIntegerProperty(&object, "value", strtol(value, NULL, 0));
|
|
ObjectAddIntegerProperty(&object, "isScaled", styleSet.constants[i]->scale);
|
|
ObjectAddIntegerProperty(&object, "isExported", 1);
|
|
arrput(objects, object);
|
|
}
|
|
|
|
y += (arrlenu(styleSet.constants) / 5) * 100 + 200;
|
|
|
|
// Styles.
|
|
|
|
int x0 = 180 * 10 + 200;
|
|
y = 0;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
int x = x0;
|
|
Style *style = styleSet.styles[i];
|
|
|
|
fprintf(stderr, "style: %.*s\n", (int) style->name.byteCount, (const char *) style->name.buffer);
|
|
|
|
Object2 layerGroup = { .type = OBJ_LAYER_GROUP, .id = ++objectIDAllocator };
|
|
Object2 metrics = { 0 }, textStyle = { 0 };
|
|
int32_t layerCount = 0;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(style->layers); i++) {
|
|
Layer *layer = LayerLookup(style->layers[i]);
|
|
bool addToLayerGroup = false;
|
|
Object2 object = { 0 };
|
|
|
|
if (layer->base.tag == LayerBase_box + 1) {
|
|
object.type = OBJ_LAYER_BOX, object.id = ++objectIDAllocator;
|
|
LayerBox *box = &layer->base.box;
|
|
ObjectAddIntegerProperty(&object, "borders0", box->borders.l);
|
|
ObjectAddIntegerProperty(&object, "borders1", box->borders.r);
|
|
ObjectAddIntegerProperty(&object, "borders2", box->borders.t);
|
|
ObjectAddIntegerProperty(&object, "borders3", box->borders.b);
|
|
ObjectAddIntegerProperty(&object, "corners0", box->corners.tl);
|
|
ObjectAddIntegerProperty(&object, "corners1", box->corners.tr);
|
|
ObjectAddIntegerProperty(&object, "corners2", box->corners.bl);
|
|
ObjectAddIntegerProperty(&object, "corners3", box->corners.br);
|
|
ObjectAddIntegerProperty(&object, "isBlurred", box->blurred);
|
|
ObjectAddIntegerProperty(&object, "autoCorners", box->autoCorners);
|
|
ObjectAddIntegerProperty(&object, "autoBorders", box->autoBorders);
|
|
ObjectAddIntegerProperty(&object, "shadowHiding", box->shadowHiding);
|
|
ObjectAddObjectProperty(&object, "mainPaint", ExportPaint2(&box->mainPaint, &x, y, &objects, &objectIDAllocator, layer));
|
|
ObjectAddObjectProperty(&object, "borderPaint", ExportPaint2(&box->borderPaint, &x, y, &objects, &objectIDAllocator, NULL));
|
|
addToLayerGroup = true;
|
|
} else if (layer->base.tag == LayerBase_text + 1) {
|
|
object.type = OBJ_LAYER_TEXT, object.id = ++objectIDAllocator;
|
|
ObjectAddObjectProperty(&object, "color", ColorLookupPointer(layer->base.text.color)->object2ID);
|
|
ObjectAddIntegerProperty(&object, "blur", layer->base.text.blur);
|
|
addToLayerGroup = true;
|
|
} else if (layer->base.tag == LayerBase_path + 1) {
|
|
object.type = OBJ_LAYER_PATH, object.id = ++objectIDAllocator;
|
|
LayerPath *path = &layer->base.path;
|
|
ObjectAddIntegerProperty(&object, "pathFillEvenOdd", path->evenOdd);
|
|
ObjectAddIntegerProperty(&object, "pathClosed", path->closed);
|
|
ObjectAddIntegerProperty(&object, "alpha", path->alpha);
|
|
ObjectAddIntegerProperty(&object, "points_count", arrlenu(path->points));
|
|
ObjectAddIntegerProperty(&object, "fills_count", arrlenu(path->fills));
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(path->points); i++) {
|
|
sprintf(cPropertyName, "points_%d_x0", (int) i);
|
|
ObjectAddFloatProperty(&object, cPropertyName, path->points[i].x0);
|
|
sprintf(cPropertyName, "points_%d_y0", (int) i);
|
|
ObjectAddFloatProperty(&object, cPropertyName, path->points[i].y0);
|
|
sprintf(cPropertyName, "points_%d_x1", (int) i);
|
|
ObjectAddFloatProperty(&object, cPropertyName, path->points[i].x1);
|
|
sprintf(cPropertyName, "points_%d_y1", (int) i);
|
|
ObjectAddFloatProperty(&object, cPropertyName, path->points[i].y1);
|
|
sprintf(cPropertyName, "points_%d_x2", (int) i);
|
|
ObjectAddFloatProperty(&object, cPropertyName, path->points[i].x2);
|
|
sprintf(cPropertyName, "points_%d_y2", (int) i);
|
|
ObjectAddFloatProperty(&object, cPropertyName, path->points[i].y2);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(path->fills); i++) {
|
|
sprintf(cPropertyName, "fills_%d_paint", (int) i);
|
|
ObjectAddObjectProperty(&object, cPropertyName, ExportPaint2(&path->fills[i].paint, &x, y, &objects, &objectIDAllocator, NULL));
|
|
sprintf(cPropertyName, "fills_%d_mode", (int) i);
|
|
ObjectAddObjectProperty(&object, cPropertyName, ExportFillMode2(&path->fills[i].mode, &x, y, &objects, &objectIDAllocator));
|
|
}
|
|
|
|
addToLayerGroup = true;
|
|
} else if (layer->base.tag == LayerBase_metrics + 1) {
|
|
LayerMetrics *m = &layer->base.metrics;
|
|
assert(!m->globalOffset.l && !m->globalOffset.r && !m->globalOffset.t && !m->globalOffset.b);
|
|
LayerMetrics *inherit = NULL;
|
|
object.type = OBJ_VAR_TEXT_STYLE, object.id = ++objectIDAllocator;
|
|
|
|
if (m->inheritText.byteCount) {
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
Style *style = styleSet.styles[i];
|
|
|
|
if (m->inheritText.byteCount == style->name.byteCount
|
|
&& 0 == memcmp(m->inheritText.buffer, style->name.buffer, style->name.byteCount)) {
|
|
inherit = &LayerLookup(style->layers[0])->base.metrics;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (inherit) {
|
|
ObjectAddObjectProperty(&object, "textColor", ColorLookupPointer(inherit->textColor)->object2ID);
|
|
ObjectAddObjectProperty(&object, "selectedBackground", ColorLookupPointer(inherit->selectedBackground)->object2ID);
|
|
ObjectAddObjectProperty(&object, "selectedText", ColorLookupPointer(inherit->selectedText)->object2ID);
|
|
ObjectAddIntegerProperty(&object, "textSize", inherit->textSize);
|
|
ObjectAddIntegerProperty(&object, "fontWeight", inherit->fontWeight);
|
|
ObjectAddIntegerProperty(&object, "isItalic", inherit->italic);
|
|
ObjectAddIntegerProperty(&object, "fontFamily", inherit->fontFamily == FONT_FAMILY_MONO ? 0xFFFD : 0xFFFF);
|
|
} else {
|
|
ObjectAddObjectProperty(&object, "textColor", ColorLookupPointer(m->textColor)->object2ID);
|
|
ObjectAddObjectProperty(&object, "selectedBackground", ColorLookupPointer(m->selectedBackground)->object2ID);
|
|
ObjectAddObjectProperty(&object, "selectedText", ColorLookupPointer(m->selectedText)->object2ID);
|
|
ObjectAddIntegerProperty(&object, "textSize", m->textSize);
|
|
ObjectAddIntegerProperty(&object, "fontWeight", m->fontWeight);
|
|
ObjectAddIntegerProperty(&object, "isItalic", m->italic);
|
|
ObjectAddIntegerProperty(&object, "fontFamily", m->fontFamily == FONT_FAMILY_MONO ? 0xFFFD : 0xFFFF);
|
|
}
|
|
|
|
ObjectAddIntegerProperty(&object, "iconSize", m->iconSize);
|
|
ObjectAddObjectProperty(&object, "iconColor", ColorLookupPointer(m->iconColor)->object2ID);
|
|
textStyle = object;
|
|
|
|
metrics.type = OBJ_LAYER_METRICS, metrics.id = ++objectIDAllocator;
|
|
ObjectAddIntegerProperty(&metrics, "clipEnabled", m->clipEnabled);
|
|
ObjectAddIntegerProperty(&metrics, "wrapText", m->wrapText);
|
|
ObjectAddIntegerProperty(&metrics, "ellipsis", m->ellipsis);
|
|
ObjectAddIntegerProperty(&metrics, "insets0", m->insets.l);
|
|
ObjectAddIntegerProperty(&metrics, "insets1", m->insets.r);
|
|
ObjectAddIntegerProperty(&metrics, "insets2", m->insets.t);
|
|
ObjectAddIntegerProperty(&metrics, "insets3", m->insets.b);
|
|
ObjectAddIntegerProperty(&metrics, "clipInsets0", m->clipInsets.l);
|
|
ObjectAddIntegerProperty(&metrics, "clipInsets1", m->clipInsets.r);
|
|
ObjectAddIntegerProperty(&metrics, "clipInsets2", m->clipInsets.t);
|
|
ObjectAddIntegerProperty(&metrics, "clipInsets3", m->clipInsets.b);
|
|
ObjectAddIntegerProperty(&metrics, "preferredWidth", m->preferredSize.width);
|
|
ObjectAddIntegerProperty(&metrics, "preferredHeight", m->preferredSize.height);
|
|
ObjectAddIntegerProperty(&metrics, "minimumWidth", m->minimumSize.width);
|
|
ObjectAddIntegerProperty(&metrics, "minimumHeight", m->minimumSize.height);
|
|
ObjectAddIntegerProperty(&metrics, "maximumWidth", m->maximumSize.width);
|
|
ObjectAddIntegerProperty(&metrics, "maximumHeight", m->maximumSize.height);
|
|
ObjectAddIntegerProperty(&metrics, "gapMajor", m->gaps.major);
|
|
ObjectAddIntegerProperty(&metrics, "gapMinor", m->gaps.minor);
|
|
ObjectAddIntegerProperty(&metrics, "gapWrap", m->gaps.wrap);
|
|
ObjectAddIntegerProperty(&metrics, "cursor", m->cursor);
|
|
ObjectAddIntegerProperty(&metrics, "horizontalTextAlign", m->textHorizontalAlign + 1);
|
|
ObjectAddIntegerProperty(&metrics, "verticalTextAlign", m->textVerticalAlign + 1);
|
|
ObjectAddIntegerProperty(&metrics, "_graphX", x);
|
|
ObjectAddIntegerProperty(&metrics, "_graphY", y);
|
|
ObjectAddIntegerProperty(&metrics, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&metrics, "_graphH", 60);
|
|
arrput(objects, metrics);
|
|
x += 100;
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
ObjectAddIntegerProperty(&object, "_graphX", x);
|
|
ObjectAddIntegerProperty(&object, "_graphY", y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
arrput(objects, object);
|
|
x += 100;
|
|
|
|
uint64_t previousOverrideID = object.id;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(layer->sequences); i++) {
|
|
Sequence *s = layer->sequences[i];
|
|
assert(arrlenu(s->keyframes) == 1);
|
|
Keyframe *keyframe = s->keyframes[0];
|
|
|
|
#if 0
|
|
char buffer[256];
|
|
snprintf(buffer, sizeof(buffer), "%s%s%s%s%s%s%s%s%s%s",
|
|
((StringOption *) PrimaryState_Type.fields[s->primaryState].item.options)->string,
|
|
s->flagFocused ? " (focused)" : "",
|
|
s->flagChecked ? " (checked)" : "",
|
|
s->flagIndeterminate ? " (indeterminate)" : "",
|
|
s->flagDefault ? " (default)" : "",
|
|
s->flagItemFocus ? " (list item focus)" : "",
|
|
s->flagListFocus ? " (list focus)" : "",
|
|
s->flagBeforeEnter ? " (before enter)" : "",
|
|
s->flagAfterExit ? " (after exit)" : "",
|
|
s->flagSelected ? " (selected)" : "");
|
|
fprintf(stderr, "%.*s:%.*s:%s:%d\n", (int) style->name.byteCount, (char *) style->name.buffer,
|
|
(int) layer->name.byteCount, (char *) layer->name.buffer, buffer, s->duration);
|
|
#endif
|
|
|
|
uint32_t stateBits = 0;
|
|
if (s->flagFocused) stateBits |= THEME_STATE_FOCUSED;
|
|
if (s->flagChecked) stateBits |= THEME_STATE_CHECKED;
|
|
if (s->flagIndeterminate) stateBits |= THEME_STATE_INDETERMINATE;
|
|
if (s->flagDefault) stateBits |= THEME_STATE_DEFAULT_BUTTON;
|
|
if (s->flagItemFocus) stateBits |= THEME_STATE_FOCUSED_ITEM;
|
|
if (s->flagListFocus) stateBits |= THEME_STATE_LIST_FOCUSED;
|
|
if (s->flagBeforeEnter) stateBits |= THEME_STATE_BEFORE_ENTER;
|
|
if (s->flagAfterExit) stateBits |= THEME_STATE_AFTER_EXIT;
|
|
if (s->flagSelected) stateBits |= THEME_STATE_SELECTED;
|
|
|
|
Object2 override = { .type = object.type, .id = ++objectIDAllocator };
|
|
ObjectAddIntegerProperty(&override, "_graphX", x);
|
|
ObjectAddIntegerProperty(&override, "_graphY", y);
|
|
ObjectAddIntegerProperty(&override, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&override, "_graphH", 60);
|
|
ObjectAddObjectProperty(&override, "_parent", previousOverrideID);
|
|
ObjectAddIntegerProperty(&override, "_primaryState", s->primaryState);
|
|
ObjectAddIntegerProperty(&override, "_stateBits", stateBits);
|
|
ObjectAddIntegerProperty(&override, "_duration", s->duration);
|
|
AutoNameOverrideObject(&override, s->primaryState, stateBits);
|
|
|
|
bool addObject = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(keyframe->properties); i++) {
|
|
Property *property = &keyframe->properties[i];
|
|
|
|
if (layer->base.tag == LayerBase_box + 1 && property->path[0] == (uint32_t) -2 && property->path[1] == 8 && property->path[2] == 0
|
|
&& property->path[3] == 2 && (property->path[4] == 2 || property->path[4] == 3)) {
|
|
continue;
|
|
}
|
|
|
|
ExportProperty2(layer, property, &override);
|
|
addObject = true;
|
|
}
|
|
|
|
if (addObject) {
|
|
arrput(objects, override);
|
|
previousOverrideID = override.id;
|
|
x += 100;
|
|
} else {
|
|
arrfree(override.properties);
|
|
}
|
|
}
|
|
|
|
if (addToLayerGroup) {
|
|
sprintf(cPropertyName, "layers_%d_layer", layerCount);
|
|
ObjectAddObjectProperty(&layerGroup, cPropertyName, previousOverrideID);
|
|
sprintf(cPropertyName, "layers_%d_offset0", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->offset.l);
|
|
sprintf(cPropertyName, "layers_%d_offset1", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->offset.r);
|
|
sprintf(cPropertyName, "layers_%d_offset2", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->offset.t);
|
|
sprintf(cPropertyName, "layers_%d_offset3", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->offset.b);
|
|
sprintf(cPropertyName, "layers_%d_position0", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->position.l);
|
|
sprintf(cPropertyName, "layers_%d_position1", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->position.r);
|
|
sprintf(cPropertyName, "layers_%d_position2", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->position.t);
|
|
sprintf(cPropertyName, "layers_%d_position3", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->position.b);
|
|
sprintf(cPropertyName, "layers_%d_mode", layerCount);
|
|
ObjectAddIntegerProperty(&layerGroup, cPropertyName, layer->mode);
|
|
layerCount++;
|
|
}
|
|
}
|
|
|
|
if (layerCount) {
|
|
Object2 object = layerGroup;
|
|
ObjectAddIntegerProperty(&object, "_graphX", x);
|
|
ObjectAddIntegerProperty(&object, "_graphY", y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
ObjectAddIntegerProperty(&object, "layers_count", layerCount);
|
|
arrput(objects, object);
|
|
x += 100;
|
|
} else {
|
|
arrfree(layerGroup.properties);
|
|
layerGroup.id = 0;
|
|
}
|
|
|
|
{
|
|
Object2 object = { .type = OBJ_STYLE, .id = ++objectIDAllocator };
|
|
snprintf(object.cName, sizeof(object.cName), "%.*s", (int) style->name.byteCount, (const char *) style->name.buffer);
|
|
ObjectAddIntegerProperty(&object, "_graphX", x);
|
|
ObjectAddIntegerProperty(&object, "_graphY", y);
|
|
ObjectAddIntegerProperty(&object, "_graphW", 80);
|
|
ObjectAddIntegerProperty(&object, "_graphH", 60);
|
|
ObjectAddIntegerProperty(&object, "isPublic", style->publicStyle);
|
|
ObjectAddIntegerProperty(&object, "headerID", style->id);
|
|
ObjectAddObjectProperty(&object, "appearance", layerGroup.id);
|
|
ObjectAddObjectProperty(&object, "metrics", metrics.id);
|
|
ObjectAddObjectProperty(&object, "textStyle", textStyle.id);
|
|
arrput(objects, object);
|
|
x += 100;
|
|
}
|
|
|
|
y += 200;
|
|
}
|
|
|
|
// Saving.
|
|
|
|
FILE *f = fopen("bin/designer2.dat", "wb");
|
|
uint32_t version = 1;
|
|
fwrite(&version, 1, sizeof(uint32_t), f);
|
|
uint32_t objectCount = arrlenu(objects);
|
|
fwrite(&objectCount, 1, sizeof(uint32_t), f);
|
|
fwrite(&objectIDAllocator, 1, sizeof(uint64_t), f);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(objects); i++) {
|
|
Object2 copy = objects[i];
|
|
uint32_t propertyCount = arrlenu(copy.properties);
|
|
copy.properties = NULL;
|
|
fwrite(©, 1, sizeof(Object2), f);
|
|
fwrite(&propertyCount, 1, sizeof(uint32_t), f);
|
|
fwrite(objects[i].properties, 1, sizeof(Property2) * propertyCount, f);
|
|
arrfree(objects[i].properties);
|
|
assert(objects[i].id);
|
|
}
|
|
|
|
fclose(f);
|
|
arrfree(objects);
|
|
}
|
|
|
|
// ------------------- Preview canvas -------------------
|
|
|
|
float Smooth(float x) {
|
|
return x * x * (3 - 2 * x);
|
|
}
|
|
|
|
void ApplyKeyframeOverrides(const Keyframe *keyframe, const PathToOffset *pathToOffsetList, const RfData data) {
|
|
for (uintptr_t j = 0; j < arrlenu(keyframe->properties); j++) {
|
|
const Property *property = keyframe->properties + j;
|
|
bool found = false;
|
|
|
|
for (uintptr_t k = 0; k < arrlenu(pathToOffsetList); k++) {
|
|
const PathToOffset *pathToOffset = pathToOffsetList + k;
|
|
|
|
if (!ArePathsEqual(pathToOffset->path, property->path + 1)) {
|
|
continue;
|
|
}
|
|
|
|
found = true;
|
|
|
|
RfItem item;
|
|
Keyframe *previousKeyframe = selected.keyframe;
|
|
selected.keyframe = (Keyframe *) keyframe; // HACK!
|
|
void *source = ResolveDataObject((RfPath *) property->path, &item);
|
|
selected.keyframe = previousKeyframe;
|
|
void *destination = (uint8_t *) data.buffer + pathToOffset->offset;
|
|
|
|
if (pathToOffset->offset + item.byteCount > data.byteCount) {
|
|
break;
|
|
}
|
|
|
|
if (item.type->op == StyleColorOp) {
|
|
*(uint32_t *) destination = ColorLookup(*(uint32_t *) source);
|
|
} else {
|
|
memcpy(destination, source, item.byteCount);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// If it wasn't found, that's fine.
|
|
// e.g. deleting gradient stop, but a keyframe still has an override.
|
|
(void) found;
|
|
}
|
|
}
|
|
|
|
void AnimatingValueCalculate(AnimatingValue *value) {
|
|
float progress = value->duration ? Smooth((float) value->elapsed / value->duration) : 1;
|
|
|
|
if (value->type == ANIMATING_VALUE_TYPE_COLOR) {
|
|
uint32_t from = value->from.u32;
|
|
float fr = UI_COLOR_RED_F(from);
|
|
float fg = UI_COLOR_GREEN_F(from);
|
|
float fb = UI_COLOR_BLUE_F(from);
|
|
float fa = UI_COLOR_ALPHA_F(from);
|
|
uint32_t to = value->to.u32;
|
|
float tr = UI_COLOR_RED_F(to);
|
|
float tg = UI_COLOR_GREEN_F(to);
|
|
float tb = UI_COLOR_BLUE_F(to);
|
|
float ta = UI_COLOR_ALPHA_F(to);
|
|
if (!fa) fr = tr, fg = tg, fb = tb;
|
|
if (!ta) tr = fr, tg = fg, tb = fb;
|
|
float dr = (tr - fr) * progress + fr;
|
|
float dg = (tg - fg) * progress + fg;
|
|
float db = (tb - fb) * progress + fb;
|
|
float da = (ta - fa) * progress + fa;
|
|
value->from.u32 = UI_COLOR_FROM_RGBA_F(dr, dg, db, da);
|
|
} else if (value->type == ANIMATING_VALUE_TYPE_I8) {
|
|
value->from.i8 = (value->to.i8 - value->from.i8) * progress + value->from.i8;
|
|
} else if (value->type == ANIMATING_VALUE_TYPE_I16) {
|
|
value->from.i16 = (value->to.i16 - value->from.i16) * progress + value->from.i16;
|
|
} else if (value->type == ANIMATING_VALUE_TYPE_FLOAT) {
|
|
value->from.f32 = (value->to.f32 - value->from.f32) * progress + value->from.f32;
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
SequenceStateSelector GetCurrentSequenceStateSelector() {
|
|
SequenceStateSelector s = { 0 };
|
|
s.primary = previewPrimaryState;
|
|
s.focused = previewStateFocused->e.flags & UI_BUTTON_CHECKED;
|
|
s.checked = previewStateChecked->e.flags & UI_BUTTON_CHECKED;
|
|
s.indeterminate = previewStateIndeterminate->e.flags & UI_BUTTON_CHECKED;
|
|
s._default = previewStateDefault->e.flags & UI_BUTTON_CHECKED;
|
|
s.itemFocus = previewStateItemFocus->e.flags & UI_BUTTON_CHECKED;
|
|
s.listFocus = previewStateListFocus->e.flags & UI_BUTTON_CHECKED;
|
|
s.selected = previewStateSelected->e.flags & UI_BUTTON_CHECKED;
|
|
s.enter = previewStateBeforeEnter->e.flags & UI_BUTTON_CHECKED;
|
|
s.exit = previewStateAfterExit->e.flags & UI_BUTTON_CHECKED;
|
|
return s;
|
|
}
|
|
|
|
bool SequenceMatchesPreviewState(Sequence *sequence, SequenceStateSelector selector) {
|
|
return (sequence->primaryState == selector.primary || sequence->primaryState == PRIMARY_STATE_ANY)
|
|
&& (!sequence->flagFocused || selector.focused)
|
|
&& (!sequence->flagChecked || selector.checked)
|
|
&& (!sequence->flagIndeterminate || selector.indeterminate)
|
|
&& (!sequence->flagDefault || selector._default)
|
|
&& (!sequence->flagItemFocus || selector.itemFocus)
|
|
&& (!sequence->flagListFocus || selector.listFocus)
|
|
&& (!sequence->flagBeforeEnter || selector.enter)
|
|
&& (!sequence->flagAfterExit || selector.exit)
|
|
&& (!sequence->flagSelected || selector.selected);
|
|
}
|
|
|
|
void ApplySequenceOverrides(Layer *layer, PathToOffset *pathToOffsetList, RfData data, SequenceStateSelector selector) {
|
|
for (uintptr_t j = 0; j < arrlenu(layer->sequences); j++) {
|
|
if (SequenceMatchesPreviewState(layer->sequences[j], selector)) {
|
|
for (uintptr_t k = 0; k < arrlenu(layer->sequences[j]->keyframes); k++) {
|
|
Layer *previousLayer = selected.layer;
|
|
selected.layer = layer; // HACK!
|
|
ApplyKeyframeOverrides(layer->sequences[j]->keyframes[k], pathToOffsetList, data);
|
|
selected.layer = previousLayer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void *PrepareThemeDataForLayer(EsBuffer *themeData, EsPainter *themePainter, Layer *layer, bool applyOverrides, uintptr_t index, UIPainter *painter) {
|
|
PathToOffset *pathToOffsetList = NULL;
|
|
RfData data = ExportToGrowableBuffer(&Layer_Type, sizeof(Layer), NULL, layer, &pathToOffsetList);
|
|
|
|
if (applyOverrides) {
|
|
Keyframe *keyframe = NULL;
|
|
|
|
if (layer == selected.layer && selected.keyframe) {
|
|
keyframe = selected.keyframe;
|
|
}
|
|
|
|
if (keyframe) {
|
|
ApplyKeyframeOverrides(keyframe, pathToOffsetList, data);
|
|
} else {
|
|
ApplySequenceOverrides(layer, pathToOffsetList, data, currentStateSelector);
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(animatingValues); j++) {
|
|
if (animatingValues[j].layer != index) {
|
|
continue;
|
|
}
|
|
|
|
AnimatingValue value = animatingValues[j];
|
|
uint8_t *destination = (uint8_t *) data.buffer + value.offset;
|
|
AnimatingValueCalculate(&value);
|
|
|
|
if (value.type == ANIMATING_VALUE_TYPE_COLOR) {
|
|
*(uint32_t *) destination = value.from.u32;
|
|
} else if (value.type == ANIMATING_VALUE_TYPE_I8) {
|
|
*(int8_t *) destination = value.from.i8;
|
|
} else if (value.type == ANIMATING_VALUE_TYPE_I16) {
|
|
*(int16_t *) destination = value.from.i16;
|
|
} else if (value.type == ANIMATING_VALUE_TYPE_FLOAT) {
|
|
*(float *) destination = value.from.f32;
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ExportFreePathToOffsetList(pathToOffsetList);
|
|
|
|
themeData->in = data.buffer;
|
|
themeData->bytes = data.byteCount;
|
|
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;
|
|
return data.buffer;
|
|
}
|
|
|
|
void DrawStyle(UIPainter *painter, UIRectangle generalBounds, UIRectangle *globalOffset, float scale, UIRectangle *opaqueRegion, bool applyOverrides, Style *style) {
|
|
for (uintptr_t i = 0; i < arrlenu(style->layers); i++) {
|
|
Layer *layer = LayerLookup(style->layers[i]);
|
|
EsPaintTarget paintTarget = { 0 };
|
|
EsPainter themePainter = { 0 };
|
|
themePainter.target = &paintTarget;
|
|
EsBuffer themeData = { 0 };
|
|
void *dataBuffer = PrepareThemeDataForLayer(&themeData, &themePainter, layer, applyOverrides, i, painter);
|
|
|
|
if (i) {
|
|
UIRectangle bounds = UIRectangleAdd(generalBounds, *globalOffset);
|
|
EsRectangle bounds2 = { bounds.l, bounds.r, bounds.t, bounds.b };
|
|
ThemeDrawLayer(&themePainter, bounds2, &themeData, scale, *(EsRectangle *) opaqueRegion);
|
|
} else {
|
|
EsBufferRead(&themeData, sizeof(ThemeLayer));
|
|
globalOffset->l = 0;
|
|
globalOffset->r = 0;
|
|
globalOffset->t = 0;
|
|
globalOffset->b = 0;
|
|
}
|
|
|
|
free(dataBuffer);
|
|
}
|
|
|
|
if (editPoints->e.flags & UI_BUTTON_CHECKED) {
|
|
for (uintptr_t i = 0; i < arrlenu(style->layers); i++) {
|
|
Layer *_layer = LayerLookup(style->layers[i]);
|
|
|
|
if (_layer->base.tag != LayerBase_path + 1 || _layer != selected.layer) {
|
|
continue;
|
|
}
|
|
|
|
EsPaintTarget paintTarget = { 0 };
|
|
EsPainter themePainter = { 0 };
|
|
themePainter.target = &paintTarget;
|
|
EsBuffer themeData = { 0 };
|
|
void *dataBuffer = PrepareThemeDataForLayer(&themeData, &themePainter, _layer, applyOverrides, i, painter);
|
|
|
|
const ThemeLayer *layer = (const ThemeLayer *) EsBufferRead(&themeData, sizeof(ThemeLayer));
|
|
UIRectangle _bounds = UIRectangleAdd(generalBounds, *globalOffset), bounds;
|
|
bounds.l = _bounds.l + (int) (scale * layer->offset.l) + THEME_RECT_WIDTH(_bounds) * layer->position.l / 100;
|
|
bounds.r = _bounds.l + (int) (scale * layer->offset.r) + THEME_RECT_WIDTH(_bounds) * layer->position.r / 100;
|
|
bounds.t = _bounds.t + (int) (scale * layer->offset.t) + THEME_RECT_HEIGHT(_bounds) * layer->position.t / 100;
|
|
bounds.b = _bounds.t + (int) (scale * layer->offset.b) + THEME_RECT_HEIGHT(_bounds) * layer->position.b / 100;
|
|
|
|
const ThemeLayerPath *path = (const ThemeLayerPath *) EsBufferRead(&themeData, sizeof(ThemeLayerPath));
|
|
const float *points = (const float *) EsBufferRead(&themeData, sizeof(float) * 6 * path->pointCount);
|
|
|
|
#if 0
|
|
for (uintptr_t i = 0; i < path->pointCount * 6; i += 2) {
|
|
intptr_t k = (i / 2) % 3;
|
|
if (k == 0) continue;
|
|
float x = points[i + 0], y = points[i + 1];
|
|
intptr_t m = k == 2 ? (i + 2) : (i - 2);
|
|
if (m < 0) m += path->pointCount * 6;
|
|
if (m == path->pointCount * 6) m = 0;
|
|
float mx = points[m + 0], my = points[m + 1];
|
|
|
|
x = UI_RECT_WIDTH(bounds) * x / 100.0f + bounds.l;
|
|
y = UI_RECT_HEIGHT(bounds) * y / 100.0f + bounds.t;
|
|
mx = UI_RECT_WIDTH(bounds) * mx / 100.0f + bounds.l;
|
|
my = UI_RECT_HEIGHT(bounds) * my / 100.0f + bounds.t;
|
|
|
|
UIDrawLine(painter, x, y, mx, my, 0xFF000000);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < path->pointCount * 6; i += 2) {
|
|
intptr_t k = (i / 2) % 3;
|
|
if (k == 0) continue;
|
|
float x = points[i + 0], y = points[i + 1];
|
|
|
|
x = UI_RECT_WIDTH(bounds) * x / 100.0f + bounds.l;
|
|
y = UI_RECT_HEIGHT(bounds) * y / 100.0f + bounds.t;
|
|
|
|
UIDrawRectangle(painter, UI_RECT_4(x - 6, x + 6, y - 6, y + 6),
|
|
0xFFE0E0E0, 0xFFA8A8A8, UI_RECT_1(1));
|
|
}
|
|
#endif
|
|
|
|
for (uintptr_t i = 0; i < path->pointCount * 6; i += 2) {
|
|
intptr_t k = (i / 2) % 3;
|
|
if (k != 0) continue;
|
|
float x = points[i + 0], y = points[i + 1];
|
|
|
|
x = UI_RECT_WIDTH(bounds) * x / 100.0f + bounds.l;
|
|
y = UI_RECT_HEIGHT(bounds) * y / 100.0f + bounds.t;
|
|
|
|
UIDrawRectangle(painter, UI_RECT_4(x - 6, x + 6, y - 6, y + 6),
|
|
0xFFFFFFFF, 0xFFA8A8A8, UI_RECT_1(1));
|
|
}
|
|
|
|
free(dataBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
#define CANVAS_DRAW_BOUNDS() \
|
|
float scale = previewScale->position * 4 + 1; \
|
|
int drawX = 100 + elementCanvas->bounds.l; \
|
|
int drawY = 100 + elementCanvas->bounds.t; \
|
|
int drawWidth = 1000 * previewWidth->position * scale; \
|
|
int drawHeight = 1000 * previewHeight->position * scale
|
|
|
|
if (message == UI_MSG_PAINT) {
|
|
UIPainter *painter = (UIPainter *) dp;
|
|
uint32_t background;
|
|
UIColorToRGB(previewBackgroundColor->hue, previewBackgroundColor->saturation, previewBackgroundColor->value, &background);
|
|
UIDrawBlock(painter, element->bounds, background | 0xFF000000);
|
|
|
|
CANVAS_DRAW_BOUNDS();
|
|
|
|
UIRectangle generalBounds = UI_RECT_4(drawX, drawX + drawWidth, drawY, drawY + drawHeight);
|
|
|
|
if (!selected.style) {
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
UIRectangle iBounds = UIRectangleAdd(generalBounds, UI_RECT_1I(-10));
|
|
|
|
if (UIRectangleContains(iBounds, element->window->cursorX, element->window->cursorY)) {
|
|
UIDrawString(painter, UI_RECT_4(element->bounds.l, element->bounds.r, element->bounds.t, element->bounds.t + 25),
|
|
styleSet.styles[i]->name.buffer, styleSet.styles[i]->name.byteCount, 0x000000, UI_ALIGN_LEFT, NULL);
|
|
UIDrawBlock(painter, iBounds, 0xFFA2A0A4);
|
|
}
|
|
|
|
UIRectangle opaqueRegion = { 0 };
|
|
UIRectangle globalOffset = { 0 };
|
|
DrawStyle(painter, generalBounds, &globalOffset, scale, &opaqueRegion, false, styleSet.styles[i]);
|
|
|
|
if (generalBounds.r + drawWidth > elementCanvas->bounds.r - 100) {
|
|
generalBounds.l = 100 + elementCanvas->bounds.l;
|
|
generalBounds.r = generalBounds.l + drawWidth;
|
|
generalBounds.t += drawHeight + 20;
|
|
generalBounds.b += drawHeight + 20;
|
|
} else {
|
|
generalBounds.l += drawWidth + 20;
|
|
generalBounds.r += drawWidth + 20;
|
|
}
|
|
}
|
|
|
|
// UIDrawString(painter, element->bounds, "(select a style to preview it)", -1, 0x000000, UI_ALIGN_CENTER, NULL);
|
|
return 0;
|
|
}
|
|
|
|
if (!arrlenu(selected.style->layers)) {
|
|
UIDrawString(painter, element->bounds, "(selected style has no layers)", -1, 0x000000, UI_ALIGN_CENTER, NULL);
|
|
return 0;
|
|
}
|
|
|
|
if (previewShowGuides->e.flags & UI_BUTTON_CHECKED) {
|
|
UIDrawBlock(painter, UIRectangleAdd(generalBounds, UI_RECT_1I(-2)), 0xFFA2A0A4);
|
|
UIDrawBlock(painter, UIRectangleAdd(generalBounds, UI_RECT_1I(0)), 0xFFC2C0C4);
|
|
}
|
|
|
|
Rectangle8 opaqueInsets = StyleCalculateOpaqueInsets(selected.style->layers);
|
|
UIRectangle opaqueRegion = { 0 };
|
|
|
|
if (opaqueInsets.l != 0x7F && opaqueInsets.r != 0x7F
|
|
&& opaqueInsets.t != 0x7F && opaqueInsets.b != 0x7F) {
|
|
opaqueRegion = UIRectangleAdd(generalBounds, UI_RECT_4(opaqueInsets.l * scale, -opaqueInsets.r * scale,
|
|
opaqueInsets.t * scale, -opaqueInsets.b * scale));
|
|
}
|
|
|
|
UIRectangle globalOffset = { 0 };
|
|
|
|
DrawStyle(painter, generalBounds, &globalOffset, scale, &opaqueRegion, true, selected.style);
|
|
|
|
if (previewShowComputed->e.flags & UI_BUTTON_CHECKED) {
|
|
Rectangle8 paintOutsets8 = StyleCalculatePaintOutsets(selected.style->layers);
|
|
UIRectangle paintOutsets = UI_RECT_4(-paintOutsets8.l * scale, paintOutsets8.r * scale, -paintOutsets8.t * scale, paintOutsets8.b * scale);
|
|
UIDrawBlock(painter, UIRectangleAdd(generalBounds, paintOutsets), 0xFF0000);
|
|
|
|
Rectangle8 opaqueInsets8 = StyleCalculateOpaqueInsets(selected.style->layers);
|
|
|
|
if (opaqueInsets8.l != 0x7F && opaqueInsets8.r != 0x7F && opaqueInsets8.t != 0x7F && opaqueInsets8.b != 0x7F) {
|
|
UIRectangle opaqueInsets = UI_RECT_4(opaqueInsets8.l * scale, -opaqueInsets8.r * scale, opaqueInsets8.t * scale, -opaqueInsets8.b * scale);
|
|
UIDrawBlock(painter, UIRectangleAdd(generalBounds, opaqueInsets), 0x00FF00);
|
|
}
|
|
}
|
|
|
|
{
|
|
char buffer[128];
|
|
snprintf(buffer, 128, "%dx%dpx at %d%% scale", (int) (1000 * previewWidth->position), (int) (1000 * previewHeight->position), (int) (100 * scale));
|
|
UIRectangle bounds = element->bounds;
|
|
bounds.b = bounds.t + 16;
|
|
UIDrawString(painter, bounds, buffer, -1, 0x000000, UI_ALIGN_LEFT, NULL);
|
|
}
|
|
} else if (message == UI_MSG_ANIMATE) {
|
|
uint64_t current = UIAnimateClock();
|
|
uint64_t delta = current - previewTransitionLastTime;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(animatingValues); i++) {
|
|
animatingValues[i].elapsed += delta;
|
|
|
|
if (animatingValues[i].elapsed >= animatingValues[i].duration) {
|
|
animatingValues[i].elapsed = animatingValues[i].duration;
|
|
}
|
|
}
|
|
|
|
previewTransitionLastTime = current;
|
|
UIElementRepaint(element, NULL);
|
|
} else if (message == UI_MSG_MOUSE_MOVE) {
|
|
if (!selected.style) {
|
|
UIElementRepaint(element, NULL);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void PreviewTransitionInvoke(void *_unused) {
|
|
previewTransition->e.flags ^= UI_BUTTON_CHECKED;
|
|
|
|
if (previewTransition->e.flags & UI_BUTTON_CHECKED) {
|
|
UIElementAnimate(elementCanvas, false);
|
|
previewTransitionLastTime = UIAnimateClock();
|
|
} else {
|
|
UIElementAnimate(elementCanvas, true);
|
|
arrfree(animatingValues);
|
|
}
|
|
|
|
UIElementRepaint(&previewTransition->e, NULL);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void PreviewPreferredSizeInvoke(void *_unused) {
|
|
previewWidth->position = selected.style ? LayerLookup(selected.style->layers[0])->base.metrics.preferredSize.width / 1000.0f : 0.1f;
|
|
previewHeight->position = selected.style ? LayerLookup(selected.style->layers[0])->base.metrics.preferredSize.height / 1000.0f : 0.1f;
|
|
|
|
UIElementRefresh(&previewWidth->e);
|
|
UIElementRefresh(&previewHeight->e);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void PreviewFixAspectRatioInvoke(void *_unused) {
|
|
previewHeight->position = previewWidth->position;
|
|
previewFixAspectRatio->e.flags ^= UI_BUTTON_CHECKED;
|
|
previewHeight->e.flags ^= UI_ELEMENT_HIDE;
|
|
UIElementRefresh(&previewFixAspectRatio->e);
|
|
UIElementRefresh(&previewHeight->e);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void PreviewShowGuidesInvoke(void *_unused) {
|
|
previewShowGuides->e.flags ^= UI_BUTTON_CHECKED;
|
|
UIElementRefresh(&previewShowGuides->e);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void PreviewShowComputedInvoke(void *_unused) {
|
|
previewShowComputed->e.flags ^= UI_BUTTON_CHECKED;
|
|
UIElementRefresh(&previewShowComputed->e);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void EditPointsInvoke(void *_unused) {
|
|
editPoints->e.flags ^= UI_BUTTON_CHECKED;
|
|
UIElementRefresh(&editPoints->e);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
int PreviewSliderMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_VALUE_CHANGED) {
|
|
if (previewFixAspectRatio->e.flags & UI_BUTTON_CHECKED) {
|
|
previewHeight->position = previewWidth->position;
|
|
}
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void UpdateAnimationListWithSequence(Sequence *sequence, int layer, PathToOffset *pathToOffsetList, uint8_t *data) {
|
|
for (uintptr_t i = 0; i < arrlenu(sequence->keyframes); i++) {
|
|
Keyframe *keyframe = sequence->keyframes[i];
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(keyframe->properties); j++) {
|
|
Property *property = keyframe->properties + j;
|
|
|
|
uint16_t offset = 0xFFFF;
|
|
|
|
for (uintptr_t k = 0; k < arrlenu(pathToOffsetList); k++) {
|
|
const PathToOffset *pathToOffset = pathToOffsetList + k;
|
|
|
|
if (!ArePathsEqual(pathToOffset->path, property->path + 1)) {
|
|
continue;
|
|
}
|
|
|
|
offset = pathToOffset->offset;
|
|
assert(pathToOffset->offset < 0xFFFF);
|
|
break;
|
|
}
|
|
|
|
if (offset == 0xFFFF) {
|
|
continue;
|
|
}
|
|
|
|
// TODO Binary search.
|
|
|
|
uintptr_t point = 0;
|
|
bool found = false;
|
|
|
|
for (uintptr_t k = 0; k < arrlenu(animatingValues); k++) {
|
|
if (animatingValues[k].layer < layer || animatingValues[k].offset < offset) {
|
|
point = k + 1;
|
|
} else if (animatingValues[k].layer == layer && animatingValues[k].offset == offset) {
|
|
found = true;
|
|
point = k;
|
|
break;
|
|
}
|
|
}
|
|
|
|
AnimatingValue *value;
|
|
|
|
if (found) {
|
|
value = animatingValues + point;
|
|
value->elapsed = 0;
|
|
value->duration = sequence->duration;
|
|
} else {
|
|
AnimatingValue _value = { 0 };
|
|
_value.offset = offset;
|
|
_value.layer = layer;
|
|
_value.duration = sequence->duration;
|
|
arrins(animatingValues, point, _value);
|
|
value = animatingValues + point;
|
|
}
|
|
|
|
RfItem item;
|
|
void *source;
|
|
|
|
{
|
|
Keyframe *previousKeyframe = selected.keyframe;
|
|
selected.keyframe = keyframe; // HACK!
|
|
source = ResolveDataObject((RfPath *) property->path, &item);
|
|
selected.keyframe = previousKeyframe;
|
|
}
|
|
|
|
if (item.type == &StyleI8_Type) {
|
|
value->type = ANIMATING_VALUE_TYPE_I8;
|
|
if (!found) value->from.i8 = *(int8_t *) (data + offset);
|
|
value->to.i8 = *(int8_t *) source;
|
|
} else if (item.type == &StyleI16_Type) {
|
|
value->type = ANIMATING_VALUE_TYPE_I16;
|
|
if (!found) value->from.i16 = *(int16_t *) (data + offset);
|
|
value->to.i16 = *(int16_t *) source;
|
|
} else if (item.type == &StyleColor_Type) {
|
|
value->type = ANIMATING_VALUE_TYPE_COLOR;
|
|
if (!found) value->from.u32 = *(uint32_t *) (data + offset);
|
|
value->to.u32 = ColorLookup(*(uint32_t *) source);
|
|
} else if (item.type == &StyleFloat_Type) {
|
|
value->type = ANIMATING_VALUE_TYPE_FLOAT;
|
|
if (!found) value->from.f32 = *(float *) (data + offset);
|
|
value->to.f32 = *(float *) source;
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateAnimationList() {
|
|
if (!selected.style) return;
|
|
|
|
SequenceStateSelector oldStateSelector = currentStateSelector;
|
|
currentStateSelector = GetCurrentSequenceStateSelector();
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(animatingValues); i++) {
|
|
AnimatingValueCalculate(animatingValues + i);
|
|
animatingValues[i].type |= ANIMATING_VALUE_TYPE_UNUSED;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(selected.style->layers); i++) {
|
|
Layer *layer = LayerLookup(selected.style->layers[i]);
|
|
|
|
PathToOffset *pathToOffsetList = NULL;
|
|
RfData data = ExportToGrowableBuffer(&Layer_Type, sizeof(Layer), NULL, layer, &pathToOffsetList);
|
|
ApplySequenceOverrides(layer, pathToOffsetList, data, oldStateSelector);
|
|
|
|
PathToOffset *pathToOffsetList2 = NULL;
|
|
RfData data2 = ExportToGrowableBuffer(&Layer_Type, sizeof(Layer), NULL, layer, &pathToOffsetList2);
|
|
ApplySequenceOverrides(layer, pathToOffsetList2, data2, currentStateSelector);
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(layer->sequences); j++) {
|
|
Sequence *sequence = layer->sequences[j];
|
|
|
|
if (SequenceMatchesPreviewState(sequence, currentStateSelector)) {
|
|
Layer *previousLayer = selected.layer;
|
|
selected.layer = layer; // HACK!
|
|
UpdateAnimationListWithSequence(sequence, i, pathToOffsetList, (uint8_t *) data.buffer);
|
|
selected.layer = previousLayer;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(animatingValues); j++) {
|
|
AnimatingValue *value = animatingValues + j;
|
|
|
|
if ((value->type & ANIMATING_VALUE_TYPE_UNUSED) && value->layer == i) {
|
|
// Return values to base.
|
|
|
|
value->type &= ~ANIMATING_VALUE_TYPE_UNUSED;
|
|
value->elapsed = 0;
|
|
uint8_t *source = ((uint8_t *) data2.buffer + value->offset);
|
|
|
|
if (value->type == ANIMATING_VALUE_TYPE_I8) {
|
|
value->to.i8 = *(int8_t *) source;
|
|
} else if (value->type == ANIMATING_VALUE_TYPE_I16) {
|
|
value->to.i16 = *(int16_t *) source;
|
|
} else if (value->type == ANIMATING_VALUE_TYPE_COLOR) {
|
|
value->to.u32 = *(uint32_t *) source;
|
|
} else if (value->type == ANIMATING_VALUE_TYPE_FLOAT) {
|
|
value->to.f32 = *(float *) source;
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
ExportFreePathToOffsetList(pathToOffsetList);
|
|
ExportFreePathToOffsetList(pathToOffsetList2);
|
|
free(data.buffer);
|
|
free(data2.buffer);
|
|
}
|
|
}
|
|
|
|
void PreviewSetPrimaryState(void *cp) {
|
|
previewPrimaryState = (uintptr_t) cp;
|
|
|
|
UIElement *child = previewPrimaryStatePanel->e.children;
|
|
|
|
while (child) {
|
|
if (child->cp == cp) child->flags |= UI_BUTTON_CHECKED;
|
|
else child->flags &= ~UI_BUTTON_CHECKED;
|
|
child = child->next;
|
|
}
|
|
|
|
UIElementRefresh(&previewPrimaryStatePanel->e);
|
|
UpdateAnimationList();
|
|
}
|
|
|
|
int PreviewToggleState(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_CLICKED) {
|
|
element->flags ^= UI_BUTTON_CHECKED;
|
|
UIElementRefresh(element);
|
|
UpdateAnimationList();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PreviewChangeBackgroundColor(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_VALUE_CHANGED) {
|
|
UIElementRefresh(elementCanvas);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ------------------- Paths -------------------
|
|
|
|
void MakeUI(MakeUIState *state, RfItem *item, void *pointer) {
|
|
RfIterator iterator = { 0 };
|
|
iterator.includeRemovedFields = true;
|
|
iterator.s.op = RF_OP_COUNT;
|
|
item->type->op(&iterator.s, item, pointer);
|
|
iterator.s.op = RF_OP_ITERATE;
|
|
uint32_t count = iterator.index;
|
|
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
iterator.index = i;
|
|
item->type->op(&iterator.s, item, pointer);
|
|
if (iterator.s.error) return;
|
|
MakeUIState s = { 0 };
|
|
s.s = state->s;
|
|
s.index = i;
|
|
s.parent = state;
|
|
s.inKeyframe = state->inKeyframe;
|
|
s.recurse = true;
|
|
if (iterator.isRemoved) continue;
|
|
iterator.item.type->op(&s.s, &iterator.item, iterator.pointer);
|
|
if (s.recurse) MakeUI(&s, &iterator.item, iterator.pointer);
|
|
}
|
|
}
|
|
|
|
void MakeHeaderAndIndentUI(const char *format, RfState *state, RfItem *item, void *pointer) {
|
|
char buffer[64];
|
|
snprintf(buffer, sizeof(buffer), format, ((MakeUIState *) state)->index + 1);
|
|
|
|
#if 0
|
|
UILabelCreate(0, 0, buffer, -1);
|
|
UIPanel *subPanel = UIPanelCreate(0, UI_PANEL_EXPAND | UI_ELEMENT_PARENT_PUSH);
|
|
subPanel->gap = 5;
|
|
subPanel->border.l = 20;
|
|
subPanel->border.b = 20;
|
|
#endif
|
|
|
|
UIExpandPane *pane = UIExpandPaneCreate(0, UI_ELEMENT_PARENT_PUSH, buffer, -1, UI_PANEL_EXPAND);
|
|
pane->panel->gap = 5;
|
|
pane->panel->border.l = 20;
|
|
pane->panel->border.t = 5;
|
|
pane->panel->border.r = 5;
|
|
pane->panel->border.b = 20;
|
|
|
|
MakeUI((MakeUIState *) state, item, pointer);
|
|
UIParentPop();
|
|
|
|
((MakeUIState *) state)->recurse = false;
|
|
}
|
|
|
|
void InspectorUnsubscribe(UIElement *element) {
|
|
for (uintptr_t i = 0; i < arrlenu(inspectorSubscriptions); i++) {
|
|
if (inspectorSubscriptions[i] == element) {
|
|
arrdel(inspectorSubscriptions, i);
|
|
free(element->cp);
|
|
return;
|
|
}
|
|
}
|
|
|
|
assert(false);
|
|
}
|
|
|
|
void BuildPathForUI(MakeUIState *state, uint32_t last, UIElement *element) {
|
|
int count = 1;
|
|
|
|
if (state->inKeyframe) {
|
|
count++;
|
|
}
|
|
|
|
MakeUIState *s = state;
|
|
|
|
while (s) {
|
|
count++;
|
|
|
|
if (!s->parent && s->basePath) {
|
|
for (uintptr_t i = 0; s->basePath[i] != RF_PATH_TERMINATOR; i++) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
s = s->parent;
|
|
}
|
|
|
|
RfPath *path = (RfPath *) malloc((count + 1) * sizeof(uint32_t));
|
|
|
|
path->indices[count - 1] = last;
|
|
path->indices[count] = RF_PATH_TERMINATOR;
|
|
|
|
s = state;
|
|
count--;
|
|
|
|
while (s) {
|
|
path->indices[--count] = s->index;
|
|
|
|
if (!s->parent && s->basePath) {
|
|
for (uintptr_t i = 0; s->basePath[i] != RF_PATH_TERMINATOR; i++) {
|
|
path->indices[state->inKeyframe ? (i + 1) : i] = s->basePath[i];
|
|
count--;
|
|
}
|
|
}
|
|
|
|
s = s->parent;
|
|
}
|
|
|
|
if (state->inKeyframe) {
|
|
path->indices[--count] = PATH_IN_KEYFRAME;
|
|
}
|
|
|
|
assert(!count);
|
|
element->cp = path;
|
|
arrput(inspectorSubscriptions, element);
|
|
UIElementMessage(element, MSG_PROPERTY_CHANGED, 0, ResolveDataObject(path, NULL));
|
|
}
|
|
|
|
// ------------------- Inspector -------------------
|
|
|
|
int StyleI8Message(UIElement *element, UIMessage message, int di, void *dp) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == UI_MSG_VALUE_CHANGED) {
|
|
textbox->string = realloc(textbox->string, textbox->bytes + 1);
|
|
textbox->string[textbox->bytes] = 0;
|
|
int8_t newValue = atoi(textbox->string);
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath((uint32_t *) element->cp);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&StyleI8_Type, sizeof(newValue), NULL, &newValue);
|
|
mod.changeProperty.source = element;
|
|
ModApply(&mod);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
char buffer[16];
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
UITextboxReplace(textbox, buffer, snprintf(buffer, 16, "%d", *(int8_t *) dp), false);
|
|
UIElementRepaint(element, NULL);
|
|
} else if (message == UI_MSG_UPDATE && di == UI_UPDATE_FOCUSED) {
|
|
if (element->window->focused == element) {
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
} else {
|
|
StyleI8Message(element, MSG_PROPERTY_CHANGED, 0, ResolveDataObject((RfPath *) element->cp, NULL));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleI16Message(UIElement *element, UIMessage message, int di, void *dp) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == UI_MSG_VALUE_CHANGED) {
|
|
textbox->string = realloc(textbox->string, textbox->bytes + 1);
|
|
textbox->string[textbox->bytes] = 0;
|
|
int16_t newValue = atoi(textbox->string);
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath((uint32_t *) element->cp);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&StyleI16_Type, sizeof(newValue), NULL, &newValue);
|
|
mod.changeProperty.source = element;
|
|
ModApply(&mod);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
char buffer[16];
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
UITextboxReplace(textbox, buffer, snprintf(buffer, 16, "%d", *(int16_t *) dp), false);
|
|
UIElementRepaint(element, NULL);
|
|
} else if (message == UI_MSG_UPDATE && di == UI_UPDATE_FOCUSED) {
|
|
if (element->window->focused == element) {
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
} else {
|
|
StyleI16Message(element, MSG_PROPERTY_CHANGED, 0, ResolveDataObject((RfPath *) element->cp, NULL));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleFloatMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == UI_MSG_VALUE_CHANGED) {
|
|
textbox->string = realloc(textbox->string, textbox->bytes + 1);
|
|
textbox->string[textbox->bytes] = 0;
|
|
float newValue = strtof(textbox->string, NULL);
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath((uint32_t *) element->cp);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&StyleFloat_Type, sizeof(newValue), NULL, &newValue);
|
|
mod.changeProperty.source = element;
|
|
ModApply(&mod);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
char buffer[16];
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
UITextboxReplace(textbox, buffer, snprintf(buffer, 16, "%.2f", *(float *) dp), false);
|
|
UIElementRepaint(element, NULL);
|
|
} else if (message == UI_MSG_UPDATE && di == UI_UPDATE_FOCUSED) {
|
|
if (element->window->focused == element) {
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
} else {
|
|
StyleFloatMessage(element, MSG_PROPERTY_CHANGED, 0, ResolveDataObject((RfPath *) element->cp, NULL));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleStringMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == UI_MSG_VALUE_CHANGED) {
|
|
RfData data = { 0 };
|
|
data.buffer = textbox->string;
|
|
data.byteCount = textbox->bytes;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath((uint32_t *) element->cp);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&StyleString_Type, sizeof(data), NULL, &data);
|
|
mod.changeProperty.source = element;
|
|
ModApply(&mod);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
RfData *data = (RfData *) dp;
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
UITextboxReplace(textbox, data->buffer, data->byteCount, false);
|
|
UIElementRepaint(element, NULL);
|
|
} else if (message == UI_MSG_UPDATE && di == UI_UPDATE_FOCUSED && element->window->focused == element) {
|
|
textbox->carets[0] = 0;
|
|
textbox->carets[1] = textbox->bytes;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleBoolMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == UI_MSG_CLICKED) {
|
|
element->flags ^= UI_BUTTON_CHECKED;
|
|
bool newValue = element->flags & UI_BUTTON_CHECKED;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath((uint32_t *) element->cp);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&StyleBool_Type, sizeof(newValue), NULL, &newValue);
|
|
mod.changeProperty.source = element;
|
|
ModApply(&mod);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
if (*(bool *) dp) element->flags |= UI_BUTTON_CHECKED;
|
|
else element->flags &= ~UI_BUTTON_CHECKED;
|
|
UIElementRepaint(element, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleUnionButtonMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
// Also used for enums.
|
|
|
|
if (message == UI_MSG_CLICKED && (~element->flags & UI_BUTTON_CHECKED)) {
|
|
uint32_t newValue = (uint32_t) (uintptr_t) element->cp;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath((uint32_t *) element->parent->cp);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&rfU32, sizeof(newValue), NULL, &newValue);
|
|
ModApply(&mod);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void StyleEnumMenuItemInvoke(void *cp) {
|
|
uint32_t newValue = (uint32_t) (uintptr_t) cp;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath(menuPath);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&rfU32, sizeof(newValue), NULL, &newValue);
|
|
ModApply(&mod);
|
|
}
|
|
|
|
int StyleUnionButtonPanelMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
// Also used for enums.
|
|
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
UIElement *child = element->children;
|
|
|
|
while (child) {
|
|
if ((uint32_t) (uintptr_t) child->cp == *(uint32_t *) dp) {
|
|
child->flags |= UI_BUTTON_CHECKED;
|
|
} else {
|
|
child->flags &= ~UI_BUTTON_CHECKED;
|
|
}
|
|
|
|
UIElementRepaint(child, NULL);
|
|
child = child->next;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void RefreshAncestorsUntilInspector(UIElement *element) {
|
|
while (element != &panelInspector->e) {
|
|
element->clip = UI_RECT_1(0);
|
|
element = element->parent;
|
|
}
|
|
|
|
UIElementRefresh(&panelInspector->e);
|
|
}
|
|
|
|
int StyleChoiceButtonMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == UI_MSG_GET_WIDTH) {
|
|
return 240 * element->window->scale;
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
RfItem item;
|
|
void *_dp = ResolveDataObject((RfPath *) element->cp, &item);
|
|
assert(dp == _dp);
|
|
UIButton *button = (UIButton *) element;
|
|
free(button->label);
|
|
button->label = UIStringCopy(((StringOption *) item.type->fields[*(uint32_t *) dp].item.options)->string, -1);
|
|
UIElementRefresh(element);
|
|
} else if (message == UI_MSG_CLICKED) {
|
|
RfItem item;
|
|
ResolveDataObject((RfPath *) element->cp, &item);
|
|
UIMenu *menu = UIMenuCreate(element, 0);
|
|
menuPath = (uint32_t *) element->cp;
|
|
|
|
for (uintptr_t i = 0; i < item.type->fieldCount; i++) {
|
|
UIMenuAddItem(menu, 0, ((StringOption *) item.type->fields[i].item.options)->string, -1, StyleEnumMenuItemInvoke, (void *) i);
|
|
}
|
|
|
|
UIMenuShow(menu);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleUnionPanelMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
UIElementDestroyDescendents(element);
|
|
|
|
uint32_t *path = (uint32_t *) element->cp;
|
|
uintptr_t last = 0;
|
|
for (; path[last] != RF_PATH_TERMINATOR; last++);
|
|
assert(last && path[last - 1] == 0);
|
|
path[last - 1] = RF_PATH_TERMINATOR;
|
|
RfItem item;
|
|
void *_dp = ResolveDataObject((RfPath *) path, &item);
|
|
assert(dp == _dp);
|
|
|
|
MakeUIState state = { 0 };
|
|
state.s.op = OP_MAKE_UI;
|
|
state.index = *(uint32_t *) dp;
|
|
state.basePath = element->cp;
|
|
state.inKeyframe = state.basePath[0] == PATH_IN_KEYFRAME;
|
|
if (state.inKeyframe) state.basePath++;
|
|
if (state.index) state.index--;
|
|
RfField *field = item.type->fields + state.index;
|
|
RfItem fieldItem = field->item;
|
|
uint8_t *pointer = (uint8_t *) dp + field->offset;
|
|
UIParentPush(element);
|
|
fieldItem.type->op(&state.s, &fieldItem, pointer);
|
|
MakeUI(&state, &fieldItem, pointer);
|
|
UIParentPop();
|
|
|
|
path[last - 1] = 0; // Restore previous path.
|
|
RefreshAncestorsUntilInspector(element);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleArrayPanelMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
UIElementDestroyDescendents(element);
|
|
|
|
uint32_t *path = (uint32_t *) element->cp;
|
|
RfItem item;
|
|
void *_dp = ResolveDataObject((RfPath *) path, &item);
|
|
assert(dp == _dp);
|
|
|
|
RfArrayHeader *header = *(RfArrayHeader **) dp;
|
|
UIParentPush(element);
|
|
|
|
for (uintptr_t i = 0; header && i < header[-1].length; i++) {
|
|
MakeUIState state = { 0 };
|
|
state.s.op = OP_MAKE_UI;
|
|
state.index = i;
|
|
state.basePath = element->cp;
|
|
state.inKeyframe = state.basePath[0] == PATH_IN_KEYFRAME;
|
|
if (state.inKeyframe) state.basePath++;
|
|
RfItem *fieldItem = (RfItem *) item.options;
|
|
uint8_t *pointer = (*(uint8_t **) dp) + i * fieldItem->byteCount;
|
|
state.recurse = true;
|
|
fieldItem->type->op(&state.s, fieldItem, pointer);
|
|
if (state.recurse) MakeUI(&state, fieldItem, pointer);
|
|
}
|
|
|
|
UIParentPop();
|
|
RefreshAncestorsUntilInspector(element);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleArrayAddMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_CLICKED) {
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_array + 1;
|
|
mod.array.property.path = DuplicatePath((uint32_t *) ((UIElement *) element->cp)->cp);
|
|
RfItem item;
|
|
ResolveDataObject((RfPath *) mod.array.property.path, &item);
|
|
RfItem *elementItem = (RfItem *) item.options;
|
|
void *temporary = malloc(elementItem->byteCount);
|
|
memset(temporary, 0, elementItem->byteCount);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(elementItem->type, elementItem->byteCount, elementItem->options, temporary);
|
|
free(temporary);
|
|
ModApply(&mod);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleArrayDeleteMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_CLICKED) {
|
|
uint32_t *path = (uint32_t *) ((UIElement *) element->cp)->cp;
|
|
RfItem item;
|
|
RfArrayHeader *pointer = *(RfArrayHeader **) ResolveDataObject((RfPath *) path, &item);
|
|
|
|
if (pointer && pointer[-1].length) {
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_array + 1;
|
|
mod.array.property.path = DuplicatePath(path);
|
|
mod.array.isDelete = true;
|
|
ModApply(&mod);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int RemoveOverrideButtonMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_CLICKED) {
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_deleteOverride + 1;
|
|
mod.deleteOverride.property.path = DuplicatePath(element->cp);
|
|
ModApply(&mod);
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
if (dp == temporaryOverride) {
|
|
element->flags &= ~UI_ELEMENT_DISABLED;
|
|
} else {
|
|
element->flags |= UI_ELEMENT_DISABLED;
|
|
}
|
|
|
|
UIElementRepaint(element, NULL);
|
|
} else if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MakeOverrideButton(UIElement *parent, RfState *state, uint32_t last) {
|
|
if (!((MakeUIState *) state)->inKeyframe) return;
|
|
UIButton *removeOverride = UIButtonCreate(parent, UI_BUTTON_SMALL, "X", -1);
|
|
removeOverride->e.messageUser = RemoveOverrideButtonMessage;
|
|
BuildPathForUI((MakeUIState *) state, last, &removeOverride->e);
|
|
}
|
|
|
|
void StyleColorMenuItemInvoke(void *cp) {
|
|
uint32_t newValue = (uint32_t) (uintptr_t) cp;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_changeProperty + 1;
|
|
mod.changeProperty.property.path = DuplicatePath((uint32_t *) menuPath);
|
|
mod.changeProperty.property.data = SaveToGrowableBuffer(&StyleColor_Type, sizeof(newValue), NULL, &newValue);
|
|
ModApply(&mod);
|
|
}
|
|
|
|
int StyleColorButtonMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_DESTROY) {
|
|
InspectorUnsubscribe(element);
|
|
} else if (message == UI_MSG_GET_WIDTH) {
|
|
return 240 * element->window->scale;
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
RfItem item;
|
|
void *_dp = ResolveDataObject((RfPath *) element->cp, &item);
|
|
assert(dp == _dp);
|
|
UIButton *button = (UIButton *) element;
|
|
free(button->label);
|
|
|
|
RfData key = ColorLookupPointer(*(uint32_t *) dp)->key;
|
|
button->label = UIStringCopy(key.buffer, key.byteCount);
|
|
|
|
UIElementRefresh(element);
|
|
} else if (message == UI_MSG_CLICKED) {
|
|
RfItem item;
|
|
ResolveDataObject((RfPath *) element->cp, &item);
|
|
UIMenu *menu = UIMenuCreate(element, 0);
|
|
menuPath = (uint32_t *) element->cp;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
UIMenuAddItem(menu, 0, styleSet.colors[i]->key.buffer, styleSet.colors[i]->key.byteCount,
|
|
StyleColorMenuItemInvoke, (void *) (uintptr_t) styleSet.colors[i]->id);
|
|
}
|
|
|
|
UIMenuShow(menu);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void StyleColorRenameButtonCommand(void *cp) {
|
|
UIButton *button = (UIButton *) cp;
|
|
RfItem item;
|
|
void *dp = ResolveDataObject((RfPath *) button->e.cp, &item);
|
|
uint32_t id = *(uint32_t *) dp;
|
|
Color *color = ColorLookupPointer(id);
|
|
char buffer[128];
|
|
snprintf(buffer, sizeof(buffer), "%.*s", (int) color->key.byteCount, (char *) color->key.buffer);
|
|
char *key = strdup(buffer);
|
|
UIDialogShow(window, 0, "Rename color \n%t\n%f%b", &key, "Rename");
|
|
free(color->key.buffer);
|
|
color->key.buffer = key;
|
|
color->key.byteCount = strlen(key);
|
|
free(button->label);
|
|
button->label = UIStringCopy(key, -1);
|
|
UIElementRefresh(&button->e);
|
|
ColorListRefresh();
|
|
}
|
|
|
|
void StyleColorOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UIPanel *labelPanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(&labelPanel->e, UI_ELEMENT_H_FILL, ((StringOption *) item->options)->string, -1);
|
|
MakeOverrideButton(&labelPanel->e, state, RF_PATH_TERMINATOR);
|
|
UIPanel *row = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UIButton *button = UIButtonCreate(&row->e, UI_BUTTON_DROP_DOWN, "", -1);
|
|
button->e.messageUser = StyleColorButtonMessage;
|
|
UIButton *rename = UIButtonCreate(&row->e, 0, "Rename", -1);
|
|
rename->e.cp = button;
|
|
rename->invoke = StyleColorRenameButtonCommand;
|
|
BuildPathForUI((MakeUIState *) state, RF_PATH_TERMINATOR, &button->e);
|
|
} else if (state->op == OP_GET_PALETTE) {
|
|
uint32_t color = *(uint32_t *) pointer;
|
|
int count = hmget(palette, color);
|
|
count++;
|
|
hmput(palette, color, count);
|
|
} else if (state->op == OP_REPLACE_COLOR) {
|
|
assert(*(uint32_t *) pointer != replaceColorTo);
|
|
|
|
if (*(uint32_t *) pointer == replaceColorFrom) {
|
|
*(uint32_t *) pointer = replaceColorTo;
|
|
}
|
|
} else if (state->op == OP_FIND_COLOR_USERS) {
|
|
uint32_t id = *(uint32_t *) pointer;
|
|
Style **list = hmget(colorUsers, id);
|
|
Layer *layer = (Layer *) currentPaletteOpLayer;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
for (uintptr_t j = 0; j < arrlenu(styleSet.styles[i]->layers); j++) {
|
|
if (styleSet.styles[i]->layers[j] == layer->id) {
|
|
for (uintptr_t k = 0; k < arrlenu(list); k++) {
|
|
if (list[k] == styleSet.styles[i]) {
|
|
goto found;
|
|
}
|
|
}
|
|
|
|
arrput(list, styleSet.styles[i]);
|
|
goto found;
|
|
}
|
|
}
|
|
}
|
|
|
|
found:;
|
|
hmput(colorUsers, id, list);
|
|
} else {
|
|
RfEndianOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleBoolOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
if (((MakeUIState *) state)->inKeyframe) return;
|
|
UIButton *button = UIButtonCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e,
|
|
0, ((StringOption *) item->options)->string, -1);
|
|
button->e.messageUser = StyleBoolMessage;
|
|
BuildPathForUI((MakeUIState *) state, RF_PATH_TERMINATOR, &button->e);
|
|
} else {
|
|
RfEndianOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleStringOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
if (((MakeUIState *) state)->inKeyframe) return;
|
|
UILabelCreate(0, 0, ((StringOption *) item->options)->string, -1);
|
|
UITextbox *textbox = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
textbox->e.messageUser = StyleStringMessage;
|
|
BuildPathForUI((MakeUIState *) state, RF_PATH_TERMINATOR, &textbox->e);
|
|
} else {
|
|
RfDataOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void Rectangle8Op(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UILabelCreate(0, 0, ((StringOption *) item->options)->string, -1);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
|
|
MakeOverrideButton(0, state, Rectangle8_l);
|
|
UITextbox *l = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
l->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle8_l, &l->e);
|
|
MakeOverrideButton(0, state, Rectangle8_r);
|
|
UITextbox *r = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
r->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle8_r, &r->e);
|
|
MakeOverrideButton(0, state, Rectangle8_t);
|
|
UITextbox *t = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
t->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle8_t, &t->e);
|
|
MakeOverrideButton(0, state, Rectangle8_b);
|
|
UITextbox *b = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
b->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle8_b, &b->e);
|
|
|
|
UIParentPop();
|
|
((MakeUIState *) state)->recurse = false;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void Rectangle16Op(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UILabelCreate(0, 0, ((StringOption *) item->options)->string, -1);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
|
|
MakeOverrideButton(0, state, Rectangle16_l);
|
|
UITextbox *l = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
l->e.messageUser = StyleI16Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle16_l, &l->e);
|
|
MakeOverrideButton(0, state, Rectangle16_r);
|
|
UITextbox *r = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
r->e.messageUser = StyleI16Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle16_r, &r->e);
|
|
MakeOverrideButton(0, state, Rectangle16_t);
|
|
UITextbox *t = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
t->e.messageUser = StyleI16Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle16_t, &t->e);
|
|
MakeOverrideButton(0, state, Rectangle16_b);
|
|
UITextbox *b = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
b->e.messageUser = StyleI16Message;
|
|
BuildPathForUI((MakeUIState *) state, Rectangle16_b, &b->e);
|
|
|
|
UIParentPop();
|
|
((MakeUIState *) state)->recurse = false;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void PathPointOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_EXPORT) {
|
|
PathPoint *point = (PathPoint *) pointer;
|
|
float themePoint[6];
|
|
themePoint[0] = point->x0; ExportAddPathToOffset((ExportState *) state, PathPoint_x0, 0 * sizeof(float));
|
|
themePoint[1] = point->y0; ExportAddPathToOffset((ExportState *) state, PathPoint_y0, 1 * sizeof(float));
|
|
themePoint[2] = point->x1; ExportAddPathToOffset((ExportState *) state, PathPoint_x1, 2 * sizeof(float));
|
|
themePoint[3] = point->y1; ExportAddPathToOffset((ExportState *) state, PathPoint_y1, 3 * sizeof(float));
|
|
themePoint[4] = point->x2; ExportAddPathToOffset((ExportState *) state, PathPoint_x2, 4 * sizeof(float));
|
|
themePoint[5] = point->y2; ExportAddPathToOffset((ExportState *) state, PathPoint_y2, 5 * sizeof(float));
|
|
state->access(state, themePoint, sizeof(themePoint));
|
|
} else if (state->op == OP_MAKE_UI) {
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
MakeOverrideButton(0, state, PathPoint_x0 + i);
|
|
UITextbox *textbox = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
textbox->e.messageUser = StyleFloatMessage;
|
|
BuildPathForUI((MakeUIState *) state, PathPoint_x0 + i, &textbox->e);
|
|
if (i == 1 || i == 3) UISpacerCreate(0, 0, 5, 0);
|
|
}
|
|
|
|
UIParentPop();
|
|
((MakeUIState *) state)->recurse = false;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void Gaps8Op(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UILabelCreate(0, 0, ((StringOption *) item->options)->string, -1);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
|
|
MakeOverrideButton(0, state, Gaps8_major);
|
|
UITextbox *major = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
major->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Gaps8_major, &major->e);
|
|
MakeOverrideButton(0, state, Gaps8_minor);
|
|
UITextbox *minor = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
minor->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Gaps8_minor, &minor->e);
|
|
MakeOverrideButton(0, state, Gaps8_wrap);
|
|
UITextbox *wrap = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
wrap->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Gaps8_wrap, &wrap->e);
|
|
|
|
UIParentPop();
|
|
((MakeUIState *) state)->recurse = false;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void Size16Op(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UILabelCreate(0, 0, ((StringOption *) item->options)->string, -1);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
|
|
MakeOverrideButton(0, state, Size16_width);
|
|
UITextbox *width = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
width->e.messageUser = StyleI16Message;
|
|
BuildPathForUI((MakeUIState *) state, Size16_width, &width->e);
|
|
MakeOverrideButton(0, state, Size16_height);
|
|
UITextbox *height = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
height->e.messageUser = StyleI16Message;
|
|
BuildPathForUI((MakeUIState *) state, Size16_height, &height->e);
|
|
|
|
UIParentPop();
|
|
((MakeUIState *) state)->recurse = false;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void Corners8Op(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UILabelCreate(0, 0, ((StringOption *) item->options)->string, -1);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
MakeOverrideButton(0, state, Corners8_tl);
|
|
UITextbox *l = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
l->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Corners8_tl, &l->e);
|
|
MakeOverrideButton(0, state, Corners8_tr);
|
|
UITextbox *r = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
r->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Corners8_tr, &r->e);
|
|
MakeOverrideButton(0, state, Corners8_bl);
|
|
UITextbox *t = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
t->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Corners8_bl, &t->e);
|
|
MakeOverrideButton(0, state, Corners8_br);
|
|
UITextbox *b = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
b->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, Corners8_br, &b->e);
|
|
UIParentPop();
|
|
((MakeUIState *) state)->recurse = false;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleI8Op(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UIPanel *labelPanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(&labelPanel->e, UI_ELEMENT_H_FILL, ((StringOption *) item->options)->string, -1);
|
|
MakeOverrideButton(&labelPanel->e, state, RF_PATH_TERMINATOR);
|
|
UITextbox *textbox = UITextboxCreate(0, 0);
|
|
textbox->e.messageUser = StyleI8Message;
|
|
BuildPathForUI((MakeUIState *) state, RF_PATH_TERMINATOR, &textbox->e);
|
|
} else {
|
|
rfI8.op(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleI16Op(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UIPanel *labelPanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(&labelPanel->e, UI_ELEMENT_H_FILL, ((StringOption *) item->options)->string, -1);
|
|
MakeOverrideButton(&labelPanel->e, state, RF_PATH_TERMINATOR);
|
|
UITextbox *textbox = UITextboxCreate(0, 0);
|
|
textbox->e.messageUser = StyleI16Message;
|
|
BuildPathForUI((MakeUIState *) state, RF_PATH_TERMINATOR, &textbox->e);
|
|
} else {
|
|
rfI16.op(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleFloatOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
UIPanel *labelPanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(&labelPanel->e, UI_ELEMENT_H_FILL, ((StringOption *) item->options)->string, -1);
|
|
MakeOverrideButton(&labelPanel->e, state, RF_PATH_TERMINATOR);
|
|
UITextbox *textbox = UITextboxCreate(0, 0);
|
|
textbox->e.messageUser = StyleFloatMessage;
|
|
BuildPathForUI((MakeUIState *) state, RF_PATH_TERMINATOR, &textbox->e);
|
|
} else {
|
|
rfF32.op(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleUnionOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
MakeUIState *makeUI = (MakeUIState *) state;
|
|
|
|
UILabelCreate(0, 0, ((StringOption *) item->options)->string, -1);
|
|
|
|
if (!makeUI->inKeyframe) {
|
|
UIPanel *buttonPanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
buttonPanel->e.messageUser = StyleUnionButtonPanelMessage;
|
|
|
|
for (uintptr_t i = 0; i < item->type->fieldCount; i++) {
|
|
uint32_t tag = i ? i + 1 : 0;
|
|
const char *name = i ? ((StringOption *) item->type->fields[i].item.options)->string : "(none)";
|
|
UIButton *button = UIButtonCreate(&buttonPanel->e, UI_BUTTON_SMALL, name, -1);
|
|
button->e.cp = (void *) (uintptr_t) tag;
|
|
button->e.messageUser = StyleUnionButtonMessage;
|
|
}
|
|
|
|
BuildPathForUI(makeUI, 0, &buttonPanel->e);
|
|
}
|
|
|
|
UIPanel *subPanel = UIPanelCreate(0, UI_PANEL_WHITE | UI_PANEL_EXPAND);
|
|
subPanel->gap = 5;
|
|
subPanel->e.messageUser = StyleUnionPanelMessage;
|
|
BuildPathForUI(makeUI, 0, &subPanel->e);
|
|
makeUI->recurse = false;
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleEnumOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
MakeUIState *makeUI = (MakeUIState *) state;
|
|
|
|
if (makeUI->inKeyframe) {
|
|
return;
|
|
}
|
|
|
|
if (item->type->fieldCount > 5) {
|
|
UIPanel *panel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(&panel->e, UI_ELEMENT_H_FILL, ((StringOption *) item->options)->string, -1);
|
|
UIButton *button = UIButtonCreate(&panel->e, UI_BUTTON_DROP_DOWN, "", -1);
|
|
button->e.messageUser = StyleChoiceButtonMessage;
|
|
BuildPathForUI(makeUI, RF_PATH_TERMINATOR, &button->e);
|
|
} else {
|
|
UIPanel *buttonPanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(&buttonPanel->e, 0, ((StringOption *) item->options)->string, -1);
|
|
buttonPanel->e.messageUser = StyleUnionButtonPanelMessage;
|
|
|
|
for (uintptr_t i = 0; i < item->type->fieldCount; i++) {
|
|
uint32_t tag = i;
|
|
const char *name = ((StringOption *) item->type->fields[i].item.options)->string;
|
|
UIButton *button = UIButtonCreate(&buttonPanel->e, UI_BUTTON_SMALL, name, -1);
|
|
button->e.cp = (void *) (uintptr_t) tag;
|
|
button->e.messageUser = StyleUnionButtonMessage;
|
|
}
|
|
|
|
BuildPathForUI(makeUI, RF_PATH_TERMINATOR, &buttonPanel->e);
|
|
}
|
|
} else {
|
|
RfEnumOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void StyleArrayOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
MakeUIState *makeUI = (MakeUIState *) state;
|
|
|
|
UIPanel *buttonPanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL);
|
|
UILabelCreate(&buttonPanel->e, 0, ((StringOption *) ((RfItem *) item->options)->options)->string, -1);
|
|
|
|
UIPanel *subPanel = UIPanelCreate(0, UI_PANEL_WHITE | UI_PANEL_EXPAND);
|
|
subPanel->gap = 5;
|
|
subPanel->e.messageUser = StyleArrayPanelMessage;
|
|
BuildPathForUI(makeUI, RF_PATH_TERMINATOR, &subPanel->e);
|
|
makeUI->recurse = false;
|
|
|
|
if (!makeUI->inKeyframe) {
|
|
UIButton *addButton = UIButtonCreate(&buttonPanel->e, UI_BUTTON_SMALL, "Add", -1);
|
|
addButton->e.cp = subPanel;
|
|
addButton->e.messageUser = StyleArrayAddMessage;
|
|
UIButton *deleteButton = UIButtonCreate(&buttonPanel->e, UI_BUTTON_SMALL, "Delete", -1);
|
|
deleteButton->e.cp = subPanel;
|
|
deleteButton->e.messageUser = StyleArrayDeleteMessage;
|
|
}
|
|
} else {
|
|
RfArrayOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ButtonMoveLayerUp(void *_unused) {
|
|
uintptr_t index = 0;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(selected.style->layers); i++) {
|
|
if (selected.style->layers[i] == selected.layer->id) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index <= 1) {
|
|
return;
|
|
}
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_swapLayers + 1;
|
|
mod.swapLayers.index = index - 1;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonMoveLayerDown(void *_unused) {
|
|
uintptr_t index = 0;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(selected.style->layers); i++) {
|
|
if (selected.style->layers[i] == selected.layer->id) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index == arrlenu(selected.style->layers) - 1) {
|
|
return;
|
|
}
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_swapLayers + 1;
|
|
mod.swapLayers.index = index;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ForkLayer(void *_unused) {
|
|
// TODO Undo.
|
|
// TODO Memory leak of state.data?
|
|
|
|
if (!selected.layer) return;
|
|
|
|
RfGrowableBuffer state = { 0 };
|
|
state.data = SaveToGrowableBuffer(&Layer_Type, sizeof(Layer), NULL, selected.layer);
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_deleteLayer + 1;
|
|
bool found = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(selected.style->layers); i++) {
|
|
if (selected.style->layers[i] == selected.layer->id) {
|
|
mod.deleteLayer.index = i;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(found);
|
|
ModApply(&mod);
|
|
|
|
state.s.version = saveFormatVersion;
|
|
state.data.byteCount -= sizeof(uint32_t);
|
|
state.s.allocate = RfRealloc;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
|
|
RfItem item = { 0 };
|
|
item.type = &Layer_Type;
|
|
item.byteCount = sizeof(Layer);
|
|
state.s.op = RF_OP_LOAD;
|
|
|
|
Layer *layer = calloc(1, sizeof(Layer));
|
|
item.type->op(&state.s, &item, layer);
|
|
layer->id = ++styleSet.lastID;
|
|
|
|
mod.tag = ModData_addLayer + 1;
|
|
mod.addLayer.index = mod.deleteLayer.index;
|
|
mod.addLayer.layer = layer;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void RebuildInspector() {
|
|
UIElementDestroyDescendents(&panelInspector->e);
|
|
UIParentPush(&panelInspector->e);
|
|
|
|
if (selected.keyframe) {
|
|
MAKE_UI(Keyframe, selected.keyframe, false);
|
|
MAKE_UI(Layer, selected.layer, true);
|
|
} else if (selected.sequence) {
|
|
MAKE_UI(Sequence, selected.sequence, false);
|
|
} else if (selected.layer) {
|
|
char buffer[256];
|
|
snprintf(buffer, 256, "Layer ID: %ld", selected.layer->id);
|
|
UILabelCreate(0, 0, buffer, -1);
|
|
|
|
uintptr_t layerUsageCount = 0;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
Style *style = styleSet.styles[i];
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(style->layers); j++) {
|
|
if (style->layers[j] == selected.layer->id) {
|
|
layerUsageCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (layerUsageCount > 1) {
|
|
snprintf(buffer, 256, "This layer is used %ld times.", layerUsageCount);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH)->gap = 10;
|
|
UILabelCreate(0, 0, buffer, -1);
|
|
UIButtonCreate(0, 0, "Fork", -1)->invoke = ForkLayer;
|
|
UIParentPop();
|
|
}
|
|
|
|
if (selected.layer->isMetricsLayer) {
|
|
assert(selected.layer->base.tag == LayerBase_metrics + 1);
|
|
MakeUIState state = { 0 };
|
|
state.s.op = OP_MAKE_UI;
|
|
state.index = Layer_base;
|
|
state.recurse = true;
|
|
RfItem item = Layer_Type.fields[Layer_base].item;
|
|
void *pointer = (uint8_t *) selected.layer + Layer_Type.fields[Layer_base].offset;
|
|
MakeUI(&state, &item, pointer);
|
|
} else {
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Move up", -1)->invoke = ButtonMoveLayerUp;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Move down", -1)->invoke = ButtonMoveLayerDown;
|
|
UIParentPop();
|
|
|
|
MAKE_UI(Layer, selected.layer, false);
|
|
}
|
|
}
|
|
|
|
UIParentPop();
|
|
UIElementRefresh(&panelInspector->e);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void StyleListRefresh();
|
|
|
|
void SetSelectedItems(ModContext context) {
|
|
if (0 == memcmp(&selected, &context, sizeof(context))) {
|
|
return;
|
|
}
|
|
|
|
arrfree(animatingValues);
|
|
|
|
selected = context;
|
|
|
|
tableLayers->itemCount = selected.style ? arrlen(selected.style->layers) : 0;
|
|
UITableResizeColumns(tableLayers);
|
|
UIElementRefresh(&tableLayers->e);
|
|
|
|
tableSequences->itemCount = selected.layer ? arrlen(selected.layer->sequences) : 0;
|
|
UITableResizeColumns(tableSequences);
|
|
UIElementRefresh(&tableSequences->e);
|
|
|
|
tableKeyframes->itemCount = selected.sequence ? arrlen(selected.sequence->keyframes) : 0;
|
|
UITableResizeColumns(tableKeyframes);
|
|
UIElementRefresh(&tableKeyframes->e);
|
|
|
|
static uint32_t layerNamePath[] = { Layer_name, RF_PATH_TERMINATOR };
|
|
if (selected.layer && !selected.sequence) tableLayers->e.cp = layerNamePath;
|
|
else tableLayers->e.cp = NULL;
|
|
|
|
static uint32_t sequencePath[] = { PATH_ANY, RF_PATH_TERMINATOR };
|
|
if (selected.sequence && !selected.keyframe) tableSequences->e.cp = sequencePath;
|
|
else tableSequences->e.cp = NULL;
|
|
|
|
static uint32_t keyframeProgressPath[] = { Keyframe_progress, RF_PATH_TERMINATOR };
|
|
if (selected.keyframe) tableSequences->e.cp = keyframeProgressPath;
|
|
else tableKeyframes->e.cp = NULL;
|
|
|
|
StyleListRefresh();
|
|
RebuildInspector();
|
|
}
|
|
|
|
// ------------------- Modifications -------------------
|
|
|
|
void ModPushUndo(ModData *data) {
|
|
Mod mod = { 0 };
|
|
mod.context = selected;
|
|
mod.data = *data;
|
|
|
|
if (modApplyUndo) {
|
|
arrput(redoStack, mod);
|
|
} else {
|
|
arrput(undoStack, mod);
|
|
}
|
|
}
|
|
|
|
void _ModApply(Mod *mod) {
|
|
if (memcmp(&mod->context, &selected, sizeof(ModContext))) {
|
|
SetSelectedItems(mod->context);
|
|
}
|
|
|
|
RfIterator iterator = { 0 };
|
|
iterator.s.op = RF_OP_ITERATE;
|
|
RfItem item = { 0 };
|
|
item.type = &ModData_Type;
|
|
item.byteCount = sizeof(Mod);
|
|
item.type->op(&iterator.s, &item, &mod->data);
|
|
|
|
RfState state = { 0 };
|
|
state.op = OP_DO_MOD;
|
|
iterator.item.type->op(&state, &iterator.item, iterator.pointer);
|
|
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void ClearUndoRedo() {
|
|
RfState state = { 0 };
|
|
state.op = RF_OP_FREE;
|
|
state.allocate = RfRealloc;
|
|
RfItem item = { 0 };
|
|
item.type = &Mod_Type;
|
|
item.byteCount = sizeof(Mod);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(redoStack); i++) {
|
|
item.type->op(&state, &item, redoStack + i);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(undoStack); i++) {
|
|
item.type->op(&state, &item, undoStack + i);
|
|
}
|
|
|
|
arrfree(redoStack);
|
|
arrfree(undoStack);
|
|
}
|
|
|
|
void ModApply(ModData *data) {
|
|
arrfree(animatingValues);
|
|
|
|
Mod mod = { 0 };
|
|
mod.context = selected;
|
|
mod.data = *data;
|
|
|
|
modApplyUndo = false;
|
|
_ModApply(&mod);
|
|
|
|
RfState state = { 0 };
|
|
state.op = RF_OP_FREE;
|
|
state.allocate = RfRealloc;
|
|
RfItem item = { 0 };
|
|
item.type = &Mod_Type;
|
|
item.byteCount = sizeof(Mod);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(redoStack); i++) {
|
|
item.type->op(&state, &item, redoStack + i);
|
|
}
|
|
|
|
arrfree(redoStack);
|
|
|
|
if (arrlenu(undoStack) >= 2 && data->tag != ModData_deleteOverride + 1) {
|
|
Mod *undo1 = undoStack + arrlen(undoStack) - 1;
|
|
Mod *undo2 = undoStack + arrlen(undoStack) - 2;
|
|
|
|
if (0 == memcmp(&undo1->context, &undo2->context, sizeof(ModContext))) {
|
|
if ((undo1->data.tag == ModData_changeProperty + 1 && undo2->data.tag == ModData_changeProperty + 1
|
|
&& ArePathsEqual(undo1->data.changeProperty.property.path, undo2->data.changeProperty.property.path))
|
|
|| (undo1->data.tag == ModData_changeProperty + 1 && undo2->data.tag == ModData_deleteOverride + 1
|
|
&& ArePathsEqual(undo1->data.changeProperty.property.path, undo2->data.deleteOverride.property.path))) {
|
|
item.type->op(&state, &item, undo1);
|
|
(void) arrpop(undoStack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotifySubscriptions(UIElement *source, uint32_t *path, void *pointer) {
|
|
for (uintptr_t i = 0; i < arrlenu(inspectorSubscriptions); i++) {
|
|
if (inspectorSubscriptions[i] == source) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t *subscription = (uint32_t *) inspectorSubscriptions[i]->cp;
|
|
|
|
if (!subscription) {
|
|
continue;
|
|
}
|
|
|
|
if (subscription[0] == PATH_ANY || ArePathsEqual(subscription, path)) {
|
|
UIElementMessage(inspectorSubscriptions[i], MSG_PROPERTY_CHANGED, 0, pointer);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModChangePropertyOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModChangeProperty *mod = (ModChangeProperty *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
// Do we need to use an override?
|
|
bool isOverride = mod->property.path[0] == PATH_IN_KEYFRAME;
|
|
void *pointer = NULL;
|
|
|
|
if (isOverride) {
|
|
bool foundOverride = false;
|
|
|
|
// Does an override exist?
|
|
for (uintptr_t i = 0; i < arrlenu(selected.keyframe->properties); i++) {
|
|
if (ArePathsEqual(mod->property.path, selected.keyframe->properties[i].path)) {
|
|
// Save the old value for undo.
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_changeProperty + 1;
|
|
undo.changeProperty.property.path = mod->property.path;
|
|
undo.changeProperty.property.data = selected.keyframe->properties[i].data;
|
|
ModPushUndo(&undo);
|
|
|
|
// Load the new value.
|
|
selected.keyframe->properties[i].data = mod->property.data;
|
|
foundOverride = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundOverride) {
|
|
// Create the undo mod.
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_deleteOverride + 1;
|
|
undo.deleteOverride.property.path = mod->property.path;
|
|
ModPushUndo(&undo);
|
|
|
|
// Add the override.
|
|
Property property = mod->property;
|
|
property.path = DuplicatePath(property.path);
|
|
arrput(selected.keyframe->properties, property);
|
|
}
|
|
|
|
// Load the object so we can notify subscribers.
|
|
RfItem item;
|
|
pointer = ResolveDataObject((RfPath *) mod->property.path, &item);
|
|
} else {
|
|
// Resolve the pointer.
|
|
RfItem item;
|
|
pointer = ResolveDataObject((RfPath *) mod->property.path, &item);
|
|
|
|
// Save the old value for undo.
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_changeProperty + 1;
|
|
undo.changeProperty.property.path = mod->property.path;
|
|
undo.changeProperty.property.data = SaveToGrowableBuffer(item.type, item.byteCount, item.options, pointer);
|
|
ModPushUndo(&undo);
|
|
|
|
// Free the old value.
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.allocate = RfRealloc;
|
|
state.s.op = RF_OP_FREE;
|
|
item.type->op(&state.s, &item, pointer);
|
|
|
|
// Load the new value.
|
|
state.s.op = RF_OP_LOAD;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = mod->property.data;
|
|
state.position = 0;
|
|
item.type->op(&state.s, &item, pointer);
|
|
free(state.data.buffer);
|
|
}
|
|
|
|
// Notify subscribed elements in the inspector.
|
|
NotifySubscriptions(mod->source, mod->property.path, pointer);
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ModDeleteOverrideOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModDeleteOverride *mod = (ModDeleteOverride *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
// Find the override.
|
|
for (uintptr_t i = 0; i < arrlenu(selected.keyframe->properties); i++) {
|
|
if (ArePathsEqual(mod->property.path, selected.keyframe->properties[i].path)) {
|
|
// Save the old value for undo.
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_changeProperty + 1;
|
|
undo.changeProperty.property.path = mod->property.path;
|
|
undo.changeProperty.property.data = selected.keyframe->properties[i].data;
|
|
ModPushUndo(&undo);
|
|
|
|
// Delete the override.
|
|
free(selected.keyframe->properties[i].path);
|
|
arrdel(selected.keyframe->properties, i);
|
|
|
|
// Notify subscribed elements in the inspector.
|
|
RfItem item;
|
|
NotifySubscriptions(NULL, mod->property.path,
|
|
ResolveDataObject((RfPath *) mod->property.path, &item));
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
assert(false);
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ModArrayOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModArray *mod = (ModArray *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
// Resolve the pointer.
|
|
RfItem item;
|
|
void **pointer = (void **) ResolveDataObject((RfPath *) mod->property.path, &item);
|
|
RfItem *elementItem = (RfItem *) item.options;
|
|
|
|
if (mod->isDelete) {
|
|
size_t length = --((RfArrayHeader *) *pointer)[-1].length;
|
|
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_array + 1;
|
|
undo.array.property.path = mod->property.path;
|
|
undo.array.property.data = SaveToGrowableBuffer(elementItem->type, elementItem->byteCount, elementItem->options,
|
|
(uint8_t *) *pointer + elementItem->byteCount * length);
|
|
ModPushUndo(&undo);
|
|
} else {
|
|
*pointer = stbds_arrgrowf(*pointer, elementItem->byteCount, 1, 0);
|
|
|
|
size_t length = ++((RfArrayHeader *) *pointer)[-1].length;
|
|
RfGrowableBuffer state = { 0 };
|
|
state.s.allocate = RfRealloc;
|
|
state.s.op = RF_OP_LOAD;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
state.data = mod->property.data;
|
|
state.position = 0;
|
|
uint8_t *p = (uint8_t *) *pointer + elementItem->byteCount * (length - 1);
|
|
memset(p, 0, elementItem->byteCount);
|
|
elementItem->type->op(&state.s, elementItem, p);
|
|
free(state.data.buffer);
|
|
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_array + 1;
|
|
undo.array.property.path = mod->property.path;
|
|
undo.array.isDelete = true;
|
|
ModPushUndo(&undo);
|
|
}
|
|
|
|
// Notify subscribed elements in the inspector.
|
|
NotifySubscriptions(NULL, mod->property.path, pointer);
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
// ------------------- Styles -------------------
|
|
|
|
UITextbox *stylesTextbox;
|
|
UITable *stylesTable;
|
|
UIButton *stylesShowWithSelectedLayer;
|
|
Style **stylesInList;
|
|
|
|
int StylesTableMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_TABLE_GET_ITEM) {
|
|
UITableGetItem *m = (UITableGetItem *) dp;
|
|
Style *style = stylesInList[m->index];
|
|
m->isSelected = style == selected.style;
|
|
|
|
if (!style) {
|
|
return snprintf(m->buffer, m->bufferBytes, "View all");
|
|
} else if (style->name.byteCount) {
|
|
return snprintf(m->buffer, m->bufferBytes, "%.*s", (int) style->name.byteCount, (char *) style->name.buffer);
|
|
} else {
|
|
return snprintf(m->buffer, m->bufferBytes, "(default)");
|
|
}
|
|
} else if (message == UI_MSG_CLICKED || message == UI_MSG_MOUSE_DRAG) {
|
|
int index = UITableHitTest(stylesTable, element->window->cursorX, element->window->cursorY);
|
|
assert(index <= arrlen(stylesInList));
|
|
|
|
if (index >= 0) {
|
|
bool keepExistingLayer = false;
|
|
|
|
if (selected.layer && stylesInList[index]) {
|
|
for (uintptr_t i = 0; i < arrlenu(stylesInList[index]->layers); i++) {
|
|
if (stylesInList[index]->layers[i] == selected.layer->id) {
|
|
keepExistingLayer = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SetSelectedItems(MOD_CONTEXT(stylesInList[index], keepExistingLayer ? selected.layer
|
|
: LayerLookup(stylesInList[index]->layers[0]), NULL, NULL));
|
|
UIElementRefresh(&stylesTable->e);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StyleCompare(const void *_left, const void *_right) {
|
|
Style *left = *(Style **) _left;
|
|
Style *right = *(Style **) _right;
|
|
return StringCompareRaw(left->name.buffer, left->name.byteCount, right->name.buffer, right->name.byteCount);
|
|
}
|
|
|
|
void StyleListRefresh() {
|
|
qsort(styleSet.styles, arrlenu(styleSet.styles), sizeof(Style *), StyleCompare);
|
|
|
|
arrfree(stylesInList);
|
|
|
|
bool showWithSelectedLayer = stylesShowWithSelectedLayer->e.flags & UI_BUTTON_CHECKED;
|
|
|
|
if (!showWithSelectedLayer) {
|
|
arrput(stylesInList, NULL);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
bool include = false;
|
|
|
|
if (showWithSelectedLayer && selected.layer) {
|
|
for (uintptr_t j = 0; j < arrlenu(styleSet.styles[i]->layers); j++) {
|
|
if (styleSet.styles[i]->layers[j] == selected.layer->id) {
|
|
include = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
include = true;
|
|
}
|
|
|
|
if (include) {
|
|
arrput(stylesInList, styleSet.styles[i]);
|
|
}
|
|
}
|
|
|
|
stylesTable->itemCount = arrlenu(stylesInList);
|
|
UITableResizeColumns(stylesTable);
|
|
UIElementRefresh(&stylesTable->e);
|
|
|
|
UITextboxClear(stylesTextbox, false);
|
|
UIElementRefresh(&stylesTextbox->e);
|
|
|
|
if (selected.style && selected.style->publicStyle) buttonPublicStyle->e.flags |= UI_BUTTON_CHECKED;
|
|
else buttonPublicStyle->e.flags &= ~UI_BUTTON_CHECKED;
|
|
}
|
|
|
|
void ButtonCreateStyle(void *_unused) {
|
|
if (!stylesTextbox->bytes) {
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
if (styleSet.styles[i]->name.byteCount == (size_t) stylesTextbox->bytes
|
|
&& 0 == memcmp(styleSet.styles[i]->name.buffer, stylesTextbox->string, stylesTextbox->bytes)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Style *style = calloc(1, sizeof(Style));
|
|
style->name.buffer = malloc((style->name.byteCount = stylesTextbox->bytes));
|
|
style->id = ++styleSet.lastID;
|
|
memcpy(style->name.buffer, stylesTextbox->string, style->name.byteCount);
|
|
Layer *metrics = calloc(1, sizeof(Layer));
|
|
metrics->id = ++styleSet.lastID;
|
|
metrics->name.buffer = malloc(16);
|
|
metrics->name.byteCount = snprintf(metrics->name.buffer, 16, "Metrics");
|
|
metrics->base.tag = LayerBase_metrics + 1;
|
|
metrics->isMetricsLayer = true;
|
|
arrput(style->layers, metrics->id);
|
|
arrput(styleSet.layers, metrics);
|
|
arrput(styleSet.styles, style);
|
|
SetSelectedItems(MOD_CONTEXT(style, NULL, NULL, NULL));
|
|
|
|
ClearUndoRedo();
|
|
StyleListRefresh();
|
|
}
|
|
|
|
void ButtonDeleteStyle(void *_unused) {
|
|
if (!selected.style) return;
|
|
Style *style = selected.style;
|
|
SetSelectedItems(MOD_CONTEXT(NULL, NULL, NULL, NULL));
|
|
|
|
bool found = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
if (styleSet.styles[i] == style) {
|
|
RfState state = { 0 };
|
|
state.op = RF_OP_FREE;
|
|
state.allocate = RfRealloc;
|
|
RfItem item = { 0 };
|
|
item.type = &Style_Type;
|
|
item.byteCount = sizeof(Style);
|
|
item.type->op(&state, &item, style);
|
|
arrdel(styleSet.styles, i);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(found);
|
|
|
|
ClearUndoRedo();
|
|
StyleListRefresh();
|
|
}
|
|
|
|
void ButtonTogglePublicStyle(void *_unused) {
|
|
if (!selected.style) {
|
|
return;
|
|
}
|
|
|
|
selected.style->publicStyle = !selected.style->publicStyle;
|
|
|
|
if (selected.style->publicStyle) buttonPublicStyle->e.flags |= UI_BUTTON_CHECKED;
|
|
else buttonPublicStyle->e.flags &= ~UI_BUTTON_CHECKED;
|
|
}
|
|
|
|
void ButtonRenameStyle(void *_unused) {
|
|
if (!stylesTextbox->bytes || !selected.style) {
|
|
return;
|
|
}
|
|
|
|
selected.style->name.buffer = realloc(selected.style->name.buffer, (selected.style->name.byteCount = stylesTextbox->bytes));
|
|
memcpy(selected.style->name.buffer, stylesTextbox->string, selected.style->name.byteCount);
|
|
|
|
ClearUndoRedo();
|
|
StyleListRefresh();
|
|
}
|
|
|
|
void ButtonShowStylesWithSelectedLayer(void *_unused) {
|
|
stylesShowWithSelectedLayer->e.flags ^= UI_BUTTON_CHECKED;
|
|
StyleListRefresh();
|
|
}
|
|
|
|
// ------------------- Constants -------------------
|
|
|
|
UITextbox *constantsTextbox;
|
|
UITextbox *constantsValue;
|
|
UIButton *constantsScale;
|
|
UITable *constantsTable;
|
|
Constant *selectedConstant;
|
|
|
|
void ConstantsDialogSetSelected(Constant *constant) {
|
|
selectedConstant = constant;
|
|
UIElementRefresh(&constantsTable->e);
|
|
|
|
UITextboxClear(constantsValue, false);
|
|
|
|
if (constant) {
|
|
UITextboxReplace(constantsValue, constant->value.buffer, constant->value.byteCount, false);
|
|
constantsValue->e.flags &= ~UI_ELEMENT_DISABLED;
|
|
UIElementRefresh(&constantsValue->e);
|
|
|
|
if (constant->scale) constantsScale->e.flags |= UI_BUTTON_CHECKED;
|
|
else constantsScale->e.flags &= ~UI_BUTTON_CHECKED;
|
|
UIElementRefresh(&constantsScale->e);
|
|
} else {
|
|
constantsValue->e.flags |= UI_ELEMENT_DISABLED;
|
|
UIElementRefresh(&constantsValue->e);
|
|
|
|
constantsScale->e.flags &= ~UI_BUTTON_CHECKED;
|
|
UIElementRefresh(&constantsScale->e);
|
|
}
|
|
}
|
|
|
|
int ConstantsTableMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_TABLE_GET_ITEM) {
|
|
UITableGetItem *m = (UITableGetItem *) dp;
|
|
Constant *constant = styleSet.constants[m->index];
|
|
m->isSelected = constant == selectedConstant;
|
|
return snprintf(m->buffer, m->bufferBytes, "%.*s", (int) constant->key.byteCount, (char *) constant->key.buffer);
|
|
} else if (message == UI_MSG_CLICKED || message == UI_MSG_MOUSE_DRAG) {
|
|
int index = UITableHitTest(constantsTable, element->window->cursorX, element->window->cursorY);
|
|
|
|
if (index != -1) {
|
|
ConstantsDialogSetSelected(styleSet.constants[index]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ConstantsValueMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_VALUE_CHANGED) {
|
|
if (selectedConstant) {
|
|
free(selectedConstant->value.buffer);
|
|
selectedConstant->value.buffer = malloc(constantsValue->bytes);
|
|
selectedConstant->value.byteCount = constantsValue->bytes;
|
|
memcpy(selectedConstant->value.buffer, constantsValue->string, constantsValue->bytes);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ConstantsScaleMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_CLICKED) {
|
|
if (selectedConstant) {
|
|
element->flags ^= UI_BUTTON_CHECKED;
|
|
selectedConstant->scale = element->flags & UI_BUTTON_CHECKED;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ConstantCompare(const void *_left, const void *_right) {
|
|
Constant *left = *(Constant **) _left;
|
|
Constant *right = *(Constant **) _right;
|
|
return StringCompareRaw(left->key.buffer, left->key.byteCount, right->key.buffer, right->key.byteCount);
|
|
}
|
|
|
|
void ConstantListRefresh() {
|
|
constantsTable->itemCount = arrlenu(styleSet.constants);
|
|
qsort(styleSet.constants, constantsTable->itemCount, sizeof(Constant *), ConstantCompare);
|
|
UITableResizeColumns(constantsTable);
|
|
UIElementRefresh(&constantsTable->e);
|
|
|
|
UITextboxClear(constantsTextbox, false);
|
|
UIElementRefresh(&constantsTextbox->e);
|
|
}
|
|
|
|
void ButtonAddConstant(void *_unused) {
|
|
if (!constantsTextbox->bytes) {
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.constants); i++) {
|
|
if (styleSet.constants[i]->key.byteCount == (size_t) constantsTextbox->bytes
|
|
&& 0 == memcmp(styleSet.constants[i]->key.buffer, constantsTextbox->string, constantsTextbox->bytes)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Constant *constant = calloc(1, sizeof(Constant));
|
|
constant->key.buffer = malloc((constant->key.byteCount = constantsTextbox->bytes));
|
|
memcpy(constant->key.buffer, constantsTextbox->string, constant->key.byteCount);
|
|
arrput(styleSet.constants, constant);
|
|
|
|
ConstantsDialogSetSelected(constant);
|
|
ConstantListRefresh();
|
|
}
|
|
|
|
void ButtonDeleteConstant(void *_unused) {
|
|
if (!selectedConstant) {
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.constants); i++) {
|
|
if (styleSet.constants[i] == selectedConstant) {
|
|
arrdel(styleSet.constants, i);
|
|
ConstantsDialogSetSelected(NULL);
|
|
ConstantListRefresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------- Colors -------------------
|
|
|
|
UITextbox *colorsTextbox;
|
|
UITextbox *colorsValue;
|
|
UIColorPicker *colorsValue2;
|
|
UITable *colorsTable;
|
|
UIElement *colorsPreview;
|
|
Color *selectedColor;
|
|
|
|
int ColorsPreviewMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_PAINT) {
|
|
UIDrawRectangle((UIPainter *) dp, element->bounds, selectedColor ? selectedColor->value : 0, 0xFF000000, UI_RECT_1(1));
|
|
} else if (message == UI_MSG_GET_WIDTH || message == UI_MSG_GET_HEIGHT) {
|
|
return 20;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ColorsDialogSetSelected(Color *color) {
|
|
selectedColor = color;
|
|
UIElementRefresh(&colorsTable->e);
|
|
|
|
UITextboxClear(colorsValue, false);
|
|
|
|
if (color) {
|
|
char buffer[16];
|
|
sprintf(buffer, "%.8X", color->value);
|
|
UITextboxReplace(colorsValue, buffer, -1, false);
|
|
colorsValue->e.flags &= ~UI_ELEMENT_DISABLED;
|
|
UIColorToHSV(color->value, &colorsValue2->hue, &colorsValue2->saturation, &colorsValue2->value);
|
|
colorsValue2->opacity = (selectedColor->value >> 24) / 255.0f;
|
|
UIElementRefresh(&colorsValue->e);
|
|
UIElementRefresh(&colorsValue2->e);
|
|
UIElementRefresh(colorsPreview);
|
|
} else {
|
|
colorsValue->e.flags |= UI_ELEMENT_DISABLED;
|
|
UIElementRefresh(&colorsValue->e);
|
|
}
|
|
}
|
|
|
|
int ColorsTableMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_TABLE_GET_ITEM) {
|
|
UITableGetItem *m = (UITableGetItem *) dp;
|
|
Color *color = styleSet.colors[m->index];
|
|
m->isSelected = color == selectedColor;
|
|
|
|
if (m->column == 1) {
|
|
return snprintf(m->buffer, m->bufferBytes, "%.8X", color->value);
|
|
} else {
|
|
return snprintf(m->buffer, m->bufferBytes, "%.*s", (int) color->key.byteCount, (char *) color->key.buffer);
|
|
}
|
|
} else if (message == UI_MSG_CLICKED || message == UI_MSG_MOUSE_DRAG) {
|
|
int index = UITableHitTest(colorsTable, element->window->cursorX, element->window->cursorY);
|
|
|
|
if (index != -1) {
|
|
ColorsDialogSetSelected(styleSet.colors[index]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ColorsValue2Message(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_VALUE_CHANGED) {
|
|
if (selectedColor) {
|
|
uint32_t newValue;
|
|
UIColorToRGB(colorsValue2->hue, colorsValue2->saturation, colorsValue2->value, &newValue);
|
|
newValue |= (uint32_t) (colorsValue2->opacity * 255.0f) << 24;
|
|
selectedColor->value = newValue;
|
|
char buffer[16];
|
|
sprintf(buffer, "%.8X", selectedColor->value);
|
|
UITextboxClear(colorsValue, false);
|
|
UITextboxReplace(colorsValue, buffer, -1, false);
|
|
UIElementRefresh(&colorsValue->e);
|
|
UIElementRefresh(colorsPreview);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ColorsValueMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_VALUE_CHANGED) {
|
|
if (selectedColor) {
|
|
char buffer[16];
|
|
int length = 15 > colorsValue->bytes ? colorsValue->bytes : 15;
|
|
memcpy(buffer, colorsValue->string, length);
|
|
buffer[length] = 0;
|
|
selectedColor->value = strtol(buffer, NULL, 16);
|
|
UITableResizeColumns(colorsTable);
|
|
UIElementRefresh(&colorsTable->e);
|
|
UIColorToHSV(selectedColor->value, &colorsValue2->hue, &colorsValue2->saturation, &colorsValue2->value);
|
|
colorsValue2->opacity = (selectedColor->value >> 24) / 255.0f;
|
|
UIElementRefresh(&colorsValue2->e);
|
|
UIElementRefresh(colorsPreview);
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ColorCompare(const void *_left, const void *_right) {
|
|
Color *left = *(Color **) _left;
|
|
Color *right = *(Color **) _right;
|
|
return StringCompareRaw(left->key.buffer, left->key.byteCount, right->key.buffer, right->key.byteCount);
|
|
}
|
|
|
|
void ColorListRefresh() {
|
|
colorsTable->itemCount = arrlenu(styleSet.colors);
|
|
qsort(styleSet.colors, colorsTable->itemCount, sizeof(Color *), ColorCompare);
|
|
UITableResizeColumns(colorsTable);
|
|
UIElementRefresh(&colorsTable->e);
|
|
|
|
UITextboxClear(colorsTextbox, false);
|
|
UIElementRefresh(&colorsTextbox->e);
|
|
}
|
|
|
|
void ButtonAddColor(void *_unused) {
|
|
if (!colorsTextbox->bytes) {
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
if (styleSet.colors[i]->key.byteCount == (size_t) colorsTextbox->bytes
|
|
&& 0 == memcmp(styleSet.colors[i]->key.buffer, colorsTextbox->string, colorsTextbox->bytes)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Color *color = calloc(1, sizeof(Color));
|
|
color->id = ++styleSet.lastID;
|
|
color->key.buffer = malloc((color->key.byteCount = colorsTextbox->bytes));
|
|
memcpy(color->key.buffer, colorsTextbox->string, color->key.byteCount);
|
|
arrput(styleSet.colors, color);
|
|
|
|
ColorsDialogSetSelected(color);
|
|
ColorListRefresh();
|
|
}
|
|
|
|
void ButtonDeleteColor(void *_unused) {
|
|
if (!selectedColor) {
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.colors); i++) {
|
|
if (styleSet.colors[i] == selectedColor) {
|
|
arrdel(styleSet.colors, i);
|
|
ColorsDialogSetSelected(NULL);
|
|
ColorListRefresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------- Item lists -------------------
|
|
|
|
void DoAddItemMod(void ***array, void *item, int index, int undoField) {
|
|
ModData undo = { 0 };
|
|
undo.tag = undoField + 1;
|
|
undo.deleteLayer.index = index;
|
|
ModPushUndo(&undo);
|
|
arrins(*array, index, item);
|
|
}
|
|
|
|
void DoDeleteItemMod(void ***array, int index, int undoField) {
|
|
ModData undo = { 0 };
|
|
undo.tag = undoField + 1;
|
|
undo.addLayer.layer = (Layer *) (*array)[index];
|
|
undo.addLayer.index = index;
|
|
ModPushUndo(&undo);
|
|
arrdel(*array, index);
|
|
}
|
|
|
|
void ButtonDeleteItem(void *selected, void **array, int field) {
|
|
if (!selected) return;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = field + 1;
|
|
bool found = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(array); i++) {
|
|
if (array[i] == selected) {
|
|
mod.deleteLayer.index = i;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(found);
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void DesignerArrayOp(RfState *state, RfItem *item, void *pointer) {
|
|
if (state->op == OP_MAKE_UI) {
|
|
((MakeUIState *) state)->recurse = false;
|
|
} else {
|
|
RfArrayOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
// ------------------- Fonts -------------------
|
|
|
|
BasicFontKerningEntry *kerningEntries;
|
|
|
|
bool ImportFont(char *fileData) {
|
|
arrfree(kerningEntries);
|
|
stbtt_fontinfo font = {};
|
|
|
|
if (!stbtt_InitFont(&font, (uint8_t *) fileData, 0)) {
|
|
return false;
|
|
}
|
|
|
|
float scale = stbtt_ScaleForPixelHeight(&font, 100);
|
|
|
|
const char *charactersToImport = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
|
|
|
|
for (uintptr_t character = 0; charactersToImport[character]; character++) {
|
|
int glyphIndex = stbtt_FindGlyphIndex(&font, charactersToImport[character]);
|
|
stbtt_vertex *vertices;
|
|
int vertexCount = stbtt_GetGlyphShape(&font, glyphIndex, &vertices);
|
|
// printf("importing glyph for character '%c'\n", charactersToImport[character]);
|
|
|
|
int x0, y0, x1, y1, advanceWidth, leftSideBearing;
|
|
stbtt_GetGlyphHMetrics(&font, glyphIndex, &advanceWidth, &leftSideBearing);
|
|
stbtt_GetGlyphBox(&font, glyphIndex, &x0, &y0, &x1, &y1);
|
|
// printf("\t%d, %d, %d, %d, %d, %d\n", x0, x1, y0, y1, advanceWidth, leftSideBearing);
|
|
|
|
Style *style = calloc(1, sizeof(Style));
|
|
style->name.buffer = malloc(32);
|
|
style->name.byteCount = snprintf(style->name.buffer, 32, "Glyph %c", charactersToImport[character]);
|
|
style->id = ++styleSet.lastID;
|
|
Layer *metrics = calloc(1, sizeof(Layer));
|
|
metrics->id = ++styleSet.lastID;
|
|
metrics->name.buffer = malloc(16);
|
|
metrics->name.byteCount = snprintf(metrics->name.buffer, 16, "Metrics");
|
|
metrics->base.tag = LayerBase_metrics + 1;
|
|
metrics->base.metrics.minimumSize.width = x0; // Put these somewhere with 16 bits of space...
|
|
metrics->base.metrics.maximumSize.width = x1;
|
|
metrics->base.metrics.minimumSize.height = y0;
|
|
metrics->base.metrics.maximumSize.height = y1;
|
|
metrics->base.metrics.preferredSize.width = advanceWidth;
|
|
metrics->isMetricsLayer = true;
|
|
arrput(style->layers, metrics->id);
|
|
arrput(styleSet.layers, metrics);
|
|
arrput(styleSet.styles, style);
|
|
|
|
Layer *layer = calloc(1, sizeof(Layer));
|
|
layer->id = ++styleSet.lastID;
|
|
layer->name.buffer = malloc(16);
|
|
layer->name.byteCount = snprintf(layer->name.buffer, 16, "Path");
|
|
layer->base.tag = LayerBase_path + 1;
|
|
layer->mode = LAYER_MODE_BACKGROUND;
|
|
layer->position.r = 100;
|
|
layer->position.b = 100;
|
|
arrput(style->layers, layer->id);
|
|
arrput(styleSet.layers, layer);
|
|
|
|
LayerPath *layerPath = &layer->base.path;
|
|
layerPath->closed = true;
|
|
layerPath->alpha = 255;
|
|
|
|
PathFill fill = {};
|
|
fill.mode.tag = PathFillMode_solid + 1;
|
|
fill.paint.tag = Paint_solid + 1;
|
|
fill.paint.solid.color = 0xFF000000;
|
|
arrput(layerPath->fills, fill);
|
|
|
|
int i = 0;
|
|
|
|
while (i < vertexCount) {
|
|
if (vertices[i].type == STBTT_vmove) {
|
|
// printf("move to %d, %d\n", vertices[i].x, vertices[i].y);
|
|
|
|
if (arrlen(layerPath->points)) {
|
|
PathPoint *p = &arrlast(layerPath->points);
|
|
p->x1 = -1e6;
|
|
}
|
|
|
|
PathPoint point = {};
|
|
point.x0 = vertices[i].x;
|
|
point.y0 = vertices[i].y;
|
|
arrput(layerPath->points, point);
|
|
} else if (vertices[i].type == STBTT_vline) {
|
|
// printf("line to %d, %d\n", vertices[i].x, vertices[i].y);
|
|
|
|
if (!arrlen(layerPath->points)) return false;
|
|
PathPoint *p = &arrlast(layerPath->points);
|
|
p->x1 = p->x0;
|
|
p->y1 = p->y0;
|
|
p->x2 = vertices[i].x;
|
|
p->y2 = vertices[i].y;
|
|
PathPoint point = {};
|
|
point.x0 = vertices[i].x;
|
|
point.y0 = vertices[i].y;
|
|
arrput(layerPath->points, point);
|
|
} else if (vertices[i].type == STBTT_vcurve) {
|
|
// printf("curve to %d, %d via %d, %d\n", vertices[i].x, vertices[i].y, vertices[i].cx, vertices[i].cy);
|
|
|
|
if (!arrlen(layerPath->points)) return false;
|
|
PathPoint *p = &arrlast(layerPath->points);
|
|
p->x1 = (p->x0 + 0.6667f * (vertices[i].cx - p->x0));
|
|
p->y1 = (p->y0 + 0.6667f * (vertices[i].cy - p->y0));
|
|
p->x2 = (vertices[i].x + 0.6667f * (vertices[i].cx - vertices[i].x));
|
|
p->y2 = (vertices[i].y + 0.6667f * (vertices[i].cy - vertices[i].y));
|
|
PathPoint point = {};
|
|
point.x0 = vertices[i].x;
|
|
point.y0 = vertices[i].y;
|
|
arrput(layerPath->points, point);
|
|
} else if (vertices[i].type == STBTT_vcubic) {
|
|
// printf("cubic to %d, %d\n", vertices[i].x, vertices[i].y);
|
|
|
|
if (!arrlen(layerPath->points)) return false;
|
|
PathPoint *p = &arrlast(layerPath->points);
|
|
p->x1 = vertices[i].cx;
|
|
p->y1 = vertices[i].cy;
|
|
p->x2 = vertices[i].cx1;
|
|
p->y2 = vertices[i].cy1;
|
|
PathPoint point = {};
|
|
point.x0 = vertices[i].x;
|
|
point.y0 = vertices[i].y;
|
|
arrput(layerPath->points, point);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(layerPath->points); i++) {
|
|
layerPath->points[i].x0 *= scale;
|
|
if (layerPath->points[i].x1 != -1e6) layerPath->points[i].x1 *= scale;
|
|
layerPath->points[i].x2 *= scale;
|
|
layerPath->points[i].y0 *= scale;
|
|
layerPath->points[i].y1 *= scale;
|
|
layerPath->points[i].y2 *= scale;
|
|
}
|
|
|
|
stbtt_FreeShape(&font, vertices);
|
|
}
|
|
|
|
int ascent, descent, lineGap;
|
|
stbtt_GetFontVMetrics(&font, &ascent, &descent, &lineGap);
|
|
|
|
{
|
|
Constant *constant = calloc(1, sizeof(Constant));
|
|
constant->key.buffer = malloc(16);
|
|
constant->key.byteCount = strlen(strcpy((char *) constant->key.buffer, "ascent"));
|
|
constant->value.buffer = malloc(16);
|
|
constant->value.byteCount = snprintf((char *) constant->value.buffer, 16, "%d", ascent);
|
|
arrput(styleSet.constants, constant);
|
|
}
|
|
|
|
{
|
|
Constant *constant = calloc(1, sizeof(Constant));
|
|
constant->key.buffer = malloc(16);
|
|
constant->key.byteCount = strlen(strcpy((char *) constant->key.buffer, "descent"));
|
|
constant->value.buffer = malloc(16);
|
|
constant->value.byteCount = snprintf((char *) constant->value.buffer, 16, "%d", descent);
|
|
arrput(styleSet.constants, constant);
|
|
}
|
|
|
|
{
|
|
Constant *constant = calloc(1, sizeof(Constant));
|
|
constant->key.buffer = malloc(16);
|
|
constant->key.byteCount = strlen(strcpy((char *) constant->key.buffer, "lineGap"));
|
|
constant->value.buffer = malloc(16);
|
|
constant->value.byteCount = snprintf((char *) constant->value.buffer, 16, "%d", lineGap);
|
|
arrput(styleSet.constants, constant);
|
|
}
|
|
|
|
for (uintptr_t c1 = 0; charactersToImport[c1]; c1++) {
|
|
for (uintptr_t c2 = 0; charactersToImport[c2]; c2++) {
|
|
int xAdvance = stbtt_GetGlyphKernAdvance(&font,
|
|
stbtt_FindGlyphIndex(&font, charactersToImport[c1]),
|
|
stbtt_FindGlyphIndex(&font, charactersToImport[c2]));
|
|
if (!xAdvance) continue;
|
|
|
|
BasicFontKerningEntry entry = { 0 };
|
|
entry.leftGlyphIndex = charactersToImport[c1];
|
|
entry.rightGlyphIndex = charactersToImport[c2];
|
|
entry.xAdvance = xAdvance;
|
|
arrput(kerningEntries, entry);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ExportFont(const char *path) {
|
|
FILE *f = fopen(path, "wb");
|
|
|
|
BasicFontHeader header = { BASIC_FONT_SIGNATURE };
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.constants); i++) {
|
|
if (styleSet.constants[i]->key.byteCount == 6 && 0 == memcmp(styleSet.constants[i]->key.buffer, "ascent", 6)) {
|
|
header.ascender = atoi((char *) styleSet.constants[i]->value.buffer);
|
|
} else if (styleSet.constants[i]->key.byteCount == 7 && 0 == memcmp(styleSet.constants[i]->key.buffer, "descent", 7)) {
|
|
header.descender = atoi((char *) styleSet.constants[i]->value.buffer);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
Style *style = styleSet.styles[i];
|
|
|
|
if (style->name.byteCount > 6 && 0 == memcmp(style->name.buffer, "Glyph ", 6)) {
|
|
header.glyphCount++;
|
|
}
|
|
}
|
|
|
|
header.kerningEntries = arrlenu(kerningEntries);
|
|
|
|
fwrite(&header, 1, sizeof(BasicFontHeader), f);
|
|
|
|
uint32_t offsetToPoints = sizeof(BasicFontHeader)
|
|
+ sizeof(BasicFontGlyph) * header.glyphCount
|
|
+ sizeof(BasicFontKerningEntry) * header.kerningEntries;
|
|
|
|
uint32_t *characters = NULL;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
Style *style = styleSet.styles[i];
|
|
|
|
if (style->name.byteCount > 6 && 0 == memcmp(style->name.buffer, "Glyph ", 6)) {
|
|
BasicFontGlyph glyph = { 0 };
|
|
assert(arrlenu(style->layers) == 2);
|
|
glyph.codepoint = ((char *) style->name.buffer)[6];
|
|
LayerMetrics *metrics = &LayerLookup(style->layers[0])->base.metrics;
|
|
LayerPath *path = &LayerLookup(style->layers[1])->base.path;
|
|
glyph.xAdvance = metrics->preferredSize.width;
|
|
glyph.xOffset = metrics->minimumSize.width;
|
|
glyph.yOffset = metrics->minimumSize.height;
|
|
glyph.width = metrics->maximumSize.width - metrics->minimumSize.width;
|
|
glyph.height = metrics->maximumSize.height - metrics->minimumSize.height;
|
|
glyph.pointCount = arrlenu(path->points);
|
|
glyph.offsetToPoints = offsetToPoints;
|
|
offsetToPoints += glyph.pointCount * 6 * sizeof(float);
|
|
fwrite(&glyph, 1, sizeof(BasicFontGlyph), f);
|
|
arrput(characters, glyph.codepoint);
|
|
}
|
|
}
|
|
|
|
arrfree(characters);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(kerningEntries); i++) {
|
|
BasicFontKerningEntry *entry = kerningEntries + i;
|
|
BasicFontKerningEntry copy = *entry;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(characters); i++) {
|
|
if (entry->leftGlyphIndex == characters[i]) {
|
|
copy.leftGlyphIndex = i;
|
|
}
|
|
|
|
if (entry->rightGlyphIndex == characters[i]) {
|
|
copy.rightGlyphIndex = i;
|
|
}
|
|
}
|
|
|
|
fwrite(©, 1, sizeof(BasicFontKerningEntry), f);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
Style *style = styleSet.styles[i];
|
|
|
|
if (style->name.byteCount > 6 && 0 == memcmp(style->name.buffer, "Glyph ", 6)) {
|
|
LayerPath *path = &LayerLookup(style->layers[1])->base.path;
|
|
// printf("%ld at %ld, %ld\n", i, ftell(f), arrlenu(path->points));
|
|
fwrite(path->points, 1, arrlenu(path->points) * 6 * sizeof(float), f);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
void ButtonImportFontConfirm(void *_unused) {
|
|
if (!importDialog) {
|
|
return;
|
|
}
|
|
|
|
char path[4096];
|
|
|
|
if (importPathTextbox->bytes > (ptrdiff_t) sizeof(path) - 1) {
|
|
UILabelSetContent(importPathMessage, "Path too long", -1);
|
|
UIElementRefresh(&importPathMessage->e);
|
|
return;
|
|
}
|
|
|
|
memcpy(path, importPathTextbox->string, importPathTextbox->bytes);
|
|
path[importPathTextbox->bytes] = 0;
|
|
|
|
char *fileData = LoadFile(path, NULL);
|
|
|
|
if (fileData && ImportFont(fileData)) {
|
|
ConstantListRefresh();
|
|
ClearUndoRedo();
|
|
StyleListRefresh();
|
|
UIElementDestroy(&importDialog->e);
|
|
importDialog = NULL;
|
|
} else {
|
|
UILabelSetContent(importPathMessage, "Invalid or unsupported font file.", -1);
|
|
UIElementRefresh(&importPathMessage->e);
|
|
}
|
|
|
|
free(fileData);
|
|
}
|
|
|
|
void ActionImportFont(void *_unused) {
|
|
if (importDialog) {
|
|
return;
|
|
}
|
|
|
|
importDialog = UIWindowCreate(window, UI_WINDOW_CENTER_IN_OWNER, "Import Font", 400, 100);
|
|
|
|
UIPanelCreate(&importDialog->e, UI_PANEL_GRAY | UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING);
|
|
UILabelCreate(0, 0, "Enter path to font file:", -1);
|
|
importPathTextbox = UITextboxCreate(0, 0);
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
importPathMessage = UILabelCreate(0, UI_ELEMENT_H_FILL, "", -1);
|
|
UIButtonCreate(0, 0, "Import", -1)->invoke = ButtonImportFontConfirm;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UIElementFocus(&importPathTextbox->e);
|
|
}
|
|
|
|
// ------------------- Importing SVG -------------------
|
|
|
|
void ImportSVGPath(NSVGimage *image, NSVGshape *shape) {
|
|
// TODO If shape has multiple paths, and has a contour fills,
|
|
// then the contours need to use each path separately.
|
|
|
|
if ((~shape->flags & NSVG_FLAGS_VISIBLE) || shape->opacity == 0) {
|
|
return;
|
|
}
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addLayer + 1;
|
|
mod.addLayer.index = arrlen(selected.style->layers);
|
|
|
|
Layer *layer = calloc(1, sizeof(Layer));
|
|
layer->id = ++styleSet.lastID;
|
|
layer->name.buffer = malloc(16);
|
|
layer->name.byteCount = snprintf(layer->name.buffer, 16, "path %d", mod.addLayer.index);
|
|
layer->base.tag = LayerBase_path + 1;
|
|
layer->mode = LAYER_MODE_BACKGROUND;
|
|
layer->position.r = 100;
|
|
layer->position.b = 100;
|
|
|
|
LayerPath *layerPath = &layer->base.path;
|
|
layerPath->evenOdd = shape->fillRule == NSVG_FILLRULE_EVENODD;
|
|
layerPath->closed = shape->paths->closed; // TODO See comment about multiple paths above.
|
|
layerPath->alpha = shape->opacity * 255;
|
|
|
|
float scale = 100.0f / image->width;
|
|
|
|
NSVGpath *path = shape->paths;
|
|
|
|
while (path) {
|
|
for (int i = 0; i < path->npts; i += 3) {
|
|
PathPoint point = {};
|
|
|
|
point.x0 = path->pts[i * 2 + 0] * scale;
|
|
point.y0 = path->pts[i * 2 + 1] * scale;
|
|
|
|
if (i + 1 < path->npts) {
|
|
point.x1 = path->pts[i * 2 + 2] * scale;
|
|
point.y1 = path->pts[i * 2 + 3] * scale;
|
|
} else {
|
|
point.x1 = path->pts[i * 2 + 0] * scale;
|
|
point.y1 = path->pts[i * 2 + 1] * scale;
|
|
}
|
|
|
|
if (i + 2 < path->npts) {
|
|
point.x2 = path->pts[i * 2 + 4] * scale;
|
|
point.y2 = path->pts[i * 2 + 5] * scale;
|
|
} else {
|
|
point.x2 = path->pts[i * 2 + 0] * scale;
|
|
point.y2 = path->pts[i * 2 + 1] * scale;
|
|
}
|
|
|
|
arrput(layerPath->points, point);
|
|
}
|
|
|
|
if (arrlenu(layerPath->points) && (path->closed || path->next)) {
|
|
PathPoint point = layerPath->points[0];
|
|
point.x1 = -1e6;
|
|
point.x2 = point.y1 = point.y2 = 0;
|
|
arrput(layerPath->points, point);
|
|
}
|
|
|
|
path = path->next;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < 2; i++) {
|
|
NSVGpaint *paint = i ? &shape->stroke : &shape->fill;
|
|
|
|
PathFill fill = {};
|
|
|
|
if (i) {
|
|
// TODO Dashes.
|
|
|
|
fill.mode.tag = PathFillMode_contour + 1;
|
|
fill.mode.contour.internalWidth = shape->strokeWidth * 0.5f + 0.5f; // TODO Floating-point contour widths?
|
|
fill.mode.contour.externalWidth = shape->strokeWidth * 0.5f;
|
|
fill.mode.contour.joinMode = shape->strokeLineJoin == NSVG_JOIN_ROUND ? JOIN_MODE_ROUND
|
|
: shape->strokeLineJoin == NSVG_JOIN_BEVEL ? JOIN_MODE_BEVEL : JOIN_MODE_MITER;
|
|
fill.mode.contour.capMode = shape->strokeLineCap == NSVG_CAP_BUTT ? CAP_MODE_FLAT
|
|
: shape->strokeLineCap == NSVG_CAP_ROUND ? CAP_MODE_ROUND : CAP_MODE_SQUARE;
|
|
fill.mode.contour.miterLimit = shape->strokeWidth * shape->miterLimit;
|
|
} else {
|
|
fill.mode.tag = PathFillMode_solid + 1;
|
|
}
|
|
|
|
if (paint->type == NSVG_PAINT_COLOR) {
|
|
fill.paint.tag = Paint_solid + 1;
|
|
fill.paint.solid.color = (paint->color & 0xFF00FF00) | ((paint->color & 0xFF) << 16) | ((paint->color & 0xFF0000) >> 16);
|
|
} else if (paint->type == NSVG_PAINT_LINEAR_GRADIENT) {
|
|
NSVGgradient *gradient = paint->gradient;
|
|
fill.paint.tag = Paint_linearGradient + 1;
|
|
|
|
fill.paint.linearGradient.transformX = gradient->xform[1] * image->width;
|
|
fill.paint.linearGradient.transformY = gradient->xform[3] * image->height;
|
|
fill.paint.linearGradient.transformStart = gradient->xform[5];
|
|
|
|
size_t stopCount = gradient->nstops;
|
|
if (stopCount > 16) stopCount = 16;
|
|
|
|
for (int i = 0; i < gradient->nstops; i++) {
|
|
GradientStop stop = {};
|
|
uint32_t color = gradient->stops[i].color;
|
|
stop.color = (color & 0xFF00FF00) | ((color & 0xFF) << 16) | ((color & 0xFF0000) >> 16);
|
|
stop.position = gradient->stops[i].offset * 100;
|
|
arrput(fill.paint.linearGradient.stops, stop);
|
|
}
|
|
|
|
if (gradient->spread == NSVG_SPREAD_PAD) {
|
|
fill.paint.linearGradient.repeat = GRADIENT_REPEAT_CLAMP;
|
|
} else if (gradient->spread == NSVG_SPREAD_REFLECT) {
|
|
fill.paint.linearGradient.repeat = GRADIENT_REPEAT_MIRROR;
|
|
} else if (gradient->spread == NSVG_SPREAD_REPEAT) {
|
|
fill.paint.linearGradient.repeat = GRADIENT_REPEAT_NORMAL;
|
|
}
|
|
} else if (paint->type == NSVG_PAINT_RADIAL_GRADIENT) {
|
|
NSVGgradient *gradient = paint->gradient;
|
|
fill.paint.tag = Paint_radialGradient + 1;
|
|
|
|
fill.paint.radialGradient.transform0 = gradient->xform[0] * image->width;
|
|
fill.paint.radialGradient.transform1 = gradient->xform[2] * image->width;
|
|
fill.paint.radialGradient.transform2 = gradient->xform[4];
|
|
fill.paint.radialGradient.transform3 = gradient->xform[1] * image->height;
|
|
fill.paint.radialGradient.transform4 = gradient->xform[3] * image->height;
|
|
fill.paint.radialGradient.transform5 = gradient->xform[5];
|
|
|
|
size_t stopCount = gradient->nstops;
|
|
if (stopCount > 16) stopCount = 16;
|
|
|
|
for (int i = 0; i < gradient->nstops; i++) {
|
|
GradientStop stop = {};
|
|
uint32_t color = gradient->stops[i].color;
|
|
stop.color = (color & 0xFF00FF00) | ((color & 0xFF) << 16) | ((color & 0xFF0000) >> 16);
|
|
stop.position = gradient->stops[i].offset * 100;
|
|
arrput(fill.paint.radialGradient.stops, stop);
|
|
}
|
|
|
|
if (gradient->spread == NSVG_SPREAD_PAD) {
|
|
fill.paint.radialGradient.repeat = GRADIENT_REPEAT_CLAMP;
|
|
} else if (gradient->spread == NSVG_SPREAD_REFLECT) {
|
|
fill.paint.radialGradient.repeat = GRADIENT_REPEAT_MIRROR;
|
|
} else if (gradient->spread == NSVG_SPREAD_REPEAT) {
|
|
fill.paint.radialGradient.repeat = GRADIENT_REPEAT_NORMAL;
|
|
}
|
|
}
|
|
|
|
arrput(layerPath->fills, fill);
|
|
}
|
|
|
|
mod.addLayer.layer = layer;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ImportSVGImage(NSVGimage *image) {
|
|
previewWidth->position = image->width / 1000.0f;
|
|
previewHeight->position = image->height / 1000.0f;
|
|
|
|
NSVGshape *shape = image->shapes;
|
|
|
|
while (shape) {
|
|
if (shape->paths) {
|
|
ImportSVGPath(image, shape);
|
|
}
|
|
|
|
shape = shape->next;
|
|
}
|
|
}
|
|
|
|
void ButtonImportSVGConfirm(void *_unused) {
|
|
if (!importDialog) {
|
|
return;
|
|
}
|
|
|
|
char path[4096];
|
|
|
|
if (importPathTextbox->bytes > (ptrdiff_t) sizeof(path) - 1) {
|
|
UILabelSetContent(importPathMessage, "Path too long", -1);
|
|
UIElementRefresh(&importPathMessage->e);
|
|
return;
|
|
}
|
|
|
|
memcpy(path, importPathTextbox->string, importPathTextbox->bytes);
|
|
path[importPathTextbox->bytes] = 0;
|
|
|
|
NSVGimage *image = nsvgParseFromFile(path, "px", 96.0f);
|
|
|
|
if (image) {
|
|
UIElementDestroy(&importDialog->e);
|
|
importDialog = NULL;
|
|
ImportSVGImage(image);
|
|
nsvgDelete(image);
|
|
} else {
|
|
UILabelSetContent(importPathMessage, "Invalid or unsupported SVG file.", -1);
|
|
UIElementRefresh(&importPathMessage->e);
|
|
}
|
|
}
|
|
|
|
void ButtonImportSVG(void *_unused) {
|
|
if (importDialog || !selected.style) {
|
|
return;
|
|
}
|
|
|
|
importDialog = UIWindowCreate(window, UI_WINDOW_CENTER_IN_OWNER, "Import SVG", 400, 100);
|
|
|
|
UIPanelCreate(&importDialog->e, UI_PANEL_GRAY | UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING);
|
|
UILabelCreate(0, 0, "Enter path to SVG file:", -1);
|
|
importPathTextbox = UITextboxCreate(0, 0);
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
importPathMessage = UILabelCreate(0, UI_ELEMENT_H_FILL, "", -1);
|
|
UIButtonCreate(0, 0, "Import", -1)->invoke = ButtonImportSVGConfirm;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UIElementFocus(&importPathTextbox->e);
|
|
}
|
|
|
|
// ------------------- Layers -------------------
|
|
|
|
void CleanupUnusedLayers(void *_unused) {
|
|
ClearUndoRedo();
|
|
|
|
struct {
|
|
uint64_t key;
|
|
bool value;
|
|
} *usedLayers = NULL;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
for (uintptr_t j = 0; j < arrlenu(styleSet.styles[i]->layers); j++) {
|
|
hmput(usedLayers, styleSet.styles[i]->layers[j], true);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(styleSet.layers); j++) {
|
|
if (hmget(usedLayers, styleSet.layers[j]->id)) {
|
|
continue;
|
|
}
|
|
|
|
printf("remove %ld\n", styleSet.layers[j]->id);
|
|
|
|
RfState state = { 0 };
|
|
state.op = RF_OP_FREE;
|
|
state.allocate = RfRealloc;
|
|
RfItem item = { 0 };
|
|
item.type = &Layer_Type;
|
|
item.byteCount = sizeof(Layer);
|
|
item.type->op(&state, &item, styleSet.layers[j]);
|
|
|
|
arrdel(styleSet.layers, j);
|
|
j--;
|
|
}
|
|
}
|
|
|
|
int TableLayersMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_TABLE_GET_ITEM) {
|
|
UITableGetItem *m = (UITableGetItem *) dp;
|
|
Layer *l = LayerLookup(selected.style->layers[m->index]);
|
|
m->isSelected = selected.layer == l;
|
|
return snprintf(m->buffer, m->bufferBytes, "%.*s", (int) l->name.byteCount, (char *) l->name.buffer);
|
|
} else if (message == UI_MSG_CLICKED || message == UI_MSG_MOUSE_DRAG) {
|
|
int index = UITableHitTest(tableLayers, element->window->cursorX, element->window->cursorY);
|
|
|
|
if (index != -1) {
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, LayerLookup(selected.style->layers[index]), NULL, NULL));
|
|
}
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
UITableResizeColumns(tableLayers);
|
|
UIElementRefresh(&tableLayers->e);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ModAddLayerOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModAddLayer *mod = (ModAddLayer *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_deleteLayer + 1;
|
|
undo.deleteLayer.index = mod->index;
|
|
ModPushUndo(&undo);
|
|
|
|
arrins(selected.style->layers, mod->index, mod->layer->id);
|
|
arrput(styleSet.layers, mod->layer);
|
|
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, mod->layer, NULL, NULL));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ButtonAddBoxLayer(void *_unused) {
|
|
if (!selected.style) return;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addLayer + 1;
|
|
mod.addLayer.index = arrlen(selected.style->layers);
|
|
|
|
Layer *layer = calloc(1, sizeof(Layer));
|
|
layer->id = ++styleSet.lastID;
|
|
layer->name.buffer = malloc(16);
|
|
layer->name.byteCount = snprintf(layer->name.buffer, 16, "box %d", mod.addLayer.index);
|
|
layer->base.tag = LayerBase_box + 1;
|
|
layer->position.r = 100;
|
|
layer->position.b = 100;
|
|
|
|
mod.addLayer.layer = layer;
|
|
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonAddTextLayer(void *_unused) {
|
|
if (!selected.style) return;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addLayer + 1;
|
|
mod.addLayer.index = arrlen(selected.style->layers);
|
|
|
|
Layer *layer = calloc(1, sizeof(Layer));
|
|
layer->id = ++styleSet.lastID;
|
|
layer->name.buffer = malloc(16);
|
|
layer->name.byteCount = snprintf(layer->name.buffer, 16, "text %d", mod.addLayer.index);
|
|
layer->base.tag = LayerBase_text + 1;
|
|
layer->mode = LAYER_MODE_CONTENT;
|
|
|
|
mod.addLayer.layer = layer;
|
|
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonAddPathLayer(void *_unused) {
|
|
if (!selected.style) return;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addLayer + 1;
|
|
mod.addLayer.index = arrlen(selected.style->layers);
|
|
|
|
Layer *layer = calloc(1, sizeof(Layer));
|
|
layer->id = ++styleSet.lastID;
|
|
layer->name.buffer = malloc(16);
|
|
layer->name.byteCount = snprintf(layer->name.buffer, 16, "path %d", mod.addLayer.index);
|
|
layer->base.tag = LayerBase_path + 1;
|
|
layer->mode = LAYER_MODE_BACKGROUND;
|
|
|
|
mod.addLayer.layer = layer;
|
|
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonAddLayer(void *_unused) {
|
|
UIMenu *menu = UIMenuCreate(&buttonAddLayer->e, 0);
|
|
UIMenuAddItem(menu, 0, "Add box...", -1, ButtonAddBoxLayer, NULL);
|
|
UIMenuAddItem(menu, 0, "Add text...", -1, ButtonAddTextLayer, NULL);
|
|
UIMenuAddItem(menu, 0, "Add path...", -1, ButtonAddPathLayer, NULL);
|
|
UIMenuShow(menu);
|
|
}
|
|
|
|
void ButtonDuplicateLayer(void *_unused) {
|
|
if (!selected.layer) return;
|
|
RfGrowableBuffer state = { 0 };
|
|
state.data = SaveToGrowableBuffer(&Layer_Type, sizeof(Layer), NULL, selected.layer);
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addLayer + 1;
|
|
mod.addLayer.index = arrlen(selected.style->layers);
|
|
Layer *layer = calloc(1, sizeof(Layer));
|
|
state.s.version = saveFormatVersion;
|
|
state.data.byteCount -= sizeof(uint32_t);
|
|
state.s.allocate = RfRealloc;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
RfItem item = { 0 };
|
|
item.type = &Layer_Type;
|
|
item.byteCount = sizeof(Layer);
|
|
state.s.op = RF_OP_LOAD;
|
|
item.type->op(&state.s, &item, layer);
|
|
layer->id = ++styleSet.lastID;
|
|
mod.addLayer.layer = layer;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonAddExistingLayer2(void *_layer) {
|
|
if (!selected.style) return;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addLayer + 1;
|
|
mod.addLayer.index = arrlen(selected.style->layers);
|
|
mod.addLayer.layer = (Layer *) _layer;
|
|
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonAddExistingLayer(void *_unused) {
|
|
UIMenu *menu = UIMenuCreate(&buttonAddExistingLayer->e, 0);
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
Style *style = styleSet.styles[i];
|
|
|
|
for (uintptr_t j = 0; j < arrlenu(style->layers); j++) {
|
|
Layer *layer = LayerLookup(style->layers[j]);
|
|
if (!layer) continue;
|
|
char name[64];
|
|
snprintf(name, sizeof(name), "%.*s:%.*s", (int) style->name.byteCount, (char *) style->name.buffer,
|
|
(int) layer->name.byteCount, (char *) layer->name.buffer);
|
|
UIMenuAddItem(menu, 0, name, -1, ButtonAddExistingLayer2, layer);
|
|
}
|
|
}
|
|
|
|
UIMenuShow(menu);
|
|
}
|
|
|
|
void ModDeleteLayerOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModDeleteLayer *mod = (ModDeleteLayer *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_addLayer + 1;
|
|
undo.addLayer.layer = LayerLookup(selected.style->layers[mod->index]);
|
|
undo.addLayer.index = mod->index;
|
|
ModPushUndo(&undo);
|
|
arrdel(selected.style->layers, mod->index);
|
|
|
|
if ((uintptr_t) mod->index < arrlenu(selected.style->layers)) {
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, LayerLookup(selected.style->layers[mod->index]), NULL, NULL));
|
|
} else if (arrlenu(selected.style->layers)) {
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, LayerLookup(arrlast(selected.style->layers)), NULL, NULL));
|
|
} else {
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, NULL, NULL, NULL));
|
|
}
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ButtonDeleteLayer(void *_unused) {
|
|
if (!selected.layer) return;
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_deleteLayer + 1;
|
|
bool found = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(selected.style->layers); i++) {
|
|
if (selected.style->layers[i] == selected.layer->id) {
|
|
mod.deleteLayer.index = i;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(found);
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonDeleteLayerInAllStyles(void *_unused) {
|
|
if (!selected.layer) {
|
|
return;
|
|
}
|
|
|
|
ClearUndoRedo();
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.styles); i++) {
|
|
for (uintptr_t j = 0; j < arrlenu(styleSet.styles[i]->layers); j++) {
|
|
if (styleSet.styles[i]->layers[j] == selected.layer->id) {
|
|
arrdel(styleSet.styles[i]->layers, j);
|
|
j--;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool found = false;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(styleSet.layers); i++) {
|
|
if (styleSet.layers[i] == selected.layer) {
|
|
arrdel(styleSet.layers, i);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(found);
|
|
|
|
RfState state = { 0 };
|
|
state.op = RF_OP_FREE;
|
|
state.allocate = RfRealloc;
|
|
RfItem item = { 0 };
|
|
item.type = &Layer_Type;
|
|
item.byteCount = sizeof(Layer);
|
|
item.type->op(&state, &item, selected.layer);
|
|
|
|
StyleListRefresh();
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, NULL, NULL, NULL));
|
|
}
|
|
|
|
void ModSwapLayersOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModSwapLayers *mod = (ModSwapLayers *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
assert(mod->index >= 0 && mod->index < arrlen(selected.style->layers) - 1);
|
|
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_swapLayers + 1;
|
|
undo.swapLayers.index = mod->index;
|
|
ModPushUndo(&undo);
|
|
|
|
uint64_t temporary = selected.style->layers[mod->index];
|
|
selected.style->layers[mod->index] = selected.style->layers[mod->index + 1];
|
|
selected.style->layers[mod->index + 1] = temporary;
|
|
|
|
UIElementRefresh(&tableLayers->e);
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
// ------------------- Sequences -------------------
|
|
|
|
int TableSequencesMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_TABLE_GET_ITEM) {
|
|
UITableGetItem *m = (UITableGetItem *) dp;
|
|
Sequence *s = selected.layer->sequences[m->index];
|
|
m->isSelected = selected.sequence == s;
|
|
return snprintf(m->buffer, m->bufferBytes, "%s%s%s%s%s%s%s%s%s%s",
|
|
((StringOption *) PrimaryState_Type.fields[s->primaryState].item.options)->string,
|
|
s->flagFocused ? " (focused)" : "",
|
|
s->flagChecked ? " (checked)" : "",
|
|
s->flagIndeterminate ? " (indeterminate)" : "",
|
|
s->flagDefault ? " (default)" : "",
|
|
s->flagItemFocus ? " (list item focus)" : "",
|
|
s->flagListFocus ? " (list focus)" : "",
|
|
s->flagBeforeEnter ? " (before enter)" : "",
|
|
s->flagAfterExit ? " (after exit)" : "",
|
|
s->flagSelected ? " (selected)" : "");
|
|
} else if (message == UI_MSG_CLICKED || message == UI_MSG_MOUSE_DRAG) {
|
|
int index = UITableHitTest(tableSequences, element->window->cursorX, element->window->cursorY);
|
|
|
|
if (index != -1) {
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, selected.layer, selected.layer->sequences[index], NULL));
|
|
}
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
UITableResizeColumns(tableSequences);
|
|
UIElementRefresh(&tableSequences->e);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ModAddSequenceOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModAddSequence *mod = (ModAddSequence *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
DoAddItemMod((void ***) &selected.layer->sequences, mod->sequence, mod->index, ModData_deleteSequence);
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, selected.layer, mod->sequence, NULL));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ButtonAddSequence(void *_unused) {
|
|
if (!selected.layer) return;
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addSequence + 1;
|
|
mod.addSequence.sequence = calloc(1, sizeof(Sequence));
|
|
mod.addSequence.index = arrlen(selected.layer->sequences);
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ModDeleteSequenceOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModDeleteSequence *mod = (ModDeleteSequence *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
DoDeleteItemMod((void ***) &selected.layer->sequences, mod->index, ModData_addSequence);
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, selected.layer, NULL, NULL));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ButtonDeleteSequence(void *_unused) {
|
|
if (!selected.sequence) return;
|
|
ButtonDeleteItem(selected.sequence, (void **) selected.layer->sequences, ModData_deleteSequence);
|
|
}
|
|
|
|
void ButtonMoveSequenceUp(void *_unused) {
|
|
if (!selected.sequence) {
|
|
return;
|
|
}
|
|
|
|
uintptr_t index = 0;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(selected.layer->sequences); i++) {
|
|
if (selected.layer->sequences[i] == selected.sequence) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index <= 1) {
|
|
return;
|
|
}
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_swapSequences + 1;
|
|
mod.swapSequences.index = index - 1;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ButtonMoveSequenceDown(void *_unused) {
|
|
if (!selected.sequence) {
|
|
return;
|
|
}
|
|
|
|
uintptr_t index = 0;
|
|
|
|
for (uintptr_t i = 0; i < arrlenu(selected.layer->sequences); i++) {
|
|
if (selected.layer->sequences[i] == selected.sequence) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index == arrlenu(selected.layer->sequences) - 1) {
|
|
return;
|
|
}
|
|
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_swapSequences + 1;
|
|
mod.swapSequences.index = index;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ModSwapSequencesOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModSwapSequences *mod = (ModSwapSequences *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
assert(mod->index >= 0 && mod->index < arrlen(selected.layer->sequences) - 1);
|
|
|
|
ModData undo = { 0 };
|
|
undo.tag = ModData_swapSequences + 1;
|
|
undo.swapSequences.index = mod->index;
|
|
ModPushUndo(&undo);
|
|
|
|
Sequence *temporary = selected.layer->sequences[mod->index];
|
|
selected.layer->sequences[mod->index] = selected.layer->sequences[mod->index + 1];
|
|
selected.layer->sequences[mod->index + 1] = temporary;
|
|
|
|
UIElementRefresh(&tableSequences->e);
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
// ------------------- Keyframes -------------------
|
|
|
|
int TableKeyframesMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_TABLE_GET_ITEM) {
|
|
UITableGetItem *m = (UITableGetItem *) dp;
|
|
Keyframe *k = selected.sequence->keyframes[m->index];
|
|
m->isSelected = selected.keyframe == k;
|
|
return snprintf(m->buffer, m->bufferBytes, "%d%%", k->progress);
|
|
} else if (message == UI_MSG_CLICKED || message == UI_MSG_MOUSE_DRAG) {
|
|
int index = UITableHitTest(tableKeyframes, element->window->cursorX, element->window->cursorY);
|
|
|
|
if (index != -1) {
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, selected.layer, selected.sequence, selected.sequence->keyframes[index]));
|
|
}
|
|
} else if (message == MSG_PROPERTY_CHANGED) {
|
|
UITableResizeColumns(tableKeyframes);
|
|
UIElementRefresh(&tableKeyframes->e);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ModAddKeyframeOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModAddKeyframe *mod = (ModAddKeyframe *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
DoAddItemMod((void ***) &selected.sequence->keyframes, mod->keyframe, mod->index, ModData_deleteKeyframe);
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, selected.layer, selected.sequence, mod->keyframe));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ButtonAddKeyframe(void *_unused) {
|
|
if (!selected.sequence) return;
|
|
ModData mod = { 0 };
|
|
mod.tag = ModData_addKeyframe + 1;
|
|
mod.addKeyframe.keyframe = calloc(1, sizeof(Keyframe));
|
|
mod.addKeyframe.index = arrlen(selected.sequence->keyframes);
|
|
mod.addKeyframe.keyframe->progress = 100;
|
|
ModApply(&mod);
|
|
}
|
|
|
|
void ModDeleteKeyframeOp(RfState *state, RfItem *item, void *pointer) {
|
|
ModDeleteKeyframe *mod = (ModDeleteKeyframe *) pointer;
|
|
|
|
if (state->op == OP_DO_MOD) {
|
|
DoDeleteItemMod((void ***) &selected.sequence->keyframes, mod->index, ModData_addKeyframe);
|
|
SetSelectedItems(MOD_CONTEXT(selected.style, selected.layer, selected.sequence, NULL));
|
|
} else {
|
|
RfStructOp(state, item, pointer);
|
|
}
|
|
}
|
|
|
|
void ButtonDeleteKeyframe(void *_unused) {
|
|
if (!selected.keyframe) return;
|
|
ButtonDeleteItem(selected.keyframe, (void **) selected.sequence->keyframes, ModData_deleteKeyframe);
|
|
}
|
|
|
|
// ------------------- Actions -------------------
|
|
|
|
void ActionSave(void *_unused) {
|
|
RfData data = SaveToGrowableBuffer(&StyleSet_Type, sizeof(styleSet), NULL, &styleSet);
|
|
FILE *f = fopen(filePath, "wb");
|
|
fwrite(&saveFormatVersion, 1, sizeof(uint32_t), f);
|
|
fwrite(data.buffer, 1, data.byteCount, f);
|
|
fclose(f);
|
|
free(data.buffer);
|
|
}
|
|
|
|
char *LoadFile(const char *inputFileName, size_t *byteCount) {
|
|
FILE *inputFile = fopen(inputFileName, "rb");
|
|
|
|
if (!inputFile) {
|
|
return NULL;
|
|
}
|
|
|
|
fseek(inputFile, 0, SEEK_END);
|
|
size_t inputFileBytes = ftell(inputFile);
|
|
fseek(inputFile, 0, SEEK_SET);
|
|
|
|
char *inputBuffer = (char *) malloc(inputFileBytes + 1);
|
|
size_t inputBytesRead = fread(inputBuffer, 1, inputFileBytes, inputFile);
|
|
inputBuffer[inputBytesRead] = 0;
|
|
fclose(inputFile);
|
|
|
|
if (byteCount) *byteCount = inputBytesRead;
|
|
return inputBuffer;
|
|
}
|
|
|
|
void ActionLoad(void *_unused) {
|
|
selectedConstant = NULL;
|
|
|
|
RfGrowableBuffer state = { 0 };
|
|
uint32_t *buffer = (uint32_t *) LoadFile(filePath, &state.data.byteCount);
|
|
|
|
if (state.data.byteCount > sizeof(uint32_t)) {
|
|
state.s.version = *buffer;
|
|
state.data.buffer = buffer + 1;
|
|
state.data.byteCount -= sizeof(uint32_t);
|
|
state.s.allocate = RfRealloc;
|
|
state.s.access = RfReadGrowableBuffer;
|
|
|
|
RfItem item = { 0 };
|
|
item.type = &StyleSet_Type;
|
|
item.byteCount = sizeof(styleSet);
|
|
state.s.op = RF_OP_FREE;
|
|
item.type->op(&state.s, &item, &styleSet);
|
|
state.s.op = RF_OP_LOAD;
|
|
item.type->op(&state.s, &item, &styleSet);
|
|
|
|
if (state.s.error) {
|
|
state.s.op = RF_OP_FREE;
|
|
state.s.error = false;
|
|
item.type->op(&state.s, &item, &styleSet);
|
|
} else {
|
|
if (state.s.version <= 17) {
|
|
RfState state = { 0 };
|
|
state.op = OP_GET_PALETTE;
|
|
RfItem item = { 0 };
|
|
item.type = &StyleSet_Type;
|
|
item.byteCount = sizeof(StyleSet);
|
|
item.options = NULL;
|
|
RfBroadcast(&state, &item, &styleSet, true);
|
|
|
|
for (uintptr_t i = 0; i < hmlenu(palette); i++) {
|
|
fprintf(stderr, "%.8X (%d)\n", palette[i].key, palette[i].value);
|
|
|
|
char name[16];
|
|
snprintf(name, sizeof(name), "Color %d", (int) i + 1);
|
|
Color *color = calloc(1, sizeof(Color));
|
|
color->key.buffer = strdup(name);
|
|
color->key.byteCount = strlen(name);
|
|
color->value = palette[i].key;
|
|
color->id = i + 1;
|
|
arrput(styleSet.colors, color);
|
|
|
|
state.op = OP_REPLACE_COLOR;
|
|
replaceColorFrom = color->value;
|
|
replaceColorTo = color->id;
|
|
RfBroadcast(&state, &item, &styleSet, true);
|
|
}
|
|
|
|
hmfree(palette);
|
|
}
|
|
|
|
if (state.s.version <= 18) {
|
|
char name[16];
|
|
snprintf(name, sizeof(name), "Uninitialised");
|
|
Color *color = calloc(1, sizeof(Color));
|
|
color->key.buffer = strdup(name);
|
|
color->key.byteCount = strlen(name);
|
|
color->value = 0;
|
|
color->id = 0;
|
|
arrput(styleSet.colors, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(buffer);
|
|
SetSelectedItems(MOD_CONTEXT(NULL, NULL, NULL, NULL));
|
|
StyleListRefresh();
|
|
ConstantListRefresh();
|
|
ColorListRefresh();
|
|
UIElementRepaint(elementCanvas, NULL);
|
|
}
|
|
|
|
void ActionUndo(void *_unused) {
|
|
if (!arrlen(undoStack)) return;
|
|
modApplyUndo = true;
|
|
Mod mod = arrpop(undoStack);
|
|
_ModApply(&mod);
|
|
}
|
|
|
|
void ActionRedo(void *_unused) {
|
|
if (!arrlen(redoStack)) return;
|
|
modApplyUndo = false;
|
|
Mod mod = arrpop(redoStack);
|
|
_ModApply(&mod);
|
|
}
|
|
|
|
// ------------------- Initialisation -------------------
|
|
|
|
#ifdef _WIN32
|
|
int WinMain(void *, void *, char *, int)
|
|
#else
|
|
int main(int argc, char **argv)
|
|
#endif
|
|
{
|
|
if (argc == 4 && 0 == strcmp(argv[1], "--make-font")) {
|
|
bool success = ImportFont(LoadFile(argv[2], NULL));
|
|
if (success) ExportFont(argv[3]);
|
|
return success ? 0 : 1;
|
|
}
|
|
|
|
if (argc < 3 || argc > 5) {
|
|
fprintf(stderr, "Usage: %s <source path> <export path> <optional: embed bitmap path> <optional: styles header path>\n", argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
filePath = argv[1];
|
|
exportPath = argv[2];
|
|
stylesPath = argc >= 4 ? argv[3] : NULL;
|
|
|
|
UIInitialise();
|
|
|
|
window = UIWindowCreate(0, 0, "Designer", 1600, 900);
|
|
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('S'), true, false, false, ActionSave, NULL));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('E'), true, false, false, ActionExport, NULL));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('Z'), true, false, false, ActionUndo, NULL));
|
|
UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('Y'), true, false, false, ActionRedo, NULL));
|
|
|
|
UISplitPane *splitPane1 = UISplitPaneCreate(&window->e, UI_ELEMENT_V_FILL, 0.25f);
|
|
|
|
UIPanel *panel1 = UIPanelCreate(&splitPane1->e, UI_PANEL_EXPAND);
|
|
|
|
UIPanelCreate(&panel1->e, UI_PANEL_GRAY | UI_PANEL_HORIZONTAL | UI_PANEL_MEDIUM_SPACING | UI_ELEMENT_PARENT_PUSH);
|
|
UIButtonCreate(0, 0, "Save", -1)->invoke = ActionSave;
|
|
UIButtonCreate(0, 0, "Load", -1)->invoke = ActionLoad;
|
|
UIButtonCreate(0, 0, "Export", -1)->invoke = ActionExport;
|
|
UIButtonCreate(0, 0, "Export for Designer2", -1)->invoke = ActionExportDesigner2;
|
|
UIButtonCreate(0, 0, "Import font", -1)->invoke = ActionImportFont;
|
|
UIParentPop();
|
|
|
|
UITabPaneCreate(&panel1->e, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_V_FILL, "Layers\tAnimation\tConstants\tColors");
|
|
UISplitPaneCreate(0, UI_SPLIT_PANE_VERTICAL | UI_ELEMENT_PARENT_PUSH, 0.5f);
|
|
UIPanelCreate(0, UI_PANEL_GRAY | UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING | UI_ELEMENT_PARENT_PUSH);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
stylesTextbox = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Create", -1)->invoke = ButtonCreateStyle;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Rename", -1)->invoke = ButtonRenameStyle;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Delete", -1)->invoke = ButtonDeleteStyle;
|
|
buttonPublicStyle = UIButtonCreate(0, UI_BUTTON_SMALL, "Public", -1);
|
|
buttonPublicStyle->invoke = ButtonTogglePublicStyle;
|
|
UIParentPop();
|
|
|
|
stylesShowWithSelectedLayer = UIButtonCreate(0, 0, "Show styles with selected layer", -1);
|
|
stylesShowWithSelectedLayer->invoke = ButtonShowStylesWithSelectedLayer;
|
|
|
|
UIButtonCreate(0, 0, "Cleanup unused layers", -1)->invoke = CleanupUnusedLayers;
|
|
|
|
stylesTable = UITableCreate(0, UI_ELEMENT_V_FILL, "Name");
|
|
stylesTable->e.messageUser = StylesTableMessage;
|
|
UITableResizeColumns(stylesTable);
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_GRAY | UI_PANEL_EXPAND | UI_ELEMENT_PARENT_PUSH);
|
|
tableLayers = UITableCreate(0, UI_ELEMENT_V_FILL, "Layers");
|
|
tableLayers->e.messageUser = TableLayersMessage;
|
|
UITableResizeColumns(tableLayers);
|
|
arrput(inspectorSubscriptions, &tableLayers->e);
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_PANEL_SMALL_SPACING | UI_ELEMENT_PARENT_PUSH);
|
|
buttonAddLayer = UIButtonCreate(0, UI_BUTTON_SMALL, "Add...", -1);
|
|
buttonAddLayer->invoke = ButtonAddLayer;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Delete", -1)->invoke = ButtonDeleteLayer;
|
|
buttonAddExistingLayer = UIButtonCreate(0, UI_BUTTON_SMALL, "Use existing", -1);
|
|
buttonAddExistingLayer->invoke = ButtonAddExistingLayer;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Duplicate", -1)->invoke = ButtonDuplicateLayer;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Import SVG", -1)->invoke = ButtonImportSVG;
|
|
UIParentPop();
|
|
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Delete layer in all styles", -1)->invoke = ButtonDeleteLayerInAllStyles;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UISplitPaneCreate(0, UI_SPLIT_PANE_VERTICAL | UI_ELEMENT_PARENT_PUSH, 0.5f);
|
|
UIPanelCreate(0, UI_PANEL_GRAY | UI_PANEL_EXPAND | UI_ELEMENT_PARENT_PUSH);
|
|
tableSequences = UITableCreate(0, UI_ELEMENT_V_FILL, "Sequences");
|
|
tableSequences->e.messageUser = TableSequencesMessage;
|
|
UITableResizeColumns(tableSequences);
|
|
arrput(inspectorSubscriptions, &tableSequences->e);
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_PANEL_SMALL_SPACING | UI_ELEMENT_PARENT_PUSH);
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Add", -1)->invoke = ButtonAddSequence;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Delete", -1)->invoke = ButtonDeleteSequence;
|
|
UISpacerCreate(0, 0, 10, 0);
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Move up", -1)->invoke = ButtonMoveSequenceUp;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Move down", -1)->invoke = ButtonMoveSequenceDown;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_GRAY | UI_PANEL_EXPAND | UI_ELEMENT_PARENT_PUSH);
|
|
tableKeyframes = UITableCreate(0, UI_ELEMENT_V_FILL, "Keyframes");
|
|
tableKeyframes->e.messageUser = TableKeyframesMessage;
|
|
UITableResizeColumns(tableKeyframes);
|
|
arrput(inspectorSubscriptions, &tableKeyframes->e);
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_PANEL_SMALL_SPACING | UI_ELEMENT_PARENT_PUSH);
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Add", -1)->invoke = ButtonAddKeyframe;
|
|
UIButtonCreate(0, UI_BUTTON_SMALL, "Delete", -1)->invoke = ButtonDeleteKeyframe;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING | UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
constantsTextbox = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
UIButtonCreate(0, 0, "Add", -1)->invoke = ButtonAddConstant;
|
|
UIButtonCreate(0, 0, "Delete", -1)->invoke = ButtonDeleteConstant;
|
|
UIParentPop();
|
|
|
|
constantsTable = UITableCreate(0, UI_ELEMENT_V_FILL, "Name");
|
|
constantsTable->e.messageUser = ConstantsTableMessage;
|
|
constantsTable->itemCount = arrlenu(styleSet.constants);
|
|
UITableResizeColumns(constantsTable);
|
|
|
|
UIPanelCreate(0, UI_PANEL_WHITE | UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING | UI_ELEMENT_PARENT_PUSH);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
UILabelCreate(0, UI_ELEMENT_H_FILL, "Value", -1);
|
|
constantsScale = UIButtonCreate(0, 0, "Scale", -1);
|
|
constantsScale->e.messageUser = ConstantsScaleMessage;
|
|
UIParentPop();
|
|
|
|
constantsValue = UITextboxCreate(0, UI_ELEMENT_DISABLED);
|
|
constantsValue->e.messageUser = ConstantsValueMessage;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING | UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND);
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
colorsTextbox = UITextboxCreate(0, UI_ELEMENT_H_FILL);
|
|
UIButtonCreate(0, 0, "Add", -1)->invoke = ButtonAddColor;
|
|
UIButtonCreate(0, 0, "Delete", -1)->invoke = ButtonDeleteColor;
|
|
UIParentPop();
|
|
|
|
colorsTable = UITableCreate(0, UI_ELEMENT_V_FILL, "Name\tValue");
|
|
colorsTable->e.messageUser = ColorsTableMessage;
|
|
colorsTable->itemCount = arrlenu(styleSet.colors);
|
|
UITableResizeColumns(colorsTable);
|
|
|
|
UIPanelCreate(0, UI_PANEL_WHITE | UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING | UI_ELEMENT_PARENT_PUSH);
|
|
colorsValue2 = UIColorPickerCreate(&UIPanelCreate(0, 0)->e, UI_COLOR_PICKER_HAS_OPACITY);
|
|
colorsValue2->e.messageUser = ColorsValue2Message;
|
|
colorsValue = UITextboxCreate(0, UI_ELEMENT_DISABLED);
|
|
colorsValue->e.messageUser = ColorsValueMessage;
|
|
colorsPreview = UIElementCreate(sizeof(UIElement), 0, 0, ColorsPreviewMessage, "color preview");
|
|
UIParentPop();
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UISplitPane *splitPane2 = UISplitPaneCreate(&splitPane1->e, 0, 0.7f);
|
|
UIPanel *panel6 = UIPanelCreate(&splitPane2->e, UI_PANEL_EXPAND);
|
|
elementCanvas = UIElementCreate(sizeof(UIElement), &panel6->e, UI_ELEMENT_V_FILL, CanvasMessage, "Canvas");
|
|
|
|
UIPanelCreate(&panel6->e, UI_PANEL_GRAY | UI_PANEL_EXPAND | UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL | UI_ELEMENT_H_FILL);
|
|
UIPanelCreate(0, UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING | UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_H_FILL);
|
|
UILabelCreate(0, 0, "Preview options", -1);
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
UISpacerCreate(0, 0, 20, 0);
|
|
UILabelCreate(0, 0, "Width: ", -1);
|
|
UISpacerCreate(0, 0, 5, 0);
|
|
previewWidth = UISliderCreate(0, 0);
|
|
previewWidth->position = 0.1f;
|
|
previewWidth->e.messageUser = PreviewSliderMessage;
|
|
UISpacerCreate(0, 0, 20, 0);
|
|
previewFixAspectRatio = UIButtonCreate(0, UI_BUTTON_SMALL, "=", -1);
|
|
previewFixAspectRatio->invoke = PreviewFixAspectRatioInvoke;
|
|
UISpacerCreate(0, 0, 20, 0);
|
|
UILabelCreate(0, 0, "Height:", -1);
|
|
UISpacerCreate(0, 0, 5, 0);
|
|
previewHeight = UISliderCreate(0, 0);
|
|
previewHeight->position = 0.1f;
|
|
previewHeight->e.messageUser = PreviewSliderMessage;
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
UISpacerCreate(0, 0, 20, 0);
|
|
UILabelCreate(0, 0, "Scale: ", -1);
|
|
UISpacerCreate(0, 0, 5, 0);
|
|
previewScale = UISliderCreate(0, 0);
|
|
previewScale->steps = 17;
|
|
previewScale->e.messageUser = PreviewSliderMessage;
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
previewTransition = UIButtonCreate(0, 0, "Preview transition", -1);
|
|
previewTransition->invoke = PreviewTransitionInvoke;
|
|
UIButtonCreate(0, 0, "Preferred size", -1)->invoke = PreviewPreferredSizeInvoke;
|
|
previewShowGuides = UIButtonCreate(0, 0, "Show guides", -1);
|
|
previewShowGuides->invoke = PreviewShowGuidesInvoke;
|
|
previewShowComputed = UIButtonCreate(0, 0, "Show computed rectangles", -1);
|
|
previewShowComputed->invoke = PreviewShowComputedInvoke;
|
|
editPoints = UIButtonCreate(0, 0, "View points", -1);
|
|
editPoints->invoke = EditPointsInvoke;
|
|
UIParentPop();
|
|
|
|
previewPrimaryStatePanel = UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
previewPrimaryStateIdle = UIButtonCreate(0, UI_BUTTON_SMALL | UI_BUTTON_CHECKED, "Idle", -1);
|
|
previewPrimaryStateIdle->invoke = PreviewSetPrimaryState;
|
|
previewPrimaryStateIdle->e.cp = (void *) PRIMARY_STATE_IDLE;
|
|
previewPrimaryStateHovered = UIButtonCreate(0, UI_BUTTON_SMALL, "Hovered", -1);
|
|
previewPrimaryStateHovered->invoke = PreviewSetPrimaryState;
|
|
previewPrimaryStateHovered->e.cp = (void *) PRIMARY_STATE_HOVERED;
|
|
previewPrimaryStatePressed = UIButtonCreate(0, UI_BUTTON_SMALL, "Pressed", -1);
|
|
previewPrimaryStatePressed->invoke = PreviewSetPrimaryState;
|
|
previewPrimaryStatePressed->e.cp = (void *) PRIMARY_STATE_PRESSED;
|
|
previewPrimaryStateDisabled = UIButtonCreate(0, UI_BUTTON_SMALL, "Disabled", -1);
|
|
previewPrimaryStateDisabled->invoke = PreviewSetPrimaryState;
|
|
previewPrimaryStateDisabled->e.cp = (void *) PRIMARY_STATE_DISABLED;
|
|
previewPrimaryStateInactive = UIButtonCreate(0, UI_BUTTON_SMALL, "Inactive", -1);
|
|
previewPrimaryStateInactive->invoke = PreviewSetPrimaryState;
|
|
previewPrimaryStateInactive->e.cp = (void *) PRIMARY_STATE_INACTIVE;
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
previewStateFocused = UIButtonCreate(0, UI_BUTTON_SMALL, "Focused", -1);
|
|
previewStateFocused->e.messageUser = PreviewToggleState;
|
|
previewStateChecked = UIButtonCreate(0, UI_BUTTON_SMALL, "Checked", -1);
|
|
previewStateChecked->e.messageUser = PreviewToggleState;
|
|
previewStateIndeterminate = UIButtonCreate(0, UI_BUTTON_SMALL, "Indeterminate", -1);
|
|
previewStateIndeterminate->e.messageUser = PreviewToggleState;
|
|
previewStateDefault = UIButtonCreate(0, UI_BUTTON_SMALL, "Default", -1);
|
|
previewStateDefault->e.messageUser = PreviewToggleState;
|
|
previewStateItemFocus = UIButtonCreate(0, UI_BUTTON_SMALL, "Item focus", -1);
|
|
previewStateItemFocus->e.messageUser = PreviewToggleState;
|
|
previewStateListFocus = UIButtonCreate(0, UI_BUTTON_SMALL, "List focus", -1);
|
|
previewStateListFocus->e.messageUser = PreviewToggleState;
|
|
previewStateSelected = UIButtonCreate(0, UI_BUTTON_SMALL, "Selected", -1);
|
|
previewStateSelected->e.messageUser = PreviewToggleState;
|
|
previewStateBeforeEnter = UIButtonCreate(0, UI_BUTTON_SMALL, "Before enter", -1);
|
|
previewStateBeforeEnter->e.messageUser = PreviewToggleState;
|
|
previewStateAfterExit = UIButtonCreate(0, UI_BUTTON_SMALL, "After exit", -1);
|
|
previewStateAfterExit->e.messageUser = PreviewToggleState;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
|
|
previewBackgroundColor = UIColorPickerCreate(0, 0);
|
|
previewBackgroundColor->e.messageUser = PreviewChangeBackgroundColor;
|
|
UIColorToHSV(0xC0C0C0, &previewBackgroundColor->hue, &previewBackgroundColor->saturation, &previewBackgroundColor->value);
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
panelInspector = UIPanelCreate(&splitPane2->e, UI_PANEL_GRAY | UI_PANEL_EXPAND | UI_PANEL_SCROLL);
|
|
panelInspector->border = UI_RECT_1(10);
|
|
panelInspector->gap = 5;
|
|
|
|
ActionLoad(NULL);
|
|
|
|
return UIMessageLoop();
|
|
}
|