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