diff --git a/docs/screenshots/offset-drag/no-selection-distance-first.png b/docs/screenshots/offset-drag/no-selection-distance-first.png new file mode 100644 index 000000000..32835108c Binary files /dev/null and b/docs/screenshots/offset-drag/no-selection-distance-first.png differ diff --git a/docs/screenshots/offset-drag/preselected-live-preview.png b/docs/screenshots/offset-drag/preselected-live-preview.png new file mode 100644 index 000000000..d7eda8df4 Binary files /dev/null and b/docs/screenshots/offset-drag/preselected-live-preview.png differ diff --git a/docs/screenshots/offset-drag/typed-minus-10-lands-outside.png b/docs/screenshots/offset-drag/typed-minus-10-lands-outside.png new file mode 100644 index 000000000..53bb21750 Binary files /dev/null and b/docs/screenshots/offset-drag/typed-minus-10-lands-outside.png differ diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index b16eb479b..eef98f760 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3035,6 +3035,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ انقر نقطة العبور أو [متعدد]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ متعدد انقر الجانب التالي [المسافة __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ متعدد انقر نقطة العبور التالية: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ انقر نقطة العبور أو اكتب المسافة، والقيمة السالبة للجهة الأخرى [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ متعدد انقر نقطة العبور أو اكتب المسافة، والقيمة السالبة للجهة الأخرى [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = إزاحة (DIMEXO) .overall-scale-dimscale = المقياس الإجمالي (DIMSCALE) .pedit-enter-option = PEDIT أدخل الخيار: diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 395758ea7..44a94f001 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Кликни точка на преминаване или [Многократно]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Многократно Кликни следваща страна [разстояние __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Многократно Кликни следваща точка на преминаване: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Кликни точка на преминаване или въведи разстояние, отрицателно за другата страна [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Многократно Кликни точка на преминаване или въведи разстояние, отрицателно за другата страна [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Отместване (DIMEXO) .overall-scale-dimscale = Общ мащаб (DIMSCALE) .pedit-enter-option = PEDIT Въведи опция: diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 724bf6e48..65f42ab34 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Bod prokliku nebo [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Vícenásobné kliknutí na další stranu [vzdálenost __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Vícenásobné kliknutí dalším průchozím bodem: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Klikněte na průchozí bod nebo zadejte vzdálenost, zápornou pro druhou stranu [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Vícenásobně Klikněte na průchozí bod nebo zadejte vzdálenost, zápornou pro druhou stranu [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Odsazení (DIMEXO) .overall-scale-dimscale = Celková stupnice (DIMSCALE) .pedit-enter-option = Možnost PEDIT Enter: diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index e5a685229..64eb484b4 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -3023,6 +3023,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Klicken Sie durch Punkt oder [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Mehrfachklicken Sie auf die nächste Seite [Abstand __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Mehrfachklicken Sie als nächstes durch den Punkt: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Durch Punkt klicken oder Abstand eingeben, negativ für die andere Seite [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Mehrfach Durch Punkt klicken oder Abstand eingeben, negativ für die andere Seite [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Abstand der Maßhilfslinie (DIMEXO) .overall-scale-dimscale = Gesamtmaßstab (DIMSCALE) .pedit-enter-option = PEDIT Option eingeben: diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index aaa837e5b..91d2ef1ec 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -3023,6 +3023,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Κάντε κλικ σε σημείο διέλευσης ή [Πολλαπλά]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Πολλαπλά Κάντε κλικ στην επόμενη πλευρά [απόσταση __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Πολλαπλά Κάντε κλικ στο επόμενο σημείο διέλευσης: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Κάντε κλικ σε σημείο διέλευσης ή πληκτρολογήστε απόσταση, αρνητική για την άλλη πλευρά [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Πολλαπλά Κάντε κλικ σε σημείο διέλευσης ή πληκτρολογήστε απόσταση, αρνητική για την άλλη πλευρά [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Μετατόπιση (DIMEXO) .overall-scale-dimscale = Συνολική κλίμακα (DIMSCALE) .pedit-enter-option = PEDIT Δώστε επιλογή: diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index b1e22ed13..a6d01eb81 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -3024,6 +3024,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Click through point or [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Multiple Click next side [distance __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Multiple Click next through point: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Click through point or type distance, negative for the other side [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Multiple Click through point or type distance, negative for the other side [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Offset (DIMEXO) .overall-scale-dimscale = Overall scale (DIMSCALE) .pedit-enter-option = PEDIT Enter option: diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 8c7b1d012..ed8b705a5 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -3023,6 +3023,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Pulse el punto de paso o [Múltiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Múltiple Pulse el siguiente lado [distancia __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Múltiple Pulse el siguiente punto de paso: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Pulse el punto de paso o escriba la distancia, negativa para el otro lado [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Múltiple Pulse el punto de paso o escriba la distancia, negativa para el otro lado [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Desfase (DIMEXO) .overall-scale-dimscale = Escala general (DIMSCALE) .pedit-enter-option = PEDIT Indique una opción: diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 4844bc1dd..dbde538b3 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Napsautuskohta tai [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Useita Napsauta seuraavaa reunaa [etäisyys __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Useita napsauttamalla seuraava kohta: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Napsauta läpikulkupistettä tai kirjoita etäisyys, negatiivinen toiselle puolelle [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Useita Napsauta läpikulkupistettä tai kirjoita etäisyys, negatiivinen toiselle puolelle [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Siirtymä (DIMEXO) .overall-scale-dimscale = Kokonaismittakaava (DIMSCALE) .pedit-enter-option = PEDIT Enter vaihtoehto: diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index b1ab5b3bf..0f6dbf6cb 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -3023,6 +3023,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Cliquez sur le point ou [multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Cliquer sur le côté suivant [distance __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Cliquez sur plusieurs ci-dessous pour accéder au point suivant: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Cliquez sur le point de passage ou saisissez la distance, négative pour l'autre côté [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Multiple Cliquez sur le point de passage ou saisissez la distance, négative pour l'autre côté [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Décomposition (DIMEXO) .overall-scale-dimscale = Échelle globale (DIMSCALE) .pedit-enter-option = PEDIT Saisissez l'option : diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index e0abed923..1c652a226 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -3011,6 +3011,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ बिंदु के माध्यम से क्लिक करें या [एकाधिक]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ अगली दिशा की ओर क्लिक करें [दूरी __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ एकाधिक बिंदु के माध्यम से आगे क्लिक करें: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ पारगमन बिंदु पर क्लिक करें या दूरी टाइप करें, दूसरी ओर के लिए ऋणात्मक [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ एकाधिक पारगमन बिंदु पर क्लिक करें या दूरी टाइप करें, दूसरी ओर के लिए ऋणात्मक [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = ऑफसेट (DIMEXO) .overall-scale-dimscale = कुल मिलाकर स्केल (DIMSCALE) .pedit-enter-option = PEDIT विकल्प दर्ज करें: diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 3a412ad09..bd470afc9 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Átkattintási pont vagy [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Több kattintás a következő oldalon [távolság __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Több kattintás a következő ponton: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Kattintson az átmenő pontra, vagy írja be a távolságot, negatívat a másik oldalhoz [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Többszörös Kattintson az átmenő pontra, vagy írja be a távolságot, negatívat a másik oldalhoz [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Eltolás (DIMEXO) .overall-scale-dimscale = Teljes skála (DIMSCALE) .pedit-enter-option = PEDIT Enter opció: diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 29901bfdc..62201d656 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Punto di passaggio o [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Clic multiplo sul lato successivo [distanza __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Clic multiplo sul punto successivo: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Fare clic sul punto di passaggio o digitare la distanza, negativa per l'altro lato [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Multiplo Fare clic sul punto di passaggio o digitare la distanza, negativa per l'altro lato [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Spostamento (DIMEXO) .overall-scale-dimscale = Scala totale (DIMSCALE) .pedit-enter-option = Opzione PEDIT Enter: diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 616fbee9e..2080e66fa 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -3013,6 +3013,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ 通過点をクリックまたは [複数]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ 複数 次の側をクリック [距離 __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ 複数 次の通過点をクリック: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 通過点をクリックまたは距離を入力、負の値で反対側 [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 複数 通過点をクリックまたは距離を入力、負の値で反対側 [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = オフセット (DIMEXO) .overall-scale-dimscale = 全体尺度 (DIMSCALE) .pedit-enter-option = PEDIT オプションを入力: diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 4e963349d..c08256edf 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ 클릭 통과 점 또는 [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ 다중 다음 측면 클릭 [거리 __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ 다중 클릭 다음 통과 지점: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 통과점을 클릭하거나 거리 입력, 음수는 반대쪽 [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 다중 통과점을 클릭하거나 거리 입력, 음수는 반대쪽 [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = 오프셋(DIMEXO) .overall-scale-dimscale = 전체 규모(DIMSCALE) .pedit-enter-option = PEDIT Enter 옵션: diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 80e8ac9eb..b3f8b9128 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -3023,6 +3023,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Klik door punt of [meerdere]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Meerdere klik op volgende zijde [afstand __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Meerdere klik volgende door punt: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Klik door punt of typ afstand, negatief voor de andere kant [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Meerdere Klik door punt of typ afstand, negatief voor de andere kant [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Verschuiving (DIMEXO) .overall-scale-dimscale = Totale schaal (DIMSCALE) .pedit-enter-option = PEDIT Voer optie in: diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 9fdaea28d..ead09a42c 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Punkt przelotowy lub [Multiple]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Wielokrotne kliknięcie następnej strony [odległość __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Wielokrotne kliknięcie następnego punktu przelotowego: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Kliknij punkt przelotowy lub wpisz odległość, ujemną dla drugiej strony [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Wielokrotnie Kliknij punkt przelotowy lub wpisz odległość, ujemną dla drugiej strony [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Przesunięcie (DIMEXO) .overall-scale-dimscale = Skala ogólna (DIMSCALE) .pedit-enter-option = Opcja PEDIT Enter: diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 8901c55da..5f4346007 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -3023,6 +3023,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Clique no ponto de passagem ou [Múltiplo]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Múltiplo Clique no próximo lado [distância __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Múltiplo Clique no próximo ponto de passagem: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Clique no ponto de passagem ou digite a distância, negativa para o outro lado [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Múltiplo Clique no ponto de passagem ou digite a distância, negativa para o outro lado [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Deslocamento (DIMEXO) .overall-scale-dimscale = Escala geral (DIMSCALE) .pedit-enter-option = PEDIT Insira a opção: diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 40697720e..f3205c936 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -3026,6 +3026,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Нажмите на точку или [Множественное число]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Несколько кликов на следующую сторону [расстояние __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Несколько кликов по следующей точке: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Укажите точку прохождения или введите расстояние, отрицательное для другой стороны [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Несколько Укажите точку прохождения или введите расстояние, отрицательное для другой стороны [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Отступ от объекта (DIMEXO) .overall-scale-dimscale = Общий масштаб (DIMSCALE) .pedit-enter-option = PEDIT Введите опцию: diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 5ca8fb648..c72668224 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -3005,6 +3005,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ Geçiş noktasına tıklayın veya [Birden Çok]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ Birden Çok Sonraki tarafa tıklayın [mesafe __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ Birden Çok Sonraki geçiş noktasına tıklayın: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Geçiş noktasına tıklayın veya mesafe yazın, diğer taraf için negatif [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ Birden Çok Geçiş noktasına tıklayın veya mesafe yazın, diğer taraf için negatif [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = Ofset (DIMEXO) .overall-scale-dimscale = Genel ölçek (DIMSCALE) .pedit-enter-option = PEDIT Seçeneği girin: diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 078806de7..5b89f5ada 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -3010,6 +3010,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ 单击通过点或 [多个]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ 多个 单击下一侧 [距离 __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ 多个 单击下一个通过点: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 单击通过点或输入距离,负值偏移到另一侧 [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 多个 单击通过点或输入距离,负值偏移到另一侧 [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = 偏移 (DIMEXO) .overall-scale-dimscale = 全局比例 (DIMSCALE) .pedit-enter-option = PEDIT 输入选项: diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 3af162dc8..90d5489f3 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -3022,6 +3022,8 @@ modify = .offset-n-click-through-point-or-multiple = OFFSET__ocs_arg_n__ 按一下通過點或 [多個]: .offset-n-multiple-click-next-side-distance-d = OFFSET__ocs_arg_n__ 多個 按一下下一側 [距離 __ocs_arg_d__]: .offset-n-multiple-click-next-through-point = OFFSET__ocs_arg_n__ 多個 按一下下一個通過點: + .offset-n-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 按一下通過點或輸入距離,負值偏移到另一側 [Multiple/Erase/Layer] <__ocs_arg_d__>: + .offset-n-multiple-click-through-point-or-type-distance = OFFSET__ocs_arg_n__ 多個 按一下通過點或輸入距離,負值偏移到另一側 [Erase/Layer] <__ocs_arg_d__>: .offset-dimexo = 偏移 (DIMEXO) .overall-scale-dimscale = 全局比例 (DIMSCALE) .pedit-enter-option = PEDIT 輸入選項: diff --git a/src/app/commands/dim.rs b/src/app/commands/dim.rs index 2bc34742a..297e89d2a 100644 --- a/src/app/commands/dim.rs +++ b/src/app/commands/dim.rs @@ -1268,7 +1268,8 @@ impl OpenCADStudio { }) .collect(); // Pick-first (#422): with offsettable objects already selected, - // skip the pick step and go straight to distance / side. + // skip the distance and pick steps and open on the live side + // preview. let preselected: Vec<_> = self.tabs[i] .scene .selected_entities() diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index ba91e7781..3fcae5ffd 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -2049,6 +2049,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "OFFSET%{n} Click through point or [Multiple]:" => Some(("modify", "offset-n-click-through-point-or-multiple")), "OFFSET%{n} Multiple Click next side [distance %{d}]:" => Some(("modify", "offset-n-multiple-click-next-side-distance-d")), "OFFSET%{n} Multiple Click next through point:" => Some(("modify", "offset-n-multiple-click-next-through-point")), + "OFFSET%{n} Click through point or type distance, negative for the other side [Multiple/Erase/Layer] <%{d}>:" => Some(("modify", "offset-n-click-through-point-or-type-distance")), + "OFFSET%{n} Multiple Click through point or type distance, negative for the other side [Erase/Layer] <%{d}>:" => Some(("modify", "offset-n-multiple-click-through-point-or-type-distance")), "OK" => Some(("common", "ok")), "OLE Frame" => Some(("properties", "ole-frame")), "OOPS: nothing to restore." => Some(("file", "oops-nothing-to-restore")), diff --git a/src/modules/draw/modify/offset.rs b/src/modules/draw/modify/offset.rs index be4a901b2..843c67a11 100644 --- a/src/modules/draw/modify/offset.rs +++ b/src/modules/draw/modify/offset.rs @@ -9,6 +9,10 @@ // 2. Pick object to offset (Line, Arc, Circle, LwPolyline) // 3. Pick a point on the side to offset toward, or choose Multiple // to keep offsetting the newly created result at the same distance +// +// With objects already selected, the command opens at step 3 instead, the +// preview following the cursor. At step 3 a typed distance places the +// offset at once on the cursor's side; a negative one, on the other side. use crate::entities::curve::{entity_curve, lwpolyline_world_xy}; use crate::modules::draw::modify::spline_ops::spline_sample_xy; @@ -331,13 +335,18 @@ fn compute_offsets(entity: &EntityType, dist: f64, side_pt: Vec3) -> Vec f64 { + closest_on_wire(entity, pt).map_or(0.0, |(_, d)| d) +} + +/// Nearest point of the entity outline to `pt`, and its distance. +fn closest_on_wire(entity: &EntityType, pt: Vec3) -> Option<([f64; 2], f64)> { let pts = entity_wire_pts(entity); if pts.len() < 2 { - return 0.0; + return None; } let px = pt.x as f64; let py = pt.y as f64; - let mut best = f64::INFINITY; + let mut best = ([px, py], f64::INFINITY); for w in pts.windows(2) { let ax = w[0][0] as f64; let ay = w[0][1] as f64; @@ -354,11 +363,35 @@ fn perp_distance(entity: &EntityType, pt: Vec3) -> f64 { let cx = ax + t * dx; let cy = ay + t * dy; let d = ((px - cx).powi(2) + (py - cy).powi(2)).sqrt(); - if d < best { - best = d; + if d < best.1 { + best = ([cx, cy], d); } } - best + Some(best) +} + +// ── Opposite side ───────────────────────────────────────────────────────── +// +// A negative typed distance offsets away from the cursor. Rather than teach +// every offset function a sign, the cursor is mirrored through its nearest +// point on the outline, which puts it on the other side for every type: the +// other half-plane of a line, inside instead of outside a circle or closed +// loop. The mirror stops at half the offset distance so that, in a narrow +// part of a shape, it cannot cross the far wall and come back to the +// cursor's own side. + +fn far_side(entity: &EntityType, pt: Vec3, dist: f64) -> Option { + let ([qx, qy], d) = closest_on_wire(entity, pt)?; + if d < 1e-9 { + // On the outline itself: there is no side to be opposite to. + return None; + } + let step = d.min(dist * 0.5) / d; + Some(Vec3::new( + (qx + (qx - pt.x as f64) * step) as f32, + (qy + (qy - pt.y as f64) * step) as f32, + pt.z, + )) } // ── Wire preview points ───────────────────────────────────────────────────── @@ -440,9 +473,13 @@ pub struct OffsetCommand { /// Unlike the command's opening snapshot, this also includes objects /// created by earlier offsets while the command remains active. picked: Option, - /// Pre-selected offsettable objects (pick-first, #422); consumed when the - /// distance step resolves. - preselected: Vec, + /// Last cursor position the preview saw. A distance typed at the side + /// step places the offset relative to it. + cursor: Option, + /// The command opened on pre-selected objects (#422) and has made nothing + /// yet: Enter takes the last distance, and Erase / Layer, which the + /// skipped distance step would have offered, are offered here. + drag_start: bool, /// A Yes/No or Current/Source answer is being collected for this option. awaiting: Option, /// Erase option: replace the source object with its offset. @@ -485,7 +522,8 @@ impl OffsetCommand { all_entities, entity_index, picked: None, - preselected: Vec::new(), + cursor: None, + drag_start: false, awaiting: None, erase_source: false, layer_current: false, @@ -494,38 +532,28 @@ impl OffsetCommand { } } - /// Pick-first flow (#422): the distance step still comes first, then the - /// pre-selected objects go straight to the side step. + /// Pick-first flow (#422): the objects are already chosen, so the command + /// opens at the side step with the preview following the cursor. Clicking + /// offsets through the cursor; typing a distance places it at that + /// distance; Enter repeats the last distance (#418). pub fn with_selection(all_entities: Vec, targets: Vec) -> Self { - let entity_index = ModifyEntityIndex::build(&all_entities); - Self { - step: Step::Distance, - all_entities, - entity_index, - picked: None, - preselected: targets, - awaiting: None, - erase_source: false, - layer_current: false, - targets_are_sources: false, - made: 0, + let mut cmd = Self::new(all_entities); + if !targets.is_empty() { + cmd.step = Step::PickSide { + targets, + locked: None, + multiple: false, + }; + cmd.targets_are_sources = true; + cmd.drag_start = true; } + cmd } /// Leave the distance step with the given mode (Some = locked distance, - /// None = through): pre-selected objects jump to the side step, otherwise - /// the object-pick loop starts. + /// None = through) for the object-pick loop. fn advance_from_distance(&mut self, locked: Option) -> CmdResult { - if self.preselected.is_empty() { - self.step = Step::SelectObject { locked }; - } else { - self.step = Step::PickSide { - targets: std::mem::take(&mut self.preselected), - locked, - multiple: false, - }; - self.targets_are_sources = true; - } + self.step = Step::SelectObject { locked }; CmdResult::NeedPoint } @@ -554,6 +582,73 @@ impl OffsetCommand { t!("OFFSET distance = %{d}", d = d).into_owned() ) } + + /// Offset the side step's targets with `pt` choosing the side: toward it, + /// or away from it when `flip` is set. Each target offsets by its own + /// through-distance, or by the locked magnitude. + fn place(&mut self, pt: DVec3, flip: bool) -> CmdResult { + let (locked, targets, multiple) = match &self.step { + Step::PickSide { + locked, + targets, + multiple, + } => (*locked, targets.clone(), *multiple), + + _ => return CmdResult::NeedPoint, + }; + let per_target: Vec<(Handle, Vec)> = targets + .iter() + .filter_map(|entity| { + let mag = locked.unwrap_or_else(|| perp_distance(entity, pt.as_vec3())); + if mag < 1e-9 { + return None; + } + let side = if flip { + far_side(entity, pt.as_vec3(), mag)? + } else { + pt.as_vec3() + }; + let offsets = compute_offsets(entity, mag, side); + (!offsets.is_empty()).then(|| (entity.common().handle, offsets)) + }) + .collect(); + let mut news: Vec = per_target + .iter() + .flat_map(|(_, offsets)| offsets.iter().cloned()) + .collect(); + if news.is_empty() { + return CmdResult::NeedPoint; + } + self.made += 1; + self.drag_start = false; + // Erase option: the offset replaces its source object. Only the + // objects picked from the drawing qualify — a Multiple chain's + // targets are the offsets just made, which are not replaced again. + let erase = self.erase_source && self.targets_are_sources; + self.targets_are_sources = false; + if multiple { + // Multiple mode chains from the result just created, matching + // OFFSET's repeated fixed-distance behavior (parallel/concentric + // series instead of duplicate copies at the first offset). + self.step = Step::PickSide { + targets: news.clone(), + locked, + multiple: true, + }; + } else { + // Classic loop (#418): commit this offset and go back to the object + // pick at the same distance, until Enter / Esc finishes. + self.step = Step::SelectObject { locked }; + } + if erase { + return CmdResult::ReplaceManyContinue(per_target); + } + if news.len() == 1 { + CmdResult::CommitEntity(news.pop().unwrap()) + } else { + CmdResult::CommitEntities(news) + } + } } impl CadCommand for OffsetCommand { @@ -602,6 +697,23 @@ impl CadCommand for OffsetCommand { } else { std::borrow::Cow::Borrowed("") }; + if self.drag_start { + let d = format!("{:.4}", defaults::get_offset_dist()); + return if *multiple { + t!( + "OFFSET%{n} Multiple Click through point or type distance, negative for the other side [Erase/Layer] <%{d}>:", + n = n, + d = d + ) + } else { + t!( + "OFFSET%{n} Click through point or type distance, negative for the other side [Multiple/Erase/Layer] <%{d}>:", + n = n, + d = d + ) + } + .into_owned(); + } match (locked, multiple) { (Some(d), false) => { let d = format!("{:.4}", d); @@ -658,6 +770,16 @@ impl CadCommand for OffsetCommand { opts.push(CmdOption::enter("Exit")); opts } + Step::PickSide { multiple, .. } if self.drag_start => { + let mut opts = Vec::new(); + if !multiple { + opts.push(CmdOption::new("Multiple", "M")); + } + opts.push(CmdOption::new("Erase", "E")); + opts.push(CmdOption::new("Layer", "L")); + opts.push(CmdOption::enter(&format!("{:.4}", defaults::get_offset_dist()))); + opts + } Step::PickSide { multiple, .. } => { let mut opts = Vec::new(); if !multiple { @@ -777,19 +899,23 @@ impl CadCommand for OffsetCommand { { return self.undo_last(); } + // Erase / Layer belong to the distance step, or to the side step when + // a pre-selection skipped the distance step. + if matches!(self.step, Step::Distance) || self.drag_start { + if upper == "E" || upper == "ERASE" { + self.awaiting = Some(Await::Erase); + return Some(CmdResult::NeedPoint); + } + if upper == "L" || upper == "LAYER" { + self.awaiting = Some(Await::Layer); + return Some(CmdResult::NeedPoint); + } + } match &mut self.step { Step::Distance => { if t.eq_ignore_ascii_case("t") || t.eq_ignore_ascii_case("through") { return Some(self.advance_from_distance(None)); } - if upper == "E" || upper == "ERASE" { - self.awaiting = Some(Await::Erase); - return Some(CmdResult::NeedPoint); - } - if upper == "L" || upper == "LAYER" { - self.awaiting = Some(Await::Layer); - return Some(CmdResult::NeedPoint); - } if let Some(d) = crate::entities::common::parse_typed_length(&t) { return Some(self.accept_distance(d)); } @@ -804,15 +930,20 @@ impl CadCommand for OffsetCommand { *multiple = true; return Some(CmdResult::NeedPoint); } - if !t.is_empty() { - if let Some(d) = crate::entities::common::parse_typed_length(&t) { - let d = d.abs().max(1e-9); - defaults::set_offset_dist(d); - *locked = Some(d); - } + let Some(d) = crate::entities::common::parse_typed_length(&t) else { + return Some(CmdResult::NeedPoint); + }; + let mag = d.abs().max(1e-9); + defaults::set_offset_dist(mag); + *locked = Some(mag); + // The cursor has already chosen the side, so the value places + // the offset now: on the cursor's side, or the other side when + // negative. Before the cursor has been seen, the click still + // chooses. + match self.cursor { + Some(cursor) => Some(self.place(cursor, d < 0.0)), + None => Some(CmdResult::NeedPoint), } - // Stay on the side step — the click chooses which side. - Some(CmdResult::NeedPoint) } _ => None, } @@ -863,66 +994,11 @@ impl CadCommand for OffsetCommand { _ => {} } - let (locked, targets, multiple) = match &self.step { - Step::PickSide { - locked, - targets, - multiple, - } => (*locked, targets.clone(), *multiple), - - _ => return CmdResult::NeedPoint, - }; - // Each target offsets by its own through-distance (or the locked - // magnitude), toward the clicked side. - let per_target: Vec<(Handle, Vec)> = targets - .iter() - .filter_map(|entity| { - let mag = locked.unwrap_or_else(|| perp_distance(entity, pt.as_vec3())); - if mag < 1e-9 { - return None; - } - let offsets = compute_offsets(entity, mag, pt.as_vec3()); - (!offsets.is_empty()).then(|| (entity.common().handle, offsets)) - }) - .collect(); - let mut news: Vec = per_target - .iter() - .flat_map(|(_, offsets)| offsets.iter().cloned()) - .collect(); - if news.is_empty() { - return CmdResult::NeedPoint; - } - self.made += 1; - // Erase option: the offset replaces its source object. Only the - // objects picked from the drawing qualify — a Multiple chain's - // targets are the offsets just made, which are not replaced again. - let erase = self.erase_source && self.targets_are_sources; - self.targets_are_sources = false; - if multiple { - // Multiple mode chains from the result just created, matching - // OFFSET's repeated fixed-distance behavior (parallel/concentric - // series instead of duplicate copies at the first offset). - self.step = Step::PickSide { - targets: news.clone(), - locked, - multiple: true, - }; - } else { - // Classic loop (#418): commit this offset and go back to the object - // pick at the same distance, until Enter / Esc finishes. - self.step = Step::SelectObject { locked }; - } - if erase { - return CmdResult::ReplaceManyContinue(per_target); - } - if news.len() == 1 { - CmdResult::CommitEntity(news.pop().unwrap()) - } else { - CmdResult::CommitEntities(news) - } + self.place(pt, false) } fn on_preview_wires(&mut self, pt: DVec3) -> Vec { + self.cursor = Some(pt); if let Step::ReferenceSecond { first } = &self.step { return vec![WireModel::solid( "offset_reference_distance".into(), @@ -979,6 +1055,17 @@ impl CadCommand for OffsetCommand { let d = defaults::get_offset_dist(); self.accept_distance(d) } + // Opened on a pre-selection, the same Enter offsets by the last + // distance on the cursor's side. + Step::PickSide { .. } if self.drag_start => { + let Some(cursor) = self.cursor else { + return CmdResult::NeedPoint; + }; + if let Step::PickSide { locked, .. } = &mut self.step { + *locked = Some(defaults::get_offset_dist()); + } + self.place(cursor, false) + } _ => CmdResult::Cancel, } } @@ -1100,3 +1187,127 @@ mod option_tests { assert!(matches!(cmd.on_point(DVec3::new(5.0, 5.0, 0.0)), CmdResult::CommitEntity(_))); } } + +#[cfg(test)] +mod drag_tests { + use super::*; + use codec::types::Vector2; + + fn line_y0() -> EntityType { + let mut line = codec::entities::Line::from_coords(0.0, 0.0, 0.0, 10.0, 0.0, 0.0); + line.common.handle = Handle::new(1); + EntityType::Line(line) + } + + /// OFFSET opened on `target`, with the cursor hovering at `cursor`. + fn dragging(target: EntityType, cursor: [f64; 2]) -> OffsetCommand { + let mut cmd = OffsetCommand::with_selection(vec![target.clone()], vec![target]); + cmd.on_preview_wires(DVec3::new(cursor[0], cursor[1], 0.0)); + cmd + } + + fn committed_line_y(result: Option) -> f64 { + match result { + Some(CmdResult::CommitEntity(EntityType::Line(l))) => l.start.y, + _ => panic!("expected one committed line"), + } + } + + #[test] + fn preselection_opens_on_the_live_preview() { + let mut cmd = OffsetCommand::with_selection(vec![line_y0()], vec![line_y0()]); + assert!(matches!(cmd.step, Step::PickSide { locked: None, .. })); + assert_eq!(cmd.options().into_iter().map(|o| o.keyword).collect::>(), ["M", "E", "L", ""]); + // The preview passes through the cursor before anything is typed. + assert_eq!(cmd.on_preview_wires(DVec3::new(5.0, 3.0, 0.0)).len(), 1); + assert_eq!(cmd.dyn_live_value(DVec3::new(5.0, 3.0, 0.0)), Some(3.0)); + // A click still offsets through the cursor. + assert!(matches!(cmd.on_point(DVec3::new(5.0, 3.0, 0.0)), CmdResult::CommitEntity(_))); + } + + #[test] + fn typed_distance_places_on_the_cursor_side() { + let mut cmd = dragging(line_y0(), [5.0, 3.0]); + assert!((committed_line_y(cmd.on_text_input("2")) - 2.0).abs() < 1e-9); + // The loop continues at the typed distance. + assert!(matches!(cmd.step, Step::SelectObject { locked: Some(d) } if d == 2.0)); + } + + #[test] + fn negative_distance_places_on_the_other_side() { + let mut cmd = dragging(line_y0(), [5.0, 3.0]); + assert!((committed_line_y(cmd.on_text_input("-2")) + 2.0).abs() < 1e-9); + assert_eq!(defaults::get_offset_dist(), 2.0); + } + + #[test] + fn negative_distance_inside_a_circle_grows_it() { + let circle = EntityType::Circle(CircleEnt { + center: codec::types::Vector3::new(0.0, 0.0, 0.0), + radius: 10.0, + ..Default::default() + }); + let mut cmd = dragging(circle.clone(), [3.0, 0.0]); + match cmd.on_text_input("-1") { + Some(CmdResult::CommitEntity(EntityType::Circle(c))) => assert!((c.radius - 11.0).abs() < 1e-9), + _ => panic!("expected one committed circle"), + } + let mut cmd = dragging(circle, [3.0, 0.0]); + match cmd.on_text_input("1") { + Some(CmdResult::CommitEntity(EntityType::Circle(c))) => assert!((c.radius - 9.0).abs() < 1e-9), + _ => panic!("expected one committed circle"), + } + } + + // A U whose arms sit 1 apart. From inside the left arm the nearest wall + // faces the gap; mirroring the cursor its full 1.9 across would land in + // the right arm, back inside the loop. -0.25 must still go outward. + #[test] + fn negative_distance_does_not_mirror_across_a_gap() { + let u = EntityType::LwPolyline(LwPolyline { + vertices: [ + [0.0, 0.0], [9.0, 0.0], [9.0, 10.0], [5.0, 10.0], + [5.0, 2.0], [4.0, 2.0], [4.0, 10.0], [0.0, 10.0], + ] + .iter() + .map(|&[x, y]| LwVertex::new(Vector2::new(x, y))) + .collect(), + is_closed: true, + ..Default::default() + }); + let mut cmd = dragging(u, [2.1, 6.0]); + match cmd.on_text_input("-0.25") { + Some(CmdResult::CommitEntity(EntityType::LwPolyline(p))) => { + let min_x = p.vertices.iter().map(|v| v.location.x).fold(f64::INFINITY, f64::min); + assert!((min_x + 0.25).abs() < 1e-6, "offset went inward: min x {min_x}"); + } + _ => panic!("expected one committed polyline"), + } + } + + #[test] + fn enter_repeats_the_last_distance_on_the_cursor_side() { + defaults::set_offset_dist(4.0); + let mut cmd = dragging(line_y0(), [5.0, -1.0]); + match cmd.on_enter() { + CmdResult::CommitEntity(EntityType::Line(l)) => assert!((l.start.y + 4.0).abs() < 1e-9), + _ => panic!("expected one committed line"), + } + // After that, Enter finishes as before. + assert!(matches!(cmd.on_enter(), CmdResult::Cancel)); + } + + #[test] + fn erase_is_offered_without_the_distance_step() { + let mut cmd = dragging(line_y0(), [5.0, 3.0]); + cmd.on_text_input("E"); + cmd.on_text_input("Y"); + assert!(matches!(cmd.on_text_input("2"), Some(CmdResult::ReplaceManyContinue(_)))); + } + + #[test] + fn without_preselection_the_distance_comes_first() { + let cmd = OffsetCommand::new(vec![line_y0()]); + assert!(matches!(cmd.step, Step::Distance)); + } +}