7 #include <gtkmm/application.h>
8 #include <gtkmm/builder.h>
9 #include <gtkmm/window.h>
10 #include <gtkmm/comboboxtext.h>
11 #include <gtkmm/button.h>
12 #include <gtkmm/scale.h>
13 #include <gtkmm/adjustment.h>
14 #include <gtkmm/checkbutton.h>
15 #include <gtkmm/expander.h>
17 #include "mouse_cursor_tracker.h"
19 extern std
::vector
<std
::string
> live2dParams
;
20 extern std
::vector
<std
::pair
<std
::string
, int> > MCT_motions
;
21 extern std
::vector
<std
::string
> MCT_expressions
;
22 extern std
::map
<std
::string
, double> *MCT_overrideMap
;
24 #define GET_WIDGET_ASSERT(id, widgetPtr) do { \
25 m_builder->get_widget(id, widgetPtr); \
26 if (!widgetPtr) abort(); \
29 void MouseCursorTracker
::onMotionStartButton(void)
31 std
::unique_lock
<std
::mutex
> lock(m_motionMutex
, std
::defer_lock
);
34 Gtk
::ComboBoxText
*cbt
;
35 GET_WIDGET_ASSERT("comboBoxMotions", cbt
);
36 std
::string motionStr
= cbt
->get_active_text();
37 if (!motionStr
.empty())
39 std
::stringstream
ss(motionStr
);
40 ss
>> m_motionGroup
>> m_motionNumber
;
43 GET_WIDGET_ASSERT("comboBoxMotionPriority", cbt
);
44 std
::string priority
= cbt
->get_active_id();
45 if (priority
== "priorityForce")
47 m_motionPriority
= MotionPriority
::force
;
49 else if (priority
== "priorityNormal")
51 m_motionPriority
= MotionPriority
::normal
;
53 else if (priority
== "priorityIdle")
55 m_motionPriority
= MotionPriority
::idle
;
59 m_motionPriority
= MotionPriority
::none
;
65 void MouseCursorTracker
::onExpressionStartButton(void)
67 std
::unique_lock
<std
::mutex
> lock(m_motionMutex
, std
::defer_lock
);
70 Gtk
::ComboBoxText
*cbt
;
71 GET_WIDGET_ASSERT("comboBoxExpressions", cbt
);
72 std
::string expStr
= cbt
->get_active_text();
75 m_expression
= expStr
;
81 void MouseCursorTracker
::onParamUpdateButton(Gtk
::Scale
*scale
, bool isInc
)
83 Glib
::RefPtr
<Gtk
::Adjustment
> adj
= scale
->get_adjustment();
84 double value
= adj
->get_value();
85 double increment
= adj
->get_step_increment();
86 adj
->set_value(value
+ increment
* (isInc ?
1 : -1));
89 void MouseCursorTracker
::onParamValChanged(Glib
::RefPtr
<Gtk
::Adjustment
> adj
, std
::string paramName
)
91 bool isResetting
= std
::find(
92 m_onClearResettingParams
.begin(),
93 m_onClearResettingParams
.end(),
94 paramName
) != m_onClearResettingParams
.end();
98 // Value changed event caused by onClearButton,
99 // don't change underlying value
103 double value
= adj
->get_value();
104 m_overrideMap
["Param" + paramName
] = value
;
105 /* Use get_object instead of get_widget to avoid
106 * "widget not found" error messages */
107 auto obj
= m_builder
->get_object("button" + paramName
+ "Clear");
110 auto button
= Glib
::RefPtr
<Gtk
::Button
>::cast_dynamic(obj
);
111 button
->set_visible(true);
115 void MouseCursorTracker
::onClearButton(Glib
::RefPtr
<Gtk
::Button
> button
, std
::string paramName
, Glib
::RefPtr
<Gtk
::Adjustment
> adj
)
117 m_overrideMap
.erase("Param" + paramName
);
119 // Reset the GtkScale display value to 0, without running the callback.
120 m_onClearResettingParams
.push_back(paramName
);
124 // Special case for MouthForm: use value from config file
125 if (paramName
== "MouthForm")
127 value
= m_cfg
.mouthForm
;
129 adj
->set_value(value
);
131 m_onClearResettingParams
.erase(
133 m_onClearResettingParams
.begin(),
134 m_onClearResettingParams
.end(),
136 m_onClearResettingParams
.end()
139 button
->set_visible(false);
142 void MouseCursorTracker
::onAutoToggle(Gtk
::CheckButton
*check
, Gtk
::Scale
*scale
,
143 Gtk
::Button
*buttonDec
, Gtk
::Button
*buttonInc
,
144 std
::string paramName
)
146 bool active
= check
->get_active();
147 scale
->set_sensitive(!active
);
148 buttonDec
->set_sensitive(!active
);
149 buttonInc
->set_sensitive(!active
);
153 m_overrideMap
.erase(paramName
);
157 auto adj
= scale
->get_adjustment();
158 double value
= adj
->get_value();
159 m_overrideMap
[paramName
] = value
;
162 // Special cases for lip sync and auto breath flags
163 if (paramName
== "ParamMouthOpenY")
165 m_cfg
.useLipSync
= active
;
167 else if (paramName
== "ParamBreath")
169 m_cfg
.autoBreath
= active
;
173 void MouseCursorTracker
::onExpanderChange(Gtk
::Window
*window
)
175 // Shrink window if enlarged by GtkExpander
176 window
->resize(1, 1);
179 void MouseCursorTracker
::guiLoop(void)
181 m_builder
= Gtk
::Builder
::create_from_file("gui.glade");
183 // Add motions list to combobox
184 Gtk
::ComboBoxText
*motionsCbt
;
185 GET_WIDGET_ASSERT("comboBoxMotions", motionsCbt
);
186 for (auto it
= MCT_motions
.begin(); it
!= MCT_motions
.end(); ++it
)
188 for (int i
= 0; i
< it
->second
; i
++)
190 std
::stringstream ss
;
191 ss
<< it
->first
<< " " << i
;
192 motionsCbt
->append(ss
.str());
196 // Add expressions list to combobox
197 Gtk
::ComboBoxText
*expsCbt
;
198 GET_WIDGET_ASSERT("comboBoxExpressions", expsCbt
);
199 for (auto it
= MCT_expressions
.begin(); it
!= MCT_expressions
.end(); ++it
)
201 expsCbt
->append(*it
);
205 // Add scale tick marks
208 std
::vector
<std
::string
> ticksAtZero
=
210 "scaleAngleX", "scaleAngleY", "scaleAngleZ",
211 "scaleEyeBallX", "scaleEyeBallY", "scaleEyeBallForm",
213 "scaleBrowLX", "scaleBrowLY", "scaleBrowLAngle",
215 "scaleBrowRX", "scaleBrowRY", "scaleBrowRAngle",
217 "scaleHairFront", "scaleHairSide", "scaleHairBack",
218 "scaleBodyAngleX", "scaleBodyAngleY", "scaleBodyAngleZ",
219 "scaleBustX", "scaleBustY", "scaleBaseX", "scaleBaseY",
220 "scaleArmLA", "scaleArmLB", "scaleArmRA", "scaleArmRB",
221 "scaleHandL", "scaleHandR"
224 for (auto it
= ticksAtZero
.begin(); it
!= ticksAtZero
.end(); ++it
)
226 GET_WIDGET_ASSERT(*it
, scale
);
227 scale
->add_mark(0, Gtk
::PositionType
::POS_BOTTOM
, "");
230 GET_WIDGET_ASSERT("scaleEyeLOpen", scale
);
231 scale
->add_mark(0, Gtk
::PositionType
::POS_BOTTOM
, "");
232 scale
->add_mark(1, Gtk
::PositionType
::POS_BOTTOM
, "");
233 GET_WIDGET_ASSERT("scaleEyeROpen", scale
);
234 scale
->add_mark(0, Gtk
::PositionType
::POS_BOTTOM
, "");
235 scale
->add_mark(1, Gtk
::PositionType
::POS_BOTTOM
, "");
237 GET_WIDGET_ASSERT("scaleMouthOpenY", scale
);
238 scale
->add_mark(1, Gtk
::PositionType
::POS_BOTTOM
, "");
240 // Bind button handlers
242 GET_WIDGET_ASSERT("buttonStartMotion", button
);
243 button
->signal_clicked().connect(sigc
::mem_fun(*this, &MouseCursorTracker
::onMotionStartButton
));
245 GET_WIDGET_ASSERT("buttonStartExpression", button
);
246 button
->signal_clicked().connect(sigc
::mem_fun(*this, &MouseCursorTracker
::onExpressionStartButton
));
248 // Bind button handlers for increment / decrement buttons
249 for (auto it
= live2dParams
.begin(); it
!= live2dParams
.end(); ++it
)
251 std
::string paramName
= *it
; // e.g. ParamAngleX
252 paramName
.erase(0, 5); // e.g. AngleX
253 std
::string buttonDecId
= "button" + paramName
+ "Dec";
254 std
::string buttonIncId
= "button" + paramName
+ "Inc";
255 std
::string buttonClrId
= "button" + paramName
+ "Clear";
256 std
::string scaleId
= "scale" + paramName
;
258 m_builder
->get_widget(scaleId
, scale
);
260 m_builder
->get_widget(buttonDecId
, button
);
263 button
->signal_clicked().connect(
264 sigc
::bind
<Gtk
::Scale
*, bool>(
265 sigc
::mem_fun(*this, &MouseCursorTracker
::onParamUpdateButton
),
271 m_builder
->get_widget(buttonIncId
, button
);
274 button
->signal_clicked().connect(
275 sigc
::bind
<Gtk
::Scale
*, bool>(
276 sigc
::mem_fun(*this, &MouseCursorTracker
::onParamUpdateButton
),
282 Glib
::RefPtr
<Gtk
::Adjustment
> adj
;
285 adj
= scale
->get_adjustment();
287 adj
->signal_value_changed().connect(
288 sigc
::bind
<Glib
::RefPtr
<Gtk
::Adjustment
>, std
::string
>(
289 sigc
::mem_fun(*this, &MouseCursorTracker
::onParamValChanged
),
295 /* Use get_object instead of get_widget to avoid
296 * "widget not found" error messages */
297 auto obj
= m_builder
->get_object(buttonClrId
);
300 auto buttonClr
= Glib
::RefPtr
<Gtk
::Button
>::cast_dynamic(obj
);
301 buttonClr
->signal_clicked().connect(
302 sigc
::bind
<Glib
::RefPtr
<Gtk
::Button
>, std
::string
, Glib
::RefPtr
<Gtk
::Adjustment
> >(
303 sigc
::mem_fun(*this, &MouseCursorTracker
::onClearButton
),
304 buttonClr
, paramName
, adj
310 // Bind handlers for auto params check boxes
311 Gtk
::CheckButton
*check
;
312 Gtk
::Button
*buttonInc
;
313 Gtk
::Button
*buttonDec
;
315 std
::vector
<std
::string
> autoTracked
=
317 "AngleX", "AngleY", "EyeLOpen", "EyeROpen", "MouthOpenY", "Breath"
320 for (auto it
= autoTracked
.begin(); it
!= autoTracked
.end(); ++it
)
322 GET_WIDGET_ASSERT("check" + *it
, check
);
323 GET_WIDGET_ASSERT("scale" + *it
, scale
);
324 GET_WIDGET_ASSERT("button" + *it
+ "Inc", buttonInc
);
325 GET_WIDGET_ASSERT("button" + *it
+ "Dec", buttonDec
);
326 check
->signal_toggled().connect(
327 sigc
::bind
<Gtk
::CheckButton
*, Gtk
::Scale
*, Gtk
::Button
*, Gtk
::Button
*, std
::string
>(
328 sigc
::mem_fun(*this, &MouseCursorTracker
::onAutoToggle
),
329 check
, scale
, buttonDec
, buttonInc
, "Param" + *it
334 // Set some values from config file
335 GET_WIDGET_ASSERT("checkMouthOpenY", check
);
336 check
->set_active(m_cfg
.useLipSync
);
337 GET_WIDGET_ASSERT("checkBreath", check
);
338 check
->set_active(m_cfg
.autoBreath
);
339 GET_WIDGET_ASSERT("scaleMouthForm", scale
);
340 auto adj
= scale
->get_adjustment();
341 // Don't trigger value changed event
342 m_onClearResettingParams
.push_back("MouthForm");
343 adj
->set_value(m_cfg
.mouthForm
);
344 m_onClearResettingParams
.erase(
346 m_onClearResettingParams
.begin(),
347 m_onClearResettingParams
.end(),
349 m_onClearResettingParams
.end()
353 GET_WIDGET_ASSERT("windowMain", window
);
355 std
::vector
<std
::string
> expanders
=
357 "expanderHead", "expanderEyes", "expanderEyebrows",
358 "expanderMouthFace", "expanderHair", "expanderBody",
361 Gtk
::Expander
*expander
;
362 for (auto it
= expanders
.begin(); it
!= expanders
.end(); ++it
)
364 m_builder
->get_widget(*it
, expander
);
367 expander
->property_expanded().signal_changed().connect(
368 sigc
::bind
<Gtk
::Window
*>(
369 sigc
::mem_fun(*this, &MouseCursorTracker
::onExpanderChange
),
376 m_gtkapp
->run(*window
);