Skip to content

Commit

Permalink
feat: move between controls using shortcut keys #548
Browse files Browse the repository at this point in the history
  • Loading branch information
Hufe921 committed May 22, 2024
1 parent f4dd90b commit c6c2f98
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 12 deletions.
6 changes: 5 additions & 1 deletion docs/en/guide/shortcut-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ Feature: Exit format brush

## Tab

Feature: Increase indent
Feature: Increase indent/Move next control

## Shift + Tab

Feature: Move previous control

## Ctrl/Cmd + Z

Expand Down
6 changes: 5 additions & 1 deletion docs/guide/shortcut-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@

## Tab

功能:增加缩进
功能:增加缩进/移动到下一个控件

## Shift + Tab

功能:移动到上一个控件

## Ctrl/Cmd + Z

Expand Down
265 changes: 264 additions & 1 deletion src/editor/core/draw/control/Control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
IControlRuleOption,
IGetControlValueOption,
IGetControlValueResult,
IInitNextControlOption,
INextControlContext,
IRepaintControlOption,
ISetControlExtensionOption,
ISetControlProperties,
Expand All @@ -38,6 +40,7 @@ import { ControlSearch } from './interactive/ControlSearch'
import { ControlBorder } from './richtext/Border'
import { SelectControl } from './select/SelectControl'
import { TextControl } from './text/TextControl'
import { MoveDirection } from '../../../dataset/enum/Observer'

interface IMoveCursorResult {
newIndex: number
Expand Down Expand Up @@ -196,7 +199,8 @@ export class Control {
public getPreY(): number {
const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap()
return this.draw.getPageNo() * (height + pageGap)
const pageNo = this.getPosition()?.pageNo ?? this.draw.getPageNo()
return pageNo * (height + pageGap)
}

public getRange(): IRange {
Expand Down Expand Up @@ -809,4 +813,263 @@ export class Control {
public drawBorder(ctx: CanvasRenderingContext2D) {
this.controlBorder.render(ctx)
}

public getPreControlContext(): INextControlContext | null {
if (!this.activeControl) return null
const position = this.draw.getPosition()
const positionContext = position.getPositionContext()
if (!positionContext) return null
const controlElement = this.activeControl.getElement()
// 获取上一个控件上下文本信息
function getPreContext(
elementList: IElement[],
start: number
): INextControlContext | null {
for (let e = start; e > 0; e--) {
const element = elementList[e]
// 表格元素
if (element.type === ElementType.TABLE) {
const trList = element.trList || []
for (let r = trList.length - 1; r >= 0; r--) {
const tr = trList[r]
const tdList = tr.tdList
for (let d = tdList.length - 1; d >= 0; d--) {
const td = tdList[d]
const context = getPreContext(td.value, td.value.length - 1)
if (context) {
return {
positionContext: {
isTable: true,
index: e,
trIndex: r,
tdIndex: d,
tdId: td.id,
trId: tr.id,
tableId: element.id
},
nextIndex: context.nextIndex
}
}
}
}
}
if (
!element.controlId ||
element.controlId === controlElement.controlId
) {
continue
}
// 找到尾部第一个非占位符元素
let nextIndex = e
while (nextIndex > 0) {
const nextElement = elementList[nextIndex]
if (
nextElement.controlComponent === ControlComponent.VALUE ||
nextElement.controlComponent === ControlComponent.PREFIX
) {
break
}
nextIndex--
}
return {
positionContext: {
isTable: false
},
nextIndex
}
}
return null
}
// 当前上下文控件信息
const { startIndex } = this.range.getRange()
const elementList = this.getElementList()
const context = getPreContext(elementList, startIndex)
if (context) {
return {
positionContext: positionContext.isTable
? positionContext
: context.positionContext,
nextIndex: context.nextIndex
}
}
// 控件在单元内时继续循环
if (controlElement.tableId) {
const originalElementList = this.draw.getOriginalElementList()
const { index, trIndex, tdIndex } = positionContext
const trList = originalElementList[index!].trList!
for (let r = trIndex!; r >= 0; r--) {
const tr = trList[r]
const tdList = tr.tdList
for (let d = tdList.length - 1; d >= 0; d--) {
if (trIndex === r && d >= tdIndex!) continue
const td = tdList[d]
const context = getPreContext(td.value, td.value.length - 1)
if (context) {
return {
positionContext: {
isTable: true,
index: positionContext.index,
trIndex: r,
tdIndex: d,
tdId: td.id,
trId: tr.id,
tableId: controlElement.tableId
},
nextIndex: context.nextIndex
}
}
}
}
// 跳出表格继续循环
const context = getPreContext(originalElementList, index! - 1)
if (context) {
return {
positionContext: {
isTable: false
},
nextIndex: context.nextIndex
}
}
}
return null
}

public getNextControlContext(): INextControlContext | null {
if (!this.activeControl) return null
const position = this.draw.getPosition()
const positionContext = position.getPositionContext()
if (!positionContext) return null
const controlElement = this.activeControl.getElement()
// 获取下一个控件上下文本信息
function getNextContext(
elementList: IElement[],
start: number
): INextControlContext | null {
for (let e = start; e < elementList.length; e++) {
const element = elementList[e]
// 表格元素
if (element.type === ElementType.TABLE) {
const trList = element.trList || []
for (let r = 0; r < trList.length; r++) {
const tr = trList[r]
const tdList = tr.tdList
for (let d = 0; d < tdList.length; d++) {
const td = tdList[d]
const context = getNextContext(td.value!, 0)
if (context) {
return {
positionContext: {
isTable: true,
index: e,
trIndex: r,
tdIndex: d,
tdId: td.id,
trId: tr.id,
tableId: element.id
},
nextIndex: context.nextIndex
}
}
}
}
}
if (
!element.controlId ||
element.controlId === controlElement.controlId
) {
continue
}
return {
positionContext: {
isTable: false
},
nextIndex: e
}
}
return null
}
// 当前上下文控件信息
const { endIndex } = this.range.getRange()
const elementList = this.getElementList()
const context = getNextContext(elementList, endIndex)
if (context) {
return {
positionContext: positionContext.isTable
? positionContext
: context.positionContext,
nextIndex: context.nextIndex
}
}
// 控件在单元内时继续循环
if (controlElement.tableId) {
const originalElementList = this.draw.getOriginalElementList()
const { index, trIndex, tdIndex } = positionContext
const trList = originalElementList[index!].trList!
for (let r = trIndex!; r < trList.length; r++) {
const tr = trList[r]
const tdList = tr.tdList
for (let d = 0; d < tdList.length; d++) {
if (trIndex === r && d <= tdIndex!) continue
const td = tdList[d]
const context = getNextContext(td.value, 0)
if (context) {
return {
positionContext: {
isTable: true,
index: positionContext.index,
trIndex: r,
tdIndex: d,
tdId: td.id,
trId: tr.id,
tableId: controlElement.tableId
},
nextIndex: context.nextIndex
}
}
}
}
// 跳出表格继续循环
const context = getNextContext(originalElementList, index! + 1)
if (context) {
return {
positionContext: {
isTable: false
},
nextIndex: context.nextIndex
}
}
}
return null
}

public initNextControl(option: IInitNextControlOption = {}) {
const { direction = MoveDirection.DOWN } = option
let context: INextControlContext | null = null
if (direction === MoveDirection.UP) {
context = this.getPreControlContext()
} else {
context = this.getNextControlContext()
}
if (!context) return
const { nextIndex, positionContext } = context
const position = this.draw.getPosition()
// 设置上下文
position.setPositionContext(positionContext)
this.draw.getRange().replaceRange({
startIndex: nextIndex,
endIndex: nextIndex
})
// 重新渲染并定位
this.draw.render({
curIndex: nextIndex,
isCompute: false,
isSetCursor: true,
isSubmitHistory: false
})
const positionList = position.getPositionList()
this.draw.getCursor().moveCursorToVisible({
cursorPosition: positionList[nextIndex],
direction
})
}
}
15 changes: 15 additions & 0 deletions src/editor/core/event/handlers/keydown/left.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { EditorMode } from '../../../..'
import { ControlComponent } from '../../../../dataset/enum/Control'
import { ElementType } from '../../../../dataset/enum/Element'
import { MoveDirection } from '../../../../dataset/enum/Observer'
import { isMod } from '../../../../utils/hotkey'
import { CanvasEvent } from '../../CanvasEvent'

Expand All @@ -16,6 +19,18 @@ export function left(evt: KeyboardEvent, host: CanvasEvent) {
const { startIndex, endIndex } = rangeManager.getRange()
const isCollapsed = rangeManager.getIsCollapsed()
const elementList = draw.getElementList()
// 表单模式下控件移动
const control = draw.getControl()
if (
draw.getMode() === EditorMode.FORM &&
control.getActiveControl() &&
elementList[index]?.controlComponent === ControlComponent.PREFIX
) {
control.initNextControl({
direction: MoveDirection.UP
})
return
}
// 单词整体移动
let moveCount = 1
if (isMod(evt)) {
Expand Down
15 changes: 15 additions & 0 deletions src/editor/core/event/handlers/keydown/right.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { ControlComponent } from '../../../../dataset/enum/Control'
import { EditorMode } from '../../../../dataset/enum/Editor'
import { ElementType } from '../../../../dataset/enum/Element'
import { MoveDirection } from '../../../../dataset/enum/Observer'
import { isMod } from '../../../../utils/hotkey'
import { CanvasEvent } from '../../CanvasEvent'

Expand All @@ -17,6 +20,18 @@ export function right(evt: KeyboardEvent, host: CanvasEvent) {
const { startIndex, endIndex } = rangeManager.getRange()
const isCollapsed = rangeManager.getIsCollapsed()
let elementList = draw.getElementList()
// 表单模式下控件移动
const control = draw.getControl()
if (
draw.getMode() === EditorMode.FORM &&
control.getActiveControl() &&
elementList[index + 1]?.controlComponent === ControlComponent.POSTFIX
) {
control.initNextControl({
direction: MoveDirection.DOWN
})
return
}
// 单词整体移动
let moveCount = 1
if (isMod(evt)) {
Expand Down
Loading

0 comments on commit c6c2f98

Please sign in to comment.