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 ¢er, 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