import type { PossibleColor, PossibleVector2, SignalValue, SimpleSignal, Vector2Signal, } from '@revideo/core'; import {Color, unwrap} from '@revideo/core'; import {computed} from '../decorators/computed'; import {initial, initializeSignals, signal} from '../decorators/signal'; import {vector2Signal} from '../decorators/vector2Signal'; export type GradientType = 'linear' | 'conic' | 'radial'; export interface GradientStop { offset: SignalValue; color: SignalValue; } export interface GradientProps { type?: SignalValue; fromX?: SignalValue; fromY?: SignalValue; from?: SignalValue; toX?: SignalValue; toY?: SignalValue; to?: SignalValue; angle?: SignalValue; fromRadius?: SignalValue; toRadius?: SignalValue; stops?: GradientStop[]; } export class Gradient { @initial('linear') @signal() public declare readonly type: SimpleSignal; @vector2Signal('from') public declare readonly from: Vector2Signal; @vector2Signal('to') public declare readonly to: Vector2Signal; @initial(0) @signal() public declare readonly angle: SimpleSignal; @initial(0) @signal() public declare readonly fromRadius: SimpleSignal; @initial(0) @signal() public declare readonly toRadius: SimpleSignal; @initial([]) @signal() public declare readonly stops: SimpleSignal; public constructor(props: GradientProps) { initializeSignals(this, props); } @computed() public canvasGradient(context: CanvasRenderingContext2D): CanvasGradient { let gradient: CanvasGradient; switch (this.type()) { case 'linear': gradient = context.createLinearGradient( this.from.x(), this.from.y(), this.to.x(), this.to.y(), ); break; case 'conic': gradient = context.createConicGradient( this.angle(), this.from.x(), this.from.y(), ); break; case 'radial': gradient = context.createRadialGradient( this.from.x(), this.from.y(), this.fromRadius(), this.to.x(), this.to.y(), this.toRadius(), ); break; } for (const {offset, color} of this.stops()) { gradient.addColorStop( unwrap(offset), new Color(unwrap(color)).serialize(), ); } return gradient; } }