mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-07 10:21:30 +00:00
Add support to resize tags with drag & drop
This idea was originally planned but never implemented. Requested several times after tags were introduced: https://github.com/aseprite/aseprite/issues/1275 https://github.com/aseprite/aseprite/issues/1875 https://community.aseprite.org/t/drag-to-resize-tag/3619
This commit is contained in:
parent
91628daa9d
commit
ed8ef5dbd7
@ -13,6 +13,7 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/cmd/set_tag_range.h"
|
||||
#include "app/cmd_transaction.h"
|
||||
#include "app/color_utils.h"
|
||||
#include "app/commands/command.h"
|
||||
@ -31,6 +32,7 @@
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/thumbnails.h"
|
||||
#include "app/transaction.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/app_menuitem.h"
|
||||
#include "app/ui/configure_timeline_popup.h"
|
||||
#include "app/ui/doc_view.h"
|
||||
@ -89,6 +91,8 @@ enum {
|
||||
PART_CEL,
|
||||
PART_RANGE_OUTLINE,
|
||||
PART_TAG,
|
||||
PART_TAG_LEFT,
|
||||
PART_TAG_RIGHT,
|
||||
PART_TAGS,
|
||||
PART_TAG_BAND,
|
||||
PART_TAG_SWITCH_BUTTONS,
|
||||
@ -963,6 +967,23 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
m_clk.frame = m_range.lastFrame();
|
||||
}
|
||||
break;
|
||||
|
||||
case PART_TAG:
|
||||
m_state = STATE_MOVING_TAG;
|
||||
m_resizeTagData.reset(m_clk.tag);
|
||||
break;
|
||||
case PART_TAG_LEFT:
|
||||
m_state = STATE_RESIZING_TAG_LEFT;
|
||||
m_resizeTagData.reset(m_clk.tag);
|
||||
// TODO reduce the scope of the invalidation
|
||||
invalidate();
|
||||
break;
|
||||
case PART_TAG_RIGHT:
|
||||
m_state = STATE_RESIZING_TAG_RIGHT;
|
||||
m_resizeTagData.reset(m_clk.tag);
|
||||
invalidate();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Redraw the new clicked part (header, layer or cel).
|
||||
@ -1145,7 +1166,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_SELECTING_CELS:
|
||||
case STATE_SELECTING_CELS: {
|
||||
Layer* hitLayer = m_rows[hit.layer].layer();
|
||||
if ((m_layer != hitLayer) || (m_frame != hit.frame)) {
|
||||
m_clk.layer = hit.layer;
|
||||
@ -1157,6 +1178,29 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
setFrame(m_clk.frame = hit.frame, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_MOVING_TAG:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case STATE_RESIZING_TAG_LEFT:
|
||||
case STATE_RESIZING_TAG_RIGHT: {
|
||||
auto tag = doc::get<doc::Tag>(m_resizeTagData.tag);
|
||||
if (tag) {
|
||||
switch (m_state) {
|
||||
case STATE_RESIZING_TAG_LEFT:
|
||||
m_resizeTagData.from = base::clamp(hit.frame, 0, tag->toFrame());
|
||||
break;
|
||||
case STATE_RESIZING_TAG_RIGHT:
|
||||
m_resizeTagData.to = base::clamp(hit.frame, tag->fromFrame(), m_sprite->lastFrame());
|
||||
break;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1309,11 +1353,37 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
if (relayout)
|
||||
layout();
|
||||
|
||||
if (m_state == STATE_MOVING_RANGE &&
|
||||
m_dropRange.type() != Range::kNone) {
|
||||
dropRange(is_copy_key_pressed(mouseMsg) ?
|
||||
Timeline::kCopy:
|
||||
Timeline::kMove);
|
||||
switch (m_state) {
|
||||
case STATE_MOVING_RANGE:
|
||||
if (m_dropRange.type() != Range::kNone) {
|
||||
dropRange(is_copy_key_pressed(mouseMsg) ?
|
||||
Timeline::kCopy:
|
||||
Timeline::kMove);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_MOVING_TAG:
|
||||
m_resizeTagData.reset();
|
||||
break;
|
||||
|
||||
case STATE_RESIZING_TAG_LEFT:
|
||||
case STATE_RESIZING_TAG_RIGHT: {
|
||||
auto tag = doc::get<doc::Tag>(m_resizeTagData.tag);
|
||||
if (tag) {
|
||||
if ((m_state == STATE_RESIZING_TAG_LEFT && tag->fromFrame() != m_resizeTagData.from) ||
|
||||
(m_state == STATE_RESIZING_TAG_RIGHT && tag->toFrame() != m_resizeTagData.to)) {
|
||||
Tx tx(UIContext::instance(), Strings::commands_FrameTagProperties());
|
||||
tx(new cmd::SetTagRange(
|
||||
tag,
|
||||
(m_state == STATE_RESIZING_TAG_LEFT ? m_resizeTagData.from: tag->fromFrame()),
|
||||
(m_state == STATE_RESIZING_TAG_RIGHT ? m_resizeTagData.to: tag->toFrame())));
|
||||
tx.commit();
|
||||
}
|
||||
}
|
||||
m_resizeTagData.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clean the clicked-part & redraw the hot-part.
|
||||
@ -1889,6 +1959,12 @@ void Timeline::setCursor(ui::Message* msg, const Hit& hit)
|
||||
else if (hit.part == PART_TAG) {
|
||||
ui::set_mouse_cursor(kHandCursor);
|
||||
}
|
||||
else if (hit.part == PART_TAG_RIGHT) {
|
||||
ui::set_mouse_cursor(kSizeECursor);
|
||||
}
|
||||
else if (hit.part == PART_TAG_LEFT) {
|
||||
ui::set_mouse_cursor(kSizeWCursor);
|
||||
}
|
||||
else {
|
||||
ui::set_mouse_cursor(kArrowCursor);
|
||||
}
|
||||
@ -2432,8 +2508,15 @@ void Timeline::drawTags(ui::Graphics* g)
|
||||
}
|
||||
}
|
||||
|
||||
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), tag->fromFrame()));
|
||||
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), tag->toFrame()));
|
||||
doc::frame_t fromFrame = tag->fromFrame();
|
||||
doc::frame_t toFrame = tag->toFrame();
|
||||
if (m_resizeTagData.tag == tag->id()) {
|
||||
fromFrame = m_resizeTagData.from;
|
||||
toFrame = m_resizeTagData.to;
|
||||
}
|
||||
|
||||
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), fromFrame));
|
||||
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), toFrame));
|
||||
gfx::Rect bounds = bounds1.createUnion(bounds2);
|
||||
gfx::Rect tagBounds = getPartBounds(Hit(PART_TAG, 0, 0, tag->id()));
|
||||
bounds.h = bounds.y2() - tagBounds.y2();
|
||||
@ -2468,39 +2551,45 @@ void Timeline::drawTags(ui::Graphics* g)
|
||||
bounds.w += dw;
|
||||
tagBounds.x += dx;
|
||||
|
||||
gfx::Color bg =
|
||||
const gfx::Color tagColor =
|
||||
(m_tagFocusBand < 0 || pass == 1) ?
|
||||
tag->color(): theme->colors.timelineBandBg();
|
||||
{
|
||||
IntersectClip clip(g, bounds);
|
||||
if (clip) {
|
||||
for (auto& layer : styles.timelineLoopRange()->layers()) {
|
||||
if (layer.type() == Style::Layer::Type::kBackground ||
|
||||
layer.type() == Style::Layer::Type::kBackgroundBorder ||
|
||||
layer.type() == Style::Layer::Type::kBorder) {
|
||||
const_cast<Style::Layer*>(&layer)->setColor(bg);
|
||||
}
|
||||
}
|
||||
drawPart(g, bounds, nullptr, styles.timelineLoopRange());
|
||||
}
|
||||
gfx::Color bg = tagColor;
|
||||
|
||||
// Draw the tag braces
|
||||
drawTagBraces(g, bg, bounds, bounds);
|
||||
if ((m_clk.part == PART_TAG_LEFT && m_clk.tag == tag->id()) ||
|
||||
(m_clk.part != PART_TAG_LEFT &&
|
||||
m_hot.part == PART_TAG_LEFT && m_hot.tag == tag->id())) {
|
||||
if (m_clk.part == PART_TAG_LEFT)
|
||||
bg = color_utils::blackandwhite_neg(tagColor);
|
||||
else
|
||||
bg = Timeline::highlightColor(tagColor);
|
||||
drawTagBraces(g, bg, bounds, gfx::Rect(bounds.x, bounds.y,
|
||||
frameBoxWidth()/2, bounds.h));
|
||||
}
|
||||
else if ((m_clk.part == PART_TAG_RIGHT && m_clk.tag == tag->id()) ||
|
||||
(m_clk.part != PART_TAG_RIGHT &&
|
||||
m_hot.part == PART_TAG_RIGHT && m_hot.tag == tag->id())) {
|
||||
if (m_clk.part == PART_TAG_RIGHT)
|
||||
bg = color_utils::blackandwhite_neg(tagColor);
|
||||
else
|
||||
bg = Timeline::highlightColor(tagColor);
|
||||
drawTagBraces(g, bg, bounds,
|
||||
gfx::Rect(bounds.x2()-frameBoxWidth()/2, bounds.y,
|
||||
frameBoxWidth()/2, bounds.h));
|
||||
}
|
||||
|
||||
// Draw tag text
|
||||
if (m_tagFocusBand < 0 || pass == 1) {
|
||||
bounds = tagBounds;
|
||||
|
||||
if (m_clk.part == PART_TAG && m_clk.tag == tag->id()) {
|
||||
bg = color_utils::blackandwhite_neg(bg);
|
||||
}
|
||||
else if (m_hot.part == PART_TAG && m_hot.tag == tag->id()) {
|
||||
int r, g, b;
|
||||
r = gfx::getr(bg)+32;
|
||||
g = gfx::getg(bg)+32;
|
||||
b = gfx::getb(bg)+32;
|
||||
r = base::clamp(r, 0, 255);
|
||||
g = base::clamp(g, 0, 255);
|
||||
b = base::clamp(b, 0, 255);
|
||||
bg = gfx::rgba(r, g, b, gfx::geta(bg));
|
||||
}
|
||||
if (m_clk.part == PART_TAG && m_clk.tag == tag->id())
|
||||
bg = color_utils::blackandwhite_neg(tagColor);
|
||||
else if (m_hot.part == PART_TAG && m_hot.tag == tag->id())
|
||||
bg = Timeline::highlightColor(tagColor);
|
||||
else
|
||||
bg = tagColor;
|
||||
g->fillRect(bg, bounds);
|
||||
|
||||
bounds.y += 2*ui::guiscale();
|
||||
@ -2530,6 +2619,26 @@ void Timeline::drawTags(ui::Graphics* g)
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::drawTagBraces(ui::Graphics* g,
|
||||
gfx::Color tagColor,
|
||||
const gfx::Rect& bounds,
|
||||
const gfx::Rect& clipBounds)
|
||||
{
|
||||
IntersectClip clip(g, clipBounds);
|
||||
if (clip) {
|
||||
SkinTheme* theme = skinTheme();
|
||||
auto& styles = theme->styles;
|
||||
for (auto& layer : styles.timelineLoopRange()->layers()) {
|
||||
if (layer.type() == Style::Layer::Type::kBackground ||
|
||||
layer.type() == Style::Layer::Type::kBackgroundBorder ||
|
||||
layer.type() == Style::Layer::Type::kBorder) {
|
||||
const_cast<Style::Layer*>(&layer)->setColor(tagColor);
|
||||
}
|
||||
}
|
||||
drawPart(g, bounds, nullptr, styles.timelineLoopRange());
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::drawRangeOutline(ui::Graphics* g)
|
||||
{
|
||||
auto& styles = skinTheme()->styles;
|
||||
@ -2791,8 +2900,15 @@ gfx::Rect Timeline::getPartBounds(const Hit& hit) const
|
||||
case PART_TAG: {
|
||||
Tag* tag = hit.getTag();
|
||||
if (tag) {
|
||||
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), tag->fromFrame()));
|
||||
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), tag->toFrame()));
|
||||
doc::frame_t fromFrame = tag->fromFrame();
|
||||
doc::frame_t toFrame = tag->toFrame();
|
||||
if (m_resizeTagData.tag == tag->id()) {
|
||||
fromFrame = m_resizeTagData.from;
|
||||
toFrame = m_resizeTagData.to;
|
||||
}
|
||||
|
||||
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), fromFrame));
|
||||
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), toFrame));
|
||||
gfx::Rect bounds = bounds1.createUnion(bounds2);
|
||||
bounds.y -= skinTheme()->dimensions.timelineTagsAreaHeight();
|
||||
|
||||
@ -3095,11 +3211,6 @@ Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
|
||||
else if (!bounds.isEmpty() && gfx::Rect(bounds.x+bounds.w-3, bounds.y, 3, bounds.h).contains(mousePos)) {
|
||||
hit.part = PART_HEADER_ONIONSKIN_RANGE_RIGHT;
|
||||
}
|
||||
// Is the mouse on the separator.
|
||||
else if (mousePos.x > separatorX()-4
|
||||
&& mousePos.x <= separatorX()) {
|
||||
hit.part = PART_SEPARATOR;
|
||||
}
|
||||
// Is the mouse on the frame tags area?
|
||||
else if (getPartBounds(Hit(PART_TAGS)).contains(mousePos)) {
|
||||
// Mouse in switch band button
|
||||
@ -3130,18 +3241,50 @@ Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
|
||||
// Mouse in frame tags
|
||||
if (hit.part == PART_NOTHING) {
|
||||
for (Tag* tag : m_sprite->tags()) {
|
||||
gfx::Rect bounds = getPartBounds(Hit(PART_TAG, 0, 0, tag->id()));
|
||||
if (bounds.contains(mousePos)) {
|
||||
const int band = m_tagBand[tag];
|
||||
if (m_tagFocusBand >= 0 &&
|
||||
m_tagFocusBand != band)
|
||||
continue;
|
||||
const int band = m_tagBand[tag];
|
||||
|
||||
// Skip unfocused bands
|
||||
if (m_tagFocusBand >= 0 &&
|
||||
m_tagFocusBand != band) {
|
||||
continue;
|
||||
}
|
||||
|
||||
gfx::Rect tagBounds = getPartBounds(Hit(PART_TAG, 0, 0, tag->id()));
|
||||
if (tagBounds.contains(mousePos)) {
|
||||
hit.part = PART_TAG;
|
||||
hit.tag = tag->id();
|
||||
hit.band = band;
|
||||
break;
|
||||
}
|
||||
// Check if we are in the left/right handles to resize the tag
|
||||
else {
|
||||
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), tag->fromFrame()));
|
||||
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), tag->toFrame()));
|
||||
gfx::Rect bounds = bounds1.createUnion(bounds2);
|
||||
bounds.h = bounds.y2() - tagBounds.y2();
|
||||
bounds.y = tagBounds.y2();
|
||||
|
||||
gfx::Rect bandBounds = getPartBounds(Hit(PART_TAG_BAND, 0, 0, doc::NullId, band));
|
||||
|
||||
const int fw = frameBoxWidth()/2;
|
||||
if (gfx::Rect(bounds.x2()-fw, bounds.y, fw, bounds.h).contains(mousePos)) {
|
||||
hit.part = PART_TAG_RIGHT;
|
||||
hit.tag = tag->id();
|
||||
hit.band = band;
|
||||
// If we are in the band, we hit this tag, in other
|
||||
// case, we can try to hit other tag that might be a
|
||||
// better match.
|
||||
if (bandBounds.contains(mousePos))
|
||||
break;
|
||||
}
|
||||
else if (gfx::Rect(bounds.x, bounds.y, fw, bounds.h).contains(mousePos)) {
|
||||
hit.part = PART_TAG_LEFT;
|
||||
hit.tag = tag->id();
|
||||
hit.band = band;
|
||||
if (bandBounds.contains(mousePos))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3170,6 +3313,11 @@ Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Is the mouse on the separator.
|
||||
else if (mousePos.x > separatorX()-4 &&
|
||||
mousePos.x <= separatorX()) {
|
||||
hit.part = PART_SEPARATOR;
|
||||
}
|
||||
// Is the mouse on the headers?
|
||||
else if (mousePos.y >= top && mousePos.y < top+headerBoxHeight()) {
|
||||
if (mousePos.x < separatorX()) {
|
||||
@ -4244,4 +4392,17 @@ void Timeline::setSeparatorX(int newValue)
|
||||
m_separator_x = std::max(0, newValue);
|
||||
}
|
||||
|
||||
//static
|
||||
gfx::Color Timeline::highlightColor(const gfx::Color color)
|
||||
{
|
||||
int r, g, b;
|
||||
r = gfx::getr(color)+64; // TODO make this customizable in the theme XML?
|
||||
g = gfx::getg(color)+64;
|
||||
b = gfx::getb(color)+64;
|
||||
r = base::clamp(r, 0, 255);
|
||||
g = base::clamp(g, 0, 255);
|
||||
b = base::clamp(b, 0, 255);
|
||||
return gfx::rgba(r, g, b, gfx::geta(color));
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -10,18 +10,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/doc_observer.h"
|
||||
#include "app/docs_observer.h"
|
||||
#include "app/doc_range.h"
|
||||
#include "app/docs_observer.h"
|
||||
#include "app/loop_tag.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/ui/editor/editor_observer.h"
|
||||
#include "app/ui/input_chain_element.h"
|
||||
#include "app/ui/timeline/ani_controls.h"
|
||||
#include "base/debug.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/selected_frames.h"
|
||||
#include "doc/selected_layers.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tag.h"
|
||||
#include "gfx/color.h"
|
||||
#include "obs/connection.h"
|
||||
#include "ui/scroll_bar.h"
|
||||
#include "ui/timer.h"
|
||||
@ -76,6 +79,9 @@ namespace app {
|
||||
STATE_MOVING_RANGE,
|
||||
STATE_MOVING_ONIONSKIN_RANGE_LEFT,
|
||||
STATE_MOVING_ONIONSKIN_RANGE_RIGHT,
|
||||
STATE_MOVING_TAG,
|
||||
STATE_RESIZING_TAG_LEFT,
|
||||
STATE_RESIZING_TAG_RIGHT,
|
||||
// Changing layers flags states
|
||||
STATE_SHOWING_LAYERS,
|
||||
STATE_HIDING_LAYERS,
|
||||
@ -273,6 +279,10 @@ namespace app {
|
||||
Cel* cel, frame_t frame, bool is_active, bool is_hover,
|
||||
DrawCelData* data);
|
||||
void drawTags(ui::Graphics* g);
|
||||
void drawTagBraces(ui::Graphics* g,
|
||||
gfx::Color tagColor,
|
||||
const gfx::Rect& bounds,
|
||||
const gfx::Rect& clipBounds);
|
||||
void drawRangeOutline(ui::Graphics* g);
|
||||
void drawPaddings(ui::Graphics* g);
|
||||
bool drawPart(ui::Graphics* g, int part, layer_t layer, frame_t frame);
|
||||
@ -360,6 +370,8 @@ namespace app {
|
||||
int separatorX() const;
|
||||
void setSeparatorX(int newValue);
|
||||
|
||||
static gfx::Color highlightColor(const gfx::Color color);
|
||||
|
||||
ui::ScrollBar m_hbar;
|
||||
ui::ScrollBar m_vbar;
|
||||
gfx::Rect m_viewportArea;
|
||||
@ -421,6 +433,26 @@ namespace app {
|
||||
layer_t activeRelativeLayer;
|
||||
frame_t activeRelativeFrame;
|
||||
} m_moveRangeData;
|
||||
|
||||
// Temporal data used to move tags.
|
||||
struct ResizeTag {
|
||||
doc::ObjectId tag = doc::NullId;
|
||||
doc::frame_t from, to;
|
||||
void reset() {
|
||||
tag = doc::NullId;
|
||||
}
|
||||
void reset(const doc::ObjectId tagId) {
|
||||
auto tag = doc::get<doc::Tag>(tagId);
|
||||
if (tag) {
|
||||
this->tag = tagId;
|
||||
this->from = tag->fromFrame();
|
||||
this->to = tag->toFrame();
|
||||
}
|
||||
else {
|
||||
this->tag = doc::NullId;
|
||||
}
|
||||
}
|
||||
} m_resizeTagData;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
|
Loading…
x
Reference in New Issue
Block a user