Add ability to select timeline + color bar at the same time (related to #1741)

This commit is contained in:
David Capello 2018-06-05 13:11:29 -03:00
parent f29ead0f1d
commit 05f6aec8b2
16 changed files with 147 additions and 62 deletions

View File

@ -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*>(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;
}

View File

@ -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;

View File

@ -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<void>(&ColorBar::onFgColorChangeFromPreferences, this));
m_bgConn = Preferences::instance().colorBar.bgColor.AfterChange.connect(base::Bind<void>(&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<void>(&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<Timeline*>(element) &&
msg && (msg->ctrlPressed() || msg->shiftPressed()))
return;
if (element != this)
m_paletteView.deselect();
}
bool ColorBar::onCanCut(Context* ctx)

View File

@ -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();

View File

@ -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
}

View File

@ -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;

View File

@ -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)

View File

@ -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 <vector>
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);

View File

@ -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;

View File

@ -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)

View File

@ -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<void()> FocusEnter;
obs::signal<void(ui::Message*)> FocusOrClick;
protected:
bool onProcessMessage(ui::Message* msg) override;

View File

@ -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<Workspace*>(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();
}
}
}

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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.