1 #include "canvas_gl.hpp"
2 #include "gl_util.hpp"
3 #include "util/geom_util.hpp"
4 #include <algorithm>
5 #include <epoxy/gl.h>
6 #include <iostream>
7 #include <glm/gtx/matrix_transform_2d.hpp>
8 #include <glm/gtc/type_ptr.hpp>
9 #include "board/board_layers.hpp"
10 #include "bitmap_font_util.hpp"
11 
12 namespace horizon {
get_scale_and_offset()13 std::pair<float, Coordf> CanvasGL::get_scale_and_offset()
14 {
15     return std::make_pair(scale, offset);
16 }
17 
can_set_scale() const18 bool CanvasGL::can_set_scale() const
19 {
20     return !(pan_dragging || zoom_animator.is_running() || gesture_zoom->is_recognized()
21              || gesture_drag->is_recognized());
22 }
23 
set_scale_and_offset(float sc,Coordf ofs)24 void CanvasGL::set_scale_and_offset(float sc, Coordf ofs)
25 {
26     if (!can_set_scale())
27         return;
28     scale = sc;
29     offset = ofs;
30     update_viewmat();
31     s_signal_scale_changed.emit();
32 }
33 
CanvasGL()34 CanvasGL::CanvasGL()
35     : Glib::ObjectBase(typeid(CanvasGL)), Canvas::Canvas(), markers(*this), selection_filter(*this), grid(*this),
36       drag_selection(*this), selectables_renderer(*this, selectables), triangle_renderer(*this, triangles),
37       marker_renderer(*this, markers), picture_renderer(*this), p_property_work_layer(*this, "work-layer"),
38       p_property_layer_opacity(*this, "layer-opacity")
39 {
40     add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON_MOTION_MASK | Gdk::POINTER_MOTION_MASK
41                | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::KEY_PRESS_MASK);
42     m_width = 1000;
43     m_height = 500;
44 
45     set_can_focus(true);
46     property_work_layer().signal_changed().connect([this] {
47         work_layer = property_work_layer();
48         request_push();
49     });
50 
51     property_layer_opacity() = 100;
52     property_layer_opacity().signal_changed().connect([this] { queue_draw(); });
53     clarify_menu = Gtk::manage(new Gtk::Menu);
54 
55     layer_colors = appearance.layer_colors;
56 
57     gesture_zoom = Gtk::GestureZoom::create(*this);
58     gesture_zoom->signal_begin().connect(sigc::mem_fun(*this, &CanvasGL::zoom_gesture_begin_cb));
59     gesture_zoom->signal_update().connect(sigc::mem_fun(*this, &CanvasGL::zoom_gesture_update_cb));
60     gesture_zoom->set_propagation_phase(Gtk::PHASE_BUBBLE);
61 
62     gesture_drag = Gtk::GestureDrag::create(*this);
63     gesture_drag->signal_begin().connect(sigc::mem_fun(*this, &CanvasGL::drag_gesture_begin_cb));
64     gesture_drag->signal_update().connect(sigc::mem_fun(*this, &CanvasGL::drag_gesture_update_cb));
65     gesture_drag->set_propagation_phase(Gtk::PHASE_BUBBLE);
66     gesture_drag->set_touch_only(true);
67 }
68 
set_grid_spacing(uint64_t x,uint64_t y)69 void CanvasGL::set_grid_spacing(uint64_t x, uint64_t y)
70 {
71     grid.spacing = Coordi(x, y);
72     queue_draw();
73 }
74 
set_grid_spacing(uint64_t s)75 void CanvasGL::set_grid_spacing(uint64_t s)
76 {
77     set_grid_spacing(s, s);
78 }
79 
set_grid_origin(const Coordi & c)80 void CanvasGL::set_grid_origin(const Coordi &c)
81 {
82     grid.origin = c;
83     queue_draw();
84 }
85 
get_grid_spacing() const86 Coordi CanvasGL::get_grid_spacing() const
87 {
88     return grid.spacing;
89 }
90 
zoom_gesture_begin_cb(GdkEventSequence * seq)91 void CanvasGL::zoom_gesture_begin_cb(GdkEventSequence *seq)
92 {
93     if (pan_dragging) {
94         gesture_zoom->set_state(Gtk::EVENT_SEQUENCE_DENIED);
95         return;
96     }
97     gesture_zoom_scale_orig = scale;
98     gesture_zoom_offset_orig = offset;
99     double cx, cy;
100     gesture_zoom->get_bounding_box_center(cx, cy);
101     gesture_zoom_pos_orig = Coordf(cx, cy);
102     gesture_zoom->set_state(Gtk::EVENT_SEQUENCE_CLAIMED);
103 }
104 
zoom_gesture_update_cb(GdkEventSequence * seq)105 void CanvasGL::zoom_gesture_update_cb(GdkEventSequence *seq)
106 {
107     auto delta = gesture_zoom->get_scale_delta();
108     double cx, cy;
109     gesture_zoom->get_bounding_box_center(cx, cy);
110     set_scale(cx, cy, gesture_zoom_scale_orig * delta);
111     offset = gesture_zoom_offset_orig + Coordf(cx, cy) - gesture_zoom_pos_orig;
112     update_viewmat();
113     s_signal_scale_changed.emit();
114 }
115 
drag_gesture_begin_cb(GdkEventSequence * seq)116 void CanvasGL::drag_gesture_begin_cb(GdkEventSequence *seq)
117 {
118     inhibit_drag_selection();
119     if (pan_dragging) {
120         gesture_drag->set_state(Gtk::EVENT_SEQUENCE_DENIED);
121     }
122     else {
123         gesture_drag_offset_orig = offset;
124         gesture_drag->set_state(Gtk::EVENT_SEQUENCE_CLAIMED);
125     }
126 }
drag_gesture_update_cb(GdkEventSequence * seq)127 void CanvasGL::drag_gesture_update_cb(GdkEventSequence *seq)
128 {
129     double x, y;
130     if (gesture_drag->get_offset(x, y)) {
131         offset = gesture_drag_offset_orig + Coordf(x, y);
132         update_viewmat();
133     }
134 }
135 
on_size_allocate(Gtk::Allocation & alloc)136 void CanvasGL::on_size_allocate(Gtk::Allocation &alloc)
137 {
138     m_width = alloc.get_width();
139     m_height = alloc.get_height();
140 
141     screenmat = glm::scale(glm::translate(glm::mat3(1), glm::vec2(-1, 1)), glm::vec2(2.0 / m_width, -2.0 / m_height));
142 
143     needs_resize = true;
144 
145     // chain up
146     Gtk::GLArea::on_size_allocate(alloc);
147 }
148 
resize_buffers()149 void CanvasGL::resize_buffers()
150 {
151     GLint rb;
152     GLint samples = gl_clamp_samples(appearance.msaa);
153     glGetIntegerv(GL_RENDERBUFFER_BINDING, &rb); // save rb
154     glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
155     glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, m_width * get_scale_factor(),
156                                      m_height * get_scale_factor());
157     glBindRenderbuffer(GL_RENDERBUFFER, stencilrenderbuffer);
158     glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, m_width * get_scale_factor(),
159                                      m_height * get_scale_factor());
160     glBindRenderbuffer(GL_RENDERBUFFER, rb);
161 }
162 
on_realize()163 void CanvasGL::on_realize()
164 {
165     Gtk::GLArea::on_realize();
166     make_current();
167     update_viewmat();
168     GL_CHECK_ERROR
169     grid.realize();
170     GL_CHECK_ERROR
171     drag_selection.realize();
172     GL_CHECK_ERROR
173     selectables_renderer.realize();
174     GL_CHECK_ERROR
175     triangle_renderer.realize();
176     GL_CHECK_ERROR
177     marker_renderer.realize();
178     GL_CHECK_ERROR
179     picture_renderer.realize();
180     GL_CHECK_ERROR
181 
182     GLint fb;
183     glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb); // save fb
184 
185     glGenRenderbuffers(1, &renderbuffer);
186     glGenRenderbuffers(1, &stencilrenderbuffer);
187 
188     resize_buffers();
189 
190     GL_CHECK_ERROR
191 
192     glGenFramebuffers(1, &fbo);
193     glBindFramebuffer(GL_FRAMEBUFFER, fbo);
194 
195     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
196     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencilrenderbuffer);
197 
198     GL_CHECK_ERROR
199 
200     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
201         Gtk::MessageDialog md("Error setting up framebuffer, will now exit", false /* use_markup */, Gtk::MESSAGE_ERROR,
202                               Gtk::BUTTONS_OK);
203         md.run();
204     }
205     glBindFramebuffer(GL_FRAMEBUFFER, fb);
206 
207     GL_CHECK_ERROR
208 }
209 
on_render(const Glib::RefPtr<Gdk::GLContext> & context)210 bool CanvasGL::on_render(const Glib::RefPtr<Gdk::GLContext> &context)
211 {
212     if (needs_push) {
213         push();
214         needs_push = false;
215     }
216     if (needs_resize) {
217         resize_buffers();
218         needs_resize = false;
219     }
220 
221     GLint fb;
222     glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb); // save fb
223 
224     glBindFramebuffer(GL_FRAMEBUFFER, fbo);
225 
226     GL_CHECK_ERROR
227     {
228         auto bgcolor = get_color(ColorP::BACKGROUND);
229         glClearColor(bgcolor.r, bgcolor.g, bgcolor.b, 1.0);
230     }
231     glClear(GL_COLOR_BUFFER_BIT);
232     GL_CHECK_ERROR
233     glEnable(GL_BLEND);
234     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
235     picture_renderer.render(false);
236     GL_CHECK_ERROR
237     grid.render();
238     GL_CHECK_ERROR
239     triangle_renderer.render();
240     GL_CHECK_ERROR
241     picture_renderer.render(true);
242     GL_CHECK_ERROR
243     selectables_renderer.render();
244     GL_CHECK_ERROR
245     drag_selection.render();
246     GL_CHECK_ERROR
247     grid.render_cursor(cursor_pos_grid);
248     marker_renderer.render();
249     glDisable(GL_BLEND);
250 
251     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb);
252     glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
253     glBlitFramebuffer(0, 0, m_width * get_scale_factor(), m_height * get_scale_factor(), 0, 0,
254                       m_width * get_scale_factor(), m_height * get_scale_factor(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
255 
256     glBindFramebuffer(GL_FRAMEBUFFER, fb);
257 
258     GL_CHECK_ERROR
259 
260     glFlush();
261     GL_CHECK_ERROR
262     return Gtk::GLArea::on_render(context);
263 }
264 
on_button_press_event(GdkEventButton * button_event)265 bool CanvasGL::on_button_press_event(GdkEventButton *button_event)
266 {
267     grab_focus();
268     pan_drag_begin(button_event);
269     drag_selection.drag_begin(button_event);
270     cursor_move((GdkEvent *)button_event);
271     return Gtk::GLArea::on_button_press_event(button_event);
272 }
273 
on_motion_notify_event(GdkEventMotion * motion_event)274 bool CanvasGL::on_motion_notify_event(GdkEventMotion *motion_event)
275 {
276     bool steal = true;
277     s_signal_can_steal_focus.emit(steal);
278     if (steal)
279         grab_focus();
280     pan_drag_move(motion_event);
281     cursor_move((GdkEvent *)motion_event);
282     drag_selection.drag_move(motion_event);
283     hover_prelight_update((GdkEvent *)motion_event);
284     return Gtk::GLArea::on_motion_notify_event(motion_event);
285 }
286 
update_cursor_pos(double x,double y)287 void CanvasGL::update_cursor_pos(double x, double y)
288 {
289     GdkEventMotion motion_event;
290     motion_event.type = GDK_MOTION_NOTIFY;
291     motion_event.x = x;
292     motion_event.y = y;
293     cursor_move((GdkEvent *)&motion_event);
294 }
295 
on_button_release_event(GdkEventButton * button_event)296 bool CanvasGL::on_button_release_event(GdkEventButton *button_event)
297 {
298     pan_drag_end(button_event);
299     drag_selection.drag_end(button_event);
300     return Gtk::GLArea::on_button_release_event(button_event);
301 }
302 
on_scroll_event(GdkEventScroll * scroll_event)303 bool CanvasGL::on_scroll_event(GdkEventScroll *scroll_event)
304 {
305 #if GTK_CHECK_VERSION(3, 22, 0)
306     auto *dev = gdk_event_get_source_device((GdkEvent *)scroll_event);
307     auto src = gdk_device_get_source(dev);
308     if (src == GDK_SOURCE_TRACKPOINT || (src == GDK_SOURCE_TOUCHPAD && touchpad_pan)) {
309         if (scroll_event->state & GDK_CONTROL_MASK) {
310             pan_zoom(scroll_event, false);
311         }
312         else {
313             pan_drag_move(scroll_event);
314         }
315     }
316     else {
317         pan_zoom(scroll_event);
318     }
319 #else
320     pan_zoom(scroll_event);
321 #endif
322 
323     return Gtk::GLArea::on_scroll_event(scroll_event);
324 }
325 
snap_to_grid(const Coordi & c,unsigned int div) const326 Coordi CanvasGL::snap_to_grid(const Coordi &c, unsigned int div) const
327 {
328     auto c2 = c - grid.origin;
329     int64_t xi = round_multiple(c2.x, grid.spacing.x / div);
330     int64_t yi = round_multiple(c2.y, grid.spacing.y / div);
331     return grid.origin + Coordi(xi, yi);
332 }
333 
get_last_grid_div() const334 int CanvasGL::get_last_grid_div() const
335 {
336     return last_grid_div;
337 }
338 
cursor_move(GdkEvent * motion_event)339 void CanvasGL::cursor_move(GdkEvent *motion_event)
340 {
341     gdouble x, y;
342     gdk_event_get_coords(motion_event, &x, &y);
343     cursor_pos = screen2canvas(Coordf(x, y));
344 
345     GdkModifierType state;
346     gdk_event_get_state(motion_event, &state);
347     last_grid_div = 1;
348     if (state & grid_fine_modifier) {
349         last_grid_div = 10;
350     }
351 
352     if (cursor_external) {
353         Coordi c(cursor_pos.x, cursor_pos.y);
354         s_signal_cursor_moved.emit(c);
355         return;
356     }
357 
358     Coordi t = snap_to_grid(Coordi(cursor_pos.x, cursor_pos.y), last_grid_div);
359 
360     const auto &f = std::find_if(targets.begin(), targets.end(), [t, this](const auto &a) -> bool {
361         return a.p == t && this->layer_is_visible(a.layer) && can_snap_to_target(a);
362     });
363     if (f != targets.end()) {
364         target_current = *f;
365     }
366     else {
367         target_current = Target();
368     }
369 
370     if (snap_to_targets) {
371         auto dfn = [this](const Target &ta) -> float {
372             // return inf if target in selection and tool active (selection not
373             // allowed)
374             if (!layer_is_visible(ta.layer))
375                 return INFINITY;
376 
377             if (!selection_allowed && !can_snap_to_target(ta))
378                 return INFINITY;
379             else
380                 return (cursor_pos - (Coordf)ta.p).mag_sq();
381         };
382 
383         auto mi = std::min_element(targets.cbegin(), targets.cend(),
384                                    [dfn](const auto &a, const auto &b) { return dfn(a) < dfn(b); });
385         if (mi != targets.cend()) {
386             auto d = sqrt(dfn(*mi));
387             if (d < appearance.snap_radius / scale) {
388                 target_current = *mi;
389                 t = mi->p;
390             }
391         }
392     }
393 
394     if (cursor_pos_grid != t) {
395         s_signal_cursor_moved.emit(t);
396     }
397 
398     cursor_pos_grid = t;
399     queue_draw();
400 }
401 
request_push()402 void CanvasGL::request_push()
403 {
404     needs_push = true;
405     push_filter = PF_ALL;
406     queue_draw();
407 }
408 
request_push(PushFilter filter)409 void CanvasGL::request_push(PushFilter filter)
410 {
411     needs_push = true;
412     push_filter = static_cast<PushFilter>(push_filter | filter);
413     queue_draw();
414 }
415 
center_and_zoom(const Coordf & center,float sc)416 void CanvasGL::center_and_zoom(const Coordf &center, float sc)
417 {
418     // we want center to be at width, height/2
419     if (sc > 0)
420         scale = sc;
421     const Coordf m(m_width / 2, m_height / 2);
422     const auto c = canvas2screen(center);
423     offset -= (c - m);
424     update_viewmat();
425     s_signal_scale_changed.emit();
426 }
427 
zoom_to_bbox(const Coordf & a_a,const Coordf & a_b)428 void CanvasGL::zoom_to_bbox(const Coordf &a_a, const Coordf &a_b)
429 {
430     Placement tr;
431     tr.set_angle_rad(view_angle);
432     auto [a, b] = tr.transform_bb(std::make_pair(a_a, a_b));
433     auto sc_x = m_width / abs(a.x - b.x);
434     auto sc_y = m_height / abs(a.y - b.y);
435     scale = std::min(sc_x, sc_y);
436     auto center = (a_a + a_b) / 2;
437     update_viewmat();
438     center_and_zoom(center, scale);
439 }
440 
zoom_to_bbox(const std::pair<Coordf,Coordf> & bb)441 void CanvasGL::zoom_to_bbox(const std::pair<Coordf, Coordf> &bb)
442 {
443     zoom_to_bbox(bb.first, bb.second);
444 }
445 
zoom_to(const Coordf & c,float inc)446 void CanvasGL::zoom_to(const Coordf &c, float inc)
447 {
448     if (!can_set_scale())
449         return;
450 
451     if (smooth_zoom)
452         start_smooth_zoom(c, inc);
453     else
454         set_scale(c.x, c.y, scale * pow(zoom_base, inc));
455 }
456 
ensure_min_size(float w,float h)457 void CanvasGL::ensure_min_size(float w, float h)
458 {
459     auto sc_x = m_width / w;
460     auto sc_y = m_height / h;
461     auto sc = std::min(sc_x, sc_y);
462     if (sc < scale) {
463         auto p = get_cursor_pos_win();
464         set_scale(p.x, p.y, sc);
465     }
466 }
467 
update_viewmat()468 void CanvasGL::update_viewmat()
469 {
470     auto scale_x = scale;
471     if (flip_view)
472         scale_x = -scale;
473     viewmat = glm::scale(glm::rotate(glm::translate(glm::mat3(1), glm::vec2(offset.x, offset.y)), -view_angle),
474                          glm::vec2(scale_x, -scale));
475     viewmat_noflip = glm::scale(glm::translate(glm::mat3(1), glm::vec2(offset.x, offset.y)), glm::vec2(scale, -scale));
476     queue_draw();
477 }
478 
push()479 void CanvasGL::push()
480 {
481     GL_CHECK_ERROR
482     if (push_filter & PF_SELECTABLES)
483         selectables_renderer.push();
484     GL_CHECK_ERROR
485     if (push_filter & PF_TRIANGLES)
486         triangle_renderer.push();
487     if (push_filter & PF_MARKER)
488         marker_renderer.push();
489     if (push_filter & PF_DRAG_SELECTION)
490         drag_selection.push();
491     picture_renderer.push();
492     push_filter = PF_NONE;
493     GL_CHECK_ERROR
494 }
495 
update_markers()496 void CanvasGL::update_markers()
497 {
498     marker_renderer.update();
499     request_push(PF_MARKER);
500 }
501 
append_target(const Target & trg)502 void CanvasGL::append_target(const Target &trg)
503 {
504     targets.emplace_back(trg);
505 }
506 
set_flip_view(bool fl)507 void CanvasGL::set_flip_view(bool fl)
508 {
509     auto toggled = fl != flip_view;
510     flip_view = fl;
511     if (toggled) {
512         // mirror offset at vertical (angle 0) line at view angle through viewport center
513         const Coordf m(m_width / 2, m_height / 2);
514         const Coordf p = offset;
515         const Coordf l(sin(view_angle), cos(view_angle));
516         const auto u = l.dot(p) - l.dot(m);
517         const auto pc = m + l * u;
518         const auto pm = pc * 2 - p;
519         offset = pm;
520     }
521     update_viewmat();
522 }
523 
set_view_angle(float angle)524 void CanvasGL::set_view_angle(float angle)
525 {
526     const auto delta = angle - view_angle;
527     const Coordf m(m_width / 2, m_height / 2);
528     const auto o = offset - m;
529     const auto o2 = o.rotate(-delta);
530     offset += (o2 - o);
531     view_angle = angle;
532     update_viewmat();
533 }
534 
get_flip_view() const535 bool CanvasGL::get_flip_view() const
536 {
537     return flip_view;
538 }
539 
get_view_angle() const540 float CanvasGL::get_view_angle() const
541 {
542     return view_angle;
543 }
544 
screen2canvas(const Coordf & p) const545 Coordf CanvasGL::screen2canvas(const Coordf &p) const
546 {
547     auto cp = glm::inverse(viewmat) * glm::vec3(p.x, p.y, 1);
548     return {cp.x, cp.y};
549 }
550 
canvas2screen(const Coordf & p) const551 Coordf CanvasGL::canvas2screen(const Coordf &p) const
552 {
553     auto cp = viewmat * glm::vec3(p.x, p.y, 1);
554     return {cp.x, cp.y};
555 }
556 
get_selection()557 std::set<SelectableRef> CanvasGL::get_selection()
558 {
559     std::set<SelectableRef> r;
560     unsigned int i = 0;
561     for (const auto it : selectables.items) {
562         if (it.get_flag(horizon::Selectable::Flag::SELECTED)) {
563             r.emplace(selectables.items_ref.at(i));
564         }
565         i++;
566     }
567     return r;
568 }
569 
set_selection(const std::set<SelectableRef> & sel,bool emit)570 void CanvasGL::set_selection(const std::set<SelectableRef> &sel, bool emit)
571 {
572     for (auto &it : selectables.items) {
573         it.set_flag(horizon::Selectable::Flag::SELECTED, false);
574         it.set_flag(horizon::Selectable::Flag::PRELIGHT, false);
575     }
576     for (const auto &it : sel) {
577         SelectableRef key(it.uuid, it.type, it.vertex);
578         if (selectables.items_map.count(key)) {
579             selectables.items.at(selectables.items_map.at(key)).set_flag(horizon::Selectable::Flag::SELECTED, true);
580         }
581     }
582     selectables.update_preview(sel);
583     if (emit)
584         s_signal_selection_changed.emit();
585     request_push(PF_SELECTABLES);
586 }
587 
select_all()588 void CanvasGL::select_all()
589 {
590     size_t i = 0;
591     for (auto &it : selectables.items) {
592         const auto &r = selectables.items_ref.at(i);
593         it.set_flag(horizon::Selectable::Flag::SELECTED, selection_filter.can_select(r));
594         it.set_flag(horizon::Selectable::Flag::PRELIGHT, false);
595         i++;
596     }
597     s_signal_selection_changed.emit();
598     request_push(PF_SELECTABLES);
599 }
600 
get_selection_at(const Coordi & c)601 std::set<SelectableRef> CanvasGL::get_selection_at(const Coordi &c)
602 {
603 
604     std::list<std::pair<const Selectable &, const SelectableRef &>> sel;
605     unsigned int i = 0;
606     for (auto &it : selectables.items) {
607         const auto &sr = selectables.items_ref.at(i);
608         if (it.inside(c, appearance.min_selectable_size / scale) && selection_filter.can_select(sr)) {
609             // in_selection.insert(selectables.items_ref[i]);
610             sel.emplace_back(it, sr);
611         }
612         i++;
613     }
614     auto n_point = std::count_if(sel.begin(), sel.end(), [](const auto &x) { return x.first.is_point(); });
615     auto n_line = std::count_if(sel.begin(), sel.end(), [](const auto &x) { return x.first.is_line(); });
616     if (n_point == 1) { // only one point, select that one
617         auto r = std::find_if(sel.begin(), sel.end(), [](const auto &x) { return x.first.is_point(); });
618         assert(r != sel.end());
619         return {r->second};
620     }
621     else if (n_line == 1 && n_point == 0) { // only one line, select that one
622         auto r = std::find_if(sel.begin(), sel.end(), [](const auto &x) { return x.first.is_line(); });
623         assert(r != sel.end());
624         return {r->second};
625     }
626     else {
627         std::set<SelectableRef> in_selection;
628         for (const auto &[s, sr] : sel) {
629             in_selection.emplace(sr);
630         }
631         return in_selection;
632     }
633 }
634 
inhibit_drag_selection()635 void CanvasGL::inhibit_drag_selection()
636 {
637     drag_selection_inhibited = true;
638 }
639 
set_selection_mode(SelectionMode mode)640 void CanvasGL::set_selection_mode(SelectionMode mode)
641 {
642     selection_mode = mode;
643     s_signal_selection_mode_changed.emit(mode);
644 }
645 
get_selection_mode() const646 CanvasGL::SelectionMode CanvasGL::get_selection_mode() const
647 {
648     return selection_mode;
649 }
650 
set_highlight_mode(CanvasGL::HighlightMode mode)651 void CanvasGL::set_highlight_mode(CanvasGL::HighlightMode mode)
652 {
653     highlight_mode = mode;
654     queue_draw();
655 }
656 
set_highlight_enabled(bool v)657 void CanvasGL::set_highlight_enabled(bool v)
658 {
659     highlight_enabled = v;
660     queue_draw();
661 }
662 
set_highlight_on_top(bool v)663 void CanvasGL::set_highlight_on_top(bool v)
664 {
665     highlight_on_top = v;
666     queue_draw();
667 }
668 
set_layer_mode(CanvasGL::LayerMode mode)669 void CanvasGL::set_layer_mode(CanvasGL::LayerMode mode)
670 {
671     layer_mode = mode;
672     queue_draw();
673 }
674 
set_appearance(const Appearance & a)675 void CanvasGL::set_appearance(const Appearance &a)
676 {
677     appearance = a;
678     layer_colors = appearance.layer_colors;
679     switch (a.grid_fine_modifier) {
680     case Appearance::GridFineModifier::ALT:
681         grid_fine_modifier = Gdk::MOD1_MASK;
682         break;
683     case Appearance::GridFineModifier::CTRL:
684         grid_fine_modifier = Gdk::CONTROL_MASK;
685         break;
686     }
687     switch (a.grid_style) {
688     case Appearance::GridStyle::CROSS:
689         grid.mark_size = 5.5;
690         break;
691     case Appearance::GridStyle::DOT:
692         grid.mark_size = 1;
693         break;
694     case Appearance::GridStyle::GRID:
695         grid.mark_size = 2000;
696         break;
697     }
698     set_cursor_size(a.cursor_size);
699     needs_resize = true;
700     queue_draw();
701 }
702 
set_cursor_size(float size)703 void CanvasGL::set_cursor_size(float size)
704 {
705     cursor_size = size;
706     queue_draw();
707 }
708 
set_cursor_size(Appearance::CursorSize csize)709 void CanvasGL::set_cursor_size(Appearance::CursorSize csize)
710 {
711     float size = 20;
712     switch (csize) {
713     case Appearance::CursorSize::DEFAULT:
714         size = 20;
715         break;
716     case Appearance::CursorSize::LARGE:
717         size = 40;
718         break;
719     case Appearance::CursorSize::FULL:
720         size = -1;
721         break;
722     }
723     set_cursor_size(size);
724 }
725 
726 const Color default_color(1, 0, 1);
727 
get_color(ColorP colorp) const728 const Color &CanvasGL::get_color(ColorP colorp) const
729 {
730     if (appearance.colors.count(colorp))
731         return appearance.colors.at(colorp);
732     else
733         return default_color;
734 }
735 
set_selection_allowed(bool a)736 void CanvasGL::set_selection_allowed(bool a)
737 {
738     selection_allowed = a;
739 }
740 
set_cursor_pos(const Coordi & c)741 void CanvasGL::set_cursor_pos(const Coordi &c)
742 {
743     if (cursor_external) {
744         cursor_pos_grid = c;
745         queue_draw();
746     }
747 }
748 
set_cursor_external(bool v)749 void CanvasGL::set_cursor_external(bool v)
750 {
751     cursor_external = v;
752 }
753 
get_cursor_pos()754 Coordi CanvasGL::get_cursor_pos()
755 {
756     if (cursor_external)
757         return Coordi(cursor_pos.x, cursor_pos.y);
758     else
759         return cursor_pos_grid;
760 }
761 
get_cursor_pos_win()762 Coordf CanvasGL::get_cursor_pos_win()
763 {
764     auto cp = viewmat * glm::vec3(cursor_pos.x, cursor_pos.y, 1);
765     return {cp.x, cp.y};
766 }
767 
get_current_target()768 Target CanvasGL::get_current_target()
769 {
770     return target_current;
771 }
772 
clear()773 void CanvasGL::clear()
774 {
775     Canvas::clear();
776     request_push();
777 }
778 
can_snap_to_target(const Target & t) const779 bool CanvasGL::can_snap_to_target(const Target &t) const
780 {
781     SnapFilter k(t.type, t.path.at(0), t.vertex);
782     if (snap_filter.count(k))
783         return false;
784     k.vertex = -1;
785     if (snap_filter.count(k))
786         return false;
787     if (t.type == ObjectType::POLYGON_ARC_CENTER || t.type == ObjectType::POLYGON_EDGE
788         || t.type == ObjectType::POLYGON_VERTEX) {
789         k.type = ObjectType::POLYGON;
790         if (snap_filter.count(k))
791             return false;
792     }
793     return true;
794 }
795 
layer_is_visible(int layer) const796 bool CanvasGL::layer_is_visible(int layer) const
797 {
798     if (layer_mode == LayerMode::AS_IS)
799         return Canvas::layer_is_visible(layer);
800     else
801         return layer == work_layer || layer >= 10000;
802 }
803 
layer_is_visible(LayerRange layer) const804 bool CanvasGL::layer_is_visible(LayerRange layer) const
805 {
806     if (layer_mode == LayerMode::AS_IS)
807         return Canvas::layer_is_visible(layer);
808     else
809         return layer.overlaps(work_layer) || layer.start() >= 10000;
810 }
811 
set_colors2(const std::vector<ColorI> & c)812 void CanvasGL::set_colors2(const std::vector<ColorI> &c)
813 {
814     colors2 = c;
815     request_push();
816 }
817 
818 
819 static const float char_space = 1;
820 
draw_bitmap_text(const Coordf & p,float sc,const std::string & rtext,int angle,ColorP color,int layer)821 void CanvasGL::draw_bitmap_text(const Coordf &p, float sc, const std::string &rtext, int angle, ColorP color, int layer)
822 {
823     Glib::ustring text(rtext);
824     auto smooth_px = bitmap_font::get_smooth_pixels();
825     Coordf point = p;
826     Placement tr;
827     tr.set_angle(angle);
828     Coordf v = tr.transform(Coordf(1, 0));
829     for (auto codepoint : text) {
830         if (codepoint != ' ') {
831             auto info = bitmap_font::get_glyph_info(codepoint);
832             if (!info.is_valid()) {
833                 info = bitmap_font::get_glyph_info('?');
834             }
835 
836             unsigned int glyph_x = info.atlas_x + smooth_px;
837             unsigned int glyph_y = info.atlas_y + smooth_px;
838             unsigned int glyph_w = info.atlas_w - smooth_px * 2;
839             unsigned int glyph_h = info.atlas_h - smooth_px * 2;
840             float aspect = (1.0 * glyph_h) / glyph_w;
841 
842             unsigned int bits =
843                     (glyph_h & 0x3f) | ((glyph_w & 0x3f) << 6) | ((glyph_y & 0x3ff) << 12) | ((glyph_x & 0x3ff) << 22);
844             auto fl = reinterpret_cast<const float *>(&bits);
845 
846             Coordf shift(info.minx, info.miny);
847 
848             add_triangle(layer, point + tr.transform(shift) * 1e6 * sc, v * (glyph_w * 1e6 * sc), Coordf(aspect, *fl),
849                          color, TriangleInfo::FLAG_GLYPH);
850             point += v * (info.advance * char_space * 1e6 * sc);
851         }
852         else {
853             point += v * (7 * char_space * 1e6 * sc);
854         }
855     }
856 }
857 
measure_bitmap_text(const std::string & rtext) const858 std::pair<Coordf, Coordf> CanvasGL::measure_bitmap_text(const std::string &rtext) const
859 {
860     std::pair<Coordf, Coordf> r;
861     Glib::ustring text(rtext);
862     auto smooth_px = bitmap_font::get_smooth_pixels();
863     Coordf point;
864     for (auto codepoint : text) {
865         auto info = bitmap_font::get_glyph_info(codepoint);
866         if (!info.is_valid()) {
867             info = bitmap_font::get_glyph_info('?');
868         }
869         Coordf p0(info.minx, info.miny);
870         Coordf p1(info.atlas_w - smooth_px * 2, info.atlas_h - smooth_px * 2);
871         p1 += p0;
872         r.first = Coordf::min(r.first, p0 + point);
873         r.second = Coordf::max(r.second, p1 + point);
874 
875         point.x += info.advance * char_space;
876     }
877     r.first *= 1e6;
878     r.second *= 1e6;
879     return r;
880 }
881 
882 
draw_bitmap_text_box(const Placement & q,float width,float height,const std::string & s,ColorP color,int layer,TextBoxMode mode)883 void CanvasGL::draw_bitmap_text_box(const Placement &q, float width, float height, const std::string &s, ColorP color,
884                                     int layer, TextBoxMode mode)
885 {
886     Placement p = q;
887     if (p.mirror)
888         p.invert_angle();
889     p.mirror = false;
890     if (height > width) {
891         std::swap(height, width);
892         p.inc_angle_deg(90);
893     }
894     if (height / width > .9) { // almost square
895         while (p.get_angle() >= 16384) {
896             std::swap(height, width);
897             p.inc_angle_deg(90);
898         }
899     }
900 
901     auto text_bb = measure_bitmap_text(s);
902     float scale_x = width / (text_bb.second.x - text_bb.first.x);
903     float scale_y = height / ((text_bb.second.y - text_bb.first.y));
904     if (mode != TextBoxMode::FULL)
905         scale_y /= 2;
906     float sc = std::min(scale_x, scale_y) * .75;
907 
908     const float text_height = (text_bb.second.y - text_bb.first.y) * sc;
909     const float text_width = (text_bb.second.x - text_bb.first.x) * sc;
910 
911     Coordf text_pos(-width / 2, 0);
912     if (mode == TextBoxMode::UPPER)
913         text_pos.y = height / 4;
914     else if (mode == TextBoxMode::LOWER)
915         text_pos.y = -height / 4;
916     text_pos.y -= text_bb.first.y * sc;
917     text_pos.y -= text_height / 2;
918 
919     text_pos.x += width / 2 - text_width / 2;
920 
921     begin_group(layer);
922     if (p.get_angle() > 16384 && p.get_angle() <= 49152) {
923         text_pos.x *= -1;
924         text_pos.y *= -1;
925         draw_bitmap_text(p.transform(text_pos), sc, s, p.get_angle() + 32768, color, layer);
926     }
927     else {
928 
929         draw_bitmap_text(p.transform(text_pos), sc, s, p.get_angle(), color, layer);
930     }
931     end_group();
932 };
933 
934 // copied from
935 // https://github.com/solvespace/solvespace/blob/master/src/platform/gtkmain.cpp#L357
936 // thanks to whitequark for running into this issue as well
937 
938 // Work around a bug fixed in GTKMM 3.22:
939 // https://mail.gnome.org/archives/gtkmm-list/2016-April/msg00020.html
on_create_context()940 Glib::RefPtr<Gdk::GLContext> CanvasGL::on_create_context()
941 {
942     return get_window()->create_gl_context();
943 }
944 } // namespace horizon
945