Skip to content

Commit

Permalink
feat: Arrow
Browse files Browse the repository at this point in the history
  • Loading branch information
sheepbox8646 committed Jun 4, 2024
1 parent ca0abdf commit fbbf89a
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 14 deletions.
2 changes: 1 addition & 1 deletion examples/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const circle = use(nc.createPolygon([
circle.animate(nc.move(100, 100)(120))

const scene = nc.createScene(
circle
use(nc.createArrow([0, 0], [100, 100]))
)

app.checkout(scene)
Expand Down
2 changes: 2 additions & 0 deletions packages/basic/src/widgets/arc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface Arc extends Path {

export function createArc(radius: number, from: number, to: number, options?: ArcOptions) {
return defineWidgetBuilder<Arc>((ck) => {
options ??= {}
options.style ??= {}
const path = createPath(options ?? {})(ck)
const rect = ck.LTRBRect(
-radius,
Expand Down
96 changes: 96 additions & 0 deletions packages/basic/src/widgets/arrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { changed, def, defineWidgetBuilder } from '@newcar/core'
import { deepMerge } from '@newcar/utils'
import type { Vector2 } from '../utils/vector2'
import { type FigureOptions, type FigureStyle, createFigure } from './figure'
import { createPolygon } from './polygon'
import { createLine } from './line'

/**
* Calculates the rotation angle for an arrow based on the line's start and end points,
* with the angle expressed in degrees. The angle is calculated with respect
* to the horizontal axis pointing to the right.
*
* @param startPoint The starting point of the line.
* @param endPoint The ending point of the line.
* @returns The rotation angle in degrees, where 0 degrees points to the right (east),
* and positive angles are measured clockwise.
*/
function calculateArrowRotationAngle(
startPoint: Vector2,
endPoint: Vector2,
): number {
// Calculate the differences in the x and y coordinates
const dx = endPoint[0] - startPoint[0]
const dy = endPoint[1] - startPoint[1]

// Calculate the angle in radians using Math.atan2(dy, dx)
const angleRadians = Math.atan2(dy, dx)

// Convert the angle to degrees
let angleDegrees = angleRadians * (180 / Math.PI)

// Normalize the angle to the range [0, 360)
if (angleDegrees < 0)
angleDegrees += 360

return angleDegrees
}

/**
* Represents options that can be applied to an arrow figure.
*/
export interface ArrowOptions extends FigureOptions {
style?: ArrowStyle
}

/**
* Represents the style properties that can be applied to an arrow figure.
*/
export interface ArrowStyle extends FigureStyle { }

export function createArrow(from: Vector2, to: Vector2, options?: ArrowOptions) {
return defineWidgetBuilder((ck) => {
options ??= {}
options.style ??= {}
const figure = createFigure(options)(ck)
const fromProp = def(from)
const toProp = def(to)

const tip = createPolygon([
[0, 10],
[22, 0],
[0, -10],
], {
x: to[0],
y: to[1],
style: {
scaleX: from[0] > to[0] ? -1 : 1,
scaleY: from[1] > to[1] ? -1 : 1,
rotation: calculateArrowRotationAngle(from, to),
...options.style,
},
})(ck)
const stem = createLine(from, to, {
style: {
color: options.style.borderColor,
width: options.style.borderWidth,
...options.style,
},
})(ck)

function reset() {
const radian = calculateArrowRotationAngle(fromProp.value, toProp.value)
tip.style.rotation.value = radian
}

changed(fromProp, reset)
changed(toProp, reset)

figure.add(stem, tip)

return deepMerge(figure, {
from: fromProp,
to: toProp,
})
})
}
2 changes: 2 additions & 0 deletions packages/basic/src/widgets/circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface Circle extends Arc {}

export function createCircle(radius: number, options?: ArcOptions) {
return defineWidgetBuilder((ck) => {
options ??= {}
options.style ??= {}
const arc = createArc(radius, 0, 360, options)(ck)
const radiusProp = def(radius)

Expand Down
1 change: 1 addition & 0 deletions packages/basic/src/widgets/figure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface Figure extends Base {

export function createFigure(options?: FigureOptions) {
return defineWidgetBuilder<Figure>((ck) => {
options ??= {}
const base = createBase(options ?? {})(ck)
options.style ??= {}
const style = {
Expand Down
1 change: 1 addition & 0 deletions packages/basic/src/widgets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './path'
export * from './rect'
export * from './line'
export * from './polygon'
export * from './arrow'
23 changes: 17 additions & 6 deletions packages/basic/src/widgets/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ export interface Line extends Path {

export function createLine(from: Vector2, to: Vector2, options?: LineOptions) {
return defineWidgetBuilder<Line>((ck) => {
const path = createPath(options)(ck)
options ??= {}
options.style ??= {}
const path = createPath(deepMerge(options, {
style: {
border: true,
borderWidth: options.style.width,
fill: false,
},
}))(ck)

const fromProp = def(from)
const toProp = def(to)
Expand All @@ -34,17 +42,20 @@ export function createLine(from: Vector2, to: Vector2, options?: LineOptions) {
width: def(options.style.width ?? 2),
}

path.path.moveTo(from[0], from[1])
path.path.lineTo(to[0], to[1])
path.path.moveTo(...from)
path.path.lineTo(...to)

function reset(v: Prop<Vector2>) {
function reset(_v: Prop<Vector2>) {
path.path.reset()
path.path.moveTo(v.value[0], v.value[1])
path.path.lineTo(v.value[0], v.value[1])
path.path.moveTo(...fromProp.value)
path.path.lineTo(...fromProp.value)
}

changed(fromProp, reset)
changed(toProp, reset)
changed(style.width, (v) => {
path.style.borderWidth.value = v.value
})

return deepMerge(path, {
style,
Expand Down
2 changes: 2 additions & 0 deletions packages/basic/src/widgets/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface Path extends Figure {

export function createPath(options?: PathOptions) {
return defineWidgetBuilder<Path>((ck) => {
options ??= {}
options.style ??= {}
const figure = createFigure(options ?? {})(ck)

const path = new ck.Path()
Expand Down
13 changes: 11 additions & 2 deletions packages/basic/src/widgets/polygon.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import type { ConvertToProp, Prop } from '@newcar/core'
import { changed, def, defineWidgetBuilder } from '@newcar/core'
import { deepMerge } from '@newcar/utils'
import type { Vector2 } from '../utils/vector2'
import { type PathOptions, type PathStyle, createPath } from './path'
import type { Path, PathOptions, PathStyle } from './path'
import { createPath } from './path'

export interface PolygonOptions extends PathOptions {
style?: PolygonStyle
}

export interface PolygonStyle extends PathStyle {}

export interface Polygon extends Path {
style: ConvertToProp<PolygonStyle>
points: Prop<Vector2>[]
}

export function createPolygon(points: Vector2[], options?: PolygonOptions) {
return defineWidgetBuilder((ck) => {
return defineWidgetBuilder<Polygon>((ck) => {
options ??= {}
options.style ??= {}
const path = createPath(options)(ck)
const pointsProp = points.map(point => def(point))
let index = 0
Expand Down
2 changes: 2 additions & 0 deletions packages/basic/src/widgets/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export interface Rect extends Path {

export function createRect(width: number, length: number, options: RectOptions) {
return defineWidgetBuilder<Rect>((ck) => {
options ??= {}
options.style ??= {}
const widthProp = def(width)
const lengthProp = def(length)

Expand Down
11 changes: 6 additions & 5 deletions packages/core/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ export function createBase(options: BaseOptions) {
progress,
style,
children,
animates,
render,
add(...children: Widget[]) {
children.push(...children)
this.children.push(...children)
return this
},
animate<T extends Widget>(animate: Animate<T>) {
animates.push(animate)
this.animates.push(animate)
return this
},
update,
Expand Down Expand Up @@ -103,12 +104,12 @@ export function createBase(options: BaseOptions) {
current = undefined
}
}
for (const child of children) {
child.update(canvas, elapsed, child.render)
}
canvas.translate(x.value, y.value)
canvas.rotate(style.rotation.value, centerX.value, centerY.value)
canvas.scale(style.scaleX.value, style.scaleY.value)
for (const child of children) {
child.update(canvas, elapsed, child.render)
}
renderFunction(canvas)
canvas.restore()
}
Expand Down

0 comments on commit fbbf89a

Please sign in to comment.