mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
refactor: default-tool box selection (#11800)
### Changed - Rewrite box selection in `default-tool`, the view can decide whether to be selected in box selection by return a boolean value in `onBoxSelected` method - Cleanup unnecessary states in `default-tool` and some naming problem
This commit is contained in:
@@ -16,6 +16,7 @@ export { GfxExtension, GfxExtensionIdentifier } from './extension.js';
|
||||
export { GridManager } from './grid.js';
|
||||
export { GfxControllerIdentifier } from './identifiers.js';
|
||||
export type {
|
||||
BoxSelectionContext,
|
||||
DragEndContext,
|
||||
DragExtensionInitializeContext,
|
||||
DragInitializationOption,
|
||||
|
||||
@@ -10,6 +10,7 @@ export type {
|
||||
ExtensionDragStartContext,
|
||||
} from './types/drag.js';
|
||||
export type {
|
||||
BoxSelectionContext,
|
||||
DragEndContext,
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { Bound, Point } from '@blocksuite/global/gfx';
|
||||
|
||||
import type { PointerEventState } from '../../event/state/pointer.js';
|
||||
import { getTopElements } from '../../utils/tree.js';
|
||||
import { GfxExtension, GfxExtensionIdentifier } from '../extension.js';
|
||||
import type { GfxModel } from '../model/model.js';
|
||||
import { createInteractionContext, type SupportedEvents } from './event.js';
|
||||
@@ -20,6 +21,7 @@ import type {
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
} from './types/drag.js';
|
||||
import type { BoxSelectionContext } from './types/view.js';
|
||||
|
||||
type ExtensionPointerHandler = Exclude<
|
||||
SupportedEvents,
|
||||
@@ -90,7 +92,12 @@ export class InteractivityManager extends GfxExtension {
|
||||
};
|
||||
}
|
||||
|
||||
dispatchOnSelected(evt: PointerEventState) {
|
||||
/**
|
||||
* Handle element selection.
|
||||
* @param evt The pointer event that triggered the selection.
|
||||
* @returns True if the element was selected, false otherwise.
|
||||
*/
|
||||
handleElementSelection(evt: PointerEventState) {
|
||||
const { raw } = evt;
|
||||
const { gfx } = this;
|
||||
const [x, y] = gfx.viewport.toModelCoordFromClientCoord([raw.x, raw.y]);
|
||||
@@ -122,15 +129,30 @@ export class InteractivityManager extends GfxExtension {
|
||||
return false;
|
||||
}
|
||||
|
||||
handleBoxSelection(context: { box: BoxSelectionContext['box'] }) {
|
||||
const elements = this.gfx.getElementsByBound(context.box).filter(model => {
|
||||
const view = this.gfx.view.get(model);
|
||||
|
||||
if (
|
||||
!view ||
|
||||
view.onBoxSelected({
|
||||
box: context.box,
|
||||
}) === false
|
||||
)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return getTopElements(elements).filter(elm => !elm.isLocked());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize drag operation for elements.
|
||||
* Handles drag start, move and end events automatically.
|
||||
* Initialize elements movements.
|
||||
* It will handle drag start, move and end events automatically.
|
||||
* Note: Call this when mouse is already down.
|
||||
*
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
initializeDrag(options: DragInitializationOption) {
|
||||
handleElementMove(options: DragInitializationOption) {
|
||||
let cancelledByExt = false;
|
||||
|
||||
const context: DragExtensionInitializeContext = {
|
||||
@@ -294,7 +316,7 @@ export class InteractivityManager extends GfxExtension {
|
||||
dragStart();
|
||||
}
|
||||
|
||||
requestElementsClone(options: RequestElementsCloneContext) {
|
||||
requestElementClone(options: RequestElementsCloneContext) {
|
||||
const extensions = this.interactExtensions;
|
||||
|
||||
for (let ext of extensions.values()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Bound, IPoint } from '@blocksuite/global/gfx';
|
||||
import type { Bound, IBound, IPoint } from '@blocksuite/global/gfx';
|
||||
|
||||
import type { GfxBlockComponent } from '../../../view/element/gfx-block-component.js';
|
||||
import type { GfxModel } from '../../model/model.js';
|
||||
@@ -56,11 +56,25 @@ export type SelectedContext = {
|
||||
position: IPoint;
|
||||
|
||||
/**
|
||||
* If the current selection is a fallback selection, like selecting the element inside a group, the group will be selected instead
|
||||
* If the current selection is a fallback selection.
|
||||
*
|
||||
* E.g., if selecting a child element inside a group, the `onSelected` method will be executed on group, and
|
||||
* the fallback is true because the it's not the original target(the child element).
|
||||
*/
|
||||
fallback: boolean;
|
||||
};
|
||||
|
||||
export type BoxSelectionContext = {
|
||||
box: Readonly<
|
||||
IBound & {
|
||||
startX: number;
|
||||
startY: number;
|
||||
endX: number;
|
||||
endY: number;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
export type GfxViewTransformInterface = {
|
||||
onDragStart: (context: DragStartContext) => void;
|
||||
onDragMove: (context: DragMoveContext) => void;
|
||||
@@ -70,8 +84,11 @@ export type GfxViewTransformInterface = {
|
||||
|
||||
/**
|
||||
* When the element is selected by the pointer
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
onSelected: (context: SelectedContext) => void;
|
||||
|
||||
/**
|
||||
* When the element is selected by box selection, return false to prevent the default selection behavior.
|
||||
*/
|
||||
onBoxSelected: (context: BoxSelectionContext) => boolean | void;
|
||||
};
|
||||
|
||||
@@ -204,12 +204,11 @@ export class GfxSelectionManager extends GfxExtension {
|
||||
return selections.every(sel => sel.elements.length === 0);
|
||||
}
|
||||
|
||||
isInSelectedRect(viewX: number, viewY: number) {
|
||||
isInSelectedRect(modelX: number, modelY: number) {
|
||||
const selected = this.selectedElements;
|
||||
if (!selected.length) return false;
|
||||
|
||||
const commonBound = getCommonBoundWithRotation(selected);
|
||||
const [modelX, modelY] = this.gfx.viewport.toModelCoord(viewX, viewY);
|
||||
if (commonBound && commonBound.isPointInBound([modelX, modelY])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import type { IBound, IPoint } from '@blocksuite/global/gfx';
|
||||
import { Signal } from '@preact/signals-core';
|
||||
import { computed, Signal } from '@preact/signals-core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import type { PointerEventState } from '../../event/index.js';
|
||||
@@ -125,6 +125,18 @@ export class ToolController extends GfxExtension {
|
||||
y: 0,
|
||||
});
|
||||
|
||||
readonly lastMouseModelPos$ = computed(() => {
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
||||
this.lastMousePos$.value.x,
|
||||
this.lastMousePos$.value.y
|
||||
);
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
};
|
||||
});
|
||||
|
||||
get currentTool$() {
|
||||
// oxlint-disable-next-line typescript/no-this-alias
|
||||
const self = this;
|
||||
@@ -330,6 +342,10 @@ export class ToolController extends GfxExtension {
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
this.lastMousePos$.value = {
|
||||
x: evt.x,
|
||||
y: evt.y,
|
||||
};
|
||||
|
||||
// this means the dragEnd event is not even fired
|
||||
// so we need to manually call the dragEnd method
|
||||
@@ -372,6 +388,11 @@ export class ToolController extends GfxExtension {
|
||||
endY: evt.y,
|
||||
};
|
||||
|
||||
this.lastMousePos$.value = {
|
||||
x: evt.x,
|
||||
y: evt.y,
|
||||
};
|
||||
|
||||
invokeToolHandler('dragMove', evt, dragContext?.tool);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { type Container, createIdentifier } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import type { Bound, IVec } from '@blocksuite/global/gfx';
|
||||
import { type Bound, type IVec } from '@blocksuite/global/gfx';
|
||||
import type { Extension } from '@blocksuite/store';
|
||||
|
||||
import type { PointerEventState } from '../../event/index.js';
|
||||
import type { EditorHost } from '../../view/index.js';
|
||||
import type { GfxController } from '../index.js';
|
||||
import type {
|
||||
BoxSelectionContext,
|
||||
DragEndContext,
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
@@ -221,6 +222,8 @@ export class GfxElementModelView<
|
||||
}
|
||||
}
|
||||
|
||||
onBoxSelected(_: BoxSelectionContext): boolean | void {}
|
||||
|
||||
onResize = () => {};
|
||||
|
||||
onRotate = () => {};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { nothing } from 'lit';
|
||||
import type { BlockService } from '../../extension/index.js';
|
||||
import { GfxControllerIdentifier } from '../../gfx/identifiers.js';
|
||||
import type {
|
||||
BoxSelectionContext,
|
||||
DragMoveContext,
|
||||
GfxViewTransformInterface,
|
||||
SelectedContext,
|
||||
@@ -113,6 +114,8 @@ export abstract class GfxBlockComponent<
|
||||
return true;
|
||||
}
|
||||
|
||||
onBoxSelected(_: BoxSelectionContext) {}
|
||||
|
||||
onRotate() {}
|
||||
|
||||
onResize() {}
|
||||
@@ -231,6 +234,8 @@ export function toGfxBlockComponent<
|
||||
return true;
|
||||
}
|
||||
|
||||
onBoxSelected(_: BoxSelectionContext) {}
|
||||
|
||||
onRotate() {}
|
||||
|
||||
onResize() {}
|
||||
|
||||
Reference in New Issue
Block a user