Convert InkShadesField in a combobox-like widget to store several shades (#85)

Changes:
* Added new ui::IconButton widget to create widgets with one skin icon
  that is colored depending on the button state.
* ContextBar::InkShadesField was divided into a couple widgets (ShadeWidget
  and IconButton to drop-down a little menu with saved shades)
* Fixed CommonLockedSurface::drawColoredRgbaSurface() impl.
* Removed invalid gfx::setr/g/b/a() functions.
This commit is contained in:
David Capello 2015-11-24 18:37:13 -03:00
parent 25a331c43a
commit cf7c4754cc
13 changed files with 428 additions and 140 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -406,6 +406,8 @@
<part id="no_symmetry" x="144" y="240" w="13" h="13" />
<part id="horizontal_symmetry" x="160" y="240" w="13" h="13" />
<part id="vertical_symmetry" x="176" y="240" w="13" h="13" />
<part id="icon_arrow_down" x="144" y="256" w="7" h="4" />
<part id="icon_close" x="153" y="256" w="7" h="7" />
</parts>
<stylesheet>

View File

@ -363,6 +363,7 @@ add_library(app-lib
ui/frame_tag_window.cpp
ui/hex_color_entry.cpp
ui/home_view.cpp
ui/icon_button.cpp
ui/input_chain.cpp
ui/keyboard_shortcuts.cpp
ui/main_menu_bar.cpp

View File

@ -52,7 +52,7 @@ void SwitchColorsCommand::onExecute(Context* context)
const auto& toolPref(Preferences::instance().tool(tool));
if (toolPref.ink() == tools::InkType::SHADING) {
App::instance()->getMainWindow()->
getContextBar()->reverseShadesColors();
getContextBar()->reverseShadeColors();
}
}

22
src/app/shade.h Normal file
View File

@ -0,0 +1,22 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_SHADE_H_INCLUDED
#define APP_SHADE_H_INCLUDED
#pragma once
#include "app/color.h"
#include <vector>
namespace app {
typedef std::vector<app::Color> Shade;
} // namespace app
#endif

View File

@ -18,6 +18,7 @@
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/shade.h"
#include "app/tools/controller.h"
#include "app/tools/ink.h"
#include "app/tools/ink_type.h"
@ -28,6 +29,8 @@
#include "app/ui/brush_popup.h"
#include "app/ui/button_set.h"
#include "app/ui/color_button.h"
#include "app/ui/icon_button.h"
#include "app/ui/skin/button_icon_impl.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/skin/style.h"
#include "app/ui_context.h"
@ -429,122 +432,288 @@ protected:
ContextBar* m_owner;
};
class ContextBar::InkShadesField : public Widget {
typedef std::vector<app::Color> Colors;
public:
class ContextBar::InkShadesField : public HBox {
InkShadesField() : Widget(kGenericWidget) {
setText("Select colors in the palette");
}
class ShadeWidget : public Widget {
public:
enum ClickType { DragAndDrop, Select };
void reverseColors() {
std::reverse(m_colors.begin(), m_colors.end());
invalidate();
}
Signal0<void> Click;
doc::Remap* createShadesRemap(bool left) {
base::UniquePtr<doc::Remap> remap;
Colors colors = getColors();
ShadeWidget(const Shade& colors, ClickType click)
: Widget(kGenericWidget)
, m_click(click)
, m_shade(colors)
, m_hotIndex(-1)
, m_boxSize(12) {
setText("Select colors in the palette");
}
if (colors.size() > 0) {
remap.reset(new doc::Remap(get_current_palette()->size()));
void reverseShadeColors() {
std::reverse(m_shade.begin(), m_shade.end());
invalidate();
}
for (int i=0; i<remap->size(); ++i)
remap->map(i, i);
doc::Remap* createShadeRemap(bool left) {
base::UniquePtr<doc::Remap> remap;
Shade colors = getShade();
if (left) {
for (int i=1; i<int(colors.size()); ++i)
remap->map(colors[i].getIndex(), colors[i-1].getIndex());
if (colors.size() > 0) {
remap.reset(new doc::Remap(get_current_palette()->size()));
for (int i=0; i<remap->size(); ++i)
remap->map(i, i);
if (left) {
for (int i=1; i<int(colors.size()); ++i)
remap->map(colors[i].getIndex(), colors[i-1].getIndex());
}
else {
for (int i=0; i<int(colors.size())-1; ++i)
remap->map(colors[i].getIndex(), colors[i+1].getIndex());
}
}
return remap.release();
}
int size() const {
int colors = 0;
for (const auto& color : m_shade) {
if (color.getIndex() >= 0 &&
color.getIndex() < get_current_palette()->size())
++colors;
}
return colors;
}
Shade getShade() const {
Shade colors;
for (const auto& color : m_shade) {
if (color.getIndex() >= 0 &&
color.getIndex() < get_current_palette()->size())
colors.push_back(color);
}
return colors;
}
void setShade(const Shade& shade) {
m_shade = shade;
invalidate();
getParent()->getParent()->layout();
}
private:
void onChangeColorBarSelection() {
if (!isVisible())
return;
doc::PalettePicks picks;
ColorBar::instance()->getPaletteView()->getSelectedEntries(picks);
m_shade.resize(picks.picks());
int i = 0, j = 0;
for (bool pick : picks) {
if (pick)
m_shade[j++] = app::Color::fromIndex(i);
++i;
}
getParent()->getParent()->layout();
}
bool onProcessMessage(ui::Message* msg) override {
switch (msg->type()) {
case kOpenMessage:
ColorBar::instance()->ChangeSelection.connect(
Bind<void>(&ShadeWidget::onChangeColorBarSelection, this));
break;
case kMouseEnterMessage:
case kMouseLeaveMessage:
invalidate();
break;
case kMouseUpMessage: {
if (m_click == Select) {
setSelected(true);
Click();
closeWindow();
break;
}
break;
}
case kMouseMoveMessage: {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
gfx::Point mousePos = mouseMsg->position() - getBounds().getOrigin();
gfx::Rect bounds = getClientBounds();
int hot = -1;
bounds.shrink(3*guiscale());
if (bounds.contains(mousePos)) {
int count = size();
hot = (mousePos.x - bounds.x) / m_boxSize;
hot = MID(0, hot, count-1);
}
if (m_hotIndex != hot) {
m_hotIndex = hot;
invalidate();
}
break;
}
case kMouseDownMessage: {
break;
}
}
return Widget::onProcessMessage(msg);
}
void onPreferredSize(PreferredSizeEvent& ev) override {
int size = this->size();
if (size < 2)
ev.setPreferredSize(Size((16+m_boxSize)*guiscale()+getTextWidth(), 18*guiscale()));
else
ev.setPreferredSize(Size(6+m_boxSize*size, 18)*guiscale());
}
void onPaint(PaintEvent& ev) override {
SkinTheme* theme = SkinTheme::instance();
Graphics* g = ev.getGraphics();
gfx::Rect bounds = getClientBounds();
gfx::Color bg = getBgColor();
if (m_click == Select && hasMouseOver())
bg = theme->colors.menuitemHighlightFace();
g->fillRect(bg, bounds);
Shade colors = getShade();
if (colors.size() >= 2) {
int w = (6+m_boxSize*colors.size())*guiscale();
if (bounds.w > w)
bounds.w = w;
}
skin::Style::State state;
if (hasMouseOver()) state += Style::hover();
theme->styles.view()->paint(g, bounds, nullptr, state);
bounds.shrink(3*guiscale());
gfx::Rect box(bounds.x, bounds.y, m_boxSize*guiscale(), bounds.h);
if (colors.size() >= 2) {
for (int i=0; i<int(colors.size()); ++i) {
if (i == int(colors.size())-1)
box.w = bounds.x+bounds.w-box.x;
draw_color(g, box, colors[i]);
box.x += box.w;
}
}
else {
for (int i=0; i<int(colors.size())-1; ++i)
remap->map(colors[i].getIndex(), colors[i+1].getIndex());
g->fillRect(theme->colors.editorFace(), bounds);
g->drawAlignedUIString(getText(), theme->colors.face(), gfx::ColorNone, bounds,
ui::CENTER | ui::MIDDLE);
}
}
return remap.release();
ClickType m_click;
Shade m_shade;
int m_hotIndex;
int m_boxSize;
};
public:
InkShadesField() :
m_button(SkinTheme::instance()->parts.iconArrowDown()->getBitmap(0)),
m_shade(Shade(), ShadeWidget::DragAndDrop) {
SkinTheme* theme = SkinTheme::instance();
m_shade.setBgColor(theme->colors.workspace());
m_button.setBgColor(theme->colors.workspace());
noBorderNoChildSpacing();
addChild(&m_button);
addChild(&m_shade);
m_button.setFocusStop(false);
m_button.Click.connect(Bind<void>(&InkShadesField::onShowMenu, this));
}
void reverseShadeColors() {
m_shade.reverseShadeColors();
}
doc::Remap* createShadeRemap(bool left) {
return m_shade.createShadeRemap(left);
}
private:
void onShowMenu() {
gfx::Rect bounds = m_button.getBounds();
Colors getColors() const {
Colors colors;
for (const auto& color : m_colors) {
if (color.getIndex() >= 0 &&
color.getIndex() < get_current_palette()->size())
colors.push_back(color);
}
return colors;
}
Menu menu;
MenuItem
reverse("Reverse Shade"),
save("Save Shade");
menu.addChild(&reverse);
menu.addChild(&save);
void onChangeColorBarSelection() {
if (!isVisible())
return;
bool hasShade = (m_shade.size() >= 2);
reverse.setEnabled(hasShade);
save.setEnabled(hasShade);
reverse.Click.connect(Bind<void>(&InkShadesField::reverseShadeColors, this));
save.Click.connect(Bind<void>(&InkShadesField::onSaveShade, this));
doc::PalettePicks picks;
ColorBar::instance()->getPaletteView()->getSelectedEntries(picks);
if (!m_shades.empty()) {
SkinTheme* theme = SkinTheme::instance();
m_colors.resize(picks.picks());
menu.addChild(new MenuSeparator);
int i = 0, j = 0;
for (bool pick : picks) {
if (pick)
m_colors[j++] = app::Color::fromIndex(i);
++i;
}
int i = 0;
for (const Shade& shade : m_shades) {
auto shadeWidget = new ShadeWidget(shade, ShadeWidget::Select);
shadeWidget->setExpansive(true);
shadeWidget->setBgColor(theme->colors.menuitemNormalFace());
shadeWidget->Click.connect(
[&]{
m_shade.setShade(shade);
});
getParent()->layout();
}
auto close = new IconButton(theme->parts.iconClose()->getBitmap(0));
close->setBgColor(theme->colors.menuitemNormalFace());
close->Click.connect(
Bind<void>(
[this, i, close]{
m_shades.erase(m_shades.begin()+i);
close->closeWindow();
}));
bool onProcessMessage(ui::Message* msg) override {
if (msg->type() == kOpenMessage) {
ColorBar::instance()->ChangeSelection.connect(
Bind<void>(&InkShadesField::onChangeColorBarSelection, this));
}
return Widget::onProcessMessage(msg);
}
void onPreferredSize(PreferredSizeEvent& ev) override {
int size = getColors().size();
if (size < 2)
ev.setPreferredSize(Size(16*guiscale()+getTextWidth(), 18*guiscale()));
else
ev.setPreferredSize(Size(6+12*size, 18)*guiscale());
}
void onPaint(PaintEvent& ev) override {
SkinTheme* theme = SkinTheme::instance();
Graphics* g = ev.getGraphics();
gfx::Rect bounds = getClientBounds();
skin::Style::State state;
if (hasMouseOver()) state += Style::hover();
g->fillRect(theme->colors.workspace(), bounds);
theme->styles.view()->paint(g, bounds, nullptr, state);
bounds.shrink(3*guiscale());
gfx::Rect box(bounds.x, bounds.y, 12*guiscale(), bounds.h);
Colors colors = getColors();
if (colors.size() >= 2) {
for (int i=0; i<int(colors.size()); ++i) {
if (i == int(colors.size())-1)
box.w = bounds.x+bounds.w-box.x;
draw_color(g, box, colors[i]);
box.x += box.w;
auto item = new HBox();
item->noBorderNoChildSpacing();
item->addChild(shadeWidget);
item->addChild(close);
menu.addChild(item);
++i;
}
}
else {
g->fillRect(theme->colors.editorFace(), bounds);
g->drawAlignedUIString(getText(), theme->colors.face(), gfx::ColorNone, bounds,
ui::CENTER | ui::MIDDLE);
}
menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
m_button.invalidate();
}
std::vector<app::Color> m_colors;
void onSaveShade() {
m_shades.push_back(m_shade.getShade());
}
IconButton m_button;
ShadeWidget m_shade;
std::vector<Shade> m_shades;
};
class ContextBar::InkOpacityField : public IntEntry
@ -1502,14 +1671,14 @@ doc::BrushRef ContextBar::createBrushFromPreferences(ToolPreferences::Brush* bru
return brush;
}
doc::Remap* ContextBar::createShadesRemap(bool left)
doc::Remap* ContextBar::createShadeRemap(bool left)
{
return m_inkShades->createShadesRemap(left);
return m_inkShades->createShadeRemap(left);
}
void ContextBar::reverseShadesColors()
void ContextBar::reverseShadeColors()
{
m_inkShades->reverseColors();
m_inkShades->reverseShadeColors();
}
} // namespace app

View File

@ -70,8 +70,8 @@ namespace app {
static doc::BrushRef createBrushFromPreferences(
ToolPreferences::Brush* brushPref = nullptr);
doc::Remap* createShadesRemap(bool left);
void reverseShadesColors();
doc::Remap* createShadeRemap(bool left);
void reverseShadeColors();
// Signals
Signal0<void> BrushChange;

View File

@ -169,7 +169,7 @@ public:
if (m_toolPref.ink() == tools::InkType::SHADING) {
m_shadingRemap.reset(
App::instance()->getMainWindow()->getContextBar()->createShadesRemap(
App::instance()->getMainWindow()->getContextBar()->createShadeRemap(
button == tools::ToolLoop::Left));
}
}

View File

@ -0,0 +1,68 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/icon_button.h"
#include "app/ui/skin/skin_theme.h"
#include "she/surface.h"
#include "ui/message.h"
#include "ui/paint_event.h"
#include "ui/preferred_size_event.h"
#include "ui/system.h"
namespace app {
using namespace ui;
using namespace app::skin;
IconButton::IconButton(she::Surface* icon)
: Button("")
, m_icon(icon)
{
setBgColor(SkinTheme::instance()->colors.menuitemNormalFace());
}
void IconButton::onPreferredSize(PreferredSizeEvent& ev)
{
ev.setPreferredSize(
gfx::Size(m_icon->width(),
m_icon->height()) + 4*guiscale());
}
void IconButton::onPaint(PaintEvent& ev)
{
SkinTheme* theme = SkinTheme::instance();
Graphics* g = ev.getGraphics();
gfx::Color fg, bg;
if (isSelected()) {
fg = theme->colors.menuitemHighlightText();
bg = theme->colors.menuitemHighlightFace();
}
else if (isEnabled() && hasMouseOver()) {
fg = theme->colors.menuitemHotText();
bg = theme->colors.menuitemHotFace();
}
else {
fg = theme->colors.menuitemNormalText();
bg = getBgColor();
}
g->fillRect(bg, g->getClipBounds());
gfx::Rect bounds = getClientBounds();
g->drawColoredRgbaSurface(
m_icon, fg,
bounds.x+bounds.w/2-m_icon->width()/2,
bounds.y+bounds.h/2-m_icon->height()/2);
}
} // namespace app

31
src/app/ui/icon_button.h Normal file
View File

@ -0,0 +1,31 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_UI_ICON_BUTTON_H_INCLUDED
#define APP_UI_ICON_BUTTON_H_INCLUDED
#pragma once
#include "ui/button.h"
namespace app {
class IconButton : public ui::Button {
public:
IconButton(she::Surface* icon);
protected:
// bool onProcessMessage(ui::Message* msg) override;
void onPreferredSize(ui::PreferredSizeEvent& ev) override;
void onPaint(ui::PaintEvent& ev) override;
private:
she::Surface* m_icon;
};
} // namespace app
#endif

View File

@ -940,7 +940,7 @@ void Tabs::createFloatingOverlay(Tab* tab)
gfx::Color c = lock->getPixel(x, y);
c = (c != gfx::rgba(255, 0, 255, 0) &&
c != gfx::rgba(255, 0, 255, 255) ?
gfx::seta(c, 255):
gfx::rgba(gfx::getr(c), gfx::getg(c), gfx::getb(c), 255):
gfx::ColorNone);
lock->putPixel(c, x, y);
}

View File

@ -1,5 +1,5 @@
// Aseprite Gfx Library
// Copyright (C) 2001-2014 David Capello
// Copyright (C) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -32,11 +32,6 @@ namespace gfx {
inline ColorComponent getb(Color c) { return (c >> ColorBShift) & 0xff; }
inline ColorComponent geta(Color c) { return (c >> ColorAShift) & 0xff; }
inline Color setr(Color c, ColorComponent v) { return Color((c & ~ColorRShift) | (v << ColorRShift)); }
inline Color setg(Color c, ColorComponent v) { return Color((c & ~ColorGShift) | (v << ColorGShift)); }
inline Color setb(Color c, ColorComponent v) { return Color((c & ~ColorBShift) | (v << ColorBShift)); }
inline Color seta(Color c, ColorComponent v) { return Color((c & ~ColorAShift) | (v << ColorAShift)); }
inline bool is_transparent(Color c) { return geta(c) == 0; }
} // namespace gfx

View File

@ -15,43 +15,39 @@
namespace she {
namespace {
namespace {
#define INT_MULT(a, b, t) \
#define MUL_UN8(a, b, t) \
((t) = (a) * (b) + 0x80, ((((t) >> 8) + (t)) >> 8))
gfx::Color blend(const gfx::Color back, gfx::Color front)
gfx::Color blend(const gfx::Color backdrop, gfx::Color src)
{
if (gfx::geta(backdrop) == 0)
return src;
else if (gfx::geta(src) == 0)
return backdrop;
int Br, Bg, Bb, Ba;
int Sr, Sg, Sb, Sa;
int Rr, Rg, Rb, Ra;
Br = gfx::getr(backdrop);
Bg = gfx::getg(backdrop);
Bb = gfx::getb(backdrop);
Ba = gfx::geta(backdrop);
Sr = gfx::getr(src);
Sg = gfx::getg(src);
Sb = gfx::getb(src);
Sa = gfx::geta(src);
int t;
Ra = Ba + Sa - MUL_UN8(Ba, Sa, t);
Rr = Br + (Sr-Br) * Sa / Ra;
Rg = Bg + (Sg-Bg) * Sa / Ra;
Rb = Bb + (Sb-Bb) * Sa / Ra;
if (gfx::geta(back) == 0) {
return front;
}
else if (gfx::geta(front) == 0) {
return back;
}
else {
int B_r, B_g, B_b, B_a;
int F_r, F_g, F_b, F_a;
int D_r, D_g, D_b, D_a;
B_r = gfx::getr(back);
B_g = gfx::getg(back);
B_b = gfx::getb(back);
B_a = gfx::geta(back);
F_r = gfx::getr(front);
F_g = gfx::getg(front);
F_b = gfx::getb(front);
F_a = gfx::geta(front);
D_a = B_a + F_a - INT_MULT(B_a, F_a, t);
D_r = B_r + (F_r-B_r) * F_a / D_a;
D_g = B_g + (F_g-B_g) * F_a / D_a;
D_b = B_b + (F_b-B_b) * F_a / D_a;
return gfx::rgba(D_r, D_g, D_b, D_a);
}
return gfx::rgba(Rr, Rg, Rb, Ra);
}
} // anoynmous namespace
@ -79,9 +75,13 @@ public:
if (gfx::geta(bg) > 0)
dstColor = blend(dstColor, bg);
int srcAlpha = (((*ptr) & format.alphaMask) >> format.alphaShift);
if (srcAlpha > 0)
dstColor = blend(dstColor, gfx::seta(fg, srcAlpha));
uint32_t src = (((*ptr) & format.alphaMask) >> format.alphaShift);
if (src > 0) {
src = gfx::rgba(gfx::getr(fg),
gfx::getg(fg),
gfx::getb(fg), src);
dstColor = blend(dstColor, src);
}
putPixel(dstColor, clip.dst.x+u, clip.dst.y+v);
++ptr;