diff --git a/data/gui.xml b/data/gui.xml index 420292921..17bc7baeb 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -149,6 +149,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/skins/default/sheet.png b/data/skins/default/sheet.png index ec2cad030..824716a46 100644 Binary files a/data/skins/default/sheet.png and b/data/skins/default/sheet.png differ diff --git a/src/app/commands/cmd_change_brush.cpp b/src/app/commands/cmd_change_brush.cpp index d670900c7..fba7b302e 100644 --- a/src/app/commands/cmd_change_brush.cpp +++ b/src/app/commands/cmd_change_brush.cpp @@ -17,6 +17,9 @@ #include "app/context.h" #include "app/settings/settings.h" #include "app/tools/tool.h" +#include "app/ui/context_bar.h" +#include "app/ui/main_window.h" +#include "base/convert_to.h" #include "doc/brush.h" namespace app { @@ -28,10 +31,9 @@ class ChangeBrushCommand : public Command { DecrementSize, IncrementAngle, DecrementAngle, + CustomBrush, }; - Change m_change; - public: ChangeBrushCommand(); @@ -39,6 +41,10 @@ protected: void onLoadParams(const Params& params) override; void onExecute(Context* context) override; std::string onGetFriendlyName() const override; + +private: + Change m_change; + int m_slot; }; ChangeBrushCommand::ChangeBrushCommand() @@ -47,6 +53,7 @@ ChangeBrushCommand::ChangeBrushCommand() CmdUIOnlyFlag) { m_change = None; + m_slot = 0; } void ChangeBrushCommand::onLoadParams(const Params& params) @@ -56,6 +63,12 @@ void ChangeBrushCommand::onLoadParams(const Params& params) else if (change == "decrement-size") m_change = DecrementSize; else if (change == "increment-angle") m_change = IncrementAngle; else if (change == "decrement-angle") m_change = DecrementAngle; + else if (change == "custom") m_change = CustomBrush; + + if (m_change == CustomBrush) + m_slot = params.get_as("slot"); + else + m_slot = 0; } void ChangeBrushCommand::onExecute(Context* context) @@ -84,6 +97,10 @@ void ChangeBrushCommand::onExecute(Context* context) if (brush->getAngle() > 0) brush->setAngle(brush->getAngle()-1); break; + case CustomBrush: + App::instance()->getMainWindow()->getContextBar() + ->setActiveBrushBySlot(m_slot); + break; } } @@ -106,6 +123,10 @@ std::string ChangeBrushCommand::onGetFriendlyName() const case DecrementAngle: text += ": Decrement Angle"; break; + case CustomBrush: + text += ": Custom Brush #"; + text += base::convert_to(m_slot); + break; } return text; diff --git a/src/app/commands/cmd_new_brush.cpp b/src/app/commands/cmd_new_brush.cpp index 03d3ff46a..ab5415de6 100644 --- a/src/app/commands/cmd_new_brush.cpp +++ b/src/app/commands/cmd_new_brush.cpp @@ -22,9 +22,12 @@ #include "app/ui/context_bar.h" #include "app/ui/editor/editor.h" #include "app/ui/editor/select_box_state.h" +#include "app/ui/keyboard_shortcuts.h" #include "app/ui/main_window.h" +#include "app/ui/status_bar.h" #include "app/ui_context.h" #include "app/util/new_image_from_mask.h" +#include "base/convert_to.h" #include "doc/mask.h" namespace app { @@ -142,7 +145,21 @@ void NewBrushCommand::createBrush(const Mask* mask) // TODO add a active stock property in app::Context ContextBar* ctxBar = App::instance()->getMainWindow()->getContextBar(); + int slot = ctxBar->addBrush(brush); ctxBar->setActiveBrush(brush); + + // Get the shortcut for this brush and show it to the user + Params params; + params.set("change", "custom"); + params.set("slot", base::convert_to(slot).c_str()); + Key* key = KeyboardShortcuts::instance()->command( + CommandId::ChangeBrush, params); + if (key && !key->accels().empty()) { + std::string tooltip; + tooltip += "Shortcut: "; + tooltip += key->accels().front().toString(); + StatusBar::instance()->showTip(2000, tooltip.c_str()); + } } Command* CommandFactory::createNewBrushCommand() diff --git a/src/app/ui/brush_popup.cpp b/src/app/ui/brush_popup.cpp index 2e278ef86..d4049a3a3 100644 --- a/src/app/ui/brush_popup.cpp +++ b/src/app/ui/brush_popup.cpp @@ -11,48 +11,158 @@ #include "app/ui/brush_popup.h" -#include "gfx/region.h" -#include "gfx/border.h" -#include "app/ui/skin/skin_theme.h" +#include "app/commands/commands.h" +#include "app/modules/palettes.h" #include "app/ui/button_set.h" +#include "app/ui/keyboard_shortcuts.h" +#include "app/ui/skin/skin_theme.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/surface.h" +#include "she/system.h" +#include "ui/tooltips.h" namespace app { using namespace app::skin; +using namespace doc; using namespace ui; +class Item : public ButtonSet::Item { +public: + Item(const BrushRef& brush) + : m_brush(brush) { + setIcon(BrushPopup::createSurfaceForBrush(brush)); + } + + ~Item() { + icon()->dispose(); + } + + const BrushRef& brush() const { return m_brush; } + +private: + BrushRef m_brush; +}; + +static BrushRef defBrushes[3]; + BrushPopup::BrushPopup() : PopupWindow("", kCloseOnClickInOtherWindow) { - SkinTheme* theme = static_cast(getTheme()); - setAutoRemap(false); setBorder(gfx::Border(0)); child_spacing = 0; - - m_brushTypeButton = new ButtonSet(3); - m_brushTypeButton->addItem(theme->get_part(PART_BRUSH_CIRCLE)); - m_brushTypeButton->addItem(theme->get_part(PART_BRUSH_SQUARE)); - m_brushTypeButton->addItem(theme->get_part(PART_BRUSH_LINE)); - m_brushTypeButton->ItemChange.connect(&BrushPopup::onBrushTypeChange, this); - m_brushTypeButton->setTransparent(true); - m_brushTypeButton->setBgColor(gfx::ColorNone); - addChild(m_brushTypeButton); } -void BrushPopup::setBrush(doc::Brush* brush) +void BrushPopup::setBrush(Brush* brush) { - m_brushTypeButton->setSelectedItem(brush->type()); + for (auto child : m_buttons->getChildren()) { + Item* item = static_cast(child); + + // Same type and same image + if (item->brush()->type() == brush->type() && + (brush->type() != kImageBrushType || + item->brush()->image() == brush->image())) { + m_buttons->setSelectedItem(item); + break; + } + } } -void BrushPopup::onBrushTypeChange() +void BrushPopup::regenerate(const gfx::Rect& box, const Brushes& brushes) { - doc::BrushType brushType = (doc::BrushType)m_brushTypeButton->selectedItem(); - doc::Brush brush; - brush.setType(brushType); + SkinTheme* theme = static_cast(getTheme()); - BrushChange(&brush); + if (m_buttons) { + for (auto child : m_buttons->getChildren()) + m_tooltipManager->removeTooltipFor(child); + removeChild(m_buttons.get()); + m_buttons.reset(); + } + + if (!defBrushes[0]) { + defBrushes[0].reset(new Brush(kCircleBrushType, 7, 0)); + defBrushes[1].reset(new Brush(kSquareBrushType, 7, 0)); + defBrushes[2].reset(new Brush(kLineBrushType, 7, 44)); + } + + m_buttons.reset(new ButtonSet(3 + brushes.size())); + m_buttons->addItem(new Item(defBrushes[0])); + m_buttons->addItem(new Item(defBrushes[1])); + m_buttons->addItem(new Item(defBrushes[2])); + + int slot = 1; + for (const auto& brush : brushes) { + Item* item = new Item(brush); + m_buttons->addItem(item); + + Params params; + params.set("change", "custom"); + params.set("slot", base::convert_to(slot).c_str()); + Key* key = KeyboardShortcuts::instance()->command( + CommandId::ChangeBrush, params); + if (key && !key->accels().empty()) { + std::string tooltip; + tooltip += "Shortcut: "; + tooltip += key->accels().front().toString(); + m_tooltipManager->addTooltipFor(item, tooltip, JI_TOP); + } + slot++; + } + + m_buttons->ItemChange.connect(&BrushPopup::onButtonChange, this); + m_buttons->setTransparent(true); + m_buttons->setBgColor(gfx::ColorNone); + addChild(m_buttons.get()); + + gfx::Rect rc = box; + rc.w *= m_buttons->getChildren().size(); + setBounds(rc); +} + +void BrushPopup::onButtonChange() +{ + Item* item = static_cast(m_buttons->getItem(m_buttons->selectedItem())); + BrushChange(item->brush()); +} + +// static +she::Surface* BrushPopup::createSurfaceForBrush(const BrushRef& origBrush) +{ + BrushRef brush = origBrush; + + if (brush->type() != kImageBrushType && brush->size() > 10) { + brush.reset(new Brush(*brush)); + brush->setSize(10); + } + + Image* image = brush->image(); + + she::Surface* surface = she::instance()->createRgbaSurface( + std::min(10, image->width()), + std::min(10, image->height())); + + 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; + + return surface; } } // namespace app diff --git a/src/app/ui/brush_popup.h b/src/app/ui/brush_popup.h index d01ad1081..0bb0b50cd 100644 --- a/src/app/ui/brush_popup.h +++ b/src/app/ui/brush_popup.h @@ -9,13 +9,19 @@ #define APP_UI_BRUSH_POPUP_H_INCLUDED #pragma once +#include "base/shared_ptr.h" #include "base/signal.h" +#include "doc/brushes.h" #include "ui/popup_window.h" namespace doc { class Brush; } +namespace ui { + class TooltipManager; +} + namespace app { class ButtonSet; @@ -24,13 +30,21 @@ namespace app { BrushPopup(); void setBrush(doc::Brush* brush); + void regenerate(const gfx::Rect& box, const doc::Brushes& brushes); - Signal1 BrushChange; + void setupTooltips(ui::TooltipManager* tooltipManager) { + m_tooltipManager = tooltipManager; + } + + Signal1 BrushChange; + + static she::Surface* createSurfaceForBrush(const doc::BrushRef& brush); private: - void onBrushTypeChange(); + void onButtonChange(); - ButtonSet* m_brushTypeButton; + base::SharedPtr m_buttons; + ui::TooltipManager* m_tooltipManager; }; } // namespace app diff --git a/src/app/ui/button_set.cpp b/src/app/ui/button_set.cpp index 4002f2af5..52deedc10 100644 --- a/src/app/ui/button_set.cpp +++ b/src/app/ui/button_set.cpp @@ -162,6 +162,11 @@ void ButtonSet::addItem(she::Surface* icon, int hspan, int vspan) { Item* item = new Item(); item->setIcon(icon); + addItem(item, hspan, vspan); +} + +void ButtonSet::addItem(Item* item, int hspan, int vspan) +{ addChildInCell(item, hspan, vspan, JI_CENTER | JI_MIDDLE); } diff --git a/src/app/ui/button_set.h b/src/app/ui/button_set.h index 0f64d6f03..fedc4d787 100644 --- a/src/app/ui/button_set.h +++ b/src/app/ui/button_set.h @@ -22,6 +22,7 @@ namespace app { public: Item(); void setIcon(she::Surface* icon); + she::Surface* icon() const { return m_icon; } ButtonSet* buttonSet(); protected: void onPaint(ui::PaintEvent& ev) override; @@ -34,6 +35,7 @@ namespace app { ButtonSet(int columns); void addItem(she::Surface* icon, int hspan = 1, int vspan = 1); + void addItem(Item* item, int hspan = 1, int vspan = 1); Item* getItem(int index); int selectedItem() const; diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index 9cb7aa412..010e566f5 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -78,35 +78,19 @@ public: } void updateBrush(tools::Tool* tool = nullptr) { - doc::BrushRef brush = m_owner->activeBrush(tool); - if (brush->type() != kImageBrushType && brush->size() > 10) { - brush.reset(new Brush(*brush)); - brush->setSize(10); - } - - Image* image = brush->image(); if (m_bitmap) m_bitmap->dispose(); - m_bitmap = she::instance()->createRgbaSurface( - std::min(10, image->width()), - std::min(10, image->height())); - Palette* palette = get_current_palette(); - if (image->pixelFormat() == IMAGE_BITMAP) { - palette = new Palette(frame_t(0), 2); - palette->setEntry(0, doc::rgba(0, 0, 0, 0)); - palette->setEntry(1, doc::rgba(0, 0, 0, 255)); - } - - convert_image_to_surface(image, palette, m_bitmap, - 0, 0, 0, 0, image->width(), image->height()); - - if (image->pixelFormat() == IMAGE_BITMAP) - delete palette; + m_bitmap = BrushPopup::createSurfaceForBrush( + m_owner->activeBrush(tool)); getItem(0)->setIcon(m_bitmap); } + void setupTooltips(TooltipManager* tooltipManager) { + m_popupWindow.setupTooltips(tooltipManager); + } + protected: void onItemChange() override { ButtonSet::onItemChange(); @@ -126,42 +110,42 @@ private: Rect rc = getBounds(); rc.y += rc.h - 2*guiscale(); rc.setSize(getPreferredSize()); - rc.w *= 3; ISettings* settings = UIContext::instance()->settings(); Tool* currentTool = settings->getCurrentTool(); IBrushSettings* brushSettings = settings->getToolSettings(currentTool)->getBrush(); doc::BrushRef brush = m_owner->activeBrush(); - m_popupWindow.setBounds(rc); + m_popupWindow.regenerate(rc, m_owner->brushes()); m_popupWindow.setBrush(brush.get()); Region rgn(m_popupWindow.getBounds().createUnion(getBounds())); m_popupWindow.setHotRegion(rgn); m_popupWindow.openWindow(); - m_popupWindow.BrushChange.connect(&BrushTypeField::onBrushTypeChange, this); + m_popupWindow.BrushChange.connect(&BrushTypeField::onBrushChange, this); } void closePopup() { m_popupWindow.closeWindow(NULL); } - void onBrushTypeChange(Brush* brush) { - m_brushType = brush->type(); + void onBrushChange(const BrushRef& brush) { + if (brush->type() == kImageBrushType) + m_owner->setActiveBrush(brush); + else { + ISettings* settings = UIContext::instance()->settings(); + Tool* currentTool = settings->getCurrentTool(); + IBrushSettings* brushSettings = settings->getToolSettings(currentTool)->getBrush(); + brushSettings->setType(brush->type()); - ISettings* settings = UIContext::instance()->settings(); - Tool* currentTool = settings->getCurrentTool(); - IBrushSettings* brushSettings = settings->getToolSettings(currentTool)->getBrush(); - brushSettings->setType(m_brushType); - - m_owner->setActiveBrush(ContextBar::createBrushFromSettings( - brushSettings)); + m_owner->setActiveBrush( + ContextBar::createBrushFromSettings(brushSettings)); + } } ContextBar* m_owner; she::Surface* m_bitmap; - BrushType m_brushType; BrushPopup m_popupWindow; }; @@ -840,6 +824,8 @@ ContextBar::ContextBar() "component is used to setup the opacity level of all drawing tools.\n\n" "When unchecked -the default behavior- the color is picked\n" "from the composition of all sprite layers.", JI_LEFT | JI_TOP); + + m_brushType->setupTooltips(tooltipManager); m_selectionMode->setupTooltips(tooltipManager); m_dropPixels->setupTooltips(tooltipManager); m_freehandAlgo->setupTooltips(tooltipManager); @@ -1021,6 +1007,19 @@ void ContextBar::updateAutoSelectLayer(bool state) m_autoSelectLayer->setSelected(state); } +int ContextBar::addBrush(const doc::BrushRef& brush) +{ + m_brushes.push_back(brush); + return (int)m_brushes.size(); // Returns the slot +} + +void ContextBar::setActiveBrushBySlot(int slot) +{ + --slot; + if (slot >= 0 && slot < (int)m_brushes.size()) + setActiveBrush(m_brushes[slot]); +} + void ContextBar::setActiveBrush(const doc::BrushRef& brush) { m_activeBrush = brush; diff --git a/src/app/ui/context_bar.h b/src/app/ui/context_bar.h index 6661131fc..682da2880 100644 --- a/src/app/ui/context_bar.h +++ b/src/app/ui/context_bar.h @@ -13,8 +13,11 @@ #include "app/ui/context_bar_observer.h" #include "base/observable.h" #include "doc/brush.h" +#include "doc/brushes.h" #include "ui/box.h" +#include + namespace ui { class Box; class Button; @@ -46,6 +49,12 @@ namespace app { doc::BrushRef activeBrush(tools::Tool* tool = nullptr) const; void discardActiveBrush(); + // Adds a new brush and returns the slot number where the brush + // is now available. + int addBrush(const doc::BrushRef& brush); + const doc::Brushes& brushes() const { return m_brushes; } + void setActiveBrushBySlot(int slot); + static doc::BrushRef createBrushFromSettings( IBrushSettings* brushSettings = nullptr); @@ -104,6 +113,7 @@ namespace app { RotAlgorithmField* m_rotAlgo; DropPixelsField* m_dropPixels; doc::BrushRef m_activeBrush; + doc::Brushes m_brushes; }; } // namespace app diff --git a/src/app/ui/skin/skin_parts.h b/src/app/ui/skin/skin_parts.h index 5287e6898..b111a947f 100644 --- a/src/app/ui/skin/skin_parts.h +++ b/src/app/ui/skin/skin_parts.h @@ -129,13 +129,6 @@ namespace app { PART_TARGET_FRAMES_LAYERS, PART_TARGET_FRAMES_LAYERS_SELECTED, - PART_BRUSH_CIRCLE, - PART_BRUSH_CIRCLE_SELECTED, - PART_BRUSH_SQUARE, - PART_BRUSH_SQUARE_SELECTED, - PART_BRUSH_LINE, - PART_BRUSH_LINE_SELECTED, - PART_SCALE_ARROW_1, PART_SCALE_ARROW_2, PART_SCALE_ARROW_3, diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index 9e72cb6fe..46c7be9a4 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -238,12 +238,6 @@ SkinTheme::SkinTheme() sheet_mapping["target_layers_selected"] = PART_TARGET_LAYERS_SELECTED; sheet_mapping["target_frames_layers"] = PART_TARGET_FRAMES_LAYERS; sheet_mapping["target_frames_layers_selected"] = PART_TARGET_FRAMES_LAYERS_SELECTED; - sheet_mapping["brush_circle"] = PART_BRUSH_CIRCLE; - sheet_mapping["brush_circle_selected"] = PART_BRUSH_CIRCLE_SELECTED; - sheet_mapping["brush_square"] = PART_BRUSH_SQUARE; - sheet_mapping["brush_square_selected"] = PART_BRUSH_SQUARE_SELECTED; - sheet_mapping["brush_line"] = PART_BRUSH_LINE; - sheet_mapping["brush_line_selected"] = PART_BRUSH_LINE_SELECTED; sheet_mapping["scale_arrow_1"] = PART_SCALE_ARROW_1; sheet_mapping["scale_arrow_2"] = PART_SCALE_ARROW_2; sheet_mapping["scale_arrow_3"] = PART_SCALE_ARROW_3; diff --git a/src/doc/brushes.h b/src/doc/brushes.h new file mode 100644 index 000000000..29238befb --- /dev/null +++ b/src/doc/brushes.h @@ -0,0 +1,21 @@ +// 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_BRUSHES_H_INCLUDED +#define DOC_BRUSHES_H_INCLUDED +#pragma once + +#include "doc/brush.h" + +#include + +namespace doc { + + typedef std::vector Brushes; + +} // namespace doc + +#endif diff --git a/src/ui/tooltips.cpp b/src/ui/tooltips.cpp index 4cef8cb80..e2b689dff 100644 --- a/src/ui/tooltips.cpp +++ b/src/ui/tooltips.cpp @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2001-2013 David Capello +// Copyright (C) 2001-2013, 2015 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -47,6 +47,13 @@ void TooltipManager::addTooltipFor(Widget* widget, const std::string& text, int m_tips[widget] = TipInfo(text, arrowAlign); } +void TooltipManager::removeTooltipFor(Widget* widget) +{ + auto it = m_tips.find(widget); + if (it != m_tips.end()) + m_tips.erase(it); +} + bool TooltipManager::onProcessMessage(Message* msg) { switch (msg->type()) { diff --git a/src/ui/tooltips.h b/src/ui/tooltips.h index 8bad58b2d..4b3c5f514 100644 --- a/src/ui/tooltips.h +++ b/src/ui/tooltips.h @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2001-2013 David Capello +// Copyright (C) 2001-2013, 2015 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -8,6 +8,7 @@ #define UI_TOOLTIPS_H_INCLUDED #pragma once +#include "base/unique_ptr.h" #include "ui/base.h" #include "ui/popup_window.h" #include "ui/window.h" @@ -25,6 +26,7 @@ namespace ui { ~TooltipManager(); void addTooltipFor(Widget* widget, const std::string& text, int arrowAlign = 0); + void removeTooltipFor(Widget* widget); protected: bool onProcessMessage(Message* msg) override;