mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-28 16:20:50 +00:00
469 lines
14 KiB
C++
469 lines
14 KiB
C++
// 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/brush_popup.h"
|
|
|
|
#include "app/app.h"
|
|
#include "app/brush_slot.h"
|
|
#include "app/commands/command.h"
|
|
#include "app/commands/commands.h"
|
|
#include "app/modules/gui.h"
|
|
#include "app/modules/palettes.h"
|
|
#include "app/pref/preferences.h"
|
|
#include "app/ui/app_menuitem.h"
|
|
#include "app/ui/button_set.h"
|
|
#include "app/ui/context_bar.h"
|
|
#include "app/ui/keyboard_shortcuts.h"
|
|
#include "app/ui/main_window.h"
|
|
#include "app/ui/skin/skin_theme.h"
|
|
#include "app/ui_context.h"
|
|
#include "base/bind.h"
|
|
#include "base/convert_to.h"
|
|
#include "doc/brush.h"
|
|
#include "doc/conversion_she.h"
|
|
#include "doc/image.h"
|
|
#include "doc/palette.h"
|
|
#include "gfx/border.h"
|
|
#include "gfx/region.h"
|
|
#include "she/scoped_surface_lock.h"
|
|
#include "she/surface.h"
|
|
#include "she/system.h"
|
|
#include "ui/button.h"
|
|
#include "ui/link_label.h"
|
|
#include "ui/listitem.h"
|
|
#include "ui/menu.h"
|
|
#include "ui/message.h"
|
|
#include "ui/separator.h"
|
|
|
|
#include "brush_slot_params.xml.h"
|
|
|
|
namespace app {
|
|
|
|
using namespace app::skin;
|
|
using namespace doc;
|
|
using namespace ui;
|
|
|
|
namespace {
|
|
|
|
void show_popup_menu(PopupWindow* popupWindow, Menu* popupMenu,
|
|
const gfx::Point& pt)
|
|
{
|
|
// Here we make the popup window temporaly floating, so it's
|
|
// not closed by the popup menu.
|
|
popupWindow->makeFloating();
|
|
|
|
popupMenu->showPopup(pt);
|
|
|
|
// Add the menu popup region to the window popup hot region so it's
|
|
// not closed after we close the menu.
|
|
popupWindow->makeFixed();
|
|
|
|
gfx::Region rgn;
|
|
rgn.createUnion(gfx::Region(popupWindow->bounds()),
|
|
gfx::Region(popupMenu->bounds()));
|
|
popupWindow->setHotRegion(rgn);
|
|
}
|
|
|
|
class SelectBrushItem : public ButtonSet::Item {
|
|
public:
|
|
SelectBrushItem(const BrushSlot& brush, int slot = -1)
|
|
: m_brushes(App::instance()->brushes())
|
|
, m_brush(brush)
|
|
, m_slot(slot) {
|
|
if (m_brush.hasBrush()) {
|
|
SkinPartPtr icon(new SkinPart);
|
|
icon->setBitmap(0, BrushPopup::createSurfaceForBrush(m_brush.brush()));
|
|
setIcon(icon);
|
|
}
|
|
}
|
|
|
|
const BrushSlot& brush() const {
|
|
return m_brush;
|
|
}
|
|
|
|
private:
|
|
void onClick() override {
|
|
ContextBar* contextBar =
|
|
App::instance()->getMainWindow()->getContextBar();
|
|
|
|
if (m_slot >= 0)
|
|
contextBar->setActiveBrushBySlot(m_slot);
|
|
else if (m_brush.hasBrush())
|
|
contextBar->setActiveBrush(m_brush.brush());
|
|
}
|
|
|
|
AppBrushes& m_brushes;
|
|
BrushSlot m_brush;
|
|
int m_slot;
|
|
};
|
|
|
|
class BrushShortcutItem : public ButtonSet::Item {
|
|
public:
|
|
BrushShortcutItem(const std::string& text, int slot)
|
|
: m_slot(slot) {
|
|
setText(text);
|
|
}
|
|
|
|
private:
|
|
void onClick() override {
|
|
Params params;
|
|
params.set("change", "custom");
|
|
params.set("slot", base::convert_to<std::string>(m_slot).c_str());
|
|
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::ChangeBrush);
|
|
cmd->loadParams(params);
|
|
std::string search = cmd->friendlyName();
|
|
if (!search.empty()) {
|
|
params.clear();
|
|
params.set("search", search.c_str());
|
|
cmd = CommandsModule::instance()->getCommandByName(CommandId::KeyboardShortcuts);
|
|
ASSERT(cmd);
|
|
if (cmd)
|
|
UIContext::instance()->executeCommand(cmd, params);
|
|
}
|
|
}
|
|
|
|
int m_slot;
|
|
};
|
|
|
|
class BrushOptionsItem : public ButtonSet::Item {
|
|
public:
|
|
BrushOptionsItem(BrushPopup* popup, int slot)
|
|
: m_popup(popup)
|
|
, m_brushes(App::instance()->brushes())
|
|
, m_slot(slot) {
|
|
setIcon(SkinTheme::instance()->parts.iconArrowDown(), true);
|
|
}
|
|
|
|
private:
|
|
|
|
void onClick() override {
|
|
Menu menu;
|
|
AppMenuItem save("Save Brush Here");
|
|
AppMenuItem lockItem("Locked");
|
|
AppMenuItem deleteItem("Delete");
|
|
AppMenuItem deleteAllItem("Delete All");
|
|
|
|
lockItem.setSelected(m_brushes.isBrushSlotLocked(m_slot));
|
|
|
|
save.Click.connect(&BrushOptionsItem::onSaveBrush, this);
|
|
lockItem.Click.connect(&BrushOptionsItem::onLockBrush, this);
|
|
deleteItem.Click.connect(&BrushOptionsItem::onDeleteBrush, this);
|
|
deleteAllItem.Click.connect(&BrushOptionsItem::onDeleteAllBrushes, this);
|
|
|
|
menu.addChild(&save);
|
|
menu.addChild(new MenuSeparator);
|
|
menu.addChild(&lockItem);
|
|
menu.addChild(&deleteItem);
|
|
menu.addChild(new MenuSeparator);
|
|
menu.addChild(&deleteAllItem);
|
|
menu.addChild(new Label(""));
|
|
menu.addChild(new Separator("Saved Parameters", HORIZONTAL));
|
|
|
|
app::gen::BrushSlotParams params;
|
|
menu.addChild(¶ms);
|
|
|
|
// Load preferences
|
|
BrushSlot brush = m_brushes.getBrushSlot(m_slot);
|
|
params.brushType()->setSelected(brush.hasFlag(BrushSlot::Flags::BrushType));
|
|
params.brushSize()->setSelected(brush.hasFlag(BrushSlot::Flags::BrushSize));
|
|
params.brushAngle()->setSelected(brush.hasFlag(BrushSlot::Flags::BrushAngle));
|
|
params.fgColor()->setSelected(brush.hasFlag(BrushSlot::Flags::FgColor));
|
|
params.bgColor()->setSelected(brush.hasFlag(BrushSlot::Flags::BgColor));
|
|
params.inkType()->setSelected(brush.hasFlag(BrushSlot::Flags::InkType));
|
|
params.inkOpacity()->setSelected(brush.hasFlag(BrushSlot::Flags::InkOpacity));
|
|
params.shade()->setSelected(brush.hasFlag(BrushSlot::Flags::Shade));
|
|
params.pixelPerfect()->setSelected(brush.hasFlag(BrushSlot::Flags::PixelPerfect));
|
|
|
|
m_changeFlags = true;
|
|
show_popup_menu(m_popup, &menu,
|
|
gfx::Point(origin().x, origin().y+bounds().h));
|
|
|
|
if (m_changeFlags) {
|
|
brush = m_brushes.getBrushSlot(m_slot);
|
|
|
|
int flags = (int(brush.flags()) & int(BrushSlot::Flags::Locked));
|
|
if (params.brushType()->isSelected()) flags |= int(BrushSlot::Flags::BrushType);
|
|
if (params.brushSize()->isSelected()) flags |= int(BrushSlot::Flags::BrushSize);
|
|
if (params.brushAngle()->isSelected()) flags |= int(BrushSlot::Flags::BrushAngle);
|
|
if (params.fgColor()->isSelected()) flags |= int(BrushSlot::Flags::FgColor);
|
|
if (params.bgColor()->isSelected()) flags |= int(BrushSlot::Flags::BgColor);
|
|
if (params.inkType()->isSelected()) flags |= int(BrushSlot::Flags::InkType);
|
|
if (params.inkOpacity()->isSelected()) flags |= int(BrushSlot::Flags::InkOpacity);
|
|
if (params.shade()->isSelected()) flags |= int(BrushSlot::Flags::Shade);
|
|
if (params.pixelPerfect()->isSelected()) flags |= int(BrushSlot::Flags::PixelPerfect);
|
|
|
|
if (brush.flags() != BrushSlot::Flags(flags)) {
|
|
brush.setFlags(BrushSlot::Flags(flags));
|
|
m_brushes.setBrushSlot(m_slot, brush);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
void onSaveBrush() {
|
|
ContextBar* contextBar =
|
|
App::instance()->getMainWindow()->getContextBar();
|
|
|
|
m_brushes.setBrushSlot(
|
|
m_slot, contextBar->createBrushSlotFromPreferences());
|
|
m_brushes.lockBrushSlot(m_slot);
|
|
|
|
m_changeFlags = false;
|
|
}
|
|
|
|
void onLockBrush() {
|
|
if (m_brushes.isBrushSlotLocked(m_slot))
|
|
m_brushes.unlockBrushSlot(m_slot);
|
|
else
|
|
m_brushes.lockBrushSlot(m_slot);
|
|
}
|
|
|
|
void onDeleteBrush() {
|
|
m_brushes.removeBrushSlot(m_slot);
|
|
m_changeFlags = false;
|
|
}
|
|
|
|
void onDeleteAllBrushes() {
|
|
m_brushes.removeAllBrushSlots();
|
|
m_changeFlags = false;
|
|
}
|
|
|
|
BrushPopup* m_popup;
|
|
AppBrushes& m_brushes;
|
|
BrushRef m_brush;
|
|
int m_slot;
|
|
bool m_changeFlags;
|
|
};
|
|
|
|
class NewCustomBrushItem : public ButtonSet::Item {
|
|
public:
|
|
NewCustomBrushItem() {
|
|
setText("Save Brush");
|
|
}
|
|
|
|
private:
|
|
void onClick() override {
|
|
ContextBar* contextBar =
|
|
App::instance()->getMainWindow()->getContextBar();
|
|
|
|
auto& brushes = App::instance()->brushes();
|
|
int slot = brushes.addBrushSlot(
|
|
contextBar->createBrushSlotFromPreferences());
|
|
brushes.lockBrushSlot(slot);
|
|
}
|
|
};
|
|
|
|
class NewBrushOptionsItem : public ButtonSet::Item {
|
|
public:
|
|
NewBrushOptionsItem() {
|
|
setIcon(SkinTheme::instance()->parts.iconArrowDown(), true);
|
|
}
|
|
|
|
private:
|
|
void onClick() override {
|
|
Menu menu;
|
|
|
|
menu.addChild(new Separator("Parameters to Save", HORIZONTAL));
|
|
|
|
app::gen::BrushSlotParams params;
|
|
menu.addChild(¶ms);
|
|
|
|
// Load preferences
|
|
auto& saveBrush = Preferences::instance().saveBrush;
|
|
params.brushType()->setSelected(saveBrush.brushType());
|
|
params.brushSize()->setSelected(saveBrush.brushSize());
|
|
params.brushAngle()->setSelected(saveBrush.brushAngle());
|
|
params.fgColor()->setSelected(saveBrush.fgColor());
|
|
params.bgColor()->setSelected(saveBrush.bgColor());
|
|
params.inkType()->setSelected(saveBrush.inkType());
|
|
params.inkOpacity()->setSelected(saveBrush.inkOpacity());
|
|
params.shade()->setSelected(saveBrush.shade());
|
|
params.pixelPerfect()->setSelected(saveBrush.pixelPerfect());
|
|
|
|
show_popup_menu(static_cast<PopupWindow*>(window()), &menu,
|
|
gfx::Point(origin().x, origin().y+bounds().h));
|
|
|
|
// Save preferences
|
|
if (saveBrush.brushType() != params.brushType()->isSelected())
|
|
saveBrush.brushType(params.brushType()->isSelected());
|
|
if (saveBrush.brushSize() != params.brushSize()->isSelected())
|
|
saveBrush.brushSize(params.brushSize()->isSelected());
|
|
if (saveBrush.brushAngle() != params.brushAngle()->isSelected())
|
|
saveBrush.brushAngle(params.brushAngle()->isSelected());
|
|
if (saveBrush.fgColor() != params.fgColor()->isSelected())
|
|
saveBrush.fgColor(params.fgColor()->isSelected());
|
|
if (saveBrush.bgColor() != params.bgColor()->isSelected())
|
|
saveBrush.bgColor(params.bgColor()->isSelected());
|
|
if (saveBrush.inkType() != params.inkType()->isSelected())
|
|
saveBrush.inkType(params.inkType()->isSelected());
|
|
if (saveBrush.inkOpacity() != params.inkOpacity()->isSelected())
|
|
saveBrush.inkOpacity(params.inkOpacity()->isSelected());
|
|
if (saveBrush.shade() != params.shade()->isSelected())
|
|
saveBrush.shade(params.shade()->isSelected());
|
|
if (saveBrush.pixelPerfect() != params.pixelPerfect()->isSelected())
|
|
saveBrush.pixelPerfect(params.pixelPerfect()->isSelected());
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
BrushPopup::BrushPopup()
|
|
: PopupWindow("", ClickBehavior::CloseOnClickInOtherWindow)
|
|
, m_tooltipManager(nullptr)
|
|
, m_standardBrushes(3)
|
|
, m_customBrushes(nullptr)
|
|
{
|
|
auto& brushes = App::instance()->brushes();
|
|
|
|
setAutoRemap(false);
|
|
setBorder(gfx::Border(2)*guiscale());
|
|
setChildSpacing(0);
|
|
m_box.noBorderNoChildSpacing();
|
|
m_standardBrushes.setTriggerOnMouseUp(true);
|
|
|
|
addChild(&m_box);
|
|
|
|
HBox* top = new HBox;
|
|
top->addChild(&m_standardBrushes);
|
|
top->addChild(new BoxFiller);
|
|
|
|
m_box.addChild(top);
|
|
m_box.addChild(new Separator("", HORIZONTAL));
|
|
|
|
for (const auto& brush : brushes.getStandardBrushes())
|
|
m_standardBrushes.addItem(
|
|
new SelectBrushItem(
|
|
BrushSlot(BrushSlot::Flags::BrushType, brush)));
|
|
|
|
m_standardBrushes.setTransparent(true);
|
|
m_standardBrushes.setBgColor(gfx::ColorNone);
|
|
|
|
brushes.ItemsChange.connect(&BrushPopup::onBrushChanges, this);
|
|
}
|
|
|
|
void BrushPopup::setBrush(Brush* brush)
|
|
{
|
|
for (auto child : m_standardBrushes.children()) {
|
|
SelectBrushItem* item = static_cast<SelectBrushItem*>(child);
|
|
|
|
// Same type and same image
|
|
if (item->brush().hasBrush() &&
|
|
item->brush().brush()->type() == brush->type() &&
|
|
(brush->type() != kImageBrushType ||
|
|
item->brush().brush()->image() == brush->image())) {
|
|
m_standardBrushes.setSelectedItem(item);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BrushPopup::regenerate(const gfx::Rect& box)
|
|
{
|
|
auto& brushSlots = App::instance()->brushes().getBrushSlots();
|
|
|
|
if (m_customBrushes) {
|
|
// As BrushPopup::regenerate() can be called when a
|
|
// "m_customBrushes" button is clicked we cannot delete
|
|
// "m_customBrushes" right now.
|
|
m_customBrushes->parent()->removeChild(m_customBrushes);
|
|
m_customBrushes->deferDelete();
|
|
}
|
|
|
|
m_customBrushes = new ButtonSet(3);
|
|
m_customBrushes->setTriggerOnMouseUp(true);
|
|
|
|
auto& parts = SkinTheme::instance()->parts;
|
|
int slot = 0;
|
|
for (const auto& brush : brushSlots) {
|
|
++slot;
|
|
|
|
// Get shortcut
|
|
std::string shortcut;
|
|
{
|
|
Params params;
|
|
params.set("change", "custom");
|
|
params.set("slot", base::convert_to<std::string>(slot).c_str());
|
|
Key* key = KeyboardShortcuts::instance()->command(
|
|
CommandId::ChangeBrush, params);
|
|
if (key && !key->accels().empty())
|
|
shortcut = key->accels().front().toString();
|
|
}
|
|
m_customBrushes->addItem(new SelectBrushItem(brush, slot));
|
|
m_customBrushes->addItem(new BrushShortcutItem(shortcut, slot));
|
|
m_customBrushes->addItem(new BrushOptionsItem(this, slot));
|
|
}
|
|
|
|
m_customBrushes->addItem(new NewCustomBrushItem, 2, 1);
|
|
m_customBrushes->addItem(new NewBrushOptionsItem);
|
|
m_customBrushes->setExpansive(true);
|
|
m_box.addChild(m_customBrushes);
|
|
|
|
// Resize the window and change the hot region.
|
|
setBounds(gfx::Rect(box.origin(), sizeHint()));
|
|
setHotRegion(gfx::Region(bounds()));
|
|
}
|
|
|
|
void BrushPopup::onBrushChanges()
|
|
{
|
|
if (isVisible()) {
|
|
gfx::Region rgn;
|
|
getDrawableRegion(rgn, DrawableRegionFlags(kCutTopWindows | kUseChildArea));
|
|
|
|
regenerate(bounds());
|
|
invalidate();
|
|
|
|
parent()->invalidateRegion(rgn);
|
|
}
|
|
}
|
|
|
|
// static
|
|
she::Surface* BrushPopup::createSurfaceForBrush(const BrushRef& origBrush)
|
|
{
|
|
Image* image = nullptr;
|
|
BrushRef brush = origBrush;
|
|
if (brush) {
|
|
if (brush->type() != kImageBrushType && brush->size() > 10) {
|
|
brush.reset(new Brush(*brush));
|
|
brush->setSize(10);
|
|
}
|
|
image = brush->image();
|
|
}
|
|
|
|
she::Surface* surface = she::instance()->createRgbaSurface(
|
|
std::min(10, image ? image->width(): 4),
|
|
std::min(10, image ? image->height(): 4));
|
|
|
|
if (image) {
|
|
Palette* palette = get_current_palette();
|
|
if (image->pixelFormat() == IMAGE_BITMAP) {
|
|
palette = new Palette(frame_t(0), 2);
|
|
palette->setEntry(0, rgba(0, 0, 0, 0));
|
|
palette->setEntry(1, rgba(0, 0, 0, 255));
|
|
}
|
|
|
|
convert_image_to_surface(
|
|
image, palette, surface,
|
|
0, 0, 0, 0, image->width(), image->height());
|
|
|
|
if (image->pixelFormat() == IMAGE_BITMAP)
|
|
delete palette;
|
|
}
|
|
else {
|
|
she::ScopedSurfaceLock lock(surface);
|
|
lock->clear();
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
} // namespace app
|