1// Aseprite
2// Copyright (C) 2020-2022 Igara Studio S.A.
3// Copyright (C) 2001-2017 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/ui/palettes_listbox.h"
13
14#include "app/app.h"
15#include "app/doc.h"
16#include "app/extensions.h"
17#include "app/modules/palettes.h"
18#include "app/res/palette_resource.h"
19#include "app/res/palettes_loader_delegate.h"
20#include "app/ui/doc_view.h"
21#include "app/ui/editor/editor.h"
22#include "app/ui/icon_button.h"
23#include "app/ui/skin/skin_theme.h"
24#include "app/ui_context.h"
25#include "base/launcher.h"
26#include "doc/palette.h"
27#include "doc/sprite.h"
28#include "os/surface.h"
29#include "ui/graphics.h"
30#include "ui/listitem.h"
31#include "ui/message.h"
32#include "ui/paint_event.h"
33#include "ui/resize_event.h"
34#include "ui/size_hint_event.h"
35#include "ui/tooltips.h"
36#include "ui/view.h"
37
38namespace app {
39
40using namespace ui;
41using namespace app::skin;
42
43static bool is_url_char(int chr)
44{
45 return ((chr >= 'a' && chr <= 'z') ||
46 (chr >= 'A' && chr <= 'Z') ||
47 (chr >= '0' && chr <= '9') ||
48 (chr == ':' || chr == '/' || chr == '@' ||
49 chr == '?' || chr == '!' || chr == '#' ||
50 chr == '-' || chr == '_' || chr == '~' ||
51 chr == '.' || chr == ',' || chr == ';' ||
52 chr == '*' || chr == '+' || chr == '=' ||
53 chr == '[' || chr == ']' ||
54 chr == '(' || chr == ')' ||
55 chr == '$' || chr == '\''));
56}
57
58class PalettesListItem : public ResourceListItem {
59
60 class CommentButton : public IconButton {
61 public:
62 CommentButton(const std::string& comment)
63 : IconButton(SkinTheme::instance()->parts.iconUserData())
64 , m_comment(comment) {
65 setFocusStop(false);
66 initTheme();
67 }
68
69 private:
70 void onInitTheme(InitThemeEvent& ev) override {
71 IconButton::onInitTheme(ev);
72
73 auto theme = SkinTheme::get(this);
74 setBgColor(theme->colors.listitemNormalFace());
75 }
76
77 void onClick(Event& ev) override {
78 IconButton::onClick(ev);
79
80 std::string::size_type j, i = m_comment.find("http");
81 if (i != std::string::npos) {
82 for (j=i+4; j != m_comment.size() && is_url_char(m_comment[j]); ++j)
83 ;
84 base::launcher::open_url(m_comment.substr(i, j-i));
85 }
86 }
87
88 std::string m_comment;
89 };
90
91public:
92 PalettesListItem(Resource* resource, TooltipManager* tooltips)
93 : ResourceListItem(resource)
94 , m_comment(nullptr)
95 {
96 std::string comment = static_cast<PaletteResource*>(resource)->palette()->comment();
97 if (!comment.empty()) {
98 addChild(m_comment = new CommentButton(comment));
99
100 tooltips->addTooltipFor(m_comment, comment, LEFT);
101 }
102 }
103
104private:
105 void onResize(ResizeEvent& ev) override {
106 ResourceListItem::onResize(ev);
107
108 if (m_comment) {
109 auto reqSz = m_comment->sizeHint();
110 m_comment->setBounds(
111 gfx::Rect(ev.bounds().x+ev.bounds().w-reqSz.w,
112 ev.bounds().y+ev.bounds().h/2-reqSz.h/2,
113 reqSz.w, reqSz.h));
114 }
115 }
116
117 CommentButton* m_comment;
118};
119
120PalettesListBox::PalettesListBox()
121 : ResourcesListBox(
122 new ResourcesLoader(
123 std::make_unique<PalettesLoaderDelegate>()))
124{
125 addChild(&m_tooltips);
126
127 m_extPaletteChanges =
128 App::instance()->extensions().PalettesChange.connect(
129 [this]{ reload(); });
130 m_extPresetsChanges =
131 App::instance()->PalettePresetsChange.connect(
132 [this]{ reload(); });
133}
134
135const doc::Palette* PalettesListBox::selectedPalette()
136{
137 Resource* resource = selectedResource();
138 if (!resource)
139 return NULL;
140
141 return static_cast<PaletteResource*>(resource)->palette();
142}
143
144ResourceListItem* PalettesListBox::onCreateResourceItem(Resource* resource)
145{
146 return new PalettesListItem(resource, &m_tooltips);
147}
148
149void PalettesListBox::onResourceChange(Resource* resource)
150{
151 const doc::Palette* palette = static_cast<PaletteResource*>(resource)->palette();
152 PalChange(palette);
153}
154
155void PalettesListBox::onPaintResource(Graphics* g, gfx::Rect& bounds, Resource* resource)
156{
157 auto theme = SkinTheme::get(this);
158 const doc::Palette* palette = static_cast<PaletteResource*>(resource)->palette();
159 os::Surface* tick = theme->parts.checkSelected()->bitmap(0);
160
161 // Draw tick (to say "this palette matches the active sprite
162 // palette").
163 auto view = UIContext::instance()->activeView();
164 if (view && view->document()) {
165 auto docPal = view->document()->sprite()->palette(view->editor()->frame());
166 if (docPal && *docPal == *palette)
167 g->drawRgbaSurface(tick, bounds.x, bounds.y+bounds.h/2-tick->height()/2);
168 }
169
170 bounds.x += tick->width();
171 bounds.w -= tick->width();
172
173 gfx::Rect box(
174 bounds.x, bounds.y+bounds.h-6*guiscale(),
175 4*guiscale(), 4*guiscale());
176
177 for (int i=0; i<palette->size(); ++i) {
178 doc::color_t c = palette->getEntry(i);
179
180 g->fillRect(gfx::rgba(
181 doc::rgba_getr(c),
182 doc::rgba_getg(c),
183 doc::rgba_getb(c)), box);
184
185 box.x += box.w;
186 }
187}
188
189void PalettesListBox::onResourceSizeHint(Resource* resource, gfx::Size& size)
190{
191 size = gfx::Size(0, (2+16+2)*guiscale());
192}
193
194} // namespace app
195