Skip to content

Commit

Permalink
feat: add event manager
Browse files Browse the repository at this point in the history
  • Loading branch information
deemaagog committed Apr 25, 2024
1 parent c31d841 commit a9c3703
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 67 deletions.
3 changes: 2 additions & 1 deletion docs/src/components/Demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import SvgRenderer from "@score-storm/svg-renderer"
function Demo({ renderer = "canvas", musicXml = undefined, bordered = false, scale = 100 }) {
const rootElementRef = useRef(null)

const scoreStorm = useRef(new ScoreStorm({scale, /* debug: {bBoxes: true} */ }))
const scoreStorm = useRef()

useEffect(() => {
if (!rootElementRef.current) {
return
}
scoreStorm.current = new ScoreStorm({scale, /* debug: {bBoxes: true} */ })

scoreStorm.current.setRenderer(
renderer === "canvas" ? new CanvasRenderer(rootElementRef.current) : new SvgRenderer(rootElementRef.current),
Expand Down
18 changes: 18 additions & 0 deletions full-featured-editor/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,28 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="preload" href="/fonts/Bravura.woff2" as="font" type="font/woff2" crossorigin>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
@font-face {
font-family: Bravura;
src: url("/fonts/Bravura.woff2") format("woff2");
font-weight: 400;
font-style: normal;
}
#font-preload {
font-family: Bravura;
opacity: 0;
height: 0;
width: 0;
line-height: 1;
/* display:inline-block */
}
</style>
<title>Music sheet editor</title>
</head>
<body>
<div id="font-preload" role="none presentation">.</div>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
22 changes: 3 additions & 19 deletions full-featured-editor/src/App.css
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
@font-face {
font-family: Bravura;
src: url("/src/assets/fonts/Bravura.woff2") format("woff2");
font-weight: 400;
font-style: normal;
}

#preload {
font-family: Bravura;
opacity:0;
height:0;
width:0;
line-height: 1;
/* display:inline-block */
}

#ss-container {
width: 100%;
line-height: 0
width: 100%;
line-height: 0;
}

main {
line-height: 0;
line-height: 0;
}
46 changes: 30 additions & 16 deletions full-featured-editor/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,67 @@ import "@mantine/core/styles.css"
import "./App.css"
import { Button, AppShell, Stack, useMantineTheme } from "@mantine/core"
import { useEffect, useRef, useState } from "react"
import ScoreStorm, { Score } from "@score-storm/core"
import SvgRenderer from "@score-storm/svg-renderer"
import ScoreStorm, { EventType, InteractionEvent, Score, GraphicalClef } from "@score-storm/core"
// import Renderer from "@score-storm/svg-renderer"
import Renderer from "@score-storm/canvas-renderer"

export default function App() {
const theme = useMantineTheme()
const rootElementRef = useRef(null)
const scoreStorm = useRef(
new ScoreStorm({ scale: 100, editor: { enable: true, styles: { hoverColor: theme.colors.blue[6] } } }),
)
const scoreStorm = useRef<ScoreStorm>()
const [removeMeasureDisabled, setRemoveMeasureDisabled] = useState(true)

const handleClick = (event: InteractionEvent) => {
if (event.object instanceof GraphicalClef) {
scoreStorm.current!.getScore().setClef()
scoreStorm.current!.render()
}
}

useEffect(() => {
if (!rootElementRef.current) {
return
}

scoreStorm.current.setRenderer(new SvgRenderer(rootElementRef.current))
scoreStorm.current! = new ScoreStorm({
scale: 100,
editor: { enable: true, styles: { hoverColor: theme.colors.blue[6] } },
})
scoreStorm.current!.setEventListener(EventType.CLICK, handleClick)
scoreStorm.current!.setRenderer(new Renderer(rootElementRef.current))

const score = Score.createQuickScore({ numberOfMeasures: 1, timeSignature: { count: 4, unit: 4 } })
scoreStorm.current.setScore(score)
scoreStorm.current.render()
}, [rootElementRef])
scoreStorm.current!.setScore(score)
scoreStorm.current!.render()

return () => {
// destroy on unmount
scoreStorm.current!.destroy()
}
}, [rootElementRef, theme])

const updateRemoveMeasureDisabled = () => {
const score = scoreStorm.current.getScore()
const score = scoreStorm.current!.getScore()
const numberOfMeasures = score.globalMeasures.length
setRemoveMeasureDisabled(numberOfMeasures === 1)
}

const handleAddMeasureClick = () => {
scoreStorm.current.getScore().addMeasure()
scoreStorm.current.render()
scoreStorm.current!.getScore().addMeasure()
scoreStorm.current!.render()
updateRemoveMeasureDisabled()
}

const handleRemoveMeasureClick = () => {
const score = scoreStorm.current.getScore()
const score = scoreStorm.current!.getScore()
const numberOfMeasures = score.globalMeasures.length
score.removeMeasure(numberOfMeasures - 1)
scoreStorm.current.render()
scoreStorm.current!.render()
updateRemoveMeasureDisabled()
}

return (
<AppShell aside={{ width: 300, breakpoint: "md", collapsed: { desktop: false, mobile: true } }} padding="xl">
<AppShell.Main>
<div id="preload">.</div>
<div id="ss-container" ref={rootElementRef} />
</AppShell.Main>
<AppShell.Aside p="xl">
Expand Down
40 changes: 25 additions & 15 deletions packages/canvas-renderer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BBox, IRenderer, Settings } from "@score-storm/core"
import { IGraphical } from "@score-storm/core"
import { BBox, EventType, IRenderer, Settings } from "@score-storm/core"
import { IGraphical, EventManager } from "@score-storm/core"
import RBush from "rbush"

type SpatialIndexItem = {
Expand All @@ -18,10 +18,10 @@ class CanvasRenderer implements IRenderer {
canvasElement!: HTMLCanvasElement
context!: CanvasRenderingContext2D

// main canvas and it's contenxt
// interaction canvas and it's contenxt
interactionsCanvasElement!: HTMLCanvasElement
interactionsContext!: CanvasRenderingContext2D
objectDetected!: boolean
detectedObject: IGraphical | null = null

// current context
currentContext!: CanvasRenderingContext2D
Expand All @@ -30,6 +30,7 @@ class CanvasRenderer implements IRenderer {
isInitialized: boolean = false

settings!: Settings
eventManager!: EventManager

private spatialSearchTree!: RBush<SpatialIndexItem>

Expand Down Expand Up @@ -67,9 +68,6 @@ class CanvasRenderer implements IRenderer {

this.isInitialized = true

this.spatialSearchTree.clear()

this.objectDetected = true
this.canvasElement.addEventListener("mousemove", (event: MouseEvent) => {
let rect = this.canvasElement.getBoundingClientRect() // TODO: make it a class member and update on resize
let x = event.clientX - rect.left
Expand All @@ -82,27 +80,39 @@ class CanvasRenderer implements IRenderer {
maxY: y,
})
if (result.length) {
if (this.objectDetected) {
if (this.detectedObject) {
return
}
this.canvasElement.style.cursor = "pointer"
this.currentContext = this.interactionsContext
this.canvasElement.style.cursor = "pointer"

this.setColor(this.settings.editor!.styles.hoverColor)
const detectedObject = result[0]
detectedObject.object.render(this, this.settings)
this.objectDetected = true
this.detectedObject = result[0].object
this.detectedObject.render(this, this.settings)
} else {
if (!this.objectDetected) {
if (!this.detectedObject) {
return
}
this.currentContext = this.interactionsContext
this.interactionsContext.clearRect(
0,
0,
this.interactionsCanvasElement.width,
this.interactionsCanvasElement.height,
)
this.canvasElement.style.cursor = "default"
this.objectDetected = false
this.detectedObject = null
}

this.currentContext = this.context
})

this.canvasElement.addEventListener("click", (event: MouseEvent) => {
let rect = this.canvasElement.getBoundingClientRect() // TODO: make it a class member and update on resize
let x = event.clientX - rect.left
let y = event.clientY - rect.top
if (this.detectedObject) {
this.eventManager.dispatch(EventType.CLICK, { x, y, object: this.detectedObject })
}
})

Expand Down Expand Up @@ -142,7 +152,7 @@ class CanvasRenderer implements IRenderer {
}

clear() {
// noop for now
this.spatialSearchTree.clear()
}

postRender(): void {}
Expand Down
23 changes: 14 additions & 9 deletions packages/core/src/BaseRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ScoreStormSettings } from "./ScoreStorm"
import { GraphicalScore } from "./graphical/GraphicalScore"
import { GraphicalMeasure } from "./graphical/GraphicalMeasure"
import { BBox } from "./graphical/interfaces"
import { EventManager } from "./EventManager"

export type Settings = {
fontSize: number
Expand All @@ -28,12 +29,14 @@ const NUMBER_OF_STAFF_LINES = 5 // hardcode for now
*/
class BaseRenderer {
private settings!: Settings
private eventManager!: EventManager
private renderer!: IRenderer
private graphicalScore!: GraphicalScore
private x: number = 0
private y: number = 0

constructor(options?: ScoreStormSettings) {
constructor(eventManager: EventManager, options?: ScoreStormSettings) {
this.eventManager = eventManager
this.setSettings(options)
}

Expand Down Expand Up @@ -61,24 +64,30 @@ class BaseRenderer {
editor: {
enable: false,
styles: {
hoverColor: "royalblue"
hoverColor: "royalblue",
},
...editor,
},
...rest,
}

// this.onMouseMoveHandler = this.onMouseMoveHandler.bind(this)
}

setRenderer(renderer: IRenderer) {
if (this.renderer) {
// eslint-disable-next-line no-console
console.log("destroying...")
this.renderer.destroy()
// this.eventManager.clear()
}
this.renderer = renderer
this.renderer.settings = this.settings // TODO: make settings singleton or use dependency injection

// TODO: make settings singleton or use dependency injection
this.renderer.settings = this.settings
this.renderer.eventManager = this.eventManager
}

destroy() {
this.renderer.destroy()
}

render(score: Score) {
Expand All @@ -92,10 +101,6 @@ class BaseRenderer {
// eslint-disable-next-line no-console
console.log("initializing...")
this.renderer.init()

// if (this.renderer.setOnMouseMoveHandler) {
// this.renderer.setOnMouseMoveHandler(this.onMouseMoveHandler)
// }
}

this.graphicalScore = new GraphicalScore(score, this.renderer.containerWidth, this.settings)
Expand Down
47 changes: 47 additions & 0 deletions packages/core/src/EventManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { IGraphical } from "./graphical/interfaces"

export enum EventType {
HOVER = "hover",
CLICK = "click",
}

export interface InteractionEvent {
x: number
y: number
object: IGraphical | null
}

interface EventMap {
[EventType.HOVER]: InteractionEvent
[EventType.CLICK]: InteractionEvent
}

export type EventListenerCallback<K extends EventType> = (event: EventMap[K]) => void
type ListenerMap<K extends EventType> = { [P in K]?: EventListenerCallback<K>[] }

export class EventManager {
private listenerMap: ListenerMap<EventType> = {}

public on<K extends EventType>(eventType: K, listener: EventListenerCallback<K>) {
const listenerMap: ListenerMap<K> = this.listenerMap
let listeners = listenerMap[eventType]
if (!listeners) {
listeners = []
listenerMap[eventType] = listeners
}
listeners.push(listener)
}

public dispatch<K extends EventType>(eventType: K, event: EventMap[K]) {
const listenerMap: ListenerMap<K> = this.listenerMap
let listeners = listenerMap[eventType]
if (!listeners) {
listeners = []
}
listeners.forEach((l) => l(event))
}

public clear() {
this.listenerMap = {}
}
}
Loading

0 comments on commit a9c3703

Please sign in to comment.