1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2016-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifndef APP_UI_COLOR_SELECTOR_H_INCLUDED
9#define APP_UI_COLOR_SELECTOR_H_INCLUDED
10#pragma once
11
12#include "app/color.h"
13#include "app/ui/color_source.h"
14#include "obs/connection.h"
15#include "obs/signal.h"
16#include "os/surface.h"
17#include "ui/mouse_button.h"
18#include "ui/timer.h"
19#include "ui/widget.h"
20
21#include <atomic>
22#include <cmath>
23
24// TODO We should wrap the SkRuntimeEffect in laf-os, SkRuntimeEffect
25// and SkRuntimeShaderBuilder might change in future Skia
26// versions.
27#if SK_ENABLE_SKSL
28 #include "include/effects/SkRuntimeEffect.h"
29#endif
30
31// TODO move this to laf::base
32inline bool cs_double_diff(double a, double b) {
33 return std::fabs((a)-(b)) > 0.001;
34}
35
36namespace app {
37
38 class ColorSelector : public ui::Widget
39 , public IColorSource {
40 public:
41 class Painter;
42
43 ColorSelector();
44 ~ColorSelector();
45
46 void selectColor(const app::Color& color);
47
48 // IColorSource impl
49 app::Color getColorByPosition(const gfx::Point& pos) override;
50
51 // Signals
52 obs::signal<void(const app::Color&, ui::MouseButton)> ColorChange;
53
54 protected:
55 // paintFlags for onPaintSurfaceInBgThread and return value of
56 // onNeedsSurfaceRepaint().
57 enum {
58 MainAreaFlag = 1,
59 BottomBarFlag = 2,
60 AlphaBarFlag = 4,
61 AllAreasFlag = MainAreaFlag | BottomBarFlag | AlphaBarFlag,
62 DoneFlag = 8,
63 };
64
65 void onSizeHint(ui::SizeHintEvent& ev) override;
66 bool onProcessMessage(ui::Message* msg) override;
67 void onInitTheme(ui::InitThemeEvent& ev) override;
68 void onResize(ui::ResizeEvent& ev) override;
69 void onPaint(ui::PaintEvent& ev) override;
70
71 virtual const char* getMainAreaShader() { return nullptr; }
72 virtual const char* getBottomBarShader() { return nullptr; }
73#if SK_ENABLE_SKSL
74 virtual void setShaderParams(SkRuntimeShaderBuilder& builder, bool main) { }
75#endif
76 virtual app::Color getMainAreaColor(const int u, const int umax,
77 const int v, const int vmax) = 0;
78 virtual app::Color getBottomBarColor(const int u, const int umax) = 0;
79 virtual void onPaintMainArea(ui::Graphics* g, const gfx::Rect& rc) = 0;
80 virtual void onPaintBottomBar(ui::Graphics* g, const gfx::Rect& rc) = 0;
81 virtual void onPaintSurfaceInBgThread(os::Surface* s,
82 const gfx::Rect& main,
83 const gfx::Rect& bottom,
84 const gfx::Rect& alpha,
85 bool& stop);
86 virtual int onNeedsSurfaceRepaint(const app::Color& newColor);
87 virtual bool subColorPicked() { return false; }
88
89 void paintColorIndicator(ui::Graphics* g,
90 const gfx::Point& pos,
91 const bool white);
92
93 // Returns the 255 if m_color is the mask color, or the
94 // m_color.getAlpha() if it's really a color.
95 int getCurrentAlphaForNewColor() const;
96
97 bool hasCaptureInMainArea() const { return m_capturedInMain; }
98
99 app::Color m_color;
100
101 // These flags indicate which areas must be redrawed in the
102 // background thread. Equal to DoneFlag when the surface is
103 // already painted in the background thread surface. This must be
104 // atomic because we need atomic bitwise operations.
105 std::atomic<int> m_paintFlags;
106
107 protected:
108#if SK_ENABLE_SKSL
109 void resetBottomEffect();
110#endif
111
112 private:
113 app::Color getAlphaBarColor(const int u, const int umax);
114 void onPaintAlphaBar(ui::Graphics* g, const gfx::Rect& rc);
115
116 gfx::Rect bottomBarBounds() const;
117 gfx::Rect alphaBarBounds() const;
118
119 void updateColorSpace();
120
121#if SK_ENABLE_SKSL
122 static const char* getAlphaBarShader();
123 bool buildEffects();
124 sk_sp<SkRuntimeEffect> buildEffect(const char* code);
125#endif
126
127 // Internal flag used to lock the modification of m_color.
128 // E.g. When the user picks a color harmony, we don't want to
129 // change the main color.
130 bool m_lockColor;
131
132 // True when the user pressed the mouse button in the bottom
133 // slider. It's used to avoid swapping in both areas (main color
134 // area vs bottom slider) when we drag the mouse above this
135 // widget.
136 bool m_capturedInBottom = false;
137 bool m_capturedInAlpha = false;
138 bool m_capturedInMain = false;
139
140 ui::Timer m_timer;
141
142 obs::scoped_connection m_appConn;
143
144#if SK_ENABLE_SKSL
145 // Shaders
146 sk_sp<SkRuntimeEffect> m_mainEffect;
147 sk_sp<SkRuntimeEffect> m_bottomEffect;
148 static sk_sp<SkRuntimeEffect> m_alphaEffect;
149#endif
150 };
151
152} // namespace app
153
154#endif
155