Unify palette editor with color selection (fix #1102)

Added a new A key shortcut (equal to F4) to switch between
selection/edition modes.
This commit is contained in:
David Capello 2017-06-22 17:44:34 -03:00
parent 85b7996e9a
commit 7d1404de0a
9 changed files with 324 additions and 817 deletions

View File

@ -102,6 +102,7 @@
<color id="flag_clicked" value="#7d929e" />
<color id="select_box_ruler" value="#0000ff" />
<color id="select_box_grid" value="#64c864" />
<color id="edit_pal_face" value="#655561" />
</colors>
<parts>
<part id="cursor_normal" x="80" y="0" w="16" h="16" focusx="0" focusy="0" />

View File

@ -114,6 +114,9 @@
<key command="Timeline" shortcut="Tab">
<param name="switch" value="true" />
</key>
<key command="PaletteEditor" shortcut="A">
<param name="switch" value="true" />
</key>
<key command="PaletteEditor" shortcut="F4">
<param name="switch" value="true" />
</key>
@ -937,7 +940,7 @@
</menu>
<menu id="palette_popup">
<item command="PaletteEditor" text="&amp;Palette Editor">
<item command="PaletteEditor" text="Edit &amp;Palette">
<param name="switch" value="true" />
</item>
<item command="PaletteSize" text="Palette Si&amp;ze" />

View File

@ -8,123 +8,12 @@
#include "config.h"
#endif
#include "app/app.h"
#include "app/cmd/set_palette.h"
#include "app/cmd_sequence.h"
#include "app/color.h"
#include "app/color_utils.h"
#include "app/commands/command.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/document_undo.h"
#include "app/file_selector.h"
#include "app/ini_file.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/transaction.h"
#include "app/ui/color_bar.h"
#include "app/ui/color_sliders.h"
#include "app/ui/editor/editor.h"
#include "app/ui/hex_color_entry.h"
#include "app/ui/palette_view.h"
#include "app/ui/skin/skin_slider_property.h"
#include "app/ui/status_bar.h"
#include "app/ui/toolbar.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "base/fs.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "gfx/hsl.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "gfx/size.h"
#include "ui/graphics.h"
#include "ui/ui.h"
#include <cstdio>
#include <cstring>
#include <vector>
namespace app {
using namespace gfx;
using namespace ui;
enum { RGB_MODE, HSV_MODE, HSL_MODE };
enum { ABS_MODE, REL_MODE };
class PaletteEntryEditor : public Window {
public:
PaletteEntryEditor();
void setColor(const app::Color& color);
protected:
bool onProcessMessage(Message* msg) override;
void onExit();
void onCloseWindow();
void onFgBgColorChange(const app::Color& _color);
void onColorSlidersChange(ColorSlidersChangeEvent& ev);
void onColorHexEntryChange(const app::Color& color);
void onColorTypeClick();
void onChangeModeClick();
private:
void selectColorType(app::Color::Type type);
void setPaletteEntry(const app::Color& color);
void setAbsolutePaletteEntryChannel(ColorSliders::Channel channel, const app::Color& color);
void setRelativePaletteEntryChannel(ColorSliders::Channel channel, int delta);
void setNewPalette(Palette* palette, const char* operationName);
void updateCurrentSpritePalette(const char* operationName);
void updateColorBar();
void updateWidgetsFromSelectedEntries();
void onPalChange();
void resetRelativeInfo();
void getPicks(PalettePicks& picks);
app::Color::Type m_type;
Box m_vbox;
Box m_topBox;
Box m_bottomBox;
ButtonSet m_colorType;
ButtonSet m_changeMode;
HexColorEntry m_hexColorEntry;
Label m_entryLabel;
ColorSliders m_sliders;
// This variable is used to avoid updating the m_hexColorEntry text
// when the color change is generated from a
// HexColorEntry::ColorChange signal. In this way we don't override
// what the user is writting in the text field.
bool m_disableHexUpdate;
ui::Timer m_redrawTimer;
bool m_redrawAll;
// True if the palette change must be implant in the UndoHistory
// (e.g. when two or more changes in the palette are made in short
// time).
bool m_implantChange;
// True if the PaletteChange signal is generated by the same
// PaletteEntryEditor instance.
bool m_selfPalChange;
obs::scoped_connection m_palChangeConn;
// Palette used for relative changes.
Palette m_fromPalette;
std::map<ColorSliders::Channel, int> m_relDeltas;
};
static PaletteEntryEditor* g_window = NULL;
class PaletteEditorCommand : public Command {
public:
PaletteEditorCommand();
@ -132,8 +21,8 @@ public:
protected:
void onLoadParams(const Params& params) override;
void onExecute(Context* context) override;
bool onChecked(Context* context) override;
void onExecute(Context* context) override;
private:
bool m_open;
@ -144,7 +33,7 @@ private:
PaletteEditorCommand::PaletteEditorCommand()
: Command("PaletteEditor",
"Palette Editor",
"Edit Palette",
CmdRecordableFlag)
{
m_open = true;
@ -172,656 +61,23 @@ void PaletteEditorCommand::onLoadParams(const Params& params)
else m_switch = false;
}
void PaletteEditorCommand::onExecute(Context* context)
{
// If this is the first time the command is execute...
if (!g_window) {
// If the command says "Close the palette editor" and it is not
// created yet, we just do nothing.
if (m_close)
return;
// If this is "open" or "switch", we have to create the frame.
g_window = new PaletteEntryEditor();
}
// If the frame is already created and it's visible, close it (only in "switch" or "close" modes)
else if (g_window->isVisible() && (m_switch || m_close)) {
// Hide the frame
g_window->closeWindow(NULL);
return;
}
if (m_switch || m_open) {
if (!g_window->isVisible()) {
// Default bounds
g_window->remapWindow();
int width = MAX(g_window->bounds().w, ui::display_w()/2);
g_window->setBounds(Rect(
ui::display_w() - width - ToolBar::instance()->bounds().w,
ui::display_h() - g_window->bounds().h - StatusBar::instance()->bounds().h,
width, g_window->bounds().h));
// Load window configuration
load_window_pos(g_window, "PaletteEditor");
}
// Run the frame in background.
g_window->openWindow();
ColorBar::instance()->setPaletteEditorButtonState(true);
}
// Show the specified target color
{
app::Color color =
(m_background ? Preferences::instance().colorBar.bgColor():
Preferences::instance().colorBar.fgColor());
g_window->setColor(color);
}
}
bool PaletteEditorCommand::onChecked(Context* context)
{
if(!g_window)
{
return false;
}
return g_window->isVisible();
return ColorBar::instance()->inEditMode();
}
//////////////////////////////////////////////////////////////////////
// PaletteEntryEditor implementation
//
// Based on ColorPopup class.
PaletteEntryEditor::PaletteEntryEditor()
: Window(WithTitleBar, "Palette Editor (F4)")
, m_type(app::Color::MaskType)
, m_vbox(VERTICAL)
, m_topBox(HORIZONTAL)
, m_bottomBox(HORIZONTAL)
, m_colorType(3)
, m_changeMode(2)
, m_entryLabel("")
, m_disableHexUpdate(false)
, m_redrawTimer(250, this)
, m_redrawAll(false)
, m_implantChange(false)
, m_selfPalChange(false)
, m_fromPalette(0, 0)
void PaletteEditorCommand::onExecute(Context* context)
{
m_colorType.addItem("RGB")->setFocusStop(false);
m_colorType.addItem("HSV")->setFocusStop(false);
m_colorType.addItem("HSL")->setFocusStop(false);
m_changeMode.addItem("Abs")->setFocusStop(false);
m_changeMode.addItem("Rel")->setFocusStop(false);
bool state = ColorBar::instance()->inEditMode();
m_topBox.setBorder(gfx::Border(0));
m_topBox.setChildSpacing(0);
m_bottomBox.setBorder(gfx::Border(0));
if (m_switch)
state = !state;
else if (m_open)
state = true;
else if (m_close)
state = false;
// Top box
m_topBox.addChild(&m_colorType);
m_topBox.addChild(new Separator("", VERTICAL));
m_topBox.addChild(&m_changeMode);
m_topBox.addChild(new Separator("", VERTICAL));
m_topBox.addChild(&m_hexColorEntry);
m_topBox.addChild(&m_entryLabel);
m_topBox.addChild(new BoxFiller);
// Main vertical box
m_vbox.addChild(&m_topBox);
m_vbox.addChild(&m_sliders);
m_vbox.addChild(&m_bottomBox);
addChild(&m_vbox);
m_colorType.ItemChange.connect(base::Bind<void>(&PaletteEntryEditor::onColorTypeClick, this));
m_changeMode.ItemChange.connect(base::Bind<void>(&PaletteEntryEditor::onChangeModeClick, this));
m_sliders.ColorChange.connect(&PaletteEntryEditor::onColorSlidersChange, this);
m_hexColorEntry.ColorChange.connect(&PaletteEntryEditor::onColorHexEntryChange, this);
m_changeMode.setSelectedItem(ABS_MODE);
selectColorType(app::Color::RgbType);
// We hook fg/bg color changes (by eyedropper mainly) to update the selected entry color
Preferences::instance().colorBar.fgColor.AfterChange.connect(
&PaletteEntryEditor::onFgBgColorChange, this);
Preferences::instance().colorBar.bgColor.AfterChange.connect(
&PaletteEntryEditor::onFgBgColorChange, this);
// We hook the Window::Close event to save the frame position before closing it.
this->Close.connect(base::Bind<void>(&PaletteEntryEditor::onCloseWindow, this));
// We hook App::Exit signal to destroy the g_window singleton at exit.
App::instance()->Exit.connect(&PaletteEntryEditor::onExit, this);
// Hook for palette change to redraw the palette editor frame
m_palChangeConn =
App::instance()->PaletteChange.connect(&PaletteEntryEditor::onPalChange, this);
initTheme();
}
void PaletteEntryEditor::setColor(const app::Color& color)
{
m_sliders.setColor(color);
if (!m_disableHexUpdate)
m_hexColorEntry.setColor(color);
PalettePicks entries;
getPicks(entries);
int i, j, i2;
// Find the first selected entry
for (i=0; i<(int)entries.size(); ++i)
if (entries[i])
break;
// Find the first unselected entry after i
for (i2=i+1; i2<(int)entries.size(); ++i2)
if (!entries[i2])
break;
// Find the last selected entry
for (j=entries.size()-1; j>=0; --j)
if (entries[j])
break;
if (i == j) {
m_entryLabel.setTextf(" Entry: %d", i);
}
else if (j-i+1 == i2-i) {
m_entryLabel.setTextf(" Range: %d-%d", i, j);
}
else if (i == int(entries.size())) {
m_entryLabel.setText(" No Entry");
}
else {
m_entryLabel.setText(" Multiple Entries");
}
m_topBox.layout();
}
bool PaletteEntryEditor::onProcessMessage(Message* msg)
{
if (msg->type() == kTimerMessage &&
static_cast<TimerMessage*>(msg)->timer() == &m_redrawTimer) {
// Redraw all editors
if (m_redrawAll) {
m_redrawAll = false;
m_implantChange = false;
m_redrawTimer.stop();
// Call all observers of PaletteChange event.
m_selfPalChange = true;
App::instance()->PaletteChange();
m_selfPalChange = false;
// Redraw all editors
try {
ContextWriter writer(UIContext::instance());
Document* document(writer.document());
if (document != NULL)
document->notifyGeneralUpdate();
}
catch (...) {
// Do nothing
}
}
// Redraw just the current editor
else {
m_redrawAll = true;
if (current_editor != NULL)
current_editor->updateEditor();
}
}
return Window::onProcessMessage(msg);
}
void PaletteEntryEditor::onExit()
{
delete this;
}
void PaletteEntryEditor::onCloseWindow()
{
// Save window configuration
save_window_pos(this, "PaletteEditor");
// Uncheck the "Edit Palette" button.
ColorBar::instance()->setPaletteEditorButtonState(false);
}
void PaletteEntryEditor::onFgBgColorChange(const app::Color& _color)
{
app::Color color = _color;
if (!color.isValid())
return;
if (color.getType() != app::Color::IndexType) {
PaletteView* paletteView = ColorBar::instance()->getPaletteView();
int index = paletteView->getSelectedEntry();
if (index < 0)
return;
color = app::Color::fromIndex(index);
}
if (color.getType() == app::Color::IndexType) {
setColor(color);
resetRelativeInfo();
}
}
void PaletteEntryEditor::onColorSlidersChange(ColorSlidersChangeEvent& ev)
{
setColor(ev.color());
if (ev.mode() == ColorSliders::Mode::Absolute)
setAbsolutePaletteEntryChannel(ev.channel(), ev.color());
else
setRelativePaletteEntryChannel(ev.channel(), ev.delta());
updateCurrentSpritePalette("Color Change");
updateColorBar();
}
void PaletteEntryEditor::onColorHexEntryChange(const app::Color& color)
{
// Disable updating the hex entry so we don't override what the user
// is writting in the text field.
m_disableHexUpdate = true;
setColor(color);
setPaletteEntry(color);
updateCurrentSpritePalette("Color Change");
updateColorBar();
m_disableHexUpdate = false;
}
void PaletteEntryEditor::onColorTypeClick()
{
switch (m_colorType.selectedItem()) {
case RGB_MODE:
selectColorType(app::Color::RgbType);
break;
case HSV_MODE:
selectColorType(app::Color::HsvType);
break;
case HSL_MODE:
selectColorType(app::Color::HslType);
break;
}
}
void PaletteEntryEditor::onChangeModeClick()
{
switch (m_changeMode.selectedItem()) {
case ABS_MODE:
m_sliders.setMode(ColorSliders::Mode::Absolute);
break;
case REL_MODE:
m_sliders.setMode(ColorSliders::Mode::Relative);
break;
}
// Update sliders, entries, etc.
updateWidgetsFromSelectedEntries();
}
void PaletteEntryEditor::setPaletteEntry(const app::Color& color)
{
PalettePicks entries;
getPicks(entries);
color_t new_pal_color = doc::rgba(color.getRed(),
color.getGreen(),
color.getBlue(), 255);
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
if (entries[c])
palette->setEntry(c, new_pal_color);
}
}
void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel channel, const app::Color& color)
{
PalettePicks entries;
getPicks(entries);
int picksCount = entries.picks();
uint32_t src_color;
int r, g, b, a;
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
if (!entries[c])
continue;
// Get the current RGB values of the palette entry
src_color = palette->getEntry(c);
r = rgba_getr(src_color);
g = rgba_getg(src_color);
b = rgba_getb(src_color);
a = rgba_geta(src_color);
switch (m_type) {
case app::Color::RgbType:
// Modify one entry
if (picksCount == 1) {
r = color.getRed();
g = color.getGreen();
b = color.getBlue();
a = color.getAlpha();
}
// Modify one channel a set of entries
else {
// Setup the new RGB values depending of the modified channel.
switch (channel) {
case ColorSliders::Channel::Red:
r = color.getRed();
case ColorSliders::Channel::Green:
g = color.getGreen();
break;
case ColorSliders::Channel::Blue:
b = color.getBlue();
break;
case ColorSliders::Channel::Alpha:
a = color.getAlpha();
break;
}
}
break;
case app::Color::HsvType: {
Hsv hsv;
// Modify one entry
if (picksCount == 1) {
hsv.hue(color.getHsvHue());
hsv.saturation(color.getHsvSaturation());
hsv.value(color.getHsvValue());
a = color.getAlpha();
}
// Modify one channel a set of entries
else {
// Convert RGB to HSV
hsv = Hsv(Rgb(r, g, b));
// Only modify the desired HSV channel
switch (channel) {
case ColorSliders::Channel::HsvHue:
hsv.hue(color.getHsvHue());
break;
case ColorSliders::Channel::HsvSaturation:
hsv.saturation(color.getHsvSaturation());
break;
case ColorSliders::Channel::HsvValue:
hsv.value(color.getHsvValue());
break;
case ColorSliders::Channel::Alpha:
a = color.getAlpha();
break;
}
}
// Convert HSV back to RGB
Rgb rgb(hsv);
r = rgb.red();
g = rgb.green();
b = rgb.blue();
break;
}
case app::Color::HslType: {
Hsl hsl;
// Modify one entry
if (picksCount == 1) {
hsl.hue(color.getHslHue());
hsl.saturation(color.getHslSaturation());
hsl.lightness(color.getHslLightness());
a = color.getAlpha();
}
// Modify one channel a set of entries
else {
// Convert RGB to HSL
hsl = Hsl(Rgb(r, g, b));
// Only modify the desired HSL channel
switch (channel) {
case ColorSliders::Channel::HslHue:
hsl.hue(color.getHslHue());
break;
case ColorSliders::Channel::HslSaturation:
hsl.saturation(color.getHslSaturation());
break;
case ColorSliders::Channel::HslLightness:
hsl.lightness(color.getHslLightness());
break;
case ColorSliders::Channel::Alpha:
a = color.getAlpha();
break;
}
}
// Convert HSL back to RGB
Rgb rgb(hsl);
r = rgb.red();
g = rgb.green();
b = rgb.blue();
break;
}
}
palette->setEntry(c, doc::rgba(r, g, b, a));
}
}
void PaletteEntryEditor::setRelativePaletteEntryChannel(ColorSliders::Channel channel, int delta)
{
PalettePicks entries;
getPicks(entries);
// Update modified delta
m_relDeltas[channel] = delta;
uint32_t src_color;
int r, g, b, a;
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
if (!entries[c])
continue;
// Get the current RGB values of the palette entry
src_color = m_fromPalette.getEntry(c);
r = rgba_getr(src_color);
g = rgba_getg(src_color);
b = rgba_getb(src_color);
a = rgba_geta(src_color);
switch (m_type) {
case app::Color::RgbType:
r = MID(0, r+m_relDeltas[ColorSliders::Channel::Red], 255);
g = MID(0, g+m_relDeltas[ColorSliders::Channel::Green], 255);
b = MID(0, b+m_relDeltas[ColorSliders::Channel::Blue], 255);
a = MID(0, a+m_relDeltas[ColorSliders::Channel::Alpha], 255);
break;
case app::Color::HsvType: {
// Convert RGB to HSV
Hsv hsv(Rgb(r, g, b));
double h = hsv.hue() +m_relDeltas[ColorSliders::Channel::HsvHue];
double s = hsv.saturation()+m_relDeltas[ColorSliders::Channel::HsvSaturation]/100.0;
double v = hsv.value() +m_relDeltas[ColorSliders::Channel::HsvValue] /100.0;
if (h < 0.0) h += 360.0;
else if (h > 360.0) h -= 360.0;
hsv.hue (MID(0.0, h, 360.0));
hsv.saturation(MID(0.0, s, 1.0));
hsv.value (MID(0.0, v, 1.0));
// Convert HSV back to RGB
Rgb rgb(hsv);
r = rgb.red();
g = rgb.green();
b = rgb.blue();
a = MID(0, a+m_relDeltas[ColorSliders::Channel::Alpha], 255);
break;
}
case app::Color::HslType: {
// Convert RGB to HSL
Hsl hsl(Rgb(r, g, b));
double h = hsl.hue() +m_relDeltas[ColorSliders::Channel::HslHue];
double s = hsl.saturation()+m_relDeltas[ColorSliders::Channel::HslSaturation]/100.0;
double l = hsl.lightness() +m_relDeltas[ColorSliders::Channel::HslLightness] /100.0;
if (h < 0.0) h += 360.0;
else if (h > 360.0) h -= 360.0;
hsl.hue (h);
hsl.saturation(MID(0.0, s, 1.0));
hsl.lightness (MID(0.0, l, 1.0));
// Convert HSL back to RGB
Rgb rgb(hsl);
r = rgb.red();
g = rgb.green();
b = rgb.blue();
a = MID(0, a+m_relDeltas[ColorSliders::Alpha], 255);
break;
}
}
palette->setEntry(c, doc::rgba(r, g, b, a));
}
}
void PaletteEntryEditor::selectColorType(app::Color::Type type)
{
m_type = type;
m_sliders.setColorType(type);
resetRelativeInfo();
switch (type) {
case app::Color::RgbType: m_colorType.setSelectedItem(RGB_MODE); break;
case app::Color::HsvType: m_colorType.setSelectedItem(HSV_MODE); break;
case app::Color::HslType: m_colorType.setSelectedItem(HSL_MODE); break;
}
m_vbox.layout();
m_vbox.invalidate();
}
void PaletteEntryEditor::updateCurrentSpritePalette(const char* operationName)
{
if (UIContext::instance()->activeDocument() &&
UIContext::instance()->activeDocument()->sprite()) {
try {
ContextWriter writer(UIContext::instance());
Document* document(writer.document());
Sprite* sprite(writer.sprite());
Palette* newPalette = get_current_palette(); // System current pal
frame_t frame = writer.frame();
Palette* currentSpritePalette = sprite->palette(frame); // Sprite current pal
int from, to;
// Check differences between current sprite palette and current system palette
from = to = -1;
currentSpritePalette->countDiff(newPalette, &from, &to);
if (from >= 0 && to >= from) {
DocumentUndo* undo = document->undoHistory();
Cmd* cmd = new cmd::SetPalette(sprite, frame, newPalette);
// Add undo information to save the range of pal entries that will be modified.
if (m_implantChange &&
undo->lastExecutedCmd() &&
undo->lastExecutedCmd()->label() == operationName) {
// Implant the cmd in the last CmdSequence if it's
// related about color palette modifications
ASSERT(dynamic_cast<CmdSequence*>(undo->lastExecutedCmd()));
static_cast<CmdSequence*>(undo->lastExecutedCmd())->add(cmd);
cmd->execute(UIContext::instance());
}
else {
Transaction transaction(writer.context(), operationName, ModifyDocument);
transaction.execute(cmd);
transaction.commit();
}
}
}
catch (base::Exception& e) {
Console::showException(e);
}
}
PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
palette_editor->invalidate();
if (!m_redrawTimer.isRunning())
m_redrawTimer.start();
m_redrawAll = false;
m_implantChange = true;
}
void PaletteEntryEditor::updateColorBar()
{
ColorBar::instance()->invalidate();
}
void PaletteEntryEditor::updateWidgetsFromSelectedEntries()
{
PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
int index = palette_editor->getSelectedEntry();
if (index >= 0)
setColor(app::Color::fromIndex(index));
resetRelativeInfo();
// Redraw the window
invalidate();
}
void PaletteEntryEditor::onPalChange()
{
if (!m_selfPalChange)
updateWidgetsFromSelectedEntries();
}
void PaletteEntryEditor::resetRelativeInfo()
{
m_sliders.resetRelativeSliders();
get_current_palette()->copyColorsTo(&m_fromPalette);
m_relDeltas.clear();
}
void PaletteEntryEditor::getPicks(PalettePicks& picks)
{
PaletteView* palView = ColorBar::instance()->getPaletteView();
palView->getSelectedEntries(picks);
if (picks.picks() == 0) {
int i = palView->getSelectedEntry();
if (i >= 0 && i < picks.size())
picks[i] = true;
}
ColorBar::instance()->setEditMode(state);
}
Command* CommandFactory::createPaletteEditorCommand()

View File

@ -47,12 +47,18 @@ WidgetType buttonset_item_type()
ButtonSet::Item::Item()
: Widget(buttonset_item_type())
, m_icon(NULL)
, m_hotColor(gfx::ColorNone)
{
setup_mini_font(this);
setAlign(CENTER | MIDDLE);
setFocusStop(true);
}
void ButtonSet::Item::setHotColor(gfx::Color color)
{
m_hotColor = color;
}
void ButtonSet::Item::setIcon(const SkinPartPtr& icon, bool mono)
{
m_icon = icon;
@ -121,7 +127,22 @@ void ButtonSet::Item::onPaint(ui::PaintEvent& ev)
rc.h += 3*guiscale();
}
theme->drawRect(g, rc, nw.get());
theme->drawRect(g, rc, nw.get(),
gfx::is_transparent(m_hotColor));
if (!gfx::is_transparent(m_hotColor)) {
gfx::Rect rc2(rc);
gfx::Rect sprite(nw->spriteBounds());
gfx::Rect slices(nw->slicesBounds());
rc2.shrink(
gfx::Border(
slices.x-1, // TODO this "-1" is an ugly hack for the pal edit
// button, replace all this with styles
slices.y-1,
sprite.w-slices.w-slices.x-1,
sprite.h-slices.h-slices.y));
g->fillRect(m_hotColor, rc2);
}
if (m_icon) {
she::Surface* bmp = m_icon->bitmap(0);

View File

@ -21,6 +21,7 @@ namespace app {
class Item : public ui::Widget {
public:
Item();
void setHotColor(gfx::Color color);
void setIcon(const skin::SkinPartPtr& icon, bool mono = false);
skin::SkinPartPtr icon() const { return m_icon; }
ButtonSet* buttonSet();
@ -33,6 +34,7 @@ namespace app {
private:
skin::SkinPartPtr m_icon;
bool m_mono;
gfx::Color m_hotColor;
};
ButtonSet(int columns);

View File

@ -16,12 +16,14 @@
#include "app/cmd/replace_image.h"
#include "app/cmd/set_palette.h"
#include "app/cmd/set_transparent_color.h"
#include "app/cmd_sequence.h"
#include "app/color.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/document_undo.h"
#include "app/document_api.h"
#include "app/ini_file.h"
#include "app/modules/editors.h"
@ -64,10 +66,8 @@
#include "ui/system.h"
#include "ui/tooltips.h"
#include <cstring>
namespace app {
enum class PalButton {
@ -122,11 +122,18 @@ ColorBar::ColorBar(int align)
, m_bgColor(app::Color::fromRgb(0, 0, 0), IMAGE_RGB, true, false)
, m_fgWarningIcon(new WarningIcon)
, m_bgWarningIcon(new WarningIcon)
, m_lock(false)
, m_syncingWithPref(false)
, m_fromPalView(false)
, m_fromPref(false)
, m_fromFgButton(false)
, m_fromBgButton(false)
, m_lastDocument(nullptr)
, m_ascending(true)
, m_lastButtons(kButtonLeft)
, m_editMode(false)
, m_redrawTimer(250, this)
, m_redrawAll(false)
, m_implantChange(false)
, m_selfPalChange(false)
{
m_instance = this;
@ -135,9 +142,16 @@ ColorBar::ColorBar(int align)
setBorder(gfx::Border(2*guiscale(), 0, 0, 0));
setChildSpacing(2*guiscale());
m_buttons.addItem(theme->parts.palEdit());
m_buttons.addItem(theme->parts.palSort());
m_buttons.addItem(theme->parts.palPresets());
m_buttons.addItem(theme->parts.palOptions());
m_paletteView.setColumns(8);
m_fgColor.setSizeHint(0, m_fgColor.sizeHint().h);
m_bgColor.setSizeHint(0, m_bgColor.sizeHint().h);
m_buttons.setMaxSize(gfx::Size(m_buttons.sizeHint().w,
16*ui::guiscale()));
// TODO hardcoded scroll bar width should be get from skin.xml file
int scrollBarWidth = 6*guiscale();
@ -184,9 +198,12 @@ ColorBar::ColorBar(int align)
m_remapButton.Click.connect(base::Bind<void>(&ColorBar::onRemapButtonClick, this));
m_fgColor.Change.connect(&ColorBar::onFgColorButtonChange, this);
m_fgColor.BeforeChange.connect(&ColorBar::onFgColorButtonBeforeChange, this);
m_bgColor.Change.connect(&ColorBar::onBgColorButtonChange, this);
m_fgWarningIcon->Click.connect(base::Bind<void>(&ColorBar::onFixWarningClick, this, &m_fgColor, m_fgWarningIcon));
m_bgWarningIcon->Click.connect(base::Bind<void>(&ColorBar::onFixWarningClick, this, &m_bgColor, m_bgWarningIcon));
m_redrawTimer.Tick.connect(base::Bind<void>(&ColorBar::onTimerTick, this));
m_buttons.ItemChange.connect(base::Bind<void>(&ColorBar::onPaletteButtonClick, this));
m_tooltips.addTooltipFor(&m_fgColor, "Foreground color", LEFT);
m_tooltips.addTooltipFor(&m_bgColor, "Background color", LEFT);
@ -206,16 +223,6 @@ ColorBar::ColorBar(int align)
Widget::setBgColor(theme->colors.tabActiveFace());
m_paletteView.setBgColor(theme->colors.tabActiveFace());
// Change labels foreground color
m_buttons.ItemChange.connect(base::Bind<void>(&ColorBar::onPaletteButtonClick, this));
m_buttons.addItem(theme->parts.palEdit());
m_buttons.addItem(theme->parts.palSort());
m_buttons.addItem(theme->parts.palPresets());
m_buttons.addItem(theme->parts.palOptions());
m_buttons.setMaxSize(gfx::Size(m_buttons.sizeHint().w,
16*ui::guiscale()));
// Tooltips
TooltipManager* tooltipManager = new TooltipManager();
addChild(tooltipManager);
@ -234,6 +241,8 @@ ColorBar::ColorBar(int align)
m_bgConn = Preferences::instance().colorBar.bgColor.AfterChange.connect(base::Bind<void>(&ColorBar::onBgColorChangeFromPreferences, this));
m_paletteView.FocusEnter.connect(&ColorBar::onFocusPaletteView, this);
m_appPalChangeConn = App::instance()->PaletteChange.connect(&ColorBar::onAppPaletteChange, this);
setEditMode(false);
}
ColorBar::~ColorBar()
@ -247,29 +256,33 @@ void ColorBar::setPixelFormat(PixelFormat pixelFormat)
m_bgColor.setPixelFormat(pixelFormat);
}
app::Color ColorBar::getFgColor()
app::Color ColorBar::getFgColor() const
{
return m_fgColor.getColor();
}
app::Color ColorBar::getBgColor()
app::Color ColorBar::getBgColor() const
{
return m_bgColor.getColor();
}
void ColorBar::setFgColor(const app::Color& color)
{
m_fgColor.setColor(color);
if (m_fromFgButton)
return;
if (!m_lock)
m_fgColor.setColor(color);
if (!m_fromPalView)
onColorButtonChange(color);
}
void ColorBar::setBgColor(const app::Color& color)
{
m_bgColor.setColor(color);
if (m_fromBgButton)
return;
if (!m_lock)
m_bgColor.setColor(color);
if (!m_fromPalView)
onColorButtonChange(color);
}
@ -278,7 +291,7 @@ PaletteView* ColorBar::getPaletteView()
return &m_paletteView;
}
ColorBar::ColorSelector ColorBar::getColorSelector()
ColorBar::ColorSelector ColorBar::getColorSelector() const
{
return m_selector;
}
@ -340,9 +353,20 @@ void ColorBar::setColorSelector(ColorSelector selector)
m_selectorPlaceholder.layout();
}
void ColorBar::setPaletteEditorButtonState(bool state)
void ColorBar::setEditMode(bool state)
{
m_buttons.getItem(int(PalButton::EDIT))->setSelected(state);
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
ButtonSet::Item* item = m_buttons.getItem((int)PalButton::EDIT);
m_editMode = state;
item->setIcon(state ? theme->parts.timelineOpenPadlockActive():
theme->parts.timelineClosedPadlockNormal());
item->setHotColor(state ? theme->colors.editPalFace():
gfx::ColorNone);
// Deselect color entries when we cancel editing
if (!state)
m_paletteView.deselect();
}
void ColorBar::onActiveSiteChange(const doc::Site& site)
@ -368,6 +392,9 @@ void ColorBar::onGeneralUpdate(doc::DocumentEvent& ev)
void ColorBar::onAppPaletteChange()
{
if (inEditMode())
return;
fixColorIndex(m_fgColor);
fixColorIndex(m_bgColor);
@ -594,7 +621,7 @@ void ColorBar::onRemapButtonClick()
void ColorBar::onPaletteViewIndexChange(int index, ui::MouseButtons buttons)
{
m_lock = true;
base::ScopedValue<bool> lock(m_fromPalView, true, m_fromPalView);
app::Color color = app::Color::fromIndex(index);
@ -606,7 +633,6 @@ void ColorBar::onPaletteViewIndexChange(int index, ui::MouseButtons buttons)
setTransparentIndex(index);
ChangeSelection();
m_lock = false;
}
void ColorBar::onPaletteViewModification(const Palette* newPalette,
@ -725,31 +751,62 @@ app::Color ColorBar::onPaletteViewGetBackgroundIndex()
void ColorBar::onFgColorChangeFromPreferences()
{
if (m_syncingWithPref)
if (m_fromPref)
return;
base::ScopedValue<bool> sync(m_syncingWithPref, true, false);
base::ScopedValue<bool> sync(m_fromPref, true, false);
setFgColor(Preferences::instance().colorBar.fgColor());
}
void ColorBar::onBgColorChangeFromPreferences()
{
if (m_syncingWithPref)
if (m_fromPref)
return;
base::ScopedValue<bool> sync(m_syncingWithPref, true, false);
setBgColor(Preferences::instance().colorBar.bgColor());
if (inEditMode()) {
// In edit mode, clicking with right-click will copy the color
// selected with eyedropper to the active color entry.
setFgColor(Preferences::instance().colorBar.bgColor());
}
else {
base::ScopedValue<bool> sync(m_fromPref, true, false);
setBgColor(Preferences::instance().colorBar.bgColor());
}
}
void ColorBar::onFgColorButtonBeforeChange(app::Color& color)
{
if (m_fromPalView)
return;
if (!inEditMode()) {
m_paletteView.deselect();
return;
}
// Here we change the selected colors in the
// palette. "m_fromPref" must be false to edit the color. (It
// means, if the eyedropper was used with the left-click, we don't
// edit the color, we just select the color to as the normal
// non-edit mode.)
if (!m_fromPref) {
int i = setPaletteEntry(color);
if (i >= 0) {
updateCurrentSpritePalette("Color Change");
color = app::Color::fromIndex(i);
}
}
}
void ColorBar::onFgColorButtonChange(const app::Color& color)
{
if (!m_lock) {
m_paletteView.deselect();
m_paletteView.invalidate();
}
if (m_fromFgButton)
return;
if (!m_syncingWithPref) {
base::ScopedValue<bool> sync(m_syncingWithPref, true, false);
base::ScopedValue<bool> lock(m_fromFgButton, true, false);
if (!m_fromPref) {
base::ScopedValue<bool> sync(m_fromPref, true, false);
Preferences::instance().colorBar.fgColor(color);
}
@ -759,13 +816,16 @@ void ColorBar::onFgColorButtonChange(const app::Color& color)
void ColorBar::onBgColorButtonChange(const app::Color& color)
{
if (!m_lock) {
m_paletteView.deselect();
m_paletteView.invalidate();
}
if (m_fromBgButton)
return;
if (!m_syncingWithPref) {
base::ScopedValue<bool> sync(m_syncingWithPref, true, false);
base::ScopedValue<bool> lock(m_fromBgButton, true, false);
if (!m_fromPalView && !inEditMode())
m_paletteView.deselect();
if (!m_fromPref) {
base::ScopedValue<bool> sync(m_fromPref, true, false);
Preferences::instance().colorBar.bgColor(color);
}
@ -775,14 +835,17 @@ void ColorBar::onBgColorButtonChange(const app::Color& color)
void ColorBar::onColorButtonChange(const app::Color& color)
{
if (color.getType() == app::Color::IndexType)
m_paletteView.selectColor(color.getIndex());
else {
m_paletteView.selectExactMatchColor(color);
if (!inEditMode() ||
m_fromPref) {
if (color.getType() == app::Color::IndexType)
m_paletteView.selectColor(color.getIndex());
else {
m_paletteView.selectExactMatchColor(color);
// As foreground or background color changed, we've to redraw the
// palette view fg/bg indicators.
m_paletteView.invalidate();
// As foreground or background color changed, we've to redraw the
// palette view fg/bg indicators.
m_paletteView.invalidate();
}
}
if (m_tintShadeTone && m_tintShadeTone->isVisible())
@ -989,6 +1052,38 @@ void ColorBar::onFixWarningClick(ColorButton* colorButton, ui::Button* warningIc
UIContext::instance()->executeCommand(command, params);
}
void ColorBar::onTimerTick()
{
// Redraw all editors
if (m_redrawAll) {
m_redrawAll = false;
m_implantChange = false;
m_redrawTimer.stop();
// Call all observers of PaletteChange event.
m_selfPalChange = true;
App::instance()->PaletteChange();
m_selfPalChange = false;
// Redraw all editors
try {
ContextWriter writer(UIContext::instance());
Document* document(writer.document());
if (document != NULL)
document->notifyGeneralUpdate();
}
catch (...) {
// Do nothing
}
}
// Redraw just the current editor
else {
m_redrawAll = true;
if (current_editor != NULL)
current_editor->updateEditor();
}
}
void ColorBar::updateWarningIcon(const app::Color& color, ui::Button* warningIcon)
{
int index = -1;
@ -1013,6 +1108,94 @@ void ColorBar::updateWarningIcon(const app::Color& color, ui::Button* warningIco
warningIcon->parent()->layout();
}
// Changes the selected color palettes with the given
// app::Color. Returns the first modified index in the palette.
int ColorBar::setPaletteEntry(const app::Color& color)
{
int selIdx = m_paletteView.getSelectedEntry();
if (selIdx < 0) {
if (getFgColor().getType() == app::Color::IndexType) {
selIdx = getFgColor().getIndex();
}
}
PalettePicks entries;
m_paletteView.getSelectedEntries(entries);
if (entries.picks() == 0) {
if (selIdx >= 0 && selIdx < entries.size()) {
entries[selIdx] = true;
}
}
doc::color_t c =
doc::rgba(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
Palette* palette = get_current_palette();
for (int i=0; i<palette->size(); ++i) {
if (entries[i])
palette->setEntry(i, c);
}
if (selIdx < 0 ||
selIdx >= entries.size() ||
!entries[selIdx])
selIdx = entries.firstPick();
return selIdx;
}
void ColorBar::updateCurrentSpritePalette(const char* operationName)
{
if (UIContext::instance()->activeDocument() &&
UIContext::instance()->activeDocument()->sprite()) {
try {
ContextWriter writer(UIContext::instance());
Document* document(writer.document());
Sprite* sprite(writer.sprite());
Palette* newPalette = get_current_palette(); // System current pal
frame_t frame = writer.frame();
Palette* currentSpritePalette = sprite->palette(frame); // Sprite current pal
int from, to;
// Check differences between current sprite palette and current system palette
from = to = -1;
currentSpritePalette->countDiff(newPalette, &from, &to);
if (from >= 0 && to >= from) {
DocumentUndo* undo = document->undoHistory();
Cmd* cmd = new cmd::SetPalette(sprite, frame, newPalette);
// Add undo information to save the range of pal entries that will be modified.
if (m_implantChange &&
undo->lastExecutedCmd() &&
undo->lastExecutedCmd()->label() == operationName) {
// Implant the cmd in the last CmdSequence if it's
// related about color palette modifications
ASSERT(dynamic_cast<CmdSequence*>(undo->lastExecutedCmd()));
static_cast<CmdSequence*>(undo->lastExecutedCmd())->add(cmd);
cmd->execute(UIContext::instance());
}
else {
Transaction transaction(writer.context(), operationName, ModifyDocument);
transaction.execute(cmd);
transaction.commit();
}
}
}
catch (base::Exception& e) {
Console::showException(e);
}
}
m_paletteView.invalidate();
if (!m_redrawTimer.isRunning())
m_redrawTimer.start();
m_redrawAll = false;
m_implantChange = true;
}
// static
void ColorBar::fixColorIndex(ColorButton& colorButton)
{

View File

@ -60,19 +60,20 @@ namespace app {
void setPixelFormat(PixelFormat pixelFormat);
app::Color getFgColor();
app::Color getBgColor();
app::Color getFgColor() const;
app::Color getBgColor() const;
void setFgColor(const app::Color& color);
void setBgColor(const app::Color& color);
PaletteView* getPaletteView();
ColorSelector getColorSelector();
ColorSelector getColorSelector() const;
void setColorSelector(ColorSelector selector);
// Used by the Palette Editor command to change the status of button
// when the visibility of the dialog changes.
void setPaletteEditorButtonState(bool state);
bool inEditMode() const { return m_editMode; }
void setEditMode(bool state);
// ContextObserver impl
void onActiveSiteChange(const doc::Site& site) override;
@ -104,6 +105,7 @@ namespace app {
void onPaletteIndexChange(PaletteIndexChangeEvent& ev);
void onFgColorChangeFromPreferences();
void onBgColorChangeFromPreferences();
void onFgColorButtonBeforeChange(app::Color& color);
void onFgColorButtonChange(const app::Color& color);
void onBgColorButtonChange(const app::Color& color);
void onColorButtonChange(const app::Color& color);
@ -112,6 +114,7 @@ namespace app {
void onSortBy(doc::SortPaletteBy channel);
void onGradient();
void onFixWarningClick(ColorButton* colorButton, ui::Button* warningIcon);
void onTimerTick();
void setAscending(bool ascending);
// PaletteViewDelegate impl
@ -128,6 +131,8 @@ namespace app {
void setPalette(const doc::Palette* newPalette, const std::string& actionText);
void setTransparentIndex(int index);
void updateWarningIcon(const app::Color& color, ui::Button* warningIcon);
int setPaletteEntry(const app::Color& color);
void updateCurrentSpritePalette(const char* operationName);
static void fixColorIndex(ColorButton& color);
class ScrollableView : public ui::View {
@ -154,8 +159,18 @@ namespace app {
ColorButton m_bgColor;
WarningIcon* m_fgWarningIcon;
WarningIcon* m_bgWarningIcon;
bool m_lock;
bool m_syncingWithPref;
// True when the user clicks the PaletteView so we're changing the
// color from the palette view.
bool m_fromPalView;
// If m_syncingWithPref is true it means that the eyedropper was
// used to change the color.
bool m_fromPref;
bool m_fromFgButton;
bool m_fromBgButton;
base::UniquePtr<doc::Palette> m_oldPalette;
doc::Document* m_lastDocument;
bool m_ascending;
@ -165,6 +180,22 @@ namespace app {
obs::scoped_connection m_bgConn;
obs::scoped_connection m_appPalChangeConn;
ui::MouseButtons m_lastButtons;
// True if we the editing mode is on.
bool m_editMode;
// Timer to redraw editors after a palette change.
ui::Timer m_redrawTimer;
bool m_redrawAll;
// True if a palette change must be implant in the UndoHistory
// (e.g. when two or more changes in the palette are made in a
// very short time).
bool m_implantChange;
// True if the App::PaletteChange signal is generated by this same
// ColorBar.
bool m_selfPalChange;
};
} // namespace app

View File

@ -48,7 +48,7 @@ ColorButton::ColorButton(const app::Color& color,
: ButtonBase("", colorbutton_type(), kButtonWidget, kButtonWidget)
, m_color(color)
, m_pixelFormat(pixelFormat)
, m_window(NULL)
, m_window(nullptr)
, m_dependOnLayer(false)
, m_canPinSelector(canPinSelector)
, m_showSimpleColors(showSimpleColors)
@ -82,13 +82,22 @@ app::Color ColorButton::getColor() const
return m_color;
}
void ColorButton::setColor(const app::Color& color)
void ColorButton::setColor(const app::Color& origColor)
{
// Before change (this signal can modify the color)
app::Color color = origColor;
BeforeChange(color);
m_color = color;
// Change the color in its related window
if (m_window)
m_window->setColor(m_color, ColorPopup::DoNotChangeType);
if (m_window) {
// In the window we show the original color. In case
// BeforeChange() has changed the color type (e.g. to index), we
// don't care, in the window we prefer to keep the original
// HSV/HSL values.
m_window->setColor(origColor, ColorPopup::DoNotChangeType);
}
// Emit signal
Change(color);

View File

@ -42,6 +42,7 @@ namespace app {
app::Color getColorByPosition(const gfx::Point& pos) override;
// Signals
obs::signal<void(app::Color&)> BeforeChange;
obs::signal<void(const app::Color&)> Change;
protected: