mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-30 15:32:38 +00:00
Add possibility to select the layer content with Ctrl+click (fix #1509)
This commit is contained in:
parent
d36179ae2a
commit
10590da7c1
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
131
src/app/util/layer_boundaries.cpp
Normal file
131
src/app/util/layer_boundaries.cpp
Normal 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
|
29
src/app/util/layer_boundaries.h
Normal file
29
src/app/util/layer_boundaries.h
Normal 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
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user