Commit 7fa2bba9 authored by Thomas Dennis's avatar Thomas Dennis
Browse files

Initial Clone/RemoveSprite and Drag/Drop stuff (SWF only for now).

Although it's not explicitly stated in the SWF specification, only GROUPS can be cloned, not text or shapes. Also, RemoveSprite isn't having any effect in either Gnash or the official Adobe player, although it appears to be implemented correctly; further investigation is needed to figure out why.

In other news, SWF files now use different types of PlaceObject/RemoveObject tag depending on the version specified. As a result, Lightspark will now (partially) render files exported with the AVM2 flag set.

Additionally, the latest known SWF version has been bumped to 25.
parent 4d13217f
......@@ -256,5 +256,10 @@ print
; ==========================================================================
;pushString "https://www.duckduckgo.com" "_blank"
;openURL
pushstring "normalobject" "testclone"
pushnumber 3 ; FIXME: Should be calculated internally...
clonesprite
; FIXME: Why doesn't this have any effect?
pushstring "testclone"
removesprite
No preview for this file type
......@@ -582,15 +582,18 @@ static v2d_stage_s *test_button_scene(void)
v2d_data_s *audio_a = NULL;
v2d_data_s *audio_b = NULL;
stage = v2dStageCreate(320, 240, 10);
stage = v2dStageCreate(320, 240, 50);
audio_a = v2dDataCreate(stage->audio, "media/oniichan/oniichan2.wav", NULL);
audio_b = v2dDataCreate(stage->audio, "media/oniichan/oniichan3.wav", NULL);
/* Create a "normal" object with a script attached. */
draw = v2dDrawCreateText(stage->draw, "Normal Object");
draw = v2dDrawCreateGroup(stage->draw);
v2dItemCreate(v2dDrawAsGroup(draw), v2dDrawCreateText(stage->draw, "Normal Object"), 0, 0, 0, V2D_FALSE);
item = v2dItemCreate(stage->item, draw, 0, 20, 20, V2D_FALSE);
v2dItemSetName(item, "normalobject");
{
......@@ -630,6 +633,7 @@ static v2d_stage_s *test_button_scene(void)
draw = v2dDrawCreateText(stage->draw, "Idle button");
item = v2dItemCreate(stage->item, draw, 0, 20, 100, V2D_TRUE);
v2dItemSetName(item, "buttonobject");
draw = v2dDrawCreateText(stage->draw, "Over button");
v2dAnimSetDraw(v2dItemGetAnim(item), V2D_STATE_OVER, draw);
......@@ -638,14 +642,18 @@ static v2d_stage_s *test_button_scene(void)
v2dAnimSetDraw(v2dItemGetAnim(item), V2D_STATE_DOWN, draw);
v2dAnimSetSound(v2dItemGetAnim(item), V2D_EVENT_PRESS, audio_a);
v2dAnimSetSound(v2dItemGetAnim(item), V2D_EVENT_MOUSEOVER, audio_b);
v2dAnimSetSound(v2dItemGetAnim(item), V2D_EVENT_RELEASE, audio_b);
if (!v2dAnimSetScript(v2dItemGetAnim(item), V2D_EVENT_PRESS, v2dxCompileASM,
"pushstring \"You pressed the button!\"\nprint"
"pushstring \"You pressed the button!\"\n"
"print\n"
"pushnumber 0 0\n"
"pushstring \"buttonobject\"\n"
"startdrag"
, 0)) { fprintf(stderr, "Failed to compile script #2.\n"); }
if (!v2dAnimSetScript(v2dItemGetAnim(item), V2D_EVENT_RELEASE, v2dxCompileASM,
"pushstring \"You released the button!\"\nprint"
"pushstring \"You released the button!\"\nprint\nenddrag"
, 0)) { fprintf(stderr, "Failed to compile script #3.\n"); }
if (!v2dAnimSetScript(v2dItemGetAnim(item), V2D_EVENT_MOUSEOVER, v2dxCompileASM,
......@@ -658,6 +666,7 @@ static v2d_stage_s *test_button_scene(void)
/* Return the result. */
v2dDrawSort(stage->draw, V2D_TRUE);
return stage;
}
......@@ -716,7 +725,7 @@ int main(int argc, char *argv[])
if (argc || argv) { /* Silence warnings. */ }
stage = test_matrix_scene();
v2dxSaveSWF(stage, "matrices.swf", 10, 0);
v2dxSaveSWF(stage, "matrices.swf", 10, V2DX_SAVE_FLAG_SWF_AVM2);
v2dxSaveSVG(stage, "matrices.svg", V2DX_SAVE_FLAG_SVG_BACKDROP | V2DX_SAVE_FLAG_SVG_EMBED);
v2dStageDelete(stage);
......@@ -726,12 +735,12 @@ int main(int argc, char *argv[])
v2dStageDelete(stage);
stage = test_button_scene();
v2dxSaveSWF(stage, "button.swf", 10, 0);
v2dxSaveSWF(stage, "button.swf", 7, 0);
v2dxSaveSVG(stage, "button.svg", 0);
v2dStageDelete(stage);
stage = test_combining_scene();
v2dxSaveSWF(stage, "combined.swf", 10, V2DX_SAVE_FLAG_ZLIB);
v2dxSaveSWF(stage, "combined.swf", 10, V2DX_SAVE_FLAG_SWF_AVM2);
v2dxSaveSVG(stage, "combined.svg", V2DX_SAVE_FLAG_ZLIB);
v2dStageDelete(stage);
......
......@@ -43,8 +43,8 @@ The project was originally geared towards generating Adobe Flash SWF files. As s
* Key-frame animation using matrix transforms. Color transforms may also be supported in the future.
* Sound effects, in either WAV or MP3 format.
* Scripting for animation key-frames and "button" objects. Any language can be used (not just ActionScript/JavaScript) to generate a series of "generic" V2D op-codes, which are converted into AVM bytecode (SWF) or ECMAScript (SVG/HTML) when the project is exported in a specific format.
* Export to SWF (up to version 19) or SVG format.
* Import from SVG files. Create static shapes in Inkscape, before importing them into V2D for animation, scripting, and SWF export.
* Export to SWF or SVG format.
* Import from SVG files.
...plus a few odds and ends, such as RDF metadata, etc.
......@@ -56,9 +56,9 @@ Both SWF and SVG files support streaming video data. However, the codecs used di
Compatibility
-------------
SWF files are tested using the official [Adobe Flash player](http://www.adobe.com/products/flashplayer.html), [GNU Gnash](http://gnashdev.org/), [SWFDec](http://swfdec.freedesktop.org/), [Lightspark](http://lightspark.github.io/), and [Mozilla Shumway](http://mozilla.github.io/shumway/). Compatibility with the official player is the highest priority, even for cases where behaviour differs from [the official SWF specification](https://www.adobe.com/devnet/swf.html).
SWF files are tested using the official [Adobe Flash player](http://www.adobe.com/products/flashplayer.html), [GNU Gnash](http://gnashdev.org/), [SWFDec](http://swfdec.freedesktop.org/), [Lightspark](http://lightspark.github.io/), and [Mozilla Shumway](http://mozilla.github.io/shumway/examples/inspector/inspector.html). Compatibility with the official player is the highest priority, even for cases where behaviour differs from [the official SWF specification](https://www.adobe.com/devnet/swf.html).
SVG files are tested using [Mozilla Firefox](https://mozilla.org/firefox), [Google Chromium](http://www.chromium.org/Home), and [Inkscape](http://www.inkscape.org/). They are also checked using the [W3 validator](http://validator.w3.org/).
SVG files are tested using [Mozilla Firefox](https://mozilla.org/firefox), [Google Chromium](http://www.chromium.org/Home), and [Inkscape](http://www.inkscape.org/). They are also verified using the [W3 validator](http://validator.w3.org/).
Licensing
---------
......
......@@ -227,6 +227,7 @@ v2d_script_s *v2dxCompileASM(const char *source, v2d_flag_t flags)
if (!strcmp(mnemonic, "LABEL" )) { op_code = V2D_ACTION_LABEL; }
else if (!strcmp(mnemonic, "SETTARGET" )) { op_code = V2D_ACTION_SETTARGET; }
else if (!strcmp(mnemonic, "PLAY" )) { op_code = V2D_ACTION_PLAY; }
else if (!strcmp(mnemonic, "STOP" )) { op_code = V2D_ACTION_STOP; }
else if (!strcmp(mnemonic, "NEXTFRAME" )) { op_code = V2D_ACTION_NEXTFRAME; }
......@@ -263,7 +264,12 @@ v2d_script_s *v2dxCompileASM(const char *source, v2d_flag_t flags)
else if (!strcmp(mnemonic, "GETVARIABLE" )) { op_code = V2D_ACTION_GETVARIABLE; }
else if (!strcmp(mnemonic, "SETVARIABLE" )) { op_code = V2D_ACTION_SETVARIABLE; }
else if (!strcmp(mnemonic, "SETTARGET" )) { op_code = V2D_ACTION_SETTARGET; }
else if (!strcmp(mnemonic, "CLONESPRITE" )) { op_code = V2D_ACTION_CLONESPRITE; }
else if (!strcmp(mnemonic, "REMOVESPRITE" )) { op_code = V2D_ACTION_REMOVESPRITE; }
else if (!strcmp(mnemonic, "STARTDRAG" )) { op_code = V2D_ACTION_STARTDRAG; }
else if (!strcmp(mnemonic, "ENDDRAG" )) { op_code = V2D_ACTION_ENDDRAG; }
else if (!strcmp(mnemonic, "OPENURL" )) { op_code = V2D_ACTION_OPENURL; }
else if (!strcmp(mnemonic, "STOPSOUNDS" )) { op_code = V2D_ACTION_STOPSOUNDS; }
else if (!strcmp(mnemonic, "TOGGLEQUALITY")) { op_code = V2D_ACTION_TOGGLEQUALITY; }
......
......@@ -92,6 +92,7 @@ v2d_bool_t v2dxDecompileASM(FILE *out, v2d_script_s *script)
case V2D_ACTION_LABEL: fprintf(out, "Label"); break;
case V2D_ACTION_SETTARGET: fprintf(out, "SetTarget"); break;
case V2D_ACTION_PLAY: fprintf(out, "Play"); break;
case V2D_ACTION_STOP: fprintf(out, "Stop"); break;
case V2D_ACTION_NEXTFRAME: fprintf(out, "NextFrame"); break;
......@@ -128,7 +129,12 @@ v2d_bool_t v2dxDecompileASM(FILE *out, v2d_script_s *script)
case V2D_ACTION_GETVARIABLE: fprintf(out, "GetVariable"); break;
case V2D_ACTION_SETVARIABLE: fprintf(out, "SetVariable"); break;
case V2D_ACTION_SETTARGET: fprintf(out, "SetTarget"); break;
case V2D_ACTION_CLONESPRITE: fprintf(out, "CloneSprite"); break;
case V2D_ACTION_REMOVESPRITE: fprintf(out, "RemoveSprite"); break;
case V2D_ACTION_STARTDRAG: fprintf(out, "StartDrag"); break;
case V2D_ACTION_ENDDRAG: fprintf(out, "EndDrag"); break;
case V2D_ACTION_OPENURL: fprintf(out, "OpenURL"); break;
case V2D_ACTION_STOPSOUNDS: fprintf(out, "StopSounds"); break;
case V2D_ACTION_TOGGLEQUALITY: fprintf(out, "ToggleQuality"); break;
......
......@@ -37,7 +37,7 @@ extern "C" {
/* From www.adobe.com/devnet/articles/flashplayer-air-feature-list.html */
#define SWF_VERSION_MIN 1 /**< @brief Lowest known SWF version. */
#define SWF_VERSION_MAX 24 /**< @brief Latest known SWF version. */
#define SWF_VERSION_MAX 25 /**< @brief Latest known SWF version. */
/* From http://kb2.adobe.com/cps/144/tn_14437.html */
......@@ -143,6 +143,17 @@ extern "C" {
#define SWF_DEFINESOUND_UNUSED6 0x40 /**< @brief about */
#define SWF_DEFINESOUND_UNUSED7 0x80 /**< @brief about */
/* PlaceObject2 flags. Also used as the first 8 bits for PlaceObject3 flags. */
#define SWF_PLACEOBJECT2_HASCLIPACTIONS 0x80 /**< @brief HAS CLIP ACTIONS */
#define SWF_PLACEOBJECT2_HASCLIPDEPTH 0x40 /**< @brief HAS CLIP DEPTH */
#define SWF_PLACEOBJECT2_HASNAME 0x20 /**< @brief HAS NAME */
#define SWF_PLACEOBJECT2_HASRATIO 0x10 /**< @brief HAS RATIO */
#define SWF_PLACEOBJECT2_HASCOLORTRANSFORM 0x08 /**< @brief HAS COLOR TRANSFORM */
#define SWF_PLACEOBJECT2_HASMATRIX 0x04 /**< @brief HAS MATRIX */
#define SWF_PLACEOBJECT2_HASCHARACTER 0x02 /**< @brief PLACES A CHARACTER */
#define SWF_PLACEOBJECT2_MOVE 0x01 /**< @brief DEFINES A CHARACTER TO BE MOVED */
/**
@brief about
......
......@@ -836,6 +836,10 @@ static v2d_bool_t v2dxSaveSVG_script(xml_s *out, v2d_script_s *script)
xmlAddText(out, "\":\n");
break;
case V2D_ACTION_SETTARGET:
xmlAddText(out, "__stack.pop(); // UNIMPLEMENTED: SetTarget\n");
break;
case V2D_ACTION_PLAY:
xmlAddText(out, "this.play();\n");
break;
......@@ -994,7 +998,20 @@ static v2d_bool_t v2dxSaveSVG_script(xml_s *out, v2d_script_s *script)
"__var[b] = a;\n");
break;
case V2D_ACTION_SETTARGET:
case V2D_ACTION_CLONESPRITE:
xmlAddText(out, "__stack.pop(); __stack.pop(); __stack.pop(); // UNIMPLEMENTED: CloneSprite\n");
break;
case V2D_ACTION_REMOVESPRITE:
xmlAddText(out, "__stack.pop(); // UNIMPLEMENTED: RemoveSprite\n");
break;
case V2D_ACTION_STARTDRAG:
xmlAddText(out, "__stack.pop(); __stack.pop(); __stack.pop(); // UNIMPLEMENTED: StartDrag\n");
break;
case V2D_ACTION_ENDDRAG:
xmlAddText(out, "// UNIMPLEMENTED: EndDrag\n");
break;
case V2D_ACTION_OPENURL:
......@@ -1080,8 +1097,15 @@ static v2d_bool_t v2dxSaveSVG_timeline(const char *name, xml_s *script, v2d_chai
for (item = v2dGroupGetItem(group); item; item = v2dItemGetNext(item))
{
sprintf(buffer, "'__%p'%s", (void*)item, v2dItemGetNext(item) ? ", " : "];\n");
if (item->name) { sprintf(buffer, "'%s'", item->name ); }
else { sprintf(buffer, "'__%p'", (void*)item); }
xmlAddText(script, buffer);
if (v2dItemGetNext(item)) { xmlAddText(script, ", " ); }
else { xmlAddText(script, "];\n"); }
}
xmlAddText(script,
......@@ -1106,12 +1130,14 @@ static v2d_bool_t v2dxSaveSVG_timeline(const char *name, xml_s *script, v2d_chai
v2d_ui32_t frame = 0;
sprintf(buffer,
" item = this.children['__%p'];\n"
" if (item) {\n"
" switch (this.frame) {\n", (void*)item);
if (item->name) { sprintf(buffer, " item = this.children['%s'];\n", item->name); }
else { sprintf(buffer, " item = this.children['__%p'];\n", (void*)item); }
xmlAddText(script, buffer);
xmlAddText(script,
" if (item) {\n"
" switch (this.frame) {\n");
for (frame = num_frames; frame > 0; --frame)
{
......
......@@ -1829,6 +1829,7 @@ static v2d_ui16_t v2dxSaveSWF_actionSize(v2d_action_s *action)
case V2D_ACTION_LABEL: return 0; /* Not a real op-code. */
case V2D_ACTION_SETTARGET: return 1;
case V2D_ACTION_PLAY: return 1;
case V2D_ACTION_STOP: return 1;
case V2D_ACTION_NEXTFRAME: return 1;
......@@ -1872,7 +1873,12 @@ static v2d_ui16_t v2dxSaveSWF_actionSize(v2d_action_s *action)
case V2D_ACTION_GETVARIABLE: return 1;
case V2D_ACTION_SETVARIABLE: return 1;
case V2D_ACTION_SETTARGET: return 1;
case V2D_ACTION_CLONESPRITE: return 1;
case V2D_ACTION_REMOVESPRITE: return 1;
case V2D_ACTION_STARTDRAG: return 1;
case V2D_ACTION_ENDDRAG: return 1;
case V2D_ACTION_OPENURL: return 4; /* UI16 size, UI8 flags */
case V2D_ACTION_STOPSOUNDS: return 1;
case V2D_ACTION_TOGGLEQUALITY: return 1;
......@@ -1934,6 +1940,10 @@ static v2d_bool_t v2dxSaveSWF_action(FILE *out, v2d_stage_s *stage, v2d_ui8_t ve
case V2D_ACTION_LABEL: /* Not a real op-code... */ break;
case V2D_ACTION_SETTARGET:
fputc(SWF_ACTION_SETTARGET2, out);
break;
case V2D_ACTION_PLAY:
fputc(SWF_ACTION_PLAY, out);
break;
......@@ -2135,8 +2145,20 @@ static v2d_bool_t v2dxSaveSWF_action(FILE *out, v2d_stage_s *stage, v2d_ui8_t ve
fputc(SWF_ACTION_SETVARIABLE, out);
break;
case V2D_ACTION_SETTARGET:
fputc(SWF_ACTION_SETTARGET2, out);
case V2D_ACTION_CLONESPRITE:
fputc(SWF_ACTION_CLONESPRITE, out);
break;
case V2D_ACTION_REMOVESPRITE:
fputc(SWF_ACTION_REMOVESPRITE, out);
break;
case V2D_ACTION_STARTDRAG:
fputc(SWF_ACTION_STARTDRAG, out);
break;
case V2D_ACTION_ENDDRAG:
fputc(SWF_ACTION_ENDDRAG, out);
break;
case V2D_ACTION_OPENURL:
......@@ -2334,62 +2356,54 @@ static v2d_bool_t v2dxSaveSWF_button(FILE *out, v2d_stage_s *stage, v2d_ui8_t ve
/**
@brief [INTERNAL] Output display items (and any animation data) to an SWF file.
@todo Use the right kind of PlaceObject/RemoveObject tag for the SWF version.
@param out The SWF file to write the animation data to.
@param stage The "stage" structure which contains the list of display items.
@param first_item The first display item in the list.
@param group The group that contains the display items.
@param version The version of the SWF file. This dictates which tags are used.
@param flags about
@return V2D_TRUE on success, or V2D_FALSE on failure.
*/
static v2d_bool_t v2dxSaveSWF_item(FILE *out, v2d_stage_s *stage, v2d_item_s *first_item, v2d_ui8_t version, v2d_flag_t flags)
static v2d_bool_t v2dxSaveSWF_item(FILE *out, v2d_stage_s *stage, v2d_chain_s *group, v2d_ui8_t version, v2d_flag_t flags)
{
v2d_ui32_t num_frames = 0;
v2d_ui32_t num_frames = v2dGroupCountFrames(group);
v2d_ui32_t frame = 0;
v2d_ui16_t thing_id = 0;
v2d_ui16_t tag_guid = 0;
v2d_ui32_t tag_offs = 0;
v2d_item_s *item = NULL;
v2d_anim_s *anim = NULL;
v2d_anim_s *prev = NULL;
if (!out || !stage || !first_item) { return V2D_FALSE; }
num_frames = v2dGroupCountFrames(first_item->link->chain);
if (!out || !stage || !group) { return V2D_FALSE; }
for (frame = 0; frame < num_frames; frame++)
{
for (item = first_item; item; item = v2dItemGetNext(item))
v2d_item_s *item = NULL;
for (item = v2dGroupGetItem(group); item; item = v2dItemGetNext(item))
{
anim = v2dItemGetFrame(item, frame);
v2d_anim_s *anim = v2dItemGetFrame(item, frame);
v2d_anim_s *prev = v2dAnimGetPrev(anim);
v2d_ui16_t tag_guid = v2dGetItemID(v2dGroupGetItem(group), item);
v2d_ui16_t character_id = 0;
v2d_ui32_t tag_offs = 0;
if (!anim ) { continue; }
else if (anim->frame != frame) { continue; }
tag_guid = v2dGetItemID(first_item, item);
/* Delete any old keyframe instance data first. */
prev = v2dAnimGetPrev(anim);
/* If this sprite was previously visible, remove the old version first. */
if (prev)
{
if (v2dItemVisible(item, prev->frame))
{
if (prev->flags & V2D_ANIM_FLAG_BUTTON) { thing_id = v2dGetButtonID(stage, prev); }
else { thing_id = v2dGetDrawID(stage, prev->draw[V2D_DEFAULT]); }
if (prev->flags & V2D_ANIM_FLAG_BUTTON) { character_id = v2dGetButtonID(stage, prev); }
else { character_id = v2dGetDrawID(stage, prev->draw[V2D_DEFAULT]); }
tag_offs = ftell(out);
v2dLongTagSWF(out, SWF_TAG_REMOVEOBJECT, 0);
v2dWriteUI16_LE(out, thing_id);
v2dWriteUI16_LE(out, tag_guid);
v2dLongTagSWF(out, version < 3 ? SWF_TAG_REMOVEOBJECT : SWF_TAG_REMOVEOBJECT2, 0);
if (version < 3) { v2dWriteUI16_LE(out, character_id); }
v2dWriteUI16_LE(out, tag_guid); /* Depth */
v2dWriteOffset(out, tag_offs + 6, -4, V2D_TRUE);
}
......@@ -2403,22 +2417,48 @@ static v2d_bool_t v2dxSaveSWF_item(FILE *out, v2d_stage_s *stage, v2d_item_s *fi
if (anim->flags & V2D_ANIM_FLAG_BUTTON)
{
v2dxSaveSWF_button(out, stage, version, flags, anim);
thing_id = v2dGetButtonID(stage, anim);
character_id = v2dGetButtonID(stage, anim);
}
else
{
thing_id = v2dGetDrawID(stage, anim->draw[V2D_DEFAULT]);
character_id = v2dGetDrawID(stage, anim->draw[V2D_DEFAULT]);
}
tag_offs = ftell(out);
v2dLongTagSWF(out, SWF_TAG_PLACEOBJECT, 0);
v2dWriteUI16_LE(out, thing_id);
v2dWriteUI16_LE(out, tag_guid);
v2dWriteMatrixSWF(out, anim->matrix, V2D_FALSE);
if (version < 3)
{
v2dLongTagSWF(out, SWF_TAG_PLACEOBJECT, 0);
v2dWriteUI16_LE(out, character_id);
v2dWriteUI16_LE(out, tag_guid); /* Depth */
v2dWriteMatrixSWF(out, anim->matrix, V2D_FALSE);
}
else
{
v2dLongTagSWF(out, version < 8 ? SWF_TAG_PLACEOBJECT2 : SWF_TAG_PLACEOBJECT3, 0);
if (item->name) { fputc(SWF_PLACEOBJECT2_HASCHARACTER | SWF_PLACEOBJECT2_HASMATRIX | SWF_PLACEOBJECT2_HASNAME, out); }
else { fputc(SWF_PLACEOBJECT2_HASCHARACTER | SWF_PLACEOBJECT2_HASMATRIX, out); }
if (version >= 8) { fputc(0, out); /* Extra flags. */ }
v2dWriteUI16_LE(out, tag_guid); /* Depth */
v2dWriteUI16_LE(out, character_id);
v2dWriteMatrixSWF(out, anim->matrix, V2D_FALSE);
if (item->name) { fprintf(out, "%s", item->name); fputc(0, out); }
}
v2dWriteOffset(out, tag_offs + 6, -4, V2D_TRUE);
}
/* Play any sounds/run any scripts for this keyframe. */
if (!(anim->flags & V2D_ANIM_FLAG_BUTTON))
{
v2dxSaveSWF_startSound(out, stage, anim->sound[V2D_DEFAULT]);
......@@ -2454,16 +2494,12 @@ about
static v2d_bool_t v2dSaveSWF_group(FILE *out, v2d_stage_s *stage, v2d_draw_s *draw, v2d_ui8_t version)
{
v2d_item_s *item = NULL;
v2d_ui32_t tag_offs = 0;
v2d_ui16_t guid = 0;
if (!out || !stage || !draw) { return V2D_FALSE; }
else if (draw->type != V2D_TYPE_GROUP) { return V2D_FALSE; }
item = v2dGroupGetItem(v2dDrawAsGroup(draw));
tag_offs = ftell(out);
guid = v2dGetDrawID(stage, draw);
......@@ -2471,7 +2507,7 @@ static v2d_bool_t v2dSaveSWF_group(FILE *out, v2d_stage_s *stage, v2d_draw_s *dr
v2dWriteUI16_LE(out, guid);
v2dWriteUI16_LE(out, v2dGroupCountFrames(v2dDrawAsGroup(draw)));
v2dxSaveSWF_item(out, stage, item, version, 0);
v2dxSaveSWF_item(out, stage, v2dDrawAsGroup(draw), version, 0);
v2dShortTagSWF(out, SWF_TAG_END, 0);
v2dWriteOffset(out, tag_offs + 6, -4, V2D_TRUE);
......@@ -2663,7 +2699,7 @@ v2d_bool_t v2dxSaveSWF(v2d_stage_s *stage, const char *name, v2d_ui8_t version,
/* Output the display items. */
v2dxSaveSWF_item(out, stage, v2dGroupGetItem(stage->item), version, flags);
v2dxSaveSWF_item(out, stage, stage->item, version, flags);
/* Update the header "file-size" value, close the file, and report success. */
......
......@@ -86,6 +86,8 @@ void v2dItemDelete(v2d_item_s *item)
if (!item) { return; }
if (item->name) { free(item->name); }
if (item->anim)
{
while (v2dItemGetAnim(item)) { v2dAnimDelete(v2dItemGetAnim(item), V2D_TRUE); }
......@@ -97,6 +99,36 @@ void v2dItemDelete(v2d_item_s *item)
}
/**
@brief Assign a name to a display item.
Note that the name string should not already be used by any other items in the
same linked list.
@param item The Display Item to assign a name to.
@param name The new name string to use, or NULL to remove the current name.
@return V2D_TRUE on success, or V2D_FALSE on failure.
*/
v2d_bool_t v2dItemSetName(v2d_item_s *item, const char *name)
{
if (!item) { return V2D_FALSE; }
if (item->name) { free(item->name); item->name = NULL; }
if (!name) { return V2D_TRUE; }
/* TODO: Reject names with invalid characters or names starting with '__'. */
/* TODO: Search through the linked list to ensure that the new name is OK. */
item->name = (char*)malloc(strlen(name) + 1);
if (!item->name) { return V2D_FALSE; }
strcpy(item->name, name);
return V2D_TRUE;
}
/*
[PUBLIC] Delete any animation keyframes that reference a given drawable object.
*/
......
......@@ -69,6 +69,8 @@ moved around the screen.
typedef struct
{
char *name; /**< @brief Optional name, used as a unique identifier. */
v2d_link_s *link; /**< @brief Linked-list data. */
v2d_chain_s *anim; /**< @brief Animation key-frame data. */
v2d_ui32_t hold; /**< @brief Number of frames after the last keyframe. */
......@@ -147,6 +149,19 @@ This also frees any animation data that the Display Item contains.
void v2dItemDelete(v2d_item_s *item);
/**
@brief Assign a name to a display item.
Note that the name string should not already be used by any other items in the
same linked list.
@param item The Display Item to assign a name to.
@param name The new name string to use, or NULL to remove the current name.
@return V2D_TRUE on success, or V2D_FALSE on failure.
*/
v2d_bool_t v2dItemSetName(v2d_item_s *item, const char *name);
/**
@brief Delete any animation keyframes that reference a given drawable object.
......
......@@ -121,6 +121,10 @@ v2d_bool_t v2dScriptValidate(v2d_script_s *script)
break;
case V2D_ACTION_SETTARGET:
if (action->num_params != 0) { return V2D_FALSE; }
break;
case V2D_ACTION_PLAY:
if (action->num_params != 0) { return V2D_FALSE; }
break;
......@@ -242,7 +246,19 @@ v2d_bool_t v2dScriptValidate(v2d_script_s *script)
if (action->num_params != 0) { return V2D_FALSE; }
break;
case V2D_ACTION_SETTARGET:
case V2D_ACTION_CLONESPRITE:
if (action->num_params != 0) { return V2D_FALSE; }
break;
case V2D_ACTION_REMOVESPRITE:
if (action->num_params != 0) { return V2D_FALSE; }
break;
case V2D_ACTION_STARTDRAG:
if (action->num_params != 0) { return V2D_FALSE; }
break;
case V2D_ACTION_ENDDRAG:
if (action->num_params != 0) { return V2D_FALSE; }
break;
......
......@@ -69,12 +69,13 @@ typedef enum
V2D_ACTION_LABEL, /**< @brief Pseudo-instruction, not based on SWF. */
V2D_ACTION_SETTARGET, /**< @brief Set the target of any future actions. */
V2D_ACTION_PLAY, /**< @brief Start playing at the current frame. */
V2D_ACTION_STOP, /**< @brief Stop playing at the current frame. */
V2D_ACTION_NEXTFRAME, /**< @brief Go to the next animation frame. */
V2D_ACTION_PREVIOUSFRAME, /**< @brief Go to the previous animation frame. */
V2D_ACTION_GOTOFRAME, /**< @brief about */
V2D_ACTION_WAITFORFRAME, /**< @brief about */
V2D_ACTION_GOTOFRAME, /**< @brief Go to a specified frame index. */
V2D_ACTION_WAITFORFRAME, /**< @brief Check if a frame has been loaded. */
V2D_ACTION_ADD, /**< @brief Pop [b, a] and push [a + b]. */
V2D_ACTION_DIVIDE, /**< @brief Pop [b, a] and push [a / b]. */
......@@ -105,8 +106,13 @@ typedef enum
V2D_ACTION_GETVARIABLE, /**< @brief Pop a variable name + push its value. */
V2D_ACTION_SETVARIABLE, /**< @brief Pop a value + name to set a variable. */
V2D_ACTION_SETTARGET, /**< @brief about */
V2D_ACTION_OPENURL, /**< @brief Pop [target,url] and show in browser. */
V2D_ACTION_CLONESPRITE, /**< @brief Create a new copy of a display item. */
V2D_ACTION_REMOVESPRITE, /**< @brief Remove a display item from the stage. */
V2D_ACTION_STARTDRAG, /**< @brief Begin dragging a display item. */
V2D_ACTION_ENDDRAG, /**< @brief Stop dragging a display item. */