From ddee74ab78a4d1fc1fcd9b988f2e4c6c3396e10f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 18 Jan 2026 21:12:26 +0100 Subject: [PATCH] Rulers: confine box/ellipse to square/circle with Ctrl, center box/ellipse with Ctrl - same as for drawing boxes --- src/ant/ant/antEditorOptionsPages.cc | 38 ++++++++-- src/ant/ant/antEditorOptionsPages.h | 2 +- src/ant/ant/antService.cc | 105 +++++++++++++++++++++++++-- src/ant/ant/antService.h | 12 ++- 4 files changed, 143 insertions(+), 14 deletions(-) diff --git a/src/ant/ant/antEditorOptionsPages.cc b/src/ant/ant/antEditorOptionsPages.cc index 572b020f2..56dea4a17 100644 --- a/src/ant/ant/antEditorOptionsPages.cc +++ b/src/ant/ant/antEditorOptionsPages.cc @@ -50,6 +50,10 @@ ToolkitWidget::ToolkitWidget (lay::LayoutViewBase *view, lay::Dispatcher *dispat mp_y_le->set_label ("dy:"); mp_layout->addWidget (mp_y_le); + mp_d_le = new lay::DecoratedLineEdit (this); + mp_d_le->set_label ("d:"); + mp_layout->addWidget (mp_d_le); + mp_layout->addStretch (1); hide (); @@ -86,12 +90,24 @@ ToolkitWidget::commit (lay::Dispatcher *dispatcher) { try { - double dx = 0.0, dy = 0.0; + if (mp_d_le->hasFocus ()) { - tl::from_string (tl::to_string (mp_x_le->text ()), dx); - tl::from_string (tl::to_string (mp_y_le->text ()), dy); + double d = 0.0; - dispatcher->call_function (ant::Service::function_name (), db::DVector (dx, dy).to_string ()); + tl::from_string (tl::to_string (mp_d_le->text ()), d); + + dispatcher->call_function (ant::Service::d_function_name (), tl::to_string (d)); + + } else { + + double dx = 0.0, dy = 0.0; + + tl::from_string (tl::to_string (mp_x_le->text ()), dx); + tl::from_string (tl::to_string (mp_y_le->text ()), dy); + + dispatcher->call_function (ant::Service::xy_function_name (), db::DVector (dx, dy).to_string ()); + + } } catch (...) { } @@ -100,7 +116,7 @@ ToolkitWidget::commit (lay::Dispatcher *dispatcher) void ToolkitWidget::configure (const std::string &name, const std::string &value) { - if (name == ant::Service::configure_name () && ! mp_x_le->hasFocus () && ! mp_y_le->hasFocus ()) { + if (name == ant::Service::xy_configure_name () && ! mp_x_le->hasFocus () && ! mp_y_le->hasFocus ()) { try { @@ -113,6 +129,18 @@ ToolkitWidget::configure (const std::string &name, const std::string &value) } catch (...) { } + } else if (name == ant::Service::d_configure_name () && ! mp_x_le->hasFocus () && ! mp_y_le->hasFocus ()) { + + try { + + double d; + tl::from_string (value, d); + + mp_d_le->setText (tl::to_qstring (tl::micron_to_string (d))); + + } catch (...) { + } + } } diff --git a/src/ant/ant/antEditorOptionsPages.h b/src/ant/ant/antEditorOptionsPages.h index 1a28ebf81..470a69686 100644 --- a/src/ant/ant/antEditorOptionsPages.h +++ b/src/ant/ant/antEditorOptionsPages.h @@ -58,7 +58,7 @@ public: private: QHBoxLayout *mp_layout; - lay::DecoratedLineEdit *mp_x_le, *mp_y_le; + lay::DecoratedLineEdit *mp_x_le, *mp_y_le, *mp_d_le; }; } diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index cdfa45719..8e5fd35d5 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1043,8 +1043,10 @@ View::render (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas) // ant::Service implementation const char *Service::editor_options_name () { return "ant-toolkit-widget-name"; } -const char *Service::configure_name () { return "ant-toolkit-widget-value"; } -const char *Service::function_name () { return "ant-toolkit-widget-commit"; } +const char *Service::xy_configure_name () { return "ant-toolkit-widget-xy-value"; } +const char *Service::d_configure_name () { return "ant-toolkit-widget-d-value"; } +const char *Service::xy_function_name () { return "ant-toolkit-widget-xy-commit"; } +const char *Service::d_function_name () { return "ant-toolkit-widget-d-commit"; } Service::Service (db::Manager *manager, lay::LayoutViewBase *view) : lay::EditorServiceBase (view), @@ -1061,6 +1063,9 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view) m_drawing (false), m_current (), m_move_mode (MoveNone), m_seg_index (0), + m_length_confined (false), + m_length (0.0), + m_centered (false), m_current_template (0), m_hover (false), m_hover_wait (false), @@ -1906,6 +1911,7 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio // cancel any edit operations so far m_move_mode = MoveNone; + m_length_confined = false; // reset selection clear_selection (); @@ -2023,6 +2029,7 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio pts.push_back (m_p1); m_current.set_points_exact (pts); + m_length_confined = false; } @@ -2058,7 +2065,7 @@ Service::create_measure_ruler (const db::DPoint &pt, lay::angle_constraint_type void Service::function (const std::string &name, const std::string &value) { - if (name == function_name ()) { + if (name == xy_function_name ()) { try { @@ -2109,6 +2116,59 @@ Service::function (const std::string &name, const std::string &value) } catch (...) { } + } else if (name == d_function_name ()) { + + try { + + double s = 0.0; + tl::from_string (value, s); + + if (m_drawing) { + + m_length_confined = true; + m_length = s; + + ant::Object::point_list pts = m_current.points (); + confine_length (pts); + m_current.set_points_exact (pts); + + } + + } catch (...) { + } + + } +} + +void +Service::confine_length (ant::Object::point_list &pts) +{ + if (m_length_confined && pts.size () >= 2) { + + const ant::Template &tpl = current_template (); + bool is_box_style = (tpl.outline () == ant::Object::OL_box || tpl.outline () == ant::Object::OL_ellipse); + + db::DPoint p1 = m_centered ? m_p1 : pts [pts.size () - 2]; + db::DVector s = pts.back () - p1; + if (is_box_style) { + db::DVector snew = s; + double l = m_centered ? m_length * 0.5 : m_length; + if (fabs (s.x ()) < fabs (s.y ()) + db::epsilon) { + snew.set_y (l * (s.y () < 0 ? -1.0 : 1.0)); + } + if (fabs (s.y ()) < fabs (s.x ()) + db::epsilon) { + snew.set_x (l * (s.x () < 0 ? -1.0 : 1.0)); + } + s = snew; + } else { + double l = s.double_length (); + if (l > db::epsilon) { + s *= m_length / l; + } + } + + pts.back () = p1 + s; + } } @@ -2131,11 +2191,31 @@ Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) } + const ant::Template &tpl = current_template (); + + // for normal rulers with box or ellipse rendering we use a different button scheme: + // Shift will keep the center, Ctrl will confine the box to a square/ellipse to a circle + bool snap_square = false; + bool is_box_style = (tpl.outline () == ant::Object::OL_box || tpl.outline () == ant::Object::OL_ellipse); + if (tpl.mode () == ant::Template::RulerNormal && is_box_style) { + snap_square = (buttons & lay::ControlButton) != 0; + m_centered = (buttons & lay::ShiftButton) != 0; + } else { + m_centered = false; + } + lay::PointSnapToObjectResult snap_details; if (m_drawing) { - snap_details = snap2_details (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)); + lay::angle_constraint_type ac; + if (snap_square) { + ac = lay::AC_DiagonalOnly; + } else if (is_box_style) { + ac = lay::AC_Any; + } else { + ac = ac_from_buttons (buttons); + } + snap_details = snap2_details (m_p1, p, mp_active_ruler->ruler (), ac); } else { - const ant::Template &tpl = current_template (); snap_details = snap1_details (p, m_obj_snap && tpl.snap () && (tpl.mode () != ant::Template::RulerAutoMetricEdge || ! view ()->transient_selection_mode ())); } @@ -2149,8 +2229,19 @@ Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) // otherwise we risk manipulating p1 too. ant::Object::point_list pts = m_current.points (); if (! pts.empty ()) { + pts.back () = snap_details.snapped_point; + + confine_length (pts); + + if (m_centered) { + pts.front () = m_p1 - (pts.back () - m_p1); + } else { + pts.front () = m_p1; + } + } + m_current.set_points_exact (pts); db::DVector delta; @@ -2161,7 +2252,9 @@ Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) lay::EditorOptionsPage *tb = toolbox_widget (); if (tb) { - tb->configure (configure_name (), delta.to_string ()); + double d = is_box_style ? std::min (fabs (delta.x ()), fabs (delta.y ())) : delta.length (); + tb->configure (xy_configure_name (), delta.to_string ()); + tb->configure (d_configure_name (), tl::to_string (d)); } mp_active_ruler->redraw (); diff --git a/src/ant/ant/antService.h b/src/ant/ant/antService.h index d8d97b6f8..6833c5acc 100644 --- a/src/ant/ant/antService.h +++ b/src/ant/ant/antService.h @@ -199,8 +199,10 @@ public: // for communicating with the toolbox widget static const char *editor_options_name (); - static const char *configure_name (); - static const char *function_name (); + static const char *xy_configure_name (); + static const char *d_configure_name (); + static const char *xy_function_name (); + static const char *d_function_name (); /** * The current move mode: @@ -598,6 +600,11 @@ private: MoveMode m_move_mode; // The currently moving segment size_t m_seg_index; + // When set to true, the length is confined to the value given by m_length + bool m_length_confined; + double m_length; + // When set to true, the last point was established in centered fashion + bool m_centered; // The ruler template std::vector m_ruler_templates; unsigned int m_current_template; @@ -618,6 +625,7 @@ private: db::DPoint snap2_visual (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); lay::PointSnapToObjectResult snap2_details (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); lay::TwoPointSnapToObjectResult auto_measure (const db::DPoint &p, lay::angle_constraint_type ac, const ant::Template &tpl); + void confine_length (ant::Object::point_list &pts); const ant::Template ¤t_template () const;