Add support to drag-and-drop colors in ColorBar/PaletteView (fix #37)

This commit is contained in:
David Capello 2015-03-23 12:25:32 -03:00
parent d41b08caa8
commit d14e4e8896
12 changed files with 263 additions and 37 deletions

View File

@ -20,11 +20,11 @@ namespace cmd {
using namespace doc;
SetPalette::SetPalette(Sprite* sprite, frame_t frame, Palette* newPalette)
SetPalette::SetPalette(Sprite* sprite, frame_t frame, const Palette* newPalette)
: WithSprite(sprite)
, m_frame(frame)
{
Palette* curPalette = sprite->palette(frame);
const Palette* curPalette = sprite->palette(frame);
// Check differences between current sprite palette and the new one
m_from = m_to = -1;

View File

@ -28,7 +28,7 @@ namespace cmd {
class SetPalette : public Cmd
, public WithSprite {
public:
SetPalette(Sprite* sprite, frame_t frame, Palette* newPalette);
SetPalette(Sprite* sprite, frame_t frame, const Palette* newPalette);
protected:
void onExecute() override;

View File

@ -11,14 +11,19 @@
#include "app/ui/color_bar.h"
#include "app/cmd/set_palette.h"
#include "app/color.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/ini_file.h"
#include "app/modules/gui.h"
#include "app/transaction.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "doc/image.h"
#include "doc/palette.h"
@ -68,7 +73,7 @@ ColorBar* ColorBar::m_instance = NULL;
ColorBar::ColorBar(int align)
: Box(align)
, m_paletteButton("Edit Palette")
, m_paletteView(false)
, m_paletteView(true, this)
, m_fgColor(app::Color::fromRgb(255, 255, 255), IMAGE_RGB)
, m_bgColor(app::Color::fromRgb(0, 0, 0), IMAGE_RGB)
, m_lock(false)
@ -97,7 +102,6 @@ ColorBar::ColorBar(int align)
addChild(&m_fgColor);
addChild(&m_bgColor);
m_paletteView.IndexChange.connect(&ColorBar::onPaletteIndexChange, this);
m_fgColor.Change.connect(&ColorBar::onFgColorButtonChange, this);
m_bgColor.Change.connect(&ColorBar::onBgColorButtonChange, this);
@ -190,13 +194,13 @@ void ColorBar::onPaletteButtonDropDownClick()
}
}
void ColorBar::onPaletteIndexChange(PaletteIndexChangeEvent& ev)
void ColorBar::onPaletteViewIndexChange(int index, ui::MouseButtons buttons)
{
m_lock = true;
app::Color color = app::Color::fromIndex(ev.index());
app::Color color = app::Color::fromIndex(index);
if ((ev.buttons() & kButtonRight) == kButtonRight)
if ((buttons & kButtonRight) == kButtonRight)
setBgColor(color);
else
setFgColor(color);
@ -204,6 +208,23 @@ void ColorBar::onPaletteIndexChange(PaletteIndexChangeEvent& ev)
m_lock = false;
}
void ColorBar::onPaletteViewRemapColors(const Remap& remap, const Palette* newPalette)
{
try {
ContextWriter writer(UIContext::instance());
Sprite* sprite = writer.sprite();
frame_t frame = writer.frame();
if (sprite) {
Transaction transaction(writer.context(), "Move Colors", ModifyDocument);
transaction.execute(new cmd::SetPalette(sprite, frame, newPalette));
transaction.commit();
}
}
catch (base::Exception& e) {
Console::showException(e);
}
}
void ColorBar::onFgColorButtonChange(const app::Color& color)
{
if (!m_lock)

View File

@ -26,7 +26,8 @@ namespace app {
class PalettesLoader;
class PaletteIndexChangeEvent;
class ColorBar : public ui::Box {
class ColorBar : public ui::Box
, public PaletteViewDelegate {
static ColorBar* m_instance;
public:
static ColorBar* instance() { return m_instance; }
@ -59,6 +60,10 @@ namespace app {
void onBgColorButtonChange(const app::Color& color);
void onColorButtonChange(const app::Color& color);
// PaletteViewDelegate impl
void onPaletteViewIndexChange(int index, ui::MouseButtons buttons) override;
void onPaletteViewRemapColors(const doc::Remap& remap, const doc::Palette* newPalette) override;
private:
class ScrollableView : public ui::View {
public:

View File

@ -55,7 +55,7 @@ ColorSelector::ColorSelector()
, m_vbox(JI_VERTICAL)
, m_topBox(JI_HORIZONTAL)
, m_color(app::Color::fromMask())
, m_colorPalette(false)
, m_colorPalette(false, this)
, m_indexButton("Index", 1, kButtonWidget)
, m_rgbButton("RGB", 1, kButtonWidget)
, m_hsvButton("HSB", 1, kButtonWidget)
@ -106,7 +106,6 @@ ColorSelector::ColorSelector()
m_maskButton.Click.connect(&ColorSelector::onColorTypeButtonClick, this);
m_warningIcon->Click.connect(&ColorSelector::onFixWarningClick, this);
m_colorPalette.IndexChange.connect(&ColorSelector::onColorPaletteIndexChange, this);
m_rgbSliders.ColorChange.connect(&ColorSelector::onColorSlidersChange, this);
m_hsvSliders.ColorChange.connect(&ColorSelector::onColorSlidersChange, this);
m_graySlider.ColorChange.connect(&ColorSelector::onColorSlidersChange, this);
@ -160,9 +159,9 @@ app::Color ColorSelector::getColor() const
return m_color;
}
void ColorSelector::onColorPaletteIndexChange(PaletteIndexChangeEvent& ev)
void ColorSelector::onPaletteViewIndexChange(int index, ui::MouseButtons buttons)
{
setColorWithSignal(app::Color::fromIndex(ev.index()));
setColorWithSignal(app::Color::fromIndex(index));
}
void ColorSelector::onColorSlidersChange(ColorSlidersChangeEvent& ev)

View File

@ -25,7 +25,8 @@
namespace app {
class PaletteIndexChangeEvent;
class ColorSelector : public PopupWindowPin {
class ColorSelector : public PopupWindowPin
, public PaletteViewDelegate {
public:
enum SetColorOptions {
ChangeType,
@ -42,13 +43,15 @@ namespace app {
Signal1<void, const app::Color&> ColorChange;
protected:
void onColorPaletteIndexChange(PaletteIndexChangeEvent& ev);;
void onColorSlidersChange(ColorSlidersChangeEvent& ev);
void onColorHexEntryChange(const app::Color& color);
void onColorTypeButtonClick(ui::Event& ev);
void onFixWarningClick(ui::Event& ev);
void onPaletteChange();
// PaletteViewDelegate impl
void onPaletteViewIndexChange(int index, ui::MouseButtons buttons) override;
private:
void selectColorType(app::Color::Type type);
void setColorWithSignal(const app::Color& color);

View File

@ -21,6 +21,7 @@
#include "doc/blend.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/remap.h"
#include "gfx/color.h"
#include "gfx/point.h"
#include "ui/graphics.h"
@ -33,6 +34,7 @@
#include "ui/view.h"
#include "ui/widget.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
@ -49,10 +51,11 @@ WidgetType palette_view_type()
return type;
}
PaletteView::PaletteView(bool editable)
PaletteView::PaletteView(bool editable, PaletteViewDelegate* delegate)
: Widget(palette_view_type())
, m_state(State::WAITING)
, m_editable(editable)
, m_delegate(delegate)
, m_columns(16)
, m_boxsize(7*guiscale())
, m_currentEntry(-1)
@ -174,9 +177,11 @@ bool PaletteView::onProcessMessage(Message* msg)
case kMouseDownMessage:
switch (m_hot.part) {
case Hit::COLOR:
m_state = State::SELECTING_COLOR;
break;
case Hit::OUTLINE:
m_state = State::DRAGGING_OUTLINE;
break;
@ -196,7 +201,7 @@ bool PaletteView::onProcessMessage(Message* msg)
StatusBar::instance()->showColor(0, "",
app::Color::fromIndex(idx), 255);
if (hasCapture() && idx != m_currentEntry) {
if (hasCapture() && (idx != m_currentEntry || msg->type() == kMouseDownMessage)) {
if (!msg->ctrlPressed())
clearSelection();
@ -206,8 +211,8 @@ bool PaletteView::onProcessMessage(Message* msg)
selectColor(idx);
// Emit signal
PaletteIndexChangeEvent ev(this, idx, mouseMsg->buttons());
IndexChange(ev);
if (m_delegate)
m_delegate->onPaletteViewIndexChange(idx, mouseMsg->buttons());
}
}
}
@ -222,6 +227,11 @@ bool PaletteView::onProcessMessage(Message* msg)
if (hasCapture()) {
releaseMouse();
if (m_state == State::DRAGGING_OUTLINE &&
m_hot.part == Hit::COLOR) {
dropColors(m_hot.color + (m_hot.after ? 1: 0));
}
m_state = State::WAITING;
invalidate();
}
@ -450,7 +460,7 @@ PaletteView::Hit PaletteView::hitTest(const gfx::Point& pos)
int outlineWidth = theme->dimensions.paletteOutlineWidth();
Palette* palette = get_current_palette();
if (m_state == State::WAITING) {
if (m_state == State::WAITING && m_editable) {
// First check if the mouse is inside the selection outline.
for (int i=0; i<palette->size(); ++i) {
if (!m_selectedEntries[i])
@ -488,4 +498,27 @@ PaletteView::Hit PaletteView::hitTest(const gfx::Point& pos)
return Hit(Hit::NONE);
}
void PaletteView::dropColors(int beforeIndex)
{
Palette* palette = get_current_palette();
Remap remap = Remap::moveSelectedEntriesTo(m_selectedEntries, beforeIndex);
auto oldSelectedCopies = m_selectedEntries;
Palette oldPalCopy(*palette);
for (int i=0; i<palette->size(); ++i) {
palette->setEntry(remap[i], oldPalCopy.getEntry(i));
m_selectedEntries[remap[i]] = oldSelectedCopies[i];
}
m_currentEntry = remap[m_currentEntry];
if (m_delegate) {
m_delegate->onPaletteViewRemapColors(remap, palette);
m_delegate->onPaletteViewIndexChange(m_currentEntry, ui::kButtonLeft);
}
set_current_palette(palette, false);
getManager()->invalidate();
}
} // namespace app

View File

@ -10,35 +10,31 @@
#pragma once
#include "base/connection.h"
#include "base/signal.h"
#include "ui/event.h"
#include "ui/mouse_buttons.h"
#include "ui/widget.h"
#include <vector>
namespace doc {
class Palette;
class Remap;
}
namespace app {
class PaletteIndexChangeEvent : public ui::Event {
class PaletteViewDelegate {
public:
PaletteIndexChangeEvent(ui::Widget* source, int index, ui::MouseButtons buttons)
: Event(source)
, m_index(index)
, m_buttons(buttons) { }
int index() const { return m_index; }
ui::MouseButtons buttons() const { return m_buttons; }
private:
int m_index;
ui::MouseButtons m_buttons;
virtual ~PaletteViewDelegate() { }
virtual void onPaletteViewIndexChange(int index, ui::MouseButtons buttons) { }
virtual void onPaletteViewRemapColors(const doc::Remap& remap, const doc::Palette* newPalette) { }
};
class PaletteView : public ui::Widget {
public:
typedef std::vector<bool> SelectedEntries;
PaletteView(bool editable);
PaletteView(bool editable, PaletteViewDelegate* delegate);
int getColumns() const { return m_columns; }
void setColumns(int columns);
@ -53,9 +49,6 @@ namespace app {
app::Color getColorByPosition(const gfx::Point& pos);
// Signals
Signal1<void, PaletteIndexChangeEvent&> IndexChange;
protected:
bool onProcessMessage(ui::Message* msg) override;
void onPaint(ui::PaintEvent& ev) override;
@ -99,9 +92,11 @@ namespace app {
void onAppPaletteChange();
gfx::Rect getPaletteEntryBounds(int index);
Hit hitTest(const gfx::Point& pos);
void dropColors(int beforeIndex);
State m_state;
bool m_editable;
PaletteViewDelegate* m_delegate;
int m_columns;
int m_boxsize;
int m_currentEntry;

View File

@ -40,6 +40,7 @@ add_library(doc-lib
palette.cpp
palette_io.cpp
primitives.cpp
remap.cpp
rgbmap.cpp
sprite.cpp
sprites.cpp

47
src/doc/remap.cpp Normal file
View File

@ -0,0 +1,47 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/remap.h"
namespace doc {
// static
Remap Remap::moveSelectedEntriesTo(const std::vector<bool>& selectedEntries, int beforeIndex)
{
Remap map(selectedEntries.size());
int selectedTotal = 0;
int selectedBeforeIndex = 0;
for (int i=0; i<map.size(); ++i) {
if (selectedEntries[i]) {
++selectedTotal;
if (i < beforeIndex)
++selectedBeforeIndex;
}
}
for (int i=0, j=0, k=0; i<map.size(); ++i) {
if (k == beforeIndex - selectedBeforeIndex)
k += selectedTotal;
if (selectedEntries[i]) {
map.map(i, beforeIndex - selectedBeforeIndex + j);
++j;
}
else {
map.map(i, k++);
}
}
return map;
}
} // namespace doc

46
src/doc/remap.h Normal file
View File

@ -0,0 +1,46 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_REMAP_H_INCLUDED
#define DOC_REMAP_H_INCLUDED
#pragma once
#include <vector>
namespace doc {
class Remap {
public:
Remap(int entries) : m_map(entries, 0) { }
// Creates a map to move a set of selected entries before the
// given index "beforeIndex".
static Remap moveSelectedEntriesTo(
const std::vector<bool>& selectedEntries, int beforeIndex);
int size() const {
return (int)m_map.size();
}
void map(int fromIndex, int toIndex) {
ASSERT(fromIndex >= 0 && fromIndex < size());
ASSERT(toIndex >= 0 && toIndex < size());
m_map[fromIndex] = toIndex;
}
int operator[](int index) const {
ASSERT(index >= 0 && index < size());
return m_map[index];
}
private:
std::vector<int> m_map;
};
} // namespace doc
#endif

76
src/doc/remap_tests.cpp Normal file
View File

@ -0,0 +1,76 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtest/gtest.h>
#include "doc/remap.h"
using namespace doc;
TEST(Remap, Basics)
{
std::vector<bool> entries(20);
std::fill(entries.begin(), entries.end(), false);
entries[6] =
entries[7] =
entries[14] = true;
Remap map = Remap::moveSelectedEntriesTo(entries, 1);
EXPECT_EQ(0, map[0]);
EXPECT_EQ(4, map[1]);
EXPECT_EQ(5, map[2]);
EXPECT_EQ(6, map[3]);
EXPECT_EQ(7, map[4]);
EXPECT_EQ(8, map[5]);
EXPECT_EQ(1, map[6]);
EXPECT_EQ(2, map[7]);
EXPECT_EQ(9, map[8]);
EXPECT_EQ(10, map[9]);
EXPECT_EQ(11, map[10]);
EXPECT_EQ(12, map[11]);
EXPECT_EQ(13, map[12]);
EXPECT_EQ(14, map[13]);
EXPECT_EQ(3, map[14]);
EXPECT_EQ(15, map[15]);
EXPECT_EQ(16, map[16]);
EXPECT_EQ(17, map[17]);
EXPECT_EQ(18, map[18]);
EXPECT_EQ(19, map[19]);
map = Remap::moveSelectedEntriesTo(entries, 18);
EXPECT_EQ(0, map[0]);
EXPECT_EQ(1, map[1]);
EXPECT_EQ(2, map[2]);
EXPECT_EQ(3, map[3]);
EXPECT_EQ(4, map[4]);
EXPECT_EQ(5, map[5]);
EXPECT_EQ(15, map[6]);
EXPECT_EQ(16, map[7]);
EXPECT_EQ(6, map[8]);
EXPECT_EQ(7, map[9]);
EXPECT_EQ(8, map[10]);
EXPECT_EQ(9, map[11]);
EXPECT_EQ(10, map[12]);
EXPECT_EQ(11, map[13]);
EXPECT_EQ(17, map[14]);
EXPECT_EQ(12, map[15]);
EXPECT_EQ(13, map[16]);
EXPECT_EQ(14, map[17]);
EXPECT_EQ(18, map[18]);
EXPECT_EQ(19, map[19]);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}