1 #include "libslic3r/Point.hpp"
2 #include "libslic3r/libslic3r.h"
3
4 #include "GLToolbar.hpp"
5
6 #include "slic3r/GUI/GLCanvas3D.hpp"
7 #include "slic3r/GUI/GUI_App.hpp"
8 #include "slic3r/GUI/Camera.hpp"
9 #include "slic3r/GUI/Plater.hpp"
10
11 #include <wx/event.h>
12 #include <wx/bitmap.h>
13 #include <wx/dcmemory.h>
14 #include <wx/settings.h>
15 #include <wx/glcanvas.h>
16
17 namespace Slic3r {
18 namespace GUI {
19
20 wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
21 wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
22 wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
23 wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
24 wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
25 wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
26 wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);
27 wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent);
28 wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent);
29 wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent);
30 wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
31
32 wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_3D, SimpleEvent);
33 wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent);
34
__anon09d882000102()35 const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){};
__anon09d882000202()36 const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; };
__anon09d882000302()37 const GLToolbarItem::EnablingCallback GLToolbarItem::Default_Enabling_Callback = []()->bool { return true; };
__anon09d882000402(float, float, float, float)38 const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){};
39
Option()40 GLToolbarItem::Data::Option::Option()
41 : toggable(false)
42 , action_callback(Default_Action_Callback)
43 , render_callback(nullptr)
44 {
45 }
46
Data()47 GLToolbarItem::Data::Data()
48 : name("")
49 , icon_filename("")
50 , tooltip("")
51 , additional_tooltip("")
52 , sprite_id(-1)
53 , visible(true)
54 , visibility_callback(Default_Visibility_Callback)
55 , enabling_callback(Default_Enabling_Callback)
56 {
57 }
58
GLToolbarItem(GLToolbarItem::EType type,const GLToolbarItem::Data & data)59 GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Data& data)
60 : m_type(type)
61 , m_state(Normal)
62 , m_data(data)
63 , m_last_action_type(Undefined)
64 {
65 }
66
update_visibility()67 bool GLToolbarItem::update_visibility()
68 {
69 bool visible = m_data.visibility_callback();
70 bool ret = (m_data.visible != visible);
71 if (ret)
72 m_data.visible = visible;
73 // Return false for separator as it would always return true.
74 return is_separator() ? false : ret;
75 }
76
update_enabled_state()77 bool GLToolbarItem::update_enabled_state()
78 {
79 bool enabled = m_data.enabling_callback();
80 bool ret = (is_enabled() != enabled);
81 if (ret)
82 m_state = enabled ? GLToolbarItem::Normal : GLToolbarItem::Disabled;
83
84 return ret;
85 }
86
render(unsigned int tex_id,float left,float right,float bottom,float top,unsigned int tex_width,unsigned int tex_height,unsigned int icon_size) const87 void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const
88 {
89 auto uvs = [this](unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) -> GLTexture::Quad_UVs
90 {
91 assert((tex_width != 0) && (tex_height != 0));
92 GLTexture::Quad_UVs ret;
93 // tiles in the texture are spaced by 1 pixel
94 float icon_size_px = (float)(tex_width - 1) / (float)Num_States;
95 float inv_tex_width = 1.0f / (float)tex_width;
96 float inv_tex_height = 1.0f / (float)tex_height;
97 // tiles in the texture are spaced by 1 pixel
98 float u_offset = 1.0f * inv_tex_width;
99 float v_offset = 1.0f * inv_tex_height;
100 float du = icon_size_px * inv_tex_width;
101 float dv = icon_size_px * inv_tex_height;
102 float left = u_offset + (float)m_state * du;
103 float right = left + du - u_offset;
104 float top = v_offset + (float)m_data.sprite_id * dv;
105 float bottom = top + dv - v_offset;
106 ret.left_top = { left, top };
107 ret.left_bottom = { left, bottom };
108 ret.right_bottom = { right, bottom };
109 ret.right_top = { right, top };
110 return ret;
111 };
112
113 GLTexture::render_sub_texture(tex_id, left, right, bottom, top, uvs(tex_width, tex_height, icon_size));
114
115 if (is_pressed())
116 {
117 if ((m_last_action_type == Left) && m_data.left.can_render())
118 m_data.left.render_callback(left, right, bottom, top);
119 else if ((m_last_action_type == Right) && m_data.right.can_render())
120 m_data.right.render_callback(left, right, bottom, top);
121 }
122 }
123
Metadata()124 BackgroundTexture::Metadata::Metadata()
125 : filename("")
126 , left(0)
127 , right(0)
128 , top(0)
129 , bottom(0)
130 {
131 }
132
133 const float GLToolbar::Default_Icons_Size = 40.0f;
134
Layout()135 GLToolbar::Layout::Layout()
136 : type(Horizontal)
137 , horizontal_orientation(HO_Center)
138 , vertical_orientation(VO_Center)
139 , top(0.0f)
140 , left(0.0f)
141 , border(0.0f)
142 , separator_size(0.0f)
143 , gap_size(0.0f)
144 , icons_size(Default_Icons_Size)
145 , scale(1.0f)
146 , width(0.0f)
147 , height(0.0f)
148 , dirty(true)
149 {
150 }
151
GLToolbar(GLToolbar::EType type,const std::string & name)152 GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name)
153 : m_type(type)
154 , m_name(name)
155 , m_enabled(false)
156 , m_icons_texture_dirty(true)
157 , m_pressed_toggable_id(-1)
158 {
159 }
160
~GLToolbar()161 GLToolbar::~GLToolbar()
162 {
163 for (GLToolbarItem* item : m_items)
164 {
165 delete item;
166 }
167 }
168
init(const BackgroundTexture::Metadata & background_texture)169 bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture)
170 {
171 if (m_background_texture.texture.get_id() != 0)
172 return true;
173
174 std::string path = resources_dir() + "/icons/";
175 bool res = false;
176
177 if (!background_texture.filename.empty())
178 res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, GLTexture::SingleThreaded, false);
179
180 if (res)
181 m_background_texture.metadata = background_texture;
182
183 return res;
184 }
185
get_layout_type() const186 GLToolbar::Layout::EType GLToolbar::get_layout_type() const
187 {
188 return m_layout.type;
189 }
190
set_layout_type(GLToolbar::Layout::EType type)191 void GLToolbar::set_layout_type(GLToolbar::Layout::EType type)
192 {
193 m_layout.type = type;
194 m_layout.dirty = true;
195 }
196
set_position(float top,float left)197 void GLToolbar::set_position(float top, float left)
198 {
199 m_layout.top = top;
200 m_layout.left = left;
201 }
202
set_border(float border)203 void GLToolbar::set_border(float border)
204 {
205 m_layout.border = border;
206 m_layout.dirty = true;
207 }
208
set_separator_size(float size)209 void GLToolbar::set_separator_size(float size)
210 {
211 m_layout.separator_size = size;
212 m_layout.dirty = true;
213 }
214
set_gap_size(float size)215 void GLToolbar::set_gap_size(float size)
216 {
217 m_layout.gap_size = size;
218 m_layout.dirty = true;
219 }
220
set_icons_size(float size)221 void GLToolbar::set_icons_size(float size)
222 {
223 if (m_layout.icons_size != size)
224 {
225 m_layout.icons_size = size;
226 m_layout.dirty = true;
227 m_icons_texture_dirty = true;
228 }
229 }
230
set_scale(float scale)231 void GLToolbar::set_scale(float scale)
232 {
233 if (m_layout.scale != scale) {
234 m_layout.scale = scale;
235 m_layout.dirty = true;
236 m_icons_texture_dirty = true;
237 }
238 }
239
add_item(const GLToolbarItem::Data & data)240 bool GLToolbar::add_item(const GLToolbarItem::Data& data)
241 {
242 GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data);
243 if (item == nullptr)
244 return false;
245
246 m_items.push_back(item);
247 m_layout.dirty = true;
248 return true;
249 }
250
add_separator()251 bool GLToolbar::add_separator()
252 {
253 GLToolbarItem::Data data;
254 GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, data);
255 if (item == nullptr)
256 return false;
257
258 m_items.push_back(item);
259 m_layout.dirty = true;
260 return true;
261 }
262
get_width() const263 float GLToolbar::get_width() const
264 {
265 if (m_layout.dirty)
266 calc_layout();
267
268 return m_layout.width;
269 }
270
get_height() const271 float GLToolbar::get_height() const
272 {
273 if (m_layout.dirty)
274 calc_layout();
275
276 return m_layout.height;
277 }
278
select_item(const std::string & name)279 void GLToolbar::select_item(const std::string& name)
280 {
281 if (is_item_disabled(name))
282 return;
283
284 for (GLToolbarItem* item : m_items)
285 {
286 if (!item->is_disabled())
287 {
288 bool hover = item->is_hovered();
289 item->set_state((item->get_name() == name) ? (hover ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed) : (hover ? GLToolbarItem::Hover : GLToolbarItem::Normal));
290 }
291 }
292 }
293
is_item_pressed(const std::string & name) const294 bool GLToolbar::is_item_pressed(const std::string& name) const
295 {
296 for (const GLToolbarItem* item : m_items)
297 {
298 if (item->get_name() == name)
299 return item->is_pressed();
300 }
301
302 return false;
303 }
304
is_item_disabled(const std::string & name) const305 bool GLToolbar::is_item_disabled(const std::string& name) const
306 {
307 for (const GLToolbarItem* item : m_items)
308 {
309 if (item->get_name() == name)
310 return item->is_disabled();
311 }
312
313 return false;
314 }
315
is_item_visible(const std::string & name) const316 bool GLToolbar::is_item_visible(const std::string& name) const
317 {
318 for (const GLToolbarItem* item : m_items)
319 {
320 if (item->get_name() == name)
321 return item->is_visible();
322 }
323
324 return false;
325 }
326
is_any_item_pressed() const327 bool GLToolbar::is_any_item_pressed() const
328 {
329 for (const GLToolbarItem* item : m_items)
330 {
331 if (item->is_pressed())
332 return true;
333 }
334
335 return false;
336 }
337
get_item_id(const std::string & name) const338 int GLToolbar::get_item_id(const std::string& name) const
339 {
340 for (int i = 0; i < (int)m_items.size(); ++i)
341 {
342 if (m_items[i]->get_name() == name)
343 return i;
344 }
345
346 return -1;
347 }
348
get_tooltip() const349 std::string GLToolbar::get_tooltip() const
350 {
351 std::string tooltip;
352
353 for (GLToolbarItem* item : m_items)
354 {
355 if (item->is_hovered())
356 {
357 tooltip = item->get_tooltip();
358 if (!item->is_pressed())
359 {
360 const std::string& additional_tooltip = item->get_additional_tooltip();
361 if (!additional_tooltip.empty())
362 tooltip += "\n" + additional_tooltip;
363
364 break;
365 }
366 }
367 }
368
369 return tooltip;
370 }
371
get_additional_tooltip(int item_id,std::string & text)372 void GLToolbar::get_additional_tooltip(int item_id, std::string& text)
373 {
374 if (0 <= item_id && item_id < (int)m_items.size())
375 {
376 text = m_items[item_id]->get_additional_tooltip();
377 return;
378 }
379
380 text.clear();
381 }
382
set_additional_tooltip(int item_id,const std::string & text)383 void GLToolbar::set_additional_tooltip(int item_id, const std::string& text)
384 {
385 if (0 <= item_id && item_id < (int)m_items.size())
386 m_items[item_id]->set_additional_tooltip(text);
387 }
388
set_tooltip(int item_id,const std::string & text)389 void GLToolbar::set_tooltip(int item_id, const std::string& text)
390 {
391 if (0 <= item_id && item_id < (int)m_items.size())
392 m_items[item_id]->set_tooltip(text);
393 }
394
update_items_state()395 bool GLToolbar::update_items_state()
396 {
397 bool ret = false;
398 ret |= update_items_visibility();
399 ret |= update_items_enabled_state();
400 if (!is_any_item_pressed())
401 m_pressed_toggable_id = -1;
402
403 return ret;
404 }
405
render(const GLCanvas3D & parent) const406 void GLToolbar::render(const GLCanvas3D& parent) const
407 {
408 if (!m_enabled || m_items.empty())
409 return;
410
411 if (m_icons_texture_dirty)
412 generate_icons_texture();
413
414 switch (m_layout.type)
415 {
416 default:
417 case Layout::Horizontal: { render_horizontal(parent); break; }
418 case Layout::Vertical: { render_vertical(parent); break; }
419 }
420 }
421
on_mouse(wxMouseEvent & evt,GLCanvas3D & parent)422 bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
423 {
424 if (!m_enabled)
425 return false;
426
427 Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY());
428 bool processed = false;
429
430 // mouse anywhere
431 if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr))
432 {
433 if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())) {
434 // prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it,
435 // as when switching between views
436 m_mouse_capture.reset();
437 return true;
438 }
439 m_mouse_capture.reset();
440 }
441
442 if (evt.Moving())
443 update_hover_state(mouse_pos, parent);
444 else if (evt.LeftUp())
445 {
446 if (m_mouse_capture.left)
447 {
448 processed = true;
449 m_mouse_capture.left = false;
450 }
451 else
452 return false;
453 }
454 else if (evt.MiddleUp())
455 {
456 if (m_mouse_capture.middle)
457 {
458 processed = true;
459 m_mouse_capture.middle = false;
460 }
461 else
462 return false;
463 }
464 else if (evt.RightUp())
465 {
466 if (m_mouse_capture.right)
467 {
468 processed = true;
469 m_mouse_capture.right = false;
470 }
471 else
472 return false;
473 }
474 else if (evt.Dragging())
475 {
476 if (m_mouse_capture.any())
477 // if the button down was done on this toolbar, prevent from dragging into the scene
478 processed = true;
479 else
480 return false;
481 }
482
483 int item_id = contains_mouse(mouse_pos, parent);
484 if (item_id != -1)
485 {
486 // mouse inside toolbar
487 if (evt.LeftDown() || evt.LeftDClick())
488 {
489 m_mouse_capture.left = true;
490 m_mouse_capture.parent = &parent;
491 processed = true;
492 if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() &&
493 ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left)))
494 {
495 // mouse is inside an icon
496 do_action(GLToolbarItem::Left, item_id, parent, true);
497 parent.set_as_dirty();
498 }
499 }
500 else if (evt.MiddleDown())
501 {
502 m_mouse_capture.middle = true;
503 m_mouse_capture.parent = &parent;
504 }
505 else if (evt.RightDown())
506 {
507 m_mouse_capture.right = true;
508 m_mouse_capture.parent = &parent;
509 processed = true;
510 if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() &&
511 ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right)))
512 {
513 // mouse is inside an icon
514 do_action(GLToolbarItem::Right, item_id, parent, true);
515 parent.set_as_dirty();
516 }
517 }
518 }
519
520 return processed;
521 }
522
calc_layout() const523 void GLToolbar::calc_layout() const
524 {
525 switch (m_layout.type)
526 {
527 default:
528 case Layout::Horizontal:
529 {
530 m_layout.width = get_width_horizontal();
531 m_layout.height = get_height_horizontal();
532 break;
533 }
534 case Layout::Vertical:
535 {
536 m_layout.width = get_width_vertical();
537 m_layout.height = get_height_vertical();
538 break;
539 }
540 }
541
542 m_layout.dirty = false;
543 }
544
get_width_horizontal() const545 float GLToolbar::get_width_horizontal() const
546 {
547 return get_main_size();
548 }
549
get_width_vertical() const550 float GLToolbar::get_width_vertical() const
551 {
552 return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale;
553 }
554
get_height_horizontal() const555 float GLToolbar::get_height_horizontal() const
556 {
557 return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale;
558 }
559
get_height_vertical() const560 float GLToolbar::get_height_vertical() const
561 {
562 return get_main_size();
563 }
564
get_main_size() const565 float GLToolbar::get_main_size() const
566 {
567 float size = 2.0f * m_layout.border;
568 for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
569 {
570 if (!m_items[i]->is_visible())
571 continue;
572
573 if (m_items[i]->is_separator())
574 size += m_layout.separator_size;
575 else
576 size += (float)m_layout.icons_size;
577 }
578
579 if (m_items.size() > 1)
580 size += ((float)m_items.size() - 1.0f) * m_layout.gap_size;
581
582 return size * m_layout.scale;
583 }
584
get_visible_items_cnt() const585 int GLToolbar::get_visible_items_cnt() const
586 {
587 int cnt = 0;
588 for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
589 if (m_items[i]->is_visible() && !m_items[i]->is_separator())
590 cnt++;
591
592 return cnt;
593 }
594
do_action(GLToolbarItem::EActionType type,int item_id,GLCanvas3D & parent,bool check_hover)595 void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas3D& parent, bool check_hover)
596 {
597 if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id))
598 {
599 if ((0 <= item_id) && (item_id < (int)m_items.size()))
600 {
601 GLToolbarItem* item = m_items[item_id];
602 if ((item != nullptr) && !item->is_separator() && !item->is_disabled() && (!check_hover || item->is_hovered()))
603 {
604 if (((type == GLToolbarItem::Right) && item->is_right_toggable()) ||
605 ((type == GLToolbarItem::Left) && item->is_left_toggable()))
606 {
607 GLToolbarItem::EState state = item->get_state();
608 if (state == GLToolbarItem::Hover)
609 item->set_state(GLToolbarItem::HoverPressed);
610 else if (state == GLToolbarItem::HoverPressed)
611 item->set_state(GLToolbarItem::Hover);
612 else if (state == GLToolbarItem::Pressed)
613 item->set_state(GLToolbarItem::Normal);
614 else if (state == GLToolbarItem::Normal)
615 item->set_state(GLToolbarItem::Pressed);
616
617 m_pressed_toggable_id = item->is_pressed() ? item_id : -1;
618 item->reset_last_action_type();
619
620 parent.render();
621 switch (type)
622 {
623 default:
624 case GLToolbarItem::Left: { item->do_left_action(); break; }
625 case GLToolbarItem::Right: { item->do_right_action(); break; }
626 }
627 }
628 else
629 {
630 if (m_type == Radio)
631 select_item(item->get_name());
632 else
633 item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed);
634
635 item->reset_last_action_type();
636 parent.render();
637 switch (type)
638 {
639 default:
640 case GLToolbarItem::Left: { item->do_left_action(); break; }
641 case GLToolbarItem::Right: { item->do_right_action(); break; }
642 }
643
644 if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled))
645 {
646 // the item may get disabled during the action, if not, set it back to hover state
647 item->set_state(GLToolbarItem::Hover);
648 parent.render();
649 }
650 }
651 }
652 }
653 }
654 }
655
update_hover_state(const Vec2d & mouse_pos,GLCanvas3D & parent)656 void GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent)
657 {
658 if (!m_enabled)
659 return;
660
661 switch (m_layout.type)
662 {
663 default:
664 case Layout::Horizontal: { update_hover_state_horizontal(mouse_pos, parent); break; }
665 case Layout::Vertical: { update_hover_state_vertical(mouse_pos, parent); break; }
666 }
667 }
668
update_hover_state_horizontal(const Vec2d & mouse_pos,GLCanvas3D & parent)669 void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent)
670 {
671 // NB: mouse_pos is already scaled appropriately
672
673 float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
674 float factor = m_layout.scale * inv_zoom;
675
676 Size cnv_size = parent.get_canvas_size();
677 Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
678
679 float scaled_icons_size = m_layout.icons_size * factor;
680 float scaled_separator_size = m_layout.separator_size * factor;
681 float scaled_gap_size = m_layout.gap_size * factor;
682 float scaled_border = m_layout.border * factor;
683
684 float separator_stride = scaled_separator_size + scaled_gap_size;
685 float icon_stride = scaled_icons_size + scaled_gap_size;
686
687 float left = m_layout.left + scaled_border;
688 float top = m_layout.top - scaled_border;
689
690 for (GLToolbarItem* item : m_items)
691 {
692 if (!item->is_visible())
693 continue;
694
695 if (item->is_separator())
696 left += separator_stride;
697 else
698 {
699 float right = left + scaled_icons_size;
700 float bottom = top - scaled_icons_size;
701
702 GLToolbarItem::EState state = item->get_state();
703 bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
704
705 switch (state)
706 {
707 case GLToolbarItem::Normal:
708 {
709 if (inside)
710 {
711 item->set_state(GLToolbarItem::Hover);
712 parent.set_as_dirty();
713 }
714
715 break;
716 }
717 case GLToolbarItem::Hover:
718 {
719 if (!inside)
720 {
721 item->set_state(GLToolbarItem::Normal);
722 parent.set_as_dirty();
723 }
724
725 break;
726 }
727 case GLToolbarItem::Pressed:
728 {
729 if (inside)
730 {
731 item->set_state(GLToolbarItem::HoverPressed);
732 parent.set_as_dirty();
733 }
734
735 break;
736 }
737 case GLToolbarItem::HoverPressed:
738 {
739 if (!inside)
740 {
741 item->set_state(GLToolbarItem::Pressed);
742 parent.set_as_dirty();
743 }
744
745 break;
746 }
747 case GLToolbarItem::Disabled:
748 {
749 if (inside)
750 {
751 item->set_state(GLToolbarItem::HoverDisabled);
752 parent.set_as_dirty();
753 }
754
755 break;
756 }
757 case GLToolbarItem::HoverDisabled:
758 {
759 if (!inside)
760 {
761 item->set_state(GLToolbarItem::Disabled);
762 parent.set_as_dirty();
763 }
764
765 break;
766 }
767 default:
768 {
769 break;
770 }
771 }
772
773 left += icon_stride;
774 }
775 }
776 }
777
update_hover_state_vertical(const Vec2d & mouse_pos,GLCanvas3D & parent)778 void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent)
779 {
780 // NB: mouse_pos is already scaled appropriately
781
782 float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
783 float factor = m_layout.scale * inv_zoom;
784
785 Size cnv_size = parent.get_canvas_size();
786 Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
787
788 float scaled_icons_size = m_layout.icons_size * factor;
789 float scaled_separator_size = m_layout.separator_size * factor;
790 float scaled_gap_size = m_layout.gap_size * factor;
791 float scaled_border = m_layout.border * factor;
792 float separator_stride = scaled_separator_size + scaled_gap_size;
793 float icon_stride = scaled_icons_size + scaled_gap_size;
794
795 float left = m_layout.left + scaled_border;
796 float top = m_layout.top - scaled_border;
797
798 for (GLToolbarItem* item : m_items)
799 {
800 if (!item->is_visible())
801 continue;
802
803 if (item->is_separator())
804 top -= separator_stride;
805 else
806 {
807 float right = left + scaled_icons_size;
808 float bottom = top - scaled_icons_size;
809
810 GLToolbarItem::EState state = item->get_state();
811 bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
812
813 switch (state)
814 {
815 case GLToolbarItem::Normal:
816 {
817 if (inside)
818 {
819 item->set_state(GLToolbarItem::Hover);
820 parent.set_as_dirty();
821 }
822
823 break;
824 }
825 case GLToolbarItem::Hover:
826 {
827 if (!inside)
828 {
829 item->set_state(GLToolbarItem::Normal);
830 parent.set_as_dirty();
831 }
832
833 break;
834 }
835 case GLToolbarItem::Pressed:
836 {
837 if (inside)
838 {
839 item->set_state(GLToolbarItem::HoverPressed);
840 parent.set_as_dirty();
841 }
842
843 break;
844 }
845 case GLToolbarItem::HoverPressed:
846 {
847 if (!inside)
848 {
849 item->set_state(GLToolbarItem::Pressed);
850 parent.set_as_dirty();
851 }
852
853 break;
854 }
855 case GLToolbarItem::Disabled:
856 {
857 if (inside)
858 {
859 item->set_state(GLToolbarItem::HoverDisabled);
860 parent.set_as_dirty();
861 }
862
863 break;
864 }
865 case GLToolbarItem::HoverDisabled:
866 {
867 if (!inside)
868 {
869 item->set_state(GLToolbarItem::Disabled);
870 parent.set_as_dirty();
871 }
872
873 break;
874 }
875 default:
876 {
877 break;
878 }
879 }
880
881 top -= icon_stride;
882 }
883 }
884 }
885
contains_mouse(const Vec2d & mouse_pos,const GLCanvas3D & parent) const886 int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const
887 {
888 if (!m_enabled)
889 return -1;
890
891 switch (m_layout.type)
892 {
893 default:
894 case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos, parent); }
895 case Layout::Vertical: { return contains_mouse_vertical(mouse_pos, parent); }
896 }
897 }
898
contains_mouse_horizontal(const Vec2d & mouse_pos,const GLCanvas3D & parent) const899 int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const
900 {
901 // NB: mouse_pos is already scaled appropriately
902
903 float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
904 float factor = m_layout.scale * inv_zoom;
905
906 Size cnv_size = parent.get_canvas_size();
907 Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
908
909 float scaled_icons_size = m_layout.icons_size * factor;
910 float scaled_separator_size = m_layout.separator_size * factor;
911 float scaled_gap_size = m_layout.gap_size * factor;
912 float scaled_border = m_layout.border * factor;
913
914 float left = m_layout.left + scaled_border;
915 float top = m_layout.top - scaled_border;
916
917
918 for (size_t id=0; id<m_items.size(); ++id)
919 {
920 GLToolbarItem* item = m_items[id];
921
922 if (!item->is_visible())
923 continue;
924
925 if (item->is_separator())
926 {
927 float right = left + scaled_separator_size;
928 float bottom = top - scaled_icons_size;
929
930 // mouse inside the separator
931 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
932 return id;
933
934 left = right;
935 right += scaled_gap_size;
936
937 if (id < m_items.size() - 1)
938 {
939 // mouse inside the gap
940 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
941 return -2;
942 }
943
944 left = right;
945 }
946 else
947 {
948 float right = left + scaled_icons_size;
949 float bottom = top - scaled_icons_size;
950
951 // mouse inside the icon
952 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
953 return id;
954
955 left = right;
956 right += scaled_gap_size;
957
958 if (id < m_items.size() - 1)
959 {
960 // mouse inside the gap
961 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
962 return -2;
963 }
964
965 left = right;
966 }
967 }
968
969 return -1;
970 }
971
contains_mouse_vertical(const Vec2d & mouse_pos,const GLCanvas3D & parent) const972 int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const
973 {
974 // NB: mouse_pos is already scaled appropriately
975
976 float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
977 float factor = m_layout.scale * inv_zoom;
978
979 Size cnv_size = parent.get_canvas_size();
980 Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
981
982 float scaled_icons_size = m_layout.icons_size * factor;
983 float scaled_separator_size = m_layout.separator_size * factor;
984 float scaled_gap_size = m_layout.gap_size * factor;
985 float scaled_border = m_layout.border * factor;
986
987 float left = m_layout.left + scaled_border;
988 float top = m_layout.top - scaled_border;
989
990 for (size_t id=0; id<m_items.size(); ++id)
991 {
992 GLToolbarItem* item = m_items[id];
993
994 if (!item->is_visible())
995 continue;
996
997 if (item->is_separator())
998 {
999 float right = left + scaled_icons_size;
1000 float bottom = top - scaled_separator_size;
1001
1002 // mouse inside the separator
1003 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
1004 return id;
1005
1006 top = bottom;
1007 bottom -= scaled_gap_size;
1008
1009 if (id < m_items.size() - 1)
1010 {
1011 // mouse inside the gap
1012 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
1013 return -2;
1014 }
1015
1016 top = bottom;
1017 }
1018 else
1019 {
1020 float right = left + scaled_icons_size;
1021 float bottom = top - scaled_icons_size;
1022
1023 // mouse inside the icon
1024 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
1025 return id;
1026
1027 top = bottom;
1028 bottom -= scaled_gap_size;
1029
1030 if (id < m_items.size() - 1)
1031 {
1032 // mouse inside the gap
1033 if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
1034 return -2;
1035 }
1036
1037 top = bottom;
1038 }
1039 }
1040
1041 return -1;
1042 }
1043
render_background(float left,float top,float right,float bottom,float border) const1044 void GLToolbar::render_background(float left, float top, float right, float bottom, float border) const
1045 {
1046 unsigned int tex_id = m_background_texture.texture.get_id();
1047 float tex_width = (float)m_background_texture.texture.get_width();
1048 float tex_height = (float)m_background_texture.texture.get_height();
1049 if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0))
1050 {
1051 float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f;
1052 float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f;
1053
1054 float internal_left = left + border;
1055 float internal_right = right - border;
1056 float internal_top = top - border;
1057 float internal_bottom = bottom + border;
1058
1059 float left_uv = 0.0f;
1060 float right_uv = 1.0f;
1061 float top_uv = 1.0f;
1062 float bottom_uv = 0.0f;
1063
1064 float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width;
1065 float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width;
1066 float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height;
1067 float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height;
1068
1069 // top-left corner
1070 if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Top))
1071 GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1072 else
1073 GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { left_uv, internal_top_uv }, { internal_left_uv, internal_top_uv }, { internal_left_uv, top_uv }, { left_uv, top_uv } });
1074
1075 // top edge
1076 if (m_layout.vertical_orientation == Layout::VO_Top)
1077 GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1078 else
1079 GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, top_uv }, { internal_left_uv, top_uv } });
1080
1081 // top-right corner
1082 if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Top))
1083 GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1084 else
1085 GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_right_uv, internal_top_uv }, { right_uv, internal_top_uv }, { right_uv, top_uv }, { internal_right_uv, top_uv } });
1086
1087 // center-left edge
1088 if (m_layout.horizontal_orientation == Layout::HO_Left)
1089 GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1090 else
1091 GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { left_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { left_uv, internal_top_uv } });
1092
1093 // center
1094 GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1095
1096 // center-right edge
1097 if (m_layout.horizontal_orientation == Layout::HO_Right)
1098 GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1099 else
1100 GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_right_uv, internal_bottom_uv }, { right_uv, internal_bottom_uv }, { right_uv, internal_top_uv }, { internal_right_uv, internal_top_uv } });
1101
1102 // bottom-left corner
1103 if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Bottom))
1104 GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1105 else
1106 GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { left_uv, bottom_uv }, { internal_left_uv, bottom_uv }, { internal_left_uv, internal_bottom_uv }, { left_uv, internal_bottom_uv } });
1107
1108 // bottom edge
1109 if (m_layout.vertical_orientation == Layout::VO_Bottom)
1110 GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1111 else
1112 GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, bottom_uv }, { internal_right_uv, bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } });
1113
1114 // bottom-right corner
1115 if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Bottom))
1116 GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
1117 else
1118 GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_right_uv, bottom_uv }, { right_uv, bottom_uv }, { right_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv } });
1119 }
1120 }
1121
render_horizontal(const GLCanvas3D & parent) const1122 void GLToolbar::render_horizontal(const GLCanvas3D& parent) const
1123 {
1124 unsigned int tex_id = m_icons_texture.get_id();
1125 int tex_width = m_icons_texture.get_width();
1126 int tex_height = m_icons_texture.get_height();
1127
1128 float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
1129 float factor = inv_zoom * m_layout.scale;
1130
1131 float scaled_icons_size = m_layout.icons_size * factor;
1132 float scaled_separator_size = m_layout.separator_size * factor;
1133 float scaled_gap_size = m_layout.gap_size * factor;
1134 float scaled_border = m_layout.border * factor;
1135 float scaled_width = get_width() * inv_zoom;
1136 float scaled_height = get_height() * inv_zoom;
1137
1138 float separator_stride = scaled_separator_size + scaled_gap_size;
1139 float icon_stride = scaled_icons_size + scaled_gap_size;
1140
1141 float left = m_layout.left;
1142 float top = m_layout.top;
1143 float right = left + scaled_width;
1144 float bottom = top - scaled_height;
1145
1146 render_background(left, top, right, bottom, scaled_border);
1147
1148 left += scaled_border;
1149 top -= scaled_border;
1150
1151 if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0))
1152 return;
1153
1154 // renders icons
1155 for (const GLToolbarItem* item : m_items)
1156 {
1157 if (!item->is_visible())
1158 continue;
1159
1160 if (item->is_separator())
1161 left += separator_stride;
1162 else
1163 {
1164 item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale));
1165 left += icon_stride;
1166 }
1167 }
1168 }
1169
render_vertical(const GLCanvas3D & parent) const1170 void GLToolbar::render_vertical(const GLCanvas3D& parent) const
1171 {
1172 unsigned int tex_id = m_icons_texture.get_id();
1173 int tex_width = m_icons_texture.get_width();
1174 int tex_height = m_icons_texture.get_height();
1175
1176 float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
1177 float factor = inv_zoom * m_layout.scale;
1178
1179 float scaled_icons_size = m_layout.icons_size * factor;
1180 float scaled_separator_size = m_layout.separator_size * factor;
1181 float scaled_gap_size = m_layout.gap_size * factor;
1182 float scaled_border = m_layout.border * factor;
1183 float scaled_width = get_width() * inv_zoom;
1184 float scaled_height = get_height() * inv_zoom;
1185
1186 float separator_stride = scaled_separator_size + scaled_gap_size;
1187 float icon_stride = scaled_icons_size + scaled_gap_size;
1188
1189 float left = m_layout.left;
1190 float top = m_layout.top;
1191 float right = left + scaled_width;
1192 float bottom = top - scaled_height;
1193
1194 render_background(left, top, right, bottom, scaled_border);
1195
1196 left += scaled_border;
1197 top -= scaled_border;
1198
1199 if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0))
1200 return;
1201
1202 // renders icons
1203 for (const GLToolbarItem* item : m_items)
1204 {
1205 if (!item->is_visible())
1206 continue;
1207
1208 if (item->is_separator())
1209 top -= separator_stride;
1210 else
1211 {
1212 item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale));
1213 top -= icon_stride;
1214 }
1215 }
1216 }
1217
generate_icons_texture() const1218 bool GLToolbar::generate_icons_texture() const
1219 {
1220 std::string path = resources_dir() + "/icons/";
1221 std::vector<std::string> filenames;
1222 for (GLToolbarItem* item : m_items)
1223 {
1224 const std::string& icon_filename = item->get_icon_filename();
1225 if (!icon_filename.empty())
1226 filenames.push_back(path + icon_filename);
1227 }
1228
1229 std::vector<std::pair<int, bool>> states;
1230 if (m_type == Normal)
1231 {
1232 states.push_back({ 1, false }); // Normal
1233 states.push_back({ 0, false }); // Pressed
1234 states.push_back({ 2, false }); // Disabled
1235 states.push_back({ 0, false }); // Hover
1236 states.push_back({ 0, false }); // HoverPressed
1237 states.push_back({ 2, false }); // HoverDisabled
1238 }
1239 else
1240 {
1241 states.push_back({ 1, false }); // Normal
1242 states.push_back({ 1, true }); // Pressed
1243 states.push_back({ 1, false }); // Disabled
1244 states.push_back({ 0, false }); // Hover
1245 states.push_back({ 1, true }); // HoverPressed
1246 states.push_back({ 1, false }); // HoverDisabled
1247 }
1248
1249 unsigned int sprite_size_px = (unsigned int)(m_layout.icons_size * m_layout.scale);
1250 // // force even size
1251 // if (sprite_size_px % 2 != 0)
1252 // sprite_size_px += 1;
1253
1254 bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false);
1255 if (res)
1256 m_icons_texture_dirty = false;
1257
1258 return res;
1259 }
1260
update_items_visibility()1261 bool GLToolbar::update_items_visibility()
1262 {
1263 bool ret = false;
1264
1265 for (GLToolbarItem* item : m_items)
1266 {
1267 ret |= item->update_visibility();
1268 }
1269
1270 if (ret)
1271 m_layout.dirty = true;
1272
1273 // updates separators visibility to avoid having two of them consecutive
1274 bool any_item_visible = false;
1275 for (GLToolbarItem* item : m_items)
1276 {
1277 if (!item->is_separator())
1278 any_item_visible |= item->is_visible();
1279 else
1280 {
1281 item->set_visible(any_item_visible);
1282 any_item_visible = false;
1283 }
1284 }
1285
1286 return ret;
1287 }
1288
update_items_enabled_state()1289 bool GLToolbar::update_items_enabled_state()
1290 {
1291 bool ret = false;
1292
1293 for (int i = 0; i < (int)m_items.size(); ++i)
1294 {
1295 GLToolbarItem* item = m_items[i];
1296 ret |= item->update_enabled_state();
1297 if (item->is_enabled() && (m_pressed_toggable_id != -1) && (m_pressed_toggable_id != i))
1298 {
1299 ret = true;
1300 item->set_state(GLToolbarItem::Disabled);
1301 }
1302 }
1303
1304 if (ret)
1305 m_layout.dirty = true;
1306
1307 return ret;
1308 }
1309
1310 } // namespace GUI
1311 } // namespace Slic3r
1312