mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-17 13:20:45 +00:00
Merge branch 'new-brush-popup'
This commit is contained in:
commit
51e3ab178d
@ -128,6 +128,17 @@
|
||||
<section id="brush">
|
||||
<option id="pattern" type="doc::BrushPattern" default="doc::BrushPattern::DEFAULT" />
|
||||
</section>
|
||||
<section id="save_brush">
|
||||
<option id="brush_type" type="bool" default="true" />
|
||||
<option id="brush_size" type="bool" default="true" />
|
||||
<option id="brush_angle" type="bool" default="true" />
|
||||
<option id="fg_color" type="bool" default="false" />
|
||||
<option id="bg_color" type="bool" default="false" />
|
||||
<option id="ink_type" type="bool" default="true" />
|
||||
<option id="ink_opacity" type="bool" default="true" />
|
||||
<option id="shade" type="bool" default="true" />
|
||||
<option id="pixel_perfect" type="bool" default="false" />
|
||||
</section>
|
||||
<section id="selection">
|
||||
<option id="mode" type="app::tools::SelectionMode" default="app::tools::SelectionMode::DEFAULT" />
|
||||
<option id="pivot_visibility" type="bool" default="false" />
|
||||
|
34
data/widgets/brush_slot_params.xml
Normal file
34
data/widgets/brush_slot_params.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<!-- ASEPRITE -->
|
||||
<!-- Copyright (C) 2015 by David Capello -->
|
||||
<gui>
|
||||
<vbox id="brush_slot_params">
|
||||
<grid columns="2">
|
||||
|
||||
<label text="Brush:" />
|
||||
<buttonset id="brush_params" columns="3" multiple="true">
|
||||
<item id="brush_type" text="Type" />
|
||||
<item id="brush_size" text="Size" />
|
||||
<item id="brush_angle" text="Angle" />
|
||||
</buttonset>
|
||||
|
||||
<label text="Color:" />
|
||||
<buttonset id="color_params" columns="2" multiple="true">
|
||||
<item id="fg_color" text="Foreground" />
|
||||
<item id="bg_color" text="Background" />
|
||||
</buttonset>
|
||||
|
||||
<label text="Ink:" />
|
||||
<buttonset id="ink_params" columns="2" multiple="true">
|
||||
<item id="ink_type" text="Type" />
|
||||
<item id="ink_opacity" text="Opacity" />
|
||||
</buttonset>
|
||||
|
||||
<label text="Extras:" />
|
||||
<buttonset id="extra_params" columns="2" multiple="true">
|
||||
<item id="shade" text="Shade" />
|
||||
<item id="pixel_perfect" text="Pixel-Perfect" />
|
||||
</buttonset>
|
||||
|
||||
</grid>
|
||||
</vbox>
|
||||
</gui>
|
@ -90,6 +90,7 @@ endif()
|
||||
|
||||
add_library(app-lib
|
||||
app.cpp
|
||||
app_brushes.cpp
|
||||
app_menus.cpp
|
||||
app_options.cpp
|
||||
app_render.cpp
|
||||
|
@ -9,6 +9,7 @@
|
||||
#define APP_APP_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/app_brushes.h"
|
||||
#include "base/signal.h"
|
||||
#include "base/string.h"
|
||||
#include "base/unique_ptr.h"
|
||||
@ -69,6 +70,7 @@ namespace app {
|
||||
RecentFiles* getRecentFiles() const;
|
||||
MainWindow* getMainWindow() const { return m_mainWindow; }
|
||||
Preferences& preferences() const;
|
||||
AppBrushes& brushes() { return m_brushes; }
|
||||
|
||||
void showNotification(INotificationDelegate* del);
|
||||
void updateDisplayTitleBar();
|
||||
@ -95,6 +97,7 @@ namespace app {
|
||||
base::UniquePtr<MainWindow> m_mainWindow;
|
||||
FileList m_files;
|
||||
base::UniquePtr<DocumentExporter> m_exporter;
|
||||
AppBrushes m_brushes;
|
||||
};
|
||||
|
||||
void app_refresh_screen();
|
||||
|
118
src/app/app_brushes.cpp
Normal file
118
src/app/app_brushes.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
// 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/app_brushes.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace doc;
|
||||
|
||||
AppBrushes::AppBrushes()
|
||||
{
|
||||
m_standard.push_back(BrushRef(new Brush(kCircleBrushType, 7, 0)));
|
||||
m_standard.push_back(BrushRef(new Brush(kSquareBrushType, 7, 0)));
|
||||
m_standard.push_back(BrushRef(new Brush(kLineBrushType, 7, 44)));
|
||||
}
|
||||
|
||||
AppBrushes::slot_id AppBrushes::addBrushSlot(const BrushSlot& brush)
|
||||
{
|
||||
// Use an empty slot
|
||||
for (size_t i=0; i<m_slots.size(); ++i) {
|
||||
if (!m_slots[i].locked() ||
|
||||
!m_slots[i].brush()) {
|
||||
m_slots[i] = brush;
|
||||
return i+1;
|
||||
}
|
||||
}
|
||||
|
||||
m_slots.push_back(brush);
|
||||
ItemsChange();
|
||||
return slot_id(m_slots.size()); // Returns the slot
|
||||
}
|
||||
|
||||
void AppBrushes::removeBrushSlot(slot_id slot)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_slots.size()) {
|
||||
m_slots[slot] = BrushSlot();
|
||||
|
||||
// Erase empty trailing slots
|
||||
while (!m_slots.empty() &&
|
||||
!m_slots[m_slots.size()-1].brush())
|
||||
m_slots.erase(--m_slots.end());
|
||||
|
||||
ItemsChange();
|
||||
}
|
||||
}
|
||||
|
||||
void AppBrushes::removeAllBrushSlots()
|
||||
{
|
||||
while (!m_slots.empty())
|
||||
m_slots.erase(--m_slots.end());
|
||||
|
||||
ItemsChange();
|
||||
}
|
||||
|
||||
bool AppBrushes::hasBrushSlot(slot_id slot) const
|
||||
{
|
||||
--slot;
|
||||
return (slot >= 0 && slot < (int)m_slots.size() &&
|
||||
!m_slots[slot].isEmpty());
|
||||
}
|
||||
|
||||
BrushSlot AppBrushes::getBrushSlot(slot_id slot) const
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_slots.size())
|
||||
return m_slots[slot];
|
||||
else
|
||||
return BrushSlot();
|
||||
}
|
||||
|
||||
void AppBrushes::setBrushSlot(slot_id slot, const BrushSlot& brush)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_slots.size()) {
|
||||
m_slots[slot] = brush;
|
||||
ItemsChange();
|
||||
}
|
||||
}
|
||||
|
||||
void AppBrushes::lockBrushSlot(slot_id slot)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_slots.size() &&
|
||||
m_slots[slot].brush()) {
|
||||
m_slots[slot].setLocked(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AppBrushes::unlockBrushSlot(slot_id slot)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_slots.size() &&
|
||||
m_slots[slot].brush()) {
|
||||
m_slots[slot].setLocked(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool AppBrushes::isBrushSlotLocked(slot_id slot) const
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_slots.size() &&
|
||||
!m_slots[slot].isEmpty()) {
|
||||
return m_slots[slot].locked();
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace app
|
52
src/app/app_brushes.h
Normal file
52
src/app/app_brushes.h
Normal file
@ -0,0 +1,52 @@
|
||||
// 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_APP_BRUSHES_H_INCLUDED
|
||||
#define APP_APP_BRUSHES_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/brush_slot.h"
|
||||
#include "base/signal.h"
|
||||
#include "doc/brushes.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace app {
|
||||
|
||||
class AppBrushes {
|
||||
public:
|
||||
// Number of slot (a range from 1 to AppBrushes::size() inclusive)
|
||||
typedef int slot_id;
|
||||
typedef std::vector<BrushSlot> BrushSlots;
|
||||
|
||||
AppBrushes();
|
||||
|
||||
// Adds a new brush and returns the slot number where the brush
|
||||
// is now available.
|
||||
slot_id addBrushSlot(const BrushSlot& brush);
|
||||
void removeBrushSlot(slot_id slot);
|
||||
void removeAllBrushSlots();
|
||||
bool hasBrushSlot(slot_id slot) const;
|
||||
const doc::Brushes& getStandardBrushes() { return m_standard; }
|
||||
BrushSlot getBrushSlot(slot_id slot) const;
|
||||
void setBrushSlot(slot_id slot, const BrushSlot& brush);
|
||||
const BrushSlots& getBrushSlots() const { return m_slots; }
|
||||
|
||||
void lockBrushSlot(slot_id slot);
|
||||
void unlockBrushSlot(slot_id slot);
|
||||
bool isBrushSlotLocked(slot_id slot) const;
|
||||
|
||||
base::Signal0<void> ItemsChange;
|
||||
|
||||
private:
|
||||
doc::Brushes m_standard;
|
||||
BrushSlots m_slots;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
107
src/app/brush_slot.h
Normal file
107
src/app/brush_slot.h
Normal file
@ -0,0 +1,107 @@
|
||||
// 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_BRUSH_SLOT_H_INCLUDED
|
||||
#define APP_BRUSH_SLOT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/color.h"
|
||||
#include "app/shade.h"
|
||||
#include "app/tools/ink_type.h"
|
||||
#include "doc/brush.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
// Custom brush slot
|
||||
class BrushSlot {
|
||||
public:
|
||||
enum class Flags {
|
||||
Locked = 0x0001,
|
||||
BrushType = 0x0002,
|
||||
BrushSize = 0x0004,
|
||||
BrushAngle = 0x0008,
|
||||
FgColor = 0x0010,
|
||||
BgColor = 0x0020,
|
||||
InkType = 0x0040,
|
||||
InkOpacity = 0x0080,
|
||||
Shade = 0x0100,
|
||||
PixelPerfect = 0x0200
|
||||
};
|
||||
|
||||
BrushSlot(Flags flags = Flags(0),
|
||||
const doc::BrushRef& brush = doc::BrushRef(nullptr),
|
||||
const app::Color& fgColor = app::Color::fromMask(),
|
||||
const app::Color& bgColor = app::Color::fromMask(),
|
||||
tools::InkType inkType = tools::InkType::DEFAULT,
|
||||
int inkOpacity = 255,
|
||||
const Shade& shade = Shade(),
|
||||
bool pixelPerfect = false)
|
||||
: m_flags(flags)
|
||||
, m_brush(brush)
|
||||
, m_fgColor(fgColor)
|
||||
, m_bgColor(bgColor)
|
||||
, m_inkType(inkType)
|
||||
, m_inkOpacity(inkOpacity)
|
||||
, m_shade(shade)
|
||||
, m_pixelPerfect(pixelPerfect) {
|
||||
}
|
||||
|
||||
Flags flags() const { return m_flags; }
|
||||
void setFlags(Flags flags) { m_flags = flags; }
|
||||
|
||||
bool isEmpty() const {
|
||||
return int(m_flags) == 0;
|
||||
}
|
||||
|
||||
bool hasFlag(Flags flag) const {
|
||||
return ((int(m_flags) & int(flag)) == int(flag));
|
||||
}
|
||||
|
||||
bool hasBrush() const {
|
||||
return
|
||||
(brush() &&
|
||||
(hasFlag(Flags::BrushType) ||
|
||||
hasFlag(Flags::BrushSize) ||
|
||||
hasFlag(Flags::BrushAngle)));
|
||||
}
|
||||
|
||||
// Can be null if the user deletes the brush.
|
||||
doc::BrushRef brush() const { return m_brush; }
|
||||
app::Color fgColor() const { return m_fgColor; }
|
||||
app::Color bgColor() const { return m_bgColor; }
|
||||
tools::InkType inkType() const { return m_inkType; }
|
||||
int inkOpacity() const { return m_inkOpacity; }
|
||||
const Shade& shade() const { return m_shade; }
|
||||
bool pixelPerfect() const { return m_pixelPerfect; }
|
||||
|
||||
// True if the user locked the brush using the shortcut key to
|
||||
// access it.
|
||||
bool locked() const {
|
||||
return hasFlag(Flags::Locked);
|
||||
}
|
||||
|
||||
void setLocked(bool locked) {
|
||||
if (locked)
|
||||
m_flags = static_cast<Flags>(int(m_flags) | int(Flags::Locked));
|
||||
else
|
||||
m_flags = static_cast<Flags>(int(m_flags) & ~int(Flags::Locked));
|
||||
}
|
||||
|
||||
private:
|
||||
Flags m_flags;
|
||||
doc::BrushRef m_brush;
|
||||
app::Color m_fgColor;
|
||||
app::Color m_bgColor;
|
||||
tools::InkType m_inkType;
|
||||
int m_inkOpacity;
|
||||
Shade m_shade;
|
||||
bool m_pixelPerfect;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -287,7 +287,8 @@ private:
|
||||
|
||||
class KeyboardShortcutsWindow : public app::gen::KeyboardShortcuts {
|
||||
public:
|
||||
KeyboardShortcutsWindow() : m_searchChange(false) {
|
||||
KeyboardShortcutsWindow(const std::string& searchText)
|
||||
: m_searchChange(false) {
|
||||
setAutoRemap(false);
|
||||
|
||||
section()->addChild(new ListItem("Menus"));
|
||||
@ -302,6 +303,11 @@ public:
|
||||
resetButton()->Click.connect(base::Bind<void>(&KeyboardShortcutsWindow::onReset, this));
|
||||
|
||||
fillAllLists();
|
||||
|
||||
if (!searchText.empty()) {
|
||||
search()->setText(searchText);
|
||||
onSearchChange();
|
||||
}
|
||||
}
|
||||
|
||||
void restoreKeys() {
|
||||
@ -523,7 +529,11 @@ public:
|
||||
Command* clone() const override { return new KeyboardShortcutsCommand(*this); }
|
||||
|
||||
protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
|
||||
private:
|
||||
std::string m_search;
|
||||
};
|
||||
|
||||
KeyboardShortcutsCommand::KeyboardShortcutsCommand()
|
||||
@ -533,9 +543,19 @@ KeyboardShortcutsCommand::KeyboardShortcutsCommand()
|
||||
{
|
||||
}
|
||||
|
||||
void KeyboardShortcutsCommand::onLoadParams(const Params& params)
|
||||
{
|
||||
m_search = params.get("search");
|
||||
}
|
||||
|
||||
void KeyboardShortcutsCommand::onExecute(Context* context)
|
||||
{
|
||||
KeyboardShortcutsWindow window;
|
||||
// Here we copy the m_search field because
|
||||
// KeyboardShortcutsWindow::fillAllLists() modifies this same
|
||||
// KeyboardShortcutsCommand instance (so m_search will be "")
|
||||
// TODO Seeing this, we need a complete new way to handle UI commands execution
|
||||
std::string neededSearchCopy = m_search;
|
||||
KeyboardShortcutsWindow window(neededSearchCopy);
|
||||
|
||||
window.setBounds(gfx::Rect(0, 0, ui::display_w()*3/4, ui::display_h()*3/4));
|
||||
g_sep = window.bounds().w / 2;
|
||||
|
@ -149,9 +149,9 @@ void NewBrushCommand::createBrush(const Site& site, const Mask* mask)
|
||||
brush->setImage(image.get());
|
||||
brush->setPatternOrigin(mask->bounds().origin());
|
||||
|
||||
// TODO add a active stock property in app::Context
|
||||
ContextBar* ctxBar = App::instance()->getMainWindow()->getContextBar();
|
||||
int slot = ctxBar->addBrush(brush);
|
||||
int slot = App::instance()->brushes().addBrushSlot(
|
||||
BrushSlot(BrushSlot::Flags::BrushType, brush));
|
||||
ctxBar->setActiveBrush(brush);
|
||||
|
||||
// Get the shortcut for this brush and show it to the user
|
||||
|
@ -11,12 +11,19 @@
|
||||
|
||||
#include "app/ui/brush_popup.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/app_brushes.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/keyboard_shortcuts.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"
|
||||
@ -28,10 +35,14 @@
|
||||
#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 "ui/tooltips.h"
|
||||
|
||||
#include "brush_slot_params.xml.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -41,51 +52,166 @@ using namespace ui;
|
||||
|
||||
namespace {
|
||||
|
||||
class Item : public ButtonSet::Item {
|
||||
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:
|
||||
Item(BrushPopup* popup, BrushPopupDelegate* delegate, const BrushRef& brush, int slot = -1)
|
||||
: m_popup(popup)
|
||||
, m_delegate(delegate)
|
||||
SelectBrushItem(BrushPopupDelegate* delegate, const BrushSlot& brush, int slot = -1)
|
||||
: m_delegate(delegate)
|
||||
, m_brush(brush)
|
||||
, m_slot(slot) {
|
||||
SkinPartPtr icon(new SkinPart);
|
||||
icon->setBitmap(0, BrushPopup::createSurfaceForBrush(brush));
|
||||
setIcon(icon);
|
||||
if (m_brush.hasBrush()) {
|
||||
SkinPartPtr icon(new SkinPart);
|
||||
icon->setBitmap(0, BrushPopup::createSurfaceForBrush(m_brush.brush()));
|
||||
setIcon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
const BrushRef& brush() const {
|
||||
const BrushSlot& brush() const {
|
||||
return m_brush;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool onProcessMessage(Message* msg) override {
|
||||
if (msg->type() == kMouseUpMessage && m_slot > 0) {
|
||||
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||
if (mouseMsg->buttons() == kButtonRight) {
|
||||
Menu menu;
|
||||
AppMenuItem lockItem(m_delegate->onIsBrushSlotLocked(m_slot) ? "Unlock Brush": "Lock Brush");
|
||||
AppMenuItem deleteItem("Delete");
|
||||
AppMenuItem deleteAllItem("Delete All");
|
||||
lockItem.Click.connect(&Item::onLockBrush, this);
|
||||
deleteItem.Click.connect(&Item::onDeleteBrush, this);
|
||||
deleteAllItem.Click.connect(&Item::onDeleteAllBrushes, this);
|
||||
menu.addChild(&lockItem);
|
||||
menu.addChild(&deleteItem);
|
||||
menu.addChild(new MenuSeparator);
|
||||
menu.addChild(&deleteAllItem);
|
||||
private:
|
||||
|
||||
// Here we make the popup window temporaly floating, so it's
|
||||
// not closed by the popup menu.
|
||||
m_popup->makeFloating();
|
||||
menu.showPopup(mouseMsg->position());
|
||||
m_popup->makeFixed();
|
||||
m_popup->closeWindow(nullptr);
|
||||
}
|
||||
}
|
||||
return ButtonSet::Item::onProcessMessage(msg);
|
||||
void onClick() override {
|
||||
if (m_slot >= 0)
|
||||
m_delegate->onSelectBrushSlot(m_slot);
|
||||
else if (m_brush.hasBrush())
|
||||
m_delegate->onSelectBrush(m_brush.brush());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
BrushPopupDelegate* m_delegate;
|
||||
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, BrushPopupDelegate* delegate, int slot)
|
||||
: m_popup(popup)
|
||||
, m_delegate(delegate)
|
||||
, 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_delegate->onIsBrushSlotLocked(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
|
||||
AppBrushes& brushes = App::instance()->brushes();
|
||||
BrushSlot brush = 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));
|
||||
|
||||
show_popup_menu(m_popup, &menu,
|
||||
gfx::Point(origin().x, origin().y+bounds().h));
|
||||
|
||||
int flags = 0;
|
||||
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));
|
||||
brushes.setBrushSlot(m_slot, brush);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void onSaveBrush() {
|
||||
AppBrushes& brushes = App::instance()->brushes();
|
||||
brushes.setBrushSlot(
|
||||
m_slot, m_delegate->onCreateBrushSlotFromActivePreferences());
|
||||
brushes.lockBrushSlot(m_slot);
|
||||
}
|
||||
|
||||
void onLockBrush() {
|
||||
if (m_delegate->onIsBrushSlotLocked(m_slot))
|
||||
m_delegate->onUnlockBrushSlot(m_slot);
|
||||
@ -107,98 +233,186 @@ private:
|
||||
int m_slot;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
class NewCustomBrushItem : public ButtonSet::Item {
|
||||
public:
|
||||
NewCustomBrushItem(BrushPopupDelegate* delegate)
|
||||
: m_delegate(delegate) {
|
||||
setText("Save Brush");
|
||||
}
|
||||
|
||||
static BrushRef defBrushes[3];
|
||||
private:
|
||||
void onClick() override {
|
||||
AppBrushes& brushes = App::instance()->brushes();
|
||||
auto slot = brushes.addBrushSlot(
|
||||
m_delegate->onCreateBrushSlotFromActivePreferences());
|
||||
brushes.lockBrushSlot(slot);
|
||||
}
|
||||
|
||||
BrushPopupDelegate* m_delegate;
|
||||
};
|
||||
|
||||
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(BrushPopupDelegate* delegate)
|
||||
: PopupWindow("", ClickBehavior::CloseOnClickInOtherWindow)
|
||||
, m_tooltipManager(nullptr)
|
||||
, m_standardBrushes(3)
|
||||
, m_customBrushes(nullptr)
|
||||
, m_delegate(delegate)
|
||||
{
|
||||
setAutoRemap(false);
|
||||
setBorder(gfx::Border(0));
|
||||
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));
|
||||
|
||||
const doc::Brushes& brushes = App::instance()->brushes().getStandardBrushes();
|
||||
for (const auto& brush : brushes)
|
||||
m_standardBrushes.addItem(
|
||||
new SelectBrushItem(
|
||||
m_delegate, BrushSlot(BrushSlot::Flags::BrushType, brush)));
|
||||
|
||||
m_standardBrushes.setTransparent(true);
|
||||
m_standardBrushes.setBgColor(gfx::ColorNone);
|
||||
|
||||
App::instance()->brushes()
|
||||
.ItemsChange.connect(&BrushPopup::onBrushChanges, this);
|
||||
}
|
||||
|
||||
void BrushPopup::setBrush(Brush* brush)
|
||||
{
|
||||
for (auto child : m_buttons->children()) {
|
||||
Item* item = static_cast<Item*>(child);
|
||||
for (auto child : m_standardBrushes.children()) {
|
||||
SelectBrushItem* item = static_cast<SelectBrushItem*>(child);
|
||||
|
||||
// Same type and same image
|
||||
if (item->brush() &&
|
||||
item->brush()->type() == brush->type() &&
|
||||
if (item->brush().hasBrush() &&
|
||||
item->brush().brush()->type() == brush->type() &&
|
||||
(brush->type() != kImageBrushType ||
|
||||
item->brush()->image() == brush->image())) {
|
||||
m_buttons->setSelectedItem(item);
|
||||
break;
|
||||
item->brush().brush()->image() == brush->image())) {
|
||||
m_standardBrushes.setSelectedItem(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BrushPopup::regenerate(const gfx::Rect& box, const Brushes& brushes)
|
||||
void BrushPopup::regenerate(const gfx::Rect& box)
|
||||
{
|
||||
int columns = 3;
|
||||
auto& brushSlots = App::instance()->brushes().getBrushSlots();
|
||||
|
||||
if (m_buttons) {
|
||||
for (auto child : m_buttons->children())
|
||||
m_tooltipManager->removeTooltipFor(child);
|
||||
removeChild(m_buttons.get());
|
||||
m_buttons.reset();
|
||||
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();
|
||||
}
|
||||
|
||||
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_customBrushes = new ButtonSet(3);
|
||||
m_customBrushes->setTriggerOnMouseUp(true);
|
||||
|
||||
m_buttons.reset(new ButtonSet(columns));
|
||||
m_buttons->addItem(new Item(this, m_delegate, defBrushes[0]));
|
||||
m_buttons->addItem(new Item(this, m_delegate, defBrushes[1]));
|
||||
m_buttons->addItem(new Item(this, m_delegate, defBrushes[2]));
|
||||
auto& parts = SkinTheme::instance()->parts;
|
||||
int slot = 0;
|
||||
for (const auto& brush : brushSlots) {
|
||||
++slot;
|
||||
|
||||
int slot = 1;
|
||||
for (const auto& brush : brushes) {
|
||||
Item* item = new Item(this, m_delegate, brush, slot);
|
||||
m_buttons->addItem(item);
|
||||
|
||||
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()) {
|
||||
std::string tooltip;
|
||||
tooltip += "Shortcut: ";
|
||||
tooltip += key->accels().front().toString();
|
||||
m_tooltipManager->addTooltipFor(item, tooltip, TOP);
|
||||
// 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();
|
||||
}
|
||||
slot++;
|
||||
m_customBrushes->addItem(new SelectBrushItem(m_delegate, brush, slot));
|
||||
m_customBrushes->addItem(new BrushShortcutItem(shortcut, slot));
|
||||
m_customBrushes->addItem(new BrushOptionsItem(this, m_delegate, slot));
|
||||
}
|
||||
// Add empty spaces
|
||||
while (((slot-1) % columns) > 0)
|
||||
m_buttons->addItem(new Item(this, m_delegate, BrushRef(nullptr), slot++));
|
||||
|
||||
m_buttons->ItemChange.connect(base::Bind<void>(&BrushPopup::onButtonChange, this));
|
||||
m_buttons->setTransparent(true);
|
||||
m_buttons->setBgColor(gfx::ColorNone);
|
||||
addChild(m_buttons.get());
|
||||
m_customBrushes->addItem(new NewCustomBrushItem(m_delegate), 2, 1);
|
||||
m_customBrushes->addItem(new NewBrushOptionsItem);
|
||||
m_customBrushes->setExpansive(true);
|
||||
m_box.addChild(m_customBrushes);
|
||||
|
||||
gfx::Rect rc = box;
|
||||
int buttons = m_buttons->children().size();
|
||||
int rows = (buttons/columns + ((buttons%columns) > 0 ? 1: 0));
|
||||
rc.w *= columns;
|
||||
rc.h = rows * (rc.h-2*guiscale()) + 2*guiscale();
|
||||
|
||||
setBounds(rc);
|
||||
// Resize the window and change the hot region.
|
||||
setBounds(gfx::Rect(box.origin(), sizeHint()));
|
||||
setHotRegion(gfx::Region(bounds()));
|
||||
}
|
||||
|
||||
void BrushPopup::onButtonChange()
|
||||
void BrushPopup::onBrushChanges()
|
||||
{
|
||||
Item* item = static_cast<Item*>(m_buttons->getItem(m_buttons->selectedItem()));
|
||||
if (item->brush())
|
||||
BrushChange(item->brush());
|
||||
if (isVisible()) {
|
||||
gfx::Region rgn;
|
||||
getDrawableRegion(rgn, DrawableRegionFlags(kCutTopWindows | kUseChildArea));
|
||||
|
||||
regenerate(bounds());
|
||||
invalidate();
|
||||
|
||||
parent()->invalidateRegion(rgn);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -9,26 +9,23 @@
|
||||
#define APP_UI_BRUSH_POPUP_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/ui/button_set.h"
|
||||
#include "base/shared_ptr.h"
|
||||
#include "base/signal.h"
|
||||
#include "doc/brushes.h"
|
||||
#include "ui/box.h"
|
||||
#include "ui/popup_window.h"
|
||||
|
||||
namespace doc {
|
||||
class Brush;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class Menu;
|
||||
class TooltipManager;
|
||||
}
|
||||
#include "ui/tooltips.h"
|
||||
|
||||
namespace app {
|
||||
class ButtonSet;
|
||||
|
||||
class BrushSlot;
|
||||
|
||||
class BrushPopupDelegate {
|
||||
public:
|
||||
virtual ~BrushPopupDelegate() { }
|
||||
virtual BrushSlot onCreateBrushSlotFromActivePreferences() = 0;
|
||||
virtual void onSelectBrush(const doc::BrushRef& brush) = 0;
|
||||
virtual void onSelectBrushSlot(int slot) = 0;
|
||||
virtual void onDeleteBrushSlot(int slot) = 0;
|
||||
virtual void onDeleteAllBrushes() = 0;
|
||||
virtual bool onIsBrushSlotLocked(int slot) const = 0;
|
||||
@ -41,21 +38,22 @@ namespace app {
|
||||
BrushPopup(BrushPopupDelegate* delegate);
|
||||
|
||||
void setBrush(doc::Brush* brush);
|
||||
void regenerate(const gfx::Rect& box, const doc::Brushes& brushes);
|
||||
void regenerate(const gfx::Rect& box);
|
||||
|
||||
void setupTooltips(ui::TooltipManager* tooltipManager) {
|
||||
m_tooltipManager = tooltipManager;
|
||||
}
|
||||
|
||||
base::Signal1<void, const doc::BrushRef&> BrushChange;
|
||||
|
||||
static she::Surface* createSurfaceForBrush(const doc::BrushRef& brush);
|
||||
|
||||
private:
|
||||
void onButtonChange();
|
||||
void onStandardBrush();
|
||||
void onBrushChanges();
|
||||
|
||||
base::SharedPtr<ButtonSet> m_buttons;
|
||||
ui::TooltipManager* m_tooltipManager;
|
||||
ui::VBox m_box;
|
||||
ButtonSet m_standardBrushes;
|
||||
ButtonSet* m_customBrushes;
|
||||
BrushPopupDelegate* m_delegate;
|
||||
};
|
||||
|
||||
|
@ -33,6 +33,10 @@ namespace app {
|
||||
using namespace ui;
|
||||
using namespace app::skin;
|
||||
|
||||
// Last selected item for ButtonSet activated on mouse up when the
|
||||
// mouse capture is get.
|
||||
static int g_itemBeforeCapture = -1;
|
||||
|
||||
WidgetType buttonset_item_type()
|
||||
{
|
||||
static WidgetType type = kGenericWidget;
|
||||
@ -50,9 +54,10 @@ ButtonSet::Item::Item()
|
||||
setFocusStop(true);
|
||||
}
|
||||
|
||||
void ButtonSet::Item::setIcon(const SkinPartPtr& icon)
|
||||
void ButtonSet::Item::setIcon(const SkinPartPtr& icon, bool mono)
|
||||
{
|
||||
m_icon = icon;
|
||||
m_mono = mono;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@ -123,11 +128,16 @@ void ButtonSet::Item::onPaint(ui::PaintEvent& ev)
|
||||
theme->drawRect(g, rc, nw.get(), bg);
|
||||
|
||||
if (m_icon) {
|
||||
she::Surface* bmp = m_icon->bitmap(0);
|
||||
|
||||
if (isSelected() && hasCapture())
|
||||
g->drawColoredRgbaSurface(m_icon->bitmap(0), theme->colors.buttonSelectedText(),
|
||||
g->drawColoredRgbaSurface(bmp, theme->colors.buttonSelectedText(),
|
||||
iconRc.x, iconRc.y);
|
||||
else if (m_mono)
|
||||
g->drawColoredRgbaSurface(bmp, theme->colors.buttonNormalText(),
|
||||
iconRc.x, iconRc.y);
|
||||
else
|
||||
g->drawRgbaSurface(m_icon->bitmap(0), iconRc.x, iconRc.y);
|
||||
g->drawRgbaSurface(bmp, iconRc.x, iconRc.y);
|
||||
}
|
||||
|
||||
if (hasText()) {
|
||||
@ -160,41 +170,64 @@ bool ButtonSet::Item::onProcessMessage(ui::Message* msg)
|
||||
if (mnemonicPressed ||
|
||||
(hasFocus() && keymsg->scancode() == kKeySpace)) {
|
||||
buttonSet()->setSelectedItem(this);
|
||||
buttonSet()->onItemChange(this);
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ui::kMouseDownMessage:
|
||||
// Only for single-item and trigerred on mouse up ButtonSets: We
|
||||
// save the current selected item to restore it just in case the
|
||||
// user leaves the ButtonSet without releasing the mouse button
|
||||
// and the mouse capture if offered to other ButtonSet.
|
||||
if (buttonSet()->m_triggerOnMouseUp) {
|
||||
ASSERT(g_itemBeforeCapture < 0);
|
||||
g_itemBeforeCapture = buttonSet()->selectedItem();
|
||||
}
|
||||
|
||||
captureMouse();
|
||||
buttonSet()->setSelectedItem(this);
|
||||
invalidate();
|
||||
|
||||
if (static_cast<MouseMessage*>(msg)->left() &&
|
||||
!buttonSet()->m_triggerOnMouseUp) {
|
||||
buttonSet()->onItemChange(this);
|
||||
onClick();
|
||||
}
|
||||
break;
|
||||
|
||||
case ui::kMouseUpMessage:
|
||||
if (hasCapture()) {
|
||||
if (g_itemBeforeCapture >= 0)
|
||||
g_itemBeforeCapture = -1;
|
||||
|
||||
releaseMouse();
|
||||
invalidate();
|
||||
|
||||
if (static_cast<MouseMessage*>(msg)->left()) {
|
||||
if (buttonSet()->m_triggerOnMouseUp)
|
||||
buttonSet()->onItemChange(this);
|
||||
onClick();
|
||||
}
|
||||
else if (static_cast<MouseMessage*>(msg)->right()) {
|
||||
buttonSet()->onRightClick(this);
|
||||
onRightClick();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ui::kMouseMoveMessage:
|
||||
if (hasCapture()) {
|
||||
if (buttonSet()->m_offerCapture)
|
||||
offerCapture(static_cast<ui::MouseMessage*>(msg), buttonset_item_type());
|
||||
if (buttonSet()->m_offerCapture) {
|
||||
if (offerCapture(static_cast<ui::MouseMessage*>(msg), buttonset_item_type())) {
|
||||
// Only for ButtonSets trigerred on mouse up.
|
||||
if (buttonSet()->m_triggerOnMouseUp &&
|
||||
g_itemBeforeCapture >= 0) {
|
||||
// As we never received a kMouseUpMessage (so we never
|
||||
// called onClick()), we have to restore the selected
|
||||
// item at the point when we received the mouse capture.
|
||||
buttonSet()->setSelectedItem(g_itemBeforeCapture);
|
||||
g_itemBeforeCapture = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -232,6 +265,16 @@ void ButtonSet::Item::onSizeHint(ui::SizeHintEvent& ev)
|
||||
ev.setSizeHint(sz);
|
||||
}
|
||||
|
||||
void ButtonSet::Item::onClick()
|
||||
{
|
||||
buttonSet()->onItemChange(this);
|
||||
}
|
||||
|
||||
void ButtonSet::Item::onRightClick()
|
||||
{
|
||||
buttonSet()->onRightClick(this);
|
||||
}
|
||||
|
||||
ButtonSet::ButtonSet(int columns)
|
||||
: Grid(columns, false)
|
||||
, m_offerCapture(true)
|
||||
|
@ -22,15 +22,18 @@ namespace app {
|
||||
class Item : public ui::Widget {
|
||||
public:
|
||||
Item();
|
||||
void setIcon(const skin::SkinPartPtr& icon);
|
||||
void setIcon(const skin::SkinPartPtr& icon, bool mono = false);
|
||||
skin::SkinPartPtr icon() const { return m_icon; }
|
||||
ButtonSet* buttonSet();
|
||||
protected:
|
||||
void onPaint(ui::PaintEvent& ev) override;
|
||||
bool onProcessMessage(ui::Message* msg) override;
|
||||
void onSizeHint(ui::SizeHintEvent& ev) override;
|
||||
virtual void onClick();
|
||||
virtual void onRightClick();
|
||||
private:
|
||||
skin::SkinPartPtr m_icon;
|
||||
bool m_mono;
|
||||
};
|
||||
|
||||
ButtonSet(int columns);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "app/ui/context_bar.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/app_brushes.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/color_utils.h"
|
||||
#include "app/commands/commands.h"
|
||||
@ -77,13 +78,13 @@ public:
|
||||
BrushTypeField(ContextBar* owner)
|
||||
: ButtonSet(1)
|
||||
, m_owner(owner)
|
||||
, m_brushes(App::instance()->brushes())
|
||||
, m_popupWindow(this) {
|
||||
SkinPartPtr part(new SkinPart);
|
||||
part->setBitmap(
|
||||
0, BrushPopup::createSurfaceForBrush(BrushRef(nullptr)));
|
||||
|
||||
addItem(part);
|
||||
m_popupWindow.BrushChange.connect(&BrushTypeField::onBrushChange, this);
|
||||
}
|
||||
|
||||
~BrushTypeField() {
|
||||
@ -103,6 +104,10 @@ public:
|
||||
m_popupWindow.setupTooltips(tooltipManager);
|
||||
}
|
||||
|
||||
void showPopupAndHighlightSlot(int slot) {
|
||||
openPopup();
|
||||
}
|
||||
|
||||
protected:
|
||||
void onItemChange(Item* item) override {
|
||||
ButtonSet::onItemChange(item);
|
||||
@ -118,24 +123,69 @@ protected:
|
||||
}
|
||||
|
||||
// BrushPopupDelegate impl
|
||||
BrushSlot onCreateBrushSlotFromActivePreferences() override {
|
||||
auto& pref = Preferences::instance();
|
||||
auto& saveBrush = pref.saveBrush;
|
||||
auto& toolPref = pref.tool(App::instance()->activeTool());
|
||||
|
||||
int flags = 0;
|
||||
if (saveBrush.brushType()) flags |= int(BrushSlot::Flags::BrushType);
|
||||
if (saveBrush.brushSize()) flags |= int(BrushSlot::Flags::BrushSize);
|
||||
if (saveBrush.brushAngle()) flags |= int(BrushSlot::Flags::BrushAngle);
|
||||
if (saveBrush.fgColor()) flags |= int(BrushSlot::Flags::FgColor);
|
||||
if (saveBrush.bgColor()) flags |= int(BrushSlot::Flags::BgColor);
|
||||
if (saveBrush.inkType()) flags |= int(BrushSlot::Flags::InkType);
|
||||
if (saveBrush.inkOpacity()) flags |= int(BrushSlot::Flags::InkOpacity);
|
||||
if (saveBrush.shade()) flags |= int(BrushSlot::Flags::Shade);
|
||||
if (saveBrush.pixelPerfect()) flags |= int(BrushSlot::Flags::PixelPerfect);
|
||||
|
||||
return BrushSlot(
|
||||
BrushSlot::Flags(flags),
|
||||
ContextBar::createBrushFromPreferences(),
|
||||
pref.colorBar.fgColor(),
|
||||
pref.colorBar.bgColor(),
|
||||
toolPref.ink(),
|
||||
toolPref.opacity(),
|
||||
m_owner->getShade(),
|
||||
toolPref.freehandAlgorithm() == tools::FreehandAlgorithm::PIXEL_PERFECT);
|
||||
}
|
||||
|
||||
void onSelectBrush(const BrushRef& brush) override {
|
||||
if (brush->type() == kImageBrushType)
|
||||
m_owner->setActiveBrush(brush);
|
||||
else {
|
||||
Tool* tool = App::instance()->activeTool();
|
||||
ToolPreferences::Brush& brushPref = Preferences::instance().tool(tool).brush;
|
||||
|
||||
brushPref.type(static_cast<app::gen::BrushType>(brush->type()));
|
||||
|
||||
m_owner->setActiveBrush(
|
||||
ContextBar::createBrushFromPreferences(&brushPref));
|
||||
}
|
||||
}
|
||||
|
||||
void onSelectBrushSlot(int slot) override {
|
||||
m_owner->setActiveBrushBySlot(slot);
|
||||
}
|
||||
|
||||
void onDeleteBrushSlot(int slot) override {
|
||||
m_owner->removeBrush(slot);
|
||||
m_brushes.removeBrushSlot(slot);
|
||||
}
|
||||
|
||||
void onDeleteAllBrushes() override {
|
||||
m_owner->removeAllBrushes();
|
||||
m_brushes.removeAllBrushSlots();
|
||||
}
|
||||
|
||||
bool onIsBrushSlotLocked(int slot) const override {
|
||||
return m_owner->isBrushSlotLocked(slot);
|
||||
return m_brushes.isBrushSlotLocked(slot);
|
||||
}
|
||||
|
||||
void onLockBrushSlot(int slot) override {
|
||||
m_owner->lockBrushSlot(slot);
|
||||
m_brushes.lockBrushSlot(slot);
|
||||
}
|
||||
|
||||
void onUnlockBrushSlot(int slot) override {
|
||||
m_owner->unlockBrushSlot(slot);
|
||||
m_brushes.unlockBrushSlot(slot);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -151,7 +201,7 @@ private:
|
||||
void openPopup() {
|
||||
doc::BrushRef brush = m_owner->activeBrush();
|
||||
|
||||
m_popupWindow.regenerate(getPopupBox(), m_owner->getBrushes());
|
||||
m_popupWindow.regenerate(getPopupBox());
|
||||
m_popupWindow.setBrush(brush.get());
|
||||
|
||||
Region rgn(m_popupWindow.bounds().createUnion(bounds()));
|
||||
@ -165,21 +215,8 @@ private:
|
||||
deselectItems();
|
||||
}
|
||||
|
||||
void onBrushChange(const BrushRef& brush) {
|
||||
if (brush->type() == kImageBrushType)
|
||||
m_owner->setActiveBrush(brush);
|
||||
else {
|
||||
Tool* tool = App::instance()->activeTool();
|
||||
ToolPreferences::Brush& brushPref = Preferences::instance().tool(tool).brush;
|
||||
|
||||
brushPref.type(static_cast<app::gen::BrushType>(brush->type()));
|
||||
|
||||
m_owner->setActiveBrush(
|
||||
ContextBar::createBrushFromPreferences(&brushPref));
|
||||
}
|
||||
}
|
||||
|
||||
ContextBar* m_owner;
|
||||
AppBrushes& m_brushes;
|
||||
BrushPopup m_popupWindow;
|
||||
};
|
||||
|
||||
@ -696,6 +733,14 @@ public:
|
||||
return m_shade.createShadeRemap(left);
|
||||
}
|
||||
|
||||
Shade getShade() const {
|
||||
return m_shade.getShade();
|
||||
}
|
||||
|
||||
void setShade(const Shade& shade) {
|
||||
m_shade.setShade(shade);
|
||||
}
|
||||
|
||||
private:
|
||||
void onShowMenu() {
|
||||
loadShades();
|
||||
@ -1633,85 +1678,61 @@ void ContextBar::updateAutoSelectLayer(bool state)
|
||||
m_autoSelectLayer->setSelected(state);
|
||||
}
|
||||
|
||||
int ContextBar::addBrush(const doc::BrushRef& brush)
|
||||
{
|
||||
// Use an empty slot
|
||||
for (size_t i=0; i<m_brushes.size(); ++i) {
|
||||
if (!m_brushes[i].locked ||
|
||||
!m_brushes[i].brush) {
|
||||
m_brushes[i].brush = brush;
|
||||
return i+1;
|
||||
}
|
||||
}
|
||||
|
||||
m_brushes.push_back(BrushSlot(brush));
|
||||
return (int)m_brushes.size(); // Returns the slot
|
||||
}
|
||||
|
||||
void ContextBar::removeBrush(int slot)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_brushes.size()) {
|
||||
m_brushes[slot].brush.reset();
|
||||
|
||||
// Erase empty trailing slots
|
||||
while (!m_brushes.empty() &&
|
||||
!m_brushes[m_brushes.size()-1].brush)
|
||||
m_brushes.erase(--m_brushes.end());
|
||||
}
|
||||
}
|
||||
|
||||
void ContextBar::removeAllBrushes()
|
||||
{
|
||||
while (!m_brushes.empty())
|
||||
m_brushes.erase(--m_brushes.end());
|
||||
}
|
||||
|
||||
void ContextBar::setActiveBrushBySlot(int slot)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_brushes.size() &&
|
||||
m_brushes[slot].brush) {
|
||||
m_brushes[slot].locked = true;
|
||||
setActiveBrush(m_brushes[slot].brush);
|
||||
}
|
||||
}
|
||||
AppBrushes& brushes = App::instance()->brushes();
|
||||
BrushSlot brush = brushes.getBrushSlot(slot);
|
||||
if (!brush.isEmpty()) {
|
||||
brushes.lockBrushSlot(slot);
|
||||
|
||||
Brushes ContextBar::getBrushes()
|
||||
{
|
||||
Brushes brushes;
|
||||
for (const auto& slot : m_brushes)
|
||||
brushes.push_back(slot.brush);
|
||||
return brushes;
|
||||
}
|
||||
Tool* tool = App::instance()->activeTool();
|
||||
Preferences& pref = Preferences::instance();
|
||||
ToolPreferences& toolPref = pref.tool(tool);
|
||||
ToolPreferences::Brush& brushPref = toolPref.brush;
|
||||
|
||||
void ContextBar::lockBrushSlot(int slot)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_brushes.size() &&
|
||||
m_brushes[slot].brush) {
|
||||
m_brushes[slot].locked = true;
|
||||
}
|
||||
}
|
||||
if (brush.brush()) {
|
||||
if (brush.brush()->type() == doc::kImageBrushType) {
|
||||
setActiveBrush(brush.brush());
|
||||
}
|
||||
else {
|
||||
setActiveBrush(ContextBar::createBrushFromPreferences());
|
||||
|
||||
void ContextBar::unlockBrushSlot(int slot)
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_brushes.size() &&
|
||||
m_brushes[slot].brush) {
|
||||
m_brushes[slot].locked = false;
|
||||
}
|
||||
}
|
||||
if (brush.hasFlag(BrushSlot::Flags::BrushType))
|
||||
brushPref.type(static_cast<app::gen::BrushType>(brush.brush()->type()));
|
||||
|
||||
bool ContextBar::isBrushSlotLocked(int slot) const
|
||||
{
|
||||
--slot;
|
||||
if (slot >= 0 && slot < (int)m_brushes.size() &&
|
||||
m_brushes[slot].brush) {
|
||||
return m_brushes[slot].locked;
|
||||
if (brush.hasFlag(BrushSlot::Flags::BrushSize))
|
||||
brushPref.size(brush.brush()->size());
|
||||
|
||||
if (brush.hasFlag(BrushSlot::Flags::BrushAngle))
|
||||
brushPref.angle(brush.brush()->angle());
|
||||
}
|
||||
}
|
||||
|
||||
if (brush.hasFlag(BrushSlot::Flags::FgColor))
|
||||
pref.colorBar.fgColor(brush.fgColor());
|
||||
|
||||
if (brush.hasFlag(BrushSlot::Flags::BgColor))
|
||||
pref.colorBar.bgColor(brush.bgColor());
|
||||
|
||||
if (brush.hasFlag(BrushSlot::Flags::InkType))
|
||||
setInkType(brush.inkType());
|
||||
|
||||
if (brush.hasFlag(BrushSlot::Flags::InkOpacity))
|
||||
toolPref.opacity(brush.inkOpacity());
|
||||
|
||||
if (brush.hasFlag(BrushSlot::Flags::Shade))
|
||||
m_inkShades->setShade(brush.shade());
|
||||
|
||||
if (brush.hasFlag(BrushSlot::Flags::PixelPerfect))
|
||||
toolPref.freehandAlgorithm(
|
||||
(brush.pixelPerfect() ?
|
||||
tools::FreehandAlgorithm::PIXEL_PERFECT:
|
||||
tools::FreehandAlgorithm::REGULAR));
|
||||
}
|
||||
else {
|
||||
updateForTool(App::instance()->activeTool());
|
||||
m_brushType->showPopupAndHighlightSlot(slot);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void ContextBar::setActiveBrush(const doc::BrushRef& brush)
|
||||
@ -1757,6 +1778,11 @@ doc::BrushRef ContextBar::createBrushFromPreferences(ToolPreferences::Brush* bru
|
||||
return brush;
|
||||
}
|
||||
|
||||
Shade ContextBar::getShade() const
|
||||
{
|
||||
return m_inkShades->getShade();
|
||||
}
|
||||
|
||||
doc::Remap* ContextBar::createShadeRemap(bool left)
|
||||
{
|
||||
return m_inkShades->createShadeRemap(left);
|
||||
|
@ -10,13 +10,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/shade.h"
|
||||
#include "app/tools/ink_type.h"
|
||||
#include "app/tools/selection_mode.h"
|
||||
#include "app/ui/context_bar_observer.h"
|
||||
#include "base/connection.h"
|
||||
#include "base/observable.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/brushes.h"
|
||||
#include "ui/box.h"
|
||||
|
||||
#include <vector>
|
||||
@ -53,26 +53,16 @@ namespace app {
|
||||
void updateAutoSelectLayer(bool state);
|
||||
|
||||
void setActiveBrush(const doc::BrushRef& brush);
|
||||
void setActiveBrushBySlot(int slot);
|
||||
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);
|
||||
void removeBrush(int slot);
|
||||
void removeAllBrushes();
|
||||
void setActiveBrushBySlot(int slot);
|
||||
doc::Brushes getBrushes();
|
||||
|
||||
void lockBrushSlot(int slot);
|
||||
void unlockBrushSlot(int slot);
|
||||
bool isBrushSlotLocked(int slot) const;
|
||||
|
||||
static doc::BrushRef createBrushFromPreferences(
|
||||
ToolPreferences::Brush* brushPref = nullptr);
|
||||
|
||||
doc::Remap* createShadeRemap(bool left);
|
||||
void reverseShadeColors();
|
||||
Shade getShade() const;
|
||||
|
||||
void setInkType(tools::InkType type);
|
||||
|
||||
@ -91,21 +81,6 @@ namespace app {
|
||||
void onSymmetryModeChange();
|
||||
void onDropPixels(ContextBarObserver::DropAction action);
|
||||
|
||||
struct BrushSlot {
|
||||
// True if the user locked the brush using the shortcut key to
|
||||
// access it.
|
||||
bool locked;
|
||||
|
||||
// Can be null if the user deletes the brush.
|
||||
doc::BrushRef brush;
|
||||
|
||||
BrushSlot(const doc::BrushRef& brush)
|
||||
: locked(false), brush(brush) {
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<BrushSlot> BrushSlots;
|
||||
|
||||
class BrushTypeField;
|
||||
class BrushAngleField;
|
||||
class BrushSizeField;
|
||||
@ -155,7 +130,6 @@ namespace app {
|
||||
RotAlgorithmField* m_rotAlgo;
|
||||
DropPixelsField* m_dropPixels;
|
||||
doc::BrushRef m_activeBrush;
|
||||
BrushSlots m_brushes;
|
||||
ui::Label* m_selectBoxHelp;
|
||||
SymmetryField* m_symmetry;
|
||||
base::ScopedConnection m_sizeConn;
|
||||
|
@ -434,6 +434,12 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
|
||||
|
||||
if (!widget && columns)
|
||||
widget = new ButtonSet(strtol(columns, NULL, 10));
|
||||
|
||||
if (ButtonSet* buttonset = dynamic_cast<ButtonSet*>(widget)) {
|
||||
bool multiple = bool_attr_is_true(elem, "multiple");
|
||||
if (multiple)
|
||||
buttonset->setMultipleSelection(multiple);
|
||||
}
|
||||
}
|
||||
else if (elem_name == "item") {
|
||||
if (!parent)
|
||||
|
@ -49,6 +49,10 @@ static void collect_widgets_with_ids(TiXmlElement* elem, XmlElements& widgets)
|
||||
|
||||
static std::string convert_type(const std::string& name)
|
||||
{
|
||||
static std::string parent;
|
||||
if (name != "item")
|
||||
parent = name;
|
||||
|
||||
if (name == "box") return "ui::Box";
|
||||
if (name == "button") return "ui::Button";
|
||||
if (name == "buttonset") return "app::ButtonSet";
|
||||
@ -59,6 +63,7 @@ static std::string convert_type(const std::string& name)
|
||||
if (name == "entry") return "ui::Entry";
|
||||
if (name == "grid") return "ui::Grid";
|
||||
if (name == "hbox") return "ui::HBox";
|
||||
if (name == "item" && parent == "buttonset") return "app::ButtonSet::Item";
|
||||
if (name == "label") return "ui::Label";
|
||||
if (name == "link") return "ui::LinkLabel";
|
||||
if (name == "listbox") return "ui::ListBox";
|
||||
|
@ -1236,7 +1236,7 @@ void Widget::releaseMouse()
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
|
||||
bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
|
||||
{
|
||||
if (hasCapture()) {
|
||||
Widget* pick = manager()->pick(mouseMsg->position());
|
||||
@ -1250,8 +1250,10 @@ void Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
|
||||
mouseMsg->position());
|
||||
mouseMsg2->addRecipient(pick);
|
||||
manager()->enqueueMessage(mouseMsg2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Widget::hasFocus()
|
||||
|
@ -340,8 +340,9 @@ namespace ui {
|
||||
bool hasMouseOver();
|
||||
bool hasCapture();
|
||||
|
||||
// Offer the capture to widgets of the given type
|
||||
void offerCapture(ui::MouseMessage* mouseMsg, int widget_type);
|
||||
// Offer the capture to widgets of the given type. Returns true if
|
||||
// the capture was passed to other widget.
|
||||
bool offerCapture(ui::MouseMessage* mouseMsg, int widget_type);
|
||||
|
||||
// Returns lower-case letter that represet the mnemonic of the widget
|
||||
// (the underscored character, i.e. the letter after & symbol).
|
||||
|
Loading…
x
Reference in New Issue
Block a user