Use a FrameTag for the loop section (fix #557)

Changes:
* Paint FrameTags in Timeline with labels
* Add app::ui::FrameTagWindow
* Fix FrameTag::m_aniDir initialization
* Add warning for files that doesn't support frame tags
* Remove document preferences related to the active loop
This commit is contained in:
David Capello 2015-03-09 13:57:16 -03:00
parent e09cdd67cb
commit 58d302749c
26 changed files with 513 additions and 185 deletions

View File

@ -133,12 +133,6 @@
<option id="opacity_step" type="int" default="28" migrate="Onionskin.OpacityStep" />
<option id="type" type="OnionskinType" default="OnionskinType::MERGE" migrate="Onionskin.Type" />
</section>
<section id="loop">
<option id="visible" type="bool" default="false" />
<option id="from" type="doc::frame_t" default="0" />
<option id="to" type="doc::frame_t" default="1" />
<option id="ani_dir" type="doc::AniDir" default="doc::AniDir::FORWARD" />
</section>
</document>
</preferences>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -10,6 +10,8 @@
<dim id="tabs_close_icon_width" value="14" />
<dim id="tabs_close_icon_height" value="12" />
<dim id="tabs_icon_width" value="10" />
<dim id="timeline_top_border" value="2" />
<dim id="timeline_tags_area_height" value="4" />
</dimensions>
<colors>
@ -348,7 +350,7 @@
<part id="timeline_padding_br" x="288" y="24" w1="1" w2="10" w3="1" h1="1" h2="10" h3="1" />
<part id="timeline_drop_layer_deco" x="252" y="127" w1="3" w2="1" w3="3" h1="2" h2="1" h3="2" />
<part id="timeline_drop_frame_deco" x="252" y="120" w1="2" w2="1" w3="2" h1="3" h2="1" h3="3" />
<part id="timeline_loop_range" x="240" y="132" w1="3" w2="6" w3="3" h1="3" h2="6" h3="3" />
<part id="timeline_loop_range" x="240" y="132" w1="4" w2="4" w3="4" h1="3" h2="6" h3="3" />
<part id="flag_normal" x="0" y="240" w="16" h="10" />
<part id="flag_highlight" x="16" y="240" w="16" h="10" />
<part id="drop_pixels_ok" x="176" y="176" w="7" h="8" />

View File

@ -5,10 +5,10 @@
<window text="Frame Tag Properties" id="frame_tag_properties">
<grid columns="2">
<label text="Name:" />
<entry maxsize="256" id="name" />
<entry maxsize="256" id="name" magnet="true" />
<label text="From:" />
<entry maxsize="10" id="from" magnet="true" />
<entry maxsize="10" id="from" />
<label text="To:" />
<entry maxsize="10" id="to" />

View File

@ -258,6 +258,7 @@ add_library(app-lib
job.cpp
launcher.cpp
log.cpp
loop_tag.cpp
modules.cpp
modules/editors.cpp
modules/gfx.cpp
@ -312,6 +313,7 @@ add_library(app-lib
ui/editor/zooming_state.cpp
ui/file_list.cpp
ui/file_selector.cpp
ui/frame_tag_window.cpp
ui/hex_color_entry.cpp
ui/home_view.cpp
ui/keyboard_shortcuts.cpp

View File

@ -16,14 +16,13 @@
#include "app/color.h"
#include "app/commands/command.h"
#include "app/context_access.h"
#include "app/loop_tag.h"
#include "app/transaction.h"
#include "app/ui/color_button.h"
#include "app/ui/frame_tag_window.h"
#include "doc/anidir.h"
#include "doc/frame_tag.h"
#include "doc/sprite.h"
#include "generated_frame_tag_properties.h"
namespace app {
using namespace ui;
@ -56,76 +55,39 @@ void FrameTagPropertiesCommand::onExecute(Context* context)
{
const ContextReader reader(context);
const Sprite* sprite = reader.sprite();
frame_t frame = reader.frame();
const FrameTag* best = nullptr;
for (const FrameTag* tag : sprite->frameTags()) {
if (frame >= tag->fromFrame() &&
frame <= tag->toFrame()) {
if (!best ||
(tag->toFrame() - tag->fromFrame()) < (best->toFrame() - best->fromFrame())) {
best = tag;
}
}
}
if (!best)
const FrameTag* foundTag = get_shortest_tag(sprite, frame);
if (!foundTag)
return;
app::gen::FrameTagProperties window;
FrameTagWindow window(sprite, foundTag);
if (!window.show())
return;
window.name()->setText(best->name());
window.from()->setTextf("%d", best->fromFrame()+1);
window.to()->setTextf("%d", best->toFrame()+1);
window.color()->setColor(app::Color::fromRgb(
doc::rgba_getr(best->color()),
doc::rgba_getg(best->color()),
doc::rgba_getb(best->color())));
ContextWriter writer(reader);
Transaction transaction(writer.context(), "Change Frame Tag Properties");
FrameTag* tag = const_cast<FrameTag*>(foundTag);
static_assert(
int(doc::AniDir::FORWARD) == 0 &&
int(doc::AniDir::REVERSE) == 1 &&
int(doc::AniDir::PING_PONG) == 2, "doc::AniDir has changed");
window.anidir()->addItem("Forward");
window.anidir()->addItem("Reverse");
window.anidir()->addItem("Ping-pong");
window.anidir()->setSelectedItemIndex(int(best->aniDir()));
std::string name = window.nameValue();
if (tag->name() != name)
transaction.execute(new cmd::SetFrameTagName(tag, name));
window.openWindowInForeground();
if (window.getKiller() == window.ok()) {
std::string name = window.name()->getText();
frame_t first = 0;
frame_t last = sprite->lastFrame();
frame_t from = window.from()->getTextInt()-1;
frame_t to = window.to()->getTextInt()-1;
from = MID(first, from, last);
to = MID(from, to, last);
app::Color color = window.color()->getColor();
doc::color_t docColor = doc::rgba(
color.getRed(), color.getGreen(), color.getBlue(), 255);
doc::AniDir anidir = (doc::AniDir)window.anidir()->getSelectedItemIndex();
ContextWriter writer(reader);
Transaction transaction(writer.context(), "Change Frame Tag Properties");
FrameTag* tag = const_cast<FrameTag*>(best);
if (tag->name() != name)
transaction.execute(new cmd::SetFrameTagName(tag, name));
if (tag->fromFrame() != from ||
tag->toFrame() != to)
transaction.execute(new cmd::SetFrameTagRange(tag, from, to));
if (tag->color() != docColor)
transaction.execute(new cmd::SetFrameTagColor(tag, docColor));
if (tag->aniDir() != anidir)
transaction.execute(new cmd::SetFrameTagAniDir(tag, anidir));
transaction.commit();
doc::frame_t from, to;
window.rangeValue(from, to);
if (tag->fromFrame() != from ||
tag->toFrame() != to) {
transaction.execute(new cmd::SetFrameTagRange(tag, from, to));
}
doc::color_t docColor = window.colorValue();
if (tag->color() != docColor)
transaction.execute(new cmd::SetFrameTagColor(tag, docColor));
doc::AniDir anidir = window.aniDirValue();
if (tag->aniDir() != anidir)
transaction.execute(new cmd::SetFrameTagAniDir(tag, anidir));
transaction.commit();
}
Command* CommandFactory::createFrameTagPropertiesCommand()

View File

@ -15,6 +15,7 @@
#include "app/context.h"
#include "app/context_access.h"
#include "app/transaction.h"
#include "app/ui/frame_tag_window.h"
#include "app/ui/main_window.h"
#include "app/ui/timeline.h"
#include "doc/frame_tag.h"
@ -50,11 +51,10 @@ bool NewFrameTagCommand::onEnabled(Context* context)
void NewFrameTagCommand::onExecute(Context* context)
{
ContextWriter writer(context);
Sprite* sprite(writer.sprite());
frame_t from = writer.frame();
frame_t to = writer.frame();
const ContextReader reader(context);
const Sprite* sprite(reader.sprite());
frame_t from = reader.frame();
frame_t to = reader.frame();
Timeline::Range range = App::instance()->getMainWindow()->getTimeline()->range();
if (range.enabled() &&
@ -64,12 +64,22 @@ void NewFrameTagCommand::onExecute(Context* context)
to = range.frameEnd();
}
base::UniquePtr<FrameTag> frameTag(new FrameTag(from, to));
FrameTagWindow window(sprite, frameTag);
if (!window.show())
return;
window.rangeValue(from, to);
frameTag->setFrameRange(from, to);
frameTag->setName(window.nameValue());
frameTag->setColor(window.colorValue());
frameTag->setAniDir(window.aniDirValue());
{
ContextWriter writer(reader);
Transaction transaction(writer.context(), "New Frames Tag");
FrameTag* frameTag = new FrameTag(from, to);
transaction.execute(new cmd::AddFrameTag(sprite, frameTag));
transaction.execute(new cmd::AddFrameTag(writer.sprite(), frameTag));
frameTag.release();
transaction.commit();
}

View File

@ -16,6 +16,7 @@
#include "app/context.h"
#include "app/context_access.h"
#include "app/handle_anidir.h"
#include "app/loop_tag.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
@ -69,18 +70,19 @@ protected:
if (m_nextFrameTime >= 0) {
m_nextFrameTime -= (ui::clock() - m_curFrameTick);
FrameTag* loopTag = get_loop_tag(m_editor->sprite());
while (m_nextFrameTime <= 0) {
frame_t frame = calculate_next_frame(
m_editor->sprite(),
m_editor->frame(),
m_docPref,
m_editor->frame(), loopTag,
m_pingPongForward);
m_editor->setFrame(frame);
m_nextFrameTime += m_editor->sprite()->frameDuration(frame);
invalidate();
}
invalidate();
m_curFrameTick = ui::clock();
}
}

View File

@ -10,12 +10,18 @@
#endif
#include "app/app.h"
#include "app/cmd/add_frame_tag.h"
#include "app/cmd/remove_frame_tag.h"
#include "app/cmd/set_frame_tag_range.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/context_access.h"
#include "app/pref/preferences.h"
#include "app/loop_tag.h"
#include "app/transaction.h"
#include "app/ui/main_window.h"
#include "app/ui/timeline.h"
#include "doc/frame_tag.h"
namespace app {
@ -32,7 +38,7 @@ protected:
void onExecute(Context* context) override;
Action m_action;
frame_t m_begin, m_end;
doc::frame_t m_begin, m_end;
};
SetLoopSectionCommand::SetLoopSectionCommand()
@ -66,13 +72,13 @@ bool SetLoopSectionCommand::onEnabled(Context* ctx)
void SetLoopSectionCommand::onExecute(Context* ctx)
{
Document* doc = ctx->activeDocument();
doc::Document* doc = ctx->activeDocument();
if (!doc)
return;
DocumentPreferences& docPref = App::instance()->preferences().document(doc);
frame_t begin = m_begin;
frame_t end = m_end;
doc::Sprite* sprite = doc->sprite();
doc::frame_t begin = m_begin;
doc::frame_t end = m_end;
bool on = false;
switch (m_action) {
@ -100,13 +106,39 @@ void SetLoopSectionCommand::onExecute(Context* ctx)
}
doc::FrameTag* loopTag = get_loop_tag(sprite);
if (on) {
docPref.loop.visible(true);
docPref.loop.from(begin);
docPref.loop.to(end);
if (!loopTag) {
loopTag = create_loop_tag(begin, end);
ContextWriter writer(ctx);
Transaction transaction(writer.context(), "Add Loop");
transaction.execute(new cmd::AddFrameTag(sprite, loopTag));
transaction.commit();
}
else if (loopTag->fromFrame() != begin ||
loopTag->toFrame() != end) {
ContextWriter writer(ctx);
Transaction transaction(writer.context(), "Set Loop Range");
transaction.execute(new cmd::SetFrameTagRange(loopTag, begin, end));
transaction.commit();
}
else {
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::FrameTagProperties);
ctx->executeCommand(cmd);
}
}
else
docPref.loop.visible(false);
else {
if (loopTag) {
ContextWriter writer(ctx);
Transaction transaction(writer.context(), "Remove Loop");
transaction.execute(new cmd::RemoveFrameTag(sprite, loopTag));
transaction.commit();
delete loopTag;
}
}
App::instance()->getMainWindow()->getTimeline()->invalidate();
}
Command* CommandFactory::createSetLoopSectionCommand()

View File

@ -133,7 +133,8 @@ class AseFormat : public FileFormat {
FILE_SUPPORT_INDEXED |
FILE_SUPPORT_LAYERS |
FILE_SUPPORT_FRAMES |
FILE_SUPPORT_PALETTES;
FILE_SUPPORT_PALETTES |
FILE_SUPPORT_FRAME_TAGS;
}
bool onLoad(FileOp* fop) override;
@ -1285,6 +1286,11 @@ static void ase_file_read_frame_tags_chunk(FILE* f, FrameTags* frameTags)
frame_t from = fgetw(f);
frame_t to = fgetw(f);
int aniDir = fgetc(f);
if (aniDir != int(AniDir::FORWARD) &&
aniDir != int(AniDir::REVERSE) &&
aniDir != int(AniDir::PING_PONG)) {
aniDir = int(AniDir::FORWARD);
}
fgetl(f); // 8 reserved bytes
fgetl(f);

View File

@ -294,7 +294,7 @@ FileOp* fop_to_save_document(Context* context, Document* document, const char* f
break;
}
// check frames support
// Frames support
if (fop->document->sprite()->totalFrames() > 1) {
if (!fop->format->support(FILE_SUPPORT_FRAMES) &&
!fop->format->support(FILE_SUPPORT_SEQUENCES)) {
@ -302,14 +302,14 @@ FileOp* fop_to_save_document(Context* context, Document* document, const char* f
}
}
// layers support
// Layers support
if (fop->document->sprite()->folder()->getLayersCount() > 1) {
if (!(fop->format->support(FILE_SUPPORT_LAYERS))) {
warnings += "<<- Layers";
}
}
// Palettes support.
// Palettes support
if (fop->document->sprite()->getPalettes().size() > 1) {
if (!fop->format->support(FILE_SUPPORT_PALETTES) &&
!fop->format->support(FILE_SUPPORT_SEQUENCES)) {
@ -317,6 +317,13 @@ FileOp* fop_to_save_document(Context* context, Document* document, const char* f
}
}
// Check frames support
if (!fop->document->sprite()->frameTags().empty()) {
if (!fop->format->support(FILE_SUPPORT_FRAME_TAGS)) {
warnings += "<<- Frame tags";
}
}
// Show the confirmation alert
if (!warnings.empty()) {
// Interative

View File

@ -25,6 +25,7 @@
#define FILE_SUPPORT_PALETTES 0x00000200
#define FILE_SUPPORT_SEQUENCES 0x00000400
#define FILE_SUPPORT_GET_FORMAT_OPTIONS 0x00000800
#define FILE_SUPPORT_FRAME_TAGS 0x00001000
namespace app {

View File

@ -11,6 +11,8 @@
#include "app/handle_anidir.h"
#include "doc/frame.h"
#include "doc/frame_tag.h"
#include "doc/sprite.h"
namespace app {
@ -18,24 +20,27 @@ namespace app {
doc::frame_t calculate_next_frame(
doc::Sprite* sprite,
doc::frame_t frame,
DocumentPreferences& docPref,
doc::FrameTag* tag,
bool& pingPongForward)
{
frame_t first = frame_t(0);
frame_t last = sprite->lastFrame();
doc::frame_t first = doc::frame_t(0);
doc::frame_t last = sprite->lastFrame();
doc::AniDir aniDir = doc::AniDir::FORWARD;
if (docPref.loop.visible()) {
frame_t loopBegin, loopEnd;
loopBegin = docPref.loop.from();
loopEnd = docPref.loop.to();
loopBegin = MID(first, loopBegin, last);
loopEnd = MID(first, loopEnd, last);
if (tag) {
doc::frame_t loopFrom, loopTo;
first = loopBegin;
last = loopEnd;
loopFrom = tag->fromFrame();
loopTo = tag->toFrame();
loopFrom = MID(first, loopFrom, last);
loopTo = MID(first, loopTo, last);
first = loopFrom;
last = loopTo;
aniDir = tag->aniDir();
}
switch (docPref.loop.aniDir()) {
switch (aniDir) {
case doc::AniDir::FORWARD:
++frame;

View File

@ -9,10 +9,10 @@
#define APP_HANDLE_ANIDIR_H_INCLUDED
#pragma once
#include "app/pref/preferences.h"
#include "doc/frame.h"
namespace doc {
class FrameTag;
class Sprite;
}
@ -21,7 +21,7 @@ namespace app {
doc::frame_t calculate_next_frame(
doc::Sprite* sprite,
doc::frame_t frame,
DocumentPreferences& docPref,
doc::FrameTag* tag,
bool& pingPongForward);
} // namespace app

53
src/app/loop_tag.cpp Normal file
View File

@ -0,0 +1,53 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/loop_tag.h"
#include "doc/sprite.h"
#include "doc/frame_tag.h"
namespace app {
const char* kLoopTagName = "Loop";
doc::FrameTag* get_shortest_tag(const doc::Sprite* sprite, doc::frame_t frame)
{
const doc::FrameTag* found = nullptr;
for (const doc::FrameTag* tag : sprite->frameTags()) {
if (frame >= tag->fromFrame() &&
frame <= tag->toFrame()) {
if (!found ||
(tag->toFrame() - tag->fromFrame()) < (found->toFrame() - found->fromFrame())) {
found = tag;
}
}
}
return const_cast<doc::FrameTag*>(found);
}
doc::FrameTag* get_loop_tag(doc::Sprite* sprite)
{
// Get tag with special "Loop" name
for (doc::FrameTag* tag : sprite->frameTags())
if (tag->name() == kLoopTagName)
return tag;
return nullptr;
}
doc::FrameTag* create_loop_tag(doc::frame_t from, doc::frame_t to)
{
doc::FrameTag* tag = new doc::FrameTag(from, to);
tag->setName(kLoopTagName);
return tag;
}
} // app

27
src/app/loop_tag.h Normal file
View File

@ -0,0 +1,27 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_LOOP_TAG_H_INCLUDED
#define APP_LOOP_TAG_H_INCLUDED
#pragma once
#include "doc/frame.h"
namespace doc {
class FrameTag;
class Sprite;
}
namespace app {
doc::FrameTag* get_shortest_tag(const doc::Sprite* sprite, doc::frame_t frame);
doc::FrameTag* get_loop_tag(doc::Sprite* sprite);
doc::FrameTag* create_loop_tag(doc::frame_t from, doc::frame_t to);
} // namespace app
#endif

View File

@ -12,17 +12,23 @@
#include "app/ui/configure_timeline_popup.h"
#include "app/app.h"
#include "app/cmd/remove_frame_tag.h"
#include "app/cmd/set_frame_tag_anidir.h"
#include "app/commands/commands.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/document.h"
#include "app/find_widget.h"
#include "app/load_widget.h"
#include "app/loop_tag.h"
#include "app/settings/settings.h"
#include "app/commands/commands.h"
#include "app/transaction.h"
#include "app/ui/main_window.h"
#include "app/ui/timeline.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "base/scoped_value.h"
#include "doc/frame_tag.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/message.h"
@ -66,10 +72,14 @@ ConfigureTimelinePopup::ConfigureTimelinePopup()
m_pingPongDir->Click.connect(Bind<void>(&ConfigureTimelinePopup::onAniDir, this, doc::AniDir::PING_PONG));
}
app::Document* ConfigureTimelinePopup::doc()
{
return UIContext::instance()->activeDocument();
}
DocumentPreferences& ConfigureTimelinePopup::docPref()
{
return App::instance()->preferences().document(
UIContext::instance()->activeDocument());
return App::instance()->preferences().document(doc());
}
void ConfigureTimelinePopup::updateWidgetsFromCurrentSettings()
@ -97,7 +107,17 @@ void ConfigureTimelinePopup::updateWidgetsFromCurrentSettings()
break;
}
switch (docPref.loop.aniDir()) {
doc::AniDir aniDir = doc::AniDir::FORWARD;
if (doc()) {
if (doc::FrameTag* tag = get_loop_tag(doc()->sprite())) {
aniDir = tag->aniDir();
}
}
else {
ASSERT(false && "We should have an active sprite at this moment");
}
switch (aniDir) {
case doc::AniDir::FORWARD:
m_normalDir->setSelected(true);
break;
@ -167,12 +187,32 @@ void ConfigureTimelinePopup::onSetLoopSection()
void ConfigureTimelinePopup::onResetLoopSection()
{
docPref().loop.visible(false);
ContextWriter writer(UIContext::instance());
Transaction transaction(writer.context(), "Remove Loop");
transaction.execute(new cmd::RemoveFrameTag(writer.sprite(),
get_loop_tag(writer.sprite())));
transaction.commit();
}
void ConfigureTimelinePopup::onAniDir(doc::AniDir aniDir)
{
docPref().loop.aniDir(aniDir);
ContextWriter writer(UIContext::instance());
doc::Sprite* sprite = writer.sprite();
if (sprite) {
ASSERT(false);
return;
}
doc::FrameTag* loopTag = get_loop_tag(sprite);
Transaction transaction(writer.context(), "Set Loop Direction");
if (loopTag)
transaction.execute(new cmd::SetFrameTagAniDir(loopTag, aniDir));
else {
loopTag = create_loop_tag(doc::frame_t(0), sprite->lastFrame());
loopTag->setAniDir(aniDir);
transaction.execute(new cmd::AddFrameTag(sprite, loopTag));
}
transaction.commit();
}
} // namespace app

View File

@ -21,6 +21,7 @@ namespace ui {
}
namespace app {
class Document;
class ConfigureTimelinePopup : public ui::PopupWindow {
public:
@ -38,6 +39,7 @@ namespace app {
private:
void updateWidgetsFromCurrentSettings();
app::Document* doc();
DocumentPreferences& docPref();
ui::RadioButton* m_merge;

View File

@ -0,0 +1,73 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/frame_tag_window.h"
#include "doc/frame_tag.h"
#include "doc/sprite.h"
namespace app {
FrameTagWindow::FrameTagWindow(const doc::Sprite* sprite, const doc::FrameTag* frameTag)
: m_sprite(sprite)
{
name()->setText(frameTag->name());
from()->setTextf("%d", frameTag->fromFrame()+1);
to()->setTextf("%d", frameTag->toFrame()+1);
color()->setColor(app::Color::fromRgb(
doc::rgba_getr(frameTag->color()),
doc::rgba_getg(frameTag->color()),
doc::rgba_getb(frameTag->color())));
static_assert(
int(doc::AniDir::FORWARD) == 0 &&
int(doc::AniDir::REVERSE) == 1 &&
int(doc::AniDir::PING_PONG) == 2, "doc::AniDir has changed");
anidir()->addItem("Forward");
anidir()->addItem("Reverse");
anidir()->addItem("Ping-pong");
anidir()->setSelectedItemIndex(int(frameTag->aniDir()));
}
bool FrameTagWindow::show()
{
openWindowInForeground();
return (getKiller() == ok());
}
std::string FrameTagWindow::nameValue()
{
return name()->getText();
}
void FrameTagWindow::rangeValue(doc::frame_t& from, doc::frame_t& to)
{
doc::frame_t first = 0;
doc::frame_t last = m_sprite->lastFrame();
from = this->from()->getTextInt()-1;
to = this->to()->getTextInt()-1;
from = MID(first, from, last);
to = MID(from, to, last);
}
doc::color_t FrameTagWindow::colorValue()
{
app::Color color = this->color()->getColor();
return doc::rgba(color.getRed(), color.getGreen(), color.getBlue(), 255);
}
doc::AniDir FrameTagWindow::aniDirValue()
{
return (doc::AniDir)anidir()->getSelectedItemIndex();
}
} // namespace app

View File

@ -0,0 +1,46 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_UI_FRAME_TAG_WINDOW_H_INCLUDED
#define APP_UI_FRAME_TAG_WINDOW_H_INCLUDED
#pragma once
#include "app/ui/color_button.h"
#include "doc/anidir.h"
#include "doc/frame.h"
#include "generated_frame_tag_properties.h"
namespace doc {
class FrameTag;
class Sprite;
}
namespace ui {
class Splitter;
}
namespace app {
class FrameTagWindow : protected app::gen::FrameTagProperties {
public:
FrameTagWindow(const doc::Sprite* sprite, const doc::FrameTag* frameTag);
bool show();
std::string nameValue();
void rangeValue(doc::frame_t& from, doc::frame_t& to);
doc::color_t colorValue();
doc::AniDir aniDirValue();
private:
const doc::Sprite* m_sprite;
};
}
#endif

View File

@ -15,6 +15,7 @@
#include "app/document.h"
#include "app/handle_anidir.h"
#include "app/ini_file.h"
#include "app/loop_tag.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
@ -35,6 +36,8 @@
#include "ui/message.h"
#include "ui/system.h"
#include "doc/frame_tag.h"
namespace app {
using namespace app::skin;
@ -157,6 +160,7 @@ PreviewEditorWindow::PreviewEditorWindow()
, m_playButton(new MiniPlayButton())
, m_playTimer(10)
, m_pingPongForward(true)
, m_refFrame(0)
{
child_spacing = 0;
setAutoRemap(false);
@ -305,7 +309,7 @@ void PreviewEditorWindow::updateUsingEditor(Editor* editor)
miniEditor->centerInSpritePoint(centerPoint);
miniEditor->setLayer(editor->layer());
miniEditor->setFrame(editor->frame());
miniEditor->setFrame(m_refFrame = editor->frame());
}
void PreviewEditorWindow::uncheckCenterButton()
@ -329,22 +333,24 @@ void PreviewEditorWindow::onPlaybackTick()
if (!miniEditor)
return;
Document* document = miniEditor->document();
Sprite* sprite = miniEditor->sprite();
doc::Document* document = miniEditor->document();
doc::Sprite* sprite = miniEditor->sprite();
if (!document || !sprite)
return;
DocumentPreferences& docPref =
App::instance()->preferences().document(document);
if (m_nextFrameTime >= 0) {
m_nextFrameTime -= (ui::clock() - m_curFrameTick);
// TODO get the frame tag in updateUsingEditor()
doc::FrameTag* tag = get_shortest_tag(sprite, m_refFrame);
if (!tag)
tag = get_loop_tag(sprite);
while (m_nextFrameTime <= 0) {
frame_t frame = calculate_next_frame(
doc::frame_t frame = calculate_next_frame(
sprite,
miniEditor->frame(),
docPref,
tag,
m_pingPongForward);
miniEditor->setFrame(frame);

View File

@ -10,6 +10,7 @@
#pragma once
#include "app/ui/document_view.h"
#include "doc/frame.h"
#include "ui/timer.h"
#include "ui/window.h"
@ -51,6 +52,7 @@ namespace app {
int m_curFrameTick;
bool m_pingPongForward;
doc::frame_t m_refFrame;
};
} // namespace app

View File

@ -13,6 +13,7 @@
#include "app/app.h"
#include "app/app_menus.h"
#include "app/color_utils.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
@ -22,6 +23,7 @@
#include "app/document_api.h"
#include "app/document_range_ops.h"
#include "app/document_undo.h"
#include "app/loop_tag.h"
#include "app/modules/editors.h"
#include "app/modules/gfx.h"
#include "app/modules/gui.h"
@ -40,6 +42,7 @@
#include "doc/frame_tag.h"
#include "gfx/point.h"
#include "gfx/rect.h"
#include "she/font.h"
#include "ui/ui.h"
#include <cstdio>
@ -76,6 +79,7 @@ using namespace ui;
enum {
A_PART_NOTHING,
A_PART_TOP,
A_PART_SEPARATOR,
A_PART_HEADER_EYE,
A_PART_HEADER_PADLOCK,
@ -86,6 +90,7 @@ enum {
A_PART_HEADER_ONIONSKIN_RANGE_RIGHT,
A_PART_HEADER_LAYER,
A_PART_HEADER_FRAME,
A_PART_HEADER_FRAME_TAGS,
A_PART_LAYER,
A_PART_LAYER_EYE_ICON,
A_PART_LAYER_PADLOCK_ICON,
@ -758,6 +763,8 @@ void Timeline::onPaint(ui::PaintEvent& ev)
getDrawableLayers(g, &first_layer, &last_layer);
getDrawableFrames(g, &first_frame, &last_frame);
drawTop(g);
// Draw the header for layers.
drawHeader(g);
@ -811,7 +818,6 @@ void Timeline::onPaint(ui::PaintEvent& ev)
}
drawPaddings(g);
drawLoopRange(g);
drawFrameTags(g);
drawRangeOutline(g);
drawClipboardRange(g);
@ -1037,6 +1043,12 @@ void Timeline::drawClipboardRange(ui::Graphics* g)
g->drawRect(0, getRangeBounds(clipboard_range));
}
void Timeline::drawTop(ui::Graphics* g)
{
g->fillRect(skinTheme()->colors.workspace(),
getPartBounds(A_PART_TOP));
}
void Timeline::drawHeader(ui::Graphics* g)
{
SkinTheme::Styles& styles = skinTheme()->styles;
@ -1253,41 +1265,50 @@ void Timeline::drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,
}
}
void Timeline::drawLoopRange(ui::Graphics* g)
void Timeline::drawFrameTags(ui::Graphics* g)
{
DocumentPreferences& docPref = this->docPref();
if (!docPref.loop.visible())
return;
frame_t begin = docPref.loop.from();
frame_t end = docPref.loop.to();
if (begin > end)
return;
gfx::Rect bounds1 = getPartBounds(A_PART_HEADER_FRAME, firstLayer(), begin);
gfx::Rect bounds2 = getPartBounds(A_PART_HEADER_FRAME, firstLayer(), end);
gfx::Rect bounds = bounds1.createUnion(bounds2);
IntersectClip clip(g, bounds);
IntersectClip clip(g, getPartBounds(A_PART_HEADER_FRAME_TAGS));
if (!clip)
return;
drawPart(g, bounds, NULL,
skinTheme()->styles.timelineLoopRange());
}
SkinTheme* theme = skinTheme();
SkinTheme::Styles& styles = theme->styles;
void Timeline::drawFrameTags(ui::Graphics* g)
{
SkinTheme::Styles& styles = skinTheme()->styles;
if (!m_sprite->frameTags().empty()) {
g->fillRect(theme->colors.workspace(),
gfx::Rect(
0, getFont()->height(),
getClientBounds().w,
theme->dimensions.timelineTagsAreaHeight()));
}
for (FrameTag* frameTag : m_sprite->frameTags()) {
gfx::Rect bounds1 = getPartBounds(A_PART_HEADER_FRAME, firstLayer(), frameTag->fromFrame());
gfx::Rect bounds2 = getPartBounds(A_PART_HEADER_FRAME, firstLayer(), frameTag->toFrame());
gfx::Rect bounds = bounds1.createUnion(bounds2);
bounds.y -= theme->dimensions.timelineTagsAreaHeight();
IntersectClip clip(g, bounds);
if (clip) {
drawPart(g, bounds, NULL, styles.timelineLoopRange());
{
IntersectClip clip(g, bounds);
if (clip)
drawPart(g, bounds, NULL, styles.timelineLoopRange());
}
{
int textHeight = getFont()->height();
bounds.y -= textHeight + 2*ui::guiscale();
bounds.x += 3*ui::guiscale();
bounds.w = getFont()->textLength(frameTag->name().c_str()) + 4*ui::guiscale();
bounds.h = getFont()->height() + 2*ui::guiscale();
g->fillRect(frameTag->color(), bounds);
bounds.y += 2*ui::guiscale();
bounds.x += 2*ui::guiscale();
g->drawString(
frameTag->name(),
color_utils::blackandwhite_neg(frameTag->color()),
gfx::ColorNone,
bounds.getOrigin());
}
}
}
@ -1365,6 +1386,7 @@ void Timeline::drawPaddings(ui::Graphics* g)
gfx::Rect client = getClientBounds();
gfx::Rect bottomLayer;
gfx::Rect lastFrame;
int top = topHeight();
if (!m_layers.empty()) {
bottomLayer = getPartBounds(A_PART_LAYER, firstLayer());
@ -1376,7 +1398,7 @@ void Timeline::drawPaddings(ui::Graphics* g)
}
drawPart(g,
gfx::Rect(lastFrame.x+lastFrame.w, client.y,
gfx::Rect(lastFrame.x+lastFrame.w, client.y + top,
client.w - (lastFrame.x+lastFrame.w),
bottomLayer.y+bottomLayer.h),
NULL, styles.timelinePaddingTr());
@ -1397,8 +1419,9 @@ gfx::Rect Timeline::getLayerHeadersBounds() const
{
gfx::Rect rc = getClientBounds();
rc.w = m_separator_x;
rc.y += HDRSIZE;
rc.h -= HDRSIZE;
int h = topHeight() + HDRSIZE;
rc.y += h;
rc.h -= h;
return rc;
}
@ -1406,6 +1429,7 @@ gfx::Rect Timeline::getFrameHeadersBounds() const
{
gfx::Rect rc = getClientBounds();
rc.x += m_separator_x;
rc.y += topHeight();
rc.w -= m_separator_x;
rc.h = HDRSIZE;
return rc;
@ -1435,78 +1459,89 @@ gfx::Rect Timeline::getCelsBounds() const
gfx::Rect rc = getClientBounds();
rc.x += m_separator_x;
rc.w -= m_separator_x;
rc.y += HDRSIZE;
rc.h -= HDRSIZE;
rc.y += HDRSIZE + topHeight();
rc.h -= HDRSIZE - topHeight();
return rc;
}
gfx::Rect Timeline::getPartBounds(int part, LayerIndex layer, frame_t frame) const
{
const gfx::Rect bounds = getClientBounds();
gfx::Rect bounds = getClientBounds();
int y = topHeight();
switch (part) {
case A_PART_NOTHING:
break;
case A_PART_TOP:
return gfx::Rect(bounds.x, bounds.y, bounds.w, y);
case A_PART_SEPARATOR:
return gfx::Rect(m_separator_x, 0,
m_separator_x + m_separator_w, bounds.h);
return gfx::Rect(bounds.x + m_separator_x, bounds.y + y,
m_separator_x + m_separator_w, bounds.h - y);
case A_PART_HEADER_EYE:
return gfx::Rect(FRMSIZE*0, 0, FRMSIZE, HDRSIZE);
return gfx::Rect(bounds.x + FRMSIZE*0, bounds.y + y, FRMSIZE, HDRSIZE);
case A_PART_HEADER_PADLOCK:
return gfx::Rect(FRMSIZE*1, 0, FRMSIZE, HDRSIZE);
return gfx::Rect(bounds.x + FRMSIZE*1, bounds.y + y, FRMSIZE, HDRSIZE);
case A_PART_HEADER_CONTINUOUS:
return gfx::Rect(FRMSIZE*2, 0, FRMSIZE, HDRSIZE);
return gfx::Rect(bounds.x + FRMSIZE*2, bounds.y + y, FRMSIZE, HDRSIZE);
case A_PART_HEADER_GEAR:
return gfx::Rect(FRMSIZE*3, 0, FRMSIZE, HDRSIZE);
return gfx::Rect(bounds.x + FRMSIZE*3, bounds.y + y, FRMSIZE, HDRSIZE);
case A_PART_HEADER_ONIONSKIN:
return gfx::Rect(FRMSIZE*4, 0, FRMSIZE, HDRSIZE);
return gfx::Rect(bounds.x + FRMSIZE*4, bounds.y + y, FRMSIZE, HDRSIZE);
case A_PART_HEADER_LAYER:
return gfx::Rect(FRMSIZE*5, 0,
return gfx::Rect(bounds.x + FRMSIZE*5, bounds.y + y,
m_separator_x - FRMSIZE*5, HDRSIZE);
case A_PART_HEADER_FRAME:
if (validFrame(frame)) {
return gfx::Rect(m_separator_x + m_separator_w - 1 + FRMSIZE*frame - m_scroll_x,
0, FRMSIZE, HDRSIZE);
return gfx::Rect(
bounds.x + m_separator_x + m_separator_w - 1 + FRMSIZE*frame - m_scroll_x,
bounds.y + y, FRMSIZE, HDRSIZE);
}
break;
case A_PART_HEADER_FRAME_TAGS:
return gfx::Rect(
bounds.x + m_separator_x + m_separator_w - 1,
bounds.y,
bounds.w - m_separator_x - m_separator_w + 1, y);
case A_PART_LAYER:
if (validLayer(layer)) {
return gfx::Rect(0,
HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
return gfx::Rect(bounds.x,
bounds.y + y + HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
m_separator_x, LAYSIZE);
}
break;
case A_PART_LAYER_EYE_ICON:
if (validLayer(layer)) {
return gfx::Rect(0,
HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
return gfx::Rect(bounds.x,
bounds.y + y + HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
FRMSIZE, LAYSIZE);
}
break;
case A_PART_LAYER_PADLOCK_ICON:
if (validLayer(layer)) {
return gfx::Rect(FRMSIZE,
HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
return gfx::Rect(bounds.x + FRMSIZE,
bounds.y + y + HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
FRMSIZE, LAYSIZE);
}
break;
case A_PART_LAYER_CONTINUOUS_ICON:
if (validLayer(layer)) {
return gfx::Rect(2*FRMSIZE,
HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
return gfx::Rect(bounds.x + 2*FRMSIZE,
bounds.y + y + HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
FRMSIZE, LAYSIZE);
}
break;
@ -1514,8 +1549,8 @@ gfx::Rect Timeline::getPartBounds(int part, LayerIndex layer, frame_t frame) con
case A_PART_LAYER_TEXT:
if (validLayer(layer)) {
int x = FRMSIZE*3;
return gfx::Rect(x,
HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
return gfx::Rect(bounds.x + x,
bounds.y + y + HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
m_separator_x - x, LAYSIZE);
}
break;
@ -1523,8 +1558,8 @@ gfx::Rect Timeline::getPartBounds(int part, LayerIndex layer, frame_t frame) con
case A_PART_CEL:
if (validLayer(layer) && frame >= frame_t(0)) {
return gfx::Rect(
m_separator_x + m_separator_w - 1 + FRMSIZE*frame - m_scroll_x,
HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
bounds.x + m_separator_x + m_separator_w - 1 + FRMSIZE*frame - m_scroll_x,
bounds.y + y + HDRSIZE + LAYSIZE*(lastLayer()-layer) - m_scroll_y,
FRMSIZE, LAYSIZE);
}
break;
@ -1612,8 +1647,11 @@ void Timeline::updateHot(ui::Message* msg, const gfx::Point& mousePos, int& hot_
hot_part = A_PART_SEPARATOR;
}
else {
int top = topHeight();
hot_layer = lastLayer() - LayerIndex(
(mousePos.y
- top
- HDRSIZE
+ m_scroll_y) / LAYSIZE);
@ -1648,7 +1686,7 @@ void Timeline::updateHot(ui::Message* msg, const gfx::Point& mousePos, int& hot_
hot_part = A_PART_SEPARATOR;
}
// Is the mouse on the headers?
else if (mousePos.y < HDRSIZE) {
else if (mousePos.y >= top && mousePos.y < top+HDRSIZE) {
if (mousePos.x < m_separator_x) {
if (getPartBounds(A_PART_HEADER_EYE).contains(mousePos))
hot_part = A_PART_HEADER_EYE;
@ -2168,4 +2206,14 @@ skin::SkinTheme* Timeline::skinTheme() const
return static_cast<SkinTheme*>(getTheme());
}
int Timeline::topHeight() const
{
int h = skinTheme()->dimensions.timelineTopBorder();
if (m_sprite && !m_sprite->frameTags().empty()) {
h += getFont()->height();
h += skinTheme()->dimensions.timelineTagsAreaHeight();
}
return h;
}
} // namespace app

View File

@ -146,13 +146,13 @@ namespace app {
void drawPart(ui::Graphics* g, const gfx::Rect& bounds,
const char* text, skin::Style* style,
bool is_active = false, bool is_hover = false, bool is_clicked = false);
void drawTop(ui::Graphics* g);
void drawHeader(ui::Graphics* g);
void drawHeaderFrame(ui::Graphics* g, frame_t frame);
void drawLayer(ui::Graphics* g, LayerIndex layerIdx);
void drawCel(ui::Graphics* g, LayerIndex layerIdx, frame_t frame, Cel* cel);
void drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,
Cel* cel, Cel* activeCel, frame_t frame, bool is_active, bool is_hover);
void drawLoopRange(ui::Graphics* g);
void drawFrameTags(ui::Graphics* g);
void drawRangeOutline(ui::Graphics* g);
void drawPaddings(ui::Graphics* g);
@ -195,6 +195,8 @@ namespace app {
bool validLayer(LayerIndex layer) const { return layer >= firstLayer() && layer <= lastLayer(); }
bool validFrame(frame_t frame) const { return frame >= firstFrame() && frame <= lastFrame(); }
int topHeight() const;
DocumentPreferences& docPref() const;
skin::SkinTheme* skinTheme() const;

View File

@ -18,6 +18,7 @@ FrameTag::FrameTag(frame_t from, frame_t to)
, m_to(to)
, m_color(rgba(0, 0, 0, 255))
, m_name("Tag")
, m_aniDir(AniDir::FORWARD)
{
}
@ -39,6 +40,10 @@ void FrameTag::setColor(color_t color)
void FrameTag::setAniDir(AniDir aniDir)
{
ASSERT(m_aniDir == AniDir::FORWARD ||
m_aniDir == AniDir::REVERSE ||
m_aniDir == AniDir::PING_PONG);
m_aniDir = aniDir;
}

View File

@ -37,6 +37,7 @@ namespace doc {
const_iterator end() const { return m_tags.end(); }
std::size_t size() const { return m_tags.size(); }
bool empty() const { return m_tags.empty(); }
private:
Sprite* m_sprite;