diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/AppConf.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/AppConf.kt index 8fc8b9ab..026e92e7 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/AppConf.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/AppConf.kt @@ -349,6 +349,8 @@ data class AppConf( * @param showTag When true, the tag or "New tag" button is shown in the editor and entry lists. * @param showExtra When true, the extra editor button/icon is shown in the editor and entry lists. * @param continuousLabelNames Appearance of the label names in the editor for continuous labelers. + * @param clickToJumpToEntry When true, clicking an entry on the canvas in multiple entry edit mode makes it the + * current entry. * @param entryNamePresets Preset entry names that can be quickly applied in entry name input dialogs. */ @Serializable @@ -363,6 +365,7 @@ data class AppConf( val lockedDrag: LockedDrag = DEFAULT_LOCKED_DRAG, val cascadedDrag: CascadedDrag = DEFAULT_CASCADED_DRAG, val lockedSettingParameterWithCursor: Boolean = DEFAULT_LOCKED_SETTING_PARAMETER_WITH_CURSOR, + val clickToJumpToEntry: Boolean = DEFAULT_CLICK_TO_JUMP_TO_ENTRY, val showDone: Boolean = DEFAULT_SHOW_DONE, val showStar: Boolean = DEFAULT_SHOW_STAR, val showTag: Boolean = DEFAULT_SHOW_TAG, @@ -427,6 +430,7 @@ data class AppConf( val DEFAULT_LOCKED_DRAG = LockedDrag.UseLabeler val DEFAULT_CASCADED_DRAG = CascadedDrag.Disabled const val DEFAULT_LOCKED_SETTING_PARAMETER_WITH_CURSOR = true + const val DEFAULT_CLICK_TO_JUMP_TO_ENTRY = false const val DEFAULT_SHOW_DONE = true const val DEFAULT_SHOW_STAR = true const val DEFAULT_SHOW_TAG = true diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/dialog/preferences/PreferencesPages.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/dialog/preferences/PreferencesPages.kt index 14900d2e..04bc9f59 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/dialog/preferences/PreferencesPages.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/dialog/preferences/PreferencesPages.kt @@ -726,6 +726,13 @@ object PreferencesPages { select = { it.lockedSettingParameterWithCursor }, update = { copy(lockedSettingParameterWithCursor = it) }, ) + switch( + title = Strings.PreferencesEditorClickToJumpToEntry, + description = Strings.PreferencesEditorClickToJumpToEntryDescription, + defaultValue = AppConf.Editor.DEFAULT_CLICK_TO_JUMP_TO_ENTRY, + select = { it.clickToJumpToEntry }, + update = { copy(clickToJumpToEntry = it) }, + ) selection( title = Strings.PreferencesEditorCascadedDrag, description = Strings.PreferencesEditorCascadedDragDescription, diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/Marker.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/Marker.kt index 5a2ea7bb..c13a49fb 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/Marker.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/Marker.kt @@ -128,7 +128,15 @@ fun MarkerPointEventContainer( ) } .onPointerEvent(PointerEventType.Press) { event -> - state.handleMousePress(tool, keyboardState, event, state.labelerConf, appState.appConf, screenRange) + state.handleMousePress( + tool, + keyboardState, + event, + state.labelerConf, + appState.appConf, + screenRange, + editorState, + ) } .onPointerEvent(PointerEventType.Release) { event -> state.handleMouseRelease( @@ -662,9 +670,10 @@ private fun MarkerState.handleMousePress( labelerConf: LabelerConf, appConf: AppConf, screenRange: FloatRange?, + editorState: EditorState, ) { when (tool) { - Tool.Cursor -> handleCursorPress(keyboardState, event, labelerConf, appConf) + Tool.Cursor -> handleCursorPress(keyboardState, event, labelerConf, appConf, screenRange, editorState) Tool.Scissors -> Unit Tool.Pan -> handlePanPress(event) Tool.Playback -> handlePlaybackPress(keyboardState, screenRange, event) @@ -676,10 +685,19 @@ private fun MarkerState.handleCursorPress( event: PointerEvent, labelerConf: LabelerConf, appConf: AppConf, + screenRange: FloatRange?, + editorState: EditorState, ) { val action = keyboardState.getEnabledMouseClickAction(event) ?: return if (action.canMoveParameter()) { val cursorStateValue = cursorState.value + if (cursorStateValue.mouse != MarkerCursorState.Mouse.Hovering) { + // Not grabbing a point/border to drag, so a click on an entry body may switch the current entry. + // This is intentionally skipped while hovering a point: dragging a (possibly shared) border should not + // change the current entry, which otherwise switched to the previous entry when its start border was + // grabbed. + maybeJumpToClickedEntry(appConf, screenRange, event, editorState) + } if (cursorStateValue.mouse == MarkerCursorState.Mouse.Hovering) { val invertLockedDrag = action == MouseClickAction.MoveParameterInvertingPrimary val lockedDrag = when (appConf.editor.lockedDrag) { @@ -707,6 +725,25 @@ private fun MarkerState.handleCursorPress( } } +private fun MarkerState.maybeJumpToClickedEntry( + appConf: AppConf, + screenRange: FloatRange?, + event: PointerEvent, + editorState: EditorState, +) { + if (!appConf.editor.clickToJumpToEntry || entries.size <= 1 || screenRange == null) return + // Derive the click position from the press event itself (not the hover-driven cursorState.position), + // so the jump also works when the mouse is pressed without any preceding move. + val position = event.changes.first().position.x + screenRange.start + val indexInGroup = getEntryIndexByCursorPosition(position) ?: return + val targetIndex = entries[indexInGroup].index + // Skip when the clicked entry is already current; jumpToEntry would otherwise re-trigger the + // auto-centering scroll for a no-op jump. + if (targetIndex != editorState.project.currentModule.currentIndex) { + editorState.jumpToEntry(editorState.project.currentModule.name, targetIndex) + } +} + private fun MarkerState.handlePanPress(event: PointerEvent) { if (event.isLeftClick) { panState.updateNonNull { copy(isDragging = true) } diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt index fb6e3601..19bb35fa 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt @@ -428,6 +428,8 @@ enum class Strings { PreferencesEditorCascadedDragEnabled, PreferencesEditorLockedSettingParameterWithCursor, PreferencesEditorLockedSettingParameterWithCursorDescription, + PreferencesEditorClickToJumpToEntry, + PreferencesEditorClickToJumpToEntryDescription, PreferencesEditorNotes, PreferencesEditorNotesDescription, PreferencesEditorShowDone, diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt index 2b5171a9..7f59ef6f 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt @@ -460,6 +460,9 @@ fun Strings.zhHans(): String? = when (this) { PreferencesEditorLockedSettingParameterWithCursor -> "光标设定时也应用锁定拖动" PreferencesEditorLockedSettingParameterWithCursorDescription -> "使用\"将参数设置到光标位置\"键盘操作时也应用上面的锁定拖动的设置。" + PreferencesEditorClickToJumpToEntry -> "单击跳转到条目" + PreferencesEditorClickToJumpToEntryDescription -> + "在多条目编辑模式下,单击画布上的条目即可将其设为当前条目。" PreferencesEditorCascadedDrag -> "级联拖动" PreferencesEditorCascadedDragDescription -> "选择是否启用对平行子项目中边界的级联拖动。\n" + diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt index d8691f90..ccb9533c 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt @@ -517,6 +517,9 @@ fun Strings.en(): String = when (this) { PreferencesEditorLockedSettingParameterWithCursorDescription -> "Apply the fixed-drag setting above also when " + "setting the parameters with \"Set Parameter To Cursor Position\" key actions" + PreferencesEditorClickToJumpToEntry -> "Click to jump to entry" + PreferencesEditorClickToJumpToEntryDescription -> + "In multiple entry edit mode, click an entry on the canvas to make it the current entry" PreferencesEditorCascadedDrag -> "Cascaded drag" PreferencesEditorCascadedDragDescription -> "Select whether to enable cascaded drag to borders in parallel subprojects.\n" + diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt index a89bfa69..465b7773 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt @@ -487,6 +487,9 @@ fun Strings.ja(): String? = when (this) { PreferencesEditorLockedSettingParameterWithCursor -> "カーソル位置に設定する場合も連動ドラッグを有効にする" PreferencesEditorLockedSettingParameterWithCursorDescription -> "「パラメータをカーソル位置に設定」ショートカットでパラメータを設定するときにも、連動ドラッグ設定を適用します。" + PreferencesEditorClickToJumpToEntry -> "クリックでエントリーへ移動" + PreferencesEditorClickToJumpToEntryDescription -> + "複数エントリー編集モードで、キャンバス上のエントリーをクリックすると現在のエントリーになります。" PreferencesEditorCascadedDrag -> "カスケードドラッグ" PreferencesEditorCascadedDragDescription -> "並列サブプロジェクトの枠線に対するカスケードドラッグを有効にするかどうかを選択します。\n" + diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt index d49f351e..3e194798 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt @@ -506,6 +506,9 @@ fun Strings.ko(): String? = when (this) { PreferencesEditorLockedSettingParameterWithCursorDescription -> "\"n번 마커 놓기 (커서 위치에)\" 입력으로 마커를 놓을 경우에도 " + "위의 고정 드래그 방식을 적용하기" + PreferencesEditorClickToJumpToEntry -> "클릭하여 엔트리로 이동" + PreferencesEditorClickToJumpToEntryDescription -> + "다중 엔트리 편집 모드에서 캔버스의 엔트리를 클릭하면 현재 엔트리로 설정됩니다." PreferencesEditorCascadedDrag -> "캐스케이드 드래그" PreferencesEditorCascadedDragDescription -> "병렬 하위 프로젝트의 테두리에 대해 캐스케이드 드래그를 활성화할지 선택합니다.\n" + diff --git a/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt b/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt index db6c9832..fb87ca34 100644 --- a/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt +++ b/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt @@ -271,6 +271,31 @@ class MarkerStateMiscTest { // endregion + // region getEntryIndexByCursorPosition + + @Test + fun getEntryIndexByCursorPositionReturnsEntryContainingPosition() { + val state = continuousState() + assertEquals(0, state.getEntryIndexByCursorPosition(150f)) + assertEquals(1, state.getEntryIndexByCursorPosition(300f)) + } + + @Test + fun getEntryIndexByCursorPositionReturnsFirstMatchOnSharedBorder() { + val state = continuousState() + // 200 is both entry "a"'s end and entry "b"'s start; the first containing entry wins + assertEquals(0, state.getEntryIndexByCursorPosition(200f)) + } + + @Test + fun getEntryIndexByCursorPositionReturnsNullWhenOutsideAllEntries() { + val state = continuousState() + assertNull(state.getEntryIndexByCursorPosition(50f)) + assertNull(state.getEntryIndexByCursorPosition(900f)) + } + + // endregion + // region computeCascadeEditions private fun cascadeState(): Pair> {