Add automatic cel guides when Ctrl/Cmd is pressed

This commit is contained in:
David Capello 2017-03-23 00:57:21 -03:00
parent dfcfab3bb3
commit 37748c3783
10 changed files with 287 additions and 61 deletions

View File

@ -174,6 +174,7 @@ Editor::Editor(Document* document, EditorFlags flags)
, m_secondaryButton(false)
, m_aniSpeed(1.0)
, m_isPlaying(false)
, m_showGuidesThisCel(nullptr)
{
m_proj.setPixelRatio(m_sprite->pixelRatio());
@ -343,7 +344,9 @@ void Editor::setLayer(const Layer* layer)
// If the user want to see the active layer edges...
m_docPref.show.layerEdges() ||
// If there is a different opacity for nonactive-layers
Preferences::instance().experimental.nonactiveLayersOpacity() < 255) {
Preferences::instance().experimental.nonactiveLayersOpacity() < 255 ||
// If the automatic cel guides are visible...
m_showGuidesThisCel) {
// We've to redraw the whole editor
invalidate();
}
@ -793,22 +796,18 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
enclosingRect.x, enclosingRect.y+enclosingRect.h, enclosingRect.w);
}
// Draw active cel edges
if (m_docPref.show.layerEdges() &&
// Draw active layer/cel edges
bool showGuidesThisCel = this->showAutoCelGuides();
if ((m_docPref.show.layerEdges() || showGuidesThisCel) &&
// Show layer edges only on "standby" like states where brush
// preview is shown (e.g. with this we avoid to showing the
// edges in states like DrawingState, etc.).
m_state->requireBrushPreview()) {
Cel* cel = (m_layer ? m_layer->cel(m_frame): nullptr);
if (cel) {
gfx::Rect layerEdges;
if (m_layer->isReference()) {
layerEdges = editorToScreenF(cel->boundsF()).offset(gfx::PointF(-bounds().origin()));
}
else {
layerEdges = editorToScreen(cel->bounds()).offset(-bounds().origin());
}
g->drawRect(theme->colors.editorLayerEdges(), layerEdges);
drawCelBounds(g, cel);
if (m_showGuidesThisCel != cel)
drawCelGuides(g, cel, m_showGuidesThisCel);
}
}
@ -859,7 +858,9 @@ void Editor::drawMask(Graphics* g)
int y = m_padding.y;
for (const auto& seg : *m_document->getMaskBoundaries()) {
CheckedDrawMode checked(g, m_antsOffset);
CheckedDrawMode checked(g, m_antsOffset,
gfx::rgba(0, 0, 0, 255),
gfx::rgba(255, 255, 255, 255));
gfx::Rect bounds = m_proj.apply(seg.bounds());
if (m_proj.scaleX() >= 1.0) {
@ -1018,6 +1019,154 @@ void Editor::drawSlices(ui::Graphics* g)
}
}
void Editor::drawCelBounds(ui::Graphics* g, const Cel* cel)
{
g->drawRect(SkinTheme::instance()->colors.editorLayerEdges(),
getCelScreenBounds(cel));
}
void Editor::drawCelGuides(ui::Graphics* g, const Cel* cel, const Cel* mouseCel)
{
gfx::Rect
sprCelBounds = cel->bounds(),
scrCelBounds = getCelScreenBounds(cel),
scrCmpBounds, sprCmpBounds;
if (mouseCel) {
scrCmpBounds = getCelScreenBounds(mouseCel);
sprCmpBounds = mouseCel->bounds();
drawCelBounds(g, mouseCel);
}
// Use whole canvas
else {
sprCmpBounds = m_sprite->bounds();
scrCmpBounds = editorToScreen(sprCmpBounds).offset(gfx::Point(-bounds().origin()));
}
const int midX = scrCelBounds.x+scrCelBounds.w/2;
const int midY = scrCelBounds.y+scrCelBounds.h/2;
if (sprCelBounds.x2() < sprCmpBounds.x) {
drawCelHGuide(g,
sprCelBounds.x2(), sprCmpBounds.x,
scrCelBounds.x2(), scrCmpBounds.x, midY,
scrCelBounds, scrCmpBounds, scrCmpBounds.x);
}
else if (sprCelBounds.x > sprCmpBounds.x2()) {
drawCelHGuide(g,
sprCmpBounds.x2(), sprCelBounds.x,
scrCmpBounds.x2(), scrCelBounds.x, midY,
scrCelBounds, scrCmpBounds, scrCmpBounds.x2()-1);
}
else {
if (sprCelBounds.x != sprCmpBounds.x &&
sprCelBounds.x2() != sprCmpBounds.x) {
drawCelHGuide(g,
sprCmpBounds.x, sprCelBounds.x,
scrCmpBounds.x, scrCelBounds.x, midY,
scrCelBounds, scrCmpBounds, scrCmpBounds.x);
}
if (sprCelBounds.x != sprCmpBounds.x2() &&
sprCelBounds.x2() != sprCmpBounds.x2()) {
drawCelHGuide(g,
sprCmpBounds.x2(), sprCelBounds.x2(),
scrCmpBounds.x2(), scrCelBounds.x2(), midY,
scrCelBounds, scrCmpBounds, scrCmpBounds.x2()-1);
}
}
if (sprCelBounds.y2() < sprCmpBounds.y) {
drawCelVGuide(g,
sprCelBounds.y2(), sprCmpBounds.y,
scrCelBounds.y2(), scrCmpBounds.y, midX,
scrCelBounds, scrCmpBounds, scrCmpBounds.y);
}
else if (sprCelBounds.y > sprCmpBounds.y2()) {
drawCelVGuide(g,
sprCmpBounds.y2(), sprCelBounds.y,
scrCmpBounds.y2(), scrCelBounds.y, midX,
scrCelBounds, scrCmpBounds, scrCmpBounds.y2()-1);
}
else {
if (sprCelBounds.y != sprCmpBounds.y &&
sprCelBounds.y2() != sprCmpBounds.y) {
drawCelVGuide(g,
sprCmpBounds.y, sprCelBounds.y,
scrCmpBounds.y, scrCelBounds.y, midX,
scrCelBounds, scrCmpBounds, scrCmpBounds.y);
}
if (sprCelBounds.y != sprCmpBounds.y2() &&
sprCelBounds.y2() != sprCmpBounds.y2()) {
drawCelVGuide(g,
sprCmpBounds.y2(), sprCelBounds.y2(),
scrCmpBounds.y2(), scrCelBounds.y2(), midX,
scrCelBounds, scrCmpBounds, scrCmpBounds.y2()-1);
}
}
}
void Editor::drawCelHGuide(ui::Graphics* g,
const int sprX1, const int sprX2,
const int scrX1, const int scrX2, const int scrY,
const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
const int dottedX)
{
auto color = SkinTheme::instance()->colors.editorLayerEdges();
g->drawHLine(color, scrX1, scrY, scrX2 - scrX1);
// Vertical guide to touch the horizontal line
{
CheckedDrawMode checked(g, 0, color, gfx::ColorNone);
if (scrY < scrCmpBounds.y)
g->drawVLine(color, dottedX, scrCelBounds.y, scrCmpBounds.y - scrCelBounds.y);
else if (scrY > scrCmpBounds.y2())
g->drawVLine(color, dottedX, scrCmpBounds.y2(), scrCelBounds.y2() - scrCmpBounds.y2());
}
auto text = base::convert_to<std::string>(ABS(sprX2 - sprX1)) + "px";
const int textW = Graphics::measureUITextLength(text, font());
g->drawText(text,
gfx::rgba(255, 255, 255, 255), color,
gfx::Point((scrX1+scrX2)/2-textW/2, scrY-textHeight()));
}
void Editor::drawCelVGuide(ui::Graphics* g,
const int sprY1, const int sprY2,
const int scrY1, const int scrY2, const int scrX,
const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
const int dottedY)
{
auto color = SkinTheme::instance()->colors.editorLayerEdges();
g->drawVLine(color, scrX, scrY1, scrY2 - scrY1);
// Horizontal guide to touch the vertical line
{
CheckedDrawMode checked(g, 0, color, gfx::ColorNone);
if (scrX < scrCmpBounds.x)
g->drawHLine(color, scrCelBounds.x, dottedY, scrCmpBounds.x - scrCelBounds.x);
else if (scrX > scrCmpBounds.x2())
g->drawHLine(color, scrCmpBounds.x2(), dottedY, scrCelBounds.x2() - scrCmpBounds.x2());
}
auto text = base::convert_to<std::string>(ABS(sprY2 - sprY1)) + "px";
g->drawText(text,
gfx::rgba(255, 255, 255, 255), color,
gfx::Point(scrX, (scrY1+scrY2)/2-textHeight()/2));
}
gfx::Rect Editor::getCelScreenBounds(const Cel* cel)
{
gfx::Rect layerEdges;
if (m_layer->isReference()) {
layerEdges = editorToScreenF(cel->boundsF()).offset(gfx::PointF(-bounds().origin()));
}
else {
layerEdges = editorToScreen(cel->bounds()).offset(-bounds().origin());
}
return layerEdges;
}
void Editor::flashCurrentLayer()
{
if (!Preferences::instance().experimental.flashLayer())
@ -1407,6 +1556,7 @@ bool Editor::onProcessMessage(Message* msg)
m_oldPos = mouseMsg->position();
updateToolByTipProximity(mouseMsg->pointerType());
updateAutoCelGuides(msg);
// Only when we right-click with the regular "paint bg-color
// right-click mode" we will mark indicate that the secondary
@ -1433,6 +1583,7 @@ bool Editor::onProcessMessage(Message* msg)
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
updateToolByTipProximity(mouseMsg->pointerType());
updateAutoCelGuides(msg);
return m_state->onMouseMove(this, static_cast<MouseMessage*>(msg));
}
@ -1445,6 +1596,7 @@ bool Editor::onProcessMessage(Message* msg)
bool result = m_state->onMouseUp(this, mouseMsg);
updateToolByTipProximity(mouseMsg->pointerType());
updateAutoCelGuides(msg);
if (!hasCapture()) {
App::instance()->activeToolManager()->releaseButtons();
@ -1486,6 +1638,7 @@ bool Editor::onProcessMessage(Message* msg)
bool used = m_state->onKeyDown(this, static_cast<KeyMessage*>(msg));
updateToolLoopModifiersIndicators();
updateAutoCelGuides(msg);
if (hasMouse()) {
updateQuicktool();
setCursor(ui::get_mouse_position());
@ -1502,6 +1655,7 @@ bool Editor::onProcessMessage(Message* msg)
bool used = m_state->onKeyUp(this, static_cast<KeyMessage*>(msg));
updateToolLoopModifiersIndicators();
updateAutoCelGuides(msg);
if (hasMouse()) {
updateQuicktool();
setCursor(ui::get_mouse_position());
@ -2103,4 +2257,38 @@ void Editor::invalidateIfActive()
invalidate();
}
bool Editor::showAutoCelGuides()
{
return
(getCurrentEditorInk()->isCelMovement() &&
m_customizationDelegate &&
int(m_customizationDelegate->getPressedKeyAction(KeyContext::MoveTool) & KeyAction::AutoSelectLayer));
}
void Editor::updateAutoCelGuides(ui::Message* msg)
{
Cel* oldShowGuidesThisCel = m_showGuidesThisCel;
// Check if the user is pressing the Ctrl or Cmd key on move
// tool to show automatic guides.
if (showAutoCelGuides() &&
m_state->requireBrushPreview()) {
ui::MouseMessage* mouseMsg = dynamic_cast<ui::MouseMessage*>(msg);
ColorPicker picker;
picker.pickColor(getSite(),
screenToEditorF(mouseMsg ? mouseMsg->position():
ui::get_mouse_position()),
m_proj, ColorPicker::FromComposition);
m_showGuidesThisCel = (picker.layer() ? picker.layer()->cel(m_frame):
nullptr);
}
else {
m_showGuidesThisCel = nullptr;
}
if (m_showGuidesThisCel != oldShowGuidesThisCel)
invalidate();
}
} // namespace app

View File

@ -280,6 +280,19 @@ namespace app {
void drawGrid(ui::Graphics* g, const gfx::Rect& spriteBounds, const gfx::Rect& gridBounds,
const app::Color& color, int alpha);
void drawSlices(ui::Graphics* g);
void drawCelBounds(ui::Graphics* g, const Cel* cel);
void drawCelGuides(ui::Graphics* g, const Cel* cel, const Cel* mouseCel);
void drawCelHGuide(ui::Graphics* g,
const int sprX1, const int sprX2,
const int scrX1, const int scrX2, const int scrY,
const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
const int dottedX);
void drawCelVGuide(ui::Graphics* g,
const int sprY1, const int sprY2,
const int scrY1, const int scrY2, const int scrX,
const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
const int dottedY);
gfx::Rect getCelScreenBounds(const Cel* cel);
void setCursor(const gfx::Point& mouseScreenPos);
@ -291,6 +304,8 @@ namespace app {
gfx::Point calcExtraPadding(const render::Projection& proj);
void invalidateIfActive();
bool showAutoCelGuides();
void updateAutoCelGuides(ui::Message* msg);
// Stack of states. The top element in the stack is the current state (m_state).
EditorStatesHistory m_statesHistory;
@ -357,6 +372,10 @@ namespace app {
double m_aniSpeed;
bool m_isPlaying;
// The Cel that is above the mouse if the Ctrl (or Cmd) key is
// pressed (move key).
Cel* m_showGuidesThisCel;
static doc::ImageBufferPtr m_renderBuffer;
// The render engine must be shared between all editors so when a

View File

@ -577,7 +577,9 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
IntersectClip clip(g, clipR);
if (clip) {
CheckedDrawMode checked(g, getMarchingAntsOffset());
CheckedDrawMode checked(g, getMarchingAntsOffset(),
gfx::rgba(0, 0, 0, 255),
gfx::rgba(255, 255, 255, 255));
g->drawRect(gfx::rgba(0, 0, 0), box);
}
}

View File

@ -1539,7 +1539,9 @@ void Timeline::drawClipboardRange(ui::Graphics* g)
if (!m_clipboard_timer.isRunning())
m_clipboard_timer.start();
CheckedDrawMode checked(g, m_offset_count);
CheckedDrawMode checked(g, m_offset_count,
gfx::rgba(0, 0, 0, 255),
gfx::rgba(255, 255, 255, 255));
g->drawRect(gfx::rgba(0, 0, 0),
getRangeBounds(clipboard_range));
}

View File

@ -17,40 +17,6 @@
#include <allegro.h>
#include <allegro/internal/aintern.h>
namespace {
void checked_mode(int offset)
{
static BITMAP* pattern = NULL;
int x, y, fg, bg;
if (offset < 0) {
if (pattern) {
destroy_bitmap(pattern);
pattern = NULL;
}
drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
return;
}
if (!pattern)
pattern = create_bitmap(8, 8);
bg = makecol(0, 0, 0);
fg = makecol(255, 255, 255);
offset = 7 - (offset & 7);
clear_bitmap(pattern);
for (y=0; y<8; y++)
for (x=0; x<8; x++)
putpixel(pattern, x, y, ((x+y+offset)&7) < 4 ? fg: bg);
drawing_mode(DRAW_MODE_COPY_PATTERN, pattern, 0, 0);
}
}
namespace she {
inline int to_allegro(int color_depth, gfx::Color color)
@ -77,6 +43,41 @@ inline gfx::Color from_allegro(int color_depth, int color)
(color_depth == 32 ? geta32(color): 255));
}
namespace {
void checked_mode(int offset,
const gfx::Color a = gfx::ColorNone,
const gfx::Color b = gfx::ColorNone)
{
static BITMAP* pattern = NULL;
if (offset < 0) {
if (pattern) {
destroy_bitmap(pattern);
pattern = NULL;
}
drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
return;
}
if (!pattern)
pattern = create_bitmap(8, 8);
int A = to_allegro(bitmap_color_depth(pattern), a);
int B = to_allegro(bitmap_color_depth(pattern), b);
offset = 7 - (offset & 7);
clear_bitmap(pattern);
for (int y=0; y<8; ++y)
for (int x=0; x<8; ++x)
putpixel(pattern, x, y, ((x+y+offset)&7) < 4 ? B: A);
drawing_mode(DRAW_MODE_COPY_PATTERN, pattern, 0, 0);
}
}
Alleg4Surface::Alleg4Surface(BITMAP* bmp, DestroyFlag destroy)
: m_bmp(bmp)
, m_destroy(destroy)
@ -175,12 +176,14 @@ void Alleg4Surface::unlock()
release_bitmap(m_bmp);
}
void Alleg4Surface::setDrawMode(DrawMode mode, int param)
void Alleg4Surface::setDrawMode(DrawMode mode, int param,
const gfx::Color a,
const gfx::Color b)
{
switch (mode) {
case DrawMode::Solid: checked_mode(-1); break;
case DrawMode::Xor: xor_mode(TRUE); break;
case DrawMode::Checked: checked_mode(param); break;
case DrawMode::Checked: checked_mode(param, a, b); break;
}
}

View File

@ -39,7 +39,9 @@ namespace she {
bool intersectClipRect(const gfx::Rect& rc) override;
void lock() override;
void unlock() override;
void setDrawMode(DrawMode mode, int param) override;
void setDrawMode(DrawMode mode, int param,
const gfx::Color a,
const gfx::Color b) override;
void applyScale(int scale) override;
void* nativeHandle() override;
void clear() override;

View File

@ -122,7 +122,9 @@ public:
return !m_clip.isEmpty();
}
void setDrawMode(DrawMode mode, int param) override {
void setDrawMode(DrawMode mode, int param,
const gfx::Color a,
const gfx::Color b) override {
switch (mode) {
case DrawMode::Solid:
m_paint.setBlendMode(SkBlendMode::kSrcOver);
@ -141,12 +143,12 @@ public:
{
bitmap.lockPixels();
SkPMColor bg = SkPreMultiplyARGB(255, 0, 0, 0);
SkPMColor fg = SkPreMultiplyARGB(255, 255, 255, 255);
SkPMColor A = SkPreMultiplyARGB(gfx::geta(a), gfx::getr(a), gfx::getg(a), gfx::getb(a));
SkPMColor B = SkPreMultiplyARGB(gfx::geta(b), gfx::getr(b), gfx::getg(b), gfx::getb(b));
int offset = 7 - (param & 7);
for (int y=0; y<8; y++)
for (int x=0; x<8; x++)
*bitmap.getAddr32(x, y) = (((x+y+offset)&7) < 4 ? fg: bg);
*bitmap.getAddr32(x, y) = (((x+y+offset)&7) < 4 ? B: A);
bitmap.unlockPixels();
}

View File

@ -38,7 +38,9 @@ namespace she {
virtual void setClipBounds(const gfx::Rect& rc) = 0;
virtual bool intersectClipRect(const gfx::Rect& rc) = 0;
virtual void setDrawMode(DrawMode mode, int param = 0) = 0;
virtual void setDrawMode(DrawMode mode, int param = 0,
const gfx::Color a = gfx::ColorNone,
const gfx::Color b = gfx::ColorNone) = 0;
virtual void lock() = 0;
virtual void unlock() = 0;

View File

@ -68,7 +68,9 @@ bool Graphics::intersectClipRect(const gfx::Rect& rc)
return m_surface->intersectClipRect(gfx::Rect(rc).offset(m_dx, m_dy));
}
void Graphics::setDrawMode(DrawMode mode, int param)
void Graphics::setDrawMode(DrawMode mode, int param,
const gfx::Color a,
const gfx::Color b)
{
switch (mode) {
case DrawMode::Solid:
@ -78,7 +80,7 @@ void Graphics::setDrawMode(DrawMode mode, int param)
m_surface->setDrawMode(she::DrawMode::Xor);
break;
case DrawMode::Checked:
m_surface->setDrawMode(she::DrawMode::Checked, param);
m_surface->setDrawMode(she::DrawMode::Checked, param, a, b);
break;
}
}

View File

@ -53,7 +53,9 @@ namespace ui {
void setClipBounds(const gfx::Rect& rc);
bool intersectClipRect(const gfx::Rect& rc);
void setDrawMode(DrawMode mode, int param = 0);
void setDrawMode(DrawMode mode, int param = 0,
const gfx::Color a = gfx::ColorNone,
const gfx::Color b = gfx::ColorNone);
gfx::Color getPixel(int x, int y);
void putPixel(gfx::Color color, int x, int y);
@ -167,8 +169,10 @@ namespace ui {
class CheckedDrawMode {
public:
CheckedDrawMode(Graphics* g, int param) : m_graphics(g) {
m_graphics->setDrawMode(Graphics::DrawMode::Checked, param);
CheckedDrawMode(Graphics* g, int param,
const gfx::Color a,
const gfx::Color b) : m_graphics(g) {
m_graphics->setDrawMode(Graphics::DrawMode::Checked, param, a, b);
}
~CheckedDrawMode() {