move Framer + Drawer to core
... from vite-vanilla-repl-cm6
This commit is contained in:
parent
9039600f6d
commit
9807c4b7a1
4 changed files with 96 additions and 232 deletions
|
|
@ -29,3 +29,98 @@ Pattern.prototype.onPaint = function (onPaint) {
|
||||||
this.context = { onPaint };
|
this.context = { onPaint };
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const round = (x) => Math.round(x * 1000) / 1000;
|
||||||
|
|
||||||
|
// encapsulates starting and stopping animation frames
|
||||||
|
export class Framer {
|
||||||
|
constructor(onFrame, onError) {
|
||||||
|
this.onFrame = onFrame;
|
||||||
|
this.onError = onError;
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
const self = this;
|
||||||
|
let frame = requestAnimationFrame(function updateHighlights(time) {
|
||||||
|
try {
|
||||||
|
self.onFrame(time);
|
||||||
|
} catch (err) {
|
||||||
|
self.onError(err);
|
||||||
|
}
|
||||||
|
frame = requestAnimationFrame(updateHighlights);
|
||||||
|
});
|
||||||
|
self.cancel = () => {
|
||||||
|
cancelAnimationFrame(frame);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
if (this.cancel) {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncs animation frames to a cyclist scheduler
|
||||||
|
// see vite-vanilla-repl-cm6 for an example
|
||||||
|
export class Drawer {
|
||||||
|
constructor(onDraw, drawTime) {
|
||||||
|
let [lookbehind, lookahead] = drawTime; // e.g. [-2, 2]
|
||||||
|
lookbehind = Math.abs(lookbehind);
|
||||||
|
this.visibleHaps = [];
|
||||||
|
this.lastFrame = null;
|
||||||
|
this.drawTime = drawTime;
|
||||||
|
this.framer = new Framer(
|
||||||
|
() => {
|
||||||
|
if (!this.scheduler) {
|
||||||
|
console.warn('Drawer: no scheduler');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// calculate current frame time (think right side of screen for pianoroll)
|
||||||
|
const phase = this.scheduler.now() + lookahead;
|
||||||
|
// first frame just captures the phase
|
||||||
|
if (this.lastFrame === null) {
|
||||||
|
this.lastFrame = phase;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// query haps from last frame till now. take last 100ms max
|
||||||
|
const haps = this.scheduler.pattern.queryArc(Math.max(this.lastFrame, phase - 1 / 10), phase);
|
||||||
|
this.lastFrame = phase;
|
||||||
|
this.visibleHaps = (this.visibleHaps || [])
|
||||||
|
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
|
||||||
|
.filter((h) => h.whole.end >= phase - lookbehind - lookahead)
|
||||||
|
// add new haps with onset (think right edge bars scrolling in)
|
||||||
|
.concat(haps.filter((h) => h.hasOnset()));
|
||||||
|
const time = phase - lookahead;
|
||||||
|
onDraw(this.visibleHaps, time, this);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.warn('draw error', err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
check() {
|
||||||
|
if (!this.scheduler) {
|
||||||
|
throw new Error('no scheduler set..');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidate() {
|
||||||
|
this.check();
|
||||||
|
const t = this.scheduler.now();
|
||||||
|
let [_, lookahead] = this.drawTime;
|
||||||
|
// remove all future haps
|
||||||
|
this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t);
|
||||||
|
// query future haps
|
||||||
|
const futureHaps = this.scheduler.pattern.queryArc(Math.max(t, 0), t + lookahead + 0.1); // +0.1 = workaround for weird holes in query..
|
||||||
|
// append future haps
|
||||||
|
this.visibleHaps = this.visibleHaps.concat(futureHaps);
|
||||||
|
}
|
||||||
|
start(scheduler) {
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
this.invalidate();
|
||||||
|
this.framer.start();
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
if (this.framer) {
|
||||||
|
this.framer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
const round = (x) => Math.round(x * 1000) / 1000;
|
|
||||||
|
|
||||||
export class Framer {
|
|
||||||
constructor(onFrame, onError) {
|
|
||||||
this.onFrame = onFrame;
|
|
||||||
this.onError = onError;
|
|
||||||
}
|
|
||||||
start() {
|
|
||||||
const self = this;
|
|
||||||
let frame = requestAnimationFrame(function updateHighlights(time) {
|
|
||||||
try {
|
|
||||||
self.onFrame(time);
|
|
||||||
} catch (err) {
|
|
||||||
self.onError(err);
|
|
||||||
}
|
|
||||||
frame = requestAnimationFrame(updateHighlights);
|
|
||||||
});
|
|
||||||
self.cancel = () => {
|
|
||||||
cancelAnimationFrame(frame);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
stop() {
|
|
||||||
if (this.cancel) {
|
|
||||||
this.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Drawer {
|
|
||||||
constructor(onDraw, drawTime) {
|
|
||||||
let [lookbehind, lookahead] = drawTime; // e.g. [-2, 2]
|
|
||||||
lookbehind = Math.abs(lookbehind);
|
|
||||||
this.visibleHaps = [];
|
|
||||||
this.lastFrame = null;
|
|
||||||
this.drawTime = drawTime;
|
|
||||||
this.framer = new Framer(
|
|
||||||
() => {
|
|
||||||
if (!this.scheduler) {
|
|
||||||
console.warn('Drawer: no scheduler');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// calculate current frame time (think right side of screen for pianoroll)
|
|
||||||
const phase = this.scheduler.now() + lookahead;
|
|
||||||
// first frame just captures the phase
|
|
||||||
if (this.lastFrame === null) {
|
|
||||||
this.lastFrame = phase;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// query haps from last frame till now. take last 100ms max
|
|
||||||
const haps = this.scheduler.pattern.queryArc(Math.max(this.lastFrame, phase - 1 / 10), phase);
|
|
||||||
this.lastFrame = phase;
|
|
||||||
this.visibleHaps = (this.visibleHaps || [])
|
|
||||||
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
|
|
||||||
.filter((h) => h.whole.end >= phase - lookbehind - lookahead)
|
|
||||||
// add new haps with onset (think right edge bars scrolling in)
|
|
||||||
.concat(haps.filter((h) => h.hasOnset()));
|
|
||||||
const time = phase - lookahead;
|
|
||||||
onDraw(this.visibleHaps, time, this);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.warn('draw error', err);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
check() {
|
|
||||||
if (!this.scheduler) {
|
|
||||||
throw new Error('no scheduler set..');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
invalidate() {
|
|
||||||
this.check();
|
|
||||||
const t = this.scheduler.now();
|
|
||||||
let [_, lookahead] = this.drawTime;
|
|
||||||
// remove all future haps
|
|
||||||
this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t);
|
|
||||||
// query future haps
|
|
||||||
const futureHaps = this.scheduler.pattern.queryArc(Math.max(t, 0), t + lookahead + 0.1); // +0.1 = workaround for weird holes in query..
|
|
||||||
// append future haps
|
|
||||||
this.visibleHaps = this.visibleHaps.concat(futureHaps);
|
|
||||||
}
|
|
||||||
start(scheduler) {
|
|
||||||
this.scheduler = scheduler;
|
|
||||||
this.invalidate();
|
|
||||||
this.framer.start();
|
|
||||||
}
|
|
||||||
stop() {
|
|
||||||
if (this.framer) {
|
|
||||||
this.framer.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
import { StrudelMirror } from '@strudel/codemirror';
|
import { StrudelMirror } from '@strudel/codemirror';
|
||||||
import { initStrudel } from './strudel';
|
import { initStrudel } from './strudel';
|
||||||
import { Drawer } from './drawer';
|
|
||||||
import { funk42 } from './tunes';
|
import { funk42 } from './tunes';
|
||||||
import { pianoroll, getDrawOptions } from '@strudel.cycles/core';
|
import { pianoroll, getDrawOptions, Drawer } from '@strudel.cycles/core';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
|
|
||||||
const repl = initStrudel();
|
const repl = initStrudel();
|
||||||
|
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import { EditorView } from '@codemirror/view';
|
|
||||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
||||||
import { tags as t } from '@lezer/highlight';
|
|
||||||
|
|
||||||
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
|
||||||
|
|
||||||
const chalky = '#e5c07b',
|
|
||||||
coral = '#e06c75',
|
|
||||||
cyan = '#56b6c2',
|
|
||||||
invalid = '#ffffff',
|
|
||||||
ivory = '#abb2bf',
|
|
||||||
stone = '#7d8799', // Brightened compared to original to increase contrast
|
|
||||||
malibu = '#61afef',
|
|
||||||
sage = '#98c379',
|
|
||||||
whiskey = '#d19a66',
|
|
||||||
violet = '#c678dd',
|
|
||||||
darkBackground = '#21252b',
|
|
||||||
highlightBackground = '#2c313a',
|
|
||||||
background = '#282c34',
|
|
||||||
tooltipBackground = '#353a42',
|
|
||||||
selection = '#3E4451',
|
|
||||||
cursor = '#528bff';
|
|
||||||
|
|
||||||
/// The colors used in the theme, as CSS color strings.
|
|
||||||
export const color = {
|
|
||||||
chalky,
|
|
||||||
coral,
|
|
||||||
cyan,
|
|
||||||
invalid,
|
|
||||||
ivory,
|
|
||||||
stone,
|
|
||||||
malibu,
|
|
||||||
sage,
|
|
||||||
whiskey,
|
|
||||||
violet,
|
|
||||||
darkBackground,
|
|
||||||
highlightBackground,
|
|
||||||
background,
|
|
||||||
tooltipBackground,
|
|
||||||
selection,
|
|
||||||
cursor,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The editor theme styles for One Dark.
|
|
||||||
export const oneDarkTheme = EditorView.theme(
|
|
||||||
{
|
|
||||||
'&': {
|
|
||||||
color: ivory,
|
|
||||||
backgroundColor: background,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-content': {
|
|
||||||
caretColor: cursor,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
|
||||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
|
||||||
{ backgroundColor: selection },
|
|
||||||
|
|
||||||
'.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
|
||||||
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
|
||||||
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
|
||||||
|
|
||||||
'.cm-searchMatch': {
|
|
||||||
backgroundColor: '#72a1ff59',
|
|
||||||
outline: '1px solid #457dff',
|
|
||||||
},
|
|
||||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
|
||||||
backgroundColor: '#6199ff2f',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
|
||||||
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
|
||||||
|
|
||||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
|
||||||
backgroundColor: '#bad0f847',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-gutters': {
|
|
||||||
backgroundColor: background,
|
|
||||||
color: stone,
|
|
||||||
border: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-activeLineGutter': {
|
|
||||||
backgroundColor: highlightBackground,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-foldPlaceholder': {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
color: '#ddd',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-tooltip': {
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: tooltipBackground,
|
|
||||||
},
|
|
||||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
|
||||||
borderTopColor: 'transparent',
|
|
||||||
borderBottomColor: 'transparent',
|
|
||||||
},
|
|
||||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
|
||||||
borderTopColor: tooltipBackground,
|
|
||||||
borderBottomColor: tooltipBackground,
|
|
||||||
},
|
|
||||||
'.cm-tooltip-autocomplete': {
|
|
||||||
'& > ul > li[aria-selected]': {
|
|
||||||
backgroundColor: highlightBackground,
|
|
||||||
color: ivory,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ dark: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
/// The highlighting style for code in the One Dark theme.
|
|
||||||
export const oneDarkHighlightStyle = HighlightStyle.define([
|
|
||||||
{ tag: t.keyword, color: violet },
|
|
||||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
|
|
||||||
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
|
|
||||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
|
|
||||||
{ tag: [t.definition(t.name), t.separator], color: ivory },
|
|
||||||
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
|
|
||||||
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
|
|
||||||
{ tag: [t.meta, t.comment], color: stone },
|
|
||||||
{ tag: t.strong, fontWeight: 'bold' },
|
|
||||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
|
||||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
|
||||||
{ tag: t.link, color: stone, textDecoration: 'underline' },
|
|
||||||
{ tag: t.heading, fontWeight: 'bold', color: coral },
|
|
||||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
|
|
||||||
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
|
|
||||||
{ tag: t.invalid, color: invalid },
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// Extension to enable the One Dark theme (both the editor theme and
|
|
||||||
/// the highlight style).
|
|
||||||
export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue