1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/ui/color_button.h"
12 
13 #include "app/app.h"
14 #include "app/color.h"
15 #include "app/color_utils.h"
16 #include "app/modules/editors.h"
17 #include "app/modules/gfx.h"
18 #include "app/site.h"
19 #include "app/ui/color_bar.h"
20 #include "app/ui/color_popup.h"
21 #include "app/ui/editor/editor.h"
22 #include "app/ui/skin/skin_theme.h"
23 #include "app/ui/status_bar.h"
24 #include "app/ui_context.h"
25 #include "doc/layer.h"
26 #include "doc/sprite.h"
27 #include "gfx/rect_io.h"
28 #include "ui/ui.h"
29 
30 namespace app {
31 
32 using namespace app::skin;
33 using namespace ui;
34 
colorbutton_type()35 static WidgetType colorbutton_type()
36 {
37   static WidgetType type = kGenericWidget;
38   if (type == kGenericWidget)
39     type = register_widget_type();
40   return type;
41 }
42 
ColorButton(const app::Color & color,const PixelFormat pixelFormat,const ColorButtonOptions & options)43 ColorButton::ColorButton(const app::Color& color,
44                          const PixelFormat pixelFormat,
45                          const ColorButtonOptions& options)
46   : ButtonBase("", colorbutton_type(), kButtonWidget, kButtonWidget)
47   , m_color(color)
48   , m_pixelFormat(pixelFormat)
49   , m_window(nullptr)
50   , m_dependOnLayer(false)
51   , m_options(options)
52 {
53   setFocusStop(true);
54   initTheme();
55 
56   UIContext::instance()->add_observer(this);
57 }
58 
~ColorButton()59 ColorButton::~ColorButton()
60 {
61   UIContext::instance()->remove_observer(this);
62 
63   delete m_window;       // widget, window
64 }
65 
pixelFormat() const66 PixelFormat ColorButton::pixelFormat() const
67 {
68   return m_pixelFormat;
69 }
70 
setPixelFormat(PixelFormat pixelFormat)71 void ColorButton::setPixelFormat(PixelFormat pixelFormat)
72 {
73   m_pixelFormat = pixelFormat;
74   invalidate();
75 }
76 
getColor() const77 app::Color ColorButton::getColor() const
78 {
79   return m_color;
80 }
81 
setColor(const app::Color & origColor)82 void ColorButton::setColor(const app::Color& origColor)
83 {
84   // Before change (this signal can modify the color)
85   app::Color color = origColor;
86   BeforeChange(color);
87 
88   m_color = color;
89 
90   // Change the color in its related window
91   if (m_window) {
92     // In the window we show the original color. In case
93     // BeforeChange() has changed the color type (e.g. to index), we
94     // don't care, in the window we prefer to keep the original
95     // HSV/HSL values.
96     m_window->setColor(origColor, ColorPopup::DontChangeType);
97   }
98 
99   // Emit signal
100   Change(color);
101 
102   invalidate();
103 }
104 
getColorByPosition(const gfx::Point & pos)105 app::Color ColorButton::getColorByPosition(const gfx::Point& pos)
106 {
107   // Ignore the position
108   return m_color;
109 }
110 
onInitTheme(InitThemeEvent & ev)111 void ColorButton::onInitTheme(InitThemeEvent& ev)
112 {
113   ButtonBase::onInitTheme(ev);
114   setStyle(SkinTheme::instance()->styles.colorButton());
115 }
116 
onProcessMessage(Message * msg)117 bool ColorButton::onProcessMessage(Message* msg)
118 {
119   switch (msg->type()) {
120 
121     case kOpenMessage:
122       if (!m_windowDefaultBounds.isEmpty() &&
123           this->isVisible()) {
124         openPopup(false);
125       }
126       break;
127 
128     case kCloseMessage:
129       if (m_window && m_window->isVisible())
130         m_window->closeWindow(NULL);
131       break;
132 
133     case kMouseEnterMessage:
134       StatusBar::instance()->showColor(0, "", m_color);
135       break;
136 
137     case kMouseLeaveMessage:
138       StatusBar::instance()->clearText();
139       break;
140 
141     case kMouseMoveMessage:
142       if (hasCapture()) {
143         gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
144         Widget* picked = manager()->pick(mousePos);
145         app::Color color = m_color;
146 
147         if (picked && picked != this) {
148           // Pick a color from a IColorSource
149           if (IColorSource* colorSource = dynamic_cast<IColorSource*>(picked)) {
150             color = colorSource->getColorByPosition(mousePos);
151           }
152         }
153 
154         // Did the color change?
155         if (color != m_color) {
156           setColor(color);
157         }
158       }
159       break;
160 
161     case kSetCursorMessage:
162       if (hasCapture()) {
163         ui::set_mouse_cursor(kCustomCursor, SkinTheme::instance()->cursors.eyedropper());
164         return true;
165       }
166       break;
167 
168   }
169 
170   return ButtonBase::onProcessMessage(msg);
171 }
172 
onSizeHint(SizeHintEvent & ev)173 void ColorButton::onSizeHint(SizeHintEvent& ev)
174 {
175   ButtonBase::onSizeHint(ev);
176 
177   gfx::Rect box;
178   getTextIconInfo(&box);
179   box.w = 64*guiscale();
180 
181   gfx::Size sz = ev.sizeHint();
182   sz.w = std::max(sz.w, box.w);
183   ev.setSizeHint(sz);
184 }
185 
onPaint(PaintEvent & ev)186 void ColorButton::onPaint(PaintEvent& ev)
187 {
188   Graphics* g = ev.graphics();
189   SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
190   gfx::Rect rc = clientBounds();
191 
192   gfx::Color bg = bgColor();
193   if (gfx::is_transparent(bg))
194     bg = theme->colors.face();
195   g->fillRect(bg, rc);
196 
197   app::Color color;
198 
199   // When the button is pushed, show the negative
200   m_dependOnLayer = false;
201   if (isSelected()) {
202     color = app::Color::fromRgb(255-m_color.getRed(),
203                                 255-m_color.getGreen(),
204                                 255-m_color.getBlue());
205   }
206   // When the button is not pressed, show the real color
207   else {
208     color = m_color;
209 
210     // Show transparent color in indexed sprites as mask color when we
211     // are in a transparent layer.
212     if (color.getType() == app::Color::IndexType &&
213         current_editor &&
214         current_editor->sprite() &&
215         current_editor->sprite()->pixelFormat() == IMAGE_INDEXED) {
216       m_dependOnLayer = true;
217 
218       if (int(current_editor->sprite()->transparentColor()) == color.getIndex() &&
219           current_editor->layer() &&
220           !current_editor->layer()->isBackground()) {
221         color = app::Color::fromMask();
222       }
223     }
224   }
225 
226   draw_color_button(g, rc,
227                     color,
228                     (doc::ColorMode)m_pixelFormat,
229                     hasMouseOver(), false);
230 
231   // Draw text
232   std::string str = m_color.toHumanReadableString(m_pixelFormat,
233     app::Color::ShortHumanReadableString);
234 
235   setTextQuiet(str.c_str());
236 
237   gfx::Color textcolor = gfx::rgba(255, 255, 255);
238   if (color.isValid())
239     textcolor = color_utils::blackandwhite_neg(
240       gfx::rgba(color.getRed(), color.getGreen(), color.getBlue()));
241 
242   gfx::Rect textrc;
243   getTextIconInfo(NULL, &textrc);
244   g->drawUIText(text(), textcolor, gfx::ColorNone, textrc.origin(), 0);
245 }
246 
onClick(Event & ev)247 void ColorButton::onClick(Event& ev)
248 {
249   ButtonBase::onClick(ev);
250 
251   // If the popup window was not created or shown yet..
252   if (!m_window || !m_window->isVisible()) {
253     // Open it
254     openPopup(false);
255   }
256   else if (!m_window->isMoveable()) {
257     // If it is visible, close it
258     closePopup();
259   }
260 }
261 
onLoadLayout(ui::LoadLayoutEvent & ev)262 void ColorButton::onLoadLayout(ui::LoadLayoutEvent& ev)
263 {
264   if (canPin()) {
265     bool pinned = false;
266     ev.stream() >> pinned;
267     if (ev.stream() && pinned)
268       ev.stream() >> m_windowDefaultBounds;
269 
270     m_hiddenPopupBounds = m_windowDefaultBounds;
271   }
272 }
273 
onSaveLayout(ui::SaveLayoutEvent & ev)274 void ColorButton::onSaveLayout(ui::SaveLayoutEvent& ev)
275 {
276   if (canPin() && m_window && m_window->isPinned())
277     ev.stream() << 1 << ' ' << m_window->bounds();
278   else
279     ev.stream() << 0;
280 }
281 
isPopupVisible()282 bool ColorButton::isPopupVisible()
283 {
284   return (m_window && m_window->isVisible());
285 }
286 
openPopup(const bool forcePinned)287 void ColorButton::openPopup(const bool forcePinned)
288 {
289   const bool pinned = forcePinned ||
290     (!m_windowDefaultBounds.isEmpty());
291 
292   if (m_window == NULL) {
293     m_window = new ColorPopup(m_options);
294     m_window->Close.connect(&ColorButton::onWindowClose, this);
295     m_window->ColorChange.connect(&ColorButton::onWindowColorChange, this);
296   }
297 
298   m_window->setColor(m_color, ColorPopup::ChangeType);
299   m_window->openWindow();
300 
301   gfx::Rect winBounds;
302   if (!pinned || (forcePinned && m_hiddenPopupBounds.isEmpty())) {
303     winBounds = gfx::Rect(m_window->bounds().origin(),
304                           m_window->sizeHint());
305     winBounds.x = MID(0, bounds().x, ui::display_w()-winBounds.w);
306     if (bounds().y2() <= ui::display_h()-winBounds.h)
307       winBounds.y = MAX(0, bounds().y2());
308     else
309       winBounds.y = MAX(0, bounds().y-winBounds.h);
310   }
311   else if (forcePinned) {
312     winBounds = m_hiddenPopupBounds;
313   }
314   else {
315     winBounds = m_windowDefaultBounds;
316   }
317   winBounds.x = MID(0, winBounds.x, ui::display_w()-winBounds.w);
318   winBounds.y = MID(0, winBounds.y, ui::display_h()-winBounds.h);
319   m_window->setBounds(winBounds);
320 
321   m_window->manager()->dispatchMessages();
322   m_window->layout();
323 
324   m_window->setPinned(pinned);
325 
326   // Add the ColorButton area to the ColorPopup hot-region
327   if (!pinned) {
328     gfx::Rect rc = bounds().createUnion(m_window->bounds());
329     rc.enlarge(8);
330     gfx::Region rgn(rc);
331     static_cast<PopupWindow*>(m_window)->setHotRegion(rgn);
332   }
333 
334   m_windowDefaultBounds = gfx::Rect();
335 }
336 
closePopup()337 void ColorButton::closePopup()
338 {
339   if (m_window)
340     m_window->closeWindow(nullptr);
341 }
342 
onWindowClose(ui::CloseEvent & ev)343 void ColorButton::onWindowClose(ui::CloseEvent& ev)
344 {
345   m_hiddenPopupBounds = m_window->bounds();
346 }
347 
onWindowColorChange(const app::Color & color)348 void ColorButton::onWindowColorChange(const app::Color& color)
349 {
350   setColor(color);
351 }
352 
onActiveSiteChange(const Site & site)353 void ColorButton::onActiveSiteChange(const Site& site)
354 {
355   if (m_dependOnLayer)
356     invalidate();
357 
358   if (canPin()) {
359     // Hide window
360     if (!site.document()) {
361       if (m_window)
362         m_window->setVisible(false);
363     }
364     // Show window if it's pinned
365     else {
366       // Check if it's pinned from the preferences (m_windowDefaultBounds)
367       if (!m_window && !m_windowDefaultBounds.isEmpty())
368         openPopup(false);
369       // Or check if the window was hidden but it's pinned, so we've
370       // to show it again.
371       else if (m_window && m_window->isPinned())
372         m_window->setVisible(true);
373     }
374   }
375 }
376 
377 } // namespace app
378