ColorSelector: Add WarningIcon to add colors to the palette

Simplified the color selector UI. Now RGB/HSV colors are shown correctly
and a new warning icon/button is visible when the color is not part of
the current palette. This button can be used to quickly add the new entry
to the palette.
This commit is contained in:
David Capello 2014-06-01 18:15:11 -03:00
parent f91f41a425
commit ed6c0f986d
12 changed files with 252 additions and 100 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -327,6 +327,7 @@
<part id="drop_pixels_ok_selected" x="176" y="184" w="7" h="8" />
<part id="drop_pixels_cancel" x="192" y="176" w="7" h="8" />
<part id="drop_pixels_cancel_selected" x="192" y="184" w="7" h="8" />
<part id="warning_box" x="112" y="80" w="9" h="10" />
</parts>
<stylesheet>
@ -545,6 +546,15 @@
<background part="flag_highlight" color="flag_clicked" />
</style>
<!-- warning_box -->
<style id="warning_box">
<background color="face" />
<icon part="warning_box" align="center" valign="middle" />
</style>
<style id="warning_box:hover">
<background color="hot_face" />
</style>
</stylesheet>
</skin>

View File

@ -601,14 +601,10 @@ int Color::getIndex() const
return 0;
case Color::RgbType:
PRINTF("Getting `index' from a RGB color\n"); // TODO
ASSERT(false);
break;
return get_current_palette()->findBestfit(getRed(), getGreen(), getBlue());
case Color::HsvType:
PRINTF("Getting `index' from a HSV color\n"); // TODO
ASSERT(false);
break;
return get_current_palette()->findBestfit(getRed(), getGreen(), getBlue());
case Color::GrayType:
return m_value.gray;

View File

@ -168,10 +168,7 @@ raster::color_t color_utils::color_for_image(const app::Color& color, PixelForma
c = graya(color.getGray(), 255);
break;
case IMAGE_INDEXED:
if (color.getType() == app::Color::IndexType)
c = color.getIndex();
else
c = get_current_palette()->findBestfit(color.getRed(), color.getGreen(), color.getBlue());
c = color.getIndex();
break;
}

View File

@ -195,13 +195,12 @@ static void rectgrid(ui::Graphics* g, const gfx::Rect& rc, const gfx::Size& tile
}
}
static void draw_color(ui::Graphics* g, const Rect& rc, PixelFormat pixelFormat, const app::Color& color)
static void draw_color(ui::Graphics* g, const Rect& rc, const app::Color& color)
{
if (rc.w < 1 || rc.h < 1)
return;
app::Color::Type type = color.getType();
BITMAP* graph;
if (type == app::Color::MaskType) {
rectgrid(g, rc, gfx::Size(rc.w/4, rc.h/2));
@ -222,59 +221,12 @@ static void draw_color(ui::Graphics* g, const Rect& rc, PixelFormat pixelFormat,
return;
}
switch (pixelFormat) {
case IMAGE_INDEXED:
g->fillRect(
color_utils::color_for_ui(
app::Color::fromIndex(
color_utils::color_for_image(color, pixelFormat))),
rc);
break;
case IMAGE_RGB:
graph = create_bitmap_ex(32, rc.w, rc.h);
if (!graph)
return;
{
raster::color_t rgb_bitmap_color = color_utils::color_for_image(color, pixelFormat);
app::Color color2 = app::Color::fromRgb(rgba_getr(rgb_bitmap_color),
rgba_getg(rgb_bitmap_color),
rgba_getb(rgb_bitmap_color));
rectfill(graph, 0, 0, rc.w-1, rc.h-1,
color_utils::color_for_allegro(color2, 32));
}
g->blit(graph, 0, 0, rc.x, rc.y, rc.w, rc.h);
destroy_bitmap(graph);
break;
case IMAGE_GRAYSCALE:
graph = create_bitmap_ex(32, rc.w, rc.h);
if (!graph)
return;
{
int gray_bitmap_color = color_utils::color_for_image(color, pixelFormat);
app::Color color2 = app::Color::fromGray(graya_getv(gray_bitmap_color));
rectfill(graph, 0, 0, rc.w-1, rc.h-1,
color_utils::color_for_allegro(color2, 32));
}
g->blit(graph, 0, 0, rc.x, rc.y, rc.w, rc.h);
destroy_bitmap(graph);
break;
}
g->fillRect(color_utils::color_for_ui(color), rc);
}
void draw_color_button(ui::Graphics* g,
const Rect& rc,
bool outer_nw, bool outer_n, bool outer_ne, bool outer_e,
bool outer_se, bool outer_s, bool outer_sw, bool outer_w,
PixelFormat pixelFormat, const app::Color& color, bool hot, bool drag)
const Rect& rc, const app::Color& color,
bool hot, bool drag)
{
SkinTheme* theme = (SkinTheme*)ui::CurrentTheme::get();
int scale = ui::jguiscale();
@ -283,20 +235,20 @@ void draw_color_button(ui::Graphics* g,
draw_color(g,
Rect(rc.x+1*scale,
rc.y+1*scale,
rc.w-((outer_e) ? 2*scale: 1*scale),
rc.h-((outer_s) ? 2*scale: 1*scale)), pixelFormat, color);
rc.w-2*scale,
rc.h-2*scale), color);
// Draw opaque border
{
int parts[8] = {
outer_nw ? PART_COLORBAR_0_NW: PART_COLORBAR_3_NW,
outer_n ? PART_COLORBAR_0_N : PART_COLORBAR_2_N,
outer_ne ? PART_COLORBAR_1_NE: (outer_e ? PART_COLORBAR_3_NE: PART_COLORBAR_2_NE),
outer_e ? PART_COLORBAR_1_E : PART_COLORBAR_0_E,
outer_se ? PART_COLORBAR_3_SE: (outer_s ? PART_COLORBAR_2_SE: (outer_e ? PART_COLORBAR_1_SE: PART_COLORBAR_0_SE)),
outer_s ? PART_COLORBAR_2_S : PART_COLORBAR_0_S,
outer_sw ? PART_COLORBAR_2_SW: (outer_s ? PART_COLORBAR_3_SW: PART_COLORBAR_1_SW),
outer_w ? PART_COLORBAR_0_W : PART_COLORBAR_1_W,
PART_COLORBAR_0_NW,
PART_COLORBAR_0_N,
PART_COLORBAR_1_NE,
PART_COLORBAR_1_E,
PART_COLORBAR_3_SE,
PART_COLORBAR_2_S,
PART_COLORBAR_2_SW,
PART_COLORBAR_0_W
};
theme->draw_bounds_array(g, rc, parts);
}
@ -304,7 +256,7 @@ void draw_color_button(ui::Graphics* g,
// Draw hot
if (hot) {
theme->draw_bounds_nw(g,
gfx::Rect(rc.x, rc.y, rc.w, rc.h-1 - (outer_s ? 1*scale: 0)),
gfx::Rect(rc.x, rc.y, rc.w, rc.h-1 - 1*scale),
PART_COLORBAR_BORDER_HOTFG_NW);
}
}

View File

@ -35,11 +35,8 @@ namespace app {
void dotted_mode(int offset);
void draw_color_button(ui::Graphics* g,
const gfx::Rect& rc,
bool outer_nw, bool outer_n, bool outer_ne, bool outer_e,
bool outer_se, bool outer_s, bool outer_sw, bool outer_w,
PixelFormat pixelFormat, const app::Color& color,
bool hot, bool drag);
const gfx::Rect& rc, const app::Color& color,
bool hot, bool drag);
} // namespace app

View File

@ -197,11 +197,7 @@ void ColorButton::onPaint(PaintEvent& ev)
color = m_color;
draw_color_button(g, rc,
true, true, true, true,
true, true, true, true,
m_pixelFormat,
color,
hasMouseOver(), false);
color, hasMouseOver(), false);
// Draw text
std::string str = m_color.toHumanReadableString(m_pixelFormat,

View File

@ -1,5 +1,5 @@
/* Aseprite
* Copyright (C) 2001-2013 David Capello
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -25,21 +25,68 @@
#include "app/app.h"
#include "app/color.h"
#include "app/console.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/document.h"
#include "app/modules/gfx.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/ui/color_selector.h"
#include "app/ui/palette_view.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/skin/style.h"
#include "app/ui_context.h"
#include "app/undo_transaction.h"
#include "app/undoers/set_palette_colors.h"
#include "base/bind.h"
#include "base/scoped_value.h"
#include "gfx/border.h"
#include "gfx/size.h"
#include "raster/image.h"
#include "raster/image_bits.h"
#include "raster/palette.h"
#include "raster/sprite.h"
#include "raster/stock.h"
#include "ui/ui.h"
namespace app {
using namespace ui;
using namespace raster;
class ColorSelector::WarningIcon : public Button {
public:
WarningIcon() : Button("") {
}
private:
skin::Style* style() {
return skin::get_style("warning_box");
}
bool onProcessMessage(Message* msg) OVERRIDE {
switch (msg->type()) {
case kSetCursorMessage:
jmouse_set_cursor(kHandCursor);
return true;
}
return Button::onProcessMessage(msg);
}
void onPreferredSize(PreferredSizeEvent& ev) OVERRIDE {
ev.setPreferredSize(
style()->preferredSize(NULL, skin::Style::State()) + 4*jguiscale());
}
void onPaint(PaintEvent& ev) OVERRIDE {
Graphics* g = ev.getGraphics();
skin::Style::State state;
if (hasMouse()) state += skin::Style::hover();
if (isSelected()) state += skin::Style::clicked();
style()->paint(g, getClientBounds(), NULL, state);
}
};
ColorSelector::ColorSelector()
: PopupWindowPin("Color Selector", PopupWindow::kCloseOnClickInOtherWindow)
@ -53,6 +100,7 @@ ColorSelector::ColorSelector()
, m_grayButton("Gray", 1, kButtonWidget)
, m_maskButton("Mask", 1, kButtonWidget)
, m_maskLabel("Transparent Color Selected")
, m_warningIcon(new WarningIcon)
, m_disableHexUpdate(false)
{
m_topBox.setBorder(gfx::Border(0));
@ -75,6 +123,7 @@ ColorSelector::ColorSelector()
m_topBox.addChild(&m_grayButton);
m_topBox.addChild(&m_maskButton);
m_topBox.addChild(&m_hexColorEntry);
m_topBox.addChild(m_warningIcon);
{
Box* miniVbox = new Box(JI_VERTICAL);
miniVbox->addChild(getPin());
@ -94,6 +143,7 @@ ColorSelector::ColorSelector()
m_hsvButton.Click.connect(&ColorSelector::onColorTypeButtonClick, this);
m_grayButton.Click.connect(&ColorSelector::onColorTypeButtonClick, this);
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);
@ -107,6 +157,8 @@ ColorSelector::ColorSelector()
m_onPaletteChangeSlot =
App::instance()->PaletteChange.connect(&ColorSelector::onPaletteChange, this);
m_tooltips.addTooltipFor(m_warningIcon, "This color isn't in the palette\nPress here to add it.", JI_BOTTOM);
initTheme();
}
@ -135,6 +187,14 @@ void ColorSelector::setColor(const app::Color& color, SetColorOptions options)
if (options == ChangeType)
selectColorType(m_color.getType());
int index = get_current_palette()->findExactMatch(
m_color.getRed(),
m_color.getGreen(),
m_color.getBlue());
m_warningIcon->setVisible(index < 0);
m_warningIcon->getParent()->layout();
}
app::Color ColorSelector::getColor() const
@ -168,14 +228,116 @@ void ColorSelector::onColorHexEntryChange(const app::Color& color)
void ColorSelector::onColorTypeButtonClick(Event& ev)
{
RadioButton* source = static_cast<RadioButton*>(ev.getSource());
app::Color newColor;
if (source == &m_indexButton) selectColorType(app::Color::IndexType);
else if (source == &m_rgbButton) selectColorType(app::Color::RgbType);
else if (source == &m_hsvButton) selectColorType(app::Color::HsvType);
else if (source == &m_grayButton) selectColorType(app::Color::GrayType);
else if (source == &m_maskButton) {
// Select mask color directly when the radio button is pressed
setColorWithSignal(app::Color::fromMask());
if (source == &m_indexButton) newColor = app::Color::fromIndex(getColor().getIndex());
else if (source == &m_rgbButton) newColor = app::Color::fromRgb(getColor().getRed(), getColor().getGreen(), getColor().getBlue());
else if (source == &m_hsvButton) newColor = app::Color::fromHsv(getColor().getHue(), getColor().getSaturation(), getColor().getValue());
else if (source == &m_grayButton) newColor = app::Color::fromGray(getColor().getGray());
else if (source == &m_maskButton) newColor = app::Color::fromMask();
setColorWithSignal(newColor);
}
void ColorSelector::onFixWarningClick(ui::Event& ev)
{
try {
Palette* newPalette = get_current_palette(); // System current pal
color_t newColor = rgba(
m_color.getRed(),
m_color.getGreen(),
m_color.getBlue());
int index = newPalette->findExactMatch(
m_color.getRed(),
m_color.getGreen(),
m_color.getBlue());
// It should be -1, because the user has pressed the warning
// button that is available only when the color isn't in the
// palette.
ASSERT(index < 0);
if (index >= 0)
return;
int lastUsed = -1;
ContextWriter writer(UIContext::instance());
Document* document(writer.document());
Sprite* sprite = NULL;
if (document) {
sprite = writer.sprite();
// Find used entries in all stock images. In this way we can start
// looking for duplicated color entries in the palette from the
// last used one.
if (sprite->getPixelFormat() == IMAGE_INDEXED) {
lastUsed = sprite->getTransparentColor();
Stock* stock = sprite->getStock();
for (int i=0; i<(int)stock->size(); ++i) {
Image* image = stock->getImage(i);
if (!image)
continue;
const LockImageBits<IndexedTraits> bits(image);
for (LockImageBits<IndexedTraits>::const_iterator it=bits.begin(); it!=bits.end(); ++it) {
if (lastUsed < *it)
lastUsed = *it;
}
}
}
}
for (int i=lastUsed+1; i<(int)newPalette->size(); ++i) {
color_t c = newPalette->getEntry(i);
int altI = newPalette->findExactMatch(
rgba_getr(c), rgba_getg(c), rgba_getb(c));
if (altI < i) {
index = i;
break;
}
}
if (index < 0) {
if (newPalette->size() < Palette::MaxColors) {
newPalette->addEntry(newColor);
index = newPalette->size()-1;
}
if (index < 0) {
Alert::show(
"Error<<The palette is full."
"<<You cannot have more than %d colors.<<&OK",
Palette::MaxColors);
return;
}
}
else {
newPalette->setEntry(index, newColor);
}
if (document) {
FrameNumber frame = writer.frame();
UndoTransaction undoTransaction(writer.context(), "Add palette entry", undo::ModifyDocument);
undoTransaction.pushUndoer
(new undoers::SetPaletteColors(undoTransaction.getObjects(),
sprite, sprite->getPalette(frame),
frame, index, index));
sprite->setPalette(newPalette, false);
undoTransaction.commit();
}
set_current_palette(newPalette, false);
ui::Manager::getDefault()->invalidate();
m_warningIcon->setVisible(index < 0);
m_warningIcon->getParent()->layout();
}
catch (base::Exception& e) {
Console::showException(e);
}
}

View File

@ -29,6 +29,7 @@
#include "ui/button.h"
#include "ui/grid.h"
#include "ui/label.h"
#include "ui/tooltips.h"
#include "ui/view.h"
namespace app {
@ -54,6 +55,7 @@ namespace app {
void onColorSlidersChange(ColorSlidersChangeEvent& ev);
void onColorHexEntryChange(const app::Color& color);
void onColorTypeButtonClick(ui::Event& ev);
void onFixWarningClick(ui::Event& ev);
void onPaletteChange();
private:
@ -61,6 +63,9 @@ namespace app {
void setColorWithSignal(const app::Color& color);
void findBestfitIndex(const app::Color& color);
class WarningIcon;
ui::TooltipManager m_tooltips;
ui::Box m_vbox;
ui::Box m_topBox;
app::Color m_color;
@ -76,6 +81,7 @@ namespace app {
HsvSliders m_hsvSliders;
GraySlider m_graySlider;
ui::Label m_maskLabel;
WarningIcon* m_warningIcon;
Signal0<void>::SlotType* m_onPaletteChangeSlot;
// This variable is used to avoid updating the m_hexColorEntry text

View File

@ -165,6 +165,23 @@ void Rules::paint(ui::Graphics* g,
if (m_text) m_text->paint(g, bounds, text);
}
gfx::Size Rules::preferredSize(const char* text)
{
gfx::Size sz(0, 0);
if (m_icon) {
sz.w += m_icon->getPart()->getBitmap(0)->w;
sz.h = m_icon->getPart()->getBitmap(0)->h;
}
if (m_text && text) {
ui::ScreenGraphics g;
gfx::Size textSize = g.measureString(text);
//if (sz.w > 0) sz.w += 2; // TODO text separation
sz.w += textSize.w;
sz.h = MAX(sz.h, textSize.h);
}
return sz;
}
Style::Style(css::Sheet& sheet, const std::string& id)
: m_id(id)
, m_compoundStyle(sheet.compoundStyle(id))
@ -179,10 +196,7 @@ Style::~Style()
}
}
void Style::paint(ui::Graphics* g,
const gfx::Rect& bounds,
const char* text,
const State& state)
Rules* Style::getRulesFromState(const State& state)
{
Rules* rules = NULL;
@ -195,7 +209,22 @@ void Style::paint(ui::Graphics* g,
m_rules[state] = rules;
}
rules->paint(g, bounds, text);
return rules;
}
void Style::paint(ui::Graphics* g,
const gfx::Rect& bounds,
const char* text,
const State& state)
{
getRulesFromState(state)->paint(g, bounds, text);
}
gfx::Size Style::preferredSize(
const char* text,
const State& state)
{
return getRulesFromState(state)->preferredSize(text);
}
} // namespace skin

View File

@ -94,6 +94,8 @@ namespace app {
void setAlign(int align) { m_align = align; }
void setPart(const SkinPartPtr& part) { m_part = part; }
SkinPartPtr getPart() { return m_part; }
protected:
void onPaint(ui::Graphics* g, const gfx::Rect& bounds, const char* text) OVERRIDE;
@ -111,6 +113,8 @@ namespace app {
const gfx::Rect& bounds,
const char* text);
gfx::Size preferredSize(const char* text);
private:
BackgroundRule* m_background;
TextRule* m_text;
@ -135,11 +139,17 @@ namespace app {
const char* text,
const State& state);
gfx::Size preferredSize(
const char* text,
const State& state);
const std::string& id() const { return m_id; }
private:
typedef std::map<State, Rules*> RulesMap;
Rules* getRulesFromState(const State& state);
std::string m_id;
css::CompoundStyle m_compoundStyle;
RulesMap m_rules;

View File

@ -499,10 +499,7 @@ void StatusBar::onPaint(ui::PaintEvent& ev)
// Draw color
draw_color_button(g, gfx::Rect(x, rc.y, 32*jguiscale(), rc.h),
true, true, true, true,
true, true, true, true,
app_get_current_pixel_format(), m_color,
false, false);
m_color, false, false);
x += (32+4)*jguiscale();