1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "app/ui/timeline/timeline.h"
12
13 #include "app/app.h"
14 #include "app/app_menus.h"
15 #include "app/color_utils.h"
16 #include "app/commands/command.h"
17 #include "app/commands/commands.h"
18 #include "app/commands/params.h"
19 #include "app/console.h"
20 #include "app/context_access.h"
21 #include "app/doc.h"
22 #include "app/doc_api.h"
23 #include "app/doc_event.h"
24 #include "app/doc_range_ops.h"
25 #include "app/doc_undo.h"
26 #include "app/loop_tag.h"
27 #include "app/modules/editors.h"
28 #include "app/modules/gfx.h"
29 #include "app/modules/gui.h"
30 #include "app/thumbnails.h"
31 #include "app/transaction.h"
32 #include "app/ui/app_menuitem.h"
33 #include "app/ui/configure_timeline_popup.h"
34 #include "app/ui/doc_view.h"
35 #include "app/ui/editor/editor.h"
36 #include "app/ui/input_chain.h"
37 #include "app/ui/skin/skin_theme.h"
38 #include "app/ui/status_bar.h"
39 #include "app/ui/workspace.h"
40 #include "app/ui_context.h"
41 #include "app/util/clipboard.h"
42 #include "app/util/layer_boundaries.h"
43 #include "app/util/readable_time.h"
44 #include "base/bind.h"
45 #include "base/convert_to.h"
46 #include "base/memory.h"
47 #include "base/scoped_value.h"
48 #include "base/unique_ptr.h"
49 #include "doc/doc.h"
50 #include "doc/frame_tag.h"
51 #include "gfx/point.h"
52 #include "gfx/rect.h"
53 #include "she/font.h"
54 #include "she/surface.h"
55 #include "she/system.h"
56 #include "ui/scroll_helper.h"
57 #include "ui/ui.h"
58
59 #include <cstdio>
60 #include <vector>
61
62 namespace app {
63
64 using namespace app::skin;
65 using namespace gfx;
66 using namespace doc;
67 using namespace ui;
68
69 enum {
70 PART_NOTHING = 0,
71 PART_TOP,
72 PART_SEPARATOR,
73 PART_HEADER_EYE,
74 PART_HEADER_PADLOCK,
75 PART_HEADER_CONTINUOUS,
76 PART_HEADER_GEAR,
77 PART_HEADER_ONIONSKIN,
78 PART_HEADER_ONIONSKIN_RANGE_LEFT,
79 PART_HEADER_ONIONSKIN_RANGE_RIGHT,
80 PART_HEADER_LAYER,
81 PART_HEADER_FRAME,
82 PART_ROW,
83 PART_ROW_EYE_ICON,
84 PART_ROW_PADLOCK_ICON,
85 PART_ROW_CONTINUOUS_ICON,
86 PART_ROW_TEXT,
87 PART_CEL,
88 PART_RANGE_OUTLINE,
89 PART_FRAME_TAG,
90 PART_FRAME_TAGS,
91 PART_FRAME_TAG_BAND,
92 PART_FRAME_TAG_SWITCH_BUTTONS,
93 PART_FRAME_TAG_SWITCH_BAND_BUTTON,
94 };
95
96 struct Timeline::DrawCelData {
97 CelIterator begin;
98 CelIterator end;
99 CelIterator it;
100 CelIterator prevIt; // Previous Cel to "it"
101 CelIterator nextIt; // Next Cel to "it"
102 CelIterator activeIt; // Active Cel iterator
103 CelIterator firstLink; // First link to the active cel
104 CelIterator lastLink; // Last link to the active cel
105 };
106
107 namespace {
108
109 template<typename Pred>
for_each_expanded_layer(LayerGroup * group,Pred && pred,int level=0,LayerFlags flags=LayerFlags (int (LayerFlags::Visible)|int (LayerFlags::Editable)))110 void for_each_expanded_layer(LayerGroup* group,
111 Pred&& pred,
112 int level = 0,
113 LayerFlags flags =
114 LayerFlags(int(LayerFlags::Visible) |
115 int(LayerFlags::Editable))) {
116 if (!group->isVisible())
117 flags = static_cast<LayerFlags>(int(flags) & ~int(LayerFlags::Visible));
118
119 if (!group->isEditable())
120 flags = static_cast<LayerFlags>(int(flags) & ~int(LayerFlags::Editable));
121
122 for (Layer* child : group->layers()) {
123 if (child->isGroup() && !child->isCollapsed())
124 for_each_expanded_layer<Pred>(
125 static_cast<LayerGroup*>(child),
126 std::forward<Pred>(pred),
127 level+1,
128 flags);
129
130 pred(child, level, flags);
131 }
132 }
133
is_copy_key_pressed(ui::Message * msg)134 bool is_copy_key_pressed(ui::Message* msg) {
135 return
136 msg->ctrlPressed() || // Ctrl is common on Windows
137 msg->altPressed(); // Alt is common on Mac OS X
138 }
139
is_select_layer_in_canvas_key_pressed(ui::Message * msg)140 bool is_select_layer_in_canvas_key_pressed(ui::Message* msg) {
141 #ifdef __APPLE__
142 return msg->cmdPressed();
143 #else
144 return msg->ctrlPressed();
145 #endif
146 }
147
get_select_layer_in_canvas_op(ui::Message * msg)148 SelectLayerBoundariesOp get_select_layer_in_canvas_op(ui::Message* msg) {
149 if (msg->altPressed() && msg->shiftPressed())
150 return SelectLayerBoundariesOp::INTERSECT;
151 else if (msg->shiftPressed())
152 return SelectLayerBoundariesOp::ADD;
153 else if (msg->altPressed())
154 return SelectLayerBoundariesOp::SUBTRACT;
155 else
156 return SelectLayerBoundariesOp::REPLACE;
157 }
158
159 } // anonymous namespace
160
Hit(int part,layer_t layer,frame_t frame,ObjectId frameTag,int band)161 Timeline::Hit::Hit(int part,
162 layer_t layer,
163 frame_t frame,
164 ObjectId frameTag,
165 int band)
166 : part(part),
167 layer(layer),
168 frame(frame),
169 frameTag(frameTag),
170 veryBottom(false),
171 band(band)
172 {
173 }
174
operator !=(const Hit & other) const175 bool Timeline::Hit::operator!=(const Hit& other) const
176 {
177 return
178 part != other.part ||
179 layer != other.layer ||
180 frame != other.frame ||
181 frameTag != other.frameTag ||
182 band != other.band;
183 }
184
getFrameTag() const185 FrameTag* Timeline::Hit::getFrameTag() const
186 {
187 return get<FrameTag>(frameTag);
188 }
189
DropTarget()190 Timeline::DropTarget::DropTarget()
191 {
192 hhit = HNone;
193 vhit = VNone;
194 outside = false;
195 }
196
DropTarget(const DropTarget & o)197 Timeline::DropTarget::DropTarget(const DropTarget& o)
198 : hhit(o.hhit)
199 , vhit(o.vhit)
200 , outside(o.outside)
201 {
202 }
203
Row()204 Timeline::Row::Row()
205 : m_layer(nullptr),
206 m_level(0),
207 m_inheritedFlags(LayerFlags::None)
208 {
209 }
210
Row(Layer * layer,const int level,const LayerFlags inheritedFlags)211 Timeline::Row::Row(Layer* layer,
212 const int level,
213 const LayerFlags inheritedFlags)
214 : m_layer(layer),
215 m_level(level),
216 m_inheritedFlags(inheritedFlags)
217 {
218 }
219
parentVisible() const220 bool Timeline::Row::parentVisible() const
221 {
222 return ((int(m_inheritedFlags) & int(LayerFlags::Visible)) != 0);
223 }
224
parentEditable() const225 bool Timeline::Row::parentEditable() const
226 {
227 return ((int(m_inheritedFlags) & int(LayerFlags::Editable)) != 0);
228 }
229
Timeline()230 Timeline::Timeline()
231 : Widget(kGenericWidget)
232 , m_hbar(HORIZONTAL, this)
233 , m_vbar(VERTICAL, this)
234 , m_zoom(1.0)
235 , m_context(UIContext::instance())
236 , m_editor(NULL)
237 , m_document(NULL)
238 , m_sprite(NULL)
239 , m_rangeLocks(0)
240 , m_state(STATE_STANDBY)
241 , m_tagBands(0)
242 , m_tagFocusBand(-1)
243 , m_separator_x(100 * guiscale())
244 , m_separator_w(1)
245 , m_confPopup(NULL)
246 , m_clipboard_timer(100, this)
247 , m_offset_count(0)
248 , m_redrawMarchingAntsOnly(false)
249 , m_scroll(false)
250 , m_fromTimeline(false)
251 {
252 enableFlags(CTRL_RIGHT_CLICK);
253
254 m_ctxConn = m_context->AfterCommandExecution.connect(
255 &Timeline::onAfterCommandExecution, this);
256 m_context->documents().add_observer(this);
257 m_context->add_observer(this);
258
259 setDoubleBuffered(true);
260 addChild(&m_aniControls);
261 addChild(&m_hbar);
262 addChild(&m_vbar);
263
264 m_hbar.setTransparent(true);
265 m_vbar.setTransparent(true);
266 initTheme();
267 }
268
~Timeline()269 Timeline::~Timeline()
270 {
271 m_clipboard_timer.stop();
272
273 detachDocument();
274 m_context->documents().remove_observer(this);
275 m_context->remove_observer(this);
276 delete m_confPopup;
277 }
278
setZoom(const double zoom)279 void Timeline::setZoom(const double zoom)
280 {
281 m_zoom = MID(1.0, zoom, 10.0);
282 m_thumbnailsOverlayDirection = gfx::Point(int(frameBoxWidth()*1.0), int(frameBoxWidth()*0.5));
283 m_thumbnailsOverlayVisible = false;
284 }
285
setZoomAndUpdate(const double zoom,const bool updatePref)286 void Timeline::setZoomAndUpdate(const double zoom,
287 const bool updatePref)
288 {
289 if (zoom != m_zoom) {
290 setZoom(zoom);
291 regenerateTagBands();
292 updateScrollBars();
293 setViewScroll(viewScroll());
294 invalidate();
295 }
296 if (updatePref && zoom != docPref().thumbnails.zoom()) {
297 docPref().thumbnails.zoom(zoom);
298 docPref().thumbnails.enabled(zoom > 1);
299 }
300 }
301
onThumbnailsPrefChange()302 void Timeline::onThumbnailsPrefChange()
303 {
304 setZoomAndUpdate(
305 docPref().thumbnails.enabled() ?
306 docPref().thumbnails.zoom(): 1.0,
307 false);
308 }
309
updateUsingEditor(Editor * editor)310 void Timeline::updateUsingEditor(Editor* editor)
311 {
312 // TODO if editor == m_editor, avoid doing a lot of extra work here
313
314 m_aniControls.updateUsingEditor(editor);
315
316 if (editor != m_editor) {
317 // Save active m_tagFocusBand into the old focused editor
318 if (m_editor)
319 m_editor->setTagFocusBand(m_tagFocusBand);
320 m_tagFocusBand = -1;
321 }
322
323 detachDocument();
324
325 if (m_range.enabled() &&
326 m_rangeLocks == 0) {
327 m_range.clearRange();
328 }
329
330 // We always update the editor. In this way the timeline keeps in
331 // sync with the active editor.
332 m_editor = editor;
333 if (!m_editor)
334 return; // No editor specified.
335
336 m_editor->add_observer(this);
337 m_tagFocusBand = m_editor->tagFocusBand();
338
339 Site site;
340 DocView* view = m_editor->getDocView();
341 view->getSite(&site);
342
343 site.document()->add_observer(this);
344
345 Doc* app_document = site.document();
346 DocumentPreferences& docPref = Preferences::instance().document(app_document);
347
348 m_thumbnailsPrefConn = docPref.thumbnails.AfterChange.connect(
349 base::Bind<void>(&Timeline::onThumbnailsPrefChange, this));
350
351 setZoom(
352 docPref.thumbnails.enabled() ?
353 docPref.thumbnails.zoom(): 1.0);
354
355 // If we are already in the same position as the "editor", we don't
356 // need to update the at all timeline.
357 if (m_document == site.document() &&
358 m_sprite == site.sprite() &&
359 m_layer == site.layer() &&
360 m_frame == site.frame())
361 return;
362
363 m_document = site.document();
364 m_sprite = site.sprite();
365 m_layer = site.layer();
366 m_frame = site.frame();
367 m_state = STATE_STANDBY;
368 m_hot.part = PART_NOTHING;
369 m_clk.part = PART_NOTHING;
370
371 m_firstFrameConn = Preferences::instance().document(m_document)
372 .timeline.firstFrame.AfterChange.connect(base::Bind<void>(&Timeline::invalidate, this));
373
374 setFocusStop(true);
375 regenerateRows();
376 setViewScroll(viewScroll());
377 showCurrentCel();
378 }
379
detachDocument()380 void Timeline::detachDocument()
381 {
382 m_firstFrameConn.disconnect();
383
384 if (m_document) {
385 m_thumbnailsPrefConn.disconnect();
386 m_document->remove_observer(this);
387 m_document = nullptr;
388 m_sprite = nullptr;
389 m_layer = nullptr;
390 }
391
392 if (m_editor) {
393 m_editor->remove_observer(this);
394 m_editor = nullptr;
395 }
396
397 invalidate();
398 }
399
isMovingCel() const400 bool Timeline::isMovingCel() const
401 {
402 return (m_state == STATE_MOVING_RANGE &&
403 m_range.type() == Range::kCels);
404 }
405
selectedLayersBounds(const SelectedLayers & layers,layer_t * first,layer_t * last) const406 bool Timeline::selectedLayersBounds(const SelectedLayers& layers,
407 layer_t* first, layer_t* last) const
408 {
409 if (layers.empty())
410 return false;
411
412 *first = *last = getLayerIndex(*layers.begin());
413
414 for (auto layer : layers) {
415 layer_t i = getLayerIndex(layer);
416 if (*first > i) *first = i;
417 if (*last < i) *last = i;
418 }
419
420 return true;
421 }
422
setLayer(Layer * layer)423 void Timeline::setLayer(Layer* layer)
424 {
425 ASSERT(m_editor != NULL);
426
427 invalidateLayer(m_layer);
428 invalidateLayer(layer);
429
430 m_layer = layer;
431
432 // Expand all parents
433 if (m_layer) {
434 LayerGroup* group = m_layer->parent();
435 while (group != m_layer->sprite()->root()) {
436 // Expand this group
437 group->setCollapsed(false);
438 group = group->parent();
439 }
440 regenerateRows();
441 invalidate();
442 }
443
444 if (m_editor->layer() != layer)
445 m_editor->setLayer(m_layer);
446 }
447
setFrame(frame_t frame,bool byUser)448 void Timeline::setFrame(frame_t frame, bool byUser)
449 {
450 ASSERT(m_editor != NULL);
451 // ASSERT(frame >= 0 && frame < m_sprite->totalFrames());
452
453 if (frame < 0)
454 frame = firstFrame();
455 else if (frame >= m_sprite->totalFrames())
456 frame = frame_t(m_sprite->totalFrames()-1);
457
458 if (m_layer) {
459 Cel* oldCel = m_layer->cel(m_frame);
460 Cel* newCel = m_layer->cel(frame);
461 std::size_t oldLinks = (oldCel ? oldCel->links(): 0);
462 std::size_t newLinks = (newCel ? newCel->links(): 0);
463 if ((oldLinks && !newCel) ||
464 (newLinks && !oldCel) ||
465 ((oldLinks || newLinks) && (oldCel->data() != newCel->data())))
466 invalidateLayer(m_layer);
467 }
468
469 invalidateFrame(m_frame);
470 invalidateFrame(frame);
471
472 gfx::Rect onionRc = getOnionskinFramesBounds();
473
474 m_frame = frame;
475
476 // Invalidate the onionskin handles area
477 onionRc |= getOnionskinFramesBounds();
478 if (!onionRc.isEmpty())
479 invalidateRect(onionRc.offset(origin()));
480
481 if (m_editor->frame() != frame) {
482 const bool isPlaying = m_editor->isPlaying();
483
484 if (isPlaying)
485 m_editor->stop();
486
487 m_editor->setFrame(m_frame);
488
489 if (isPlaying)
490 m_editor->play(false,
491 Preferences::instance().editor.playAll());
492 }
493 }
494
prepareToMoveRange()495 void Timeline::prepareToMoveRange()
496 {
497 ASSERT(m_range.enabled());
498
499 layer_t i = 0;
500 for (auto layer : m_range.selectedLayers().toLayerList()) {
501 if (layer == m_layer)
502 break;
503 ++i;
504 }
505
506 frame_t j = 0;
507 for (auto frame : m_range.selectedFrames()) {
508 if (frame == m_frame)
509 break;
510 ++j;
511 }
512
513 m_moveRangeData.activeRelativeLayer = i;
514 m_moveRangeData.activeRelativeFrame = j;
515 }
516
moveRange(const Range & range)517 void Timeline::moveRange(const Range& range)
518 {
519 regenerateRows();
520
521 // We have to change the range before we generate an
522 // onActiveSiteChange() event so observers (like cel properties
523 // dialog) know the new selected range.
524 m_range = range;
525
526 layer_t i = 0;
527 for (auto layer : range.selectedLayers().toLayerList()) {
528 if (i == m_moveRangeData.activeRelativeLayer) {
529 setLayer(layer);
530 break;
531 }
532 ++i;
533 }
534
535 frame_t j = 0;
536 for (auto frame : range.selectedFrames()) {
537 if (j == m_moveRangeData.activeRelativeFrame) {
538 setFrame(frame, true);
539 break;
540 }
541 ++j;
542 }
543
544 // Select the range again (it might be lost between all the
545 // setLayer()/setFrame() calls).
546 m_range = range;
547 }
548
setRange(const Range & range)549 void Timeline::setRange(const Range& range)
550 {
551 m_range = range;
552 invalidate();
553 }
554
activateClipboardRange()555 void Timeline::activateClipboardRange()
556 {
557 m_clipboard_timer.start();
558 invalidate();
559 }
560
getFrameTagByFrame(const frame_t frame,const bool getLoopTagIfNone)561 FrameTag* Timeline::getFrameTagByFrame(const frame_t frame,
562 const bool getLoopTagIfNone)
563 {
564 if (!m_sprite)
565 return nullptr;
566
567 if (m_tagFocusBand < 0) {
568 FrameTag* tag = get_animation_tag(m_sprite, frame);
569 if (!tag && getLoopTagIfNone)
570 tag = get_loop_tag(m_sprite);
571 return tag;
572 }
573
574 for (FrameTag* frameTag : m_sprite->frameTags()) {
575 if (frame >= frameTag->fromFrame() &&
576 frame <= frameTag->toFrame() &&
577 m_tagBand[frameTag] == m_tagFocusBand) {
578 return frameTag;
579 }
580 }
581
582 return nullptr;
583 }
584
onProcessMessage(Message * msg)585 bool Timeline::onProcessMessage(Message* msg)
586 {
587 switch (msg->type()) {
588
589 case kFocusEnterMessage:
590 App::instance()->inputChain().prioritize(this, msg);
591 break;
592
593 case kTimerMessage:
594 if (static_cast<TimerMessage*>(msg)->timer() == &m_clipboard_timer) {
595 Doc* clipboard_document;
596 DocRange clipboard_range;
597 clipboard::get_document_range_info(
598 &clipboard_document,
599 &clipboard_range);
600
601 if (isVisible() &&
602 m_document &&
603 m_document == clipboard_document) {
604 // Set offset to make selection-movement effect
605 if (m_offset_count < 7)
606 m_offset_count++;
607 else
608 m_offset_count = 0;
609
610 bool redrawOnlyMarchingAnts = getUpdateRegion().isEmpty();
611 invalidateRect(gfx::Rect(getRangeBounds(clipboard_range)).offset(origin()));
612 if (redrawOnlyMarchingAnts)
613 m_redrawMarchingAntsOnly = true;
614 }
615 else if (m_clipboard_timer.isRunning()) {
616 m_clipboard_timer.stop();
617 }
618 }
619 break;
620
621 case kMouseDownMessage: {
622 MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
623
624 if (!m_document)
625 break;
626
627 if (mouseMsg->middle() ||
628 she::instance()->isKeyPressed(kKeySpace)) {
629 captureMouse();
630 m_state = STATE_SCROLLING;
631 m_oldPos = static_cast<MouseMessage*>(msg)->position();
632 return true;
633 }
634
635 // As we can ctrl+click color bar + timeline, now we have to
636 // re-prioritize timeline on each click.
637 App::instance()->inputChain().prioritize(this, msg);
638
639 // Update hot part (as the user might have left clicked with
640 // Ctrl on OS X, which it's converted to a right-click and it's
641 // interpreted as other action by the Timeline::hitTest())
642 setHot(hitTest(msg, mouseMsg->position() - bounds().origin()));
643
644 // Clicked-part = hot-part.
645 m_clk = m_hot;
646
647 // With Ctrl+click (Win/Linux) or Shift+click (OS X) we can
648 // select non-adjacents layer/frame ranges
649 bool clearRange =
650 #if !defined(__APPLE__)
651 !msg->ctrlPressed() &&
652 #endif
653 !msg->shiftPressed();
654
655 captureMouse();
656
657 switch (m_hot.part) {
658
659 case PART_SEPARATOR:
660 m_state = STATE_MOVING_SEPARATOR;
661 break;
662
663 case PART_HEADER_EYE: {
664 ASSERT(m_sprite);
665 if (!m_sprite)
666 break;
667
668 bool regenRows = false;
669 bool newVisibleState = !allLayersVisible();
670 for (Layer* topLayer : m_sprite->root()->layers()) {
671 if (topLayer->isVisible() != newVisibleState) {
672 topLayer->setVisible(newVisibleState);
673 if (topLayer->isGroup())
674 regenRows = true;
675 }
676 }
677
678 if (regenRows) {
679 regenerateRows();
680 invalidate();
681 }
682
683 // Redraw all views.
684 m_document->notifyGeneralUpdate();
685 break;
686 }
687
688 case PART_HEADER_PADLOCK: {
689 ASSERT(m_sprite);
690 if (!m_sprite)
691 break;
692
693 bool regenRows = false;
694 bool newEditableState = !allLayersUnlocked();
695 for (Layer* topLayer : m_sprite->root()->layers()) {
696 if (topLayer->isEditable() != newEditableState) {
697 topLayer->setEditable(newEditableState);
698 if (topLayer->isGroup()) {
699 regenRows = true;
700 }
701 }
702 }
703
704 if (regenRows) {
705 regenerateRows();
706 invalidate();
707 }
708 break;
709 }
710
711 case PART_HEADER_CONTINUOUS: {
712 bool newContinuousState = !allLayersContinuous();
713 for (size_t i=0; i<m_rows.size(); i++)
714 m_rows[i].layer()->setContinuous(newContinuousState);
715 invalidate();
716 break;
717 }
718
719 case PART_HEADER_ONIONSKIN: {
720 docPref().onionskin.active(!docPref().onionskin.active());
721 invalidate();
722 break;
723 }
724 case PART_HEADER_ONIONSKIN_RANGE_LEFT: {
725 m_state = STATE_MOVING_ONIONSKIN_RANGE_LEFT;
726 m_origFrames = docPref().onionskin.prevFrames();
727 break;
728 }
729 case PART_HEADER_ONIONSKIN_RANGE_RIGHT: {
730 m_state = STATE_MOVING_ONIONSKIN_RANGE_RIGHT;
731 m_origFrames = docPref().onionskin.nextFrames();
732 break;
733 }
734 case PART_HEADER_FRAME: {
735 bool selectFrame = (mouseMsg->left() || !isFrameActive(m_clk.frame));
736
737 if (selectFrame) {
738 m_state = STATE_SELECTING_FRAMES;
739 if (clearRange)
740 clearAndInvalidateRange();
741 m_range.startRange(m_layer, m_clk.frame, Range::kFrames);
742 m_startRange = m_range;
743
744 setFrame(m_clk.frame, true);
745 }
746 break;
747 }
748 case PART_ROW_TEXT: {
749 base::ScopedValue<bool> lock(m_fromTimeline, true, false);
750 const layer_t old_layer = getLayerIndex(m_layer);
751 const bool selectLayer = (mouseMsg->left() || !isLayerActive(m_clk.layer));
752 const bool selectLayerInCanvas =
753 (m_clk.layer != -1 &&
754 mouseMsg->left() &&
755 is_select_layer_in_canvas_key_pressed(mouseMsg));
756
757 if (selectLayerInCanvas) {
758 select_layer_boundaries(m_rows[m_clk.layer].layer(), m_frame,
759 get_select_layer_in_canvas_op(mouseMsg));
760 }
761 else if (selectLayer) {
762 m_state = STATE_SELECTING_LAYERS;
763 if (clearRange)
764 clearAndInvalidateRange();
765 m_range.startRange(m_rows[m_clk.layer].layer(),
766 m_frame, Range::kLayers);
767 m_startRange = m_range;
768
769 // Did the user select another layer?
770 if (old_layer != m_clk.layer) {
771 setLayer(m_rows[m_clk.layer].layer());
772 invalidate();
773 }
774 }
775
776 // Change the scroll to show the new selected layer/cel.
777 showCel(m_clk.layer, m_frame);
778 break;
779 }
780
781 case PART_ROW_EYE_ICON:
782 if (validLayer(m_clk.layer)) {
783 Row& row = m_rows[m_clk.layer];
784 Layer* layer = row.layer();
785 ASSERT(layer)
786
787 // Hide everything or restore alternative state
788 bool oneWithInternalState = false;
789 if (msg->altPressed()) {
790 for (const Row& row : m_rows) {
791 const Layer* l = row.layer();
792 if (l->hasFlags(LayerFlags::Internal_WasVisible)) {
793 oneWithInternalState = true;
794 break;
795 }
796 }
797
798 // If there is one layer with the internal state, restore the previous visible state
799 if (oneWithInternalState) {
800 for (Row& row : m_rows) {
801 Layer* l = row.layer();
802 if (l->hasFlags(LayerFlags::Internal_WasVisible)) {
803 l->setVisible(true);
804 l->switchFlags(LayerFlags::Internal_WasVisible, false);
805 }
806 else {
807 l->setVisible(false);
808 }
809 }
810 }
811 // In other case, hide everything
812 else {
813 for (Row& row : m_rows) {
814 Layer* l = row.layer();
815 l->switchFlags(LayerFlags::Internal_WasVisible, l->isVisible());
816 l->setVisible(false);
817 }
818 }
819
820 regenerateRows();
821 invalidate();
822
823 m_document->notifyGeneralUpdate();
824 }
825
826 if (layer->isVisible() && !oneWithInternalState)
827 m_state = STATE_HIDING_LAYERS;
828 else
829 m_state = STATE_SHOWING_LAYERS;
830
831 setLayerVisibleFlag(m_clk.layer, m_state == STATE_SHOWING_LAYERS);
832 }
833 break;
834
835 case PART_ROW_PADLOCK_ICON:
836 if (validLayer(m_hot.layer)) {
837 Row& row = m_rows[m_clk.layer];
838 Layer* layer = row.layer();
839 ASSERT(layer);
840 if (layer->isEditable())
841 m_state = STATE_LOCKING_LAYERS;
842 else
843 m_state = STATE_UNLOCKING_LAYERS;
844
845 setLayerEditableFlag(m_clk.layer, m_state == STATE_UNLOCKING_LAYERS);
846 }
847 break;
848
849 case PART_ROW_CONTINUOUS_ICON:
850 if (validLayer(m_hot.layer)) {
851 Row& row = m_rows[m_clk.layer];
852 Layer* layer = row.layer();
853 ASSERT(layer);
854
855 if (layer->isImage()) {
856 if (layer->isContinuous())
857 m_state = STATE_DISABLING_CONTINUOUS_LAYERS;
858 else
859 m_state = STATE_ENABLING_CONTINUOUS_LAYERS;
860
861 setLayerContinuousFlag(m_clk.layer, m_state == STATE_ENABLING_CONTINUOUS_LAYERS);
862 }
863 else if (layer->isGroup()) {
864 if (layer->isCollapsed())
865 m_state = STATE_EXPANDING_LAYERS;
866 else
867 m_state = STATE_COLLAPSING_LAYERS;
868
869 setLayerCollapsedFlag(m_clk.layer, m_state == STATE_COLLAPSING_LAYERS);
870 updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
871 }
872 }
873 break;
874
875 case PART_CEL: {
876 base::ScopedValue<bool> lock(m_fromTimeline, true, false);
877 const layer_t old_layer = getLayerIndex(m_layer);
878 const bool selectCel = (mouseMsg->left()
879 || !isLayerActive(m_clk.layer)
880 || !isFrameActive(m_clk.frame));
881 const bool selectCelInCanvas =
882 (m_clk.layer != -1 &&
883 mouseMsg->left() &&
884 is_select_layer_in_canvas_key_pressed(mouseMsg));
885 const frame_t old_frame = m_frame;
886
887 if (selectCelInCanvas) {
888 select_layer_boundaries(m_rows[m_clk.layer].layer(),
889 m_clk.frame,
890 get_select_layer_in_canvas_op(mouseMsg));
891 }
892 else {
893 if (selectCel) {
894 m_state = STATE_SELECTING_CELS;
895 if (clearRange)
896 clearAndInvalidateRange();
897 m_range.startRange(m_rows[m_clk.layer].layer(),
898 m_clk.frame, Range::kCels);
899 m_startRange = m_range;
900 }
901
902 // Select the new clicked-part.
903 if (old_layer != m_clk.layer
904 || old_frame != m_clk.frame) {
905 setLayer(m_rows[m_clk.layer].layer());
906 setFrame(m_clk.frame, true);
907 invalidate();
908 }
909 }
910
911 // Change the scroll to show the new selected cel.
912 showCel(m_clk.layer, m_frame);
913 invalidate();
914 break;
915 }
916 case PART_RANGE_OUTLINE:
917 m_state = STATE_MOVING_RANGE;
918
919 // If we select the outline of a cels range, we have to
920 // recalculate the dragged cel (m_clk) using a special
921 // hitTestCel() and limiting the clicked cel inside the
922 // range bounds.
923 if (m_range.type() == Range::kCels) {
924 m_clk = hitTestCel(mouseMsg->position() - bounds().origin());
925
926 if (m_range.layers() > 0) {
927 layer_t layerFirst, layerLast;
928 if (selectedLayersBounds(selectedLayers(),
929 &layerFirst, &layerLast)) {
930 layer_t layerIdx = m_clk.layer;
931 layerIdx = MID(layerFirst, layerIdx, layerLast);
932 m_clk.layer = layerIdx;
933 }
934 }
935
936 if (m_clk.frame < m_range.firstFrame())
937 m_clk.frame = m_range.firstFrame();
938 else if (m_clk.frame > m_range.lastFrame())
939 m_clk.frame = m_range.lastFrame();
940 }
941 break;
942 }
943
944 // Redraw the new clicked part (header, layer or cel).
945 invalidateHit(m_clk);
946 break;
947 }
948
949 case kMouseLeaveMessage: {
950 if (m_hot.part != PART_NOTHING) {
951 invalidateHit(m_hot);
952 m_hot = Hit();
953 }
954 break;
955 }
956
957 case kMouseMoveMessage: {
958 if (!m_document)
959 break;
960
961 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position()
962 - bounds().origin();
963
964 Hit hit;
965 setHot(hit = hitTest(msg, mousePos));
966
967 if (hasCapture()) {
968 switch (m_state) {
969
970 case STATE_SCROLLING: {
971 gfx::Point absMousePos = static_cast<MouseMessage*>(msg)->position();
972 setViewScroll(
973 viewScroll() - gfx::Point(
974 (absMousePos.x - m_oldPos.x),
975 (absMousePos.y - m_oldPos.y)));
976
977 m_oldPos = absMousePos;
978 return true;
979 }
980
981 case STATE_MOVING_ONIONSKIN_RANGE_LEFT: {
982 gfx::Rect onionRc = getOnionskinFramesBounds();
983
984 int newValue = m_origFrames + (m_clk.frame - hit.frame);
985 docPref().onionskin.prevFrames(MAX(0, newValue));
986
987 onionRc |= getOnionskinFramesBounds();
988 invalidateRect(onionRc.offset(origin()));
989 return true;
990 }
991
992 case STATE_MOVING_ONIONSKIN_RANGE_RIGHT: {
993 gfx::Rect onionRc = getOnionskinFramesBounds();
994
995 int newValue = m_origFrames - (m_clk.frame - hit.frame);
996 docPref().onionskin.nextFrames(MAX(0, newValue));
997
998 onionRc |= getOnionskinFramesBounds();
999 invalidateRect(onionRc.offset(origin()));
1000 return true;
1001 }
1002
1003 case STATE_SHOWING_LAYERS:
1004 case STATE_HIDING_LAYERS:
1005 m_clk = hit;
1006 if (hit.part == PART_ROW_EYE_ICON) {
1007 setLayerVisibleFlag(hit.layer, m_state == STATE_SHOWING_LAYERS);
1008 }
1009 break;
1010
1011 case STATE_LOCKING_LAYERS:
1012 case STATE_UNLOCKING_LAYERS:
1013 m_clk = hit;
1014 if (hit.part == PART_ROW_PADLOCK_ICON) {
1015 setLayerEditableFlag(hit.layer, m_state == STATE_UNLOCKING_LAYERS);
1016 }
1017 break;
1018
1019 case STATE_ENABLING_CONTINUOUS_LAYERS:
1020 case STATE_DISABLING_CONTINUOUS_LAYERS:
1021 m_clk = hit;
1022 if (hit.part == PART_ROW_CONTINUOUS_ICON) {
1023 setLayerContinuousFlag(hit.layer, m_state == STATE_ENABLING_CONTINUOUS_LAYERS);
1024 }
1025 break;
1026
1027 case STATE_EXPANDING_LAYERS:
1028 case STATE_COLLAPSING_LAYERS:
1029 m_clk = hit;
1030 if (hit.part == PART_ROW_CONTINUOUS_ICON) {
1031 setLayerCollapsedFlag(hit.layer, m_state == STATE_COLLAPSING_LAYERS);
1032 updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
1033 }
1034 break;
1035
1036 }
1037
1038 // If the mouse pressed the mouse's button in the separator,
1039 // we shouldn't change the hot (so the separator can be
1040 // tracked to the mouse's released).
1041 if (m_clk.part == PART_SEPARATOR) {
1042 m_separator_x = MAX(0, mousePos.x);
1043 layout();
1044 return true;
1045 }
1046 }
1047
1048 updateDropRange(mousePos);
1049
1050 if (hasCapture()) {
1051 switch (m_state) {
1052
1053 case STATE_SELECTING_LAYERS: {
1054 Layer* hitLayer = m_rows[hit.layer].layer();
1055 if (m_layer != hitLayer) {
1056 m_clk.layer = hit.layer;
1057
1058 // We have to change the range before we generate an
1059 // onActiveSiteChange() event so observers (like cel
1060 // properties dialog) know the new selected range.
1061 m_range = m_startRange;
1062 m_range.endRange(hitLayer, m_frame);
1063
1064 setLayer(hitLayer);
1065 }
1066 break;
1067 }
1068
1069 case STATE_SELECTING_FRAMES: {
1070 invalidateRange();
1071
1072 m_range = m_startRange;
1073 m_range.endRange(m_layer, hit.frame);
1074
1075 setFrame(m_clk.frame = hit.frame, true);
1076
1077 invalidateRange();
1078 break;
1079 }
1080
1081 case STATE_SELECTING_CELS:
1082 Layer* hitLayer = m_rows[hit.layer].layer();
1083 if ((m_layer != hitLayer) || (m_frame != hit.frame)) {
1084 m_clk.layer = hit.layer;
1085
1086 m_range = m_startRange;
1087 m_range.endRange(hitLayer, hit.frame);
1088
1089 setLayer(hitLayer);
1090 setFrame(m_clk.frame = hit.frame, true);
1091 }
1092 break;
1093 }
1094 }
1095
1096 updateStatusBar(msg);
1097 updateCelOverlayBounds(hit);
1098 return true;
1099 }
1100
1101 case kMouseUpMessage:
1102 if (hasCapture()) {
1103 ASSERT(m_document != NULL);
1104
1105 MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
1106
1107 if (m_state == STATE_SCROLLING) {
1108 m_state = STATE_STANDBY;
1109 releaseMouse();
1110 return true;
1111 }
1112
1113 bool regenRows = false;
1114 bool relayout = false;
1115 setHot(hitTest(msg, mouseMsg->position() - bounds().origin()));
1116
1117 switch (m_hot.part) {
1118
1119 case PART_HEADER_GEAR: {
1120 gfx::Rect gearBounds =
1121 getPartBounds(Hit(PART_HEADER_GEAR)).offset(bounds().origin());
1122
1123 if (!m_confPopup) {
1124 ConfigureTimelinePopup* popup =
1125 new ConfigureTimelinePopup();
1126
1127 popup->remapWindow();
1128 m_confPopup = popup;
1129 }
1130
1131 if (!m_confPopup->isVisible()) {
1132 gfx::Rect bounds = m_confPopup->bounds();
1133 ui::fit_bounds(BOTTOM, gearBounds, bounds);
1134
1135 m_confPopup->moveWindow(bounds);
1136 m_confPopup->openWindow();
1137 }
1138 else
1139 m_confPopup->closeWindow(NULL);
1140 break;
1141 }
1142
1143 case PART_HEADER_FRAME:
1144 // Show the frame pop-up menu.
1145 if (mouseMsg->right()) {
1146 if (m_clk.frame == m_hot.frame) {
1147 Menu* popupMenu = AppMenus::instance()->getFramePopupMenu();
1148 if (popupMenu) {
1149 popupMenu->showPopup(mouseMsg->position());
1150
1151 m_state = STATE_STANDBY;
1152 invalidate();
1153 }
1154 }
1155 }
1156 break;
1157
1158 case PART_ROW_TEXT:
1159 // Show the layer pop-up menu.
1160 if (mouseMsg->right()) {
1161 if (m_clk.layer == m_hot.layer) {
1162 Menu* popupMenu = AppMenus::instance()->getLayerPopupMenu();
1163 if (popupMenu) {
1164 popupMenu->showPopup(mouseMsg->position());
1165
1166 m_state = STATE_STANDBY;
1167 invalidate();
1168 }
1169 }
1170 }
1171 break;
1172
1173 case PART_CEL: {
1174 // Show the cel pop-up menu.
1175 if (mouseMsg->right()) {
1176 Menu* popupMenu =
1177 (m_state == STATE_MOVING_RANGE &&
1178 m_range.type() == Range::kCels &&
1179 (m_hot.layer != m_clk.layer ||
1180 m_hot.frame != m_clk.frame)) ?
1181 AppMenus::instance()->getCelMovementPopupMenu():
1182 AppMenus::instance()->getCelPopupMenu();
1183 if (popupMenu) {
1184 popupMenu->showPopup(mouseMsg->position());
1185
1186 // Do not drop in this function, the drop is done from
1187 // the menu in case we've used the
1188 // CelMovementPopupMenu
1189 m_state = STATE_STANDBY;
1190 invalidate();
1191 }
1192 }
1193 break;
1194 }
1195
1196 case PART_FRAME_TAG: {
1197 FrameTag* frameTag = m_clk.getFrameTag();
1198 if (frameTag) {
1199 Params params;
1200 params.set("id", base::convert_to<std::string>(frameTag->id()).c_str());
1201
1202 // As the m_clk.frameTag can be deleted with
1203 // RemoveFrameTag command, we've to clean all references
1204 // to it from Hit() structures.
1205 cleanClk();
1206 m_hot = m_clk;
1207
1208 if (mouseMsg->right()) {
1209 Menu* popupMenu = AppMenus::instance()->getFrameTagPopupMenu();
1210 if (popupMenu) {
1211 AppMenuItem::setContextParams(params);
1212 popupMenu->showPopup(mouseMsg->position());
1213 AppMenuItem::setContextParams(Params());
1214
1215 m_state = STATE_STANDBY;
1216 invalidate();
1217 }
1218 }
1219 else if (mouseMsg->left()) {
1220 Command* command = Commands::instance()
1221 ->byId(CommandId::FrameTagProperties());
1222 UIContext::instance()->executeCommand(command, params);
1223 }
1224 }
1225 break;
1226 }
1227
1228 case PART_FRAME_TAG_SWITCH_BAND_BUTTON:
1229 if (m_clk.band >= 0) {
1230 focusTagBand(m_clk.band);
1231 regenRows = true;
1232 relayout = true;
1233 }
1234 break;
1235
1236 }
1237
1238 if (regenRows) {
1239 regenerateRows();
1240 invalidate();
1241 }
1242 if (relayout)
1243 layout();
1244
1245 if (m_state == STATE_MOVING_RANGE &&
1246 m_dropRange.type() != Range::kNone) {
1247 dropRange(is_copy_key_pressed(mouseMsg) ?
1248 Timeline::kCopy:
1249 Timeline::kMove);
1250 }
1251
1252 // Clean the clicked-part & redraw the hot-part.
1253 cleanClk();
1254
1255 if (hasCapture())
1256 invalidate();
1257 else
1258 invalidateHit(m_hot);
1259
1260 // Restore the cursor.
1261 m_state = STATE_STANDBY;
1262 setCursor(msg, hitTest(msg, mouseMsg->position() - bounds().origin()));
1263
1264 releaseMouse();
1265 updateStatusBar(msg);
1266 return true;
1267 }
1268 break;
1269
1270 case kDoubleClickMessage:
1271 switch (m_hot.part) {
1272
1273 case PART_ROW_TEXT: {
1274 Command* command = Commands::instance()
1275 ->byId(CommandId::LayerProperties());
1276
1277 UIContext::instance()->executeCommand(command);
1278 return true;
1279 }
1280
1281 case PART_HEADER_FRAME: {
1282 Command* command = Commands::instance()
1283 ->byId(CommandId::FrameProperties());
1284 Params params;
1285 params.set("frame", "current");
1286
1287 UIContext::instance()->executeCommand(command, params);
1288 return true;
1289 }
1290
1291 case PART_CEL: {
1292 Command* command = Commands::instance()
1293 ->byId(CommandId::CelProperties());
1294
1295 UIContext::instance()->executeCommand(command);
1296 return true;
1297 }
1298
1299 case PART_FRAME_TAG_BAND:
1300 if (m_hot.band >= 0) {
1301 focusTagBand(m_hot.band);
1302 regenerateRows();
1303 invalidate();
1304 layout();
1305 return true;
1306 }
1307 break;
1308
1309 }
1310 break;
1311
1312 case kKeyDownMessage: {
1313 bool used = false;
1314
1315 switch (static_cast<KeyMessage*>(msg)->scancode()) {
1316
1317 case kKeyEsc:
1318 if (m_state == STATE_STANDBY) {
1319 clearAndInvalidateRange();
1320 }
1321 else {
1322 m_state = STATE_STANDBY;
1323 }
1324
1325 // Don't use this key, so it's caught by CancelCommand.
1326 // TODO The deselection of the current range should be
1327 // handled in CancelCommand itself.
1328 //used = true;
1329 break;
1330
1331 case kKeySpace: {
1332 // If we receive a key down event when the Space bar is
1333 // pressed (because the Timeline has the keyboard focus) but
1334 // we don't have the mouse inside, we don't consume this
1335 // event so the Space bar can be used by the Editor to
1336 // activate the hand/pan/scroll tool.
1337 if (!hasMouse())
1338 break;
1339
1340 m_scroll = true;
1341 used = true;
1342 break;
1343 }
1344 }
1345
1346 updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
1347 if (used)
1348 return true;
1349
1350 break;
1351 }
1352
1353 case kKeyUpMessage: {
1354 bool used = false;
1355
1356 switch (static_cast<KeyMessage*>(msg)->scancode()) {
1357
1358 case kKeySpace: {
1359 m_scroll = false;
1360
1361 // We have to clear all the kKeySpace keys in buffer.
1362 she::instance()->clearKeyboardBuffer();
1363 used = true;
1364 break;
1365 }
1366 }
1367
1368 updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
1369 if (used)
1370 return true;
1371
1372 break;
1373 }
1374
1375 case kMouseWheelMessage:
1376 if (m_document) {
1377 gfx::Point delta = static_cast<MouseMessage*>(msg)->wheelDelta();
1378 const bool precise = static_cast<MouseMessage*>(msg)->preciseWheel();
1379
1380 // Zoom timeline
1381 if (msg->ctrlPressed() || // TODO configurable
1382 msg->cmdPressed()) {
1383 double dz = delta.x + delta.y;
1384
1385 if (precise) {
1386 dz /= 1.5;
1387 if (dz < -1.0) dz = -1.0;
1388 else if (dz > 1.0) dz = 1.0;
1389 }
1390
1391 setZoomAndUpdate(m_zoom - dz, true);
1392 }
1393 else {
1394 if (!precise) {
1395 delta.x *= frameBoxWidth();
1396 delta.y *= layerBoxHeight();
1397
1398 if (delta.x == 0 && // On macOS shift already changes the wheel axis
1399 msg->shiftPressed()) {
1400 if (std::fabs(delta.y) > delta.x)
1401 std::swap(delta.x, delta.y);
1402 }
1403
1404 if (msg->altPressed()) {
1405 delta.x *= 3;
1406 delta.y *= 3;
1407 }
1408 }
1409 setViewScroll(viewScroll() + delta);
1410 }
1411 }
1412 break;
1413
1414 case kSetCursorMessage:
1415 if (m_document) {
1416 setCursor(msg, m_hot);
1417 return true;
1418 }
1419 break;
1420
1421 case kTouchMagnifyMessage:
1422 setZoomAndUpdate(
1423 m_zoom + m_zoom * static_cast<ui::TouchMessage*>(msg)->magnification(),
1424 true);
1425 break;
1426 }
1427
1428 return Widget::onProcessMessage(msg);
1429 }
1430
onInitTheme(ui::InitThemeEvent & ev)1431 void Timeline::onInitTheme(ui::InitThemeEvent& ev)
1432 {
1433 Widget::onInitTheme(ev);
1434
1435 SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
1436 int barsize = theme->dimensions.miniScrollbarSize();
1437 m_hbar.setBarWidth(barsize);
1438 m_vbar.setBarWidth(barsize);
1439 m_hbar.setStyle(theme->styles.transparentScrollbar());
1440 m_vbar.setStyle(theme->styles.transparentScrollbar());
1441 m_hbar.setThumbStyle(theme->styles.transparentScrollbarThumb());
1442 m_vbar.setThumbStyle(theme->styles.transparentScrollbarThumb());
1443 }
1444
onInvalidateRegion(const gfx::Region & region)1445 void Timeline::onInvalidateRegion(const gfx::Region& region)
1446 {
1447 Widget::onInvalidateRegion(region);
1448 m_redrawMarchingAntsOnly = false;
1449 }
1450
onSizeHint(SizeHintEvent & ev)1451 void Timeline::onSizeHint(SizeHintEvent& ev)
1452 {
1453 // This doesn't matter, the AniEditor'll use the entire screen anyway.
1454 ev.setSizeHint(Size(32, 32));
1455 }
1456
onResize(ui::ResizeEvent & ev)1457 void Timeline::onResize(ui::ResizeEvent& ev)
1458 {
1459 gfx::Rect rc = ev.bounds();
1460 setBoundsQuietly(rc);
1461
1462 gfx::Size sz = m_aniControls.sizeHint();
1463 m_aniControls.setBounds(
1464 gfx::Rect(
1465 rc.x,
1466 rc.y+(visibleTagBands()-1)*oneTagHeight(),
1467 MIN(sz.w, m_separator_x),
1468 oneTagHeight()));
1469
1470 updateScrollBars();
1471 }
1472
onPaint(ui::PaintEvent & ev)1473 void Timeline::onPaint(ui::PaintEvent& ev)
1474 {
1475 Graphics* g = ev.graphics();
1476 bool noDoc = (m_document == NULL);
1477 if (noDoc)
1478 goto paintNoDoc;
1479
1480 try {
1481 // Lock the sprite to read/render it. We wait 1/4 secs in case
1482 // the background thread is making a backup.
1483 const DocReader docReader(m_document, 250);
1484
1485 if (m_redrawMarchingAntsOnly) {
1486 drawClipboardRange(g);
1487 m_redrawMarchingAntsOnly = false;
1488 return;
1489 }
1490
1491 layer_t layer, firstLayer, lastLayer;
1492 frame_t frame, firstFrame, lastFrame;
1493
1494 getDrawableLayers(g, &firstLayer, &lastLayer);
1495 getDrawableFrames(g, &firstFrame, &lastFrame);
1496
1497 drawTop(g);
1498
1499 // Draw the header for layers.
1500 drawHeader(g);
1501
1502 // Draw the header for each visible frame.
1503 {
1504 IntersectClip clip(g, getFrameHeadersBounds());
1505 if (clip) {
1506 for (frame=firstFrame; frame<=lastFrame; ++frame)
1507 drawHeaderFrame(g, frame);
1508
1509 // Draw onionskin indicators.
1510 gfx::Rect bounds = getOnionskinFramesBounds();
1511 if (!bounds.isEmpty()) {
1512 drawPart(
1513 g, bounds, nullptr,
1514 skinTheme()->styles.timelineOnionskinRange(),
1515 false, false, false);
1516 }
1517 }
1518 }
1519
1520 // Draw each visible layer.
1521 DrawCelData data;
1522 for (layer=lastLayer; layer>=firstLayer; --layer) {
1523 {
1524 IntersectClip clip(g, getLayerHeadersBounds());
1525 if (clip)
1526 drawLayer(g, layer);
1527 }
1528
1529 IntersectClip clip(g, getCelsBounds());
1530 if (!clip)
1531 continue;
1532
1533 Layer* layerPtr = m_rows[layer].layer();
1534 if (!layerPtr->isImage()) {
1535 // Draw empty cels
1536 for (frame=firstFrame; frame<=lastFrame; ++frame) {
1537 drawCel(g, layer, frame, nullptr, nullptr);
1538 }
1539 continue;
1540 }
1541
1542 // Get the first CelIterator to be drawn (it is the first cel with cel->frame >= first_frame)
1543 LayerImage* layerImagePtr = static_cast<LayerImage*>(layerPtr);
1544 data.begin = layerImagePtr->getCelBegin();
1545 data.end = layerImagePtr->getCelEnd();
1546 data.it = layerImagePtr->findFirstCelIteratorAfter(firstFrame-1);
1547 data.prevIt = data.end;
1548 data.nextIt = (data.it != data.end ? data.it+1: data.end);
1549
1550 // Calculate link range for the active cel
1551 data.firstLink = data.end;
1552 data.lastLink = data.end;
1553
1554 if (layerPtr == m_layer) {
1555 data.activeIt = layerImagePtr->findCelIterator(m_frame);
1556 if (data.activeIt != data.end) {
1557 data.firstLink = data.activeIt;
1558 data.lastLink = data.activeIt;
1559
1560 ObjectId imageId = (*data.activeIt)->image()->id();
1561
1562 auto it2 = data.activeIt;
1563 if (it2 != data.begin) {
1564 do {
1565 --it2;
1566 if ((*it2)->image()->id() == imageId) {
1567 data.firstLink = it2;
1568 if ((*data.firstLink)->frame() < firstFrame)
1569 break;
1570 }
1571 } while (it2 != data.begin);
1572 }
1573
1574 it2 = data.activeIt;
1575 while (it2 != data.end) {
1576 if ((*it2)->image()->id() == imageId) {
1577 data.lastLink = it2;
1578 if ((*data.lastLink)->frame() > lastFrame)
1579 break;
1580 }
1581 ++it2;
1582 }
1583 }
1584 }
1585 else
1586 data.activeIt = data.end;
1587
1588 // Draw every visible cel for each layer.
1589 for (frame=firstFrame; frame<=lastFrame; ++frame) {
1590 Cel* cel =
1591 (data.it != data.end &&
1592 (*data.it)->frame() == frame ? *data.it: nullptr);
1593
1594 drawCel(g, layer, frame, cel, &data);
1595
1596 if (cel) {
1597 data.prevIt = data.it;
1598 data.it = data.nextIt; // Point to next cel
1599 if (data.nextIt != data.end)
1600 ++data.nextIt;
1601 }
1602 }
1603 }
1604
1605 drawPaddings(g);
1606 drawFrameTags(g);
1607 drawRangeOutline(g);
1608 drawClipboardRange(g);
1609 drawCelOverlay(g);
1610
1611 #if 0 // Use this code to debug the calculated m_dropRange by updateDropRange()
1612 {
1613 g->drawRect(gfx::rgba(255, 255, 0), getRangeBounds(m_range));
1614 g->drawRect(gfx::rgba(255, 0, 0), getRangeBounds(m_dropRange));
1615 }
1616 #endif
1617 }
1618 catch (const LockedDocException&) {
1619 noDoc = true;
1620 defer_invalid_rect(g->getClipBounds().offset(bounds().origin()));
1621 }
1622
1623 paintNoDoc:;
1624 if (noDoc)
1625 drawPart(
1626 g, clientBounds(), nullptr,
1627 skinTheme()->styles.timelinePadding());
1628 }
1629
onAfterCommandExecution(CommandExecutionEvent & ev)1630 void Timeline::onAfterCommandExecution(CommandExecutionEvent& ev)
1631 {
1632 if (!m_document)
1633 return;
1634
1635 // TODO improve this: no need to regenerate everything after each command
1636 regenerateRows();
1637 showCurrentCel();
1638 invalidate();
1639 }
1640
onActiveSiteChange(const Site & site)1641 void Timeline::onActiveSiteChange(const Site& site)
1642 {
1643 if (hasMouse()) {
1644 updateStatusBarForFrame(site.frame(), nullptr, site.cel());
1645 }
1646 }
1647
onRemoveDocument(Doc * document)1648 void Timeline::onRemoveDocument(Doc* document)
1649 {
1650 if (document == m_document) {
1651 detachDocument();
1652 }
1653 }
1654
onGeneralUpdate(DocEvent & ev)1655 void Timeline::onGeneralUpdate(DocEvent& ev)
1656 {
1657 invalidate();
1658 }
1659
onAddLayer(DocEvent & ev)1660 void Timeline::onAddLayer(DocEvent& ev)
1661 {
1662 ASSERT(ev.layer() != NULL);
1663
1664 setLayer(ev.layer());
1665
1666 regenerateRows();
1667 showCurrentCel();
1668 clearClipboardRange();
1669 invalidate();
1670 }
1671
onAfterRemoveLayer(DocEvent & ev)1672 void Timeline::onAfterRemoveLayer(DocEvent& ev)
1673 {
1674 Sprite* sprite = ev.sprite();
1675 Layer* layer = ev.layer();
1676
1677 // If the layer that was removed is the selected one
1678 if (layer == getLayer()) {
1679 LayerGroup* parent = layer->parent();
1680 Layer* layer_select = NULL;
1681
1682 // Select previous layer, or next layer, or the parent (if it is
1683 // not the main layer of sprite set).
1684 if (layer->getPrevious())
1685 layer_select = layer->getPrevious();
1686 else if (layer->getNext())
1687 layer_select = layer->getNext();
1688 else if (parent != sprite->root())
1689 layer_select = parent;
1690
1691 setLayer(layer_select);
1692 }
1693
1694 regenerateRows();
1695 showCurrentCel();
1696 clearClipboardRange();
1697 invalidate();
1698 }
1699
onAddFrame(DocEvent & ev)1700 void Timeline::onAddFrame(DocEvent& ev)
1701 {
1702 setFrame(ev.frame(), false);
1703
1704 showCurrentCel();
1705 clearClipboardRange();
1706 invalidate();
1707 }
1708
onRemoveFrame(DocEvent & ev)1709 void Timeline::onRemoveFrame(DocEvent& ev)
1710 {
1711 // Adjust current frame of all editors that are in a frame more
1712 // advanced that the removed one.
1713 if (getFrame() > ev.frame()) {
1714 setFrame(getFrame()-1, false);
1715 }
1716 // If the editor was in the previous "last frame" (current value of
1717 // totalFrames()), we've to adjust it to the new last frame
1718 // (lastFrame())
1719 else if (getFrame() >= sprite()->totalFrames()) {
1720 setFrame(sprite()->lastFrame(), false);
1721 }
1722
1723 // Disable the selected range when we remove frames
1724 if (m_range.enabled())
1725 clearAndInvalidateRange();
1726
1727 showCurrentCel();
1728 clearClipboardRange();
1729 invalidate();
1730 }
1731
onSelectionChanged(DocEvent & ev)1732 void Timeline::onSelectionChanged(DocEvent& ev)
1733 {
1734 if (m_rangeLocks == 0)
1735 clearAndInvalidateRange();
1736 }
1737
onLayerNameChange(DocEvent & ev)1738 void Timeline::onLayerNameChange(DocEvent& ev)
1739 {
1740 invalidate();
1741 }
1742
onAddFrameTag(DocEvent & ev)1743 void Timeline::onAddFrameTag(DocEvent& ev)
1744 {
1745 if (m_tagFocusBand >= 0) {
1746 m_tagFocusBand = -1;
1747 regenerateRows();
1748 layout();
1749 }
1750 }
1751
onRemoveFrameTag(DocEvent & ev)1752 void Timeline::onRemoveFrameTag(DocEvent& ev)
1753 {
1754 onAddFrameTag(ev);
1755 }
1756
onStateChanged(Editor * editor)1757 void Timeline::onStateChanged(Editor* editor)
1758 {
1759 m_aniControls.updateUsingEditor(editor);
1760 }
1761
onAfterFrameChanged(Editor * editor)1762 void Timeline::onAfterFrameChanged(Editor* editor)
1763 {
1764 if (m_fromTimeline)
1765 return;
1766
1767 setFrame(editor->frame(), false);
1768
1769 if (!hasCapture())
1770 clearAndInvalidateRange();
1771
1772 showCurrentCel();
1773 }
1774
onAfterLayerChanged(Editor * editor)1775 void Timeline::onAfterLayerChanged(Editor* editor)
1776 {
1777 if (m_fromTimeline)
1778 return;
1779
1780 if (!hasCapture())
1781 m_range.clearRange();
1782
1783 setLayer(editor->layer());
1784 showCurrentCel();
1785 }
1786
onDestroyEditor(Editor * editor)1787 void Timeline::onDestroyEditor(Editor* editor)
1788 {
1789 ASSERT(m_editor == editor);
1790 if (m_editor == editor) {
1791 m_editor->remove_observer(this);
1792 m_editor = nullptr;
1793 }
1794 }
1795
setCursor(ui::Message * msg,const Hit & hit)1796 void Timeline::setCursor(ui::Message* msg, const Hit& hit)
1797 {
1798 // Scrolling.
1799 if (m_state == STATE_SCROLLING || m_scroll) {
1800 ui::set_mouse_cursor(kScrollCursor);
1801 }
1802 // Moving.
1803 else if (m_state == STATE_MOVING_RANGE) {
1804 if (is_copy_key_pressed(msg))
1805 ui::set_mouse_cursor(kArrowPlusCursor);
1806 else
1807 ui::set_mouse_cursor(kMoveCursor);
1808 }
1809 // Normal state.
1810 else if (hit.part == PART_HEADER_ONIONSKIN_RANGE_LEFT
1811 || m_state == STATE_MOVING_ONIONSKIN_RANGE_LEFT) {
1812 ui::set_mouse_cursor(kSizeWCursor);
1813 }
1814 else if (hit.part == PART_HEADER_ONIONSKIN_RANGE_RIGHT
1815 || m_state == STATE_MOVING_ONIONSKIN_RANGE_RIGHT) {
1816 ui::set_mouse_cursor(kSizeECursor);
1817 }
1818 else if (hit.part == PART_RANGE_OUTLINE) {
1819 ui::set_mouse_cursor(kMoveCursor);
1820 }
1821 else if (hit.part == PART_SEPARATOR) {
1822 ui::set_mouse_cursor(kSizeWECursor);
1823 }
1824 else if (hit.part == PART_FRAME_TAG) {
1825 ui::set_mouse_cursor(kHandCursor);
1826 }
1827 else {
1828 ui::set_mouse_cursor(kArrowCursor);
1829 }
1830 }
1831
getDrawableLayers(ui::Graphics * g,layer_t * firstLayer,layer_t * lastLayer)1832 void Timeline::getDrawableLayers(ui::Graphics* g, layer_t* firstLayer, layer_t* lastLayer)
1833 {
1834 int hpx = (clientBounds().h - headerBoxHeight() - topHeight());
1835 layer_t i = this->lastLayer() - ((viewScroll().y+hpx) / layerBoxHeight());
1836 i = MID(this->firstLayer(), i, this->lastLayer());
1837
1838 layer_t j = i + (hpx / layerBoxHeight() + 1);
1839 if (!m_rows.empty())
1840 j = MID(this->firstLayer(), j, this->lastLayer());
1841 else
1842 j = -1;
1843
1844 *firstLayer = i;
1845 *lastLayer = j;
1846 }
1847
getDrawableFrames(ui::Graphics * g,frame_t * firstFrame,frame_t * lastFrame)1848 void Timeline::getDrawableFrames(ui::Graphics* g, frame_t* firstFrame, frame_t* lastFrame)
1849 {
1850 int availW = (clientBounds().w - m_separator_x);
1851
1852 *firstFrame = frame_t(viewScroll().x / frameBoxWidth());
1853 *lastFrame = *firstFrame
1854 + frame_t(availW / frameBoxWidth())
1855 + ((availW % frameBoxWidth()) > 0 ? 1: 0);
1856 }
1857
drawPart(ui::Graphics * g,const gfx::Rect & bounds,const std::string * text,ui::Style * style,const bool is_active,const bool is_hover,const bool is_clicked,const bool is_disabled)1858 void Timeline::drawPart(ui::Graphics* g, const gfx::Rect& bounds,
1859 const std::string* text, ui::Style* style,
1860 const bool is_active,
1861 const bool is_hover,
1862 const bool is_clicked,
1863 const bool is_disabled)
1864 {
1865 IntersectClip clip(g, bounds);
1866 if (!clip)
1867 return;
1868
1869 PaintWidgetPartInfo info;
1870 info.text = text;
1871 info.styleFlags =
1872 (is_active ? ui::Style::Layer::kFocus: 0) |
1873 (is_hover ? ui::Style::Layer::kMouse: 0) |
1874 (is_clicked ? ui::Style::Layer::kSelected: 0) |
1875 (is_disabled ? ui::Style::Layer::kDisabled: 0);
1876
1877 theme()->paintWidgetPart(g, style, bounds, info);
1878 }
1879
drawClipboardRange(ui::Graphics * g)1880 void Timeline::drawClipboardRange(ui::Graphics* g)
1881 {
1882 Doc* clipboard_document;
1883 DocRange clipboard_range;
1884 clipboard::get_document_range_info(
1885 &clipboard_document,
1886 &clipboard_range);
1887
1888 if (!m_document || clipboard_document != m_document)
1889 return;
1890
1891 if (!m_clipboard_timer.isRunning())
1892 m_clipboard_timer.start();
1893
1894 IntersectClip clip(g, getRangeClipBounds(clipboard_range));
1895 if (clip) {
1896 CheckedDrawMode checked(g, m_offset_count,
1897 gfx::rgba(0, 0, 0, 255),
1898 gfx::rgba(255, 255, 255, 255));
1899 g->drawRect(gfx::rgba(0, 0, 0),
1900 getRangeBounds(clipboard_range));
1901 }
1902 }
1903
drawTop(ui::Graphics * g)1904 void Timeline::drawTop(ui::Graphics* g)
1905 {
1906 g->fillRect(skinTheme()->colors.workspace(),
1907 getPartBounds(Hit(PART_TOP)));
1908 }
1909
drawHeader(ui::Graphics * g)1910 void Timeline::drawHeader(ui::Graphics* g)
1911 {
1912 auto& styles = skinTheme()->styles;
1913 bool allInvisible = allLayersInvisible();
1914 bool allLocked = allLayersLocked();
1915 bool allContinuous = allLayersContinuous();
1916
1917 drawPart(g, getPartBounds(Hit(PART_HEADER_EYE)),
1918 nullptr,
1919 allInvisible ? styles.timelineClosedEye(): styles.timelineOpenEye(),
1920 m_clk.part == PART_HEADER_EYE,
1921 m_hot.part == PART_HEADER_EYE,
1922 m_clk.part == PART_HEADER_EYE);
1923
1924 drawPart(g, getPartBounds(Hit(PART_HEADER_PADLOCK)),
1925 nullptr,
1926 allLocked ? styles.timelineClosedPadlock(): styles.timelineOpenPadlock(),
1927 m_clk.part == PART_HEADER_PADLOCK,
1928 m_hot.part == PART_HEADER_PADLOCK,
1929 m_clk.part == PART_HEADER_PADLOCK);
1930
1931 drawPart(g, getPartBounds(Hit(PART_HEADER_CONTINUOUS)),
1932 nullptr,
1933 allContinuous ? styles.timelineContinuous(): styles.timelineDiscontinuous(),
1934 m_clk.part == PART_HEADER_CONTINUOUS,
1935 m_hot.part == PART_HEADER_CONTINUOUS,
1936 m_clk.part == PART_HEADER_CONTINUOUS);
1937
1938 drawPart(g, getPartBounds(Hit(PART_HEADER_GEAR)),
1939 nullptr,
1940 styles.timelineGear(),
1941 m_clk.part == PART_HEADER_GEAR,
1942 m_hot.part == PART_HEADER_GEAR,
1943 m_clk.part == PART_HEADER_GEAR);
1944
1945 drawPart(g, getPartBounds(Hit(PART_HEADER_ONIONSKIN)),
1946 NULL, styles.timelineOnionskin(),
1947 docPref().onionskin.active() || (m_clk.part == PART_HEADER_ONIONSKIN),
1948 m_hot.part == PART_HEADER_ONIONSKIN,
1949 m_clk.part == PART_HEADER_ONIONSKIN);
1950
1951 // Empty header space.
1952 drawPart(g, getPartBounds(Hit(PART_HEADER_LAYER)),
1953 NULL, styles.timelineBox(), false, false, false);
1954 }
1955
drawHeaderFrame(ui::Graphics * g,frame_t frame)1956 void Timeline::drawHeaderFrame(ui::Graphics* g, frame_t frame)
1957 {
1958 bool is_active = isFrameActive(frame);
1959 bool is_hover = (m_hot.part == PART_HEADER_FRAME && m_hot.frame == frame);
1960 bool is_clicked = (m_clk.part == PART_HEADER_FRAME && m_clk.frame == frame);
1961 gfx::Rect bounds = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frame));
1962 IntersectClip clip(g, bounds);
1963 if (!clip)
1964 return;
1965
1966 // Draw the header for the layers.
1967 const int n = (docPref().timeline.firstFrame()+frame);
1968 std::string text = base::convert_to<std::string, int>(n % 100);
1969 if (n >= 100 && (n % 100) < 10)
1970 text.insert(0, 1, '0');
1971
1972 drawPart(g, bounds, &text,
1973 skinTheme()->styles.timelineHeaderFrame(),
1974 is_active, is_hover, is_clicked);
1975 }
1976
drawLayer(ui::Graphics * g,int layerIdx)1977 void Timeline::drawLayer(ui::Graphics* g, int layerIdx)
1978 {
1979 auto& styles = skinTheme()->styles;
1980 Layer* layer = m_rows[layerIdx].layer();
1981 bool is_active = isLayerActive(layerIdx);
1982 bool hotlayer = (m_hot.layer == layerIdx);
1983 bool clklayer = (m_clk.layer == layerIdx);
1984 gfx::Rect bounds = getPartBounds(Hit(PART_ROW, layerIdx, firstFrame()));
1985 IntersectClip clip(g, bounds);
1986 if (!clip)
1987 return;
1988
1989 // Draw the eye (visible flag).
1990 bounds = getPartBounds(Hit(PART_ROW_EYE_ICON, layerIdx));
1991 drawPart(
1992 g, bounds, nullptr,
1993 (layer->isVisible() ? styles.timelineOpenEye():
1994 styles.timelineClosedEye()),
1995 is_active || (clklayer && m_clk.part == PART_ROW_EYE_ICON),
1996 (hotlayer && m_hot.part == PART_ROW_EYE_ICON),
1997 (clklayer && m_clk.part == PART_ROW_EYE_ICON),
1998 !m_rows[layerIdx].parentVisible());
1999
2000 // Draw the padlock (editable flag).
2001 bounds = getPartBounds(Hit(PART_ROW_PADLOCK_ICON, layerIdx));
2002 drawPart(
2003 g, bounds, nullptr,
2004 (layer->isEditable() ? styles.timelineOpenPadlock():
2005 styles.timelineClosedPadlock()),
2006 is_active || (clklayer && m_clk.part == PART_ROW_PADLOCK_ICON),
2007 (hotlayer && m_hot.part == PART_ROW_PADLOCK_ICON),
2008 (clklayer && m_clk.part == PART_ROW_PADLOCK_ICON),
2009 !m_rows[layerIdx].parentEditable());
2010
2011 // Draw the continuous flag/group icon.
2012 bounds = getPartBounds(Hit(PART_ROW_CONTINUOUS_ICON, layerIdx));
2013 if (layer->isImage()) {
2014 drawPart(g, bounds, nullptr,
2015 layer->isContinuous() ? styles.timelineContinuous():
2016 styles.timelineDiscontinuous(),
2017 is_active || (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON),
2018 (hotlayer && m_hot.part == PART_ROW_CONTINUOUS_ICON),
2019 (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON));
2020 }
2021 else if (layer->isGroup()) {
2022 drawPart(g, bounds, nullptr,
2023 layer->isCollapsed() ? styles.timelineClosedGroup():
2024 styles.timelineOpenGroup(),
2025 is_active || (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON),
2026 (hotlayer && m_hot.part == PART_ROW_CONTINUOUS_ICON),
2027 (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON));
2028 }
2029
2030 // Get the layer's name bounds.
2031 bounds = getPartBounds(Hit(PART_ROW_TEXT, layerIdx));
2032
2033 // Draw layer name.
2034 doc::color_t layerColor = layer->userData().color();
2035 gfx::Rect textBounds = bounds;
2036 if (m_rows[layerIdx].level() > 0) {
2037 int w = m_rows[layerIdx].level()*frameBoxWidth();
2038 textBounds.x += w;
2039 textBounds.w -= w;
2040 }
2041
2042 drawPart(g, bounds, nullptr, styles.timelineLayer(),
2043 is_active || (clklayer && m_clk.part == PART_ROW_TEXT),
2044 (hotlayer && m_hot.part == PART_ROW_TEXT),
2045 (clklayer && m_clk.part == PART_ROW_TEXT));
2046
2047 if (doc::rgba_geta(layerColor) > 0) {
2048 // Fill with an user-defined custom color.
2049 auto b2 = bounds;
2050 b2.shrink(1*guiscale()).inflate(1*guiscale());
2051 g->fillRect(gfx::rgba(doc::rgba_getr(layerColor),
2052 doc::rgba_getg(layerColor),
2053 doc::rgba_getb(layerColor),
2054 doc::rgba_geta(layerColor)),
2055 b2);
2056
2057 drawPart(g, textBounds,
2058 &layer->name(),
2059 styles.timelineLayerTextOnly(),
2060 is_active,
2061 (hotlayer && m_hot.part == PART_ROW_TEXT),
2062 (clklayer && m_clk.part == PART_ROW_TEXT));
2063 }
2064 else {
2065 drawPart(g, textBounds,
2066 &layer->name(),
2067 styles.timelineLayer(),
2068 is_active,
2069 (hotlayer && m_hot.part == PART_ROW_TEXT),
2070 (clklayer && m_clk.part == PART_ROW_TEXT));
2071 }
2072
2073 if (layer->isBackground()) {
2074 int s = ui::guiscale();
2075 g->fillRect(
2076 is_active ?
2077 skinTheme()->colors.timelineClickedText():
2078 skinTheme()->colors.timelineNormalText(),
2079 gfx::Rect(bounds.x+4*s,
2080 bounds.y+bounds.h-2*s,
2081 font()->textLength(layer->name().c_str()), s));
2082 }
2083 else if (layer->isReference()) {
2084 int s = ui::guiscale();
2085 g->fillRect(
2086 is_active ?
2087 skinTheme()->colors.timelineClickedText():
2088 skinTheme()->colors.timelineNormalText(),
2089 gfx::Rect(bounds.x+4*s,
2090 bounds.y+bounds.h/2,
2091 font()->textLength(layer->name().c_str()), s));
2092 }
2093
2094 // If this layer wasn't clicked but there are another layer clicked,
2095 // we have to draw some indicators to show that the user can move
2096 // layers.
2097 if (hotlayer && !is_active && m_clk.part == PART_ROW_TEXT) {
2098 // TODO this should be skinneable
2099 g->fillRect(
2100 skinTheme()->colors.timelineActive(),
2101 gfx::Rect(bounds.x, bounds.y, bounds.w, 2));
2102 }
2103 }
2104
drawCel(ui::Graphics * g,layer_t layerIndex,frame_t frame,Cel * cel,DrawCelData * data)2105 void Timeline::drawCel(ui::Graphics* g, layer_t layerIndex, frame_t frame, Cel* cel, DrawCelData* data)
2106 {
2107 auto& styles = skinTheme()->styles;
2108 Layer* layer = m_rows[layerIndex].layer();
2109 Image* image = (cel ? cel->image(): nullptr);
2110 bool is_hover = (m_hot.part == PART_CEL &&
2111 m_hot.layer == layerIndex &&
2112 m_hot.frame == frame);
2113 const bool is_active = isCelActive(layerIndex, frame);
2114 const bool is_loosely_active = isCelLooselyActive(layerIndex, frame);
2115 const bool is_empty = (image == nullptr);
2116 gfx::Rect bounds = getPartBounds(Hit(PART_CEL, layerIndex, frame));
2117 gfx::Rect full_bounds = bounds;
2118 IntersectClip clip(g, bounds);
2119 if (!clip)
2120 return;
2121
2122 // Draw background
2123 if (layer == m_layer && frame == m_frame)
2124 drawPart(g, bounds, nullptr,
2125 m_range.enabled() ? styles.timelineFocusedCel():
2126 styles.timelineSelectedCel(), false, is_hover, true);
2127 else if (m_range.enabled() && is_active)
2128 drawPart(g, bounds, nullptr, styles.timelineSelectedCel(), false, is_hover, true);
2129 else
2130 drawPart(g, bounds, nullptr, styles.timelineBox(), is_loosely_active, is_hover);
2131
2132 // Fill with an user-defined custom color.
2133 if (cel && cel->data()) {
2134 doc::color_t celColor = cel->data()->userData().color();
2135 if (doc::rgba_geta(celColor) > 0) {
2136 auto b2 = bounds;
2137 b2.shrink(1 * guiscale()).inflate(1 * guiscale());
2138 g->fillRect(gfx::rgba(doc::rgba_getr(celColor),
2139 doc::rgba_getg(celColor),
2140 doc::rgba_getb(celColor),
2141 doc::rgba_geta(celColor)),
2142 b2);
2143 }
2144 }
2145
2146 // Draw keyframe shape
2147
2148 ui::Style* style = nullptr;
2149 bool fromLeft = false;
2150 bool fromRight = false;
2151 if (is_empty || !data) {
2152 style = styles.timelineEmptyFrame();
2153 }
2154 else {
2155 // Calculate which cel is next to this one (in previous and next
2156 // frame).
2157 Cel* left = (data->prevIt != data->end ? *data->prevIt: nullptr);
2158 Cel* right = (data->nextIt != data->end ? *data->nextIt: nullptr);
2159 if (left && left->frame() != frame-1) left = nullptr;
2160 if (right && right->frame() != frame+1) right = nullptr;
2161
2162 ObjectId leftImg = (left ? left->image()->id(): 0);
2163 ObjectId rightImg = (right ? right->image()->id(): 0);
2164 fromLeft = (leftImg == cel->image()->id());
2165 fromRight = (rightImg == cel->image()->id());
2166
2167 if (fromLeft && fromRight)
2168 style = styles.timelineFromBoth();
2169 else if (fromLeft)
2170 style = styles.timelineFromLeft();
2171 else if (fromRight)
2172 style = styles.timelineFromRight();
2173 else
2174 style = styles.timelineKeyframe();
2175 }
2176
2177 drawPart(g, bounds, nullptr, style, is_loosely_active, is_hover);
2178
2179 // Draw thumbnail
2180 if ((docPref().thumbnails.enabled() && m_zoom > 1) && image) {
2181 gfx::Rect thumb_bounds =
2182 gfx::Rect(bounds).shrink(
2183 skinTheme()->calcBorder(this, style));
2184
2185 if (!thumb_bounds.isEmpty()) {
2186 she::Surface* thumb_surf = thumb::get_cel_thumbnail(cel, thumb_bounds.size());
2187 if (thumb_surf) {
2188 g->drawRgbaSurface(thumb_surf, thumb_bounds.x, thumb_bounds.y);
2189 thumb_surf->dispose();
2190 }
2191 }
2192 }
2193
2194 // Draw decorators to link the activeCel with its links.
2195 if (data && data->activeIt != data->end)
2196 drawCelLinkDecorators(g, full_bounds, cel, frame, is_loosely_active, is_hover, data);
2197 }
2198
updateCelOverlayBounds(const Hit & hit)2199 void Timeline::updateCelOverlayBounds(const Hit& hit)
2200 {
2201 gfx::Rect inner, outer;
2202
2203 if (docPref().thumbnails.overlayEnabled() && hit.part == PART_CEL) {
2204 m_thumbnailsOverlayHit = hit;
2205
2206 int max_size = headerBoxWidth() * docPref().thumbnails.overlaySize();
2207 int width, height;
2208 if (m_sprite->width() > m_sprite->height()) {
2209 width = max_size;
2210 height = max_size * m_sprite->height() / m_sprite->width();
2211 }
2212 else {
2213 width = max_size * m_sprite->width() / m_sprite->height();
2214 height = max_size;
2215 }
2216
2217 gfx::Rect client_bounds = clientBounds();
2218 gfx::Point center = client_bounds.center();
2219
2220 gfx::Rect bounds_cel = getPartBounds(m_thumbnailsOverlayHit);
2221 inner = gfx::Rect(
2222 bounds_cel.x + m_thumbnailsOverlayDirection.x,
2223 bounds_cel.y + m_thumbnailsOverlayDirection.y,
2224 width,
2225 height
2226 );
2227
2228 if (!client_bounds.contains(inner)) {
2229 m_thumbnailsOverlayDirection = gfx::Point(
2230 bounds_cel.x < center.x ? (int)(frameBoxWidth()*1.0) : -width,
2231 bounds_cel.y < center.y ? (int)(frameBoxWidth()*0.5) : -height+(int)(frameBoxWidth()*0.5)
2232 );
2233 inner.setOrigin(gfx::Point(
2234 bounds_cel.x + m_thumbnailsOverlayDirection.x,
2235 bounds_cel.y + m_thumbnailsOverlayDirection.y
2236 ));
2237 }
2238
2239 outer = gfx::Rect(inner).enlarge(1);
2240 }
2241 else {
2242 outer = gfx::Rect(0, 0, 0, 0);
2243 }
2244
2245 if (outer != m_thumbnailsOverlayOuter) {
2246 if (!m_thumbnailsOverlayOuter.isEmpty()) {
2247 invalidateRect(gfx::Rect(m_thumbnailsOverlayOuter).offset(origin()));
2248 }
2249 if (!outer.isEmpty()) {
2250 invalidateRect(gfx::Rect(outer).offset(origin()));
2251 }
2252 m_thumbnailsOverlayVisible = !outer.isEmpty();
2253 m_thumbnailsOverlayOuter = outer;
2254 m_thumbnailsOverlayInner = inner;
2255 }
2256 }
2257
drawCelOverlay(ui::Graphics * g)2258 void Timeline::drawCelOverlay(ui::Graphics* g)
2259 {
2260 if (!m_thumbnailsOverlayVisible) {
2261 return;
2262 }
2263
2264 Layer* layer = m_rows[m_thumbnailsOverlayHit.layer].layer();
2265 Cel* cel = layer->cel(m_thumbnailsOverlayHit.frame);
2266 if (!cel) {
2267 return;
2268 }
2269 Image* image = cel->image();
2270 if (!image) {
2271 return;
2272 }
2273
2274 IntersectClip clip(g, m_thumbnailsOverlayOuter);
2275 if (!clip)
2276 return;
2277
2278 double scale = (
2279 m_sprite->width() > m_sprite->height() ?
2280 m_thumbnailsOverlayInner.w / (double)m_sprite->width() :
2281 m_thumbnailsOverlayInner.h / (double)m_sprite->height()
2282 );
2283
2284 gfx::Size overlay_size(
2285 m_thumbnailsOverlayInner.w,
2286 m_thumbnailsOverlayInner.h
2287 );
2288
2289 gfx::Rect cel_image_on_overlay(
2290 (int)(cel->x() * scale),
2291 (int)(cel->y() * scale),
2292 (int)(image->width() * scale),
2293 (int)(image->height() * scale)
2294 );
2295
2296 she::Surface* overlay_surf = thumb::get_cel_thumbnail(cel, overlay_size, cel_image_on_overlay);
2297
2298 g->drawRgbaSurface(overlay_surf,
2299 m_thumbnailsOverlayInner.x, m_thumbnailsOverlayInner.y);
2300 g->drawRect(gfx::rgba(0,0,0,255), m_thumbnailsOverlayOuter);
2301
2302 overlay_surf->dispose();
2303 }
2304
drawCelLinkDecorators(ui::Graphics * g,const gfx::Rect & bounds,Cel * cel,frame_t frame,bool is_active,bool is_hover,DrawCelData * data)2305 void Timeline::drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,
2306 Cel* cel, frame_t frame, bool is_active, bool is_hover,
2307 DrawCelData* data)
2308 {
2309 auto& styles = skinTheme()->styles;
2310 ObjectId imageId = (*data->activeIt)->image()->id();
2311
2312 ui::Style* style1 = nullptr;
2313 ui::Style* style2 = nullptr;
2314
2315 // Links at the left or right side
2316 bool left = (data->firstLink != data->end ? frame > (*data->firstLink)->frame(): false);
2317 bool right = (data->lastLink != data->end ? frame < (*data->lastLink)->frame(): false);
2318
2319 if (cel && cel->image()->id() == imageId) {
2320 if (left) {
2321 Cel* prevCel = m_layer->cel(cel->frame()-1);
2322 if (!prevCel || prevCel->image()->id() != imageId)
2323 style1 = styles.timelineLeftLink();
2324 }
2325 if (right) {
2326 Cel* nextCel = m_layer->cel(cel->frame()+1);
2327 if (!nextCel || nextCel->image()->id() != imageId)
2328 style2 = styles.timelineRightLink();
2329 }
2330 }
2331 else {
2332 if (left && right)
2333 style1 = styles.timelineBothLinks();
2334 }
2335
2336 if (style1) drawPart(g, bounds, nullptr, style1, is_active, is_hover);
2337 if (style2) drawPart(g, bounds, nullptr, style2, is_active, is_hover);
2338 }
2339
drawFrameTags(ui::Graphics * g)2340 void Timeline::drawFrameTags(ui::Graphics* g)
2341 {
2342 IntersectClip clip(g, getPartBounds(Hit(PART_FRAME_TAGS)));
2343 if (!clip)
2344 return;
2345
2346 SkinTheme* theme = skinTheme();
2347 auto& styles = theme->styles;
2348
2349 g->fillRect(theme->colors.workspace(),
2350 gfx::Rect(
2351 0, font()->height(),
2352 clientBounds().w,
2353 theme->dimensions.timelineTagsAreaHeight()));
2354
2355 // Draw active frame tag band
2356 if (m_hot.band >= 0 &&
2357 m_tagBands > 1 &&
2358 m_tagFocusBand < 0) {
2359 gfx::Rect bandBounds =
2360 getPartBounds(Hit(PART_FRAME_TAG_BAND, -1, 0,
2361 doc::NullId, m_hot.band));
2362 g->fillRect(theme->colors.timelineBandHighlight(), bandBounds);
2363 }
2364
2365 int passes = (m_tagFocusBand >= 0 ? 2: 1);
2366 for (int pass=0; pass<passes; ++pass) {
2367 for (FrameTag* frameTag : m_sprite->frameTags()) {
2368 int band = -1;
2369 if (m_tagFocusBand >= 0) {
2370 auto it = m_tagBand.find(frameTag);
2371 if (it != m_tagBand.end()) {
2372 band = it->second;
2373 if ((pass == 0 && band == m_tagFocusBand) ||
2374 (pass == 1 && band != m_tagFocusBand))
2375 continue;
2376 }
2377 }
2378
2379 gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->fromFrame()));
2380 gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->toFrame()));
2381 gfx::Rect bounds = bounds1.createUnion(bounds2);
2382 gfx::Rect frameTagBounds = getPartBounds(Hit(PART_FRAME_TAG, 0, 0, frameTag->id()));
2383 bounds.h = bounds.y2() - frameTagBounds.y2();
2384 bounds.y = frameTagBounds.y2();
2385
2386 int dx = 0, dw = 0;
2387 if (m_dropTarget.outside &&
2388 m_dropTarget.hhit != DropTarget::HNone &&
2389 m_dropRange.type() == DocRange::kFrames) {
2390 switch (m_dropTarget.hhit) {
2391 case DropTarget::Before:
2392 if (m_dropRange.firstFrame() == frameTag->fromFrame()) {
2393 dx = +frameBoxWidth()/4;
2394 dw = -frameBoxWidth()/4;
2395 }
2396 else if (m_dropRange.firstFrame()-1 == frameTag->toFrame()) {
2397 dw = -frameBoxWidth()/4;
2398 }
2399 break;
2400 case DropTarget::After:
2401 if (m_dropRange.lastFrame() == frameTag->toFrame()) {
2402 dw = -frameBoxWidth()/4;
2403 }
2404 else if (m_dropRange.lastFrame()+1 == frameTag->fromFrame()) {
2405 dx = +frameBoxWidth()/4;
2406 dw = -frameBoxWidth()/4;
2407 }
2408 break;
2409 }
2410 }
2411 bounds.x += dx;
2412 bounds.w += dw;
2413 frameTagBounds.x += dx;
2414
2415 gfx::Color bg =
2416 (m_tagFocusBand < 0 || pass == 1) ?
2417 frameTag->color(): theme->colors.timelineBandBg();
2418 {
2419 IntersectClip clip(g, bounds);
2420 if (clip) {
2421 for (auto& layer : styles.timelineLoopRange()->layers()) {
2422 if (layer.type() == Style::Layer::Type::kBackground ||
2423 layer.type() == Style::Layer::Type::kBackgroundBorder ||
2424 layer.type() == Style::Layer::Type::kBorder) {
2425 const_cast<Style::Layer*>(&layer)->setColor(bg);
2426 }
2427 }
2428 drawPart(g, bounds, nullptr, styles.timelineLoopRange());
2429 }
2430 }
2431
2432 if (m_tagFocusBand < 0 || pass == 1) {
2433 bounds = frameTagBounds;
2434
2435 if (m_clk.part == PART_FRAME_TAG && m_clk.frameTag == frameTag->id()) {
2436 bg = color_utils::blackandwhite_neg(bg);
2437 }
2438 else if (m_hot.part == PART_FRAME_TAG && m_hot.frameTag == frameTag->id()) {
2439 int r, g, b;
2440 r = gfx::getr(bg)+32;
2441 g = gfx::getg(bg)+32;
2442 b = gfx::getb(bg)+32;
2443 r = MID(0, r, 255);
2444 g = MID(0, g, 255);
2445 b = MID(0, b, 255);
2446 bg = gfx::rgba(r, g, b, gfx::geta(bg));
2447 }
2448 g->fillRect(bg, bounds);
2449
2450 bounds.y += 2*ui::guiscale();
2451 bounds.x += 2*ui::guiscale();
2452 g->drawText(
2453 frameTag->name(),
2454 color_utils::blackandwhite_neg(bg),
2455 gfx::ColorNone,
2456 bounds.origin());
2457 }
2458 }
2459 }
2460
2461 // Draw button to expand/collapse the active band
2462 if (m_hot.band >= 0 && m_tagBands > 1) {
2463 gfx::Rect butBounds =
2464 getPartBounds(Hit(PART_FRAME_TAG_SWITCH_BAND_BUTTON, -1, 0,
2465 doc::NullId, m_hot.band));
2466 PaintWidgetPartInfo info;
2467 if (m_hot.part == PART_FRAME_TAG_SWITCH_BAND_BUTTON) {
2468 info.styleFlags |= ui::Style::Layer::kMouse;
2469 if (hasCapture())
2470 info.styleFlags |= ui::Style::Layer::kSelected;
2471 }
2472 theme->paintWidgetPart(g, styles.timelineSwitchBandButton(),
2473 butBounds, info);
2474 }
2475 }
2476
drawRangeOutline(ui::Graphics * g)2477 void Timeline::drawRangeOutline(ui::Graphics* g)
2478 {
2479 auto& styles = skinTheme()->styles;
2480
2481 IntersectClip clip(g, getRangeClipBounds(m_range).enlarge(outlineWidth()));
2482 if (!clip)
2483 return;
2484
2485 PaintWidgetPartInfo info;
2486 info.styleFlags =
2487 (m_range.enabled() ? ui::Style::Layer::kFocus: 0) |
2488 (m_hot.part == PART_RANGE_OUTLINE ? ui::Style::Layer::kMouse: 0);
2489
2490 gfx::Rect bounds = getPartBounds(Hit(PART_RANGE_OUTLINE));
2491 theme()->paintWidgetPart(
2492 g, styles.timelineRangeOutline(), bounds, info);
2493
2494 Range drop = m_dropRange;
2495 gfx::Rect dropBounds = getRangeBounds(drop);
2496
2497 switch (drop.type()) {
2498
2499 case Range::kCels: {
2500 dropBounds = dropBounds.enlarge(outlineWidth());
2501 info.styleFlags = ui::Style::Layer::kFocus;
2502 theme()->paintWidgetPart(
2503 g, styles.timelineRangeOutline(), dropBounds, info);
2504 break;
2505 }
2506
2507 case Range::kFrames: {
2508 int w = 5 * guiscale(); // TODO get width from the skin info
2509
2510 if (m_dropTarget.hhit == DropTarget::Before)
2511 dropBounds.x -= w/2;
2512 else if (drop == m_range)
2513 dropBounds.x = dropBounds.x + getRangeBounds(m_range).w - w/2;
2514 else
2515 dropBounds.x = dropBounds.x + dropBounds.w - w/2;
2516
2517 dropBounds.w = w;
2518
2519 info.styleFlags = 0;
2520 theme()->paintWidgetPart(
2521 g, styles.timelineDropFrameDeco(), dropBounds, info);
2522 break;
2523 }
2524
2525 case Range::kLayers: {
2526 int h = 5 * guiscale(); // TODO get height from the skin info
2527
2528 if (m_dropTarget.vhit == DropTarget::Top)
2529 dropBounds.y -= h/2;
2530 else if (drop == m_range)
2531 dropBounds.y = dropBounds.y + getRangeBounds(m_range).h - h/2;
2532 else
2533 dropBounds.y = dropBounds.y + dropBounds.h - h/2;
2534
2535 dropBounds.h = h;
2536
2537 theme()->paintWidgetPart(
2538 g, styles.timelineDropLayerDeco(), dropBounds, info);
2539 break;
2540 }
2541 }
2542 }
2543
drawPaddings(ui::Graphics * g)2544 void Timeline::drawPaddings(ui::Graphics* g)
2545 {
2546 auto& styles = skinTheme()->styles;
2547
2548 gfx::Rect client = clientBounds();
2549 gfx::Rect bottomLayer;
2550 gfx::Rect lastFrame;
2551 int top = topHeight();
2552
2553 if (!m_rows.empty()) {
2554 bottomLayer = getPartBounds(Hit(PART_ROW, firstLayer()));
2555 lastFrame = getPartBounds(Hit(PART_CEL, firstLayer(), this->lastFrame()));
2556 }
2557 else {
2558 bottomLayer = getPartBounds(Hit(PART_HEADER_LAYER));
2559 lastFrame = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), this->lastFrame()));
2560 }
2561
2562 drawPart(g,
2563 gfx::Rect(lastFrame.x+lastFrame.w, client.y + top,
2564 client.w - (lastFrame.x+lastFrame.w),
2565 bottomLayer.y+bottomLayer.h),
2566 NULL, styles.timelinePaddingTr());
2567
2568 drawPart(g,
2569 gfx::Rect(client.x, bottomLayer.y+bottomLayer.h,
2570 lastFrame.x+lastFrame.w - client.x, client.h - (bottomLayer.y+bottomLayer.h)),
2571 NULL, styles.timelinePaddingBl());
2572
2573 drawPart(g,
2574 gfx::Rect(lastFrame.x+lastFrame.w, bottomLayer.y+bottomLayer.h,
2575 client.w - (lastFrame.x+lastFrame.w),
2576 client.h - (bottomLayer.y+bottomLayer.h)),
2577 NULL, styles.timelinePaddingBr());
2578 }
2579
getLayerHeadersBounds() const2580 gfx::Rect Timeline::getLayerHeadersBounds() const
2581 {
2582 gfx::Rect rc = clientBounds();
2583 rc.w = m_separator_x;
2584 int h = topHeight() + headerBoxHeight();
2585 rc.y += h;
2586 rc.h -= h;
2587 return rc;
2588 }
2589
getFrameHeadersBounds() const2590 gfx::Rect Timeline::getFrameHeadersBounds() const
2591 {
2592 gfx::Rect rc = clientBounds();
2593 rc.x += m_separator_x;
2594 rc.y += topHeight();
2595 rc.w -= m_separator_x;
2596 rc.h = headerBoxHeight();
2597 return rc;
2598 }
2599
getOnionskinFramesBounds() const2600 gfx::Rect Timeline::getOnionskinFramesBounds() const
2601 {
2602 DocumentPreferences& docPref = this->docPref();
2603 if (!docPref.onionskin.active())
2604 return gfx::Rect();
2605
2606 frame_t firstFrame = m_frame - docPref.onionskin.prevFrames();
2607 frame_t lastFrame = m_frame + docPref.onionskin.nextFrames();
2608
2609 if (firstFrame < this->firstFrame())
2610 firstFrame = this->firstFrame();
2611
2612 if (lastFrame > this->lastFrame())
2613 lastFrame = this->lastFrame();
2614
2615 return getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), firstFrame))
2616 .createUnion(getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), lastFrame)));
2617 }
2618
getCelsBounds() const2619 gfx::Rect Timeline::getCelsBounds() const
2620 {
2621 gfx::Rect rc = clientBounds();
2622 rc.x += m_separator_x;
2623 rc.w -= m_separator_x;
2624 rc.y += headerBoxHeight() + topHeight();
2625 rc.h -= headerBoxHeight() + topHeight();
2626 return rc;
2627 }
2628
getPartBounds(const Hit & hit) const2629 gfx::Rect Timeline::getPartBounds(const Hit& hit) const
2630 {
2631 gfx::Rect bounds = clientBounds();
2632 int y = topHeight();
2633
2634 switch (hit.part) {
2635
2636 case PART_NOTHING:
2637 break;
2638
2639 case PART_TOP:
2640 return gfx::Rect(bounds.x, bounds.y, bounds.w, y);
2641
2642 case PART_SEPARATOR:
2643 return gfx::Rect(bounds.x + m_separator_x, bounds.y + y,
2644 m_separator_x + m_separator_w, bounds.h - y);
2645
2646 case PART_HEADER_EYE:
2647 return gfx::Rect(bounds.x + headerBoxWidth()*0, bounds.y + y,
2648 headerBoxWidth(), headerBoxHeight());
2649
2650 case PART_HEADER_PADLOCK:
2651 return gfx::Rect(bounds.x + headerBoxWidth()*1, bounds.y + y,
2652 headerBoxWidth(), headerBoxHeight());
2653
2654 case PART_HEADER_CONTINUOUS:
2655 return gfx::Rect(bounds.x + headerBoxWidth()*2, bounds.y + y,
2656 headerBoxWidth(), headerBoxHeight());
2657
2658 case PART_HEADER_GEAR:
2659 return gfx::Rect(bounds.x + headerBoxWidth()*3, bounds.y + y,
2660 headerBoxWidth(), headerBoxHeight());
2661
2662 case PART_HEADER_ONIONSKIN:
2663 return gfx::Rect(bounds.x + headerBoxWidth()*4, bounds.y + y,
2664 headerBoxWidth(), headerBoxHeight());
2665
2666 case PART_HEADER_LAYER:
2667 return gfx::Rect(bounds.x + headerBoxWidth()*5, bounds.y + y,
2668 m_separator_x - headerBoxWidth()*5, headerBoxHeight());
2669
2670 case PART_HEADER_FRAME:
2671 return gfx::Rect(
2672 bounds.x + m_separator_x + m_separator_w - 1
2673 + frameBoxWidth()*MAX(firstFrame(), hit.frame) - viewScroll().x,
2674 bounds.y + y, frameBoxWidth(), headerBoxHeight());
2675
2676 case PART_ROW:
2677 if (validLayer(hit.layer)) {
2678 return gfx::Rect(bounds.x,
2679 bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2680 m_separator_x, layerBoxHeight());
2681 }
2682 break;
2683
2684 case PART_ROW_EYE_ICON:
2685 if (validLayer(hit.layer)) {
2686 return gfx::Rect(bounds.x,
2687 bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2688 headerBoxWidth(), layerBoxHeight());
2689 }
2690 break;
2691
2692 case PART_ROW_PADLOCK_ICON:
2693 if (validLayer(hit.layer)) {
2694 return gfx::Rect(bounds.x + headerBoxWidth(),
2695 bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2696 headerBoxWidth(), layerBoxHeight());
2697 }
2698 break;
2699
2700 case PART_ROW_CONTINUOUS_ICON:
2701 if (validLayer(hit.layer)) {
2702 return gfx::Rect(bounds.x + 2* headerBoxWidth(),
2703 bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2704 headerBoxWidth(), layerBoxHeight());
2705 }
2706 break;
2707
2708 case PART_ROW_TEXT:
2709 if (validLayer(hit.layer)) {
2710 int x = headerBoxWidth()*3;
2711 return gfx::Rect(bounds.x + x,
2712 bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2713 m_separator_x - x, layerBoxHeight());
2714 }
2715 break;
2716
2717 case PART_CEL:
2718 if (validLayer(hit.layer) && hit.frame >= frame_t(0)) {
2719 return gfx::Rect(
2720 bounds.x + m_separator_x + m_separator_w - 1 + frameBoxWidth()*hit.frame - viewScroll().x,
2721 bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2722 frameBoxWidth(), layerBoxHeight());
2723 }
2724 break;
2725
2726 case PART_RANGE_OUTLINE: {
2727 gfx::Rect rc = getRangeBounds(m_range);
2728 int s = outlineWidth();
2729 rc.enlarge(s);
2730 if (rc.x < bounds.x) rc.offset(s, 0).inflate(-s, 0);
2731 if (rc.y < bounds.y) rc.offset(0, s).inflate(0, -s);
2732 return rc;
2733 }
2734
2735 case PART_FRAME_TAG: {
2736 FrameTag* frameTag = hit.getFrameTag();
2737 if (frameTag) {
2738 gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->fromFrame()));
2739 gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->toFrame()));
2740 gfx::Rect bounds = bounds1.createUnion(bounds2);
2741 bounds.y -= skinTheme()->dimensions.timelineTagsAreaHeight();
2742
2743 int textHeight = font()->height();
2744 bounds.y -= textHeight + 2*ui::guiscale();
2745 bounds.x += 3*ui::guiscale();
2746 bounds.w = font()->textLength(frameTag->name().c_str()) + 4*ui::guiscale();
2747 bounds.h = font()->height() + 2*ui::guiscale();
2748
2749 if (m_tagFocusBand < 0) {
2750 auto it = m_tagBand.find(frameTag);
2751 if (it != m_tagBand.end()) {
2752 int dy = (m_tagBands-it->second-1)*oneTagHeight();
2753 bounds.y -= dy;
2754 }
2755 }
2756
2757 return bounds;
2758 }
2759 break;
2760 }
2761
2762 case PART_FRAME_TAGS:
2763 return gfx::Rect(
2764 bounds.x + m_separator_x + m_separator_w - 1,
2765 bounds.y,
2766 bounds.w - m_separator_x - m_separator_w + 1, y);
2767
2768 case PART_FRAME_TAG_BAND:
2769 return gfx::Rect(
2770 bounds.x + m_separator_x + m_separator_w - 1,
2771 bounds.y
2772 + (m_tagFocusBand < 0 ? oneTagHeight() * MAX(0, hit.band): 0),
2773 bounds.w - m_separator_x - m_separator_w + 1,
2774 oneTagHeight());
2775
2776 case PART_FRAME_TAG_SWITCH_BUTTONS: {
2777 gfx::Size sz = theme()->calcSizeHint(
2778 this, skinTheme()->styles.timelineSwitchBandButton());
2779
2780 return gfx::Rect(
2781 bounds.x + bounds.w - sz.w,
2782 bounds.y,
2783 sz.w, y);
2784 }
2785
2786 case PART_FRAME_TAG_SWITCH_BAND_BUTTON: {
2787 gfx::Size sz = theme()->calcSizeHint(
2788 this, skinTheme()->styles.timelineSwitchBandButton());
2789
2790 return gfx::Rect(
2791 bounds.x + bounds.w - sz.w - 2*ui::guiscale(),
2792 bounds.y
2793 + (m_tagFocusBand < 0 ? oneTagHeight() * MAX(0, hit.band): 0)
2794 + oneTagHeight()/2 - sz.h/2,
2795 sz.w, sz.h);
2796 }
2797
2798 }
2799
2800 return gfx::Rect();
2801 }
2802
getRangeBounds(const Range & range) const2803 gfx::Rect Timeline::getRangeBounds(const Range& range) const
2804 {
2805 gfx::Rect rc;
2806 switch (range.type()) {
2807 case Range::kNone:
2808 // Return empty rectangle
2809 break;
2810 case Range::kCels:
2811 for (auto layer : range.selectedLayers()) {
2812 layer_t layerIdx = getLayerIndex(layer);
2813 for (auto frame : range.selectedFrames())
2814 rc |= getPartBounds(Hit(PART_CEL, layerIdx, frame));
2815 }
2816 break;
2817 case Range::kFrames: {
2818 for (auto frame : range.selectedFrames())
2819 rc |= getPartBounds(Hit(PART_HEADER_FRAME, 0, frame));
2820 break;
2821 }
2822 case Range::kLayers:
2823 for (auto layer : range.selectedLayers()) {
2824 layer_t layerIdx = getLayerIndex(layer);
2825 rc |= getPartBounds(Hit(PART_ROW, layerIdx));
2826 }
2827 break;
2828 }
2829 return rc;
2830 }
2831
getRangeClipBounds(const Range & range) const2832 gfx::Rect Timeline::getRangeClipBounds(const Range& range) const
2833 {
2834 gfx::Rect clipBounds;
2835 switch (range.type()) {
2836 case Range::kCels: clipBounds = getCelsBounds(); break;
2837 case Range::kFrames: clipBounds = getFrameHeadersBounds(); break;
2838 case Range::kLayers: clipBounds = getLayerHeadersBounds(); break;
2839 }
2840 return clipBounds;
2841 }
2842
invalidateHit(const Hit & hit)2843 void Timeline::invalidateHit(const Hit& hit)
2844 {
2845 if (hit.band >= 0) {
2846 Hit hit2 = hit;
2847 hit2.part = PART_FRAME_TAG_BAND;
2848 invalidateRect(getPartBounds(hit2).offset(origin()));
2849 }
2850
2851 invalidateRect(getPartBounds(hit).offset(origin()));
2852 }
2853
invalidateLayer(const Layer * layer)2854 void Timeline::invalidateLayer(const Layer* layer)
2855 {
2856 if (layer == nullptr)
2857 return;
2858
2859 layer_t layerIdx = getLayerIndex(layer);
2860 if (layerIdx < firstLayer())
2861 return;
2862
2863 gfx::Rect rc = getPartBounds(Hit(PART_ROW, layerIdx));
2864 gfx::Rect rcCels;
2865 rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, firstFrame()));
2866 rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, lastFrame()));
2867 rcCels &= getCelsBounds();
2868 rc |= rcCels;
2869 rc.offset(origin());
2870 invalidateRect(rc);
2871 }
2872
invalidateFrame(const frame_t frame)2873 void Timeline::invalidateFrame(const frame_t frame)
2874 {
2875 if (!validFrame(frame))
2876 return;
2877
2878 gfx::Rect rc = getPartBounds(Hit(PART_HEADER_FRAME, -1, frame));
2879 gfx::Rect rcCels;
2880 rcCels |= getPartBounds(Hit(PART_CEL, firstLayer(), frame));
2881 rcCels |= getPartBounds(Hit(PART_CEL, lastLayer(), frame));
2882 rcCels &= getCelsBounds();
2883 rc |= rcCels;
2884 rc.offset(origin());
2885 invalidateRect(rc);
2886 }
2887
regenerateRows()2888 void Timeline::regenerateRows()
2889 {
2890 ASSERT(m_document);
2891 ASSERT(m_sprite);
2892
2893 size_t nlayers = 0;
2894 for_each_expanded_layer(
2895 m_sprite->root(),
2896 [&nlayers](Layer* layer, int level, LayerFlags flags) {
2897 ++nlayers;
2898 });
2899
2900 if (m_rows.size() != nlayers) {
2901 if (nlayers > 0)
2902 m_rows.resize(nlayers);
2903 else
2904 m_rows.clear();
2905 }
2906
2907 size_t i = 0;
2908 for_each_expanded_layer(
2909 m_sprite->root(),
2910 [&i, this](Layer* layer, int level, LayerFlags flags) {
2911 m_rows[i++] = Row(layer, level, flags);
2912 });
2913
2914 regenerateTagBands();
2915 updateScrollBars();
2916 }
2917
regenerateTagBands()2918 void Timeline::regenerateTagBands()
2919 {
2920 // TODO improve this implementation
2921 std::vector<unsigned char> tagsPerFrame(m_sprite->totalFrames(), 0);
2922 std::vector<FrameTag*> bands(4, nullptr);
2923 m_tagBand.clear();
2924 for (FrameTag* frameTag : m_sprite->frameTags()) {
2925 frame_t f = frameTag->fromFrame();
2926
2927 int b=0;
2928 for (; b<int(bands.size()); ++b) {
2929 if (!bands[b] ||
2930 frameTag->fromFrame() > calcTagVisibleToFrame(bands[b])) {
2931 bands[b] = frameTag;
2932 m_tagBand[frameTag] = b;
2933 break;
2934 }
2935 }
2936 if (b == int(bands.size()))
2937 m_tagBand[frameTag] = tagsPerFrame[f];
2938
2939 frame_t toFrame = calcTagVisibleToFrame(frameTag);
2940 if (toFrame >= frame_t(tagsPerFrame.size()))
2941 tagsPerFrame.resize(toFrame+1, 0);
2942 for (; f<=toFrame; ++f) {
2943 ASSERT(f < frame_t(tagsPerFrame.size()));
2944 if (tagsPerFrame[f] < 255)
2945 ++tagsPerFrame[f];
2946 }
2947 }
2948
2949 const int oldVisibleBands = visibleTagBands();
2950 m_tagBands = 0;
2951 for (int i : tagsPerFrame)
2952 m_tagBands = MAX(m_tagBands, i);
2953
2954 if (m_tagFocusBand >= m_tagBands)
2955 m_tagFocusBand = -1;
2956
2957 if (oldVisibleBands != visibleTagBands())
2958 layout();
2959 }
2960
visibleTagBands() const2961 int Timeline::visibleTagBands() const
2962 {
2963 if (m_tagBands > 1 && m_tagFocusBand == -1)
2964 return m_tagBands;
2965 else
2966 return 1;
2967 }
2968
updateScrollBars()2969 void Timeline::updateScrollBars()
2970 {
2971 gfx::Rect rc = bounds();
2972 m_viewportArea = getCelsBounds().offset(rc.origin());
2973 ui::setup_scrollbars(getScrollableSize(),
2974 m_viewportArea, *this,
2975 m_hbar,
2976 m_vbar);
2977 }
2978
updateByMousePos(ui::Message * msg,const gfx::Point & mousePos)2979 void Timeline::updateByMousePos(ui::Message* msg, const gfx::Point& mousePos)
2980 {
2981 Hit hit = hitTest(msg, mousePos);
2982 if (hasMouseOver())
2983 setCursor(msg, hit);
2984 setHot(hit);
2985 }
2986
hitTest(ui::Message * msg,const gfx::Point & mousePos)2987 Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
2988 {
2989 Hit hit(PART_NOTHING, -1, -1);
2990 if (!m_document)
2991 return hit;
2992
2993 if (m_clk.part == PART_SEPARATOR) {
2994 hit.part = PART_SEPARATOR;
2995 }
2996 else {
2997 gfx::Point scroll = viewScroll();
2998 int top = topHeight();
2999
3000 hit.layer = lastLayer() -
3001 ((mousePos.y
3002 - top
3003 - headerBoxHeight()
3004 + scroll.y) / layerBoxHeight());
3005
3006 hit.frame = frame_t((mousePos.x
3007 - m_separator_x
3008 - m_separator_w
3009 + scroll.x) / frameBoxWidth());
3010
3011 // Flag which indicates that we are in the are below the Background layer/last layer area
3012 if (hit.layer < 0)
3013 hit.veryBottom = true;
3014
3015 if (hasCapture()) {
3016 hit.layer = MID(firstLayer(), hit.layer, lastLayer());
3017 if (isMovingCel())
3018 hit.frame = MAX(firstFrame(), hit.frame);
3019 else
3020 hit.frame = MID(firstFrame(), hit.frame, lastFrame());
3021 }
3022 else {
3023 if (hit.layer > lastLayer()) hit.layer = -1;
3024 if (hit.frame > lastFrame()) hit.frame = -1;
3025 }
3026
3027 // Is the mouse over onionskin handles?
3028 gfx::Rect bounds = getOnionskinFramesBounds();
3029 if (!bounds.isEmpty() && gfx::Rect(bounds.x, bounds.y, 3, bounds.h).contains(mousePos)) {
3030 hit.part = PART_HEADER_ONIONSKIN_RANGE_LEFT;
3031 }
3032 else if (!bounds.isEmpty() && gfx::Rect(bounds.x+bounds.w-3, bounds.y, 3, bounds.h).contains(mousePos)) {
3033 hit.part = PART_HEADER_ONIONSKIN_RANGE_RIGHT;
3034 }
3035 // Is the mouse on the separator.
3036 else if (mousePos.x > m_separator_x-4
3037 && mousePos.x <= m_separator_x) {
3038 hit.part = PART_SEPARATOR;
3039 }
3040 // Is the mouse on the frame tags area?
3041 else if (getPartBounds(Hit(PART_FRAME_TAGS)).contains(mousePos)) {
3042 // Mouse in switch band button
3043 if (hit.part == PART_NOTHING) {
3044 if (m_tagFocusBand < 0) {
3045 for (int band=0; band<m_tagBands; ++band) {
3046 gfx::Rect bounds = getPartBounds(
3047 Hit(PART_FRAME_TAG_SWITCH_BAND_BUTTON, 0, 0,
3048 doc::NullId, band));
3049 if (bounds.contains(mousePos)) {
3050 hit.part = PART_FRAME_TAG_SWITCH_BAND_BUTTON;
3051 hit.band = band;
3052 break;
3053 }
3054 }
3055 }
3056 else {
3057 gfx::Rect bounds = getPartBounds(
3058 Hit(PART_FRAME_TAG_SWITCH_BAND_BUTTON, 0, 0,
3059 doc::NullId, m_tagFocusBand));
3060 if (bounds.contains(mousePos)) {
3061 hit.part = PART_FRAME_TAG_SWITCH_BAND_BUTTON;
3062 hit.band = m_tagFocusBand;
3063 }
3064 }
3065 }
3066
3067 // Mouse in frame tags
3068 if (hit.part == PART_NOTHING) {
3069 for (FrameTag* frameTag : m_sprite->frameTags()) {
3070 gfx::Rect bounds = getPartBounds(Hit(PART_FRAME_TAG, 0, 0, frameTag->id()));
3071 if (bounds.contains(mousePos)) {
3072 const int band = m_tagBand[frameTag];
3073 if (m_tagFocusBand >= 0 &&
3074 m_tagFocusBand != band)
3075 continue;
3076
3077 hit.part = PART_FRAME_TAG;
3078 hit.frameTag = frameTag->id();
3079 hit.band = band;
3080 break;
3081 }
3082 }
3083 }
3084
3085 // Mouse in bands
3086 if (hit.part == PART_NOTHING) {
3087 if (m_tagFocusBand < 0) {
3088 for (int band=0; band<m_tagBands; ++band) {
3089 gfx::Rect bounds = getPartBounds(
3090 Hit(PART_FRAME_TAG_BAND, 0, 0,
3091 doc::NullId, band));
3092 if (bounds.contains(mousePos)) {
3093 hit.part = PART_FRAME_TAG_BAND;
3094 hit.band = band;
3095 break;
3096 }
3097 }
3098 }
3099 else {
3100 gfx::Rect bounds = getPartBounds(
3101 Hit(PART_FRAME_TAG_BAND, 0, 0,
3102 doc::NullId, m_tagFocusBand));
3103 if (bounds.contains(mousePos)) {
3104 hit.part = PART_FRAME_TAG_BAND;
3105 hit.band = m_tagFocusBand;
3106 }
3107 }
3108 }
3109 }
3110 // Is the mouse on the headers?
3111 else if (mousePos.y >= top && mousePos.y < top+headerBoxHeight()) {
3112 if (mousePos.x < m_separator_x) {
3113 if (getPartBounds(Hit(PART_HEADER_EYE)).contains(mousePos))
3114 hit.part = PART_HEADER_EYE;
3115 else if (getPartBounds(Hit(PART_HEADER_PADLOCK)).contains(mousePos))
3116 hit.part = PART_HEADER_PADLOCK;
3117 else if (getPartBounds(Hit(PART_HEADER_CONTINUOUS)).contains(mousePos))
3118 hit.part = PART_HEADER_CONTINUOUS;
3119 else if (getPartBounds(Hit(PART_HEADER_GEAR)).contains(mousePos))
3120 hit.part = PART_HEADER_GEAR;
3121 else if (getPartBounds(Hit(PART_HEADER_ONIONSKIN)).contains(mousePos))
3122 hit.part = PART_HEADER_ONIONSKIN;
3123 else if (getPartBounds(Hit(PART_HEADER_LAYER)).contains(mousePos))
3124 hit.part = PART_HEADER_LAYER;
3125 }
3126 else {
3127 hit.part = PART_HEADER_FRAME;
3128 }
3129 }
3130 // Activate a flag in case that the hit is in the header area (AniControls and tags).
3131 else if (mousePos.y < top+headerBoxHeight())
3132 hit.part = PART_TOP;
3133 // Is the mouse on a layer's label?
3134 else if (mousePos.x < m_separator_x) {
3135 if (getPartBounds(Hit(PART_ROW_EYE_ICON, hit.layer)).contains(mousePos))
3136 hit.part = PART_ROW_EYE_ICON;
3137 else if (getPartBounds(Hit(PART_ROW_PADLOCK_ICON, hit.layer)).contains(mousePos))
3138 hit.part = PART_ROW_PADLOCK_ICON;
3139 else if (getPartBounds(Hit(PART_ROW_CONTINUOUS_ICON, hit.layer)).contains(mousePos))
3140 hit.part = PART_ROW_CONTINUOUS_ICON;
3141 else if (getPartBounds(Hit(PART_ROW_TEXT, hit.layer)).contains(mousePos))
3142 hit.part = PART_ROW_TEXT;
3143 else
3144 hit.part = PART_ROW;
3145 }
3146 else if (validLayer(hit.layer) && validFrame(hit.frame)) {
3147 hit.part = PART_CEL;
3148 }
3149 else
3150 hit.part = PART_NOTHING;
3151
3152 if (!hasCapture()) {
3153 gfx::Rect outline = getPartBounds(Hit(PART_RANGE_OUTLINE));
3154 if (outline.contains(mousePos)) {
3155 auto mouseMsg = dynamic_cast<MouseMessage*>(msg);
3156
3157 if (// With Ctrl and Alt key we can drag the range from any place (not necessary from the outline.
3158 is_copy_key_pressed(msg) ||
3159 // Drag with right-click
3160 (m_state == STATE_STANDBY &&
3161 mouseMsg &&
3162 mouseMsg->right()) ||
3163 // Drag with left-click only if we are inside the range edges
3164 !gfx::Rect(outline).shrink(2*outlineWidth()).contains(mousePos)) {
3165 hit.part = PART_RANGE_OUTLINE;
3166 }
3167 }
3168 }
3169 }
3170
3171 return hit;
3172 }
3173
hitTestCel(const gfx::Point & mousePos)3174 Timeline::Hit Timeline::hitTestCel(const gfx::Point& mousePos)
3175 {
3176 Hit hit(PART_NOTHING, -1, -1);
3177 if (!m_document)
3178 return hit;
3179
3180 gfx::Point scroll = viewScroll();
3181 int top = topHeight();
3182
3183 hit.layer = lastLayer() - (
3184 (mousePos.y
3185 - top
3186 - headerBoxHeight()
3187 + scroll.y) / layerBoxHeight());
3188
3189 hit.frame = frame_t((mousePos.x
3190 - m_separator_x
3191 - m_separator_w
3192 + scroll.x) / frameBoxWidth());
3193
3194 hit.layer = MID(firstLayer(), hit.layer, lastLayer());
3195 hit.frame = MAX(firstFrame(), hit.frame);
3196
3197 return hit;
3198 }
3199
setHot(const Hit & hit)3200 void Timeline::setHot(const Hit& hit)
3201 {
3202 // If the part, layer or frame change.
3203 if (m_hot != hit) {
3204 // Invalidate the whole control.
3205 if (isMovingCel()) {
3206 invalidate();
3207 }
3208 // Invalidate the old and new 'hot' thing.
3209 else {
3210 invalidateHit(m_hot);
3211 invalidateHit(hit);
3212 }
3213
3214 // Change the new 'hot' thing.
3215 m_hot = hit;
3216 }
3217 }
3218
updateStatusBar(ui::Message * msg)3219 void Timeline::updateStatusBar(ui::Message* msg)
3220 {
3221 if (!hasMouse())
3222 return;
3223
3224 StatusBar* sb = StatusBar::instance();
3225
3226 if (m_state == STATE_MOVING_RANGE && msg) {
3227 const char* verb = is_copy_key_pressed(msg) ? "Copy": "Move";
3228
3229 switch (m_range.type()) {
3230
3231 case Range::kCels:
3232 sb->setStatusText(0, "%s cels", verb);
3233 break;
3234
3235 case Range::kFrames:
3236 if (validFrame(m_hot.frame)) {
3237 if (m_dropTarget.hhit == DropTarget::Before) {
3238 sb->setStatusText(0, "%s before frame %d", verb, int(m_dropRange.firstFrame()+1));
3239 return;
3240 }
3241 else if (m_dropTarget.hhit == DropTarget::After) {
3242 sb->setStatusText(0, "%s after frame %d", verb, int(m_dropRange.lastFrame()+1));
3243 return;
3244 }
3245 }
3246 break;
3247
3248 case Range::kLayers: {
3249 layer_t firstLayer;
3250 layer_t lastLayer;
3251 if (!selectedLayersBounds(m_dropRange.selectedLayers(),
3252 &firstLayer, &lastLayer))
3253 break;
3254
3255 if (m_dropTarget.vhit == DropTarget::VeryBottom) {
3256 sb->setStatusText(0, "%s at the very bottom", verb);
3257 return;
3258 }
3259
3260 layer_t layerIdx = -1;
3261 if (m_dropTarget.vhit == DropTarget::Bottom ||
3262 m_dropTarget.vhit == DropTarget::FirstChild)
3263 layerIdx = firstLayer;
3264 else if (m_dropTarget.vhit == DropTarget::Top)
3265 layerIdx = lastLayer;
3266
3267 Layer* layer = (validLayer(layerIdx) ? m_rows[layerIdx].layer(): nullptr);
3268 if (layer) {
3269 switch (m_dropTarget.vhit) {
3270 case DropTarget::Bottom:
3271 sb->setStatusText(0, "%s at bottom of layer %s", verb, layer->name().c_str());
3272 return;
3273 case DropTarget::Top:
3274 sb->setStatusText(0, "%s at top of layer %s", verb, layer->name().c_str());
3275 return;
3276 case DropTarget::FirstChild:
3277 sb->setStatusText(0, "%s as first child of layer %s", verb, layer->name().c_str());
3278 return;
3279 }
3280 }
3281 break;
3282 }
3283
3284 }
3285 }
3286 else {
3287 Layer* layer = (validLayer(m_hot.layer) ? m_rows[m_hot.layer].layer():
3288 nullptr);
3289
3290 switch (m_hot.part) {
3291
3292 case PART_HEADER_ONIONSKIN: {
3293 sb->setStatusText(0, "Onionskin is %s",
3294 docPref().onionskin.active() ? "enabled": "disabled");
3295 return;
3296 }
3297
3298 case PART_ROW_TEXT:
3299 if (layer != NULL) {
3300 sb->setStatusText(
3301 0, "%s '%s' [%s%s]",
3302 layer->isReference() ? "Reference layer": "Layer",
3303 layer->name().c_str(),
3304 layer->isVisible() ? "visible": "hidden",
3305 layer->isEditable() ? "": " locked");
3306 return;
3307 }
3308 break;
3309
3310 case PART_ROW_EYE_ICON:
3311 if (layer != NULL) {
3312 sb->setStatusText(0, "Layer '%s' is %s",
3313 layer->name().c_str(),
3314 layer->isVisible() ? "visible": "hidden");
3315 return;
3316 }
3317 break;
3318
3319 case PART_ROW_PADLOCK_ICON:
3320 if (layer != NULL) {
3321 sb->setStatusText(0, "Layer '%s' is %s",
3322 layer->name().c_str(),
3323 layer->isEditable() ? "unlocked (editable)": "locked (read-only)");
3324 return;
3325 }
3326 break;
3327
3328 case PART_ROW_CONTINUOUS_ICON:
3329 if (layer) {
3330 if (layer->isImage())
3331 sb->setStatusText(0, "Layer '%s' is %s (%s)",
3332 layer->name().c_str(),
3333 layer->isContinuous() ? "continuous": "discontinuous",
3334 layer->isContinuous() ? "prefer linked cels/frames": "prefer individual cels/frames");
3335 else if (layer->isGroup())
3336 sb->setStatusText(0, "Group '%s'", layer->name().c_str());
3337 return;
3338 }
3339 break;
3340
3341 case PART_HEADER_FRAME:
3342 case PART_CEL:
3343 case PART_FRAME_TAG: {
3344 frame_t frame = m_frame;
3345 if (validFrame(m_hot.frame))
3346 frame = m_hot.frame;
3347
3348 updateStatusBarForFrame(
3349 frame,
3350 m_hot.getFrameTag(),
3351 (layer ? layer->cel(frame) : nullptr));
3352 return;
3353 }
3354 }
3355 }
3356
3357 sb->clearText();
3358 }
3359
updateStatusBarForFrame(const frame_t frame,const FrameTag * frameTag,const Cel * cel)3360 void Timeline::updateStatusBarForFrame(const frame_t frame,
3361 const FrameTag* frameTag,
3362 const Cel* cel)
3363 {
3364 if (!m_sprite)
3365 return;
3366
3367 char buf[256] = { 0 };
3368 frame_t base = docPref().timeline.firstFrame();
3369 frame_t firstFrame = frame;
3370 frame_t lastFrame = frame;
3371
3372 if (frameTag) {
3373 firstFrame = frameTag->fromFrame();
3374 lastFrame = frameTag->toFrame();
3375 }
3376 else if (m_range.enabled() &&
3377 m_range.frames() > 1) {
3378 firstFrame = m_range.firstFrame();
3379 lastFrame = m_range.lastFrame();
3380 }
3381
3382 std::sprintf(
3383 buf+std::strlen(buf), ":frame: %d",
3384 base+frame);
3385 if (firstFrame != lastFrame) {
3386 std::sprintf(
3387 buf+std::strlen(buf), " [%d...%d]",
3388 int(base+firstFrame),
3389 int(base+lastFrame));
3390 }
3391
3392 std::sprintf(
3393 buf+std::strlen(buf), " :clock: %s",
3394 human_readable_time(m_sprite->frameDuration(frame)).c_str());
3395 if (firstFrame != lastFrame) {
3396 std::sprintf(
3397 buf+std::strlen(buf), " [%s]",
3398 frameTag ?
3399 human_readable_time(tagFramesDuration(frameTag)).c_str():
3400 human_readable_time(selectedFramesDuration()).c_str());
3401 }
3402 if (m_sprite->totalFrames() > 1)
3403 std::sprintf(
3404 buf+std::strlen(buf), "/%s",
3405 human_readable_time(m_sprite->totalAnimationDuration()).c_str());
3406
3407 if (cel) {
3408 std::sprintf(
3409 buf+std::strlen(buf), " Cel :pos: %d %d :size: %d %d",
3410 cel->bounds().x, cel->bounds().y,
3411 cel->bounds().w, cel->bounds().h);
3412
3413 if (cel->links() > 0) {
3414 std::sprintf(
3415 buf+std::strlen(buf), " Links %d",
3416 int(cel->links()));
3417 }
3418 }
3419
3420 StatusBar::instance()
3421 ->setStatusText(0, buf);
3422 }
3423
showCel(layer_t layer,frame_t frame)3424 void Timeline::showCel(layer_t layer, frame_t frame)
3425 {
3426 gfx::Point scroll = viewScroll();
3427
3428 gfx::Rect viewport = m_viewportArea;
3429
3430 // Add the horizontal bar space to the viewport area if the viewport
3431 // is not big enough to show one cel.
3432 if (m_hbar.isVisible() && viewport.h < layerBoxHeight())
3433 viewport.h += m_vbar.getBarWidth();
3434
3435 gfx::Rect celBounds(
3436 viewport.x + frameBoxWidth()*frame - scroll.x,
3437 viewport.y + layerBoxHeight()*(lastLayer() - layer) - scroll.y,
3438 frameBoxWidth(), layerBoxHeight());
3439
3440 const bool isPlaying = m_editor->isPlaying();
3441
3442 // Here we use <= instead of < to avoid jumping between this
3443 // condition and the "else if" one when we are playing the
3444 // animation.
3445 if (celBounds.x <= viewport.x) {
3446 scroll.x -= viewport.x - celBounds.x;
3447 if (isPlaying)
3448 scroll.x -= viewport.w/2 - celBounds.w/2;
3449 }
3450 else if (celBounds.x2() > viewport.x2()) {
3451 scroll.x += celBounds.x2() - viewport.x2();
3452 if (isPlaying)
3453 scroll.x += viewport.w/2 - celBounds.w/2;
3454 }
3455
3456 if (celBounds.y <= viewport.y) {
3457 scroll.y -= viewport.y - celBounds.y;
3458 }
3459 else if (celBounds.y2() > viewport.y2()) {
3460 scroll.y += celBounds.y2() - viewport.y2();
3461 }
3462
3463 setViewScroll(scroll);
3464 }
3465
showCurrentCel()3466 void Timeline::showCurrentCel()
3467 {
3468 layer_t layer = getLayerIndex(m_layer);
3469 if (layer >= firstLayer())
3470 showCel(layer, m_frame);
3471 }
3472
focusTagBand(int band)3473 void Timeline::focusTagBand(int band)
3474 {
3475 if (m_tagFocusBand < 0) {
3476 m_tagFocusBand = band;
3477 }
3478 else {
3479 m_tagFocusBand = -1;
3480 }
3481 }
3482
cleanClk()3483 void Timeline::cleanClk()
3484 {
3485 invalidateHit(m_clk);
3486 m_clk = Hit(PART_NOTHING);
3487 }
3488
getScrollableSize() const3489 gfx::Size Timeline::getScrollableSize() const
3490 {
3491 if (m_sprite) {
3492 return gfx::Size(
3493 m_sprite->totalFrames() * frameBoxWidth() + getCelsBounds().w/2,
3494 (m_rows.size()+1) * layerBoxHeight());
3495 }
3496 else
3497 return gfx::Size(0, 0);
3498 }
3499
getMaxScrollablePos() const3500 gfx::Point Timeline::getMaxScrollablePos() const
3501 {
3502 if (m_sprite) {
3503 gfx::Size size = getScrollableSize();
3504 int max_scroll_x = size.w - getCelsBounds().w + 1*guiscale();
3505 int max_scroll_y = size.h - getCelsBounds().h + 1*guiscale();
3506 max_scroll_x = MAX(0, max_scroll_x);
3507 max_scroll_y = MAX(0, max_scroll_y);
3508 return gfx::Point(max_scroll_x, max_scroll_y);
3509 }
3510 else
3511 return gfx::Point(0, 0);
3512 }
3513
allLayersVisible()3514 bool Timeline::allLayersVisible()
3515 {
3516 for (Layer* topLayer : m_sprite->root()->layers())
3517 if (!topLayer->isVisible())
3518 return false;
3519
3520 return true;
3521 }
3522
allLayersInvisible()3523 bool Timeline::allLayersInvisible()
3524 {
3525 for (Layer* topLayer : m_sprite->root()->layers())
3526 if (topLayer->isVisible())
3527 return false;
3528
3529 return true;
3530 }
3531
allLayersLocked()3532 bool Timeline::allLayersLocked()
3533 {
3534 for (Layer* topLayer : m_sprite->root()->layers())
3535 if (topLayer->isEditable())
3536 return false;
3537
3538 return true;
3539 }
3540
allLayersUnlocked()3541 bool Timeline::allLayersUnlocked()
3542 {
3543 for (Layer* topLayer : m_sprite->root()->layers())
3544 if (!topLayer->isEditable())
3545 return false;
3546
3547 return true;
3548 }
3549
allLayersContinuous()3550 bool Timeline::allLayersContinuous()
3551 {
3552 for (size_t i=0; i<m_rows.size(); i++)
3553 if (!m_rows[i].layer()->isContinuous())
3554 return false;
3555
3556 return true;
3557 }
3558
allLayersDiscontinuous()3559 bool Timeline::allLayersDiscontinuous()
3560 {
3561 for (size_t i=0; i<m_rows.size(); i++)
3562 if (m_rows[i].layer()->isContinuous())
3563 return false;
3564
3565 return true;
3566 }
3567
getLayerIndex(const Layer * layer) const3568 layer_t Timeline::getLayerIndex(const Layer* layer) const
3569 {
3570 for (int i=0; i<(int)m_rows.size(); i++)
3571 if (m_rows[i].layer() == layer)
3572 return i;
3573
3574 return -1;
3575 }
3576
isLayerActive(const layer_t layerIndex) const3577 bool Timeline::isLayerActive(const layer_t layerIndex) const
3578 {
3579 if (layerIndex == getLayerIndex(m_layer))
3580 return true;
3581 else
3582 return m_range.contains(m_rows[layerIndex].layer());
3583 }
3584
isFrameActive(const frame_t frame) const3585 bool Timeline::isFrameActive(const frame_t frame) const
3586 {
3587 if (frame == m_frame)
3588 return true;
3589 else
3590 return m_range.contains(frame);
3591 }
3592
isCelActive(const layer_t layerIdx,const frame_t frame) const3593 bool Timeline::isCelActive(const layer_t layerIdx, const frame_t frame) const
3594 {
3595 if (m_range.enabled())
3596 return m_range.contains(m_rows[layerIdx].layer(), frame);
3597 else
3598 return (layerIdx == getLayerIndex(m_layer) &&
3599 frame == m_frame);
3600 }
3601
isCelLooselyActive(const layer_t layerIdx,const frame_t frame) const3602 bool Timeline::isCelLooselyActive(const layer_t layerIdx, const frame_t frame) const
3603 {
3604 if (m_range.enabled())
3605 return (m_range.contains(m_rows[layerIdx].layer()) ||
3606 m_range.contains(frame));
3607 else
3608 return (layerIdx == getLayerIndex(m_layer) ||
3609 frame == m_frame);
3610 }
3611
dropRange(DropOp op)3612 void Timeline::dropRange(DropOp op)
3613 {
3614 bool copy = (op == Timeline::kCopy);
3615 Range newFromRange;
3616 DocRangePlace place = kDocRangeAfter;
3617 Range dropRange = m_dropRange;
3618 bool outside = m_dropTarget.outside;
3619
3620 switch (m_range.type()) {
3621
3622 case Range::kFrames:
3623 if (m_dropTarget.hhit == DropTarget::Before)
3624 place = kDocRangeBefore;
3625 break;
3626
3627 case Range::kLayers:
3628 switch (m_dropTarget.vhit) {
3629 case DropTarget::Bottom:
3630 place = kDocRangeBefore;
3631 break;
3632 case DropTarget::FirstChild:
3633 place = kDocRangeFirstChild;
3634 break;
3635 case DropTarget::VeryBottom:
3636 place = kDocRangeBefore;
3637 {
3638 Layer* layer = m_sprite->root()->firstLayer();
3639 dropRange.clearRange();
3640 dropRange.startRange(layer, -1, Range::kLayers);
3641 dropRange.endRange(layer, -1);
3642 }
3643 break;
3644 }
3645 break;
3646 }
3647
3648 prepareToMoveRange();
3649
3650 try {
3651 TagsHandling tagsHandling = (outside ? kFitOutsideTags:
3652 kFitInsideTags);
3653
3654 invalidateRange();
3655 if (copy)
3656 newFromRange = copy_range(m_document, m_range, dropRange,
3657 place, tagsHandling);
3658 else
3659 newFromRange = move_range(m_document, m_range, dropRange,
3660 place, tagsHandling);
3661
3662 // If we drop a cel in the same frame (but in another layer),
3663 // document views are not updated, so we are forcing the updating of
3664 // all views.
3665 m_document->notifyGeneralUpdate();
3666
3667 moveRange(newFromRange);
3668
3669 invalidateRange();
3670
3671 if (m_range.type() == Range::kFrames &&
3672 m_sprite &&
3673 !m_sprite->frameTags().empty()) {
3674 invalidateRect(getFrameHeadersBounds().offset(origin()));
3675 }
3676 }
3677 catch (const std::exception& ex) {
3678 Console::showException(ex);
3679 }
3680 }
3681
visibleSize() const3682 gfx::Size Timeline::visibleSize() const
3683 {
3684 return getCelsBounds().size();
3685 }
3686
viewScroll() const3687 gfx::Point Timeline::viewScroll() const
3688 {
3689 return gfx::Point(m_hbar.getPos(), m_vbar.getPos());
3690 }
3691
setViewScroll(const gfx::Point & pt)3692 void Timeline::setViewScroll(const gfx::Point& pt)
3693 {
3694 const gfx::Point oldScroll = viewScroll();
3695 const gfx::Point maxPos = getMaxScrollablePos();
3696 gfx::Point newScroll = pt;
3697 newScroll.x = MID(0, newScroll.x, maxPos.x);
3698 newScroll.y = MID(0, newScroll.y, maxPos.y);
3699
3700 if (newScroll.y != oldScroll.y) {
3701 gfx::Rect rc;
3702 rc |= getPartBounds(Hit(PART_ROW, 0));
3703 rc |= getPartBounds(Hit(PART_ROW, lastLayer()));
3704 rc.offset(origin());
3705 invalidateRect(rc);
3706 }
3707
3708 if (newScroll != oldScroll) {
3709 gfx::Rect rc;
3710 if (m_tagBands > 0)
3711 rc |= getPartBounds(Hit(PART_FRAME_TAG_BAND));
3712 rc |= getFrameHeadersBounds();
3713 rc |= getCelsBounds();
3714 rc.offset(origin());
3715 invalidateRect(rc);
3716 }
3717
3718 m_hbar.setPos(newScroll.x);
3719 m_vbar.setPos(newScroll.y);
3720 }
3721
3722
lockRange()3723 void Timeline::lockRange()
3724 {
3725 ++m_rangeLocks;
3726 }
3727
unlockRange()3728 void Timeline::unlockRange()
3729 {
3730 --m_rangeLocks;
3731 }
3732
updateDropRange(const gfx::Point & pt)3733 void Timeline::updateDropRange(const gfx::Point& pt)
3734 {
3735 const DropTarget oldDropTarget = m_dropTarget;
3736 m_dropTarget.hhit = DropTarget::HNone;
3737 m_dropTarget.vhit = DropTarget::VNone;
3738 m_dropTarget.outside = false;
3739
3740 if (m_state != STATE_MOVING_RANGE) {
3741 m_dropRange.clearRange();
3742 return;
3743 }
3744
3745 switch (m_range.type()) {
3746
3747 case Range::kCels:
3748 m_dropRange = m_range;
3749 m_dropRange.displace(m_hot.layer - m_clk.layer,
3750 m_hot.frame - m_clk.frame);
3751 break;
3752
3753 case Range::kFrames:
3754 case Range::kLayers:
3755 m_dropRange.clearRange();
3756 m_dropRange.startRange(m_rows[m_hot.layer].layer(), m_hot.frame, m_range.type());
3757 m_dropRange.endRange(m_rows[m_hot.layer].layer(), m_hot.frame);
3758 break;
3759 }
3760
3761 gfx::Rect bounds = getRangeBounds(m_dropRange);
3762
3763 if (pt.x < bounds.x + bounds.w/2) {
3764 m_dropTarget.hhit = DropTarget::Before;
3765 m_dropTarget.outside = (pt.x < bounds.x+2);
3766 }
3767 else {
3768 m_dropTarget.hhit = DropTarget::After;
3769 m_dropTarget.outside = (pt.x >= bounds.x2()-2);
3770 }
3771
3772 if (m_hot.veryBottom)
3773 m_dropTarget.vhit = DropTarget::VeryBottom;
3774 else if (pt.y < bounds.y + bounds.h/2)
3775 m_dropTarget.vhit = DropTarget::Top;
3776 // Special drop target for expanded groups
3777 else if (m_range.type() == Range::kLayers &&
3778 m_hot.layer >= 0 &&
3779 m_hot.layer < int(m_rows.size()) &&
3780 m_rows[m_hot.layer].layer()->isGroup() &&
3781 static_cast<LayerGroup*>(m_rows[m_hot.layer].layer())->isExpanded()) {
3782 m_dropTarget.vhit = DropTarget::FirstChild;
3783 }
3784 else {
3785 m_dropTarget.vhit = DropTarget::Bottom;
3786 }
3787
3788 if (oldDropTarget != m_dropTarget)
3789 invalidate();
3790 }
3791
clearClipboardRange()3792 void Timeline::clearClipboardRange()
3793 {
3794 Doc* clipboard_document;
3795 DocRange clipboard_range;
3796 clipboard::get_document_range_info(
3797 &clipboard_document,
3798 &clipboard_range);
3799
3800 if (!m_document || clipboard_document != m_document)
3801 return;
3802
3803 clipboard::clear_content();
3804 m_clipboard_timer.stop();
3805 }
3806
invalidateRange()3807 void Timeline::invalidateRange()
3808 {
3809 if (m_range.enabled()) {
3810 for (const Layer* layer : m_range.selectedLayers())
3811 invalidateLayer(layer);
3812 for (const frame_t frame : m_range.selectedFrames())
3813 invalidateFrame(frame);
3814
3815 invalidateHit(Hit(PART_RANGE_OUTLINE));
3816 }
3817 }
3818
clearAndInvalidateRange()3819 void Timeline::clearAndInvalidateRange()
3820 {
3821 if (m_range.enabled()) {
3822 invalidateRange();
3823 m_range.clearRange();
3824 }
3825 }
3826
docPref() const3827 DocumentPreferences& Timeline::docPref() const
3828 {
3829 return Preferences::instance().document(m_document);
3830 }
3831
skinTheme() const3832 skin::SkinTheme* Timeline::skinTheme() const
3833 {
3834 return static_cast<SkinTheme*>(theme());
3835 }
3836
celBoxSize() const3837 gfx::Size Timeline::celBoxSize() const
3838 {
3839 return gfx::Size(frameBoxWidth(), layerBoxHeight());
3840 }
3841
headerBoxWidth() const3842 int Timeline::headerBoxWidth() const
3843 {
3844 return skinTheme()->dimensions.timelineBaseSize();
3845 }
3846
headerBoxHeight() const3847 int Timeline::headerBoxHeight() const
3848 {
3849 return skinTheme()->dimensions.timelineBaseSize();
3850 }
3851
layerBoxHeight() const3852 int Timeline::layerBoxHeight() const
3853 {
3854 return int(zoom()*skinTheme()->dimensions.timelineBaseSize());
3855 }
3856
frameBoxWidth() const3857 int Timeline::frameBoxWidth() const
3858 {
3859 return int(zoom()*skinTheme()->dimensions.timelineBaseSize());
3860 }
3861
outlineWidth() const3862 int Timeline::outlineWidth() const
3863 {
3864 return skinTheme()->dimensions.timelineOutlineWidth();
3865 }
3866
oneTagHeight() const3867 int Timeline::oneTagHeight() const
3868 {
3869 return
3870 font()->height() +
3871 2*ui::guiscale() +
3872 skinTheme()->dimensions.timelineTagsAreaHeight();
3873 }
3874
zoom() const3875 double Timeline::zoom() const
3876 {
3877 if (docPref().thumbnails.enabled())
3878 return m_zoom;
3879 else
3880 return 1.0;
3881 }
3882
3883 // Returns the last frame where the frame tag (or frame tag label)
3884 // is visible in the timeline.
calcTagVisibleToFrame(FrameTag * frameTag) const3885 int Timeline::calcTagVisibleToFrame(FrameTag* frameTag) const
3886 {
3887 return
3888 MAX(frameTag->toFrame(),
3889 frameTag->fromFrame() +
3890 font()->textLength(frameTag->name())/frameBoxWidth());
3891 }
3892
topHeight() const3893 int Timeline::topHeight() const
3894 {
3895 int h = 0;
3896 if (m_document && m_sprite) {
3897 h += skinTheme()->dimensions.timelineTopBorder();
3898 h += oneTagHeight() * visibleTagBands();
3899 }
3900 return h;
3901 }
3902
onNewInputPriority(InputChainElement * element,const ui::Message * msg)3903 void Timeline::onNewInputPriority(InputChainElement* element,
3904 const ui::Message* msg)
3905 {
3906 // It looks like the user wants to execute commands targetting the
3907 // ColorBar instead of the Timeline. Here we disable the selected
3908 // range, so commands like Clear, Copy, Cut, etc. don't target the
3909 // Timeline and they are sent to the active sprite editor.
3910 //
3911 // If the Workspace is selected (an sprite Editor), maybe the user
3912 // want to move the X/Y position of all cels in the Timeline range.
3913 // That is why we don't disable the range in this case.
3914 Workspace* workspace = dynamic_cast<Workspace*>(element);
3915 if (!workspace) {
3916 // With Ctrl or Shift we can combine ColorBar selection + Timeline
3917 // selection.
3918 if (msg && (msg->ctrlPressed() || msg->shiftPressed()))
3919 return;
3920
3921 if (element != this && m_rangeLocks == 0) {
3922 m_range.clearRange();
3923 invalidate();
3924 }
3925 }
3926 }
3927
onCanCut(Context * ctx)3928 bool Timeline::onCanCut(Context* ctx)
3929 {
3930 return false; // TODO
3931 }
3932
onCanCopy(Context * ctx)3933 bool Timeline::onCanCopy(Context* ctx)
3934 {
3935 return
3936 m_range.enabled() &&
3937 ctx->checkFlags(ContextFlags::HasActiveDocument);
3938 }
3939
onCanPaste(Context * ctx)3940 bool Timeline::onCanPaste(Context* ctx)
3941 {
3942 return
3943 (clipboard::get_current_format() == clipboard::ClipboardDocRange &&
3944 ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable));
3945 }
3946
onCanClear(Context * ctx)3947 bool Timeline::onCanClear(Context* ctx)
3948 {
3949 return (m_document && m_sprite && m_range.enabled());
3950 }
3951
onCut(Context * ctx)3952 bool Timeline::onCut(Context* ctx)
3953 {
3954 return false; // TODO
3955 }
3956
onCopy(Context * ctx)3957 bool Timeline::onCopy(Context* ctx)
3958 {
3959 if (m_range.enabled()) {
3960 const ContextReader reader(ctx);
3961 if (reader.document()) {
3962 clipboard::copy_range(reader, m_range);
3963 return true;
3964 }
3965 }
3966 return false;
3967 }
3968
onPaste(Context * ctx)3969 bool Timeline::onPaste(Context* ctx)
3970 {
3971 if (clipboard::get_current_format() == clipboard::ClipboardDocRange) {
3972 clipboard::paste();
3973 return true;
3974 }
3975 else
3976 return false;
3977 }
3978
onClear(Context * ctx)3979 bool Timeline::onClear(Context* ctx)
3980 {
3981 if (!m_document || !m_sprite || !m_range.enabled())
3982 return false;
3983
3984 Command* cmd = nullptr;
3985
3986 switch (m_range.type()) {
3987 case DocRange::kCels:
3988 cmd = Commands::instance()->byId(CommandId::ClearCel());
3989 break;
3990 case DocRange::kFrames:
3991 cmd = Commands::instance()->byId(CommandId::RemoveFrame());
3992 break;
3993 case DocRange::kLayers:
3994 cmd = Commands::instance()->byId(CommandId::RemoveLayer());
3995 break;
3996 }
3997
3998 if (cmd) {
3999 ctx->executeCommand(cmd);
4000 return true;
4001 }
4002 else
4003 return false;
4004 }
4005
onCancel(Context * ctx)4006 void Timeline::onCancel(Context* ctx)
4007 {
4008 if (m_rangeLocks == 0)
4009 clearAndInvalidateRange();
4010
4011 clearClipboardRange();
4012 invalidate();
4013 }
4014
tagFramesDuration(const FrameTag * frameTag) const4015 int Timeline::tagFramesDuration(const FrameTag* frameTag) const
4016 {
4017 ASSERT(m_sprite);
4018 ASSERT(frameTag);
4019
4020 int duration = 0;
4021 for (frame_t f=frameTag->fromFrame();
4022 f<frameTag->toFrame(); ++f) {
4023 duration += m_sprite->frameDuration(f);
4024 }
4025 return duration;
4026 }
4027
selectedFramesDuration() const4028 int Timeline::selectedFramesDuration() const
4029 {
4030 ASSERT(m_sprite);
4031
4032 int duration = 0;
4033 for (frame_t f=0; f<m_sprite->totalFrames(); ++f) {
4034 if (isFrameActive(f))
4035 duration += m_sprite->frameDuration(f);
4036 }
4037 return duration; // TODO cache this value
4038 }
4039
setLayerVisibleFlag(const layer_t l,const bool state)4040 void Timeline::setLayerVisibleFlag(const layer_t l, const bool state)
4041 {
4042 if (!validLayer(l))
4043 return;
4044
4045 Row& row = m_rows[l];
4046 Layer* layer = row.layer();
4047 ASSERT(layer);
4048 if (!layer)
4049 return;
4050
4051 bool redrawEditors = false;
4052 bool regenRows = false;
4053
4054 if (layer->isVisible() != state) {
4055 layer->setVisible(state);
4056 redrawEditors = true;
4057
4058 // Regenerate rows because might change the flag of the children
4059 // (the flag is propagated to the children in m_inheritedFlags).
4060 if (layer->isGroup() && layer->isExpanded()) {
4061 regenRows = true;
4062 }
4063
4064 // Show parents too
4065 if (!row.parentVisible() && state) {
4066 layer = layer->parent();
4067 while (layer) {
4068 if (!layer->isVisible()) {
4069 layer->setVisible(true);
4070 regenRows = true;
4071 redrawEditors = true;
4072 }
4073 layer = layer->parent();
4074 }
4075 }
4076 }
4077
4078 if (regenRows) {
4079 regenerateRows();
4080 invalidate();
4081 }
4082
4083 if (redrawEditors)
4084 m_document->notifyGeneralUpdate();
4085 }
4086
setLayerEditableFlag(const layer_t l,const bool state)4087 void Timeline::setLayerEditableFlag(const layer_t l, const bool state)
4088 {
4089 Row& row = m_rows[l];
4090 Layer* layer = row.layer();
4091 ASSERT(layer);
4092 if (!layer)
4093 return;
4094
4095 bool regenRows = false;
4096
4097 if (layer->isEditable() != state) {
4098 layer->setEditable(state);
4099
4100 if (layer->isGroup() && layer->isExpanded())
4101 regenRows = true;
4102
4103 // Make parents editable too
4104 if (!row.parentEditable() && state) {
4105 layer = layer->parent();
4106 while (layer) {
4107 if (!layer->isEditable()) {
4108 layer->setEditable(true);
4109 regenRows = true;
4110 }
4111 layer = layer->parent();
4112 }
4113 }
4114 }
4115
4116 if (regenRows) {
4117 regenerateRows();
4118 invalidate();
4119 }
4120 }
4121
setLayerContinuousFlag(const layer_t l,const bool state)4122 void Timeline::setLayerContinuousFlag(const layer_t l, const bool state)
4123 {
4124 Layer* layer = m_rows[l].layer();
4125 ASSERT(layer);
4126 if (!layer)
4127 return;
4128
4129 if (layer->isImage() && layer->isContinuous() != state) {
4130 layer->setContinuous(state);
4131 invalidate();
4132 }
4133 }
4134
setLayerCollapsedFlag(const layer_t l,const bool state)4135 void Timeline::setLayerCollapsedFlag(const layer_t l, const bool state)
4136 {
4137 Layer* layer = m_rows[l].layer();
4138 ASSERT(layer);
4139 if (!layer)
4140 return;
4141
4142 if (layer->isGroup() && layer->isCollapsed() != state) {
4143 layer->setCollapsed(state);
4144
4145 regenerateRows();
4146 invalidate();
4147 }
4148 }
4149
4150 } // namespace app
4151