1 #include "3d_view.hpp"
2 #include "canvas3d/canvas3d.hpp"
3 #include "import_step/import.hpp"
4 #include "util/gtk_util.hpp"
5 #include "board/board.hpp"
6 #include "pool/part.hpp"
7 #include "util/str_util.hpp"
8 #include "axes_lollipop.hpp"
9 #include "imp/action_catalog.hpp"
10 #include "imp/actions.hpp"
11 #include "preferences/preferences.hpp"
12
13 namespace horizon {
14
create(const class Board & b,class IPool & p,Mode m,Canvas3D * ca_custom)15 View3DWindow *View3DWindow::create(const class Board &b, class IPool &p, Mode m, Canvas3D *ca_custom)
16 {
17 View3DWindow *w;
18 Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
19 x->add_from_resource("/org/horizon-eda/horizon/imp/3d/3d_view.ui");
20 x->get_widget_derived("window", w, b, p, m, ca_custom);
21
22 return w;
23 }
24
bind_color_button(Gtk::ColorButton * color_button,FnSetColor fn_set,std::function<void (void)> extra_fn)25 void View3DWindow::bind_color_button(Gtk::ColorButton *color_button, FnSetColor fn_set,
26 std::function<void(void)> extra_fn)
27 {
28 color_button->property_color().signal_changed().connect([this, color_button, fn_set, extra_fn] {
29 auto co = color_button->get_color();
30 Color color;
31 color.r = co.get_red_p();
32 color.g = co.get_green_p();
33 color.b = co.get_blue_p();
34 std::invoke(fn_set, canvas, color);
35 extra_fn();
36 });
37 }
38
39 struct ViewInfo {
40 public:
41 ActionID action;
42 std::string icon;
43 std::string tooltip;
44 float azimuth;
45 float elevation;
46 };
47
48 static const std::vector<ViewInfo> views = {
49 {ActionID::VIEW_3D_FRONT, "front", "Front", 270., 0.},
50 {ActionID::VIEW_3D_BACK, "back", "Back", 90., 0.},
51 {ActionID::VIEW_3D_TOP, "top", "Top", 270., 89.99},
52 {ActionID::VIEW_3D_BOTTOM, "bottom", "Bottom", 270., -89.99},
53 {ActionID::VIEW_3D_RIGHT, "right", "Right", 0., 0.},
54 {ActionID::VIEW_3D_LEFT, "left", "Left", 180., 0.},
55 };
56
View3DWindow(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,const class Board & bo,class IPool & p,Mode md,Canvas3D * ca_custom)57 View3DWindow::View3DWindow(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, const class Board &bo,
58 class IPool &p, Mode md, Canvas3D *ca_custom)
59 : Gtk::Window(cobject), board(bo), pool(p), mode(md), state_store(this, "imp-board-3d")
60 {
61 Gtk::Box *gl_box;
62 GET_WIDGET(gl_box);
63
64 if (ca_custom)
65 canvas = ca_custom;
66 else
67 canvas = Gtk::manage(new Canvas3D);
68 gl_box->pack_start(*canvas, true, true, 0);
69 canvas->show();
70
71 Gtk::Revealer *revealer;
72 GET_WIDGET(revealer);
73 Gtk::ToggleButton *settings_button;
74 GET_WIDGET(settings_button);
75 settings_button->signal_toggled().connect(
76 [settings_button, revealer] { revealer->set_reveal_child(settings_button->get_active()); });
77
78 Gtk::Button *update_button;
79 GET_WIDGET(update_button);
80 update_button->signal_clicked().connect([this] { update(); });
81
82 {
83 Gtk::Box *view_buttons_box;
84 GET_WIDGET(view_buttons_box);
85
86 for (const auto &it : views) {
87 auto b = Gtk::manage(new Gtk::Button);
88 b->set_image_from_icon_name("view-3d-" + it.icon + "-symbolic", Gtk::ICON_SIZE_BUTTON);
89 b->set_tooltip_text(it.tooltip);
90 b->show();
91 float az = it.azimuth;
92 float el = it.elevation;
93 b->signal_clicked().connect([this, az, el] {
94 canvas->set_cam_azimuth(az);
95 canvas->set_cam_elevation(el);
96 });
97 view_buttons_box->pack_start(*b, false, false, 0);
98 }
99 }
100
101 Gtk::Button *rotate_left_button;
102 Gtk::Button *rotate_right_button;
103 GET_WIDGET(rotate_left_button);
104 GET_WIDGET(rotate_right_button);
105 rotate_left_button->signal_clicked().connect([this] { canvas->inc_cam_azimuth(-90); });
106 rotate_right_button->signal_clicked().connect([this] { canvas->inc_cam_azimuth(90); });
107
108 Gtk::Button *view_all_button;
109 GET_WIDGET(view_all_button);
110 view_all_button->signal_clicked().connect([this] { canvas->view_all(); });
111
112 auto explode_adj = Glib::RefPtr<Gtk::Adjustment>::cast_dynamic(x->get_object("explode_adj"));
113 explode_adj->signal_value_changed().connect([explode_adj, this] { canvas->set_explode(explode_adj->get_value()); });
114
115 GET_WIDGET(background_top_color_button);
116 bind_color_button(background_top_color_button, &Canvas3D::set_background_top_color, [this] {
117 if (!setting_background_color_from_preset && background_color_preset_combo)
118 background_color_preset_combo->set_active(-1);
119 });
120 background_top_color_button->set_color(Gdk::Color("#333365"));
121
122 GET_WIDGET(background_bottom_color_button);
123 bind_color_button(background_bottom_color_button, &Canvas3D::set_background_bottom_color, [this] {
124 if (!setting_background_color_from_preset && background_color_preset_combo)
125 background_color_preset_combo->set_active(-1);
126 });
127 background_bottom_color_button->set_color(Gdk::Color("#9797aa"));
128
129 Gtk::Switch *solder_mask_switch;
130 GET_WIDGET(solder_mask_switch);
131 solder_mask_switch->property_active().signal_changed().connect(
132 [this, solder_mask_switch] { canvas->set_show_solder_mask(solder_mask_switch->get_active()); });
133
134 GET_WIDGET(solder_mask_color_button);
135 bind_color_button(solder_mask_color_button, &Canvas3D::set_solder_mask_color, [this] { s_signal_changed.emit(); });
136 solder_mask_color_button->set_color(Gdk::Color("#008000"));
137
138
139 Gtk::Switch *silkscreen_switch;
140 GET_WIDGET(silkscreen_switch);
141 silkscreen_switch->property_active().signal_changed().connect(
142 [this, silkscreen_switch] { canvas->set_show_silkscreen(silkscreen_switch->get_active()); });
143
144 GET_WIDGET(silkscreen_color_button);
145 bind_color_button(silkscreen_color_button, &Canvas3D::set_silkscreen_color, [this] { s_signal_changed.emit(); });
146 silkscreen_color_button->set_color(Gdk::Color("#FFFFFF"));
147
148 Gtk::Switch *substrate_switch;
149 GET_WIDGET(substrate_switch);
150 substrate_switch->property_active().signal_changed().connect(
151 [this, substrate_switch] { canvas->set_show_substrate(substrate_switch->get_active()); });
152
153 GET_WIDGET(substrate_color_button);
154 bind_color_button(substrate_color_button, &Canvas3D::set_substrate_color, [this] { s_signal_changed.emit(); });
155 substrate_color_button->set_color(Gdk::Color("#332600"));
156
157 Gtk::Switch *paste_switch;
158 GET_WIDGET(paste_switch);
159 paste_switch->property_active().signal_changed().connect(
160 [this, paste_switch] { canvas->set_show_solder_paste(paste_switch->get_active()); });
161
162 {
163 Gtk::RadioButton *models_none_rb;
164 GET_WIDGET(models_none_rb);
165 models_none_rb->property_active().signal_changed().connect(
166 [this, models_none_rb] { canvas->set_show_models(!models_none_rb->get_active()); });
167 }
168
169 {
170 Gtk::RadioButton *models_placed_rb;
171 GET_WIDGET(models_placed_rb);
172 if (mode == Mode::BOARD) {
173 models_placed_rb->property_active().signal_changed().connect(
174 [this, models_placed_rb] { canvas->set_show_dnp_models(!models_placed_rb->get_active()); });
175 }
176 else {
177 models_placed_rb->hide();
178 }
179 }
180 {
181 Gtk::RadioButton *models_all_rb;
182 GET_WIDGET(models_all_rb);
183 if (mode == Mode::PACKAGE) {
184 models_all_rb->set_active();
185 }
186 }
187
188 {
189 Gtk::RadioButton *copper_on_rb;
190 GET_WIDGET(copper_on_rb);
191 copper_on_rb->property_active().signal_changed().connect([this, copper_on_rb] {
192 if (copper_on_rb->get_active()) {
193 canvas->set_show_copper(true);
194 canvas->set_use_layer_colors(false);
195 }
196 });
197 }
198 {
199 Gtk::RadioButton *copper_layer_colors_rb;
200 GET_WIDGET(copper_layer_colors_rb);
201 copper_layer_colors_rb->property_active().signal_changed().connect([this, copper_layer_colors_rb] {
202 if (copper_layer_colors_rb->get_active()) {
203 canvas->set_show_copper(true);
204 canvas->set_use_layer_colors(true);
205 }
206 });
207 }
208 {
209 Gtk::RadioButton *copper_off_rb;
210 GET_WIDGET(copper_off_rb);
211 copper_off_rb->property_active().signal_changed().connect([this, copper_off_rb] {
212 if (copper_off_rb->get_active()) {
213 canvas->set_show_copper(false);
214 }
215 });
216 }
217
218
219 GET_WIDGET(proj_persp_rb);
220 GET_WIDGET(proj_ortho_rb);
221 proj_persp_rb->signal_toggled().connect([this] {
222 canvas->set_projection(proj_persp_rb->get_active() ? Canvas3D::Projection::PERSP : Canvas3D::Projection::ORTHO);
223 });
224
225 GET_WIDGET(background_color_preset_combo);
226
227 // gradients from https://uigradients.com/
228 static const std::vector<std::pair<std::string, std::pair<Gdk::Color, Gdk::Color>>> background_color_presets = {
229 {"Default", {Gdk::Color("#333365"), Gdk::Color("#9797aa")}},
230 {"Sunset 1", {Gdk::Color("#333365"), Gdk::Color("#B3A26B")}},
231 {"Sunset 2", {Gdk::Color("#35FFEE"), Gdk::Color("#FFC674")}},
232 {"White", {Gdk::Color("#ffffff"), Gdk::Color("#ffffff")}},
233 {"Black", {Gdk::Color("#000000"), Gdk::Color("#000000")}},
234 {"Grey", {Gdk::Color("#808080"), Gdk::Color("#808080")}},
235 {"Honey Dew", {Gdk::Color("#C33764"), Gdk::Color("#F8FFAE")}},
236 {"80s Sunset", {Gdk::Color("#3494E6"), Gdk::Color("#EC6EAD")}},
237 {"Deep Sea Space", {Gdk::Color("#2C3E50"), Gdk::Color("#4CA1AF")}},
238 {"Dark Skies", {Gdk::Color("#4B79A1"), Gdk::Color("#283E51")}},
239 {"Friday", {Gdk::Color("#83a4d4"), Gdk::Color("#b6fbff")}},
240 };
241 for (const auto &it : background_color_presets) {
242 background_color_preset_combo->append(it.first, it.first);
243 }
244 background_color_preset_combo->set_active_id("Default");
245 background_color_preset_combo->signal_changed().connect([this] {
246 std::string id = background_color_preset_combo->get_active_id();
247 for (const auto &it : background_color_presets) {
248 if (it.first == id) {
249 setting_background_color_from_preset = true;
250 background_top_color_button->set_color(it.second.first);
251 background_bottom_color_button->set_color(it.second.second);
252 setting_background_color_from_preset = false;
253 break;
254 }
255 }
256 });
257
258 GET_WIDGET(model_loading_revealer);
259 GET_WIDGET(model_loading_spinner);
260 GET_WIDGET(model_loading_progress);
261 canvas->signal_models_loading().connect([this](unsigned int i, unsigned int n) {
262 bool loading = i < n;
263 model_loading_revealer->set_reveal_child(loading);
264 model_loading_spinner->property_active() = loading;
265 model_loading_progress->set_visible(n > 1);
266 model_loading_progress->set_fraction(i / (n * 1.0));
267 });
268
269 Gtk::ComboBoxText *msaa_combo;
270 GET_WIDGET(msaa_combo);
271 msaa_combo->append("0", "Off");
272 for (int i = 1; i < 5; i *= 2) {
273 msaa_combo->append(std::to_string(i), std::to_string(i) + "× MSAA");
274 }
275 msaa_combo->signal_changed().connect([this, msaa_combo] {
276 int msaa = std::stoi(msaa_combo->get_active_id());
277 canvas->set_msaa(msaa);
278 });
279 msaa_combo->set_active_id("4");
280
281 GET_WIDGET(hud_label);
282 GET_WIDGET(hud_revealer);
283 revealer->signal_size_allocate().connect(
284 [this](Gtk::Allocation &alloc) { hud_revealer->set_margin_start(alloc.get_width() + 50); });
285
286 canvas->signal_package_select().connect([this](const auto &uu) {
287 if (mode == Mode::BOARD) {
288 hud_set_package(uu);
289 if (uu)
290 canvas->set_highlights({uu});
291 else
292 canvas->set_highlights({});
293 }
294 });
295
296 {
297 Gtk::Box *lollipop_box;
298 GET_WIDGET(lollipop_box);
299 auto axes_lollipop = Gtk::manage(new AxesLollipop());
300 axes_lollipop->show();
301 lollipop_box->pack_start(*axes_lollipop, true, true, 0);
302 canvas->signal_view_changed().connect(sigc::track_obj(
303 [this, axes_lollipop] {
304 const float alpha = -glm::radians(canvas->get_cam_azimuth() + 90);
305 const float beta = glm::radians(canvas->get_cam_elevation() - 90);
306 axes_lollipop->set_angles(alpha, beta);
307 },
308 *axes_lollipop));
309 }
310
311 canvas->signal_key_press_event().connect(sigc::mem_fun(*this, &View3DWindow::handle_action_key));
312 signal_key_press_event().connect([this](GdkEventKey *ev) {
313 if (canvas->has_focus()) {
314 return false; // handled by canvas
315 }
316 else {
317 return handle_action_key(ev);
318 }
319 });
320
321 connect_action(ActionID::VIEW_ALL, [this](const auto &conn) { canvas->view_all(); });
322 connect_action(ActionID::PAN_LEFT, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
323 connect_action(ActionID::PAN_RIGHT, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
324 connect_action(ActionID::PAN_UP, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
325 connect_action(ActionID::PAN_DOWN, sigc::mem_fun(*this, &View3DWindow::handle_pan_action));
326
327 connect_action(ActionID::ZOOM_IN, sigc::mem_fun(*this, &View3DWindow::handle_zoom_action));
328 connect_action(ActionID::ZOOM_OUT, sigc::mem_fun(*this, &View3DWindow::handle_zoom_action));
329
330 connect_action(ActionID::ROTATE_VIEW_LEFT, sigc::mem_fun(*this, &View3DWindow::handle_rotate_action));
331 connect_action(ActionID::ROTATE_VIEW_RIGHT, sigc::mem_fun(*this, &View3DWindow::handle_rotate_action));
332
333 connect_action(ActionID::VIEW_3D_FRONT, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
334 connect_action(ActionID::VIEW_3D_BACK, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
335 connect_action(ActionID::VIEW_3D_BOTTOM, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
336 connect_action(ActionID::VIEW_3D_TOP, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
337 connect_action(ActionID::VIEW_3D_LEFT, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
338 connect_action(ActionID::VIEW_3D_RIGHT, sigc::mem_fun(*this, &View3DWindow::handle_view_action));
339
340 connect_action(ActionID::VIEW_3D_ORTHO, sigc::mem_fun(*this, &View3DWindow::handle_proj_action));
341 connect_action(ActionID::VIEW_3D_PERSP, sigc::mem_fun(*this, &View3DWindow::handle_proj_action));
342
343
344 GET_WIDGET(main_box);
345 }
346
add_widget(Gtk::Widget * w)347 void View3DWindow::add_widget(Gtk::Widget *w)
348 {
349 main_box->pack_start(*w, false, false, 0);
350 }
351
update(bool clear)352 void View3DWindow::update(bool clear)
353 {
354 s_signal_request_update.emit();
355 canvas->update(board);
356 if (clear)
357 canvas->clear_3d_models();
358 canvas->load_models_async(pool);
359 }
360
set_highlights(const std::set<UUID> & pkgs)361 void View3DWindow::set_highlights(const std::set<UUID> &pkgs)
362 {
363 canvas->set_highlights(pkgs);
364 if (pkgs.size() == 1) {
365 hud_set_package(*pkgs.begin());
366 }
367 else {
368 hud_set_package(UUID());
369 }
370 }
371
set_solder_mask_color(const Gdk::RGBA & c)372 void View3DWindow::set_solder_mask_color(const Gdk::RGBA &c)
373 {
374 solder_mask_color_button->set_rgba(c);
375 }
376
get_solder_mask_color()377 Gdk::RGBA View3DWindow::get_solder_mask_color()
378 {
379 return solder_mask_color_button->get_rgba();
380 }
381
set_silkscreen_color(const Gdk::RGBA & c)382 void View3DWindow::set_silkscreen_color(const Gdk::RGBA &c)
383 {
384 silkscreen_color_button->set_rgba(c);
385 }
386
get_silkscreen_color()387 Gdk::RGBA View3DWindow::get_silkscreen_color()
388 {
389 return silkscreen_color_button->get_rgba();
390 }
391
set_substrate_color(const Gdk::RGBA & c)392 void View3DWindow::set_substrate_color(const Gdk::RGBA &c)
393 {
394 substrate_color_button->set_rgba(c);
395 }
396
get_substrate_color()397 Gdk::RGBA View3DWindow::get_substrate_color()
398 {
399 return substrate_color_button->get_rgba();
400 }
401
signal_package_select()402 View3DWindow::type_signal_package_select View3DWindow::signal_package_select()
403 {
404 return canvas->signal_package_select();
405 }
406
hud_set_package(const UUID & uu)407 void View3DWindow::hud_set_package(const UUID &uu)
408 {
409 if (!uu || board.packages.count(uu) == 0) {
410 hud_revealer->set_reveal_child(false);
411 return;
412 }
413 const auto &pkg = board.packages.at(uu);
414 if (pkg.component) {
415 hud_revealer->set_reveal_child(true);
416 auto part = pkg.component->part;
417 std::string s = "<b>" + pkg.component->refdes + ":</b> ";
418 s += Glib::Markup::escape_text(part->get_value()) + "\n";
419 if (part->get_value() != part->get_MPN()) {
420 s += "MPN: " + Glib::Markup::escape_text(part->get_MPN()) + "\n";
421 }
422 s += "Manufacturer: " + Glib::Markup::escape_text(part->get_manufacturer()) + "\n";
423 s += "Package: " + Glib::Markup::escape_text(part->package->name) + "\n";
424 if (part->get_description().size())
425 s += Glib::Markup::escape_text(part->get_description()) + "\n";
426 if (part->get_datasheet().size())
427 s += "<a href=\"" + Glib::Markup::escape_text(part->get_datasheet()) + "\" title=\""
428 + Glib::Markup::escape_text(Glib::Markup::escape_text(part->get_datasheet())) + "\">Datasheet</a>\n";
429 trim(s);
430 hud_label->set_markup(s);
431 }
432 }
433
handle_action_key(const GdkEventKey * ev)434 bool View3DWindow::handle_action_key(const GdkEventKey *ev)
435 {
436 if (ev->is_modifier)
437 return false;
438 if (ev->keyval == GDK_KEY_Escape) {
439 if (keys_current.size() == 0) {
440 s_signal_present_imp.emit();
441 return true;
442 }
443 else {
444 keys_current.clear();
445 return true;
446 }
447 }
448 else {
449 auto display = get_display()->gobj();
450 auto hw_keycode = ev->hardware_keycode;
451 auto state = static_cast<GdkModifierType>(ev->state);
452 auto group = ev->group;
453 guint keyval;
454 GdkModifierType consumed_modifiers;
455 if (gdk_keymap_translate_keyboard_state(gdk_keymap_get_for_display(display), hw_keycode, state, group, &keyval,
456 NULL, NULL, &consumed_modifiers)) {
457 auto mod = static_cast<GdkModifierType>((state & (~consumed_modifiers))
458 & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK));
459 keys_current.emplace_back(keyval, mod);
460 }
461 std::map<ActionConnection *, std::pair<KeyMatchResult, KeySequence>> connections_matched;
462 for (auto &it : action_connections) {
463 auto k = std::make_pair(it.second.action_id, it.second.tool_id);
464 if (action_catalog.at(k).availability & ActionCatalogItem::AVAILABLE_IN_3D) {
465 bool can_begin = true;
466 if (can_begin) {
467 for (const auto &it2 : it.second.key_sequences) {
468 if (const auto m = key_sequence_match(keys_current, it2); m != KeyMatchResult::NONE) {
469 connections_matched.emplace(std::piecewise_construct, std::forward_as_tuple(&it.second),
470 std::forward_as_tuple(m, it2));
471 }
472 }
473 }
474 }
475 }
476
477
478 if (connections_matched.size() == 1) {
479 keys_current.clear();
480 auto conn = connections_matched.begin()->first;
481 trigger_action(conn->action_id);
482 return true;
483 }
484
485 else if (connections_matched.size() > 1) { // still ambigous
486 std::list<std::pair<std::string, KeySequence>> conflicts;
487 bool have_conflict = false;
488 for (const auto &[conn, it] : connections_matched) {
489 const auto &[res, seq] = it;
490 if (res == KeyMatchResult::COMPLETE) {
491 have_conflict = true;
492 }
493 conflicts.emplace_back(action_catalog.at(std::make_pair(conn->action_id, conn->tool_id)).name, seq);
494 }
495 if (have_conflict) {
496 keys_current.clear();
497 return false;
498 }
499 return true;
500 }
501 else if (connections_matched.size() == 0) {
502 keys_current.clear();
503 return false;
504 }
505 else {
506 return false;
507 }
508 }
509 return false;
510 }
511
trigger_action(ActionID action)512 void View3DWindow::trigger_action(ActionID action)
513 {
514 auto conn = action_connections.at(action);
515 conn.cb(conn);
516 }
517
connect_action(ActionID action_id,std::function<void (const ActionConnection &)> cb)518 ActionConnection &View3DWindow::connect_action(ActionID action_id, std::function<void(const ActionConnection &)> cb)
519 {
520 if (action_connections.count(action_id)) {
521 throw std::runtime_error("duplicate action");
522 }
523 if (action_catalog.count(make_action(action_id)) == 0) {
524 throw std::runtime_error("invalid action");
525 }
526 auto &act = action_connections
527 .emplace(std::piecewise_construct, std::forward_as_tuple(action_id),
528 std::forward_as_tuple(make_action(action_id), cb))
529 .first->second;
530
531 return act;
532 }
533
apply_preferences(const Preferences & prefs)534 void View3DWindow::apply_preferences(const Preferences &prefs)
535 {
536 canvas->smooth_zoom = prefs.zoom.smooth_zoom_3d;
537 canvas->set_appearance(prefs.canvas_layer.appearance);
538 const auto av = ActionCatalogItem::AVAILABLE_IN_3D;
539 for (auto &it : action_connections) {
540 const auto k = make_action(it.first);
541 auto act = action_catalog.at(k);
542 if (!(act.flags & ActionCatalogItem::FLAGS_NO_PREFERENCES) && prefs.key_sequences.keys.count(k)) {
543 const auto &pref = prefs.key_sequences.keys.at(k);
544 const std::vector<KeySequence> *seqs = nullptr;
545 if (pref.count(av) && pref.at(av).size()) {
546 seqs = &pref.at(av);
547 }
548 else if (pref.count(ActionCatalogItem::AVAILABLE_EVERYWHERE)
549 && pref.at(ActionCatalogItem::AVAILABLE_EVERYWHERE).size()) {
550 seqs = &pref.at(ActionCatalogItem::AVAILABLE_EVERYWHERE);
551 }
552 if (seqs) {
553 it.second.key_sequences = *seqs;
554 }
555 else {
556 it.second.key_sequences.clear();
557 }
558 }
559 }
560 }
561
handle_pan_action(const ActionConnection & c)562 void View3DWindow::handle_pan_action(const ActionConnection &c)
563 {
564 Coordf d;
565 switch (c.action_id) {
566 case ActionID::PAN_DOWN:
567 d.y = 1;
568 break;
569
570 case ActionID::PAN_UP:
571 d.y = -1;
572 break;
573
574 case ActionID::PAN_LEFT:
575 d.x = 1;
576 break;
577
578 case ActionID::PAN_RIGHT:
579 d.x = -1;
580 break;
581 default:
582 return;
583 }
584 d = d * 50;
585 auto center = canvas->get_center();
586 auto shift = canvas->get_center_shift({d.x, d.y});
587 canvas->set_center(center + shift);
588 }
589
handle_zoom_action(const ActionConnection & conn)590 void View3DWindow::handle_zoom_action(const ActionConnection &conn)
591 {
592 auto inc = 1;
593 if (conn.action_id == ActionID::ZOOM_IN)
594 inc = -1;
595
596 canvas->set_cam_distance(canvas->get_cam_distance() * pow(1.5, inc));
597 }
598
handle_rotate_action(const ActionConnection & conn)599 void View3DWindow::handle_rotate_action(const ActionConnection &conn)
600 {
601 auto inc = 90;
602 if (conn.action_id == ActionID::ROTATE_VIEW_LEFT)
603 inc = -90;
604 canvas->inc_cam_azimuth(inc);
605 }
606
handle_view_action(const ActionConnection & conn)607 void View3DWindow::handle_view_action(const ActionConnection &conn)
608 {
609 for (const auto &it : views) {
610 if (it.action == conn.action_id) {
611 canvas->set_cam_azimuth(it.azimuth);
612 canvas->set_cam_elevation(it.elevation);
613 return;
614 }
615 }
616 }
617
handle_proj_action(const ActionConnection & conn)618 void View3DWindow::handle_proj_action(const ActionConnection &conn)
619 {
620 if (conn.action_id == ActionID::VIEW_3D_ORTHO)
621 proj_ortho_rb->set_active(true);
622 else
623 proj_persp_rb->set_active(true);
624 }
625
626
627 } // namespace horizon
628