1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/app.h"
13#include "app/color_spaces.h"
14#include "app/color_utils.h"
15#include "app/modules/gfx.h"
16#include "app/ui/color_sliders.h"
17#include "app/ui/skin/skin_slider_property.h"
18#include "app/ui/skin/skin_theme.h"
19#include "base/scoped_value.h"
20#include "gfx/hsl.h"
21#include "gfx/rgb.h"
22#include "ui/box.h"
23#include "ui/entry.h"
24#include "ui/graphics.h"
25#include "ui/label.h"
26#include "ui/message.h"
27#include "ui/size_hint_event.h"
28#include "ui/slider.h"
29#include "ui/theme.h"
30
31#include <algorithm>
32#include <limits>
33
34namespace app {
35
36using namespace app::skin;
37using namespace gfx;
38using namespace ui;
39
40namespace {
41
42 // This class is used as SkinSliderProperty for RGB/HSV sliders to
43 // draw the background of them.
44 class ColorSliderBgPainter : public ISliderBgPainter {
45 public:
46 ColorSliderBgPainter(ColorSliders::Channel channel)
47 : m_channel(channel)
48 { }
49
50 void setColor(const app::Color& color) {
51 m_color = color;
52 }
53
54 void paint(Slider* slider, Graphics* g, const gfx::Rect& rc) {
55 // Special alpha bar (with two vertical lines)
56 if (m_channel == ColorSliders::Channel::Alpha) {
57 draw_alpha_slider(g, rc, m_color);
58 return;
59 }
60
61 // Color space conversion
62 auto convertColor = convert_from_current_to_screen_color_space();
63
64 gfx::Color color = gfx::ColorNone;
65 int w = std::max(rc.w-1, 1);
66
67 for (int x=0; x <= w; ++x) {
68 switch (m_channel) {
69 case ColorSliders::Channel::Red:
70 color = gfx::rgba(255 * x / w, m_color.getGreen(), m_color.getBlue());
71 break;
72 case ColorSliders::Channel::Green:
73 color = gfx::rgba(m_color.getRed(), 255 * x / w, m_color.getBlue());
74 break;
75 case ColorSliders::Channel::Blue:
76 color = gfx::rgba(m_color.getRed(), m_color.getGreen(), 255 * x / w);
77 break;
78
79 case ColorSliders::Channel::HsvHue:
80 color = color_utils::color_for_ui(
81 app::Color::fromHsv(360.0 * x / w,
82 m_color.getHsvSaturation(),
83 m_color.getHsvValue()));
84 break;
85 case ColorSliders::Channel::HsvSaturation:
86 color = color_utils::color_for_ui(
87 app::Color::fromHsv(m_color.getHsvHue(),
88 double(x) / double(w),
89 m_color.getHsvValue()));
90 break;
91 case ColorSliders::Channel::HsvValue:
92 color = color_utils::color_for_ui(
93 app::Color::fromHsv(m_color.getHsvHue(),
94 m_color.getHsvSaturation(),
95 double(x) / double(w)));
96 break;
97
98 case ColorSliders::Channel::HslHue:
99 color = color_utils::color_for_ui(
100 app::Color::fromHsl(360.0 * x / w,
101 m_color.getHslSaturation(),
102 m_color.getHslLightness()));
103 break;
104 case ColorSliders::Channel::HslSaturation:
105 color = color_utils::color_for_ui(
106 app::Color::fromHsl(m_color.getHslHue(),
107 double(x) / double(w),
108 m_color.getHslLightness()));
109 break;
110 case ColorSliders::Channel::HslLightness:
111 color = color_utils::color_for_ui(
112 app::Color::fromHsl(m_color.getHslHue(),
113 m_color.getHslSaturation(),
114 double(x) / double(w)));
115 break;
116
117 case ColorSliders::Channel::Gray:
118 color = color_utils::color_for_ui(
119 app::Color::fromGray(255 * x / w));
120 break;
121 }
122 g->drawVLine(convertColor(color), rc.x+x, rc.y, rc.h);
123 }
124 }
125
126 private:
127 ColorSliders::Channel m_channel;
128 app::Color m_color;
129 };
130
131 class ColorEntry : public Entry {
132 public:
133 ColorEntry(Slider* absSlider, Slider* relSlider)
134 : Entry(4, "0")
135 , m_absSlider(absSlider)
136 , m_relSlider(relSlider)
137 , m_recent_focus(false) {
138 }
139
140 private:
141 int minValue() const {
142 if (m_absSlider->isVisible())
143 return m_absSlider->getMinValue();
144 else if (m_relSlider->isVisible())
145 return m_relSlider->getMinValue();
146 else
147 return 0;
148 }
149
150 int maxValue() const {
151 if (m_absSlider->isVisible())
152 return m_absSlider->getMaxValue();
153 else if (m_relSlider->isVisible())
154 return m_relSlider->getMaxValue();
155 else
156 return 0;
157 }
158
159 bool onProcessMessage(Message* msg) override {
160 switch (msg->type()) {
161
162 case kFocusEnterMessage:
163 m_recent_focus = true;
164 break;
165
166 case kKeyDownMessage:
167 if (Entry::onProcessMessage(msg))
168 return true;
169
170 if (hasFocus()) {
171 int scancode = static_cast<KeyMessage*>(msg)->scancode();
172
173 switch (scancode) {
174 // Enter just remove the focus
175 case kKeyEnter:
176 case kKeyEnterPad:
177 releaseFocus();
178 return true;
179
180 case kKeyDown:
181 case kKeyUp: {
182 int value = textInt();
183 if (scancode == kKeyDown)
184 --value;
185 else
186 ++value;
187
188 setTextf("%d", std::clamp(value, minValue(), maxValue()));
189 selectAllText();
190
191 onChange();
192 return true;
193 }
194 }
195
196 // Process focus movement key here because if our
197 // CustomizedGuiManager catches this kKeyDownMessage it
198 // will process it as a shortcut to switch the Timeline.
199 //
200 // Note: The default ui::Manager handles focus movement
201 // shortcuts only for foreground windows.
202 // TODO maybe that should change
203 if (hasFocus() &&
204 manager()->processFocusMovementMessage(msg))
205 return true;
206 }
207 return false;
208 }
209
210 bool result = Entry::onProcessMessage(msg);
211
212 if (msg->type() == kMouseDownMessage && m_recent_focus) {
213 m_recent_focus = false;
214 selectAllText();
215 }
216
217 return result;
218 }
219
220 Slider* m_absSlider;
221 Slider* m_relSlider;
222
223 // TODO remove this calling setFocus() in
224 // Widget::onProcessMessage() instead of
225 // Manager::handleWindowZOrder()
226 bool m_recent_focus;
227 };
228
229}
230
231//////////////////////////////////////////////////////////////////////
232// ColorSliders
233
234ColorSliders::ColorSliders()
235 : Widget(kGenericWidget)
236 , m_items(int(Channel::Channels))
237 , m_grid(3, false)
238 , m_mode(Mode::Absolute)
239 , m_lockSlider(-1)
240 , m_lockEntry(-1)
241 , m_color(app::Color::fromMask())
242{
243 addChild(&m_grid);
244 m_grid.setChildSpacing(0);
245
246 // Same order as in Channel enum
247 static_assert(Channel::Red == (Channel)0, "");
248 static_assert(Channel::Alpha == (Channel)10, "");
249 addSlider(Channel::Red, "R", 0, 255, -100, 100);
250 addSlider(Channel::Green, "G", 0, 255, -100, 100);
251 addSlider(Channel::Blue, "B", 0, 255, -100, 100);
252 addSlider(Channel::HsvHue, "H", 0, 360, -180, 180);
253 addSlider(Channel::HsvSaturation, "S", 0, 100, -100, 100);
254 addSlider(Channel::HsvValue, "V", 0, 100, -100, 100);
255 addSlider(Channel::HslHue, "H", 0, 360, -180, 180);
256 addSlider(Channel::HslSaturation, "S", 0, 100, -100, 100);
257 addSlider(Channel::HslLightness, "L", 0, 100, -100, 100);
258 addSlider(Channel::Gray, "V", 0, 255, -100, 100);
259 addSlider(Channel::Alpha, "A", 0, 255, -100, 100);
260
261 m_appConn = App::instance()
262 ->ColorSpaceChange.connect([this]{ invalidate(); });
263}
264
265void ColorSliders::setColor(const app::Color& color)
266{
267 m_color = color;
268 onSetColor(color);
269 updateSlidersBgColor();
270}
271
272void ColorSliders::setColorType(const app::Color::Type type)
273{
274 std::vector<app::Color::Type> types(1, type);
275 setColorTypes(types);
276}
277
278void ColorSliders::setColorTypes(const std::vector<app::Color::Type>& types)
279{
280 for (Item& item : m_items)
281 item.show = false;
282
283 bool visible = false;
284 for (auto type : types) {
285 switch (type) {
286 case app::Color::RgbType:
287 m_items[Channel::Red].show = true;
288 m_items[Channel::Green].show = true;
289 m_items[Channel::Blue].show = true;
290 m_items[Channel::Alpha].show = true;
291 visible = true;
292 break;
293 case app::Color::HsvType:
294 m_items[Channel::HsvHue].show = true;
295 m_items[Channel::HsvSaturation].show = true;
296 m_items[Channel::HsvValue].show = true;
297 m_items[Channel::Alpha].show = true;
298 visible = true;
299 break;
300 case app::Color::HslType:
301 m_items[Channel::HslHue].show = true;
302 m_items[Channel::HslSaturation].show = true;
303 m_items[Channel::HslLightness].show = true;
304 m_items[Channel::Alpha].show = true;
305 visible = true;
306 break;
307 case app::Color::GrayType:
308 m_items[Channel::Gray].show = true;
309 m_items[Channel::Alpha].show = true;
310 visible = true;
311 break;
312 case app::Color::MaskType:
313 case app::Color::IndexType:
314 // Do nothing
315 break;
316 }
317 }
318
319 setVisible(visible);
320
321 updateSlidersVisibility();
322 updateSlidersBgColor();
323 layout();
324}
325
326void ColorSliders::setMode(Mode mode)
327{
328 m_mode = mode;
329
330 updateSlidersVisibility();
331 resetRelativeSliders();
332 layout();
333}
334
335void ColorSliders::updateSlidersVisibility()
336{
337 for (auto& item : m_items) {
338 bool v = item.show;
339 item.label->setVisible(v);
340 item.box->setVisible(v);
341 item.entry->setVisible(v);
342 item.absSlider->setVisible(v && m_mode == Mode::Absolute);
343 item.relSlider->setVisible(v && m_mode == Mode::Relative);
344 }
345}
346
347void ColorSliders::resetRelativeSliders()
348{
349 for (Item& item : m_items)
350 item.relSlider->setValue(0);
351}
352
353void ColorSliders::onSizeHint(SizeHintEvent& ev)
354{
355 ev.setSizeHint(m_grid.sizeHint());
356}
357
358void ColorSliders::addSlider(const Channel channel,
359 const char* labelText,
360 const int absMin, const int absMax,
361 const int relMin, const int relMax)
362{
363 auto theme = skin::SkinTheme::get(this);
364
365 Item& item = m_items[channel];
366 ASSERT(!item.label);
367 item.label = new Label(labelText);
368 item.box = new HBox();
369 item.absSlider = new Slider(absMin, absMax, 0);
370 item.relSlider = new Slider(relMin, relMax, 0);
371 item.entry = new ColorEntry(item.absSlider, item.relSlider);
372
373 item.relSlider->setSizeHint(gfx::Size(128, 0));
374 item.absSlider->setSizeHint(gfx::Size(128, 0));
375 item.absSlider->setProperty(std::make_shared<SkinSliderProperty>(new ColorSliderBgPainter(channel)));
376 item.absSlider->setDoubleBuffered(true);
377 get_skin_property(item.entry)->setLook(MiniLook);
378
379 item.absSlider->Change.connect([this, channel]{ onSliderChange(channel); });
380 item.relSlider->Change.connect([this, channel]{ onSliderChange(channel); });
381 item.entry->Change.connect([this, channel]{ onEntryChange(channel); });
382
383 item.box->addChild(item.absSlider);
384 item.box->addChild(item.relSlider);
385 item.absSlider->setFocusStop(false);
386 item.relSlider->setFocusStop(false);
387 item.absSlider->setExpansive(true);
388 item.relSlider->setExpansive(true);
389 item.relSlider->setVisible(false);
390
391 gfx::Size sz(std::numeric_limits<int>::max(),
392 theme->dimensions.colorSliderHeight());
393 item.label->setMaxSize(sz);
394 item.box->setMaxSize(sz);
395 item.entry->setMaxSize(sz);
396
397 m_grid.addChildInCell(item.label, 1, 1, LEFT | MIDDLE);
398 m_grid.addChildInCell(item.box, 1, 1, HORIZONTAL | VERTICAL);
399 m_grid.addChildInCell(item.entry, 1, 1, LEFT | MIDDLE);
400}
401
402void ColorSliders::setAbsSliderValue(const Channel i, int value)
403{
404 if (m_lockSlider == i)
405 return;
406
407 m_items[i].absSlider->setValue(value);
408 updateEntryText(i);
409}
410
411void ColorSliders::setRelSliderValue(const Channel i, int value)
412{
413 if (m_lockSlider == i)
414 return;
415
416 m_items[i].relSlider->setValue(value);
417 updateEntryText(i);
418}
419
420int ColorSliders::getAbsSliderValue(const Channel i) const
421{
422 return m_items[i].absSlider->getValue();
423}
424
425int ColorSliders::getRelSliderValue(const Channel i) const
426{
427 return m_items[i].relSlider->getValue();
428}
429
430void ColorSliders::syncRelHsvHslSliders()
431{
432 // From HSV -> HSL
433 if (m_items[Channel::HsvHue].show) {
434 setRelSliderValue(Channel::HslHue, getRelSliderValue(Channel::HsvHue));
435 setRelSliderValue(Channel::HslSaturation, getRelSliderValue(Channel::HsvSaturation));
436 setRelSliderValue(Channel::HslLightness, getRelSliderValue(Channel::HsvValue));
437 }
438 // From HSL -> HSV
439 else if (m_items[Channel::HslHue].show) {
440 setRelSliderValue(Channel::HsvHue, getRelSliderValue(Channel::HslHue));
441 setRelSliderValue(Channel::HsvSaturation, getRelSliderValue(Channel::HslSaturation));
442 setRelSliderValue(Channel::HsvValue, getRelSliderValue(Channel::HslLightness));
443 }
444}
445
446void ColorSliders::onSliderChange(const Channel i)
447{
448 base::ScopedValue<int> lock(m_lockSlider, i, m_lockSlider);
449
450 updateEntryText(i);
451 onControlChange(i);
452}
453
454void ColorSliders::onEntryChange(const Channel i)
455{
456 base::ScopedValue<int> lock(m_lockEntry, i, m_lockEntry);
457
458 // Update the slider related to the changed entry widget.
459 int value = m_items[i].entry->textInt();
460
461 Slider* slider = (m_mode == Mode::Absolute ?
462 m_items[i].absSlider:
463 m_items[i].relSlider);
464 value = std::clamp(value, slider->getMinValue(), slider->getMaxValue());
465 slider->setValue(value);
466
467 onControlChange(i);
468}
469
470void ColorSliders::onControlChange(const Channel i)
471{
472 m_color = getColorFromSliders(i);
473 updateSlidersBgColor();
474
475 // Fire ColorChange() signal
476 ColorSlidersChangeEvent ev(i, m_mode, m_color,
477 m_items[i].relSlider->getValue(), this);
478 ColorChange(ev);
479}
480
481// Updates the entry related to the changed slider widget.
482void ColorSliders::updateEntryText(const Channel i)
483{
484 if (m_lockEntry == i)
485 return;
486
487 Slider* slider = (m_mode == Mode::Absolute ? m_items[i].absSlider:
488 m_items[i].relSlider);
489
490 m_items[i].entry->setTextf("%d", slider->getValue());
491 if (m_items[i].entry->hasFocus())
492 m_items[i].entry->selectAllText();
493}
494
495void ColorSliders::updateSlidersBgColor()
496{
497 for (auto& item : m_items)
498 updateSliderBgColor(item.absSlider, m_color);
499}
500
501void ColorSliders::updateSliderBgColor(Slider* slider, const app::Color& color)
502{
503 auto sliderProperty = std::static_pointer_cast<SkinSliderProperty>(
504 slider->getProperty(SkinSliderProperty::Name));
505
506 static_cast<ColorSliderBgPainter*>(sliderProperty->getBgPainter())->setColor(color);
507
508 slider->invalidate();
509}
510
511void ColorSliders::onSetColor(const app::Color& color)
512{
513 setAbsSliderValue(Channel::Red, color.getRed());
514 setAbsSliderValue(Channel::Green, color.getGreen());
515 setAbsSliderValue(Channel::Blue, color.getBlue());
516 setAbsSliderValue(Channel::HsvHue, int(color.getHsvHue()));
517 setAbsSliderValue(Channel::HsvSaturation, int(color.getHsvSaturation() * 100.0));
518 setAbsSliderValue(Channel::HsvValue, int(color.getHsvValue() * 100.0));
519 setAbsSliderValue(Channel::HslHue, int(color.getHslHue()));
520 setAbsSliderValue(Channel::HslSaturation, int(color.getHslSaturation() * 100.0));
521 setAbsSliderValue(Channel::HslLightness, int(color.getHslLightness() * 100.0));
522 setAbsSliderValue(Channel::Gray, color.getGray());
523 setAbsSliderValue(Channel::Alpha, color.getAlpha());
524}
525
526app::Color ColorSliders::getColorFromSliders(const Channel channel) const
527{
528 // Get the color from sliders.
529 switch (channel) {
530 case Channel::Red:
531 case Channel::Green:
532 case Channel::Blue:
533 return app::Color::fromRgb(
534 getAbsSliderValue(Channel::Red),
535 getAbsSliderValue(Channel::Green),
536 getAbsSliderValue(Channel::Blue),
537 getAbsSliderValue(Channel::Alpha));
538 case Channel::HsvHue:
539 case Channel::HsvSaturation:
540 case Channel::HsvValue:
541 return app::Color::fromHsv(
542 getAbsSliderValue(Channel::HsvHue),
543 getAbsSliderValue(Channel::HsvSaturation) / 100.0,
544 getAbsSliderValue(Channel::HsvValue) / 100.0,
545 getAbsSliderValue(Channel::Alpha));
546 case Channel::HslHue:
547 case Channel::HslSaturation:
548 case Channel::HslLightness:
549 return app::Color::fromHsl(
550 getAbsSliderValue(Channel::HslHue),
551 getAbsSliderValue(Channel::HslSaturation) / 100.0,
552 getAbsSliderValue(Channel::HslLightness) / 100.0,
553 getAbsSliderValue(Channel::Alpha));
554 case Channel::Gray:
555 return app::Color::fromGray(
556 getAbsSliderValue(Channel::Gray),
557 getAbsSliderValue(Channel::Alpha));
558 case Channel::Alpha: {
559 app::Color color = m_color;
560 color.setAlpha(getAbsSliderValue(Channel::Alpha));
561 return color;
562 }
563 }
564 return app::Color::fromMask();
565}
566
567} // namespace app
568