Add possibility to select the layer content with Ctrl+click (fix #1509)

This commit is contained in:
David Capello 2018-03-15 16:05:56 -03:00
parent d36179ae2a
commit 10590da7c1
7 changed files with 292 additions and 34 deletions

View File

@ -530,6 +530,7 @@ add_library(app-lib
util/expand_cel_canvas.cpp
util/filetoks.cpp
util/freetype_utils.cpp
util/layer_boundaries.cpp
util/msk_file.cpp
util/new_image_from_mask.cpp
util/pic_file.cpp

View File

@ -38,6 +38,7 @@
#include "app/ui/workspace.h"
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "app/util/layer_boundaries.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "base/memory.h"
@ -129,6 +130,31 @@ namespace {
}
}
bool is_copy_key_pressed(ui::Message* msg) {
return
msg->ctrlPressed() || // Ctrl is common on Windows
msg->altPressed(); // Alt is common on Mac OS X
}
bool is_select_layer_in_canvas_key_pressed(ui::Message* msg) {
#ifdef __APPLE__
return msg->cmdPressed();
#else
return msg->ctrlPressed();
#endif
}
SelectLayerBoundariesOp get_select_layer_in_canvas_op(ui::Message* msg) {
if (msg->altPressed() && msg->shiftPressed())
return SelectLayerBoundariesOp::INTERSECT;
else if (msg->shiftPressed())
return SelectLayerBoundariesOp::ADD;
else if (msg->altPressed())
return SelectLayerBoundariesOp::SUBTRACT;
else
return SelectLayerBoundariesOp::REPLACE;
}
} // anonymous namespace
Timeline::Hit::Hit(int part,
@ -607,10 +633,18 @@ bool Timeline::onProcessMessage(Message* msg)
}
case PART_ROW_TEXT: {
base::ScopedValue<bool> lock(m_fromTimeline, true, false);
layer_t old_layer = getLayerIndex(m_layer);
bool selectLayer = (mouseMsg->left() || !isLayerActive(m_clk.layer));
const layer_t old_layer = getLayerIndex(m_layer);
const bool selectLayer = (mouseMsg->left() || !isLayerActive(m_clk.layer));
const bool selectLayerInCanvas =
(m_clk.layer != -1 &&
mouseMsg->left() &&
is_select_layer_in_canvas_key_pressed(mouseMsg));
if (selectLayer) {
if (selectLayerInCanvas) {
select_layer_boundaries(m_rows[m_clk.layer].layer(), m_frame,
get_select_layer_in_canvas_op(mouseMsg));
}
else if (selectLayer) {
m_state = STATE_SELECTING_LAYERS;
if (clearRange)
m_range.clearRange();
@ -637,26 +671,37 @@ bool Timeline::onProcessMessage(Message* msg)
break;
case PART_CEL: {
base::ScopedValue<bool> lock(m_fromTimeline, true, false);
layer_t old_layer = getLayerIndex(m_layer);
bool selectCel = (mouseMsg->left()
const layer_t old_layer = getLayerIndex(m_layer);
const bool selectCel = (mouseMsg->left()
|| !isLayerActive(m_clk.layer)
|| !isFrameActive(m_clk.frame));
frame_t old_frame = m_frame;
const bool selectCelInCanvas =
(m_clk.layer != -1 &&
mouseMsg->left() &&
is_select_layer_in_canvas_key_pressed(mouseMsg));
const frame_t old_frame = m_frame;
if (selectCel) {
m_state = STATE_SELECTING_CELS;
m_range.clearRange();
m_range.startRange(m_rows[m_clk.layer].layer(),
m_clk.frame, Range::kCels);
m_startRange = m_range;
if (selectCelInCanvas) {
select_layer_boundaries(m_rows[m_clk.layer].layer(),
m_clk.frame,
get_select_layer_in_canvas_op(mouseMsg));
}
else {
if (selectCel) {
m_state = STATE_SELECTING_CELS;
m_range.clearRange();
m_range.startRange(m_rows[m_clk.layer].layer(),
m_clk.frame, Range::kCels);
m_startRange = m_range;
}
// Select the new clicked-part.
if (old_layer != m_clk.layer
|| old_frame != m_clk.frame) {
setLayer(m_rows[m_clk.layer].layer());
setFrame(m_clk.frame, true);
invalidate();
// Select the new clicked-part.
if (old_layer != m_clk.layer
|| old_frame != m_clk.frame) {
setLayer(m_rows[m_clk.layer].layer());
setFrame(m_clk.frame, true);
invalidate();
}
}
// Change the scroll to show the new selected cel.
@ -1078,7 +1123,7 @@ bool Timeline::onProcessMessage(Message* msg)
if (m_state == STATE_MOVING_RANGE &&
m_dropRange.type() != Range::kNone) {
dropRange(isCopyKeyPressed(mouseMsg) ?
dropRange(is_copy_key_pressed(mouseMsg) ?
Timeline::kCopy:
Timeline::kMove);
}
@ -1636,7 +1681,7 @@ void Timeline::setCursor(ui::Message* msg, const Hit& hit)
}
// Moving.
else if (m_state == STATE_MOVING_RANGE) {
if (isCopyKeyPressed(msg))
if (is_copy_key_pressed(msg))
ui::set_mouse_cursor(kArrowPlusCursor);
else
ui::set_mouse_cursor(kMoveCursor);
@ -2919,7 +2964,7 @@ Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
auto mouseMsg = dynamic_cast<MouseMessage*>(msg);
if (// With Ctrl and Alt key we can drag the range from any place (not necessary from the outline.
isCopyKeyPressed(msg) ||
is_copy_key_pressed(msg) ||
// Drag with right-click
(m_state == STATE_STANDBY &&
mouseMsg &&
@ -2990,7 +3035,7 @@ void Timeline::updateStatusBar(ui::Message* msg)
StatusBar* sb = StatusBar::instance();
if (m_state == STATE_MOVING_RANGE) {
const char* verb = isCopyKeyPressed(msg) ? "Copy": "Move";
const char* verb = is_copy_key_pressed(msg) ? "Copy": "Move";
switch (m_range.type()) {
@ -3465,12 +3510,6 @@ void Timeline::clearClipboardRange()
m_clipboard_timer.stop();
}
bool Timeline::isCopyKeyPressed(ui::Message* msg)
{
return msg->ctrlPressed() || // Ctrl is common on Windows
msg->altPressed(); // Alt is common on Mac OS X
}
DocumentPreferences& Timeline::docPref() const
{
return Preferences::instance().document(m_document);

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.
@ -283,8 +283,6 @@ namespace app {
void updateDropRange(const gfx::Point& pt);
void clearClipboardRange();
bool isCopyKeyPressed(ui::Message* msg);
// The layer of the bottom (e.g. Background layer)
layer_t firstLayer() const { return 0; }
// The layer of the top.

View File

@ -0,0 +1,131 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/util/layer_boundaries.h"
#include "app/cmd/set_mask.h"
#include "app/context_access.h"
#include "app/modules/gui.h"
#include "app/transaction.h"
#include "app/ui_context.h"
#include "doc/cel.h"
#include "doc/document.h"
#include "doc/image.h"
#include "doc/image_impl.h"
#include "doc/layer.h"
#include "doc/mask.h"
using namespace doc;
namespace app {
void select_layer_boundaries(Layer* layer,
const frame_t frame,
const SelectLayerBoundariesOp op)
{
Mask newMask;
const Cel* cel = layer->cel(frame);
if (cel) {
const Image* image = cel->image();
if (image) {
newMask.replace(cel->bounds());
newMask.freeze();
{
LockImageBits<BitmapTraits> maskBits(newMask.bitmap());
auto maskIt = maskBits.begin();
auto maskEnd = maskBits.end();
switch (image->pixelFormat()) {
case IMAGE_RGB: {
LockImageBits<RgbTraits> rgbBits(image);
auto rgbIt = rgbBits.begin();
#if _DEBUG
auto rgbEnd = rgbBits.end();
#endif
for (; maskIt != maskEnd; ++maskIt, ++rgbIt) {
ASSERT(rgbIt != rgbEnd);
color_t c = *rgbIt;
*maskIt = (rgba_geta(c) >= 128); // TODO configurable threshold
}
break;
}
case IMAGE_GRAYSCALE: {
LockImageBits<GrayscaleTraits> grayBits(image);
auto grayIt = grayBits.begin();
#if _DEBUG
auto grayEnd = grayBits.end();
#endif
for (; maskIt != maskEnd; ++maskIt, ++grayIt) {
ASSERT(grayIt != grayEnd);
color_t c = *grayIt;
*maskIt = (graya_geta(c) >= 128); // TODO configurable threshold
}
break;
}
case IMAGE_INDEXED: {
const doc::color_t maskColor = image->maskColor();
LockImageBits<IndexedTraits> idxBits(image);
auto idxIt = idxBits.begin();
#if _DEBUG
auto idxEnd = idxBits.end();
#endif
for (; maskIt != maskEnd; ++maskIt, ++idxIt) {
ASSERT(idxIt != idxEnd);
color_t c = *idxIt;
*maskIt = (c != maskColor);
}
break;
}
}
}
newMask.unfreeze();
}
}
ContextWriter writer(UIContext::instance());
app::Document* doc = writer.document();
Sprite* spr = layer->sprite();
ASSERT(doc == spr->document());
if (doc->isMaskVisible()) {
switch (op) {
case SelectLayerBoundariesOp::REPLACE:
// newMask is the new mask
break;
case SelectLayerBoundariesOp::ADD:
newMask.add(*doc->mask());
break;
case SelectLayerBoundariesOp::SUBTRACT: {
Mask oldMask(*doc->mask());
oldMask.subtract(newMask);
newMask.copyFrom(&oldMask); // TODO use something like std::swap()
break;
}
case SelectLayerBoundariesOp::INTERSECT:
newMask.intersect(*doc->mask());
break;
}
}
Transaction transaction(writer.context(), "Select Layer Boundaries", DoesntModifyDocument);
transaction.execute(new cmd::SetMask(doc, &newMask));
transaction.commit();
doc->resetTransformation();
doc->generateMaskBoundaries();
update_screen_for_document(doc);
}
} // namespace app

View File

@ -0,0 +1,29 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_LAYER_BOUNDARIES_H_INCLUDED
#define APP_LAYER_BOUNDARIES_H_INCLUDED
#pragma once
#include "doc/frame.h"
namespace doc {
class Layer;
}
namespace app {
enum SelectLayerBoundariesOp {
REPLACE, ADD, SUBTRACT, INTERSECT
};
void select_layer_boundaries(doc::Layer* layer,
const doc::frame_t frame,
const SelectLayerBoundariesOp op);
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2016 David Capello
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -19,6 +19,32 @@
namespace doc {
namespace {
template<typename Func>
void for_each_mask_pixel(Mask& a, const Mask& b, Func f) {
a.reserve(b.bounds());
{
LockImageBits<BitmapTraits> aBits(a.bitmap());
auto aIt = aBits.begin();
auto bounds = a.bounds();
for (int y=0; y<bounds.h; ++y) {
for (int x=0; x<bounds.w; ++x, ++aIt) {
color_t aColor = *aIt;
color_t bColor = (b.containsPoint(bounds.x+x,
bounds.y+y) ? 1: 0);
*aIt = f(aColor, bColor);
}
}
}
a.shrink();
}
} // namespace namespace
Mask::Mask()
: Object(ObjectType::Mask)
{
@ -132,6 +158,36 @@ void Mask::replace(const gfx::Rect& bounds)
clear_image(m_bitmap.get(), 1);
}
void Mask::add(const doc::Mask& mask)
{
for_each_mask_pixel(
*this, mask,
[](color_t a, color_t b) -> color_t {
return a | b;
});
}
void Mask::subtract(const doc::Mask& mask)
{
for_each_mask_pixel(
*this, mask,
[](color_t a, color_t b) -> color_t {
if (a)
return a - b;
else
return 0;
});
}
void Mask::intersect(const doc::Mask& mask)
{
for_each_mask_pixel(
*this, mask,
[](color_t a, color_t b) -> color_t {
return a & b;
});
}
void Mask::add(const gfx::Rect& bounds)
{
if (m_freeze_count == 0)

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -79,10 +79,14 @@ namespace doc {
// Inverts the mask.
void invert();
// Adds the specified rectangle in the mask/selection
void add(const doc::Mask& mask);
void subtract(const doc::Mask& mask);
void intersect(const doc::Mask& mask);
void add(const gfx::Rect& bounds);
void subtract(const gfx::Rect& bounds);
void intersect(const gfx::Rect& bounds);
void byColor(const Image* image, int color, int fuzziness);
void crop(const Image* image);