1// Aseprite
2// Copyright (C) 2020-2021 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/configure_timeline_popup.h"
13
14#include "app/app.h"
15#include "app/commands/commands.h"
16#include "app/context.h"
17#include "app/context_access.h"
18#include "app/doc.h"
19#include "app/find_widget.h"
20#include "app/load_widget.h"
21#include "app/loop_tag.h"
22#include "app/transaction.h"
23#include "app/ui/main_window.h"
24#include "app/ui/timeline/timeline.h"
25#include "app/ui_context.h"
26#include "base/scoped_value.h"
27#include "ui/box.h"
28#include "ui/button.h"
29#include "ui/manager.h"
30#include "ui/message.h"
31#include "ui/scale.h"
32#include "ui/slider.h"
33#include "ui/theme.h"
34
35#include "timeline_conf.xml.h"
36
37namespace app {
38
39using namespace ui;
40
41ConfigureTimelinePopup::ConfigureTimelinePopup()
42 : PopupWindow("Timeline Settings", ClickBehavior::CloseOnClickInOtherWindow)
43 , m_lockUpdates(false)
44{
45 // TODO we should add a new hot region to automatically close the
46 // popup if the mouse is moved outside or find other kind of
47 // dialog/window
48 setHotRegion(gfx::Region(manager()->bounds())); // for the color selector
49
50 setAutoRemap(false);
51 setBorder(gfx::Border(4*guiscale()));
52
53 m_box = new app::gen::TimelineConf();
54 addChild(m_box);
55
56 m_box->position()->ItemChange.connect([this]{ onChangePosition(); });
57 m_box->firstFrame()->Change.connect([this]{ onChangeFirstFrame(); });
58 m_box->merge()->Click.connect([this]{ onChangeType(); });
59 m_box->tint()->Click.connect([this]{ onChangeType(); });
60 m_box->opacity()->Change.connect([this]{ onOpacity(); });
61 m_box->opacityStep()->Change.connect([this]{ onOpacityStep(); });
62 m_box->resetOnionskin()->Click.connect([this]{ onResetOnionskin(); });
63 m_box->loopTag()->Click.connect([this]{ onLoopTagChange(); });
64 m_box->currentLayer()->Click.connect([this]{ onCurrentLayerChange(); });
65 m_box->behind()->Click.connect([this]{ onPositionChange(); });
66 m_box->infront()->Click.connect([this]{ onPositionChange(); });
67
68 m_box->zoom()->Change.connect([this]{ onZoomChange(); });
69 m_box->thumbEnabled()->Click.connect([this]{ onThumbEnabledChange(); });
70 m_box->thumbOverlayEnabled()->Click.connect([this]{ onThumbOverlayEnabledChange(); });
71 m_box->thumbOverlaySize()->Change.connect([this]{ onThumbOverlaySizeChange(); });
72
73 const bool visibleThumb = docPref().thumbnails.enabled();
74 m_box->thumbHSeparator()->setVisible(visibleThumb);
75 m_box->thumbBox()->setVisible(visibleThumb);
76}
77
78Doc* ConfigureTimelinePopup::doc()
79{
80 return UIContext::instance()->activeDocument();
81}
82
83DocumentPreferences& ConfigureTimelinePopup::docPref()
84{
85 return Preferences::instance().document(doc());
86}
87
88void ConfigureTimelinePopup::updateWidgetsFromCurrentSettings()
89{
90 DocumentPreferences& docPref = this->docPref();
91 base::ScopedValue<bool> lockUpdates(m_lockUpdates, true, false);
92
93 auto position = Preferences::instance().general.timelinePosition();
94 int selItem = 2;
95 switch (position) {
96 case gen::TimelinePosition::LEFT: selItem = 0; break;
97 case gen::TimelinePosition::RIGHT: selItem = 1; break;
98 case gen::TimelinePosition::BOTTOM: selItem = 2; break;
99 }
100 m_box->position()->setSelectedItem(selItem, false);
101
102 m_box->firstFrame()->setTextf(
103 "%d", docPref.timeline.firstFrame());
104
105 switch (docPref.onionskin.type()) {
106 case app::gen::OnionskinType::MERGE:
107 m_box->merge()->setSelected(true);
108 break;
109 case app::gen::OnionskinType::RED_BLUE_TINT:
110 m_box->tint()->setSelected(true);
111 break;
112 }
113 m_box->opacity()->setValue(docPref.onionskin.opacityBase());
114 m_box->opacityStep()->setValue(docPref.onionskin.opacityStep());
115 m_box->loopTag()->setSelected(docPref.onionskin.loopTag());
116 m_box->currentLayer()->setSelected(docPref.onionskin.currentLayer());
117
118 switch (docPref.onionskin.type()) {
119 case app::gen::OnionskinType::MERGE:
120 m_box->merge()->setSelected(true);
121 break;
122 case app::gen::OnionskinType::RED_BLUE_TINT:
123 m_box->tint()->setSelected(true);
124 break;
125 }
126
127 switch (docPref.onionskin.position()) {
128 case render::OnionskinPosition::BEHIND:
129 m_box->behind()->setSelected(true);
130 break;
131 case render::OnionskinPosition::INFRONT:
132 m_box->infront()->setSelected(true);
133 break;
134 }
135
136 const bool visibleThumb = docPref.thumbnails.enabled();
137
138 m_box->zoom()->setValue(int(docPref.thumbnails.zoom())); // TODO add a slider for floating points
139 m_box->thumbEnabled()->setSelected(visibleThumb);
140 m_box->thumbHSeparator()->setVisible(visibleThumb);
141 m_box->thumbBox()->setVisible(visibleThumb);
142 m_box->thumbOverlayEnabled()->setSelected(docPref.thumbnails.overlayEnabled());
143 m_box->thumbOverlaySize()->setValue(docPref.thumbnails.overlaySize());
144
145 expandWindow(sizeHint());
146}
147
148bool ConfigureTimelinePopup::onProcessMessage(ui::Message* msg)
149{
150 switch (msg->type()) {
151
152 case kOpenMessage: {
153 updateWidgetsFromCurrentSettings();
154 break;
155 }
156 }
157 return PopupWindow::onProcessMessage(msg);
158}
159
160void ConfigureTimelinePopup::onChangePosition()
161{
162 gen::TimelinePosition newTimelinePos =
163 gen::TimelinePosition::BOTTOM;
164
165 int selITem = m_box->position()->selectedItem();
166 switch (selITem) {
167 case 0: newTimelinePos = gen::TimelinePosition::LEFT; break;
168 case 1: newTimelinePos = gen::TimelinePosition::RIGHT; break;
169 case 2: newTimelinePos = gen::TimelinePosition::BOTTOM; break;
170 }
171 Preferences::instance().general.timelinePosition(newTimelinePos);
172}
173
174void ConfigureTimelinePopup::onChangeFirstFrame()
175{
176 docPref().timeline.firstFrame(
177 m_box->firstFrame()->textInt());
178}
179
180void ConfigureTimelinePopup::onChangeType()
181{
182 if (m_lockUpdates)
183 return;
184
185 docPref().onionskin.type(m_box->merge()->isSelected() ?
186 app::gen::OnionskinType::MERGE:
187 app::gen::OnionskinType::RED_BLUE_TINT);
188}
189
190void ConfigureTimelinePopup::onOpacity()
191{
192 if (m_lockUpdates)
193 return;
194
195 docPref().onionskin.opacityBase(m_box->opacity()->getValue());
196}
197
198void ConfigureTimelinePopup::onOpacityStep()
199{
200 if (m_lockUpdates)
201 return;
202
203 docPref().onionskin.opacityStep(m_box->opacityStep()->getValue());
204}
205
206void ConfigureTimelinePopup::onResetOnionskin()
207{
208 DocumentPreferences& docPref = this->docPref();
209
210 docPref.onionskin.type(docPref.onionskin.type.defaultValue());
211 docPref.onionskin.opacityBase(docPref.onionskin.opacityBase.defaultValue());
212 docPref.onionskin.opacityStep(docPref.onionskin.opacityStep.defaultValue());
213 docPref.onionskin.loopTag(docPref.onionskin.loopTag.defaultValue());
214 docPref.onionskin.currentLayer(docPref.onionskin.currentLayer.defaultValue());
215 docPref.onionskin.position(docPref.onionskin.position.defaultValue());
216
217 updateWidgetsFromCurrentSettings();
218}
219
220void ConfigureTimelinePopup::onLoopTagChange()
221{
222 docPref().onionskin.loopTag(m_box->loopTag()->isSelected());
223}
224
225void ConfigureTimelinePopup::onCurrentLayerChange()
226{
227 docPref().onionskin.currentLayer(m_box->currentLayer()->isSelected());
228}
229
230void ConfigureTimelinePopup::onPositionChange()
231{
232 docPref().onionskin.position(m_box->behind()->isSelected() ?
233 render::OnionskinPosition::BEHIND:
234 render::OnionskinPosition::INFRONT);
235}
236
237void ConfigureTimelinePopup::onZoomChange()
238{
239 docPref().thumbnails.zoom(m_box->zoom()->getValue());
240}
241
242void ConfigureTimelinePopup::onThumbEnabledChange()
243{
244 docPref().thumbnails.enabled(m_box->thumbEnabled()->isSelected());
245 updateWidgetsFromCurrentSettings();
246}
247
248void ConfigureTimelinePopup::onThumbOverlayEnabledChange()
249{
250 docPref().thumbnails.overlayEnabled(m_box->thumbOverlayEnabled()->isSelected());
251}
252
253void ConfigureTimelinePopup::onThumbOverlaySizeChange()
254{
255 docPref().thumbnails.overlaySize(m_box->thumbOverlaySize()->getValue());
256}
257
258} // namespace app
259