1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/ui/configure_timeline_popup.h"
12 
13 #include "app/app.h"
14 #include "app/commands/commands.h"
15 #include "app/context.h"
16 #include "app/context_access.h"
17 #include "app/doc.h"
18 #include "app/find_widget.h"
19 #include "app/load_widget.h"
20 #include "app/loop_tag.h"
21 #include "app/transaction.h"
22 #include "app/ui/main_window.h"
23 #include "app/ui/timeline/timeline.h"
24 #include "app/ui_context.h"
25 #include "base/bind.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 
37 namespace app {
38 
39 using namespace ui;
40 
ConfigureTimelinePopup()41 ConfigureTimelinePopup::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(base::Bind<void>(&ConfigureTimelinePopup::onChangePosition, this));
57   m_box->firstFrame()->Change.connect(base::Bind<void>(&ConfigureTimelinePopup::onChangeFirstFrame, this));
58   m_box->merge()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onChangeType, this));
59   m_box->tint()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onChangeType, this));
60   m_box->opacity()->Change.connect(base::Bind<void>(&ConfigureTimelinePopup::onOpacity, this));
61   m_box->opacityStep()->Change.connect(base::Bind<void>(&ConfigureTimelinePopup::onOpacityStep, this));
62   m_box->resetOnionskin()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onResetOnionskin, this));
63   m_box->loopTag()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onLoopTagChange, this));
64   m_box->currentLayer()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onCurrentLayerChange, this));
65   m_box->behind()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onPositionChange, this));
66   m_box->infront()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onPositionChange, this));
67 
68   m_box->zoom()->Change.connect(base::Bind<void>(&ConfigureTimelinePopup::onZoomChange, this));
69   m_box->thumbEnabled()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onThumbEnabledChange, this));
70   m_box->thumbOverlayEnabled()->Click.connect(base::Bind<void>(&ConfigureTimelinePopup::onThumbOverlayEnabledChange, this));
71   m_box->thumbOverlaySize()->Change.connect(base::Bind<void>(&ConfigureTimelinePopup::onThumbOverlaySizeChange, this));
72 
73   const bool visibleThumb = docPref().thumbnails.enabled();
74   m_box->thumbHSeparator()->setVisible(visibleThumb);
75   m_box->thumbBox()->setVisible(visibleThumb);
76 }
77 
doc()78 Doc* ConfigureTimelinePopup::doc()
79 {
80   return UIContext::instance()->activeDocument();
81 }
82 
docPref()83 DocumentPreferences& ConfigureTimelinePopup::docPref()
84 {
85   return Preferences::instance().document(doc());
86 }
87 
updateWidgetsFromCurrentSettings()88 void 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   gfx::Rect prevBounds = bounds();
146   setBounds(gfx::Rect(gfx::Point(bounds().x, bounds().y), sizeHint()));
147   manager()->invalidateRect(prevBounds);
148   invalidate();
149 }
150 
onProcessMessage(ui::Message * msg)151 bool ConfigureTimelinePopup::onProcessMessage(ui::Message* msg)
152 {
153   switch (msg->type()) {
154 
155     case kOpenMessage: {
156       updateWidgetsFromCurrentSettings();
157       break;
158     }
159   }
160   return PopupWindow::onProcessMessage(msg);
161 }
162 
onChangePosition()163 void ConfigureTimelinePopup::onChangePosition()
164 {
165   gen::TimelinePosition newTimelinePos =
166     gen::TimelinePosition::BOTTOM;
167 
168   int selITem = m_box->position()->selectedItem();
169   switch (selITem) {
170     case 0: newTimelinePos = gen::TimelinePosition::LEFT; break;
171     case 1: newTimelinePos = gen::TimelinePosition::RIGHT; break;
172     case 2: newTimelinePos = gen::TimelinePosition::BOTTOM; break;
173   }
174   Preferences::instance().general.timelinePosition(newTimelinePos);
175 }
176 
onChangeFirstFrame()177 void ConfigureTimelinePopup::onChangeFirstFrame()
178 {
179   docPref().timeline.firstFrame(
180     m_box->firstFrame()->textInt());
181 }
182 
onChangeType()183 void ConfigureTimelinePopup::onChangeType()
184 {
185   if (m_lockUpdates)
186     return;
187 
188   docPref().onionskin.type(m_box->merge()->isSelected() ?
189     app::gen::OnionskinType::MERGE:
190     app::gen::OnionskinType::RED_BLUE_TINT);
191 }
192 
onOpacity()193 void ConfigureTimelinePopup::onOpacity()
194 {
195   if (m_lockUpdates)
196     return;
197 
198   docPref().onionskin.opacityBase(m_box->opacity()->getValue());
199 }
200 
onOpacityStep()201 void ConfigureTimelinePopup::onOpacityStep()
202 {
203   if (m_lockUpdates)
204     return;
205 
206   docPref().onionskin.opacityStep(m_box->opacityStep()->getValue());
207 }
208 
onResetOnionskin()209 void ConfigureTimelinePopup::onResetOnionskin()
210 {
211   DocumentPreferences& docPref = this->docPref();
212 
213   docPref.onionskin.type(docPref.onionskin.type.defaultValue());
214   docPref.onionskin.opacityBase(docPref.onionskin.opacityBase.defaultValue());
215   docPref.onionskin.opacityStep(docPref.onionskin.opacityStep.defaultValue());
216   docPref.onionskin.loopTag(docPref.onionskin.loopTag.defaultValue());
217   docPref.onionskin.currentLayer(docPref.onionskin.currentLayer.defaultValue());
218   docPref.onionskin.position(docPref.onionskin.position.defaultValue());
219 
220   updateWidgetsFromCurrentSettings();
221 }
222 
onLoopTagChange()223 void ConfigureTimelinePopup::onLoopTagChange()
224 {
225   docPref().onionskin.loopTag(m_box->loopTag()->isSelected());
226 }
227 
onCurrentLayerChange()228 void ConfigureTimelinePopup::onCurrentLayerChange()
229 {
230   docPref().onionskin.currentLayer(m_box->currentLayer()->isSelected());
231 }
232 
onPositionChange()233 void ConfigureTimelinePopup::onPositionChange()
234 {
235   docPref().onionskin.position(m_box->behind()->isSelected() ?
236                                render::OnionskinPosition::BEHIND:
237                                render::OnionskinPosition::INFRONT);
238 }
239 
onZoomChange()240 void ConfigureTimelinePopup::onZoomChange()
241 {
242   docPref().thumbnails.zoom(m_box->zoom()->getValue());
243 }
244 
onThumbEnabledChange()245 void ConfigureTimelinePopup::onThumbEnabledChange()
246 {
247   docPref().thumbnails.enabled(m_box->thumbEnabled()->isSelected());
248   updateWidgetsFromCurrentSettings();
249 }
250 
onThumbOverlayEnabledChange()251 void ConfigureTimelinePopup::onThumbOverlayEnabledChange()
252 {
253   docPref().thumbnails.overlayEnabled(m_box->thumbOverlayEnabled()->isSelected());
254 }
255 
onThumbOverlaySizeChange()256 void ConfigureTimelinePopup::onThumbOverlaySizeChange()
257 {
258   docPref().thumbnails.overlaySize(m_box->thumbOverlaySize()->getValue());
259 }
260 
261 } // namespace app
262