Skip to content
This repository has been archived by the owner on Mar 4, 2024. It is now read-only.

Commit

Permalink
feat: basic node moving and dragging
Browse files Browse the repository at this point in the history
  • Loading branch information
prescientmoon committed Jun 15, 2020
1 parent b1a58d4 commit f014b13
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 22 deletions.
5 changes: 3 additions & 2 deletions src/Component/Editor/Scene.purs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import Effect.Aff.Class (class MonadAff)
import Effect.Class (class MonadEffect, liftEffect)
import Halogen (Component, HalogenM, RefLabel(..), defaultEval, getHTMLElementRef, gets, mkComponent, mkEval, modify_, subscribe)
import Halogen.HTML as HH
import Halogen.HTML.Events (onMouseMove, onMouseUp)
import Halogen.HTML.Events (onMouseDown, onMouseMove, onMouseUp)
import Halogen.HTML.Properties as HP
import Halogen.Query.EventSource as ES
import Lunarbox.Config (Config)
import Lunarbox.Data.Editor.Node.NodeId (NodeId)
import Lunarbox.Foreign.Render (Context2d, GeomEventHandler, GeomteryCache, NodeRenderingData, getContext, handleMouseMove, handleMouseUp, loadNodes, renderScene, resizeCanvas, resizeContext)
import Lunarbox.Foreign.Render (Context2d, GeomEventHandler, GeomteryCache, NodeRenderingData, getContext, handleMouseDown, handleMouseMove, handleMouseUp, loadNodes, renderScene, resizeCanvas, resizeContext)
import Web.Event.Event (EventType(..))
import Web.HTML as Web
import Web.HTML.HTMLCanvasElement as HTMLCanvasElement
Expand Down Expand Up @@ -110,4 +110,5 @@ component =
[ HP.ref canvasRef
, onMouseMove $ Just <<< HandleEvent handleMouseMove
, onMouseUp $ Just <<< HandleEvent handleMouseUp
, onMouseDown $ Just <<< HandleEvent handleMouseDown
]
2 changes: 2 additions & 0 deletions src/Foreign/Render.purs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ foreign import handleMouseMove :: GeomEventHandler

foreign import handleMouseUp :: GeomEventHandler

foreign import handleMouseDown :: GeomEventHandler

instance defaultGeomtryCache :: Default GeomteryCache where
def = emptyGeometryCache

Expand Down
1 change: 1 addition & 0 deletions src/Foreign/Render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ export const resizeContext = (ctx: CanvasRenderingContext2D) =>
// Reexports with a few changed names
export const handleMouseMove = Native.onMouseMove
export const handleMouseUp = Native.onMouseUp
export const handleMouseDown = Native.onMouseDown
2 changes: 1 addition & 1 deletion src/typescript/mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ export const enum MouseButtons {
* @param bits The bits from the event object.
*/
export const isPressed = (bits: number) => (button: MouseButtons) =>
bits !== (0 & button)
bits & button
137 changes: 119 additions & 18 deletions src/typescript/render.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import * as g from "@thi.ng/geom"
import { walk } from "@thi.ng/hdom-canvas"
import { Mat23Like, transform23, mulV23 } from "@thi.ng/matrices"
import {
Mat23Like,
transform23,
mulV23,
concat,
translation23,
invert23
} from "@thi.ng/matrices"
import type {
GeometryCache,
NodeData,
NodeId,
NodeGeometry
} from "./types/Node"
import { Vec2Like, distSq2, Vec, dist } from "@thi.ng/vectors"
import { Vec2Like, distSq2, Vec, dist, add2 } from "@thi.ng/vectors"
import * as Arc from "./arcs"
import {
nodeRadius,
Expand All @@ -23,7 +30,8 @@ import {
import { TAU } from "@thi.ng/math"
import { ADT } from "ts-adt"
import { Type } from "@thi.ng/geom-api"
import { closestPoint, withAttribs } from "@thi.ng/geom"
import { closestPoint, withAttribs, translate } from "@thi.ng/geom"
import { isPressed, MouseButtons } from "./mouse"

// Used in the Default purescript implementation of GeomCache
export const emptyGeometryCache: GeometryCache = {
Expand Down Expand Up @@ -105,32 +113,50 @@ export const renderNode = (
alpha: nodeBackgroundOpacity
})

return { inputs: inputGeom, output, background }
return { inputs: inputGeom, output, background, position: node.position }
}

/**
* Returns a transform matrix moving the canvas by half it's size
*
* @param ctx The canvas rendering context to get the middle of.
* @param cache The cache to get the camera from.
*/
const getTransform = (ctx: CanvasRenderingContext2D) => {
return transform23(null, [ctx.canvas.width / 2, ctx.canvas.height / 2], 0, 1)
const getTransform = (ctx: CanvasRenderingContext2D, cache: GeometryCache) => {
const transform = transform23(
null,
[ctx.canvas.width / 2, ctx.canvas.height / 2],
0,
1
)

concat(null, transform, cache.camera)

return transform
}

/**
* Returns a transform matrix moving the mouse to world coordinates
*
* @param ctx The canvas rendering context
* @param cache The cache to use the camera from
*/
const getMouseTransform = (ctx: CanvasRenderingContext2D) => {
const getMouseTransform = (
ctx: CanvasRenderingContext2D,
cache: GeometryCache
) => {
const bounds = ctx.canvas.getBoundingClientRect()

return transform23(
const transform = transform23(
null,
[-bounds.left - bounds.width / 2, -bounds.height / 2],
0,
1
)

concat(null, transform, invert23([], cache.camera)!)

return transform
}

export const renderScene = (
Expand All @@ -140,7 +166,7 @@ export const renderScene = (
ctx.resetTransform()
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)

const matrix = getTransform(ctx)
const matrix = getTransform(ctx, cache)

const nodes = [
...cache.nodes.values()
Expand Down Expand Up @@ -285,11 +311,14 @@ const getMouseTarget = (
}
}

export const selectNode = (
cache: GeometryCache,
node: NodeGeometry,
id: NodeId
) => {
/**
* Selects a particular node in a geometry.
*
* @param cache The cache to mutate.
* @param node The geometry of the node to select
* @param id THe id of the node to select
*/
const selectNode = (cache: GeometryCache, node: NodeGeometry, id: NodeId) => {
cache.selectedNodes.add(id)

if (cache.selectedNode === node) {
Expand All @@ -300,26 +329,85 @@ export const selectNode = (
}

/**
* Handle a mouseMove event
* Unselect a particular node from a geometry.
*
* @param cache The cache to mutate
* @param node THe geometry of the node to unselect
* @param id THe id of the node to unselect
*/
const unselectNode = (cache: GeometryCache, node: NodeGeometry, id: NodeId) => {
cache.selectedNodes.delete(id)

node.background.attribs!.fill = undefined
}

/**
* Handle a mouseUp event
*
* @param ctx The context to re-render to.
*/
export const onMouseUp = (ctx: CanvasRenderingContext2D) => (
event: MouseEvent
) => (cache: GeometryCache) => () => {
const mouse = [event.pageX, event.pageY]
const transform = getMouseTransform(ctx)
const transform = getMouseTransform(ctx, cache)
const mousePosition = mulV23(null, transform, mouse)

// const target = getMouseTarget(mousePosition, cache)
// const nodes = [...cache.nodes.values()]

for (const [id, node] of cache.nodes) {
unselectNode(cache, node, id)
}

renderScene(ctx, cache)
}

/**
* Handle a mouseDown event
*
* @param ctx The context to re-render to.
*/
export const onMouseDown = (ctx: CanvasRenderingContext2D) => (
event: MouseEvent
) => (cache: GeometryCache) => () => {
const mouse = [event.pageX, event.pageY]
const transform = getMouseTransform(ctx, cache)
const mousePosition = mulV23(null, transform, mouse)

const target = getMouseTarget(mousePosition, cache)
const nodes = [...cache.nodes.values()]

if (target._type === MouseTargetKind.Node) {
selectNode(cache, target.node, target.id)
}

renderScene(ctx, cache)
}
/**
* Move a single node by a certain offset
*
* @param geom The geometry to move
* @param offset The offset to move the geometry by.
*/
export const moveNode = (geom: NodeGeometry, offset: Vec) => {
add2(null, geom.position, offset)
}

/**
* Move a all selected nodes by some offset.
*
* @param cache The cache to mutate.
* @param offset Amount to move.
*/
const moveNodes = (cache: GeometryCache, offset: Vec) => {
for (const id of cache.selectedNodes) {
moveNode(cache.nodes.get(id)!, offset)
}
}

const pan = (cache: GeometryCache, offset: Vec) => {
concat(null, cache.camera, translation23([], offset))
}

/**
* Handle a mouseMove event
Expand All @@ -330,8 +418,9 @@ export const onMouseMove = (ctx: CanvasRenderingContext2D) => (
event: MouseEvent
) => (cache: GeometryCache) => () => {
const mouse = [event.pageX, event.pageY]
const transform = getMouseTransform(ctx)
const transform = getMouseTransform(ctx, cache)
const mousePosition = mulV23(null, transform, mouse)
const pressed = isPressed(event.buttons)

const target = getMouseTarget(mousePosition, cache)
const nodes = [...cache.nodes.values()]
Expand Down Expand Up @@ -397,5 +486,17 @@ export const onMouseMove = (ctx: CanvasRenderingContext2D) => (
cache.selectedNode = null
}

const mouseScreenOffset = [event.movementX, event.movementY]

// Handle dragging nodes
if (pressed(MouseButtons.LeftButton)) {
moveNodes(cache, mouseScreenOffset)
}

// Handle panning
if (pressed(MouseButtons.RightButton)) {
pan(cache, mouseScreenOffset)
}

renderScene(ctx, cache)
}
4 changes: 3 additions & 1 deletion src/typescript/types/Node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IHiccupShape, IShape } from "@thi.ng/geom-api"
import type { Mat23Like } from "@thi.ng/matrices"
import { Circle, Arc } from "@thi.ng/geom"
import { Vec } from "@thi.ng/vectors"

export interface InputData {
color: string
Expand All @@ -19,7 +20,8 @@ export type Effect<T> = () => T
export interface NodeGeometry {
background: Circle
output: Circle
inputs: IHiccupShape[]
inputs: (Circle | Arc)[]
position: Vec
}

export type GeometryCache = {
Expand Down

0 comments on commit f014b13

Please sign in to comment.