1// Aseprite
2// Copyright (C) 2020-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/ui/resources_listbox.h"
13
14#include "app/res/resource.h"
15#include "app/res/resources_loader.h"
16#include "app/ui/skin/skin_theme.h"
17#include "ui/graphics.h"
18#include "ui/message.h"
19#include "ui/paint_event.h"
20#include "ui/size_hint_event.h"
21#include "ui/view.h"
22
23namespace app {
24
25using namespace ui;
26using namespace skin;
27
28//////////////////////////////////////////////////////////////////////
29// ResourceListItem
30
31ResourceListItem::ResourceListItem(Resource* resource)
32 : ListItem(resource->id()), m_resource(resource)
33{
34}
35
36bool ResourceListItem::onProcessMessage(ui::Message* msg)
37{
38 switch (msg->type()) {
39 case kMouseLeaveMessage:
40 case kMouseEnterMessage:
41 invalidate();
42 break;
43 }
44 return ListItem::onProcessMessage(msg);
45}
46
47void ResourceListItem::onPaint(PaintEvent& ev)
48{
49 auto theme = SkinTheme::get(this);
50 Graphics* g = ev.graphics();
51 gfx::Rect bounds = clientBounds();
52 gfx::Color bgcolor, fgcolor;
53
54 if (isSelected()) {
55 bgcolor = theme->colors.listitemSelectedFace();
56 fgcolor = theme->colors.listitemSelectedText();
57 }
58 else {
59 bgcolor = theme->colors.listitemNormalFace();
60 fgcolor = theme->colors.listitemNormalText();
61 }
62
63 g->fillRect(bgcolor, bounds);
64
65 static_cast<ResourcesListBox*>(parent())->
66 paintResource(g, bounds, m_resource.get());
67
68 g->drawText(text(), fgcolor, gfx::ColorNone,
69 gfx::Point(
70 bounds.x + 2*guiscale(),
71 bounds.y + bounds.h/2 - g->measureUIText(text()).h/2));
72}
73
74void ResourceListItem::onSizeHint(SizeHintEvent& ev)
75{
76 ev.setSizeHint(
77 static_cast<ResourcesListBox*>(parent())->
78 resourceSizeHint(m_resource.get()));
79}
80
81//////////////////////////////////////////////////////////////////////
82// ResourcesListBox::LoadingItem
83
84class ResourcesListBox::LoadingItem : public ListItem {
85public:
86 LoadingItem()
87 : ListItem("Loading")
88 , m_state(0) {
89 }
90
91 void makeProgress() {
92 std::string text = "Loading ";
93
94 switch ((++m_state) % 4) {
95 case 0: text += "/"; break;
96 case 1: text += "-"; break;
97 case 2: text += "\\"; break;
98 case 3: text += "|"; break;
99 }
100
101 setText(text);
102 }
103
104private:
105 int m_state;
106};
107
108//////////////////////////////////////////////////////////////////////
109// ResourcesListBox
110
111ResourcesListBox::ResourcesListBox(ResourcesLoader* resourcesLoader)
112 : m_resourcesLoader(resourcesLoader)
113 , m_resourcesTimer(100)
114 , m_reload(false)
115 , m_loadingItem(nullptr)
116{
117 m_resourcesTimer.Tick.connect([this]{ onTick(); });
118}
119
120Resource* ResourcesListBox::selectedResource()
121{
122 if (ResourceListItem* listItem = dynamic_cast<ResourceListItem*>(getSelectedChild()))
123 return listItem->resource();
124 else
125 return nullptr;
126}
127
128void ResourcesListBox::reload()
129{
130 auto children = this->children(); // Create a copy because we'll
131 // modify the list in the for()
132
133 // Delete all ResourcesListItem. (PalettesListBox contains a tooltip
134 // manager too, so we cannot remove just all children.)
135 for (auto child : children) {
136 if (dynamic_cast<ResourceListItem*>(child))
137 delete child;
138 }
139
140 m_reload = true;
141}
142
143void ResourcesListBox::paintResource(Graphics* g, gfx::Rect& bounds, Resource* resource)
144{
145 onPaintResource(g, bounds, resource);
146}
147
148gfx::Size ResourcesListBox::resourceSizeHint(Resource* resource)
149{
150 gfx::Size pref(0, 0);
151 onResourceSizeHint(resource, pref);
152 return pref;
153}
154
155bool ResourcesListBox::onProcessMessage(ui::Message* msg)
156{
157 switch (msg->type()) {
158
159 case kOpenMessage: {
160 if (m_reload) {
161 m_reload = false;
162 m_resourcesLoader->reload();
163 }
164
165 m_resourcesTimer.start();
166 break;
167 }
168
169 }
170 return ListBox::onProcessMessage(msg);
171}
172
173void ResourcesListBox::onChange()
174{
175 Resource* resource = selectedResource();
176 if (resource)
177 onResourceChange(resource);
178}
179
180ResourceListItem* ResourcesListBox::onCreateResourceItem(Resource* resource)
181{
182 return new ResourceListItem(resource);
183}
184
185void ResourcesListBox::onTick()
186{
187 if (m_resourcesLoader == nullptr) {
188 stop();
189 return;
190 }
191
192 if (!m_loadingItem) {
193 m_loadingItem = new LoadingItem;
194 addChild(m_loadingItem);
195 }
196 m_loadingItem->makeProgress();
197
198 std::unique_ptr<Resource> resource;
199 std::string name;
200
201 while (m_resourcesLoader->next(resource)) {
202 std::unique_ptr<ResourceListItem> listItem(onCreateResourceItem(resource.get()));
203 insertChild(getItemsCount()-1, listItem.get());
204 sortItems();
205 layout();
206
207 if (View* view = View::getView(this))
208 view->updateView();
209
210 resource.release();
211 listItem.release();
212 }
213
214 if (m_resourcesLoader->isDone()) {
215 FinishLoading();
216 stop();
217 }
218}
219
220void ResourcesListBox::stop()
221{
222 m_resourcesTimer.stop();
223
224 if (m_loadingItem) {
225 removeChild(m_loadingItem);
226 layout();
227
228 delete m_loadingItem;
229 m_loadingItem = nullptr;
230
231 if (View* view = View::getView(this))
232 view->updateView();
233 }
234}
235
236} // namespace app
237