From 05f6aec8b216d6a74accc37de8cf4caf2c6daa3f Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 5 Jun 2018 13:11:29 -0300 Subject: [PATCH] Add ability to select timeline + color bar at the same time (related to #1741) --- src/app/document_range.cpp | 54 +++++++++++++++++++++++++------- src/app/document_range.h | 19 ++++++----- src/app/ui/color_bar.cpp | 19 +++++++---- src/app/ui/color_bar.h | 5 +-- src/app/ui/document_view.cpp | 3 +- src/app/ui/document_view.h | 5 +-- src/app/ui/input_chain.cpp | 22 +++++++------ src/app/ui/input_chain.h | 9 ++++-- src/app/ui/input_chain_element.h | 9 ++++-- src/app/ui/palette_view.cpp | 10 ++++-- src/app/ui/palette_view.h | 4 +-- src/app/ui/timeline/timeline.cpp | 32 ++++++++++++++----- src/app/ui/timeline/timeline.h | 4 ++- src/app/ui/workspace.cpp | 7 +++-- src/app/ui/workspace.h | 5 +-- src/app/ui_context.cpp | 2 +- 16 files changed, 147 insertions(+), 62 deletions(-) diff --git a/src/app/document_range.cpp b/src/app/document_range.cpp index 0894aaa26..5c9912640 100644 --- a/src/app/document_range.cpp +++ b/src/app/document_range.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -21,6 +21,7 @@ using namespace doc; DocumentRange::DocumentRange() : m_type(kNone) + , m_flags(m_type) , m_selectingFromLayer(nullptr) , m_selectingFromFrame(-1) { @@ -28,6 +29,7 @@ DocumentRange::DocumentRange() DocumentRange::DocumentRange(Cel* cel) : m_type(kCels) + , m_flags(m_type) , m_selectingFromLayer(nullptr) , m_selectingFromFrame(-1) { @@ -38,6 +40,7 @@ DocumentRange::DocumentRange(Cel* cel) void DocumentRange::clearRange() { m_type = kNone; + m_flags = kNone; m_selectedLayers.clear(); m_selectedFrames.clear(); } @@ -45,6 +48,7 @@ void DocumentRange::clearRange() void DocumentRange::startRange(Layer* fromLayer, frame_t fromFrame, Type type) { m_type = type; + m_flags |= type; m_selectingFromLayer = fromLayer; m_selectingFromFrame = fromFrame; @@ -69,6 +73,7 @@ void DocumentRange::selectLayer(Layer* layer) { if (m_type == kNone) m_type = kLayers; + m_flags |= kLayers; m_selectedLayers.insert(layer); } @@ -77,19 +82,48 @@ void DocumentRange::selectLayers(const SelectedLayers& selLayers) { if (m_type == kNone) m_type = kLayers; + m_flags |= kLayers; for (auto layer : selLayers) m_selectedLayers.insert(layer); } -bool DocumentRange::contains(Layer* layer) const +bool DocumentRange::contains(const Layer* layer) const { if (enabled()) - return m_selectedLayers.contains(layer); + return m_selectedLayers.contains(const_cast(layer)); else return false; } +bool DocumentRange::contains(const Layer* layer, + const frame_t frame) const +{ + switch (m_type) { + case DocumentRange::kNone: + return false; + case DocumentRange::kCels: + return contains(layer) && contains(frame); + case DocumentRange::kFrames: + if (contains(frame)) { + if ((m_flags & (kCels | kLayers)) != 0) + return contains(layer); + else + return true; + } + break; + case DocumentRange::kLayers: + if (contains(layer)) { + if ((m_flags & (kCels | kFrames)) != 0) + return contains(frame); + else + return true; + } + break; + } + return false; +} + void DocumentRange::displace(layer_t layerDelta, frame_t frameDelta) { m_selectedLayers.displace(layerDelta); @@ -104,18 +138,16 @@ bool DocumentRange::convertToCels(const Sprite* sprite) case DocumentRange::kCels: break; case DocumentRange::kFrames: { - LayerList layers = sprite->allBrowsableLayers(); - ASSERT(layers.empty()); - if (!layers.empty()) { - selectLayerRange(layers.front(), layers.back()); - m_type = DocumentRange::kCels; + if ((m_flags & (kCels | kLayers)) == 0) { + for (auto layer : sprite->allBrowsableLayers()) + m_selectedLayers.insert(layer); } - else - return false; + m_type = DocumentRange::kCels; break; } case DocumentRange::kLayers: - selectFrameRange(0, sprite->lastFrame()); + if ((m_flags & (kCels | kFrames)) == 0) + selectFrameRange(0, sprite->lastFrame()); m_type = DocumentRange::kCels; break; } diff --git a/src/app/document_range.h b/src/app/document_range.h index 4328d297b..511b9ab74 100644 --- a/src/app/document_range.h +++ b/src/app/document_range.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -22,7 +22,10 @@ namespace app { class DocumentRange { public: - enum Type { kNone, kCels, kFrames, kLayers }; + enum Type { kNone = 0, + kCels = 1, + kFrames = 2, + kLayers = 4 }; DocumentRange(); DocumentRange(Cel* cel); @@ -36,13 +39,12 @@ namespace app { void displace(layer_t layerDelta, frame_t frameDelta); - bool contains(Layer* layer) const; - bool contains(frame_t frame) const { + bool contains(const Layer* layer) const; + bool contains(const frame_t frame) const { return m_selectedFrames.contains(frame); } - bool contains(Layer* layer, frame_t frame) const { - return contains(layer) && contains(frame); - } + bool contains(const Layer* layer, + const frame_t frame) const; void clearRange(); void startRange(Layer* fromLayer, frame_t fromFrame, Type type); @@ -66,7 +68,8 @@ namespace app { void selectLayerRange(Layer* fromLayer, Layer* toLayer); void selectFrameRange(frame_t fromFrame, frame_t toFrame); - Type m_type; + Type m_type; // Last used type of the range + int m_flags; // All used types in startRange() SelectedLayers m_selectedLayers; SelectedFrames m_selectedFrames; Layer* m_selectingFromLayer; diff --git a/src/app/ui/color_bar.cpp b/src/app/ui/color_bar.cpp index 9a39a47de..faebe62dd 100644 --- a/src/app/ui/color_bar.cpp +++ b/src/app/ui/color_bar.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2017 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -45,6 +45,7 @@ #include "app/ui/palette_popup.h" #include "app/ui/skin/skin_theme.h" #include "app/ui/status_bar.h" +#include "app/ui/timeline/timeline.h" #include "app/ui_context.h" #include "app/ui_context.h" #include "app/util/clipboard.h" @@ -271,7 +272,7 @@ ColorBar::ColorBar(int align) m_afterCmdConn = UIContext::instance()->AfterCommandExecution.connect(&ColorBar::onAfterExecuteCommand, this); m_fgConn = Preferences::instance().colorBar.fgColor.AfterChange.connect(base::Bind(&ColorBar::onFgColorChangeFromPreferences, this)); m_bgConn = Preferences::instance().colorBar.bgColor.AfterChange.connect(base::Bind(&ColorBar::onBgColorChangeFromPreferences, this)); - m_paletteView.FocusEnter.connect(&ColorBar::onFocusPaletteView, this); + m_paletteView.FocusOrClick.connect(&ColorBar::onFocusPaletteView, this); m_appPalChangeConn = App::instance()->PaletteChange.connect(&ColorBar::onAppPaletteChange, this); KeyboardShortcuts::instance()->UserChange.connect( base::Bind(&ColorBar::setupTooltips, this, tooltipManager)); @@ -451,9 +452,9 @@ void ColorBar::onAppPaletteChange() updateWarningIcon(m_bgColor.getColor(), m_bgWarningIcon); } -void ColorBar::onFocusPaletteView() +void ColorBar::onFocusPaletteView(ui::Message* msg) { - App::instance()->inputChain().prioritize(this); + App::instance()->inputChain().prioritize(this, msg); } void ColorBar::onBeforeExecuteCommand(CommandExecutionEvent& ev) @@ -967,9 +968,15 @@ void ColorBar::hideRemap() layout(); } -void ColorBar::onNewInputPriority(InputChainElement* element) +void ColorBar::onNewInputPriority(InputChainElement* element, + const ui::Message* msg) { - m_paletteView.deselect(); + if (dynamic_cast(element) && + msg && (msg->ctrlPressed() || msg->shiftPressed())) + return; + + if (element != this) + m_paletteView.deselect(); } bool ColorBar::onCanCut(Context* ctx) diff --git a/src/app/ui/color_bar.h b/src/app/ui/color_bar.h index 298c4ec76..17ddb7401 100644 --- a/src/app/ui/color_bar.h +++ b/src/app/ui/color_bar.h @@ -90,7 +90,8 @@ namespace app { void onGeneralUpdate(doc::DocumentEvent& ev) override; // InputChainElement impl - void onNewInputPriority(InputChainElement* element) override; + void onNewInputPriority(InputChainElement* element, + const ui::Message* msg) override; bool onCanCut(Context* ctx) override; bool onCanCopy(Context* ctx) override; bool onCanPaste(Context* ctx) override; @@ -105,7 +106,7 @@ namespace app { protected: void onAppPaletteChange(); - void onFocusPaletteView(); + void onFocusPaletteView(ui::Message* msg); void onBeforeExecuteCommand(CommandExecutionEvent& ev); void onAfterExecuteCommand(CommandExecutionEvent& ev); void onPaletteButtonClick(); diff --git a/src/app/ui/document_view.cpp b/src/app/ui/document_view.cpp index 8ca796c51..927a3e8c1 100644 --- a/src/app/ui/document_view.cpp +++ b/src/app/ui/document_view.cpp @@ -455,7 +455,8 @@ void DocumentView::onLayerRestacked(doc::DocumentEvent& ev) m_editor->invalidate(); } -void DocumentView::onNewInputPriority(InputChainElement* element) +void DocumentView::onNewInputPriority(InputChainElement* element, + const ui::Message* msg) { // Do nothing } diff --git a/src/app/ui/document_view.h b/src/app/ui/document_view.h index 02ac5de8b..abc63be62 100644 --- a/src/app/ui/document_view.h +++ b/src/app/ui/document_view.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -84,7 +84,8 @@ namespace app { void onLayerRestacked(doc::DocumentEvent& ev) override; // InputChainElement impl - void onNewInputPriority(InputChainElement* element) override; + void onNewInputPriority(InputChainElement* element, + const ui::Message* msg) override; bool onCanCut(Context* ctx) override; bool onCanCopy(Context* ctx) override; bool onCanPaste(Context* ctx) override; diff --git a/src/app/ui/input_chain.cpp b/src/app/ui/input_chain.cpp index 66d595b53..1e1955fbd 100644 --- a/src/app/ui/input_chain.cpp +++ b/src/app/ui/input_chain.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -16,19 +16,23 @@ namespace app { -void InputChain::prioritize(InputChainElement* element) +void InputChain::prioritize(InputChainElement* element, + const ui::Message* msg) { - if (!m_elements.empty() && m_elements.front() == element) - return; + const bool alreadyInFront = + (!m_elements.empty() && m_elements.front() == element); - auto it = std::find(m_elements.begin(), m_elements.end(), element); - if (it != m_elements.end()) - m_elements.erase(it); + if (!alreadyInFront) { + auto it = std::find(m_elements.begin(), m_elements.end(), element); + if (it != m_elements.end()) + m_elements.erase(it); + } for (auto e : m_elements) - e->onNewInputPriority(element); + e->onNewInputPriority(element, msg); - m_elements.insert(m_elements.begin(), element); + if (!alreadyInFront) + m_elements.insert(m_elements.begin(), element); } bool InputChain::canCut(Context* ctx) diff --git a/src/app/ui/input_chain.h b/src/app/ui/input_chain.h index 9645638ac..2447b74cb 100644 --- a/src/app/ui/input_chain.h +++ b/src/app/ui/input_chain.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -10,6 +10,10 @@ #include +namespace ui { + class Message; +} + namespace app { class Context; @@ -21,7 +25,8 @@ namespace app { // user focus, they call InputChain::prioritize(). class InputChain { public: - void prioritize(InputChainElement* element); + void prioritize(InputChainElement* element, + const ui::Message* msg); bool canCut(Context* ctx); bool canCopy(Context* ctx); diff --git a/src/app/ui/input_chain_element.h b/src/app/ui/input_chain_element.h index 76bc133a7..3a96135a5 100644 --- a/src/app/ui/input_chain_element.h +++ b/src/app/ui/input_chain_element.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -8,6 +8,10 @@ #define APP_INPUT_CHAIN_ELEMENT_H_INCLUDED #pragma once +namespace ui { + class Message; +} + namespace app { class Context; @@ -17,7 +21,8 @@ namespace app { virtual ~InputChainElement() { } // Called when a new element has priorty in the chain. - virtual void onNewInputPriority(InputChainElement* element) = 0; + virtual void onNewInputPriority(InputChainElement* element, + const ui::Message* msg) = 0; virtual bool onCanCut(Context* ctx) = 0; virtual bool onCanCopy(Context* ctx) = 0; diff --git a/src/app/ui/palette_view.cpp b/src/app/ui/palette_view.cpp index 9c77bc36a..660249ff1 100644 --- a/src/app/ui/palette_view.cpp +++ b/src/app/ui/palette_view.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2017 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -276,7 +276,7 @@ bool PaletteView::onProcessMessage(Message* msg) switch (msg->type()) { case kFocusEnterMessage: - FocusEnter(); + FocusOrClick(msg); break; case kKeyDownMessage: @@ -291,6 +291,10 @@ bool PaletteView::onProcessMessage(Message* msg) case Hit::COLOR: m_state = State::SELECTING_COLOR; + + // As we can ctrl+click color bar + timeline, now we have to + // re-prioritize the color bar on each click. + FocusOrClick(msg); break; case Hit::OUTLINE: @@ -322,7 +326,7 @@ bool PaletteView::onProcessMessage(Message* msg) (msg->type() == kMouseDownMessage) || ((buttons & kButtonMiddle) == kButtonMiddle))) { if ((buttons & kButtonMiddle) == 0) { - if (!msg->ctrlPressed()) + if (!msg->ctrlPressed() && !msg->shiftPressed()) deselect(); if (msg->type() == kMouseMoveMessage) diff --git a/src/app/ui/palette_view.h b/src/app/ui/palette_view.h index 3e21a9c1c..773609e37 100644 --- a/src/app/ui/palette_view.h +++ b/src/app/ui/palette_view.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2017 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -82,7 +82,7 @@ namespace app { void pasteFromClipboard(); void discardClipboardSelection(); - obs::signal FocusEnter; + obs::signal FocusOrClick; protected: bool onProcessMessage(ui::Message* msg) override; diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index d3cd16f56..511c9edf6 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -565,7 +565,7 @@ bool Timeline::onProcessMessage(Message* msg) switch (msg->type()) { case kFocusEnterMessage: - App::instance()->inputChain().prioritize(this); + App::instance()->inputChain().prioritize(this, msg); break; case kTimerMessage: @@ -610,6 +610,10 @@ bool Timeline::onProcessMessage(Message* msg) return true; } + // As we can ctrl+click color bar + timeline, now we have to + // re-prioritize timeline on each click. + App::instance()->inputChain().prioritize(this, msg); + // Update hot part (as the user might have left clicked with // Ctrl on OS X, which it's converted to a right-click and it's // interpreted as other action by the Timeline::hitTest()) @@ -2024,8 +2028,7 @@ void Timeline::drawCel(ui::Graphics* g, layer_t layerIndex, frame_t frame, Cel* bool is_hover = (m_hot.part == PART_CEL && m_hot.layer == layerIndex && m_hot.frame == frame); - const bool is_active = (isLayerActive(layerIndex) || - isFrameActive(frame)); + const bool is_active = isCelActive(layerIndex, frame); const bool is_empty = (image == nullptr); gfx::Rect bounds = getPartBounds(Hit(PART_CEL, layerIndex, frame)); gfx::Rect full_bounds = bounds; @@ -3423,6 +3426,15 @@ bool Timeline::isFrameActive(const frame_t frame) const return m_range.contains(frame); } +bool Timeline::isCelActive(const layer_t layerIdx, const frame_t frame) const +{ + if (m_range.enabled()) + return m_range.contains(m_rows[layerIdx].layer(), frame); + else + return (layerIdx == getLayerIndex(m_layer) && + frame == m_frame); +} + void Timeline::dropRange(DropOp op) { bool copy = (op == Timeline::kCopy); @@ -3697,7 +3709,8 @@ int Timeline::topHeight() const return h; } -void Timeline::onNewInputPriority(InputChainElement* element) +void Timeline::onNewInputPriority(InputChainElement* element, + const ui::Message* msg) { // It looks like the user wants to execute commands targetting the // ColorBar instead of the Timeline. Here we disable the selected @@ -3709,10 +3722,15 @@ void Timeline::onNewInputPriority(InputChainElement* element) // That is why we don't disable the range in this case. Workspace* workspace = dynamic_cast(element); if (!workspace) { - if (m_rangeLocks == 0) - m_range.clearRange(); + // With Ctrl or Shift we can combine ColorBar selection + Timeline + // selection. + if (msg && (msg->ctrlPressed() || msg->shiftPressed())) + return; - invalidate(); + if (element != this && m_rangeLocks == 0) { + m_range.clearRange(); + invalidate(); + } } } diff --git a/src/app/ui/timeline/timeline.h b/src/app/ui/timeline/timeline.h index a991ecb6f..fd9d2fdcf 100644 --- a/src/app/ui/timeline/timeline.h +++ b/src/app/ui/timeline/timeline.h @@ -147,7 +147,8 @@ namespace app { void onDestroyEditor(Editor* editor) override; // InputChainElement impl - void onNewInputPriority(InputChainElement* element) override; + void onNewInputPriority(InputChainElement* element, + const ui::Message* msg) override; bool onCanCut(Context* ctx) override; bool onCanCopy(Context* ctx) override; bool onCanPaste(Context* ctx) override; @@ -283,6 +284,7 @@ namespace app { layer_t getLayerIndex(const Layer* layer) const; bool isLayerActive(const layer_t layerIdx) const; bool isFrameActive(const frame_t frame) const; + bool isCelActive(const layer_t layerIdx, const frame_t frame) const; void updateStatusBar(ui::Message* msg); void updateDropRange(const gfx::Point& pt); void clearClipboardRange(); diff --git a/src/app/ui/workspace.cpp b/src/app/ui/workspace.cpp index dfced1036..3f4a03587 100644 --- a/src/app/ui/workspace.cpp +++ b/src/app/ui/workspace.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2017 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -303,12 +303,13 @@ WorkspaceTabs* Workspace::getTabsAt(const gfx::Point& pos) return nullptr; } -void Workspace::onNewInputPriority(InputChainElement* newElement) +void Workspace::onNewInputPriority(InputChainElement* newElement, + const ui::Message* msg) { WorkspaceView* view = activeView(); InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr); if (activeElement) - activeElement->onNewInputPriority(newElement); + activeElement->onNewInputPriority(newElement, msg); } bool Workspace::onCanCut(Context* ctx) diff --git a/src/app/ui/workspace.h b/src/app/ui/workspace.h index dbf5128be..55b3040d8 100644 --- a/src/app/ui/workspace.h +++ b/src/app/ui/workspace.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2017 David Capello +// Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -60,7 +60,8 @@ namespace app { DropViewAtResult dropViewAt(const gfx::Point& pos, WorkspaceView* view, bool clone); // InputChainElement impl - void onNewInputPriority(InputChainElement* element) override; + void onNewInputPriority(InputChainElement* element, + const ui::Message* msg) override; bool onCanCut(Context* ctx) override; bool onCanCopy(Context* ctx) override; bool onCanPaste(Context* ctx) override; diff --git a/src/app/ui_context.cpp b/src/app/ui_context.cpp index 6775e662f..d75034d31 100644 --- a/src/app/ui_context.cpp +++ b/src/app/ui_context.cpp @@ -79,7 +79,7 @@ void UIContext::setActiveView(DocumentView* docView) MainWindow* mainWin = App::instance()->mainWindow(); // Prioritize workspace for user input. - App::instance()->inputChain().prioritize(mainWin->getWorkspace()); + App::instance()->inputChain().prioritize(mainWin->getWorkspace(), nullptr); // Do nothing cases: 1) the view is already selected, or 2) the view // is the a preview.